GDB: An Open Source Debugger for Embedded Development

< Prev Contents Next >

GDB Features

Generic Features
Like any debugger, GDB provides users with the ability to control the execution of a program by setting breakpoints and single-stepping, and to look at the program's state by asking it to display the values of variables and expressions. But, since it was developed for heavy-duty workstation debugging, GDB includes high-powered features not commonly found in embedded debuggers.

GDB includes support for programs written in multiple source languages. At present, this includes C, C++, Java, Fortran, Modula-2, Scheme, and CHILL (a telecommunications language popular in Europe). GDB can also handle assembly source; more on this later. Each language has its own expression parser that kicks in if the user is currently looking at code in that language, so the most natural syntax is always in effect.

Expressions may also contain function calls. GDB implements these by composing a dummy stack frame, restarting the program at the function call, then catching it when it returns. Although this has the potential to cause mischief (what if the function does a longjmp(), or raises an exception?), users find the feature worth the risks. Many users even define functions in their program whose sole purpose is to be called from GDB, and use these to display complex data in more useful ways.

GDB is optimized for fast startup. Instead of loading the entire symbol table, which would be slow for a large program, it loads a partial symbol table, which contains only the globally visible symbols. Then, during the debugging session, it automatically fills in parts of the full symbol table as needed. For instance, it will fill in all the symbols for a function if it stops at the beginning of the function.

GDB includes a basic scripting language that supports arguments, conditionals, and loops. When used in conjunction with expression evaluation, you can construct very sophisticated debug support for an application.

Embedded Features
For embedded programming, GDB has a number of special features. As you would expect, it provides direct read/write access to raw memory and registers, breakpoints at raw addresses, and so forth.

Assembly programmers can use GDB in two different ways. If the assembler issues line number info, then GDB can debug assembly source just as if it were C. For instance, if there were a line with a macro, a single-step would step over all the instructions in the macro's expansion. For assemblers that don't generate line numbers, you can look at the disassembly and single-step by individual instructions.

GDB offers separate commands connecting to the target, downloading, and running. While it requires the novice to learn a little more, the increased flexibility is worthwhile. For instance, one can download, then examine/modify memory to ensure that the target is in the right state, and only then run the program.

GDB includes a built-in simulator for most types of processors. Despite limitations-for instance, most of these simulators do not include peripherals-this is an incredibly useful feature; simulators never glitch, downloads are fast, and you have complete control over the execution environment.

GDB's thread support works for embedded RTOSes, such as eCos. You can list all the threads, and define thread-specific breakpoints.

GDB's standard debug protocol is quite simple. A minimal implementation of the protocol need only recognize about nine types of packets, and take up less than 5K of target-side code. The protocol has many additional capabilities, but these are optional, and the target may ignore optional packets without causing any problems.

Although the standard GDB protocol is usually the best way to go for debugging a target system, it is not the only option. GDB includes some 30 different debug protocols, mostly specialized to particular targets. These protocols include support for a number of different ICEs and on-chip debugging devices, such as EST and Macraigor wigglers.

Cygnus has recently added a program tracing facility called Introspect™. This was originally developed for a Cygnus customer, as a C-level adaptation of their low-level nonstop tracing tools. The basic idea is that you install tracepoints, which, instead of stopping and waiting for you to enter commands, just collect a small amount of data and continue on. You get a high degree of control over what data to collect and when; for example, you could collect all the registers at each instruction in a function, or collect the value of localvar->field[x1] at a particular line. Later on, GDB will upload the data from the target system and let you work with it, using all the usual operations, just as if you were controlling the system directly. If you collect registers at every line in a function, you can single-step through the function as if it were live, even though the program has executed millions of instructions since the tracepoint was hit. Even better, you can single-step backwards!

GDB includes the ability to debug the debug connection as well, with the use of the remotedebug flag. This may not seem like a big deal, but in our experience of working with embedded programmers, it is very common for a seemingly innocuous target system change to disrupt the debugging connection. This could range from the debug port's UART being turned off, to a subtle mistake in the saving of registers when a double interrupt happens. By making it easy to see the actual packets going back and forth, the debugging flags allow for speedier isolation of these sorts of problems.

An even more powerful debugging option is the GDB flag remotelogfile. When set, this causes GDB to make an exact record of all the interaction with the target, allowing someone else (such as a support engineer) to replay the interaction and get GDB into a particular state. This is especially useful for debugging GDB when access to target hardware is difficult to arrange.


< Prev Contents Next >