汇编语言(王爽)第三版笔记

汇编语言

第一章 基础知识

汇编语言的组成

  1. 汇编指令:机器码的助记符,有对应的机器码。
  2. 伪指令:没有对应的机器码,由编译器执行,计算机并不执行。
  3. 其他符号:如+、-、*、/等,由编译器识别,没有对应的机器码
    在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。

CPU对存储器的读写

存储器,即我们平时所说的内存,存储了指令和数据。存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号,这些编号可以看作存储单元在存储器中的地址。
CPU想要进行数据的读写,必须和外部器件(芯片)进行下面3类信息交互:

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

CPU通过地址总线、控制总线和数据总线将这些信息传到存储器芯片中。
总线中有 n n n根数据线就可以传输 2 n 2^n 2n的数据。控制总线是一些不同控制线的集合。控制总线的宽度决定了CPU对外部器件的控制能力。

第二章 寄存器

一个典型的CPU由运算器、控制器、寄存器等器件构成。在CPU中:

  1. 运算器进行信息处理;
  2. 存储器进行信息存储;
  3. 控制器控制各种器件进行工作;
  4. 内部总线连接各种器件,在他们之间进行数据的传送。
    8086CPU有14个寄存器,每个寄存器有一个名称。这些寄存器是:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。

通用寄存器

8086CPU的所有寄存器都是16位的,可以存放两个字节。AX、BX、CX、DX这4个寄存器通常用来存放一般性的数据,被称为通用寄存器。而上一代的寄存器都是8位的,因此这四个寄存器又可以分为两个可独立使用的8位寄存器来用。AX=AH+AL;BX=BH+BL;CX=CH+CL;DX=DH+DL。L代表的是X的低8位寄存器,H代表的是高八位寄存器。
8086CPU可以一次性处理以下两种尺寸的数据:

  1. 字节:byte,8bit
  2. 字:word,2byte,16bit

几条汇编指令

指令意义
mov a,b把b移入a中
add a,ba+=b

物理地址

8086CPU有20位地址总线,但它是16位结构,在内部一次性处理、传输、暂时存储的地址为16位。所以8086CPU采用两个16位地址合成的方法来形成一个20位的物理地址。
当8086CPU要读写内存时:

  1. CPU中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址;
  2. 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件;
  3. 地址加法器将两个16位地址合成为一个20位的物理地址;
  4. 地址加法器通过内部总线将20位物理地址送入输入输出控制电路;
  5. 输入输出控制电路将20位物理地址送上地址总线
  6. 20位物理地址被地址总线传送到存储器
    地址加法器采用物理地址=段地址*16+偏移地址的方法用段地址和偏移地址合成物理地址。

段寄存器

8086CPU有4个段寄存器:CS、DS、SS、ES。当8086CPU要访问内存时,由这4个段寄存器提供内存单元的段地址。

CS和IP

CS为代码段寄存器,IP为指令指针寄存器。若CS内容为M,IP内容为N,则8086CPU将从内存为M*16+N单元开始读取一条指令并执行,其工作过程可以概括为:

  1. 从CS:IP指向的内存单元中读取指令,读取的指令进入指令缓冲器;
  2. IP=IP+所读取指令的长度,从而指向下一条指令;
  3. 执行指令。跳转到步骤(1),重复这个过程。
    mov指令无法修改CS:IP的值,jmp指令可以。

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

内存中字的存储

CPU中用16位寄存器来存储一个字。高8位存放高位字节,低8位存放低位字节。而在内存单元中低位字节放在低地址单元中,高位字节放在高地址单元中(小端法)。
字单元:存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。高地址内存单元中存放字型数据的高位字节,低地址内存单元中存放字型数据的低位字节。

DS和[address]

DS寄存器给出CPU要读写的内存单元的段地址。偏移地址用[偏移地址]表示
8086CPU不支持将数据直接送入段寄存器的操作,因此需要利用另一个一般的寄存器进行中转:

mov bx,1000H
mov ds,bx
mov [0],al

用mov指令访问内存单元可以只给出单元的偏移地址,此时段地址默认在DS寄存器中

字的传送

一些可以执行的指令:

指令目标来源
mov寄存器数据
mov寄存器寄存器
mov寄存器内存单元
mov内存单元寄存器
mov段寄存器寄存器
mov寄存器段寄存器
mov内存单元段寄存器
mov段寄存器内存单元

特点:后进先出

CPU提供的栈机制

8086CPU提供入栈和出栈的指令分别是PUSH和POP
栈顶元素由SS:IP指向。
push ax

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

pop ax

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

内存分配:当你将地址A~地址B当作栈空间时,SP的初始位置应该是地址B的偏移地址+2.(栈空间的第一个元素的偏移地址是地址B的偏移地址-2,SP是基于偏移地址-2再加的4)
我们可以将一组地址连续、起始地址是16的倍数的内存单元当作栈空间来使用。使用的时候要注意栈可能越界(push和pop都可能越界),这个时候都会把其他不相干的地址空间的数据覆盖掉或者取出。

第四章 第一个程序

一个源程序从写出到执行的过程

  1. 编写汇编源程序
  2. 对源程序进行编译连接生成可执行文件
  3. 执行可执行文件中的程序

可执行文件中包含两部分内容:

  1. 程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
  2. 相关的描述信息(比如,程序有多大、要占用多少内存空间等)

源程序

下面是一段简单的汇编语言源程序

assume cs:codesg
codesg segment
	mov ax,0123H
	mov bx,0456H
	add ax,bx
	add ax,ax
	mov ax,4c00H
	int 21H
codesg ends
end

汇编语言源程序中包含汇编指令和伪指令。

伪指令

XXX segment…… XXX ends

segment和ends的功能是定义一个段。segment说明一个段开始,ends说明一个段结束。一个段必须有一个名称来标识,使用格式为:

段名 segment
……
段名 ends
end

end是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译。
end!=ends,后者是和segment成对使用的,可以看成是“end segment”。

assume

假设某一段寄存器和程序中的某一个用segment…end定义的段相关联。

源程序中的“程序”

源程序包括伪指令和汇编指令。汇编指令组成了最终由计算机执行的程序,伪指令是由编译器处理的。这里说的程序就是指源程序中最终由计算机执行、处理的指令或数据

标号

一个标号作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址

程序的结构

  1. 定义一个段的名称
  2. 在这个段中写入汇编指令
  3. 指出程序在哪里结束(end)
  4. 将段和寄存器联系起来(assume)

程序返回

mov ax,4c00H
int 21H

程序是由command.com从可执行文件中加载入内存并转交CPU的控制权的,因此结束运行后要把控制权还给它。
一个程序前会有256位的PSP内容,因此程序内容在DS所指的后256位处存储。

编辑、编译、连接、执行

dosbox相关操作

第五章 [bx]和loop指令

概述

[bx]和内存单元的描述

[bx]表示一个内存单元,它的偏移地址在bx中(段地址在ds中)
()表示一个寄存器或一个内存单元中的内容。里面的元素可以有3种类型:①寄存器名。②段寄存器名。③内存单元的物理地址(一个20位的数据)。
idata表示常量。

[BX]

mov ax,[bx]
bx中存放的数据作为一个偏移地址EA,段地址SA默认存放在ds中,将SA:EA处的数据送入ax中。即:(ax)=((ds)*16+(bx))。
mov [bx],ax
bx中存放的数据作为一个偏移地址EA,段地址SA默认存放在ds中,将ax处的数据送入SA:EA中。即:((ds)*16+(bx))=(ax)。

Loop指令

loop指令的格式是:loop 标号。
CPU执行loop指令时,要进行两步操作:①(cx)=(cx)-1。②判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。
用cx和loop指令相配合实现循环功能的3个要点:

  1. 在cx中存放循环次数。
  2. loop指令中的标号所标识地址要在前面。
  3. 要循环执行的程序段,要写在标号和loop指令的中间。

用cx和loop指令相配合实现循环功能的程序框架如下:

	mov cx, 循环次数
s:
	循环执行的程序段
	loop s

在汇编源程序中,数据不能以字母开头,以字母开头的数据要在前面补0。

段前缀

mov ax,ds:[bx]
这种出现在访问内存单元的指令中,用于显示地指明内存单元的段地址的“ds:”,在汇编语言中称为段前缀

一段安全的空间

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

第六章 包含多个段的程序

在代码段中使用数据

dw定义字型数据,“define word”。dw定义的数据处于代码段的最开始,从CS中可以得到定义的数据的段地址,起始的偏移地址为0。
但因为前面的内容是数据而不是指令,如果直接运行会将这些数据错误地当成指令执行,因此需要表明程序的入口,从入口处开始执行指令。
start(可以替换成任何东西)用于表明程序的入口
end start用于通知编译器程序的入口在什么地方。
有了这两个伪指令我们可以这样安排程序的框架:

assume cs : code
code segment
			…数据…
start:	
			…代码…
code ends
end start

在代码段中使用栈

dw不仅可以用来定义数据,还可以用来开辟空间以供之后使用。此时不管所开辟的空间里存储的是什么都没有关系,它们最终的效果是一样的。

将数据、代码、栈放入不同的段

定义多个段的方法
对于不同的段有不同的段名即可。
对段地址的引用
段名相当于一个标号,它代表了段地址。因此在汇编指令里段名是一个代表地址的数值,不可以直接放到段寄存器里。mov ds, data这段代码是错的,应该写成

mov ax,data
mov ds,ax

“代码段”、“数据段”、“栈段”完全是我们的安排
你可以更改任何命名,只需要最后实现的时候一一对应上就可以。

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

and和or指令

and指令

“逻辑与”指令,按位进行与运算。

or指令

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

以字符形式给出的数据

' '的方式指明数据是以字符的形式给出的。编译器将把它们转化为相应的ASCII码

大小写转换问题

通常的思路是大写字母+20H小写字母-20H。但这样会涉及很多判断。另一种方法是,大小写字母之间差32(10进制),因此他们的第五位(从第0位开始算起)是不一样的,大写字母第五位是0,小写字母第五位是1。通过and 11011111B可将小写字母变成大写字母。通过or 00100000B可将大写字母变为小写字母。

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

处理的数据在什么地方?

bx、si、di、bp

  • 在8086CPU中,只有这4个寄存器可以用在[ ]中来进行内存单元的寻址
  • [ ]中,这四个寄存器可以单个出现,或只能以4种组合出现:bx和si、bx和di、bp和si、bp和di
  • 只要在[ ]中使用寄存器bp,而指令中没有显性地给出段地址,段地址就默认在ss中。否则默认在ds中

寻址方式

SA:段地址。EA:偏移地址

寻址方式含义名称
[idata]EA=idata;SA=(ds)直接寻址
[bx]EA=(bx);SA=(ds)寄存器间接寻址
[bx+idata]EA=(bx)+idata;SA=(ds)寄存器相对寻址
[bx+si]EA=(bx)+(si);SA=(ds)基址变址寻址
[bx+si+idata]EA=(bx)+(si)+idata;SA=(ds)相对基址变址寻址

汇编语言为结构体提供了更贴切的寻址书写方式。[bx].idata[bx].idata[si]

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

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

div指令

div指令是除法指令。使用div做除法时应注意以下问题。

  • 除数:有8位和16位两种,在一个reg或内存单元中
  • 被除数:默认放在AX或DX和AX中,如果除数为8位,被除数则为16位,默认在AX中存放;如果除数为16为,被除数则为32位,在DX和AX中存放,DX中存放高16位,AX中存放低16位。
  • 结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。

伪指令dd

dd是用来定义dword(double word,双字)型数据的,占4个字节。

dup

dup用来进行数据的重复,使用格式为
d(字长) 重复的次数 dup (重复的数据)
例如db 3 dup('a','B')的定义了6个字节,是’aBaBaB’。

第九章 转移指令的原理

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

  • 只修改IP时称为段内转移,比如jmp ax,短转移IP的修改范围为 − 128 — 127 -128—127 128127。近转移IP的修改范围为 − 32768 — 32767 -32768—32767 3276832767
  • 同时修改CS和IP时,称为段间转移,比如jmp 1000:0

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

  1. 无条件转移指令(如:jmp
  2. 条件转移指令
  3. 循环指令(如:loop
  4. 过程
  5. 中断

操作符offset

offset的功能是取得标号的偏移地址。比如下面的程序:

assume cs:codesg
codesg segment
	start	:	mov ax,offset start		;相当于mov ax,0
		s	:	mov ax,offset s			;相当于mov ax,3

start是代码段中的标号,它所标记的指令是代码段中的第一条指令,偏移地址为0。第一条指令长度为3个字节,所以第二条指令(s)的偏移地址是3。

jmp指令

jmp为无条件转移指令,可以只修改IP,也可以同时修改CS和IP。
jmp指令要给出两种信息:

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

依据位移进行的jmp指令

CPU在执行jmp指令的时候并不需要转移的目的地址。需要的是两条指令之间的位移,这个位移是由编译器通过标号算出(标号处的地址-jmp指令后的第一个字节的地址),用补码表示的(有正负号)
jmp short 标号(转到标号处执行指令)(IP)=(IP)+8位位移
这种格式的jmp指令实现的是段内短转移,它对IP的修改范围为 − 128 — 127 -128—127 128127
jmp near 标号(转到标号处执行指令)(IP)=(IP)+16位位移
这种格式的jmp指令实现的是段内近转移,它对IP的修改范围为 − 32768 — 32767 -32768—32767 3276832767

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

jmp far ptr 标号实现的是段间转移,又称为远转移。far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。这个时候机器码里会直接显示目的指令的地址(高地址处存放转移的段地址,低地址处存放指令的偏移地址)而不是两个指令之间的位移。

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

指令格式:jmp 16位reg
功能:(IP)=(16位reg)

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

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

  1. jmp word ptr 内存单元地址(段内转移)从内存单元地址处开始存放着一个字,是转移的目的偏移地址。
  2. jmp dword ptr 内存单元地址(段间转移)从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。(CS)=(内存单元地址+2)(IP)=(内存单元地址)

jcxz指令

jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移而不是目的地址。对IP的修改范围都是 − 128 — 127 -128—127 128127
指令格式:jcxs 标号(如果(CX)=0,转移到标号处执行。)
操作:当(CX)=0时,(IP)=(IP)+8位位移。

loop指令

loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移而不是目的地址。对IP的修改范围都是 − 128 — 127 -128—127 128127
指令格式:loop 标号((CX)=(CX)-1,如果(CX)≠0,转移到标号处执行。)
操作:

  1. (CX)=(CX)-1
  2. 当(CX)≠0时,(IP)=(IP)+8位位移。

根据位移进行转移的意义

方便了程序段在内存中的浮动装配。

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

根据位移进行转移的指令,它们的转移范围受到转移位移的限制,如果在源程序中出现了转移范围超界的问题,在编译的时候,编译器将报错。
形如jmp 2000:0100的转移指令,是在Debug中使用的汇编指令,汇编编译器并不认识。如果在源程序中使用,编译时也会报错。

第十章 CALL和RET指令

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

ret和retf

CPU执行ret指令时,进行下面两步操作:

  1. (IP)=((ss)*16+(sp))
  2. (sp)=(sp)+2

可以看出CPU相当于在执行pop IP

CPU执行retf指令时,进行下面4步操作:

  1. (IP)=((ss)*16+(sp))
  2. (sp)=(sp)+2
  3. (CS)=((ss)*16+(sp))
  4. (sp)=(sp)+2

可以看出CPU相当于在执行pop IP pop CS

call 指令

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

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

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

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

call 标号(将当前的IP压栈后,转到标号处执行指令)
CPU执行此种格式的call指令时,进行如下的操作:

  1. (sp)=(sp)-2 ((ss)*16+(sp)=(IP)
  2. (IP)=(IP)+16位位移

16位位移=标号处的地址-call指令后的第一个字节的地址,范围为-32768~32767,用补码表示。16位位移由编译程序在编译时算出。
CPU在执行“call 标号”时,相当于进行:

push	IP
jmp		near ptr 标号

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

call far ptr 标号实现的是段间转移。
CPU执行此种格式的call指令时,进行如下的操作。

  1. (sp)=(sp)-2 ((ss)*16+(sp))=(CS) (sp)=(sp)-2 ((ss)*16+(sp))=(IP)
  2. (CS)=标号所在段的段地址 (IP)=标号在段中的偏移地址

CPU在执行“call far ptr 标号”时相当于在执行:

push	CS
push	IP
jmp		far ptr 标号

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

指令格式:call 16位reg
功能:
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(16位reg)
CPU在执行“call far ptr 标号”时相当于在执行:

push	IP
jmp		16位reg

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

转移地址在内存中的call指令有两种格式
(1)call word ptr 内存单元地址
CPU在执行“call word ptr 内存单元地址”时相当于在执行:

push	IP
jmp		word ptr 内存单元地址

(2)call dword ptr 内存单元地址
CPU在执行“call dword ptr 内存单元地址”时相当于在执行:

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

call和ret的配合使用

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

mul指令

mul reg
mul 内存单元
内存单元可以用不同的寻址方式给出,前面要声明是byte ptr还是word ptr。
使用mul做乘法的时候,注意以下两点:

  1. 两个相乘的数:两个相乘的数,要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg或内存字节单元中;如果是16位,一个默认在AX中,另一个放在16位reg或内存字单元中。
  2. 结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果高位默认在DX中存放,低位在AX中放。

参数和结果传递的问题

可以将其放入内存中,传递存储数据的地址。

寄存器冲突的问题

子程序开始:	子程序中使用的寄存器入栈
			子程序内容
			子程序中使用的寄存器出栈
			返回(ret,retf)
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值