1

我有一个关于 Linux 上的动态链接的问题。考虑以下 ARM 二进制文件的反汇编。

8300 <printf@plt-0x40>:
     ....
8320:   e28fc600    add ip, pc, #0, 12
8324:   e28cca08    add ip, ip, #8, 20  ; 0x8000
8328:   e5bcf344    ldr pc, [ip, #836]! ; 0x344
     ....
83fc <main>:
    ...
8424:ebffffbd   bl  8320 <_init+0x2c>

主函数在 8424 调用 printf:bl 8320。8320 是上面显示的 .plt 中的地址。现在 .plt 中的代码调用动态链接器来调用 printf 例程。我的问题是动态链接器如何能够说它是对 printf 的调用?

4

3 回答 3

2

TLDR;PLT 通过传递调用动态链接器:

  • IP ( ) 中 GOT 条目的地址&PLTGOT[n+3]

  • &PLTGOT[2]在 LR 中;

此外PLTGOT[1]标识共享对象/可执行文件。

动态链接器使用它来查找重定位条目 ( plt_relocation_table[n]) 和符号 ( printf)。

PLT入口代码说明

这在ELF for ARM 的 A.3 节中(以某种方式)进行了解释:

8320: e28fc600 添加 ip, pc, #0, 12
8324: e28cca08 添加 ip, ip, #8, 20 ; 0x8000
8328:e5bcf344 ldr 电脑,[ip,#836]!; 0x344

解释如下:

添加 ip, pc, #-8:PC_OFFSET_27_20:__PLTGOT(X)
     ; R_ARM_ALU_PC_G0_NC(__PLTGOT(X))
添加 ip, ip, #-4:PC_OFFSET_19_12: __PLTGOT(X)
     ;R_ARM_ALU_PC_G1_NC(__PLTGOT(X))
LDR 电脑,[ip,#0:PC_OFFSET_11_0:__PLTGOT(X)]!
     ; R_ARM_LDR_PC_G2(__PLTGOT(X))

这些指令做了两件事:

  • 他们将 GOT 条目的地址计算为与 PC 的偏移量,并将其存储在 IP 寄存器中;

  • 他们跳到这个 GOT 条目。

该规范指出:

对最终 LDR 的回写确保 ip 包含 PLTGOT 条目的地址。这对于增量动态链接至关重要。

“回写”是使用“!” 在最后一条指令中:这用于使用最终偏移量更新 IP 寄存器(#836)。这样,IP 在 PLT 条目的末尾包含了 GOT 条目的地址。

动态链接器在 IP 中具有 GOT 条目的地址:

  • 它可以找到共享对象或可执行文件;

  • 它可以找到正确的重定位条目。

此重定位条目引用目标函数的符号(printf在您的情况下):

偏移信息类型 Sym。价值符号。姓名
0001066c 00000116 R_ARM_JUMP_SLOT 00000000 printf

ARM 架构的基础平台 ABI指出:

当平台支持惰性函数绑定(如 ARM Linux 那样)时,此 ABI 需要 ip 来寻址 PLT 通过它调用的点处的相应 PLTGOT 条目。(要求 PLT 表现得好像它以 LDR pc, [ip] 结尾)。

从 GOT 中查找重定位条目

现在从GOT地址中找到重定位入口的方式还不清楚。可以使用二进制搜索,但不方便。GNU ld.so 是这样做的(glibc/sysdeps/arm/dl-trampoline.S):


dl_runtime_resolve:
        cfi_adjust_cfa_offset (4)
        cfi_rel_offset (lr, 0)

        @ we get called with
        @       stack[0] contains the return address from this call
        @       ip contains &GOT[n+3] (pointer to function)
        @       lr points to &GOT[2]

        @ Save arguments.  We save r4 to realign the stack.
        push    {r0-r4}
        cfi_adjust_cfa_offset (20)
        cfi_rel_offset (r0, 0)
        cfi_rel_offset (r1, 4)
        cfi_rel_offset (r2, 8)
        cfi_rel_offset (r3, 12)

        @ get pointer to linker struct
        ldr     r0, [lr, #-4]

        @ prepare to call _dl_fixup()
        @ change &GOT[n+3] into 8*n        NOTE: reloc are 8 bytes each
        sub     r1, ip, lr
        sub     r1, r1, #4
        add     r1, r1, r1
[...]
  1. 第二个 GOT 条目的地址在 LR 中。我想这是 donebyt .PLT0

    00015b84:
     15b84: e52de004 推 {lr} ; (str lr,[sp,#-4]!)
     15b88:e59fe004 ldr lr,[pc,#4];15b94
     15b8c:e08fe00e 添加 lr、pc、lr
     15b90:e5bef008 ldr 电脑,[lr,#8]!
     15b94: 0012f46c andseq pc, r2, ip, ror #8
    
  2. 从这两个 GOT 地址中,动态链接器可以找到 GOT 偏移量和 PLT 重定位表中的偏移量。

  3. &GOT[2]中,动态链接器可以找到 PLTGOT ( GOT[1]) 的第二个条目,其中包含链接器结构的地址(动态链接器用来重新识别此共享对象/可执行文件的引用)。

我没有在哪里指定:它似乎不是基本 ARM ABI 规范的一部分。

于 2015-09-26T23:09:59.233 回答
1

.rela.plt包含 printf 的地址,以通知动态链接器从何处定位 printf

检查此链接以了解非常容易消化的详细信息https://www.technovelty.org/linux/plt-and-got-the-key-to-code-sharing-and-dynamic-libraries.html。本文还阐明了首先通过共享库访问变量的过程,然后是函数。

于 2015-09-25T13:52:48.497 回答
1

动态链接的过程在这里进行了非常详细的描述。

TL;DR:在静态链接时,ld在特殊部分(例如 、 等)中创建一组表,.rel.dyn这些表.rel.plt告诉运行时加载程序在运行时要做什么。

nm -D您可以使用、readelf -Wrobjdump -R等来检查这些表。

于 2015-09-25T14:10:43.603 回答