uboot启动流程详解(5)-_main
2018-02-06 08:58
543 查看
转载地址:http://blog.csdn.net/silent123go/article/details/53198125
_main标号中主要调用的函数有三个,board_init_f,relocate_code,board_init_r,这里先贴出_main的代码并注释,然后对这三个函数的流程及原理进行详细介绍。
这个函数的主要功能就是初始化一些硬件设备(串口、定时器等)并且设置gd结构体中的成员。执行完后,gd中一些重要成员的指向如下图所示:
刚开始uboot在编译地址TEXT_BASE处执行,relocate完成后,uboot会被搬移到gd->relocaddr这个地址执行。gd->start_addr_sp是relocate完成后新的堆栈起始地址。接下来对其调用到的主要函数进行介绍。
1、获取整个uboot的大小到gd->mon_len
__bss_end ,_start这两个标号在链接脚本u-boot.lds中定义。__bss_end -_start uboot就是整个uboot的大小。如果对链接脚本不了解,可以参考《编译及连接过程
》。
2、获取环境变量的起始地址到gd->env_addr
default_environment是全局初始化变量,保存在uboot的data段中!
3、设置gd->baudrate
从环境变量中获取波特率到gd->baudrate成员中。
4、获取内存的实际大小到gd->ram_size
5、获取gd->relocaddr
重新设置内存大小,预留出内存顶部的4K不用。
6、设置gd->reloc_off
设置relocate的偏移量,并将旧gd中的内容拷贝到新gd中。
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,用来存储绝对地址符号的地址。
为了理解rel.dyn段的作用,在uboot源代码的common/main.c文件中,加入如下代码,并在main_loop函数中调用rel_dyn_test。
然后重新编译uboot,并且对新生成的elf文件(u-boot)进行反汇编:
代码段:
这些函数末尾存储变量地址的内存空间称为Label(编译器自动分配),这里记
数据段
rel.dyn段
接下来看一下
由于arm的流水线架构,所以执行第(1)条指令时pc是指向第(3)条指令的,再加上44(也就是再往下数11条指令,因为一条指令4个字节),即指向了地址为
也就是说,代码在取符号(全局变量、函数等)的时候,会使用相对地址的方法去函数末尾的Lable中取到该符号的地址(说明白点,就是这些Lable存储了要查找符号的地址),然后再根据该地址读取符号的内容。所以当data段搬移时,Lable中的内容也要跟着改变!而这些Lable的地址保存在rel.dyn段中,所以代码relocate时要用到rel.dyn段,对Lable中的内容进行修改!
前言
_main标号中主要调用的函数有三个,board_init_f,relocate_code,board_init_r,这里先贴出_main的代码并注释,然后对这三个函数的流程及原理进行详细介绍。
1、代码注释
ENTRY(_main) /* * Set up initial C runtime environment and call board_init_f(0). */ /* *这里首先为调用board_init_f准备一个临时堆栈,CONFIG_SYS_INIT_SP_ADDR这个宏 *就是cpu片上内存的高地址(片上内存的大小减去GD_SIZE)。然后将堆栈初始的地址保存在 *r9,所以r9就是gd的起始地址,后面需要靠r9访问gd中的成员。然后将r0赋值成0,r0就是 *要调用的board_init_f函数的第一个参数! */ #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) ldr sp, =(CONFIG_SPL_STACK) #else ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) #endif bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ sub sp, sp, #GD_SIZE /* allocate one GD above SP */ bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ mov r9, sp /* GD is above SP */ 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. */ /* *这段代码的主要功能就是将uboot搬移到内存的高地址去执行,为kernel腾出低端空间, *防止kernel解压覆盖uboot。 *adr这个指令非常有意识,可以自己问度娘了解以下。这里这三行代码 *adr lr, here *ldr r0, [r9, #GD_RELOC_OFF] *add lr, lr, r0 *的功能就是,将relocate后的here标号的地址保存到lr寄存器,这样等到relocate *完成后,就可以直接跳到relocate后的here标号去执行了。 *relocate_code函数的原理及流程,是uboot的重要代码,下面详解! */ ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */ bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ 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 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ b relocate_code here: /* Set up final (full) environment */ /* *relocate完成后,uboot的代码被搬到了内存的顶部,所以必须重新设置异常向量表的 *地址,c_runtime_cpu_setup这个函数的主要功能就是重新设置异常向量表的地址。 */ bl c_runtime_cpu_setup /* we still call old routine here */ /* *在relocate的过程中,并没有去搬移bss段。bss段是auto-relocated的!为什么? *可以自己思考一下,又或许看完我后面介绍的relocate的原理后你会明白! */ ldr r0, =__bss_start /* this is auto-relocated! */ ldr r1, =__bss_end /* this is auto-relocated! */ mov r2, #0x00000000 /* prepare zero to clear BSS */ /* *清空bss段。 */ clbss_l:cmp r0, r1 /* while not at end of BSS */ strlo r2, [r0] /* clear 32-bit BSS word */ addlo r0, r0, #4 /* move to next */ blo clbss_l /* *这两行代码无视之,点灯什么的,和这里要讲的uboot的原理及过程没有半毛钱关系。 */ bl coloured_LED_init bl red_led_on /* *将relocate后的gd的地址保存到r1,然后调用board_init_r函数,进入uboot的新天地! */ /* call board_init_r(gd_t *id, ulong dest_addr) */ mov r0, r9 /* gd_t */ ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */ /* call board_init_r */ ldr pc, =board_init_r /* this is auto-relocated! */ /* we should not return here. */ #endif ENDPROC(_main)
2、board_init_f
这个函数的主要功能就是初始化一些硬件设备(串口、定时器等)并且设置gd结构体中的成员。执行完后,gd中一些重要成员的指向如下图所示: 刚开始uboot在编译地址TEXT_BASE处执行,relocate完成后,uboot会被搬移到gd->relocaddr这个地址执行。gd->start_addr_sp是relocate完成后新的堆栈起始地址。接下来对其调用到的主要函数进行介绍。
1、获取整个uboot的大小到gd->mon_len
static int setup_mon_len(void) { #ifdef __ARM__ gd->mon_len = (ulong)&__bss_end - (ulong)_start; #elif defined(CONFIG_SANDBOX) gd->mon_len = (ulong)&_end - (ulong)_init; #else /* TODO: use (ulong)&__bss_end - (ulong)&__text_start; ? */ gd->mon_len = (ulong)&__bss_end - CONFIG_SYS_MONITOR_BASE; #endif return 0; }
__bss_end ,_start这两个标号在链接脚本u-boot.lds中定义。__bss_end -_start uboot就是整个uboot的大小。如果对链接脚本不了解,可以参考《编译及连接过程
》。
2、获取环境变量的起始地址到gd->env_addr
int env_init(void) { /* use default */ gd->env_addr = (ulong)&default_environment[0]; gd->env_valid = 1; return 0; }
default_environment是全局初始化变量,保存在uboot的data段中!
3、设置gd->baudrate
从环境变量中获取波特率到gd->baudrate成员中。
static int init_baud_rate(void) { gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE); return 0; }
4、获取内存的实际大小到gd->ram_size
int dram_init(void) { gd->ram_size = get_ram_size((void *)PHYS_SDRAM, PHYS_SDRAM_SIZE); return 0; }
5、获取gd->relocaddr
重新设置内存大小,预留出内存顶部的4K不用。
static int setup_dest_addr(void) { debug("Monitor len: %08lX\n", gd->mon_len); /* * Ram is setup, size stored in gd !! */ debug("Ram size: %08lX\n", (ulong)gd->ram_size); #if defined(CONFIG_SYS_MEM_TOP_HIDE) /* * Subtract specified amount of memory to hide so that it won't * get "touched" at all by U-Boot. By fixing up gd->ram_size * the Linux kernel should now get passed the now "corrected" * memory size and won't touch it either. This should work * for arch/ppc and arch/powerpc. Only Linux board ports in * arch/powerpc with bootwrapper support, that recalculate the * memory size from the SDRAM controller setup will have to * get fixed. */ gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE; #endif #ifdef CONFIG_SYS_SDRAM_BASE gd->ram_top = CONFIG_SYS_SDRAM_BASE; #endif gd 16345 ->ram_top += get_effective_memsize(); gd->ram_top = board_get_usable_ram_top(gd->mon_len); gd->relocaddr = gd->ram_top; debug("Ram top: %08lX\n", (ulong)gd->ram_top); #if defined(CONFIG_MP) && (defined(CONFIG_MPC86xx) || defined(CONFIG_E500)) /* * We need to make sure the location we intend to put secondary core * boot code is reserved and not used by any part of u-boot */ if (gd->relocaddr > determine_mp_bootpg(NULL)) { gd->relocaddr = determine_mp_bootpg(NULL); debug("Reserving MP boot page to %08lx\n", gd->relocaddr); } #endif return 0; }
6、设置gd->reloc_off
设置relocate的偏移量,并将旧gd中的内容拷贝到新gd中。
static int setup_reloc(void) { gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE; memcpy(gd->new_gd, (char *)gd, sizeof(gd_t)); debug("Relocation Offset is: %08lx\n", gd->reloc_off); debug("Relocating to %08lx, new gd at %08lx, sp at %08lx\n", gd->relocaddr, (ulong)map_to_sysmem(gd->new_gd), gd->start_addr_sp); return 0; }
3、relocate_code
3.1 原理介绍
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,用来存储绝对地址符号的地址。
3.2 调试过程
为了理解rel.dyn段的作用,在uboot源代码的common/main.c文件中,加入如下代码,并在main_loop函数中调用rel_dyn_test。void test_func(void) { printf("test func\n"); } static void * test_func_val = test_func; static int test_val = 10; void rel_dyn_test() { test_val = 20; test_func(); printf("test = 0x%x\n", test_func); printf("test_func = 0x%x\n", test_func_val); }
然后重新编译uboot,并且对新生成的elf文件(u-boot)进行反汇编:
arm-fsl-linux-gnueabi-objdump -D u-boot > u-boot.huibian。然后在反汇编文件中找到对应的代码段、数据段以及rel.dyn段。如下:
代码段:
/*这里是test_func()函数的反汇编*/ 87802fe0 <test_func>: 87802fe0: e59f300c ldr r3, [pc, #12] ; 87802ff4 <test_func+0x14> 87802fe4: e3a01007 mov r1, #7 87802fe8: e59f0008 ldr r0, [pc, #8] ; 87802ff8 <test_func+0x18> 87802fec: e5831000 str r1, [r3] 87802ff0: ea003a4a b 87811920 <printf>/*注:以下两行为Lable1*/ 87802ff4: 8784004c strhi r0, [r4, ip, asr #32] 87802ff8: 8783127f ; <UNDEFINED> instruction: 0x8783127f /*这里是rel_dyn_test函数的反汇编*/ 878031c0 <rel_dyn_test>: 878031c0: e59f302c ldr r3, [pc, #44] ; 878031f4 <rel_dyn_test+0x34> 878031c4: e3a02014 mov r2, #20 878031c8: e92d4010 push {r4, lr} 878031cc: e59f4024 ldr r4, [pc, #36] ; 878031f8 <rel_dyn_test+0x38> 878031d0: e5832000 str r2, [r3] 878031d4: ebffff81 bl 87802fe0 <test_func> 878031d8: e59f001c ldr r0, [pc, #28] ; 878031fc <rel_dyn_test+0x3c> 878031dc: e1a01004 mov r1, r4 878031e0: eb0039ce bl 87811920 <printf> 878031e4: e59f0014 ldr r0, [pc, #20] ; 87803200 <rel_dyn_test+0x40> 878031e8: e1a01004 mov r1, r4 878031ec: e8bd4010 pop {r4, lr} 878031f0: ea0039ca b 87811920 <printf> /*注:以下四行为Lable2*/ 878031f4: 87838c3c ; <UNDEFINED> instruction: 0x87838c3c 878031f8: 87802fe0 strhi r2, [r0, r0, ror #31] 878031fc: 8783129b ; <UNDEFINED> instruction: 0x8783129b 87803200: 878312a8 strhi r1, [r3, r8, lsr #5]
这些函数末尾存储变量地址的内存空间称为Label(编译器自动分配),这里记
<test_func>的最后两行为Lable1,
<rel_dyn_test>的最后四行为Lable2。等一下可以看到这些Lable的地址是存储在rel.dyn段中的。
数据段
87838c3c <test_val>: 87838c3c: 0000000a andeq r0, r0, sl 8784004c <jimmy_val>: 8784004c: 00000000 andeq r0, r0, r0
rel.dyn段
/*Lable1的rel.dyn段*/ 8783b4a0: 87802ff4 ; <UNDEFINED> instruction: 0x87802ff4 8783b4a4: 00000017 andeq r0, r0, r7, lsl r0 8783b4a8: 87802ff8 ; <UNDEFINED> instruction: 0x87802ff8 8783b4ac: 00000017 andeq r0, r0, r7, lsl r0 /*Lable2的rel.dyn段*/ 8783b4c8: 878031f4 ; <UNDEFINED> instruction: 0x878031f4 8783b4cc: 00000017 andeq r0, r0, r7, lsl r0 8783b4d0: 878031f8 ; <UNDEFINED> instruction: 0x878031f8 8783b4d4: 00000017 andeq r0, r0, r7, lsl r0 8783b4d8: 878031fc ; <UNDEFINED> instruction: 0x878031fc 8783b4dc: 00000017 andeq r0, r0, r7, lsl r0 8783b4e0: 87803200 strhi r3, [r0, r0, lsl #4] 8783b4e4: 00000017 andeq r0, r0, r7, lsl r0
接下来看一下
test_val = 20;这条c语句对应的汇编代码为:
ldr r3, [pc, #44] -----(1) mov r2, #20 -----(2) str r2, [r3] -----(3)
由于arm的流水线架构,所以执行第(1)条指令时pc是指向第(3)条指令的,再加上44(也就是再往下数11条指令,因为一条指令4个字节),即指向了地址为
<878031f4>的语句,然后将其内容加载到r3中(即r3=0x87838c3c,对照上面的数据段,可知这就是变量test_val的地址);接着将立即数20放到r2;最后将r2的内容放到以r3为地址的空间。
也就是说,代码在取符号(全局变量、函数等)的时候,会使用相对地址的方法去函数末尾的Lable中取到该符号的地址(说明白点,就是这些Lable存储了要查找符号的地址),然后再根据该地址读取符号的内容。所以当data段搬移时,Lable中的内容也要跟着改变!而这些Lable的地址保存在rel.dyn段中,所以代码relocate时要用到rel.dyn段,对Lable中的内容进行修改!
3.3 代码分析
ENTRY(relocate_code) /* *拷贝__image_copy_start到__image_copy_end之间的内容到新地址,即只拷贝了代码段 *和data段,没有拷贝bss段和rel.dyn段。 */ 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 */ /* *根据rel.dyn段设置Lable的内容,relocate后运行地址和编译地址不一致还能跑, *全靠这里啦。 */ 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, #23 /* relative fixup? */ 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)
相关文章推荐
- uboot启动流程详解(5)-_main
- uboot启动流程详解(3)-cpu_init_cp15
- uboot启动流程详解(4)-cpu_init_crit
- uboot启动流程详解(1)-_start
- linux 启动流程详解之/init/main.c:init调用/etc/rc.d/rc.sysinit
- uboot启动流程详解(3)-cpu_init_cp15
- uboot启动流程详解(2)-reset
- uboot启动流程详解(2)-reset
- 1.移植uboot-分析uboot启动流程(详解)
- CentOS系列启动流程详解
- 启动流程详解(关于解决gaosi.x脚本)的方法
- Centos 6启动流程详解
- linux系统启动流程详解
- Uboot启动流程
- uboot 分析之 启动流程
- 详解Linux启动流程及需要使用到的配置文件
- uboot启动流程
- Uboot详细启动流程
- uboot启动流程
- Uboot启动流程分析:启动阶段1 Start.S