本文系转载他人 感谢原文作者
https://blog.csdn.net/Gibbs_p/article/details/72257840
8086汇编
本笔记是笔者观看小甲鱼老师(鱼C论坛)《零基础入门学习汇编语言》系列视频的笔记,是王爽所著的《汇编语言》的简单版,感谢ttps://my.csdn.net/baidu_36313748的,在建议此感谢小甲鱼和像他一样共享资源、帮助他人的筒子们==本文比较长,由于笔者个人能力有限,错漏在所难免,欢迎读者们批评指正。
一、基础知识
引言
- 基本了解硬件系统的结构;
- 利用硬件系统的编程结构和指令集,有效灵活地控制系统进行工作。
1.1 机器语言
- 机器语言是机器指令的集合。电子计算机的机器指令是一系列二进制数字。计算机将之转换为一系列高低电平脉冲信号来驱动硬件工作的。
1.2 汇编语言的产生
- 由于机器语言指令都是由01组成,难以编写,记忆和维护程序.所以汇编语言为了解决这一问题产生。汇编语言的主体是汇编指令,汇编指令是机器指令的助记符。
- 寄存器: CPU中存储数据的器件,一个CPU中有多个寄存器。
1.3 汇编语言的组成
- 1、汇编指令(机器码的助记符,有对应的机器码);
- 2、伪指令(由编译器执行)和其他符号(由编译器识别)。
1.4 存储器
- CPU工作需要指令和数据,指令和数据存储在存储器中。
1.5 指令和数据
- 在内存或者磁盘中存储的都是为二进制信息,指令和数据由我们设定(走的总线)。
1.6 存储单元
- 存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号。
- B、KB、MB、GB、TB等单位。
1.7 CPU对存储器的读写
CPU要对数据进行读写,必须和外部器件进行以下三类信息的交互:
- 1、存储单元的地址(地址信息);
- 2、器件的选择、读或写命令(控制信息);
- 3、读或写的数据(数据信息) 。
总线是连接CPU和其他芯片的导线,逻辑上分为地址总线、数据总线、控制总线。
CPU从内存单元中读写数据的过程:
- 1、CPU通过地址线将地址信息发出;
- 2、CPU通过控制线发出内存读命令,选中存储器芯片,并通知它将要从中读或写数据;
- 3、存储器将相应的地址单元中的数据通过数据线送入CPU或CPU通过数据线将数据送入相应的内存单元。
1.8 地址总线
- CPU是通过地址总线指定存储单元,地址总线传送的能力决定了CPU对存储单元的寻址能力。(一般32位CPU,寻址能力为2^32=4G)
1.9 数据总线
- CPU通过数据总线来与内存等器件进行数据传送,数据总线的宽度决定了CPU和外界的数据传送速度。
1.10 控制总线
- 控制总线是一些不同控制的集合,CPU通过控制总线对外部器件的控制。控制总线的宽度决定了CPU对外部器件的控制能力。
小结
- 1、汇编指令时机器指令的助记符,与机器指令一一对应。
- 2、每一种CPU都有自己的汇编指令集。
- 3、CPU可以直接使用的信息在存储器中存放。
- 4、在存储器中指令和数据都是二进制信息。
- 5、存储单元从0开始顺序编号。
- 6、一个存储单元可以存储8个bit。
- 7、B、KB、MB、GB等单位之间的转换。
- 8、CPU管脚和总线相连。总线的宽度表示CPU不同方面的性能:
- 地址总线的宽度决定了CPU的寻址能力;
- 数据总线的宽度决定了CPU与其他器件进行一次数据传送的量;
- 控制总线宽度决定了CPU对系统中其他器件的控制。
检测点 1.1
1.11 内存地址空间(概述)
- CPU可寻的内存单元构成这个CPU的内存地址空间。例如一个CPU的地址总线宽度为10,那么可以寻址的1024个内存单元构成了这个CPU的内存空间。
1.12 主板
- 主板主板,主要的电路板 :laughing:
1.13 接口卡
- CPU通过接口卡间接控制外部设备。
1.14 各类存储器
- 随机存储器RAM(主板上的RAM、拓展插槽上的RAM和接口卡上的RAM)和只读存储器器ROM(装有BIOS的ROM)。
1.15 内存地址空间
各类存储器在物理上是独立的,但是:
- 1、都和CPU的总线相连;
- 2、 CPU对他们进行读或写的时候都通过控制线发出的内存读写命令。
- 不同的计算机系统的内存地址空间分配情况是不同的。
二、寄存器(CPU的工作原理)
引言
- CPU由运算器、控制器、寄存器 等器件组成,靠内部总线相连。
- 内部总线实现CPU内部各器件之间的联系;外部总线实现CPU和主板上其他器件的联系。
- 在CPU中:
- 运算器进行信息处理;
- 寄存器进行信息存储;
- 控制器控制各种器件进行工作;
- 内部总线连接各种器件在它们之间进行数据的传送。
2.1 通用寄存器
- 8086有14个寄存器:
- AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、CS、ES、PSW。
- AX、BX、CX、DX通常用来存放一般性数据,被称为通用寄存器。
- 16位寄存器所能存储的数据最大值为2^16^-1 。
- 为保证兼容性,8086 CPU的通用寄存器可以分为两个独立的8位寄存器使用。例: AX可分为AH和AL。
2.2 字在寄存器中的存储
8086 CPU所有的寄存器是16位,可以存放2个字节(一个字)。
一字节由8 bit 组成,可以存在8位寄存器中。
字(word)是两字节,16位。
2.3 几条汇编指令
汇编指令对大小写不敏感
汇编指令举例
汇编指令 控制CPU完成的操作 用高级语言的语法描述 mov ax,18 将8送入AX AX=18 mov ah,78 将78送入AH AH=78 add ax,8 将寄存器AX中的数值加上8结果存入AX中 AX=AX+8 mov ax,bx 将寄存器BX中的数据送入寄存器AX AX=BX add ax,bx 将AX,BX中的内容相加结果存入AX中 AX=AX+BX 检测点 2.1
2.4 物理地址
- 所有的内存单元构成一个一维的线性存储空间。
- CPU访问内存单元时要给出内存单元的唯一地址就是物理地址。
2.5 16位结构的CPU
- 1、运算器一次最多可以处理16位数据。
- 2、 寄存器的最大宽度为16位。
- 3、寄存器和运算器之间的通路是16位。
2.6 8086 CPU给出物理地址的方法
- 8086有20位的地址总线,可以传送20位地址,寻址能力为1M;但8086内部为16位结构,只能传送16位的地址。
- 8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。
- 8086CPU读写内存的步骤:
- 1、CPU中的相关部件提供段子和偏移地址这两个16位的地址;
- 2、段地址和偏移地址通过内部总线送入到一个称为地址加法器的部件;
- 3、地址加法器将两个16位地址合并成一个20位的地址;
- 4、地址加法器通过内部总线将20位物理地址送送入输入输出地址;
- 5、输入输出控制电路将20位物理地址送上地址总线;
- 6、20位物理地址被地址总线传送到存储器。
- 地址加法器工作原理:物理地址=段地址*16+偏移地址。
- 段地址*16就是数据左移4位(二进制)移位位数 二进制 十六进制 十进制 0 10B 2H 2 1 100B 4H 4 2 1000B 8H 8 3 10000B 10H 16 4 100000B 20H 32 - 一个数据的二进制形式左移N位,相当于该数据乘以2的N次方。一个数据X进制形式左移N位,相当乘以NX。
2.7 段地址*16+偏移地址=物理地址
- CPU可以通过不同的段地址和偏移地址形成一个相同的物理地址。
段地址*16是移位
2.8 段的概念
人为定义的,将若干地址连续的内存单元看作一个段。用段地址*16定位段的起始地址(基址),用偏移地址定位段中的内存单元。
一个段的起始地址是16的倍数。偏移地址为16位,寻址能力为64K,所以段的最大长度也是64K。
检测点 2.2
2.9 段寄存器
- 8086 CPU有4个段寄存器:CS(代码段)、DS(数据段)、SS(堆栈段)、ES(附加段),这4个段提供给8086CPU内存单元的段地址。
2.10 CS和IP
- CS(代码段寄存器) 和IP(指令指针寄存器) 是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。在任意时刻CPU将CS:IP指向的内容当作指令执行。
8086CPU工作过程的简要概述:
1、从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器;
8086PC机刚开始启动时,CPU从内存FFFF0h单元中读取指令执行,FFFF0h单元中的指令时8086PC机开机后执行的第一条指令。
2、 IP=IP+所读取指令的长度,从而正确的指向下一条指令;
- 3、执行指令。转到步骤1,周而复始。
2.11 修改CS、IP的指令
- mov指令(传送指令) 可以改变8086CPU大部分寄存器的值,但不能用于设置CS、IP的值。
- jmp指令(转移指令) 可以用来同时修改CS和IP的值,格式为
jmp 段地址:偏移地址;同时修改CS和IP jmp 某一合法寄存器;则是仅修改IP
- 1
- 2
2.12 代码段
- 对于8086PC机,在编程时可以将长度为N(N小于等于64KB)的一组代码存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是用来存放代码的,从而定义了一个代码段。
- 利用CS:IP来指向内存单元从而让CPU执行其中的内容。
检测点 2.3
使用Debug
windows xp系统自带debug,请使用xp以上系统的读者执行自行下载debug.exe和dosbox,使用方法笔者不再赘述,在dosbox中可以使用debug。
- 可以使用汇编金手指查阅指令。
- R命令查看、改变CPU寄存器的内容;
- D命令查看内存中的内容;
- E命令改写内存中的内容;
- U命令将内存中的机器指令翻译成汇编指令;
- T命令执行一条机器指令;
- G命令跳转到偏移地址;
- P命令结束循环或者是int 21H时是退出程序;
- A命令是以汇编指令的格式在内存中写入一条机器指令。
三、寄存器(内存访问)
3.1 内存中字的存储
- 字是两个字节,要用两个地址连续的内存来存放,字的低位字节存在低地址中,高位字节存放在高地址单元中。
3.2 DS和[address]
- DS通常存放要访问的数据的段地址。
8086 CPU由于硬件的设计不支持将数据直接送入段寄存器的操作。
数据 -> 通用寄存器 -> 段寄存器
[ ]里边的数据代表偏移地址值
- mov指令:
- 将数据直接送入寄存器;
- 将一个寄存器或内存单元中的内容送入另一个寄存器;
- mov指令格式:
mov 寄存器名,内存单元
- 1
3.3 字型的传送
- 高地址单元和高8位寄存器,低地址单元和低8位寄存器相对应。
3.4 mov、add、sub指令
- 有两个操作对象,jmp只有一个操作对象。
- 使用汇编金手指查阅指令
- mov指令的几种形式
mov 寄存器,数据;mov ax,8 mov 寄存器,寄存器;mov ax,bx mov 寄存器,内存单元;mov ax,[0] mov 内存单元,寄存器;mov [0],ax mov 段寄存器,寄存器;mov ds,ax mov 寄存器,段寄存器;mov ax,ds ……
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- add指令的几种形式
add 通用寄存器,数据 add 通用寄存器,通用寄存器 add 通用寄存器,内存单元 add 内存单元,寄存器
- 1
- 2
- 3
- 4
- sub指令的几种形式
sub 通用寄存器,数据 sub 通用寄存器,通用寄存器 sub 通用寄存器,内存单元 sub 内存单元,通用寄存器
- 1
- 2
- 3
- 4
3.5 数据段
- 对于8086PC机,在编程时可以将长度为N(N小于等于64KB)的一组代码存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是用来存放数据的,从而定义了一个数据段。
- 可以通过在DS中存放数据段的段地址,用相关的指令访问数据段中的具体单元来访问数据段中的数据。
检测点 3.1
3.6 栈
- 具有特殊的访问方式的存储空间,也是内存空间的一部分,数据先进后出。
- 有两个基本操作:
- 入栈:将一个新的元素放到栈顶;
- 出栈:从栈顶取出一个元素。
- 栈顶元素最后入栈最先出栈。
3.7 8086 CPU提供的栈机制
- 现今的CPU都有栈的设计,基于8086CPU编程可以将一段内存当作栈来使用。
- 8086CPU的入栈(PUSH)和POP(出栈),以字为单位。
- push ax 将寄存器ax中的数据送入栈
- pop ax 从栈顶取出数据送入ax
- 段寄存器SS存放栈顶的段地址,寄存器SP存放栈顶的偏移地址。任意时刻SS:SP指向栈顶元素。push时SP先自减法后写内存,pop先读内存sp后自加。
- pop之后数据还是存在内存中,push时覆盖。
CS和IP存放当前指令的段地址和偏移地址。
3.8 栈顶越界的问题
- 栈是空的,则SP指向栈底+1的内存。
- 8086 CPU只纪录栈顶,栈空间由自己控制。栈顶越界问题导致溢出漏洞。
- 8086CPU只考虑当前的情况:
- 当前栈顶在何处;
- 当前要执行的指令时哪一条。
3.9 push、pop指令
- 可以直接对段寄存器使用。
;push和pop格式 push 寄存器 pop 寄存器 push 段寄存器 pop 段寄存器 push 内存单元 pop 内存单元
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 通用寄存器命名是x结尾的,段寄存器是以s结尾。
- CPU在执行指令时,数据的段地址是从DS中获得,代码是在CS中获得,栈地址是从SS获得。
3.10 栈段
- 对于8086PC机,在编程时可以将长度为N(N小于等于64KB)的一组代码存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是当作栈来用,从而定义了一个栈段。
- 寄存器清零可用sub ax,ax或者直接赋值0,常见的也有使用xor。
- 当栈空间定义为最大时,栈为空时SP=0。
检测点 3.2
四、第一个程序
引言
编写完成的汇编语言程序,用编译器编译成可执行文件并在操作系统中运行。
4.1 一个源程序从写出到执行的过程
- 编写
- 用编辑器(Sublime Text、Nodepad++、UltraEdit)编写,文件后缀为.asm。
编译链接
- 使用MASM.EXE编译生产obj(目标文件)。masm也请读者自行搜索下载。
- LINKE.EXE对目标文件进行连接生产可在操作系统中直接运行的可执行文件。
可执行文件包含程序(机器码)、数据(源程序中定义的数据)和相关的描述信息。
执行
- 操作系统中依照可执行文件中的描述信息将可执行文件中的机器码和数据加载入内存并进行相关的初始化,然后CPU执行。
4.2 源程序
- 汇编指令:有对应的机器码的指令,编译为机器码被CPU执行
- 伪指令:没有对应的机器码,不被CPU所执行,由编译器执行来进行相关的编译工作。
- segment和ends是用来定义一个段的,是成对使用的伪指令,再写可被编译器编译的汇编程序是必须要用的。
assume cs:codesg ;假设代码段的名称为codesg codesg segment ;定义一个codesg段 mov ax,0123H mov bx,0456H add ax,bx add ax,ax mov ax,4c00h int 21h codesg ends ;codesg段结束 end ;是个伪指令,程序的结束标记
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
assume用来加上某一段寄存器和程序中的某一用segment……ends定义的段相关联。通过assume说明这种关联,在需要的情况下编译程序可以将段寄存器和某一个具体的段相联系。
- 一个汇编程序是由多个段组成。一个有意义的汇编程序中至少要用一个段来存放代码。
- 程序与源程序
- 标号:指代地址
程序的结构
小练习:
;编程运算2^3 assume cs:abc ;段与寄存器关联 abc segment ;定义一个段,名称为abc mov ax,2;写入汇编指令 add ax,ax add ax,ax abd ends end ;程序结束处
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 程序的返回:一个程序结束后将CPU的控制权交还给使它得以运行的程序的过程。应该在程序的末尾添加返回的程序段。
codesg:放在segment前面,作为一个段的名称,这个段的名称最终将被编译、连接程序,称为一个段的段地址 。
mov ax,4c00H int 21H ;第21号中断 ;这两条指令说实现的功能就是程序返回。
- 1
- 2
- 3
- 语法错误和逻辑错误
4.3 编辑源程序
- 使用编辑器编辑,扩展名为.asm
assume cs:ABC ABC segment mov ax,2 add ax,ax add ax,ax mov ax,4c00H int 21h ABC ends end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
4.4 编译
masn和 1.asm在同一目录中,dos下使用masm 1.asm命令即可生产1.obj文件。
4.5 连接
link 1.obj,生成exe文件,摁enter忽略编译程序提示输入的信息。
当源程序很大时,可以将它分成多个源程序文件编译,每个源程序编译成目标文件后再用连接程序将他们连接到一起,生成一个可执行文件。或者程序中调用了某个库文件中的子程序,需要将这个库文件和该目标文件连接到一起,生成一个可执行文件。或者一个源程序编译后得到存有机器码的目标文件,目标文件中的有些内容还不能直接生成可执行文件,连接程序将此内容处理为最终的可执行文件信息。
4.6 简化编译和连接
- 使用ml命令,ml 1.asm
4.7 exe的执行
- 为兼容16位的程序,使用dosbox运行。
4.8 可执行文件中的程序转入内存并运行的原理
- 在dos中可执行文件中的程序p1若要运行吗必须有一个正在运行的程序p2将p1从可执行文件中加载如内存,将CPU的控制权交给它,p1才能得以运行;当p1运行完毕后,应该将CPU的控制权交还给使它de’yi 运行的程序p2。
- 汇编程序从写出到执行的过程:编程 -> 编译 -> 连接 -> 加载 -> 内存中的程序 -> 运行
在dos系统中.exe文件中的加载过程
4.9 程序执行过程的跟踪
- 使用debug(xp以上的系统在dosbox中使用)来跟踪一个程序的运行过程。
五、[BX]和loop指令
引言
- 约定符号()来表示一个寄存器或者一个内存单元中的内容。例如(ax)=0010H表示ax中的内容为0010H;(21000H)=0010H,表示2000:1000处的内容为0010H。
- 约定符号idata表示常量。
5.1 [BX]
- inc指令是自增1的意思
- 和[0]有些类似,[0]表示内存单元,它的偏移地址是0。[bx]也是表示一个内存单元,它的内存偏移地址在bx中。
mov bx,0 mov ax,[bx] mov al,[bx]
- 1
- 2
- 3
- 用以下两种信息描述一个内存单元:
- 1、内存单元的地址;
- 2、内训单元的长度(类型)。
我们用[0]表示一个内训单元时,0表示单元的偏移地址,段地址默认在DS中,单元的长度(类型)可以由具体指令中的其他的操作对象(比如说寄存器)指出。
mov ax,[0];0对应的字单元,主要单位要看操作对象(寄存器) mov al,[0];字节
- 1
- 2
5.2 loop指令
- 指令的格式是loop 标号。CUP执行loop指令时要进两步操作:
- CX中存放循环的次数,执行时CX中的内容自减1。相当于C的do while
- 判断CX中的值,不为0则转至标号处执行程序,为0则向下执行。
- 通常loop指令来实现循坏功能CX中存放循环的次数。
assume cs:code code segment mov ax,2 add ax,ax mov ax,4c00H int 21H code ends end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
;计算2^3 assume cs:code code segment mov ax,2 add ax,ax add,ax,ax mov ax,4c00H int 21h code ends end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
;计算2^12 assume cs:code code segment start: mov ax,2 mov cx,11 p:add,ax,ax loop p;p是标号 mov ax,4c00H;masm默认数字是十进制 int 21H code ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
;编程计算123*236,结果放在ax中 assume cs:code code segment start:mov ax,0 mov cx,236 an:add ax,123 loop an mov ax,4c00H int 21H code ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
assume cs:code code segment start:mov ax,0 mov cx,123 pa:add ax,236 loop pa mov ax,4c00H int 21H code ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
5.3 在Debug中跟踪用loop指令实现的循环程序
- 注意:在汇编源程序中数据不能以字母开头,有字母的在前面加0处理。
- t命令单步执行、G命令和P命令。
- 使用汇编金手指查阅指令。
5.4 Debug和汇编编译器Masm对指令的不同处理
- Degug中mov ax,[0],表示将ds:0处的数据存入al中。ah=0,因为一个内存单元是8位的,ax是16位的,同位存储。而编译器[0]会被当作0处理
将内存2000:0、2000:1、2000:2、2000:3单元中的数据(字节)送入阿al、bl、cl、dl中。
- debug中:
- 在MASM中:
- 要在编译器中实现用偏移地址[]中的内容传送先bx来代替,mov 偏移地址,bx 再 mov al,[bx]。如要直接使用[ ]则要加上段地址ds:[偏移地址]
- 在MASM中:
mov al,[0] ;将al赋值0 mov al,ds[0] ;将al赋值段地址为ds,偏移地址为0的内存单元中的内容 mov al,[bx] ;默认段地址为ds,将al赋值偏移地址为bx mov al,ds:[bx] ;将al赋值段地址为ds,偏移地址为bx
- 1
- 2
- 3
- 4
5.5 loop和[BX]的联合应用
- 可以用循环来解决处理地址连续的内存单元中的数据的问题,用变量来给出内存单元的地址。
5.6 段前缀
- 出现在访问内存单元的指令中用显式地指明内存单元的段地址的ds、cs、ss、es称为段前缀。没有显式地给出内存单元的段地址则默认在ds中。
5.7 一段安全的空间
在8086模式中,随意向一段内存空间写入数据是危险的,因为这段空间中可能存放着重要的系统数据或代码。
assume cs:code code segment mov ax,0 mov ds,ax mov ds:[26H],ax mov ax,4c00H int 21H code ends end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
但笔者在练习的时候出现dosbox下debug卡死
- dos下0:200H~0:2FFH的256个字节的空间是安全的,dos和其他合法程序一般都不会使用这段空间。内存0000:0000~0000:03FF大小为1kb的空间是系统存放中断处理程序入口地址的中断向量表。一般情况下0:200H~0:2FFH的256个字节的空间所对应的中断向量表都是空的,操作系统和其他应用程序都不占用。
5.8 段前缀的使用
- 将内存ffff:0~ffff:b段单元中的数据拷贝到0:200 ~ 0:20b单元中
assume cs:code code segment mov bx,0 ;(bx)=0,偏移地址从0开始 mov cx,12 ;(cx)=12,循环12次 s: mov ax,offffh mov ds,ax ;(ds)=0ffffh mov dl,[bx] ;(ds)=((ds)*16+(bx)),将ffff:bx中的数据送入dl mov ax,0020h mov ds,ax ;(ds)=0020h mov [bx],dl ;((ds)*16+(bx))=dl,将数据送入0020:bx inc bx ;(bx)=(bx)+1 loop s mov ax,4c00h int 21h code ends end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 两个内存单元相差64KB则不再同一个段里,需要设置ds的值两次,效率不高。
- 使用 es(附加段)
;优化后的代码,优化了两次设置ds assume cs:code code segment mov ax,offffh mov ds,ax ;(ds)=0ffffh mov ax,0020h mov es,ax ;(es)=0020H mov bx,0 ;(bx)=0,此时ds:bx指向ffff:0,es:bx指向0020:0 mov cx,12 ;(cx)=12,循环12次 s: mov dl,[bx] ;(ds)=((ds)*16+(bx)),将ffff:bx中的数据送入dl mov es:[bx],dl ;((es)*16+(bx))=dl,将数据送入0020:bx inc bx ;(bx)=(bx)+1 loop s mov ax,4c00h int 21h code ends end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
六、包含多个段的程序
6.1在代码段中使用数据
- 编程计算0123H、0456H,0abxH、0defH、0fesH、0cbaH、0987H这8个数据的和,结果存放在ax中:
assume cs:codesg codesg segment dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H ;dw,define word,定义字型数据,db定义字节型数据 ;由于数据在代码段中,所以段地址是CS ;dw定义的数据在最开始的地方,所以偏移地址是0开始 start:mov bx,0 ;第一条指令 mov ax,0 mov cx,8 s: add ax,cs:[bx] add bx,2 loop s mov ax,4c00H int 21H codesg ends end start ;入口找end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- end的作用除了通知编译器结束之外还有告诉编译器程序的入口在什么地方。
- 可执行文件中的程序执行过程
6.2 在代码段中使用栈
- 利用栈编程将定义的数据逆序(联想栈的特性)存放:dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
assume cs:codesg codesg segment dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H;地址0~15 dw 0,0,0,0,0,0,0,0;定义8个字型空数据,后面当作栈来使用,地址是16~31 start: mov ax,cs mov ss,ax mov sp,32;设置栈底ss:sp指向cs:32,十进制的32 mov bx,0 mov cx,8 s:push cs:[bx] add bx,2 loop s; 以上代码段0~15个单元中的8个字型数据一次入栈 mov bx,0 mov cx,8 s0:pop cs:[bx] add bx,2 loop s0;依次出栈8个执行数据到代码段0~15单元中 mov ax,4c00h int 21h codesg ends end start;指明程序入口在start处
- 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
- 如果对此程序的栈有疑惑,跳转到 3.6 栈和3.10 栈段
6.3 将数据、代码、栈放入不同的段
- 在8086CPU中数据、栈和代码存储空间不能大于64KB。可以用像定义代码段一样的方法来定义多个段并在其中定义需要的数据,或者通过定义数据来取得栈空间。
assume cs:codesg,ds:data,ss:stack;在源程序中为三个段进行有意义的名称 data segment dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H data ends stack segment dw 0,0,0,0,0,0,0,0;定义8个字型空数据,后面当作栈来使用 stack ends code segment start: mov ax,stack mov ss,ax mov sp,16;设置栈底ss:sp指向stack:16, mov ax,data mov ds,ax;ds指向data段 mov bx,0;ds:bx指向data段中的第一个单元 s:push cs:[bx] add bx,2 loop s; 以上代码段0~16个单元中的8个字型数据一次入栈 mov bx,0 mov cx,8 s0:pop cs:[bx] add bx,2 loop s0;依次出栈8个执行数据到代码段0~16单元中 mov ax,4c00h int 21h codesg ends end start;指明程序入口在start处
- 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
- 程序中指令决定了断中的内容是作为数据处理还是作为指令执行还是作为栈空间使用。
检测点 6.1
实验五
assume cs:codesg,ds:data,ss:stack data segment dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H data ends stack segment dw 0,0,0,0,0,0,0,0 stack ends codesg segment start: mov ax,stack mov ss,ax mov sp,16 mov ax,data mov ds,ax push ds:[0] push ds:[2] pop ds:[2] pop ds:[0] mov ax,4c00h int 21h codesg ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
七、更灵活的定位内存地址的方法
7.1 and和or指令
- and指令:逻辑与指令,按位进行与运算。
and两个同时为真的结果才为真。
mov al,01100011B and al,00111011B ;执行后 al=00100011B
- 1
- 2
- 3
- 可用and指令将操作对象的相应位设为0,其他位不变
and al,10111111B;将al第六位设为0 and al,01111111B;将al第七位设为0 and al,11111110B;将al第0位设为0
- 1
- 2
- 3
- or指令:逻辑或指令,按位进行或运算。
mov al,01100011B and al,00111011B ;执行后 al=01111011B
- 1
- 2
- 3
- 可用or指令将操作对象的相应位设为1,其他位不变
and al,01000000B;将al第六位设为1 and al,10000000B;将al第七位设为1 and al,00000001B;将al第0位设为1
- 1
- 2
- 3
7.2 关于ASCII码
- 将字符的ascii码写入显存屏幕就显示出相关的字符。
7.3 以字符形式给出数据
- 用‘’的方式指明数据是以字符的形式给出的。例如’A’
assume cs:code,ds:data data segment db 'unIx' db 'foRK' data ends code segment start: mov al,'a' mov bx,'b' mov ax,4c00h int 21h code ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
7.4 大小写转换的问题
- 大写字母比小写字母ASCII大32(20H)。
大写 二进制 小写 二进制 A 01000001 a 01100001 B 01000010 b 01100010 C 01000011 c 01100011 D 01000100 d 01100100 - 从第0位开始计算,大写字母ASCII码第五位为0,小写字母ASCII码第五位为1。
;大小写转换 assume cs:codesg,ds:datasg datasg segment db'BaSiC' db'iNfOfMaTiOn' datasg ends codesg segment start: mov ax,datasg mov ds,ax;设置ds执行datasg段 mov bx,0;设置(bx)=0,ds:bx指向'BaSiC'的第一个字母 mov cx,5;设置循环次数,因为BaSiC有5个字母 s:mov al,[bx];将ASCII码从ds:bx所指向的单元中取出 and al,11011111B;口岸al中ASCII码的第5个位置变为0,变为大写字母 mov [bx],al;转变后将ASCII码写回单元 inc bx;(bx)加1,ds:bx指向下一个字母 loop x mov bx,5;设置(bx)=5,ds:bx指向'iNfOfMaTiOn'的第一个字母 mov cx,11 s0:mov al,[bx] or al,00100000B mov [bx],al inc bx loop s0 mov ax,4c00H int 21H codesg ends end start
- 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
7.5 [bx+idata]
- [bx+idata]表示的是一个内存单元,它的偏移地址为bx+idata
;[bx+idata]可以写成以下格式 mov ax,[200+bx] mov ax,200[bx] mov ax,[bx].200
- 1
- 2
- 3
- 4
;使用debug查看内存 mov ax,2000H mov ds:ax mov bx,1000H mov ax,[bx] mov cx,[bx+1] add cx,[bx+2]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
7.6 用[bx+idata]的方式进行数组的处理
- 用[bx+idata]的方式进行数组处理
;改进大小写转换程序 assume cs:codesg,ds:datasg datasg segment db'BaSiC' db'iNfOfMaTiOn' datasg ends codesg segment start: mov ax,datasg mov ds,ax;设置ds执行datasg段 mov bx,0;设置(bx)=0,ds:bx指向'BaSiC'的第一个字母 mov cx,5;设置循环次数,因为BaSiC有5个字母 s:mov al,[bx+0];将ASCII码从ds:bx所指向的单元中取出 and al,11011111B;口岸al中ASCII码的第5个位置变为0,变为大写字母 mov [bx],al;转变后将ASCII码写回单元 mov [bx+5];定位第二个字符串的字符 or al,00100000B mov [bx+5],al inc bx loop s mov ax,4c00H int 21H codesg ends end start
- 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
- C语言的形式
include<stdio.h> char a[5]="BaSiC"; char b[11]="iNfOfMaTiOn"; main() { int i; i=0; do { a[i]=a[i]&0xDF; b[i]=b[i]|0x20; i++; }while(i<5); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
7.7 SI和DI
- SI和DI在8086CPU中和bx功能相近,充当BX的扩充,但是不能分成两个8位寄存器来使用。[SI]段地址默认也是在DS中。
- 下面的指令实现了相同的功能
mov bx,0 mov ax,[bx] mov si,0 mov ax,[si] mov di,0 mov ax,[di] ;------------- ;下面的三组指令也实现了另一个组相同的功能 ;------------- mov bx,0 mov ax,[bx+123] mov si,0 mov ax,[si+123] mov di,0 mov ax,[di+123]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 一般ds:si指向要复制的原始空间,ds:di指向复制的目的空间。
;用DI和SI实现复制到它后面的数据区中 assume cs:codesg,ds:datasg datasg segment db'welcome to asm!' db'................' datasg ends codesg segment start :mov ax,datasg mov ds,ax mov si,0 mov di,16 mov cx,8 s:mov ax,[si] mov [di],ax add si,2 add di,2 loop s mov ax,4c00h int 21H ;------ ;用数组的思维[bx(si或di)+idata]的方式优化程序 ;------ assume cs:codesg,ds:datasg datasg segment db'welcome to asm!' db'................' datasg ends codesg segment start :mov ax,datasg mov ds,ax mov si,0 mov cx,8 s:mov ax,[si];第一个字符串的的第一个元素 mov [si+16],ax;目标字符串的第二个元素 add si,2 loop s mov ax,4c00h int 21H codesg ends end start
- 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
7.8 [bx+si]和[bx+di]
mov ax,2000h mov ds,ax mov bx,1000h mov si,0 mov ax,[bx+si] inc si mov cx,[bx+si] inc si mov di,si mov ax,[bx+di]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
7.9 [bx+si+idata]和[bx+di+idata]
- 常数后要加.例如[bx+si].idata或者[bx].idata[si]
mov ax,2000h mov ds,ax mov bx,1000h mov si,0 mov ax,[bx+2+si] inc si mov cx,[bx+si+2] inc si mov di,si mov ax,[bx+di+2]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
7.10 不同的寻址方式的灵活应用
- 编程将数据段中每一个单词的头一个字母改为大写字母。
assume cs:codesg,ds:datasg datasg segment db'1. file ';长度刚好都是16个字节 db'2. edit ' db'3. search ' db'4. view ' db'5. options ' db'6. help ' datasg ends codesg segment start: mov ax,datasg mov ds,ax mov bx,0 mov cx,6 s: mov al,[bx+3] and al,11011111B mov [bx+3],al add bx,16 loop s mov ax,4c00h int 21h codesg ends end start
- 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
- 编程将数据段中每个单词改为大写字母
;有bug,问题在于cx的使用,进行二重循环,只用一个循环计数器,造成在进行内层的时候覆盖了外层循环的循环计数值。 assume cs:codesg,ds:datasg datasg segment db 'ibm ' db 'dec ' db 'dos ' db 'vax ' datasg ends codesg segment start:mov ax,datasg mov ds,ax mov bx,0;用bx来定位行 mov cx,4 s0:mov si,0;用si来定位列 mov cx,3 s:mov al,[bx+si] and al,11011111B mov [bx+si],al inc si loop s add bx,16 loop s0 mov ax,4c00h int 21h codesg ends end start
- 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
- 程序没有返回到cmd
loop s;三次循环后cx等于0了 add bx,16 loop s0;先是cx=cx-1再判断时候等于0,此时cx=FFFF不为0再循环,变成死循环了
- 1
- 2
- 3
- 因为loop是和cx一起使用的,不能多用个寄存器来解决loop循环次数的问题。解决的方法是在每次开始内层循环时用dx将外层循环cx的值保存起来,在执行外层循环的loop指令前再回复外层循环的cx的数值。
- 改进后程序
assume cs:codesg,ds:datasg datasg segment db 'ibm ' db 'dec ' db 'dos ' db 'vax ' datasg ends codesg segment start: mov ax,datasg mov ds,ax mov bx,0;用bx来定会行 mov cx,4 s0: mov dx,cx;用dx寄存器来临时存放外层cx的值 mov si,0;用si来定位列 mov cx,3 s: mov al,[bx+si] and al,11011111B mov [bx+si],al inc si loop s add bx,16 mov cx,dx;在进行外层循环的时候回复cx的值 loop s0 mov ax,4c00h int 21h codesg ends end start
- 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
- 在上面的程序中,8086 CPU si、cx、ax、bx这些寄存器经常要使用到;cs、ip、ds也不能用,因为cs:ip时刻指向当前指令,ds指向datasg段;那么可用的寄存器就只用dx、di、es、ss、sp、bp等寄存器了。内存可以解决经常性的数据暂存问题。为了使程序结构清晰便于阅读,应该使用栈
- 再次被改进的程序
assume cs:codesg,ds:datasg datasg segment db 'ibm ' db 'dec ' db 'dos ' db 'vax ' dw 0;定义一个字用来保存cx的值 datasg ends codesg segment start:mov ax,datasg mov ds,ax mov bx,0;用bx来定位行 mov cx,4 s0:mov ds:[40h],cx;datasg:40h单元存放外层cx的值 mov si,0;用si来定位列 mov cx,3 s:mov al,[bx+si] and al,11011111B mov [bx+si],al inc si loop s add bx,16 mov cx,ds:[40h];在进行外层循环的时候回复cx的值 loop s0 mov ax,4c00h int 21h codesg ends end start
- 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
- 再次使用栈改进程序
assume cs:codesg,ds:datasg,ss:stacksg datasg segment db 'ibm ' db 'dec ' db 'dos ' db 'vax ' datasg ends stacksg segment dw 0,0,0,0,0,0,0,0;定义一个段,用作栈段,容量为16个字节 stacksg ends codesg segment start:mov ax,stacksg mov ss,ax mov sp,16 mov ax,datasg mov ds,ax mov bx,0;用bx来定位行 mov cx,4 s0:push cx;datasg:40h单元存放外层cx的值 mov si,0;用si来定位列 mov cx,3 s:mov al,[bx+si] and al,11011111B mov [bx+si],al inc si loop s add bx,16 pop cx;在进行外层循环的时候回复cx的值 loop s0 mov ax,4c00h int 21h codesg ends end start
- 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
- 编程将数据段中的每个单词的前四个字母改为大写字母
assume cs:codesg,ds:datasg,ss:stacksg stacksg segment stacksg ends datasg segment db '1. display ' db '2. brows ' db '3. replace ' db '4. modify ' datasg ends codesg segment start:mov ax,stacksg mov ss,ax mov sp,16 mov ax,datasg mov ds,ax mov bx,0 mov cx,4 s0:push cx mov si,0 mov cx,4 s:mov al,[bx+si+3] and al,11011111B mov [bx+si+3],al inc si loop s add bx,16 pop cx loop s0 mov ax,4c00h int 21h codesg ends end start
- 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
八、数据处理的两个基本问题
引言
- 本章是总结性的内容,数据处理的两个基本问题是
- 处理的数据在哪?
- 要处理的数据有多长?
- 自定义得描述符:
- reg寄存器
- ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di;
- sreg段寄存器
- ds、ss、cs、es。
- -
- reg寄存器
8.1 bx、si、di、bp
- 在8086 CPU中只有bx、si、di、bp这四个寄存器用在[ ]中进行内存单元寻址。在[]中,组合只能以这四种形式:bx和si、bx和di、bp和si、bp和di
;以下指令是错误的 mov ax,[ax] mov ax,[cx] mov ax,[dx] mov ax,[ds] mov ax,[bx+bp] mov ax,[si+di]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 正确的指令
mov ax,[bx] mov ax,[si] mov ax,[di] mov ax,[bp] mov ax,[bx+si] mov ax,[bx+di] mov ax,[bp+si] mov ax,[bp+di] mov ax,[bx+si+idata] mov ax,[bx+di+idata] mov ax,[bp+si+idata] mov ax,[bp+di+idata]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- [bp]的段地址默认在ss中。
8.2 机器指令处理的数据所在的位置
- 绝大部分机器指令时进行数据处理的,大致可以分为3类:读、写、运算。指令在处理前可以在三个地方:CPU内部、内存、端口。
机器码 汇编指令 指令执行前数据的位置 89C3 mov bx,[0] 内存,ds:0单元 89C3 mov bx,ax CPU内部,ax寄存器 BB0100 mov bx,1 CPU内部,指令缓冲器 8.3 汇编语言中数据位置的表达
- 汇编语言中用三个概念来表达数据的位置。
- 1、立即数(idata)
- 2、寄存器
- 3、段地址(SA)和偏移地址(EA)
8.4 寻址方式总小结
8.5 指令要处理的数据有多长
- 8086 CPU可以处理byte和word两种数据尺寸。
- 通过寄存器指明要处理的数据尺寸;push指令只进行字操作,若没有寄存器名存在的情况下,用操作符word ptr或者byte ptr指明内存单元的长度。 例如
mov word ptr ds:[0],1 inc word ptr [bx] inc word ptr ds:[0] add byte ptr [bx],2
- 1
- 2
- 3
- 4
;假设内存2000:1000 FF FF FF FF FF FF …… ;如果用以下指令 mov ax,2000H mov ds,ax mov byte ptr [1000H],1 ;那么内存中的内容变为 ;2000:1000 01 FF FF FF FF FF …… 如果是用以下指令 mov ax,2000H mov ds,ax mov word ptr [1000H],1 ;那么内存中的内容变为 ;2000:1000 01 00 FF FF FF ……
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
8.6 寻址方式的综合应用
- 初步汇编代码
mov ax,seg mov ds,ax mov bx,60h;确定记录物理地址:ds:bx mov word ptr [bx+0ch],38;寄存器相对寻址 排名字段改为38 add word ptr [bx+0eh],70;收入字段增加70 mov si,0;用si来定位产品字符串中的字符 mov byte ptr [bx+10h+si],'V';相对基址变址寻址 inc si mov byte ptr [bx+10h+si],'A' inc si mov byte ptr [bx+10h+si],'X'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- c语言描述
struct company /*定义一个公司记录的结构体*/ { char cn[3]; /*公司名称*/ char hn[9]; /*总裁姓名*/ int pm; /*排名*/ int sr; /*收入*/ char cp[3]; /*著名产品*/ }; struct compant dec={"DEC","Ken Olsen",137,40,"PDF"}; /*定义一个公司记录的变量,内存中将存有一条公司的记录*/ mian() { int i; dec.pm=38; dec.sr=dec.sr+70; i=0; dec.cp[i]='V'; i++; dec.cp[i]='A'; i++; dec.cp[i]='X'; return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 按照c语言的风格用汇编写
mov ax,seg mov ds,ax mov bx,60h;记录首地址送入bx mov word ptr [bx].och,38;排名字段改为38 add word ptr [bx].0eh,70;收入字段增加70 ;产品名字段改为字符串'VAX' mov si,0 mov byte ptr [bx].10h[si],'V' inc si mov byte ptr [bx].10h[si],'A' inc si mov byte ptr [bx].10h[si],'X'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 多种寻址方式为结构化数据的处理提供了方便。
- 一般用[bx+idata+si]的方式来访问结构体,用idata定位结构体中的某一数据项,用si定位数组项中的每个元素。 例如:[bx].idata、[bx].idata[si]。
8.7 div指令
- div(divide)是除法指令,可用乘法模拟,格式为:
div reg(寄存器) div 内存单元。
- 1
- 2
- 除数:8位或16位,在寄存器或内存单元中;被除数:默认放在AX或DX和AX中。
div byte ptr ds:[0] div byte ptr [bx+si+idata] ;al放商,ah放余数 div word ptr es:[0] div word ptr [bx+si+idata] ;ax放商,dx放余数
- 1
- 2
- 3
- 4
- 5
- 6
- 7
除数 被除数 8位 16为(AX) 16位 32位(DX高16位+AX低16位) - 8位或16位看的是除数。
运算 8位 16位 商 AL AX 余数 AH DX - 利用除法指令计算10001/100编程
;被除数1001可用ax寄存器存放,除数100可用8位寄存器存放,要进行8位除法。 mov ax,1001 mov bl,100 div bl ;执行后al的值等于0AH(10),ah的值等于1(余数为1)。
- 1
- 2
- 3
- 4
- 5
- 利用除法指令计算100001/100编程
;被除数100001大于2^16=65535(FFFF),不能用ax来存放,要用dx和ax两个寄存器联合存放。除数小于255,可用一个8位寄存器存放,但是被除数是32位的,除数应为16位,所以要用一个16位寄存器来存放除数。 ;100001的十六进制为186A1H,100001的高16位(1)存放在dx,低16位(86AH)存放在ax中。 mov dx,1 mov ax,86A1H mov bx,100 div bx ;执行后ax内容等于03E8H(即1000),dx的值等于1(余数)。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
8.8 伪指令dd
- db定义字节型数据,dw定于字型数据,dd 定于 dword(double word双字型数据)
data segment db 1;第一个数据为01h,在data:0处,占1个字节 dw 1;第二个数据为0001h,在data:1处,占1个字 dd 1;第三个数据为00000001h,在data:3处,占2个字 data ends
- 1
- 2
- 3
- 4
- 5
- 利用除法指令计算 dd 100001H 除以 dw 100,商放在 dw 0中
data segment dd 100001H;低16位存储在ax中,高16位存储在dx中 dw 100 dw 0 data ends mov ax,data mov ds,ax mov ax,ds:[0];低16位存储在ax中 mov dx,ds:[2];高16位存储在dx中 div word ptr ds:[4] mov ds:[6],ax
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
8.9 伪指令dup
- 和db、dw、dd等数据定义伪指令配合使用,用来进行数据的重复。格式 db或者dw或者dd 重复的次数 dup (重复的数据)
- 例如:
db 3 dup(0) ;定义了3个字节,它们的值都是0,等同于db 0,0,0。 db 3 dup(0,1,2) ;定义了9个直接,它们是0、1、2、0、1、2、0、1、2,相当于db 0、1、2、0、1、2、0、1、2 db 3 dup('abc','ABC') ;定义了18个直接,它们是'abcABCabcABCabcABC'
- 1
- 2
- 3
- 4
- 5
- 6
实验七 寻址方式在结构化数据访问中的应用
- ds已经和data段联系了,数据段不够用时用扩展段ES
;初始化阶段 mov ax,data mov ds,ax mov ax,table;data已经被占用 mov es,ax mov bx,0 mov si,0 mov di,0 mov cx,21 ;存放年份,每一个bx就是一个字节 mov al,[bx] mov es:[di],al mov al,[bx+1] mov es:[di+1],al mov al,[bx+2] mov es:[di+2],al mov al,[bx+3] mov es:[di+3],al ;存放公司的总收入 mov ax,[bx+54H];第一个年收入是dd数据类型,段地址为54H mov dx,[bx+54H] mov es:[di+5H],ax mov es:[di+7H],dx ;存放公司的人数 mov ax,[si+0A8H];第一个人数的数据段地址为0A8H mov es:[di+0A8H],ax ;计算人均收入并存放 mov ax,[bx+54H] mov dx,[bx+56H];这两句诗初始化被除数 div word ptr,ds:[si+0A8H];除以人数 mov es:[di+0dH],ax;将商放入指定位置 ;为下一次循环时存放数据做准备 add bx,4;bx确定年份和收入 add si,2;si确定人数 add di,16;di确定的是每行的列数
- 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
- 完整的程序
assume cs:codesg,ds:data,es:table data segment db '1975','1976' '1977' …… dd 16,22,382 …… dw 3,7,9 …… ;数据在题目中 data ends table segment db 21 dup('year summ ne ?? ') table ends start:mov ax,data mov ds,ax mov ax,table mov es,ax mov bx,0 mov si,0 mov di,0 mov cx,21 s:mov al,[bx] mov es:[di],al mov al,[bx+1] mov es:[di+1],al mov al,[bx+2] mov es:[di+2],al mov al,[bx+3] mov es:[di+3],al mov ax,[bx+54H] mov dx,[bx+56H] mov es:[di+5H],ax mov es:[di+7H],dx mov ax,[si+0A8H] mov es:[di+0AH],ax mov ax,[bx+54H] div word ptr ds:[si+0A8H] mov es:[di+0dH],ax add bx,4 add si,2 loop s mov ax,4c00h int 21h codesg ends end start
- 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
九、转移指令的原理
引言
- 可以修改IP,或者同时修改CS和IP的指令统称为转移指令。 简单的来说可以控制CPU执行内存中某处代码的指令就是转移指令。
- 8086
- CPU的转移行为有只修改的段内转移(如jmp ax) 和同时修改该CS和IP的段间转移(如jmp 1000:0)。其中段内转移分为短转移(IP的修改范围为-128~127)和近转移 (IP的修改范围为-32768~32767)。
- 8086 CPU的转移指令分为以下几类:
- 无条件转移指令(如:jmp)
- 条件转移指令
- 循环指令(如:loop)
- 过程
- 中断
9.1 操作符offset
- offset是伪指令,由编译器处理,它的功能是取得标号的偏移地址。
assume cs:codesg codesg segment start:mov ax,offset start;相当于 mov ax,偏移地址0,段地址是从0开始 s:mov ax,offset s;相当于 mov ax,3,标记的是代码段中的第二条指令,第一条指令长度为3个字节,则s的偏移地址为3 codesg ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
9.2 jmp指令
- jmp为无条件转移,可以只修改IP,也可以同时修改CS和IP。
- jmp需要两种信息
- 1、转移的目的地址;
- 2、转移的距离(段间转移、段内转移、段内近转移)。
9.3 依据位移进行转移的jmp指令
- 段内短转移,jmp short 标号 ,对IP的修改范围是-128~127,一个字节的空间,即向前转移最多128字节,向后最多127字节。short 表明指令进行的是短转移,标号指明了指令要转移的目的地,转移指令结束后CS:IP指向标号处的指令。
assume cs:codesg codesg segment start:mov ax,0 jmp short s add ax,1 s:inc ax codesg ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
一般汇编指令中的立即数(idata)会出现在对应的机器指令中。而jmp指令的机器指令并不包含目的地址,包含的是相对于当前IP的转移位移,CPU并不需要目的地址就可以实现对IP的修改。
CPU执行指令的过程 在 2.10 CS和IP
- jmp short s 指令的读取和执行过程:
- 1、CS:IP指向jmp short s 的机器码;
- 2、读取指令码进入指令缓冲器
- 3、 改变IP,(IP)=(IP)+所读取指令的长度,IP指向下一个指令;
- 4、CPU执行指令缓冲器中的指令;
- 5、执行后CS:IP继续指向下一个指令
- jmp short 标号的功能为(IP)=(IP)+8位位移。
- 1、8位为=标号处的地址-jmp指令后的第一个字节的地址;
- 2、short 指明此处的位移为8位;
- 3、8位位移的范围为-128~127,用补码表示。
- 4、8位位移由编译程序在编译时算出的。
- jmp near ptr 标号 指令实现段内近转移,功能为(IP)=(IP)+16位位移。
- 1、16位为=标号处的地址-jmp指令后的第一个字节的地址;
- 2、nearptr 指明此处的位移为16位;
- 3、16位位移的范围为-32769~32767,用补码表示。
- 4、16位位移由编译程序在编译时算出的。
9.4 转移的目的地址在指令中的jmp指令
- jmp far ptr 段间转移,又称为远转移
- jmp far ptr 标号的功能:
- (CS)=标号所在段的段地址;
- (IP)=标号所在段总的偏移地址;
- far ptr 指明了指令用标号的段地址和偏移地址修改CS和IP。
assume cs:codesg codesg segment start:mov ax,0 mov bx,0 jmp far ptr s db 256 dup(0) s:add ax,1 inc ax codesg ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 机器码中包含了转移的目的地址。
附注3 汇编编译器(masm.exe)对jmp的相关处理
9.5 转移地址在寄存器中的jmp指令
- jmp 16位寄存器,功能是16位寄存器赋值给IP,实现段内的近(短)转移。
- 参考 2.11 修改CS、IP的指令
9.6 转移地址在内存中的jmp指令
转移地址在内存中的jmp指令有两种格式:
1、jmp word ptr内存单元地址(16位只能实现段内转移)。 功能是从内存单元地址处开始存放一个字(转移的目的偏移地址),内存单元地址可用寻址方式的格式给出。
mov ax,0123H mov ds:[0],ax jmp word ptr ds:[0] ;相当于 jmp ax,执行后(IP)=0123h mov ax,0123H mov [bx],ax jmp word ptr [bx] ;执行后(IP)=0123h
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2、jmp dword ptr 内存单元地址(段间转移)。 功能:从内存单元地址处开始存放两个字型数据,高地址是转移的目的段地址,低地址处是转移的目的偏移地址。(CS)=(内存单元地址+2),(IP)=(内存单元地址),内存单元地址可用寻址方式的任一格式给出。
mov ax,0123H mov ds:[0],ax mov word ptr ds:[2],0 jmp dword ptr ds:[0] mov ax,0123H mov [dx],ax mov word ptr [bx+2],0 jmp dword ptr [bx] ;执行后 (CS)=0,(IP)=0123H CS:IP指向0000:0123
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
检测点 9.1
9.7 jcxz指令
- 指令格式为jcxz 标号,如果cx的值为0,则转移到标号处执行,不为0则向下执行。
- 当cx的值为0时,(IP)=(IP)+8位位移,8位位移=标号处的地址-jcxz指令后的第一个字节的地址。
- 8位位移的范围是-128~127,用补码表。
- 8位位移由编译器在编译时算出。
- jcxz指令是有条件转移指令,所有的条件转移指令都是短指令,在对应的机器码中包含转移的位移而不包含目的地址,对IP的修改范围都为-128-127。
检测点 9.2
9.8 loop指令
- loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移而不包含目的地址。操作i:
- cx先自减1;
- 当cx的值不为0时,(IP)=(IP)+8位位移,8位位移=标号处的地址-loop指令后的第一个字节的地址。
- 8位位移的范围是-128~127,用补码表。
- 8位位移由编译器在编译时算出。
检测点 9.3
9.9 根据位移进行转移的意义
jmp short 标号 jmp near ptr 标号 jcxz 标号 loop 标号
- 1
- 2
- 3
- 4
- 它们对IP的修改时根据转移目的地址和转移起始地址自检的位移来进行的。在它们对应的机器码中不包含转移的目的地址,而包含的是目的地址的位移距离。方便了程序段在内存中的浮动分配,没有固定目的地址的限制,更灵活。
9.10 编译器对转移位移超界的检测
- 根据位移进行转移的指令,它们的转移范围受到了转移位移的限制,如果在源程序中出现了转移范围超界的问题,在编译时编译器会报错。
assume cs:code code segment start: jmp short s db 128 dup(0) s:mov ax,0FFFFH code ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
实验8
- 实验八可以正常退出
assume cs:codesg codesg segment mov ax,4c00h int 21h start:mov ax,0 s:nop nop;nop占用两个字节,不执行任何操作 mov di,offset s mov si,offset s2 mov ax,cs:[si];jmp short s1的机器码给了ax mov cs:[di],ax;覆盖到指令 s:nop nop那 s0:jmp short s;s那已经被jmp short s1机器码覆盖 s1:mov ax,0 int 21h mov ax,0 s2:jmp short s1;jmp -8h,向上跳到s1,s1又向上跳-10字节 nop codesg ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
实验9
assume cs:code,ds:data,ss:stack data segment db'welcome to masm!';定义要显示的字符串(共16字节) db 02H,24H,71H;定义字符的属性 data ends stack segment dw 8 dup(0) stack ends code segment start: mov ax,data mov ds,ax mov ax,stack mov ss,ax mov sp,10H xor bx,bx;bx清零,用来索引颜色 mov ax,0b872H;算出屏幕第12行中间的显存的段起始位置放入ax中 mov cx,3;s3循环控制行数,要显示三个字符串外循环为3次 s3: push cx;三个进栈操作为外循环s3保存相关寄存器的值 push ax;以防止它们的值在内循环中被破坏 push bx mov es,ax;此时es为屏幕第12行中间的显存的段起始位置 mov si,0;si用来索引代码列的字符 mov di,0;di用来定位目标列 mov cx,10H ;s1循环控制存放的字符,一个字符串中含有10H个字节内循环为10H次 s1: mov al,ds:[si] mov es:[di],al inc si add id,2 loop s1;吃循环实现偶地址中存放字符 mov di,1;设置di的值为1,为在显存奇数地址中存放字符的颜色属性做准备 pop bx mov al.ds:[bx+10H];取消颜色属性 inc bx mov cx,10H;第二个内循环也为10H s2: mov es:[di],al add di 2 loop s2;此循环实现奇数地址存放字符的颜色属性 ;以下4句为下一趟外循环做准备 pop ax add ax,0AH;将显存的段地址起始地址设置为当前行的下一行 ;[在段地址中甲0aH,相当于在偏移地址中加了0a0h(=160d)] pop cx loop s3 mov ax,4C00H int 21H code ends end start
- 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
- 62
- 63
- welcome to masm
十、CALL和RET指令
引言
- 回想程序之间的加载返回过程。
- call和ret指令都是转移指令,它们都修改IP或者同时修改CS和IP,经常被共用来实现程序的设计。
- 这一章讲解call和ret指令的原理。
10.1 ret和retf指令
- ret指令用栈中的数据来修改IP的内容,从而实现近转移。
- CPU执行ret指令时:
- 1、(IP)=((SS)*16+(SP)),指向栈顶
- 2、(SP)=(SP)+2
- retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。
- CPU执行retf指令时,进行下面两步操作:
- 1、(IP)=((SS)*16+(SP))
- 2、(SP)=(SP)+2
- 3、(CS)=((SS)*16+(SP))
- 4、(SP)=(SP)+2
- 用汇编的语法来解释ret和retf指令:
- CPU执行ret指令相当于进行 POP IP
- CPU执行retf指令相当于进行 POP IP和POP CS
assume cs:codesg stack segment db 16 dup(0) stack ends codesg segment mov ax,4c00h int 21h start: mov ax,stack mov ss,ax mov sp,16 mov ax,0 push ax mov bx,0 ret codesg ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
assume cs:codesg stack segment db 16 dup(0) stack ends codesg segment mov ax,4c00h int 21h start: mov ax,stack mov ss,ax mov sp,16 mov ax,0 push cs push ax mov bx,0 retf codesg ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
检测点 10.1
10.2 call指令
- call指令经常跟ret指令配合使用,CPU执行call指令时:
- 1、将当前的IP或者CS和IP压入栈;
- 2、转移(jmp)。
- call指令除了不能实现短转移之外,call指令实现转移的方法和jmp指令的原理相同。call指令实现段间的转移(远转移)或近转移。
10.3 依据位移进行转移的call指令
- call标号(将当前的IP压入栈后转到目标处执行指令),执行时进行以下操作:
- 1、(SP)=(SP)-2
((SS)*16+(SP))=(IP) - 2、(IP)=(IP)+16位位移;
- 3、16位位移=标号处的地址减去call指令后的第一个字节的地址。16位位移的范围是-32768~32767,用补码表示。16位位移由编译器编译时算出。
- 1、(SP)=(SP)-2
- 用汇编语法解释call指令:
push IP jmp near 标号
- 1
- 2
检测点 10.2
10.4 转移的目的地址在指令中的call指令
- call far ptr 标号 实现的是段间转移,执行时:
- 1、CS先自减2;
- 2、CS的值等于SS的值乘以16加上SP的值,SP自减2,IP的值等于SS的值*16加上SP的值;
- 3、CS的值等于标号所在的段地址,IP的值等于标号所在的偏移地址.
- 用汇编语法解释call指令:
push CS push IP jmp far ptr 标号
- 1
- 2
- 3
检测点 10.3
10.5 转移地址在寄存器中的call指令
- 指令格式是:call 16位寄存器,功能是:
- 1、SP的值先自减2;
- 2、IP的值SS的值乘以16再加上SP的值;
- 3、 IP的值等于16位寄存器的内容。
- 用汇编语法解释此种call指令,CPU执行call 16位reg时,相当于:
push IP jmp 16位寄存器
- 1
- 2
检测点 10.4
10.6 转移地址在内存中的call指令
- 转移地址在内存中的call指令有两种格式
call word ptr 内存单元地址;段内跳转 call dword ptr 内存单元地址;段间跳转
- 1
- 2
- 用汇编语法解释call word ptr 内存单元地址
push IP jmp word ptr 内存单元地址
- 1
- 2
- 例子:
mov sp,10h mov ax,0123H mov ds:[0],ax call word ptr ds:[0] ;执行后IP的值等于0123H,SP的值等于0EH
- 1
- 2
- 3
- 4
- 5
- 用汇编语法解释call dword ptr 内存单元地址
push CS push IP jmp word ptr 内存单元地址
- 1
- 2
- 3
- 例子:
mov sp,10h mov ax,0123H mov ds:[0],ax mov word ptr ds:[0],0 call dword ptr ds:[0] ;执行后IP的值等于0123H,SP的值等于0CH,CS的值等于0
- 1
- 2
- 3
- 4
- 5
- 6
检测点 10.5
10.7 call和ret的配合使用
- 下面的程序返回前,bx中的值是多少?
assume cs:code code segment start: mov ax,1 mov cx,3 call s mov bx,ax mov ax,4c00h int 21h s: add ax,ax loop s ret code ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 具有一定功能的程序段称为子程序,用call转去执行,在子程序后面使用ret实现返回。
- 具有子程序的源程序的框架如下
10.8 mull指令
- mull指令时乘法指令,相乘的两个数要么都是8位的,要么都是16位的
- 8位:在AL中和8位寄存器中或内存字节单元中;
- 16位:在AX中和16位寄存器或内存字单元中。
- 结果
- 8位的存放在AX中;
- 16位:DX(高位)和AX(低位)中。
mull reg mull 内存单元 mull byte ptr ds:[0] mull word ptr [bx+si+idata] ;(ax)=(ax)*((ds)*16+(bx)+(si)+idata) ;(dx)=(ax)*((ds)*16+(bx)+(si)+idata)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
;计算100*10,两个数都小于255,可以做8位乘法 mov ax,100 mov bx,10 mull bl ;结果(ax)=1000(03E8H) ;计算100*1000,1000都大于255,要做16位乘法 mov ax,100;高位自动补零 mov bx,10000 mull bx ;结果(ax)=4240H,(dx)=000FH,F4240H=1000000
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
10.9 模块化程序设计
- cal和ret指令共同支持汇编语言编程中的模块化设计。
10.10 参数和结果传递的问题
用寄存器来存储参数和结果是最常用的方法。对于存放参数的寄存器和存放结果的寄存器,调用者和子程序的读写操作恰恰相反:
- 调用者将参数送入参数寄存器,从结果寄存器中取到返回值;
- 子程序 从参数寄存器中取到参数,将返回值送入结果寄存器。
编程:根据提供的N来计算N^3
cube:mov ax,bx mul bx mul bx ret
- 1
- 2
- 3
- 4
- 编程:计算data段中第一组数据的3次方,结果保存在后面一组dword单元中
assume cs:code data segment dw 1,2,3,4,5,6,7,8 dd 8 dup (0) data ends code segment start: mov ax,data mov ds,ax mov si,0;ds:si指向第一组word单元 mov di,16;ds:di指向第二组dword单元 mov cx,8 s: mov bx,[si] call cube mov [di],ax mov [di+2],dx add si,2;ds:di指向下一个word单元 add di,4;ds:di指向下一个dword单元 loop s mov ax,4c00h int 21h cube:mov ax,bx mul bx mul bx ret code ends end start
- 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
10.11 批量数据的传递
- 将批量数据放在内存中,然后将他们呢所在内存空间的首地址放在寄存器中,传递给需要的子程序,批量数据的返回结果也是采用同样的方法。除此之外还可以用栈来传递参数。
assume cs:code data segment db'conversation' data ends start: mov ax,data mov ds,ax mov si,0;ds:si指向字符串(批量数据)所在空间的首地址 mov cx,12;cx存放字符串的长度 call capital mov ax,4c00h int 21h capital: add byte ptr [si],11011111B inc si loop capital ret code ends
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
10.12 寄存器冲突的问题
- 编程:将一个全是字母,以0结尾的字符串转化为大写
capital: mov cl,[si];低8位 mov ch,0;高8位设置为0 jcxz ok;如果(cx)=0则结束,如果不是0则处理 and byte ptr [si],11011111B inc si jmp short capital ok: ret
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 编程将data段中的字符串全部转化为大写
assume cs:code data segment db'word',0 db'unix',0 db'wind',0 db'good',0 data ends
- 1
- 2
- 3
- 4
- 5
- 6
- 7
;此程序有bug,cx有问题 assume cs:code data segment db'word',0 db'unix',0 db'wind',0 db'good',0 data ends code segment start: mov ax,data mov ds,ax mov bx,0 mov cx,4 s: mov si,bx call capital add bx,5 loop s mov ax,4c00h int 21h capital: mov cl,[si] mov ch,0 jcxz ok and byte ptr [si],11011111b inc si jmp short capital ok: ret code ends end start
- 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
实验十
- 实验10.1 显示字符串
assume cs:code data segment db 'welcome to masm!',0 data ends code segment start: mov dh,8;行号 mov dl,3;列号 mov cl,2;颜色属性 mov ax,data mov ds,ax mov si,0 call show_str mov ax,4c00h int 21h show_str:;子程序 push cx push si mov al,0A0h;每行有80*2=160个字节=0a0h dec dh;行号在显存中下标从0开始,所以减1 mul dh;相当于从第(n-1)*0a0h个byte单元开始 mov bx,ax;定位好的位置偏移地址存放在bx里(行) mov al,2;每个字符占2个字节 mul dl;定位列,结果ax存放的是定位好的列的位置 sub ax,2;列号在显存中下标从0开始,又因为是偶字节存放字符,所以减2 add bx,ax;此时bx中存放的是行与列的偏移地址 mov ax,0B800h;显存开始的地方 mov es,ax;es中存放的是显存的第0页的起始地段地址 mov di,0;di指向显存的偏移地址,确定指向下一个要处理的字符的位置 mov al,cl;cl存放颜色参数,下边cl要用来临时存放要处理的字符 mov ch,0;下边cx存放的是每次准备处理的字符 s: mov cl,ds:[si];指向'welcome to masm ',0 jcxz ok;cl为0时跳转 mov es:[bx+di],cl;偶地址存放字符 mov es:[bx+di+1],al;奇地址存放字符的颜色属性 inc si add di,2;指向了下个字符 jmp short s ;无条件跳转,jcxz是离开的关键跳 ok: pop si pop cx ret;定义结束 code ends end start
- 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
- 实验10.2
assume cs:code,ss:stack stack segment dw 8 dup(0) stack ends code segment start: mov ax,stack mov ss,ax mov sp,10h mov ax,4240h mov dx,0fh mov xx,0ah call divdw mov ax,4c00h int 21h divdw: push ax;低16位先保存 mov ax,dx;ax这时是高16位了 mov dx,0;为了不影响余数位和高位数 div cx mov bx,ax pop ax div cx mov cx,dx mov dx,dx ret code ends end start
- 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
- 实验10.3
assume cs:code,ds:data data segment db 10 dup(0) data ends code segment start: mov ax,12666 mov bx,data;指向字符串的首地址 mov ds,bx mov si,0 call dtoc;实现将word型整数转化为字符串并存储 mov dh,8;打印初始化 mov dl,3 mov cl,0cah call show_str;开始打印字符串 mov ax,4c00h int 21h dtoc: push dx push cx push ax push si mov bx,0;bx在子程序中用来存放位数,用栈来临时存放修改后的字符 s1: mov cx,10d;d表示十进制,cx准备被除,用取余法来取出数字 mov dx,0 div cx;除以十 mov cx,ax;得到的商复制给cx,要利用jcxz jcxz s2;当商为0则跳到s2 add dx,30h;余数加上30h得到相应的ascii码 push dx inc bx jmp short s1 s2: add ax,30h;当商为0的时候,余数为个位 push dx inc bx;再进行一次栈操作(补充当商为零而余数不为零时的情况) mov cx,bx;总共有bx位进栈,所以循环次数为bx mov si,0 s3: pop ax;s3实现将栈中的数据依次出栈放到指定的内存中 mov [si],al inc si loop s3 okay: pop bx pop si pop ax pop dx ret show_str:;子程序 push bx push cx push si mov al,0A0h;每行有80*2=160个字节=0a0h dec dh;行号在显存中下标从0开始,所以减1 mul dh;相当于从第(n-1)*0a0h个byte单元开始 mov bx,ax;定位好的位置偏移地址存放在bx里(行) mov al,2;每个字符占2个字节 mul dl;定位列,结果ax存放的是定位好的列的位置 sub ax,2;列号在显存中下标从0开始,又因为是偶字节存放字符,所以减2 add bx,ax;此时bx中存放的是行与列的偏移地址 mov ax,0B800h;显存开始的地方 mov es,ax;es中存放的是显存的第0页的起始地段地址 mov di,0;di指向显存的偏移地址,确定指向下一个要处理的字符的位置 mov al,cl;cl存放颜色参数,下边cl要用来临时存放要处理的字符 mov ch,0;下边cx存放的是每次准备处理的字符 S: mov cl,ds:[si] jcxz ok mov es:[bx+di],cl mov es:[bx+di+i],al inc si add di,2 jmp short s ok: pop si pop cx pop bx ret code ends end start
- 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
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
十一、标志寄存器
引言
- CPU内部的寄存器中有一种特殊的寄存器:
- 1、用来存储相关指令的某些执行结果;
- 2、用来为CPU执行相关指令提供行为依据;
- 3、用来控制CPU的相关工作方式。
- 8086 CPU的标志寄存器只有16位,其中存储的信息通常被称为程序状态字(PSW)。
- 本章中的标志寄存器(以下简称为flag)。某些指令将影响标志寄存器中的多个标志位,这些被影响的标记位比较全面地记录ill指令的执行结果,为相关的处理提供了所需的依据。
- flag寄存器是按位起作用的,每一位都有专门的含义,记录特定的信息,与其他寄存器不一样。
- 8086 CPU的flag寄存器的结构:
- flag的1、3、5、12、13、14、15位在8086 CPU中没有使用,而0、2、4、6、7、8、9、10、11位都具有特殊的含义。
11.1 ZF(zero flag)标志
- flag的第6位是ZF,零标志位,它记录相关指令执行后,结果为0,ZF=1(记录下是0这样的肯定信息),结果不为0,ZF=0(表示结果非0)。
mov ax,1 sub ax,1 mov ax,1 and ax,0 ;指令执行后,结果为0,则ZF=1 mov ax,2 sub ax,1 mov ax,1 or ax,0 ;指令执行后,结果为1,则ZF=0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 在8086CPU中,add、sub、mul、div、inc、or、and等它们大多都是运算(逻辑运算或是算术运算)指令,是影响标志寄存器的,而mov、push、pop等传送指令对标志寄存器一般没有影响,因为不会产生结果。
11.2 PF标志
- flag的第2位是PF,奇偶标志位,记录指令执行后结果所有的二进制位中1的个数。为偶数,PF=1,为奇数PF=0
mov al,1 add al,10 ;执行结果为00001011B,有3个1,则PF=0 mov al,1 or al,10 ;执行后结果为00000011B,有2个1,则PF=1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
11.3 SF(sign flag)标志
- flag的第7位是SF符号标志位,记录指令执行后结果为负则SF=1,结果为正,SF=0。弱国我们将数据当作无符号数来运算,SF的值没有意义,虽然相关的指令影响了它的值。
- 有符号数与补码
- 计算机默认把负数用补码记录。
- 00000001B,可以看作无符号数1,也可以看作符号数+1;
- 10000001B,可以看作无符号数129,也可以看作有符号数-127。
- 补码
mov al,10000001B add al,1 ;执行指令后al的值是10000010B,无符号数130,有符号数-126
- 1
- 2
- 3
检测点 11.1
11.4 CF(carry flag)标志
flag的第0位是CF,进位标志位。一般情况下,在进行无符号运算的时候,它记录了运算结果的最高有效位向更高位的进位值或从更高位的借位值。对于位数为N的无符号数,其对应的二进制信息的最高位为N-1位的最高有效位,假想存在第N位。
两个8位的数据运算可能产生进位或者借位,由于这个进位值在8位数中无法保存,8086CPU就用flag的CF位来记录这个进位值。
mov al.98h add al,al;执行后(al)=30h,cf=1,cf记录了从最高有效位向更高位的进位值 add al,al;执行后(al)=60h,cf=0,cf记录了从更高有效位向更高位的进位值 mov al,97h sub al,98h;执行后(al)=ffh,cf=1,cf记录了向更高位的借位值 sub al,al;执行后(al)=0,cf=0,cf记录了向更高位的借位值
- 1
- 2
- 3
- 4
- 5
- 6
- 7
11.5 OF(overflow flag)标志
- 如果运算结果超出了机器所能表达的范围(对于8位有符号数,机器所能表达的范围是-128~127)将产生溢出,对有符号数而言。
assume cs:code code segment start: mov al,01100010b add al,01100011b mov ax,4c00h int 21h code ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
assume cs:code code segment start: mov al,10001000b add al,11110000b mov ax,4c00h int 21h code ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
assume cs:code code segment start: mov al,98h add al,al add al,al mov ax,4c00h int 21h code ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
assume cs:code code segment start: mov al,97h sub al,98h add al,al mov ax,4c00h int 21h code ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位; CPU用CF位来记录无符号数运算是否产生了进位,用OF位来记录有符号数是否产生了溢出。用SF位来记录结果的符号
mov al,98d add al,99d ;对于无符号数运算,98+99没有进位,CF=0 ;对于有符号数运算,98+99发生溢出,OF=1
- 1
- 2
- 3
- 4
检测点 11.2
11.6 adc指令
- adc是带有进位加法指令,利用了CF位上记录的进位值。格式:adc操作对象1,操作对象2,功能:操作对象1=操作对象1+操作对象2+CF。
mov ax,2 mov bx,1 sub bx,ax adx ax,1 ;执行后 (ax)=4,相当于计算(ax)+1+CF=2+1+1+4 mov ax,1 add ax,ax adc ax,3 ;执行后(ax)=5,相当于执行(ax)+3+CF=2+3+0=5 mov al,98H add al,al adx al,3 ;执行后 (ax)=34H,相当于执行(ax)+3+CF=30H+3+1=34H
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 由adc指令前面的指令决定在执行adc指令的时候加上的CF的值的含义,关键在于所加上的CF的值是被什么指令设置的。如果CF的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值。加法运算先是低位相加,再高位相加加上低位相加产生的进位值。
- 编程:计算1EF000H+201000H,结果存放在AX(高16位)和BX(低16位)中。
mov ax,001EH mov bx,0F000H add bx,1000H adc ax,0020H
- 1
- 2
- 3
- 4
- 编程:1EF0001000H+2010001EF0H,结果存放在AX(高16位)、BX(次16位)中和cx(低16位)。
mov ax,001EH mov bx,0F000H mov cx,1000H add cx,1EF0H add bx,1000H adc ax,0020H
- 1
- 2
- 3
- 4
- 5
- 6
- 编程:对两个128位数据进行相加
assume cs:code,ds:data data segment db 16 dup(88H) db 16 dup(11H) data ends code segment start: mov ax,data mov ds,ax mov si,0 mov di,16 mov cx,8 call add128 mov ax,4C00H int 21H add128: push ax push cx push si push di sub ax,ax;将CF设置为0 s: mov ax,[si] adc ax,[di] mov [si],ax inc si;不能用add si,2代替 inc si;因为会影响cf位 inc di;而loop和inc不会影响 inc di loop s pop di pop si pop cx pop ax ret code ends end start
- 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
11.7 sbb指令
- sbb是带借位减法指令,利用了CF位上记录的借位值。格式:sbb 操作对象1,操作对象2,功能是:操作对象1=操作对象1-操作对象2-CF。
- 利用sbb指令我们可以对任意大的数据进行减法运算。sbb和adc是基于同样的思想设计的两条指令,在应用思路上sbb和adc类似。
- 编程:计算003E1000H-00202000H,结果放在ax、bx中
mov bx,1000H mov ax,003EH sbb bx,2000H sbb ax,0020H
- 1
- 2
- 3
- 4
11.8 cmp指令
- cmp是比较指令,功能上相当于减法指令,只是不保存结果。格式:cmp 操作对象1,操作对象2.功能:计算操作对象1-操作对象2但不保存结果,仅仅是根据计算结果对标志寄存器进行设置。
- cmp指令运算执行后通过做减法将对标志寄存器产生影响,其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
cmp ax,ax ;执行后结果为0,ZF=1,PF=1,SF=0,CF=0,OF=0 mov ax,8 mov bx,3 cmp ax,bx ;执行后ax、bx的值不变,ZF=0,PF=1,SF=0,CF=0,OF=0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
cmp ax,bx
- 1
- CPU在执行cmp指令时也包含了对无符号数运算和进行有符号数运算,所以利用cmp指令可以对无符号数进行比较也可以对有符号数进行比较。
- 单纯地考察SF的值不可能知道结果的正负。因为SF记录的只是可以在计算机中存放的相应位数的结果的正负(例如:add ah, al执行后,SF记录的是ah中的8位二进制信息所表示的数据的正负)。如果没有溢出发生的话,实际结果的正负和逻辑上真正结果的正负就一致了。。例如:22H(34)-0A0H(-96)=130=82H(是-126的补码),SF=1。
- 1、如果SF=1或SF=0,OF=0,逻辑上真正结果的正负=实际结果的正负。
- 2、如果SF=1或SF=0,OF=1,逻辑上真正结果的负正=实际结果的正负。
11.9 检测比较结果的条件转移指令
- 与cmp相配使用,根据cmp指令的比较结果(cmp指令执行后相关标志位的值)进行工作的指令。
- cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种:
- 根据无符号数的比较结果进行转移的条件转移指令,它们检测ZF、CF的值;
- 根据有符号数的比较结果进行转移的条件转移指令,它们检测SF、OF、ZF的值。
- 它们所检测的标志位都是cmp指令进行无符号数比较时候记录比较结果的标志位。
指令 含义 检测的相关标志位 je 等于则转移 ZF=1 jne 不等于则转移 ZF=0 jb 低于则转移 CF=1 jnb 不低于则转移 CF=0 ja 高于则转移 CF=0 and ZF=0 jna 不高于则转移 CF=1 or ZF=1 j e ne b nb a na jump equal not equal below not below above not above - 编程:如果ah的值等于bh则ah的值等于ah的值加ah的值,否则ah的值等于ah的值加上bh的值。
cmp ah,bh je s;ZF=1则跳转 add ah,bh jmp short ok s: add ah,bh ok:ret
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- je检测的是ZF的位置,不管je前面是什么指令,只要CPU执行je指令时,ZF=1那么就发生转移。
mov ax,0 mov ax,0 je s inc ax s: inc ax ;执行后ax的值等于1,add ax,0使得ZF=1,所以je指令将进行转移。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
课堂练习
编程:统计data段中数值为8的字节的个数,用ax保存统计结果。
;方案一 assume cs:code data segment db 8,11,8,1,8,5,63,38 data ends code segment start: mov ax,data mov ds,ax mov bx,0;ds:bx指向第一个字节 mov ax,0;初始化累加器 mov cx,0 s: cmp byte ptr [bx],8;和8进行比较 jne next;如果不相等转到next,继续循环 inc ax;如果相等就计数值加1 next: inc bx loop s;执行后:(ax)=3 mov ax,4c00h int 21h code ends end segment
- 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
;方案二 assume cs:code data segment db 8,11,8,1,8,5,63,38 data ends code segment start: mov ax,data mov ds,ax mov bx,0;ds:bx指向第一个字节 mov ax,0;初始化累加器 mov cx,0 s: cmp byte ptr [bx],8;和8进行比较 je ok;如果不相等转到ok,继续循环 jmp short next;如果不想等就转到next,继续循环 ok: inc ax;如果相等就计数值加1 next: inc bx loop s;执行后:(ax)=3 mov ax,4c00h int 21h code ends end segment
- 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
- 编程:统计data段中数值大于8的字节的个数,用ax保存统计结果。
assume cs:code data segment db 8,11,8,1,8,5,63,38 data ends code segment start: mov ax,data mov ds,ax mov bx,0;ds:bx指向第一个字节 mov ax,0;初始化累加器 mov cx,0 s: cmp byte ptr [bx],8;和8进行比较 jna next;如果大于8转到next,继续循环 inc ax;如果大于就计数值加1 next: inc bx loop s;执行后:(ax)=3 mov ax,4c00h int 21h code ends end segment
- 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
检测点 11.3
11.10 DF(direction flag)标志和串传送指令
- flag的第10位是DF,方向标志位,在串处理指令中,控制每次操作后si(一般指向原始偏移地址)、di(一般指向目标偏移地址)的增减。
- DF=0:每次操作后si、di递增;
- DF=1,每次操作后so、di递减。
- movsb(mov string byte)串传送指令,以字节为单位传送。将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器DF位的值将si和di递增1或递减1。movsw,以字为单位传送。将ds:si指向的内存单元中的字送入es:di中,然后根据标志寄存器DF位的值将si和di递增2或递减2。
- movsb和movsw进行的是串传送操作中的一个步骤,一般和rep配合使用,格式:rep movsb,rep的作用是根据cx 的值,重复执行后面的串传送指令。由于每次执行一次movsb指令si和di都会递增或递减指向后一个单元或前个单元,则rep movsb就可以循环实现(cx)个字符的传送。
- 1、传送的原始位置;
- 2、传送的目的位置;
- 3、传送的长度;
- 4、传送的方向。
- movsb功能:((es)*16+(di))=((ds)*16+(si)),如果DF=0,则(si)=(si)+1,(di)=(di)+1;如果DF=1,则(si)=(si)-1,(di)=(di)-1。
- 由于flag的DF位决定着串传送指令执行后,si和di改变的方向,8086CPU提供两条指令对DF位进行设置:
- cld指令:将标志寄存器的DF位设置为0;
- std指令:将标志寄存器的DF位设置为1。
11.11 pushf和popf
- pushf的功能 是件标志寄存器的值压栈,popf是从栈中弹出数据m,送入标志寄存器中。pushf和popf为直接访问标志寄存器提供了一种方法。
;下面的程序执行后ax的值是多少? mov ax,0 push ax popf mov ax,0fff0h add ax,0010h pushf pop ax and al,11000101b and ah 00001000b
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 编程:用串传送指令将data段总的第一个字符串复制到它后面的空间中。
assume cs:code data segment db'welcome to masm!' db 16 dup(0) data ends code segment start: mov ax,data mov ds,ax mov si,0;指向data:0 mov es,ax mov di,16;指向data:16 mov cx,16;rep循环16次 cld;设置DF=0,正向传送 rep movsb mov ax,4c00h int 21h code ends end start
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 用串传送指令将F00H段中的最后16个字符复制到data段中