ARTICLE DETAIL

资讯详情

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

Keil MDK报错L6236E排查指南:从.sct文件到启动文件的完整解决方案

Keil MDK报错L6236E排查指南:从.sct文件到启动文件的完整解决方案 这个L6236E报错只要搞过Keil MDK开发的朋友多多少少应该都撞见过。它长得比较吓人又是.sct文件又是error级别但实际处理起来绝大多数情况都是几分钟的事。这篇文章我会把这个错误的来龙去脉、真正触发的原因、一步步排查的方法以及几个不太容易想到的冷门坑都捋一遍。不管你用的是AC5还是AC6编译器不管你是新手还是老手按文中的顺序走一遍基本都能解决。1. L6236E这个错到底在报什么1.1 报错原文逐字段拆解先看这行完整的错误信息.\Objects\xxx.sct(7): error: L6236E: No section matches selector - no section to be FIRST/LAST.把这行拆开看其实信息量很大.\Objects\xxx.sct(7)说明问题出在链接阶段的分散加载文件里具体是第7行。注意这里的路径是工程编译中间产物里的.sct文件不是你源文件目录下的工程文件。L6236E这是ARM链接器armlink生成的错误编号L开头表示Linker6236是个错误码。No section matches selector没有任何输入段section能匹配上这个选择器。no section to be FIRST/LAST因为没有匹配的section所以链接器无法把这个段放到执行区的起始或末尾位置。翻译成人话就是链接器在打包的时候.sct文件里规定某个执行区Execution Region的第一个位置FIRST或最后一个位置LAST必须是某个指定的东西但链接器找遍了所有编译产物发现根本没有这个东西可选。于是它罢工了。1.2 .sct文件在链接时干了什么.sct全称是Scatter File中文叫分散加载描述文件。它在单片机工程里的作用简单来说就是告诉链接器你的代码、数据、堆栈分别应该放到存储器的哪些位置。一个典型的Cortex-M工程里.sct文件长下面这个样子; 12345678.sct LR_IROM1 0x08000000 0x00080000 { ER_IROM1 0x08000000 0x00080000 { *.o (RESET, First) *(InRoot$$Sections) .ANY (RO) } RW_IRAM1 0x20000000 0x00010000 { .ANY (RW ZI) } }注意看第一行执行区ER_IROM1里的*.o (RESET, First)它的意思就是把任意目标文件里名为RESET的section放到这块存储区的最前面。为什么要把RESET放最前面因为STM32这类芯片上电后从0x08000000开始读取栈顶地址紧接着读取Reset_Handler的入口地址。这块区域是向量表必须从Flash起始地址开始放顺序一乱程序启动就飞了。所以.sct里每个执行区选的首和尾都是被硬件启动要求或特定设计逻辑锁定的缺了它链接器不知道该怎么排布内存布局只能报错。2. 会触发L6236E的常见情况2.1 启动文件缺席或没进工程这是最最常见的原因没有之一。启动文件Startup是负责定义堆栈、中断向量表、启动代码的核心汇编文件通常叫startup_stm32f10x_hd.s之类。如果新建工程的时候忘了把启动文件加进去或者加了之后不小心删掉了又或者文件显示灰色没有参与编译那么RESET向量表对应的section就压根不存在。此时.sct里*.o (RESET, First)自然匹配不到任何东西L6236E就冒出来了。判断方法很简单打开工程左侧Project栏展开Application/User相关分组看Startup文件是否在列并且文件名是否正常显示。如果文件被排除编译图标上会有一个类似减号或禁止的标记。2.2 AC5与AC6编译器版本不匹配这个问题在近几年的工程迁移中尤其突出。Keil MDK从5.x版本开始默认集成了AC6基于armclang同时保留AC5基于armcc。老工程大多数沿用AC5对应的启动文件语法是基于ARM汇编风格的比如AREA RESET, DATA, READONLY而AC6要求启动文件必须是GNU风格的ARM汇编对于Cortex-M系列代码区别其实不是特别大但指令伪指令的写法、宏定义的处理、节名的声明方式都有差异。同一种启动文件在AC5和AC6下的报错逻辑不同有时AV6会直接报语法错误但有些时候编译能过链接阶段却出现L6236E因为编译器处理sections的方式不一致。2.3 分散加载描述与section名对不上有些工程师喜欢自己改.sct文件比如把代码区拆成两部分一部分放IROM1一部分放IROM2然后在每个执行区里都写了xxx.o (SECTIONNAME, First)。如果这么写了但对应的目标文件或者section名拼写错了比如把STARTUP写成了START或者把RESET写成了RESERT链接器一样匹配不到。要特别留意.sct文件里大写小写的问题ARM链接器对符号名的大小写敏感reset和RESET不是同一个东西。2.4 条件编译让向量表凭空消失这种相对隐蔽。启动文件里有时会这样写IF :LNOT::DEF:__STARTUP_CONFIG IMPORT SystemInit IMPORT __main ENDIF IMPORT |Image$$ARM_LIB_STACK$$ZI$$Limit|加上ST公司的宏定义、芯片型号未定义或定义错误的时候某些vector entry被条件编译活生生跳过了导致最终保留的section结构不完整。还有一种情况是所有DCD指令被编译成了空的向量表链接器虽然找到了RESET section但是KEYWORDS列表不匹配链接时依旧报FIRST/LAST错误。3. 按这个顺序排查基本都能解决3.1 第一步确认启动文件是否真的参与编译这个步骤其实最容易被忽略因为只要之前工程能用很少有人怀疑启动文件不见了。实际上我遇到过好几次这样的情况工程从A电脑拷贝到B电脑之后启动文件路径失效显示的是黄色感叹号但工程还能打开编译和链接时不报文件找不到而是直接当成不存在。原因在于MDK对缺失文件会跳过但不会主动提示链接阶段就表现为找不到section。操作建议右键点击工程名选择Manage Project Items。找到放置启动文件的分组看文件名前有没有异常标记。如果文件丢了直接从安装目录拷贝一个对应型号的启动文件进来。右键启动文件确认勾选了Include in Target Build。如果启动文件状态正常却还是报错再看下一步。3.2 第二步核对编译器版本与启动文件版本点击魔术棒Options for Target进入Target页面看右侧ARM Compiler下拉框选的是什么版本。如果选的是Use default compiler version 5对应AC5。如果选的是Use default compiler version 6对应AC6。有的工程还选了Version 5.06 update 7 (build 960)这种具体版本号。知道编译器版本以后再看启动文件是哪个版本。MDK安装目录下不同编译器版本对应的启动文件目录不一样。如果工程里的启动文件是AC5时代的老文件而你切到了AC6就会有兼容性问题。有个快速验证方法把工程里的启动文件替换成当前MDK安装目录下对应芯片厂商、对应型号的startup_xxx.s文件。比如ST官方库里的Device目录下一般同时有ARM和GCC两个版本的启动文件AC6用ARMGCC兼容的风格也没问题。这里有一个非常具体的点AC6下打开老工程会报L6236E原因往往出在RESET区域的定义上。AC5的汇编器会默认帮你在AREA声明里补全属性而AC6不会。如果一个启动文件开头只写了PRESERVE8 THUMB AREA RESET, DATA, READONLY在AC5下编译完全没问题但AC6下可能仍然编译通过却在链接时报FIRST/LAST错误。解决办法不是改.sct而是把启动文件源码里那段AREA RESET的定义改成完整形式AREA RESET, DATA, READONLY或者使用更符合GNU规范的.syntax unified .section .isr_vector,a,%progbits很多人不熟悉直接从ST官方新固件包里拿最新的startup文件替换即可ST已经适配了AC6。3.3 第三步恢复官方默认.sct再增量修改这是个特别实用的排错手段。如果你自己动过.sct文件又不太确定改得对不对最快的方式是恢复默认配置让Keil自己生成分散加载描述.操作路径打开Options for Target - Linker页面。取消勾选Use Memory Layout from Target Dialog。此时你可以看到当前的.sct内容先备份一下然后清空写回默认配置。重新勾选Use Memory Layout from Target Dialog点击OK。恢复默认后系统会根据Target页面里的ROM/RAM地址范围自动生成一份.sct这份文件理论上不会出现FIRST/LAST错误。如果恢复默认后编译还报错那问题一定出在工程文件本身而不是你的分散加载配置。如果恢复默认后错误消失说明你的原始.sct文件写错了。此时再增量修改每次只改一小部分编译一次就能很快定位到是哪条规则被链接器拒绝。3.4 第四步用map文件和fromelf验证section是否生成这一招很少在基础教程里被提到但排查这类问题非常管用。在编译完成后进入Objects或者Listings目录找到.map文件。这是链接器生成的存储器映像文件里面记录了所有被列入链接的section以及每个section的起始地址、大小和所属的目标文件。打开.map文件搜索RESET看能不能找到类似这样的行RESET 0x08000000 Section 384 startup_stm32f103xe.o如果找到了说明RESET section在链接阶段是存在的报错点可能在LAST部分比如某个指定为Last的段没有被生成。如果没找到说明这个section从入口Catalog阶段就被丢弃了。很多使用AC6的环境里链接器默认会回收未引用的section这意味着即使编译出RESET如果代码里没有引用到其中的符号链接器也会把它丢弃。你可以通过加入--keep参数强制保留或者仔细检查.sct里的匹配规则。具体做法在Linker页面往下找Misc Controls输入框加入--keepstartup_stm32f10x_hd.o(RESET)保存重新编译。注意这个写法在不同编译器版本下略有差异AC6里也可以这样写--keep*.o(RESET)另外可以用fromelf命令行工具直接查看elf文件里的section。在命令行下进入Objects目录执行fromelf --text -z -d output.axf-z参数可以输出section布局能很方便看到哪些section真实存在哪些被丢弃了。这招比翻map文件更直观因为它是直接读取最终elf的输出结果。4. 冷门但真实存在的深坑4.1 自定义.sct里出现空执行区这个情况是我在一次多段Flash映射的工程里踩到的。当时把Flash切成了几段不同段放不同功能.sct里写了多个执行区。其中一个执行区的代码是由条件编译控制的某个板卡配置下这整块代码都不会编译进来。结果就是执行区定义里没有任何有效section能放进去而我在这个空执行区里还写了First和Last链接器直接报L6236E。解决办法是把FIRST/LAST从空执行区里移走或者在链接选项里添加允许空区的参数。ARM链接器其实有一个指令EMPTY专门给这种可能为空的执行区预留位置ER_IROM2 0x08040000 0x00010000 { .ANY (RO) }本质上如果一个执行区里没有实际内容最好的做法是不给这个区加任何约束让它能够被链接器忽略。4.2 Last与.ANY的配合问题另一种情况和优先级有关。如果你在.sct里写了类似ER_IROM1 0x08000000 0x00080000 { *.o (RESET, First) .ANY (RO) *(.rodata*) }然后期待.rodata段被放到Last位置写法上需要明确加上Last否则它的位置完全由链接器根据内存填充逻辑决定。更重要的是如果.ANY匹配到的段已经在物理上占据了这个LAST地址而你又额外指定了别的段放最后就会产生L6236E。正确写法是把Last直接加在指定的section上ER_IROM1 0x08000000 0x00080000 { *.o (RESET, First) .ANY (RO) *(LastRegion, Last) }但前提是这个LastRegionsection确实存在于某个文件中。如果它不存在错误照旧。所以用Last时一定要确认对应的section确实存在并且没有被垃圾回收。4.3 老工程从C51迁移到MDK-ARM时的遗留问题还有个大坑来自51单片机转ARM工程的开发者。C51工程里习惯把所有代码段都用code关键字定义而MDK-ARM工程里RAM和ROM的分工更明确。有些从C51复制过来的代码在启动文件里定义了自定义的CODE段或者IDATA段然后下意识地在.sct里增加了对应执行区结果模块之间引用关系混乱出现了L6236E。如果是从STC、N76这类51单片机转过来的建议干脆把启动文件换掉不要沿用任何旧式工程模板直接新建立一个标准MDK-ARM工程再手动添加源文件能省去大量莫名其妙的兼容性问题。5. 把历史上撞见过的同类报错汇总一下5.1 不同变体错误信息的速查对照L6236E有很多变体核心不变报错位置和selector内容却各不一样。我整理了一份对照表方便以后遇到类似问题可以直接判断方向。错误信息片段真正的含义排查方向No section matches selector - no section to be FIRST/LAST指定为FIRST或LAST的section不存在启动文件缺失、section名拼错、函数被裁剪No section matches selector - no section matches startup.o(RESET)明确指定了startup.o的RESET段但没匹配到启动文件没有参与编译或目标文件名不同No section matches selector - no section matches * (CheckButton)自定义段CheckButton没有生成条件编译未开启、源文件漏添加No section matches selector - no section matches main.o(RO)找不到main目标文件的只读段main文件没加入工程或编译类型不对Execution region ... has no space执行区空间耗尽和L6236E伴生出现Flash内存不足或体积超过了目标芯片容量这个表的核心思想是信息里出现的selector越具体越容易定位。什么都没写只说FIRST/LAST的反而是最模糊的优先考虑启动文件层面。5.2 解决之后务必做的三项检查我见过一些朋友报错解决了但不注意细节后面很快又踩到别的坑。解决L6236E之后我建议至少做三次确认第一检查生成的.axf文件里RESET vector的地址是否符合预期。用fromelf或者调试器查看复位向量所在位置Cortex-M3/4/7系列的向量表首地址应该是0x08000000对应字节是栈顶地址。如果栈顶地址明显不对说明启动文件里的堆栈定义也有问题。第二清空一次编译中间文件重新全量编译。Keil的增量编译偶尔会因为Objects目录存在旧文件而出问题。Clean Target以后重新Rebuild可以排除中间文件干扰尤其适合刚还完编译器或工程模板的情况。第三检查启动文件中SystemInit函数的调用路径。如果SystemInit没被正确导入虽然不会直接触发L6236E但可能引发其他启动异常。启动文件底部的__main和SystemInit引用是ARM嵌入式程序启动的重要链条最好确保它们都正常。写在最后我也踩过很多次L6236E印象最深的是有一次折腾了一下午查了启动文件、编译器设置、分散加载都找不到原因最后发现只是自己在加源文件的时候不小心把MALLOC.C的文件名拼错了个字母导致某个全局数组所在section没编译出来。所以现在的习惯是报错之后先不急着修改按启动文件参与情况 - 编译器版本匹配 - .sct恢复默认 - 用map文件验证这个顺序走一遍。这样看起来慢实际是最快找到问题的路径。如果遇到不确定的地方最重要的就是区分清楚这个section到底有没有被编译出来。只要它存在解决L6236E就只是链接规则的问题它要是压根不存在你再怎么写选择器链接器也不可能凭空帮你造出来。
返回列表