内存与重定位——重定位的理论与代码实践(涉及位置有/无关码)

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

参考博客

位置无关码、位置有关码 - biaohc - 博客园

位置有关码和位置无关码-CSDN博客

一、链接地址与运行地址

1、链接地址

链接地址,是指程序员通过Makefile中“ -Ttext  xxx ”或者在链接脚本中指定的地址。程序员预先知道程序的执行要求,比如放在哪里才能顺利执行,于是就把这个地址当做链接地址。

(1)Linux中应用程序默认的链接地址是0x0000_0000

Linux中应用程序默认链接地址是0x0000_0000,比如使用命令“gcc hello.c -o hello”进行编译与链接时,使用的就是默认的链接地址0x0000_0000。

每个应用程序运行在一个独立进程中,可以独享4G的虚拟地址空间,因此每个应用程序都可以链接到0x0000_0000地址处,因为每个进程都是从0地址开始的。

(2)X210中裸机程序的链接地址应该为0xd002_0010

X210开发板的链接地址,理论上应该是0xd002_0010。这个地址不是随意定的,而是iROM中的BL0加载BL1时指定好的地址,由CPU设计时决定的。

2、运行地址

运行地址,是指代码运行时的地址。

运行时地址无法在编译链接时绝对地确定,它是在下载时确定的。

二、重定位与位置无关码

1、重定位的概念

链接地址和运行地址可能不同,比如我们指定链接地址为0xd0024000,但后来通过dnw软件将镜像文件下载到0xd002_0010,则此时的运行地址是0xd002 0010。

这种情况下,如果镜像文件里有位置相关码,则位置相关码会运行不起来,因为位置已经发生改变。因此在位置相关码运行之前,需要先执行一段位置无关码,把整个镜像文件拷贝到以链接地址为起始地址的空间里,然后通过长跳转指令,跳转到(以链接地址为起始地址的那份代码的)相应位置继续运行。

当执行完代码重定位后,在SRAM中有2份镜像文件,一份是下载到0xd0020010处开头的镜像文件,另一份是复制到0xd0024000处开头的镜像文件。

接下来如果使用短跳转 “bl led_blink”,则执行的是运行地址0xd0020010开头的那一份代码。如果使用长跳转“ldr pc, =led_blink”,则执行的是链接地址 0xd0024000 开头的那一份代码,即直接从 0xd0020010开头的代码跳转到0xd0024000开头的那一份代码的 led_blink函数处。

当链接地址和运行地址相同时,短跳转和长跳转效果是一样的。

注意

(1)adr指令加载符号地址,加载的是运行时地址;ldr指令在加载符号地址时(第1个操作数是pc则叫长跳转,若是其他寄存器则叫长加载),加载的是链接地址。

(2)长跳转指跳转到的地址和当前地址差异比较大,跳转的范围比较宽广。它通过给PC(r15)赋一个新值来完成代码段的跳转执行。

2、位置无关码的概念

位置无关码与位置有关码的实质区别,在于操作时,位置有关码使用的是绝对地址,而位置无关码使用的是相对地址(PC + offset),主要体现在取址、跳转这两个操作上。判断方法就是看某指令是否受到链接脚本中链接地址的影响。

常见的情形分析

简单总结:b、bl、adr、ldr指令是位置无关码,ldr伪指令去加载标号地址时是位置有关码。

(1)b、BL、adr指令用的都是相对地址,所以是位置无关码。

(2)ldr pc,=main;因为main标志在编译的时候,会受到链接脚本的链接位置的影响,因此main是链接后的绝对地址,所以是位置有关码。

(3)ldr r0, =bss_start,这句代码的作用是把bss_start(在链接脚本中)的地址放入r0中,因为bss_start的值会受到链接脚本中链接位置的影响,因此bss_start是链接后的绝对地址,所以是位置有关码。

(4)ldr r0, 0xe0002700;这个操作取0xe0002700内存地址中的值赋值给r0,因为这个内存地址不会受到链接脚本中链接位置的影响,所以是位置无关码。下面的这几句代码也不会受到链接脚本中链接地址的影响,所以也是位置无关码。

#define WTCON         0xE2700000
#define PS_HOLD_CONTROL  0xE010E81C
#define SVC_STACK      0xD0037D80
ldr r0, =WTCON
ldr r0, =PS_HOLD_CONTROL

三、例子说明

一开始代码运行于SRAM内部的 0xd002_0010 这个位置,也就是BL1该呆的位置。后续进行代码重定位测试,包括在SRAM内部重定位,重定位到DDR SDRAM(内存)中。代码如下: 

1、在SRAM内部重定位

链接脚本如下(其中0xd002_4000这个地址位于SRAM中)。

SECTIONS
{
	. = 0xd0024000;
	
	.text : {
		start.o
		* (.text)
	}
    		
	.data : {
		* (.data)
	}
	
	bss_start = .; 
	.bss : {
		* (.bss)
	}
	
	bss_end  = .;	
}

 led.S文件如下:

/*
 * 文件名:	led.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 即地址加4
	bne clear_loop

run_on_dram:	
	// 长跳转到led_blink开始第二阶段
	ldr pc, =led_blink				// ldr指令实现长跳转
	
	// 从这里之后就可以开始调用C程序了
	//bl led_blink					// bl指令实现短跳转
	
// 汇编最后的这个死循环不能丢
	b .

清除bss段是为了满足C语言的运行时要求(C语言要求显式初始化为0的全局变量,或者未显式初始化的全局变量的值为0,实际上C语言编译器就是通过清bss段来实现C语言的这个特性的)。一般情况下我们的程序是不需要负责清零bss段的(C语言编译器和链接器会帮我们的程序自动添加一段头程序,这段程序会在我们的main函数之前运行,这段代码就负责清除bss)。但是在我们代码重定位了之后,因为编译器帮我们附加的代码只是帮我们清除了运行地址那一份代码中的bss,而未清除重定位地址处开头的那一份代码的bss,所以重定位之后需要自己去清除bss。

2、重定位到内存中

链接脚本如下(其中0x2000_0000这个地址位于内存中)。

SECTIONS
{
	. = 0x20000000;
	
	.text : {
		start.o
		sdram_init.o
		* (.text)
	}
    		
	.data : {
		* (.data)
	}
	
	bss_start = .; 
	.bss : {
		* (.bss)
	}
	
	bss_end  = .;	
}

  led.S文件如下:

/*
 * 文件名:	led.s	
 * 描述:	演示重定位至DDR中,,需要先初始化内存
 */

#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步:初始化ddr
	bl sdram_asm_init
	
	// 第5步:重定位
	// 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 .

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天糊土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值