打印
[开发工具]

一次看懂:为什么切换BOOT引脚后MCU“上锁”依然能重新下载程序?

[复制链接]
60|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
DKENNY|  楼主 | 2025-2-23 13:55 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 DKENNY 于 2025-2-23 13:54 编辑

#技术资源# #申请原创# @21小跑堂

前言
      在各种ARM Cortex-M系列微控制器中,经常会看见BOOT0、BOOT1或者类似功能的引脚。我们常常会疑惑:  
        • 这个BOOT0究竟是干什么用的?  
        • 原本已经给MCU上锁了,为什么换个BOOT引脚的电平,就能再次下载程序呢?  
        • 这和Keil里的Flash地址设置有什么关系,难道新的程序不是写到原来上锁的地方吗?  

      这些问题在嵌入式开发过程中很常见,但它们的答案却与MCU底层的安全机制、启动流程(Boot sequence)以及厂商预置的系统级引导程序(System Bootloader)息息相关。要想透彻地回答,要先明白一些关键概念,包括:  
        • 上锁(Lock)是什么含义;  
        • BOOT模式在MCU启动时如何决定程序入口;  
        • 系统引导程序(System Bootloader)与用户程序的对应关系;  
        • 解锁与擦除(Erase)的过程;  
        • Keil等IDE中的Flash地址配置与MCU内部分区关联。  

      这里先上一张完整的思维导图,后面的内容就是围绕这个思维导图加以分析。


1. 什么是“上锁”,以及它的保护机制原理
      要解释“切换BOOT”为何能重新下载程序,首先要认识到MCU内部的那些“上锁机制”到底是怎么回事。

1.1 上锁的含义  
      通常来说,“上锁”(Lock)指的是对MCU内置的Flash或特定存储区域设置了“读写保护”或“调试保护”,从而阻止外部设备(例如J-Link、DAP-Link或其他仿真器)直接在运行阶段获取固件。这样做是为了防止竞争对手反编译或复制固件,也为了保护开发者的知识产权和商业机密。因此,一旦MCU处于锁定状态,正常调试接口(SWD或JTAG)经常会被屏蔽或受限,读出功能大多被禁止,这就导致我们无法再用普通手段重新烧写程序或者读取其中的代码。

1.2 上锁的层次与类型  
      一般,芯片内部会提供几个不同的“保护等级”。以某些常见架构为例,可能包括:  
        • Level 0:无保护。可以随心所欲地读取或写入;  
        • Level 1:部分保护/读保护。阻止外部读取,但是仍可通过特定过程执行擦除;  
        • Level 2:最高级保护,甚至Chip Erase也无效;一旦开启,可能几乎无法解锁。
      APM32系列一般也会存在类似的等级划分,但具体命名或数量可能略有差异。无论如何,关键点在于:“上锁”后,外部工具想要访问MCU,就必须遵从厂商预先定好的解锁流程,其中往往会借助一个比较特别的手段——系统级Bootloader。

1.3 用户在程序中启动的保护  
      多数情况下,我们在固件里会设置相应的选项字节(Option Bytes)或寄存器,并在上电或复位后自动生效。等到我们再想连接仿真器时,就会发现“被锁住了”,无法直接读写内部Flash。  
      如果上锁了,在MCU“以用户程序启动”的状态下,调试器连接就无法执行正常的擦写操作——这是因为芯片判断到你想访问用户区,但权限不足或已受保护。此时,就需要“换个入口”来进行解锁,这个“入口”就是系统Bootloader模式,它是通过BOOT引脚进行选择的。

2.BOOT引脚带来的启动流程差异

2.1 BOOT模式概述  
      从硬件层面看,大多数Cortex-M MCU会在上电或复位瞬间,先读取一个或多个BOOT引脚(如BOOT0、BOOT1)的电平,以确定接下来程序计数器(PC寄存器)应该跳到什么地址开始执行。常见的模式包括:  
        • 模式A:从用户Flash启动(User Flash Memory);  
        • 模式B:从系统存储区域(System Memory)启动,也就是去执行系统级Bootloader;  
        • 模式C:从内部RAM或外部存储启动(有些MCU支持,依据引脚组合决定)。
      以APM32系列为例,通常:  
        • 若BOOT0=0,则直接执行用户区;  
        • 若BOOT0=1,BOOT1=0,则进入系统Bootloader;  
        • BOOT0=1, BOOT1=1可能是其他特殊模式,依不同芯片而定。

2.2 若从用户Flash启动  
      MCU若侦测到BOOT0=0,就会把PC寄存器设置为User Flash入口(0x08000000)处的复位向量,然后执行用户程序里的启动代码。那意味着:  
        • 如果用户程序之前已经设置了读写保护,外部调试器此时往往无法访问;  
        • 也就无法直接擦掉或写入任何东西;  
        • 也就相当于“被锁在门外”。

2.3 若从系统Bootloader启动  
      而当BOOT0=1(或相应引脚配置)让MCU跳到“System Bootloader”那一段时,芯片将执行厂商预先烧录、不可更改的固件逻辑。该System Bootloader通常具备较高权限,能够进行擦除、下载、解锁等操作,且不会去管用户程序本身的保护设置。因为这时它自成一体,具有“低层访问权”。

2.4 为什么系统Bootloader能绕过用户保护?
      系统Bootloader是芯片自带的底层软件,存在于特别分区(如System Memory)中,与用户区Flash分离。它在设计上就拥有一些特权,如:  
        • 允许接收外部指令(UART、SWD、USB DFU等),  
        • 允许对用户区进行批量擦除(Chip Erase),  
        • 可以修改选项字节从而撤销读写保护或对Flash重新编程。  
      由于它不执行用户程序的上锁逻辑,也不受用户程序中设置的防护机制影响,所以就可以帮我们绕过用户程序的封锁,完成重新下载或清空Flash的目的。


3.系统Bootloader与用户程序存储位置的区别

3.1 为什么有人误以为代码会烧到System Memory里?  
      有些人看到“切换BOOT就能下载程序”时,会误以为“那程序是不是下载到系统存储区里啦?”其实并不是。系统存储区是厂商预先烧录出来、只读且大小有限,主要放置一些用于ISP(In-System Programming)的固件。真正留给用户放置应用程序的区域,依旧是User Flash,比如APM32中常见的0x08000000等起始地址。

3.2 Keil工程中的Flash地址设置  
      在Keil MDK等IDE中,我们常会在“Project->Options for Target->Target”里配置:  
        • IROM1 Start: 0x08000000  
        • IROM1 Size: 根据MCU的Flash容量设置,如128KB或256KB等。  


      这样做表示:编译生成的最终映像文件是给“0x08000000”这片地址空间使用的,也即用户Flash。这与系统Bootloader所在区域(有时在0x1FFF0000之类的地址)并不冲突。换言之,我们在System Bootloader模式下进行下载时,只是“让系统Bootloader把新的固件写到0x08000000起始的区段里。”

3.3 只能阅读、不能覆写的System Bootloader区  
      厂商之所以把System Bootloader区做成只读或高保护权限,目的就在于防止用户程序的错误操作毁坏这段关键固件。若这段固件被破坏,MCU就很可能无法再进行自我编程或出厂状态的恢复。  
      正因如此,我们无法随意把自己的程序写到系统Bootloader区里去,也不会有人在正常场合故意替换它。因此,下载新程序还是回到用户区,也就是原本编译时指定的地址区段。

4.为什么切换BOOT就能破除“防盗”?

4.1 系统Bootloader的特权机制  
      前面已经提到,系统Bootloader不受用户程序中保护逻辑的限制,可以执行那些“只有厂商才授权”的指令序列,包括:  
        1) 解锁:复位或清除某些选项字节,使Flash恢复到可写状态;  
        2) Chip Erase:整片擦除用户Flash;  
        3) 再次设置新的保护级别。  
      对某些安全要求较高的场合,这也意味着必须要有物理上的BOOT引脚接线权限,才能对系统Bootloader进行访问。毕竟一旦可以进入System Bootloader,通常就能擦除用户代码(虽然有些最高级别保护可能依旧阻止此操作)。

4.2 这算不上“破除防盗”  
      从严格意义上讲,这并不算对MCU保护措施的“**”。而是厂商在设计时就预留了“一条可以通过擦除来解锁”的途径。但随着保护等级的提高,比如Level 2,有些芯片会彻底禁止再进入系统Bootloader进行解锁,或者即使进入也无法解锁,这样才是真正的牢固。  
      因此,“利用BOOT引脚进入系统Bootloader然后擦除并写入新程序”是厂商公开提供的正规流程,这对低级或中级保护来说合情合理;高保护级别才会视情况关闭这条通路。

4.3为什么BOOT能够绕过保护?


5.Keil中的Flash地址与System Memory的关系再议

5.1 IDE配置与硬件启动的对应表  
      在Keil里指定0x08000000作为应用程序起点,并不意味MCU上电时自动就会从此处开始执行——还要看BOOT引脚实际选择了哪个启动模式。如果设置BOOT0=0,MCU就会默认从0x08000000的映射区取出复位向量。  
      若我们通过切换BOOT进入System Bootloader时,MCU最先执行的地址就变成了系统Memory地址,如0x1FFFxxxx(具体以芯片手册为准)。但在System Bootloader的指令集下,目标烧写地址依旧是0x08000000。

5.2 那烧写区是不是可变的?
      某些高阶需求下,我们也可以指定别的起始地址(如为了做双分区升级,或者插入自定义Bootloader)——但这在Keil中就需要重新指定IROM1的起始地址。例如,我们可以让应用程序从0x08001000开始,把0x08000000~0x08000FFF留给自定义Bootloader。  
      这与系统Bootloader并不矛盾——系统Bootloader只是对你指定的地址空间进行擦写操作,如果你告诉系统Bootloader“我的新程序要写到0x08001000起”,它也能执行。

5.3 总结:一致性与灵活性  
      • 一致性:系统Bootloader写进去的区域,与Keil工程里预先编译好的用户区是匹配的;  
      • 灵活性:如果开发者想在用户Flash里分割出另一片给自定义Bootloader或数据存储,仍需自己在编译时修改对应链接脚本或Keil设置。

6.上锁与切换BOOT的过程
      为了便于理解,我这里用一张流程图表示其详细过程。


7.常见疑问
7.1 切换BOOT就能读出原程序吗?
      通常不行。理由是:如果MCU已经开启读保护,系统Bootloader可能允许你擦除整个Flash,但不会直接将密码学意义上的“已锁死”区域解锁给你任意读取。通常,进入System Bootloader之后,你可以做的最彻底操作是“Chip Erase+解锁”,这样原固件就被抹除了,所以也谈不上“把加密固件读出来”。  
      当然,如果保护等级不高,有些MCU或工具可能会提示你“是否要执行完整擦除来解除读保护?”一旦你选择执行,MCU内部用户程序就完全清空。换句话说,你拿不到原内容,顶多是把Flash清零,恢复到可写的空状态,然后再烧写新程序。

7.2 新的程序下载后,是否依旧处于上锁状态?
      这取决于你有没有在选项字节里再次设置保护。如果你没有专门开启保护,新下载的程序就默认为无锁或者低级保护。我们常在初始化代码里或在编程软件中设置选项字节,将其重新保护起来,看具体需求。

7.3 如果有人拔高BOOT0就能随意解锁,那保护岂不是很弱?
      这是一个常见的误解。实际上,低等级的“读保护”本来就提供的是“防止轻易读取或复制程序”的门槛,并不保证物理防攻破。如果在硬件层面无法阻止别人动手脚接线到BOOT0,那的确能进行擦除操作,但也只是擦除了原程序而已,“读取原程序”依然是不可得的。对某些非常敏感的场合,就需要更高的保护等级或者在封装层面物理封堵BOOT引脚,以加强安全。

7.4 BOOT1有什么作用?
      在一些MCU中,BOOT1也可能辅助决定“当BOOT0=1时,是去System Bootloader还是去SRAM”之类的启动方式。通俗来说,BOOT1往往是用来进一步细化启动地址,但并不是每个型号都必须用到。

7.5 能否在用户程序里实现一个‘软件跳转’到System Bootloader?
      通常可以。厂商在应用笔记中有时会提供“从用户程序软件跳转到System Bootloader”的方式,比如先设置一些特定寄存器,然后以某种地址跳转过去。这样就不用去动硬件引脚了。但是如果用户程序本身已经上锁并阻止了调试,那么通过软件跳转也要看保护级别和跳转策略。很多时候为了保证安全,还是通过硬件BOOT引脚最为可靠。

8.常见误区与进一步思考

8.1 “上锁”并非无懈可击  
      我们以为芯片一旦上锁,外界就地无法触碰。其实低等级锁更多只是防君子不防小人,能阻止一般性读取,但并非防止“额外硬件攻击”或“极端物理攻击”等。这就像房子上了一把锁,一般人不会去破坏门,但真正有心的人可以使用扭力工具破门而入。同理,对嵌入式系统,有物理接触的人通常会有更大权限来尝试突破,除非你用非常高强度的安全措施(例如芯片硬件熔丝、最高级别不可逆保护等)。

8.2 为什么厂商要留这种解锁通道?
      这是为了生产维护方便,也是为了给用户保留某种“后门”以防止刷机失败或程序进入不可挽回的死循环状态时,还有办法重新让系统复活。如果MCU完全没有这样的Bootloader和BOOT引脚,一旦用户程序错配或锁定,芯片就只能报废,这会是很大的浪费。

8.3 为何要特别关注Keil中的Flash设置  
      有时候,我们可能在工程里错误地把IROM1起始地址设为0x00000000或与系统Bootloader重叠的地址,结果导致程序无效或无法启动。正确的做法是查阅芯片手册中描述的Flash映射结构,把编译链接地址与MCU实际用户地址保持一致,通常是0x08000000(APM32系列的常用数值)。不然即使你成功擦写了,也可能跑不起来。

8.4 给Bootloader再次上锁?  
      有些高安全场合会采用物理手段拉死BOOT0=0,或者把BOOT0电路焊接到GND并封装,使人无法轻易改动,这样就算别人拿到硬件也难以进入System Bootloader执行擦除。当然,我们在生产阶段如果不需要再反复升级,这就是一种提高安全性的方法。但这也意味着若程序出了严重Bug,后续也无法用这个通路来“抢救”芯片了。

9.进一步的安全策略与扩展

9.1 自定义Bootloader vs. System Bootloader  
      • 自定义Bootloader:指在用户Flash最前面自行编写的一段小程序,用于在线升级(IAP),不需要物理切换BOOT引脚即可更新固件。它能通过串口、CAN、以太网等对新固件进行写入,然后再跳转到主应用。  
      • System Bootloader:由厂商内置,地位特殊,即使用户程序“崩溃”或被锁保护,也可通过它来低层次地擦除或恢复。这就是“最后的防线”。自定义Bootloader再怎么强大,也不可能拥有“擦除整个Flash并解锁”这类超高权限;只有System Bootloader才能做这些事。

9.2 最高级(Level 2)保护下的例外情形  
      有些MCU在最高等级保护下,连系统Bootloader也可能无法完成解锁操作,目的就是防止即使能接触到BOOT0依旧无法恢复或读取,以保证最高程度的安全。此时,如果开启这种保护,除非厂商预留了特殊的内部熔丝或极端手段,否则MCU就到达了真正的不可更改状态。

10.总结
      回顾本文,现在回答最初的那个问题,为什么切换BOOT引脚后MCU“上锁”依然能重新下载程序?简言之,核心原因在于:
        1) 用户程序中的上锁机制,在MCU启动时若从用户Flash入口进入,就会禁止外部调试器或访问;  
        2) 当我们切换BOOT,引导MCU进入系统Bootloader模式时,MCU会执行厂商固化的、高权限的小程序,该程序并不会理会用户程序的保护;  
        3) 系统Bootloader可以执行芯片擦除、解锁或写入新程序等操作,从而抹除原有上锁状态或重新写入新的固件;  
        4) 最终烧写的程序仍被写入到User Flash区域(如0x08000000),并未覆盖System Bootloader分区;  
        5) 重新上电并将BOOT恢复到0(或其他正常模式)后,MCU就从新的用户程序开始执行。

      因此,BOOT引脚实际上是给了工程师一个“后门”或“援救通道”,让MCU在陷入用户程序失效或读写保护时,也能被挽救或重新烧录。当然,这种通道即是一把双刃剑:它既方便普通开发场景下的维护和量产,也可能在安全层面为攻击者提供物理靠近后破坏或擦除用户程序的机会。厂商往往会根据等级不同,对System Bootloader能否解锁进行区分;若你确实需要极高安全,你也可以考虑将BOOT引脚物理封锁或启用最顶级的保护等级。
      总之,理解了BOOT引脚与System Bootloader的工作原理,便能洞悉“切换BOOT后就能重新下载程序”并不是“破除了锁”,而是“走了厂商留出的底层授权通道”,其中最典型的操作过程就是“Chip Erase”和“Option Byte解锁”之类的命令。只要完成了这些操作,MCU的Flash就被清空或恢复到开放状态,自然能够再次烧写用户应用。




使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

44

主题

80

帖子

7

粉丝