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. Rust Helper Library (sdk/lib/driver/debug/rust): Simplifies the authoring of debug subcommands in Rust drivers with argh 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 in their outgoing directory (/out/svc/fuchsia.driver.debug.Debug).

FIDL Protocol (fuchsia.driver.debug)

The fuchsia.driver.debug protocol exposes two methods:

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.

{
    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)]:

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:

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:

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)?;

Using the debug CLI in component explore

When debugging a running system, use ffx component explore to drop into the driver component namespace:

$ ffx component explore /bootstrap/boot-drivers:my-driver

List Available Commands

$ debug list-commands
COMMAND              DESCRIPTION
-------              -----------
ping                 Ping the driver to verify connectivity.
reset                Reset the hardware device state.

Execute Commands

$ debug ping -c 3
ping 1
ping 2
ping 3
$ debug reset --hard
hard reset completed

Help and Diagnostics

$ 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).