参考文件:重定位反汇编后代码,重定位汇编代码,链接脚本
三星开发板S5PV210
参考文件一:链接脚本
链接脚本的理解:
SECTION{} 这个是整个链接脚本
. 点号在链接脚本中代表当前位置
.text:代码段
.data:数据段
.bss:bss段
SECTIONS
{
. = 0xd0024000;
.text : {
start.o
* (.text)
}
.data : {
* (.data)
}
bss_start = .;
.bss : {
* (.bss)
}
bss_end = .;
}
参考文件二:重定位汇编代码
未添加注释的代码:
// 第4步:重定位
adr r0, _start
ldr r1, =_start
ldr r2, =bss_start
cmp r0, r1
beq clean_bss
copy_loop:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r1, r2
bne copy_loop
clean_bss:
ldr r0, =bss_start
ldr r1, =bss_end
cmp r0, r1
beq run_on_dram
mov r2, #0
clear_loop:
str r2, [r0], #4
cmp r0, r1
bne clear_loop
run_on_dram:
ldr pc, =led_blink
// 从这里之后就可以开始调用C程序了
bl led_blink // bl指令实现短跳转
添加注释的代码:
// 第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 //源
//从r0这个地址里取一个32bit的数据,放到r3,并将r0+4,指向下一个内存池里的数据
str r3, [r1], #4 // 目的
//这条指令实现了把r3里的数据赋值给r1所标示的地址,并将r1+4
//以上两句代码就完成了4个字节内容的拷贝
cmp r1, r2
// r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2,当r1等于r2
//复制长度是bss_start减去_start,bss段不需要重定位
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完之后的地址
//把0赋给寄存器r2
mov r2, #0
clear_loop:
str r2, [r0], #4
// 先将r2中的值放入r0所指向的内存地址(r0中的值作为内存地址),
// 然后r0 = r0 + 4
cmp r0, r1 //比较r0和r1地址是否相等,不相等循环执行知道bss段清零
bne clear_loop
run_on_dram:
// 长跳转到led_blink开始第二阶段
ldr pc, =led_blink // ldr指令实现长跳转
// 从这里之后就可以开始调用C程序了
//bl led_blink // bl指令实现短跳转
参考文件三:重定位反汇编代码
led.elf: file format elf32-littlearm
Disassembly of section .text:
//实际pc地址d0020010(下载执行地址)
d0024000 <_start>:
//格式:
内存地址:汇编机器码 反汇编得到的指令:注释
d0024000: e59f0060 ldr r0, [pc, #96] ; d0024068 <run_on_dram+0x8>
d0024004: e3a01000 mov r1, #0
d0024008: e5801000 str r1, [r0]
d002400c: e59fd058 ldr sp, [pc, #88] ; d002406c <run_on_dram+0xc>
d0024010: ee110f10 mrc 15, 0, r0, cr1, cr0, {0}
d0024014: e3800a01 orr r0, r0, #4096 ; 0x1000
d0024018: ee010f10 mcr 15, 0, r0, cr1, cr0, {0}
d002401c: e24f0024 sub r0, pc, #36 ; 0x24
// r0 = pc(d0020034) - 0x24 = d0020010
d0024020: e59f1048 ldr r1, [pc, #72] ; d0024070 <run_on_dram+0x10>
//r1 = d0024000
//pc(d0020038 + 72),从pc存储器地址(d0024070)取出数据,加载到寄存器r1
d0024024: e59f2048 ldr r2, [pc, #72] ; d0024074 <run_on_dram+0x14>
d0024028: e1500001 cmp r0, r1
d002402c: 0a000003 beq d0024040 <clean_bss>
.
.
.
d0024060 <run_on_dram>:
d0024060: e59ff014 ldr pc, [pc, #20] ; d002407c <run_on_dram+0x1c>
d0024064: eafffffe b d0024064 <run_on_dram+0x4>
d0024068: e2700000 rsbs r0, r0, #0
d002406c: d0037d80 andle r7, r3, r0, lsl #27
d0024070: d0024000 andle r4, r2, r0
d0024074: d0024128 andle r4, r2, r8, lsr #2
d0024078: d0024128 andle r4, r2, r8, lsr #2
d002407c: d002409c mulle r2, ip, r0
d0024080: 00001a41 andeq r1, r0, r1, asr #20
d0024084: 61656100 cmnvs r5, r0, lsl #2
d0024088: 01006962 tsteq r0, r2, ror #18
d002408c: 00000010 andeq r0, r0, r0, lsl r0
d0024090: 45543505 ldrbmi r3, [r4, #-1285] ; 0x505
d0024094: 08040600 stmdaeq r4, {r9, sl}
d0024098: 00010901 andeq r0, r1, r1, lsl #18
d002409c <led_blink>:
d002409c: e92d4800 push {fp, lr}
d00240a0: e28db004 add fp, sp, #4
d00240a4: e59f3028 ldr r3, [pc, #40] ; d00240d4 <led_blink+0x38>
d00240a8: e59f2028 ldr r2, [pc, #40] ; d00240d8 <led_blink+0x3c>
d00240ac: e5832000 str r2, [r3]
d00240b0: e59f3024 ldr r3, [pc, #36] ; d00240dc <led_blink+0x40>
d00240b4: e3a02000 mov r2, #0
d00240b8: e5832000 str r2, [r3]
d00240bc: eb000007 bl d00240e0 <delay>
d00240c0: e59f3014 ldr r3, [pc, #20] ; d00240dc <led_blink+0x40>
d00240c4: e3a02038 mov r2, #56 ; 0x38
d00240c8: e5832000 str r2, [r3]
d00240cc: eb000003 bl d00240e0 <delay>
d00240d0: eafffff6 b d00240b0 <led_blink+0x14>
d00240d4: e0200240 eor r0, r0, r0, asr #4
d00240d8: 11111111 tstne r1, r1, lsl r1
d00240dc: e0200244 eor r0, r0, r4, asr #4
一. 代码重定位实战(1)
1.1 任务:在SRAM中将代码从0xd0020010重定位到0xd0024000
任务解释:本来代码是运行在0xd0020010的,但是因为一些原因我们又希望代码实际是在0xd0024000位置运行的。这时候就需要重定位了。
注解:本练习对代码本身运行无实际意义,我们做这个重定位存粹是为了练习重定位技能。但是某些情况重定位就是必须的,譬如在uboot中。
1.2 思路:
第一步:通过链接脚本将代码链接到0xd0024000
第二步:dnw下载时将bin文件下载到0xd0020010
第一点加上第二点,就保证了:代码实际下载运行在0xd0020010,但是却被链接在0xd0024000。从而为重定位奠定了基础。
当我们把代码链接地址设置为0xd0024000时,实际隐含的意思就是我这个代码将来必须放在0xd0024000位置才能正确执行。如果实际运行地址不是这个地址就要出事(除非代码是PIC位置无关码),当以上都明白了后,就知道重定位代码的作用就是:在PIC执行完之前(在代码中第一句位置有关码执行之前)必须将整个代码搬移到0xd0024000位置去执行,这就是重定位。
第三步:代码执行时通过代码前段的少量位置无关码将整个代码搬移到0xd0024000
第四步:使用一个长跳转到0xd0024000处的代码继续执行,重定位完成
长跳转:首先这句代码是一句跳转指令(ARM中的跳转指令就是类似于分支指令B、BL等作用的指令),跳转指令通过给pc寄存器(r15)赋一个新值来完成代码段的跳转执行。
寄存器r15 可参考之前文章:ARM的37个寄存器详解
r15(pc)是程序控制计数器,记录当前程序处理到哪里。所以CPU只有一个程序指针,pc指向哪里,CPU就会执行哪条指令(所以程序跳转时就是把目标地址代码放到pc中)
长跳转指的是跳转到的地址和当前地址差异比较大,跳转的范围比较宽广。
当我们执行完代码重定位后,实际上在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),这样就实现了重定位后的无缝链接。
二.代码重定位实战(2)
2.1 adr与ldr伪指令的区别
ldr和adr都是伪指令,区别是ldr是长加载、adr是短加载。
重点:adr指令加载符号地址,加载的是运行时地址;ldr指令在加载符号地址时,加载的时链接地址。
深入分析:只要知道adr和ldr分别用于加载运行地址和链接地址,从而可以判断是否需要重定位即可。但是扩展讲下为什么adr和ldr可以加载不同的地址
(参考反汇编文件)
// adr指令用于加载_start当前运行地址:0xd0020010
adr r0, _start // adr加载时就叫短加载
// ldr指令用于加载_start的链接地址:0xd0024000
ldr r1, =_start // ldr加载时如果目标寄存器是pc就叫长跳转,如果目标寄存器是r1等就叫长加载
ldr cp, =_start //长跳转
反汇编后:
Disassembly of section .text:
实际pc地址d0020010(下载执行地址)
d0024000 <_start>:
//格式:
第一列:内存地址
第二列:汇编机器码
第三列:反汇编得到的指令
第四列:注释
d002401c: e24f0024 sub r0, pc, #36 ; 0x24
// r0 = pc(d0020034) - 0x24 = d0020010
//pc的地址根据实际运行的地址加上偏移量计算,d0020010(实际运行地址) + (d0024024 - d0024000)(偏移量),其中d0024024是当前地址加上流水线两条指令
d0024020: e59f1048 ldr r1, [pc, #72] ; d0024070 <run_on_dram+0x10>
//r1 = d0024000
//pc(d0020038 + 72),从pc存储器地址(d0024070)取出数据,加载到寄存器r1 。
其中d0020038是当前运行地址(d0020010)加上偏移量计算(d0024028 - d0024000)
2.2 重定位(代码拷贝)
重定位就是汇编代码中的copy_loop函数,代码的作用是使用循环结构来逐句复制代码到链接地址。
复制的源地址是SRAM的0xd0020010,复制的目标地址是SRAM的0xd0024000,
复制长度是bss_start减去_start,bss段不需要重定位
// 用汇编来实现的一个while循环
copy_loop:
ldr r3, [r0], #4 // 源
str r3, [r1], #4 // 目的 这两句代码就完成了4个字节内容的拷贝
cmp r1, r2 // r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2
其中r2:ldr r2, =bss_start // 就是我们重定位代码的结束地址,重定位只需重定位代码段和数据段即可
bne copy_loop
2.3 清bss段
清除bss段是为了满足C语言的运行时要求(C语言要求显示初始化为0的全局变量,或者未显式初始化的全局变量的值为0,实际上C语言编译器就是通过清bss段来实现C语言的这个特性的)。一般情况下我们的程序是不需要负责清零bss段的(C语言编译器和链接器会帮我们的程序自动添加一段头程序,这段程序会在我们的main函数之前运行,这段代码就负责清除bss)。但是在我们代码重定位了之后,因为编译器帮我们附加的代码只是帮我们清除了运行地址那一份代码中的bss,而未清除重定位地址处开头的那一份代码的bss。
2.4 长跳转
清理完bss段后重定位就结束了。然后当前的状况是:
1.当前运行地址还在0xd0020010开头的(重定位前的)那一份代码中运行着。
2.此时SRAM中已经有了 2份代码,1份在d0020010开头,另一份在d0024000开头的位置,然后就要长跳转了。