GDB: Digging Deep with the GNU Debugger

One of the things I like the most about Ruby is the availability of gems that allow runtime debugging (i.e. pry). With all of my past projects before, in school and in work, let it be PHP or C, to debug a certain hard-to-catch error, I had to do a lot of test prints after every block of code to check what the states and values of each of my variables were and to also double check that the program reached a certain part.

Upon reading on gdb, I got very excited and glad that there actually is a runtime debugger for C and C++ (*flashback to all my undergrad C machine problems and C++ thesis*)!!!

In this blog post, we’ll be having an overview of what GDB is and some of its basic capabilities.

gdb or the GNU Debugger is a debugger tool for several languages (including C, C++, Fortran, etc) which can be used to inspect what the program is doing at certain points of execution. It is by default already installed in most operating systems (tried on OSX Yosemite and Ubuntu 14.04).

You can try to see if it already exists by doing a basic help command with gdb:

gdb -help

But in case your system does not have it yet, you may follow these  installation instructions. Once gdb is installed in your system, we can now start gdb-ugging!

  1. Let’s first start gdb on our command line by typing:
    gdb

    Screen Shot 2015-12-19 at 9.44.11 PM.png

  2. Now that our gdb console is ready, we can load the file that we want to inspect. I have this following file: prog.c, that basically just performs basic arithmetic using the four basic math operations.
    
    #include<stdio.h>;
    
    int add(int x, int y) {
     return x + y;
    }
    
    int subtract(int x, int y) {
     return x - y;
    }
    
    int multiply(int x, int y) {
     return x * y;
    }
    
    double divide(int x, int y) {
     return (x * 1.0) / y;
    }
    
    int main() {
     int x, y;
    
    printf("Enter first number: ");
     scanf("%d", &x);
    
    printf("Enter second number: ");
     scanf("%d", &y);
    
    printf("Sum is: %d\n", add(x,y));
     printf("Difference is: %d\n", subtract(x,y));
     printf("Product is: %d\n", multiply(x,y));
     printf("Quotient is: %f\n", divide(x,y));
    
    }
    

    We save it and compile it with prog as the name of our output file.

    gcc -g prog.c -o prog

    You may notice the -g flag, this enables built-in debugging support which our gdb program will need to execute some of the commands.

    Let’s just try to run prog in our console just to make sure that it works and see the results.

    Screen Shot 2015-12-19 at 8.39.03 PM.png

    Now that we saw that our program runs fine, let’s now load  it inside gdb.

    file prog

    Take note that what we are loading into our gdb is the executable file and not the uncompiled source code.

    Screen Shot 2015-12-19 at 8.37.56 PM.png

  3. Once our file is loaded, let’s execute it in our gdb by issuing the run command:
    run

    Screen Shot 2015-12-19 at 8.41.00 PM.png

    And there, our files ran without any errors and it also produced the same results as with our console given the same pair of input.

  4. Let’s say our file encountered a segmentation fault somewhere. Let’s We can force a segmentation fault by adding the following lines near the bottom before we close the main() function:
    
    printf("\nForcing a segmentation fault: \n");
    *(char *)0 = 0;
    
    

    Let’s try to see if this indeed results to a segmentation fault in our command line. You may notice that a warning was given when we compiled our program. Since this is just a warning, we can proceed as our code was still compiled. Running ./prog, we now encounter a segmentation fault.

    Screen Shot 2015-12-19 at 8.43.30 PM.png

    Now let us load and run our file in gdb,

    Screen Shot 2015-12-19 at 9.56.06 PM.png

    gdb also encountered the same segmentation fault but this time, we are given more information (i.e. what line did the segfault occur at). These additional information could truly be useful in debugging and tracing errors in programs.

Now that we have seen a basic use case for gdb, let’s delve in deeper with breakpoints and watchpoints.

Breakpoints

Breakpoints are parts that you can assign at which execution will stop for a while to allow inspection of the states and values of variables at that point of execution. If the program halted or resulted to a segmentation fault before a certain breakpoint, the breakpoint won’t be reached anymore.

Setting break points is easy. You can either set break points via specifying the specific line where you want execution to pause:

break prog.c:31

Note: Be sure to compile your C program with the -g flag to enable  built in debugging utilities. Also, if you made any changes to your file, be sure to recompile it again and reload it in gdb.

Let’s now set our break point at line 31, right before the sum of the two numbers are printed.

vv.png

What we first did was to load the program in (1) and then proceeded to define a breakpoint at (2) at line 31. And then we proceed with the program’s execution inside gdb, and once we encounter line 31, gdb notifies us of a breakpoint at (3). At that breakpoint, we can also issue commands to inspect the value of variables:

print x
print y

We could also specify breakpoints at functions. SO every time the function is called, a breakpoint is reached:

break divide

This adds a breakpoint every time our divide() function is called.

While on breakpoints, it is also possible for you to alter the variable values:

in (3)
set variable x = 100
set variable y = 80

ee.png

What we did above was to first enter the initial values for x and y as requested by scanf in (1). And then since we set our breakpoint at divide(), a breakpoint was reached once divide() wascalled at (2). We first printed the current values for x and y at (2) and they are 10 and 2 respectively.

In (3), we then proceeded to alter the value’s variables – setting x and y to 100 and 80 respectively and then issued the continue command. The continue command is used to exit from the breakpoint and continue execution of the program. We see in (4) that indeed our changes to the values of x and y took effect as instead of 5 (10 / 2), 1.25 (100 / 80) appeared as the quotient.

 

*Conditional Breakpoints

Breakpoints can also only apply if a certain condition is met. For this purpose, gdb provides conditional breakpoints. The if condition is just appended to the break statement form that we saw a while ago.

break x:20 if y == 0

*Clearing Defined Breakpoints

If in case we want to reset our breakpoints, we just issue the clear commands and our breakpoints will be reset.

clear

Screen Shot 2015-12-19 at 9.18.34 PM.png

Watchpoints

In contrast to breakpoints, gdb also provide watchpoints to watch variables. Every time the value of the watched variable changes, the program temporarily stops to print the old and new values. On that temporary stop as well, you will enter a breakpoint-like console where you can inspect variable states and even change some of their values.

Let’s say we change our main() function to be the following. There are occasional changes to the value of x and y along the execution of the program (at some parts they were multiplied by 2 and 3 and then increased by 5). We will see if the execution will indeed be interrupted by the watch points every time x and/or will change values.


int main() {
 int x, y;

printf("Enter first number: ");
 scanf("%d", &x);

printf("Enter second number: ");
 scanf("%d", &y);

printf("Sum is: %d\n", add(x,y));
 printf("Difference is: %d\n", subtract(x,y));

 x = x * 2;
 y = y * 3;

printf("Product is: %d\n", multiply(x,y));

x = x + 5;
 y = y + 5;

 printf("Quotient is: %f\n", divide(x,y));

}

In order for us to watch variables, the variables that we are interested in should be in context so we should set the watch points while the program is in execution. For this, we first set a breakpoint on our main function (1) so that as soon as our program starts, we can assign watch points.

ff.png

Once main is executed our breakpoint is reached (2). We then tell gdb to watch x and y (3). Issuing the continue command would now continue the execution of the program. It asks for the first and second numbers to which we respond:

Screen Shot 2015-12-19 at 9.29.38 PM.png

Remember that assigning values to our variables changes their values so our watchpoints were activated. The old and new values were displayed as well. Similar with breakpoints, we can just issue the continue command to leave watch point and continue execution.

And when our program reached one of lines that mutate x’s and y’s value:

line 37: y = y * 3

Screen Shot 2015-12-19 at 9.30.14 PM.png

It then again printed the old and new values of y. Again similar to breakpoints, we can alter variable values. In this case, we set x = 100 and it took effect as product is already 900.

So there! In this blog post, we were able to see some basic use cases for gdb and how we can use it to debug our programs.

Thanks for reading! ‘Til next time! 🙂

Sources:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s