Debug tips

All programmers know that it takes a lot of work to write good code. Even very good experienced programmers only manage to write an average of a few lines of (efficient, debugged) code a day.

Most programmers rely on some kind of debugging tools to help find bugs in their code. And there are many different debugging tools around.

On this page I present an include file for C programming that will help debug programs, by writing selective information to a log file.

Some of the ideas behind this tool were learned when I worked for a computer graphics firm in Rio de Janeiro, way back in 1986 and 1987. Even being so old, the concepts still apply.

The main idea is to define some DEBUG statements that can be included (or not) in your final code. All that is needed to include the code is to define a compile variable. If the variable is not defined, the program will be compiled without any of my debug tools. If the variable is defined, then the program will be compiled with the code, and a selective log file will be generated.

The code bits I supply here are free of charge, and can be used in just about any C program. It can also be modified slightly and be used in other languages (such as PHP). The main difference in other languages is that most don't have the preprocessor, and therefore the commands cannot be included and excluded as in C. But, they can be substituted for IF statements, and this way only the IF statement will be executed, and not the debug code if the IF statement comes up false.

Of course, just because the code can be used free of charge, doesn't mean that if you find the code useful, you can't thank the author. If you use the code, and find it helpful, please thank the author. If you want, you can even send him a pizza (or the cash to buy one). That is just a suggestion, of course.

Now, on with the tips:

Here I will explain the concepts, and you can take your pick:

  • use the tips to create your own code;
  • download my debug.h file and use it.

The file starts with the preprocessor directive #ifdef DEBUG
If DEBUG is defined, all the code up until (excluding) the #ENDIF is included in the compiled code. If DEBUG is not defined, none of the code will be included in the compiled code, making the output code lighter.

#ifdef DEBUG
#define DEBUG_LOG "debug.log"
int debug_log_fp;
char debug_txt_tmp[1024];
int debug_level = 0;
int debug_indent = 0;
int debug_loop_count;
#endif

The first things we define are:

  • the name of the log file
  • an int to be used to point to the file descriptor of the log file.
  • a char array (string buffer) for formatting the strings to be written to the log file
  • an int to hold the current debug level
  • an int to hold the current indent level
  • an int for a temporary loop counter (for command).
  • any other temporary variable we need for the routines.

Just like the first section, the second section starts with the preprocessor directive: #ifdef DEBUG
If DEBUG is defined, the DEBUG_OPEN() function will be included in your output code.
If DEBUG is not defined, the DEBUG_OPEN() function will be substituted for nothing at all (making your code lighter).

#ifdef DEBUG
#define DEBUG_OPEN() debug_log_fp = open(DEBUG_LOG, O_WRONLY|O_CREAT|O_TRUNC, 00644); \
if(debug_log_fp == -1) { \
fprintf(stderr, "Error opening debug log file: DEBUG_LOG\n"); \
exit(1); \
}
#else
#define DEBUG_OPEN()
#endif

The DEBUG_OPEN code is basic code to open a file in write-only mode, truncate it, and create it if it doesn't already exist. If there is an error in creating/truncating the file, this will be reported, and your program execution terminated.
On successful file opening, the file is changed to mode 0644 (read and write by owner, read by group and others).

The next two sections deal with entering and exiting functions or subroutines.

/*
** Entering functions
*/
#ifdef DEBUG
#define DEBUG_ENTER(function_name) \
for(debug_loop_count = 0; debug_loop_count < debug_indent; debug_loop_count++) \
write(debug_log_fp, " ", strlen(" ")); \
sprintf(debug_txt_tmp, "Entering %s\n", function_name); \
write(debug_log_fp, debug_txt_tmp, strlen(debug_txt_tmp)); \
if(debug_indent < 100) debug_indent += 2;
#else
#define DEBUG_ENTER(function_name)
#endif

/*
** Exiting functions
*/
#ifdef DEBUG
#define DEBUG_EXIT(function_name) if(debug_indent > 1) debug_indent -= 2; \
for(debug_loop_count = 0; debug_loop_count < debug_indent; debug_loop_count++) \
write(debug_log_fp, " ", strlen(" ")); \
sprintf(debug_txt_tmp, "Exiting %s\n", function_name); \
write(debug_log_fp, debug_txt_tmp, strlen(debug_txt_tmp));
#else
#define DEBUG_EXIT(function_name)
#endif

The idea of the indentation is that for each function you enter, you offset the text 2 spaces. Then for each function you exit, you subtract 2 spaces from the indentation. This make it easy on the eye to spot where you entered and exited functions or subroutines, in the generated log file.

The next 3 sections deal with the debug level. Here we can equate, or bitwise set or reset each of the bits (int) of the level.

/*
** assign the debug level
*/
#ifdef DEBUG
#define DEBUG_LEVEL(level) debug_level = level;
#else
#define DEBUG_LEVEL(level)
#endif

/*
** set (add) the level of debug
*/
#ifdef DEBUG
#define DEBUG_SET_LEVEL(level) debug_level |= level;
#else
#define DEBUG_SET_LEVEL(level)
#endif

/*
** reset (subtract) the level of debug
*/
#ifdef DEBUG
#define DEBUG_RESET_LEVEL(level) debug_level &= ~level;
#else
#define DEBUG_RESET_LEVEL(level)
#endif

Each bit can be assigned to a certain type of action in our main program. Since we are dealing with each bit, our int is formed by several bits (at least 16) and each one represents a power of 2 (in decimal notation).

As an example, lets say we code all the input parts of our program as 1, all our output parts as 2, our loops as 4, and our calculated values as 8. In this way we can add what parts we want to log data for, like this:

  1. will give us only our input data.
  2. will give us only our output data.
  3. will give us output and input data.
  4. will give us our loops.
  5. will give us our loops and input data.
  6. will give us our loops and output data.
  7. will give us our loops, output and input data.
  8. will give us our calculations.
  9. will give us our calculations and input values.
  10. will give us our calculations and output values.
  11. will give us our calculations, output and input values.
  12. will give us our calculations and our loops.
  13. will give us our calculations, our loops and input data.
  14. will give us our calculations, our loops and output data.
  15. will give us our calculations, our loops, our output and input data.

With our predefined routines we can set or reset each bit individualy, or we can just plain assign a complete new value to our debug level.

The next 4 sections deal with writing the data to the log file. Each one will only write the data to the log file, if the level requested in the command has been previously set in the master debug level. Just like our example above, with input, output, loops, and calculations.

/*
** write a string to the logfile. Format:
** DEBUG_WRITE("String to print in logfile");
*/
#ifdef DEBUG
#define DEBUG_WRITE(level,string) if(level & debug_level) { \
for(debug_loop_count = 0; debug_loop_count < debug_indent; debug_loop_count++) \
write(debug_log_fp, " ", strlen(" ")); \
write(debug_log_fp, string, strlen(string)); \
}
#else
#define DEBUG_WRITE(level,string)
#endif

/*
** write a formatted string to the logfile.
** Example 1:
** int j = 5;
** DEBUG_WRITE2(level, "J = %d\n", j);
**
** Example 2:
** double j = 6.0;
** DEBUG_WRITE2(level, "J = %f\n", j);
**
** for char or string variables use DEBUG_WRITE()
*/
#ifdef DEBUG
#define DEBUG_WRITE2(level, string1, string2) if(debug_level & level) { \
for(debug_loop_count = 0; debug_loop_count < debug_indent; debug_loop_count++) \
write(debug_log_fp, " ", strlen(" ")); \
sprintf(debug_txt_tmp, string1, string2); \
write(debug_log_fp, debug_txt_tmp, strlen(debug_txt_tmp)); \
}
#else
#define DEBUG_WRITE2(level, string1, string2)
#endif

#ifdef DEBUG
#define DEBUG_WRITE3(level, string1, string2, string3) if(level & debug_level) { \
for(debug_loop_count = 0; debug_loop_count < debug_indent; debug_loop_count++) \
write(debug_log_fp, " ", strlen(" ")); \
sprintf(debug_txt_tmp, string1, string2, string3); \
write(debug_log_fp, debug_txt_tmp, strlen(debug_txt_tmp)); \
}
#else
#define DEBUG_WRITE3(level, string1, string2, string3)
#endif

#ifdef DEBUG
#define DEBUG_WRITE4(level,string1,string2,string3,string4) if(level & debug_level) { \
for(debug_loop_count = 0; debug_loop_count < debug_indent; debug_loop_count++) \
write(debug_log_fp, " ", strlen(" ")); \
sprintf(debug_txt_tmp, string1, string2, string3, string4); \
write(debug_log_fp, debug_txt_tmp, strlen(debug_txt_tmp)); \
}
#else
#define DEBUG_WRITE3(level,string1,string2,string3, string4)
#endif

The difference in the 4 routines is in the formatting of the string to print.

The first routine (DEBUG_WRITE(level, string)) takes 2 parameters: the debug level at which to print the string, and the preformated string to print. As long as the level is satisfied, the string will be added directly to the log file, just as it is.
EX: DEBUG_WRITE(1, "Went thru if statement\n");

The second routine (DEBUG_WRITE2(level, string1, string2)) takes 3 parameters: the debug level at which to print the string, the string to print (using sprintf) and one variable. As long as the level is satisfied, the string will be printed to the log file, following the rules for sprintf(logfile, "formatted string %d\n", string2)

The formatted string can include ONE variable parameter to be printed, usually a %d, %f or %s.
EX: DEBUG_WRITE2(1, "Value = %f\n", variable_name);

The third routine (DEBUG_WRITE3(level, string1, string2, string3)) takes 4 parameters: the debug level at which to print the string, the string to print (using sprintf) and two variables.
EX: DEBUG_WRITE3(1, "Array a[%d] = %f\n", index, a[index]);

The fourth routine (DEBUG_WRITE4(level, string1, string2, string3, string4)) takes 5 parameters: the debug level at which to print the string, the string to print (using sprintf) and three variables.
EX: DEBUG_WRITE4(1, "matrix[%d][%d]=%f\n", index1, index2, matrix[index1][index2]);

The next section was written specifically for my PhD work, because I use frames and vectors. This macro writes a vector (or frame) to the debug.log file. For this macro we pass the level, the vector, the length of the vector (or frame), and the string of the type data to print (typically "%d" or "%f").
/*
** Write a vector
** EX: DEBUG_WRITE_VECTOR(1, vector, length, "%f");
*/
#ifdef DEBUG
#define DEBUG_WRITE_VECTOR(level,vector,size,string) if(level & debug_level) { \
                for(debug_loop_count = 0; debug_loop_count < debug_indent; debug_loop_count++) write(debug_log_fp, " ", strlen(" ")); \
                for(debug_loop_count2 = 0; debug_loop_count2 < size; debug_loop_count2++) { \
                        sprintf(debug_txt_tmp, string, vector[debug_loop_count2]); \
                        write(debug_log_fp, debug_txt_tmp, strlen(debug_txt_tmp)); \
                } \
                sprintf(debug_txt_tmp, "\n"); \
                write(debug_log_fp, debug_txt_tmp, strlen(debug_txt_tmp)); \
        }
#else
#define DEBUG_WRITE_VECTOR(level,vector,size,string)
#endif

Then, our last section just closes the log file that we opened at the beginning. Just plain good programming practice.

/* 
** close log file
*/
#ifdef DEBUG
#define DEBUG_CLOSE() close(debug_log_fp);
#else
#define DEBUG_CLOSE()
#endif

As in most C programs that do anything useful, to be able to compile a program using our debug.h macro definitions, several system include files need to be used:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

Just to help you a little more, I have a sample program in C and a Makefile you can use to try out my debug.h macros.




ċ
Makefile
(0k)
Robert Rice Brandt,
May 19, 2010, 2:08 PM
ċ
debug.h
(6k)
Robert Rice Brandt,
Jul 14, 2010, 1:30 PM
ċ
main.c
(1k)
Robert Rice Brandt,
May 19, 2010, 2:07 PM