前言
相信无论大佬还是小白,都在开发中遇到过栈溢出的问题,而且因为没有明确日志,难以定位问题的根源。Stack Overflow 社区的命名也由此而生,而到现在虽然Stack Overflow因为大模型已经几乎要凉凉了,但是栈溢出的问题仍然困扰着许多开发者。
正好RT-Trace发布了他们的内测新功能——栈保护,与此同时,采集时长也有了大幅提升,在我的板子上甚至可以稳定采集 三分钟。那我们就来看看他的效果和实用性吧。
测试环境
星火一号开发板
rt-trace工具
使用方法和效果

可以看到使用方法顺延了先前trace功能的配置界面,加上了两个框,来选择需要保护的线程和需要被保护的栈底空间大小,使用起来还是很简单的

配置成功后就可以去trace_view界面测试了,这里我星火一号上跑了一个递归爆栈测试程序,没有优化
#include#include#include#ifndefRT_USING_NANO#include#include#include#include#endif/* RT_USING_NANO */rt_thread_tstack_thread =NULL;#defineTHREAD_PRIORITY 25#defineTHREAD_STACK_SIZE 512#defineTHREAD_TIMESLICE 5intmain(void){ while(1) { rt_thread_mdelay(500); }}#defineMAX_RECURSION_DEFAULT 5 // 默认最大递归次数staticintmax_recursion = MAX_RECURSION_DEFAULT;// 可控制的最大递归次数void*get_stack_top_addr(){ return(void*)((uint32_t)stack_thread->stack_addr + stack_thread->stack_size);}void*get_stack_bottom_addr(){ return(void*)((uint32_t)stack_thread->stack_addr);}/*** 递归栈溢出测试函数* 每次递归仅创建一个32位变量* @param depth 当前递归深度*/voidrecursive_stack_overflow(intdepth){ volatileuint32_ta =0x12345678;// 创建一个32位变量并赋值 staticvoid*last_a_addr =NULL; // 获取栈边界地址 void*stack_bottom_addr =get_stack_bottom_addr(); void*stack_top_addr =get_stack_top_addr(); if(depth !=1) { uint32_tstack_used = (uint32_t)last_a_addr - (uint32_t)&a; rt_kprintf("[Depth:%2d] var_a addr:0x%08X,stack_used: %d\n ", depth, &a, stack_used); } else { rt_kprintf("[Depth:%2d] var_a addr:0x%08X\n ", depth, &a); } // 终止条件:达到最大递归次数 if(depth >= max_recursion) { rt_kprintf("[Depth:%2d] 已达到最大递归次数 %d,终止递归\n", depth, max_recursion); return; } last_a_addr = (void*)&a; // 短暂延迟,便于观察输出 rt_thread_mdelay(10); // 递归调用 recursive_stack_overflow(depth +1);}/*** 栈保护线程入口函数* @param p 线程参数*/voidstack_protect_thread(void*p){ // 获取栈信息 void*stack_bottom_addr =get_stack_bottom_addr(); void*stack_top_addr =get_stack_top_addr(); uint32_tstack_size = stack_thread->stack_size; // 打印线程启动信息 rt_kprintf("线程启动:\n"); rt_kprintf(" 栈底地址: 0x%08X\n", stack_bottom_addr); rt_kprintf(" 栈顶地址: 0x%08X\n", stack_top_addr); rt_kprintf(" 栈大小: %d 字节\n", stack_size); rt_kprintf(" 最大递归次数: %d\n", max_recursion); rt_kprintf("----------------------------------------\n"); rt_thread_mdelay(20); // 开始递归测试 recursive_stack_overflow(1); rt_kprintf("----------------------------------------\n"); rt_kprintf("递归测试结束\n");}/*** 设置最大递归次数(外部可调用)* @param count 最大递归次数,<=0 则使用默认值 */void set_max_recursion(int argc, char **argv){ int count = atoi(argv[1]); if (count <= 0) { max_recursion = MAX_RECURSION_DEFAULT; rt_kprintf("已设置最大递归次数为默认值: %d\n", MAX_RECURSION_DEFAULT); } else { max_recursion = count; rt_kprintf("已设置最大递归次数为: %d\n", max_recursion); }}MSH_CMD_EXPORT(set_max_recursion, 设置最大递归次数(参数为次数));/** * 创建栈保护测试线程 */void create_stack_protect_thread(void){ // 创建线程(栈大小2048字节) stack_thread = rt_thread_create( "stack_thread", // 线程名称 stack_protect_thread, // 入口函数 RT_NULL, // 参数 512, // 栈大小 20, // 优先级 10 // 时间片 ); if (stack_thread != RT_NULL) { rt_thread_startup(stack_thread); rt_kprintf("栈保护线程创建成功\n"); } else { rt_kprintf("栈保护线程创建失败\n"); }}MSH_CMD_EXPORT(create_stack_protect_thread, 创建栈保护测试线程);
经过递归三次,五次,八次(第八次溢出)的测试后,捕获到的trace图像是这样的



0-4s 和 0-8s 递归三次以及递归五次,都没有踩到我们的报警阈值
8-12s 递归八次时,在第6次踩到我们的64字节报警线
msh />create_stack_protect_thread栈保护线程创建成功msh />线程启动: 栈底地址:0x20004160 栈顶地址:0x20004360 栈大小: 512字节 最大递归次数:10----------------------------------------[Depth: 1] var_a addr:0x20004330[Depth: 2] var_a addr:0x20004310,stack_used:32[Depth: 3] var_a addr:0x200042F0,stack_used:32[Depth: 4] var_a addr:0x200042D0,stack_used:32[Depth: 5] var_a addr:0x200042B0,stack_used:32[Depth: 6] var_a addr:0x20004290,stack_used:32[Depth: 7] var_a addr:0x20004270,stack_used:32[Depth: 8] var_a addr:0x20004250,stack_used:32[E/kernel.sched] thread:stack_tstack overflow
从上面的调试日志可以看到,栈溢出前64字节,正好是第六次递归的时候,这说明这个栈溢出报警起码准确度没问题。
但是细心的小伙伴可能发现了另一个问题,距离栈底似乎还有很大的空间,但是栈溢出“提前发生了”,我们的报警也提前了。这是因为我们的递归函数中调用了其他的函数,经过调试发现,造成栈溢出的直接原因是rt_thread_mdelay,最后栈溢出时,第八次递归进入后,sp位置在0x20004240,而调用rt_thread_mdelay最大深度能到0x2000412c,此时已经远远超过了我们的栈底,所以溢出和警报都是正常的。
总结
体验下来,rt-trace的栈保护功能确实能很好的提示我线程栈的使用情况,可调的阈值也给了用户比较大的自由度,这次的升级trace的采集时间也大大加长了,之前只能采12秒,现在可以以分钟为单位进行采集。
不过还是有些可以继续提升的部分,比如现在的栈保护只能保护一个线程,如果能实时自动保护所有的线程,可能使用的体验和带来的帮助会更好。
-
嵌入式系统
+关注
关注
41文章
3716浏览量
133107 -
内存
+关注
关注
9文章
3173浏览量
76118 -
堆栈溢出
+关注
关注
0文章
10浏览量
8098
发布评论请先 登录
【RT-Trace】功能再升级!GDB Server功能 + Flash一键烧录,嵌入式开发更加便捷!| 技术集结
揭秘!基于RT-Thread探究“优先级反转”下的任务调度究竟是什么样的?| 技术集结
基于“互联网+”与北斗的精准定位智慧停车系统
trace32 for rt-thread support的基本使用及系统插件原理
jvm内存溢出该如何定位解决
RT-Trace调试工具正式发布!
RT-Trace初体验一之使用Trace功能调试Cortex-M4 | 技术集结
【直播预告】RT-Trace调试工具V1.1.0版本功能全解析 | 问学直播
【干货分享】RT-Trace国产调试工具 | 技术集结
【直播预告】RT-Trace 全新版本发布|ITM输出 MemoryWatch 功能首发实测! | 问学直播

告别 “栈溢出”!用 RT-Trace 工具精准定位嵌入式系统内存隐患 | 技术集结
评论