本文转自公众号,欢迎关注
https://mp.weixin.qq.com/s/uzaglftdban8wyr84yaiiw
1. 前言rtos的环境开发中,栈的溢出检测是一个重要的工作。栈溢出检测我们可以借助硬件的mpu等实现,也可以使用软件检测。这里分享freertos中的实现。这里基于cortex-m4硬件平台,一些具体的代码就未贴出了,顺便介绍了一下cortex-m4栈相关的基础知识。
2. 栈初始化2.1任务启动前栈复位后汇编代码
import __mainldr r0, =systeminitblx r0ldr r0, =__mainbx r0endp会进入__main将栈内容写为0。该部分由编译器产生代码实现。
栈的位置是链接脚本中指定。
2.2任务栈xtaskcreate -> prvinitialisenewtask将任务栈填充为tskstack_fill_byte = ( 0xa5u )
然后调用pxportinitialisestack初始化任务栈上下文
任务初始化时
高地址
->任务切出时栈指针
低地址
任务运行一段时间后
高地址
已使用部分
->任务切出时栈指针
未使用部分
低地址
对应实际中断后的栈如下:
3.任务切换vportsvchandler函数模拟中断返回
__asm void vportsvchandler( void ){preserve8/* get the location of the current tcb. */ldr r3, =pxcurrenttcbldr r1, [r3]ldr r0, [r1]/* pop the core registers. */ldmia r0!, {r4-r11, r14}msr psp, r0isbmov r0, #0msr basepri, r0bx r14}其中
ldr r3, =pxcurrenttcbldr r1, [r3]ldr r0, [r1]是获取栈指针r0即指向任务栈表中r4位置
ldmia r0!, {r4-r11, r14}是恢复r4-r11和portinitial_exc_return
msr psp, r0,更新栈指针,指向指向任务栈表中r0位置
bx r14模拟中断返回 恢复r0-r3 r12 pc xpsr(硬件实现)。
由于r14=portinitial_exc_return=0xfffffffd
根据手册描述
返回时使用psp栈,返回后使用psp栈。与初始化对应。
4.任务return栈初始化时lr = prvtaskexiterror 进入子函数时lr会入栈,退出子函数时lr出栈。
所以如果任务不是while(1)形式而是在最后return则最终会进入
prvtaskexiterror执行。一般rtos的任务都是while(1)结构 不return。
5.栈指针复位后使用msp,任务根据返回时的lr值portinitial_exc_return使用psp见“2.任务切换”。
中断中固定使用msp。
6.栈使用中断函数和mian使用中断向量第一个字指向的栈区域。
任务使用任务栈。
在os启动前默认时使用msp,根据中断向量的第一个字加载msp
硬件实现,或者bootloader跳转到应用时配置。
启动os时prvstartfirsttask,又重新将中断向量第一个字加载到msp。
今后中断就使用msp对应的栈,即os启动前main使用的栈。
因为main一去不复返,所以这里覆盖使用main时的栈,这样可以节约内存。
/* use the nvic offset register to locate the stack. */ldr r0, =0xe000ed08ldr r0, [r0]ldr r0, [r0]/* set the msp back to the start of the stack. */msr msp, r07栈检测7.1任务栈检测栈初始化时全部初始化为0xa5,运行一段时间后栈顶部分使用变为其他值。
检查栈底有多少连续的0xa5即可知道栈剩余多少。
freertos提供接口函数uxtaskgetsystemstate获取栈信息。
shell中输入ps查看(具体代码未贴出)。
7.2中断栈/main函数栈检测根据4.和5.的分析,中断和main函数栈使用中断向量第一个字对应的栈区域。
由于__main.c会将栈内容清除为0.所以在启动第一个任务前将栈重新填充为0xa5。
有__main.c之前将栈填充为0xa5又会被清除为0,将填充代码放在了任务启动前prvstartfirsttask函数中。这样main函数到prvstartfirsttask之前的栈使用大小不可监控。
只能监控后续中断使用的栈大小。如果要检测main函数栈使用则要将填充代码放在main函数执行的第一条代码后,需要嵌入汇编影响代码阅读和可移植性,所以不按这种方式。
实际上main函数栈溢出也没关系 ,但是编程必须要求提供手动初始化变量的代码,而不是依赖于编译器的初始化。
比如有一个变量static int i =0;
编译器提供代码在__main中会对该变量初始化,如果main函数栈溢出覆盖了这个变量的值。
那么在任务函数执行时提供 void mode_init(void)函数
手动再次初始化该变量i=0.
就可以避免问题。
建议在模块任务启动时对属于模块的全局变量再次提供”构造函数”手动初始化。
修改freertos底层移植代码
__asm void prvstartfirsttask( void ){preserve8/* use the nvic offset register to locate the stack. */ldr r0, =0xe000ed08ldr r0, [r0]ldr r0, [r0]/* set the msp back to the start of the stack. */msr msp, r0//;初始化栈为0xa5a5a5a5 mov r2,#0xa5a5a5a5ldr r0, =0x4000mrs r1, msp subs r1,r1,#4loop str r2,[r1,#0x00]subs r0,r0,#4 subs r1,r1,#4cmp r0,#0x00bne loop增加检测代码
其中0x4000需要根据实际设置的栈大小修改。0xe000ed08为中断向量表地址。
/****************************************************************************** fn uint32_t bsp_sys_getstack(void)* brief 获取栈大小.* note .* return 剩余栈字节数******************************************************************************/uint32_t bsp_sys_getstack(void){uint32_t size = 0;uint32_t* p = (uint32_t*)(*(uint32_t*)(*(uint32_t*)0xe000ed08) - 0x4000);while(*p == (uint32_t)0xa5a5a5a5){size += 4;p++;}return size;}shell中输入stack命令查看(具体代码未贴出)
8. 总结简单来说软件实现栈检测,就是将栈初始化为固定值。如果栈有使用则初始化值会变化,软件从栈底开始查找看剩余多少内容没有被改写就是剩余多少栈未使用。软件检测不是可靠的,因为溢出可能是跳跃的,即栈底一部分实际未用指针直接跳到了更后面的溢出位置,软件检测还存在延迟,所以软件检测一般可用于评估栈使用大小。使用硬件mpu更可靠,设置只有本任务只能访问本任务栈对应的空间,一旦访问其他空间就可以触发mpu中断这样更及时可靠检测。
小米王者归来,有望成为全球第二大手机制造商
数字孪生智慧海上风电场Web3D可视化运维平台
信号隔离器功能及工作原理分析
对于RFID的组件RFID读写器和电子标签的工作原理,你了解嘛?
E5071C ENA射频网络分析仪多端口网络分析解决方案
Freertos栈检测
农用自动供水器电路图
自然光电视亮相,它能挑战激光电视的大屏霸主地位吗?
行业方案|数商云医疗器械行业SRM供应商协同管理解决方案
构建一个简单的提醒报警电路
博通推出支持Enterprise2.0应用的新StrataXGS系列交换产品
2018年半导体市场将呈现什么样的发展趋势
通信5G转型,预计2022年声波滤波器市场规模将达160亿美元
戴尔U2520DR上架国内电商平台 25英寸2K分辨率+HDR 400认证
什么是运动控制卡以及其应用领域?
Nexperia推出首款支持USB4标准的ESD保护器件
特性阻抗怎么计算
在台式机变得和家具一样重要的时代 它正悄悄且快速的消失
如果未来的计算界面都采用语音控制,那些失聪或失语者该怎么办?
OPPO携手美的将重磅发力打造自在智美生活