更新 VMO 文件格式

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

在扩展格式时,您必须确保不破坏任何现有功能,尤其是检查读取器和验证测试。不过,将所有更改打包到单个更改中可能会难以审核。一般来说,更改 VMO 格式的链中应有 5-9 个更改或步骤:

  1. (可能不适用)通过更新 VMO 格式文档来选择类型编号
  2. 更新 Rust 读取器。
  3. 更新 C++ 读取器。
  4. 更新 Rust 写入器。
  5. 更新 C++ 写入器。
  6. (可能)更新验证器。
  7. 更新文档。
  8. (不是更改)(可选)发送功能公告。

选择类型编号

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

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

实现

如果没有另一个读取器或写入器,则测试读取器或写入器会很麻烦;如果没有读取器和写入器,则很难获得适当的块级 API。

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

  1. 选择一种语言,并完全使用该语言设计和实现该功能,使用单元测试来模拟 API 的实际使用情况。

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

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

  3. 重新基于该更改,并使用较低级别的功能重写测试(保留原始版本的测试)。

    通常,您可以将对块代码的所有更改都放在读取器更改中,这样就可以为读取器 API 编写测试,但会很混乱。

  4. 测试会很糟糕。重新基于写入器更改,并移除修改后的测试,将其替换为使用高级 API 编写的原始测试。

  5. 使用这两个更改作为参考,并在第二种语言中复制它们。

  6. (可选)根据更改的内容,您可能需要随时更新 验证器测试,因为对现有 块的现有格式的更改可能会破坏它们。

以下部分概述了如何将每个更改放在一起,但在实践中,请将这些内容作为提示,以便在按上述方式拆分设计之前,以一致的方式设计整个系统。

更新 Rust 实现

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

设置

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

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

读取器更改

位字段更新

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

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

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

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

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

  2. 编写执行新功能的块测试。

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

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

更新读取器

您可以在 mod.rs 中找到读取器代码。 此更改中的测试可能会很棘手,因为高级 API 写入器尚不存在。

写入器更改

状态

此处的主要更改将位于 the State functionality 中。 在此处,块可以分配并转换为新类型。 如果您只是修改现有块,则这可能是您需要进行更改的唯一位置。

创建新的值类型

有一个 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

读取器更改

位字段更新

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

更新块定义

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

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

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

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

实现类型读取器

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

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

值(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 是 Insert 的实参。

定制

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

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

状态操作更新

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

  1. 更新 State 标头

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

    2. 为需要在类型上支持的每个操作添加方法。例如,如果您的类型 可以设置为 void SetMyFoo(MyFoo* property, T),其中 T 是类型 更新中的相同类型。

  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. 文档更新