Introduction
GDB is EXTREMELY handy. I used to doing a lot of print statements, but now I use GDB a lot. The reason is simple: I can check multiple things at once without recompiling everytime, especially my compilation time is long.
Concepts
Debug Symbols in C or C++ are mappings from machine code to function names, variable names, line numbers, etc.
E.g., in a binary, i
is just a memory location. When built with a Debug
flag (see the Usage section), we know the variable’s name.
1
int i = 1;
Debugging symbols do not impact the binary execution logic. It’s purely informational and consumed by gdb. The binary filesize though would increase.
Compilation
- In
CMakeLists.txt
1
set(CMAKE_BUILD_TYPE Debug)
1
- Or alternatively, `catkin_make -DCMAKE_BUILD_TYPE=Debug`
- In
gcc
:
1
gcc -g -o myprog myprogram.c
Run GDB
- In a ROS Launch file:
1
<node pkg="rgbd_slam_rico" type="rgbd_rico_slam_mini" name="rgbd_rico_slam_mini" output="screen" launch-prefix="gdb -ex run --args"/>
- Alternatively, you can run the binary using the command
gdb
:
1
gdb ./devel/lib/<PACKAGE_NAME>/binary
GDB Usage
Some common usages include:
b <FILE_NAME>:<LINE_NUM>
set a breakpointr
: runc
: continuep <var_name>
: print a variable nameinfo breakpoints
: check which breakpoints are availabledelete <BREAKPOINT_NUM>
: to delete a break point’s number
Some cautions:
- When debug printing an element in a
cv::Mat
, we need to specify its data type. To print it as a:- double (
CV_64F
), do(gdb) x/4g var
. 4 means the first 4 values,g
means double precision. - float (
CV_32F
):(gdb) x/4f var
- int:
(gdb) x/4d var
- double (
With ROS2
1
2
3
4
5
6
7
8
9
10
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Debug
gdb --args ./test_direct_3d_ndt_lo --gtest_catch_exceptions=0
# 5 In gdb:
(gdb) catch throw # break on any C++ throw
(gdb) run
# gdb will now break inside std::_Deque_base<...>::_M_range_check
# you can then:
(gdb) bt # to see the backtrace
(gdb) info locals # to inspect variables
Core Dump
Assume we have multiple C++ nodes that crash after running a long time (so they are hard to reproduce). What would be a good way to start debugging?
- Core dump
A “core dump” is a snapshot of a computer program’s memory state, typically taken when the program crashes or terminates abnormally, and it’s used to help diagnose the cause of the crash.
- How to get it working:
ulimit -c unlimited
- Adjust kernel settings to store it in /tmp:
1
cat /proc/sys/kernel/core_pattern
- To change it:
sudo sysctl -w kernel.core_pattern=/tmp/core.%e.%p.%h.%t
/etc/systemd/system.conf
1
DefaultLimitCORE=infinity
sudo systemctl daemon-reload
- Create a crash program:
1 2 3 4 5 6 7
#include <stdio.h> int main() { // Intentionally dereference a NULL pointer to cause a segmentation fault. int *ptr = NULL; *ptr = 42; return 0; }
- Compile the executable with debugging symbols:
1
gcc -o0 crash_example crash_example.c
- Temporarily Enable Core Dumps:
ulimit -c unlimited
- Verify that the current core dump file name pattern with:
cat /proc/sys/kernel/core_pattern
- Run the program:
./crash_example
- Launch gdb:
gdb ./crash_example /tmp/core.<executable_name>.<pid>.<hostname>.<timestamp>
: and runbt
at gdb. There will be backtrace saved