Daniel is an engineer for Advanced Micro Devices and can be contacted at 5204 East Ben White Blvd., Austin, TX 78741.
It is generally more costly to develop programs for embedded processors than for equivalent applications on engineering workstations. For one thing, embedded-application code usually can't take advantage of underlying operating-system support. Consequently, embedded systems developers might choose to first install in their code a small debug-support monitor or third-party executive. And, in the process of getting an embedded support monitor running or developing application code to run directly on the processor, emulation hardware might also be used. Thus, the availability and configurability of debug tools is an important factor when selecting a processor for an embedded project.
In this article, I'll discuss a processor-independent specification called the Universal Debug Interface (UDI) that enables greater debug-tool configurability. A number of emulator and embedded-monitor suppliers, as well as high-level language debug-tool suppliers, are currently configuring their tools to comply with a proposed UDI standard. Current implementations are targeted for RISC-based code development. UDI should ease the selection of tools and ultimately help developers move to RISC. Here I'll examine issues involved in integrating the the UDI with GDB, the Free Software Foundation's C-language source-level debugger.
GDB conforms to the UDI specification and is an example of a DFE process. As an example of a TIP process, I'll look at the MiniMON monitor for the Am29000 RISC processor.
When developing code to run on engineering workstations, the host processor that supports debugger execution is the same as the target. This means the debugger can use operating-system services such as the ptrace() UNIX system call to examine and control the program being debugged. (I'll go into more detail about ptrace a little further on.) When developing code for an embedded application, however, the target program and processor are usually different from the host processor and debugger. The host and target processors do not communicate via ptrace(), but through whatever hardware communication path links the two. The portion of the debugger which controls communication with the target processor is known as the "target interface module." Each time a change or addition is required in the communications mechanism, the debugger must be recompiled to produce a binary executable specific to the target processor and target communications requirements.
The goal of the UDI is to provide a standard interface between the debugger developer and the target communications module, so the two can be developed and supplied separately. In fact, application developers could construct their own communications module for some special hardware communications link, as long as it complied with the standard. Additionally, debuggers from one company could operate with emulators from another.
If UDI were a specification at the procedural level, debugger and communications-module developers would have to supply linkable images of their code so the debug-tool combination could be linked by the user. This isn't desirable because a linked image would be needed for every tool combination. Additionally, the final linked program would be required to run on a single debug host. UDI actually relies on an interprocess communication (IPC) mechanism to connect two different processes: the debugger front end (DFE) process, whereby the debugger is linked into an executable program to run on the host processor; and the target interface process (TIP), in which the communications module is linked as a separate process which runs on the same or a different host processor. The two processes communicate via the UDI interprocess-communication specification.
Two IPC mechanisms have been specified by the UDI committee: One uses shared memory and is intended for DOS developers; and the other uses sockets and is intended for UNIX and VMS developers. Of course, when the shared-memory IPC implementation is used, the DFE and TIP processes must both execute on the same host processor. Using sockets with Internet domain communication enables the DFE and TIP to execute on separate hosts on a computer network. Thus, a target processor connected to a network node located in a remote hardware lab can be debugged from the application developer's workstation. Using sockets with UNIX domain addresses (the method used to implement UNIX pipes) enables both processes to run on the same host.
Some currently available UDI-conforming debug tools are presented in Figure 1. The interprocess-communications layer defined by UDI enables the application developer to select any DFE front-end tool with any TIP target-control tool.
Because developers of UDI-conforming tools must each have code which interfaces with the IPC mechanism according to the UDI protocol, the UDI community freely shares a library of code know as the UDI-p library; see Table 1. This code presents a procedural layer which hides the IPC implementation. In Example 1, for instance, the DFE code calls the UDIRead function, which transports the function call to the TIP process. The TIP programmer must resolve the function request by adding code specific to controlling the particular target. Since the IPC layer is effectively transparent, the TIP programmer is unaware that the procedure caller is from a different process, possibly on a different host machine.
Procedure Operation -------------------------------------------------------------------- UDIConnect Connect to selected TIP. UDIDisconnect Disconnect from TIP. UDISetCurrentConnection For multiple TIP selection. UDICapabilities Obtain DFE and TIP capability information. UDIEnumerateTIPs List multiple TIPs available. UDICreateProcess Load a program for debugging. UDISetCurrentProcess Select from multiple loaded programs. UDIDestroyProcess Discontinue program debugging. UDIInitializeProcess Prepare runtime environment. UDIRead Read data to target-processor memory. UDIWrite Write data to target-processor memory. UDICopy Duplicate a block of data in target memory. UDIExecute Start/continue target-processor execution. UDIStep Execute the next instruction. UDIStop Request the target to stop execution. UDIWait Inquire about target status. UDISetBreakpoint Insert a breakpoint. UDIQueryBreakpoint Inquire about a breakpoint. UDIClearBreakpoint Remove a breakpoint.
UDIRead( /* a UDI-p procedure */ UDIResource from, /* source address on target */ UDIHOSTMemPtr to, /* destination address on DFE host */ UDICount count, /* count of objects */ size_t size, /* size of each object */ UDICount *count_done, /* count actual transferred */ UDIBool host_endian); /* endian conversion flag */
Because the DFE and TIP processes may be running on different machines, you must be careful when moving data objects between hosts. An int-sized object on the DFE-supporting machine may be a different size from an int on the TIP-supporting machine. Further, the machines may have different endian formats. The UDI-p procedures use a machine-independent data-description technique similar to the UNIX XDR library. Data is converted into a universal data representation (UDR) format before being transferred via sockets. When received, the data is converted from UDR format into data structures appropriate for the receiving machine. The UDI-p procedures keep the UDR activity hidden from the UDI user.
ptrace() is a UNIX system call that provides a way for one process to control the execution of another executing on the same processor. The process being debugged is said to be "traced." However, this does not mean that the execution path of a process is recorded in a "trace buffer," as with many processor emulators. Debugging with ptrace relies on the use of instruction breakpoints and other hardware- or processor-generated signals that cause execution to stop; for example, ptrace(request, pid, addr, data).
There are four arguments, the interpretation of which depends on the request argument. Generally, pid is the process id of the traced process. A process being debugged behaves normally until it encounters some signal--an internally (processor) generated illegal instruction or externally generated interrupt, for example. The traced process then enters a stopped state and the tracing process is notified using the wait() system call. When the traced process is in the stopped state, you can examine and modify its core image with ptrace(). If necessary, you can use another ptrace() request to terminate or to continue the process. Table 2 lists ptrace() request services.
Request Operation ------------------------------------------------------------- TraceMe Declare that the process is to be traced. PeekText Read one word in process's instruction space. PeekData Read one word in process's data space. PeekUser Examine the process-control data structure. PokeText Write one word in process's data space. PokeData Write one word in process's data space. PokeUser Write to the process-control data structure. Cont Startup process execution. Kill Terminate the process being debugged. SingleStep Execute the next instruction. GetRegs Read processor registers. SetRegs Write processor registers. ReadText Read data from process's instruction space. ReadData Read data from process's data space. WriteText Write data into process's instruction space. WriteData Write data into process's data space. SysCall Continue execution until system call.
However, you can't use ptrace to debug embedded-application software because the process with the user-interface that controls debugging and the application being debugged may not be executing on the same processor. DFE must run on a separate processor and communicate with the processor supporting execution of the application code.
In place of ptrace(), GDB can use a procedural interface which allows communication with a remote-target processor. The procedures implement the necessary protocols to control the hardware connecting the remote processor to the "host" debug processor. By this means, GDB can be used to debug embedded-application software running on application-specific hardware.
GDB 3.98 (and up) achieves this via procedure pointers which are members of a target_ops structure. The procedures currently available are listed in Table 3 . According to GDB-configuration convention, the file remote-udi.c must be used to implement the remote-interface procedures. In the case of interfacing to the IPC mechanism used by UDI, the procedures in Table 2 are mapped into the UDI-p procedures in Table 1. With the UDI-p library, it is simple to map the GDB remote-interface procedures for socket communication with a remote target processor.
Function Operation ------------------------------------------------------------------------ to_open() Open communication connection to remote target. to_close() Close connection to remote target. to_attach() Attach to a loaded and running program. to_detach() Detach for multitarget debugging. to_start() Load program into target-system memory. to_wait() Wait until target-system execution stops. to_resume() Startup/continue target-system execution. to_fetch_register() Read target-system processor register(s). to_store_register() Write register(s) in target-system processor. to_xfer_memory() Read/write data to target-system memory. to_insert_breakpoint() Establish an instruction break address. to_remove_breakpoint() Remove a breakpoint. to_load() Load a program into the target-processor memory.
MiniMon is not intended to be a stand-alone monitor. That is, it requires the support of a software module located in a support processor--the TIP software module. The Am29000 target processor communicates with the processor running the TIP process via a serial link or other higher-performance channel. This link supports a message system private to the MiniMon monitor--that is, completely independent of the UDI protocol; see Figure 2.
Embedded systems developers are used to working with emulators that enable code to be downloaded to application memory or installed in substitute overlay memory. This avoids the development delays associated with running code from EPROM. However, although emulators are the first stage in getting the target hardware functional, once the processor can execute out of target-system memory and a communications channel (like serial link) is available, the need for an emulator decreases. Emulators are expensive, and it is not always possible to make one available to each team member. The use of a debug monitor (like MiniMon) during the software-debug stage of a project is an economical alternative.
MiniMon must be installed in target-system ROM memory or downloaded by the host via a shared-memory interface. The target-application code and additional operating-system code can then be downloaded via the message system. If changes to the code are required, the message system can quickly download new code without changing any ROM devices.
Most monitors do not offer high-level language support. For example, assembly-code instructions must be debugged rather than the original C code. Using GDB in conjunction with MiniMon enables source-level debugging, which is far more productive and necessary for large software projects.
Debug-tool developers are beginning to offer UDI-compliant tools. Typically the DFEs are C source-level debuggers. This isn't surprising, as the increased use of RISC-processor designs has resulted in a corresponding increase in software complexity. The use of a high-level language such as C is more productive than developing code at machine-instruction level. And further, the use of C enables much greater portability of code among current and future projects. The low cost of GDB makes it an attractive choice for developers.
Target processors and their control mechanisms are much more varied than DFEs. In the MiniMon TIP I described here, for instance, a small amount of code known as the "debugcore" is placed in processor ROM memory, enabling examination of the processor state. The MiniMon TIP communicates with the debugcore via a hardware link specific to the embedded-application hardware.
Additional TIPs already exist and others are under development. I know of an Am29000 simulator (ISS) which runs on UNIX hosts. The DFE communicating with the simulator TIP is unaware that the Am29000 processor is not present, but is instead simulated by a process, executing on, say, a UNIX workstation. Tool developers are also constructing TIP programs to control processor emulators, which will make a top-of-the-line debug environment possible.
UDI broadens the choice of embedded-application development tools and configurability options. Debugger front-end tools are supplied separately from target-control programs. The user can consider cost, availability, and functionality when selecting the debug environment.
Because debuggers like GDB are available in source form, developers can add additional debug commands, such as examination of real-time operating-system performance. This would require adding OS structural information into GDB. When the debugger front end and, for example, emulator-interface module are supplied as a single executable, adding new commands is not possible. Via the use of Internet sockets, the debugger may execute on a different networked host than the node supporting the emulator-control process.
Copyright © 1992, Dr. Dobb's Journal