uboot代码重定位

这篇文章主要基于u-boot-2012.04.01版本的代码进行分析。

对于uboot启动流程分析参考:

对于uboot源码的分析参考:

uboot重定位代码分析

​ uboot的重定位代码位于start.S的relocate_code标号处,“函数”的注释如下:

/*
 * void relocate_code (addr_sp, gd, addr_moni)
 *
 * This "function" does not return, instead it continues in RAM
 * after relocating the monitor code.
 *
 */
 	.globl	relocate_code
relocate_code:
	mov	r4, r0	/* save addr_sp */
	mov	r5, r1	/* save addr of gd */
	mov	r6, r2	/* save addr of destination */

调用该函数需要三个参数:

  1. addr_sp:新的栈地址;
  2. gd:全局数据的地址,全局数据(global_data)里面记录一些信息。如:IRQ中断的栈指针、RAM的大小、U-Boot在RAM的起始地址等等;
  3. addr_moni:uboot代码的新地址;

从源码我们可以看见,r4、r5、r6分别存放这三个数据。

	/* Set up the stack	*/
stack_setup:
	mov	sp, r4

首先是重新设置栈地址,将其从 0 x 30000000 0x3000 0000 0x30000000 (数值分析见uboot源码的分析)改为addr_sp中记录的新的栈地址。

	adr	r0, _start
	/* 先比较当前地址(r0处)和新地址(r6处记录的addr_moni)是否相同,
	 * 如果不同才进行重定位操作。否则直接跳转到清除clear_bss处的代码执行
	 */
	cmp	r0, r6
	beq	clear_bss		/* skip relocation */
	mov	r1, r6			/* r1 <- scratch for copy_loop */
	ldr	r3, _bss_start_ofs
	add	r2, r0, r3		/* r2 <- source end address	*/

设置好栈地址后,继续往下。获取uboot的源码长度,包括text代码段、data数据段、还有记录了符号信息的符号段.rel.dyn和.dynsym两个段。

r0被指定存放_start的地址,r3被指定存放_bss_start_ofs,找到_bss_start_ofs的定义,我们可以看到它就是__bss_start与_start的偏移地址之差。

_bss_start_ofs:
	.word __bss_start - _start

需要注意的是第二行处的cmp r0, r6,先比较当前地址(r0处)和新地址(r6处记录的addr_moni)是否相同,如果不同才进行重定位操作。否则直接跳转到清除clear_bss处的代码执行。

明白了这一点,我们就可以继续往下了,我们记录一下当前(部分)寄存器内的数据是:

  • r0:记录的是 _start处的地址,uboot代码旧的起始地址;

  • r1:uboot代码的新地址;

  • r2:记录的是 __bss_start的地址,旧bss段的起始地址;

  • r3:记录的是 _bss_start_ofs,即uboot代码和非bss数据的总长度;

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

继续往下就是进行内存拷贝,或者也叫自搬移操作。从注释可以看出,该代码就是将r0的uboot代码,拷贝到r1处的新地址

ldmia r0!, {r9-r10}:将r0所指单元的数据拷贝到r9寄存器,然后r0向高位递增,接着取出当前处的数据放入r10寄存器。

stmia r1!, {r9-r10}:与上面相似,不过是将r9、r10依次放入r1所指空间,并且自动地址r1。

cmp r0, r2:当r0递增到r2处,也就是到达bss段的起始地址时,拷贝结束。

拷贝完就结束了吗?并不是的,一些符号表信息需要修改,例如:

_start:	b	start_code
	ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq
	ldr	pc, _fiq

_undefined_instruction:	.word undefined_instruction
_software_interrupt:	.word software_interrupt
_prefetch_abort:	.word prefetch_abort
_data_abort:		.word data_abort
_not_used:		.word not_used
_irq:			.word irq
_fiq:			.word fiq

在start.S的开始处,我们定义了中断向量表,我们查看uboot的反汇编代码:

u-boot:     file format elf32-littlearm

Disassembly of section .text: 

00000000 <__image_copy_start>:
       0:   ea000013    b   54 <start_code>
       4:   e59ff014    ldr pc, [pc, #20]   ; 20 <_undefined_instruction>
       8:   e59ff014    ldr pc, [pc, #20]   ; 24 <_software_interrupt>
       c:   e59ff014    ldr pc, [pc, #20]   ; 28 <_prefetch_abort>
      10:   e59ff014    ldr pc, [pc, #20]   ; 2c <_data_abort>
      14:   e59ff014    ldr pc, [pc, #20]   ; 30 <_not_used>
      18:   e59ff014    ldr pc, [pc, #20]   ; 34 <_irq> 
      1c:   e59ff014    ldr pc, [pc, #20]   ; 38 <_fiq> 

00000020 <_undefined_instruction>:
      20:   000001e0    .word   0x000001e0

可以看到如对于未定义指令的中断向量_undefined_instruction,位于0x20处的00000020 <_undefined_instruction>:,里面记录的0x000001e0这个地址,这个地址是undefined_instruction的中断向量入口。所以我们还需要修改类似_undefined_instruction这样类型的符号内的地址信息。

那么我们怎么判断哪些是需要修改的呢?

其实这些符号的信息就记录在**.rel.dyn段里,而.rel.dyn**内地址符号表分为两种:

第一种是相对地址(relative),只需要在原理的地址上面加上新的偏移地址就可以了,如我们上面的undefined_instruction。只需要_undefined_instruction内记录的地址000001e0,我们就只需要 000001 e 0 − _ s t a r t + a d d r m o n i 000001e0 - \_start + addr_moni 000001e0_start+addrmoni ,就可以计算得到新的undefined_instruction地址。

第二种比较复杂是绝对地址(absolute),此时**.rel.dyn中记录的地址就只是一个标号,这个标号指向.dynsym里面的一个下标,而真实的地址记录在.dynsym里面,那么这时我们需要修改的就是.dynsym**里面的地址。

往下的代码就是重定位修改符号表,下面进行代码重定位操作,修改符号表(符号段.rel.dyn和.dynsym)内的信息:

fixloop:
	/* 这段代码段的意思是,从.rel.dyn中读取一个word,
	 * 这个word保存的是一个地址
	 * 由于代码已经重定位了,因此这个地址加上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 symbol addrress */
	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

参考:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值