加载程序是否在程序启动时修改重定位信息?

does the loader modify relocation information on program startup?

我一直认为解析绝对地址完全是链接器的工作。也就是说,链接器将所有目标文件组合成一个可执行文件后,它会修改所有绝对地址以反映可执行文件中的新位置。但是在读到 here 加载器不必将程序文本放置在链接器指定的地址后,我真的很困惑。

以这段代码为例

Main.c

 void printMe();
int main(){
    printMe();

    return 0;

}

Foo.c

/* Lots of other functions*/
void printMe(){
     printf("Hello");
}

假设链接后,main 的代码被放置在地址 0x00000010,printMe 的代码被放置在地址 0x00000020。然后当程序启动时,加载器确实会将 main 和 printMe 加载到链接器指定的虚拟地址。但是如果加载程序不以这种方式加载程序,那么不会破坏所有绝对地址引用。

据我所知,这里不是这样的。

如果是静态link,那么函数的地址是linker静态计算的。因为知道相对地址,所以发出相对函数调用,就万事大吉了。

如果它是动态 linked,那么 ld.so 会进入并加载库。该符号通过 共享库的加载时重定位来解析 或通过共享库中的 位置独立代码 (PIC) (这两篇不是我写的)

简单地说,

  1. 加载时重定位是通过重写代码为它们提供正确的地址来完成的,这会禁用 wrte-protect 和在不同进程之间共享。

  2. PIC 是通过添加 2 个部分(称为 GOT 和 PLT)来完成的,它们都位于一个特定的地址,可以在 link 时知道。调用动态库中的函数将首先调用 ...@plt 函数 (E.x.printf@plt),然后它将 jump *GOT[offset]。在第一次调用时,这实际上是下一条指令的地址,它将调用动态加载器来加载函数。在第二次调用时,这将是函数的地址。如您所见,与普通代码相比,这需要额外的内存和时间。

一个程序通常由链接器创建的几个模块组成。有可执行文件,通常还有一些共享库。在某些系统上,一个可执行文件可以加载另一个可执行文件并将其启动例程作为函数调用。

如果所有这些编译使用都有固定地址,加载时很可能会发生冲突。如果两个链接模块使用相同的地址,则应用程序无法加载。

几十年来,可重定位代码一直是该问题的解决方案。模块可以在任何地方加载。一些系统将此带到下一步并随机将模块放置在内存中以确保安全。

有些情况下代码不能完全重定位。

如果你有这样的事情:

static int b, *a = &b ;

初始化取决于模型在内存中的位置(以及 "b" 所在的位置)。链接器通常会为此类构造生成信息,以便加载程序可以修复它们。

因此,这是不正确的:

I have always believed that resolving absolute addresses is completely the linker's job.