代码重定位实战2

1、adr与ldr伪指令的区别
ldr和adr都是伪指令,区别是ldr是长加载,adr是短加载
重点:adr指令加载符号地址,加载的是运行时地址,ldr指令加载符号地址时,加载的是链接地址。
深入分析:只要知道adr和ldr分别用于加载运行地址和链接地址,从而可以判断是否需要重定位即可;要想知道adr和ldr加载同一个符号而地址不同的原理要深入分析反汇编文件。
在这里我个人的理解:adr r0, _start.和ldr r1, = _start 中因为_start是汇编中的符号,本质是个内存地址,这个地址是特定的不会变。adr中_start这个地址指向的是代码,而ldr中加载的_start指向的是另外一个地址,而另外这个地址里如果存放的是运行时地址的话。链接地址就和运行地址相同,否则不同。其实可以把adr中的_start看成是个一级指针,ldr中的_start看成是一个二级指针。
我们只要知道adr和ldr分别用于加载运行地址和链接地址,从而可以判断是否需要重定位即可;
2、重定位(代码拷贝)
重定位就是汇编代码中的copy_loop函数,代码的作用是使用循环结构来逐句复制代码到链接地址。复制的源地址是SRAM的0xd0020010复制目标地址是SRAM的0xd0024000,复制的长度是bss_start减去_start(这是编译器自动计算的)所以复制的长度就是整个重定位需要重定位的长度,也就是整个程序中代码段+数据段的长度。不需要重定位bss段(因为这个段全是零所以不需要重定位)
3、清bss段
清除bss段是为了满足C语言的运行时要求(C语言要求显式初始化为0的全局变量,或者未显式初始化的全局变量的值为0,实际上C语言编译器就是通过清bss段来实现C语言的这个特性的)。一般情况下我们的程序是不需要程序员负责清零bss段的(C语言编译器和链接器会帮我们的程序自动添加一段头程序,这段程序会在我们的main函数之前运行,这段代码实际负责清除bss)。但是在我们的代码重定位了之后,因为编译器帮我们附加的代码只是帮我们清除了运行地址那一份代码中的bss,而未清除重定位地址处开头的那一份代码的bss,所以重定位之后需要自己去清除bss。
4、长跳转
清理完bss段后重定位就结束了,这时当前的状况是:
1、当前运行地址还在0xd0020010开头的(重定位前的)那一份代码中运行着。
2、此时SRAM中已经有了2份代码,1份在d0020010开头,另一份在d0024000开头的位置。
然后就要长跳转了。
ldr 加载时如果目标寄存器是pc就叫长跳转,如果目标寄存器是r1等通用寄存器就叫长加载。而adr加载时就叫短加载.
本节实验项目代码是从上节复制过来的,工程中添加了link_les链接脚本文件,改动了Makefile,在这里中添加了链接脚本文件名(arm-linux-ld -Tlink.lds -o led.elf $^) 。而且在start.S中添加了重定位汇编代码。见下面代码段。

start.S汇编文件示例代码:

//重定位演示代码

#define WTCON 0xE2700000
#define SVC_STACK 0xD0037D80

.global _start
_start:

//第一步:关看门狗 应该在所有启动代码前关:(实现方法是向WTCON的bit5写入0即可)
ldr r0, =WTCON
ldr r1, =0x0	//因为看门关了,其它功能就没意义了,所以直接把WTCON所有引脚设置为0
str r1, [r0]

//第二步:设置SVC栈

ldr sp, =SVC_STACK

//第三步:开/关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;

//第四步重定位


//adr指令用于加载_start当前运行地址
adr r0, _start
//ldr 指令用于加载_start的链接地址0xd0024000
ldr r1, = _start

//bss段的起始地址
ldr r2, = bss_start //是重定位代码的结束地址,bss_start是链接脚本指定的
cmp r0, r1		//比较_start的运行地址和链接地址是否相等。
beq clean_bss   //如果相等不需要重定位跳过copy_loop直接到clean_bss

//如果不相等要重定位直接执行下面的cop_loop进行重定 //位重定位完成后继续执行clean_bss.
copy_loop:
ldr r3, [r0] #4 //源:将R0地址里的数据存到R3后再加4
str r3, [r1] #4 //目标,将r3的数据放入R1指向的内存地址里,再加4

cmp r1, r2  //重定位起始地址和结束地址比较,r2是引用链接脚本符号得			//到的。R1和r2都是链接地址,所以经过加肯定会相等。
bne copy_oop

//清bss段,其实就是在链接地址处把了bss全部都清零
clean_bss:
ldr r0, =bss_start //bss起始地址,引用链接脚本符号
ldr r1, =bss_end //bss结束地址。
cmp r0, r1 //如果r0=r2,说明bss段为空,不用清,没全局变量
beq run_on_dram //直接跳到这个标号去。
mov r2, #0 //r2=0
clear_loop:
str r2, [r0] #4 //先将r2中的值放入r0所指向的内存地址,r0再加4
cmp r0, r1
bne clear_oop

run_on_dram: //清除完bss之后的地址。

//长跳转到led_blink开始第二阶段
ldr pc, =led_blink //led_blink是C语言里LDE闪烁的一个函数。

    //汇编最后这个死循环不能丢
b .

link.lds链接脚本文件:(调试代码时有注释报错,去掉注释ok)

SECTIONS
{
. = 0xd0024000; //指定链接地址

.text : {

     start.o
     *(.text)
}


.data : {
   *(.data)
}	

bss_start = .;
.bss : {
   *(.bss)
}

bss_end = .;

}

c语言LED闪烁代码:
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244

#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)

void delay(void);

//该函数要实现led闪烁效果
void led_blink(void)
{
//led的初始化,也就是把GPJ0CON控制器设置为输出模式

//unsigned int  *p = (unsigned int *)GPJ0CON;
//unsigned int  *p1 = (unsigned int *)GPJ0DAT;
//*p = 0x11111111;
rGPJ0CON = 0x11111111;

while(1)
{
	//led亮

	//*p1 = ((0<<3) | (0<<4) | (0<<5));
	rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	
	//延时
	delay();		
	//led灭

	//*p1 = ((1<<3) | (1<<4) | (1<<5));
	rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	
	
	//延时
	delay();
}

}

//延时函数实现
void delay(void)
{
volatile unsigned int i = 900000; //volatile让编译器不要优化,这样才能真正减
while(i–); //i不为0时一直循环,直到i为零循环结束
}

重定位图解:
在这里插入图片描述
反汇编图解adr和ldr

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值