ARM指令:什么是adr adrl ldr mov?
ADR是一條小范圍的地址讀取偽指令,它將基于PC的相對偏移的地址值讀到目標寄存器中。格式:ADR register,exper。
編譯源程序時,匯編器首先計算當前PC值(當前指令位置)到exper的距離,然后用一條ADD或者SUB指令替換這條偽指令,
例如:ADD register,PC,#offset_to_exper。
注意,標號exper與指令必須在同一代碼段。
比如:adr r0, _start ://將指定地址賦到r0中
……
_start:
b _start
r0的值為標號_start與此指令的距離差 + PC值。
ADRL:
這是一條中等范圍的地址讀取偽指令,它將基于PC的相對偏移的地址值讀到目標寄存器中。格式:ADRL register,exper。編譯源程序時,匯編器會用兩條合適的指令替換這條偽指令。
比如:
ADD register,PC,offset1
ADD register,register,offset2
與ADR相比,它能讀取更大范圍的地址。
注意,標號exper與指令必須在同一代碼段。
接下來是LDR,首先要說兩個家伙,他們都叫LDR。
一個是LDR偽指令,一個是LDR指令,名字相同卻不是一個東西。
區分的方法就是看第二個參數,如果有等號,就是偽指令。
LDR指令:
例: ldr r0, 0x12345678
是把0x12345678這個地址中的值存放到r0中。而mov不能干這個活,mov只能在寄存器之間移動數據,或者把立即數移動到寄存器中。
LDR偽指令:
例1(立即數): ldr r0, =0x12345678
這樣,就把0x12345678這個地址寫到r0中了。所以,ldr偽指令和mov是比較相似的。只不過mov指令限制了立即數的長度為8位,也就是不能超過512。而ldr偽指令沒有這個限制。如果使用ldr偽指令,后面跟的立即數沒有超過8位,那么在實際匯編的時候該ldr偽指令會被轉換為mov指令。
例2(標號): ldr r0, =_start //將指定標號的值賦給r0
這里取得的是標號_start的絕對地址,這個絕對地址(運行地址)是在鏈接的時候確定的。它要占用 2 個32bit的空間,一條是指令,另一條是文字池中存放_start 的絕對地址。
對比adr r0, _start和 ldr r0, =_start
它們的目的一樣,都是把標簽的賦給r0,區別---左邊是相對地址,右邊絕對地址。目的一樣,但結果不一定相同。結果是否相同,要看PC值是否和鏈接地址相同。
ldr 和 adr 的區別在哪里?
很多人在寫簡單的裸機代碼或分析uboot時,常常遇到adr ldr指令。卻分不清這2者的區別,今天就來談談adr與ldr指令。
參照韋老師的代碼和Makefile寫了test_adr.S:
9681d7b5259b4122b7042244943275be.jpeg (4.79 KB, 下載次數: 100)
下載附件
2017-9-7 18:14 上傳
Makefile:
135de2fbe8a3441692780f81c60c7dbc.jpeg (13.2 KB, 下載次數: 107)
下載附件
2017-9-7 18:14 上傳
反匯編test_adr.S得到test_adr.dis:
7eaab7b6d47d4d82bee3159cbfd307d1.jpeg (14.97 KB, 下載次數: 113)
下載附件
2017-9-7 18:14 上傳
很顯然,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
a8b3b1ebb9a149588b7dac39efd6a993.jpeg (11.92 KB, 下載次數: 114)
下載附件
2017-9-7 18:14 上傳
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獲取的是內存的值,像指針一樣間接尋址。
|