Lab: Using the Code::Blocks Debugger

Steven Zeil

Last modified: Jul 22, 2016
Contents:

In this lab, we will look at the basic use of an automated debugger. Later lessons will cover overall strategies for debugging, but in this lab we will look at the mechanics of using supporting tools.

The program we will work with computes some basic statistics on plain text.

Enter some text, terminated by an empty line:
  In this lab, we will look at the basic use of an automated debugger.
  The program we will work with computes some basic statistics
  on plain text.

There are 27 words in 2 sentences.
The average word length is 4.3 characters.

1 Setup

  1. Start Code::Blocks. Create a new Console Application project containing the files words.h, words.cpp), and textstats.cpp.

2 Finding the Location of a Crash

Now, let’s pretend that we were in the process of developing this program and had not yet worked out all the bugs.

  1. First, we’ll need a bug. In the function body for initializeWords, change the initialization of the words variable to:
    words = NULL; // new string[MaxWords];  
    

    Recompile, and run the program again. The program will crash as soon as you finish entering the first line of input. (If you get a Windows pop-up box saying “A problem caused the program to stop working correctly” and offering a choice of buttons “Debug” and “Close Program”, choose “Close Program”. The “Debug” option it is offering will launch a different debugger that is not the one we want.)

    Now, unfortunately this is all too typical a scenario during program development. Our code has crashed and we have no idea why. (OK, this time we know it’s because of the change we made to initializeWords, but play along with me here. Normally, we would not know where the problem was.)

You won’t be able to go to the code for the function operator>>, because that’s actually part of the C++ std library, but you can see the portions of your own code that were directly involved in the crash. We now know exactly where the crash occurred.

3 Debugger Commands

There’s a relatively common set of commands that most automated debuggers will provide:

Running and Stopping
Expect a debugger to provide commands to run your program and to stop it. In addition, debuggers allow you to set , locations in the source code where the debugger will pause the program execution. The debugger will have some sort of “continue” command to restart execution that has paused at a breakpoint. (This is usually different from the main “run” command, which restarts the program from its beginning.)
Stepping
Once the program is paused, instead of simply using “continue” to start it moving again, you can step through the code a statement or so at a time. There are 3 common variants on this idea:
  • Step “in”: If the current statement calls any functions, step into body of the function being called. Otherwise step to the next statement in this function body.
  • Step “over”: Regardless of whether or not the current statement calls any functions, step to the next statement in this function body.
  • Finish: run to the end of this function, stopping once it returns back to its caller.
Examining the State
Most debuggers have a way that you can type in a variable name (or sometimes an expression involving one or more variables), after which the debugger will show you the value of that variable (or expression) at the current moment in time for a paused program. Then you can step forward, repeat the process, and see if the value has changed.

Many debuggers will present you with a list of all the local variables declared in the current function, allowing you to simply select them to see their values. You may have to fall back on the first, more general, approach to see global or static variable values.

Some debuggers allow you to put variables into a list of “watched” variables, in which case that variable will be printed each time the program pauses.

Besides displaying variables, most debuggers can provide you with other important information about the program state. In particular, they allow you to move up and down in the call stack. We’ve already seen this with the Call Stack window that popped up in response to our request for a backtrace after a crash.

3.1 Observing Variables

  1. Using the Call Stack window, go to the line in addText associated with our crash.

3.2 Setting Breakpoints

A breakpoint is a location in the source code where the debugger will pause the program execution.

  1. Set a breakpoint by clicking, just to the right of the line number, on the first line of code in the function addText. A small red circle should appear to show that the breakpoint has been set. (You can remove a breakpoint by clicking on this circle. ).

Breakpoints give you an opportunity to decide where in the code you would like to pause execution and to start observing the call stack and program variables.

3.3 Stepping Through Code

Sometimes, after stopping execution at a breakpoint, we want to move forward just a little bit and observe the changes made to our variables.

We could do this by setting another breakpoint a line or two away, but the total number of breakpoints can get unwieldy.

Debuggers provide commands for moving forward by different amounts. We have already used one of these - Continue - which moves us forward to the next breakpoint (or until the program crashes.

The other commands you will want to know, which can be accessed from the Debug menu or via buttons in the debugging toolbar, are “Next Line”, “Step Into”, and “Step Out”.

“Next Line” and “Step Into” are similar in that each tries to move you forward one “step” of code. They differ, however, in what happens if the current line of code contains a function call. “Next Line” executes the function call without stopping (unless it hits a breakpoint) and stops at the next line of code within the same function where you started. “Step Into”, on the other hand, starts the function call executing but stops at the first line of code inside the called function.

“Step Out” is the logical opposite of “Step Into”. “Step Out” will try to execute the rest of the current function, and will stop in the caller of that function, just after the point at which the current function was called. (One of the most common uses of “Step Out” is to get back to where you were if you do a “Step Into” and then realize that you wish you had done a “Next Line” instead.

  1. Make sure your breakpoints are still set. Restart the program from the debugger menu. Have the Call Stack and Watches windows open and showing the local variables.

Debuggers are powerful tools. They can help a lot. But you want to be smart about their use as well. They can become a tremendous time sink if you simply go single-stepping through all of your code hoping to notice when things go wrong. You need to think about likely causes for the symptoms you have observed, and set your breakpoints wisely to investigate possible causes for those symptoms.