Fuchsia 提供了一个生产化的驱动程序调试框架,让开发者可以编写、公开和以交互方式执行特定于驱动程序的诊断和调试命令。
架构
调试框架由四个主要组件组成:
fuchsia.driver.debug.DebugFIDL 协议 :定义了用于列出和执行跨驱动程序的调试命令的标准化接口。- 帮助程序库(
sdk/lib/driver/debug/rust和sdk/lib/driver/debug/cpp) :通过实参解析、自动--help生成、命令发现和请求调度,简化了在 Rust(通过argh::SubCommands和driver_debug)和 C++(通过fdf_debug::DebugServer)中编写调试子命令的过程。 debugCLI 实用程序 (src/devices/bin/driver_debug):一种主机/目标 CLI 工具,打包用于在component explore内以交互方式执行。- 驱动程序集成 (
driver/debug.shard.cml):DFv2 驱动程序在其组件清单中包含driver/debug.shard.cml,并通过ServiceFs或OutgoingDirectory在其传出目录 (/out/svc/fuchsia.driver.debug.Debug) 中导出fuchsia.driver.debug.Debug。
FIDL 协议 (fuchsia.driver.debug)
fuchsia.driver.debug 协议公开了两种方法:
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;
};
在 Rust 驱动程序中实现调试命令
1. 在驱动程序清单中添加 CML 分片
并非所有驱动程序都隐式包含 driver/debug.shard.cml。任何实现 fuchsia.driver.debug.Debug 协议的驱动程序都必须在其 .cml 组件清单中明确包含 "driver/debug.shard.cml"。
此分片:
- 声明并公开来自 self 的 fuchsia.driver.debug.Debug 协议功能。
- 配置 fuchsia.dash.launcher-tool-urls facet ("fuchsia-pkg://fuchsia.com/driver_debug"),以便 debug CLI 工具在 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. 使用 argh 定义子命令
使用 #[derive(FromArgs)] 和 #[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. 处理调试请求
在 while let 循环中使用 driver_debug::next_command 从 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 会自动执行以下操作:
- 使用 argh::SubCommands 元数据(命令名称和文档注释)响应 ListCommands 请求。
- 处理 Execute 请求中的 --help 标志和语法/实参解析错误,将输出写入 stdout/stderr 并返回相应的退出代码(帮助为 0,语法错误为 2)。
或者,如果您不需要自定义流循环控制,可以使用带有异步闭包的 driver_debug::serve(stream, handler)。
4. 在驱动程序中提供协议
在驱动程序的 start 方法中:
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)?;
在 C++ 驱动程序中实现调试命令 (fdf_debug::DebugServer)
C++ DFv2 驱动程序可以使用 //sdk/lib/driver/debug/cpp 库注册调试命令并提供 fuchsia.driver.debug.Debug。
1. 添加 build 依赖项
在 GN (BUILD.gn) 或 Bazel (BUILD.bazel) 中,将 //sdk/lib/driver/debug/cpp 添加到驱动程序的目标依赖项:
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",
],
)
2. 添加头文件并声明 DebugServer
在驱动程序类中,添加 <lib/driver/debug/cpp/debug_server.h> 并将 fdf_debug::DebugServer 添加为成员:
#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. 在 Start() 中注册命令并提供服务
注册同步、异步或字符串返回命令,并调用 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);
在 component explore 中使用 debug CLI
调试正在运行的系统时,使用 ffx component explore 进入驱动程序组件命名空间:
$ 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:成功(或--help输出已打印到标准输出)。1:驱动程序内部执行错误 / 处理程序失败(详细信息位于标准错误中)。2:语法或实参解析错误(详细信息位于标准错误中)。