1、一个程序可以分成哪些部分?
代码段、数据段,BSS段。
代码段:指令。
数据段:有初始值,并初值不等于0的全局变量或静态变量。
BSS段:初始化为0或没初始化的全局变量或静态变量。
2、链接脚本
以前的makefile 中指定的数据都是顺序存放的,现在我们要弄复杂一点,用一个链接脚本来指定。
led.bin: start.o led.o
arm-linux-ld -T leds.lds -o led.elf start.o led.o
arm-linux-objcopy -O binary led.elf led.bin
arm-linux-objdump -D led.elf > led.dis
start.o : start.S
arm-linux-gcc -o start.o start.S -c
led.o : led.c
arm-linux-gcc -o led.o led.c -c
clean:
rm *.o led.elf led.bin led.dis -f
arm-linux-ld 链接命令 -T leds.lds 指定链接脚本 -o led.elf start.o led.o 编译输出 arm-linux-gcc -nostdlib 是不导入标准库
所谓链接脚本就是定义了整个程序编译之后的链接过程,决定了一个可执行程序的各个段的存储位置。来看看这个leds.lds怎么写?
SECTIONS
{
. = 0x50000000; /* ‘.’当前地址,也就是程序的链接地址从0x50000000开始 */
.text : { /* .text 段的名称,这个无所谓,叫什么都可以,{ }中的是内容*/
start.o /* start.o 整个文件都放这里*/
* (.text) /* *表示所有文件,整行就是所有程序的代码段都放这里 */
}
.data : {
* (.data) /*所有文件的数据段*/
}
bss_start = .; /*定义一个变量bss_start,把当前地址赋给它*/
.bss : {
* (.bss)
}
bss_end = .; /*定义一个变量bss_end,把当前地址赋给它*/
}
一般型式是这样的:
SECTIONS
{...
secname start BLOCK(align) (NOLOAD) : AT (ldadr) {contents} >region:phdr=fill
...}
secname和contents是必须的,其他的都是可选的。
secname:段名
contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)
start:本段连接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也就是start地址。start可以用任意一种描述地址的符号来描述。
AT(ldadr):定义本段存储(加载)的地址。
一个程序编译出来的.bin文件是不包含bss段的(避免存太多了,以节省空间)。在程序启动之前,这个bss段应该自己清0,这两个变量就是来记录清哪些内存。
/* 清BSS段 */
clean_bss:
ldr r0, =bss_start
ldr r1, =bss_end
mov r2, #0
clean_loop:
str r2, [r0], #4
cmp r0, r1
bne clean_loop
2> . = 0x50000000; 链接地址不同会有什么不一样呢?
链接地址为0 时 全局变量 i 的地址为:0x100.
链接地址为0x50000000时 全局变量 i 的地址为:0x50000100.
main程序执行时,是从i的地址去把值读出来,链接地址不一样,存放i的地址也不一样。所以 一个程序运行的时候应该位于它的链接地址,否则去访问全局变量时就会出错。
3、重定位
首先面临一个问题: 为什么重定位代码呢?系统一启动,就会把nand flash前8k的内容拷到6410内存(硬件实现刚好复制到内存0地址开始)上去,然后从内存0地址开始运行。即使这个程序的链接地址是0x50000000,我们知道一个程序应该位于它的链接地址去执行,否则访问全局变量的地址就会出错,得到的值不对。所以要使这个程序正确运行,在被拷贝到内存的8k代码中要做一件事情:就是把自己拷贝到链接地址去运行,这个就叫做重定位。 重定位:就是把程序拷贝到它的链接地址去。
/* 重定位 */
adr r0, _start /* _start当前指令地址 */
ldr r1, =_start /* _start的编译地址 */
ldr r2, =bss_start
cmp r0, r1 /*如果当前地址不是链接地址,执行copy_loop*/
beq clean_bss
copy_loop:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r1, r2
bne copy_loop
copy_loop:把程序拷贝到链接地址去。内存拷贝3要素:源,目的,大小。
src:从0地址开始拷,即_start开始。 des:拷到链接地址去,即=_start 大小:从链接地址到bss_start的内容,bss段不拷贝
那为什么重定位之前的代码 没在链接地址,也能够正确运行呢?
这是另外一个问题了,因为它是用位置无关码写的。
4、位置无关码
位置无关码就是不管在什么位置,在不在它的链接地址,都可以跑。它要符合什么条件呢?
条件:1、跳转时用相对跳转 b,bl。 2、不能用全局变量和静态变量。
bl test /* 位置无关 */
ldr pc, =test /* 位置相关 */
很简单,给pc 直接赋值就可以了。
ldr pc, =main /* 重定位完后, 使用位置相关的指令跳转 */
这样就跑到链接地址去执行了。