Linux LZO 解压程序所理解的 LZO 流格式

简介

这不是一份规范。目前似乎没有公开的 LZO 流格式规范。本文档描述了 Linux 内核中实现的 LZO 解压程序所能理解的输入格式。此分析的主题文件是 lib/lzo/lzo1x_decompress_safe.c。并未对压缩器或任何其他实现进行分析,尽管该格式很可能与标准格式一致。本文档的目的是更好地理解代码的功能,以便为未来的错误报告提出更有效的修复方案。

描述

该流由一系列指令、操作数和数据组成。指令由表示操作码的几个位以及构成指令操作数的位组成,其大小和位置取决于操作码以及前一条指令复制的字面量数量。操作数用于指示

  • 从字典(之前的输出缓冲区)复制数据时的距离

  • 一个长度(从字典中复制的字节数)

  • 要复制的字面量数量,它被保留在变量“state”中,作为下一条指令的信息。

根据操作码和操作数,可能还会包含额外数据。这些额外数据可以是操作数的补充(例如:以更大数值编码的长度或距离),或者是将要复制到输出缓冲区的字面量。

块的第一个字节采用与其他字节不同的编码方式,它似乎专门针对字面量使用进行了优化,因为在该字节之前还没有字典。

长度总是以可变大小编码,在操作数中以少量位开始。如果位数不足以表示该长度,则可以通过消耗更多字节来以每次最多 255 的增量增加长度(因此压缩比不能超过约 255:1)。使用 #bits 的可变长度编码总是相同的

length = byte & ((1 << #bits) - 1)
if (!length) {
        length = ((1 << #bits) - 1)
        length += 255*(number of zero bytes)
        length += first-non-zero-byte
}
length += constant (generally 2 or 3)

对于字典引用,距离是相对于输出指针的。距离使用属于特定范围的极少量位进行编码,从而导致多种使用不同编码的复制指令。某些编码涉及一个额外字节,其他编码则涉及两个形成小端 16 位数值(下文标记为 LE16)的额外字节。

除了大字面量复制之外的任何指令之后,在开始下一条指令之前,会复制 0、1、2 或 3 个字面量。已复制的字面量数量可能会改变下一条指令的含义和行为。实际上,只有一条指令需要知道复制了 0 个、少于 4 个还是更多字面量。这就是此实现中存储在 <state> 变量中的信息。要立即复制的字面量数量通常编码在指令的最后两位中,但也可能取自额外操作数(例如:距离)的最后两位。

当看到距离为 0 的块复制时,即表示流结束。只有一条指令可以编码此距离 (0001HLLL),它需要一个用于距离的 LE16 操作数,因此需要 3 个字节。

重要提示

在代码中,由于某些指令是在假设随后会有一定数量字节的前提下调用的(因为在解析指令之前已经做出了保证),所以缺少了一些长度检查。如果它们消耗了额外的字节,它们只需要“重新填充”这个信用额度。这是一种独立于算法或编码的实现设计选择。

版本

0: 原始版本 1: LZO-RLE

LZO 的版本 1 实现了一个扩展,使用游程编码(run length encoding)来编码零序列。这提高了具有大量零的数据的处理速度,这在 zram 中很常见。这以向后兼容的方式修改了位流(v1 可以正确解压 v0 压缩的数据,但 v0 无法读取 v1 数据)。

为了实现最大兼容性,两个版本以不同的名称(lzo 和 lzo-rle)提供。编码上的差异在本文档中已注明,例如:仅版本 1。

字节序列

第一个字节的编码

0..16   : follow regular instruction encoding, see below. It is worth
          noting that code 16 will represent a block copy from the
          dictionary which is empty, and that it will always be
          invalid at this place.

17      : bitstream version. If the first byte is 17, and compressed
          stream length is at least 5 bytes (length of shortest possible
          versioned bitstream), the next byte gives the bitstream version
          (version 1 only).
          Otherwise, the bitstream version is 0.

18..21  : copy 0..3 literals
          state = (byte - 17) = 0..3  [ copy <state> literals ]
          skip byte

22..255 : copy literal string
          length = (byte - 17) = 4..238
          state = 4 [ don't copy extra literals ]
          skip byte

指令编码

0 0 0 0 X X X X  (0..15)
  Depends on the number of literals copied by the last instruction.
  If last instruction did not copy any literal (state == 0), this
  encoding will be a copy of 4 or more literal, and must be interpreted
  like this :

     0 0 0 0 L L L L  (0..15)  : copy long literal string
     length = 3 + (L ?: 15 + (zero_bytes * 255) + non_zero_byte)
     state = 4  (no extra literals are copied)

  If last instruction used to copy between 1 to 3 literals (encoded in
  the instruction's opcode or distance), the instruction is a copy of a
  2-byte block from the dictionary within a 1kB distance. It is worth
  noting that this instruction provides little savings since it uses 2
  bytes to encode a copy of 2 other bytes but it encodes the number of
  following literals for free. It must be interpreted like this :

     0 0 0 0 D D S S  (0..15)  : copy 2 bytes from <= 1kB distance
     length = 2
     state = S (copy S literals after this block)
   Always followed by exactly one byte : H H H H H H H H
     distance = (H << 2) + D + 1

  If last instruction used to copy 4 or more literals (as detected by
  state == 4), the instruction becomes a copy of a 3-byte block from the
  dictionary from a 2..3kB distance, and must be interpreted like this :

     0 0 0 0 D D S S  (0..15)  : copy 3 bytes from 2..3 kB distance
     length = 3
     state = S (copy S literals after this block)
   Always followed by exactly one byte : H H H H H H H H
     distance = (H << 2) + D + 2049

0 0 0 1 H L L L  (16..31)
     Copy of a block within 16..48kB distance (preferably less than 10B)
     length = 2 + (L ?: 7 + (zero_bytes * 255) + non_zero_byte)
  Always followed by exactly one LE16 :  D D D D D D D D : D D D D D D S S
     distance = 16384 + (H << 14) + D
     state = S (copy S literals after this block)
     End of stream is reached if distance == 16384
     In version 1 only, to prevent ambiguity with the RLE case when
     ((distance & 0x803f) == 0x803f) && (261 <= length <= 264), the
     compressor must not emit block copies where distance and length
     meet these conditions.

  In version 1 only, this instruction is also used to encode a run of
     zeros if distance = 0xbfff, i.e. H = 1 and the D bits are all 1.
     In this case, it is followed by a fourth byte, X.
     run length = ((X << 3) | (0 0 0 0 0 L L L)) + 4

0 0 1 L L L L L  (32..63)
     Copy of small block within 16kB distance (preferably less than 34B)
     length = 2 + (L ?: 31 + (zero_bytes * 255) + non_zero_byte)
  Always followed by exactly one LE16 :  D D D D D D D D : D D D D D D S S
     distance = D + 1
     state = S (copy S literals after this block)

0 1 L D D D S S  (64..127)
     Copy 3-4 bytes from block within 2kB distance
     state = S (copy S literals after this block)
     length = 3 + L
   Always followed by exactly one byte : H H H H H H H H
     distance = (H << 3) + D + 1

1 L L D D D S S  (128..255)
     Copy 5-8 bytes from block within 2kB distance
     state = S (copy S literals after this block)
     length = 5 + L
   Always followed by exactly one byte : H H H H H H H H
     distance = (H << 3) + D + 1

作者

本文档由 Willy Tarreau <w@1wt.eu> 在 2014/07/19 编写,当时是对 Linux 3.16-rc5 中可用的解压代码进行分析;并由 Dave Rodgman <dave.rodgman@arm.com> 在 2018/10/30 更新,引入了游程编码。代码很复杂,本文档可能包含错误或遗漏了一些边界情况。无论如何,请将任何疑问、修复或建议的更新报告给作者,以便更新本文档。