测试和调试驱动程序

Fuchsia 支持使用 Fuchsia 调试程序 (zxdb) 对组件进行分步调试。调试程序连接到运行组件的主机进程,允许开发者设置断点并逐步完成代码执行。借助测试运行程序框架,开发者可以编写执行驱动程序组件的测试。

在本部分中,您将使用 Fuchsia 调试程序 (zxdb) 检查正在运行的驱动程序,并构建测试组件来运行驱动程序的功能。

连接调试程序

如需将 Fuchsia 调试程序连接到驱动程序组件,您需要确定主机进程的 PID。使用 ffx driver list-hosts 命令发现加载驱动程序的主机进程的 PID:

ffx driver list-hosts

该命令会输出类似于以下内容的列表。找到列出了 qemu_edu 驱动程序的驱动程序主机:

Driver Host: 5053
    fuchsia-boot:///#meta/block.core.cm
    fuchsia-boot:///#meta/bus-pci.cm
    fuchsia-boot:///#meta/cpu-trace.cm
    fuchsia-boot:///#meta/fvm.cm
    fuchsia-boot:///#meta/hid.cm
    fuchsia-boot:///#meta/netdevice-migration.cm
    fuchsia-boot:///#meta/network-device.cm
    fuchsia-boot:///#meta/platform-bus-x86.cm
    fuchsia-boot:///#meta/platform-bus.cm
    fuchsia-boot:///#meta/ramdisk.cm
    fuchsia-boot:///#meta/sysmem.cm
    fuchsia-boot:///#meta/virtio_block.cm
    fuchsia-boot:///#meta/virtio_ethernet.cm
    fuchsia-pkg://fuchsia.com/virtual_audio#meta/virtual_audio_driver.cm

Driver Host: 7774
    fuchsia-boot:///#meta/intel-rtc.cm

Driver Host: 7855
    fuchsia-boot:///#meta/pc-ps2.cm

Driver Host: 44887 
    fuchsia-pkg://bazel.pkg.component/qemu_edu#meta/qemu_edu.cm 

记下 qemu_edu 驱动程序主机的 PID。在上述示例中,PID 为 44887。

使用 ffx debug connect 启动 Fuchsia 调试程序:

ffx debug connect

调试程序连接到目标设备后,从 zxdb 提示符连接到 qemu_edu 驱动程序主机:

[zxdb] attach HOST_PID

HOST_PID 替换为上一步中标识的驱动程序主机的 PID。例如:

[zxdb] attach 44887

在驱动程序的 ComputeFactorial 函数中设置断点:

[zxdb] break QemuEduServer::ComputeFactorial

该命令会输出类似于以下内容的输出,以指明断点的设置位置:

[zxdb] break QemuEduServer::ComputeFactorial
Created Breakpoint 1 @ QemuEduServer::ComputeFactorial
   47 void QemuEduServer::ComputeFactorial(ComputeFactorialRequestView request,
 ◉ 48                                      ComputeFactorialCompleter::Sync& completer) {
   49   auto edu_device = device_.lock();

逐步了解驱动程序函数

在一个单独的终端中,再次运行 eductl 工具:

bazel run //fuchsia-codelab/qemu_edu/tools:pkg.eductl_tool -- fact 12

zxdb 终端中,验证调试程序已命中驱动程序的 ComputeFactorial 函数中的断点。例如:

🛑 thread 2 on bp 1 qemu_edu::QemuEduServer::ComputeFactorial(qemu_edu::QemuEduServer*, fidl::WireServer<fuchsia_examples_qemuedu::Device>::ComputeFactorialRequestView, fidl::Completer<fidl::internal::WireCompleterBase<fuchsia_examples_qemuedu::Device::ComputeFactorial> >::Sync&) • qemu_edu.cc:144
   46 // Driver Service: Compute factorial on the edu device
   47 void QemuEduServer::ComputeFactorial(ComputeFactorialRequestView request,
 ▶ 48                                      ComputeFactorialCompleter::Sync& completer) {
   49   auto edu_device = device_.lock();
   50   if (!edu_device) {

zxdb 提示符处使用 list 命令,以显示当前暂停执行的位置:

[zxdb] list

该命令会输出类似于以下内容的输出:

   46 // Driver Service: Compute factorial on the edu device
   47 void QemuEduServer::ComputeFactorial(ComputeFactorialRequestView request,
 ▶ 48                                      ComputeFactorialCompleter::Sync& completer) {
   49   auto edu_device = device_.lock();
   50   if (!edu_device) {
   51     FDF_LOG(ERROR, "Unable to access device resources.");
   52     completer.ReplyError(ZX_ERR_BAD_STATE);
   53     return;
   54   }
   55
   56   uint32_t input = request->input;
   57
   58   edu_device->ComputeFactorial(input);

使用 next 命令进入 ComputeFactorial 函数:

[zxdb] next

输出传入该函数的请求的内容:

[zxdb] print request

该命令会输出包含阶乘输入值的输出:

(*)0x747c1f2e98 ➔ {input = 12}

退出调试程序会话并断开连接:

[zxdb] exit

创建新的系统测试组件

在本部分中,您将创建一个新的测试组件,用于执行 qemu_edu 驱动程序的公开函数。

在 Bazel 工作区中为新的测试组件创建新的项目目录:

mkdir -p fuchsia-codelab/qemu_edu/tests

完成本部分后,项目应具有以下目录结构:

//fuchsia-codelab/qemu_edu/tests
                  |- BUILD.bazel
                  |- meta
                  |   |- qemu_edu_system_test.cml
                  |- qemu_edu_system_test.cc

创建 qemu_edu/tests/BUILD.bazel 文件并添加以下语句,以包含 Fuchsia SDK 中必要的构建规则:

qemu_edu/tests/BUILD.bazel

load(
    "@fuchsia_sdk//fuchsia:defs.bzl",
    "fuchsia_cc_test",
    "fuchsia_component_manifest",
    "fuchsia_test_component",
    "fuchsia_test_package",
    "if_fuchsia",
)

使用以下内容在项目中创建一个新的 qemu_edu/tests/meta/qemu_edu_system_test.cml 组件清单文件:

qemu_edu/tests/meta/qemu_edu_system_test.cml

{
    include: [
        "syslog/client.shard.cml",
        "sys/testing/elf_test_runner.shard.cml",
    ],
    program: {
        binary: 'bin/qemu_edu_system_test',
    },
    use: [
        {
            directory: "dev-topological",
            rights: [ "r*" ],
            path: "/dev",
        },
    ],
    // Required to enable access to devfs
    facets: {
        "fuchsia.test": { type: "devices" },
    },
}

eductl 类似,测试组件使用 dev 目录功能发现和访问驱动程序。此组件还包含 elf_test_runner.shard.cml,支持使用测试运行程序框架运行。

创建包含以下内容的新 qemu_edu/tests/qemu_edu_system_test.cc 文件以实现这些测试:

qemu_edu/tests/qemu_edu_system_test.cc

#include <dirent.h>
#include <fcntl.h>
#include <fidl/examples.qemuedu/cpp/wire.h>
#include <lib/fdio/directory.h>
#include <sys/types.h>

#include <filesystem>

#include <gtest/gtest.h>

namespace {

constexpr char kDevfsRootPath[] = "/dev/sys/platform/";
constexpr char kEduDevicePath[] = "qemu-edu";

class QemuEduSystemTest : public testing::Test {
 public:
  void SetUp() {
    auto device_path = SearchDevicePath();
    ASSERT_TRUE(device_path.has_value());
    zx::result endpoints = fidl::CreateEndpoints<examples_qemuedu::Device>();
    ASSERT_EQ(endpoints.status_value(), ZX_OK);
    ASSERT_EQ(fdio_service_connect(device_path.value().c_str(),
                                   endpoints->server.TakeChannel().release()),
              ZX_OK);
    device_ = fidl::WireSyncClient(std::move(endpoints->client));
  }

  // Search for the device file entry in devfs
  std::optional<std::string> SearchDevicePath() {
    for (auto const& dir_entry : std::filesystem::recursive_directory_iterator(kDevfsRootPath)) {
      if (dir_entry.path().string().find(kEduDevicePath) != std::string::npos) {
        return {dir_entry.path()};
      }
    }

    return {};
  }

  fidl::WireSyncClient<examples_qemuedu::Device>& device() { return device_; }

 private:
  fidl::WireSyncClient<examples_qemuedu::Device> device_;
};

TEST_F(QemuEduSystemTest, LivenessCheck) {
  fidl::WireResult result = device()->LivenessCheck();
  ASSERT_EQ(result.status(), ZX_OK);
  ASSERT_TRUE(result->value()->result);
}

TEST_F(QemuEduSystemTest, ComputeFactorial) {
  std::array<uint32_t, 11> kExpected = {
      1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800,
  };
  for (uint32_t i = 0; i < kExpected.size(); i++) {
    fidl::WireResult result = device()->ComputeFactorial(i);
    ASSERT_EQ(result.status(), ZX_OK);
    EXPECT_EQ(result->value()->output, kExpected[i]);
  }
}

}  // namespace

每个测试用例都会打开设备驱动程序,并执行某个公开的函数。

将以下新规则添加到项目的 build 配置中,以将测试组件构建到 Fuchsia 测试软件包中:

qemu_edu/tests/BUILD.bazel

fuchsia_cc_test(
    name = "qemu_edu_system_test",
    size = "small",
    srcs = [
        "qemu_edu_system_test.cc",
    ],
    deps = ["@com_google_googletest//:gtest_main"] + if_fuchsia([
        "//fuchsia-codelab/qemu_edu/fidl:examples.qemuedu_cc",
        "@fuchsia_sdk//pkg/fdio",
    ]),
)

fuchsia_component_manifest(
    name = "manifest",
    src = "meta/qemu_edu_system_test.cml",
    includes = [
        "@fuchsia_sdk//pkg/sys/testing:elf_test_runner",
        "@fuchsia_sdk//pkg/syslog:client",
    ],
)

fuchsia_test_component(
    name = "component",
    manifest = "manifest",
    deps = [
        ":qemu_edu_system_test",
    ],
)

fuchsia_test_package(
    name = "pkg",
    package_name = "qemu_edu_system_test",
    test_components = [
        ":component",
    ],
    visibility = ["//visibility:public"],
)

运行系统测试

使用 bazel run 命令构建并执行测试组件目标:

bazel run //fuchsia-codelab/qemu_edu/tests:pkg.component

bazel run 命令会执行以下步骤:

  1. 构建组件和软件包。
  2. 将软件包发布到本地软件包代码库。
  3. 向目标设备注册软件包代码库。
  4. 使用 ffx test run 执行组件的测试套件。

验证是否成功通过所有测试:

Running test 'fuchsia-pkg://bazel.test.pkg.system.test.component/qemu_edu_system_test#meta/qemu_edu_system_test.cm'
[RUNNING]   main
[stdout - main]
Running main() from gmock_main.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from QemuEduSystemTest
[ RUN      ] QemuEduSystemTest.LivenessCheck
[       OK ] QemuEduSystemTest.LivenessCheck (4 ms)
[ RUN      ] QemuEduSystemTest.ComputeFactorial
[       OK ] QemuEduSystemTest.ComputeFactorial (4 ms)
[----------] 2 tests from QemuEduSystemTest (9 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (9 ms total)
[  PASSED  ] 2 tests.
[PASSED]    main

后续操作

恭喜!您已成功调试并将测试添加到 Fuchsia 驱动程序。

至此,您已经体验了在 Fuchsia 上开发驱动程序的基础知识,不妨学习以下知识,进一步了解相关信息:

驱动程序概念