您正在阅读的是旧版本但仍受支持的 ROS 2 文档。 Jazzy.
在 ROS 2 中获取回溯
目标 展示 ROS 2 中获取回溯的各种方法
辅导水平: 中级
时间 15 分钟
下面的步骤向 ROS 2 用户展示了如何在遇到问题时获取回溯。
概述
什么是回溯?
想象一下,您的程序就像一叠薄饼,每张薄饼代表当前正在执行的函数。回溯就像一张煎饼堆倒塌的照片,向你展示了它们的顺序,揭示了程序是如何导致失败的。
它列出了导致故障点的函数调用顺序,一个接一个。
为什么有用?
准确定位问题: 反向跟踪会显示导致崩溃的确切行号,而不是猜测代码中发生错误的位置。
揭示内涵: 您可以看到最终引发故障的一系列事件(函数调用其他函数)。这不仅能帮助你了解哪里出了问题,还能帮助你了解原因。
视觉类比:煎饼堆
每个薄饼都是一个函数想象一下,堆栈中的每个薄饼都代表着程序当前正在执行的一个函数。最下面的薄饼是你的 main() 函数,一切从这里开始。
添加薄饼每当一个函数调用另一个函数时,就会在堆栈顶部放置一个新的薄饼。
崩溃崩溃就像从堆栈底部滑落的盘子--当前正在执行的函数出了大问题。
回溯:回溯就像是掉落的煎饼堆的照片。它显示了薄饼(功能)从上到下的顺序,揭示了你是如何到达坠机地点的。
代码示例:
空白 函数 C() {
// 这里发生了不好的事情,导致崩溃
}
空白 函数 B() {
函数 C();
}
空白 函数 A() {
函数 B();
}
int 主要() {
函数 A();
返回 0;
}
崩溃后的回溯
#0 functionC() at file.cpp:3 // 这里发生崩溃
#1 functionB() at file.cpp:8
#2 functionA() at file.cpp:13
#3 main() at file.cpp:18
回溯如何提供帮助
碰撞起源: 显示
函数 C()
引发了崩溃。呼叫序列: 揭示了
main()
人称函数 A()
其中称函数 B()
最终导致了函数 C()
.
上面的例子让我们清楚地了解了什么是回溯以及回溯的作用。下面的步骤将向 ROS 2 用户展示如何在遇到问题时从特定节点获取轨迹。本教程适用于模拟机器人和物理机器人。
本节将介绍如何使用 玫瑰2 运行
,从代表单个节点的启动文件中使用 玫瑰2 启动
以及更复杂的节点协调。本教程结束时,您应该能够在 ROS 2 中发现节点崩溃时获得回溯。
序言
GDB 是 Unix 系统上最常用的 C/C++ 调试器。它可用于确定崩溃原因和跟踪线程。它还可用于在代码中添加断点,以检查软件中特定点的内存值。
对于所有使用 C/C++ 的软件开发人员来说,使用 GDB 都是一项关键技能。虽然许多集成开发环境都内置了某种调试器或剖析器,但重要的是要了解如何使用这些可用的原始工具,而不是依赖集成开发环境提供的工具。了解这些工具是 C/C++ 开发的一项基本技能,如果您更换了角色,无法再使用集成开发环境,或者正在通过 ssh 会话到远程资产进行即时开发,那么将其留给集成开发环境就会造成问题。
幸运的是,在掌握了基础知识之后,使用 GDB 就变得相当简单了。下面介绍如何确保 ROS2 代码已为调试做好准备:
通过使用
--cmake-args
:包含调试符号的最简单方法是添加--cmake-args -DCMAKE_BUILD_TYPE=Debug
到您的胶管 构建
指挥:
胶管 构建 --包至 <package_name>; --cmake-args -dcmake_build_type(构建类型=调试
通过编辑
CMakeLists.txt
:另一种方法是添加-g
编译器标志。该标记会生成调试符号,GDB 可以通过读取这些符号来判断项目中出现故障的具体代码行及其原因。如果不设置此标志,你仍然可以获得回溯,但它不会提供失败代码的行号。
现在你可以调试代码了!如果这是一个非 ROS 项目,此时你可能会像下面这样做。在这里,我们启动一个 GDB 会话,并告诉程序立即运行。一旦程序崩溃,它将返回一个 gdb 会话提示,以 (gdb)
.在这个提示符下,你可以访问你感兴趣的信息。不过,由于这是一个 ROS 项目,需要进行大量节点配置和其他工作,因此对于初学者或不喜欢大量命令行工作和不了解文件系统的人来说,这并不是一个好选择。
gdb 从 运行 --参数 /path/to/exe/program
下面的章节描述了基于 ROS 2 的系统可能遇到的三种主要情况。请阅读最能描述您试图解决的问题的章节。
使用 GDB 调试特定节点
要在启动 ROS 2 节点前轻松设置 GDB 会话,可利用 --前缀
选项可在启动 ROS 2 节点前轻松设置 GDB 会话。对于 GDB 调试,使用方法如下:
备注
请记住,ROS 2 可执行文件可能包含多个节点。节点 --前缀
这种方法可以确保你调试的是进程中正确的节点。
为什么直接使用 GDB 会很麻烦?
--前缀
将在我们的 ROS 2 命令之前执行一些代码,允许我们插入一些信息。如果您尝试 gdb 从 运行 --参数 玫瑰2 运行 <pkg>; node>;
与我们在序言中的例子类似,你会发现它找不到 玫瑰2
命令。此外,试图在 GDB 中获取工作区的源代码也会因类似原因而失败。这是因为 GDB 在以这种方式启动时缺乏环境设置,而环境设置通常会使 玫瑰2
命令可用。
使用前缀简化流程
我们可以使用 --前缀
.这样我们就可以使用相同的 玫瑰2 运行
而无需担心 GDB 的一些细节。
玫瑰2 运行 --前缀 'gdb -ex run --args'; <pkg>; node>; --其他所有发射 论点
GDB 体验
与之前一样,该前缀将启动一个 GDB 会话,并使用所有额外的命令行参数运行您请求的节点。现在,你的节点应该已经在运行了,并会进行一些调试打印。
读取堆栈跟踪
使用 GDB 获取回溯后,以下是解释回溯的方法:
从底部开始:回溯以相反的时间顺序列出函数调用。最下面的函数就是崩溃的源头。
沿着堆栈向上上面的每一行都代表调用下面函数的函数。向上追溯,直到找到自己项目中的一行代码。这通常能揭示问题的起因。
调试线索:函数名及其参数可以提供有价值的线索,帮助我们了解出错的原因。
节点崩溃时如何调试
一旦节点崩溃,就会出现如下提示。此时,您可以获取回溯信息。
(gdb)
在此环节中,键入 追溯
它将为您提供回溯信息。请根据需要复制。
回溯示例
(gdb) 追溯
#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 0x0000555555936c in std::vector<int, std::allocator<int> >::_M_range_check (
此=0x5555555cfdb0、 __n=100) 于 /usr/include/c++/9/bits/stl_vector.h:1070
#8 0x00005555558e1d in std::vector<int, std::allocator<int> >::at (this=0x5555555cfdb0、
__n=100) 于 /usr/include/c++/9/bits/stl_vector.h:1091
#9 0x000055555555828b in GDBTester::VectorCrash (this=0x5555555cfb40)
于 /home/steve/Documents/nav2_ws/src/gdb_test_pkg/src/gdb_test_node.cpp:44
#10 0x0000555555559cfc in main (argc=1, argv=0x7fffffffc108)
于 /home/steve/Documents/nav2_ws/src/gdb_test_pkg/src/main.cpp:25
在这个示例中,您应该从底部开始,按以下方式阅读:
在主函数的第 25 行,我们调用了一个函数 VectorCrash。
在 VectorCrash 的第 44 行,我们在 Vector 的
在()
方法,输入100
.它坠毁在
在()
在 STL 向量第 1091 行,因范围检查失败而产生异常。
阅读这些轨迹需要一些时间,但一般来说,从最底层开始,沿着堆栈一直向上,直到看到崩溃的那一行。然后就能推断出崩溃的原因了。使用 GDB 完成后,键入 烟
就会退出会话,并杀死所有仍在运行的进程。最后可能会问你是否要杀死某些线程,请说是。
从启动文件
就像在非 ROS 示例中一样,我们需要在启动 ROS 2 启动文件前设置一个 GDB 会话。虽然我们可以通过命令行进行设置,但我们也可以利用在 玫瑰2 运行
节点示例,现在使用启动文件。
在启动文件中找到要调试的节点。在本节中,我们假设启动文件只包含一个节点(也可能包含其他信息)。节点 节点
中使用的 发射
包将接收一个字段前缀和一系列前缀参数。我们将在此处插入 GDB 片段。
根据您的设置,可以考虑以下方法:
使用图形用户界面进行本地调试 : 如果在本地调试并有图形用户界面系统,请使用
词头=['xterm -e gdb -ex run --args';]
这将提供交互性更强的调试体验。在以下基础上进行调试的用例 启动同步工具箱节点
-
start_sync_slam_toolbox_node = 节点(
参数=[
获取软件包共享目录("slam_toolbox";) + '/config/mapper_params_online_sync.yaml';,
{'use_sim_time': 使用模拟时间}
],
包装='slam_toolbox',
可执行='sync_slam_toolbox_node';,
名字='slam_toolbox',
词头=['xterm -e gdb -ex run --args';], # 在单独的窗口/用户界面中使用交互式 GDB
产量='屏幕';)
远程调试(无图形用户界面): 如果不使用图形用户界面进行调试,则省略
xterm -e
:
词头=['gdb -ex run --args';]
GDB 的输出和交互将在您启动 ROS 2 应用程序的终端会话中进行。下面是一个类似的示例 启动同步工具箱节点
-
start_sync_slam_toolbox_node = 节点(
参数=[
获取软件包共享目录("slam_toolbox";) + '/config/mapper_params_online_sync.yaml';,
{'use_sim_time': 使用模拟时间}
],
包装='slam_toolbox',
可执行='sync_slam_toolbox_node';,
名字='slam_toolbox',
词头=['gdb -ex run --args';], # 用于启动终端内的 GDB
产量='屏幕';)
和以前一样,该前缀将启动一个 GDB 会话,现在在 xterm
并在定义了所有附加启动参数后运行您请求的启动文件。
节点崩溃后,您会看到如下提示,现在在 xterm
会话。此时,您可以获取回溯信息,并使用 读取堆栈跟踪.
来自大型项目
处理包含多个节点的启动文件则略有不同,这样你就可以与 GDB 会话进行交互,而不会被同一终端中的其他日志所拖累。因此,在处理较大的启动文件时,最好先取出你感兴趣的特定节点,然后单独启动它。
如果您感兴趣的节点是通过嵌套启动文件(如包含的启动文件)启动的,您可能需要执行以下操作:
注释父启动文件中包含的启动文件
重新编译感兴趣的软件包
-g
调试符号的标志在终端中启动父启动文件
在另一个终端中启动节点的启动文件,具体操作请参考 从启动文件.
或者,如果您感兴趣的节点是直接在这些文件中启动的(例如,您看到一个 节点
, 生命周期节点
或在 组件容器
),您需要将其与其他内容分开:
从父启动文件中注释出节点的包含内容
重新编译感兴趣的软件包
-g
调试符号的标志在终端中启动父启动文件
在另一个终端中启动节点,具体操作请参阅 使用 GDB 调试特定节点.
备注
在这种情况下,如果该节点以前由启动文件提供,则可能需要重新映射或提供参数文件。使用 --ros-args
可以为其提供新参数文件的路径、重映射或名称。参见 本教程 所需的命令行参数。
我们知道这样做可能会很麻烦,因此您可以将每个节点作为单独的启动文件,这样调试起来会更容易。参数集示例如下 --ros-args -r __node:=<node_name>; --参数文件 /absolute/path/to/params.yaml
(作为模板)。
节点崩溃后,您将在特定节点的终端看到如下提示。此时,您可以获取回溯信息,并使用 读取堆栈跟踪.
使用 GDB 调试测试
如果 C++ 测试失败,可直接在构建目录中的测试可执行文件上使用 GDB。确保在调试模式下编译代码。由于 CMake 可能会缓存之前的编译类型,因此请清除缓存并重新编译。
colcon build --cmake-clean-cache --mixin debug
为了让 GDB 能够为任何调用的共享库加载调试符号,请务必为环境设置源代码。这将配置 ld_library_path
.
source install/setup.bash
最后,直接通过 GDB 运行测试。例如
gdb -ex run ./build/rcl/test/test_logging
如果代码抛出了一个未处理的异常,可以在 gtest 处理之前在 GDB 中捕获它。
gdb ./build/rcl/test/test_logging
接抛
运行
崩溃时自动回溯
"(《世界人权宣言》) 后向-cpp 库提供了漂亮的堆栈跟踪,而 后退 包装器简化了集成过程。
只需将其添加为依赖项,然后 查找软件包
在 CMakeLists 中加入该库,这样所有可执行文件和库中都会注入后向库。