程式碼重定位
一. 為什麼需要程式碼重定位
1.1. 連結地址和執行地址不同
1.1.1. 只有位置無關編碼才能在執行地址和連結地址不同的情況下執行。
a. 位置無關編碼(PIC,position independent code):彙編原始檔被編碼成二進位制可執行程式時編碼方式與位置(記憶體地址)無關。
b. 位置有關編碼:彙編原始碼編碼成二進位制可執行程式後和記憶體地址是有關的。
c. 大部分指令是位置有關編碼
1.1.2. 連結地址
a. 連結時指定的地址(指定方式為:Makefile中用-Ttext,或者連結指令碼)
1.1.3. 執行地址
a. 程式實際執行時地址(指定方式:由實際執行時被載入到記憶體的哪個位置說了算)
1.2. 分析uboot載入
1.2.1. uboot載入流程
a. 先開機上電後BL0執行(s5pv210 IROM韌體程式)
b. BL0會載入外部啟動裝置中的uboot的前16KB(BL1)到SRAM中去執行,
c. BL1執行時會初始化DDR,然後將整個uboot搬運到DDR中,然後用一句長跳轉(從SRAM跳轉到DDR)指令從SRAM中直接跳轉到DDR中繼續執行uboot直到uboot完全啟動。uboot啟動後在uboot命令列中去啟動OS。
1.2.2. 分析載入過程
1.2.2.1. 我們分析uboot既要在iram中執行也要在ddr中執行。故uboot的連結地址只有一個,而其執行地址有兩個。
1.2.2.2. 解決辦法一:
a. uboot連結地址設定到ddr中,
b. uboot在執行 位置有關編碼前完成程式的重定位
c. 我使用的uboot採用該方法
1.2.2.3. 解決方法二(分散載入):
a. 把uboot分成2部分(BL1和整個uboot),兩部分分別指定不同的連結地址。啟動時將兩部分載入到不同的地址(BL1載入到SRAM,整個uboot載入到DDR),這時候不用重定位也能啟動。
二. 編寫測試重定位程式碼
2.1. 程式碼說明
a. 在SRAM中將程式碼從0xd0020010重定位到0xd0024000
b. 此程式碼並沒有重定位到ddr中,我們僅測試,重定位到哪都可以提供測試
c. 本來程式碼是執行在0xd0020010的,但是因為一些原因我們又希望程式碼實際是在0xd0024000位置執行的。這時候就需要重定位了
2.2. 測試步驟
2.2.1. 通過連結指令碼將程式碼連結到0xd0024000
2.2.2. dnw下載時將bin檔案下載到0xd0020010
2.2.3. 程式碼執行時通過程式碼前段的少量位置無關碼將整個程式碼搬移到0xd0024000
2.2.3. 使用一個長跳轉跳轉到0xd0024000處的程式碼繼續執行,重定位完成
三. 程式碼分析
3.1. 連結檔案
a. 設定程式碼連結到0xd0024000
b. bss_start記錄bss段起始地址
c. bss_end記錄程式碼結束地址
SECTIONS { . = 0xd0024000; .text : { start.o * (.text) } .data : { * (.data) } bss_start = .; .bss : { * (.bss) } bss_end = .; }View Code
3.2. 重定位程式碼分析
3.2.1. adr與ldr偽指令的區別
3.2.1.1. adr指令
a. adr是短載入,將執行地址載入到暫存器
b. 分析反彙編程式碼分析該指令
PS: 反編譯出來的地址都是連結地址,其實此時執行時_srart的地址是0xd0020010,根據偏移計算,r0= 0xd0020010 + 0x00000000
3.2.1.1. ldr指令
a. ldr是長載入,將連結設定的地址載入到暫存器
b. 分析反彙編程式碼分析該指令
PS: 反編譯出來的地址都是連結地址,其實此時執行時_srart的地址是0xd0020010,但此時r1值是從記憶體池中直接得到,r1 = 0xd0024000
3.2.2. 真正實現重定位彙編
a. 通過迴圈完成程式的遷移
// 用匯編來實現的一個while迴圈 copy_loop: ldr r3, [r0], #4 // 源 str r3, [r1], #4 // 目的 這兩句程式碼就完成了4個位元組內容的拷貝 cmp r1, r2 // r1和r2都是用ldr載入的,都是連結地址,所以r1不斷+4總能等於r2 bne copy_loopView Code
3.2.3. 清bss段
a. bss_start和bss_end在連結指令碼設定了,彙編中可以直接使用
b. 由於bss段都是0故重定位時不用copy bss段
3.2.4. 長跳轉
a. ldr pc, =led_blink// ldr指令實現長跳轉,跳轉到連結地址
b. 分析反彙編程式碼分析該指令
c. bl led_blink// bl指令實現短跳轉,跳轉到執行地址下相對應的led_blink地址處
// 清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_loopView Code
/* * 檔名: 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指令用於載入_start的連結地址:0xd0024000 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 .View Code
/* * 檔名:led.s * 作者:朱老師 * 描述:演示重定位(在SRAM內部重定位) */
#define WTCON0xE2700000
#define SVC_STACK0xd0037d80
.global _start// 把_start連結屬性改為外部,這樣其他檔案就可以看見_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 r0, _start // adr載入時就叫短載入// ldr指令用於載入_start的連結地址:0xd0024000ldr 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總能等於r2bne copy_loop
// 清bss段,其實就是在連結地址處把bss段全部清零clean_bss:ldr r0, =bss_startldr r1, =bss_endcmp r0, r1// 如果r0等於r1,說明bss段為空,直接下去beq run_on_dram// 清除bss完之後的地址mov r2, #0clear_loop:str r2, [r0], #4// 先將r2中的值放入r0所指向的記憶體地址(r0中的值作為記憶體地址),cmp r0, r1// 然後r0 = r0 + 4bne clear_loop
run_on_dram:// 長跳轉到led_blink開始第二階段ldr pc, =led_blink// ldr指令實現長跳轉// 從這裡之後就可以開始呼叫C程式了//bl led_blink// bl指令實現短跳轉// 彙編最後的這個死迴圈不能丟b .