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

描述

 

前言


 

相信无论大佬还是小白,都在开发中遇到过栈溢出的问题,而且因为没有明确日志,难以定位问题的根源。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秒,现在可以以分钟为单位进行采集。

不过还是有些可以继续提升的部分,比如现在的栈保护只能保护一个线程,如果能实时自动保护所有的线程,可能使用的体验和带来的帮助会更好。

 

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分