链接脚本与重定位

一、位置有关代码和位置无关代码

以前,我们编写程序的时候,根本不知道还有位置有关代码和位置无关代码,不知道代码的执行居然和代码的链接地址有关,当然也不知道链接地址是什么,但是在Linux的学习中,这些都是必须的。

位置无关代码PIC:汇编源文件被编码成二进制可执行程序时编码方式与位置(内存地址)无关。

位置有关代码:汇编源码编码成二进制可执行程序后和内存地址是有关的。

链接地址:就是我们在编写程序的时候,事先判断程序将来在内存的什么位置执行代码,程序通过编译器和连接器指定链接地址,每个程序的执行都是需要链接地址的。但是以前为什么我们很少接触链接地址呢?因为我们在Linux中编写应用程序,如果我们没有特别指定程序的链接地址,默认的链接地址为0。在Linux中的每一个程序都是一个进程,每一个进程都是独占4G的内存空间,都是地址为0处,开始执行的。所以我们以前不考虑这些都没有问题。

举个例子:我们在设计一个程序的时候,就必须给这个程序指定一个运行地址(这个指定的运行地址就是链接地址),这个地址是我们事先认为程序将来应该运行在哪个地址上。而且编译器和链接器指定这个链接地址。最后我们编译程序得到可执行的程序,理论上这个可执行的程序的运行地址(运行地址)和我们编写程序的时候指定的那哥哥链接地址应该是一样的才行,不然程序就无法执行(这个就是位置有关代码)。但是个别指令的程序的运行地址和链接地址可以不同,也可以正确执行代码,这就是位置无关指令。

二、链接地址的指定

这里我们必须通过编译器和连接器指定链接地址,这里我们有两种方法指定链接地址:

1、在Makefile中,编译的时候通过-Ttext xxx命令直接指定链接地址xxx。

2、在链接脚本中指定链接地址,在Makefile中调用这个链接脚本。

方法一:arm-linux-ld -Ttext 0x0 -o led.elf $^  指定链接地址为0 ld是连接器

方法二:arm-linux-ld -Tlink.lds -o led.elf $^    这里指定的链接脚本link.lds文件

4、链接脚本简单介绍

介绍链接脚本之前,介绍一下程序段的概念

  1. 代码段(.text段):又叫文本段,函数编译后生成的代码
  2. 数据段(.data):C语言中初始化为非0的全局变量。
  3. bss段(.bss):C语言中初始化为0的全局变量,或者是未初始化的全局变量(默认为0).
  4. 自定义段:程序员自己定义的段,关于段的名字、属性、特征,都是自定义的。
SECTIONS
{
	. = 0xd0024000;    #这里指定了链接地址
	 
	.text : {           #.text指定了代码段
		start.o
		* (.text)
	}
    		
	.data : {          #.data指定了数据段
		* (.data)
	}
	
	bss_start = .;     #给变量赋值bss_start赋值为当前的地址
	.bss : {           #.bss指定了bss段
		* (.bss)
	}
	
	bss_end  = .;	
}

链接脚本的作用:链接脚本是一个规则文件,用来指导连接器的链接工作,连接器会参考链接脚本,并且使用其中规定的规则来处理.o文件的那些段,将其链接成可执行文件。

 

三、简单分析一个C语言程序的一生

1、预编译:预编译器执行了一些宏定义的解析,头文件的包含问题,以及注释的问题。

2、编译:编译器把.c、.S文件编译程机器码(.o文件),但是编译后的代码没有任何的关联性,仅仅是把各个函数,编译成单独的模块,一个函数一个.o文件。

3、链接:连接器把编译生成的.o文件按照一定的规则(链接脚本)累计在一起,形成可执行文件。

                               

四、简单概述重定位

1、简单来说,重定位就是将代码从将代码从内存的某个地方,复制到内存的另一个地址处,然后跳转到复制的那一个内存地址处去执行的代码。

                                                                       

2、相关的代码实践

下面我们写代码来实现重定位,主要的就是start.S文件、Makefile文件,和链接脚本。

start.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
	bne clear_loop

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

代码的几个关键地方:

1、首先就是判断运行地址和链接地址是否相同?

ldr指令:长加载指令,加载的地址为程序的链接地址。

adr指令:短加载命令,加载的地址为程序的运行地址。

通过这两个指令,得到程序的运行地址和链接地址,从来判断程序是否需要进行重定位。

2、重定位代码

目的是将整个代码,从运行地址处copy到链接地址处。这里需要注意的地方就是,判断代码copy结束的表示bss_start,是在链接脚本中定义的,bss段的起始地址。

3、清bss段

我们指导bss段都是为0的全局变量,这里我们不再使用复制命令,来实现这段内容的复制,然是通过相应的指令代码,将这段内存地址所对应的内容清零即可。

4、长跳转指令

ldr pc, =led_blink                // ldr指令实现长跳转

最后使用长跳转指令,实现从地址0xd0020010处的代码,跳转到地址为0xd002400处的led_blink函数执行程序。

 

链接脚本:

SECTIONS
{
	. = 0xd0024000;   #指定链接地址
	
	.text : {
		start.o
		* (.text)
	}
    		
	.data : {
		* (.data)
	}
	
	bss_start = .; 
	.bss : {
		* (.bss)
	}
	
	bss_end  = .;	
}

 

makefile文件:

led.bin: start.o led.o
	arm-linux-ld -Tlink.lds -o led.elf $^    #链接方式的指定,通过链接脚本
	arm-linux-objcopy -O binary led.elf led.bin
	arm-linux-objdump -D led.elf > led_elf.dis
	gcc mkv210_image.c -o mkx210
	./mkx210 led.bin 210.bin
	
%.o : %.S
	arm-linux-gcc -o $@ $< -c -nostdlib

%.o : %.c
	arm-linux-gcc -o $@ $< -c -nostdlib

clean:
	rm *.o *.elf *.bin *.dis mkx210 -f

	

这样就实现了重定位。

 

五、S5PV210的开启启动方式

uboot中怎么实现S5PV210开机启动,首先BL0执行,进行相关的的初始化,然后加载BL1到SRAM在中执行,在BL1中会进行内存等相关初始化,然后将整个uboot重定位到内存的0x33e0000处,然后跳转到内存中的相应位置,执行第二阶段的代码。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值