本文主要是介绍栈回溯,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
首先必须明确一点也是非常重要的一点,栈是向下生长的,所谓向下生长是指从内存高地址->低地址的路径延伸,那么就很明显了,栈有栈底和栈顶,那么栈顶的地址要比栈底低。对x86体系的CPU而言,其中:
---> 寄存器ebp(base pointer )可称为“帧指针”或“基址指针”,其实语意是相同的。
---> 寄存器esp(stack pointer)可称为“ 栈指针”。
要知道的是:
--->ebp 在未受改变之前始终指向栈帧的开始,也就是栈底,所以ebp的用途是在堆栈中寻址用的。
—>esp是会随着数据的入栈和出栈移动的,也就是说,esp始终指向栈顶。
寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(地地址)
这是来自apue里一张经典的c程序内存分布图:
基于多核异构处理器B4860平台的实例,对栈回溯功能做如下描述,仅供参考。假设函数A调用函数B,我们称A函数为"调用者",B函数为“被调用者”则函数调用过程:
(1)先将调用者(A)的堆栈的基址(ebp)入栈,以保存之前任务的信息;
(2)然后将调用者(A)的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者B的栈底);
(3)然后在这个基址(被调用者B的栈底)上开辟(一般用sub指令)相应的空间用作被调用者B的栈空间;
(4)函数B返回后,从当前栈帧的ebp即恢复为调用者A的栈顶(esp),使栈顶恢复函数B被调用前的位置;然后调用者A再从恢复后的栈顶可弹出之前的ebp值(可以这么做是因为这个值在函数调用前一步被压入堆栈)。这样,ebp和esp就都恢复了调用函数B前的位置,也就是栈恢复函数B调用前的状态。
以DSP侧异常为例说明,如下所示:
1.Task创建时,会为每个Task分配好Task的栈,并指定当前Task栈的size。
其中,栈是向下生长(高地址 -> 低地址),因此文中所描述的“top_of_stack”实则为栈的底(EBP)。
2.DSP出现异常时,会记录当前函数的上下文信息,如PC,SP,os_context_info,top_of_stack,etc.可结合这些关键信息及Map文件进行栈回溯,找到出现异常函数的上下文。
3.PC指针,通常是用来指向当前运行指令的下一条指令的指针。计算机取指令也就是根据PC指针所指向的那条指令来进行取指的,接着就是译码等操作。异常时的PC,归属于当前栈帧,要想获取到异常函数的多级调用关系,就需要知道当前栈帧的栈顶(根据函数的入栈会动态变化)和栈底。
4.得到完全任务栈的栈帧,以及当前错误的PC,以及Map文件,就可以获取到函数调用的上下文,描述如下:
(1)错误的PC不一定指向当前出现异常时函数,但一定会指向出现异常函数的栈帧内。PC缺省是uint32_t类型,这里我们对PC截断处理,PC的高16bit+offset就会指向出现异常时的函数,offset表示当函数压栈时的偏移地址。
(2)函数的压栈操作是从高地址到低地址方向的一个生长,换句话说,函数的调用关系是从高地址往低地址方向调入的一个过程,因此,为了找到出现异常时函数调用的上下文,就要从低地址往高地址方向反推。
注:在一体机SC3900中,函数的压栈是从低地址到高地址。其中,嵌汇编操作描述如下:
以CPU侧异常为例说明,结合coredump文件即核心转储,进行GDB调试即可,本文不过多描述。
这篇关于栈回溯的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!