概要:一个例子,两个思路,一些思考。
问题现象
struct bmm150_dev {
/*! Chip Id */
uint8_t chip_id;
/*! Device Id */
uint8_t dev_id;
/*! SPI/I2C Interface */
enum bmm150_intf intf;
/*! Bus read function pointer */
bmm150_com_fptr_t read;
…//此处为省略
};
//定义一个结构体变量
struct bmm150_dev MagDev ;
//检查不BUG的地方
static int8_t null_ptr_check(const struct bmm150_dev *dev)
{
int8_t rslt;
if ((dev == NULL) || (dev->read == NULL) || (dev->write == NULL) || (dev->delay_ms == NULL) ) {
/* Device structure pointer is not valid */
rslt = BMM150_E_NULL_PTR;
} else {
/* Device structure is fine */
rslt = BMM150_OK;
}
return rslt;
}
注:以上代码是由博世的BMM150的开源驱动。程序是在Stm32上运行的。
注:推荐使用NotePad,然后用Ctrl+F进行查找
在传感器通过MagDev句柄读取传感器数据,在调用API函数周期性读取传感器函数时,通常都会判断句柄是否有效。而出现的现象是程序运行一会儿后,发现“(dev->read == NULL)”为真了,而MagDev.read是在程序初始化时就不再改变的值。也就是说该值被意外篡改了。
查找问题引发源的方式1:
程序还没调用操作系统,但此时已经有开启了几个中断了。
1、首先我先把所有中断都屏蔽了,只运行主函数,现象不再现。
2、每次只打开一个中断,在调试模式下依次排查。问题复现并找到原因。
我定义了如下变量
static double AGdataSum[6] = {0};
但由于需求改变,实际上使用了7个double值,于是由于数组越界,意外改变了下一个静态/全局变量的值。
查找问题引发源的方式2:
==============================================================================
Image Symbol Table
Local Symbols
Symbol Name Value Ov Type Size Object(Section)
…
adcBuf 0x240041a2 Data 14 cfileObject1.o(.bss)
AGdataSum 0x240041b0 Data 56 cfileObject1.o(.bss)
.bss 0x240041e8 Section 72 magnetrun.o(.bss)
.bss 0x24004230 Section 192 cfileObject2.o(.bss)
.bss 0x240042f0 Section 1044 cfileObject3.o(.bss)
…
Global Symbols
Symbol Name Value Ov Type Size Object(Section)
…
sensorData_value 0x24004068 Data 112 cfileObject4.o(.bss)
MagDev 0x240041e8 Data 72 magnetrun.o(.bss)
next_value 0x24004230 Data 64 cfileObject2.o(.bss)
…
==============================================================================
注:此表是在BUG还没修改前的表,表中部分内容已经匿名处理。“…”为省略部分。
其实我一开始就怀疑是数组越界,于是到map文件中查找。但以为编译器将全局变量一次存在RAM中,找到变量MagDev后面的 变量next_value 的相关程序,并未发现有什么问题。后来才知道,编译器分配全局变量和静态变量的内存时,编译器内存不是先分配静态变量内存后分配全局变量的方式进行的,而是按文件搜索顺序分配内存。
变量 AGdataSum的地址加上其内存大小后就是变量MagDev 的首地址了。显然如果变量 AGdataSum操作第7位时正好改变了变量MagDev的值。
0x240041b0+56=0x240041e8
MAP文件的说明:
1、0x2400xxxx表明存储空间在RAM;若为0x0800xxxx,则表明存储空间为Flash。
2、静态变量区的符号名为“.bss”的部分可能会在全局变量区写出具体变量名。
思考:
1、局部变量使用的是栈,若出现错误,一般会使局部逻辑出错,也一般不会导致夸逻辑区域的错处。
2、计算资源不紧张的话,函数尽量每次使用函数指针时都判断一下指针是否为空,当然能让本文例子那样判断出BUG来也是要有一定运气的。
3、谨慎使用数组,谨慎使用指针,多写些检验程序,提高程序鲁棒性。
4、虽然函数指针能够使程序的结构更加合理,但在不考虑外界因素(如电磁干扰)的影响下,程序跑飞的原因主要来自于函数指针。然而,并不推荐因为函数指针这一缺点而不使用函数指针。
5、在可确定函数指针指向的函数地址的具体范围时,在该范围判断指针指向的地址是否在范围内,比判断指针是否指向NULL更可靠。(一般NULL判断只能避免未初始化时使用函数指针。)
#include
#define SYS_RAM_ADR_MIN 0x20000000UL
#define SYS_RAM_ADR_MAX 0x24080000UL
static bool ptr_check(const void* pntr)
{
bool rslt;
if ((uint32_t)pntr >= SYS_RAM_ADR_MIN && (uint32_t)pntr <= SYS_RAM_ADR_MAX)
{
rslt = true;
}
else
{
rslt = false;
}
return rslt;
}