| <!-- | |
| Copyright 2026 The Fuchsia Authors. All rights reserved. | |
| Use of this source code is governed by a BSD-style license that can be | |
| found in the LICENSE file. | |
| --> | |
| # Driver Debugging Framework | |
| Fuchsia provides a productionized driver debugging framework that allows developers to author, expose, and interactively execute driver-specific diagnostic and debug commands. | |
| ## Architecture | |
| The debugging framework consists of four primary components: | |
| 1. **`fuchsia.driver.debug.Debug` FIDL Protocol**: Defines the standardized interface for listing and executing debug commands across drivers. | |
| 2. **Helper Libraries (`sdk/lib/driver/debug/rust` & `sdk/lib/driver/debug/cpp`)**: Simplifies the authoring of debug subcommands in Rust (via `argh::SubCommands` and `driver_debug`) and C++ (via `fdf_debug::DebugServer`) with argument parsing, automatic `--help` generation, command discovery, and request dispatching. | |
| 3. **`debug` CLI Utility (`src/devices/bin/driver_debug`)**: A host/target CLI tool packaged for interactive execution inside `component explore`. | |
| 4. **Driver Integration (`driver/debug.shard.cml`)**: DFv2 drivers include `driver/debug.shard.cml` in their component manifest and export `fuchsia.driver.debug.Debug` via `ServiceFs` or `OutgoingDirectory` in their outgoing directory (`/out/svc/fuchsia.driver.debug.Debug`). | |
| --- | |
| ## FIDL Protocol (`fuchsia.driver.debug`) | |
| The `fuchsia.driver.debug` protocol exposes two methods: | |
| ```fidl | |
| library fuchsia.driver.debug; | |
| using zx; | |
| const MAX_COMMAND_NAME_LENGTH uint32 = 256; | |
| const MAX_ARG_LENGTH uint32 = 1024; | |
| const MAX_ARG_COUNT uint32 = 128; | |
| const MAX_DESCRIPTION_LENGTH uint32 = 1024; | |
| const MAX_COMMAND_COUNT uint32 = 64; | |
| type CommandInfo = table { | |
| 1: name string:MAX_COMMAND_NAME_LENGTH; | |
| 2: description string:MAX_DESCRIPTION_LENGTH; | |
| }; | |
| @discoverable | |
| open protocol Debug { | |
| flexible Execute(resource struct { | |
| args vector<string:MAX_ARG_LENGTH>:MAX_ARG_COUNT; | |
| stdout zx.Handle:SOCKET; | |
| stderr zx.Handle:SOCKET; | |
| }) -> (struct { | |
| exit_code int32; | |
| }) error zx.Status; | |
| flexible ListCommands() -> (struct { | |
| commands vector<CommandInfo>:MAX_COMMAND_COUNT; | |
| }) error zx.Status; | |
| }; | |
| ``` | |
| --- | |
| ## Implementing Debug Commands in a Rust Driver | |
| ### 1. Include the CML Shard in the Driver Manifest | |
| `driver/debug.shard.cml` is not included implicitly for all drivers. Any driver that implements the `fuchsia.driver.debug.Debug` protocol must explicitly include `"driver/debug.shard.cml"` in its `.cml` component manifest. | |
| This shard: | |
| - Declares and exposes the `fuchsia.driver.debug.Debug` protocol capability from `self`. | |
| - Configures the `fuchsia.dash.launcher-tool-urls` facet (`"fuchsia-pkg://fuchsia.com/driver_debug"`) so the `debug` CLI tool is automatically available in `ffx component explore`. | |
| ```json5 | |
| { | |
| include: [ | |
| "driver/debug.shard.cml", | |
| "driver_component/driver.shard.cml", | |
| "inspect/client.shard.cml", | |
| "syslog/client.shard.cml", | |
| ], | |
| program: { | |
| runner: "driver", | |
| binary: "driver/my_driver.so", | |
| bind: "meta/bind/my_driver.bindbc", | |
| }, | |
| } | |
| ``` | |
| ### 2. Define Subcommands with `argh` | |
| Define your subcommand structs and top-level subcommand enum with `#[derive(FromArgs)]` and `#[argh(subcommand)]`: | |
| ```rust | |
| use argh::FromArgs; | |
| #[derive(FromArgs, Debug, PartialEq)] | |
| #[argh(subcommand, name = "ping")] | |
| /// Ping the driver to verify connectivity. | |
| pub struct PingArgs { | |
| #[argh(option, short = 'c', default = "1", description = "number of pings")] | |
| pub count: u32, | |
| } | |
| #[derive(FromArgs, Debug, PartialEq)] | |
| #[argh(subcommand, name = "reset")] | |
| /// Reset the hardware device state. | |
| pub struct ResetArgs { | |
| #[argh(switch, description = "perform a hard reset")] | |
| pub hard: bool, | |
| } | |
| #[derive(FromArgs, Debug, PartialEq)] | |
| #[argh(subcommand)] | |
| pub enum MyDriverCommands { | |
| Ping(PingArgs), | |
| Reset(ResetArgs), | |
| } | |
| ``` | |
| ### 3. Handle Debug Requests | |
| Use `driver_debug::next_command` in a `while let` loop to read parsed subcommands from a `DebugRequestStream`: | |
| ```rust | |
| use fidl_fuchsia_driver_debug::DebugRequestStream; | |
| async fn handle_debug(mut stream: DebugRequestStream) -> Result<(), fidl::Error> { | |
| while let Some((cmd, responder)) = driver_debug::next_command(&mut stream).await? { | |
| let result: Result<String, anyhow::Error> = match cmd { | |
| MyDriverCommands::Ping(args) => { | |
| let mut out = String::new(); | |
| for i in 1..=args.count { | |
| out.push_str(&format!("ping {i}\n")); | |
| } | |
| Ok(out) | |
| } | |
| MyDriverCommands::Reset(args) => { | |
| if args.hard { | |
| // perform hardware reset | |
| Ok("hard reset completed\n".to_string()) | |
| } else { | |
| Ok("soft reset completed\n".to_string()) | |
| } | |
| } | |
| }; | |
| responder.send(result)?; | |
| } | |
| Ok(()) | |
| } | |
| ``` | |
| `driver_debug::next_command` automatically: | |
| - Responds to `ListCommands` requests using `argh::SubCommands` metadata (command names and doc comments). | |
| - Handles `--help` flags and syntax/argument parsing errors on `Execute` requests, writing output to `stdout`/`stderr` and returning the appropriate exit code (`0` for help, `2` for syntax errors). | |
| Alternatively, you can use `driver_debug::serve(stream, handler)` with an async closure when you don't need custom stream loop control. | |
| ### 4. Serve the Protocol in the Driver | |
| In your driver's `start` method: | |
| ```rust | |
| use fidl_fuchsia_driver_debug::DebugRequestStream; | |
| use fuchsia_component::server::ServiceFs; | |
| let mut service_fs = ServiceFs::new(); | |
| service_fs.dir("svc").add_fidl_service(move |stream: DebugRequestStream| { | |
| fuchsia_async::Scope::current().spawn(async move { | |
| let _ = handle_debug(stream).await; | |
| }); | |
| }); | |
| context.serve_outgoing(&mut service_fs)?; | |
| ``` | |
| --- | |
| ## Implementing Debug Commands in a C++ Driver (`fdf_debug::DebugServer`) | |
| C++ DFv2 drivers can use the `//sdk/lib/driver/debug/cpp` library to register debug commands and serve `fuchsia.driver.debug.Debug`. | |
| ### 1. Add Build Dependency | |
| Add `//sdk/lib/driver/debug/cpp` to your driver's target dependencies in GN (`BUILD.gn`) or Bazel (`BUILD.bazel`): | |
| GN (`BUILD.gn`): | |
| ```gn | |
| fuchsia_cc_driver("my_driver") { | |
| deps = [ | |
| "//sdk/lib/driver/component/cpp", | |
| "//sdk/lib/driver/debug/cpp", | |
| ] | |
| } | |
| ``` | |
| Bazel (`BUILD.bazel`): | |
| ```bazel | |
| fuchsia_cc_driver( | |
| name = "my_driver", | |
| deps = [ | |
| "//sdk/lib/driver/debug/cpp", | |
| "@fuchsia_sdk//pkg/driver_component_cpp", | |
| ], | |
| ) | |
| ``` | |
| ### 2. Include Header and Declare `DebugServer` | |
| In your driver class, include `<lib/driver/debug/cpp/debug_server.h>` and add `fdf_debug::DebugServer` as a member: | |
| ```cpp | |
| #include <lib/driver/component/cpp/driver_base2.h> | |
| #include <lib/driver/component/cpp/driver_export2.h> | |
| #include <lib/driver/debug/cpp/debug_server.h> | |
| class MyDriver : public fdf::DriverBase2 { | |
| public: | |
| MyDriver() : fdf::DriverBase2("my_driver") {} | |
| zx::result<> Start(fdf::DriverContext context) override; | |
| private: | |
| fdf_debug::DebugServer debug_server_; | |
| }; | |
| ``` | |
| ### 3. Register Commands and Serve in `Start()` | |
| Register synchronous, asynchronous, or string-returning commands and call `Serve(dispatcher(), outgoing())`: | |
| ```cpp | |
| zx::result<> MyDriver::Start(fdf::DriverContext context) { | |
| // Register synchronous string command: | |
| debug_server_.RegisterCommand( | |
| "ping", "Ping the driver.", "[-c <count>]", | |
| [](cpp20::span<const std::string> args) -> fit::result<std::string, std::string> { | |
| int count = 1; | |
| for (size_t i = 0; i < args.size(); ++i) { | |
| if (args[i] == "-c" && i + 1 < args.size()) { | |
| count = std::stoi(std::string(args[++i])); | |
| } else { | |
| return fit::error("Unknown option: " + std::string(args[i])); | |
| } | |
| } | |
| std::string out; | |
| for (int i = 1; i <= count; ++i) { | |
| out += "ping " + std::to_string(i) + "\n"; | |
| } | |
| return fit::ok(out); | |
| }); | |
| // Register full ExecutionResult command: | |
| debug_server_.RegisterCommand( | |
| "reset", "Reset the driver state.", "[--hard]", | |
| [](cpp20::span<const std::string> args) -> fdf_debug::ExecutionResult { | |
| bool hard = false; | |
| for (const auto& arg : args) { | |
| if (arg == "--hard") { | |
| hard = true; | |
| } | |
| } | |
| fdf_debug::ExecutionResult res; | |
| res.exit_code() = 0; | |
| res.stdout_() = hard ? "hard reset\n" : "soft reset\n"; | |
| res.stderr_() = ""; | |
| return res; | |
| }); | |
| // Register asynchronous command: | |
| debug_server_.RegisterCommand( | |
| "async_action", "Run async diagnostic test.", | |
| [this](cpp20::span<const std::string> args, | |
| fit::callback<void(fdf_debug::ExecutionResult)> completer) { | |
| // Run asynchronous workload and invoke completer when done | |
| }); | |
| // Serve Debug protocol on the driver's outgoing directory: | |
| zx::result result = debug_server_.Serve(dispatcher(), outgoing()); | |
| if (result.is_error()) { | |
| return result.take_error(); | |
| } | |
| return zx::ok(); | |
| } | |
| FUCHSIA_DRIVER_EXPORT2(MyDriver); | |
| ``` | |
| --- | |
| ## Using the `debug` CLI in `component explore` | |
| When debugging a running system, use `ffx component explore` to drop into the driver component namespace: | |
| ```sh | |
| $ ffx component explore /bootstrap/boot-drivers:my-driver | |
| ``` | |
| ### List Available Commands | |
| ```sh | |
| $ debug list-commands | |
| COMMAND DESCRIPTION | |
| ------- ----------- | |
| ping Ping the driver to verify connectivity. | |
| reset Reset the hardware device state. | |
| ``` | |
| ### Execute Commands | |
| ```sh | |
| $ debug ping -c 3 | |
| ping 1 | |
| ping 2 | |
| ping 3 | |
| ``` | |
| ```sh | |
| $ debug reset --hard | |
| hard reset completed | |
| ``` | |
| ### Help and Diagnostics | |
| ```sh | |
| $ debug --help | |
| Usage: debug <command> [<args>] | |
| Driver debug commands. | |
| Options: | |
| --help display usage information | |
| Commands: | |
| ping Ping the driver to verify connectivity. | |
| reset Reset the hardware device state. | |
| ``` | |
| ### Return Codes | |
| - `0`: Success (or `--help` output printed to standard output). | |
| - `1`: Driver internal execution error / handler failure (details in standard error). | |
| - `2`: Syntax or argument parsing error (details in standard error). |