Lazy Foo' Productions

Pinpointing a Crash

Last Updated 6/09/12
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. std::ofstream logger( "log.txt" );
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.

If you don't know file IO, I cover it in tutorial 24. We're going to be doing simple stuff with file IO so it shouldn't be too hard to learn as we go along.
void log( std::string message ) { //Write message to file logger << message << std::endl; //Flush the buffer logger.flush(); }
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" or "endl" 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.

Actually, here it is redundant since std:endl also flushes the buffer. If you used "\n" instead of endl you'd have to call flush() manually. I just wanted to demonstrate the flush() function.
//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 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.
So back when I was developing lazy blocks, I was testing it and it started crashing at random. Using a game log, I would have done something like this in the next chunk of code.

Note: I won't be actually using the real lazy blocks code because it makes me want to vomit.
//File stream std::ofstream logger; //Logging function void log( std::string message ); int main( int argc, char* args[] ) { //Start up init(); load_files(); while( quit == false ) { //Open log file logger.open( "log.txt" ) log( "handling events..." ); handle_events(); log( "doing logic..." ); logic(); log( "rendering..." ); render(); //Update screen update_screen(): cap_fps(); //Close log file logger.close(); }
Here is what the code would look like if I was making a game log for a game loop. Notice at the top we open the game log and at the bottom we close it. This is so we start the game log over every time we go through the game loop. If we just keep appending to game log, we could eventually create one giant file that wastes space on our hard drive.

So I had the program run a few times and the last line said "Deleting Piece..." whenever it crashed. This means the program crashed when the function that deleted the piece objects was called. It also only crashed when the square piece hit the bottom of the stage.

So then I logged the delete_piece() function and I found out the the program would crash at the next chuck of code inside of the set_blocks() function of the piece class.
log( "Setting blocks..." ); //Set blocks for( int s = 0; s < numBlocks; s ++ ) { blocks[ s ]->set_status( BLOCK_STATIONARY ); }
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.
So something was causing us to go out of bounds. This loop keeps going for however long numBlock says to, so the problem is probably that numBlocks was set wrong. Sure enough, I looked in the constructor for the square piece class and I forgot to initialize numBlocks so this was looping for an indefinite amount of time.

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.
#include "SDL/SDL.h" #include <fstream> //File stream for the game log. std::ofstream logger( "log.txt" ); void log( std::string file, int line, std::string message = "" ) { //Write message to file logger << file << ", " << line << ":" << message << std::endl; } 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.
If you have any suggestions to improve this article, It would be great if you contacted me so I can improve this article.