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:
fuchsia.driver.debug.DebugFIDL Protocol: Defines the standardized interface for listing and executing debug commands across drivers.- Rust Helper Library (
sdk/lib/driver/debug/rust): Simplifies the authoring of debug subcommands in Rust drivers witharghargument parsing, automatic--helpgeneration, command discovery, and request dispatching. debugCLI Utility (src/devices/bin/driver_debug): A host/target CLI tool packaged for interactive execution insidecomponent explore.- Driver Integration (
driver/debug.shard.cml): DFv2 drivers includedriver/debug.shard.cmlin their component manifest and exportfuchsia.driver.debug.DebugviaServiceFsin 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--helpoutput 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).