重定位與鏈接腳本

1.為什么需要重定位  

  位置無關編碼(PIC,position independent code):匯編源文件被編碼成二進制可執行程序時編碼方式與位置(內存地址)無關。
  位置有關編碼:匯編源碼編碼成二進制可執行程序后和內存地址是有關的。

  我們在設計一個程序時,會給這個程序指定一個運行地址(鏈接地址)。就是說我們在編譯程序時其實心里是知道我們程序將來被運行時的地址(運行地址)的,而且必須給編譯器鏈接器指定這個地址(鏈接地址)才行。最后得到的二進制程序理論上是和你指定的運行地址有關的,將來這個程序被執行時必須放在當時編譯鏈接時給定的那個地址(鏈接地址)下才行,否則不能運行(就叫位置有關代碼)。但是有個別特別的指令他可以跟指定的地址(鏈接地址)沒有關系,也就是說這些代碼實際運行時不管放在哪里都能正常運行。

  對于位置有關代碼來說:最終執行時的運行地址和編譯鏈接時給定的鏈接地址必須相同,否則一定出錯。
  之前的裸機程序中,Makefile中用 -Ttext 0x0 來指定鏈接地址是0x0。這意味著我們認為這個程序將來會放在0x0這個內存地址去運行。
  但是實際上我們運行時的地址是0xd0020010(我們用dnw下載時指定的下載地址)。這兩個地址看似不同,但是實際相同。這是因為S5PV210內部做了映射,把SRAM映射到了0x0地址去。

  分清楚這兩個概念:
  鏈接地址:鏈接時指定的地址(指定方式為:Makefile中用-Ttext,或者鏈接腳本)
  運行地址:程序實際運行時地址(指定方式:由實際運行時被加載到內存的哪個位置說了算)

2.再解S5PV210的啟動過程:三星推薦和uboot的實現是不同的

  三星推薦的啟動方式中:bootloader必須小于96KB并大于16KB,假定bootloader為80KB,啟動過程是這樣子:先開機上電后BL0運行,BL0會加載外部啟動設備中的bootloader的前16KB(BL1)到SRAM中去運行,BL1運行時會加載BL2(bootloader中80-16=64KB)到SRAM中(從SRAM的16KB處開始用)去運行;BL2運行時會初始化DDR并且將OS搬運到DDR去執行OS,啟動完成。
  uboot實際使用的方式:uboot大小隨意,假定為200KB。啟動過程是這樣子:先開機上電后BL0運行,BL0會加載外部啟動設備中的uboot的前16KB(BL1)到SRAM中去運行,BL1運行時會初始化DDR,然后將整個uboot搬運到DDR中,然后用一句長跳轉(從SRAM跳轉到DDR)指令從SRAM中直接跳轉到DDR中繼續執行uboot直到uboot完全啟動。uboot啟動后在uboot命令行中去啟動OS。

  鏈接地址和運行地址有時候必須不相同,而且還不能全部用位置無關碼,這時候只能重定位。


  擴展:
  分散加載:把uboot分成2部分(BL1和整個uboot),兩部分分別指定不同的鏈接地址。啟動時將兩部分加載到不同的地址(BL1加載到SRAM,整個uboot加載到DDR),這時候不用重定位也能啟動。
  評價:分散加載其實相當于手工重定位。重定位是用代碼來進行重定位,分散加載是手工操作重定位的。

?

3.對比操作系統下的程序與裸機程序

  linux中的應用程序。gcc hello.c -o hello,這時使用默認的鏈接地址就是0x0,所以應用程序都是鏈接在0地址的。因為應用程序運行在操作系統的一個進程中,在這個進程中這個應用程序獨享4G的虛擬地址空間。所以應用程序都可以鏈接到0地址,因為每個進程都是從0地址開始的。(編譯時可以不給定鏈接地址而都使用0)
  210中的裸機程序。運行地址由我們下載時確定,下載時下載到0xd0020010,所以就從這里開始運行。(這個下載地址也不是我們隨意定的,是iROM中的BL0加載BL1時事先指定好的地址,這是由CPU的設計決定的)。所以理論上我們編譯鏈接時應該將地址指定到0xd0020010,但是實際上我們在之前裸機程序中都是使用位置無關碼PIC,所以鏈接地址可以是0。

4.關于鏈接

  從源碼到可執行程序的步驟:預編譯、編譯、鏈接、strip
  預編譯:預編譯器執行。譬如C中的宏定義就是由預編譯器處理,注釋等也是由預編譯器處理的。
  編譯: 編譯器來執行。把源碼.c .S編程機器碼.o文件。
  鏈接: 鏈接器來執行。把.o文件中的各函數(段)按照一定規則(鏈接腳本來指定)累積在一起,
  形成可執行文件。
  strip: strip是把可執行程序中的符號信息給拿掉,以節省空間。(Debug版本和Release版本)
  objcopy:由可執行程序生成可燒錄的鏡像bin文件。

  程序段的概念:代碼段、數據段、bss段(ZI段)、自定義段
  段就是程序的一部分,我們把整個程序的所有東西分成了一個一個的段,給每個段起個名字,然后在鏈接時就可以用這個名字來指示這些段。也就是說給段命名就是為了在鏈接腳本中用段名來讓段站在核實的位置。

  段名分為2種:一種是編譯器鏈接器內部定好的,先天性的名字;一種是程序員自己指定的、自定義的段名。
先天性段名:
  代碼段:(.text),又叫文本段,代碼段其實就是函數編譯后生成的東西
  數據段:(.data),數據段就是C語言中有顯式初始化為非0的全局變量
  bss段:(.bss),又叫ZI(zero initial)段,就是零初始化段,對應C語言中初始化為0的全局變量。
  后天性段名:

5、鏈接腳本究竟要做什么?
  鏈接腳本其實是個規則文件,他是程序員用來指揮鏈接器工作的。鏈接器會參考鏈接腳本,并且使用其中規定的規則來處理.o文件中那些段,將其鏈接成一個可執行程序。
  鏈接腳本的關鍵內容有2部分:段名 + 地址(作為鏈接地址的內存地址)
  鏈接腳本的理解:
  SECTIONS {} 這個是整個鏈接腳本
  . 點號在鏈接腳本中代表當前位置。
  = 等號代表賦值

?

6.代碼重定位實戰(理論分析)

  任務:在SRAM中將代碼從0xd0020010重定位到0xd0024000
  任務解釋:本來代碼是運行在0xd0020010的,但是因為一些原因我們又希望代碼實際是在0xd0024000位置運行的。這時候就需要重定位了。
  注解:本練習對代碼本身運行無實際意義,我們做這個重定位純粹是為了練習重定位技能。但是某些情況重定位就是必須的,譬如在uboot中。

  思路:
  第一點:通過鏈接腳本將代碼鏈接到0xd0024000
  第二點:dnw下載時將bin文件下載到0xd0020010
  第一點加上第二點,就保證了:代碼實際下載運行在0xd0020010,但是卻被鏈接在0xd0024000。從而為重定位奠定了基礎。
  當我們把代碼鏈接地址設置為0xd0024000時,實際隱含意思就是我這個代碼將來必須放在0xd0024000位置才能正確執行。如果實際運行地址不是這個地址就要出事(除非代碼是PIC位置無關碼),當以上都明白了后,就知道重定位代碼的作用就是:在PIC執行完之前(在代碼中第一句位置有關碼執行之前)必須將整個代碼搬移到0xd0024000位置去執行,這就是重定位。
  第三點:代碼執行時通過代碼前段的少量位置無關碼將整個代碼搬移到0xd0024000
  第四點:使用一個長跳轉跳轉到0xd0024000處的代碼繼續執行,重定位完成

總結:
重定位實際就是在運行地址處執行一段位置無關碼PIC,讓這段PIC(也就是重定位代碼)從運行地址處把整個程序鏡像拷貝一份到鏈接地址處,

完了之后使用一句長跳轉指令從運行地址處直接跳轉到鏈接地址處去執行同一個函數(led_blink),這樣就實現了重定位之后的無縫連接。

?

7.代碼重定位實戰(代碼)

  adr與ldr偽指令的區別
  ldr和adr都是偽指令,區別是ldr是長加載、adr是短加載。
  重點:adr指令加載符號地址,加載的是運行時地址;ldr指令在加載符號地址時,加載的是鏈接地址。

  第一步:重定位(代碼拷貝)
  重定位就是匯編代碼中的copy_loop函數,代碼的作用是使用循環結構來逐句復制代碼到鏈接地址。
  復制的源地址是SRAM的0xd0020010,復制目標地址是SRAM的0xd0024000,復制長度是bss_start減去_start
  所以復制的長度就是整個重定位需要重定位的長度,也就是整個程序中代碼段+數據段的長度。
  bss段(bss段中就是0初始化的全局變量)不需要重定位。
  第二步:清bss段
  清除bss段是為了滿足C語言的運行時要求(C語言要求顯式初始化為0的全局變量,或者未顯式初始化的全局變量的值為0,實際上C語言編譯器就是通過清bss段來實現C語言的這個特性的)。一般情況下我們的程序是不需要負責清零bss段的(C語言編譯器和鏈接器會幫我們的程序自動添加一段頭程序,這段程序會在我們的main函數之前運行,這段代碼就負責清除bss)。但是在我們代碼重定位了之后,因為編譯器幫我們附加的代碼只是幫我們清除了運行地址那一份代碼中的bss,而未清除重定位地址處開頭的那一份代碼的bss,所以重定位之后需要自己去清除bss。
  第三步:長跳轉
  清理完bss段后重定位就結束了。然后當前的狀況是:
  1、當前運行地址還在0xd0020010開頭的(重定位前的)那一份代碼中運行著。
  2、此時SRAM中已經有了2份代碼,1份在d0020010開頭,另一份在d0024000開頭的位置。
  然后就要長跳轉了。

?

/** 文件名:    led.s    * 作者:    朱老師* 描述:    演示重定位(在SRAM內部重定位)*/#define WTCON        0xE2700000#define SVC_STACK    0xd0037d80.global _start                    // 把_start鏈接屬性改為外部,這樣其他文件就可以看見_start了
_start:// 第1步:關看門狗(向WTCON的bit5寫入0即可)ldr r0, =WTCONldr r1, =0x0str r1, [r0]// 第2步:設置SVC棧ldr sp, =SVC_STACK// 第3步:開/關icachemrc p15,0,r0,c1,c0,0;            // 讀出cp15的c1到r0中//bic r0, r0, #(1<<12)            // bit12 置0  關icacheorr r0, r0, #(1<<12)            // bit12 置1  開icachemcr p15,0,r0,c1,c0,0;
    // 第4步:重定位// adr指令用于加載_start當前運行地址adr r0, _start          // adr加載時就叫短加載        // ldr指令用于加載_start的鏈接地址:0xd0024000ldr r1, =_start // ldr加載時如果目標寄存器是pc就叫長跳轉,如果目標寄存器是r1等就叫長加載    // bss段的起始地址ldr r2, =bss_start    // 就是我們重定位代碼的結束地址,重定位只需重定位代碼段和數據段即可cmp r0, r1            // 比較_start的運行時地址和鏈接地址是否相等beq clean_bss        // 如果相等說明不需要重定位,所以跳過copy_loop,直接到clean_bss// 如果不相等說明需要重定位,那么直接執行下面的copy_loop進行重定位// 重定位完成后繼續執行clean_bss。// 用匯編來實現的一個while循環
copy_loop:ldr r3, [r0], #4    // 源 將地址為r0的數據給r3.然后r0=r0+4str r3, [r1], #4    // 目的   這兩句代碼就完成了4個字節內容的拷貝cmp r1, r2            // r1和r2都是用ldr加載的,都是鏈接地址,所以r1不斷+4總能等于r2bne copy_loop// 清bss段,其實就是在鏈接地址處把bss段全部清零
clean_bss:ldr r0, =bss_start                    ldr r1, =bss_endcmp r0, r1                // 如果r0等于r1,說明bss段為空,直接下去beq run_on_dram            // 清除bss完之后的地址mov r2, #0
clear_loop:str r2, [r0], #4        // 先將r2中的值放入r0所指向的內存地址(r0中的值作為內存地址),cmp r0, r1                // 然后r0 = r0 + 4bne clear_looprun_on_dram:    // 長跳轉到led_blink開始第二階段ldr pc, =led_blink                // ldr指令實現長跳轉// 從這里之后就可以開始調用C程序了//bl led_blink                    // bl指令實現短跳轉// 匯編最后的這個死循環不能丟b .

?

led_blink在c語言程序里,與上章代碼相同不再展開。
鏈接腳本
link.lds
SECTIONS
{. = 0xd0024000;    //指定鏈接地址
    .text : {start.o* (.text)}.data : {* (.data)}bss_start = .; 
    .bss : {* (.bss)}bss_end  = .;    
}

Makefile

led.bin: start.o led.oarm-linux-ld -Tlink.lds -o led.elf $^      //-T后面跟的為鏈接腳本arm-linux-objcopy -O binary led.elf led.binarm-linux-objdump -D led.elf > led_elf.disgcc mkv210_image.c -o mkx210./mkx210 led.bin 210.bin%.o : %.Sarm-linux-gcc -o $@ $< -c -nostdlib%.o : %.carm-linux-gcc -o $@ $< -c -nostdlibclean:rm *.o *.elf *.bin *.dis mkx210 -f  

?

?

轉載于:https://www.cnblogs.com/PengfeiSong/p/6345615.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/457251.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/457251.shtml
英文地址,請注明出處:http://en.pswp.cn/news/457251.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux bashrc和profile的用途和區別

導讀使用終端ssh登錄Linux操作系統的控制臺后&#xff0c;會出現一個提示符號&#xff08;例如&#xff1a;#或~&#xff09;&#xff0c;在這個提示符號之后可以輸入命令&#xff0c;Linux根據輸入的命令會做回應&#xff0c;這一連串的動作是由一個所謂的Shell來做處理。Shel…

python讀取word文檔結構圖_Word 有什么技巧,讓你相見恨晚?

Word作為日常辦公最常用的軟件之一&#xff0c;其實真沒你想得那么簡單&#xff01;你不知道的每一個技巧&#xff0c;都會讓你相見恨晚&#xff01;每當身邊的小伙伴詢問這些疑難雜癥時&#xff0c;我都會拋出這張圖…真的沒騙你&#xff0c;我們遇到的 99% 的Word難題&#x…

Golang 特性簡介

by sheepbao 主要大概介紹go語言的歷史和特性&#xff0c;簡單的入門。 來歷 很久以前&#xff0c;有一個IT公司&#xff0c;這公司有個傳統&#xff0c;允許員工擁有20%自由時間來開發實驗性項目。在2007的某一天&#xff0c;公司的幾個大牛&#xff0c;正在用c開發一些比較繁…

HTML實體字符轉化為HTML標簽

html_entity_decode方法 參數描述string必需。規定要解碼的字符串。flags 可選。規定如何處理引號以及使用哪種文檔類型。 可用的引號類型&#xff1a; ENT_COMPAT - 默認。僅解碼雙引號。ENT_QUOTES - 解碼雙引號和單引號。ENT_NOQUOTES - 不解碼任何引號。規定所使用文檔類型…

華為2017java筆試題_2017年java華為面試題

2017年java華為面試題通過HCNP認證&#xff0c;將證明您對中小型網絡有全面深入的了解&#xff0c;掌握中小型網絡的通用技術&#xff0c;并具備獨立設計中小型網絡以及使用華為路由交換設備實施設計的能力。下面是小編收集的關于java華為面試題&#xff0c;希望大家認真閱讀!1…

Tomcat 配置詳解/優化方案

Server.xml 【原地址&#xff1a;http://blog.csdn.net/cicada688/article/details/14451541】 Server.xml配置文件用于對整個容器進行相關的配置。 <Server>元素&#xff1a;是整個配置文件的根元素。表示整個Catalina容器。 屬性&#xff1a;className&#xff1a;實現…

MySQL創建數據庫與創建用戶以及授權

1、create schema [數據庫名稱] default character set utf8 collate utf8_general_ci;--創建數據庫 采用create schema和create database創建數據庫的效果一樣。 2、create user [用戶名稱]% identified by [用戶密碼];--創建用戶 密碼8位以上&#xff0c;包括&#xff1a;大寫…

java 防止url重復請求_Web項目如何防止客戶端重復發送請求

在Web項目中&#xff0c;有一些請求或操作會對數據產生影響(比如新增、刪除、更新)&#xff0c;針對這類請求一般都需要做一些保護&#xff0c;以防止用戶有意或無意的重復發起這樣的請求導致的數據錯亂。本文總結了一些防止客戶端重復發送請求的方法。方法一&#xff1a;JS監聽…

【bzoj1010-toy】斜率優化入門模板

dsy1010: [HNOI2008]玩具裝箱 【題目描述】 有n個數&#xff0c;分成連續的若干段&#xff0c;每段&#xff08;假設從第j個到第i個組成一段&#xff09;的分數為 (X-L)^2&#xff0c;X為j-iSigma(Ck) i<k<j&#xff0c;其中L是一個常量。目標&#xff1a;各段分數的總和…

itellyou操作系統,office等軟件的很全的下載站

itellyou操作系統&#xff0c;office等軟件的很全的下載站http://www.itellyou.cn/轉載于:https://blog.51cto.com/wangheyu1/1894724

矩陣的馬鞍點

#include<stdio.h>#define n 4//馬鞍點是第I行值最小第J列值最大 void maxmin(int a[n][n]){ int i,j ,flag; int max[n],min[n]; for(i0;i<n;i) { min[i]a[i][0];//將數組每行的第一個元素賦值給min[]數組 for(j1;j<n;j) { if(a[i][j]<min[i]) min[i]a[i][j];…

Linux運維工程師面試-部分題庫

一、Linux操作系統知識 1.常見的Linux發行版本都有什么&#xff1f;你最擅長哪一個&#xff1f;它的官網網站是什么&#xff1f;說明你擅長哪一塊&#xff1f; 2.Linux開機啟動流程詳細步驟是什么&#xff1f;系統安裝完&#xff0c;忘記密碼如何破解&#xff1f; 3.企業中Linu…

java統計系統線程數_Java并發(八)計算線程池最佳線程數

目錄一、理論分析二、實際應用為了加快程序處理速度&#xff0c;我們會將問題分解成若干個并發執行的任務。并且創建線程池&#xff0c;將任務委派給線程池中的線程&#xff0c;以便使它們可以并發地執行。在高并發的情況下采用線程池&#xff0c;可以有效降低線程創建釋放的時…

php大小寫轉換函數

1.將字符串轉換成小寫 strtolower(): 該函數將傳入的字符串參數所有的字符都轉換成小寫,并以小定形式放回這個字 符串.例: <?php$str "I want To FLY";$str strtolower($str);echo $str; ?>輸出結果: i want to fly 2.將字符轉成大寫 strtoupper(): 該…

關于移動端 1px 像素問題

移動端1px變粗的原因 移動端html的header總會有一句<meta name"viewport" content"widthdevice-width, initial-scale1.0, maximum-scale1.0, user-scalableno">這句話定義了本頁面的viewport的寬度為設備寬度,初始縮放值和最大縮放值都為1,并禁止了…

java框架概念_java概念(2)

java概念(2)重載和重寫重載&#xff1a;同一個類中&#xff0c;方法名相同&#xff0c;參數不同重寫&#xff1a;父子類中&#xff0c;子類重新定義父類的方法多態? 多態&#xff1a;同一種行為&#xff0c;不同的對象有不同的表現形式。? 重載 編譯時根據參數決定調用的方法…

CentOS(八)--crontab命令的使用方法

crontab命令常見于Unix和Linux的操作系統之中&#xff0c;用于設置周期性被執行的指令。該命令從標準輸入設備讀取指令&#xff0c;并將其存放于"crontab"文件中&#xff0c;以供之后讀取和執行。 在Linux系統中&#xff0c;Linux任務調度的工作主要分為以下兩類&…

有健忘癥嗎?

今天興高采烈&#xff0c;早上空氣不錯&#xff0c; 但是騎自行車的我&#xff0c;還是得戴一個面罩。 半個小時后買了早餐&#xff0c; 一份炒粉、一豆漿&#xff0c;今天早上豆漿沒有掉地上&#xff0c; 但是~~~~~~~~~~~~~~shit~~!~!~,居然忘記帶要換的衣服了&#xff0c; …

下載java后綴的文件閃退_關于jarfile 打開閃退問題

后面才發現&#xff0c;原來是因為我把文件拖入了新建的文件夾&#xff0c;改變了路徑&#xff0c;而且我的java環境沒有配置好是全局變量&#xff0c;所以新建文件夾之后&#xff0c;就會出現找不到了路徑&#xff0c;閃退的問題&#xff0c;&#xff0c;&#xff0c;還有就是…