微机原理与汇编语言难舍难分,某乎上建议先把汇编过一遍的同时学习微原效果非常好,再加上本人的确有深入理解计算机系统的需求,所以开了这篇博客。
本篇博客随着学校进度更新。
1 前言
基于8086/8088的汇编语言+微机原理
参考教材:
①《微型计算机原理及技术接口第三版》-裘雪红 车向泉 西电出版社
②《汇编语言第四版》-王爽 清华大学出版社
③《CS:APP》-机械工业出版社
2 Intel单核处理器(8086-16位)
时代进步很快,RISC,CISC已经非常流行,但是为什么要学8086?
个人认为单核是多核的基础,从8086 CPU开始,Intel CPU设计采用了向后兼容(backward compatibility,也称作向下兼容Downward Compatibility)的特性。8086是Intel CPU的基石,非常经典,8086的设计思想不可或缺。
8086是16位结构,16根数据线,一次最多传1个字,用mov
指令给出16位寄存器就可以进行16位数据传送。
2.1.1 8086/8088功能特性
①CPU内部有14个16位的程序员可见的寄存器。
②支持多种寻址方式;不支持浮点数运算。
③指令的操作数可以是16位的,也可以是8位的。(类似32位的程序也可以在64位上跑)
④独立编址(*RISC是统一编址)
⑤内存、接口按字节(Byte)编址。
⑥内存地址空间分段,可寻址1MB;接口地址空间不分段,可寻址64KB。
2.1.2 8086功能特性(与上面区分,算是上面的一个子集)
①CPU内部有14个16位的程序员可见的寄存器。
②支持多种寻址方式(24种);不支持浮点数运算。
③操作数类型:位(bit)、字节(byte)、字(word)和块(block)。
④8、16位无符号和带符号二进制或十进制运算,包括乘法和除法。
⑤体系结构是针对强大的汇编语言和有效的高级语言设计的。
⑥直接主存寻址能力1MB。
8086与8088区别
8086(标准16位微处理器) | 8088(准16位微处理器) | |
---|---|---|
内部数据总线 | 16位 | 16位 |
外部数据总线 | 16位 | 8位 |
推出时间 | 1978年 | 1978年 |
地址位数 | 20位 | 20位 |
寻址空间 | 1MB | 1MB |
指令就绪队列 | 6 字节 | 4 字节 |
2.1.3 8086体系结构
CPU对内存读写
㈠读:
①CPU通过地址总线向内存发出地址信息
②CPU通过控制总线向内存发出读命令,选中内存某个芯片,告诉他:我CPU要读你的数据
③内存将地址信息里面的数据通过数据总线传给CPU
㈡写:
①CPU通过地址总线向内存发出地址信息
②CPU通过控制总线向内存发出写命令,选中内存某个芯片,告诉他:我CPU要给你写数据
③CPU将数据通过数据总线传给内存对应地址的存储单元
注意
CPU访问存储器时,地址总线AB上送出的是物理地址,编程时采用的则是逻辑地址。
微处理器执行程序的步骤
- 从内存储器(Cache)中取出一条指令,分析指令
操作码 - 从内存储器(Cache)或寄存器中获取数据
操作数 - 执行指令
- 将结果存入内存储器(Cache)或寄存器中
微处理器在取操作码、存取操作数时要占用总线,而分析操作码和执行指令时不占用总线,因此在执行指令的过程中,总线会出现空闲时间,而不能被充分利用。
为了能充分利用总线,8086/8088微处理器的内部结构从功能上被设计为两个独立的功能部件:总线接口单元BIU与执行单元EU。
内部组成1-总线接口单元BIU(Bus Interface Unit,取指令)
负责与存储器、I/O接口传递数据,具体完成:
①从内存取指令,送到指令队列
②配合EU从指定的内存单元或IO端口取数据
③将EU的操作结果送到指定的内存单元或IO端口
常见IO接口
VGA(Video Graphics Array即视频图形阵列),USB,标准视频输入接口,音频输入输出接口。
内部组成2-执行单元EU(Execute Unit,执行指令)
负责指令的执行,(算术、逻辑、移位运算,有效地址计算,控制命令、……)
工作原理
①BIU取指令,EU执行指令,二者相互独立相互配合
②指令队列有2+空字节,BIU自动取指 → 队列
③EU总是从队列前部取指令去执行
④指令需要访问M或I/O,EU会请求BIU去完成
8086寄存器结构
✍内存中字的存储
CPU用16位
N地址字单元:N和N+1内存单元组成的地址,注意:高地址在前
数据寄存器
8086有4个16位的数据寄存器:AX,BX,CX,DX。每个寄存器又可以分为高、低字节独立的2个8位寄存器:高8位AH(High),低8位AL(Low)。
位bit,字节byte=8bit,字word=2byte
同时这4个数据寄存器有特殊用途:
寄存器 | 特殊用途 |
---|---|
AX | 字节乘(乘积),字节除(被除数),字IO,字乘法(乘数/乘积L),字除法(被除数L/商) |
AL | 字节乘(乘数),字节除(商),字节IO,十进制运算校正 |
BX | 段内偏移地址 |
CX | 串操作计数,循环计数(loop),存储程序字节数(王爽实验4-3) |
CL | 移位次数 |
DX | 字乘法(乘积H),字除法(被除数H),IO地址 |
A:Accumulator,又叫累加器
B:Base,基址寄存器,可当作指针,可以用来寄存器间接寻址:[BX],后面汇编语言中如果写MOV CL,[BX]
是对的,但是如果写MOV CL,[AX]
或者CX,DX都不行!
C:Count,计数/移位。移位次数大于1的默认存在CX中。
指针寄存器2个
SP:堆栈指针寄存器,存放堆栈区偏移地址,指示堆栈的当前操作位置
【注意】堆栈主要用于 保护断点和现场 |
---|
①暂存数据 |
②为程序保存提供返回地址:子程序调用CALL指令用堆栈保存程序返回地址,子程序返回指令RET从堆栈取出返回地址。 |
③对于堆栈区域中存储单元的寻址是一种特殊的【存储器操作数】的寻址方式,它利用两个寄存器访问:堆栈指针SP和堆栈段寄存器SS; |
BP:基数指针寄存器,存放主存基本偏移地址
变址寄存器2个
SI:源变址寄存器,指向源操作数
DI:目的变址寄存器,指向目的操作数
SI,DI可自动修改内容
访问数据段能当指针的有:SI,DI,BX
控制寄存器2个
IP:指令指针寄存器,指示当前指令所在存储单元的段内偏移地址。每当8086依据CS和IP从主存取得一个指令字节后,IP自动+1,指向下一个要读取的指令字节。
PSW(背):程序状态字,也叫状态寄存器或标志寄存器,共16位,暂定义9个标志位,存放8086CPU在工作中的状态信息。清零和置1要记住
会使用和编程即可,FLAGS/PSW可以不用记
(记法:CPAZ,STIDO)
FLAGS/PSW | 含义 |
---|---|
AF(8086) | 辅助进位标志位:低4位向高4位如果有进位,AF置1,做BCD加法运算时判断是否修正 |
CF | 进位/借位标志位:无符号数加减有进位或借位时置1 |
PF | 奇偶标志位 |
SF | 符号标志位:对应运算结果最高位 |
ZF | 0标志位:运算结果为0 |
DF(8088) | |
IF | |
OF | 溢出标志位 |
TF |
段寄存器
通用用途:
8086CPU在访问内存时由这4个段寄存器提供内存单元的段地址,不同种类的段寄存器存储不同属性段的段地址,与段内偏移地址一起确定主存的物理地址(后面会讲)。
通过四个段寄存器,CPU每次可同时对4个段进行寻址,即可同时提供容量达64KB的程序区(CS),64KB的堆栈区(SS)、128KB的数据区(DS & ES)。
CS:指示程序区,存储段高16位地址,剩下4位默认为0
DS:指示数据区
ES:指示数据区(附加段)
SS:指示堆栈区,8086栈顶对应小地址
8086不支持将数据直接送入段寄存器,比如你直接写MOV DS 1000H
是非法的,要想将数据送入DS,只能先将数据送入一个一般的寄存器比如BX,再通过BX把内容送入DS。至于说为什么这样干,我们不必深究,知道有这么一回事就行。
👋拓展:DS和[address]
DS:存放要访问的数据的段地址
比如我们要访问10000H单元的内容,可以这样:
1 | mov bx,1000H |
第三条指令作用:将内存单元中的内容送入寄存器中
[
[BX]:偏移地址BX中的内容
1 | DS<=====>BX,SI,DI |
8086主存储器和IO结构
🧐系统讲解此部分知识前,先弄懂几个概念
①16位结构的CPU(16位机,字长为16位)
运算器一次最多处理16位数据;寄存器最大宽度16位;寄存器和运算器之间通路是16位。
内存单元的地址在送上地址总线之前,必须在CPU中处理、传输、暂存。
✍注意:8086是16位CPU不代表传送地址是16位!
②8086给地址的方法
8086CPU有20位地址总线,即寻址能力为
这种寻址方式的本质是:物理地址=基础地址+偏移地址的一种实现。
段基址:段寄存器保存的高16位(段的首地址的高16位地址)
段内偏移地址:段中某存储单元 “相对于段基址的偏移量”
段地址由16位寄存器提供,段内地址偏移由IP或EU确定的有效地址(EA)提供。
③段的概念
🤡这个概念挺会混淆视听的,内存压根没有分段这一说,如果在开始有这样的错误认知,会影响后续的学习。
✍段的划分实际上来源于CPU。比如:我们可以认为地址10000H-100FFH的内存单元算一个段,段起始地址为10000H,段地址为1000H,段的大小为100H;当然我们也可以认为地址10000H-1007FH、10080H-100FFH的内存单元是两个段,段的大小都是80H。编程时根据需要,将若干地址连续看成一个段,就是逻辑段。段地址
8086系统将1MB存储空间分为若干存储段,每段固定大小64KB,正是8086这样的设计,才有主存储器的IO空间的特殊结构。8086支持20位地址,总共1MB内存,但只能使用640KB,因为其余的必须保留,供系统使用。(见下图)
8086的RESET叫复位电路,高电平有效,通电后给它大于4个时钟周期的正脉冲,将CS初始化为FFFFH,IP初始化为0000H,所以第一条指令从FFFF0开始取,此处放的是跳转指令,跳到BIOS,然后从小地址到大地之执行。
《汇编语言》实验 查看CPU和内存,用机器指令和汇编指令编程-总结
1 | #预备知识 |
1.主存和IO地址空间大小与分体结构
偶地址空间对应低字节的访问,
奇地址空间对应高字节的访问,
🧐
🧑因为8086这两个体可能同时要读地址,即
✋✋8086分段存储非常重要,贯穿全书
2.主存分段与段寄存器的使用
分段结构优势:
①代码、数据量不大 → 使其处于同一段内(64KB范围内)→ 可减少指令长度、提高运行速度。
②内存分段为程序的浮动分配创造了条件。
③各个分段之间可以重叠。
下面结合例题说明
第一问,CS与IP配合做PC的功能,即:任意时刻CPU将CS:IP指向的内容当作指令执行。FFFF0H单元中的指令是8086开机执行的第一条指令。其余略。
第二问,“由该物理地址决定的逻辑地址是什么?”,这句话就让你求CS和IP(段地址+偏移量),在JMP的前一个指令执行完成后,逻辑地址加载到CS和IP中,例如选择逻辑地址为2000H:0100H,则JMP执行后,CS=2000H,IP=0100H。JMP指令执行前后CS发生改变,故该程序实现了段间转移。一般来说,大约有0x1000(4096)对不同的CS:IP地址组合能指向同一个内存地址。
显示缓冲区就是个例子
老师上课时候提到了JMP指令,并且拓展延申,这里就直接学了(以后也得学):
拓展:转移指令(王爽汇编第9章)
转移指令是:修改CS和IP的指令,也可以说是控制CPU执行内存中某处代码的指令。
offset
取得标号的偏移地址
1 | start:mov ax,offset start ;相当于mov ax,0 |
依据位移进行转移的jmp(修改IP)
1 | jmp short xxx(段内短转移)8位:-128~127:最多向高地址移动127位,或向低地址移动128位 |
这两条指令对应的机器指令并没有转移的目的地址,而是相对于当前IP的转移位移。
转移的目的地址在指令中的jmp(修改CS和IP)
1 | jmp far ptr xxx(段间转移;远转移) |
转移地址在寄存器中的jmp
1 | jmp 16位REG |
转移地址在内存中的jmp
1 | jmp word ptr 内存地址单元 (======>段内转移) |
jcxz指令
Jump if CX equals Zero
1 | if((cx)==0) jmp short xxx |
loop指令
1 | (cx)--;#所以在汇编程序里面一定有一条和inc cx或dec cx相关的指令 |
在任何模式下,LOOPD指令都使用ECX作为循环计数器;LOOPW都使用CX作为循环计数器。
例程1:整数数组求和
1 | ; This program sums an array of words. (SumArray.asm) |
实验8
分析下面的程序,在运行前仔细思考:这个程序可以正确的返回吗?运行后在思考:为什么是这样的结果?
1 | assueme cs:codesg |
先来看一下程序的执行流程
1 | 1.start: mov ax,0 |
注意👋使用debug的u命令时要加参数:
1 | -u 0 |
否则默认反汇编从start标号处开始。
红线就是jmp short s1
对应的机器码,偏移量是F6,对应有符号10进制数就是-10,所以要找到就是ip-10,指向的地址是codesg。
2.1.4 8086芯片引脚(背,内容很多,具体看书P29,30,31)
(1)两种模式下共用信号
(2)仅在最小模式下使用的信号
(3)仅在最大模式下使用的信号
2.1.5 工作时序
时钟周期:CPU处理动作的最小时间单位,是 CPU 主频的倒数。
计组学过指令周期:取指+执行用的时间,但是8086取指和执行是重叠进行的,所以引出总线周期。
✍总线周期:CPU通过系统总线进行存储器读写、I/O读写、中断响应所需时间。8086执行程序的过程就是若干个总线周期实现过程,许多操作都要用到系统总线,所以也叫总线操作。
正常情况下8086CPU一个总线周期由4个时钟周期组成,CPU速度高于主存或IO设备时,要在正常的的总线周期中插入等待时间
必须掌握典型8086/8088时序分析
T1:送地址
T3:纯等待,主存从给出的地址选中单元需要时间,判断Ready
Ready:由慢速内存或接口或控制电路产生
T4:读数据,上升沿获取数据
2.1.6 系统总线形成(要会做题)
使用器件:
1.单向总线
单向三态门驱动器74LS244
2.双向总线
①8286
②8287(带反向)
③74LS245
3.带有三态输出的锁存器
①8282
②8283(带反向)
③74LS373、74LS374
驱动器/缓冲器:增加驱动能力、减轻负载。
💪扇出能力大
👍延时小
驱动的输入与输出是一致的,只有当总电流大于各个支路电流之和才能起到驱动的效果,即:
微融合与宏融合
微融合(Micro-Fusion):将来自同一指令的多个微操作融合为单一的复杂微操作。如果不是被微融合,这些微操作会被多次发送到乱序执行核中。
宏融合(Macro-Fusion):让处理器在译码的同时,将同类的指令融合为单一的指令,以减少处理的指令数量,让处理器在更短的时间内处理更多的指令。
拓展:虚拟化技术
👦用虚拟机那么长时间一直都没了解虚拟化技术,好惭愧,先挖个坑,后续打完国赛补上,多少得懂一点。
==更新==
已经在写了
第3章前置技能-汇编程序🤛
源程序从写出到执行
1.编写
产生了存储源程序的文本文件
2.编译连接
编译:对源程序文件中的源程序编译
连接:目标文件
可执行文件包含2部分:
①程序(从源程序中的汇编指令翻译过来的机器码)和数据
②相关描述信息(eg:程序占了多大内存空间等等)
3.执行可执行文件中的程序
在OS中开始执行,将可执行文件中的机器码和数据加载
源程序
以👇这个为例
1 | assume cs:codesg |
1.伪指令
汇编语言包含2种指令:
①汇编指令:有对应机器码的指令,可以被编译为机器指令,最终被CPU执行
②伪指令:没有对应机器码指令,是被编译器执行的指令
👆的程序出现了3种伪指令:
1.assume
作用:将有特定用途的段和相应的段寄存器关联起来
2.segment-ends
成对使用,segment
3.end
汇编语言结束标记,遇见end
注意👋ends与end区分
2.源程序中的“程序”
程序是源程序中最终由计算机执行、处理的指令或数据。
3.标号
一个标号指代一个地址,比如codesg在segment前面,作为一个段的名称,这个段的名称最终被编译、连接程序处理为一个段的段地址。
4.程序结构
源程序是由一个一个一个
5.程序返回
我们在DOS(一个单任务操作系统)的基础上讲解。
一个程序P2在可执行文件中必须有个正在运行的程序P1,
①将P2从可执行文件中加载进内存后;
②将CPU控制权交给P2,P2才开始运行,此时P1暂停;
③P2运行完,将CPU控制权交给P1,P1继续运行。
所以✍程序返回:一个程序结束后,将CPU的控制权交还给使得它运行的程序
1 | mov ax,4c00H |
这两条指令实现的功能就是程序返回,一共占5字节。
实战编写运行汇编语言
1.实战环境
ubuntu20.04 DOSBOX
自动挂载虚拟硬盘(搜索引擎有)
2.步骤
①编辑源程序
在挂载硬盘的文件夹查看文件是否齐全:dir命令;然后在具有MASM,ASM,File的文件夹的终端gedit hello.asm
,输入以下内容:
1 | data segment ;数据段 |
②编译:masm hello.asm
+3个回车
③连接:link hello.obj
+3个回车
④运行:hello.exe
DOS中有一个程序叫command.com
,就是DOS系统的shell
。
3.程序执行过程的跟踪
1 | debug filename.exe |
这里讲解一下exe文件中程序的加载过程
🧐🤨SA+10H是怎么来的?
👋✍PSP占256字节,程序开始地址为:SA
DOSBOX加载1.exe时顺序:
①command加载debug
②debug加载1.exe
返回时顺序:
①1.exe
②debug
寄存器组的环境
4.汇编基础
(1)操作数
立即操作数(immediate)
imm:8、16或32位立即数
imm8:8位立即数(字节)
imm16:16位立即数(字)eg:short
imm32:32位立即数(双字) eg:int
寄存器操作数
reg:任意的通用寄存器
sreg:16位段寄存器CS、DS、SS、ES、FS、GS
r8:AH、AL、BH、BL、CH、CL、DH、DL
r16:AX、BX、CX、DX、SI、DI、SP、BP
r32:EAX、EBX、ECX、EDX、ESI、EDI、ESP、EBP
内存操作数
mem:8、16或32位
其他约定
r/m8:8位操作数(8位通用寄存器或内存字节)
r/m16:16位操作数(16位通用寄存器或内存字)
r/m32:32位操作数(32位通用寄存器或内存双字)
(2)整数表达式
(3)字符、字符串常量
用’ ‘括起来
(4)保留字
有特殊含义,只能用于正确的上下文环境中
例如@data
,编译时返回整数常量值
(5)标识符
尽量避免用“@”和“_”作为第一个字符,因为它们即用于汇编器,也用于高级语言编译器。
(6)定义数据
(7)符号常量
不占用任何实际存储空间
但是下面的例子要懂:
1 | list1 BYTE 10,20,30,40 |
1 | list2 WORD 1000h,2000h,3000h,4000h |
(8)例程1——加减法
1 | .MODEL small,stdcall |
中断
中断分为内中断和外中断,内中断来源于CPU内部,外中断来源于CPU外部。
如何理解中断
任何一个通用的CPU,比如8086,都具备这样一种能力:可以在执行完当前正在执行的指令后,检测到从CPU外部发送过来的或内部的一种特殊信息,并且可以立即对所接收到的信息进行处理。
这样特殊的信息我们起个名,叫中断。
中断就是:CPU不再接着刚执行完的指令向下执行,而是转去处理这个特殊信息。
举个例子:你作业写到一半,你妈妈叫你吃饭,然后你就放下作业去吃饭,吃完饭后继续写作业,这个就是外中断;你作业写到一半,饿了,自己点了外卖,就是内中断。
8086用中断类型码的数据来标识中断信息的来源,中断信息码是一个字节型数据(byte),一共可以表示256种中断信息来源,所以我们将产生中断信息的事件,即中断信息的来源,简称为中断源。
内中断(异常、例外、陷阱)
当 8086CPU 内部有下面的情况发生的时候,将会产生相应的中断信息。
- 除法错误,比如,执行 div 指令产生的除法溢出;==>0
- 单步执行;==>1
- 执行 into 指令;==>4
- 执行 int 指令,格式为 int n,指令中 n 为字节型立即数,即为中断类型码。
外中断(中断)
PC系统的接口卡和主板上,装有各种接口芯片,这些外设接口芯片内部有若干寄存器,CPU将这些寄存器当作端口来访问。
外设的输入通过端口送入相关接口芯片的端口中;CPU向外设的输入也是先送入端口中,再由对应的芯片送入外设。
即:CPU通过端口和外部设备进行联系。
✍一般CPU设有两个中断请求输入引脚:可屏蔽中断请求输入引脚和不可屏蔽中断请求输入引脚。
中断处理过程
- 中断请求
- 中断判优
- 中断响应
- 中断服务
- 中断返回
1.中断请求
内中断或外中断,每个中断源都有一个中断请求触发器,锁存自己的中断请求信号,并保持到CPU响应这个中断请求后才将其清除。
CPU内部还有一个中断允许触发器,置1时允许CPU响应中断称为开中断,置0时屏蔽中断,即不允许中断,被称为关中断。
2.中断判优
多个中断源同时向CPU提出中断请求时,CPU必须找出中断优先级最高的中断源,这个过程叫中断判优,可以采用硬件方法或者软件方法。
3.中断响应
中断响应时,CPU向中断源发出中断响应信号
4.中断服务
①保护现场:在中断服务程序的起始部分安排若干条入栈指令,将各寄存器的内容压入堆栈保存。
②开中断:在中断服务程序执行期间允许级别更高的中断请求中断现 行的中断服务程序,实现中断嵌套。
③中断服务:完成中断源的具体要求。
④恢复现场:通常是将保存在堆栈中的现场信息弹出到原来的寄存器中。
⑤中断返回:返回到原程序的断点处,继续执行原程序。
5.中断返回
返回到原程序的断点处,恢复硬件现场,继续执行原程序。
3 Intel指令系统与程序设计
16位寻址方式
8086中,只有
这4个寄存器可单独行动,或以4种组合出现:
①bx+si(+idata)
②bx+di(+idata)
③bp+si(+idata)
④bp+di(+idata)
西电计科微原讲的寻址方式如下:
数据传送指令
1.MOV
1 | mov destination,source |
✍注意事项
两个操作数的尺寸必须一致。
两个操作数不能同时为内存操作数。
目的操作数不能是CS,EIP和IP。
立即数不能直接送至段寄存器。
2.MOVZX
整数的零/符号拓展:
move with zero-extened
move with sigh-extened
1 | 格式: |
3.XCHG指令
exchange data——交换两个操作数内容
1 | 格式 |
交换两个内存操作数:用寄存器,MOV和XCHG结合使用
1 | mov ax,val1 |
4.直接偏移操作数(直接寻址)
1 | .data |
👋注意:
MASM并不要求一定要使用方括号。
MASM对有效地址没有内建的范围检查模块。
加法和减法指令
1.INC和DEC指令
这两条指令不影响进位标志
1 | .data |
2.ADD指令
注意同尺寸即可,不改变源操作数
3.SUB指令
不改变源操作数
影响的标志位有:
CF、ZF、SF、OF、AF、PF
4.NEG指令(negate)
将操作数按位取反,末位加1,即求补码
eg:
1 | neg 0D8h |
影响的标志位有:
CF、ZF、SF、OF、AF、PF
5.NOT指令
将操作数按位取反,即求反码,不影响任何状态标志
例程—实现算数表达式
1 | .data |
和数据相关操作符和伪指令
1.OFFSET操作符
是由编译器处理的符号,它的功能是取得标号的偏移地址。
2.PTR操作符
用来重载操作数的默认尺寸。
3.TYPE操作数
返回按字节计算的变量的单个元素的大小。
4.LENGTHOF操作符
计算数组中元素的个数。
👋与sizeof
区分
拓展✍C语言中sizeof
详解:
sizeof
是操作符operator而不是函数,它以字节形式给出了其操作数的存储大小。
1 | int i; |
5.SIZEOF操作符
本质就是存储数据的大小
call和ret指令
call
主体思想:(不是朝鲜那个!🤣🤣)
①将call指令的下一条指令的CS和IP入栈(段间CS和IP,段内IP)
②操作与call对应的jmp指令
✍要运用push和pop的原理!
根据位移转移的call
1 | call 标号 |
检测点10.2
1 | 内存地址 机器码 汇编指令 |
1.push IP:
IP计算方式:
①call指令的下一条指令的IP 或者
②call指令的IP是3,call指令占3个字节,3+3=6,就是IP
这两种计算方式都可以
2.jmp near ptr s
将栈的元素弹出,是IP,也就是6,所以ax=6
注意:跳过去不能再跳回来!有同学还加了1,我不说是谁🤡
转移地址在指令中的call
1 | call far ptr(段间转移) |
检测点:ax值是多少?1010h
1 | 内存地址 机器码 汇编指令 |
转移地址在寄存器中的call
1 | call 16位reg |
检测点:ax值?0Bh
1 | 内存地址 机器码 汇编指令 |
转移地址在内存中的call
1 | call word ptr 内存单元地址 |
例题:
1 | mov sp,10h ;sp = 10h |
IP = 0123h,sp = 0Eh
检测点:ax值?
1 | assume cs:code |
ret
用栈中的数据修改IP中的内容,实现近转移
1 | IP = SS:SP |
相当于
1 | POP IP |
retf
用栈中的数据修改CS和IP的内容,实现远转移
1 | IP = SS:SP |
相当于
1 | POP IP |
1 | low |_____________| |
乘法与除法指令
mul:无符号数乘法
32 位模式下,MUL(无符号数乘法)指令有三种类型:
- 第一种执行 8 位操作数与 AL 寄存器的乘法;
- 第二种执行 16 位操作数与 AX 寄存器的乘法;
- 第三种执行 32 位操作数与 EAX 寄存器的乘法。
乘数和被乘数的大小必须保持一致,这三种类型都可以使用寄存器和内存操作数,但不能使用立即数。MUL指令不会发生溢出。
被乘数 | 乘数 | 乘积 |
---|---|---|
AL | reg/mem8 | AX |
AX | reg/mem16 | DX:AX |
EAX | reg/mem32 | EDX:EAX |
👋如果 DX或EDX 不等于零,则进位标志位置 1,这就意味着隐含的目的操作数的低半部分容纳不了整个乘积。
有个很好的理由要求在执行 MUL 后检查进位标志位,即,确认:忽略乘积的高半部分是否安全。
div:无符号数除法
32 位模式下,DIV(无符号数除法)指令有三种类型:
- 第一种执行 8 位无符号数除法;
- 第二种执行 16 位无符号数除法;
- 第三种执行 32 位无符号数除法。
被除数 | 除数 | 商 | 余数 |
---|---|---|---|
AX | reg/mem8 | AL | AH |
DX:AX | reg/mem16 | AX | DX |
EDX:EAX | reg/mem32 | EAX | EDX |
示例:
1.8位无符号除法
2.16位无符号除法
DX 包含的是被除数的高位部分,因此在执行 DIV 指令之前,必须将其清零
1 | mov dx,0 ;清除被除数高16位 |
3.32位无符号除法
除数为内存操作数
1 | .data |
IMUL指令:有符号乘法
如果积的高半部分不是低半部分的符号扩展,则设置CF和OF。
IDIV指令:有符号除法
当执行8位数的除法指令之前,必须将被除数符号扩展到AH中(可用CBW指令)。
16位除法要求AX被符号扩展到DX中。
32位除法要求将EAX符号扩展到EDX中。
布尔和比较指令
AND指令(按位与
1 | AND destination,source |
结果保存在目的操作数中
1 | AND reg,reg |
总是清除OF和CF
用途举例:
1.特定位清0
2.小写转大写
例程:字符转换为大写
1 | .data |
OR指令(按位或
格式与AND相同
用途:
1.特定位置1
2.大写转小写
要掌握:
1 | mov dl,5 ;二进制数 |
XOR指令(按位异或
用途:
1.对某些位取反,同时不影响其它位
2.判断16位或32位的奇偶性
1 | mov ax , 64C1h |
3.简单数据加密
TEST指令
两操作数按位与,根据结果设置标志位但不回送结果,即不修改目的操作数。
用途:测试操作数某一位是0还是1
例子:想指定AL中第0位和第三位是否同时为0
1 | test AL,00001001b |
清除 OF、CF;修改 SF、ZF、PF。
CMP指令
1 | cmp destination,source |
无符号数比较:
有符号数的比较:
设置和清除单个CPU标志
1 | stc ;设置进位标志 |
条件跳转
按照以下条件可将跳转指令分成4组:
·根据特定的标志值。
·根据操作数之间是否相等,或根据(E)CX的值。
·根据无符号操作数的比较结果。A: Above B: Below
·根据有符号操作数的比较结果。G: Greater L: Less
例程1:比较 V1、V2、V3 三个无符号变量的值,将最小值送入AX寄存器。
1 | .data |
移位和循环移位指令
逻辑移位和算数移位
SHL/R:逻辑左/右移
快速乘/除法
1 | mov dl,5 |
1 | mov dl,32 |
SAL/R:算数左/右移
1 | mov al,0F0h ; AL = 11110000b (-16) |
1 | mov dl,-128 ; DL = 10000000b |
ROL:循环左移
特点:不丢失任何数据位
例:将一个字节的低4位和高4位交换
1 | mov al,26h |
ROR:循环右移
1 | mov al,01h ;00000001 |
RCL/R(带进位的循环左/右移
例子:从进位标志中恢复一个位
1 | .data |
SHLD/SHRD
要求至少是I386处理器
SHLD:双精度左移
1 | SHLD 目的操作数,源操作数,移位位数 |
SHRD:双精度右移
1 | SHRD 目的操作数,源操作数,移位位数 |
间接寻址
1.间接操作数
可以是任何用方括号括起来的32位通用寄存器(EAX,EBX,ECX,EDX,ESI,EDI,EBP、ESP)。
实地址模式下只能用SI,DI,BX,BP。通常尽量避免使用BP(BP常用来寻址堆栈而不是数据段)。
✍实模式(real model):
又叫实地址模式(real address mode),是所有x86兼容CPU的一种操作模式,采用和8086相同的16位段和偏移量,最大寻址空间1MB,是CPU启动时的模式,相当于一个速度超快的8086。
在这个实模式下,可以运行DOS操作系统。著名的MSDOS是微软的DOS操作系统,微软用它赚了第一桶金。
用户写的代码中CS与IP寄存器组合成的地址即为实际的物理内存地址。这种方式非常的不安全,并且不方便实现多任务系统。在设计80286的时候要解决的主要就是这个问题。
✍保护模式(protected mode):
又叫保护地址模式(protected address mode),从80286开始,有了存储器分段管理部件,支持操作系统实现一种新的存储器管理模式,保护模式。
在80286中,段式访存得到的改进,原来段寄存器+IP得到的地址不在是实际的物理地址,而是要经过一个转换层转换才变成一个物理地址。CS里面的内存不再是20位物理地址的高16位,而变成了段描述符表中的索引。
2.数组
例程1:32位三个双字相加
1 | .data |
例程2:16位三个双字相加
1 | .data |
3.变址操作数(寄存器相对寻址)
例程
1 | .data |
过程论
随着程序规模的增长,到了需要对程序进行逻辑划分并将其分为过程的时候了。
一.与外部库链接
二.堆栈操作
1 | PUSH POP |
以32位为例
堆栈用途:
📒过程内的局部变量在堆栈上创建,过程结束时,这些变量被丢弃。
📓CALL指令执行时,CPU用堆栈保存当前过程的返回地址。
📚调用过程时,可通过堆栈传递参数。
📖寄存器在用作多种用途的时候,堆栈可方便地作为其临时保存区域;当寄存器使用完毕后,可从堆栈中恢复其原始值。
三.过程定义与使用
1.PROC伪指令
①过程定义
过程用PROC和ENDP伪指令来声明,另外还必须给过程起一个名字(一个有效的标识符,下面的就有main和sample)。
②例程:三个整数之和
1 | SumOf PROC |
③为过程添加文档
几点建议:
- [ ] 过程完成的所有任务的描述。
- [ ] 输入参数的清单及使用方法。
- [ ] 过程返回值的描述。
- [ ] 列出特殊要求,即调用过程之前必须满足的条件。
举例:
1 | ;-------------------------------------------------------- |
2.CALL和RET指令
1 | main PROC |
1 | MySub PROC |
向过程传递寄存器参数例程:
1 | .data |
例程—对整数数组求和并调用
1 | ;----------------------------------------------------- |
1 | .data |
四.使用过程进行程序设计
五.高级过程
堆栈参数
需要的参数由调用程序压入堆栈。
·PROTO伪指令
函数声明,为一个已存在的过程创建原型。原型声明了过程的名字和参数列表,允许一个过程在被定义之前就可以在其它地方被调用。
·ADDR运算符
可在使用INVOKE调用过程时传递指针(地址参数)。
·内存模式
①.MODEL伪指令
1 | .MODEL 内存模式 [,模式选项] |
例:.MODEL flat,stdcall
保护模式程序使用平坦内存模式
②STDCALL关键字
指定过程按照从右到左的顺序压入参数。
👋STDCALL要求过程在RET指令后提供常量操作数。
寄存器参数
寄存器内容在装入参数前必须保存。
INVOKE指令
自动在堆栈上压入参数并调用程序。
使用invoke伪指令会帮你完成参数校检和压参操作,直接和高级语言一样直接调用函数即可。
👋几乎所有高级语言都使用堆栈参数
堆栈框架
1 | LEA reg,mem |
计算并装入内存操作数的偏移。
LOCAL
伪指令在过程中声明一个或多个局部变量。
六.结构与宏
结构(Structure)
结构:逻辑上相互关联的一组变量的模板或模式。
结构中单个变量称为域(field)。
域的初始化:
·未定义:使用“?”
·字符串:引号引起的字符初始化字符串域
·整数:整数常量、整数表达式
·数组:用DUP
操作符
宏(Macro)
命名的汇编语句块。调用宏的时候,汇编语句块的一份拷贝被直接插入到程序中。
宏和子程序区别
①宏调用通过宏指令名进行的;汇编时,由汇编程序展开,调用多少次就展开多少次,不能缩短目标程序。子程序调用是在程序执行期间执行CALL
指令进行的,子程序的代码只在目标程序中出现一次。
②宏调用时的参数由汇编程序通过实参替换形参的方式传递;子程序调用时的参数通过寄存器、堆栈或约定的内存单元传递。
③宏调用是在汇编时完成,运行无需额外开销;子程序调用和返回均需时间,且涉及堆栈,速度慢。
硬件I/O
1.内存映射
程序直接向特定的内存地址写入数据,数据则自动传送至设备。
例如:视频显示。
2.基于端口
要求使用 OUT
和 IN
指令向“端口”写入或读取数据。
IN:从端口输入一个字节、字 或 双字。
OUT:向端口输出一个字节、字 或 双字。
32/64位处理器拓展指令
4.总线技术
概述
总线驱动和控制
负载
驱动(重点是板子的设计
常用芯片
①单向驱动器(三态输出)
②双向驱动器(三态输出)
例题
某电路板上驱动电路如图所示。
1)编程:从
2)画出:
解:
1)
1 | mov ax,0D100H |
2)波形的题已经10年没有考过了
对于这个总线对应情况的解答:
CXQ老师:比如你设计了一个基于ISA总线的网卡,插在计算机主板的ISA总线扩展插槽上即可使用。
在你的网卡内部,要对ISA总线过来的信号进行驱动,比如数据线,用74LS245驱动,A边连ISA总线信号(即计算机主板过来的信号),B边连网卡内部电路,则74LS245的方向控制可以用ISA总线上的/MEMR或者/IOR信号。
如果74LS245的B边接ISA总线、A边接网卡内部电路,那么方向控制信号就要用写信号(/MEMW或/IOW)了。
可以记为:A主板读,A网卡写。
附录1 英文术语
全称 | 缩写 | 全文 |
---|---|---|
【1】大规模集成电路 | LSI | Large Scale Integration |
【2】个人电脑 | PC | Private Computer |
【3】输入/输出操作 | I/O | Input/Output |
【4】数据总线 | DB | Data Bus |
【5】地址总线 | AB | Address Bus |
【6】控制总线 | CB | Control Bus |
【7】中央处理器 | CPU | Central Processing Unit |
【8】微处理器 | MPU | Microprocessor Unit |
【9】累加器 | ACC | Accumulator |
【10】标志寄存器 | FR | Flag Registers |
【11】暂存器 | TMP | Temporary |
【12】算术逻辑单元 | ALU | Arithmetic Logic Unit |
【13】指令寄存器 | IR | Instruction Register |
【14】指令译码器 | ID | Instruction Decoder |
【15】操作控制电路 | OC | Operate Control |
【16】程序计数器 | PC | Program Counter |
【17】堆栈指示器 | SP | Stack Pointer |
【18】二进制编码的十进制数 | BCD | Binary-Coded Decimal |
【19】总线接口单元 | BIU | Bus Interface Unit |
【20】执行单元 | EU | Execution Unit |
【21】指令单元 | IU | Instruction Unit |
【22】地址单元 | AU | Address Unit |
【23】存储器管理部件 | MMU | Memory Management Unit |
【24】指令预取单元 | IPU | Instruction Prefetch Unit |
【25】指令译码单元 | IDU | Instruction Decode Unit |
【26】进位标志 | CF | Carry Flag |
【27】奇偶标志 | PF | Parity Flag |
【28】辅助进位标志 | AF | Accessary Carry Flag |
【29】零标志 | ZF | Zero Flag |
【30】符号标志 | SF | Sign Flag |
【31】溢出标志 | OF | Overflow Flag |
【32】方向标志 | DF | Direction Flag |
【33】陷阱标志 | TF | Trap Flag |
【34】中断允许标志 | IF | Interrupt Enable Flag |
【35】I/O特权级标志 | IOPL | I/O Priority Level |
【36】任务嵌套标志 | NT | Nested Task Flag |
【37】恢复标志 | RF | Resume Flag |
【38】虚拟8086模式 | VM | Virtual 8086 Mode |
【39】对准检查标志 | AC | Alignment Check |
【40】浮点运算单元 | FPU | Float Point Unit |
【41】引脚网络阵列 | PGA | Pin-Grid Array |
【42】总线单元 BU | BU | Bus Unit |
【43】寻址方式 | ———————— | Addressing Mode |
【44】立即寻址 | ———————— | Immediate Addressing |
【45】寄存器寻址 | ———————— | Register Addressing |
【46】存储器操作数寻址 | ———————— | Memory Operator Addressing |
【47】有效地址(偏移地址) | EA | Effective Address |
【48】直接寻址 | ———————— | Direct Addressing |
【49】寄存器间接寻址 | ———————— | Register Indirect Addressing |
【50】寄存器相对寻址方式 | ———————— | Register Relative Addressing |
【51】基址变址寻址方式 | ———————— | Based Indexed Addressing |
【52】相对基址变址寻址 | ———————— | Relative Based Indexed Addressing |
【53】比例变址寻址 | ———————— | Scaled Indexed Addressing |
【54】基址比例变址寻址 | ———————— | Based Scaled Indexed Addressing |
【55】相对基址比例变址寻址 | ———————— | Relative Based Scaled Indexed Addressing |