先看一下LDR指令。
LDR指令LDR指令的格式為:LDR{條件} 目的寄存器,<儲存空間地址>LDR指令用於從儲存空間中將一個32位的字資料傳送到目的寄存器中。該指令通常用於從儲存空間中讀取32位的字資料到通用寄存器,然後對資料進行處理。當程式計數器PC作為目的寄存器時,指令從儲存空間中讀取的字資料被當作目的地址,從而可以實現程式流程的跳轉。該指令在程式設計中比較常用,且定址方式靈活多樣,請讀者認真掌握。指令樣本:LDR R0,[R1] ;將儲存空間地址為R1的字資料讀入寄存器R0。LDR R0,[R1,R2] ;將儲存空間地址為R1+R2的字資料讀入寄存器R0。LDR R0,[R1,#8] ;將儲存空間地址為R1+8的字資料讀入寄存器R0。LDR R0,[R1,R2] ! ;將儲存空間地址為R1+R2的字資料讀入寄存器R0,並將新地址R1+R2寫入R1。LDR R0,[R1,#8] ! ;將儲存空間地址為R1+8的字資料讀入寄存器R0,並將新地址R1+8寫入R1。LDR R0,[R1],R2 ;將儲存空間地址為R1的字資料讀入寄存器R0,並將新地址R1+R2寫入R1。LDR R0,[R1,R2,LSL#2]! ;將儲存空間地址為R1+R2×4的字資料讀入寄存器R0,並將新地址R1+R2×4寫入R1。LDR R0,[R1],R2,LSL#2 ;將儲存空間地址為R1的字資料讀入寄存器R0,並將新地址R1+R2×4寫入R1。
ADR(小範圍的地址讀取偽指令):該指令是將基於PC的地址值或基於寄存器的地址值讀取到寄存器中,它是基於PC值進行地址運算的。
LDR指令和ADR指令簡要對比一下:
LDR reg, ={lab}或={num}是用於載入標號地址和立即數的偽指令,不加"="號時,表示記憶體定址。 ADR是將基於PC相對位移的地址值或基於寄存器相對位址值讀取的偽指令
其實這兩個都是偽指令:adr是小範圍的地址讀取偽指令,ldr是大範圍的讀取地址偽指令。可實際上adr是將基於PC相對位移的地址值或基於寄存器相對位址值讀取的為指令,而ldr用於載入32為立即數或一個地址到指定的寄存器中。到這兒就會看到其中的區別了。如果在程式中想載入某個函數或者某個在聯結時候指定的地址時請使用adr,例如在lds中需要重新置放的地址。當載入32為的立即數或外部地址時請用ldr。
一個網上的例子:
AREA test,CODE,READONLY ENTRYSTART ldr r0,_start adr r0,_start ldr r0,=_start nop _start nop END
這段代碼並無實際意義,只是為了方便說明。我們反組譯碼一下看看:
START $a test 0x00000000: e59f0008 .... LDR r0,_start ; [0x10] = 0xe1a00000 0x00000004: e28f0004 .... ADR r0,{pc}+0xc ; 0x10 0x00000008: e59f0004 .... LDR r0,[pc,#4] ; [_start = 0x14] = 0 0x0000000c: e1a00000 .... MOV r0,r0 _start 0x00000010: e1a00000 .... MOV r0,r0 $d 0x00000014: 00000000 .... DCD 0 ; _start/***********************************
AXD模擬 確定RO段地址為:0x50200000
反編譯
********************************/
ldr r0, _start
從記憶體位址 _start 的地方把值讀入。執行這個後,r0 = 0xe1a00000
adr r0, _start
取得 _start 的地址到 r0,但是請看反編譯的結果,它是與位置無關的,其實取得的是相對的位置。例如這段代碼在 0x00000000 運行,那麼 adr r0, _start 得到 r0 = 0x00000010;
ldr r0, =_start
這個取得標號 _start 的絕對位址。這個絕對位址是在 link 的時候確定的。看上去這隻是一個指令,但是它要佔用 2 個 32bit 的空間,一條是指令,另一條是 _start 的資料(因為在編譯的時候不能確定 _start 的值,而且也不能用 mov 指令來給 r0 賦一個 32bit 的常量,所以需要多出一個空間存放 _start 的真正資料,在這裡就是 0x0000000c)。
因此可以看出,這個是絕對的定址,不管這段代碼在什麼地方運行,它的結果都是 r0 = 0x0000000c。
再看一段代碼:
ldr r0, _start adr r0, _start ldr r0, =_start_start: b _start
下面是反組譯碼的結果:
START $a $v0 test 0x00000000: e59f0004 .... LDR r0,_start ; [0xc] = 0xeafffffe 0x00000004: e28f0000 .... ADR r0,{pc}+8 ; 0xc 0x00000008: e59f0000 .... LDR r0,[pc,#0] ; [_start = 0x10] = 0 _start 0x0000000c: eafffffe .... B {pc} ; 0xc $d 0x00000010: 00000000 .... DCD 0 ; _start
設定RO為0X50200000 AXD模擬反編譯為:
1.ldr r0, _start
這是一條指令,從記憶體位址 _start 的位置把值讀入。
在這裡_start是一個標號(是一個相對程式的運算式),組譯工具計算相對於 PC 的位移量,並產生相對於 PC的前索引的指令:ldr r0, [0xc]。執行指令後,r0 = 0xeafffffe。
2.adr r0, _start
這是一條偽指令,總是會被組譯工具彙編為一個指令。組譯工具嘗試產生單個 ADD 或 SUB 指令來裝載該地址。如果不能在一個指令中構造該地址,則產生一個錯誤,並且彙編失敗。
在這裡是取得標號_start 的地址到 r0,因為地址是相對程式的,因此ADR產生依賴於位置的代碼,在此例中被彙編成:add r0, pc, #0。因此該代碼可以在和標號相對位置不變的情況下移動;執行後r0=0x5020000c
假如這段代碼在 0x50200000 運行,那麼 adr r0, _start 得到 r0 = 0x5020000c;如果在地址 0 運行,就是 0x0000000c 了。
通過這一點可以判斷程式在什麼地方運行。U-boot中那段relocate代碼就是通過adr實現當前程式是在RAM中還是flash中,下面進行簡要分析。
3.ldr r0, =_start
這是一條偽指令,是一個相對程式的或外部的運算式。組譯工具將相對程式的標號運算式 label-expr 的值放在一個文字池中,並產生一個相對程式的 LDR 指令來從文字池中裝載該值,在此例中產生的指令為:ldr r0, [pc, #0],對應文字池中的地址以及值為:0x50200010: 5020000c。如果 label-expr 是一個外部表格達式,或者未包含於當前段內,則組譯工具在目標檔案中放置一個連結程式重定位命令。連結程式在連結時產生地址。
因此取得的是標號 _start 的絕對位址,這個絕對位址(運行地址)是在串連的時候確定的。它要佔用 2 個 32bit 的空間,一條是指令,另一條是文字池中存放_start 的絕對位址。因此可以看出,不管這段代碼將來在什麼地方運行,它的結果都是 r0 = 0x5020000c。由於ldr r0, =_start取得的是_start的絕對位址,這句代碼可以在_start標號的絕對位置不變的情況下移動;如果使用寄存器pc在程式中可以實現絕對轉移。