【ARM裸机】 - 重定位

1、 重定位的目的

       对于2440上电后,分两种情况,从nor启动,直接从nor作为0地址,开始在nor中运行。从nand启动,拷贝前4K到片内SRAM中。当代码大于4K的时候,我们需要重定位代码到更大的SDRAM中去运行。从nor运行时,由于nor只可读而不可以修改nor中的内容,就会导致一些全局变量,在代码中无法修改,此时我们可以将全局变量重定位(指定链接地址)到SDRAM中,这样我就可以nor中运行,然后对全局变量进行修改。

       总结来说:重定位就是代码的存储地址和运行地址不一致,我们需要把代码拷贝到运行地址处。可以重定位所有代码,也可以重定位数据段等。由于代码存在flash中,一般可在链接脚本中加入AT()来指定加载地址,以防止编译出的代码过大。

       链接脚本中,指定程序运行地址位于SDRAM,上电后从片内0地址开始运行,所以重定位前是位置无关码。重定位后,跳转到SDRAM中运行。


2、 程序组成

  1. 代码段(text):运行指令
  2. 数据段(data):全局变量
  3. 只读数据段(rodata):const全局变量
  4. Bss段:初值为0的全局变量
  5. Common段:注释

3、使用链接脚本

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 -Ttext 0 -Tdata 0x30000000  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

当代码段与数据段之间差距较大时,我们编译出的可执行文件会很大,这样会导致无法下载运行,如上。由于我们从nor启动,又想修改全局变量,所以我们要对数据段进行重定位。从0地址放代码段,然后0x800放数据段,当程序运行时,将0x800的数据段复制到0x30000000地址去,这样就可以在nor启动时,实现修改全局变量。

此时链接脚本这样写:
SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) { *(.data) }
   .bss  : { *(.bss) *(.COMMON) }
}

代码中重定位数据段内容到0x30000000:

	bl sdram_init	

	/* 重定位data段 */
	mov r1, #0x800
	ldr r0, [r1]
	mov r1, #0x30000000
	str r0, [r1]

	bl main

代码中包含可能变化的数字,可以当数据段较大的时候,灵活性很差,此时我们可以完善链接脚本:

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);
      data_start = . ;
      *(.data) 
      data_end = . ;
   }
   .bss  : { *(.bss) *(.COMMON) }
}
并优化代码:
	bl sdram_init	

	/* 重定位data段 */
	ldr r1, =data_load_addr  /* data段在bin文件中的地址, 加载地址 */
	ldr r2, =data_start 	 /* data段在重定位地址, 运行时的地址 */
	ldr r3, =data_end 	     /* data段结束地址 */

cpy:
	ldrb r4, [r1]
	strb r4, [r2]
	add r1, r1, #1
	add r2, r2, #1
	cmp r2, r3
	bne cpy

	bl main

另一种方法是,我们让程序的链接地址是0x30000000,上电后从0地址运行,此时把所有代码复制到0x30000000开始的地方,然后到SDRAM中运行。

3、 bss段

存储了初值为0的变量,bss段不保存在bin中,但是我们要链接bss的地址,并清除bss段,否在初值会不定。因此链接脚本如下:
SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);
      data_start = . ;
      *(.data) 
      data_end = . ;
   }
   
   bss_start = .;
   .bss  : { *(.bss) *(.COMMON) }
   bss_end = .;
}

Bss会跟着data段的地址往后排,因此bss地址为0x3XXXXXXX。然后在代码中清除bss段:

	bl sdram_init	

	/* 重定位data段 */
	ldr r1, =data_load_addr  /* data段在bin文件中的地址, 加载地址 */
	ldr r2, =data_start 	 /* data段在重定位地址, 运行时的地址 */
	ldr r3, =data_end 	     /* data段结束地址 */

cpy:
	ldrb r4, [r1]
	strb r4, [r2]
	add r1, r1, #1
	add r2, r2, #1
	cmp r2, r3
	bne cpy


	/* 清除BSS段 */
	ldr r1, =bss_start
	ldr r2, =bss_end
	mov r3, #0
clean:
	strb r3, [r1]
	add r1, r1, #1
	cmp r1, r2
	bne clean

	bl main

上面是按字节拷贝和读取,nor是16bit,sdram是32bit,可以改为按4字节操作:

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, =bss_end
	mov r3, #0
clean:
	str r3, [r1]
	add r1, r1, #4
	cmp r1, r2
	ble clean

	bl main
同时完善链接脚本,使4字节对齐:
SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);
	  . = ALIGN(4);
      data_start = . ;
      *(.data) 
      data_end = . ;
   }
   
   . = ALIGN(4);
   bss_start = .;
   .bss  : { *(.bss) *(.COMMON) }
   bss_end = .;
}

4、 链接脚本解析

http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html

5、 位置无关码-重定位所有代码

(1)标准的链接脚本
SECTIONS
{
	. = 0x30000000;

	__code_start = .;

	. = ALIGN(4);
	.text      :
	{
	  *(.text)
	}

	. = ALIGN(4);
	.rodata : { *(.rodata) }

	. = ALIGN(4);
	.data : { *(.data) }

	. = ALIGN(4);
	__bss_start = .;
	.bss : { *(.bss) *(.COMMON) }
	_end = .;
}
(2)位置无关码
调转的时候使用的是偏移地址,相对跳转 如B/BL,不使用全局变量、静态变量、不使用有初始值的数组。
重定位前使用位置无关码,不使用绝对地址。看反汇编。
一切就绪之后:ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
	bl sdram_init
	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	mov r0, #0
	ldr r1, =_start 	    /* 第1条指令运行时的地址 */
	ldr r2, =__bss_start    /* bss段的起始地址 */
	sub r2, r2, r1

	bl copy2sdram  /* src, dest, len */

	/* 清除BSS段 */
	ldr r0, =__bss_start
	ldr r1, =_end

	bl clean_bss  /* start, end */

	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */

halt:
	b halt
重定位及清楚bss的c代码
void copy2sdram(volatile unsigned int *src, volatile unsigned int *dest, unsigned int len)  /* src, dest, len */
{
	unsigned int i = 0;

	while (i < len)
	{
		*dest++ = *src++;
		i += 4;
	}
}

void clean_bss(volatile unsigned int *start, volatile unsigned int *end)  /* start, end */
{
	while (start <= end)
	{
		*start++ = 0;
	}
}
ldr pc, =main 之前的都是位置无关码。通过 ldr pc, =main跳转到SDRAM中继续执行。
(3)在C中直接获得链接脚本中的地址信息
C函数怎么使用lds文件中的变量abc?
a. 在C函数中声明改变量为extern类型, 比如:
    extern int abc;
b. 使用时, 要取址, 比如:
     int *p = &abc;  // p的值即为lds文件中abc的值
c. 汇编可直接使用
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++;
	}
}


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;
	}
}



猜你喜欢

转载自blog.csdn.net/fengyuwuzu0519/article/details/78939758