ARTICLE DETAIL

资讯详情

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

CLion链接STM32Cube工程报错.ARM.extab?修改链接脚本即可解决

CLion链接STM32Cube工程报错.ARM.extab?修改链接脚本即可解决 在 CLion 里编译 STM32Cube 生成的初始化工程编译阶段一路绿灯到链接阶段突然炸一句arm-none-eabi-ld: non constant or forward reference address expression for section .ARM.extab这应该是很多人第一次接触这个报错时的状态。我第一次遇到时也懵了翻了半天关键词最后发现核心问题根本不在 CLion而在链接脚本。这个报错表面上是 ELF 段.ARM.extab的地址表达式没法被链接器解析实际上绝大多数情况都是同一个原因生成的.ld链接脚本没有对.ARM.extab和.ARM.exidx做显式声明导致 GNU LD 只能按“孤立段”去猜摆放位置猜着猜着就把地址表达式算爆了。如果你也卡在 CLion 和 STM32Cube 这个组合上这篇文章可以让你少走弯路。我会先讲清楚这个错误出现的链接链路再拆解.ARM.extab到底是什么最后给出实际能落地的修复方法以及几个我踩过的坑。1. 错误现场CLion 编译 STM32Cube 工程时卡在最后一步先说场景。STM32CubeMX 不是一个完整的 IDE它帮你生成了外设初始化和工程骨架。常见的做法是CubeMX 里配置完引脚和时钟选择Makefile或者STM32CubeIDE方式生成工程然后你用 CLion 打开。CLion 不会直接消费 CubeMX 的图形化配置它最终还是要拿到一串编译和链接命令。你可以直接在 CLion 里建 CMake 工程也可以让它读 CubeMX 生成的 Makefile。实际跑编译和链接的则是arm-none-eabi-gcc和它背后的arm-none-eabi-ld。到这里就会出现一个很多人忽略的问题CMake 或者 Makefile 都只是外壳真正决定固件怎么排布内存的是链接脚本.ld。编译阶段每个.c、.s文件会被编译成目标文件里面带着各种段比如.text、.data、.bss、.rodata。链接脚本负责说清楚这些段最后放在哪块地址。如果链接脚本没有写全链接器就会自动处理。典型报错长这样[ 50%] Linking CXX executable firmware.elf /opt/gcc-arm-none-eabi/bin/../lib/gcc/arm-none-eabi/.../libgcc.a(...) arm-none-eabi-ld: non constant or forward reference address expression for section .ARM.extab make[2]: *** [CMakeFiles/firmware.elf] Error 1注意报错发生在“链接”阶段不是编译阶段。这说明代码本身没有问题问题出在“把多个目标文件拼成一个固件”这个环节。很多人第一反应是去调 CMake 或者重新安装工具链方向就偏了。看到.ARM.extab这种段名应该先打开工程里的.ld文件。CLion 在这条链路里扮演的角色是“包装器”。它把编译命令、链接命令、工具链路径、目标文件组织起来但内核还是 GNU LD。所以排查思路应该围绕链接器而不是 IDE。验证的方法很简单在 CLion 的 Build 窗口打开详细输出看链接命令是不是arm-none-eabi-gcc或arm-none-eabi-g并且留意命令里-T参数指向的.ld文件路径。2. .ARM.extab 是什么ARM EABI 给 C 异常处理留的段要解决这个报错先得认识.ARM.extab和它的同伴.ARM.exidx。这两个段不是普通业务代码的段它们是 ARM EABI 规定的异常展开信息段。.ARM.exidx可以理解成一张“异常索引表”每一个可能抛出异常的函数都会在这张表里有一条记录。当 C 的异常发生后运行时需要通过这张索引表找到当前函数对应的“展开记录”。.ARM.extab就是展开记录的存放位置里面包含了如何在异常发生时恢复寄存器、找到调用者栈帧等信息。你可以粗暴地把它们理解为“C 异常处理时用来回溯函数调用栈的辅助数据”。在 STM32 这类资源紧张的嵌入式环境里异常处理的开销并不小。所以很多嵌入式编译器默认会把 C 异常关掉或者用户主动加上-fno-exceptions来禁止生成异常表。只要你没有显式关掉使用 G 编译.cpp文件时编译器就可能生成.ARM.extab段。如果你整个工程都是纯 C但某个链接进去了的第三方静态库用了 C也同样可能把这两个段带进链接。Cortex-M 系列一般没有硬件异常辅助单元异常展开信息靠的更多是编译器和运行时配合。在 ARM GCC 工具链里这两个段属于“附加段”默认的 STM32CubeMX 生成的.ld脚本往往就漏了它们。CubeMX 生成的手册链路更偏向标准 C 工程没有针对 C 异常展开做专门照顾于是链接器遇到这些段时就容易出错。顺带看一下我们日常会遇到的段划分很多链接报错其实都围绕这些段在做文章段名作用常见出现时机.text代码段存放函数指令总是存在.rodata只读数据字符串常量、const 变量有常量时存在.data已初始化全局变量启动时从 Flash 拷贝到 RAM有全局变量时存在.bss未初始化或零初始化全局变量有全局变量时存在.ARM.exidx异常展开索引表使用 ARM 工具链且开启异常或 unwind 时存在.ARM.extab异常展开扩展表使用 C 异常时存在当你看到报错指向.ARM.extab说明工程里已经产生了异常展开信息而链接器却不知道怎么安放这些信息。这时候工程里大概率出现了.cpp文件或者某个链接进来的库带有 C 运行时特征。别急着觉得是代码写错了先按段缺失来处理。3. 根因拆解为什么链接器会说“non constant or forward reference address expression”报错信息里的“non constant or forward reference address expression”是理解问题的一把钥匙。翻译成人话是链接器在某个时刻需要为一个地址表达式算出确定的值但这个值既不是常量也不能通过向前引用拿到最后只能中止。链接脚本里其实是一堆输出段定义比如常见的.text : { *(.text*) *(.rodata*) . ALIGN(4); _etext .; } FLASH .data : AT ( _etext ) { . ALIGN(4); _sdata .; *(.data*) . ALIGN(4); _edata .; } RAM这个脚本把代码段放在 Flash把数据段放在 RAM并且用_etext这个符号记录了数据段的加载地址。_etext的取值取决于.text段里有多少内容只有在链接器处理完.text之后才能确定。这就是“顺序相关”的地址求值。GNU LD 在遇到脚本里没有写的输入段时会把它当成“孤儿段”orphan section处理。链接器有两种办法一是按“跟前面最近的一个输出段”来继承内存区域尽量放在合理位置二是实在没地方放就插在地址空间的某个位置。被动安放的过程中链接器需要对这个段做地址对齐、计算重定位。如果这个孤儿段里又包含对其它段地址的引用而引用的段在布局顺序上还没最终定下来LD 就会认为当前表达式既不是常量也不能前向引用只能报错。.ARM.extab和.ARM.exidx恰恰就是最容易变成孤儿段的两个。因为 CubeMX 生成的默认链接脚本里.text、.data、.bss、.isr_vector这些都有明确安排但很少提.ARM.extab。一旦编译器生成了异常展开表链接器就打乱既有计划去安排它最后算出无法解析的地址表达式。打个比方你在装修图纸上给每个房间都标了用途但忘了标“杂物间”。工人进场后没法把杂物归类只能临时放到客厅和卧室之间的过道可过道尺寸又是根据客厅和卧室方案推算的最终导致整个排布表的预算没法算。链接器遇到孤儿段就是这么一种“没法算”的状态。解决思路也简单在图纸上把杂物间也明确画出来。4. 实际修复从改链接脚本到梳理编译选项下面这几种方式我按使用频率排序大多数情况下第一种就能解决问题后面几种是配套检查项。4.1 方案 A在 .ld 链接脚本里显式声明 .ARM.extab 和 .ARM.exidx先找到工程实际使用的.ld文件。CubeMX 生成的 Makefile 工程一般放在工程目录下有些 STM32 系列叫STM32F103RCTx_FLASH.ld有些叫linker.ld。如果用的是 CMake 方式可能还会有一份专门拷贝到cmake目录下的副本务必看链接命令里-T参数真正指向的那个。打开.ld在.text段的}FLASH之后、.data段定义之前加上这两段. ALIGN(4); .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } FLASH . ALIGN(4); .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } FLASH这里的FLASH表示把这两个段放在 Flash 区域。你脚本里实际的区域名可能不是FLASH比如有的 STM32H7 工程叫FLASH有的不同系列会叫ER_FLASH改成你自己脚本里的名字就行。加这两段的作用是让链接器在布局时明确知道异常索引和展开表是合法的输出段放在代码段之后按 4 字节对齐。如果你愿意也可以把这两段放在.text块内部在_etext符号赋值之前。效果上差不多但放到.text外更清晰一眼就能看到这是额外加的段。加完保存重新编译多数情况就过了。我在几个 C 工程上实测过改动只影响 Flash 末尾偏移不会破坏启动文件里的中断向量表布局。这里有个非常容易踩的坑CubeMX 重新生成工程时可能把.ld文件覆盖回默认版本。尤其是你在 CubeMX 里改了引脚或者外设重新生成后链接脚本一旦被覆盖这个错误就会原样回来。所以我建议在改之前先备份.ld或者干脆把修改后的内容记下来重新生成后第一时间对比。4.2 方案 B纯 C 或不需要异常时关闭异常展开如果工程本身是纯 C没必要开 C 异常或者你明确自己不会用try/catch那直接关掉异常和 RTTI 也是一个干净的处理方式。不需要try/catch时编译器就不会生成.ARM.extab链接器自然也就不会被这个段卡住。CMake 方式可以在CMakeLists.txt里加编译选项只对 C 文件生效add_executable(firmware ${C_SOURCES} ${CPP_SOURCES} ${LINKER_SCRIPT} ) target_compile_options(firmware PRIVATE $$COMPILE_LANGUAGE:CXX:-fno-exceptions $$COMPILE_LANGUAGE:CXX:-fno-rtti )用 CubeMX 生成的 Makefile 则要留意生成规则对 C 的支持。老版本的 CubeMX Makefile 主要面向 C 源码你如果需要加.cpp文件可能连编译规则都要自己补。比如 Makefile 里有C_SOURCES \ Core/Src/main.c \ ... CPP_SOURCES \ Core/Src/my_class.cpp链接命令也要确认。CubeMX 默认的LINKER变量一般是arm-none-eabi-gcc如果工程里有 C 文件这里最好改成arm-none-eabi-g或者在链接命令里补上-lstdc否则后续还会遇到undefined reference to __gxx_personality_v0。关掉异常会带来一个影响如果某个第三方库内部用了try/catch直接编译不过。所以这个方案适合“确认没有外部依赖异常”的工程。如果你只是想把异常表去掉但代码里确实有用到异常那就不要关用方案 A 更稳妥。4.3 方案 C确认链接器驱动是 gcc 还是 g这个报错和 C 异常相关所以链接器用哪个驱动很关键。CMake 在同时存在.c和.cpp源文件时链接阶段默认会调用CMAKE_CXX_COMPILER也就是arm-none-eabi-g这没问题。但如果你是从旧工程迁移过来的或者自己写的 CMakeLists 里强行把链接器指定成了arm-none-eabi-gcc就可能在链接时缺少 C 运行时的处理。在 CMake 工具链文件里常见配置是set(CMAKE_C_COMPILER arm-none-eabi-gcc) set(CMAKE_CXX_COMPILER arm-none-eabi-g)CLion 识别到.cpp文件时默认会用 CXX 编译器去链接。如果只有.c文件则用 C 编译器。问题常出现在你手动往工程里塞了.cpp文件但又没有让 CLion 正确处理文件类型的情况下链接命令仍然走arm-none-eabi-gcc于是异常处理的 personality 例程找不到。你可以打开 CLion 的 Build 窗口看详细输出如果链接命令里出现的是arm-none-eabi-gcc而工程里有 C 源码优先改成arm-none-eabi-g。反过来也成立。如果工程是纯 C但你无意间让链接命令走了g链接器会主动搜索 C 运行时的启动代码可能反而把不需要的异常段引入进来。这时候要让链接器回到arm-none-eabi-gcc或者至少检查CMAKE_EXE_LINKER_FLAGS里有没有多余参数。4.4 方案 D检查 CLion 工具链和 CMake 配置最后一个容易被忽略的点CLion 里配置的工具链是不是真的指向了 ARM GCC。很多人在 Windows 上装了 STM32CubeMX 和 GNU Arm Embedded Toolchain但 CLion 默认用的是系统自带的 MinGW 或者 MSVC导致编译命令本身就不对再叠加链接脚本问题表现就会很怪。建议在 CLion 的Settings - Build, Execution, Deployment - Toolchains里新增一个arm-none-eabi-gcc工具链CMake 配置里把Toolchain指过去。对于嵌入式工程还可以在CMakeLists.txt顶部加一句set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR cortex-m4)这样 CMake 不会去做常规主机系统的链接测试避免它在测试阶段就调用arm-none-eabi-gcc去链接一个没有启动文件的可执行文件从而产生莫名其妙的报错。另外可以给 CMake 配置一个合理的构建目录。CLion 默认的build目录没问题但如果你之前用过 CubeMX 生成的 Makefile工程根目录可能残留了.o文件和旧的.elf文件。链接器有时会拿旧目标文件参与布局产生非常难查的怪问题。建议在 CLion 里新建一个build-clion目录保持构建环境干净。5. 和 .ARM.extab 相关的同类链接报错排查实际工作中.ARM.extab这个错往往不是孤立出现的。它可能只是一个开始后面还跟着一堆异常相关或不相关的链接错误。我把自己踩过和见过的几个整理成下面这张表方便你对照。报错信息常见原因处理思路non constant or forward reference address expression for section .ARM.extab.ld脚本没有声明.ARM.extab孤儿段放置时算不出地址在.ld里显式加入.ARM.extab和.ARM.exidxundefined reference to __gxx_personality_v0链接命令用了gcc但工程里有 C 异常相关代码链接命令改为g或补-lstdc或禁用异常section .ARM.exidx not contiguous with .text.ARM.exidx没有紧跟在代码段后或者链接脚本没声明显式在 Flash 区域加入.ARM.exidx保证和.text连续region FLASH overflowed by X bytes加入异常段后 Flash 空间不足或原本就接近满去掉-O0改-Os开--gc-sections关闭不需要的异常file not found: libstdc.a链接时调用了 g但工具链的 C 运行库路径不对确认 ARM GCC 安装完整或者在 CMake 里正确设置工具链路径.ARM.exidx段出现在 RAM 区域.ld脚本里误把异常表放到了 RAM或放到了错误的REGION修改后面的内存区域名强制放 Flash这里多说一句--gc-sections。它在嵌入式工程里经常被用来裁剪掉未使用的函数一般会配合-ffunction-sections -fdata-sections使用。加了--gc-sections之后未引用的异常展开信息也可能被整体裁掉这本身是好事。但如果你裁剪的力度太大某些段虽然被裁了链接脚本里却还保留着对它们的物理地址引用也会出现跟地址表达式相关的怪错。所以遇到涉及段的报错先别盲目加裁剪参数把.ld和map文件对照着看。验证是否修好的方法也很简单。链接成功后用 ARM 工具链自带的工具看一眼 elf 文件里的段分布arm-none-eabi-objdump -h firmware.elf | grep ARM正常情况你能看到.ARM.exidx和.ARM.extab都有了明确的 VMA 和 LMA位置在 Flash 区段内。这说明链接器已经正常处理它们不会再把它们当孤儿段。6. 我踩过的坑和现在固定保留的排查习惯这类问题重复出现几次后我养成了几个习惯分享出来也许能帮你节省时间。第一个习惯是永远先备份.ld。不管是为了这个错误还是别的链接问题链接脚本是整个固件布局的“宪法”。CubeMX 重新生成工程时对已有的.ld文件处理方式因版本而异有时会保留有时会覆盖。我经历过改了一堆链接脚本重新生成工程之后全部白改的惨况。现在我的做法是把修改过的.ld单独复制一份放在config/目录下重新生成工程后自动对比差异需要时再复制回去。第二个习惯是学会读map文件。链接器看到的所有段、符号、最终地址都在map文件里。给链接命令加上-Wl,-Mapfirmware.map然后搜索.ARM.extab和.ARM.exidx你可以非常直观地看到这两个段被放到了哪里是不是跟 Flash 区域的地址重叠。很多看似随机的链接错误在map文件里都能找到线索。第三个小技巧是善用 CLion 的构建消息窗口。CLion 默认只显示绿色进度条和错误摘要但你可以点开具体任务的详细输出或者直接用下面的命令手动编译cmake --build build-clion --target firmware.elf --verbose详细输出里能看到完整的链接命令行、链接器脚本路径、库搜索路径。对照这些信息至少能确认你改的.ld确实被链接器用到了。很多时候我们在根目录改了一个.ld链接器实际用的是另一个副本改了半天当然无效。这种问题在 CMake 多层目录结构里尤其容易发生。处理这种“段相关”的链接错误我现在的第一反应是先看链接脚本有没有把该声明的段声明全而不是急着改编译选项或者换工具链。.ARM.extab这个错给了我们一个很好的提醒——CLion、CMake、CubeMX 都是工具链的外壳真正决定固件从哪里开始、数据放哪里的还是链接脚本。把 ARM EABI 的这些基础段在脚本里写清楚不但能解决眼下这个报错后续你再引入 C 代码、第三方库甚至开启更复杂的链接优化都会少掉很多莫名其妙的坑。
返回列表