根据栈帧FP进行backtrace,threadx会在任务调度前将sp指向申请的任务栈空间,任务栈空间可在链接脚本中指定。
随便写个例子如下:
void func0()
{
int i = 6;
i++;
}
void func1()
{
*(uint32_t *)0x80000000 = 1; //访问一块不在mpu范围内的内存会发生Abort
func0();
}
void func2()
{
func1();
}
void func3()
{
func2();
}
func3在任务中调用;反汇编如下:
据此可画出调用关系图:
如上图所示,每个函数的堆栈区中的FP指向它的调用这的LR地址,对于func1、func2、func3三个非叶子函数而言,寄存器R11(即fp)指向它们各自堆栈区的LR地址(add fp,sp,#4实现),对于叶子函数func0而言,寄存器R11(即fp)指向它堆栈区的FP地址(add fp,sp,#0实现,最后一级函数不调用别的函数,因此LR不用入栈)。
综上,当根据FP进行backtrace的时候,需要区分叶子函数和非叶子函数。叶子函数func0的FP寄存器指向func0堆栈中的FP值,该FP指向func1的堆栈中的LR地址,以此类推。非叶子函数func1的FP寄存器指向func1堆栈中的LR地址,由此可直接获取LR值,以此类推。
那么如何区分crash的时候所在函数是叶子函数还是非叶子函数呢,可通过FP的值与任务栈的起始地址做比较得到:
如果是非叶子函数,FP指向的是LR,那么*fp得到的值一定存在text段,该值小于start;如果是叶子函数,FP指向的是任务栈中的FP,那么*fp得到的是上个栈帧,在任务栈中,该值大于start。由此可分情况进行backtrace。
另外,对于在叶子函数func0中进入异常的情况,由于func0的堆栈中并没有将LR入栈,该LR指向func1中,所以要想得到func1,需要在异常中(各个异常有各自的LR和SP)将异常模式切换成出异常之前的模式(通过CPS指令),这时候的LR就指向func1,如此能得到完成的backtrace。