Mini2440之uboot移植之源碼分析u-boot重定位(三)


所謂的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: 啟動流程和移植框架


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM