参考文章
一、为什么需要重定位
- NandFlash启动时:CPU无法直接读写NandFlash中的数据,因此当设置为NandFlash启动时,硬件会自动将NandFlash的前4k的数据复制到SRAM中,CPU会从SRAM中执行烧录到NandFlash的程序,此时SRAM的基地址是0地址。但如果启动时的程序超过了4k,则前4k的内容需将整个程序复制到SDRAM中,然后程序在SDRAM中执行。
- NorFlash启动时:NorFlash的特性是只能像内存一样的读,但不能像内存一样的写,因此当设置为NorFlash启动时,启动的程序无法修改和写入程序中的全局变量和静态变量,导致无法正常启动。因此需要将整个程序复制到SDRAM中,然后程序在SDRAM中执行。
二、如何重定位?
- 段的概念
所有的程序在都包括:
.text 代码段 .data 数据段
.rodata 只读数据段(const全局变量)
.bss段 (初始值为0,无初始值的全局变量)
.commen 注释段
其中.bss段和.commen段不保存在bin文件汇总,但保存在elf文件中。
- 链接脚本:用来指导程序的链接及运行地址
链接脚本的语法:
SECTIONS {
…
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
…
}
语法的解释
secname :段名
start :起始地址:运行时的地址(runtime addr);重定位地址(relocate addr)
AT ( ldadr ) :可有可无(load addr:加载地址) 不写时LoadAddr = runtime addr
{ contents } 的内容:
start.o //内容为start.o文件
*(.text)所有的代码段文件
start.o *(.text)文件
对于不同的段,可以设置不同的链接地址和加载地址。
- 重定位方法示例
1)
arm-linux-ld -Ttest 0 -Tdata 0x30000000 start.o main.o -o sdram.elf
这种方式本意是将.data段放到0x30000000(SDRAM)中运行,从而避免NorFlash无法写数据的问题,但同时也将数据段的加载地址放到0x30000000,而从.text段后到.data段中间会产生黑洞也包括在文件中,从而导致bin文件过大。
2)
SECTIONS{
.test 0 : {*(.text)}
.rodata : {*(.radata)}
.data 0x30000000 : AT(0x700)
{
data_loadaddr = LOADADDR(.data);
data_start = .;
*.data
data_end = .;
}
.bss : {*(.bss) *(.COMMOM)}
ldr r0, =data_loadaddr
ldr r1, =data_start
ldr r2, =data_end
cpy:
ldrb r4, [r0]
strb r4, [r1]
add r0, r0, #1
add r1, r1, #1
cmp r1, r2
ble cpy
这种方法是将.rodata放到.text段后,将.data段的加载地址放到0x700,运行地址放到0x30000000,由于加载地址和运行地址不一致,因此将.data段的加载地址的数据复制到了0x30000000,当程序在0x30000000读写数据时,就可以正常读写.data段的数据。由于.rodata段本身就是只读不能更改,所以无需重定位。
3)
SECTIONS{
.test 0 : {*(.text)}
.rodata : {*(.radata)}
.data 0x30000000 : AT(0x700)
{
data_loadaddr = LOADADDR(.data);
data_start = .;
*.data
data_end = .;
}
bss_start = .;
.bss : {*(.bss) *(.COMMOM)}
bss_end = .;
/**********复制.data段*************/
ldr r0, =data_loadaddr
ldr r1, =data_start
ldr r2, =data_end
cpy:
ldrb r4, [r0]
strb r4, [r1]
add r0, r0, #1
add r1, r1, #1
cmp r1, r2
ble cpy
/**********清除.bss段*************/
ldr r0, =bss_start
ldr r1, =bss_end
ldr r2, =0
clean:
str r2, [r0]
add r0, r0, #1
cmp r0, r1
ble clean
此部分是清除bss段,由于.bss段是所有初始值为0的数据,如果放在bin文件会占用大量的空间导致文件过大,可以通过清零arm中相关的内存空间达到相同的效果,因此在.bin文件中不会含有bss段,需要烧录的bin文件中的程序根据.bss段的地址和大小将.bss段在内存中占用的空间清零。
- 重定位程序完整版
SECTIONS{
. = 0x30000000;
. = ALIGN(4);
.text : {*(.text)}
. = ALIGN(4);
.rodata : {*(.rodata)}
. = ALIGN(4);
.data : {*(.data)}
. = ALIGN(4);
bss_start = .;
.bss : {*(.bss) *(.COMMON)}
bss_end = .;
}
/*******************整体重定位******************/
ldr r0, =0
ldr r1, =_start
ldr r2, =bss_start
cpy:
ldr r3, [r0]
str r3, [r1] //第一条指令运行的地址,即0x30000000
add r0, r0, #1
add r1, r1, #1
cmp r1, r2
ble cpy
/*******************整体重定位******************/
ldr r0, =0
ldr r1, =bss_start
ldr r2, =bss_end
clean:
str r0, [r1]
add r1, r1, #1
cmp r1, r2
ble clean
- 位置无关码:加载地址与运行地址可以不一致,因为位置无关码使用的是相对地址来进行跳转和执行,即只要在原来的pc指向的地址上加减,不需要绝对地址进行跳转执行。
包括:1、使用相对跳转指令b、bl,而不是用绝对跳转指令ldr pc, =xxx;
2、全局变量/静态变量、初始值不为0的数组(在.rodata段中)的访问都是需要绝对地址访问,因此不是位置无关码
3、重定位后需要绝对跳转指令将当前运行地址跳转到重定位地址。