關於LDR指令的理解

2021-06-17 21:20:37 字數 4495 閱讀 8356

之前在閱讀arm的彙編**時,碰到了adr指令,查arm的指令手冊,只說該指令是採用相對位址的,但這個相對位址應該怎麼理解,卻沒有具體說明。之後在網上以adr指令為關鍵字進行搜尋,也沒有找到進一步的知識。結果,今天在搜尋android資料的時候,意外的發現了adr指令與ldr指令的不同,一下子解決了心中的問題。以adr指令與ldr指令對比作為關鍵字,甚至可以搜到好幾篇文章,實在是...... 竟然困擾了自己那麼長時間。

將兩篇轉來,作為備忘吧。

一、adr和ldr的區別

同學們在學習arm指令時,多數都會對adr和ldr這兩個命令產生疑惑,那他們究竟有什麼區別呢?

其實這兩個都是偽指令:adr是小範圍的位址讀取偽指令,ldr是大範圍的讀取位址偽指令。可實際上adr是將基於pc相對偏移的位址值或基於暫存器相對位址值讀取的為指令,而ldr用於載入32為立即數或乙個位址到指定的暫存器中。到這兒就會看到其中的區別了。如果在程式中想載入某個函式或者某個在聯接時候指定的位址時請使用adr,例如在lds中需要重新定位的位址。當載入32為的立即數或外部位址時請用ldr。

area test,code,readonly

entry

ldr r0,_start

adr r0,_start

ldr r0,=_start

nop

_start

nopend

這段**並無實際意義,只是為了方便說明。我們反彙編一下看看:

4:                 ldr      r0,_start

0x00000000          e59f0008      ldr       r0,[pc,#0x0008]

5:                  adr      r0,_start 

0x00000004          e28f0004       add      r0,pc,#0x00000004

6:                  ldr      r0,=_start 

0x00000008          e59f0004      ldr       r0,[pc,#0x0004]

7:                  nop 

8: 9: 

10: _start 

0x0000000c           e1a00000           nop 

11:           nop

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和adr在使用標號表示式作為運算元的區別

arm彙編有ldr指令以及ldr、adr偽指令,他門都可以將標號表示式作為運算元,下面通過分析一段**以及對應的反彙編結果來說明它們的區別。 

ldr     r0, _start

adr     r0, _start

ldr     r0, =_start

_start:

b  _start

編譯的時候設定 ro 為 0x30000000(好像有問題),下面是反彙編的結果:

0x00000000: e59f0004  ldr r0, [pc, #4] ; 0xc

0x00000004: e28f0000  add r0, pc, #0 ; 0x0

0x00000008: e59f0000  ldr r0, [pc, #0] ; 0x10

0x0000000c: eafffffe  b 0xc

0x00000010: 3000000c  andcc r0, r0, ip ;注這條指令是不在上面指令中的任何一條

ldr在此是一條指令,把記憶體位址 _start 位置中的值讀入r0。(_start為指標之意,讀取指標的值)

在這裡_start是乙個標號(是乙個相對程式的表示式),匯程式設計序計算相對於 pc 的偏移量,並生成相對於 pc的前索引指令:ldr r0, [pc, #4]。執行指令後,r0 = 0xeafffffe。

可以在和_start標號的相對位置不變的情況下移動( 也就是說整段**從flash中拷貝到ram中依然可以正常執行)。

adr是小範圍的位址讀取偽指令.adr 指令將基於pc 相對偏移的位址值讀取到暫存器中.在彙編編譯源程式時,adr 偽指令被編譯器替換成一條合適的指令.通常,編譯器用一條

add 指令或sub 指令來實現該adr 偽指令的功能,若不能用一條指令實現,則產生錯誤,

編譯失敗.

r0的值為((標號_start 的位址與此指令的距離差)+(此指令的位址))。在此例中被彙編成:add r0, pc, #0。該**可以在和標號相對位置不變的情況下移動(也就是說整段**從flash中拷貝到ram中依然可以正常執行);

假如這段**在 0x30000000 執行,那麼 adr r0, _start 得到 r0 = 0x3000000c;如果在位址 0 執行,就是 0x0000000c 了。

通過這一點可以判斷程式在什麼地方執行。u-boot中那段relocate**就是通過adr實現判斷當前程式是在ram中還是flash中。

3.ldr     r0, =_start  :將指定標號的值賦給r0

ldr在此是一條偽指令,_start(即:label-expr)是乙個相對程式的或外部的表示式。匯程式設計序將相對程式的標號表示式 label-expr 的值放在乙個文字池中,並生成乙個相對程式的 ldr 指令來從文字池中裝載該值,在此例中生成的指令為:ldr r0, [pc, #0],對應文字池中的位址以及值為:0x00000010: 3000000c。如果 label-expr 是乙個外部表示式,或者未包含於當前段內,則匯程式設計序在目標檔案中放置乙個鏈結程式重定位命令。鏈結程式在鏈結時生成位址。

因此取得的是標號 _start 的絕對位址,這個絕對位址(執行位址)是在連線的時候確定的。它要占用 2 個 32bit 的空間,一條是指令,另一條是文字池中存放_start 的絕對位址。因此可以看出,不管這段**將來在什麼地方執行,它的結果都是 r0 = 0x3000000c。由於ldr r0, =_start取得的是_start的絕對位址,這句**可以在_start標號的絕對位置不變的情況下移動;如果使用暫存器pc在程式中可以實現絕對轉移。(1.絕對位址;2.標號對應的值)

舉例:gpfcon      equ  0x56000050

ldr   r0,=gpfcon

gpfcon      :標號

0x56000050      :標號的值

ldr的確是個複雜的指令,現總結一下: 

首先要判斷我們用的是ldr arm指令還是偽指令。 當我們用的是arm指令時,它的作用不是向暫存器裡載入立即數,而是將某個位址裡的內容載入到暫存器。而偽指令ldr的作用就是向暫存器裡載入立即數。

(1) ldr偽指令

ldr偽指令的格式是 ldr rn, =expr

其中,expr是要載入到rn中的內容,一般可以是立即數或者label。

如果expr可以用8bit資料向右移偶數字得到,那麼這條偽指令就被編譯器翻譯成mov指令。具體的移位情況可以去查閱資料。反之如果立即數很大,超過了12bit的表示範疇,那麼就不能用一條mov指令了,畢竟arm指令最大只有32bit的空間可用(risc的arm所有的指令長度是一致的,效率較高,當然我們並不關心16bit的thumb指令)。如果不能用一條32bit的指令乘下來,那麼就只能另闢蹊徑了,新開一段緩衝,將立即數expr放到裡面,然後將其位址(暫時標記為addr)拿來使用:

ldr rn, addr

*** (***就是expr)

***

(2)ldr arm 指令

就是將乙個位址的內容載入到暫存器。不能用mov,因為arm裡的mov只是在暫存器之間傳輸資料,不支援在寄出器和memory之間傳遞資料。因此就出現了ldr/str指令。如ldr rn, addr,注意這裡的addr的值也是有限制的。這個label應該距離當前指令的距離不超過4k。因為我們知道label在具體使用的時候應該是被翻譯成了相對偏移,如果這個label長度不超過12bit,那麼就不應超過4k,我們可以這樣做:

ldr pc, _start_armboot

_start_armboot: .word arm_startboot

這樣label _start_armboot就在指令下方,因此肯定是合法的。

LDR指令和LDR偽指令

arm指令集中,ldr通常都是作載入指令,但是它也可以作偽指令。初學者一般不會注意到它們的區別,其實在嵌入式開發過程中,這兩條指令時非常常用的!我們應該了解他們的區別。ldr偽指令的形式是 ldr rn,expr 作用是裝在乙個32bit常數和乙個位址到暫存器。下面舉乙個例子來說明它的用法。coun...

LDR指令和LDR偽指令的區別

arm指令集中,ldr通常都是作載入指令,但是它也可以作偽指令。初學者一般不會注意到它們的區別,其實在嵌入式開發過程中,這兩條指令時非常常用的!我們應該了解他們的區別。ldr偽指令的形式是 ldr rn,expr 作用是裝在乙個32bit常數和乙個位址到暫存器。下面舉乙個例子來說明它的用法。coun...

LDR偽指令與LDR載入指令

arm指令集中,ldr通常都是作載入指令的,但是它也可以作偽指令。ldr偽指令的形式是 ldr rn,expr 下面舉乙個例子來說明它的用法。count equ 0x40003100 ldr r1,count mov r0,0 str r0,r1 count是我們定義的乙個變數,位址為0x40003...