目标:使用可执行文件中的函数的共享库(不导出符号)。
意味着: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库/可执行文件中变得很有用,在我看来这是一个值得欢迎的进展。
答案 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
时,它会完全。您可以在objdump
,nm
和readelf -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 问题,而是问题。