/**
- @defgroup runtime_stackcheck Runtime Stack Overflow Detection
- @ingroup runtime_diagnosis
- @brief 栈溢出与栈破坏检测机制
- 在嵌入式系统中,栈空间往往是最容易被忽视、却又最容易引发系统级异常的内存区域。
- 一次看似轻微的越界写操作,可能不会立刻触发崩溃,而是以随机崩溃、不可复现异常、
- 函数返回异常或调度混乱等形式,在系统后续运行过程中逐步显现。
- 栈溢出检测机制的核心目标并非在系统已经崩溃后还原现场,而是在栈结构被破坏或即将
- 越界时,尽早发现异常并阻止问题进一步扩散。
- OpenVela 针对不同层级的栈使用场景,提供了多种互补的栈溢出检测方案,覆盖函数栈与
- 线程栈两类对象,在性能开销、检测时效性与覆盖范围之间形成平衡。 */
/**
- @section stack_concepts Stack Concepts
- @brief 函数栈与线程栈的区分
- 在理解栈溢出检测机制之前,需要区分两个在工程实践中经常被混用但语义并不相同的概念:
- 函数栈(Function Stack)与线程栈(Thread Stack)。 */
/**
- @subsection function_stack Function Stack
- @brief 函数栈(函数调用栈帧)
- 函数栈是指单次函数调用过程中使用的栈帧(Stack Frame)。每次函数调用时,
- 编译器都会在当前线程栈上分配一个新的栈帧,用于保存:
-
- 返回地址
-
- 上一层栈帧指针
-
- 局部变量
-
- 寄存器保存区
- 当函数返回后,该栈帧随即被释放,栈指针恢复至调用前位置。
- 函数栈相关问题通常由以下因素引起:
-
- 过深的函数调用嵌套
-
- 递归失控
-
- 局部变量尺寸过大
-
- 非法指针写操作
- 此类问题的破坏范围通常局限于相邻栈帧,但一旦破坏返回地址等控制信息,
- 往往会导致不可预测的执行结果。 */
/**
- @subsection thread_stack Thread Stack
- @brief 线程栈(任务栈)
- 线程栈是在任务或线程创建时分配的一段连续、独立的内存区域。
- 该线程内所有函数调用、异常处理以及上下文切换相关的数据,
- 均在这块栈空间中完成。
- 线程栈在任务创建时一次性分配,其大小通常由系统配置决定,
- 常见范围从 1KB 到数十 KB 不等。
- 当线程栈发生溢出时,栈指针(SP)将越过合法边界,破坏相邻内存区域,
- 其影响范围更大,更容易引发系统级异常。 */
/**
- @section stack_methods Stack Check Mechanisms
- @brief OpenVela 栈溢出检测机制概览
- 基于函数栈与线程栈在生命周期和破坏范围上的差异,
- OpenVela 未采用单一栈保护方案,而是提供了多种互补的检测机制:
-
- 基于编译器插桩的栈保护(Stack Canaries)
-
- 基于函数插桩的栈检查(Function Instrumentation)
-
- 基于栈顶标记的栈检查(Watermark)
-
- 基于硬件机制的栈边界保护(ARMv8-M MSP/PSPLIM)
- 这些机制适用于不同架构、不同调用深度以及不同调试阶段,
- 可在系统发生严重异常之前提前暴露潜在风险。 */
/**
- @subsection stack_canary Stack Canaries
- @brief 基于编译器插桩的栈保护
- @par 基本配置
- @code
- CONFIG_STACK_CANARIES=y
- @endcode
- @par 工作原理
- 编译器在函数栈帧中插入一个哨兵值(canary),通常位于局部变量与控制信息之间。
- 在函数返回前,自动校验该值是否被篡改:
-
- 若值保持不变,函数正常返回
-
- 若值被覆盖,立即触发异常处理流程
- 任何越界写操作在覆盖返回地址之前,极有可能先破坏 canary,
- 从而被及时检测。
- @par 性能开销
-
- 函数入口写入 canary
-
- 函数返回路径执行一次校验
- 该机制引入的额外指令和栈空间占用极小,对系统性能影响较低。
- @par 适用场景
-
- 高时效性、高精度
-
- 仅适用于函数栈
-
- 适合检测局部缓冲区越界、错误的 memcpy/memset 操作 */
/**
- @subsection stack_instrument Function Instrumentation Stack Check
- @brief 基于函数插桩的栈检查
- @par 基本配置
- @code
- CONFIG_ARMV7M_STACKCHECK=y
- CONFIG_ARMV8M_STACKCHECK=y
- @endcode
- @par 工作原理
- 编译器通过 -finstrument-functions 在函数入口插入检测逻辑,
- 在函数执行期间检查当前栈指针(SP)是否仍处于合法范围内,
- 从而更早发现异常增长或越界风险。
- @par 性能开销
- 相较 Stack Canaries,该机制开销更高,主要来自:
-
- 函数入口处的额外检测指令
-
- 栈指针读取与边界比较
-
- 高频函数调用下的重复执行
- @par 适用场景
-
- 高时效性、高精度
-
- 仅适用于函数栈
-
- 适合捕获递归失控、调用深度异常增长等问题
-
- 仅支持 ARMv7-M / ARMv8-M 架构 */
/**
- @subsection stack_watermark Stack Watermark
- @brief 基于栈顶标记的线程栈检查
- @par 基本配置
- @code
- CONFIG_STACKCHECK_MARGIN=16
- @endcode
- @par 工作原理
- 在线程创建或初始化时,对整段线程栈进行固定模式填充。
- 在运行过程中,通过扫描栈区域中仍保持初始标记的部分,
- 推算线程历史上的最大栈使用深度。
- 若标记被覆盖至接近栈边界,说明该线程存在明显的栈溢出风险。
- @par 性能开销
-
- 线程创建时的一次性初始化
-
- 检查阶段对栈区域的顺序扫描
- @par 适用场景
-
- 时效性较低
-
- 仅适用于线程栈
-
- 适合长期监控和评估线程栈大小配置是否合理 */
/**
- @subsection stack_hardware ARMv8-M Hardware Stack Check
- @brief 基于硬件的栈边界保护(MSP/PSPLIM)
- @par 基本配置
- @code
- CONFIG_ARMV8M_STACKCHECK_HARDWARE=y
- @endcode
- @par 工作原理
- ARMv8-M 架构提供 MSPLIM 与 PSPLIM 寄存器用于定义主栈与进程栈的下界。
- 当栈指针向低地址移动并越过配置的下界时,
- 硬件会立即触发异常(UsageFault 或 HardFault)。
- 该检测完全由硬件完成,不依赖函数返回或软件插桩。
- @par 性能开销
-
- 几乎为零
- @par 适用场景
-
- 高时效性
-
- 适用于线程栈
-
- 仅支持具备 MSP/PSPLIM 的 ARMv8-M 平台 */