本文档介绍了如何更新或扩展组件检查文件 格式。
在扩展格式时,您必须确保不破坏任何现有功能,尤其是检查读取器和验证测试。不过,将所有更改打包到单个更改中可能会难以审核。一般来说,更改 VMO 格式的链中应有 5-9 个更改或步骤:
- (可能不适用)通过更新 VMO 格式文档来选择类型编号。
- 更新 Rust 读取器。
- 更新 C++ 读取器。
- 更新 Rust 写入器。
- 更新 C++ 写入器。
- (可能)更新验证器。
- 更新文档。
- (不是更改)(可选)发送功能公告。
选择类型编号
查看检查文件格式中的类型表,然后选择 可用的类型编号。在当前规范中,共有 256 种可能的类型。
如需预留新类型,请更新检查文件格式。
实现
如果没有另一个读取器或写入器,则测试读取器或写入器会很麻烦;如果没有读取器和写入器,则很难获得适当的块级 API。
如需测试读取器和写入器,请执行以下操作:
选择一种语言,并完全使用该语言设计和实现该功能,使用单元测试来模拟 API 的实际使用情况。
将更改拆分为单独的读取器和写入器更改,并将写入器堆叠在读取器之上。
此时,读取器中依赖于写入器 API 的测试可能会在读取器更改中中断。
重新基于该更改,并使用较低级别的功能重写测试(保留原始版本的测试)。
通常,您可以将对块代码的所有更改都放在读取器更改中,这样就可以为读取器 API 编写测试,但会很混乱。
测试会很糟糕。重新基于写入器更改,并移除修改后的测试,将其替换为使用高级 API 编写的原始测试。
使用这两个更改作为参考,并在第二种语言中复制它们。
(可选)根据更改的内容,您可能需要随时更新 验证器测试,因为对现有 块的现有格式的更改可能会破坏它们。
以下部分概述了如何将每个更改放在一起,但在实践中,请将这些内容作为提示,以便在按上述方式拆分设计之前,以一致的方式设计整个系统。
更新 Rust 实现
本部分中的示例会创建一个名为 MyFoo 的新类型。
设置
- 包含测试:
fx set core.x64 --with //src/lib/diagnostics:tests
运行测试:
fx test inspect-format-tests fuchsia-inspect-tests
读取器更改
位字段更新
如果定义新的块类型,请更新 the
BlockType枚举。更新为
BlockType定义的方法和函数。如果更改现有块中的字段或创建新的
BlockType,请更新 位字段布局。运行
inspect-format-tests以验证更改是否编译:
fx test inspect-format-tests
更新块定义,以包含用于读取和写入新 字段的方法。此时在块库中包含写入功能是合适的;如果没有写入功能,则几乎不可能编写读取器测试。
编写执行新功能的块测试。
编写对块的前 8-16 个字节进行断言的测试。
通常,这意味着将预期内容编写为包含十六进制值的
&[u8],并断言其与块用作容器的缓冲区等效。
更新读取器
您可以在 mod.rs 中找到读取器代码。 此更改中的测试可能会很棘手,因为高级 API 写入器尚不存在。
写入器更改
状态
此处的主要更改将位于 the State functionality 中。
在此处,块可以分配并转换为新类型。
如果您只是修改现有块,则这可能是您需要进行更改的唯一位置。
创建新的值类型
有一个 types 目录,您可以在其中为您的类型添加新文件。
在创建的文件中创建新类型。使用现有类型作为示例。类型始终有权访问内部
State。使用此方法在您的新类型上创建必要的 方法,调用在State中创建的方法。向
Node添加一个用于创建新类型的方法。确保您的类型在 VMO 中具有 RAII 语义。如果您的类型是值,则可能由在第 1 步中从现有类型复制的样板自动完成。
最后,返回并更新读取器更改中的测试以使用新的 API!
更新 C++ 实现
本部分中的示例会创建一个名为 MyFoo 的新类型。
如上所述,本部分在 Gerrit 中应有两个更改。
设置
- 包含测试:
fx set core.x64 --with //zircon/system/ulib/inspect:tests
- 运行测试。
fx test inspect-cpp-unittest
读取器更改
位字段更新
本部分介绍了如何为新类型定义位字段。
更新块定义。
更改
BlockType以包含您的新类型。例如:kMyFoo = #;如果您的类型需要新标头(通常如果它不是
VALUE),请使用结构体为您的类型定义标头位字段。例如:struct MyFooBlockFields final : public BlockFields。如果您的类型需要新载荷(它需要使用块的第二个 8 字节),请使用结构体为您的类型定义载荷位字段。例如:
struct MyFooBlockPayload final。如果您的类型包含枚举(例如格式),请在 block.h 的顶部定义新的枚举。例如:
enum class MyFooBlockFormat : uint8_t。
实现类型读取器
本部分介绍了如何使您的新类型可读。
根据您的类型更新检查层次结构:
值(Node 的子项)
使用新编号更新
PropertyFormat枚举。这必须在此特定枚举中是连续的,并且不需要与您选择的格式类型序号匹配。创建新的值类型。例如,
using MyFooValue = internal::Value<T, static_cast<size_t>(PropertyFormat::kMyFoo)>;使用新值更新
PropertyValue变体。 注意:fit::internal::variant中的索引必须与PropertyFormat的值匹配。
不是值
- 您需要在 层次结构文件中创建自己的内存中表示对象。
更新
InnerScanBlocks以调度您的类型。如果您要创建新的Property,则可能只需要添加BlockType。如果您需要自定义解析器,请实现
InnerParseMyFoo,它会接受父项(如果需要)和指向扫描块的指针。
写入器更改
类型封装容器声明
本部分介绍了如何为新类型声明 C++ RAII 样式的封装容器。
类型封装容器包含类型拥有的块的索引。您负责 在 状态操作更新中实现对这些块的操作,包括创建和删除。
更新写入器类型定义。
确定您是否可以重复使用现有封装容器,或者是否需要定制类型:
重复使用
如果您需要支持 Add、Subtract 和 Set:
using MyFoo = internal::NumericProperty<T>, 其中T是这些操作的实参类型。如果您需要支持 Set:
using MyFoo = internal::Property<T>,其中T是 Set 的实参 类型。如果您需要支持对数组执行数值运算:
using MyFood = internal::ArrayProperty<T>,其中T是数组中槽的实参类型。如果您需要支持插入到直方图:
using MyFoo = internal::{Linear,Exponential}Histogram<T>,其中T是 Insert 的实参。
定制
创建新的类型封装容器。例如
class MyFoo final。确保您的类将
internal::State作为友元类。注意:如需了解可复制的起点,请参阅class Link。
状态操作更新
State 类是所有类型的所有操作的实际实现。本部分介绍了如何实现完成封装容器实现所需的操作。
更新
State标头:添加 Create 和 Free 方法。例如:
MyFoo CreateMyFoo(<args>); void FreeMyFoo(MyFoo* property);,其中args通常 包括名称、父项和一些初始值。为需要在类型上支持的每个操作添加方法。例如,如果您的类型 可以设置为
void SetMyFoo(MyFoo* property, T),其中T是类型 更新中的相同类型。
更新
State:实现新类型的方法。不同类型的实现各不相同。 本部分简要概述了每种方法必须执行的操作:
MyFoo CreateMyFoo(Args...)负责分配多个块、设置 其值,并将其封装在MyFoo中返回。您可以使用私有构造函数根据BlockIndex对象创建MyFoo。存在各种内部辅助程序来简化此操作。如需查看示例,请参阅CreateIntProperty。void FreeMyFoo(MyFoo* property)负责释放MyFoo封装的所有块。有时,释放块需要特定的排序要求或更新。如需查看如何释放值的示例,请参阅InnerFreeValue。操作(例如
void SetMyFoo(MyFoo* property, T value))会更改分配给MyFoo的块 的值以实现该操作。如需查看示例,请参阅SetIntProperty。
实现类型封装容器
本部分介绍了如何实现之前声明的封装容器方法。
更新写入器类型定义:
如果您使用了现有的模板化类型,则需要为新的基本类型
T替换每种方法。例如,如果您输入了using MyFoo = internal::Property<T>,则您将编写:template<> void internal::Property<T>::OPERATION(...) { ... }如果您创建了自己的类型,只需为您声明的方法创建定义即可。您需要执行以下操作:
让您的构造函数调用
state_->CreateMyFoo(...);让您的析构函数调用
state_->FreeMyFoo(...);让您的其他方法调用 State 上的相应实现。
让您的所有构造函数和方法在调用之前检查
state_是否为 null。