x86_64:是否可以"在线替代"PLT/GOT参考?

Fra*_*kH. 6 assembly gcc x86-64 elf ld

我不确定这个问题的主题是什么,但是我们走了......

为了强制代码的关键部分的代码局部性/紧凑性,我正在寻找一种方法在调用时R_X86_64_JUMP_SLOT直接通过"跳槽"(ELF 重定位)在外部(动态加载)库中调用函数site - 链接器通常放入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;
}
Run Code Online (Sandbox Code Playgroud) 为了得到一个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_64reloc.将目标文件与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钩子,然后跳转到库入口点.

有没有一种方法可以指示编译器/汇编器/链接器 - 在本例中 - "内联"地址的跳槽目标0x400511?即将"本地"(在程序链接时解析ld)R_X86_64_64reloc替换为"remote"(在程序加载时解析ld.so)R_X86_64_JUMP_SLOT1(并强制执行此部分代码的非延迟加载)?也许链接器mapfiles可能使这成为可能 - 如果是这样,怎么样?

编辑:
为了清楚说明,问题是如何在动态链接的可执行文件/外部函数中实现此功能,该函数仅在动态库中可用.是的,这是真正的静态链接以更简单的方式解决这个问题,但是:

  • 有些系统(如Solaris),供应商通常不提供静态库
  • 有些库既不是源代码也不是静态版本

因此静态链接在这里没有用:(

Edit2:
我发现在某些体系结构中(SPARC,显然,请参阅GNU中的SPARC重定位部分作为手册),GNU能够使用修饰符就地为链接器创建某些类型的重定位引用.引用的SPARC将用于%gdop(symbolname)使汇编程序向链接器发出指令,指出"在此处创建重定位".英特尔在Itanium上的汇编程序知道@fptr(symbol) 链接重定位运算符的相同类型(另请参见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;
}
Run Code Online (Sandbox Code Playgroud)

我从链接器收到错误(请注意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不是"已解决" 的二进制文件...

Flo*_*mer 4

此优化已在 GCC 中实现。可以使用-fno-plt选项noplt功能属性启用它:

不要在与位置无关的代码中使用 PLT 进行外部函数调用。相反,从 GOT 加载调用站点的被调用者地址并分支到它。通过消除 PLT 存根并使 GOT 负载进行优化,可以产生更高效的代码。在 32 位 x86 等架构上,PLT 存根期望 GOT 指针位于特定寄存器中,这为编译器提供了更多的寄存器分配自由。惰性绑定需要使用PLT;所有外部符号都-fno-plt在加载时解析。

或者,函数属性noplt可用于避免通过 PLT 调用特定外部函数。

在位置相关代码中,一些目标还会将对标记为不使用 PLT 的函数的调用转换为使用 GOT。