这篇文章主要基于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 */
调用该函数需要三个参数:
- addr_sp:新的栈地址;
- gd:全局数据的地址,全局数据(global_data)里面记录一些信息。如:IRQ中断的栈指针、RAM的大小、U-Boot在RAM的起始地址等等;
- 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
参考: