汇编学习笔记

汇编学习笔记 
<本人转载自网络>

本书不对硬件系统进行全面和深入的研究:

关于PC机及CPU物理结构和编程结构的全面研究,在《微机原理与接口》中进行;对于计算机一般的结构
、功能、性能的研究在一门称为《组成原理》的理论层次更高的课程中进行。
汇编将研究重点放在如何利用硬件系统的编程结构和指令集有效灵活地控制系统进行工作。

计算机的机器指令是一列二进制数字,计算机将之转变为一列高低电平,以使计算机的电子器件受到驱动
,进行运算。

每一种微处理器,由于硬件设计和内部结构的不同,就需要用不同的电平脉冲来控制,使它工作。所以
每一种微处理器都有自己的机器指令集,也就是机器语言。

寄存器:(大小???)
汇编编译器。

存储器(与内存的区别):由存储单元组成
指令和数据在存储器中存放:内存???

指令和数据是应用上的概念。在内存和磁盘上,指令和数据没有任何区别,都是二进制信息。
CPU在工作的时候把有的信息看作指令,有的信息看作数据,为同样的信息赋予了不同的意义(如何实现??)

存储单元(1BYTE):

CPU要从内存中读数据,首先要指定存储单元的地址。另外,在一台微机上不只有存储器一种器件,CPU
在读写数据时还要指明对哪一种器件进行操作,进行哪种操作,是从中读出数据,还是向里面写入数据。

CPU要想进行数据的读写,必须和外部的器件(标准的说是芯片)进行三类信息的交互:

1.存储单元的地址(地址信息)
2.器件的选择,读或写的命令(控制信息)
3.读或写的数据(数据信息)。

CPU从存储器中读写数据的过程及图解??

地址总线、控制总线、数据总线

内存地址空间
主板
接口卡
各类存储器芯片

各类存储器,它们在物理上是独立的器件,但是它们在以下两点上相同:

1.都和CPU的总线相连。
2.CPU对它们进行读或写的时候都通过控制线发出内存读写命令。

逻辑存储器


这也就是说,CPU在操纵和控制它们的时候,把它们都当作内存来对待,把它们看作一个由若干存储单元
组成的逻辑存储器,这个逻辑存储器就是我们所说的内存地址空间。

8086CPU 内存地址空间分配:
00000~9FFFF 主存储器地址空间(RAM)
A0000~BFFFF 显存地址空间
C0000~FFFFF 各类ROM地址空间

内存地址空间大小限制:

8086 20根地址总线 能支持1M内存 16位数据线
80386 32根 能支持4G内存 32位数据线


二、寄存器(CPU工作原理)

CPU:运算器、控制器、寄存器
外部总线和内部总线

8086CPU的所有寄存器都是16位的,可以存放两个字节。

通用寄存器

8位寄存器和16位寄存器

(存储单元/内存单元的)物理地址

8086CPU如何在内部形成内存单元的物理地址及其图解?

什么是16位结构的CPU?
我们说8086CPU的上一代CPU(8080、8085)等是8位机,而8086是16位机,也可以说8086是16位结构的
CPU。那么什么是16位结构的CPU呢?

概括地讲,16位结构(16位机、字长位16位等常见说法,与16位结构的含义相同)描述了一个CPU具有下面
几方面的结构特性:

1.运算器一次最多可以处理16位的数据。
2.寄存器的最大宽度为16位。
3.寄存器与运算器之间的通路位16位。

8086是16位结构的CPU,这也就是说,在8086内部,能够一次性处理、传输、暂时存储的信息的最大长度
是16位的。


物理地址:8086CPU的物理地址是20位的

8086CPU给出物理地址的方法:
8086CPU有20位地址总线,可以传送20位地址,达到1MB的寻址能力。8086CPU又是16位结构,在内部一次
性处理、传输、暂时存储的地址为16位。从8086CPU的内部结构来看,如果将地址从内部简单地发出,那
么它只能送出16位的地址,表现出的寻址能力只有64KB.

8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。

CPU如何操作寄存器和存储器,两者有什么区别?
操作存储器的汇编指令??

理解“基础地址(段地址X16)+偏移地址=物理地址”及段的概念

在编程时可以根据需要,将若干地址连续的内存单元看作一个段,用段地址X16定位段的起始地址(基础
地址),用偏移地址定位段中的内存单元。有两点需要注意:段地址X16必然是16的倍数,所以一个段的
起始地址也一定是16的倍数;偏移地址位16位,16位地址的寻找能力位64KB,所以一个段的长度最大为
64KB.

段寄存器

8086CPU在要访问内存时,要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器合成物理
地址。

什么部件提供段地址?

段地址在8086CPU的段寄存器中存放,8086CPU有4个段寄存器。

CS和IP

CS和IP是8086CPU中两个最关键的寄存器,它们指示了CPU当前要读取指令的地址。

在8086PC机中,任意时刻,设CS中的内容为M,IP中的内容为N,8086CPU将从内存MX16+N单元开始,读取
一条指令并执行。
也可以这样表述:8086机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。

8086CPU读取、执行指令流程及图解!!

在8086CPU加电启动或复位后(即CPU刚开始工作时)CS和IP被设置位CS=F000H,IP=FFFFH,即在8086PC机
刚启动时,CPU从内存FFFF0H单元中读取指令执行,FFFF0H单元中的指令是8086PC机开机后执行的第一
条指令。

我们在第1章中讲过,在内存中,指令和数据没有任何区别,都是二进制信息,CPU在工作的时候把有的
信息看作指令,有的信息看作数据。现在,如果提出一个问题:CPU根据什么将内存中的信息看作指令?

我们可以说,CPU将CS:IP指向的内存单元中的内容看作指令,因为在任何时候,CPU将CS、IP中的内容
当作指令的段地址和偏移地址,用它们合成指令的物理地址,到内存中读取指令码,执行。如果说,内存
中的一段信息曾被CPU执行过的话,那么,它所在的内存单元必然被CS:IP指向过。

修改CS、IP指令

如何实现对CPU的控制?
在CPU中,程序员能够用指令读写的部件只有寄存器,程序员可以通过改变寄存器中的内容实现对CPU的
控制。

CPU从何处执行指令是由CS、IP中的内容决定的,程序员可以通过改变CS、IP中的内容来控制CPU执行的
目标指令。

我们如何改变CS、IP的值呢?显然,8086CPU必须提供相应的指令。

传送指令:mov可用来改变8086CPU大部分寄存器的值
转移指令:jmp

代码段:段仅仅是我们的一种安排,CPU并不能识别所谓的段,所以CPU不能识别出代码段而自动执行。
我们必须将CS:IP指向代码段


总结:

通用寄存器:AX、BX、CX、DX
段寄存器:CS、DS、SS、ES

mov及jmp指令
传送指令和转移指令

改变寄存器CS及IP的指令?

jmp 段地址:偏移地址
指令的功能为:用指令中给出的段地址修改CS,偏移地址修改IP。

jmp 2AE3:3,执行后:CS=2AE3H,IP=0003H

注意:这种jmp格式的转移指令是在Debug中使用的汇编指令,汇编编译器并不认识。如果在源程序
中使用,编译时也会报错。

若想仅修改IP的内容,可用指令“jmp 某一合法寄存器”完成,如:

jmp ax,jmp bx等,如:

jmp ax,指令执行前:ax=1000H,CS=2000H,IP=0003H
        指令执行后:ax=1000H, CS=2000H,IP=1000H


第三章 寄存器(内存访问)

字单元

任何两个地址连续的内存单元,N号单元和N+1号单元,可以将它们看成两个内存单元,也可看成一个
地址为N的字单元的高位字节单元和低位字节单元。

DS寄存器
通常用来存放要访问数据的段地址。

寄存器和内存之间进行字节型数据的传送
mov bx,1000H
mov ds,bx
mov al,[0]

寄存器和内存之间进行字数据的传送

DS和[addr]

8086CPU不支持直接将数据送入段寄存器的操作。
改变段寄存器DS的指令?

MOV ADD SUB指令:

mov 寄存器 数据
mov 寄存器 寄存器
mov 段寄存器 寄存器
mov 寄存器 段寄存器
mov 寄存器 内存
mov 内存 寄存器
mov 段寄存器 内存
mov 内存 段寄存器

数据段与DS(是不是一定要用DS实现??)

mov ax,1000H
mov ds,ax
add ax,[0]
add ax,[1]
add ax,[2]

现今的CPU都有栈的设计,8086CPU也不例外。8086CPU提供相关的指令来以栈的方式访问内存空间。
这意味我们在基于8086CPU编程的时候,可以将一段内存当作栈来使用。

CPU如何知道当前要执行的指令所在的位置?CPU如何知道栈顶的位置?

push ax的执行,由以下两步完成:

(1) SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
(2) 将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。

如果我们将10000H~1000FH这段空间当作栈,初始状态栈是空的,此时,SS=1000H,SP=?


pop ax的执行过程和push ax刚好相反,由以下两步完成:

(1)将SS:SP指向的内存单元处的数据送入ax中;
(2)SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。

栈顶超界的问题

我们当然希望CPU可以帮我们解决这个问题,不过对于8086CPU,这只是我们的一个设想,8086CPU不保证
我们对栈的操作不会超界。这也就是说,8086CPU只知道栈顶在何处(由SS:SP指示),而不知道读者安排
的栈空间有多大。这点就好像,CPU只知道当前要执行的指令在何处(由CS:IP指示),而不知道读者要执
行的指令有多少。

我们在编程的时候要自己操心栈顶超界的问题,要根据可能用到的最大栈空间,来安排栈的大小,防止
入栈的数据太多而导致的超界;执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。

push pop指令

栈操作(push、pop)都是以字为单位!

SS和SP寄存器

push 寄存器
push 段寄存器
push 内存单元

注意:
CS:IP jmp
DS mov 寄存器 [...] ...应该是什么呢?
SS mov 寄存器 SP mov 数字

mov ax,1000H
mov ss,ax
mov sp,2            // ???
mov ax,2266H
push ax

push pop等栈操作指令,修改的只是SP,也就是说,栈顶的变化范围最大为:0~FFFFH

SS、SP指示栈顶;改变SP后写内存的入栈指令;读内存后改变SP的出栈指令。这就是8086CPU提供的
栈操作机制。

8086CPU只记录栈顶,栈空间大小由我们自己控制。

栈段
将一段内存当作栈段,仅仅是我们在编程时的一种安排,CPU并不会由于这种安排,就在执行push、pop
等栈操作指令时就自动地将我们定义的栈段当作栈空间来访问。如何使得如push、pop等栈操作指令
访问我们定义的栈段呢?就是要将SS:SP指向我们定义的栈段。

如果我们将10000H~1FFFFH这段空间当作栈段,初始状态是空的,此时,SS=1000H,SP=?

第四章 第一个程序

伪指令

定义一个段

XXX segment
   .
   .
XXX ends


end
汇编程序结束标记

assume
“假设” 它假设某一“段寄存器”和程序中的某一个用segment...ends定义的段相关联。

标号:指代了一个地址。

程序返回指令:
mov ax,4c00H
int 21H
汇编指令 执行时,由CPU执行。

段名 ends 伪指令 通知编译器一个段结束 编译时,由编译器执行
End 伪指令 通知编译器程序结束 编译时,由编译器执行

edit-->compile-->link-->exe

简化编译、链接操作。

理解汇编程序的加载、执行及返回过程。
程序被装入内存的什么地方?

ds PSP:256BYTES
cs:ip
cx存储了程序的长度

第五章:[bx]和loop指令

mov ax,[bx]
mov [bx],ax


注意
:inc bx使bx加1
mov bx,1
inc bx

loop指令的格式:loop 标号,CPU执行loop指令的时候,要进行两步操作:1,(cx)=(cx)-1;
2,判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。

是不是一定要用cx寄存器呢?

s:add ax,ax
  loop s

debug的G、P命令

Debug和汇编编译器Masm对指令的不同处理

Debug中 mov ax,[0]表示将ds:0处的数据送入ax中。
但是在汇编源程序中,指令“mov ax,[0]”被编译器当作指令“mov ax,0”处理。

那么我们如何在源程序中实现将内存单元中的数据送入寄存器中呢?
mov ax,2000h
mov ds,ax
mov bx,0
mov al,[bx]

方法二:
mov ax,2000h
mov ds,ax
mov al,ds:[0]

mov al,[0]和mov al,ds:[0]不同
mov al,[bx]和mov al,ds:[bx]相同。

1~9不需要加h,因为加不加一样。这是我们看到有的mov ax,0|1|2|....的原因。
10不加h表示十进制10,加h表示十六进制,值为16,因此加和没加有很大的区别。

mov ax,12345 可以
mov ax,12345h 不可以

计算ffff:0~ffff:b单元中数据的和,结果存储在dx中???
注意的地方:
运算对象不匹配、超界、循环

段前缀:段寄存器都可以作段前缀
mov ax,ds:[bx]
mov ax,cs:[bx]
mov ax,ss:[bx]
mov ax,es:[bx]
mov ax,ds:[0]
mov ax,cs:[0]
mov ax,ss:[0]
mov ax,es:[0]

一段安全的空间:
在一般的PC机中,DOS方式下,DOS和其他合法的程序一般都不会使用0:200~0:300(00200h~00300h)的
256个字节的空间。
最好先查看

段前缀的使用:
考虑一个问题,将内存ffff:0~ffff:b单元中的数据拷贝到0:200~0:20b单元中。
注意:
同一段内存的不同表述。
提高效率,循环语句的简化


注意:CPU执行loop指令使用cx寄存器!!

mov ax,0ffffh
注意上面的语句:在汇编源程序中,数据不能以字母开头,所以要在前面加0.比如,9138h在汇编
源程序中可以直接写为9138h,而A000h在汇编源程序中要写为0A000h。

assume cs:code
code segment
  mov ax,0ffffh
  mov ds,ax
  mov ax,20h
  mov ex,ax

  mov bx,0
  mov cx,12
  s : mov al,[bx]
      mov es:[bx],al
      inc bx
      loop s

  mov 4c00h
  int 21h
code ends
end


第六章 包含多个段的程序

在操作系统的环境中,合法地通过操作系统取得的空间都是安全的。
程序取得所需空间的方法有两种,一是在加载程序的时候为程序分配,再就是程序在执行的过程中向
系统申请。
加载程序的时候为程序分配空间,我们在前面已经有所体验,比如我们的程序在加载的时候,取得了
代码段中的代码的存储空间。

注意合法地通过操作系统获取空间与前五章随意操作内存空间的区别。

要一个程序在被加载的时候取得所需的空间,则必须要在源程序中做出说明,我们通过在源程序中
定义段来进行内存空间的获取。

code segment
  dw 0123H,0456H,0789H
code ends

从规范的角度来讲,我们是不能自己随便决定哪段空间可以使用的(所以我们不能把上面的那些数据像
前面几章一样,随便选个内存地址存进去),应该让系统来为我们分配。我们可以在程序中,定义我们
希望处理的数据(像上面代码一样),这些数据就会被编译、连接程序作为程序的一部分写到可执行文
件中。当可执行文件中的程序被加载入内存时,这些数据同时被加载入内存中。与此同时,我们要处理
的数据也就自然而然地获得了存储空间。

dw的含义是定义字型数据。

在源程序中指明程序的入口所在!

可执行文件中的程序执行过程:
1.怎么加载入内存?
2.怎么设置CS:IP使程序得以运行?
3.程序返回?

根据什么设置CPU的CS:IP指向程序的第一条要执行的指令?也就是说,如何知道哪一条指令是程序
第一条要执行的指令?这一点,是由可执行文件中的描述信息指明的。

可执行文件由描述信息和程序组成,程序来自于源程序中的汇编指令和定义的数据;描述信息注意
是编译、连接程序对源程序中的相关伪指令进行处理所得到的信息。

根据对end start伪指令进行编译、连接后生成的描述信息

为什么需要将数据、代码、栈放入不同的段??

对段地址的引用

程序中有多个段,如何访问段中的数据呢?当然要通过地址,而地址是分为两部分的,即段地址和
偏移地址。如何指明要访问的数据的段地址呢?

在程序中,段名就相当于一个标号,它代表了段地址。

mov ax,data
mov ds,ax
mov ax,ds:[6]

不能用下面的指令:
mov ds,data

理解代码段、数据段、栈段完全是我们的安排。

伪指令是由编译器执行的,也是仅在源程序中存在的信息,CPU并不知道它们。


第七章 更灵活定位内存地址的方法

and和or指令

mov al,01100011B
and al,00111011B

执行后:al=00100011B

and or 好像只能对通用寄存器使用

关于ASCII码:
我们要把信息存储在计算机中,就要对其进行编码,将其转化为二进制信息进行存储。而计算机要将
这些存储的信息再显示给我们看,就要再对其进行解码。只要编码和解码采用同样的规则,我们就可以
将人能理解的信息存入到计算机中,再从计算机中取出。

计算机系统通常采用ASCII编码方案.简单地说,所谓编码方案,就是一套规则,它约定了用什么样的
信息来表示现实对象。比如说,在ASCII编码方案中,用61H表示“a”,62H表示“b”。一种规则需要人们
遵守才有意义。
一个文本编辑过程中,就包含按照ASCII编码规则进行的编码和解码。
我们按一下键盘的a键,就会在屏幕上看到“a”。这是怎样一个过程呢?我们按下键盘a键,这个按键的
信息被送入计算机,计算机用ASCII码的规则对其进行编码,将其转化为61H存储在内存的指定空间中;
文本编辑软件从内存中取出61H,将其送到显卡上的显存中;工作在文本模式下的显卡,用ASCII码的
规则解释显存中的内容,61H被当作字符“a”,(为什么不当作数字呢?)显卡驱动显示器,将字符“a”
的图像画在屏幕上。我们可以看到,显卡在处理文本信息的时候,是按照ASCII码的规则进行的。这也
就是说,如果我们要想在显示器上看到“a”,就要给显卡提供“a”的ASCII码,61H。如何提供?当然是写
入显存中。

以字符的形式给出数据
我们可以在汇编程序中,用’.......‘的方式指明数据是以字符的形式给出的,编译器将把它们转化为
相对应的ASCII码。

以字符的形式给出数据,也就是说,数据可以直接以数据的形式给出,也可以通过字符的形式给出。
例如:db 61 和db 'a'相同, mov al,61和mov al,'a'相同,注意这里的相同是指内存和al中的二进制
值相同。不过计算机怎么将内存和al中的同一个二进制值区分成数据和字符呢?

data segment
  db ’unix‘
data ends

[bx+idata]表示内存单元法

mov ax,[bx+200] 或

mov ax,[200+bx]
mov ax, 200[bx]
mov ax,[bx].200

按[bx+idata]方式进行数组处理

SI和DI寄存器
不能分成两个8位寄存器
mov si,1
mov ax,[si]
mov ax,[si+12]

[bx+si]和[bx+di]

mov ax,[bx+si] 或
mov ax,[bx][si]

[bx+si+idata]和[bx+di+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

[bx] [si] [di]
[bx+idata] [si+idata] [di+idata]
[bx+si] [bx+di]
[bx+si+idata] [bx+di+idata]

不同寻址方式的灵活应用:
[idata]
[bx]
[bx+idata]
[bx+si]
[bx+si+idata]


注意多重循环情况下,内循环将覆盖外循环cx值的情况。
一般来说,需要暂存数据的时候,我们都应该使用栈


第八章 数据处理的两个基本问题

1.处理的数据在什么地方?
2.要处理的数据有多长?

Reg:ax,bx,cx,dx,ah,al,bh,bl,ch,cl,dh,dl,si,di,sp,bp
sreg:ds,ss,cs,es

关于bx、si、di、bp寄存器

1.在8086CPU中,只有这4个寄存器可以用在[...]中来进行内存单元的寻址。
比如下面的指令都是正确的:
mov ax,[bx]
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp]
mov ax,[bp+si]
mov ax,[bp+di]

而下面的指令是错误的:
mov ax,[cx]
mov ax,[ax]
mov ax,[dx]
mov ax,[ds]

2.在[...]中,这4个寄存器可以单个出现,或只能以四种组合出现bx和si、bx和di、bp和si、bp和di。
比如下面的指令是正确的:

mov ax,[bx]
mov ax,[si]
mov ax,[di]
mov ax,[bp]
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp+si]
mov ax,[bp+di]
mov ax,[bx+si+idata]
mov ax,[bx+di+idata]
mov ax,[bp+si+idata]
mov ax,[bp+di+idata]

下面指令是错误的:
mov ax,[bx+bp]
mov ax,[si+di]

3.只要在[...]中使用寄存器bp,而指令中没有显性地给出段地址,段地址就默认在ss中。
何谓“没有显性地给出段地址”:就是没有给出段前缀~~

机器指令处理的数据所在位置:
指令执行前,所要处理的数据可以在三个地方:CPU内部、内存、端口。
mov bx,[0]  指令执行前数据的位置:内存,ds:0单元
mov bx,ax  CPU内部,ax寄存器
mov bx,1   CPU内部,指令缓冲器

汇编语言中数据位置的表达:
1.立即数:对于直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中)。
 
mov ax,1
add bx,2000h
or bx,00010000b
mov al,'a'

2.寄存器

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

存放段地址的寄存器可以是默认的,比如:
mov ax,[0]     ??????
mov ax,[di]
mov ax,[bx+8]
mov ax,[bx+si]
mov ax,[bx+si+8]

等指令,段地址默认在ds中。

 mov ax,[bp]
 mov ax,[bp+8]
 mov ax,[bp+si]
 mov ax,[bp+si+8]
等指令,段地址默认在ss中。

存放段地址的寄存器也可以是显性给出的,比如:

mov ax,ds:[bp]
mov ax,es:[bx]
mov ax,ss:[bx+si]
mov ax,cs:[bx+si+8]

关于寻址方式:

当数据存放在内存中的时候,我们可以用多种方式来给定这个内存单元的偏移地址,这种定位内存
单元的方法一般被称为寻址方式。

8086CPU都多种寻址方式,概括如下:

1.直接寻址

[idata]

2.寄存器间接寻址

[bx]
[si]
[di]
[bp]

3.寄存器相对寻址

[bx+idata]
[si+idata]
[di+idata]
[bp+idata]

常用格式:
用于结构体:[bx].idata
用于数组:idata[si],idata[di]
用于二维数组:[bx][idata]

4.基址变址寻址

[bx+si]
[bx+di]
[bp+si]
[bp+di]

常用格式:
用于二维数组:[bx][si]

5.相对基址变址寻址

[bx+si+idata]
[bx+di+idata]
[bp+si+idata]
[bp+di+idata]

常用格式:
用于表格(结构)中的数组项:[bx].idata[si]
用于二维数组:idata[bx][si]

指令要处理的数据有多长?

8086CPU的指令,可以处理两种尺寸的数据,byte和word,所以在机器指令中要指明,指令进行的是
字操作还是字节操作。对于这个问题,汇编语言用以下方法处理:

1.通过寄存器名指明要处理的数据的尺寸。

例如:ax等进行字操作,al、ah等进行字节操作。

2.在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令中可以为
word或byte。

例如:

mov word ptr ds:[0],1 为字单元操作

mov byte ptr ds:[0],1 为字节操作

3.其他方法
有些指令默认了访问的是字单元还是字节单元,比如push就不用指明访问的是字单元还是字节单元,
因为push指令只进行字操作。


div指令,使用div做除法的时候:
1.除数:有8位和16位两种,在一个寄存器或内存单元中。
2.被除数:默认放在AX或DX和AX中,如果除数为8位,被除数则为16位,默认在AX中存放;如果除数
为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。
3.结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则
AX存储除法操作的商,DX存储除法操作的余数。

格式如下:
div reg
div 内存单元

伪指令dd

我们用db和dw定义字节型数据和字型数据,dd是用来定义dword(double word,双字)型数据的。

dup

dup是一个操作符,在汇编语言中同db、dw、dd等一样,也是由编译器识别处理的符号。它是和db、
dw、dd等数据定义伪指令配合使用的,用来进行数据的重复。比如:

db 3 dup (0) 定义了3个字节,它们的值都是0,相当于db 0,0,0
db 3 dup (0,1,2)

定义了9个字节,它们是0、1、2、0、1、2、0、1、2,相当于db 0,1,2,0,1,2,0,1,2

db 3 dup (‘abc’,‘ABC')
定义了18个字节,它们是'abcABCabcABCabcABC',相当于db ’abcABCabcABCabcABC‘

dup的使用格式如下:

db 重复的次数 dup(重复的字节型数据)。
dw 重复的次数 dup(重复的字型数据)。
dd 重复的次数 dup(重复的双字数据)。

第9章 转移指令的原理

什么是转移指令?

可以修改IP,或同时修改CS和IP的指令统称为转移指令,概括地讲,转移指令就是可以控制CPU
执行内存中某处的代码的指令。

8086CPU的转移行为分为以下几类:

只修改IP时,称为段内转移,比如:jmp ax。
同时修改CS和IP时,称为段间转移,比如:jmp 1000:0。

由于转移指令对IP的修改范围不同,段内转移又分为:短转移和近转移。

短转移IP的修改范围为-128~127
近转移IP的修改范围为-32768~32768

8086CPU的转移指令分为以下几类:

无条件转移指令(如:jmp)
条件转移指令
循环指令(如:loop)
过程
中断

操作符offset

操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址。

比如下面的程序:
codesg segment

  start:mov ax,offset start           ;相当于mov ax,0
      s:mov ax,offset s               ;相当于mov ax,3

codesg ends
end start

jmp指令:无条件转移指令
jmp指令要给出两种信息:
1.转移的目的地址
2.转移的距离(段间转移、段内短转移、段内近转移)

jmp指令的分类:
一.依据位移进行转移的jmp指令

jmp short 标号(转到标号处执行指令)
这种格式的jmp指令实现的是段内短转移,它对IP的修改范围为-128~127,也就是说,它向前转移时
可以最多越过128个字节,向后转移可以最多越过127个字节。

转移指令结束后,CS:IP应该指向标号处的指令。

汇编指令               机器指令
mov ax,0123            b8 23 01
mov ax,ds:[0123]       A1 23 01
push ds:[0123]         FF 36 23 01

可以看到,在一般的汇编指令中,汇编指令中的idata(立即数),不论它是表示一个数据还是内存
单元的偏移地址,都会在对应的机器指令中出现,因为CPU执行的是机器指令,它必须要处理这些
数据或地址。

但jmp指令却不同!!!

jmp指令对应的机器码不包含转移的目的地址,这意味着,CPU在执行机器码指令时候,并不知道转移
的目的地址,那么,CPU根据什么进行转移呢?

CPU在执行jmp指令的时候并不需要转移的目的地址。

jmp指令对应的机器码中没有给出转移的目的地址,但是给出了目的地址与当前指令(jmp)的位移,这个
位移是编译器根据汇编指令中的“标号”计算出来的。

实际上,指令“jmp short 标号”的功能为:(IP)=(IP)+8位位移。
1.8位位移=“标号”处的地址-jmp指令后的第一个字节的地址;
2.short指明此处的位移为8位位移;
3.8位位移的范围为-128~127,用补码表示;
4.8位位移由编译程序在编译时算出。

还有一种和指令“jmp short 标号”功能相近的指令格式:jmp near ptr 标号,它实现的是段内近转移。

指令“jmp near ptr 标号”的功能为:(IP)=(IP)+16位位移。
1.16位位移=指令“标号”处的地址-jmp指令后的第一个字节的地址;
2.near ptr指明此处的位移为16位位移,进行的是段内近转移;
3.16位位移的范围为-32768~32767,用补码表示;
4.16位位移由编译程序在编译时算出。

二.转移的目的地址在指令中的jmp指令

前面讲的jmp指令,其对应的机器指令中并没有转移的目的地址,而是相对于当前IP的转移位移。

指令“jmp far ptr 标号”实现的是段间转移,又称为远转移,功能如下:
 
(CS)=标号所在段的段地址;(IP)=标号在段中的偏移地址。

far ptr指明了指令用标号的段地址和偏移地址修改CS和IP.

assume cs:codesg
codesg segment
  start:mov ax,0
        mov bx,0
        jmp far ptr s
        db 256 dup (0)
      s:add ax,1
        inc ax
codesg ends
end start

请问:jmp short 标号;jmp near ptr 标号 和jmp far ptr 标号是不是和中间的代码长度有关???
例如上面一段代码jmp指令和标号之间的代码长度较长,所以用far ptr;而下面的代码段因为jmp指令
和标号之间的代码长度较短,所以用short!!不知道这样认为对不对???

assume cs:codesg
codesg segment
  start:mov ax,0
        mov bx,0
        jmp short s
        add ax,1
      s:inc ax
codesg ends
end start

同时,我们应该注意jmp far ptr 标号指令所对应的机器码中包含转移的目的地址。这与
jmp short 标号和jmp near ptr 标号指令不同.所以它们属于不同类型的jmp指令~~

三.转移地址在寄存器中的jmp指令

指令格式:jmp 16位寄存器

功能:(IP)=(16位寄存器)

注意:是16位寄存器!8位寄存器不能够,因为IP是16位的,不能与8位寄存器进行操作。

四.转移地址在内存中的jmp指令

转移地址在内存中的jmp指令有两种格式:
1.jmp word ptr 内存单元地址(段内转移)

功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。
比如,下面的指令:
mov ax,0123H
mov ds:[0],ax
jmp word ptr ds:[0]
执行后,(IP)=0123H.

2.jmp 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指令:有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的
位移,而不是目的地址。对IP的修改范围都为:-128~127.

指令格式:jcxz 标号(如果(CX)=0,转移到标号处执行.)
操作:当(cx)=0时,(IP)=(IP)+8位位移;
8位位移=“标号”处的地址-jcxz指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出。
当(cx)!=0时,什么也不做(程序向下执行)。

我们从jcxz的功能中可以看出,指令“jcxz 标号”的功能相当于:
if((cx)==0)jmp short 标号;

loop指令:为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是
目的地址。对IP的修改范围都为:-128~127.

指令格式:loop 标号((cx)=(cx)-1,如果(cx)!=0,转移到标号处执行。)
操作:1.(cx)=(cx)-1;
      2.如果(cx)!=0,(IP)=(IP)+8位位移。

8位位移=“标号”处的地址-jcxz指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出。
如果(cx)=0,什么都不做(程序向下执行)。

我们从loop的功能可以看出,指令“loop 标号”的功能相当于:
(cx)--;
if((cx)!=0)jmp short 标号;


根据位移进行转移的意义??

jmp short 标号
jmp near ptr 标号
jcxz 标号
loop 标号
等几种汇编指令,它们对IP的修改是根据转移目的地址和转移起始地址之间的位移来进行的。在它们
对应的机器码中不包含转移的目的地址,而包含的是到目的地址的位移。

这种设计,使得程序装在内存中的不同位置都可以正确执行。


编译器对转移位移超界的检测

注意:根据位移进行转移的指令,它们的转移范围受到转移位移的限制,如果在源程序中出现了
转移范围超界的问题,在编译的时候,编译器将报错。

比如,下面的程序将引起编译错误:
assume cs:code
code segment
  start:jmp short s
        db 128 dup (0)
      s:mov ax,0ffffh
code ends
end start

jmp short s的转移范围是-128~127,IP最多向后移动127个字节。

注意:我们在第二章中讲到的形如“jmp 2000:0100”的转移指令,是在Debug中使用的汇编指令,
汇编编译器并不认识,如果在源程序中使用,编译时也会报错。


感觉上:jmp short ptr 标号 指令<=jmp near ptr 标号 指令<=jmp far prt 标号 指令

当转移范围在-128~127时,jmp near prt 标号 指令也可以使用,此时在功能上,
jmp short ptr 标号 指令=jmp near ptr 标号 指令
设转移范围为X,当-32768<X<-128或127<X<32767时,不能使用jmp short ptr 标号 指令,应该使用
jmp near ptr 标号 指令。

当标号和jmp far ptr 标号 指令在同一段时,也可以使用jmp far ptr 标号 指令,此时在功能上
jmp near ptr 标号 指令=jmp far prt 标号 指令
而当标号和jmp far ptr 标号 指令不在同一段时,不能使用jmp near ptr 标号 指令,应该使用
jmp far ptr 标号 指令。


第十章 call和ret指令

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

ret和retf

ret指令用栈中的数据,修改IP的内容,从而实现近转移;
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。

CPU执行ret指令时,进行下面两步操作:
1.(IP)=((ss)*16+(sp))
2.(sp)=(sp)+2

CPU执行retf指令时,进行下面的四步操作:
1.(IP)=((ss)*16+(sp))
2.(sp)=(sp)+2
3.(cs)=((ss)*16+(sp))
4.(sp)=(sp)+2

可以看出,如果我们用汇编语法来解释ret和retf指令,则:
CPU执行ret指令时,相当于进行:
pop IP

CPU执行retf指令时,相当于进行:

pop IP
pop CS


CALL指令

CPU执行call指令时,进行两步操作:
1.将当前的IP或CS和IP压入栈中
2.转移。

call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同。

call指令的分类(与jmp指令类似):
一、依据位移进行转移的call指令

call 标号(将当前的IP压栈后,转到标号处执行指令)

CPU执行此种格式的call指令时,进行如下的操作:
1.(sp)=(sp)-2
  ((ss)*16+(sp))=(IP)
2.(IP)=(IP)+16位位移
 
16位位移=“标号”处的地址-call指令后的第一个字节的地址;
16位位移的范围为-32768~32767,用补码表示;
16位位移由编译程序在编译时算出。

从上面的描述中,可以看出,如果我们用汇编语法来解释此种格式的call指令,则:
CPU执行指令“call 标号”时,相当于进行:
push IP
jmp near ptr 标号

二、转移的目的地址在指令中的call指令
前面讲的call指令,其对应的机器码指令中并没有转移的目的地址,而是相对于当前IP的转移位移。

指令“call far ptr 标号”实现的是段间转移。
CPU执行此种格式的call指令时,进行如下的操作:
1.(sp)=(sp)-2
  ((ss)*16+(sp))=(cs)
  (sp)=(sp)-2
  ((ss)*16+(sp))=(ip)
2.(cs)=标号所在段的段地址
  (ip)=标号在段中的偏移地址

从上面的描述中可以看出,如果我们用汇编语法来解释此种格式的call指令,则:
push cs
push ip
jmp far ptr 标号

三、转移地址在寄存器中的call指令
指令格式:call 16位寄存器

功能:
(sp)=(sp)-2
((ss)*16+(sp))=(ip)
(ip)=(16位寄存器)

用汇编语法来解释此种格式的call指令,CPU执行call 16位reg时,相当于进行:
push IP
jmp 16位寄存器

四、转移地址在内存中的call指令

转移地址在内存中的call指令有两种格式:
1.call word ptr 内存单元地址

用汇编语法来解释此种格式的call指令,则:
CPU执行call word ptr 内存单元地址 时,相当于进行:

push IP
jmp word ptr 内存单元地址

2.call dword ptr 内存单元地址

用汇编语法来解释此种格式的call指令,则:
CPU执行call dword ptr 内存单元地址 时,相当于进行:

push CS
push IP
jmp dword ptr 内存单元地址


call和ret的联合使用。

可以利用call和ret来实现子程序的机制。子程序的框架如下:
标号:
     指令
     ret

具有子程序的源程序的框架如下:

assume cs:code
code segment
  main: :
        :
        call sub1            ;调用子程序sub1
        :
        :
        mov ax,4c00h
        int 21h
  sub1: :
        :
        call sub2
        :
        :
        ret
  sub2: :
        :
        :
        ret
code ends
end main


mul指令:乘法指令,使用mul做乘法的时候:
1.两个相乘的数:两个相乘的数,要么都是8位,要么都是16位,如果是8位,一个默认放在AH中(AH??
看例子好像是AL哦!,另一个放在8位寄存器或内存字节单元中;如果是16位,一个默认在AX中,另一个
放在16位寄存器或内存字单元中。
2.结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果高位默认在DX中存放,低位在
AX中存放。

格式如下:
mul reg
mul 内存单元

比如:
mul byte ptr ds:[0]的含义为:
(ax)=(al)*((ds)*16+0);

例如:计算100*10,程序如下:
mov al,100
mov bl,10
mul bl
结果(ax)=1000(03E8H)


汇编利用call和ret指令实现模块化程序设计

参数和结果传递的问题
子程序一般都要根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者。其实,我们
讨论参数和返回值传递的问题,实际上就是在探讨,应该如何存储子程序的参数和产生的返回值。

我们可以用寄存器来存储,用寄存器来存储参数和结果是最常用的方法。

但考虑一下批量数据的传递:
子程序只有一个参数,可以用一个寄存器,如果有两个参数,那么可以用两个寄存器,可是如果需要
传递的数据有3个、4个或更多直至N个,我们怎样存放呢?
寄存器的数量终究有限,我们不可能简单地用寄存器来存放多个需要传递的数据,对于返回值,也有
同样的问题。
在这种时候,我们将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给
需要的子程序。对于具有批量数据的返回结果,也可以用同样的方法。

除了用寄存器传递参数外,还有一种通用的方法是用栈来传递参数。

关于寄存器冲突的问题及解决方法:

解决这个问题的简捷方法是,在子程序的开始将子程序中所有用到的寄存器中的内容都保存起来,在
子程序恢复,我们可以用栈来保存寄存器中的内容。

字符串以0作为结尾符,这个字符串可以如下定义:
db 'conversation',0


注意:不要以为ret指令和call指令一定要一起使用。

assume cs:code

stack segment
  db 16 dup (0)
stack ends

code segment
  mov ax,4c00h
  int 21h
start:mov ax,stack
      mov ss,ax
      mov sp,16
      mov ax,0
      push ax
      mov bx,0
      ret
code ends
end start

子程序的标准框架:

子程序开始:子程序中使用的寄存器入栈
            子程序内容
            子程序中使用的寄存器出栈
            返回(ret、retf)

注意寄存器入栈和出栈的顺序。


第11章 标志寄存器

ZF标志 PF标志

SF标志

我们知道计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以
看成是无符号数。比如:

00000001B,可以看作为无符号数1,或有符号数+1;
10000001B,可以看作为无符号数129,也可以看作有符号数-127.

这也就是说,对于同一个二进制数据,计算机可以将它当作无符号数据来运算,也可以当作有符号数据
来运算。比如:

mov al,10000001B
add al,1

结果:(al)=10000010B
我们可以将add指令进行的运算当作无符号数的运算,那么add指令相当于计算129+1,结果为130
(10000010B);也可以将add指令进行的运算当作有符号数的运算,那么add指令相当于计算-127+1,
结果为-126(10000010B)。

不管我们如何看待,CPU在执行add等指令的时候,就已经包含了两种含义,也将得到用同一种信息来
记录的两种结果,关键在于我们的程序需要哪一种结果。

SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数
来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF的值则没有
意义,虽然相关的指令影响了它的值。

这也就是说,CPU在执行add等指令时,是必然要影响到SF标志位的值的。至于我们需不需要这种影响,
那就看我们如何看待指令所进行的运算了。

比如:

mov al,10000001B
add al,1
执行后,结果为10000010B,SF=1,表示:如果指令进行的是有符号数运算,那么结果为负:

mov al,10000001B
add al,01111111B

执行后,结果为0,SF=0,表示:如果指令进行的是有符号数运算,那么结果为非负。

CF标志

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

我们知道,当两个数相加的时候,有可能产生从最高有效位向更高位的进位。比如,两个8位数据:
98H+98H,将产生进位。由于这个进位值在8位数中无法保存,我们在前面的课程中,就只是简单地说
这个进位值丢失了。其实CPU在运算的时候,并不丢弃这个进位值,而是记录在一个特殊的寄存器的
某一位上。8086CPU就用flag的CF位来记录这个进位值,比如,下面的指令:
mov al,98H
add al,al      ;执行后:(al)=30H,CF=1,CF记录了从最高有效位向更高位的进位值
add al,al      ;执行后:(al)=30H,CF=0,CF记录了从最高有效位向更高位的进位值

OF标志

关于溢出:
在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。

比如说,指令运算的结果用8位寄存器或内存单元来存放,比如:add al,3,那么对于8位的有符号
数据,机器所能表示的范围就是-128~127.同理,对于16位有符号数,机器所能表示的范围是
-32768~32767.

注意,这里所讲的溢出,只是对有符号数运算而言。

一定要注意CF和OF的区别:CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的
标志位。

mov al,98
add al,99

add指令执行后:CF=0,OF=1,前面我们讲过,CPU在执行add等指令的时候,就包含了两种含义:
无符号数运算和有符号数运算。对于无符号数运算,CPU用CF位来记录是否产生了进位;对于有
符号数运算,CPU用OF位来记录是否产生了溢出,当然,还要用SF位来记录结果的符号。对于无
符号数运算,98+99没有进位,CF=0;对于有符号数运算,98+99发生溢出,OF=1.

对于无符号数,8位寄存器能表示的数据范围为0~257.

CF和OF所表示的进位和溢出,是分别对无符号数和有符号数运算而言的。

adc指令

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

指令格式:adc 操作对象1,操作对象2
功能:操作对象1=操作对象1+操作对象2+CF

CPU提供adc指令的目的,就是来进行加法的第二步运算的,adc指令和add指令相配合就可以对更大
的数据进行加法运算。

sbb指令

sbb是带借位减法指令,它利用了CF位上记录的借位值。
指令格式:sbb 操作对象1,操作对象2
功能:操作对象1=操作对象1-操作对象2-CF

计算003E1000H-00202000H,结果放在ax,bx中!

cmp指令

cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器
产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较的结果。

cmp指令格式:cmp 操作对象1,操作对象2
功能:计算操作对象1-操作对象2,但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。

同add、sub指令一样,CPU在执行cmp指令的时候,也包含两种含义:进行无符号数运算和进行有
符号数运算。所以利用cmp指令可以对无符号数进行比较,也可以对有符号数进行比较。

cmp进行无符号数比较和进行有符号数比较,CPU分别用哪些标志位对比较结果进行记录。

CF ZF
ZF SF OF


检测比较结果的条件转移指令:

“转移”指的是它能够修改IP,而“条件”指的是它可以根据某种条件,决定是否修改IP。

除了jcxz之外,CPU还提供了其他条件转移指令,大多数条件转移指令都检测标志寄存器的相关
标志位,根据检测的结果来决定是否修改IP。它们检测的是哪些标志位呢?就是被cmp指令影响
的那些,表示比较结果的标志位。

这些条件转移指令通常都和cmp相配合使用,就好像call和ret指令通常相配合使用一样。

因为cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果
进行转移的指令也分为两种,即:根据无符号数的比较结果进行转移的条件转移指令,它们检测
ZF、CF的值;和根据有符号数的比较结果进行转移的条件转移指令,它们检测SF、OF和ZF的值。

根据无符号数的比较结果进行转移的条件转移指令:
je jne jb jnb ja jna

虽然je的逻辑含义是“相等则转移”,但它进行的操作是,ZF=1时则转移。“相等则转移”这种逻辑含义,
是通过和cmp指令配合使用来体现的,因为是cmp指令为“ZF=1”赋予了“两数相等”的含义。

至于究竟在je之前使不使用cmp指令,在于我们的安排。je检测的是ZF位置,不管je前面是什么指令,
只要CPU执行je指令时,ZF=1,那么就会发生转移。

DF标志和串传送指令

DF,方向标志位。在串处理指令中,控制每次操作后si,di的增减。
DF=0 每次操作后si,di递增;
DF=1 每次操作后si,di递减。

串传送指令movsb

格式:movsb
功能:执行movsb指令相当于进行下面几步操作:
1.((es)*16+(di))=((ds)*16+(si))
2.如果DF=0则:(si)=(si)+1
              (di)=(di)+1
  如果DF=1则:(si)=(si)-1
              (di)=(di)-1

我们可以用汇编语法描述movsb的功能如下:

mov es:[di],byte ptr ds:[si]    ;8086并不支持这样的指令,这里只是个描述。

如果DF=0:

inc si
inc di

如果DF=1:

dec si
dec di

可以看出,movsb的功能是将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器DF位
的值,将si和di递增或递减。

串传送指令movsw,传送一个字

格式:movsw

我们可以用汇编语法描述movsb的功能如下:

mov es:[di],word ptr ds:[si]    ;8086并不支持这样的指令,这里只是个描述。

如果DF=0:

add si,2
add di,2

如果DF=1:

sub si,2
sub di,2

movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb和movsw都和rep配合使用,格式
如下:

rep movsb

用汇编语法来描述rep movsb的功能就是:

s:movsb
loop s

可见,rep的作用是根据cx的值,重复执行后面的串传送指令。由于每执行一次movsb指令,si和di
都会递增或递减指向后一个单元或前一个单元,则rep movsb就可以循环实现(cx)个字符的传送。


rep movsw指令类似。

8086CPU提供下面两条指令对DF位进行设置:

cld指令:将标志寄存器的DF位置0
std指令:将标志寄存器的DF位置1

pushf和popf

pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中。


第十二章 内中断

中断信息及中断
中断信息可以来自CPU内部和外部

内中断的产生:
对于8086CPU,当CPU内部有下面的情况发生的时候,将产生相应的中断信息:
1.除法错误,比如:执行div指令产生的除法溢出;
2.单步执行;
3.执行int0指令;
4.执行int指令。

中断源及中断类型码

在8086CPU中的中断类型码如下:
1.除法错误:0
2.单步执行:1
3.执行int0指令:4
4.执行int指令,该指令的格式为int n,指令中的n为字节型立即数,是提供给CPU的中断类型码。

中断处理程序及其定位。

中断向量及中断向量表

中断向量表在内存中存放,对于8086PC机,中断向量表指定放在内存地址0处。从内存0000:0000
到0000:03E8的1000个单元中存放中断向量表。如果使用8086CPU,中断向量表必须放在0000:0000
到0000:03E8单元中,不能放在别处,这是规定,因为8086CPU就从这个地方读取中断向量表。

一个表现存放一个中断向量,也就是一个中断处理程序的入口地址,对于8086CPU,这个入口地址包括
段地址和偏移地址,所以一个表项占两个字,高地址字存放段地址,低地址字存放偏移地址。

我们知道,可以用中断类型码,在中断向量表中找到中断处理程序的入口,找到这个入口地址的最终
目的是用它设置CS和IP,使CPU执行中断处理程序。用中断类型码找到中断向量,并用它设置CS和IP
这个工作是由CPU的硬件自动完成的。CPU硬件完成这个工作的过程被称为中断过程。

下面是8086CPU在收到中断信息后,所引发的中断过程:
1.(从中断信息中)取得中断类型码;
2.标志寄存器的值入栈;(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中)
3.设置标志寄存器的第8位TF和第9位IF的值为0;
4.CS的内容入栈;
5.IP的内容入栈;
6.从内存地址为中断类型码*4和中断类型码*4+2的两个字单元中读取中断处理程序的入口地址设置
  IP和CS。

更简洁地描述中断过程,如下:
1.取得中断类型码N;
2.pushf
3.TF=0,IF=0
4.push CS
5.push IP
6.(IP)=(N*4),(CS)=(N*4+2)


中断处理程序

由于CPU随时都可能检测到中断信息,也就是说,CPU随时都可能执行中断处理程序,所以中断处理
程序必须一直存储在内存某段空间之中。而中断处理程序的入口地址,即中断向量,必须存储在
对应的中断向量表表项中。

中断处理程序的编写方法和子程序的比较相似,下面是常规的步骤:
1.保存用到的寄存器;
2.处理中断;
3.恢复用到的寄存器;
4.用iret指令返回。

iret指令的功能用汇编语法描述为:
pop IP
pop CS
popf

iret通常和硬件自动完成的中断过程配合使用。可以看到,在中断过程中,寄存器入栈的顺序是
标志寄存器、CS、IP,而iret的出栈顺序是IP、CS、标志寄存器,刚好和其相对应,实现了用执行
中断处理程序前的CPU现场恢复标志寄存器和CS、IP的工作。iret指令执行后,CPU回到执行中断
处理程序前的执行点继续执行程序。

单步中断

CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。

在进入中断处理程序之前,设置TF=0.从而避免CPU在执行中断处理程序的时候发生单步中断。
CPU提供单步中断功能的原因就是,为单步跟踪程序的执行过程,提供了实现机制。


第十三章 int指令

int指令的格式为:int n,n为中断类型码,它的功能是引发中断过程。
CPU执行int n指令,相当于引发一个n号中断的中断过程。

可以在程序中使用int指令调用任何一个中断的中断处理程序。

即使程序中没有做除法,不可能产生除法溢出,但是如果在程序中使用了int 0指令。CPU执行int 0指令
时,将引发中断过程。

可见,int指令的最终功能和call指令相似,都是调用一段程序。不同之处在于call调用的是代码段中的
子程序,而int调用的是长期驻内存的中断例程。

一般情况下,系统将一些具有一定功能的子程序,以中断处理程序的方式提供给应用程序调用。我们
在编程的时候,可以用int指令调用这些子程序。当然,也可以自己编写一些中断处理程序供别人使用。

中断处理程序简称为中断例程。

int指令和iret指令的配合使用与call指令和ret指令的配合使用具有相似的思路。

编写中断例程和编写子程序的时候具有同样的问题,就是要避免寄存器的冲突。应该注意例程中用到
的寄存器的值的保存和恢复。

BIOS和DOS所提供的中断例程。

在系统板的ROM中存放着一套程序,称为BIOS(基本输入输出系统),BIOS中主要包含以下几部分内容:
1.硬件系统的检测和初始化程序;
2.外部中断和内部中断的中断例程;
3.用于对硬件设备进行I/O操作的中断例程。
4.其他和硬件系统相关的中断例程。

操作系统DOS也提供了中断例程,从操作系统的角度来看,DOS的中断例程就是操作系统向程序员提供的
编程资源。

BIOS和DOS在所提供的中断例程中包含了许多子程序,这些子程序实现了程序员在编程的时候经常需要
用到的功能。程序员在编程的时候,可以用int指令直接调用BIOS和DOS提供的中断例程,来完成某些
工作。

和硬件设备相关的DOS中断例程中,一般都调用了BIOS的中断例程。

BIOS和DOS提供的中断例程是如何安装到内存中的呢?下面看看它们的安装过程:
1.开机后,CPU一加电,初始化(CS)=0FFFFH,(IP)=0,自动从FFFF:0单元开始执行程序,FFFF:0处
有一条跳转指令,CPU执行该指令后,转去执行BIOS中的硬件系统检测和初始化程序。
2.初始化程序将建立BIOS所支持的中断向量,即将BIOS提供的中断例程的入口地址登记在中断向量表
中。注意,对于BIOS所提供的中断例程,只需将入口地址登记在中断向量表中即可,因为它们是固化

BIOS中断例程应用

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

到ROM中的程序,一直在内存中存在。
3.硬件系统检测和初始化完成后,调用int 19h进行操作系统的引导。从此将计算机交由操作系统控制。
4.DOS启动后,除完成其他工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量。

一般来说,一个供程序员调用的中断例程中往往包括多个子程序,中断例程内部用传递进来的参数来
决定执行哪一个子程序。BIOS和DOS提供的中断例程,都用ah来传递内部子程序的编号。

内存地址空间中,B8000h~BFFFFh共32K的空间,为80*25彩色字符模式的显示缓冲区。一屏的内容在
显示缓存区中共占4000个字节。

显示缓冲区分为8页,每页4K(约等于4000),显示器可以显示任意一页的内容。一般情况下,显示
第0页的内容。也就是说,通常情况下,B8000~B8F9F中的4000个字节的内容将出现在显示器上。

DOS中断例程应用

int 21h中断例程是DOS提供的中断例程,其中包含了DOS提供给程序员在编程时调用的子程序。

我们从前一直使用的是int 21h中断例程的4ch号功能,即程序返回功能,如下:

mov ah,4ch       ;程序返回
mov al,0         ;返回值
int 21h

(ah)=4ch表示调用第21h号中断例程的4ch号子程序,功能为程序返回,可以提供返回值作为参数。

DOS为程序员提供了许多可以调用的子程序,都包含在int 21h中断例程中。


第14章 端口

在PC机系统中,和CPU通过总线相连的芯片除各种存储器外,还有以下3种芯片:
1.各种接口卡(比如:网卡、显卡)上的接口芯片,它们控制接口卡进行工作;
2.主板上的接口芯片,CPU通过它们对部分外设进行访问;
3.其他芯片,用来存储相关的系统信息,或进行相关的输入输出处理。

在这些芯片中,都有一组可以由CPU读写的寄存器。这些寄存器,它们在物理上可能处于不同的芯片
中,但是它们在以下两点上相同:
1.都和CPU的总线相连,当然这种连接是通过它们所在的芯片进行的;
2.CPU对它们进行读或写的时候都通过控制线向它们所在的芯片发出端口读写命令。

可见,从CPU的角度,将这些寄存器都当作端口,对它们进行统一编址,从而建立了一个统一的
端口地址空间。每一个端口在地址空间中都有一个地址。(这和我们把各种存储器看做一个由
若干存储单元组成的逻辑存储器相似,这个逻辑存储器我们称其为内存地址空间)

CPU可以直接读写3个地方的数据:
1.CPU内部的寄存器;
2.内存单元;
3.端口。(也就是CPU外部的寄存器)

端口的读写

在访问端口的时候,CPU通过端口地址来定位端口。因为端口所在的芯片和CPU通过总线相连,所以,
端口地址和内存地址一样,通过地址总线来传送。在PC系统中,CPU最多可以定位64K个不同的端口
则端口地址的范围为0~65535.(这跟内存地址不一样啊,8086CPU因为是20根总线,内存地址可以
达到1M,按道理说,端口地址应该也可以达到1M)

端口的读写指令只有两条:in和out,分别用于从端口读取数据和往端口写入数据。

对0~255以内的端口进行读写时:

in al,20h       ;从20h端口读入一个字节
out 20h,al       ;往20h端口写入一个字节

对256~65535的端口进行读写时,端口号放在dx中:

mov dx,3f8h        ;将端口号3f8h送入dx
in al,dx           ;从3f8h端口读入一个字节
out dx,al         ;向3f8h端口写入一个字节


shl和shr指令

shl是逻辑左移指令,它的功能为:
1.将一个寄存器或内存单元中的数据向左移位;
2.将最后移出的一位写入CF中;
3.最低位用0补充。

指令格式: shl 寄存器或内存单元,移动位数

如果移动位数大于1时,必须将移动位数放在cl中。

BCD码

BCD码是以4位二进制数表示十进制数码的编码方法,如下所示:
0,0000,1,0001,2,0010,3,0011,4,0100,5,0101,6,0110,7,0111
8,1000,9,1001

比如:数值26,用BCD码表示为:0010 0110

可见,一个字节可表示两个BCD码。


第15章 外中断

以前我们讨论的都是CPU对指令的执行。我们知道,CPU在计算机系统中,除了能够执行指令,进行
运算以外,还应该能够对外部设备进行控制,接收它们的输入,向它们进行输出。也就是说,CPU
除了有运算能力外,还要有I/O(Input/Output,输入/输出)能力。

接口芯片和端口

在PC系统的接口卡和主板上,装有各种接口芯片。这些外设接口芯片的内部有若干寄存器,CPU将
这些寄存器当作端口来访问。

外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中;CPU向外设的输出也不是直接
送入外设,而是先送入端口中,再由相关的芯片送到外设。CPU还可以向外设输出控制命令,而这些
控制命令也是先送到相关芯片的端口中,然后再由相关的芯片根据命令对外设实施控制。


可见,CPU通过端口和外部设备进行联系。

外中断信息:
汇编编译器可以处理表达式,比如:
指令:mov ax,(5+3)*5/10,被编译器处理为指令:mov ax,4

好像不能mov 段寄存器,段寄存器
好像不能mov 内存单元,内存单元

内存怎么释放或回收??

注意pop操作并没有将数据从栈中删除,数据仍然在栈中,只是改变SP指针,因此此数据不再被
当作栈的元素了。

关于原码、反码、补码的知识。

12的原码为:00001100B 反码:00001100B 补码:00001100B
-12的原码为:10001100B 反码:11110011B 补码:11110100B

正数的原码、补码、反码相同。
负数的反码为其原码除符号位外其它位取反
负数的补码为其反码+1

计算机系统一般用补码表示有符号数。至于无符号数直接用本身二进制形式表示就可以了。

8位寄存器能保存的无符号数范围为0~255
8位寄存器能保存的有符号数范围为-127~127

原码就是这个数本身的二进制形式。
一个数和它的补码是可逆的。


计算机系统怎么保存130、128呢?
130当作无符号数不会溢出,当作有符号数又会溢出,怎么办?

原码、反码、补码都是相对于有符号数而言的,无符号数直接表示成二进制形式,没有所谓的
原码、反码和补码形式。

10000001
当作无符号数是129;
当作有符号数时:
1.如果是原码表示法:-1
2.如果是补码表示法:-127
3.如果是反码表示法:-126

但在计算机系统中10000001表示-127,因为在计算机系统中用补码表示负数。

计算机系统如何区分无符号数和负数?

mov al,130这条指令正确吗?
如果计算机系统将130当作无符号数,那么可以存入8位寄存器al,但如果计算机系统将其当作
有符号数,那么不可以存入al,不知道计算机如何处理这种情况?

应该是正确的,此时计算机系统将130看作无符号数对待。所以如果出现下面的指令:
add al,-1 将产生错误,因为进行无符号数和有符号数运算。

 

关于原码、反码、补码的加减运算

98+99

01100010
01100011
--------
11000101

CF=0 OF=1
如果是无符号数,结果为11000101,即197,正确。
如果是有符号数,因为OF=1,发生溢出,所以结果错误。


11000101既可以当作无符号数197看待,也可以当作有符号数-59看待,这样会不会有问题呢?

考虑11000101+1

  11000101
+ 00000001
-----------
  11000110

如果是无符号数运算,结果为11000110即198,正确。
如果是有符号数运算,结果为11000110即-58,正确。

考虑-59+98

  11000101
+ 01100010
------------
 100100111

溢出忽略不计,转为十进制=39

考虑-59+59

  11000101
+ 00111011
------------
 100000000

考虑-59+60

  11000101
+ 00111100
------------
 100000001


十进制 248 对应二进制 0000 0000 1111 1000
十进制 -56 对应二进制 1111 1111 1100 1000
两个二进制数相加
  0000 0000 1111 1000
+ 1111 1111 1100 1000
-----------------------
1 0000 0000 1100 0000
溢出忽略不计

0000 0000 1100 0000换为十进制=192

db 97          01100001
db 'a'         01100001
dw 97          00000000 01100001

因为字符只占一个字节,所以只能用db声明字符形式的数据
如果要显示97,必须将97看作字符串,即9和7
mov ax,0b800h
mov ds,ax
mov byte ptr ds:[...],57
mov byte ptr ds:[...],55

假设某个内存中存储了01100001,那么我们必须根据它的类型来对待它,如果是int型,则当作字符串
’97‘,分别取得ASCII码57和55,而如果是char型,则取得ASCII码97

在汇编语言中,如果某个内存中存储了01100001,则看我们自己的需要了(即程序的需要),如果我们
把内存中的二进制当作数据对待,需要显示的是97,那么使用下面的指令:
mov ax,0b800h
mov ds,ax
mov byte ptr ds:[...],57
mov byte ptr ds:[...],55
如果我们把内存中的二进制当作字符对待,则需要显示的是字符’a‘,那么使用下面的指令
mov ax,0b800h
mov ds,ax
mov byte ptr ds:[...],97


char c = 'a';   01100001
int n = 97;     00000000 01100001
char *p = &n+1;
printf("%c,%d,%c",c,n,*p);
mov ax,0b800h
mov ds,ax
mov byte ptr ds:[...],97
mov byte ptr ds:[...],57
mov byte ptr ds:[...],55
mov byte ptr ds:[...],97

显示只能用mov byte ptr,不能用mov word ptr
例如下面的代码能够显示的只有
mov byte ptr ds:[160*12+40*2],61h和mov byte ptr ds:[160*12+40*2+6],48两句。

assume ds:data,cs:code
data segment
  db 61h
  db 'a'
  dw 61h
data ends

code segment
  start:mov bx,0b800h
        mov ds,bx
        mov byte ptr ds:[160*12+40*2],61h
        mov byte ptr ds:[160*12+40*2+6],48
        mov word ptr ds:[160*12+40*2+2],0061h
        mov word ptr ds:[160*12+40*2+4],48  
        mov ax,4c00h
        int 21h
code ends
end start


从键盘输入97和a 有什么区别?
 
请问,a的ASCII码是97,那从键盘输入97和a 有什么区别? 在内存中是怎么存储? 谢谢 !

从键盘输入后可以有很多中方式,
如果是读取的 int 数据,
那么输入 97 ,
和这个 a 就没有什么区别:

int i;
scanf("%d", &i);
printf("%c",i);

输入 97 , 输出字符为 a ...

同样输入了字符 a (以字符接受),
用整型输出的时候也可以得到 97 ....

只要在范围内,int 和 char 可以直接转化 。

如果 97 是以字符串变量被接收,
那么 和 a 就没有什么关系了 ....

在内存中存储和你的变量类型相关,
如果是一个 int i = 97;
那么在内存中就是一个整型数据(转化到 2进制补码 存储);
如果是字符串,
那么就是多个字符,
需要一个一个字符分别存储了 ....

键盘输入97 是9 和7 两个字符,ascii码分别是55 和57
字符a在内存中存的是二进制的97,而97是二进制的55和57输入的

你可能感兴趣的:(汇编学习笔记)