编译链接实战elf符号表
Posted 奇妙之二进制
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编译链接实战elf符号表相关的知识,希望对你有一定的参考价值。
文章目录
前面介绍了elf文件的两种视图,以及两种视图的各自几个组成部分:
elf文件有两种视图,链接视图和执行视图。在链接视图里,elf文件被划分成了elf 头、节头表、若干的节(section);在执行视图里,elf文件被划分成了elf头、程序头表、段(segment)。
可以看到,段实际上包含了若干个节,执行视图是给程序启动用的,用于指示如何加载程序;而链接视图是连接时使用的,用于指示链接器如何链接文件。
节头表就是一个表,可以理解成一个结构体数组,每一项用于描述一个节。每个我们今天来看下其中的一个节【符号表】。
今天我们关注**.symtab和.dynsym**这两个节。
动态符号表 (.dynsym) 用来保存与动态链接相关的导入导出符号,不包括模块内部的符号。
而 .symtab 则保存所有符号,包括 .dynsym 中的符号。
符号的概念
什么是符号?我们将函数和全局变量(static局部变量也算)统称为符号(Symbol),函数名和变量名就是符号名(Symbol Name),我们可以将符号看做是链接中的粘合剂,整个链接过程正是基于符号才能够正确完成。每个目标文件都会有一个符号表(Symbol Table),即上图的.symtab段,这个表里记录了目标文件所用到的所有符号。每个定义的符号有一个对应的值,叫做符号值(Symbol Value),对于变量和函数来说,符号值就是它们的地址。
在本目标文件中引用的全局符号,却没有定义在本目标文件中,一般叫做外部符号。
实际上段名(比如.text),汇编语言里的标签(比如_start、_edata、_end),源文件名也是符号。
备注:局部变量临时分配在栈中,不会在过程外被引用,所以不会产生符号。
弱符号
符号可以分为强符号和弱符号。
- 强符号:函数名、初始化的全局变量名;
- 弱符号:未初始化的全局变量名。
符号表探索
我们来写一个测试程序:
oop.c:
#include <stdio.h>
int g_a = 10;
static int g_s_b;
static int g_s_c = 0;
int g_d;
__thread int g_tls_e;
static __thread int g_tls_s_f;
__thread int g_tls_g = 0;
extern int g_e_h;
extern void fun1();
void fun()
int l_i;
g_d = 10;
static int l_s_i;
g_e_h= 10;//外部变量和函数如果不使用的话,编译器不会为其产生符号表,因为会优化
fun1();
printf("hello\\n");
return;
//定义一个弱函数
__attribute__((weak)) void fun_weak()
return;
该程序定义了若干个变量用于覆盖所有情况。
前缀g表示全局,l表示局部,s表示静态,tls表示线程局部数据,e表示extern。
进行编译而不链接:
gcc oop.c -c
得到oop.o可重定位对象文件,前面说过,也是elf格式文件。
查看其节头表:
$ readelf -S oop.o --wide
There are 15 section headers, starting at offset 0x458:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 0000000000000000 000040 000043 00 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000330 000078 18 I 12 1 8
[ 3] .data PROGBITS 0000000000000000 000084 000004 00 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000088 000010 00 WA 0 0 4
[ 5] .tbss NOBITS 0000000000000000 000088 00000c 00 WAT 0 0 4
[ 6] .rodata PROGBITS 0000000000000000 000088 000006 00 A 0 0 1
[ 7] .comment PROGBITS 0000000000000000 00008e 00002c 01 MS 0 0 1
[ 8] .note.GNU-stack PROGBITS 0000000000000000 0000ba 000000 00 0 0 1
[ 9] .note.gnu.property NOTE 0000000000000000 0000c0 000020 00 A 0 0 8
[10] .eh_frame PROGBITS 0000000000000000 0000e0 000058 00 A 0 0 8
[11] .rela.eh_frame RELA 0000000000000000 0003a8 000030 18 I 12 10 8
[12] .symtab SYMTAB 0000000000000000 000138 000198 18 13 8 8
[13] .strtab STRTAB 0000000000000000 0002d0 00005a 00 0 0 1
[14] .shstrtab STRTAB 0000000000000000 0003d8 00007a 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)
通过readelf的-s选项可以查看符号表:
$ readelf -s oop.o
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS oop.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
3: 0000000000000004 4 OBJECT LOCAL DEFAULT 4 g_s_b
4: 0000000000000008 4 OBJECT LOCAL DEFAULT 4 g_s_c
5: 0000000000000004 4 TLS LOCAL DEFAULT 5 g_tls_s_f
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6 .rodata
7: 000000000000000c 4 OBJECT LOCAL DEFAULT 4 l_s_i.0
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 g_a
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 4 g_d
10: 0000000000000000 4 TLS GLOBAL DEFAULT 5 g_tls_e
11: 0000000000000008 4 TLS GLOBAL DEFAULT 5 g_tls_g
12: 0000000000000000 56 FUNC GLOBAL DEFAULT 1 fun
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND g_e_h
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND fun1
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
16: 0000000000000038 11 FUNC WEAK DEFAULT 1 fun_weak
strip之后就查询不到静态符号表信息了,但是动态符号表信息还是可以查到的。
可以看到该符号表包含了17项(entry),每一项用Elf32_Sym结构体描述:
typedef struct
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
Elf32_Sym;
每一列的值其实都和结构体的成员相对应。
-
Name 符号的名字
-
Ndx 表示其所在section的序号,初始化过的全局变量g_a肯定在数据段.data,函数在代码段.text,【未经初始化的全局变量】和static变量在.bss段(static不管是否初始化都在bss段)。由于.bss段在程序启动时会被清0,所以未初始化的全局变量和static变量的值是0。
对于在本文件中引用到,但是在其他文件定义的符号,其Ndx为UND,例如本例的extern变量和函数。在链接阶段,找到该符号的定义之后,会对其Ndx进行修正。 -
Value 该符号的值(注意不是变量的值),符号的值就是指符号的地址,对于编译的中间文件.ol来说,value其实是该符号在其所在的section的偏移,是个相对值;对于链接后的可执行文件来说,该值是个绝对值,表示符号的绝对地址。
-
Type 表示符号的类型,常见的几种类型如下:
484 #define STT_NOTYPE 0 /* Symbol type is unspecified */
485 #define STT_OBJECT 1 /* Symbol is a data object */
486 #define STT_FUNC 2 /* Symbol is a code object */
487 #define STT_SECTION 3 /* Symbol associated with a section */
488 #define STT_FILE 4 /* Symbol's name is file name */
489 #define STT_COMMON 5 /* Symbol is a common data object */
490 #define STT_TLS 6 /* Symbol is thread-local data object*/
-
OBJECT 表示全局变量和static变量
FUNC 表示函数
FILE 文件名
COM 公用全局变量
TLS 线程本地数据 -
Bind列有以下几个常见值:
472 #define STB_LOCAL 0 /* Local symbol */
473 #define STB_GLOBAL 1 /* Global symbol */
474 #define STB_WEAK 2 /* Weak symbol */
分别表示局部、全局、弱符号。
- Vis表示该符号的可见范围,visibility的缩写:
/* Symbol visibility specification encoded in the st_other field. */
#define STV_DEFAULT 0 /* Default symbol visibility rules */
#define STV_INTERNAL 1 /* Processor specific hidden class */
#define STV_HIDDEN 2 /* Sym unavailable in other modules */
#define STV_PROTECTED 3 /* Not preemptible, not exported */
以上是关于编译链接实战elf符号表的主要内容,如果未能解决你的问题,请参考以下文章