5

Go:对象文件&重定位

 4 years ago
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.
neoserver,ios ssh client

Go:对象文件&重定位

vearne · 大约17小时之前 · 66 次点击 · 预计阅读时间 4 分钟 · 大约8小时之前 开始浏览    

1_HxAju6n33e9Y8AJwMuQL3w.png

本文章基于 Go 1.14

重定位是链接过程中的一个阶段,重定位是链接过程中为每个外部符号分配适当地址。由于每个包都是单独编译的,因此它们不知道来自其它包的函数或者变量在哪里。 让我们从一个需要重定位的简单示例开始。

以下程序涉及两个不同的程序包:main 和 fmt。

1_4_DaAwHmqJbhwP8Tn10Dzg.png

构建此程序将首先涉及编译器,该编译器分别编译每个包。

1_4HLpept1qBXFJvL_r4qptQ.png

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,并列出其中包含的符号。下图是输出

1__cz0Ozr4acR3Sj0GbirP2Q.png

我们可以注意到,它需要使用 go 工具 nm 命令而不是本机 nm 命令。 实际上,Go 生成的目标文件(.o)具有自定义格式。

符号 U 代表未定义,表示编译器不知道该符号在哪里。该符号必须重定位,即找到 Println 的地址,才能成功的进行调用。这就是链接器需要参与的工作。在介绍链接器的工作之前,我们分析了目标文件 main.o, 以及它能够提供所有可用数据。链接器可以基于这些数据开展工作。

这篇文档解释了目标文件的内容和格式

1_WwlsAnj0J9-dUkvBYWS5sQ.png

该文件由依赖项,调试信息(DWARF), 索引符号列表,数据段以及符号列表。符号列表中包含每个符号都需要我们进行重定向。以下是它的格式:

1_so340hPaauZOPChu3tvSCA.png

每个符号均以十六进制字节 fe 开头。可以使用十六进制编辑器打开目标文件 main.o 时。例如,对于 Mac,可以使用 xxd(译者注:xxd 是 mac 下的一个命令)。 下面是内容的一部分,对符号(译者注:实际是对符号开头的标志"fe")进行了高亮显示。

1_PL_o1t7dokehoO3X6rbUaw.png

符号 main.main 是符号列表中的第一个符号。

1_KOng-8Ed1XkprfvxSsqaXg.png

前几个字节 0102 00dc 0100 dc01 0a 代表了前面定义的一系列属性:type、flag、size、data、以及重定位的次数。

字节以 zigzag-varint 格式存储。varint 是以可变长字节的方式存储整数的值。 zigzag 通过对最少有效位进行编码来是以减少编码后数据的大小

然后,重定位 Println 是一组字节序列 b201 0810 0008:

  • b201 是偏移值 89 编码后的结果。这个偏移值是一个 int32 类型。感谢 varint,存储它仅耗费了 2 个字节.
  • 08 是需要重写的字节的数量,编码后的值是 4
  • 10 是重定位的类型,编码值 8 表示 R_CALL, 即重定位函数调用
  • 08 是对索引符号的引用

装载器现在已经拥有了重定位所需的所有必要信息,可以生成可执行的二进制文件了。

链接器的其中一个阶段是分配虚地址给所有的段和指令。可以使用命令

objdump -h my-binary

可视化每个段的地址。下面是前面示例的输出

1_JGMu2mnGI-HTp35GHqx3mg.png

函数 main 位于__text 段,它也能通过命令

objdump -d my-binary

找到,这个命令显示了指令的地址。

1_tZX5Ills5d4Dnk0Z5iZ1pA.png

函数 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 原创翻译,Go语言中文网 首发。也想加入译者行列,为开源做一些自己的贡献么?欢迎加入 GCTT
翻译工作和译文发表仅用于学习和交流目的,翻译工作遵照 CC-BY-NC-SA 协议规定,如果我们的工作有侵犯到您的权益,请及时联系我们。
欢迎遵照 CC-BY-NC-SA 协议规定 转载,敬请在正文中标注并保留原文/译文链接和作者/译者等信息。
文章仅代表作者的知识和看法,如有不同观点,请楼下排队吐槽


有疑问加站长微信联系(非本文作者))

280

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK