Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

重定位

复习

  • 目标文件格式:含符号表与重定位信息
  • 程序在内存里的样子:代码区与数据区
  • 地址:内存位置的编号

TL;DR

  • 编译单个文件时,不知道外部符号的最终地址
  • 重定位就是把这些引用改成真实的地址
  • 它依赖目标文件里记录的“哪里需要改”
  • 链接器在合并目标文件时完成重定位

正文

  上一章留下一个悬念:目标文件里有一堆“需要填地址”的位置,等着将来被填上。这个“填地址”的动作,叫重定位(relocation)。

问题从哪来

  设想你在 a 文件里调用了 b 文件里的函数 foo。编译 a 时,你知道“要调用 foo”,却不知道 foo 最终会被放在哪个地址——因为那时还没把 b 合进来,也没决定整个程序怎么摆放。

  于是,a 的目标文件里,这条调用指令先留一个占位地址,同时记下一笔:“这里,等链接时填 foo 的真实地址。”

  类似地,访问一个外部变量、引用一段外部数据,都会产生这种“待填”的引用。

链接时统一解决

  等到链接阶段,所有目标文件汇到一起,链接器终于能总览全局:

  1. 决定各个段、各个符号最终摆在哪里(分配地址)
  2. 找到目标文件里所有“待填”的位置
  3. 把它们替换成真实的最终地址

  这一步做完,程序里所有的跨文件引用就都“对上号”了。重定位,本质上是把“临时的占位”换成“确定的地址”。

注意别混淆

  这里要提醒一句:编译器/链接器语境下的“重定位”,和操作系统里的“地址重定位”(程序装入内存时地址的调整)不是同一个东西

  这一章讲的是链接时的重定位:在生成可执行文件时,把地址确定下来。而程序运行、装入内存时可能还会有一层地址转换——那是操作系统部分的话题。同一个词,在不同层次有不同含义,要放回语境里理解。

  重定位是链接器的核心工作之一。下一章,我们看看把一堆目标文件连起来的完整过程。

思考题 1

  为什么单文件编译时,无法确定外部符号的地址?

思考题 2

  重定位需要目标文件提供什么信息?

小结

知识点

  • 单文件编译时外部符号地址未知
  • 目标文件先留占位并记录待填位置
  • 链接时统一分配地址并完成重定位
  • 注意与操作系统的“地址重定位”区分

参考资料

  1. Wikipedia(zh):重定位:在链接时确定符号的最终地址
  2. Wikipedia(zh):链接器:合并目标文件并完成重定位

思考题答案(仅供参考)

思考题 1

  因为那时还没有把所有文件合并、也没有决定整个程序的内存布局,外部符号最终会落在哪个地址无从得知。地址要在链接阶段统一分配之后才能确定。

思考题 2

  需要目标文件里的重定位信息:它记录了哪些位置是对外部符号的引用、需要在链接时填入地址,链接器据此逐一修正。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪