0%

__main

0、说明

LPC1115,keil。

本文主要分析启动文件中__main函数的功能。

1、函数入口

文件:startup_LPC11xx.s

1
2
3
4
5
6
7
8
9
Reset_Handler   PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP

这里的__main并不是main()函数,而是在进入C环境之前的预备工作,包括将RW变量从rom复制到ram指定位置,将.bss段清零,最后才跳转到用户的main函数。

可以将编译完成的.axf反汇编,结合map文件来分析__main函数。反汇编配置如下图所示。

RPQASO.png

“4”框是生成bin文件的命令,也就是最终下载到单片机中的文件。

2、反汇编
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
__rt_entry
__rt_entry_presh_1
__rt_entry_sh
0x0000012c: f000ff88 .... BL __user_setup_stackheap ; 0x1040
0x00000130: 4611 .F MOV r1,r2

;......

!!!main
__main
0x000000c0: f000f802 .... BL __scatterload ; 0xc8
0x000000c4: f000f832 ..2. BL __rt_entry ; 0x12c
!!!scatter
__scatterload
__scatterload_rt2
__scatterload_rt2_thumb_only
0x000000c8: a00c .. ADR r0,{pc}+0x34 ; 0xfc
0x000000ca: c830 0. LDM r0!,{r4,r5}
0x000000cc: 3808 .8 SUBS r0,r0,#8
0x000000ce: 1824 $. ADDS r4,r4,r0;r4=0x6ca0,Region$$Table$$Base
0x000000d0: 182d -. ADDS r5,r5,r0 ;r5=0x6cc0,Region$$Table末尾
0x000000d2: 46a2 .F MOV r10,r4
0x000000d4: 1e67 g. SUBS r7,r4,#1
0x000000d6: 46ab .F MOV r11,r5
__scatterload_null
0x000000d8: 4654 TF MOV r4,r10 ;r4=0x6ca0
0x000000da: 465d ]F MOV r5,r11 ;r5=0x6cc0
0x000000dc: 42ac .B CMP r4,r5
0x000000de: d101 .. BNE 0xe4 ; __scatterload_null + 12
0x000000e0: f000f824 ..$. BL __rt_entry ; 0x12c,处理为2函数,跳转到该函数
0x000000e4: 467e ~F MOV r6,pc
0x000000e6: 3e0f .> SUBS r6,r6,#0xf
0x000000e8: cc0f .. LDM r4!,{r0-r3}
;1st:
;r0=0x6cc0,r1=0x10000000,r3=0x6e8,r4=0x10f2
;其实就是Region$$Table区域前4行,见Region$$Table$$Base
;2nd:
;r0=0x6eb0,r1=0x100006e8,r3=0x1578,r4=0x104
;其实就是Region$$Table区域后4行,见Region$$Table$$Base

;Region$$Table见下面的Region$$Table$$Base
;

0x000000ea: 46b6 .F MOV lr,r6
0x000000ec: 2601 .& MOVS r6,#1
0x000000ee: 4233 3B TST r3,r6
0x000000f0: d000 .. BEQ 0xf4 ; __scatterload_null + 28
0x000000f2: 1afb .. SUBS r3,r7,r3
0x000000f4: 46a2 .F MOV r10,r4
0x000000f6: 46ab .F MOV r11,r5
0x000000f8: 4333 3C ORRS r3,r3,r6
0x000000fa: 4718 .G BX r3 ;跳转到相应地处理函数
$d
0x000000fc: 00006ba4 .k.. DCD 27556
0x00000100: 00006bc4 .k.. DCD 27588
$t
!!handler_zi
__scatterload_zeroinit
0x00000104: 2300 .# MOVS r3,#0
0x00000106: 2400 .$ MOVS r4,#0
0x00000108: 2500 .% MOVS r5,#0
0x0000010a: 2600 .& MOVS r6,#0
0x0000010c: 3a10 .: SUBS r2,r2,#0x10
0x0000010e: d301 .. BCC 0x114 ; __scatterload_zeroinit + 16
0x00000110: c178 x. STM r1!,{r3-r6}
0x00000112: d8fb .. BHI 0x10c ; __scatterload_zeroinit + 8
0x00000114: 0752 R. LSLS r2,r2,#29
0x00000116: d300 .. BCC 0x11a ; __scatterload_zeroinit + 22
0x00000118: c130 0. STM r1!,{r4,r5}
0x0000011a: d500 .. BPL 0x11e ; __scatterload_zeroinit + 26
0x0000011c: 600b .` STR r3,[r1,#0]
0x0000011e: 4770 pG BX lr
;......

__decompress
__decompress1
__semihosting_library_function
0x000010f2: b570 p. PUSH {r4-r6,lr}
0x000010f4: 188c .. ADDS r4,r1,r2
0x000010f6: 7805 .x LDRB r5,[r0,#0]
;......

Region$$Table$$Base
0x00006ca0: 00006cc0 .l.. DCD 27840 ;flash起始地址0x00006cc0
0x00006ca4: 10000000 .... DCD 268435456 ;ram起始地址0x10000000
0x00006ca8: 000006e8 .... DCD 1768 ;数据长度:000006e8
0x00006cac: 000010f2 .... DCD 4338 ;处理函数:__decompress

0x00006cb0: 00006eb0 .n.. DCD 28336 ;flash起始地址0x00006eb0
0x00006cb4: 100006e8 .... DCD 268437224 ;ram起始地址0x100006e8
0x00006cb8: 00001578 x... DCD 5496 ;数据长度0x00001578
0x00006cbc: 00000104 .... DCD 260 ;处理函数
Region$$Table$$Limit

__decompress函数作用就是将RW数据从rom复制到ram,这些数据在运行过程中会改变,需要在ram中运行。

__scatterload_zeroinit函数的作用是将未初始化的全局变量区域清零。在rom代码区不存在该部分,当程序启动时才会出现在ram区域。Remember, this is the section we put uninitialized static memory in. .bss must be reserved in the memory map, but there is nothing to load, as all variables are initialized to zero.

3、map文件

再来看map文件中关于栈的部分,如下。这里设置heap大小为0,stack大小为0x600,即1536字节。

HEAP              0x10001660   Section        0  startup_lpc11xx.o(HEAP)
STACK             0x10001660   Section     1536  startup_lpc11xx.o(STACK)
Heap_Mem          0x10001660   Data           0  startup_lpc11xx.o(HEAP)
Stack_Mem         0x10001660   Data        1536  startup_lpc11xx.o(STACK)
__initial_sp      0x10001c60   Data           0  startup_lpc11xx.o(STACK)

Code (inc. data)  RO Data   RW Data   ZI Data   Debug   

20824   2016       7016      1768      5496   160460   Grand Totals
20824   2016       7016       496      5496   160460   ELF Image Totals (compressed)
20824   2016       7016       496         0        0   ROM Totals

=========================================================

Total RO  Size (Code + RO Data)                27840 (  27.19kB)
Total RW  Size (RW Data + ZI Data)              7264 (   7.09kB)
Total ROM Size (Code + RO Data + RW Data)      28336 (  27.67kB)

Grand Totals”行显示的RW数据时1768字节长度,为何下面两行是496字节?这可能就是压缩(compressed)的缘故。既然有压缩,那么就要解压缩,也就是上面__decompress函数的作用。

最终的bin文件大小就是28336字节。可以通过生成放的bin文件确认,如下图。

RPQqHA.png

也可以打开bin文件,如下图所示,可以看出文件长度就是0x6eb0(28336).

RPlSgS.png

有些程序会是这样的情况:

RPlY8K.png

这是个演示程序,可以看到RW是8字节。此时的处理函数是__scatterload_copy,与上面所说的__decompress功能并不相同。

1
2
3
4
5
6
7
8
9
10
11
0x000000e8:    cc0f        ..      LDM      r4!,{r0-r3}      
; 1st: r0=300, r1=10000000(IRAM起始地址), r2=8(RW-data), r3=104(__scatterload_copy)
; 2nd:r0=308, r1=10000008, r2=260(ZI-data), r3=120(__scatterload_zeroinit)

!!handler_copy
__scatterload_copy
0x00000104: 3a10 .: SUBS r2,r2,#0x10
0x00000106: d302 .. BCC 0x10e ; __scatterload_copy + 10
0x00000108: c878 x. LDM r0!,{r3-r6}
0x0000010a: c178 x. STM r1!,{r3-r6}
;......
4、空间分布

按照上面的分析,得到下图,其中PAD是填充字节,为了保证对齐。

RPQr1U.png

图中右侧可以理解为程序在rom中的存储分布,而左侧是上电后的数据分布。代码区和只读区位置不变,可以直接运行,RW区在初始化时会复制到0x10000000(ram起始地址)开始的区域,接着是.bss区域、堆区、栈区。

一句话解释:

开机上电后,从Region$$Table区域获取相关参数(数据的源地址、目标地址,长度,处理函数),将RW区域数据解压(复制)到ram区域,接着将.bss区域清零,建立堆栈,之后跳转到用户main函数执行。

5、其它
Reset_Handler                0x00000185   Thumb Code     8  startup_lpc11xx.o(.text)
NMI_Handler                  0x0000018d   Thumb Code     2  startup_lpc11xx.o(.text)
HardFault_Handler            0x0000018f   Thumb Code     2  startup_lpc11xx.o(.text)
SVC_Handler                  0x00000191   Thumb Code     2  startup_lpc11xx.o(.text)
PendSV_Handler               0x00000193   Thumb Code     2  startup_lpc11xx.o(.text)

下图来自bin文件

RPQI1O.png

前4字节时栈指针,即0x1c60,接着4字节时入口函数,地址为0x0185。栈指针与上图中的__initial_sp一致,而0x0185处的函数为Reset_Handler,如上面map文件所示。继续看bin文件,后续的每4字节为一个地址,与各个中断函数地址相对应,具体含义可以看启动文件中的描述,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
__Vectors       DCD     __initial_sp              ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler

;......
DCD CAN_IRQHandler ; 16+13: CAN
DCD SSP1_IRQHandler ; 16+14: SSP1
DCD I2C_IRQHandler ; 16+15: I2C
DCD TIMER16_0_IRQHandler ; 16+16: 16-bit Counter-Timer 0
DCD TIMER16_1_IRQHandler ; 16+17: 16-bit Counter-Timer 1
DCD TIMER32_0_IRQHandler ; 16+18: 32-bit Counter-Timer 0
DCD TIMER32_1_IRQHandler ; 16+19: 32-bit Counter-Timer 1
DCD SSP0_IRQHandler ; 16+20: SSP0
DCD UART_IRQHandler ; 16+21: UART
;......
* 参考

参考网上诸多资料,没办法一一列出,在此一并致谢。