我对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
" 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
此处所有符号的值均为零。
答案 0 :(得分:2)
x86_64 SV ABI强制要求(强调我的):
允许比较函数地址按预期工作, 如果可执行文件引用共享对象中定义的函数, 链接编辑器将放置过程链接表的地址 该函数在其关联的符号表条目中的条目。 这将导致符号表条目的节索引为 SHN_UNDEF但是一种STT_FUNC和非零st_value 。 从共享内引用函数的地址 图书馆会满意的 通过可执行文件中的这种定义。
我的GCC,这个程序:
#include <stdio.h>
int main()
{
printf("hello %i\n", 42);
return 0;
}
直接编译成可执行文件时会生成一个空值:
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
但是这个程序与printf
函数的比较:
#include <stdio.h>
int main()
{
printf("hello %i\n", 42);
if (printf == puts)
return 1;
return 0;
}
生成非空值:
3: 0000000000400410 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
在.o文件中,第一个程序生成:
000000000014 000a00000002 R_X86_64_PC32 0000000000000000 printf - 4
和第二个:
000000000014 000a00000002 R_X86_64_PC32 0000000000000000 printf - 4
000000000019 000a0000000a R_X86_64_32 0000000000000000 printf + 0
差异是由获取函数地址的额外R_X86_64_32
重定位引起的。
答案 1 :(得分:1)
在某些二进制文件上运行readelf
进行观察
所有未完成的功能都为零。
这些未定义的函数是通过库调用的函数。在我的小型ELF二进制文件中,所有对GLIBc的引用都未定义,大小为零
第21页的http://docs.oracle.com/cd/E19457-01/801-6737/801-6737.pdf
很明显,符号表可以有三种类型的符号。在这三种中,两种类型的UNDEFINED和TENTATIVE符号是那些没有分配存储的符号。在后面的例子中,您可以在readelf输出中看到一些未定义的函数(具有索引)并且没有存储。
为清楚起见,未定义的符号是那些被引用但未分配存储(尚未创建)的符号,而暂定符号是那些被创建但没有分配存储的符号。例如未初始化的符号
编辑
如果你在谈论.plt,共享库符号bind是懒惰的。
如何控制绑定请参阅http://www.linuxjournal.com/article/1060
此功能称为延迟符号绑定。这个想法是,如果你有很多共享库,它可能需要动态加载器很多时间来查找所有函数来初始化所有.plt插槽,所以最好将绑定地址推迟到函数,直到我们确实需要它们。如果您最终只使用共享库中的一小部分功能,那么这将是一个巨大的胜利。在将控制权转移到应用程序之前,可以指示动态加载程序将地址绑定到所有.plt插槽 - 这是通过在运行程序之前设置环境变量 LD_BIND_NOW = 1 来完成的。例如,在调试程序时,这在某些情况下非常有用。另外,我应该指出.plt在只读内存中。因此,用于跳转目标的地址实际上存储在.got部分中。 .got还包含一组指针,用于指向来自共享库的程序中使用的所有全局变量。