当前位置:首页>面试真题>嵌入式面试真题第 15 题:不可恢复异常后的通用崩溃快照、调用栈保存与离线分析架构

嵌入式面试真题第 15 题:不可恢复异常后的通用崩溃快照、调用栈保存与离线分析架构

  • 2026-07-29 00:35:42
嵌入式面试真题第 15 题:不可恢复异常后的通用崩溃快照、调用栈保存与离线分析架构

嵌入式面试真题第 15 题:不可恢复异常后的通用崩溃快照、调用栈保存与离线分析架构

在这里插入图片描述

问题

在量产嵌入式设备中,系统可能在任意业务状态、任意线程、任意中断嵌套层级和任意外设工作阶段发生不可恢复异常,例如 Arm Cortex-M 的 HardFault、MemManage、BusFault、UsageFault、SecureFault,RISC-V 的同步异常或 NMI,Xtensa 的 panic,RTOS assert,栈溢出,内存破坏,非法指令,访问越界,双重释放,DMA 越界,Flash/cache 异常,锁死后的看门狗复位,以及电压跌落前的紧急复位。

发生异常时,调度器、线程栈、堆、文件系统、日志系统、Flash 驱动甚至时钟和缓存状态都可能已经不可信。设备又可能位于用户现场,无法连接调试器,研发只能在返修、远程上报或下次启动后分析有限数据。

你会如何设计一套通用的嵌入式 Crash Snapshot/Core Dump 机制,在异常入口的极短时间内,安全、确定且可验证地保存 CPU 上下文、故障寄存器、当前调用栈、关键线程状态、最近事件和固件身份,并在复位后可靠地转存、上报和离线符号化?设计时应说明:

  1. 1. 异常入口如何取得正确的栈指针和异常栈帧。
  2. 2. 为什么不能在异常上下文中直接做完整调用栈分析。
  3. 3. 如何防止栈损坏、二次 Fault、Flash 正在忙、掉电或看门狗超时导致快照再次失败。
  4. 4. Crash Record 如何实现版本兼容、完整性校验和原子提交。
  5. 5. 如何兼容裸机、RTOS、多核、FPU、TrustZone、不同存储介质和不同 CPU 架构。
  6. 6. 如何借鉴 CMSIS-View Fault Storage、CrashCatcher、Zephyr Coredump、ESP-IDF Core Dump、RT-Thread 异常框架、Memfault Firmware SDK 等现有方案。

回答

结论:不要把“异常处理”设计成在 Fault Handler 中打印日志、遍历线程、回溯符号或写普通文件。正确方案是把整个流程拆成三个彼此隔离的阶段:

  1. 1. 一级捕获阶段:异常入口只执行固定上界、无动态分配、无锁、尽量不依赖外设的最小快照逻辑,把原始寄存器、异常栈帧、故障状态和受边界保护的栈窗口写入保留 RAM、Backup SRAM、.noinit区或预擦除的 Crash Slot。
  2. 2. 二级持久化阶段:系统复位后,在调度器、文件系统和网络尚未启动或刚恢复可信状态时,校验一级快照并转存到专用 Flash 分区、FRAM、eMMC、LittleFS 文件或远程上报队列。
  3. 3. 离线分析阶段:PC 工具结合与故障固件严格匹配的 ELF、MAP、DWARF、Build ID 和链接地址,完成符号化、栈展开、源码定位、故障寄存器解码和同类崩溃聚类。

核心原则是:异常上下文只保存事实,不做复杂推理;复位后完成可靠存储;主机侧完成重型分析。

总体架构

不可恢复异常\nFault / Panic / Assert / WDT Pretimeout
最小汇编入口
识别原始 SP\nMSP / PSP / 安全域 / 核心号
切换独立 Crash Stack
一级快照引擎
CPU 寄存器
故障状态寄存器
受边界保护的栈窗口
线程/系统最小元数据
最近事件 Ring Buffer
一级存储后端
Retention RAM / Backup SRAM / .noinit
预擦除 Crash Flash Slot
FRAM / MRAM / 主机直连
系统复位
Early Boot Crash Collector
校验 magic / version / length / CRC
持久化、去重、加密、上传
ELF + MAP + DWARF + Build ID
离线符号化 / GDB / 聚类分析

这个架构的关键不是保存尽可能多的数据,而是先保证最小快照在最差故障条件下仍有较高成功率。完整性和确定性优先于信息量。一个只包含 PC、LR、SP、CFSR、HFSR 和 256 字节栈窗口但 CRC 正确、固件版本匹配的记录,通常比一个写了一半、版本不明、来源不明的“全内存 Dump”更有价值。

将问题抽象为通用 Crash Capture,而不是只处理 HardFault

HardFault 只是不可恢复异常的一种入口。通用方案应把各种异常统一映射为 crash_reason,共用记录格式、存储后端和离线工具。

异常来源
典型触发原因
一级捕获入口
需要额外保存的信息
HardFault
可配置 Fault 升级、向量表异常、非法返回
HardFault_Handler
HFSR、CFSR、原始 EXC_RETURN
MemManage
MPU 访问违规、栈限制越界
MemManage_Handler
MMFAR、MPU 配置、MSPLIM/PSPLIM
BusFault
非法地址、总线超时、精确或非精确总线错误
BusFault_Handler
BFAR、总线矩阵/外部存储状态
UsageFault
未定义指令、除零、非对齐、非法状态
UsageFault_Handler
CFSR.UFSR、PC 附近指令字
SecureFault
TrustZone 安全属性、非法跨域访问
SecureFault_Handler
SFSR、SFAR、安全域和安全栈信息
RTOS Assert/Panic
内核一致性检查失败
assert/panic hook
文件、行号、当前线程、调度状态
栈溢出
线程栈破坏、MSP/PSP 越界
MPU Fault、RTOS hook
栈边界、水位、canary、TCB 指针
看门狗
死循环、锁死、中断关闭过久
WDT pretimeout/NMI 或复位原因
当前 PC、任务心跳、最后喂狗点
Brownout
电源跌落、瞬态复位
Brownout IRQ/NMI 或复位寄存器
电压状态、复位源、未完成事务
RISC-V Trap
访问错误、非法指令、断点
trap entry
mcause
mepcmtvalmstatus
Xtensa Panic
IllegalInstruction、Load/StoreProhibited
panic handler
EPC、EXCCAUSE、EXCVADDR、任务栈

因此,接口层不应叫 hardfault_save(),而应抽象为:

crash_capture(reason, arch_context, platform_context)

其中 arch_context由 CPU 架构端口提供,platform_context由 RTOS、SoC 和产品层按需扩展。

机制与开源方案的对应关系

本文机制
可参考组件或方案
核心原理
可直接使用程度
主要限制或注意事项
最小 Fault 快照到未初始化 RAM
CMSIS-View Fault Storage[1]
从 Fault Handler 直接分支到无栈/无堆保存函数,将寄存器、故障寄存器、magic、版本和 CRC 写入未初始化 RAM
Cortex-M 项目可直接集成或参考
重点是最小 Fault 信息,不等同于完整 RTOS Core Dump
独立 Crash Stack、寄存器和 RAM 区域 Dump
CrashCatcher[2]
+ CrashDebug
汇编入口切换专用栈,收集硬件压栈寄存器、软件保存寄存器和指定 RAM 区域,主机侧进行事后调试
Cortex-M GCC 工程可集成
需要自行实现输出后端;示例中的文件系统写入不代表任何故障环境都安全
架构块 + 内存块 + 多后端 Core Dump
Zephyr Coredump[3]
Fatal Error 时输出 CPU 寄存器和选定内存,支持 logging、UDP、Flash Partition 等后端,并由自定义 GDB Server 还原目标
Zephyr 项目可直接配置
Flash 后端仍依赖具体 Flash 控制器和驱动在异常场景下可用
任务 TCB/栈快照、Flash/UART、ELF 解析
ESP-IDF Core Dump[4]
Panic Handler 保存任务快照和寄存器到专用分区或 UART,主机用 idf.py coredump-info/debug分析
ESP-IDF 项目可直接使用
数据量与任务数、栈大小相关;Flash cache 损坏场景需要特别配置 IRAM panic 路径
RTOS 异常栈、异常钩子和 Backtrace
RT-Thread[5]
Cortex-M CPU Port
利用异常栈结构、独立中断栈、异常钩子和架构 Backtrace 能力输出上下文
RT-Thread 项目可在现有异常框架上扩展
默认打印不等于持久化;需自行增加 Crash Record 和复位后转存
可配置 Coredump 区域、重启原因、远程聚类
Memfault Firmware SDK[6]
Firmware SDK 采集寄存器、任务和选定内存区域,经持久存储和分片上传后进行符号化与聚类
可参考或集成,云端服务商业化
SDK 使用 Memfault License,不应简单视为宽松许可证开源组件
掉电安全的复位后文件持久化
littlefs[7]
Copy-on-write、元数据日志、掉电回退、动态磨损均衡和有界 RAM
适合二级持久化
不应在 HardFault 最小路径中直接 mount/open/write/close

这些方案共同体现了几条成熟原则:异常入口必须尽量小;原始状态和解析工具分离;记录必须携带格式版本和固件身份;存储后端应可替换;内存 Dump 区域必须受控;真正的调用栈还原应在主机侧完成。

三级处理模型

一级:Fault Context Capture

一级阶段的任务只有四类:

  1. 1. 取得原始异常上下文,而不是 Handler 已经修改后的上下文。
  2. 2. 把最关键数据复制到可信目标区域。
  3. 3. 以原子方式标记记录完成或部分完成。
  4. 4. 在确定的时间上界内复位或停机。

禁止项包括:

  • • printfsprintf、格式化日志和复杂串口驱动。
  • • malloc/free、C++ new/delete、容器扩容。
  • • mutex、semaphore、message queue 等可能阻塞的同步原语。
  • • 普通文件系统的 mount/open/write/fsync/close 链路。
  • • 依赖中断完成的 DMA、Flash、UART 驱动。
  • • 遍历不可信链表、线程表、堆块或文件系统元数据。
  • • 在故障处理器中执行符号解析、DWARF 解析或无限深度栈回溯。

二级:Early Boot Commit

二级阶段发生在复位后。此时可以逐步恢复更多能力,但仍应尽早执行,避免 .bss初始化、内存清零、Bootloader 升级或新的崩溃覆盖一级记录。

ApplicationCrash PartitionEarly Boot CollectorSystem ResetRetention/.noinitFault/Panic EntryApplicationCrash PartitionEarly Boot CollectorSystem ResetRetention/.noinitFault/Panic Entryalt[有效记录][无效或半写记录]写 header,state=WRITING写寄存器/栈窗口/事件计算 CRC最后写 state=VALID复位或等待 WDT启动早期检查校验 magic/version/length/CRC追加到预留 Crash Slot写入并读回校验标记已搬运或清除 magic记录 partial/corrupt 统计正常启动或进入 Safe Mode

二级阶段可以执行:

  • • 将 Retention RAM 快照搬到专用 Flash 分区。
  • • 使用 LittleFS、NVS 或数据库保存索引,但前提是文件系统可正常挂载。
  • • 加密、压缩、脱敏、去重和上传。
  • • 根据连续崩溃次数进入 Safe Mode、回滚固件或禁用高风险功能。
  • • 保留“首个故障”与“最近故障”的不同策略。

三级:Host-side Post-mortem Analysis

三级阶段由 PC 或服务器完成:

  • • 依据 Build ID 找到精确 ELF、MAP、DWARF 和 ROM ELF。
  • • 解码 CPU Fault Status Register。
  • • 根据 PC、LR、SP、栈内容和 unwind 信息展开调用栈。
  • • 映射到函数、文件和源码行。
  • • 读取局部变量、线程状态和最近事件。
  • • 对大量设备记录按 fault signature 聚类。

Cortex-M 异常入口必须先理解硬件自动压栈

以下实现以 Arm Cortex-M 为主要示例。其他架构应保留同样的分层思想,但用各自的 trap frame、cause register 和特权级状态替换。

Cortex-M 进入异常时,硬件会把基础异常栈帧压入异常前正在使用的栈。基础帧通常包含:

R0
R1
R2
R3
R12
LR
PC
xPSR

CMSIS-Core 的 System Control Block 还提供 CFSR、HFSR、DFSR、MMFAR、BFAR、AFSR 等故障状态寄存器。具体存在性随 Cortex-M 代际和实现变化,应通过 CMSIS 设备头文件和对应架构手册判断,而不是硬编码所有芯片都存在相同寄存器。

EXC_RETURN 指示 MSP

EXC_RETURN 指示 PSP

异常前 Thread/Handler Context
原来使用哪个 SP?
基础/扩展栈帧位于 MSP 指向区域
基础/扩展栈帧位于 PSP 指向区域
R0-R3,R12,LR,PC,xPSR
是否存在 FPU 扩展帧?
S0-S15,FPSCR,保留字及基础帧
仅基础帧

必须注意以下细节:

  1. 1. Handler Mode 使用的 SP 不一定等于故障线程原 SP。Fault Handler 开始执行后,处理器处于 Handler Mode;真正的异常栈帧位置应根据入口时 LR 中的 EXC_RETURN 解码。
  2. 2. FPU 可能形成扩展栈帧。启用 FPU 和 lazy stacking 后,栈布局不能只按固定 8 个字处理。
  3. 3. xPSR 可能指示栈对齐填充。离线恢复原始 SP 时要考虑异常入口的对齐规则。
  4. 4. 异常可能发生在另一个异常中。此时原上下文可能本来就处于 Handler Mode,必须保留 IPSR、ICSR 和异常嵌套信息。
  5. 5. Armv8-M TrustZone 存在安全/非安全状态和更多栈指针。安全固件必须明确哪些信息允许跨域保存和上报。
  6. 6. 栈已经损坏时,任何基于栈的 C 函数调用都可能再次失败。因此应尽早切换到独立 Crash Stack,或者采用 CMSIS-View Fault Storage 这类不使用栈的最小保存实现。

最小汇编入口

下面代码只表达设计方法,不是所有 Cortex-M、编译器、FPU 和 TrustZone 配置下可直接复制的最终版本。正式工程应针对 GCC、Arm Compiler、IAR 以及具体 CPU 架构分别验证生成指令。

/**
 * @brief Cortex-M hardware-stacked basic exception frame.
 */

typedefstruct
{

uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;
uint32_t r12;
uint32_t lr;
uint32_t pc;
uint32_t xpsr;
crash_cm_basic_frame_t;

/**
 * @brief Capture a fatal Cortex-M exception using the original exception frame.
 *
 * @param frame      Pointer to the hardware-stacked exception frame.
 * @param exc_return EXC_RETURN value captured from LR at handler entry.
 */

__attribute__((noreturn))
voidcrash_capture_cortex_m(constcrash_cm_basic_frame_t *frame, uint32_t exc_return);

/**
 * @brief Minimal HardFault entry. No C prologue or local stack is allowed here.
 */

__attribute__((naked)) voidHardFault_Handler(void)
{
    __asm volatile(
"tst lr, 
#4            \n"
"ite eq                \n"
"mrseq r0, msp         \n"
"mrsne r0, psp         \n"
"mov r1, lr            \n"
"b crash_capture_cortex_m \n");
}

这段入口完成两件事:根据 EXC_RETURN 选择 MSP 或 PSP,并把 EXC_RETURN 传给后续捕获函数。更稳健的量产实现还会:

  • • 在汇编中保存 R4-R11。
  • • 读取 MSP、PSP、CONTROL、PRIMASK、BASEPRI、FAULTMASK、IPSR。
  • • 尽早切换到单独的 Crash Stack。
  • • 对 FPU 扩展帧和 lazy stacking 做架构相关处理。
  • • 对 Armv8-M 保存 MSPLIM、PSPLIM、安全域状态和 SecureFault 寄存器。
  • • 对多核系统保存 core id,并通过原子变量争夺 Crash Owner。

为什么需要独立 Crash Stack

如果故障是线程栈溢出、返回地址破坏、错误 SP、越界写或内存踩踏引起,继续使用原栈执行 C 代码会显著提高二次 Fault 概率。CrashCatcher 的重要设计就是切换到专用 g_crashCatcherStack后再运行收集逻辑。

原线程栈\n可能溢出/损坏
异常硬件压栈
汇编入口保存原 SP
切换专用 Crash Stack
执行有界 C 捕获函数
写 Crash Buffer

Crash Stack 应满足:

  1. 1. 静态分配,链接时确定地址和大小。
  2. 2. 与普通线程栈、堆、DMA Buffer 和 .bss清零区域隔离。
  3. 3. 最好带 MPU Guard 或 stack limit。
  4. 4. 编译期和运行期测量最大使用量,保留足够余量。
  5. 5. 不在 Crash Stack 上创建大数组;所有大数据复制到 Crash Buffer。
  6. 6. 若处理器支持 TCM 或独立 SRAM,可放入更可靠且不依赖 cache 的 RAM。

示例链接脚本:

/* Retained across software reset; startup code must not clear this section. */
.noinit.crash (NOLOAD) :
{
    . = ALIGN(8);
    __crash_record_start__ = .;
    KEEP(*(.noinit.crash_record))
    __crash_record_end__ = .;

    . = ALIGN(8);
    __crash_stack_bottom__ = .;
    KEEP(*(.noinit.crash_stack))
    __crash_stack_top__ = .;
} > RETENTION_RAM

如果芯片没有真正的 Retention RAM,而 .noinit仍位于普通 SRAM,则必须确认复位类型不会断电或由启动代码清除。Power-on Reset、Brownout Reset 和某些 Boot ROM 流程可能不保留普通 SRAM,不能把 .noinit当作绝对可靠的非易失存储。

一级快照应该保存什么

建议按优先级分层,而不是一次性把所有数据都塞入 Handler。

P0:必须保存

  • • magic、格式版本、记录长度、状态、CRC。
  • • crash reason、复位原因、CPU 架构、核心号、安全域。
  • • 固件 Build ID、镜像版本、镜像槽位、链接基址。
  • • PC、LR、SP、xPSR/状态寄存器。
  • • EXC_RETURN 或对应架构的 exception return 信息。
  • • Cortex-M 的 CFSR、HFSR、DFSR、MMFAR、BFAR、AFSR;RISC-V 的 mcause/mepc/mtval/mstatus;Xtensa 的 EPC、EXCCAUSE、EXCVADDR。
  • • 当前时间基准:uptime tick、RTC 时间、启动计数。
  • • 至少 128~512 字节的受保护栈窗口。

P1:高价值信息

  • • R0-R12、MSP、PSP、CONTROL、PRIMASK、BASEPRI、FAULTMASK、IPSR。
  • • FPU 状态、FPSCR 和必要的浮点寄存器。
  • • 当前线程 ID、TCB 地址、线程栈起止地址、线程名的固定长度副本。
  • • 最近几十条二进制事件记录。
  • • 当前中断号、临界区嵌套、调度锁嵌套、heap watermark。
  • • 关键外设状态,例如 DMA channel、Flash controller busy/error、总线错误寄存器。

P2:资源允许时保存

  • • 当前线程完整已用栈。
  • • 所有线程的 TCB 和栈片段。
  • • 全局 RAM、特定内存池、消息队列和协议状态。
  • • 外部存储控制器寄存器和缓存标签。
  • • 业务级诊断区,例如播放状态机、网络连接状态、最近命令。

Crash Capture Budget
P0 最小记录\n必须成功
剩余时间和存储是否足够?
提交 P0 并复位
P1 当前线程和最近事件
仍有预算?
提交 P0+P1
P2 多线程/大内存区域
提交完整记录

应通过配置决定层级,而不是把所有产品都编译成最大 Dump。资源很小的 MCU 可以只保存 P0;高端 MCU 或带 FRAM/eMMC 的系统可以保存 P0+P1+部分 P2。

Crash Record 设计

一个可维护的记录格式应具备:

  • • 固定头部,便于 Bootloader 或 PC 工具快速识别。
  • • 明确的 format_version和 header_size
  • • 整体长度和各段长度,避免解析越界。
  • • 架构无关头 + 架构相关块 + 平台扩展块。
  • • Build ID、镜像版本和地址模型。
  • • CRC32 或更强校验。
  • • 写入状态和 partial flags。
  • • 允许旧解析器跳过未知 TLV。

推荐使用 TLV 或 section table,而不是一个永远扩大的 C 结构体。

Fixed Header
Arch Registers TLV
Fault Status TLV
Stack Region TLV
RTOS Thread TLV
Recent Events TLV
Platform Registers TLV
CRC / Commit Marker

参考结构:

#define CRASH_RECORD_MAGIC          UINT32_C(0x43525348) /* "CRSH" */
#define CRASH_RECORD_FORMAT_VERSION UINT16_C(2)

/**
 * @brief Persistent crash record state encoded as one-way Flash bit transitions.
 */

typedefenum
{

    CRASH_RECORD_STATE_ERASED  = 0xFFFFFFFFu,
    CRASH_RECORD_STATE_WRITING = 0xFFFFFFFEu,
    CRASH_RECORD_STATE_VALID   = 0xFFFFFFFCu,
    CRASH_RECORD_STATE_USED    = 0xFFFFFFF8u,
crash_record_state_t;

/**
 * @brief Architecture-independent crash record header.
 */

typedefstruct
{

uint32_t magic;
uint16_t format_version;
uint16_t header_size;
uint32_t total_size;
uint32_t state;
uint32_t flags;
uint32_t sequence;
uint32_t crash_reason;
uint32_t reset_reason;
uint32_t architecture;
uint32_t core_id;
uint64_t uptime_ticks;
uint8_t  build_id[20];
uint32_t image_version;
uint32_t image_slot;
uint32_t payload_crc32;
uint32_t header_crc32;
crash_record_header_t;

/**
 * @brief Generic TLV header used to extend crash records without breaking parsers.
 */

typedefstruct
{

uint16_t type;
uint16_t version;
uint32_t length;
crash_tlv_header_t;

为什么 Build ID 是必需字段

仅记录“V1.2.3”通常不够,因为同一版本号可能存在不同编译时间、不同编译选项、不同链接布局或临时补丁。PC、LR 只有在与准确 ELF 对应时才有意义。

建议 CI 为每个镜像生成不可冲突的 Build ID,并归档:

firmware.bin
firmware.elf
firmware.map
firmware.sym
compiler version
linker script
Kconfig/menuconfig
source commit
submodule commit

可在 GNU 链接阶段启用或保留 ELF Build ID,也可以把 Git commit、配置哈希和镜像哈希组合成产品自己的 128/160 位标识。Crash Record 中应保存二进制值,不要只存可能被截断的字符串。

原子提交和掉电一致性

记录写入时必须假设随时会发生二次复位、看门狗到期或掉电。

推荐顺序:

  1. 1. 选择一个已擦除 Slot。
  2. 2. 写 state=WRITING或在 RAM 中清零记录区。
  3. 3. 写固定头中除 CRC 和 VALID 外的字段。
  4. 4. 写 TLV payload。
  5. 5. 计算并写 payload CRC、header CRC。
  6. 6. 执行必要的 memory barrier、cache clean 或存储同步。
  7. 7. 最后一步把状态从 WRITING 单向编程为 VALID。

开始新记录

payload和CRC完成后最后提交

中途复位/掉电

已上传或已转存

首故障保护

正常运行期后台擦除

启动后回收

ERASED
WRITING
VALID
CORRUPT
USED
PRESERVED

对 NOR Flash,应利用“擦除后为 1、编程只能从 1 变 0”的性质设计状态位,避免完成标记需要 0 变 1。Flash 擦除绝不能放在紧急 Fault 路径中,应在系统健康时预擦除 Crash Slot。

对 Retention RAM,可先写 payload 和 CRC,最后写 magic;启动时只有 magic、长度和 CRC 都合法才接收。若 CPU 有 data cache,必须确认写入目标是否 cacheable,并在复位前执行正确的 cache clean 和屏障,否则 RAM 或外部存储中的内容可能尚未真正落地。

存储后端如何选择

优先级通常如下:

  1. 1. Retention RAM/Backup SRAM:写入快、依赖少,适合作为一级快照。
  2. 2. 预擦除的专用内部 Flash Slot:掉电后仍保留,但要求异常时 Flash 控制器可用。
  3. 3. FRAM/MRAM:字节写、低延迟、无需擦除,非常适合 Crash Record,但成本和容量受限。
  4. 4. 外部 QSPI NOR 的专用分区:容量大,但可能与代码执行、cache、XIP 和业务写入共享控制器。
  5. 5. UART/SWO/USB/网络直传:只适合实验室或确认链路简单可靠的场景,不应作为现场唯一方案。
  6. 6. 普通文件系统:仅用于复位后的二级持久化。

选择一级存储
有 Retention/Backup RAM?
先写 RAM,复位后转存
有 FRAM/MRAM?
直接写固定地址记录
Flash 有预擦除 Slot\n且故障路径可用?
RAMFUNC 轮询写入
有可靠调试链路?
最小 UART/SWO Hex Dump
至少保存复位原因和少量 Backup Register

直接写 Flash 的前置条件

只有同时满足下列条件时,才建议在一级 Fault 路径直接写 Flash:

  • • Crash 区已经擦除,不需要执行耗时擦除。
  • • 写入函数、常量和必要数据位于 RAM/ROM,不依赖当前不可访问的 XIP Flash。
  • • 驱动使用轮询,不依赖中断、DMA、mutex 或调度器。
  • • Flash 控制器不处于擦除、编程、挂起或错误状态。
  • • 写入目标与当前执行 bank 的限制已评估。
  • • 电压和时钟满足编程条件。
  • • 有严格写入长度和超时上界。
  • • 看门狗预算允许;或者只保存 P0 后立即复位。

ESP-IDF 文档特别说明,panic handler 若位于 Flash,而故障发生时 Flash cache 被关闭或损坏,重新启用 cache 会增加风险;把关键 panic 路径放入 IRAM 可以降低对 Flash 可执行路径的依赖。这个思路对所有 XIP MCU 都适用。

安全复制栈窗口,避免二次 Fault

不能直接执行:

memcpy(record->stack, (void *)sp, 1024);

因为 sp可能已经越界、未对齐、指向外设、指向不可访问安全域,或距离 SRAM 末端不足 1024 字节。

正确流程是:

  1. 1. 从链接脚本、MPU 配置或 RTOS TCB 获得允许读取的 RAM 区间。
  2. 2. 判断 SP 是否落入已知栈区域。
  3. 3. 对起止地址执行防溢出的范围裁剪。
  4. 4. 只按架构允许的访问宽度读取。
  5. 5. 限制最大长度。
  6. 6. 若不能证明安全,只保存寄存器,不复制栈。
/**
 * @brief Copy a bounded stack window from a validated RAM range.
 *
 * @param dst          Destination buffer in the crash record.
 * @param dst_capacity Destination capacity in bytes.
 * @param sp           Fault-time stack pointer.
 * @param stack_low    Inclusive stack lower bound.
 * @param stack_high   Exclusive stack upper bound.
 * @return Number of bytes copied.
 */

staticsize_tcrash_copy_stack_window(uint8_t *dst,
size_t dst_capacity,
uintptr_t sp,
uintptr_t stack_low,
uintptr_t stack_high)

{
size_t available;
size_t copy_len;

if ((dst == NULL) || (stack_low >= stack_high) || (sp < stack_low) || (sp >= stack_high))
    {
return0U;
    }

    available = (size_t)(stack_high - sp);
    copy_len = (available < dst_capacity) ? available : dst_capacity;

/* The production implementation may use guarded word reads instead of libc memcpy. */
for (size_t i = 0U; i < copy_len; ++i)
    {
        dst[i] = ((constvolatileuint8_t *)sp)[i];
    }

return copy_len;
}

这段代码仍假定整个声明的栈区可读。对于有 MPU、ECC、总线超时或安全域隔离的系统,应进一步采用平台安全读取函数,并允许在读取失败时提前结束、设置 CRASH_FLAG_PARTIAL_STACK,而不是让整个记录失败。

调用栈保存不等于在 Handler 中完成 Backtrace

“保存调用栈”通常有四个层级:

层级一:PC + LR

最小成本,能定位当前指令和直接调用者,但对内联、尾调用、异常返回和 LR 已覆盖的场景信息有限。

层级二:原始栈窗口 + 地址扫描

保存 SP 后的一段原始内存,PC 工具扫描落在可执行代码区的 Thumb 地址,再结合反汇编判断哪些值可能是返回地址。实现简单,但存在误报,不能把扫描结果当作严格调用链。

层级三:帧指针链

使用固定帧指针可以较稳定地沿 frame chain 回溯,但会增加寄存器压力和代码开销。不同编译器、优化级别和 ABI 对 R7/R11 使用不同,必须由实际工具链验证。可只对关键模块使用 -fno-omit-frame-pointer,不必全局关闭优化。

层级四:Unwind Table / DWARF / RTOS Core Dump

保存完整寄存器和足够栈内存后,由 GDB、CrashDebug、Zephyr GDB Server、ESP-IDF espcoredump 或自研解析器根据 unwind tables、函数序言和调试信息还原调用栈。这是信息最完整的方式,但 ELF 和故障固件必须严格匹配。

故障 PC/LR/SP
原始栈字节
离线展开策略
帧指针链
ARM EHABI/.ARM.exidx
DWARF CFI
启发式返回地址扫描
符号化调用栈
候选调用路径

因此,Fault Handler 中最合理的行为是保存“可供展开的原料”,而不是调用一个复杂 backtrace 函数打印几十层符号。尤其当故障本身由栈破坏引起时,在线回溯很容易再次访问非法地址。

RTOS 场景下如何保存线程信息

RTOS Core Dump 的难点不是读取当前线程,而是内核数据结构可能已经损坏,且遍历所有线程会扩大故障路径。

当前线程最小信息

优先保存:

  • • 当前 TCB 指针。
  • • 当前线程静态 ID 和固定长度名称副本。
  • • 线程栈起止地址和当前 SP。
  • • 线程优先级、状态、CPU affinity。
  • • 调度锁、中断嵌套和临界区计数。

这些信息最好在系统正常运行时维护到一个只读或简单的 crash_runtime_info中,而不是故障时遍历内核对象。

所有线程快照

若系统资源允许,可参考 Zephyr DEBUG_COREDUMP_MEMORY_DUMP_THREADS或 ESP-IDF 的任务 TCB/Stack 快照:

  1. 1. 先保存当前故障线程。
  2. 2. 再按固定上限保存其他线程的 TCB 摘要。
  3. 3. 每个线程只保存已用栈或顶部固定窗口。
  4. 4. 对所有指针做地址范围验证。
  5. 5. 达到时间或容量预算立即停止,并设置 partial flag。

RT-Thread 的接入思路

RT-Thread Cortex-M 端口已经具备异常栈结构、独立中断栈、异常处理和 backtrace 相关能力。量产方案可在 rt_exception_hook或架构 Fault 入口中加入一级 Crash Capture,但不应只依赖 rt_kprintf输出。更稳妥的做法是:

RT-Thread Fault Entry
    -> 保存 exception_stack 原始内容
    -> 保存 rt_thread_self() 的静态摘要
    -> 写 .noinit/Backup SRAM Crash Record
    -> 复位
    -> board early init 校验并转存到 Flash

若当前故障发生在中断上下文,rt_thread_self()只能表示被中断线程,不代表当前正在执行的 ISR,因此还应保存 IPSR/IRQ number 和中断嵌套层级。

最近事件 Ring Buffer 比大量文本日志更适合 Crash Capture

Fault Handler 中临时打印文本往往失败,但系统可以在正常运行时维护一个固定大小的二进制 Flight Recorder。

/**
 * @brief Compact event stored in a lock-free diagnostic ring.
 */

typedefstruct
{

uint32_t timestamp;
uint16_t event_id;
uint16_t source_id;
uint32_t arg0;
uint32_t arg1;
crash_event_t;

事件可以包括:

  • • 线程切换。
  • • ISR 进入/退出。
  • • Flash 擦写开始/完成。
  • • DMA 启动/完成/超时。
  • • 内存分配失败。
  • • 协议状态切换。
  • • 看门狗喂狗点。
  • • 电源、电压和温度告警。
  • • 业务状态机关键节点。

一级捕获只需要复制 ring 的 head、sequence 和最近 N 条固定长度记录,不进行字符串格式化。主机工具再根据 event_id和固件版本映射为文本。这样既降低故障路径复杂度,也能还原“崩溃前发生了什么”。

多核系统的 Crash Owner 和其他核心冻结

双核或多核 MCU 可能出现多个核心同时进入 panic,若都写同一 Crash Slot 会互相破坏。

推荐设计:

  1. 1. 使用位于共享、原子可访问 RAM 中的 crash_owner
  2. 2. 第一个进入的核心通过 compare-and-swap 成为 owner。
  3. 3. Owner 发送 NMI/IPI 请求其他核心进入 secondary crash handler。
  4. 4. 每个核心把自己的寄存器写到独立 per-core 区域。
  5. 5. Owner 负责最终 CRC、commit 和复位。
  6. 6. 若其他核心在固定时间内未响应,设置 missing-core mask 后继续提交。
Shared Crash RecordCore 1Crash Owner StateCore 0Shared Crash RecordCore 1Crash Owner StateCore 0CAS owner = Core0成功发送 NMI/IPI Freeze写 Core1 寄存器块设置 ready bit写 Core0 和公共块CRC + VALID系统复位

不能无限等待另一个核心,否则故障核心可能因为对方锁死而无法完成最小记录。所有等待都必须有 cycle counter 或硬件 timer 上界。

看门狗、复位循环和 Safe Mode

Crash Capture 必须与 Watchdog 策略协同,而不是简单地在 Handler 中无限喂狗。

推荐策略

  • • P0 快照时间必须小于最短看门狗剩余时间。
  • • 如果有 Window Watchdog,不要在非法窗口喂狗。
  • • 若有 pretimeout interrupt/NMI,先保存轻量 Snapshot,再让最终 WDT Reset 发生。
  • • Handler 最多喂狗一次或按严格次数喂狗,防止故障代码永久卡住。
  • • Early Boot 统计连续崩溃次数和相同签名。
  • • 在短时间内重复崩溃超过阈值时进入 Safe Mode、回滚或关闭高风险模块。
crash_loop_count += 1
if same_build_id && boot_uptime < short_boot_threshold:
    rapid_crash_count += 1
else:
    rapid_crash_count = 0

if rapid_crash_count >= safe_mode_threshold:
    enter_safe_mode()

Crash Loop 统计也必须掉电安全,并避免每次启动都擦写同一 Flash 字。可使用 Backup Register、FRAM、带磨损均衡的 KV 或追加式小日志。

安全、隐私和可信边界

Crash Dump 可能包含密码、密钥、Token、用户数据、音频片段、网络报文和安全域内容。量产系统不能只考虑调试价值,还要考虑数据泄露。

建议:

  1. 1. 定义允许 Dump 的内存白名单,不默认 Dump 全 SRAM。
  2. 2. 对密钥区、加密引擎上下文和用户隐私 Buffer 设置 exclusion region。
  3. 3. 一级记录只做最小快照;复杂加密放到复位后的可信阶段。
  4. 4. Crash 分区设置访问控制,量产接口需要鉴权。
  5. 5. 上传时使用设备身份认证和传输加密。
  6. 6. 服务器按产品、版本、权限和保留周期管理符号和 Dump。
  7. 7. TrustZone 系统由 Secure 侧决定哪些 Secure Fault 信息可以暴露给 Non-secure 世界。
  8. 8. 记录格式中加入 redaction_policy_version,便于审计。

不能为了调试方便而把整个安全 RAM 或完整用户数据无条件写入可直接读取的外部 Flash。

一级捕获伪代码

下面示例展示“最小记录优先、逐级扩展、最后提交”的结构。

/**
 * @brief Capture a fatal exception into a preallocated crash record.
 *
 * @param reason     Architecture-independent crash reason.
 * @param arch_ctx   Architecture-specific exception context.
 * @param platform   Platform runtime information maintained during normal operation.
 */

__attribute__((noreturn))
voidcrash_capture(uint32_t reason,
constcrash_arch_context_t *arch_ctx,
constcrash_platform_context_t *platform)

{
crash_record_writer_t writer;
uint32_t irq_state;

    irq_state = crash_disable_maskable_interrupts();
    crash_switch_to_dedicated_stack_if_required();

    crash_writer_begin(&writer, &g_crash_record, sizeof(g_crash_record));
    crash_writer_set_state(&writer, CRASH_RECORD_STATE_WRITING);

    crash_write_fixed_header(&writer, reason, arch_ctx, platform);
    crash_write_arch_registers(&writer, arch_ctx);
    crash_write_fault_status(&writer, arch_ctx);
    crash_write_validated_stack_window(&writer, arch_ctx, platform);

if (crash_budget_remaining())
    {
        crash_write_current_thread(&writer, platform);
    }

if (crash_budget_remaining())
    {
        crash_write_recent_events(&writer, platform);
    }

    crash_writer_finalize_crc(&writer);
    crash_storage_barrier();
    crash_writer_commit_valid(&writer);
    crash_storage_barrier();

    (void)irq_state;
    crash_platform_reset_or_halt();
}

量产实现还应有“二次 Fault 保护”。最简单的方法是设置一个全局原子标志:如果进入 Fault 时发现 crash_in_progress已经置位,则不再执行完整路径,只保存一个极小 secondary-fault 标志,然后立即复位。

if crash_in_progress was already set:
    write SECONDARY_FAULT marker if possible
    reset immediately

Early Boot Collector 伪代码

/**
 * @brief Validate and persist a retained crash record during early boot.
 */

voidcrash_early_boot_collect(void)
{
constcrash_record_header_t *record = crash_retained_record();

if (!crash_record_header_sane(record))
    {
return;
    }

if (!crash_record_crc_valid(record))
    {
        crash_stats_note_corrupt_record();
        crash_retained_record_clear();
return;
    }

    crash_boot_policy_update(record);

if (crash_flash_slot_available())
    {
if (crash_flash_append_and_verify(record) == 0)
        {
            crash_retained_record_mark_used();
        }
    }

if (crash_boot_policy_requires_safe_mode())
    {
        crash_enter_safe_mode();
    }
}

Early Boot Collector 的执行顺序很重要。它应位于会清除 Retention RAM 的初始化之前,也应位于可能再次触发同类故障的复杂驱动初始化之前。Bootloader 与应用必须对 Crash RAM 的所有权、布局、版本和清除策略达成一致。

离线符号化流程

设备 Crash Record
解析 Header/TLV
读取 Build ID
从符号仓库定位 ELF/MAP/DWARF
校验架构/地址基址/镜像槽
解码 Fault Status
恢复寄存器
装载栈内存块
根因候选
GDB/CrashDebug/自研 Unwinder
函数/文件/行号/局部变量
生成故障签名并聚类

最基础的地址符号化可以使用:

arm-none-eabi-addr2line -e firmware.elf -f -C 0x08012345 0x08006789

但仅靠 addr2line不能恢复完整运行时调用栈。完整分析通常需要把保存的寄存器和内存块呈现给 GDB。CrashCatcher 配套 CrashDebug、Zephyr 的 coredump_gdbserver.py、ESP-IDF 的 idf.py coredump-debug都体现了这一模式。

故障签名

为了聚类,不能直接用完整 Dump 哈希,因为时间戳和栈变量会导致每条记录不同。可构造稳定签名:

signature = hash(
    build_id,
    crash_reason,
    normalized_pc,
    normalized_lr,
    top_3_call_frames,
    selected_fault_status_bits
)

同一根因的设备通常会落入同一签名组,研发可以按设备数、版本、时间和硬件批次排序处理。

记录容量和时间预算如何量化

Crash Record 容量不是越大越好,必须同时满足 RAM/Flash 容量、写入时间和看门狗时间上限。

B_record = B_header
         + B_arch_regs
         + B_fault_regs
         + B_current_stack
         + B_thread_meta
         + B_event_ring
         + B_platform_regs
         + B_crc_padding

一级捕获时间近似为:

T_capture = T_entry
          + T_register_copy
          + T_memory_copy
          + T_crc
          + T_storage_program
          + T_commit

必须满足:

T_capture < min(T_watchdog_remaining, T_power_hold_up, T_product_recovery_budget)

示例:

固定头和寄存器          512 B
当前栈窗口             1024 B
最近事件 64 条          1024 B
当前线程元数据           128 B
平台寄存器               256 B
对齐和 CRC               128 B
--------------------------------
总计                    3072 B

3KB 的一级记录通常已经能提供较高诊断价值。若 Retention RAM 只有 1KB,可缩减为 512B 栈窗口和 16 条事件;若有 64KB Crash 分区,可以在复位后追加多个一级记录,而不是在 Fault 中保存所有任务完整栈。

Crash Slot 数量

如果返修周期或上报周期较长,应估算:

N_slots >= peak_crash_rate_per_device * maximum_offline_duration

但频繁重复崩溃不应无限占用 Flash。可采用:

  • • First crash wins:保留首次异常,后续只累计次数。
  • • Ring slots:保留最近 N 次。
  • • Signature dedup:相同签名只更新计数和最后时间。
  • • Priority slots:未知新签名覆盖低价值重复签名。

Flash Crash 分区布局

Crash Partition
Superblock A
Superblock B
Slot 0
Slot 1
Slot 2
...
Header
TLV Payload
CRC
Commit Word

建议:

  1. 1. Slot 按 erase block 对齐,或多个小 Slot 共享一个预擦除 erase block。
  2. 2. Superblock 采用双副本、sequence 和 CRC,或完全避免必须更新的全局索引,启动时扫描 Slot。
  3. 3. 系统健康时后台维持至少一个 ERASED Slot。
  4. 4. 写完后读回关键头和 CRC。
  5. 5. 禁止 Crash 分区与 OTA image slot、配置区或文件系统元数据区重叠。
  6. 6. Bootloader、应用和量产工具共享同一份分区定义。

LittleFS 适合在二级阶段把 Crash Record 组织成文件,并提供掉电恢复和磨损均衡;但最小 Fault 路径更适合固定 Slot 和追加式写入,因为依赖更少、时间更容易上界化。

常见失败设计及原因

失败设计
为什么危险
推荐替代方案
Handler 中直接 printf全部寄存器
stdio 可能加锁、分配、等待 UART 中断,格式化耗时不可控
保存二进制寄存器,复位后格式化
Handler 中打开 FAT/LittleFS 文件
文件系统、块缓存、锁和 Flash 驱动可能已经不可信
先写 Retention RAM 或固定 Crash Slot
不判断 MSP/PSP,直接把当前 SP 当故障 SP
可能读取 Handler 栈而不是故障线程栈
解码 EXC_RETURN 并保存原始 frame
固定复制 4KB 栈
SP 可能越界,引发二次 Fault
依据已知栈边界裁剪并限制长度
只保存 PC,不保存 Build ID
不同固件地址含义不同
Build ID + ELF 符号仓库强绑定
先写 VALID,再写 payload
掉电后会出现“看似有效”的半记录
payload/CRC 完成后最后提交 VALID
Fault 中擦除 Flash
擦除时间长且电源/看门狗风险高
正常运行期预擦除 Slot
在线完整 Backtrace
依赖损坏栈、unwind table、复杂访问
保存原始栈,主机侧展开
每次重复崩溃都写完整 Dump
快速耗尽 Slot 和擦写寿命
签名去重、计数、Safe Mode
Dump 全 SRAM
容量大、耗时长、可能泄露密钥和用户数据
内存白名单和分级采集
异常后直接继续运行
系统状态不可证明,可能造成更严重数据破坏
提交快照后受控复位或安全停机

验证与故障注入

Crash Capture 不能只在“手工空指针”场景验证。必须建立自动化故障注入矩阵。

CPU 异常测试

  • • 空指针读写。
  • • 非法地址访问。
  • • 未对齐访问,并分别测试 trap 开关。
  • • 除零。
  • • 未定义指令。
  • • 非法异常返回。
  • • MPU 读、写、执行违规。
  • • 精确和可构造的非精确 BusFault。
  • • FPU 使用与未使用状态。
  • • Thread Mode 使用 PSP、MSP 两种场景。
  • • Fault 发生在 ISR 中。
  • • Fault 发生在嵌套中断中。

栈和内存破坏测试

  • • 当前线程栈溢出。
  • • MSP/Interrupt Stack 溢出。
  • • 栈 canary 被破坏但 SP 仍合法。
  • • SP 指向栈边界之外。
  • • TCB 指针损坏。
  • • Crash Buffer 前后 guard 被破坏。

存储和电源测试

  • • Flash 正在 program 时触发 Fault。
  • • Flash 正在 erase 时触发 Fault。
  • • XIP cache 关闭时触发 Fault。
  • • 一级写入任意字节位置断电。
  • • VALID 标记前后断电。
  • • CRC 破坏。
  • • Crash 分区满。
  • • 没有预擦除 Slot。
  • • Retention RAM 在不同复位类型下的保持性测试。

看门狗和二次 Fault 测试

  • • 一级捕获执行到每个阶段时触发 Watchdog。
  • • Crash Handler 内故意访问非法地址,验证 secondary-fault 快速复位。
  • • UART/Flash 后端永久 busy,验证超时上界。
  • • 双核同时 panic,验证 Crash Owner。
Fault Injection Test
确认设备可复位
确认记录 magic/version/length
确认 CRC
确认 Build ID 匹配
确认 PC/LR/故障寄存器
确认栈窗口边界
确认离线调用栈
确认重复崩溃和分区回收

验收标准不只是“能看到日志”,而应包括:记录成功率、最坏捕获时间、掉电恢复率、错误记录拒绝率、符号化正确率和对正常业务 RAM/Flash/实时性的影响。

推荐的工程落地步骤

第一步:先做 256B~1KB 的 P0 Retention Snapshot

保存:magic、版本、Build ID、PC、LR、SP、xPSR、CFSR、HFSR、BFAR、MMFAR 和 256B 栈窗口。不要一开始就做全线程 Dump。

第二步:加入独立 Crash Stack 和边界检查

用栈溢出和错误 SP 故障注入验证二次 Fault 风险。

第三步:实现 Early Boot 转存

把 .noinit/Backup SRAM 记录追加到专用 Flash Slot,完成 CRC、读回和 Slot 管理。

第四步:建立符号归档和 PC 工具

CI 自动归档 ELF/MAP/Build ID;PC 工具解析 Record,并调用 addr2line、GDB 或自研 unwinder。

第五步:增加二进制 Flight Recorder

先记录调度、Flash、DMA、协议和看门狗关键事件,再根据实际故障补充业务事件。

第六步:按产品资源扩展 RTOS 多线程和平台寄存器

只增加能显著缩短定位时间的数据,所有扩展都必须有容量和时间上限。

第七步:增加安全、去重、Safe Mode 和远程上报

确保大量现场设备发生同类异常时不会反复磨损 Flash 或形成无限重启循环。

不同资源等级的推荐配置

设备等级
一级保存位置
建议记录量
二级存储
分析能力
超小 MCU,RAM < 32KB
Backup Register / 128~512B .noinit
PC、LR、SP、Fault Reg、少量栈
可选固定 Flash 页
addr2line
+ 栈候选扫描
普通 Cortex-M + RTOS
Retention RAM 1~8KB
全寄存器、当前栈、线程摘要、事件 ring
预留 Flash Crash 分区
GDB/CrashDebug/自研工具
高端 MCU,多核/FPU
Retention SRAM 8~64KB
per-core 寄存器、多个线程栈、平台寄存器
双 bank Flash、FRAM、eMMC
完整 Core Dump 和聚类
ESP-IDF 平台
框架 panic/coredump
TCB、任务栈、寄存器
Flash coredump partition 或 UART
idf.py coredump-info/debug
Zephyr 平台
Zephyr coredump backend
MIN/THREADS/LINKER_RAM 可配置
logging、UDP、Flash Partition
coredump_gdbserver.py
联网量产设备
本地最小记录 + 上传队列
P0/P1 + Build ID + 事件
Flash/FRAM + 云端
符号化、签名聚类、版本趋势

Cortex-M 故障寄存器应该如何解码

保存寄存器只是第一步,离线工具还需要把状态位转换成可执行的诊断结论。Cortex-M3/M4/M7/M23/M33/M55 等内核的可用寄存器和位定义并不完全相同,解析器应根据 CPUID、架构编号和记录中的 feature flags 选择对应规则,不能假设所有 Cortex-M 都具备完整的 MemManage、BusFault 和 UsageFault 子寄存器。

HFSR:先判断 HardFault 是原生故障还是升级结果

HardFault 常见来源包括:

  • • 可配置 Fault 没有使能,最终升级为 HardFault。
  • • Fault Handler 本身又发生故障。
  • • 向量表读取失败。
  • • 调试事件或强制升级。

如果 HFSR 的 forced 类状态被置位,不能停留在“发生 HardFault”这个表面结论,而应继续解析 CFSR,找出最初的 MemManage、BusFault 或 UsageFault 原因。若只保存 HFSR 而不保存 CFSR,现场信息会严重不足。

CFSR:按三个子域分别解释

CFSR 逻辑上由 MemManage Fault Status、BusFault Status 和 UsageFault Status 组成。离线工具应输出原始值、有效位和人类可读解释,同时保留多个状态位并存的可能性。

子域
重点问题
典型分析方向
MemManage
指令取址违规、数据访问违规、异常入栈/出栈失败、懒浮点状态保存失败
MPU 配置、栈边界、安全域、错误函数指针
BusFault
精确数据总线错误、非精确异步错误、指令总线错误、异常栈操作失败
外部 SRAM/QSPI、DMA、总线超时、ECC、Flash 控制器
UsageFault
未定义指令、非法状态、非法 PC、未对齐、除零、协处理器错误
返回地址破坏、函数指针错误、编译选项、FPU 配置

“精确 BusFault”通常能把栈帧中的 PC 指向触发访问的指令;“非精确 BusFault”可能由缓冲写或异步总线事务延迟上报,故障 PC 未必是根因位置。此时最近事件、DMA 状态、总线矩阵寄存器和写缓冲相关配置比单纯 PC 更重要。

BFAR/MMFAR 只能在地址有效位成立时使用

BFAR 和 MMFAR 中的地址并非任何时候都有效。解析器必须先检查对应的 address-valid 状态,再显示“故障地址”。否则寄存器可能保留上一次值或无意义值,容易把研发引向错误方向。

异常入栈或出栈失败意味着原始栈帧可能不完整

如果状态寄存器表明异常入口 stacking、异常返回 unstacking 或 lazy FPU stacking 失败,硬件自动压栈的数据可能没有完整写入。此时:

  1. 1. 不应盲目信任 frame 中所有 R0~xPSR 字段。
  2. 2. 应提高 MSP、PSP、MSPLIM、PSPLIM、栈边界和 MPU 状态的诊断优先级。
  3. 3. PC 工具应把记录标为 frame_may_be_incomplete
  4. 4. 可以使用软件入口保存的 R4~R11、Handler LR、当前 MSP 等信息辅助判断。

保存原始值,不要只保存解码后的字符串

固件中的解码逻辑可能有错误,也可能在未来新增架构支持。Crash Record 应保存所有原始寄存器数值,文本解释只由复位后或 PC 工具生成。这样工具升级后可以重新分析历史记录。

FPU、Lazy Stacking 和扩展异常栈帧

带 FPU 的 Cortex-M4F、M7、M33、M55 等平台需要额外处理浮点上下文。异常发生时,处理器可能保存基础栈帧,也可能保存包含 S0~S15、FPSCR 等内容的扩展栈帧。启用 lazy stacking 后,浮点上下文是否已经真正压栈还与 FPCCR 等状态有关。

通用实现应遵循以下原则:

  1. 1. 保存 EXC_RETURN 原始值,不能在 C 函数中重新读取 Handler LR 代替。
  2. 2. 根据架构定义判断基础帧或扩展帧,不能固定偏移读取 PC。
  3. 3. 保存 FPCCR、FPCAR、FPDSCR 等实现存在的浮点控制状态。
  4. 4. 如果捕获函数自身使用浮点指令,可能触发新的 lazy stacking 或覆盖现场,因此 Crash Capture 模块应使用禁止浮点的编译选项,并避免任何浮点运算。
  5. 5. 完整保存 S16~S31 是否必要取决于 RTOS 上下文切换策略和故障线程是否使用 FPU;资源不足时至少保存栈中硬件帧、FPSCR 和 RTOS 已保存区的地址。
  6. 6. PC 工具要区分“寄存器未保存”“寄存器值为零”和“扩展帧读取失败”三种状态。

在测试中,应分别构造:故障前从未使用 FPU、刚使用 FPU、ISR 使用 FPU、线程切换后使用 FPU、lazy stacking 过程中发生总线错误等场景。只测试普通整数代码会掩盖扩展帧偏移错误。

TrustZone 和安全域故障

Armv8-M TrustZone 系统至少要面对 Secure/Non-secure 两套状态、不同栈指针和安全故障寄存器。通用 Crash 设计需要先确定安全策略,再决定技术实现。

安全侧统一捕获

由 Secure Firmware 提供最终 Crash Owner,可以读取并保存允许的 Secure 和 Non-secure 上下文。优点是信息完整、可信度高;风险是 Crash Dump 本身可能包含高敏感密钥和安全服务状态。

两侧分别捕获

Secure 和 Non-secure 各自维护独立记录区。Non-secure 记录不能读取 Secure RAM,Secure 记录由安全服务控制导出。优点是边界清晰;缺点是跨域调用故障的关联分析更复杂。

生产建议

  • • Secure Dump 默认只保存故障类型、PC 范围、SFSR/SFAR 和经过审核的栈窗口。
  • • 密钥、随机数种子、认证上下文和安全引擎寄存器加入永久排除列表。
  • • Non-secure 侧只能获取经过脱敏的安全故障摘要。
  • • 记录中明确保存 capture_security_state和 fault_security_state
  • • 安全侧负责校验 Non-secure 提供的指针,不能直接信任其栈边界或 TCB 地址。

跨 CPU 架构的端口层设计

通用 Crash 框架不应把 Cortex-M 的寄存器直接写进公共头。推荐把公共层、架构层、RTOS 层和存储层分开。

crash/core
    record writer
    TLV encoder
    CRC
    budget controller

crash/arch/cortex_m
    exception entry
    EXC_RETURN decode
    SCB fault registers

crash/arch/riscv
    trap entry
    mcause/mepc/mtval/mstatus

crash/arch/xtensa
    panic frame
    EPC/EXCCAUSE/EXCVADDR

crash/rtos
    baremetal
    RT-Thread
    FreeRTOS
    Zephyr adapter

crash/storage
    retention RAM
    internal flash slot
    FRAM
    UART/SWO

公共层只理解 architecturereason和 TLV,不解析具体寄存器。PC 工具根据 architecture TLV version 加载对应解析器。这样增加 RISC-V 或新 Cortex-M 代际时,不需要修改旧记录格式。

RISC-V 端口要点

RISC-V 同步异常通常至少保存:

  • • mepc:异常时程序计数器。
  • • mcause:中断/异常及原因编号。
  • • mtval:故障地址、非法指令值或架构定义信息。
  • • mstatus:中断使能、前一特权级等状态。
  • • 通用寄存器和原始 SP。
  • • 若运行 S/U Mode,还需保存对应级别的 cause、epc、tval 和 status。

与 Cortex-M 一样,trap entry 应使用最小汇编保存原始寄存器,再切换到可信异常栈。不能在 C prologue 之后才尝试恢复已经被覆盖的临时寄存器。

Xtensa/ESP 平台要点

Xtensa 需要按 ABI 和 windowed/call0 模式处理寄存器窗口。ESP-IDF 已经提供成熟 panic 和 coredump 流程,优先使用框架能力,而不是在其上叠加第二套相互冲突的异常入口。

解析器必须按不可信输入设计

Crash Record 可能因掉电、内存破坏、传输错误或旧固件 Bug 而损坏。PC 解析器不能假定来自设备的数据可信。

必须检查:

  1. 1. header_sizetotal_size是否小于实际文件长度。
  2. 2. 加法和对齐计算是否整数溢出。
  3. 3. TLV 长度是否越界、是否导致死循环。
  4. 4. TLV 数量是否超过合理上限。
  5. 5. 地址范围和内存块是否重叠或超出目标位宽。
  6. 6. Build ID 长度和编码是否合法。
  7. 7. 压缩数据解压后大小是否受限。
  8. 8. 架构版本未知时是否可以安全跳过。
  9. 9. CRC 不匹配时是否仍允许以“部分记录”模式提取固定头,而不是完全崩溃。
  10. 10. 符号文件和 Dump 的 Build ID 是否一致,不一致时必须拒绝给出确定源码行。

解析器应输出置信度。例如:

PC symbolization: exact
LR symbolization: exact
stack frame #1: unwind table
stack frame #2: frame pointer
stack frame #3: heuristic candidate
fault address: invalid/not-present
record integrity: partial, payload CRC failed

这样研发能区分客观现场、可靠推导和启发式候选,避免把扫描到的一个代码地址误认为真实调用链。

量产运行指标

Crash 系统本身也需要可观测性。建议长期统计:

  • • 一级快照成功次数。
  • • 一级快照 CRC 失败次数。
  • • Retention RAM 丢失次数。
  • • Flash 转存成功/失败次数。
  • • 无可用预擦除 Slot 次数。
  • • secondary fault 次数。
  • • 平均和最大捕获时间。
  • • 平均记录大小和 partial record 比例。
  • • 相同 Build ID 下各 Crash Signature 数量。
  • • 快速重启循环和 Safe Mode 进入次数。
  • • 上传成功率、积压量和最旧记录年龄。

这些指标可以发现“Crash 功能看似存在,但现场记录经常写坏”这类系统性问题。特别是捕获时间和 Flash 后端失败率,应在不同温度、电压、Flash 老化程度和最高业务负载下测量。

开源方案的组合建议

不同平台不必从零实现全部功能,可以按场景组合:

裸机 Cortex-M,小资源

  • • 用 CMSIS-View Fault Storage 作为 P0 最小寄存器记录。
  • • 增加产品自己的 Build ID、栈窗口和 Early Boot Flash Slot。
  • • 主机用 addr2line或简单 GDB 脚本分析。

Cortex-M + 自研 RTOS/RT-Thread

  • • 借鉴 CrashCatcher 的独立 Crash Stack、内存区域回调和 CrashDebug 思路。
  • • 复用 RTOS 已有异常栈结构和线程栈边界。
  • • 自研 TLV Record、Retention RAM 和 Flash 分区管理。

Zephyr

  • • 优先启用 Zephyr Coredump,选择 MIN、THREADS 或 LINKER_RAM 模式。
  • • 根据产品可靠性评估 logging、UDP、Flash Partition 或自定义 backend。
  • • 保留 Zephyr ELF,并使用官方 GDB Server 脚本。

ESP-IDF

  • • 使用 ESP-IDF 自带 Core Dump 到 Flash 或 UART。
  • • 根据 Flash cache 故障风险评估 panic handler IRAM 配置。
  • • 配置合理的最大任务数和 coredump partition 容量。
  • • 使用 idf.py coredump-infoidf.py coredump-debug和 ROM ELF。

大规模联网设备

  • • 本地采用最小可靠记录和分级内存区域。
  • • 可评估 Memfault Firmware SDK 或自建类似的 Build ID、分片上传、符号化和聚类平台。
  • • 无论使用商业服务还是自研服务,都应保留原始 Record 和精确符号归档,避免平台迁移后历史数据无法重放。

面试回答时的组织方式

回答这类题时,可以按以下顺序展开:

  1. 1. 先讲核心原则:Fault Handler 只做最小二进制快照,不做打印、文件系统和在线分析。
  2. 2. 再讲三阶段架构:一级 RAM/预擦除 Slot,二级启动后持久化,三级 PC 离线分析。
  3. 3. 说明 Cortex-M 入口:根据 EXC_RETURN 选择 MSP/PSP,保存硬件异常栈帧和 SCB Fault Registers。
  4. 4. 说明可靠性:独立 Crash Stack、地址白名单、有界栈复制、二次 Fault 标记、看门狗时间上限。
  5. 5. 说明记录格式:magic、版本、长度、Build ID、TLV、CRC、最后写 VALID。
  6. 6. 说明 Flash:正常期预擦除 Crash Slot;Fault 中只允许 RAMFUNC 轮询编程;更推荐先写 Retention RAM。
  7. 7. 说明调用栈:保存原始栈和寄存器,复位后或 PC 工具结合 ELF/DWARF 展开。
  8. 8. 说明 RTOS、多核、FPU、TrustZone、安全和隐私扩展。
  9. 9. 最后列出现成方案:CMSIS-View Fault Storage、CrashCatcher、Zephyr Coredump、ESP-IDF Core Dump、RT-Thread、Memfault。

一句话概括:把不可恢复异常处理设计成一个有时间上界、无动态依赖、可原子提交的“飞行数据记录器”;Fault 中只保存不可再现的现场,复位后再持久化,主机侧再完成符号化和根因分析。

参考链接

  • • CMSIS-View Fault Storage[8]
  • • CMSIS-Core SCB Register Mapping[9]
  • • CMSIS-Core SCB_Type[10]
  • • CrashCatcher[11]
  • • Zephyr Core Dump[12]
  • • ESP-IDF Core Dump[13]
  • • ESP-IDF Fatal Errors[14]
  • • RT-Thread Repository[15]
  • • RT-Thread Interrupt Management[16]
  • • Memfault Firmware SDK[17]
  • • Memfault Coredump Collection[18]
  • • littlefs[19]

引用链接

[1]CMSIS-View Fault Storage: https://arm-software.github.io/CMSIS-View/latest/group__Fault__Storage.html
[2]CrashCatcher: https://github.com/adamgreen/CrashCatcher
[3]Zephyr Coredump: https://docs.zephyrproject.org/latest/services/debugging/coredump.html
[4]ESP-IDF Core Dump: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/core_dump.html
[5]RT-Thread: https://github.com/RT-Thread/rt-thread
[6]Memfault Firmware SDK: https://github.com/memfault/memfault-firmware-sdk
[7]littlefs: https://github.com/littlefs-project/littlefs
[8]CMSIS-View Fault Storage: https://arm-software.github.io/CMSIS-View/latest/group__Fault__Storage.html
[9]CMSIS-Core SCB Register Mapping: https://arm-software.github.io/CMSIS_6/latest/Core/regMap_pg.html
[10]CMSIS-Core SCB_Type: https://arm-software.github.io/CMSIS_6/v6.0.0/Core/structSCB__Type.html
[11]CrashCatcher: https://github.com/adamgreen/CrashCatcher
[12]Zephyr Core Dump: https://docs.zephyrproject.org/latest/services/debugging/coredump.html
[13]ESP-IDF Core Dump: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/core_dump.html
[14]ESP-IDF Fatal Errors: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/fatal-errors.html
[15]RT-Thread Repository: https://github.com/RT-Thread/rt-thread
[16]RT-Thread Interrupt Management: https://rt-thread.github.io/rt-thread/page_interrupt_management.html
[17]Memfault Firmware SDK: https://github.com/memfault/memfault-firmware-sdk
[18]Memfault Coredump Collection: https://docs.memfault.com/docs/mcu/coredumps
[19]littlefs: https://github.com/littlefs-project/littlefs

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-29 05:53:27 HTTP/2.0 GET : https://15386.cn/a/470631.html
  2. 运行时间 : 0.193157s [ 吞吐率:5.18req/s ] 内存消耗:4,582.56kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=e6d947694843066efc4830084c89c8c7
  1. /yingpanguazai/ssd/ssd1/www/no.15386.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/no.15386.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/no.15386.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/no.15386.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/no.15386.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/no.15386.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/no.15386.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/no.15386.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/no.15386.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/no.15386.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/no.15386.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/no.15386.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/no.15386.cn/runtime/temp/97c957f747c268aee476c4e16775dd7c.php ( 12.06 KB )
  140. /yingpanguazai/ssd/ssd1/www/no.15386.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000803s ] mysql:host=127.0.0.1;port=3306;dbname=no_15386;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001555s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000702s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000673s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001351s ]
  6. SELECT * FROM `set` [ RunTime:0.000620s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001510s ]
  8. SELECT * FROM `article` WHERE `id` = 470631 LIMIT 1 [ RunTime:0.002412s ]
  9. UPDATE `article` SET `lasttime` = 1785275607 WHERE `id` = 470631 [ RunTime:0.001321s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 65 LIMIT 1 [ RunTime:0.001176s ]
  11. SELECT * FROM `article` WHERE `id` < 470631 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001284s ]
  12. SELECT * FROM `article` WHERE `id` > 470631 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.001779s ]
  13. SELECT * FROM `article` WHERE `id` < 470631 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.010388s ]
  14. SELECT * FROM `article` WHERE `id` < 470631 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.004414s ]
  15. SELECT * FROM `article` WHERE `id` < 470631 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.002167s ]
0.196738s