视频地址是:https://www.bilibili.com/video/BV1pi4y1P76P
笔记:P1-P43
一、绪论
定位:理解硬件结构,掌握指令集,理解程序的执行过程
内容:8088、8086指令集与汇编语言程序设计
1.1 由机器语言到汇编语言
1.1.1 机器语言与汇编语言概念
- 机器语言是机器指令的集合。
- 机器指令是一台机器可以正确执行的命令。
- 机器指令由一串二进制数表示。例 01010000
- 电平脉冲:
- 汇编语言的主体是汇编指令。
- 汇编指令和机器指令的差别在于指令的表示方法上。
汇编指令是机器指令便于记忆的书写格式。
汇编指令是机器指令的助记符。
1.1.2 用汇编语言编写程序的工作过程
1.2 计算机组成
1.2.1 指令和数据的表示
- 计算机中的数据和指令,存储在内存或磁盘上。
- 数据和指令,都是二进制信息。
- 问题:二进制信息1000100111011000是数据,还是指令?
【既可以是数据也可以是指令,由CPU决定!】
- 问题:数据如何表示?
- 数据量:B、KB、MB、GB、TB…
1.2.2 计算机中的存储单元
存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号。
例如:一个存储器有128个存储单元,编号从0~127(如下面左图所示)
实际(如上面右图所示):
1.2.3 计算机中的总线
在计算机中专门有连接CPU和其他芯片的导线,通常称为
总线。
1.2.4 三类总线
- 地址总线
- CPU是通过地址总线来指定 存储单元【指编的这个地址】的。
- 想到内存的某个地方取数据或者要把数据写到内存的某个地方需要指定地址。
问题:那么如何做?——首先地址准备好,然后通过地址总线传输到内存里面就可以进行定位了
- 地址总线宽度,决定了可寻址的存储单元大小。
- N根地址总线(宽度为N),对应寻址空间2^N。
- 数据总线
- CPU与内存或其他器件之间的数据传送是通过数据总线来进行的。
- 数据总线的宽度决定了CPU和外界的数据传送速度
- 例:向内存中写入数据89D8H时的数据传送。
8位数据总线:意味着当我们传送一个16位数据时候,因为他总线是8位,则要先传送8位再传送8位。
- 控制总线
- CPU通过控制总线对外部器件进行控制。
- 控制总线是一些不同控制线的集合
- 控制总线宽度决定了CPU对外部器件的控制能力。
性能:
1.3 内存的读写与地址空间
1.3.1 CPU对存储器的读写
- 演示过程
首先:由读取的地址信息3,它通过地址总线,传输到内存当中去,找到3号单元。
其次:这时控制总线发出读的信号,然后把3号单元的08读到了AL里面去了。
在读取的过程里面,它到3号单元里面找数据了,那么这个3号单元如何找到数据的呢?——即引出下面的内存地址空间。
1.3.2 内存地址空间
- 什么是内存地址空间
- 从CPU角度看地址空间分配
- RAM:随机存储器(能读能写、断电数据消失。放的是整个计算机运行里面动态变化的数据)
- ROM:只读存储器 (只读不能写、读取的数据在造计算机的时候就已经写好了。放的是在计算机启动的时候就要去用到的信息或者说固定的信息)
- BIOS:基本的输入输出系统
- BIOS:基本的输入输出系统
- 将各类存储器看作一个逻辑存储器——统一编址
- 统一编排地址的优势:将来用到内存的时候就不用再考虑这段内存是这个设备的还是内个设备的,我们仅需拿一个统一的地址方式去表示就行了。即方便工作。
- 如何做?
- 所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器
- 每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间
- CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据。
1.3.3 汇编语言实践环境搭建
- 挂接工作目录
- 作用:指定将来我们在DOS中用到的C盘,它实际上对应的是D盘上的MASM这个文件夹
mount c d:\masm
- 查看
- c: 【指转到c盘】
dir 【指列出目录】
发现与D盘下MASM文件目录一致,表明挂接成功!
- c: 【指转到c盘】
1.4 书本习题
二、访问寄存器和内存
2.1 寄存器及数据存储
2.1.1 通用寄存器——以AX为例
- 例:在AX中存储18D
- 18D可以转成——12H、10010B
- 18D可以转成——12H、10010B
- 问题:8086是16位的 但他的上一代CPU中的寄存器都是8位的,如何保证程序的兼容性?【即:CPU在此工作模式下都是针对8位寄存器进行操作的,会造成在8086下编写的程序在原有的平台下不能进行了,这样的话如何保证程序的兼容性?】
- 方案:
- 通用寄存器均分为两个独立的8位寄存器使用
- 即:给出AX 16位寄存器之后,分为两个寄存器AH和AL 【其中H代表高地址、L代表低地址】
- 方案:
2.1.2 “字”在寄存器中的存储
2.2 mov和add指令
【注】汇编指令不区分大小写
2.3 确定物理地址的方法
-
CPU访问内存单元时要给出内存单元的地址。
-
所有的内存单元构成的存储空间是一个一维的线性空间。
-
每一个内存单元在这个空间中都有唯一的地址,这个唯一的地址称为物理地址。
-
事实:
- 8086有20位地址总线,可传送20位地址,寻址能力为1M。
- 8086是16位结构的CPU
- 运算器一次最多可以处理16位的数据,寄存器的最大宽度为16位。
- 在8086内部处理的、传输、暂存的地址也是16位,寻址能力也只有64KB!
-
问题:8086如何处理在寻址空间上的矛盾【1M与64KB差异很大】?
- 处理方法如下:
-
①8086给出的方法:用两个16位地址(段地址、偏移地址)合成一个20位的物理地址
-
②地址加法器合成物理地址的方法:物理地址=段地址(基地址)x16+偏移地址
其中 x16 的意思是:向左移4位(4个二进制位)
【注】:向左移1位——x2
【注】:向左移2位——x4
【注】:向左移3位——x8
-
- 处理方法如下:
-
下面是具体的演示图:
①初始状态:
②首先:有了段地址和偏移地址,然后把它俩放到地址加法器中然后做出运算【物理地址=段地址(基地址)x16+偏移地址】得到20位物理地址结果
③然后:物理地址通过地址总线传到内存。完成寻址!
-
【例】8086CPU访问地址为123C8 H的内存单元
-
【思考】上面的演示过程中段地址一定是1230吗?使用123C、123B等等,可以吗?
上图计算说明可以!即说明:合成物理地址时,段地址在编程序时可以灵活安排!并不是一个物理地址就能决定段地址和偏移地址的!而是决定段地址以后通过适当的调整偏移地址可以确定同一个物理地址!
2.4 内存的分段表示法
- CPU对整个内存的管理是采取分段的方式进行管理的!
- 8086用“物理地址=段地址x16+偏移地址”的方法给出内存单元的物理地址。这里的段地址就是分段的意思。
- 内存本身并没有分段,段的划分来自于CPU!!!
- 同一段内存,多种分段方案
- 不同的段地址和偏移地址形成同一物理地址
2.5 Debug的使用
- 启动debug ,在DOS提示符下输入命令:debug
2.5.1 用R命令查看、改变CPU寄存器的内容
- -r 【表示查看寄存器内容】
其中【CS是指代码段 段地址 】 【IP指令寄存器】 - -r 寄存器名 【改变指定寄存器内容】
【注】 -r寄存器名 【中间无空格也是对的!】
2.5.2 用D命令查看内存中的内容
- -d 【列出预设地址内存处的128个字节的内容】
- -d 段地址:偏移地址 【列出内存中指定地址处的内容】
- -d 段地址:偏移地址 结尾偏移地址 【列出内存中指定地址范围内的内容】
2000是指定的段地址,0000是偏移地址,而f是显示多少个字节,默认显示128个,这里我们要求显示16个字节,2f就是48个字节,这里使用和显示的数值都是十六进制形式的
2.5.3 用E命令改变内存中的内容
- -E 段地址:偏移地址 数据1 数据2 …
- -E 段地址:偏移地址
- 逐个询问式修改
- 空格 - 接受,继续
- 回车 -结束
- 如果采用询问式方式修改,那么每按一次空格,他都会给出下一个字节的内容,例如: 12. 然后我们需要在.后面给出需要替换掉当前字节的内容
2.5.4 用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 地址 【查看代码】
2.5.5 用A命令以汇编指令的格式在内存中写入机器指令
- 有汇编指令
mov ax, 0123H
mov bx, 0003H
mov ax, bx
add ax, bx - 对应的机器码为
B8 23 01
BB 03 00
89 D8
01 D8 - -a 地址 数据 【写入汇编指令】
- -d 地址 【查看】
- -u 地址 【查看代码】
2.5.6 用T命令执行机器指令
- t命令:逐条指令的在机器代码集去执行这些代码指令,结果在寄存器里面可以直接看,而结果在内存里面可以通过d命令再看。
- t - 执行CS:IP处的指令
mov ax, 0123H
mov bx, 0003H
mov ax, bx
add ax, bx
2.5.7 用Q命令退出Debug
- -q 【退出Debug】
2.6 CS、IP与代码段
2.6.1 两个关键的寄存器
- CS :代码段寄存器
- IP :指令指针寄存器
- CS:IP :CPU将内存中CS:IP 指向的内容当作指令执行
- 8086PC工作过程的简要描述:
- 从CS:IP指向内存单元读取 指令,读取的指令进入指 令缓冲器
- IP = IP + 所读取指令的长 度,从而指向下一条指令
- 执行指令。 转到步骤
- 重复这个过程
- 8086PC工作过程的简要描述:
2.6.2 指令读取和执行的实证演示-Debug
- 用debug程序执行下面的代码
mov ax, 0123H
mov bx, 0003H
mov ax, bx
add ax, bx - 对应的机器码为
B8 23 01
BB 03 00
89 D8
01 D8
- -a 地址 【写入汇编指令】
- -u 地址 【查看代码】
- -t 【执行CS:IP处代码】
- 首先:修改CS与IP地址为题目所说的条件 改成CS为2000 IP为0000
- 然后:将汇编指令写入
- 再进行查看,一一对应,输入正确!
- 最后:执行
- 首先:修改CS与IP地址为题目所说的条件 改成CS为2000 IP为0000
2.7 jmp指令
2.7.1 修改CS、IP的指令
- 事实:执行何处的指令,取决于CS:IP
- 应用:可以通过改变CS、IP中的内容,来控制CPU要执行的目标指令
- 【问题】:如何改变CS、IP的值?
- 方法1:Debug 中的 R 命令可以改变寄存器的值——r cs, r ip
【注】但Debug是调试手段,并非程序方式!那么真实情况下怎么做? - 方法2:用指令修改【但此方法不可行】
- 方法3:转移(跳转)指令 jmp【正确方法】
- 方法1:Debug 中的 R 命令可以改变寄存器的值——r cs, r ip
2.7.2 转移指令 jmp
- 同时修改CS、IP的内容
- jmp 段地址:偏移地址
jmp 2AE3:3
jmp 3:0B16
功能:用指令中给出的段地址修改CS,偏移地址修改IP。
- jmp 段地址:偏移地址
- 仅修改IP的内容 jmp
- jmp 某一合法寄存器
jmp ax (类似于 mov IP, ax)
jmp bx
功能:用寄存器中的值修改IP。
在debug中实现上图:
1.先在对应的地址中将对应地址的汇编指令存入
2.将CS、IP修改成指定的2000:0 这样就使得开始执行的第一条指令是题目所给的
3.开始执行指令
- jmp 某一合法寄存器
2.8 内存中字的存储
- 事实:对8086CPU,16位作为一个字
- 【问题】 16位的字存储在一个16位的寄存器中,如何存储?
- 【回答】 高8位放高字节,低8位放低字节
- 【问题】 16位的字在内存中需要2个连续字节存储,怎么存放?
- 【回答】 低位字节存在低地址单元,高位字节存在高地址单元
【注】先读高地址单元再读低地址单元!
2.8.1 字单元
- 字单元:由两个地址连续的内存单元组成,存放一个字型数据(16位)
- 原理:在一个字单元中,低地址单元存放低位字节,高地址单元存放高位字节
- 在起始地址为0的单元中,存放的是4E20H
- 在起始地址为2的单元中,存放的是0012H
- 【问题】(根据上图)
2.9 用DS和[address]实现字的传送
- 要解决的问题:CPU从内存单元中要读取数据
- 要求: CPU要读取一个内存单元的时候,必须先给出这个内存单元的地址
- 原理: 在8086PC中,内存地址由段地址和偏移地址组成(段地址:偏移地址)
- 解决方案: DS和[address]配合
- 用 DS寄存器存放要访问的数据的段地址
- 偏移地址用[…]形式直接给出
2.9.1 字的传送
- 8086CPU可以一次性传送一个字(16位的数据)
例:
mov bx, 1000H
mov ds, bx
mov ax, [0] —> 1000:0处的字型数据送入ax
mov [0],cx —> cx中的16位数据送到1000:0处
既可以把CPU内部的寄存器数据传到内存中,也可以把内存中的数据传到寄存器里面!
在debug可视化中中实现上图:
1.在内存中存入上图所示数据
2.输入汇编指令
3.接下来一个个执行即可
2.10 DS与数据段
2.10.1 对内存单元中数据的访问
- 对于8086PC机,可以根据需要将一组内存单元定义为一个段。
- 物理地址=段地址×16+偏移地址
- 将一组长度为N(N≤64K)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。
- 处理方法:(DS): ( [address])
- 用DS存放数据段的段地址
- 用相关指令【指mov、add等】访问数据段中的具体单元,单元地址由[address]指出
2.10.2 将123B0H~123BAH的内存单元定义为数据段
2.10.3 用mov指令操作数据
2.10.4 加法add和减法sub指令
2.10.5 用DS和[address]形式访问内存中数据段方法小结
2.11 栈及栈操作的实现
2.11.1 栈结构
- 栈是一种只能在一端进行插入或删除操作的数据结构。
- 栈有两个基本的操作:入栈和出栈。
- 入栈:将一个新的元素放到栈顶
- 出栈:从栈顶取出一个元素
- 栈顶的元素总是最后入栈,需要出栈时,又最先被从栈 中取出。
- 栈的操作规则:LIFO(Last In First Out,后进先出)
- CPU提供的栈机制:
- 现今的CPU中都有栈的设计
- 8086CPU提供相关的指令,支持用栈的方式访问内存空间
- 基于8086CPU的编程,可以将一段内存当作栈来使用
2.11.2 例:设将10000H~1000FH内存当作栈来使用……
- 【问题】
- 1、CPU如何知道一段内存空间被当作栈使用?
- 2、执行push和pop的时候,如何知道哪个单元是栈顶单元?
- 【回答】8086CPU中,有两个与栈相关的寄存器:
- 栈段寄存器SS - 存放栈顶的段地址
- 栈顶指针寄存器SP - 存放栈顶的偏移地址
- 段地址和偏移地址确定,则起始地址也就确定了
- 任意时刻,SS:SP指向栈顶元素
2.11.3 栈的操作
- 执行入栈(push)时,栈顶超出栈空间
- 执行出栈(pop)时,栈顶超出栈空间
- 栈顶超界问题:如何能够保证在入栈、出栈时,栈顶不会超出栈空间?【看下面】
2.11.4 栈顶超界问题的解决
- 汇编语言 低级语言无法保证!
2.11.5 栈的总结
- push、pop 实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。
- 执行push和pop指令时,SP中的内容自动改变。
- 8086CPU提供的栈操作机制:
- 在SS,SP中存放栈顶的段地址和偏移地址,入栈和出栈指令根据SS:SP指示的地址,按照栈的方式访问内存单元。
- push指令的执行步骤:
- pop指令的执行步骤:
2.11.6 关于“段”的总结
2.11.7 综合案例:按要求设置段并执行代码
2.11.8 综合案例:三个段地址可以一样!
三、汇编语言程序
3.1 用汇编语言写的源程序
3.1.1 用汇编语言编写程序的工作过程
- 人写的是汇编程序而最终要的是机器码,机器码让机器去执行的
- 编译器根据伪指令进行相关的编译工作
- 伪指令指导编译器把汇编语言的源程序转成机器码用的
3.1.2 程序中的三种伪指令
3.1.3 源程序经编译连接后变为机器码
3.1.4 汇编程序的结构
- 在Debug中直接写入指令编写的汇编程序
- 适用于功能简单、短小精悍的程序
- 只需要包含汇编指令即可
- 单独编写成源文件后再编译为可执行文件的程序
- 适用于编写大程序
- 需要包括汇编指令,还要有指导编译器工作的伪指令
- 源程序由一些段构成,这些段存放代码、数据,或将某个段当作栈空间
- ; 【注释】
3.1.5 如何写出一个程序来?
3.1.6 程序中可能的错误
3.2 由源程序到程序运行
3.2.1 由写出源程序到执行可执行文件的过程
3.2.2 编辑源程序
3.2.3 编译
- 下面是编译演示过程
3.2.4 提示语法错误
3.2.5 连接
- link
3.2.6 执行可执行程序
3.2.7 小结
- 用软件写出程序
- 然后用masm这样的工具去进行编译 结果由 .asm源文件 产生 .obj目标文件
- 再用link命令把 .obj目标文件 转成 .exe可执行文件
- 最后执行可执行文件得到最终的结果
3.3 运行及跟踪
- .exe可执行文件是如何运行的?
- 采取的方法是Debug装载程序
3.3.1 用Debug装载程序
- debug 编译好的可执行文件名.exe
3.3.2 用Debug单步执行程序
- -r
3.3.3 其他方式执行
- -p
- -g
3.3.4 程序执行的不同方式
3.4 […]和(…)
3.4.1 […]的规定与(…)的约定
- […] 指(汇编语法规定)表示一个内存单元
- (…)指(为学习方便做出的约定)表示一个内存单元或寄存器中的内容
3.4.2 再约定:符号idata表示常量
- 【注】 ds:数据段寄存器 对于ds来说不能把某一个数据移进去
3.4.3 案例分析
3.5 Loop指令
- 功能:实现循环(计数型循环)
- 指令的格式:loop 标号
- CPU 执行loop指令时要进行的操作:
- ① (cx)=(cx)-1;
- ②判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行
- 要求:
- cx 中要提前存放循环次数,因为(cx)影响着 loop指令的执行结果。即在循环之前 一定要给cx赋值!
- 要定义一个标号
3.5.1 用loop指令编程实例
3.5.2 loop指令使用再例
3.6 段前缀的使用
3.6.1 引入段前缀:一个“异常”现象及对策
- 段前缀:ds:[…]
3.6.2 访问连续的内存单元——loop和[bx]联手!
3.6.3 段前缀的使用
- 如果不明确指定,那么[0]就表示ds:[0],如果明确指定了,像上面es:[bx],那对应的段地址就默认按照es寄存器中的值为准
3.7 在代码段中使用数据
3.7.1 存在的问题
3.7.2 改进方案
- 加上标号:start
3.8 在代码段中使用栈
3.8.1 以数据逆序存放为例
3.9 将数据、代码、栈放入不同段
【纠正】:上图中[初始化数据段]和[初始化栈段]标反了!
四、内存寻址方式
4.1 处理字符问题
4.1.1 概述
汇编程序中,用 ‘……’ 的方式指明数据是以字符的形式给出的,编译器将把它们转化为相对应的ASCII码。
- 大写+20H ----> 小写
- 小写-20H ----->大写
4.1.2 大小写转换问题
- 小写转大写用 and 运算
- 大写转小写用 or 运算
4.2 [bx+idata]方式寻址
- bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata(bx中的数值加上idata)。
- mov ax,[bx+200] 或 mov ax, [200+bx] 的含义
- 将一个内存单元的内容送入ax
- 这个内存单元的长度为2字节(字单元),存放一个字
- 内存单元的段地址在ds中,偏移地址为200加上bx中的数值
- 数学化的描述为: *(ax)=((ds)16+200+(bx))
- 指令mov ax,[bx+200]的其他写法(常用)
- mov ax,[200+bx]
- mov ax,200[bx]
- mov ax,[bx].200
- 它就类型于数组。‘ (ds)*16+200 ’表示起始位置,‘ (ds)*16+200+(bx) ’中bx是可变的。
4.3 SI和DI寄存器
4.3.1 CPU内部寄存器
4.3.2 SI和DI常执行与地址有关的操作
4.4 [bx+si]和[bx+di]方式寻址
4.5 [bx+si+idata]和[bx+di+idata]方式寻址
4.6 不同的寻址方式的灵活应用
4.6.1 对内存的寻址方式
4.7 不同寻址方式演示
4.7.1 内存的寻址方式
视频里的不同寻址演示认真看!!!!
4.8 用于内存寻址的寄存器
4.9 在哪里?有多长?
4.10 寻址方式的综合应用