调试一个基于STM32F429的RTX5多任务工程时,我打开Peripherals -> RTX RTOS -> System and Thread Viewer,窗口空白,左下角弹出一行让人印象深刻的提示:'os_Info': 'osRtxInfo' not found。
这个报错在MDK+RTX5组合里非常典型,几乎每个用RTX5做完整调试视图的工程师都会遇到至少一次。它并不是代码逻辑出错,而是MDK的RTX RTOS Viewer插件没法从符号表里找到RTX5内核用来描述所有任务、队列、信号量、事件标志的全局结构体osRtxInfo。也就是说,RTX5运行得很好,任务调度正常,但调试器失去了“观察窗口”。
这篇文章会把这个问题的来龙去脉讲清楚,然后给出我实际验证过的几种解法:在Linker层面强制保留符号、在代码里按需引用一次、通过分散加载文件KEEP对应数据,以及调试器/Event Recorder配置上的常见遗漏。适合正在被这个弹窗折磨的嵌入式开发人员,也适合刚接触CMSIS-RTX5、想用RTX5调试视图排查任务优先级或栈溢出的朋友。
1. 这个报错到底在说什么
1.1 RTX5的调试视图靠什么工作
RTX5(CMSIS-RTOS2实现)调试视图不是“凭空”知道当前有几个线程、哪个就绪、哪个阻塞。它的底层逻辑是:MDK的调试插件在进入调试状态后,会读取目标固件里的全局变量osRtxInfo。这是一个类型为osRtxInfo_t的结构体,定义在RTX5的rtx_lib.h中,内部挂着一整条内核对象链表:线程控制块(TCB)、消息队列、互斥锁、信号量、事件标志等。说人话就是,RTX5每创建一个内核对象,都会把对象控制块挂到osRtxInfo指向的多条链表上;调试器只要拿到这个根节点,就能像翻通讯录一样把整个系统的运行时状态遍历出来。
这里有个容易被忽略的细节:osRtxInfo这个名字里带os前缀,但它不是osKernelGetInfo()之类API的返回值,也不是用户代码主动维护的数据。它是在rtx_lib.c里定义、在rtx_lib.h里通过extern导出的全局变量,普通业务代码根本不会直接引用它。内核用它管理对象,调试器通过它“偷看”系统,而用户程序本身有没有引用它,完全不影响运行。问题恰恰出在这里——一个没有任何业务代码引用的全局变量,在编译器/链接器眼中就是“死符号”,很容易被优化策略和链接器垃圾回收机制当作无用数据剔除掉。
1.2 为什么提示的是“not found”而不是编译报错
理解这个报错的关键,是分清“编译期报错”和“调试器插件的提示”是两回事。如果你在源码中直接使用osRtxInfo,却没有包含正确头文件或命名空间不对,大概率编译不过,会出现undefined identifier之类的错误。而'os_Info': 'osRtxInfo' not found这个提示出现在调试会话中,是MDK的RTOS Viewer插件在加载ELF文件、解析符号表后,发现没有找到osRtxInfo符号,于是弹出提示,并且干脆不渲染RTOS视图。
“not found”主要有三种可能:第一,RTX5组件压根没被正确编译进工程,比如RTE里没有勾选CMSIS-RTOS2,或者RTE_Components.h里没有定义与CMSIS_RTOS2_RTX5相关的宏,导致rtx_lib.c根本没参与编译;第二,库编进来了,但符号被优化器/链接器移除;第三种很隐蔽——整个符号虽然还在,但MDK的调试配置里RTOS类型没有选成RTX5,或者勾选了Event Recorder却没有打开对应的Trace通道,调试器明明有符号也不知道该去访问它。大家最常遇到的是第二种,所以才有了“方法齐全”这个说法。
2. 先别急着改代码,花两分钟确认根因
2.1 三种根因对照
我在整理这个问题时,把网上能搜到的案例和自己在几个工程里实测的情况汇总成了一张对照表。你拿到报错后第一件事不是去改Linker选项,而是先确认自己属于哪一类。
| 根因分类 | 典型现象 | 快速判断方法 |
|---|---|---|
| RTX5组件未编译 | 代码里能用osKernelStart,但整个工程的map文件搜索不到osRtxInfo | 打开工程的map文件搜索osRtxInfo,没有任何结果 |
| 符号被优化/链接器剔除 | map文件里有osRtxInfo字样,但并没有对应的全局符号地址,或只有* ABSENT标记 | 搜索后看到* ABSENT或缺少全局地址,基本就是被去掉了 |
| 调试器配置错误 | map文件里能查到符号且有地址,但RTX Viewer仍报not found | 检查Debug的Trace设置、RTOS下拉框、Event Recorder配置 |
这里要额外说一句,网上很多教程会让你直接给rtx_lib.h里osRtxInfo声明加__attribute__((used)),这确实有效,但这属于改了库文件,一旦你用Pack Installer升级CMSIS版本,修改会被覆盖,而且对整个团队来说也不够优雅。除非是临时验证,我一般不推荐走改库的路子。
2.2 用map文件快速确认符号是否存活
打开MDK的工程目录,默认每个Target都会生成Listings\xxx.map文件。用任意文本编辑器打开,在搜索框里输入osRtxInfo。如果你能看到类似下面的内容,说明符号已经存在于可执行镜像里:
osRtxInfo 0x20000098 Data 4 rtx_lib.o(.data.osRtxInfo)注意Data和地址是重点。如果只搜到在Global Symbols列表里有osRtxInfo但没有任何地址,或者根本搜不到,那就说明这个符号在链接阶段没有被保留。此时基本可以判定是符号被优化/链接器垃圾回收掉了。
如果map里正常,那么继续排查配置。在Options for Target -> Debug -> Settings -> Trace页签,确认是否勾选了Enable,RTOS那栏是否选了RTX5。不同MDK版本这个界面长得不太一样,5.30以后的版本通常在Trace页面会有一个RTOS下拉框,默认是Off,你要手动改成RTX5。这一步非常容易被忽略,尤其是从旧工程升级过来的时候。
3. 解决方案:让符号活着,并让调试器找到它
3.1 方案一:在Linker层面强制保留符号(最推荐)
要让一个“无人引用”的全局符号在被编译后不被armlink剔除,最直接的办法就是告诉链接器:这个符号我要保留。在MDK的Options for Target -> Linker -> Misc controls(杂项控制)里追加一行参数:--keep=osRtxInfo。
注意这里的--keep是armlink(ARM链接器)的参数,它和编译器选项不在同一个输入框。经常有人把参数写到了C/C++的Misc Controls里,结果编译报unrecognized command line option,这属于填错位置。正确位置是Linker标签页的Misc Controls,填完后重新编译链接。Arm Compiler 5(armcc)和Arm Compiler 6(armclang)都适用,因为最终调用的是同一套armlink。
有的版本里,如果你想让符号稳定出现在map文件里,可以再加一个--map参数看完整打印,其实MDK默认就会生成map文件,因此不用额外加。另外,如果你碰到的是osRtxInfo所在的section被编译器丢弃,光加--keep还不够,需要在源码层面对定义处加section属性再KEEP,这种情况非常少见,我放在3.3节讲。
3.2 方案二:在代码里“正经引用”一次
如果你不想动工程配置,或者你用的是其他IDE/GCC工具链(比如你实际编译用的是arm-none-eabi-gcc,--keep参数在GCC下叫--undefined或-u),那么最通用的做法是在某个C源文件里让osRtxInfo被真正引用一次。下面这段代码是我项目里一直在用的:
/* keep_osrtx_info.c */ #include "rtx_lib.h" #if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6000000) __attribute__((used)) #elif defined(__GNUC__) __attribute__((used)) #endif static void keep_osrtx_info_reference(void) { volatile uint32_t dummy = (uint32_t)(uintptr_t)&osRtxInfo; (void)dummy; }这段代码的思路很简单:把osRtxInfo的地址赋给一个volatile局部变量,只要这个函数被__attribute__((used))标注,编译器就不会把它优化掉,链接器也就自然看到了对osRtxInfo的引用,进而强制保留该全局变量。如果你担心函数未被调用、又被更高层的gc-section移除,可以在链接器Misc Controls里再加一个--keep=keep_osrtx_info_reference,双保险。
这段代码的好处是不改库文件、不依赖特定链接器,就算你哪天把工具链换成GCC也能直接复用。缺点是多占了几行源码,不过对嵌入式项目来说几乎可以忽略不计。
3.3 方案三:用分散加载文件或section属性强制保留
有一种极端情况:osRtxInfo所在的section被编译器单独标记为remove,或者在gc-sections时整体丢弃。此时--keep=osRtxInfo可能仍然找不到符号,因为链接器在section完全被编译器剔除后,已经没有可保留的对象了。
对应的解法是在自己的补充文件里重新声明一个带section属性的引用,比如:
__attribute__((section(".keep.osRtxInfo"), used)) const uint32_t keep_osRtxInfo_section = (uint32_t)(uintptr_t)&osRtxInfo;然后在分散加载文件里对这个section做KEEP:
KEEP keep_osRtxInfo_section.o (.keep.osRtxInfo)通常在MDK的Linker页签,如果你勾选了Use Memory Layout from Target Dialog,是不会手动维护scatter文件的。你需要先把布局方式改成Use Scatter File,否则你在分散加载文件里写的KEEP不生效。这个方案适合那些项目本身就使用了自定义分散加载文件的大工程,对于普通STM32模板工程反而显得有点杀鸡用牛刀。
3.4 方案四:检查调试器与Event Recorder配置
如果map文件确认符号存在,那问题多半在调试器配置。把Options for Target -> Debug页签右侧的Settings打开,找到Trace页签。先勾选Enable,再把RTOS下拉框从Off改为RTX5。此时Trace页签下方的Cortex-M相关设置也会激活,记得把Core Clock填成和你实际MCU主频一致的数值,否则System Analyzer里显示的时间轴全是错的,查任务占用率时会误判。
如果你是配合Event Recorder使用,还需要在RTE组件里确认Event Recorder已勾选,并且CMSIS-RTOS2的配置里使能Event Recorder相关宏。具体是在RTX_Config.h里检查RTX_EVENT_RECORD是否定义为1,以及在EventRecorderConf.h里确认缓冲区大小和使能通道。Event Recorder本身不负责读取osRtxInfo,它只是提供更详细的事件时间戳,但没有它,System Analyzer的很多统计是不可用的。很多帖子把它们混为一谈,容易让新手白折腾半天,这里先帮你拆开:RTX Viewer找不到符号,优先查符号和Linker;System Analyzer没数据,再查Trace配置和Event Recorder。
4. 实操记录:从报错到RTX5 Viewer正常显示
4.1 复盘一个真实工程的报错现场
为了把这几种方案讲透,我拿一个实际调过的板子举例。硬件是STM32F429,MDK 5.36,CMSIS-RTX版本5.8.0。工程里启用了RTX5,创建了led_task、uart_task、key_task三个线程。烧录后线程跑得很欢快,串口日志一切正常,但我想看线程切换的时间线,打开Peripherals -> RTX RTOS -> System and Thread Viewer,窗口一片空白,同时有报错弹窗'os_Info': 'osRtxInfo' not found。
当时我第一反应也是代码写错了,检查了RTE_Components.h,没什么问题;又确认了RTX_Config.h存在。真正一锤定音的是map文件——搜索osRtxInfo,只搜到一行:
osRtxInfo * ABSENT好,这条路断了。符号没进镜像。
4.2 修改步骤与验证结果
我当时优先选择了方案一,因为不改源码、不影响其他人。在Options for Target -> Linker -> Misc controls填入:
--keep=osRtxInfo然后重新编译链接。这次再打开map文件,搜索关键字,出现了:
osRtxInfo 0x20000098 Data 4 rtx_lib.o(.data.osRtxInfo)说明符号存活。回到调试会话,重新下载全速运行,再打开System and Thread Viewer,三个线程的名称、优先级、状态(Ready/Running/Blocked)、事件标志和等待中的队列长度全部正常显示。这个方案在MDK 5.30到5.39的各个版本里我都试过,稳定有效。
如果你用的是极老的MDK 5.2x,又恰好还在用AC5,也可以把同样的参数加到armlink的命令行。MDK的Options for Target -> Linker页签里如果不勾选Use Memory Layout from Target Dialog,还能看到完整的L1链接命令,直接在里面加也行。新版本MDK的这个UI改成了全自动布局,没法直接编辑命令行,只能在Misc controls里追加。
4.3 打开RTX Viewer后的确认细节
符号保留后,RTOS Viewer能显示任务列表只是第一步。我建议你再打开View -> System Analyzer,那个窗口会画出每个线程在时间轴上的运行窗口。如果时间轴空白,或者任务名对不上,先检查Trace页签的Core Clock是不是写成了外部晶振频率而没有乘以PLL倍频。以F429为例,外部晶振25MHz、PLL到180MHz,Core Clock就得填180000000,填成25000000会导致时间轴横向比例完全失真,看起来像是所有线程都在同一个时间片里,非常容易误判。
还有一个小要点,RTX5的RTOS Viewer需要调试器连接保持全速运行,如果你在断点处停下来看视图,看到的多半是最后一个触发断点时的静态快照,很多新手以为视图卡住了,其实只是没有跑起来。让程序全速运行几秒钟,再点暂停,配合System Analyzer的Buffered模式,才能看到完整的调度历史。
5. 常见问题与排查技巧实录
5.1 速查表:为什么我改了还不行
| 症状 | 可能原因 | 处理办法 |
|---|---|---|
加了--keep=osRtxInfo仍报not found | 参数填到了C/C++编译器选项,而不是Linker的Misc controls | 调整到Linker页签 |
| 符号在map里存在,但Viewer空白 | 调试器Trace页签未开启RTX5,或Core Clock不对 | 打开Trace,选RTX5,填对主频 |
| 使用Event Recorder但没有任务时间线 | Event Recorder组件未勾选或宏未使能 | 勾选Event Recorder,确认RTX_EVENT_RECORD |
| 使用AC6编译器,代码段被整体gc | 优化级别过高,且未使用used属性 | 给引用函数加__attribute__((used)) |
| 升级CMSIS包后突然失效 | Pack更新覆盖了旧的改库方案 | 记录改动点,改用Linker参数或代码引用 |
在MDK 5.39里找不到RTOS下拉框 | 高版本UI位置变化 | 在Debug的设置页Trace页签里仔细找,或在Components里确认RTX5版本一致 |
这张表里的最后一类问题,很多人都遇到。MDK近几个版本把调试器的UI重新整理过,有的版本把RTOS下拉框放到Debug -> Settings -> Trace页签的最底部,有的版本移到了Components视图。如果实在找不到,可以直接在命令行工具链里手动验证:fromelf -z --symbols .\Objects\xxx.axf,看看osRtxInfo是否在镜像符号表里,这一步能帮你把问题边界收窄到“符号在不在”和“工具能不能访问”这两条线。
5.2 我踩过的坑和建议
第一个坑是改库文件。我之前图省事,直接改了rtx_lib.h,给osRtxInfo声明补了__attribute__((used))。当天确实有效,但后来升级CMSIS版本,全工程报一堆莫名错误,排查半天才发现是库文件被覆盖,之前加的东西全没了。从那以后我给自己定了个规矩:不修改Pack里带$CMSIS$路径的库文件,非要改就放到用户代码目录里做wrapper。这也是我为什么推荐方案一和方案二的原因,它们不碰库。
第二个坑是团队协作时,Linker选项里手动加的--keep不会自动带注释。新同事接手工程,看到Misc controls里躺着一行--keep=osRtxInfo,不知道是干嘛的,某天“清理工程配置”顺手删掉,RTX Viewer接着就会复现问题。我的建议是把这行参数的用途写进工程的README或代码注释里。这一点看起来不起眼,但对维护周期超过一年的固件项目真的很重要。
第三个坑是关于优化级别。有些项目开-O3配合-flto,--keep并不能完全保证符号存活,因为链接时优化会重新分析和裁剪函数与数据。如果遇到这种场景,请优先用方案二在源码层面引用,并给引用函数打上__attribute__((used)),必要时再在Linker层面双保险。我在一个对性能比较极端的音频DSP工程里就碰到过这个情况,加--keep加源码引用双保险后才稳定。
项目做得久了,你会发现这类“RTOS明明在跑,调试器却看不见”的问题,本质上都是调试符号的生命周期管理问题。osRtxInfo被优化掉这个坑,我在至少五个不同工程里都遇见过,解法不尽相同,但排查思路始终是那三步:先看map确认符号在不在,再确认Linker有没有保留它,最后检查调试器的RTOS配置。把这条链路弄清楚,以后再遇到类似的“not found”,基本都能十分钟内定位。
我个人最推荐的方式是方案一(Linker加--keep),如果项目用了LTO再叠加方案二。最后提醒一句:升级CMSIS Pack之前,先备份工程配置和改动记录,让调试器依赖的符号稳定留在镜像里,比什么都重要。