對比理解adr,ldr指令

很多人在寫簡單的裸機代碼或分析uboot時,常常遇到adr ldr指令。卻分不清這2者的區別,今天就來談談adr與ldr指令。
 
 
參照韋老師的代碼和Makefile寫了test_adr.S
 
.text
.globl _start
_start:
    ldr r0, test
    adr r0, test
    ldr r0, =test
    nop
test:
nop
 
Makefile
 
all:test_adr.S
       arm-linux-gcc -c -o test_adr.o test_adr.S
         arm-linux-ld -Ttext 0x00000000 -gtest_adr.o -o test_adr_elf
        arm-linux-objcopy -O binary -S test_adr_elf test_adr.bin
        arm-linux-objdump -D -m arm test_adr_elf test_adr.dis
clean:
        rm -ftest_adr.dis test_adr.bin test_adr_elf *.o
 
反彙編test_adr.S得到test_adr.dis
 
test_adr_elf:
file format elf32-littlearm
Disassembly of section .text:
00000000 _start:
0: e59f0008 ldr r0, [pc, #8]; 10 test
4: e28f0004 add r0, pc, #4; 0x4
8: e59f0004 ldr r0, [pc, #4]; 14.text+0x14
c: e1a00000 nop (mov r0,r0)
00000010 test:
10:e1a00000 nop (mov r0,r0)
14:00000010 andeq r0, r0, r0, lsl r0
 
很顯然,ldr獲取的是內存的值(至於這個內存存的是數據還是地址,不是問題重點),像指針一樣間接尋址(看到了[]符號咯),而adr是得到一個與PC有關的值,必定是個地址。
 
韋老師舉了個例子:
adr r0, _start,r0就是_start對應指令當前的地址
對於“_start對應指令當前的地址”,我理解了很久,終於想清楚,比如在uboot中,_start標號對應的指令(即b reset)的鏈接地址是0x33f80000確鑿無疑。
 
如果從NOR Flash啓動,b reset被燒在NOR Flash 0地址,那麼b reset相對於此時的PC來說,它的地址就是0。
 
如果u-boot被直接下載到SDRAM的0x33f80000處運行,那麼b reset自然處在SDRAM的0x33f80000。
 
所謂“當前”---是以運行時的PC爲參照。
 
下面基於以上理解,分析test_adr.dis
 
00000000 _start:
0: e59f0008 ldr r0, [pc, #8]; 10 test
4: e28f0004 add r0, pc, #4; 0x4
8: e59f0004 ldr r0, [pc, #4]; 14.text+0x14
c: e1a00000 nop (mov r0,r0)
 
00000010 test:
10:e1a00000 nop (mov r0,r0)
14:00000010 andeq r0, r0, r0, lsl r0
 
1、先分析第一條指令ldr r0,test被編譯成ldr
r0, [pc, #8],即到當前PC+8的存儲器取值,運行第一條指令時,PC其實已經是8了(流水線決定的)。
 
那麼8+8等於0x10,所以r0等於e1a00000,此指令的作用就是讀取test地址處存放的值。由於此處放了一條nop,即得到nop的機器碼。
 
2、第二條adr r0,test被編譯成add r0, pc, #4
這顯然是依賴程序執行到此處的PC值。ADR是小範圍地址讀取僞指令,會將基於PC 相對偏移的地址值讀取到寄存器中,此指令在4地址,PC是4+8=0xc再加4,於是r0=0x10。

 
從結果上來看,test自身的值(標號值),被讀到了r0,這個值是以PC爲參考的,也就是test對應的指令(第二個nop)當前的地址。r0=(標號test的地址與此指令的距離差)+(此指令的地址)=((0x10-0x4=12)+(4))=16=0x10。
 
假如在0x30000000以上運行,r0=((12)+(0x30000004))= 0x30000010。
 
3、ldr r0,=test被編譯成兩個字,一個指令,一個文字池。執行到這裏PC=8, 8+8+4=0x14,所以在14地址取值,編譯器在14地址處放了0x00000010,0x00000010是test的值,假如在Makefile指定連接地址是0x30000000,那麼編譯器放在這裏的就是0x30000010,可見,這個值是編譯時確定的。
 
最後一行andeq r0, r0, r0, lsl r0大概是編譯器的機械動作,把一個數字翻譯成了指令。
 
總結
ADR是小範圍的地址讀取僞指令,它將基於PC 相對偏移的地址值讀取到寄存器中。而ldr獲取的是內存的值,像指針一樣間接尋址。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章