鏈結指令碼與重定位

2021-09-27 12:01:28 字數 4231 閱讀 7829

一、位置有關**和位置無關**

以前,我們編寫程式的時候,根本不知道還有位置有關**和位置無關**,不知道**的執行居然和**的鏈結位址有關,當然也不知道鏈結位址是什麼,但是在linux的學習中,這些都是必須的。

舉個例子:我們在設計乙個程式的時候,就必須給這個程式指定乙個執行位址(這個指定的執行位址就是鏈結位址),這個位址是我們事先認為程式將來應該執行在哪個位址上。而且編譯器和鏈結器指定這個鏈結位址。最後我們編譯程式得到可執行的程式,理論上這個可執行的程式的執行位址(執行位址)和我們編寫程式的時候指定的那哥哥鏈結位址應該是一樣的才行,不然程式就無法執行(這個就是位置有關**)。但是個別指令的程式的執行位址和鏈結位址可以不同,也可以正確執行**,這就是位置無關指令。

二、鏈結位址的指定

1、在makefile中,編譯的時候通過-ttext ***命令直接指定鏈結位址***。

4、鏈結指令碼簡單介紹

介紹鏈結指令碼之前,介紹一下程式段的概念

**段(.text段):又叫文字段,函式編譯後生成的**

資料段(.data):c語言中初始化為非0的全域性變數。

bss段(.bss):c語言中初始化為0的全域性變數,或者是未初始化的全域性變數(預設為0).

自定義段:程式設計師自己定義的段,關於段的名字、屬性、特徵,都是自定義的。

sections

.data :

bss_start = .; #給變數賦值bss_start賦值為當前的位址

.bss :

bss_end = .;

}

鏈結指令碼的作用:鏈結指令碼是乙個規則檔案,用來指導聯結器的鏈結工作,聯結器會參考鏈結指令碼,並且使用其中規定的規則來處理.o檔案的那些段,將其鏈結成可執行檔案。

三、簡單分析乙個c語言程式的一生

1、預編譯:預編譯器執行了一些巨集定義的解析,標頭檔案的包含問題,以及注釋的問題。

2、編譯:編譯器把.c、.s檔案編譯程機器碼(.o檔案),但是編譯後的**沒有任何的關聯性,僅僅是把各個函式,編譯成單獨的模組,乙個函式乙個.o檔案。

四、簡單概述重定位

1、簡單來說,重定位就是將**從將**從記憶體的某個地方,複製到記憶體的另乙個位址處,然後跳轉到複製的那乙個記憶體位址處去執行的**。

2、相關的**實踐

下面我們寫**來實現重定位,主要的就是start.s檔案、makefile檔案,和鏈結指令碼。

start.s

/*

* 檔名: led.s

* 描述: 演示重定位(在sram內部重定位)

*/#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載入時就叫短載入

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:

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、首先就是判斷執行位址和鏈結位址是否相同?

通過這兩個指令,得到程式的執行位址和鏈結位址,從來判斷程式是否需要進行重定位。

2、重定位**

3、清bss段

我們指導bss段都是為0的全域性變數,這裡我們不再使用複製命令,來實現這段內容的複製,然是通過相應的指令**,將這段記憶體位址所對應的內容清零即可。

4、長跳轉指令

ldr pc, =led_blink                // ldr指令實現長跳轉

最後使用長跳轉指令,實現從位址0xd0020010處的**,跳轉到位址為0xd002400處的led_blink函式執行程式。

sections

.data :

bss_start = .;

.bss :

bss_end = .;

}

makefile檔案:

led.bin: start.o led.o

arm-linux-ld -tlink.lds -o led.elf $^ #鏈結方式的指定,通過鏈結指令碼

arm-linux-objcopy -o binary led.elf led.bin

arm-linux-objdump -d led.elf > led_elf.dis

gcc mkv210_image.c -o mkx210

./mkx210 led.bin 210.bin

%.o : %.s

arm-linux-gcc -o $@ $< -c -nostdlib

%.o : %.c

arm-linux-gcc -o $@ $< -c -nostdlib

clean:

rm *.o *.elf *.bin *.dis mkx210 -f

這樣就實現了重定位。

五、s5pv210的開啟啟動方式

uboot中怎麼實現s5pv210開機啟動,首先bl0執行,進行相關的的初始化,然後載入bl1到sram在中執行,在bl1中會進行記憶體等相關初始化,然後將整個uboot重定位到記憶體的0x33e0000處,然後跳轉到記憶體中的相應位置,執行第二階段的**。

重定位和鏈結指令碼

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

重定位和鏈結指令碼

但是也有一種特別的指令他可以跟指定的鏈結位址沒有關係,這些 不管放在 都可以正常執行。分析 在linux中的應用程式 gcc hello.c o hello bootloader必須大於16kb小於96kb,假定為80kb。啟動過程如下 開機上電後bl0執行,bl0載入外部啟動裝置中的bootloa...

重定位 與 鏈結

動態重定向 現代技術機基本都用這種技術。裝入程式把裝入模組裝入記憶體後,並不會立即把邏輯位址轉換為實體地址,而是把位址轉換推遲到程式真正執行時才發生。這種方式需要乙個重定位暫存器的支援。並且支援換入換出 每次位址會不同 靜態鏈結 將幾個目標模組鏈結裝配成乙個裝入模組時,即將每個模組中所用的外部呼叫符...