zoukankan      html  css  js  c++  java
  • Mini440之uboot移植之源码分析uboot重定位(三)

    所谓的relocation,就是重定位,u-boot运行后会将自身代码拷贝到SDRAM的另一个位置继续运行。

    但基于以前的理解,一个完整可运行的bin文件,link时指定的链接地址,load时的加载地址,运行时的运行地址,这3个地址应该是一致的。

    relocation后运行地址不同于加载地址,特别是链接地址,ARM的寻址会不会出现问题?

    u-boot启动后会计算出一个靠近SDRAM顶端的地址,也就是gd->relocaddr,将自身代码拷贝到该地址,继续运行。

    但是这样会有一个问题,relocation后u-boot的运行地址跟其链接地址不一致,compiler会在link时确定了其中全局变量的绝对地址,链接地址 加载地址 运行地址应该一致,这样看来,arm在寻址这些变量时找到的应该是relocation之前的地址,这样relocation就没有意义了!

    因此我们在进行u-boot重定位需要包含代码段、rodata、data以及全局变量、函数的重定位。

    一、u-boot重定位

    1.1 重定位原理

    我们C代码编译成汇编后,我们需要思考以下几个问题:

    • 我们的函数跳转转为汇编代码后是怎样的;
    • 全局变量又是如何寻址的;

    实际上函数用b/bl相对跳转,因此代码进行重定位之后函数跳转是没有影响的。但是全局变量使用的是绝对地址,是有影响的。

    这里我们以LCD项目为例,来介绍全局变量的寻址原理。

    我们定义了const常量为例:

    const u8 sunflower_320x240[] = {
     ...
    }

    LCD项目的链接地址为0x30000000,代码反汇编后,查看只读数据段:

    Disassembly of section .rodata:
    
    30005634 <sunflower_320x240-0x18>:
    30005634:    aeb0d2ce     cdpge    2, 11, cr13, cr0, cr14, {6}
    30005638:    f5c1e3c4     .word    0xf5c1e3c4
    3000563c:    4920e0d1     stmdbmi    r0!, {r0, r4, r6, r7, sp, lr, pc}
    30005640:    564f4c20     .word    0x564f4c20
    30005644:    4f592045     svcmi    0x00592045
    30005648:    00002055     .word    0x00002055
    
    3000564c <sunflower_320x240>:
    3000564c:    73229322     .word    0x73229322
    30005650:    73229322     .word    0x73229322
    30005654:    93229322     .word    0x93229322
    30005658:    93229322     .word    0x93229322
    3000565c:    93227322     .word    0x93227322
    30005660:    93229322     .word    0x93229322
    ...

    我们在lcd_test函数使用到了sunflower_320x240全局变量:

    void lcd_test()
    {
    300017f0:    e92d4800     push    {fp, lr}
    300017f4:    e28db004     add    fp, sp, #4    ; 0x4
    300017f8:    e24dd010     sub    sp, sp, #16    ; 0x10
        /* 文件必须采用GBK编码 */
        lcd_draw_bmp(0,0,LCD_WIDTH,LCD_HEIGHT, sunflower_320x240);
    300017fc:    e59f3050     ldr    r3, [pc, #80]    ; 30001854 <lcd_test+0x64>
    30001800:    e58d3000     str    r3, [sp]
    30001804:    e3a00000     mov    r0, #0    ; 0x0
    30001808:    e3a01000     mov    r1, #0    ; 0x0
    3000180c:    e3a02d05     mov    r2, #320    ; 0x140
    30001810:    e3a030f0     mov    r3, #240    ; 0xf0
    30001814:    ebfffe7c     bl    3000120c <lcd_draw_bmp>
        char *data = "我爱你刘燕 I LOVE YOU ";
    30001818:    e59f3038     ldr    r3, [pc, #56]    ; 30001858 <lcd_test+0x68>
    3000181c:    e50b3008     str    r3, [fp, #-8]
       
        lcd_draw_char_lib(100, 50, 0xFF00FF, data, strlen(data));
    30001820:    e51b0008     ldr    r0, [fp, #-8]
    30001824:    eb0003c8     bl    3000274c <strlen>
    30001828:    e1a03000     mov    r3, r0
    3000182c:    e58d3000     str    r3, [sp]
    30001830:    e3a00064     mov    r0, #100    ; 0x64
    30001834:    e3a01032     mov    r1, #50    ; 0x32
    30001838:    e3a028ff     mov    r2, #16711680    ; 0xff0000
    3000183c:    e28220ff     add    r2, r2, #255    ; 0xff
    30001840:    e51b3008     ldr    r3, [fp, #-8]
    30001844:    ebffff83     bl    30001658 <lcd_draw_char_lib>
    }
    30001848:    e24bd004     sub    sp, fp, #4    ; 0x4
    3000184c:    e8bd4800     pop    {fp, lr}
    30001850:    e12fff1e     bx    lr
    30001854:    3000564c     .word    0x3000564c
    30001858:    30005634     .word    0x30005634

    我们可以发现:

    • 在函数的后面有label,PC+offset找到label;
    • label存放全局变量地址;
    • 函数后面的label作为text段的一部分;

    可以看到[pc,#80]地址处保存sunflower_320x240全局变量的地址0x3000564c。

    30001854:    3000564c     .word    0x3000564c

    我们LCD项目链接地址为0x30000000,我们将代码加载0x30000000地址处,并跳转到0x30000000处运行。

    我们在LCD项目初始化代码中将.text、.rodata.、.data段代码从0x30000000重新定位到A,重定位偏移为relocaddr=A-0x30000000,那么地址0x30001854(位于.text段)重定位后为:

    30001854+relocaddr:    3000564c     .word    0x3000564c  => 我们需要将这个地址值+重定位偏移

    此时我们会发现地址30001854+relocaddr处保存的sunflower_320x240全局变量的地址仍然是0x3000564c,很显然这个地址不是sunflower_320x240全局变量重定位之后的地址。那么如何解决这个问题呢?

    这个问题可以通过重定位.rel.dyn段解决。具体实现思路如下:

    在代码编译的时候指定-pie选项,将会生成.rel.dyn段,.rel.dyn段将会保存label的地址,我们在重定位的过程中从.rel.dyn段中找到label的地址:

    30006660:    30001854    .word    0x30001854

    获取lable地址中的值0x30001854,然后加上relocaddr得到地址30001854+relocaddr,这个地址存放的应该是重定位后sunflower_320x240全局变量的地址。我们取出这个地址的值0x3000564c然后加上偏移地址relocaddr,再写回地址30001854+relocaddr,这样我们就可以实现全局变量的重定位了。

    30001854+relocaddr:    3000564c+relocaddr     .word    0x3000564c+relocaddr

    下面是别人绘制的一张简图,比较生动,我就直接贴过来了:

    1.2  u-boot重定位

    我们已经分析到了_main(arch/arm/lib/crt0.S)中board_init_f函数的执行,接着继续分析_main。

    #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
    #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 */
    
        bl    c_runtime_cpu_setup    /* we still call old routine here */
    #endif

    其中GD_START_ADDR_SP 定义如下:

    #define GENERATED_GBL_DATA_SIZE 176 /* (sizeof(struct global_data) + 15) & ~15    @ */
    #define GENERATED_BD_INFO_SIZE 80 /* (sizeof(struct bd_info) + 15) & ~15    @ */
    #define GD_SIZE 168 /* sizeof(struct global_data)    @ */
    #define GD_BD 0 /* offsetof(struct global_data, bd)    @ */
    #define GD_RELOCADDR 44 /* offsetof(struct global_data, relocaddr)    @ */
    #define GD_RELOC_OFF 64 /* offsetof(struct global_data, reloc_off)    @ */
    #define GD_START_ADDR_SP 60 /* offsetof(struct global_data, start_addr_sp)    @ */

    以上代码主要做了以下事情:

    • 设置栈指针为gd->start_addr_sp,并且8字节对齐;
    • 设置r9=gd->bd,在内存图上可以看出,新的gd结构在bd结构的下面紧挨着,所以减去gd的大小就是新的gd起始地址,r9变成了重定位后的新地址的gd结构了;
    • 将here标号的地址值(adr取的是相对当前PC的偏移地址)读取到LR中,将重定位偏移值gd->reloc_off加载到R0寄存器中;
    • 链接寄存器LR加上偏移值R0后,LR的地址就变成重定位后的地址了;
    • 将重定位地址gd->relocaddr加载到R0中,作为参数传给relocate_code;
    • 执行重定位,从relocate_code回来后,就直接运行在重定位后的u-boot中了,here标号已经是重定位后那个here标号了;

    上面代码主要是用来将u-boot从CONFIG_SYS_TEXT_BASE重定位到gd->relocaddr位置。

    具体如图所示:这里为了方便绘制,将内存地址拆分成了两部分,右侧地址存放的是u-boot的运行地址。

    当Mini2440从NOR启动时,0x00000000就是板上2MB NOR FLASH实际的起始地址,NOR FLASH中的程序就从这里开始运行。

    需要注意的是:u-boot程序默认是从NOR启动的,此时2MB的NOR FLASH足够放下u-boot的所有代码。

    二、relocate_code(arch/arm/lib/relocate.S)

    2.1.text、.rodata、.data、.rel.dyn重定位(arch/arm/lib/relocate.S)

    /*
     * void relocate_code(addr_moni)
     *
     * This function relocates the monitor code.
     *
     * NOTE:
     * To prevent the code below from containing references with an R_ARM_ABS32
     * relocation record type, we never refer to linker-defined symbols directly.
     * Instead, we declare literals which contain their relative location with
     * respect to relocate_code, and at run time, add relocate_code back to them.
     */
    
    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

    根据u-boot.lds我们绘制各个段在内存中的分布图:

    以上代码主要重定位__image_copy_start、__image_copy_end的数据到新的地址:

    • r0设置为u-boot重定位后的位置gd->reloacaddr;
    • r1设置为__image_copy_start,为u-boot链接起始地址(链接地址需要和运行地址一致,此时链接地址就是目前u-boot运行的起始地址);
    • r4设置为u-boot重定位偏移地址gd->reloc_off;
    • 如果r4=0,表示r0=r1,不再进行重定位;
    • r2设置为__image_copy_end;
    • 以r1为起始地址(也就是目前u-boot的起始地址),加载[r1],[r1+4]字到r10,r11;
    • 以r0为起始地址(也就是重定位后的的新地址),加载r10,r11的值到[r0],[r0+4]中;
    • 比较是否读取到结束地址__image_copy_end,一直循环,直到拷贝结束;

    除了代码段外,这里我们重定位了只读数据段和初始化数据段:

    • rodata{}:声明只读数据段,简称rodata段,存放常量,字符常量,const常量,据说还存放调试信息;
    • .data{}:声明初始化数据段(Initialized data segment),简称data段,存放程序中已经初始化全局与初始化静态变量;

    2.2 .rel.dyn重定位(arch/arm/lib/relocate.S)

        /*
         * 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, #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)

    这里主要用于重定位.rel.dyn段,至于为什么需要定义这个段,实际上就是解决上面我们介绍的类似sunflower_320x240全局变量重定位存在的问题。这里仍然以sunflower_320x240为例:

    • r2设置为__rel_dyn_start;
    • r3设置为__rel_dyn_end;
    • 以r2为起始地址(也就是动态符号表的起始地址),加载[r2],[r2+4]到r0,r1中,那么 r0得到的就是0x30001854;
    • 取出r1中数据的低8位、23用来检查这个符号是不是需要被重定位,不需要的话就跳过;
    • r4是重定位偏移,设置r0=r0+r4,也就是新的u-boot里面sunflower_320x240全局变量的地址;
    • 将r0地址内的数据存到r1中,即0x3000564c,这个值就是sunflower_320x240全局变量数据的存放地址;
    • 给r1加上偏移r4,将加了偏移后的值(变量的地址)写回,这样新的u-boot就能正确访问了;
    • 跳转到到lr,lr为u-boot重定位之后here标号的位置;

    三、 relocate_vectors

    接下来重定位向量表,这个很简单,就是操作协处理器:

    /*
     * Default/weak exception vectors relocation routine
     *
     * This routine covers the standard ARM cases: normal (0x00000000),
     * high (0xffff0000) and VBAR. SoCs which do not comply with any of
     * the standard cases must provide their own, strong, version.
     */
    
        .section    .text.relocate_vectors,"ax",%progbits
        .weak        relocate_vectors
    
    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.
         */
        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)

    这里主要做了一下事情:

    • r0设置为u-boot重定位后的位置gd->reloacaddr;
    • 读协处理器中的c1寄存器数据到ARM处理器的r2里面;
    • 主要是控制bit[13]:V,对于支持高端异常向量表的系统,本控制位控制向量表的位置: 
      • 0 :选择低端异常中断向量 0x0~0x1c;
      • 1 :选择高端异常中断向量0xffff0000~ 0xffff001c;

      • 对于不支持高端异常向量表的系统,读取时该位返回0,写入时忽略;

    • ands 后面的 s 会影响CPSR状态的寄存器的标志位,若相与的结果为0,则CPSR的状态标志位 Z = 1;反之,Z = 0;
    • If V=0,则Z=1,可执行 ldr r1,=0x00000000指令;

    • If V=1,则Z=0,可执行 ldr r1,=0xFFFF0000指令;
    • 最终,r1则为异常向量表的地址;
    • 将r0地址的值加载到r2,r0+4地址的值加载到r3,...;
    • 将r2-r8,r10寄存器的值街道地址r1,r1+4,...;
    • 最终会将r0地址处的值写到r1,长度为16*4个字节,即将重定位向量表和向量处理函数;
    • !表示将地址写回,IA:(Increase After)的含义每次传送后地址加4

     0x00-0x1c为向量表,0x20~0x40为向量处理函数。因此需要重定位64个字节的数据。

    四、清bss段

    介绍完代码重定位以及终端向量重定位之后,_main函数还剩下一小部分没有介绍,这部分主要涉及到bss段清0:

    • .bss:简称bss段,存放程序中未初始化全局与未初始化静态变量,该区域会需要清零;
    #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
        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) */
        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

    这一段代码主要做了以下事情:

    • 设置r0为__bss_start,设置r1为__bss_end,设置r2为0 ;
    • 将r0~r1地址数据清零;
    • 初始化led、点亮led;
    • 设置r0=r9,r9为u-boot重定位后的新地址的gd结构;设置r1为重定位后的地址,作为参数传给board_init_r;
    • 调用board_init_r,进入u-boot第二阶段;

    参考文章:

    [1]u-boot2020.04移植(5、u-boot重定位)

    [2]嵌入式Linux学习:重定位(Relocation)

    [3]uboot1: 启动流程和移植框架

  • 相关阅读:
    Universal-image-loader Mason 修复版(ImageLoader Image can't be decoded)
    118、通过solid来定义不同边框的颜色,可以只定义一个边框的颜色
    Freeline的快速集成(转载)
    SQL存储过程解密
    Bootstrap
    PHP5.6.15连接Sql Server 2008配置方案
    php使用saop简单例子和注意事项
    php使用mysql和mysqli连接查询数据
    MYSQL中UNIX时间戳与日期的转换
    xp和win7安装telnet服务
  • 原文地址:https://www.cnblogs.com/zyly/p/15590834.html
Copyright © 2011-2022 走看看