1.重定位:
就是用地址无关码,把地址有关码加载到正确物理地址上。
地址有关码就是使用地址有关的汇编命令的代码。
2.地址无关码与地址有关码
地址无关码是运行地址与连接地址无关,也可以正确运行。
地址有关吗是运行地址必须匹配链接地址才能运行。
3.连接地址与运行地址
链接地址,是用来指导链接器连接的,本身可能会以立即数形式出现在代码中。(下面会有事例)
运行地址,也是物理地址,是硬件设计时就决定了的。
4.为什么地址有关码必须要链接地址与运行地址匹配?
因为地址有关码使用了地址有关的汇编指令,如长跳转 ldr(后面会分析)。
5.链接地址的指定方式
(1)默认指定
gcc test.c -o test.c // 链接地址默认从 0 开始
(2)-Ttext直接指定
ld -o test.elf -Ttext 0x10 a.o b.o c.o // 从 0x10 开始
(3)使用连接脚本
SECTIONS{ . = 0xd0020010; .text : { start.o * (.text) } .data : { * (.data) } bss_start = .; .bss : { * (.bss) } bss_end = .; }
这时连接地址从 0xd0020010开始
6. 代码分析
.global _start _start: bl relocate b . relocate: // 通过比较_start符号的实际运行地址与连接地址决定是否重定位 adr r0, _start // run addr ldr r1, =_start // link addr cmp r0, r1 beq run_dram ldr r2, =bss_start // 进行重定位,拷贝 .text , .data relocate_loop: ldr r3, [r0], #4 str r3, [r1], #4 cmp r1, r2 bne relocate_loop // clear bss ldr r0, =bss_start ldr r1, =bss_end mov r2, #0 // 进行重定位, 清 .bss clear_bss_loop: cmp r0, r1 beq run_dram str r2, [r0], #4 b clear_bss_loop
run_dram:
ldr pc, =led_after
理解代码的关键是:
adr r0, _start // run addr ldr r1, =_start // link addr
首先 _start 是标号,本质就是地址的别名。
adr 是短跳转,本质是通过pc计算出 _start 标号的值(也就是程序的起始地址)。
ldr 是长跳转,本质是通过链接器根据连接脚本对代码进行了修改,这里加载的 _start是连接脚本给的值。
下面给出反汇编:
首先我的连接脚本为:
SECTIONS{ . = 0xd0024000; .text : { start.o * (.text) } .data : { * (.data) } bss_start = .; .bss : { * (.bss) } bss_end = .; }
说明 _start 的连接地址为 0xd0024000
下面汇编,
第一列为使用链接地址时,没32位代码的地址,注意,这并不存在于运行的程序中。
第二列为32位代码,后面是对应的汇编。
d002402c <relocate>: d002402c: e24f0034 sub r0, pc, #52 ; 0x34 // 这句为 adr d0024030: e59f1040 ldr r1, [pc, #64] ; d0024078 <run_dram+0xc> // 这句是 ldr d0024034: e1500001 cmp r0, r1 d0024038: 0a00000b beq d002406c <run_dram> d002403c: e59f2038 ldr r2, [pc, #56] ; d002407c <run_dram+0x10> d0024040 <relocate_loop>: d0024040: e4903004 ldr r3, [r0], #4 d0024044: e4813004 str r3, [r1], #4 d0024048: e1510002 cmp r1, r2 d002404c: 1afffffb bne d0024040 <relocate_loop> d0024050: e59f0024 ldr r0, [pc, #36] ; d002407c <run_dram+0x10> d0024054: e59f1024 ldr r1, [pc, #36] ; d0024080 <run_dram+0x14> d0024058: e3a02000 mov r2, #0 d002405c <clear_bss_loop>: d002405c: e1500001 cmp r0, r1 d0024060: 0a000001 beq d002406c <run_dram> d0024064: e4802004 str r2, [r0], #4 d0024068: eafffffb b d002405c <clear_bss_loop> d002406c <run_dram>: d002406c: e59ff010 ldr pc, [pc, #16] ; d0024084 <run_dram+0x18> d0024070: e2700000 rsbs r0, r0, #0 d0024074: d0037d80 andle r7, r3, r0, lsl #27 d0024078: d0024000 andle r4, r2, r0 d002407c: d0024190 mulle r2, r0, r1 d0024080: d0024190 mulle r2, r0, r1 d0024084: d0024114 andle r4, r2, r4, lsl r1 d0024088: 00001a41 andeq r1, r0, r1, asr #20 d002408c: 61656100 cmnvs r5, r0, lsl #2 d0024090: 01006962 tsteq r0, r2, ror #18 d0024094: 00000010 andeq r0, r0, r0, lsl r0 d0024098: 45543505 ldrbmi r3, [r4, #-1285] ; 0x505 d002409c: 08040600 stmdaeq r4, {r9, sl} d00240a0: 00010901 andeq r0, r1, r1, lsl #18
分析这句:
d002402c: e24f0034 sub r0, pc, #52
程序运行,将 pc 值减 52,放到 r0,注意这里pc是运行地址,减52后正好就是程序的起始地址 _start处。
所以,adr 的本质是通过 运行地址计算得到加载到寄存器的值。
再分析这句:
d0024030: e59f1040 ldr r1, [pc, #64] ; d0024078 <run_dram+0xc>
ldr说明是将内存中的值放到 r1,具体是 pc+64 指向的内存,这里的 pc 装的也是运行地址,但由于二级流水线技术(取指,解析,运行),运行这句时,pc已经后移了8字节,所以实际上是 后移(64 + 8)/4 行,也就是这一行:
d0024078: d0024000 andle r4, r2, r0
将这一行的数据放到 r1 中,这一行的数据是 0xd0024000 ,这是链接器根据链接脚本对代码进行修改的结果。
所以,ldr的本质是使用 链接地址给的立即数。
最后看这句
run_dram: ldr pc, =led_after
这是完成重定位执行的一句,
是一句长跳转,通过上面的分析知道了这句会使用链接地址,程序会直接到 物理地址 == led_after 处运行,这就要求连接地址与物理地址匹配,这也正是前面进行重定位的意义。
来源:https://www.cnblogs.com/yangxinrui/p/9925986.html