Skip to content

末尾 zstd Blob

本页中的所有偏移、地址和节名均适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(包版本 0.0.40,build-id 89edbbe81c5b328a958fe628a9f2207d,781,691,048 字节)。请固定到 build-id;这是唯一明确的标识符。其他构建会有所不同。

摘要

libtpu.so 做一次朴素的 binwalk 风格 magic-byte carve,会找到 zstd 帧魔数 28 b5 2f fd,并容易误报文件偏移 0x20F99BEF 处存在一个约 4.1 MB 的追加在 ELF 节头之后、使用 zstd 字典压缩的 blob。并不存在这样的 blob。没有末尾载荷,没有嵌入式字典,也没有可用于恢复字典的 ZSTD_DCtx_loadDictionary 调用点。carve 锚定的字节序列是位于 .text 深处的一个 x86-64 指令立即数,而清楚地证明这一点正是本页的目的。

直接读取该二进制后,机制很简单。四个字节 28 b5 2f fdZSTD_MAGICNUMBER0xFD2FB528,RFC 8878 §3.1.1.1)的小端编码。libtpu.so 静态链接了完整的 libzstd 源码,包括压缩器和解压器、309 个 ZSTD_* 符号,并且完整符号以本地 (t) 条目保留,因此这个常量出现了五次,每次都是某个 libzstd 函数中的 mov/cmp 立即数,用于把帧头写入调用者提供的输出缓冲区,或将其与输入缓冲区进行检查。五处都不在数据节中;没有一处被指针或重定位引用;五处都只能通过程序执行流进入 libzstd 代码而到达。

本页做三件事:证明 ELF 节头表恰好在 EOF 结束(不存在超出节之后的末尾区域),走读产生误报 movZSTD_compressEnd_public 空帧收尾逻辑,并记录实际的运行时 zstd 表面,即由 riegeli 驱动的流式(解)压缩;如果使用字典,字典也是应用在运行时通过 ZSTD_DCtx_refDDict 提供,而不是烘焙在镜像中。carve 可能猜测该 blob 会解码出的按 codename 区分的硬件常量包是真实制品,但它位于别处,是未压缩的 protobuf;参见 chip-parts binarypb

对重新验证本结论的读者来说,契约是:

  • ELF 布局计算e_shoff + e_shnum × e_shentsize = file size,因此没有追加内容。
  • 五处 magic 出现 — 每一处的文件偏移、所在 libzstd 函数和指令角色。
  • 空帧收尾逻辑ZSTD_compressEnd_public 中把 28 b5 2f fd 写入堆缓冲区的确切字节序列。
  • 缺失的符号 — 为什么不存在 ZSTD_DCtx_loadDictionary 会否定字典恢复方案,以及现有的 ZSTD_DCtx_refDDict 实际做什么。
声称的 blob0x20F99BEF 处约 4.1 MB 的 zstd-dict 压缩载荷 — 不存在
0x20F99BEF 处实际内容movl $0xfd2fb528,(%r14) 立即数(文件偏移 0x20F99BEF.text
容器[21] .text (PROGBITS, R+X) — 不在节表之后
节表e_shoff 0x2E979BA8 + 52 × 64 = 0x2E97A8A8 = EOF(没有末尾区域)
Magic 出现共 5 处,全部在 .text 中,全部是 mov/cmp 立即数
字典机制没有静态字典;ZSTD_DCtx_loadDictionary 缺失;只有运行时 ZSTD_DCtx_refDDict
解压格式n/a — 没有可解压的 blob
置信度(负面结论,由字节和反汇编锚定)

**注意:**不存在“位于 0x20F99BEF、约 4.1 MB、zstd 字典编码、解码为按 codename 区分的硬件常量的末尾 zstd blob”。该偏移位于 .text 内,不在 EOF 之后;这些字节是一个 mov 立即数,不是帧;没有嵌入式字典;不存在载荷。下文给出字节级和反汇编级证明,用来解答 forensics overview 指向这里的 magic-byte 问题。


“EOF 之后的末尾内容”前提是错误的

ELF 实际在哪里结束

最初的假设是该 blob “位于最后一个 ELF 节头之后”,意味着有一个不属于任何节的追加载荷。ELF 头部计算直接否定了这一点。

text
e_shoff (Start of section headers) = 781,687,720 = 0x2E979BA8
e_shentsize                        = 64          = 0x40
e_shnum                            = 52          = 0x34
section-header table end = 0x2E979BA8 + 52 × 0x40
                         = 0x2E979BA8 + 0xD00
                         = 0x2E97A8A8
file size                          = 781,691,048 = 0x2E97A8A8   ← identical
```text

节头表一直延伸到文件的**最后**一个字节。它之后没有任何区域。最后一个承载数据的节是 `[51] .strtab`,结束于 `0x23DF76C3 + 0xAB824DE = 0x2E979BA1`;一个 7 字节对齐间隙位于 `0x2E979BA8` 的表之前。这个 745 MB 镜像中的每个字节都可归属于某个节,或归属于节头表本身。

> **陷阱 —** 节头表在 EOF 结束,是对“追加载荷”说法最干净的反证,只需一次 `readelf -h`。今后对这个二进制提出任何“节之后的末尾 blob”主张,都应当先检查 `e_shoff + e_shnum × e_shentsize`;在此构建中,该和逐字节等于文件大小。

### 锚点偏移位于 `.text` 结束前约 2.6 MB

carve 锚点 `0x20F99BEF`(文件偏移 553,229,295)**不在** EOF 处或之后。它落在节 `[21] .text` 内:

| 区域 | 文件范围 |
|---|---|
| `[21] .text` (PROGBITS, R+X) | `0xE63C000 .. 0x21217484` |
| `[11] .rodata` (PROGBITS, R) | `0x84A0000 .. 0xBE8AF28` |
| `[51] .strtab`(最后一个数据节) | `0x23DF76C3 .. 0x2E979BA1` |
| 节头表 | `0x2E979BA8 .. 0x2E97A8A8` (= EOF) |
| 锚点 `0x20F99BEF` | 位于 `.text` 内,距其结束约 2.6 MB |

`.text` 结束于 `0xE63C000 + 0x12BDB484 = 0x21217484`。锚点位于代码节结束前 `0x21217484 − 0x20F99BEF = 0x27D895 ≈ 2.6 MB`,正处在可执行代码中,不在任何数据区域,也没有追加到任何地方。

> **说明 —** 因为在这个镜像中 `.text` 的文件偏移与虚拟地址相同(段在 `0xE63C000..0x21217484` 范围内按 1:1 布局),所以下面的每个偏移同时也是文件偏移和运行时 vaddr。这就是为什么 `objdump --start-address=0x20F99BEC` 可以在无需 offset/vaddr 转换的情况下正确反汇编磁盘上的字节。

### grep 返回零的伪象

对该文件运行普通的 `grep -c -a -P '\x28\xb5\x2f\xfd'` 会返回 **0** 个命中,也就是 forensics overview 标出的结果。这是工具伪象,不是缺失证据:GNU `grep` 以行为缓冲,并会误处理一个布满 NUL 字节和超长“行”的 745 MB 二进制。分块的精确字节扫描(读取 16 MB 窗口,保留 3 字节重叠,使用 `bytes.find`)会返回真实计数:

```text
total occurrences of 28 b5 2f fd: 5
  0x20F99BEF  (553,229,295)
  0x20F9B2FE  (553,235,198)
  0x2100C714  (553,699,092)
  0x2100C72A  (553,699,114)
  0x2100C77D  (553,699,197)

陷阱 — “magic byte scan returned zero hits” 可能意味着扫描器错了,而不是字节不存在。对这个二进制的诚实答案是处出现,全部在 .text 内。先使用二进制安全扫描器(分块 bytes.findxxd | rg,或理解十六进制的工具),再判断某个 magic 是否缺失。


五处 Magic 出现

每一处是什么

每一处都是 libzstd 指令的立即操作数。没有一处是数据。下表是完整清单。

文件偏移所在函数函数基址指令角色
0x20F99BEFZSTD_compressEnd_public0x20F99AC0movl $0xfd2fb528,(%r14)(位于 0x20F99BEC,+0x12C)将 magic 写入输出缓冲区(空帧收尾逻辑)
0x20F9B2FEZSTD_writeFrameHeader0x20F9B200movl $0xfd2fb528,(%rdi)(位于 0x20F9B2FC,+0xFC)在每个写出帧的开头写入 magic
0x2100C714ZSTD_getFrameHeader_advanced0x2100C6C0movl $0xfd2fb528,-0x1c(%rbp)(位于 0x2100C711,+0x51)在 header memcpy 前写入 stack-local 哨兵
0x2100C72AZSTD_getFrameHeader_advanced0x2100C6C0cmpl $0xfd2fb528,-0x1c(%rbp)(位于 0x2100C727,+0x67)将复制后的 stack-local 与 magic 比较
0x2100C77DZSTD_getFrameHeader_advanced0x2100C6C0cmp $0xfd2fb528,%ecx(位于 0x2100C77B,+0xBB)验证输入帧 magic;不匹配则 jne 到错误

第三和第四行在同一个 ZSTD_getFrameHeader_advanced 基本块内相距 22 字节(0x2100C711/0x2100C727):第一条把 magic 写入一个 stack-local 作为哨兵,然后 memcpy 把候选 header 覆盖到它上面,第二条再把结果与 magic 比较。它们属于同一套 header 验证逻辑,不是相互独立的发现。

怪癖 — 该常量在 codec 的两侧都会出现。ZSTD_writeFrameHeaderZSTD_compressEnd_public发出它;ZSTD_getFrameHeader_advanced检查它。朴素的“搜索 magic 来寻找载荷”会把写 header 的编码器和验证 header 的解码器都当作已存储帧。两者都不是。

0x2100C77B 处的解码器侧检查

这些内容是代码而不是数据的最清楚单点证明,是解码器中的 magic 验证 cmp。反汇编会读取输入缓冲区的前四个字节,并将其与常量比较:

asm
2100c779:  8b 0e                 mov   (%rsi),%ecx          ; load 4 bytes of input frame
2100c77b:  81 f9 28 b5 2f fd     cmp   $0xfd2fb528,%ecx     ; compare to ZSTD_MAGICNUMBER
                                                            ; jne -> "not a zstd frame" error path
```text

`%rsi` 是调用者提供的源指针。该常量是解码器要求**外部**输入具备的*期望* magic,正好与已存储帧相反。4 字节 `cmp` 立即数 `28 b5 2f fd` 从文件偏移 `0x2100C77D` 开始(opcode `81` 位于 `0x2100C77B`,ModR/M `f9` 位于 `0x2100C77C`,随后是立即数);carve 锚定的就是这个立即数。

---

## 空帧收尾逻辑 — `ZSTD_compressEnd_public`

### 为什么该函数会写入 magic

`ZSTD_compressEnd_public`(`0x20F99AC0`,大小 `0x27F` = 639 字节;下一个符号 `ZSTD_createCDict_advanced` 位于 `0x20F99D40`)是上游 zstd 的流终结器。它会冲刷最后一个块,并在退化的“压缩 0 字节 / 流从未开始”情况下,直接在调用者的输出缓冲区中合成一个完整的**空 zstd 帧**。`movl $0xfd2fb528,(%r14)` 就位于这个合成逻辑中。`%r14` 保存 `dst` 指针,即调用者拥有的堆缓冲区,从来不是 ELF 镜像中的某个区域。

### 算法

```c
function ZSTD_compressEnd_public(cctx, dst, dstCap, src, srcSize):   // sub @ 0x20F99AC0
    // ... normal flush path elided ...
    if cctx.stage == init_only:               // degenerate "empty frame" case
        out = (byte*)dst;                     // %r14 = dst (heap, caller-owned)

        // --- build the 6-byte frame header from cctx.cParams ---
        WD  = window_descriptor(cParams.windowLog);   // movzbl 0xf4(%rbx); shl/add
        chk = (cParams.checksumFlag != 0);            // setne %dl  -> Content_Checksum_flag
        did = (cParams.dictID > 0);                   // setg %dil  -> Dictionary_ID present
        FHD = assemble_frame_header_descriptor(chk, did);  // shl/or into FHD bit positions

        // --- emit the empty frame, 10 bytes total ---
        *(uint32_t*)out = 0xFD2FB528;         // 0x20F99BEC: movl  -> magic 28 b5 2f fd
        r8 = 4;                               // 0x20F99BF3: cursor past the 4-byte magic
        out[r8 + 0] = FHD;                    // 0x20F99C01: Frame_Header_Descriptor
        out[r8 + 1] = WD;                     // 0x20F99C05: Window_Descriptor
        *(uint16_t*)&out[r8 + 2] = 0x0001;    // 0x20F99C10: block header, last=1 type=Raw
        out[r8 + 4] = 0x00;                   // 0x20F99C18: 1-byte raw block content
        return 10;                            // magic(4)+FHD(1)+WD(1)+blkhdr(3)+content(1)

原始字节及其含义

如果把 0x20F99BEF 之后的字节按十六进制转储读取,它们看起来像是帧数据,而这正是朴素 carve 被欺骗的原因:

text
20f99bef: 28 b5 2f fd  41 b8 04 00 00 00  85 f6  0f b6 c9  0f
20f99bff: 44 f9  43 88 14 06  43 88 7c 06 01  c7 03 02 00 00 00
```text

按 x86-64 反汇编时,它们是一段连贯的指令流:`movl $magic,(%r14)`;`mov $0x4,%r8d`;`test %esi,%esi`;`movzbl %cl,%ecx`;`cmove %ecx,%edi`;`mov %dl,(%r14,%r8,1)`;……并流向该函数的 `vzeroupper`/`call ZSTD_trace_compress_end` 出口。若(误)按 zstd 帧反汇编,它们会立即产生“frame parameters too large”错误:伪 header 之后的字节会解码出 33 的 window log(`2^33` = 8 GiB,超过 `ZSTD_WINDOWLOG_MAX = 31`),并且第一个块大小远超 128 KiB 块上限。zstd 会拒绝这样的帧;carve 的“4.1 MB”长度来自签名处理器沿着可执行代码追逐无意义的块大小字段,直到出错为止,结束位置在某个 MLIR pass 中间,仍然位于 `.text` 内。

> **怪癖 —** 没有交叉验证的 4 字节 magic 检测器,会在*任何*通过 32 位 `mov` 立即数构造 zstd 帧的代码路径上误报。可靠的判断点是所在符号:如果命中位于名为 `ZSTD_writeFrameHeader`、`ZSTD_compressEnd_*` 或 `ZSTD_*FrameHeader*` 的函数中,就是代码立即数,不是已存储帧。carve 之前先把锚点解析到符号表。

---

## 字典机制 — 不存在静态字典

### 缺失的符号否定了恢复方案

字典恢复假设需要找到 `ZSTD_DCtx_loadDictionary` 调用点,向后回溯每个调用点到 `.rodata`/`.data` 中的固定字典偏移,并匹配帧头中的 dictionary-ID。该前提在第一步就失败了:这个二进制中不存在公共的**解压侧**字典加载器。

| 恢复方案假定的符号 | 是否存在于 `libtpu.so`? | 地址 |
|---|---|---|
| `ZSTD_DCtx_loadDictionary` | **否** | — |
| `ZSTD_DCtx_loadDictionary_byReference` | **否** | — |
| `ZSTD_DDict_createByReference` | **否** | — |
| `ZSTD_loadDictionaryContent` | 是(压缩器内部) | `0x20FA0020` |
| `ZSTD_dedicatedDictSearch_lazy_loadDictionary` | 是(压缩器 match-finder) | `0x20FBB120` |
| `ZSTD_DDict_dictContent` | 是 | `0x2100C100` |
| `ZSTD_createDDict_advanced` | 是 | `0x2100C200` |
| `ZSTD_DCtx_refDDict` | 是 | `0x2100E300` |
| `ZSTD_decompressDCtx` | 是 | `0x2100D3E0` |
| `ZSTD_decompressStream` | 是 | `0x2100E840` |

实际存在的两个名称包含 “loadDictionary” 的符号是**压缩器**内部符号(`ZSTD_loadDictionaryContent` 把字典摄入 `ZSTD_CCtx`;`ZSTD_dedicatedDictSearch_lazy_loadDictionary` 是 lazy match-finder 辅助函数)。二者都不会把字典加载到解压上下文,也没有接到这个镜像中的静态缓冲区。没有 `ZSTD_DCtx_loadDictionary`,就不存在“把固定嵌入式字典加载到 DCtx 并解压 blob”的可逆向路径。

### 运行时字典路径实际是什么

现有的 `ZSTD_DCtx_refDDict` 和 `ZSTD_createDDict_advanced` 是**引用运行时提供的已消化字典**的 API。字符串 sidecar 显示它们由哪里驱动:riegeli 的 zstd reader/writer 和一个 RPC/program 压缩层,而不是静态 blob:

```text
REQUEST_COMPRESSION_ZSTD          ZstdReaderBase           ZSTD_DCtx_refDDict
RESPONSE_COMPRESSION_ZSTD         ZstdReaderINS_           ZSTD_decompressStream
TPU_CORE_PROGRAM_COMPRESSION_ZSTD ZStdCompressor           ZSTD_CCtx_refCDict
zstd_compression_level            zstd_reader / zstd_writer

这些名称指向用于 RPC 载荷和 TPU-core 程序的流式压缩设施。此类流使用的任何字典都由应用在运行时传入(JAX/TF 制品、RPC 协商),通过 ZSTD_DCtx_refDDict 附加,并且从不从 libtpu.so 的固定区域读取。

唯一嵌入的 zstd 参数数据

镜像中烘焙的唯一 zstd 参数区域是 8 字节,不是字典:

符号Vaddr / file off字节解码后
tpu::(anonymous)::kZstdParams0x0B831ACC[11] .rodata01 00 00 00 1B 00 00 00{ level = 1, window_log = 27 }

这是在没有 override 时构造 TPU zstd compressor 使用的默认 tpu::ZStdParams 结构(level 1,128 MiB window)。它是配置,不是内容,更不可能是 4 MB 字典。


运行时解压路径 — 替代 blob 的实际存在物

没有什么需要定位或解压

因为不存在静态 blob,也不存在静态字典,所以没有“运行时定位 + 解压末尾 blob”的序列可供记录;本页标题提出的问题,其答案是该操作并未发生。实际存在的运行时 zstd 代码是通用的静态链接 libzstd,由 riegeli 驱动,对来自 .so 外部的数据做流式压缩:

text
application / RPC layer
  └─ supplies a compressed stream (+ optional dictionary buffer) at runtime
       └─ riegeli ZstdReaderBase
            └─ ZSTD_createDCtx        (0x2100C480)
            └─ ZSTD_DCtx_refDDict     (0x2100E300)   ── attach app-supplied digested dict
            └─ ZSTD_decompressStream  (0x2100E840)   ── stream-decompress into output
                 └─ ZSTD_getFrameHeader_advanced (0x2100C6C0)  ── validates magic (the cmp above)
```text

`0x2100C77B` 处的 frame-magic `cmp` 位于这条解码路径上:它验证应用送入的帧。blob 搜寻把这个验证常量以及编码器的发射常量误认为了已存储数据。

### 想象中的载荷实际位于何处

人们可能期望这种 blob 解压出的硬件常量包,在这个二进制中确实是真实制品,但它是一个从数据节解析出的**未压缩 protobuf**,另有文档说明:

- 按 codename 区分的硬件常量和 `chip_parts` binarypb 包在 [`targets/chip-parts-binarypb.md`](../targets/chip-parts-binarypb.md) 和 [`targets/per-codename-hw-constants.md`](../targets/per-codename-hw-constants.md) 中说明。它们没有经过 zstd 压缩,也不依赖任何 blob 恢复。

> **说明 —** 对语料库其余部分可泛化的经验是:任何静态链接 libzstd 的二进制(crucible-notes 中有若干个)都会在 `ZSTD_writeFrameHeader` / `ZSTD_compressEnd_*` / `ZSTD_*FrameHeader*` 函数通过 `mov` 立即数构造帧的位置产生同样的误报 carve。若某个 zstd magic 命中的锚点解析到 `.text` 内,应先把它当作代码立即数,除非能够通过所在*数据*节和有效 header(window-log ≤ 31,block-size ≤ 128 KiB)证明它是已存储帧。

---

## 相关组件

| 名称 | 关系 |
|---|---|
| `ZSTD_compressEnd_public` (`0x20F99AC0`) | `0x20F99BEF` 误报 magic 的来源(空帧收尾逻辑) |
| `ZSTD_writeFrameHeader` (`0x20F9B200`) | `0x20F9B2FE` magic 的来源(每个发射出的帧头) |
| `ZSTD_getFrameHeader_advanced` (`0x2100C6C0`) | `0x2100C714/72A/77D` magic 的来源(输入帧验证) |
| riegeli `ZstdReaderBase` / `ZStdCompressor` | 真正的运行时 zstd 表面 — 流式处理,应用提供字典 |
| `tpu::(anonymous)::kZstdParams` (`0x0B831ACC`) | 唯一嵌入的 zstd 配置:`{level=1, window_log=27}`,8 字节 |

## 交叉引用

- [Forensics Overview](overview.md) — 将 magic-byte 谜题指向这里;其关于节表在 EOF 结束的陷阱在本页解决。
- [ELF Anatomy](elf-anatomy.md) — 结尾与 EOF 重合的节表;完整的 `[0]..[51]` 布局表明没有末尾区域。
- [Embedded Library Atlas](embedded-library-atlas.md) — 静态链接的 libzstd(309 个 `ZSTD_*` 符号),其代码立即数产生了五处 magic 命中。
- [Custom Sections](custom-sections.md) — 确实承载载荷的非标准数据节(`filewrapper_toc`、`.lrodata` 等),但都不是 zstd blob。
- [chip-parts binarypb](../targets/chip-parts-binarypb.md) — 真实的、未压缩的硬件常量包,从数据节解析,而不是从任何压缩 blob 解码。
- [Per-Codename HW Constants](../targets/per-codename-hw-constants.md) — 按 codename 区分的硅片常量,从数据节解析,不来自任何压缩 blob。