Table of Contents
1、代码启动过程简要分析
2、存储地址,运行地址,链接地址
2.1、存储地址
2.2、链接地址
2.3、运行地址
2.4、链接地址和运行地址的区别
3、位置有关码,位置无关码详细解释
4、代码重定位
4.1、重定位代码详解
1、代码启动过程简要分析问题1:代码下载到哪里去?
数据手册上会有说明,位置不是我们想往哪里下载就往哪里下载的,下载错了启动的时候就找不到代码的位置在哪,一般是下载到如下存储设备中:1、内部自带的FLASH, 2、外扩NANDFLASH(EMMC), 3、外扩NORFLASH, 4、SD卡,(存储地址)
1、大部分单片机(stm32)是下载到内部自带FLASH中,起始地址是严格指定的(如0x08000000)
2、大部分运行安卓或者linux的ARM一般是下载到SD卡或者外扩的FLASH(EMMC)中,然而在SD卡或者EMMC中的起始位置是严格规定的(比如第0块开始存放,或者跳过一块,从第1块位置开始存放)
问题2:从哪个位置去加载代码,加载到哪里去运行?
1、以上存储设备分两种,一种是可以直接运行代码的存储设备(内部自带FLASH、外扩NORFLASH),一种是不能直接寻址运行代码的设备(SD卡,NANDFLASH、EMMC)
2、一般芯片都有启动引脚,我们需要根据我们下载代码的位置来正确配置启动引脚的高低电平,这样上电才能正确启动代码
3、对于可以直接运行代码的存储设备,前期不需要加载代码,而是直接从该设备的起始位置运行代码,至于后期是否需要加载代码到其它位置运行就看程序员的期望运行地址(链接脚本里面编写的链接地址),如果没啥期望(用编译软件默认的链接地址,而编译软件默认的链接地址和实际运行地址都是一样的)那就不需要做任何拷贝代码动作,代码从头跑到尾(stm32等单片机之类的芯片就是这种情况),如果程序员希望一部分代码在其他位置运行,就需要修改后期代码的链接地址而不是一直系统默认地址,在前期代码中把后期代码拷贝到理想位置,然后长跳转到理想位置去运行(此情况如下:我们有时候觉得FLASH速度太慢,需要把代码拷贝到RAM中运行)(代码重定位)
4、对于不可以直接运行代码的存储设备,前期芯片内部的固件会把一部分代码加载到默认的内存位置(内部RAM)运行,为什么是一部分呢,因为此情况一般是代码很大(比如跑linux系统)内部RAM资源有限,所以只能加载一部分代码到内部RAM,另外一部分大多数代码就需要加载到外扩的RAM中运行,所以程序员自己编写链接脚本(链接地址)希望把后面的一部分代码能加载到他期望的地址去运行,并且程序员编写前期运行的代码(位置无关码)中包含拷贝函数,拷贝函数把后一部分代码拷贝到理想位置(代码重定位),在运行完前期代码后实现长跳转,跳转到理想的链接位置去运行
总结:
从哪里加载代码根据启动引脚指定,加载到哪里运行代码分前期和后期,前期运行基本都是加载到默认地方运行,后期代码根据自己需求位置来加载运行(大部分单片机还是系统默认,大部分跑大型系统的ARM后期需要进行代码重定位)
2、存储地址,运行地址,链接地址 2.1、存储地址就是代码下载到存储器(FLASH)的位置
2.2、链接地址这个地址就是链接脚本里面指定的代码的运行位置(程序员期望的运行地址)
比如我们希望代码将来加载到我们外扩的内存中运行(而不是一直在刚上电系统指定的位置运行)
2.3、运行地址就是当前程序正在运行的地址(PC)
理论来讲我们可以把代码加载到内存的任何位置然后跳转到相应位置运行,至于会不会出问题,就看我们的链接地址是否和当前运行地址是否一致并且运行的是位置有关码还是位置无关码,如果地址不一致且是位置有关码可能会出现错误(看后面的位置有关码和无关码的解释)
单片机之类的芯片(stm32)
大部分哈弗结构单片机(包括stm32)是把不需要另外加载代码到RAM中运行的,上电是直接从我们下载代码的位置(FLASH)运行
一般单片机对于代码来讲:存储地址 = 链接地址 = 运行地址
2.4、链接地址和运行地址的区别反汇编看出的其实就是我们的链接地址,而实际运行地址就看内部代码怎么加载代码,加载到哪里去运行,
假设我们的链接地址是0x33f800000如果我们把代码加载到0x00000000地址运行实际如图所示:
机器代码里面包含了变量和函数的地址,到底是根据绝对链接地址取值或者是跳转还是根据PC当前位置加偏移地址取值或者跳转分为位置有关码和位置无关码:
1、按我的理解就是,位置无关码就是这条代码和绝对链接地址无关,跳转或者取值所需地址都是当前PC+偏移地址,这样总是能找到正确位置,所以里面的地址是相对地址,也可以理解为动态地址,运行地址改变那这个地址也改变,所谓你变我也变
2、而位置有关码里面是绝对链接地址,每次根据链接地址去取值,如果变量或者是函数正好在链接地址处存放,那么可以正确找到位置实现跳转或者是取值,如果变量或者函数被加载到ram的位置和我们链接的地址位置不一样,那么去链接地址取值或者跳转肯定会出现错误
问题1:链接地址一定要和运行地址要相等吗?
回答:按前面的分析,前期代码(一般是些基本的硬件初始化代码,如果需要重定位还包括拷贝函数代码)运行地址和链接地址可以不相等后期代码的链接地址必须要等于运行地址,也就是说后期代码当前的实际运行地址和链接地址不相等就有可能出错,因为:前期代码大部分是位置无关码,而位置无关码无论加载到RAM中的什么位置都可以正常运行,后期的代码中有些代码是位置有关码,位置有关码的实际运行地址和我们的期望运行地址(链接地址)不一样就有可能出错,
举两个例子说明一下:
1、比如在一个函数中读取一个全局变量的值,读取时会根据变量的链接地址而不是实际存放地址去取值,如果实际存放的地址=链接地址那么取到的值肯定是正确的,如果链接地址和实际的存放地址不一样那么取出的值肯定是错误的。
2、再比如:一个c语言函数调用另外c语言一个函数,如果两个函数的运行地址相差很大(大于32M)编译器就会生成位置有关码的跳转指令跳转到那个被调用的函数位置,如果那个函数的此时不在我们链接指定的位置处,那么跳转肯定出现错误
总结:前期代码运行地址可以和链接地址不一样,后期代码运行地址必须和链接地址一样
3、位置有关码,位置无关码详细解释点击进入:位置有关码和位置无关码详细解释
4、代码重定位简述:我们希望后期代码在我们指定的位置运行(比如为了加快代码运行速度,把代码从FLASH上面加载到外部RAM中运行),我们会在前期代码中用位置无关码拷贝代码到链接地址处,然后长跳转到链接地址处运行,这样一个过程就是代码重定位
问题1:为什么一开始不直接把代码加载到RAM运行呢?
回答:一般我们外扩的RAM是需要通过代码初始化才能正常工作的,所以前期是不能直接加载到RAM中运行的
问题2:stm32可不可以偏要把代码放到内部RAM运行呢?
回答:肯定是可以的,但是要设置好链接地址让编译器按你的链接地址编译,并且在前期编写拷贝函数把代码拷贝到链接地址(RAM)中运行
4.1、重定位代码详解
重定位汇编代码:
// adr指令用于加载_start当前运行地址,它是位置无关码,所以加载的不是链接地址
adr r0, _start // adr加载时就叫短加载
ldr r1, =_start // 位置有关码,加载的是_start的链接地址
// bss段的起始地址
ldr r2, =bss_start // 就是我们重定位代码的结束地址,重定位只需重定位代码段和数据段即可
cmp r0, r1 // 比较_start的运行时地址和链接地址是否相等
beq clean_bss // 如果相等说明不需要重定位,所以跳过copy_loop,直接到clean_bss
// 如果不相等说明需要重定位,那么直接执行下面的copy_loop进行重定位
// 重定位完成后继续执行clean_bss。
// 用汇编来实现的一个while循环
copy_loop:
ldr r3, [r0], #4 // 源
str r3, [r1], #4 // 目的 这两句代码就完成了4个字节内容的拷贝
cmp r1, r2 // r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2
bne copy_loop
// 清bss段,其实就是在链接地址处把bss段全部清零
clean_bss:
ldr r0, =bss_start
ldr r1, =bss_end
cmp r0, r1 // 如果r0等于r1,说明bss段为空,直接下去
beq run_on_dram // 清除bss完之后的地址
mov r2, #0
clear_loop:
str r2, [r0], #4 // 先将r2中的值放入r0所指向的内存地址(r0中的值作为内存地址),
cmp r0, r1 // 然后r0 = r0 + 4
bne clear_loop
run_on_dram:
// 长跳转到led_blink开始第二阶段
ldr pc, =led_blink // 位置有关码, ldr指令实现长跳转
// 汇编最后的这个死循环不能丢
b .
问题1:不是说前期代码是位置无关码,怎么里面还出现了位置有关码,不是会出错吗?
答:运行位置有关码只是有可能会出错,不是一定会出错,具体看你怎么用这个地址,像上面的用途只是用来运算比较使用,如果在没有代码重定位之前你用链接地址去取变量的值或者进行函数长跳转,那肯定会出错,因为重定位代码之前后期代码还没有拷贝到我们的链接地址处
作者:shenlong1356