Skip to main content Link Search Menu Expand Document (external link)

Theme: auto

Debugging

Your code doesn’t work as you expect it to. Now what?

To debug properly, you need to systematically compare your expectations of what SHOULD happen to what ACTUALLY happens. You should validate each expectation one by one to narrow down exactly where it’s going wrong.

As an example, suppose your ship doesn’t appear to be moving. Here’s an example of how you might go through the process of debugging:

  1. “I expect that MoveComponent::Update is called once per frame.” Is it? Put a breakpoint at the start of the function. If it’s not getting hit, then that means it’s not being called.
  2. “I expect the code in MoveComponent::Update updates the position of the actor”. Is it? Use variable watches and step through the code. What’s the actor’s position before your code runs? What about after the code runs? Do the values before/after seem sensible and reasonable? (As an aside, using temporary variables may help with debugging as it’s easier to see the values in the debugger).
  3. If you got through step 2 and it seems reasonable but still doesn’t work, then the next expectation might be “I expect only MoveComponent::Update is changing the actor’s position.” One way to validate this assumption is to continue past the breakpoint to the next time the function gets hit. Did the change you made persist, or did it reset to a different value? If it reset to a different value, and you’re not sure where it might’ve changed, you can us a conditional breakpoint (see below).

When posting on Piazza or asking questions in office hours, the more debugging legwork you do beforehand, the more we can help you. If you just say “it doesn’t work” and that’s it, we will ask you to do some debugging before helping. Please read the office hours expectations for more information.

Using the CLion Debugger

Using your development environment’s debugger is an extremely important programming skill as it allows you to trace through your code to figure out where it’s going wrong. Although different IDEs have slightly different user interfaces, the overall process of debugging and most terminology is similar.

Running in Debug Mode

To be able to use any debugging features, you need to run in debug mode. This is done by clicking the “bug” button on the top toolbar (instead of the play button):

Bug button

As a general rule, you should always run using the bug button. In this class, there’s no reason to run with just the play button.

Setting Breakpoints

A breakpoint allows you to pause the execution of the program at a specific line. Note that it pauses before the line runs.

The easiest way to set a breakpoint is to mouse over the line number where you want the breakpoint and click, which will create a red circle that looks like this:

Setting a breakpoint

Keep in mind that you can add and remove breakpoints even while the program is running in debug mode! You will do this often when you want to get the game to a specific state before putting a breakpoint.

Now, if you click the bug button to run and reach the line of code, the debugger will pause at the line. When paused in the debugger, the line you are paused on will be highlighted:

Paused on a line

While you can put breakpoints on empty lines or comments, it just means the debugger will pause on the next line of code that actually does something. In general, this doesn’t make sense to do, so you’re better off putting the breakpoint on real lines of code.

Inspecting Variables

When paused in the debugger, the bottom pane in CLion will show you the call stack on the left (which says which functions are running), and will show you the values of variables in the rest of the view. You can expand variables by clicking the arrows next to them. In this screenshot, I clicked the arrow next to this to see what’s in the Game object:

Debug pane

Notice that mShouldContinue shows that its value is true even though the line I’m paused on sets it to false! This is because when we pause on a line, it’s before the line is run.

As a shortcut, you can also mouse over variables that are in the current function to view their current values:

Mousing over a variable to see its value

The Call Stack

When paused in the debugger, the call stack shows which functions are currently active. Remember that every time a new function is called, it gets pushed onto the top of the call stack. Similarly, when a function returns, it gets popped off.

You can use this to understand where a function is being called from. Take, for example, this call stack:

Call stack view

The top (and currently active) function or stack frame is Game::HandleEvent, and we’re paused on line 54 of Game.cpp. We can see that HandleEvent was called by the SDL_AppEvent in Main.cpp on line 25. This in turn was called by an internal SDL function and so on, until we get all the way down to WinMain, which is the main entry point for Windows programs.

In most cases, you are going to ignore internal SDL (and other) functions and just focus on the functions that you wrote.

In the call stack view, you can click on any function in the list to see what’s on that specific line of code, and the state of that function, as well.

Controlling Execution

Step Over

The debug pane also contains several buttons that control execution. The “Step Over” button looks like an arrow hopping over something and will complete execution of all code on the current line (including any functions) and continue to the next line.

Step Over Button

In this example, if you click the “Step Over” button, the execution will continue to the next line of code with something to do. In this case, that’s the closing brace of the function:

Execution continued to the closing brace

You’ll also notice that the debug pane highlights the text of any variables which have changed:

mShouldContinue is now false

Step Into

If you are paused at a line which calls a function, the “Step Over” button will complete execution of everything on that line, including the function call, before pausing again.

However, sometimes you want to actually go into the function, in which case you can use “Step Into,” which is the arrow pointing straight down:

Step into

If the line you’re paused at doesn’t have any function calls on it, “Step Into” will do the same thing as “Step Over.”

Step Out

Sometimes, you may step into a function that you actually don’t want to try to debug, such as an SDL function. In this case, you can “Step Out” of the function which will finish the execution of the function and pause after it returns. The “Step Out” button is the arrow pointing straight up:

Step Out button

Resume Program

The “Resume Program” button tells the debugger to continue running in debug mode as normal. It will pause the next time a breakpoint gets hit, but otherwise will just keep running.

Resume Program

Stop

The “Stop” button will end the debugging session by immediately closing the program.

Stop button

Viewing, Muting, and Removing Breakpoints

If you’ve placed lots of breakpoints and want to view them, you can click the “View Breakpoints” button on the toolbar, which is two red circles:

View breakpoints

In this window, you can click on an individual breakpoint (or a category of breakpoints) and use the minus button to remove them:

Removing breakpoints through the "View Breakpoints" dialog box

You can also remove breakpoints in the source code view by just clicking on the red circle for the breakpoint you want to remove.

Sometimes, you may want to keep the breakpoints where they are but “mute” them so none of them get hit. In this case, you can click the red circle with a slash through it to temporarily mute them:

Muting breakpoints

Clicking the button again will turn them back on.

Debugging Crashes and Traversing the Call Stack

If the game crashes while in debug mode, the debugger will pause on the line of code that crashes. You’ll notice that the debug pane will show the error code (in this case, it’s a segmentation fault, which likely means a bad memory access):

Segmentation fault

In this case, the code crashed in std::vector, and if you look at the lines of code it will be a lot of code that you didn’t write and don’t understand:

Crash in vector?!

While it’s tempting to throw your hands up and say you don’t understand why it’s crashing, the key thing to take advantage of is the call stack on the left of the debug pane. Go down the list of functions and find the first function that you wrote, and click on it.

In this case, I’d click on the appropriately-named DoCrash function I wrote:

DoCrash function

Inspecting the lines of code, I’d see that I “accidentally” have an uninitialized vector pointer, and that’s the true cause of the crash:

Uninitialized pointer

Debugging Infinite Loops

If you introduce an infinite loop, your game will freeze and be entirely unresponsive. To debug this, press the pause button on the top toolbar (or in the debug pane):

Pause button

Then you can inspect the line of code and/or traverse the call stack to figure out where the infinite loop is coming from:

Call stack view of infinite loop

In the above case, we can see that Game::UpdateGame is somehow involved in the infinite loop. And if we look at the line we can see it:

Infinite loop in the code

Debugging Stack Overflows

In most cases, you’ll only get a stack overflow if you have infinite recursion. We won’t really use recursion in this class (with maybe one exception), but if you get a stack overflow, the program will crash in the debugger and the hallmark is that the call stack will show lots of entries of the same function at the top:

Stack overflow on the callstack

In the above case, we can see that the UpdateGame function appears to the the culprit. If we view the code in question, we can clearly see there’s infinite recursion, and in this case, the IDE even helpfully warns us:

Infinite recursion warning

Data Breakpoints

Data breakpoints are a slightly more advanced debugging feature, but they are extremely powerful.

Let’s say my mShouldContinue variable in Game is mysteriously changing, and I’m not really sure why. A data breakpoint tells the debugger to pause when a specific variable changes.

To setup a data breakpoint, you should first put a breakpoint somewhere where the variable you want to watch is in scope. For this example, you could put a breakpoint in Game::Initialize since you know mShouldContinue would be in scope there.

Then, when you hit the breakpoint, in the debug pane, right click on the variable in question and select “Add Data Breakpoint”:

Add Data Breakpoint

This will bring up a small box. In this box, make sure you change the “Life Time” to “Persistent” and then click “Done”:

Set Life Time to "Persistent"

Now you can “Resume Program” and when the variable changes, the debugger will pause where it happened, highlighting the variable:

Data breakpoint hit!