开发软件意味着处理程序崩溃问题并找出 bug 的根源。Fuchsia 拥有一套工具来帮助识别和诊断 从分析崩溃日志到全面逐步说明, 调试核心代码
分析崩溃
Fuchsia 会在启动时启动一个名为 crashanalyzer
的程序,用于报告程序
崩溃,并在系统日志中输出崩溃线程的回溯。虽然
你可以在运行时查看日志、通过回溯窗口直接了解
使用堆栈内存地址引用(而非
指向程序源文件中的相应行。
[klog][I] devmgr: crash_analyzer_listener: analyzing exception type 0x108
[klog][I] <== fatal exception: process crasher[42410] thread initial-thread[42424]
[klog][I] <== fatal page fault, PC at 0x1e1888dbbbd7
[klog][I] CS: 0 RIP: 0x1e1888dbbbd7 EFL: 0x10246 CR2: 0
[klog][I] RAX: 0 RBX: 0x1 RCX: 0x721ad98697c6 RDX: 0x77accb36f264
[klog][I] RSI: 0 RDI: 0 RBP: 0x2781c4816f90 RSP: 0x2781c4816f80
[klog][I] R8: 0 R9: 0 R10: 0 R11: 0x246
[klog][I] R12: 0x773bf11dcda0 R13: 0x773bf11dcdd0 R14: 0x16 R15: 0x78050d69
[klog][I] errc: 0x6
[klog][I] bottom of user stack:
[klog][I] 0x00002781c4816f80: f11dcda0 0000773b 9ccd2b38 000039b2 |....;w..8+...9..|
[klog][I] 0x00002781c4816f90: c4816fd0 00002781 88dbbba7 00001e18 |.o...'..........|
[klog][I] 0x00002781c4816fa0: 00000008 00000000 9ccd2b38 000039b2 |........8+...9..|
[klog][I] 0x00002781c4816fb0: f11dcf70 0000773b f11dcf70 0000773b |p...;w..p...;w..|
[klog][I] 0x00002781c4816fc0: cb36f570 000077ac f11dcdd0 0000773b |p.6..w......;w..|
[klog][I] 0x00002781c4816fd0: c4816ff0 00002781 cb2b0d0f 000077ac |.o...'....+..w..|
[klog][I] 0x00002781c4816fe0: 00000054 00000000 f11dcf70 0000773b |T.......p...;w..|
[klog][I] 0x00002781c4816ff0: f11dcfe0 0000773b 00000000 00000000 |....;w..........|
[klog][I] arch: x86_64
[klog][I] dso: id=a94c78564173530d51670b6586b1aa471e004f06 base=0x7d3506a49000 name=libfdio.so
[klog][I] dso: id=a61961ba9776a67a00fb322af9ebbdcfd1ce3f62 base=0x77accb297000 name=libc.so
[klog][I] dso: id=760f1e6e47d3dd8b6a19150aa47241279ec75a9c base=0x721ad9863000 name=<vDSO>
[klog][I] dso: id=b18462140c6784a53736105bbf3021852eeda68c base=0x1e1888dbb000 name=app:crasher
[klog][I] bt#01: pc 0x1e1888dbbbd7 sp 0x2781c4816f80 (app:crasher,0xbd7)
[klog][I] bt#02: pc 0x1e1888dbbba7 sp 0x2781c4816fa0 (app:crasher,0xba7)
[klog][I] bt#03: pc 0x77accb2b0d0f sp 0x2781c4816fe0 (libc.so,0x19d0f)
[klog][I] bt#04: pc 0 sp 0x2781c4817000
[klog][I] bt#05: end
这是因为默认情况下,调试符号会在构建时从核心二进制文件中剥离。如需正确分析崩溃日志,您需要将这些符号重新应用于回溯,以便按源代码行号查看调用堆栈。当您调用 ffx log
命令时,开发者工具会
通过另外一个名为 symbolizer
的二进制文件处理原始日志,
会将本地构建配置中的符号重新应用到
日志。
ffx log
您看到的输出包含重新应用于回溯的符号:
[klog][I] devmgr: crash_analyzer_listener: analyzing exception type 0x108
... same output as "raw" backtrace ...
start of symbolized stack:
[klog][I] #01: blind_write at ../../src/developer/forensics/crasher/cpp/crasher.c:21
[klog][I] #02: main at ../../src/developer/forensics/crasher/cpp/crasher.c:137
[klog][I] #03: start_main at ../../zircon/third_party/ulib/musl/src/env/__libc_start_main.c:49
[klog][I] #04: unknown, can't find pc, sp or app/library in line
end of symbolized stack
<ph type="x-smartling-placeholder">
通过正确符号化的回溯,您可以直接发现 导致崩溃的问题
逐步调试
仅仅知道程序崩溃的位置可能不足以全面了解
诊断问题。有时,您需要浏览代码
甚至可以逐步检查内存中变量的状态。为此,
Fuchsia 提供了一个名为 zxdb
的核心代码的调试程序。
zxdb
工具是一个客户端,可连接到正在运行的 debug_agent
组件
在目标设备上的表现。您可以使用 zxdb
命令来配置
debug_agent
,以附加到特定进程并设置断点。进行调试
附加到正在运行的进程,zxdb
可让您逐步完成
并检查堆栈帧。
设置调试会话需要执行以下高级步骤:
- 在目标设备上运行
debug_agent
组件。 - 运行
zxdb
客户端并连接到目标设备。 - 为
zxdb
设置位置以查找调试符号。
启动调试会话的最简单方法是使用 ffx debug connect
命令,该命令将在您的本地 Fuchsia 构建环境中执行所有这些操作。
不过,如果您需要单独配置这些步骤,也可以手动执行这些步骤。
调试会话处于活动状态后,您会进入 [zxdb]
提示,以便发出调试程序命令。您可以使用 zxdb
配置 debug_agent
,以便使用名称过滤器附加到进程并设置待处理的断点,即使当前没有任何匹配的进程正在运行也是如此。
以下示例在 main 上设置待定断点,以在 开始执行,并等待名为“hello-world”的进程开始:
[zxdb] attach hello-world
Waiting for process matching "hello-world"
[zxdb] break main
Breakpoint 1 (Software) on Global, Enabled, stop=All, @ main
Pending: No matches for location, it will be pending library loads.
<ph type="x-smartling-placeholder">
调试程序连接到进程后,您可以使用 zxdb
命令来
控制和检查进程状态这里提供了
常用命令:
step
|
跳过线程中的下一行代码 |
next
|
进入线程中的下一行代码 |
continue
|
继续执行,直到下一个断点、异常或退出 |
frame
|
列出或从当前堆栈帧中选择 |
print
|
评估表达式并输出结果 |
练习:使用 Fuchsia 调试程序
在本练习中,您将使用 Fuchsia 调试程序 (zxdb
) 检查 echo
组件的正在运行的实例,并了解崩溃的原因。
启动模拟器
如果您尚未运行实例,请通过网络启动 FEMU 支持:
ffx emu start workstation_eng.x64 --headless
启动调试会话
模拟器启动后,使用zxdb
ffx debug connect
命令:
ffx debug connect
Connecting (use "disconnect" to cancel)...
Connected successfully.
👉 To get started, try "status" or "help".
[zxdb]
成功连接后,zxdb
提示符已准备好接受命令。
附加到组件
在启动组件之前,请将 zxdb
配置为附加到
echo
。这样可让调试程序在进程启动后立即连接:
[zxdb] attach echo
在 greeting()
函数上设置断点:
[zxdb] break greeting
在调试程序准备就绪的情况下,启动一个新的 echo
组件实例:
ffx component run /core/ffx-laboratory:echo fuchsia-pkg://fuchsiasamples.com/echo-example#meta/echo.cm
探索调试会话
到达 greeting()
中的断点后,执行会停止,调试程序会等待新命令。使用 list
命令显示执行位置
当前已暂停:
[zxdb] list
17
18 // Return a proper greeting for the list
▶ 19 std::string greeting(std::vector<std::string>& names) {
20 // Join the list of names based on length
21 auto number_of_names = names.size();
22 switch (number_of_names) {
23 case 0:
24 return "Nobody!";
25 case 1:
26 return join(names, "");
27 case 2:
28 return join(names, " and ");
29 default:
使用 next
命令单步进入 greeting()
函数:
[zxdb] next
print
命令会输出当前堆栈中所有变量的状态
帧。打印 names
的当前值:
[zxdb] print names {"Alice", "Bob", "Spot"}
使用 next
多次单步执行 greeting()
函数:
[zxdb] next
如需让程序继续完成,请使用 continue
命令:
[zxdb] continue
退出调试会话以返回到终端:
[zxdb] exit
引入一些崩溃代码
接下来,您将向 main()
添加一些代码,使组件崩溃
(或恐慌)。通过添加 strlen(nullptr)
引用来模拟此行为
在收集参数之后:
echo/main.cc
:
int main(int argc, const char* argv[], char* envp[]) {
// ...
// Simulate a crash
std::strlen(nullptr);
// Print a greeting to syslog
std::cout << "Hello, " << echo::greeting(arguments) << "!" << std::endl;
return 0;
}
构建并将更新后的软件包发布到 fuchsiasamples.com
代码库:
bazel run //fuchsia-codelab/echo:pkg.publish -- \
--repo_name fuchsiasamples.com
使用 zxdb
启动新的调试会话:
ffx debug connect
调试崩溃的堆栈帧
配置要附加到 echo
组件的调试程序:
[zxdb] attach echo
启动组件的新实例:
ffx component run /core/ffx-laboratory:echo fuchsia-pkg://fuchsiasamples.com/echo-example#meta/echo.cm --recreate
这一次,调试程序检测到抛出异常并停止执行:
Attached Process 1 state=Running koid=1164808 name=echo.cm
════════════════════════════════════════════════
Page fault reading address 0x0 (second chance)
════════════════════════════════════════════════
Process 1 (koid=1164808) thread 1 (koid=1164810)
Faulting instruction: 0x43e0fd349210
🛑 strlen(const char*) • strlen.c:21
[zxdb]
使用 frame
命令检查崩溃时刻的堆栈轨迹:
[zxdb] frame
▶ 0 strlen(…) • strlen.c:21
1 main(…) • main.cc:27
2 «libc startup» (-r expands)
3 «libc startup» (-r expands)
4 $elf(_start) + 0x11
请注意,堆栈轨迹中的第 1 行表示 main.cc
中发生崩溃的位置,对应于 nullptr
引用。
当前堆栈帧(帧 0)位于系统库的深层,但 可以检查任意堆栈帧,只需在命令前添加帧号 堆栈轨迹
通过传递 帧号如下:
[zxdb] frame 1 print arguments {"Alice", "Bob", "Spot"}
退出调试会话以返回终端:
[zxdb] exit
销毁实例
使用以下命令清理 echo
实例:
ffx component destroy /core/ffx-laboratory:echo