1、RISC-V通用极简栈回溯组件 RvBacktrace,RT-Thread 王顺,RISC-V Universal Stack Backtrace Component,组件介绍与使用举例,https:/,组件链接:,什么是函数调用栈?,调用栈展现了程序运行到某个位置时的函数调用关系。,什么是栈回溯?,从当前执行的指令找到上一步进行函数跳转的指令,直至运行至入口函数,对于用户需要的是指令对应的跳转的函数的函数名。,调用栈是正向显示函数调用关系,栈回溯是逆向显示函数调用关系。,addr2line工具,通常嵌入式的固件不包含函数名称信息。通常软件仅输出被调用函数指针,使用PC端的工具从ELF格式的文
2、件查找对应的函数名称,将被调用函数的名称逆向显示。GUN提供了addr2line工具,这个工具与GCC编译工具在同一目录,用户可以在自己的工具链安装路径找到该工具。,rvbacktrace组件仅提供一个用户API:void rvbacktrace(void);,用户可在需要栈回溯的地方调用该API,则会使用printf逆向输出被调用函数的地址,以及addr2line工具支持的命令。同样可以将该API适配至当前系统的断言API,异常处理函数中。,示例:在RT-Studio中点击RT-Thread Setting在搜索框输入rvbacktrace,选择latest版本,extern char*_e
3、text;extern char*_stext;,组件默认使用软件遍历方式,该方式的查找范围依赖上述符号,上述符号需要添加在链接脚本代码段的始末位置,用于判定PC是否在合理位置。,串口终端输出的栈回溯信息:,使用addr2line工具,输入终端输出的命令,将组件获取的地址对应到具体的文件的函数名称及其所在行号。,组件实现原理剖析,rvbacktrace组件仅提供一个API:void rvbacktrace(void);,默认使用软件栈回溯的方式,用户可以直接在裸机或RTOS环境中使用。,rvbacktrace组件支持两种栈回溯方式:(1)硬件实现方式:依赖GCC编译参数-fno-omit-fr
4、ame-pointer,这种方式会占用一个内核寄存器FP,但回溯代码的实现较为简单,具有节省内存易于理解的优点。(2)软件实现方式:根据RISC-V函数调用规范,逆向遍历指令查找函数调用堆栈指针与被调函数指针。,硬件实现方式原理,-硬件实现方式即依赖内核的fp寄存器,在编译时需要添加编译参数-fno-omit-frame-pointer,-当开启上述参数后s0便不能作为普通寄存使用。,-ra:函数返回地址寄存器。被调函数执行完毕后的返回地址。,-sp:堆栈指针寄存器。入栈与出栈操作的指针,指向栈顶。,一个概念与三个重要的寄存器:,-fp:栈帧指针寄存器。栈顶的对端,当前函数栈的初始指向。指向栈
5、底。,-栈帧:保存调用该函数与函数内部的开销。上一级函数的栈帧指针与函数返回地址即保存在这里,末端函数不继续调用,无需保存返回地址。,示例:以RV32为例,栈回溯,软件实现方式原理,函数调用规范:函数调用规范用于描述父函数/子函数是如何编译链接的,例如父函数与子函数的之间的调用关系的约定,如栈的布局,参数的传递等。,所以编译出来的程序遵循规范且有迹可循!,下述是未使用-fno-omit-frame-pointer 编译参数生成的代码的反汇编举例。,可以看出上述函数的反汇编代码对栈的操作与返回地址的操作的相对位置与行为是一致的。,结论:-操作栈的指令的行为是一致的-操作栈之后会将返回地址寄存器的
6、至保存至栈空间,方法:-调用rvbacktrace函数,从当前pc指向的指令开始反向遍历,查找符合要求的修改栈指针的指令,根据指令参数计算出栈帧大小。-修改栈指针的指令的下一条指令会将ra寄存器的值保存在栈帧中。-ra寄存器保存的是函数跳转指令,根据其向可逆向求出pc。,方法:-调用rvbacktrace函数,从当前pc指向的指令反向遍历,查找符合要求的修改栈指针的指令,根据指令参数计算出栈帧大小。-修改栈指针的指令的下一条指令会将ra寄存器的值保存在栈帧中。-ra寄存器保是