5. Debugging OpenMOC¶
This section describes some recommended debugging techniques for developers working with OpenMOC source code. Although many novice code developers are accustomed to use printf debugging with the printf(...)
C/C++ routing. The printf debugging methodology is highly discouraged since it leads to a very long debug iteration cycle between source code revision, compilation, and testing. Instead, developers are advised to use some form of a debug environment for C/C++ and/or Python.
In particular, this section describes GNU debugger gdb
in detail with some basic examples of how to use it with the shared library C++ extension module that is built for OpenMOC. In addition, some debugging techniques for the Python source are overviewed.
5.1. The GNU Debugger (GDB)¶
The GNU Project Debugger (GDB) provides the free open source gdb
program for debugging of programs written in C/C++ (and other languages). The GNU debugger is typically installed when the GNU compiler collection (e.g., gcc
, g++
) is installed. However, if for some reason you do not have gdb
, you may install it in Ubuntu using the apt-get
package manager as follows:
sudo apt-get install gdb
Likewise, the GNU debugger can be installed using MacPorts on machines running Mac OS X as follows:
sudo port install gdb
There are a number of different ways to use to use and interact with GDB. There are a variety of graphical user interfaces for GDB, including DDD and Eclipse CDT which may be helpful for advanced code developers. This section, however, will focus on using the command line interface to GDB via the console.
5.2. Debugging C/C++ Source Code¶
The majority of the compute-intensive code in OpenMOC is written in C/C++ as a compiled back-end shared library. This section overviews some of the key steps to using GDB to debug OpenMOC’s C/C++ source code. By no means does this section cover all of GDB’s capabilities. Advanced developers should consult one of the many outlets for GDB documentation available online, including the following
- Official GDB Documentation
- GNU GDB Debugger Command Cheat Sheet
- The Art of Debugging
- Debugging with GDB
5.2.1. Installing OpenMOC with Debug Symbols¶
In order to debug OpenMOC C/C++ source code, you must build and install the C/C++ extension module for Python with debug symbols. This is easily done by appending the -g
command line option for the gcc
compiler. The build system for OpenMOC can automatically append this flag for the compilation process with the --debug-mode
option:
python setup.py install --user --debug-mode
5.2.2. Starting GDB¶
The GNU Debugger may be started from the console with the gdb
command:
$ gdb
GNU gdb (GDB) 7.5-ubuntu
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 "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
(gdb)
If you are debugging code written for the GPU, the CUDA GNU Debugger should be used:
$ cuda-gdb
NVIDIA (R) CUDA Debugger
5.5 release
Portions Copyright (C) 2007-2013 NVIDIA Corporation
GNU gdb (GDB) 7.2
Copyright (C) 2010 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:
<http://www.gnu.org/software/gdb/bugs/>.
(cuda-gdb)
The CUDA GDB debugger oftentimes does not fail at the exact location of the error, so it is recommended to turn cuda memcheck on:
(cuda-gdb) set cuda memcheck on
These commands will leave you in an interactive session with GDB, complete with its own command line interface. From now on we will give all examples with gdb, but the same commands apply for cuda-gdb.
5.2.3. Initializing Python¶
From within the interactive GDB session, you must inform GDB that you plan to use Python as your binary executable:
(gdb) file python
Reading symbols from /usr/bin/python...Reading symbols from /usr/lib/debug/usr/bin/python2.7...done.
5.2.4. Running Python in GDB¶
Next you can select a Python file to execute using the run
command with the Python script as the argument. For example, to run the /OpenMOC/sample-input/simple-lattice/simple-lattice.py
from within GDB, simply execute the following:
(gdb) run simple-lattice.py -i 5
Starting program: /usr/bin/python simple-lattice.py -i 5
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[ NORMAL ] Importing materials data from HDF5...
[ NORMAL ] Creating surfaces...
[ NORMAL ] Creating cells...
[ NORMAL ] Creating simple 4 x 4 lattice...
[ NORMAL ] Creating geometry...
[ NORMAL ] Number of flat source regions: 60
[ NORMAL ] Initializing the track generator...
[ NORMAL ] Returning from readTracksFromFile
[ NORMAL ] Computing azimuthal angles and track spacings...
[ NORMAL ] Generating track start and end points...
[ NORMAL ] Segmenting tracks...
[ NORMAL ] Initializing track boundary conditions...
[ NORMAL ] Converging the source...
[ NORMAL ] Iteration 0: k_eff = 1.000000 res = 0.000E+00
[ NORMAL ] Iteration 1: k_eff = 1.270088 res = 2.594E+02
[ NORMAL ] Iteration 2: k_eff = 1.290540 res = 4.797E-01
[ NORMAL ] Iteration 3: k_eff = 1.309195 res = 1.919E-01
[ NORMAL ] Iteration 4: k_eff = 1.318423 res = 1.680E-01
[ WARNING ] Unable to converge the source after 5 iterations
[ TITLE ] *******************************************************************
[ TITLE ] TIMING REPORT
[ TITLE ] *******************************************************************
[ RESULT ] Total time to solution...............................3.9251E-03 sec
[ RESULT ] Solution time per unknown............................9.3454E-06 sec
[ RESULT ] Solution time per iteration..........................7.8502E-04 sec
[ RESULT ] Integration time per segment integration.............2.0098E-08 sec
[SEPARATOR] -------------------------------------------------------------------
[ RESULT ] # tracks # segments # FSRs
[SEPARATOR] -------------------------------------------------------------------
[ RESULT ] 116 930 60
[SEPARATOR] -------------------------------------------------------------------
[ NORMAL ] Plotting data...
[ TITLE ] *******************************************************************
[ TITLE ] Finished
[ TITLE ] *******************************************************************
[Inferior 1 (process 25820) exited normally]
(gdb)
To obtain more information about program execution, GDB can be run in verbose mode using the -v
optional argument to the run
command:
(gdb) run -v simple-lattice.py
...
[Inferior 1 (process 25820) exited normally]
(gdb)
5.2.5. Set Breakpoints¶
A breakpoint is an intentional stopping or pausing place in a program for debugging purposes. A breakpoint can be set using gdb
using the breakpoint
or br
commands. The br
command can be set at a specific line number in a specific source file. For example, if we wanted to run an OpenMOC program until the Material::setNumEnergyGroups(...)
routine was called, we could set a breakpoint to the first line in that routine and then execute simple-lattice.py
as follows:
(gdb) br Material.cpp:349
No source file named Material.cpp.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (Material.cpp:349) pending.
(gdb) run simple-lattice.py
Starting program: /usr/bin/python simple-lattice.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[ NORMAL ] Importing materials data from HDF5...
Breakpoint 1, Material::setNumEnergyGroups (this=0x1b6ef80, num_groups=7)
at src/Material.cpp:349
349 if (num_groups < 0)
(gdb)
As shown in the snippet above, the code executes until the breakpoint is reached and short summary of the source code is printed to the console. Alternatively, we could have set the breakpoint using the name of the routine instead:
(gdb) br Material::setNumEnergyGroups
Function "Material::setNumEnergyGroups" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (Material::setNumEnergyGroups) pending.
(gdb) run simple-lattice.py
Starting program: /usr/bin/python simple-lattice.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[ NORMAL ] Importing materials data from HDF5...
Breakpoint 1, Material::setNumEnergyGroups (this=0x1b6ef80, num_groups=7)
at src/Material.cpp:347
347 void Material::setNumEnergyGroups(const int num_groups) {
(gdb)
In each case, a breakpoint is set and the program is executed until that line is reached. The entire program state is stored and the execution is simply interrupted until further notice to GDB is given by the user.
5.2.6. Set Watchpoints¶
A watchpoint is a conditional breakpoint, or a breakpoint that is only reached when a certain condition is met. The condition may be the reading, writing, or modification of a specific location in memory. For example, if we wanted to watch the value of the Material::_num_groups
private class attribute we could place a watchpoint on it. First, we might start gdb and place a breakpoint on the Material::setNumEnergyGroups(...)
routine as shown in the preceding section. Then we could place a watchpoint as follows:
(gdb) watch _num_groups
Watchpoint 2: _num_groups
(gdb) continue
Continuing.
Watchpoint 2: _num_groups
Old value = 0
New value = 7
Material::setNumEnergyGroups (this=0x1b6ef80, num_groups=7)
at src/Material.cpp:358
358 if (_data_aligned) {
(gdb)
As illustrated, GDB stepped through the program until _num_groups
was modified or used and reported its value to the console. Note that this snippet made use of the continue
command which is covered in th next section. At this point, it suffices to say that continue
resumes program execution from a breakpoint until it a new breakpoint or watchpoint is reached.
GDB provides a variety of method to set watchpoints during a program’s execution. For example, instead of placing a watchpoint on _num_groups
, we could instead have placed a watchpoint on the condition that _num_groups
> 0 as follows:
(gdb) watch _num_groups > 0
Watchpoint 2: _num_groups > 0
(gdb) continue
Continuing.
Watchpoint 2: _num_groups > 0
Old value = false
New value = true
Material::setNumEnergyGroups (this=0x1b6ef80, num_groups=7)
at src/Material.cpp:358
358 if (_data_aligned) {
(gdb)
In this case, the result of the conditional is reported to the screen.
5.2.7. Step through the Program¶
This section highlights a few of the key commands which may be used to control program execution using GDB.
Continue
The
continue
orc
command is used to instruct GDB to continue program execution until the next breakpoint or watchpoint is reached (i.e., useful for loops). An optional integer argument<number>
may be given tocontinue
to instruct GDB to ignore the current breakpoint some number of times.(gdb) continue <number>
Step
The
step
ors
command will step to the next line of code. If the next line of code is a function call, thestep
command will step into the function. An optional integer argument<number>
may be given tostep
to instruct GDB to step through some number of lines.(gdb) step <number>
Next
The
next
orn
command will step to the next line of code. If the next line of code is a function call, thestep
command will not step into the function. An optional integer argument<number>
may be given tonext
to instruct GDB to step through some number of lines (without entering functions).(gdb) next <number>
Until
The
until
command will continue processing until reaching a specifed line number<number>
. This is akin to setting a breakpoint which is only used once and which is immediately deleted following its first use.(gdb) until <number>
Where
The
where
command will show which line number you are at and which function you are in.(gdb) where
5.2.8. Examine Variables¶
The print
or p
command may be used to examine variables within some scope of the code with GDB. For example, if you were interested in the value of variable
, you might set a breakpoint at the entrance point to the code region of interest, and step through the region while printing the value as follows:
(gdb) print variable
5.2.9. Report the Debugger State¶
The info
or i
command may be used to report debugger state information to the console. For example, to list all breakpoints - including the file and line numbers where each is set - the info
command is used with the breakpoints
option:
(gdb) info breakpoints
To list breakpoint numbers only, info
command is used with the break
option:
(gdb) info break
Likewise, to list all watchpoints, the info
command is used with the watchpoints
option:
(gdb) info watchpoints
5.2.10. Disable Breakpoints¶
The disable
command is used to disable breakpoints with GDB. When a breakpoint is disabled, it is still retained by GDB but is not used during program execution. The enable
command may be used to continue using the breakpoint again at a later time. The following illustrates how to cancel breakpoints 1, 3, 4, 5, and 6, and re-enable breakpoints 4 and 5:
(gdb) disable 1 3-6
(gdb) enable 4-5
5.2.11. Printing the Stack¶
The backtrace
or bt
command may be used to show the trace of the function the program is currently in:
(gdb) bt
The backtrace
command can be particularly useful when debugging segmentation faults. In particular, GDB may be used to run the program until the segmentation fault is reached. At this point, the use of backtrace
will print the function call stack, showing where the segmentation faul occurred.
5.2.12. Debugging with OpenMP threads¶
Most of the time, OpenMOC will be run with more than one thread, and bugs might not manifest themselves on all threads. The info
command can be run to check what each thread is doing.
(gdb) info thread
To switch between threads and print variables local to each thread, one can use the thread
command with the desired thread number.
(gdb) thread 4
5.2.13. Debugging with multiple MPI processes¶
Some simulations may require OpenMOC to be run on more than one computing node. Debugging those situations with GDB is rather difficult, but not impossible. xterm can be used to spawn terminals which host each MPI process. On a cluster, a sshx connection and an interactive node reservation is required.
(gdb) mpirun -np 2 xterm -e gdb python
Another handy command is to tell GDB to start each process, and to run backtrace when it crashes/reaches a breakpoint using “-ex”.
(gdb) mpirun -np 2 gdb -batch -ex "run assembly-case.py" -ex "bt" python
5.2.14. Stop Program Execution¶
The kill
command may be used to stop a program’s execution while keeping the GDB process running:
(gdb) kill
5.2.15. Exiting GDB¶
The quit
or q
command may be used to exit the gdb
debugger and return to the console:
(gdb) quit()
5.3. Debugging Python Source Code¶
There are a a number of resources which one may use to debug Python code. Many popular Integrated Development Environments (IDEs) for Python include interactive visual debugging support, including PyCharm, Eclipse PyDev, and Wing IDE. It is highly recommended that code developers use one of these IDEs for Python development and debugging. The PyCharm IDE is especially recommended for OpenMOC users developing input and data processing modules in Python. Although PyCharm is a commercial product, a community version is provided for free with many of the most essential features including the following:
- Syntax highlighting
- Auto-indentation
- Code formatting
- Code completion
- Line/block commenting
- Refactoring
- Python interpreter
- Integrated debugger
In addition, advanced developers should consult one of the many online outlets for documentation on debugging Python programs, including the following: