0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

聊聊程序分散加载启动的奥秘

技术让梦想更伟大 来源:CSDN技术社区 作者:张一西 2022-12-09 11:37 次阅读

笔者来聊聊分散加载的东西。

1、什么是分散加载

程序是静态的概念,有数据有代码,都是存在不同的区域,但是进程是动态的概念,主进程在运行的时候,会实际修改对应的数据,还有在上电加载的时候将数据段搬到对应的位置,都是属于运行态,由程序执行来保证。

分散加载会把Code与Data放在指定的区域,保证程序在进入main函数后正常运行,如果有多个Code或者Data的时候,会分别加载到对应的区域,不会直接按照起始地址连着一起加载。

09464d5c-76f1-11ed-8abf-dac502259ad0.png
比如上图,在可执行的视图里面,分散加载会找到对于的Code、Data地址,然后加载,对于一些其他段,比如bss段会进行初始化为0的操作。

如果全部按照Code和data这种顺序加载,那在执行视图里面则会出现顺序错误,比如Code3加载到bss1,导致程序执行异常

2、分散加载的作用

2.1 ARMCC 编译器分散加载代码

本文以STM32的启动为介绍,在介绍分散加载启动之前,介绍一下STM32的启动方式,总共有三种启动方式。09a231d0-76f1-11ed-8abf-dac502259ad0.png
根据选定的启动模式,主闪存存储器、系统存储器或SRAM可以按照以下方式访问:

从主闪存存储器启动:主闪存存储器被映射到启动空间(0x0000 0000),但仍然能够在它原有的地址(0x0800 0000)访问它,即闪存存储器的内容可以在两个地址区域访问, 0x00000000或0x0800 0000。

从系统存储器启动:系统存储器被映射到启动空间(0x0000 0000),但仍然能够在它原有的地址(互联型产品原有地址为0x1FFF B000,其它产品原有地址为0x1FFF F000)访问它。

从内置SRAM启动:只能在0x2000 0000开始的地址区访问SRAM。当从内置SRAM启动,在应用程序的初始化代码中,必须使用NVIC的异常表和偏移寄存器,从新映射向量表到SRAM。

本文中介绍的是:从主闪存启动,也就是内置的主闪存Flash启动。0a273b46-76f1-11ed-8abf-dac502259ad0.png
上图为 一个简单的STM32 加载与执行视图的绘制,链接脚本指定Code 从0x0800 0000开始,RW ZI 从0x2000 0000开始放置。

LR_IROM10x080000000x00010000{;loadregionsize_region
ER_IROM10x080000000x00010000{;loadaddress=executionaddress
*.o(RESET,+First)
*(InRoot$$Sections)
.ANY(+RO)
}
RW_IRAM10x200000000x00020000{;RWdata
.ANY(+RW+ZI)
}
}
12345678910

左边加载视图静态的Code和Data放置方式,比如download的时候 两者把axf 解析成bin文件,然后烧录到nor flash中,可以看到其实静态放置的位置 关系不是很大,主要是执行的时候 位置正确就行 ,因为Code中有绝对地址,不然PC跑飞。

执行视图即程序正常运行的时候 Code或者Data放置的位置。

烧录的位置程序执行的位置不同,分散加载 负责讲其加载到对应位置,保证main 函数执行正常

图中BSS段初始化为0 或者未初始化的全局变量,不占用ImageSIze(bin文件大小),所以加载视图中并没有其,执行视图必须有,上电的时候会将这部分初始化为0。

综述函数的作用

来看看具体的分散加载代码,是如何搬运data 和初始化bss段的。(下文中中断向量表偏移0x10000 偏移64K)

0a4c2280-76f1-11ed-8abf-dac502259ad0.png
armcc 手册里面介绍:__main__rt_entry 是初始化运行态的环境,以及后面运行APP程序。

通俗点来讲__main函数初始化运行态的环境,主要的功能就是做分散加载将Code位置搬运正确,才能正常运行Code。其作用如下:

将section 拷贝到对应的执行域地址执行,(把RO RW从加载域拷贝到执行域,如果有压缩的Section 会进行解压缩并进行拷贝)

还有bss 段的初始化,将其初始化为0,

之后跳到__rt_entry。

以及堆栈的初始化,

lib库的初始化

跳到对应的用户程序(main)。

main函数结束后,调用exit函数。

手册内容如下:
0a668012-76f1-11ed-8abf-dac502259ad0.png

__user_setup_stackheap

初始化堆栈地址,以及SP指针位置

__scatterload_copy

主要是RW data的拷贝

__scatterload_zeroinit

主要是ZI data的初始化

__rt_entry如下图armcc 手册所说:

建立堆栈

初始化C库(方便固件使用C库)

调用main函数

关闭C库

离开
0a858a66-76f1-11ed-8abf-dac502259ad0.png

启动代码的简单介绍

0x08010188F000F802__main:bl0x8010190;__scatterload_rt2
1
0x0801018CF000F83Cbl0x8010208;__rt_entry
1

跳到初始化堆栈区域,执行完成之后,跳到main函数。

10x08010190A00A__scatterload_rt2:adrr0,0x80101BC
20x08010192E8900C00ldmr0,{r10,r11}
30x080101964482addr10,r10,r0
40x080101984483addr11,r11,r0
50x0801019AF1AA0701sub.wr7,r10,#0x1
12345

第一句adr指令,其作用就是将地址读到寄存器中,接着,以r0为基地址,读取r0+3464地址的值,将其放到r10,r0+3464+4 的值 ,将其放到r11,然后,r10+=r0,r11+=r0,r7=r10-10a99361a-76f1-11ed-8abf-dac502259ad0.png
而 0x08013620 ~ 0x08013640 是一个region表,记录着加载域或者执行域的地址信息,从map文件中也可以看到一些信息,0aa9e60e-76f1-11ed-8abf-dac502259ad0.png
根据链接脚本信息,RW的起始地址0x2000 0000,前三个信息:RW 起始地址,数量size,拷贝的函数后三个信息:ZI 起始地址,数量size,初始化为0的函数0ac24906-76f1-11ed-8abf-dac502259ad0.png

备注学习:ldm 指令,与stm指令是一对,加载指定地址的数据LDM{cond} mode Rn{!}, reglist{^} 读取Rn 地址中的数据,放到寄存器,并且之后地址自增,再次读取STM{cond} mode Rn{!}, reglist{^} 以Rn 地址为基地址,将寄存器的值放到基地址内存,并且之后地址自增,再次写入

10x0801019E45DA__scatterload_null:cmpr10,r11
20x080101A0D101bne0x80101A6
30x080101A2F000F831bl0x8010208;__rt_entry
40x080101A6F2AF0E09adrr14,0x80101A1
50x080101AAE8BA000Fldmr10!,{r0-r3}
60x080101AEF0130F01tstr3,#0x1
70x080101B2BF18itne
80x080101B41AFBsubner3,r7,r3
90x080101B6F0430301orrr3,r3,#0x1
100x080101BA4718bxr3
110x080101BC00003464dcd0x3464
120x080101C000003484dcd0x3484
123456789101112

第一行:比较r10 r11 r10 是region表的首地址,先读三个,后读三个数据,之后就等于r11,直接跳到__rt_entry第二行:如果不等,跳转到第三行,第三行:如果相等,则跳到__rt_entry第四行:则将地址到r14 ,用于返回。第五行:读取RW的地址信息,size 和拷贝函数地址,r0-r3 可以看到region的信息被读出来了。
0ad7b75a-76f1-11ed-8abf-dac502259ad0.png
第六-第十行:将跳转地址转成奇数地址,用于bx指令跳转,080101C4 -> 080101C5,之后跳到拷贝函数。
0ae84264-76f1-11ed-8abf-dac502259ad0.png

10x080101C43A10__scatterload_copy:subsr2,r2,#0x10
20x080101C6BF24ittcs
30x080101C8C878ldmcsr0!,{r3-r6}
40x080101CAC178stmcsr1!,{r3-r6}
50x080101CCD8FAbhi0x80101C4;__scatterload_copy
60x080101CE0752lslsr2,r2,#0x1D
70x080101D0BF24ittcs
80x080101D2C830ldmcsr0!,{r4,r5}
90x080101D4C130stmcsr1!,{r4,r5}
100x080101D6BF44ittmi
110x080101D86804ldrmir4,[r0]
120x080101DA600Cstrmir4,[r1]
130x080101DC4770bxr14
140x080101DE0000movsr0,r0
1234567891011121314

r2 是RW data的size 信息,r0 是 RW的起始地址信息,0x08013640,从map信息也可以看到0af768f2-76f1-11ed-8abf-dac502259ad0.png
r0:0801340r1: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:为负数则执行

10x080101E02300__scatterload_zeroinit:movsr3,#0x0
20x080101E22400movsr4,#0x0
30x080101E42500movsr5,#0x0
40x080101E62600movsr6,#0x0
50x080101E83A10subsr2,r2,#0x10
60x080101EABF28itcs
70x080101ECC178stmcsr1!,{r3-r6}
80x080101EED8FBbhi0x80101E8
90x080101F00752lslsr2,r2,#0x1D
100x080101F2BF28itcs
110x080101F4C130stmcsr1!,{r4,r5}
120x080101F6BF48itmi
130x080101F8600Bstrmir3,[r1]
140x080101FA4770bxr14
1234567891011121314

获取到bss段的数据后,可以看到r0-r3更新信息,数据size 为 0x0728个
0b0972ae-76f1-11ed-8abf-dac502259ad0.png
第一到第八行:同样则是循环语句,每次初始化16个字的数据为0,第九行:同样左移29位,将数据清零第十行 到 第 十一行:将最后八个字节数据写入0第十二行 到第十三行:同上面一致(数据拷贝)

数据初始化完成后,同样跳转到 cmp r10 r11,则相等,跳到 __rt_entry

0x080101FCB51F__rt_lib_init:push{r0-r4,r14}
0x080101FEF003FA09__rt_lib_init_fp_1:bl0x8013614;_fp_init
0x08010202BD1F__rt_lib_init_alloca_1:pop{r0-r4,pc}
0x08010204B510__rt_lib_shutdown:push{r4,r14}
0x08010206BD10__rt_lib_shutdown_cpp_1:pop{r4,pc}

0x08010208F003F9BE__rt_entry:bl0x8013588;__user_setup_stackheap
0x0801020C4611movr1,r2
0x0801020EF7FFFFF5__rt_entry_li:bl0x80101FC;__rt_lib_init

0x08010212F000F811__rt_entry_main:bl0x8010238;main

0x08010216F003F9F0bl0x80135FA;exit
0x0801021AB403__rt_exit:push{r0,r1}
0x0801021CF7FFFFF2__rt_exit_ls:bl0x8010204;__rt_lib_shutdown
0x08010220BC03__rt_exit_exit:pop{r0,r1}

0x08010222F000FAF5bl0x8010810;_sys_exit
0x080102260000movsr0,r0
12345678910111213141516171819

__rt_enry:进入到初始化堆栈的地方:初始化堆栈的位置,以及SP指针。

之后初始化 fp_init,需要用到p10 协处理器,之后再研究。__rt_entry_main:进入main函数。则分散加载完成。

0801360C4800__user_libspace:ldrr0,0x8013610;r0,=__libspace_start
0801360E4770bxr14
0801361020000140dcd0x20000140;__libspace_start
123

libspace_start:lib库 空间使用 栈空间,在初始化堆栈空间的时候。0b1bd9c6-76f1-11ed-8abf-dac502259ad0.png

1080135884675__user_setup_stackheap:movr5,r14
20801358AF000F83Fbl0x801360C;__user_libspace
30801358E46AEmovr14,r5
4080135900005movsr5,r0
5080135924669movr1,r13
6080135944653movr3,r10
708013596F0200007bicr0,r0,#0x7
80801359A4685movr13,r0
90801359CB018addsp,sp,#0x60
100801359EB520push{r5,r14}
11080135A0F7FDFA3Cbl0x8010A1C;__user_initial_stackheap
12080135A4E8BD4020pop{r5,r14}
13080135A8F04F0600mov.wr6,#0x0
14080135ACF04F0700mov.wr7,#0x0
15080135B0F04F0800mov.wr8,#0x0
16080135B4F04F0B00mov.wr11,#0x0
17080135B8F0210107bicr1,r1,#0x7
18080135BC46ACmovr12,r5
19080135BEE8AC09C0stmr12!,{r6-r8,r11}
20080135C2E8AC09C0stmr12!,{r6-r8,r11}
21080135C6E8AC09C0stmr12!,{r6-r8,r11}
22080135CAE8AC09C0stmr12!,{r6-r8,r11}
23080135CE468Dmovr13,r1
24080135D04770bxr14
123456789101112131415161718192021222324

第八行 到 第十二行:r13 = 20000140 加 0x60之后,正好指向 lib库的地址的栈顶。2000 01A0.第十八行:r12 = 2000 0140,后面初始化 lib 库 后 变成 r12 = 2000 0180第二十三行:r13 指向栈顶 2000 07A0

0b300f40-76f1-11ed-8abf-dac502259ad0.png
可以看到向量表的第一个地址也是 2000 07A0,符合预期。
0b431edc-76f1-11ed-8abf-dac502259ad0.png

__user_initial_stackheap
LDRR0,=Heap_Mem
08010A1C4804__user_initial_stackheap:ldrr0,0x8010A30
LDRR1,=(Stack_Mem+Stack_Size)
08010A1E4905ldrr1,0x8010A34
LDRR2,=(Heap_Mem+Heap_Size)
08010A204A05ldrr2,0x8010A38
LDRR3,=Stack_Mem
08010A224B06ldrr3,0x8010A3C
BXLR
08010A244770bxr14
08010A260000dcw0x0
08010A2808010371dcd0x8010371;SystemInit
08010A2C08010189dcd0x8010189;__main
08010A30200001A0dcd0x200001A0;Heap_Mem
08010A34200007A0dcd0x200007A0;__initial_sp
08010A38200003A0dcd0x200003A0;Stack_Mem
08010A3C200003A0dcd0x200003A0;Stack_Mem
123456789101112131415161718

0b700cf8-76f1-11ed-8abf-dac502259ad0.png
初始化堆栈的地址 开始位置,将其存储到 r0- r3中。

可以看到R0、R1和R2 分别存放的是堆的基地址、栈的基地址(栈顶)和堆的极限地址(与arm cc手册说的一致。)
0b838896-76f1-11ed-8abf-dac502259ad0.png
来看看armcc 手册上面是怎么介绍堆栈初始化函数的:

__user_initial_stackheap 函数将栈基地址(栈顶)值放在r1中,可以从上面寄存器看到,确实寄存器r1存放的是栈顶的地址。

__user_setup_stackheap 初始化sp的地址为栈顶地址(上面的代码中第二十三行)

__user_initial_stackheap 之所以可以使用C语言来编写,是因为 __user_setup_stackheap 提供了一个 临时的栈,(手册可能比较老,和代码有点出入。)0bd42b52-76f1-11ed-8abf-dac502259ad0.png

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 程序
    +关注

    关注

    114

    文章

    3631

    浏览量

    79546
  • Code
    +关注

    关注

    0

    文章

    65

    浏览量

    15275
  • 编译器
    +关注

    关注

    1

    文章

    1577

    浏览量

    48621
  • 分散加载
    +关注

    关注

    0

    文章

    3

    浏览量

    1414

原文标题:聊聊程序分散加载启动的奥秘

文章出处:【微信号:技术让梦想更伟大,微信公众号:技术让梦想更伟大】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    使用分散加载将部分程序放到RAM,RAM掉电后数据就没有了,如何复原?

    我使用分散加载将部分程序放到RAM,RAM掉电后数据就没有了,重新上电后,芯片是如何将RAM区程序复原的呢。
    发表于 03-06 07:01

    ARM分散加载及应用

    从ARM ELF目标文件主要构成出发,详细介绍了分散加载的基本原理、分散加载文件的语法、分散加载
    发表于 05-04 16:09

    调试启动加载程序启动

    使用IPE工具和t加载旧版本的引导加载程序(v05)。母鸡从SD卡加载应用程序,然后这个应用程序
    发表于 04-13 09:58

    分享下载算法设计背后的奥秘

    RT-UFL 项目开发过程所有疑难点及其解决方法,和大家分享下载算法设计背后的奥秘。  本篇是开发笔记第一篇,咱们重点聊聊这个项目...
    发表于 12-21 07:19

    什么是分散加载文件?

    分散加载的作用是什么?什么是分散加载文件?
    发表于 02-16 06:48

    FAQ0086 Eclipse中实现分散加载的方法

    Eclipse中实现分散加载的方法如何在Eclipse 中实现分散加载
    发表于 10-20 06:05

    RT1052 分散加载问题

    我在分散加载中是这样配置的: 目的是把代码拆开,存储在flash的不同扇区。 但我发现上电后函数RunPro的代码没有从nor flash成功加载到SDRAM,所以单片机运行到函数RunPro就死机
    发表于 11-08 18:40

    基于NXP LPC2000的次级启动加载程序解析

    LPC1300/1700等系列。 在大多数的LPC2000器件内部,存在着一个被称为主启动加载程序(Primary Boot Loader)的固件,它在每次上电或复位时被首先运行。本文所讲的次级
    发表于 10-30 11:13 1次下载
    基于NXP LPC2000的次级<b class='flag-5'>启动</b><b class='flag-5'>加载</b><b class='flag-5'>程序</b>解析

    GD32单片机程序分散加载的方法

    本文档内容介绍了GD32单片机程序分散加载的方法,图像详解,供参考。
    发表于 11-22 11:02 38次下载
    GD32单片机<b class='flag-5'>程序</b><b class='flag-5'>分散</b><b class='flag-5'>加载</b>的方法

    ARM分散加载文件

    分散加载作用:可以将代码放入不同的存储空间。1.基本概念了解分散加载文件之前,首先需要了解Code、RO-Data、RW-Data、ZI-Data。Code:
    发表于 12-20 19:10 7次下载
    ARM<b class='flag-5'>分散</b><b class='flag-5'>加载</b>文件

    什么是分散加载文件?何时进行分散加载

    分散加载文件(scatter file)是一个文本文件,它的作用是可以用于描述 ARM 链接器生成映像文件所需要的信息。
    的头像 发表于 09-14 10:38 2352次阅读

    什么是分散加载文件?

    分散加载文件(scatter file)是一个文本文件,它的作用是可以用于描述 ARM 链接器生成映像文件所需要的信息。 如果不使用 scatter file 文件来指定,那么 ARM 链接
    的头像 发表于 01-30 14:12 1626次阅读
    什么是<b class='flag-5'>分散</b><b class='flag-5'>加载</b>文件?

    AN024 GD32F4xx_Keil分散加载说明

    AN024 GD32F4xx_Keil分散加载说明
    发表于 02-27 18:25 1次下载
    AN024 GD32F4xx_Keil<b class='flag-5'>分散</b><b class='flag-5'>加载</b>说明

    AN032GD32F4xx_IAR分散加载说明

    AN032 GD32F4xx_IAR分散加载说明
    发表于 02-27 18:26 0次下载
    AN032GD32F4xx_IAR<b class='flag-5'>分散</b><b class='flag-5'>加载</b>说明

    车规MCU的启动加载程序是什么

    启动加载程序(bootloader) 车规MCU的启动加载程序(bootloader)是一种用于
    的头像 发表于 10-27 17:26 1087次阅读