S5PV210裸机之重定位
1、重定位相關(guān)概念
位置無(wú)關(guān)碼(PIC,position independent code):匯編源文件被編譯成二進(jìn)制可執(zhí)行文件時(shí)編碼方式與位置(內(nèi)存地址)無(wú)關(guān)。?
位置有關(guān)碼:匯編源文件被編譯成二進(jìn)制可執(zhí)行程序后和位置(內(nèi)存地址)有關(guān)。?
鏈接地址:鏈接時(shí)指定的地址(指定方式:Makefile中用-Ttext,或者鏈接腳本)?
運(yùn)行地址:程序?qū)嶋H運(yùn)行的地址(指定方式:由實(shí)際運(yùn)行時(shí)被加載到內(nèi)存的哪個(gè)位置說(shuō)了算)
鏈接地址是由程序員在編譯鏈接的過(guò)程中,通過(guò)Makefile中-Ttext xxx或者在鏈接腳本中指定的。程序員事先會(huì)預(yù)知自己的程序的執(zhí)行要求,并且有一個(gè)期望的執(zhí)行地址,并且會(huì)用這個(gè)地址來(lái)做鏈接地址。?
運(yùn)行地址是由運(yùn)行時(shí)決定的,編譯鏈接時(shí)是無(wú)法絕對(duì)確定運(yùn)行時(shí)地址的。
2、S5PV210的啟動(dòng)方式和uboot的啟動(dòng)方式的區(qū)別
三星推薦的啟動(dòng)方式:Bootloader必須小于96KB并大于16KB(BL1),假定Bootloader為80KB,啟動(dòng)過(guò)程為:?
a、先開(kāi)機(jī)上電后BL0運(yùn)行,BL0會(huì)加載外部啟動(dòng)設(shè)備中的Bootloader的前16KB(BL1)到SRAM中去運(yùn)行,BL1運(yùn)行時(shí)會(huì)加載BL2(80-16=64KB)到SRAM中去運(yùn)行?
b、BL2運(yùn)行時(shí)會(huì)初始化DDR并且將整個(gè)OS搬運(yùn)到DDR去執(zhí)行
uboot實(shí)際使用方式:uboot大小隨意,假定200KB。啟動(dòng)過(guò)程為:?
a、先開(kāi)機(jī)上電后BL0運(yùn)行,BL0會(huì)加載外部啟動(dòng)設(shè)備中的uboot的前16KB(BL1)到SRAM中去運(yùn)行,BL1運(yùn)行時(shí)會(huì)初始化DDR,然后將整個(gè)uboot搬運(yùn)到DDR中(重定位),然后用一句長(zhǎng)跳轉(zhuǎn)(從SRAM跳轉(zhuǎn)到DDR)指令從SRAM中直接跳轉(zhuǎn)到DDR中繼續(xù)執(zhí)行uboot,直到uboot完全啟動(dòng)。?
b、uboot啟動(dòng)后在uboot命令行中去啟動(dòng)OS。
分散加載:把uboot分成2部分(BL1和uboot),兩部分分別指定不同的鏈接地址。啟動(dòng)時(shí)將兩部分加載到不同的地址(BL1加載到SRAM中,整個(gè)uboot加載到DDR中),這時(shí)候不用重定位也能啟動(dòng)。
3、從源碼到可執(zhí)行程序的步驟:預(yù)編譯、編譯、鏈接、strip
預(yù)編譯: 預(yù)編譯器執(zhí)行。譬如C中的宏定義就是由預(yù)編譯器處理,注釋等也是由預(yù)編譯器處理的。?
編譯: 編譯器來(lái)執(zhí)行。把源碼.c .s編程機(jī)器碼.o文件。?
鏈接: 鏈接器來(lái)執(zhí)行。把.0文件中的各函數(shù)(段)按照一定規(guī)則(鏈接腳本來(lái)指定)累積在一塊。形成可執(zhí)行文件。?
strip: 把可執(zhí)行程序中的符號(hào)信息給拿掉,以節(jié)省空間。(Debug版本和Release版本)?
objcopy: 由可執(zhí)行程序生產(chǎn)科燒錄的鏡像.bin文件。
4、程序段的概念:代碼段、數(shù)據(jù)段、bss段(ZI段)、自定義段
段就是程序的一部分,我們把整個(gè)程序的所有東西分成一個(gè)一個(gè)的段,給每個(gè)段起個(gè)名字,然后在鏈接時(shí)就可以用整個(gè)名字來(lái)指示這些段或者代表這些段。其本質(zhì)就是段命名是為了在鏈接腳本中用段名來(lái)讓段站在核實(shí)的位置。?
段名分兩種:一種是編譯器鏈接器內(nèi)部定好的,先天性名字;一種是程序員自己指定的、自定義的名字。?
先天性名字:?
代碼段(.text): 又叫文本段,代碼段其實(shí)就是函數(shù)編譯后的文件?
數(shù)據(jù)段(.data):C語(yǔ)音中有顯示初始化為非0的全局變量。如 int a = 0; 函數(shù)外聲明定義?
bss段(.bss): 又叫ZI段(zero initial),就是零初始化段,對(duì)應(yīng)C語(yǔ)言中全為0的全局變量。?
如 int a; 函數(shù)外聲明定義?
后天性名字:段名由程序員自定義,段的屬性和特征也是程序員自定義
5、鏈接腳本
鏈接腳本其實(shí)是個(gè)規(guī)則文件,它是由程序員用來(lái)指揮鏈接器工作的。鏈接器會(huì)參考鏈接腳本,兵器使用其中規(guī)定的規(guī)則來(lái)處理.o文件中的那些段,將其連接處一個(gè)可執(zhí)行程序。?
鏈接腳本的關(guān)鍵內(nèi)容有兩部分組成:段名+地址(作為鏈接地址的內(nèi)存)?
鏈接腳本的理解:?
SECTIONS { } 這個(gè)是真?zhèn)€鏈接腳本?
. 點(diǎn)好在鏈接腳本中代表當(dāng)前位置。?
= 代表賦值?
如下腳本分析:
上面的腳本中 . = 0xd0024000; 代表當(dāng)前的內(nèi)存地址是0xd0024000?
.text?和?.data?和?.bss代表段名?
start.o 在段的內(nèi)容中排在了第一個(gè)位置,第一執(zhí)行?
*(.text)?中的 * 代表萬(wàn)能匹配符 ,?(.text)代表是屬性是文本段或代碼段,所以這句話的意思就是所有的文本段或代碼段,一下類同。?
bss_start = .;?這句話中說(shuō)明 bss_start 的地址是當(dāng)前的內(nèi)存地址,注意此時(shí)的當(dāng)前地址并不是0xd0024000,而是 0xd0024000 + .text的內(nèi)存長(zhǎng)度 + .data的內(nèi)存長(zhǎng)度。長(zhǎng)度是按照從上到下順序遞增來(lái)算的。?
bss_end = .;?意思和bss_start = .;?相同,只不過(guò)此時(shí) bss_end的內(nèi)存地址應(yīng)該等于 bss_start的內(nèi)存地址 + .bss段的內(nèi)存長(zhǎng)度。?
這個(gè)鏈接腳本中就規(guī)定了一些程序段的分配原則。
注意,在使用鏈接腳本時(shí)需要修改Makefile中的編譯規(guī)則。?
需要把?
arm-linux-ld -Ttext 0x0 -o led.elf $^?
改為?
arm-linux-ld -Tlink.lds -o led.elf $^?
其中的link.lds就是鏈接腳本。
6、重定位相關(guān)指令基本概念
長(zhǎng)跳轉(zhuǎn):這句代碼是一句跳轉(zhuǎn)指令(ARM中的跳轉(zhuǎn)指令類似于分支指令B、BL等作用的指令),跳轉(zhuǎn)指令通過(guò)給pc(r15) 賦一個(gè)新值來(lái)完成代碼段的跳轉(zhuǎn)執(zhí)行。?
比如:ldr pc, =led_blink // ldr指令實(shí)現(xiàn)長(zhǎng)跳轉(zhuǎn)?
這句長(zhǎng)跳轉(zhuǎn)直接從當(dāng)前地址0xd0020010處所對(duì)應(yīng)的運(yùn)行時(shí)地址跳轉(zhuǎn)到鏈接地址0xd0024000開(kāi)頭的那一份代碼中的led_blink函數(shù)去執(zhí)行。?
其實(shí)在重定位過(guò)程中,在SRAM中有兩份一模一樣的代碼,分別在0xd0020010地址和0xd0024000地址,兩份代碼一模一樣,區(qū)別就在放在不同的內(nèi)存地址中都可以執(zhí)行。?
短跳轉(zhuǎn):這句跳轉(zhuǎn)指令還是停留在當(dāng)前運(yùn)行時(shí)地址內(nèi)跳轉(zhuǎn),而長(zhǎng)跳轉(zhuǎn)則是跳轉(zhuǎn)到鏈接地址內(nèi)運(yùn)行。?
總結(jié):重定位實(shí)際就是在運(yùn)行地址處執(zhí)行一段位置無(wú)關(guān)碼(PIC) ,讓這段PIC(重定位碼)從運(yùn)行時(shí)地址處把整個(gè)程序拷貝一份到鏈接地址處,完了之后使用一句長(zhǎng)跳轉(zhuǎn)指令從運(yùn)行時(shí)地址直接跳轉(zhuǎn)到鏈接地址處執(zhí)行同一個(gè)函數(shù),這樣就實(shí)現(xiàn)了重定位的無(wú)縫對(duì)接。?
adr與ldr偽指令的區(qū)別?
adr和ldr都是偽指令,區(qū)別是adr是短加載,ldr是長(zhǎng)加載。?
重點(diǎn):adr指令加載符號(hào)地址,加載的是運(yùn)行時(shí)地址;ldr指令加載符號(hào)地址,加載的是連接地址
7、重定位代碼分析
a、Makefile文件代碼
led.bin: start.o led.oarm-linux-ld -Tlink.lds -o led.elf $^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其中?
arm-linux-gcc -o $@ $< -c -nostdlib?用來(lái)編譯源文件為目標(biāo)文件?
arm-linux-ld -Tlink.lds -o led.elf $^?用來(lái)編譯鏈接文件到二進(jìn)制文件led.elf?
arm-linux-objcopy -O binary led.elf led.bin?用來(lái)編譯二進(jìn)制文件為可燒錄的鏡像文件led.bin?
arm-linux-objdump -D led.elf > led_elf.dis?用來(lái)編譯二進(jìn)制文件led.elf為反編譯文件led_elf.dis?
gcc mkv210_image.c -o mkx210?用來(lái)編譯源文件為可執(zhí)行的文件?
./mkx210 led.bin 210.bin?用來(lái)把led.bin添加16Bytes的校驗(yàn)和,放在第三個(gè)字節(jié)中。
b、鏈接腳本文件代碼
SECTIONS {. = 0xd0024000;.text : {start.o* (.text)}.data : {* (.data)}bss_start = .; .bss : {* (.bss)}bss_end = .; }解釋如上:5、鏈接腳本
c、啟動(dòng)代碼
/** 文件名: led.s * 作者: 朱老師* 描述: 演示重定位(在SRAM內(nèi)部重定位)*/#define WTCON 0xE2700000#define SVC_STACK 0xd0037d80// 把_start鏈接屬性改為外部,這樣其他文件就可以看見(jiàn)_start了 .global _start _start:// 第1步:關(guān)看門狗(向WTCON的bit5寫入0即可)ldr r0, =WTCONldr r1, =0x0str r1, [r0]// 第2步:設(shè)置SVC棧ldr sp, =SVC_STACK// 第3步:開(kāi)/關(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 開(kāi)icachemcr p15,0,r0,c1,c0,0;// 第4步:重定位// adr指令用于加載_start當(dāng)前運(yùn)行地址// adr加載時(shí)就叫短加載 adr r0, _start // ldr指令用于加載_start的鏈接地址:0xd0024000// ldr加載時(shí)如果目標(biāo)寄存器是pc就叫長(zhǎng)跳轉(zhuǎn),如果目標(biāo)寄存器是r1等就叫長(zhǎng)加載 ldr r1, =_start// bss段的起始地址// 就是我們重定位代碼的結(jié)束地址,重定位只需重定位代碼段和數(shù)據(jù)段即可ldr r2, =bss_start // 比較_start的運(yùn)行時(shí)地址和鏈接地址是否相等,// 如果相等說(shuō)明不需要重定位,所以跳過(guò)copy_loop,直接到clean_bss// 如果不相等說(shuō)明需要重定位,那么直接執(zhí)行下面的copy_loop進(jìn)行重定位// 重定位完成后繼續(xù)執(zhí)行clean_bss。cmp r0, r1 beq clean_bss // 用匯編來(lái)實(shí)現(xiàn)的一個(gè)while循環(huán) copy_loop:ldr r3, [r0], #4 // 源str 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,說(shuō)明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: // 長(zhǎng)跳轉(zhuǎn)到led_blink開(kāi)始第二階段ldr pc, =led_blink // ldr指令實(shí)現(xiàn)長(zhǎng)跳轉(zhuǎn)// 從這里之后就可以開(kāi)始調(diào)用C程序了//bl led_blink // bl指令實(shí)現(xiàn)短跳轉(zhuǎn)// 匯編最后的這個(gè)死循環(huán)不能丟b .說(shuō)明:清楚bss段是為了滿足C語(yǔ)音的運(yùn)行時(shí)要求(C語(yǔ)音要求顯示初始化為0的全局變量,或者未顯示初始化的全局變量的值為0,實(shí)際上C語(yǔ)音編輯器就是通過(guò)請(qǐng)bss段來(lái)實(shí)現(xiàn)C語(yǔ)音的這個(gè)特性的)。一般情況下我們的程序是不需要負(fù)責(zé)清零bss段的(C語(yǔ)音編譯器和鏈接器會(huì)幫我們自動(dòng)添加一段頭程序,這段程序會(huì)在我們的main函數(shù)之前運(yùn)行,負(fù)責(zé)清除bss段)。但是我們重定位之后,因?yàn)榫幾g器幫我們添加的那段清零的bss段是在運(yùn)行時(shí)地址,而并不是在鏈接地址的bss段,所以重定位之后需要我們手動(dòng)自己去清除鏈接地址的那段bss段。
總結(jié)
以上是生活随笔為你收集整理的S5PV210裸机之重定位的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 从1维到6维,一文读懂多维数据可视化策略
- 下一篇: STM32之FSMC-SRAM/NOR原