彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
先來寫一遍64位的hello world代碼
section .data
text db "Hello World",10H
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, text
mov rdx, 12
syscall
mov rax,60
mov rdi,0
syscall
再寫一遍
section .data
msg db "Hello World", 10H
section .text
global _start
_start:
mov rax,1
mov rdi,1
mov rsi, msg
mov rdx,12
syscall
mov rax,60
mov rdi,0
syscall
有些人會寫成這樣
section .data
text db "Hello World",10H
section .text
global _start
_start:
mov rax,1
mov rdi,1
mov rsi,text
mov rdx,12
syscall
mov rax,60
xor rdi,rdi
syscall
其他都一樣,只有mov rdi,0
改成了 xor rdi,rdi
xor的意思是異或,也就是說,如果rdi和rdi相同的話,就是0
rdi和rdi肯定相同,所以就是0
所以
xor rdi,rdi 和 mov rdi,0
其實是一樣的
1.現在我們先來看一下
text db "Hello World",10H
或者
msg db "Hello World",10H
這兩個是一樣的
text和msg就是一個名字
可以改成其他的,比如abc,aaa,zzz都沒關係
那麼db是什麼意思
db是define bytes的意思
就是我們將要定義一些bytes字節數據
所以"Hello World"全都被看做事字節數據
H是一個字節,e是一個字節,l是一個字節
然後後面還有一個逗號,10H
逗號,是連接的意思,也就是在Hello World後面連接一個10H,
所以10H也是被看做一個字節,那麼10H是什麼呢
我們可以看一下ASCII,
10H是 換行符號,所以連起來就是
Hello World 加 換行符號
現在
msg db "Hello World",10H
這句我們已經搞懂了
2.Register寄存器
寄存器是CPU的一部分
在x86_64架構中,寄存器是64位
那麼每一個寄存器可以放64位,也就是8個字節,也就是8個單元
那麼範圍就是
0到2^64
如果帶符號的話,第1位表示符號,範圍是
-2^32 到 2^32
3.寄存器
16位寄存器有
ax,bx,cx,dx,si,di,bp,sp
32位寄存器有:
eax,ebx,ecx,edx,esi,edi,ebp,esp
看一下
16位 32位 64位
ax eax rax
bx ebx rbx
cx ecx rcx
dx edx rdx
si esi rsi
di edi rdi
bp ebp rbp
sp esp rsp
4.System Call
剛剛代碼裏有syscall,意思就是SystemCall
syscall就是程序調用內核的服務
不同的操作系統的內核是不同的,所以也會使用不同的syscall的方法
每一個syscall都有一個對應的ID(數字)
syscall也需要參數,輸入等等
那麼syscall怎麼知道ID和參數呢
我們看一下一個表格
參數 寄存器
ID rax
1 rdi
2 rsi
3 rdx
4 r10
5 r8
6 r9
這個表格的意思就是說
syscall調用的時候,會從rax裏面取出一個值,那麼這個值就是對應的執行ID,或者說功能ID,
然後從rdi裏面取出一個值,作爲第一個參數
rsi中取出一個值,作爲第二個參數
以此類推
5.syscall的ID
剛剛說了要取出ID,指代不同的功能,
有哪些ID呢
看下錶格
syscall ID 參數1 參數2 參數3
sys_read 0 filedescriptor buffer count
sys_write 1 filedescriptor buffer count
sys_open 2 filename flag mode
sys_close 3 filedescriptor
0表示 讀
1表示 寫
2表示 打開
3表示 關閉
6.sys_write
我們看下 sys_write 也就是寫
1.filedescriptor 0(標準輸入)
1(標準輸出)
2(錯誤)
2.buffer 要寫的string的位置
3.count string的長度
三個參數
我們看一下hello world 代碼
section .data
text db "Hello World",10H
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, text
mov rdx, 12
syscall
mov rax,60
mov rdi,0
syscall
rax是1,也就是sys_write,寫
rdi是1,那麼filedescriptor是標準輸出
rsi是buffer
rdx是長度
mov rax,1
mov rdi,1
mov rsi,text
mov rdx,12
現在這4行看懂了
就是剛剛syscall需要的
1.ID
2.參數一
3.參數二
4.參數三
7.exit退出
再來看一下我們的hello world
section .data
text db "Hello World",10H
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, text
mov rdx, 12
syscall
mov rax,60
mov rdi,0
syscall
上半部分全都清楚了,只剩下面的
mov rax,60
mov rdi,0
syscall
那麼這裏我們發現其實也是一次syscall
按照syscall的要求
1.ID rax
2.參數一 rdi
3.參數二 rsi
4.參數三 rdx
這次rax是60, rdi是0
所以
1.ID rax 是60
2.參數一 rdi 是0
https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
這個網站是所有的syscall的ID
我們可以在表格中找一下ID60
ID rdi rsi
ID60 sys_exit int error_code
原來60是sys_exit
然後rsi是error_code,也就是錯誤代碼
到此爲止
我們的Hello World就全部搞清楚了
section .data
text db "Hello World",10H
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, text
mov rdx, 12
syscall
mov rax,60
mov rdi,0
syscall
1.
再看一下section
這裏出現了
section .data
和
section .text
每個64位彙編文件都有3個section,分別
1.section .data
2.section .text
3.section .bss
.data裏面的數據都是編譯前的數據,比如"Hello World",是編譯前的數據
.text是彙編代碼的部分
.bss是未來可能要用的部分
2._start:
這裏還出現了 _start:
這是一個label標籤,用來隔開一部分的代碼
3.Flags
Flags就像寄存器一樣,用來保存數據
但是flag只有1位,所以只是用來表示true和false的
每一個flag都是一個寄存器的一部分
看下錶格
flag symbol description
CF Carry
PF Parity
ZF Zero
SF Sign
OF Overflow
AF Adjust
IF Interrupt Enabled
4.Pointer 指針寄存器
指針寄存器保存了數據的內存地址
1. rip
64位是rip,32位是eip,16位是ip
意思是Index pointer 索引指針寄存器
2.rsp (rsp,esp,sp)
stack pointer
棧指針寄存器
3.rbp (rbp,ebp,bp)
棧基指針寄存器
5. rip 索引指針寄存器
我們的彙編代碼是從上到下來執行,那麼這個從上到下執行的流 叫做
control flow 控制流
rip寄存器保存的是下一個指令的地址
比如這樣
假設第一行指令的地址是 x
那麼rip保存的就是x, rip=x
然後執行一行, rip就+1
但這裏的+1只是一個大致的概念,可能有些代碼執行完,rip是+2或+3或+4等等
比如
mov rax,1
mov rdi,1
mov rsi, text
mov rdx,14
這幾行代碼所需要的字節數,所改變的字節數都是不一樣的,所以rip加幾都是不一定的
我們只要知道
control flow控制流和rip的這些概念就行了
6. Jump跳轉
我們可以用Jump跳到不同的label
比如
_start:
add rax,1
jmp _start
這三行代碼的意思就是
rax=rax+1 自增1
然後jmp _start又跳轉到了_start
所以又執行
所以就是一個無限循環
7.Comparisons 比較
cmp rax,10
cmp rax,rbx
比較兩個
這裏就要牽扯到剛剛說的flag了
如果
a=b 那麼 ZF=1
a!=b 那麼 ZF=0
剛剛說的ZF是Zero Flag
然後cmp還要和jump做一下結合
看下錶格
標誌 標誌 cmp比較
je - a=b
jne - a!=b
jg ja a>b
jge jae a>=b
jl jb a<b
jle jbe a<=b
jz - a=0
jnz - a!=0
舉個例子
cmp rax,23
je _doThis
這2行代碼的意思就是,比較rax和23
如果相等,那麼je _doThis
跳轉到 _doThis
com rbx,100
jg _doThis
如果 rbx大於100,那麼跳轉到_doThis
很簡單
je 相等
jne 不相等
jg 大於
jl 小於
8.
默認的寄存器可以被用作 指針寄存器
只要加上[]就行了,比如[rbx]
比如
mov rax,rbx
這樣是把rbx的值給到rax
mov rax, [rbx]
這樣是把rbx這個地址的值給rax
9.Call
Call就像高級語言的方法一樣
比如剛剛的hello world代碼
可以這樣
section .data
text db "Hello World",10H
section .text
global _start
_start:
call _printHello
call _printHello
call _printHello
mov rax,60
mov rdi,0
syscall
_printHello:
mov rax,1
mov rdi,1
mov rsi,text
mov rdx,12
syscall
ret
這樣就相當於把輸出Hello World封裝成了一個_printHello方法
然後連續調用了_printHello三次
10.鍵盤輸入
section .data
text1 db "What is your name? "
text2 db "Hello, "
section .bss
name resb 16
section .text
global _start
_start:
call _printText1
call _getName
call _printText2
call _printName
mov rax,60
mov rdi,0
syscall
_getName:
mov rax,0
mov rdi,0
mov rsi,name
mov rdx,16
syscall
ret
_printText1:
mov rax,1
mov rdi,1
mov rsi,text1
mov rdx,19
syscall
ret
_printText2:
mov rax,1
mov rdi,1
mov rsi,text2
mov rdx,7
syscall
ret
_printName:
mov rax,1
mov rdi,1
mov rsi,name
mov rdx,16
syscall
ret
其他的都是輸入,和hello world一模一樣
只有_getName方法不一樣
_getName:
mov rax,0
mov rdi,0
mov rsi,name
mov rdx,16
syscall
ret
所以想要獲得輸入
mov rax,0
mov rdi,0
mov rsi,name
mov rdx,16
syscall
ret
11.數學操作
add rax,5 rax=rax+5
sub rbx,rax rbx=rbx+rax
操作有
1.add a=a+b
2.sub a=a-b
3.mul rax=rax*reg
4.div rax=rax/reg
5.neg reg=-reg
6.inc reg=reg+1
7.dec reg=reg-1
12.展示數字
section .data
digit db 0,10H
section .text
mov rax,0
call _printDigit
mov rax,1
call _printDigit
mov rax,2
call _printDigit
mov rax,3
call _printDigit
mov rax,60
mov rbx,0
syscall
_printDigit:
add rax,48
mov [digit],al
mov rax,1
mov rdi,1
mov rsi,digit
mov rdx,2
syscall
ret
看下這段代碼
rax=rax+48
然後
[digit]=al
意思是把al的值移動到digit的地址的位置
然後再輸出一下digit
我們的
digit db 0,10H
是一個0字符再加上一個10H,也就是換行符號
那麼一共是2個字節
這裏al是8位的,所以只有一個字節
所以mov [digit],al
實際上只改變了第一個字節,也就是0字符
所以這幾次rax賦值之後,再調用方法,
就輸出了
0123
13.展示字母
其實和展示數字是一模一樣的
section .data
digit db 0,10H
section .text
mov rax,0
call _printChar
mov rax,1
call _printChar
mov rax,2
call _printChar
mov rax,3
call _printChar
mov rax,60
mov rbx,0
syscall
_printDigit:
add rax,65
mov [digit],al
mov rax,1
mov rdi,1
mov rsi,digit
mov rdx,2
syscall
ret
rax=rax+65
然後rax分別是0,1,2,3
那麼最後輸出的就是65,66,67,68
也就是
ABCD
14.棧
操作
1.push 入棧
2.pop 出棧
3.mov reg, [rsp] 把棧內最頂部的值放在reg裏面
例子
push 3
push 4
push 5
pop rax
call _printDigit
pop rax
call _printDigit
pop rax
call _printDigit
這裏我們push了3個,3,4,5
那麼按照棧的特性,先進後出
也就是說
3在底部,4在中間,5在頂部
那麼第一次
pop rax
rax就是5
第二次
pop rax
rax 是4
15.計算string的長度
之前,我們是自己數出來Hello World的長度再進行輸出
那麼如果這個string很長,我們也數的話就很麻煩
所以我們要計算string的長度,自動輸出
section .data
text db "Hello World",10,0
section .text
global _start
_start:
mov rax,text
call _print
mov rax,60
mov rdi,0
syscall
_print:
push rax
mov rbx,0
_printLoop:
inc rax
inc rbx
mov cl,[rax]
cmp cl,0
jne _printLoop
mov rax,1
mov rdi,1
pop rsi
mov rdx,rbx
syscall
ret
解釋一下
先是move rax,text
那麼現在rax就是text的地址值
先把rax的值存到棧裏面去,然後rbx=0
然後進入_printLoop
先讓rax和rbx自增
然後mov cl,[rax]
把rax地址的東西給cl,這裏是8位,1個字節,因爲我們只比較第一個字節
然後比較cl和0,因爲我們的最後一個字符是0
cmp cl,0
如果不相等的話,就再次進入_printLoop
相等的話,就說明這個時候,rax作爲指針,指向了0字符
那麼rbx也就是我們的長度了
然後
mov rax,1
mov rdi,1
pop rsi
mov rdx,rbx
syscall
ret
正常打印
複習一次
section .data
text db "Hello World",10,0
text2 db "aaabbbccc",10,0
section .text
global _start
_start:
mov rax,text
call _print
mov rax,text2
call _print
mov rax,60
mov rdi,0
syscall
_print:
push rax
mov rbx,0
_printLoop:
inc rax
inc rbx
mov cl,[rax]
cmp cl,0
jne _printLoop
mov rax,1
mov rdi,1
pop rsi
mov rdx,rbx
syscall
ret