BES平台系统基础知识

总结:系统基数知识为四类:分别为死机类型、Map表重要信息、Lst表重要信息、ARM基础知识

Ⅰ、死机类型:

  1. ASSERT死机、
  2. 空指针死机
  3. 野指针死机
  4. 看门狗死机
  5. 无coredump死机

Ⅱ、Map表重要信息:

  1. RAM、Flash的合法地址
  2. .bss\.data\text等各区段的地址分布
  3. thread stack地址与大小
  4. ram free情况

Ⅲ、Lst表重要信息:

  1. 根据LR、BackTrace确定最后的函数入口
  2. 函数执行过程、掌握一些汇编指令(汇编指令可以问ChatGPT)

Ⅳ、ARM基础知识:

  1. RO~R15寄存器的作用
  2. MSP与PSP
  3. 中断关系
  4. thread优先级关系
  5. 入栈与出栈
  6. coredump基础知识

分析:第一类:死机类型

1. ASSERT死机(断言失败死机)

  • 本质 :这是一种主动的、受控的程序崩溃,是开发者在代码中故意设置的"安全检查点"。

  • 触发条件 :当程序运行到 ASSERT(condition) 语句时,会检查 condition 这个条件是否为真。如果为假(即程序状态违反了开发者预想的不变量、前置或后置条件),断言失败,系统主动触发死机/崩溃。

  • 代码中可以设置断言,当断言的条件为false时,则触发死机。
    ASSERT死机包括但不限于,malloc死机、看门狗死机、BTCcrash死机、mailbox死机

  • 典型原因

    • 函数参数无效(如传递了NULL给不允许为NULL的参数)。

    • 数据结构损坏(如链表断裂、计数溢出)。

    • 状态机处于非法状态。

    • 在中断上下文中调用了可能睡眠的函数。

  • 特点与价值

    • 开发阶段的宝贵工具:ASSERT在调试版本中通常启用,在发布版本中关闭。它的目的是在开发阶段尽早、清晰地暴露程序逻辑中的隐藏错误。

    • 信息明确:死机时通常会打印出文件名、行号、失败的条件表达式,指向问题根源非常直接。

    • 它不是"意外"的硬件错误,而是程序逻辑错误的明确告警。

2. 空指针死机(空指针解引用)

  • 本质 :这是一种被动的、非法的内存访问

  • 触发条件 :当程序试图通过一个值为 NULL(或0)的指针去访问或修改内存时,CPU会触发一个内存访问异常(如Data Abort, Segment Fault)。

  • 根据PC、或寄存器的值为0,则判断为空指针死机
    比如PC=0x0000000000,或者R0=0x0000000000并以R0的地址寻值,会触发空指针,
    常见的空指针死机,多为:静态变量未赋值就引用、指针free后并置为NULL后被引用

  • 典型原因

    • 指针未初始化就使用。

    • 函数返回NULL,调用方未检查直接使用。

    • 资源申请失败(如malloc, kmalloc 返回NULL)后未做错误处理。

    • 对象已被释放,但指针未置NULL(与野指针相关)。

  • 特点

    • 崩溃地址通常是一个低地址(如0x00000000,0x0000000X)。

    • 在有MMU的系统中,该地址通常是未映射的,会立即触发异常。

    • 问题相对容易定位,因为崩溃点就是解引用的那一行代码。

3. 野指针死机(野指针解引用)

  • 本质 :这是最危险、最随机、最难调试的一类内存访问错误。

  • 触发条件 :指针指向一个已经被释放、无效或未知的内存区域。当通过该指针访问内存时,行为不可预测。

  • 根据PC、或寄存器的值不为0且不是合法地址,则判断为野指针死机。(合法范围见下面第二类Map表的RAM、Flash合法地址)

    比如PC=0x000000084,或者R0=0x00000084并以R0的地址寻值,会触发野指针

    常见的野指针死机,多为:局部变量未初始化就引用、栈溢出、指针free后不置为NULL被引用、踩内存

  • 典型原因

    • 释放后使用 :内存/对象被freedelete后,指针未置NULL,后续再次使用。

    • 作用域问题:使用了局部变量的地址并传递出去,函数返回后该地址失效。

    • 指针计算错误:数组越界访问后,误修改了相邻内存中的指针变量。

    • 多线程环境下,一个线程释放了内存,另一个线程仍在使用。

  • 特点

    • 崩溃地址是随机的、不可预测的

    • 崩溃可能发生在访问操作的很久之后,因为内存可能已被重新分配用作他途。

    • 可能导致数据静默损坏(写入到其他合法数据结构中),在真正崩溃前系统行为已经异常。

    • 调试极其困难,需要借助内存调试工具(如Valgrind, AddressSanitizer, 硬件Watchpoint)来捕捉。

4. 看门狗死机(看门狗超时复位)

  • 本质 :这是一种系统自我恢复机制,是硬件或系统软件触发的复位,而非软件直接崩溃。

  • 触发条件 :系统有一个硬件或软件看门狗定时器。主程序需要在定时器超时前周期性地"喂狗"(重置定时器)。如果未按时喂狗,则认为系统已"死锁"或"跑飞",看门狗电路会自动触发整个系统的复位。

  • 看门狗死机,是指MCU固定一段时间会喂狗一次,当喂狗不及时,就就会触发看门狗死机。
    常见的看门狗死机:线程卡死,中断不退出

  • 典型原因

    • 主线程阻塞:陷入死循环、不可中断的睡眠、或长时间关中断。

    • 调度问题:高优先级任务饿死低优先级的喂狗任务。

    • 中断风暴:CPU被中断长期占用,无法执行主任务。

    • 喂狗线程意外退出

  • 特点

    • 崩溃现象是系统重启,而非某个程序的异常退出。

    • 问题不在崩溃点,而在导致无法喂狗的根本原因上。

    • 调试方法:检查喂狗任务的状态,分析系统在复位前的最后日志,使用调试器回溯死锁位置。

5. 无coredump死机(无转储死机)

  • 本质 :这不是一种独立的死机原因,而是一种现象描述------系统发生了严重错误,但未能生成用于事后分析的核心转储文件。

  • 触发条件:在发生致命错误(如空指针、野指针、断言失败)时,生成coredump的过程本身失败或被破坏。

  • 目前这个没有特别的规律。以下为遇见次数从高到底排列:
    1、bt_drv驱动不对,导致死机
    2、电压不稳,用io口外部供电负载电流大、串口上拉5V电平不匹配、Vcore设置异常导致的死机。
    3、cp打印没有开启,cp死机
    btc死机(个别patch会死机无log)
    5、主频错误、PLL分频异常。
    6、flash读写异常,某个地方就是读写不了,芯片直接卡住。(没遇到过,但是听说别人有遇到

  • 典型原因

    1. 磁盘空间不足:无法写入巨大的coredump文件。

    2. 权限或路径问题:coredump保存目录不可写。

    3. 系统配置关闭 :coredump功能被禁用(ulimit -c 0)。

    4. 内存严重损坏:崩溃破坏了coredump生成机制所需的关键数据结构(如内存管理器的元数据)。

    5. 内核崩溃:用户态的coredump机制依赖于内核,如果内核本身崩溃(如驱动里的野指针),则无法生成用户态coredump。此时需依赖内核转储(如Kdump)。

    6. 硬件致命错误:严重的ECC内存错误、总线错误等,可能导致CPU立即停止或复位。

  • 特点与挑战

    • "死无对证":缺乏最关键的现场快照,使得问题复现和定位极其困难。

    • 调试此类问题需要依赖:

      • 系统最后的日志输出。

      • 串口控制台的崩溃信息(如Oops信息)。

      • 硬件调试器(JTAG/SWD)直接抓取崩溃瞬间的CPU寄存器和内存状态。

      • 增加更早、更详尽的日志记录。


总结与对比

死机类型 本质 关键特征 调试线索
ASSERT死机 主动暴露逻辑错误 信息明确(文件、行号、条件) 断言表达式、程序逻辑
空指针死机 非法访问已知无效地址 崩溃地址为低地址(如0x0) 调用栈、指针来源
野指针死机 非法访问未知无效地址 崩溃地址随机、行为不可预测 内存调试工具、崩溃现场内存内容
看门狗死机 系统自恢复机制 整体复位、喂狗中断 喂狗任务状态、系统阻塞点
无coredump死机 转储失败现象 缺乏核心现场文件 最后日志、内核信息、硬件调试器

在实际工作中,遇到死机问题,通常会按照以下思路排查:

  1. 首先查看是否有明确的崩溃信息(如ASSERT信息、Oops信息)。

  2. 如果有coredump,优先分析它。

  3. 如果无coredump,分析最后日志,判断死机前系统在做什么。

  4. 检查是否是看门狗复位,如果是,重点排查系统阻塞或任务调度问题。

  5. 对于随机性死机,高度怀疑野指针内存溢出,需要使用专项工具进行长时间压力测试捕捉。

  6. 考虑硬件问题(如内存条接触不良、电源不稳)的可能性,尤其是在排除了所有软件可能之后。

分析:第二类:Map表重要信息

一、RAM、Flash 的合法地址

这是什么

合法地址是指 芯片硬件实际存在的存储器地址范围,程序必须在这个范围内分配代码和数据,否则就是"非法地址",运行必崩。

Map 文件里怎么看

Map 文件本身不主动告诉你"芯片合法地址是多少",但它显示的所有段地址都必须落在合法范围内。你需要做的是:

  1. 拿到芯片手册(或链接脚本)确认合法地址区间。

    • Cortex-M 主流 MCU(STM32、GD32、NXP):

      • Flash:0x08000000 ~ 0x080xxxxx(大小由型号决定)

      • RAM: 0x20000000 ~ 0x200xxxxx

    • CC26xx/CC13xx(TI 无线芯片):

      • Flash:0x00000000 ~ 0x000xxxxx(片内 Flash)

      • RAM: 0x20000000 ~ 0x200xxxxx

    • RTL87x2G(瑞昱蓝牙芯片):

      • Flash:0x08000000 ~ 0x080xxxxx

      • RAM: 0x00100000 ~ 0x0010xxxx(紧耦合内存 TCM)+ 0x20000000 ~ 0x200xxxxx(系统 RAM)

  2. 打开 Map 文件,找到所有段的起始地址和结束地址,逐一比对。

    • 必须检查.text.rodata.data(加载地址和运行地址)、.bss、堆、栈。

    • 任何段地址落在区间外 → 链接脚本错误,立即修正,否则芯片根本不会执行。

典型错误例子

  • 误把 Flash 起始设为 0x00000000,但芯片实际 Flash 在 0x08000000 → 启动后 PC 指向空白地址,跑飞。

  • .bss 结束地址 = 0x2000B000,芯片 RAM 最高是 0x2000AFFF严重超限,运行后 .bss 清零会破坏外设寄存器或直接 HardFault。

一句话记住
合法地址是硬件划定的红线,Map 里所有地址都必须在线内。

BES平台:

Map表中,存在一个总表,可以搜关键字"FRAMX"找到

Memory Configuration

所有可以访问的地址,都必须在上述范围内。0x2c000000~0x22c010000 是boot使用,APPbin正常不可访问。
注意,如果一个地址是变量,但是对这个地址执行跳转指令也是错误的。

Flash区表示实际的物理地址
Flash_NC区主要区分只读的属性。比如常见的const字符串,或者宏常量就常使用这些地址。
FLASHX区主要是区分XR的属性。表示可执行


二、.bss/.data/.text 等各区段的地址分布

这是什么

这是 Map 文件的 绝对核心,直接告诉你:

  • 代码占了多少 Flash(.text + .rodata + .data 的加载区)

  • 全局变量占了多少 RAM(.data + .bss)

  • 每个段的起始地址、结束地址、大小

Map 里长什么样(以 GCC/ARMCC 输出为例)

复制代码
==============================================================================
                  Section Cross References
==============================================================================
    .text           0x08001000    0x3A4C    // Flash 地址,长度 14924 字节
    .rodata         0x08004A4C    0x1230    // Flash 地址,只读常量
    .data           0x20000000    0x04E8    // RAM 地址,长度 1256 字节
                    load address 0x08005C7C // **这行极关键**:初始值存在 Flash!
    .bss            0x200004E8    0x09A0    // RAM 地址,长度 2464 字节,不占 Flash
    .heap           0x20000E88    0x0800    // 堆起始地址、大小(可选)
    .stack          0x20001688    0x1000    // 栈起始地址、大小(可选)
==============================================================================

每段必须理解到骨子里的细节

段名 运行时位置 加载位置(初始值) 占用 Flash? 占用 RAM? 存储内容
.text Flash Flash(原地执行) 函数指令
.rodata Flash Flash const、字符串字面量
.data RAM Flash 已初始化的全局变量(非 0)
.bss RAM (启动时清零) 未初始化/初始化为 0 的全局变量
.heap RAM 动态内存池(malloc)
.stack RAM 函数调用栈、局部变量

两个最容易算错的内存公式(必须背)

  • 程序占用 Flash 总量 = .text + .rodata + .data(只计 load address 那部分)
    → 有的工具链把 .data 加载区合并显示为 .text,注意区分。

  • 程序静态占用 RAM 总量 = .data + .bss
    → 堆和栈是动态分配的,不在此列。

Map 里找这些信息的具体位置

  • MDK/ARMCC :打开 .map 文件,搜索 Memory Map of the image,会列出所有执行段。

  • IAR :搜索 Memory map summary

  • GCC :搜索 Memory ConfigurationLinker script and memory map

实战检查清单

  • .text 结束地址 < Flash 最高地址

  • .rodata 结束地址 < Flash 最高地址

  • .data 加载地址(LMA)+ 大小 < Flash 最高地址

  • .data 运行地址(VMA)+ 大小 < RAM 最高地址

  • .bss 结束地址 < RAM 最高地址

BES平台:

以.bss为例,确定.bss的地址分布。
搜索map中"bss"、确定.bss起止地址
搜索map中"_data"、确定.data起止地址
确定text的分布,注意.text包含很多段,比如boot_struct\,boot_text_flash\,boot_text_sram等等
搜索Flash起始地址0x2c010000,就可以得到.text段的起始地址。
boot_struct 0x2c010000 0x10 _flash_start=. /表示整个boot_struct占用0x10字节, 0x2c010000+00x10就是下一个段起始地址

boot_text_flash 0x0c010010 0x1560 load address 0x2c010010 flashx_start_addr=./表示.bpoot_text_flash占用0x1560字节, 0x2c010010+0x1560是下一个段起始

依次跟踪,可以得到最后的.text地址


三、thread stack 地址与大小

⚠️ 最重要且最容易混淆的一类

你必须先确认自己处在 裸机单栈 还是 RTOS 多任务 环境,Map 文件能提供的信息完全不同。

BES平台:

3.1 裸机 / 单线程系统(只有主栈)

  • Map 里一定有明确的栈段,名字通常是:

    • STACK(ARMCC)

    • CSTACK(IAR)

    • .stack(GCC)

  • 如何看地址和大小

    在 Section 列表里找到该段,例如:

    复制代码
    .stack          0x20001688    0x1000    // 起始 0x20001688,大小 4KB
    
    【0x1000(十六进制)= 4096(十进制)4096 字节 = 4 × 1024 字节 = 4KB】

    栈顶(初始堆栈指针 SP)通常在该段的末尾地址:__initial_sp = 0x20002688

    这个符号在 Map 的 Symbol Table 里可以找到。

3.2 RTOS 多线程系统(用户线程栈)

Map 文件里没有名为 thread_stack 的段!

线程栈是在运行时 通过 xTaskCreateosThreadNew 动态创建的,不是链接时分配的 ,因此Map 不直接包含它们的信息

那怎么看线程栈的地址和大小?

情况 A:静态分配的任务栈(最常见于小型 RTOS)

  • 你在代码里定义了一个全局数组作为栈,例如:
    static StackType_t task1Stack[512];

  • 去 Map 的 Global Symbols 表里搜索数组名,会看到它的地址和长度。

    复制代码
    task1Stack      0x20001A88    0x0800  // 任务栈在 .bss 段,大小 2KB
  • 此时你可以直接从 Map 得知这个任务栈在 RAM 的具体位置。

情况 B:动态分配的任务栈(使用 malloc / pvPortMalloc)

  • 栈在运行时从堆里分配,Map 完全看不到

  • 必须通过 RTOS 提供的 API 在运行时查询:

    • FreeRTOS:uxTaskGetStackHighWaterMark()

    • RT-Thread:rt_thread_control()rt_thread_measure_stack_water_mark()

关键结论

  • 裸机主栈:Map 里明明白白,直接读。

  • RTOS 任务栈静态分配 → 搜数组名动态分配 → 运行时 API

  • 任何声称"Map 文件能直接显示所有任务栈"的资料都是错误的。

3.3 栈使用量(水线)------ Map 帮不上忙

Map 只给总大小用了多少还剩多少必须运行时看:

  • 裸机:填充栈区域为固定模式(如 0xCC),运行后扫描。

  • RTOS:使用上述 API。

  • 调试器:查看 SP 指针,结合栈底地址,估算当前使用量。


四、ram free 情况

这是 Map 信息里最容易被高估的一项 ------Map 只能回答"编译时静态还剩多少",回答不了"运行时还剩多少"

BES平台:

4.1 静态剩余 RAM(从 Map 直接算)

定义 :链接器把 .data.bss、堆、栈都安排好后,RAM 里还有多少字节是完全没有被分配给任何用途的。

计算公式

复制代码
静态剩余 RAM = 芯片 RAM 最高地址 - max( 已分配区域最高地址 )

已分配区域最高地址 是以下三者的最大值:

  1. .bss 结束地址

  2. 堆结束地址(__heap_end

  3. 栈起始地址(注意栈是向下生长的,起始地址就是栈底,结束地址是栈顶?要谨慎)

在 Map 里找这些地址

你需要找 常用符号名(GCC) ARMCC / MDK IAR
RAM 最高地址 在链接脚本定义,如 _estack Image$$RAM$$ZI$$Limit CSTACK$$Limit
.bss 结束 __bss_end__ Image$$RW_IRAM1$$ZI$$Limit BSS$$Base + 大小
堆结束 __heap_end Heap$$Limit HEAP$$Base + 大小
栈起始 __stack_start__ STACK$$Base CSTACK$$Base

实战步骤

  1. 打开 Map,搜索 RAM 末尾符号(如 _estack = 0x20010000),确认芯片 RAM 终点。

  2. 找出所有已分配段的最大结束地址(通常是堆结束或栈起始,取决于链接脚本)。

  3. 差值 = 剩余静态 RAM。

用途

  • 这个剩余 RAM 最终会被 动态使用(或者闲置)。

  • 如果静态剩余 RAM 已经是负数 → 链接阶段就溢出,程序无法启动,必须立即修改链接脚本或裁剪代码。

4.2 动态剩余 RAM(Map 不能提供

堆剩余

  • 必须调用运行时函数:

    • malloc_stats()

    • xPortGetFreeHeapSize()(FreeRTOS)

    • rt_memory_info()(RT-Thread)

栈剩余

  • 必须用"水线"监测:

    • 裸机:预先填充 0xCC,运行时从栈底向上找第一个非 0xCC 的字节。

    • RTOS:uxTaskGetStackHighWaterMark()

碎片率

  • 堆剩余空间大不等于能分配到大块内存。

  • 需要调用堆调试函数(如 malloc_dump())查看最大连续空闲块。

一句话总结
Map 只给你看"地皮规划图",不告诉你"现在每栋楼住了多少人"。

  • 静态剩余 → 打开 Map 算一算。

  • 动态剩余 → 插桩、API、调试器。


附:Map 文件速查索引表(实战专用)

你想知道什么 在 Map 里搜什么 在哪个区块找
代码总共占多少 Flash .text + .rodata + .data 的 load address Section Cross Table
全局变量占多少 RAM .data + .bss Section Cross Table
这个全局变量在哪个地址 变量名(全字匹配) Global Symbols
裸机栈总大小 STACKCSTACK.stack Section Table
裸机栈顶(SP 初始值) __initial_spCSTACK$$Limit Symbols
堆起始地址 __heap_baseHeap$$Base Symbols
堆结束地址 __heap_endHeap$$Limit Symbols
RAM 还剩多少静态空间 RAM 终点符号(如 _estack) - 已分配区域最大地址 自行计算
中断向量表位置 __Vectors Symbols
程序入口 __main_startReset_Handler Symbols
RTOS 静态任务栈地址 你定义的栈数组名 Global Symbols

Lst表重要信息、ARM基础知识。有空再更

相关推荐
雪域迷影14 小时前
sdl3-sample-简明教程,指导如何在包括移动和 Web 在内的各种平台上构建和使用 SDL3
github·c·开源软件·sdl3
吃杠碰小鸡1 天前
高中数学运算方法
学习方法
qq_459234422 天前
【题库】| 商用密码应用安全性评估从业人员考核题库(十二)
安全·职场和发展·密码学·跳槽·学习方法
hqyjzsb3 天前
亲历AI浪潮5年:技术更新快,但掌握底层逻辑永远有价值
人工智能·程序人生·职场和发展·创业创新·学习方法·业界资讯·远程工作
证榜样呀3 天前
2026 大专大数据专业零基础能考的证书有哪些
学习方法
方见华Richard3 天前
世毫九实验室:自指认知=递归对抗架构
人工智能·经验分享·交互·学习方法·原型模式
qq_459234423 天前
【题库】| 商用密码应用安全性评估从业人员考核题库(十三)
linux·服务器·网络·网络安全·职场和发展·密码学·学习方法
2501_926978333 天前
当AI--遇到语言的模糊性--需要警惕的危险
大数据·人工智能·经验分享·笔记·机器学习·学习方法
零售ERP菜鸟5 天前
范式革命:从“信息化”到“数字化”的本质跃迁
大数据·人工智能·职场和发展·创业创新·学习方法·业界资讯