本帖最后由 DKENNY 于 2024-11-19 19:32 编辑
#技术资源# #申请原创# @21小跑堂
前言
在嵌入式系统开发中,APM32微控制器的应用日益广泛。为了确保程序高效运行,理解链接文件和启动文件的关系至关重要。链接文件(Linker Script)定义了程序的内存布局及各个段的起始地址和大小,而启动文件(Startup File)负责初始化系统环境,包括堆栈和全局变量的设置。这两者紧密配合,确保固件在微控制器上顺利执行。
在APM32微控制器的开发中,链接文件和启动文件是固件编写的关键组成部分。它们相辅相成,确保程序从上电到运行的每个环节都能顺利进行。接下来,我们将详细分析这两个文件的内容及其相互关联。
1. 链接文件分析
链接文件的主要功能是定义程序的内存布局,包括程序代码、数据、堆栈和其他重要段的起始地址和大小。如下是 startup_apm32f40xxG.ld源码。
/* Entry Point */
ENTRY(Reset_Handler)
/* Flash Configuration*/
/* Flash Base Address */
_rom_base = 0x8000000;
/*Flash Size (in Bytes) */
_rom_size = 0x0100000;
/* Embedded RAM Configuration */
/* RAM Base Address */
_ram_base = 0x20000000;
/* RAM Size (in Bytes) */
_ram_size = 0x00020000;
/* CCMRAM Base Address */
_ccmram_base = 0x10000000;
/* CCMRAM Size (in Bytes) */
_ccmram_size = 0x00010000;
/* Stack / Heap Configuration */
_end_stack = 0x20020000;
/* Heap Size (in Bytes) */
_heap_size = 0x200;
/* Stack Size (in Bytes) */
_stack_size = 0x400;
MEMORY
{
FLASH (rx) : ORIGIN = _rom_base, LENGTH = _rom_size
RAM (xrw) : ORIGIN = _ram_base, LENGTH = _ram_size
CCMRAM (xrw) : ORIGIN = _ccmram_base, LENGTH = _ccmram_size
}
SECTIONS
{
.apm32_isr_vector :
{
. = ALIGN(4);
KEEP(*(.apm32_isr_vector))
. = ALIGN(4);
} >FLASH
.text :
{
. = ALIGN(4);
*(.text)
*(.text*)
*(.glue_7)
*(.glue_7t)
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .;
} >FLASH
.rodata :
{
. = ALIGN(4);
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} >FLASH
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
.ARM : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
_start_address_init_data = LOADADDR(.data);
.data :
{
. = ALIGN(4);
_start_address_data = .;
*(.data)
*(.data*)
. = ALIGN(4);
_end_address_data = .;
} >RAM AT> FLASH
_siccmram = LOADADDR(.ccmram);
.ccmram :
{
. = ALIGN(4);
_sccmram = .;
*(.ccmram)
*(.ccmram*)
. = ALIGN(4);
_eccmram = .;
} >CCMRAM AT> FLASH
. = ALIGN(4);
.bss :
{
_start_address_bss = .;
__bss_start__ = _start_address_bss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_end_address_bss = .;
__bss_end__ = _end_address_bss;
} >RAM
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _heap_size;
. = . + _stack_size;
. = ALIGN(8);
} >RAM
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}
链接文件通过 MEMORY 指令定义了内存的使用情况,包括 FLASH、RAM 和 CCMRAM 的基本信息。例如:
MEMORY
{
FLASH (rx) : ORIGIN = _rom_base, LENGTH = _rom_size
RAM (xrw) : ORIGIN = _ram_base, LENGTH = _ram_size
CCMRAM (xrw) : ORIGIN = _ccmram_base, LENGTH = _ccmram_size
}
这里定义了各内存区域的属性:
- FLASH:
- rx:表示该区域可读并可执行,适合存储程序代码。
- ORIGIN:指定 FLASH 的起始地址(如 _rom_base)。
- LENGTH:指定 FLASH 的大小(如 _rom_size)。
- RAM:
- xrw:表示该区域可读、可写并可执行,适合存储变量和数据。
- ORIGIN:指定 RAM 的起始地址(如 _ram_base)。
- LENGTH:指定 RAM 的大小(如 _ram_size)。
- CCMRAM:
- 同样具有 xrw 属性,表示可读、可写并可执行。
- ORIGIN 和 LENGTH 属性分别指定 CCMRAM 的起始地址和大小。
通过这些定义,程序在内存中能够找到合适的位置来存储数据和代码,从而确保系统的正常运行和高效访问。
1.2 段的定义
接下来,链接文件通过 SECTIONS 指令定义了程序的各个段。这些段包括 .text、.data、.bss 等等,分别用于存储程序代码、已初始化的全局变量和未初始化的全局变量。例如:
.text :
{
. = ALIGN(4);
*(.text)
*(.text*)
*(.glue_7)
*(.glue_7t)
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .;
} > FLASH
在这个段定义中:
- .text 段:存储程序的所有可执行代码。
- ALIGN(4):确保段的起始地址是4字节对齐,以满足处理器的对齐要求。
- *(.text):将所有 .text 段的内容,包括各个翻译单元中的代码,合并到这个段中。
- KEEP (*(.init)) 和 KEEP (*(.fini)):确保初始化和清理函数在链接时不会被丢弃。
- _etext:用于标记 .text 段的结束地址,以便在程序中进行引用。
1.3 其他段的定义
除了 .text 段,链接文件还定义了其他多个重要段,例如:
.data 段:
.data :
{
. = ALIGN(4);
_start_address_data = .;
*(.data)
*(.data*)
. = ALIGN(4);
_end_address_data = .;
} > RAM AT> FLASH
存储已初始化的全局变量,程序运行时将这些数据从 Flash 复制到 RAM 中。
使用 AT> FLASH 指定数据在 Flash 中的存储位置。
.bss 段:
.bss :
{
_start_address_bss = .;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_end_address_bss = .;
} > RAM
存储未初始化的全局变量和静态变量,这些变量在程序启动时会被自动初始化为零。
.rodata 段:
.rodata :
{
. = ALIGN(4);
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} > FLASH
存储只读数据,通常是字符串常量和查找表。
.ccmram 段:
.ccmram :
{
. = ALIGN(4);
_sccmram = .;
*(.ccmram)
*(.ccmram*)
. = ALIGN(4);
_eccmram = .;
} > CCMRAM AT> FLASH
存储需要快速访问的变量,配置在 CCMRAM 中以提高访问速度。
1.4 堆栈的配置
链接文件还定义了堆和栈的大小和位置:
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _heap_size;
. = . + _stack_size;
. = ALIGN(8);
} > RAM
这部分定义了堆的起始位置,堆和栈的大小由 _heap_size 和 _stack_size 指定。PROVIDE 用于在其他部分引用堆和栈的结束地址。
1.5 丢弃不需要的部分
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
这一部分用于指定在链接时丢弃不需要的库文件,减小最终生成的可执行文件的大小。
1.6 结论
通过对链接文件的结构和各部分功能的分析,可以看出其在嵌入式系统中的重要性。链接文件通过对内存的合理配置和段的清晰定义,确保了程序在运行时的稳定性和效率。
2. 启动文件分析
/*!
* [url=home.php?mod=space&uid=288409]@file[/url] startup_apm32f40x.S
*
* [url=home.php?mod=space&uid=247401]@brief[/url] APM32F40xxx Devices vector table for GCC based toolchains.
* This module performs:
* - Set the initial SP
* - Set the initial PC == Reset_Handler,
* - Set the vector table entries with the exceptions ISR address
* - Branches to main in the C library (which eventually
* calls main()).
* After Reset the Cortex-M4 processor is in Thread mode,
* priority is Privileged, and the Stack is set to Main.
*
* [url=home.php?mod=space&uid=895143]@version[/url] V1.0.0
*
* [url=home.php?mod=space&uid=212281]@date[/url] 2023-03-02
*
* @attention
*
* Copyright (C) 2021-2023 Geehy Semiconductor
*
* You may not use this file except in compliance with the
* GEEHY COPYRIGHT NOTICE (GEEHY SOFTWARE PACKAGE LICENSE).
*
* The program is only for reference, which is distributed in the hope
* that it will be useful and instructional for customers to develop
* their software. Unless required by applicable law or agreed to in
* writing, the program is distributed on an "AS IS" BASIS, WITHOUT
* ANY WARRANTY OR CONDITIONS OF ANY KIND, either express or implied.
* See the GEEHY SOFTWARE PACKAGE LICENSE for the governing permissions
* and limitations under the License.
*/
.syntax unified
.cpu cortex-m4
.fpu softvfp
.thumb
.global g_apm32_Vectors
.global Default_Handler
/* start address for the initialization values of the .data section.
defined in linker script */
.word _start_address_init_data
/* start address for the .data section. defined in linker script */
.word _start_address_data
/* end address for the .data section. defined in linker script */
.word _end_address_data
/* start address for the .bss section. defined in linker script */
.word _start_address_bss
/* end address for the .bss section. defined in linker script */
.word _end_address_bss
/* stack used for SystemInit_ExtMemCtl; always internal RAM used */
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
// Reset handler routine
Reset_Handler:
ldr sp, =_end_stack
/* Copy the data segment initializers from flash to SRAM */
ldr r0, =_start_address_data
ldr r1, =_end_address_data
ldr r2, =_start_address_init_data
movs r3, #0
b L_loop0_0
L_loop0:
ldr r4, [r2, r3]
str r4, [r0, r3]
adds r3, r3, #4
L_loop0_0:
adds r4, r0, r3
cmp r4, r1
bcc L_loop0
ldr r2, =_start_address_bss
ldr r4, =_end_address_bss
movs r3, #0
b L_loop1
L_loop2:
str r3, [r2]
adds r2, r2, #4
L_loop1:
cmp r2, r4
bcc L_loop2
bl SystemInit
bl __libc_init_array
bl main
bx lr
.size Reset_Handler, .-Reset_Handler
// This is the code that gets called when the processor receives an unexpected interrupt.
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.size Default_Handler, .-Default_Handler
// The minimal vector table for a Cortex M4.
.section .apm32_isr_vector,"a",%progbits
.type g_apm32_Vectors, %object
.size g_apm32_Vectors, .-g_apm32_Vectors
// Vector Table Mapped to Address 0 at Reset
g_apm32_Vectors:
.word _end_stack // Top of Stack
.word Reset_Handler // Reset Handler
.word NMI_Handler // NMI Handler
.word HardFault_Handler // Hard Fault Handler
.word MemManage_Handler // MPU Fault Handler
.word BusFault_Handler // Bus Fault Handler
.word UsageFault_Handler // Usage Fault Handler
.word 0 // Reserved
.word 0 // Reserved
.word 0 // Reserved
.word 0 // Reserved
.word SVC_Handler // SVCall Handler
.word DebugMon_Handler // Debug Monitor Handler
.word 0 // Reserved
.word PendSV_Handler // PendSV Handler
.word SysTick_Handler // SysTick Handler
/* External Interrupts */
.word WWDT_IRQHandler // Window WatchDog
.word PVD_IRQHandler // PVD through EINT Line detection
.word TAMP_STAMP_IRQHandler // Tamper and TimeStamps through the EINT line
.word RTC_WKUP_IRQHandler // RTC Wakeup through the EINT line
........
.word Default_IRQHandler // Default exception/interrupt handler
.weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler
.weak HardFault_Handler
.thumb_set HardFault_Handler,Default_Handler
.weak MemManage_Handler
.thumb_set MemManage_Handler,Default_Handler
.weak BusFault_Handler
.thumb_set BusFault_Handler,Default_Handler
.weak UsageFault_Handler
.thumb_set UsageFault_Handler,Default_Handler
.weak SVC_Handler
.thumb_set SVC_Handler,Default_Handler
.weak DebugMon_Handler
.thumb_set DebugMon_Handler,Default_Handler
.weak PendSV_Handler
.thumb_set PendSV_Handler,Default_Handler
.weak SysTick_Handler
.thumb_set SysTick_Handler,Default_Handler
.weak WWDT_IRQHandler
.thumb_set WWDT_IRQHandler,Default_Handler
.weak PVD_IRQHandler
.thumb_set PVD_IRQHandler,Default_Handler
.weak TAMP_STAMP_IRQHandler
.thumb_set TAMP_STAMP_IRQHandler,Default_Handler
.weak RTC_WKUP_IRQHandler
.thumb_set RTC_WKUP_IRQHandler,Default_Handler
.....
启动文件负责初始化系统和处理器的状态,包括设置堆栈指针、数据段的拷贝和调用main()函数。
2.1 文件头和基本指令
.syntax unified
.cpu cortex-m4
.fpu softvfp
.thumb
- .syntax unified:指明使用统一语法,这在 ARM 汇编中是常用的语法形式。
- .cpu cortex-m4:指定目标处理器为 Cortex-M4。
- .fpu softvfp:指定所使用的浮点单元(FPU),这里表示使用软件浮点运算。
- .thumb:启用 Thumb 模式,Cortex-M4 支持 Thumb 和 ARM 两种指令集,但 Thumb 模式通常用于嵌入式开发。
2.2 全局符号
.global g_apm32_Vectors
.global Default_Handler
这些指令将 g_apm32_Vectors 和 Default_Handler 符号标记为全局,以便其他文件可以访问这些符号。
2.3 内存地址指定
.word _start_address_init_data
.word _start_address_data
.word _end_address_data
.word _start_address_bss
.word _end_address_bss
这些 .word 指令定义了与链接文件中定义的地址相关的符号。这些符号用于在程序中引用数据段(.data)和未初始化数据段(.bss)的起始和结束地址。
2.4 重置处理程序定义
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
ldr sp, =_end_stack
- .section .text.Reset_Handler:定义一个名为 .text.Reset_Handler 的代码段,所有代码将被放置在这个段中。
- .weak Reset_Handler:将 Reset_Handler 定义为弱链接符号,允许它在其他地方被重定义。
- .type Reset_Handler, %function:标记 Reset_Handler 为函数类型。
2.4.1 重置处理程序逻辑
/* Copy the data segment initializers from flash to SRAM */
ldr r0, =_start_address_data
ldr r1, =_end_address_data
ldr r2, =_start_address_init_data
movs r3, #0
b L_loop0_0
L_loop0:
ldr r4, [r2, r3]
str r4, [r0, r3]
adds r3, r3, #4
L_loop0_0:
adds r4, r0, r3
cmp r4, r1
bcc L_loop0
- 这部分代码负责将 Flash 中的初始化数据复制到 SRAM 中。
- 使用 Load 和 Store 指令将数据从一个地址复制到另一个地址,直到复制完成。
2.4.2 BSS 段初始化
ldr r2, =_start_address_bss
ldr r4, =_end_address_bss
movs r3, #0
b L_loop1
L_loop2:
str r3, [r2]
adds r2, r2, #4
L_loop1:
cmp r2, r4
bcc L_loop2
- 这段代码将未初始化的 BSS 段(全局变量和静态变量)初始化为零。
- 使用循环将值 0 写入 BSS 段的每个位置。
2.4.3 系统初始化和主程序调用
bl SystemInit
bl __libc_init_array
bl main
bx lr
- bl 指令用于调用其他函数,依次调用 SystemInit(用于系统初始化)、__libc_init_array(初始化 C 库)和 main 函数。
- bx lr 指令用于返回到调用者。
2.5 默认处理程序定义
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.size Default_Handler, .-Default_Handler
- .section .text.Default_Handler:定义默认处理程序的代码段。
- Default_Handler 是在发生未定义的中断时调用的处理程序,进入无限循环。
2.6 中断向量表
.section .apm32_isr_vector,"a",%progbits
.type g_apm32_Vectors, %object
.size g_apm32_Vectors, .-g_apm32_Vectors
g_apm32_Vectors:
.word _end_stack // Top of Stack
.word Reset_Handler // Reset Handler
.word NMI_Handler // NMI Handler
.word HardFault_Handler // Hard Fault Handler
.word MemManage_Handler // MPU Fault Handler
.word BusFault_Handler // Bus Fault Handler
.word UsageFault_Handler // Usage Fault Handler
.word 0 // Reserved
...
.word Default_Handler // Default exception/interrupt handler
- 这部分定义了中断向量表,包含了不同中断源对应的处理程序的地址。
- 向量表的第一个条目是堆栈的顶部地址,第二个是重置处理程序的地址,后面跟着其他中断处理程序的地址。
2.7 弱链接中断处理程序
.weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler
- 使用 .weak 指令声明多个中断处理程序为弱链接符号,允许用户在其他地方定义它们的实际实现。
- 如果没有提供自定义的实现,系统将使用 Default_Handler 作为中断处理程序。
2.8 结论
这个汇编文件是为 APM32F40xx 系列微控制器编写的启动代码,包含了设备的重置处理程序、默认中断处理程序和中断向量表。通过对不同内存段的初始化和中断处理的定义,文件确保了设备在上电或复位后的正常启动和运行。理解这些内容对于嵌入式系统的开发和调试至关重要。
3. 链接文件与启动文件的关联
3.1 整体文件编译
在编译和链接过程中,startup_apm32f40xxG.ld(链接脚本文件)和startup_apm32f40x.S(汇编源文件)的处理顺序如下。
3.1.1 编译顺序
- 汇编 (.S 文件):
- 首先,startup_apm32f40x.S 文件会被编译成目标文件 .o。在这个步骤中,汇编器将汇编语言转换为机器代码并生成目标文件。
- 在汇编过程中,处理器指令和数据段会按照汇编源文件中的定义生成相应的机器代码。
- 链接 (.ld 文件):
- 然后,链接器会使用 startup_apm32f40xxG.ld 文件来链接不同的目标文件。在这个步骤中,链接器根据链接脚本的定义将 .o 文件中的各个段合并,并分配它们在内存中的位置。
- 链接器会根据 .ld 文件中的 MEMORY 和 SECTIONS 指令安排各个段的位置,同时处理符号解析和重定位。
3.1.2 总体流程
- 步骤 1: 先编译汇编源文件 startup_apm32f40x.S,生成目标文件(如 startup_apm32f40x.o)。
- 步骤 2: 接着,链接器读取 startup_apm32f40xxG.ld 文件,链接 startup_apm32f40x.o 和其他相关的目标文件,生成最终的可执行文件。
3.1.3 重要性
汇编文件中的 Reset_Handler 函数会作为程序的入口点,而链接脚本文件则定义了程序的内存布局,确保代码和数据在运行时的位置正确。
因此,首先编译汇编文件是重要的,因为链接的过程依赖于汇编后的目标文件内容。
3.1.4 总结
整体编译顺序是先汇编 .S 文件生成目标文件,然后使用 .ld 文件进行链接。这种顺序确保了程序在内存中的各个段能够按照预定的布局正确运行。
3.2 文件段编译
在 startup_apm32f40xxG.ld(链接脚本)和 startup_apm32f40x.S(汇编源文件)中,各个段的编译顺序如下。
3.2.1 链接脚本 (startup_apm32f40xxG.ld) 中的段顺序
在链接脚本中,段的定义顺序决定了它们在生成的可执行文件中的顺序和在内存中的分布。以下是主要段的顺序:
- .apm32_isr_vector: 存放中断向量表,优先加载,以确保在复位时可以找到正确的中断服务程序。
- .text: 存放程序的执行代码,包括Reset_Handler和其他初始化代码。
- .rodata: 存放只读数据,如字符串常量等。
- .ARM.extab: 存放异常处理相关数据。
- .preinit_array: 存放在程序开始运行前需要调用的初始化函数的地址。
- .init_array: 存放在main函数之前需要调用的初始化函数。
- .fini_array: 存放在程序结束后需要调用的清理函数的地址。
- .data: 存放初始化后的全局变量,从FLASH复制到RAM。
- .ccmram: 存放特定数据或代码,位于CCMRAM区域。
- .bss: 存放未初始化的全局变量,分配在RAM中。
- ._user_heap_stack: 定义堆和堆栈的大小和位置。
3.2.2 汇编源文件 (startup_apm32f40x.S) 中的段顺序
在汇编文件中,虽然汇编器会将各个指令和数据段放置在不同的段中,但它们的顺序通常与链接脚本一致,主要包括:
- .section .text.Reset_Handler
- 定义了Reset_Handler函数,作为程序的入口点。
- .section .text.Default_Handler:
- 定义了默认的中断处理程序。
- .section .apm32_isr_vector:
- 定义了中断向量表,包括各个中断处理程序的地址。
- 数据段的初始化:
- 在Reset_Handler中,包含了从FLASH复制.data段初始化数据到RAM的代码,以及清零.bss段的代码。
3.2.3 总结
- 在链接过程中,首先处理中断向量表以确保系统重置时能正确跳转到Reset_Handler。
- 然后是程序主代码段.text,接着是只读数据段.rodata,再到初始化数据段.data。
- 最后处理未初始化数据段.bss和动态分配的堆栈区域。
这种顺序确保了系统启动时能够按照预定义的流程和内存布局正常运行。
其链接关系可见如下图。
4. 结论
通过对APM32的链接文件和启动文件的分析,我们可以看到它们在嵌入式系统开发中的重要性和相互依赖性。链接文件负责定义内存布局,明确各个段(如代码段、数据段、未初始化段等)在内存中的位置和大小,从而确保系统能够正确地执行程序。它确保了在将程序加载到设备内存时,各个部分能够按照预期的顺序和位置进行分布。
启动文件则负责在设备上电时进行必要的初始化,包括设置堆栈指针、清零未初始化的数据段以及调用用户的初始化函数。启动文件通常包含复位处理程序(Reset_Handler)和默认的中断处理程序,确保系统在复位后能够顺利启动,并在中断发生时有正确的处理机制。
这两个文件的相互依赖性体现在,链接文件所定义的内存布局必须与启动文件中的初始化逻辑相匹配。例如,启动文件中的代码需要按照链接文件定义的地址来执行,从而确保系统能够正确运行。此外,链接文件中的 .isr_vector 段需要指向启动文件中的复位处理程序,以确保在复位时能够正确跳转。
总结来说,链接文件和启动文件在嵌入式系统的开发中是密不可分的。它们共同确保了系统的正常启动和运行,提供了基础的环境支持,使得开发者能够更方便地进行后续的应用开发。
|
一篇文章,带你详解单片机开发中的启动文件和链接文件,让我们从底层了解单片机。文章段落清晰,描述详细,值得一看。