我一直认为解析绝对地址完全是链接器的工作。也就是说,在链接器将所有目标文件合并为一个可执行文件之后,它将修改所有绝对地址以反映可执行文件中的新位置。但在阅读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
加载到链接器指定的虚拟地址。但是如果加载器没有以这种方式加载程序,那么就不会破坏所有绝对地址引用。
答案 0 :(得分:1)
程序通常由链接器创建的几个模块组成。有可执行文件,通常还有许多共享库。在某些系统上,一个可执行文件可以加载另一个可执行文件并将其作为函数调用它的启动例程。
如果所有这些编译的用途都有固定地址,则加载时可能会发生冲突。如果两个链接的模块使用相同的地址,则无法加载应用程序。
几十年来,可重定位代码一直是解决该问题的方法。可以在任何地方加载模块。有些系统会将此操作带到下一步,并随机将模块放在内存中以确保安全。
在某些情况下,代码无法完全重定位。
如果你有这样的事情:
static int b, *a = &b ;
初始化取决于模型在内存中的位置(以及" b"所在的位置)。链接器通常会为这样的构造生成信息,以便加载器可以修复它们。
因此,这是不对的:
我一直认为解析绝对地址完全是链接器的工作。
答案 1 :(得分:0)
据我所知,情况并非如此。
如果它是静态链接的,那么函数的地址由链接器静态计算。因为相对地址是已知的,所以发出相对函数调用,一切都会好的。
如果它是动态链接的,则ld.so进入并加载lib。该符号由Load-time relocation of shared libraries 或Position Independent Code (PIC) in shared libraries 解析(这两篇文章不是我写的)。
简单地说,
加载时重定位是通过重写代码来为它们提供正确的地址来完成的,这会禁用wirte-protect并在不同的进程之间共享。
通过添加2个名为GOT和PLT的部分完成PIC,所有部分都在链接时可以知道的特定地址。在动态库中调用函数将首先调用... @ plt函数(E.x.printf @ plt),然后调用jump *GOT[offset]
。在第一次调用时,这实际上是下一条指令的地址,它将调用动态加载程序来加载函数。在第二次调用时,这将是函数的地址。如您所见,与普通代码相比,这会增加内存和时间。