Thursday, January 19, 2012

Embedding GDB breakpoints in C source code

Have you ever wanted to embed GDB breakpoints in C source code?

int main() {
printf("Hello,\n");
EMBED_BREAKPOINT;
printf("world!\n");
EMBED_BREAKPOINT;
return 0;
}

One way is to directly insert your CPU's breakpoint instruction. On x86:

#define EMBED_BREAKPOINT  asm volatile ("int3;")

There are at least two problems with this approach:

  • They aren't real GDB breakpoints. You can't disable them, count how many times they've been hit, etc.

  • If you run the program outside GDB, the breakpoint instruction will crash your process.

Here is a small hack which solves both problems:

#define EMBED_BREAKPOINT \
    asm("0:"                              \
        ".pushsection embed-breakpoints;" \
        ".quad 0b;"                       \
        ".popsection;")

We place a local label into the instruction stream, and then save its address in the embed-breakpoints linker section.

Then we need to convert these addresses into GDB breakpoint commands. I wrote a tool that does this, as a wrapper for the gdb command. Here's how it works, on our initial example:

$ gcc -g -o example example.c

$ ./gdb-with-breakpoints ./example
Reading symbols from example...done.
Breakpoint 1 at 0x4004f2: file example.c, line 8.
Breakpoint 2 at 0x4004fc: file example.c, line 10.
(gdb) run
Starting program: example 
Hello,

Breakpoint 1, main () at example.c:8
8           printf("world!\n");
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000004004f2 in main at example.c:8
        breakpoint already hit 1 time
2       breakpoint     keep y   0x00000000004004fc in main at example.c:10

If we run the program normally, or in GDB without the wrapper, the EMBED_BREAKPOINT statements do nothing. The breakpoint addresses aren't even loaded into memory, because the embed-breakpoints section is not marked as allocatable.

You can find all of the code on GitHub under a BSD license. I've done only minimal testing, but I hope it will be a useful debugging tool for someone. Let me know if you find any bugs or improvements. You can comment here, or find my email address on GitHub.

I'm not sure about the decision to write the GDB wrapper in C using BFD. I also considered Haskell and elf, or Python and the new pyelftools. One can probably do something nicer using the GDB Python API, which was added a few years ago.

This code depends on a GNU toolchain: it uses GNU C extensions, GNU assembler syntax, and BFD. The GDB wrapper uses the Linux proc filesystem, so that it can pass to GDB a temporary file which has already been unlinked. You could port it to other UNIX systems by changing the tempfile handling. It should work on a variety of CPU architectures, but I've only tested it on 32- and 64-bit x86.

19 comments:

  1. call me a moron, but why would you want to put gdb breakpoints into the source code?

    ReplyDelete
    Replies
    1. This is how ASSERT works

      Delete
    2. You can put complex if statements and other exploration of state to trigger the breakpoint.

      Delete
    3. I thought gdb conditional breakpoints could inspect the state already, though.

      Delete
    4. beeing able to put gdb breakpoint in the code can be useful when, in between runs you loose the debugging configuration, which is sometimes the case when you do embedded development (the device reboots, communication link goes down, gdb ide loose connection and...you're done).
      Personally I would be very glad to see the equivalent for ARM, ST20, ST40 and boadcom chips...

      Delete
    5. I am using asm("bkpt;"); to break on ARM architecture, but for some reason GDB cannot continue after this occurs. It says it cannot access the memory any more.

      Delete
  2. Another technique I've seen, admittedly a bit more convoluted, is to redefine assert like the following (only relevant portions shown):

    #define assert(EXPR)\
    {\
    if (!(EXPR)) {\
    if (isatty(STDIN_FILENO)) {\
    cerr << "PID: " << getpid() << endl;\
    char c;\
    cin.get(c);\
    }
    abort();\
    }
    }

    Basically, if you're running the program from a tty, upon assertion failure, the program outputs the PID and then waits for a single keystroke before aborting. At this point, the developer has a chance to fire up gdb, "attach" to the given PID. Once attached, you can then navigate the call stack, etc.

    ReplyDelete
  3. Using pyelftools and the GDB Python API, I implemented the functionality of gdb-with-breakpoints.c a new "break-embed" GDB command here: https://github.com/scottt/embedded-breakpoints/blob/master/gdb-with-breakpoints.py

    ReplyDelete
  4. How about

    #define EMBED_BREAKPOINT bp()
    void bp(void) {}

    Then when you start gdb, do this:

    (gdb) b bp

    ReplyDelete
    Replies
    1. This won't work while running with O[1/2/3] - GCC will omit all calls to bp

      Delete
    2. Using something like this will prevent the function call from being optimised away (gcc):

      __attribute__((noinline)) void __brk(void) { asm (""); }

      Delete
  5. How about: gdb -ex 'break example.c:8' -ex 'break example.c:10' -ex run ./example

    ReplyDelete
  6. Or: gdb -x GDB_commands_file ./example

    ReplyDelete
  7. Hi, Keegan!

    I’m the web editor at iMasters, one of the largest developer communities in Brazil. I´d like to talk to you about republishing your article at our site. Can you contact me at rina.noronha@imasters.com.br?

    Bests,
    Rina Noronha
    Journalist – web editor
    www.imasters.com.br
    redacao@imasters.com.br
    rina.noronha@imasters.com.br
    +55 27 3327-0320 / +55 27 9973-0700

    ReplyDelete
  8. I've used "sleep" routines in code: when the program hits that, it sleeps and you can attach gdb at that point. very useful for those programs that fork/exec and you typically need to step through the child.

    ifdef it out when you are done.

    ReplyDelete
  9. It would be great if there was a standard way to do this throughout the GCC toolchain.

    Techniques that require you to set breakpoints are usable but not helpful for others who won't know where to set them.

    Here's an example of an assert implementation I recently did:
    http://www.microchip.com/forums/tm.aspx?m=680347

    For GUI app debug builds, I usually override assert to pop a dialog, but that doesn't necessarily catch assertions within library code.

    As I said, it would be great if this was fully supported through GCC toolchain !
    Hope that's helpful,
    Best Regards, Dave

    ReplyDelete
  10. Good day! It was so amazing to visit your personal blog and in particularly to read this blog post. In addition to that I would like to ask you a question that is very intriguing for me. Did you try guest blogging?

    ReplyDelete