可变长数据
在整个DWARF中有大量信息使用整数值来表示,从数据段偏移量,到数组或结构体的大小,等等。由于大多数整数值是小整数,用几位就可以表示,因此这意味着数据主要由零组成,对应的bits相当于被浪费了。
DWARF定义了一个可变长度的整数编码方案,称为Little Endian Base 128(LEB128),它能够压缩实际占用的字节数,减小编码后的数据量。LEB128有两种变体:
- ULEB128: 用于编码无符号整数
- SLEB128: 用于编码有符号整数
ULEB128编码方案
UELB128编码算法:
MSB ------------------ LSB
10011000011101100101 In raw binary
010011000011101100101 Padded to a multiple of 7 bits
0100110 0001110 1100101 Split into 7-bit groups
00100110 10001110 11100101 Add high 1 bits on all but last (most significant) group to form bytes
0x26 0x8E 0xE5 In hexadecimal
→ 0xE5 0x8E 0x26 Output stream (LSB to MSB)
用咱家乡话总结下就是:
- 将数字转换为二进制数表示,这些字节按小端序排列(即最低有效字节在前)
- 将整数按7位一组进行分割
- 每组7位存储在一个字节中,该字节的最高位(第8位)用作标志位:
- 如果后面还有更多组,则该位设为1
- 如果是最后一组,则该位设为0
例如,数字Uint64 624485的ULEB128编码为:
624485 = 0x98765
十六进制数,一个数位由4位二进制表示,转换为二进制表示为:
1001 1000 0111 0110 0101
考虑到7位一组进行分割,先填充为7bits的整数倍:
0 1001 1000 0111 0110 0101
然后7位一组进行分割,注意是小端字节序,所以从右边开始分割:
0 1001 10 / 00 0111 0 / 110 0101
- 第1组: 110 0101,因为后续还有,所以第8位标记为1,最终为 1110 0101 = 0xe5
- 第2组: 00 0111 0, 000 1110,第8位标记为1,最终为 1000 1110 = 0x8e
- 第3组: 0 1001 10, 010 0110,第8位标记wi0,最终为 0010 0110 = 0x26
编码后的字节序列为: []byte{0xe5 0x8e 0x26},最终只占用了3个字节,而如果用原始数据类型uint64则要用8个字节。
SLEB128编码方案
SLEB128的编码规则类似,只是在处理负数时需要考虑符号位扩展,处理正数时没有区别。
SLEB128负数编码算法:
MSB ------------------ LSB
11110001001000000 Binary encoding of 123456
000011110001001000000 As a 21-bit number
111100001110110111111 Negating all bits (ones' complement)
111100001110111000000 Adding one (two's complement)
1111000 0111011 1000000 Split into 7-bit groups
01111000 10111011 11000000 Add high 1 bits on all but last (most significant) group to form bytes
0x78 0xBB 0xC0 In hexadecimal
用咱家乡话说就是:
- 如果是负数,先转换为二进制补码表示
- 然后7bits一组进行分组,依然按照小端来
- 除了最后一组,每个分组的第8位都标记为1
- 得到最终结果
本文总结
本文介绍了ULEB128、SLEB128这种对小整数编码存储友好的编码方案,可以节省存储空间占用。对于正数,ULEB128、SLEB128编码结果是相同的,只是对于负数时,SLEB128要先转换为其补码再进行编码。通过这种编码方式,DWARF能够有效压缩整数数据,减小调试信息的体积。在实际应用中,大多数整数值都可以用1-2个字节表示,相比固定4字节或8字节的存储方式节省了大量空间。