Skip to content

Instantly share code, notes, and snippets.

@ocelotsloth
Created November 15, 2016 21:57
Show Gist options
  • Save ocelotsloth/f426e5a78ca127f7d9a5efd25d40352c to your computer and use it in GitHub Desktop.
Save ocelotsloth/f426e5a78ca127f7d9a5efd25d40352c to your computer and use it in GitHub Desktop.
CS222 HW8 Assignment Sheet

Homework Assignment 8 - Debugging Code with GDB

Debugging computer source code can be a rewarding and frustrating experience. Since source code is becoming much more complex, many tools and techniques to assist in debugging have been developed. It is helpful to be aware of these tools and techniques so that you can use them while working on your projects and labs. With them, you can become successful in submitting correct, well-written source code. One of the tools, which integrates visualization, is an interactive website at: http://www.pythontutor.com/c.html#mode=edit. A lot of us have been using it already. It is not fully tested, but quite impressive.

Debuggers

A debugger is a program that allows you to see what is going on while a different program is executing. Most debuggers allow programmers to set breakpoints in their code (places where execution is paused) and examine the state of variables while the program is paused. Other features include the ability to set conditional breakpoints which will pause execution at the point where the value of a variable is changed or set to a specific value. The ability to see what is happening is very handy when you are trying to track down problems inside your code.

There are many different debuggers available on many different systems. They can be divided into two types:

  1. Command Line Interface (CLI) - These debuggers use commands typed on a keyboard to operate. Common debuggers of this type are DBX and GDB.
  2. Graphical User Interface - These debuggers are often GUI wrappers over a Command Line Interface debugger. They allow programmers to use a mouse to set breakpoints, run code, and inspect variables. Certain GUI debuggers are part of a larger Integrated Desktop Environment (IDE). Examples of popular IDEs are Eclipse and Microsoft Visual Studio. The C tutor (http://www.pythontutor.com/c.html#mode=edit) belongs to this category.

GDB

This lab will introduce you to one of the more popular CLI debuggers, GDB. GDB is the GNU Project debugger, and is part of the overall GNU consortium, the same group that maintains GCC, the GNU Project C Compiler. More information about GDB can be found at its project page: (http://www.gnu.org/software/gdb/) and on the online documentation "Debugging with GDB" found at: (http://sourceware.org/gdb/download/onlinedocs/gdb/index.html)

GDB is very powerful and allows you to do many useful things to examine your code. However, this assignment will concentrate on setting breakpoints and inspecting variables, along with an introduction to finding the source of segmentation faults (a sometimes tricky issue). You will practice using gdb by debugging an existing program. The source code for this program, Debug2.c, can be found with the materials for this lab.

Compiling

In order to use gdb to debug an executable, you must compile the program with "debugging options" turned on. The most common option is the -g option. Thus, to compile Debug2.c, you will use the following command:

gcc -g -o Debug2 Debug2.c

The -g option will add debugging information to your executable. This will make your program size larger, and could potentially cause your program to run slower. To see this, use the following command to compile Debug2.c:

gcc -o Debug2 Debug2.c

Now check the size of the Debug2 executable with the ls -l command:

ls -l Debug2

Write the size (in bytes) of the executable _________ (Note: The size in bytes is the number immediately before the date in the output from the ls -l command)

Now, recompile using the -g command line option and write the size in bytes of the new executable: _________

There are many options that can be used to generate debugging information in your code. The man page for gcc will list many of them. For now, just stick with the -g option.

Starting GDB

To start GDB, the easiest way is to type gdb at the command line. Do so now. You should see something similar to:

bash $ gdb
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>.
(gdb)

What you actually see depends upon what type of computer you run the command on, and the particular version of gdb installed. Copy the first line that is printed when you run gdb: _______________

Now, copy the portion in the quotes on the line that states This GDB was configured as. For instance, in the example above, you would copy i686-linux-gnu: _______________

Getting Help

GDB has internal help. You can get a list of help topics by typing help at the (gdb) prompt. Do so now, and count the number of topics shown for List of classes of commands:

Write the number of topics/classes: _______________

To get help on a specific topic, you type help <topic name> So, to get help on breakpoints, you type help breakpoints. Try it now. Get help on breakpoints. You should see a list of all the commands you can use regarding breakpoints.

As you can see, there are many different commands you can use regarding setting and using breakpoints. We will discuss some of the more common commands later.

The help command can also give you additional information on various commands. For instance the break command is one you will likely use many times in your programming career. Try it now - type help break to get specific information:

Write the first line printed after the executing the command here:

_______________

Quitting GDB

Just as important as starting GDB, you will need to know how to quit GDB. This is done with the quit command. Type quit <return> at the (gdb) prompt now.

Note on GDB Commands

Many gdb commands have shortcuts so you won't have to type the complete command. For instance, instead of typing quit to exit GDB, you can type q to have the same result. Or, for the break command, you can just type b.

Using GDB to Debug a Program

Now that you know how to start and quit GDB, let's debug the Debug2 program you compiled earlier. Make sure you are using the version you compiled with the -g option. Type the following command to start GDB:

bash$ gdb Debug2

GDB will start, and will automatically load your program so that it can be run within GDB. You should see a line such as the following as GDB loads your program:

Reading symbols from /home/user/DebugLab/Debug2...done.

While debugging your code with GDB, you may find errors and wish to recompile and reload your program without exiting GDB. You load a program into GDB with the file command:

(gdb) file Debug2

GDB will prompt you to make sure you wish to Load new symbol table ... You can also use the file command to load a program into GDB if you started it without specifying a file on the Unix command line. However, most of the time, GDB will realize that you recompiled your code and automatically load the latest version. That way, when you start your program, you are using the most recent update.

Running a Program

You run your program using the run command:

(gdb) run

Try it now. What happened? ________________

Your program takes command line arguments. However, no arguments were used when executing the run command. If you are debugging a program with command line arguments, you simply add them to the run command:

(gdb) run 1

Try the run command again, this time adding the command line argument 1.

What happened this time? ________________

Debugging Program Debug2

When option 1 is chosen while running Debug2, it (incorrectly) adds the numbers from 1 to 10 and prints the result. You might be able to inspect the code to see the error and fix it, but let's practice using some typical GDB commands to inspect the code and discover what the problem is. At this point, you will likely find it easier to have three Unix windows open on your computer. One window will have a vim/vi session where you edit the source code, one window will be used for compiling your code after making changes, and the third window will contain your GDB session. Set these windows up before continuing. In vi you can type :set number to turn on the line number. :55 to go to line number 55.

Setting Breakpoints

There are two main ways to set breakpoints in gdb:

  1. Line number
  2. Function

When setting a breakpoint by function you use the syntax:

(gdb) break <function name>

Try to set a breakpoint so that the program pauses execution after entering the DebugOption1() function. The gdb command you will use is:

(gdb) break DebugOption1

You should see gdb give a response similar to:

Breakpoint 1 at 0x804860a: file Debug2.c, line 55.

You can set many breakpoints, so each breakpoint you set is assigned a number. This is so you can toggle breakpoints on or off, or delete them.

Now run the program in gdb using 1 as the command line argument.

At what line number does execution pause? _________________

A Brief Digression

Before going on to step through the program, there are some other useful commands you can do at this point. One is to print the current stack. To do this, you use the command "where." Try this command now. You should see a response similar to:

#0  DebugOption1 () at Debug2.c:55  
#1  0x08048564 in main (argc=2, argv=0xbffff3e4) at Debug2.c:32

The lines above state where each function call occurred up to the point execution halted. As mentioned before (and shown by line #0), you are currently at line 55 in Debug2.c, which is the start of the DebugOption1 function. You can also see (from line #1) that this function was called from the main() function and the call occured at line 32 in the file. You can also move up and down the stack using the up and down functions (go figure). Try typing up at the gdb command line. The general "position" of gdb is now in the main() function. That means you can print values of variables that are contained in main() but perhaps not accessable from DebugOption1(). For example, main() contains a variable named option. According to the source code, if option is equal to 1, it will call the DebugOption1() function. Let's verify the value of option by using the following command:

(gdb) print option

Write what you see as a result: _______________________

Print is one of the commands you will use most often when using gdb. We will discuss its usage a bit more later. For now, go back to the DebugOption1() function by typing the down command.

Stepping Through Code

Besides using GDB to print values of variables and set breakpoints, the next most common GDB usage has to do with stepping through the code line by line. There are several commands that you can use to do this:

  • step
    • step to the next source code line. If the line to be executed is a function call, the function will be entered and the execution will pause.
  • next
    • similar to step, but will skip over a function call before stopping. The function call is still executed, but the program does not step inside the function.
  • continue
    • continue running from the current line until the next breakpoint or the end of the program is reached.
  • finish
    • continue until the current function exits and stop after the return (also called "step out of function").

Most of these commands can be specified simply by using the first few letters in the command. For instance, s can be used for step and n can be used for next.

Notice that gdb skipped line 54 which contained:

int i, j;

The reason it did not stop there is because there is no code to execute on that line. That line only declares variables. Instead, the execution stops at line 55 which contains:

int sum=0;

Because the variable sum is assigned a value which requires an "execution" of code, gdb stops on that line. The variable sum has not yet been assigned. Use the print statement mentioned above to print the current value of sum.

What is its current value? _______________

It is likely that the current value of sum is some large random integer. This is because sum has not yet been initialized. Remember how problematic using uninitialized variables can be? This is an example.

Note: Sometimes the value of sum will be 0, but you can't assume this will always be the case.

Let's try using the step command. If you have been following the directions up to this point, you will be at the beginning of the DebugOption1() function (line 55 in Debug2.c). Keep the window that shows the source code handy. Now type the step command at the gdb prompt.

On what line does the program execution pause? _______________

Now, use the step command to step over the current line.

At what line does the execution pause? ___________________

Print the value of sum again.

What is its value now? _______________

Continue stepping through the program until the following line is reached:

sum +=sum + i;

Print the value of sum: ________________. You can print the value of i as well.

Step and next can take an integer as an optional argument. For instance:

(gdb) step 5

will cause the program to step through five source code lines before pausing. In essence, it is similar to using the step command five times in a row.

Breakpoints Again

Now we will demonstrate the other typical way breakpoints are set - by line number. Note the line number of the current place where execution is paused. To set a breakpoint at a specific line number, simply type break <line number>.

Practice this by typing the following command:

(gdb) b 63

Note we are using the shortened command for break this time. The second breakpoint has now been set. This means that each time the continue command is given, the execution will pause at line 63 until the loop finishes.

Displaying Variables

We want to observe how the value of sum changes as the program runs. We could use the print command each time we reach a breakpoint. However, this can become tedious, especially if there are several variables we wish to monitor. gdb has a command called display. The syntax for this command is display <variable name>.

Type the following command to turn on the display of the variable sum:

(gdb) display sum

This command is similar to print, except that every time execution pauses, the values of all variables that are set to display are printed. To turn off the display of specific variables, use the undisplay <variable>. command.

Now, continue execution several times with the continue (cont) command. Stop when sum is equal to 57.

What is the value of the variable i at this point? _____________

Does it make sense that 0 + 1 + 2 + 3 + 4 + 5 + 6=57? Do you see what the bug is in the program? If you don't see it, start the run of the program from the beginning with the run 1 command and step through the code within this loop carefully. The breakpoints and the display are still set, even though you are restarting the program. You may also wish to display the i variable instead of printing it out each time the program pauses.

Once you see what the bug is, describe it below:

_______________________________________

Re-running After Changes

Fix the bug in your source file window and save your changes. Recompile the executable using the compiling window.

Now, type the command to run the executable (run 1). If you were still in a debugging session, gdb will ask you if you wish to start again from the beginning (y or n). Pick y and hit return. gdb will notice that the executable has changed and will reload the symbol table (i.e. load the new program). Note that the breakpoints will still be present. However, you will have to redisplay any variables you had set to display automatically. Note also that if you added or removed lines of code during editing, your breakpoints may be at different source code lines. If this is the case, you may need to update your breakpoints.

More on Breakpoints

Suppose you have set several breakpoints, but you don't currently remember at what lines they were set. You can use the following command to list them:

(gdb) info breakpoints

You can also disable or enable breakpoints as you are debugging. Disabling a breakpoint leaves the breakpoint present, but execution will not pause when the breakpoint is hit. To disable or enable a breakpoint, you must use the breakpoint number (id). This value can be found with the info breakpoints command. You disable a breakpoint with the disable <breakpoint id> command. A disabled breakpoint is enabled with enable <breakpoint id>.

If you don't need a specific breakpoint for debugging anymore, you can delete it with the delete <breakpoint id> command. For example:

(gdb) delete 1

Intermission

The above narrative discusses several important and useful gdb commands. Before moving on, you may wish to experiment with the commands you have learned, using the given code. The more you experiment, the more comfortable you will be using gdb. And the more comfortable you are, the faster you will be able to find bugs in your code.

There are many, many additional commands that can be used with gdb, and this portion of the assignment only scratched the surface. It is recommended that you use the help command liberally and seek out other gdb tutorials so that you may gain practice with some of the finer points of using gdb.

The next section of this assignment will discuss using gdb to find and debug segmentation faults. Exit your current session of gdb so that you can start fresh with the next section.

Segmentation Faults

As you use pointers more often, you will encounter Segmentation Fault (seg fault) errors. These errors may seem scary, but with gdb, it is usually not difficult to discover the source of the problem.

Generally, seg faults occur when a divide by 0 occurs, and when a NULL pointer is de-referenced. A divide by 0 should be fairly self-explanatory. However, an example of dereferencing a NULL pointer is given below.

typedef struct _debugHw
{
    int i;
    char c;
} debugHw;

Later on in the code:

debugHw *dl=NULL;
dl->i=35;

Note that since *d1 is set to NULL, it has no memory address that it can go to in order to set the variable i in the debugHw structure. This is a serious problem for the computer, so it performs a segmentation fault and ends the program, usually dumping a large core file that takes up disk space. (Core files can be very useful for debugging, but that technique goes far beyond the scope of this assignment).

Finding the Seg Fault

Run the Debug2 code on the Unix command line, but this time use option 2 as a command line argument. You should see a segmentation fault message:

mason> Debug2 2

Segmentation Fault (core dumped)  

Now, load Debug2 into gdb and type the command run 2. Notice that gdb tells you exactly the line of code where the seg fault occurs.

What line is it? ___________________

Before inspecting the actual line of code where the problem occurs, let's look at some of the other variables that did NOT cause the seg fault. Print the variable dl3 using the print command. Note that the entire structure is printed with the values of the internal variables of the structure.

Write the values of i and c: _________________

Now print the value of dl2 using the following command:

(gdb) print dl2

What is its value? ___________________

Note that dl2 is a pointer, so a memory address is printed. To see the contents of the memory pointed to by that address, use the following command:

(gdb) print *dl2

What are the values of i and c? ___________________

Now, let's look at the actual line of code causing the seg fault. Using gdb, print the value of dl1 using the following command:

(gdb) print dl1

What is its value? ___________________

Note that dl1 is a pointer. Now, try to dereference dl1 and print its value using the following command:

(gdb) print *dl1

Write the message given: ___________________________

Because dl1 is set to NULL, it can't dereference memory address 0x0. Therefore a segmentation fault occurs. Not all segmentation faults will be as easy to find as this one, but knowing how to step through code and inspecting pointer variables along the way can help you narrow down where and why such errors occur.

Conclusion

The above narrative discusses several important and useful gdb commands. Using debuggers like gdb can make it much easier to find bugs in your code. The faster you find bugs in your code, the sooner you will complete your assignments. The sooner you complete your assignments, the more your quality of life will improve. See what wonderful things gdb can do for you?

There are many, many additional commands that can be used with gdb, and this assignment only scratched the surface. If you wish to learn more about gdb, use the help command liberally and seek out other gdb tutorials to gain practice with some of the finer points.

Submission

Copy your answers from this document to the more streamlined "Answer Sheet" attached to this assignment. Submit your answer sheet and your modified source code as HW 8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment