刚调了好几个小时的灵异bug,现象是启动后有时候会死机。这时候已经启动了看门狗,但狗没有起作用,系统一直卡在那不reset。
最后调出来是个空指针错误。空指针毕竟是编程比较常见的错误,空指针能导致看门狗无效,F1C100s的这个bug也是有点严重。
一番精简之后,找到了最小复现代码:
只要用一个基类指针,指向一块内容为0的区域,调用基类的第二个虚函数,就很容易使看门狗失效并死机。
这个bug可能跟编译器的具体实现(C++类对象内存布局)有关,我用的GCC,其他编译器不一定是这样。
class Base
{
public:
virtual void f() = 0;
virtual void g() = 0;
};
// main():
// 设置看门狗,5s的时长
uint8_t empty[1024];
memset(empty, 0, sizeof(empty));
Base* p = (Base*)∅ // 野指针,指向了一块内容全为0的内存
feedWatchdog(); // 喂狗
mdelay(10); // 等待超过1500ms则不出bug,等待0ms也不出bug,这之间都会遇到bug
p->g(); // 调用基类的第二个虚函数(第一个没问题),从这里就死机了。看门狗无效。
离线
继续研究。C++对象内存布局虽然没有固定标准,是编译器实现的,但基本是按照《深度探索C++对象模型》的样子。
一个带有虚函数的类的指针,指向的内存区域最开头应该是类的虚函数表指针。该指针指向一个数组,数组成员是函数指针。
在上面的例子中,当调用p->g();时,由于p指向的内存区域全是0,所以被当做是地址0x0处开始为虚函数表。
g()是类的第二个虚函数,所以把地址0x4当做函数指针。
打印出地址0x0附近的内存看:
0: 0xea00000d
1: 0xe59ff014
2: 0xe59ff014
3: 0xe59ff014
4: 0xe59ff014
5: 0xe59ff014
6: 0xe59ff014
7: 0xe59ff014
8: 0x80000260
看到0x4地址处的内容是0xe59ff014,接下来会把0xe59ff014当做函数地址去调用。就是经过这个非法函数调用后,看门狗失效并且死机!
复现此bug的代码可以再精简为:
typedef void (*FuncPtr)();
FuncPtr f = (FuncPtr)0xe59ff014;
f(); // 触发看门狗bug
继续做实验发现,并不是只有这一个特殊的地址调用会触发bug,实际上有很大范围的地址都能引起看门狗无效。
从 0xdba00000 ~ 0xffff4040,这个区域内的地址,如果赋值给函数指针来调用,都会引发看门狗失效bug!!小于0xdba00000或大于0xffff4040的地址调用,则能正常死机并触发看门狗。(具体地址会变化,还跟CPU频率有关,不是完全精确的范围。)
这个范围是很大的,也就是说如果程序写错了,还是会有挺大的可能性遇到此bug。
比如考虑下面这个常见的野指针访问代码,触发看门狗bug的概率并不小。
struct A
{
int data;
void (*func)();
};
A* a = (A*)malloc(sizeof(A));
// ...
free(a);
// a被释放,指向的地址重新分配后写入了其他内容
// ...
a->func(); // 野指针使用,危险!
离线
这两天临时出去有事了,所以没能按时试。
经测试RTT用GCC编译,没有这样的问题。WDT正确的工作了。rt_kprintf("start wdg test----------------------------------\n");
struct A* a = (struct A*)malloc(sizeof(struct A));
// ...
free(a);
// a被释放,指向的地址重新分配后写入了其他内容
// ...
a->func(); // 野指针使用,危险!
rt_kprintf("end wdg test----------------------------------\n");
我那个只是示意代码。是要向那块被释放的地址里写入合适的数据,使a->func的值为0xdba00000 ~ 0xffff4040。
离线
我也还不知如何解决。
离线
0xdba00000 ~ 0xffff4040,将这个虚地址段用mmu映射到sdram区,这个总可以完成的吧!这样cpu不会司机了,因为实地址是真实可用地址啊
多谢,简单测试了一下,这个方法是可以解决问题的。现在我是把从0xd0000000到0xffffffff的地址映射到0x90000000。
只测试了一些地址,也不敢保证所有的地址都没问题。毕竟不知道该bug产生的原理。不过确实是暂时解决了。
离线