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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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