首先看兩行匯編代碼:
1: adr r0, _start
2: ldr r1, =_start
同樣是加載一個標號的地址值,adr和ldr有什麼區別呢?注意這裡的ldr不是命令ldr,而是偽指令ldr,若想區分它們請參看我的一篇博文《adr adrl ldr mov總結整理》。
要區分它們,就需要引入4個概念:
1、運行時地址起始位置:它芯片公司指定的一開始運行代碼的位置。這個位置和芯片本身有關,不可改動。對於2440來說一般就是片內SRAM的首地址0x0;對於210來說就是片內SRAM中的地址0xD0020010。
2、鏈接地址起始位置:它是由程序員指定的,或者說是有鏈接腳本設定。是可以變動的。但是這個位置在程序鏈接之後,就會確定下來。
3、運行時地址:就在從運行時地址起始位置(包括起始位置)往後排都是運行時地址。
4、鏈接地址:就是從鏈接地址起始位置(包括起始位置)往後排都是鏈接地址。
說明了以上4點內容之後,我需要鋪墊一些前提內容,adr r0, _start ; ldr r1, =_start
這兩句代碼是從朱老師的一個實驗程序裡直接截取出來,這實驗的目的是演示重定位。之後這段代碼我會貼到文章的最後。因為開發板是210的板子所以運行時地址是從0xd0020010開始的,鏈接地址設置為0xd0024000開始的。
整個程序編譯之後,在進行反編譯,我們查找adr r0, _start ; ldr r1, =_start 對應的反匯編內容:
1、adr r0, _start 對應的是: d002401c: e24f0024 sub r0, pc, #36 ; 0x24 2、ldr r1, =_start對應的是: d0024020: e59f1048 ldr r1, [pc, #72] ; d0024070 <run_on_dram+0x10>
同樣是加載_start的地址,反匯編之後卻是截然不同的命令。首先我們需要會看反匯編,最左邊的是鏈接地址,第二個是機器碼,第三個是反匯編得到的內容,最右邊分號之後的是反匯編編譯器額外幫我們注釋了一些內容方便我們閱讀。
我們發現反匯編之後,有一個地方很不同,就是pc指針。ldr r1, =_start對於的反匯編pc指針被放到的了[]裡面,而另一條反匯編沒有。我們知道對於匯編而言,放到[]裡面代表是取得寄存器的值並且將寄存器的值當作地址,來訪問地址中存儲的值。
而對於pc而言,當你直接讀取pc的值時訪問的是運行時地址,而當你讀取[pc]的值時訪問的是鏈接地址。
反觀adr r0, _start 和ldr r1, =_start它們都是偽指令,意思也分別是讀取運行時地址和讀取鏈接地址。和反匯編意義吻合。
我們現在來驗證,我們前面分析的是否正確。首先_start作為程序的最開始,所以_start如果對應運行時地址,那麼讀取的_start的值應該是運行時地址起始位置及0xd0020010。
觀察反匯編及對應的匯編
1、adr r0, _start d002401c: e24f0024 sub r0, pc, #36 ; 0x24
由於此時該句代碼的鏈接地址是d002401c鏈接地址的起始位置設定的是0xd0024000,偏移量是0x1c,根據這個便宜量可以算出該句代碼的運行時地址為0xd0020010 +0x1c = d002002C,前面提到pc的值對應的就是運行時地址所以pc = d002002C。
d002002C - (36 十進制)+ 8 (流水線)= D002 0010 ;正好得到了_start的運行時地址完全沒錯。
再看鏈接地址是否算錯,首先_start作為程序的最開始,所以_start如果對應鏈接地址,那麼讀取的_start的值應該是鏈接地址起始位置及之前設定的0xd0024000。
2、ldr r1, =_start d0024020: e59f1048 ldr r1, [pc, #72] ; d0024070 <run_on_dram+0x10>
根據偏移量,這句的運行時地址是d0020030,如果說是運行時地址 + 偏移量(72十進制),得到的是D002 0078,再加8(流水線)等於D002 0080,顯然不對。
明顯這裡的[pc]的值得到的是當前語句對應的鏈接地址,d0024020 + 偏移量(72十進制)+ 8 才等於D002 4070(這個值也正好是注釋裡的值)大家是不是奇怪,為啥值不是0xd0024000?是不是算錯了?其實不是,你到D002 4070這個鏈接地址看看就會發現這裡存放的值正好就是D002 4070。
代碼如下:d0024070: d0024000 andle r4, r2, r0
這裡符合ldr r1, [pc, #72]這句指令的本意,他訪問的就是這個值代表的地址中的值。(這種跳轉的方法其實就是為了應對非法立即數,導致在一個機器碼裡放不下命令和數據的情況)
朱老師代碼如下:
1 /* 2 * 文件名: led.s 3 * 作者: 朱老師 4 * 描述: 演示重定位(在SRAM內部重定位) 5 */ 6 7 #define WTCON 0xE2700000 8 9 #define SVC_STACK 0xd0037d80 10 11 .global _start // 把_start鏈接屬性改為外部,這樣其他文件就可以看見_start了 12 _start: 13 // 第1步:關看門狗(向WTCON的bit5寫入0即可) 14 ldr r0, =WTCON 15 ldr r1, =0x0 16 str r1, [r0] 17 18 // 第2步:設置SVC棧 19 ldr sp, =SVC_STACK 20 21 // 第3步:開/關icache 22 mrc p15,0,r0,c1,c0,0; // 讀出cp15的c1到r0中 23 //bic r0, r0, #(1<<12) // bit12 置0 關icache 24 orr r0, r0, #(1<<12) // bit12 置1 開icache 25 mcr p15,0,r0,c1,c0,0; 26 27 // 第4步:重定位 28 // adr指令用於加載_start當前運行地址 29 adr r0, _start // adr加載時就叫短加載 30 // ldr指令用於加載_start的鏈接地址:0xd0024000 31 ldr r1, =_start // ldr加載時如果目標寄存器是pc就叫長跳轉,如果目標寄存器是r1等就叫長加載 32 // bss段的起始地址 33 ldr r2, =bss_start // 就是我們重定位代碼的結束地址,重定位只需重定位代碼段和數據段即可 34 cmp r0, r1 // 比較_start的運行時地址和鏈接地址是否相等 35 beq clean_bss // 如果相等說明不需要重定位,所以跳過copy_loop,直接到clean_bss 36 // 如果不相等說明需要重定位,那麼直接執行下面的copy_loop進行重定位 37 // 重定位完成後繼續執行clean_bss。 38 39 // 用匯編來實現的一個while循環 40 copy_loop: 41 ldr r3, [r0], #4 // 源 42 str r3, [r1], #4 // 目的 這兩句代碼就完成了4個字節內容的拷貝 43 cmp r1, r2 // r1和r2都是用ldr加載的,都是鏈接地址,所以r1不斷+4總能等於r2 44 bne copy_loop 45 46 // 清bss段,其實就是在鏈接地址處把bss段全部清零 47 clean_bss: 48 ldr r0, =bss_start 49 ldr r1, =bss_end 50 cmp r0, r1 // 如果r0等於r1,說明bss段為空,直接下去 51 beq run_on_dram // 清除bss完之後的地址 52 mov r2, #0 53 clear_loop: 54 str r2, [r0], #4 // 先將r2中的值放入r0所指向的內存地址(r0中的值作為內存地址), 55 cmp r0, r1 // 然後r0 = r0 + 4 56 bne clear_loop 57 58 run_on_dram: 59 // 長跳轉到led_blink開始第二階段 60 ldr pc, =led_blink // ldr指令實現長跳轉 61 62 // 匯編最後的這個死循環不能丟 63 b . View Code