The processors built-in the Nintendo DS, an ARM946E-S and ARM7TDMI, support hardware exceptions such as “Data Abort – accessing invalid memory addresses” and “Undefined Instruction”. The Nintendo DS homebrew library libnds includes exception handling and comes with a default handler that displays the exception type, at which address in the instruction sequence the exception occured and a dump of all registers.
In this article I show what you can do with this information and why it is useful during development.
libnds exceptionTest example
If you installed the libnds examples, you should find the exeptionTest example in devkitPro\examples\nds\debugging\exceptionTest.
Copy&Paste from exceptionTest.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #include <nds.h> /*--------------------------------------------------------------------------------- The default exception handler displays the exception type - data abort or undefined instruction you can relate the exception to your code using arm-eabi-addr2line -e <elf file> <address> assuming you built with debug info this will display a source file and a line number The address of the instruction is shown as pc, beside the address which faulted the rest of the screen is a dump of the registers. ---------------------------------------------------------------------------------*/ //--------------------------------------------------------------------------------- int main(void) { // install the default exception handler defaultExceptionHandler(); // generate an exception *(unsigned int*)0xFA = 0x64; // never comes here while(1) swiWaitForVBlank(); return 0; } |
When you build and run the example project, it displays:
This is actually a fake screenshot. I rebuilt the Guru Meditation Error with printf, because the example didn’t behave in any emulator as on actual hardware. I tested no$gba 2.6a, iDeaS 1.0.3.3 beta and DeSmeME 0.9.5, none of them displayed the error report as on the real Nintendo DS hardware.
guru meditation origin
Guru Meditation is the name of the error that occurred on early versions of the Commodore Amiga computer when they crashed. It is analogous to the “Blue Screen Of Death” in Microsoft Windows operating systems.
The Guru Meditation error message was a black screen with the following animation at the top:
decode nds guru meditation data abort message
One of the Nintendo DS variants of the Guru Meditation error message is the data abort, which includes the following information:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | Guru Meditation Error!
data abort!
pc: 02000386 addr: 000000FA
r0: 020005BD r8 : 00000000
r1: 00000000 r9 : 00000000
r2: 00000064 r10: 00000000
r3: 000000FA r11: 00000000
r4: 0B000000 r12: 02000000
r5: 00000000 sp : 0B003CF8
r6: 00000000 lr : 020005B5
r7: 00000000 pc : 0200038E
0B003CF8: 0B000000 02000180
0B003D00: 00000000 00000000
0B003D08: 00000000 00000000
0B003D10: 00000000 00000000
0B003D18: 00000000 00000000
0B003D20: 00000000 00000000
0B003D28: 00000000 00000000
0B003D30: 00000000 00000000
0B003D38: 00000000 00000000
0B003D40: 00000000 00000000 |
The first line is always “Guru Meditation Error!”, followed by the type of the exception:
1 2 | Guru Meditation Error!
data abort! |
The libnds default exception handler displays “Data Abort” and “Undefined Instructions” exceptions.
Below the exception type is pc and addr displayed, which I guess is not very obvious for many novice programmers:
1 | pc: 02000386 addr: 000000FA |
pc is short for Program Counter. The Program Counter register indicates where the processor is in its instruction sequence. In this case, it’s the address of the instruction that caused the exception.
addr represents the exception address. In this case, writing to the memory address 0xFA:
1 2 | // generate an data abort exception *(unsigned int*)0xFA = 0x64; |
This again is followed by a register dump:
1 2 3 4 5 6 7 8 | r0: 020005BD r8 : 00000000 r1: 00000000 r9 : 00000000 r2: 00000064 r10: 00000000 r3: 000000FA r11: 00000000 r4: 0B000000 r12: 02000000 r5: 00000000 sp : 0B003CF8 r6: 00000000 lr : 020005B5 r7: 00000000 pc : 0200038E |
- r0 – r12 are the so called General Purpose Registers.
- sp (r13) is the Stack Pointer Register, which indicates the current top of stack memory.
- lr (r14) represents the Link Register, which holds the return address when the current function completes (it’s the address of the caller).
- pc (r15) is short for Program Counter. The Program Counter register indicates where the processor is in its instruction sequence.
The last information in the error report is a memory dump of the top 80 bytes of stack memory:
1 2 3 4 5 6 7 8 9 10 | 0B003CF8: 0B000000 02000180 0B003D00: 00000000 00000000 0B003D08: 00000000 00000000 0B003D10: 00000000 00000000 0B003D18: 00000000 00000000 0B003D20: 00000000 00000000 0B003D28: 00000000 00000000 0B003D30: 00000000 00000000 0B003D38: 00000000 00000000 0B003D40: 00000000 00000000 |
- First column specifies the stack address (0B003CF8)
- Second column shows the 32bit value from the stack address (0B000000)
- Third column shows the 32bit value from the stack address + 4 bytes (02000180)
how to make use of all this
This information is informative only when the output file was built with debug information. You can include debug information by adding the -g option (produce debugging information) to CFLAGS in the project makefile:
Copy&Paste from exceptionTest makefile:
1 2 3 4 5 | CFLAGS := -g -Wall -O2\ -march=armv5te -mtune=arm946e-s \ -fomit-frame-pointer\ -ffast-math \ $(ARCH) |
devkitARM includes an application named “arm-eabi-addr2line.exe”, located in devkitPro\devkitARM\bin, that can be used to convert addresses into line number/file name pairs. It supports the following command line options:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Usage: arm-eabi-addr2line.exe [option(s)] [addr(s)] Convert addresses into line number/file name pairs. If no addresses are specified on the command line, they will be read from stdin The options are: @<file> Read options from <file> -b --target=<bfdname> Set the binary file format -e --exe=<executable> Set the input file name (default is a.out) -i --inlines Unwind inlined functions -j --section=<name> Read section-relative offsets instead of addresses -s --basenames Strip directory names -f --functions Show function names -C --demangle[=style] Demangle function names -h --help Display this information -v --version Display the program's version |
We are interested in the address where the Data Abort exception occured, named pc here:
1 2 3 4 | Guru Meditation Error!
data abort!
pc: 02000386 addr: 000000FA |
I used the following commandline for conversion:
1 | arm-eabi-addr2line.exe -f -C -e -s -i exceptionTest.elf 02000386 |
The output is:
1 2 | main exceptionTest.c:23 |
When you scroll at the beginning of this article, where I present the source code of the exceptionTest example, you see line 23 is inside main(), which is stored in exceptionTest.c and contains the code that writes to the address 0xFA.
conclusion
Make sure to install an exception handler in your projects. It’s very helpful to actually see that an error occured rather than a freeze. libnds makes it enormously easy, it’s just one line of code!



Nice post ^^, and nice blog altogether.
I used to hack defaultHandler() from libnds to make sure it won’t reset too much when an exception occurs. The second screen usually proves a wonderful help to figure out the context of the crash, especially if it is showing a debug log.
(that makes me think: the debug log could be revealed only in defaultHandler() and be a hidden tiled layer for the rest of the application’s life
)
Add A Comment