ARM Uboot经历——Uboot代码重定位

Uboot重定位是uboot启动后的一个重要功能,重定位的目的是为了让uboot运行在速度性能更好的RAM上,一般是从外部RAM搬移到内部RAM。重定位这块说简单也简单,说复杂也复杂,主要涉及到编译和链接等相关原理才能很好的理解整个过程。另外,理解了ARM的重定位,对于Kernel启动过程的重定位也就理解了。

编译器惹的祸

uboot重定位不是简单的copy,归根结底在于gcc的编译和链接过程。编译和链接的过程决定了重定位不是简单的copy,而是一件包含有技术含量的事情。

举个简单的例子来说明为什么不是简单的copy。ARM访问一个全局变量,一般的情况是先将地址写到寄存器里面,然后通过寄存器寻址来访问。假设地址是0xC0000000,那么ARM指令是无法直接将地址写入的,因为ARM最长指令也才32bit,不可能包含一个32bit的地址。那是不是可以分两步写?这个是可以,但ARM的编译器通常不这么做。

ARM会在编译的时候产生一个section,这个section是紧邻着代码段,这个section就会保留一个word存储了0xC0000000这个地址。当ARM在这段代码中要访问这个全局变量的时候,ARM会寻到这个section的起始地址,然后编译器计算这个偏移,根据起始地址和偏移计算得到保存0xC0000000的地址,最终得到这个需要地址再进行寻址。注意,从section的首地址到计算偏移这个过程是在编译链接的时候就完成了,因此代码中看到的汇编就是对地址的直接访问。

举例来说:

0x0000   test()
...
/* read 全局变量,地址为0xC0000000 */
0x0010   ldr r1, [r0] /* 读取保存0xC0000000的地址 */
0x0014 ldr r2, [r1] /* 读取0xc0000000的内容 */
...
0x0100  .section
0x0100  0xC0000000

简单来说,在每个代码段都会包含一个section,这个section保留了代码里面由于指令长度限制不能直接访问的变量或者函数的地址。

那么,重定位的时候,所有代码和数据都不在原来位置上了,那么这个section里面的所有数据的内容也就要改变。因此,简单的copy是不行的,还有些section里面的数据要重新写入。

uboot重定位过程

uboot的重定位包括3个过程,启动、拷贝和修改。哪些是拷贝,哪些是修改,这是通过对内存划分来决定的。内存划分就是链接时用到的lds文件。

lds文件

lds文件在这个目录下arch\arm\cpu\u-boot.lds文件,这里把整个系统空间分成了几个大块:依次为.text, .data, .rel和.bss段。

SECTIONS
{
	. = 0x00000000;

	. = ALIGN(4);
	.text :
	{
		__image_copy_start = .;
		CPUDIR/start.o (.text*)
		*(.text*)
	}

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

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

	. = ALIGN(4);

	. = .;

	. = ALIGN(4);
	.u_boot_list : {
	#include 
	}

	. = ALIGN(4);

	__image_copy_end = .;

	.rel.dyn : {
		__rel_dyn_start = .;
		*(.rel*)
		__rel_dyn_end = .;
	}

	.dynsym : {
		__dynsym_start = .;
		*(.dynsym)
	}

	_end = .;

	/*
	 * Deprecated: this MMU section is used by pxa at present but
	 * should not be used by new boards/CPUs.
	 */
	. = ALIGN(4096);
	.mmutable : {
		*(.mmutable)
	}

	.bss __rel_dyn_start (OVERLAY) : {
		__bss_start = .;
		*(.bss*)
		 . = ALIGN(4);
		__bss_end__ = .;
	}

}
.text段毫无疑问就保存了各种代码,.data包括(.rodata和.data)保存了初始化的各种全局变量,.rel段很关键,就是指导我们上面提高的需要修改的内容的段落,.bss段就保存了未初始化的全局变量的内容。

uboot重定位启动

启动代码如下:

ENTRY(relocate_code)
	mov	r4, r0	/* save addr_sp */
	mov	r5, r1	/* save addr of gd */
	mov	r6, r2	/* save addr of destination */

	adr	r0, _start	/* 将r0指向_start的地址,是整个uboot的首地址 */
	cmp	r0, r6		/* r6是目的地址,也就是重定位后的首地址,这里比较两者是否相同,如果相同则不需重定位 */
	moveq	r9, #0		/* no relocation. relocation offset(r9) = 0 */
	beq	relocate_done		/* skip relocation */
	mov	r1, r6			/* r1 = 目的地址 */
	ldr	r3, _image_copy_end_ofs	/* _image_copy_end_ofs是在lds文件中定义的,就是.data段落之后,.rel段之前的那个地址 */
	add	r2, r0, r3		/* r2 =	源地址    */

启动代码的主要作用就是计算需要拷贝的起始地址和结束地址。起始地址就是_start函数的地址,结束地址就是_image_copy_end_ofs的地址。

拷贝过程

拷贝过程代码如下:

copy_loop:
	ldmia	r0!, {r9-r10}		/* copy from source address [r0]    */
	stmia	r1!, {r9-r10}		/* copy to   target address [r1]    */
	cmp	r0, r2			/* until source end address [r2]    */
	blo	copy_loop
拷贝过程很简单,就是将源地址拷贝到目的地址,然后源地址递增,比较是否到了结束地址,如果到了就结束,否则继续循环。

修改过程

在将修改代码之前,需要具体下ARM里面的对于.rel段落的格式问题。.rel段落保存了相对跳转的地址和相对跳转的类型。对每个相对跳转入口的结构体如下:

struct
{
    unsigned int address;
    unsigned int type;
};

按照最开始的例子,address保存的就是0x0100的地址,type主要有两种"R_ARM_ABS32"和“R_ARM_RELATIVE"。羞涩的是,我还没怎么弄明白这两种的具体出处,但是不妨碍我们理解代码。

修改过程的代码如下:

	/*
	 * fix .rel.dyn relocations
	 */
	ldr	r0, _TEXT_BASE		/* _TEXT_BASE在start.s中定义,一般也是保存了_start函数的地址,也就是uboot首地址 */
	sub	r9, r6, r0		/* r9 = 目的地址-原地址,其实就是搬移的偏移 */

	/* _dynsym_start_ofs,_rel_dyn_start_ofs和_rel_dyn_end_ofs的定义在此代码的最下方 */
	/* 作用就是计算.rel.dyn代码段的起始位置,结束位置和.dynsym代码的起始位置,这三个位置相对于_start函数的偏移 */
	/* 然后分别和r0相加得到重定位前的地址 */
	/* 这里之所以有先减去_start的地址,然后加上TEXT_BASE的地址,是因为有时候uboot的运行地址和加载地址是不同的 */
	ldr	r10, _dynsym_start_ofs	/* r10 <- sym table ofs */
	add	r10, r10, r0		/* r10 <- sym table in FLASH */
	ldr	r2, _rel_dyn_start_ofs	/* r2 <- rel dyn start ofs */
	add	r2, r2, r0		/* r2 <- rel dyn start in FLASH */
	ldr	r3, _rel_dyn_end_ofs	/* r3 <- rel dyn end ofs */
	add	r3, r3, r0		/* r3 <- rel dyn end in FLASH */

fixloop:
	/* 这段代码段的意思是,从.rel.dyn中读取一个word,这个word保存的其实是一个地址(就是上面举例中的0x0100这个地址 */
	/* 由于代码已经重定位了,因此这个地址加上offset就成了重定位后的地址 */
	/* 继而去读重定位之后偏移4byte的内容,也就是type的内容,这个参数是如果是23,那么就是相对修正表,如果是2那么就是绝对修正表 */
	ldr	r0, [r2]		/* r0 <- location to fix up, IN FLASH! */
	add	r0, r0, r9		/* r0 <- location to fix up in RAM */
	ldr	r1, [r2, #4]
	and	r7, r1, #0xff
	cmp	r7, #23			/* relative fixup? */
	beq	fixrel
	cmp	r7, #2			/* absolute fixup? */
	beq	fixabs
	/* ignore unknown type of fixup */
	b	fixnext
fixabs:
	/* absolute fix: set location to (offset) symbol value */
	/* 绝对修正表需要用的.dynsym里面的数值,这时address里面保存的只是一个_label,可以理解为.rel里面保存了一个偏移*/
	/* 根据这个偏移到.dynsym里面找到一个值,这个就是真实的地址 */
	/* 将真实的地址加上重定位后的offset,就将源地址改为了重定位之后的地址 */
	mov	r1, r1, LSR #4		/* r1 <- symbol index in .dynsym */
	add	r1, r10, r1		/* r1 <- address of symbol in table */
	ldr	r1, [r1, #4]		/* r1 <- symbol value */
	add	r1, r1, r9		/* r1 <- relocated sym addr */
	b	fixnext
fixrel:
	/* relative fix: increase location by offset */
	/* 相对修正就没那么复杂,.rel里面就直接保存了需要的地址,直接修改就可以了 */
	ldr	r1, [r0]
	add	r1, r1, r9
fixnext:
	str	r1, [r0]
	add	r2, r2, #8		/* each rel.dyn entry is 8 bytes */
	cmp	r2, r3
	blo	fixloop
relocate_done:
	bx	lr

_rel_dyn_start_ofs:
	.word __rel_dyn_start - _start
_rel_dyn_end_ofs:
	.word __rel_dyn_end - _start
_dynsym_start_ofs:
	.word __dynsym_start - _start
ENDPROC(relocate_code)

至此,在.rel段落的指导下,uboot终于能成功的将需要修改的部分修改,完成了整个过程中最复杂也是最难的部分。


总结

重定位需要完全理解很复杂,比如相对修正表和绝对修正表的使用规则我就在google上没有搜索到。可能还是需要大牛的指导。

对于重定位,如果看完这篇文章你有所收获,建议看一篇更高级的文档,里面讲的更全面,当然也更复杂。

http://www.verydemo.com/demo_c269_i1305.html












你可能感兴趣的:(嵌入式研发)