main函数之前都发生了什么?

STM32启动代码

编译器:keil MDK4.1
 
进入到嵌入式领域,main函数之前还有一段启动代码!
究竟在main函数之前,发生了什么?如果你觉得已经明白了这个过程,那么请试着回答这个问题:程序是存储到FLASH中的,运行时static变量地址是指向RAM,那么这些static变量的初始值是如何映射到RAM中的?
 
我们以STM32F10x的启动代码为例,先看看其完整的源码:
; Dummy Exception Handlers (infinite loops which can be modified)
 
NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler          [WEAK]
                B       .
                ENDP
MemManage_Handler\
                PROC
                EXPORT  MemManage_Handler          [WEAK]
                B       .
                ENDP
BusFault_Handler\
                PROC
                EXPORT  BusFault_Handler           [WEAK]
                B       .
                ENDP
UsageFault_Handler\
                PROC
                EXPORT  UsageFault_Handler         [WEAK]
                B       .
                ENDP
SVC_Handler     PROC
                EXPORT  SVC_Handler                [WEAK]
                B       .
                ENDP
DebugMon_Handler\
                PROC
                EXPORT  DebugMon_Handler           [WEAK]
                B       .
                ENDP
PendSV_Handler  PROC
                EXPORT  PendSV_Handler             [WEAK]
                B       .
                ENDP
SysTick_Handler PROC
                EXPORT  SysTick_Handler            [WEAK]
                B       .
                ENDP
 
Default_Handler PROC
 
                EXPORT  WWDG_IRQHandler            [WEAK]
                EXPORT  PVD_IRQHandler             [WEAK]
                EXPORT  TAMPER_IRQHandler          [WEAK]
                EXPORT  RTC_IRQHandler             [WEAK]
                EXPORT  FLASH_IRQHandler           [WEAK]
                EXPORT  RCC_IRQHandler             [WEAK]
                EXPORT  EXTI0_IRQHandler           [WEAK]
                EXPORT  EXTI1_IRQHandler           [WEAK]
                EXPORT  EXTI2_IRQHandler           [WEAK]
                EXPORT  EXTI3_IRQHandler           [WEAK]
                EXPORT  EXTI4_IRQHandler           [WEAK]
                EXPORT  DMA1_Channel1_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel2_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel3_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel4_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel5_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel6_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel7_IRQHandler   [WEAK]
                EXPORT  ADC1_2_IRQHandler          [WEAK]
                EXPORT  USB_HP_CAN1_TX_IRQHandler  [WEAK]
                EXPORT  USB_LP_CAN1_RX0_IRQHandler [WEAK]
                EXPORT  CAN1_RX1_IRQHandler        [WEAK]
                EXPORT  CAN1_SCE_IRQHandler        [WEAK]
                EXPORT  EXTI9_5_IRQHandler         [WEAK]
                EXPORT  TIM1_BRK_IRQHandler        [WEAK]
                EXPORT  TIM1_UP_IRQHandler         [WEAK]
                EXPORT  TIM1_TRG_COM_IRQHandler    [WEAK]
                EXPORT  TIM1_CC_IRQHandler         [WEAK]
                EXPORT  TIM2_IRQHandler            [WEAK]
                EXPORT  TIM3_IRQHandler            [WEAK]
                EXPORT  TIM4_IRQHandler            [WEAK]
                EXPORT  I2C1_EV_IRQHandler         [WEAK]
                EXPORT  I2C1_ER_IRQHandler         [WEAK]
                EXPORT  I2C2_EV_IRQHandler         [WEAK]
                EXPORT  I2C2_ER_IRQHandler         [WEAK]
                EXPORT  SPI1_IRQHandler            [WEAK]
                EXPORT  SPI2_IRQHandler            [WEAK]
                EXPORT  USART1_IRQHandler          [WEAK]
                EXPORT  USART2_IRQHandler          [WEAK]
                EXPORT  USART3_IRQHandler          [WEAK]
                EXPORT  EXTI15_10_IRQHandler       [WEAK]
                EXPORT  RTCAlarm_IRQHandler        [WEAK]
                EXPORT  USBWakeUp_IRQHandler       [WEAK]
                EXPORT  TIM8_BRK_IRQHandler        [WEAK]
                EXPORT  TIM8_UP_IRQHandler         [WEAK]
                EXPORT  TIM8_TRG_COM_IRQHandler    [WEAK]
                EXPORT  TIM8_CC_IRQHandler         [WEAK]
                EXPORT  ADC3_IRQHandler            [WEAK]
                EXPORT  FSMC_IRQHandler            [WEAK]
                EXPORT  SDIO_IRQHandler            [WEAK]
                EXPORT  TIM5_IRQHandler            [WEAK]
                EXPORT  SPI3_IRQHandler            [WEAK]
                EXPORT  UART4_IRQHandler           [WEAK]
                EXPORT  UART5_IRQHandler           [WEAK]
                EXPORT  TIM6_IRQHandler            [WEAK]
                EXPORT  TIM7_IRQHandler            [WEAK]
                EXPORT  DMA2_Channel1_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel3_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]
 
WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_IRQHandler
DMA1_Channel3_IRQHandler
DMA1_Channel4_IRQHandler
DMA1_Channel5_IRQHandler
DMA1_Channel6_IRQHandler
DMA1_Channel7_IRQHandler
ADC1_2_IRQHandler
USB_HP_CAN1_TX_IRQHandler
USB_LP_CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_IRQHandler
TIM1_UP_IRQHandler
TIM1_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler
TIM8_BRK_IRQHandler
TIM8_UP_IRQHandler
TIM8_TRG_COM_IRQHandler
TIM8_CC_IRQHandler
ADC3_IRQHandler
FSMC_IRQHandler
SDIO_IRQHandler
TIM5_IRQHandler
SPI3_IRQHandler
UART4_IRQHandler
UART5_IRQHandler
TIM6_IRQHandler
TIM7_IRQHandler
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
                B       .
 
                ENDP
 
                ALIGN
一些旁枝末节和本文的主题无关,我们先不要去理会,只需要知道这个启动代码是设置向量表,然后跳转到__main函数。跳转具体到代码段部分如下:
; Reset handler
Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDP

当大家看到__main函数时,估计应该有不少人认为这个是main函数的别名或是编译之后的名字,否则在启动代码中再也无法找到和main相关的字眼了。可事实是,__main和main是完全两个不同的函数!如果这还不足以让你诧异,那么再告诉你另一个事实:你无法找到__main代码,因为这个是编译器自动创建的!

如果你对此还半信半疑,可以查看MDK的文档,会发现有这么一句说明:It is automatically created by the linker when it sees a definition of main()。简单点来说,当编译器发现定义了main函数,那么就会自动创建__main。

__main函数的出身我们基本搞清楚了,那么现在的问题是,它和main又有什么关系呢?其实__main主要做这么两件事:初始化C/C++所需的资源,调用main函数。初始化先暂时不说,但“调用main函数”这个功能能够让我们解决为什么之前的启动代码调用的是__main,最后却能转到main函数的疑惑。

初始化C/C++所需的资源,如果脱离了具体情况,实在很难解释清楚,还是先看看编译出来的汇编代码片段:

凡是以__rt开头的,都是用来初始化C/C++运行库的;而以__scatterload开头,则是根据离散文件的定义,将代码中的变量映射到相应的内存位置。而回答本文开头的问题,关键就在于__scatterload_copy函数!

我们在STM32F10x平台举个简单的例子,首先要明白一点是,该平台的的flash地址以0x08000000为起始,主要是存储代码;而SRAM是以0x20000000为起始,也就是内存。然后C/C++有这么一行代码:

static int g_iVal = 12; 

当我们程序开始跑起来的时候,通过IDE发现,g_iVal被映射到内存地址0x20000000,数值为一个随机数0xFFFFBE00,而不是代码中设置的12,如图:

我们让程序继续往下执行,当执行完毕__scatterload_copy之后,我们发现g_iVal这时候已经变成我们所需要的初始值了:

接下来就是C/C++库的初始化,最后就是进入到main函数,而此时已经是万事俱备。

STM32与ARM启动代码比较

通常的启动代码结构:

1.  首先是中断向量表的定义.

Ø         ARM

ARM代码在这块的代码为跳转语句,因为指令长度的限制,4个字节也就能放个跳转语就差不多了。通常两种实现方式:

1.       B   Reset_Handler

2.       LDR PC, Reset_Handler

其实都是一个意思,跳转到真正实现Reset_Handler功能的地方去。ARM中断向量在这里总共有8条(复位、未定义、SWI、指令、数据异常、预留、IRQ、FIQ),具体的当前中断类型,在IRQ或FIQ的中断实现里面判断,之后再转到对应的中断处理函数里面。

注意,仔细看,想一想,这里的中断向量处存放的是机器指令码。然而,STM在中断向量处存放的是实现中断功能的入口地址,而不是指令功能码。 

Ø         STM

正如上面所说,STM中断向量处存放的是目标地址。但是要注意的是,第一条中断向量存放的堆栈的地址,真正的传统意义上的中断向量从第二条开始。除此之外,STM的中断向量表很长,它不像ARM由IRQ或FIQ进行判断后再处理,而是将所有的中断处理函数入口地址全列在这里: 

__Vectors      DCD     __initial_sp          ; Top of Stack

                DCD     Reset_Handler         ; Reset Handler

                DCD     NMI_Handler           ; NMI Handler

                DCD     HardFault_Handler     ; Hard Fault Handler

                DCD     MemManage_Handler     ; MPU Fault Handler

                DCD     BusFault_Handler      ; Bus Fault Handler

                DCD     UsageFault_Handler    ; Usage Fault Handler

                DCD     0                       ; Reserved

                DCD     0                       ; Reserved

                DCD     0                      ; Reserved

                DCD     0                      ; Reserved

                DCD     SVC_Handler           ; SVCall Handler

                DCD     DebugMon_Handler     ; Debug Monitor Handler

                DCD     0                      ; Reserved

                DCD     PendSV_Handler       ; PendSV Handler

                DCD     SysTick_Handler      ; SysTick Handler

 

                ; External Interrupts

                DCD     WWDG_IRQHandler      ; Window Watchdog

                DCD     PVD_IRQHandler       ; PVD through EXTI Line detect

                DCD     TAMPER_IRQHandler    ; Tamper

                …………….

 

2.  中断函数的跳转实现

这块功能的实现依赖于编译器、链接器的功能,实现方法各不相同。

Ø         ARM

        CODE32

                AREA    Startup,CODE,READONLY

Vectors

LDR     PC, ResetAddr

              LDR     PC, UndefinedAddr

              LDR     PC, SWI_Addr

              LDR     PC, PrefetchAddr

              LDR     PC, DataAbortAddr

              B            .

              LDR       PC, IRQ_Addr ;跳转至标号IRQ_Addr处

              LDR     PC, FIQ_Addr 

ResetAddr              DCD     Reset

UndefinedAddr      DCD     Undefined

SWI_Addr               DCD     SoftwareInterrupt

PrefetchAddr         DCD     PrefetchAbort

DataAbortAddr      DCD     DataAbort

Nouse                    DCD     0

IRQ_Addr              DCD     IRQ_Handler ; IRQ_Addr定义为IRQ_Handler地址

FIQ_Addr               DCD     FIQ_Handler 

; IRQ_Handler在这里定义

IRQ_Handler

           SUB SP, SP, #4

           STMFD SP!, {R8-R9}   

           LDR R9, =INTOFFSET

           LDR R9, [R9]

           LDR       R8, =HandleEINT0

           ADD R8, R8,R9,LSL #2

           LDR R8, [R8]

           STR R8, [SP,#8]

           LDMFD SP!,{R8-R9,PC} 

注意上面的HandleEINT0标号,它是中断函数的入口首地址,加上当前中断编号的偏移值INTOFFSET。具体对应到哪里呢?看下面:

;这是定义(或者说预留)一个段指定位置开始的内存空间.

         MAP (0x33FFBF00)      

SysRstVector    #     4    

UdfInsVector     #     4    

SwiSvcVector    #     4

InsAbtVector     #     4

DatAbtVector     #     4

ReservedVector       #     4

IrqSvcVector      #     4

FiqSvcVector     #     4

 

HandleEINT0        #   4

HandleEINT1          #   4

HandleEINT2          #   4

HandleEINT3          #   4

HandleEINT4_7     #   4

….

实际上这里也可以理解为定义一个结构体变量,各个标号对应结构体的域,跟C语言不同的是,这里定义的结构体变量可以指定它在内存空间中的地址。

好了,如果当前来了一个IRQ类型的EINT3中断,按照上面的代码应该是跳转至以HandleEINT3这个域存储的值为地址处。那么HandleEINT3这个域里存储的值是什么呢?

下面的代码即可在C语言中定义了。

#define _ISR_STARTADDRESS   0x33FFBF00

#define pISR_EINT3     (*(unsigned *)(_ISR_STARTADDRESS+0x2c))

pISR_EINT3 = (unsigned int)EINT3_Handler; 

static void __irq EINT3_Handler(void)

{

Ø         STM32

STM32中断处理实现跟ARM不一样。来看代码:

启动代码处的中断向量表(我们以EXTI0为例):

__Vectors      DCD     __initial_sp              ; Top of Stack

                DCD     Reset_Handler           ; Reset Handler

                DCD     NMI_Handler             ; NMI Handler

                DCD     HardFault_Handler       ; Hard Fault Handler

                DCD     MemManage_Handler    ; MPU Fault Handler

                DCD     BusFault_Handler        ; Bus Fault Handler

                DCD     UsageFault_Handler      ; Usage Fault Handler

                DCD     0                         ; Reserved

                DCD     0                         ; Reserved

                DCD     0                         ; Reserved

                DCD     0                         ; Reserved

                DCD     SVC_Handler             ; SVCall Handler

                DCD     DebugMon_Handler       ; Debug Monitor Handler

                DCD     0                         ; Reserved

                DCD     PendSV_Handler          ; PendSV Handler

                DCD     SysTick_Handler          ; SysTick Handler

 

                ; External Interrupts

                DCD     WWDG_IRQHandler        ; Window Watchdog

                DCD     PVD_IRQHandler           ; PVD through EXTI Line detect

                DCD     TAMPER_IRQHandler       ; Tamper

                DCD     RTC_IRQHandler           ; RTC

                DCD     FLASH_IRQHandler         ; Flash

                DCD     RCC_IRQHandler           ; RCC

                DCD     EXTI0_IRQHandler      ; EXTI Line 0 中断发生时跳转至EXT0_IRQHandler地址处。@@@记住这条代码,下面以此处为例@@@

                       …. 

Default_Handler PROC

                EXPORT  WWDG_IRQHandler           [WEAK]

                EXPORT  PVD_IRQHandler            [WEAK]

                EXPORT  TAMPER_IRQHandler         [WEAK]

                EXPORT  RTC_IRQHandler            [WEAK]

                EXPORT  FLASH_IRQHandler          [WEAK]

                EXPORT  RCC_IRQHandler            [WEAK]

                EXPORT  EXTI0_IRQHandler          [WEAK]

                       …..

WWDG_IRQHandler

PVD_IRQHandler

TAMPER_IRQHandler

RTC_IRQHandler

FLASH_IRQHandler

RCC_IRQHandler

EXTI0_IRQHandler

EXTI1_IRQHandler

….

                B   .

                ENDP 

这段是啥意思呢?这里是定义各个中断向量的处理函数处,所有列出来的中断向量处理函数地址一致,功能也是一致:原地跳转。

既然所有的中断处理函数功能一致,那它是如何跳转至用户定义在C语言中的中断处理函数的呢?答案是,如果用户没有在用户代码(C语言)中定义对应向量的中断处理函数,则实际起作用的真正的中断处理函数即为上面列出的原地跳转功能处。

它是如何实现的? 注意到在声明导出处理函数后面的[WEAK]了吗?它的功能由链接器实现:如果在别处也定义该标号(函数),在链接时用别处的地址。如果没有其它定方定义,则以此处地址进行链接。

可能不太好理解,实际上是启动代码已经预定义了中断处理函数,它的功能很简单,就是原地跳转。只不过这块预定义的中断处理函数是否真正起作用,要看你是否在别处重定义了相同标号的中断处理函数。如果你已经重定义了,则以你重定义的中断处理函数为准。

以EXTI0中断为列,假设用户在自已的代码中配置好了EXTI0的中断,并且重定义了下面的EXTI0_IRQHandler函数,则链接器会以此函数地址进行链接。

void EXTI0_IRQHandler()

{

}

也就是在上面启动代码的@@@标注处(DCD  EXTI0_IRQHandler),会以用户重定义的EXTI0_IRQHandler()函数地址填入。

永不止步步 发表于12-17 10:00 浏览65535次
分享到:

已有0条评论

暂时还没有回复哟,快来抢沙发吧

添加一条新评论

只有登录用户才能评论,请先登录注册哦!

话题作者

永不止步步
金币:67410个|学分:307967个
立即注册
畅学电子网,带你进入电子开发学习世界
专业电子工程技术学习交流社区,加入畅学一起充电加油吧!

x

畅学电子网订阅号