考虑以下程序:
#include <stdio.h>
__attribute__((noinline))
int add(int a, int b)
{
return a + b;
}
int add_external_def(int a, int b);
int main()
{
printf("5 + 5 = %d\n", add(5, 5));
printf("5 + 5 = %d\n", add_external_def(5, 5));
return 0;
}
使用 Clang 17.0.1(对于 x86-64)使用
-O3
编译此程序将生成一些基本上符合我预期的程序集。在此程序集中,我们将 add
、add_external_def
和 printf
称为如下 (godbolt link):
call add
call printf@PLT
call add_external_def@PLT
我的理解是,
printf
需要动态链接器/加载器解析,而add_external_def@PLT
可能只会在链接时优化期间被替换(如果我错了,请纠正我)。
但是,如果我选中“编译为二进制对象”,这些调用将分别替换为以下内容
call 20 <main+0x10>
R_X86_64_PLT32 add-0x4
call 33 <main+0x23>
R_X86_64_PLT32 printf-0x4
call 42 <main+0x32>
R_X86_64_PLT32 add_external_def-0x4
这
R_X86_64_PLT32 ...-0x4
部分有何意义? “这个功能是否要通过PLT解决”的信息哪里去了?
PLT是程序链接表 它用于调用动态加载的库
示例:
printf@plt
是一个小存根,它调用真正的printf
函数,在途中修改一些东西以使后续调用更快。
真实的
printf
被映射到进程地址空间中的任意位置
第一次调用GOT设置后,后续调用比第一次调用要快。