昨天有提到,ELF 裡面的:
.text
主要存放 CPU 真正會執行的 Machine Code
但 Machine Code 對人來說其實很難直接閱讀,例如:
55 48 89 e5 b8 00 00 00 00 5d c3
所以通常會把 Machine Code 轉換成比較容易閱讀的形式,也就是 Assembly
今天就來簡單看看 Assembly 到底在寫什麼
Assembly Language 是一種很接近 Machine Code 的 Low-Level Language
例如:
mov eax, 10
add eax, 20
CPU 真正執行的並不是 mov 或 add 這些文字,而是對應的 Machine Code
Assembly 只是讓人比較容易理解這些 Instruction
例如可以使用:
objdump -d hello
把 ELF 裡的 Machine Code Disassemble 出來
如果想用 Intel Syntax:
objdump -d -M intel hello
之後的文章基本上也會以 Intel Syntax 為主
Assembly 裡面很常看到:
rax
rbx
rcx
rdx
rsi
rdi
rsp
rbp
rip
這些東西叫做:
Register
可以先簡單理解成:
CPU 裡面很小但很快的儲存空間
例如:
mov eax, 10
代表把:
10
放進 eax。
又例如:
add eax, 20
代表:
eax = eax + 20
所以如果:
mov eax, 10
add eax, 20
最後:
eax = 30
有時候會看到:
rax
eax
ax
al
它們其實不是完全不同的 Register,而是同一組 Register 的不同大小。
可以簡單記成:
RAX → 64-bit
EAX → 32-bit
AX → 16-bit
AL → 8-bit
例如:
mov eax, 10
就是操作 RAX 的低 32-bit。
這種命名之後會很常遇到,例如:
RBX / EBX / BX / BL
RCX / ECX / CX / CL
RDX / EDX / DX / DL
先看幾個最基本的。
mov eax, 10
可以理解成:
eax = 10
或:
mov eax, ebx
代表:
eax = ebx
add eax, 10
代表:
eax = eax + 10
sub eax, 10
代表:
eax = eax - 10
ret
通常代表目前的 Function 執行結束,回到原本呼叫它的位置
所以如果看到:
mov eax, 10
ret
可以先理解成這個 Function 最後留下了一個 10
在 Linux x86-64 中,Function 的前幾個 Argument 通常會依序放在:
RDI
RSI
RDX
RCX
R8
R9
而 Return Value 通常會放在:
RAX
例如:
int add(int a, int b) {
return a + b;
}
Compile 之後可能看到:
add:
lea eax, [rdi+rsi]
ret
現在先不用管 lea 的細節。
只要知道:
RDI → 第一個 Argument,也就是 a
RSI → 第二個 Argument,也就是 b
RAX → Return Value
就已經可以大概猜出:
eax = edi + esi
也就是:
return a + b;
之後也可以自己嘗試看看 Compile Assembly 的部分
今天可以先知道Assembly 是 Machine Code 比較容易讓人閱讀的表示方式
而 Assembly 裡面會大量操作:
Register
目前可以先記得:
RDI → 第一個 Argument
RSI → 第二個 Argument
RDX → 第三個 Argument
RAX → Return Value
以及幾個基本 Instruction:
mov
add
sub
ret
現在已經可以開始閱讀一些非常簡單的 Assembly。
但如果程式裡面出現:
if
else
for
while
它們又會變成什麼樣子?
明天就來看看:Assembly 裡的條件判斷與跳轉是怎麼做的?