上一篇写了.o目标文件分析,.o文件只是一个.c文件通过汇编生成的一个可重定位文件,并没有真正进行链接,现在我们就分析一个链接完成后的可执行文件hello_world,经过两个文件的对比,让我们更好的掌握ELF文件。
3.1 hello_world.o补充
上一篇我们只是使用了objdump -h 查看各个段,其实-h只是把关键中的段显示了出来,这次我们用readelf -S来看全部的段。
root@ubuntu:~/c_test/03
There are 13 section headers, starting at offset 0x490:
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
000000000000007f 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000338
00000000000000c0 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000c0
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000000c8
0000000000000008 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 000000c8
000000000000001e 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 000000e6
0000000000000036 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 0000011c
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 00000120
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 000003f8
0000000000000030 0000000000000018 I 11 8 8
[10] .shstrtab STRTAB 0000000000000000 00000428
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 00000178
0000000000000180 0000000000000018 12 11 8
[12] .strtab STRTAB 0000000000000000 000002f8
000000000000003b 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
root@ubuntu:~/c_test/03
上一节我们介绍了7个section段,这次再来补一补:
3.1.1 .rela.text
重定位的表,这个表记录中程序中需要重定位的函数或变量,就是代码段和数据段那些对绝对地址的引用。
这个.rela.text是对.text段的重定位表,应该就是printf函数的,没有引用全局的数据,所以没有.rela.data段。
root@ubuntu:~/c_test/03
Relocation section '.rela.text' at offset 0x338 contains 8 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000011 00050000000a R_X86_64_32 0000000000000000 .rodata + 0
00000000001b 000e00000002 R_X86_64_PC32 0000000000000000 printf - 4
00000000003e 000400000002 R_X86_64_PC32 0000000000000000 .bss + 0
000000000044 000300000002 R_X86_64_PC32 0000000000000000 .data + 0
000000000057 000d00000002 R_X86_64_PC32 0000000000000000 func1 - 4
00000000005d 000b00000002 R_X86_64_PC32 0000000000000000 g_a - 4
00000000006a 00050000000a R_X86_64_32 0000000000000000 .rodata + 8
000000000074 000e00000002 R_X86_64_PC32 0000000000000000 printf - 4
Relocation section '.rela.eh_frame' at offset 0x3f8 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
000000000040 000200000002 R_X86_64_PC32 0000000000000000 .text + 26
这个等下一节分析链接的时候,再好好分析。
3.1.2 字符串表
这个字符串表不是代码中的字符串,代码中的字符串是存储在rodata区,这里说的字符串表是.strtab和.shstrtab这两个section段的字符串。
下面我们继续用readelf工具来看看这两个段有啥
root@ubuntu:~/c_test/03
String dump of section '.shstrtab':
[ 1] .symtab
[ 9] .strtab
[ 11] .shstrtab
[ 1b] .rela.text
[ 26] .data
[ 2c] .bss
[ 31] .rodata
[ 39] .comment
[ 42] .note.GNU-stack
[ 52] .rela.eh_frame
root@ubuntu:~/c_test/03
String dump of section '.strtab':
[ 1] hello_world.c
[ f] s_a.2292
[ 18] s_b.2293
[ 21] g_a
[ 25] g_b
[ 29] func1
[ 2f] printf
[ 36] main
root@ubuntu:~/c_test/03
.shstrtab这个段主要是保存section段名的,.strtab这个段主要是保存代码中的函数名和变量名。
下面是存储的格式,所以如果需要取字符串的话,需要取上图的左边的偏移就能取到字符串。
3.1.3 .symtab
符号表,很重要,后面再分析
3.1.4 elf header
上一篇也看了头信息,不过没有详细说,差点就忽略了重要信息,
root@ubuntu:~/c_test/02
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: 1168 (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: 10
root@ubuntu:~/c_test/02
解释重要字段:
- Type:一个是EXEC (Executable file),一个是REL (Relocatable file)
- Entry point address:入口地址,规定ELF程序的入口虚拟地址,操作系统会在加载完程序后,从这个地址开始执行程序。可重定位文件一般是没链接成功的,所以这个值为0,可执行文件是链接成功了,所以有了一个地址0x400430。
- Start of program headers:编程头信息,对各种Segment的描述,在链接部分会描述
- Size of program headers:既然有了编程头信息,那当然有大小了,要不然怎么知道结束位置,这个大小是每个Segment的大小
- Number of program headers:顺带送了一个编程头信息有几个segment段
- Start of section headers:section段的头,就是上一节讲了那么多段,他们的段信息是存在这里的
- Size of section headers:这个也是描述section段的大小,每个section段的大小
- Number of section headers:另外还附带了section段的个数
- Size of this header:这个是我们现在查看的头信息的大小
- Section header string table index:在’.shstrtab’段中的字符串.shstrtab的下标。(但是我们在字符串中看到的下标是0x11,在头信息中看到是10,不是很明白哪里错了,以后理解了再补)
3.1.5 hello_world.0分布图
0x7d0其实就是文件的大小,不信可以用ls 命令查看文件大小就清楚了。
3.2 可执行文件
上面主要是描述了可重定位文件,下面可以简单看看可执行文件,就是链接后的,可以直接执行的,不过这里不会详细讲,下一节讲链接的时候,就会比较详细的描述。
3.2.1 头信息
root@ubuntu:~/c_test/03
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: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400430
Start of program headers: 64 (bytes into file)
Start of section headers: 6784 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 28
readelf: Error: Reading 0x19740000 bytes extends past end of file for string table
readelf: Error: no .dynamic section in the dynamic segment
可以看可执行文件跟可重定位文件的区别就是type,还有Entry point address,链接完成了,是有main函数可以执行了。并且多了Start of program headers这个段。
3.2.2 program headers
也可以通过命令来查看这个段有什么:
root@ubuntu:~/c_test/03
Elf file type is EXEC (Executable file)
Entry point 0x400430
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000007a4 0x00000000000007a4 R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x0000000000000230 0x0000000000000240 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x0000000000000654 0x0000000000400654 0x0000000000400654
0x000000000000003c 0x000000000000003c R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x00000000000001f0 0x00000000000001f0 R 1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
root@ubuntu:~/c_test/03
这个segment段可以理解为链接器把section段的比较相似的段会统一整理形成一个新的segment段,这样会更利于节省内存。
3.2.3 section段查看
可执行文件中也是有section段的,下面我们来看看:
root@ubuntu:~/c_test/03
There are 31 section headers, starting at offset 0x1a80:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8
0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 00000318
000000000000003f 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400358 00000358
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 00000360
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 00000380
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400398 00000398
0000000000000030 0000000000000018 AI 5 24 8
[11] .init PROGBITS 00000000004003c8 000003c8
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004003f0 000003f0
0000000000000030 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000400420 00000420
0000000000000008 0000000000000000 AX 0 0 8
[14] .text PROGBITS 0000000000400430 00000430
00000000000001f2 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 0000000000400624 00000624
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 0000000000400630 00000630
0000000000000022 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 0000000000400654 00000654
000000000000003c 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000400690 00000690
0000000000000114 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000000 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000000 WA 0 0 8
[21] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[23] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 0000000000601000 00001000
0000000000000028 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000601028 00001028
0000000000000018 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000601040 00001040
0000000000000010 0000000000000000 WA 0 0 4
[27] .comment PROGBITS 0000000000000000 00001040
0000000000000035 0000000000000001 MS 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00001974
000000000000010c 0000000000000000 0 0 1
[29] .symtab SYMTAB 0000000000000000 00001078
00000000000006c0 0000000000000018 30 49 8
[30] .strtab STRTAB 0000000000000000 00001738
000000000000023c 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
root@ubuntu:~/c_test/03
这个段是真多啊,现在是粗略浏览一下,我画了一个hello_world的布局图,可以简单看看。
简单粗略的看看就可以了,后面还会继续分析。
3.3 符号表
3.3.1 符号是什么
所说的符号其实就是我们代码中的函数和变量,在链接的过程中,链接器会把这些所有符号都统一管理起来,然后给他们赋值,叫符号值。函数和变量的符号值就是自己的地址。
所以写c语言的时候,函数不能重名,重名的时候会在链接的时候会报错,就是这个道理,当然可以用static来限制作用域。
所以每一个目标文件(hello_world.o)都有一份自己的符号表,存储在.symtab段中,链接的时候,链接器只要去找这个段中的符号,然后各种操作就可以了。
下面具体来看看符号有哪些:
- 定义在本目标文件的全局符号,可以被其他目标文件引用。(函数和全局变量)
- 在本目标文件中引用的全局符号,却没有定义在本目标文件。这叫外部符号。(printf,这个经常忘记)
- 段名,这种符号是编译器产生的。
- 局部符号,就是局部静态变量,链接器往往会忽略他们
- 行号信息,目标文件指令与源代码中代码行的对应关系。(这个还真没注意)
3.3.2 分析一个符号表
我们就拿hello_world.o分析一下符号表
root@ubuntu:~/c_test/03
Symbol table '.symtab' contains 16 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello_world.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: 0000000000000004 4 OBJECT LOCAL DEFAULT 4 s_a.2292
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 s_b.2293
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 6
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 4 g_a
12: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 g_b
13: 0000000000000000 38 FUNC GLOBAL DEFAULT 1 func1
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
15: 0000000000000026 89 FUNC GLOBAL DEFAULT 1 main
num:个数
value:在可执行文件中,这个值就是符号值
Size:大小
Type:符号类型
Bind:符号绑定信息
Ndx:UND:表示未找到,printf函数在其他地方,其他的表示section段的
图示:
3.3.3 其他
-
符号修饰 c++语言具有重载、命名空间等特性,那链接器是怎么处理这些符号的,其实链接器处理的时候也比较简单,就在原来的符号上,再做修饰,比如下图:
是不是就懂了,重载的话,会把参数带在符号上,命名空间等都是这样处理的。
-
extren c 我们在写在c++中引用c是不是需要添加extren c,这个做法其实也是链接器不会吧c的函数进行修饰,这样c++就可以调用了。 -
强符号与弱符号
? gcc编译器是支持定义这种弱符号的,弱符号的意思就是可以定义一个强符号(前面不加修饰就是强符号)来覆盖这个弱符号,调用的时候,如果有强符号就会调用强符号,如果没有强符号就调用弱符号,这样也不会报重复定义的错误。
编译器对弱符号的函数没有定义是没有报错的,以后可以验证一下。
__attribute__((weak)) weak2 = 2;
参考链接:
计算机原理系列之六 ——– 可执行文件详解
计算机原理系列之四 ——– 可重定位文件详解
《程序员的自我修养——链接、装载与库》
|