Skip to content

How to Debug Guru Meditation Errors

Matthew edited this page May 15, 2015 · 1 revision

Here is an example of how CoolAs tracked the source of a certain Guru Meditation Error: This one was a bit of a challenge to find exactly what was going wrong, I've written a full description of the process I went through to figure it out. It won't exactly tell you how to do all this ASM trickery stuff, but should give you an idea of how figuring stuff like this out works. TL;DR is at the end.

First of all the Program Counter is at 0FFFFFFC . You can tell that this memory address is immediately invalid as the nintendo's 4MB ram runs from 0x2000000 to 0x2400000 (or something like that). So there is no hope trying to find anything there. I looked at the other variables and checked what function they originated from.

lr: cactusCheck (mobCollisions.cpp) Random Addr on stack: calculateMiscData (movFunctions.cpp) r6 & r10: Address of the static world pointer

One fairly important thing to note is that you can get a complete disassembly of the Mine-DS.elf file by using arm-none-eabi-objdump -d Mine-DS.elf I used this to figure out where the program crashed. The LR is the link register. It will generally hold the value of the address of the function that called the current one. Due to the fact that calculateMiscData is on the stack and that the program crashed after exiting cactusCheck, we can assume the execution of the program.

(something) -> calculateMiscData -> cactusCheck -> CRASH

The link register points to the instruction after the following assembly code: (The LR is misaligned at 200b4d3 instead of 200b4d2 because the arm chip on the NDS in little endian and the pc will need to be set to that value) 200b4d0: 4798 blx r3 This code will jump to the function held in the address in r3, and store the next instructions address in the link register. This piece of assembly code is generally used when you call a function from a class, as the address of the function you call and easily change if the base class points to a different inheritent, and the address is therefore always calculated at runtime. As our program crashed here we can assume that the crash was caused due to a broken pointer to a class.

What's strange about this however is that no class's function was called in cactusCheck. We will need to look closely at the disassembly of the file. This will mean following the assembly and cross checking it with the C code to see what is going on. I've attached an annotated disassembly that I made.

The first section of code loads baseMob->host and compares it to 0 (false). It the comparison was equal, the function returns. Next the code loads the address of world->blocks[x][y] and compares it to 15 (I'm assuming that this is CACTUS). If equal, the function jumps forward. This is quite important, as the compiler could have called the collisionWithCactus function directly, (argument list is the same), but instead jumped. This however shows something clever that the compiler has done. Due to the fact that collisionWithCactus is only called from cactusCheck, the compiler has inserted the code for collisionWithCactus inline, that is, the code for collisionWithCactus was inserted directly into the code for checkCactus. The compiler does this to save on space and execution time.

We can then assume the following: (something) -> calculateMiscData -> cactusCheck -> collisionWithCactus -> CRASH

collisionWithCactus then does some things to compare baseMob->timeOnCactus and quits if outside certain values. Next it loads baseMob into r0. (For C++'s self->(thingie) instruction), 1 into r1, and 0 into r2 (I assume that this is CACTUS_HURT) Next it calculates the address of the 12 byte in the baseMob's vtable and stores it into r3, and executes the blx r3 , which in turn crashes the program.

TD;LR From the analysis, I believe that the code crashed after calling baseMob->hurt(1,CACTUS_HURT) in collisionWithCactus(worldObject*, baseMob*, int, int, int, bool), mobCollisions.cpp:30 This was most likely due to the fact that the pointer to baseMob was invalid. Assuming that the registers were not modified since the jump and crash, (which is a fairly safe assumption as many of the registers can be confirmed to not have changed) The program jumped to the nonexistent address 0x0F000000, and the baseMob pointer was equal to 0x60. It took until 0x0FFFFFFC for the program to properly crash.

Annotated Disassembly: ;Used this to figure out what r6 was.

020045d0 <_Z10isSurvivalv>:
20045d0: 4b03 ldr r3, [pc, #12] ; (20045e0 <_Z10isSurvivalv+0x10>) ;Load random value
20045d2: 4a04 ldr r2, [pc, #16] ; (20045e4 <_Z10isSurvivalv+0x14>) ;Load same value as r6 in the crashed code
20045d4: 5cd0 ldrb r0, [r2, r3] ;Load r6->(something)
;The only (something)->(something) in isSurvival is the following
;world->gameMode
;Can safely assume that r6 in the crashed code is pointer to world.
;The rest of the assembly is not as important, kept for reference
20045d6: 3802 subs r0, #2 ;Random crap
20045d8: 4243 negs r3, r0 ;Random crap
20045da: 4158 adcs r0, r3 ;Random crap
20045dc: 4770 bx lr ;Return
20045de: 46c0 nop ; (mov r8, r8)
20045e0: 00183c28 .word 0x00183c28
20045e4: 021792d8 .word 0x021792d8 ;This value was in r6 in the crashed code



;Analysis of cactusCheck

0200b450 <_Z11cactusCheckP11worldObjectP7baseMobiiib>:
200b450: 2250 movs r2, #80 ; 0x50 ;Store 0x50 in r2
200b452: b5f8 push {r3, r4, r5, r6, r7, lr}
200b454: 5c8a ldrb r2, [r1, r2] ;Load the 50th variable from baseMob
200b456: 2a00 cmp r2, #0 ;Compare it to zero
200b458: d024 beq.n 200b4a4 <_Z11cactusCheckP11worldObjectP7baseMobiiib+0x54>
;If equal to zero, exit
;if( mob->host == false) exit;
200b45a: 01da lsls r2, r3, #7
200b45c: 18d3 adds r3, r2, r3 ;Multiply x by 128 or 129 or something
200b45e: 9a06 ldr r2, [sp, #24] ;Put y in r2
200b460: 1c0d adds r5, r1, #0 ;r5 is pointer to baseMob
200b462: 189c adds r4, r3, r2 ;r4 is equal to address in blocks array (y+x*129)
200b464: 00a3 lsls r3, r4, #2 ;Random crap
200b466: 581b ldr r3, [r3, r0] ;r3 = world->blocks[x][y]
200b468: 1c06 adds r6, r0, #0 ;*r6 = pointer to world* (is in the guru error)
200b46a: 2b0f cmp r3, #15 ;Check if world->blocks[x][y] is 15 (CACTUS (?))
200b46c: d01b beq.n 200b4a6 <_Z11cactusCheckP11worldObjectP7baseMobiiib+0x56>
;If so jump to a function which is inline to this one.
;Jumps to collisionWithCactus()
;Not so interesting stuff was here, basically was a ton of if statements that did stuff

;This is the code that exits the function, it was referenced to earlier
;Notice how it pops the same way that things were pushed onto the stack at the start of the function
;The link register is poped directly onto the pc, returning immediately
200b4a4: bdf8 pop {r3, r4, r5, r6, r7, pc}

;This is the start of an inline function within cactusCheck.
;r5 has pointer to baseMob, r6 has pointer to world
200b4a6: 6ba9 ldr r1, [r5, #56] ; 0x38 ;Loads r1 with 56th variable in baseMob
;There is no way of knowing which variable this is that I know
; other than looking at the C code
;r1 = baseMob->timeOnCactus
200b4a8: 1c4b adds r3, r1, #1 ;r3 = baseMob->timeOnCactus + 1
200b4aa: 2b00 cmp r3, #0 ;If time on cactus was - 1 go to c6
200b4ac: d00b beq.n 200b4c6 <_Z11cactusCheckP11worldObjectP7baseMobiiib+0x76>
200b4ae: 2201 movs r2, #1 ;Random crap
200b4b0: 2928 cmp r1, #40 ; 0x28 ;Compares baseMob->timeOnCactus to 40
200b4b2: dd06 ble.n 200b4c2 <_Z11cactusCheckP11worldObjectP7baseMobiiib+0x72>
;If it was less that jump do random crap and go to 200b4b4
200b4b4: 0612 lsls r2, r2, #24
;Do random thing
200b4b6: d106 bne.n 200b4c6 <_Z11cactusCheckP11worldObjectP7baseMobiiib+0x76>
;If baseMob->timeOnCactus was greater than 40, goto c6
200b4b8: 63ab str r3, [r5, #56] ; 0x38 ;Store baseMob->timeOnCactus+1 into baseMob->timeOnCactus
200b4ba: 2201 movs r2, #1 ;Random crap
200b4bc: 233c movs r3, #60 ; 0x3c ;Random crap
200b4be: 54ea strb r2, [r5, r3] ;Random crap
200b4c0: e7f0 b.n 200b4a4 <_Z11cactusCheckP11worldObjectP7baseMobiiib+0x54> ;Quit
200b4c2: 2200 movs r2, #0 ;Random crap (from If it was less ... do random crap)
200b4c4: e7f6 b.n 200b4b4 <_Z11cactusCheckP11worldObjectP7baseMobiiib+0x64> ;Random crap (and go to 200b4b4)
;More interesting part, I've moved the order around a bit.
200b4c8: 1c28 adds r0, r5, #0 ;r0 = baseMob (self->)
200b4c6: 682b ldr r3, [r5, #0] ;r3 = baseMob's vtable (table of all functions)
200b4ca: 68db ldr r3, [r3, #12] ;r3 = pointer to baseMob->hurt()
200b4cc: 2101 movs r1, #1 ;arg1 = 1
200b4ce: 2200 movs r2, #0 ;arg2 = 0 (CACTUS_HURT (?))
200b4d0: 4798 blx r3 ;Jump here, crashed.
;More random crap that we don't care about 
Clone this wiki locally