NAME

dbug - C Program Debugging Package

SYNOPSIS

cc [ flag ... ] file ... -ldbug [ library ... ]

#include <dbug.h>

dbug_errno_t dbug_init_ctx( const char * options, const char *name, dbug_ctx_t* dbug_ctx );

dbug_errno_t dbug_init( const char * options, const char *name );

dbug_errno_t dbug_push( const char * options );

dbug_errno_t dbug_done_ctx( dbug_ctx_t* dbug_ctx );

dbug_errno_t dbug_done( void );

dbug_errno_t dbug_enter_ctx( const dbug_ctx_t dbug_ctx, const char *file, const char *function, const int line, int *dbug_level );

dbug_errno_t dbug_enter( const char *file, const char *function, const int line, int *dbug_level );

dbug_errno_t dbug_leave_ctx( const dbug_ctx_t dbug_ctx, const int line, int *dbug_level );

dbug_errno_t dbug_leave( const int line, int *dbug_level );

dbug_errno_t dbug_print_ctx( const dbug_ctx_t dbug_ctx, const int line, const char *break_point, const char *format, /* fields */ ... );

dbug_errno_t dbug_print( const int line, const char *break_point, const char *format, /* fields */ ... );

dbug_errno_t dbug_print_start_ctx( const dbug_ctx_t dbug_ctx, const int line, const char *break_point );

dbug_errno_t dbug_print_start( const int line, const char *break_point );

dbug_errno_t dbug_print_end( const char *format, /* fields */ ... );

dbug_errno_t dbug_dump_ctx( const dbug_ctx_t dbug_ctx, const int line, const char *break_point, const void *memory, const unsigned int len );

dbug_errno_t dbug_dump( const int line, const char *break_point, const void *memory, const unsigned int len );

void DBUG_INIT_CTX( const char * options, const char *name, dbug_ctx_t* dbug_ctx );

void DBUG_INIT( const char * options, const char *name );

void DBUG_PUSH( const char * options );

void DBUG_DONE_CTX( dbug_ctx_t* dbug_ctx );

void DBUG_DONE( void );

void DBUG_POP( void );

void DBUG_ENTER_CTX( const dbug_ctx_t dbug_ctx, const char *function );

void DBUG_ENTER( const char *function );

void DBUG_LEAVE_CTX( const dbug_ctx_t dbug_ctx );

void DBUG_LEAVE( void );

void DBUG_PRINT_CTX( const dbug_ctx_t dbug_ctx, const char *break_point, ( const char *format, /* fields */ ... ) );

void DBUG_PRINT( const char *break_point, ( const char *format, /* fields */ ... ) );

void DBUG_DUMP_CTX( const dbug_ctx_t dbug_ctx, const char *break_point, const void *memory, const unsigned int len );

void DBUG_DUMP( const char *break_point, const void *memory, const unsigned int len );

DESCRIPTION

This documentation describes dbug, a macro based C debugging package which has proven to be a very flexible and useful tool for debugging, testing, and porting C programs.

All of the features of the dbug package can be enabled or disabled dynamically at execution time. This means that production programs will run normally when debugging is not enabled, and eliminates the need to maintain two separate versions of a program.

Many of the things easily accomplished with conventional debugging tools, such as symbolic debuggers, are difficult or impossible with this package, and vice versa. Thus the dbug package should not be thought of as a replacement or substitute for other debugging tools, but simply as a useful addition to the program development and maintenance environment.

Almost every program development environment worthy of the name provides some sort of debugging facility. Usually this takes the form of a program which is capable of controlling execution of other programs and examining the internal state of other executing programs. These types of programs will be referred to as external debuggers since the debugger is not part of the executing program. Examples of this type of debugger include the adb and sdb debuggers provided with the UNIX operating system.

One of the problems associated with developing programs in an environment with good external debuggers is that developed programs tend to have little or no internal instrumentation. This is usually not a problem for the developer since he is, or at least should be, intimately familiar with the internal organization, data structures, and control flow of the program being debugged. It is a serious problem for maintenance programmers, who are unlikely to have such familiarity with the program being maintained, modified, or ported to another environment. It is also a problem, even for the developer, when the program is moved to an environment with a primitive or unfamiliar debugger, or even no debugger.

On the other hand, dbug is an example of an internal debugger. Because it requires internal instrumentation of a program, and its usage does not depend on any special capabilities of the execution environment, it is always available and will execute in any environment that the program itself will execute in. In addition, since it is a complete package with a specific user interface, all programs which use it will be provided with similar debugging capabilities.

This is in sharp contrast to other forms of internal instrumentation where each developer has their own, usually less capable, form of internal debugger. In summary, because dbug is an internal debugger it provides consistency across operating environments, and because it is available to all developers it provides consistency across all programs in the same environment.

The dbug package imposes only a slight speed penalty on executing programs, typically much less than 10 percent, and a modest size penalty, typically 10 to 20 percent. By defining a specific C preprocessor symbol both of these can be reduced to zero with no changes required to the source code.

The following list is a quick summary of the capabilities of the dbug package. Each capability can be individually enabled or disabled at the time a program is invoked by specifying the appropriate command line arguments.

Debugging using print statements

Internal instrumentation is already a familiar concept to most programmers, since it is usually the first debugging technique learned. Typically, "print statements" are inserted in the source code at interesting points, the code is recompiled and executed, and the resulting output is examined in an attempt to determine where the problem is.

The procedure is iterative, with each iteration yielding more and more output, and hopefully the source of the problem is discovered before the output becomes too large to deal with or previously inserted statements need to be removed. Figure 1 is an example of this type of primitive debugging technique.

  #include <stdio.h>

  int main (int argc, char **argv)
  {
      printf ("argv[0] = %d\n", argv[0]);
      /*
       *  Rest of program
       */
      printf ("== done ==\n");
  }

  Figure 1
  Primitive Debugging Technique

Eventually, and usually after at least several iterations, the problem will be found and corrected. At this point, the newly inserted print statements must be dealt with. One obvious solution is to simply delete them all.

Debug disable techniques

Beginners usually do this a few times until they have to repeat the entire process every time a new bug pops up. The second most obvious solution is to somehow disable the output, either through the source code comment facility, creation of a debug variable to be switched on or off, or by using the C preprocessor. Figure 2 is an example of all three techniques.

  #include <stdio.h>

  int debug = 0;

  int main (int argc, char **argv)
  {
      /* printf ("argv = %x\n", argv) */
      if (debug) printf ("argv[0] = %d\n", argv[0]);
      /*
       *  Rest of program
       */
  #ifdef DEBUG
      printf ("== done ==\n");
  #endif
  }
      
  Figure 2
  Debug Disable Techniques

Each technique has its advantages and disadvantages with respect to dynamic vs static activation, source code overhead, recompilation requirements, ease of use, program readability, etc. Overuse of the preprocessor solution quickly leads to problems with source code readability and maintainability when multiple #ifdef symbols are to be defined or undefined based on specific types of debug desired.

The source code can be made slightly more readable by suitable indentation of the #ifdef arguments to match the indentation of the code, but not all C preprocessors allow this.

More readable preprocessor usage

The only requirement for the standard UNIX C preprocessor is for the '#' character to appear in the first column, but even this seems like an arbitrary and unreasonable restriction. Figure 3 is an example of this usage.

  #include <stdio.h>

  int main (int argc, char **argv)
  {
  #   ifdef DEBUG
      printf ("argv[0] = %d\n", argv[0]);
  #   endif
      /*
       *  Rest of program
       */
  #   ifdef DEBUG
      printf ("== done ==\n");
  #   endif
  }

  Figure 3
  More Readable Preprocessor Usage

OPTIONS

Debug macro's

This section summarizes the usage of all currently defined macros in the dbug package. These macro's can be used in C programs. The macros definitions are found in the user include file dbug.h.

DBUG_OFF

This macro disables compilation of all other dbug macro's. This will result in null macros expansions so that the resulting code will be smaller and faster. (The difference may be smaller than you think so this step is recommended only when absolutely necessary). In general, tradeoffs between space and efficiency are decided in favor of efficiency since space is seldom a problem on the new machines). By default this macro is disabled.

EX: #define DBUG_OFF

DBUG_INIT and DBUG_INIT_CTX

Initializes a debugger context using the debug control string passed as the macro argument. The name supplied may be NULL. In that case a name for the debugging context is generated. DBUG_INIT uses an internal debugging context. In a multi-threaded environment (POSIX threads), thread specific data is used for DBUG_INIT.

EX: DBUG_INIT ("Odbug.log,d,t", NULL);

EX: dbug_ctx_t dbug_ctx; DBUG_INIT_CTX ("d,t", NULL, &dbug_ctx);

EX: DBUG_INIT ("", "Process DBUG");

DBUG_PUSH

This macro converts the options parameter from the old format to the new format and then calls DBUG_INIT.

EX: DBUG_PUSH ("O,dbug.log:d:t");

DBUG_DONE and DBUG_DONE_CTX

Destroys the debugging context as initialized by DBUG_INIT. The DBUG_DONE macro has no arguments. The DBUG_DONE_CTX has the debugging context as parameter.

EX: DBUG_DONE ();

EX: dbug_ctx_t dbug_ctx; DBUG_DONE_CTX (&dbug_ctx);

DBUG_POP

Is the same as DBUG_DONE.

DBUG_ENTER and DBUG_ENTER_CTX

Used to tell the run-time support module the name of the function being entered. The DBUG_ENTER must be supplied the name of the function entered. The DBUG_INIT_CTX needs a debugging context. The DBUG_ENTER macro must precede all executable lines in the function just entered, and must come after all local declarations. Each DBUG_ENTER macro must have a matching DBUG_LEAVE macro at the function exit points. DBUG_ENTER macros used without a matching DBUG_LEAVE macro will cause warning messages from the dbug package run-time support module.

Before a DBUG_ENTER call can be executed, the (internal) debugging context must have been initialized via DBUG_INIT.

EX: DBUG_ENTER ("main");

EX: dbug_ctx_t dbug_ctx; DBUG_ENTER_CTX (dbug_ctx, "main");

DBUG_LEAVE and DBUG_LEAVE_CTX

Used at each exit point of a function containing a DBUG_ENTER macro at the entry point. It is an error to have a DBUG_LEAVE macro in a function which has no matching DBUG_ENTER macro, and the compiler will complain if the macros are actually used (expanded).

EX: DBUG_LEAVE();

EX: dbug_ctx_t dbug_ctx; DBUG_LEAVE_CTX(dbug_ctx);

DBUG_PRINT and DBUG_PRINT_CTX

Used to do printing via the "fprintf" library function on the current debug stream. The input arguments are a break point, a format string and the corresponding argument list. Note that the format and following parameters are all one macro argument and must be enclosed in parenthesis. The DBUG_PRINT_CTX has one extra input parameter: the debugging context. When the DBUG_PRINT statement is called within a pair of DBUG_ENTER/DBUG_LEAVE statements, the function context is also printed, otherwise that is empty.

EX: DBUG_PRINT ("type", ("type is %x", type));

EX: dbug_ctx_t dbug_ctx; DBUG_PRINT_CTX (dbug_ctx, "stp", ("%x -> %s", stp, stp -> name));

DBUG_DUMP and DBUG_DUMP_CTX

Used to do dump a piece of memory via the "fprintf" library function on the current debug stream. The input arguments are a break point, a memory address and the number of bytes to print. The DBUG_DUMP_CTX has one extra input parameter: the debugging context. Before a DBUG_DUMP statement can be executed a DBUG_ENTER statement must have been executed, i.e. DBUG_DUMP needs a function context.

EX: char buf[] = "abcde"; DBUG_DUMP("type", buf, strlen(buf) );

DBUG_EXECUTE(break_point, a1)

This macro is preserved for backwards compatibility. It does nothing.

DBUG_PROCESS(a1)

This macro has been used to supply the process name for the debugging context. Use DBUG_INIT to specify the name.

DBUG_FILE

Same as stderr. This macro is preserved for backwards compatibility.

DBUG_RETURN(a1)

Performs a DBUG_LEAVE and returns parameter a1. This macro is preserved for backwards compatibility.

DBUG_VOID_RETURN

Performs a DBUG_LEAVE and returns. This macro is preserved for backwards compatibility.

Debug functions

This section summarizes the usage of all currently defined functions in the dbug package. The main purpose of these functions is to invoke them from external programs written in another language, such as Perl. The functions are found in the user include file dbug.h.

dbug_init_ctx and dbug_init

See DBUG_INIT.

dbug_push

See DBUG_PUSH.

dbug_done_ctx and dbug_done

See DBUG_DONE.

dbug_enter_ctx and dbug_enter

Used to tell the run-time support module the name of the function being entered, the name of the file, the line number and a nesting level. The nesting level is also used for determining how much stack space is used. See also DBUG_ENTER.

EX: dbug_enter( __FILE__, "main", __LINE__, &dbug_level );

EX: dbug_enter_ctx( dbug_ctx, __FILE__, "main", __LINE__, &dbug_level );

dbug_leave_ctx and dbug_leave

Used at each exit point of a function containing a dbug_enter or dbug_enter_ctx function at the entry point. Input parameters are the line number and the nesting level set at the start of the function must also be supplied to enable checking balanced dbug_enter/dbug_leave calls. See also DBUG_LEAVE.

EX: dbug_leave( __LINE__, &dbug_level );

EX: dbug_leave_ctx( dbug_ctx, __LINE__, &dbug_level );

dbug_print_ctx and dbug_print

Print a formatted string with variable arguments. See also DBUG_PRINT.

EX: dbug_print( 1, "info", "This is %s and that is %s", "this", "that" );

EX: dbug_print_ctx( dbug_ctx, 1, "info", "This is %s and that is %s", "this", "that" );

dbug_print_start_ctx and dbug_print_start

Used for saving print info which is used in dbug_print_end. Is used in DBUG_PRINT. Should not be used directly.

dbug_print_end

Print a formatted string with variable arguments. A call to dbug_print_start (or dbug_print_start_ctx) must have preceded a call to dbug_print_end. See also DBUG_PRINT. Should not be used directly.

dbug_dump and dbug_dump_ctx

See DBUG_DUMP.

Debug arguments

break_point

The break point for a line of output. Can be used by the reporting tool to display only lines which have this break point.

dbug_ctx

A debugging context. Must be treated as an opaque type. See also dbug_ctx_t.

dbug_level

An optional check for matching dbug_enter() and dbug_leave() (and their variants) calls.

fields

A variable argument list.

file

The source file where the call is performed. The __FILE__ preprocessor constant is recommended.

format

A printf format.

function

The name of the function which is entered.

len

The number of bytes to dump for a piece of memory.

line

The source line where the call is performed. The __LINE__ preprocessor constant is recommended.

memory

The memory to dump.

name

The name of the debug thread. It may be NULL which means the DBUG library will generate a name.

options

The debug control string which will be described later in detail. If the options string is NULL the first ever options string will be used. Thus it is possible to supply the options string only once and use this string for other threads which may not have access to the first options string (for instance a command line parameter).

Type definitions

dbug_ctx_t
      typedef void * dbug_ctx_t;
dbug_errno_t
      typedef int dbug_errno_t;

Debug control string

The debug control string is used to set the state of the debugger via the DBUG_INIT macro. This section summarizes the currently available debugger options and the flag characters which enable or disable them. Arguments are enclosed in '<' and '>'. Arguments which are also enclosed in '[' and ']' are optional. The options in the debug control string are separated by the comma (,) or semi-colon (;) and may not have spaces. The argument of an option follows the option and is separated by an equal sign (=).

d

Enable output from DBUG_PRINT macros.

D=<time>

Delay for specified time after each output line, to let output drain. Time is given in milliseconds (a value of 1000 is one second). Default is zero. Either tracing or debugging (options t or d) must be enabled.

g

Turn on machine independent profiling. Timing results are collected and sent to output.

o[=<file>]

Redirect the debugger output stream to the specified file. A null argument causes output to be redirected to stderr.

O=<file>

Append the debugger output stream to the specified file.

t

Enable function control flow tracing., i.e. print enter and leave info for functions.

Debugging output

Any program where debugging, tracing or profiling is enabled, will send debugging output in a standard format to the output destination (which may have been specified in the dbug control string).

This output can be formatted by the Perl script dbugrpt. There are several formatting options available for this program.

This is the output of the DBUG library.

Debug reporting tool

After a program has sent its output to a file (or whatever), the debug report tool can be used to create a user friendly output in various ways. The options are supplied to dbugrpt in a standard (Unix) way. This program uses standard input to create the report. It is possible to store several runs of a program in an output file and input this file to the debug reporting tool.

Debug report options

This section summarizes the currently available report options and the flag characters which enable or disable them.

d[<break points>]

Enable output from DBUG_PRINT macros which have a break point which is in the list of specified break points (separated by comma's). A null list means all break points are enabled.

D

Emulate a done() call (if missing) at the end of each debugging context in order to print totals.

F

Mark each debugger output line with the name of the source file containing the macro causing the output.

h

Print help.

i

Identify the process emitting each line of debug or trace output with the process id for that process.

L

Mark each debugger output line with the source file line number of the macro causing the output.

n

Mark each debugger output line with the current function nesting depth.

N

Sequentially number each debugger output line starting at 1. This is useful for reference purposes when debugger output is interspersed with program output.

P

Mark each debugger output line with the name of the debugging context as supplied to DBUG_INIT.

r[<resolution>]

The time resolution. A value of 1000 (default) means that time is printed in milliseconds.

t[<functions>]

Limit debugger and profiling actions to the specified list of comma separated functions. A null list of functions implies that all functions are selected.

T

Print date/time of each output line.

v

Print the debug reporting tool version.

EXAMPLES

We will start off learning about the capabilities of the dbug package by using a simple minded program which computes the factorial of a number. In order to better demonstrate the function trace mechanism, this program is implemented recursively.

Main function for factorial program

  #include <stdio.h>
  #include <stdlib.h>
  #include "dbug.h"

  extern int factorial (int value);

  int main (int argc, char *argv[])
  {
    int result, ix;
    char *options = "";

    for (ix = 1; ix < argc && argv[ix][0] == '-'; ix++) 
      {
        switch (argv[ix][1]) 
    {
    case '#':
      options = &(argv[ix][2]);
      break;
    }
      }

    DBUG_INIT( options, "factorial" );
    {
      DBUG_ENTER("main");
      for (; ix < argc; ix++) 
        {
    DBUG_PRINT("args", ("argv[%d] = %s", ix, argv[ix]));
    result = factorial (atoi (argv[ix]));
    printf ("%d\n", result);
    fflush( stdout );
        }
      DBUG_LEAVE();
    }
    DBUG_DONE();

    return (0);
  }

  Figure 4
  Factorial Program Mainline

The main function is responsible for processing any command line option arguments and then computing and printing the factorial of each non-option argument.

First of all, notice that all of the debugger functions are implemented via preprocessor macros. This does not detract from the readability of the code and makes disabling all debug compilation trivial (a single preprocessor symbol, DBUG_OFF, forces the macro expansions to be null).

Also notice the inclusion of the header file dbug.h from the local header file directory. (The version included here is the test version in the dbug source distribution directory). This file contains all the definitions for the debugger macros, which all have the form DBUG_XX...XX.

The DBUG_INIT macro sets up a debugging context based on the control string passed as its argument. The DBUG_ENTER macro informs that debugger that we have entered the function named main. It must be the very first "executable" line in a function, after all declarations and before any other executable line. The DBUG_PRINT macro is used to print the values of each argument for which a factorial is to be computed. The DBUG_LEAVE macro tells the debugger that the end of the current function has been reached and returns a value to the calling function. All of these macros will be fully explained in subsequent sections.

To use the debugger, the factorial program is invoked with a command line of the form:

  factorial -#d,t 1 2 3 | perl -S dbugrpt

The main function recognizes the "-#d,t" string as a debugger control string, and passes the debugger arguments ("d,t") to the dbug run-time support routines via the DBUG_INIT macro. This particular string enables output from the DBUG_PRINT macro with the 'd' flag and enables function tracing with the 't' flag. The factorial function is then called three times, with the arguments "1", "2", and "3".

Note that the DBUG_PRINT takes exactly one arguments enclosed in parentheses.

Debug control strings consist of a header, the "-#", followed by a comma (or semi-colon) separated list of debugger options.

Each debugger argument is a single character flag followed by an optional equal sign and an argument specific to the given flag.

Some examples are:

  -#d,t,o
  -#d,o=dbug.log

The definition of the factorial function, symbolized as "N!", is given by:

  N! = N * N-1 * ... 2 * 1

Factorial function

Figure 5 is the factorial function which implements this algorithm recursively. Note that this is not necessarily the best way to do factorials and error conditions are ignored completely.

  #include <stdio.h>
  #include "dbug.h"

  int factorial(int value)
  {
    DBUG_ENTER("factorial");
    DBUG_PRINT("find", ("find %d factorial", value));
    if (value > 1) {
      value *= factorial (value - 1);
    }
    DBUG_PRINT("result", ("result is %d", value));
    DBUG_LEAVE();
    return (value);
  }

  Figure 5
  Factorial Function

One advantage (some may not consider it so) to using the dbug package is that it strongly encourages fully structured coding with only one entry and one exit point in each function. Multiple exit points, such as early returns to escape a loop, may be used, but each such point requires the use of an appropriate DBUG_LEAVE macro.

To build the factorial program on a UNIX system, compile and link with the command:

  cc -o factorial main.c factorial.c -ldbug

The "-ldbug" argument tells the loader to link in the run-time support modules for the dbug package.

No debugging and/or tracing

Executing the factorial program with a command of the form:

  factorial 1 2 3 4 5

generates the output shown in figure 6.

  1
  2
  6
  24
  120
  
  Figure 6
  factorial 1 2 3 4 5

Function level tracing

Function level tracing is enabled by passing the debugger the 't' flag in the debug control string. Figure 7 is the output resulting from the command "factorial -#t 3 2 | perl -S dbugrpt".

  >main
  |   >factorial
  |   |   >factorial
  |   |   |   >factorial
  |   |   |   <factorial
  |   |   <factorial
  |   <factorial
  6
  |   >factorial
  |   |   >factorial
  |   |   <factorial
  |   <factorial
  2
  <main
  
  Figure 7
  factorial -#t 3 2 | perl -S dbugrpt

Each entry to or return from a function is indicated by '>' for the entry point and '<' for the exit point, connected by vertical bars to allow matching points to be easily found when separated by large distances.

This trace output indicates that there was an initial call to factorial from main (to compute 2!), followed by a single recursive call to factorial to compute 1!. The main program then output the result for 2! and called the factorial function again with the second argument, 3. Factorial called itself recursively to compute 2! and 1!, then returned control to main, which output the value for 3! and exited.

The mechanism used to produce "printf" style output is the DBUG_PRINT macro.

To allow selection of output from specific macros, one of the arguments to every DBUG_PRINT macro is a dbug break point.

When this break point appears in the argument list of the 'd' flag in a debug report option, as in "-d <break point1>,<break point2>,...", output from the corresponding macro is enabled. The default when there is no 'd' flag in the control string is to enable output from all DBUG_PRINT macros.

Typically, a program will be run once, with no break points specified, to determine what break points are significant for the current problem (the break points are printed in the macro output line). Then the program will be run again, with the desired break points, to examine only specific areas of interest.

Another argument to a DBUG_PRINT macro is a standard printf style format string and one or more arguments to print, all enclosed in parenthesis so that they collectively become a single macro argument. This is how variable numbers of printf arguments are supported. Also note that no explicit newline is required at the end of the format string. As a matter of style, two or three small DBUG_PRINT macros are preferable to a single macro with a huge format string.

Function level tracing and debugging

Figure 8 shows the output for default tracing and debug.

  >main
  |   args: argv[2] = 3
  |   >factorial
  |   |   find: find 3 factorial
  |   |   >factorial
  |   |   |   find: find 2 factorial
  |   |   |   >factorial
  |   |   |   |   find: find 1 factorial
  |   |   |   |   result: result is 1
  |   |   |   <factorial
  |   |   |   result: result is 2
  |   |   <factorial
  |   |   result: result is 6
  |   <factorial
  6
  <main
  
  Figure 8
  factorial -#d,t 3 | perl -S dbugrpt

The output from the DBUG_PRINT macro is indented to match the trace output for the function in which the macro occurs. When debugging is enabled, but not trace, the output starts at the left margin, without indentation.

Debugging a break point

To demonstrate selection of specific macros for output, figure 9 shows the result when the report program is invoked with the debug report option "-d result".

  factorial: result: result is 1
  factorial: result: result is 2
  factorial: result: result is 6
  factorial: result: result is 24
  24
  
  Figure 9
  factorial -#d 4 | perl -S dbugrpt -d result

Debugging a function

It is sometimes desirable to restrict debugging and trace actions to a specific function or list of functions.

This is accomplished with the 'f' debug report option. Figure 10 is the output of the factorial program when run with the debug control string "-#d" and the report options "-t factorial -FL". The 'F' flag enables printing of the source file name and the 'L' flag enables printing of the source file line number.

  factorial.c: 7: factorial: find: find 3 factorial
  factorial.c: 7: factorial: find: find 2 factorial
  factorial.c: 7: factorial: find: find 1 factorial
  factorial.c: 11: factorial: result: result is 1
  factorial.c: 11: factorial: result: result is 2
  factorial.c: 11: factorial: result: result is 6
  6
  
  Figure 10
  factorial -#d 3 | perl -S dbugrpt -t factorial -FL

The output in figure 10 shows that the "find" macro is in file "factorial.c" at source line 7 and the "result" macro is in the same file at source line 11.

Profiling a program

It is sometimes desirable to profile your program. The option 'g' provides support for profiling. The debug reporting tool is able to print a profile report for each debugging context encountered as well as a total. Besides displaying timing results, also stack usage is printed. The stack usage is only determined for functions which have a dbug_enter/dbug_leave pair. Figure 11 is the output of the factorial program for determining the factorial of 3 when run with the debug control string "-#t,D=1000,g".

  >main
  |   >factorial
  |   |   >factorial
  |   |   |   >factorial
  |   |   |   <factorial
  |   |   <factorial
  |   <factorial
  6
  <main

  -------------------------------------------------------------------------------
  Profile of execution for debug context 'factorial'
  Stack usage: 40 bytes
  Execution times are in seconds

         Calls           Time
         -----           ----
  function                 # calls % calls        time % time   time/call  weight
  ======================== ======= ======= =========== ====== =========== =======
  factorial                      3   75.00       5.000  71.74       1.667    5380
  main                           1   25.00       1.970  28.26       1.970     706
         ======= ======= =========== ======                    
  Totals                         4  100.00       6.970 100.00
  -------------------------------------------------------------------------------


  -------------------------------------------------------------------------------
  Profile of execution for debug context 'total'
  Stack usage: 0 bytes
  Execution times are in seconds

         Calls           Time
         -----           ----
  function                 # calls % calls        time % time   time/call  weight
  ======================== ======= ======= =========== ====== =========== =======
  factorial                      3   75.00       5.000  71.74       1.667    5380
  main                           1   25.00       1.970  28.26       1.970     706
         ======= ======= =========== ======                    
  Totals                         4  100.00       6.970 100.00
  -------------------------------------------------------------------------------

  Figure 11
  factorial -#t,D=1000,g 3 | perl -S dbugrpt

As you can see, it's quite self-evident. The 'weight' column is a metric obtained by multiplying the percentage of the calls and the percentage of the time. Functions with higher 'weight' benefit the most from being sped up.

NOTES

Tips

One of the most useful capabilities of the dbug package is to compare the executions of a given program in two different environments. This is typically done by executing the program in the environment where it behaves properly and saving the debugger output in a reference file. The program is then run with identical inputs in the environment where it misbehaves and the output is again captured in a reference file. The two reference files can then be differentially compared to determine exactly where execution of the two processes diverges.

A related usage is regression testing where the execution of a current version is compared against executions of previous versions. This is most useful when there are only minor changes.

Compatibility

The original DBUG library written by Fred Fish differs from the current library. I tried to keep it code compatible, but I have made some minor changes. Keep in mind that the original library has been written in the 80's.

These are the differences:

ANSI C

I have converted the source to ANSI C since this is the standard now.

POSIX threads

I have modified the library to make use of POSIX threads. All macro's (and functions) have now reentrant functions with the extension _ctx. The standard functions (without _ctx) are also thread-safe because they use thread-specific data (pthread_getspecific).

Delay

The delay argument in the debug control string is now specified in milliseconds, instead of tenths of seconds.

Debug control string

The old format for the debug control string used options separated by colons and modifiers separated by comma's. Since the colon might be part of a (DOS) file name, I had to change the option separator into the comma. The modifier separator has become the equal sign. So -#d:D,2 has become -#d,D=200. I also separated content and formatting of the debugging output, hence several options are now obsolete in the debug control string and have been moved to the options of the debug reporting tool.

Debug reporting tool

There is now a Perl debug reporting tool which formats the debugging output in a user friendly way.

DEBUG_LEAVE

Needs an empty parameter list now.

DBUG_2

This macro is obsolete now. Use DBUG_PRINT instead.

DBUG_3

This macro is obsolete now. Use DBUG_PRINT instead.

DBUG_4

This macro is obsolete now. Use DBUG_PRINT instead.

DBUG_5

This macro is obsolete now. Use DBUG_PRINT instead.

DBUG_SETJMP

This macro is obsolete now. I think it is better not to use setjmp and longjmp in programs.

DBUG_LONGJMP

This macro is obsolete now. I think it is better not to use setjmp and longjmp in programs.

CAVEATS

he dbug package works best with programs which have "line oriented" output, such as text processors, general purpose utilities, etc. It can be interfaced with screen oriented programs such as visual editors by redefining the appropriate macros to call special functions for displaying the debugger results. Of course, this caveat is not applicable if the debugger output is simply dumped into a file for post-execution examination.

This library uses standard memory allocation routines such as malloc and realloc. Programs using other memory allocation routines might suffer from memory holes.

AUTHOR

Fred Fish - Developer of first version.

Gert-Jan Paulissen - Redesign for enabling multi threaded support.

HISTORY

Originally, this library has been written by Fred Fish in the 80's. In 1999 Gert-Jan Paulissen modified the source to make it portable using ANSI C and an imake configuration. The documentation is now in Perl's pod format.