在计算机系统中,二进制与十六进制的相互转换是数据存储、网络传输、加密算法等场景中的基础操作。Linux内核中的lib/hexdump.c模块提供了三个关键函数:bin2hex、hex2bin和hex_to_bin,分别实现二进制到十六进制的转换、十六进制到二进制的转换,以及单个字符的十六进制解析。本文将从设计原理、实现细节和应用场景三个方面深入解析这些函数。
一、bin2hex:二进制数据转十六进制字符串
1. 功能与参数
- 功能:将二进制数据转换为可读的十六进制ASCII字符串。 
- 参数: - char *dst:目标缓冲区,需预分配至少- 2*count字节。
- const void *src:输入的二进制数据指针。
- size_t count:待转换的字节数。
 
2. 实现逻辑
char *bin2hex(char *dst, const void *src, size_t count) {
    const unsigned char *_src = src;
    while (count--) 
        dst = hex_byte_pack(dst, *_src++);
    return dst;
}
- 逐字节处理:通过循环遍历每个字节,调用 - hex_byte_pack将其转换为两个十六进制字符。
- hex_byte_pack:将单个字节拆分为高4位和低4位,分别转换为对应的ASCII字符(如 - 0xA3转为- "A3")。
- 返回值:返回目标缓冲区的末尾指针,便于链式操作。 
3. 注意事项
- 缓冲区安全:调用者需确保 - dst有足够空间。
- 无终止符:函数不会自动添加 - \0,需手动处理字符串终止。
4. 应用场景
- 日志输出:将二进制数据转为可读字符串以便调试。 
- 密钥显示:在加密场景中安全展示二进制密钥。 
二、hex2bin:十六进制字符串转二进制数据
1. 功能与参数
- 功能:将十六进制ASCII字符串还原为二进制数据。 
- 参数: - u8 *dst:目标二进制缓冲区,需预分配至少- count字节。
- const char *src:输入的十六进制字符串。
- size_t count:待转换的字节数(需满足- strlen(src) >= 2*count)。
 
2. 实现逻辑
int hex2bin(u8 *dst, const char *src, size_t count) {
    while (count--) {
        int hi = hex_to_bin(*src++); // 高4位
        int lo = hex_to_bin(*src++); // 低4位
        if (hi < 0 || lo < 0) return -EINVAL;
        *dst++ = (hi << 4) | lo;
    }
    return 0;
}
- 字符对处理:每两个字符组成一个字节,高4位在前,低4位在后。 
- 错误检查:若字符非法(如 - 'G'),立即返回错误码- -EINVAL。
3. 注意事项
- 输入合法性:字符串必须为偶数长度,且字符范围为 - 0-9、- a-f、- A-F。
- 大小写兼容: - hex_to_bin函数统一处理大小写字母。
4. 应用场景
- 协议解析:解析网络协议中的十六进制字段。 
- 密钥加载:将配置文件中存储的十六进制密钥转为二进制。 
三、hex_to_bin:无分支的字符解析核心
1. 功能与设计目标
- 功能:将单个十六进制字符转为4位二进制值(0-15),非法字符返回 - -1。
- 设计目标:避免条件分支,防止侧信道攻击(对加密场景至关重要)。 
2. 实现逻辑
int hex_to_bin(unsigned char ch) {
    unsigned char cu = ch & 0xdf; // 小写转大写
    int part1 = (ch - '0' + 1) & (unsigned)((ch - '9' - 1) & ('0' - 1 - ch)) >> 8;
    int part2 = (cu - 'A' + 11) & (unsigned)((cu - 'F' - 1) & ('A' - 1 - cu)) >> 8;
    return -1 + part1 + part2;
}
- 数字字符处理( - 0-9):- 通过算术运算生成掩码,合法字符返回 - 1-10,非法返回- 0。
 
- 字母字符处理( - A-F/a-f):- 统一转为大写后,合法字符返回 - 11-16,非法返回- 0。
 
- 合并结果: - -1 + part1 + part2确保非法字符返回- -1,合法字符返回正确值。
3. 无分支设计解析
- 掩码生成:通过符号位运算生成合法字符的掩码(如 - 0xFFFFFF)。
- 值计算:将字符偏移量(如 - ch - '0')与掩码按位与,避免- if判断。
4. 示例验证
- '3'→- 3,- 'A'→- 10,- 'f'→- 15,- 'X'→- -1。
四、综合应用与总结
1. 协作流程
- 加密密钥处理: - 存储:使用 - bin2hex将二进制密钥转为十六进制字符串。
- 加载:使用 - hex2bin反向解析,依赖- hex_to_bin逐字符校验。
 
2. 性能与安全
- 无分支优势: - hex_to_bin避免分支预测错误和时序攻击风险。
- 内存安全:调用者需确保缓冲区大小,防止溢出。 
3. 扩展思考
- Unicode支持:当前实现仅限ASCII字符,需扩展以支持宽字符。 
- 错误处理增强: - hex2bin可返回更详细的错误类型(如非法字符位置)。
4. 总结
bin2hex、hex2bin和hex_to_bin共同构建了一套高效、安全的进制转换工具链。其设计体现了以下原则:
- 效率优先:通过循环和位运算减少开销。 
- 安全可靠:严格的输入校验和无分支设计。 
- 接口简洁:清晰的参数约定和返回值语义。 
这些函数不仅是Linux内核的基石,也可为其他系统开发提供借鉴,尤其在需要高频数据转换或安全敏感的场景中,其实现思路具有重要参考价值。

hex_to_bin
**
 * hex_to_bin - convert a hex digit to its real value
 * @ch: ascii character represents hex digit
 *
 * hex_to_bin() converts one hex digit to its actual value or -1 in case of bad
 * input.
 *
 * This function is used to load cryptographic keys, so it is coded in such a
 * way that there are no conditions or memory accesses that depend on data.
 *
 * Explanation of the logic:
 * (ch - '9' - 1) is negative if ch <= '9'
 * ('0' - 1 - ch) is negative if ch >= '0'
 * we "and" these two values, so the result is negative if ch is in the range
 *	'0' ... '9'
 * we are only interested in the sign, so we do a shift ">> 8"; note that right
 *	shift of a negative value is implementation-defined, so we cast the
 *	value to (unsigned) before the shift --- we have 0xffffff if ch is in
 *	the range '0' ... '9', 0 otherwise
 * we "and" this value with (ch - '0' + 1) --- we have a value 1 ... 10 if ch is
 *	in the range '0' ... '9', 0 otherwise
 * we add this value to -1 --- we have a value 0 ... 9 if ch is in the range '0'
 *	... '9', -1 otherwise
 * the next line is similar to the previous one, but we need to decode both
 *	uppercase and lowercase letters, so we use (ch & 0xdf), which converts
 *	lowercase to uppercase
 */
int hex_to_bin(unsigned char ch)
{
	unsigned char cu = ch & 0xdf;
	return -1 +
		((ch - '0' +  1) & (unsigned)((ch - '9' - 1) & ('0' - 1 - ch)) >> 8) +
		((cu - 'A' + 11) & (unsigned)((cu - 'F' - 1) & ('A' - 1 - cu)) >> 8);
}
EXPORT_SYMBOL(hex_to_bin);hex_to_bin 函数的作用是将单个十六进制字符(0-9, A-F, a-f)转换为对应的 4 位二进制值(0-15),若字符非法则返回 -1。以下是分步讲解:
1. 函数参数与目标
- 输入: - unsigned char ch(一个 ASCII 字符)。
- 输出: - 合法字符:返回对应的数值(0-15)。 
- 非法字符:返回 - -1。
 
2. 核心逻辑
函数通过位运算和算术运算,避免条件分支,直接计算字符的合法性及其对应的值。逻辑分为两部分:
- 处理数字 - 0-9。
- 处理字母 - A-F/a-f(统一转为大写处理)。
3. 处理数字字符 0-9
// 判断字符是否在 '0'-'9' 范围内,并计算对应值
int part1 = (ch - '0' + 1) & (unsigned)((ch - '9' - 1) & ('0' - 1 - ch)) >> 8);
步骤拆解:
- 范围检测: - (ch - '9' - 1) < 0:若- ch <= '9',结果为负。
- ('0' - 1 - ch) < 0:若- ch >= '0',结果为负。
- 两者按位与后,结果为负 当且仅当 - ch在- '0'-'9'范围内。
 
- 掩码生成: - 将结果转为 - unsigned并右移 8 位,生成掩码- 0xFFFFFF(合法)或- 0(非法)。
 
- 值计算: - 若合法, - ch - '0' + 1生成值- 1-10,与掩码按位与后保留原值。
- 若非法,结果为 - 0。
 
4. 处理字母字符 A-F/a-f
// 统一转为大写后,判断是否在 'A'-'F' 范围内,并计算对应值
unsigned char cu = ch & 0xdf;  // 小写转大写(如 'a' → 'A')
int part2 = (cu - 'A' + 11) & (unsigned)((cu - 'F' - 1) & ('A' - 1 - cu)) >> 8);
步骤拆解:
- 小写转大写: - ch & 0xdf将小写字母转为大写(如- 'a' → 'A')。
 
- 范围检测: - (cu - 'F' - 1) < 0:若- cu <= 'F',结果为负。
- ('A' - 1 - cu) < 0:若- cu >= 'A',结果为负。
- 两者按位与后,结果为负 当且仅当 - cu在- 'A'-'F'范围内。
 
- 掩码生成: - 同上,生成掩码 - 0xFFFFFF或- 0。
 
- 值计算: - 若合法, - cu - 'A' + 11生成值- 11-16,与掩码按位与后保留原值。
- 若非法,结果为 - 0。
 
5. 合并结果
return -1 + part1 + part2;
逻辑解释:
- 合法数字: - part1为- 1-10,- part2为- 0→ 结果为- 0-9。
- 合法字母: - part1为- 0,- part2为- 11-16→ 结果为- 10-15。
- 非法字符: - part1和- part2均为- 0→ 结果为- -1。
6. 示例验证
- 输入 - '3':- part1 = 4(- 3 - '0' + 1 = 4),- part2 = 0→- -1 + 4 + 0 = 3。
 
- 输入 - 'A':- part1 = 0,- part2 = 11→- -1 + 0 + 11 = 10。
 
- 输入 - 'f'(转为- 'F'):- part1 = 0,- part2 = 16→- -1 + 0 + 16 = 15。
 
- 输入 - 'G':- part1 = 0,- part2 = 0→- -1 + 0 + 0 = -1。
 
7. 设计优点
- 无分支:避免条件跳转,防止侧信道攻击(对加密场景至关重要)。 
- 统一处理:小写字母通过位运算转为大写,简化逻辑。 
- 紧凑高效:纯算术和位运算,适合高频调用场景。 
8. 注意事项
- 依赖 ASCII 编码:假设字符编码为 ASCII(如 - 'A'-'F'连续)。
- 不可变内存访问:所有操作基于寄存器计算,无依赖数据的访存。 
总结
hex_to_bin 通过巧妙的位运算,将字符合法性检查和值计算合并为无分支的算术操作,兼顾安全性与效率,是处理十六进制字符的经典实现。

hex2bin
lib\hexdump.c
/**
 * hex2bin - convert an ascii hexadecimal string to its binary representation
 * @dst: binary result
 * @src: ascii hexadecimal string
 * @count: result length
 *
 * Return 0 on success, -EINVAL in case of bad input.
 */
int hex2bin(u8 *dst, const char *src, size_t count)
{
	while (count--) {
		int hi, lo;
		hi = hex_to_bin(*src++);
		if (unlikely(hi < 0))
			return -EINVAL;
		lo = hex_to_bin(*src++);
		if (unlikely(lo < 0))
			return -EINVAL;
		*dst++ = (hi << 4) | lo;
	}
	return 0;
}
EXPORT_SYMBOL(hex2bin);hex2bin 函数的作用是将十六进制的 ASCII 字符串转换为二进制数据。以下是分步讲解:
1. 函数参数
- u8 *dst:目标缓冲区,用于存储转换后的二进制数据。
- const char *src:输入的十六进制 ASCII 字符串。
- size_t count:要转换的字节数(每个字节对应 2 个十六进制字符)。
2. 循环处理每个字节
while (count--) {
    // 处理高 4 位和低 4 位
}
- 循环 - count次,每次处理一个字节(需要 2 个十六进制字符)。
3. 字符到二进制值的转换
hi = hex_to_bin(*src++); // 处理高 4 位 lo = hex_to_bin(*src++); // 处理低 4 位
- hex_to_bin函数:- 将单个十六进制字符(如 - 'A'、- '3')转换为对应的 4 位二进制值(0-15)。
- 若输入字符非法(非 - 0-9、- a-f、- A-F),返回负数。
 
- 字符处理顺序: - 先处理高 4 位(如 - 'A'对应- 0xA,左移 4 位后为- 0xA0)。
- 再处理低 4 位(如 - '3'对应- 0x3,直接合并)。
 
4. 错误检测
if (unlikely(hi < 0)) return -EINVAL; if (unlikely(lo < 0)) return -EINVAL;
- unlikely宏:提示编译器错误是低概率事件,优化分支预测。
- 若 - hi或- lo转换失败(非法字符),立即返回- -EINVAL。
5. 合并高 4 位和低 4 位
*dst++ = (hi << 4) | lo; // 合并为一个字节
- 将高 4 位左移后与低 4 位按位或,合并为一个完整的字节。 
- 例如: - hi = 0xA(- 'A'),- lo = 0x3(- '3') →- 0xA3。
 
6. 返回值
- 成功:返回 - 0。
- 失败:返回 - -EINVAL(输入包含非法字符)。
7. 注意事项
- 输入合法性: - src必须是有效的十六进制字符串,每个字节对应 2 个字符。
- 若 - src长度不足- 2*count,可能导致越界访问(需调用者确保)。
 
- 目标缓冲区大小: - dst必须有至少- count字节的空间。
 
- 大小写不敏感: - hex_to_bin应正确处理大小写(如- 'a'和- 'A'均转换为- 0xA)。
 
示例
假设 src = "1a2B",count = 2:
- 第一个字节: - hi = '1' → 0x1,- lo = 'a' → 0xA→ 合并为- 0x1A。
 
- 第二个字节: - hi = '2' → 0x2,- lo = 'B' → 0xB→ 合并为- 0x2B。
 
- dst结果为- 0x1A, 0x2B。
总结
hex2bin 逐字节将十六进制字符串转换为二进制数据,严格检测输入合法性,适用于需要反向解析十六进制字符串的场景(如协议解析、数据恢复)。调用者需确保输入格式正确,避免潜在的内存越界问题。

bin2hex
/**
 * bin2hex - convert binary data to an ascii hexadecimal string
 * @dst: ascii hexadecimal result
 * @src: binary data
 * @count: binary data length
 */
char *bin2hex(char *dst, const void *src, size_t count)
{
	const unsigned char *_src = src;
	while (count--)
		dst = hex_byte_pack(dst, *_src++);
	return dst;
}
EXPORT_SYMBOL(bin2hex);
这个函数的作用是将二进制数据转换为十六进制的ASCII字符串。以下是分步讲解:
- 函数参数: - char *dst:目标缓冲区,用于存储转换后的十六进制字符串。
- const void *src:源二进制数据的指针。
- size_t count:要转换的字节数。
 
- 类型转换: - 将 - src转换为- const unsigned char *类型,便于按字节处理。
 
- 循环处理每个字节: - 使用 - while (count--)循环处理每个字节,共处理- count次。
- 每次循环调用 - hex_byte_pack函数处理当前字节(- *_src++),并将结果写入- dst。
 
- hex_byte_pack函数的作用: - 将单个字节转换为两个十六进制字符,并写入 - dst。
- 例如,字节 - 0xAB会被转换为字符'A'和'B'。
- 更新 - dst指针,使其指向下一个写入位置(即每次写入后- dst增加2)。
 
- 返回值: - 返回最终的 - dst指针,指向转换后的字符串末尾的下一个位置。这便于连续调用或计算长度。
 
- 注意事项: - 目标缓冲区大小:调用者需确保 - dst有足够空间(至少- 2*count字节)。
- 字符串终止符:函数不会自动添加'\0',调用者需手动添加以确保字符串正确终止。 
 
示例:
 假设src指向数据0x1A, 0x2B,count为2:
- hex_byte_pack将0x1A转换为"1a",0x2B转换为"2b"。
- dst最终存储"1a2b",返回指针指向"b"之后的位置。
总结:该函数高效地将二进制数据逐字节转换为十六进制字符串,适用于需要灵活处理多段数据拼接的场景。