Comparisons of MIPS disassembly and decompilation

Here are some side-by-side comparisons of disassembly and decompiler for MIPS. Please maximize the window too see both columns simultaneously.

The following examples are displayed on this page:

  1. Simple code
  2. 64-bit comparison
  3. Magic divisions
  4. Hard cases with delay slots
  5. Little-endian MIPS
  6. MicroMIPS
  7. Floating-point operations
  8. Compiler helpers

Simple code

This is a very simple code to decompile and the output is perfect. The only minor obstacle are references through the global offset table but both IDA and the Decompiler handle them well. Please note the difference in the number of lines to read on the left and on the right.

Assembler code
# void __fastcall free_argv(int argc, char **argv) .globl _Z9free_argviPPc # weak _Z9free_argviPPc: # CODE XREF: test_expand_argv(void)+264↑p # test_expand_argv(void)+51C↑p ... var_10 = -0x10 var_4 = -4 var_s0 = 0 var_s4 = 4 arg_0 = 8 arg_4 = 0xC # __unwind { addiu $sp, -0x28 sw $ra, 0x20+var_s4($sp) sw $fp, 0x20+var_s0($sp) move $fp, $sp la $gp, _GLOBAL_OFFSET_TABLE_+0x7FF0 sw $gp, 0x20+var_10($sp) sw $a0, 0x20+arg_0($fp) sw $a1, 0x20+arg_4($fp) lw $v0, 0x20+arg_4($fp) beqz $v0, loc_17778 nop sw $zero, 0x20+var_4($fp) loc_1770C: # CODE XREF: free_argv(int,char **)+80↓j lw $v1, 0x20+var_4($fp) lw $v0, 0x20+arg_0($fp) slt $v0, $v1, $v0 beqz $v0, loc_17760 nop lw $v0, 0x20+var_4($fp) sll $v0, 2 lw $v1, 0x20+arg_4($fp) addu $v0, $v1, $v0 lw $v0, 0($v0) move $a0, $v0 lw $v0, (qfree_ptr-0x7FF0 - _GLOBAL_OFFSET_TABLE_)($gp) move $t9, $v0 jalr $t9 ; qfree nop lw $gp, 0x20+var_10($fp) lw $v0, 0x20+var_4($fp) addiu $v0, 1 sw $v0, 0x20+var_4($fp) b loc_1770C nop # --------------------------------------------------------------------------- loc_17760: # CODE XREF: free_argv(int,char **)+40↑j lw $a0, 0x20+arg_4($fp) lw $v0, (qfree_ptr-0x7FF0 - _GLOBAL_OFFSET_TABLE_)($gp) move $t9, $v0 jalr $t9 ; qfree nop lw $gp, 0x20+var_10($fp) loc_17778: # CODE XREF: free_argv(int,char **)+28↑j nop move $sp, $fp lw $ra, 0x20+var_s4($sp) lw $fp, 0x20+var_s0($sp) addiu $sp, 0x28 jr $ra nop # } // starts at 176D8
Pseudocode
void __fastcall free_argv(int argc, char **argv) { int i; // [sp+1Ch] [+1Ch] if ( argv ) { for ( i = 0; i < argc; ++i ) qfree(argv[i]); qfree(argv); } }

64-bit comparison

Sorry for another long assembler listing. It shows that for MIPS, as for other platforms, the decompiler can recognize 64-bit operations and collapse them into very readable constructs.

Assembler code
# =============== S U B R O U T I N E ======================================= # Attributes: bp-based frame fpd=0x18 # _DWORD uh_eq_s(void) .globl _Z7uh_eq_sv _Z7uh_eq_sv: # DATA XREF: .eh_frame:000478E4↓o var_s0 = 0 var_s4 = 4 var_s8 = 8 var_sC = 0xC var_s10 = 0x10 var_s14 = 0x14 var_s18 = 0x18 var_s1C = 0x1C # __unwind { addiu $sp, -0x38 sw $ra, 0x18+var_s1C($sp) sw $fp, 0x18+var_s18($sp) sw $s5, 0x18+var_s14($sp) sw $s4, 0x18+var_s10($sp) sw $s3, 0x18+var_sC($sp) sw $s2, 0x18+var_s8($sp) sw $s1, 0x18+var_s4($sp) sw $s0, 0x18+var_s0($sp) move $fp, $sp jal uh nop move $s5, $v1 move $s4, $v0 jal s nop move $s3, $v0 sra $v0, 31 move $s2, $v0 xor $s0, $s4, $s2 xor $s1, $s5, $s3 or $v0, $s0, $s1 sltiu $v0, 1 andi $v0, 0xFF move $sp, $fp lw $ra, 0x18+var_s1C($sp) lw $fp, 0x18+var_s18($sp) lw $s5, 0x18+var_s14($sp) lw $s4, 0x18+var_s10($sp) lw $s3, 0x18+var_sC($sp) lw $s2, 0x18+var_s8($sp) lw $s1, 0x18+var_s4($sp) lw $s0, 0x18+var_s0($sp) addiu $sp, 0x38 jr $ra nop # } // starts at 25C
Pseudocode
bool uh_eq_s(void) { unsigned __int64 v0; // $v1 v0 = uh(); return v0 == s(); }

Magic divisions

We recognize magic divisions for MIPS the same way as for other processors. Note that this listing has a non-trivial delay slot.

Assembler code
.globl smod199 smod199: # DATA XREF: .eh_frame:0000875C↓o # __unwind { lui $v1, 0x5254 sra $v0, $a0, 31 li $v1, 0x5254E78F mult $a0, $v1 mfhi $v1 sra $v1, 6 subu $v1, $v0 li $v0, 0xC7 mul $a1, $v1, $v0 jr $ra subu $v0, $a0, $a1 # } // starts at 4F2C
Pseudocode
int __fastcall smod199(int a1) { return a1 % 199; }

Hard cases with delay slots

The previous example was a piece of cake. This one shows a tougher nut to crack: there is a jump to a delay slot. A decent decompiler must handle these cases too and produce a correct output without misleading the user. This is what we do. (We spent quite long time inventing and testing various scenarios with delay slots).

Assembler code
branch_to_b_dslot: # CODE XREF: branch_to_bal_dslot+14↓p # DATA XREF: branch_likely_cond_move+10↓o move $t2, $a0 addiu $t3, $t2, -0x18 bltz $t3, l1 li $a0, 1 sllv $a0, $t3 b l2 l1: # CODE XREF: branch_to_b_dslot+8↑j li $t4, 0xFFFFFFC0 li $t3, 0x18 subu $t3, $t2 srav $a0, $t3 l2: # CODE XREF: branch_to_b_dslot+14↑j jr $ra addu $v0, $a0, $t4 # End of function branch_to_b_dslot
Pseudocode
int __fastcall branch_to_b_dslot(int a1) { int v1; // $a0 if ( a1 - 24 < 0 ) v1 = 1 >> (24 - a1); else v1 = 1 << (a1 - 24); return v1 - 64; }

Little-endian MIPS

We support both big-endian and little-endian code. Usually they look the same but there may be subtle differences in the assembler. The decompiler keeps track of the bits involved and produces human-readable code.

Little endian
.globl upd_d2 upd_d2: lwl $v0, 5($a0) lwr $v0, 2($a0) addiu $v0, $v0, 1 swl $v0, 5($a0) swr $v0, 2($a0) jr $ra lb $v0, 0($a0) # End of function upd_d2
Big endian
.globl upd_d2 upd_d2: lwl $v0, 2($a0) lwr $v0, 5($a0) addiu $v0, $v0, 1 swl $v0, 2($a0) swr $v0, 5($a0) jr $ra lb $v0, 0($a0) # End of function upd_d2
Pseudocode
int __fastcall upd_d2(char *a1) { ++*(_DWORD *)(a1 + 2); return *a1; }

MicroMIPS

MicroMIPS, as you have probably guessed, is supported too, with its special instructions and quirks.

Assembler code
lwm16_sp: var_10 = -0x10 addiu $sp, -0x10 swm $ra,$s0-$s2, 0x10+var_10($sp) move $s0, $a0 move $s1, $a1 move $s2, $a2 addu $s0, $s1 addu $v0, $s0, $s2 lwm $ra,$s0-$s2, 0x10+var_10($sp) jraddiusp 0x10
Pseudocode
__int64 __fastcall lwm16_sp(int a1, int a2, int a3) { return a1 + a2 + a3; }

Floating-point operations

The MIPS processor contains a number of complex floating point instructions, which perform several operations at once. It is not easy to decipher the meaning of the assembler code but the pseudocode is the simplest possible.

Assembler code
x2y2m1f: lui $v0, %hi(dbl_50) ldc1 $f1, dbl_50 sub.d $f0, $f12, $f1 add.d $f1, $f12, $f1 mul.d $f0, $f1 jr $ra madd.d $f0, $f13, $f0, $f12
Pseudocode
double __fastcall x2y2m1f(double a1, double a2) { return a2 * ((a1 - 1.0) * (a1 + 1.0)) + a1; }

Compiler helpers

A compiler sometime uses helpers; our decompiler knows the meaning of the many helpers and uses it to simplify code.

Assembler code
mod4: var_C = -0xC var_s0 = 0 lui $gp, %hi(_GLOBAL_OFFSET_TABLE_+0x7FF0) addiu $sp, -0x20 la $gp, _GLOBAL_OFFSET_TABLE_+0x7FF0 li $a3, 5 sw $ra, 0x1C+var_s0($sp) sw $gp, 0x1C+var_C($sp) lw $t9, (__moddi3_ptr-0x7FF0 - _GLOBAL_OFFSET_TABLE_)($gp) jalr $t9 ; __moddi3 move $a2, $zero lw $ra, 0x1C+var_s0($sp) jr $ra addiu $sp, 0x20
Pseudocode
__int64 __fastcall mod4(__int64 a1) { return a1 % 5; }