💁♂️个人主页:进击的荆棘
👇作者其它专栏:
目录
1.什么是库
2.静态库
3.动态库
4.使用外部库
5.目标文件
6.ELF文件
7.ELF从形成到加载轮廓
8.理解链接与加载
附录:操作大全
6.ELF文件
要理解编译链链接的细节,就必须了解ELF文件。以下四种文件都是ELF文件:
●可重定位文件(Relocatable File):即xxx.o文件。包含适合于与其它目标文件链接来创建可执行文件或共享目标文件的代码和数据
●可执行文件(Executable File):即可执行程序
●共享目标文件(Shared Object File):即xxx.so文件
●内核转储(core dumps):存放当前进程的执行上下文,用于dump信号触发
一个ELF文件由以下四部分组成:
●ELF头(ELT header):描述文件的主要特性。其位于文件的开始位置,它的主要目的是定位文件的其它部分
●程序头表(Program header table):列举了所有有效的段(segments)和它们的属性。表里记着每个段的开始的位置和位移(offset)、长度,这些段,都是紧密的放在二进制文件中,需要段表的描述信息,才能把它们每个段分割开
●节头表(Section header table):包含对节(sections)的描述
●节(Section):ELF文件中的基本组成单位,包含了特定类型的数据。ELF文件的各种信息和数据都存储在不同的节中,如代码节存储了可执行代码,数据节存储了全局变量和静态数据等
最常见的节:
●代码节(.text):用于保存机器指令,是程序的主要执行部分
●数据节(.data):保存已初始化的全局变量和局部静态变量
bash
[sjx@VM-0-6-centos ~]$ size /usr/bin/ls
text data bss dec hex filename
103207 4792 3360 111359 1b2ff /usr/bin/ls

7.ELF从形成到加载轮廓
7.1ELF形成可执行
●step1:将多份C/C++源代码,翻译成目标.o文件+动静态库(ELF)
●step2:将多份.o文件section进行合并

注:
●实际合并是在链接是进行的,但是并不是简单的合并,也会涉及对库合并
7.2ELF可执行文件加载
●一个ELF会由多种不同的Section,在加载到内存时,也会进行Section合并,形成segment
●合并原则:相同属性,如:可读,可写,可执行,需要加载时申请空间等
●这样,即使是不同的Section,在加载到内存中,可能会以segment的形式,加载到一起
●很显然,合并工作在形成ELF的时候,合并方式就已经确定了,具体合并原则被记录在了ELF的程序头表(Program header table)中
bash
#查看可执行程序的section
[sjx@VM-0-6-centos ~]$ readelf -S main.exe
There are 30 section headers, starting at offset 0x1960:
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
000000000000003d 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400356 00000356
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
0000000000000048 0000000000000018 AI 5 23 8
[11] .init PROGBITS 00000000004003e0 000003e0
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000400400 00000400
0000000000000040 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400440 00000440
0000000000000192 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 00000000004005d4 000005d4
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 00000000004005e0 000005e0
000000000000002b 0000000000000000 A 0 0 8
[16] .eh_frame_hdr PROGBITS 000000000040060c 0000060c
000000000000003c 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000400648 00000648
0000000000000114 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000008 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000008 WA 0 0 8
[20] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[23] .got.plt PROGBITS 0000000000601000 00001000
0000000000000030 0000000000000008 WA 0 0 8
[24] .data PROGBITS 0000000000601030 00001030
0000000000000004 0000000000000000 WA 0 0 1
[25] .bss NOBITS 0000000000601034 00001034
0000000000000004 0000000000000000 WA 0 0 1
[26] .comment PROGBITS 0000000000000000 00001034
000000000000002d 0000000000000001 MS 0 0 1
[27] .symtab SYMTAB 0000000000000000 00001068
0000000000000618 0000000000000018 28 47 8
[28] .strtab STRTAB 0000000000000000 00001680
00000000000001d5 0000000000000000 0 0 1
[29] .shstrtab STRTAB 0000000000000000 00001855
0000000000000108 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)
#查看可执行程序合并后的segment
[sjx@VM-0-6-centos ~]$ readelf -l main.exe
Elf file type is EXEC (Executable file)
Entry point 0x400440
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
0x000000000000075c 0x000000000000075c R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x0000000000000224 0x0000000000000228 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x000000000000060c 0x000000000040060c 0x000000000040060c
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 .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
为什么要将section合并成为segment
●Section合并的主要原因是为了减少页面碎片,提高内存使用效率。若不进行合并,假设页面大小为4096字节(内存块基本大小,加载,管理的基本单位),若.text部分为4097字节,.init部分为512字节,那么它们将占用3个页面,而合并后,只需2个页面
●此外,操作系统在加载程序时,会将具有相同属性的section合并成一个大的segment,这样就可以实现不同的访问权限,从而优化内存管理和权限访问控制
对于程序头表和节头表,ELF文件提供了2个不同的视角/视图来理解这两个部分:
●链接视图(Linking view)-对应节头表 Section header table
▢文件结构的粒度更细,将文件按功能模块的差异进行划分,静态链接分析的时候一般关注的是链接视图,能够理解ELF文件中包含的各个部分的信息
▢为了空间布局上的效率,将来在链接目标文件是,链接器会把很多节(section)合并,规整成可执行的段(segment)、可读写的段、只读段。合并了后,空间利用率就高了,否则,很小的一段,未来物理内存页浪费太大(物理内存页分配一般都是整数倍一块给你,如4k),所以,链接器趁着链接就把小块们都合并了
●执行视图(execution view)-对应程序头表Program header table
▢告诉操作系统,如何加载可执行文件,完成进程内存的初始化。一个可执行程序的格式中,一定有 program header table
●即一个在链接时作用,一个在运性加载时作用

从链接视图来看:
●命令readelf -S main.exe可以帮助查看ELF文件的节头表
●.text节:是保存了程序代码指令的代码节
●.data节:保存了初始化的全局变量和局部静态变量等数据
●.rodata节:保存了只读的数据,如一行C代码中的字符串。由于.rodata节是只读的,所以只能存在一个可执行的只读段中。因此,只能是在text段(不是data段)中找到.rodata节
●.BSS节:为未初始化的全局变量和局部静态变量预留位置
●.symtab:symbol Table 符号表,就是源码中的那些函数名、变量名和代码的对应关系
●.got.plt节(全局偏移表-过程链接表):.got节保存了全局偏移表。.got节和.plt节一起提供了对导入的共享库函数的访问入口,有动态链接器在运行时进行修改。
▢使用readelf命令查看.so文件可以看到该节
从执行视图来看:
●告诉操作系统哪些模块可以被加载进内存
●加载进内存之后哪些分段是可读可写,哪些分段是只读,哪些分段是可执行的
可以在ELF头中找到文件的基本信息,以及可以看到ELF头是如何定位程序头表和节头表的。例:可以查看main.exe这个可重定位文件的主要信息:
bash
#查看目标文件
[sjx@VM-0-6-centos ~]$ readelf -h code.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 #ELF文件标识符(魔数)
Class: ELF64 #文件类:64位架构
Data: 2's complement, little endian #数据编码:小端序二进制补码
Version: 1 (current) #ELF版本:当前版本(1)
OS/ABI: UNIX - System V #操作系统ABI:System V UNIX
ABI Version: 0 #ABI扩展版本:未扩展(0)
Type: REL (Relocatable file) #文件类型:可重定位文件(目标文件)
Machine: Advanced Micro Devices X86-64 #目标平台:x86-64架构
Version: 0x1 #对象文件版本:1
Entry point address: 0x0 #入口点地址:无(目标文件为0)
Start of program headers: 0 (bytes into file) #程序头表起始偏移:无(目标文件为0)
Start of section headers: 664 (bytes into file) #节头表起始偏移664字节处
Flags: 0x0 #处理器特定标志:五标志(0)
Size of this header: 64 (bytes) #ELF头大小:64字节
Size of program headers: 0 (bytes) #程序头表条目大小:无(目标文件为0)
Number of program headers: 0 #程序头表条目数:无(目标文件为0)
Size of section headers: 64 (bytes) #节头表条目大小:64字节
Number of section headers: 13 #节头表条目数:13个字节
Section header string table index: 12 #节名称字符串表索引:第12节(.shstrtab)
#查看可执行程序
[sjx@VM-0-6-centos ~]$ readelf -h main.exe
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 #ELF文件标识符(魔数)
Class: ELF64 #文件类:64为架构
Data: 2's complement, little endian #数据编码:小端序二进制补码
Version: 1 (current) #ELF版本:当前版本(1)
OS/ABI: UNIX - System V #操作系统ABI:System V UNIX
ABI Version: 0 #ABI扩展版本:未扩展(0)
Type: EXEC (Executable file) #文件类型:可重定位文件(目标文件
Machine: Advanced Micro Devices X86-64 #目标平台:x86-64架构
Version: 0x1 #对象文件版本:1
Entry point address: 0x400440 :入口点地址:0x400440
Start of program headers: 64 (bytes into file) #程序头表起始偏移:64字节处
Start of section headers: 6496 (bytes into file) #节头表起始偏移:6496字节处
Flags: 0x0 #处理器特定标志:无标志(0)
Size of this header: 64 (bytes) #ELF头大小:64字节
Size of program headers: 56 (bytes) #程序头表条目大小:56个字节
Number of program headers: 9 #程序头表条目数:9个节
Size of section headers: 64 (bytes) #节头表条目数:64个字节
Number of section headers: 30 #节头表条目数:30个节
Section header string table index: 29 #节名称字符串表索引:第29节(.shstrtab)
对于ELF HEADER来说,它的主要目的是定位文件的其它部分
8.理解链接与加载
8.1静态链接
●无论是自己的.o,还是静态库中的.o,本质都是把.o文件进行链接的过程
●所以:研究静态链接,本质就是研究.o是如何链接的
bash
[sjx@VM-0-6-centos test]$ ll
total 16
-rw-rw-r-- 1 sjx sjx 65 Oct 9 17:41 code.c
-rw-rw-r-- 1 sjx sjx 1496 Oct 9 17:42 code.o
-rw-rw-r-- 1 sjx sjx 85 Oct 9 17:41 main.c
-rw-rw-r-- 1 sjx sjx 1552 Oct 9 17:42 main.o
[sjx@VM-0-6-centos test]$ gcc -o main.exe *.o
[sjx@VM-0-6-centos test]$ ll
total 28
-rw-rw-r-- 1 sjx sjx 65 Oct 9 17:41 code.c
-rw-rw-r-- 1 sjx sjx 1496 Oct 9 17:42 code.o
-rw-rw-r-- 1 sjx sjx 85 Oct 9 17:41 main.c
-rwxrwxr-x 1 sjx sjx 8416 Oct 9 20:15 main.exe
-rw-rw-r-- 1 sjx sjx 1552 Oct 9 17:42 main.o
查看编译后的.o目标文件
bash
[sjx@VM-0-6-centos test]$ objdump -d code.o
code.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <test>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: bf 00 00 00 00 mov $0x0,%edi
9: e8 00 00 00 00 callq e <test+0xe>
e: 5d pop %rbp
f: c3 retq
[sjx@VM-0-6-centos test]$ objdump -d main.o
main.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 00 00 00 00 mov $0x0,%eax
9: e8 00 00 00 00 callq e <main+0xe>
e: bf 00 00 00 00 mov $0x0,%edi
13: e8 00 00 00 00 callq 18 <main+0x18>
18: 5d pop %rbp
19: c3 retq
●objdump -d命令:将代码段(.text)进行反汇编查看
●main.o中的main函数不认识printf和test函数
bash
[sjx@VM-0-6-centos test]$ cat main.c
#include <stdio.h>
void test();
int main(){
test();
printf("hello world\n");
}
●code.o不认识printf函数
bash
[sjx@VM-0-6-centos test]$ cat code.c
#include <stdio.h>
void test(){
printf("this is a test\n");
}
可以看到这里的call指令,分别对应之前调用的printf和run函数,但是它们的跳转地址都被设成了0。为什么?
因为在编译main.c的时候,编译器不知道printf和test函数的存在,如它们位于内存的哪个区块,代码长什么样都是不知道的。因此,编译器只能将这两个函数的跳转地址先暂时设为0
这个地址什么时候被修正?链接的时候。为了让链接器将来在链接时能正确定位到这些被修正的地址,在代码块(.data)中还存在一个重定位表,这张表将来在链接的时候,就会根据表里记录的地址将其修正
bash
#读取code.o符号表
[sjx@VM-0-6-centos test]$ readelf -s code.o
Symbol table '.symtab' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS code.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 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 test
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
puts:就是printf的实现
UND:就是undefine,表示未定义,即本.o文件找不到
#读取main.o符号表
[sjx@VM-0-6-centos test]$ readelf -s main.o
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.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 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 26 FUNC GLOBAL DEFAULT 1 main
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND test
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
puts:就是printf的实现
test:我们自己定义的方法在main.o中未定义(因为在code.o中)
UND:就是undefine,表示未定义,即本.o文件找不到
[sjx@VM-0-6-centos test]$ readelf -s main.exe
Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 65 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400238 0 SECTION LOCAL DEFAULT 1
2: 0000000000400254 0 SECTION LOCAL DEFAULT 2
3: 0000000000400274 0 SECTION LOCAL DEFAULT 3
4: 0000000000400298 0 SECTION LOCAL DEFAULT 4
5: 00000000004002b8 0 SECTION LOCAL DEFAULT 5
6: 0000000000400318 0 SECTION LOCAL DEFAULT 6
7: 0000000000400356 0 SECTION LOCAL DEFAULT 7
8: 0000000000400360 0 SECTION LOCAL DEFAULT 8
9: 0000000000400380 0 SECTION LOCAL DEFAULT 9
10: 0000000000400398 0 SECTION LOCAL DEFAULT 10
11: 00000000004003e0 0 SECTION LOCAL DEFAULT 11
12: 0000000000400400 0 SECTION LOCAL DEFAULT 12
13: 0000000000400440 0 SECTION LOCAL DEFAULT 13
14: 00000000004005d4 0 SECTION LOCAL DEFAULT 14
15: 00000000004005e0 0 SECTION LOCAL DEFAULT 15
16: 000000000040060c 0 SECTION LOCAL DEFAULT 16
17: 0000000000400648 0 SECTION LOCAL DEFAULT 17
18: 0000000000600e10 0 SECTION LOCAL DEFAULT 18
19: 0000000000600e18 0 SECTION LOCAL DEFAULT 19
20: 0000000000600e20 0 SECTION LOCAL DEFAULT 20
21: 0000000000600e28 0 SECTION LOCAL DEFAULT 21
22: 0000000000600ff8 0 SECTION LOCAL DEFAULT 22
23: 0000000000601000 0 SECTION LOCAL DEFAULT 23
24: 0000000000601030 0 SECTION LOCAL DEFAULT 24
25: 0000000000601034 0 SECTION LOCAL DEFAULT 25
26: 0000000000000000 0 SECTION LOCAL DEFAULT 26
27: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
28: 0000000000600e20 0 OBJECT LOCAL DEFAULT 20 __JCR_LIST__
29: 0000000000400470 0 FUNC LOCAL DEFAULT 13 deregister_tm_clones
30: 00000000004004a0 0 FUNC LOCAL DEFAULT 13 register_tm_clones
31: 00000000004004e0 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux
32: 0000000000601034 1 OBJECT LOCAL DEFAULT 25 completed.6355
33: 0000000000600e18 0 OBJECT LOCAL DEFAULT 19 __do_global_dtors_aux_fin
34: 0000000000400500 0 FUNC LOCAL DEFAULT 13 frame_dummy
35: 0000000000600e10 0 OBJECT LOCAL DEFAULT 18 __frame_dummy_init_array_
36: 0000000000000000 0 FILE LOCAL DEFAULT ABS code.c
37: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
38: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
39: 0000000000400758 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__
40: 0000000000600e20 0 OBJECT LOCAL DEFAULT 20 __JCR_END__
41: 0000000000000000 0 FILE LOCAL DEFAULT ABS
42: 0000000000600e18 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
43: 0000000000600e28 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
44: 0000000000600e10 0 NOTYPE LOCAL DEFAULT 18 __init_array_start
45: 000000000040060c 0 NOTYPE LOCAL DEFAULT 16 __GNU_EH_FRAME_HDR
46: 0000000000601000 0 OBJECT LOCAL DEFAULT 23 _GLOBAL_OFFSET_TABLE_
47: 00000000004005d0 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
48: 0000000000601030 0 NOTYPE WEAK DEFAULT 24 data_start
49: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
50: 0000000000601034 0 NOTYPE GLOBAL DEFAULT 24 _edata
51: 00000000004005d4 0 FUNC GLOBAL DEFAULT 14 _fini
52: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
53: 0000000000601030 0 NOTYPE GLOBAL DEFAULT 24 __data_start
54: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
55: 00000000004005e8 0 OBJECT GLOBAL HIDDEN 15 __dso_handle
56: 00000000004005e0 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
57: 0000000000400560 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init
58: 0000000000601038 0 NOTYPE GLOBAL DEFAULT 25 _end
59: 0000000000400440 0 FUNC GLOBAL DEFAULT 13 _start
60: 0000000000601034 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
61: 000000000040053d 26 FUNC GLOBAL DEFAULT 13 main
62: 0000000000601038 0 OBJECT GLOBAL HIDDEN 24 __TMC_END__
63: 00000000004003e0 0 FUNC GLOBAL DEFAULT 11 _init
64: 000000000040052d 16 FUNC GLOBAL DEFAULT 13 test
两个.o进行合并之后,在最终的可执行程序中,找到了test
000000000040052d:地址
FUNC:表示test符号类型是个函数
13:test函数所在的section被合并最终的那一个section中,13就是下标