汇编语言设计
汇编语言设计
机器语言是机器指令集合
早期:打纸带:1打孔,0不打孔
汇编语言与汇编指令
汇编语言主体是汇编指令
-
汇编指令是机器指令便于记忆书写格式
-
汇编指令是机器指令助记符
-
机器指令100110001100001100000
汇编MOV,AX,BX
汇编语言编写程序工作过程
汇编指令--编译器--机器码
伪指令--编译器执行
汇编指令--机器码助记符
其他符号--编译器识别
计算机组成
cpu,总线,内存,扩展槽
指令和数据存放在内存或磁盘上,都是二进制
数据如何表示:二进制B,八进制O,十进制D,十六进制H
计算机中的存储单元
一个存储器有128单元
8086有20条数据总线,那有2的20次方的,1MB的空间
总线:链接cpu和其他芯片的导线,
逻辑上总线分:数据总线,地址总线,控制总线
地址总线
地址总线宽度,决定了可寻址总线的存储单元大小
数据总线
cou内存和其他器件之间的数据传送是通过数据总线进行的,数据总线宽度决定了cou的外界数据传输速度
8088 8位,8086,16位
控制总线
cpu对外部设备器件进行控制
内存的读写和地址空间
存储单元地址(地址信息)
器件的选择,读或写的命令(控制信息)
读或写的数据(数据信息)
什么是内存地址空间
RAM:能读能写,没电就没了,
ROM:只读,系统的BIOS
所有物理存储器看做一个由若干存储单元组成的逻辑存储器
每个物理存储器在逻辑存储器中占有一个地址段,即一段地址空间
汇编语言实践语言的环境搭建
目标:理解计算机底层工作原理
DOS环境
链接:https://pan.baidu.com/s/1SMBlRQQXeJ4EUMmVhPfCxw?pwd=kaip
提取码:kaip
--来自百度网盘超级会员V5的分享
解压后的文件夹如下:
其中:
DOSBox0.74-win32-installer.exe是DOS模拟器的安装文件;
MASM文件夹中是汇编程序设计中用到的命令;
EX文件夹中提供了几个汇编程序作为示例。
照下面的提示完成环境的配置。
1. 将MASM文件夹拷贝到工作盘
MASM中包含了8086汇编程序设计中要用到的几个必需的工具(masm.exe汇编、link.exe连接、Debug.exe调试,edit.com和edlin.com是两个编辑程序。),如下:
将这个文件夹拷贝到你习惯的工作盘(我用D盘作工作盘,拷贝后,这些文件在D:\MASM中)。
2. 安装DOSBox
双击DOSBox0.74-win32-installer.exe,照提示安装,最后,桌面上有快捷方式:
3. 启动DOSBox
双击上面的图标,启动DOSBox0.74。启动后的界面如下:
4.挂接MASM文件夹
在Z:>提示符后输入命令mount c d:\masm。这个命令的意思是,将本机d:\masm文件夹作为模拟器的C盘。
运行命令后的界面如下:
注意:d:\masm是第1步拷贝到的工作目录,若你在第1步设置的是其他工作,请按你的实际设置改动。
下面,将工作盘转换到C盘(输入C:并回车),再查看目录(dir再回车),可以看到类似下面的界面,标志着工作环境设置好了。
在以后工作时,只需要第3、4步即可。
寄存器和数据存储
运算器进行信息处理
寄存器进行信息存储
控制器协调各种器件
内存总线实现CPU各器件之间的联系
14个寄存器
通用寄存器AX例子
在AX中存储18D
18D --十进制
12H --十六进制
10010 --二进制
问:8088 如何8086兼容?
分高低位
01001110 00100000
高位 低位
AH AL AX
BH BL BX
CH CL CX
DH DL DX
字:一个字可以存在一个16位的寄存器中
高位字节在高位寄存器高8位,低位字节在低8位寄存器
MOV add指令
mov ax.18 AX = 18 相当于赋值
mov ah.78 AH = 78 把78放到了高 8位
add ax.8 AX = AX +8 在AX数据加8
mov.ax.bx AX = BX BX数据送入AX中
add ax.bx AX = AX+BX 把AX,BX 内容相加
汇编指令不区分大小写
确定物理地址方法
川普访问内存单元都要给出内存单元地址
每一个内存单元在这个空间构成的空间都有唯一地址,这个地址叫物理地址
8086有20位地址总线,寻址能力是1mb
8086是16位cpu,寻址能力是64KB
如何解决寻址空间的矛盾
用两个16位地址和段地址,偏移地址合成一个20位的物理地址
物理地址 = 段地址X16+偏移地址
例子:
段地址: 1230 偏移1位
偏移地址 00C8
12300
+00C8
= 123C8
物理地址 = 123C8
本质含义
用两个16位地址相加得到20位物理地址
本质含义:cpu在访问内存时,用一个基础地址和一个相对于基础地址的偏移地址相加,给出一个内存单元的物理地址
内存的分段表示法
内存并没有分段,段的划分来自于cpu
同一段内存,多种分段方案
根据段地址计算偏移地址
4个段寄存器
CS-代码段DS数据段-SS栈段-ES段加
debug的使用
R查看寄存器的内容
R 寄存器名 改变指定寄存器内容(128字节)
RCS,RIP
D命令:查看内存中的内容
D段地址:偏移地址-列出内存中指定地址处的内容
D 1000:0
D段地址:偏移地址 结尾偏移地址-;列出内存中指定地址范围内容
E命令
E段地址:偏移地址 数据1
EE段地址:偏移地址
E 1000:0
逐个询问式修改
空格接收,继续
回车,结束
U命令将内存中机器指令翻译成汇编指令
有汇编指令
mov ax,0123H
mov bx,0003H
mov ax,bx
add ax,bx
对应机器码
B8 23 01
BB 03 00
89 D8
01 D8
e 地址- 数据写入
d 地址查看
u地址-查看代码
A命令
有汇编指令
mov ax,0123H
mov bx,0003H
mav ax,bx
add ax,bx
对应机器码
B8 23 01
BB 03 00
89 D8
01 D8
a 地址-写入汇编指令
d 地址-查看数据
u 地址 -查看代码
T指令(执行机器命令)
t 执行CS:IP处的指令
mov ax,123H
两个关键的寄存器
CS:代码寄存器
IP:指令指针寄存器
CS:IP :cpu将内存中CS:IP指向的内容当做指令执行
CS内容是2000H,IP中内容是0000H
8086PC工作过程简要概述
1.CS:IP指向内存单元读取指令,读取的指令进入指令缓存器
2.IP = IP+ 所读取的指令的长度,从而指向下一条指令
3.执行指令,转到步骤1
jmp指令
事实:执行何处的指令,去取决于CS:IP
应用;可以通过改变CS IP的内容,来控制执行目标的指令
转移指令jmp
同时修改cs,ip内容
jmp段地址:偏移地址
jmp:2AE3:3
jmp:30B16
用指令中给出的段地址修改cs,偏移地址修改ip、
仅修改ip内容
jmp 某一合法寄存器
jmp ax (类似于mov IP,ax)
jmp bx
内存中字的存储
8086 16位作为一个字
高8位,低8位
低位字节低地址单元,高位字节高地址单元
0是低地址单元,1是高地址单元
字单元:由两个连续地址连续内存单元组成,存放一个字型数据
原理在一个字单元中,低地址单元存放低位字节,搞地质单元存放高位字节
用DS和[address]实现字的传送
要解决的问题:cpu从内存单元中要读取数据
在8086中,内存地址有段地址和偏移地址组成(段地址:偏移地址)
DS寄存器存放要访问的数据和段地址
偏移地址用[]形式
将段地址送入DS的方式
mov bx,1000H
mov bs, bx
套路是: 数据---一般寄存器---段寄存器
字的传送
8086可以一次性传送一个字
例子:
mov bx,1000H
mov ds ,bx
mov ax, [0] # 1000:0处的字型数据送入ax
mov[0],cx # cx中的16位数据送到1000:0处
[0] -- 1123
[1] -- 2211
[2] -- 1122
DS与数据段
对内存单元中数据的访问
对于8086 可以根据需要将一组内存单元定义为一个段
将一组长度N,地址连续,起始地址16的倍数内存单元当做专门存储数据内存空间,从而定义的一个数据段
段地址 123BH 起始偏移地址0000H 长度10字节
处理方法 DS([address])
用DS存放数据段的段地址
用相关指令访问数据段中的具体单元,单元地址由[address]
将123B0H--123BAH定义为数据段
累加数据段中前3个单元的数据
mov ax,123BH
mov ds,ax
mov al,[0]
add al,[0]
add,al,[1]
add al,[2]
用mov指令操作数据
add指令操作
栈结构
栈的基本操作
入栈和出栈
入栈:将一个新的元素放到栈顶
出栈:从栈顶取出一个元素
栈顶元素总是在最后入栈,需要出栈,又最先从栈中取出
cpu提供栈机制
8086cpu提供相关指令,支持用栈的方式访问内存空间
基于8086cpu编程,可以将一段内存当栈来使用
PUSH入栈和POP出栈指令
push ax:将ax从中的数据送入栈中
pop ax:从栈顶取出数据送入ax
问题:cpu如何知道一段内存空间被当做栈使用?
执行push和pop的时候,如何知道哪个单元是栈顶单元?
答案
8086中有两个与栈相关的寄存器
栈段寄存器SS--存放栈顶的段地址
栈顶指针寄存器SP--存放栈顶的偏移地址
栈的操作
mov ax,1000H
mov ss,ax
mov sp,0010H
mov ax,001AH
mov bx,001BH
push ax
push bx
pop ax
pop bx
!
执行入栈,栈顶超出栈空间
总结
push,pop实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push指令访问的内存单元地址不是在指令中给出的,而是有SS:SP指出的
段总结
物理地址 = 段地址x16 +偏移地址
数据段
- 将段地址放在DS中
- 用mov,add,sub等访问内存单元指令时,cpu将我们定义的数据段中内容当做数据段访问
代码段
- 将段地址放在CS中,将段中第一条指令的偏移地址放在IP
- CPU将执行我么定义四的代码段中指令
栈段
- 将段地址放在SS,将栈顶单元偏移的放在SP中
- CPU在需要我们进行栈操作push,pop,就将我们定义的栈段当做栈空间来使用
汇编语言程序
汇编语言编程序的工作过程
程序员--汇编程序--编译器--机器码--计算机
伪指令:没有对应的机器码指令,最终不被cpu所执行
伪指令是编译器执行,编译器根据伪指令进行相关的编译工作
汇编程序:汇编程序好伪指令的文本
程序返回:程序结束后将cpu的控制权交还给使运行的程序系统
程序的三种伪指令
段定义:一个会变程序是多个段组成的,这些段用来被存放代码,数据或当做栈空间来使用
一个有意义的汇编程序中至少有一个段,这个段用来存放代码
段名 segment --- 段的开始
段名 ends --- 段的结束
assume cs:codesg
codesg segment
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00h
int 21h
codesg ends
end
'''
end
汇编程序结束标志
assume
含义是假设某一段寄存器中某一个使用segment,ends定义段相关联--assume cs:cdoesg指的是cs寄存器与codesg关联,将定义的codesg当做程序的代码来使用
'''
源程序经过编译连接后变为机器码
源程序文件 .asm ---可执行 .exe
汇编程序结构
在debug中直接写入指令编写汇编程序
适用于简单的短小精悍的程序
单独编写成源文件在编译可执行文件--编写大程序
注释 ;
编写一个汇编程序
步骤
-
定义一个段
-
实现处理任务
-
指出程序在何时结束
-
段与段寄存器关联
-
加上程序返回的代码
-
assume cs:abc abc segment abc segment mov ax,2 add ax,ax add ax,ax mov ax,4c00h int 21h abc ends end
程序中出现的错误
语法错误
写错了。。。
逻辑错误
脑子抽了。。。
写出源程序到可执行文件过程
编辑源程序
编译
对目标文件.OB对一个源程序进行编译要得到的结果
列表文件.LST是编译器将源程序编译为目标文件的过程中产生的中间结果
交叉引用文件。CRF和列表文件一样,是编译器将源程序编译为目标文件过程中产生的中间结果
对源程序编译结束,编译器输出最后两行告诉错误必须改正的错误
assume cs:codesg
codesg segment
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00h
int 21h
codesg ends
end
提示语法错误
severe errirs
找不到所给出的源文件
连接
可执行文件是对一个程序进行连接要得到的最终结果
映象文件:是连接程序将目标文件连接为可执行文件过程中产生中间结果
库文件包含了一些可以调用的子程序,如果我们的程序中调用了某一个库文件中的子程序,就需要连接的时候将这个库文件和我们的目标文件连接到一起,生成可执行文件、
no stack segment,一个 没有栈段的错误警告,可以不去理会
执行程序;直接输入文件名
程序的运行和跟踪
DEBUG装在程序EXE
程序加载后,DS中存放着程序所在内存区的段地址,这个内存区的偏移地址是0,程序内存区地址是DS:0
这个内存区前256字节存PSP,DOS用来程序进行通信
从256字节向后空间存放的是程序,CS值是DS+10H
程序加载后,CX存放代码长度
继续命令P
类似于T命令,逐条执行指令,显示结果,但遇到子程序,中断等,直接执行,然后显示结果
运行命令G
从指定地址处开始运行程序,知道断点结束或者程序正常结束
在DOS中执行
程序执行的常态
一个内存单元的描述:
- 内存单元地址
- 内存单元长度
[...]表示一个内存单元
、
(...)表示一个内存单元或寄存器中内容
LOOP指令
功能:实现循环
cpu执行loop指令时进行的操作
-
(cx)=(cx)-1
-
判断cx的值
-
不为0则转到标号处执行程序
-
如果是0就向下执行
-
要求
cx要提前存放循环次数,(cx)影响着loop指令执行结果
要定义一个符号
assume cs:code
code segment
mov ax,2
mov cx,11
s:add ax,ax
loop s
mov ax,4c00h
int 21h
code ends
end
例子:编程计算 2^2 , 2^3
cx和loop指令想配合实现循环功能的三个要点
1.在cx中存放循环次数
2.用标号指定循环开始的位置
3.在标号和loop指令的中间,写上要循环执行的程序段(循环体)
例子:计算 123x236
assume cs:code
code segment
mov ax,0
mov cx,236
s: add ax,123
loop s
mov ax,4c00h
code ends
end
例子
编译:
masm p5-1.asm
link p5-1
debug p5-1.exe
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,6
mov al,[bx]
mov ah,0
mov dx,0
mov cx,3
s: add dx,ax
loop s
mov ax,4c00h
int 21h
code ends
end
引入段前缀,异常现象的对策
访问内存单元--loop和[bx]
问题:计算ffff:0-ffff:b字节单元中的数据的和,结果存储在dx中
分析:略
对策:取出8位数据,加到16位寄存器
mov al,ds:[addr] 取出8位数据
mov ah,0
add dx,ax
段前缀的使用
代码段中数据的使用
将内存ffff:0中数据拷贝到0:200-0:20b
问题,程序中直接写地址,很危险
对策
在程序中段中存放数据,运行时由操作系统分配空间
段的类别:数据段,代码段,栈段
各种段中均可以有数据
可以在单个段中安置,也可以将数据代码,栈放入不同的段中
实际应用
问题编程计算以下8个数据的和,结果存放到ax寄存器中
解决方案
assume cd:code
code segment
dw 0123H,0456H,0789H,0ABCH,0DEFH,0CBAH,0987 ;在代码段中定义数据,只要求数据本身,并未指定在哪些单元中
mov bx,0
mov ax,0
mov cx,8
s: add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end
dw:定义一个字
db:定义一个字节
dd:定义一个双字
代码有问题
改进
assume cs:code
code segment
dw 0123H,0456H,0789H,0ABCH,0DEFH,0CBAH,0987 ;在代码段中定义数据,只要求数据本身,并未指定在哪些单元中
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
code ends
end start ;end 通知编译器程序结束外,还可以通知编译器入口在什么地方
在代码段中使用栈
程序运行时候。定义存放的数据cs:0--cs:f,一共8个单元
依次将八个单元中数据入栈,然后一次出栈找到8个字单元,从而实现数据的逆序存放
栈需要内存空间,在程序中通过定义:空数据来获得
数据逆序存放程序
assume cs:codesg
codesg segment
dw 0123H,0456H,0789H,0ABCH,0DEFH,0CBAH,0987 ;在代码段中定义数据,只要求数据本身,并未指定在哪些单元中
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
start:
mov ax,cs
mov ss,ax
mov sp,30h
mov bx,0
mov cx,8
s:push cs:[bx]
add bx,2
loop s
mov bx,0
mov cx,8
s0:pop cs:[bx]
add bx,2
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
将数据代码栈放入不同段
assume cs:code,ds:data,ss:stack
data segment
dw 0123H,0456H,0789H,0ABCH,0DEFH,0CBAH,0987
data ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends
; --------------------------- 数据
code segment
start:
mov ax,stack
mov ss,ax
mov sp,20h
mov ax,data
mov ds,ax
mov bx,0
mov cx,8
;---------------------------入栈
s:push [bx]
add bx,2
loop s
mov bx,0
mov cx,8
; --------------------------出栈
s0:pop [bx]
add bx,2
loop s0
mov ax,4c00h
int 21h
code ends
end
内存寻址方式
处理字符问题
汇编中,用‘..................'的方式指明数据是字符形式给出的,编译器把他们转化为响应的ASCII码
大小写字符问题
大写+20H --->小写 小写-20H --->大写
大小写转换
第一个字符串:小--大
第二个 大--小
assume cs:codesg,ds:datasg
datasg segment
db 'BsSic'
db 'INforMAtion'
datasg ends
codesg:segment
start:
mov ax,datasg
mov ds,ax
;第一个字符串:
mov bx,0
mov cx,5
s:mov al,[bx]
and al,11011111b
mov [bx],al
inc bx
loop s
;第二个字符串
mov bx,5
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
[bx+idata]含义
表示一个内存单元,它的偏移地址为(bx)+idata (bx中数值加上idata)
mov ax,[bx+200]/mov ax,[200+bx]
将一个内存单元的内容送入ax
这个内存单元的段地址在ds中,偏移地址在ds中,偏移地址为200加上bx的数值,数学化的描述是(ax) = ((ds)*16+200+(bx))
指令mov,ax,[bx+200]其他用法
mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200
用数组方法解决大小写转换问题
assume cs:codesg,ds:datasg
datasg segment
db 'BasiC'
db 'MinIX'
datasg ends
codesg segment
start:
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s:mov al,[bx]
and al,11011111b
mov [bx],al
mov al,[5+bx]
or al,00100000b
mov [5+bx],al
inc bx
loop s
mov ax,4c00h
int 21h
codesg ends
end start
SI和DI寄存器
8086cpu有十四个寄存器
通用寄存器 AX,BX,CX,DX
变址寄存器 SI,DI
指针寄存器 SP,DP
指令指针寄存器 IP
段寄存器 CS,SS,DS,ES
标志寄存器 PSW
执行与地址有关操作
SI,DI和BX相近的寄存器
SI:源变址寄存器
DI目标变址寄存器
区别:SI,DI不能够分成两个8位寄存器来使用
1.mov bx,0
- mov s,0
- mov di 0
例子:用寄存器SI,SI实现将字符串‘welcome to masm’复制到特后面的数据区
源数据起始地址:datasg:0
目标数据起始地址:datasg:16
用ds:si指向要复制的原始字符串
用ds:di指向目的空间
然后用一个循环来完成复制
原地址:si
目标地址:di
assume cs:codesg,ds:datasg
datasg segment
db 'welcome to masm'
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
codesg ends
end start
[bx+si] [bx+di]指定地址
- [bx+si] 表示一个内存单元
- 偏移地址 bx +si 即是数值上加si中的数值
指令mov ax, bx+si 含义
- 将一个内存单元的内容送入ax
- 这个内存单元长度是2字节(字单元),存放一个字
- 偏移地址是bx中数值加上si中的数值
- 段地址 在ds中
指令mov ax,[bx+si]的数学化描述
- (ax) = ((ds)*16+(bx)+(si))
- mov ax,[bx+si]其他用法
- mov ax,[bx] [si]
例子
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]
[bx+si+idata] 和 [bx+di+idata]
偏移地址是 (bx) +(si)+idata 就是bx中的数值加上si中数值再加上idata
move ax,[bx+si+idata]含义
将一个内存单元的内容送入ax
这个内存单元长度是2字节,存放一个字
偏移地址中bx中数值加上idata,段地址在ds中
数字化描述
(ax=(ds)*16)+(bx)+(si)+idata
指令mov,ax,[bx+si+idata] 其他写法
- mov ax,[bx+200+si]
- mov ax,[200+bx+si]
- mov ax,200[bx] [si]
- mov ax,[bx].200[si]
- mov ax,[bx] [si].200
- mov ax,[bx] [si]
例子
内存中数据 2000:1000 BE 00 06 00 6A 22
程序执行后
mov ax,2000H
mov ds,ax
mov bx,1000H
mov si,0
mov ax,[bx+2+si]
inc si
mov cx,[bx+2+si]
inc si
mov di,si
mov ax,[bx+2+di]
内存寻址方式的灵活应用
例子
例子
二重循环用了同一个cx,解决办法
但是存在问题是,cx占用寄存器资源,比较浪费
可以利用固定的储存单元,也可以用栈来存数据
直接寻址过程
寄存器间间接寻址过程
寄存器相对寻址过程
基址变址寻址过程
相对基址变址寻址过程
哪些寄存器用于寻址
用于内存寻址的寄存器用法
只有bx,bp,si,di可以用在[…]对内存单元寻址
bx以外的通用寄存器,段寄存器不可以用[…]
bx,bp区别:bx默认指ds段
bp默认指ss段
正确的指令
错误的指令
汇编语言中数据位置的表达
立即数
对于直接包含在机器指令的数据称为立即数
寄存器
指令要处理数据在寄存器中
内存:段地址和偏移地址
指令要处理的数据在内存中,由SA:EA确定内存单元
指令要处理的数据有多长
字word操作
-----------------
mov ax 1
mov bx,ds:[0]
mov ds,ax
mov ds:[0] ,ax
inc ax
add ax,1000
--------------------
mov al,1
mov al,bl
mov al,ds:[0]
mov ds:[0],al
inc al
add al,100
--------------------
mov word ptr ds:[0],1
inc word ptr [bx]
inc word ptr ds:[0]
add word ptr [bx],2
-----------------
mov byte ptr ds:[0],1
inc byte ptr [bx]
inc byte ptr ds:[0]
add byte ptr [bx],2
字节操作
用word ptr 或 byte ptr指明
在没有寄存器参与的内存单元访问指令中,用word ptr 或 byte ptr 显性指明所要访问的内存单元长度是很有必要的,否则cpu将无法得知所要访问是字单元,还是字节单元
DIV指令
div是除法指令,使用div做除法时候
被除数:默认放在放在AX或DX和AX中
除数:8位或16位,在寄存器或内存单元中
div指令格式
div 寄存器
div 内存单元
例子
双字型数据定义
data segment
db 1:定义字节型数据 在,data:0 占一个字节
dw 1:定义字型数据0001H,在data:1处,占2个字节
dd 1 定义双字型数据00000001H在data:3处,占2个字(4个字节)
data ends
data segment
dd 100001
dw 100
dw 0
data ends
mov ax,data
mov ds,ax
mov ax,ds:[0]
mov dx,ds:[2]
div word ptr ds:[4]
mov ds:[6],ax
dup 功能
dup 和db,dw,dd等数据定义伪指令配合使用,用来进行数据的重复
db 重复的次数dup(重复字节型数据)
dw 重复的次数dup(重复字节型数据)
dd 重复双字数据
assume cs:code,ds:data
data segment
db 3 dup(0)
db 3 dup(0,1,2)
db 80 dup(0)
db 3 dup('abc','ABC')
data ends
code segment
mov ax,,data
mov ds,ax
mov ax,4c00h
code ends
end
流程转移和子程序
北京:一般情况下指令是顺序逐条执行的,实际中,需要改变执行流程
可以控制cpu的内存中某处代码指令
可以修改ip,或同时修改cs和ip指令
转移指令分类
- 按转移行为
-
- 段内转移:只修改ip 如imp ax
- 段间转移: 同时修改cs和ip ,imp 1000。
- 根据指令对ip修改的范围不同
-
- 段内短转移:ip修改范围128-127
- 段内近转移:ip修改范围32768-32767
- 按转移指令
-
- 无条件转移指令(imp)
- 条件转移指令 jcxz
- 循环指令 loop
- 过程
- 中断
操作符 offset
用操作符 offset 取得标号的偏移地址
offse 标号
assume cs:codeseg
start: mov ax,offset start ;相当于mov ax,0
s:mov ax,offset s ;相当于 mov ax,3
codeseg ends
end start
有如下程序段,添加2条指令,使改程序运行汇总s指令复制到s0处
assume cs:codesg
codesg segment
s:mov ax,bx
mov si,offset s
mov di,offset s0
mov ax,cs:[si]
mov cs:[di],ax
s0:nop
nop ; nop机器码 占一个字节,起到一个占位的作用
codesg ends
end
s 和s0处指令所在的内存单元是多少
cs:offset 和cs:offset s0
将 s处的指令复制到s0处就是将cs:offset s处的数据复制到cs:offset s0处
地址如何表示
段地址已知在cs中,偏移地址已经送到si和di中
要复制的数据有多长
mov,ax,bx指令长度是两个字节,1个字
jmp指令--无条件转移
jmp指令的功能
无条件转移,可以只修改ip,也可以同时修改cs和ip
jmp指令要给出两种信息
转移的目的地址
转移的距离
依据位移进行转移
jmp指令:依据位移进行转移
assume cs:codesg
codesg segment
start:
mov ax,0
jmp short s
add ax,1
s:inc ax
codesg ends
end start
1.ip = 0003 cs:ip 指向E8 05
读取指令码 E8 05 进入指令缓冲器
ip = ip +所读指令长度 = ip +2=0005 cs:ip 指向 add ax,0001
cpu指令缓冲器中的指令E805
指令E805执行后,ip=ip+05=000AH cs:ip 指向inc ax
assume cs:codesg
codesg segment
start: mov ax,0
jmp short s
add ax,1
nop
nop
s: inc ax
codesg ends
end start
俩种段内转移
短转移
jmp short 标号
功能 ip = ip+8位位移
原理
8位位移 = 标号 处的地址-jmp指令后的第一个字节的地址
short 指明此处的位移是9位位移
8位位移的范围是-128~127用补码表示,不能超出范围
8位位移编译程序在编译时候算出
进转移
jmp near ptr 标号
功能 ip = ip +16
原理
16位位移 = 标号处的地址 imp 指令后的第一个、字节的地址
near ptr 指明此处的位移是16位位移,进行段内转移
16位移范围 -32769~32767,用补码表示
远转移 jmp far ptr 标号
段间转移:
far ptr 指明了跳转目的地址,即包含了标号段地址cs和偏移地址ip
段内转移
near ptr 指明了相对于当前ip转移位移,而不是转移的目的地址
转移地址在寄存器中jmp指令
指令格式 jmp 16位寄存器
ip = 16位寄存器
jmp ax jmp bx
assume cs:codesg
codesg segement
start:mov ax,0
mov bx,ax
jmp bx
mov ax,0123H
codesg ends
end start
转移地址在内存中的jmp指令
jwp word ptr 内存单元地址
功能:从内存单元地址处开始存放一个字,是转移的目的偏移地址
jmp dword ptr 内存单元的地址
段间转移
功能:从内存单元地址开始存放两个字,高地址处的字是转移的目的段地址,低地址是转移的目的偏移地址
小结
jmp 标号
段间转移(远转移) : jmp far ptr 标号
段内短转移 jmp short 标号 8位位移
段内近转移: jmp near ptr 标号 16位位移
jmp 寄存器 jmp bx 16位移
jmp内存单元(表示跳转到地址)
段内转移 jmp word ptr 内存单元地址 jmp word ptr [bx]
段间转移 jmp dword ptr 内存单元地址 jmp dword ptr [bx]
注意
源程序中,不允许使用“jmp 2000:0100”的转移指令实现段间转移 • 这是在 Debug 中使用的汇编指令,汇编编译器并不认识 • 如果在源程序中使用,编译时也会报错。
其他转移指令
jcxz指令
格式 jcxz 标号
功能 如果 cx=0 则转移到标号执行
- cx 不等于 0 什么也不做
- cx = 0 时ip=ip +8位位移
- 9位位移 = 标号 处的地址-jxcz指令后的第一个字节的地址
- 8位位移的范围是-128~127用补码表示
- 8位位移由编译程序在编译时算出
jxcz是有条件转移指令
- 所有的有条件转移指令
- 所有有条件转移指令都是短转移
- 对ip修改范围是-128~127
- 在对应的机器码中包含转移的位移,而不是目的的地址
loop指令
指令操作
cx=cx-1
当cx不等于0时则转移到标号处执行
cx=0,程序向下执行
loop s在执行时只涉及到s的位移
-4前移4个字节,补码表示FCH
- 所有的有条件转移指令
- 所有有条件转移指令都是短转移
- 对ip修改范围是-128~127
- 在对应的机器码中包含转移的位移,而不是目的的地址
根据位移进行相对转移的意义
对ip的修改时根据转移的目的地址和转移起始地址之间的位移进行
jmp short
jmp near ptr
jxcz 标号
loop标号
如果 loop s 的机器码中包含是s地址,则就对程序段的内存偏移地址有了严格的限制,易引发错误
当机器码中包含的是转移的位移无论s处的指令的实际地址是多少,loop指令的相对位移是不变的
call指令 ret指令
mov ax,0
call s
mov ax,4c00h
int 21h
s:add ax,1
ret
实质就是流程转移指令,它们都是修改ip同时修改cs,ip
call 指令
就是调用子程序
实质就是流程转移,和jmp原理相似
两步操作
当前ip或cs:ip压入栈
转移到标号处执行指令
call far ptr 标号实现的是段间转移
sp = sp -2
ssx 16+sp = cs
sp = sp -2
ssx16+sp = ip
相当于
push cs
push ip
jmp far ptr
转移地址在寄存器中的call指令
格式
call 16 寄存器
sp = sp-2
ss*16 +sp = ip
ip=16位寄存器
相当于
push ip
jmp 16位寄存器
转移地址在内存中call指令
call word ptr 内存单元地址
mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0]
执行后 ip = 0123H ,sp=0EH
call word ptr
push cs
push ip
jmp word ptr 内存单元地址
mov sp,10h
mov ax,0123H
mov ds:[0],ax
mov word ptr ds:[2],0
call word ptr ds:[0]
执行cs = 0 ip = 0123H sp = 0CH
低地址放偏移地址
高地址放段地址
call 和ret配合使用
计算2的N次方,N由cx提供
assume cs:code
code segment
start:
mov ax,1
mov cx,3
call s
mov bx,ax
mov ax,4c00h
int 21h
ret
code ends
end start
assume cs:code,ss:stack
stack segment
db 8 dup (0)
db 8 dup (0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,1000
call s
mov ax,4c00h
int 21h
s:add ax,ax
ret
code ends
end start
除法div指令
div是除法
格式 div 寄存器
div 内存单元
使用 div做除法时候
被除数 放在AX DX和AX中
除数8位16位,在寄存器或内存单元
mull指令做乘法
计算 100*10
mov al,100
mov bl,10
mul bl
ax = 1000
计算 100*10000
分析100小于255 课10000大于255,所以必须做61乘法
mov ax,100
mov bx,10000
mul bx
结果 dx =000FH
ax = 4240h
F4240H = 1000000
模块化程序设计
调用子程序:call指令
返回:ret指令
子程序:根据提供的参数处理一定事务处理后,将返回值提供给调用者
参数和结果传递
方案
用寄存器
用内存单元
用栈
用寄存器传递参数
参数放到dx,bx = N
子程序中多个mul指令计算N^#
汇编子程序
cube: mov ax,bx
mul bx
mul bx
ret
计算data段一组数据3次方
结果保存在后面dword中
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start:mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8
S:mov bx,[si]
call cube
mov [di],ax
mov [di].2,dx
add si,2
add di,4
loop s
mov ax,4c00h
int 21h
code ends
end start
用内存单元批量出阿迪数据
将批量数据放到内存汇总,然后将它们所在内存空间的首地址放在寄存器中,首地址放到寄存器中,传递哦给需要的子程序中
用栈传递参数
由调用者将需要传递给子程序的参数
压入栈中,子程序从栈中取得参数
进入子程序前,参数 a,b入栈
调用子程序,将使栈顶存放IP
结果 dx:ax = (a-b)^3
mov ax,1
push ax
mov ax,3
push ax
call difcube
diffcube: push bp
mov bp,sp
mov ax,[bp+4]
sub ax,[bp+6]
mov bp,ax
mul bp
mul bp
pop bp
ret 4
code ends
j寄存器冲突方案解决
在编写调用子程序冲突时有没有会产生冲突的寄存器
如果有,调用者会使用别的寄存器
在编写子程序时候,不要使用会产生冲突的寄存器
但是这个方案不好
子程序标准框架:子程序使用的寄存器入栈
子程序使用的寄存器出栈
返回(ret,retf)
在子程序开始,将要用到的所寄存器内容都保存起来,在子程序返回前再恢复
标志寄存器
标志寄存器结构
flag寄存器是按位起作用,每一位都有专门的含义,记录特定信息
8086没有使用flag 1,3,5,12,13,14 这些位不具有任何含义
作用
用来存储相关指令某些结果
用来为cpu执行相关指令提供行为依据
用来控制CPU相关工作方式
直接访问标志寄存器
pushf:将标志寄存器值压入栈
popf:从栈中弹出数据,送入标志寄存器
ZF零标志
ZF标记相关指令计算结果是否为0
ZF=1 表示结果是0 1表示逻辑真
ZF=0 结果不是0,0表示逻辑假
8086指令集中,有的指令执行是影响标志寄存器,多数是逻辑算数运算
有的指令执行对标志寄存器没有影响,比如mov,push,pop,多数是传送指令
使用一条指令时候,要注意这条指令的全部功能,包括执行结果对标记寄存的哪些标志位影响
PF奇偶标志
PF记录指令执行,结果所有二进制位1个数
1的个数是偶数 PF=1
1的个数是奇数 PF=0
SF符号标志
结果是负 SF=1
不是负数 SF=0
CF进位标志
在进行无符号数运算,CF记录了运算届法国最高有效位和更高位进位值
对于位数为N的无符号的数来说,对应的二进制信息最高位就是第N-1位,是最高的有效位
假想存在第N位,就是相对最高有效位的更高位
CF记录执行执行后CF=1
无进位或借位 CF=0
OF溢出标志
在进行有符号数运算时候,如果超过了机器所能表示的范围叫溢出
CF和OF区别
CF是对无符号数运算有意义的进位标志位
OF是对有符号运算有意义的溢出标志位
mov al,0F0H
add al,88H
CF=1,OF=1 当无符号数运算有进位,当有符号运算溢出
adc带进位加法指令
格式 adc操作对象,操作对象2
功能操作对象1 = 操作对象1+操作对象2+CF
例 adc ax,bx 实现功能ax = ax+bx+CF
8086指令提供add指令,完成8位或16位加法
例子:计算·11EF000H +201000H 结果放在ax 高16位和 bx低16位
先将低16位相加,然后将16位进位值相加
mov ax,001EH
mov bx,0F000H
add bx,1000H
adc ax,0020H
例子 计算1E F000 1000H+20 1000 1EF0H
结果放在 ax 高16位 bx 次高16位 cx低16位
mov ax,001EH
mov bx,0F000H
mov cx,1000H
add cx,1EF0H
add bx,1000H
adc ax,0020H
128位数据相加
对128位数据相加
add128
两个逆序存放128数据相加
数据是128位,需要8个单元,由低地址单元到高地址单元,依次存放由低到高各个字
ds:si指向存储第一个数的内存空间
ds:di指向存储存储的第二个数内存空间
运算结果存储的第一个数的存储空间
sbb指令
带借位减法指令
格式 sbb操作对象,操作对象2
功能操作对象1 = 操作对象1-操作对象2-CF
与sub区别,利用CF位上记录的借位值
比如 sbb ax bx
实现功能 ax = ax-bx-CF
应用 对任意大的数据进行减法运算
计算
结果放在 ax,bx
mov bx,1000H
mov ax,003EH
sub bx,2000H
sbb ax,0020H
cmp指令
格式 cmp操作对象1,操作对象2
功能计算操作对象-操作对象2
cmp是比较指令功能相当于减法指令,知识不保存结果
cmp指令执行后,将对标志寄存器产生影响
无符号数比较标志位取值
通过cmp指令执行后相关标志位的值,可以看出结果
比较指令设计思路,通过做减法运算影响标志寄存器,标志寄存器相关位取值,体现比较好的结果
有符号数比较与标志位取值
用cmp进行有符号数比较时,cpu哪些标志比较时
cmp ah,bh
仅凭结果正负SF无法得出结论,需要配合1是否溢出 得到结论
条件转移指令
根据单个标志位转移指令
调价转移指令使用
j新系列指令cmp指令配合,构造条件转移指令
不必再考虑cmp指令对相关标志影响
可以直接考虑cmp指令配合
例子
条件转移指令
可以根据某种条件,决定是否转移,程序执行流程
转移 = 修改ip
通过检测标志位,由标志体现调价
条件转移指令通常都和cmp相配合使用、
cmp指令改变标志位
双分支结结构实现
例子
初始设置ax=0,然后用循环依次比较每个字节的值,找到一个和8相等的数就将ax+1
初始设置ax=0,然后循环依次比较每个字节的值,找到一个大于8的数就将ax+1
初始设置ax=0,然后循环依次比较每个字节,找到一个小于8的数就将ax+1
DF标志和串传送指令
在串处理指令中华,控制每次、操作后si,di增减
DF=0每次操作后si,di递增
DF=1,每次操作后si,di递减
对DF进行设置指令
cld指令 将标志旗寄存器DF为设为0
std指令,将标志寄存器DF设为1
rep指令
rep指令常和串传送指令搭配使用
根据cx值,重复执行后面的指令
assume csd:code,ds:data
data segment
db ''
db ''
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0
mov es,ax
mov di,16
cld
mov cx,8
rep movsw
mov ax,4c00h
int 21h
code ends
end start
例子,用串传送指令,将F000H中最后16个字符复制到data中
assume cs:code,ds:data
data segment
db 16 dup (0)
data ends
code segment
start:
mov ax,0f000h
mov ds,ax
mov si,0ffffh
mov ax,data
mov es,ax
mov di,15
mov cx,16
std
rep movsb
mov ax,4c00h
int 21h
code ends
end start
中断和外部设备操作
移位指令 SHL OPR CNT
逻辑右移 SHR OPR CNT
算数左移 SAL OPR CNT
带进位循环左移 RCL OPR CNT
循环左移ROL OPR CNT
循环右移 ROR OPR CNT
算数右移 SAR OPR CNT
带进位循环右移 RCR OPR CNT
逻辑移位指令shl shr
SHL OPR CNT
将寄存器或内存单元中数据向左一维
将最后移出一位写入CF
移动位数大于1时必须用cl
mov al,010100001b
mov cl,3
shl al,cl
操作显存数据
显示信息一种直接方式
assume cs:codeseg,ds:datasg
datasg segment
db 'welcome'
datasg ends
codeseg segment
start:
mov ax,datasg
mov ds,ax
mov ax,0B800H
mov es,ax
mov si,0
mov di,160*12+80-16
mov cx,16
w:mov al,[si]
mov es:[di],al
inc di
mov al,71H
mov es:[di],al
inc si
inc di
loop w
mov ax,4c00h
int 21h
codeseg ends
end start
描述内存单元的标号
代码段中标号可以来标记指令,段的起始地址
代码段中也可以用标号
在代码段中使用标号a,b后面没有“:”,它们描述内存地址和单元标号
标号a,地址code:0
以后内存单元都是字节
标号b
地址 code:8
以后内存单元都是字
数据标号
数据标号标记了存储数据的单元的地址和长度
数据标号不同于仅仅表示地址的地址标号
数据的直接定址表
以十六进制形式在屏幕中间显示给定的byte数据
先将一个byte高4位和低4位分开,显示对应的数码字符
如果数值显示是0,则显示“0”
如果数值是1,则显示1
数值是11 显示B
建立一张表,表中依次存储字符 0F,通过数值0 ~15直接查找对应的字符
assume cd:code
code segment
start:
mov al,2Bh
call showbyte
mov ax,4c00h
int 21h
showbyte:
jmp short show
table db '0123456AVCDEF'
show:
push bx
push es
push cx
mov ah,al
mov cl,4
shr ah,cl
and al,00001111b
mov bl,ah
mov bh,0
mov ah,table[bx]
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah
mov bl,al
mov bh,0
mov al,table[bx]
mov es:[160*12+40*2+2],al
pop cx
pop cs
pop bx
ret
code ends
end start
利用表在两个数据几个之间简历哦一种映射关系,用查表方法根据得到在另一个集合中对应的数据