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 个字节。但他们没有:/ 不过代码只运行一次。
当我了解 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 个字节。但他们没有:/ 不过代码只运行一次。