所谓的relocation,就是重定位,uboot运行后会将自身代码拷贝到sdram的另一个位置继续运行。新版uboot跟老版uboot不太一样的地方在于新版uboot不管uboot的load addr(entry pointer)在哪里,启动后会计算出一个靠近sdram顶端的地址,将自身代码拷贝到该地址,继续运行。
uboot的编译选项发现,在arch/arm/config.mk,如下:
# needed for relocation
LDFLAGS_u-boot += -pie
uboot只指定了-pie给ld,而没有指定-fPIC或-fPIE给gcc。
指定-pie后编译生成的uboot中就会有一个rel.dyn段,uboot就是靠rel.dyn段实现了完美的relocation!
ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */ bic r0, r0, #7 /* 8-byte alignment for ABI compliance */ mov sp, r0 ldr r9, [r9, #GD_NEW_GD] /* r9 <- gd->new_gd */ adr lr, here ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */ add lr, lr, r0 #if defined(CONFIG_CPU_V7M) orr lr, #1 /* As required by Thumb-only */ #endif ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ b relocate_code here: /* * now relocate vectors */ bl relocate_vectors
NTRY(relocate_code) ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */ subs r4, r0, r1 /* r4 <- relocation offset */ beq relocate_done /* skip relocation */ ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */ copy_loop: ldmia r1!, {r10-r11} /* copy from source address [r1] */ stmia r0!, {r10-r11} /* copy to target address [r0] */ cmp r1, r2 /* until source end address [r2] */ blo copy_loop
r1=__image_copy_start,也就是 r1 寄存器保存源地址
r0= gd->relocaddr 这个地址就是 uboot 拷贝的目标首地址, 此处的gd为放置到sdram高端的gd,是在setup_reloc函数中对原来gd的复制。
/*主要目的是修改label中的地址。
*对于一些绝对地址符号(例如已经初始化的全局变量),会将其以label的形式放在每个函数的代码实现的末端。
*同时,在链接的过程中,会把这些label的地址统一维护在.rel.dyn段中,当relocation的时候,方便对这些地址的fix。 * fix .rel.dyn relocations */ ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */ ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */ fixloop: ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */ and r1, r1, #0xff cmp r1, #R_ARM_RELATIVE bne fixnext /* relative fix: increase location by offset */
// label在relocate uboot的时候也已经复制到了新的uboot地址空间了!!!
// 这里要注意,是对新的uboot地址空间label进行修改!!!
add r0, r0, r4
// 根据前面的描述,我们的目的就是要fix label中绝对地址符号的地址,也就是将其修改为新地址空间的地址
// 所以为r1加上偏移之后,重新存储到label中。
// 后面CPU就可以根据LABEL在新uboot的地址空间中寻址到正确的符号。
//对于一些绝对地址符号(例如已经初始化的全局变量),会将其以label的形式放在每个函数的代码实现的末端。
//同时,在链接的过程中,会把这些label的地址统一维护在.rel.dyn段中,当relocation的时候,方便对这些地址的fix
ldr r1, [r0]
// 从label中获取绝对地址符号的地址,存放在r1中 add r1, r1, r4 str r1, [r0]
// 根据前面的描述,我们的目的就是要fix label中绝对地址符号的地址,也就是将其修改为新地址空间的地址
// 所以为r1加上偏移之后,重新存储到label中。
// 后面CPU就可以根据LABEL在新uboot的地址空间中寻址到正确的符号。 fixnext: cmp r2, r3 blo fixloop
可以看出__image_copy_start---end之间包括了text data rodata段,但是没有包括rel_dyn。
继续看relocate_code函数,拷贝__image_copy_start----end之间的数据,但没有拷贝rel.dyn段。
首先获取__rel_dyn_start地址到r2,将start地址上连续2个4字节地址的值存在r0 r1中
判断r1中的值低8位,如果为0x17,则将r0中的值加relocation offset。
获取以此r0中值为地址上的值,存到r1中
将r1中值加relocation offset,再存回以r0中值为地址上。
以此循环,直到__rel_dyn_end。
* 绝对地址符号的地址会放在label中提供位置无关代码使用
* label的地址会放在.rel.dyn段中
当uboot对自身进行relocate之后,此时全局变量的绝对地址已经发生变化,如果函数按照原来的label去获取全局变量的地址的时候,这个地址其实是relocate之前的地址。因此,在relocate的过程中需要对全局变量的label中的地址值进行修改,所以uboot将这些label的地址全部维护在.rel.dyn段中,然后再统一对.rel.dyn段指向的label进行修改。后续代码可以看出来。可以看出.rel.dyn段用了8个字节来描述一个label,其中,高4字节是label地址标识0x17,低4字节就是label的地址。
所以需要先判断label地址标识是否正确,然后再根据第四字节获取label,对label中的符号地址进行修改。
ARM9中断向量重定位
* Copy the relocated exception vectors to the * correct address * CP15 c1 V bit gives us the location of the vectors: * 0x00000000 or 0xFFFF0000. */ ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */ ands r2, r2, #(1 << 13) ldreq r1, =0x00000000 /* If V=0 */ ldrne r1, =0xFFFF0000 /* If V=1 */ ldmia r0!, {r2-r8,r10} stmia r1!, {r2-r8,r10} ldmia r0!, {r2-r8,r10} stmia r1!, {r2-r8,r10}
arm9手册中:
[13] V bit Location of exception vectors:
0 = Normal exception vectors selected, address range = 0x0000 0000 to
0x0000 001C
1 = High exception vectors selected, address range = 0xFFFF 0000 to
0xFFFF 001C. Set to the value of VINITHI on reset.
/*
* Copy the relocated exception vectors to the
* correct address
* CP15 c1 V bit gives us the location of the vectors:
* 0x00000000 or 0xFFFF0000.
*/
@@ 注意看注释,通过cp15协处理器的c1寄存器的V标志来判断cpu从什么位置获取中断向量表,
@@ 换句话说,就是中断向量表应该被复制到什么地方!!!
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
@@ 获取uboot新地址空间的起始地址,存放到r0寄存器中
mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */
ands r2, r2, #(1 << 13)
ldreq r1, =0x00000000 /* If V=0 */
ldrne r1, =0xFFFF0000 /* If V=1 */
@@ 获取cp15协处理器的c1寄存器的V标志,当V=0时,cpu从0x00000000获取中断向量表,当V=1时,cpu从0xFFFF0000获取中断向量表
@@ 将该地址存在r1中
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
@@ 前面说了异常中断向量表就是从偏移0x20开始的32个字节。
@@ 所以这里是过滤掉前面的0x20个字节(32个字节,8*4)
@@ 但是不明白为什么还要stmia r1!, {r2-r8,r10},理论上只需要让r0的值产生0x20的偏移就可以了才对???不明白。
//一下的数据是中断矢量的全局符号,这些符号在rel段中已经进行了位置重新计算。
@@ 经过上述两行代码之后,此时r0的值已经偏移了0x20了
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
@@ 继续从0x20开始,获取32个字节,存储到r1指向的地址,也就是cpu获取中断向量表的地址
@@ r2-r8,r10表示从r2到r8寄存器和r10寄存器,一个8个寄存器,每个寄存器有4个字节,所以就从r0指向的地址处获取到了32个字节
@@ 再把 {r2-r8,r10}的值存放到r1指向的地址,也就是cpu获取中断向量表的地址