繁体   English   中英

没有符号解析怎么能编译?

[英]How can compilation occur without symbol resolution?

这是我的问题。 假设要编译 c 代码:

void some_function() {
  write_string("Hello, World!\n");
}

对于这个例子,我想特别关注字符串:“Hello, World.\n”。 我的理解是编译器会将字符串放入elf文件中的.rodata部分,A符号。 指的是它在,rodata 部分中的位置。 被添加到符号表中,并且该符号作为字符串位置的占位符保存在 .text 部分中。

这就是问题所在。 你怎么能在机器代码中留下这样一个未解析的值? 在 x86 中,当位置已知时,linker 应该很容易对符号进行查找和替换。 但是,在许多 CPU 架构中,无法将地址整体编码为单个机器指令。 因此,该值必须分两个阶段加载,使用单独的机器指令,linker 必须弄清楚这一点。 它必须足够聪明才能操纵机器代码,其中一半的地址在一个地方,一半的地址在另一个地方。 此外,不知何故,elf 文件必须在稍后为 linker 表示这种复杂的编码方案。 这一切如何运作?

我的大多数程序,这将在用户空间应用程序中。 因此,kernel 可以在 memory 中的任何位置加载 .rodata 部分。 所以看起来,当程序被加载时,不知何故,在运行时,kernel 加载程序必须在开始执行之前解析程序中的所有这些符号。 它必须注入到放置每个部分的机器代码中,以便可以适当地引用它们。 这是如何运作的?

我有一种感觉,我的理解和上述描述是错误的,或者我遗漏了一些非常重要的东西,因为这对我来说似乎不正确。 以太,或者实际上有逻辑在现代内核和链接器中执行这些复杂的功能。 我正在寻找一些进一步的解释和理解。

编译发生,发出如下内容:

lea rdi, [rip+some_function.hello_world]
mov rax, [rip+some_function.write_string]
call rax

在 asm 通过之后,我们最终得到了一些反汇编的东西

lea rdi, [rip+00000000]
mov rax, [rip+00000000]
call rax

其中两个00000000插槽被填充为加载时修复。 加载器执行符号解析并用正确的值填充00000000值。

这是一个简化。 实际上,有一个额外的间接层称为全局偏移表,它用于(除其他外)将所有修正彼此相邻。

它的工作原理是特定于 CPU 和操作系统的,但通常你不必真正关心它是如何工作的,它可能会在编译器的下一个版本中发生变化(并且已经改变了至少两次)。 加载器使用修复表在非常通用的级别上理解修复,并且可以处理新想法,只要它们决定将符号的(绝对或相对)地址放在偏移量 + 大小处。

Alpha 处理器在当时有点糟糕。 修正必须在函数之间,并且相对寻址只能在有符号的 16 位大小中完成,因此函数的修正位于每个 function 之前或之后,如果指针没有,您可能会在 ASM 传递中出错不适合,因为 function 太大。 我确实想出了一个巧妙的序列,可以解决 Alpha 上的问题,但那是在平台退役很久之后,没有人再关心了,所以它从未得到实施。

我记得在装载机可以进行良好修补之前的糟糕日子。 曾经有一个共享库加载地址的全局(我真的是指全局)表,编译器发出绝对地址,如果您更改了库,即使您使用了共享库,您也必须重新构建您的应用程序。 这并不是最聪明的想法,难怪人们会保持静态链接的紧急二进制文件。 破坏 libc 并不好玩。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM