Table of Contents
Table of Contents
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.
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.
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.
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
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.
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.
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.
These macro functions output a message, depending on their importance and whether or not reporting is compiled in.
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
Print message if APD_REPORT(importance, ...) would not print.
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.
The assertions macros test a condition, and if it isn't true, the program exits with a stack trace (if function tracking is on).
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)
Abort with message if:
test expression is false (equal to 0)
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.
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
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
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.
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.
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
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.
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.
Table of Contents
APD uses both preprocessor variables and function calls at run-time to control its behavior.
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.
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:
are self-explanitory.
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().
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 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.
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 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
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().
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.
Table of Contents
There are several methods for adjusting when assertions and reports are executed.
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).
You can also change the threshold using the run-time function apd_set().
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().
where set_type can be:
APD_SET_ERROR_STREAM
Table of Contents
Table of Contents
Some related project are:
log4j is a popular logging package for Java. It can be found at http://logging.apache.org/log4j/docs/index.html
log4c is a port of log4j to C. It can be found at http://log4c.sourceforge.net/.