ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

STM32链接报错L6236E?启动文件与分散加载文件排查指南

STM32链接报错L6236E?启动文件与分散加载文件排查指南 1. 先看懂L6236E在说什么链接器找不到第一个出场的代码段用Keil MDK开发的朋友十有八九都撞到过这个报错。当时我第一回见是在一个接手项目的第二天——别人给的工程一编译直接卡死在链接阶段弹出一行红色的.\Objects\xxx.sct(7): error: L6236E: No section matches selector - no section to be FIRST/LAST.行号还不固定有时候是第7行有时候是第3行但错误码永远是这个L6236E。很多人第一反应是去网上搜怎么解决但说实话你如果不懂这行字背后的链接机制搜来的答案大概率是加个启动文件检查一下芯片型号这种知其然不知其所以然的回复照着弄可能碰巧好了换个场景又废。先把这行报错拆开看。在ARM Compiler的链接阶段链接器会读取分散加载描述文件就是.sct文件这个文件里定义了ROM、RAM的分布区域以及每个区域里应该放什么内容。以最常见的STM32工程为例默认生成的xxx.sct大概长这样; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************************* LR_IROM1 0x08000000 0x00080000 { ; load region size_region ER_IROM1 0x08000000 0x00080000 { ; load address execution address *.o (RESET, First) *(InRoot$$Sections) .ANY (RO) .ANY (XO) } RW_IRAM1 0x20000000 0x00010000 { ; RW data .ANY (RW ZI) } }重点看这行*.o (RESET, First)意思是在某个目标文件*.o里必须存在一个名为RESET的代码/数据段并且它要被放在这个region的最前面First。这个RESET段就是启动文件里的中断向量表。处理器复位后第一条指令从0x08000000取到的是栈顶地址紧接着取到的是复位向量地址这决定了程序必须在这个位置放中断向量表。现在你回头看那个报错No section matches selector - no section to be FIRST/LAST.它的意思就是链接器在整个工程的编译产物里没找到任何一个满足条件、能被标记为FIRST或LAST的section。翻译成大白话——你工程里压根没有一个叫RESET的段或者说没有任何一段代码被指定放到这个位置所以链接器直接罢工了。那问题就变成为什么好好的工程会没有RESET段下面几节我把实战里遇到过的原因一个一个捋并且给出验证方法避免你瞎猜。2. 从启动文件入手排查九成的L6236E都在这儿2.1 启动文件到底去哪儿了这是最常见的情况没有之一。Keil工程树里的源文件其实分几种状态普通参与编译、被排除构建Exclude from build、以及压根不在工程里。很多人接手旧工程或者自己在网上下了个Demo会发现工程树里所有文件都在唯独少了startup开头的那个文件。STM32的启动文件命名通常是这样的startup_stm32f103xe.sstartup_stm32f407xx.sstartup_stm32f429xx.s或者老一些的startup_stm32f10x_hd.s这个文件就是产生RESET段的源头它里面写了整个中断向量表包括初始栈指针、复位处理器、各个中断的默认处理函数。没有它编译器再努力也编不出RESET段。排查方法很简单在Keil左侧Project窗口展开你的目标分组找有没有.s后缀的文件找到了就先双击打开看一眼里面有没有AREA RESET, CODE, READONLY这行不同芯片写法略有差异但关键词是RESET确认文件在但编译还是报L6236E——右键该文件看选项里Options for File中Exclude from build是否被勾上了。勾了就取消重新编译。这里插一句很多人在网上下的HAL库Demo或者GitHub上拉的开源工程作者用的是IAR或者STM32CubeIDE文件夹里根本没放Keil的启动文件。这时候你不能只加个空文件必须去ST官方固件库的CMSIS/Device/ST/STM32F1xx/Source/Templates/arm目录下找对应型号的启动文件加进工程。2.2 芯片型号和启动文件不匹配这种错误更隐蔽编译不报错但链接必炸。我举个例子。假设你现在用的是STM32F103ZET6512KB Flash但工程里挂的启动文件是startup_stm32f103c8t6.s64KB Flash的多见于小容量芯片。启动文件里不只会放向量表还会定义堆栈大小、以及一些芯片特有的初始化方式。两者混用在某些链接配置下不会直接报型号不对而是导致section属性异常最后就推送出L6236E。更典型的场景是从F1往F4迁移或者在F407和F429之间切换。有人为了省事换芯片后只改了Device型号启动文件还是老的。F4系列的启动文件在向量表长度、SystemInit调用方式上和F1完全不同这时候报的错五花八门L6236E就是其中之一。验证与处理点击魔术棒Options for Target→ Device选项卡确认当前选择的芯片型号去ST固件库里找到对应型号的Keil启动文件替换工程里原有的替换完务必把原来那个.s文件从工程里移除别让两个启动文件同时存在。工程里挂两个启动文件是个非常经典的低级错误链接器有可能会去匹配其中任意一个的RESET段结果两个都不是合法定义照样报错。2.3 C/C选项卡里的宏定义被清空这点很多老手都会忽略但新手特别容易踩。某些芯片的启动文件里不仅仅有向量表还包含一些条件编译的代码。比如有些F1系列的启动文件里会通过STM32F10X_HD这种宏来决定是否编译进大容量芯片特有的中断处理。Cortex-M内核的芯片启动文件相对干净但很多厂商比如GD32、华大、NXP的部分系列的启动文件里都会预编译宏相关的判断。当你在C/C选项卡的Define框里把宏删了或者覆盖了启动文件里的一部分段定义就不会被编译进去RESET段缺失链接阶段就冒出L6236E。检查方法打开 Options for Target → C/C → Preprocessor Symbols → Define对照芯片型号查看是否缺少必须的宏。以STM32F103系列为例如果用标准外设库至少要有STM32F10X_HD或STM32F10X_MD这类区分容量的宏具体看是Standard Peripheral Library还是HAL库HAL库通常需要STM32F103xE这种宏可以用一个已知能正常编译的工程作为参照把Define里的内容补全。提示有些移植老工程的朋友不知道Keil 5从某个版本起对启动文件的默认编译行为做了调整个别旧工程需要手动勾选Use default compiler version 5或者加--c99才能正常编过。但L6236E和编译器版本的关系没那么大如果上述方法都不生效再考虑这个方向。3. 分散加载文件.sct被改坏或选错链接器动手脚的元凶第二种高频场景是.sct文件本身出了问题。很多人对它理解不深觉得就是个自动生成的东西碰都不碰。但一旦你手动改过一次后面就可能埋雷。3.1 被选中执行的.sct和实际内容对不上Keil里分散加载文件的位置在Options for Target → Linker → Use Memory Layout from Target Dialog当你勾选Use Memory Layout from Target Dialog时Keil会根据你在Target选项卡里填的ROM/RAM地址自动生成.sct文件你不需要关心它长什么样。但如果你取消了这个勾选下面那个Scatter File编辑框变为可编辑状态你就可以指定一个自定义的.sct文件。问题就出在这儿——当你指定的.sct文件内容是针对其他芯片写的或者是从旧工程拷过来忘了改地址链接器就会拿着这个错误地图去寻找段。举个真实场景一个F103ZE的工程Flash是512K0x08000000–0x0807FFFF但.sct文件还是从F103C8T6工程拷来的只定义了64K0x08000000–0x0800FFFF。程序稍微大一点链接器在ROM区后半部分找不到足够的空间放启动文件定义的段就会报一堆乱七八糟的错误其中一定包含L6236E。另外还有一种情况是这个.sct文件虽然格式正确、地址也对但缺少了启动段标记。比如说有人手动改过把*.o (RESET, First)这行误删了。那就会立刻触发L6236E——因为链接器要求region有一个FIRST段但你的描述文件里根本没说用哪段来当FIRST。排查方法打开 Options for Target → Linker检查是否勾选了Use Memory Layout from Target Dialog如果没勾看Scatter File指向的文件路径用编辑器打开对照你当前芯片的起始地址和大小确认每一段region里是否包含类似的FIRST/LAST标记行尤其是第一个加载域。3.2 恢复默认.sct的操作如果排查一番发现.sct文件里内容乱七八糟最简单止损的办法是打开 Options for Target → Linker勾选Use Memory Layout from Target Dialog删除下面Scatter File编辑框里的自定义路径点OK重新编译。这样Keil会重新按照Target对话框里的IROM1/IRAM1配置自动生成一份干净的sct文件。绝大多数情况下这个动作就能解决由于sct文件损坏导致的问题。3.3 手动改.sct时的保命原则如果你确实有需求要手动定制内存布局比如在RAM里跑代码、或者把某个固定地址放Bootloader的跳转信息那么修改.sct文件时务必保留每个region内部的FIRST/LAST声明。一个可用的最小示例LR_IROM1 0x08000000 0x00100000 { ER_IROM1 0x08000000 0x00100000 { *.o (RESET, First) *(InRoot$$Sections) .ANY (RO) .ANY (XO) } RW_IRAM1 0x20000000 0x00020000 { .ANY (RW ZI) } }如果你自定义了某个段放在固定地址比如要用__attribute__((section(MY_DATA)))可以在RW区加一行RW_IRAM1 0x20000000 0x00020000 { .ANY (RW ZI) *(.MY_DATA) }反过来如果你的项目里确实没有启动文件比如纯内存初始化、或者某个已经初始化好环境的场景你需要把*.o (RESET, First)删掉同时把ER_IROM1里的First属性改成其他段名。但这是极少数场景普通应用不要碰。4. 进阶排查链路从.map文件和链接日志找到真正的断点如果前面几招都试过了还不行就说明问题不像少了启动文件这么简单。这时候我建议你不要继续盲试而是翻开链接器给出的详细日志和生成产物进行逻辑推理。4.1 打开链接器的详细输出默认情况下Keil的Build Output只显示错误和警告很多细节被吞了。你可以通过设置让链接器输出更详细的信息Options for Target → Listing → 在Linker Listing区域勾选所有项目尤其是Memory Map 和 Cross Reference重新编译后在工程目录的Listings文件夹下会生成一个.map文件用记事本打开。先看文件末尾的Image Symbol Table和Memory Map of the image确认启动文件里的标号是否存在。正常情况下你会看到类似Reset_Handler 0x08000000 Code SystemInit 0x080001b8 Code __Vectors 0x08000000 Data ; 注意某些编译器把向量表识别为Data如果连__Vectors、Reset_Handler都没有基本可以确认RESET段没有被打进最终镜像。4.2 用ARM编译器工具链做尸检Keil安装目录下有一堆命令行工具其中fromelf很实用可以把最终ELF文件反汇编/导出详细信息。命令类似于fromelf --text -z -d --outputoutput.txt .\Objects\YourProject.axf-z是显示零初始化段信息-d是反汇编代码段。查看生成的txt里有没有Reset_Handler和__Vectors再看它们的地址是不是落在0x08000000附近。如果没有生成.axf文件说明链接失败看不到产物。这时候可以考虑先注释掉.sct里的FIRST行临时编一个不要求FIRST的镜像看看能不能链接成功。如果这样能出.axf就证明问题确实出在没有可用的RESET段而不是.sct文件其他地方的语法或地址问题。4.3 查看Build Output窗口最顶部的警告有些L6236E报错之前其实编译过程已经给出过警告只不过在长长的输出里被滚动淹没了。编译完后从Build Output窗口的最顶上开始往下翻留意类似这样的信息compiling startup_stm32f103xe.s... assembling startup_stm32f103xe.s...如果有assembling这行说明启动文件被汇编器正常处理了。如果这一行都没出现说明这个.s文件根本没进入编译流程——这时候就算你加再多的.sct配置也白搭。我遇到过一种情况工程里.s文件确实在但我用的Arm Compiler 6编译器需要额外的汇编选项结果启动文件被静默跳过。后来在.s文件的Options里把Assembler选项卡的Assemble Thumb Code和相关配置改成和默认工程一致问题才解决。4.4 从报错行号反推哪个region缺段L6236E报错信息里括号中的数字比如.sct(7)指的就是出错位置在第7行。你可以打开自己工程的链接文件数一数是哪一行对应的region报错。以我前面的示例sct为例第1行: ; ************************************************************* 第2行: ; *** Scatter-Loading Description File generated by uVision *** 第3行: ; ************************************************************* 第4行: 第5行: LR_IROM1 0x08000000 0x00080000 { 第6行: ER_IROM1 0x08000000 0x00080000 { 第7行: *.o (RESET, First) 第8行: *(InRoot$$Sections) 第9行: .ANY (RO) 第10行: .ANY (XO) 第11行: } 第12行: RW_IRAM1 0x20000000 0x00010000 { ...报错说是第7行对应就是*.o (RESET, First)这一句。所以该行所要求的RESET段缺失。如果你的报错行号是其他行比如指向LR_IROM1那一行说明整个加载域都有问题可能就是地址重叠或者非法。这个方法能帮你快速确认到底是所有region都没段匹配还是只有某一个region缺段。这两种情况的处理逻辑不太一样——前者通常是启动文件缺失后者往往是段选择器的写法问题。5. 从CubeMX生成工程和旧工程迁移中的特殊雷区5.1 STM32CubeMX生成的Keil工程为什么也会报L6236E这几年用CubeMX建工程的人越来越多按理说它生成的工程应该是开箱即用的。但你要是遇到过以下操作照样会踩到L6236E生成工程后手动把startup_stm32xxxx.s文件从工程里删了想自己写启动逻辑用CubeMX切换了芯片型号但Project Manager → Project里的工具链设置没跟着变或者旧启动文件没被覆盖在.ioc文件里改了Flash/RAM大小但重新生成代码时勾选了备份旧工程结果新旧启动文件混杂使用了自己添加的链接脚本而CubeMX自动生成的sct被覆盖了。第一种场景比较难办。如果你确定不要ST的启动文件那么你得自己提供一个向量表段并且保证它带有初始栈指针和复位向量。很多从裸机汇编开始写固件的老工程师会这么干但对于绝大多数应用来说直接用官方启动文件是成本最低、最稳妥的方案。第二种场景的处理方法是在CubeMX里重新选择正确的芯片然后在Project Manager → Linker Settings里确认Heap/Stack大小没问题最后重新生成代码让CubeMX把启动文件一并刷新。5.2 旧工程换芯片最完整的操作顺序如果你手上有个F103C8T6的旧工程要改成F103ZET6千万别只改Device型号就完事。我的标准操作顺序如下你可以直接照着做备份整个工程目录这一步每次都要做别省Options for Target → Device选择新芯片Options for Target → Target核对ROM/RAM地址和大小。F103C8T6的RAM是20KB0x20000000大小0x5000Flash 64KB0x08000000大小0x10000换成F103ZET6后RAM是64KB0x10000Flash是512KB0x80000从ST官方固件库或者另一个正常运行的F103ZET6工程里复制startup_stm32f103xe.s到工程目录然后在Keil里删除旧启动文件、添加新启动文件检查C/C选项卡的DefineF103ZE对应STM32F10X_HD标准外设库或STM32F103xEHAL库清理编译产物点Rebuild不是Build是Rebuild。如果还报错去检查Linker选项卡里是否勾着Use Memory Layout from Target Dialog确认Scatter File路径没有指向旧芯片的sct。这套流程走下来L6236E基本不会再出现。5.3 使用AC5和AC6编译器时的启动文件差异Keil MDK从5.x版本开始默认的编译器逐渐向Arm Compiler 6基于Clang迁移。AC6和AC5在处理汇编启动文件上有很多差异这一点会被很多人忽略。AC5使用的armcc/armasm对.s汇编文件的语法比较宽松。而AC6使用的armclang内置的汇编器对伪指令的支持不够全面某些老启动文件在AC6下会汇编失败或者产生不同的段属性。如果你的工程是从AC5迁移到AC6后开始报L6236E优先检查启动文件是否是比较老、为AC5编写的版本ST官方针对AC6发布过新版本的启动文件CMSIS pack里的通常没问题在Options for Target → Target → ARM Compiler里尝试暂时切换回Use default compiler version 5如果能编译通过说明就是启动文件对AC6兼容性的问题。从Arm Compiler 5切到6之后很多旧版启动文件会直接报汇编错误不会走到L6236E这一步。但有一种情况是汇编只产生警告没有报错最后段的属性不对才最终在链接阶段爆炸。5.4 工程路径和特殊字符的隐患这个方法很少有人提最后分享一个很多人不会第一时间想到的原因工程所在路径太长或者包含中文、空格、特殊符号。当年我遇到过一个很邪门的L6236E所有的检查和替换都做完了问题就是复现不了。折腾了一下午最后把整个工程文件夹从D:\工作文件\测试项目\基于STM32的温控系统_V2_最终版(1)\挪到D:\keil_projects\temp\重新编译一切正常。原因大概是链接器在解析sct里通配符和路径时对中文字符和多级嵌套目录的处理在某些编译器版本里有Bug。虽然这个bug不是每次必现但一旦遇到排查起来极其耗费时间。所以建议你的工程存放路径尽量满足全英文不要有空格层级不要超过三四级不要以数字开头在某些老版本里也会出问题更不要放在桌面这种权限有时受限的目录下。6. 实操总结最快的解决路径和一套顺手能用的验证方法我把上面所有的排查逻辑压缩成一套可以直接照做的动作建议你按顺序执行不要跳步。第一步确认启动文件存在且参与编译工程树里有没有.s文件右键.s文件 → Options for File → 确认 Exclude from Build 未勾选。打开.s文件搜索AREA和RESET关键字。第二步检查芯片型号和宏定义Options for Target → Device确认芯片型号。Options for Target → C/C确认Define里有对应型号的宏。如果不确定宏应该是什么去正常能编译的工程抄一份或者查ST官方模板。第三步重置分散加载文件Options for Target → Linker勾选Use Memory Layout from Target Dialog清除自定义Scatter File路径。Options for Target → Target确认IROM1起始地址0x08000000大小按芯片Flash实际大小填IRAM1起始0x20000000大小按芯片实际RAM填。Rebuild整个工程。第四步查看详细链接输出定位Options for Target → Listing勾选Linker Listing下的Memory Map、Cross Reference等。重新编译后打开.map文件搜索Reset_Handler和__Vectors。如果找不到说明启动段的产生就有问题回头检查汇编文件。也可以用fromelf反汇编.axf文件验证段地址。第五步整路径、整编译器工程路径改成纯英文短路径。尝试在Target选项卡里切AC5/AC6判断是否是编译器兼容性问题。这套流程我前前后后帮同事处理了不下十次耗时从几分钟到一下午都有。但其实九成以上的情况走完第二步就能解决第三步都很少需要用到。再分享一个我自己的习惯**每次新建工程时先不写任何业务代码只放一个main函数和一个printf重定向确认能在这个工程模板里编译、下载、跑通串口再往里加功能。**这样一旦后面报错你能确定是新增代码导致的问题而不是工程基础配置本来就有毛病。L6236E这类链接错误说白了就是工程配置和实际代码对不上号你得让工程底子干净问题才好定位。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表