嵌入式 ARM 學習總結(4) 重定位

2021-10-09 02:29:26 字數 4041 閱讀 3093

二、鏈結指令碼

一般學習乙個知識點,需要知道是什麼,在什麼情況下用,怎麼用。這三步走,即可把乙個知識點吃透。

那重定位是什麼呢?

1.重定位是什麼

重定位實際上就是在執行一段位置無關碼pic,讓這段pic(也就是重定位**)從執行位址處把整個程式映象拷貝乙份到鏈結位址處,完了之後使用一句長跳轉指令從執行位址處,直接跳轉到鏈結位址去執行同乙個函式,這樣就實現了重定位之後的無縫連線。

這個裡面出現乙個名詞位置無關碼,這是什麼呢?

2.位置無關編碼與位置有關編碼

位置無關編碼(pic,postition independent code):迴避原始檔被編碼成二進位制可執行程式時編碼方式與位置(記憶體位址)無關。

**位置有關編碼:**彙編原始碼編碼成二進位制可執行程式後和記憶體位址是有關的。

對比:位置無關**要好一些,適應性強,放在**都能正常執行;位置有關**就必須執行在鏈結時指定的位址上,適應性差。位置無關碼有一些限制,不能完成所有功能,有時候不得不使用位置有關**。

3.為什麼需要重定位呢

因為對於有些時候,鏈結位址和執行位址必須是不同位址。而且還不能全部使用位置無關**,所以需要使用重定位來實現分散載入。(這裡的分散載入相當於一種手工的重定位)

4.鏈結位址和執行位址

鏈結位址:鏈結時指定的位址(指定方式:makefile中用-ttext,或者鏈結指令碼)

執行位址:程式實際執行時的位址(指定方式:由實際執行時被載入到記憶體的哪個位置說了算)

5.重定位的使用

長跳轉和短跳轉的區別

當鏈結位址和執行位址相同時,短跳轉和長跳轉實際效果是一樣的;但是當鏈結位址不等於執行位址時,短跳轉和長跳轉就有差異了。這時候短跳轉實際執行的是執行位址處的那乙份,而長跳轉執行的是鏈結位址處那乙份。

6.例子:

檔案

#define wtcon 0xe2700000

#define svc_stack 0xd0037d80

.global _start // 把_start鏈結屬性改為外部,這樣其他檔案就可以看見_start了

_start:

// 第1步:關看門狗(向wtcon的bit5寫入0即可)

ldr r0,

=wtcon

ldr r1,

=0x0

str r1,

[r0]

// 第2步:設定svc棧

ldr sp,

=svc_stack

// 第3步:開/關icache

mrc p15,

0,r0,c1,c0,0;

// 讀出cp15的c1到r0中

//bic r0, r0, #(1<<12) // bit12 置0 關icache

orr r0, r0, #(

1<<12)

// bit12 置1 開icache

mcr p15,

0,r0,c1,c0,0;

// 第4步:重定位

// adr指令用於載入_start當前執行位址

adr r0, _start // adr載入時就叫短載入(整個匯程式設計序的開始為_start)

ldr 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:

//從執行位址[r0]讀取內容寫到r3中,再把r3中的內容寫到鏈結位址[r1]中

ldr r3,

[r0]

, #4

// 源

str r3,

[r1]

, #4

// 目的 這兩句**就完成了4個位元組內容的拷貝

cmp r1, r2 // r1和r2都是用ldr載入的,都是鏈結位址,所以r1不斷+4總能等於r2

bne copy_loop

// 清bss段,其實就是在鏈結位址處把bss段全部清零

clean_bss:

ldr r0,

=bss_start

ldr r1,

=bss_end

cmp 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 + 4

bne clear_loop

run_on_dram:

// 長跳轉到led_blink開始第二階段

ldr pc,

=led_blink // ldr指令實現長跳轉(到這裡就是位置有關碼了)

// 從這裡之後就可以開始呼叫c程式了

//bl led_blink // bl指令實現短跳轉

// 彙編最後的這個死迴圈不能丟

b .

(1)重定位:重定位就是彙編**中的copy_loop函式,**的作用是使用迴圈結構來逐句複製**到鏈結位址。複製的源位址是sram的0xd0020010,複製目標位址是sram的0xd0024000,複製長度是bss_start減去_start。複製的長度就是整個重定位需要重定位的長度,也就是整個程式中**段+資料段的長度。bss段(bss段中就是0初始化的全域性變數)不需要重定位。

(2)清bss段:清除bss段是為了滿足c語言的執行時要求(c語言要求顯式初始化為0的全域性變數,或者未顯式初始化的全域性變數的值為0,實際上c語言編譯器就是通過清bss段來實現c語言的這個特性的)。一般情況下我們的程式是不需要負責清零bss段的(c語言編譯器和鏈結器會幫我們的程式自動新增一段頭程式,這段程式會在我們的main函式之前執行,這段**就負責清除bss)。但是在我們**重定位了之後,因為編譯器幫我們附加的**只是幫我們清除了執行位址那乙份**中的bss,而未清除重定位位址處開頭的那乙份**的bss,所以重定位之後需要自己去清除bss。

(3)長跳**清理完bss段後重定位就結束了。然後當前的狀況是:1、當前執行位址還在0xd0020010開頭的(重定位前的)那乙份**中執行著。2、此時sram中已經有了2份**,1份在d0020010開頭,另乙份在d0024000開頭的位置。最後通過長跳轉ldr pc, =led_blink 跳轉到重定位後的led_blink函式(也就是以d0024000位址開頭的**中的led_blink函式)。

1.鏈結指令碼

2.原始碼到可執行程式的步驟

3.鏈結指令碼要做什麼

鏈結指令碼其實是個規則檔案,他是程式設計師用來指揮鏈結器工作的。鏈結器會參考鏈結指令碼,並且使用其中規定的規則來處理.o檔案中那些段,將其鏈結成乙個可執行程式。

鏈結指令碼的關鍵內容有2部分:段名 + 位址(作為鏈結位址的記憶體位址)

檔案

sections

//這一段是資料段

.data :

//到這裡的位址x為dx0024000+**段的大小+資料段的大小

bss_start =.;

//彙編中的標號,這裡的bss_start可以在彙編中使用,表示bss段開始的位址x

//這一段是bss段

.bss :

bss_end =.;

}

嵌入式arm開發學習

今天是學習arm的第二天.感覺又懂了一點.記錄下來,以便以後不時之需.今天先是看了一些linux應用開發的東西,由於之前自己對linux程式設計還算是有過了解,這部分看得很快,基本沒有什麼閱讀障礙.個人建議想學嵌入式linux的人,最好先把unix環境高階程式設計看一遍.你會發現一切都會順其自然.之...

嵌入式 ARM 學習總結(11) ADC

二 adc的相關概念 三 s5pv210的adc adc analog digital converter,ad轉換,模數轉換 也就是模擬轉數字 模擬 就是連續的,現實生活當中的時間 電壓 高度等都是模擬的 連續分布的,劃分的話可以無限的更小劃分 模擬量反映在數學裡面就是無限小數字 從0到1之間有無...

嵌入式設計總結4

1 gnu計畫的工具 核心除錯kgdb 2 程式的錯誤分類 3 gdb的功能 1 工程管理器 如何編寫makefile檔案?編譯很多個檔案 型別不同 存放不再不同目錄 資源檔案使用 組成的軟體 結構 實現自動編譯 根據makefile檔案的內容進行 自動編譯 1 根據makefile檔案的內容進行自...