偵錯元件

開發軟體時,您必須處理程式當機問題,並找出錯誤來源。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

有了正確符號化的回溯追蹤,您就能直接在原始碼中找出當機位置。

逐步偵錯

光是知道程式當機的位置,可能不足以完整診斷問題。有時必須逐步檢查程式碼,甚至檢查記憶體中的變數狀態。為支援這項功能,Fuchsia 提供了核心程式碼的偵錯工具,稱為 zxdb

zxdb 工具是連線至目標裝置上執行中 debug_agent 元件的用戶端。您可以使用 zxdb 指令設定要附加至特定程序的 debug_agent,並設定中斷點。偵錯工作階段附加至執行中的程序後,zxdb 可讓您逐步執行程式碼,並檢查堆疊框架。

這張圖表顯示 Fuchsia 除錯工具 (zxdb) 如何與 Fuchsia 裝置上執行的 debug_agent 服務互動,以對程序執行互動式除錯。

如要設定偵錯工作階段,請按照下列高階步驟操作:

  1. 在目標裝置上執行 debug_agent 元件。
  2. 執行 zxdb 用戶端並連線至目標裝置。
  3. 設定 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.

將偵錯工具附加至程序後,您可以使用 zxdb 指令控制及檢查程序狀態。以下簡要列出常見指令:

step 略過執行緒中的下一行程式碼
next 逐步執行執行緒中的下一行程式碼
continue 繼續執行,直到下一個中斷點、例外狀況或結束
frame 列出或選取目前的堆疊框架
print 評估運算式並列印結果

練習:使用 Fuchsia 偵錯工具

在本練習中,您將使用 Fuchsia 偵錯工具 (zxdb) 檢查 echo-args 元件的執行個體,並瞭解當機原因。

啟動模擬器

如果沒有正在執行的執行個體,請啟動模擬器:

  1. 啟動新的模擬器執行個體:

    ffx emu start --headless

    啟動完成後,模擬器會輸出下列訊息並傳回:

    Logging to "$HOME/.local/share/Fuchsia/ffx/emu/instances/fuchsia-emulator/emulator.log"
    Waiting for Fuchsia to start (up to 60 seconds)........
    Emulator is ready.
    
  2. 啟動套件伺服器,讓模擬器載入軟體套件:

    fx serve

啟動偵錯工作階段

模擬器啟動後,請使用 ffx debug connect 指令啟動 zxdb 偵錯工作階段:

ffx debug connect
Connecting (use "disconnect" to cancel)...
Connected successfully.
👉 To get started, try "status" or "help".
[zxdb]

連線成功後,zxdb 提示符號即可接受指令。

附加至元件

啟動元件前,請設定 zxdb,以便附加至 echo-args 的執行個體。這樣一來,偵錯工具就能在程序啟動後立即附加:

[zxdb] attach echo-args

greeting() 函式上設定中斷點:

[zxdb] break greeting

準備好偵錯工具後,請啟動新的 echo-args 元件執行個體:

ffx component run /core/ffx-laboratory:echo-args fuchsia-pkg://fuchsia.com/echo-args#meta/echo-args.cm

探索偵錯工作階段

greeting() 中達到中斷點後,執行作業會停止,偵錯工具會等待新指令。使用 list 指令顯示目前暫停執行的位置:

荒漠油廠

[zxdb] list
  18
  19 // Return a proper greeting for the list
  20 fn greeting(names: &Vec<String>) -> String {
  21     // Join the list of names based on length
▶ 22     match names.len() {
  23         0 => String::from("Nobody"),
  24         1 => names.join(""),
  25         2 => names.join(" and "),
  26         _ => names.join(", "),
  27     }
  28 }
  29

C++

[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:

print 指令會輸出目前堆疊框架中任何變數的狀態。列印 names 的目前值:

荒漠油廠

[zxdb] print names
vec!["Alice", "Bob", "Spot"]

C++

[zxdb] print names
{"Alice", "Bob", "Spot"}

使用 next 指令逐步執行 greeting() 函式幾次:

[zxdb] next

如要讓程式繼續執行直到完成,請使用 continue 指令:

[zxdb] continue

結束偵錯工作階段,返回終端機:

[zxdb] exit

導入一些會導致當機的程式碼

接著,您會在 src/main.rs 中新增一些程式碼,導致元件當機 (或恐慌)。在收集引數後,立即新增 assert!(false) 巨集,模擬這項行為:

荒漠油廠

echo-args/src/main.rs

#[fuchsia::main(logging = true)]
async fn main() -> Result<(), anyhow::Error> {
    // ...

    // Simulate a crash 
    assert!(false, "fake crash");

    // Print a greeting to syslog
    info!("Hello, {}!", greeting(&args));

    Ok(())
}

C++

echo-args/main.cc

int main(int argc, const char* argv[], char* envp[]) {
  // ...

  // Simulate a crash 
  std::strlen(nullptr);

  // Print a greeting to syslog
  FX_LOGS(INFO) << "Hello, " << echo::greeting(arguments) << "!" << std::endl;

  return 0;
}

再次執行 fx build,重建元件:

fx build

使用 zxdb 啟動新的偵錯工作階段:

ffx debug connect

偵錯導致當機的堆疊框架

設定偵錯工具,附加至 echo-args 元件:

[zxdb] attach echo-args

啟動元件的新執行個體:

ffx component run /core/ffx-laboratory:echo-args fuchsia-pkg://fuchsia.com/echo-args#meta/echo-args.cm

這次偵錯工具會偵測到擲回的例外狀況。使用 frame 指令檢查當機時的堆疊追蹤記錄:

荒漠油廠

[zxdb] frame
▶ 0 abort() • abort.c:7
  1 panic_abort::__rust_start_panic::abort() • panic_abort/src/lib.rs:43
  2 panic_abort::__rust_start_panic(…) • panic_abort/src/lib.rs:38
  3 std::panicking::rust_panic(…) • library/std/src/panicking.rs:672
  4 std::panicking::rust_panic_with_hook(…) • library/std/src/panicking.rs:642
  5 std::panicking::begin_panic::$({closure#0}<&str>)() • rust/library/std/src/panicking.rs:544
  6 std::sys_common::backtrace::$(__rust_end_short_backtrace<std::panicking::begin_panic::{closure#0}, !>)(…) • rust/library/std/src/sys_common/backtrace.rs:144
  7 std::panicking::begin_panic<…>(…) • rust/library/std/src/panicking.rs:543
  8 echo_args::main::component_entry_point::$({generator#0})(…) • main.rs:18
  9 core::future::from_generator::$({impl#1})::$(poll<echo_args::main::component_entry_point::{generator#0}>)(…) • rust/library/core/src/future/mod.rs:80
  10 core::future::future::$({impl#1})::$(poll<&mut core::future::from_generator::GenFuture<echo_args::main::component_entry_point::{generator#0}>>)(…) • future/future.rs:119
  11 futures_util::future::future::FutureExt::$(poll_unpin<core::pin::Pin<&mut core::future::from_generator::GenFuture<echo_args::main::component_entry_point::{generator#0}>>>)(…) • future/future/mod.rs:562
  12 fuchsia_async::runtime::fuchsia::executor::local::MainTask::$(poll<core::pin::Pin<&mut core::future::from_generator::GenFuture<echo_args::main::component_entry_point::{generator#0}>>>)(…) • fuchsia/src/lib/fuchsia-async/src/runtime/fuchsia/executor/local.rs:444
  13 fuchsia_async::runtime::fuchsia::executor::local::LocalExecutor::$(run_singlethreaded<core::future::from_generator::GenFuture<echo_args::main::component_entry_point::{generator#0}>>)(…) • fuchsia/src/lib/fuchsia-async/src/runtime/fuchsia/executor/local.rs:73
  14 fuchsia::$(main_singlethreaded<fuchsia::init_logging_for_component_with_executor::{closure#0}, core::future::from_generator::GenFuture<echo_args::main::component_entry_point::{generator#0}>, core::result::Result<(), anyhow::Error>>)(…) • fuchsia/src/lib/fuchsia/src/lib.rs:152
  15 echo_args::main() • main.rs:7
  16 core::ops::function::FnOnce::call_once<…>(…) • /b/s/w/ir/x/w/rust/library/core/src/ops/function.rs:227
  17 std::sys_common::backtrace::__rust_begin_short_backtrace<…>(…) • rust/library/std/src/sys_common/backtrace.rs:125
  18 std::rt::lang_start::$({closure#0}<core::result::Result<(), anyhow::Error>>)() • rust/library/std/src/rt.rs:63
  19 core::ops::function::impls::$({impl#2})::call_once<…>(…) • /b/s/w/ir/x/w/rust/library/core/src/ops/function.rs:259 (inline)
  20 std::panicking::try::do_call<…>(…) • library/std/src/panicking.rs:403 (inline)
  21 std::panicking::try<…>(…) • library/std/src/panicking.rs:367 (inline)
  22 std::panic::catch_unwind<…>(…) • library/std/src/panic.rs:129 (inline)
  23 std::rt::lang_start_internal::$({closure#2})() • library/std/src/rt.rs:45 (inline)
  24 std::panicking::try::$(do_call<std::rt::lang_start_internal::{closure#2}, isize>)(…) • library/std/src/panicking.rs:403 (inline)
  25 std::panicking::$(try<isize, std::rt::lang_start_internal::{closure#2}>)(…) • library/std/src/panicking.rs:367 (inline)
  26 std::panic::$(catch_unwind<std::rt::lang_start_internal::{closure#2}, isize>)(…) • library/std/src/panic.rs:129 (inline)
  27 std::rt::lang_start_internal(…) • library/std/src/rt.rs:45
  28 std::rt::lang_start<…>(…) • rust/library/std/src/rt.rs:62
  29 $elf(main) + 0x1f
  30 «libc startup» (-r expands)
  31 «libc startup» (-r expands)
  32 $elf(_start) + 0x11

請注意,堆疊追蹤中的第 8 行指出 src/main.rs 發生當機的點,對應於 assert!() 巨集程式碼行。

C++

[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 中發生當機的 main.cc 參照點。nullptr

目前的堆疊框架 (框架 0) 位於系統程式庫深處,但您可以在指令前加上堆疊追蹤中的框架編號,檢查任何堆疊框架。

如要輸出當機時的引數值,請傳遞影格編號,如下所示:

荒漠油廠

[zxdb] frame 8 print args
vec!["Alice", "Bob", "Spot"]

C++

[zxdb] frame 1 print arguments
{"Alice", "Bob", "Spot"}

結束偵錯工作階段,返回終端機:

[zxdb] exit

終止執行個體

使用下列指令清理 echo-args 執行個體:

ffx component destroy /core/ffx-laboratory:echo-args