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:

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.

# 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

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.

# =============== 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

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.

.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

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).

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

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.

.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

MicroMIPS

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

 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

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.

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

Compiler helpers

A compiler sometime uses helpers; our decompiler knows the meaning of the many helpers and uses it to simplify 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

Last updated