在sim环境下调个bug
好久没更新。。。
昨天反馈来一个bug,今天在sim上重现了,定位过程还有点意思,记录下。
Here is this bug.
Becasue, previously, there was a heap-related bug in the cmn_init() function.
具体不说了,就是这里的malloc和其它一个模块里自带的malloc重名了,结果编译器用了自带的malloc,而这时自带的malloc还没初始化,导致malloc失败返回NULL。
解决了之前的bug,后面还是在cmn_init()里出了HardFault,所以还是怀疑和heap什么地方有关。
因为没有打开tarmac,所以信息就自己翻。
pc0_iss, ia0_ex1, ia0_ex2, ia0_wr, ia0_ret还有对应的pc1_iss, ia1_ex1, ia1_ex2, ia1_wr, ia1_ret是cortex-m7里双发射的两条流水线里执行阶段的pc值,这里叫ia,instruction address?
因为出错也不知道是在那个pipeline stage,所以还得具体看是啥指令,但能知道个大概范围。
从出错信息能看到有HardFault,是exception 3。
这时候看nvic_int_nxt_isr_i[7:0],这里给出了exception或者interrupt number。可以看到前面的值都是2A和2F,减去16(exception),就是1A和1F,在这实现里是uart0和dmac的外部中断,是正常的。
等到这个值变成3的时候,就是发生HardFault的地方了。
对着代码看,大概觉得是30049A46这的这条指令,是条load指令。
很有可能是load个无效地址导致HardFault,所以接着看r3和r6寄存器这时候都是什么值。
在dpu里找到rf。
r6里是0x161,r3的值是0x3B00003E,很奇怪,这个地址没有sram,所以load这个值导致HardFault没错了。
结合代码,感觉是malloc出来的buff是个靠近0地址的位置,这里是rom,看下内容,都解释通了。
剩下的问题就是heap为啥会这样, heap base在哪。
因为是代码里直接就用的malloc,所以是crt里自带的malloc,好像用的libc是newlib?
crt里的malloc没有个heap init这类的函数,是在malloc里调用_sbrk()分出空间来给heap用。
_sbrk的实现貌似很简单,网上找了一段:
https://community.silabs.com/s/article/malloc-and-sbrk?language=en_US
#include <sys/stat.h>
extern char _end; /**< Defined by the linker */
extern char __HeapLimit; /**< Defined by the linker */
caddr_t _sbrk(int incr)
{
static char *heap_end;
char *prev_heap_end;
if (heap_end == 0) {
heap_end = &_end;
}
prev_heap_end = heap_end;
if ((heap_end + incr) > (char*) &__HeapLimit) {
//errno = ENOMEM;
return ((void*)-1); // error - no more memory
}
heap_end += incr;
return (caddr_t) prev_heap_end;
}
在编译的代码里找_sbrk(),逆出来看也差不多。
代码里用到3000182c和30001830这两个变量,3000182c应该就是heap base。
这个名字是叫heap_end差不多的一个,linker脚本里给出的,不知道后面的0是什么意思。
最后一看,确实出问题的这个case的linker脚本这个变量没给复制,默认是0了。链接的时候不知道是不报错还是warning了没看到,感觉是没有。
所以就是这个bug,导致heap的base在0,分配出来的buffer全是rom里的地址,而且碰巧heap和分配出的buffer都没有清0.
rom里这些值其实是vector table里重复的dumb function。
这种crt里的函数用的时候得小心,因为没有显式的初始化函数,很容易就丢掉点东西。
这个代码框架不是我搞的,所以对crt heap这些linker脚本里相关的也是不了解,调了一天。。。
这里有篇跟linker有关的。
https://interrupt.memfault.com/blog/how-to-write-linker-scripts-for-firmware#complete-linker-script
还有后续,当时以为heap_end.0是heap base,在linker脚本来给这个名字就行了,也没仔细看_sbrk()的汇编。
实际上heap_end.0是个static的局部变量,所以linker脚本给不了这个,而且也不是heap base。
heap base实际上是0x30001830,每次调用_sbrk()一点点的分配后面的内存。所以heap base是没问题的,这个end也在linker脚本里给了。
PROVIDE(end=.);
但上次的HardFault,node_tree的值怎么是0x161?
所以要看下malloc是不是正常,分配出来的buffer的地址是在什么位置。
Here is calling malloc inside cmn_init() for the first time. The position 0x300499f6 is where the malloc returns and the r0 contains the buffer address allocated.
So, check out the r0 value. 0x30003EC8 looks genuine. The malloc works fine, so maybe the node_tree gets the buffer right at the beginning, but get messed up somehow later.
看了下,node_tree这个指向机构体的指针是在最上层的函数里分配在栈上的。
It means that the stack get overflowed and node_tree get corrupted.
Let’s see where node_tree* pointer actually gets the buffer.
r4 is the allocated buffer address. r6 is the node_tree* pointer address and it is on the stack.
They are quite close. I thought the stack was set up at the end of SRAM0, which is 0x3003FFFC. But actually the stack was at 0x30004000, very close to the heap. (The fault is that the heap is only assigned 3KB space)
From the wave dump, the _sbrk() was called four times.
At the position 0xD224, where is the _sbrk() at, r0 contains the first parameters passed in, and, in this case, it is 0x418.
30001830 + 418 + 3B8 + 1000 + 1000 = 30004000
The heap obviously overflowed the stack.
So, set the stack at 0x3003FFFC, and the problem was solved.