嵌入式Linux裸机开发(四)——重定位relocate
一、位置有关编码
汇编源文件被编译成二进制可执行程序时编码方式可能与内存地址有关,也可能与内存地址无关。与内存地址有关的为位置有关编码,与内存地址无关的为位置无关编码。
程序在设计时需要规划一个程序运行时的地址(链接地址),编译连接器在链接时必须指定这个链接地址,得到的二进制程序的程序理论规划的运行时地址和编译连接器指定的链接地址才相同,程序才能正常运行。位置无关编码程序则无需设计程序时规划运行时地址,编译链接器链接时同样无需指定链接地址。
二、S5PV210的iROM启动方式与uboot启动方式分析
三星官方推荐的S5PV210的iROM启动方式规定,BL1最大为16KB,BL2最大为80KB,bootloader最大为96KB。如果bootloader的大小为86KB,根据iROM启动过程分析,开发板上电后运行在iROM阶段,即BL0阶段,BL0会初始化系统时钟、特殊设备控制寄存器和启动设备,加载外部启动设备中bootloader的BL1(大小16KB)到iRAM中运行,BL1运行时会加载bootloader中的BL2(70KB)到iRAM中运行,BL2运行时会初始化SDRAM,将OS kernel拷贝到SDRAM,最后跳转到SDRAM执行kernel,进入系统。
Uboot的启动方式规定,uboot的大小任意,开机上电后BL0开始运行,BL0会初始化系统时钟、特殊设备控制寄存器和启动设备,加载外部启动设备中uboot的BL1(大小16KB)到iRAM中运行,BL1运行时会初始化SDRAM,然后将整个uboot拷贝到SDRAM中,采用长跳转指令从iRAM中直接跳转到SDRAM中继续执行uboot(16KB开始),uboot启动后再命令行中加载、启动OS kernel。
结合三星的iROM启动方式和uboot启动方式分析,uboot的启动方式必须需要重定位。
三、重定位
1、链接地址的确定
运行时地址由运行时决定,编译链接阶段无法指定运行时地址,但程序员可以在程序设计时规划指定。链接地址由程序员在编译链接阶段通过Makefile中的-Ttext参数指定,如果没有明确指定,则默认编译链接阶段指定的链接地址为0。因此,程序员在程序设计规划时指定的地址与编译链接阶段指定的链接地址相同时,程序才能正常运行。
S5PV210的运行地址是SoC设计时规划确定,所以编译链接阶段时链接地址指定为0xD0020010。
2、程序的编译过程
源代码程序编译成可执行程序的过程包括:预编译、编译、汇编、链接四个阶段。
预编译:预编译处理器对宏定义指令、条件编译指、头文件包含指令等伪指令(以# 开头的指令)和特殊符号进行处理
编译:编译阶段要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
汇编:汇编阶段把汇编语言代码翻译成目标机器指令的过程。
链接:链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体
3、链接脚本
系统预定义的程序段有:
.text:代码段
.data:初始化非0的全局变量数据段
.bss:未初始化全局变量和初始化为0的数据段
每一个链接过程都由链接脚本(linked script,一般以lds做文件后缀名)控制,链接脚本主要用于如何把输入文件内的section放入输出文件,并控制输出文件内各部分在程序地址空间的布局。
ld -verbose可以查看默认的链接脚本的内容。简单的链接脚本如下:
SECTIONS
{
.=0xD0020010;//指定当前地址为0xD0020010
.text:{
*(.text)
}
.=0x8000000;
.data:{
*(.data)
}
.bss:{
*(.bss)
}
}
四、重定位代码实践
目标:在iram中将代码从0xD0020010重定位到0xD0024000
通过链接脚本将代码链接到0xD0024000
将代码下载运行在0xD0020010
在位置无关编码代码段执行完之前将代码拷贝到0xD0024000去执行。使用长跳转指令跳转到0xD0024000地址执行,重定位完成。重定位后iram中有两份相同的代码镜像,一份在0xD0020010处,一份在0xD0024000地址处,重定位后使用长跳转指令ldr pc,=led_blink直接跳转到0xD0024000地址执行。当链接地址和运行地址相同时,长跳转和短跳转(BL)实际效果一样;当链接地址和运行地址不同时,长跳转跳转到链接地址,短跳转跳转到运行地址。
adr指令加载符号地址加载的是运行地址,ldr加载符号地址加载的链接地址。adr是小范围的地址读取伪指令,ldr是大范围的读取地址伪指令。adr是将基于PC相对偏移的地址值或基于寄存器相对地址值读取的为指令,而ldr用于加载32为立即数或一个地址到指定的寄存器中。
strat.S源码:
#define SVC_SP0xD0037D80
.global _start
_start:
//C语言运行时栈设置SVC模式下的栈 CPU复位后为SVC模式,DRAM尚未初始化,
//只有SRAM可用,0xD0037780,0xD0037D80,大小1.5K
ldr sp,=SVC_SP
//重定位
adr r0,_start//加载_start当前运行地址
ldr r1,=_start//加载_start的链接地址
ldr r2,=bss_start//加载bss段的起始地址
cmp r0,r1//比较运行地址和链接地址是否相等
beq clean_bss//相等则表明不需要重定位
//拷贝text段和data段
copy_loop:
ldr r3,[r0],#4
str r3,[r1],#4
cmp r1,r2
bne copy_loop
//清除bss段满足C语言运行时要求,编译器会负责清除bss段,
//编译器清除了运行时地址的bss段,链接地址的bss段未清除 clean_cclean_bss:
ldr r0,=bss_start
ldr r1,=bss_end
cmp r0,r1
beq run_on_dram//相等则bss为空
mov r2,#0
//清除bss段
clear_loop:
str r2,[r0],#4//现将r2放入r0的值对应的内存地址中,然后r0=r0+4
cmp r0,r1
bne clear_loop
run_on_dram:
ldr pc,=led_blink
.end
led_blink.c源码:
#define rGPJ2CON (*((volatile unsigned int *)0xE0200280))
#define rGPJ2DAT (*((volatile unsigned int *)0xE0200284))
void led_blink(void);
void delay(void);
void led_blink(void)
{
rGPJ2CON = 0x00001111;//设置led1--led4为output
rGPJ2DAT = (0<<0 | 0<<1 | 0<<2 | 0<<3);//点亮led1--led4
delay();//延时
rGPJ2DAT = (1<<0 | 1<<1 | 1<<2 | 1<<3);//熄灭led1--led4
delay();
rGPJ2DAT = (0<<0 | 1<<1 | 1<<2 | 1<<3);//点亮led1
delay();
rGPJ2DAT = (1<<0 | 0<<1 | 1<<2 | 1<<3);//点亮led2
delay();
rGPJ2DAT = (1<<0 | 1<<1 | 0<<2 | 1<<3);//点亮led3
delay();
rGPJ2DAT = (1<<0 | 1<<1 | 1<<2 | 0<<3);//点亮led4
delay();
}
void delay(void)
{
volatile unsigned int i = 0x1FFFFF;
while(i--);
}
link.lds:
SECTIONS
{
. = 0xD0024000;
.text :{
start.o
* (.text)}
.data :{
* (.data)
}
bss_start = .;
.bss :{
* (.bss)
}
bss_end = .;
}
Makefile:
OBJS += start.o led_blink.o
CFLAGS += -Wall -O2
LDFLAGS += -Tlink.lds
CROSS_COMPILER := arm-linux-
CC := $(CROSS_COMPILER)gcc
led.bin: $(OBJS)
$(CROSS_COMPILER)ld -Tlink.lds -o led.elf $^
$(CROSS_COMPILER)objcopy -O binary led.elf led.bin
$(CROSS_COMPILER)objdump -D led.elf > led_elf.dis
gcc mkv210_image.c -o mk210
./mk210 led.bin smart210.bin
%.o:%.c
$(CC) $(CFLAGS) -c $^ -o [email protected]
%.o:%.S
$(CC) $(CFLAGS) -c $^ -o [email protected]
.PHONY = clean
clean:
rm -rf *.o *.elf *.bin *.dis mk210