-
Notifications
You must be signed in to change notification settings - Fork 10
Jump Tables
As you work through the project you will encounter the following error when you attempt to run mips2c against a function that makes use of a jump table (or tables):
Found jr instruction at /dev/stdin line 54, but the corresponding jump table is not provided.
When the compiler encounters a switch/case statement there are a couple of ways to translate it into code:
- a bunch of if/else statements
- a chunk of code which gets indexed into based on the value of the item being switched - this is the jump table.
IDO likes to generate jump tables for switch statements that have more than 5 (6?) cases. Otherwise it will generate a more traditional switch comprising of beq
statements.
In order for mips2c to decompile a function with a jump table, it needs the jump table. The jump table gets stored in the .rodata
section of the compiled object, so if we know where the rodata for the particular file is in the rom, then we are good.
If we don't know, then we can look at the memory (i.e. via Project64) when the game is running and then search for the strings in the ROM itself.
The below is a snippet of assembly for the instructions that are making use of the jump table.
/* 6C5064 802B39B4 3C01803C */ lui $at, %hi(jtbl_803BBE9C_7CD54C)
/* 6C5068 802B39B8 002B0821 */ addu $at, $at, $t3
/* 6C506C 802B39BC 8C2BBE9C */ lw $t3, %lo(jtbl_803BBE9C_7CD54C)($at)
/* 6C5070 802B39C0 01600008 */ jr $t3
/* 6C5074 802B39C4 00000000 */ nop
The jump table can be found at 0x803BBE9C
in RAM. Looking at that section in Project64 Memory Viewer gives us:
803BBE9C 802B39C8 802B39E8 802B3A04 802B3A24
803BBEAC 802B3A24 802B3A24 802B3B80 802B3B98
803BBEBC 802B3BB0 802B3BB8 802B3BC8 802B3C38
803BBECC 802B3C48 802B3C58 802B3C68 802B3C68
803BBEDC 802B3C68 802B3CE0 802B3CF4 802B3D44
803BBEEC 802B3D10 802B3D2C 00000000 00000000
The jump table is:
802B39C8 802B39E8 802B3A04 802B3A24 802B3A24 802B3A24
We need to extract the rodata for this file, this will allow splat to generate a file which we can feed into mips2c to get our function decompiled.
rodata is aligned to 0x10 (16) so we can search back through memory to try to identify the start of the rodata for this file. Note that rodata includes floats/doubles/strings/jump tables/more.
It's not trivial to find the start/end of rodata, you can look through the assembly to determine whether you've reached the start/end (i.e. is this memory address referenced by a function in a file before/after the current one).
Assuming you've found the start and end of the rodata, you can update the splat configuration:
- [0x7CC3C0, bin] # ...
- [0x7CC470, rodata, overlay2_6B5380] # note "rodata" not ".rodata"
- [0x7CD5B0, bin] # the end of rodata for overlay2_6B5380
When we execute splat again, it will create a file asm/data/overlay2_6B5380.rodata.s
.
We then feed this into mips2c:
python3 tools/mips2c/mips2c.py asm/nonmatchings/overlay2_6B5380/func_802B3B48_6C51F8.s --rodata asm/data/overlay2_6B5380.rodata.s
And it will spit out something resembling the original function.
At the time of writing, mips2c treats the switch
statement as an if
with a goto
into the jump table. Remove this and switch
on the variable being checked.
Once you've got the function matching according to asm-differ
, you'll need to update the splat configuration again -- you need to use the .rodata
from the compiled function, rather than from the ROM:
- [0x7CC470, rodata, overlay2_6B5380]
- [0x7CD590, .rodata, overlay2_6B5380] # note .rodata
- [0x7CD5B0, bin] # the end of rodata for overlay2_6B5380
When you run make
the result may still not OK
. You may need to juggle the case statements around until you get the match.
You can dump out the .rodata
section of a given object, e.g.
mips-linux-gnu-objcopy -O binary --only-section .rodata build/src.us/overlay2_739290.c.o rodata.o
You can then sanity check the offsets, e.g.
xxd rodata.o
00000000: 0000 0880 0000 08d4 0000 08ac 0000 08d4 ................
00000010: 0000 08d4 0000 08d4 0000 08d4 0000 08d4 ................
00000020: 0000 08d4 0000 08d4 0000 08d4 0000 08d4 ................
00000030: 0000 08d4 0000 08d4 0000 08d4 0000 08d4 ................
00000040: 0000 08d4 0000 08d4 0000 08d4 0000 08d4 ................
00000050: 0000 08d4 0000 08d4 0000 08d4 0000 0000 ................
- If you find your switch being given an offset e.g.
switch(SOME_VAR - 10)
, this means that the firstcase
starts at10
, so you can either keep the subtraction as part of theswitch
check, or remove it and then add the difference to eachcase
statement