Gcc x86_64:是否可以;“在线替代”;PLT/有参考资料吗?
我不确定这个问题的主题是什么,但我们来看看: 为了强制关键代码部分的代码局部性/紧凑性,我正在寻找一种方法,通过“跳转槽”(ELFGcc x86_64:是否可以;“在线替代”;PLT/有参考资料吗?,gcc,assembly,x86-64,ld,elf,Gcc,Assembly,X86 64,Ld,Elf,我不确定这个问题的主题是什么,但我们来看看: 为了强制关键代码部分的代码局部性/紧凑性,我正在寻找一种方法,通过“跳转槽”(ELFR\u X86\u 64\u跳转槽relocation)直接在调用站点调用外部(动态加载)库中的函数-链接器通常将其放入PLT/GOT中,但是,在调用站点将这些内联 如果我像这样模拟调用: #include <stdio.h> int main(int argc, char **argv) { asm ("push $1f\n\t"
R\u X86\u 64\u跳转槽
relocation)直接在调用站点调用外部(动态加载)库中的函数-链接器通常将其放入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;
}
也就是说,这仍然提供了两步重定向,首先将执行转移到PLT挂钩,然后跳转到库入口点
有没有一种方法可以指示编译器/汇编器/链接器在本例中“内联”地址0x400511
处的跳转槽目标
也就是说,将“本地”(在程序链接时由ld
解析)R_X86_64_64
reloc替换为“远程”(在程序加载时由ld.so
解析)R_X86_64_跳转\u插槽
一(并强制非延迟加载这段代码)?也许链接器映射文件可以实现这一点-如果是,如何实现
编辑:为了说明这一点,问题是如何在动态链接的可执行文件中/对于仅在动态库中可用的外部函数实现这一点。是的,静态链接确实以一种更简单的方式解决了这一问题,但是:
- 有些系统(如Solaris)的静态库通常不是由供应商提供的
- 有些库不能作为源代码或静态版本使用
我发现在某些体系结构中(SPARC,很明显,请参见),GNU能够使用修饰符为链接器创建某些类型的重新定位引用。引用的SPARC one将使用
%gdop(symbolname)
使汇编程序向链接器发出指令,说明“在此处创建重新定位”.Itanium上的英特尔汇编程序知道相同类型的@fptr(symbol)
(另请参见中的第4节)。但是x86_64是否存在一种等效机制(指示汇编程序在代码中的特定位置发出特定链接器重定位类型的机制)
我还发现GNU汇编程序有一个指令,应该用于此目的;但是,如果我尝试:
#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;
}
汇编程序创建一个对象文件,其偏移量0x5e8处的“.rela.text.startup”包含两个条目:
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
偏移信息类型符号的值符号的名称+加数
000000000000000 1 0000000 50000000A R_X86_64_32 0000000000000000.rodata.str1.1+0
00000000000000 17 0000000 B00000007 R\U X86\U 64\U跳转\U插槽0000000000000000打印F+0
这是我想要的-但链接器不接受它。链接器接受只使用上面的
R\u X86\u 64\u 64
;这样做会创建与第一种情况相同的二进制文件…重定向到printf@plt
,而不是“已解决”一个。您可以静态链接可执行文件。只需将-static
添加到最终链接命令,所有间接跳转将被直接调用取代。为了内联调用,您需要一个代码(.text
)重新定位,其结果是动态加载的共享库中函数的最终地址。使用GNU/Linux的GNU工具链,x86_64上不存在此类重新定位(现代静态链接器不允许),因此您无法按自己的意愿内联整个调用
您能得到的最接近的是通过GOT(避免PLT)的直接呼叫:
这将在上面的序列使用的GOT中针对printf生成一个R\u X86\u 64\u GLOB\u DAT
重定位。您需要避免使用C代码,因为一般来说,编译器可能会在序言和尾声中使用任何数量的调用方保存的寄存器,这将迫使您保存和恢复asm函数调用中的所有此类寄存器r有可能损坏这些寄存器,以便以后在包装器函数中使用。因此,在纯汇编中编写包装器更容易
另一个选项是使用-Wl,-z,now-Wl,-z,relro
进行编译,这确保在启动时解析PLT和与PLT相关的GOT条目,以增加代码的局部性和紧凑性。使用完全relro,您只需在PLT中运行代码并访问GOT中的数据,这两件事应该已经存在于逻辑核心。如果完全的重新注册足以满足您的需求,那么您就不需要包装器,您将获得额外的安全优势
最好的选项是真正的静态链接或LTO(如果您可用)。此优化已在GCC中实现。可通过和启用: 不要在位置无关的代码中使用PLT进行外部函数调用。相反,请在调用站点从GOT加载被调用方地址并将其分支到该地址。通过消除PLT存根并将GOT加载公开给优化,这将导致更高效的代码。在32位x86等体系结构上,PLT存根希望在特定的re中使用GOT指针gister,这为编译器提供了更多的寄存器分配自由。延迟绑定需要使用PLT;对于
-fno PLT
,所有外部符号都在加载时解析
或者,函数属性noplt
可用于避免通过PLT调用特定的外部函数
在位置相关代码中,一些目标还将调用转换为标记为不使用PLT的函数,以使用GOT
您只能在目标库的静态版本可用时执行此操作:(
printf
应该可用:-)好吧,我给您这个;-)但是示例就是这样,一个示例。Y
#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
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
.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