map文件里显示栈占用不大,运行时却出现栈空间不足,这两个结果为什么对不上?先要分清看到的是什么:链接器计算的是调用链的静态栈需求,调试器显示的栈水位则来自本次运行留下的内存痕迹。它们各有用途,也各有盲区。下面以使用ILINK和C-SPY的IAR Embedded Workbench for Arm为例,说明报告生成及差异核查方法。
一、IAR怎样生成并查看栈使用统计报告
栈使用分析结果写在链接器map文件中。只开启map输出,还不一定有调用链统计,需要同时启用栈分析,并重新完成链接。
1、开启栈分析与map输出
应在准备核查的构建配置中设置。Debug与Release的优化、库配置可能不同,各自产生的栈需求也可能不同,不能拿一个配置的报告解释另一个程序。
①点击【Project】→【Options】→【Linker】→【Advanced】,勾选【Enable stack usage analysis】。
②切换到【List】,勾选【Generate linker map file】,确认输出位置。
③点击【OK】,再点击【Project】→【Rebuild All】。
2、查看调用链,而非只读汇总数字
打开本次构建生成的map文件,找到STACK USAGE部分,查看各调用图根节点及其最深调用链。程序入口、任务入口和中断入口需要分别核对,不能只看main附近的一条链。
Max Use表示相应类别中最深调用链的深度,Total Use表示该类别各根节点最深调用链深度之和。后者也不直接等于整个系统实际同时占用的栈空间,还要结合栈划分和嵌套关系理解。
①点击【File】→【Open】,打开当前配置生成的map文件。
②点击【View】→【Messages】→【Build】,检查本次链接是否出现栈分析相关警告。
二、IAR栈使用报告未反映实际峰值如何核查
报告数字偏小,未必是计算出了错误结果,也可能是分析所需的信息不全,或比较时漏掉了另一块栈。先确定差异属于哪一种,再补充数据。
1、检查调用链是否缺少信息
函数指针、递归、汇编模块或第三方目标文件,都值得重点查看。链接器不知道回调可能进入哪些函数,后面的栈需求就可能没有完整纳入。递归没有明确深度,也难以给出可靠估计。
可以通过栈使用控制文件补充调用目标、根节点和已确认的栈信息。填写内容必须有代码或设计依据,不能为了消除警告随意指定数值。
①点击【Project】→【Options】→【Linker】→【Advanced】。
②在栈分析设置的【Control file】中选择已准备的.suc文件,点击【OK】后重新构建。
若模块缺失栈信息,不能把未计算出的部分当作零占用。内联汇编自行调整栈帧或调用函数,也需要单独检查。
2、对照运行水位,确认观测方法
C-SPY可以在运行前用0xCD填充栈区,暂停时检查填充值被改写到哪里,据此估算已使用范围。它能留下部分历史使用痕迹,但不等同于连续记录栈指针的最低位置。
例如函数预留了较大的局部空间,却没有写遍这片内存,水位就可能低于实际预留深度;其他代码误写栈区,也可能让水位看起来偏高。
①在启动调试前,点击【Tools】→【Options】→【Stack】,勾选【Enable graphical stack display and stack usage tracking】。
②启动C-SPY,点击【View】→【Stack】→【Stack 1】,运行测试并暂停查看。
3、核对中断、任务栈与启动阶段
Cortex-M工程使用RTOS时,任务栈与主栈通常需要分别检查。某个任务的水位正常,不能说明中断使用的栈也足够;静态调用链之外,还应考虑硬件异常压栈、操作系统保存上下文等额外开销。
此外,修改过启动代码或栈初始化方式,应检查链接配置中的栈区定义是否同步更新。调试器依赖这些信息确定监视范围,范围错了,读数就失去比较意义。
①点击【View】→【Stack】→【Stack 1】,核对当前显示的栈范围。
②点击【Tools】→【Options】→【Stack】,检查【Stack pointer(s)not valid until program reaches】对应的位置。
默认从main附近开始验证的设置,不宜直接用来判断整个启动阶段。任务栈则应结合相应RTOS的调试插件或栈检查机制逐项确认。
三、怎样把报告用于栈空间调整
找到差异后,不建议马上缩小栈。应先补齐分析缺口,再覆盖容易产生深调用的运行场景,确认哪些数据可以作为分配依据。
1、补测平时很少进入的路径
正常通信跑很久,也未必经过错误处理、格式化输出或嵌套中断等路径。测试应围绕可能增加栈需求的场景安排,而不能只比较运行时间。
同一轮测试中,应在复位前保存观察结果。C-SPY重新加载或复位时会初始化栈填充值,之前的水位不能指望自动保留。
①点击【Debug】→【Go】,执行选定的压力或异常场景。
②暂停后选择【Stack 1】,记录水位及对应输入,再开始下一轮测试。
2、设置提醒,同时保留必要余量
栈水位接近边界时,可以启用阈值提醒和栈指针越界警告,帮助发现问题。不过,填充值检查无法保证捕获每次越界,提醒没有出现也不能作为栈绝对充足的证明。
余量应结合分析完整性、中断嵌套、任务配置及后续变化确定,不宜统一套一个百分比。调整后还需重新构建和测试。
①点击【Tools】→【Options】→【Stack】,设置【%stack usage threshold】,勾选【Warn when exceeding stack threshold】。
②按需勾选【Warn when stack pointer is out of bounds】,在【Debug Log】中检查相关提示。
总结
栈空间是否够用,不能靠一个漂亮的数字决定。静态报告应说明计算覆盖了哪些调用,运行水位应说明测试经过了哪些场景,两边没有覆盖的部分还需要补查。建议把map文件、固件构建信息和测试条件一起保存,修改代码或升级库后再作比较。希望这些方法能帮大家更有依据地分配栈空间,避免过早压缩余量。如有相关需求,欢迎进一步交流。