如何检查字节串内容?

最近碰到一个字节串被截断bug:base64解码之后的字节串, 以C字符串的方式进行处理,导致当字节串中包含0x00时被截断了, 费了不少精力才定位出来,主要原因在于没能及时地检查出串的实际内容。

这里字节串指其中每一个字节的可能值是[0x00, 0xff],不一定是可打印的ASCII字符, 在C中一般定义为unsigned char数组。

下面总结下如何检查一块内存。

GDB

如果环境允许,首选GDB进行调试,不需要来回增加调试代码,方便快捷。 GDB中的x命令能很方便地输出对应的十六进制,以确定内容是否被包含0x00。

用以下代码构造一个包含0x00的字符串byte_str:

#include <string>

using namespace std;

int
main(void)
{
    string byte_str("hello world!");
    byte_str[5] = 0;

    printf("byte string: %s\n", byte_str.c_str());

    return 0;
}

然后用x命令检查内存,可以发现第6个字节变为0x00了:

(gdb) x /12xb byte_str.c_str()
0x7fffffffd820: 0x68    0x65    0x6c    0x6c    0x6f    0x00    0x77    0x6f
0x7fffffffd828: 0x72    0x6c    0x64    0x21

此时如果以C字符串的方式使用,内容就会被截断:

(gdb) p byte_str.c_str()
$1 = 0x7fffffffd820 "hello"

BTW,此时string的长度还是包含了0x00后的内容:

(gdb) p byte_str.length()
$1 = 12
(gdb) p byte_str.size()
$2 = 12

GDB的常用命令可以查看这个Cheat Sheet - by darkdust.net

转换为十六进制后打印

如果环境中没有GDB,可能就需要增加代码,以打日志的方式进行调试了。 比较容易想到的做法是把字节串转为十六进制,然后打印。

比如一个字节串包含5个字节:abcde,那么打印日志就是:6162636465, 因为字符a的十六进制表示就是0x61,b的是0x62,以此类推。

按照这个思路可以写一个辅助的转换函数bin2hex, 加到代码中,方便打印日志:

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define BUFFER_SIZE 1024

/**
* Convert byte string `bin' with length `len' to hex string.
*/
char *
bin2hex(const unsigned char *bin, unsigned int len)
{
    static char buf[BUFFER_SIZE + 1];  // static is convenient for debugging purpose
    unsigned int i;

    if(len > (BUFFER_SIZE / 2)) {
        len = BUFFER_SIZE / 2;
    }

    for(i = 0; i < len; i++) {
        sprintf(buf + 2 * i, "%02x", bin[i]);
    }
    buf[2 * i] = 0;

    return buf;
}

hexdump

还有一种方法是直接把字节串写到日志中,然后通过hexdump命令来检查内容, 比如以下是检查abcde的十六进制值:

$ echo abcde | hexdump -C
00000000  61 62 63 64 65 0a                                 |abcde.|
00000006

本来我以为hexdump有选项能够支持仅输出十六进制结果,结果却意外地发现没有, 且其自定义格式的参数也不易懂,那就直接用-C参数看吧。

不过hexdump不支持,我们不妨自己制作一个这样的工具, 用Python很快就写出来了hexdump.py, 运行结果如下:

$ echo abcde | python hexdump.py
61626364650a

social