匯編指令
計算機在執行過程時只識別代表0或者1的電信號。因此為了讓計算機能夠執行則須向計算機輸入一系列01構成的指令。
例如在x64平臺下,0x53
,二進制為01010011
,表示將rbx寄存器中的值壓棧。
但是,對于程序員而言,如果每個操作都用二進制數來表示(早期的程序員確實是這樣)則會帶來2個問題。
- 容易出錯
- 不方便記憶
為了避免上述的錯誤,各廠商定義了一系列由英文關鍵字用來表示二進制指令,這些關鍵字構成的指令稱為匯編指令,或者匯編語言。
例如,上述0x53
在x64下的匯編指令為
push %rbx
如果將上述匯編指令寫入匯編文件(.s),經過編譯器(gcc)編譯后,形成了可被執行的二進制文件,該文件中的對應位置將push %rbx
轉換為0x53
。
此時,如果CPU中執行上述指令時,則會將rbx的值壓棧。
總而言之,push %rbx
一定會轉為唯一的二進制指令讓CPU執行。
指令編碼
上述的匯編指令由英文構成,其中開始部分的關鍵字稱為助記符(mnemonic),后面的被操作對象稱為操作數。編譯器將根據助記符和操作數按照一定的規則將其翻譯成唯一的二進制數供CPU執行。
每個廠商由于其指令集的區別(CISC:復雜指令集;RISC:精簡指令集)而有各自的編碼規則。
根據x64的手冊,其push %rbx
的編碼規則如下
push
代表0x50
reg64由%rbx代替,而rbx寄存器中x64架構下的編碼為0x3,因此,將二者相加后最終的編碼結果為0x53
。
匯編轉換規則
根據上述描述可發現,匯編指令轉二進制指令根據的是助記符和操作碼的值進行編制。首先需確定助記符的值,該值在二進制指令中被稱為操作碼,后面緊跟的值是操作數。每個廠商編制了各自的編碼規則。
例如,假設一個寄存器加法指令。將一個寄存器(源寄存器)內的值與另一個寄存器(源寄存器)相加,將結果放入寄存器(目的寄存器)中。我們看看在x64指令下是如何編碼的。
假設加法指令如下
add %rbx, %rax
則翻譯的機器碼為
48 01 D8
其翻譯規則如下
Byte(s) | Meaning |
---|---|
48 | REX prefix: 64-bit operand size (REX.W = 1)| |
01 | Opcode: ADD r/m64, r64 (adds second register to the first) |
D8 | ModR/M byte: specifies registers → rbx to rax |
關于x86的編碼規則,可參見我另一篇文章
AArch64
是ARM v8
架構。其指令屬于RISC
指令集。嵌入式系統,蘋果M系列芯片以及國產飛騰,鯤鵬等服務器芯片等采用該架構的指令集。如果是在AArch64
下實現上述加法, 其寫法如下
add x0, x1, x2
由于AArch64
指令與x86
不是同一指令集,因此寄存器定義也不一致,只是廠商在定義加法指令時都采用了add
助記符。上述指令的意思是將x1
寄存器內的值加上x2
寄存器內的值之后的結果放入x0
寄存器。
AArch64
針對上述指令的編碼如下
由于AArch64
的編碼規則較x86
簡單,這里以AArch64
為例進行說明。
根據手冊上的描述可以看出,對于64位機器,31位是1,Rm是x2,Rn是x1, Rd是x0的編碼。即2,1和0,其他位置按照規范不變,option和imm3規定了其他的行為,這里是單純的寄存器賦值,按照手冊全部為0
因此機器碼為
#二進制
10001011000 00010 000000 00001 00000#十六進制
0x8B020020
#小端編碼需要反轉
0x2000028B