驅動程式偵錯架構

Fuchsia 提供正式版驅動程式庫偵錯架構,可讓開發人員編寫、公開及互動式執行驅動程式庫專屬的診斷和偵錯指令。

建築

偵錯架構包含四個主要元件:

  1. fuchsia.driver.debug.Debug FIDL 通訊協定:定義標準化介面,用於列出及執行驅動程式的偵錯指令。
  2. 輔助程式庫 (sdk/lib/driver/debug/rust 和 sdk/lib/driver/debug/cpp):簡化 Rust (透過 argh::SubCommands 和 driver_debug) 和 C++ (透過 fdf_debug::DebugServer) 中偵錯子指令的編寫作業,提供引數剖析、自動產生 --help、指令探索和要求調度功能。
  3. debug CLI 公用程式 (src/devices/bin/driver_debug):主機/目標 CLI 工具,封裝後可在 component explore 內互動執行。
  4. 驅動程式整合 (driver/debug.shard.cml):DFv2 驅動程式會在元件資訊清單中加入 driver/debug.shard.cml,並透過傳出目錄 (/out/svc/fuchsia.driver.debug.Debug) 中的 ServiceFs 或 OutgoingDirectory 匯出 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 shard

並非所有驅動程式都隱含 driver/debug.shard.cml。任何實作 fuchsia.driver.debug.Debug 通訊協定的驅動程式庫,都必須在 .cml 元件資訊清單中明確包含 "driver/debug.shard.cml"。

這個分片: - 宣告並公開 self 的 fuchsia.driver.debug.Debug 通訊協定能力。 - 設定 fuchsia.dash.launcher-tool-urls 構面 ("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. 新增建構依附元件

在 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:語法或引數剖析錯誤 (詳情請參閱標準錯誤)。