Thursday, July 25, 2013

Talking in GDB Protocol - How to support Software Breakpoint

Recently I have been working on a OS project and my job is to build debugging support. More specifically, I am writing a GDB stub for an OS. I will not talk about the OS itself here but only some details of my work writing this GDB stub.

Here we are debugging a "remote" target with GDB. Unlike debugging local programs, GDB talks the remote target (the OS to be debugged) to via network/serial port connection, in a language called GDB Protocol. In this protocol, GDB send requests to the remote target to do various things, like read/write register/memory, insert/remove breakpoint/watchpoint, single step, resume execution (after single step or breakpoint), etc.

There are many articles talking about how to write a GDB stub. And you can easily find sample code, for example, remtoe.c on Linux. I am not going to repeat these things. Today I want to talk specifically about how to support software breakpoint.

If you want to play with GDB protocol, simply type in GDB prompt "set debug remote 1" so the packets sent/received will be shown on screen.

Let's assume that we want to break at a certain address. The basic idea of software breakpoint is to replace the instruction at this address with a breakpoint instruction, i.e. "INT 3" for x86 or "BRKT" for ARM. When the control flow executes to this address, the breakpoint instruction will bring you to breakpoint handler (actually an exception handler). Now GDB can interact with stub to debug the target, i.e. examine memory, insert watchpoint, etc. When the user types "continue" or "step" in GDB, the control flow on target should resume from the breakpoint.

But how to resume? Of course we should put the original instruction at breakpoint back and jump to that address. Then the breakpoint is gone and we will not break at here again. However we usually expect breakpoint to persist. After playing with GDB for a while, here is what GDB does with breakpoint:

1. GDB remembers all the breakpoints.

2. When target halts (e.g. hits a breakpoint), GDB sends 'z' commands ("remove breakpoint") to tell stub to remove all active software breakpoints. This might be so that when you examine instructions, you will see the normal ones, rather than inserted breakpoint instructions. Note that when execution is later resumed, the normal instruction at this breakpoint will be executed, rather than the breakpoint instruction (otherwise you will jump to breakpoint handler again when you try to resume).

3. When user requests to insert a software breakpoint, GDB does not immediately send 'Z' command ("insert breakpoint").

4. When user requests to continue, GDB tells stub to single step to the next instruction. Then GDB sends 'Z' commands to insert newly requested breakpoints and temporarily removed breakpoints. This way, the breakpoint we recently hit is recovered and thus software breakpoints will persist.

5. I think GDB does not care how the stub implements single step. It simply tell the stub to single step. If hardware single step is not supported, you will need to implement software single step. Hope the above description may help someone working on GDB stub like me. Good luck with yours!