IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 解析cortex-m3启动脚本 -> 正文阅读

[嵌入式]解析cortex-m3启动脚本


前言

通过上一个博客,我们已经非常舒适的读完cortex-m0的启动代码,本节继续解读keil自带的cortex-m3启动代码,找找你的这个文件吧:
G:\Keil_v5\ARM\PACK\ARM\CMSIS\5.3.0\Device\ARM\ARMCM3
G:\Keil_v5\ARM\PACK\ARM\CMSIS\5.3.0\CMSIS\Include

ARMCM3/
├── Include
│   ├── ARMCM3.h
│   └── system_ARMCM3.h
└── Source
    ├── ARM
    │   └── startup_ARMCM3.s
    ├── GCC
    │   ├── gcc_arm.ld
    │   ├── startup_ARMCM3.c
    │   └── startup_ARMCM3.S
    ├── IAR
    │   └── startup_ARMCM3.s
    └── system_ARMCM3.c

5 directories, 8 files

我们关注的是:

  1. startup_ARMCM3.S 启动脚本GCC汇编版本
  2. gcc_arm.ld链接脚本
  3. system_ARMCM3.c系统文件
  4. startup_ARMCM3.c启动脚本C语言部分

这次我们就不直接列出源文件了,而是一行不落的逐个分析,不然翻页困难。


1. Keil M3启动脚本

1.1 编译它

$ arm-none-eabi-gcc -O -c -g -Wall -mthumb -o  startup_ARMCM3_as.o  startup_ARMCM3.s
$ readelf.exe -h startup_ARMCM3_as.o
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          7392 (bytes into file)
  Flags:                             0x5000000, Version5 EABI
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         21
  Section header string table index: 20
$ readelf.exe -S startup_ARMCM3_as.o
There are 21 section headers, starting at offset 0x1ce0:
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 00008c 00  AX  0   0  4
  [ 2] .rel.text         REL             00000000 001a80 000058 08   I 18   1  4
  [ 3] .data             PROGBITS        00000000 0000c0 000000 00  WA  0   0  1
  [ 4] .bss              NOBITS          00000000 0000c0 000000 00  WA  0   0  1
  [ 5] .stack            PROGBITS        00000000 0000c0 000400 00      0   0  8
  [ 6] .heap             PROGBITS        00000000 0004c0 000c00 00      0   0  8
  [ 7] .vectors          PROGBITS        00000000 0010c0 0000c0 00      0   0  4
  [ 8] .rel.vectors      REL             00000000 001ad8 000110 08   I 18   7  4
  [ 9] .debug_line       PROGBITS        00000000 001180 000087 00      0   0  1
  [10] .rel.debug_line   REL             00000000 001be8 000008 08   I 18   9  4
  [11] .debug_info       PROGBITS        00000000 001207 000026 00      0   0  1
  [12] .rel.debug_info   REL             00000000 001bf0 000038 08   I 18  11  4
  [13] .debug_abbrev     PROGBITS        00000000 00122d 000014 00      0   0  1
  [14] .debug_aranges    PROGBITS        00000000 001248 000020 00      0   0  8
  [15] .rel.debug_arange REL             00000000 001c28 000010 08   I 18  14  4
  [16] .debug_str        PROGBITS        00000000 001268 000032 01  MS  0   0  1
  [17] .ARM.attributes   ARM_ATTRIBUTES  00000000 00129a 00001b 00      0   0  1
  [18] .symtab           SYMTAB          00000000 0012b8 0004a0 10     19  22  4
  [19] .strtab           STRTAB          00000000 001758 000326 00      0   0  1
  [20] .shstrtab         STRTAB          00000000 001c38 0000a6 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  y (purecode), p (processor specific)

1.2 语法规则和架构

Cortex-M3是第一个Cortex处理器,其架构称为ARMv7-M。

	.syntax	unified
	.arch	armv7-m

在这里插入图片描述

1.3 栈、堆

定义了一个名为“.stack”的段,首地址8字节对齐,缺省占用空间0x400,链接器可以看到指定符号__StackTop__StackLimit

定义了一个名为“.heap”的段,首地址8字节对齐,缺省占用空间0xc00,链接器可以看到指定符号__HeapBase__HeapLimit

其中#ifdef-else-endif是GNU的编码标准,跟C语言中的用法一样,不是汇编器独有的语法。

.if.endif这种以点号.打头的是汇编伪指令,意思也很直白。

	.section .stack
	.align	3
#ifdef __STACK_SIZE
	.equ	Stack_Size, __STACK_SIZE
#else
	.equ	Stack_Size, 0x00000400
#endif
	.globl	__StackTop
	.globl	__StackLimit
__StackLimit:
	.space	Stack_Size
	.size	__StackLimit, . - __StackLimit
__StackTop:
	.size	__StackTop, . - __StackTop

	.section .heap
	.align	3
#ifdef __HEAP_SIZE
	.equ	Heap_Size, __HEAP_SIZE
#else
	.equ	Heap_Size, 0x00000C00
#endif
	.globl	__HeapBase
	.globl	__HeapLimit
__HeapBase:
	.if	Heap_Size
	.space	Heap_Size
	.endif
	.size	__HeapBase, . - __HeapBase
__HeapLimit:
	.size	__HeapLimit, . - __HeapLimit

1.4 IVT

首先定义一个名为.vectors的段,对齐方式为4字节,定义了一个链接器可见的全局变量__Vectors。
.long汇编伪指令同.int,的语法为.long expressions,插入一个4字节的数据。

可见,链接脚本中将一系列数值塞入向量表,把__StackTop塞入IVT的第一个位置,把Reset_Handler塞入IVT的第二个位置,随后是14个异常向量、32个中断向量。

从readelf -S的结果可以看到,目标文件中的确有一个叫做“.vectors"的段,而且size是0x00c0=192字节,正好是塞进去的48个4字节数据。

	.section .vectors
	.align	2
	.globl	__Vectors
__Vectors:
	.long	__StackTop            /* Top of Stack */
	.long	Reset_Handler         /* Reset Handler */
	.long	NMI_Handler           /* NMI Handler */
	.long	HardFault_Handler     /* Hard Fault Handler */
	.long	MemManage_Handler     /* MPU Fault Handler */
	.long	BusFault_Handler      /* Bus Fault Handler */
	.long	UsageFault_Handler    /* Usage Fault Handler */
	.long	0                     /* Reserved */
	.long	0                     /* Reserved */
	.long	0                     /* Reserved */
	.long	0                     /* Reserved */
	.long	SVC_Handler           /* SVCall Handler */
	.long	DebugMon_Handler      /* Debug Monitor Handler */
	.long	0                     /* Reserved */
	.long	PendSV_Handler        /* PendSV Handler */
	.long	SysTick_Handler       /* SysTick Handler */

	/* External interrupts */
	.long	WDT_IRQHandler        /*  0:  Watchdog Timer            */
	.long	RTC_IRQHandler        /*  1:  Real Time Clock           */
	.long	TIM0_IRQHandler       /*  2:  Timer0 / Timer1           */
	.long	TIM2_IRQHandler       /*  3:  Timer2 / Timer3           */
	.long	MCIA_IRQHandler       /*  4:  MCIa                      */
	.long	MCIB_IRQHandler       /*  5:  MCIb                      */
	.long	UART0_IRQHandler      /*  6:  UART0 - DUT FPGA          */
	.long	UART1_IRQHandler      /*  7:  UART1 - DUT FPGA          */
	.long	UART2_IRQHandler      /*  8:  UART2 - DUT FPGA          */
	.long	UART4_IRQHandler      /*  9:  UART4 - not connected     */
	.long	AACI_IRQHandler       /* 10: AACI / AC97                */
	.long	CLCD_IRQHandler       /* 11: CLCD Combined Interrupt    */
	.long	ENET_IRQHandler       /* 12: Ethernet                   */
	.long	USBDC_IRQHandler      /* 13: USB Device                 */
	.long	USBHC_IRQHandler      /* 14: USB Host Controller        */
	.long	CHLCD_IRQHandler      /* 15: Character LCD              */
	.long	FLEXRAY_IRQHandler    /* 16: Flexray                    */
	.long	CAN_IRQHandler        /* 17: CAN                        */
	.long	LIN_IRQHandler        /* 18: LIN                        */
	.long	I2C_IRQHandler        /* 19: I2C ADC/DAC                */
	.long	0                     /* 20: Reserved                   */
	.long	0                     /* 21: Reserved                   */
	.long	0                     /* 22: Reserved                   */
	.long	0                     /* 23: Reserved                   */
	.long	0                     /* 24: Reserved                   */
	.long	0                     /* 25: Reserved                   */
	.long	0                     /* 26: Reserved                   */
	.long	0                     /* 27: Reserved                   */
	.long	CPU_CLCD_IRQHandler   /* 28: Reserved - CPU FPGA CLCD   */
	.long	0                     /* 29: Reserved - CPU FPGA        */
	.long	UART3_IRQHandler      /* 30: UART3    - CPU FPGA        */
	.long	SPI_IRQHandler        /* 31: SPI Touchscreen - CPU FPGA */

	.size	__Vectors, . - __Vectors

1.5 代码段

.text告诉汇编器as把下列内容放入代码段;
.thumb选择Thumb指令集;
.thumb_func用来指明随后的符号Reset_Handler是Thumb指令集编码的函数;
.type汇编伪指令指定Reset_Handler符号的类型是函数。

	.text
	.thumb
	.thumb_func
	.align	2
	.globl	Reset_Handler
	.type	Reset_Handler, %function
Reset_Handler:

1.6 程序入口Reset_Handler

Keil提供的链接脚本中有两种拷贝数据的方式,一种是一次可以拷贝多个段,但是要用到更多的指令;另一种是一次拷贝一个段,我们看这个简单的写法:

Reset_Handler:
#ifdef __STARTUP_COPY_MULTIPLE
    ...
#else
	/* copy from flash to ram */
#endif /*__STARTUP_COPY_MULTIPLE */

ldr指令用于将执行单次存储器的读操作,读到寄存器中,读4字节。
ldr r1, =__etext的作用是将标号__etext代表的程序加载到寄存器r1中,其他两句类似。

怎么没看到__etext呢?因为它并不在这个文件中,而是在链接脚本gcc_arm.ld中,我把__etext摘抄到这里:

MEMORY
{
  FLASH (rx)  : ORIGIN = 0x00000000, LENGTH = 512K
  RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
}
ENTRY(Reset_Handler)
SECTIONS
{
	.text :
	{
		KEEP(*(.vectors))
		...
	} > FLASH
	...
	__etext = .;
	.data : AT (__etext)
	{
		__data_start__ = .;
		...
		__data_end__ = .;
	} > RAM
	...
	.heap (COPY):
	{
		__HeapBase = .;
		__end__ = .;
		end = __end__;
		KEEP(*(.heap*))
		__HeapLimit = .;
	} > RAM
	.stack_dummy (COPY):
	{
		KEEP(*(.stack*))
	} > RAM
	...
	__StackTop = ORIGIN(RAM) + LENGTH(RAM);
	__StackLimit = __StackTop - SIZEOF(.stack_dummy);
	PROVIDE(__stack = __StackTop);

	/* Check if data + heap + stack exceeds RAM limit */
	ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")

可见:

  1. 链接脚本把启动脚本中定义的Reset_Handler函数设置为程序入口
  2. 链接脚本把启动脚本中定义的向量表.vectors放在.text输出段的0地址
  3. 启动脚本将链接脚本中定义的__etext加载到寄存器r1中,也就是.data的起始地址、也是.text段的结束地址
  4. 启动脚本将链接脚本中定义的__data_start____data_end__分别加载到寄存器r2、r3中
  5. 启动脚本中定义了的__StackTop__StackLimit__HeapLimit等在链接脚本中又重新计算了

1.7 拷贝数据段

为什么要拷贝数据,因为flash是只读的,而每个进程运行时要对数据进行读和写,因此要拷贝到RAM中。而且RAM的访问速度大于flash。

#else
	ldr	r1, =__etext
	ldr	r2, =__data_start__
	ldr	r3, =__data_end__

.L_loop1:
	cmp	r2, r3
	ittt	lt
	ldrlt	r0, [r1], #4
	strlt	r0, [r2], #4
	blt	.L_loop1
#endif /*__STARTUP_COPY_MULTIPLE */

cmp比较:计算r2-r3,APSR更新但结果不会保存
it指令也就是条件执行指令(If-Then),后面跟条件,条件满足时接下来的最多4条指令可以根据条件执行,it操作码可以附加最多3个可选后缀t(Then)和e(Else)。
因此上面的语句伪代码可以理解为:

if (r2 < r3) 
then 
{
	ldrlt	r0, [r1], #4
	strlt	r0, [r2], #4
	blt	.L_loop1
}
else {
}

三个操作数的形式为Rm, [Rn], #offset,这就是个后序存储器寻址模式,注意跟前序对比Rm, [Rn, #offset]

综上为:

  1. r1内容是flash中text段的截止位置、也就是数据的开始位置;r2、r3分别是RAM中数据区的起止地址;
  2. 比较r2、r3,也就是还没有到终点__data_end__
  3. 满足r2<r3的条件的话,执行下列三条指令
  4. 将r1的内容指向的4字节数据从存储器中拷贝到寄存器r0中,并将r1的内容增加4;
  5. 将r0的数据从寄存器中拷贝到r2的内容指向的4字节存储器位置中,并将r2的内容增加4;
  6. 跳到2,继续。

1.8 bss置0

与上一小节类似,不过这段多了一个#elif,也就是说想要在汇编语言中实现bss置0的话,要显式的定义__STARTUP_CLEAR_BSS,否则这事汇编就不干了,交给C语言去干。

movs指令在处理器内来回传送数据,例如寄存器之间、寄存器与特殊寄存器、将立即数送到寄存器。

#ifdef __STARTUP_CLEAR_BSS_MULTIPLE
#elif defined (__STARTUP_CLEAR_BSS)
	ldr	r1, =__bss_start__
	ldr	r2, =__bss_end__

	movs	r0, 0
.L_loop3:
	cmp	r1, r2
	itt	lt
	strlt	r0, [r1], #4
	blt	.L_loop3
#endif /* __STARTUP_CLEAR_BSS_MULTIPLE || __STARTUP_CLEAR_BSS */

1.9 调用SystemInit

要调用函数,可以使用链接跳转bl <label>或带链接的间接跳转blx <Rm>指令。

#ifndef __NO_SYSTEM_INIT
	bl	SystemInit
#endif

SystemInit()函数定义在system_ARMCM3.c系统文件中,如下:

#include "ARMCM3.h"
#define  XTAL            ( 5000000UL)
#define  SYSTEM_CLOCK    (5U * XTAL)
...
uint32_t SystemCoreClock = SYSTEM_CLOCK;
...
void SystemInit (void)
{

#if defined (__VTOR_PRESENT) && (__VTOR_PRESENT == 1U)
  SCB->VTOR = (uint32_t) &__Vectors;
#endif

  SystemCoreClock = SYSTEM_CLOCK;
}

1.10 调用_start

C程序的入口并不是main()函数,而是_start()函数,只不过gcc链接器默认会依赖一些库,然后_start()会调用main():

#ifndef __START
#define __START _start
#endif
	bl	__START

例如在linux操作系统中:
在这里插入图片描述
main()则是上次应用自己写的了,如果是裸机嵌入式系统开发,这里可以直接写成:
(假设有个main()结束后的exit()函数)

    bl    main
    bl    exit

1.11 结束Reset_Handler

同cortex-m0。

	.pool
	.size	Reset_Handler, . - Reset_Handler

1.12 定义宏与函数指针

同cortex-m0。

    .macro    def_default_handler    handler_name
    .align 1
    .thumb_func
    .weak    \handler_name
    .type    \handler_name, %function
\handler_name :
     b    .
    .size    \handler_name, . - \handler_name
    .endm
/* System Exception Handlers */

    def_default_handler    NMI_Handler
    def_default_handler    HardFault_Handler
    def_default_handler    MemManage_Handler
    def_default_handler    BusFault_Handler
    def_default_handler    UsageFault_Handler
    def_default_handler    SVC_Handler
    def_default_handler    DebugMon_Handler
    def_default_handler    PendSV_Handler
    def_default_handler    SysTick_Handler

/* IRQ Handlers */

    def_default_handler    UART0_Handler       
    def_default_handler    GPIO0_Handler       
    def_default_handler    GPIO1_Handler       
    def_default_handler    QSPI0_Handler       
    def_default_handler    DAP_QSPI0_Handler   
    def_default_handler    DAP_SPI0_Handler    
    def_default_handler    DAP_QSPI_XIP_Handler
    def_default_handler    DAPLinkFittedn      
    def_default_handler    Unused_IRQ8         
    def_default_handler    Unused_IRQ9         
    def_default_handler    Unused_IRQ10        
    def_default_handler    Unused_IRQ11        
    def_default_handler    Unused_IRQ12        
    def_default_handler    Unused_IRQ13        
    def_default_handler    Unused_IRQ14        
    def_default_handler    Unused_IRQ15        
    def_default_handler    Unused_IRQ16        
    def_default_handler    Unused_IRQ17        
    def_default_handler    Unused_IRQ18        
    def_default_handler    Unused_IRQ19        
    def_default_handler    Unused_IRQ20        
    def_default_handler    Unused_IRQ21        
    def_default_handler    Unused_IRQ22        
    def_default_handler    Unused_IRQ23        
    def_default_handler    Unused_IRQ24        
    def_default_handler    Unused_IRQ25        
    def_default_handler    Unused_IRQ26        
    def_default_handler    Unused_IRQ27        
    def_default_handler    Unused_IRQ28        
    def_default_handler    Unused_IRQ29        
    def_default_handler    Unused_IRQ30        
    def_default_handler    Unused_IRQ31        

    /*
    def_default_handler    Default_Handler
    .weak    DEF_IRQHandler
    .set    DEF_IRQHandler, Default_Handler
    */
    .end

其中伪指令.end标识这汇编文件全文结束,优雅。

2. Keil M3链接脚本

我们在这篇博客中已经系统的学习过了GCC链接器,正好借这个机会练练手。

2.1 内存配置

MEMORY
{
  FLASH (rx)  : ORIGIN = 0x00000000, LENGTH = 512K
  RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
}

2.2 增加输入库文件

GROUP(file, file, …),增加输入文件,只能是库文件libxx.a,效果等同于在ld命令后加选项-l

GROUP(libgcc.a libc.a libm.a libnosys.a)

这些依赖的库文件在哪里呢?不在Keil的安装包里,而是在GNU的交叉编译工具链里:
在这里插入图片描述

2.3 设置程序入口

ENTRY(Reset_Handler)

SECTIONS
{
...
}

2.4 代码段

flash的绝对地址本来就是0x0,这里又把flash的0偏移位置先存IVT,这个IVT就是启动脚本1.4小节中设置的.vectors。随后放所有输入文件的.text段、再放所有输入文件的.rodata段。

	.text :
	{
		KEEP(*(.vectors))
		...
		*(.text*)
		...
		*(.rodata*)
		...
	} > FLASH

中间省略了一些我们启动脚本中不涉及的部分;
其他放入flash的还有:.ARM.extab .ARM.exidx .copy.table .zero.table。

2.5 数据段

注意,这里和启动脚本相互配合,这些数据在芯片启动过程中要从FLASH拷贝到RAM。

	__etext = .;
	.data : AT (__etext)
	{
		__data_start__ = .;
		...
		*(.data*)
		...
		__data_end__ = .;
	} > RAM

2.6 bss段

.bss段要和启动脚本相互配合,在启动过程中分配空间、并置0.

	.bss :
	{
		. = ALIGN(4);
		__bss_start__ = .;
		*(.bss*)
		*(COMMON)
		. = ALIGN(4);
		__bss_end__ = .;
	} > RAM

2.7 堆、栈

以下都很直白,其中COPY是输出段的type,功能是:指明该输出段不可分配(not allocatable),这样在程序运行时就不会为该部分分配内存。

	.heap (COPY):
	{
		__HeapBase = .;
		__end__ = .;
		end = __end__;
		KEEP(*(.heap*))
		__HeapLimit = .;
	} > RAM

	.stack_dummy (COPY):
	{
		KEEP(*(.stack*))
	} > RAM
	
	__StackTop = ORIGIN(RAM) + LENGTH(RAM);
	__StackLimit = __StackTop - SIZEOF(.stack_dummy);
	PROVIDE(__stack = __StackTop);

	ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")

至此,分析完毕。


总结

启动脚本还可以用C语言编写,启动脚本与uboot、外设初始化相关内容还没有研究。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-09-12 13:19:13  更:2021-09-12 13:19:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/30 1:30:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码