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

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

配置成功后就可以去trace_view界面测试了,这里我星火一号上跑了一个递归爆栈测试程序,没有优化
#include #include #include #ifndef RT_USING_NANO#include #include #include #include #endif /* RT_USING_NANO */rt_thread_t stack_thread = NULL;#define THREAD_PRIORITY 25#define THREAD_STACK_SIZE 512#define THREAD_TIMESLICE 5int main(void){ while (1) { rt_thread_mdelay(500); }}#define MAX_RECURSION_DEFAULT 5 // 默认最大递归次数static int max_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 当前递归深度 */void recursive_stack_overflow(int depth){ volatile uint32_t a = 0x12345678; // 创建一个32位变量并赋值 static void *last_a_addr = NULL; // 获取栈边界地址 void *stack_bottom_addr = get_stack_bottom_addr(); void *stack_top_addr = get_stack_top_addr(); if (depth != 1) { uint32_t stack_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 线程参数 */void stack_protect_thread(void *p){ // 获取栈信息 void *stack_bottom_addr = get_stack_bottom_addr(); void *stack_top_addr = get_stack_top_addr(); uint32_t stack_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秒,现在可以以分钟为单位进行采集。
不过还是有些可以继续提升的部分,比如现在的栈保护只能保护一个线程,如果能实时自动保护所有的线程,可能使用的体验和带来的帮助会更好。
全部0条评论
快来发表一下你的评论吧 !