在ROS 2/Nav2中获取回溯信息 [校准@haisenzeng]
概述
本文将会说明一些列获取ROS 2和Nav2回溯信息的方法。有许多方法可以实现这一点,但这对于没有GDB经验的C++新手开发人员来说是一个很好的起点。 [校准@haisenzeng]
以下步骤向ROS 2用户展示了如何修改Nav2堆栈,以便在遇到问题时从特定服务器获取跟踪。本教程适用于模拟和物理机器人。 [校准@haisenzeng]
这将涵盖如何从使用 ros2 run
的特定节点、使用 ros2 launch
的单个节点的启动文件以及更复杂的节点组合中获取错误回溯信息。在本教程结束时,当遇到ROS 2中的服务器崩溃时,应该能够获得错误回溯信息。 [校准@haisenzeng]
Preliminaries
GDB是Unix系统上最流行的C++ 调试器。它可用于确定崩溃的原因并跟踪线程。它还可用于在代码中添加断点以检查软件中某个特定点在内存中的值。 [校准@haisenzeng]
对于所有在C/C ++ 上工作的软件开发人员来说,使用GDB是一项关键技能。许多IDE会内置某种调试器或分析器,但对于ROS,可供选择的IDE很少。因此,很重要的一点就是要了解如何使用这些可用的原始调试工具,而不是依赖IDE来提供调试工具。而且,了解这些调试工具是C/C++开发的一项基本技能,如果您更改开发人员的这个角色且不再有能力使用调试工具,或者正在通过ssh会话用远程调试工具进行动态开发,则将调试工具留给IDE可能会出问题。 [校准@haisenzeng]
幸运的是,在你掌握了基本知识后,使用GDB就相当简单。第一步是将 -g
添加到要分析/调试的ROS软件包的编译器标志中。这个标志会构建GDB和valgrind可以读取的调试符号,以告诉您该项目中失败的具体代码行及失败原因。如果没有设置这个标志,虽然仍可以获取错误回溯信息,但不会提供失败的代码行号。请确保在调试后删除此标志,因为它将降低运行时的性能。 [校准@haisenzeng]
为您的项目在 CMakeLists.txt
中添加这一行应该可以解决问题。如果项目已经有 add_compile_options()
这行代码 ,可以简单地在这行代码中添加 -g
。然后简单地用这个包 colcon build --packages-select <package-name>
重建您的工作空间。添加了-g标志后编译可能比平时花费更长的时间。 [校准@haisenzeng]
add_compile_options(-g)
现在,您可以调试代码了! 如果这是一个非ROS项目,此时您可能需要执行以下操作。启动一个GDB会话并告诉程序立即运行。一旦您的程序崩溃,它将返回一个由 (gdb)
表示的gdb会话提示。在此提示下,您可以访问您感兴趣的信息。然而,由于这是一个包含大量节点配置和其他任务的ROS项目,因此对于初学者或那些不喜欢大量命令行工作和了解文件系统的人来说,这并不是一个很好的选择。 [校准@haisenzeng]
gdb ex run --args /path/to/exe/program
以下部分描述了基于ROS 2的系统可能遇到的3种主要情况。请阅读最能描述你试图解决的问题的部分。 [校准@haisenzeng]
从一个节点进行回溯 [校准@haisenzeng]
就像在刚才那个非ROS项目示例中一样,需要在启动ROS 2节点之前设置GDB会话。虽然可以在具有ROS 2文件系统的一些知识的情况下通过命令行进行GDB会话设置,但还可以使用Open Robotics的好心人提供的 --prefix
启动选项。 [校准@haisenzeng]
参数``--prefix``会在 ros2
命令之前执行一些代码,允许插入一些信息。如果尝试执行类似于“前提条件”一节中的示例命令 gdb ex run --args ros2 run <pkg> <node>
会发现它找不到 ros2
命令。甚至如果您更聪明而尝试对工作空间进行source,还是会发现因类似原因而失败。 [校准@haisenzeng]
我们可以使用 --prefix
,而不必回头去查找可执行文件的安装路径并全部重新输入。这允许使用用户习惯的同一条ros2 run命令,而不必担心GDB的一些细节。 [校准@haisenzeng]
ros2 run --prefix 'gdb -ex run --args' <pkg> <node> --all-other-launch arguments
正如之前,这个前缀会启动一个GDB会话并运行您请求的带有所有附加命令行参数的节点。现在系统应该会让您的节点运行,并且应该会与一些调试打印信息一起运行。 [校准@haisenzeng]
一旦服务器崩溃,你会看到提示如下。此时你就可以获取错误回溯信息。 [校准@haisenzeng]
(gdb)
在本会话中,键入 backtrace
,它将为您提供错误回溯信息。根据您的需要复制这些信息。例如: [校准@haisenzeng]
(gdb) backtrace
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007ffff79cc859 in __GI_abort () at abort.c:79
#2 0x00007ffff7c52951 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff7c5e47c in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff7c5e4e7 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007ffff7c5e799 in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x00007ffff7c553eb in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#7 0x000055555555936c in std::vector<int, std::allocator<int> >::_M_range_check (
this=0x5555555cfdb0, __n=100) at /usr/include/c++/9/bits/stl_vector.h:1070
#8 0x0000555555558e1d in std::vector<int, std::allocator<int> >::at (this=0x5555555cfdb0,
__n=100) at /usr/include/c++/9/bits/stl_vector.h:1091
#9 0x000055555555828b in GDBTester::VectorCrash (this=0x5555555cfb40)
at /home/steve/Documents/nav2_ws/src/gdb_test_pkg/src/gdb_test_node.cpp:44
#10 0x0000555555559cfc in main (argc=1, argv=0x7fffffffc108)
at /home/steve/Documents/nav2_ws/src/gdb_test_pkg/src/main.cpp:25
In this example you should read this in the following way, starting at the bottom:
在main函数中的第25行,调用了一个函数VectorCrash。 [校准@haisenzeng]
在VectorCrash函数的第44行,在Vector的输入为
100
的at()
方法中崩溃。 [校准@haisenzeng]在从范围检查失败中抛出异常后,在STL vector第1091行上的at()中崩溃。 [校准@haisenzeng]
这些需要一些时间来适应这些跟踪阅读,但总的来说,从底部开始,沿着堆栈往上走,直到你看到它崩溃的那行。然后你可以推断出它崩溃的原因。使用完GDB完成错误回溯后,键入 quit
,它将退出会话并终止任何仍在运行的进程。最后系统可能会询问是否要关闭一些线程,回答yes即可。 [校准@haisenzeng]
从启动文件进行回溯 [校准@haisenzeng]
就像在非ROS示例中一样,我们需要在启动ROS 2启动文件之前设置GDB会话。虽然我们可以通过命令行来设置它,但是我们可以使用与在 ros2 run
节点示例中相同的机制,只是现在使用启动文件而不是可执行文件。 [校准@haisenzeng]
在启动文件中,找到想要进行调试的节点。本节中假设启动文件只包含一个节点(且可能还有其他信息)。 launch_ros
包中使用的 Node
函数可以接受一个字段 prefix
,其中包含前缀参数列表。我们将在这里插入GDB代码段,其中包含对节点示例的一个更改,即 xterm
的使用。 xterm
将弹出一个新的终端窗口来显示GDB并与之交互。我们这样做是因为在解决启动文件上的 stdin
问题 (例如,如果你点击了CTRL + C,您是在和GDB对话还是在与launch对话?)。有关更多信息,请参见 this ticket 。有关调试SLAM工具箱的示例,请参见下文。 [校准@haisenzeng]
start_sync_slam_toolbox_node = Node(
parameters=[
get_package_share_directory("slam_toolbox") + '/config/mapper_params_online_sync.yaml',
{'use_sim_time': use_sim_time}
],
package='slam_toolbox',
executable='sync_slam_toolbox_node',
name='slam_toolbox',
prefix=['xterm -e gdb -ex run --args'],
output='screen')
正如之前,此前缀将启动一个GDB会话,现在在 xterm
中且运行您请求的所有附加启动参数定义的启动文件。 [校准@haisenzeng]
一旦服务器崩溃,就会在 xterm
会话中看到如下所示的提示符。此时你可以获得错误回溯信息。 [校准@haisenzeng]
(gdb)
在此会话中,键入 backtrace
,这样它就会提供一个错误回溯信息。根据需要复制这些错误回溯信息。有关示例,请参阅上一节中的示例跟踪。 [校准@haisenzeng]
这些需要一些时间来适应这些跟踪阅读,但总的来说,从底部开始,沿着堆栈往上走,直到你看到它崩溃的那行。然后你可以推断出它崩溃的原因。使用完GDB完成错误回溯后,键入 quit
,它将退出会话并终止任何仍在运行的进程。最后系统可能会询问是否要关闭一些线程,回答yes即可。 [校准@haisenzeng]
从Nav2 Bringup软件包中进行回溯 [校准@haisenzeng]
使用具有多个节点的启动文件工作方式会有点不同,因此可以与GDB会话进行交互,而不会被同一终端中的其他日志记录所困扰。因此,在从更大型启动文件进行回溯时,最好把需要回溯的具体服务器节点从中独立出来并单独启动它。 [校准@haisenzeng]
因此,在这种情况下,当看到所调查的东西崩溃时,将此服务器节点与其他服务器节点分开是有益的。 [校准@haisenzeng]
如果想要回溯的服务器节点是从一个嵌套的启动文件(例如包含另一个启动文件的启动文件)中启动的,则可能需要执行以下操作: [校准@haisenzeng]
注释掉启动文件中对父级启动文件进行包含的包含语句 [校准@haisenzeng]
使用用于调试符号的
-g
标志重新编译感兴趣的软件包 [校准@haisenzeng]在终端中启动父启动文件 [校准@小鱼]
按照 From a Launch File 中的说明,在另一个终端中启动服务器的启动文件。 [校准@小鱼]
或者,如果您感兴趣的服务器正在这些文件中直接启动 (例如g.您看到的 Node
、 LifecycleNode
或 ComponentContainer
),你需要将该节点和其他节点分开: [校准@haisenzeng]
注释掉节点对父级启动文件进行包含的包含语句 [校准@haisenzeng]
使用用于调试符号的
-g
标志重新编译感兴趣的软件包 [校准@haisenzeng]在终端中启动父启动文件 [校准@小鱼]
备注
请注意,在这种情况下,如果之前由启动文件提供重映射或参数文件,则可能需要对该节点进行重映射或向该节点提供参数文件。使用 --ros-args
选项可以为该节点指定新参数文件的路径、重映射或名称。有关所需命令行参数的更多信息,请参见 this ROS 2 tutorial 。 [校准@haisenzeng]
我们知道这可能会很痛苦,所以鼓励您将每个可能的节点都作为一个单独包含的启动文件来简化调试。一组实例的参数可能是 --ros-args -r __node:=<node_name> --params-file /absolute/path/to/params.yaml
(作为模板)。 [校准@haisenzeng]
一旦服务器崩溃,就会在特定服务器的终端中看到如下所示的提示符。此时,就可以获取错误回溯信息了。 [校准@haisenzeng]
(gdb)
在此会话中,键入 backtrace
,这样它就会提供一个错误回溯信息。根据需要复制这些错误回溯信息。有关示例,请参阅上一节中的示例跟踪。 [校准@haisenzeng]
这些需要一些时间来适应这些跟踪阅读,但总的来说,从底部开始,沿着堆栈往上走,直到你看到它崩溃的那行。然后你可以推断出它崩溃的原因。使用完GDB完成错误回溯后,键入 quit
,它将退出会话并终止任何仍在运行的进程。最后系统可能会询问是否要关闭一些线程,回答yes即可。 [校准@haisenzeng]
基于系统崩溃时的自动错误回溯
backward-cpp 库提供了漂亮的堆栈跟踪功能,而 backward_ros 封装器则简化了该库的集成使用。 [校准@haisenzeng]
只需将该封装器添加为软件包的依赖项并在软件包的CMakeLists.txt文件中对该库使用 find_package 语句来查找它,这样上述两个backward库就会被植入到软件包的所有可执行文件和库中。 [校准@haisenzeng]