概念
符号(Symbel):函数和变量
符号名(Symbel Name):函数名和变量名
每个目标文件中都有符号表(Synbel Table),在上篇文章中提到的段表中可以找到它:
其中包含有:
- 全局符号,包括本文件中的函数,以及全局变量
- 外部符号,本目标文件中引用但未定义的符号
- 局部符号,主要为局部变量,在链接过程中不起作用
- 段名(如".text",".bss"),由编译器产生,其值为该段的起始地址。
- 行号信息,目标文件指令对应源码中的行号
链接过程主要针对上述前两种类型
ELF符号表结构
符号表保存在".symtab"段中,为一个 Elf32_Sym 结构体数组,该结构体的定义如下
代码段1
/*./include/linux/elf.h +178*/
typedef struct elf32_sym{
Elf32_Word st_name; //符号名在字符串表(elf文件中的".strtab"段)中的下标
Elf32_Addr st_value; //通常是符号在其所在段中的偏移地址,有时候是一个值
Elf32_Word st_size; //符号大小。如double型的符号(全局变量等),st_size为 8 byte。
unsigned char st_info; //符号类型(包括数据,函数,段,当前elf对应的源文件名等),
//和绑定信息(包括局部,全局,弱引用)
unsigned char st_other; //
Elf32_Half st_shndx; //符号所在段的下标
} Elf32_Sym;
使readelf -s
可以查看符号表,每一行即为一个Elf32_Sym 结构体,如下
代码段2
zqxl@ubuntu:/work/myprojects/code3-1$ readelf -s simple_section.o
Symbol table '.symtab' contains 19 entries:
Num: Value Size Type Bind Vis Ndx Name
//符号表 大小 所在段
//数组下标 符号值 (字节) 类型 绑定信息 下标 符号名
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS simple_section.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 var.1803
8: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 var2.1804
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 9
11: 0000000000000000 0 SECTION LOCAL DEFAULT 7
12: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var
13: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM g_uninit_var
14: 0000000000000000 4 OBJECT GLOBAL DEFAULT 5 var
15: 0000000000000000 36 FUNC GLOBAL DEFAULT 1 func1
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
17: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
18: 0000000000000024 43 FUNC GLOBAL DEFAULT 1 main
查看上边的符号表信息时可以对照段表结构(见本文附录)。如符号表下标为7的符号为已初始化的静态局部变量var(见附录源码);该符号的 Ndx 为3,从段表结构可以看出该段为.data段。又如未初始化的静态局部变量位于4段,即.bss段。
值得注意的是,未初始化的全局变量g_uninit_var 以及静态局部变量的 Ndx 是“COM”,即.comment段,而不是预想的.bss段。这与编译器有关。这里的g_uninit_var目前只是一个符号,等最终链接生成了可执行文件,才会在.bss段分配空间
强/弱符号
-
定义
编译器默认将函数和初始化了的全局变量作为强符号,未初始化的全局变量为弱符号。 -
规则
- 同名强符号只能有一个,但是同名弱符号可以同时存在多个。
- 编译时优先使用强符号
- 没有强符号,只有多个弱符号时,优先使用占用内存较大的那个弱符号
-
举例
在不同的文件中定义两个同名全局变量将导致符号重复定义错误。但是如果将其中一个定义为弱符号,将不会报错,如下:
在code1.c文件中:
int g_var=1;
在code2.c文件中:
__attribute__((weak)) int g_var=1;
这样将编译通过。
强/弱引用
对于强引用,链接器找不到符号的定义就会报错;对于弱引用则不会。同时,弱引用的符号如果未被定义,会被赋值为0 或者一个特殊值。
附录 源码
int printf(const char *format,...);
int global_init_var=90;
int g_uninit_var;
__attribute__((section("Test_Section"))) int var=126;
void func1(int i)
{
printf("a=%d\n",i);
}
int main(void)
{
static int var = 89;
static int var2;
int a=1;
int b;
func1(var + var2);
return 0;
}
附录 段表结构
使用readelf -S
查看对应的段表结构:
代码段3
zqxl@ubuntu:/work/myprojects/code3-1$ readelf -S simple_section.o
There are 14 section headers, starting at offset 0x478:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000004f 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000360
0000000000000078 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 00000090
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 00000098
0000000000000004 0000000000000000 WA 0 0 4
[ 5] TestSection PROGBITS 0000000000000000 00000098
0000000000000004 0000000000000000 WA 0 0 4
[ 6] .rodata PROGBITS 0000000000000000 0000009c
0000000000000006 0000000000000000 A 0 0 1
[ 7] .comment PROGBITS 0000000000000000 000000a2
000000000000002c 0000000000000001 MS 0 0 1
[ 8] .note.GNU-stack PROGBITS 0000000000000000 000000ce
0000000000000000 0000000000000000 0 0 1
[ 9] .eh_frame PROGBITS 0000000000000000 000000d0
0000000000000058 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 000003d8
0000000000000030 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 00000128
00000000000001c8 0000000000000018 12 12 8
[12] .strtab STRTAB 0000000000000000 000002f0
000000000000006a 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 00000408
000000000000006d 0000000000000000 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),
l (large), p (processor specific)