ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ARMCC与Keil MDK实战:ARM Compiler 5老工程编译排障指南

ARMCC与Keil MDK实战:ARM Compiler 5老工程编译排障指南 简介这是一套ARM官方推出的ARMCC编译工具链资源包面向嵌入式开发者用于在ARM架构处理器上完成C与C代码的编译、链接与调试支持Cortex-A、Cortex-R和Cortex-M全系列核心广泛适用于物联网、汽车电子、工业控制等领域。压缩包内共含六百五十个文件约五十六点八三兆字节除编译驱动与调试工具外还包含大量C与C标准库头文件、库文件、汇编源文件、工程配置及说明文档等可满足从底层寄存器操作到上层算法开发的完整需求。资源包提供从零到三的多级代码优化、嵌入式链接编辑、硬浮点与软浮点支持、CMSIS标准接口及调试信息生成使用者可借助其中的库文件和宏定义快速构建裸机或RTOS工程并基于GDB进行源码级调试。目前已有四千二百一十六人浏览学习尤其适合正在学习ARM体系结构、准备部署交叉编译工具链的初级嵌入式工程师和学生参考使用。 近十年嵌入式开发的老工程师八成都有过被工具链折腾到怀疑人生的经历。尤其是手头维护着存量项目、又想着升级IDE的时候ARMCC这个老伙计总会冒出来刷一波存在感。今天想聊的是很多人在Keil MDK里遇到的一个老朋友——ARM Compiler Version 5也就是大家常说的ARMCC。严格来说它指的是一套完整的C/C编译工具链包含编译器、汇编器、链接器和相关库而armcc就是其中负责C语言编译的核心程序。放在今天它已经不属于新项目推荐的选择但在大量量产产品和老代码仓库里它依然是不可替代的版本。这篇文章适合三类人一是在Keil里打开老工程、却突然提示“missing compiler version 5”而编译不了的开发者二是接手旧项目、需要搞清楚ARMCC和现在的ARMCLANG到底差在哪儿的维护者三是还在用5.06 update 6或update 7、想把手头环境理顺的嵌入式工程师。我尽量把这些年碰到的问题、踩过的坑和排查思路一次讲透。1. ARMCC到底是什么为什么老项目绕不开它1.1 从RealView到ARM Compiler 5的演进ARMCC并不是凭空出现的。早期ARM公司提供的是SDTSoftware Development Toolkit后来演变为ADSARM Developer Suite再往后是RVCT——也就是RealView Compiler Tools。这套工具链的核心编译命令就是armcc。到了2010年前后ARM把这些工具整合成了ARM Compiler这个产品线版本号从4.x一直升到5.x。我们常说的ARM Compiler 5.06就是这一系列的成熟版本。ARMCC的定位非常明确专门针对ARM架构的C/C交叉编译器输出的是ARM/Thumb指令集的目标代码。它最大的特征是和老式的RVCT风格选项一脉相承比如用--cpu、--apcs、--split_section这类参数来控制生成代码。这对于维护了十年以上的老工程来说是“祖传习惯”很多项目的链接脚本、启动文件甚至内联汇编都是按照ARMCC的语法来写的换到其他编译器往往得伤筋动骨。1.2 ARMCC和ARMCLANG的关键差异这几年ARM主推的是ARM Compiler 6对应的编译命令是armclang底层是基于LLVM的Clang前端。很多人以为这只是版本号变了实际区别很大对比维度ARMCCAC5ARMCLANGAC6语法标准C89/C99为主C98编译器扩展较多C99/C11更完善C11/14支持更好优化能力经典老项目调校成熟优化策略激进执行效率可能更高内联汇编语法__asm { } 风格ARMCC专属asm ( ) 或 __asm类GCC风格编译选项以--开头如--cpu、--apcs以-m开头如-mcpu、-mfloat-abi对老工程兼容性友好几乎无缝需要改启动文件、分散加载文件甚至C代码Keil MDK内置情况由用户安装的AC5版本提供MDK自带AC6说白了老工程里的startup_xxx.s、分散加载描述文件.sct、以及用__attribute__语法写的代码在AC6下极大概率需要手动调整。这也是为什么很多产品虽然已经量产多年但维护它的工程师依然坚持用ARMCC 5——并不是不追求新而是换工具链的隐性成本太高风险不可控。1.3 ARMCC 5.06的正常使用寿命ARMCC 5在2020年前后已经不再由ARM官方提供新版本当前能拿到的最终版本基本就是5.06 update 7build 960。这意味着它不会有新特性、新架构支持安全问题也只能靠使用方自己规避。但在Cortex-M3/M4/M7这些老内核产品上它的稳定性和生成代码的成熟度是经过海量产线验证的。我自己评估下来如果项目不是非要上Cortex-M33、Cortex-M55这些新内核或者不需要C新标准特性那么ARMCC 5继续用是没有问题的。但如果项目准备往新平台迁移建议尽早规划工具链切换别等产品要迭代了才仓促动手。2. 版本梳理5.06 update 6和update 7有什么区别2.1 如何看懂ARMCC的版本命名ARM Compiler 5.06的完整版本号一般长这样5.06 update 6 (build 750)或者5.06 update 7 (build 960)。其中5.06是主版本号update x是维护更新build号是具体的构建版本。ARM官方没有采用大版本跳变的方式而是通过update来迭代。实际装到Keil里以后在uVision的“Project - Manage - Project Items - Folders/Extensions”里可以看到当前可用的编译器版本列表。如果你装了多个ARMCC版本这里会依次列出比如ARM_COMPILER_V5.06_update7这样的名字。2.2 update 6和update 7的差异细节从官方Release Note来看update 6对应build 750update 7对应build 960两者修复的问题有重叠但update 7会更靠后。具体到日常使用我的感受是版本修复重点实际体验5.06 update 6 (build 750)修复了一些较新的Cortex-M内核支持问题和链接器边界问题在多数Cortex-M3/M4工程中表现稳定5.06 update 7 (build 960)修复了DWARF调试信息生成、部分优化器崩溃问题这是AC5的最终版本推荐老项目优先使用避免升级到6的改动如果是从update 6迁移到update 7基本上是不会引起编译产物行为变化的最多是相同源码编译出来的二进制哈希不同但功能等价。我把一个稳定量产项目从update 6切到update 7跑完整回归测试没有任何差异。2.3 完整命名示例说明很多人找“arm compiler 5.06 update 6 (build 750) 下载”或者“arm compiler 5.06 update 7 (build 960) 下载”其实不用单独跑去下载ARM官方的独立安装包。只要你的Keil MDK版本在5.37之前并且安装了对应的packKeil通常会自动带上ARMCC 5。从MDK 5.37开始ARM官方不再默认捆绑ARMCC 5而是只提供AC6。如果需要在MDK 5.37及以上版本里使用ARMCC 5得通过额外安装“Legacy Pack”的方式补回来。注意在MDK 5.37及以上版本中如果打开老工程时出现“missing compiler version 5”或“compiler version 5 not found”的报错第一件事不是重装软件而是检查Legacy Pack是否已安装。这个坑我见过太多人踩。3. Keil MDK中ARMCC 5的安装配置与编译错误排查3.1 老工程报“missing compiler version 5”的原因这个报错的本质是工程文件.uvprojx里明确记录了它创建时使用的编译器版本例如使用CompilerV5.06 update 7 (build 960)/Compiler这样的字段。当你在新电脑或新版本Keil中打开这个工程如果当前环境不存在AC5工具链uVision就不知道用哪个编译器来构建于是直接报缺失。很多工程师第一反应是“我的Keil明明能编译新工程”但这没有用——新工程默认走AC6而老工程写死的是AC5。解决思路不是去改工程文件而是把AC5工具链装进环境里。3.2 解决“missing compiler version 5”的完整步骤我用Windows 10 Keil MDK 5.38环境来举例实操路径如下打开Keil MDK安装目录查看是否已经存在ARM\ARMCC这个文件夹。如果存在说明AC5已经内置只是工程配置没识别到继续第4步即可。如果不存在去ARM官网下载并安装Legacy Support包。这个包从MDK 5.36之后被独立出来就是为了兼容老编译器的。安装Legacy包时注意勾选ARM Compiler 5.06 update 7其余版本除非特殊需要否则不必全选。重启Keil打开老工程进入Project - Manage - Project Items - Folders/Extensions在Default File Path或Compiler下拉菜单里可以看到ARMCC版本选择5.06 update 7。对工程执行一次Rebuild确认工具栏右侧显示的编译器名称已经是ARM Compiler V5.06 update 7。这个过程下来90%的missing报错都能解决。剩下的10%通常是工程文件里使用了某种奇怪的路径引用或者项目本身是MDK4转过来的需要先执行一次Project - Migrate操作。3.3 MDK4工程升级到MDK5需要注意的额外设置从MDK4时代遗留的工程除了编译器版本还常常存在“器件包路径引用”、“分散加载文件格式”等兼容问题。我自己习惯在迁移后先做三件事一是确认Device选型正确二是把Target页签里的ARM Compiler改成5.06 update 7三是检查Utilities页签里的Flash Download是否还匹配当前调试器。涉及Cortex-M0/M0的项目还要特别注意ARMCC的默认字节对齐行为。AC5对结构体对齐的处理是按照AAPCS标准来的但某些老外设库可能默认开了--apcsinterwork这个选项在新旧编译器间没有语法差异但会影响启动代码的生成方式。4. ARMCC 5编译器的实际使用心得与避坑4.1 怎样用好ARMCC的编译选项用ARMCC 5光会点Rebuild是不够的。它提供了大量编译选项优化代码体积和性能主要靠这几个-O0/-O1/-O2/-O3优化级别。老工程通常用-O2平衡性能和调试量产代码建议根据实际栈使用和实时性需求测试后再定不要盲目上-O3。--split_section把每个函数放到独立的ELF section中配合链接器的--remove选项可以剔除未使用函数显著减少最终镜像体积。--apcsinterwork开启ARM/Thumb指令集交叉调用配合Cortex-M默认的Thumb模式。--c99启用C99标准模式。老代码如果用了for循环内声明变量必须要开这个否则编译报错。--diag_errorwarning将警告升级为错误适合团队协作时强制代码质量。组合使用示例armcc --cpu Cortex-M4 -O2 --split_section --apcsinterwork --c99 -I.\RTE -I.\User -c .\main.c -o .\build\main.o这段命令在命令行里手动单文件编译时很常用。在Keil工程里你可以把--split_section和--c99直接填到Options for Target - C/C - Misc Controls里比在图形界面一项项勾选更直接。4.2 编译优化导致软件“不正常”的定位思路ARMCC的优化器和GCC、Clang一样都可能在优化后改变未定义行为的执行结果。常见表现是Debug下一切正常Release或-O2下程序跑飞、中断进不去、外设寄存器读写异常。我的排查套路是二分法加编译选项降级。先全局退回-O1如果能复现问题范围缩小到某个文件就把该文件单独设为-O0。结合--list选项生成汇编列表比对C源码对应的汇编是否做了激进重排比如将易失性访问合并或者把循环变量直接优化掉。提示ARMCC 5对volatile关键字的处理比ARMCLANG更宽松但千万不要因此就不写volatile。尤其在裸机中断共享变量、DMA缓冲区和寄存器映射结构体上volatile该写还是得写。4.3 内联汇编和关键字兼容性ARMCC的内联汇编是个特色功能它支持__asm { ... }块语法。这套语法在ARMCLANG里不兼容因为后者改用GCC风格的__asm表达式。写内联汇编时我建议遵循两条原则能用C语言做的不要轻易用内联汇编编译器生成的汇编往往更稳。必须用汇编的地方把输入输出操作数、clobber列表写清楚不要在__asm块里依赖编译器帮你保存寄存器。例如ARMCC里关全局中断常用__asm { CPSID i }换成ARMCLANG则要写成__asm volatile(cpsid i);仅仅是这一处差异就足以让老工程在升级AC6时冒出各种诡异问题。所以凡是涉及老工程迁移的人我都建议先全文搜索__asm把内联汇编数量摸清楚再评估工作量。4.4 分散加载文件.sct的写法要点ARMCC 5使用分散加载文件来指定代码、数据在Flash和RAM中的布局扩展名是.sct。它和AC6使用的.scat文件scatter file语法几乎相同但细节上又有微小差别。常用的场景是把某个关键函数放到指定RAM地址加速执行。示例LR_IROM1 0x08000000 0x00080000 { ER_IROM1 0x08000000 0x00080000 { *.o (RESET, First) *(InRoot$$Sections) .ANY (RO) } RW_IRAM1 0x20000000 0x00020000 { .ANY (RW ZI) } RW_IRAM2 0x20020000 0x00010000 { main.o (RW ZI) critical_code.o (RO) } }如果这段配置写错最常见的现象是链接时报L6220E: Region RW_IRAM2 overlaps RW_IRAM1。排查时要先计算各个RAM区间的起始地址和大小是否有重叠再确认每个.o文件的段属性是否正确划分。5. 常见问题与排查技巧实录5.1 “compiler version 5 not found”的补充排查在前文讨论的Legacy包之外还有一类情况是装了Legacy包但仍报错。这时候去检查环境变量用管理员权限跑一次Keil把安装目录下的ARM_Compiler_5.06_update7文件夹拷贝到别人的机器上注册路径即可复用。一个更隐蔽的坑是杀毒软件把ARMCC的临时文件误删。armcc在编译过程中会生成很多中间文件某些安全软件会拦截导致编译中途退出且没有明确报错信息。可以在杀毒软件里把Keil安装目录加入白名单如果问题消失那就是它的问题。5.2 ARMCC编译速度慢的优化方法ARMCC 5的编译速度本就不算快老电脑上更明显。如果工程包含几十个.c文件每次全量编译都要等一两分钟。我常用的优化手段是把不常改的底层驱动文件预先编译成静态库.aKeil里只需要在Project中添加库文件这样增量编译只需重新编译改动的文件速度提升非常明显。具体操作是单独建一个Lib工程加入底层驱动源码优化级别开-O2--split_section编译生成库然后主工程引用。这样做还有一个好处如果驱动有bug只需替换库文件主工程几乎不需要重新编译。5.3 如何判断工程实际使用的是哪个编译器这个问题看着简单但实测中经常有人搞混。看Keil主界面底部状态栏或者工具栏的编译器名称都不太靠谱最直接的办法是在Options for Target - Target页签最下方查看ARM Compiler下拉框里选中的值。工程文件里也会写清楚。用文本编辑器打开.uvprojx搜索Compiler标签看到的值就是当前工程绑定的编译器。如果值是V5.06 update 7 (build 960)那么无论界面怎么显示构建时用的都是AC5。5.4 ARMCC开发环境常见问题速查表问题现象可能原因处理建议打开工程提示missing compiler version 5未安装AC5或Legacy包缺失安装Legacy包或将AC5路径加入Keil编译报错L6220E区域重叠.sct分散加载文件RAM区间重叠重新规划RAM地址区间优化的代码执行顺序异常依赖未定义行为局部降低优化级别细化volatileC代码中无法声明for循环变量未开启C99Misc Controls中添加--c99内联汇编报错语法不对AC5与AC6语法差异按上面示例改写为GCC风格下载调试正常脱机运行异常启动代码或分散加载文件配置问题核对启动文件和复位向量映射5.5 结合HardFault问题聊ARMCC的使用注意ARMCC环境下HardFault定位方法网上很多教程是基于Keil的Call Stack窗口。实际现场中HardFault发生后先看SCB-HFSR和SCB-CFSR寄存器判断是总线错误、用法错误还是断言错误。再结合LR寄存器分析原始返回地址多数时候能定位到具体函数。ARMCC的-O2优化下局部变量可能被优化到寄存器里调试时看到的变量值不一定准确。遇到这种情况我习惯把那一段单独设-O0重新编译验证往往能确认是优化带来的问题还是代码本身的问题。最后再分享一个小技巧如果你手头维护多个老工程经常切换不同编译器版本最好不要只依赖Keil的默认路径而是把ARMCC 5.06 update 7单独安装到一个固定目录比如D:\Keil_v5\ARM\ARMCC。然后在每个工程里手动指定编译器的绝对路径。这样即使日后换了电脑只要恢复这个目录所有工程都能直接编译不用一个一个大费周章地重装。另外我在实际项目里还会把启动文件、分散加载文件和关键汇编代码单独备份一份放在版本管理工具的independent目录下。因为这类文件一旦在编辑器里被自动转换过格式再想恢复成ARMCC可编译的状态往往要花掉比预期多得多的时间。老工具虽然没有新工具花哨但摸透它的脾气它依然是很可靠的伙伴。本文还有配套的精品资源点击获取
返回列表