重定位和链接脚本
(1)重定位:分为静态重定位和动态重定位
静态重定位:静态重定位是在程序执行之前进行重定位,它根据装配模块将要装入的内存起始位置,直接修改装配模块中的有关使用地址的指令
我们下面要分析就是静态重定位的情况。
动态重定位:动态重定位是指,不是在程序执行之前而是在程序执行过程中进行地址重定位。更确切地说,是在每次访问内存单元前才进行地址变换。动态重定位可使装配模块不加任何修改而装入内存,但是它需要硬件——定位寄存器的支持。
图片的参考来源:http://c.biancheng.net/cpp/html/2608.html
上面重定位的定义的参考来源:http://bbs.pediy.com/showthread.php?t=76876
(2)为什么需要重定位呢?
我们的程序中的代码可分为:位置无关码和位置有关码。顾名思义,位置无关码就是代码在哪个地址运行都行的。那么位置有关码呢?就必须在规定的地址处执行才可以,这个规定的地址就是链接地址,而我们代码执行时候的地址是运行地址,位置有关码就要求链接地址和运行地址必须一致,执行的时候才不会出错。但由于某些原因(譬如,我们在用裸机的时候,我的DRAM还没初始化,而我们的代码又必须要在DRAM上才可以继续运行,那么直接下载肯定就是不行的了),我们又不能把程序下载到相应的地址处去执行,所以,这时候就要用到重定位了。
(3)在说重定位的具体实现之前,我们先来说一下链接脚本(文件.lds)
链接脚本的代码如下:看懂下面的代码我们还需要了解:bss段,数据段(.data),代码段(.text)等知识,可以参考我的这篇博客:
程序中的bss段,数据段(.data),代码段(.text):http://www.cnblogs.com/nibuyaoni/p/5724013.html
然后,我们通过Makefile,编译链接程序时可以指定按照链接脚本的顺序来链接代码:arm-linux-ld -Tlink.lds -o led.elf $^
link.lds为链接脚本的名字
1 SECTIONS 2 { 3 . = 0x20000000; //指定链接地址为0x20000000 4 5 .text : { //代码段 6 start.o //指定链接的顺序为:start.o->sdram_init.o->其他的一些文件 7 sdram_init.o 8 * (.text) //这里表示其他的一些.o文件 9 } 10 11 .data : { //数据段 12 * (.data) //这里表示所有的数据段的文件 13 } 14 15 bss_start = .; //把当前的地址赋值给bss_start 16 .bss : { //bss段 17 * (.bss) //所有bss段的文件 18 } 19 20 bss_end = .; //把当前的地址赋值给bss_end 21 }
(4)下面,我们就来看看重定位代码的具体实现
2 // adr指令用于加载_start当前运行地址 3 adr r0, _start // adr加载时就叫短加载 4 // ldr指令用于加载_start的链接地址:0x20000000 5 ldr r1, =_start // ldr加载时如果目标寄存器是pc就叫长跳转,如果目标寄存器是r1等就叫长加载 6 // bss段的起始地址 7 ldr r2, =bss_start // 就是我们重定位代码的结束地址,重定位只需重定位代码段和数据段即可 8 cmp r0, r1 // 比较_start的运行时地址和链接地址是否相等 9 beq clean_bss // 如果相等说明不需要重定位,所以跳过copy_loop,直接到clean_bss 10 // 如果不相等说明需要重定位,那么直接执行下面的copy_loop进行重定位 11 // 重定位完成后继续执行clean_bss。 12 13 // 用汇编来实现的一个while循环 14 copy_loop: 15 ldr r3, [r0], #4 // 源,后面的#4就是:地址在不断的加4 16 str r3, [r1], #4 // 目的 这两句代码就完成了4个字节内容的拷贝 17 cmp r1, r2 // r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2 18 bne copy_loop 19 20 // 清bss段,其实就是在链接地址处把bss段全部清零 21 clean_bss: 22 ldr r0, =bss_start 23 ldr r1, =bss_end 24 cmp r0, r1 // 如果r0等于r1,说明bss段为空,直接下去 25 beq run_on_dram // 清除bss完之后的地址 26 mov r2, #0 27 clear_loop: 28 str r2, [r0], #4 // 先将r2中的值放入r0所指向的内存地址(r0中的值作为内存地址), 29 cmp r0, r1 // 然后r0 = r0 + 4 30 bne clear_loop 31 32 run_on_dram: 33 // 长跳转到led_blink开始第二阶段 34 ldr pc, =led_blink // ldr指令实现长跳转和bl是短加载 35 //最后这里跳转,是跳转到重定位的代码所对应的led_blink这个函数那里去执行的 36 37 // 汇编最后的这个死循环不能丢 38 b .
总结:由上面我们可以看到:重定位的时候,先使用一段位置无关码来对重定位的地址那里的内存进行一些操作:
(1)把整段代码搬运过重定位的内存那里(用copy_loop来实现)
(2)清bss段(clean_bss来实现)
(3)跳转到重定位的那段内存去执行(run_on_dram来实现)
注意:上面那些汇编指令所实现的功能我们可以通过反汇编文件(.dis文件)来验证:arm-linux-objdump -D led.elf > led_elf.dis
参考来源:朱老师物联网大教程