Page 1 of 1

How to perform a simple and effective debugging?

Posted: Wed Mar 28, 2007 3:46 am
by ~
It's far too common and frequently a big source of the most "slippery" bugs to construct a bunch of related pointers, pointers to pointers and so on.

What would be a way of easily avoiding constructing wrong pointers and in consequence filling correctly its structures?

Will it be enough to show the contents of every single pointer we want to debug as well as a hex and/or screen dump of its contents and the contents of the structures it points to?

Posted: Wed Mar 28, 2007 4:15 am
by Solar
You could pepper your code with something like this:

Code: Select all

struct my_struct
{
#ifndef NDEBUG
    int id = 0xdeadbeef;
#endif
    /* rest of the definitions here */
};
Then, when you handle a pointer that should be of the given type, simply add an assert( foo->id == 0xdeadbeef ) to assert your assumption. When everything is done, compile with -DNDEBUG to get rid of the id and the assert() calls.

(Of course, when you do this you'll use symbolic constants instead of hard-coded numbers. ;-) )

Posted: Wed Mar 28, 2007 6:27 am
by mystran
I find the most helpful practice to be avoiding "void*" type like a plague. That way you (and the compiler) will always know what type you're supposed to be pointing at. Then tell your compiler to complain about pointer assignments between different types of pointers, and use explicit casts, and certain types of problems will be catched before you even have a chance to run the code.

If (when) you need to deal with pointers that could point to several types of objects, consider doing some kind of object-oriented design for them, with generic functions you can call, which know how to handle a practicular type of object, casting to correct type once in the beginning of said function.

Generally, I like to turn on as many warning as possible, and then add -Werror to cause any warning to prevent compilation in presence of warnings, since this catches many common bugs (I use -Wall but then I also use some more warnings).

The other helpful thing, is that when a structure is allocated, I usually first clear it with zero. This can be done by either calling calloc() instead of malloc(), or simply by running memset() over the structure. This will take care of initializing any potential pointers to null, if they aren't assigned anything more meaningful. Since null-pointer checking should be every C-programmers second nature, this catches errors where a structure was extended, but not all code was modified to deal with the new fields.

A related practice is to always set a pointer to null after calling free() on it, with the only exception being if the structure containing the pointer is unconditionally free()d immediately afterwards. This helps catch certain types of problems, where one piece of code thinks something points somewhere, and other piece of code just free()d it.

If there's virtual memory running, it's good idea to map the first page to a not-present entry, so that you can gracefully panic if you kernel tries to refer to address 0. If usercode does the same, you want to send it a signal (or whatever your method for signalling exceptions). This will help take care of those references to null pointers which aren't explicitly checked.

Finally, always initialize structure members by names, such that if you reorder the members, your code will still refer to the correct fields.

As for bugs that aren't caught by keeping invalid pointers null, and by having compiler insist on explicit casts, I think one just must be careful. Experience will help, but.. :P