GDB Primer for CS1550 Project

(prepared by Takashi Okumura, aka taka)


What is it?

gdb is an utility for debugging and executing programs.

Why gdb, even though we have printf()?

So-called printf debugging may be useful for chasing the macroscopic flow of a program. This is typically done by coding Debug() function into the program. However, debugging requires microscopic tracing of program and inspection of variables, though printf() needs to be explicitly embeded into program before compile time. Thus, it is not practical for debugging.

Professional programmers use debuggers, instead, for debugging. It is extremely useful, once becomes familiar, and provides way to trace flows, inspect variables, and locate points of failure at runtime.

How about ddd, or xgdb?

Yes, you may use these packages as well. They are basically just an interface to gdb, which provides the same functionality.

However, the heart of debugging is the interactive inspection of flows and variables. Since they need mouse operations everytime, you may have frustration.

For those of you who have experience with graphical debuggers, you may continue using them. The same idea in the lecture applies to most of them.

Scenario

segmentation fault

Most of the serious bugs result in this error, and in termination of program. Formally speaking, they are caused by accessing addresses outside of the assigned address space. However, practically speaking, 99% of the segmentation fault bugs we have is caused by the memory reference with a null pointer. (A null pointer is a pointer with null value.) The rest is by using out of range index.
Anyhow, it is difficult to detect which pointer is the cause and where it occurs with printf debugging.

bus error

You may have not seen this one. But, it is one of the common runtime error, which causes the termination of process. This happens basically when a CPU uses a pointer with misaligned address. Most CPUs accesses memory in a chunk for performance reason nowadays. Thus, they cannot execute instructions with badly aligned address and terminates. This is mostly caused by bugs in pointer handling routine. It is also difficult to find with printf.

silent bug

You may have had a bug in which a program goes astray, without crashing, or printing any useful information. Without such clue, you cannot track the cause down. You might have embedded a number of printfs into the code, but it is clearly inefficient.

If you run the program on gdb, you can stop and locate the point of execution by just sending a break signal any time. Then you can trace or inspect. It's much more efficient.

Now you should have been enlightened enough.


How to use?

To be debugged with gdb, target program written in C or C++ needs to be compiled with the -g option. Note that you should not use optimization option such as -01 and -02.

%cc -g -o filename filename.c ...
%gcc -g filename.c
If you use makefile, put the -g option into appropriate line of your Makefile.

Now, to start off gdb, type gdb filename at the command line. A few messages are printed, and then you are left at the (gdb) prompt:

%gdb filename
%gdb
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-unknown-freebsd".
(gdb) _
Or, you may use filecommand to read an executable at prompt:

%gdb filename
[messages]
(gdb) file filename
(gdb) _

Important Command list

Some of the important and most often used commands are listed and briefly explained below. Actually, these are all you need to debug with gdb:

Execution control

Breakpoint management

Listing of program

Examination of data

Examination of Stack

Others


Debugging sequence

Here is a sample debugging sequence.

Got a segmentation fault

%nachos ... 
segmentation fault
%

Run on gdb

%gdb nachos

(gdb) run ...
segmentation fault at line xxx of yyy.cc.
(gdb)
gdb will show you the location of the error. (It stops execution automatically, and prints error messages)

Check the program

(gdb) list
       19 {
       20     int sum = 0;
       21     int i;
       22
       23     for(i = 0;;i++)
       24        sum += *ptrs[i];
       25 }
       26
       27
       28 void PrintArray()
(gdb) print i
       $1 = 23424592334
Use list command, and check the lines around the point of failure. You may use print command to check the value of variables.

If you could find the cause, just fix it, compile the source code, and run it again. Otherwise, proceed to next step.

Set a breakpoint at the beginning of the code segment

You may set a breakpoint at the beginning of the function of interest. Just run the program, and trace the lines with next command, checking the variables. Iterate the procedure, until you find the cause.

Nachos thread switching

Set breakpoints at the beginning of P() and V() and use the C-x space command to set breakpoint before and after the call to SWITCH() in Scheduler::Run().

Now run the program using run. Let it continue until it gets top the first call to P(). Use where to observe the stack. We are in foo():

(gdb) where
#0  Semaphore::P (this=0x29768) at ../threads/synch.cc:67
#1  0x154c0 in foo (unused=0) at ../threads/threadtest.cc:11
Continue execution until you reach the SWITCH() call. We are still in foo():
(gdb) where
#0  Scheduler::Run (this=0x29250, nextThread=0x29700)
    at ../threads/scheduler.cc:116
#1  0x14d7c in Thread::Sleep (this=0x296a0) at ../threads/thread.cc:225
#2  0x134cc in Semaphore::P (this=0x29768) at ../threads/synch.cc:71
#3  0x154c0 in foo (unused=0) at ../threads/threadtest.cc:11
Now let execution continue. It stops at a call to V(). Now we are in the bar() thread, as the stack shows:
(gdb) where
#0  Semaphore::V (this=0x29768) at ../threads/synch.cc:91
#1  0x15520 in bar (unused=0) at ../threads/threadtest.cc:20
Continue again and we stop at P(), still in the bar() thread. Continue one more time and bar() is about to switch back to foo().
(gdb) where
#0  Scheduler::Run (this=0x29250, nextThread=0x296a0)
    at ../threads/scheduler.cc:116
#1  0x14d7c in Thread::Sleep (this=0x29700) at ../threads/thread.cc:225
#2  0x134cc in Semaphore::P (this=0x29780) at ../threads/synch.cc:71
#3  0x15530 in bar (unused=0) at ../threads/threadtest.cc:21
Continue one more time and observe that we stop right after the switch. It appears as though SWITCH() has returned normally. But observe the stack:
(gdb) where
#0  Scheduler::Run (this=0x29250, nextThread=0x29700)
    at ../threads/scheduler.cc:118
#1  0x14d7c in Thread::Sleep (this=0x296a0) at ../threads/thread.cc:225
#2  0x134cc in Semaphore::P (this=0x29768) at ../threads/synch.cc:71
#3  0x154c0 in foo (unused=0) at ../threads/threadtest.cc:11
We are now back in the foo() thread!

Reference


Last changed: 6 Apr 2000; taka@cs.pitt.edu