ADM中文网

CENS中文网    你的位置:ADM中文网 > CENS中文网 >

Freertos栈检测

发布日期:2025-01-04 14:51    点击次数:88

本文转自公众号,欢迎关注 https://mp.weixin.qq.com/s/uzaGLFTDBAn8wyR84yaiIw1. 前言RTOS的环境开发中,栈的溢出检测是一个重要的工作。栈溢出检测我们可以借助硬件的MPU等实现,也可以使用软件检测。这里分享Freertos中的实现。这里基于Cortex-M4硬件平台,一些具体的代码就未贴出了,顺便介绍了一下Cortex-M4栈相关的基础知识。2. 栈初始化2.1任务启动前栈复位后汇编代码IMPORT __main LDR R0, =SystemInit BLX R0 LDR R0, =__main BX R0 ENDP 会进入__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, =pxCurrentTCB ldr r1, [r3] ldr r0, [r1] /* Pop the core registers. */ ldmia r0!, {r4-r11, r14} msr psp, r0 isb mov r0, #0 msr basepri, r0 bx r14 } 其中ldr r3, =pxCurrentTCB ldr r1, [r3] ldr r0, [r1] 是获取栈指针r0即指向任务栈表中R4位置ldmia r0!, {r4-r11, r14}是恢复R4-R11和portINITIAL_EXC_RETURNmsr 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, =0xE000ED08 ldr r0, [r0] ldr r0, [r0] /* Set the msp back to the start of the stack. */ msr msp, r0 7栈检测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, =0xE000ED08 ldr r0, [r0] ldr r0, [r0] /* Set the msp back to the start of the stack. */ msr msp, r0 //;初始化栈为0xA5A5A5A5 MOV R2,#0xA5A5A5A5 LDR R0, =0x4000 MRS R1, MSP SUBS R1,R1,#4 LOOP STR R2,[R1,#0x00] SUBS R0,R0,#4 SUBS R1,R1,#4 CMP R0,#0x00 BNE 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中断这样更及时可靠检测。 审核编辑:汤梓红

上一篇:美团服务员ios版

下一篇:纳斯达克CEO:数字货币ICO在“利用”散户投资者

Powered by ADM中文网 @2013-2022 RSS地图 HTML地图

Copyright Powered by365建站 © 2013-2024

top