共享可寫節包含重定位 理解重定位

2021-10-14 05:03:43 字數 3731 閱讀 3265

一、段的概念

段是程式的組成元素。將整個程式分成乙個乙個段,並且給每個段起乙個名字,然後在鏈結時就可以用這個名字來指示這些段,使得這些段排布在合適的位置。

乙個程式通常包含以下五個段:

**段(.text)

存放**指令

唯讀資料段(.rodata)

存放有初始值並且const修飾的全域性類變數(全域性變數或static修飾的區域性變數)

資料段(.data)

存放有初始值的全域性類變數

零初始化段(.bss)

存放沒有初始值或初始值為0的全域性類變數

注釋段(.comment)

存放注釋

注意:

char g_chara = 'a'; //儲存在 .data段 const char g_charb = 'b'; //儲存在 .rodata段 const char g_charc; //儲存在 .bss段 int g_inta = 0; //儲存在 .bss段 int g_intb; //儲存在 .bss段
二、鏈結指令碼為了在鏈結時使用鏈結指令碼,需要在 makefile 用-t filename.lds 指定。否則在編譯時將使用預設的鏈結指令碼。

需要注意,對於結構較為簡單的程式,也可以使用預設的鏈結指令碼,並手動指定不同段在輸出檔案中的位置。

#將所有程式的.text段放在一起,起始位址設定為0x80100000#將所有程式的.data段放在一起,起始位址設定為0x80102000$ (ld) -ttext 0x80100000 -tdata 0x80102000 ...
預設的鏈結指令碼無法進行一些段的複雜操作,這時候就需要用到鏈結指令碼。

sections  >region :phdr =fill    ...}
sections         . = align(4);              //將當前位址以4位元組為標準對齊    .rodata :    //.rodata存放在.text之後,包含所有鏈結檔案的唯讀資料段        . = align(4);    .data :     //.data存放在.rodata之後,包含所有鏈結檔案的資料段        . = align(4);    __bss_start = .;    //將當前位址的值儲存為變數__bss_start    .bss :    //.bss存放在.data段之後, 包含所有檔案的bss段和注釋段    __bss_end = .;        //將當前位址的值儲存為變數__bss_end}
注意:我們需要依次排列**段、唯讀資料段、資料段、.bss段、.common。上面寫的鏈結指令碼稱為一體式鏈結指令碼,與之相對的是分體式鏈結指令碼,區別在於**段(.text) 和資料段(.data)的存放位置是否是分開的。

分體式鏈結指令碼例項:

sections     . = align(4);    .rodata :     . = align(4);    .data 0x80800000 :         ......(省略)
很多**採用了一體式鏈結指令碼,原因如下:

三、清除bss段

最開始就說過了,bin 檔案中並不會儲存 bss 段的值,因為這些值都是 0,儲存這些值沒有意義並會使得 bin 檔案臃腫。當程式執行涉及到 bss 段上的資料時,cpu 會從 bss 段對應的記憶體位址去讀取對應的值,為了確保從這段記憶體位址上讀取到的 bss 段數值為 0,在程式執行前需要將這一段記憶體位址上的資料清零,即清除 bss 段。

具體思路就是將 bss 段對應的位址讀取,並將位址上的資料依次清零。

如下為在彙編檔案中實現清除bss段功能:

.text  //.text表示**段,彙編系統預定義段名,說明下面的彙編是**段.global  _start  //.global表示_start是乙個全域性符號/* 標籤_start,說明匯程式設計序的預設入口是_start,也可以在鏈結指令碼中使用entry來指明其它的入口點,類似 c 語言 main()函式,_start 是整個程式的入口,即程式執行的第一條指令*/_start:     /* 設定棧 */    ldr  sp,=0x80200000 //將0x80200000賦值給暫存器sp,即設定棧位址,因為c語言函式呼叫時,儲存現場/上下文和傳遞引數需要用到棧    bl clean_bss //跳轉到標籤clean_bss,相當於呼叫clean_bss函式,並將bl main指令位址儲存到暫存器lr中    bl main //進入c語言的main()函式,並將b halt指令位址儲存到暫存器lr中halt:  //標籤halt    b  halt    //跳轉到標籤halt,迴圈執行b halt指令執行,這就是乙個死迴圈。如果main函式返回,就在這裡死迴圈。clean_bss: //相當於乙個函式,clean_bss 是函式名,下面彙編指令是函式內容    /* 清除bss段 */    ldr r1, =__bss_start  //將鏈結指令碼定義的bss起始位址賦值給暫存器r1    ldr r2, =__bss_end    //將鏈結指令碼定義的bss結束位址賦值給暫存器r2    mov r3, #0            //將0賦值給暫存器r3,即r3=0clean:        //下面彙編指令相當於迴圈體,直到 r1 與 r2 相等    str r3, [r1]    //將暫存器r3的值儲存到暫存器r1的值對應位址中    add r1, r1, #4    //將暫存器 r1 的值加上 4,賦值給暫存器 r1,即 r1 = r1+4    cmp r1, r2    //比較暫存器r1的值與暫存器r2的值    bne clean  //如果暫存器r1的值與暫存器r2的值不相等,跳轉到標籤clean    mov pc, lr   //如果暫存器r1的值與暫存器r2的值相等,就執行此行,返回到 bl main 處,繼續執行
四、重定位程式是按順序,從起始位址依次往下執行的;當需要破壞這種順序執行跳轉到某一具體目標位址執行時,需要告知cpu下一條要執行的指令位於這一目標位址處,而這一目標位址需要通過計算目標位於程式中的位置來獲得;計算目標位址的過程稱為重定位。簡單的理解重定位,就是當a.c中呼叫b.c中的函式relfun()時,需要通過計算獲得relfun位於可執行程式中的具體位置,這裡計算得到relfun位址的過程即可理解為重定位。重定位所做的事就是使每個引用 (目標)的位置,都能找到對應的目標:

/* a.c 檔案 */int main(void)/* b.c 檔案 */void relfun(void)/* a.c和b.c編譯後生成可執行檔案ab.exe */位址  |  偽**0:main1:       call  relfun….200:     relcun  /* 程式鏈結時計算得到的位址 */
重定位實際就是在執行位址處執行一段位置無關碼pic(position-independent code),讓這段pic(也就是重定位**)從執行位址處把整個程式映象拷貝乙份到鏈結位址處,完了之後使用一句長跳轉指令從執行位址處直接跳轉到鏈結位址處去執行同乙個函式,這樣就實現了重定位之後的無縫連線。

這裡要注意,位置無關碼需要使用相對跳轉命令 b 或 bl。

重定位之後,使用 ldr pc = ***,跳轉到絕對位址(runtime address)

移動重定位表到新增節

資料目錄中的表是分散在各個節裡的,如果對節進行加密,作業系統找不到表,就無法引導程式。因此加密前要先把表移動到新的節裡。計算重定位表的大小,首先要遍歷重定位表,累加 sizeofblock,然後新增乙個節,大小是表的大小檔案對齊。最後把錶複製到新增節的開頭,然後更新資料目錄的virtualaddre...

什麼是重定位

6 1 儲存器的基本概念 主儲存器管理仍然是今天作業系統十分重要的內容 能否合理而有效的使用主存,在很大成度上反映了作業系統的效能,並直接影響到整個計算機系統作用的發揮。6.1.1 儲存器的層次 目前在許多計算機系統中,採用 儲存器結構,即高速緩衝儲存器 主儲存器和外部儲存器。儲存器的比較 從快取記...

u boot中重定位

在學習 u boot的時候,對程式重定位不理解,不知道為什麼要進行重定位,在網上查詢資料學習了一下。首先,要了解一下乙個程式的生成的四個步驟 預處理 編譯 彙編 鏈結。經過這四步,最終才生成可執行檔案bin檔案。預處理主要是巨集定義的展開,編譯主要是進行語法此分析,如我們常見的語法錯誤,某些函式未定...