简介:本篇旨在对汇编的基础知识进行汇总,汇总的都是要点(简明扼要),目前还在更新中。

第一章 基础知识

1.1 存储单元

一个存储单元存储的信息量以bit为单位(最小)。而转换关系中8 bit=1 bytes(B)。而一个字=两个字节
在微型计算机存储器的存储单元中,一个单元可以存1 B(即8个进制位)。而一个存储器有128个单元,则可以存储128 B。

1.2 CPU对存储器的读写

要读写,则应该与三类信息进行交互:

  1. 地址信息
  2. 控制信息
  3. 数据信息

而逻辑上又分为3类总线,分别传输信息:

  1. 地址总线(指出内存中的信息放在那里(自下而上读01))
  2. 控制总线(若要传输的数据多于一次性传输的宽度,则分开来多传几次)
  3. 数据总线

1.2.1 影响性能的因素:

宽度(线的条数):它决定了部件的能力(速度,控制能力等)。8086为16条,8088为8条

若CPU的寻址能力为8KB,则总线宽度:8*1024/1 = $2^13$ (注意单位是B,13则为总线宽度)

第二章 寄存器

2.1 通用寄存器

AX BX CX DX四种,它们分为高位和低位以兼容以前的程序,如(AX = AH(高) + AL(低))
要注意的是:若只用AL进行运算,若超出其范围,超出的数据不会到AH当中!

2.2 CPU给出物理地址的方法

段地址(SA):只是为了内存的管理,并不是真分段。16位地址分段,一段64KB
偏移地址(EA):16位地址最多64KB
公式:物理地址=段地址*16+偏移地址

2.3 段寄存器

CS DS SS ES四种。CS常存储段地址。它们提供了一下功能:

  1. CS:CPU要执行指令的地址。相当于命令指针
  2. DS:读取内存的地址(返回数据)。相当于数据指针
  3. SS:存储内存中的栈的顶。相当于栈顶指针

2.3.1 CS和IP

IP是存储基于CS的偏移量。修改的流程:

一般流程:读取指令 -> 修改 IP+=命令长度 -> 执行指令(可能会涉及修改CS:IP) -> 读取指令…
8086PC启动时在FFFF0H单元中读取指令执行

2.4 疑问

1. 为什么偏移地址只能在64Kb内?

一个16位的CPU,只能有1111 1111 1111 1111,代表了4个16进制数,所以能表示2**16 bit的数据,恰好,就是64Kb。

2.暂留

第三章 寄存器(内存访问)

3.1 内存中字的存储

字单元的概念:即存放一个字型数据的内存单元,由两个地址连续的内存单元(一个8位)组成。高放高位,低放低位。

这里解释一下: 若有单元[0]->2e,[1]->33,那么我们读[0]的字型数据为:332eH(高放高位)

3.2 DS和[address]

“[…]”表示一个内存单元,表示在段地址下的偏移量。如10000H = 1000:0 = [0]

  • 在masm汇编编辑器中,如果单纯写mov ax,[0],那么他会把[0]当做0来解释,即指令变成:mov ax,0
    而若用bx作为中转,那是可以的!如:

    1
    2
    mov bx,0
    mov ax,[bx]

    这个是可以把[bx]当成段地址,返回ds:bx下的数据的!
    而如果偏要用[idata]的话,那么就必须加上ds:前缀,如:

    1
    mov ax,ds:[0]
  • 而对于Debug编译器的话,ds是自动加上的,用

    1
    mov ax,[0]

    即可,如果使用[]的话,而且他(段寄存器)是不能直接用mov命令改变的,必须通过其他的寄存器中转才能改变。

3.3 字的传送(mov指令)

在传送字之后, DS和[address]也会跟着变,如1000:0,若传入一个fffeH,则address会从0->2,此时,[0]存有feH,[1]存有ffH,[2]为空。

3.4 mov、add、sub指令

主要是说说置零问题,与机械码长度问题,稍微提一下,以后会细讲。
举例子:

1
2
sub bx, bx
mov bx, 0

两个都是可以将bx寄存器置零的指令,但是sub的机械码长度为2,而mov为3,用sub更好。
另外说说jmp指令,他是转移指令,具有一个操作对象的指令,而上面的具有两个。

3.5 栈

他是一个FILO结构(先入后出),而且是从大变小(地址)。

3.5.1 push和pop

执行push ax命令,会把ax中的数据传入ss:sp指向的地址空间。此时sp-=2
执行pop ax命令,会把ss:sp指向的地址空间中的数据传给ax。此时sp+=2

执行之后的指针变化:

  • push先令sp-=2,然后将ax中的数据放进去。称为出栈。
  • pop是先取出数据放到ax中,再sp+=2。称为入栈。

3.5.2 数据传输

如8086cpu的入栈和出栈都是以为单位进行的。

3.5.3 栈指针

上面已经用到了:段寄存器:寄存器存储(ss:sp)
sp是指偏移地址,ss:sp始终指向栈顶。

3.6 栈顶越界问题

栈溢出问题都是大问题,如果栈顶越界了,那么执行push和pop会受影响吗?当然不会!直接刚,写!写!写!出!出!出!管你呢。
所以就会有可能读了其他程序的数据,或者修改了(覆盖)其他程序的数据(直接崩溃的说)
执行情况(取自《汇编语言第二版》王爽著)

3.7 问题

1.程序与数据有区别吗?

可以说有,也可以说没有。由于指令都是0和1组成的,只是用不同功能的段地址读出来的不一样,如,CS和DS都指向10000H,那么,CS会将里头的数据转化为机械码,执行指令,而DS就直接读数据,作为单纯的数据使用。

2.空栈如(10000H~1000FH)此时ss=1000H,那么sp是多少?

因为是自下而上(自大到小),所以sp=0010H。
换一种方式看,若存储了一个数据,则一个存到000EH,一个存到000FH,此时的栈指针在栈顶,当然指向000EH了,此时是已经让sp-=2了,那么回溯一下,不就有sp=0010H了。

  • 好了,问题来了,如果将(10000H~1FFFFH)作为空间呢?
    答案:sp=0000H,因为,不能变成10000H(笑)

3.栈会溢出,那么,为什么不设置栈大小

emmm,可惜8086cpu就是没有,所以变成自己注意。

4.只有mov可以完成传输数据吗?

push也可以哦,只要把sp定位为ds:[address]那就好了哦。直接写进去。

第四章 第一个程序

4.1 一个源程序从写出到执行的过程

执行过程包括:写程序->编译连接成可执行文件->执行。

可执行文件文件中包括以下两部分:

  1. 程序和数据
  2. 相关的描述:程序多大,占用多少运行空间

4.2 源程序

4.2.1 伪指令

也就是只能被编译器识别的指令,如assumesegmentend等,这是没有机械码的
一般的伪指令使用如下:

1
2
3
4
5
assume cs:code  #声明(假设)cs:段名(标号)
code segment #段名 segment
_指令_
code ends #对应segment,表示一个段结束
end #对应assume,表示程序结束

4.2.2 程序返回

现在考虑,如何让上一个程序把CPU控制权交给写一个程序,这时候就要使用程序返回
其代码如下:

1
2
mov ax, 4c00H
int 21H

4.3 程序生成流程

编程 -> 1.asm -> 编译 -> 1.obj -> 连接 -> 1.exe -> 加载 -> 内存中的程序 -> 运行
注:加载就是将程序加载到内存,执行就是在内存中执行程序的指令

4.4 怎么装载程序?

操作系统是一个模块组成庞大的系统。任何通用的系统都要提供一个称为shell的程序,用户使用这个程序来操作计算机系统进行工作。

如DOS中有一个程序command.com,这个程序称为:命令解释器,也就是DOS中的SHELL
(其实跟我们的cmd.exe差不多)

先运行shell,然后在加载1.exe可执行程序的时候,cmd会自动把cpu的控制权交给1.exe,执行完成再返回cmd。

4.4 展示EXE的加载过程

第五章 [BX]和loop指令

5.1 [BX]

这个之前就讲过:其实就是把BX中的值作为ds的偏移地址EA,然后可以用这个来访问特定地址下的数据。
如ds:1000H,bx=0001H,那么[BX]就是指ds*16+bx下的值。

5.2 loop

直接上程序:

1
2
3
4
5
6
7
8
assume cs:eloop
eloop segment
mov ax,128
mov cx,36
s:add ax,ax
loop s
eloop ends
end

解释:

  1. loop是按照cx当中的值进行对有标号的s下的命令进行循环的。当cx不等于0,则执行循环,这个程序实际上是实现128*36的程序。
  2. s实际上是IP值,指向s标记的地址,所以loop s实际上是给IP赋值:IP=s,然后就可以通过cs:ip来执行指令了

注意:汇编程序中,数据不能以字母开头,所以要在前面加个0。比如:9138h可以写成9138h,而A000h就必须写成0A000h

5.3 loop和[bx]的联合运用

例如来一个如同C++语言中的:

1
2
3
4
5
6
int dx = 0;
int ds[12] = {1, 2, ...};
for(int i=0; i < 12; i++)
{
dx += ds[i];
}

那么汇编语言就有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:code
code segment:
mov ax, 0ffffh ;因为ds寄存器不能直接赋值,所以用ax作为中间变量
mov ds,ax
mov bx,0 ;初始化ds:bx指向ffff:0

mov dx,0 ;初始化**累加寄存器**dx,让值为0

mov cx,12 ;初始化循环计数寄存器cx,令值为12,指循环12次

s: mov al,[bx] ;以下两步执行ax的赋值操作
mov ah,0 ;
add dx,ax ;执行加法
inc bx ;ds:bx指向下一个单元,因为bx的值增加了1,相当于C++代码中的*i++*
loop s ;loop完成一次,则cx=cx-1,然后判断cx是否等于0,如果等于则退出循环

mov ax,4c00h
int 21h

code ends
end

好了,现在来说说这段代码里面有什么需要注意的点:

  1. cx寄存器一般用作loop循环的判断条件,即循环的粗次数
  2. ax寄存器的初始化有两步,是,因为一个字符型数据是8位这样的话转化为16位就得让高8位的值赋为0。
  3. dx寄存器是常用的累加寄存器。
  4. inc指令是让后面的值加一的指令,相当于C++语言中的‘++’自增。
  5. int指令:

    由int 指令引发的中断是一种重要的内中断。
    格式: int n //相当于引发一个n号中断的过程,最终功能和call指令相似,都是调用一段程序。

    • 取中断类型码n
    • 标志寄存器入栈,并IF=0,TF=0 //TF=0使得避免中断程序执行过程中引发单步中断
    • CS,IP寄存器入栈
    • IP=(n*4) , CS=(n*4 + 2)
      原文链接

5.4 段前缀与其使用

  • 能够显示地指令内存单元的段地址的,如:”ds:”、”cs:”、”ss:”、”es:”。这就是段前缀。
  • 默认的[x]是指:(dx*16)+x,而如果要用其他的段寄存器,则需要加上它的段前缀。

第六章 包含多个段的程序

由于软件是由数据和程序组成的,数据当然也是程序里面必不可少的部分,所以会将代码分为多段进行,如数据段、代码段等。

6.1 在代码段中使用数据

汇编程序若没有特殊的说明(或者注明在哪里开始),则默认从头开始!
所以,一旦你的程序头部有数据,则将会把头部的数据翻译为机器码执行。(冤)

那么解决方式是:在开始执行代码的地方加上 start:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment
dw 0123h,0456h,0789h ;这里是数据部分 dw意思是定义字符型数据
start: mov bx,0 ;注意,从这开始,标识为start!
mov ax,0

mov cx,8
s: add ax,cs:[bx] ;用cs作为段地址,取数据!
add bx,2
loop s

mov ax,4c00h
int 2h
code ends
end start ;注意end后面要加上end开始的名称!

6.2 将数据、代码、栈放入不同的段

上面说了,代码从哪里开始就在哪里加上start,结束就用end start(其他标识也行)
好,记住,那是代码,别把接下来说的混淆了。

不同的段可以放不同的东西,例如数据,代码等等。
只要记住将段名end了再定义其他段。也只有定义了,才能使用这个段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
assume cs:b,ds:a,ss:c
a segment
dw 0123h, 0456h, 0789h, 0abch, 0de fh, Ofedh, 0cbah, 0987h
a ends

c segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
c ends

b segment
d: mov ax,c ;使用段c中的数据
mov ss,ax
mov sp, 20h ;希望用c段当作栈空间,设置ss:sp指向c:20

mov ax,a ;使用段a
mov ds,ax ;希望用ds:bx访问a段中的数据,ds指向a段
mov bx,0 ;ds:bx指向a段中的第一一个单元
mov Cx,8

s: push [bx]
add bx,2
1oop s ;以上将a段中的0~15单元中的8个字型数据依次入栈

mov bx,0
mov cx, B
s0: pop [bx]
add bx,2
1oop s0 ;以上依次出栈8个字型数据到a段的0~15单元中

mov ax, 4c00hint 21h
b ends

end d ;d处是要执行的第一- 条指令,即程序的入口

第七章 更灵活的定位内存地址的方法

之前的[0]和[bx]均可以起到定位的作用,现在有更灵活的定位方式:

7.1 and和or指令

and是有零变零,而or是有一变一
也可以想象成and是乘法,or是加法

7.2 以字符型给出数据

凡是以’…’形式给出数据的,都被译为是字符型数据,这样,编译器会将里面的数据变成ASCII码值。具体实例:

1
2
db 'unIX'
mov al,'a'

这里被译为

1
2
db 75H,6EH,49H,58H
mov al,61H

而大小写转换的方法有两种:

  1. 基于比较的,在ASCII码中,大写字母+20H=小写字母
  2. 基于二进制数的,在ASCII表达字符时,若第五位为0,则为大写字母;若第五位为1则为小写字母(这里可以用or或者and来进行转换)

7.3 以[bx+idata]的方式寻址

现在以例题进行解析,将第一组字符串变成大写,第二组变成小写

1
2
3
4
5
6
7
8
9
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db 'MinIX'
datasg ends
codesg segment
start: ;代码段
codesg ends
end start
  • 原来的思想:取[bx]中的字符,再对其进行and 11011111b操作,再放回去,然后将bx自增,再重新···,而变成小写就or 00100000b。(这里要执行两次循环啊,一个变大写,一个变小写)
  • 然而我们可以使用以下思想,更加便捷,就是[bx+idata]:思想和前面的一样,但是,不用执行两次循环,只要一次就够了:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
      mov ax,datasg
    mov ds, ax
    mov bx,0

    mov cx,5
    s:mov al,[bx] ;定位第一个字符串的第bx个字符
    and al,11011111b ;执行完变大工作
    mov [bx],al ;放回去
    mov al[5+bx] ;定位第二个字符串的第bx个字符(因为一个字符串只有5个字符)
    or al,00100000b ;执行变小工作
    mov [5+bx],al ;放回去
    inc bx ;自增
    loop s
    这个其实,学过高级语言都知道这方法,只不过,没想到汇编也能做吧,汇编语言也不是那么死板的。

7.4 si与di寄存器和多重循环

这个只要知道是16位寄存器,功能和bx差不多,可以用作[bx+si+idata]和[bx+di+idata]就可以了。其中,idata是常量,其他的为变量。这个结构可以执行二重循环,相当于处理二维数组。
需要注意的是二重循环的实现:

  • 问题一:cx寄存器是用作循环的判定条件的,那么二重循环对于没有两个cx的我们来说,如何是好?
    将之前的cx用其他寄存器保存起来啊!
  • 问题二:二重循环可以那样做,那么多重循环呢!?你只有有限个寄存器。
    利用内存空间!开辟一段dw 0,让后指向它,放进去就完事了。
  • 问题三:那这个方法的前提是我得知道有多少个循环,很麻烦啊,换个简单的。
  • 在需要暂存数据的时候我们都应该使用栈*
  • 在需要暂存数据的时候我们都应该使用栈*
  • 在需要暂存数据的时候我们都应该使用栈*
    按顺序push,然后逆向按顺序pop

第八章 数据处理的两个基本问题

这两个问题是:

  1. 处理的数据在什么地方
  2. 要处理的数据有多长

8.1 bx、si、di和bp

这几个都用过,主要说说bp:这个只要在[…]里使用了它,没有段地址声明,那么默认是ss

8.2 机器指令处理的数据在什么地方

在指令执行前,所要处理的数据可以在三个地方:

  1. CPU内部
  2. 内存
  3. 端口
    下面举例子:

    上图还表明了汇编语言中数据位置的表达
  4. 立即数(idata):如上图的最后一个例子,是直接赋值的
  5. 寄存器:如上图的第二个例子,使用寄存器名。
  6. 段地址:如果是[bx]则默认段地址是ds,如果是[bp]则默认段地址为ss,当然可以显性给出。

8.3 指令处理的数据长度

这个得看具体例子。

  1. 如果是ax,bx,那么这个就是字操作
  2. 如说是al,bl,那么就是字节操作
  3. 在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编中可以为word或byte(且必须显式声明)。
1
2
mov word ptr ds:[0],1	;字
mov byte ptr ds:[0],1 ;字节
  1. 有些指令默认了访问的是字还是字节,如push默认进行操作,而用[…]的是字单元操作(一个8位)。

8.4 div指令

div是除法指令,使用div做除法的时候应注意以下问题.

  1. 除数:有8位和16位两种,在一个reg内存单元中。
  2. 被除数:默认放在AXDX和AX
    1. 如果除数为8位,被除数则为16位默认在AX中存放;
    2. 如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位
  3. 结果:
    1. 如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数:
    2. 如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。

格式如下:

1
2
3
4
5
div reg
div 内存单元
div byte ptr ds:[0]
含义:(al)=(ax)/((ds)*16+0)的商
(ah)=(ax)/((ds)*16+0)的余数

8.5 实例

利用除法指令计算100001/100
分析:

  1. 由于100001大于整数最大值(16位)65535,不能用ax寄存器存放,所以只能用dx和ax两个寄存器联合存放,共32位
  2. 而出书100小于255(8位),但是因为被除数是32位,所以除数应该用16位的来存放除数100
    所以有程序:
1
2
3
4
mov dx,1
mov ax,86A1H ;(dx)*10000H+(ax)=100001 100001=186A1H
mov bx,100
div bx

执行后,(ax)=03E8H(即10000),(dx)=1(余数为1)。

8.6 伪指令dd

之前有学过用db和dw定义字节型数据和字型数据。dd是用来定义dword(double word,双字)型数据的。

  • db 占1个字节
  • dw 占1个字(即两个字节)
  • db 占2个字(即四个字节)

8.7 dup

dup是一个操作符,在汇编语言中与dw,db,dd一样,都是由编译器识别处理的符号,且与它们一同配合使用,用来进行数据重复。
如:

1
2
3
4
5
6
7
8
9
db 3 dup (0)
定义了3个字节,它们的值都是0,相当于执行了 db 0,0,0。

db 3 dup (1,2,3)
定义了3\*3=9个字节,它们是0、1、2、0、1、2、0、1、2,相当于db 0,1,2,0,1,2,0,1,2。
字符型也适用。

总结:
d? 重复的次数 dup (重复的(字节/字/双字型)数据)

第九章 转移指令的原理

可以修改IP 或同事修改CS和IP的指令统称为转移指令统称为转移指令。如jmp、jno等,即可以控制CPU指令内存中某处代码的指令。
具体分类有:

  1. 无条件转移指令(jmp)
  2. 条件转移指令(jno)
  3. 循环指令(loop)
  4. 过程
  5. 中断

还有其他的分类:

  1. 只修改IP:段内转移(jmp ax)

  2. 同时修改CS和IP:段间转移(jmp 1000:0)

  3. 短转移

  4. 近转移

9.1 操作符offset

offset是编译器处理符号,功能:取得标号的额编译地址。
如:

1
2
start: mov ax, offset start	;相当于mov ax,0
s: mov ax, offset s ;相当于mov ax,3

解析:第一条指令mov … 是三个字节,所以s段的offset为3

9.2 jmp指令和依据译为进行转移的jmp指令

9.2.1 jmp short 标号(转移到标号出执行指令)

1. short标号下,它属于段内短转移,对IP的修改范围为-128~127。

9.2.2 立即数在机器码中的表示

在一般的汇编指令当中,汇编指令的idata(立即数),不论他是表示一个数据还是内存单元的偏移地址,都会在相应的机器指令中出现,因为CPU执行的是机器指令,他必须处理这些数据或地址。

1
2
3
mov ax,0123h		B8 23 01
mov ax,ds:[0123h] A1 23 01
push ds:[0123h] FF 36 23 01

9.2.3 jmp转移的机制(是怎么实现转移的)

1
2
3
4
5
start:	mov ax,0
mov bx,0
jmp short s
add ax,1
s: inc ax
  1. 首先我们应该知道,jmp指令是2个字节,如代码中的jmp机械码是:EB 03 。这样的机械码,没有包含s段的地址!所以将jmp指令写入内存之后,IP=IP+2=0008h。
  2. 但是,执行完jmp后,IP=000Bh。因为执行JMP,就是执行IP=IP+(EB之后的偏移量)=IP+3=000Bh。
  3. 所以jmp执行的是给IP加偏移地址!

所以 jmp short 标号 的功能为:(ip)=(ip)+8位位移

9.2.4 继续上面的,8位位移是怎么得出来的?

  1. 8位位移=标号处的地址 - jmp指令后的第一个字节的地址.
  2. short指明此处的位移为8位位移;
  3. 8位位移的范围为-128-127,用补码表示
  4. 8位位移由编译程序在编译时算出。

还有一种和”jmp short标号”功能相近的指令格式:
jmp near ptr标号
它实现的是段内近转移。功能为:(IP)=(LP)+16位位移
(1) 16位位移=标号处的地址 - jmp指令后的第一个字节的地址:
(2) near ptr指明此处的位移为16位位移,进行的是段内近转移:
(3) 16位位移的范围为一32768~32767,用补码表示:
(4) 16位位移由编译程序在编译时算出。

9.2.5 jmp far ptr 标号(段间转移/远转移)

是不是所有的jmp机械码都没有段数据呢?当然不是!小标题就说到了:far ptr,这个指令用标号的段地址和偏移地址修改CS和IP
如:

解释:“OB 01 BD 0B”是目的地之在指令中的存储顺序:
高地址:“BD 0B”是段地址 0BBDH ;
低地址:“0B 01”是偏移地址 010BH ;

9.2.6 转移地址在寄存器的jmp指令(这就相当于总结了,上面废话有点多)

  • 若只是一个字,则只是偏移地址(直接EB ??完事)
  • 若是两个字,那么一个是段,一个是偏移地址。

9.3 jcxz指令

该指令为有条件转移指令,所有的有条件转移指令都是短指令,即上面9.2.6说的1个字,只有位移(偏移地址),对ip的修改范围是:-128~127.

指令格式:jcxz 标号
如果(cx)=0,则转移到标号这

9.4 loop指令

也是短转移指令,pass

9.5 编译器对转移位移超界的检测

编译的时候就会出错!因为,这超界,编译器可识别不了。这不存在的玩意儿。

第十章 CALL和RET指令

10.1 ret和retf

它们都是用栈中的指令,而ret是改变ip,而retf是改变cs:ip,实现远转移。
而正是因为它们是取栈中的指令,所以要和push 和pop结合起来,而ret或者retf执行完后,相当于执行了指令pop
如:

1
2
3
4
5
6
mov ax stack	;在前面声明的stack数据段,共16B
mov ss ax ;栈段地址
mov sp 16 ;将值(Base)设置为16,往上加。(栈的特性)
push cx
push ip
retf

这里就相当于执行了刚放进去的机器码。

10.2 call指令

  • 将打那个钱的IP或CS:IP压入栈中
  • 转移

注意:call指令不能执行短转移,其实现方法与jmp指令相同

10.2.1 依据位移进行转移的call指令

call 标号(将当期那的ip压入栈后,转到标号处执行指令)

指令执行时会带有以下操作:

1
2
3
1. (sp)=(sp)-2	;压栈
((ss)*16+(sp))=(IP)
2. (IP)=(IP)+16位位移

这个位移之前也说过了,不多讲。
看到上面的代码我们也能联想到jmp的执行了,相当于:

1
2
push IP
jmp near ptr 标号

举个例子!

1
2
3
4
5
内存地址 机器码 汇编指令 
1000:0 b8 00 00 mov ax,0 
1000:3 e8 01 00 call s 
1000:6 40  inc ax 
1000:7 58  s:pop ax

也许你会想到:我没有push哪来的pop!?

  1. 在call之时已经有了push ip,此时的ip=提取call s之后指向下一个执行机器码(inc ax)的ip指针,也就是ip=6。
  2. 而当call转移到s,ip就变为call之后的ip。
  3. 此时再pop ax,也就是ax=6。
  4. 因为执行完s,没有回调指令pop ip。。。所以不会执行inc ax

10.2.2 转移地址在指令中的call指令

这个也就是段间转移了,是相对于上头说的段内转移

1
call far ptr 标号

此时会执行cs和ip双压栈

1
2
3
4
5
6
7
8
9
10
11
1. (sp)=(sp)-2	;压栈
((ss)*16+(sp))=(CS)
(sp)=(sp)-2 ;压栈
((ss)*16+(sp))=(IP)
2. (CS)=标号所在的段地址
(IP)=标号在段中的偏移地址

即:
push CS
push IP
jmp far ptr 标号

举个例子:

1
2
3
4
5
6
7
1000:0 b8 00 00 		mov ax,0
1000:3 9a 09 00 00 10 call far ptr s
1000:8 40 inc ax
1000:9 58 s: pop ax
add ax,ax
pop bx
add ax,bx

ax是多少呢?

  1. call指令执行之后,stack中有cs和ip两个数据,CS在底部,IP在顶部。所以第一次pop ax,ax=ip=8
  2. 执行add后ax=16即10H
  3. 再执行pop,bx=1000H
  4. add指令执行后ax=ax+bx=1010H

10.2.3 转移指令在寄存器中的call指令

call 16位reg

功能:

1
2
push IP
jmp 16位reg ;IP=(reg)

这个跟10.2.1是不是很像?段内转移。例子:call ax(当ax=6,则转到cs:6处执行)

10.2.4 转移指令在内存中的call指令

call word ptr 内存单元地址

功能:

1
2
push IP
jmp word ptr 内存单元地址

其实跟10.2.3没啥两样。

但是还有一种双字型:
call dword ptr 内存单元地址
功能:

1
2
3
push CS
push IP
jmp dword ptr 内存单元地址

举个例子就明白了

1
2
3
4
5
mov sp,10h
mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2],0
call dword ptr ds:[0]

执行后:(CS)=0, (IP)=0123H, (sp)=0CH.

10.3 call与ret的配合使用(敲黑板!)

  • call之时压ip到栈中!
  • call完之后,执行转移后的指令
  • 通过ret取出栈中的地址,执行转移操作!即回调作用!
  • 回调完成就可以执行call之后的代码了!
    分析例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    assume cs:code
    code segment
    start: mov ax,1
    mov cx,3
    call s
    mov bx,ax ;(b)=?
    mov ax,4c00h
    int 21h
    s: add ax,ax
    loop s
    ret
    code ends
    end start

    我们来看一下CPU执行这个程序的主要过程。
    (1)CPU将call s指令的机器码读入,IP指向了call s后的指令mov bx,ax.然后CPU执行call s指令,将当前的IP值(指令mov bx,ax的偏移地址)压栈,并将IP的值改变为标号s处的偏移地址:
    (2) CPU从标号s处开始执行指令,loop循环完毕后,(ax)=8;
    (3) CPU将ret指令的机器码读入,IP指向了ret指令后的内存单元,然后CPU执行ret指令,从栈中弹出一个值(即call s先前压入的mov bx,ax指令的偏移地址)送入IP中。则CS:IP指向指令mov bx,ax;
    (4) CPU从mov bx,ax开始执行指令,直至完成。

10.4 mul乘法指令

之前说了div除法指令,现在说乘法指令。

10.4.1 乘法规则

这里跟div的除数和被除数不一样的是,乘法的两个数,要么都是8位要么都是16位!

  1. 乘法的两个数
  • 如果是8位,一个放在AL中,一个放在8位的reg或内存字节单元中。
  • 如果是16位,一个放在AX中,一个放在16位的reg或内存字节单元中。
  1. 乘法的结果
  • 如果是8位乘法,结果放在AX
  • 如果是16位乘法,高位放DX低位放AX

10.4.2 指令格式:

1
2
mul reg
mul 内存单元

格式说明:
因为有一个放在了al(以8位为例)

1
2
3
mov al 1
mov byte ptr ds:[0] 2
mul ds:[0]

这个就是用2*1了~
结果是ax=2(当然,我赋值的是十进制嘛)

10.4.3 用mul寻址

  1. mul byte ptr ds:[0]
    含义:ax=al(ds16+0)
  2. mul word ptr [bx+si+8]
    含义:dx=

10.5 模块化程序设计

10.5.1 参数和结果的传递问题

也就是说,传进来的参数是啥,返回值是个啥。

  1. 传入的参数:
    这个有很多,有用栈传递的,也有直接用内存单元或者寄存器。
  2. 返回值:
    这个可以存在寄存器中返回跟传入参数的方法很像

而模块化设计,其实就是相当于c语言中的函数,把一个段中的代码当做一个函数执行。

10.5.2 寄存器冲突问题

冲突问题是什么呢?例如如果上面的模块使用的loop,即使用了寄存器cx,而call的模块也适用了loop,即也使用了cx,那么两者就将共用cx,肯定会出错!
解决办法有:

  1. 让别的调用者调用其他寄存器(这个很难实现,你也不知道他会用到什么寄存器)
  2. 不要使用会冲突的寄存器(这个不可能实现,只是说说理想罢了)
  3. 用栈,压进去,保存,call完在出栈!(这个OK哦)

10.6 课后

下面的程序执行后,ax和bx中的数值为多少?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
assume cs:codesg
stack segment
dw 8 dup(0)
stack ends
codesg segment
start:
mov ax,stack
mov ss,ax
mov sp,10h
mov word ptr ss:[0],offset s ;(ss:[0])=1ah
mov ss:[2],cs ;(ss:[2])=cs
call dword ptr ss:[0] ;cs入栈,ip=19h入栈,转到cs:1ah处执行指令
;(ss:[4])=cs,(ss:[6])=ip
nop
s: mov ax,offset s ;ax=1ah
sub ax,ss:[0ch] ;ax=1ah-(ss:[0ch])=1ah-19h=1
mov bx,cs ;bx=cs=0c5bh
sub bx,ss:[0eh] ;bx=cs-cs=0
mov ax,4c00h
int 21h
codesg ends
end start

实验10-1 编写子程序 之 显示字符串

在这次实验中,我们将要编写3个子程序,通过它们来认识几个常见的问题和掌握解决这些问题的方法.同前面的所有实验一样,这个实验是必须独立完成的,在后面的课程中,将要用到这个实验中编写的3个子程序。

显示字符串

问题

显示字符串是现实工作中经常要用到的功能,应该编写一个通用的子程序来实现这个功
能.我们应该提供灵活的调用接口,使调用者可以决定显示的位置(行、列)、内容和颜色。

子程序描述

名称:show str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数:(dh)=行号(取值范围。-24), (dl)=列号(取值范围0-79),
(cl)=颜色,ds:si指向字符串的首地址
返回:无
应用举例:在屏幕的8行3列,用绿色显示data段中的字符串。

提示

  1. 子程序的入口参数是屏幕上的行号和列号,注意在子程序内部要将它们转化为显存中的地址,首先要分析一下屏幕上的行列位置和显存地址的对应关系:
  2. 注意保存子程序中用到的相关寄存器:
  3. 这个子程序的内部处理和显存的结构密切相关.但是向外提供了与显存结构无关的接口。通过调用这个子程序,进行字符串的显示时可以不必了解显存的结构,为编程提供了方便。在实验中,注意体会这种设计思想。
  4. 其实这玩意,就是抢占了本来应该在屏幕中输出的系统信息。那么设置行列号的,就是占的信息的位置。
    点击查看参考代码
      
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    assume cs:code,ds:data

    data segment
    db 'Welcome to masm!',0
    data ends

    code segment
    start:
    mov dh,8 ;行号
    mov dl,3 ;列号
    mov cl,07h ;白色字
    mov ax,data
    mov ds,ax
    mov si,0 ;循环加入字
    call show_str

    mov ax,4c00h
    int 21h
    show_str:
    push cx ;保存用到的寄存器
    push si
    push es
    push di
    push bx

    mov ax,0b800h
    mov es,ax

    mov al,0a0h ;一行的总列数160字节
    dec dh ;行号减1,因为是从0开始的
    mul dh ;计算行开始偏移地址
    mov bx,ax

    mov al,2
    mul dl ;计算列
    sub ax,2 ;列也是从0开始,而且一个字符占两个字节
    add bx,ax ;求出开始位置

    mov di,0
    mov al,cl
    mov ch,0 ;高8位为0
    s:
    mov cl,ds:[si] ;判断是否到了字符结束
    jcxz ok
    mov es:[bx+di],cl
    mov es:[bx+di+1],al

    inc si
    add di,2
    jmp short s
    ok:
    pop bx
    pop di
    pop es
    pop si
    pop cx
    ret

    code ends

    end start

解决除法溢出问题

问题:

其实就是如果商超过了应该存储商的寄存器的大小,应该怎么办。emmm可以用大一点的,用dword

第十一章 标志寄存器

这是一种特殊的寄存器,一共16位:

  1. 用来存储相关指令的执行结果
  2. 用来为CPU执行相关指令提供行为依据
  3. 用来控制CPU的相关工作方式
    其分布如下:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
* * * * OF DF IF TF SF ZF * AF * PF * CF

其中*表示都没用:1 3 5 12 13 14 15在8086CPU中都没有使用。
注意:对标志位有影响的指令有:add,sub,mul,div,inc,or,and等,而没有影响的有:mov,push,pop等,它们大多数为传送指令。

11.1 标志表

名称 代号 作用 备注
零标志 ZF 判断结果是否为零 若为0则ZF=1,否则ZF=0
奇偶标志 PF 判断结果中1的个数是奇是偶 若1的个数为奇数则PF=0,否则为1
符号标志 SF 判断结果是正是负 若为正数则SF=0,否则为1
进位标志位 CF 在进行无符号运算时记录是否有向最高位进位或者借位 那是假想的最高位
溢出标志 OF 判断是否超过容器所能装的最大数 如al=98+99=197,197>127溢出,实际结果:-59
方向标志 DF 控制每次操作后si、di的增减 df=0 每次si,di增加;df=1 每次si,di减少。经常用作字符串的传送

11.2 与标志有关的指令

11.2.1 与CF相关的:adc,sdd指令

  1. adc:带进位加法。如:adc al,8h == al+8h+CF
  2. sdd:带借位减法。如:sdd al,8h == al-8h-CF

11.2.2 与CF和ZF有关的:cmp指令(比较指令)

指令格式:cmp s1,s2
举个例子:cmp ax,bx
比对结果如下:

情况 结果分析 结果
(ax)=(bx) 则(ax)-(bx)=0 zf=1;
(ax)≠(bx) 则(ax)-(bx)≠0 zf=0;
(ax)<(bx) 则(ax卜(bx)将产生借位 cf=1;
(ax)≥(bx) 则(ax)-(bx)不必借位 cf=0;
(ax)>(bx) 则(ax)-(bx)既不必借位结果又不为0 cf=0并且zf=0;
(ax)≤(bx) 则(ax)-(bx)既可能借位结果可能为0 cf=1或zf=1.

11.2.3 与CMP有关的比较结果条件转移指令

指令 含义 简记 符号 监测的相关标志位
je 等于则转移 equal = zf=1
jne 不等于则转移 not equal zf=0
jb 低于则转移 below cf=1
jnb 不低于则转移 not below cf=0
ja 高于则转移 above cf=0且zf=0
jna 不高于则转移 not above cf=1或zf=1

注意,这个一般是与cmp配合使用,如同call和ret一样,但是没有说一定要配合使用。

11.2.4 与DF有关指令

指令 功能 解释
movsb mov es:[di],byte ptr ds:[si] 从ds中取字符放到es指定的位置中,单位为字节,会令di和si同时±1
movsw mov es:[di],word ptr ds:[si] 从ds中取字符放到es指定的位置中,单位为字,会令di和si同时±2
cld 设置df=0,正向 令di,si向增方向
std 设置df=1,逆向 令di,si向减方向
rep 重复指令,根据CX当中的值,与LOOP相似 配合movsb使用

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
我们来看下面的两个程序。
(1)编程,用串传送指令,将data段中的第一个字符串复制到它后面的空间中。
data segment
db 'Welcome to masm!.
db 16 dup (0)
data ends
我们分析一下,使用串传送指令进行数据的传送,需要给它提供一些必要的信息
它们是:
1. 传送的原始位置:ds:si:
2. 传送的目的位置:es:di;
3. 传送的长度:CX;
4. 传送的方向:df.
在这个问题中,这些信息如下。
1. 传送的原始位置:data:O;
2. 传送的目的位置:data:0010;
3. 传送的长度:16;
4. 传送的方向:因为正向传送(每次串传送指令执行后,Si和di递增此较方便,所以设置df=o
好了,明确了这些信息之后,我们来编写程序:
mov ax,data
mov ds,ax
mov si,0 ;ds:si指向data: 0
mov es,ax
mov di,16 ;es.di于行向data:0010
mov cx,16 ;(cx)-16, rep循环1‘次
cld ;设置df=0,正向传送
rep movsb

11.3 标志位有关的问题

11.3.1 单纯就符号标志位就可以判断结果的正负吗?(即判断相减的两个数的大小)

当然不是!

当1-2=-1<0,则SF为1,表示负数,则前一个数比后一个数大
当34-(-96)=82H时,82H是-126的补码,所以SF=1,但是前一个数比后一个数大?当然不。

为什么呢?因为发生了溢出

  • 也就是说,保存结果的寄存器不足以表示那么大的数。
  • 而34-(-96)=130明显超过127(假设保存结果的是ah)。
  • 那么结果就会表示为-126(130-127=3,-128+3-1=-126)
  • 而此时,SF=1,同时,OF=1
  • 所以要看结果的正负,要结合SF和OF的值.

总结:

标志 结果 备注
SF=1,OF=0
SF=1,OF=1 因为溢出导致实际结果为负,那么逻辑上必定为正
SF=0,OF=1 因为溢出导致实际结果为正,那么逻辑上必定为负
SF=0,OF=0

11.4 pushf和popf

因为标志寄存器共16位,所以pushf就直接将16位当做寄存器放入栈中,popf是出栈。
注意,它们不需要加对象参数,入栈和出栈的对象都是标志寄存器

第十二章 内中断

也就是CPU不再往下执行下去。而CPU内部的四种中断信息是:

  1. 除法错误,如div指令产生的除法溢出(中断类型码:0)
  2. 单步执行(中断类型码:1)
  3. 执行into指令(中断类型码:4)
  4. 执行int指令(指令格式:int n,其中n是中断类型码,是字节型立即数)

12.1 中断一条龙(中断程序,中断向量表,中断过程)

中断过程:

  1. 取得中断类型码(即中断向量表中的中断程序位置)
  2. 标志位入栈(因为执行中断过程要改变标志寄存器的值)
  3. 设置寄存器的TFIF的值为0
  4. CS内容入栈
  5. IP内容入栈(结束中断程序回调的时候用,和call差不多)
  6. 从内存地址为中断类型码*4和中断类型码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。(用来取得中断程序在内存中的位置

其实就是:

  1. get N
  2. pushf
  3. TF=0, IF=0
  4. push CS
  5. push IP
  6. (IP)=(N*4), (CS)=(N/4+2)
  7. 执行程序!

12.2 中断处理程序,安装,中断向量等

这个只是稍微述说,额,因为我不太能用到这个,作为了解而已。
中断处理程序就如同除法溢出程序一样(输出‘…overflow’),如何处理后续的事物做中断处理,然后返回系统程序。
而如果想要自己编一个中断处理程序,首先

  1. 把中断码想好,把程序要放的地方想好,*放入中断向量表
  2. 编程序,安装(写到相应位置)
  3. 测试

这里当然要用到之前学的几乎所有内容(包括ret,movsb等)

12.3 单步中断和TF,DF标志

说到这里,你看标题就知道,TF肯定与单步中断有关,具体有什么关系呢?
首先说说单步中断,也就是不停止(退出)运行的程序,却能返回其实时的寄存器信息(也就是还在运行,只不过暂时停止了)。
而TF就是实现单步中断的重要标志,当TF=1时将引发单步中断,所以要想中断就先置TF=1。
这就是为什么中断执行(刚开始)就要把TF置0,就是为了防止步步中断

12.4 int指令

int指令引发的内中断很重要。虽然其过程与12.1所说的别无二致。
接下来就深入理解理解int、iret和栈:

12.4.1 int、iret和栈

第十三章 汇编语言重点知识总结

13.1寄存器与存储器

  1. 寄存器功能
  • 寄存器的一般用途和专用用途
  • CS:IP 控制程序执行流程
  • SS:SP 提供堆栈栈顶单元地址
  • DS:BX(SI,DI) 提供数据段内单元地址
  • SS:BP 提供堆栈内单元地址
  • ES:BX(SI,DI) 提供附加段内单元地址
  • AX,CX,BX 和CX 寄存器多用于运算和暂存中间计算结果,但又专用于某些指令(查阅指令
    表)。
  • PSW 程序状态字寄存器只能通过专用指令(LAHF, SAHF)和堆栈(PUSHF,POPF)进行存取。
  1. 存储器分段管理
  • 解决了16 位寄存器构成20 位地址的问题
  • 便于程序重定位
  • 20 位物理地址=段地址* 16 + 偏移地址
  • 程序分段组织: 一般由代码段,堆栈段,数据段和附加段组成,不设置堆栈段时则使用系统
    内部的堆栈。
  1. 堆栈
  • 堆栈是一种先进后出的数据结构, 数据的存取在栈顶进行, 数据入栈使堆栈向地址减小
    的方向扩展。
  • 堆栈常用于保存子程序调用和中断响应时的断点以及暂存数据或中间计算结果。
  • 堆栈总是以字为单位存取

13.2 指令系统与寻址方式

  1. 指令系统
  • 计算机提供给用户使用的机器指令集称为指令系统,大多数指令为双操作数指令。执行指令
    后,一般源操作数不变,目的操作数被计算结果替代。
  • 机器指令由CPU 执行,完成某种运算或操作,8086/8088 指令系统中的指令分为6 类: 数据传
    送,算术运算,逻辑运算,串操作,控制转移和处理机控制。
  1. 寻址方式
  • 寻址方式确定执行指令时获得操作数地址的方法
  • 分为与数据有关的寻址方式(7 种)和与转移地址有关的寻址方式(4)种。
  • 与数据有关的寻址方式的一般用途:

(1) 立即数寻址方式–将常量赋给寄存器或存储单元
(2) 直接寻址方式–存取单个变量
(3) 寄存器寻址方式–访问寄存器的速度快于访问存储单元的速度
(4) 寄存器间接寻址方式–访问数组元素
(5) 变址寻址方式
(6) 基址变址寻址方式
(7) 相对基址变址寻址方式
(5),(6),(7)都便于处理数组元素

. 与数据有关的寻址方式中,提供地址的寄存器只能是BX,SI,DI 或BP
. 与转移地址有关的寻址方式的一般用途:

(1) 段内直接寻址–段内直接转移或子程序调用
(2) 段内间接寻址–段内间接转移或子程序调用
(3) 段间直接寻址–段间直接转移或子程序调用
(4) 段间间接寻址–段间间接转移或子程序调用

13.3 汇编程序和汇编语言

  1. 汇编程序
  • 汇编程序是将汇编语言源程序翻译成二进制代码程序的语言处理程序,翻译的过程称为汇
    编。
  1. 汇编语言
  • 汇编语言是用指令助记符,各种标识变量,地址,过程等的标识符书写程序的语言, 汇编语言
    指令与机器指令一一对应。
  • 伪指令,宏指令不是由CPU 执行的指令,而是由汇编程序在汇编期间处理的指令。
  • 伪指令指示汇编程序如何完成数据定义,存储空间分配,组织段等工作。
  • 宏指令可简化程序并减少程序书写量。
  • 条件汇编伪指令的功能是确定是否汇编某段源程序,而不是实现程序分支,对未汇编的程序
    将不产生相应的目标代码。
  • 结构作为一种数据结构可将一组类型不同但有逻辑关联的数据组织在一起,便于整体处理
    数据。
  • 记录可用于提高存储单元的利用率,将若干不足一个字节或字且有逻辑关联的信息压缩存
    放在一个字节或字中。
  • 指令中的表达式在汇编期间计算,并且只能对常量或地址进行计算。

13.4 程序设计基础

  1. 分支程序设计
  • 程序分支由条件转移指令或无条件转移指令实现
  • 存放若干目的转移地址或跳转指令的跳转表常用于实现多路分支
  • 条件转移指令只能实现偏移量为-128 至+127 字节范围的转移
  • 无条件转移指令根据寻址方式可实现短转移(偏移量为-128 至+127 字节),段内转移,段间
    转移。
  1. 循环程序设计
  • 可由循环控制指令或条件转移指令组织循环结构
  • 内层循环结构必须完全包含在外层循环结构内,并不能发生从循环结构外向循环结构内的
    转移。
  1. 子程序设计
  • 子程序中应保护寄存器内容,并正确使用堆栈, 成对执行PUSH 和POP 指令,保证执行RET
    指令时堆栈栈顶为返回地址。
  • 主程序可通过寄存器,参数表,或堆栈传递参数给子程序
  1. EXE 文件和COM 文件
  • 二者都是可执行文件
  • COM 文件源程序的特点是: 第一条可执行指令的起始存放地址必须是100H,不能分段,不用
    定义堆栈,所有过程为NEAR 类型,直接用INT 20H 指令返回DOS。
  1. DOS 功能调用与BIOS 中断调用
  • 二者都是完成DOS 系统提供给用户的输入/输出等常用功能,通过执行软中断指令完成一
    次软中断服务。
  • DOS 功能调用的中断服务程序是操作系统的一部分,存于RAM 中; 而BIOS 中断调用的中
    断服务程序存放在ROM 中。

13.5 输入/输出与中断系统

  1. 输入/输出的方式
  • 程序直接I/O 方式: 用IN 和OUT 指令直接在端口级上进行I/O 操作,数据传送方式分为无
    条件传送方式和查询传送方式。
  • 中断传送方式: 由CPU 响应中断请求完成中断服务。
  • DMA 传送方式: 直接在存储器与外设之间传送数据。
  1. 有关中断的概念
  • 中断、中断源、中断请求、中断服务、中断向量、中断向量表、中断响应过程、中断指令、
    开中断、关中断、内部中断、外部中断、可屏蔽中断、非屏蔽中断。
  1. 键盘I/O、显示器I/O 操作
  • 键盘的输入操作用BIOS 的16H 中断调用控制,也可直接访问60H 端口(数据端口), 61H 端
    口(状态端口)检测键盘的按键操作。
  • 对于特殊键(如Shift , Ctrl , Alt , NumLock , ScrollLock 等键)的按动情况,可以直接从来
    40:17H 单元取得有关信息。
  • 显示器的图形显示可以用BIOS 的10H 中断调用实现,另一种速度更快的方法是直接读写
    视频缓冲区。
  1. 打印机I/O 操作由INT 17H 中断调用实现, 串行通讯口操作由INT 14H 中断调用实现。
    CLD Clear the direction flag (set to forward direction)
    将方向标志置0,使si 和di 增量,串处理从低地址向高地址处理

13.6 8088 汇编速查手册

13.6.1 数据传输指令

它们在存贮器和寄存器、寄存器和输入输出端口之间传送数据.

  1. 通用数据传送指令.
指令 描述
MOV 传送字或字节.
MOVSX 先符号扩展,再传送.
MOVZX 先零扩展,再传送.
PUSH 把字压入堆栈.
POP 把字弹出堆栈.
PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI 依次压入堆栈.
POPA 把DI,SI,BP,SP,BX,DX,CX,AX 依次弹出堆栈.
PUSHAD 把EAX,ECX,EDX, EBX,ESP,EBP,ESI,EDI 依次压入堆栈.
POPAD 把EDI,ESI,EBP,ESP,EBX,EDX, ECX,EAX 依次弹出堆栈.
BSWAP 交换32 位寄存器里字节的顺序
XCHG 交换字或字节.( 至少有一个操作数为寄存器,段寄存器不可作为操作数)
CMPXCHG 比较并交换操作数.( 第二个操作数必须为累加器AL/AX/EAX )
XADD 先交换再累加.( 结果在第一个操作数里)
XLAT 字节查表转换.── BX 指向一张256 字节的表的起点, AL 为表的索引值(0-255,即0-FFH); 返回AL 为查表结果. ( [BX+AL]->AL )
  1. 输入输出端口传送指令.
指令 描述
IN I/O 端口输入. ( 语法: IN 累加器, {端口号│DX} )
OUT I/O 端口输出. ( 语法: OUT {端口号│DX},累加器)
输入输出端口由立即方式指定时, 其范围是0-255; 由寄存器DX 指定时,其范
围是0-65535.
  1. 目的地址传送指令.
指令 描述 例子
LEA 装入有效地址. 例: LEA DX,string ;把偏移地址存到DX.
LDS 传送目标指针,把指针内容装入DS. 例: LDS SI,string ;把段地址:偏移地址存到DS:SI.
LES 传送目标指针,把指针内容装入ES. 例: LES DI,string ;把段地址:偏移地址存到ES:DI.
LFS 传送目标指针,把指针内容装入FS. 例: LFS DI,string ;把段地址:偏移地址存到FS:DI.
LGS 传送目标指针,把指针内容装入GS. 例: LGS DI,string ;把段地址:偏移地址存到GS:DI.
LSS 传送目标指针,把指针内容装入SS. 例: LSS DI,string ;把段地址:偏移地址存到SS:DI.
  1. 标志传送指令.
指令 描述
LAHF 标志寄存器传送,把标志装入AH.
SAHF 标志寄存器传送,把AH 内容装入标志寄存器.
PUSHF 标志入栈.
POPF 标志出栈.
PUSHD 32 位标志入栈.
POPD 32 位标志出栈.

13.6.2 算术运算指令

指令 描述
ADD 加法.
ADC 带进位加法.
INC 加1.
AAA 加法的ASCII 码调整.
DAA 加法的十进制调整.
SUB 减法.
SBB 带借位减法.
DEC 减1.
NEC 求反(以0 减之).
CMP 比较.(两操作数作减法,仅修改标志位,不回送结果).
AAS 减法的ASCII 码调整.
DAS 减法的十进制调整.
MUL 无符号乘法.
IMUL 整数乘法.以上两条,结果回送AH 和AL(字节运算),或DX 和AX(字运算),
AAM 乘法的ASCII 码调整.
DIV 无符号除法.
IDIV 整数除法.以上两条,结果回送:商回送AL,余数回送AH, (字节运算);或商回送AX,余数回送DX, (字运算).
AAD 除法的ASCII 码调整.
CBW 字节转换为字. (把AL 中字节的符号扩展到AH 中去)
CWD 字转换为双字. (把AX 中的字的符号扩展到DX 中去)
CWDE 字转换为双字. (把AX 中的字符号扩展到EAX 中去)
CDQ 双字扩展. (把EAX 中的字的符号扩展到EDX 中去)

13.6.3 逻辑运算指令

指令 描述
AND 与运算.
OR 或运算.
XOR 异或运算.
NOT 取反.
TEST 测试.(两操作数作与运算,仅修改标志位,不回送结果).
SHL 逻辑左移.
SAL 算术左移.(=SHL)
SHR 逻辑右移.
SAR 算术右移.(=SHR)
ROL 循环左移.
ROR 循环右移.
RCL 通过进位的循环左移.
RCR 通过进位的循环右移.

以上八种移位指令,其移位次数可达255 次.
移位一次时, 可直接用操作码. 如SHL AX,1.
移位>1 次时, 则由寄存器CL 给出移位次数.
如MOV CL,04
SHL AX,CL

13.6.4 串指令

指令 描述
DS:SI 源串段寄存器:源串变址.
ES:DI 目标串段寄存器:目标串变址.
CX 重复次数计数器.
AL/AX 扫描值.
D 标志0 表示重复操作中SI 和DI 应自动增量; 1 表示应自动减量.
Z 标志用来控制扫描或比较操作的结束.
MOVS 串传送.( MOVSB 传送字符. MOVSW 传送字. MOVSD 传送双字. )
CMPS 串比较.( CMPSB 比较字符. CMPSW 比较字. )
SCAS 串扫描.把AL 或AX 的内容与目标串作比较,比较结果反映在标志位.
LODS 装入串.把源串中的元素(字或字节)逐一装入AL 或AX 中.( LODSB 传送字符. LODSW 传送字. LODSD 传送双字. )
STOS 保存串. 是LODS 的逆过程.
REP 当CX/ECX<>0 时重复.
REPE/REPZ 当ZF=1 或比较结果相等,且CX/ECX<>0 时重复.
REPNE/REPNZ 当ZF=0 或比较结果不相等,且CX/ECX<>0 时重复.
REPC 当CF=1 且CX/ECX< >0 时重复.
REPNC 当CF=0 且CX/ECX<>0 时重复.

13.6.5 程序转移指令

  1. 无条件转移指令(长转移)

  2. 指令 描述
    JMP 无条件转移指令
    CALL 过程调用
    RET/RETF 过程返回.
  3. 条件转移指令(短转移,-128 到+127 的距离内)
    ( 当且仅当(SF XOR OF)=1 时,OP1<OP2 )

指令 描述
JA/JNBE 不小于或不等于时转移.
JAE/JNB 大于或等于转移.
JB/JNAE 小于转移.
JBE/JNA 小于或等于转移.
以上四条,测试无符号整数运算的结果(标志C 和Z).
JG/JNLE 大于转移.
JGE/JNL 大于或等于转移.
JL/JNGE 小于转移.
JLE/JNG 小于或等于转移.
以上四条,测试带符号整数运算的结果(标志S,O 和Z).
JE/JZ 等于转移.
JNE/JNZ 不等于时转移.
JC 有进位时转移.
JNC 无进位时转移.
JNO 不溢出时转移.
JNP/JPO 奇偶性为奇数时转移.
JNS 符号位为”0” 时转移.
JO 溢出转移.
JP/JPE 奇偶性为偶数时转移.
JS 符号位为”1” 时转移.
  1. 循环控制指令(短转移)
指令 描述
LOOP CX 不为零时循环.
LOOPE/LOOPZ CX 不为零且标志Z=1 时循环.
LOOPNE/LOOPNZ CX 不为零且标志Z=0 时循环.
JCXZ CX 为零时转移.
JECXZ ECX 为零时转移.
  1. 中断指令
指令 描述
INT 中断指令
INTO 溢出中断
IRET 中断返回
  1. 处理器控制指令
指令 描述
HLT 处理器暂停, 直到出现中断或复位信号才继续.
WAIT 当芯片引线TEST 为高电平时使CPU 进入等待状态.
ESC 转换到外处理器.
LOCK 封锁总线.
NOP 空操作.
STC 置进位标志位.
CLC 清进位标志位.
CMC 进位标志取反.
STD 置方向标志位.
CLD 清方向标志位.
STI 置中断允许位.
CLI 清中断允许位.

13.6.6 伪指令

指令 描述
DW 定义字(2 字节).
PROC 定义过程.
ENDP 过程结束.
SEGMENT 定义段.
ASSUME 建立段寄存器寻址.
ENDS 段结束.
END 程序结束.