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.
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.
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:
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.
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.
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:
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.
- Uninitialized pointers.
- Trying to use objects from NULL pointers.
- Arrays going out of bounds.
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
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.
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.