Introduction
iDeaS is a Nintendo DS software emulator available for Linux and Microsoft Windows. Since version 1.0.2.8 (21 Dec 2008) iDeaS features program breakpoints and user messages that can be sent to the debug console:
# Added program breakpoint (SWI #0xFDFDFD).
# Added output on console for user’s messages (SWI #0xFCFCFC).
My alarm bells started to bang in the moment I saw the changelog. Both features have been implemented using software interrupts, that do not exist on the target hardware.
What does it mean? It means, those software interrupt ID’s make your NDS application incompatible with the NDS hardware. Whenever you accidentally print an iDeaS debug message, the application will crash on actual hardware.
no$gba implemented the breakpoint and debug message facility quite excellent. In no$gba a breakpoint is just a mov r11, r11 instruction (a nop), which can be executed by the hardware by all means. The no$gba debug message system is a bit more complicated. It uses a combination of harmless instructions to detect you want to print text. However, all instructions used for that feature do work on hardware as well, this is what we want, this is how it should be implemented.
The current scenario
Shortly after the release I contacted the iDeaS author and told him the current approach isn’t top notch and should be changed that it won’t break program execution on actual hardware. Unfortunalety he didn’t see the advantage, since you can:
- Remove all prints before you test on hardware
Obviously, this cannot be the way to handle it. Manually removing all prints would be enormously time consuming and error-prone.
- Use some sort of #ifdef blocks to automatically remove all prints
This might sound like a solid idea at first glance, but thinking about it a further minute, proves it isn’t. The problem is simple, it’s too time comsuning. When you use the pre-processor to remove all print calls, you have to rebuild the entire project every time you change the corresponding pre-processor switch. Depending on how many files the project has, can it take a significant amount of time to rebuild, which is no option for many people.
- Wrap print calls with some enabled/disabled mechanism
A function that wraps print calls with a surrounding if block, removes the need to rebuild the project. Just disable debug output at program initialization and there you go. However, I tend to forget things that are of no importance for me and this is something I would forget many times!
How to solve the puzzle
We developers want a debug text system that will not break program execution, no matter if the application runs in an emulator or on actual hardware. All those previous points do make some sense, but don’t remove the problem in a whole. What we have to create is a system that:
- Is able to print text to the debug console
- Can be switched on/off at runtime
- Automatically detects when it runs on hardware and discards all print calls in this case
- Remove all print calls when building a “release version” (pre-processor)
Everthing on the list should be quite clear, except for the hardware detection. When I had the requirements-list done and was wondering how to implement that, I remembered this post at gbadev.org. They used a hardware feature that no emulator seems to emulate correctly, instruction fetching:
1 2 3 4 5 | detectGBA: ;returns 0 if emulated mov r0,#0 str r0,_0 _0: mov r0,#1 mov pc,lr |
The str r0, _0 instruction overwrites the following instruction, where r0 is set to 1. However, on real hardware the instruction would be fetched already and the change has no effect. Basically the memory at this address is being overwritten, but the instruction pipeline fetched the instruction before, so the original instruction is used. When you call the function more than once, it won’t work correctly anymore.
So I slightly changed the code to restore the original instruction to be able to call the function more than once. I also made it arm and thumb compatible. Here is the entire source code to print text to iDeaS debug console, that detects real hardware and discards all prints at runtime. You can also use a pre-processor #define to remove all print calls when you build a “release version”.
ideas.h
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 | #ifndef __ideas_h__ #define __ideas_h__ #if __cplusplus extern "C" { #endif #if !_RELEASE int IdeasEnableDebugOutput(int enable); int IdeasOutputDebugString(const char* format, ...); #else // in release mode use empty functions, so the compiler // optimizes any call to them away. inline int IdeasEnableDebugOutput(int /*enable*/) { return 0; } inline int IdeasOutputDebugString(const char* /*format*/, ...) { return 0; } #endif // _RELEASE int IdeasIsEmulator(); #if __cplusplus } // extern "C" #endif #endif // __ideas_h__ |
ideas.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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | #include "ideas.h" #include <stdio.h> #include <stdarg.h> #if !_RELEASE static int IdeasDebugOutputEnabled=1; // Gets if the program runs in an emulator. __attribute__ ((noinline)) int IdeasIsEmulator() { // The idea behind the code is to overwrite // the "mov r0, #0" instruction with "mov r0, r0" (NOP). // On real hardware, the instruction would have been fetched, // so the overwrite has no effect for the first time executed. // In order to be able to call the function more than once, // the original instruction is being restored. // see http://forum.gbadev.org/viewtopic.php?t=910 #ifdef __thumb__ int mov_r0_r0 = 0x1c00; // mov r0, r0 int mov_r0_0 = 0x2000; // mov r0, #0 asm volatile ( "mov r0, %1 \n\t" // r0 = mov_r0_r0 "mov r2, %2 \n\t" // r2 = mov_r0_0 "mov r1, pc \n\t" // r1 = program counter "strh r0, [r1] \n\t" // Overwrites following instruction with mov_r0_r0 "mov r0, #0 \n\t" // r0 = 0 "strh r2, [r1] \n\t" // Restore previous instruction : "=r"(mov_r0_r0) // output registers : "r"(mov_r0_r0), "r"(mov_r0_0) // input registers : "%r1","%r2" // clobbered registers ); #else int mov_r0_r0 = 0xe1a00000; // mov r0, r0 int mov_r0_0 = 0xe3a00000; // mov r0, #0 asm volatile ( "mov r0, %1 \n\t" // r0 = mov_r0_r0 "mov r2, %2 \n\t" // r2 = mov_r0_0 "mov r1, pc \n\t" // r1 = program counter "str r0, [r1] \n\t" // Overwrites following instruction with mov_r0_r0 "mov r0, #0 \n\t" // r0 = 0 "str r2, [r1] \n\t" // Restore previous instruction : "=r"(mov_r0_r0) // output registers : "r"(mov_r0_r0), "r"(mov_r0_0) // input registers : "%r1","%r2" // clobbered registers ); #endif return mov_r0_r0 != 0; } // This function must be noinline, because // iDeaS expects the text to output in register r0. // If this code is inlined somewhere, it's not guaranteed // that text is located in r0 anymore, thus will not work. static __attribute__ ((noinline)) void IdeasOutputDebugStringInternal(const char* text) { #ifdef __thumb__ asm volatile ("swi #0xfc"); #else asm volatile ("swi #0xfc000"); #endif } // Prints formatted output to the iDeaS debug console // Returns false when text has not been printed, true otherwise. int IdeasOutputDebugString(const char* format, ...) { va_list args; char buffer[128]; // increase to support more characters if(!IdeasDebugOutputEnabled || !IdeasIsEmulator()) return 0; va_start(args,0); vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); IdeasOutputDebugStringInternal(buffer); return 1; } // Enables or disables debug output. // Returns the previous enabled state. int IdeasEnableDebugOutput(int enable) { int old = IdeasDebugOutputEnabled; IdeasDebugOutputEnabled = enable; return old; } #endif // !_RELEASE |

I was looking for a function to print debug text for some time, but didn’t find any tutorial anywhere. This blog helped me alot, thanks!
Thanks for sharing this. I added code to my game to support printing to console via ideas protocol, but I do the whole “change preprocessor define, rebuild all” dance whenever I need to test on hardware. This will be so much better!
Add A Comment