本文转自公众号欢迎关注
https://mp.weixin.qq.com/s/ErIa2ss2YZLGYbSwoJEzog
一. 前言
内存未对齐访问问题这个已经是老生常谈的问题了, 由于LWIP的堆管理中也用到了地址(指针)强制转换所以也会遇到这个问题。对于老手比较容易发现,对于新手可能会比较疑惑。所以也单独分享一个案例吧,权当一个小的check list的case。
二. 问题
Lwipopts.h中MEM_ALIGNMENT可以配置堆对齐大小,有问题时是配置为1
#define MEM_ALIGNMENT 1U
异常时打印寄存器如下,当然不同平台异常时如何获取上下文信息方式不一样,不在本文讨论范围内,我这里是RISC-V环境。
看到打印的mepc是0x20006C88,异常原因是地址未对齐。
所以是在运行0x20006C88时进入了异常,当然这个地方不一定是原始问题所在点,异常可能是跑了很久才出现的。
所以先在这里打个断点试试
可以看到是pbuf.c的代码中,所以可以怀疑是内存池或者堆的问题。
我们运行发现断点并不能触发,之前就已经异常了,所以只能跟代码逐渐缩小范围确认问题的。一般采用的方式是,逐步断点或者打印或者删除代码,逐步缩小范围的方法。
可以借鉴一些二分的思想,加快定位。
这里还是从pbuf开始,先找到相关代码上层函数处,断点
b pbuf_init_alloced_pbuf
看到异常前是可以停下来的
看到此时p的值是0x28201406
查看如下汇编代码可知
sw zero,0(a0)即对应代码p->next = NULL;
sw是word操作指令,但是地址a0不是word对齐,所以会产生异常
再si单步确实进入异常
所以问题确认了。
因为堆是分配的一块区域,每一块区域的开始地址对齐值就是上面设置的对齐大小,分配区块后作为其他模块使用,比如pbuf使用,前面部分作为管理结构体
struct pbuf 操作,所以实际是将一个区块地址强制转为了结构体指针。
此时访问结构体成员,编译器是自动按照自然对齐生成汇编指令的,因为编译器并不知道你的对齐要求,所以如果系统不支持对应的指令非对其访问就有问题,但是有些系统对应的汇编指令的行为支持不对齐访问那么就没有问题。
当然出于可靠性设计,建议不要进行强制类型转换,比如MISRA标准里的规范就是如此。
如果代码要做到兼容性可靠性非常好就要注意这个问题,此时不能使用强制类型转换,而是使用字节序手动拼接得到成员的值。
但是出于灵活性考虑,很多协议栈的设计都是直接使用强制类型转换的,所以这时用户就需要注意,比如这里我们可以配置#define MEM_ALIGNMENT 4U
来保证上述分配出来的地址p是4字节对齐的,所以按照偏移,其成员也是4字节对齐的,sw指令操作的就是4字节对齐的成员,就不会有问题。
三. 总结
以上分享一个简单的案例,目的是提醒下要注意类似问题,尤其有指针强制类型转换的要注意对齐问题。问题不难,也不复杂,但是可以作为check list的case可以作为检查项目。
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !