Fuchsia provides a productionized driver debugging framework that allows developers to author, expose, and interactively execute driver-specific diagnostic and debug commands.
The debugging framework consists of four primary components:
fuchsia.driver.debug.Debug FIDL Protocol: Defines the standardized interface for listing and executing debug commands across drivers.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.debug CLI Utility (src/devices/bin/driver_debug): A host/target CLI tool packaged for interactive execution inside component explore.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).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; };
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:
fuchsia.driver.debug.Debug protocol capability from self.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", }, }
arghDefine 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), }
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:
ListCommands requests using argh::SubCommands metadata (command names and doc comments).--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.
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)?;
debug CLI in component exploreWhen debugging a running system, use ffx component explore to drop into the driver component namespace:
$ ffx component explore /bootstrap/boot-drivers:my-driver
$ debug list-commands COMMAND DESCRIPTION ------- ----------- ping Ping the driver to verify connectivity. reset Reset the hardware device state.
$ debug ping -c 3 ping 1 ping 2 ping 3
$ debug reset --hard hard reset completed
$ 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.
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).