1、引言
最近在编写代码时,出现了这样一个 bug。程序一跑,系统就崩溃了,报错是 bus error。
目标平台:ARM32
最终定位到出错的代码片段:
*((uint32_t *)ptr) = id;
这里的 ptr 是一个非 4 字节对齐的地址!!!
2、解决方法
使用 Linux 下的 put_unaligned_le32
接口,用于向非对齐(unaligned)内存地址写入一个 32 位小端(Little-Endian)数据。
代码修改如下:
put_unaligned_le32(id, ptr);
put_unaligned_le32
函数实现如下:
#define __put_unaligned_t(type, val, ptr) do { \struct { type x; } __packed * __put_pptr = (typeof(__put_pptr))(ptr); \__put_pptr->x = (val); \
} while (0)static inline void put_unaligned_le32(u32 val, void *p)
{__put_unaligned_t(__le32, cpu_to_le32(val), p);
}
逐句拆分讲解:
struct { type x; } __packed * __put_pptr = (typeof(__put_pptr))(ptr);
- __packed:__packed 是 GCC 的扩展,用于告诉编译器不要对结构体进行字节对齐,即使它打破对齐规则
- typeof:是 GUN C 提供的一种特性,可以获取变量或者表达式的类型
- 整体来说,使用到了 C 语言的匿名结构体
- __packed 是修饰 struct 的
- * 表示 __put_pptr 是一个指针,指向这个紧凑的结构体
- (typeof(__put_pptr))(ptr),是一个类型转换(强制类型转换)
__put_pptr->x = (val);
- 真正的数据写入操作。
- 写入会在字节级别进行,而不会使用直接的内联 32 位或 64 位存储指令(LDR/STR)。
2.1 反汇编代码对比
对于以下代码:
*((uint32_t *)ptr) = id;
不使用 put_unaligned_le32
接口,对应的汇编:
95d3480: e8830006 stm r3, {r1, r2}
使用 put_unaligned_le32
接口,对应的汇编:
95d3488: e5431008 strb r1, [r3, #-8]95d348c: e5432004 strb r2, [r3, #-4]
可以看到,从 STM
指令换成了 STRB
指令。
3、拓展知识
处理器访存指令所发出的内存地址如果不是被访问的数据类型位宽的整数倍,称为非对齐访问。处理器访存指令支持字节访问(8bit),半字访问(16bit),字访问(32bit),双字访问(64bit)等,处理器的存储空间最小单位为字节,因此除了字节访问,其余访存指令均存在非对齐访问可能性。对于处理器硬件而言是否支持非对齐,如果出现非对齐访问时如何操作,需要有明确的规定。
还是以 ARMV7 架构为例,从下面的图我们也可以看到,之前出现了非对齐访问异常,就是因为使用了 STM
指令。该指令必须要 Word 对齐,也就是 4 字节对齐。
修改后的代码,使用了 STRB
指令进行字节访问,不需要对齐。
关于处理器具体的字大小,