文章目录
Central Processing Unit 中央处理单元 简称CPU
汇编语言的组成
-
汇编指令:机器码助记符 有对应机器码
-
伪指令:没有对应机器码 由编译器执行 计算机不执行
-
其他符号:如+ - * / 等 又编译器识别 计算机不执行
-
存储器 = 128存储单元 = 128Byte
-
Byte = 8bit =一个内存单元
-
1bit = 1个二进制位 一个bit只能表示一个开关量,例如 l代表“开关闭合”,0代表“开关断开”。
Cpu对存储器的读写
- 存储单元信息(地址信息)
- 器件选择 读或写的命令(控制信息)
- 读或写的数据(数据信息)
CPU与其他存储芯片的导线-总线
-
地址总线 发出地址信息
-
控制总线 发出内存读命令 选中存储芯片 通知他从中读取数据
-
数据总线 将读取到的数据送入CPU
-
地址总线 一根线表示一位 N根表示地址总线宽度为N,寻址范围为0-2^N。
-
数据总线 总线宽度决定了传输的速度,8根表示一个1Byte,16根表示2Byte.
-
控制总线 总线宽度决定了CUP对外部器件的控制能力
存储器
- 随机存储器
- 只读存储器
- BOIS(Basic Input?output System,基本输入/输出系统)
- 接口卡上的RAM
内存地址空间 书中11页 作者对其作出了很好的解释
CUP通过总线于各个物理存储器相连,将他们看作一个个存储器单元,他们分别占据一个地址段,CUP在这段地址空间读写数据 实际上就是在相应的物理存储器上读写数据。而汇编语言就是面对的内存地址空间。
寄存器
CPU内部
- 运算器:进行信息计算
- 寄存器:进行信息存储
- 控制器:控制各器件进行工作
- 内部总线:连接各个器件 在他们工作时进行数据传递
通用寄存器 兼容8位寄存器
- AX : 分AH和AL 书中16页介绍了 AX 20000(4E20) AH 78(4E) AL 32(20)
- BX : 分BH和BL
- CX : 分CH和CL
- DX : 分DH和DL
- ADD AX ,BX AX为16位 最多存4位16进制 最高位去掉 即0000H
- ADD AL ,BL AL为8位 最多存2位16进制 最高位去掉 00H
注意:
- MOV AX,BL
- 8位寄存器与16位寄存器之间传递数据
- MOV BH,AX
- 16位寄存器与8位寄存器之间传递数据
- MOV AL,2000
- 8位最多存储255的数值
- MOV AL,100H
- 不要将一个高于8位的数加到8位寄存器
物理地址
内存单元位线性空间 拥有唯一地址 即为物理地址
16位寄存器
- 运算器一次处理16位数据
- 寄存器最大宽度16位
- 寄存器和运算器通路位16位
8086CPU的物理地址
- 提供两个16位地址,段地址 偏移地址
- 通过内部总线送入地址假发器
- 地址加法器合成为一个20位物理地址
- 地址加法器送入BIOS
- BOIS将20位物理地址送入地址总线
- 地址总线送到储存器
物理地址 = 段地址 * 16 + 偏移地址
下图对物理地址左移做了很详细的总结
那么 16位地址 * 16 其实是乘10H,也可以说是16位地址转成10进制成16。最简单的方式就是16位地址左移1位
本质:基础地址+偏移地址 = 物理地址
基础地址 = 段地址 * 16
书中22对其本质做出了生动的解释
段的概念 书中24页
- 段地址 大小一定为16的倍数
- 偏移地址最大为16位 则最大寻址能力为64kb
段寄存器 书中25页
- 8086CPU提供四个段寄存器CS DS SS ES
- CS 代码段寄存器
- IP 指令指针寄存器
书中28页将CS配合IP寻址 查询 的具体图解
当进入指令缓冲器 即为读取一条指令 IP 值自动增加并读取下一条指令根据第一条指令的大小增加值
复位或者加电启动 CS=FFFFH IP=0000H FFFF0H即为8086PC启动执行的第一条指令
转移指令 修改CS IP 的值
- jmp段地址:偏移地址
- jmp 3:0B16 CS= 0003H IP= 0B16H 即从00B46H读取指令
- jmp 2AE3:3 CS= 2AE3 IP= 0003H 即从2AE33H读取指令
- jmp bx 等同于 mov IP,ax CS不变 BX赋值给IP
书中34对jmp做了图文并茂的解释
jmp 1000:3 就是跳转到1000*16+0003H = 10003H处读取指令。
实验一:CPU 的debug使用
先找个引路篇-安转篇https://blog.youkuaiyun.com/try2find/article/details/39619999
以及debug的命令行使用 https://blog.youkuaiyun.com/liuer2004_82/article/details/52638516#t2
第三章
内存中字的存储
- 字单元:存放一个字型数据(16位)的内存单元,两个连续的内存单元
- 高地址存字型数据的高位字节 低地址存字型数据的地位字节
- 地址单元和地址子单元:每个地址单元包含一个高位字节单元和一个低位字节单元
DS和[address]书中48页
- mov al,[0] 表示: mov 寄存器名,内存单元地址
- […] 表示内存单元 […]中的0表示内存单元的偏移地址。
- mov ds,bx ds会被默认取值 作为段地址 所以在调用mov al,[0]前必须给值ds.
- mov ds,1000H 为非法操作 数据不能直接送入段寄存器 即ds 需要先mov bx,1000H
字的传送
书中51页对字的传送做了图文并茂的介绍 表3.2
mov add sub
- mov 寄存器,数据 mov ax,8
- mov 寄存器,寄存器 mov ax,bx
- mov 寄存器,内存单元 mov ax,[0]
- mov 内存单元,寄存器 mov [],ax
- mov 段寄存器,寄存器 mov ds,ax
书中52验证了mov [0] cs
我学会debug指令 r,t,d查看 a修改
数据段
数据段:将一组长度为N(N<=64KB),地址连续,起始地址为16的倍数的内存单元当做专门存储数据的内存空间,即为数据段。
各寄存器初始值:CS = 2000H,IP = 0,DS = 1000H,AX = 0,BX = 0;
栈 后进先出
CPU 栈机制
- 如何确定栈空间?
- 如何确定栈顶?
- 段寄存器SS 存放栈顶栈地址
- 寄存器SP 存放偏移地址
- 任意时刻 SS:SP指向栈顶 SS 为段寄存器 SP为 偏移地址
栈超界
栈满push 栈空pop都会造成栈超界问题
8086CPU并没有给出解决方法 需自己注意
push 和 pop 指令 书中64页
//基本操作
mov ax,1000H
mov ss,ax ;设置栈的段地址 值必须由ax传递
push [0] ;将1000:0处的字压入栈中
pop [2] ;出栈 出栈的数据送入1000:2中
书中65页问题3.8将栈操作写的很完善
push时:CPU操作:先改变SP:SP-2 后向SS:SP处传送
pop时: CPU操作:先读取SS:SP处的数据,后改变SP :SP+2
栈段
- DS 内存区间 段地址
- CS 代码段 段地址
- SS 栈 段地址
-r ds
:1000
-d ds:0 ;查看1000:0开始的内存区间 128字节
-r ds
:1000
-d ds:10 18 ;查看1000:0~1000:18 中的内容
-d cs:0 ;查看当前代码段中的指令代码
-d ss:0 ;查看当前栈中的内容
E 直接修改内存
U 显示代码 以汇编的形式
A 汇编指令修改内存
-r ds
:1000
-e ds:0 11 12 33 11 44 ;在从1000:0开始的内存区间写入数据
-u cs:0 ;以汇编指令的形式,显示代码段中的代码,0是段地址
-r ds
:1000
-a ds:0 ;以汇编指令的形式,向1000:0开始的内存单元写入指令
代码练习 书中78页
assume cs:codesg;‘假设’编译程序将段寄存器和某个具体的段相关联
codesg segment ;与codesg ends 成对使用 定义一个代码段,名称为‘codesg’
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00H
int 21H
codesg ends
end ;汇编程序结束的标志
段结束 程序结束 程序返回
目的 | 相关指令 | 指令性质 | 指令执行者 |
---|---|---|---|
通知编译器一个段结束 | 段名 ends | 伪指令 | 编译时 编译器 |
通知编译器程序结束 | end | 伪指令 | 编译时 编译器 |
程序返回 | mov ax,4c00H int 21H | 汇编指令 | 编译时 由CPU行 |
编译asm文件过程 书中 第84页
首先需要一套工具 这边提供下载链接 https://blog.youkuaiyun.com/wybliw/article/details/53259602
设置完环境后打开dosbox 输入Masm 输入文件路径 一路enter会得到 .OBJ后缀文件 而实际上会得到三个文件
- 目标文件 obj
- 列表文件 lst
- 交叉引用文件 crf
连接 obj -> exe
输入Link 然后输入文件名 一路enter 生成 exe
简化编译连接 注意加上 ‘;’。
- masm newone.asm;
- link newone.obj;
cmd命令行是怎么执行程序的 书中90页
关于DOSBOX的快捷键操作
https://blog.youkuaiyun.com/lovefengruoqing/article/details/79009456
DEBUG追踪exe执行过程
- debug newone.exe 可跟踪执行
- CX 指定机器码字节数
- PSP 为DOS创建程序前缀 与被加载程序通信 256字节
- 可用段地址为 PSP 段地址+偏移地址为 SA+10H:0
- U命令查看汇编命令
- int 21H 使用 P命令结束
第五章 [BX]和loop命令
[bx]和内存单元
- [bx]同样表示一个内存单元 偏移地址为bx
- loop 循环指令
- ‘()’表示一个寄存器或者一个内存单元中的内容
- ‘()’可用元素的种类型 1 寄存器 2 段寄存器 3 内存单元物理地址(20位)
- ‘()’表示两种数据类型 1 字型 2字节型 (al) = (20000H)取字节型 (ax)=(20000H) 20000H取字型
-idata 表示常量
Loop循环
mov cx ,循环次数
s:
循环执行程序块
loop s
通过编译连接的asm文件 对[]偏移地址的解释错误
使用[bx]间接传递偏移地址
mov ax , 2000H
mov ds , ax ;段地址2000H送入ds
mov bx , 0 ;偏移地址0送入bx
mov al , [bx] ;ds:bx的送入al
或者
mov al , ds:[0]
段前缀
- ds 默认段地址
- cs 代码段地址
- ss 栈段地址
- es 显存段地址
书中120页给出了loop循环很好的实例
第六章 包含多个段的程序
在代码中使用数据
assume cs:code ;与系统cs相关联 固定写法
code segment ;代码段 名称 'code'
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;声明字型数据
mov bx,0 ;赋值语句
mov ax,0 ;赋值语句
mov cx,8 ;循环8次
s:add ax,cs:[bx] ;(ax) =((cs*16)+bx)
add bx,2 ;偏移地址+2
loop s
mov ax,4c00h ;类似于return 固定写法
int 21h
code ends
end ; 代码段结束标记 固定写法
标记程序开始部分 类似于smali语言中的 .prologue 书中126页
assume cs:code ;与系统cs相关联 固定写法
code segment ;代码段 名称 'code'
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;声明字型数据
start: mov bx,0 ;赋值语句
mov ax,0 ;赋值语句
mov cx,8 ;循环8次
s:add ax,cs:[bx] ;(ax) =((cs*16)+bx)
add bx,2 ;偏移地址+2
loop s
mov ax,4c00h ;类似于return 固定写法
int 21h
code ends
end start ; 代码段结束标记 固定写法
自定义段 书中130页
- 一个段的数据段可以由段名表示,如:data:6表示’0cbah’
- 这也是JAVA思想的雏形啊
- CPU 将 cs指向code ds指向data ss指向stack(可以这样理解)
assume cs:code,ds:data,ss:stack ;关联系统段地址 :固定写法
data segment ;自定义段 data
dw 0123h,0456h,0789h,0abch,0defh,0cbah,0987h ;16字节内存单元
data ends ;段结束
stack segment ; 自定义栈 stack
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;16字节内存单元
stack ends ;段结束
code segment ;自定义代码段 code
start:mov ax,stack ;start 开始标记 stack段地址给寄存器ax
mov ss,ax ;ax赋给栈段地址
mov sp,20h ;设置栈顶ss:sp 指向stack:20
mov ax,data ;data段地址给寄存器ax
mov ds,ax ;ds 指向 data
mov cx,8 ;设置循环次数8
s:push [bx] ;将ds:[bx]的值入栈,也就是data:0~15的16个字型数据
add bx,2 ;bx 自加 2
loop ;循环标志 固定写法
mov bx,0 ;将bx置空
mov cx,8 ;循环标志 固定写法
s0:pop [bx] ;将栈stack依次出栈 赋值给 ds:[bx]
add bx,2 ;bx 自加 2
loop s0 ;循环标志 固定写法
mov ax,4c00h ;结束标志 固定写法
int 21h
code ends ;代码段结束标记
end start ;程序结束标记
第七章
or and 指令
and: 与指令 同为1 则为1
or : 只要有1 则为1
mov al 1100100B
add al 1000000B
//执行和结果为 10000000B
mov al 1000111B
or al 1110111B
//执行结果为 1110111B
书中139页介绍了ASCII从键盘到显示器过程
ASCII码表 http://ascii.911cha.com/
字符串数据
这边先介绍一下 db dw dd
- db定义字节类型变量,一个字节数据占1个字节单元,读完一个,偏移量加1
- dw定义字类型变量,一个字数据占2个字节单元,读完一个,偏移量加2
- dd定义双字类型变量,一个双字数据占4个字节单元,读完一个,偏移量加4
转换大小写案例
这里有意思的是
- 大写转小写 即 and 11011111B 也就是-20H 也就是第五位置0 大写转大写无变化
- 小写转大写 即 and 00100000B 也就是+20H 也就是第五位置1 小写转小写无变化
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db 'iNfOrMaTiOn'
datasg ends
codesg segment
start:mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s:mov al,[bx] ;取出ds:bx所指的第一个字母
and al,11011111B ;将第五位置0 就是-20H
mov [bx],al ;将值返还给ds:[bx]
inc bx
loop s
mov bx,5 ;从ds:5开始指向information的第一个字母
mov cx,11
s0:mov al,[bx]
or al,00100000B ;将第五位置1 就是+20H
mov [bx],al
inc bx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
[bx+idata]进行数组处理 简化上面的程序
mov al,[5+bx]
mov [5+bx],al
- C语言:a[i],b[i]
- 汇编语言:0[bx],5[bx] 那这里的 0 5 代表的就是 数组的首地址了 而c语言用数组名表示
SI DI 与bx功能相似 不能分成两个8位寄存器 书中147页
mov ax ,[bx][si]
//等同于
mov ax,[bx=si]
//描述为
(ax) = (ds*16 + bx + si)
寻址方式的灵活运用 书中152页
- [idata]
- [bx]
- [bx+idata]
- [bx+si]
- [bx+si+idata]
为了培养我们汇编中数组的概念 书中153页
定义如下数据段 6个字符串 等同于下图 的一个
datasg segment;注意每个字符被扩展为16位
db '0. file '
db '1. edit '
db '2. search '
db '3. veiw '
db '4. options '
db '5. helps '
datasg ends
循环嵌套
最终:是将datasg数组中的数据全部大写
datasg segment;注意每个字符被扩展为16位
db 'fil '
db 'edi '
db 'sea '
db 'vei '
datasg ends
mov ax,datasg
mov ds,ax
mov bx,0 ;ds:bx 指向datasg
mov cx,4 ;循环次数4行
s0:mov si,0 ;外层循环 第一行 当si=3退出循环
mov cx,3 ;循环次数3列
s: mov al,[bx+si] ;内层循环 ds:[bx][si] 理解为 第bx行 第si列
and al,11011111b ;第五位置0 也就是-20H 也就是 小写 -> 大写
mov [bx+si],al ;
inc si si加1
loop s
add bx,16 ;外层循环 +16 即+10H 即换行
loop s0
改进之-栈存储数据(节省寄存器的使用数量)
用栈的方式暂时保存cx的值
datasg segment;注意每个字符被扩展为16位
db 'fil '
db 'edi '
db 'sea '
db 'vei '
datasg ends
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg segment
codesg segment
start:mov ax ,stacksg
mov ss,ax
mov sp,16 ;ss:sp 指向 stacksg:10H
mov ax,datasg
mov ds,ax
mov bx,0 ;ds:bx 指向 datasg:0
mov cx,4
s0: push cx ;入栈 cx
mov si,0
mov cx,3
s: mov al,[bx+si] ;(al) = (ds*16+bx+si) 也就是[bx][si] bx行si列
and al,11011111b ;改成大写
mov [bx+si],al ;(ds*16+bx+si) = (al)
inc si ;si自加1
loop s
add bx,16 ;换行
pop cx ;出栈 给cx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
第八章 数据处理
-
处理数据在什么地方?
-
要处理的数据有多长?
-
reg:寄存器 ax bx cx dx al ah bl bh cl ch dl dh cl ch sp bp si di
-
sreg:段寄存器 ds cs ss es
使用规则 mov ax […]
- bx si bp di 可用于内存单元寻址
- bx si bp di 只能单独出现,且 bx与bp,si与di不能组合使用
- []中使用bp 默认使用段寄存器 ss 即 ss*16+bp
机器指令处理的数据在什么地方
机器码 | 汇编指令 | 指令执行前数据的位置 |
---|---|---|
8E1E0000 | mov bx,[0] | 内存,ds:0单元 |
89c3 | mov bx,ax | CPU内部,ax寄存器 |
BB0100 | mov bx,1 | CPU内部,指令缓冲器 |
数据位置的表达
- 立即数(idata)
- 寄存器
- 段地址(SA)和偏移地址(EA)
- 存放段地址的寄存器 如:mov ax ds:[bx]
寻址方式
指令处理的数据长度
- ax 字单元
- al 字节单元
- mov byte ptr ds:[0],1 字节单元
- mov word ptr ds:[0],1 字单元
- [bx].idata [bx].idata[si] bx定位结构体 idata定位数据项 si定位数组元素 (书中169页)
div指令 除法指令
- 除数:8位和16位 在一个reg或内存单元中
- 除号前面位被除数,后面为除数
- 被除数:默认放在AX或者DX和AX中 除数8位则被除数16位 除数16位则被除数32位,由DX(高16位)和AX存放(底16位)
- 结果:除数8位 AL存商 AH存余数 除数16位 AX存商 DX存余数
实例
div word ptr es:[0]
(ax) = [(dx)*10000H+(ax)]/((es)*16+0)的商 //注意这里是10000H
(dx) = [(dx)*10000H+(ax)]/((es)*16+0)的余数
div byte ptr ds:[0]
(al) = (ax)/((ds)*16+0)的商
(ah) = (ax)/((ds)*16+0)的余数
实例计算 100001/100
mov dx,1
mov ax,86a1h
mov bx,100
div bx
根据公式一:
100 = 64H
- (ax) = [1*10000H+ax]/100 = 186a1H/64H = 03e8h
- (dx) = [1*10000H+ax]%100 = 186a1H%64H = 1
伪指令dd ((dword)double word 双字型 即四个字节 32位)
- db 00H 2个8位
- dw 0000H 2个byte
- dd 00000000H 2个word
dup(两份) 配合dd 进行数据重复 书中172页
- db 3 dup (0) == db 0,0,0
试验七 书中173页 请认真完成!!
答案地址 https://blog.youkuaiyun.com/love_jing_forever/article/details/53204818
第九章 转移指令原理
定义:可以修改IP,或同事修改CS和IP的指令统称为转移指令。
转移行为:
- 段内转移 只修改IP jmp ax
- 段间转移 同事修改 CS IP jmp 1000:0
对IP修改范围不同
- 短转移 对IP修改范围 -128~127
- 近转移 对IP修改范围 -32768~32767
指令分类
- 无条件转移指令 jmp
- 条件转移指令
- 循环指令 loop
- 过程
- 中断
操作符 offset 获取标号的偏移地址
start:mov ax ,offset start ;相当于mov ax ,0
s:mov ax ,offset s ;相当于 mov ax s
jmp指令并不需要转移的目的地址 只需要转移的位移
assume cs:codesg
codesg segment
start:mov ax,0
jmp short s ;short 表示范围在 -128~127之间 即‘段内短转移’
add ax,1
s:inc ax
codesg ends
end start
我们先复习一下补码 书中329页
-
8位无符号数 00000000B~11111111F 共255个数
-
8位有符号数
-
10000000B~11111111F -127~127 共254个数 注意:10000000B不算-0没有意义
-
最高位为1 表示负数
-
整数补码取反加1或,为其对应负数的补码,负数补码取反后加1,位其绝对值
-
1的补码:11111111B,表示-1
-
-1的补码:00000001B,其绝对值为1.
-
补码计算法定义:非负数的补码是其原码本身;负数的补码是其绝对值的原码最高位符号位不变,其它位取反,再加1。
-
模定义:一个负整数与其补码的和。
求0-9的补码
0000H-0009H
因为指定jmp short 转成二进制:
00000000B-00001001B
应为没有减法器 所以理解为加法
00000000B+(-00001001B)
也就是求-9(10001001B)的补码
10001001B 取反且首位不变(11110111B)
即F7H
- jmp near ptr 标号(IP) = (IP)+16位位移
- jmp far ptr 标号 段间转移 远转移 (CS)=标号所在段的段地址;(IP)=标号在段中的偏移地址
转移地址在内存中的jmp指令
- jmp word ptr ds:[0] 将内存单元中的存放的赋给 偏移地址 (IP) = (ds:[0])
- jmp word ptr [bx]将内存单元中的存放的赋给 偏移地址 (IP) = ([bx])
- jump dword ptr 内存单元地址(段地址) (CS) = (内存单元地址+2) (IP) = (内存单元地址)
mov ax ,0123H
mov ds:[0],ax
mov word ptr ds:[2],0
jmp dword ptr ds:[0]
//(CS)=0,(IP)=0123H,CS:IP指向0000:0123
jcxz指令 条件转移指令(短指令)
JCXZ CX: 为零时转移
格式:jcxz 标号
用高级语言描述:if (cx == 0)
jmp short 标号 ;------> 当cx= 0 转移到标号处
else
next ;------>当cx != 0 程序什么也不做,继续向下执行
(短转移,通过位移进行转移,IP修改范围-128~127
loop指令前面讲过了
书中189页 试验8.9部分以后再看
第十章 Call和Ret指令
- call和ret都是转移指令 都可修改IP 或同时修改CS和IP
- ret 修改IP 实现近转移 相当于 pop IP
- retf 修改SS和IP 实现远转移 相当于 POP IP POP CS
实例:
assume cs:codesg
stack segment
db 16 dup (0) ;申请一个16字节的空间
stack ends
code segment
mov ax,4c00h
int 12h
start: mov ax,stack ;代码开始部分
mov ss,ax
mov sp,16
mov ax,0
push cs
push ax
mov bx,0
retf cs:IP指向 cs:ax即 cs:0000H;代码第一行mov ax,4c00h
code ends
end start
Call指令
- 将当前IP或者CS和IP压入栈中
- 转移
- 不能实现短转移 范围2个有符号字节
call 标号
汇编表示: 入栈IP后
(SP) = (SP)-2
((ss)*16+(sp))=(IP) 将IP对应的值存入ss:sp中 sp偏移向上2个字节
(IP)=(IP)+16
push IP
jmp near ptr 标号 ;对应(IP)=(IP)+16 就是跳过一行
```c
cal far ptr 标号 段间转移
汇编表示:入栈CS 和 IP后
push CS
push IP
jmp far ptr 标号 cs:ip指向 标号处
call 16位reg
汇编表示:
```c
push IP
jmp 16位 reg ; 直接跳转到ss:reg位置
call word ptr
汇编表示: 跳转到 标号处 SP+2
push IP
jmp word ptr 内存单元地址
call dword ptr
汇编表示: 跳转到 标号处 SP+4
push CS
push IP
jmp word ptr 内存单元地址
call 和 ret 配合使用
- 子程序:具有一定功能的程序。
- call:调用子程序 并将IP压入栈中
ret:返回子程序 POP IP弹栈后 调用cs:ip处的代码 即 call的下一行。
mul指令 乘法指令
- 想成两数 必须同时为16位或8位
- 8位时 一个默认放在AL中,另外一个放在8位reg或者内存单元中
- 16位 一个默认放在AX中,另一个放在16位reg或者内存单元中
对于结果:
- 8位乘法 结果默认放在AX中
- 16位乘法 结果高位放在DX,低位放在AX中
mul byte ptr ds:[0]
;含义: (ax) = (al)*((ds)*16+0); (ax) = (al)*(ds:bx)
mul word ptr [bx+si+8]
;(ax) = (ax)*((ds)*16+(bx)+si+8)结果的底16位
;(dx) = (ax)*((ds)*16+(bx)+si+8)结果的高16位
计算100*10
分析:100和10 都小于255 可以做8位乘法
mov al ,100
mov bl ,10
mul bl
结果:(ax) = al*bl = 1000 = 0e38H
计算10000*100
100小于255,10000大于255,所以做16位乘法
mov ax,100
mov bx,10000
mul bx
结果: ax*bx = F4240 = 11110100001001000000 =
1111 0100 0010 0100 0000
(ax) = 4240H dx = 0000 0000 0000 1111 = 0000FH
参数和结果的传递
子程序:也就java中的Util方法 或者普通方法
将
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8 ;这边申请的是word 也就是一个字16位
dd 0,0,0,0,0,0,0,0 ;这边申请的是dword 也就是一个双字32位
data ends
code segment
start:mov ax,data
mov ds,ax
mov si,0 ;ds:si 指向第一组word单元
mov di,16 ;ds:di 指向第二组的dword单元
;si di等同于bx 不能分成两个8位
mov cx 8 ;循环8次
s:mov bx,[si] ;(bx) = ([ds:si])=([ds:0])=1
call cube ;假设bx = 8
mov [di],ax ;([di]) = 0040H 低16位
mov [di].2,ax ;向后移动2个字节 高16位
add si,2 ;ds:si 指向下一个word单元
add di,4 ;ds:di 指向下一个dword单元
loop s
mov ax,4c00h
int 21h
cube:mov ax,bx ;该方法被调用 因为bx小于255(FF) 用8位
mul bx ;(ax) = (al)*(bx) = 0*H*0008H =8 * 8 = 64 =0040H
mul bx ;(ax) = (al)*(bx) = 04H*0040H =4 * 64 = 64 =0100H
ret ;返回调用处的下一行,即:mov [di],ax
code ends
end start
批量数据传递
作者告诉我们为什么定义字符串使用 db https://blog.youkuaiyun.com/weixin_42553435/article/details/80919722
assume cs:code
data segment
db 'conversation'
;存的byte字节 是顺序的
;可以理解成 db 'c','o','n','v','e','r','s','a','t','i','o','n' 总共12个字符
data ends
code segment
start:mov ax,data
mov ds,ax
mov si,0
mov cx,12
call capital
mov ax,4c00h
int 21h
capital:and byte ptr [si],11011111B
inc si
loop capital
ret
code ends
end start
编写子程序的标准
子程序开始: 子程序中使用的寄存器入栈
子程序内容
子程序中使用的寄存器出栈
返回(ret,retf)
试验10-编写子程序 书中206页
第十一章 标志寄存器
- 存储相关指令的执行结果
- 位CPU执行指令提供行为依据
- 控制CUP的工作方式
ZF标志 零标志位 zero flag
- 记录指令执行的结果是否为0
- 1则zf = 0 , 0则 zf = 1
- 影响zf指令:add,sub,mul,div,inc,or,and
- 不影响zf指令:mov push pop
mov ax,1
sub ax,1
//ax=0,zf =1
PF标志 奇偶标志 parity flag
- 记录指令执行的结果 的bit位中1的个数是否为偶数,偶数则pf=1 奇数则pf=0
mov ax,1
add al,10
//ax=00001011B,zf =0
SF标志 符号标志位 symbol flag
- 记录指令执行的结果 是否为负,为负数则sf=1 非负则sf=0
- 当做无符号数计算 SF无意义
- 只记录实际结果的正负 不记录逻辑结果的正负 (考虑溢出)
mov al,10000001B
add al,1
//结果:(al) = 10000010B
//SF=1
mov al,10000001B
add al,01111111B
//结果:(al) = 100000000B //溢出则 去掉最高位1 取00000000B = 00H
//SF=0 结果非负数
CF标志 进位标志 carry flag
- 无符号位的计算中
- 记录运算结果的: 1.最高有效位 2.更高位进位值 3.从更高位的借位值
- 比如一个8位数 第7位位最高有效位 第8位即相对于最高有效位的更高位
- 8位溢出后 由CF记录这个值
- 97H-98H 将产生借位
mov al,98H
add al,al ;98H*2 = 0130H执行后:(al) = 30H, CF = 1,,CF记录最高有效位的更高位的进位值
add al,al ;30H*2 = 60H ,CF = 0
mov al,97H
sub al,98H ;借位后即: (al) = 197H-98H = FFH ,CF=1,记录向更高位的借位值
sub al,al ;(al) = FFH-FFH = 0H ,CF = 0
OF标志 溢出标志位 overflow Flag
- OF只对有符号数有意义 计算出理想值进行比较 如8位范围-128~127
- 8位有效范围:-128~127
- 溢出OF =1
- CF只对无符号数有意义
- CPU 在执行指令时 会对数据分别做有符号和无符号的计算 并对CF OF分别赋值
mov al,98
add al,97
;结果为::98+97 = 195
;实际结果位 195 = C3H=11000011B
//11000011B 为有符号数
//10111100B+1
//10111101B =-61
- 对于无符号数:没有产生进位 CF = 0
- 对于有符号数:产生溢出 OF = 1
mov al,0F0H
; FOH 为有符号数 -16
//11110000
//10001111+1
//10010000
add al,088H
; 088H 为有符号数 -120
//10001000
//11110111+1
//11111000
;0F0H+ 088H 结果为 101111000
//101111000
//110000111+1
//110001000 -136 溢出
;但是实际结果为
//01111000 超界则去掉最高位
//78H
- 对于无符号数:没有产生进位 CF = 1
- 对于有符号数:产生溢出 OF = 1
adc指令 add +carry 带进位的加法指令
adc ax,bx 实现功能:(ax) = (ax)+(bx)+CF
mov ax,2
mov bx,1
sub bx,ax ;(bx)<(ax) 产生借位:CF=1
adc ax,1 ;(ax)+1+CF = 2+1+1=4
sbb指令 subtraction+borrow 带进位的减法指令
cmp指令 compare 比较指令
- 于减法指令,只是不保存结果
- p指令执行后,将对标志寄存器产生影响
mov ax,8
mov bx,3
cmp ax,bx
//执行后:(ax)-(bx) =5, zf = 0,pf = 1,sf = 0,cf = 0,of =0;
书中225页进行了一通cmp分析
- zf = 1 说明:ax = bx
- zf = 0 说明:ax != bx
- cf = 1 说明:ax < bx
- cf = 0 说明:ax >= bx
- cf = 0 并且 zf = 0 说明:ax > bx
- cf = 1 或 zf =1 说明:ax <= bx
- sf = 1 并且 of = 1 (ah) > (bh)
- sf = 0 并且 of = 1 (ah) < (bh)
- sf = 0 并且 0f = 0 (ah) >=(bh)
检测比较结果的条件转移指令
指令 | 含义 | 检测相关标志位 |
---|---|---|
je(equal) | 等于则转移 | zf=1 |
jne | 不等于 | zf!=0 |
jb(below) | 低于 | cf=1 |
jnb | 不低于 | cf=0 |
ja(above) | 高于 | cf = 0 并且 zf = 0 |
jna | 不高于 | cf = 1 后者 zf = 1 |
cmp ah,bh
je s ;如果(ah)= (bh) 其实只要ZF = 1 就执行 :跳转到 s 否则向下执行
add ah,bh
jmp short ok
s:add ah,ah
ok:...
DF标志 和 串传送指令
- DF标志:direction flag 方向标志
- 串处理指令:控制每次操作后 si di 的增减
- df = 0 每次操作后 si di 递增
- df = 1 每次操作后 si di 递减
- movsb(byte) 描述:mov es:[di],byte ptr ds:[si] df=0: di si 加1 ,df=0: di si 减1
- movsw(word) 描述:mov es:[di],byte ptr ds:[si] df=0: di si 加2 ,df=0: di si 减2
- cld指令 将df置1
- std指令 将df置0
- rep movsb 配合movsb 和 movsw 使用 实现循环 汇编描述:
s:movsb
loop s
实现内存的传送
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
code segment
mov ax ,data
mov ds,ax
mov si,0 ;ds:si 指向 data:0
mov es,ax
mov di:16 ;es:di 指向 data:0010H
mov cx,16
cld ;df=0 正向传送
rep movsb
code ends
pushf 和 popf 针对标志寄存器压弹栈 书中234页
检测点11.4 记得这是存的flag 是一个16位的数
下面指令执行后,(ax)=0045h
mov ax,0 ;(ax)=0
push ax ;将(ax)入栈
popf ;将flag寄存器的所有为都初始化为0.因为它们使用的是同一个栈结构。
mov ax,0fff0h ;(ax)=0fff0H
add ax,0010h ;执行add后,结果是:(ax)=0000H(作为无符号数,产生进位cf=1),
;作为有符号数没有溢出,of=0;其它zf=1,sf=0,pf=1.我们把这些标 ; 位按照顺序组合起来(不能确定的我们不管了):00000xxx010x0101 ;我们不能确定的都用X表示。没有使用的用0表示吧
pushf ;将flag的值入栈。
pop ax ;弹栈,(ax)= 00000xxx010x0101B
and al,11000101B ;ax低8位按位与, 010x0101 and 11000101=01000101B=45H
and ah,00001000B ;ax高8位按位与,00000xxx and 00001000=0000000B=00H
Debug中的flag 标记
试验11 书中234页
第十二章 内中断
- CPU不再接着(刚执行完的的指令)向下执行,而是转去处理这种中断信息
- 中断信息由CPU的内部和外部产生。
内中断的产生
- 除法错误 执行div产生除法溢出
- 单步执行
- 执行into指令
- 执行int指令
中断类型
- 中断类型码:一个字节型数据 256中种中断信息
- 中断信息来源:中断源
- 除法错误:0
- 单步执行:1
- 执行into指令:4
- 执行int指令:格式为 int n ,n为字节型立即数
中断向量表:中断处理程序入口地址列表
- 中断向量表:通过中断类型码找到中断入口地址的的先决条件
- 8086CPU中 0000:0000~003F 共1024个单元 存放中断向量表
- 一个表存放一个地址 = 段地址+偏移地址 = 2个字大小
中断过程
- 中断过程:通过中断类型码找到中断入口地址 设置CS:IP CPU执行中断程序
- (中断信息中)取得中断类型
- 标志寄存器入栈
- 设置标志寄存器 第八位TF 和第九位IF 为0
- CS 内容入栈
- IP 内容入栈
- 从内存地址为中断类型码4 和中断类型码4+2 的两个字节单元中读取中断处理程序的入口地址设置IP和CS(*4是因为每个向量地址有4个字节)
iret指令和中断处理程序
CPU随时可能执行中断程序,所以中断程序必须一直存储在内存的某段中
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- 用iret指令返回
iret指令功能用汇编描述:
pop IP
pop CS
popf
除法溢出错误
mov ax,1000h
mov bh,1
div bh
计算:
(al) = (ax)/((ds)*16+0)的商 = 1000h/0001H = 1000H (溢出 al范围为00H~FFH)
(ah) = (ax)/((ds)*16+0)的余数 = 0H
do0 修改 除法溢出的错误提示
- 编写显示“overflow!”的中断程序:do0
- 将do0送入内存0000:0200处
- 将do0的入口地址0000:0200存储在中断向量表0号表项中
实现代码:
assume cs:code
code segment
start:do0 安装程序
设置中断向量表
mov ax,4c00h
int 21h
do0: 显示字符串“overflow!”
mov ax,4c00h
int 21h
code ends
end start
安装 do0
assume cs:code
code segment
start:mov ax,cs
mov ds,ax
mov si,offset do0 ;设置ds:si指向源地址
mov ax,0
mov es,ax
mov di,200h ;设置es:di指向目的地址
mov cx,offset do0end-offset do0 ;do0 部分代码的长度 ;设置cx为传输长度
cld ;设置传输方向
rep movb
设置中环向量表
mov ax,4c00h
int 21h
do0: 显示字符串“overflow!”
mov ax,4c00h
int 21h
code ends
end start
编写do0程序
这里存在一个问题 当code segment执行玩之后data
segment内存随时可能被释放。所以需将data实时生成。
do0代码不执行 只是装载到200H处
assume cs:code
data segment
db "overflow!"
data ends
code segment
start:mov ax,cs
mov ds,ax
mov si,offset do0 ;设置ds:si指向源地址
mov ax,0
mov es,ax
mov di,200h ;设置es:di指向目的地址
mov cx,offset do0end-offset do0 ;do0 部分代码的长度 ;设置cx为传输长度
cld ;设置传输方向
rep movsb
;设置中环向量表
mov ax,4c00h
int 21h
do0: mov ax,data
mov ds,ax
mov si,0 ;设置ds:si指向字符串
mov ax,0b800H
mov es,ax
mov di,12*160+36*2 ;设置es:di指向显存空间的中间位置
mov cx 9 ;设置cx字符串长度
s: mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end:nop
code ends
end start
改进do0
assume cs:code
code segment
start:mov ax,cs
mov ds,ax
mov si,offset do0 ;设置ds:si指向源地址
mov ax,0
mov es,ax
mov di,200h ;设置es:di指向目的地址
mov cx,offset do0end-offset do0 ;do0 部分代码的长度 ;设置cx为传输长度
cld ;设置传输方向
rep movsb
;设置中环向量表
mov ax,4c00h
int 21h
do0: jmp short do0start
db "overflow!"
do0start: mov ax,cs
mov ds,ax
mov si,0 ;设置ds:si指向字符串
mov ax,0b800H
mov es,ax
mov di,12*160+36*2 ;设置es:di指向显存空间的中间位置
mov cx, 9 ;设置cx字符串长度
s: mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end:nop
code ends
end start
设置中断向量
do0 入口地址0:200 0:0存偏移地址 0:200子单元存段地址.
单步中断 类似Debug的t命令单步调试
- TF为1 则(Step Flag)
- 去除中断类型码1 (提前置0 防止无限循环)
- 标志寄存器入栈,TF,IF 设置为0
- CS,IP入栈
- (IP)=(14),(cs)= (14+2)
响应中断的特殊情况
中断后 并不会设置 ss:SP 且当执行栈操作时 不会去响应中断,此时我们应该将SS和SP连续存放。
mov ax,1000h
mov ss,ax
mov sp,0
试验12 编写0号中断处理程序 书中251页
第十三章 int指令:内中断之一
格式:int n,n为中断类型码 引发中断
执行过程:
- 去除中断类型码1 (提前置0 防止无限循环)
- 标志寄存器入栈,TF,IF 设置为0
- CS,IP入栈
- (IP)=(14),(cs)= (14+2)
书中253页 编写一个中断程序
问题 编写安装中断7ch中断例程
求2*3456^2
第一步:
assume cs:code
code segment
start :mov ax,3456
int 7ch
add ax,ax
adc dx,dx
mov ax,4c00h
int 21h
code ends
end start
第二步:
assume cs:code
code segment
start :mov ax,cs
mov ds,ax
mov si,offset sqr
mov ax,0
mov di,200h
mov cx,offset sqrend-offset sqr ;安装子程序到 es:di 也就是 0:200H处
cld ;df=0 正向传送 di si 加1
rep movsb; 循环复制 (mov es:[di],byte ptr ds:[si] df=0)
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h ;es:[7ch*4]中断向量表 偏移地址
mov word ptr es:[7ch*4+2],0 ;es:[7ch*4+2]中断向量表 段地址
mov ax,4c00h
int 21h
sqr: mul ax
iret
sqrend:nop
code ends
end start
```c
其中iret为 恢复数 CS IP 和 flag
pop IP
pop CS
popf
###### int和iret和栈深入理解 书中256页已经变得很复杂了。可能是前面基础没打好。
**先休息一下 本文共17章 且 课后试验部分未做,可谓是任重而道远,未完待续**