汇编语言笔记-ARM架构指令集
目錄
- 指令的執行
- 指令后綴
- 指令集
- 數據傳送指令
- 處理器內數據傳送
- 存儲器訪問指令
- 不同數據大小的存儲器訪問
- 存儲器訪問方式(地址表達式)(部分省略)
- 立即數偏移(前序)
- 寄存器偏移
- 多加載和多存儲
- 壓棧和出棧
- 算術運算
- 邏輯運算
- 移位
- 數據轉換
- 展開
- 反轉
- 位域處理
- 比較和測試
- 程序流控制
- 跳轉
- 函數調用
- 條件跳轉
- 比較并跳轉
- 條件執行(IT指令)
- 表格跳轉
- 飽和運算
- 異常相關
- 休眠模式相關
- 存儲器屏障
- 其它指令
???????偽指令和指令的區別:只存在于匯編語言中,而不存在于機器語言中。幫助編譯器將匯編語言(或者其它高級語言)轉換成機器語言(偽指令被編譯時被等效的指令替換)。
ARM架構的操作狀態有Thumb狀態和ARM狀態之分(當然還有調試狀態):
| 指令集 | Thumb指令集 | ARM指令集 |
| 指令長度 | 16位(半字指令) | 32位 |
| 指令執行條件 | 大多數指令無條件執行 | 大多數指令有條件執行 |
| 優點 | 低功耗,存儲空間要求低 | 代碼需要的指令數少,性能高 |
Cortex-M系列不支持ARM狀態,因此本文內容基于Thumb狀態進行撰寫。
Thumb指令集的操作數和指令地址仍為32位
指令的執行
???????程序要執行的指令都保存在存儲器中(指令轉化為機器碼,也稱操作碼)。當計算機需要執行一條指令時,首先產生這條指令的地址,并根據地址號打開相應的存儲單元并取出指令代碼,最后CPU根據指令代碼的要求以及指令中的操作數去執行相應的操作。
指令后綴
| S | 更新APSR(應用程序狀態寄存器,如進位、溢出、零和負標志),例如:ADDS R0,R1;該ADD操作會更新APSR |
| EQ, NE, CS, CC, MI,PL,VS,VC,HI,LS,GE, LT, GT, LE | 條件執行后綴,若滿足相應條件則執行后面的語句,例如:BEQ label;若之前的操作得到相等的狀態(狀態寄存器Z置位),則跳轉至 label,各個條件碼的介紹如條件碼介紹表所示 |
| .N,.W | 指定使用的是16位指令( narrow)或32位指令(wide) |
| .32,.F32 | 指定32位單精度運算,對于多數工具鏈,32后綴是可選的 |
| .64,.F64 | 指定64位單精度運算,對于多數工具鏈,64后綴是可選的 |
可以通過S后綴的指令影響狀態寄存器的標志位,再通過各類條件碼后綴執行相應判斷
| EQ | 0000 | Z=1 | 相等 |
| NE | 0001 | Z=0 | 不相等 |
| CS/HS | 0010 | C=1 | 無符號數大于或等于 |
| CC/LO | 0011 | C=0 | 無符號數小于 |
| MI | 0100 | N=1 | 負數 |
| PL | 0101 | N=0 | 正數 |
| VS | 0110 | V=1 | 溢出 |
| VC | 0111 | V=0 | 沒有溢出 |
| HI | 1000 | C=1,Z=0 | 無符號數大于 |
| LS | 1001 | C=0或Z=1 | 無符號數小于或等于 |
| GE | 1010 | N=V | 帶符號數大于或等于 |
| LT | 1011 | N!=V | 帶符號數小于 |
| GT | 1100 | Z=0,N=V | 帶符號數大于 |
| LE | 1101 | Z=1或N!=V | 帶符號數小于或等于 |
???????條件碼應用舉例:
???????1、比較兩個值大小,C語言代碼如下:
???????對應的ARM指令代碼如下:(設R0為a,R1為b)
CMP R0, R1 ;R0與R1比較 ADDHI R0,R0,#1 ;若R0 > R1,則R0 = R0 + 1 ADDLS R1,R1,#1 ;若R0 <= R1,則R1 = R1 + 1???????2、若兩個條件均成立,則將這兩個數值相加,C語言代碼如下:
if((a != 10)&&(b != 20)) a = a + b;???????對應的ARM指令代碼為:
CMP R0,#10 ;比較R0是否為10 CMPNE R1,#20 ;若R0不為10,則比較R1是否為20 ADDNE R0,R0,R1 ;若R0不為10且R1不為20,則執行 R0 = R0+R1???????3、若兩個條件有一個成立,則將這兩個數值相加,C語言代碼如下:
if((a!=10)||(b!=20)) a=a+b;???????對應的ARM指令代碼為:
CMP R0,#10 CMPEQ R1,#20 ADDNE R0,R0,R1指令集
Rx、Ry、Rz為寄存器,#num32為小于0xFFFF FFFF(32位)的立即數(即未溢出),ADDR為[地址表達式],Fun_lab表示函數標號,num_lab表示變量或常量標號,cond表示條件碼
數據傳送指令
處理器內數據傳送
| MOV | MOV Rx,Ry/#num32 | 將源操作數的值賦給目的操作數 | |
| MRS | MRS Rx,Rs | 同MOV | 源操作數應為特殊寄存器 |
| MSR | MSR Rs,Rx | 同MOV | 目的操作數應為特殊寄存器 |
| MOVW | MOVW Rx,#num16 | 將源操作數賦給目的操作數的低16位 | 高位清零 |
| MOVT | MOVT Rx,#num16 | 將源操作數賦給目的操作數的高16位 | 低位不變 |
MOV指令傳遞的立即數應在0~0xFFFF范圍內或可以通過8bit連續有效位通過移位或復制全部奇數或偶數字節能得到
存儲器訪問指令
不同數據大小的存儲器訪問
| 32位 | LDR | STR | LDR Rx,ADDR;將地址ADDR上的值賦給Rx STR Rx,ADDR;將Rx的值賦給地址為ADDR的存儲空間 |
| 16位有符號 | LDRSH | 無 | |
| 16位無符號 | LDRH | STRH | |
| 8位有符號 | LDRSB | 無 | |
| 8位無符號 | LDRB | STRB | |
| 多個32位 | LDM | STM | LDM、STM |
| 雙字(64位) | LDRD | STRD | LDRD/STRD R1,R2,ADDR;從地址ADDR上讀出兩個字并分別賦給兩個寄存器 |
| 棧操作(32位) | POP | PUSH | PUSH、POP |
有符號讀和無符號讀的區別在于,同樣的數據0x83,通過LDRB讀取是0x0000 0083,通過LDRSB指令讀取是0xFFFF FF83,即無符號讀將對應8位數據進行符號位擴展
LDR等指令的操作數為立即數時,范圍為±4095
LDR不一定是指令,當使用格式為LDR 寄存器,=立即數時為偽操作,作用是將該立即數賦給寄存器,與MOV指令的區別在于,MOV指令賦的立即數有限制
存儲器訪問方式(地址表達式)(部分省略)
立即數偏移(前序)
???????數據傳輸使用的存儲器地址為:寄存器中的數值+立即數常量(偏移地址)
LDRB R0,[R1,#0x3];從地址R1+0x3中讀取一個字節并將其存入R0加入感嘆號(!)可更新存放地址的寄存器的值(寫回):
LDRB R0,[R1,#0x3]!;從地址R1+0x3中讀取一個字節并將其存入R0后令R1=R1+0x3
可以使用寄存器R15(PC)作為寄存器,PC寄存器的值為當前指令地址
寄存器偏移
???????類似立即數偏移,但這里的寄存器可以通過移位指令進行移位:
LDR R3,[R0, R2, LSL #2];將存儲器[R0+(R2<<2)]讀入R3 LDR R3,[R0, R2];將存儲器[R0+R2]讀入R3???????注意:這里進行的是前序偏移,也就是以地址偏移后的值為地址進行取值,下面介紹一下后序尋址:
???????后序尋址是取地址上的值,后進行地址偏移:
LDR R0, [R1], #offset;讀取存儲器[R1],然后R1被賦值為R1+偏移后序尋址不能使用R14(SP)或R15(PC)。
多加載和多存儲
???????介紹:連續取出一個地址上連續的多個數據
LDM和STM可添加相應后綴以控制地址增長方向及地址變化方式,可添加后綴如下表所示。
| IB | 地址增加后完成操作 | FA |
| IA | 完成操作后地址增加 | EA |
| DB | 地址減少后完成操作 | FD |
| DA | 完成操作后地址減少 | ED |
注意,在Cortex-M4上似乎只能使用IA和DB后綴,另外兩個可能需要ARM狀態。
| LDMIA(/LDMEA) | 以目的操作數的值為地址,讀取多個值(數量和源操作數的數量一致) | “IA/EA”后綴表示操作后地址增加 |
| LDMDB | 同“LMDIA” | “DB/FD”后綴代表操作后地址減小 |
| STMIA | 以目的操作數的值為地址,寫入多個值(數量和源操作數的數量一致) | “IA”后綴表示操作后地址增加 |
| STMDB | 同“STMIA” | “DB”后綴代表操作后地址減小 |
???????注意:地址增加/減少最終并不會影響目的操作數的值,即不會“寫回”。
源操作數應為多個寄存器,格式如下:
1.以“{”為開始,“}”為結束。
2.可以使用“-”表示范圍:R2-R5表示R2、R3、R4、R5四個寄存器。
3.通過“,”將各個寄存器隔開。
注意:{R1-R4}、{R1,R2,R3,R4}、{R4,R3,R2,R1}三者等效,順序都是R1 ~ R4
加入感嘆號(!)可更新存放地址的寄存器的值(寫回):
LDMIA R0!,[R1,R3-R5];從地址R0中讀取4個字并將其分別賦給R1、R3、R4、R5四個寄存器后令R0=R0+4
壓棧和出棧
???????壓棧指令PUSH和出棧指令POP的操作數可以為多個寄存器,格式如:多寄存器格式
PUSH {R4-R6, LR} ;在子程序開始處將R4-R6和LR(鏈接寄存器)中的值入棧 POP {R4-R6, PC} ;從棧中恢復R4-R6和返回地址,返回地址存入PC以返回子程序算術運算
| ADD | ADD Rx,Ry/#num32 ADD Rx,Ry,Rz/#num32 | Rx=Rx+Ry Rx=Ry+Rz加法運算 | 源操作數可以為立即數,立即數限制與MOV同:限制 |
| ADDW | ADD Rx,#num12 ADD Rx,Ry,#num12 | 同ADD | 源操作數至少有一個是立即數,且不超過12位。 |
| ADC | 同ADD | Rx=Rx+Ry+CF Rx=Ry+Rz+CF,帶進位的加法運算 | 在ADD的基礎上加上進位標志位的值(0/1) |
| SUB | 同ADD | 減法運算 | 可添加W后綴(SUBW) |
| SBC | 同ADD | 帶借位的減法運算 | 在SUB的基礎上減去借位標志位的值(0/1) |
| RSB | 同ADD | Rx=Ry-Rx Rx=Rz-Ry減法反轉,結果為-SUB | |
| MUL | 同ADD | 32位乘法 | |
| UDIV | 同ADD | 無符號除法 | |
| SDIV | 同ADD | 有符號除法 |
注意:許多數據處理指令會有多種形式,形式差別包括操作數數量(2 ~ 3)以及操作數種類(寄存器、立即數、存儲器),以ADD為例:
操作數數目為2:ADD Rx,Ry;Rx=Rx+Ry
操作數數目為3:ADD Rx,Ry,Rz;Rx=Ry+Rz
如果具有兩個源操作數,這不可都為立即數,且立即數不可為第一源操作數。
邏輯運算
| AND | AND Rx,Ry/#num AND Rx,Ry,Rz/#num | 按位進行與運算 |
| ORR | 同AND | 按位進行或運算 |
| BIC | 同AND | Rx=Rx&(~Ry) Rx=Ry&(~Rz)進行非與操作 |
| ORN | 同AND | 進行或非操作 |
| EOR | 同AND | 進行異或操作 |
| MVN | AND Rx,Ry/#num | 進行取反操作(位非) |
邏輯運算的立即數應是可以通過8bit連續有效位通過移位或復制全部奇數或偶數字節能得到的立即數。
移位
| ASR | ASR Rx,Ry/#num ASR Rx,Ry,Rz/#num | Rx=Rx<<Ry/#num Rx=Ry<<Rz/#num 算術右移 | 右移指定位數,并往左側補原符號位 |
| LSL | 同ASR | 邏輯左移 | 左移指定位數,并往右側補0 |
| LSR | 同ASR | 邏輯右移 | 右移指定位數,并往左側補0 |
| ROR | 同ASR | 循環右移 | 右移x位=左移32-x位 |
| RRX | RRX Rx,Ry | 將進位標志并在最右側后進行循環移動一位 |
移位指令使用的立即數范圍為1 ~ 32
對于上述移位指令,更加常用的用法是,在進行數據操作時進行移位:
指令名 Rx,Ry,移位指令名 #num/Rz,如
MOV R0,Ry,LSL #2
注意:RRX作為移位指令名時,不需要立即數或寄存器作為移位位數
圖解:
數據轉換
展開
| SXTB | SXTB Rx,Ry | 將源操作數[7,0]有符號地展開 |
| SXTH | 同SXTB | 將源操作數[15,0]有符號地展開 |
| UXTB | 同SXTB | 將源操作數[7,0]無符號地展開 |
| UXTH | 同SXTB | 將源操作數[15,0]無符號地展開 |
舉例:
MOV R0,#0xBB ;R0=0x0000 00BBSXTB R1,R0 ;R1=0xFFFF FFBBSXTH R1,R0 ;R1=0x0000 00BBUXTB R1,R0 ;R1=0x0000 00BBUXTH R1,R0 ;R1=0x0000 00BB可在源操作數后加入{,ROR #n}進行先移位后展開,n的取值為0、8、16、24,且僅能ROR
反轉
| REV | REV Rx,Ry | 反轉字中的字節,如圖解所示 |
| REV16 | 同REV | 反轉每個半字中的字節 |
| REVSH | 同REV | 反轉低半字中的字節并將結果有符號展開 |
圖解:
???????舉例:
MOV R1,#0x01MOV R0,R1,LSL #24ADD R1,#1ADD R0,R1,LSL #16ADD R1,#1ADD R0,R1,LSL #8ADD R1,#1ADD R0,R1 ;R0=0x0102 0304REV R2,R0 ;R2=0x0403 0201REV16 R3,R0 ;R3=0x0201 0403REVSH R4,R0 ;R4=0x0000 0403位域處理
| BFC | BFC Rx,#num_1,#num_2 | 將寄存器Rx中num_1到num_1+num_2-1對應的位清零,即將寄存器對應位清零,num_1為清零的最低位,num_2為清零寬度 |
| BFI | BFI Rx,Ry,#num_1,#num_2 | 寄存器Ry中的0到num_2-1位賦給R1的num_1到num_1+num_2-1對應的位 |
| CLZ | CLZ Rx,Ry | 將源操作數中從31位開始連續為0的個數賦給目的操作數,即計算前導零的個數 |
| RBIT | RBIT Rx,Ry | 將源操作數按位反轉,即0和31位互換,1和30位互換… |
| SBFX | SBFX Rx,Ry,#num_1,#num_2 | 將寄存器Ry中的num_1到num_1+num_2-1對應的位有符號的展開并賦給Rx:舉例 |
| UBFX | 同SBFX | 同SBFX,但將對應的位無符號展開 |
注意,這里的LDR是LDR偽指令,與LDR指令不同,=表示將后面的值賦給寄存器R0
這里LDR偽指令和MOV指令的區別是,MOV指令并不能傳遞所有的32位立即數,具體限制:MOV立即數限制。
比較和測試
| CMP | CMP Rx,Ry/#num | 計算Rx-Ry/#num,并更新APSR寄存器 |
| CMN | 同CMP | 計算Rx+Ry/#num,并更新APSR寄存器 |
| TST | 同CMP | 計算Rx&Ry/#num(按位與),并更新APSR寄存器的N和Z位 |
| TEQ | 同CMP | 計算Rx⊕Ry/#num(按位異或),并更新APSR寄存器的N和Z位 |
這4個指令使用的立即數同樣有限制,同MOV:具體限制:MOV立即數限制。
上述計算的結果不會保存,如CMP指令,不會把Rx-Ry的差值賦給Rx
程序流控制
跳轉
???????引起跳轉操作的指令:
???????- 跳轉指令
???????- 更新R15(PC)的數據處理指令(MOV、ADD等)
???????- 寫入PC的讀存儲器指令(LDR、LDM、POP等)
| B | B label | 跳轉到標號對應的地址,屬于相對跳轉(會計算標號和當前PC的差),跳轉范圍為±2KB(可添加.W后綴使用32位版本的指令) |
| BX | BX Rx | 跳轉到存放于寄存器Rx中的地址值,并基于Rx第0位設置處理器執行狀態(Cortex-M只支持Thumb狀態,因此第0位必須為1) |
函數調用
| BL | BL label | 跳轉到標號位置并將返回地址保存到鏈接寄存器R14(LR)中 |
| BLX | BLX Rx | 跳轉到存放于寄存器Rx中的地址值并將返回地址保存到LR中,以及更新EPSR中的T位為Rx的最低位 |
程序計數器R15(PC)為跳轉目標地址(即將標號/地址賦給PC)
返回地址即BL/BLX指令后的指令的地址
由于Cortex-M只支持Thumb狀態,因此使用BLX指令時,Rx的第0位必須為1
函數調用和標號跳轉的區別在于,函數調用需要將返回地址保存,這也是BL和BLX與B和BX的區別
條件跳轉
???????通過指令改變標志寄存器后根據條件碼運行程序。
???????指令格式:B(cond) label/B(cond).W label
B(cond)表示指令B和條件碼cond相連,中間無空格
???????各個條件碼如條件碼助記符所示。
???????舉例:
CMP R0,#2 ;比較R0和1的值,并根據結果修改應用程序狀態寄存器APSR BEQ LOOP ;注意,BEQ是B和EQ結合,但APSR的Z位置位,即符合條件碼EQ,則跳轉到LOOP對應的位置可用于if語句之類的分支選項
比較并跳轉
| CBZ | CBZ Rx label | 當Rx為0時跳轉到標號對應的位置 |
| CBNZ | 同CBZ | 當Rx不為0時跳轉到標號對應的位置 |
標號的相對偏移量應該在0x00 ~ 0x7E內
可用于循環中的條件判斷
指令不影響APSR的值
條件執行(IT指令)
???????指定下面語句執行的條件
???????語法:
IT(T/E)(T/E)(T/E) cond;(T/E)數量為0~3,決定下面指令的數量 ins_1(cond) ;若數量為0,則下面不需要寫,ins_1表示指令,這里的cond和上一行的cond相同 ins_2(cond/~cond) ;若數量為1,則需要寫到這一句(當然下面的不用寫),當第一個(T/E)為T時,這里的cond和第一行的cond相同,為E時相反 ins_3(cond/~cond) ; ins_4(cond/~cond) ;???????舉例:
ITETT NE ADDNE R0, R0, R1 ADDEQ R0, R0, R3 ADDNE R2, R4, #1 MOVNE R5, R3表格跳轉
| TBB | TBB語法及示例 | 進行字節跳轉(偏移),偏移范圍為0 ~ 512(2*2^8)字節 |
| TBH | TBH語法及示例 | 進行字節跳轉(偏移),偏移范圍為0 ~ 128k(2*2^16)字節 |
上述的TBB和TBH實際上實現的是表格跳轉,使用PC時可以實現分支選擇,即c語言中的switch語句
飽和運算
| SSAT | SSAT Rx,#num,Ry | 將Ry局限到-2 ^(num-1)-1 ~ 2 ^(num-1)并將該值賦給Rx |
| USAT | USAT Rx,#num,Ry | 將Ry局限到0 ~ 2 ^(num-1)并將該值賦給Rx |
注意,num為位數限制,因此1 <= num <= 32
Ry后可加位移運算,但僅局限于ASR與LSL
???????示例:
LDR R0,=0x00020000SSAT R1,#16,R0 ;R1=0x00007FFFSSAT R1,#17,R0 ;R1=0x00020000USAT R1,#16,R0 ;R1=0x00008000USAT R1,#17,R0 ;R1=0x00020000LDR R0,=0xFFF20000SSAT R1,#16,R0 ;R1=0xFFFF8000USAT R1,#16,R0 ;R1=0x00000000異常相關
| SVC | SVC num | 觸發SVC中斷,可用于執行狀態的切換。num為優先級,范圍為0x00 ~ 0xFF,并且該立即數前面可不加"#" |
| CPS | CPSIE/CPSID I/F | "IE"后綴用于使能中斷,"ID"后綴用于禁止中斷 |
休眠模式相關
| WFI(Wait for interrupt) | WFI | 進入休眠(中斷可喚醒) |
| WFE(Wait for event) | WFE | 進入休眠,等待事件(中斷、復位、外部輸入等)發生后喚醒 |
| SEV | SEV | 發送事件 |
存儲器屏障
| DMB | DMB | 數據存儲器屏障。確保在執行新的存儲器訪問前所有的存儲器訪問都已經完成 |
| DSB | DSB | 數據同步屏障。確保在下一條指令執行前所有的存儲器訪問都已經完成 |
| ISB | ISB | 指令同步屏障。清空流水線,確保在執行新的指令前,之前所有的指令都已完成 |
???????應用場景:
其它指令
| NOP | NOP | 空語句,無任何功能,可用于指令對齊或延時 |
| BKPT | BKPT num | num立即數,且此時其前面可不加"#",大小為0x00 ~ 0xFF。該指令的作用是實現軟件斷點,特殊的num值會使處理器執行某些動作 |
本文基于keil5,芯片STM32F429IGx撰寫,參考書籍:《ARM Cortex-M3與Cortex-M4權威指南》
總結
以上是生活随笔為你收集整理的汇编语言笔记-ARM架构指令集的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 鸿蒙的路还很长
- 下一篇: 如何快速构建嵌入式全栈知识体系?