现象

加入新的函数后,运行时无法启动,报错

psr: 0x21000000
r00: 0x20002478
r01: 0x0807f0b0
r02: 0x20002c2c
r03: 0x00000000
r04: 0x00000002
r05: 0xffffffff
r06: 0x0807f0b0
r07: 0x20000904
r08: 0x20000368
r09: 0x20002c2c
r10: 0x1eb85420
r11: 0x00000190
r12: 0x000007e0
 lr: 0x080729b9
 pc: 0x080729d8
hard fault on thread: main

thread   pri  status      sp     stack size max used left tick  error
-------- ---  ------- ---------- ----------  ------  ---------- ---
sys_work  23  ready   0x00000044 0x00000800    03%   0x0000000a 000
tidle0    31  ready   0x00000048 0x00000100    28%   0x00000020 000
main      10  running 0x00000048 0x00000800    20%   0x00000003 000
bus fault:
SCB_CFSR_BFSR:0x82 PRECISERR SCB->BFAR:FFFFFFFF

注释掉原来的函数,运行正常

说明

开始怀疑是栈溢出,后来发现是链接脚本的问题,
本文主要记录分析思路和调试过程,结论在最后一节

分析过程

排查过程

检查新加代码 是否有数组越界

之前也遇到过 bus fault ,原因是数组越界,检查新加的代码,没发现问题

怀疑栈溢出,开始实验

增加全局变量数组长度,能复现
加const,放到flash,能复现
注释掉一些之前的代码,正常

链接脚本

指定flash起始地址 大小 //就是这里的问题哦
指定sram 起始地址 大小
CODE段
.data
DATA段
.stack
.bss
heap
对应生成的map文件看各个段的起始地址,结束地址
_sdata
看是否重叠,是否在芯片手册规定的范围内。
注意看数据手册里芯片型号对应的flash和sram大小(用户手册里提到的大小有可能是系列里最大的)

查看启动流程,分析栈

链接脚本里有个系统栈

ENTRY(Reset_Handler)
_system_stack_size = 0x200;

rtconfig.h里有个主线程栈 RT_MAIN_THREAD_STACK_SIZE
主线程启动前,用的是系统栈,主线程启动后,用的是主线程栈
主线程,动态创建,从堆上分配一块空间,作为主线程的栈

rtthread_startup【src/components.c】
	...
	rt_show_version();//打印版本号
	...
	rt_application_init //创建了main线程,但没开始执行
		rt_thread_create("main", main_thread_entry, RT_NULL,
                           RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
         rt_thread_startup
	...
	rt_system_scheduler_start(); //main线程,开始执行
	//不会执行到这

加打印看看rt-thread 启动到哪了,发现是在component初始化

main_thread_entry
	rt_components_init
		#if RT_DEBUG_INIT //打开这个宏,有调试打印 组件名
		...
		for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++)
	    {
	        rt_kprintf("initialize %s", desc->fn_name);
	        //print_real_time_stack_info();
	        print_detailed_stack_usage();
	        //print_stack_info();
	        result = desc->fn();
	        rt_kprintf(":%d done\n", result);
	    }
    ...

打开 RT_DEBUG_INIT后,发现执行到 soft_rtc
当时怀疑栈,就想获取下当前栈指针位置,看看是不是要越界了

void print_stack_info(void)
{
    rt_thread_t main_thread = rt_thread_self();
    
    if (main_thread != RT_NULL && rt_strcmp(main_thread->name, "main") == 0) {
        rt_kprintf("=== Main线程栈信息 ===\n");
        rt_kprintf("栈起始地址: 0x%08x\n", main_thread->stack_addr);
        rt_kprintf("栈结束地址: 0x%08x\n", (rt_uint8_t *)main_thread->stack_addr + main_thread->stack_size);
        rt_kprintf("栈大小: %d 字节\n", main_thread->stack_size);
        rt_kprintf("sp: 0x%08x\n", main_thread->sp);
        //rt_kprintf("已使用: %d 字节\n", main_thread->stack_used);
        //rt_kprintf("剩余空间: %d 字节\n", main_thread->stack_size - main_thread->stack_used);
        //rt_kprintf("使用率: %d%%\n", (main_thread->stack_used * 100) / main_thread->stack_size);
    }
}
void print_real_time_stack_info(void)
{
    uint32_t current_psp, current_msp, control_reg;
    rt_thread_t main_thread = rt_thread_self();
    
    // 读取当前进程栈指针
    asm volatile ("MRS %0, psp" : "=r" (current_psp));
    asm volatile ("MRS %0, msp" : "=r" (current_msp));
    asm volatile ("MRS %0, control" : "=r" (control_reg));
    
    rt_kprintf("MSP: 0x%08x\n", current_msp);
    rt_kprintf("CONTROL: 0x%08x\n", control_reg);
    
    // 分析CONTROL寄存器
    rt_kprintf("当前模式: %s\n", (control_reg & 0x01) ? "线程模式(可能用PSP)" : "处理模式(用MSP)");
    rt_kprintf("当前栈: %s\n", (control_reg & 0x02) ? "PSP" : "MSP");
    

    rt_kprintf("线程名: %s\n", main_thread->name);
    rt_kprintf("thread->sp (保存的): 0x%08x\n", main_thread->sp);
    rt_kprintf("当前PSP (实时的): 0x%08x\n", current_psp);
    //rt_kprintf("栈起始: 0x%08x, 大小: %d\n", 
      //         main_thread->stack_addr, main_thread->stack_size);
    
    // 计算实时栈使用量
    rt_uint8_t *stack_top = (rt_uint8_t *)main_thread->stack_addr + main_thread->stack_size;
    rt_uint32_t real_used = (uint32_t)stack_top - current_psp;
    
    rt_kprintf("实时栈使用: %d 字节\n", real_used);
}

void print_detailed_stack_usage(void)
{
    rt_thread_t current = rt_thread_self();
    if (!current) return;
    
    uint32_t current_sp;
    // 获取当前栈指针(无论是PSP还是MSP)
    asm volatile (
        "MRS %0, CONTROL \n"  // 先读CONTROL寄存器
        "TST %0, #2 \n"       // 检查使用的是PSP还是MSP
        "ITE EQ \n"
        "MRSEQ %0, MSP \n"    // 如果使用MSP
        "MRSNE %0, PSP \n"    // 如果使用PSP
        : "=r" (current_sp)
    );
    
    uint32_t stack_start = (uint32_t)current->stack_addr;
    uint32_t stack_end = stack_start + current->stack_size;
    uint32_t stack_used = stack_end - current_sp;
    uint32_t stack_free = current->stack_size - stack_used;
    uint32_t usage_percent = (stack_used * 100) / current->stack_size;
    
    rt_kprintf("=== 详细栈使用情况 ===\n");
    rt_kprintf("线程: %s\n", current->name);
    rt_kprintf("栈范围: 0x%08x - 0x%08x\n", stack_start, stack_end);
    rt_kprintf("栈大小: %d 字节\n", current->stack_size);
    rt_kprintf("当前SP: 0x%08x\n", current_sp);
    rt_kprintf("已使用: %d 字节\n", stack_used);
    rt_kprintf("剩余: %d 字节\n", stack_free);
    rt_kprintf("使用率: %d%%\n", usage_percent);
    
    // 警告级别
    if (usage_percent > 90) {
        rt_kprintf("⚠️  严重警告: 栈使用率超过90%%!\n");
    } else if (usage_percent > 80) {
        rt_kprintf("⚠️  警告: 栈使用率超过80%%\n");
    }
}

参考ai给出的打印栈信息的代码
栈起始结束地址都对应,但当前指针一直不变 PSP MSP都不变
听说是线程不切换,就不会更新 (没继续验证)
后面想到可以定义一个局部变量,打印地址

继续跟 soft rtc的初始化

void monitor_stack_peak(void)
{
    static rt_uint32_t min_sp = 0xFFFFFFFF;
    
    rt_uint32_t current_sp;
    asm volatile ("MRS %0, MSP" : "=r" (current_sp));
    
    if (current_sp < min_sp) {
        min_sp = current_sp;
        
        rt_thread_t main_thread = rt_thread_self();
        rt_uint32_t stack_start = (rt_uint32_t)main_thread->stack_addr;
        rt_uint32_t stack_used = min_sp - stack_start;
        rt_uint32_t usage_percent = (stack_used * 100) / main_thread->stack_size;
        
        rt_kprintf("栈峰值: %d/%d bytes (%d%%)\n", 
                  stack_used, main_thread->stack_size, usage_percent);
    }
}

rt_soft_rtc_init
	...
	//这里有打印
	//monitor_stack_peak
	uint8_t temp = 123; //加个局部变量
    rt_kprintf("temp addr: 0x%08x\n", (uint32_t)&temp); //取地址,即当前栈指针指向的地址
	mktime()
	//这里没有

本来想的是继续在mktime里继续加打印,看具体执行到哪了,但2出函数加打印后都没有输出
在map文件里搜 mktime

.text.mktime   0x000000000806ddd0      0x444 /xxxxxx/libc.a(lib_a-mktime.o)

发现调用的是标准库libc.a里的,应该不会有问题
看到806ddd0这个flash地址,又去检查了一下flash大小
链接脚本

MEMORY
{
    CODE (rx) : ORIGIN = 0x08000000, LENGTH = 256k /* 256KB flash */
    DATA (rw) : ORIGIN = 0x20000000, LENGTH = 128k /* 128KB sram */
}
ENTRY(Reset_Handler)
_system_stack_size = 0x200;

SECTIONS
{
...

如果整个flash大小是256k,如果分为bootloader和app两部分
假设前64k给bootloader,剩余192k给app

bootloader的链接脚本
 CODE (rx) : ORIGIN = 0x08000000, LENGTH = 64k /* 64KB flash */
app
bootloader的链接脚本
 CODE (rx) : ORIGIN = 0x08010000, LENGTH = 192k /* 192KB flash */

即编译出来的不能超过192k
检查map文件

.text
...
.rel.dyn        0x000000000803ef28        0x0
 .rel.iplt      0x000000000803ef28        0x0 build/applications/main.o 
 .data

假设flash地址从0x8000000开始
64k 起始地址 0x8000000 结束地址 0x8010000
192k 起始地址 0x8010000 结束地址 0x8040000
.data之前的地址要小于 0x8040000

最终原因

链接脚本里flash大小

更多推荐