Breakpoints

A breakpoint provides the ability to tell the application that at a particular address or event, control is to be turned over to some other authority (in this case, the monitor/debugger). When the application relinquishes control, all context is made available to the debugger so that the debugger can display local variables , dump a stack trace, and so forth. For this discussion, I will describe two distinct types of breakpoints:

  • Hard breakpoints Established by the monitor so that when the breakpoint occurs, the monitors CLI takes control. There is no way to return to the application code after this breakpoint occurs unless the application is restarted.

  • Soft breakpoints Established by the monitor for runtime analysis (referred to before as an auto-return breakpoint). When the instruction at the breakpoint executes, control transfers to the monitor code (through the exception handler). After storing some information, the monitor returns control to the application in real time. Soft breakpoints come in several different types. Each type alters some state maintained by the monitor. This state information can be statistics that reflect execution behavior or control values used by the monitor itself (possibly to change the action taken by the soft breakpoint handler). For reasons Ill explain later, a soft breakpoint is harder to implement than a hard breakpoint.

The breakpoint is usually inserted into the application by replacing the instruction at the specified address with some other instruction that triggers an exception. [1] From this point on, I refer to this instruction as a trap . The exception handler for this trap should save the entire execution context (or register set) of the CPU. It then transfers control to some debugger-specific entry-point in the monitor. If the trap was triggered by a hard breakpoint, the monitor then presents its CLI. If the trap was triggered by a soft breakpoint, the monitor logs some state and returns control to the application. The difficulty of returning control to the application is what makes soft breakpoints more difficult than hard breakpoints.

Before returning control to the application the monitor must restore the code at the breakpoint (remember, a trap was inserted to create the breakpoint), restore all the registers that were active at the time of the breakpoint, and resume execution at the address of the breakpoint.

This last step is somewhat tricky. If the monitor simply replaces the trap with the original code, then the breakpoint is erased. To resume execution and keep the breakpoint, the monitor must somehow execute only the instruction at the breakpoint, recover control long enough to re-insert the trap instruction, and then allow the application to continue execution. Also, because changing the instruction at the breakpoint amounts to an exercise in self-modifying code, the monitor might need to take special actions to preserve cache coherency.

The full algorithm for breakpoints consists of these steps:

  1. Configure the exception handler that corresponds to the trap so that it points to code owned by the monitor. To support soft breakpoints, you must also configure the processors trace (or single-step) exception handler owned by the monitor.

  2. Insert trap(s) into the instruction address space (be aware of cache).

  3. Transfer control to the application you are debugging.

  4. At the time of the exception, copy all registers to a local area accessible by the monitor.

  5. Determine the type of exception and take the appropriate action. If the breakpoint is a hard breakpoint, branch to the monitors CLI; otherwise , take the appropriate action based on the type of soft breakpoint and continue with Step 6.

  6. Install the original instruction back into the address space (be aware of cache).

  7. Restore the register context.

  8. Put the processor into trace mode and return from the exception to the address that now contains the original instruction. Executing the original instruction now causes a trace exception.

  9. When the trace exception occurs, reinstall the trap instruction and again return control to the application. (The trap must be re-installed so that the breakpoint is active for the next time the CPU fetches from that address.)

You can avoid some of the complexity by only implementing the functionality you need. For instance, the soft breakpoint could be limited to a single break per setting. After the breakpoint is reached, your monitor would restore the original instruction (disabling the breakpoint) and resume full speed execution. This approach eliminates the complexity of Steps 8 and 9 but also eliminates the ability to take the breakpoint again in real time. To simplify things further, the whole soft breakpoint mechanism can be omitted, eliminating everything after Step 4.

Because the code for this part of the monitor is complex and extremely dependent on processor-specific techniques, I have chosen not to present any code examples here. If you are interested in the details, you can explore specific implementations on the CD.

Using Breakpoints for Code Analysis

I suspect most programmers think of breakpoints purely as tools for tracing program execution. The code analysis model Im going to describe views the breakpoint more as an event that can be used to trigger some activity. The idea behind code analysis is to provide the developer with some convenient mechanism for gathering runtime information. This information might indicate which path the code executed, but it might also reflect other information about the executing program, such as how much stack space was used or whether a particular data structure was changed. Code analysis requires the use of the soft breakpoint mechanism described above. The important result, however, isnt that some breakpoint was encountered . The important result is the information that is collected as a result of the breakpoint events.

This section assumes that you are going to bite the bullet and implement the whole soft breakpoint mechanism discussed in the previous section. Note that the implementation of this portion of the debugger is available only with certain ports of the monitor on the CD.

To enable this style of debugging, the monitor must support some means of associating some user -defined, breakpoint-specific code with each breakpoint. In MicroMonitor, the at command creates this association. For added flexibility, the code can always perform the action or else selectively perform the action based on some condition.

The monitor command syntax is as follows

 at {breakpoint tag} [if condition] {action} 

The three parameters after the command are

  • Breakpoint tag This is the name of a particular breakpoint. The existence of a tag implies that the at command must be coordinated with some other processor-specific mechanism that sets breakpoints (using some of the methods discussed before). This tag is processor-specific because the breakpoint mechanism is processor-specific.

  • If condition This is an optional test that can become part of the logic to decide whether or not to perform the action. The conditions can be the non-zero return of a specified function call, an at variable reaching some count, or the at flag containing some predetermined bit setting.

  • Action This is the operation controlled by the command. The action can adjust state maintained by the command or simply return control to the monitor.

The following examples illustrate how to use this facility.

Example 1:

The following sequence of at commands establishes a counting breakpoint based on the exception that occurs as a result of a DATA_1RD breakpoint. The breakpoint mechanism is CPU-dependent, so, for the sake of this discussion, it might be an exception that occurs using the first data breakpoint provided by the CPU. When the exception occurs, the at handler logic is part of the exception handler, and, for each at statement established by the user, there is one pass through the logic. The first pass increments the ATV1 variable (within the context of the at command), and the second pass checks to see if ATV1 is 5 . If ATV1 is 5 , the logic halts the application and turns over full control to the monitors CLI.

 at DATA_1RD ATV1++           # Increment internal AT variable one at DATA_1RD if ATV==5 BREAK  # If ATV1 equals 5, then break 

Example 2:

The following set of at commands breaks (transfers control to monitor CLI) when breakpoint ADDR_1 is executed after breakpoint ADDR_2 has executed.

 at ADDR_1 FSET01 
 # Set bit zero of the internal AT flag 
 at ADDR_1 FALL03 BREAK 
 # If both bits are set, then break 
 at ADDR_2 FCLR01 
 # Clear bit zero of the internal AT flag 
 at ADDR_2 FSET02 
 # Set bit 1 of the internal AT flag 

Example 3:

The following example demonstrates the idea of using functionality within the application to aid in the code analysis. The break occurs if the function at address 0x1234 returns 1 .

 at ADDR_1 0x1234()==1 BREAK 

Example 4:

As a final example, Ill use the at command to help detect a memory leak. Assume ADDR_1 is malloc , ADDR_2 is free and, at the time ADDR_3 is hit, I expect there to be no allocated memory. I can verify the differential between malloc / free calls by observing the content of the ATV1 variable after the breakpoint.

 at ADDR_1 ATV1++ 
 # Increment ATV1 at ADDR_1 (malloc) 
 at ADDR_2 ATV1-- 
 # Decrement ATV1 at ADDR_2 (free) 
 at ADDR_3 BREAK 
 # Break at ADDR_3 

The point of these examples is to show the power and flexibility that you get with the at command. The backend, CPU-specific code is similar to what would be used to implement basic breakpoints, but adding this at [if-condition] {action} extension puts a whole new spin on monitor-based breakpoints. While the code overhead isnt great, at actions can impose a significant real-time hit, because they insert extra code into the runtime stream every time the breakpoint event occurs. Whether this overhead is tolerable depends on the requirements of the application, but, because all processing is on-board , the performance hit is usually acceptable.

Some CPUs Provide Debug Hooks

Historically, the use of monitor-based debugging has been limited by the requirement that the instruction space be modifiable. For the vast majority of embedded systems designs, the instruction space is not modifiable. Typically instructions are stored in flash [2] memory or EPROM, and the CPU executes the code directly from that space. Modern processor designs are addressing this issue. Many of todays processors are equipped with special debug capabilities that overcome this limitation. (Plus, under MicroMonitor, the application is transferred to RAM anyway, so the instruction space of an executing application is writable.)

Debug registers add to the versatility of the monitor-based debugger because now the CPU can be configured to take a breakpoint based on one instruction address (or a range) without the requirement that the instruction space be written. Additionally, some processors support data breakpoints (sometimes referred to as watchpoints). A data breakpoint triggers a trap any time a particular piece (or range) of data is accessed. Data breakpoints can usually be qualified to specify whether a trap occurs only on reads, only on writes , or on both.

The monitor must be able to deal with these added debug capabilities (different capabilities from different CPUs). Instead of implementing a generic mechanism for inserting some trap into the instruction space, a monitor for one of these machines should exploit these extra capabilities.

Data breakpoints add even more complexity to the monitor code (but theyre worth it). Data breakpoints are more difficult to support because the monitor cannot use the address at which the exception occurred to determine what breakpoint triggered the exception. With data breakpoints, the breakpoint exception handler must be smart enough to look into the CPU state to determine if a data breakpoint occurred and, if multiple breakpoints were set, be able to determine which one caused the trap.

[1] On the 68000, the appropriate instruction is trap. For the x86, use INT3. Most CPUs have some instruction that can cause an exception, even if its nothing more than an intentional divide by zero.

[2] Yep, flash memory can be written but not in a way that would allow our implementation of a breakpoint to be practical.



Embedded Systems Firmware Demystified
Embedded Systems Firmware Demystified (With CD-ROM)
ISBN: 1578200997
EAN: 2147483647
Year: 2002
Pages: 118
Authors: Ed Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net