2021 January 9,

CS 311: Debugging Ideas

Graphics programs written in C can be hard to debug. Sometimes they show useful information, but sometimes they just show a black screen, and sometimes they just crash. Here are a few remarks about debugging, in increasing order of sophistication.

First, What is a Seg Fault?

A segmentation fault or seg fault occurs when your program attempts to access memory that is forbidden to it. The operating system detects the illegal memory access and shuts down your program. But why does it happen? In my experience, there are two main causes.

The first cause is array overruns. Suppose that you declare an array with 100 elements, and then you accidentally try to access the 200th element. The C compiler does very few checks for such errors. Even if it did more checks, it is not possible to detect all such errors at compile time. Anyway, if the array overrun causes your program to leave its allocated memory, then you get a seg fault. (If the array overrun does not cause your program to leave its allocated memory, then you don't get a seg fault, but your program probably doesn't work correctly.)

The second cause is dereferencing a bad pointer. For example, if you declare a pointer variable but forget to initialize it, then its value is garbage, and dereferencing the pointer might cause you to leave allocated memory. Also, many functions that return a pointer use the convention that NULL (the address 0) is returned when an error occurs. If you fail to check for null pointers, then you might accidentally attempt to access memory location 0, which is always invalid.

Printing

The simplest debugging tactic is adding print statements to your code. To do this well, define a function as follows, near the top of your main.c.

void debug(int integer, double real, const char *message) {
    fprintf(stderr, "%d %f %s", integer, real, message);
    fflush(stderr);
}

Then, for example, to examine the values of three doubles named x, y, z, you can do

debug(0, x, "");
debug(0, y, "");
debug(0, z, "\n");

You get some extraneous 0s, but that's not terrible, and of course you can alter the code to print exactly what you want. If you're debugging a program that crashes, then sprinkle debug statements throughout your code, and see which ones produce output before the crash. By iterating this process, you can pinpoint the exact line that causes the crash.

The key trick here is fflush. Without it, there may be a delay between the time when your program prints and the time when the output shows up in your Unix shell. If the program crashes during that delay, then you might lose some output and get the wrong idea about where the crash happens. The fflush forces the print statement to complete before your program moves on to the next statement, thus avoiding that pitfall.

Testing Your Math Against Mine

During the first part of the course (rasterization in software), you have to implement some math functions, and finding bugs in them might be difficult. So here is a way to check your implementations against mine.

Download testJRD.h and either testJRDmacOS.o or testJRDLinux.o. These files are my implementation of 120vector.c, 140matrix.c, 140isometry.c, and 150camera.c (which are compatible with earlier versions). Also download testJRD.c. It gives examples of how to test your code. Adapt it to perform whatever tests you need.

(The ".o" files above are compiled for Intel processors. They will not work on ARM processors such as Apple's M1 and Microsoft's SQ1. If your computer is ARM-based, then test on a friend's computer or on Carleton's remote macOS machines.)

C Debugger

A debugger is a program that allows you to inspect your program in memory as it is running. In a sense, it's the print-out-variable-values idea, which I explained above, made more powerful and convenient. See my Debugger Tutorial.

Graphics Debugger

None of the debugging ideas above is specifically about graphics. What do you do when your program runs without crashing, and there are no obvious problems with the values of your variables, but the image you get is just your black background? Perhaps your code and your scene are perfect except that you're pointing your camera in the wrong direction.

Well, there are debugging tools that intercept the calls to OpenGL, so that you can visualize and analyze them. I don't use these tools myself. RenderDoc seems to be a well-respected example, that runs on Linux (and Windows) but not macOS. Apple used to offer an OpenGL Profiler and Driver Monitor, but they probably aren't supported anymore. You might try poking around the Web for others.