【问题标题】:ELF dynamic symbol tableELF动态符号表
【发布时间】:2015-12-20 15:21:58
【问题描述】:

我有一个关于 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 

这里所有符号的值都是零。

【问题讨论】:

  • 创建 prog1 和 prog2 的确切命令是什么?
  • prog2: gcc -o prog2 prog2.c。但是关于prog1,我不知道。我想知道是否有任何选项可以创建此类二进制文件。

标签: gcc elf


【解决方案1】:

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 重定位导致的,用于获取函数的地址。

【讨论】:

  • 这应该是答案
【解决方案2】:

通过在某些二进制文件上运行 readelf 进行观察

所有未定义的函数的大小为零。

这些未定义的函数是通过库调用的函数。在我的小型 ELF 二进制文件中,所有对 GLIBc 的引用都未定义,大小为零

来自第 21 页的http://docs.oracle.com/cd/E19457-01/801-6737/801-6737.pdf

很明显,符号表可以包含三种类型的符号。在这三种中,两种类型的 UNDEFINED 和 TENTATIVE 符号是那些没有分配存储的符号。在以后的情况下,您可以在 readelf 输出中看到一些未定义(具有索引)且没有存储的函数。

为清楚起见,未定义符号是那些被引用但未分配存储(尚未创建)的符号,而暂定符号是那些已创建但未分配存储的符号。例如未初始化的符号

编辑

如果您在谈论 .plt,共享库符号绑定是惰性的。

如何控制绑定见http://www.linuxjournal.com/article/1060

此功能称为惰性符号绑定。这个想法是,如果您有很多共享库,则动态加载程序可能会花费大量时间来查找所有函数以初始化所有 .plt 插槽,因此最好将绑定地址推迟到函数,直到我们实际上需要它们。如果您最终只使用共享库中的一小部分函数,​​这将是一个巨大的胜利。在将控制权转移给应用程序之前,可以指示动态加载程序将地址绑定到所有 .plt 插槽 - 这是通过在运行程序之前设置环境变量 LD_BIND_NOW=1 来完成的。例如,在调试程序时,这在某些情况下很有用。另外,我应该指出 .plt 位于只读内存中。因此,用于跳转目标的地址实际上存储在 .got 部分中。 .got 还包含一组指针,用于在来自共享库的程序中使用的所有全局变量。

【讨论】:

  • 这里我不是指函数的大小,我指的是对应于 GLIBC 函数的动态符号的值,我是指 .PLT 中的条目地址,从该地址跳转到实际的 glibc 函数(由 libc.so 提供)。我注意到这个条目在一些二进制文件中是非零的。
  • 因为共享库在运行时绑定地址而不是编译时。
  • 是的,但是动态 FUNC 符号的地址不是加载库例程的地址,而是对应 libc 例程的二进制文件中 plt 条目的地址。我将编辑我的问题以添加 readelf --dyn-syms 的屏幕截图以使您相信。
  • 我已编辑问题以获取更多详细信息,请参阅。谢谢
猜你喜欢
  • 2016-10-12
  • 2012-09-21
  • 2011-10-29
  • 2012-08-28
  • 2014-07-11
  • 2015-07-07
  • 2013-12-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多