代码重定位
一. 为什么需要代码重定位
1.1. 链接地址和运行地址不同
1.1.1. 只有位置无关编码才能在运行地址和链接地址不同的情况下运行。
a. 位置无关编码(PIC,position independent code):汇编源文件被编码成二进制可执行程序时编码方式与位置(内存地址)无关。
b. 位置有关编码:汇编源码编码成二进制可执行程序后和内存地址是有关的。
c. 大部分指令是位置有关编码
1.1.2. 链接地址
a. 链接时指定的地址(指定方式为:Makefile中用-Ttext,或者链接脚本)
1.1.3. 运行地址
a. 程序实际运行时地址(指定方式:由实际运行时被加载到内存的哪个位置说了算)
1.2. 分析uboot加载
1.2.1. uboot加载流程
a. 先开机上电后BL0运行(s5pv210 IROM固件程序)
b. BL0会加载外部启动设备中的uboot的前16KB(BL1)到SRAM中去运行,
c. BL1运行时会初始化DDR,然后将整个uboot搬运到DDR中,然后用一句长跳转(从SRAM跳转到DDR)指令从SRAM中直接跳转到DDR中继续执行uboot直到uboot完全启动。uboot启动后在uboot命令行中去启动OS。
1.2.2. 分析加载过程
1.2.2.1. 我们分析uboot既要在iram中执行也要在ddr中执行。故uboot的链接地址只有一个,而其运行地址有两个。
1.2.2.2. 解决办法一:
a. uboot链接地址设置到ddr中,
b. uboot在执行 位置有关编码前完成程序的重定位
c. 我使用的uboot采用该方法
1.2.2.3. 解决方法二(分散加载):
a. 把uboot分成2部分(BL1和整个uboot),两部分分别指定不同的链接地址。启动时将两部分加载到不同的地址(BL1加载到SRAM,整个uboot加载到DDR),这时候不用重定位也能启动。
二. 编写测试重定位代码
2.1. 代码说明
a. 在SRAM中将代码从0xd0020010重定位到0xd0024000
b. 此代码并没有重定位到ddr中,我们仅测试,重定位到哪都可以提供测试
c. 本来代码是运行在0xd0020010的,但是因为一些原因我们又希望代码实际是在0xd0024000位置运行的。这时候就需要重定位了
2.2. 测试步骤
2.2.1. 通过链接脚本将代码链接到0xd0024000
2.2.2. dnw下载时将bin文件下载到0xd0020010
2.2.3. 代码执行时通过代码前段的少量位置无关码将整个代码搬移到0xd0024000
2.2.3. 使用一个长跳转跳转到0xd0024000处的代码继续执行,重定位完成
三. 代码分析
3.1. 链接文件
a. 设置代码链接到0xd0024000
b. bss_start记录bss段起始地址
c. bss_end记录代码结束地址
SECTIONS { . = 0xd0024000; .text : { start.o * (.text) } .data : { * (.data) } bss_start = .; .bss : { * (.bss) } bss_end = .; }
3.2. 重定位代码分析
3.2.1. adr与ldr伪指令的区别
3.2.1.1. adr指令
a. adr是短加载,将运行地址加载到寄存器
b. 分析反汇编代码分析该指令
PS: 反编译出来的地址都是链接地址,其实此时运行时_srart的地址是0xd0020010,根据偏移计算,r0= 0xd0020010 + 0x00000000
3.2.1.1. ldr指令
a. ldr是长加载,将链接设置的地址加载到寄存器
b. 分析反汇编代码分析该指令
PS: 反编译出来的地址都是链接地址,其实此时运行时_srart的地址是0xd0020010,但此时r1值是从内存池中直接得到,r1 = 0xd0024000
3.2.2. 真正实现重定位汇编
a. 通过循环完成程序的迁移
// 用汇编来实现的一个while循环 copy_loop: ldr r3, [r0], #4 // 源 str r3, [r1], #4 // 目的 这两句代码就完成了4个字节内容的拷贝 cmp r1, r2 // r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2 bne copy_loop
3.2.3. 清bss段
a. bss_start和bss_end在链接脚本设定了,汇编中可以直接使用
b. 由于bss段都是0故重定位时不用copy bss段
3.2.4. 长跳转
a. ldr pc, =led_blink// ldr指令实现长跳转,跳转到链接地址
b. 分析反汇编代码分析该指令
c. bl led_blink// bl指令实现短跳转,跳转到运行地址下相对应的led_blink地址处
// 清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
/* * 文件名: led.s * 作者: 朱老师 * 描述: 演示重定位(在SRAM内部重定位) */ #define WTCON 0xE2700000 #define SVC_STACK 0xd0037d80 .global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了 _start: // 第1步:关看门狗(向WTCON的bit5写入0即可) ldr r0, =WTCON ldr r1, =0x0 str r1, [r0] // 第2步:设置SVC栈 ldr sp, =SVC_STACK // 第3步:开/关icache mrc p15,0,r0,c1,c0,0; // 读出cp15的c1到r0中 //bic r0, r0, #(1<<12) // bit12 置0 关icache orr r0, r0, #(1<<12) // bit12 置1 开icache mcr p15,0,r0,c1,c0,0; // 第4步:重定位 // adr指令用于加载_start当前运行地址 adr r0, _start // adr加载时就叫短加载 // ldr指令用于加载_start的链接地址:0xd0024000 ldr r1, =_start // ldr加载时如果目标寄存器是pc就叫长跳转,如果目标寄存器是r1等就叫长加载 // 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指令实现长跳转 // 从这里之后就可以开始调用C程序了 //bl led_blink // bl指令实现短跳转 // 汇编最后的这个死循环不能丢 b .
参考《朱老师.1.2ARM裸机课件》