Debugging components

<<../../_common/components/_debugging_intro.md>>

<<../../_common/components/_debugging_analyze.md>>

Exercise: Using the Fuchsia debugger

In this exercise, you'll use the Fuchsia debugger (zxdb) to inspect a running instance of the echo-args component and understand the cause of a crash.

<<../_common/_start_femu_with_packages.md>>

Start a debug session

Once the emulator has started up, start a zxdb debugging session with the ffx debug connect command:

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

After successfully connecting, the zxdb prompt is ready to accept commands.

Attach to the component

Before launching the component, configure zxdb to attach to an instance of echo-args. This enables the debugger to attach as soon as the process starts:

Set a breakpoint on the greeting() function:

With the debugger ready, start a new echo-args component instance:

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

Explore the debug session

Upon reaching the breakpoint in greeting(), execution stops and the debugger waits for a new command. Use the list command to show where execution is currently paused:

  • {Rust}

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

The print command will output the state of any variables in the current stack frame. Print the current value of names:

  • {Rust}

  • {C++}

Step through the greeting() function a few times using the next command:

To let the program continue to completion, use the continue command:

Exit the debugging session to return to the terminal:

Introduce some crashing code

Next, you'll add some code to src/main.rs to cause the component to crash (or panic). Simulate this behavior by adding an assert!(false) macro just after the arguments are collected:

  • {Rust}

    echo-args/src/main.rs:

    #[fuchsia::main(logging = true)]
    async fn main() -> Result<(), anyhow::Error> {
        // ...
    
        {{ '<strong>' }}// Simulate a crash {{ '</strong>' }}
        {{ '<strong>' }}assert!(false, "fake crash");{{ '</strong>' }}
    
        // Print a greeting to syslog
        info!("Hello, {}!", greeting(&args));
    
        Ok(())
    }
    
  • {C++}

    echo-args/main.cc:

    int main(int argc, const char* argv[], char* envp[]) {
      // ...
    
      {{ '<strong>' }}// Simulate a crash {{ '</strong>' }}
      {{ '<strong>' }}std::strlen(nullptr);{{ '</strong>' }}
    
      // Print a greeting to syslog
      FX_LOGS(INFO) << "Hello, " << echo::greeting(arguments) << "!" << std::endl;
    
      return 0;
    }
    

Run fx build again to rebuild the component:

fx build

Start a new debug session with zxdb:

ffx debug connect

Debug the crashing stack frame

Configure the debugger to attach to the echo-args component:

Start a new instance of the component:

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

This time, the debugger detects that an exception was thrown. Use the frame command to inspect the stack trace at the point of the crash:

  • {Rust}

    [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
      {{ '<strong>' }}8 echo_args::main::component_entry_point::$({generator#0})(…) • main.rs:18{{ '</strong>' }}
      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
    

    Notice line 8 in the stack trace indicates the point in src/main.rs where the crash happened, corresponding to the assert!() macro line of code.

  • {C++}

    [zxdb] frame
     0 strlen(…)  strlen.c:21
      {{ '<strong>' }}1 main(…)  main.cc:27{{ '</strong>' }}
      2 «libc startup» (-r expands)
      3 «libc startup» (-r expands)
      4 $elf(_start) + 0x11
    

    Notice line 1 in the stack trace indicates the point in main.cc where the crash happened, corresponding to the nullptr reference.

The current stack frame (frame 0) is deep within the system library, but you can inspect any stack frame by prefixing the command with the frame number from the stack trace.

Print the value of the arguments at the point of the crash by passing the frame number as follows:

  • {Rust}

  • {C++}

Exit the debugging session to return to the terminal:

Destroy the instance

Clean up the echo-args instance using the following command:

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