gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

DS development > Quick/Convenient debugging using the PC register.

#177571 - Dark Knight ez - Wed Sep 05, 2012 12:17 pm

Hello everyone,

It's been quite a while since I've asked something at these forums, but hopefully you'll be able to help me out once again.
Currently my engine on the DS suffers from a crash/halt, which is likely caused by an infinite loop. So far, I haven't been able to reproduce the flaw, so I was hoping the following scenario is a possibility (whenever we come across the issue by coincidence).

When the engine is stuck in an infinite ARM9 loop somehow, is it possible to:
1. Trigger an interrupt (say, by means of ejecting the slot1-card)?
2. In the triggered code, display the code that was executed before the interrupt (i.e. the value of the pc register where it will eventually return to)?
3. To see which function the executed-code belongs to?

Knowing in which function the engine is stuck would help a great deal in determining which for-loop or while-loop causes it to occur.

If the above is possible, could you enlighten me on how to perform steps 2 and 3? I've looked at gbatek, but cannot find the information I'm looking for (not having much or any experience in assembly does not aid the cause).

Thank you in advance!

#177572 - elhobbs - Wed Sep 05, 2012 2:20 pm

are you calling defaultExceptionHandler()? it will dump registers to the screen when an exception occurs. if you are not using it then it is a good place to start. without it, it is hard to identify a crash vs. an infinite loop.

I am sure that it is possible to do what you are saying but it would require unwinding the stack which is not trivial. particularly since a lot of those events happen on the arm7 and then are pushed to the arm9 via fifo.

you could try a function that breaks and dumps registers when a key (or a combination) is held. then you need to call that function in likely places.

#177574 - sverx - Wed Sep 05, 2012 3:07 pm

elhobbs wrote:are you calling defaultExceptionHandler()? it will dump registers to the screen when an exception occurs. if you are not using it then it is a good place to start.
is it possible to trigger it directly? that's interesting!

#177575 - PypeBros - Wed Sep 05, 2012 4:06 pm

sverx wrote:
elhobbs wrote:are you calling defaultExceptionHandler()? it will dump registers to the screen when an exception occurs. if you are not using it then it is a good place to start.
is it possible to trigger it directly? that's interesting!
Hmm, iirc, it's only the function that *registers* the default Exception handler that is pointed out here.

that being said, the register dump feature has been extracted to its own function in libnds at some point, so it is possible to set your *own* handler and invoke libnds's goodie here and there at will. (I'm likely to use an outdated libnds here, so double-check your own gurumeditation.[cho] before you go for it).

The interrupt-to-quit-endless-loop could be a vblank IRQ that checks for some specific input condition (L+R+START+SELECT), for instance. (haven't tried that out... sounds interesting). It wouldn't actually *get you out*, but it could at least reveal where you are.

using an emulator that can host a gdb/ddd session is much more time-efficient for the job, however.

#177577 - elhobbs - Wed Sep 05, 2012 5:06 pm

yes, defaultExceptionHandler to register the exception handler. "when an exception occurs" was the keep point.

the problem with emulators is that they do not support exceptions. so, the emulator will tend to run a lot of stuff that the hardware has a problem with. still, it can be useful.

it may be crude, but print statements are a very useful debug tool on the ds.

#177579 - Dark Knight ez - Wed Sep 05, 2012 7:44 pm

Thanks for the suggestions.
The interrupt-to-quit-endless-loop could be a vblank IRQ that checks for some specific input condition (L+R+START+SELECT), for instance. (haven't tried that out... sounds interesting). It wouldn't actually *get you out*, but it could at least reveal where you are.
I'll write a test program to see if the remove-slot1-card IRQ still triggers properly even when ARM9 is stuck in a loop (and perhaps does not retrieve proper key presses from ARM7 etc anymore). Still, how would I go about in retrieving the pc register value from before the interrupt?

P.S.
Using an emulator is something I cannot do. Emulating my real time strategy game does not run at a doable speed (if at all), nor does it guarantee I get to the point of the crash/being-stuck of the engine.

#177580 - Dwedit - Wed Sep 05, 2012 7:50 pm

I did this exact thing for PocketNES.
If it takes more than 5 seconds to execute code that normally finishes within one frame, then assume it has crashed. Your interrupt handlers should still be running, so use the vblank handler to perform that check. Use a counter that counts up. If it reaches 300, the game has crashed. Have the main loop set this variable to zero every frame. If that doesn't happen, you are probably in an infinite loop.

You'd also need to be able to turn the "crash detection" feature on and off, for all the times where you aren't yet in the main loop, or have left the main loop.

Then once you've triggered the "yep we've crashed" feature, show a stack dump. You can figure out what stack address corresponds to what register, so you can annotate the registers in the stack dump.

#177581 - Dark Knight ez - Wed Sep 05, 2012 7:57 pm

You can figure out what stack address corresponds to what register, so you can annotate the registers in the stack dump.
Like I said, I'm not too good with assembly, so I'm unsure how. I noticed the following in gbatek:

Code: Select all

BIOS Interrupt handling
Upon interrupt execution, the CPU is switched into IRQ mode, and the physical interrupt vector is called - as this address is located in BIOS ROM, the BIOS will always execute the following code before it forwards control to the user handler:
 00000018  b      128h              ;IRQ vector: jump to actual BIOS handler
 00000128  stmfd  r13!,r0-r3,r12,r14;save registers to SP_irq
 0000012C  mov    r0,4000000h        ;ptr+4 to 03FFFFFC (mirror of 03007FFC)
 00000130  add    r14,r15,0h        ;retadr for USER handler $+8=138h
 00000134  ldr    r15,[r0,-4h]      ;jump to [03FFFFFC] USER handler
 00000138  ldmfd  r13!,r0-r3,r12,r14;restore registers from SP_irq
 0000013C  subs   r15,r14,4h        ;return from IRQ (PC=LR-4, CPSR=SPSR)

As shown above, a pointer to the 32bit/ARM-code user handler must be setup in [03007FFCh]. By default, 160 bytes of memory are reserved for interrupt stack at 03007F00h-03007F9Fh.
Which might give the much needed clue, but, again, I'm not sure. Perhaps anyone more assembly-savvy would be able to help me out with a nice code snippet for obtaining the value of the pc register before the interrupt actually triggered?

#177582 - Dwedit - Wed Sep 05, 2012 8:06 pm

Make a little ASM function that sets r0-r12 and LR to known values, then immediately does an infinite loop.
Use values like 0xDEADBEE0-0xDEADBEEF, so you know what register corresponds to what hex number.
Something like this:

ldr r0,=0xDEADBEE0
ldr r1,=0xDEADBEE1
ldr r2,=0xDEADBEE2
...
ldr r12,=0xDEADBEEC
ldr lr,=0xDEADBEEE
infiniteloop:
b infiniteloop

Then you'll see the program counter somewhere in there too.
Note that they won't be in a perfect 0-F order.

screenshot of what a stack dump may look like: Image

#177584 - Dark Knight ez - Wed Sep 05, 2012 9:28 pm

I see what you're getting at...
After careful examination of the code snippet I posted, it seems I could also do the following:

Code: Select all

asm(".arm     \n"        
    "ldmfd  r13!,{r0-r3,r12,r14} \n" // restore registers from SP_irq 
    "sub    r4,r14,#4            \n" // the r4 register now holds stack's previous pc value
    // line below is only required if hereafter the code 
    // needs to continue running where it once left off 
    "stmfd  r13!,{r0-r3,r12,r14} \n" // save registers to SP_irq
);
        
register int pc_prev asm("r4"); 
I'll have to consider whether I want to go for the quick and 'dirty' approach stated above, or the nice stack dump using vblank.

Regardless of which I opt for, thanks for your expert input! (The same goes for elhobbs and PypeBros, obviously!)

#177588 - sverx - Thu Sep 06, 2012 8:52 am

PypeBros wrote:
sverx wrote:
elhobbs wrote:are you calling defaultExceptionHandler()? it will dump registers to the screen when an exception occurs. if you are not using it then it is a good place to start.
is it possible to trigger it directly? that's interesting!
Hmm, iirc, it's only the function that *registers* the default Exception handler that is pointed out here.
Oh, ok, I completely misunderstood. Thanks :)

#177593 - Dark Knight ez - Fri Sep 07, 2012 10:10 am

Well, I tried to implement it, but somehow failed.
In the function triggered by a VBlank interrupt, the following code occurs if the engine is stuck in an infinite loop somewhere (and I tested it indeed gets to this part of the code):

Code: Select all

asm(".arm\n\t"        
    "ldmfd  r13!,{r0-r3,r12,r14}\n\t" // restore registers from SP_irq 
    "sub    r4,r14,#4\n\t" // the r4 register now holds stack's previous pc value 
    // line below is only required if hereafter the code 
    // needs to continue running where it once left off 
    "stmfd  r13!,{r0-r3,r12,r14}" // save registers to SP_irq 
); 
        
register int pc_prev asm("r4"); 
Although that code compiles fine, the engine crashes and shows the output from the defaultExceptionHandler(), stating it encountered an undefined instruction in my asm code. Any idea what I've done wrong? I simply adjusted, but only slightly, the asm code gbatek states the DS itself uses in its BIOS for handling interrupts. *shrugs*

#177594 - Dark Knight ez - Fri Sep 07, 2012 11:29 am

When adding ".align 4\n\t", the error turns into a "data abort" error. Not sure if that's an improvement :')

#177595 - elhobbs - Fri Sep 07, 2012 11:31 am

Did you add a ".type" identifier to your asm function? I believe it is needed to properly transition to/from thumb/arm mode. I am not sure how it works with inline asm though.

#177596 - Dark Knight ez - Fri Sep 07, 2012 1:23 pm

I thought .arm would take care of that.

Anyway, I noticed how defaultExceptionHandler() was outputting values on the stack as well. That's all I needed anyway, and apparently the third value (2nd row, 1st column) is the previous program counter. Huzzah!

P.S.
For those interested, the fourth value seems to be the previous stack pointer. I haven't checked/investigated the other values.

#177597 - elhobbs - Fri Sep 07, 2012 5:04 pm

Dark Knight ez wrote:
I thought .arm would take care of that.
I think for inline asm code you are probably correct - I do not really know. I was thinking of asm functions see here