Download the Debuggers Special Report PDF
Learn to love your debugger–you're going to spend a lot of time with it.
At the 1998 Embedded Systems Conference, Larry Constantine described theonly known way to write totally bug-free programs: find the smartestprogrammer around, and stick him in an auditorium with a computer andprojection screen. Fill the other seats with a hundred or so of thesecond-best programmers you can find and let them kibitz about theguru's work.
As long as this option remains unavailable, we'll continue to writedefect-laden code demanding long, painful sessions with our debuggingtools. The fact is that, on average, about 5% of the firmware will be justplain wrong, yielding about 50 bugs per 1,000 lines of code. Clearly,we've got to ensure our debuggers are powerful assistants and notclunky half-measures.
Most modern debuggers are PC- or Unix-hostedsoftware GUIs divorced from the target-end debugging resources. That is,the debugger may drive an in-circuit emulator, BDM, or ROM monitor. Thetarget-end resource provides basic debug functionality like breakpoints,single-step, memory access, perhaps trace, and the like. The debuggeritself, directing all actions from the PC, correlates low-level actions tosource code, both when you issue a command (“break on C line110”) and when something happens in the target (“stopped at0x1214,” which the debugger will translate to a graphical view of thecorresponding source code).
Once, each emulator and ROM monitor vendorprovided a custom debugger tuned to the particular needs and capabilitiesof their target-end device. No longer. Now the debugger tends to be a partof the compiler/linker toolchain; usually it supports a wide range of debughardware resources. For example, virtually every vendor of target-end toolsfor the PowerPC and 68K supports SDS's SingleStep, so you can findemulators, BDMs, ROM monitors, and the like that all have the same userinterface.
Having one common front end for a wide range of debug tools isa real benefit to the user. With time-to-market pressures drivingdevelopment at a frenetic pace, we're often forced to start debuggingcode long before the hardware is ready. This means we need full-cycle debugsupport, from pre-hardware to bringing the prototype boards up to finalintegration and test. Before there's a lick of hardware, asimulator—driven by a software debugger—might be the naturaldevelopment platform. When bringing the boards to life there's no toollike an ICE—again, driven by a software debugger. Final integrationand test might use almost any target-end debugging resource, but given thatthe front end is always the same debugger, we're not learning newtools at every phase of the project.
Learning issues pervade the computerfield; most of us are fed up with reading manuals and mastering newsoftware. For instance, who has time to master a new CPU's on-chipperipherals? Some parts have literally hundreds of registers used toconfigure peripheral operation. Many debuggers now include a completeinteractive CPU description that lets you examine and modify the contentsof each I/O register using high-level commands, often accompanied withcomplete English descriptions of the meanings of each bit.
When selectingany development tool, particularly a debugger, first understand what thetool's impact will be on your target system. Will it require ROM andRAM resources? Many now do. Are other resources needed, like communicationsports, extra stack space, and the like?
Real-time trace was once considered an essential feature of any embeddeddevelopment environment, and, in fact, served to differentiate manyembedded tools from the ones used for, say, PC-application development.We're perhaps in the nadir of development at the moment, with CPUs socomplex (due to cache, prefetchers, systems-on-chip, and the like) thattrace is now often an impossible luxury.
Over the next few years we'll see this start to change, as various CPUvendors find ways of bringing limited trace information from the target tothe debugger. The good old days of 100+ bit-wide trace buffers are ending,though; most of the solutions now offered or proposed involve sending onlypartial address information to the PC. The address might come only when abranch takes place or partial address information appears.
Regardless,it's up to the debugger to take this limited and sometimes incompletebit of trace data and reconstruct the processor's execution path,shifting the burden of execution path analysis to the debugger. Today, mostdebuggers are not up to the task of making sense of the data. This surelywill change.
Another trace option is to modify the code itself (forexample, Applied Microsystems' CodeTEST products). Run the sourcethough a pre-processor that seeds a bit of instrumentation into it, and anexternal hardware unit with associated debugger can capture and reconstructprogram flow. Again, this requires very close coupling between thetarget-end unit and the debugger itself.
Probably the most significantdebugger trend in recent years is the addition of RTOS-awareness to mosthigher-end products. As 32-bit processors come down in price, more complexapplications are common, usually requiring an RTOS. The idea is simple: anRTOS is a complex beast, sequencing many different activities concurrently.The debugger, if RTOS-aware, can tell the user what happens when, andallows debugging from the same high level of abstraction used to write thecode. Any time we add layers of abstraction to the coding process, weinvariably see corresponding needs in the debugging tools. For instance,write in C++ and you expect the tools to include a class browser. No doubtsomeday we'll see TCP/IP packet sniffers as a standard part of mostdebuggers as well (in fact, Hitex optionally includes such network sniffersfor their CAN-bus tools).
RTOS-awareness comprises two basic sets ofresources: RTOS state knowledge, and tracking of code execution on athread, rather than on a procedural basis.
The state of the RTOS isrepresented in its data structures, and includes simple information likethe task control block (TCB), which shows the current status of eachtask—ready, running, suspended, waiting, and so on. Equally importantare the contents of the message queues and mailboxes used to passinformation between tasks. The debugger, knowing the internal operation ofthe RTOS, displays these data structures in a high-level format to theuser, giving a quick snapshot of the system'soperation.
Thread-specific information is just as important, but issomewhat less intuitive. Remember that a task is a distinct chunk of codethat does something concurrently with other tasks. A thread is an instanceof a task. That is, the CPU could be running through one task three or fourtimes, all more or less simultaneously. Each instance, of course, has itsown data area to keep one from affecting another. A simple example of amulti-threaded task is one that performs the same Fourier transform on manysets of data—it may make little sense to write multiple copies of themath code when a single task, using separate data areas, can handle all ofthe math requirements.
Conventional debuggers fail miserably inmulti-threaded environments. There's 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 ofresponses as the code stops in seemingly random instances of the task (withdata pointers aimed at a different instance each time).
An RTOS-awaredebugger lets you specify the thread of interest. Set the breakpoint orsingle step and you stay locked into that thread, with the displayed dataalways corresponding to that instance of the code.
Often this capabilityrequires more target-end debugging resources, usually with the addition ofa bit of code linked into your own. ROM monitor users have long acceptedthis requirement, one that usually has little impact on your developmentexcept for a slightly increased demand for ROM and RAM.
RTOS-awarenessprovides a tremendous amount of insight into the structure of the RTOS; assuch, debuggers support only commercial operating systems, not homemadeones. No single debugger supports the more than 80 commercial OSesavailable, so you've got to ensure the debugger vendor explicitlysupports the RTOS you're using.
Probably one of the more frustrating parts of choosing a debugger today isfinding one that matches the entire selected tool chain. Does the debuggerhandle your compiler? Your CPU? The specific RTOS you're using? Thetarget-end debugging tool? Is it compatible with your version controlsystem? If you're really fond of a particular editor, can youincorporate that into the debugger's IDE?
A debugger is like that old overstuffed armchair at home. Get oneyou're comfortable with, as you'll spend a lot of time—morethan you figure—one-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 firstname.lastname@example.org.
Download the Debuggers Special Report PDF
|Table 1: Debugger Checklist|
|Does the debugger support the language and CPUyou're using? Be sure that it understands your language subset, like EC++, if any|
|Does the debugger support the full line of target-end tools you'll use, like a ROMemulator, ROMmonitor, ICE, BDM, JTAG, and so forth|
|Does the debugger require system resources like RAM, ROM, interrupts, or I/O? If so, are such resources available?|
|If you're 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 RTOSyou've selected|
|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|