Step 1 - Finding the problem (Part 3)

Workshop Resources

Debuggers

Debugging code is such a widespread, common problem that people have built entire programs designed to help other programmers debug more efficiently. These are suitably named debuggers, and there are plenty of debuggers that work for the C programming language. Let’s take a look at gdb, a common debugger used with the command line.

Launch Replit

For our examples, we will make use of the Quicksort algorithm.

Quicksort is an algorithm that sorts an array by first selecting an element in the array as a pivot.

Next, the elements are organized based on one of the following conditions:

When the sorting is done, the same process is recursively called in the upper and lower partitions of the array, taking the pivot as the middle point.

Our version of quicksort assumes the leftmost element is the “greater” element and the rightmost element is the pivot within the partition.

Quicksort using pivot as the rightmost element.
Figure 1: Quicksort using the rightmost element as the pivot and assuming the leftmost element is the “greater” element.

GDB (GNU Project Debugger) is a powerful debugger that lets you debug programs from the command line, which is useful in cases where you don’t have access to a GUI (Graphical User Interface).

It is important to understand how the program works to do effective debugging. Our ‘quicksort’ implementation runs a recursive implementation of quicksort and performs the sorting if the current element is less than the pivot, and assuming the first element is the “greater” element. The sorting itself occurs in the partition function.

Steps to debugging with GDB

Compile the program

  1. Open the Shell tab and compile the program:
make Quicksort

When the program is compiled, the flags: -g and -Og are used. The first one tells the compiler to add debugging information, meaning that without that flag, gdb would not be able to debug the program. The second tells the compiler to optimize the program in a way that wouldn’t affect the program’s run structure. Without -Og, the compiler could potentially optimize away some of your code, so a debugger would not be nearly as effective.

It is important to keep note of the second flag. For debugging, you should ALWAYS make sure the compiler makes minimal optimizations to your code because optimizations could drastically change how the code is run!

Run the program

  1. Type the command gdb examples/Quicksort. This will open the GDB command line interface. To debug a program with gdb you can use gdb <name of program>.

  2. Make sure that GDB says that it is Reading symbols from ./examples/Quicksort..., otherwise you didn’t attach the program to GDB.

  3. You can exit GDB by entering the command quit (or any of its prefixes: q works) as if it were the normal shell command line.

You should see something like this:

Running GDB on ‘Quicksort’ file.
Figure 2: Running GDB on ‘Quicksort’ file.

Debug the program

To debug the program, we need to run it from GDB.

  1. Enter the command run (or r). This will execute the program as if you’d run it from the normal command line.
(gdb) run

The program first prints the contents of the array to be sorted: an array of unordered numbers. Next, it runs the sort algorithm, and finally, prints out the array sorted array. You can see how the array is shifted during the sort!

However, it seems that the sorting is not happening as intended.

Let’s use one of the most important tools debuggers offer: breakpoints. A breakpoint tells the debugger to pause the program whenever it reaches that line of code as it is executing. This enables you to take a look at what is happening within the program in real-time. Note that the debugger does not run the line of code the breakpoint is on until after you continue executing the program.

First let’s identify where all of the element sorting takes place. Do you know where the sorting is happening?

Answer

  1. Place a breakpoint where the sorting takes place using the syntax break <filename:line>.
Answer

  1. Run the program with run and observe how the program halts when it reaches the breakpoint.

  2. While the program is paused, you can see the values of variables. Test out some print statements to evaluate expressions. Here’s an example of how to use print statements:

# you can print out the value of a variable
print myVariable

# you can create more elaborate print statements by using entire expressions
print myVariable + 2 
  1. You can manually step through your code by calling next (or n), which will tell the debugger to advance to the next line of code without jumping into a function call.

  2. The step command will move the debugger to the first line of code in a function call.

You need to find the faulty variables that cause your program to not work as expected. Try and see if you can spot the problem!

Hint: Keep an eye on how the variables in the loop change.
Answer

Note that all debuggers should have the same basic concepts: they allow you to step through your code as it runs, in real-time. You should almost always use debuggers over print statements. You won’t regret it!

GDB Commands

There are plenty of commands GDB offers. This table summarizes some useful ones, but we also recommend using this GDB Reference Sheet to see more commands you can use.

CommandWhat It Does
gdb <name of program>Runs/loads the program with GDB
run or rRuns the loaded executable program
quit or qExits GDB
break <filename:line>Create a breakpoint at the specified file and line number
print <variable name> or print <expression>Print the value of an expression which can contain variable names and constants
next or nAdvance to the next line of code without jumping into a function call
stepMove to the first line of code in a function call
continue or cContinue running your code until the next breakpoint
delete <breakpoint #> or d <breakpoint #>Delete a breakpoint at line number #
backtrace or btPrint a stack trace, or which functions were called to get to the line of code the program is halted on