The debugger

What is a debugger

A debugger is a program that can control the execution of an other program in order to track down the bugs inside it. A debugger can only be executed on a program compiled in debug mode, otherwise, the information it needs is missing from the executable file (release mode).

With a debugger, we can, for example, retrieve the location (the file and line) of the instruction where the program crashed. We can also make the execution of the program stop at specific places in the code and make it execute the instructions one-by-one. When doing so, we can look at the value of the variables, in real time.

The cause of crashes

What makes a program crash? A program crashes when the operating system decides to terminate (kill) its execution. When does the operating system does that? When a program tries to access a memory space it is not allowed to, or when it becomes unresponsive (Example: It entered an infinite loop). The first one is the usual cause of a program crashing.

So, if we directly access a memory space that does not belong to the program, it will crash:

// Defines a pointer pointing to an unknown memory location. int *ptr; // Accessing the pointed memory space; *ptr = 34; // Crashes.

Even if we do not directly access a memory space outside the range assigned to the program, it may still crash if, for example, we call a program that does it or if a stack overflow happens.

The basics of the debugger

First, we will see the basics of the debugger and then, in the next pages, the debugging interface of some IDEs will be presented.

When we launch the debugger (Without breakpoints), it executes the instructions of the program and stops its execution if the program crashes. If the program finishes its execution without crashing, the debugger closes with success.

Here is an example, with Code::Blocks, were the debugger stops at the line the program crashes:

The debugger stopped and indicated (With a yellow triangle) that the program crashed when it executed the instruction at the line number 5. We now know where the program crashed. In the example above, the location where the program crashed is pretty obvious, but this is not always the case.

When the debugger stops, we can see the value of the variables, at the moment it stopped. Here is a window from Code::Blocks showing the value of the variable ptr when the program crashed:

From the window, we can see that the value (memory address) stored in the pointer ptr was 0x10 (16 in decimal). The variable is classified under the category Locals, because it is a local variable of the function inside which the program crashed (the main function).

Note that sometimes, when the program crashes while executing a function from an external library, the debugger may not be able to find where the program crashed.

Breakpoints

A breakpoint is a symbol that marks a line such that when reached, it causes the debugger to stop. Here, the line number 3 contains a breakpoint:

When the debugger is launched:

Note that the debugger stops before the line is executed. Now, we make the debugger execute the next line of code:

We make the debugger execute the next line of code:

Next line:

Step into and step out

When we encounter a function call, we can either execute the line of code (Execute the function call completely) or 'Step into' the function and execute its instructions one-by-one. Inside a function, we can 'Step out' to execute all the following instructions of the function at once.

Launch the debugger:

Execute the next line of code:

Step into the function call:

Execute the next line of code:

Now, we step out the function to execute all its remaining lines of code at once.

Make the debugger continue

Note that at any moment in the debugging process, we can make the debugger continue and it will proceed the execution of the program until it crashes or, an other breakpoint or the end of the program is reached.

Start the debugger:

Make the debugger continue:

Make the debugger stop at the cursor

Also, it is possible to make the debugger stop at the position of the cursor, instead of a breakpoint.

Make the debugger 'Run to the cursor':

The function call stack

Using the debugger, it is possible to see, in real time, the function call stack. We can then see, in order, the functions that have been called, but that have not returned yet.

Launch the debugger:

In the call stack window, we can see the function main that has been called at the start of the program.

Step into:

From the call stack window, we can see that the function funcC is the last function that has been called without returning.

Step into:

Step into:

Step out:

Step out: