1. 程式人生 > 其它 >Arm入門第六講 偽指令與Load/Store架構

Arm入門第六講 偽指令與Load/Store架構

目錄

Arm入門第六講 偽指令與Load/Store架構

ARM 彙編器支援ARM偽指令,這些偽指令在彙編階段被翻譯成ARM或者Thumb(or Thumb-2)指令(或者指令序列)

ARM偽指令不是ARM指令集中的指令,只是為了程式設計方便,編譯器定義了偽指令。 使用的時候可以像其它的ARM指令一樣使用,但是在編譯的時候這些偽指令將被等效的ARM指令代替。

ARM偽指令分為如下:

  • ADR 偽指令,裝載程式相關,或暫存器相關地址到暫存器
  • MOV32裝載32位常數或地址到暫存器(ARMV6T2體系以上支援)
  • LDR裝載32位常數到地址暫存器(所有ARM版本均支援)

一丶偽指令

ADR偽指令

ADR偽指令會被彙編器編譯成一條指令。 彙編器通常使用 ADD或者SUB 指令來實現地址裝載功能。 意思就是ADR便於編寫,但是底層會用別的指令來代替。 在inter x86指令集下也有偽指令的概念。 在這裡如果不能用一條指令來實現ADR偽指令的功能,那麼彙編器將報告錯誤。

ARD{cond}{.W} register,lable
cond: 可選的指令執行條件
.W  : 也是可選項,指定指令的寬度
register: 目的暫存器
label: 地址,或者基於PC或者具有暫存器的表示式。

例子:
LDR R0,_START ;從記憶體地址_start的地址把值讀入,設 R0 = 0XE1A00000
ADR R0,_START; 將_START的地址給R0,他是與位置無關的。
也就是基於地址的偏移值。 也就是當前的PC值加上_START的偏移量。

ARM中的彙編指令與ARM中的偽指令都有LDR 但是注意,彙編中的LDR與偽指令中的LDR是不一樣的。

ADRL 中等範圍地址讀取偽指令

ADRL偽指令將基於PC的相對偏移的地址或者基於暫存器對偏移的地址讀取到暫存器中,當地址是位元組對齊的時候,取值範圍位-64kb~64kb之間,當地址值是 字對齊的時候他的取值範圍是 -256-~256kb之間。 當地址是16位元組對齊 的時候,那麼取值範圍更大。在32BIT的Thumb-2指令中,地址取值範圍為 -1MB~1MB

其實他與ADR指令一樣,唯一不同就是讀取的地址範圍比ADR廣。在編譯階段中,ADRL將產生兩條指令,其中一條為多餘指令。如果彙編器不能再兩條指令內完成操作也會報告錯誤。

注意:

ADRL只能是在ARM彙編或者Thumb-2彙編中,Thumb彙編器不支援ADRL偽指令。

使用ADRL指令的時候那麼被裝載的地址必須和偽指令ADRL是一個段中

ADRL{cond} register,label
cond: 可選的指令執行條件
register: 目標暫存器
 label: 地址,或者基於PC或者具有暫存器的表示式。


MOV32 偽指令

MOV32偽指令是裝載一個32位常數或者地址 到暫存器中。與ADR ADRL 不同,他裝載的地址是與地址位置相關的。

一般在彙編層面,彙編器會把它翻譯成 MOV 或者 MOVT指令,這樣任何32bit常亮都可以被裝載到暫存器。

MOV32{COND} register,expr
cond 可選的指令執行條件
reg: 目標暫存器
expr: 表示式的形式,有以下幾種。
    symbol程式中定義的標號地址。
    constant任意32bit常數
    symbol+constant地址標號+32bit常數。

LDR 偽指令

LDR偽指令用於裝載一個32bit常數和一個地址到暫存器。

LDR{COND}{.W} register, =[expr | label-expr]
cond 可選的指令執行條件
.W 可選用來指定指令寬度(Thumb-2指令支援)
reg: 目標暫存器
expr: 32位常亮表示式,彙編器會根據expr的取值情況,對LDR偽指令做如下處理。
    1.expr表示的地址值 沒有超過MOV 或者MOVN偽指令的地址取值範圍,那麼LDR在底層會用MOV和MVN來替換LDR
    2.如果超出了MOV MVN 那麼彙編器會將資料防暑資料快取池(理解為記憶體)同時用一條基於PC的LDR偽指令來讀取這個常數。
    
label-expr方式:
    1.一個程式相關或宣告位外部的表示式,那麼彙編器會將label-expr表示式的值方式資料快取池(記憶體)使用一條程式相關的LDR偽指令將該值取出放入暫存器。
    
    2.一個程式相關或宣告為外部表示式,彙編器會將LABEL_EXPR表示式的值放入資料快取池,然後使用一條程式相關的LDR偽指令將該值取出並放入暫存器。 當lable-expr被宣告位外部的表示式的時候,彙編器將在目標檔案中插入連結重定位偽操作,並且由聯結器在連線的時候生成改地址。

使用說明:

​ LDR載入的地址是絕對地址,也就是與PC相關的地址。 當要裝載的資料不符合MOV MVN指令的裝載的時候,它會先放到資料快取池(記憶體中),此時LDR偽指令處的PC值到資料快取池中目標資料所在的地址的偏移量有一定的限制。 ARM或者32bit的Thumb-2指令中這個範圍是-4kb~4kb。 Thumb或者16bit的Thumb-2指令中這個範圍是0~1kb.

例子:

區別正常的LDR彙編指令與偽指令的LDR只需要看後面的=是否有

LDR R3,=0xff0  ;這句話是將0xFF0給R3。 它符合mov指令
;真正的彙編則會用 MOV R3,#0xff0 替代上條指令
    
LDR R2,=place;  place是標號地址,這裡是將標號地址讀入R2中
彙編層面的指令則會變成如下:
LDR R2,[pc,offset_to_litpool]
 ...
litpool DCD place

二丶Load/Store架構

不是每個Arm指令都支援直接的記憶體處理,必須通過專門的指令把資料先從記憶體(Load)載入到暫存器,然後在處理資料,處理後在放到記憶體中(Store)

處理指令包括如下

  • 單個暫存器資料傳入 LDR/STR
  • 塊資料傳輸: LDM/STM
  • 單個數據交換: SWP

單個暫存器資料讀取指令

LDR 型別資料載入指令

LDR 字資料載入指令

LDR{條件} 目的暫存器,<儲存器地址>

LDR指令是從儲存器地址讀取一個32位的雙字資料傳送到目的暫存器中。

這個指令通常用於將記憶體中的資料讀取到通用暫存器,然後處理資料。如果目的暫存器是PC(R15 == EIP)暫存器,那麼記憶體中讀取的地址就會當做目標地址,從而實現程式的跳轉。

例子:

LDR RO,[R1] ; 讀取記憶體中R1的32位資料到R0中
LDR RO,[R1,R2]; 讀記憶體資料R1+R2的32位資料到給R0
LDR R0,[R1,#8] ;  將R1+8的地址中的32位資料到R0。
重點 後面帶有!:
LDR R0,[R1 + R2]!;  R1+R2的地址中的32位資料給R0,並且R1 + R2的值賦值給R1. 意思就是不光給R0資料 R1還把自身給修改了。

LDR R0,[R1,#8]!; R0 = R1+8的地址中的值。 R1 = R1 + 8
LDR R0,[R1],R2; 將儲存器R1的值讀取出來給R0。 然後R1 = R1 + R2
LDR R0,[R1,R2,LSL#2]!; 將 R1 + (R2 * 4)的地址裡面的值給R0,並且 R1 = R1 + R2*4
LDR R0,[R1],R2,LSL#2; 將儲存區R1的值給R0,R1 = R1 + R2 * 4

LDRB 位元組資料載入指令

LDRB指令是用於將儲存器中一個8位的位元組資料傳送到目的暫存器中。同時將暫存器的高24位清零。 這個指令通常是讀取8位的資料到通用暫存器,然後處理資料。 如果目的暫存器是PC暫存器,那麼可以實現程式流程的跳轉。

LDR{條件}B 目的暫存器,<儲存器地址>
    
LDRB R0,[R1]; 將儲存器地址R1的位元組資料讀入R0,並且將R0的高24位清零。
LDRB R0,[R1,#8]; R1+8的位元組資料給R0,R0&= 0x000000FF;

LDRH 字資料載入指令

H就是一個16位資料。C語言中是一個 short 或者Unsigned short

這個指令是讀取16位資料給通用暫存器。其效果同上。唯一不同的就是讀取到暫存器中的時候高16位清零。

指令條件如下:

LDR{條件}H 目的暫存器,<地址>
LDRH R0,[R1]

單個暫存器資料設定指令

STR 雙字資料存放指令

STR與LDR與之對應,並且相反。 是用於將一個32位的資料傳送到儲存器中。

STR{條件} 源暫存器,<儲存器地址>
STR R0,[R1],#8; R0的資料設定到R1地址中,並且R1 = R1 + 8
STR R0,[R1,#8]; R0的資料存放到R1+8的地址中。

STRB 位元組資料儲存指令

與LDRB與之對應,是將一個位元組(8位)資料設定到目的地址中

STR{條件}B 源暫存器,<儲存器地址>
STRB R0,[R1]; 暫存器R0中的位元組寫入到R1中
STRB R0,[R1,#8]; 暫存器R0中的資料寫入到 R1+8中

STRH 字資料儲存指令

與LDRH與之對應,是將暫存器的低16位資料傳送到儲存器中。

STR{條件}H 源暫存器,<儲存器地址>
STRH R0,[R1]; 暫存器R0中的字寫入到R1中
STRH R0,[R1,#8]; 暫存器R0中的資料寫入到 R1+8中

其它LDR/STR變形指令

LDR或者STR指令,後面都可以跟H或者B 代表2個位元組或者一個位元組。

而我們後面也可以跟 SH SB 代表有符號的2個位元組 和有符號的一個位元組。

如下:

LDRSB R0,[R1] 
STRSB R1,[R0]
LDRSH R1,[RO]
STRSH RO,[R1]

塊傳輸指令

LDM/STM簡介

​ 上面所講的LDR STR指令,最高可操作的是4個位元組。 但是ARM也支援一塊指令的傳輸。 那就是 LDM,STM塊傳輸指令。LDM STM的運算元1後面會跟著! 代表的意思就是我們是塊操作。

LDM/STM塊傳輸

看例子:

LDM R0!,{R3-R9} ; 含義是R0儲存的是基地址,然後R3-R9用於儲存R0中的資料 R3-R9 = 7個暫存器 每個暫存器4個位元組。那麼就是儲存4*7 = 28個位元組。 意思就是從R0讀取28個位元組給R3-R9儲存
    
STMIA R1!,{R3-R9}; 從R3-R9暫存器中讀取的資料寫入到R1的基地址中

注意: 這裡的暫存器的操作順序與LDR是不同的。 是將R0給R3-R9 而LDR 是從運算元2讀取資料給運算元1的

其中LDM後面可以跟

  • IA (Increase After) 基址暫存器在執行指令後增加,意思就是執行完指令,然後自身地址自增,然後再從自增的地方繼續執行指令。高階語言中可以理解為 i++
  • IB: increase Before 基址暫存器在執行指令前增加,僅(arm),意思就是在執行此指令的時候,先自增然後在執行指令。 ++i
  • DA: (Decrease After),基址暫存器在執行指令後減少(僅ARM) 同IA相反的操作。 高階語言中可以理解為 i--
  • DB: (dEcrease Before) 基址暫存器在執行指令前減少,可以理解為--i

例子: 使用LDM STM實現資料拷貝

設R12 = 要拷貝的資料的起始地址(Src 等價於x86 ESI) Src_start
設R13 = 目的的儲存地址
設R14 = 要拷貝資料的終止地址 src_end

LOOP:
	LDMIA R12!,{R0-R11}; R0-R11儲存了R12的資料
    STMIA R13!,{R0-R11}; R0-R11資料放到R13中
    CMP R12,R14;
    BNE LOOP

1.首先使用LDMIA 從R12暫存器中讀取資料,並且交給R0-R11儲存

2.使用STMIA 把R0-R11的資料儲存到R13指向的記憶體中

3.比較是否拷貝完畢

4.如果起始地址!= 結束地址 那麼就是沒拷貝完畢,繼續拷貝。否則繼續執行。

LDM/STM棧操作

在計算中有一種資料結構叫做棧。在x86平臺下棧的應用很廣,比如儲存返回地址,儲存區域性變數,儲存暫存器環境等等。總的來說棧也是記憶體,既然是記憶體那麼肯定需要用Load/store架構來進行操作的。 在ARM中棧的作用起到備份的作用,因為大部分都是使用暫存器來進行操作了。LDM STM就可以操作棧,與上面講的快傳輸指令一樣,後面可以跟著IA IB DA DB. 那麼如果操作棧就會有以下幾種附加值。

  • FD 滿遞減棧,低地址生長,棧指標指向最後一個入棧的有效資料項,指標先減,然後再存資料。 這個和x86下一樣,意思就是先開闢棧,然後在移動棧指標。

    如下圖:

  • ED 空遞減棧,低地址生長,棧指標(sp)是指向下一個入棧的空間,他是先存資料,然後指標在遞減。在x86下就是先移動棧指標,然後在進行資料儲存。sp 永遠指向下一棧。

例子:FD

STMFD SP!,{R0-R7,LR}; 這個指令是將RO-R7,LR的資料交給SP儲存
等價於以下指令:
STMDB SP!,{R0-R7,LR};
或者
PUSH {R0-R7,LR}

LDMFD SP!,{RO-R7,LR}; 將SP的資料儲存到R0-R7,LR
等價於:
LDMDB SP!,{R0-R7,LR};
或者:
pop{R0-R7,LR}

資料交換指令

SWP資料交換指令