Lazy Foo' Productions

SDL Forums external link SDL Tutorials Articles OpenGL Tutorials OpenGL Forums external link
Follow BlueSky Follow Facebook Follow Twitter Follow Threads
Donate
News FAQs Contact Bugs

Pinpointing a Crash

Last Updated: Apr 30th, 2023

So your application seems to be crashing at random. Here you'll learn a way to find the location of the crash.
Before I start, let me say this isn't the best method to isolate segfaults. The best way is by using your debugger. The thing is this site is all about being cross platform, and having to make an article for each of the debuggers would take more time than I have. After you read this article (or now if you want to), I recommend looking up how to use your IDEs/compilers debugger and learn to use it. This article is just to give you a method I've used that helped me successfully find and fix segfaults.

So one day I get an e-mail about someone who can't get one of my tutorial applications to work because it keeps opening and immediately closing. All the code is correct, he has all the files in the right place, but it still crashes. The thing is he's on OS X and I don't have a Mac to test the code on. It's in my interest to make sure my code is bug free so I had to find a way to debug over e-mail.

I decided to use a game log. Game logs record how the application is running. We use it to find the line that causes the crash by checking the game log to find the last thing the application did before crashing. So I tell the Mac user to paste some logging code into the program to find the problem.
//File stream for the game log.
FILE* gLogger = fopen( "log.txt", "w+" );
Here's the file stream we would use to create a game log. It's global because we're going to need it available at all points in the program.
void log( std::string message )
{
    //Write message to file
    char buf[ 1024 ];
    sprintf( buf, "%\n", message.c_str() );

    //Flush the buffer
    fflush( gLogger );
}
This is our function that writes to the game log. It writes the message in the argument and goes to the next line. You don't have to start a new line every time you write, but most of the time in our game log we write one line at a time and it's easier not having to add a "\n" to the message every single time we write to the game log.

The after we write to the game log we flush the buffer. The reason we do this is because whenever we write to a file we are actually requesting to write. It can happen where you request to write to the file and program crashes before the write goes through. The flush() function makes sure that it gets written..

If you're wondering why we aren't using C++ fstream, the answer is like cout, it isn't thread safe. Sometimes the older methods work best.
    //Quit flag
    bool quit = false;
    
    //Initialize
    if( init() == false )
    {
        return 1;    
    }
    
    //Load the files
    if( load_files() == false )
    {
        return 1;    
    }
    
    //Apply the surfaces to the screen
    apply_surface( 0, 0, background, screen );
    apply_surface( 240, 190, foo, screen );
    
    //Update the screen
    if( SDL_Flip( screen ) == -1 )
    {
        return 1;    
    }
This code is from the old color keying tutorial. Say if this code was crashing, how would we use game logging to find the problem?
    //Quit flag
    bool quit = false;
    
    log( "Initializing..." );
    //Initialize
    if( init() == false )
    {
        return 1;    
    }
    
    log( "Loading files..." );
    //Load the files
    if( load_files() == false )
    {
        return 1;    
    }
    
    log( "Blitting..." );
    //Apply the surfaces to the screen
    apply_surface( 0, 0, background, screen );
    apply_surface( 240, 190, foo, screen );
    
    log( "Updating screen..." );
    //Update the screen
    if( SDL_Flip( screen ) == -1 )
    {
        return 1;    
    }
Here you see the logger is writing to the log what's happening in the program as its happening. So if we open log.txt and we see this:

Initializing...
Loading files...

It means the program managed to get to the file loading function but it never got to the blitting. Now we know the crash happened somewhere in the load_files() function. Now it's time to dig deeper.
bool load_files()
{
    log( "Loading background..." );
    //Load the background image
    background = load_image( "background.png" );
    
    //If the background didn't load
    if( background == NULL )
    {
        log( SDL_GetError() );
        log( IMG_GetError() );
        return false;    
    }
    
    log( "Loading foo..." );
    //Load the stick figure
    foo = load_image( "foo.png" );
    
    //If the stick figure didn't load
    if( foo == NULL )
    {
        log( SDL_GetError() );
        log( IMG_GetError() );
        return false;    
    }
    
    return true;    
}
Now we log the file loading function.

Now we not only log to isolate the problem, we also use SDL's error reporting functions. SDL_GetError() returns errors as a string from SDL so we can write it to the game log and IMG_GetError() is used for SDL_image errors. Other error reporting functions you'll want to know about are TTF_GetError() for SDL_ttf and Mix_GetError() for SDL_mixer.

When our Mac user opened up the log again, he found out load_image() returned NULL and IMG_GetError() reported that the problem was he didn't have proper png support installed.
Remember, segfaults are caused by the program trying to access a piece of memory it shouldn't. This is usually caused by:
  1. Uninitialized pointers.
  2. Trying to use objects from NULL pointers.
  3. Arrays going out of bounds.
I was working on an old tetris clone and I had a for loop that was going out of bounds and crashing the application. This loop was deleting game objects after they were no longer being used but it was deleting more objects than there were that existed. The for loop kept going until it reach the object count, but the object count was wrong. Sure enough, I looked in the constructor for a game object class and I forgot to initialize the object count so this was looping for an indefinite amount of time. I found this out because I stuck logging code in the object deletion code and the logs showed it has a rediculous number instead of the 4 objects that were expected to be deleted.

Another tool you can use are the __FILE__ and __LINE__ macros. Every modern C/C++ compiler has these macros that store the value of what file and line the program is at. It's easier to see with an example.
void log( std::string file, int line, std::string message = "" )
{
    //Write message to file
    char buf[ 1024 ];
    sprintf( buf, "%s - %d : %s", file.c_str(), line, message.c_str() );
    fputs( buf, gLogger );
    fflush( gLogger );
}

int main( int argc, char* args[] )
{
    //Start SDL
    SDL_Init( SDL_INIT_EVERYTHING );
    
    log( __FILE__, __LINE__ );
    
    //Quit SDL
    SDL_Quit();
    
    return 0;    
}
This would output mysource.cpp 21 into your log. This method is useful when you don't want to make unique messages for each log.

Now you have a method to find your segfault which is half the battle when trying to fix them. You can use this method when you don't have access to a debugger, but don't use it as an excuse not to learn to use your debugger.

Well why don't I create a debugger tutorial instead of showing this hack method of finding segfaults that's more difficult than using debuggers? I originally wrote this article as a college freshman and if you asked me as college senior to take it down I would have. If you ask me now as somebody who has had years of experience as a game developer, I would say keep it. This specific use case is impractical, but it's simple introduction to logging which is something that is very important in real software development. I have had more than a few esoteric memory/multithreading bugs that effective use of logging was used to solve them. There are game engine books that have entire chapters dedicated to logging, because imagine how much an engine like Unreal has going on in a single frame. There are entire companies whose only purpose is managing logs because imagine how many logs a company like Amazon produces in a day.

While a debugger would be much more useful in this specific case because it would immediately spit out the file/line that causes a segfault, logging is another tool in your debugging toolkit when things like breakpoints and watches aren't enough.
If you have any suggestions to improve this article, It would be great if you contacted me so I can improve this article.