R_MIPS_HI16/R_MIPS_LO16 对在不同的寄存器上

R_MIPS_HI16/R_MIPS_LO16 pair on different register

我正在尝试了解 R_MIPS_HI16 和 R_MIPS_LO16 重定位在 mips 中的工作原理。

我写了一点c代码:

static char buf1[0x100];
static int a = 0;
static int b = 0;

char f(int i, int n)
{
    a++;
    b++;
    return buf1[n];
}

使用 mips-linux-gnu-gcc-8 -o test.o -c -O2 -G 0 -g test.c 和 运行 objdump 编译:mips-linux-gnu-objdump -d -j .text test.o

我得到以下信息:

00000030 <f>:
  30:   3c070000    lui a3,0x0
  34:   3c060000    lui a2,0x0
  38:   3c020000    lui v0,0x0
  3c:   8ce50004    lw  a1,4(a3)
  40:   8cc30000    lw  v1,0(a2)
  44:   24420008    addiu   v0,v0,8
  48:   00442021    addu    a0,v0,a0
  4c:   24a50001    addiu   a1,a1,1
  50:   24630001    addiu   v1,v1,1
  54:   80820000    lb  v0,0(a0)
  58:   ace50004    sw  a1,4(a3)
  5c:   03e00008    jr  ra
  60:   acc30000    sw  v1,0(a2)

现在我运行mips-linux-gnu-readelf -r test.o得到搬迁table:

Relocation section '.rel.text' at offset 0x79c contains 12 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000008  00000405 R_MIPS_HI16       00000000   .bss
00000018  00000406 R_MIPS_LO16       00000000   .bss
00000014  00000405 R_MIPS_HI16       00000000   .bss
0000001c  00000406 R_MIPS_LO16       00000000   .bss
00000030  00000405 R_MIPS_HI16       00000000   .bss
0000003c  00000406 R_MIPS_LO16       00000000   .bss
00000040  00000406 R_MIPS_LO16       00000000   .bss
00000038  00000405 R_MIPS_HI16       00000000   .bss
00000044  00000406 R_MIPS_LO16       00000000   .bss
00000058  00000406 R_MIPS_LO16       00000000   .bss
00000034  00000405 R_MIPS_HI16       00000000   .bss
00000060  00000406 R_MIPS_LO16       00000000   .bss

现在,根据 ABI,第 4-17 节,每个 R_MIPS_HI16 搬迁都有匹配的 R_MIPS_LO16。如果 R_MIPS_LO16 前面没有 R_MIPS_HI16,它将引用前面的 R_MIPS_HI16。

如果我没理解错的话,那就意味着38和44中的重定位是成对的。这是有道理的——在地址 38 中,我们将地址的高位部分移动到寄存器 (v0),在 44 中,我们添加到同一寄存器以完成地址的加载。

我不明白的是,58 中的重定位也耦合到相同的 R_MIPS_HI16,但在该地址中,我们不访问任何在先前命令中使用的寄存器,也不访问他们似乎是相关的。事实上,这个命令似乎与30和3c这对有关。

这是怎么回事?

您正在观察 a++b++ 的优化。

虽然有人可能认为 b = b + 1 包含 b 的负载和 b 的存储,但需要 lui/lw 对才能读取 blui/sw 对用于 b 的写入,编译器知道 b 的读取和 b 的写入都需要完全相同的 lui,因此它消除第二个 lui 并重用第一个 lui 的目标寄存器——因此它正在做 lui/lw/sw.


如果您手动编写此程序,并且有一个小程序。您 可能 use/share 一个 lui 用于所有全局变量。

但是因为这些变量被分配给了.bss(它们都是零初始化的),链接器决定了它们的最终位置(在某些系统上虽然它们在源程序中一起声明,但它们是独立的项目),所以我们实际上并不知道它们在编译时有多接近。

此外,由于这是一个程序片段,我们也不知道该程序总共会有多少个全局变量,因此,任何两个全局变量之间都可能出现 64k 的边界,这意味着这两个全局变量需要不同的 lui.

由于链接器做出最终决定,因此编译器会假设最坏的情况,并对每个全局变量使用单独的 lui


经过一番讨论...

我明白你的意思了。我告诉你的是 30,3c 和 58 与变量的相同访问有关,这是真的。搬迁,我不熟悉,但你可能会发现这个相关:

https://github.com/NationalSecurityAgency/ghidra/issues/909

While I understand the manner in which the ABI is explained, a HI16 does not impact the LO16 relocation fixup, Our current implementation only has the LO16 affecting the HI16 fixup due to potential carry into the upper 16-bits during HI16 computation.

最重要的是,虽然 HI16 需要与 LO16 配对来计算进位,但在没有前面的 HI16 的情况下重复先前配对的 LO16 是可以的,因为已经完成了对 HI16 的适当进位 —所以 HI16/LO16 重定位并不是真正配对的 1:1.