一 概述
二 类型
三 语句
四 函数
五 数据
六 内存
七 代码
附录
4. 编译链接
编译器将面向人类的高级语言翻译成面向机器的低级机器代码。
编译四个步骤:
- 预处理(prepressing):处理源码和相关头文件。(gcc -E, .i)
- 编译(compilation):作词法、语法、语义分析,生成汇编文件。(-S, .s)
- 汇编(assembly):将汇编代码转换成可执行机器代码。(as, gcc -c, .o)
- 链接(linking):组装所有目标文件,重定位地址。(ld)
词法分析是用扫描器(scanner, lex)将源码分割成一系列记号(token,关键字、标识符、字面量和操作符等),再以语法分析器(grammaer parser, yacc)分析记号生成以表达式(expression)为节点的语法树(syntax tree)。
语义包括编译期可确定的静态语义(static semantic),以及运行期的动态语义(dynamic semantic)。语义分析器(semantic analyzer)检查语句合法性,对语法树标识类型,插入隐式转换节点。接下来,编译器前端的源代码优化器(source code optimizer)会将语法树转换成中间代码(intermediate code)。
中间代码使得编译器分成前端和后端。前端负责生成机器无关的中间代码,后端将中间代码转换为机器代码。后端包括机器代码生成器(code generator)和目标代码优化器(target code optimizer)。
链接器要完成符号解析(symbol resolution)和重定位(relocation)。符号解析是将符号引用和符号定义联系起来。重定位是将符号与存储器位置联系起来,并修改所有对该符号的引用,使其指向具体地址。
目标文件类型:
- 可重定位目标文件(.o)。
- 可执行目标文件(exec)。
- 共享目标文件(.so)。
目标文件格式包括早期通用格式 COFF,以及 Windows/PE、OSX/Mach-O 和 Unix-like/ELF。
符号和符号表
每个目标文件都有一个符号表,其中包括:
- 当前模块内,可被外部引用的全局符号,比如函数和全局变量(非 static)。
- 只被当前模块定义和引用的本地符号,比如 static 函数和变量,不能被外部引用。
- 其他模块定义,被当前模块所引用的全局符号(external)。
在 .symtab 内存储了程序定义和引用的函数和全局变量信息,其中不包含局部变量。在 .strtab 内存储 .symtab 和 .debug 字符串信息。ABS 表示无需链接器处理。
UND 表示在本地引用的外部全局符号。COM 表示未初始化,比如分配在 .bss 的全局变量。OBJECT 变量。
$ readelf -s main.o Symbol table '.symtab' contains 14 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 9 9: 0000000000000000 0 SECTION LOCAL DEFAULT 6 10: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM x 11: 0000000000000000 38 FUNC GLOBAL DEFAULT 1 main 12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_ 13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
$ readelf -p .strtab main.o String dump of section '.strtab': [ 1] main.c [ 8] x [ a] main [ f] _GLOBAL_OFFSET_TABLE_ [ 25] puts
C 可用 static 隐藏函数和全局变量,类似其他语言的 private 声明。和全局变量类似,static 局部变量也在 .data/.bss 存储。默认是 extern,表示可被其他模块引用。
符号解析是将符号引用和它所在模块中的定义联系起来。
- 如果是本地符号,那么直接查找本地符号表。
- 全局符号引用,由链接器在组装时查找。
完成符号解析后,链接器将合并模块,并为每个符号分配运行时地址。
- 合并不同模块的相同段,如 .data、.text。
- 修改代码和数据中对符号的引用,使其指向正确的地址。
静态链接
可将多个目标模块(.o)打包成一个独立文件(.a),称做静态库(static library)。静态库可像普通模块那样作为链接器的输入。链接器只获取静态库中被引用的目标模块。
$ ar rs mylib.a lib1.o lib2.o # 创建静态库。
$ gcc -static # 全静态链接。
动态链接
相比静态库内嵌方式,动态链接库(shared library, .so)以独立文件的方式链接。在运行时由动态链接器(dynamic linker, ld-linux.so)执行动态链接,可被加载到任意位置。
$ ldd ./test linux-vdso.so.1 (0x00007ffce838d000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9ea9e07000) /lib64/ld-linux-x86-64.so.2 (0x00007f9eaa000000)
编译参数 -fPIC 模式指示生成位置无关代码,以便实现动态链接。链接参数 -shared 指示创建一个动态库。
$ gcc -fPIC -shared -o mylib.so lib1.c lib2.c
位置无关代码(position-independent code, PIC)。
程序运行时,动态链接器在 全局偏移表 (global offset table, GOT)中存储共享库符号重地位地址。调用共享库函数时,编译器使用延迟绑定(lazy binding),将地址重定位推迟到目标函数第一次被调用时。延迟绑定使用 过程链接表 (procedure linkage table, PLT),每个被调用共享库函数都有自己的记录,内容是一段从 GOT 查找地址的可执行代码。
GOT 是 .data 的一部分,PLT 是 .text 的一部分。
第一次调用时,GOT 内没有目标地址,重定位并用真实地址修改 GOT 记录。
第二次调用是,PLT 直接从 GOT 返回地址并跳转。
+-----+ +-----+ +------+ call puts@plt ----> | PLT | ----------> | GOT | ------> | puts | +-----+ +-----+ +------+ .text .data libc
PIE(position-independent executable)是一种防护技术。每次程序加载时,随机分配 .text、.data、.bss 等地址。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论