一、位置有关代码和位置无关代码
以前,我们编写程序的时候,根本不知道还有位置有关代码和位置无关代码,不知道代码的执行居然和代码的链接地址有关,当然也不知道链接地址是什么,但是在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、链接脚本简单介绍
介绍链接脚本之前,介绍一下程序段的概念
- 代码段(.text段):又叫文本段,函数编译后生成的代码
- 数据段(.data):C语言中初始化为非0的全局变量。
- bss段(.bss):C语言中初始化为0的全局变量,或者是未初始化的全局变量(默认为0).
- 自定义段:程序员自己定义的段,关于段的名字、属性、特征,都是自定义的。
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处,然后跳转到内存中的相应位置,执行第二阶段的代码。