by Jack Ganssle
Download
the Debuggers Special Report PDF
Learn to love your debugger youre
going to spend a lot of
time with it.
At the 1998 Embedded Systems Conference, Larry Constantine described the
only known way to write totally bug-free programs: find the smartest
programmer around, and stick him in an auditorium with a computer and
projection screen. Fill the other seats with a hundred or so of the
second-best programmers you can find and let them kibitz about the
gurus work.
As long as this option remains unavailable, well continue to write
defect-laden code
demanding long, painful sessions with our debugging
tools. The fact is that, on average, about 5% of the firmware will be just
plain wrong, yielding about 50 bugs per 1,000 lines of code. Clearly,
weve got to ensure our debuggers are powerful assistants and not
clunky half-measures.
Most modern debuggers are PC- or Unix-hosted
software GUIs divorced from the target-end debugging resources. That is,
the debugger may drive an in-circuit emulator, BDM, or ROM monitor. The
target-end resource provides basic
debug functionality like breakpoints,
single-step, memory access, perhaps trace, and the like. The debugger
itself, directing all actions from the PC, correlates low-level actions to
source code, both when you issue a command (break on C line
110) and when something happens in the target (stopped at
0x1214, which the debugger will translate to a graphical view of the
corresponding source code).
Once, each emulator and ROM monitor vendor
provided a custom debugger tuned to the
particular needs and capabilities
of their target-end device. No longer. Now the debugger tends to be a part
of the compiler/linker toolchain; usually it supports a wide range of debug
hardware resources. For example, virtually every vendor of target-end tools
for the PowerPC and 68K supports SDSs SingleStep, so you can find
emulators, BDMs, ROM monitors, and the like that all have the same user
interface.
Having one common front end for a wide range of debug tools is
a real benefit to the user. With
time-to-market pressures driving
development at a frenetic pace, were often forced to start debugging
code long before the hardware is ready. This means we need full-cycle debug
support, from pre-hardware to bringing the prototype boards up to final
integration and test. Before theres a lick of hardware, a
simulatordriven by a software debuggermight be the natural
development platform. When bringing the boards to life theres no tool
like an ICEagain, driven by a software
debugger. Final integration
and test might use almost any target-end debugging resource, but given that
the front end is always the same debugger, were not learning new
tools at every phase of the project.
Learning issues pervade the computer
field; most of us are fed up with reading manuals and mastering new
software. For instance, who has time to master a new CPUs on-chip
peripherals? Some parts have literally hundreds of registers used to
configure peripheral operation. Many debuggers now
include a complete
interactive CPU description that lets you examine and modify the contents
of each I/O register using high-level commands, often accompanied with
complete English descriptions of the meanings of each bit.
When selecting
any development tool, particularly a debugger, first understand what the
tools impact will be on your target system. Will it require ROM and
RAM resources? Many now do. Are other resources needed, like communications
ports, extra stack space, and the like?
Real-time
trace was once considered an essential feature of any embedded
development environment, and, in fact, served to differentiate many
embedded tools from the ones used for, say, PC-application development.
Were perhaps in the nadir of development at the moment, with CPUs so
complex (due to cache, prefetchers, systems-on-chip, and the like) that
trace is now often an impossible luxury.
Over the next few years well see this start to change, as various CPU
vendors find ways of bringing limited
trace information from the target to
the debugger. The good old days of 100+ bit-wide trace buffers are ending,
though; most of the solutions now offered or proposed involve sending only
partial address information to the PC. The address might come only when a
branch takes place or partial address information appears.
Regardless,
its up to the debugger to take this limited and sometimes incomplete
bit of trace data and reconstruct the processors execution path,
shifting the burden of execution
path analysis to the debugger. Today, most
debuggers are not up to the task of making sense of the data. This surely
will change.
Another trace option is to modify the code itself (for
example, Applied Microsystems CodeTEST products). Run the source
though a pre-processor that seeds a bit of instrumentation into it, and an
external hardware unit with associated debugger can capture and reconstruct
program flow. Again, this requires very close coupling between the
target-end unit and the debugger
itself.
Probably the most significant
debugger trend in recent years is the addition of RTOS-awareness to most
higher-end products. As 32-bit processors come down in price, more complex
applications are common, usually requiring an RTOS. The idea is simple: an
RTOS is a complex beast, sequencing many different activities concurrently.
The debugger, if RTOS-aware, can tell the user what happens when, and
allows debugging from the same high level of abstraction used to write the
code. Any time we add layers of
abstraction to the coding process, we
invariably see corresponding needs in the debugging tools. For instance,
write in C++ and you expect the tools to include a class browser. No doubt
someday well see TCP/IP packet sniffers as a standard part of most
debuggers as well (in fact, Hitex optionally includes such network sniffers
for their CAN-bus tools).
RTOS-awareness comprises two basic sets of
resources: RTOS state knowledge, and tracking of code execution on a
thread, rather than on a procedural
basis.
The state of the RTOS is
represented in its data structures, and includes simple information like
the task control block (TCB), which shows the current status of each
taskready, running, suspended, waiting, and so on. Equally important
are the contents of the message queues and mailboxes used to pass
information between tasks. The debugger, knowing the internal operation of
the RTOS, displays these data structures in a high-level format to the
user, giving a quick snapshot of the systems
operation.
Thread-specific information is just as important, but is
somewhat less intuitive. Remember that a task is a distinct chunk of code
that does something concurrently with other tasks. A thread is an instance
of a task. That is, the CPU could be running through one task three or four
times, all more or less simultaneously. Each instance, of course, has its
own data area to keep one from affecting another. A simple example of a
multi-threaded task is one that performs the same Fourier transform on
many
sets of datait may make little sense to write multiple copies of the
math code when a single task, using separate data areas, can handle all of
the math requirements.
Conventional debuggers fail miserably in
multi-threaded environments. Theres no way to differentiate threads,
so the breakpoint you set on one affects all instances of the code. Worse,
single-stepping through one thread results in a crazy patchwork quilt of
responses as the code stops in seemingly random instances of the
task (with
data pointers aimed at a different instance each time).
An RTOS-aware
debugger lets you specify the thread of interest. Set the breakpoint or
single step and you stay locked into that thread, with the displayed data
always corresponding to that instance of the code.
Often this capability
requires more target-end debugging resources, usually with the addition of
a bit of code linked into your own. ROM monitor users have long accepted
this requirement, one that usually has little impact on your
development
except for a slightly increased demand for ROM and RAM.
RTOS-awareness
provides a tremendous amount of insight into the structure of the RTOS; as
such, debuggers support only commercial operating systems, not homemade
ones. No single debugger supports the more than 80 commercial OSes
available, so youve got to ensure the debugger vendor explicitly
supports the RTOS youre using.
Probably one of the more frustrating parts of choosing a debugger today is
finding one that matches the
entire selected tool chain. Does the debugger
handle your compiler? Your CPU? The specific RTOS youre using? The
target-end debugging tool? Is it compatible with your version control
system? If youre really fond of a particular editor, can you
incorporate that into the debuggers IDE?
A debugger is like that old overstuffed armchair at home. Get one
youre comfortable with, as youll spend a lot of timemore
than you figureone-on-one with the tool.
Jack G.
Ganssle is a lecturer and consultant on embedded development issues. He conducts seminars on embedded systems and helps companies with their embedded challenges. He has founded two companies specializing in embedded systems. Contact him at
jack@ganssle.com.
Download
the Debuggers Special Report PDF
| Table 1: Debugger Checklist
|
| Language/CPU
|
| Does the debugger support the language and CPUyoure using? Be sure that it understands your language subset, like EC++, if any
|
| The connection
|
| Does the debugger support the full line of target-end tools youll use, like a ROMemulator, ROMmonitor, ICE, BDM, JTAG, and so forth
|
| System impact
|
| Does the debugger require system resources like RAM, ROM, interrupts, or I/O? If so, are such resources available?
|
| RTOS-awareness
|
| If youre using an RTOS, you owe it to yourself to get a debugger that understands your RTOSimplicitly. Make sure the debugger is aware of the specific RTOSyouve selected
|
| Trace
|
| If you need trace, be sure the target-end
hardware tool provides such data, and that the debugger understands how to reconstruct corresponding program flow
|