Some program behaviors can be analyzed only by collecting data over long periods of time for later analysis. Other problems occur in sections of code that are too time-sensative to be debugged interactively. In these cases, tracing may be more appropriate than breakpoint debugging.
This section discusses the following topics.
Like a breakpoint, a tracepoint makes your program stop whenever a certain point in the program is reached. However, while a breakpoint stops the program for a "long" time (while GDB prompts you and lets you type commands), a tracepoint stops the program for only a "short" time, after which the program gets to continue with minimal disruption of its behavior. During this "short" interval, the trace mechanism records the fact that it has been there (ie. that the tracepoint was executed), and may also perform certain actions that you've requested such as recording the values of selected variables and registers. Thus it's not well defined how long a "short" time is, but in any event it will be thousands of times shorter than the time required for a human to do the same tasks interactively.
As with breakpoints, GDB assigns a number to each tracepoint when you create it. Like breakpoint numbers, tracepoint numbers are successive integers starting from one. Many of the commands associated with tracepoints use the tracepoint number to identify which tracepoint to delete, disable, etc.
A trace experiment must involve each of the following steps: defining the tracepoints and actions, running the experiment, and reviewing the results. Many of the commands defined below (such as setting and deleting tracepoints) have no real effect until you actually begin running the trace experiment. Moreover, many of them also have no effect during an already running trace experiment. Once a trace experiment is running, changing the tracepoints or their attributes will have no effect unles you stop the experiment and start it again.
Tracepoints are set with the trace command (abbreviated tr). The debugger convenience variable '$tpnum' records the tracepoint number of the most recently set tracepoint; see Convenience Variables for a discussion of what you can do with convenience variables.
The location of a tracepoint can be specified in exactly the same ways as the location of a breakpoint, here reproduced for convenience.
NOTE: this command has no effect on a running trace experiment.
NOTE: these commands have no effect on a running trace experiment.
Any number of tracepoints may have passcounts of any integer value. When the first tracepoint reaches its passcount, the trace experiment will terminate.
To clear a passcount, set it to zero.
NOTE: this command has no effect on a running trace experiment.
The actions at a tracepoint may also include single-stepping for some number of machine instructions, collecting data at each step. In this mode you can collect the changes in registers or variables over a range of instructions.
Here are the commands for defining the actions for a tracepoint.
end
Specify a list of actions for tracepoint number tpnum. The actions themselves appear on the following lines. Type a line containing just end to terminate the actions. To remove all actions from a tracepoint, type actions [tpnum] and follow it immediately with end; that is, give no actions. With no tpnum argument, actions refers to the last tracepoint set.
while-stepping <n> collect [exprs] end
After collecting any data requested at the tracepoint itself, execute <n> machine instructions, collecting the following expressions after each instruction. Each step will generate a separate trace event in the trace buffer (see Reviewing trace results).
NOTE: these commands have no effect on a running trace experiment.
You start and stop trace collection by using the tstart and tstop commands.
NOTE: trace data collection may also be stopped automatically if any tracepoint's passcount is reached. It may also stop automatically if the trace buffer becomes full, unles the trace buffer is set to operate in a continuous "circular buffer" mode.
Note also that starting trace collection has the side effect of discarding any old trace data in the buffer, so that the buffer starts out empty each time. You may stop a trace experiment and still look at your saved data, but as soon as you start a new trace experiment, the previously collected data will be irretrievably gone.
Each trace frame has a sequential identifying number starting from zero, plus the address (PC) of the event. In addition, the trace frame contains any values collected by the actions for the corresponding tracepoint.
The basic commands for selecting a trace frame and extracting data from it are tfind and tdump. In addition, several built-in GDB variables are provided for identifying the currently selected trace frame.
By using combinations of the above commands and convenience variables, together with GDB's built-in scripting language, there are many options for viewing the collected trace data. The most straightforward (but not necessarily the most useful) would be to simply print out everything that was collected.
(gdb) tfind start (gdb) while ($trace_frame != -1) > tdump > tfind > endThere are many ways to filter and format the data. As a simple example, you might want to extract only the data for certain tracepoints.
(gdb) tfind start (gdb) while ($trace_frame != -1) > if ($tracepoint == 2 || $tracepoint == 7) > tdump > end > tfind > end
Whenever a trace frame is selected, all of GDB's existing commands for examining machine state and data will get their values from the trace frame. Therefore you can use commands such as GDB's printf command to format the collected trace data any way you like (so long as you only attempt to print data that you've collected!). For example, you might want to produce a verbose listing of certain values from each of several tracepoints.
(gdb) tfind start (gdb) while ($trace_frame != -1) > printf "Tracepoint %d, ", $tracepoint > printf "line %d, ", $trace_line > if ($tracepoint == 1) > printf "x == %d\n", x > end > if ($tracepoint == 2) > printf "gradient == %d, ", gradient > printf "register D0 == 0x%x\n", $d0 > end > if ($tracepoint == 3) > printf "z / q == %f\n", z / q > end > tfind > endHowever, many people find that the most useful way to review the collected data is interactively. Whenever you use the tfind command, GDB displays the source line corresponding to the selected trace frame, just as if you had stepped to that line or hit a breakpoint there. The next tfind command takes you to the next source line, very much like stepping. At each trace frame, you can use all of the customary GDB commands to examine the data collected, very much as you could if you were sitting at a breakpoint, except that you can only examine values that you collected earlier by listing them in the action lists of your tracepoints.
In fact, if you plan ahead and collect a chunk of memory from the machine stack, you can even use GDB's backtrace or up and down commands to find out what functions called the function that you're tracing.
(gdb) trace foo (gdb) actions > collect $fp, *(long **) $sp @ 64 > end