为什么链接器会修改--defsym“绝对地址”

时间:2011-12-03 14:43:54

标签: linux linker shared

目标:使用可执行文件中的函数的共享库(不导出符号)。

意味着:gcc -Wl,--defsym,function=0x432238

手册页指出:

"--defsym symbol=expression" Create a global symbol in the output
file, containing the absolute address given by expression.

令我沮丧的是,dlopen()正在将0x7ffff676f000,共享库的基地址(这是64位代码)添加到导出的“绝对符号地址”中:

        executable        shared library
        ---------- linker --------------
symbol: 0x432238   =====> 0x7ffff6ba1238

objdump在库中显示正确的符号地址(0x432238),但加载dlopen()后,符号的地址为0x7ffff6ba1238

如果,一旦加载,我手动将库符号修补到正确的地址,然后一切正常(否则,库SEGFAULTs)。

  • 为什么修改“绝对地址”?
  • 如何避免?

更新

我对下面回复的技术相关性提出质疑,更多的是“更新”:

使用--defsym在PIC库/可执行文件中定义重定位符号是没有意义的(除了在没有任何可用功能的情况下污染二进制文件之外,它不起任何作用)。

因此,PIC共享库或PIC可执行文件中唯一相关的--defsym用法应该是定义(非重定位的)“绝对地址”。

顺便说一下,如果您懒得阅读手册页,这就是--defsym的官方用途:

“在输出文件中创建一个全局符号,其中包含由表达式给出的absolute address。”

充其量,这是一个Linux链接器的影响,这将是微不足道的修复。对于那些不能等待拒绝人员实现(并修复)他们的错误的人来说,解决方案是在缺陷链接器加载二进制映像后修补重定位表。

然后,-defsym在PIC库/可执行文件中变得很有用,在我看来这是一个值得欢迎的进展。

3 个答案:

答案 0 :(得分:8)

您似乎从根本上误解了--defsym的作用。

--defsym=symbol=expression
   Create a global symbol in the *output* file, ...

也就是说,您正在构建正在构建的库中的新符号。因此,符号(自然地)与库重新定位。

我猜你想要这样的东西:

// code in library
int fn()
{
    // exe_fn not exported from the executable, but we know where it is.
    int (*exe_fn)(void) = (int (*)(void)) 0x432238;
    return (*exe_fn)();
}

如果您不想将0x432238硬编码到库中,而是在构建时在命令行上传递值,只需使用-DEXE_FN=0x432238即可实现。

<强>更新

  

目标:使用可执行文件中的函数的共享库

您选择的方法无法实现 目标。你必须使用其他方法。

  

为什么要修改“绝对地址”?

不是。当您要求链接器在绝对地址function定义0x432238时,它会完全。您可以在objdumpnmreadelf -s输出中看到它。

但是因为符号在共享库中定义,所有引用到该符号都被重定位,即由共享库加载地址调整(由动态装载机)。它使动态加载器毫无意义

  

如何避免它?

你做不到。使用其他方法来实现目标。

答案 1 :(得分:0)

添加一个对立点:是的,它有实际用途,但我认为它确实被破坏了,不仅对于动态库,而且对于位置无关的可执行文件也是如此。

ld本身在将二进制文件嵌入可执行文件时将使用符号:

ld -r -b binary hello_world.txt -o hello_world.o

这将产生一个带有以下符号的目标文件:

000000000000000c g       .data  0000000000000000 _binary_hello_world_txt_end
000000000000000c g       *ABS*  0000000000000000 _binary_hello_world_txt_size
0000000000000000 g       .data  0000000000000000 _binary_hello_world_txt_start

,以便包含它们的可执行文件可以仅使用extern变量来访问它们。 (...如上:来自hello_world.txt的“ hello world”文本是.data部分中唯一的内容,长度为0xc。)

将此目标文件链接到可执行文件中(而不去除符号)会导致

0000000000411040 g     .data  0000000000000000              _binary_hello_world_txt_start
000000000041104c g     .data  0000000000000000              _binary_hello_world_txt_end
000000000000000c g     *ABS*  0000000000000000              _binary_hello_world_txt_size

我们可以做类似的事情

extern char _binary_hello_world_txt_start;
extern char _binary_hello_world_txt_size; // "char" is just made up in this one

// (...)
printf("text: %s\n", &_binary_hello_world_txt_start);
printf("number of bytes in it: %d\n", (int) (&_binary_hello_world_txt_size));

(是的,我们正在寻找某物的地址(通常使用符号),然后将其视为整数...,但实际上有效,这看起来很奇怪。)

还要注意,链接器如何知道应该重定位的对象以及不应该重定位的对象;数据指针是相对于.data的,而大小是*ABS*的,正如Gil所描述的,它不应该被重定位(...因为它没有相对于任何东西进行计算)。 / p>

但是,这仅在与位置无关的可执行文件中起作用。。一旦从-fPIE(看起来像是现代Linux发行版中gcc的默认值)转到-no-pie,动态链接器就会重新定位所有内容,包括*ABS*符号。这是在运行时链接时发生的:符号表看起来相同,而与可执行文件的编译方式无关。

共享库发生相同的事实似乎是同一件事的结果:动态放置的二进制文件(位置无关的可执行文件或共享库)的重定位会导致类似的重定位,这确实有意义适用于二进制文件本身包含的功能,但不适用于*ABS*数据。

可悲的是,我对以下两个问题都没有答案:我也认为它做得不正确,而且我也不知道如何解决(有关遇到相同问题的另一个问题,请参见Getting the value of *ABS* symbols from C

但是,考虑到GNU ld本身如何选择以这种方式嵌入大小作为符号...我确实认为此应用程序/问题完全有效,因此对于答案:

  • ...之所以完成,是因为实施方式实际上不正确
  • 作为解决方法,请遵循俄文的回答
  • “使用内联绝对地址生成头文件”

...但是我实际上会对如何按照问题中提到的方式修补重定位表感兴趣!

答案 2 :(得分:0)

--defsym 的行为在 gcc 5.4.0(使用 Ubuntu 16.04.4)和 7.3.0(Ubuntu 18.04)之间发生了变化。在 5.40,--defsym 创建了一个符号,表示绝对的、不可重定位的地址。在 7.3.0 中,readelf -s 将符号显示为“ABS”,但实际上该符号在程序执行时已重新定位。 (这也给我的应用程序带来了问题。)

绝对地址可能代表内存映射设备寄存器或中断向量之类的东西,无论应用程序加载到哪里,它都停留在一个地方。较旧的行为是正确的 - 绝对地址不得重定位。如果在可执行映像加载到内存时发生重定位,则可能不是 gcc 问题,而是问题。