创建时间: | 2022/1/17 17:09 |
更新时间: | 2022/2/22 18:19 |
作者: | gi51wa2j |
标签: | 100ask_IMX6ULL_v11, bingo, 正文 |
段名 | 名称 | 存放的指令 | 是否需要重定位 |
代码段 | .text | 存放代码本身,不会被修改 | 不在链接地址上就需要重定位 |
只读数据段/存放常量数据 | .rodata | 只读变量,一些常量字符串放在这里。可以放在ROM上,不需要复制到内存 | 不在链接地址上就需要重定位 |
数据段 | data | 有初值的,且非const属性的全局变量、static静态局部变量,需要从ROM复制到内存 | 如果不在链接地址上,就需要重定位 |
零初始化段 | .bss/ZI |
| 不需要重定位,因为程序里面 不保存bss段 |
注释段 | .comment | 存放注释 | |
局部变量 | 保存在栈中,运行时生成 | ||
堆 | 一块空闲空间,使用malloc函数来管理它,malloc函数可以自己写 |
想把代码移动到其他位置,这就是代码重定位。
什么叫位置无关码:这段代码扔在任何位置都可以运行,跟它所在的位置无关
怎么写出位置无关码:
跳转:使用相对跳转指令,不能使用绝对跳转指令
只能使用branch指令(比如bl main),不能给PC直接复制,比如ldr pc, =main
不要访问全局变量、静态变量
不使用字符串
也就是复制!
数据复制的三要素:源、目的、长度。
数据保存在哪里?加载地址
数据要复制到哪里?链接地址
长度
这3要素怎么得到?
在GCC中,使用链接脚本来描述。
在keil中,跟链接脚本对应的是散列文件,散列的意思就是"分散排列",在STM32F103这类资源紧缺的单片机芯片中:
代码段保存在Flash上,直接在Flash上运行(当然也可以重定位到内存里)
数据段保存在Flash上,使用前被复制到内存里
但是,在资源丰富的MPU板子上:
内存很大,几十M、几百M,甚至几G
可能没有XIP设备(XIP: eXecute In Place,原地执行)
没有类似STM32F103上的Flash,代码无法在存储芯片上直接运行
基于这些特点,在MPU板子上
代码段、数据段、BSS段等等,运行时没有必要分开存放
重定位时,把整个程序(包括代码段、数据段等),一起复制到它的链接地址去
使用函数地址时用的是"函数的链接地址",所以代码段应该位于链接地址处
去访问全局变量、静态变量时,用的是"变量的链接地址",所以数据段应该位于链接地址处
但是: 程序一开始时可能并没有位于它的"链接地址":
比如对于STM32F103,程序被烧录器烧写在Flash上,这个地址称为"加载地址"
比如对于IMX6ULL/STM32MP157,片内ROM根据头部信息把程序读入内存,这个地址称为“加载地址”
当加载地址 != 链接地址时,就需要重定位。
怎么知道某个段的加载地址、链接地址、长度?
实际上,adr r0, _start指令的本质是r0 = pc - offset,offset是在链接时就确定了。
_start的链接地址在链接时,由链接脚本确定。
比如对于下面的链接脚本,可以使用__bss_start、__bss_end得到BSS段的起始、结束地址:
08 900000: e59fd00c ldr sp, [pc, #12] ; 900014 <halt+0x4>
09 900004: fa00016f blx 9005c8 <copy_data>
10 900008: fb000180 blx 900612 <clean_bss>
11 90000c: e59ff004 ldr pc, [pc, #4] ; 900018 <halt+0x8>
12
13 00900010 <halt>:
14 900010: eafffffe b 900010 <halt>
15 900014: 80200000 eorhi r0, r0, r0
16 900018: 009001b3 ; <UNDEFINED> instruction: 0x009001b3
……
009001b2 <main>:
第9行的blx命令,并不是跳到0x9005c8。这要根据当前的PC值来计算,在dis里写成9005c8,这只是表示“如果程序从0x900000开始运行的话,第9行就会跳到0x9005c8”。现在程序被boot ROM复制到0x80100000,从0x80100000开始运行,我们需要根据机器码来计算出实际跳转的地址。
blx是相对跳转指令,要跳到“pc + offset”这个地址去。程序从0x8010000运行,运行到第9行时,如下计算新地址:
PC=当前地址+8=0x8010004+8=0x801000C
offset=机器码“fa00016f”里的bit[23:0]*4=0x16f*4=0x5BC
新PC=PC + offset = 0x80105C8
此时pc = 0x80100000 + 8 = 0x80100008。
执行到第2条指令“fa00016f”时,根据上述算法,它跳到地址0x80105C8去执行copy_data函数
在执行完copy_data和clean_bss函数后,片内RAM 0x900000上已经有程序了。
执行绝对跳转命令“ldr pc, =main
”,它是一条伪指令,真实指令是“ldr pc, [pc, #4] ; 900018 <halt+0x8>
”:
从dis文件里很容易看出,执行完这条指令后,pc等于dis文件中“900018”上的值“009001b3”,所以程序跳到片内RAM去执行main函数了。
注意:在dis文件中,main函数的链接地址是0x009001b2,往pc寄存器里赋值0x009001b3时,bit0为1,表示main函数的代码是用Thumb指令写的。
那么我们应该如何写位置无关码呢?
答:使用相对跳转命令 b或bl,并注意
重定位之前,不可使用绝对地址
不可访问全局类变量(全局变量或static修饰的局部变量)
不可访问有初始值的数组(初始值放在rodata里,需要绝对地址来访问)
重定位之后,使用ldr pc = xxx,跳转到绝对地址(runtime address)