十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32F411链接脚本详解:从复位向量到main的硬件级启动流程

STM32F411链接脚本详解:从复位向量到main的硬件级启动流程 1. 为什么 STM32F411 的链接脚本不能照抄 STM32F103——从复位向量到 main() 的真实路径你手头有一块 STM32F411RE-Nucleo 开发板烧录了官方 HAL 库的 LED 闪烁例程一切正常但当你尝试用裸机方式不调用 HAL_Init、不启用 SysTick、不配置 RCC写一个最简程序时LED 却纹丝不动。调试器停在 0x08000000单步执行几条指令后就跳进一片空白内存最终卡死。你反复检查启动文件 startup_stm32f411xe.s确认 Reset_Handler 地址已正确填入向量表首项你也确认编译器用了 -mcpucortex-m4 -mfloat-abihard -mfpufpv4和芯片手册完全匹配。问题出在哪不是启动代码不是编译选项而是你根本没给它“画一张地图”——一张告诉 GNU Linker哪里放代码、哪里放数据、哪里放栈、哪里放堆、哪里是真正的程序入口的地图。这张地图就是链接脚本Linker Script而它的核心使命是从硬件复位那一刻起把 CPU 引导到 C 语言世界的起点main()函数。STM32F411 和 F103 看似同属 Cortex-M 系列但它们的存储器映射Memory Map有本质差异。F103 的 Flash 起始地址是 0x08000000SRAM 是 0x20000000而 F411 的 Flash 起始地址同样是 0x08000000但其 SRAM 分为两块主 SRAM128KB位于 0x20000000而 CCM RAM64KB则位于 0x10000000。更重要的是F411 的向量表偏移寄存器VTOR默认指向 0x08000000但如果你把向量表放在别处比如为了 OTA 升级预留空间就必须在 Reset_Handler 中手动设置 VTOR。这些细节绝不是MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 512K }一行就能概括的。一个为 F411 写的链接脚本必须精确反映其 128KB 主 SRAM 的布局、CCM RAM 的可用性、以及 Flash 中用于存放中断向量表、代码段、只读数据段的严格分区。否则即使你的main()函数逻辑完美无缺它也永远无法被 CPU 找到并执行——因为链接器已经把.text段塞进了错误的地址区间或者把.data初始化值写到了一块未使能的内存区域上。我第一次遇到这个问题时花了整整两天时间用逻辑分析仪抓取复位信号波形确认硬件没问题又用 J-Link Commander 读取 Flash 前 128 字节发现向量表里的 Reset_Handler 地址确实是 0x08000101即第一条指令地址但跳转过去后CPU 却在执行一堆 0xFF 指令。最后才发现链接脚本里把.text的起始地址错设成了 0x08000100导致编译器生成的机器码被写到了向量表之后的“空洞”里而 Reset_Handler 实际上被覆盖掉了。这根本不是代码 bug而是地图画错了。提示STM32F411 的复位流程是上电/复位引脚拉低 → 内部复位电路释放 → CPU 从 0x00000000通过向量重映射实际访问 Flash 起始读取 MSP 初始值 → 读取 Reset_Handler 入口地址 → 跳转执行。这个过程里没有任何 C 运行时CRT参与全靠硬件和链接脚本协同完成。所以链接脚本的第一行ENTRY(Reset_Handler)不是可选的而是强制要求——它告诉链接器整个程序的“法定入口”是 Reset_Handler而不是main()。main()只是 C 世界的一个普通函数它的地址必须由 Reset_Handler 显式调用才能抵达。2. 解剖startup_stm32f411xe.sReset_Handler 如何成为通往 main() 的唯一桥梁很多人以为只要startup_stm32f411xe.s文件存在Reset_Handler 就会自动运行。这是个危险的误解。.s文件只是汇编源码它本身不会执行它必须被编译成目标文件.o再由链接器根据链接脚本的指示将其放置在正确的内存位置并确保其符号Reset_Handler被正确解析为向量表的第一项。因此理解 Reset_Handler 的内部逻辑是编写链接脚本的前提。我们来逐行拆解标准启动文件中 Reset_Handler 的关键部分Reset_Handler: /* 1. 初始化主堆栈指针 MSP */ ldr r0, _estack msr msp, r0 /* 2. 调用 SystemInit —— 这是 CMSIS 定义的芯片初始化函数 */ ldr r0, SystemInit blx r0 /* 3. 调用 __main —— 这是 ARM C 库的初始化入口不是你的 main() */ ldr r0, __main bx r0这里的关键在于第三步blx r0调用的是__main而不是main。__main是 ARM 编译器ARMCC 或 GNU Arm Embedded Toolchain提供的一个库函数它的职责是执行 C 运行时环境的初始化包括将 Flash 中的.data段已初始化的全局/静态变量复制到 RAM 中的对应位置将.bss段未初始化的全局/静态变量清零设置堆heap和栈stack的边界最后才跳转到用户定义的main()函数。这意味着你的main()函数是整个初始化链条的终点而非起点。而链接脚本正是为__main的这些操作提供“施工图纸”。例如.data段在 Flash 中的存放位置LOADADDR(.data)和在 RAM 中的目标位置ADDR(.data)必须由链接脚本明确定义.bss段的起始地址和长度也必须精确计算以便__main知道要清零哪一片内存。如果链接脚本中.data的LOADADDR错误__main就会从错误的 Flash 地址读取初始值导致全局变量被赋予随机垃圾数据如果.bss的长度算少了__main就不会清零所有未初始化变量留下不可预测的隐患。我曾在一个项目中为了节省 Flash 空间将.data段的LOADADDR设置为紧贴.text段之后但忘了调整.text段的ALIGN(4)属性。结果.text段末尾不是 4 字节对齐导致.data的LOADADDR落在一个奇数地址上。当__main执行memcpy时由于 ARM Cortex-M4 的 Thumb-2 指令集要求数据访问必须字对齐CPU 触发了 HardFault 异常程序直接崩溃。调试时HardFault_Handler 里看到的PC寄存器值指向__main的内部 memcpy 循环而LR寄存器则显示调用来源是 Reset_Handler。这个坑根源不在 C 代码而在链接脚本里一个小小的对齐疏忽。2.1_estack和_Min_Stack_Size栈空间的生死线Reset_Handler的第一行ldr r0, _estack加载的是栈顶地址。这个_estack符号必须由链接脚本定义。常见的错误写法是_estack ORIGIN(RAM) LENGTH(RAM);这看起来很合理栈从 RAM 顶端向下生长。但问题在于RAM 的LENGTH是总大小而_estack必须是一个绝对地址。更致命的是如果你的 RAM 区域包含多个不连续的块如 F411 的主 SRAM 和 CCM RAM这种写法就会失效。正确的做法是在链接脚本的SECTIONS中为每个内存区域单独定义栈顶/* 主 SRAM 栈顶 */ _estack_main ORIGIN(RAM_MAIN) LENGTH(RAM_MAIN); /* CCM RAM 栈顶如果要用作栈 */ _estack_ccm ORIGIN(RAM_CCM) LENGTH(RAM_CCM);然后在启动文件中通过#ifdef宏选择使用哪一个。此外_Min_Stack_Size这个符号通常在startup_stm32f411xe.s中被定义为.equ _Min_Stack_Size, 0x4001KB。这个值是最低保障但绝非安全值。在实际项目中如果你启用了 FreeRTOS每个任务都有自己的栈那么主栈MSP只需处理中断和启动过程但如果你用的是裸机且中断服务程序ISR很复杂比如在 ADC DMA 回调里做了大量浮点运算1KB 栈很快就会溢出。栈溢出的后果不是报错而是悄无声息地覆盖相邻的.data或.bss数据导致变量值诡异变化极难定位。我的经验是在开发阶段将_Min_Stack_Size设为 0x10004KB并在调试器里观察SP寄存器的最低水位Low Water Mark再据此裁剪。一个可靠的技巧是在main()开头插入一段代码将栈底_estack - _Min_Stack_Size附近的一片内存全部写入 0xAA然后在程序运行一段时间后用调试器查看这片内存被覆盖了多少。被覆盖的深度就是你实际需要的最小栈空间。2.2SystemInit的隐含依赖RCC 配置与链接脚本的协同Reset_Handler在调用__main之前先调用了SystemInit。这个函数由system_stm32f4xx.c提供其核心是配置 RCC复位和时钟控制寄存器将系统时钟SYSCLK从默认的 16MHz HSI 切换到更高频率如 100MHz 的 PLL。但这里有一个关键前提SystemInit的代码必须被正确地链接到 Flash 中并且其调用的RCC-CR,RCC-PLLCFGR等寄存器地址必须在编译时被解析为正确的物理地址。这依赖于链接脚本中.text段的ORIGIN和LENGTH设置。如果.text段被错误地链接到了一个未使能的内存区域比如你误将ORIGIN(FLASH)设为 0x08020000而你的 Bootloader 占据了前 128KB那么SystemInit的指令就会被加载到错误的位置CPU 执行的将是乱码必然触发 HardFault。更隐蔽的问题是SystemInit会修改SCB-VTOR寄存器将向量表基址重映射到新的位置例如为了支持 IAP 升级把向量表放到 RAM 中。这时链接脚本就必须为 RAM 中的向量表预留空间并确保__Vectors符号被正确定义。否则即使SystemInit成功执行后续的中断如 SysTick也无法被正确响应因为 CPU 还在旧的向量表地址上查找 ISR 地址。3. 一份为 STM32F411 量身定制的链接脚本从 MEMORY 到 SECTIONS 的完整实现现在我们来构建一份真正适配 STM32F411RE 的链接脚本。它不是网上随处可见的通用模板而是基于芯片手册RM0383第 3.3.1 节“Memory map”和第 3.4 节“Embedded Flash memory”的精确描述。我们将分步实现每一步都解释其背后的硬件依据。3.1 MEMORY 区域定义精确到字节的物理地址规划STM32F411RE 的存储器资源如下来自数据手册 DS10792Flash: 512 KB地址范围 0x08000000 – 0x0807FFFFMain SRAM: 128 KB地址范围 0x20000000 – 0x2001FFFFCCM RAM: 64 KB地址范围 0x10000000 – 0x1000FFFF注意CCM RAM 是紧耦合内存Closely Coupled Memory专为 CPU 访问优化不支持 DMA但访问速度极快。它常被用作关键 ISR 的栈或存放高频访问的变量。因此我们的链接脚本必须为它单独建模/* 定义三个独立的内存区域 */ MEMORY { /* Flash 用于存放代码和常量 */ FLASH (rx) : ORIGIN 0x08000000, LENGTH 512K /* 主 SRAM 用于存放 .data, .bss, heap, stack */ RAM_MAIN (rwx) : ORIGIN 0x20000000, LENGTH 128K /* CCM RAM 用于高速数据不支持 DMA */ RAM_CCM (rwx) : ORIGIN 0x10000000, LENGTH 64K }这里的关键是LENGTH 128K而不是128*1024。虽然两者等价但128K是 GNU ld 的标准单位更清晰。rx表示该区域可读、可执行rwx表示可读、可写、可执行因为我们要在 RAM 中运行代码比如自定义的 bootloader。ORIGIN必须是十六进制且与芯片手册完全一致。任何偏差都会导致链接失败或运行时错误。3.2 SECTIONS 段布局让代码、数据、栈各归其位SECTIONS是链接脚本的核心它定义了各个段section在内存中的具体位置和顺序。我们按执行流程来组织SECTIONS { /* 1. 向量表必须放在 Flash 的最开始占据前 256 字节64 个向量 * 4 字节 */ .isr_vector : { . ALIGN(4); __vector_table_start .; KEEP(*(.isr_vector)) /* 保留所有 .isr_vector 段通常是 startup 文件里的 */ __vector_table_end .; } FLASH /* 2. 代码段 .text紧跟向量表之后 */ .text : { . ALIGN(4); __text_start .; *(.text) /* 所有 .text 段 */ *(.text*) /* 所有以 .text 开头的段如 .text.startup */ *(.rodata) /* 只读数据如字符串常量、const 变量 */ *(.rodata*) . ALIGN(4); __text_end .; } FLASH /* 3. 初始化数据段 .data存放在 Flash 中但需复制到 RAM */ .data : AT (ADDR(.text) SIZEOF(.text)) { . ALIGN(4); __data_start_flash .; /* Flash 中 .data 的起始地址 */ *(.data) /* 所有 .data 段 */ *(.data*) . ALIGN(4); __data_end_flash .; __data_size __data_end_flash - __data_start_flash; } RAM_MAIN /* 4. 未初始化数据段 .bss只存在于 RAM 中需清零 */ .bss : { . ALIGN(4); __bss_start .; *(.bss) *(.bss*) *(COMMON) . ALIGN(4); __bss_end .; } RAM_MAIN /* 5. 堆和栈放在 RAM_MAIN 的末端 */ . ALIGN(4); __heap_start .; .heap : { __heap_begin__ .; . . _Heap_Size; __heap_end__ .; } RAM_MAIN __stack_top ORIGIN(RAM_MAIN) LENGTH(RAM_MAIN); __stack_bottom __heap_end__; __stack_size __stack_top - __stack_bottom; /* 6. CCM RAM 段用于高速数据 */ .ccm_data (NOLOAD) : { . ALIGN(4); __ccm_start .; *(.ccm_data) *(.ccm_data*) . ALIGN(4); __ccm_end .; } RAM_CCM }这份脚本的精妙之处在于.data段的AT (...)属性明确指定了它在 Flash 中的加载地址LOADADDR即紧接在.text段之后。这确保了__main能准确找到.data的初始值。__data_size的计算为__main的memcpy提供了精确的字节数。堆.heap和栈__stack_top/__stack_bottom被显式地定义在 RAM_MAIN 的末端避免了与.bss段的潜在冲突。NOLOAD属性用于.ccm_data表示该段在加载时不占用 Flash 空间只在运行时存在于 RAM_CCM 中符合其物理特性。注意_Heap_Size是一个链接时定义的符号通常在启动文件或 C 代码中通过#define _Heap_Size 0x200来设置。如果你不需要动态内存分配可以将其设为 0。3.3 符号定义与入口点让链接器和启动代码无缝对接最后我们必须定义所有启动代码依赖的关键符号并指定程序入口/* 定义所有启动文件需要的符号 */ PROVIDE(_estack ORIGIN(RAM_MAIN) LENGTH(RAM_MAIN)); PROVIDE(_Min_Stack_Size 0x400); /* 定义堆的大小可由用户在 C 代码中覆盖 */ PROVIDE(_Heap_Size 0x200); /* 指定程序的唯一入口点 */ ENTRY(Reset_Handler) /* 生成一个符号方便在 C 代码中获取 Flash 大小 */ PROVIDE(__flash_size LENGTH(FLASH));PROVIDE是 GNU ld 的关键字它定义了一个符号但如果该符号已在目标文件中定义则以目标文件中的定义为准避免了链接冲突。ENTRY(Reset_Handler)是强制性的它告诉链接器无论main()在哪里程序的“法律意义上的起点”都是Reset_Handler。没有这一行链接器会默认使用_start而你的程序将无法启动。4. 实战验证如何用 GDB 和 objdump 确认你的链接脚本是否生效写完链接脚本绝不意味着万事大吉。你必须用工具进行验证确保每一个符号、每一个段都落在了预期的地址上。这是嵌入式开发中“所见即所得”的关键一步。4.1 使用arm-none-eabi-objdump查看段布局编译完成后运行以下命令arm-none-eabi-objdump -h your_project.elf输出会列出所有段及其地址、大小和属性。你应该看到类似这样的结果Sections: Idx Name Size VMA LMA File off Algn 0 .isr_vector 00000100 08000000 08000000 00010000 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .text 00001a2c 08000100 08000100 00010100 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 2 .data 00000020 20000000 08001b2c 00011b2c 2**2 CONTENTS, ALLOC, LOAD, DATA 3 .bss 00000040 20000020 20000020 00011b4c 2**2 CONTENTS, ALLOC, ZERO, DATA关键观察点.isr_vector的VMAVirtual Memory Address和LMALoad Memory Address都是08000000说明它被正确地放在了 Flash 起始。.text的VMA和LMA都是08000100即向量表256 字节之后符合预期。.data的VMA是20000000RAM_MAIN 起始而LMA是08001b2cFlash 中紧随.text之后这正是AT (...)属性的效果。.bss的VMA是20000020即.data在 RAM 中结束后的下一个地址且LMA为 0因为它不需要被加载只需清零。如果.data的LMA和VMA相同那说明你漏掉了AT (...)这是一个严重错误。4.2 使用arm-none-eabi-nm查看符号地址运行arm-none-eabi-nm --print-size --radixd your_project.elf | grep -E (Reset_Handler|main|_estack|__data_start_flash)你会得到符号的十进制地址和大小0000000008000101 T Reset_Handler 00000000080008a5 T main 0000000020020000 A _estack 0000000008001b2c A __data_start_flash这直接验证了Reset_Handler的地址是08000101向量表第二项因为第一项是 MSP正确。main的地址在 Flash 中是080008a5说明它已被编译进.text段。_estack是20020000即0x20000000 0x20000128KB正确。__data_start_flash是08001b2c与objdump中.data的LMA一致。4.3 使用 GDB 进行动态验证连接调试器后在 GDB 中执行(gdb) info symbol _estack _estack in section .data of your_project.elf (gdb) x/4xw 0x08000000 0x8000000: 0x20020000 0x08000101 0x08000115 0x08000129第一行x/4xw 0x08000000读取 Flash 起始的 4 个字16 字节即向量表的前 4 项MSP 初始值、Reset_Handler 地址、NMI_Handler 地址、HardFault_Handler 地址。如果0x08000000处的值是0x20020000那就完美印证了_estack符号被正确写入了向量表的第一项。提示在 GDB 中你可以用monitor reset halt命令复位芯片并暂停然后用stepi单步执行Reset_Handler的每一条指令亲眼看着MSP被加载SystemInit被调用最后__main被跳转。这是最直观、最可靠的验证方式。5. 常见陷阱与避坑指南那些让你调试到凌晨三点的“幽灵 Bug”即使你严格按照手册写了链接脚本依然可能掉进一些深不见底的坑里。这些坑往往不报错不崩溃只是让程序行为变得诡异消耗你大量的时间和耐心。以下是我在 STM32F411 项目中踩过的、最具代表性的几个。5.1 “编译器未包含 main 类型”符号名修饰Name Mangling的陷阱当你在 C 项目中使用extern C声明main函数时链接器却报错undefined reference to main或者更诡异的undefined reference to _main。这是因为 C 编译器会对函数名进行修饰以支持函数重载。main函数在 C 中被修饰为_main或main取决于编译器和 ABI。解决方案是在链接脚本中不要硬编码main而是使用PROVIDE定义一个弱符号PROVIDE(main main); PROVIDE(_main main);但这治标不治本。最好的实践是在裸机项目中永远使用纯 C 语言编写main.c并确保其main函数声明为int main(void)。C 的优势在于面向对象和 STL但在资源极度受限的 MCU 上这些优势往往被其带来的复杂性和不确定性所抵消。5.2 “异步复位同步撤离”硬件复位电路与软件初始化的时序鸿沟这是一个硬件与软件交界处的经典问题。STM32F411 的复位引脚是异步的即复位信号可以在任何时钟周期到来。但芯片内部的复位逻辑需要经过一个同步器Synchronizer来滤除毛刺确保复位信号稳定。这个过程需要几个时钟周期。如果你的外部复位电路如 RC 电路时间常数太小复位脉冲过窄就可能被同步器滤掉导致芯片无法可靠复位。反之如果时间常数太大复位脉冲过长SystemInit中的RCC-CR寄存器配置可能会因等待 PLL 锁定而超时导致时钟初始化失败。我的经验是对于 F411推荐的复位电路参数是 R10kΩ, C100nF时间常数约 1ms既能保证可靠复位又不会拖慢启动速度。在链接脚本层面这提醒我们SystemInit的执行时间必须小于硬件复位信号的持续时间否则你的软件初始化就建立在一个不稳定的硬件基础上。5.3 “YModem 固件升级”场景下的链接脚本重构YModem 协议常用于通过串口进行固件升级。它要求新固件被写入 Flash 的一个特定区域如 0x08010000而旧固件Bootloader则驻留在 0x08000000 – 0x0800FFFF。这时你的应用固件的链接脚本必须彻底重构MEMORY { /* Bootloader 占用前 64KB */ FLASH_BOOT (rx) : ORIGIN 0x08000000, LENGTH 64K /* 应用固件从 64KB 后开始 */ FLASH_APP (rx) : ORIGIN 0x08010000, LENGTH 448K } SECTIONS { .isr_vector : { ... } FLASH_APP .text : { ... } FLASH_APP /* .data 和 .bss 仍放在 RAM_MAIN */ }同时SystemInit中必须调用HAL_FLASHEx_OBProgram()来配置 Flash 的 Option Bytes允许从0x08010000开始执行。否则即使链接脚本正确CPU 也会因 Flash 保护而无法读取代码。这个场景下链接脚本不再是孤立的文件而是整个固件升级方案中承上启下的关键一环。5.4 “单环”与“急停程序”实时性要求对链接脚本的反向约束在电机控制等实时应用中“单环”Single Loop指的是一个固定的、高优先级的控制循环它必须在严格的时间窗口内完成。而“急停程序”则要求在任何时刻都能被最高优先级的中断如 EXTI打断并立即执行。这要求急停相关的 ISR 代码必须被链接到 Flash 中的低延迟区域靠近向量表以减少取指时间关键的控制变量如 PID 参数必须被__attribute__((section(.ccm_data)))放置在 CCM RAM 中以获得最快的访问速度。因此你的链接脚本必须为.ccm_data段预留足够空间并在 C 代码中显式地使用section属性/* 放在 CCM RAM 中确保最快访问 */ __attribute__((section(.ccm_data))) static float kp 1.0f; __attribute__((section(.ccm_data))) static float ki 0.1f;如果不这样做这些变量会被默认放在主 SRAM 中访问延迟增加可能导致控制环路抖动甚至失控。这再次证明链接脚本不是编译的终点而是性能优化的起点。6. 从main()到main()一个完整的、可运行的裸机工程骨架现在让我们把所有这些知识整合成一个最小但功能完整的 STM32F411 裸机工程。它不依赖任何 HAL 或 CMSIS 库只使用标准的stdint.h和core_cm4.h。6.1main.c最简的 C 世界入口#include stdint.h #include stm32f411xe.h // 仅包含寄存器定义无函数实现 // 声明外部符号由链接脚本提供 extern uint32_t _estack; extern uint32_t __data_start_flash; extern uint32_t __data_start; extern uint32_t __data_end; extern uint32_t __bss_start; extern uint32_t __bss_end; // 时钟配置将 SYSCLK 设为 100MHz void SystemInit(void) { // 1. 使能 HSE RCC-CR | RCC_CR_HSEON; while (!(RCC-CR RCC_CR_HSERDY)); // 2. 配置 PLL: HSE/2 * 100 100MHz RCC-PLLCFGR (RCC_PLLCFGR_PLLSRC_HSE) | (100 6) | (2 0); // 3. 使能 PLL RCC-CR | RCC_CR_PLLON; while (!(RCC-CR RCC_CR_PLLRDY)); // 4. 切换 SYSCLK 到 PLL RCC-CFGR ~RCC_CFGR_SW; RCC-CFGR | RCC_CFGR_SW_PLL; while ((RCC-CFGR RCC_CFGR_SWS) ! RCC_CFGR_SWS_PLL); } // 主函数点亮 LED int main(void) { // 1. 初始化 GPIOA (LED 连接 PA5) RCC-AHB1ENR | RCC_AHB1ENR_GPIOAEN; GPIOA-MODER | GPIO_MODER_MODER5_0; // PA5 输出模式 GPIOA-OTYPER ~GPIO_OTYPER_OT_5; // 推挽输出 GPIOA-OSPEEDR | GPIO_OSPEEDER_OSPEEDR5; // 高速 // 2. 主循环 while (1) { GPIOA-ODR ^ GPIO_ODR_ODR_5; // 翻转 PA5 for (volatile int i 0; i 1000000; i); // 简单延时 } }6.2startup_stm32f411xe.s精简版启动代码.syntax unified .cpu cortex-m4 .fpu softvfp .thumb .global g_pfnVectors .global Default_Handler .global Reset_Handler .global SystemInit .global __main /* 向量表 */ .section .isr_vector,a,%progbits g_pfnVectors: .word _estack .word Reset_Handler .word NMI_Handler .word HardFault_Handler /* ... 其他向量此处省略 ... */ /* 默认中断处理程序 */ .section .text.Default_Handler,
返回列表