1.5.8.代码重定位实战1 1.5.8.1、任务:在SRAM中将代码从0xd0020010重定位到0xd0024000 任务解释:本来代码是运行在0xd0020010的,但是因为一些原因我们又希望代码实际是在0xd0024000位置运行的。这时候就需要重定位了。 注解:本练习对代码本身运行无实际意义,我们做这个重定位纯粹是为了练习重定位技能。但是某些情况重定位就是必须的,譬如在uboot中。 1.5.8.2、思路: 第一点:通过链接脚本将代码链接到0xd0024000 第二点:dnw下载时将bin文件下载到0xd0020010 第一点加上第二点,就保证了:代码实际下载运行在0xd0020010,但是却被链接在0xd0024000。从而为重定位奠定了基础。 当我们把代码链接地址设置为0xd0024000时,实际隐含意思就是我这个代码将来必须放在0xd0024000位置才能正确执行。如果实际运行地址不是这个地址就要出事(除非代码是PIC位置无关码),当以上都明白了后,就知道重定位代码的作用就是:在PIC执行完之前(在代码中第一句位置有关码执行之前)必须将整个代码搬移到0xd0024000位置去执行,这就是重定位。 第三点:代码执行时通过代码前段的少量位置无关码将整个代码搬移到0xd0024000 第四点:使用一个长跳转跳转到0xd0024000处的代码继续执行,重定位完成 长跳转:首先这句代码是一句跳转指令(ARM中的跳转指令就是类似于分支指令B、BL等作用的指令),跳转指令通过给PC(r15)赋一个新值来完成代码段的跳转执行。长跳转指的是跳转到的地址和当前地址差异比较大,跳转的范围比较宽广。 当我们执行完代码重定位后,实际上在SRAM中有2份代码的镜像(一份是我们下载到0xd0020010处开头的,另一份是重定位代码复制到0xd0024000处开头的),这两份内容完全相同,仅仅地址不同。重定位之后使用ldr pc, =led_blink这句长跳转直接从0xd0020010处代码跳转到0xd0024000开头的那一份代码的led_blink函数处去执行。(实际上此时在SRAM中有2个led_blink函数镜像,两个都能执行,如果短跳转bl led_blink则执行的就是0xd0020010开头的这一份,如果长跳转ldr pc, =led_blink则执行的是0xd0024000开头处的这一份)。这就是短跳转和长跳转的区别。
当链接地址和运行地址相同时,短跳转和长跳转实际效果是一样的;但是当链接地址不等于运行地址时,短跳转和长跳转就有差异了。这时候短跳转实际执行的是运行地址处的那一份,而长跳转执行的是链接地址处那一份。
总结:重定位实际就是在运行地址处执行一段位置无关码PIC,让这段PIC(也就是重定位代码)从运行地址处把整个程序镜像拷贝一份到链接地址处,完了之后使用一句长跳转指令从运行地址处直接跳转到链接地址处去执行同一个函数(led_blink),这样就实现了重定位之后的无缝连接。 1.5.8.3、链接脚本分析讲解
Makefile:
led.bin: start.o led.o //原来代码:arm-linux-ld -Ttext 0x0 -o led.elf $^ arm-linux-ld -Tlink.lds -o led.elf $^ arm-linux-objcopy -O binary led.elf led.bin arm-linux-objdump -D led.elf > led_elf.dis gcc mkv210_image.c -o mkx210 ./mkx210 led.bin 210.bin %.o : %.S arm-linux-gcc -o $@ $< -c -nostdlib
%.o : %.c arm-linux-gcc -o $@ $< -c -nostdlib
clean: rm *.o *.elf *.bin *.dis mkx210 -f 增加 link.lds 链接脚本文件: SECTIONS { . = 0xd0024000; .text : { start.o * (.text) } .data : { * (.data) } bss_start = .; .bss : { * (.bss) } bss_end = .; } 更改 start.S 汇编启动文件,其他文件不边: /* * 文件名: start.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 .
|