前言
汇编是很多课程的重要基础,正好最近在学习操作系统,发现在操作系统的学习中,汇编也表现得极为重要,同样在逆向的学习中,不会汇编基本等于与逆向无门了。
此篇是个人笔记,供自己复习和做笔记。
参考资料:《汇编语言(第3版) 》王爽著
基础知识
进制转换和数据宽度
16进制:
0123456789ABCDEF
10 11 12 13 14 15 16 17 18 19 20 21….
One single hexadecimal digit = 4 bit
8进制:
01234567
10 11 12 13 14 15
One single Octal digit = 3 bit
数据宽度:
1 Byte = 8 Bit
1 Word = 2 Bytes =》四个十六进制数字为一个word
Double Word = 2 Word
需要记住的一些数字:
1024=2^10
1KB=1024B(Byte)
1MB=1024KB
1GB=1024MB
1TB=1024GB
CPU对存储器的读写
比如CPU要从内存地址3中读写数据:
读
- CPU通过地址总线将地址信息3发出。
- CPU通过控制总线发出读内存的命令,选中存储器的芯片,通知它我要读取数据。
- 存储器将地址3中的数据通过数据线送入CPU
地址总线
地址总线是一根导线,我们知道一根导线传输数据的时候只有两种稳定状态 - 要么是高电平要么是低电平 - 二进制表示就是0和1。
CPU用地址总线来寻址,有多少不同的信息,CPU就可以对多少个存储单元(一个存储单元里有1byte的信息)寻址
所以如果现一CPU有10根导线,那么它可以发出10 bit的二进制数,而10bit的二进制数可以表示2^10=1024个不同的数据,也就是可以寻找2^10=1024个内存单元。
上图演示的是将地址1011通过地址总线发出
数据总线
CPU和其他器件之间的数据传送是通过数据总线进行的。数据总线的宽度决定了CPU和外界数据传送的速度。
一个数据总线可以传送一个bit(位)的数据,8根数据总线可以传送一个byte的数据
假设你有16根数据线,那么你一次性可以传输16bit的数据 - 所以你可以传输89D8H - 因为89D8是十六进制,一个十六进制数字为4bit,所以89D8刚好16bit. 如果数据线只有八根,意味着你只能传输8bit的数据,那么你只能分两次传送:先D8后89(从低位开始传)
控制总线
CPU对外部器件的控制是通过控制总线来进行的。此处控制总线是一个总称,它有多少根控制总线,意味着CPU提供了对外部器件的多少种控制。所以控制总线的宽度决定了CPU对外部器件的控制能力。
比如读和写就分别为两条控制总线。
课后习题 1.1
1 | (1) 该CPU的寻址能力为8Kb,换算成Byte为8*1024Byte。 我们知道一个内存单元可以储存1个Byte,那么意味着该CPU可以寻址8*1024=2^13个内存单元。 |
寄存器
通用寄存器
CPU由运算器、控制器、寄存器构成。
- 运算器进行信息处理
- 寄存器进行信息存储
- 控制器控制各种器件进行工作
- 内部总线连接各种器件,在它们之间进行数据的传送
16位寄存器:
AX
BX
CX
DX
8位寄存器:会被单独使用,比如AH溢出后,不会把溢出的位补到AX前面的高位(AH)去
H表示high,L表示low
AH AL
BH BL
CH CL
DH DL
几条汇编指令
mov 寄存器,数据 mov ax,2
mov 寄存器,寄存器 mov ax,bx
mov 寄存器,内存单元 mov ax,[0]
mov 内存单元,寄存器 mov [0], ax
mov 段寄存器,寄存器 mov ds,ax
mov 寄存器,段寄存器 mov ax,ds
课后习题2.1
(1)
1 | mov ax,62627 AX=F4A3H |
(2)用目前学过的汇编指令,最多使用四条指令,计算2的4次方
1 | mov ax,0002 |
物理地址、段和偏移地址
8086中,地址总线有20根,但是数据总线只有16根。所以需要用两个16进制表示20进制的地址。段地址(16位)×16+偏移地址(16位)=地址(20位)
地址加法器处理后可以得到物理地址
物理地址=段地址*16+偏移地址
短地址*16
表示十六进制左移1位,也就是二进制左移4位比如十进制20要左移一位:20*10=200
二进制10B左移一位:100B转换成十进制2*2=4
一个数据的二进制形式左移1位,相当于该数据×2
一个数据的二进制形式左移N位,相当于该数据×2^N
偏移地址16位,最多可以寻址64KB(2^16bit个内存单元,一个内存单元8bit,所以2^16*2^3=2^19bit=2^6KB=64KB)
课后习题2.2
1 | (1)给定段地址位0001H,仅通过变化偏移地址寻址,CPU的寻址范围为? |
段寄存器
8086CPU有四个段寄存器:
CS(code segment), DS(data segment), SS(stack segment), ES(extra segment)
CS和IP(instructor pointer指令指针寄存器)是最关键寄存器 -》指向了CPU当前要读的地址
8086CPU工作过程:
- 从CS:IP指向的内存单元获取指令
- IP=IP+所读取指令的长度,从而指向下一条按指令
- 执行指令,回到第一步,重复这个过程
修改CS IP的指令不用mov
指令(因为不支持把数据直接送入段寄存器)而是用jmp
- 同时修改CS 和 IP:
- jmp CS: IP
- 如 jmp 2AE3:3
- CS=2AE3H, IP=0003H -> 2AE30+0003=2AE33H
- jmp 3:0B16
- CS=0003H, IP =0B16H -> 0030+0B16=0B46H
- 只修改IP,当前CS不变
- jmp IP
课后习题 2.3
下列3条指令执行后,CPU几次修改IP?都是在什么时候?最后IP中的值是多少?
1 | mov ax,bx#读取指令后IP被修改 |
1 | mount c: d:\asm |
寄存器(内存访问)
内存中字的存储
一个内存单元是一个byte(字节)
一个字单元由两个内存单元组成,也就是两个字节(bytes)=1个word
0地址: 2E
1地址:3C
问0地址单元存储的**字型(word)**数据是:3C2EH
0地址单元存储的**字节型(byte)**数据是:2EH
DS和[address]
要给ds寄存器赋值,必须要先把值赋给通用寄存器,然后通用寄存器再赋值给ds寄存器。数据不能直接送进段寄存器。
[]
是对数据段操作
例如:从10000H中读取数据
1 | mov bx,1000H#为什么是1000H,因为1000H段地址*16=左移一位,然后加上偏移地址就可以得到最后的真实地址 |
字的传送
ax是一个word的长度,所以这里mov ax,[0]其实取的是1123而不是23
11是高位,22是低位,我们取了连续的两个内存单元而不是一个内存单元
1 | sub ax,1CH#ax=ax-1CH |
mov, add, sub指令
mov ds, ax和mov ax,ds都可以实现。
段寄存器也可以给通用寄存器传数据
数据段
我们用123B0H~123B9H这段空间来存放数据:
- 段地址:123BH
- 长度:10字节(一个地址指向一个内存单元,一个内存单元等于一个字节,0~9有10个内存单元,所以有10个字节)
高位字节存放在高位内存
低位字节存在低位内存
栈
由高地址向低地址增长
CS:IP指向指令
DS:IP指向数据
SS栈段寄存器(Stack segment) - 存放栈顶的段地址
SP寄存器(Stack pointer) - 存放栈顶的偏移地址
SS:SP指向栈顶元素,但如果stack为空的时候,会指向最高地址单元的下一个单元,比如栈空间是10000H~1000FH,如果stack为空,会指向1000:10(所以不存在栈顶元素)
- 也可以说是最后一个元素被pop, SP=E(最后一个元素所在栈地址)+2=10
push ax
先SP = SP-2(因为ax是2个bytes = word,所以是2个内存空间)
把ax放进SS:SP指向的位置
pop ax
先把SS:SP指向位置的内容pop并赋值给ax
SP = SP+2
pop的时候其实没有删除数据,只是指针改变了(所以被pop的数据就不在栈里了,因为它的地址不在栈顶),下次push的时候原本的东西就被覆盖了。
C语言里面的函数其实就是用到了栈,函数会把局部变量什么的都放在stack里,当函数返回后,stack就没有了。
栈顶越界的问题
8086CPU只知道:
- 当前栈顶在何处
- 当前要执行的指令是哪一条
要防止push和pop导致栈顶超界,根据可能用到的最大栈空间,来安排栈的大小 - 防止push的数据太多而导致超界、栈空的时候继续pop导致超界。
push、pop指令
1 | push 寄存器 |
上图演示了将2266H放入10000H的位置。因为push会先减去sp,所以我们要先把sp+2
栈段
再次强调:
SS:SP指向栈顶元素,但如果stack为空的时候,会指向最高地址单元的下一个单元,比如栈空间是10000H~1000FH,如果stack为空,会指向1000:10(所以不存在栈顶元素)
- 也可以说是最后一个元素被pop, SP=E(最后一个元素所在栈地址)+2=10
第一个程序
伪指令是让编译器去处理
汇编指令是
1 | assume cs:codesg #假设代码代码段叫做codesg |
程序返回:
1 | mov ax,4c00H |
CMD将程序载入内存,将CPU的CS:IP指向程序,程序运行结束后回到command,CPU继续运行command