1. Overview
In this lab you will:
Understand how program memory works
Evaluate debugging both logical and runtime errors
Examine how gdb works
Practice debugging code with runtime errors and logic errors
2. Program Memory
As we have discussed briefly in class, the memory a program uses are typically divided into a few different areas, called segments:
The code segment (also called a text segment), where the compiled program sits in memory. The code segment is typically read-only.
The bss segment (also called the uninitialized data segment), where zero-initialized global and static variables are stored.
The data segment (also called the initialized data segment), where initialized global and static variables are stored.
The heap, where dynamically allocated variables are allocated from.
The call stack, where function parameters, local variables, and other function-related information are stored.
The reason this is important is because when we have a bug (or error) in our code, we need to be able to figure out where it came from. The two primary places that we will look to debug our code will be in the call stack for most of our errors and the heap for errors related to dynamically allocated variables.
The heap is a bit more difficult to debug because the compiler will often crash and you will only get an error message that says, “Segmentation Fault.”
These types of bug can be very tricky to figure out and fix without some help which is why we will be practicing using the g++ debugger or gdb for short.
3. Introduction to Debuggers
When you run your program within a debugger, you can stop the program at critical points and examine the values of variables and objects. The debugger provides much more capability and flexibility than debugging a program using print statements.
The debugger allows you to examine the value of a variable after your program has crashed. Using print (such as cout) statements, you can only see values before a program crashes. If you have a segmentation fault, you may not even know where the error occurred.
What? Where do I start to debug my code? Without using a debugger, we may not have any idea where to start to debug our code. Thankfully, using a debugger will tell us where the code failed. This is the output of the gdb running my code:
Ahh, the error is on line 11. Now I know where to start to figure out what is wrong with my code.
In this lab, we will use gdb, a debugger with a command line interface.
Although the user interface is a bit clunky, you will find that gdb has many useful features. It understands C and C++ types and syntax, and it works well with source code that is distributed across multiple files.
4. Errors
There are three main types of “errors” in C++ code.
1. Syntax Errors
Syntax errors are based on the grammatical rules of the programming language. They are identified during compilation and may include not declaring an identifier used in a program or not closing an open parenthesis or brace. These are usually the easiest types of errors to identify and fix.
2. Logical Errors
A logic error is a bug in a program that causes it to operate incorrectly, but not to terminate abnormally (or crash). A logic error produces unintended or undesired output or other behavior, although it may not immediately be recognized as such.
3. Runtime Errors
A runtime error occurs whenever the program instructs the computer to do something that it is either incapable or unwilling to do. These can be caused by things such as trying to push information to a location in memory that doesn’t exist or from a divide by 0 error.
5. gdb Commands
Before you can use GDB, you need to change your compilation command from -Wall to -g. GDB offers a big list of commands, however the following commands are the ones used most frequently:
b - Puts a breakpoint at the current line
b main - Puts a breakpoint at the beginning of the program
b N - Puts a breakpoint at line N
b +N - Puts a breakpoint N lines down from the current line
b fn - Puts a breakpoint at the beginning of function "fn"
d N - Deletes breakpoint number N
info break - list breakpoints
r - Runs the program until a breakpoint or error
c - Continues running the program until the next breakpoint or error
f - Runs until the current function is finished
s – Steps to the next line of the program
s N – Steps through the next N lines of the program
n – Continues to the next function call.
u N - Runs until you get N lines in front of the current line
p var - Prints the current value of the variable "var"
bt - Prints a back trace which prints the stack trace
u - Goes up a level in the stack
d - Goes down a level in the stack
q - Quits gdb
6. Example 1 – Logic Error
For this example, let’s look at a logical error. In this case, the code is supposed to use a pass a variable to a function called addLoop(). AddLoop is supposed to use the number to loop from 0 to n-1 adding the value to the initial number. For example, we are going to set the total to 0 and pass the function a literal of 10. We are expecting the output to be
0+1+2+3+4+5+6+7+8+9 = 45. Here is the code:
#include <iostream>
using namespace std;
//Takes in a number and loops through and adds the number to
the total.
//If i = 10 and num = 10 then it should print
0+0+1+2+3+4+5+6+7+8+9=45
int addLoop(int num);
int main() {
int total = 0; //Set total to 0
total = addLoop(10);//Call addLoop with a literal of 10
cout << "New total is: " << total << endl; //Print output
return 0;
}
int addLoop(int num){
for(int i = 0; i < num; i++) { //Loop 10 times
num += i; //Add i to num
}
return num;
}
Here is the sample output:
New total is: -2147450870
Comments