内容提要
本文介绍了生成ELF动态库的方法,包括使用位置无关代码(PIC)和哈希表来实现动态链接。动态链接相比静态链接具有减小二进制体积、提高安全性和支持动态库替换的优势。文章提供了具体的代码实现和相关文档供参考。
延伸解读
动态链接的权衡:空间与安全性的提升
文章指出静态链接会导致二进制体积膨胀,且库的安全漏洞需要重新编译所有依赖程序。动态链接通过延迟部分链接到程序启动时,减小了可执行文件体积,并允许替换动态库而无需重新编译。但这也引入了新的要求:动态库必须支持加载到任意地址,因此需要位置无关代码(PIC),并且动态链接的性能至关重要,这解释了后续引入哈希表等优化机制的原因。
PIC 实践:从绝对寻址到相对寻址
文章通过一个具体例子展示了生成动态库时遇到的 relocation 错误:R_X86_64_32S 要求绝对地址,但动态库加载地址未知。解决方案是将 `mov $hello, %rsi` 改写为 `lea hello(%rip), %rsi`,从而产生相对 relocation R_X86_64_PC32。这强调了编写位置无关代码需要从源码层面避免绝对地址,汇编器无法自动转换,对于手写汇编或底层开发具有实际指导意义。
动态库的额外结构:段与哈希表
与可执行文件相比,动态库增加了 .hash、.gnu.hash、.dynsym、.dynstr 和 .dynamic 等段。.dynsym 仅保留外部可能引用的全局符号,以减轻动态链接器负担;.dynamic 则集中提供哈希表、符号表等的位置和大小,避免动态链接器解析整个 ELF。此外,GNU_RELRO 段允许在重定位后将部分内存设为只读,增强安全性。这些结构共同支持高效的动态链接。
符号查找性能:SystemV Hash 与 GNU Hash 对比
文章对比了两种哈希表实现。SystemV Hash 使用拉链法,通过 bucket 和 chain 数组解决冲突,但内存访问随机且每次比较都需字符串对比。GNU Hash 结合 Bloom Filter 和线性法:Bloom Filter 快速排除不存在的符号;线性法将同一 bucket 的符号连续存放,提升缓存局部性;比较时先对比哈希值(忽略最低位),减少字符串比较。这些优化显著提高了动态链接器的符号查找速度。
Q&A
如何生成ELF动态库?
生成ELF动态库需要使用位置无关代码(PIC)并使用 -shared 命令行参数进行链接。
动态链接相比静态链接有哪些优势?
动态链接可以减小二进制体积、提高安全性,并支持动态库的替换。
动态链接器在程序启动时的作用是什么?
动态链接器负责加载动态库并解析符号,以便程序可以正常调用所需的函数。
什么是位置无关代码(PIC),它有什么作用?
位置无关代码(PIC)是一种编译方式,使得代码可以在内存中的任意地址加载,避免地址冲突。
动态库中包含哪些额外的段?
动态库包含 .hash、.gnu.hash、.dynsym、.dynstr 和 .dynamic 等段,用于支持动态链接。
GNU Hash和SystemV Hash有什么区别?
GNU Hash结合了Bloom Filter和哈希表,提供更高的性能,而SystemV Hash使用拉链法解决哈希冲突。