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