APD: Assertions and Printf Debugging Library for C


Table of Contents

1. About APD
Features
In defense of printf debugging
Terminology
Requirements and Incompatabilities
Bug Reports
2. Using APD
Basic concepts
Report macros
Assert macros
Other macros
Function call macros
3. APD Settings
Main compile-time configuration
Main run-time configuration
4. Other settings
Internal debug
Valgrind stack traces
Error streams
Other preprocessor variables
5. Adjusting
Compile-time Adjustments
Run-time Adjustments
6. Reference
Example code
7. Other
Related Projects

Chapter 1. About APD

Assertion testing and message logging are two methods for catching bugs in software. However, the C programming language has very simplistic versions of these features: printf() is commonly used to output messages (sometimes derisively called "printf debugging"), and the C library has a very simplistic assertions macro called assert, defined in assert.h.

APD is a more sophisticated implementation, but the idea is the same: output messages describing program progression, and check to make sure conditions are correct along the way.

Features

The APD library provides the following:

  • Assertion testing

  • Debug printing (or logging)

  • Descriptive function stack trace

  • Monitoring of internal data structures against corruption by rogue pointers

The library allows fine-grained control over each assertion or debug print statement. Assertions and report code can be eliminated completely at compile-time. If they are compiled in, whether they are active can be adjusted by both preprocessor variables at compile-time, and functions at run-time. Custom stack traces can be printed at any time.

In defense of printf debugging

While many programmers look down on "printf debugging", this method can be an advantage for several reasons:

  • in complex code, the reason for an error is likely to be an incorrect value far back in the code which would be difficult to quickly find using a debugger, but obvious on inspection using printf debugging

  • it can be faster and easier to scan a few pages of printf debugging than to set the apropriate watches and breakpoints in a debugger

  • if compiled in by default, the end user can turn on debugging by simply changing a command-line switch

  • They can then submit the resulting debug file demonstrating the error they are reporting. Instead of being clueless why the error occurred, you now have a good chance of tracking it down.

Terminology

APD is a mixture of compile-time and run-time techniques. Following convention:

  • all preprocessor macros will have names in ALL CAPS

  • all externally callable library functions are all lower case and start with "apd_"

  • all internal library functions are all lower case and start with "_apd_"

  • square brackets "[" and "]" are used to indicate optional parameters in a parameter list

  • regular functions and macro functions will end with "()" when in descriptive text to indicate that they are functions

Requirements and Incompatabilities

APD needs a C compiler than implements the ISO 1999 C standard. It was developed using the GNU project's compiler gcc. APD relies on variable argument macros, which is a new feature in the 1999 ISO C standard. GNU gcc has had this feature for some time, though, so any modern version of gcc should work. APD was originally developed using gcc 3.2 and is known to work with this version. The one GNU extension used is the library is the library initializer at the beginning of apd.c. This is not necessary if your code calls the initialization function before calling any APD macros or functions. Please send a bug report if your version of gcc or other compiler doesn't work with APD so I can update this documentation.

Bug Reports

APD has its own internal reporting and assertions system. If you think you have found a bug in APD, you need to turn on APD's internal debugging to help me find the problem. To turn on all internal debugging, add the following statements after APD initialization:

    apd_set(apd_object, APD_SET_INTERNAL_DEBUG, 1);
    apd_set(apd_object, APD_SET_INTERNAL_DEBUG_LEVEL, 5);
    apd_set(apd_object, APD_SET_CHECK_INTERNAL, 1);
    

This will turn on internal debugging and internal consistency checking to the highest levels. If this creates too much output, move these statements closer to the place in the code that has the problem. Send bug reports to software@danielwebb.us.

Chapter 2. Using APD

Basic concepts

Each assertion testing and debug printing statement is given an "importance level". If the threshold level for reporting or assertions is greater than the importance level, then that statement will be executed. For example, if the assertions threshold is 3, and the following assertion is used:

    APD_ASSERT(2, abc > 0);
    

the assertion abc > 0 will be tested. You can change the reporting and assertions thresholds independently, and you can change them in two different ways (described in sections Compile-time Adjustments and Run-time Adjustments).

At the start of each function should be a call to APD_PUSH_FUNCTION(), and at the end of each function should be a call to APD_POP_FUNCTION(). This will maintain a stack description, including any custom text you want, such as parameter values.

Most of the APD macros can take a "message" followed by optional message arguments. Treat this exactly as you would the message string and optional aruments to printf. The APD library uses functions in the vprintf family, so that is really what you are doing.

Report macros

These macro functions output a message, depending on their importance and whether or not reporting is compiled in.

APD_REPORT(importance, "message" [, message arguments])

Print message if both:

  • APD_DEBUG_COMPILE or APD_REPORT_COMPILE was defined when the apd.h header was processed

  • (report threshold + APD_ADJUST_REPORT_THRESHOLD) >= importance

APD_REPORT_OTHERWISE(importance, "message" [, message arguments])

Print message if APD_REPORT(importance, ...) would not print.

APD_REPORT_ALWAYS("message" [, message arguments])

Always report the message. If APD_REPORT_COMPILE is not defined, then the message will be sent to APD_DEFAULT_ERROR_STREAM.

Note that a trailing newline is added automatically to all messages, so you don't need to add one.

Assert macros

The assertions macros test a condition, and if it isn't true, the program exits with a stack trace (if function tracking is on).

APD_ASSERT(importance, test_expression [, "message" [, message arguments]])

Abort with message if:

  • APD_DEBUG_COMPILE or APD_ASSERT_COMPILE was defined when the apd.h header was processed

  • (assert threshold + APD_ADJUST_ASSERT_THRESHOLD) >= importance

  • test expression is false (equal to 0)

APD_ASSERT_ALWAYS(test_expression [, "message" [, message arguments]])

Abort with message if:

  • test expression is false (equal to 0)

APD_ASSERT_ALWAYS_LIBRARY(error_stream, file, line, test_expression [, "message" [, message arguments]])

Abort with message if:

  • test expression is false (equal to 0)

This function is inteded for use in library packages where the package is keeping track of the file and line number where some package function was originally called by the user. The asserion is shown as failing at file:line, but the file and line where the APD_ASSERT_ALWAYS_LIBRARY call was located is also reported.

APD_ASSERT_BOUNDS(importance, expression, low_bound, high_bound [, "message" [, message arguments]])

Abort with message if:

  • APD_DEBUG_COMPILE or APD_ASSERT_COMPILE was defined when the apd.h header was processed

  • (assert threshold + APD_ADJUST_ASSERT_THRESHOLD) >= importance

  • expression is less than low_bound or greater than high_bound

APD_ASSERT_BOUNDS_ALWAYS(expression, low_bound, high_bound [, "message" [, message arguments]])

Abort with message if:

  • APD_DEBUG_COMPILE or APD_ASSERT_COMPILE was defined when the apd.h header was processed

  • (assert threshold + APD_ADJUST_ASSERT_THRESHOLD) >= importance

  • expression is less than low_bound or greater than high_bound

Other macros

APD_ACTION(importance, C code here)

Compile in if APD_ASSERT_COMPILE was defined when the apd.h header was processed. Execute C code if (assert threshold + APD_ADJUST_ASSERT_THRESHOLD) >= importance.

APD_ASSERT_ACTION(importance, test_expression, C code here)

Compile in if APD_ASSERT_COMPILE was defined when the apd.h header was processed. Test assertion if (assert threshold + APD_ADJUST_ASSERT_THRESHOLD) >= importance. If assertion fails, execute C code, then abort with stack trace.

APD_REPORT_ACTION(importance, C code here)

Compile in code if APD_REPORT_COMPILE was defined when the apd.h header was processed. Execute C code if (report threshold + APD_ADJUST_REPORT_THRESHOLD) >= importance

APD_DIE(message [, message arguments])

Abort program execution after printing message (with file, line, and function info included). If APD_DEBUG_COMPILE, APD_ASSERT_COMPILE or APD_REPORT_COMPILE is defined, send message to the error stream set with apd_initialize(), otherwise use APD_DEFAULT_ERROR_STREAM.

Function call macros

Each function in your code should include the following macro calls:

APD_PUSH_FUNCTION(importance [, message [, message arguments]]);

near the start of the function, and

APD_POP_FUNCTION(importance [, message [, message arguments]]);

near the end of the function (it should be the last thing other than a return).

Each function should call the PUSH_FUNCTION_DESCRIPTION() macro near the beginning of the function after variable declarations. PUSH_FUNCTION_DESCRIPTION() does two things: first, the message is pushed to the internal function stack, and second the message is passed on as if APD_REPORT() had been called with the same importance and message. If a function's message is printed by APD_PUSH_FUNCTION(), then an end of function message will also be printed by APD_POP_FUNCTION(). Note that the function call description is added to the function call stack regardless of the importance value.

The APD_PUSH_FUNCTION() and APD_POP_FUNCTION() macros allow the APD library to keep a descriptive function call stack that can be reported using APD_REPORT_FUNCTION_STACK(), or during an assertion failure. They are compiled in if either APD_ASSERT_COMPILE or APD_REPORT_COMPILE are defined. The message should normally not be a description of the function, unless it isn't clear from the name. It should include parameter values to help trace program progress quickly.

The descriptive function stack is also used indirectly by all the normal function reporting routines so that each reported line can be prepended with a number of spacing characters to indicate the depth of the function.

APD_REPORT_FUNCTION_STACK(importance)

This macro function reports a stack trace, including functions whose importance is less than or equal to threshold.

APD_REPORT_FUNCTION_STACK_ALL()

This macro function reports a stack trace including all functions.

Chapter 3. APD Settings

APD uses both preprocessor variables and function calls at run-time to control its behavior.

Main compile-time configuration

Defining APD_ASSERT_COMPILE compiles in regular assertions checking.

Defining APD_REPORT_COMPILE compiles in regular reporting.

Defining APD_DEBUG_COMPILE is a shortcut that sets both variables above.

Define APD_CHECK_REGULARLY to a block of code that you want to run every time any APD code is run. For example, if you wanted to check on a runtime flag such as the math library errno variable, you could use:

#define APD_CHECK_REGULARLY if(errno != 0) \
    {printf("errno!=0\n");exit(1);}

Because this code is in all the APD functions, it can't contain any APD calls, because that would be self-referential.

These preprocessor variables should be set before apd.h is read by the C preprocessor.

Main run-time configuration

Before your program calls any APD library macros, you should call apd_initialize_object().

apd_object = apd_initialize(report_threshold, assert_threshold,
    max_function_depth, report_threshold_decreases_with_stack, error_stream);

apd_initialize sets up a structure which holds all the runtime data for the APD library. By default, the library is compiled to use a global variable "apd_object" declared in apd.h and defined in apd.c. If you need multiple debug structures (like when using threads), check apd.h to see what needs to be done.

Parameters of apd_initialize:

report_threshold and assert_threshold

are self-explanitory.

max_function_depth

is the maximum function stack depth. Set this to some value much larger than the maximum call depth in your program, and it will catch recursions that are too deep, or the case when you accidentally forget to match APD_PUSH_FUNCTION() with APD_POP_FUNCTION().

error_stream

is the file to send messages to from the APD library. error_stream can be any file stream, including the standard files stdout and stderr if you include the header file stdio.h.

report_threshold_decreases_with_stack

report_threshold_decreases_with_stack is an int (0 or 1). If 1, each time a new function is added to the function stack using APD_PUSH_FUNCTION(), the report threshold is decreased by one. Note that this is done before the push function report. After the function is popped, and the pop function report has been processed, the report threshold is increased by 1. The effect of this is that as you go deeper in the call stack, less is reported.

At the end of the program, call apd_end(&apd_object); to free the memory used by apd.

Chapter 4. Other settings

Internal debug

The APD library has its own internal debugging if the problem is in APD itself. By default, APD internal debugging is compiled in, but not on. To turn in internal debug, add this code:

apd_set(apd_object, APD_SET_INTERNAL_DEBUG, 1);
apd_set(apd_object, APD_SET_INTERNAL_DEBUG_LEVEL, level);
apd_set(apd_object, APD_SET_CHECK_INTERNAL, 1);

Where level is between 1 and 5.

Internal debugging must be turned on at runtime to be active, but just having them compiled in will slow down execution slightly. Define APD_INTERNAL_DEBUG_COMPILE to 0 to not compile internal debugging.

Valgrind stack traces

Valgrind is an excellent run-time memory checker and profiler. If you are running your code under Valgrind, Valgrind stack traces are triggered after each APD stack trace. Valgrind stack traces are better than APD stack traces in that they show the line of the calling function when it called each function further down the stack. APD shows the line where APD_PUSH_FUNCTION() was called. On the other hand, Valgrind only shows the line of code, while APD can have a custom string for each function in the stack.

By default, a Valgrind stack trace is triggered any time an APD stack trace is generated using APD_REPORT_FUNCTION_STACK. This feature works by reading an allocated but uninitialized variable, thus attracting the all-seeing eye of Valgrind. If Valgrind is not running, this won't do anything. To turn off this feature, add this code:

apd_set(apd_object, APD_SET_VALGRIND_STACK_TRACE, 0);

Valgrind is also triggered by an intentional segfault caused at the end of an assertion failure or a call to APD_DIE. This feature can be turned off by changing APD_SEGFAULT_ON_DIE to 0 in apd_internal.h

Error streams

There are several ways to control where messages from assertions and reports go.

If APD_ASSERT_COMPILE or APD_REPORT_COMPILE are undefined APD_DEFAULT_ERROR_STREAM holds the output stream for the "ALWAYS" functions. The other functions won't be compiled in, so they won't output anything. See apd.h for the default value of APD_DEFAULT_ERROR_STREAM.

If APD_ASSERT_COMPILE or APD_REPORT_COMPILE are defined, output goes to the error stream defined in the call to apd_initialize().

Other preprocessor variables

APD_IMPORTANCE_ABSOLUTE_LIMIT defines the absolute limit of importance. This is useful for assertions inside the debug library that make sure importance is within some reasonable limits. APD_IMPORTANCE_ABSOLUTE_LIMIT should be about 4 times greater than the largest importance value you use. The default value is 20, corresponding to the default system of 0 to 5 importance.

Function names are prepended by characters to indicate the depth of the function call. APD_LEVEL_SPACER_CHARACTER is the character used. "|" is the default.

Chapter 5. Adjusting

There are several methods for adjusting when assertions and reports are executed.

Compile-time Adjustments

Other than the all-or-nothing option of compiling out normal assertions and reports (described in section Main compile-time configuration), you can also adjust the thresholds using preprocessor variables. This allows you, for instance, to change the threshold in a short section of code that is giving you problems. Place the following at the beginning of the problem code:

#undef APD_ADJUST_ASSERT_THRESHOLD
#define APD_ADJUST_ASSERT_THRESHOLD 1

and at the end of the problem section put

#undef APD_ADJUST_ASSERT_THRESHOLD
#define APD_ADJUST_ASSERT_THRESHOLD 0

This example raises the assert threshold by 1. The #undef is needed because apd.h will have already defined APD_ADJUST_ASSERT_THRESHOLD to the default value.

APD_ADJUST_REPORT_THRESHOLD is the corresponding preprocessor variable for reporting.

Note that these variables are added to the run-time thresholds. Whereas a run-time threshold could be different on different runs of the program, the compile-time adjustments are the same for every run. The default if these variables are not defined is 0 (no effect).

Run-time Adjustments

You can also change the threshold using the run-time function apd_set().

apd_set(apd_object_internal, set_type, value);

where set_type can be one of:

            APD_SET_REPORT_THRESHOLD
            APD_SET_ASSERT_THRESHOLD
            APD_SET_MAX_FUNCTION_DEPTH
            APD_SET_INTERNAL_DEBUG
            APD_SET_CHECK_INTERNAL
            APD_SET_ACTION_EXTRA_INFO_ON
            APD_SET_REPORT_THRESHOLD_DECREASES_WITH_STACK
            APD_SET_VALGRIND_STACK_TRACE
            

Streams are changed using apd_set_file().

apd_set_file(apd_object_internal, set_type, file);

where set_type can be:

APD_SET_ERROR_STREAM

Chapter 6. Reference

Table of Contents

Example code

Example code

See the file example.c in the source directory for an example of APD in action. Use

make example

to compile and run the example code.

Chapter 7. Other

Table of Contents

Related Projects

Related Projects

Some related project are:

log4j

log4j is a popular logging package for Java. It can be found at http://logging.apache.org/log4j/docs/index.html

log4c

log4c is a port of log4j to C. It can be found at http://log4c.sourceforge.net/.

log4j