当我了解MIPS处理器时,我的脑海里一直认为$ 0寄存器的读取总是返回0,并且写入$ 0总是被丢弃。来自MIPS程序员手册:
2.13.4.1 CPU通用寄存器 [...] r0硬连接到值 零,可以用作任何指令的目标寄存器 结果将被丢弃。当零为零时,r0也可以用作源 价值是必要的。
由此可见,or $0,$r31,$0
指令是无操作的。
当我在ELF MIPS二进制文件的启动代码中查看时,想象一下,当我看到以下指令序列时,我感到惊讶:
00000610 03 E0 00 25 or $0,$ra,$0
00000614 04 11 00 01 bgezal $0,0000061C
00000618 00 00 00 00 nop
0000061C 3C 1C 00 02 lui $28,+0002
00000620 27 9C 84 64 addiu $28,$28,-00007B9C
00000624 03 9F E0 21 addu $28,$28,$ra
00000628 00 00 F8 25 or $ra,$0,$0
地址0x610处的指令是将$ ra的值复制到$ r0,根据上面的段落等于丢弃它。然后,地址0x628处的指令从$ 0读回值,但由于$ 0硬连线为0,因此将$ ra设置为0.
这一切似乎都毫无意义:为什么只执行0x628就执行语句0x610。 glibc人员在编写此代码时显然有一些意图。似乎$ 0毕竟是可写的和可读的!
那么在什么情况下程序可以读/写$ 0寄存器,好像它是任何一个其他通用寄存器一样?
编辑:
查看glibc源代码并不是很有用。 __start
的代码
使用宏:
https://github.com/bminor/glibc/blob/master/sysdeps/mips/start.S#L80
ENTRY_POINT:
# ifdef __PIC__
SETUP_GPX($0)
...
注意这里有意指定$ 0。 SETUP_GPX宏在此处定义:
https://github.com/bminor/glibc/blob/master/sysdeps/mips/sys/asm.h#L75
# define SETUP_GPX(r) \
.set noreorder; \
move r, $31; /* Save old ra. */ \
bal 10f; /* Find addr of cpload. */ \
nop; \
10: \
.cpload $31; \
move $31, r; \
.set reorder
"保存旧的"明确表示保存寄存器的意图,但为什么是$ 0?
答案 0 :(得分:4)
它使用$0
因为在入口点没有理由保存$ra
,所以它只是被丢弃了。由于它手写的asm代码来自一个宏,因此它没有像通常情况那样进行优化。
答案 1 :(得分:1)
请注意,glibc仅将此用于PIC。 (见Is all MIPS code on Linux supposed to be PIC?)
MIPS jal
(与其他j
指令一样)不是PIC;它用imm26 << 2
取代了低28位的PC。 It's an absolute call within that 1/16th of address space
但是b
指令编码确实使用相对位移,所以它仍然有效。 bal
是无条件PIC函数调用的伪指令:它设置PC += imm16<<2
(参见同一链接)。它是针对$0
测试>= 0
的条件分支链接的伪指令,所以它总是被采用。正如您的反汇编所示,真正的指令是"BGEZAL -- Branch on greater than or equal to zero and link"。它只能在-2 ^ 17 / +(2 ^ 17 - 4)字节内工作。
&#34;和链接&#34;部分是这段代码所需要的:它通过使用分支链接将PC变为$ra
,因为在PIC中,您在汇编或链接时不知道自己的地址。
无论如何,这解释了为什么bgezal $0
正在阅读$0
。通过特殊设置这个宏的使用,他们可以通过将旧值无用的写入$0
来保存每个可执行文件至少4个字节。但是他们并没有:/代码只运行一次。