dbug - C Program Debugging Package
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
);
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.
Execution trace showing function level control flow in a semi-graphically manner using indentation to indicate nesting depth.
Output the values of all, or any subset of, key internal variables.
Limit actions to a specific set of named functions.
Label each output line with source file name and line number.
Label each output line with name of current process.
Use several debugging contexts to support multi-threaded programs.
Redirect the debug output stream to standard output (stdout), standard error (stderr) or a named file. The default output stream is standard output (stdout). The redirection mechanism is completely independent of normal command line redirection to avoid output conflicts.
Enable profiling of the program being debugged as well as stack usage.
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.
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.
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
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.
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
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");
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");
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);
Is the same as DBUG_DONE
.
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");
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);
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));
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) );
This macro is preserved for backwards compatibility. It does nothing.
This macro has been used to supply the process name for the debugging context. Use DBUG_INIT
to specify the name.
Same as stderr. This macro is preserved for backwards compatibility.
Performs a DBUG_LEAVE
and returns parameter a1. This macro is preserved for backwards compatibility.
Performs a DBUG_LEAVE
and returns. This macro is preserved for backwards compatibility.
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.
See DBUG_INIT
.
See DBUG_PUSH
.
See DBUG_DONE
.
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 );
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 );
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" );
Used for saving print info which is used in dbug_print_end
. Is used in DBUG_PRINT
. Should not be used directly.
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.
See DBUG_DUMP
.
The break point for a line of output. Can be used by the reporting tool to display only lines which have this break point.
A debugging context. Must be treated as an opaque type. See also dbug_ctx_t
.
An optional check for matching dbug_enter() and dbug_leave() (and their variants) calls.
A variable argument list.
The source file where the call is performed. The __FILE__ preprocessor constant is recommended.
A printf format.
The name of the function which is entered.
The number of bytes to dump for a piece of memory
.
The source line where the call is performed. The __LINE__ preprocessor constant is recommended.
The memory to dump.
The name of the debug thread. It may be NULL which means the DBUG library will generate a name.
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).
typedef void * dbug_ctx_t;
typedef int dbug_errno_t;
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 (=).
Enable output from DBUG_PRINT macros.
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.
Turn on machine independent profiling. Timing results are collected and sent to output.
Redirect the debugger output stream to the specified file. A null argument causes output to be redirected to stderr.
Append the debugger output stream to the specified file.
Enable function control flow tracing., i.e. print enter and leave info for functions.
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.
Output of DBUG_INIT, DBUG_INIT_CTX, dbug_init and dbug_init_ctx.
A line containing 'DBUG', the UID (unique id) of dbug context, date (in GMT YYYYMMDDhhmmss format), a sequence number, 'I', major version, minor version, teeny version, name, address of dbug context, process id and the flags will be printed. Flags is a bit masked integer where tracing equals 1 (see t
), debugging equals 2 (see d
) and profiling equals 4 (see g
).
This is the line printed:
DBUG#<dbug uid>#<date>#<seq>#I#<major version>.<minor version>.<teeny version>#<name>#<dbug context>#<process id>#<flags>
Output of DBUG_DONE, DBUG_DONE_CTX, dbug_done and dbug_done_ctx.
A line containing 'DBUG', the UID of the dbug context, date (in GMT YYYYMMDDhhmmss format), a sequence number, 'D', the maximum number of functions on the stack and the stack usage will be printed.
This is the line printed:
DBUG#<dbug uid>#<date>#<seq>#D#<maximum number of functions on the stack>#<stack usage>
Output of DBUG_ENTER, DBUG_ENTER_CTX, dbug_enter and dbug_enter_ctx.
A line consisting of 'DBUG', the UID of dbug context, date (in GMT YYYYMMDDhhmmss format), a sequence number, 'E', file, function, line, level and the (wall) time will be printed.
The (wall) time is printed as a double using the "printf" library function format "%015.*f" where the start ('*') is for the variable for the number of digits after the radix character (.). The number of digits depends on the clock library function resolution. The clock library function is one of the library functions "clock_gettime", "gettimeofday", "getrusage", "ftime" or "clock".
If profiling is off, the (wall) time equals -1.
This is the line printed:
DBUG#<dbug uid>#<date>#<seq>#E#<file>#<function>#<line>#<level>#<time>
Output of DBUG_LEAVE, DBUG_LEAVE_CTX, dbug_leave and dbug_leave_ctx.
A line consisting of 'DBUG', the UID of dbug context, date (in GMT YYYYMMDDhhmmss format), a sequence number, 'L', file, function, line, level and (wall) time will be printed.
See for an explanation of (wall) time above.
This is the line printed:
DBUG#<dbug uid>#<date>#<seq>#L#<file>#<function>#<line>#<level>#<time>
Output of DBUG_PRINT, DBUG_PRINT_CTX, dbug_print and dbug_print_ctx.
A line consisting of 'DBUG', the UID of dbug context, date (in GMT YYYYMMDDhhmmss format), a sequence number, 'P', file, function, line, level, break point and user supplied parameters is printed. File and function may be empty when there is no active function context.
DBUG#<dbug uid>#<date>#<seq>#P#<file>#<function>#<line>#<level>#<break point>#<user supplied parameters>
Output of errors.
Errors are printed to stderr in the following format:
DBUG#<dbug context>#ERROR: <function name>: <description>
Status values are defined in <errno.h>.
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.
This section summarizes the currently available report options and the flag characters which enable or disable them.
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.
Emulate a done() call (if missing) at the end of each debugging context in order to print totals.
Mark each debugger output line with the name of the source file containing the macro causing the output.
Print help.
Identify the process emitting each line of debug or trace output with the process id for that process.
Mark each debugger output line with the source file line number of the macro causing the output.
Mark each debugger output line with the current function nesting depth.
Sequentially number each debugger output line starting at 1. This is useful for reference purposes when debugger output is interspersed with program output.
Mark each debugger output line with the name of the debugging context as supplied to DBUG_INIT
.
The time resolution. A value of 1000 (default) means that time is printed in milliseconds.
Limit debugger and profiling actions to the specified list of comma separated functions. A null list of functions implies that all functions are selected.
Print date/time of each output line.
Print the debug reporting tool version.
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.
#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
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.
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 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.
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.
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
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.
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.
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.
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:
I have converted the source to ANSI C since this is the standard now.
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).
The delay argument in the debug control string is now specified in milliseconds, instead of tenths of seconds.
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.
There is now a Perl debug reporting tool which formats the debugging output in a user friendly way.
Needs an empty parameter list now.
This macro is obsolete now. Use DBUG_PRINT instead.
This macro is obsolete now. Use DBUG_PRINT instead.
This macro is obsolete now. Use DBUG_PRINT instead.
This macro is obsolete now. Use DBUG_PRINT instead.
This macro is obsolete now. I think it is better not to use setjmp and longjmp in programs.
This macro is obsolete now. I think it is better not to use setjmp and longjmp in programs.
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.
Fred Fish - Developer of first version.
Gert-Jan Paulissen - Redesign for enabling multi threaded support.
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.