uboot 與 代碼重定位


ref: https://blog.csdn.net/dhauwd/article/details/78566668、https://blog.csdn.net/yueqian_scut/article/details/39004727、https://blog.csdn.net/Egean/article/details/84889565、https://www.cnblogs.com/zafu/p/7399859.html

涉及領域:

裸機程序,uboot,Linux kernel

1、為什么需要重定位?

大部分的程序是不需要重定位的,但是有時候需要。

最常見的例子就是我們的UBOOT,因為我們的UBOOT有200多KB,但是我們開始BL0的地方只有96KB。所以我們需要在96KB之前進行重定位,使開發板能夠進行重定位。如果代碼不是位置無關碼,代碼必須放在鏈接地址開始的地方,程序才可以正常運行,否則的話當PC去訪問、執行某個變量名、函數名對應地址上的代碼時就會找不到,接着程序無疑就是跑飛。

2、什么是重定位?

重定位:把代碼搬移到你想要的地址,本來程序是運行在運行地址處的,你可以通過重定位搬移到鏈接地址處。

鏈接地址: 編譯器對代碼中的變量名、函數名等東西進行一個地址的編排,賦予這些抽象的東西一個地址,然后在程序中訪問這些變量名、函數名就是在訪問一些地址,這些地址我們稱之為編譯地址。

運行地址:是指程序指令真正運行的地址,是由用戶指定的,用戶將運行地址燒錄到哪里,也就是PC當前執行指令所在的實際地址,就是運行的地址。也就是真實在程序中運行的地址。

3、重定位的基礎知識

(1)大部分指令是位置有關編碼。

位置無關碼(PIC, position independent code):

匯編源文件被編碼成二進制可執行程序后與位置無關。有些特別的指令,可以跟地址沒有關系。也就是說這些代碼實際運行時,不管放在哪里都能正常運行。

位置有關碼:

匯編編碼成二進制可執行程序后和內存地址是有關的。

PS:

我們在設計一個程序時,會給這個程序指定一個運行地址。就是說我們在寫程序時,其實我們是知道我們程序將來被運行的地址的。

必須給編譯器和鏈接器指定這個地址才行,最后得到二進制程序。

理論上和你指定的運行地址是有關的,這就叫做位置有關代碼。

(2)對於位置有關碼來說:最終執行時的運行地址和編譯鏈接時給定的鏈接地址必須相同,否則一定會出錯。

如果編譯時 使用-Ttext 0x0來指定鏈接地址是0x0,這意味着我們認為這個程序將來會放在這個內存地址中運行。但是實際上我們運行的地址是下載在開發板的地址0xd0020010。因為是位置無關碼,所以運行程序來是沒有什么問題的。而且我們開發板對這些程序進行了映射,所以說這是一個偶然的情況。

(3)我們再來分析一下S5PV210的啟動過程。

img

官方建議的啟動過程(假定你的Bootloader為80KB)

開機啟動,執行BL0,BL0會加載外部啟動設備中的bootloader的前16KB到SRAM,

​ (BL0是廠家事先固化好的程序)

校驗BL1,運行BL1

BL1在運行時,初始化外部DDR,加載剩余的64kb代碼到 BL2中 ( 64 = 80 - 16)

運行BL2,初始化DDR,並且將OS搬運到DDR

執行OS,啟動完成。

UBOOT實際上的啟動的方法:

(由於 BL2 的空間也太小了,使用起來非常有局限,所以uboot的設計者干脆在BL1以后,連同BL2與OS有關的直接放到DDR上運行了)

先開機上電,BL0運行,BL0會加載外部啟動設備中的UBOOT的前16KB(BL1)到SRAM中去運行,

BL1運行會初始化DDR。然后將整個UBOOT,搬運到我們的DDR中。

從SRAM中直接長跳轉到DDR中繼續執行我們的UBOOT。直到UBOOT完全啟動。

​ 長跳轉的意思就是從SRAM中跳轉到DDR中。UBOOT啟動后在命令行中去執行OS。

4、從源碼到可執行程序的步驟:預編譯、編譯,鏈接、[strip]

預編譯: 比如C中的宏定義就是由預編譯器處理的,注釋等也是由預編譯器處理的。

編譯: 編譯器來執行,把源碼中的.c/.s文件轉換為.o文件。

鏈接: 鏈接器來執行,把.o文件中的各種函數(段)按照一定的規則(即使不用用鏈接腳本指定,鏈接器也有默認的固定的順序)鏈接到一起,形成可執行程序。

鏈接的本質是規則文件,它指明了一種行動的規則,它是我們程序員用來指揮鏈接器工作的一種語言。

鏈接器會參考鏈接腳本來處理我們.o文件哪些段,將其鏈接成一個可執行程序。

strip: 把可執行程序中的符號信息給拿掉,以節省空間,一般可以節省3分之一的空間。這樣就從 elf 文件 轉換 為 bin 文件

5、鏈接腳本存在的意義

鏈接腳本用來指定編譯時的一些選項,使得程序能夠按照開發者的意志進行指定的排布,也為了在某些特定的場合滿足需求。

1.代碼段 (.text)

代碼段(code segment/text segment)通常是指用來存放 程序執行代碼 的一塊內存區域。這部分區域的大小在程序運行前就已經確定,並且內存區域通常屬於 只讀 , 某些架構也允許代碼段為可寫,即允許修改程序。在代碼段中,也有可能包含一些 只讀的常數變量 ,例如字符串常量等。程序段為程序代碼在內存中的映射。一個程序可以在內存中多有個副本。

2.數據段(.data)

數據段就是C語言中有顯示的初始化為非0的全局變量。數據段(data segment)通常是指用來存放程序中 已初始化 的 全局變量 的一塊內存區域。數據段屬於靜態內存分配。

3.BSS段(.bss) ,又叫做ZI段,零初始化段,

通常是指用來存放程序中未初始化或初始化為0的全局變量和靜態變量的一塊內存區域。BSS是英文Block Started by Symbol的簡稱。BSS段屬於靜態內存分配。特點是可讀寫的,在程序執行之前BSS段會自動清0。bss段的存放是指為其預留空間(占位符)BSS段在可執行文件中時候不占磁盤空間,要運行的時候才分配空間並清0.

4.自定義段

由我們程序員自己定義,段的屬性和特征也由我們自己定義。

在移植 uboot 時,接觸到一個概念叫做 位置無關碼,那么與它對應的就是位置有關碼。提到這兩個概念就還得提一提鏈接地址、加載地址。

鏈接地址,鏈接腳本里指定的,理論上程序運行時所處的地址。在編譯時,編譯器會根據鏈接地址來翻譯位置有關碼。

加載地址,程序運行時,實際所處的地址。

位置無關碼,位置有關碼,是相對於一條指令的正常目的來說的。比如 ldr r0 ,=標號,它的正常目的是取得標號處的地址,對於這個目的,它是位置有關碼,運行的地址不對就獲取不到正確的標號地址,其實它無論在哪都是獲取的程序加載地址等於鏈接地址時,標號的地址,如果你就是想要這個值,那么用這條指令是非常正確的,就不用理會什么位置無關碼,位置有關碼的概念了,這一點非常重要。

因此,當加載地址不等於鏈接地址時,並不是不可以用位置無關碼,而是要看你用位置無關碼是否達到了你想要的目的。

位置無關碼,依賴於程序當前運行的PC值,進行相對的跳轉,導致的結果就是,無論代碼在哪,總能達到指令的正常目的,因此是位置無關的。

位置有關碼,不依賴當前PC值,是絕對跳轉,只有程序運行在鏈接地址處時,才能達到指令的正常目的,因此是位置有關系的。

下面,我們來看常用的匯編指令以及C語言中哪些操作是位置有關碼,哪些是位置無關碼。


SECTIONS {  
   . = 0x33f80000;  
   .text : { *(.text) }  
     
   . = ALIGN(4);  
   .rodata : {*(.rodata*)}   
     
   . = ALIGN(4);  
   .data : { *(.data) }  
     
   . = ALIGN(4);  
   __bss_start = .;  
   .bss : { *(.bss)  *(COMMON) }  
   __bss_end = .;  
}  

.text  
.global _start  
_start:  
 
   bl close_watch_dog      /* 相對跳轉,位置無關 */  
   bl _start  
   adr r0, close_watch_dog /* 獲取標號地址,位置無關 */  
     
   ldr r0, SMRDATA         /* 獲取標號處的值,位置無關 */  
 
   ldr r0, =0x12345678  
   ldr r0, =SMRDATA        /* 獲取標號地址,位置有關 */  
   ldr r0, =main           /* 獲取函數名的地址,位置有關 */  
   ldr r0 ,=__bss_start    /* 獲取鏈接腳本里標號的地址,位置有關 */  
 
     
close_watch_dog:  
   mov r1, #0  
   str r1, [r0]  
   mov pc, lr  
     
SMRDATA:  
   .word  0x22111120 

int a;  
void abc(){  
    a = 2;  
}  
int main(){  
    int b;  
    a =1 ;  
    b =1 ;  
    abc();  
    return 0;  
}

如果加載地址為 0 ,那么代碼將按照下面的順序排放


00000000 <_start>:  
00000000:   eb000006    bl  33f80020 <close_watch_dog>  
00000004:   ebfffffd    bl  33f80000 <_start>  
00000008:   e28f0010    add r0, pc, #16  
0000000c:   e59f0018    ldr r0, [pc, #24]   ;   
00000010:   e59f0018    ldr r0, [pc, #24]   ;   
00000014:   e59f0018    ldr r0, [pc, #24]   ;   
00000018:   e59f0018    ldr r0, [pc, #24]   ;   
0000001c:   e59f0018    ldr r0, [pc, #24]   ;   
 
00000020 <close_watch_dog>:  
00000020:   e3a01000    mov r1, #0  
00000024:   e5801000    str r1, [r0]  
00000028:   e1a0f00e    mov pc, lr  
 
0000002c <SMRDATA>:  
0000002c:   22111120    andscs  r1, r1, #8  
00000030:   12345678    eorsne  r5, r4, #125829120  ; 0x7800000  
00000034:   33f8002c    mvnscc  r0, #44 ; 0x2c  
00000038:   33f80064    mvnscc  r0, #100    ; 0x64  
0000003c:   33f800a0    mvnscc  r0, #160    ; 0xa0  
 
00000040 <abc>:  
00000040:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)  
00000044:   e28db000    add fp, sp, #0  
00000048:   e59f3010    ldr r3, [pc, #16]   ; 33f80060 <abc+0x20>  
0000004c:   e3a02002    mov r2, #2  
00000050:   e5832000    str r2, [r3]  
00000054:   e28bd000    add sp, fp, #0  
00000058:   e8bd0800    pop {fp}  
0000005c:   e12fff1e    bx  lr  
00000060:   33f800a0    mvnscc  r0, #160    ; 0xa0  
 
00000064 <main>:  
00000064:   e92d4800    push    {fp, lr}  
00000068:   e28db004    add fp, sp, #4  
0000006c:   e24dd008    sub sp, sp, #8  
00000070:   e59f3024    ldr r3, [pc, #36]   ; 33f8009c <main+0x38>  
00000074:   e3a02001    mov r2, #1  
00000078:   e5832000    str r2, [r3]  
0000007c:   e3a03001    mov r3, #1  
00000080:   e50b3008    str r3, [fp, #-8]  
00000084:   ebffffed    bl  33f80040 <abc>  
00000088:   e3a03000    mov r3, #0  
0000008c:   e1a00003    mov r0, r3  
00000090:   e24bd004    sub sp, fp, #4  
00000094:   e8bd4800    pop {fp, lr}  
00000098:   e12fff1e    bx  lr  
0000009c:   33f800a0    mvnscc  r0, #160    ; 0xa0

如果加載地址為0x33f80000 則按照下邊的順序排放


33f80000 <_start>:  
33f80000:   eb000006    bl  33f80020 <close_watch_dog>  
33f80004:   ebfffffd    bl  33f80000 <_start>  
33f80008:   e28f0010    add r0, pc, #16  
33f8000c:   e59f0018    ldr r0, [pc, #24]   ; 33f8002c <SMRDATA>  
33f80010:   e59f0018    ldr r0, [pc, #24]   ; 33f80030 <SMRDATA+0x4>  
33f80014:   e59f0018    ldr r0, [pc, #24]   ; 33f80034 <SMRDATA+0x8>  
33f80018:   e59f0018    ldr r0, [pc, #24]   ; 33f80038 <SMRDATA+0xc>  
33f8001c:   e59f0018    ldr r0, [pc, #24]   ; 33f8003c <SMRDATA+0x10>  
 
33f80020 <close_watch_dog>:  
33f80020:   e3a01000    mov r1, #0  
33f80024:   e5801000    str r1, [r0]  
33f80028:   e1a0f00e    mov pc, lr  
 
33f8002c <SMRDATA>:  
33f8002c:   22111120    andscs  r1, r1, #8  
33f80030:   12345678    eorsne  r5, r4, #125829120  ; 0x7800000  
33f80034:   33f8002c    mvnscc  r0, #44 ; 0x2c  
33f80038:   33f80064    mvnscc  r0, #100    ; 0x64  
33f8003c:   33f800a0    mvnscc  r0, #160    ; 0xa0  
 
33f80040 <abc>:  
33f80040:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)  
33f80044:   e28db000    add fp, sp, #0  
33f80048:   e59f3010    ldr r3, [pc, #16]   ; 33f80060 <abc+0x20>  
33f8004c:   e3a02002    mov r2, #2  
33f80050:   e5832000    str r2, [r3]  
33f80054:   e28bd000    add sp, fp, #0  
33f80058:   e8bd0800    pop {fp}  
33f8005c:   e12fff1e    bx  lr  
33f80060:   33f800a0    mvnscc  r0, #160    ; 0xa0  
 
33f80064 <main>:  
33f80064:   e92d4800    push    {fp, lr}  
33f80068:   e28db004    add fp, sp, #4  
33f8006c:   e24dd008    sub sp, sp, #8  
33f80070:   e59f3024    ldr r3, [pc, #36]   ; 33f8009c <main+0x38>  
33f80074:   e3a02001    mov r2, #1  
33f80078:   e5832000    str r2, [r3]  
33f8007c:   e3a03001    mov r3, #1  
33f80080:   e50b3008    str r3, [fp, #-8]  
33f80084:   ebffffed    bl  33f80040 <abc>  
33f80088:   e3a03000    mov r3, #0  
33f8008c:   e1a00003    mov r0, r3  
33f80090:   e24bd004    sub sp, fp, #4  
33f80094:   e8bd4800    pop {fp, lr}  
33f80098:   e12fff1e    bx  lr  
33f8009c:   33f800a0    mvnscc  r0, #160    ; 0xa0  
 
Disassembly of section .bss:  
 
33f800a0 <a>:  
33f800a0:   00000000    andeq   r0, r0, r0

一、B BL指令

bl close_watch_dog
33f80000:  eb000006   bl33f80020  <close_watch_dog> 

b 是相對跳轉:PC + 偏移值 (PC值等於當前地址+8)

偏移值:機器碼 0xeb000006 低 24位 0x000006 按符號為擴展為 32 位 0x00000006 正數,向后跳轉 0x6 個 4字節 也就是 0x1c

1、加載地址0:0 + 8 + 0x1c = 0x20 正確跳轉

2、加載地址0x3ff80000:0x3ff80000 + 8 + 0x1c = 0x33f80020 正確跳轉

bl _start
33f80004:  ebfffffd  bl33f80000 <_start>

偏移值:機器碼 0xebfffffd 低 24位 fffffd 按符號位擴展為 32 位 0xfffffffd 負數(-3),向前跳轉 0x3 個 4字節 也就是 0xc

1、加載地址0:4 + 8 - 0xc = 0 正確跳轉

2、加載地址0x3ff80000:   0x3ff80004 + 8 + 0xc = 0x33f80000 正確跳轉

通過以上分析,我們知道B是相對跳轉,位置無關碼,也可以知道為什么32為arm指令集,B的范圍為正負32M了,24位去掉1位符號位,恰好等於32M。

二、ADR

adr r0, close_watch_dog   /* 獲取標號處的地址,位置無關 */
33f80008: e28f0010  add  r0, pc, #16 

1、加載地址0:0 + 8 + 16 = 0x20 正確

2、加載地址0x3ff80000:0x3ff80008 + 8 + 16 = 0x33f80020 正確

adr 獲取的是標號處的“實際”地址,標號在哪就是哪個地址,跟位置無關,總能獲得想要的值。

三、LDR

ldr r0, SMRDATA    /* 獲取標號處的值,位置無關 */
33f8000c:   e59f0018  ldr   r0, [pc, #24]; 33f8002c <SMRDATA>

ldr r0, SMRDATA /* 獲取標號處的值,位置無關 */

33f8000c:e59f0018ldrr0, [pc, #24]; 33f8002c <SMRDATA>

1、加載地址0:r0 = c + 8 + 24 = 0x2c 處的值 0x22111120 正確

2、加載地址0x3ff80000:r0 = 0x3ff8000c + 8 + 24 = 0x33f8002c處的值 0x22111120 正確

 ldr r0, =0x12345678  /* 常數賦值,位置無關 */
 33f80010: e59f0018  ldr  r0, [pc, #24]; 33f80030 <SMRDATA+0x4>

1、加載地址0:r0 = 0x10 + 8 + 24 = 0x30 處的值 0x12345678 正確

2、加載地址0x3ff80000:r0 = 0x3ff80010 + 8 + 24 = 0x33f80030處的值 0x12345678 正確

ldr r0, =SMRDATA      /* 獲取標號地址,位置有關 */
33f80014: e59f0018  ldrr0, [pc, #24]; 33f80034 <SMRDATA+0x8>

1、加載地址0:r0 = 0x14 + 8 + 24 = 0x34 處的值 33f8002c 與標號實際地址(2c)不符合,不正確

2、加載地址0x3ff80000:r0 = 0x3ff80014 + 8 + 24 = 0x33f80034 處的值 33f8002c 正確

 ldr r0, =main/* 獲取函數名的地址,位置有關 */
 ldr r0 ,=__bss_start /* 獲取鏈接腳本里標號的地址,位置有關 */

這倆和 ldr r0, =SMRDATA 一致,位置有關,在0地址處運行不正確。

四、C函數

1、全局變量

00000040 <abc>:  
00000040:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)  
00000044:   e28db000    add fp, sp, #0  
00000048:   e59f3010    ldr r3, [pc, #16]   ; 33f80060 <abc+0x20>  
0000004c:   e3a02002    mov r2, #2  
00000050:   e5832000    str r2, [r3]  
00000054:   e28bd000    add sp, fp, #0  
00000058:   e8bd0800    pop {fp}  
0000005c:   e12fff1e    bx  lr  
00000060:   33f800a0    mvnscc  r0, #160    ; 0xa0  
000000a0 <a>: 
000000a0:  00000000  andeq  r0, r0, r0 
33f80040 <abc>:  
33f80040:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)  
33f80044:   e28db000    add fp, sp, #0  
33f80048:   e59f3010    ldr r3, [pc, #16]   ; 33f80060 <abc+0x20>  
33f8004c:   e3a02002    mov r2, #2  
33f80050:   e5832000    str r2, [r3]  
33f80054:   e28bd000    add sp, fp, #0  
33f80058:   e8bd0800    pop {fp}  
33f8005c:   e12fff1e    bx  lr  
33f80060:   33f800a0    mvnscc  r0, #160    ; 0xa0  
33f800a0 <a>: 
33f800a0:  00000000  andeq  r0, r0, r0 

r3 為全局變量 a 的地址,a 是存放在 0起始的地址還是0x33f80000起始的地址,它都認為 a 的地址是 0x33f800a0 。因此,C函數中調用全局變量是位置有關碼。

2、函數調用

33f80084:  ebffffed  bl  33f80040 <abc>

由於 main 函數和 abc 函數挨得比較近,在32M范圍之內,因此被翻譯成了一條 bl 指令,那么與位置無關。

如果,調用的函數比較遠,大於32M的話,我認為是與位置有關系的,這個不再驗證了。

3、局部變量

局部變量在函數剛開始的地方被壓入棧,賦值語句被翻譯成:

  33f8007c:   e3a03001  mov  r3, #1
  33f80080:   e50b3008
  str r3, [fp, #-8]

位置無關。


免責聲明!

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



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