I'm decompiling a PS2 game that was shipped as a debug build. I've gotten as far as decompiling enough to be able compile an ELF file using the compiler that was originally used (Metrowerks CodeWarrior).
Now I'm doing comparisons between the disassembly of the original ELF file and the disassembly of the one I compiled. There's one recurring pattern that the original assembly has that mine doesn't: regular shifting using the dsll32 and dsra32 instructions.
Original assembly:
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
This was decompiled to the following C code:
d1 = -d1;
And was compiled to the following assembly:
subu v1,$0,s1
dsll32 s1,v1,16
dsra32 s1,s1,16
Notice that one pair of shifting instructions is missing. So far, I've failed to replicate this. I've tried adding various casts, changing it to d1 = 0 - d1, I was suggested to add a 64-bit cast, but nothing achieves the desired result.
Here's another example:
Original assembly:
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 code:
x = xposi + sprdat->xoff;
Compiled to:
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
Does anyone have any idea what C code would be responsible for this?
The TL;DR: Don't worry things are fine.
d1 = -d1;d1were in (e.g.)%ecx, the assembly would be:neg %ecxmips), we subtract the value from 0.The following is a bit of a rough explanation.
The C [decompiled] variable is
d1. It resides in MIPS registers1. The code uses a temporary variable (call ittmp) that resides in MIPS registerv1To avoid confusion [possibly just mine :-)] between the arbitrary name assigned by the decompiler
d1, let's rename this asmydata.In the original assembly, the first two instructions are converting a 16 bit integer into a 32 bit integer:
Logical right shift would shift zeroes in from the left.
However ...
Arithmetic right shift will shift bit 31 into the number from the left side.
This is common trick for a number of architectures that don't have (as x86 does) special CISC instructions for moving a 16 bit number into a 32 bit register with automatic sign extension (e.g.
movswl), either from memory or another register.For example, a -1 in a
shortwould have a bit pattern of 0xFFFF (i.e. 0x0000FFFF). We are converting this to 32 bits (i.e. 0xFFFFFFFF)This uses an extra
tmpvariable that is put in registerv1. Thetmpis probably [implicitly] defined as:This all implies that
mydata(which resides in registers1) was defined as a 16 bit integer:Although x86 has word size arithmetic instructions that operate directly on 16 bits (e.g.
subw), MIPS only has 32 bit arithmetic register-to-register instructions (the exception is theaddiwhich sign extends the immediate operand).Now, to actually negate the number, we subtract it from zero:
The equivalent C code is:
The next two instructions put the sign bit from the 32 bit
tmpinto the sign bit of a 16 bit number:The remaining two instructions are doing a similar [and unnecessary] conversion to get the final 16 bit value for
mydata:The rebuilt assembly already assumes that
s1has been sign extended to 32 bits (in prior instructions). So, the first two instructions of the original assembly aren't needed:And, the extra unnecessary final two instructions are elided.
To summarize: The recompiled code does exactly what the original code does, but is optimized to do it in fewer instructions.
The second example is doing much the same thing.