1. 程式人生 > 其它 >imx6ull 重定位分析

imx6ull 重定位分析

uboot段相關變數

在分析relocate_code函式之前,先來總結一下相關的uboot段相關變數,這些段的地址在uboot程式碼重定位的時候需要用到,將uboot原始碼進行編譯後,會在原始碼根目錄生成u-boot.lds連結檔案和u-boot.map記憶體對映檔案,通過這兩個檔案,可以尋找到uboot段的地址

在上面尋找到的變數值中,除了_start和__image_copy_start值不會該變,當修改了uboot的原始碼或改變了編譯條件,其他的變數都可能會發生改變,因此分析時,一定要以實際編譯時進行uboot分析。

relocate_code函式

relocate_code函式會傳入一個引數,該引數為gd->relocaddr,也就是uboot重定位的目的地址

接下來,對relocate_code函式進行分析,該函式用於重定位uboot,函式的定義在下面的彙編檔案中:

1 uboot/arch/arm/lib/relocate.S
 1 /*
 2  * void relocate_code(addr_moni)
 3  *
 4  * This function relocates the monitor code.
 5  *
 6  * NOTE:
 7  * To prevent the code below from containing references with an R_ARM_ABS32
 8  * relocation record type, we never refer to linker-defined symbols directly.
9 * Instead, we declare literals which contain their relative location with 10 * respect to relocate_code, and at run time, add relocate_code back to them. 11 */ 12 13 ENTRY(relocate_code) 14 ldr r1, =__image_copy_start/* r1 <- SRC &__image_copy_start */ //r1儲存源image開始地址 15 //r0 = gd->relocaddr,r4 = r0 - r1 = 0x8ff3b000 - 0x87800000 = 0x873b000
16 subs r4, r0, r1 /* r4 <- relocation offset */ //r4 = gd->reloc_off,儲存偏移地址 17 beq relocate_done /* skip relocation */ //如果r0和r1相等,則不需要uboot重定位 18 ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */ //r2儲存源image結束地址 19 20 copy_loop: 21 ldmia r1!, {r10-r11} /* copy from source address [r1] */ //拷貝uboot程式碼到r10和r11 22 stmia r0!, {r10-r11} /* copy to target address [r0] */ //將uboot程式碼寫到目的地址 23 cmp r1, r2 /* until source end address [r2] */ //判斷uboot是否拷貝完成 24 blo copy_loop //迴圈拷貝 25 26 /* 27 * fix .rel.dyn relocations //修復.rel.dyn段 28 */ 29 ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */ 30 ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */ 31 fixloop: 32 ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */ 33 and r1, r1, #0xff 34 cmp r1, #23 /* relative fixup? */ 35 bne fixnext 36 37 /* relative fix: increase location by offset */ 38 add r0, r0, r4 39 ldr r1, [r0] 40 add r1, r1, r4 41 str r1, [r0] 42 fixnext: 43 cmp r2, r3 44 blo fixloop 45 46 relocate_done: 47 48 ENDPROC(relocate_code)

函式傳入的引數為r0 = 0x8ff3b000,uboot重定位的目的地址,函式進來後,將__image_copy_start的數值賦值給r1,也就是uboot複製開始地址,從表格可以知道r1 = 0x87800000,接下來進行一個減法操作,此時r4將儲存著uboot的偏移量,也就是r4 = gd->reloc_off。

接下來會進行一個判斷,判斷uboot重定位的目的地址是否和uboot源地址是否相等,如果相等的話,表示不需要重定位,跳到relocate_done處,繼續執行,如果不相等的話,則是需要進行重定位。

將__image_copy_end的值賦值給r2,也就是uboot複製結束的地址,從表格中可以知道,此時r2 = 0x87868960,接下來,開始進行uboot程式碼的拷貝,進入到copy_loop迴圈,從r1,也就是__image_copy_start開始,讀取uboot程式碼到r10和r11暫存器,一次就拷貝兩個32位的資料,r1自動更新,儲存下一個需要拷貝的地址,然後將r10和r11裡面的資料,寫到r0儲存的地址,也就是uboot重定位的目的地址,r0自動更新。

接下來,比較r1和r2是否相等,也就是判斷uboot程式碼是否拷貝完成,如果沒完成的話,跳轉到copy_loop處,繼續迴圈,直到uboot拷貝完成。

函式relocate_code()的第二部分是修復.rel.dyn的定位問題,.rel.dyn段存放了.text段中需要重定位地址(也就是搬移了__image_copy_start~__image_copy_end的程式碼後,需要對其中存了的絕對地址進行更新,即絕對地址+偏移地址)的集合,uboot重定位就是uboot將自身拷貝到DRAM的另外一個地址繼續執行(DRAM的高地址),一個可執行的bin檔案,它的連結地址和執行地址一定要相等,也就是連結到哪個地址,執行之前就需要拷貝到哪個地址中進行執行,在前面的重定位後,執行地址和連結地址就不一樣了,這樣在進行定址就會出問題,對.rel.dyn的定位問題進行修復,就是為了解決該問題。

下面,使用一個簡單的例子進行分析:

先在新適配的板級檔案中新增測試程式碼,檔案如下:

uboot/board/freescale/mx6ul_comp6ul_nand/mx6ul_comp6ul_nand.c

新增的內容如下:

static int rel_a = 0;

void rel_test(void)
{
    rel_a = 100;
    printf("rel_test\n");
}

/* 在board_init()函式中呼叫rel_test()函式 */
int board_init(void)
{
        ...
        ...
        rel_test();
        
        return 0;
}

接下來對uboot原始碼重新編譯,並將u-boot檔案進行反彙編分析,使用命令如下:

$ cd uboot
$ make
$ arm-linux-gnueabihf-objdump -D -m arm u-boot > u-boot.dis

然後,開啟u-boot.dis檔案,並在檔案中,找到rel_a變數、rel_test函式和board_init函式相關的彙編程式碼,如下所示:

/* rel_test定址分析 */
8780424c <rel_test>:
8780424c:    e59f300c     ldr    r3, [pc, #12]    ; 87804260 <rel_test+0x14>
87804250:    e3a02064     mov    r2, #100    ; 0x64
87804254:    e59f0008     ldr    r0, [pc, #8]    ; 87804264 <rel_test+0x18>
87804258:    e5832000     str    r2, [r3]
8780425c:    ea00f1a9     b    87840908 <printf>
87804260:    87868994             ; <UNDEFINED> instruction: 0x87868994
87804264:    878494ce     strhi    r9, [r4, lr, asr #9]
...
...
878043e4:    e59f2038     ldr    r2, [pc, #56]    ; 87804424 <board_init+0x1bc>
878043e8:    e5923068     ldr    r3, [r2, #104]    ; 0x68
878043ec:    e3833030     orr    r3, r3, #48    ; 0x30
878043f0:    e5823068     str    r3, [r2, #104]    ; 0x68
878043f4:    ebffff94     bl    8780424c <rel_test>
...
...
87868994 <rel_a>:
87868994:    00000000     andeq    r0, r0, r0

在0x878043f4處,也就是board_init()函式中呼叫了rel_test()函式,在彙編程式碼中,可以看到是使用了bl指令,而bl指令是相對定址的(pc + offset),因此,可以知道,uboot中的函式呼叫時與絕對地址無關。

接下來,分析rel_test()函式對全域性變數rel_a的呼叫過程,在0x8780424c處開始,先設定r3的值為pc + 12地址處的值,由於ARM流水線的原因,當前pc的值為當前的地址加8,所以pc = 0x8780424c + 8 = 0x87804254,因此r3 = pc + 12 = ‬0x87804254 + 12 = 0x87804260,從反彙編的程式碼中可以看到0x87804260處的值為0x87868994,所以,最終r3的值為0x87868994,而且從反彙編程式碼中可以看到0x87868994就是變數rel_a的地址,rel_test()函式繼續執行,將100賦值到r2暫存器,然後通過str指令,將r2的值寫到r3的地址處,也就是將0x87868994地址處的值賦值100,就是函式中呼叫的rel_a = 100;語句。

從上面的分析,可以知道rel_a = 100;的執行過程為:

  • 在函式rel_test()的末尾處有一個地址為0x87804260的記憶體空間,此記憶體空間中儲存著變數rel_a的地址;
  • 函式rel_test()想要訪問變數rel_a,首先需要訪問0x87804260記憶體空間獲取變數rel_a的地址,而訪問0x87804260是通過偏移來訪問的,與位置無關的操作;
  • 通過訪問0x87804260獲取變數rel_a的地址,然後對變數rel_a進行賦值操作;
  • 函式rel_test()對變數rel_a的訪問並沒有直接進行,而是通過一個偏移地址0x87804260,找到變數rel_a真正的地址,然後才對其操作,偏移地址的專業術語為Label。

從分析中,可以知道,該偏移地址就是uboot重定位執行是否會出錯的原因,在上面可以知道,uboot重定位的偏移量為0x8ff3b000,將上面給出的反彙編程式碼進行重定位後,也就是加上地址偏移量0x873b000後,如下:

/* rel_test定址分析(重定位後) */
8ff3f24c‬ <rel_test>:
8ff3f24c:    e59f300c     ldr    r3, [pc, #12]
8ff3f250:    e3a02064     mov    r2, #100    ; 0x64
8ff3f254:    e59f0008     ldr    r0, [pc, #8]    
8ff3f258:    e5832000     str    r2, [r3]
8ff3f25c:    ea00f1a9     b    87840908 <printf>
8ff3f260:    87868994             ; <UNDEFINED> instruction: 0x87868994
8ff3f264:    878494ce     strhi    r9, [r4, lr, asr #9]
...
...
8ff3f3e4:    e59f2038     ldr    r2, [pc, #56]    
8ff3f3e8:    e5923068     ldr    r3, [r2, #104]    ; 0x68
8ff3f3ec:    e3833030     orr    r3, r3, #48    ; 0x30
8ff3f3f0:    e5823068     str    r3, [r2, #104]    ; 0x68
8ff3f3f4:    ebffff94     bl    8780424c <rel_test>
...
...
8ffa3994 <rel_a>:
8ffa3994:    00000000     andeq    r0, r0, r0

函式rel_test()假設呼叫後對rel_a變數進行訪問,分析和上面一樣,先通過pc和偏移量找到0x8ff3f260,該地址是重定位後的地址,可此時該地址的值為0x87868994,也就是沒有重定位後的rel_a變數的地址,但是從上面可以知道,uboot重定位後,rel_a變數的新地址為0x8ffa3994,因此,我們可以知道,如果uboot重定位後,要想正常訪問rel_a變數,必須要將0x8ff3f260地址中的值0x87868994加上偏移量0x873b000,才能保證uboot重定位後,能正常訪問到rel_a變數,將.rel.dyn段進行重定位修復,就是為了解決連結地址和執行地址不一致的問題。

在uboot中,對於重定位後連結地址與執行地址不一致的解決辦法就是使用位置無關碼,在uboot編譯使用ld連結的時候使用引數"-pie"可生成與位置無關的可執行程式,使用該引數後,會生成一個.rel.dyn段,uboot則是靠該段去修復重定位後產生的問題的,在uboot的反彙編檔案中,有.rel.dyn段程式碼,如下:

Disassembly of section .rel.dyn:

87868988 <__rel_dyn_end-0x91a0>:
87868988:    87800020     strhi    r0, [r0, r0, lsr #32]
8786898c:    00000017     andeq    r0, r0, r7, lsl r0
87868990:    87800024     strhi    r0, [r0, r4, lsr #32]
87868994:    00000017     andeq    r0, r0, r7, lsl r0
...
...
87868f08:    87804260     strhi    r4, [r0, r0, ror #4]
87868f0c:    00000017     andeq    r0, r0, r7, lsl r0
87868f10:    87804264     strhi    r4, [r0, r4, ror #4]
87868f14:    00000017     andeq    r0, r0, r7, lsl r0
...
...

rel.dyn段的格式如下,類似下面的兩行就是一組:

87868f08:    87804260     strhi    r4, [r0, r0, ror #4]
87868f0c:    00000017     andeq    r0, r0, r7, lsl r0

也就是兩個4位元組資料為一組,其中高4位元組是Label地址的標識0x17,低4位元組就是Label的地址,首先會判斷Label地址標識是否正確,也就是判斷高4位元組是否為0x17,如果是的話,低4位元組就是Label地址,在上面給出的兩行程式碼中就是存放rel_a變數地址的Label,0x87868f0c的值為0x17,說明低4位元組是Label地址,也就是0x87804260,需要將0x87804260 + offset處的值改為重定位後的變數rel_a地址。

在對.rel.dyn段以及Label的相關概念有一定的瞭解後,接下來,我們分析函式relocate_code()的第二部分程式碼,修復.rel.dyn段重定位問題,程式碼如下:

/*
     * fix .rel.dyn relocations //修復.rel.dyn段
     */
    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_code()函式第二部分程式碼呼叫後,將__rel_dyn_start的值賦給r2,也就是r2中儲存著.rel.dyn段的起始地址,然後將__rel_dyn_end的值賦給r3,也就是r3中儲存著.rel.dyn段的結束地址。

接下來,進入到fixloop處執行,使用ldmia指令,從.rel.dyn段起始地址開始,每次讀取兩個4位元組資料存放到r0和r1暫存器中,其中r0存放低4位元組的資料,也就是Label的地址,r1存放高4位元組的資料,也就是Label的標識,然後將r1的值與0xff相與,取r1值的低8位,並將結果儲存到r1中,接下來,判斷r1中的值是否等於23(0x17),如果r1不等於23的話,也就說明不是描述Label,跳到fixnext處迴圈執行,直到r2和r3相等,也就是遍歷完.rel.dyn段。

如果r1的值等於23(0x17)的話,繼續執行,r0儲存著Label地址,r4儲存著uboot重定位的偏移,因此,r0 + r4就得到了重定位後的Label地址,也就是上面分析的0x87804260 + 0x873b000 = 0x8ff3f260 = r0,此時r0已經儲存著重定位後的Label地址,然後使用ldr指令,讀取r0中儲存的值到r1中,也就是Label地址所儲存的rel_a變數的地址,但是此時,該rel_a變數的地址仍然是重定位之前的地址0x87868994,所以,此時r1 = 0x87868994,接下來,使用add指令,將r1中的值加上r4,也就是加上uboot重定位偏移量,所以,此時r1 = 0x87868994 + 0x873b000 = 0x8ffa3994,這不就是前面分析的uboot重定位後的rel_a變數的地址嗎?接下來使用str指令,將r1中的值寫入到r0儲存的地址中,也就是將Label地址中的值設定為0x8ffa3994,就是重定位後rel_a變數的新的地址。

比較r2和r3的值,檢視.rel.dyn段重定位修復是否完成,迴圈直到完成,才能執行完relocate_code()函式。

第二部分執行完成後,就解決了.rel.dyn段的重定位問題,從而解決了uboot重定位後,連結地址和執行地址不一致的問題。

relocate_vectors函式

執行完relocate_code函式後,接下來就是執行relocate_vectors函式,該函式用於重定位中斷向量表,該函式的定義同樣在彙編檔案:

uboot/arch/arm/lib/relocate.S

函式的定義如下所示:

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)

在該函式中,對於i.mx6ul晶片來說CONFIG_CPU_V7M沒有定義,接下來,判斷是否定義了CONFIG_HAS_VBAR,表示向量表偏移,在.config檔案有該配置,因此,會執行該定義相關的程式碼,首先是將gd->relocaddr的值賦給r0,也就是r0裡面儲存重定位後uboot的首地址,中斷向量表也是從這個首地址開始儲存的,接下來,使用mcr指令,將r0的值寫到CP15的VBAR暫存器中,其實就是將新的中斷向量表首地址寫到VBAR暫存器中,設定中斷向量表偏移。