我正在反编译作为调试版本提供的 PS2 游戏。我已经进行了足够的反编译,能够使用最初使用的编译器(Metrowerks CodeWarrior)编译 ELF 文件。
现在我正在对原始ELF文件的反汇编和我编译的ELF文件的反汇编进行比较。原始程序集有一种重复出现的模式,但我的程序集没有:使用 dsll32 和 dsra32 指令进行定期转换。
原始组装:
dsll32 v1,s1,16
dsra32 v1,v1,16
subu v1,$0,v1
dsll32 v1,v1,16
dsra32 v1,v1,16
dsll32 s1,v1,16
dsra32 s1,s1,16
这被反编译为以下 C 代码:
d1 = -d1;
并被编译为以下程序集:
subu v1,$0,s1
dsll32 s1,v1,16
dsra32 s1,s1,16
请注意,缺少一对移位指令。到目前为止,我还未能复制这一点。我尝试过添加各种类型转换,将其更改为
d1 = 0 - d1
,有人建议我添加 64 位类型转换,但没有达到预期的结果。
这是另一个例子:
原始组装:
lh v0,80(sp)
dsll32 v1,v0,16
dsra32 v1,v1,16
lw v0,128(sp)
lb v0,0(v0)
dsll32 v0,v0,24
dsra32 v0,v0,24
dsll32 v0,v0,16
dsra32 v0,v0,16
addu v0,v1,v0
dsll32 v0,v0,16
dsra32 v0,v0,16
dsll32 s3,v0,16
dsra32 s3,s3,16
C代码:
x = xposi + sprdat->xoff;
编译为:
lh v1,80(sp)
lw v0,128(sp)
lb v0,0(v0)
dsll32 v0,v0,24
dsra32 v0,v0,24
addu v0,v1,v0
dsll32 s3,v0,16
dsra32 s3,s3,16
有谁知道什么 C 代码会对此负责吗?
TL;DR:别担心,一切都很好。
d1 = -d1;
d1
位于(例如)%ecx
,则装配将是: neg %ecx
mips
),我们从 0 中减去该值。以下是一些粗略的解释。
在原始汇编中,前两条指令将 16 位整数转换为 32 位整数:
dsll32 v1,s1,16 # move 16 bit int's sign bit into bit 31
dsra32 v1,v1,16 # shift back with sign extension
算术右移会将第31位移入左侧的数字中。
因此,
short
中的-1将具有0xFFFF的位模式(即0x0000FFFF)。我们将其转换为 32 位(即 0xFFFFFFFF)
这使用了一个额外的
tmp
变量,该变量被放入寄存器 v1
中。 tmp
可能[隐式]定义为:
int tmp;
这意味着
d1
(位于寄存器 s1
中)被定义为 16 位整数:
short d1;
现在,要实际对数字求负,我们用零减去它:
subu v1,$0,v1 # perform negation
等效的 C 代码是:
tmp = 0 - tmp;
接下来的两条指令将 32 位
tmp
中的符号位放入 16 位数字的符号位中:
dsll32 v1,v1,16 # put sign bit from 16 bit part into bit 31
dsra32 v1,v1,16 # shift back to get sign bit for 16 bit number
其余两条指令正在执行类似的[且不必要的]转换,以获得
d1
的最终 16 位值:
dsll32 s1,v1,16 # d1 = tmp << 16
dsra32 s1,s1,16 # d1 = d1 >> 16
重建的程序集已假定
s1
已符号扩展至 32 位。因此,不需要原始程序集的前两条指令:
# assume s1 has _already_ been sign extended to 32 bits here in _prior_
# instructions
subu v1,$0,s1
# move sign bit from 32 bits back to 16 bits
dsll32 s1,v1,16
dsra32 s1,s1,16
并且,额外不必要的最后两条指令被省略。
总结一下:重新编译的代码与原始代码的功能完全相同,但经过优化,可以用更少的指令来完成。
快乐重新编译!