Catchpoints: Unlimited Number of FLASH Breakpoints with GDB

Embedded hardware comes with limitations, and one if it is the given number of hardware breakpoints. Depending on your MCU, 4 or only 2 hardware breakpoints are available, making debugging and stepping in read-only memory (FLASH) a challenge.

Debugging NXP LPC845 with unlimited FLASH Breakpoints using MCU-Link

Did you know that one can have ‘unlimited’ number of breakpoints in FLASH, with the help of GDB? This is very useful for extended debugging, or if you want to use breakpoints for testing?

Limited Breakpoints

As explained in Software and Hardware Breakpoints, the target can be stopped either by a hardware comparator or a breakpoint instruction in the code. The number of hardware comparator is limited by the hardware, and usually is in the range of 2-4 for an ARM Cortex-M. If I try to set more than the limit, it will not work:

If debugging an application in RAM, the debugger can change the RAM ‘on-the-fly’ and allow an ‘unlimited’ number of breakpoints. SEGGER has implemented ‘unlimited flash breakpoints‘ which modifies the FLASH, but requires a J-Link debug probe or debug firmware. A similar thing can be implemented with GDB and the help of a small script.

I have published the settings and demo project on GitHub (see links at the end of the article), using the NXP LPC845-BRK using a NXP McuLink debug probe.

‘Unlimited’ Breakpoints

On ARM Cortex-M, a breakpoint instruction can be written as

__asm(“bkpt #1”);

The bkpt instruction has an optional argument which is zero by default. When the target executes such a breakpoint instruction, it raises an exception. If a debugger is attached, the debugger (or GDB) catches the exception, and you see the application halted in the debugger.

So adding above breakpoint instructions in the code will get you any number of breakpoints. The problem with this approach is that the debugger will stop on the breakpoint instruction, and if you press ‘continue’ in the debugger, you will run again on that breakpoint. So we need a way to deal with that situation and allow a continue or custom action.

GDB Catchpoints: SIGTRAP Catching

GDB has a feature where the user can add custom ‘signal handlers’. In case of a breakpoint instruction hit, the GDB will get a ‘SIGTRAP’ signal which we can handle with a script.

This drills down to the following concept:

  • Breakpoint instructions are placed in the debug version of the application code and compiled with the application. You can place as many breakpoints as needed.
  • When launching GDB, a script is provided to catch the SIGTRAP exception, caused by the breakpoint instruction
  • Based on the immediate value of the breakpoint instruction, a custom action can be performed

The possibilities of custom actions are endless: I can print a variable, continue the application (skipping the breakpoint and continue) or keep it stopped and move the program counter after the breakpoint instruction.

Breakpoint Instruction

Let’s implement different kind of breakpoints. Because they shall be only be present in the debug version, I can write macros for it:

#ifdef DEBUG
  #define BREAK_1 __asm("bkpt #1")
  #define BREAK_2 __asm("bkpt #2")
#else
  #define BREAK_1 /* empty */
  #define BREAK_2 /* empty */
#endif

__asm(“bkpt #1”) shall stop the target, but move the program counter after the breakpoint instruction. Looking at the disassembly, the code looks like this (opcode 0xbe01):

To make it easier to enable/disable it, I’m using a macro for it, below with two different bkpt versions:

With that, I can use it like this in the code to test things:

static int test1(void) {
  BREAK_1; /* hit breakpoint and move PC to next line */
  return 1;
}

static int test2(void) {
  BREAK_2; /* hit breakpoint and continue */
  return 1;
}

SIGTRAP

To catch the breakpoint instruction, I can use catch signal SIGTRAP in GDB:

catch signal SIGTRAP
commands
...
end

This catches the signal, and inside that ‘catcher’ I can check for the breakpoint instruction and the number used with it:

# GDB script to catch ARM bkpt instruction
catch signal SIGTRAP
commands
  # check if it is a BKPT (0xbe) instruction:
  if (*(unsigned char*)($pc+1)) == 0xbe
    # check if it is a BKPT #1 instruction:
    if (*(unsigned char*)($pc)) == 0x01
      # yes: move PC after bkpt instruction
      set $pc=$pc+2
    end
  end
end

The above script gets executed whenever a breakpoint is hit. It checks if it is a ‘BKPT #1’ (Opcode 0xbe01). If so, it increments the PC by 2 to place it after the instruction. That way I can place as many breakpoints I like and the debugger will stop and I can inspect the target.

Now what if I want to skip or ignore some breakpoints. For example if I have breakpoints with ‘BKPT #2’ in my code, I can change the script to ignore them, without a need to recompile my code:

catch signal SIGTRAP
commands
  # check if it is a BKPT (0xbe) instruction:
  if (*(unsigned char*)($pc+1)) == 0xbe
    # check if it is a BKPT #1 instruction:
    if (*(unsigned char*)($pc)) == 0x01
      # yes: move PC after bkpt instruction
      set $pc=$pc+2
    else
      # in case of BKPT #2, skip it and continue
      if (*(unsigned char*)($pc)) == 0x02
        set $pc=$pc+2
        continue
      end
    end
  end
end

With this, I can have as many ‘software’ breakpoint in FLASH and deal with it using or changing my GDB script. If I want to handle one specific breakpoint instruction, I could use the code location (PC) as decision criteria too.

GDB Script and Debugging

The last missing piece is to tell GDB about that script. One way is to pass the script the the GDB launch configuration. In my case below, I have placed the .gdbinit file in the project root:

.gdbinit script

With the script loaded, the debugger stops correctly on “bkpt 1” and skips the “bkpt 2”, as expected:

Catchpoints with break and continue

Summary

Using gdb with a script to catch and handle breakpoint instructions gives me an unlimited amount of breakpoints. As a plus, I can perform specific actions by breakpoint type (number) or location (program counter). This is especially useful for automated testing or bug hunting where I need to place breakpoints in many locations, exceeding the number of hardware breakpoints.

Happy Breaking 😊

Links

2 thoughts on “Catchpoints: Unlimited Number of FLASH Breakpoints with GDB

    • Good point :-). Yes and no: yes, with the BKPT instructions the binary is different, and you cannot keep them into the non-debug version. No, because the runtime behaviour is somewhat similar to what you have with J-Link if you use more than the available hardware breakpoint. The ‘skipping’ part is the same as if you do ‘skip-points’ with the debugger. And the main part of that referenced article is about different compiler options (e.g. developing/debugging with non-optimized code, and then optimize it just for release.

      Like

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.