MIPS 寄存器 $0 可以用来存储和检索值吗?

Can the MIPS register $0 be used to store and retrieve values?

当我了解 MIPS 处理器时,我的脑子里一直有这样的想法:读取 $0 寄存器总是 return 0,而写入 $0 总是被丢弃。来自 MIPS 程序员手册:

2.13.4.1 CPU General-Purpose Registers [...] r0 is hard-wired to a value of zero, and can be used as the target register for any instruction whose result is to be discarded. r0 can also be used as a source when a zero value is needed.

由此可知,or [=13=],$r31,[=13=] 指令是空操作。

当我在 ELF MIPS 二进制文件的启动代码中四处寻找时,当我看到以下指令序列时,想象一下我的惊讶:

00000610 03 E0 00 25   or     [=10=],$ra,[=10=]
00000614 04 11 00 01   bgezal [=10=],0000061C
00000618 00 00 00 00   nop
0000061C 3C 1C 00 02   lui    ,+0002
00000620 27 9C 84 64   addiu  ,,-00007B9C
00000624 03 9F E0 21   addu   ,,$ra
00000628 00 00 F8 25   or     $ra,[=10=],[=10=]

地址0x610处的指令正在将$ra的值复制到$r0中,根据上面的段落,这相当于丢弃它。然后,地址 0x628 处的指令从 $0 读回值,但由于 $0 硬连线为 0,它导致将 $ra 设置为 0。

这一切似乎毫无意义:为什么执行语句 0x610 就足以执行 0x628。 glibc 人员在编写这段代码时显然有一些意图。好像$0毕竟是可写可读的!

那么在什么情况下程序可以读取/写入 $0 寄存器,就好像它是任何其他通用寄存器一样?

编辑: 查看 glibc 源代码没有帮助。 __start 的代码 使用宏:

https://github.com/bminor/glibc/blob/master/sysdeps/mips/start.S#L80

ENTRY_POINT:
# ifdef __PIC__
    SETUP_GPX([=11=])
...

注意这里是如何故意指定 $0 的。 SETUP_GPX 宏定义在这里:

https://github.com/bminor/glibc/blob/master/sysdeps/mips/sys/asm.h#L75

# define SETUP_GPX(r)                           \
        .set noreorder;                         \
        move r, ;     /* Save old ra.  */     \
        bal 10f; /* Find addr of cpload.  */    \
        nop;                                    \
10:                                             \
        .cpload ;                             \
        move , r;                             \
        .set reorder

"Save old ra" 明确表示保存寄存器的意图,但为什么 $0?

它使用 [=10=] 因为在入口点没有理由保存 $ra,所以它被丢弃了。由于它是来自宏的手写 asm 代码,因此没有像通常情况下那样进行优化。

请注意,glibc 仅将其用于 PIC。 (参见 Is all MIPS code on Linux supposed to be PIC?


MIPS jal(与其他 j 指令一样)不是 PIC;它用 imm26 << 2 替换了 PC 的低 28 位。 It's an absolute call within that 1/16th of address space.

但是b指令编码确实使用了相对位移,所以它仍然有效。 bal 是用于无条件 PIC 函数调用的伪指令:它设置 PC += imm16<<2(请参阅相同的 link)。这是条件分支和 link 的伪指令,测试 [=16=] 是否为 >= 0,因此它总是被采用。如您的反汇编所示,真正的指令是 "BGEZAL -- Branch on greater than or equal to zero and link"。它仅适用于 -2^17 / +(2^17 - 4) 字节。

"and link" 部分是这段代码想要的:它通过使用分支和 link 使 PC 进入 $ra,因为在 PIC 中你不知道你自己的assemble 或 link 时间的地址。

无论如何,这解释了为什么 bgezal [=19=] 正在阅读 [=16=]。通过对宏的这种使用进行特殊封装,他们可以通过省去将旧值无用地写入 [=16=] 来为每个可执行文件节省至少 4 个字节。但他们没有:/ 不过代码只运行一次。