11

我不确定这个问题的好主题是什么,但我们开始吧:

为了强制代码的关键部分的代码局部性/紧凑性,我正在寻找一种方法来通过“跳转槽”(ELFR_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 <主>:
0: bf 00 00 00 00 移动 $0x0,%edi
1:R_X86_64_32.rodata.str1.1
5:68 00 00 00 00 推 0x0
6:R_X86_64_32 .text+0x19
一个: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 <主>:
  400500: bf 0c 06 40 00 移动 $0x40060c,%edi
  400505:68 19 05 40 00 推 0x400519
  40050a: ff 24 25 11 05 40 00 jmpq *0x400511
  400511:[.quad 400428]
  400519: 31 c0 xorl %eax, %eax
  40051b:c3 retq
[ ... ]
动态搬迁记录
偏移类型值
[ ... ]
00000000005008c0 R_X86_64_JUMP_SLOT printf

即这仍然给出了两步重定向,首先将执行转移到PLT钩子,然后跳转到库入口点。

有没有办法指示编译器/汇编器/链接器 - 在这个例子中 - “内联”地址处的跳转槽目标0x400511

即用“远程”(在程序加载时通过)reloc替换“本地”(在程序链接时通过)reloc (并ld强制这部分代码进行非延迟加载)?也许链接器映射文件可能使这成为可能 - 如果是这样,如何?R_X86_64_64ld.soR_X86_64_JUMP_SLOT

编辑:
为了清楚起见,问题是关于如何在动态链接的可执行文件中实现这一点/对于仅在动态库中可用的外部函数。是的,真正的静态链接以更简单的方式解决了这个问题,但是:

  • 在某些系统(如 Solaris)中,供应商通常不提供静态库
  • 有些库既不能作为源代码也不能作为静态版本使用

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

Edit2:
我发现在某些体系结构中(SPARC,值得注意的是,请参阅GNU 中的 SPARC 重定位部分作为手册),GNU 能够使用修饰符为链接器就地创建某些类型的重定位引用。引用的 SPARC 将用于%gdop(symbolname)使汇编器向链接器发出指令,说明“在此处创建该重定位”。Intel 在 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;
}

我从链接器收到一个错误(请注意7 == R_X86_64_JUMP_SLOT):

错误:/tmp/cc6BUEZh.o:目标文件中出现意外的 reloc 7
汇编器创建一个目标文件,其中readelf说:
偏移 0x5e8 处的重定位部分“.rela.text.startup”包含 2 个条目:
偏移量信息类型符号的值符号的名称+加数
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,而不是“已解决”的二进制文件。

4

3 回答 3

3

为了内联调用,您需要一个代码 ( .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 中生成一个R_X86_64_GLOB_DAT针对 printf 的重定位,以供上述序列使用。您需要避免使用 C 代码,因为通常编译器可能会在序言和尾声中使用任意数量的调用者保存的寄存器,这会迫使您在 asm 函数调用周围保存和恢复所有此类寄存器,否则可能会损坏这些寄存器以供以后使用在包装函数中。因此,在纯汇编中编写包装器更容易。

另一种选择是进行编译,-Wl,-z,now -Wl,-z,relro以确保在启动时解析 PLT 和 PLT 相关的 GOT 条目,以增加代码局部性和紧凑性。使用完整的 RELRO,您只需在 PLT 中运行代码并访问 GOT 中的数据,这两件事应该已经在逻辑核心的缓存层次结构中的某个位置。如果完整的 RELRO 足以满足您的需求,那么您将不需要包装器,并且您将获得额外的安全优势。

最好的选择是真正的静态链接或 LTO,如果它们可供您使用的话。

于 2016-05-25T17:54:54.397 回答
3

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

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

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

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

于 2020-04-18T16:08:04.663 回答
-1

您可以静态链接可执行文件。只需添加-static到最后的链接命令,您所有的间接跳转都将被直接调用所取代。

于 2012-06-01T11:54:47.773 回答