Arm Compiler 5.05 update 1 老工程编译链接避坑

发布时间:2026/10/27 12:54:16
Arm Compiler 5.05 update 1 老工程编译链接避坑 简介Arm Compiler 5.05 update 1build 106是面向嵌入式开发者的 Windows 平台 ARMCC 编译器安装包适合维护旧版 Keil MDK 工程、复现 ARM Compiler 5 编译环境或排查新旧编译器兼容问题的场景。压缩包共 3 个文件以 msi 安装包、exe 启动程序和 html 说明页为主整体约 80.56MB体积紧凑便于归档留存。已有 890 人学习下载说明其在旧工程维护与工具链补全方面仍有稳定需求。对于仍依赖 ARMCC 5.05 的项目可用于补齐本机编译器环境、核对版本信息并保留原始安装介质减少因工具链缺失导致的工程无法打开或编译失败等问题适合作为传统 Keil 工程的工具备份。1. 为什么还要找 Arm Compiler 5.05 update 1一条被新 IDE 判了“死刑”的老工具链去年帮人看一块 Cortex-M3 的老板子工程是 2013 年建的源码一行没改换台机器一编译就红满屏__packed不认、__irq不认、内联汇编整段报错。折腾半天才反应过来不是代码坏了是 IDE 自带的编译器早就换成了基于 LLVM 的那一套而这份工程从头到尾是按 armcc 的扩展写的。Arm Compiler 5.05 update 1build 106就是这类工程的对症药一套完整的 AC5 工具链armcc、armasm、armlink、armar、fromelf 全在能把这批老.c和.s原样编成.axf再转 bin。适合手上压着老项目、要复现老固件、要给现场补一个能编译版本的人新开的项目不用往回找直接用新编译器就行。2. 拆开这个包armcc、armasm、armlink、fromelf 各管什么很多人拿到工具链第一反应是找 IDE 的安装包其实 AC5 是一组纯命令行的可执行文件IDE 只是在外面套了层壳。把它拆明白后面所有“IDE 里编不过但命令行能过”的问题都有了解释。2.1 目录里到底有哪几个可执行文件解压后核心就是bin目录下的一排程序其余是include标准头文件、lib标准库和 microlib 的.a、lib/rw之类。真正干活的就这几个可执行文件职责常见误用armccC 编译器吃.c吐.o把一堆.c直接丢给它当链接器用armcppC 编译器重载和异常的处理在这里C 工程误用它链接期报一堆__cpp符号armasm汇编器吃.s吐.o忘了--pd传预定义宏条件汇编全走默认分支armlink链接器吃.o加 scatter吐.axf不给--scatter按默认地址摆烧进去就跑飞armar静态库打包生成.a打包顺序不对链接时符号解析不出来fromelf从.axf抽 bin/hex、反汇编、看体积用--bin不加--output二进制数据直接糊到终端我一般会先在bin目录上跑一次目录列表确认armcc和armlink的时间戳是同一天。这两个文件的版本如果串了症状非常玄学.o能生成链接时报格式不认识或者Fatal error: L6002U之类看着像工程问题其实是装了两份不同版本。2.2 为什么是 5.05 update 1 而不是随便一个 5.xAC5 从 5.03 到 5.06 的各小版本之间语言扩展的支持程度、内联汇编的展开策略、-O2的代码生成都会变。老工程最怕的不是编不过而是编过了但 bin 变了现场设备靠 CRC 校验固件你换个编译器小版本重新出一版功能一模一样校验值对不上升级流程直接卡住。所以复现老固件这件事工具链版本本身就是一个参数不能随手换。另一个现实理由是老启动文件和 scatter 文件的写法。有些 2010 年前后的工程.sct里用First、*(InRoot$$Sections)这种写法新链接器还能认但也有工程用了当年的--ro-base/--rw-base命令行摆位方式这种只有老版本能原样编过。我见过一次一个--rw-base写死的老工程换新链接器后 RW 段被自动重排全局变量初值全错查了两天才定位到链接脚本上血泪经验。反过来讲边界如果只是维护、不要求二进制一致用 AC6 更省事如果要求“和当年出货的固件一模一样”那就得把 AC5 的具体小版本锁死连-O等级都不能动。2.3 装完先跑三条自检命令先把bin加进环境变量别急着编工程。Windows 上路径里有空格是常态加引号这一步很多教程省掉了后面报cannot open source input file就是这里埋的雷。# Linux/macOS 写法Windows 上是 set ARMCC5D:\tools\armcompiler5 export ARMCC5/opt/armcompiler5 export PATH$ARMCC5/bin:$PATH # 三条版本号必须能对上且 build 号一致 armcc --version armasm --version armlink --version # 顺手确认 fromelf 也在后面出 bin 全靠它 fromelf --versionarmcc --version输出的第一行会写明产品名和 build 编号这一行就是你要贴进工程文档的东西别只写“用的 AC5”。参数上没什么可调的唯一要注意的是别把ARMCC5指到include目录上路径指向解压根目录bin由 PATH 补全。三条命令都通说明工具链本身没问题。接下来任何报错都是工程配置的锅排查范围一下就缩小了。3. 用 armcc 编一个最小工程从 .c 到 .axf 的完整命令行IDE 里点一下 Build 很舒服但出问题的时候你根本不知道它替你做了什么。我习惯先用命令行把整条链路跑通再回头对照 IDE 的选项页这样任何一条报错都能对应到具体的命令参数上。3.1 编译--cpu、-O、--c99、-I、-D 怎么设单个 C 文件的编译命令长这样每一行都有它的道理armcc --cpuCortex-M3 \ --c99 \ -O2 -Otime \ -g \ -I./inc -I./cmsis \ -DUSE_FULL_ASSERT -DCHIP_MD \ -c src/main.c -o build/main.o--cpu决定指令集和默认对齐规则Cortex-M3 是 Thumb-2 only写错了要么生成 ARM 指令链接时报错要么浮点相关的 ABI 对不上。--c99打开 C99注意 AC5 默认是 C90 加一堆扩展代码里用了//注释、声明在语句之后、stdint.h这些不加--c99会有概率报#268-D之类的声明位置错误。-O2 -Otime是常用的发布组合-Otime表示优先速度而不是体积。-g生成调试信息配合-O0才能舒服地单步。-I和-D就是把 IDE 里那两栏原样搬过来注意-I后面不要有空格写成-I ./inc有的 shell 会把它拆成两个参数。想看看预处理到底展开了什么加-E输出到文件宏冲突这种问题一眼就能看出来armcc --cpuCortex-M3 --c99 -I./inc -DCHIP_MD -E src/main.c -o build/main.i-E不做语法检查只跑预处理。我一般在“某个头文件被谁包含进来了”这种扯皮场景下用它比在 IDE 里翻 include 树快得多。3.2 汇编启动文件--pd 和 --cpreproc 别漏启动文件是汇编写的里面通常有一堆IF :DEF:XXX的条件块靠--pd把宏传进去armasm --cpuCortex-M3 \ --pd USE_MICROLIB SETA 1 \ --pd STACK_SIZE SETA 0x400 \ -g \ startup/startup_chip.s -o build/startup.o--pd的格式是符号 SETA 值SETA是给数值赋值字符串要用SETS。漏掉这个参数最典型的后果是启动文件里默认走了Heap_Size EQU 0x00000000那条分支程序里malloc一调就返回空现象是“链接不报错、跑起来跑一半死”属于最费时间的那类坑。如果.s文件里用了#include或#define还得加--cpreproc让汇编器先过一遍 C 预处理器armasm --cpuCortex-M3 --cpreproc --pd USE_MICROLIB SETA 1 \ startup/startup_chip.s -o build/startup.o开关一加#include reg_map.h就能用了代价是预处理阶段的报错信息会指向展开后的行号看着有点乱。3.3 链接与 scatterRO / RW / ZI 这笔账怎么算链接阶段最容易出问题因为它决定了每一段字节最终落在哪个地址。一个典型的 scatter 长这样LR_IROM1 0x08000000 0x00040000 { ER_IROM1 0x08000000 0x00040000 { startup_chip.o (RESET, First) *(InRoot$$Sections) .ANY (RO) } RW_IRAM1 0x20000000 0x00010000 { .ANY (RW ZI) } }LR_IROM1是加载域写在 Flash 里ER_IROM1是执行域First保证向量表落在区域最前面——这一行丢了中断就进不去现象是“上电就 HardFault”属于经典翻车点。*(InRoot$$Sections)是库的根区__main里的 scatter loading 代码靠它被拉进来删了会报L6915E: Library reports error: No __main (with call to __scatterload) was found。三个概念要对上号RO 是代码加常量烧在 FlashRW 是有初值的全局变量初值存 Flash、运行时拷到 RAMZI 是初值为零的变量只占 RAM。map 文件最后的 totals 会给出三行名字含义关注点Total RO SizeCode RO Data决定 Flash 占用Total RW SizeRW Data ZI Data决定 RAM 占用含栈堆Total ROM SizeCode RO Data RW Data烧录文件实际大小很多人看 Flash 够不够只看 Total RO Size忘了 RW 的初值也要占 Flash结果 bin 比预期大了一截硬是多花了半天查。链接命令armlink --cpuCortex-M3 \ --scatterchip_flash.sct \ --infosizes,totals,unused \ --map --listbuild/app.map \ -o build/app.axf \ build/startup.o build/main.o build/system.o build/uart.o--infototals在终端直接打印三段大小--map加--list生成可读的 map 文件unused会列出被删掉的段——排查“这个函数明明写了怎么没编进去”全靠它。3.4 fromelf 出 bin、反汇编和体积核对.axf是带调试信息的 ELF不能直接烧最后一步交给 fromelf# 出纯二进制 fromelf --bin --outputbuild/app.bin build/app.axf # 出 Intel hex某些烧录器只认 hex fromelf --i32 --outputbuild/app.hex build/app.axf # 反汇编 符号表 段大小一份文本全给你 fromelf --text -c -s -z -o build/app.dis.txt build/app.axf--bin出来的文件大小应该等于Total ROM Size对不上就说明 scatter 里有空洞中间被填充了 0xFF。-c反汇编代码段-d反汇编数据段-s打符号表-z打段大小。我最常用的场景是查“这个函数到底有没有被内联”反汇编里搜函数名搜不到那就是被-O2吃掉了别再怀疑宏写错。4. 优化与体积-O0 到 -O3、--split_sections 与 microlib 的取舍工具链跑通之后下一个问题永远是代码放不下或者跑得不够快。AC5 在这块的选项不算多但组合起来的差异很大选错了轻则调试没法看重则逻辑直接变了。4.1 优化等级怎么选-O0到-O3不是“越大越好”每个等级的行为差别很具体选项主要行为适用场景-O0几乎不优化变量都在栈上单步调试、定位时序问题-O1常量折叠、死代码消除想留点调试能力又要降体积-O2循环优化、公共子表达式、部分内联发布版本的默认档-O3更激进的内联和循环展开纯计算热点调试基本别想-Ospace与-O2/-O3组合优先体积Flash 紧张时用-Otime与-O2/-O3组合优先速度RAM 换 Flash 的场景-O2 -Ospace和-O3 -Ospace的体积差通常在一两个百分点但-Otime和-Ospace之间能差出 10% 以上这个量级足够决定一个 64KB 的片子放不放得下。注意-O2以上调试时watch窗口里很多变量会显示成not in scope或者一个固定的$xx值。这不是 IDE 的 bug是变量被优化到寄存器里去了。想看清某个变量别降整个工程的优化等级用volatile或者把它挪到一个不参与优化的调试函数里更划算。还有一个常被忽略的点优化等级会改变“读一次”还是“读多次”。寄存器轮询循环在-O0下每轮都读-O2下可能只读一次然后把结果缓存在寄存器里循环体被优化成一个死循环。这不是编译器的问题是代码里少了volatile。修法有两个给指针加volatile或者在循环体里插屏障/* 方案一给寄存器指针加 volatile最正规 */ volatile uint32_t * const status_reg (volatile uint32_t *)0x40001000; while ((*status_reg 0x01u) 0u) { /* 等硬件置位 */ } /* 方案二不想改指针类型时用编译器屏障挡住重排 */ while ((*(uint32_t *)0x40001000 0x01u) 0u) { __schedule_barrier(); /* armcc 内建防止循环被提出去 */ }__schedule_barrier()是 AC5 的内建函数作用是不让编译器跨过这一行重排访存。它不影响执行效率编译后不生成指令属于很便宜的后悔药。4.2 --split_sections 与未引用段的清除--split_sections让每个函数单独成段好处是链接器可以把没调用到的函数整段删掉坏处是段数量暴涨map 文件体积翻几倍。开关长这样armcc --cpuCortex-M3 -O2 --split_sections -c src/driver.c -o build/driver.o armlink --cpuCortex-M3 --scatterchip_flash.sct --infounused \ -o build/app.axf build/*.o规则要先说清楚--split_sections只管“能不能拆”真正删段的是链接器的--remove而 armlink 默认就会做这一步。所以你会看到一种迷惑现象——加了这个开关之后明明在.c里定义、只是没被别的文件调用的函数链接完就没了。不想让它被删用--keep保住或者在被调函数上加属性/* 只被汇编启动文件里 BL 调用编译器看不到引用关系 */ __attribute__((used)) void SystemInit(void) { /* 时钟初始化 */ }__attribute__((used))的意思是“别管有没有引用都给我留着”成本几乎为零。中断服务函数倒是不用加因为启动文件的向量表里有一份地址引用链接器能看见。4.3 microlib 的边界与 printf 重定向片内 Flash 只剩几 KB 的时候切换到 microlib 是第一步。命令行方式是给链接器加--library_typemicrolib或者在 Keil 的选项里勾 C Library/Microlib 那个单选。维度microlib标准 C 库代码体积几 KB二三十 KB 起浮点 printf不支持支持文件/OS 接口不支持支持半主机依赖无默认走半主机必须重定向或__use_no_semihosting可重入不是线程安全的可配置最容易翻车的是第二行microlib 的printf不支持浮点格式化%f打出来是乱码或者直接没有输出很多人第一反应是“串口坏了”其实是库的问题。要打浮点只能自己转成整数位和小数位拼字符串或者换回标准库并接受体积代价。重定向这一块标准库和 microlib 的要求还不一样。microlib 下实现fputc就够了/* retarget_io.c —— microlib 下 printf 最终走到 fputc */ #include stdio.h #include uart_hal.h int fputc(int ch, FILE *f) { (void)f; uart_write_byte(UART_DEBUG, (unsigned char)ch); /* 阻塞发一个字节 */ return ch; }换成标准库链接器会去找一整套_sys_*接口缺一个报一个。不想链接半主机就先用#pragma import(__use_no_semihosting)声明再补齐链接器点名的符号#pragma import(__use_no_semihosting) /* 声明不用半主机 */ #include stdio.h #include uart_hal.h int fputc(int ch, FILE *f) { (void)f; uart_write_byte(UART_DEBUG, (unsigned char)ch); return ch; } /* 下面这几个是链接器会来问的缺哪个补哪个 */ void _sys_exit(int x) { (void)x; for (;;) { } } void _ttywrch(int ch) { (void)ch; } char *_sys_command_string(char *cmd, int len) { (void)cmd; (void)len; return NULL; }如果工程里还有fopen/fread这类文件操作还得补_sys_open、_sys_close、_sys_write、_sys_read、_sys_seek、_sys_istty、_sys_flen报错信息会一个个把它们点出来顺着补就行。这一步看着琐碎但比“printf 没反应”在那里瞎猜串口配置高效多了。5. 避坑与排查L6218E、L6406E 和“编过了但跑不对”AC5 的报错信息不算友好但编号很稳定。把常见的几个编号和现象对应起来排查速度能快一个量级。这一章按“现象 → 原因 → 解决”记录几条我实际踩过的。5.1 链接期报错先看 map 再改代码L6218E: Undefined symbol xxx (referred from yyy.o)是最常见的一个。别看它就说“符号没定义”先分三种情况符号压根没编进来文件没参加链接、符号编进来了但被链接器当未引用段删了--remove干的、符号在.a里的打包顺序不对导致没被拉出来。判断方法很直接打开 map搜Removing Unused input sections如果你那个文件出现在列表里就是第二种。L6406E: No space in execution regions with .ANY selector matching ...是 RAM 或 Flash 溢出。map 末尾的 Execution Region 表会告诉你每个区域的Max和Used差多少一目了然。九成情况是启动文件里的Stack_Size和Heap_Size写太大了尤其是那份从别的工程拷过来的启动文件栈给了 0x1000、堆给了 0x800两个加起来就吃掉一大块 RAM。L6915E: Library reports error: No __main (with call to __scatterload) was found翻译过来就是“没人调用__main”。原因通常是启动文件里的IMPORT __main和LDR R0, __main被删了或者 scatter 里漏了*(InRoot$$Sections)。这条报错出现的时候链接其实是成功的只是生成的镜像根本没法初始化 RW 段烧进去就是跑飞属于典型的“编过了但不对”。5.2 五条高频踩坑记录坑一L6218E: Undefined symbol SystemInit (referred from startup.o)现象代码里明明有system_chip.c也加进工程了链接还是报这个。 原因SystemInit在 C 文件里定义但 C 编译器不知道汇编那边会调它函数被--split_sections拆成独立段后链接器判定“无人引用”直接把段删了。 解决在定义处加__attribute__((used))或者链接时--keepsystem_chip.o。前者更稳不依赖命令行。坑二RW 段被重排全局变量初值全错现象-O0下正常-O2下某个全局结构体初值变成 0或者换了台机器编出来的固件一上电就跑飞。 原因工程用的是--rw-base命令行摆位而不是 scatter 文件链接器版本一变RW 的执行域地址重排__scatterload拷贝的源地址和目标地址对不上。 解决统一改成 scatter 文件描述别用命令行摆位迁移前把老的 map 留一份出问题时逐段比对地址。坑三printf打浮点没输出或者输出一串乱码现象printf(%.2f, 3.14f)什么都没打出来串口是通的printf(%d)正常。 原因开了 microlib精简后的printf不带浮点格式化能力。 解决要么换标准库要么自己拆整数和小数部分分别打印。我一般选后者因为换库之后体积会涨回去前后矛盾。坑四中断一进去就 HardFault现象外设中断使能后立刻进 HardFault单步走发现 PC 跳到一个不存在的地址。 原因scatter 里startup.o (RESET, First)这一行丢了或者被* (RO)排在前面向量表没落在 Flash 起始地址CPU 从 0x00000000 取到的前两个字不是栈顶和复位向量。 解决First必须写且RESET段的段名要和启动文件里的AREA RESET, DATA, READONLY严格一致大小写敏感。坑五一堆#1-D和#1295-D警告把真正的错误淹了现象编译输出几百行警告翻半天找不到那条 error。 原因老文件普遍末尾没有换行#1-D: last line of file ends without a newline函数声明没写参数类型#1295-D: Deprecated declaration - give arg types。 解决先用--diag_suppress把噪音压掉再单独看错误armcc --cpuCortex-M3 -O2 --c99 \ --diag_suppress1,1295,177 \ -I./inc -c src/main.c -o build/main.o--diag_suppress后面跟编号列表用逗号分隔不带#和-D。压警告的度要把握住#177-D变量声明了没用可以压但涉及隐式类型转换的警告别压那玩意儿在小端机上能把uint16_t拼成高字节在前。5.3 用 -E 和 map 做交叉验证怀疑宏展开有问题用前面提的-E怀疑某段代码没进去用 map 的Image Symbol Table搜符号。两个手段配合起来绝大多数“代码明明写了”的问题都能闭环。还有一个偏门但好用的方法让编译器把汇编也吐出来直接看它生成了什么。armcc --cpuCortex-M3 -O2 --c99 -S -I./inc src/main.c -o build/main.s-S只生成汇编不汇编输出的.s里能看到每条 C 语句对应的指令、内联展开的结果、以及寄存器分配。查“为什么这个函数这么慢”时看这份汇编比看 map 有用得多。6. 迁到新编译器之前我固定要跑的一张自检表AC5 迟早要退场手上这些工程早晚得挪到 AC6 上。直接换编译器报错会多到你怀疑人生但如果提前把差异项列出来逐条过一遍工作量能砍掉一大半。差异最集中的就是语言扩展这一块我一般先全局搜一遍关键词命中多少心里就有数了。AC5 写法AC6 对应写法备注__packed struct {...}struct __attribute__((packed)) {...}AC6 不认__packed关键字__align(8) uint8_t buf[64];__attribute__((aligned(8)))位置要放到声明符上__weak void f(void)__attribute__((weak))语义一致__irq void isr(void)直接写普通函数用向量表绑定Cortex-M 上本来就不需要__irq__attribute__((at(0x20000000)))__attribute__((section(.ARM.__at_0x20000000)))地址写在段名里内联汇编__asm { ... }GNU 风格__asm volatile(...)语法完全不同必须逐条改#pragma import(__use_no_semihosting)__asm(.global __use_no_semihosting);效果一样搜的时候别一个个文件看用 grep 一把捞# 把工程里所有 AC5 专有写法列出来按文件统计命中数 grep -rnE __packed|__align\(|__weak|__irq|__asm[[:space:]]*\{|__use_no_semihosting|__attribute__\(\(at\( \ --include*.c --include*.h src/ | tee build/ac5_ext_usage.txt # 只统计每个文件命中多少条先啃大头 cut -d: -f1 build/ac5_ext_usage.txt | sort | uniq -c | sort -rn-E是扩展正则[[:space:]]是为了兼容 tab 和空格混排的写法。tee那份文件别删改完一条划掉一条比在脑子里记账靠谱。按文件排序之后通常前三个文件就占了七成的命中量先改它们剩下的零散项改起来很快。改的过程中有几个判断标准值得提前定下来__irq这一类如果目标是 Cortex-M直接删掉关键字就行本来就不该有__packed改成 attribute 之后要重新确认一次sizeof我在两个工程上见过 AC5 的__packed加在 typedef 上、attribute 加在变量上导致的布局差异通信协议字段直接错位内联汇编是最费时的别指望批量替换老老实实一条条对照手册改改完一定要把生成的汇编再反汇编回来比对一遍指令序列。最后跑一次体积和符号的双重核对新编译器的 map 和老 map 的 totals 三段大小逐项比代码段差 5% 以内算正常差 20% 以上多半是某个大函数被内联策略改变了。我一般会把两份 map 的Image Symbol Table按大小排序取前二十diff 一下异常项一眼就能看出来。从那以后我每次碰老工程第一件事不是打开 IDE而是先用命令行把armcc --version、armasm --version、armlink --version三条跑一遍把版本号抄进工程根目录的构建说明里再开始改代码。这个习惯救过我至少三次——有两次是“编不过”还有一次是“编过了但 bin 不对”全靠那三行版本号定位到工具链串了。工具链版本这件事出问题的时候就是唯一的线索事前记一笔比事后猜半个月划算。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询