日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

重定位与链接脚本

發(fā)布時(shí)間:2023/12/19 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 重定位与链接脚本 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1.為什么需要重定位  

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

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

  對于位置有關(guān)代碼來說:最終執(zhí)行時(shí)的運(yùn)行地址和編譯鏈接時(shí)給定的鏈接地址必須相同,否則一定出錯。
  之前的裸機(jī)程序中,Makefile中用 -Ttext 0x0 來指定鏈接地址是0x0。這意味著我們認(rèn)為這個(gè)程序?qū)頃旁?x0這個(gè)內(nèi)存地址去運(yùn)行。
  但是實(shí)際上我們運(yùn)行時(shí)的地址是0xd0020010(我們用dnw下載時(shí)指定的下載地址)。這兩個(gè)地址看似不同,但是實(shí)際相同。這是因?yàn)镾5PV210內(nèi)部做了映射,把SRAM映射到了0x0地址去。

  分清楚這兩個(gè)概念:
  鏈接地址:鏈接時(shí)指定的地址(指定方式為:Makefile中用-Ttext,或者鏈接腳本)
  運(yùn)行地址:程序?qū)嶋H運(yùn)行時(shí)地址(指定方式:由實(shí)際運(yùn)行時(shí)被加載到內(nèi)存的哪個(gè)位置說了算)

2.再解S5PV210的啟動過程:三星推薦和uboot的實(shí)現(xiàn)是不同的

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

  鏈接地址和運(yùn)行地址有時(shí)候必須不相同,而且還不能全部用位置無關(guān)碼,這時(shí)候只能重定位。


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

?

3.對比操作系統(tǒng)下的程序與裸機(jī)程序

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

4.關(guān)于鏈接

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

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

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

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

?

6.代碼重定位實(shí)戰(zhàn)(理論分析)

  任務(wù):在SRAM中將代碼從0xd0020010重定位到0xd0024000
  任務(wù)解釋:本來代碼是運(yùn)行在0xd0020010的,但是因?yàn)橐恍┰蛭覀冇窒Ma實(shí)際是在0xd0024000位置運(yùn)行的。這時(shí)候就需要重定位了。
  注解:本練習(xí)對代碼本身運(yùn)行無實(shí)際意義,我們做這個(gè)重定位純粹是為了練習(xí)重定位技能。但是某些情況重定位就是必須的,譬如在uboot中。

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

總結(jié):
重定位實(shí)際就是在運(yùn)行地址處執(zhí)行一段位置無關(guān)碼PIC,讓這段PIC(也就是重定位代碼)從運(yùn)行地址處把整個(gè)程序鏡像拷貝一份到鏈接地址處,

完了之后使用一句長跳轉(zhuǎn)指令從運(yùn)行地址處直接跳轉(zhuǎn)到鏈接地址處去執(zhí)行同一個(gè)函數(shù)(led_blink),這樣就實(shí)現(xiàn)了重定位之后的無縫連接。

?

7.代碼重定位實(shí)戰(zhàn)(代碼)

  adr與ldr偽指令的區(qū)別
  ldr和adr都是偽指令,區(qū)別是ldr是長加載、adr是短加載。
  重點(diǎn):adr指令加載符號地址,加載的是運(yùn)行時(shí)地址;ldr指令在加載符號地址時(shí),加載的是鏈接地址。

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

?

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

?

?

轉(zhuǎn)載于:https://www.cnblogs.com/PengfeiSong/p/6345615.html

總結(jié)

以上是生活随笔為你收集整理的重定位与链接脚本的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。