本文面向将 cmsis-runtime-watchpoint 集成到裸机、FreeRTOS 或其他 RTOS 工程的开发者。
当前发布版本为 0.3.0。
核心模块只依赖 C99 和目标芯片对应的 CMSIS Device Header,不依赖堆内存或 RTOS。
- DWTv1:Cortex-M3/M4/M7。
- DWTv2:Cortex-M33/M35P/M52/M55/M85。
- 支持单地址、对齐的 1/2/4 字节 READ、WRITE 和 READ_WRITE。
- Cortex-M0/M0+/M23 不支持本模块所需的完整 DebugMonitor 路径。
FreeRTOS 是可选适配层。其他系统只需实现一个能够从 DebugMonitor 异常上下文唤醒 工作线程的回调,无需修改核心代码。
推荐使用 CMake:
set(CWP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
add_subdirectory(path/to/cmsis-runtime-watchpoint)
target_link_libraries(app PRIVATE cwp::core)
target_compile_definitions(app PRIVATE
CWP_CMSIS_HEADER="your_device_header.h"
CWP_PROVIDE_DEBUGMON_HANDLER=1)也可以直接加入以下源码:
src/cwp.c
src/cwp_policy.c
src/cwp_debugmon_gcc.c # 仅在使用模块提供的 GCC/Clang handler 时加入
并添加 include/ 到头文件搜索路径。CWP_CMSIS_HEADER 必须是最终芯片的 Device
Header,例如 stm32f407xx.h,不要仅指定通用 core_cm4.h。
如果应用已经拥有 DebugMon_Handler,不要定义 CWP_PROVIDE_DEBUGMON_HANDLER。现有
handler 应根据 EXC_RETURN 选择 MSP/PSP,再调用:
cwp_debugmon_dispatch(hardware_stack_frame, exc_return);简单工程可调用:
cwp_status_t status = cwp_init(debug_monitor_priority);产品工程推荐显式限定比较器:
cwp_init_config_t init = {
.struct_size = sizeof(cwp_init_config_t),
.api_version = CWP_INIT_CONFIG_VERSION,
.debug_monitor_priority = 8,
.claim_policy = CWP_CLAIM_MASK,
.comparator_mask = (1UL << 0) | (1UL << 2)
};
cwp_status_t status = cwp_init_ex(&init);默认 CWP_CLAIM_FREE_ONLY 不覆盖已经启用的外部比较器。退出时调用 cwp_deinit(),
模块会恢复自己接管的比较器、DEMCR 位和原 DebugMonitor 优先级。
连接 halting debugger 时,DWT 匹配通常由调试器接管。产品脱机验证应确保
CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk 为 0。
static volatile uint32_t guarded_value;
cwp_handle_t write_guard;
cwp_watchpoint_config_t config = {
.address = (uintptr_t)&guarded_value,
.size = sizeof(guarded_value),
.access = CWP_ACCESS_WRITE,
.user_data = 0x1001U
};
status = cwp_create(&config, &write_guard);要区分读和写,尤其是 DWTv1,必须分别创建 READ 与 WRITE 观察点。DWTv1 的 READ_WRITE 命中不能反推出本次实际访问类型。
DebugMon_Handler 是上半段,只负责保存事件并调用事件就绪回调。回调运行在异常
上下文,必须有界、不可阻塞、不可打印、不可分配内存。
裸机可以在主循环调用 cwp_poll_event()。其他 RTOS 可以注册自己的 ISR-safe 唤醒:
static void event_ready_from_exception(void *context)
{
/* 仅调用当前系统允许在异常上下文使用的通知原语。 */
}
cwp_set_event_ready_callback(event_ready_from_exception, context);下半段线程被唤醒后应循环调用 cwp_poll_event(),直到队列为空。一次通知可能对应
多个事件,不能假设通知次数与事件数严格相等。
target_link_libraries(app PRIVATE cwp::freertos freertos_kernel)worker 示例:
static cwp_freertos_notifier_t notifier;
static void watchpoint_worker(void *argument)
{
cwp_event_t event;
cwp_freertos_notifier_bind_current(¬ifier);
cwp_init(8);
/* 在这里创建观察点。 */
for (;;) {
(void)cwp_freertos_wait(portMAX_DELAY);
while (cwp_poll_event(&event)) {
/* 当前为 task/PSP:分类、记录、告警或复位。 */
}
}
}DebugMonitor 会调用 FreeRTOS FromISR API,因此其数值优先级必须不小于
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY。例如最大系统调用优先级为 5 时,
可选择 5~15;STM32F407 示例使用 8。
适配层默认使用 task notification slot 0。如需其他 slot,在编译所有相关源文件时
定义 CWP_FREERTOS_NOTIFICATION_INDEX,并保证小于
configTASK_NOTIFICATION_ARRAY_ENTRIES。删除 worker 前必须先解绑 notifier。
若需要区分合法与非法访问,可以把已知合法函数放入 .cwp_authorized:
static void authorized_write(volatile uint32_t *address, uint32_t value)
CWP_AUTHORIZED_CODE;
static void authorized_write(volatile uint32_t *address, uint32_t value)
{
*address = value;
__DSB();
}链接脚本必须导出半开区间:
.cwp_authorized :
{
. = ALIGN(4);
__cwp_authorized_start__ = .;
KEEP(*(.cwp_authorized))
KEEP(*(.cwp_authorized.*))
. = ALIGN(4);
__cwp_authorized_end__ = .;
} > FLASH__DSB() 只用于让已知授权访问在离开授权 section 前接收 DebugMonitor,从而稳定
白名单分类。未知非法写入不需要、也不可能提前调用 __DSB();DWT 会自动捕获它。
验证工程中的“非法”测试函数为了让测试输出在不同优化等级下保持确定,也在访问后
执行了 __DSB()。这只是测试同步点,不是实际踩踏检测的使用要求。
cwp_event_t.pc 是硬件异常栈中的异常返回 PC,并设置
CWP_EVENT_FLAG_PC_IS_EXCEPTION_RETURN。它不保证等于精确的 LDR/STR 地址。
下半段日志应使用 stacked_pc 名称,并结合 ELF 向前反汇编:
arm-none-eabi-addr2line -f -C -e firmware.elf 0x08001232
arm-none-eabi-objdump -dS firmware.elf \
--start-address=0x08001220 --stop-address=0x08001240DWT 是访问后的审计与定位工具。若必须在写入发生前阻止访问,需要 MPU 或 TrustZone;若必须获得完整指令执行历史,需要 ETM 等追踪能力。
- CMSIS Device Header 与目标 MCU 一致。
- 目标内核实现 DebugMonitor 和可用 DWT 数据地址比较器。
- DebugMonitor handler 正确区分 MSP、PSP 和浮点扩展帧。
- 不覆盖调试器、ETM 或其他组件使用的比较器。
- FreeRTOS 下 DebugMonitor 优先级满足
FromISR约束。 - READ/WRITE 分开配置,并验证 8/16/32 位访问。
- 验证队列溢出时
cwp_dropped_event_count()可观测。 - 断开 halting debugger 后重新上电验证。
- 检查深睡眠唤醒后 DWT 寄存器是否需要重新配置。
完整 STM32F407 工程位于 examples/stm32f407_freertos/,对应真机输出见
docs/VALIDATION_STM32F407_FREERTOS.md。