

Go:对象文件&重定位
source link: https://studygolang.com/articles/35196
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go:对象文件&重定位
vearne · 大约17小时之前 · 66 次点击 · 预计阅读时间 4 分钟 · 大约8小时之前 开始浏览本文章基于 Go 1.14
重定位是链接过程中的一个阶段,重定位是链接过程中为每个外部符号分配适当地址。由于每个包都是单独编译的,因此它们不知道来自其它包的函数或者变量在哪里。 让我们从一个需要重定位的简单示例开始。
以下程序涉及两个不同的程序包:main 和 fmt。
构建此程序将首先涉及编译器,该编译器分别编译每个包。
go tool compile -S -l main.go
我们可以查看指令在中间文件(译者注:目标文件)中的临时地址。
一旦我们的程序被编译,我们可以使用
go tool compile -S -l main.go
来查看程序对应的汇编代码
要查看编译器生成的指令,你有多种不同的方法:
- 重新编译,并打印汇编指令。命令是:
go tool compile -S -l main.go
"".main STEXT size=137 args=0x0 locals=0x58
0x0000 00000 (main.go:7) TEXT "".main(SB)
[...]
0x0058 00088 (main.go:8) CALL fmt.Println(SB)
参数 -l
用于避免内联,使得汇编代码更容易被阅读。
生成的汇编文件表明调用 Println
的指令相对 main
函数入口偏移 88 个字节。这个偏移对于链接器重新定位函数调用将会非常有用。
- 使用以下命令,反汇编已经生成的 main.o:
go tool objdump main.o
TEXT %22%22.main(SB)
[...]
main.go:8 0x57e e800000000 CALL 0x583 [1:5]R_CALL:fmt.Println
标识符 R_CALL
代表重定位调用
然而由于该函数属于另一个包,因此编译器不知道该函数实际位于何处。使用命令:
go tool nm main.o
可以检查生成的文件 main.o
,并列出其中包含的符号。下图是输出
我们可以注意到,它需要使用 go 工具 nm 命令而不是本机 nm 命令。 实际上,Go 生成的目标文件(.o)具有自定义格式。
符号 U 代表未定义,表示编译器不知道该符号在哪里。该符号必须重定位,即找到 Println
的地址,才能成功的进行调用。这就是链接器需要参与的工作。在介绍链接器的工作之前,我们分析了目标文件 main.o
, 以及它能够提供所有可用数据。链接器可以基于这些数据开展工作。
这篇文档解释了目标文件的内容和格式
该文件由依赖项,调试信息(DWARF), 索引符号列表,数据段以及符号列表。符号列表中包含每个符号都需要我们进行重定向。以下是它的格式:
每个符号均以十六进制字节 fe 开头。可以使用十六进制编辑器打开目标文件 main.o 时。例如,对于 Mac,可以使用 xxd(译者注:xxd 是 mac 下的一个命令)。 下面是内容的一部分,对符号(译者注:实际是对符号开头的标志"fe")进行了高亮显示。
符号 main.main
是符号列表中的第一个符号。
前几个字节 0102 00dc 0100 dc01 0a
代表了前面定义的一系列属性:type、flag、size、data、以及重定位的次数。
字节以 zigzag-varint
格式存储。varint
是以可变长字节的方式存储整数的值。 zigzag
通过对最少有效位进行编码来是以减少编码后数据的大小
然后,重定位 Println
是一组字节序列 b201 0810 0008
:
b201
是偏移值 89 编码后的结果。这个偏移值是一个int32
类型。感谢varint
,存储它仅耗费了 2 个字节.08
是需要重写的字节的数量,编码后的值是 410
是重定位的类型,编码值 8 表示R_CALL
, 即重定位函数调用08
是对索引符号的引用
装载器现在已经拥有了重定位所需的所有必要信息,可以生成可执行的二进制文件了。
链接器的其中一个阶段是分配虚地址给所有的段和指令。可以使用命令
objdump -h my-binary
可视化每个段的地址。下面是前面示例的输出
函数 main
位于__text 段,它也能通过命令
objdump -d my-binary
找到,这个命令显示了指令的地址。
函数 main
入口地址是 109cfa0
,函数 fmt.Println
的入口地址是 1096a00
。一旦虚地址被分配,就会非常容易的重定位 fmt.Println
的入口地址。链接器将会用 fmt.Println
的入口地址依次减去 main
的入口地址、指令的偏移值、指令所占的字节大小。这样我们就能得到调用 fmt.Println
的全局偏移。对于前面的例子中,我们可以进行如下的操作:
1096a00 (fmt.Println) — 109cfa0 (main) — 84 (offset inside the main function) — 4 (size) = -26109
现在,指令知道函数 fmt.Println
的入口地址与当前内存地址的偏移是 -26109
,调用可以成功执行。
via: https://medium.com/a-journey-with-go/go-object-file-relocations-804438ec379b
作者:Vincent Blanchon 译者:vearne 校对:unknwon
本文由 GCTT 原创翻译,Go语言中文网 首发。也想加入译者行列,为开源做一些自己的贡献么?欢迎加入 GCTT!
翻译工作和译文发表仅用于学习和交流目的,翻译工作遵照 CC-BY-NC-SA 协议规定,如果我们的工作有侵犯到您的权益,请及时联系我们。
欢迎遵照 CC-BY-NC-SA 协议规定 转载,敬请在正文中标注并保留原文/译文链接和作者/译者等信息。
文章仅代表作者的知识和看法,如有不同观点,请楼下排队吐槽
有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK