我不确定这个问题的主题是什么,但是我们走了......
为了强制代码的关键部分的代码局部性/紧凑性,我正在寻找一种通过“跳槽”(ELF {{1)在外部(动态加载)库中调用函数的方法直接在调用站点 - 链接器通常放入PLT / GOT的内容,但是在调用站点处直接插入这些内容。
如果我模仿这样的电话:
R_X86_64_JUMP_SLOT
为了得到一个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但是(由于使用
#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;
}
作为直接,我猜......?)这里的目标地址仍然是PLT钩子的目标地址 - 相同的printf
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即。这仍然提供了两步重定向,首先将执行转移到PLT钩子,然后跳转到库入口点。
有没有办法可以指示编译器/汇编器/链接器 - 在本例中 - “内联”地址R_X86_64_64
的跳槽目标?即将“本地”(在0x400511
)ld
reloc解析为“远程”(在程序加载时由R_X86_64_64
解决)ld.so
一个(和强制这段代码的非延迟加载)?也许链接器mapfiles可能使这成为可能 - 如果是这样,怎么样?
修改
为了清楚说明,问题是关于如何在动态链接的可执行文件中实现这一点,或者只能在动态库中使用外部函数。是的,这是真正的静态链接以更简单的方式解决这个问题,但是:
因此静态链接在这里没有用处:(
EDIT2:
我发现在某些体系结构中(SPARC,显然请参见section on SPARC relocations in the GNU as manual),GNU能够使用修饰符为链接器就地创建某些类型的重定位引用。引用的SPARC将使用R_X86_64_JUMP_SLOT
使汇编程序向链接器发出指令,指出“在此处创建重定位”。英特尔在Itanium上的汇编程序知道同一类型的%gdop(symbolname)
link-relocation operator(另请参阅Itanium psABI中的第4节)。但是,对于x86_64,存在一种等效机制 - 指示汇编程序在代码中的特定位置发出特定的链接器重定位类型吗?
我还发现GNU汇编程序有一个.reloc
指令,据说可以用于此目的;如果我尝试,仍然是:
@fptr(symbol)
我从链接器收到错误(请注意#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;
}
):
error: /tmp/cc6BUEZh.o: unexpected reloc 7 in object file汇编程序创建一个
7 == R_X86_64_JUMP_SLOT
表示的对象文件: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这就是我想要的 - 但链接器不接受它 链接器 接受仅使用
readelf
而不是上面;这样做可以创建与第一种情况相同的二进制文件...重定向到R_X86_64_64
而不是“已解决”的那个......
答案 0 :(得分:2)
为了内联调用,您需要一个代码(.text
)重定位,其结果是动态加载的共享库中函数的最终地址。使用GNU / Linux的GNU工具链在x86_64上不存在这样的重定位(并且现代静态链接器不允许它们),因此您无法按照自己的意愿内联整个调用。
你最接近的是通过GOT直接打电话(避免PLT):
.section .rodata
.LC0:
.string "Hello, World!\n"
.text
.globl main
.type main, @function
main:
pushq %rbp
movq %rsp, %rbp
movl $.LC0, %eax
movq %rax, %rdi
call *printf@GOTPCREL(%rip)
nop
popq %rbp
ret
.size main, .-main
这应该在GOT中对printf生成R_X86_64_GLOB_DAT
重定位,以供上面的序列使用。您需要避免使用C代码,因为通常编译器可能会在序言和结尾中使用任意数量的调用者保存的寄存器,这会强制您围绕asm函数调用保存和恢复所有这些寄存器,否则可能会破坏这些寄存器以供以后使用在包装函数中。因此,在纯装配中编写包装器更容易。
另一种选择是使用-Wl,-z,now -Wl,-z,relro
进行编译,以确保在启动时解析PLT和PLT相关的GOT条目,以增加代码局部性和紧凑性。使用完整的RELRO,您只需要在PLT中运行代码并访问GOT中的数据,这两个应该已经存在于逻辑核心的缓存层次结构中。如果完全RELRO足以满足您的需求,那么您将不需要包装器,您将获得额外的安全性好处。
如果可以使用,最好的选项是静态链接或LTO。
答案 1 :(得分:1)
此优化已在GCC中实施。可以使用-fno-plt
option和noplt
function attribute启用它:
请勿将PLT用于与位置无关的代码中的外部函数调用。取而代之的是,从GOT将呼叫者地址加载到呼叫站点,然后跳转到该地址。通过消除PLT存根并将GOT负载暴露给优化,可以提高代码效率。在32位x86这样的体系结构上,PLT存根期望在特定寄存器中使用GOT指针,这为编译器提供了更大的寄存器分配自由度。延迟绑定需要使用PLT。使用
-fno-plt
时,所有外部符号都在加载时解析。或者,函数属性
noplt
可用于避免通过PLT调用特定的外部函数。在与位置相关的代码中,一些目标还将调用转换为标记为不使用PLT的函数,而改为使用GOT。
答案 2 :(得分:-1)
您可以静态链接可执行文件。只需将-static
添加到最终链接命令,所有间接跳转将被直接调用替换。