M0AGX / LB9MG

Amateur radio and embedded systems

Lifehack: detecting debugger connection for Cortex-M0 & Ozone

I find it useful to be able to detect from firmware if a debugger is attached. For example when an assertion fails I can use a simple __BKPT(123); to stop the code and immediately inspect everything manually in the debugger. In the field I would rather prepare debugging breadcrumbs and reset. Of course having two different firmware behaviours comes with its own risks. For example enabling debug output can affect timings and make some bugs (dis)appear. However, when hunting bugs this is also an important information on its own.

Cortex-M CPUs have a DEBUGEN bit in the debug interface that is set high whenever there is a debugger connected. However, this bit is exposed to firmware only on Cortex-M3 and larger cores. Detecting the debugger is as simple as doing CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk. Cortex-M0 and Cortex-M0+ do not have this bit available to the firmware. This article shows a very simple solution for SEGGER Ozone, my favorite Cortex-M debugger.

Firmware side

The hack starts with a simple global volatile bool GLOBAL_debugger_attached. Any global volatile variable will do. Instead of checking if (CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk)... you simply check if (GLOBAL_debugger_attached... where you have to. So far so good.

The variable will be zero initialized by the startup code. Runtime overhead will be practically the same as the DHCSR register is also volatile. Perhaps the masking will generate extra instructions so checking a bool may be actually faster.

Ozone hooks

Now... how to set this variable automatically in Ozone? Of course you can change it manually but that would be too tiring to do hundred times a day during development. Fortunately Ozone has support for scripting and hooks. The scripting system might not be as advanced as the one in gdb but good enough in this case.

To add a hook you have to edit the .jdebug file of your project and find the AfterTargetHalt function. Of course Ozone should not be open when you are editing the file.

1
2
3
4
5
6
void AfterTargetHalt (void) {
    unsigned int debug_var_address;
    debug_var_address = Elf.GetExprValue("&GLOBAL_debugger_attached");
    Target.WriteU8(debug_var_address, 1);
    Util.LogHex("Writing debug variable", debug_var_address);
}

The code is basically self-explanatory: look up the address of the variable from the elf file (mind the ampersand!) and write 1 to it whenever the CPU is halted.

Why exactly AfterTargetHalt is the best place, and not any other hook (like AfterTargetConnect)? Target connection happens before flashing and start of the firmware so the variable would be overwritten by the startup code to zero. By default Ozone halts on main() so the halt hook is very convenient because it is executed after the startup code has run. The side effect is that it will of course be ran whenever the target is manually halted but... I can live with that.

How to clear the debug variable when the debugger is disconnected? You can write an almost identical piece of code (just change 1 to 0) for the BeforeTargetDisconnect hook. 🙂