Debugging with GDB
From The Player Project
The GNU Project debugger, gdb, is a powerful tool for debugging and stepping through programs interactively. This is a brief guide on how to get started with gdb, how to obtain useful stack trace information, and how to set breakpoints and interactively step through a program.
Installing debug info
To get the most out of gdb, one must have "debug symbols" in compiled libraries and executables. When using gcc, the -g compile flag is what tells the compiler to create and embed debug symbols in the output files. When building software from the Player project, the easiest way to ensure that debug symbols are present is to verify the CMake option CMAKE_BUILD_TYPE is set to either DEBUG or RELWITHDEBINFO.
If you installed the software as a pre-compiled binaries (i.e. you downloaded them using a package manager), then you may have to go through some extra steps to obtain the debug info that gdb needs. Most linux distributions will build software with debug symbols, and then strip them out of the binaries to save space. Most times the debug symbols can be installed separately with special debug info packages. RPM based distributions like Fedora and openSUSE create special RPM packages with the -debuginfo suffix; these must be installed for each package you would like to debug. For example, on Fedora if you have the yum-utils package installed, you can issue the following command to install debug information for Player:
$ sudo debuginfo-install player
This will download and install a package called player-debuginfo which contains all of the debugging information that was built into the Player binaries. The version of gdb shipped with Fedora can find these symbols when you attempt to debug Player, and will load them accordingly. Other distributions have similar procedures, you may want to consult your distribution's developer documentation for specifics.
Once you have debug information installed, you can run gdb and attempt to recreate your problem. If, for example, you would like to debug a segmentation fault in Player, you would issue the following command:
$ gdb /usr/bin/player
Note that you don't have to specify the path to Player if it is on your system $PATH. This command will start gdb, which will load the player binary and debug information. You should see some startup information and then arrive at a gdb prompt, like so:
$ gdb /usr/bin/player GNU gdb (GDB) Fedora (18.104.22.16810525-39.fc15) Copyright (C) 2011 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-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /usr/bin/player...done. (gdb)
Once you get to the (gdb) prompt, you can issue gdb commands to aid in your debugging (i.e. setting breakpoints, etc.) You can type "help" for more information. In this case, we'll pretend that Player is segfaulting when run with a configuration file called "broken.cfg". To recreate the problem, you can direct gdb to load and run Player with command line arguments, as below:
(gdb) run broken.cfg
The run command tells gdb to execute the binary you loaded when you started gdb. Anything after run is passed to the underlying binary's command line. In this case, the name of the configuration file we want to load.
Player should proceed to load and run until the segmentation fault occurs. At that point, gdb will catch the error and return you to a gdb prompt. gdb has effectively frozen player at the point where the segmentation fault occured, and allows you to see which function call it was in and which lines of code it was executing as the segmentation fault happened. You may also query the values of variables around the crash.
When you arrive at a breakpoint or crash point, gdb returns you to the gdb prompt. From there, a couple of commands are especially useful:
- bt - Provides a backtrace of the stack at the break point. This is one of the more useful gdb commands when dealing with segfaults, as it pinpoints exactly where the crash occured
- You may also use bt full to print out the values of all local variables at each frame.
- For a complete backtrace, use thread apply all bt full
- list- Provides printout of the source code being executed at the time of the crash.
- up and down - Move gdb between the different frames listed in the bt function. This is useful so you can query different variables at levels of the call stack.
- print or p - Print a local variable in the call stack. For example, print i will print the value of the variable called i
- help - Provides much more thorough information about commands provided by gdb.
Stepping through code
gdb isn't only good at debugging crashes, it can also help you troubleshoot strange code behavior, or get a feeling for how a program works. You can use gdb interactively by setting breakpoints before (or during) execution. For example, say you want to place a breakpoint at line 92 of a file called "main.cc". Before issuing the run command at the gdb prompt, you can issue the command:
(gdb) b main.cc:92
It's also possible to set a breakpoint when a function executes. If you want to stop at a call to a function named "main" (which is coincidentally the entry point to most C/C++ programs,) you can issue a command like:
(gdb) b main
This also works for class methods; if you want to stop at method PrintSomething from class FooBar, you can issue the following:
(gdb) b FooBar::PrintSomething
When the program arrives at the breakpoint, it will stop and return you to the gdb prompt. From there, you can use all of the commands from the section above, and you can step through the program with the following commands:
- next or n - Execute the current line of code and proceed to the next line
- step or s - Step into the current function call
- continue or c - Continue running the program until another breakpoint is reached
- quit or q - Stop running the program and exit gdb.
At any time during execution, you may press Ctrl+C to return to the gdb prompt. From there you can set breakpoints, print variables, and continue execution using the above commands.