R_X86_64_IRELATIV是什么意思?

时间:2013-07-01 12:38:06

标签: linker x86 x86-64 loader elf

我在x86 arch中通过静态链接libc库为简单程序构建了一个可执行文件。该可执行文件的重定位表按预期为空:

$ readelf -r test
There are no relocations in this file.
$ 

当我为同一个程序构建可执行文件时,通过静态链接libc库,在x86_64 arch中,重定位表不为空:

$ readelf -r test

Relocation section '.rela.plt' at offset 0x1d8 contains 12 entries:

  Offset          Info           Type           Sym. Value    Sym. Name + Addend
0000006c2058  000000000025 R_X86_64_IRELATIV                    000000000042de70
0000006c2050  000000000025 R_X86_64_IRELATIV                    00000000004829d0
0000006c2048  000000000025 R_X86_64_IRELATIV                    000000000042dfe0
0000006c2040  000000000025 R_X86_64_IRELATIV                    000000000040a330
0000006c2038  000000000025 R_X86_64_IRELATIV                    0000000000432520
0000006c2030  000000000025 R_X86_64_IRELATIV                    0000000000409ef0
0000006c2028  000000000025 R_X86_64_IRELATIV                    0000000000445ca0
0000006c2020  000000000025 R_X86_64_IRELATIV                    0000000000437f40
0000006c2018  000000000025 R_X86_64_IRELATIV                    00000000004323b0
0000006c2010  000000000025 R_X86_64_IRELATIV                    0000000000430540
0000006c2008  000000000025 R_X86_64_IRELATIV                    0000000000430210
0000006c2000  000000000025 R_X86_64_IRELATIV                    0000000000432400
$

我搜索了重定位类型“R_X86_64_IRELATIV”,但我可以找到有关它的任何信息。那么有人可以告诉我它是什么意思吗?

我想如果我用gdb调试可执行文件,我可能会找到答案。但实际上它提出了很多问题:)这是我的分析:

上表中的Sym.Name字段列出了某些libc函数的虚拟地址。当我objdump'd可执行'test'时,我发现虚拟地址0x430210包含strcpy函数。在加载时,在位置0x6c2008找到的相应PLT条目从0x400326(下一条指令的虚拟地址,即设置解析器)更改为0x0x443cc0(一个名为__strcpy_sse2_unaligned的libc函数的虚拟地址)我不知道为什么它会被解析为另一个函数而不是strcpy?我假设它是strcpy的另一种变体。

完成这项分析之后,我意识到我错过了基本的观点“加载静态可执行文件时,动态链接器怎么会出现?”我没有找到.interp部分,所以动态链接器不能确定。然后我观察到,libc函数“__libc_csu_irel()”修改了PLT条目而不是动态链接器。

如果我的分析对任何人都更有意义,请告诉我这是什么意思。我很高兴知道背后的原因。

非常感谢!!!

3 个答案:

答案 0 :(得分:2)

TL; DR

你是对的。这些重定位只是试图找出应该使用(不仅仅是)libc函数的实现。它们在链接时插入二进制文件的函数main执行__libc_start_main之前解析。

我将尝试解释此重定位类型的工作原理。

示例

我使用此代码作为参考

//test.c
#include <stdio.h>
#include <string.h>

int main(void)
{
    char tmp[10];
    char target[10];
    fgets(tmp, 10, stdin);
    strcpy(target, tmp);
}

使用GCC 7.3.1编译

gcc -O0 -g -no-pie -fno-pie -o test -static test.c

重定位表(readelf -r test)的缩短输出:

Relocation section '.rela.plt' at offset 0x1d8 contains 21 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
...
00000069bfd8  000000000025 R_X86_64_IRELATIV                    415fe0
00000069c018  000000000025 R_X86_64_IRELATIV                    416060

节标题(readelf -S test)的缩短输出:

[Nr] Name              Type             Address           Offset
     Size              EntSize          Flags  Link  Info  Align
...
[19] .got.plt          PROGBITS         000000000069c000  0009c000
     0000000000000020  0000000000000008  WA       0     0     8
...

它表示.got.plt部分位于地址0x69c000

如何解决R_X86_64_IRELATIV重定位

重定位表中的每条记录都包含两个重要的信息offset和addend。加法是指向函数的指针(也称为间接函数),它不带参数并返回指向函数的指针。返回的指针位于重定位记录的偏移量上。

简单的重新定位解析器实现:

void reolve_reloc(uintptr_t* offset, void* (*addend)())
{
    //addend is pointer to function
    *offset = addend();
}

从这个答案开头的例子。重定位表中的最后一个加数指向地址0x416060,即函数strcpy_ifunc。请参阅反汇编的输出:

0000000000416060 <strcpy_ifunc>:
  416060:       f6 05 05 8d 28 00 10    testb  $0x10,0x288d05(%rip)        # 69ed6c <_dl_x86_cpu_features+0x4c>
  416067:       75 27                   jne    416090 <strcpy_ifunc+0x30>
  416069:       f6 05 c1 8c 28 00 02    testb  $0x2,0x288cc1(%rip)        # 69ed31 <_dl_x86_cpu_features+0x11>
  416070:       75 0e                   jne    416080 <strcpy_ifunc+0x20>
  416072:       48 c7 c0 70 dd 42 00    mov    $0x42dd70,%rax
  416079:       c3                      retq   
  41607a:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
  416080:       48 c7 c0 30 df 42 00    mov    $0x42df30,%rax
  416087:       c3                      retq   
  416088:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  41608f:       00 
  416090:       48 c7 c0 f0 0e 43 00    mov    $0x430ef0,%rax
  416097:       c3                      retq   
  416098:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  41609f:       00 

strcpy_ifunc选择所有strcpy实现的最佳替代方法,adn会在其上返回指针。在我的情况下,它返回地址0x430ef0 __strcpy_sse2_unaligned。这个地址是0x69c018的10 .glob.plt + 0x18

谁和何时解决

通常重新分配的第一个想法是所有这些东西都处理动态解释器(ldd)。但在这种情况下,程序是静态链接的,.interp部分是空的。在这种情况下,它在函数__libc_start_main中解析,它是GLIBC的一部分。除了解决重定位问题之外,此函数还负责将命令行参数传递给main并执行其他操作。

访问重定位表

当我弄清楚我有最后一个问题时,__libc_start_main如何访问重定位表保存在ELF标题中?第一个想法是它以某种方式打开正在运行的二进制文件来读取和处理它。当然这是完全错误的。如果查看可执行文件的程序头,您将看到类似这样的内容(readlef -l test):

Type           Offset             VirtAddr           PhysAddr
               FileSiz            MemSiz              Flags  Align
LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
               0x0000000000098451 0x0000000000098451  R E    0x200000
...

此标头中的偏移量偏离可执行文件的第一个字节。那么程序头中的第一项是将test文件的第一个0x98451字节复制到内存中。但是在偏移量0x0上是ELF头。因此,对于代码段,它还会将ELF标头加载到内存中,__libc_start_main可以轻松访问它。

答案 1 :(得分:1)

您可以查看“System V Application Binary Interface” AMD64架构处理器补充资料“ - 我在https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf

下找到了它

如果您转到重定位部分(4.4),您将找到此RLD类型的文档以及计算方法的说明

R_X86_64_IRELATIVE 37个字类间接(B + A)

其中

  • wordclass为LP64指定word64,并为ILP32指定word32。
  • A表示用于计算可重定位字段值的加数。
  • B表示在执行期间将共享对象加载到内存中的基址。通常,共享对象使用0基本虚拟地址构建,但执行地址将不同。

goodluck - 顺便说一句,感谢sploitfun的精彩帖子; - )

答案 2 :(得分:0)

  

我不为什么将它解析为其他函数而不是strcpy?我认为它是strcpy的另一个变体。

glibc使用动态链接器在运行时为主机CPU选择最佳版本的strcpy,strlen,memcpy等。

实际的strcpy函数是调度程序/选择器,用于检查CPU功能并进行设置,以便将来的调用直接达到适用于CPU的最佳版本。  我不确定静态链接仍然可以使用这种机制。

对于strcpy,我认为__strcpy_sse2_unaligned在现代CPU上可能仍然是最佳选择(如果没有AVX2版本)。

__strcpy_ssse3使用SSSE3 palignr进行对齐的加载和对齐的存储,即使src和dst相对于彼此未对齐。 (它有16个不同的循环,用于所有16种可能的相对对齐,因为palignr将移位计数作为立即数,因此有点膨胀。)在Core2上可能不错,但后来CPU的未对齐装载/存储效率更高最好使用__strcpy_sse2_unaligned实现。