Using GDB with ROS2, a reference

Keywords: #ros #ros2 #gdb #debugging

It has been around a year since I moved to working in ROS2 full-time. Much like the Post about using GDB in ROS1, I wanted to make a reference for myself, and potentially anyone else, for creating a decent debugging workflow for ROS2. Unlike my ROS1 guide, I will be covering launch files here, as information regarding debugging with ROS2 is a bit sparse.

Step 1: Compile with debug flags

$ colcon build [flags] --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo

You don’t need to build your whole workspace with debug flags, just the package you want to debug. Thus, you can use packages-select to compile a single package, like so:

$ colcon build --packages-select [package_name] --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo 

Step 2: Launch node with GDB attached

The two most common methods of launching with GDB attached are to either execute the process inside of GDB (Option 1), or to launch a gdbserver instance alongside the process, then target it in a separate terminal instance (Option 2). The latter can be useful if you’re doing remote development of some sort, and cannot spawn new sessions easily.

Case 1: Launch standalone

Option 1: Through GDB
$ gdb
(gdb) exec-file [workspace]/build/lib/[ros_package_name]/[node_name]
(gdb) start
Option 2: With ros2 launch, attaching a gdb-server to the process
$ ros2 run --prefix 'gdbserver localhost:3000' [package_name] [executable_name]

In a second terminal:

$ gdb
(gdb) target remote localhost:3000

Case 2: Configure in your launch file

GDB can be launched as part of your launch files, in the form of a launch prefix. This allows you to essentially set and forget, having the GDB session operate as part of the standard launch process. This is particularly useful if your node requires a number of parameters to be set.

Python launch file
your_node = Node(
    package='package',
    executable='executable',
    name='name',
    prefix=['gdbserver localhost:3000'],
    output='screen')
XML launch file
<launch>
  <node pkg="pkg" exec="node" name="name" launch-prefix="gdbserver localhost:3000">
  </node>
</launch>
Spawning a terminal instance

If you prefer to not use gdbserver with your launch files, you can simply substitute gdbserver localhost:3000 with xterm -e gdb -ex run --args (replace XTerm with your terminal of choice).

An addition to your ~/.bashrc to streamline your workflow.

This function, when added to your ~/.bashrc will allow you to simply type $debug_node $node_name arg1 arg2, and a GDB instance will spawn, running the target node. I use this function more than a few times a day if I’m developing a node.

debug_node()
{
    # Launch a GDB session on {node}
    # Usage: debug_node {node_name}                        "
    local full_name="$(ros2 pkg executables | grep -i $*)"
    local node_name=$(echo "$full_name" | cut -d' ' -f2)
    local pkg_name=$(echo "$full_name" | cut -d' ' -f1)
    if [ -z "$node_name" ]
    then
        echo "ERROR! Could not find node: $*, available candidates:"
        ros2 pkg executables
    else
        local pkg_dir=$(ros2 pkg prefix $pkg_name)
        gdb --args $pkg_dir/lib/$pkg_name/$node_name
    fi
}

It will also give you a list of executable options, if it fails to find the requested node.

A plug for gdb-dashboard

Without question, my favourite extension to GDB is the GDB Dashboard, it will completely transform how you use GDB, and I cannot recommend it enough.

drawing

Another plug for Beej’s guide to GDB

I’ll say it again, the best introduction to GDB is Beej’s Quick Guide to GDB. Strong recommend if you’re new to using GDB.

Fin

In the future, I’d love to start playing around with using rr for debugging. I’ve heard nothing but good things about it.