目标文件

目标文件就是链接前的.o文件或者其他格式的二进制文件,通过链接可以生成可执行文件。

目标文件的格式

现在流行的可执行文件格式(Executable)主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),都是COFF(Common file format)的变种。目标文件就是源代码编译后但未进行链接的那些中间文件(Windows的.obj和Linux的.o),因为和可执行文件结构相似,也用相同的结构存储。

静态链接库和动态链接库同样用可执行文件的格式存储。静态链接库稍有不同,它把很多目标文件绑定在一起,再加上一些索引,可以简单理解为一个包含有很多目标文件的文件包。

ELF格式主要有四类:

ELF文件类型 说明 实例
可重定位文件(Relocatable File) 这类文件包含了代码和数据,可以被用来链接成为可执行文件和共享目标文件,静态链接库也归于这一类 Linux的.o
可执行文件(Executable File) 这类文件包含了可以直接执行的程序,它的代表是ELF可执行文件 /bin/bash
共享目标文件(Shared Object File) 这种文件包含了代码和数据,可以在两种情况下使用。一种是链接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,第二帧是动态链接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像的一部分来执行 Linux的.so
核心转换文件(Core Dump File) 当进程意外终止的时候,系统可以将该进程的地址空间的内容及终止时的一些其他信息转存到核心转储文件 Linux的Core Dump

Linux下使用file命令来查看对应的文件格式。

目标文件是什么样的

目标文件不仅包含了编译后的代码和数据,还包括了链接时所需要的一些信息,例如符号表、调试信息、字符串等。根据这些信息的不同属性,以Section(翻译为节或段,书中用段)的形式存储。

程序编译后机器指令会放在代码段,代码段一般名字为”.code”或”.text”。已经初始化的全局变量和局部静态变量经常放在数据段”.data”,而未初始化的全局变量和局部静态变量一般放在一个叫做”.bss”的段里面,因为未初始化的全局变量和局部静态变量默认值都为0,本来它们也可以被放在.data段的,但是因为他们都是0,所以没有必要在.data段中分配空间存储,但是在程序运行过程中它们确实要占内存空间的,并且可执行文件必须记录所有未初始化的全局变量和局部静态变量的大小总和,记作.bss段,所以.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,并没有内容,在文件中不占据空间。

example

书本提供了如下的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
* simple_section.c
*
* gcc -c simple_section.c
*/

int printf(const char* format, ...);

int global_init_var = 84;
int global_uninit_var;
static int global_static_var;

extern int reference_to_out;

void func1(int i) {
printf("%d\n", i);
}

int main(void) {
static int static_var = 85;
static int static_var2;

int a = 1;
int b;

func1(static_var + static_var2 + a + b);

return a;
}

gcc -c simple_section.c 得到simple_section.o,然后用objdump查看:

1
objdump -h simple_section.o

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
simple_section.o:     file format elf64-x86-64

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000057 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 0000000000000000 0000000000000000 00000098 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000008 0000000000000000 0000000000000000 000000a0 2**2
ALLOC
3 .rodata 00000004 0000000000000000 0000000000000000 000000a0 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002a 0000000000000000 0000000000000000 000000a4 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000ce 2**0
CONTENTS, READONLY
6 .eh_frame 00000058 0000000000000000 0000000000000000 000000d0 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

由于编译器版本和架构的问题,输出的结果和书本上的不完全一样,但是大部分是相同的。

除了代码段,数据段和BSS段之外,还有只读数据段(.rodata),注释信息段(.comment)和堆栈提示段(.note.GNU-stack)。eh_frame段在书中没有,这个应该是后来新增的段,暂时不研究。

注意几个段的属性,最容易理解的是段的长度(Size)和段所在的位置(File offset),每个段的第二行中的“CONTENTS, ALLOC, LOAD, DATA”等表示段的各种属性。CONTENTS表示该段在文件中存在。

执行size simple_section.o,得到:

1
2
text    data     bss     dec     hex filename
179 8 8 195 c3 simple_section.o

在这里发现size命令和objdump显示的.text段大小不一样,查了一下找到了一篇文章:

https://blog.csdn.net/Little_ant_/article/details/119214033

为什么 size 和 objdump 查看目标文件的 .text 段的大小不一样呢?
因为size默认是运行在”Berkeley compatibility mode”下。在这种模式下,会将不可执行的拥有”ALLOC”属性的只读段归到.text段下,很典型的就是.rodata段。而在我们这个例子中,使用 size 命令得到的 text 段长度 = .text + .rodata + .eh_frame 。如果你使用”size -A obj.o”,那么size会运行在”System V compatibility mode”,此时,用objdump -h和size显示的.text段大小就差不多了

1
2
3
4
5
6
7
8
9
10
11
size -A simple_section.o 
simple_section.o :
section size addr
.text 87 0
.data 8 0
.bss 8 0
.rodata 4 0
.comment 42 0
.note.GNU-stack 0 0
.eh_frame 88 0
Total 237

代码段

objdump -s -d simple_section.o得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
simple_section.o:     file format elf64-x86-64

Contents of section .text:
0000 554889e5 4883ec10 897dfc8b 45fc89c6 UH..H....}..E...
0010 488d3d00 000000b8 00000000 e8000000 H.=.............
0020 0090c9c3 554889e5 4883ec10 c745f801 ....UH..H....E..
0030 0000008b 15000000 008b0500 00000001 ................
0040 c28b45f8 01c28b45 fc01d089 c7e80000 ..E....E........
0050 00008b45 f8c9c3 ...E...
Contents of section .data:
0000 54000000 55000000 T...U...
Contents of section .rodata:
0000 25640a00 %d..
Contents of section .comment:
0000 00474343 3a202855 62756e74 7520372e .GCC: (Ubuntu 7.
0010 352e302d 33756275 6e747531 7e31382e 5.0-3ubuntu1~18.
0020 30342920 372e352e 3000 04) 7.5.0.
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 24000000 00410e10 8602430d ....$....A....C.
0030 065f0c07 08000000 1c000000 3c000000 ._..........<...
0040 00000000 33000000 00410e10 8602430d ....3....A....C.
0050 066e0c07 08000000 .n......

Disassembly of section .text:

0000000000000000 <func1>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 89 7d fc mov %edi,-0x4(%rbp)
b: 8b 45 fc mov -0x4(%rbp),%eax
e: 89 c6 mov %eax,%esi
10: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 17 <func1+0x17>
17: b8 00 00 00 00 mov $0x0,%eax
1c: e8 00 00 00 00 callq 21 <func1+0x21>
21: 90 nop
22: c9 leaveq
23: c3 retq

0000000000000024 <main>:
24: 55 push %rbp
25: 48 89 e5 mov %rsp,%rbp
28: 48 83 ec 10 sub $0x10,%rsp
2c: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp)
33: 8b 15 00 00 00 00 mov 0x0(%rip),%edx # 39 <main+0x15>
39: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 3f <main+0x1b>
3f: 01 c2 add %eax,%edx
41: 8b 45 f8 mov -0x8(%rbp),%eax
44: 01 c2 add %eax,%edx
46: 8b 45 fc mov -0x4(%rbp),%eax
49: 01 d0 add %edx,%eax
4b: 89 c7 mov %eax,%edi
4d: e8 00 00 00 00 callq 52 <main+0x2e>
52: 8b 45 f8 mov -0x8(%rbp),%eax
55: c9 leaveq
56: c3 retq

objdump的-s参数可以将所有段的内容以十六进制的形式打印出来,-d则可以将所有包含指令的段进行反汇编。

数据段和只读数据段

.data段保存的是那些已经初始化了的全局静态变量和局部静态变量

在上面打印的内容中,与.data相关的有:

1
2
3
4
Contents of section .data:
0000 54000000 55000000 T...U...
Contents of section .rodata:
0000 25640a00 %d..

.data段里前四个字节0x54,0x00,0x00,0x00,这是小端模式,所以就是0x00000054,也就是84,这就是simple_section.c第一个全局静态变量,值为84。

BSS段

objdump -s -x -d simple_section.o得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
simple_section.o:     file format elf64-x86-64
simple_section.o
architecture: i386:x86-64, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x0000000000000000

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000057 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 0000000000000000 0000000000000000 00000098 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000008 0000000000000000 0000000000000000 000000a0 2**2
ALLOC
3 .rodata 00000004 0000000000000000 0000000000000000 000000a0 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002a 0000000000000000 0000000000000000 000000a4 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000ce 2**0
CONTENTS, READONLY
6 .eh_frame 00000058 0000000000000000 0000000000000000 000000d0 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 simple_section.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l O .bss 0000000000000004 global_static_var
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000004 l O .data 0000000000000004 static_var.1804
0000000000000004 l O .bss 0000000000000004 static_var2.1805
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g O .data 0000000000000004 global_init_var
0000000000000004 O *COM* 0000000000000004 global_uninit_var
0000000000000000 g F .text 0000000000000024 func1
0000000000000000 *UND* 0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000000 *UND* 0000000000000000 printf
0000000000000024 g F .text 0000000000000033 main


Contents of section .text:
0000 554889e5 4883ec10 897dfc8b 45fc89c6 UH..H....}..E...
0010 488d3d00 000000b8 00000000 e8000000 H.=.............
0020 0090c9c3 554889e5 4883ec10 c745f801 ....UH..H....E..
0030 0000008b 15000000 008b0500 00000001 ................
0040 c28b45f8 01c28b45 fc01d089 c7e80000 ..E....E........
0050 00008b45 f8c9c3 ...E...
Contents of section .data:
0000 54000000 55000000 T...U...
Contents of section .rodata:
0000 25640a00 %d..
Contents of section .comment:
0000 00474343 3a202855 62756e74 7520372e .GCC: (Ubuntu 7.
0010 352e302d 33756275 6e747531 7e31382e 5.0-3ubuntu1~18.
0020 30342920 372e352e 3000 04) 7.5.0.
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 24000000 00410e10 8602430d ....$....A....C.
0030 065f0c07 08000000 1c000000 3c000000 ._..........<...
0040 00000000 33000000 00410e10 8602430d ....3....A....C.
0050 066e0c07 08000000 .n......

Disassembly of section .text:

0000000000000000 <func1>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 89 7d fc mov %edi,-0x4(%rbp)
b: 8b 45 fc mov -0x4(%rbp),%eax
e: 89 c6 mov %eax,%esi
10: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 17 <func1+0x17>
13: R_X86_64_PC32 .rodata-0x4
17: b8 00 00 00 00 mov $0x0,%eax
1c: e8 00 00 00 00 callq 21 <func1+0x21>
1d: R_X86_64_PLT32 printf-0x4
21: 90 nop
22: c9 leaveq
23: c3 retq

0000000000000024 <main>:
24: 55 push %rbp
25: 48 89 e5 mov %rsp,%rbp
28: 48 83 ec 10 sub $0x10,%rsp
2c: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp)
33: 8b 15 00 00 00 00 mov 0x0(%rip),%edx # 39 <main+0x15>
35: R_X86_64_PC32 .data
39: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 3f <main+0x1b>
3b: R_X86_64_PC32 .bss
3f: 01 c2 add %eax,%edx
41: 8b 45 f8 mov -0x8(%rbp),%eax
44: 01 c2 add %eax,%edx
46: 8b 45 fc mov -0x4(%rbp),%eax
49: 01 d0 add %edx,%eax
4b: 89 c7 mov %eax,%edi
4d: e8 00 00 00 00 callq 52 <main+0x2e>
4e: R_X86_64_PC32 func1-0x4
52: 8b 45 f8 mov -0x8(%rbp),%eax
55: c9 leaveq
56: c3 retq

与书上不完全相同,我的编译器输出的符号表里未初始化的全局变量和静态变量都在BSS段中,

1
2
2 .bss          00000008  0000000000000000  0000000000000000  000000a0  2**2
ALLOC

大小为8个字节(两个int)

其他段

还可以用objcopy将二进制文件作为目标文件的一个段,也可以自定义段,如下:

ELF文件结构

文件头

执行如下readelf -h simple_section.o ,输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
readelf -h simple_section.o 
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 1144 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 13
Section header string table index: 12

在/usr/include/elf.h中可以找到与之对应的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/* Type for a 16-bit quantity.  */
typedef uint16_t Elf32_Half;
typedef uint16_t Elf64_Half;

/* Types for signed and unsigned 32-bit quantities. */
typedef uint32_t Elf32_Word;
typedef int32_t Elf32_Sword;
typedef uint32_t Elf64_Word;
typedef int32_t Elf64_Sword;

/* Types for signed and unsigned 64-bit quantities. */
typedef uint64_t Elf32_Xword;
typedef int64_t Elf32_Sxword;
typedef uint64_t Elf64_Xword;
typedef int64_t Elf64_Sxword;

/* Type of addresses. */
typedef uint32_t Elf32_Addr;
typedef uint64_t Elf64_Addr;

/* Type of file offsets. */
typedef uint32_t Elf32_Off;
typedef uint64_t Elf64_Off;
...

#define EI_NIDENT (16)

typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;

typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;

可以看到,根据机器的字长,定义了Elf32_Ehdr和Elf64_Ehdr,两个结构体的变量相同,就是部分变量的长度不同。readelf的结果除了e_ident之外都可以与Elf32_Ehdr和Elf64_Ehdr中的成员变量对应

  • 魔数

    e_ident有16个元素,unsigned char类型,正好存储了16个字节的Magic(魔数)。

    最开始的四个字节必须是0x7f 0x45 0x4c 0x46,接下来一个字节是表示ELF文件类的,0x01是32位,0x02是64位。第六个字节是大小端,第七个字节规定ELF文件的主版本号,一般是1,因为没有更新。后面的九个字节没有定义,一般填0,有些平台会扩展。

  • 文件类型

    就是三种ELF文件:可重定位文件,可执行文件和共享目标文件,还有提到过的CORE DUMP,不过这个似乎不太常见

1
2
3
4
5
6
#define ET_NONE		0		/* No file type */
#define ET_REL 1 /* Relocatable file */
#define ET_EXEC 2 /* Executable file */
#define ET_DYN 3 /* Shared object file */
#define ET_CORE 4 /* Core file */
#define ET_NUM 5 /* Number of defined types */
  • 机器类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/* Legal values for e_type (object file type).  */

#define ET_NONE 0 /* No file type */
#define ET_REL 1 /* Relocatable file */
#define ET_EXEC 2 /* Executable file */
#define ET_DYN 3 /* Shared object file */
#define ET_CORE 4 /* Core file */
#define ET_NUM 5 /* Number of defined types */
#define ET_LOOS 0xfe00 /* OS-specific range start */
#define ET_HIOS 0xfeff /* OS-specific range end */
#define ET_LOPROC 0xff00 /* Processor-specific range start */
#define ET_HIPROC 0xffff /* Processor-specific range end */

/* Legal values for e_machine (architecture). */

#define EM_NONE 0 /* No machine */
#define EM_M32 1 /* AT&T WE 32100 */
#define EM_SPARC 2 /* SUN SPARC */
#define EM_386 3 /* Intel 80386 */
#define EM_68K 4 /* Motorola m68k family */
#define EM_88K 5 /* Motorola m88k family */
#define EM_IAMCU 6 /* Intel MCU */
#define EM_860 7 /* Intel 80860 */
#define EM_MIPS 8 /* MIPS R3000 big-endian */
#define EM_S370 9 /* IBM System/370 */
#define EM_MIPS_RS3_LE 10 /* MIPS R3000 little-endian */
/* reserved 11-14 */
#define EM_PARISC 15 /* HPPA */
/* reserved 16 */
#define EM_VPP500 17 /* Fujitsu VPP500 */
#define EM_SPARC32PLUS 18 /* Sun's "v8plus" */
#define EM_960 19 /* Intel 80960 */
#define EM_PPC 20 /* PowerPC */
#define EM_PPC64 21 /* PowerPC 64-bit */
#define EM_S390 22 /* IBM S390 */
#define EM_SPU 23 /* IBM SPU/SPC */
/* reserved 24-35 */
#define EM_V800 36 /* NEC V800 series */
#define EM_FR20 37 /* Fujitsu FR20 */
#define EM_RH32 38 /* TRW RH-32 */
#define EM_RCE 39 /* Motorola RCE */
#define EM_ARM 40 /* ARM */
#define EM_FAKE_ALPHA 41 /* Digital Alpha */
#define EM_SH 42 /* Hitachi SH */
#define EM_SPARCV9 43 /* SPARC v9 64-bit */
#define EM_TRICORE 44 /* Siemens Tricore */
#define EM_ARC 45 /* Argonaut RISC Core */
#define EM_H8_300 46 /* Hitachi H8/300 */
#define EM_H8_300H 47 /* Hitachi H8/300H */
#define EM_H8S 48 /* Hitachi H8S */
#define EM_H8_500 49 /* Hitachi H8/500 */
#define EM_IA_64 50 /* Intel Merced */
#define EM_MIPS_X 51 /* Stanford MIPS-X */
#define EM_COLDFIRE 52 /* Motorola Coldfire */
#define EM_68HC12 53 /* Motorola M68HC12 */
#define EM_MMA 54 /* Fujitsu MMA Multimedia Accelerator */
#define EM_PCP 55 /* Siemens PCP */
#define EM_NCPU 56 /* Sony nCPU embeeded RISC */
#define EM_NDR1 57 /* Denso NDR1 microprocessor */
#define EM_STARCORE 58 /* Motorola Start*Core processor */
#define EM_ME16 59 /* Toyota ME16 processor */
#define EM_ST100 60 /* STMicroelectronic ST100 processor */
#define EM_TINYJ 61 /* Advanced Logic Corp. Tinyj emb.fam */
#define EM_X86_64 62 /* AMD x86-64 architecture */
#define EM_PDSP 63 /* Sony DSP Processor */
#define EM_PDP10 64 /* Digital PDP-10 */
#define EM_PDP11 65 /* Digital PDP-11 */
#define EM_FX66 66 /* Siemens FX66 microcontroller */
#define EM_ST9PLUS 67 /* STMicroelectronics ST9+ 8/16 mc */
#define EM_ST7 68 /* STmicroelectronics ST7 8 bit mc */
#define EM_68HC16 69 /* Motorola MC68HC16 microcontroller */
#define EM_68HC11 70 /* Motorola MC68HC11 microcontroller */
#define EM_68HC08 71 /* Motorola MC68HC08 microcontroller */
#define EM_68HC05 72 /* Motorola MC68HC05 microcontroller */
#define EM_SVX 73 /* Silicon Graphics SVx */
#define EM_ST19 74 /* STMicroelectronics ST19 8 bit mc */
#define EM_VAX 75 /* Digital VAX */
#define EM_CRIS 76 /* Axis Communications 32-bit emb.proc */
#define EM_JAVELIN 77 /* Infineon Technologies 32-bit emb.proc */
#define EM_FIREPATH 78 /* Element 14 64-bit DSP Processor */
#define EM_ZSP 79 /* LSI Logic 16-bit DSP Processor */
#define EM_MMIX 80 /* Donald Knuth's educational 64-bit proc */
#define EM_HUANY 81 /* Harvard University machine-independent object files */
#define EM_PRISM 82 /* SiTera Prism */
#define EM_AVR 83 /* Atmel AVR 8-bit microcontroller */
#define EM_FR30 84 /* Fujitsu FR30 */
#define EM_D10V 85 /* Mitsubishi D10V */
#define EM_D30V 86 /* Mitsubishi D30V */
#define EM_V850 87 /* NEC v850 */
#define EM_M32R 88 /* Mitsubishi M32R */
#define EM_MN10300 89 /* Matsushita MN10300 */
#define EM_MN10200 90 /* Matsushita MN10200 */
#define EM_PJ 91 /* picoJava */
#define EM_OPENRISC 92 /* OpenRISC 32-bit embedded processor */
#define EM_ARC_COMPACT 93 /* ARC International ARCompact */
#define EM_XTENSA 94 /* Tensilica Xtensa Architecture */
#define EM_VIDEOCORE 95 /* Alphamosaic VideoCore */
#define EM_TMM_GPP 96 /* Thompson Multimedia General Purpose Proc */
#define EM_NS32K 97 /* National Semi. 32000 */
#define EM_TPC 98 /* Tenor Network TPC */
#define EM_SNP1K 99 /* Trebia SNP 1000 */
#define EM_ST200 100 /* STMicroelectronics ST200 */
#define EM_IP2K 101 /* Ubicom IP2xxx */
#define EM_MAX 102 /* MAX processor */
#define EM_CR 103 /* National Semi. CompactRISC */
#define EM_F2MC16 104 /* Fujitsu F2MC16 */
#define EM_MSP430 105 /* Texas Instruments msp430 */
#define EM_BLACKFIN 106 /* Analog Devices Blackfin DSP */
#define EM_SE_C33 107 /* Seiko Epson S1C33 family */
#define EM_SEP 108 /* Sharp embedded microprocessor */
#define EM_ARCA 109 /* Arca RISC */
#define EM_UNICORE 110 /* PKU-Unity & MPRC Peking Uni. mc series */
#define EM_EXCESS 111 /* eXcess configurable cpu */
#define EM_DXP 112 /* Icera Semi. Deep Execution Processor */
#define EM_ALTERA_NIOS2 113 /* Altera Nios II */
#define EM_CRX 114 /* National Semi. CompactRISC CRX */
#define EM_XGATE 115 /* Motorola XGATE */
#define EM_C166 116 /* Infineon C16x/XC16x */
#define EM_M16C 117 /* Renesas M16C */
#define EM_DSPIC30F 118 /* Microchip Technology dsPIC30F */
#define EM_CE 119 /* Freescale Communication Engine RISC */
#define EM_M32C 120 /* Renesas M32C */
/* reserved 121-130 */
#define EM_TSK3000 131 /* Altium TSK3000 */
#define EM_RS08 132 /* Freescale RS08 */
#define EM_SHARC 133 /* Analog Devices SHARC family */
#define EM_ECOG2 134 /* Cyan Technology eCOG2 */
#define EM_SCORE7 135 /* Sunplus S+core7 RISC */
#define EM_DSP24 136 /* New Japan Radio (NJR) 24-bit DSP */
#define EM_VIDEOCORE3 137 /* Broadcom VideoCore III */
#define EM_LATTICEMICO32 138 /* RISC for Lattice FPGA */
#define EM_SE_C17 139 /* Seiko Epson C17 */
#define EM_TI_C6000 140 /* Texas Instruments TMS320C6000 DSP */
#define EM_TI_C2000 141 /* Texas Instruments TMS320C2000 DSP */
#define EM_TI_C5500 142 /* Texas Instruments TMS320C55x DSP */
#define EM_TI_ARP32 143 /* Texas Instruments App. Specific RISC */
#define EM_TI_PRU 144 /* Texas Instruments Prog. Realtime Unit */
/* reserved 145-159 */
#define EM_MMDSP_PLUS 160 /* STMicroelectronics 64bit VLIW DSP */
#define EM_CYPRESS_M8C 161 /* Cypress M8C */
#define EM_R32C 162 /* Renesas R32C */
#define EM_TRIMEDIA 163 /* NXP Semi. TriMedia */
#define EM_QDSP6 164 /* QUALCOMM DSP6 */
#define EM_8051 165 /* Intel 8051 and variants */
#define EM_STXP7X 166 /* STMicroelectronics STxP7x */
#define EM_NDS32 167 /* Andes Tech. compact code emb. RISC */
#define EM_ECOG1X 168 /* Cyan Technology eCOG1X */
#define EM_MAXQ30 169 /* Dallas Semi. MAXQ30 mc */
#define EM_XIMO16 170 /* New Japan Radio (NJR) 16-bit DSP */
#define EM_MANIK 171 /* M2000 Reconfigurable RISC */
#define EM_CRAYNV2 172 /* Cray NV2 vector architecture */
#define EM_RX 173 /* Renesas RX */
#define EM_METAG 174 /* Imagination Tech. META */
#define EM_MCST_ELBRUS 175 /* MCST Elbrus */
#define EM_ECOG16 176 /* Cyan Technology eCOG16 */
#define EM_CR16 177 /* National Semi. CompactRISC CR16 */
#define EM_ETPU 178 /* Freescale Extended Time Processing Unit */
#define EM_SLE9X 179 /* Infineon Tech. SLE9X */
#define EM_L10M 180 /* Intel L10M */
#define EM_K10M 181 /* Intel K10M */
/* reserved 182 */
#define EM_AARCH64 183 /* ARM AARCH64 */
/* reserved 184 */
#define EM_AVR32 185 /* Amtel 32-bit microprocessor */
#define EM_STM8 186 /* STMicroelectronics STM8 */
#define EM_TILE64 187 /* Tileta TILE64 */
#define EM_TILEPRO 188 /* Tilera TILEPro */
#define EM_MICROBLAZE 189 /* Xilinx MicroBlaze */
#define EM_CUDA 190 /* NVIDIA CUDA */
#define EM_TILEGX 191 /* Tilera TILE-Gx */
#define EM_CLOUDSHIELD 192 /* CloudShield */
#define EM_COREA_1ST 193 /* KIPO-KAIST Core-A 1st gen. */
#define EM_COREA_2ND 194 /* KIPO-KAIST Core-A 2nd gen. */
#define EM_ARC_COMPACT2 195 /* Synopsys ARCompact V2 */
#define EM_OPEN8 196 /* Open8 RISC */
#define EM_RL78 197 /* Renesas RL78 */
#define EM_VIDEOCORE5 198 /* Broadcom VideoCore V */
#define EM_78KOR 199 /* Renesas 78KOR */
#define EM_56800EX 200 /* Freescale 56800EX DSC */
#define EM_BA1 201 /* Beyond BA1 */
#define EM_BA2 202 /* Beyond BA2 */
#define EM_XCORE 203 /* XMOS xCORE */
#define EM_MCHP_PIC 204 /* Microchip 8-bit PIC(r) */
/* reserved 205-209 */
#define EM_KM32 210 /* KM211 KM32 */
#define EM_KMX32 211 /* KM211 KMX32 */
#define EM_EMX16 212 /* KM211 KMX16 */
#define EM_EMX8 213 /* KM211 KMX8 */
#define EM_KVARC 214 /* KM211 KVARC */
#define EM_CDP 215 /* Paneve CDP */
#define EM_COGE 216 /* Cognitive Smart Memory Processor */
#define EM_COOL 217 /* Bluechip CoolEngine */
#define EM_NORC 218 /* Nanoradio Optimized RISC */
#define EM_CSR_KALIMBA 219 /* CSR Kalimba */
#define EM_Z80 220 /* Zilog Z80 */
#define EM_VISIUM 221 /* Controls and Data Services VISIUMcore */
#define EM_FT32 222 /* FTDI Chip FT32 */
#define EM_MOXIE 223 /* Moxie processor */
#define EM_AMDGPU 224 /* AMD GPU */
/* reserved 225-242 */
#define EM_RISCV 243 /* RISC-V */

#define EM_BPF 247 /* Linux BPF -- in-kernel virtual machine */

#define EM_NUM 248

(这似乎就比较多了,列一下好了)

段表

段表用于存储段的基本结构,比如每个段的段名,段的长度,在文件中的偏移,文件读写权限及其他属性。readelf读取段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ readelf -S simple_section.o 
There are 13 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
0000000000000057 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000368
0000000000000078 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 00000098
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000000a0
0000000000000008 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 000000a0
0000000000000004 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 000000a4
000000000000002a 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000ce
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000d0
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 000003e0
0000000000000030 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 00000128
00000000000001b0 0000000000000018 11 12 8
[11] .strtab STRTAB 0000000000000000 000002d8
000000000000008f 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000410
0000000000000061 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)

前文用objdump -h看到的是一些关键段,不包括像符号表这样的辅助段。

段表的结构也比较简单,是以一个Elf32_Shdr或Elf64_Shdr结构体为元素的数组,结构体也被称为段描述符(Section Descriptor)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* Section header.  */

typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;

typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;

根据Section Table可以把段给重构出来,如果出现了某些地方的段不连续,可能是对齐的问题。

段的类型

image-20211130142301803

段的标志位

类似于rwx,用对应的位是否为0或1来表示这个段是否具有这个特性

段的链接信息

如果段的类型与链接相关(动态或静态)比如重定位表、符号表等,如下:

对于其他类型的段,这两个成员没有意义

重定位表

链接器在处理目标文件的时候,需要对目标文件中的某些部位进行重定位,即代码段和数据段中那些对绝对地址的引用的位置。对于每个需要重定位的代码段或者数据段,都会有一个对应的重定位表。

字符串表

ELF文件中用到了很多字符串,比如段名和变量名,因为字符串的长度是不定的,所以用固定的结构表示很困难,因此把字符串集中存起来放到字符串表,然后用字符串在表中的偏移来引用字符串,如下:

有两个字符串表,一个是字符串表,一个是段表字符串表。前者用于保存普通的字符串,后者用于保存段表用到的字符串,最常见的就是段名。

ELF头文件中,最后一个元素是e_shstrndx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;

Section header string table index,可以通过这个访问到段表字符串表,然后再结合获得的段表信息,就可以解析整个ELF文件了。

链接的接口——符号

在链接的过程中,统一把函数和变量称为符号,函数名和变量名称为符号名。

每一个目标文件都有一个符号表,记录了目标文件中所用到的符号。每个定义的符号有一个对应的值叫做符号值,对于函数和变量来说,符号值就是地址。

符号表的符号进行分类,如下:

  • 定义在本目标文件的全局符号,可以被其他目标文件引用。
  • 在本目标文件中引用的全局符号,却没有定义在本目标文件。比如simple_section.o中定义了printf()函数,没有实现,这一般叫做外部符号。
  • 段名,这种符号一般是编译器产生,是该段的起始地址。
  • 局部符号,这类符号只有在编译单元内部可见,比如static_var和static_var2.这些符号对链接没有用,一般被忽略。
  • 行号信息,用于调试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
* simple_section.c
*
* gcc -c simple_section.c
*/

int printf(const char* format, ...);

int global_init_var = 84;
int global_uninit_var;
static int global_static_var;

extern int reference_to_out;

void func1(int i) {
printf("%d\n", i);
}

int main(void) {
static int static_var = 85;
static int static_var2;

int a = 1;
int b;

func1(static_var + static_var2 + a + b);

return a;
}

一般关注全局符号,因为这对链接比较重要。

ELF符号表结构

1
2
3
4
5
6
7
8
9
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;
  • st_name:符号名,这个成员包含了该符号名在字符串表的下标
  • st_value:符号对应的地址值,可能是绝对值,也可能是一个地址,对不同符号的意义不同
  • st_size:符号大小,比如对于一个double的符号,占8个字节,如果该值为0,则表示该符号大小为0或未知
  • st_info: 符号类型喝绑定信息
  • st_other:This member currently specifies a symbol’s visibility. A list of the values and meanings appears below. The following code shows how to manipulate the values for both 32 and 64-bit objects. Other bits contain 0 and have no defined meaning.
    这个标志位应该是最近几年才被规定的,书上显示预留,现在表示符号的可见性。
  • st_shndx 符号所在的段

st_info

成员低4位表示符号类型,高28位表示符号绑定信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* Legal values for ST_BIND subfield of st_info (symbol binding).  */

#define STB_LOCAL 0 /* Local symbol */ // 对目标文件外部不可见
#define STB_GLOBAL 1 /* Global symbol */ // 全局符号,外部可见
#define STB_WEAK 2 /* Weak symbol */ // 弱引用
#define STB_NUM 3 /* Number of defined types. */
#define STB_LOOS 10 /* Start of OS-specific */
#define STB_GNU_UNIQUE 10 /* Unique symbol. */
#define STB_HIOS 12 /* End of OS-specific */
#define STB_LOPROC 13 /* Start of processor-specific */
#define STB_HIPROC 15 /* End of processor-specific */

/* Legal values for ST_TYPE subfield of st_info (symbol type). */

#define STT_NOTYPE 0 /* Symbol type is unspecified */
#define STT_OBJECT 1 /* Symbol is a data object */ // 数据对象,变量、数组等
#define STT_FUNC 2 /* Symbol is a code object */ // 函数或可执行代码
#define STT_SECTION 3 /* Symbol associated with a section */ // 符号是一个段,这种符号必须是STB_LOCAL的
#define STT_FILE 4 /* Symbol's name is file name */ // 该符号表示文件名,一般都是该目标文件所对应的源文件名,一定是STB_LOCAL的,并且st_shndx 一定是SHN_ABS
#define STT_COMMON 5 /* Symbol is a common data object */
#define STT_TLS 6 /* Symbol is thread-local data object*/
#define STT_NUM 7 /* Number of defined types. */
#define STT_LOOS 10 /* Start of OS-specific */
#define STT_GNU_IFUNC 10 /* Symbol is indirect code object */
#define STT_HIOS 12 /* End of OS-specific */
#define STT_LOPROC 13 /* Start of processor-specific */
#define STT_HIPROC 15 /* End of processor-specific */

st_shndx

如果符号定义在本目标文件中,这个成员变量表示符号所在的下标,但是如果不是定义在本目标文件中,或者对于有些特殊符号,st_shndx的值比较特殊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Special section indices.  */

#define SHN_UNDEF 0 /* Undefined section */ // 表示该符号未定义,这个符号表示该符号在本目标文件中被引用到,但是定义在其他目标文件中
#define SHN_LORESERVE 0xff00 /* Start of reserved indices */
#define SHN_LOPROC 0xff00 /* Start of processor-specific */
#define SHN_BEFORE 0xff00 /* Order section before all others
(Solaris). */
#define SHN_AFTER 0xff01 /* Order section after all others
(Solaris). */
#define SHN_HIPROC 0xff1f /* End of processor-specific */
#define SHN_LOOS 0xff20 /* Start of OS-specific */
#define SHN_HIOS 0xff3f /* End of OS-specific */
#define SHN_ABS 0xfff1 /* Associated symbol is absolute */ // 表示该符号包含了一个绝对的值,比如表示文件名的符号就属于这种类型
#define SHN_COMMON 0xfff2 /* Associated symbol is common */ // 表示该符号是一个COMMON块类型的符号
#define SHN_XINDEX 0xffff /* Index is in extra table. */
#define SHN_HIRESERVE 0xffff /* End of reserved indices */

符号值

  • 在目标文件中,如果是符号的定义且该符号不是COMMON块,,则st_value表示该符号在段中的偏移
  • 如果是该符号是COMMON块则st_value是该符号的对齐属性
  • 在可执行文件中,st_value表示符号的虚拟地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
readelf -s simple_section.o 

Symbol table '.symtab' contains 18 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 4 OBJECT LOCAL DEFAULT 4 global_static_var
6: 0000000000000000 0 SECTION LOCAL DEFAULT 5
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_var.1804
8: 0000000000000004 4 OBJECT LOCAL DEFAULT 4 static_var2.1805
9: 0000000000000000 0 SECTION LOCAL DEFAULT 7
10: 0000000000000000 0 SECTION LOCAL DEFAULT 8
11: 0000000000000000 0 SECTION LOCAL DEFAULT 6
12: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var
13: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var
14: 0000000000000000 36 FUNC GLOBAL DEFAULT 1 func1
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
17: 0000000000000024 51 FUNC GLOBAL DEFAULT 1 main

对照段表来看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
There are 13 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
0000000000000057 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000368
0000000000000078 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 00000098
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000000a0
0000000000000008 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 000000a0
0000000000000004 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 000000a4
000000000000002a 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000ce
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000d0
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 000003e0
0000000000000030 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 00000128
00000000000001b0 0000000000000018 11 12 8
[11] .strtab STRTAB 0000000000000000 000002d8
000000000000008f 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000410
0000000000000061 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)

可以通过Ndx和段表来找到符号所在的段。

具体细节在书中的P85,比较简单。

特殊符号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
* simple_section.c
*
* gcc -c simple_section.c
*/

int printf(const char* format, ...);

int global_init_var = 84;
int global_uninit_var;
static int global_static_var;

extern int reference_to_out;

void func1(int i) {
printf("%d\n", i);
}

int main(void) {
static int static_var = 85;
static int static_var2;

int a = 1;
int b;

func1(static_var + static_var2 + a + b);

return a;
}

这里面有一些符号是程序的开始地址和结束地址等。

符号修饰和函数签名

在这里就是因为早期C和Fortran如果一起用的话,难免用到同名符号,所以就要对符号名进行处理,而到了C++之后,编译的过程会根据更复杂的规则生成符号。

因此如果遇到C和C++混编的时候需要用extern “C”操作来避免找不到对应的符号。

弱符号和强符号

如果多个目标文件中出现重复的符号,链接的时候会出现符号重复定义的错误,这种符号称为强符号。而如果存在一个强符号和多个弱符号,则不会出错。特别注意的是,C和C++编译器默认初始化的全局变量为强符号,未初始化的为弱符号

在这里定义了两个简单的代码文件1.c和2.c:

1
2
3
4
5
6
7
// 1.c
#include <stdio.h>
int x = 1;

int main(){
printf("%d\n",x);
}
1
2
// 2.c
int x;

执行:

1
2
3
gcc 1.c 2.c
./a.out
1

没有问题,因为2.c中的x是弱符号。

而将2.c改为

1
2
// 2.c
int x = 2;

再用gcc编译的时候遇到了报错:

1
2
3
/tmp/ccO97LRd.o:(.data+0x0): multiple definition of `x'
/tmp/ccVlsyAp.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

而我们也可以用如下的方式来定义一个弱符号:

1
__attribute__((weak)) 
1
2
3
4
5
6
7
#include <stdio.h>

__attribute__((weak)) int x = 1;

int main(){
printf("%d\n",x);
}
1
int x = 2;

再执行:

1
2
3
gcc 1.c 2.c
./a.out
2

规则主要如下:

  • 不允许多个强符号被多次定义
  • 如果一个符号在某个目标文件中是强符号,其他文件中都是弱符号,那么选择强符号
  • 只有多个弱符号则与链接顺序相关

强引用和弱引用

对目标文件外的文件的符号进行引用的时候需要被正确决议,如果没有找到符号的定义,就会报符号未定义错误,这种引用被称为强引用。

对应的是弱引用,在处理弱引用的时候,有就决议,没有就忽视。差别在于对未定义的弱引用,链接器不认为是个错误。

1
__attribute__((weakref)) 

调试信息

gcc加上-g参数,就可以增加调试信息了,会多了很多debug相关段。