Zxdb provides scripting support that lets you automate repetitive debugging tasks, jump to a specific application state, and verify debugger behavior in an automated way. Scripts are plain text files containing a sequence of zxdb commands alongside their expected output.
To run a script from the command line when launching zxdb, use the -S or --script-file option.
ffx debug:ffx debug connect -- --script-file=my_script.script
zxdb directly (note that this will only work with offline core files, like a minidump, so your script should start by loading a core file with the opendump command. See help opendump for details.):zxdb --script-file=my_script.script
Scripts are evaluated line-by-line and generally alternate between entering commands and matching lines of output.
Lines starting with [zxdb] are interpreted as commands to run. These commands are executed the same as they would be in the interactive zxdb console.
[zxdb] attach my_component.cm
You can use abbreviations and any standard zxdb features:
[zxdb] t * f
Which is equivalent to:
[zxdb] thread * frame
If a line does not start with [zxdb] and is not a comment, it is treated as a pattern to match against the output of the previously executed command.
Because many commands in zxdb are asynchronous, the debugger needs to know when a command has actually completed its output before issuing the next one. Zxdb will wait for the output to match the specified lines before proceeding to the next command. This ensures your script stays synchronized with the debugger's asynchronous events.
[zxdb] attach cobalt.cm Attached Process Done.
If you don‘t care about the output of a command, you don’t need to specify any matching lines. You can write the next [zxdb] command, and the previous command will be allowed to finish immediately.
If the commands have any dependencies, this might lead to flaky results. For example:
[zxdb] b $main Created Breakpoint 1 @ $main [zxdb] run-component fuchsia-pkg://fuchsia.com/zxdb_e2e_inferiors#meta/async_rust_multithreaded.cm Launched Process 1 state=Running koid=?? name=async_rust_multithreaded.cm component=async_rust_multithreaded.cm 🛑 [zxdb] until foo # This might lead to flaky results [zxdb] thread
The result from thread could be either
▶ 7 #[fasync::run(2)] 8 async fn main() { 9 let _task_a = fasync::Task::spawn(async {}); 🛑 on bp 1 async_rust_multithreaded::main() • async_rust_multithreaded.rs:7 [zxdb] until foo [zxdb] thread # state koid name ▶ 1 Blocked (Futex) 24769196 initial-thread 2 Blocked (Port) 24769684 executor_worker 3 Blocked (Port) 24769691 executor_worker 19 } 20 ▶ 21 async fn foo(i: u64) { 22 fasync::Timer::new(std::time::Duration::from_secs(i)).await; 23 }
or
▶ 7 #[fasync::run(2)] 8 async fn main() { 9 let _task_a = fasync::Task::spawn(async {}); 🛑 on bp 1 async_rust_multithreaded::main() • async_rust_multithreaded.rs:7 [zxdb] until foo 19 } 20 ▶ 21 async fn foo(i: u64) { 22 fasync::Timer::new(std::time::Duration::from_secs(i)).await; 23 } 🛑 thread 2 async_rust_multithreaded::foo(u64) • async_rust_multithreaded.rs:21 [zxdb] thread # state koid name 1 Suspended 24775467 initial-thread ▶ 2 Blocked (Exception) 24775973 executor_worker 3 Suspended 24775980 executor_worker
You can reproduce it by manually entering the commands very fast or use the script runner.
It‘s because the until and thread commands are running concurrently. To avoid flakiness, it’s recommended to use a pause symbol (🛑) as an expected output. For example:
[zxdb] until foo 🛑 [zxdb] thread
If you only want to match parts of a line, or if a line contains unpredictable data (like memory addresses, process IDs, or file paths), you can use the ?? wildcard. It matches arbitrary sections of a line (up to the entire line).
[zxdb] frame ▶ 0 MyFunction() • my_file.cc:??
Lines starting with # are comments and are ignored. Comments are useful for documenting the script, but remember that UTF-8 characters are also supported.
# This is a comment explaining what the next command does. [zxdb] pause # Wait for the stop event 🛑
By default, the script expects output lines to arrive in the exact order specified. If the command produces output where the order is not guaranteed, you can add ## allow-out-of-order-output anywhere in the block of expected output. This ensures all the specified lines match, regardless of the order they are printed in. This directive must come immediately after the zxdb command line and before any expected output that is to be matched for the matching algorithm to correctly take into account all output.
[zxdb] thread ## allow-out-of-order-output 1 State: Running 2 State: Suspended
Once the script completes, you will automatically be dropped into the interactive [zxdb] command line.
If you prefer zxdb to exit automatically at the end of the script instead, you can append a quit command at the end of the file. However, ensure that the previous command has matching output specified, otherwise quit might be executed before the previous command has finished.
[zxdb] quit --force
This script attaches to a component, pauses it, prints the async backtrace, and leaves you in the interactive console to investigate further.
# Attach to the component [zxdb] attach hwinfo.cm Attached Process Done. # Pause execution [zxdb] pause 🛑 # Output the tree of async tasks. [zxdb] async-backtrace
This script attaches to a component, waits for it to stop, prints the backtraces of all threads, and then drops into the interactive console.
[zxdb] attach cobalt.cm Attached Process Done. [zxdb] pause 🛑 # Output the backtraces of all threads of the current process. [zxdb] thread * frame