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