ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VSCode下Apollo断点调试全攻略:容器、符号与launch配置详解

VSCode下Apollo断点调试全攻略:容器、符号与launch配置详解 简介面向自动驾驶开发者讲解在 Visual Studio Code 中用 GDB 调试 Apollo 项目的完整思路与工具配置方法适合已有 C 基础、正在学习 Apollo 或需要排查代码运行问题的中高级开发者。资源包共 5 个文件以 4 个 JSON 配置为主分别对应 launch.json、tasks.json、settings.json、c_cpp_properties.json可直接参考或复用至本地 .vscode 目录另附 1 个 HTML 说明文档系统梳理启动调试会话、设置断点、单步执行、观察变量及调用栈等关键操作。压缩包整体约 5KB轻量精炼重点突出。已有 1141 人学习下载。结合 Apollo 工程特性资料针对 bazel 构建产物路径、gdb 调试参数、条件断点与日志输出等常见难点给出了配置示例与排错思路可帮助读者直接跳过繁琐的环境摸索快速建立起可用的 VS Code 调试环境并在调试过程中理解感知、规划、控制等模块的运行逻辑提升对大型开源系统的代码阅读与问题定位效率。1. 在VSCode里给Apollo下断点为什么这一步能把人卡一天在VSCode里给Apollo代码下断点听起来是填个launch.json的事实际第一步往往是断点是灰的、进程起不来、附加时报Operation not permitted。我见过把printf用得比谁都快的老手一换到断点就连连踩坑最后又退回printf大法。卡人的不是VSCode本身是Apollo工程的形状代码在Docker容器里编译走bazel模块跑在cyber上旁边还有保活机制盯心跳。这三样叠加断点能不能停、停了会不会被清掉全看前置配置严不严实。下面按实际能复现的路线讲先串通容器和VSCode再写一份能用的launch.json然后处理符号、路径和权限这些说不清的坑最后给几个一查一个准的调试招。适合已经能把Apollo编译起来、想在VSCode里看清数据流但还没跑通断点的人。2. VSCode连进Apollo开发环境容器边界、插件与三个前置项2.1 先确认代码真正跑在哪容器就是调试边界Apollo从6.0之后的标准开发路径基本是进Docker容器容器里编译、容器里运行宿主机通常只挂载一份源码目录。所以VSCode的断点调试必须触达容器内的进程换句话说gdb要能attach或launch容器里的程序。有人想在Windows宿主机的VSCode里直接调试Linux容器里的Apollo路径、符号、权限三层都对不上基本走不通Apollo的容器化开发环境是ubuntu调试这步也请认准Linux侧。先看当前有没有Apollo容器在跑。在宿主机执行docker ps | grep apollo docker inspect -f {{.Name}} {{.State.Status}} $(docker ps -q)正常情况下你会看到一个名字像apollo_dev_xxx或者apollo_local_xxx的容器。如果没有说明平时是现开现用先把容器拉起来再继续。容器这个边界想清楚了后面很多问题都能提前规避。2.2 两条连接路线Remote-Containers还是Remote-SSH不用急着找vscode安装教程本机VSCode装好只是开始远端的扩展和路径才决定调试顺不顺。连接方案有两条适合不同环境。路线打开的文件系统路径一致性主要坑Remote-Containers容器内文件系统完全一致省去sourceFileMap容器重建后要重连Remote-SSH连宿主机宿主机文件系统编译在容器内两者路径不同需要额外配置路径映射我一般首选Remote-Containers的Attach to Running Container让VSCode整个窗口开进Apollo容器。这样launch.json里写/apollo/...gdb里看到的源码路径天然一致少掉一层玄学。具体操作是装上remote-containers插件在左侧Remote Explorer里选中运行中的容器点Attach。VSCode会自动往容器里推一份server和C扩展第一次会慢一点后面再进就快得多。如果开发机只能通过SSH访问容器没法从本机直接暴露就换Remote-SSH连宿主机再把源码路径手动映射过去。这条路不是不能走只是调试多一个sourceFileMap要维护。新手还是走Remote-Containers更省心。2.3 三个前置项源码目录、bazel产物、环境脚本进容器之后别急着开调试先检查三件事它们分别对应launch.json里的cwd、program和environmentpwd # 源码根目录一般就是/apollo ls -d /apollo/modules 2/dev/null ls -l /apollo/bazel-bin/modules 2/dev/null | head find / -maxdepth 4 -name setup.bash -path *apollo* 2/dev/null第一项是源码根目录第二项是这个模块编译出来的可执行文件bazel-bin通常是符号链接第三项是Apollo环境脚本。不同版本脚本位置差别很大8.x、9.x常见/opt/apollo/neo/setup.bash更早的版本可能在/apollo/scripts/setup.bash。脚本里导出CYBER_PATH、LD_LIBRARY_PATH、PATH等变量没有这些被调试进程一启动就可能在动态链接阶段退出。注意bazel-bin本身是指向真实输出目录的软链后面配program时我习惯先用readlink -f拿真实路径避免调试器报program does not exist。2.4 容器里要有gdb、VSCode里装C扩展这一步其实就是在容器里做vscode配置c/c环境的最小集合容器里必须存在gdbVSCode这边必须装了ms-vscode.cpptools。调试器真正干活的是gdbVSCode只是图形壳这一点很多人一开始没意识到。which gdb gdb --version | head -1如果没有gdb容器里通常有权限直接装sudo apt-get update sudo apt-get install -y gdb然后打开VSCode扩展面板确认在“远程-容器”侧栏里C/C扩展显示已安装而不是只在本地显示已安装。验证方式很简单命令面板CtrlShiftP里输入C/C: Edit Configurations能出来说明扩展在远端生效了。本地插件没推到容器里是所有配置看起来都对了但断点不工作的前置原因之一。2.5 先把模块跑起来调试前的冒烟验证调试前要把模块本身能跑通这件事确认掉否则后面所有排查都会混在一起。容器里执行source /opt/apollo/neo/setup.bash /apollo/bazel-bin/modules/planning/planning --flagfile/apollo/modules/planning/conf/planning.conf这里只是确认进程能否起来不是让你直接调试。如果进程秒退大概率是环境或配置问题先解决再谈断点。如果这个路径在你工程里不存在先find /apollo/bazel-bin -name planning -type f找真实可执行文件路径以自己工程为准。下一章会把这一长串命令翻译成VSCode能直接复用的配置。3. 把launch.json改到能跑attach与launch的取舍四个必填字段3.1 首推attach模式先在另一个终端把模块跑起来Apollo数据流很长多数情况是你知道目标模块正在运行只想进去看内部状态。这种场景别从launch折腾起attach最快。容器终端先把模块启动起来VSCode再用调试配置挂上去。一份能用的launch.json{ version: 0.2.0, configurations: [ { name: Apollo Planning Attach, type: cppdbg, request: attach, program: /apollo/bazel-bin/modules/planning/planning, processId: ${command:pickProcess}, cwd: /apollo, MIMode: gdb, miDebuggerPath: /usr/bin/gdb, additionalSOLibSearchPath: [ /apollo/bazel-bin/modules/planning ], setupCommands: [ { description: Enable pretty printing for gdb, text: -enable-pretty-printing, ignoreFailures: true } ], sourceFileMap: { /apollo: /apollo } } ] }几个字段的用途说清楚request必须是attachprocessId填${command:pickProcess}启动调试时VSCode会弹一个进程列表让你选program指向可执行文件本身gdb主要靠它读取符号如果这个文件是bazel-bin下的软链建议先用readlink -f确认它指到哪cwd设成源码根目录保证相对路径的配置文件能解析。additionalSOLibSearchPath在Apollo工程里尤其重要。新版Apollo很多模块编译成.so插件运行时由mainboard按需加载VSCode需要这个字段告诉gdb去哪儿找插件里的调试符号。setupCommands里的pretty-printing会启用gdb的Python美化器让你在Watch面板直接展开std::vector、Eigen::Matrix这些模板类型别省。3.2 需要从第一条指令查起时用launch模式有些问题在进程初始化阶段或者模块启动前几秒就崩了attach根本来不及。这时候必须用launch模式让VSCode从零把进程拉起来并接管。难点在于把容器环境完整喂给被调试进程。{ name: Apollo Planning Launch, type: cppdbg, request: launch, program: /apollo/bazel-bin/modules/planning/planning, args: [ --flagfilemodules/planning/conf/planning.conf ], cwd: /apollo, environment: [ { name: LD_LIBRARY_PATH, value: /opt/apollo/neo/lib }, { name: CYBER_PATH, value: /opt/apollo/neo } ], MIMode: gdb, miDebuggerPath: /usr/bin/gdb, sourceFileMap: { /apollo: /apollo } }environment里的值不能照抄必须从你容器里的setup.bash读出来填。跑一句source /opt/apollo/neo/setup.bash env | grep -E LD_LIBRARY_PATH|CYBER_PATH把实际值填进去。这里有个常见误解我专门说一次preLaunchTask里source过的环境不会自动进到被调试进程。task和调试子进程是两回事很多人tasks里写了source setup.bashlaunch照样报找不到libcyber.so。老老实实把关键变量写进environment字段比什么都稳。3.3 编译出带符号的产物dbg模式没有调试符号的断点是装饰品。Apollo默认的release编译会strip符号调试前需要用dbg模式编译目标模块cd /apollo bazel build --compilation_modedbg //modules/planning:planning如果用的是工程自带构建工具很多版本是buildtool build --debug modules/planning:planning本质也是把--compilation_mode切到dbg。dbg模式会带上DWARF调试符号并把优化级别压到最低模块体积明显变大planning这种动辄几百MB正常现象。编译完确认符号真的进去了file /apollo/bazel-bin/modules/planning/planning输出里有with debug_info字样说明符号齐了。改完代码重新执行同一条命令即可增量更新如果改了.bazelrc之类的全局配置触发了大量重编是bazel的输入变了不是坏了。3.4 tasks.json与c_cpp_properties.json调试和智能提示是两套体系经常遇到一种情况断点能停源码里全是红色波浪线报no such file也有人反过来。原因就是调试和智能提示各走各的配置。tasks.json用来给launch提供前置任务比如先拉起DreamView或者初始化cyber环境。一个实际可用的例子{ version: 2.0.0, tasks: [ { label: apollo-dreamview, type: shell, command: bash, args: [-lc, source /opt/apollo/neo/setup.bash /apollo/bazel-bin/modules/dreamview_plus/dreamview_plus], isBackground: true } ] }launch里的preLaunchTask填apollo-dreamview它只负责把附属服务拉起来不影响符号。isBackground设trueVSCode不会一直转圈等任务结束。智能提示这块由c_cpp_properties.json管。命令面板执行C/C: Edit Configurations生成把includePath指到Apollo源码和依赖目录{ configurations: [ { name: Apollo, includePath: [ /apollo, /apollo/cyber, /apollo/modules, /opt/apollo/neo/include ], defines: [], cStandard: c17, cppStandard: c17, intelliSenseMode: linux-gcc-x64 } ], version: 4 }includePath按容器实际路径调整不用追求一次到位。IntelliSense提示不全不代表调试不能走别让这两件事互相干扰。4. 断点为什么不亮符号表、优化级别、路径映射与回调断点4.1 VSCode断点的三种状态哪种才算就绪没启动调试时的空心圆是正常状态不代表有问题。真正要看的是Launch之后如果断点仍是空心圆或者下方出现“未绑定”字样说明二进制和源码对不上。实心的红色圆点才表示断点已绑定到实际指令。还有第三种情况圆点实心但运行永远不触发。常见于断点打在模板实例化失败的代码、被编译器内联的小函数或者编译器判定不可达的分支。这跟配置无关是调试信息本身的位置问题。4.2 一条命令定位符号有没有加载启动调试后打开调试控制台输入-exec info sharedlibrary输出是一长串动态库列表找到你的目标模块比如libplanning.so。如果该行有Symbols loaded符号加载了如果显示Symbols not loaded多半是编译产物被strip或者路径对不上。再补一条确认源码-exec info sources执行后在输出里搜一下自己的文件比如modules/planning/...。源码列表里没有目标文件问题基本锁定在符号缺失或路径映射而不是断点位置。4.3 optimized outdbg模式也救不了内联和模板Apollo大量使用cyber回调、Eigen表达式和模板工厂这些代码经常被编译器处理成没有实体变量的状态。dbg模式已经关掉大部分优化但宏、内联小函数仍可能没有独立栈帧。遇到Watch面板显示optimized out先别怀疑配置把断点往上一层函数移或者临时加一条日志把值打出来。这是血泪经验在Eigen表达式内部下断点watch里全是看不懂的花括号不如断到外层拿到Matrix对象再展开。cyber的Reader回调也是同理断点打在lambda表达式外层能看到message参数打在内层可能在优化后被抹掉。4.4 sourceFileMap与容器内外路径为什么源码对不上Remote-Containers方式下路径天然一致这条可以跳过。但Remote-SSH或者宿主机直接打开挂载目录时gdb从符号表里读到的编译路径是容器内路径比如/apollo/modules/planning/...而VSCode打开的源码在宿主机路径比如/workspace/apollo/modules/...。断点映射就靠sourceFileMap做翻译sourceFileMap: { /apollo: /workspace/apollo }键是gdb里看到的编译路径值是VSCode打开的当前路径方向别反。改完重新启动调试立即生效不需要重启VSCode。这条配置错了典型现象就是断点能绑定但一直显示“源文件不可用”让你怀疑人生。4.5 vscode头文件no such file为什么和断点一点关系都没有这是搜索量很高的现象但本质和断点调试无关。IntelliSense的includePath没配对会让VSCode报一堆no such file可gdb断点走的是编译期生成的DWARF路径跟c_cpp_properties.json完全是两个体系。遇到满屏波浪线回第三章把编译命令里的-I路径补进includePath或者直接引入bazel生成的compile_commands.json。如果你当前目标只是断点黄色波浪线可以先无视不要在找头文件这件事上耗掉调试时间。5. Apollo断点调试避坑清单5个现场与排查顺序5.1 现场一launch报program does not exist现象调试器刚启动就退出错误提示找不到program文件。 原因program写的路径在VSCode这一侧解析不到。最常见是宿主机窗口里写了容器路径或者bazel-bin软链指向的输出目录已经被clean。 解决容器内执行readlink -f /apollo/bazel-bin/modules/planning/planning把真实路径填进去。如果目标是.so插件program要填加载它的主程序不能直接填so文件。5.2 现场二断点灰掉显示Unresolved breakpoint现象启动调试后断点仍是空心圆悬停提示unresolved breakpoint。 原因编译模式没带符号或者改代码之后没有重编行号已经错位。 解决用第三章的bazel build --compilation_modedbg重编目标重编后还灰调试控制台执行-exec info sources确认源码在不在列表里。注意外部依赖仓库里的代码gdb不一定有对应路径断点优先打到/apollo工作区内的文件。5.3 现场三attach失败报Operation not permitted现象request为attach选完进程后报Operation not permitted。 原因容器默认对非特权进程有ptrace限制gdb没法接管目标进程这跟VSCode配置无关。 解决启动容器时给docker加--cap-addSYS_PTRACE重进容器再试。临时验证可以在容器里执行sudo gdb -p pid能attach就说明问题在权限而不是路径。注意每次重建容器这个参数都要重新带。5.4 现场四launch没几秒就退出报libcyber.so找不到现象launch模式启动后立刻崩输出是error while loading shared libraries: libcyber.so。 原因调试进程没继承容器环境变量LD_LIBRARY_PATH不对。很多人在preLaunchTask里写了source setup.bash以为环境会传进被调试进程实际不会。 解决容器里source setup.bash env | grep LD_LIBRARY_PATH把值原样写进launch.json的environment。同时确认CYBER_PATH也导出了模块运行期读这个变量找配置。5.5 现场五断点一停DreamView那边的模块马上掉线现象断点能进单步也正常但停久了模块在DreamView里变灰甚至整个进程被杀。 原因Apollo模块之间走cyber的共享内存通信保活机制盯着心跳。你单步执行把主线程卡住心跳发不出去节点被当成失联清掉。这是Apollo调试最有特色的一个坑。 解决单步节奏放快避免在长等待上停太久需要仔细看数据时改用记录点也就是下一章要说的Log Point记录点不会让进程长时间停摆。模块被清掉后重启一次即可代码多半没有死锁不用怀疑自己的逻辑。5.6 排查顺序建议遇到断点不工作按一条链查进程还在吗ps -ef | grep符号在吗-exec info sharedlibrary能attach吗权限断点位置对吗行号、条件数据看得见吗路径映射和优化级别。这条链我每次都用90%的问题在第2、3步就能定位。别一上来就改launch.json或者翻源码顺序反了只会越调越乱。6. 从断点到root cause记录点、条件断点与调试控制台三招6.1 用记录点代替断点稳住cyber心跳右键断点选择Add Log Point就能做到不停下来、只在命中时打印信息。对planning这种高频回调特别合适。你担心的断点导致掉线问题用记录点就从根上绕开了。记录点可以访问局部变量格式和printf一样但不需要重编译。6.2 条件断点减少无效命中Apollo一个模块几十个线程断点可能一秒触发几十次。右键断点选择Edit Condition写this-state 8这类表达式只有满足条件才停。注意条件语法是gdb的表达式不是完整C语句访问成员直接写this-调用函数要谨慎副作用会改变被调试进程状态。6.3 调试控制台里的原生gdbVSCode的调试控制台可以直接透传gdb命令。前缀-exec后面的东西原样交给gdb执行-exec p current_lane -exec bt -exec set variable *ptr 0我常用来验证符号是否就绪的-exec info sources也是这么用。想看某个指针指向的对象里有什么又懒得一个个点Watch面板直接打一条-exec p比鼠标快得多。这几个习惯是怎么来的呢早期我在Apollo里调试模块掉线总以为是死锁翻了几轮代码没结论。后来先改用记录点确认心跳线程正常再用条件断点卡住真正可疑的分支最后用调试控制台把关键变量一次性打印出来定位时间从半天缩到半小时。断点调试不是玄学路径、符号、权限、心跳四条线理顺VSCode就是Apollo开发里最趁手的工具。希望帮到你。本文还有配套的精品资源点击获取
返回列表