35 调试工具与技术
调试构建失败的具体方法取决于问题性质及其发生的系统区域。与最后已知正常 版本比较、检查变更、逐步重新执行以定位出错步骤等常规方法,同样适用于 Yocto Project。虽然无法详述所有可能故障,本节提供适用于不同情况的一般技巧。
备注
错误报告工具有助于调试。启用后,构建系统会在控制台输出错误报告命令, 构建完成后执行这些命令,可将错误信息记录到公共数据库,辅助调查。 启用与使用方法见 “使用错误报告工具”。
本节包含以下主题:
“查看失败任务日志”:查找和查看 构建失败任务的日志。
“查看变量值”:使用 BitBake
-e检查配方解析后的变量值。“使用 oe-pkgdata-util 查看软件包信息”: 查询 PKGDATA_DIR,显示已构建软件包的信息。
“查看配方与任务间的依赖”: 使用
-g显示构建使用的配方依赖。“查看任务的变量依赖”:结合
bitbake-dumpsig和 Build Directory 子目录分析变量依赖。“运行特定任务”:使用
-c、-C、-f等运行特定任务;排查问题时,按非默认顺序执行可能很有帮助。“一般 BitBake 问题”:通过
-D调试 输出了解 BitBake 的行为。“不检查依赖的构建”:通过
-b忽略依赖构建配方。“配方日志机制”:使用配方日志函数 输出调试信息、错误和警告。
“调试并行 Make 竞态”:排查并行构建 中某部分输出尚未就绪,依赖它的另一部分却已开始执行的问题。
“使用 GNU 调试器(GDB)远程调试”: 用 GDB 检查运行中的程序,辅助修复问题。
“在目标上使用 GNU 调试器(GDB)”: 直接在目标硬件使用 GDB。
“其他调试技巧”:其他实用技巧。
35 查看失败任务日志
任务日志位于 ${WORKDIR}/temp/log.do_taskname。
例如,qemux86 最小镜像的 do_compile 日志可能为
tmp/work/qemux86-poky-linux/core-image-minimal/1.0-r0/temp/log.do_compile。
要查看 BitBake 执行了哪些命令,检查同目录的 run.do_taskname。
log.do_taskname 和 run.do_taskname 实际是符号链接,分别指向
log.do_taskname.pid 和
log.run_taskname.pid,其中 pid 是执行时的进程 ID。
链接始终指向最近一次运行的文件。
35 查看变量值
遇到意外行为,或 修改变量 未按预期生效时,可能需要查看 BitBake 解析后的变量值。
bitbake-getvar 用于显示解析后的值。以下命令显示 local.conf、
build/conf/bblayers.conf、bitbake.conf 等配置解析后的
OVERRIDES:
$ bitbake-getvar OVERRIDES
查看特定配方解析后的 PV:
$ bitbake-getvar -r recipename PV
备注
每个配方拥有独立数据存储。配置解析完成后,BitBake 为每个待解析配方 复制一份,因此一个配方设置的变量对其他配方不可见。
同样,配方内每个任务也基于配方数据存储获得私有副本,一个任务设置的变量 对其他任务不可见。
bitbake-getvar 在最终值前显示其形成过程,包括后来被覆盖的临时值和
变量标志(varflags),对调试很有帮助。
导出到环境的变量在输出中带 export 前缀,例如:
export CC="i586-poky-linux-gcc -m32 -march=i586 --sysroot=/home/ulf/poky/build/tmp/sysroots/qemux86"
Shell 函数和任务也可用相同机制检查:
$ bitbake-getvar -r recipename do_install
Python 函数和任务则可以使用 bitbake -e recipename。
bitbake -e 及针对配方的 bitbake -e 输出还包括:
35 使用 oe-pkgdata-util 查看软件包信息
oe-pkgdata-util 查询 PKGDATA_DIR,显示软件包相关信息,只能查询
已经构建的软件包。
可用子命令包括:
备注
包名和路径可以使用标准通配符 * 和 ?。
oe-pkgdata-util list-pkgs [pattern]:列出所有已构建包,可限制为匹配 pattern 的包。oe-pkgdata-util list-pkg-files package ...:列出指定包中的文件和目录。备注
也可以检查生成该包的配方的
${WORKDIR}/packages-split。该目录由 do_package 创建,每个包有独立子目录,保存其文件。如需检查此目录,构建配方时不要启用 rm_work。
oe-pkgdata-util find-path path ...:列出包含指定路径的软件包。例如, 以下显示/usr/share/man/man1/make.1属于make-doc:$ oe-pkgdata-util find-path /usr/share/man/man1/make.1 make-doc: /usr/share/man/man1/make.1
oe-pkgdata-util lookup-recipe package ...:列出生成指定包的配方名称。
更多信息见帮助:
$ oe-pkgdata-util --help
$ oe-pkgdata-util subcommand --help
35 查看配方与任务间的依赖
有时难以理解 BitBake 为何先构建其他配方。依赖信息可以解释原因。
生成配方依赖信息:
$ bitbake -g recipename
当前目录将生成:
pn-buildlist:构建 recipename 涉及的配方或目标。这里“涉及”是指从头 构建时,至少需要执行该配方的一个任务。ASSUME_PROVIDED 中的目标 不列出。task-depends.dot:任务依赖图。
图采用 DOT 格式,可通过
Graphviz 的 dot 等工具转换为图片。
备注
DOT 是纯文本。
bitbake -g生成的图往往很大,未经-I等裁剪或 处理难以阅读,但直接查看.dot仍可能得到有用信息。例如,
task-depends.dot中可能有:"libxslt.do_configure" -> "libxml2.do_populate_sysroot"
表示
libxslt的 do_configure 依赖libxml2的 do_populate_sysroot,即两配方之间普通的 DEPENDS。scripts/contrib/graph-toolPython 脚本展示了如何处理 DOT,查找 和显示节点之间的路径。
还可用以下方式查看依赖:
$ bitbake -g -u taskexp recipename
或者:
$ bitbake -g -u taskexp_ncurses recipename
-u taskdep 打开图形窗口,查看构建 recipename 涉及配方的构建和运行时
依赖。-u taskexp_ncurses 使用 ncurses 而非 GTK 呈现界面。
35 查看任务的变量依赖
如 BitBake 用户手册
“Checksums (Signatures)”
所述,BitBake 自动推断任务依赖的变量,使变量变化时重新运行任务。通常可靠,
但若在运行时拼接变量名等,可能需用 vardeps 手动声明依赖,见
“Variable Flags”。
如果不确定依赖是否被识别,可以列出自动推断结果:
构建包含任务的配方:
$ bitbake recipename
在 STAMPS_DIR 找到对应签名数据
sigdata。其中以 Python pickle 数据库形式保存生成任务输入校验和的全部元数据。例如,db的 do_fetch 文件可能位于:${BUILDDIR}/tmp/stamps/i586-poky-linux/db/6.0.30-r1.do_fetch.sigdata.7c048c18222b16ff0bcee2000ef648b1使用 共享状态 加速 的任务,还会在 SSTATE_DIR 随缓存输出写入
siginfo,与sigdata内容完全相同。对
sigdata或siginfo运行bitbake-dumpsig:$ bitbake-dumpsig ${BUILDDIR}/tmp/stamps/i586-poky-linux/db/6.0.30-r1.do_fetch.sigdata.7c048c18222b16ff0bcee2000ef648b1输出中的以下行列出推断的所有变量依赖,包括递归的间接变量依赖:
Task dependencies: ['PV', 'SRCREV', 'SRC_URI', 'SRC_URI[sha256sum]', 'base_do_fetch']
备注
base_do_fetch等函数也算变量依赖,函数又依赖其引用的变量。输出还包含各变量的值、各自依赖列表及 BB_BASEHASH_IGNORE_VARS 信息。
35 调试签名生成与意外任务执行
bitbake-diffsigs 比较两个 siginfo 或 sigdata,帮助判断任务的
两个版本之间发生了什么变化。只传一个文件时,行为与 bitbake-dumpsig 相同。
也可以让 BitBake 不执行任务,仅导出签名生成信息:
--dump-signatures=SIGNATURE_HANDLER
-S SIGNATURE_HANDLER
备注
SIGNATURE_HANDLER 常用 "none" 和 "printdiff":前者只导出签名,后者 与最近签名比较。"printdiff" 尝试在共享状态缓存等位置寻找最近匹配,再 比较签名,确定两棵戳记树的分歧和差异,用于解释意外重跑任务的原因。
这些选项让 BitBake 为原本会执行的每个任务在 stamps 中写入 sigdata,
而不构建目标软件包。
35 运行特定任务
配方由一组任务组成,通常依次包括 do_fetch、do_unpack、
do_patch、do_configure、do_compile、
do_install、do_package、
do_package_write_* 和 do_build。
默认任务是 do_build,其依赖先执行。do_devshell
等不属于默认链,可以通过 -c 指定:
$ bitbake matchbox-desktop -c devshell
-c 遵循依赖关系,依赖的所有任务(包括其他配方任务)先运行。即使手动
指定,BitBake 也只在判断任务过期时执行。判断方法见
“戳记文件与任务重跑”。
要强制重跑未过期任务,例如试验手动修改的 WORKDIR,可用 -f。
备注
do_devshell 已设置
[nostamp],
因此无需 -f。
示例:
$ bitbake matchbox-desktop
.
.
make some changes to the source code in the work directory
.
.
$ bitbake matchbox-desktop -c compile -f
$ bitbake matchbox-desktop
先构建再重新编译 matchbox-desktop。最后一条命令重跑编译后的任务,主要
是打包。BitBake 知道编译已重跑,因而其后任务也需要再次执行。
更简短的方法是使用 -C,重跑任务及依赖它的
常规配方构建任务。
备注
此处是大写 -C,与小写 -c 不同。
它使指定任务失效,再运行默认 do_build 及其依赖。前例最后 两条命令可以替换为:
$ bitbake matchbox-desktop -C compile
内部实现上,-f 和 -C 修改指定任务的输入校验和,使其被标记为受污染,
再通过正常依赖机制间接触发任务及其依赖者重跑。
备注
BitBake 跟踪这些受污染任务,涉及它们的构建会显示类似警告:
WARNING: /home/ulf/openembedded-core/meta/recipes-sato/matchbox-desktop/matchbox-desktop_2.1.bb.do_compile is tainted from a forced run
警告提醒你,取决于之前操作,工作目录和输出可能不再处于普通构建的干净 状态。删除工作目录并重建可消除警告:
$ bitbake matchbox-desktop -c clean
$ bitbake matchbox-desktop
运行 do_listtasks 可查看某个包的任务列表:
$ bitbake matchbox-desktop -c listtasks
结果显示在控制台,也保存在 ${WORKDIR}/temp/log.do_listtasks。
35 一般 BitBake 问题
-D 输出更多关于 BitBake 行为及原因的信息。每增加一个 -D,日志级别
提高一级,常用 -DDD。
bitbake -DDD -v targetname 可解释为何选择某个包版本或提供者,也适合
调查其他意外行为。
35 不检查依赖的构建
可用以下方式构建特定 .bb 文件:
$ bitbake -b somepath/somerecipe.bb
这种方式不检查依赖,只应在确认依赖已满足时使用。
备注
也可以只指定文件名片段,BitBake 会检查是否唯一匹配。
35 配方日志机制
Yocto Project 提供多个日志函数,输出调试信息、错误和警告。Python 函数
均写入 ${T}/log.do_task,适当设置后也写到标准输出:
bb.plain(msg):原样写日志,并输出到 stdout。bb.note(msg):写入 "NOTE: msg";BitBake 使用 "-v" 时也输出到 stdout。bb.debug(level, msg):写入 "DEBUG: msg";日志级别不低于 level 时也 输出到 stdout。详见 “Usage and syntax”。bb.warn(msg):写入 "WARNING: msg",同时输出到 stdout。bb.error(msg):写入 "ERROR: msg",同时输出到 stdout。备注
此函数不会使任务失败。
bb.fatal(msg):类似bb.error(msg),但还会使调用任务失败。备注
bb.fatal()抛出异常,因此后面无需 "return"。
Shell 中也有相同功能,名称为 bbplain、bbnote、bbdebug、bbwarn、
bberror 和 bbfatal,由 logging 实现。详情见
OpenEmbedded-Core (OE-Core) 的 meta/classes 中该类。
35 使用 Python 记录日志
编写 Python 配方日志代码时,应让日志充分说明情况,同时尽量减少控制台输出。 状态消息应使用 "debug" 级别。
以下示例记录用于确定待运行任务数量的函数,另见 do_listtasks:
python do_listtasks() {
bb.debug(2, "Starting to figure out the task list")
if noteworthy_condition:
bb.note("There are 47 tasks to run")
bb.debug(2, "Got to point xyz")
if warning_trigger:
bb.warn("Detected warning_trigger, this might be a problem later.")
if recoverable_error:
bb.error("Hit recoverable_error, you really need to fix this!")
if fatal_error:
bb.fatal("fatal_error detected, unable to print the task list")
bb.plain("The tasks present are abc")
bb.debug(2, "Finished figuring out the tasklist")
}
35 使用 Bash 记录日志
Bash 配方的目标相同:日志内容充分,控制台输出尽量少。语法与 Python 用法 类似。
以下示例记录 do_my_function 进度:
do_my_function() {
bbdebug 2 "Running do_my_function"
if [ exceptional_condition ]; then
bbnote "Hit exceptional_condition"
fi
bbdebug 2 "Got to point xyz"
if [ warning_trigger ]; then
bbwarn "Detected warning_trigger, this might cause a problem later."
fi
if [ recoverable_error ]; then
bberror "Hit recoverable_error, correcting"
fi
if [ fatal_error ]; then
bbfatal "fatal_error detected"
fi
bbdebug 2 "Completed do_my_function"
}
35 调试并行 Make 竞态
并行 make 竞态是指多个构建部分同时运行,其中某部分所需的另一部分输出
尚未就绪。它可能难以复现和修复。本节通过 Yocto Project 自动构建器中的
真实错误,介绍一些实用排查方法。
备注
如果无法正确修复竞态,可以清空 PARALLEL_MAKE 或 PARALLEL_MAKEINST 暂时绕过。
35 失败现象
假设镜像依赖 "neard",BitBake 构建遇到问题并输出以下内容。
备注
为便于阅读,示例日志的长行经过人为折行。
检查输出或日志,可以看到 make 失败:
| DEBUG: SITE files ['endian-little', 'bit-32', 'ix86-common', 'common-linux', 'common-glibc', 'i586-linux', 'common']
| DEBUG: Executing shell function do_compile
| NOTE: make -j 16
| make --no-print-directory all-am
| /bin/mkdir -p include/near
| /bin/mkdir -p include/near
| /bin/mkdir -p include/near
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/types.h include/near/types.h
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/log.h include/near/log.h
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/plugin.h include/near/plugin.h
| /bin/mkdir -p include/near
| /bin/mkdir -p include/near
| /bin/mkdir -p include/near
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/tag.h include/near/tag.h
| /bin/mkdir -p include/near
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/adapter.h include/near/adapter.h
| /bin/mkdir -p include/near
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/ndef.h include/near/ndef.h
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/tlv.h include/near/tlv.h
| /bin/mkdir -p include/near
| /bin/mkdir -p include/near
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/setting.h include/near/setting.h
| /bin/mkdir -p include/near
| /bin/mkdir -p include/near
| /bin/mkdir -p include/near
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/device.h include/near/device.h
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/nfc_copy.h include/near/nfc_copy.h
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/snep.h include/near/snep.h
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/version.h include/near/version.h
| ln -s /home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/work/i586-poky-linux/neard/
0.14-r0/neard-0.14/include/dbus.h include/near/dbus.h
| ./src/genbuiltin nfctype1 nfctype2 nfctype3 nfctype4 p2p > src/builtin.h
| i586-poky-linux-gcc -m32 -march=i586 --sysroot=/home/pokybuild/yocto-autobuilder/nightly-x86/
build/build/tmp/sysroots/qemux86 -DHAVE_CONFIG_H -I. -I./include -I./src -I./gdbus -I/home/pokybuild/
yocto-autobuilder/nightly-x86/build/build/tmp/sysroots/qemux86/usr/include/glib-2.0
-I/home/pokybuild/yocto-autobuilder/nightly-x86/build/build/tmp/sysroots/qemux86/usr/
lib/glib-2.0/include -I/home/pokybuild/yocto-autobuilder/nightly-x86/build/build/
tmp/sysroots/qemux86/usr/include/dbus-1.0 -I/home/pokybuild/yocto-autobuilder/
nightly-x86/build/build/tmp/sysroots/qemux86/usr/lib/dbus-1.0/include -I/home/pokybuild/yocto-autobuilder/
nightly-x86/build/build/tmp/sysroots/qemux86/usr/include/libnl3
-DNEAR_PLUGIN_BUILTIN -DPLUGINDIR=\""/usr/lib/near/plugins"\"
-DCONFIGDIR=\""/etc/neard\"" -O2 -pipe -g -feliminate-unused-debug-types -c
-o tools/snep-send.o tools/snep-send.c
| In file included from tools/snep-send.c:16:0:
| tools/../src/near.h:41:23: fatal error: near/dbus.h: No such file or directory
| #include <near/dbus.h>
| ^
| compilation terminated.
| make[1]: *** [tools/snep-send.o] Error 1
| make[1]: *** Waiting for unfinished jobs....
| make: *** [all] Error 2
| ERROR: oe_runmake failed
35 复现错误
竞态具有间歇性,不会每次构建都出现。即使存在潜在竞态,多数构建也可能成功, 因此需要找到复现方式。
本例问题发生在编译 "neard",先尝试本地构建。为增加复现概率,在 build/conf/local.conf 中将 PARALLEL_MAKE 设为较大 值,例如 "-j 20":
$ bitbake neard
本地构建完成后,启动 devshell:
$ bitbake neard -c devshell
用法见 “使用开发 Shell”。
在其中运行:
$ make clean
$ make tools/snep-send.o
这使故障清楚显现:neard 的 Makefile 目标缺少依赖。以下简化输出末尾
明确显示了问题:
i586-poky-linux-gcc -m32 -march=i586 --sysroot=/home/scott-lenovo/......
.
.
.
tools/snep-send.c
In file included from tools/snep-send.c:16:0:
tools/../src/near.h:41:23: fatal error: near/dbus.h: No such file or directory
#include <near/dbus.h>
^
compilation terminated.
make: *** [tools/snep-send.o] Error 1
$
35 创建修复补丁
需要修补由 Makefile.in 生成的 Makefile.am,为目标补齐依赖。
可用 Quilt 创建补丁:
$ quilt new parallelmake.patch
Patch patches/parallelmake.patch is now on top
$ quilt add Makefile.am
File Makefile.am added to patch patches/parallelmake.patch
Quilt 用法见 “在工作流程中使用 Quilt”。
编辑 Makefile.am,本例添加以下行:
tools/snep-send.$(OBJEXT): include/near/dbus.h
然后运行 refresh 生成补丁:
$ quilt refresh
Refreshed patch patches/parallelmake.patch
将补丁放回原配方目录。假定 OpenEmbedded-Core (OE-Core) 目录名为
openembedded-core:
$ cp patches/parallelmake.patch openembedded-core/meta/recipes-connectivity/neard/neard
最后更新补丁上一级目录中的 neard-0.14.bb,在 SRC_URI 中加入
补丁文件。修改后如下:
SRC_URI = "${KERNELORG_MIRROR}/linux/network/nfc/${BPN}-${PV}.tar.xz \
file://neard.in \
file://neard.service.in \
file://parallelmake.patch \
"
补丁放置和配方更新完成后,退出 devshell:
$ exit
35 测试构建
重新尝试本地构建:
$ bitbake neard
构建应成功。然后再次打开 devshell,重复清理和 make:
$ bitbake neard -c devshell
$ make clean
$ make tools/snep-send.o
这次应不再出错。
如果问题源自上游,应同时向 OE-Core 和上游提交修复,从源头解决。详见 “向组件贡献修改”。
35 使用 GNU 调试器(GDB)远程调试
GDB 能检查运行中的程序,帮助理解和修复问题,也能在崩溃后作事后分析。 Yocto Project 提供 GDB 软件包,SDK 镜像默认安装它。镜像介绍见参考手册的 “镜像”,GDB 信息见 https://sourceware.org/gdb/。
备注
为获得最佳效果,应安装被调试应用的 -dbg 包,使额外调试符号可用,
得到更有意义的输出。
内存或磁盘受限时,可能无法直接在目标运行 GDB。GDB 需要加载调试信息和 二进制文件,并进行大量计算来定位函数名、变量名及值、栈回溯等,甚至在真正 开始调试之前就有这些负担。它们会增加目标负载,并可能改变被调试程序的特征。
可采用两种方法缓解:运行 debuginfod 服务器,或使用 gdbserver。
35 使用 debuginfod 服务器
elfutils 的 debuginfod 用于分发调试信息文件。运行服务器后,可直接
获取所需符号,无需预先下载被调试进程的完整调试信息和二进制文件。
设置方法如下:
确保 DISTRO_FEATURES 包含
debuginfod。 OpenEmbedded-Core (OE-Core) 默认配置和 Poky 已包含它。否则,在发行版 configuration file 或 build/conf/local.conf 中添加:
DISTRO_FEATURES:append = " debuginfod"
该功能启用
elfutils的服务器和客户端库,以及目前gdb和binutils客户端的支持。在主机运行以下命令启动服务器:
$ oe-debuginfod
在目标使用时,需知道主机监听的 IP 和端口,默认端口为 8002,并导出到 Shell 环境。例如在
qemu中:root@qemux86-64:~# export DEBUGINFOD_URLS="http://192.168.7.1:8002/"
随后运行目标
gdb、readelf或objdump即可获取调试信息,例如:root@qemux86-64:~# gdb /bin/cat ... Reading symbols from /bin/cat... Downloading separate debug info for /bin/cat... Reading symbols from /home/root/.cache/debuginfod_client/923dc4780cfbc545850c616bffa884b6b5eaf322/debuginfo...
也可用
debuginfod-find单独查询服务器:root@qemux86-64:~# debuginfod-find debuginfo /bin/ls /home/root/.cache/debuginfod_client/356edc585f7f82d46f94fcb87a86a3fe2d2e60bd/debuginfo
35 使用 gdbserver
gdbserver 在目标运行,不加载被调试进程的调试信息。主机 GDB 负责处理信息, 并向 gdbserver 发送命令以启停程序、读写内存。繁重的调试处理都放在主机上, 目标端 gdbserver 因而可以保持轻巧快速。
主机必须能够访问未剥离且包含调试信息的二进制文件,以及程序使用的全部库。 目标程序应关闭编译优化。目标二进制可以保留剥离状态,因为 gdbserver 不需要 本地调试信息,但也必须无优化编译,以匹配主机二进制。
按 GDB 文档术语,目标上被调试的程序称为 "inferior"。文档见 GDB 网站。
调试步骤如下:
配置配套调试文件系统:
在 build/conf/local.conf 设置:
IMAGE_GEN_DEBUGFS = "1" IMAGE_FSTYPES_DEBUGFS = "tar.bz2"
构建系统根据已部署文件系统的内容,收集对应
-dbg包,生成包含匹配 源码和符号的配套文件系统片段。它并非完整文件系统,只含调试片段,必须与完整文件系统结合。后续步骤 将介绍组合方式。
在目标文件系统加入 gdbserver:
在 build/conf/local.conf 添加:
EXTRA_IMAGE_FEATURES:append = " tools-debug"
这样确保包含
gdbserver包。构建环境:
构建镜像及配套调试文件系统:
$ bitbake image
还需构建交叉 GDB。构建与镜像匹配的 SDK 最适合生产版本,便于日后调试, 尤其适合长期维护:
$ bitbake -c populate_sdk image
也可只构建与目标匹配的最小工具链,得到更小的 SDK,包含构建简单测试 应用和运行调试器所需的最少组件:
$ bitbake meta-toolchain
最后一种方法是在构建系统内单独构建 GDB:
$ bitbake gdb-cross-<architecture>
这会生成开发时使用的临时
cross-gdb,速度最快,但长期维护更适合 前两种方法。备注
运行
bitbake gdb-cross时,构建系统会建议实际名称,例如gdb-cross-i586,通常这正是需要的名称。设置
debugfs:执行以下命令:
$ mkdir debugfs $ cd debugfs $ tar xvfj build-dir/tmp/deploy/images/machine/image.rootfs.tar.bz2 $ tar xvfj build-dir/tmp/deploy/images/machine/image-dbg.rootfs.tar.bz2
设置 GDB:
安装所构建的 SDK 并加载正确环境文件,将 SDK 加入
PATH,并使$GDB指向 SDK 调试器。使用构建系统时,GDB 位于 build-dir
/tmp/sysroots/host/usr/bin/architecture/architecture-gdb。启动目标:
QEMU 运行方法见 QEMU 文档。
备注
确认主机能够通过 TCP 访问目标。
调试程序:
先在目标运行 gdbserver,再在主机运行 GDB。以下调试
gzip:root@qemux86:~# gdbserver localhost:1234 /bin/gzip --help
其他选项见 GDB Server 文档。
然后在主机启动 GDB,配置并连接目标:
$ cd directory-holding-the-debugfs-directory $ arch-gdb (gdb) set sysroot debugfs (gdb) set substitute-path /usr/src/debug debugfs/usr/src/debug (gdb) target remote IP-of-target:1234
此时匹配的二进制、符号和头文件应自动加载。
备注
可将上述 GDB
set命令放入~/.gdbinit,启动时自动执行。无需完整重建镜像的部署:
开发时经常需要快速部署新二进制并调试,不想等待完整镜像构建。
可只构建目标组件,然后将可执行文件同时复制到目标和主机
debugfs。如果构建经过调试信息拆分,还需从工作目录复制
.debug内容和对应的/usr/src/debug文件,例如:$ bitbake bash $ bitbake -c devshell bash $ cd .. $ scp packages-split/bash/bin/bash target:/bin/bash $ cp -a packages-split/bash-dbg/\* path/debugfs
35 在目标上使用 GNU 调试器(GDB)
多数嵌入式设备硬件受限,通常采用上一节的远程调试。不过,性能较强设备也可 直接运行 GDB。本节介绍所需设置:
确保目标安装 GDB,在 build/conf/local.conf 添加:
EXTRA_IMAGE_FEATURES:append = " tools-debug"
确保调试符号存在,将对应
-dbg包加入 IMAGE_INSTALL:IMAGE_INSTALL:append = " packagename-dbg"
或在 build/conf/local.conf 中包含所有调试符号:
EXTRA_IMAGE_FEATURES:append = " dbg-pkgs"
备注
降低优化级别可提高调试信息准确性。例如添加以下设置,将 FULL_OPTIMIZATION 的 "-O2" 改为 DEBUG_OPTIMIZATION 的 "-O -fno-omit-frame-pointer":
DEBUG_BUILD = "1"
这会降低应用性能,仅建议用于调试。
35 启用 Minidebuginfo
在 DISTRO_FEATURES 启用 minidebuginfo 后,会为所有二进制添加压缩
ELF 节 .gnu_debugdata,只包含函数名,体积增加约 5% 到 10%。完整符号
可能达到剥离后二进制的 10 倍,并非总能部署;minidebuginfo 则允许无需完整
符号,就用 GDB backtrace 获取调用栈。使用 systemd-coredump 管理
转储时,也可通过 coredumpctl list 和 coredumpctl info 获得符号化栈。
该功能由 Fedora 创建,详情见 https://fedoraproject.org/wiki/Features/MiniDebugInfo。
35 其他调试技巧
其他可能有用的技巧包括:
添加新包时,检查编译命令行是否出现不应有的内容,例如对主机
/usr/lib/或/usr/include/的引用。要移除
psplash启动画面,在内核命令行添加psplash=false,避免 加载它,从而查看控制台。也可切换虚拟控制台,例如 Zaurus 上的 Fn+左或右。删除 TMPDIR,通常为构建目录下的
tmp/,经常能解决临时问题。 成本通常较低,因为任务输出缓存在 SSTATE_DIR,通常也是 Build Directory 下的sstate-cache/。备注
删除临时目录可能只是绕过问题,应尽量先查明根因。
理解现有配方如何实际使用某项功能很有帮助,建议准备快速搜索文件的方法。
以下 GNU Grep Shell 函数递归搜索常见配方相关文件,跳过二进制、
.git及名称以 "build" 开头的 Build Directory:g() { grep -Ir \ --exclude-dir=.git \ --exclude-dir='build*' \ --include='*.bb*' \ --include='*.inc*' \ --include='*.conf*' \ --include='*.py*' \ "$@" }
用法示例:
$ g FOO # Search recursively for "FOO" $ g -i foo # Search recursively for "foo", ignoring case $ g -w FOO # Search recursively for "FOO" as a word, ignoring e.g. "FOOBAR"
如果理解某项功能需要大量搜索,可能说明文档应补充或改进。可在 Bugzilla 提交文档问题。方法见 Bugzilla Wiki 页面 以及 “报告 Yocto 项目与 OpenEmbedded 的缺陷”。
备注
仅供内部使用且作用域有限的变量,例如实现单个
.bbclass的内部变量, 未必适合写入手册。