【笔记】8086汇编-直接定址表

前言:本篇仅仅是笔记,整理于2017/9/4,结构可能会略有混乱,见谅。

-引入:这一章,我们讨论如何有效合理地组织数据,以及相关的编程技术。

-【16.1  描述了单元长度的标号——数据标号】

–将标号后面的冒号“:”去掉,我们可以给标号一个明确的数据类型,比如:a db 0, b dw 0, c dd 0。它们代表了相应数据的段地址和偏移地址,在编写程序时,我们可以直接将这个标号当做个内存单元来使用(比如与寄存器互传数据、自增、自减、直接传入数据),或者比如我们的标号是a(假设地址为2000:0),那么a[si]的意思就是2000:[0+si]。【我们可以使用a[bx+si+idata]】

–需要注意:带有冒号的地址标号只可以在代码段中使用的。

–我们也可以对数据标号使用xxx ptr来限定其类型,这在有时数据类型不匹配时很好用。

-【16.2  在其它段中使用数据标号】

–注意:需要在assume将标号所在段与段寄存器连接起来,否则编译器在编译时会找不到。【这是编译器工作需要,而不是程序运行需要】

–数据标号代表着对应寄存器的段地址与偏移地址,比如,我们在assume中将data段对应到es,那么我们下面使用这里的数据标号时,段地址就是执行该指令时的实际es中的段地址,无论其是否正确,所以我们在代码中应当先将data段段地址给了es,这样才能将开辟出来的空间与指向的空间匹配。

–seg操作符(segment),功能为取得某一标号的段地址。

-【16.3  直接定址表】

–我们使用数据标号来建立一个表(可以存放数据,或者是偏移地址),再将与我们要解决的问题相关的数据通过一定的方式转化为正确的序号(从0开始),得到一种对应关系。

-【16.4  使用直接定址表】

–提示:我们可以使用直接定址表来设置一个子程序选择机制,将直接定址表中存储子程序偏移地址。需要注意的时,这些偏移地址是在编译时就会被固定,所以,无法在运行期变更,请一定不要在运行期对代码位置进行随意更改,这样会导致访问不到正确的指令。【我们可以使用一种等价变换——即变换后与变换前具有相同的偏移地址,这种方法一般被我们用来设计中断例程】

【笔记】8086汇编-外中断

前言:本篇仅仅是笔记,整理于2017/9/4,结构可能会略有混乱,见谅。

-引入:CPU除了能够运算外,还能够对外部设备进行控制,也就是说,它除了有运算能力外,还要有I/O能力。【两个问题:CPU如何知道外部输入发生、CPU从哪里得到外设的输入】要及时处理随时可能发生的外部输入,就需要我们的外中断。这一章我们以键盘输入为例。

-【15.1  接口芯片的端口】

–外设的输入不是直接传给计算机的,而是先送入相关的接口芯片的端口中;对称的,CPU向外输出也是要经过端口,再由端口给芯片,再给外设输出【CPU控制外设也是同理】

–综上所述,CPU通过端口和外部设备进行联系。

-【15.2  外中断信息】

–当有外部输入时,相关芯片向CPU发送中断信息,CPU在执行完当前指令后,可以检索中断信息,引发中断。

–PC中,外中断源一共有以下两类:

—1)可屏蔽中断

—-这种中断并不是一定会得到CPU的响应,CPU检测到这种信息时,如果IF=1,CPU会执行完当前指令后中断,而IF=0时,可不响应中断。【IF(interrupt flag),指示是否响应中断】【顺便补充前面的遗漏,TF(tarp flag)陷阱标志,指示是否单步调试】

—-这种外中断与内中断的流程相似,过程:总线传入中断类型码→标志寄存器入栈,IF=0,TF=0→CS、IP入栈→CS与IP正确变更【通过中断向量表,访问中断向量表仍然是将中断类型码×4得到向量表的IP位置,再加2得到中断向量表的CS】

—-现在我们可以解释为什么会将IF设置为0了,因为进入中断处理程序后,当然需要禁止其它的可屏蔽中断,不过,8086CPU也提供了设置IF的指令【所以让中断例程响应可屏蔽中断也是可以的】:

—–sti,设置IF为1

—–cli,设置IF为0

—2)不可屏蔽中断

—-不可屏蔽中断会在CPU执行完当前指令后立即响应,引发中断。

—-对于8086CPU,不可屏蔽中断类型码固定为2,所以中断过程中不需要取得类型码,则其中断过程为:标志寄存器入栈,IF=0,TF=0→CS、IP入栈→(IP)=8,(CS)=(0AH)。

–几乎所有外设引发的外中断,都是可屏蔽中断。不可屏蔽中断,是在系统中有必须处理的紧急情况发生时用来通知CPU的中断信息,这里主要讨论可屏蔽中断。

-【15.3  PC机键盘的处理过程】

–①键盘输入:键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描。按下一个键,开关接通,芯片产生扫描码(用来描述位置),扫描码会被送入主板上相关接口芯片的寄存器中,这个寄存器端口为60h。松开与按下,都会产生一个扫描码【扫描码是1个字节的数据】,说明了松开或按下的键在键盘上的位置。

–一般我们将按下一个键的扫描码称为通码,松开一个键产生的扫描码称为断码。通码与断码区别在于第7位,通码的第7位是0,断码的第7位是1,也就是说:断码=通码+80h。

–需要注意:shift的扫描码分键盘左右shift,而alt则不分左右。

–②引发9号中断:键盘输入到达60h端口时,相关芯片就会向CPU发出中断类型码为9的可屏蔽中断,如果这时IF=1,CPU检测到中断后就会响应。

–③执行int 9中断例程:BIOS提供了int 9 中断例程,用来进行基本的键盘输入处理,主要工作如下:

—1)读出60h端口的扫描码

—2)处理方式:

—-字符键扫描码:将扫描码与对应字符码(ASCII)送入BIOS键盘缓冲区(这个缓冲区可以存储15个键盘输入);

—-控制键(如ctrl)和切换键(如CapsLock):将其转变为状态字节(用二进制位记录控制键和切换键状态的字节),写入内存中存储状态字节的单元(0040:17)

—3)相关控制:BIOS键盘缓冲区是系统启动后,BIOS用于存放int 9中断例程所接受的键盘输入的内存区,如上述,可以存放15个键盘输入,每个键盘输入用一个字单元来存储(扫描码和ASCII码),高位存放扫描码,低位存放字符码。键盘状态字节各位记录如下:

—-0:右shif。1:左shift。2:Ctrl。3:Alt。【1表示按下】

—-4:ScrollLock。5:Numlock。6:CapsLock。7:Insert。【1表示开启】

-【15.4  编写int9中断例程】

–梳理一下处理过程:产生扫描码→进入60h端口→引发9号中断→CPU执行int 9中断例程处理键盘输入。只有最后一步,是我们能够在不改变硬件的情况下进行改变。

–我们想要让我们的中断例程能够读取ESC,功能如下:

—1)从60h端口读出键盘的输入

—2)调用BIOS的int 9中断例程,处理其他硬件细节

—3)判断是否为ESC的扫描码,如果是,改变显示颜色后返回;如果不是则直接返回。

–细节问题:我们想要在新的程序中调用原来的int 9h中断例程,所以我们需要将其原始地址提前保存。并且我们需要模拟中断调用:

—标志寄存器入栈→IF、TF=0、call dword ptr ds:[0]

—关于这里同时修改TF、IF,我们可以通过pushf后将其读入ax,在对ah使用and指令【11111100b】,再入栈,再popf

—不过,由于我们是在我们自己的中断例程调用的原中断例程,所以IF、TF已经被设置为0,所以我们可以略过设置IF、TF这个步骤,直接进行pushf,再call就可以了。

—我们还应当注意一个问题,那就是:在设置int 9中断例程的段地址和偏移地址的指令间也可能发生中断,这时CPU会转去了错误地址,所以,我们应当使用sti与cli指令,准去来说,在设置前使用cli,设置后使用sti,这样可以屏蔽掉可屏蔽中断。

-【15.5  安装新的int 9中断例程】

–和以往的安装中断例程类似,都要安装在0:200处,不过这里,我们建议先将原来的int 9向量存放在0:200和0:202,从204开始存放代码。

—关于备份旧的向量,我们可以直接使用栈来操作,这样是最便捷的:

—-push es:[9*4]

—-pop es:[200h]

—-push es:[9*4+2]

—-pop es:[202h]

–关于新地址设置:

—由于与外中断有关,所以我们这样写【原因见15.4最后一点】:

—-cli

—-mov word ptr es:[9*4],204h

—-mov word ptr es:[9*4+2],0

—-sti

-【总结】

–端口和中断机制,是CPU进行I/O的基础

【笔记】8086汇编-端口

前言:本篇仅仅是笔记,整理于2017/9/4,结构可能会略有混乱,见谅。

-CPU通过总线相连的芯片除了各种存储器外,还有以下3中芯片:

–1)各种接口卡(如:网卡、显卡)上的接口芯片,他们控制接口卡工作

–2)主板上的接口芯片,CPU通过他们对部分外设进行访问

–3)其他芯片,用来存储相关的系统信息,或进行相关的输入输出处理。

-这些芯片中,都有一组可以由CPU读写的寄存器。这些向寄存器,它们在物理上可能在不同的芯片中,但它们在以下两点上相同:

–1)都和CPU的总线相连【即通过其所在的芯片来连接】

–2)CPU对它们进行读或写的时候都通过控制西安向它们所在的芯片发出端口读写命令。

-也就是说,CPU将这些寄存器都当做端口,对它们统一编址,从而建立了一个统一的地址空间

-以上,CPU可以从3个地方读写数据:

–1)CPU内部寄存器

–2)内存单元

–3)端口。

-【14.1  端口的读写】

–不可以使用mov、pop、push等指令,只可以使用in和out指令

–在PC系统中,CPU最多可定位64KB不同端口,则端口地址的范围为:0~65535.

–访问过程与我们对普通寄存器读写时类似,这里用读来举例,如果我们输入in al,60h,就意味着:通过地址总线发送60h来定位,通过控制总线告知将读,通过数据总线将数据传入。【这与我们之前mov ax,ds:[8]这样的指令的过程十分类似——地址总线定位→控制总线安排→数据总线传输】

–in与out指令只能够使用ax或al,访问8位端口使用al,访问16位端口使用ax。

–对0~255以内的端口进行读写,可以直接输入地址:

—in al,20h

—out 20h,al

–对256~65536端口进行读写,需要用dx存储端口号:

—mov dx,3f8h

—in al,dx

—out dx,al

-【14.2  CMOS RAM芯片】

–PC机中,一般有一个CMOS RAM芯片,一般简称CMOS。

–特征:

—1)包含一个实时钟,和一个有128个存储单元的RAM存储器(早期的计算机为64个字节)

—2)该芯片靠电池供电,关机后内部的实时钟仍然可以正常工作,RAM中信息不丢失。

—3)128的RAM中,内部实时钟用0~0dh单元保存时间信息,其余大部分单元用于保存系统配置信息,供系统启动时BIOS程序读取。BIOS也有提供程序使我们可以在开机时配置CMOS RAM中的系统信息。

—4)该芯片内部有两个端口,端口地址为70h和71h,CPU通过这两个端口来读写CMOS RAM。

—5)70h为地址端口,存放要访问的CMOS RAM单元的地址;71h为数据端口,存放从选定的CMOS RAM单元中读取的数据,或要写入的数据。CPU对CMOS RAM的读写份两步进行:(这里用2号单元举例)

—-①将2送入端口70h

—-②从端口71h读出2号单元的内容

–【注意:out是指向端口传数据,in是指从端口读数据】

-【shl(shift left)和shr(shift right)指令】

–这两个是逻辑移位指令。

–shl是逻辑左移指令,功能:

—将一个寄存器或内存单元中的数据左移位→将最后移出的一位写入CF中→最低位用0补充。

–语法:shl 寄存器/内存单元,移位个数

–shr指令:

—右移(右移过程相当于除以2【但实际结果可能不是,因为有移出的一位,这位可能会导致结果-1】)→尾部移出的一位放到CF中→最高位补充0

–注意:如果移动次数超过1的话,要使用cl来存储。

–【检测点14.2】

—编程,用加法和移位指令计算(ax)=(ax)*10。

-【14.4  CMOS RAM中存储的时间信息】

–在CMOS RAM中存放着当前的时间:年(9)、月(8)、日(7)、时(4)、分(2)、秒(0)【括号内是存放单元】,长度都是一个字节。

–这些数据以BCD码的方式存放,BCD码是4位二进制数与1位十进制数的对应关系,【注意:四位二进制数能够表示的数的范围比1位十进制数能够表示的范围要大】,从数字0到9对应的二进制数,分别是其自身的等值二进制数。

–在存放数据时,高4位表示BCD码的十位,低四位表示BCD码的个位。

–编写程序:在屏幕上显示月份

—将高4位于低4位分离的巧妙办法:用al读取,并复制给ah,之后ah右移4位,这样就可以完成放置工作

-【实验14  访问CMOS RAM】

–警告:不要向不了解的单元写入内容,否则可能引起一系列系统错误。

【笔记】8086汇编-int(interrupt)指令

前言:本篇仅仅是笔记,整理于2017/9/4,结构可能会略有混乱,见谅。

-【注意:内存单元与数字发生关系时,必需要限定数据占据的位数】

-汇编指令缩写:http://www.cnblogs.com/ljtknowns/p/5741341.html

-这一章我们讲解int指令引发的中断

-【int指令】

–语法:int n

–作用:相当于引发一个n号中断过程。

–我们可以将中断处理程序简称为中断例程。

–这既提供了方便,却又降低了dos的安全性。

-【13.2  编写供应用程序调用的中断例程】

–问题一:编写、安装中断7ch的中断例程。

—功能:求一word型数据的平方

—参数:(ax)=原始数据(即底数)

—返回值:dx、ax中存放结果的高16位和低16位。

–问题二:编写、安装中断7ch的中断例程。

—功能:将以0结尾的字符串转化为大写。【C、C++的字符串都是这种以0结尾风格】

—参数:ds:si指向字符串的首地址。

-【13.3  对int、iret和栈的深入理解】

–我们现在要用int来模拟loop指令:我们用cx存储循环次数,bx存储位移。

–关键:iret实际上是是出栈操作,所以我们可以直接在栈中,将原来的CS、IP进行修改。

–我们这里最好是使用bp来代替sp操作,这样可以防止意外情况将sp弄乱(结果造成严重错误),我们这里的方法是:将bp入栈(因为要还原),将sp给bp,通过bp来定位IP在栈中的位置,修改IP(实现段内近转移),还原bp,iret中断返回。

-【13.4  BIOS和DOS所提供的中断例程】

–BIOS(basic input output system)是存放在系统板的ROM中的一套程序,其中主要包含以下部分内容:

—1)硬件系统的检测及初始化程序

—2)外部中断和内部中断的中断例程

—3)用于硬件设备进行I/O操作的中断例程

—4)其它和硬件系统相关的中断例程

-【13.5  BIOS和DOS中断例程的安装过程】

–引入:我们之前是通过安装程序来完成安装到内存中这一操作的,那么BIOS和DOS提供的中断例程是如何安装到内存中呢?

–1)开机后,CPU加电,初始化(CS)=0FFFFH,(IP)=0,自动从这个位置开始执行程序,这里有一条跳转指令,CPU执行后,转去执行BIOS中的硬件系统检测和初始化程序。

–2)初始化程序会建立BIOS所支持的中断向量,即将BIOS提供的中断例程的入口地址登记在中断向量表中。【因为这些中断例程存放在ROM中,所以它们能够持续存在,只需要进行定位即可】

–3)硬件系统检测和初始化完成后,调用int 19h进行操作系统的引导,从此计算机交给操作系统来控制。

–4)DOS启动后【这里的操作系统是DOS】,除完成其它工作外,还将它提供的中断例程装入内存,并建立相应的中断向量。

–检测点13.2结论:

—我们无法通过直接的编程来改变FFFF:0的指令,而使CPU不执行BIOS的硬件检测程序。

—int 19h执行前,DOS还未获得控制权,所以int 19h的中断例程不可以由DOS提供。

-【13.6  BIOS中断例程应用】

–int 10h是BIOS提供的中断例程,其中包含了多个和屏幕输出相关的子程序。

–【使用ah来确定子程序,ah为2表示放置光标,ah为9表示字符输出】

–一般一个中断例程,提供了许多的子程序。

–【int 10h的子程序使用dh存储行,dl存储列,这里我们可以联想记忆h=行,l=列】

–BIOS和DOS提供的中断例程,都用ah来传递内部子程序的编号。

-【13.7  DOS中断例程的应用】

–int 21h就是DOS提供的中断例程。

–我们最常使用的就是它的4ch号功能,详解:

—ah放置功能的序号,al存放返回值,一般0表示正常返回。

–我们来看一下int 21h显示字符串的功能:

—ds:dx指向字符串,字符串以’$’结尾【与C语言的方式不同】。

—mov ah,9,因为这个9号程序就是在光标处显示字符串。【所以我们可以用上一节的内容定位光标】

—这个字符串输出能够在行尾时自动向下一行,而达到最后一行时,还可以自动上卷,十分方便。

【笔记】8086汇编-内中断

前言:本篇仅仅是笔记,整理于2017/9/4,结构可能会略有混乱,见谅。

-中断可能来自于计算机内部和外部,我们这里只讨论内中断。

-内中断是不可以屏蔽的,因为内中段常常反映了异常工作。

-【12.1  内中断的产生】

–对于8086CPU,当CPU内部有以下情况发生时,会产生中断信息:

—1)除法错误,比如:执行div指令产生的除法溢出;【中断类型码:0】

—2)单步执行;【中断类型码:1】

—3)执行into指令;【中断类型码:4】

—4)执行int指令。【int n,其中的n为字节型立即数,是提供给CPU的中断类型码】

–8086CPU使用中断类型码来标识中断信息的来源。中断类型码是一个字节型数据,可以表示256种中断信息的来源。【为了方便,以后,我们将中断信息的来源,简称为中断源】

-【12.2/3  中断处理程序、中断向量表】

–我们知道,中断后CPU要执行一定的程序来处理这一中断信息,这种程序我们称为中断处理程序。

–我们需要用8位中断类型码来定位对应的中断处理程序,我们创造并使用中断向量表来建立这种对应关系。

–所谓中断向量,就是中断处理程序的入口地址。

–中断向量表就是中断处理程序入口地址的列表。

–中断向量表在内存中保存,存放着256个中断源所对应的中断处理程序的入口。

–8086CPU,内存0000:0000~0000:03FF的1024个单元中存放着中断向量表,中断向量表固定放在这里,一个表占4个字节(1024/256),高地址字存放段地址,低地址字存放偏移地址。

–存储N号中断源对应的中断处理程序入口的偏移地址的内存单元(也就是入口地址的位置)为:4N

–存储N号中断源对应的中断处理程序入口的段地址的内存单元地址为:4N+2.

-【12.4  中断过程】

–中断过程是由CPU的硬件自动完成的,这个过程是:用中断码找到中断向量,并用它设置CS和IP。

–另外,在执行完中断程序后,应当回到原来的位置继续向下执行,所以在设置CS、IP前,应当将原来的CS、IP储存起来。

–中断过程:

—取得中断类型码→标志寄存器的值入栈(因为中断过程中要改变标志寄存器的值)→设置标志寄存器的TF(8位)、IF(9位)的值为0→CS入栈→IP入栈→从对应位置读取IP与CS。

-【12.5  中断处理程序和iret指令】

–中断处理程序与中断向量表是需要时刻存储在内存空间中的,因为CPU随时都可能执行中断处理程序。

–关于中断处理程序的编写,主要有以下四块:1)保存用到的寄存器;2)处理中断;3)恢复用到的寄存器;4)使用iret指令返回【iret相当于:pop IP→pop CS→popf】。可以看出,这个框架与子程序的编写框架十分类似。

-【12.6  除法错误中断处理】

–我们试验如下程序:

–mov ax,1000h

–mov bh,1

–div bh

-执行完第三行后,显示提示信息:”Divide overflow”

-【12.7  编程处理0号中断】

–我们只要将中断向量表中的数据设置为指向中断处理程序就可以了。

–我们在这里不想通过向操作来申请空间,所以我们要将中断处理指令复制到一个固定的位置(下面简称:安装),我们前面讲的安全的空间就可以满足需求(0:200~0:2FF),由于没有那么多的中断向量,这段空间便被空了出来。

-【12.8  安装】

–我们使用rep movsb来进行安装,es:di指向目的地址,ds:si指向原始地址,cx设定传送长度。

–之后再设定中断向量表。

–这里有个关键问题:如何知道要传送的长度?

—我们的解决方法是,将起始位置设置一个标号a,然后将结束的位置后加一行nop指令,并将另一个标号b设置在这里,我们使用mov cx,offset b – offset a就可以知道要复制的长度了。【编译器能够进行四则运算(加减乘除),可以直接用运算符号来表示】

–我们还要注意:要输出的字符不可以放在一个单独的段中,因为一个段中的空间,在程序结束时会被回收,我们需要将要显示的字符串也放到刚才我们找到的安全空间,所以我们可以在要复制的程序的开头加上jmp指令,下一行加上db来定义字符串,而前面的jmp用来跳过这个字符串定义过程。

–【个人想到:如果代码段的开头加上jmp,然后下一行定义的是一系列数据,而jmp来跳过这些数据,这样在实际运行时,就无法通过开头来知晓所有的指令了,以此起到伪装作用】

–【注意:jmp short指令只占两个字节】

-【12.10  设置中断向量】

–这步十分简单,只要将IP、CS以字型数据存放到对应的位置即可。

-【12.11  单步中断】

–产生方式:CPU执行完一条指令后,如果检测到标志寄存器的TF位为1,则产生单步中断。【或者说,当TF变成1,再执行一条指令后,进入单步中断】

–单步中断的中断类型码是1,CPU取得中断类型码1后,会进行中断过程(包括一系列的入栈、TF, IF归零、CS, IP重指向)。

–实际上,当我们在debug中使用t命令时,Debug就会将TF设置为1,执行一条语句(TF=1时执行一条语句就会引发单步中断),自然引发单步中断,之后所有寄存器中的内容被显示在屏幕上,等待输入指令。

–为了避免单步中断进入死循环,当进入单步中断时,TF会自动变成0.

-【12.12  响应中断的特殊情况】

–有些情况,当CPU执行完当前指令,即便发生中断,也不会响应。我们这里仅举一例。

—我们之前就遇到过,当执行mov ss,ax之后,下一条mov sp,16并不会在debug中显示,这是因为,在执行完向ss寄存器传送数据的指令后,即便发生中断,CPU也不会响应,这是为了防止因为栈还未配置好,就进行中断过程的入栈操作【如果这样,必然会引发错误】。不过这种机制只有在改变ss的指令后紧跟着改变sp的指令时才会触发,所以我们的设计应当将修改sp的指令放在修改ss的指令紧下方。

【笔记】8086汇编-标志寄存器

前言:本篇仅仅是笔记,整理于2017/9/4,结构可能会略有混乱,见谅。

-引入:

–这是一类特殊的寄存器,具有以下3中作用:

—1)用来存储相关指令的某些执行结果

—2)用来为CPU执行相关指令提供行为依据

—3)用来控制CPU的相关工作方式

-在8086CPU中,标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW, program state words)

-本章将标志寄存器简称为flag(flags register)

-标志寄存器的特点是:按照位来起作用,每一位有不同的含义

-在8086CPU中,1、3、5、12、13、14、15位是没有被使用的,因而无含义;而0、2、4、6、7、8、9、10、11位是有特殊意义的。

-【11.1  ZF(zero flag)】

–flag的第6位是ZF,零标志位。它记录相关指令执行后,其结果是否为0。为是,则ZF为1,反之ZF为0。

–可以记忆为它包含了“结果为0”这一命题,ZF的数值表示了真假,一般1为真,0意假。

–运算指令会直接影响这个寄存器。【在8086CPU的指令集中,有的指令会直接影响标志寄存器,比如:add、sub、mul、div、inc、or、and等,它们大多都是运算指令(进行逻辑或算术运算);而有的指令的执行对flag没有影响,比如mov、push、pop等,它们大多是传送指令。】

–【在使用一条指令的时候,要注意这条指令会对标志寄存器的哪些位造成影响】

-【11.2  PF(parity flag)】

–第2位

–判断执行结果的1的个数是否为偶数。是为1,否为0。

-【11.3  SF(sign flag)】

–第7位,符号标志位

–判断条件:结果为负?(是1,否0)【判断最高位是否为1】

–【重要,关于补码实用技巧】对于有符号数:取反后加1就可以实现正负数转换,要注意,取反后加1与减1后取反得到的结果完全相同,所以我们为了使用时不混淆,只要记住取反+1就可以了。

–它的取值首先与数据是否含符号有关,其次又与运算结果有关。

–【一定要注意,传送指令不改变以上三个位的值,比如:mov ax,0虽然让ax变成0,但是ZF、PF、SF的值保持不变】

-【11.4  CF(carry flag)】

–flag的第0位是CF,进位标志位。

–记录无符号数是否发生进位/借位。

–在进行无符号数运算时,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。

–CF记录的是进位或借位这一动作,只要有借位或进位,则记录为1,反之则记录为0。

-【11.5  OF(overflow flag)】

–第11位,溢出标志位。

–记录有符号数运算是否发生溢出。

–【注意区分CF和OF,CF针对无符号数,OF针对有符号数。】

–【计算机分不清是否是有符号数运算,所以它将CF和OF都进行记录,而对于程序员,则可以根据需要来选择性观看(观看CF意味着它是无符号数,观看OF兼且SF意味着它是有符号数)(计算机需要两种都记录,将未知类型问题转化为条件讨论,即同时研究两个条件导致的结果)】

-【事实上,计算机没有区分有无符号数,它的运算法则唯一,永远是逢二进一,这会产生这样一个结果:假设是8位寄存器,(这句话是错误的)当6号位改变时,(错误)就会将OF改变为1,而当7号位产生进位/借位时,就会将CF改变为1,虽然这只是在8位寄存器情况下的讨论,但是对于不同大小的寄存器,这个原理是类似的。】

-要注意的是:并不是第6位改变,OF就改变,比如:FFF0h+10h会使第6号位改变,但不溢出,因为这里仅仅是-1+16,没有溢出,所以要注意溢出发生的情况:1)正+正=负;2)负+负=正。

-【11.6  adc指令】

–adc是带进位加法指令,利用了CF位上记录的进位值

–语法:adc 操作对象1,操作对象2

–功能:操作对象1 = 操作对象1 + 操作对象2 + CF

–【十六进制与二进制对应关系:1=1,2=10,3=11,4=100,5=101,6=110,7=111,8=1000,9=1001,a=1010,b=1011,c=1100,d=1101,e=1110,f=1111,在这里,我们发现,当十六进制数最高位是8~f时,代表着对应二进制数最高位是1】

–这样的相加模式关键是用来进行低位到高位的进位,比如,我们想让ax与bx相加,这样我们可以使用add al,bl与adc ah,bh,这样可以达到同样地效果。因此我们可以使用多个寄存器来表示位,比如我们可以将ax表示为最低16位,bx表示为次高16位,dx表示位最高16位。

–有时要注意CF的置0。

–子程序编写,128位数相加,对应test49,其实现与教材要求不同,需要将两个数连续存放。

—【注意:在改变si的值时,一定不要使用add si,2因为这样会改变CF的值,而应当使用不会改变CF值的inc指令(inc与loop都不会改变CF的值)(注意:并不是说inc不改变任何值,inc会改变包括但不限于ZF的值)】

-【11.7  sbb指令(borrow 借位)】

–语法:sbb argument1,argument2

–功能:argument1 = argument – argument2 – CF

-【11.8  cmp(compare)指令】

–cmp是比较指令

–语法:cmp argument1,argument2

–功能:计算argument1 – argument2,但是但是不保存结果,仅仅根据结果对标志寄存器进行设置。

–对于程序员,使用起来可以理解为减法,这样是在遗忘比较结果与两个参数关系之间的对应情况时使用,我们要知道,这个指令实际上是反应了两个参数的大小关系吗,比如:如果(ax)>(bx),cmp ax,bx会使CF=0(没有借位),并且ZF=0(不为0)。

–以上在无符号运算时是十分准确的,但是如果对于有符号数,我们无法根据sf=1说明argument1<argument2,因为在有符号且位数有限时,一个正数减去一个负数,可能得到一个负数 (由于溢出),这时sf=1,但arguement1>argument2。

—以上,我们发现只OF也是需要观察的,这样才能判断是否发生溢出,以此来获得正确结果。

—我们要注意:正数相加溢出一定是负数,负数相加溢出一定是正数。

—所以:

—-1)如果sf=1,而of=0,由于of=0没有溢出,所以sf表示的正负是真正意义上的正负,所以这时,a1<a2

—-2)如果sf=0,of=0,则a1>a2

—-3)如果sf=1,of=1,则a1>a2【相当于结果相反】

—-4)如果sf=0,of=1,则a1<a2

–无论有无溢出,zf的判断是绝对的,zf=0则a1≠a2,zf=1则a1=a2。

–8086CPU这种工作机制的设计思想,对于很多处理机来说是普遍的,我们应当留意这种设计思想。

-【11.9  检测比较结果的条件转移指令】

–所有条件转移指令(如jcxz【jump cx zero】)的转移位移都是[-128,127]

–还有许多的条件转移指令,它们都是针对标志寄存器位的,并且,我们常常将cmp指令与它们配合使用。

–对于无符号数,我们常常观察zf、cf,使用:je[jump equal],jne[jump not equal],jb[jump below],jnb,ja[jump above],jna。

–【编程思想】【等价转化问题来简化代码】在编写程序时,我们不一定要完全遵循要求,比如,要求我们判断一段内存中等于8的数据的个数,这时,我们并不需要严格地来判断这个数据是否等于(je)8,而可以判断其不等于(jne)8,我们可以在jne指令下一行进行统计数据的增加,再下面的指令加上标号,这样我们可以将jne指令设定为跳转到这个标号下,也就是说,如果不等于8,我们直接跳过“累计增加”这一操作,直接进行下一次循环,这样的编写有时能够减少我们的代码量。

–对于有符号数的条件转移指令,原理与无符号的相同,只是检测了不同的标志位,我们这里仅仅是讨论cmp、标志寄存器相关位、条件转移指令三者配合应用的原理,这个原理具有普遍性【在使用时关注其逻辑上的对等关系(如je表示cmp的等于),又明白原理是对标志位的检测】,其它的指令可以查看相关的指令手册。

–在使用cmp时,如果要访问内存单元,记得要限定读取类型(如:byte、word、dword)

–我们在这里可以用一个逆转思想:不是想如果什么条件会执行什么,而是想如果不满足某一条件就跳出【重要设计思路】,这样我们可以在主线上设计好全部内容,在相应内容设计不满足条件时的跳出,将地址指向这一部分的末尾(比如:有时可能是循环体的最后)。

-【11.10  DF(Direction flag)标志和串传送指令】

–第10位,方向标志位。

–决定si与di的变化。

—当df=0,一次传送后,si、di会增加

—当df=1,依次传送后,si、di会减少

–指令:movsb【movs byte】、movsw【movs word】

–作用将字(byte)节型(字(word)型)数据从ds:si处传送到es:di处,之后进行si、di数值的改变(改变方式由df值决定)。

—常常配合rep(意为repeat)使用:rep movsb、rep movsw。【注意不要与我们临时用来表示寄存器的reg混淆】

—-作用:根据cx中存放的次数,循环传送

–设置DF值:CLD(clear DF)将DF设置为0,STD(set DF)将DF设置为1。

-【11.11  pushf(push flag)和popf(pop flag)】

–作用:将标志寄存器的值入栈,或将栈中的数据弹入标志寄存器。

–意义:为直接访问标志寄存器提供了一种方法。

-【11.12  标志寄存器在Debug中的表示】

【备注:以下圆括号内单词仅仅为猜测】

–of:OV(overflow)/NV(not overflow)【斜杠左边代表1,右边代表0】

–sf:NG(negative sign)/PL(plus sign)

–zf:ZR(zero)/NZ(not zero)

–pf:PE(parity even)/PO(parity odd)

–cf:CY(carry)/NC(not carry)

–df:DN(down)/UP(up)

-【实验11  编写子程序】

–记得要定义栈段。

-【注意】

–串传送前记得要设置DF

【笔记】8086汇编-CALL和RET指令

前言:本篇仅仅是笔记,整理于2017/8/18,结构可能会略有混乱,见谅。

-call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计。

-【10.1 ret和retf】

–ret使用栈中的数据,修改IP内容,实现近转移

–retf指令使用栈中的数据,修改CS和IP的内容,实现远转移

–ret指令的操作

—1)读取当前栈中的数据

—2)(sp)=(sp)+2

–retf指令的操作:相当于连续出栈两次,第一次出栈的是IP,第二次出栈的是CS。【也就是说:后入栈的将会被作为IP的值,先入栈的会被作为CS的值】

-【10.2 call指令】

–操作:

—1)将当前的IP或CS和IP入栈【注意,这里指的是call指令后的第一个字节的地址】【注意入栈顺序:CS先入栈占据高位,IP后入栈占据低位】

—2)转移

–call指令不能实现短转移,只能实现近转移和远转移

-【10.3 依据位移进行转移的call指令】

–和原来的jmp一样,16位位移=标号处的地址-call指令后第一个字节的地址,位移使用补码来表示,位移在编译时算出。

–语法:call 标号

—使用相当于先进行push ip,在执行jmp short ptr 标号

-【转移的目的地址在指令中的call指令】

–call far ptr 标号  实现的是段间转移

–执行步骤相当于:

—push CS

—push IP

—jmp far ptr 标号

–【注意:这一情况下,目的地址会出现在机器码中,低地址是IP,高地址是CS】

-【转移地址在寄存器中的call指令】

–语法: call 16位reg

–等价于:

—push IP

—jmp 16位reg

–这个实现的是段内近转移,转移的IP地址存储在ax中。

-【10.6  转移地址在内存中的call指令】

–语法:

—1)call word ptr 内存地址【段内转移】

—2)call dword ptr 内存地址 【段间转移】

–解释:

—读取word(16位)的指令只改变IP,而读取dword的指令依次改变IP、CS【CS放在高地址位】

-【10.7  call与ret配合使用】

–使用意义:我们使用call与ret配合,可以实现主程序与子程序的跳转,当我们想要跳转到子程序中,我们可以使用“call 子程序标号”,在子程序中设置最后一个指令为ret,这样在执行完毕子程序后会自动跳转回主程序。

–根据习惯,我们不妨将主程序标号名称设置为main,子程序标号可以自定义名称,或简单地设定为“sub1”等。

–→获得:子程序框架←

-【10.8  mul指令】

–作用:乘法

–两个相乘的数:要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg或内存字节单元中;如果都是16位,则一个默认AX,另一个放在16位reg或内存单元字型数据。

–结果:8位相乘在AX;16位相乘高位在DX,低位在AX。

–注意:可以将8位数通过补0来变成16位。

-【10.9  模块化程序设计】

–下面来看相关解决方法:

-【10.10  参数和结果传递的问题】

–关键解决:传递参数和返回值。

–【对于子程序,建议做好备注,这样方便日后理解,并且也可以便于他人使用】

–解决方法:定义两个寄存器,一个当做参数寄存器,另一个当做结果寄存器。

-【10.11批量数据的传递】

–在特殊的情况下,我们可以这样设计:将要批量处理的数据的首地址放在si中,然后将cx也在主程序中设置好(可以理解为这是一个需要两个参数数值的函数,这两个参数分别是首地址和数据长度),在子程序中设计循环。【比如我们要将全部的小写字母设置为大写字母】

–在大多情况下,我们使用栈来传递参数,C语言中往往也是这样,在进入函数时,我们可以将现有数据全部入栈,子程序返回时会全部出栈。

-【10.12  寄存器冲突的问题】

–我们可以将一个字符串最后定义一个0作为结尾,所以我们可以使用jcxz来跳转(在ret【return?】前加一个标号,跳转到这里),而不用实现知道字符串有多长。【高级语言中以\0结尾(只是C与C++?)】

–【关于此处的一个备注:由于要操作字符,请一定要将al存上字符的值,ah设置为0,也就是说要分两步操作,而不可以直接mov ax,byte ptr [bx],这样会得到一个“类型不匹配”的warning,并且,程序将无视我们限定的“一个字节”这个要求,而是直接读取了两个字节】

–对于连续的多个字符串,我们可以加上循环,循环次数就是字符串总数。【注意不要忘了将cx记录的循环次数提前入栈】

–我们发现了一个一般性的问题

—很多情况下,我们在子程序中使用的寄存器在主程序中也会被用到,这样很容易发生冲突现象,造成运行时的错误,我们需要寻找一个万用方案,或者说“通解”。

—解答:我们不想麻烦地分析哪个寄存器被使用,哪个没有,在大型程序中,这样的分析也不现实,所以我们使用了栈来临时存放这些数据,为此我们提出了一个常用框架。

—框架:

—-子程序开始:子程序中使用的寄存器入栈

—-                    子程序内容

—-                    子程序中使用的寄存器出栈

—-                    程序返回

—注意栈的特点LIFO

-【实验10  编写子程序】

–【子程序1  通用的显示字符串子程序】

—要求:提供灵活的调用接口,使调用者可以决定现实的位置、内容以及颜色。

—名称:print_str

—详细功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串

—参数:(dh)=行号(取值范围0~24),(dl)=列号(取值范围0~79),(cl)=颜色,ds:si指向字符串的首地址。

—返回值:无

—开始设计:首先确定需要的功能:1)正确读取并存放参数,2)正确读取字符串,3)正确显示(存放数据)

—-不妨假设,现在dh、dl、cl、ds、si已经存放好了需要的数据。那么我们知道,彩色字符显示模式显示区域在B8000H~BFFFFH,总共64KB,由于要给定行号和列号,所以我们想先将这个存放在第0页,由于每个字符的属性的存放地址都比字符本身存放地址大1,所以这个1可以通过idata来表示,我们可以使用di确定起始位置,es存放段地址B8000H,那么如何知道具体偏移地址呢,这就要通过dh和dl中的数值进行计算了,由于每行是80列,我们不妨把除最后一列后的行数乘80*2,再加上列的个数数,就可以进行定位,这其中我们应当注意几个细节,首先行数与列数是从0开始的,所以行号=实际行的个数-1,比如在第三行,它的行号是2,所以我们应当直接使用通过dh传递来的行号来乘80*2,而列号应当怎么使用呢?不妨假设现在是在0行,要从第三个(列号为2)列来输出,它的地址应当是列号乘2(因为列号实际上就是表示了前面一共有几个字符存在了,所以只要将行首的第一个字符的起始地址加上中间的内存单元个数,就可以得到现在的内存单元地址了),所以我们知道了(di) = (dh)*80*2 + (dl)*2,由于这是典型的8位乘法,所以我们需要使用ax寄存器,为了减少操作步骤,所以我们先将ax的值设为160,之后使用mul dh,这样就可以在不动dh、dl的情况下进行计算,但是,我们发现一个问题,也就是说,我们计算好了dh*160后,我们还要再算dl*2,但是ax现在已经被占用了,为了方便,我们不妨改进一下流程(虽然临时存储到内存单元中也是可以的,但这里想要设计地尽量不再访问内存单元),我们不妨做出如下改进:1)将al设置为80;2)使用mul dh指令;3)将dl的值加到ax中;4)对dx进行赋值为2(之所以使用dx,而不是dl,是因为我们这里要使用16位与16位相乘,ax是16位的);5)使用mul dx,我们可以估算到,这个乘积最大是40FEh,所以乘积不需要使用dx来存放,我们可以放心地将ax的值放到di中。以上我们关于寻址的设计已经完成,之后我们需要设计一下字符与属性读取及存放的部分程序:

—-我们需要使用循环来读取字符串,这个循环应当包括跳转部分(用来识别0),转存部分,所以我们分开来设计:

—–跳转部分:我们这时有一个想法,那就是,循环的控制应当在这个子程序之外,而跳转程序只需要做一件事——判断是不是0,是0则跳出,不是0则进入转存,所以我们自然要用到jcxz来判断,因此我们会首先将字符传送到cx中(所以我们需要提前将存储着循环次数的cx的值入栈),然后,我们便要紧跟上jcxz指令,跳转到一个标号处,这个标号指向了ret或retf指令(具体是哪一个,要再后边讨论,如果段内转移能够满足要求,那就使用ret即可),而在jcxz指令后边,则应当是访问转存指令,然后这条指令的下一个指令就是刚才的ret或retf。以上,跳转部分设计完毕,现在重新来观察,我们发现,这个跳转部分实际上是实现了判断一个字符是否为0的功能。

—–转存部分:当进入转存部分时,cx中一定存放着字符数据,所以我们首先将cx值存放到之前准备好的es:di中,然后我们再将属性存到es:[di+1]中,可是这时我们发现,原先的颜色属性是在cl中存放的,而刚才跳转部分的设计已经将这个覆盖了(因为jcxz必须要判断完整的cx的值),我们不妨对跳转部分的设计进行修改,让跳转部分在最开始时,先push cx,然后再jcxz指令之后,再使用pop cx,之后再进入转存部分,可是这样的话,我们又会发现,刚刚字符数据不见了,并且这个设计再每次循环中都要多次访问内存,这会使计算机处理所需时间增长,这显然不是我们想要的;我们不妨在进入跳转部分前就将cl的值给另外一个寄存器来存放,比如在循环存放步骤中没有被使用到的al,至此问题解决了,我们先在跳转中将cl设置为字符数据,在转存部分先存cl,再存al,之后离开转存部分,回到跳转部分的开头处。(因为既然这个字符进入了转存部分,那么这个字符一定不是最后一个字符)

—-我们总结一下这个子程序:

—–栈的定位→入栈部分→存储地址计算部分→循环部分→出栈部分→返回部分

—–这个子程序中,我们使用到了ax,bx,cx,dx,si,di,ds,ss,es。其中,ds是没有被改变的,我们不需要将它入栈,其它的部分都进行了一定程度的改变,所以我们在子程序最开始部分,要执行一个入栈部分,这个栈应当足够大,根据我们要存储的数据,发现需要18个字节,所以我们将这个栈的大小声明为32个字节。

—–存储地址计算部分已经如上所述,返回结果是,di中存放了正确的偏移地址。

—–循环部分:

——循环部分最开始应当是循环初始化操作,我们将cl存储的属性值传给al,然后用cx设定循环次数【这里出现了错误,由于我们要使用跳转来判断,所以不需要循环次数】,然后访问跳转部分(转存部分由跳转部分内部进行跳转),之后就是参数变化部分,这一部分,我们要让si自增1,di自增2。

——之后就是循环部分的返回部分

—–子程序的出栈部分

—–最后就是这个子程序的返回部分

–子程序3:数值转字符:

—ASCII有这样的规律:数值+30H = 数字的ASCII码

—我们可以先按照倒过来的顺序存放,之后再进行一次倒序,这样就可以得到正确顺序的字符串。

【笔记】8086汇编-转移指令的原理

前言:本篇仅仅是笔记,整理于2017/8/18,结构可能会略有混乱,见谅。

-定义:可以修改IP,或同时修改CS和IP的指令统称为转移指令。

-8086CPU转移行为有以下几类:

–段内转移:一个只修改IP的转移行为(jmp ax是其中一个实现方式)

–段间转移:同时修改CS和IP的转移指令

-由于转移指令对IP的修改范围不同,段内转移分为:

–短转移IP的修改范围:-128~127

–近转移IP的修改范围:-32768~32767

-8086CPU的转移至零分为以下几类:

–无条件转移指令(如:jmp)

–条件转移指令

–循环指令(如:loop【备注:loop会判断cx是否为0,如果不为0,则会跳转到标号处】)

–过程

–中断

-【9.1 操作符offset】

–操作原理:由编译器处理的伪指令

–功能:取得标号的偏移地址

–使用方法示例:

—s: mov ax, offset s

–【备注:这里的问题9.1涉及到了一个“无效”指令nop,它没有特殊效果,机器码占据一个字节】

-【9.2jmp指令】

–jmp为无条件转移指令,可以只修改IP,也可以同时修改CS和IP。

–jmp指令给出了两种信息:

—1)转移的目的地址

—2)转移的距离(段间转移,段内短转移,段内近转移)

–【备注:不同的给出目的地址的方法,对应了不同格式的jmp指令,也就有不同的距离】

-【9.3 依据位移进行转移的jmp指令】

–jmp short 标号(转移到标号处执行指令)

—这实现的是段内短转移

–我们发现,汇编指令中的idata,总会出现在对应的机器指令中。

–但是,这里我们发现了一个问题,便是jmp short 标号这个指令的机器码并不包含绝对的ip值,并且,在我们将jmp指令之前的指令进行修改后,这里的指令的机器码仍然没有改变。

—原因:在这个指令所对应的机器码中,不包含转移的绝对地址,而是包含了相对位移,这是编译器根据汇编指令中的“标号”计算出来的。

–由此,我们总结出了这个指令的功能:(IP) = (IP) + 8位位移

—8位位移 = 标号处的地址 – jmp指令后的第一个字节的地址(因为IP的增加是在执行完jmp指令后的;确切来说是:将jmp指令读入后,IP会先指向下一条指令,之后再执行刚才读入的指令)

–【short指明此处的位移为8位位移】

–【备注:补码计算,源码取反(not)再加1】

–jmp near ptr 标号

—这一条指令实现段内近转移(16位位移)

—功能:(IP) = (IP) + 16位位移

-【6.4 转移的目的地址在指令中的jmp指令】

–实现:jmp far ptr 标号

–这是段间转移,又称远转移

–解释:far ptr说明了要用标号所在的段地址修改CS,用标号所在的偏移地址修改IP。

–关于这个指令的机器指令:

—汇编指令为jmp 0BBD:010B(这个值是由标号替换而成) 的机器指令是EA0B01BD0B,我们发现,第一个字节对应了jmp指令,之后两个字节是段地址,再之后两个字节是偏移地址。【可以这样记忆:此处段地址与偏移地址对人来说是要从右向左读的】

-【9.5 转移地址在寄存器中的jmp指令】

–jmp 16位reg,相当于直接修改ip的值

-【9.6 转移地址在内存中的jmp指令】

–1)jmp word ptr内存单位地址(只能实现段内转移)

–2)jmp dword ptr内存单元地址(段间转移)

—解释:从内存单元地址处开始,高地址处作为转移的段地址,低地址处作为偏移地址。【同样可以记忆成从右(高)向左(低)读】

—常见用法:我们将内存地址单元用bx记录,之后我们在存入高位地址时使用[bx+2]

–注意:mov word ptr [bx],offset start以及mov word ptr [bx+2],code,这些语句必须加上word ptr来表明是什么类型数据。

-【9.7 jcxz指令】

–jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移(-128~127)

–作用是:如果jcxz=0时,会进行跳转,反之则不进行跳转。

-【9.8 loop指令】

–loop指令是循环指令,所有的循环指令都是短转移。

–CX不等于0时才跳转

–【备注dec指令(decrease)是自减1】

-【9.9 根据位移进行转移的意义】

–前面的jmp short X, jmp near ptr X, jcxz X, loop X都是使用位移来进行转移

–这种设计,方便了程序段在内存中的浮动装配。

—因为指令间的相对位置是固定的,如果使用位移来定义,这个程序可以在内存的任意一处正常运行。

–【备注:跳转位移写在机器码的第一个字节上】

-【9.10 编译器对转移位移超界的检测】

–Jump out of range by n byte(s).【n表示超出的字节数】

-【注意:第二章中讲到的形如”jmp 2000:0”这样的指令只能在debug中使用,在汇编源程序中使用会报错】

-【实验八 跳转实验】

-【实验九 彩色输出】

–任务目标:编程,在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串”welcome to masm!”

–知识储备:

—80×25彩色字符模式显示缓冲区(以下简称为显示缓冲区)的结构:

—内存地址空间中,B8000H~BFFFFH共32KB的空间,为80×25彩色字符模式(以下简称“模式”)的显示缓冲区。特点是:向这个地址空间写入的数据,会立即出现在显示器上。

—在这一模式下,显示器可以显示25行,每行80个字符,每个字符额可以有256种属性(背景色、前景色、闪烁、高亮等组合信息)

—一个字符要占据两个字节:分别存放字符ASCII码和属性。

—缓冲区分为8页,每页4KiB(每页均是80×25),显示器可以显示任意一页的内容,一般情况显示第0页。

—如果以每一页起始为段地址,则

—-偏移000~09F对应第一行(80个字符,160个字节)

—-偏移0A0~13F对应第二行

—-偏移140~1DF对应第三行

—一个字符低8位存储ASCII码,高八位存储属性。

—-由此我们得出结论,在显示缓冲区,偶地址(包含0)存放字符,奇地址存放属性。

—关于属性:

—-7号位BL,记录闪烁,1闪0不闪

—-6、5、4号位依次记录背景RGB

—-3号位记录高亮,1亮0不亮

—-2、1、0号位依次记录前景RGB

—-【备注:RGB属性仅能设置0或1】

—-【注意:闪烁效果仅仅能在全屏DOS方式下看到】

–设计分析:

—主要思路:将字符存入→赋予属性

—字符存入:

—-将原始字符存入一个段中,之后将这个段的每个字符存放在奇数的目标位置,并循环3次。bx定位最终显示的每行起始数据存放位置,di定位每行每个字符存放位置,si定位原始位置。

—赋予属性:

—-首先确定需求,

—–第一行(黑底绿字)字符属性:00000010B

—–第二行(绿底红字)字符属性:00100100B

—–第三行(白底蓝字)字符属性:01110001B

—–三行的属性不同,故而可以设计三个循环来完成属性赋值。

—接下来分析每行分布:每行有80个字符,160个字节,所以:

—-段地址定位第0页基础地址:B800H

—-第一行偏移地址:000H~09FH

—-第二行偏移地址:0A0H~13FH

—-第三行偏移地址:140H~1DFH

—-这个将由bx存储

—之后,要对每一个属性存放位置进行定位,故而我们用di存储。

—以上,要设置两层循环,内层每次循环让di+2,外层每次循环后将di赋1,将bx加A0H,属性提前存到数据段中,使用si定位,每个属性占1个字节,所以我们要让外循环每次对si自增1,将属性值赋给ax。

-【详解实验八】

–结论:利用位移的跳转并不是绝对位置,所以不能直接将这个指令复制到其它位置使用。此时计算方法:标号位置=位移+jmp指令后第一个字节所在位置

-【详解实验九】

–实验记录:Win7旗舰版下无法正常显示,即便使用mov ah,0与int 16h强行防止自动跳转仍然无显示。

【笔记】8086汇编-数据处理的两个基础问题

前言:本篇仅仅是笔记,整理于2017/8/9,结构可能会略有混乱,见谅。

-为了表示方便,我们使用reg表示一个寄存器,使用sreg表示段寄存器

–reg的集合包括:ax、bx、cx、dx、ah、al、ah、bl、bh、cl、ch、dl、dh、sp、bp、si、di;

–sreg的集合包括:ds、ss、cs、es。

-【8.1 bx、si、di、bp】

–在8086CPU中,只有这四个寄存器可以放在中括号中。

—关于两个寄存器的联合使用:要注意si和di不可以放在一起,bx与bp不可以放在一起!!(指同一个括号)

–其中bp的特殊性在于,如果指令中没有显性地给出段地址,段地址就默认在ss中。它可用来帮助sp“解决负担”。

-【8.2 机器指令处理的数据在什么地方】

–在指令执行前,所要处理的数据可以放在3个地方:CPU内部、内存、端口(后面章节讨论)。

–mov bx,1的数据1存放在CPU内部的指令缓冲器

-【8.3 汇编语言中数据位置的表达】

-1)立即数(idata)

–对于直接包含在机器指令中的数据(存放在CPU的指令缓冲器中),在汇编语言中称为:立即数(idata)

-2)寄存器

-3)段地址(SA)和偏移地址(EA)

–正如我们前边所说,存放段地址的寄存器可以显性给出(这样就可以使用中括号加数值),也可以隐性给出(使用寄存器传址)

-【8.4 寻址方式】

–只使用idata的方式称为直接寻址, 只使用寄存器的方法称为寄存器间接寻址,结合一个寄存器和一个常数叫做寄存器相对寻址,结合两个寄存器的方法叫做基址变址寻址,同时使用两个寄存器以及常数的方式叫做相对基址变址寻址。

-【8.5 指令要处理的数据有多长】

–8086CPU可以处理byte和word,8086CPU的word长度为16位。

–在有寄存器的情况下,数据的长度会有寄存器大小来决定。

–在指令中没有寄存器的情况下,我们使用X ptr,X可以填byte或word

—如:mov word ptr ds:[0],1【ptr是pointer的缩写】

–push与pop这类的指令默认了是访问字单元

-【8.6 寻址方式的综合利用】

–通常的方式是:用bx定位整个结构体,用idata定位结构体中某一个数据项,用si定位数组项中的每个元素。

—为此,汇编语言提供了更为贴切的书写方式,这与c语言中我们的书写方法十分相似:如:

—-[bx].idata

—-[bx].idata[si]

-【div指令】

–div(division)

–除数:8位或16位,在一个reg或内存单元中

–被除数:默认放在AX(16位)或DX和AX(一共32位)中,如果除数为8位,被除数要有16位;如果除数有16位,被除数要有32位,并且DX中存放高16位,AX中存放低16位。

–结果的存放:

—1)如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数是16位,则AX存储除法操作的商,DX存放除法操作的余数。【记忆:原来存放高位的地方要存放余数】

–【注意:DX与AX中数据的关系是组合,而不是相加。】

–【注意:就笔者而言,关于div指令的计算原理与限制还有待进一步研究,首先应当知道,不当的值可能会引发运行时divide overflow错误】

-【8.8 伪指令dd】

–dd是用来定义dword(double word,双字)型数据的。

-【8.9 dub】

–操作符,由编译器处理,和db、dw、dd配合使用。

–使用方法:

—db 重复次数 dup (重复的字节型数据)

—dw 重复次数 dup (重复的字型数据)

—dd 重复次数 dup (重复的双字型数据)

–解释说明:

—对于“db 3 dup (‘abc’)”是什么意思?首先我们要明确,这里的db不可改为dw或dd,那样会造成syntax error;而这里将会在数据段中依次存放abcabcabc,也就是说,这条代码等价于db 3 dup (‘a’,’b’,’c’)。

—而如果输入dw 3 dup (‘a’,’b’,’c’),则不会引发语法错误,不过这与上面的语句产生的效果不同,每个字型数据的第八位代表字符,高八位则由0填补。

–应用:

—这一功能主要用于开辟大量的内存空间,比如我们想有一段包含200字节的内存空间,这时我们就需要使用db 200 dup (0)来快速声明。

-【实验七:寻址方式在结构化数据访问中的应用】

–我们花费了空间,得到了效率,这在算法中常常见到【牺牲空间得到时间】。在这个试验中,我们根据一种数据的最大长度,来决定使用了多长的数据类型(这里我们用了4个字节的dword型),这样,我们可以很方便的确定他们的位置。

【笔记】8086汇编-更灵活的内存控制

前言:本篇仅仅是笔记,整理于2017/8/4,结构可能会略有混乱,见谅。

-本章核心:灵活定位

-关键知识:

–二重循环

–栈的应用

–大小互换

–and和or

-【and和or指令】

–and指令:逻辑“与”指令,按位进行“与”运算

—都为1时结果才为1【同真为真】

—常用技巧:可将特定位设置为0,方法是:

—-and al,10111111B,这样可以将第6位设置为0,其它位不变

–or指令:逻辑“或”指令,按位进行“或”运算

—常用技巧:可将特定位设置为1,方法是:

—or al,01000000B,这样可以将第6位设置为1,其它位不变

-【关于ASCII码】

–我们按下a键,屏幕是如何显示出a呢?

—按下的同时,会将61H(a的ASCII码)传送到内存空间,文本编辑器取出这个61H再放到显卡的显存中;工作在文本模式下的显卡,用ASCII码的规则解释内容,显卡驱动显示器,将字符呈现在屏幕上。

-【用字符形式给出的数据】

–示例

—db ‘test’

—1)注意要使用单引号

—2)注意要使用db

–备注:一个英文字符对应1个字节

-【大小写转换问题】

–关键在于寻找到规律:大写比小写字母的二进制小10000B(也就是20H)

—但是,我们并不会使用条件判断,这样我们就无法判断一个字母是大写还是小写,呢么我们应当换一个思路:我们可以用or与and,因为大小写字母的区别仅仅是:大写字母第5位是0,小写字母第5位是1。

-【[bx+idata]】

–指令mov ax,[bx+200]可以写成如下格式

—1)mov ax,[200+bx]

—2)mov ax,200[bx]

—3)mov ax,[bx].200

–使用这个指令后,偏移地址为(bx)+idata

-【使用[bx+idata]实现对数组的处理】

-【SI与DI】

–在8086CPU中,SI与DI与BX作用类似,但是SI、DI不可以分成两个八位寄存器来使用。所以可以使用[si]、[di],并且也可以增加idata。

–习题:使用寄存器SI和DI实现字符串复制

—注意mov [si],[di]是非法指令,也就是说无法直接将内存单元的数据传给另一个内存单元。

—习惯上,我们习惯用si指向原始地址,用di指向目标地址。

—实际上,这里我们可以使用数组(统一)的思维来处理,直接用bx指向原始地址,bx+idata指向目标地址。

-【[bx+si]、[bx+di]】【下面使用bx与si进行解释】

–偏移地址将会是(bx)+(si)

–mov ax,[bx+si]指令也可以写为

—mov ax,[bx][si]

-【[bx+si+idata]】

–也可以写作:

—mov ax,200[bx][si]

—mov ax,[bx].200[si]

—mov ax,[bx][si].200

—mov ax,[200+bx+si]

—mov ax,[200+bx][si]

—【需要注意:如果数据在后面,则一定要加”.”】

-【不同寻址方式的灵活应用】

–灵活使用实际上是让我们能够以更加结构化的方式来看待数据。

–【编程错误记录,备注:这里在调试程序时,不小心把结束指令中mov ax,4c00h写成了mov cx,4c00h,最终运行到int 21h时,使用debug中的p指令,提示FCB unavailable,下面一行提示Abort, Fail? 这时,输入f就会自动跳到下一条指令。(这一问题的原理暂时不了解,或许与中断机制有关,有待后日学习)】

–这里有一道题目,用到了双层循环,对于这类问题,我们需要将数据暂时存放,有些时候,我们可以将临时数据存放到空闲的寄存器中【这道题我们就是使用了dx来临时存放外层cx的状态】,但是寄存器的数量总是有限的,所以我们的解决办法是:提前开辟一段内存空间,我们将数据存储在这部分内存空间中。但是还有一个问题,这里我们只存放了一个数据,可是我们有时会遇到多个数据,这个时候如果我们还是用定值来确定内存位置会十分麻烦,所以:遇到要临时存放的数据,我们通常会使用栈来存放

–【备注:如果cx一开始为0,这样,运行到loop时,cx-1会得到ffffh】