Skip to content

[arm][cortex-m4] Add hardware stack guard support - #11740

Merged
Rbb666 merged 1 commit into
RT-Thread:masterfrom
Zepp-Hanzj:agent/cortex-m4-hw-stack-guard
Aug 26, 2026
Merged

[arm][cortex-m4] Add hardware stack guard support#11740
Rbb666 merged 1 commit into
RT-Thread:masterfrom
Zepp-Hanzj:agent/cortex-m4-hw-stack-guard

Conversation

@Zepp-Hanzj

Copy link
Copy Markdown
Contributor

Description / 描述

为 Cortex-M4 补齐 RT_USING_HW_STACK_GUARD 硬件栈保护支持。此前该能力仅 Cortex-M7 和 Cortex-M33 可用,M4(尤其是大量 STM32F4 BSP)缺失。

Add hardware stack guard support for Cortex-M4, which was previously only available on Cortex-M7 and Cortex-M33.

Why / 为什么

栈溢出是嵌入式系统最高频、最隐蔽的故障之一。Cortex-M4 同样具备 MPU,但 RT-Thread 的 mprotect 框架尚未在 M4 上落地 rt_hw_stack_guard_init

Stack overflow is one of the most common and hardest-to-debug faults in embedded systems. Cortex-M4 has an MPU, but the mprotect framework has not yet provided rt_hw_stack_guard_init on M4.

Modified Files / 修改文件

核心移植 core porting (libcpu/arm/cortex-m4/):

  • mpu.c / mpu.h / mputype.h(新增):对齐 Cortex-M7 的 MPU 抽象层。唯一差异在默认内存类型属性——M4 无 L1 cache,cacheability 位简化为三种情况。
  • cpuport.c:新增 rt_hw_stack_guard_init()
  • context_gcc.S:补上缺失的 #include <rtconfig.h>,并在 PendSV 上下文切换路径中调用 rt_hw_mpu_table_switch()
  • SConscript:未开启内存保护时排除 mpu.c

BSP 参考实现 (bsp/stm32/stm32f407-fk407m2-zgt6/board/):

  • board.h:include rtthread.h,定义 NUM_STATIC_REGIONS#ifdef 保护)。
  • board.c:定义 static_regions[](Flash 区域只读,#ifdef 保护,不默认开启)。

Verification / 验证

  • 编译通过(STM32F407ZGT6 开启/关闭 RT_USING_HW_STACK_GUARD 两种配置均通过;frdm-k64f 无回归)。
  • 真机验证:STM32F407ZGT6 上创建小栈线程递归溢出,栈溢出被 MPU 保护区有效拦截。

@github-actions

Copy link
Copy Markdown

👋 感谢您对 RT-Thread 的贡献!Thank you for your contribution to RT-Thread!

为确保代码符合 RT-Thread 的编码规范,请在你的仓库中执行以下步骤运行代码格式化工作流(如果格式化CI运行失败)。
To ensure your code complies with RT-Thread's coding style, please run the code formatting workflow by following the steps below (If the formatting of CI fails to run).


🛠 操作步骤 | Steps

  1. 前往 Actions 页面 | Go to the Actions page
    点击进入工作流 → | Click to open workflow →

  2. 点击 Run workflow | Click Run workflow

  • Use workflow from 保持默认分支(通常为 master
    Keep the default branch (usually master) in Use workflow from
  • branch 输入框填写 PR 分支 agent/cortex-m4-hw-stack-guard
    Enter PR branch agent/cortex-m4-hw-stack-guard in the branch field
  • 设置需排除的文件/目录(目录请以"/"结尾)
    Set files/directories to exclude (directories should end with "/")
  1. 等待工作流完成 | Wait for the workflow to complete
    格式化后的代码将作为独立提交推送至你的分支。
    The formatting changes will be pushed to your branch as a separate commit.

完成后,提交将自动更新至 agent/cortex-m4-hw-stack-guard 分支,关联的 Pull Request 也会同步更新。
Once completed, commits will be pushed to the agent/cortex-m4-hw-stack-guard branch automatically, and the related Pull Request will be updated.

如有问题欢迎联系我们,再次感谢您的贡献!💐
If you have any questions, feel free to reach out. Thanks again for your contribution!

@github-actions github-actions Bot added BSP: STM32 BSP related with ST/STM32 BSP libcpu labels Aug 24, 2026
@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

📌 Code Review Assignment

🏷️ Tag: bsp_stm32

Reviewers: @Liang1795 @hamburger-os @wdfk-prog

Changed Files (Click to expand)
  • bsp/stm32/stm32f407-fk407m2-zgt6/board/board.c
  • bsp/stm32/stm32f407-fk407m2-zgt6/board/board.h

📊 Current Review Status (Last Updated: 2026-08-25 14:35 CST)


📝 Review Instructions

  1. 维护者可以通过单击此处来刷新审查状态: 🔄 刷新状态
    Maintainers can refresh the review status by clicking here: 🔄 Refresh Status

  2. 确认审核通过后评论 LGTM/lgtm
    Comment LGTM/lgtm after confirming approval

  3. PR合并前需至少一位维护者确认
    PR must be confirmed by at least one maintainer before merging

ℹ️ 刷新CI状态操作需要具备仓库写入权限。
ℹ️ Refresh CI status operation requires repository Write permission.

@Zepp-Hanzj
Zepp-Hanzj force-pushed the agent/cortex-m4-hw-stack-guard branch from d1ff4f6 to 07b426d Compare August 24, 2026 03:09
@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Cortex-M4 support for RT-Thread’s RT_USING_HW_STACK_GUARD (hardware stack overflow protection) by introducing an MPU backend aligned with existing Cortex-M7 / Cortex-M33 implementations, and wiring MPU table switching into the PendSV context switch path.
本次 PR 为 Cortex-M4 补齐 RT_USING_HW_STACK_GUARD(硬件栈保护)能力:新增与 Cortex-M7/M33 对齐的 MPU 后端,并在 PendSV 上下文切换路径中切换线程对应的 MPU 表。

Changes / 变更点:

  • Add Cortex-M4 MPU abstraction + implementation (mpu.c/.h, mputype.h) to support memory protection and stack guard.
    新增 Cortex-M4 的 MPU 抽象与实现(mpu.c/.h, mputype.h),用于内存保护与栈保护。
  • Add rt_hw_stack_guard_init() for Cortex-M4 and integrate MPU table switching into context_gcc.S PendSV path.
    Cortex-M4 增加 rt_hw_stack_guard_init(),并在 context_gcc.S 的 PendSV 路径中调用 rt_hw_mpu_table_switch()
  • Provide an STM32F407 BSP reference static MPU region definition (Flash RX) guarded by RT_USING_MEM_PROTECTION.
    提供 STM32F407 BSP 参考实现:在 RT_USING_MEM_PROTECTION 条件下定义静态 MPU 区域(Flash 只读可执行)。

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
libcpu/arm/cortex-m4/SConscript Excludes mpu.c when neither mem protection nor stack guard is enabled
libcpu/arm/cortex-m4/mputype.h Adds Cortex-M4 rt_mem_attr_t and dynamic region count definitions
libcpu/arm/cortex-m4/mpu.h Adds Cortex-M4 MPU permission/type macros and MPU APIs
libcpu/arm/cortex-m4/mpu.c Implements MPU init/region ops/table switch + MemManage handler for Cortex-M4
libcpu/arm/cortex-m4/cpuport.c Adds rt_hw_stack_guard_init() and adjusts formatting in several areas
libcpu/arm/cortex-m4/context_gcc.S Includes rtconfig.h and switches MPU table during PendSV context restore
bsp/stm32/stm32f407-fk407m2-zgt6/board/board.h Adds NUM_STATIC_REGIONS definition under mem protection
bsp/stm32/stm32f407-fk407m2-zgt6/board/board.c Adds static_regions[] (Flash RX) under mem protection

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread libcpu/arm/cortex-m4/cpuport.c Outdated
Comment on lines +612 to +616
__asm int __rt_ffs(int value)
{
CMP r0, #0x00
BEQ exit
CMP r0, #0x00 BEQ exit

RBIT r0, r0
CLZ r0, r0
ADDS r0, r0, #0x01
RBIT r0,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修复。Fixed.

The __rt_ffs inline assembly has been restored to the correct ARMCC syntax (same as the Cortex-M7 implementation), and wrapped with // clang-format off / // clang-format on to prevent the automatic clang-format workflow from corrupting it again.

已将 __rt_ffs 内联汇编恢复为正确的 ARMCC 语法(与 Cortex-M7 实现一致),并用 // clang-format off / // clang-format on 包裹,防止格式化工作流再次破坏。

@Rbb666

Rbb666 commented Aug 25, 2026

Copy link
Copy Markdown
Member

存在一个ci格式化问题,可以用评论区的ci机器人格式化下

@Zepp-Hanzj
Zepp-Hanzj force-pushed the agent/cortex-m4-hw-stack-guard branch from 4feec6f to 094bf55 Compare August 25, 2026 01:19
Comment thread libcpu/arm/cortex-m4/SConscript Outdated
if rtconfig.PLATFORM in ['iccarm']:
src += Glob('*_iar.S')

if not GetDepend('RT_USING_MEM_PROTECTION') and not GetDepend('RT_USING_HW_STACK_GUARD'):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当前只有 context_gcc.S 实现了 rt_hw_mpu_table_switch(),但 RT_USING_MEM_PROTECTION 和 RT_USING_HW_STACK_GUARD 在其他工具链下仍可开启,mpu.c 也会被 ARMCC/ArmClang/IAR 构建。这会导致 unsupported configuration 编译失败,或者更危险地成功构建但任务切换时没有更新 MPU。

建议把这段代码加上,约束一下

using_mpu = (
    GetDepend('RT_USING_MEM_PROTECTION') or
    GetDepend('RT_USING_HW_STACK_GUARD')
)

if using_mpu:
    if rtconfig.PLATFORM != 'gcc':
        raise RuntimeError(
            'Cortex-M4 memory protection currently supports GCC only'
        )
else:
    SrcRemove(src, 'mpu.c')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已采纳并修复。Fixed.

你的建议非常正确,非 GCC 工具链下开启 MPU 会导致 mpu.c 被编译但任务切换时不会更新 MPU 表。我已按你的建议加上约束,并做了一处小调整:

using_mpu = (
GetDepend('RT_USING_MEM_PROTECTION') or
GetDepend('RT_USING_HW_STACK_GUARD')
)

if using_mpu:
if rtconfig.PLATFORM not in ['gcc', 'llvm-arm']:
raise RuntimeError(
'Cortex-M4 memory protection currently supports GCC only'
)
else:
SrcRemove(src, 'mpu.c')

调整说明:M4 的 SConscript 中 ['gcc', 'llvm-arm'] 都会编译 *_gcc.S(其中包含 rt_hw_mpu_table_switch 实现),所以约束条件用了 not in ['gcc', 'llvm-arm'] 而不是 != 'gcc',避免误伤 llvm-arm 工具链。

已验证:gcc 下开启/关闭 MPU 两种场景均正常编译。

@Zepp-Hanzj
Zepp-Hanzj force-pushed the agent/cortex-m4-hw-stack-guard branch from 094bf55 to 7c057d0 Compare August 25, 2026 06:35
Enable RT_USING_HW_STACK_GUARD on Cortex-M4, which was previously only
available on Cortex-M7 and Cortex-M33.

Core porting (libcpu/arm/cortex-m4/):
- Add mpu.c/mpu.h/mputype.h aligned with the Cortex-M7 MPU layer. The only
  difference is the default memory type attribute: Cortex-M4 has no L1 cache,
  so the cacheability bits are reduced to three cases.
- Add rt_hw_stack_guard_init() to cpuport.c.
- Add missing #include <rtconfig.h> to context_gcc.S and invoke
  rt_hw_mpu_table_switch() in the PendSV context switch path.
- Update SConscript to exclude mpu.c when memory protection is disabled.

BSP reference (bsp/stm32/stm32f407-fk407m2-zgt6/):
- board.h: include rtthread.h and define NUM_STATIC_REGIONS.
- board.c: define static_regions[] marking the Flash region read-only,
  guarded by RT_USING_MEM_PROTECTION so it is not enabled by default.
@Rbb666
Rbb666 merged commit e2e6245 into RT-Thread:master Aug 26, 2026
108 checks passed
@wdfk-prog

Copy link
Copy Markdown
Contributor

我在 STM32F407 上实际集成并验证 Cortex-M4 RT_USING_HW_STACK_GUARD 时,发现目前这部分还存在几个比较关键的问题,建议后续补充/修正一下。

1. NUM_CONFIGURABLE_REGIONS / NUM_EXCLUSIVE_REGIONS 可能生成空宏,直接导致编译失败

实际生成的 rtconfig.h 中,我遇到了类似:

#define NUM_EXCLUSIVE_REGIONS
#define NUM_CONFIGURABLE_REGIONS

也就是宏被定义了,但是没有数值。

这样在 Cortex-M4 MPU 代码中:

#define NUM_DYNAMIC_REGIONS (2 + NUM_CONFIGURABLE_REGIONS)

预处理后会直接变成:

(2 + )

而:

for (i = 0U; i < NUM_EXCLUSIVE_REGIONS; i++)

会变成:

for (i = 0U; i < ; i++)

因此不是运行时配置问题,而是会直接产生非法 C 表达式并导致编译失败。

目前 components/mprotect/Kconfig 中:

config NUM_MEM_REGIONS
    int "Total number of memory protection regions supported by hardware"

config NUM_EXCLUSIVE_REGIONS
    int "Total number of exclusive memory regions ..."

config NUM_CONFIGURABLE_REGIONS
    int "Maximum number of configurable memory regions ..."

这几个 int 配置项目前都没有 default

建议至少:

  • NUM_CONFIGURABLE_REGIONS 提供安全默认值,例如 0
  • NUM_EXCLUSIVE_REGIONS 提供安全默认值,例如 0
  • NUM_MEM_REGIONS 因为取决于具体 MCU,可以由 BSP 明确配置,或者在参考 BSP 中给出正确值

例如 STM32F407 MPU 有 8 个 region,一个最小的 Stack Guard 配置可以明确写成:

NUM_MEM_REGIONS=8
NUM_CONFIGURABLE_REGIONS=0
NUM_EXCLUSIVE_REGIONS=0
NUM_STATIC_REGIONS=1

这样 Stack Guard 自身使用 2 个动态 region,也不会因为空宏导致编译失败。


2. STM32 CubeMX 工程中的 MemManage_Handler() 会与 mpu.c 冲突

Cortex-M4 的 mpu.c 现在已经提供了自己的:

void MemManage_Handler(void)

它负责读取 CFSR/MMFAR、查找 MPU region,并调用 MPU exception hook。

但是 STM32 CubeMX 默认生成的 stm32f4xx_it.c 同样包含:

/**
  * @brief This function handles Memory management fault.
  */
void MemManage_Handler(void)
{
  /* USER CODE BEGIN MemoryManagement_IRQn 0 */
  LOG_E("MemManage_Handler");
  /* USER CODE END MemoryManagement_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
    /* USER CODE END W1_MemoryManagement_IRQn 0 */
  }
}

实际链接时会直接出现:

multiple definition of `MemManage_Handler'

例如:

stm32f4xx_it.o: multiple definition of `MemManage_Handler';
mpu.o: first defined here

因此启用 Cortex-M4 MPU / Stack Guard 时,CubeMX stm32f4xx_it.c 中这个函数必须删除,或者通过其他机制保证它不会再形成 strong definition。

我认为至少应该在 Cortex-M4 / STM32F4 的使用说明中明确指出这一点,否则从现有普通 STM32 CubeMX BSP 打开 RT_USING_HW_STACK_GUARD 后,很容易直接遇到链接失败。


3. 目前缺少完整的 Cortex-M4 / STM32F4 使用和修改示例

现有 mprotect README 有通用 MPU 说明,但是针对这次 Cortex-M4 Stack Guard 支持,还缺少一个能够从普通 STM32F4 BSP 直接照着完成的集成示例。

建议至少给出一套完整示例,包括:

  1. menuconfig 中需要打开哪些配置;
  2. NUM_MEM_REGIONS / NUM_CONFIGURABLE_REGIONS / NUM_EXCLUSIVE_REGIONS 应该如何设置;
  3. board.h 中如何定义:
#define NUM_STATIC_REGIONS 1
  1. board.c 中如何定义:
rt_mem_region_t static_regions[NUM_STATIC_REGIONS]
  1. CubeMX BSP 中需要删除原来的 MemManage_Handler()
  2. Stack Guard 打开后线程实际可用栈空间会减少;
  3. 如何确认 MPU 初始化成功;
  4. 如何确认线程切换时 MPU region 已正确切换。

否则目前虽然底层 port 已经加入,但普通 BSP 用户仍然需要通过阅读 mpu.cmprotect 和 Cortex-M7/M33 实现才能推断出完整接入步骤。


4. 建议增加一个可以直接运行的 Stack Guard 验证测试

这项功能比较适合提供一个最小目标板测试,而不仅仅是说明“递归溢出验证通过”。

例如可以提供类似下面的测试:

#include <rtthread.h>
#include <mprotect.h>

#ifdef RT_USING_HW_STACK_GUARD

static void stack_guard_fault_hook(rt_mem_exception_info_t *info)
{
    rt_kprintf("MemManage fault:\n");
    rt_kprintf("  thread = %s\n",
               info->thread ? info->thread->parent.name : "NULL");
    rt_kprintf("  addr   = %p\n", info->addr);
    rt_kprintf("  MMFSR  = 0x%02x\n", info->mmfsr);
}

__attribute__((noinline))
static void stack_guard_overflow(unsigned int depth)
{
    volatile rt_uint8_t buffer[128];

    for (rt_size_t i = 0; i < sizeof(buffer); i++)
    {
        buffer[i] = (rt_uint8_t)(depth + i);
    }

    stack_guard_overflow(depth + 1);

    /* Prevent tail-call/unused-stack optimization. */
    (void)buffer[0];
}

static void stack_guard_test_entry(void *parameter)
{
    stack_guard_overflow(0);
}

static int stack_guard_test(void)
{
    rt_thread_t thread;

    rt_hw_mpu_exception_set_hook(stack_guard_fault_hook);

    thread = rt_thread_create("sg_test",
                              stack_guard_test_entry,
                              RT_NULL,
                              512,
                              20,
                              10);
    if (thread == RT_NULL)
    {
        return -RT_ENOMEM;
    }

    return rt_thread_startup(thread);
}
MSH_CMD_EXPORT(stack_guard_test, test Cortex-M MPU stack guard);

#endif /* RT_USING_HW_STACK_GUARD */

测试预期应该明确:

1. 工程能够正常编译、链接;
2. MPU 初始化成功;
3. sg_test 正常启动;
4. 递归持续消耗线程栈;
5. 访问 Stack Guard region 时产生 MemManage fault;
6. 进入 libcpu/arm/cortex-m4/mpu.c 的 MemManage_Handler;
7. exception hook 能看到 sg_test 线程以及对应的 MMFSR/fault address;
8. 不应该因为缺失 MemManage_Handler、空 Kconfig 数值等问题在编译或链接阶段失败。

如果 MMARVALID 有效,还可以进一步验证 MMFAR 是否位于该线程的 Stack Guard region 内。

另外建议至少增加下面的配置构建矩阵:

RT_USING_HW_STACK_GUARD=n       -> build PASS
RT_USING_HW_STACK_GUARD=y       -> build PASS
NUM_CONFIGURABLE_REGIONS=0      -> build PASS
NUM_EXCLUSIVE_REGIONS=0         -> build PASS

我认为 Stack Guard 属于比较底层的内存安全功能,除了“能够编译”之外,最好能留下一个可重复执行的目标板测试代码。这样后续 Cortex-M4、M7、M33 修改 MPU 相关代码时,也可以有一个明确的回归验证方法。

1 similar comment
@wdfk-prog

Copy link
Copy Markdown
Contributor

我在 STM32F407 上实际集成并验证 Cortex-M4 RT_USING_HW_STACK_GUARD 时,发现目前这部分还存在几个比较关键的问题,建议后续补充/修正一下。

1. NUM_CONFIGURABLE_REGIONS / NUM_EXCLUSIVE_REGIONS 可能生成空宏,直接导致编译失败

实际生成的 rtconfig.h 中,我遇到了类似:

#define NUM_EXCLUSIVE_REGIONS
#define NUM_CONFIGURABLE_REGIONS

也就是宏被定义了,但是没有数值。

这样在 Cortex-M4 MPU 代码中:

#define NUM_DYNAMIC_REGIONS (2 + NUM_CONFIGURABLE_REGIONS)

预处理后会直接变成:

(2 + )

而:

for (i = 0U; i < NUM_EXCLUSIVE_REGIONS; i++)

会变成:

for (i = 0U; i < ; i++)

因此不是运行时配置问题,而是会直接产生非法 C 表达式并导致编译失败。

目前 components/mprotect/Kconfig 中:

config NUM_MEM_REGIONS
    int "Total number of memory protection regions supported by hardware"

config NUM_EXCLUSIVE_REGIONS
    int "Total number of exclusive memory regions ..."

config NUM_CONFIGURABLE_REGIONS
    int "Maximum number of configurable memory regions ..."

这几个 int 配置项目前都没有 default

建议至少:

  • NUM_CONFIGURABLE_REGIONS 提供安全默认值,例如 0
  • NUM_EXCLUSIVE_REGIONS 提供安全默认值,例如 0
  • NUM_MEM_REGIONS 因为取决于具体 MCU,可以由 BSP 明确配置,或者在参考 BSP 中给出正确值

例如 STM32F407 MPU 有 8 个 region,一个最小的 Stack Guard 配置可以明确写成:

NUM_MEM_REGIONS=8
NUM_CONFIGURABLE_REGIONS=0
NUM_EXCLUSIVE_REGIONS=0
NUM_STATIC_REGIONS=1

这样 Stack Guard 自身使用 2 个动态 region,也不会因为空宏导致编译失败。


2. STM32 CubeMX 工程中的 MemManage_Handler() 会与 mpu.c 冲突

Cortex-M4 的 mpu.c 现在已经提供了自己的:

void MemManage_Handler(void)

它负责读取 CFSR/MMFAR、查找 MPU region,并调用 MPU exception hook。

但是 STM32 CubeMX 默认生成的 stm32f4xx_it.c 同样包含:

/**
  * @brief This function handles Memory management fault.
  */
void MemManage_Handler(void)
{
  /* USER CODE BEGIN MemoryManagement_IRQn 0 */
  LOG_E("MemManage_Handler");
  /* USER CODE END MemoryManagement_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
    /* USER CODE END W1_MemoryManagement_IRQn 0 */
  }
}

实际链接时会直接出现:

multiple definition of `MemManage_Handler'

例如:

stm32f4xx_it.o: multiple definition of `MemManage_Handler';
mpu.o: first defined here

因此启用 Cortex-M4 MPU / Stack Guard 时,CubeMX stm32f4xx_it.c 中这个函数必须删除,或者通过其他机制保证它不会再形成 strong definition。

我认为至少应该在 Cortex-M4 / STM32F4 的使用说明中明确指出这一点,否则从现有普通 STM32 CubeMX BSP 打开 RT_USING_HW_STACK_GUARD 后,很容易直接遇到链接失败。


3. 目前缺少完整的 Cortex-M4 / STM32F4 使用和修改示例

现有 mprotect README 有通用 MPU 说明,但是针对这次 Cortex-M4 Stack Guard 支持,还缺少一个能够从普通 STM32F4 BSP 直接照着完成的集成示例。

建议至少给出一套完整示例,包括:

  1. menuconfig 中需要打开哪些配置;
  2. NUM_MEM_REGIONS / NUM_CONFIGURABLE_REGIONS / NUM_EXCLUSIVE_REGIONS 应该如何设置;
  3. board.h 中如何定义:
#define NUM_STATIC_REGIONS 1
  1. board.c 中如何定义:
rt_mem_region_t static_regions[NUM_STATIC_REGIONS]
  1. CubeMX BSP 中需要删除原来的 MemManage_Handler()
  2. Stack Guard 打开后线程实际可用栈空间会减少;
  3. 如何确认 MPU 初始化成功;
  4. 如何确认线程切换时 MPU region 已正确切换。

否则目前虽然底层 port 已经加入,但普通 BSP 用户仍然需要通过阅读 mpu.cmprotect 和 Cortex-M7/M33 实现才能推断出完整接入步骤。


4. 建议增加一个可以直接运行的 Stack Guard 验证测试

这项功能比较适合提供一个最小目标板测试,而不仅仅是说明“递归溢出验证通过”。

例如可以提供类似下面的测试:

#include <rtthread.h>
#include <mprotect.h>

#ifdef RT_USING_HW_STACK_GUARD

static void stack_guard_fault_hook(rt_mem_exception_info_t *info)
{
    rt_kprintf("MemManage fault:\n");
    rt_kprintf("  thread = %s\n",
               info->thread ? info->thread->parent.name : "NULL");
    rt_kprintf("  addr   = %p\n", info->addr);
    rt_kprintf("  MMFSR  = 0x%02x\n", info->mmfsr);
}

__attribute__((noinline))
static void stack_guard_overflow(unsigned int depth)
{
    volatile rt_uint8_t buffer[128];

    for (rt_size_t i = 0; i < sizeof(buffer); i++)
    {
        buffer[i] = (rt_uint8_t)(depth + i);
    }

    stack_guard_overflow(depth + 1);

    /* Prevent tail-call/unused-stack optimization. */
    (void)buffer[0];
}

static void stack_guard_test_entry(void *parameter)
{
    stack_guard_overflow(0);
}

static int stack_guard_test(void)
{
    rt_thread_t thread;

    rt_hw_mpu_exception_set_hook(stack_guard_fault_hook);

    thread = rt_thread_create("sg_test",
                              stack_guard_test_entry,
                              RT_NULL,
                              512,
                              20,
                              10);
    if (thread == RT_NULL)
    {
        return -RT_ENOMEM;
    }

    return rt_thread_startup(thread);
}
MSH_CMD_EXPORT(stack_guard_test, test Cortex-M MPU stack guard);

#endif /* RT_USING_HW_STACK_GUARD */

测试预期应该明确:

1. 工程能够正常编译、链接;
2. MPU 初始化成功;
3. sg_test 正常启动;
4. 递归持续消耗线程栈;
5. 访问 Stack Guard region 时产生 MemManage fault;
6. 进入 libcpu/arm/cortex-m4/mpu.c 的 MemManage_Handler;
7. exception hook 能看到 sg_test 线程以及对应的 MMFSR/fault address;
8. 不应该因为缺失 MemManage_Handler、空 Kconfig 数值等问题在编译或链接阶段失败。

如果 MMARVALID 有效,还可以进一步验证 MMFAR 是否位于该线程的 Stack Guard region 内。

另外建议至少增加下面的配置构建矩阵:

RT_USING_HW_STACK_GUARD=n       -> build PASS
RT_USING_HW_STACK_GUARD=y       -> build PASS
NUM_CONFIGURABLE_REGIONS=0      -> build PASS
NUM_EXCLUSIVE_REGIONS=0         -> build PASS

我认为 Stack Guard 属于比较底层的内存安全功能,除了“能够编译”之外,最好能留下一个可重复执行的目标板测试代码。这样后续 Cortex-M4、M7、M33 修改 MPU 相关代码时,也可以有一个明确的回归验证方法。

@wdfk-prog

Copy link
Copy Markdown
Contributor
#include <rtthread.h>
#include <mprotect.h>

#if defined(RT_USING_MEM_PROTECTION) && defined(RT_USING_HW_STACK_GUARD)

static volatile rt_uint32_t g_stack_guard_sink;

/**
 * @brief MPU memory fault callback used by stack guard test.
 */
static void stack_guard_fault_hook(rt_mem_exception_info_t *info)
{
    rt_kprintf("\n");
    rt_kprintf("========== STACK GUARD FAULT ==========\n");

    if (info == RT_NULL)
    {
        rt_kprintf("info: NULL\n");
        return;
    }

    rt_kprintf("thread       : %s\n",
               info->thread != RT_NULL ?
               info->thread->parent.name : "NULL");

    rt_kprintf("fault address: %p\n", info->addr);
    rt_kprintf("region start : %p\n", info->region.start);
    rt_kprintf("region size  : %u\n",
               (unsigned int)info->region.size);
    rt_kprintf("MMFSR        : 0x%02x\n",
               (unsigned int)info->mmfsr);

    rt_kprintf("DACCVIOL     : %d\n",
               !!(info->mmfsr & (1U << 1)));
    rt_kprintf("MSTKERR      : %d\n",
               !!(info->mmfsr & (1U << 4)));
    rt_kprintf("MMARVALID    : %d\n",
               !!(info->mmfsr & (1U << 7)));

    rt_kprintf("=======================================\n");
}


/**
 * @brief Deliberately access one of the stack guard regions.
 *
 * parameter:
 *     0 -> bottom guard
 *     1 -> top guard
 */
static void stack_guard_probe_thread(void *parameter)
{
    rt_thread_t self;
    rt_uint8_t *stack_begin;
    rt_uint8_t *stack_end;
    volatile rt_uint8_t *fault_addr;

    self = rt_thread_self();

    stack_begin = (rt_uint8_t *)self->stack_addr;
    stack_end = stack_begin + self->stack_size;

    rt_kprintf("\n");
    rt_kprintf("========== STACK GUARD PROBE ==========\n");
    rt_kprintf("thread     : %s\n", self->parent.name);

#ifdef RT_USING_HW_STACK_GUARD
    rt_kprintf("stack_buf  : %p\n", self->stack_buf);
#endif

    rt_kprintf("stack_addr : %p\n", self->stack_addr);
    rt_kprintf("stack_size : %u\n",
               (unsigned int)self->stack_size);
    rt_kprintf("stack_end  : %p\n", stack_end);

    if ((rt_ubase_t)parameter == 0U)
    {
        /*
         * Cortex-M stack grows downward.
         *
         * stack_addr points to the first usable byte after
         * the bottom guard region.
         *
         * Therefore stack_addr - 1 is guaranteed to be
         * inside the bottom guard region.
         */
        fault_addr = stack_begin - 1;

        rt_kprintf("probe      : bottom guard\n");
    }
    else
    {
        /*
         * stack_addr + stack_size is the first byte of
         * the top guard region.
         */
        fault_addr = stack_end;

        rt_kprintf("probe      : top guard\n");
    }

    rt_kprintf("fault addr : %p\n", fault_addr);
    rt_kprintf("MPU CTRL   : 0x%08x\n",
               (unsigned int)MPU->CTRL);
    rt_kprintf("SHCSR      : 0x%08x\n",
               (unsigned int)SCB->SHCSR);

    rt_kprintf("about to access protected address...\n");

    /*
     * Give console output a chance to finish before faulting.
     */
    rt_thread_mdelay(100);

    /*
     * Expected:
     *
     * This write MUST trigger MemManage_Handler.
     *
     * This statement must never complete.
     */
    *fault_addr = 0x5A;

    /*
     * Reaching this point means stack guard protection failed.
     */
    rt_kprintf("ERROR: guard write succeeded!\n");

    while (1)
    {
        rt_thread_mdelay(1000);
    }
}


/**
 * @brief Consume stack recursively until the bottom stack guard is hit.
 */
__attribute__((noinline))
static void stack_guard_consume_stack(rt_uint32_t depth)
{
    volatile rt_uint8_t frame[128];
    rt_size_t i;

    /*
     * Force actual memory accesses so the compiler cannot optimize
     * the local stack object away.
     */
    for (i = 0; i < sizeof(frame); i += 16)
    {
        frame[i] = (rt_uint8_t)depth;
    }

    g_stack_guard_sink += frame[0];

    /*
     * Large enough depth to exceed the test thread stack.
     */
    if (depth < 1000U)
    {
        stack_guard_consume_stack(depth + 1U);
    }

    /*
     * Prevent tail-call optimization.
     */
    g_stack_guard_sink += frame[depth % sizeof(frame)];
}


/**
 * @brief Real stack overflow test thread.
 */
static void stack_guard_overflow_thread(void *parameter)
{
    rt_thread_t self = rt_thread_self();

    RT_UNUSED(parameter);

    rt_kprintf("\n");
    rt_kprintf("========== REAL STACK OVERFLOW ==========\n");
    rt_kprintf("thread     : %s\n", self->parent.name);
    rt_kprintf("stack_addr : %p\n", self->stack_addr);
    rt_kprintf("stack_size : %u\n",
               (unsigned int)self->stack_size);

    rt_thread_mdelay(100);

    stack_guard_consume_stack(0);

    rt_kprintf("ERROR: stack overflow was not detected!\n");
}


/**
 * Usage:
 *
 * stack_guard_test bottom
 * stack_guard_test top
 * stack_guard_test overflow
 */
static int stack_guard_test(int argc, char **argv)
{
    rt_thread_t thread;

    if (argc != 2)
    {
        rt_kprintf("usage:\n");
        rt_kprintf("  stack_guard_test bottom\n");
        rt_kprintf("  stack_guard_test top\n");
        rt_kprintf("  stack_guard_test overflow\n");
        return -RT_ERROR;
    }

    rt_hw_mpu_exception_set_hook(stack_guard_fault_hook);

    if (rt_strcmp(argv[1], "bottom") == 0)
    {
        thread = rt_thread_create("sg_bottom",
                                  stack_guard_probe_thread,
                                  (void *)0,
                                  1024,
                                  10,
                                  10);
    }
    else if (rt_strcmp(argv[1], "top") == 0)
    {
        thread = rt_thread_create("sg_top",
                                  stack_guard_probe_thread,
                                  (void *)1,
                                  1024,
                                  10,
                                  10);
    }
    else if (rt_strcmp(argv[1], "overflow") == 0)
    {
        thread = rt_thread_create("sg_over",
                                  stack_guard_overflow_thread,
                                  RT_NULL,
                                  1024,
                                  10,
                                  10);
    }
    else
    {
        rt_kprintf("unknown test: %s\n", argv[1]);
        return -RT_ERROR;
    }

    if (thread == RT_NULL)
    {
        rt_kprintf("create stack guard test thread failed\n");
        return -RT_ERROR;
    }

    rt_thread_startup(thread);

    return RT_EOK;
}

MSH_CMD_EXPORT(stack_guard_test, test MPU hardware stack guard);

#endif

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BSP: STM32 BSP related with ST/STM32 BSP libcpu

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants