MIPS 二进制中的奇怪指令模式 (lw)
与许多 RISC 实现一样,MIPS 指令集使用固定宽度的 32 位指令,而指令只有 16 位用于偏移字段,这意味着您只能使用 16 位常量,从而为您提供 64KB 的寻址空间。但是,MIPS CPU 的实际地址空间是 4GB(32 位地址大小),那么您如何访问所有这些呢?好吧,可以选择使用部分 16 位移动在寄存器中构建 32 位地址,然后使用间接加载/存储来访问它。它通常看起来类似于:
lui $r1, 0x0123
addi $r1, $r1, 0xabcd
或者
lui $r1, 0x0123
ori $r1, $r1, 0xabcd
这两个都加载0x0123abcd到r1aka at(请参阅此处)。
但是,这需要在编译(或至少是链接)时了解地址。如果我们的二进制文件需要加载到不同的地址(通常是共享库的情况),则必须重新定位(需要修补指令)。修补需要时间,防止代码页共享并增加内存消耗,这就是为什么位置无关代码 (PIC) 比固定地址更受欢迎。
那么,我们如何独立于加载地址进行地址计算呢?好吧,我们只需要获取我们的执行地址并为其添加一个固定的增量(通常二进制文件作为一个块加载到内存中,因此从它的一部分到另一部分的偏移量是固定的)。MIPS ABI 规定,对于公共函数,$t9函数入口处的值应该等于它的运行时地址,我们可以看到使用这个事实的代码:
109BC: li $gp, 0x830e4
109C4: addu $gp, $gp, $t9
如果我们假设$t9等于0x109BC,我们得到:
gp = 0x830e4 + 0x109BC = 0x93AA0
gp代表“全局指针”,在函数执行期间不应该改变(通常假设它在程序的所有函数中具有相同的值)。编译器可以在涉及程序地址的所有其他计算中使用这一事实。例如,查看突出显示的区域:
lw $v1, -0x7fd4($gp)
0x93AA0-0x7fd4 = 0x8BACC,显然程序存储0x70000在该地址,该地址用于下一条加载指令$a1:
lw $a1, -0x1b4c($v1)
计算它:(-0x1b4c+0x70000=0x6E4B4由反汇编器显示很有帮助)。
因此,$v1(以及v0稍后)这里只是一个用于地址计算的中间变量(通常$at为此目的而保留,但一直重用它可能会导致代码变慢)。
顺便说一下,0x70000低 16 位全为零,这不是偶然的。它可能碰巧指向rsl_setNetCfgObj但它只是一个红鲱鱼。
如果您转到二进制文件的 .got 部分,您通常会看到如下内容:
.got:00515B40 .word 0
.got:00515B44 .word 0x80000000
.got:00515B48 .word 0x510000
.got:00515B4C .word 0x4D0000
.got:00515B50 .word 0x420000
.got:00515B54 .word 0x4C0000
.got:00515B58 .word 0x520000
.got:00515B5C .word 0x430000
.got:00515B60 .word 0x440000
.got:00515B64 .word 0x450000
.got:00515B68 .word 0x460000
.got:00515B6C .word 0x470000
.got:00515B70 .word 0x480000
.got:00515B74 .word 0x490000
.got:00515B78 .word 0x4A0000
.got:00515B7C .word 0x530000
.got:00515B80 .word 0x4B0000
.got:00515B84 .word 0
.got:00515B88 .word 0
.got:00515B8C .word 0
这些是所谓的本地GOT 条目,编译器仅将其用于二进制内部的地址计算,而不是作为指向外部符号的指针。编译器在那里分配足够多的不同地址,因此它可以通过 16 位(有符号)偏移量到达本地二进制文件中的任何所需地址。它gp本身通常设置为 GOT+ 7FF0,它允许编译器在一条指令中加载任何 GOT 条目(外部符号或本地地址以供进一步计算)(假设 GOT 不超过 64KB)。
因此,总而言之:您在这里看到的不是混淆或反汇编程序错误,而是展示 MIPS 指令集及其调用约定局限性的正常代码。
顺便说一句,IDA 知道这些事情,并且默认情况下使用最终地址表示大多数此类引用。例如,来自示例二进制文件:
.text:0042C34C la $v0, dword_520000
.text:0042C350 lbu $v0, (byte_5193CD - 0x520000)($v0)
.text:0042C354 beqz $v0, loc_42C36C
.text:0042C358 li $v1, 8
.text:0042C35C li $v1, 9
.text:0042C360 la $v0, dword_520000
.text:0042C364 b loc_42C380
.text:0042C368 sb $v1, (sLastPayedFileInfo - 0x520000)($v0)
并关闭简化:
.text:0042C34C lw $v0, -0x7FD8($gp)
.text:0042C350 lbu $v0, (byte_5193CD - 0x520000)($v0)
.text:0042C354 beq $v0, $zero, loc_42C36C
.text:0042C358 addiu $v1, $zero, 8
.text:0042C35C addiu $v1, $zero, 9
.text:0042C360 lw $v0, -0x7FD8($gp)
.text:0042C364 beq $zero, $zero, loc_42C380
.text:0042C368 sb $v1, (sLastPayedFileInfo - 0x520000)($v0)
虽然你仍然可以看到发生了什么,但我个人更喜欢第一个。
有关 MIPS 的更多信息,我会推荐Sweetman的See MIPS Run手册,以及 MIPS ABI 规范(请参阅此处开始)。或者只是通过指令逐条阅读代码指令并尝试弄清楚它们的作用。
