查看原文
其他

开发者说丨使用GDB调试Apollo项目

贺志国 Apollo开发者社区 2022-07-29


社区公众号曾经推送过一篇《技术文档丨使用VSCode构建、调试Apollo项目 》,基本能满足Apollo项目的调试需求,但直接在终端中使用GDB调试Apollo项目,灵活性更强。本文简介借助GDB调试Apollo项目的基本方法,希望给大家学习Apollo带来一定的帮助。


下面是由社区荣誉布道师——贺志国提供的文章使用GDB调试Apollo项目进行详细讲解,希望这篇文章能给感兴趣的开发者带来更多帮助。



  以下,ENJOY  




1# 进入Apollo项目根目录(我的路径为:`~/code/apollo`,你需要修改为自己的路径)
2cd ~/code/apollo
3# 启动Apollo项目的Docker(注意:2.0以上版本在后面加上一个“-C”选项,
4# 表示从中国服务器拉取镜像文件,以加快下载速度)
5bash docker/scripts/dev_start.sh
6# 进入Docker
7bash docker/scripts/dev_into.sh

<左右滑动以查看完整代码>





使用GDB调试Apollo项目必须带有调试符号信息,因此编译Apollo项目时,不能使用opt选项,可根据实际需求使用如下两个编译命令中任意一个进行构建:

1# 构建方法1:使用8个线程(根据你的CPU核数确定)编译Apollo项目,不使用GPU也不优化
2bash apollo.sh build -j 8
3# 构建方法2:使用8个线程(根据你的CPU核数确定)编译Apollo项目,使用GPU但不优化
4bash apollo.sh build_gpu -j 8

<左右滑动以查看完整代码>




以Planning模块为例说明。完成第2步编译后,会在/apollo/bazel-bin/modules/planning目录中生成可执行文件Planning

首先启动Apollo后台核心进程和Dreamview

1bash scripts/bootstrap.sh

<左右滑动以查看完整代码>


Dreamview中开启与Planning模块相关的其他模块进程,这个需要根据实际情况确定,无法给出统一的操作方法。



接下来,可以使用如下几种方法启动Planning模块的调试:

1# 启动方法1:直接使用GDB启动planning模块,注意后面的flagfile需根据需要指定,
2# 如果是Navigation模式,则有--flagfile=/apollo/modules/planning/conf/planning_navi.conf
3# 注意:--args及后面的--flagfile也可以先不设置,而在进入GDB调试界面后,使用set args
4# 命令进行设置,下同。
5gdb -q --args bazel-bin/modules/planning/planning --flagfile=/apollo/modules/planning/conf/planning.conf
6
7# 启动方法2:借助Apollo提供的脚本程序,注意后面的flagfile需根据需要指定,
8# 如果是Navigation模式,则有--flagfile=/apollo/modules/planning/conf/planning_navi.conf
9bash scripts/planning.sh start_gdb --flagfile=/apollo/modules/planning/conf/planning.conf
10
11# 启动方法3:在Dreamview中启动Planning模块,然后使用ps aux | grep planning命令查找
12# planning进程ID(PID),假设为35872,则使用attach模式附加到当前planning进程调试
13sudo gdb -q bazel-bin/modules/planning/planning -p 35872

<左右滑动以查看完整代码>


注意:如果需要调试各模块内部包含的一些小工具程序,则只能使用如下方法启动调试(以modules/planning/reference_line/smoother_util.cc为例进行说明):

1# 注意:--args及后面的一串参数也可以先不设置,而在进入GDB调试界面后,使用set args
2# 命令进行设置。
3gdb -q --args bazel-bin/modules/planning/reference_line/smoother_util --input_file /apollo/data/bag/record_data.txt --smooth_length 200

<左右滑动以查看完整代码>


Apollo 3.5以上版本(基于Cyber RT框架)的调试启动命令:


Apollo 3.5以上版本使用
Cyber RT进行任务调度与通信,调试功能模块的命令更新为(进入GDB后的操作方法相同):


1gdb -q --args /apollo/bazel-bin/cyber/mainboard -d /apollo/modules/planning/dag/planning.dag
2sudo gdb -q /apollo/bazel-bin/cyber/mainboard -p 35872

<左右滑动以查看完整代码>




进入GDB调试界面后,可以使用如下常见命令调试:


注意:因为Apollo项目文件很多,不要过多使用
TAB键进行提示,否则可能会出现响应异常缓慢的现象。



关于打印STL库元素方面更多的内容,可以参考这篇文章:《打印STL容器中的内容》

https://www.kancloud.cn/wizardforcel/gdb-tips-100/146748


下图给出了一个显示STL容器调试的一个示例:




1(gdb) p (*(task_list_._M_impl._M_start))->Name()          
2$9 = "LANE_CHANGE_DECIDER"
3(gdb) p (*(task_list_._M_impl._M_start+1))->Name()
4$10 = "PATH_LANE_BORROW_DECIDER"
5(gdb) p (*(task_list_._M_impl._M_start+2))->Name()
6$11 = "PATH_BOUNDS_DECIDER"
7(gdb) p (*(task_list_._M_impl._M_start+3))->Name()
8$12 = "PiecewiseJerkPathOptimizer"
9(gdb) p (*(task_list_._M_impl._M_start+4))->Name()
10$13 = "PATH_ASSESSMENT_DECIDER"
11(gdb) p (*(task_list_._M_impl._M_start+5))->Name()
12$14 = "PathDecider"
13(gdb) p (*(task_list_._M_impl._M_start+6))->Name()
14$15 = "SpeedBoundsDecider"
15(gdb) p (*(task_list_._M_impl._M_start+7))->Name()
16$16 = "DpStSpeedOptimizer"
17(gdb) p (*(task_list_._M_impl._M_start+8))->Name()
18$17 = "SpeedDecider"
19(gdb) p (*(task_list_._M_impl._M_start+9))->Name()
20$18 = "SpeedBoundsDecider"
21(gdb) p (*(task_list_._M_impl._M_start+10))->Name()
22$19 = "PiecewiseJerkSpeedOptimizer"
23(gdb) p (*(task_list_._M_impl._M_start+11))->Name()
24$20 = "RssDecider"
25(gdb) p (*(task_list_._M_impl._M_start+12))->Name()
26$21 = <error reading variable: cannot access memory at address 0x4024000000000048>
27(gdb) where
28#0  0x00007f66aab24278 in apollo::planning::scenario::lane_follow::LaneFollowStage::PlanOnReferenceLine (this=0x22baa70, planning_start_point=..., frame=0x7f66c00470f0, reference_line_info=0x7f66c004f9e0)
29    at modules/planning/scenarios/lane_follow/lane_follow_stage.cc:163
30#1  0x00007f66aab23ad4 in apollo::planning::scenario::lane_follow::LaneFollowStage::Process (this=0x22baa70, planning_start_point=..., frame=0x7f66c00470f0) at modules/planning/scenarios/lane_follow/lane_follow_stage.cc:125
31#2  0x00007f66a99ba732 in apollo::planning::scenario::Scenario::Process (this=0x22ba5d0, planning_init_point=..., frame=0x7f66c00470f0) at modules/planning/scenarios/scenario.cc:76
32#3  0x00007f66ab5f553a in apollo::planning::PublicRoadPlanner::Plan (this=0x2273e30, planning_start_point=..., frame=0x7f66c00470f0, ptr_computed_trajectory=0x7f66247fedf0) at modules/planning/planner/public_road/public_road_planner.cc:51
33#4  0x00007f66d0239130 in apollo::planning::NaviPlanning::Plan (this=0x223c1f0, current_time_stamp=1557975960.7090025, stitching_trajectory=std::vector of length 1, capacity 1 = {...}, trajectory_pb=0x7f66247fedf0) at modules/planning/navi_planning.cc:486
34#5  0x00007f66d0236cf5 in apollo::planning::NaviPlanning::RunOnce (this=0x223c1f0, local_view=..., trajectory_pb=0x7f66247fedf0) at modules/planning/navi_planning.cc:268
35#6  0x00007f66b230c494 in apollo::planning::PlanningComponent::Proc (this=0x1bca110, prediction_obstacles=std::shared_ptr (count 4, weak 00x7f661c076338, chassis=std::shared_ptr (count 7, weak 00x7f661c0663f8
36    localization_estimate=std::shared_ptr (count 7, weak 00x7f661c05e688) at modules/planning/planning_component.cc:134
37#7  0x00007f66b23b36c4 in apollo::cyber::Component<apollo::prediction::predictionobstacles, apollo::cyber::nulltype="">::Process (this=0x1bca110
38    msg0=std::shared_ptr (count 4, weak 00x7f661c076338, msg1=std::shared_ptr (count 7, weak 00x7f661c0663f8, msg2=std::shared_ptr (count 7, weak 00x7f661c05e688) at ./cyber/component/component.h:291
39#8  0x00007f66b23a1698 in apollo::cyber::Component<apollo::prediction::predictionobstacles, apollo::cyber::nulltype="">::Initialize(apollo::cyber::proto::ComponentConfig const&)::{lambda(std::shared_ptr<apollo::prediction::predictionobstacles> const&, std::shared_ptr<:canbus::chassis> const&, std::shared_ptr<apollo::localization::localizationestimate> const&)#2}::operator()(std::shared_ptr<apollo::prediction::predictionobstacles> const&, std---Type <return> to continueor q <return> to quit---
</apollo::prediction::predictionobstacles></apollo::localization::localizationestimate></apollo::prediction::predictionobstacles></apollo::prediction::predictionobstacles,></apollo::prediction::predictionobstacles,></error reading variable: cannot access memory at address 

<左右滑动以查看完整代码>




实际使用时,可以将VSCodeGDB结合起来使用。一般使用VSCode上方的文本编辑器显示源代码文件,在VSCode下方的终端窗口进行GDB调试,如下图所示:



在进入GDB界面后,使用b命令设置相关断点,使用r命令启动待调试进程,待运行至断点处后,再根据具体需要合理使用nscpbt等命令进行单步调试。


注意:如果使用attach模式附加到已有进程PID调试,则不能使用r命令启动进程,而必须使用c命令继续执行当前进程。否则,GDB永远不会跳转至你所设置的断点处。



调试过程中,如果想执行某条Shell命令(例如查找某个文件是否存在),可不必退出GDB界面而直接操作,具体方法如下:

1# 方法1
2shell command-string
3# 例如在所有目录中查找apollo.sh是否存在
4shell sudo find / -name "apollo.sh"
5# 方法2
6!command-string
7# 例如在所有目录中查找apollo.sh是否存在
8!sudo find / -name "apollo.sh"

<左右滑动以查看完整代码>



有时可能需要将调试信息输出到日志文件,操作方法如下:

进行GDB调试界面后,使用如下命令进行日志输出设置:

1# 打开调试日志,这时GDB会在当前目录中生成一个“gdb.txt”文件,并将调试信息输出到该文件
2set logging on
3# 关闭调试日志
4set logging off
5# 将调试日志输出到指定文件/apollo/data/log/davidhopper_gdb.txt
6set logging file /apollo/data/log/davidhopper_gdb.txt
7set logging on

<左右滑动以查看完整代码>





考虑到某些特殊情形,可能需要暂时退出当前调试,待下次重启调试时又希望自动加载当前所有断点,GDB完全支持该需求,操作方法如下:


保存断点到指定文件


1# 在GDB调试界面,将当前设置的所有断点全部保存到文件/apollo/data/log/a.break
2save b /apollo/data/log/a.break

<左右滑动以查看完整代码>



从指定文件加载断点


1# 在GDB调试界面,从文件/apollo/data/log/a.break中恢复所有断点
2source /apollo/data/log/a.break

<左右滑动以查看完整代码>



在调试过程中,不可避免地需要重新编译代码,这时不必退出GDB,只需在外部重新编译代码后(也可参考5.3 在GDB中执行Shell命令的方法直接在GDB内部重新编译),在GDB内部使用指令r重新运程程序,GDB会自动更新程序状态。下面以一个小实例进行具体说明:


示例很简单,就是将modules/planning/planning.cc中的Status Planning::Start()函数注释一行代码AINFO << "Planning started";,如下所示:

1Status Planning::Start() {
2  timer_ = AdapterManager::CreateTimer(
3      ros::Duration(1.0 / FLAGS_planning_loop_rate), &Planning::OnTimer, this);
4  // The "reference_line_provider_" may not be created yet in navigation mode.
5  // It is necessary to check its existence.
6  if (reference_line_provider_) {
7    reference_line_provider_->Start();
8  }
9  start_time_ = Clock::NowInSeconds();
10  // Note that the following line will be commented.
11  AINFO << "Planning started";
12  return Status::OK();
13}

<左右滑动以查看完整代码>


以下是修改之前的modules/planning/planning.cc文件中的Status Planning::Start()函数:



顺利编译Apollo项目后,在Docker内部使用指令gdb -q --args bazel-bin/modules/planning/planning--flagfile=/apollo/modules/planning/conf/

planning.conf启动Planning模块调试,使用指令b modules/planning/planning.cc:206设置断点,使用指r运行Planning模块:



GDB会在断点planning.cc:206处暂停,使用指令n执行单步调试,可观察到语句AINFO << "Planning started";未被注释:



接下来,在VSCode中将语句AINFO << "Planning started";注释:



在GDB调试界面中,使用指令!bash apollo.sh build -j 8重新编译Apollo项目:



编译成功后,使用指令r并回答y重新运行Planning模块:



GDB同样在断点planning.cc:206处暂停,使用指令l显示断点附件处的代码,可观察到语句AINFO << "Planning started";已被注释,代码修改生效:



注意:如果使用attach模式附加到已有进程PID调试Planning模块,该方法不会生效。


以上是"使用GDB调试Apollo项目"的全部内容,更多话题讨论、技术交流可以扫描下方二维码添加『Apollo小哥哥』为好友,进开发者交流群。






* 以上内容为开发者原创,不代表百度官方言论。

内容来自开发者CSDN:

https://blog.csdn.net/davidhopper/article/details/83445976,欢迎大家收藏点赞。已获开发者授权,原文地址请戳阅读原文。






您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存