重定位
复习
- 目标文件格式:含符号表与重定位信息
- 程序在内存里的样子:代码区与数据区
- 地址:内存位置的编号
TL;DR
- 编译单个文件时,不知道外部符号的最终地址
- 重定位就是把这些引用改成真实的地址
- 它依赖目标文件里记录的“哪里需要改”
- 链接器在合并目标文件时完成重定位
正文
上一章留下一个悬念:目标文件里有一堆“需要填地址”的位置,等着将来被填上。这个“填地址”的动作,叫重定位(relocation)。
问题从哪来
设想你在 a 文件里调用了 b 文件里的函数 foo。编译 a 时,你知道“要调用 foo”,却不知道 foo 最终会被放在哪个地址——因为那时还没把 b 合进来,也没决定整个程序怎么摆放。
于是,a 的目标文件里,这条调用指令先留一个占位地址,同时记下一笔:“这里,等链接时填 foo 的真实地址。”
类似地,访问一个外部变量、引用一段外部数据,都会产生这种“待填”的引用。
链接时统一解决
等到链接阶段,所有目标文件汇到一起,链接器终于能总览全局:
- 决定各个段、各个符号最终摆在哪里(分配地址)
- 找到目标文件里所有“待填”的位置
- 把它们替换成真实的最终地址
这一步做完,程序里所有的跨文件引用就都“对上号”了。重定位,本质上是把“临时的占位”换成“确定的地址”。
注意别混淆
这里要提醒一句:编译器/链接器语境下的“重定位”,和操作系统里的“地址重定位”(程序装入内存时地址的调整)不是同一个东西。
这一章讲的是链接时的重定位:在生成可执行文件时,把地址确定下来。而程序运行、装入内存时可能还会有一层地址转换——那是操作系统部分的话题。同一个词,在不同层次有不同含义,要放回语境里理解。
重定位是链接器的核心工作之一。下一章,我们看看把一堆目标文件连起来的完整过程。
思考题 1
为什么单文件编译时,无法确定外部符号的地址?
思考题 2
重定位需要目标文件提供什么信息?
小结
知识点
- 单文件编译时外部符号地址未知
- 目标文件先留占位并记录待填位置
- 链接时统一分配地址并完成重定位
- 注意与操作系统的“地址重定位”区分
参考资料
- Wikipedia(zh):重定位:在链接时确定符号的最终地址
- Wikipedia(zh):链接器:合并目标文件并完成重定位
思考题答案(仅供参考)
思考题 1
因为那时还没有把所有文件合并、也没有决定整个程序的内存布局,外部符号最终会落在哪个地址无从得知。地址要在链接阶段统一分配之后才能确定。
思考题 2
需要目标文件里的重定位信息:它记录了哪些位置是对外部符号的引用、需要在链接时填入地址,链接器据此逐一修正。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪