069-Assembly彙編06 (精華)

 

 

 

 

 

 

彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心

彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心
彙編是真的噁心

 

 

 


先來寫一遍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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章