8086汇编
Last Update:
仅作自己学习参考,内容仅记录自己认为比较重要的,对于知识体系来说不全
放在博客这格式就有点不对劲,代码块明明设置的assembly,但是显示的是text,高亮也显示不了,也许是这个主题的问题,但本地显示都没问题,不管了
本文参考(40条消息) 王爽《汇编语言》笔记(详细)_汇编语言笔记_洋葱汪的博客-CSDN博客
一.基础知识
1.指令
机器指令:CPU能直接识别并执行的二进制编码
汇编指令:汇编指令是机器指令的助记符,同机器指令一一对应。
指令:指令通常由操作码和地址码(操作数)两部分组成
指令集:每种CPU都有自己的汇编指令集。
编译器:够将汇编指令转换成机器指令的翻译程序每一种CPU都有自己的汇编指令集。
2.存储器
随机存储器(RAM)在程序的执行过程中可读可写,必须带电存储
只读存储器(ROM)在程序的执行过程中只读,关机数据不丢失
3.总线
总线是连接各个部件的信息传输线,是各个部件共享的传输介质。
根据传送信息的不同,系统总线从逻辑上分为3类,地址总线、控制总线和数据总线。
二.寄存器
1.寄存器
CPU由运算器、控制器、寄存器等器件构成,这些器件靠片内总线相连。
运算器进行信息处理;控制器控制各种器件进行工作;寄存器进行信息存储;
8086CPU有14个寄存器:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW都是16位
8086采用小端模式:高地址存放高位字节,低地址存放低位字节
2.通用寄存器
通常用来存放一般性的数据,有AX、BX、CX、DX,它们可分为两个可独立使用的8位寄存器
16位 | 高八位 | 低八位 |
---|---|---|
AX | AH | AL |
BX | BH | BL |
CX | CH | CL |
DX | DH | DL |
在进行数据传送或运算时,要注意指令的两个操作对象的位数应当是一致的
一个8位寄存器所能存储的数据范围是0 ~ 28-1。
3.8086CPU给出物理地址的方法
8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。
当8086CPU要读写内存时:
CPU中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址;
地址加法器将两个16位地址合成为一个20位的物理地址;
地址加法器采用物理地址 = 段地址×16 + 偏移地址的方法用段地址和偏移地址合成物理地址。
例如,8086CPU要访问地址为123C8H的内存单元,1230H左移一位(空出4位)加上00C8H合成123C8H
4.段寄存器
我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元,可以用分段的方式来管理内存。
用一个段存放数据,将它定义为“数据段”;
用一个段存放代码,将它定义为“代码段”;
用一个段当作栈,将它定义为“栈段”。
注意:
一个段的起始地址一定是16的倍数;
偏移地址为16位,变化范围为0-FFFFH,所以一个段的长度最大为64KB。
CPU可以用不同的段地址和偏移地址形成同一个物理地址。
段寄存器:8086CPU有4个段寄存器:CS、DS、SS、ES,提供内存单元的段地址。
1.CS和IP
CS为代码段寄存器,IP为指令指针寄存器,
CPU将CS、IP中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,
CPU将CS:IP指向的内容当作指令执行。(即PC)
8086CPU的工作过程简要描述
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
- IP=IP+所读取指令的长度,从而指向下一条指令;
- 执行指令。转到步骤1,重复这个过程
2.DS 和 [address]
DS寄存器:通常用来存放要访问数据的段地址
[address]表示一个偏移地址为address的内存单元,段地址默认放在ds中
通过数据段段地址和偏移地址即可定位内存单元。
3.SS 和 SP
在基于8086CPU编程的时候,可以将一段内存当作栈来使用。
栈段寄存器SS,存放段地址,SP寄存器存放偏移地址,任意时刻,SS:SP指向栈顶元素
8086CPU中,入栈时,栈顶从高地址向低地址方向增长。
push ax表示将寄存器ax中的数据送入栈中,由两步完成。
SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。
pop ax表示从栈顶取出数据送入ax,由以下两步完成。
- 将SS:SP指向的内存单元处的数据送入ax中;
- SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
一看到这就想到脱壳了……
三.第一个程序
汇编程序从写出到执行的过程:
加载后,CPU的CS:IP指向程序的第一条指令(即程序的入口)
四.[bx] 和 loop指令
1.[bx] 和 loop指令
[bx] 的含义:[bx]同样表示一个内存单元,它的偏移地址在bx中,段地址默认在ds中
loop指令的格式是:loop 标号,CPU执行loop指令的时候,要进行两步操作:
(cx) = (cx) - 1;
判断 cx 中的值,不为零则转至标号处执行程序,如果为零则向下执行。
例如:计算2的12次:
1 |
|
loop 和 [bx] 的联合应用
计算ffff:0 ~ ffff:b单元中的数据的和,结果存储在dx中
问题分析:
这些内存单元都是字节型数据范围0 ~ 255 ,12个字节数据和不会超过65535,dx可以存下
对于8位数据不能直接加到 dx
解决方案:
用一个16位寄存器来做中介。将内存单元中的8位数据赋值到一个16位寄存器a中,再将ax中的数据加到dx
1 |
|
2.段前缀
1 |
|
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址
的“ds:”,“cs:”,“ss:”,“es:”,在汇编语言中称为段前缀。
3.段前缀的使用
将内存ffff:0 ~ ffff:b
单元中的数据复制到0:200 ~ 0:20b
单元中。
1 |
|
五.包含多个段的程序
在代码段中使用数据
1 |
|
在代码段中使用栈
1 |
|
六.更灵活的定位内存地址的方法
1.and 和 or
and指令:逻辑与指令,按位进行与运算。
mov al, 01100011B
and al, 00111011B
执行后:al=00100011B即都为1才为1
or指令:逻辑或指令,按位进行或运算。
mov al, 01100011B
or al, 00111011B
执行后:al=01111011B 即只要有一个为1就为1
2.[bx+idata]
[bx+idata]表示一个内存单元, 例如:mov ax, [bx+200]
该指令也可以写成如下格式:
1 |
|
用[bx+idata]的方式进行数组的处理
1 |
|
用c语言表示:
1 |
|
3.SI 、DI 与 寻址方式的灵活应用
1.si 、di
si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用。
1 |
|
2.[bx + si] 和 [bx + di]
[bx+si]和[bx+di]的含义相似
[bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)
指令mov ax, [bx + si]的含义:将一个内存单元字数据的内容送入ax,段地址在ds中
该指令也可以写成如下格式:mov ax, [bx] [si]
3.[bx+si+idata]和[bx+di+idata]
[bx+si+idata]表示一个内存单元,它的偏移地址为(bx)+(si)+idata
指令mov ax,[bx+si+idata]的含义:将一个内存单元字数据的内容送入ax,段地址在ds中
4.不同的寻址方式的灵活应用
- [idata]用一个常量来表示地址,可用于直接定位一个内存单元;
- [bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
- [bx+idata]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
- [bx+si]用两个变量表示地址;
- [bx+si+idata]用两个变量和一个常量表示地址。
1 |
|
七、数据处理的两个基本问题
1. bx、si、di和bp
在8086CPU中,只有这4个寄存器可以用在“[…]”中来进行内存单元的寻址。
在[ ]中,这4个寄存器可以单个出现,或只能以4种组合出现:bx和si、bx和di、bp和si、bp和di。
只要在[……]中使用寄存器bp,而指令中没有显性地给出段地址, 段地址就默认在ss中
2.机器指令处理的数据在什么地方
数据处理大致可分为3类:读取、写入、运算。
在机器指令这一层来讲,并不关心数据的值是多少,而关心指令执行前一刻,它将要处理的数据所在的位置。指令在执行前,所要处理的数据可以在3个地方:CPU内部、内存、端口
3.汇编语言中数据位置的表达
汇编语言中用3个概念来表达数据的位置
- 立即数(idata)
1 |
|
- 寄存器
1 |
|
- 段地址(SA)和偏移地址(EA)
1 |
|
寻址方式
4.指令要处理的数据有多长
8086CPU的指令,可以处理两种尺寸的数据,byte和word
- 通过寄存器名指明要处理的数据的尺寸。
例如: mov al, ds:[0] 寄存器al指明了数据为1字节 - 在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令中可以为word或byte。
例如:mov byte ptr ds:[0], 1 byte ptr 指明了指令访问的内存单元是一个字节单元 - 有些指令默认了访问的是字单元还是字节单元
例如,push [1000H],push 指令只进行字操作。
5.寻址方式的综合应用
eg:
1 |
|
C语言描述
1 |
|
6.div指令、dd、dup、mul指令
div是除法指令
除数:有8位和16位两种,在一个寄存器或内存单元中。
被除数:默认放在AX或DX和AX中,
如果除数为8位,被除数则为16位,默认在AX中存放;
如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。
结果:
如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;
如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。
1 |
|
伪指令dd
db和dw定义字节型数据和字型数据。
dd是用来定义dword(double word,双字)型数据的伪指令
操作符dup
dup在汇编语言中同db、dw、dd等一样,也是由编译器识别处理的符号。
它和db、dw、dd等数据定义伪指令配合使用,用来进行数据的重复
1 |
|
mul 指令
mul是乘法指令,使用 mul 做乘法的时候:相乘的两个数:要么都是8位,要么都是16位。
8 位: AL中和 8位寄存器或内存字节单元中;
16 位: AX中和 16 位寄存器或内存字单元中。
结果
8位:AX中;
16位:DX(高位)和 AX(低位)中。
格式:==mul 寄存器== 或 ==mul 内存单元==
1 |
|
1 |
|
八、转移指令的原理
可以修改IP,或同时修改CS和IP的指令统称为转移指令。概括地讲,转移指令就是可以控制CPU执行内存中某处代码的指令。
8086CPU的转移行为有以下几类。
只修改IP时,称为==段内转移==,比如:==jmp ax==。
同时修改CS和IP时,称为==段间转移==,比如:==jmp 1000:0==。
由于转移指令对IP的修改范围不同,段内转移又分为:==短转移和近转移==
短转移IP的修改范围为==-128 ~ 127==。
近转移IP的修改范围为==-32768 ~ 32767==。
8086CPU的转移指令分为以下几类
- 无条件转移指令(如:jmp)
- 条件转移指令
- 循环指令(如:loop)
- 过程
- 中断
1.操作符offset
操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址
1 |
|
2.jmp指令
jmp为无条件转移,转到标号处执行指令可以只修改IP,也可以同时修改CS和IP;
jmp指令要给出两种信息:
- 转移的目的地址
- 转移的距离(段间转移、段内短转移,段内近转移)
jmp short 标号, jmp near ptr 标号, jcxz 标号, loop 标号 等几种汇编指令,它们对 IP的修改是根据转移目的地址和转移起始地址之间的位移来进行的。在它们对应的机器码中不包含转移的目的地址,而包含的是到目的地址的位移距离。
依据位移进行转移的jmp指令
jmp short 标号==(段内短转移)==
指令“jmp short 标号”的功能为(IP)=(IP)+8位位移,转到标号处执行指令
(1)8位位移 = “标号”处的地址 - jmp指令后的第一个字节的地址;
(2)short指明此处的位移为8位位移;
(3)8位位移的范围为-128~127,用补码表示
(4)8位位移由编译程序在编译时算出。
1 |
|
CPU不需要这个目的地址就可以实现对IP的修改。这里是依据==位移进行转移==
jmp short s指令的读取和执行过程:
(CS)=0BBDH,(IP)=0006,上一条指令执行结束后CS:IP指向EB 03(jmp short s的机器码);
读取指令码EB 03进入指令缓冲器;
(IP) = (IP) + 所读取指令的长度 = (IP) + 2 = 0008,CS:IP指向add ax,1;
CPU指行指令缓冲器中的指令EB 03;
指令EB 03执行后,(IP)=000BH,CS:IP指向inc ax
jmp near ptr 标号 ==(段内近转移)==
指令“jmp near ptr 标号”的功能为:**(IP) = (IP) + 16位位移**。
转移的目的地址在指令中的jmp指令
jmp far ptr 标号==(段间转移或远转移)==
指令jmp far ptr 标号 功能如下:
- (CS) = 标号所在段的段地址;
- (IP) = 标号所在段中的偏移地址。
- far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。
1 |
|
3.转移地址在寄存器或内存中的jmp指令
jmp 16位寄存器 功能:
IP =(16位寄存器)
转移地址在内存中的jmp指令有两种格式:
jmp word ptr 内存单元地址
(段内转移)
功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。
1 |
|
jmp dword ptr 内存单元地址
(段间转移)
功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。
- (CS)=(内存单元地址+2)
- (IP)=(内存单元地址)
1 |
|
4.jcxz指令和loop指令
jcxz指令
jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,
在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为-128~127。
指令格式:jcxz 标号(如果(cx)=0,则转移到标号处执行。)
当(cx) = 0时,(IP) = (IP) + 8位位移
8位位移 = “标号”处的地址 - jcxz指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出。
当(cx)!=0时,什么也不做(程序向下执行)
loop指令
loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。
对IP的修改范围都为-128~127。
指令格式:loop 标号 ((cx) = (cx) - 1,如果(cx) ≠ 0,转移到标号处执行)。
(cx) = (cx) - 1;如果 (cx) != 0,(IP) = (IP) + 8位位移。
8位位移 = 标号处的地址 - loop指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出。
如果(cx)= 0,什么也不做(程序向下执行)。
九、call和ret指令
call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。
1.ret 和 retf
ret指令用栈中的数据,修改IP的内容,从而实现近转移;
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。
CPU执行ret指令时,相当于进行: pop IP:
(1)(IP) = ( (ss) * 16 + (sp) )
(2)(sp) = (sp) + 2
CPU执行retf指令时,相当于进行:pop IP, pop CS:
(1)(IP) = ( (ss) * 16 + (sp) )
(2)(sp) = (sp) + 2
(3)(CS) = ( (ss) * 16 + (sp) )
(4)(sp) = (sp) + 2
1 |
|
2.call 指令
call指令经常跟ret指令配合使用,因此CPU执行call指令,进行两步操作:
(1)将当前的 IP 或 CS和IP 压入栈中;
(2)转移(jmp)。
call指令不能实现短转移,除此之外,call指令实现转移的方法和 jmp 指令的原理相同。
- call 标号(近转移)
CPU执行此种格式的call指令时,相当于进行 push IP jmp near ptr 标号
- call far ptr 标号(段间转移)
CPU执行此种格式的call指令时,相当于进行:push CS,push IP jmp far ptr 标号
- call 16位寄存器
CPU执行此种格式的call指令时,相当于进行: push IP jmp 16位寄存器
- call word ptr 内存单元地址
CPU执行此种格式的call指令时,相当于进行:push IP jmp word ptr 内存单元地址
- call dword ptr 内存单元地址
CPU执行此种格式的call指令时,相当于进行:push CS push IP jmp dword ptr 内存单元地址
3.call 和 ret 的配合使用
示例程序:
1 |
|
十、标志寄存器
(这部分不太好记,那就不记了,实战过程中慢慢积累吧,看着看着就记住了)
1.标志寄存器
CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下3种作用。
(1)用来存储相关指令的某些执行结果;
(2)用来为CPU执行相关指令提供行为依据;
(3)用来控制CPU的相关工作方式。
这种特殊的寄存器在8086CPU中,被称为标志寄存器(flag)。
8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW-Program Status Word)
flag寄存器是按位起作用的,它的每一位都有专门的含义,记录特定的信息。
在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and等,它们大都是运算指令(进行逻辑或算术运算);有的指令的执行对标志寄存器没有影响,比如,mov、push、pop等,它们大都是传送指令
1.零标志位 (ZF)
零标志位(Zero Flag)。它记录相关指令执行后,其结果是否为0。
如果结果为0,那么zf = 1(表示结果是0);如果结果不为0,那么zf = 0。
1 |
|
2.奇偶标志位 (PF)
奇偶标志位(Parity Flag)。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。
如果1的个数为偶数,pf = 1,如果为奇数,那么pf = 0。
1 |
|
3.符号标志位(SF)
符号标志位(Symbol Flag)。它记录相关指令执行后,其结果是否为负。
如果结果为负,sf = 1;如果非负,sf = 0。
计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看成是无符号数。
00000001B,可以看作为无符号数1,或有符号数+1;
10000001B,可以看作为无符号数129,也可以看作有符号数-127。
对于同一个二进制数据,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算
CPU在执行add等指令的时候,就包含了两种含义:可以将add指令进行的运算当作无符号数的运算,也可以将add指令进行的运算当作有符号数的运算
SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值
1 |
|
4.进位标志位(CF)
进位标志位(Carry Flag)。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值
5.溢出标志位(OF)
溢出标志位(Overflow Flag)。一般情况下,OF记录了有符号数运算的结果是否发生了溢出。
如果发生溢出,OF = 1;如果没有,OF = 0。
CF和OF的区别:CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位
CPU在执行add等指令的时候,就包含了两种含义:无符号数运算和有符号数运算。
对于无符号数运算,CPU用CF位来记录是否产生了进位;
对于有符号数运算,CPU用OF位来记录是否产生了溢出,当然,还要用SF位来记录结果的符号。
1 |
|
2.adc指令和sbb指令
adc指令
adc是带进位加法指令,它利用了CF位上记录的进位值。
指令格式:adc 操作对象1, 操作对象2
功能:操作对象1 = 操作对象1 + 操作对象2 + CF
1 |
|
1 |
|
sbb指令
sbb是带借位减法指令,它利用了CF位上记录的借位值。
指令格式:sbb 操作对象1, 操作对象2
功能:操作对象1 = 操作对象1 - 操作对象2 - CF
1 |
|
3.cmp指令
cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响。
其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
cmp指令格式:cmp 操作对象1,操作对象2
例如:
指令cmp ax, ax,做(ax)-(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位。
指令执行后:zf=1,pf=1,sf=0,cf=0,of=0。
CPU在执行cmp指令的时候,也包含两种含义:进行无符号数运算和进行有符号数运算。
cmp ax, bx | 无符号比较时 |
---|---|
(ax) = (bx) | zf = 1 |
(ax) ≠ (bx) | zf = 0 |
(ax) < (bx) | cf = 1 |
(ax) ≥ (bx) | cf = 0 |
(ax) > (bx) | cf = 0 且 zf = 0 |
(ax) ≤ (bx) | cf = 1 且 zf = 1 |
上面的表格可以正推也可以逆推
如果用cmp来进行有符号数比较时:
SF只能记录实际结果的正负,发生溢出的时候,实际结果的正负不能说明逻辑上真正结果的正负。
但是逻辑上的结果的正负,才是cmp指令所求的真正结果,所以我们在考察SF的同时考察OF,就可以得知逻辑上真正结果的正负,同时就知道比较的结果。
1 |
|
4.检测比较结果的条件转移指令
可以根据某种条件,决定是否修改IP的指令
jcxz它可以检测cx中的数值,如果(cx)=0,就修改IP,否则什么也不做。
所有条件转移指令的转移位移都是[-128,127]。
多数条件转移指令都检测标志寄存器的相关标志位,根据检测的结果来决定是否修改IP
这些条件转移指令通常都和cmp相配合使用,它们所检测的标志位,都是cmp指令进行无符号数比较的时记录比较结果的标志位
根据无符号数的比较结果进行转移的条件转移指令(它们检测zf、cf的值)
这个挺重要的:
指令 | 含义 | 检测的相关标志位 |
---|---|---|
je | 等于则转移 | zf = 1 |
jne | 不等于则转移 | zf = 0 |
jb | 低于则转移 | cf = 1 |
jnb | 不低于则转移 | cf = 0 |
ja | 高于则转移 | cf = 0 且 zf = 0 |
jna | 不高于则转移 | cf = 1 且 zf = 1 |
贴张图,需要魔法上网才可以看:
5.DF标志和串传送指令
方向标志位。在串处理指令中,控制每次操作后si、di的增减。
df = 0每次操作后si、di递增;
df = 1每次操作后si、di递减。
格式:movsb
功能:将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将si和di递增或递减
格式:movsw
功能:将ds:si指向的内存字单元中的字送入es:di中,然后根据标志寄存器df位的值,将si和di递增2或递减2。
格式:rep movsb
movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb和movsw都和rep配合使用,
功能:rep的作用是根据cx的值,重复执行后面的串传送指令
8086CPU提供下面两条指令对df位进行设置。
cld指令:将标志寄存器的df位置0
std指令:将标志寄存器的df位置1
1 |
|
6.pushf和popf
pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中
pushf和popf,为直接访问标志寄存器提供了一种方法。
接下来还有一些内容,但是目前来说是用不到的,就先不学了,什么时候用到了再说