素材来源:https://blog.csdn.net/zhengyangliu123/article/details/78788815
整理:技术让梦想更伟大 | 李肖遥
笔者来聊聊分散加载的东西。
程序是静态的概念,有数据有代码,都是存在不同的区域,但是进程是动态的概念,主进程在运行的时候,会实际修改对应的数据,还有在上电加载的时候将数据段搬到对应的位置,都是属于运行态,由程序执行来保证。
分散加载会把Code与Data放在指定的区域,保证程序在进入main函数后正常运行,如果有多个Code或者Data的时候,会分别加载到对应的区域,不会直接按照起始地址连着一起加载。
比如上图,在可执行的视图里面,分散加载会找到对于的Code、Data地址,然后加载,对于一些其他段,比如bss段会进行初始化为0的操作。
如果全部按照Code和data这种顺序加载,那在执行视图里面则会出现顺序错误,比如Code3加载到bss1,导致程序执行异常。
本文以STM32的启动为介绍,在介绍分散加载启动之前,介绍一下STM32的启动方式,总共有三种启动方式。
根据选定的启动模式,主闪存存储器、系统存储器或SRAM可以按照以下方式访问:
本文中介绍的是:从主闪存启动,也就是内置的主闪存Flash启动。
上图为 一个简单的STM32 加载与执行视图的绘制,链接脚本指定Code 从0x0800 0000开始,RW ZI 从0x2000 0000开始放置。
LR_IROM1 0x08000000 0x00010000 { ; load region size_region
ER_IROM1 0x08000000 0x00010000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data
.ANY (+RW +ZI)
}
}
12345678910
综述函数的作用
来看看具体的分散加载代码,是如何搬运data 和初始化bss段的。(下文中中断向量表偏移0x10000 偏移64K)
armcc 手册里面介绍:__main 和 __rt_entry 是初始化运行态的环境,以及后面运行APP程序。
通俗点来讲__main函数初始化运行态的环境,主要的功能就是做分散加载将Code位置搬运正确,才能正常运行Code。其作用如下:
手册内容如下:
__user_setup_stackheap
__scatterload_copy
__scatterload_zeroinit
__rt_entry如下图armcc 手册所说:
0x08010188 F000F802 __main: bl 0x8010190 ; __scatterload_rt2
1
0x0801018C F000F83C bl 0x8010208 ; __rt_entry
1
跳到初始化堆栈区域,执行完成之后,跳到main函数。
1 0x08010190 A00A __scatterload_rt2: adr r0,0x80101BC
2 0x08010192 E8900C00 ldm r0,{r10,r11}
3 0x08010196 4482 add r10,r10,r0
4 0x08010198 4483 add r11,r11,r0
5 0x0801019A F1AA0701 sub.w r7,r10,#0x1
12345
第一句adr指令,其作用就是将地址读到寄存器中, 接着,以r0为基地址,读取r0+3464地址的值,将其放到r10,r0+3464+4 的值 ,将其放到r11, 然后,r10+=r0,r11+=r0, r7=r10-1
而 0x08013620 ~ 0x08013640 是一个region表,记录着加载域或者执行域的地址信息,从map文件中也可以看到一些信息,
根据链接脚本信息,RW的起始地址0x2000 0000, 前三个信息:RW 起始地址,数量size,拷贝的函数 后三个信息:ZI 起始地址,数量size,初始化为0的函数
备注学习:ldm 指令,与stm指令是一对,加载指定地址的数据 LDM{cond} mode Rn{!}, reglist{^} 读取Rn 地址中的数据,放到寄存器,并且之后地址自增,再次读取 STM{cond} mode Rn{!}, reglist{^} 以Rn 地址为基地址,将寄存器的值放到基地址内存,并且之后地址自增,再次写入
1 0x0801019E 45DA __scatterload_null: cmp r10,r11
2 0x080101A0 D101 bne 0x80101A6
3 0x080101A2 F000F831 bl 0x8010208 ; __rt_entry
4 0x080101A6 F2AF0E09 adr r14,0x80101A1
5 0x080101AA E8BA000F ldm r10!,{r0-r3}
6 0x080101AE F0130F01 tst r3,#0x1
7 0x080101B2 BF18 it ne
8 0x080101B4 1AFB subne r3,r7,r3
9 0x080101B6 F0430301 orr r3,r3,#0x1
10 0x080101BA 4718 bx r3
11 0x080101BC 00003464 dcd 0x3464
12 0x080101C0 00003484 dcd 0x3484
123456789101112
第一行:比较r10 r11 r10 是region表的首地址,先读三个,后读三个数据,之后就等于r11,直接跳到__rt_entry 第二行:如果不等,跳转到第三行, 第三行:如果相等,则跳到__rt_entry 第四行:则将地址到r14 ,用于返回。第五行:读取RW的地址信息,size 和拷贝函数地址,r0-r3 可以看到region的信息被读出来了。
第六-第十行:将跳转地址转成奇数地址,用于bx指令跳转,080101C4 -> 080101C5,之后跳到拷贝函数。
1 0x080101C4 3A10 __scatterload_copy: subs r2,r2,#0x10
2 0x080101C6 BF24 itt cs
3 0x080101C8 C878 ldmcs r0!,{r3-r6}
4 0x080101CA C178 stmcs r1!,{r3-r6}
5 0x080101CC D8FA bhi 0x80101C4 ; __scatterload_copy
6 0x080101CE 0752 lsls r2,r2,#0x1D
7 0x080101D0 BF24 itt cs
8 0x080101D2 C830 ldmcs r0!,{r4,r5}
9 0x080101D4 C130 stmcs r1!,{r4,r5}
10 0x080101D6 BF44 itt mi
11 0x080101D8 6804 ldrmi r4,[r0]
12 0x080101DA 600C strmi r4,[r1]
13 0x080101DC 4770 bx r14
14 0x080101DE 0000 movs r0,r0
1234567891011121314
r2 是RW data的size 信息,r0 是 RW的起始地址信息,0x08013640,从map信息也可以看到
r0:0801340 r1:20000000地址 第一到第五行:是一个循环语句,每次拷贝16个字节,到RAW区域,即0x2000 0000地址中。对于78个字数据 拷贝70个后,最后八个数字没办法拷贝,不满足CS 第六行:左移29位,将个数清零。r2 = 0-8 = 0xFFFF FFF8 第七行 第八行:拷贝最后八个数据到RAW数据 第十 十一 十二行:不满MI (复数)则直接跳过 如果满足的话,则拷贝最后一个数据, 最后跳转到 cmp r10 r11 ,进行bss 段的初始化。
备注:BHI则表示大于则跳转,看之前文章介绍,ARM学习(2) 寄存器的理解 ===》通用寄存器及状态寄存器IT:if then 分支指令,后面如果满足状态标志位,则执行,否则直接跳过, lsl:左移指令, MI:为负数则执行
1 0x080101E0 2300 __scatterload_zeroinit: movs r3,#0x0
2 0x080101E2 2400 movs r4,#0x0
3 0x080101E4 2500 movs r5,#0x0
4 0x080101E6 2600 movs r6,#0x0
5 0x080101E8 3A10 subs r2,r2,#0x10
6 0x080101EA BF28 it cs
7 0x080101EC C178 stmcs r1!,{r3-r6}
8 0x080101EE D8FB bhi 0x80101E8
9 0x080101F0 0752 lsls r2,r2,#0x1D
10 0x080101F2 BF28 it cs
11 0x080101F4 C130 stmcs r1!,{r4,r5}
12 0x080101F6 BF48 it mi
13 0x080101F8 600B strmi r3,[r1]
14 0x080101FA 4770 bx r14
1234567891011121314
获取到bss段的数据后,可以看到r0-r3更新信息,数据size 为 0x0728个
第一到第八行:同样则是循环语句,每次初始化16个字的数据为0, 第九行:同样左移29位,将数据清零 第十行 到 第 十一行:将最后八个字节数据写入0 第十二行 到第十三行:同上面一致(数据拷贝)
数据初始化完成后,同样跳转到 cmp r10 r11,则相等,跳到 __rt_entry
0x080101FC B51F __rt_lib_init: push {r0-r4,r14}
0x080101FE F003FA09 __rt_lib_init_fp_1: bl 0x8013614 ; _fp_init
0x08010202 BD1F __rt_lib_init_alloca_1: pop {r0-r4,pc}
0x08010204 B510 __rt_lib_shutdown: push {r4,r14}
0x08010206 BD10 __rt_lib_shutdown_cpp_1: pop {r4,pc}
0x08010208 F003F9BE __rt_entry: bl 0x8013588 ; __user_setup_stackheap
0x0801020C 4611 mov r1,r2
0x0801020E F7FFFFF5 __rt_entry_li: bl 0x80101FC ; __rt_lib_init
0x08010212 F000F811 __rt_entry_main: bl 0x8010238 ; main
0x08010216 F003F9F0 bl 0x80135FA ; exit
0x0801021A B403 __rt_exit: push {r0,r1}
0x0801021C F7FFFFF2 __rt_exit_ls: bl 0x8010204 ; __rt_lib_shutdown
0x08010220 BC03 __rt_exit_exit: pop {r0,r1}
0x08010222 F000FAF5 bl 0x8010810 ; _sys_exit
0x08010226 0000 movs r0,r0
12345678910111213141516171819
__rt_enry:进入到初始化堆栈的地方:初始化堆栈的位置,以及SP指针。
之后初始化 fp_init,需要用到p10 协处理器,之后再研究。__rt_entry_main:进入main函数。则分散加载完成。
0801 360C 4800 __user_libspace: ldr r0,0x8013610 ; r0,=__libspace_start
0801 360E 4770 bx r14
0801 3610 20000140 dcd 0x20000140 ; __libspace_start
123
libspace_start:lib库 空间使用 栈空间,在初始化堆栈空间的时候。
1 08013588 4675 __user_setup_stackheap: mov r5,r14
2 0801358A F000F83F bl 0x801360C ; __user_libspace
3 0801358E 46AE mov r14,r5
4 08013590 0005 movs r5,r0
5 08013592 4669 mov r1,r13
6 08013594 4653 mov r3,r10
7 08013596 F0200007 bic r0,r0,#0x7
8 0801359A 4685 mov r13,r0
9 0801359C B018 add sp,sp,#0x60
10 0801359E B520 push {r5,r14}
11 080135A0 F7FDFA3C bl 0x8010A1C ; __user_initial_stackheap
12 080135A4 E8BD4020 pop {r5,r14}
13 080135A8 F04F0600 mov.w r6,#0x0
14 080135AC F04F0700 mov.w r7,#0x0
15 080135B0 F04F0800 mov.w r8,#0x0
16 080135B4 F04F0B00 mov.w r11,#0x0
17 080135B8 F0210107 bic r1,r1,#0x7
18 080135BC 46AC mov r12,r5
19 080135BE E8AC09C0 stm r12!,{r6-r8,r11}
20 080135C2 E8AC09C0 stm r12!,{r6-r8,r11}
21 080135C6 E8AC09C0 stm r12!,{r6-r8,r11}
22 080135CA E8AC09C0 stm r12!,{r6-r8,r11}
23 080135CE 468D mov r13,r1
24 080135D0 4770 bx r14
123456789101112131415161718192021222324
第八行 到 第十二行:r13 = 20000140 加 0x60之后,正好指向 lib库的地址的栈顶。2000 01A0. 第十八行:r12 = 2000 0140,后面初始化 lib 库 后 变成 r12 = 2000 0180 第二十三行:r13 指向栈顶 2000 07A0
可以看到向量表的第一个地址也是 2000 07A0,符合预期。
__user_initial_stackheap
LDR R0, = Heap_Mem
08010A1C 4804 __user_initial_stackheap: ldr r0,0x8010A30
LDR R1, =(Stack_Mem + Stack_Size)
08010A1E 4905 ldr r1,0x8010A34
LDR R2, = (Heap_Mem + Heap_Size)
08010A20 4A05 ldr r2,0x8010A38
LDR R3, = Stack_Mem
08010A22 4B06 ldr r3,0x8010A3C
BX LR
08010A24 4770 bx r14
08010A26 0000 dcw 0x0
08010A28 08010371 dcd 0x8010371 ; SystemInit
08010A2C 08010189 dcd 0x8010189 ; __main
08010A30 200001A0 dcd 0x200001A0 ; Heap_Mem
08010A34 200007A0 dcd 0x200007A0 ; __initial_sp
08010A38 200003A0 dcd 0x200003A0 ; Stack_Mem
08010A3C 200003A0 dcd 0x200003A0 ; Stack_Mem
123456789101112131415161718
初始化堆栈的地址 开始位置,将其存储到 r0- r3中。
来看看armcc 手册上面是怎么介绍堆栈初始化函数的:
版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。
‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧ END ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧