Application Development with Qt Creator(Second Edition)
上QQ阅读APP看书,第一时间看更新

Getting lost and found again – debugging

Qt Creator has a state-of-the-art GUI that hooks into either the GNU debugger GDB, or Microsoft's command-line debugger CDB if you use Microsoft tools.

If you've installed Qt Creator on Mac OS or Linux or the MinGW version of Qt Creator for Windows, you have everything you need to begin debugging your application. If you already had Microsoft Visual Studio installed and then installed a version of Qt Creator that uses Microsoft's compiler, you also need to install the Microsoft command-line debugger to use Qt Creator's debugging features. Here's how you can install the command-line debugger:

  1. Download the debugging tools for Windows, either from http://msdn.microsoft.com/en-us/windows/hardware/hh852365 if you are using the 32-bit version of the compiler and Qt Creator, or from http://msdn.microsoft.com/en-us/windows/hardware/hh852365 for the 64-bit version of the compiler and Qt Creator.
  2. Configure the debugging symbol server by going to Options under the Tools menu, selecting the Debugger item on the left-hand side, clicking on the CDB Paths pane, and edit the textbox next to the Symbol Paths line.

Tip

Usually, the debugger works out of the box with Qt Creator, unless you're using the Microsoft tool chain. However, if you encounter problems, consult the Qt documentation about setting up the debugger at http://qt-project.org/doc/qt-5/debug.html.

The following screenshot shows the debugger in action with our test project, stopped at a breakpoint. Let's take a look at the following screenshot in detail to get oriented:

In the screenshot, you'll see the following components:

  • On the left-hand side is the usual row of buttons to pick a view in Qt Creator
  • Next to the buttons is the view of the project files and the list of open documents
  • In the main editor pane, every source line has a clickable indicator to let you set and clear breakpoints
  • The call stack, indicating how the program got to the line execution is stopped at, is shown in the pane below the editor pane
  • In the upper-right corner is the variable inspector, where you can see the values of the variables in the current stack frame, along with any global variables
  • Below the variable inspector is a list of pending breakpoints, so you can turn on and turn off breakpoints without needing to hunt through the code

To generate the screen that you can see in the preceding screenshot, I clicked to the left of line 6 placing a breakpoint and then clicked the Debug button on the left after ensuring I'd specified a Debug build in the build selector. Qt Creator built the application in the Debug mode, started the application, and let it run to the breakpoint on line 6.

Setting breakpoints and stepping through your program

A breakpoint, if you haven't encountered the idea before, is just that—a point at which execution breaks and you can examine the program's state. Once the execution is stopped at a breakpoint, you can step into a function or step over a line, executing your program one line at a time to see how it's behaving. Clicking to the left-hand side of a line number in the Debug view lets you set or clear breakpoints. While stopped at a breakpoint, a yellow-colored arrow in the margin of the editor pane indicates the line of code that the processor is about to execute.

While at a breakpoint, several buttons appear above the call stack pane that let you control the program flow, which you can see in the following screenshot:

The buttons are defined as follows:

  • The green-colored Continue button, which continues execution at the line indicated by the arrow. You can also continue by pressing the F5 function key.
  • The red-colored Stop button, which stops debugging altogether.
  • The Step Over button, which executes the current line and advances to the next line before stopping again. You can step over one line by pressing F10.
  • The Step Into button, which enters the next function to be called and stops again. You can step into a function by pressing F11.
  • The Step Out button, which runs the remainder of the function in the current calling context before stopping again. You can step out of the current function by pressing Shift + F11.
  • The instruction-wise button (looks like a little screen), which toggles the debugger between working a source line at a time and an assembly line at a time.
  • There's also a menu of threads, so you can see which thread is running or has stopped.

For example, (in the previous screenshot) from line 7 if we step over line 8 (pressing F10) and then press F11, we'll end up inside our factorial function. At this point, if we step into the function again, we'll see the value for n change in the right-hand side column, and the arrow advance to point toward line 9 (again, as numbered in the screenshot). From here, we can debug the factorial function in several ways:

  • We can examine the contents of a variable by looking at it in the right-hand side pane. If it's in a stack frame above the current calling frame, we can change call frames and see variables in a different call frame too.
  • We can modify a variable by clicking on its value and entering a new value.
  • With some debuggers, we can move the arrow to different lines in the calling function to skip one or more lines of code or rewind the execution to rerun a segment of code again.

This last feature—which unfortunately doesn't work with the Microsoft Command-Line Debugger—is especially powerful, because we can step through a program, observe an error, modify variables to work around the course of the error, and continue testing the code without needing to recompile it and rerun the executable. Or, I can skip a bit of the code that I know takes a while to run by substituting the new state in the variables in question and continuing from a new location in the current call frame.

Also, there are a number of other things that we can do, from how we debug the application to various ways in which we can view the state of the application when it's running. From the main Debug menu, we can do the following:

  • Detach the debugger from a running process by selecting Detach from the Debug menu (this is handy if the debugger is slowing things down and we know that part of our code doesn't need to be debugged).
  • Interrupt the program execution by stopping the execution and examining the current state by choosing Interrupt from the Debug menu (useful if our application seems caught in a long loop we weren't expecting and appears hung).
  • While it is stopped, run to the line that the cursor is on by choosing Run to Line or pressing Ctrl + F10.
  • While it is stopped, skip to the line that the cursor is on by choosing Jump to Line. Choosing Jump to Line lets you skip the lines of code between the current point and the target line.

Examining variables and memory

The variables pane shows you the values of all the variables in the current stack frame. Structures show the values of their members, so you can walk through complex data structures as well. From the variables pane, you can also copy a variable name and value to the clipboard or just a variable value.

In the variables pane, there's a really useful feature called the Expression Evaluator, which lets you construct algebraic expressions for the variables in your code and see the results. For example, if I'm stopped at the beginning of the factorial function with n set to 6, I can right-click on the variables pane, choose Insert New Expression Evaluator, and type in a formula such as n*(n-1) in the dialog that appears. Thus, a new line appears in the pane showing the expression and the value 30. While this is a pretty contrived example, I can view pointer values and pointer dereferences as well.

I can also conditionally break the execution when a variable changes; this is called a conditional breakpoint or a data breakpoint. For example, let's put a loop in our main function and break as we execute the loop. To do this, first change main to read like the following block of code:

#include <QCoreApplication>
#include <QDebug>
#include "mathfunctions.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    int values[] = { 6, 7, 8 };

    for(unsigned int i = 0; i < sizeof(values)/sizeof(int); i++)
    {
        qDebug() << values[i]
                 << "! = "
                 << MathFunctions::factorial(values[i]);
    }
    return a.exec();
}

This will walk the values stored in the integer array values and print the computed factorial of each value. Start debugging again, and let's add a data breakpoint on i. To do this, perform the following steps:

  1. Put a breakpoint on the first line of main, the line initializing QCoreApplication.
  2. Step over until the for loop, then right-click on i in the right pane, and choose Add Data Breakpoint at Object's Address from the Add Data Breakpoint submenu.
  3. Continue by pressing F5 or the Continue button.

The execution will stop at line 11, the beginning of the for loop, when i is set to 0. Each time I hit F5 to continue, the application runs until the value of i changes as a result of the i++ statement at the end of the for loop.

You can also inspect and change the individual values of arrays in the variable inspector by clicking on the expansion arrow next to the array name in the variable inspector pane.

In addition to viewing and changing variable values, you can view and change individual memory locations. You might want to do this if you're debugging a decoder or encoder for a binary format, for example, where you need to see a specific location in the memory. From the variables pane, you have several choices by which you can check a memory location; a few of them are given as follows:

  • You can right-click on a given variable and open a memory window at this variable's address
  • You can right-click on a given variable and open a memory window at the value that the variable points to (in other words, dereference a pointer to a memory location)
  • You can right-click on the variable pane and open up a memory browser at the beginning of the current stack frame
  • You can right-click on the variable pane and open up a memory browser at an arbitrary location in the memory

The following screenshot shows the memory viewer showing the memory that contains the array values:

The window shows the memory addresses on the left-hand side, the values of the memory in 16 bytes to a line (first in hexadecimal and then in ASCII), and colors the actual variable you've selected to open the window. You can select a range of values and then right-click on them to do the following:

  • Copy the values in ASCII or hexadecimal
  • Set a data breakpoint on the memory location you've selected
  • Transfer the execution to the address you've clicked (probably not what you want to do if you're viewing data!)

Examining the call stack

The call stack is the hierarchy of function calls in your application's execution at a point in time. Although the actual flow varies; typically in your code it begins in main, although what calls main differs from platform to platform. An obvious use for the call stack is to provide context when you click on the Interrupt button; if your program is just off contemplating its navel in a loop somewhere, clicking on Interrupt and taking a look at the call stack can give you a clue as to what's going on.

Remember how I defined the factorial function in terms of itself? You can see this very clearly if you put a breakpoint in the factorial and call it and then continue through the breakpoint a few times before looking at the call stack; you'll see something akin to the following screenshot:

Working from left to right, the fields in the call stack window are the stack levels (numbering starts from the top of the stack and moves down), the functions being invoked, the files that the function is defined in, and the line numbers of the function currently being executed. So, this stack frame says that we're on line 9 of MathFunctions::factorial in mathfunctions.cpp, called by line 13 of MathFunctions::factorial, which is called by line 13 of MathFunctions::factorial… and so on, until it bottoms out in our main function and the system startup code that the operating system uses to set up the application process before that.

If you right-click on a line of the call stack pane, you can perform the following actions:

  • Reload the stack, in case the display appears corrupted
  • Copy the contents of the call stack to the clipboard—great for bug reports; if your application throws an exception or crashes in the debugger, you can copy the call stack and send it off to the developer responsible for that part of the code (or keep it for yourself as a souvenir)
  • Open the memory editor at the address of the instruction at the line of code indicated by the function call in the call stack
  • Open the disassembler at the address of the instruction at the line of code indicated by the function call in the call stack
  • Disassemble a region of the memory or the current function
  • Show the program's counter address in the call stack window while debugging