news 2026/10/5 3:04:54

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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开头表示Linker,6236是个错误码。
  • 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。

操作建议:

  1. 右键点击工程名,选择Manage Project Items。
  2. 找到放置启动文件的分组,看文件名前有没有异常标记。
  3. 如果文件丢了,直接从安装目录拷贝一个对应型号的启动文件进来。
  4. 右键启动文件,确认勾选了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自己生成分散加载描述.

操作路径:

  1. 打开Options for Target -> Linker页面。
  2. 取消勾选Use Memory Layout from Target Dialog。
  3. 此时你可以看到当前的.sct内容,先备份一下,然后清空写回默认配置。
  4. 重新勾选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输入框,加入:

--keep=startup_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就只是链接规则的问题;它要是压根不存在,你再怎么写选择器,链接器也不可能凭空帮你造出来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 3:04:10

Spring Boot教学资源库系统开发实战:从需求分析到答辩准备

1. 选题背景与系统定位1.1 为什么“教学资源库系统”一直是毕设热门每到毕业季,Java方向的同学都会面临同一个灵魂拷问:做什么题目才能既有工作量、又有技术含量、还不会被答辩老师当场问倒?我的建议一直是,像“基于Spring Boot的…

作者头像 李华
网站建设 2026/10/5 3:03:27

GitHub 内网镜像搭建实战:把 Clone 从几分钟降到十秒

最近我们组做一次大型依赖升级,十几个开发同时开工,结果第一波 clone 就把人卡在原地。有人仓库拉到一半报错,有人 CI 里拉依赖直接超时重试。问题根本不是代码冲突,而是大家都在等 GitHub 传数据。GitHub 的服务节点主要在海外&a…

作者头像 李华
网站建设 2026/10/5 3:02:34

如何为Agent代码写可靠的测试?Shepherd离线确定性测试实践

如何为Agent代码写可靠的测试?Shepherd离线确定性测试实践 【免费下载链接】shepherd A runtime substrate that turns an agents execution into a reversible, Git-like trace, so meta-agents can observe, fork, replay, and revert any run. Couples agent and…

作者头像 李华
网站建设 2026/10/5 3:01:51

数据分析入门实战:用Pandas搞定数据清洗三大硬骨头

1. 任务定位:一个“看名字就知道要求”的开胃题Wk.2_HW,拆开就是 Week 2 Homework。但凡经历过几个像样的项目制课程或内部训练营,一看到这种命名的目录,基本就能猜到它背后是一套有节奏的任务体系:第一周打基础&#…

作者头像 李华
网站建设 2026/10/5 3:01:43

慈善钓鱼:灾害背后的双向诈骗与识别防护

每次大型灾害过后,社交媒体上都会被两类内容刷屏:一类是真实的求助与募捐,另一类是趁乱生长的伪装帖。后者在安全圈里有个专门说法,叫“慈善钓鱼”——拿灾难当鱼饵,拿同情心当鱼钩,等捐款人咬住钩子再完成…

作者头像 李华
网站建设 2026/10/5 3:01:00

GPU租赁实战指南:从算力瓶颈到PyTorch环境配置一次讲透

上周一个做视觉检测的朋友问我要怎么上深度学习,说公司预算卡得紧,两片RTX 4090就要四万多,实在下不去手。我给他指了条租GPU的路子,按小时包一台带RTX 4090的云主机,第一天跑下来成本不到一百块钱,效果和自…

作者头像 李华