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 & 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.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 or OutgoingDirectory 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)?;
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.
Add //sdk/lib/driver/debug/cpp to your driver's target dependencies in GN (BUILD.gn) or Bazel (BUILD.bazel):
GN (BUILD.gn):
fuchsia_cc_driver("my_driver") { deps = [ "//sdk/lib/driver/component/cpp", "//sdk/lib/driver/debug/cpp", ] }
Bazel (BUILD.bazel):
fuchsia_cc_driver( name = "my_driver", deps = [ "//sdk/lib/driver/debug/cpp", "@fuchsia_sdk//pkg/driver_component_cpp", ], )
DebugServerIn your driver class, include <lib/driver/debug/cpp/debug_server.h> and add fdf_debug::DebugServer as a member:
#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_; };
Start()Register synchronous, asynchronous, or string-returning commands and call Serve(dispatcher(), outgoing()):
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);
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).