上一小节已经分析完了 board_init_f的内容,接下来我们从他后面继续分析
mov r0, #0
bl board_init_f
#if ! defined(CONFIG_SPL_BUILD) /* 没定义,所以要执行,详细的后面拆分开分析 */
/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endif
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0 /* lr = new_uboot-old_uboot + here */
#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 /* 重定位中断向量地址 */
/* Set up final (full) environment */
/* 建立C运行环境 */
bl c_runtime_cpu_setup /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# ifdef CONFIG_SPL_BUILD /* 这个没定义 */
/* Use a DRAM stack for the rest of SPL, if requested */
bl spl_relocate_stack_gd
cmp r0, #0
movne sp, r0
movne r9, r0
# endif
/* 清bss段 */
ldr r0, =__bss_start /* this is auto-relocated! */
#ifdef CONFIG_USE_ARCH_MEMSET
ldr r3, =__bss_end /* this is auto-relocated! */
mov r1, #0x00000000 /* prepare zero to clear BSS */
subs r2, r3, r0 /* r2 = memset len */
bl memset
#else
ldr r1, =__bss_end /* this is auto-relocated! */
mov r2, #0x00000000 /* prepare zero to clear BSS */
clbss_l:cmp r0, r1 /* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
itt lo
#endif
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
#endif
#if ! defined(CONFIG_SPL_BUILD) /* 点个灯,调试用 */
bl coloured_LED_init
bl red_led_on
#endif
/* call board_init_r(gd_t *id, ulong dest_addr) */
/* 把gd_t和新的uboot的起始地址传给board_init_r */
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r */
#if defined(CONFIG_SYS_THUMB_BUILD)
ldr lr, =board_init_r /* this is auto-relocated! */
bx lr
#else
ldr pc, =board_init_r /* this is auto-relocated! */
#endif
/* we should not return here. */
#endif
ENDPROC(_main)
分析之前,先把上一节末尾的图拿出来,以备查询
1.1、先分析relocate之前的代码
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp,对照上图可以发现在fdt下面 */
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8-byte alignment for ABI compliance,8字节对齐sp */
#endif
ldr r9, [r9, #GD_BD] /* r9 = gd->bd,先找到bd位置 */
sub r9, r9, #GD_SIZE /* new GD is below bd,gd就在bd下面,从这后面开始r9,即gd指针已经是用新的重定位后的内存里的数据了*/
/* 下面三句代码是 很有意思的,我们来重点分析一下 */
/* adr是个位置无关的加载指令,及无论 here的链接地址是多少,adr加载的都是当前pc + 偏移(adr lr,here到here的偏移)
然后得到新的规划的uboot和旧的uboot之家的偏移量给r0
之后把偏移和旧的uboot的here加起来,最终得到新的uboot里面的here的地址,然后放进lr
等进入下面的relocate_code函数,返回的时候就直接返回到新的uboot的代码段运行了
*/
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0 /* lr = new_uboot-old_uboot + here */
#if defined(CONFIG_CPU_V7M)
orr lr, #1 /* As required by Thumb-only */
#endif
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr,给relocate的传参是将要重定位的起始地址 */
b relocate_code /* 注意这里必须使用b跳转,这样返回时才能用前面设置的lr中国新的here地址 */
here:
在分析relocate_codez之前我们先分析一下uboot中各个段的含义
.text 代码段
.rodata 只读数据段
.data 数据段
.u_boot_list uboot的命令
.efi_runtime/efi_runtime_rel 不懂,跳过
.rel.dyn 存放.text段中需要重定位地址的集合
.mmutable 存放页表的段
.bss 初始化为0的全局或静态变量
1.2、relocate_code 这里先放出来代码,接下来差分开来一段一段分析
ENTRY(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
/*
* 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) */
/* r1 = r1 & 0xff */
and r1, r1, #0xff
/* 比较是否要更改 */
cmp r1, #R_ARM_RELATIVE
bne fixnext
/* relative fix: increase location by offset */
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop
relocate_done:
#ifdef __XSCALE__
/*
* On xscale, icache must be invalidated and write buffers drained,
* even with cache disabled - 4.2.7 of xscale core developer's manual
*/
mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif
/* ARMv4- don't know bx lr but the assembler fails to see that */
#ifdef __ARM_ARCH_4__
mov pc, lr
#else
bx lr
#endif
ENDPROC(relocate_code)
1.2.1、对比下图分析拷贝的内容
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start,旧的uboot的起始地址 */
subs r4, r0, r1 /* r4 <- relocation offset,新的relocate_addr - 旧的uboot起始地址 给r4 */
beq relocate_done /* skip relocation ,上面相减完,相等表示不用重定位了,显然我们不相等*/
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end 旧的uboot的__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
下面画出前四句代码指定的r0,r1,r2,r4的位置
copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] 把r1地址的数据放到r10,r1自增4,然后把r1地址的数据放到r11后把r1自增4 */
stmia r0!, {r10-r11} /* copy to target address [r0] 把r0地址的数据放到r0所在地址后r0自增4,把r11内容放到r0地址后r0自增4 */
/* 比较image段是否拷贝结束,没有则继续拷贝至结束 */
cmp r1, r2 /* until source end address [r2], */
blo copy_loop
接下来就要修改rel.dyn段了
/*
* 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:
/* 把r2 指向内存块的连续值加载到r0和r1中,加载完后再增加8个字节 */
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
/* r1 = r1 & 0xff */
and r1, r1, #0xff
/* 比较是否要更改 */
cmp r1, #R_ARM_RELATIVE
bne fixnext
/* relative fix: increase location by offset */
add r0, r0, r4 /* r0 += r4 r4=新旧.text的偏移 */
ldr r1, [r0] /* 得到新的.text段里面的数据 */
add r1, r1, r4 /* 数据偏移 */
str r1, [r0] /* 写回偏移后新的数据 */
fixnext:
cmp r2, r3 /* 判断有处理完 */
blo fixloop
直行完前两行的代码后r2、r3如下
接下来我们来说明一个很重要的概念。
u-boot在启动过程中,会把自己拷贝到RAM的顶端去执行。这一拷贝带来的问题是执行地址的混乱。代码的执行地址通常都是在编译时有链接地址指定的,如何保证拷贝前后都可以执行呢?
一个办法是使用拷贝到RAM后的地址作为编译时的链接地址,拷贝前所有函数与全局变量的调用都增加偏移量。(如VxWorks的bootloader)尽量减少拷贝前需要执行的代码量。
另一个办法是把image编译成与地址无关的程序,也就是PIC - Position independent code。编译器无法保证代码的独立性,它需要与加载器配合起来。U-boot自己加载自己,所以它自己就是加载器。PIC依赖于下面两种技术:
1) 使用相对地址
2) 加载器可以自动更新涉及到绝对地址的指令
对于PowerPC架构,u-boot只是在编译时使用了-fpic,这种方式会生成一个.got段来存储绝对地址符号。对于ARM架构,则是在编译时使用-mword-relocations,生成与位置无关代码,链接时使用-pie生成.rel.dyn段,该段中的每个条目被称为一个LABEL,用来存储绝对地址符号的地址。
为了理解地址表的概念我们分析一段代码。
借助一个工具
arm-none-linux-gnueabi-readelf help
因为我们这里关注relocate段,所以只查看relocate段
arm-none-linux-gnueabi-readelf -r u-boot |less
可以看到它的类型是可重定位段。
为了方便起见,我们就以第一个rel.dyn段的标号为例来分析它的作用。(.efi_runtime_rel段远离相同)
每个需要修改地址的信息占用8个字节。
可以看到第一个标号34800020是可重定位的地址,00000017是标明那种CPU,0x17代表ARM32位
现在我们看一下34800020位置到底方的什么
arm-none-linux-gnueabi-objdump -S u-boot | less
可以看到它放的是一个地址。0x34800060
对应我们vectors.S里面的下面的指令
反汇编后分析,发现它就是存放的这个子过程的入口地址
因为这个入口地址如果直接.text段拷贝过去,将来执行跳转的还是旧的uboot里面的undefined_instruction,而不是我们新uboot里面的undefined_instruction,所以这个要修改。
即要修改所有位置有关码的地址。
如何修改?
很简单,旧的地址的值是什么我们取出来加上新地址和旧地址的偏移。然后存入新的地址就可以了。
因为编译器已经帮我们剥离出来要修改的地址和它所属的类型,存放进了rel.dyn段,所以我们只要修改新地址的就可以。
经过上面的分析,下面的使用rel段,修改.text段就很简单了。
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
/* 把r2 指向内存块的连续值加载到r0和r1中,同时 每次加载完一个寄存器就给r2加4 */
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
/* r1 = r1 & 0xff */
and r1, r1, #0xff
/* 比较是否属于可重定位的,当然我们是从rel段取出来的,所以肯定是 */
cmp r1, #R_ARM_RELATIVE
bne fixnext /* 如果不是则调到后面 */
/* relative fix: increase location by offset */
add r0, r0, r4 /* r0 += r4 r4=新旧.text的偏移 */
ldr r1, [r0] /* 得到新的.text段里面的未修改的数据 */
add r1, r1, r4 /* 数据修改 */
str r1, [r0] /* 写回偏移后新的数据 */
fixnext:
cmp r2, r3 /* 判断有处理完 */
blo fixloop
调进新的uboot
blo fixloop
relocate_done:
#ifdef __XSCALE__ /* 是否无效cache和我们关系不大,无所谓 */
/*
* On xscale, icache must be invalidated and write buffers drained,
* even with cache disabled - 4.2.7 of xscale core developer's manual
*/
mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif
/* ARMv4- don't know bx lr but the assembler fails to see that */
#ifdef __ARM_ARCH_4__
mov pc, lr
#else
bx lr /* 我们从这里返回,还记得lr是调用这个函数之前设置好的,新的uboot的here的地址吗,这里返回后就,运行在新的uboot里面了 */
#endif
ENDPROC(relocate_code)
.text段也好了
这里有一点小疑惑,为什么不把rel段也拷贝上去新的uboot上面,毕竟地方也留了。
如果拷贝上去,是不是也该把offset也修改成新的uboot中.text里面的地址
here:
/*
* now relocate vectors
*/
bl relocate_vectors /* 重定位中断向量 */
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here,重新设置C运行时环境(因为可能cache里面存的是旧的uboot的数据和指令) */
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# ifdef CONFIG_SPL_BUILD /* 没定义 */
/* Use a DRAM stack for the rest of SPL, if requested */
bl spl_relocate_stack_gd
cmp r0, #0
movne sp, r0
movne r9, r0
# endif
/* 清bss段,很简单,直接写0就可以,要注意的是这些标号都是位置有关的,在前面的rel段已经修改过了,所以此时清的已经是新的uboot上没的bss段了 */
ldr r0, =__bss_start /* this is auto-relocated! */
#ifdef CONFIG_USE_ARCH_MEMSET
ldr r3, =__bss_end /* this is auto-relocated! */
mov r1, #0x00000000 /* prepare zero to clear BSS */
subs r2, r3, r0 /* r2 = memset len */
bl memset
#else
ldr r1, =__bss_end /* this is auto-relocated! */
mov r2, #0x00000000 /* prepare zero to clear BSS */
clbss_l:cmp r0, r1 /* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
itt lo
#endif
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
#endif
#if ! defined(CONFIG_SPL_BUILD) /* 点灯,调试,随便,想怎么点你就怎么点 */
bl coloured_LED_init
bl red_led_on
#endif
/* call board_init_r(gd_t *id, ulong dest_addr) */
/* gd的 地址和 当前新的uboot的起始地址传参给board_init_r */
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r */
#if defined(CONFIG_SYS_THUMB_BUILD)
ldr lr, =board_init_r /* this is auto-relocated! */
bx lr
#else
ldr pc, =board_init_r /* this is auto-relocated! ,这边使用pc直接赋值,说明决心很大,绝不可能return 之类回来的*/
#endif
/* we should not return here. */
#endif
ENDPROC(_main)
中断向量表的重新布置。
ENTRY(relocate_vectors)
#ifdef CONFIG_CPU_V7M /* 没定义 */
/*
* On ARMv7-M we only have to write the new vector address
* to VTOR register.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
ldr r1, =V7M_SCB_BASE
str r0, [r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR /* 定义了 */
/*
* If the ARM processor has the security extensions,
* use VBAR to relocate the exception vectors.
*/
/* 重设中断向量的基地址为我们的新uboot的入口地址 */
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
#else
/*
* 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}
#endif
#endif
bx lr /* 返回 */
ENDPROC(relocate_vectors)
ENTRY(c_runtime_cpu_setup)
/*
* If I-cache is enabled invalidate it
*/
#ifndef CONFIG_SYS_ICACHE_OFF /* 没定义,所以还是要无效cache的 */
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
mcr p15, 0, r0, c7, c10, 4 @ DSB
mcr p15, 0, r0, c7, c5, 4 @ ISB
#endif
bx lr
ENDPROC(c_runtime_cpu_setup)
总结:可以看到uboot的前半部分主要是把定位在0x34800000地址的uboot搬移到DDR的顶部位置,同时初始化了一些底层的配置,把板子的一些参数保存进了gd中,方便后面使用。