静心学习之路(10)——链接(2)
0x07 重定位
链接器完成了符号解析以后,把代码里每个符号引用每个符号定义关联了起来。此时链接器就知道了目标模块中代码节和数据节的确切大小,此时就可以执行重定位步骤了,重定位由两步组成:
-
重定位节和符号定义。链接器把相同类型的节合并为同一类型的聚合节。比如把所有输入模块的
.data
节合并为可执行目标文件的.data
节。然后链接器把运行时的内存地址赋给新的聚合节、输入模块定义的每个节以及输入模块定义的每个符号。 - 重定位节的符号引用。链接器修改代码节和数据节中对每个符号的引用,使它们指向正确的运行时的地址。
0x08 可执行目标文件
可执行目标文件的格式类似于可重定位目标文件.o
。比较之下,多了程序的入口点(entry point),因为可执行文件是完全链接
的,所以他不再需要rel
节。
节 | 说明 |
---|---|
ELF头 | 略 |
段头部表 | 略 |
.init | 定义了_init函数,程序初始化会用到 |
.text | 已编译程序的机器码 |
.rodata | read-only data(比如printf的字符串,和swtich的跳转表) |
.data | init过的 global & static c-vars |
.bss | 未init过或init(0)的 global & static c-vars(纯占位,省空间用) |
.symtab | 符号表,存放定义和引用的 funciton 和 global vars 的信息 |
.debug | 调试符号表,-g选项才获得 |
.line | C代码行号和.text映射表,-g选项才获得 |
.strtab | 字符串表,包含.symtab和.debug的符号表,以及节头部中的节名字 |
节头部表 | 描述不同节的位置和大小 |
当然上面是课本内容,我们也可以用反汇编工具objdump
查看文件:(假设prog文件由此产生:linux > gcc -Og -o prog main.c sum.c
)
因为内容输出巨多,为了方便浏览(且不污染终端界面)建议输出到文件或者vim类浏览器中(比如less
):
linux > objdump -x prog | less
部分结果如下:
prog: file format elf64-x86-64
prog
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00000000004003a0
Program Header:
PHDR off 0x0000000000000040 vaddr 0x0000000000400040 paddr 0x0000000000400040 align 2**3
filesz 0x00000000000001c0 memsz 0x00000000000001c0 flags r-x
INTERP off 0x0000000000000200 vaddr 0x0000000000400200 paddr 0x0000000000400200 align 2**0
filesz 0x000000000000001c memsz 0x000000000000001c flags r--
LOAD off 0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**21
filesz 0x00000000000006dc memsz 0x00000000000006dc flags r-x
LOAD off 0x00000000000006e0 vaddr 0x00000000006006e0 paddr 0x00000000006006e0 align 2**21
filesz 0x00000000000001f8 memsz 0x0000000000000208 flags rw-
DYNAMIC off 0x0000000000000700 vaddr 0x0000000000600700 paddr 0x0000000000600700 align 2**3
filesz 0x0000000000000190 memsz 0x0000000000000190 flags rw-
NOTE off 0x000000000000021c vaddr 0x000000000040021c paddr 0x000000000040021c align 2**2
filesz 0x0000000000000020 memsz 0x0000000000000020 flags r--
EH_FRAME off 0x00000000000005c8 vaddr 0x00000000004005c8 paddr 0x00000000004005c8 align 2**2
filesz 0x0000000000000034 memsz 0x0000000000000034 flags r--
STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**3
filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
...
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 0000001c 0000000000400200 0000000000400200 00000200 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020 000000000040021c 000000000040021c 0000021c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .hash 00000018 0000000000400240 0000000000400240 00000240 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .dynsym 00000048 0000000000400258 0000000000400258 00000258 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynstr 00000038 00000000004002a0 00000000004002a0 000002a0 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .gnu.version 00000006 00000000004002d8 00000000004002d8 000002d8 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version_r 00000020 00000000004002e0 00000000004002e0 000002e0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .rela.dyn 00000018 0000000000400300 0000000000400300 00000300 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rela.plt 00000030 0000000000400318 0000000000400318 00000318 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .init 00000024 0000000000400348 0000000000400348 00000348 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
10 .plt 00000030 000000000040036c 000000000040036c 0000036c 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .text 00000214 00000000004003a0 00000000004003a0 000003a0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .fini 0000000e 00000000004005b4 00000000004005b4 000005b4 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .rodata 00000004 00000000004005c4 00000000004005c4 000005c4 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
14 .eh_frame_hdr 00000034 00000000004005c8 00000000004005c8 000005c8 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
15 .eh_frame 000000dc 0000000000400600 0000000000400600 00000600 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
16 .ctors 00000010 00000000006006e0 00000000006006e0 000006e0 2**3
CONTENTS, ALLOC, LOAD, DATA
17 .dtors 00000010 00000000006006f0 00000000006006f0 000006f0 2**3
CONTENTS, ALLOC, LOAD, DATA
18 .dynamic 00000190 0000000000600700 0000000000600700 00000700 2**3
CONTENTS, ALLOC, LOAD, DATA
19 .got 00000008 0000000000600890 0000000000600890 00000890 2**3
CONTENTS, ALLOC, LOAD, DATA
20 .got.plt 00000028 0000000000600898 0000000000600898 00000898 2**3
CONTENTS, ALLOC, LOAD, DATA
21 .data 00000018 00000000006008c0 00000000006008c0 000008c0 2**3
CONTENTS, ALLOC, LOAD, DATA
22 .bss 00000010 00000000006008d8 00000000006008d8 000008d8 2**3
ALLOC
23 .comment 00000011 0000000000000000 0000000000000000 000008d8 2**0
CONTENTS, READONLY
24 .debug_aranges 00000100 0000000000000000 0000000000000000 000008f0 2**4
CONTENTS, READONLY, DEBUGGING
25 .debug_info 00000361 0000000000000000 0000000000000000 000009f0 2**0
CONTENTS, READONLY, DEBUGGING
26 .debug_abbrev 000001a0 0000000000000000 0000000000000000 00000d51 2**0
CONTENTS, READONLY, DEBUGGING
27 .debug_line 0000022c 0000000000000000 0000000000000000 00000ef1 2**0
CONTENTS, READONLY, DEBUGGING
28 .debug_str 00000216 0000000000000000 0000000000000000 0000111d 2**0
CONTENTS, READONLY, DEBUGGING
29 .debug_loc 00000155 0000000000000000 0000000000000000 00001333 2**0
CONTENTS, READONLY, DEBUGGING
30 .debug_ranges 00000080 0000000000000000 0000000000000000 00001490 2**4
CONTqENTS, READONLY, DEBUGGING
SYMBOL TABLE:
...
可以在Sections
这部分找到具体的节信息。顺便一提笔者在生产环境中得知,debug
相对于release
来说会多了 .stab
和 .stabstr
这两部分(在.bss
和.common
之间),这样用gdb
进行dump文件分析的时候就能追踪的具体代码行数,release
版本就不行。
...
24 .bss 003d7680 0000000000c285c0 0000000000c285c0 006285b0 2**5
ALLOC
25 .stab 00adb068 0000000000000000 0000000000000000 006285b0 2**2
CONTENTS, READONLY, DEBUGGING
26 .stabstr 05379895 0000000000000000 0000000000000000 01103618 2**0
CONTENTS, READONLY, DEBUGGING
27 .comment 0000004f 0000000000000000 0000000000000000 0647cead 2**0
CONTENTS, READONLY
...
(↑ 生产环境中线上Debug版本反编译时看到的多出的两节内容)
而在Program Header
这部分里展示了prog
的头部表, 这里截取上面输出的部分内容:
LOAD off 0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**21
filesz 0x00000000000006dc memsz 0x00000000000006dc flags r-x
LOAD off 0x00000000000006e0 vaddr 0x00000000006006e0 paddr 0x00000000006006e0 align 2**21
filesz 0x00000000000001f8 memsz 0x0000000000000208 flags rw-
我们可以看到可执行目标文件的内容初始化两个内存段,前两行告诉我们第一个代码
段具有读/执行权限r-x
并开始于内存地址vaddr
为 0x400000
,总内存大小memsz
为0x6dc
个字节,其中包括ELF头
程序头部表
.init
.text
.rodata
。
后两行告诉第二个数据
段具有读/写权限rw-
并开始于内存地址0x6006e0
,总大小为0x208
个字节,并用从目标文件偏移量off
为0x6e0
处的.data
节中的filesz
0x1f8
个字节初始化。该段的 0x208 - 0x1f8 = 0x10
即16个字节用于运行时初始化为0的.bss
数据。
0x09 加载可执行目标文件
linux > ./prog
因为prog
不是内置shell命令,所以shell判定prog是一个可执行目标文件,然后linux执行execve函数=>调用加载器(loader)。loader把目标文件的代码和数据从磁盘复制到内存,然后跳转到程序的第一条指令或者入口点来运行程序,这个过程就叫做加载。
Linux x86-64系统中,代码段总是从0x400000
开始(vaddr 0x0000000000400000
),后面是数据段,再后面是运行是的堆
,由调用malloc
往上增长。而用户栈则从最大用户合法地址(2^48-1 即 0x0000ffffffffffff
,)开始高位向低位,向下增长。
具体关于loader的工作方式,要到CSAPP第8章和第9章中了解。