土豆泥老师 (知乎:@土豆泥): 上海交通大学在读博士.
一、前期知识
了解GDB的运行机制:首先需要生成带调试信息的可执行文件,然后用调试模块启动这个文件,即可进入调试。
了解Apollo的运行机制:在编译之后会生成可执行文件mainboard,并且会把各个模块编译成动态库。然后由mainboard去调用具体模块的动态库,从而启动各个模块。
二、单步调试的3种方式
直接用GDB的命令行调试。 对于小型项目还好,对于像Apollo这样的大型项目不方便,参数复杂,容易出错。 在vscode中直接调用GDB进行调试。需要配置json文件,本文采用这种方式。 使用cmake构建项目,在cmake tools中调用gdb进行调试。但是,这需要项目在开发初期就采用cmake方式构建,由于apollo包管理构建方式为bazel,如果要用cmake,就要重写很多cmakeLists文件,并且熟悉各模块之间的依赖和包含关系。这对我们来说是不划算的。
三、Apollo调试具体步骤
用vscode打开Apollo工作目录。
首先打开Apollo源码所在的工作目录,在该目录下打开终端,输入
code .
安装Docker插件并配置 Docker Dev Containers
安装插件后,在进入容器前,还需要进行一个配置。
按住ctrl+shfit+P,
选择→开发容器:打开附加的容器配置文件…。
选择→ register.baidubce.com/apollo/apollo-env-gpu:9.0-latest。
打开一个json的文件,对该文件做如下配置:
{
"extensions":[ //容器中安装的插件,可以先不配置,进入容器后单独安装
"franneck94.c-cpp-runner",
"ms-ceintl.vscode-language-pack-zh-hans",
"ms-vscode.cmake-tools",
"ms-vscode.cpptools",
"ms-vscode.cpptools-extension-pack",
"ms-vscode.cpptools-themes",
"twxs.cmake",
"vadimcn.vscode-lldb"
],
"workspaceFolder":"/", //指定容器挂载的目录
"remoteUser": "mash" //指定用户名,具体用户名在@in-dev-docker前面的那个
}
启动容器,并附加到vscode
在vscode中打开终端,并启动容器
aem start
aem enter
此时,可以看到Docker选项下,在CONTAINERS中容器已经处在打开的状态;右键点击该容器,并选择附加Visual Studio Code
通过这种方式打开一个新的窗口,就进入到这个容器中了。此时,窗口挂载在工作空间的根目录下,而我们的工作空间在apollo_workspace中,包括接下来要调试的源代码也在其中。
注意:由于容器是一个全新的环境,相当于在新环境中打开了vscode。此时需要重新安装C++、Cmake等插件。
查看运行中的节点
在容器窗口中打开终端,输入
cyber_node list
这时候应该没有节点运行,输出下图所示的结果
下载并安装planning源码
这部分在之前安装apollo的时候应该已经完成了,如果没有完成,按照顺序执行下列步骤
//先进入工作空间
cd /apollo_workspace
//检查buildtool版本
buildtool -v
//如果buildtool版本需要更新,使用以下指令更新(不过对于刚刚安装apollo用户,一般不需要)
sudo apt update && sudo apt install --only-upgrade apollo-neo-buildtool
//下载安装依赖包:会拉取安装core目录下的cyberfile.xml里面的所有依赖包
buildtool build -p core
//下载planning_base源码(其他包可以在core/cyberfile.xml查看)
buildtool install planning
编译源码
生成带有调试信息的可执行文件
buildtool build —dbg -j 10 -m 0.7 -p modules/planning
其中:
—dbg是添加调试信息;
-j 10 是使用10个进程;
-m 0.7是使用70%的内存;
关于buildtool的使用,可参考官方安装文档。位置为:文档→个人开发者文档→框架设计→软件核心→包管理工具→buildtool
根目录下新建文件夹.vscode,创建launch.json文件
//回到根目录
cd
//新建文件夹
mkdir .vscode
//添加权限
sudo chmod -R 777 .vscode/
//进入目录
cd .vscode/
//创建launch.json文件
touch launch.json
这个时候,这个文件应该具备可读可写可执行的权限了,接下来对json进行配置
{
"version":"0.2.0",
"configurations":[
{
"name":"gdb planning",
"type":"cppdbg",
"request":"launch",
"program":"/opt/apollo/neo/bin/mainboard",
"args":[ //传递给mainboard的参数
"-d", //dag文件所在路径
"/apollo_workspace/modules/planning_component/dag/planning.dag",
"-p", //进程所在的命名空间
"planning",
"-s",
"CYBER_DEFAULT"
],
"stopAtEntry":false,
"cwd":"/apollo_workspace", //源码所存放的目录
"environment":[],
"externalConsole":false,
"MIMode":"gdb",
"setupCommands":[
{
"description":"为 GDB 启用整齐打印",
"text":"-enable-pretty-printing",
"ignoreFailures":true
},
{
"description":"防止 GDB 打开标准函数库",
"text":"-interpreter-exec console\"skip -rfu std::.*\"",
"ignoreFailures":true
}
],
"miDebuggerPath":"/usr/bin/gdb" //gdb所在路径
}
]
}
检查一下容器内是否安装了GDB
which GDB
如果没有输出路径,则执行下述代码重新安装GDB
sudo apt install gdb
//查看一下版本
gdb --version
8.调试步骤
接下来就可以进行调试了。打开要调试的代码,以planning component为例。这个是planning模块的一个入口,在planning模块被调用后,会优先进入这一部分的代码。
我们在planning_base_→Init(config_);这一行打一个断点。在左侧选择和刚刚json文件对应的配置,点击绿色三角按钮开始,或者按F5。程序会成功命中这个断点。
同时,再开两个终端窗口,一个是cyber_monitor,现在由于程序刚刚启动,在非常早的位置就被停住了,所以cyber_monitor中还没有数据。在另一个终端中输入cyber_node list,同样没有启动的节点。
接下来按单步调试,或F11,会进入到OnLanePlanning::Init这个函数中了,这就是单步调试的过程。
按F5,让程序运行起来。
左侧终端里出现了一些打印信息,显示右边的宽度太小了,应该大于半个车身的宽度,就说明planning模块已经启动了,由于现在没有地图,没有其他输入,所以会有这样的一个判断,这是没关系的。
在cyber_monitor中已经有数据了,都是planning模块相关的一些数据。
在最右侧终端再次输入cyber_node list,看有哪些节点运行了,可以看到有Monitor相关的节点,然后planning,作为一个节点也运行了,就说明我们的调试是成功的。
9.改写源码后重新编译,再调试验证
在on_lane_planning.cc中加入一行
std::cout << "从零开始学Apollo" <<std::endl;
然后重新编译后,先运行到断点,单步调试到这句代码下,可以看到正确打印了,说明我们刚刚的修改已经生效了。
总结
这一部分一共分了三个章节,分别介绍了Apollo环境安装、vscode中代码下载与编译、以及在vscode中使用GDB对Apollo代码进行单步调试。这其中不仅涉及到一些Apollo工程构建的知识点(例如bazel,docker),也包含C++工程调试的基本方法,需要反复练习。
如果我们需要在Apollo代码基础上开发自己的代码,不要像上文演示中直接修改原代码。Cyber RT中提供了组件、插件机制,能够帮助我们扩展代码。因此,接下来将对Apollo的操作系统Cyber RT进行学习。
推荐阅读:
🏎️自动驾驶小白说官网:https://www.helloxiaobai.cn