前言
相信无论大佬还是小白,都在开发中遇到过栈溢出的问题,而且因为没有明确日志,难以定位问题的根源。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文章
3696浏览量
131899 -
内存
+关注
关注
8文章
3137浏览量
75527 -
堆栈溢出
+关注
关注
0文章
10浏览量
8064
发布评论请先 登录
【RT-Trace】功能再升级!GDB?Server功能?+?Flash一键烧录,嵌入式开发更加便捷!|?技术集结

揭秘!基于RT-Thread探究“优先级反转”下的任务调度究竟是什么样的?| 技术集结

基于“互联网+”与北斗的精准定位智慧停车系统
trace32 for rt-thread support的基本使用及系统插件原理
jvm内存溢出该如何定位解决
RT-Trace调试工具正式发布!

RT-Trace初体验一之使用Trace功能调试Cortex-M4 | 技术集结

评论