开发一个链接器(2)

开发一个链接器(2)

💡 原文中文,约9600字,阅读约需23分钟。
📝

内容提要

本文介绍了如何将多个ELF .o文件链接成一个可执行文件,包括模拟函数调用和重定位的过程,处理不同文件之间的函数调用和重定位,以及实现多文件链接器所需的额外工作。

🔎

延伸解读

从单文件到多文件链接的关键挑战

当链接器从处理单个 .o 文件升级到多个 .o 文件时,核心难点在于处理跨文件的符号引用和重定位。单个文件时,所有符号和节区都在同一文件内,地址计算相对简单;而多文件时,一个文件可能引用另一个文件中定义的函数或数据,链接器必须合并相同类型的节区,并正确解析符号地址。这要求链接器维护全局符号表,并记录每个输入文件各节区在输出文件中的相对位置,以便在重定位时使用正确的基地址。

理解 R_X86_64_PLT32 重定位的计算逻辑

在 x86-64 中,call 指令使用相对偏移,因此汇编器会生成 R_X86_64_PLT32 重定位(可视为 R_X86_64_PC32 的变体)。重定位条目中的 addend 为 -4,且重定位地址指向 call 指令的操作数字节(而非指令起始)。链接器计算时使用公式 S + A - P:S 是目标符号地址,A 是 addend(-4),P 是重定位自身地址。这样设计使链接器无需识别指令类型,只需按重定位规则填入偏移,从而支持多种指令场景。

节区合并与符号地址计算的实际操作

链接器需要将多个输入文件的同名节区(如 .text、.rodata)按顺序合并到输出节区中。合并时,每个输入节区在输出节区中的起始地址等于输出节区基地址加上之前所有输入节区的大小。同时,符号的最终地址由其所在输入节区在输出节区中的偏移加上节区基地址得到。例如,若 main.o 的 .text 大小为 0xa,printer.o 的 .text 紧随其后,则 printer.o 中符号的地址需加上 0xa 的偏移。这一过程必须在复制节区内容时同步记录,以便后续重定位计算。

❓

Q&A

如何将多个ELF .o文件链接成一个可执行文件?

通过解析输入的ELF文件,收集各个section的内容,规划文件布局,计算重定向,并将内容写入输出的ELF文件。

在链接多个文件时,如何处理函数调用和重定位?

需要处理不同文件之间的函数声明和定义,汇编器生成重定位信息,链接器根据这些信息填入正确的地址。

重定位类型R_X86_64_PLT32和R_X86_64_PC32有什么区别?

R_X86_64_PLT32涉及到过程链接表(PLT),而R_X86_64_PC32是基于程序计数器计算的相对地址偏移。

在实现多文件链接器时需要注意哪些额外工作?

需要合并来自多个文件的同一section,完善对section的重定位处理,并维护符号表以找到符号对应的地址。

如何在汇编语言中模拟函数调用?

通过将代码分成多个文件,并在入口函数中调用这些函数来实现,例如使用call指令。

链接器如何处理来自不同.o文件的section?

链接器需要合并来自不同.o文件的同一section,并记录各个section在内存中的相对位置。

🏷️

标签

➡️

继续阅读