35 调试工具与技术

调试构建失败的具体方法取决于问题性质及其发生的系统区域。与最后已知正常 版本比较、检查变更、逐步重新执行以定位出错步骤等常规方法,同样适用于 Yocto Project。虽然无法详述所有可能故障,本节提供适用于不同情况的一般技巧。

备注

错误报告工具有助于调试。启用后,构建系统会在控制台输出错误报告命令, 构建完成后执行这些命令,可将错误信息记录到公共数据库,辅助调查。 启用与使用方法见 “使用错误报告工具”。

本节包含以下主题:

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 输出还包括:

  • 开头以树形结构列出全局包含的配置文件和类,并递归列出它们包含或继承的 文件。构建系统的许多行为,包括 常规配方构建任务, 都实现在 base 及其继承的类中,而非 BitBake 内部。

  • 变量后列出所有函数。Shell 函数体内的变量已展开;通过覆盖、:append、 :prepend 等修改的函数,会显示最终组装后的函数体。

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-tool Python 脚本展示了如何处理 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”。

如果不确定依赖是否被识别,可以列出自动推断结果:

  1. 构建包含任务的配方:

    $ bitbake recipename
    
  2. 在 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 内容完全相同。

  3. 对 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 查看生成共享状态任务输入签名的元数据

了解哪些元数据参与共享状态任务输入签名,有助于调试。信息保存在 SSTATE_DIR 的 siginfo 中,查看与解释方法见 “查看任务的变量依赖”。

共享状态概念见概述与概念手册的 “共享状态”。

35 使共享状态失效以强制执行任务

构建系统使用 校验和 及 共享状态 缓存,避免不必要的重建, 整体称为“共享状态代码”。

这种机制也有局限。某些隐式变化没有进入校验和计算,却影响任务输出,因此 不会触发重建。例如,rpmdeps 输出变化,理应使所有 package 和 package_write_rpm 缓存失效,但变化位于代码之外,相关缓存仍有效,构建 就继续使用缓存而不执行任务。这可能产生问题。

因此,需要理解变更影响。直接修改函数会自动纳入校验和,使对应缓存失效; 但并非明显代码变化的隐式影响,需要自行识别。

发现隐式变化后,可主动使缓存失效。简单修改函数注释也能做到。例如,修改 do_package 或其调用函数的注释,即使仅是表面变化,也会重新 计算校验和并强制执行任务。

备注

示例见这个通过表面修改使共享状态失效的 提交。

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 网站。

调试步骤如下:

  1. 配置配套调试文件系统:

    在 build/conf/local.conf 设置:

    IMAGE_GEN_DEBUGFS = "1"
    IMAGE_FSTYPES_DEBUGFS = "tar.bz2"
    

    构建系统根据已部署文件系统的内容,收集对应 -dbg 包,生成包含匹配 源码和符号的配套文件系统片段。

    它并非完整文件系统,只含调试片段,必须与完整文件系统结合。后续步骤 将介绍组合方式。

  2. 在目标文件系统加入 gdbserver:

    在 build/conf/local.conf 添加:

    EXTRA_IMAGE_FEATURES:append = " tools-debug"
    

    这样确保包含 gdbserver 包。

  3. 构建环境:

    构建镜像及配套调试文件系统:

    $ 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,通常这正是需要的名称。

  4. 设置 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
    
  5. 设置 GDB:

    安装所构建的 SDK 并加载正确环境文件,将 SDK 加入 PATH,并使 $GDB 指向 SDK 调试器。

    使用构建系统时,GDB 位于 build-dir/tmp/sysroots/host/usr/bin/architecture/architecture-gdb。

  6. 启动目标:

    QEMU 运行方法见 QEMU 文档。

    备注

    确认主机能够通过 TCP 访问目标。

  7. 调试程序:

    先在目标运行 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,启动时自动执行。

  8. 无需完整重建镜像的部署:

    开发时经常需要快速部署新二进制并调试,不想等待完整镜像构建。

    可只构建目标组件,然后将可执行文件同时复制到目标和主机 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 的内部变量, 未必适合写入手册。