汇编语言

汇编是各种CPU提供的机器指令的助记符的集合,人们可以用汇编语言直接控制硬件系统进行工作。

 

汇编指令由三类指令组成:

汇编指令(核心):有对应机器码

伪指令:没有对应机器码,由编译器执行

其他符号:如+ - * /等,由编译器识别,没有对应机器码

 

一个典型的CPU由运算器,寄存器,控制器组成。

 

寄存器16位( 8086 CPU )

通用寄存器

AX、BX、CX、DX

存放一般性的数据

可通过mov指令修改

段寄存器

CS、DS、SS、ES

存放段地址

(CS存放代码段寄存器,cpu只认被CS:IP指向的内存为指令)

(DS通常用来存放数据的段地址。)

 

CS和IP不可通过mov指令修改,可通过转移指令修改,如jmp。

“jmp 短地址:偏移地址”同时修改CS和IP。

“jmp 某一合法寄存器”仅修改IP。

 

DS 可以用mov 修改(只能把数据先mov到一般寄存器,然后从一般寄存器mov到DS)。

“mov al, [0]”数据段地址默认存在DS中,执行指令时,cpu自动从DS取段地址。

指令指针寄存器

IP

 

 

SI、DI

SI和DI是8086cpu中和bx功能相近的寄存器。

si和di不能分成两个8位寄存器来使用。

标志寄存器

FLAG

第6位ZF:零标志位,记录相关指令执行后,结果是否为0.结果为0,zf为1。

第2位PF:奇偶标志位。记录相关指令执行后,结果的所有bit中,1的个数是否为偶数。

第7位:SF,符号标志位,记录相关指令执行后,结果是否为负。

第0位:CF,进位标志位,在进行无符号数运算的时候,记录了运算结果的最高有效位向更高有效位的进位,或从更高位的借位。

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

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

 

 

 

 

 

 

 

一个8位寄存器所能存储的数据的最大值为多少?

为了区分不同的进制,十六进制数据后加H,二进制后加B,十进制什么都不加

在写一条汇编指令或者寄存器名称时不区分大小写。

 

 

8086CPU 给出物理地址的方法:

8080cpu有20位地址总线,8086CPU又时16位结构,一次处理/传输/暂存的地址为16位。

8086cpu采用2个16位地址(段地址和偏移地址)合成的方法形成20位的物理地址。

物理地址=段地址*16+偏移地址

 

内存中字的存储:

CPU中,用16位寄存器存储一个字。高8位存放高字节,低8位存放低位字节。

在内存中,字的低位字节存放在低地址单元,高位字节存放在高地址单元。

 

栈:

栈中字的存储方式是低位低地址(靠近栈顶),高位高地址(靠近栈底)。

任意时刻SS:SP指向栈顶元素。

8086CPU不保证我们对栈的操作不会越界。

栈操作以字为单位

(PUSH 或者POP 内存偏移地址时,自动获取DS)

 

 

 

 

MOV

 

SUB

 

ADD

 

POP

 

PUSH

 

LOOP

 

OFFEST

 

JZ

 

LEA

 

TEST

Test命令将两个操作数进行逻辑与运算,并根据运算结果设置相关的标志位。但是,Test命令的两个操作数不会被改变。运算结果在设置过相关标记位后会被丢弃。

JLE

意思是小于等于,则跳转,转移条件寄存器描述是ZF=1 OR SF≠OF

JNE

不等于则跳转

JE

 

JG

 

AND

 

OR

同位 同为0时才为0 否则为1

div

div是除法指令,使用div做除法运算时,

  1. 除数:有8位和16位两种,在一个reg或者内存单元中。
  2. 被除数:默认放在AX(如果除数为8位,被除数为16位)或者 DX和AX中(如果除数为16位,被除数为32位,DX放高16位,AX放低16位)。
  3. 结果:如果除数为8位,则AL存商,AH存余数。如果除数为16位,AX存商,DX存余数。

Jmp

无条件转移,可只修改ip,也可同时修改CS和IP.

Jmp指令要给出的两种信息。

  1. 转移的目的地址
  2. 转移的距离(段间转移、段内短转移、段内近转移)

jcxz

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

指令格式“ jcxz 标号” :如果(cx)=0转移到标号处执行。操作:(IP)=(IP)+8位位移。

(八位位移计算方法,范围,表示方式同JMP指令。

Loop

循环指令,所有的循环指令都是短转移。在对应机器码中包含转移的位移。

指令格式 “loop标号”:(cx)=(cx)-1:如果(cx)不等于0,转移到标号处执行。

位移的计算,范围,表示同jcxz。

 

RET

指令用栈数据,修改IP的内容,实现近转移。

  1. (IP)=((SS)*16+(SP))
  2. (SP)=(SP)+2

相当于执行 pop IP

 

RET后加一个数字  相当于在RET后ESP加上操作数

RETF

指令用栈中的数据,修改CS和IP,实现远转移。

(1)(IP)=((SS)*16+(SP))

(2)(SP)=(SP)+2

(3)(CS)=((SS)*16+(SP))

(4)(SP)=(SP)+2

相当于执行 pop IP

           pop CS

CALL

CPU 执行call指令时,进行两步操作:

  1. 将当前的IP或CS和IP压入栈中
  2. 转移

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

ADC

带进位加法指令,利用了CF位上记录的值。方便对任意大的数据进行加法运算。

SBB

带借位减法指令。利用了CF位上记录的借位值。

CMP

cmp比较指令,相当于减法指令,只是不保存结果,但对标志寄存器产生影响。

JE

等于则转移

JNE

 

JB

低于则转移

JNB

 

JA

高于则转移

JNA

 

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

 

MOVSW

一次传送一个字

REP

REP 的作用是根据CX的值,重复执行后面的串传送指令。rep movsb可以实现(cx)个字符的传送。

CLD

将标志寄存器df置0

 

STD

将标志寄存器df置1

PUSHF

将标志寄存器的值压栈

POPF

从栈中弹出数据,送入标志寄存器中。

 

[BX]:

[BX]指的是偏移地址在BX中,段地址在DS中的那个内存单元。

要完整描述一个内存单元,需要内存单元地址和内存单元长度(类型),单元长度可以从其他操作对象(比如寄存器)指出。

 

在工具书中(地址)用来描述对应地址的内容,地址只能是一个20位数据。

 

Loop指令:

loop指令格式是“loop 标号”。

CPU执行loop指令时进行两个操作:(CX)=(CX)-1 ,判断CX的值,不为零则转至标号处执行程序,为零则向下执行。

例子:

s: add ax,ax

  loop s

CX和LOOP配合实现循环的三个要点:CX存放循环次数。Loop指令中标号所标识的地址要在前面。循环体在标号和LOOP指令中间。

 

在出现访问内存单元的指令中,用于显示指明内存单元段地址的“ds”“ss”“cs”“es”,在汇编语言中称为段前缀

 

 

一段安全的空间:

我们需要直接向一段内存中写入内容时,这段内存空间不应存放系统或者其他程序的数据或者代码,否则写入操作可能引发错误,DOS方式下,一般情况,0:200~0:2ff空间中没有系统或者其他程序的数据或者代码

 

 

在汇编程序中,用’......’的形式指明数据以字符的形式给出, 编译器将他们转换为相应的ASCII码。

 

 

[BX+idata]:

[BX+idata]指明一个内存单元,他的偏移地址为(bx)+idata,内存单元地址为((ds)*16+(bx)+idata)。这种形式便于数组的处理。

 

 

一般来说,需要暂存数据的时候,应该使用栈。

 

 

数据处理的两个基本问题:

  1. 数据在什么地方
  2. 数据有多长

在工具书中,使用reg表示寄存器,sreg表示段寄存器。

  1. 在8086CPU中,只有BX、SI、DI、BP这四个寄存器可以用在“[.....]”中进行内存单位的寻址,[cx]、[dx]、[ds]都是错的。
  2. 在[....]中,这四个寄存器可以单个出现,也可以组合出现。
  3. 只要在[....]中出现BP,而指令中没有明显给出段地址,段地址默认在SS中。

 

 

机器指令处理的数据在什么地方:

三个地方:CPU内部、内存、端口。如 “mov bx ,1”和“mov bx ,cx”是在cpu内部,“mov bx,[0]”是在内存。

 

汇编语言中数据位置的表达:

  1. 立即数(idata)
  2. 寄存器
  3. 段地址(SA)和偏移地址(EA)。如mov AX,[.....]。

 

寻址方式:

寻址方式

名称

含义

[idata]

直接寻址

SA=(ds)

[bx]

寄存器间接寻址

 

[si]

 

[di]

 

[bp]

SA=(ss)

[bx+idata]

寄存器相对寻址

 

[si+idata]

 

[di+idata]

 

[bp+idata]

SA=(ss)

 

 

[bx+si]

基址变址寻址

 

[bx+di]

 

[bp+si]

SA=(ss)

[bp+di]

SA=(ss)

 

 

 

[bx+si+idata]

相对基址变址寻址

 

[bx+di+idata]

 

[bp+si+idata]

SA=(ss)

[bp+di+idata]

SA=(ss)

 

 

 

 

 

 

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

8086CPU 的指令可以处理两种尺寸的数据,byte和word。所以在机器指令中要指明,指令进行的是字操作还是字节操作。

  1. 通过寄存器名指明要处理的数据的尺寸。
  2. 在没有寄存器名存在的情况下,用操作符X ptr指明单元内存的长度,X 在汇编指令中可以为word 或者byte.

 

 

伪指令dd:

db和dw分别定义字节和字型数据。dd定义dword型数据。

 

dup:

dup是操作符,同db、dw、dd、一样,由编译器识别处理的符号,用来进行数据的重复。例:

db 2 dup (0)

db 2 dup (0,1,2)

db 2 dup (‘abc’,’ABC’)

 

可见dup的使用格式如下:

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

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

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

 

定义200字节的栈段:

法1.

stack segment

dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

 

stack ends

 

法2.

stack segment

db 200 dup (0)

stack ends

 

 

 

转移指令的原理:

可以修改Ip,或者同时修改CS和IP的指令统称为转移指令。

只修改IP称为段内转移,比如 jmp ax.

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

段内转移又分为短转移和近转移。短转移的修改范围是-128~127。近转移IP的修改范围为-32768~32767。

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

无条件转移

条件转移

循环指令

过程

中断

 

操作符offset:

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

 

 

依据位移进行转移的jmp指令:

Jmp short 标号(转到标号处执行指令):段内短转移。指令对应的机器码中,并不包含转移的目的地址,而包含的是转移的位移,位移是根据汇编指令中的标号计算出来的。

Jmp short 标号:功能(IP)=(IP)+8位位移:

8位位移=标号处地址-jmp指令后第一个字节的地址。

8位位移用补码表示

位移由编译器在编译程序时算出

Jmp near ptr 标号:功能为:(IP)=(IP)+16位位移。

16位位移的计算和表示同上。

 

 

转移目的地址在指令中的jmp指令:

“jmp far ptr 标号”实现的是段间转移,又称远转移。far ptr 指明了指令用标号的段地址和偏移地址同时修改CS:和IP

 

 

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

格式:jmp 16位reg

 

 

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

  1. jmp word ptr  内存单元地址(段内转移) ,内存单元中存放的是转移的目的偏移地址
  2. Jmp dword ptr内存单元地址(段间转移),高地址处存的是转移的目的段地址,低地址处是转移目的偏移地址。(CS)=(内存单元地址+2)(IP)=(内存单元地址)

 

CALL 和RET:

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

 

 

依据位移进行转移的call指令:

Call标号(将当前IP压入栈后,转到标号处执行指令)。

具体操作:

  1. (sp)=(sp)-2

((ss)*16+(sp))=(IP)

  1. (IP)=(IP)+16位位移

相当于:push IP

Jmp near ptr 标号

关于位移参考jmp指令。

 

 

转移的目的地址在指令中的call 指令:

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

Cpu 执行此种格式的call 指令时,进行如下操作。

1.(sp)=(sp)-2

   ((ss)*16+(sp))=(cs)

   (sp)=(sp)-2

   ((ss)*16+(sp))=(ip)

2  (cs)=标号所在段的段地址

       (ip)=标号在段中的偏移地址

相当于:

Push cs

Push ip

Jmp far ptr 标号

 

指令中这么存放地址?

 

 

转移地址在寄存器中的call指令:(近转移)

指令格式:call 16位reg

(sp)=(sp)-2

((ss)*16+(sp))=(ip)

(ip)=(16位reg)

 

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

转移地址在内存中的call指令有两种格式。

  1. (近转移)call word ptr 内存单元地址

相当于push ip

Jmp word ptr 内存单元地址

  1. (远转移)call dword ptr 内存单元地址

相当于push cs

Push ip

Jmp far ptr 内存单元地址。

 

 

call与ret的配合使用:

在需要的时候,用call指令转去执行子程序,在执行完子程序后,在子程序后面使用ret指令,用栈中的数据设置IP值,从而转到call指令后面的代码处执行。

 

 

 

标志寄存器:

 

作用:

存储指令执行结果;为CPU执行相关指令提供行为依据;控制CPU工作方式。

在8086cpu中,标志寄存器有16位,存储的信息被称为程序状态字(PSW)。

在8086cpu指令集中,有的指令是影响标志寄存器的,比如,add、sub、mul、div、

、inc、or、and等,他们大多都是运算指令(逻辑或者算术运算)。有的指令的执行对标志寄存器没有影响。比如mov push pop 等,他们大多都是传送指令。

计算机通常用补码来表示有符号数据,计算机中的一个数据可看作是有符号数,也可看做是无符号数。对于同一个数据,计算机可把它当作有符号数运算,也可把它当作无符号数运算,不管我们如何看待,如add指令执行的时候就包含了两种含义,也将得到用同一种x信息来记录两种结果。关键在于我们程序需要哪一种结果。

CF是对无符号数运算有效的标志位,而OF是对有符号数运算有意义的标志位。他们之间没有任何关系。

 

 

内中断产生:

8086CPU当有下面情况产生的时候,将会产生相应的中断信息:

  1. 除法错误:如div指令产生的除法溢出
  2. 单步执行
  3. 执行into指令
  4. 执行int指令

上述的四种中断源,在8086CPU中的中断类型码(字节型数据,可表示256种中断源)如下:

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

 

 

中断处理程序:

根据中断信息中的中断类型码定位中断处理程序(得到段地址和偏移地址):

 

CPU用中断类型码,查找中断向量表,就可以得到中断c处理程序的入口地址。

中断向量表在内存呢中存放。

对于8086PC机,中断向量表指定放在内存地址0处,从0000:0000到0000:03ff的1024个内存单元存放着中断向量表。

一个表项两个字,高地址字存放段地址。低地址存放偏移地址。

 

 

中断过程:

CPU在收到中断信息之后,如果处理该中断信息,就完成一个由硬件自动执行的中断过程(程序员无法改变这个过程中所要做的工作)。

中断过程的简洁秒速:

  1. 取得中断码N
  2. pushf
  3. TF=0,IF=0 (标志寄存器的8、9位)
  4. CS内容入栈
  5. IP内容入栈
  6. 从地址为 中断类型码*4+中断类型码*4+2 的两个字单元中获取中断处理程序的入口地址设置IP和CS。

         在最后一步完成后,cpu开始执行由c程序员编写的中断处理程序。

 

 

 

描述了单元长度的标号:

 

上图中箭头处都是标号,近表示内存单元的地址。

 

上图中标号(称为数据标号)后面不带:,不仅表示内存地址,还表示内存单元的长度。

 

 

在其他段中使用数据标号:

(在后面加有:的地址标号,只能在代码段中使用,不能再其他段中使用)

 

 

如果想在代码段中直接使用数据标号访问数据,则需要伪指令assume将标号所在的段和一个段寄存器联系起来。否则编译器在编译的时候,无法确定标号的段地址在那个寄存器中。当然,这种联系是编译器所需要的,但这不是说,因为编译器工作需要,用assume将段寄存器和某个段相联系,段寄存器就真的会存放改段地址,在程序中还要使用指令对段寄存器进行设置。

 

 

程序入口地址的直接地址表:

可以在直接地址表中存储子程序的地址,方便实现不同子程序的调用。

 

posted @ 2020-09-06 19:54  KnowledgePorter  阅读(230)  评论(0)    收藏  举报