【嵌入式Linux学习七步曲之第三篇 Linux系统bootlaoder移植】 从反汇编来看U-boot在PPC架构下的PIC(位置无关程序)设计

时间:2021-07-22 18:52:37

从反汇编来看U-boot在PPC架构下的PIC(位置无关程序)设计
Sailor_forever  sailing_9806#163.com 转载请注明
http://blog.csdn.net/sailor_8318/archive/2009/11/14/4811785.aspx



【摘要】本文详细分析了U-boot1.3.4在PPC架构下的位置无关程序设计的细节。首先介绍了PIC的相关概念,接着从汇编指令的角度分析了如何访问函数及全局变量。最后介绍了U-boot为了在PPC下实现位置无关程序所做的一系列调整。

【关键字】PPC  MPC8270  PIC GOT
 
目录
1    PIC(位置无关程序)的基本概念    1
2    如何访问函数    2
2.1    相对跳转    2
2.2    绝对跳转    4
3    如何访问全局变量    5
3.1    汇编代码    6
3.2    C代码    8
4    U-boot如何实现PIC    9

 

 

1 PIC(位置无关程序)的基本概念
程序经过编译链接后,每一个符号(函数、全局变量及汇编程序里的标号)都将对应一个特定的地址,计算机程序见到的符号就是这个地址。

PIC(位置无关程序)指当程序的加载位置和程序的链接地址不等时,程序仍然可以正确访问函数及变量。否则即为位置相关程序,程序必须加载到其链接地址运行,这种相关性对于嵌入式bootloader阶段的有很大的限制,因为各种各样的原因,在启动阶段程序大多数时候不是运行在链接地址上的。

对于具备操作系统的application程序而言,其都是PIC(位置无关程序),具备动态加载的能力。试想,各个应用程序在内存中都占据了一个特定的地址,并且必须是不同的,但是你在编译应用程序时指定过链接脚本么?执行时指定了加载到某个位置么?没有!!既然application开发者未指定程序的编译链接及运行地址,那是谁捣的鬼呢?那只能是操作系统了,在application执行前,操作系统会根据内存分布情况自动申请一块可用的地址空间,然后对application映像进行重地位,并对函数及变量的访问进行修正,从而正确解析。

但是在嵌入式bootloader的启动阶段是没有OS的,这种位置无关的特性就需要开发者自己实现了。要深刻的理解PIC(位置无关程序),首先我们从反汇编的角度来看看,程序是如何访问函数及全局变量的。

2 如何访问函数
首先,我们获得源代码和反汇编代码的对应关系。
ppc_82xx-objdump -S u-boot > u-boot_mix.S
ppc_82xx-objdump -d u-boot > u-boot.S
u-boot为ELF格式的映像,其包含了各种符号调用的相关信息,和application的格式一样。但是嵌入式平台启动阶段没有加载器,烧录到flash中的是BIN格式的二进制映像。

2.1 相对跳转
相对跳转指令包括BL和B,其都是基于PC来寻址的。

40000100 <_start>:
40000100:    3a a0 00 01     li      r21,1
40000104:    60 00 00 00     nop
40000108:    48 00 00 10     b       40000118 <boot_cold>
4000010c:    00 00 00 00     .long 0x0

40000110 <_start_warm>:
40000110:    3a a0 00 02     li      r21,2
40000114:    48 00 00 04     b       40000118 <boot_cold>

40000118 <boot_cold>:
40000118:    7c a0 00 a6     mfmsr   r5
4000011c:    48 00 30 5d     bl      40003178 <init_8260_core>

b    boot_cold指令对应的地址为40000108,boot_cold函数对应的地址标号为40000118,差值为40000118-40000108=0x10
b    boot_cold对应的汇编指令为48 00 00 10,其中48为操作数,表示跳转指令,10为操作码,为下一条指令相对于当前PC的偏移量,其可正可负。

在程序映像中,b    boot_cold和boot_cold函数的距离为0x10,无论执行b    boot_cold语句时当前PC值为多少,下一条待取指的指令地址为PC+0x10,也就对应着boot_cold函数所在的位置。这种就是位置无关代码。

40000118 <boot_cold>:
40000118:    7c a0 00 a6     mfmsr   r5
    bl    init_8260_core
4000011c:    48 00 30 5d     bl      40003178 <init_8260_core>

40003178 <init_8260_core>:
40003178:    38 60 20 02     li      r3,8194
4000317c:    50 a3 06 72     rlwimi  r3,r5,0,25,25
40003180:    50 a3 05 6c     rlwimi  r3,r5,0,21,22

函数调用bl    init_8260_core的汇编指令为48 00 30 5d,指令地址为4000011c,init_8260_core函数对应的指令地址为40003178,与指令bl    init_8260_core的距离为40003178 - 4000011c = 0x305d,正好为指令的48 00 30 5d操作码30 5d。原理同上,不管执行语句bl    init_8260_core时的PC为何值,总能够正确寻址到函数init_8260_core。这种情况属于PC+的寻址。


B指令仅存在于手动编写的汇编代码中,而对应c代码中的函数调用,汇编指令是BL。

void board_init_f (ulong bootflag)
{
。。。
    memcpy (id, (void *)gd, sizeof (gd_t));
400058bc:    7c 40 13 78     mr      r0,r2
400058c0:    80 7f 00 0c     lwz     r3,12(r31)
400058c4:    7c 04 03 78     mr      r4,r0
400058c8:    38 a0 00 4c     li      r5,76
400058cc:    4b ff f7 25     bl      40004ff0 <memcpy>
。。
}

40004ff0 <memcpy>:
40004ff0:    54 a7 e8 ff     rlwinm. r7,r5,29,3,31
40004ff4:    38 c3 ff fc     addi    r6,r3,-4
40004ff8:    38 84 ff fc     addi    r4,r4,-4
40004ffc:    41 82 00 28     beq-    40005024 <memcpy+0x34>

函数调用memcpy(id, (void *)gd, sizeof (gd_t))最终对应的跳转语句为bl      40004ff0 <memcpy>,汇编指令为4b ff f7 25,指令地址为400058cc ,memcpy函数对应的指令地址为40004ff0,在指令bl      40004ff0 <memcpy>前方,因此属于PC-寻址。距离为FFFFFF + 1 -(400058cc-40004ff0)= ff f7 24
其中FFFFFF为操作码的最大值,而指令4b ff f7 25从操作数为ff f7 25,我们知道RISC指令集都是4字节编码,对于PC来说,其值必须是4字节对齐的,此处的5非对齐,可能表示PC-寻址。

2.2 绝对跳转
绝对跳转指令通常是对PC直接进行赋值的,可达到此效果的指令包括BLR、RFI
40000100 <_start>:
    . = EXC_OFF_SYS_RESET

    .globl    _start
_start:
    li    r21, BOOTFLAG_COLD    /* Normal Power-On: Boot from FLASH*/
40000100:    3a a0 00 01     li      r21,1
。。。。。。。。。
    lis    r3, CFG_MONITOR_BASE@h
40000134:    3c 60 40 00     lis     r3,16384
    ori    r3, r3, CFG_MONITOR_BASE@l
40000138:    60 63 00 00     ori     r3,r3,0
    addi    r3, r3, in_flash - _start + EXC_OFF_SYS_RESET
4000013c:    38 63 01 48     addi    r3,r3,328
    mtlr    r3
40000140:    7c 68 03 a6     mtlr    r3
    blr
40000144:    4e 80 00 20     blr

40000148 <in_flash>:

in_flash:
    /* initialize some things that are hard to access from C    */
    /*--------------------------------------------------------------*/

    lis    r3, CFG_IMMR@h        /* set up stack in internal DPRAM */


待跳转的指令地址为r3,其计算过程如下:
    addi    r3, r3, in_flash - _start + EXC_OFF_SYS_RESET
4000013c:    38 63 01 48     addi    r3,r3,328
其中CFG_MONITOR_BASE为40000000,EXC_OFF_SYS_RESET为0x100
40000148 - 40000100 + 0x100+ 40000000 = 40000148
即为in_flash标号的编译链接地址,blr会将LR链接寄存器中保存的40000148赋值给PC,实现绝对跳转

中断返回指令rfi可以同时更新MSR和PC,SRR1中的位0、5~9和16~31被放在MSR中对应的位置,SRR0[0-29]赋值给PC,通常为中断前下一条指令的地址
int_return:
。。。。。
    lwz    r0,_MSR(r1)
40003154:    80 01 00 94     lwz     r0,148(r1)
    mtspr    SRR0,r2
40003158:    7c 5a 03 a6     mtsrr0  r2
    mtspr    SRR1,r0
4000315c:    7c 1b 03 a6     mtsrr1  r0
    lwz    r0,GPR0(r1)
40003160:    80 01 00 10     lwz     r0,16(r1)
    lwz    r2,GPR2(r1)
40003164:    80 41 00 18     lwz     r2,24(r1)
    lwz    r1,GPR1(r1)
40003168:    80 21 00 14     lwz     r1,20(r1)
    SYNC
4000316c:    7c 00 04 ac     sync    
40003170:    4c 00 01 2c     isync
    rfi
40003174:    4c 00 00 64     rfi


另外RFI指令在进行MMU映射时也有特定应用,在Uboot中未涉及。


3 如何访问全局变量
我们知道对于全局变量的访问,首先要获取该变量的全局地址,而这个地址是编译链接时生成的一个地址,对于没有-fpic选项编译的程序,此值是一个静态的地址,当程序从flash重新定位到ram后,访问的地址仍然在Flash中,那么如何读写呢??

这就需要一种机制来保证获取的全局变量的地址是一个动态的值,也是说当程序重定位到RAM中后,这个地址也需要随之修正。而U-boot正是将所有全局符号的地址保存在一个表中GOT,程序访问变量时先从这个表中动态获取该符号的地址,而这个地址在重定位时已经修正过了,从而保证能从重定位后的地址处正确访问全局变量。

链接脚本中定义了一个.got2段
  .reloc   :
  {
    *(.got)
    _GOT2_TABLE_ = .;
    *(.got2)
    _FIXUP_TABLE_ = .;
    *(.fixup)
  }
  __got2_entries = (_FIXUP_TABLE_ - _GOT2_TABLE_) >> 2;
所有全局符号的地址都保存在*(.got2)中,每个符号的地址占4个字节,_GOT2_TABLE_是该段在代码段中的起始地址,__got2_entries是全局符号的个数。

以下是U-boot.map中*(.got2)的详细列表
.reloc          0x4003f000     0x1318
 *(.got)
                0x4003f000                _GOT2_TABLE_ = .
 *(.got2)
 .got2          0x4003f000       0x24 cpu/mpc8270/start.o
 .got2          0x4003f024       0x4c cpu/mpc8270/libmpc8270.a(traps.o)
 .got2          0x4003f070       0x20 cpu/mpc8270/libmpc8270.a(cpu_init.o)
。。。。。。。。。。
 .got2          0x40040174       0x18 lib_generic/libgeneric.a(vsprintf.o)
 .got2          0x4004018c       0x8c lib_generic/libgeneric.a(zlib.o)
 .got2          0x40040218        0x0 lib_generic/libgeneric.a(ctype.o)
 .got2          0x40040218        0x4 cpu/mpc8270/libmpc8270.a(serial_smc.o)
 .got2          0x4004021c       0x50 cpu/mpc8270/libmpc8270.a(cpu.o)
 .got2          0x4004026c       0x30 cpu/mpc8270/libmpc8270.a(speed.o)
 .got2          0x4004029c       0x48 cpu/mpc8270/libmpc8270.a(ether_fcc.o)
 .got2          0x400402e4       0x28 lib_ppc/libppc.a(bootm.o)
 .got2          0x4004030c        0x0 lib_ppc/libppc.a(cache.o)
 .got2          0x4004030c        0x8 drivers/rtc/librtc.a(date.o)
 .got2          0x40040314        0x4 common/libcommon.a(miiphybb.o)
                0x40040318                _FIXUP_TABLE_ = .
 *(.fixup)
                0x000004c6                __got2_entries = ((_FIXUP_TABLE_ - _GOT2_TABLE_) >> 0x2)
                0x00000000                __fixup_entries = ((. - _FIXUP_TABLE_) >> 0x2)

由上述表可知*(.got)和*(.fixup)为空

3.1 汇编代码
在PPC架构的start.S中定义了如下几个变量

/*
 * Set up GOT: Global Offset Table
 *
 * Use r14 to access the GOT
 */
 START_GOT
 GOT_ENTRY(_GOT2_TABLE_)
 GOT_ENTRY(_FIXUP_TABLE_)
 GOT_ENTRY(_start)
 GOT_ENTRY(_start_of_vectors)
 GOT_ENTRY(_end_of_vectors)
 GOT_ENTRY(transfer_to_handler)
 GOT_ENTRY(__init_end)
 GOT_ENTRY(_end)
 GOT_ENTRY(__bss_start)
#if defined(CONFIG_FADS)
 GOT_ENTRY(environment)
#endif
 END_GOT

相关的宏在./include/ppc_asm.tmpl中定义:
#define START_GOT   /
 .section ".got2","aw"; /
.LCTOC1 = .+32768
#define END_GOT    /
 .text
#define GET_GOT    /
 bl 1f  ; /
 .text  2  ; /
0: .long .LCTOC1-1f ; /
 .text   ; /
1: mflr r14  ; /
 lwz r0,0b-1b(r14) ; /
 add r14,r0,r14 ;
#define GOT_ENTRY(NAME) .L_ ## NAME = . - .LCTOC1 ; .long NAME
#define GOT(NAME)  .L_ ## NAME (r14)

关于这些符号的详细解释可以参考http://coryxie.spaces.live.com/Blog/cns!9429CA1F587766E2!208.entry

总体来说,START_GOT用于定义表的开始,END_GOT用于定义表的结束,GOT_ENTRY用于将全局变量NAME的地址写入表中,GOT用于从表中读出NAME的地址,GET_GOT用于将表进行初始化。

START_GOT定义了段"got2",属性为"allocatable and writable",并定义了变量.LCTOC1。如果设表的起始地址为TABLE_START,则.LCTOC1的值为TABLE_START+0x8000。至于为什么是0x8000,因为PPC中立即数的最大值为16位,以TABLE_START+0x8000为中心时即可前后寻址0x8000的空间,因为got2段的最大长度不能超过0x10000

END_GOT定义为子段text 0的开始,也就是结束got2段的定义。

GOT_ENTRY定义了变量.L_NAME,其值为当前表项的地址(.)-.LCTOC1。如果设NAME的表项偏移地址为NAME_OFFSET,那么.L_NAME = . - .LCTOC1 = TABLE_START + NAME_OFFSET - (  TABLE_START + 0x8000 ) =  NAME_OFFSET - 0x8000,是一个相对距离。之后将全局变量NAME的地址写入当前表项,这些地址值是在链接的时候确定的。
 
GET_GOT用于初始化GOT表。首先程序跳转到标号为"1"的地址处(bl 1f),然后将lr的值赋值给r14(此时lr的值为当前实际运行时"1"的地址值)。然后令r0 = 0b - 1b(r14),0b为"0"处的地址值,1b为"1"处的地址值。这样r0就等于"0"处的值,也就是.LCTOC1-1f。最后r14 = r0 + r14 = .LCTOC1 - 1f + r14
当程序当前加载的地址等于链接的地址时,r14 = .LCTOC1,也就是等于编译链接时GOT表的0x8000偏移处。否则r14是动态修正过的当前实际代码GOT表的0x8000偏移处。这就保证了无论哪种情况在汇编代码中都可以争取获得全局变量的地址。

GOT(NAME)的值定义为.L_NAME(r14),这里面r14的值由GET_GOT初始化,也就是.LCTOC1的值。这样GOT(NAME) = .L_NAME + r14 = .L_NAME + .LCTOC1 = NAME_OFFSET - 0x8000 + TABLE_START + 0x8000 = NAME_OFFSET + TABLE_START,也就是NAME所在表项的地址。这样,通过查表,就可以找到当初存储在表中的名字为NAME的offset值。

GOT(NAME)用于在汇编代码中访问全局变量。

3.2 C代码
C文件在-fPIC编译选项的控制下,会自动将全局变量的地址添加到.got2段中,同时在访问全局变量时先获取该变量在.got2段中的位置,从此位置读取修正后的全局变量的地址。

serial_setbrg (void)
{
4003432c:    94 21 ff f0     stwu    r1,-16(r1)
40034330:    7c 08 02 a6     mflr    r0
40034334:    bf c1 00 08     stmw    r30,8(r1)
40034338:    90 01 00 14     stw     r0,20(r1)
4003433c:    7c 3f 0b 78     mr      r31,r1
40034340:    42 9f 00 05     bcl-    20,4*cr7+so,40034344 <serial_setbrg+0x18>
40034344:    7f c8 02 a6     mflr    r30
40034348:    80 1e ff e4     lwz     r0,-28(r30)
4003434c:    7f c0 f2 14     add     r30,r0,r30
#if defined(CONFIG_CONS_USE_EXTC)
    m8260_cpm_extcbrg(brg_map[SMC_INDEX], gd->baudrate,
        CONFIG_CONS_EXTC_RATE, CONFIG_CONS_EXTC_PINSEL);
#else
    m8260_cpm_setbrg(brg_map[SMC_INDEX], gd->baudrate);
40034350:    81 3e 80 00     lwz     r9,-32768(r30)
40034354:    88 09 00 01     lbz     r0,1(r9)
40034358:    54 00 06 3e     clrlwi  r0,r0,24
4003435c:    7c 0b 03 78     mr      r11,r0
40034360:    7c 49 13 78     mr      r9,r2
40034364:    80 09 00 08     lwz     r0,8(r9)
40034368:    7d 63 5b 78     mr      r3,r11
4003436c:    7c 04 03 78     mr      r4,r0
40034370:    4b fd 08 ad     bl      40004c1c <m8260_cpm_setbrg>
#endif
}
40034374:    81 61 00 00     lwz     r11,0(r1)
40034378:    80 0b 00 04     lwz     r0,4(r11)
4003437c:    7c 08 03 a6     mtlr    r0
40034380:    bb cb ff f8     lmw     r30,-8(r11)
40034384:    7d 61 5b 78     mr      r1,r11
40034388:    4e 80 00 20     blr

4003434c:    7f c0 f2 14     add     r30,r0,r30
此句之后,r30即为本C文件的全局变量在.got段中能够首地址的0x8000偏移处,-32768(r30)即可获得第一个表项在.got段中的地址
40034350:    81 3e 80 00     lwz     r9,-32768(r30)
将经过修正过的brg_map的地址加载到r9中。

4 U-boot如何实现PIC
除了上面说到链接脚本中的.got2段和编译选项-fPIC外,实际上还有很多地方需要修正。

/*
 * Set up GOT: Global Offset Table
 *
 * Use r14 to access the GOT
 */
    START_GOT
    GOT_ENTRY(_GOT2_TABLE_)
    GOT_ENTRY(_FIXUP_TABLE_)

    GOT_ENTRY(_start)
    GOT_ENTRY(_start_of_vectors)
    GOT_ENTRY(_end_of_vectors)
    GOT_ENTRY(transfer_to_handler)

    GOT_ENTRY(__init_end)
    GOT_ENTRY(_end)
    GOT_ENTRY(__bss_start)
#if defined(CONFIG_HYMOD)
    GOT_ENTRY(environment)
#endif
    END_GOT
此处定义了几个变量用于重定位时的代码拷贝


#ifndef CFG_RAMBOOT
    /* When booting from ROM (Flash or EPROM), clear the        */
    /* Address Mask in OR0 so ROM appears everywhere        */
    /*--------------------------------------------------------------*/

    lis    r3, (CFG_IMMR+IM_REGBASE)@h
    lwz    r4, IM_OR0@l(r3)
    li    r5, 0x7fff
    and    r4, r4, r5
    stw    r4, IM_OR0@l(r3)

    /* Calculate absolute address in FLASH and jump there        */
    /*--------------------------------------------------------------*/

    lis    r3, CFG_MONITOR_BASE@h
    ori    r3, r3, CFG_MONITOR_BASE@l
    addi    r3, r3, in_flash - _start + EXC_OFF_SYS_RESET
    mtlr    r3
    blr

in_flash:
#endif    /* CFG_RAMBOOT */

当从Flash中启动时,此处第一次实现绝对跳转,跳转到链接地址处运行

GET_GOT            /* initialize GOT access        */
初始化r14为当前.got2段的0x8000处的偏移,便于cpu_init_f代码中能正确访问全局变量,但此时程序在Flash中运行,全局变量是只读的。
.rodata        0x4003ccac       0x92 board/eric8270/liberic8270.a(eric8270.o)
                0x4003ccac                iop_conf_tab
static void config_8260_ioports (volatile immap_t * immr)
{
40003d6c:    94 21 ff b8     stwu    r1,-72(r1)
40003d70:    7c 08 02 a6     mflr    r0
40003d74:    bf c1 00 40     stmw    r30,64(r1)
40003d78:    90 01 00 4c     stw     r0,76(r1)
40003d7c:    7c 3f 0b 78     mr      r31,r1
40003d80:    42 9f 00 05     bcl-    20,4*cr7+so,40003d84 <config_8260_ioports+0x18>
40003d84:    7f c8 02 a6     mflr    r30
40003d88:    80 1e ff e4     lwz     r0,-28(r30)
40003d8c:    7f c0 f2 14     add     r30,r0,r30
40003d90:    90 7f 00 38     stw     r3,56(r31)
    int portnum;
。。。
}

bl    board_init_f    /* run 1st part of board init code (in Flash)*/

void board_init_f (ulong bootflag)
{
40005610:    94 21 ff c8     stwu    r1,-56(r1)
40005614:    7c 08 02 a6     mflr    r0
40005618:    bf c1 00 30     stmw    r30,48(r1)
4000561c:    90 01 00 3c     stw     r0,60(r1)
40005620:    7c 3f 0b 78     mr      r31,r1
40005624:    42 9f 00 05     bcl-    20,4*cr7+so,40005628 <board_init_f+0x18>
40005628:    7f c8 02 a6     mflr    r30
4000562c:    80 1e ff e4     lwz     r0,-28(r30)
40005630:    7f c0 f2 14     add     r30,r0,r30
40005634:    90 7f 00 28     stw     r3,40(r31)
。。。。。。
    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
40005640:    80 1e 80 2c     lwz     r0,-32724(r30)
40005644:    90 1f 00 08     stw     r0,8(r31)
40005648:    48 00 00 30     b       40005678 <board_init_f+0x68>
        if ((*init_fnc_ptr) () != 0) {
4000564c:    81 3f 00 08     lwz     r9,8(r31)
40005650:    80 09 00 00     lwz     r0,0(r9)
40005654:    7c 09 03 a6     mtctr   r0
40005658:    4e 80 04 21     bctrl
4000565c:    7c 60 1b 78     mr      r0,r3
40005660:    2f 80 00 00     cmpwi   cr7,r0,0
40005664:    41 9e 00 08     beq-    cr7,4000566c <board_init_f+0x5c>
            hang ();
40005668:    48 00 05 ed     bl      40005c54 <hang>
4000566c:    81 3f 00 08     lwz     r9,8(r31)
40005670:    38 09 00 04     addi    r0,r9,4
40005674:    90 1f 00 08     stw     r0,8(r31)
40005678:    81 3f 00 08     lwz     r9,8(r31)
4000567c:    80 09 00 00     lwz     r0,0(r9)
40005680:    2f 80 00 00     cmpwi   cr7,r0,0
40005684:    40 9e ff c8     bne+    cr7,4000564c <board_init_f+0x3c>
        }
    }
。。。

依次执行init_sequence数组中保存的各个函数,能够正确访问init_sequence数组,但是数组里面保存的函数指针是编译链接时的静态值,当绝对跳转至函数入口时,要求此处必须有程序映像,因此此时对于变量的访问是位置无关的,但是对于函数指针的访问却是位置相关的,这也就意味着在Flash中的运行的代码必须在链接地址处运行。

relocate_code (addr_sp, id, addr);
    .globl    relocate_code
relocate_code:
    mr    r1,  r3        /* Set new stack pointer        */
在PPC架构下r1为栈指针,建立新的栈,此时的SP处于SDRAM中

400033e4:    7c 61 1b 78     mr      r1,r3
    mr    r9,  r4        /* Save copy of Global Data pointer    */
400033e8:    7c 89 23 78     mr      r9,r4
    mr    r10, r5        /* Save copy of Destination Address    */
400033ec:    7c aa 2b 78     mr      r10,r5

    mr    r3,  r5                /* Destination Address    */
400033f0:    7c a3 2b 78     mr      r3,r5
    lis    r4, CFG_MONITOR_BASE@h        /* Source      Address    */
400033f4:    3c 80 40 00     lis     r4,16384
    ori    r4, r4, CFG_MONITOR_BASE@l
400033f8:    60 84 00 00     ori     r4,r4,0
    lwz    r5, GOT(__init_end)
400033fc:    80 ae 80 18     lwz     r5,-32744(r14)
    sub    r5, r5, r4
40003400:    7c a4 28 50     subf    r5,r4,r5
    li    r6, CFG_CACHELINE_SIZE        /* Cache Line Size    */
40003404:    38 c0 00 20     li      r6,32

从CFG_MONITOR_BASE处拷贝代码,因此链接地址处必须有映像备份
    lwz    r5, GOT(__init_end)
400033fc:    80 ae 80 18     lwz     r5,-32744(r14)
即为在汇编代码中访问全局变量__init_end,__init_end的值在链接脚本中定义,其为程序映像除bss段的末地址

    /*
     * Fix GOT pointer:
     *
     * New GOT-PTR = (old GOT-PTR - CFG_MONITOR_BASE) + Destination Address
     *
     * Offset:
     */
    sub    r15, r10, r4

    /* First our own GOT */
    add    r14, r14, r15
    /* then the one used by the C code */
    add    r30, r30, r15
当程序重定位到RAM中后,.got2段的首地址也发生变化,对其进行修正,r14用于汇编代码,r30用于C代码

    /*
     * Now relocate code
     */
。。。。
7:    sync            /* Wait for all icbi to complete on bus    */
    Isync
将整个映像拷贝到SDRAM中,此地址是RAM的高端地址,是动态计算出来的


/*
 * We are done. Do not return, instead branch to second part of board
 * initialization, now running from RAM.
 */

    addi    r0, r10, in_ram - _start + EXC_OFF_SYS_RESET
    mtlr    r0
    blr

in_ram:
blr实现绝对跳转,此时程序在RAM中运行

    /*
     * Relocation Function, r14 point to got2+0x8000
     *
     * Adjust got2 pointers, no need to check for 0, this code
     * already puts a few entries in the table.
     */
    li    r0,__got2_entries@sectoff@l
400034d4:    38 00 04 c6     li      r0,1222
    la    r3,GOT(_GOT2_TABLE_)
400034d8:    38 6e 80 00     addi    r3,r14,-32768
    lwz    r11,GOT(_GOT2_TABLE_)
400034dc:    81 6e 80 00     lwz     r11,-32768(r14)
    mtctr    r0
400034e0:    7c 09 03 a6     mtctr   r0
    sub    r11,r3,r11
400034e4:    7d 6b 18 50     subf    r11,r11,r3
    addi    r3,r3,-4
1:    lwzu    r0,4(r3)
    add    r0,r0,r11
    stw    r0,0(r3)
    bdnz    1b
la    r3,GOT(_GOT2_TABLE_)获得重定位后.got2段在RAM中的地址,lwz    r11,GOT(_GOT2_TABLE_)获得.got2段链接时候的地址,二者之差即为所有全局符号在Flash和RAM中地址的偏移量,add    r0,r0,r11将此偏移量添加到.got2表中,即实现了对所有全局符号地址的修正

    /*
     * Now adjust the fixups and the pointers to the fixups
     * in case we need to move ourselves again.
     */
2:    li    r0,__fixup_entries@sectoff@l
    lwz    r3,GOT(_FIXUP_TABLE_)
    cmpwi    r0,0
    mtctr    r0
    addi    r3,r3,-4
    beq    4f
3:    lwzu    r4,4(r3)
    lwzux    r0,r4,r11
    add    r0,r0,r11
    stw    r10,0(r3)
    stw    r0,0(r4)
    bdnz    3b、
对.fixup段进行修正,修正原理同.got2,但实际上在U-boot中,此段为空

4000352c <clear_bss>:
4:
clear_bss:
    /*
     * Now clear BSS segment
     */
    lwz    r3,GOT(__bss_start)
4000352c:    80 6e 80 20     lwz     r3,-32736(r14)
#if defined(CONFIG_HYMOD)
    /*
     * For HYMOD - the environment is the very last item in flash.
     * The real .bss stops just before environment starts, so only
     * clear up to that point.
     *
     * taken from mods for FADS board
     */
    lwz    r4,GOT(environment)
#else
    lwz    r4,GOT(_end)
40003530:    80 8e 80 1c     lwz     r4,-32740(r14)
#endif

    cmplw    0, r3, r4
40003534:    7c 03 20 40     cmplw   r3,r4
    beq    6f
40003538:    41 82 00 18     beq-    40003550 <clear_bss+0x24>
GOT(__bss_start)和GOT(_end)分别获得bss段在RAM中的首末地址,清除bss段


    mr    r3, r9        /* Global Data pointer        */
    mr    r4, r10        /* Destination Address        */
    bl    board_init_r
跳转至RAM中的初始化函数
/************************************************************************
 *
 * This is the next part if the initialization sequence: we are now
 * running from RAM and have a "normal" C environment, i. e. global
 * data can be written, BSS has been cleared, the stack size in not
 * that critical any more, etc.
 *
 ************************************************************************
 */
void board_init_r (gd_t *id, ulong dest_addr)
{
400058e4:    94 21 ff b8     stwu    r1,-72(r1)
400058e8:    7c 08 02 a6     mflr    r0
400058ec:    bf c1 00 40     stmw    r30,64(r1)
400058f0:    90 01 00 4c     stw     r0,76(r1)
400058f4:    7c 3f 0b 78     mr      r31,r1
400058f8:    42 9f 00 05     bcl-    20,4*cr7+so,400058fc <board_init_r+0x18>
400058fc:    7f c8 02 a6     mflr    r30
40005900:    80 1e ff e4     lwz     r0,-28(r30)
40005904:    7f c0 f2 14     add     r30,r0,r30
40005908:    90 7f 00 28     stw     r3,40(r31)
4000590c:    90 9f 00 2c     stw     r4,44(r31)
。。。。

    debug ("Now running in RAM - U-Boot at: %08lx/n", dest_addr);
40005948:    80 7e 80 50     lwz     r3,-32688(r30)
4000594c:    80 9f 00 2c     lwz     r4,44(r31)
40005950:    4c c6 31 82     crclr   4*cr1+eq
40005954:    48 01 8e d1     bl      4001e824 <printf>
"Now running in RAM - U-Boot at: %08lx/n"字符串的首地址也经过修正了

    for (cmdtp = &__u_boot_cmd_start; cmdtp !=  &__u_boot_cmd_end; cmdtp++) {
        ulong addr;
。。。
命令数组中的指令指针及注释字符串都是编译时获取的静态值,需要修正。当初始化的变量的值为指针及数组首地址时,都需要修正


#ifndef CFG_ENV_IS_NOWHERE
    env_name_spec += gd->reloc_off;
#endif
修改环境变量的地址

    /*
     * Copy exception vector code to low memory
     *
     * r3: dest_addr
     * r7: source address, r8: end address, r9: target address
     */
    .globl    trap_init
trap_init:
    lwz    r7, GOT(_start)
    lwz    r8, GOT(_end_of_vectors)

    li    r9, 0x100        /* reset vector always at 0x100 */

    cmplw    0, r7, r8
    bgelr                /* return if r7>=r8 - just in case */

    mflr    r4            /* save link register        */
1:
    lwz    r0, 0(r7)
    stw    r0, 0(r9)
    addi    r7, r7, 4
    addi    r9, r9, 4
    cmplw    0, r7, r8
    bne    1b
将中断处理相关的代码拷贝到reset vector开始的位置

    /*
     * relocate `hdlr' and `int_return' entries
     */
    li    r7, .L_MachineCheck - _start + EXC_OFF_SYS_RESET
    li    r8, Alignment - _start + EXC_OFF_SYS_RESET
2:
    bl    trap_reloc
对中断处理过程中的函数指针进行修正


参考文章
http://coryxie.spaces.live.com/Blog/cns!9429CA1F587766E2!208.entry
http://www.linuxforum.net/forum/showflat.php?Board=embedded&Number=632242
http://blog.sina.com.cn/s/blog_4ae6bb59010005y6.html~type=v5_one&label=rela_prevarticle