MIPS寄存器$0是否可以用于存储和检索值



当我了解MIPS处理器时,我突然意识到对$0寄存器的读取总是返回0,对$0的写入总是被丢弃。来自MIPS程序员手册:

2.13.4.1 CPU通用寄存器[…]r0硬连接到值零,并且可以用作任何指令的目标寄存器结果将被丢弃。当值是必需的。

由此可知,指令or $0,$r31,$0是no-op。

想象一下,当我在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。能说会道的人在写这段代码时显然有一些意图。看来$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

"保存旧ra"清楚地表明了保存寄存器的意图,但为什么是0美元?

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

请注意,glibc仅将其用于PIC。(请参阅Linux上的所有MIPS代码都应该是PIC吗?)


MIPSjal(与其他j指令一样)不是PIC;它用CCD_ 7替换PC的低28位。在那1/16的地址空间内,这是一个绝对的调用。

b指令编码确实使用了相对位移,因此它仍然有效。bal是用于无条件PIC函数调用的伪指令:它设置PC += imm16<<2(参见同一链接)。它是条件分支和链接的伪指令,用于测试$0是否为>= 0,因此它总是被采用。正如您的反汇编所示,真正的指令是"BGEZAL——大于或等于零的分支和链接"。它只能在-2^17/+(2^17-4)字节内工作。

"和链接"部分就是这个代码想要的:它通过使用分支和链接将PC进入$ra,因为在PIC中,您在组装或链接时不知道自己的地址。

无论如何,这解释了bgezal $0读取$0的原因。通过对该宏的特殊使用,他们可以省去将旧值写入$0的无用操作,从而为每个可执行文件至少节省4个字节。但他们没有:/不过,代码只运行一次。

最新更新