Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

A Debug/Trace Tool


Dr. Dobb's Journal February 1997: A Debug/Trace Tool

Rainer, who studied at the University of Stuttgart, Germany, is a postdoctoral fellow at the International Computer Science Institute and can be contacted at storn@ icsi.berkeley.edu or [email protected].


When it comes to debugging, programmers usually fall into one of two camps: those who regard printf() as old-fashioned and believe that modern source-level debuggers do everything necessary, and those who claim that source-level debuggers often don't. For instance, the former argue that nothing can replace features such as stepping forward and backward and the option of looking at arbitrary variables at any time. The latter, however, claim that source-level debuggers often don't let you set the granularity or skip breakpoints for a certain amount of times, that few offer CRC capabilities, that regression tests require code instrumentation anyway, and that they are tired of setting each breakpoint anew once they've changed and recompiled their sources.

The debug/trace tool I present in this article provides help for the second camp, and maybe even for the first one -- if you are willing to exploit the advantages of both strategies. The tool is also appropriate when you don't have a powerful debugger available, or when you need trace functionality built into your code to support error-spotting at the client site.

This tool has its roots in the debugger developed by Robert Ward in Debugging C (Que, 1986). However, I have changed and extended it significantly. Although my tool uses Borland C++ 3.1 and runs on a 486 PC, its parts are independent of the 80x86 family's memory organization and have run on a SPARC 5 under SunOS 4.1.3. The complete source code, sample data, and executables are provided electronically; see "Availability," page 3.

The Debug/Trace Tool

The dbg() function is central to the functionality of the debug/trace tool I present here. For practical purposes, dbg() constitutes an extended printf(); it is declared as in Example 1(a) and can be used as in Example 1(b). The first variable in dbg(), arbitrarily chosen to be 7_stp, is a label and will be used by the tool like a breakpoint address or a trace identifier. The second dbg() variable, arbitrarily chosen to be 2, is a trace level and will be used to enable or disable the printing part of a dbg() statement, depending on the trace level activated by the tool. If you choose the trace level to be 2, then all dbg() statements that exhibit the levels 0, 1, or 2 will perform the appropriate printout, which is defined by the subsequent parameters. These parameters follow the syntax used in a regular printf() statement.

The Command Interpreter

Once you've started to run your program, the tool will start to display DBG>> in order to give you access to the tool's command interpreter. The commands that you have access to are grouped into basic and advanced commands. In many cases, the basic commands suffice to spot the error you are looking for. For more intricate bugs, however, you can resort to the more advanced commands.

Basic Commands

From a programmer's point of view, the basic commands allow for an enhanced printf facility with the option of switching the printf function on and off as well as stopping the execution at predefined stop points (breakpoints). A major asset is the ability to store and load the breakpoint configuration.

The question mark (?) activates the help comment, which will display all commands of the interpreter along with brief descriptions of their use.

The command STOP n pattern defines stop pattern n, where n=1, 2,...,10, which indicates a breakpoint. If the tool detects a dbg() statement with this pattern in the label part of dbg(), it will stop execution and prompt DBG>>. For Example 1(b), an appropriate command to reach the stop point would be STOP 1 7_stp1. Note that the parameters after STOP must be separated with blanks and that the parameter pattern does not store spaces; the label "7_stp1 " (note the trailing space) will not trigger the dbg() function, as it cannot be represented properly by pattern. Always finish a stop label in dbg() with a nonwhitespace character.

For pattern, you can also use the wildcards "?" and "*". For example, the pattern 7_s* will match all labels in your program starting with 7_s. The pattern 7_stp?? will match any label starting with 7_stp and having two additional arbitrary characters. Again, only the stop numbers 1 through 10 are available. However, this can be extended easily in the source code if you need more than ten stop points.

The GO command resumes execution and returns control to the program being tested. The program will run until the next stop point is encountered.

The command TRACE n pattern defines the trace pattern, where n=1, 2,...,10. If the tool detects a dbg() statement with this pattern, it will execute the dbg() statement; that is, it will print the desired message if the trace level allows it to. For Example 1(b), an appropriate command to achieve printout would be TRACE 1 7_stp1. The treatment of the parameters is the same as described for STOP. As with the stop command, only the trace numbers 1 through 10 are available. However, this can be extended if you need more than ten trace points. As a default, all trace labels are enabled, and the debugger acts as if TRACE 1 * were used at initialization.

The command LEVEL n specifies trace level n and suppresses all enabled trace points with a level parameter greater than n.

The STAT command displays the status of the debug/trace tool. The status comprises the activated stop and trace points, the trace level, and some advanced command parameters.

SAVE filename saves the status of the debug/trace tool in the file specified by filename. If just the command SAVE is used, STAT.DAT will be used as the default filename. The save feature is helpful if multiple runs of a program with the same trace, stops points, and the like are required, especially if the program code has been changed between runs. You can use this command not only for debugging, but also for regression testing, provided that your dbg() statements remain in the code. SAVE is used together with LOAD.

The LOAD filename command loads the status of the debug/trace tool from the file specified by filename. If just the command LOAD is used, STAT.DAT will be used as the default filename.

Finally, the command QUIT quits the debug/trace tool and finishes the program.

Advanced Commands

Advanced commands extend the basic commands. For instance, you can skip the stop and trace points, store the trace output, and gain insight into the physical memory by using a dump and checksum facility.

The GRAN n command sets the trace granularity to n so that only every nth dbg() call, the level variable of which is smaller or equal to the current trace level, will be printed. The argument n must always be greater than zero. GRAN n can be helpful when you know that a bug involves a certain variable. The variable ends up with a wrong value, but you are uncertain where or when the variable acquired that value. In such a case, you may decide to examine every thousandth reference to the suspect variable. Then, within the 1000 references that surround the error, you examine every hundredth reference. Finally, within the 100 references that surround the error, every reference is examined. This is referred to as an "adjustment in granularity."

The command SSKIP n is the stop-point skip command to skip n stop points. For example, by entering SSKIP 3, you tell the debug/trace tool to skip the next three activated stop points. The SSKIP command is particularly useful when you don't want to cancel the stop points (because you want to use them all again); just skip some in the present run to concentrate on a specific part of your program. A good example of this involves loops, where you just want to examine their beginning and end.

For instance, once you've examined the first output for u=0 and v=0 in Example 2, you might want to skip the next 34 stop points by entering SSKIP 34 and resume with the last one. As an aside, once you've activated tracing on label 1_init1, the output portion of the dbg() statement will always be executed, which means that 34 outputs are running over your screen. If you don't like this, you should first switch off tracing by entering LEVEL 1 before you type GO, thus changing the trace level. Alternatively, you can use the TSKIP n command, which is analogous to SSKIP n but has influence on the activated trace points.

The command TRCMD c filename specifies the trace mode, which selects either the console, the file specified by filename, or both as the output device. If no filename is specified, TRACE.DAT is the default. The options for c are: c, tracing to console only; f, tracing to file only; and a, tracing to console as well as file. The default setting for c is "console only." This is used every time the debugger is started and cannot be influenced by the LOAD command.

WATCH n addr defines a memory address that will be watched and assigns this address the identification number n (up to now, only 1, 2, and 3 have been implemented). Watch addresses are examined automatically at certain dbg() calls according to the watch mode selected. Whenever an examination reveals that the contents of a watch address have changed, a stop point is forced. The syntax of addr depends on the memory model used for 80x86 processors. For the tiny and small model, addr is a four-digit hexadecimal number. For the medium, compact, large, and huge models, addr consists of two four-digit hexadecimal numbers separated by a colon.

The command WATMD c specifies which dbg() calls trigger automatic watch examination. The options, specified by a single character, are: o, all watches off (the default); s, examine at stop points only; t, examine at trace points only; and a, examine at all occurrences of dbg().

DUMP len start prints, in hexadecimal and ASCII format, the n-byte block of memory beginning at address start. The syntax of start depends on the memory model used for 80x86 processors (see WATCH n addr). If the parameters len and start are not specified (that is, only DUMP is used), the dump is performed using the previous values for len and start, if any.

The CRCK len start command computes a cyclic redundancy check over the n-byte block of memory, beginning at start. The notational remarks for len and start are the same as for DUMP len start. Checksums are a powerful technique for detecting errant pointers that write on code. The user simply recomputes the checksum at regular intervals during the program's execution. If the checksum at one computation differs from the previous computation, an intervening pointer reference must have modified the code. If the parameters len and start are not specified (that is, only CRCK is used), the checksum is computed using the previous values for len and start, if any.

Additional Advanced Commands

Some commands are available only in basic form because their implementations depend either on the memory model, compiler, or application program. I've implemented these commands using the DOS small memory model. However, since the source code of the debugging facility is available and documented, you can adapt the appropriate functions to fit your environment.

STACK gives a stack dump of all stack locations with return addresses (frame-pointer chain).

The command STCMD c specifies which dbg() calls trigger automatic stack examination. The options, specified by a single character, are: o, stack examination off (the default); s, examine only at stop points; t, examine only at trace points; and a, examine at all occurrences of dbg().

The LOCAL n command prints the local variables and parameters associated with the last n stack frames.

SNAP n invokes one of three user-defined snapshot functions (n=1,2,3). Snapshots usually are defined separately for each program tested and are used to dump global or local variables accessible via a globally known pointer.

Using the Debug/Trace Tool

To use the debug/trace tool, you must include dbg() statements in your code wherever necessary. You must also include the statements in Example 3(a) in the program you want to debug. Next, you must place the function dbg_init() at the beginning of your program to initialize the tool, as in Example 3(b). Finally, you need to link the object file of the tool to the object file of the program under test. You can then run the program to be tested.

When instrumenting your program, it sometimes helps to think of the code as divided into logical sections. In the first section, all labels might start with 1_, in the second, all labels start with 2_, and so on. In each section, the trace level can be used to distinguish the nesting level, starting with level 1. Level 0 should be reserved so that tracing can be switched off if necessary. Listing One is C code that illustrates this approach.

DDJ

Listing One

#include <stdio.h>#include <stdlib.h>
#include <stdarg.h>


</p>
#define INF 1000       /*Infinity must be higher than the largest path  value */
#define N     10       /*Maximum number of nodes in the network */
#define TRUE   1
#define FALSE  0
#define PERM   1       /*Permanently labeled */
#define TEMP   0       /*Temporarily labeled */


</p>
/*--------Function declarations------------------------------------*/


</p>
int dijkstra(int s, int t, int w[][N], int final[], int dist[], int pred[]);
extern void dbg_init(void);
extern void dbg(char *, int, char *, ...);


</p>
/*--------Function definitions-------------------------------------*/
int dijkstra(int s, int t, int w[][N], int final[], int dist[], int pred[])
{
  int u, v, y, recent, newlabel, min, path;


</p>
/*--------Initialization---------------------------*/
dbg_init();
  for (v=0; v<N; v++)
  {
    dist[v]  = INF;   /*Set all distances to infinity */
    final[v] = TEMP;  /*Set all nodes to "temporarily labeled" */
    pred[v]  = -1;    /*Set nodes in shortest path trace to "non existent" */
  }
  dist[s]  = 0;       /*Distance from s to s is 0 */
  final[s] = PERM;    /*Set node s to "permanently labeled" */
  path     = TRUE;    /*Assume there is a path from s to t */
  recent   = s;       /*Most recent node is s */


</p>


</p>
/*--------Run Dijkstra's Algorithm------------------*/
  for(u=0;u<6;u++)

  {
     for(v=0;v<6;v++)
       dbg("1_init1",2,"w[%d][%d] = %d   ",u,v,w[u][v]);
  }
  dbg("1_init2",1,"&u = %p\n",&u);
  while(final[t]==TEMP)   /*while destination node t is temporarily labeled */
  {
     for(v=0; v<N; v++)
     { 
    if ((w[recent][v] < INF) && (final[v]==TEMP)) /*node temporarily labeled */
        {
           newlabel = dist[recent] + w[recent][v];
           if (newlabel < dist[v]) /*new distance, smaller than previous one */
           {
          dist[v] = newlabel;   /*update distance */
          pred[v] = recent;     /*put predecessor in shortest path */
          dbg("2_dist",4,"recent = %d   newlabel = %d \n",recent,newlabel);
           }
        }
     }
     min = INF;           /*reset temporary minimum */
     for(u=0; u<N; u++)   /*Find smallest labeled node */
     {
    dbg("3_u",1,"u=%d\n",u);
        if ((final[u]==TEMP) && (dist[u] < min))  /*node temporarily labeled */
        {
           y   = u;       /*Save node in y */
           min = dist[u]; /*Reduce temporary minimum */
        } 
     }
     if (min < INF)       /*if there is a path */
     {
        final[y] = PERM;
        recent   = y;
     }
     else                 /*if there is no path */
     {
        path     = FALSE;
        final[t] = PERM;  /*induce break of while loop */
     }
  }
  return(path);
}

Back to Article


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.