stm32在manin()前做了什么?
扫描二维码
随时随地手机看文章
最近要在Cortex-M3上写一个简单的操作系统,打算使用IAR,为了写好启动代码,花了一些时间了解了IAR在main()以前做了些什么事。
首先系统复位时,Cortex-M3从代码区偏移0x0000'0000处获取栈顶地址,用来初始化MSP寄存器的值。
接下来从代码区偏移0x0000'0004获取第一个指令的跳转地址。这些地址,是CM3要求放置中断向量表的地方。
这里是一个程序的启动区的反汇编:
__vector_table:
080040002600
080040022000
080040047E1D
080040060800
这个程序是由IAP程序来启动的,IAP程序获取0x0800'4000处的MSP值(0x20002600),并设置为MSP的值,即主堆栈最大范围是0x2000'0000~0x2000'25FF。接下来IAP程序获取0x0800'4004处的Reset_Handler的地址(0x0800'7E1D),并跳转到Reset_Handler()执行。
IAP在这里完全是模仿了Cortex-M3的复位序列,也就是说,在没有IAP的系统上,CM3只能从0x0800'0000获取MSP,从0x0800'0004获取第一条指令所处地址。而IAP就存在在0x0800'0000这个地址上,IAP的启动,已经消耗掉了这个复位序列,所以IAP要启动UserApp程序的时候,也是完全模仿Cortex-M3的复位序列的。
接下来我们看看复位后第一句指令——Reset_Handler()函数里有什么。
若我们使用的是ST公司标准外设库,那么已经有了现成的Reset_Handler,不过他是弱定义——PUBWEAK,可以被我们重写的同名函数覆盖。一般来说,我们使用的都是ST提供的Reset_Handler,在V3.4版本的库中,可以在startup_stm32f10x_xx.s中找到这个函数:
PUBWEAK Reset_Handler
SECTION .text:CODE:REORDER(2)
Reset_Handler
LDRR0, =SystemInit
BLXR0
LDRR0, =__iar_program_start
BXR0
看来ST没有做太多的事,他只调用了自家库提供的SystemInit函数进行系统时钟、Flash读取的初始化,并把大权交给了__iar_program_start这个IAR提供的“内部函数”了,我们就跟紧这个__iar_program_start跳转,看看IAR做了什么,上面一段代码的反汇编如下:
Reset_Handler:
__iar_section$$root:
08007E1C4801LDRR0, [PC, #0x4]; LDRR0, =SystemInit
08007E1E4780BLXR0;BLXR0
08007E204801LDRR0, [PC, #0x4];LDRR0, =__iar_program_start
08007E224700BXR0;BXR0
08007E246C69
08007E260800
08007E287D8D
08007E2A0800
细心的观众会发现地址是0x0800'7E1C,比我们查到的0x0800'7E1D差了1,这是ARM家族的遗留问题,因为ARM处理器的指令至少是半字对齐的(16位THUMB指令集or 32位ARM指令集),所以PC指针的LSB是常为0的,为了充分利用寄存器,ARM公司给PC的LSB了一个重要的使命,那就是在执行分支跳转时,PC的LSB=1,表示使用THUMB模式,LSB=0,表示使用ARM模式,但在最新的Cortex-M3内核上,只使用了THUMB-2指令集挑大梁,所以这一位要常保持1,所以我们查到的地址是0x0800'7E1D(C=1100,D=1101),放心,我们的CM3内核会忽略掉LSB(除非为0,那么会引起一个fault),从而正确跳转到0x0800'7E1C。
从0x0800'7E20处的加载指令,我们可以算出__iar_program_start所处的位置,就是当前PC指针(0x0800'7E24),再加上4,即0x0800'7E28处的所指向的地址——0x0800'7D8D(0x0800'7D8C),我们跟紧着跳转,__iar_program_start果然在这里:
__iar_program_start:
08007D8CF000F88CBL__low_level_init
08007D902800CMPR0, #0x0
08007D92D001BEQ__iar_init$$done
08007D94F7FFFFDEBL__iar_data_init2
08007D982000MOVSR0, #0x0
08007D9AF7FDFC49BLmain
我们看到IAR提供了__low_level_init这个函数进行了“底层”的初始化,进一步跟踪,我们可以查到__low_level_init这个函数做了些什么,不是不是我们想象中的不可告人。
__low_level_init:
08007EA82001MOVSR0, #0x1
08007EAA4770BXLR
__low_level_init出乎想象的简单,只是往R0寄存器写入了1,就立即执行"BX LR"回到调用处了,接下来,__iar_program_start检查了R0是否为0,为0,则执行__iar_init$$done,若不是0,就执行__iar_data_init2。__iar_init$$done这个函数很简单,只有2句话,第一句是把R0清零,第二句就直接"BL main",跳转到main()函数了。不过既然__low_level_init已经往R0写入了1,那么我们还是得走下远路——看看__iar_data_init2做了些什么,虽然距离main只有一步之遥,不过这中间隐藏了编译器的思想,我们得耐心看下去。
__iar_data_init2:
08007D54B510PUSH{R4,LR}
08007D564804LDRR0, [PC, #0x10]
08007D584C04LDRR4, [PC, #0x10]
08007D5AE002B0x8007D62
08007D5CF8501B04LDRR1, [R0], #0x4
08007D604788BLXR1
08007D6242A0CMPR0, R4
08007D64D1FABNE0x8007D5C
08007D66BD10POP{R4,PC}
08007D687C78
08007D6A0800
08007D6C7C9C
08007D6E0800
看来IAR迟迟不执行main()函数,就是为了执行__iar_data_init2,我们来分析分析IAR都干了些什么坏事~
首先压R4,LR入栈,然后加载0x0800'7C78至R0,0x0800'7C9C至R4,马上跳转到0x0800'7D62执行R0,R4的比较,结果若是相等,则弹出R4,PC,然后立即进入main()。不过IAR请君入瓮是自不会那么快放我们出来的——结果不相等,跳转到0x0800'7D5C执行,在这里,把R0指向的地址——0x0800'7C78中的值——0x0800'7D71加载到R1,并且R0中的值自加4,更新为0x0800'7C7C,并跳转到R1指向的地址处执行,这里是另一个IAR函数:__iar_zero_init2:
__iar_zero_init2:
08007D702300MOVSR3, #0x0
08007D72E005B0x8007D80
08007D74F8501B04LDRR1, [R0], #0x4
08007D78F8413B04STRR3, [R1], #0x4
08007D7C1F12SUBSR2, R2, #0x4
08007D7ED1FBBNE0x8007D78
08007D80F8502B04LDRR2, [R0], #0x4
08007D842A00CMPR2, #0x0
08007D86D1F5BNE0x8007D74
08007D884770BXLR
08007D8A0000MOVSR0, R0