我有一个关于 ELF 动态符号表的问题。对于 FUNC 类型的符号,我注意到某些二进制文件中的值为 0。但在其他二进制文件中,它具有一些非零值。这两个二进制文件都是由 gcc 生成的,我想知道为什么会出现这种差异?有没有编译器选项来控制这个?
编辑:这是 readelf --dyn-syms prog1 的输出
Symbol table '.dynsym' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 000082f0 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.4 (2)
3: 00008314 0 FUNC GLOBAL DEFAULT UND abort@GLIBC_2.4 (2)
4: 000082fc 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.4
Run Code Online (Sandbox Code Playgroud)
这里“printf”符号的值为82f0,它恰好是printf的plt表条目的地址。
readelf --dyn-syms prog2 的输出
Symbol table '.dynsym' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.4 (2)
3: 00000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.4 (2)
4: 00000000 0 FUNC GLOBAL DEFAULT UND abort@GLIBC_2.4 (2)
5: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.4
Run Code Online (Sandbox Code Playgroud)
这里所有符号的值都为零。
x86_64 SV ABI要求(强调我的):
为了允许函数地址的比较按预期工作,如果可执行文件引用共享对象中定义的函数,则链接编辑器将将该函数的过程链接表条目的地址放置在其关联的符号表条目中。这将导致符号表条目的节索引为 SHN_UNDEF 但类型为 STT_FUNC 且st_value 为非零。可执行文件中的此类定义将满足对共享库内函数地址的引用。
在我的 GCC 中,这个程序:
#include <stdio.h>
int main()
{
printf("hello %i\n", 42);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
当直接编译成可执行文件时会生成空值:
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
Run Code Online (Sandbox Code Playgroud)
但这个程序与函数的比较printf
:
#include <stdio.h>
int main()
{
printf("hello %i\n", 42);
if (printf == puts)
return 1;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
生成一个非空值:
3: 0000000000400410 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
Run Code Online (Sandbox Code Playgroud)
在 .o 文件中,第一个程序生成:
000000000014 000a00000002 R_X86_64_PC32 0000000000000000 printf - 4
Run Code Online (Sandbox Code Playgroud)
第二个:
000000000014 000a00000002 R_X86_64_PC32 0000000000000000 printf - 4
000000000019 000a0000000a R_X86_64_32 0000000000000000 printf + 0
Run Code Online (Sandbox Code Playgroud)
差异是由于R_X86_64_32
获取函数地址的额外重定位造成的。
通过运行readelf
一些二进制文件进行观察
所有未定义的函数的大小为零。
\n\n这些未定义的函数是通过库调用的函数。在我的小型 ELF 二进制文件中,所有对 GLIBC 的引用均未定义,大小为零
\n\n来自http://docs.oracle.com/cd/E19457-01/801-6737/801-6737.pdf第 21 页
\n\n很明显,符号表可以包含三种类型的符号。在这三种符号中,有两种类型的UNDEFINED和TENTATIVE符号是没有分配存储空间的符号。在后面的情况下,您可以在 readelf 输出中看到一些不是未定义的函数(有索引)并且没有存储。
\n\n为了清楚起见,未定义的符号是那些被引用但未分配存储(尚未创建)的符号,而暂定符号是那些已创建但未分配存储的符号。例如未初始化的符号
\n\n编辑
\n\n如果您正在谈论 .plt,则共享库符号绑定是惰性的。
\n\n如何控制bind参见http://www.linuxjournal.com/article/1060
\n\n\n\n此功能称为惰性符号绑定。这个想法是,如果您有很多共享库,则动态加载程序可能会花费大量时间来查找所有函数以初始化所有 .plt 槽,因此最好将函数的绑定地址推迟到我们实际上需要它们。如果您最终只使用共享库中函数的一小部分,那么这将是一个巨大的胜利。在将控制权转移到应用程序\xe2\x80\x94之前,可以指示动态加载程序将地址绑定到所有 .plt 插槽,这是通过在运行程序之前设置环境变量LD_BIND_NOW =1来完成的。事实证明,这在某些情况下很有用,例如在调试程序时。另外,我应该指出 .plt 位于只读存储器中。因此,用于跳转目标的地址实际上存储在 .got 部分中。.got 还包含一组指针,指向来自共享库的程序中使用的所有全局变量。
\n