我不确定这个问题的主题是什么,但是我们走了......
为了强制代码的关键部分的代码局部性/紧凑性,我正在寻找一种方法,通过直接在呼叫站点的"jump slot"(ELF R_X86_64_JUMP_SLOT
重定位)调用外部(动态加载)库中的函数 - 链接器通常放入PLT / GOT的内容,但是在呼叫站点上有这些内联 .
如果我模仿这样的调用:
#include <stdio.h>
int main(int argc, char **argv)
{
asm ("push $1f\n\t"
"jmp *0f\n\t"
"0: .quad %P0\n"
"1:\n\t"
: : "i"(printf), "D"("Hello, World!\n"));
return 0;
}
为了得到一个64位字的空间,这个电话本身是有效的(拜托,没有评论这是幸运的巧合,因为这打破了某些ABI规则 - 所有这些都不是这个问题的主题......而且,对于我的情况,可以是以其他方式解决/解决,我试图保持这个例子简短) .
它创建以下程序集:
0000000000000000 <main>:
0: bf 00 00 00 00 mov $0x0,%edi
1: R_X86_64_32 .rodata.str1.1
5: 68 00 00 00 00 pushq $0x0
6: R_X86_64_32 .text+0x19
a: ff 24 25 00 00 00 00 jmpq *0x0
d: R_X86_64_32S .text+0x11
...
11: R_X86_64_64 printf
19: 31 c0 xor %eax,%eax
1b: c3 retq
但是(由于使用 printf
作为即时,我猜......?)这里的目标地址仍然是PLT钩子的目标地址 - 相同的 R_X86_64_64
reloc . 将目标文件与libc链接到实际可执行文件中的结果如下:
0000000000400428 <printf@plt>:
400428: ff 25 92 04 10 00 jmpq *1049746(%rip) # 5008c0 <_GLOBAL_OFFSET_TABLE_+0x20>
[ ... ]
0000000000400500 <main>:
400500: bf 0c 06 40 00 mov $0x40060c,%edi
400505: 68 19 05 40 00 pushq $0x400519
40050a: ff 24 25 11 05 40 00 jmpq *0x400511
400511: [ .quad 400428 ]
400519: 31 c0 xorl %eax, %eax
40051b: c3 retq
[ ... ]
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
[ ... ]
00000000005008c0 R_X86_64_JUMP_SLOT printf
I.e . 这仍然提供了两步重定向,首先将执行转移到PLT钩子,然后跳转到库入口点 .
有没有办法如何指示编译器/汇编器/链接器 - 在本例中 - "inline"地址 0x400511
的跳槽目标?即将"local"(在程序链接时解析 ld
) R_X86_64_64
reloc替换为"remote"(在程序加载时由 ld.so
解析) R_X86_64_JUMP_SLOT
one(并强制执行此段代码的非延迟加载)?也许链接器mapfiles可能使这成为可能 - 如果是这样,怎么样?
Edit:
为了清楚说明,问题是关于如何在动态链接的可执行文件/外部函数中实现这一点's only available in a dynamic library. Yes, it'真正的静态链接以更简单的方式解决这个问题,但是:
-
有些系统(如Solaris),供应商通常不提供静态库
-
有些库既不是源代码也不是静态版本
因此静态链接在这里没有用:(
Edit2:
我发现在某些体系结构中(SPARC,显然,参见section on SPARC relocations in the GNU as manual),GNU能够使用修饰符就地为链接器创建某些类型的重定位引用 . 引用的SPARC将使用 %gdop(symbolname)
使汇编程序向链接器发出指示"create that relocation right here"的指令 . 英特尔在Itanium上的汇编程序知道 @fptr(symbol)
link-relocation operator同样的事情(参见Itanium psABI中的第4节) . 但是,对于x86_64,是否存在等效机制 - 指示汇编程序在代码中的特定位置发出特定的链接器重定位类型?
我还发现GNU汇编程序有一个.reloc指令,据说可以用于此目的;如果我尝试:
#include <stdio.h>
int main(int argc, char **argv)
{
asm ("push %%rax\n\t"
"lea 1f(%%rip), %%rax\n\t"
"xchg %%rax, (%rsp)\n\t"
"jmp *0f\n\t"
".reloc 0f, R_X86_64_JUMP_SLOT, printf\n\t"
"0: .quad 0\n"
"1:\n\t"
: : "D"("Hello, World!\n"));
return 0;
}
我从链接器收到错误(请注意 7 == R_X86_64_JUMP_SLOT
):
error: /tmp/cc6BUEZh.o: unexpected reloc 7 in object file
汇编程序创建了一个目标文件, readelf
表示:
Relocation section '.rela.text.startup' at offset 0x5e8 contains 2 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000000001 000000050000000a R_X86_64_32 0000000000000000 .rodata.str1.1 + 0
0000000000000017 0000000b00000007 R_X86_64_JUMP_SLOT 0000000000000000 printf + 0
这就是我想要的 - 但链接器不接受它 .
链接器确实接受只使用 R_X86_64_64
而不是上面;这样做会创建与第一种情况相同的二进制文件...重定向到 printf@plt
而不是"resolved"一个...
2 回答
为了内联调用,您需要一个代码(
.text
)重定位,其结果是动态加载的共享库中函数的最终地址 . 在x86_64上使用GNU / Linux的GNU工具链不存在这样的重定位(并且现代静态链接器不允许它们),因此您不能按照您的意愿内联整个调用 .最接近的是通过GOT直接调用(避免PLT):
这应该对GOT中的printf产生
R_X86_64_GLOB_DAT
重定位,以供上面的序列使用 . 您需要避免使用C代码,因为通常编译器可能会在序言和结尾中使用任意数量的调用者保存的寄存器,这会强制您围绕asm函数调用保存和恢复所有这些寄存器,否则可能会破坏这些寄存器以供以后使用在包装函数中 . 因此,在纯装配中编写包装器更容易 .另一种选择是使用
-Wl,-z,now -Wl,-z,relro
进行编译,以确保在启动时解析PLT和PLT相关的GOT条目,以增加代码局部性和紧凑性 . 使用完整的RELRO,你需要包装器,你会增加安全性 .最好的选择是静态链接或LTO(如果是)可以使用 .
您可以静态链接可执行文件 . 只需将
-static
添加到最终链接命令,所有间接跳转将被直接调用替换 .