Bill Gatliff.com billgatliff.com Home
Articles

Implementing a Debugging Agent for the GNU Debugger

TODO: this article is still undergoing revision and proofing.

Despite its low cost and popularity as a workstation debugger, the GNU debugger, gdb, is an extremely powerful and flexible tool for embedded systems development. The following paragraphs describe how the GNU debugger works, and how you can implement a remote debugging agent that allows gdb to debug application code running on your embedded system. The text introduces the basics of gdb's communications protocol, then provides example implementations for the commands gdb needs to step code, read and write registers, and handle breakpoints.

What is Gdb?

The GNU Debugger, gdb is the GNU software debugger. It is an extremely versatile and powerful application that can be used to debug sophisticated programs written in C, C++ and several other programming languages. Gdb is most often used for debugging programs running on the same machine as the debugger itself.

Gdb provides a "remote target" capability that, when properly accommodated in a device, allows source level debugging across a serial port or network connection to any microprocessor GNU supports (more than 30 to date), even if the target microprocessor is different than the one running the debugger.

The key to integrating this capability into an embedded system lies in knowing gdb's Remote Serial Protocol (RSP), and understanding how to implement a debugging agent: a small program running on the target hardware that helps gdb carry out requests to monitor and control the application being debugged.

Gdb in Action

Figure 1 is a partial screen capture of a typical debugging session. It lists the commands used to initiate communications with the debugging target, download a program, and debug it. Commands to step code, set breakpoints and display data are also shown.

Figure 1. A typical debugging session (paraphrased).

localhost$ sh-hitachi-hms-gdb a.out
GNU gdb 5.0
Copyright 2000 Free Software Foundation, Inc.
(gdb) target remote /dev/ttyS0
(gdb) load
Loading section .text, size 0x1280 vma 0x1000
Loading section .data, size 0x760 vma 0x2280
Loading section .stack, size 0x10 vma 0x30000
Start address 0x1000
Transfer rate: 53120 bits in <1 sec.
(gdb) b main
Breakpoint 1 at 0x8048476: file test.c, line 5.
(gdb) continue
Breakpoint 1, main () at test.c:5
5 for( i = 0; i < 10; i++ ) {
(gdb) display j
1: j = 1074136126
(gdb) step
6 j = i * 2 + 1;
1: j = 1074136126
(gdb) step
5 for( i = 0; i < 10; i++ ) {
1: j = 1
(gdb) quit

An Overview of Remote Debugging

If you are familiar with the concept of a rom monitor, or you have ever used a typical, commercially prepared microprocessor evaluation kit, then you have already experienced the concept being discussed here. The important idea is that, in such a setup, there is always a small amount of code resident in the debugging target, usually in a ROM or flash chip. This code, which is the monitor itself, boots the target hardware and then waits until the debugger tells it what to do next.

In GNU lingo, the small bit of code that lives on the debugging target is called a debugging agent (or, sometimes, a debugging stub), and its job is to implement debugger commands to read and write memory, query and set register values, and run the program being debugged. The debugging agent also tells the debugger about unexpected events like breakpoint hits, and application errors like an attempt to divide by zero.

In general, what happens during a debugging session is this: the debugger sends the debugging target a series of memory writing commands that transfer the application to be debugged from the development host to the debugging target (this step is not necessary if the application has already been installed in the target system by some other means). After that, the debugger tells the target to jump to the memory location that is the start of the program.

The running application encounters a breakpoint, and the debugging agent notifies the debugger that the application has been paused and is now waiting for further instructions. The debugger may peek and poke memory to discover and assert the values of registers and data, but eventually it tells the target to resume the application. At some point, either the application ends or the debugger tells it to go away.

On a typical day, shortly thereafter this cycle repeats itself.

How Do We Do That?

The implementation of a debugging agent requires a lot of information about the target processor's debugging capabilities, and the communications protocol used by the debugger. Most processors feature opcodes and hardware for providing breakpoints and catching exceptions generated by the program; the communications protocol for the debugger, in the case of gdb, is well documented and freely available.

The following sections describe gdb's debugging agent communications protocol, and provide examples on how to use the features of the Hitachi SH-2 microprocessor family to implement a debugging agent.

The GDB Remote Serial Protocol

The GDB Remote Serial Protocol (RSP) is the lingua franca between gdb and a debugging target. It defines messages for reading and writing data, controlling the application being debugged, and reporting application status.

The RSP uses plain ASCII characters for most messages. Messages begin with a dollar sign ($), and end with a gridlet (#) and an ASCII encoded, 8-bit checksum. Put another way, each message looks like this:

$ <data> # CKSUM_MSN CKSUM_LSN

where <data> is a string of ASCII hex [0-9,a-f,A-F] characters, and CKSUM_MSN and CKSUM_LSN are ASCII hex bytes representing the most and least significant nibbles of the packet's eight-bit checksum.

When a message is sent, the receiver acknowledges with either:

+ if the received checksum was correct, and the receiver is ready for the next packet; or,
- if the received checksum was incorrect, and the message needs to be retransmitted.

A debugging stub responds to messages from gdb with either data, an OK, or an error code. When gdb receives an error code, it reports the number to the user via the gdb console, and then halts whatever activity is currently in progress.

The following sections summarize and provide examples of the essential RSP commands. If the command is issued by gdb, then the examples show that command packet on the first line; the second line is the target's response. If the packet is documented as a target response, then the first line of the example is a command (perhaps one of many) that gdb may issue to elicit that response.

Read Registers (g)

This command is issued by gdb to request the values of all target registers. The target replies with a single message containing ordered register values packed end-to-end. The ordering of the values is hardcoded in the debugger, and is target specific. The ordering is
documented in the target specific configuration files in the gdb/config/ directory of gdb's source code.

[gdb]     $g#67
[target] +
$0123456789abcdef0123456789abcdef...#xx

Write Registers (G)

This command is issued by gdb to assert the values of all target registers. The target replies with OK. The ordering of the values is identical to the ordering used in the Read Registers (g) command.

[gdb]     $0123456789abcdef0123456789abcdef...#xx
[target] + $OK#9a

Note that this command does not necessarily cause immdediate modification of target register contents. Consider the case where the target's stack pointer value is modified by this command: such action would likely crash the debugging agent and/or the application being
debugged. Instead, the typical implementation stores values received by this command in a data structure called a register file; these values are placed into physical registers as the debugging agent transfers system control back to the application.

Write Register n (P)

This command is sent by gdb to set the value of a particular register, esp. the program counter. The register is enumerated per the Read Registers (g) command. The target replies with OK.

As with the other commands that modify register values, this command does not cause modification of physical register values until the debugging agent transfers control back to the application.

[gdb]     $P10=0040149c#b3
[target] +
$OK#9a

Read Memory (m)

This command is sent by gdb to request the contents of target memory. The target responds with the data, sorted in order of increasing byte address. The target is responsible for handling any alignment or other access restriction issues.

Arguments are the memory address, and the number of bytes. The example reads two bytes from address 0x4015bc.

[gdb]     $m4015bc,2#5a
[target] +
$2f86#06

Write Memory (M)

Sent by gdb to set the values of a region of target memory. The target must deal with any alignment or access restriction issues, esp. writing single bytes to control registers that have fixed multibyte widths. Such accesses would not normally be encountered during a program download, but may occur if a startup script or user at the debugging console tries to configure target peripheral control registers.

Arguments are the memory address, length of the data in bytes, and the data. Data is sorted in increasing byte order. Target responds with OK.

The example sets the byte at address 0x4015cc to
0xc3, and the byte at address 0x4015cd to 0x20.

[gdb]     $M4015cc,2:c320#6d
[target] +
$OK#9a

Query Section Offsets (qOffsets)

This command is sent by gdb to determine what relocation, if any, the target applies to applications and data during the download process. Unused in typical embedded systems, recognition of this command improves gdb startup performance by avoiding a timeout delay waiting for a response.

There are no arguments to this command. Target responds with the offsets it applies to the application's .text, .data and .bss sections, all of which are typically zero.

[gdb]     $qOffsets#4b
[target] +
$Text=0;Data=0;Bss=0#04

Set Thread (H)

Sent by gdb to constrain future commands to the thread specified in the arguments. Particularly important for thread-aware debugging in applications that utilize an RTOS. The thread number is user-specified, and usually maps directly to a thread or task identifier in the application's operating system.

In non-multithreaded applications, or applications that can stop all threads when a breakpoint is encountered, it is safe to leave this command either unimplemented, or implemented as a do-nothing.

The lone argument to this command is the thread number; if the thread number specified is -1, then gdb is requesting that any thread-specific capabilities in the debugging agent be disabled, i.e. "apply the following to all threads". The target replies with OK.

[gdb]     $Hc-1#09
[target] +
$OK#9a

Step (s)

Sent by gdb to request that the debugging agent step one machine instruction. This command has one optional argument: the address of the instruction to step. There is no return message from the target, except for the message acknowledgement.

[gdb]     $s#73
[target] +

After sending this command, gdb waits for the target to indicate that it has stopped at a breakpoint, encountered an exception condition, or terminated the program; this response will come in the form of one of the target responses described in the following sections. If the user presses a CTRL-C at the gdb console, gdb sends a ^C (\003, ASCII ETX) to the target, in an attempt to interrupt the running application. The target may choose to ignore the request.

Continue (c)

Sent by gdb to instruct the target to continue program execution. An optional argument specifies the address to resume at; if this argument is omitted, the target resumes at the current Program Counter.

As with the Step (s) command, gdb waits for the target to indicate that it has stopped at a breakpoint, encountered an exception condition, or terminated the program. If the user presses a CTRL-C at the gdb console, gdb sends a ^C to the target. The target may choose to ignore the request.

[gdb]     $c#63
[target] +

Report Target Status (?)

Sent by gdb to request target status, esp. during initial serial link setup. Target responds with a signal identifier as per TODO Section 5.12 , or an expedited message that contains the signal identifier plus register values, as per TODO Section 5.13.

[gdb]     $?#3f
[target] + $S05#b8

Terminate Application (k)

Sent by gdb to instruct the target to terminate the application, esp. when gdb itself exists or disconnects from the target. The target may implement this command as a forced reset, a do-nothing, or something in between. The target responds with an OK.

[gdb]     $k#6b
[target] + $OK#9a

Target Status Response (S)

Sent by the target in response to a Report Target Status command, at the occurrence of a breakpoint, in response to an exception condition, or upon application termination. Gdb does not send a reply to this message, other than acknowledgement.

The message's single argument is the signal number, encoded in a two-byte hex format. Signal numbers are specified in gdb/target.h, the enumeration enum target_signal.

[gdb]     $?#3f
[target] + $S05#b8

Expedited Target Status Response (T)

Sent by the target in response to a Report Target Status command, at the occurrence of a breakpoint, in response to an exception condition, or upon application termination. Semantically equivalent to the Target Status Response message documented in TODO Section 5.12.

The Expedited Target Status Response message is intended to minimize target communications, by eliminating the need for gdb to issue another target request to fetch register values. As such, implementation of this message is optional but recommended, as it improves gdb performance over low-bandwidth and/or high-latency links.

Arguments to this message are a signal number encoded as a two-byte hex number, followed by one or more combinations of a register number, a colon, register data, and a semicolon. Registers are numbered per TODO Section 5.1.

[gdb]     $?#3f
[target] +
$T0510:1238;F:FFE0..#xx

Console Output (O)

Sent by the target to request output at the gdb console. There is currently no corresponding strategy for gdb to request target console output, or for either gdb or a target to simulate console input.

The argument to this message is a string of data encoded as two-byte hex. Gdb will not display the message until a newline, 0x0a, is received.

The following example sends a "Hello, world!\n" message to the gdb console. Gdb does not respond to this message, other than message acknowledgement.

[target]  $O48656c6c6f2c20776f726c64210a#55
[gdb] +

TODO: continue here

 

Site Design by: One Hat Design Studio.