一、基礎概念
1.1 指令分類
- 指令
能夠最終生成機器指令的代碼。 - 僞指令(也叫僞操作)
用於協助整個編譯過程的代碼;最終不會形成機器指令。以.開頭的語句是僞指令。 - 宏指令
就是用前兩種彙編指令寫的宏。
1.2 指令大小寫
彙編不區分大小寫,只有字符型數據或者字符串區分大小寫。所以彙編中的指令和寄存器可以是大寫也可以是小寫。例如:如下兩行指令是等價的。
MOV Xo, X1 ; 大寫的彙編指令
mov x0, x1 ; 小寫風格的彙編指令
Arm風格的彙編指令一般採用大寫,GUN風格的彙編代碼採用小寫字母作爲指令(iOS Arm64採用小寫的指令)。
1.3 彙編指令註釋
關於彙編如何添加註釋?彙編語言的註釋是以分號";"開頭的,分號之後的內容都屬於註釋。一般而言,彙編語言的註釋出現在以下3個地方:
- 程序的最前面,註釋內容一般是該程序的說明,解釋程序的主要功能,程序的版本號,程序的修改日誌,程序的編制人等等
- 子程序的前面,一般說明該子程序或函數完成的功能,輸入參數,輸出參數,影響的標誌位等等。
- 指令行的後面,解釋該行指令語句的功能。
1.4 指令尋址方式
指令尋址方式,其實質就是指令找到所要操作的內存地址、寄存器的方式(尋址方式是指處理器根據指令中給出的地址信息來尋找物理地址的方式)——大部分彙編指令都是操作內存或者寄存器。
- 寄存器尋址
mov x0, x1
- 立即數尋址
也稱爲立即數尋址,這種尋址方式指令中就已經給出了操作數。也就是在執行指令的過程中,處理器取得指令的同時也取得了操作數,因此稱爲立即數尋址。例如:
ADD R0, #1 @R0+1->R0
ADD R0, R0, #0x3F @R0+0x3F->R0
在上面兩條指令中,源操作數就是立即數,要求以“#”開始,對於十六進制的立即數,要求在“#”後面加“0x”或“&”。
- 寄存器位移尋址
mov x0, lsl #3 ;
- 寄存器間接尋址
mov x0, [x1] ; 其實質就是對x1寄存器的值所代表的內存地址進行操作
- 基址變址尋址
基址變址尋址是把基址寄存器的內容與指令中給出的地址偏移量相加,從而得到一個操作數的有效地址。該方式常用於訪問基地址附近的某些存儲單元,一般有以下幾種方式:
LDR R0, [R1, #4] ;將寄存器R1的值加上4作爲操作數的有效地址,取得操作數後存入R0中。
LDR R0, [R1, #4]! ; 將寄存器R1的值加上4作爲操作數的有效地址,取得操作數後存入R0中,然後寄存器R1的值加上4個字節。
LDR R0, [R1], #4 ; 將寄存器R1的值作爲操作數的有效地址,取得操作數後存入R0中,然後寄存器R1的值加上4個字節。
LDR R0, [R1, R2] ; 將寄存器R1和R2的值相加作爲操作數的有效地址,取得操作數後存入R0中。
- 多寄存器尋址
LDMIA R0, {R1,R2,R3,R4,} @ [R0]->R1,[R0+4]->R2,[R0+8]->R3,[R0+12]->R4
- 堆棧尋址
堆棧是一種數據結構,按先進後出的方式工作,使用一個稱爲堆棧指針的專用寄存器指示當前的操作,堆棧指針總是指向堆棧頂端。當堆棧指針指向最後壓入的數據時,稱爲滿堆棧;當堆棧指針指向下一個將要壓入的位置時,稱爲空堆棧。
根據堆棧的生成方式,可分爲遞增堆棧和遞減堆棧。當堆棧由低地址向高地址生成時,稱爲遞增堆棧,反之稱爲遞減堆棧。排列組合後可得到4中類型的堆棧工作方式,ARM微處理器支持全部4種類型的堆棧工作方式。
滿遞增堆棧:堆棧指針指向最後壓入的數據,由低地址向高地址生成。
滿遞減堆棧:堆棧指針指向最後壓入的數據,由高地址向低地址生成。
空遞增堆棧:堆棧指針指向下一個將要壓入數據的空位置,由低地址向高地址生成。
空遞減堆棧:堆棧指針指向下一個將要壓入數據的空位置,由高地址向低地址生成。
stmfd sp!, {r2 - 47, lr}
- 相對選址
與基址變址尋址類似,相對尋址以程序計數器PC的當前值作爲基地址,指令中的地址標號作爲偏移量,將兩者相加後得到操作數的有效地址。以下程序完成子程序的調用和返回,跳轉指令BL採用了相對尋址方式:
BL NEXT @ 跳轉到子程序NEXT處執行指令
......
NEXT
......
MV PC, LR @ 從子程序返回
二、常見指令
2.1 算術指令
- add
add X0, X1, X2 ; 把寄存器X1、X2的值相加後賦值給寄存器X0。即X0 = X1+X2
- sub
sub X0, X1, X2 ; 把寄存器x1、x2的值相減後賦值給寄存器x0。即x0 = X1-X2
- mul
mul X0, X0, X8 ; 把寄存器x0、x8的值相乘後賦值給寄存器x0。即x0 = X0*X8
- sdiv
sdiv X0, X0, X1 ; 即X0 = X0 / X1;有符號除法運算指令。
- udiv
UDIV X0, X0, X1 ; 即X0 = X0 / X1;無符號除法運算指令。
- cmp
cmp X28, X0 ; X28與X0相減,不存儲結果只更新CPSR中的標誌位。 (CPSR即爲current program status register)
2.2 跳轉指令
跳轉指令分爲條件跳轉和無條件跳轉。條件跳轉即若條件爲真則跳轉。無條件跳轉是直接跳轉到某個位置執行指令。
- b
b label ; 跳轉到label標籤處開始執行,這是無條件跳轉
- bl
b label ; 跳轉到label標籤處開始執行,這是無條件跳轉(與b相比,執行bl前返回地址會被保存到X30(LR)寄存器。bl執行完後會把lr保存的地址賦值給pc寄存器。這樣就可以回到bl跳轉前的位置繼續向下執行)
- ret
; 子程序返回指令。返回地址默認保存在x30(lr)寄存器,這是無條件跳轉
條件跳轉?????
2.3 邏輯指令
- and
AND X0, X1, X2 ; X1和X2寄存器的數據進行邏輯與運算結果保存到X0中。即:X0 = X1 & X2
- orr
ORR X0, X1, X2 ; X1和X2寄存器的數據進行邏輯或運算結果保存到X0中。即:X0 = X1 | X2
- eor
EOR X0, X1, X2 ; X1和X2寄存器的數據進行邏輯抑或運算結果保存到X0中。即:X0 = X1 ^ X2
2.4 數據傳輸指令
- MOV
MOV X19, X1 ; 把寄存器X1 中的數據存儲到寄存器X19中。即 X1 = X19
其他常用數據傳輸指令還有:MOVZ、MOVN、MOVK。
2.5 地址偏移指令
- ADR
ADR Xn, label ; 即Xn = PC + label(小範圍的地址讀取指令。ADR 指令將基於PC 相對偏移的地址值讀取到寄存器中)
- ADRP
; 以頁爲單位的大範圍的地址讀取指令,這裏的P就是page的意思。
2.6 存儲系統操作指令
加載(load)、存儲(store)指令經常成對出現的;無論是加載還是存儲相關的指令,指令後面都只能寫寄存器,不能把寄存器放到指令行的最後面。
LDR、LDUR、LDP都是和加載相關的指令。即把內存中的數據讀取到寄存器中。LD即爲load的縮寫,R即爲register的縮寫,P即爲pair的縮寫,即同時操作兩個寄存器。
LDR & LDUR :從內存中讀取8/4字節數據到一個64/32位寄存器中,即從源寄存器中讀取數據寫入目的寄存器。LDUR中的“U”是unscaled的縮寫,代表不需要按字節對齊。
LDUR和LDR指令的區別:可以簡單理解爲地址偏移量爲負數則使用LDUR,偏移量爲正數則使用LDR。例如:
LDR X1, [sp, #0x28] ; 從SP+ 0x28處開始讀取8字節數據到X1寄存器中。
LDUR X30, [X29, #-0x10] ; 從X29 - 0x10出開始讀取8字節數據到X30(LR)寄存器中。
LDP:從內存中讀取數據放到兩個寄存器中。例如:
LDP w0, w1, [x0, #0x10] ; 讀取x0+0x10內存的字數據,然後四個字節賦值給w0, 另外四個字節賦值給w1
STR、STUR、STP都是和存儲相關的指令。即把寄存器中的數據寫入內存中。ST即爲store的縮寫,R即爲register的縮寫,P即爲pair的縮寫,即同時操作兩個寄存器。
STR & STUR:將寄存器中的數據寫入內存中,即把源寄存器的內容寫入目的寄存器。STR&STUR的區別和LDR&LDUR的區別類似。如下:
STR X0, [sp, #0x28] ; 將X0寄存器中的數據寫入SP + 0x28的位置。
STUR X0, [sp, #-0x10] ; 將X0寄存器中的數據寫入SP - 0x10的位置。
STP:將兩個寄存器的數據寫入內存中。例如:
STP X1, X2, [X0, #0x28]; 將X1和X2的數據 寫入 X0+#0x28的位置
參考文章
1、iOS逆向之ARM64彙編基礎
2、第七章 ARM 反彙編基礎(七)(AArch64 彙編指令集)
3、ARM彙編基本指令
4、ARM學習路線02-ARM指令集、尋址方式