35 GNU Debugger (GDB)

Dr Narayan Joshi

epgp books

 

Introduction

 

It is common phenomena that during computer program development and testing, several times the computer program may not generate correct output for a given set of input. Moreover, it is common experience of several computer programmers that the computer program may not behave as it should for the given set of input. Also, many of us would have surely faced a situation of abnormal program termination during program development. In such cases, it becomes necessary for the programmer to find out the root cause for erroneous output or unexpected behavior of the computer program.

 

Finding the cause of error and resolving the error is not only important but also unavoidable in the domain of computer science and information technology.

2.  What is debugging?

 

in 1940s, Admiral Grace Hopper and her team coined two terms: ‘bug’ and ‘debugging’. In computer programming domain, the term bug is used to refer some technical error. Whereas, solving the technical errors is referred as debugging.

 

In order to rectify errors in a computer program and to make it bug-free program, it becomes essential for a programmer to know about the cause of errors in the program. It means that programmer must know about the set of problematic statement or statements which are responsible for the erroneous behavior by the computer program. The process of finding source of errors or bugs in a computer program is known as debugging. Often, the debugging becomes challenging task for programmers for resolving logical errors.

 

It is known that there are two types of errors: syntax errors and logical errors. With help of the compilation tools, it is easy to find the source of syntax errors as normally the compiler at the time of program compilation, reports about the syntax errors. So, the programmer is not required to put additional efforts for finding syntax errors in the computer program.

 

However, finding source of non-syntactical errors often become a tedious job for programmers. Many times, it becomes very difficult to find errors in the self-written source code. In earlier days, programmers used to manually wander through the source code for detecting the cause of bugs from erroneous code; and such manual wandering used continue until the errors were found. The debugging process not only requires a highly skilled programmer but also depends on the complexity of the computer program.

 

Nowadays several automated debugging software are available in market. Automated debugging tools are also known as debuggers. Debuggers are computer programs that widely help in testing and debugging other programs. They run other programs, empower the programmer to govern control over these programs; and thereby debuggers facilitate programmer to examine variables (which have been used in the program) for identifying error. Debuggers largely help programmers in expediting debugging computer programs.

 

Debuggers nowadays are integrated with the Integrated Development Environment (IDE). Such debuggers are also known as source level debuggers. While debugging, the source level debuggers indicate the location of errors in the source code. Whereas the other type of debuggers which are not integrated with IDE, indicate the line number where an error exists. GNU Debugger is one of the best example of such editors.

3.  GNU Debugger

 

GNU Debugger is one of the most popular debugger for UNIX & LINUX family of systems for debugging programs; and study what is happening inside the program at the time of program execution. It is available under GNU Project. The GNU Debugger is also known as GDB. GDB is used for debugging programs written in various programming languages including C, C++, D, Go, Java, Fortran, Pascal etc.

 

The feature-rich GNU Debugger software pack facilitates programmer in various error detection scenarios:

  • Discovering the source of logical errors in a program is difficult just by looking at the source code. The GDB can be helpful in such cases.
  • Often, the programmer desires to know value of particular variable(s) at specific point during program execution. The GDB provides facility to inspect particular variable(s) at program run time.

Furthermore, while debugging the program, GDB allows to change values of variables to experiment the effect on program’s behavior.

  • During program execution, the GNU Debugger allows to test particular expression on command line.
  • Sometimes, programs crash abnormally, which generally results into core dump. In such cases it becomes necessary for programmer to detect the statement or expression which is responsible for the abnormal program crash. The GDB helps in finding cause for abnormal program termination.
  • Many times, execution of function fails due to error(s). In such cases, programmer would obviously wish to recognize the line number of program which contains call to the erroneous function. The GDB is beneficial in solving such cases.

However, the GDB can be used with only executable program binaries and not with the program source code.

  1. How does GDB function?

GDB may be used either to understand the behavior of currently running program for the sake of identifying logical errors; or to analyze the coredump generated due to the abnormal program termination.

 

The GDB can be used in two ways. In one way, the programmer may let the program execute up to a specific point, then pause the program and inspect values of desired variables at that point. Whereas the other approach of GDB usage facilitates to step through the program line by line and inspect values of desired variables at particular line while stepping through the program.

 

For finding the cause of error and stepping through a binary executable program, the GNU Debugger uses Symbol table. Symbol table is generated by gcc compiler. The symbol table maps instructions in the compiled binary executable to their corresponding line, variable or function in the source code, such as:

 

Program instruction => item name, item type, source file, line number…

  1. Make GDB available in your system

Issue the gdb on command-line to check whether the GNU Debugger GDB is installed in your system not. If the GDB is already available in your system, the gdb shell will be displayed as shown in Figure 1.

Figure 1: gdb is available

 

If the GDB is not available in your system, an error message ‘gdb: command not found’ will be reported on command line. In such case, you may install GDB in one of the two ways. One way to install GDB is install prebuilt binaries. Whereas other way is to download GDB source code; compile the source code and install it.

 

The first approach of installation is straight forward. Use the following two commands to install prebuild GDB binaries from preconfigured repositories.

  • # yum update all
  • # yum install gdb-8.0
  • # debuginfo-install glibc-<appropriate version>

On the other side, the second approach offers more flexibility; it involves downloading the latest source code; if required customize it as per your requirements; compile the source code and install the resultant binaries.

  1. Download the GDB source code.
  • # wget “https://ftp.gnu.org/gnu/gdb/gdb-8.0.tar.gz”
  1. Extract the downloaded GDB source code (compressed file) in the previous step.
  • # tar -xvzf gdb-8.0.tar.gz
  1. Customize the extracted GDB source code (if required). Configure the source code (GDB comes with prebuilt configure script that helps automating preparing GDB for installation) and compile it.
    • # cd gdb-8.0
    • # ./configure
    • # make
  2. Install GDB.
  • # make install
  • # gdb –version

Successful completion of the above mentioned steps will result in availability of the GNU Debugger in your system.

  1. Some commonly used GDB commands

Being a command line software, the GNU Debugger offers a rich pool of facilities at the gdb shell. Some of the commonly used commands in gdb shell are described in following Table 1:

 

 

 

Debugging programs using GDB

 

This section explains how to debug program written in C/C++ language. As described earlier in the ‘How GDB functions?’ section, let us recall that the GNU Debugger can be used mainly for two purposes:

 

(i) For understanding behavior of currently running program for the sake of identifying logical errors and/or inspecting variables/expressions/functions.

(ii) For analyzing the automatically generated core-dump due to abnormal program termination.

 

Let us see how to debug a particular running program. In order to debug the C/C++ programs with GDB, it is recommended to compile and build the program with the debugging symbol table enabled. For generating symbol table, use –g option while compiling the program using gcc as shown below:

 

# gcc –g someProgram.c –o someProgram.out

 

The above command will compile the source file someProgram.c to a binary executable file someProgram.out. Presence of the option –g will ensure generation of symbol table which will enable the as well for debugging purpose. However one should remember that failing to provide the –g option while compiling the program, will surely generate the executable file (in case source file contains no errors); but no suitable support will be available in gdb while debugging such cases (Figure 2).

 

Figure 2: Effect of program compilation without generating symbol table

 

Now our binary executable someProgram.out is ready for debugging. For debugging purpose, the executable should be run in gdb shell instead of running it directly on bash shell, as shown in the following command:

# gdb someProgram.out gdb)

(in this case someProgram.out will run inside

 

While the program is running inside GDB, use various GDB commands (Table 1) for analyzing and/or controlling program behavior.

Figure 3: Erroneous program to calculate factorial of a number

Example 1

 

Let us understand usage of the GNU Debugger using the example shown in Figure 3. Figure 3 describes a program factorial.c for calculating factorial of a given number; the program is erroneous however as it contains logical error. Total three variables are used in the program factorial.c. A variable ‘n’ is used to store value of a number whose factorial is required to be calculated. For instance, the variable ‘n’ contains value 5, i.e. the current version of program will calculate factorial of 5. A variable ‘factorial’ is used to store the computed result factorial of a value stored in the variable ‘n’; ‘factorial’ is initialized with 1. Role of variable ‘i’ is to act as a loop variable.

 

Let us compile the program factorial.c and generate symbol table (Figure 4):

 

Figure 4: Compile source file and generate symbol table

 

Launch GNU Debugger to debug the executable factorial.out (Figure 5):

 

Figure 5: Launch GDB for factorial.out

 

Let us set a breakpoint on the function main()(Figure 6). See that command ‘b main’ is used to set breakpoint. GDB shows resultant output mentioning the breakpoint set at the address containing the function main(). The output also mentions the source file name and line number of the main()function in the source.

 

Figure 6: Set breakpoint on the function main()

Now instruct GDB to start running the program (Figure 7):

 

Figure 7: Start running the program

 

Figure 7 indicates that as breakpoint was set on the function main()(Figure 6), gdb stops program execution, and now it is waiting for further instruction from user. Now step through the program using command ‘s’ upto line number 12 (Figure 8).

Figure 8: step through the program

Inspect the program variables n, factorial and i by printing them on gdb shell prompt using the ‘p’ command (Figure 9):

Figure 9: Inspect program variables

We may step through the program upto line number 15 and inspect the variables again. Also observe that now the execution control has come out from the loop, i.e. the control has iterated the do-while loop only once (Figure 10).

 

Values of ‘factorial’ and ‘i’ are not acceptable as they should be 120 and 0 after successful completion of the do-while loop. However, the output value of ‘i’ seems to be doubtful and smells a logical error. After the loop is completed, value of ‘i’ should be 0, but the actual output value of ‘i’ differs from the expected value of ‘i’. It gives a hint that there is some problem with loop condition. Due to wrong implementation of the loop condition (i <= 1), the program is not behaving as it should. Instead, the correct loop condition should be (i >= 1). Finally, using the GNU Debugger we have identified source for the logical error.

 

Figure 10: Step through the program further and inspect variables

 

Example 2

 

Now let us take one more example C++ program to understand how to deal with user-defined functions in GDB (Figure 11).

Figure 11: C++ program with user-defined function

 

Compile the C++ program shown in Figure 11 with the –g option to enable debugging.

# g++ -g detectError.cc –o detectError.x

 

Start debugging the detectError.x program and set breakpoint at the main() function. Run the program and step to line number 14. Stepping further to next line number 15, involves a call to user-defined function divide(), which will land the debugger inside the divide function i.e. line number 7. Continuing line-by-line stepping through the divide()function will eventually complete the function execution and will eventually turn the debugging control back to the parent function main() i.e. at line number 15 (Figure 12).

 

Figure 12: step through the program

 

Further stepping through the main(), function will again move the gdb control to line 7 into the detectError() fuction. However, you may skip tracing the function using the gdb command ‘c’. Inspect the values parameters of the detectError()function on gdb shell (Figure 13).

 

Figure 13: Inspect function parameters and skip tracing function

 

Use ‘where’ command to know about related functions causing crash and corresponding line numbers in functions. Use ‘list’ (or ‘l’) command to view the context of crash (Figure 14).

Figure 14: Usage of where and list commands

 

While debugging in the gdb shell, value of variables may be modified using ‘set’ command, e.g. set d = 9. Use up (or u) and down (or d) commands for moving up or down in the function stack.

Having understood how to debug running programs, now let us see how to deal with abnormal program termination resulting into coredump. Following Example 3 describes a scenario representing program crash and coredump.

 

Example 3

 

Source code of the faulty C++ program coredump.cc is shown in Figure 15. The program consists of a user defined function setVal (int *pInt, int val) with two parameters: int *pInt and int val. Job of the setVal() function is just to store value of the integer parameter val at memory location pointed by the integer pointer pInt. The setVal() function is called twice in main(): once for assigning value 7 to integer variable x (line no. 14) and once for assigning value 9 to integer pointer p (line no. 18). Compile the program with –g option, observe execution and abnormal termination of program (Figure 16).

 

Figure 15: A faulty program

Figure 16: Coredump due to abnormal program termination

 

In figure 16, it is seen that the program crashes abnormally and core is dumped. Let us debug program and coredump to find out the cause and location of error. The coredump debug output is shown in Figure 17. In Figure 17 it is seen that stepping through the program up to line no. 17 and then inside the function call at line no. 6, the program crashes due to a signal SIGSEGV. In Linux, the SIGSEGV signal represents segmentation fault indicating that the program tried to access (dereference) an invalid memory location which was either not allowed to access or allocated at all to the program. It means that there is some problem with the value of the formal parameter pInt (pointer to some memory location) of the function setVal()i.e. the memory location which was passed to the function as parameter could be invalid. Investigating the related actual parameter ‘int* p’ in the function call (line no. 17 and 18) reveals that the variable *p is not initialized at all. It means the pointer p possessed some garbage memory-address, i.e. the pointer p was pointing to some invalid/inaccessible memory location. Thus the source of error is found in the program using the GNU Debugger.

Figure 17: Program termination due to Segmentation fault

 

Summary

 

Identifying the cause of error and resolving the error is one of the important and unavoidable phases in application development. The process of finding source of errors or bugs in a computer program is known as debugging. Identifying source of logical and non-syntactical errors becomes challenging task programmers. Nowadays there are many debugger tools available. GNU Debugger is a widely accepted software for UNIX & LINUX family of systems for debugging programs written in various programming languages. GDB is used for investigating behavior of currently running program for inspecting variables/expressions/functions for given set of input.

you can view video on GNU Debugger (GDB)

References and Further Reading

  • GDB Pocket Reference, Arnold Robbins, O’Reilly Publication
  • The Art of Debugging with GDB and DDD, Norman Matloff, Peter Salzman, O’Reilly Publication
  • Web resource: GDB: The GNU Project Debugger – Official Documentation, https://www.gnu.org/software/gdb/documentation/