FlatBuffers 注释二进制(Annotated Binary)与无效二进制测试全解析
【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers
本篇技术指南围绕 FlatBuffers 仓库中tests/annotated_binary/tests/README.md所记录的无效二进制测试集合展开,系统讲解flatc --annotate如何把二进制 FlatBuffer 逐字节注释为可读文本,并深入分析 22 个手工构造的损坏二进制用例——它们分别篡改根表偏移、vtable 尺寸、字段偏移、字符串/向量长度、union 类型值或直接截断文件,以验证注释工具能够精准标记出这些损坏点。读完本文,你将掌握 Annotated Binary 文本格式的五列结构、偏移量(UOffset32/SOffset32/VOffset16)的解读方法,以及如何利用这些测试用例排查二进制数据损坏。
一、Annotated Binary 是什么
Annotated Flatbuffer Binary(.afb)是 FlatBuffers 提供的一种二进制逐字节注释能力:flatc读取一个 schema(.fbs文本 schema 或.bfbs二进制 schema)与按该 schema 序列化出的二进制文件,输出包含全部二进制数据与逐行注释的.afb文本文件。仓库中的官方说明见 docs/source/annotation.md,其核心实现位于 src/annotated_binary_text_gen.cpp 与 src/binary_annotator.h。
注释不仅服务于正常数据的可读化,更重要的用途是诊断损坏的二进制:当一个偏移或长度字段被改成一个非法值(指向缓冲区之外、小于最小合法尺寸等)时,注释输出会在对应行上标记ERROR: ... Invalid offset, points outside the binary.之类的诊断信息,这正是tests/annotated_binary/tests/目录下整套无效二进制测试的验证目标。
1.1 基本用法
flatc --annotate {schema_file} -- {binary_file}...schema可以是纯文本.fbs,也可以是已经编译好的.bfbs;binary是依据该 schema 生成的 FlatBuffer 二进制。命令会为每个输入二进制生成一个.afb文件。
仓库自带的示例(tests/annotated_binary/README.md):
cd tests/annotated_binary ../../flatc --annotate annotated_binary.fbs -- annotated_binary.bin该命令在当前目录生成annotated_binary.afb。其中annotated_binary.bin是annotated_binary.json中的数据经如下命令序列化而来:
../../flatc -b annotated_binary.fbs annotated_binary.json与之配套的 schema 位于 tests/annotated_binary/annotated_binary.fbs,它定义了一个非常全面的演示模型:包含标量(int、long、bool、float、double)、枚举(Food)、多个结构体(Tolerance、Dimension、Building、Location)、多个表(Baz、Bar、Foo)、三种 union(BarBaz、Measurement、Any)、标量向量、字符串向量、表向量、结构体向量、union 向量、可空标量(maybe_i32: int32 = null)、默认值字段以及file_identifier "ANNO"与root_type Foo。这份 schema 几乎覆盖了 FlatBuffers 的所有数据形态,使注释输出能展示每种形态的二进制布局。
flatc还支持一个相关选项--annotate-sparse-vectors,用于不逐元素注释向量内容(见 src/flatc.cpp 的选项注册,对应解析在 src/flatc.cpp),在处理超长向量时可显著缩减输出。
二、.afb 文本格式详解
annotated_binary.afb是文本格式的权威示例。整个文件按偏移量从小到大排列,每一行的第一列即该区域相对缓冲区起点的十六进制偏移(如+0x003C)。以 tests/annotated_binary/annotated_binary.afb 中实际的一行为例:
vtable (AnnotatedBinary.Bar): +0x00B8 | 08 00 | uint16_t | 0x0008 (8) | size of this vtable共五列:
- 偏移:
+0x00B8,该区域距缓冲区起点的十六进制字节偏移。 - 原始字节:
08 00,按 FlatBuffers 线格式使用的小端序十六进制字节。 - 类型:解释这些字节的类型。既有 FlatBuffers 内部专用类型(如
UOffset32、SOffset32、VOffset16、UType8),也有 C++ 风格定宽标量(如uint16_t、uint32_t、double、char[4])。 - 值:将原始字节按紧凑的大端序重新呈现(如
0x0008),便于阅读,随后是十进制值(如(8));字符串区域则直接显示原始字符串内容。当类型为绝对偏移量(SOffset32或UOffset32)时,还会追加一个Loc: +0xXXXX,指明该偏移实际指向的缓冲区位置。 - 注释:关于该值的文本说明,尽量携带已知元数据,例如字段名、字段 id、所属类型、默认值提示等。
2.1 Binary Section 与 Binary Region
.afb 文件在结构上分为两层:
- Binary Section(二进制节):逻辑上聚合成组的连续字节区域,例如一个
Table实例或它的vtable。节可以附带 schema 中定义的关联类型名,如vtable (AnnotatedBinary.Bar):、root_table (AnnotatedBinary.Foo):、string (AnnotatedBinary.Foo.charlie):、vector (AnnotatedBinary.Foo.names):、union (AnnotatedBinary.Tolerance.measurement):、padding:、header:等。节类型在 src/annotated_binary_text_gen.cpp 中通过BinarySectionType枚举映射为header、table、root_table、vtable、struct、string、vector、vector64、union、padding、unknown等标签。 - Binary Region(二进制区域):组成某个值(标量或标量数组)的连续字节段;若区域过大,可跨多行输出。
2.2 偏移量的解读
注释中出现的三类核心偏移(对应实现见 src/annotated_binary_text_gen.cpp 的IsOffset判断):
| 类型 | 宽度 | 含义 | 定位方式 |
|---|---|---|---|
UOffset32 | 4 字节 | 无符号偏移,通常指向缓冲区中更靠前的位置 | 字段所在位置减去该值 |
SOffset32 | 4 字节 | 有符号偏移,可为负(例如表内指向 vtable 的偏移) | 当前地址加上(带符号)该值 |
VOffset16 | 2 字节 | vtable 内的字段偏移 | 相对所属 table 起始位置 |
例如在 annotated_binary.afb 中,根表第一条目:
root_table (AnnotatedBinary.Foo): +0x0044 | 3A 00 00 00 | SOffset32 | 0x0000003A (58) Loc: 0x000A | offset to vtable0x0044 + 0x3A = 0x007E,但注释显示Loc: 0x000A,说明SOffset32的定位规则是按符号偏移回退(0x0044 - 0x3A = 0x000A),与 vtable 实际所在位置一致。而字符串引用则相反:
+0x0074 | C8 01 00 00 | UOffset32 | 0x000001C8 (456) Loc: 0x023C | offset to field `name` (string)0x023C - 0x1C8 = 0x74,即UOffset32从自身字段位置向前回溯定位目标。两种偏移的精确计算公式在 tests/annotated_binary/README.md 中说明为"取决于上下文"——这正是注释工具依据二进制区域类型分别计算Loc的原因。
三、无效二进制测试:验证损坏可被识别
3.1 测试思路
tests/annotated_binary/tests/README.md的核心内容是:基于annotated_binary.bin手工构造一系列损坏二进制,每个文件只修改某个偏移或长度/尺寸条目,使其指向非法位置(超出缓冲区、小于最小合法尺寸、截断在字段中间等),然后用flatc --annotate生成对应的.afb,通过检查注释输出中的ERROR/WARN标记证明这些损坏可以被注释工具发现。
测试文件分三组存放于 tests/annotated_binary/tests/:
- 22 个
invalid_*.bin:手工损坏的二进制输入; - 22 个
invalid_*.afb:对每个损坏二进制运行注释工具后的输出,其中包含错误标记。
3.2 运行命令
原文档给出的运行方式(从仓库根目录出发):
cd tests/annotated_binary ../../flatc --annotate annotated_binary.fbs tests/invalid_root_offset.bin ...即用annotated_binary.fbs作为 schema,一次性对多个tests/目录下的损坏二进制执行注释。批量执行时可借助仓库自带的自动化脚本 tests/annotated_binary/generate_annotations.py:脚本定位仓库根目录与flatc可执行文件(Windows 下为flatc.exe),遍历test_files列表中的 22 个tests/invalid_*.bin以及正常文件annotated_binary.bin,逐一调用flatc --annotate生成.afb。该脚本是整套测试可重复运行的关键:任何对 schema 或注释逻辑的改动,都可以通过重跑脚本快速重建全部.afb基准文件。
3.3 22 个损坏用例逐项解析
下表完整列出原文档记录的每一个损坏二进制及其损坏手法:
| 二进制文件 | 损坏方式 |
|---|---|
invalid_root_offset.bin | 将前两个字节从4400改为FFFF,产生一个大于二进制文件长度的根表偏移 |
invalid_root_table_vtable_offset.bin | 将0x0044处两个字节从3A00改为FFFF,指向二进制文件之外 |
invalid_root_table_too_short.bin | 将文件截断到0x46字节,切入了根表的 vtable 偏移字段 |
invalid_vtable_size.bin | 将0x000A处两个字节从3A00改为FFFF,vtable 尺寸大于文件长度 |
invalid_vtable_size_short.bin | 将0x000A处两个字节从3A00改为0100,vtable 尺寸小于最小合法值 4 字节 |
invalid_vtable_ref_table_size.bin | 将0x000C处两个字节从6800改为FFFF,所引用表尺寸大于文件长度 |
invalid_vtable_ref_table_size_short.bin | 将0x000C处两个字节从6800改为0100,所引用表尺寸小于最小合法值 4 字节 |
invalid_vtable_field_offset.bin | 将0x0016处两个字节从1000改为FFFF,字段偏移指向文件之外 |
invalid_table_field_size.bin | 将文件截断到0x52字节,把一个 Uint32 值截成一半 |
invalid_table_field_offset.bin | 将文件截断到0x96字节,把一个 UOffset32 值截成一半;同时把0x90处两个字节从DC00改为FFFF,指向超出文件的部分 |
invalid_string_length_cut_short.bin | 将文件截断到0xAD字节,把字符串长度 Uint32 值截成一半 |
invalid_string_length.bin | 将0x00AC处两个字节从0500改为FFFF,字符串长度大于文件长度 |
invalid_vector_length_cut_short.bin | 将文件截断到0x0136字节,把向量长度 Uint32 值截成一半 |
invalid_struct_field_cut_short.bin | 将文件截断到0x5D字节,把结构体字段值截成一半 |
invalid_struct_array_field_cut_short.bin | 将文件截断到0x6A字节,把结构体数组字段值截成一半 |
invalid_vector_structs_cut_short.bin | 将文件截断到0x0154字节,切入结构体向量内部 |
invalid_vector_tables_cut_short.bin | 将文件截断到0x01DE字节,切入表偏移向量内部 |
invalid_vector_strings_cut_short.bin | 将文件截断到0x0176字节,切入字符串偏移向量内部 |
invalid_vector_scalars_cut_short.bin | 将文件截断到0x01C1字节,切入标量向量内部 |
invalid_vector_unions_cut_short.bin | 将文件截断到0x01DE字节,切入 union 偏移向量内部 |
invalid_union_type_value.bin | 将0x004D处一个字节从02改为FF,union 类型值超出枚举范围 |
invalid_vector_union_type_value.bin | 将0x0131处一个字节从02改为FF,向量 union 类型值超出枚举范围 |
其中截断类用例正是通过原文档给出的命令生成的:
truncate annotated_binary.bin --size=70 >> invalid_root_table_too_short.bin可见这些损坏主要分为四类:
- 偏移越界:根偏移、vtable 偏移、字段偏移被改成远超文件长度的值;
- 尺寸字段异常:vtable 尺寸、所引用表尺寸被改成大于文件或小于最小合法值(FlatBuffers 规定 vtable 头部两个
uint16_t至少占用 4 字节); - 文件截断:把文件切断在标量、字符串长度、向量长度、结构体/表/字符串/标量/union 向量内部,模拟传输或存储中被截断的数据;
- union 类型值非法:把
UType8类型的 union 判别值改成超出枚举定义的值。
3.4 注释输出中的错误与警告
以invalid_root_table_vtable_offset.afb为例(tests/annotated_binary/tests/invalid_root_table_vtable_offset.afb),修改根表 vtable 偏移后,注释输出首先把原本可识别的 vtable 节降级为:
unknown (no known references): +0x0008 | 00 00 3A 00 68 00 0C 00 | ?uint8_t[60] | ..:.h... | WARN: nothing refers to this section.随后根表条目被标记为错误:
root_table (AnnotatedBinary.Foo): +0x0044 | FF FF 00 00 | SOffset32 | 0x0000FFFF (65535) Loc: 0xFFFFFFFFFFFF0045 | ERROR: offset to vtable. Invalid offset, points outside the binary.再看invalid_vtable_field_offset.afb(tests/annotated_binary/tests/invalid_vtable_field_offset.afb),vtable 中的单个字段偏移被标记:
+0x0016 | FF FF | VOffset16 | 0xFFFF (65535) | ERROR: offset to field `bar` (id: 4). Invalid offset, points outside the binary.注释工具的输出规则可以总结为:
ERROR标记:出现在被篡改的偏移/长度字段所在行,明确指出"Invalid offset, points outside the binary"(偏移越界)或尺寸不合法等诊断信息;WARN: nothing refers to this section.:出现在因引用被破坏而"失联"的节上,节类型退化为unknown (no known references),字节类型显示为?uint8_t[N];- 截断类损坏:表现为某个标量、长度或偏移值只剩一半字节,注释会按照不完整的数据给出其解读,从而暴露数据不完整的事实。
这种"错误精确到字节、警告定位到节"的呈现方式,让开发者能一眼看出损坏发生在哪个偏移、影响了哪个字段,是排查序列化数据损坏的利器。
四、从源码理解注释与校验逻辑
注释工具的实现分为两层:
- 遍历与分析:src/binary_annotator.h 负责按 schema 递归遍历缓冲区,依据
UOffset32/SOffset32/VOffset16追踪 vtable、table、struct、string、vector、union 等所有区域,并维护区域之间的引用关系; - 文本渲染:src/annotated_binary_text_gen.cpp 将分析结果渲染为五列文本。其
OutputConfig结构(见 src/annotated_binary_text_gen.cpp)控制输出细节:max_bytes_per_line = 8(每行最多 8 字节)、offset_max_char = 4、分隔符|、include_vector_contents(与--annotate-sparse-vectors联动)等。
在 src/flatc.cpp 中,--annotate选项(src/flatc.cpp)接收 schema 文件名存入options.annotate_schema,之后加载并解析 schema(src/flatc.cpp),最终对每个输入二进制调用注释生成。文件头部的"Schema file / Binary file"注释行与逐节输出均由该流程产生。
值得注意的一点:原文档中注释示例出现过SOffet32/Offset32的拼写,实际实现中使用的类型枚举为UOffset、SOffset、UOffset64(见 src/annotated_binary_text_gen.cpp),本文统一采用后者。
五、如何自行复现与扩展验证
如果你想在本仓库中复现这套测试:
- 构建
flatc:参考 docs/source/building.md 或直接使用仓库预编译产物; - 生成全部
.afb:确保flatc位于仓库根目录(脚本从根目录定位flatc),然后运行:
python3 tests/annotated_binary/generate_annotations.py脚本会重新生成annotated_binary.afb以及tests/下全部 22 个invalid_*.afb(列表见 tests/annotated_binary/generate_annotations.py)。
- 手动验证单个用例:例如检查根偏移损坏:
cd tests/annotated_binary ../../flatc --annotate annotated_binary.fbs -- tests/invalid_root_offset.bin然后打开生成的tests/invalid_root_offset.afb,观察头部的offset to root table行是否带有ERROR标记。
- 构造你自己的损坏用例:参照
invalid_root_table_too_short.bin的做法,先用flatc -b从 JSON 生成二进制,再用truncate截断或直接编辑字节(修改偏移/长度字段),最后运行--annotate观察诊断输出——这既是学习 FlatBuffers 线格式布局的最好方式,也是为tests/目录贡献新测试用例的标准流程。
六、小结
tests/annotated_binary/tests/目录通过 22 个精心构造的损坏二进制,系统验证了 FlatBuffers 注释二进制工具的诊断能力:无论是根偏移、vtable 尺寸、字段偏移越界,还是各类字段/向量被截断,抑或 union 类型值非法,flatc --annotate都能在.afb输出的对应字节行上给出明确的ERROR标记,并将失联区域标记为unknown (no known references)附以WARN。这套机制让二进制序列化数据的损坏定位精确到字节级,是 FlatBuffers 调试与数据完整性验证体系中极具实战价值的一环,也是理解 FlatBuffers 底层线格式(偏移、vtable、对齐、向量与 union 布局)最直观的教材。
【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考