ARM匯編語言編程

內(nèi)存參考視頻和知乎文章https://zhuanlan.zhihu.com/p/388683540
復(fù)制內(nèi)容到md編輯器應(yīng)該就可讀了。
> 點(diǎn)開頭的是偽指令,例如`.file` `.global`
> 冒號結(jié)尾的是標(biāo)簽,例如`main:`、`_start`
## 寄存器
在ARM64架構(gòu)下,CPU提供了33個寄存器, 其中前31個(0~30)是通用寄存器,最后2個(31,32)是專用寄存器(sp 寄存器和 pc 寄存器)
前面0~30個通用寄存器的訪問方式有2種:
- 當(dāng)將其作為 32bit 寄存器的時候,使用 W0 ~ W30 來引用它們。(數(shù)據(jù)保存在寄存器的低32位)
- 當(dāng)將其作為 64bit 寄存器的時候,使用 X0 ~ X30 來引用它們。
第31個專用寄存器的訪問方式有4種:
- 當(dāng)將其作為 32bit 棧幀指針寄存器(stack pointer) 的時候,使用 WSP 來引用它。
- 當(dāng)將其作為 64bit 棧幀指針寄存器(stack pointer) 的時候,使用 SP 來引用它。
- 當(dāng)將其作為 32bit 零寄存器( zero register )的時候,使用 WZR 來引用它。
- 當(dāng)將其作為 64bit 零寄存器( zero register )的時候,使用 ZR 來引用它。
另外需要注意的,像 FP (X29) ,LR(X30) 寄存器都不能和 SP(x31) 寄存器一樣用名字來訪問,而只能使用數(shù)字索引來訪問它們。
其實(shí)還有第32個專用寄存器,它就是 PC ( x32)寄存器,但是在ARM的匯編文檔里面說明了,你無法在匯編中使用 PC 名稱的方式或者用 X32 數(shù)字索引的訪問它,因?yàn)樗皇墙o匯編用的,而是給CPU執(zhí)行匯編指令時用的,它永遠(yuǎn)記錄著當(dāng)前CPU正在執(zhí)行哪一句指令的地址。
| 寄存器?????| 說明?????????????|
| -------------- | ---------------------------- |
| X0 寄存器???| 用來保存返回值(或傳參)???|
| X1 ~ X7 寄存器 | 用來保存函數(shù)的傳參??????|
| X8寄存器????| 也可以用來保存返回值?????|
| X9 ~ X28寄存器 | 一般寄存器,無特殊用途????|
| x29(FP)寄存器?| 用來保存棧底地址???????|
| X30 (LR)寄存器 | 用來保存返回地址???????|
| X31(SP) 寄存器 | 用來保存棧頂?shù)刂???????|
| X31(ZR)寄存器?| 零寄存器,恒為0???????|
| X32(PC)寄存器?| 用來保存當(dāng)前執(zhí)行的指令的地址 |
## 內(nèi)存布局
一個ARM64的進(jìn)行會擁有一個非常大的虛擬內(nèi)存映射空間,其中又分為兩大塊:
- 內(nèi)核地址(0xffff_ffff_ffff_ffff ~ 0xffff_0000_0000_0000范圍的256TB的尋址空間),
- 用戶地址 (0x0000_ffff_ffff_ffff ~ 0x0000000000000_0000范圍的256TB的尋址空間) 。
這里我們只關(guān)心用戶地址,其中有分為兩大塊:
- 棧內(nèi)存( Stack),從高位向低位生長。
- 堆內(nèi)存 ( Heap ), 從低位向高位生長。
其中我們知道棧內(nèi)存首先是按照線程為單元的,每個線程都有自己的棧內(nèi)存塊,然后每個線程的棧內(nèi)存又可以根據(jù)函數(shù)的調(diào)用層級關(guān)系分為不同的棧幀( Stack Frame )。
## 尋址模式
### 立即數(shù)尋址
將數(shù)據(jù)直接存放的指令中發(fā)給CPU,首先由于ARM的一條指令占了32bit,而操作碼本身也要占據(jù)一些位,所以留給立即數(shù)的位數(shù)肯定不到32bit,其次并不是滿足指定位數(shù)的數(shù)字都是立即數(shù),ARM中的立即數(shù)必須可以通過某個8bit的數(shù)據(jù)經(jīng)過循環(huán)右移得到。
```asm
ADD R0, R0, #1 ;R0 <- (R0 + #1)
MOV R7, #1 ;R7 <- #1
SWI 0
```
### 寄存器直接尋址
將寄存器中的數(shù)據(jù)用作操作數(shù)
```asm
MOV R0, R1 ;R0 <- R1
ADD R0, R1, R2 ;R0 <- (R1 + R2)
```
```asm
.global _start
_start:
LDR R0, =list;=list是list標(biāo)簽第一個元素的內(nèi)存地址,將這個內(nèi)存地址賦值給R0寄存器
.data
list:
.word 4, 5, -5, 1, 0, 2, -3
;以字為元素類型的列表
```
### 寄存器間接尋址
將寄存器中的數(shù)據(jù)作為主存中操作數(shù)的地址,去到相應(yīng)的主存地址取得操作數(shù),用`[R0]`表示將`R0`中的數(shù)據(jù)當(dāng)作操作數(shù)的地址,`[R0]!`表示將`R0`中的數(shù)據(jù)當(dāng)作操作數(shù)的地址并將操作后的結(jié)果地址給`R0`
```asm
LDR R0,[R1]????;將R1指向的數(shù)據(jù)加載到R0中?
STR R0, [R1]!????;將R0存儲的數(shù)據(jù)加載到R1指向的主存地址中,加載完畢R1中為操作后的地址
ADD R0,R1,[R2]
```
```asm
.global _start
_start:
LDR R0, =list;=list是list標(biāo)簽第一個元素的內(nèi)存地址,將這個內(nèi)存地址賦值給R0寄存器
LDR R1, [R0] ;將R0寄存器中數(shù)據(jù)作為內(nèi)存地址,從內(nèi)存中取數(shù)據(jù)賦值給R1
LDR R2, [R0, #4] ;將R0寄存器中數(shù)據(jù)加4作為內(nèi)存地址,從內(nèi)存中取數(shù)據(jù)賦值給R2(R0寄存器中數(shù)據(jù)不變)
LDR R2, [R0, #4]! ;將R0寄存器中數(shù)據(jù)加4作為內(nèi)存地址,從內(nèi)存中取數(shù)據(jù)賦值給R2。(R0寄存器中數(shù)據(jù)也加4),R2 = list[++i]
LDR R2, [R0], #4 ;將R0寄存器中數(shù)據(jù)作為內(nèi)存地址,從內(nèi)存中取數(shù)據(jù)賦值給R2。然后將R0寄存器中值加4, R2 = list[i++]
; LDR R2, [R0, #4]! 與 LDR R2, [R0], #4 的關(guān)系類似于++i與i++
.data
list:
.word 4, 5, -5, 1, 0, 2, -3
;以字為元素類型的列表
```
## 算術(shù)與CPSR標(biāo)志
```asm
.global _start
_start:
MOV R0, #5
MOV R1, #7
ADD R2, R0, R1 ; R2 = R0 + R1
ADDS R2, R0, R1
ADC R2, R0, R1
SUB R2, R0, R1 ; R2 = R0 - R1
SUBS R2, R0, R1 ; R2 = R0 - R1,設(shè)置CPSR
SUBS R2, R1, R0 ; R2 = R1 - R0,設(shè)置CPSR
MUL R2, R0, R1 ; R2 = R0 * R1?
.data
list:
.word 4, 5, -5, 1, 0, 2, -3
```


如圖可以看出,如何解釋寄存器中值是正數(shù)還是負(fù)數(shù)需要額外標(biāo)志位。cpsr寄存器就存儲這些標(biāo)志位。
`SUB`命令是不設(shè)置`CPSR`寄存器的,`SUBS`會設(shè)置`CPSR`寄存器。當(dāng)所得結(jié)果可能是負(fù)數(shù)時應(yīng)該使用`SUBS`。
## 邏輯運(yùn)算與移位
```asm
AND R2, R0, R1
ORR R2, R0, R1
EOR R2, R0, R1
MVN R0, R0
LSL ; 邏輯左移
LSR ; 邏輯右移
ROR ; ROR(循環(huán)右移)
```
`ROR`(循環(huán)右移指令把所有位都向右移,最低位復(fù)制到進(jìn)位標(biāo)志位和最高位。

```asm
MOV R0, #10
MOV R1, R0
LSL R1, #1
; 以上兩句等價于 MOV R1, R0, LSL #1
ROR R0, #1
```
## 條件與分支
假設(shè)現(xiàn)在AX寄存器中的數(shù)是0002H,BX寄存器中的數(shù)是0003H。
執(zhí)行的指令是:CMP?AX,?BX
??執(zhí)行這條指令時,先做用AX中的數(shù)減去BX中的數(shù)的減法運(yùn)算。
列出二進(jìn)制運(yùn)算式子:

所以,運(yùn)算結(jié)果是 0FFFFH
根據(jù)這個結(jié)果,各標(biāo)志位將會被分別設(shè)置成以下值:
?????CF=1,因?yàn)橛薪栉?/p>
?????OF=0,未溢出
?????SF=1,結(jié)果是負(fù)數(shù)
?????ZF=0,結(jié)果不全是零
???還有AF, PF等也會相應(yīng)地被設(shè)置。CMP 比較指令做了減法運(yùn)算以后,根據(jù)運(yùn)算結(jié)果設(shè)置了各個標(biāo)志位。標(biāo)志位設(shè)置過以后,0FFFFH這個減法運(yùn)算的結(jié)果就沒用了,它被丟棄,不保存。執(zhí)行過了CMP指令以后,除了CF,ZF,OF, SF,等各個標(biāo)志位變化外,其它的數(shù)據(jù)不變。
### 控制流指令
```asm
cmp r0, #0
beq LABEL1 ; r0==0那么向前跳轉(zhuǎn)到LABEL1處執(zhí)行
bne LABEL2 ; 否則向后跳轉(zhuǎn)到LABEL2處執(zhí)行
```
無條件轉(zhuǎn)移指令:`BAL`
條件轉(zhuǎn)移指令
```asm
BEQ??相等
BNE??不等
BPL??非負(fù)
BMI??負(fù)
BCC??無進(jìn)位
BCS??有進(jìn)位
BLO??小于(無符號數(shù))
BHS??大于等于(無符號數(shù))
BHI??大于(無符號數(shù))
BLS??小于等于(無符號數(shù))
BVC??無溢出(有符號數(shù))
BVS??有溢出(有符號數(shù))
BGT??大于(有符號數(shù))
BGE??大于等于(有符號數(shù))
BLT??小于(有符號數(shù))
BLE??小于等于(有符號數(shù))
```
## 帶有分支的循環(huán)
```asm
.global _start
.equ endlist, 0xaaaaaaaa
_start:
LDR R0, =list
MOV R1, #0
LDR R2, =endlist
LDR R3, [R0]
ADD R1, R1, R3
loop:
LDR R3, [R0, #4]!
CMP R2, R3
BEQ exit
ADD R1, R1, R3
BAL loop
exit:
.data
list:
.word 1,2,3,4,5,6,7,8,9,10
```
## 條件指令執(zhí)行
```asm
CMP R0, R1
ADDLT R2, R2, #1 ;如果R0小于R1,那么將R2加1
;類似的有MOVEGE R2, #1
```
## 使用LR寄存器進(jìn)行分支并返回
```asm
.global _start
_start:
MOV R0, #1
MOV R1, #2
BL add2??;存儲下一個要執(zhí)行的指令的地址到lr寄存器,并跳轉(zhuǎn)到標(biāo)簽add2
MOV R3, #3
add2:
ADD R2, R0, R1
bx lr?;返回到lr保存的地址繼續(xù)執(zhí)行
```
## 從堆棧內(nèi)存中保存和檢索數(shù)據(jù)
```asm
.global _start
_start:
MOV R0, #1
MOV R1, #2
PUSH {R0, R1}
BL get_value
POP {R0, R1}
BAL exit
get_value:
MOV R0, #5
MOV R1, #7
ADD R2, R0, R1
BX lr
exit:
```