rt-thread无法启动
现象
加入新的函数后,运行时无法启动,报错
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大小
更多推荐




所有评论(0)