Part10: 代码重定位与位置无关码

1 代码重定位
什么是代码重定位呢?
来一个例子,对于S3C2440这款开发板,Nor Flash可以直接读,但不能直接写(可通过代码验证,此处忽略)。
但是如果要写的话,如何实现?
这时候就要用“代码重定位”了。有两个办法,一是重定位需写的数据到SDRAM  二 是重定位整个程序到SDRAM
下面依次讲解这两个方法。
1.1 重定位需写的数据到SDRAM
直接上代码,再解释。如下
char g_Char = 'A';
int main(void)
{
	uart0_init();  // 串口初始化
	while (1)
 	{
 		putchar(g_Char);
 		g_Char++;         /* nor启动时, 此代码无效 */
 		delay(1000000);
 	}
 	return 0;

对于g_Char 这个全局变量,烧写到Nor Flash是不能直接修改的,故g_Char++会无效
因此,这里需重定位g_Char到sdram(即0x3000,0000处), 如何实现呢?

  1. 使用链接脚本 sdram.lds
all:
 arm-linux-gcc -c -o led.o led.c
 arm-linux-gcc -c -o uart.o uart.c
 arm-linux-gcc -c -o init.o init.c
 arm-linux-gcc -c -o main.o main.c
 arm-linux-gcc -c -o start.o start.S
arm-linux-ld -T sdram.lds  start.o led.o uart.o init.o main.o -o sdram.elf
 arm-linux-objcopy -O binary -S sdram.elf sdram.bin
 arm-linux-objdump -D sdram.elf > sdram.dis
clean:
 rm *.bin *.o *.elf *.dis

2.修改start.S文件,将g_Char 写到 0x30000000,如下

bl sdram_init	//注意,使用sdram前,需初始化

mov r1, #0x800
ldr r0, [r1]
mov r1, #0x30000000
str r0, [r1]

bl main
  1. sdram.s 链接脚本内容如下
SECTIONS {
 .text 0 : { *(.text) }
 .rodata : { *(.rodata) }
 .data 0x30000000 : AT(0x800) { *(.data) }
 .bss  : { *(.bss) *(.COMMON) }
}
补充下链接脚本的语法
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
  { contents } >region :phdr =fill
...
}
解释:secname:段名   start:运行时地址(runtime addr) 或 重定位地址(relocate addr) 
BLOCK(align)  (NOLOAD )很少用,用到再查  AT(ldadr): ldadr即load addr加载地址,当未指定时,= runtime addr
{ contents } 即这个段的内容,可以指定某个目标文件对应的段 如 .text  0 : { start.o }即代码段为start.o的代码段
且放在运行时地址0. 还可指定所有目标文件的对应的段,如 .data 0xXXX : { *(.data) } 表示所有目标文件的data段
放在0xXXX开始处
>region:phdr = fill  基本不用,忽略
更详细的用法可参考官方文档:http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html 

以上面的链接脚本为例,解释如下

链接脚本解释

可能难点在运行地址 和 加载地址的 理解,对于这两个,可理解为,加载地址指 代码在“可执行程序”中的地址,
而 运行地址 即 程序加载进内存运行的地址。
1.2 重定位整个程序到SDRAM
  1. sdram.lds 链接脚本如下
SECTIONS
{
 . = 0x30000000;
 
 . = ALIGN(4);  
 .text      :
 {
   *(.text)
 }
 
 . = ALIGN(4);
 .rodata : { *(.rodata) }
 
 . = ALIGN(4);
 .data : { *(.data) }
 
 . = ALIGN(4);
 __bss_start = .;
 .bss : { *(.bss) *(.COMMON) }
 _end = .;
}
  1. 修改start.S 文件
bl sdram_init
/* 下面是复制整个程序到SDRAM的0x3000,0000开始处 */
mov r1, #0
ldr r2, =_start
ldr r3, =__bss_start

cpy:
	ldr r4, [r1]
	str r4, [r2]
	add  r1, r1, #4
	add  r2, r2, #4
	cmp  r2, r3
	ble  cpy 
	
/* 下面是清零BSS段 */
ldr r1, =__bss_start
ldr r2, =_end
mov r3, #0

clean:
	str r3, [r1]
	add r1, r1, #4
	cmp r1, r2
	ble clean

bl main

halt:
	b halt
  1. main函数如下
char g_Char = 'A';
int g_A = 0;

int main(void)
{	
	puts("\n\rg_A=");
	printHex(g_A);
 	puts("\n\r");
 	while (1)
 	{
  		putchar(g_Char);
 		g_Char++;         /* nor启动时, 此代码无效 */
  		delay(1000000);
	}
	return 0;
}
上面两个实验的效果都是一样的,如下

实验效果图

2 位置无关码
其实,上面将整个程序重定位到SDRAM的0x30000000开始的地方,就实现了位置无关码
怎么理解位置无关码呢?看下图

位置无关码

如图所示,整个程序的前面部分代码需将整个程序复制到SDRAM的开始处0x3000,0000。
那红框部分代码就是位置无关码? 其实这样理解是不对的(我一开始也这样认为:) 
实际,“位置无关码”是这整个程序。因为整个程序实际可复制到SDRAM任意地方,但是这整个程序里面的
代码有要求:
1) 在重定位整个程序到SDRAM前,不可使用绝对地址。因为还未将整个程序复制到SDRAM,那引用
SDRAM里面地址,肯定不行的啊。 具体来说,重定位前避免使用全局变量/全局静态变量,或已初始化的数组
因为这些变量在rodata 或 data段,这些段的地址是绝对地址。

2)在重定位前的代码,尽量使用相对跳转指令,如B/BL。为什么呢?整个程序重定位到新地址后,基址改变了,
但是相对偏移量offset是不变的,即寻址 PC + offset。 不管怎么修改PC,offset不变,那访问时只需修改PC即可

3)重定位之后, 使用绝对跳转命令跳到Runtime Addr,比如:
	ldr pc, =main
上面三点,也就是如何写“位置无关码”的三大技巧
2.1 C中如何使用链接脚本的变量
OK,理解了“代码重定位” 和 “位置无关码”,那怎么完善上面第二个程序呢?即使用ldr的变量,用C替换汇编实现
整个程序的复制和BSS段的清零工作呢?

实现如下
对于 复制整个程序,如下	
void copy2sdram(void)
{
 /* 要从lds文件中获得 __code_start, __bss_start
  * 然后从0地址把数据复制到__code_start
  */
 extern int __code_start, __bss_start;
 volatile unsigned int *dest = (volatile unsigned int *)&__code_start;
 volatile unsigned int *end = (volatile unsigned int *)&__bss_start;
 volatile unsigned int *src = (volatile unsigned int *)0;
 while (dest < end)
 {
  *dest++ = *src++;
 }
}
对于 清零BSS段,如下
void clean_bss(void)
{
 /* 要从lds文件中获得 __bss_start, _end
  */
 extern int _end, __bss_start;
 volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
 volatile unsigned int *end = (volatile unsigned int *)&_end;
 while (start <= end)
 {
  *start++ = 0;
 }
}
因此,start.S 即可修改为
bl sdram_init
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
上面C中的变量来自sdram.lds链接脚本,如下
SECTIONS
{
 . = 0x30000000;
 
 __code_start = .;
 
 . = ALIGN(4);
 .text      :
 {
   *(.text)
 }
 
 . = ALIGN(4);
 .rodata : { *(.rodata) }
 
 . = ALIGN(4);
 .data : { *(.data) }
 
 . = ALIGN(4);
 __bss_start = .;
 .bss : { *(.bss) *(.COMMON) }
 _end = .;
}
总结一下
C函数怎么使用lds文件中的变量abc?
a. 在C函数中声明改变量为extern类型, 比如:
		extern int abc; 
b. 使用时, 要取址, 比如:
		int *p = &abc;  // p的值即为lds文件中abc的值
		
	OK,以上就是这篇文章的所有内容(我觉得讲得不够详细,后面有时间再完善,有问题可以提出来,一起进步呀:)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值