I remember learning how to use a debugger and Utah State University as an undergraduate. We learned to program in Fortran 90, and used a Microsoft debugger. It seemed cool, but honestly with the types of programs I was writing at the time, using print statement seemed to be effective enough and less of a hassle. Recently I have been working with the Uintah MPM code, which is MUCH larger and more complex than any code I’ve ever written. After several months of wading through the code I finally came across a problem that “cout” just could not help me solve. The code was suddenly crashing with an allocation error and giving no useful backtrace information. A colleague suggested that I use gdb to find out where the problem was occurring. It turned out to be very, very helpful and surprisingly easy to use. I’ve recently become a convert to Emacs, and found that the gdb interface in Emacs is especially nice. So here is goes:
Make sure you have gdb and Emacs installed. I think gdb comes with Emacs, but I could be wrong. If you are using Ubuntu simply issue “sudo apt-get install gdb Emacs” and you should be home free. If you do not have sudo access to your machine, ask your system administrator to install them, or you can build them locally (yuk!). Another important note is that the executable you wish to debug must be compiled with the ‘-g’ option if you are using g++. If you are using a different compiler you are on your own to figure out what options you need.
2. Open gdb within Emacs
Open a terminal and “cd” to the directory where you executable lives and type
This will open Emacs in a separate window in the background so that your terminal is still usable.
Inside emacs type ‘M-x’ , which means holding down your ‘meta’ key, which is usually ‘Alt’, and press x). A dialog should appear at the bottom of the Emacs screen. Now type ‘gdb’ and hit enter. Now the dialog will change to:
Run gdb (like this): gdb –annotate=3
Now type the name of the executable that you want to debug and hit enter (you may have to first erase the default placed there by emacs). In my case with Uintah, it looks like this:
Run gdb (like this): gdb –annotate=3 /work/cdpi_uintah/opt/StandAlone/sus
This will launch gdb within emacs and you should see something like this in your window:
Current directory is /work/cdpi_uintah/opt/StandAlone/
GNU gdb (GDB) 7.0
Copyright (C) 2009 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 “x86_64-unknown-linux-gnu”.
For bug reporting instructions, please see:
Reading symbols from /work/cdpi_uintah/opt/StandAlone/sus…done.
3. Set command line arguments within gdb
Now we need to tell gdb what command line arguments need to be issued to actually run the executable, which for Uintah is the name of the input file. As an example, we will run a simple input file named ‘disks.ups’ which is located in ‘/src/StandAlone/inputs/MPM/’. To do this we type:
(gdb) set args /work/cdpi_uintah/src/StandAlone/inputs/MPM/disks.ups
Then hit return. Now when we run the executable ‘sus’ inside gdb it will be executed with the command line argument ‘/work/cdpi_uintah/src/StandAlone/inputs/MPM/disks.ups’
4. Open the other useful windows in emacs
Right now it might be looking like nothing is different that when gdb is ran from the command line. However, emacs provides some nice functionality to enhance the utility of gdb. To take best advantage of this functionality click on the menu item (top of the window) ‘Gud > GDB-UI > Display Other Windows’. Your window should now look something like this:
The upper left frame is the gdb window where you type commands to interact with gdb. The upper right frame will contain a list of variables which are in the current scope. Of course, our executable is currently not running so no variables are in the current scope. The center frame is where we view relevant source code. This frame behaves just like a standard emacs window with a few added features which will be discussed below. The lower left window contains information about the current stack which again is empty, but when the program is running it will provide useful information about where you have been in the code. The lower right window is a list of breakpoints, which we have not yet set.
Now, for the useful part: breakpoints. Suppose we wanted to inspect a routine which is contained in a source file named ‘SerialMPM.cc’, which in Uintah is the guts of the MPM algorithms. Specifically, line 577 in ‘SerialMPM.cc’ is where the main MPM routines are called. To set a breakpoint at this location we would type the following in the upper left-hand window (the gdb window):
(gdb) break SerialMPM.cc:557
Then hit enter. You will likely see some message which looks like this:
No source file named SerialMPM.cc.
Make breakpoint pending on future shared library load? (y or [n])
Hit ‘y’ to say yes to this question. The reason for this message is that since the code has not yet run, gdb has not yet used any routines in ‘SerialMPM.cc’, so it doesn’t recognize it. This is kind of an annoying quirk, but lets move on. After pressing ‘y’ and hitting enter gdb will print the following message:
Breakpoint 1 (SerialMPM.cc:557) pending.
Now you should see a breakpoint listed in the lower right-hand window as well. You may set any other breakpoints now, or start running the code and set more breakpoints later. It is easier to set breakpoints after the code has begun running due to the issue mentioned above. To run the code type:
And hit enter. For convenience you can abbreviate most commands with just enough letters to make it unique. For example, instead of going to the trouble of typing the whole three letters in ‘run’ you can be three times as efficient and optionally just type ‘r’ and it will have the same effect. This will run the code until it hits the breakpoint or it crashes, which ever comes first. When it reaches the breakpoint the line of code where the breakpoint was set will automatically be displayed in the center frame. Now the window should look like this:
The red dot in the center frame is the location of the breakpoint, and the black triangle indicates the next line to be executed. From now on you can set additional breakpoints on any line you want by simply clicking on the gray bar where the red dot and black triangle are at. For example suppose we wanted to set a breakpoint four lines down from the current breakpoint. Simply click in the shaded box to the left of the line and a red dot will appear there. Now we have a second breakpoint. Cool.
6. Basics of using gdb
Now, how do we use this to debug? To illustrate lets go to a different part of the code first and show you how to disable or delete breakpoints in the process. First, lets delete the last breakpoint we set since it isn’t very useful. To do this type
(gdb) delete 2
then hit enter. This will delete breakpoint 2. If you instead just wanted to disable the breakpoint temporarily, you could type, you guessed it, ‘disable 2’. Now lets set a breakpoint somewhere more useful. Click in the middle frame and type ‘C-x C-f’ which in Emacs lingo means hold the control key and press ‘x’, then hold the control key and press ‘f’. This will open a dialog box and the bottom of the window where you can type the name of a new file to visit. In our case type ‘/work/cdpi_uintah/src/Core/Grid/Node27Interpolator.cc’ and hit enter. The new source file will now be loaded in the middle frame. Now scroll down to line 90. None of the code in this file has been executed yet so gdb knows nothing about this file, just as before. This means that you can’t simply click to the left of the line where you want to set the break, you have to type it in manually with
(gdb) break Node27Interpolator.cc:90
and press enter. This will create breakpoint number 3. The code is still stopped at breakpoint number 1, so to continue execution until breakpoint 3 is reached (remember breakpoint 2 was deleted), simply type
and hit enter. Now the code will run until it reached line 90 in ‘Node27Interpolator.cc’. Now you can see quite a few variables in the upper right hand window. The values of doubles and integers are actually displayed there. However, the value for other objects such as arrays are not displayed there. For example the ‘ni’ array is a 27 element array of integer vectors ( which is composed of three integers). Line 90 is assigning element 9 of that array. Suppose we want to know what the value of element 4 is. Simple type:
(gdb) print ni
This will print out the value of the fifth element in the ‘ni’ array and tell us what object type it is. You can do this for any other variables in the current scope. You can also change the value of any variables in the current scope. This can get you in trouble if you aren’t careful, but can be a handy debugging tool. For example, suppose we want to change the value of the first element in ni to 1. Simply type
(gdb) print ni = 1
Now if you print out the value again you can see that it has been changed. Now suppose we want to see exactly how the program proceeds with the new value. We can use the ‘step’ command to step through the program step by step.
7. Conditional break points
Now suppose that your really want to see what is going on in the code at timestep 5000. You can either set a break point and then hit continue 5000 times, or you can set a conditional breakpoint. We find a place in the code where time is in scope, such as at line 3013 in SerialMPM.cc. We first set a breakpoint at this location with
(gdb) break SerialMPM.cc:3013
Now lets make this a conditional break point. Suppose we only want to stop at this new breakpoint if the time is greater than one second. Simply type
(gdb) condition 4 time>1.0
This command tells gdb to only stop at breakpoint 4 if the variable ‘time’ is greater than 1.0. Then we disable all other breakpoints and type continue. Then the code will continue to run normally until the time is greater than one second, at which point it will stop at line 3013 in SerialMPM.cc. Now you can re-enable any breakpoints at other places in the code.