更新 VMO 文件格式

本文档介绍了如何更新或扩展组件检查文件格式

在扩展格式时,切勿破坏任何现有功能,尤其是检查读取器和验证测试。但是,将所有更改打包到一项更改中可能会使审核工作量过大。通常,如果要更改 VMO 格式,应在链中包含 5-9 个更改或步骤:

  1. (可能不适用)通过更新 VMO 格式文档来选择类型编号
  2. 更新 Rust 读取器
  3. 更新 C++ 读取器
  4. 更新 Rust 编写器。
  5. 更新 C++ 编写器
  6. (可能)更新验证程序。
  7. 更新文档。
  8. (没有变化)(可选)发送功能通知。

选择数字类型

检查文件格式中查看类型表,并选择可用的类型编号。当前规范中总共有 256 种可能的类型。

如需预留新类型,请更新检查文件格式

实现

在没有读取器或写入器的情况下测试读取器或写入器非常麻烦;如果没有读取器和写入器,则很难获得适当的块级 API。

如需测试读取器和写入器,请执行以下操作:

  1. 选择一种语言,设计并完全用该语言设计并实现功能,同时通过单元测试对 API 的实际使用情况进行建模。

  2. 将更改拆分为单独的读取器和写入器更改,并将写入器堆叠在读取器上。

    此时,读取器中依赖于 writer API 的测试可能会在读取器更改中中断。

  3. 根据该变更进行 rebase 操作,并使用较低级别的功能重写测试(保留原始版本的测试)。

    通常,您可以将对块代码的所有更改都放入读取器更改中,这样一来,为读取器 API 编写测试就可能又麻烦,

  4. 考试太难了。衍合到写入者变更并移除修改后的测试,将它们替换为根据高级 API 编写的原始测试。

  5. 参考这两个更改,并将其复制到第二种语言中。

  6. (可选)根据更改的内容,您可能需要随时更新validator测试,因为更改现有代码块的现有格式可能会破坏这些测试。

下面几部分概述了如何整合各项更改,但实际上,请在按上述方法拆分设计之前,将这些更改作为提示来统一设计整个系统。

更新 Rust 实现

本部分中的示例将创建一个名为 MyFoo 的新类型。

设置

  1. 包含测试:
fx set core.x64 --with //src/lib/diagnostics:tests
  1. 运行测试:

fx test inspect-format-tests fuchsia-inspect-tests

读取器更改

Bitfield 更新

  1. 如果要定义新的块类型,请更新 BlockType 枚举

  2. 更新为 BlockType 定义的方法和函数。

  3. 如果更改现有块中的字段或创建新的 BlockType,请更新位字段布局

  4. 运行 inspect-format-tests 以验证更改是否编译:

fx test inspect-format-tests
  1. 更新块定义,以包含用于读取和写入新字段的方法。此时,在块库中添加写入功能是恰当的做法;如果没有该功能,几乎不可能编写读取器测试。

  2. 编写用于运用新功能的块测试。

  3. 编写对块的前 8-16 个字节进行断言的测试。

    通常,这意味着将预期内容编写为包含十六进制值的 &[u8],并声明其与块用作容器的缓冲区的等效性。

更新读取器

您可以在 mod.rs 中找到读者代码。此变更中的测试可能会比较棘手,因为高级 API 编写程序尚不存在。

写入者变更

状态

此处的主要更改将发生在 State 功能中。您可以在此处分配块并将其转换为新类型。如果您只是修改现有块,则可能是您唯一需要更改的地方。

创建新的值类型

利用 types 目录,您可以为自己的类型添加新文件。

  1. 在所创建的文件中创建新类型。使用现有类型作为示例。类型始终可以访问内部 State。使用此方法可为新类型创建必要的方法,并调用在 State 中创建的方法。

  2. Node 添加用于创建新类型的方法。

  3. 确保您的类型在 VMO 中具有 RAII 语义。如果您的类型是一个值,则系统可能会通过在第 1 步中从现有类型复制的样板自动完成此操作。

最后,返回并更新读取器更改中的测试,以使用新的 API!

更新 C++ 实现

本部分中的示例将创建一个名为 MyFoo 的新类型。

如上所述,此部分应该是 Gerrit 中的两项更改。

设置

  1. 包含测试:
fx set core.x64 --with //zircon/system/ulib/inspect:tests
  1. 运行测试。
fx test inspect-cpp-unittest

读取器更改

Bitfield 更新

本部分介绍了如何为新类型定义位字段。

更新块定义

  1. 更改 BlockType 以包含您的新类型。例如:kMyFoo = #;

  2. 如果您的类型需要新标头(通常如果它不是 VALUE),请使用结构体定义该类型的标头位字段。例如:struct MyFooBlockFields final : public BlockFields

  3. 如果您的类型需要新的载荷(需要使用分块的后 8 个字节),请使用结构体为您的类型定义载荷位字段。例如:struct MyFooBlockPayload final

  4. 如果您的类型包含枚举(例如格式),请在 block.h 的顶部定义一个新枚举,例如:enum class MyFooBlockFormat : uint8_t

实现类型读取器

本部分介绍如何使新类型可读。

根据您的类型更新 Inspect 层次结构

值(Node 的子项)

  1. 使用类型的新数字更新 PropertyFormat 枚举。此枚举在此特定枚举中必须依序排列,无需与您选择的格式类型序数匹配。

  2. 创建一个新的值类型。例如 using MyFooValue = internal::Value<T, static_cast<size_t>(PropertyFormat::kMyFoo)>;

  3. 使用新值更新 PropertyValue 变体。 注意:fit::internal::variant 中的索引必须与 PropertyFormat 的值匹配。

不是值

  1. 您需要在层次结构文件中创建自己的内存中表示形式对象。
  1. 更新实际读取器

  2. 更新 InnerScanBlocks 以分派类型。如果要创建新的 Property,可能只需添加 BlockType

  3. 如果您需要自定义解析器,请实现 InnerParseMyFoo,它会接受父级(如果需要)和指向已扫描块的指针。

写入者变更

类型封装容器声明

本部分介绍了如何为新类型声明 C++ RAII 样式的封装容器。

类型封装容器包含相应类型拥有的块索引。您负责在状态操作更新中针对这些块执行操作,包括创建和删除操作。

更新写入者类型定义

确定您可以重复使用现有封装容器,还是需要定制类型:

重复使用

  1. 如果您需要支持“Add”(添加)、“Subtract”(减法)和“Set”(设置),请执行以下操作:using MyFoo = internal::NumericProperty<T>,其中 T 是这些操作的参数类型。

  2. 如果您需要支持 Set: using MyFoo = internal::Property<T>,其中 T 是 Set 的参数类型。

  3. 如果您需要支持对数组执行数字操作,请使用 using MyFood = internal::ArrayProperty<T>,其中 T 是数组中槽的参数类型。

  4. 如果您需要支持向直方图插入内容:using MyFoo = internal::{Linear,Exponential}Histogram<T>,其中 T 是插入的参数。

定制

  1. 创建一个新的类型封装容器。例如 class MyFoo final

  2. 确保您的类将 internal::State 作为好友类。注意:如需了解可复制的起点,请参阅 class Link

状态操作更新

State 类是针对所有类型的所有操作的实际实现。本部分介绍如何实现完成封装容器实现所需的操作。

  1. 更新 State 标头

    1. 添加了 Create 和 Free 方法。例如:MyFoo CreateMyFoo(<args>); void FreeMyFoo(MyFoo* property);,其中 args 通常包括名称、父级和一些初始值。

    2. 为您需要支持的每种操作添加方法。例如,如果您的类型可以是 Set,则 void SetMyFoo(MyFoo* property, T),其中 T 是对 types.h 的更新中的类型。

  2. 更新 State

    1. 实现新类型的方法。实现方式因类型而异。本部分简要介绍了每种方法必须执行的操作:

      • MyFoo CreateMyFoo(Args...) 负责分配一些块、设置它们的值,并返回封装在 MyFoo 中的块。您可以使用私有构造函数根据其封装的 BlockIndex 对象创建 MyFoo。各种内部帮助程序可以简化此操作。如需查看示例,请参阅 CreateIntProperty

      • void FreeMyFoo(MyFoo* property) 负责释放由 MyFoo 封装的所有块。有时,释放块会有一些特定的排序要求或更新。如需查看有关如何释放值的示例,请参阅 InnerFreeValue

      • void SetMyFoo(MyFoo* property, T value) 等操作会更改分配给 MyFoo 的块的值,以实现相应操作。如需查看示例,请参阅 SetIntProperty

实现类型封装容器

本部分介绍了如何实现之前声明的封装容器方法。

  1. 更新写入者类型定义

    • 如果您使用现有的模板化类型,则需要为新基本类型 T 替换每个方法。例如,如果您输入 using MyFoo = internal::Property<T>,则需要输入:template<> void internal::Property<T>::OPERATION(...) { ... }

    • 如果您创建了自己的类型,只需为您声明的方法创建定义即可。您需要执行以下操作:

      • 让您的构造函数调用 state_->CreateMyFoo(...);

      • 让析构函数调用 state_->FreeMyFoo(...);

      • 让其他方法调用 State 上的相应实现。

      • 让您的所有构造函数和方法在调用之前检查 state_ 是否不为 null。

实现测试

  1. 使用针对低级别操作的测试更新状态单元测试

  2. 使用高级读取器实现的测试更新了读取器单元测试

变更链示例

  1. C++ 读取器
  2. Rust 读取器
  3. Rust 写入者
  4. Rust 验证器变更
  5. C++ 验证器变更
  6. 文档更新