汇编知识
摘录
1 通用寄存器:
寄存器是学习汇编知识时的第一课,常用寄存器如下:
AX/EAX BX/EBX CX/ECX DX/EDX 是通用的数据寄存器 用于暂时存放计算过程中的操作数结果 或者其他信息。他们可以分为两个独立的8位寄存器使用,AL/AH BL/BH CL/CH DL/DH,除了通用功能之外还有以下用途:
AX/EAX作为累加器用,是算数运算的主要寄存器,在乘除指令中用来存放操作数,另外,所有的IO指令都是用AX或AL与外部设备传送信息。
BX/EBX在计算存储器地址的时候,可以作为基址寄存器使用。
CX/ECX常用来保存计数值,比如移位指令,循环指令和串处理指令中用作隐含的计数器。
DX在做双字节长运算的时候,可以把DX和AX组合在一起存放一个双字长数,DX存放高16位数据,对于某些IO操作,DX还常常存放IO的端口地址。
SP/ESP BP/EBP SI/ESI DI/EDI四个16/32位寄存器可以像数据寄存器一样在运算过程中存放操作数,但是他们只能以字(16/32位)为单位来使用,他们的主要用途是在存储器寻址时,提供偏移地址,因此他们可称为指针或变址寄存器。
SP/ESP称为堆栈指针寄存器,用来指出栈顶的偏移地址。
BP/EBP称为基址指针寄存器,用来在寻址的时候作为基地址存放的地方,但他必须和堆栈段寄存器SS联用来确定堆栈段中的存储单元地址。
2 标志寄存器
条件码标志是用来记录程序中运行结果的状态信息,他们是根据有关指令的运行结果由CPU自动设置的,由于这些状态信息往往作为后续条件转移指令的转移控制条件,所以称为条件码。
1 进位标志 CF 记录运算时最高有效位产生的进位值
2 符号标志 SF 记录运算结果的符号 1表示负数 0表示正数
3 零标志 ZF 运算结果为0时ZF为1 否则为0
4 溢出标志 OF 在运算中如操作数超出了机器可表示数的范围称为溢出 溢出时为1 否则为0
5 辅助进位标志 AF 记录运算时第3位产生的进位值
6 奇偶标志 PF 用来为机器中传送信息时可能产生的代码出错情况提供检验条件,当结果操作数中1的个数为偶数时置1 否则为0
先写几个英文简写及含义。
reg - 寄存器
mem- 内存
lmm - 立即数
1. 传送指令MOV reg/mem reg/mem/lmm
传送指令,相当于高级语言的赋值语句,把源操作数(reg/mem/lmm)传送到前面的寄存器和内存地址中。
2. 传送填充指令MOVSX/MOVZX reg/mem reg/mem/lmm
传送填充指令,和MOV语句具有相同功能的基础上,对目的操作数的高位进行填充,又分为符号填充(MOVSX)和零(MOVZX)填充。
(1)符号填充指令:MOVSX
用源操作数的符号位填充目的操作数的高位数据位。
比如AL = 87H,则MOVSX CX AL指令执行之后 CX就是0FF87H
(2)零填充指令:MOVZX
用0来填充目的操作数的高位数据位。
比如AL = 87H,则MOVZX CX AL指令执行之后 CX就是0087H
3. 交换指令XCHG reg/mem reg/mem
交换指令XCHG是交换两个寄存器,寄存器和内存变量之间内容的指令,两个操作数的数据类型要相同。
4. 取有效地址指令LEA reg mem
是把一个内存变量的有效地址传送到寄存器中
5. 取段寄存器指令LDS/LES/LFS/LGS/LSS reg mem
LDS是把内存单元的一个“低字”传送到指定的寄存器,并把“高字”传送到段寄存器DS中。
LES,LFS,LGS,LSS是把高字传送到相应的段寄存器ES,FS,GS,SS中。
6. 栈操作指令
栈的实质是一块内存空间,栈的标准访问形式不同于一般的内存访问形式(用内存地址去访问),而是通过入栈(压栈)与出栈(弹栈)指令进行访问,且栈是后进先出的。(也可以用内存地址的形式去访问栈中的内容,但这属于特殊手段),以下就是栈操作指令
(1) 压栈指令:PUSH, PUSHA, PUSHAD
- PUSH reg/mem: 把寄存器和内存单元中的值压入栈中
- PUSHA: 依次把AX,CX,DX,BX,SP,BP,SI,DI等压入栈中
- PUSHAD: 依次把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI等压入栈中
(2) 弹栈指令:POP, POPA, POPAD
- POP reg/mem: 弹出栈首的值到指定的寄存器和内存单元中
- POPA: 把栈中的值依次弹到DI,SI,BP,SP,BX,DX,CX,AX等寄存器中
- POPAD: 把栈中的值依次弹到EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX等寄存器中
压栈与弹栈的补充说明:
ESP/SP寄存器始终指向栈首,所以在PUSH时,SP=SP-2或4, *SP = 操作数(C语法,就是SP寄存器先向前移动两位和四位,然后将值写入指向的内存区域),POP一个项的时候,操作数 = *SP, SP = SP+ 2或4
在16位操作系统使用SP,AX等寄存器,在32位操作系统则应该使用ESP,EAX系列的寄存器,在64位操作系统则可以使用RSP寄存器。
VC++编译器生成的汇编代码使用栈的时候,并不是按标准做法PUSH和POP,而是PUSH多个项目后,用ESP做为基址,向后偏移四位来寻找第二个项,直到访问结束后一次性的把栈清空,这样省略了多次弹栈。
算术运算指令是反映CPU运算能力的指令,也是编程时候最常用到的一组指令,包括加减乘除以及相关的辅助指令。
该组指令的操作数可以是8位,16位,32位(80386+),当存储单元是该类指令的操作数的时候,该操作数的寻址方式可以是任何一种存储单元的寻址方式。
1. 加法指令
ADD reg/mem, reg/mem/imm - 加法指令
受影响的标志位:AF CF OF PF SF ZF
功能:把源操作数的值加到目标操作数
INC reg/mem
受影响的标志位:AF、OF、PF、SF和ZF,不影响CF
功能:把操作数的值+1
ADC reg/mem, reg/mem/imm - 带进位加指令(较少使用)
受影响的标志位:AF CF OF PF SF ZF
功能:把源操作数和进位标志位CF的值一起加到目的操作数中
XADD reg/mem reg(较少使用)
受影响的标志位:AF CF OF PF SF ZF
功能:先交换两个操作数 然后执行加法运算
以上指令的ADC和XADD在标准VC++编译后的汇编代码中不出现。
2. 减法指令
SUB reg/mem reg/mem/imm
受影响的标志位:AF CF OF PF SF ZF
功能:把源操作数的值减去目的操作数
DEC reg/mem
受影响的标志位:AF、OF、PF、SF和ZF,不影响CF
功能:把操作数的值-1
SBB reg/mem reg/mem/imm
受影响的标志位:AF CF OF PF SF ZF
功能:把源操作数和进位标志位CF的值从目的操作数一起减去
NEG reg/mem
受影响的标志位:AF CF OF PF SF ZF
功能: 操作数 = 0 - 操作数, 改变操作数的正负号。
3. 乘法指令
计算机的乘法指令分为无符号指令和有符号指令两种,他们的区别在于:数值的最高位是做为“数值”参与运算还是做为“符号”参与运算。
乘法指令的被乘数都是做为隐含操作数,乘数在指令中显示的写出来,CPU会根据乘数是8位,16位,32位来自动选用被乘数:AL,AX或EAX。指令的功能是把显示操作数和隐含操作数相乘,并把乘积存入AL,AX或EAX中。(现在的80386+一般都是用EAX做为被乘数)
MUL/FMUL reg/mem - 无符号整数/浮点数乘法指令
受影响的标志位:CF和OF
功能:把显示操作数和隐含操作数都做为无符号数相乘。比如MUL 5就是eax = eax * 5
IMUL/FIMUL reg/mem - 有符号整数/浮点数乘法指令
IMUL/FIMUL reg, imm
IMUL/FIMUL reg,mem
功能:把两个操作数做为有符号数相乘。
4. 除法指令
DIV,无符号数的除法指令,和8086一样, 指令给出一个操作数,被除数已默认。如果指令中给出的操作数为32位,那么被除数将是EDX:EAX,最终的商将存放在EAX, 余数将存放在EDX中。如果指令给出操作数为16位,那么被除数为EAX,最终得到的商放在AX,余数放在EAX的高16位。如果指令中给出的操作数为8 位,那么被除数是16位,最终得到的商将放在AL中,余数放在AH中。
IDIV,有符号数的除法指令,用法和8086相同,不过支持32位操作
逻辑运算指令是另外一组重要的常用指令,包括逻辑与AND,逻辑或OR,逻辑非NOT,和异或指令XOR。
1. AND reg/mem, reg/mem/imm - 逻辑与指令
受影响的标志位:CF,OF,PF,SF,ZF
功能:把源操作数和目的操作数进行二进制位的与操作,结果存入源操作数中。
2. OR reg/mem, reg/mem/imm - 逻辑或指令
受影响的标志位:CF,OF,PF,SF,ZF
功能:把源操作数和目的操作数进行二进制位的或操作,结果存入源操作数中。
3. NOT reg/mem
不影响任何标志位
功能:把操作数的每个二进制位取反
4. XOR reg/mem, reg/mem/imm
受影响的标志位CF,OF,PF,SF,ZF
功能:把源操作数和目的操作数进行每个二进制位的异或操作,结果存入源操作数中。
1. TEST reg/mem, reg/mem/imm - 检测位指令
受影响标志位:CF(0),OF(0),PF,SF和ZF
功能:检测位指令会将源操作数和目标操作数进行逻辑与操作,根据运算结果设置标志位,但是并不保存运算结果到源操作数,只是设置标志位。该指令执行后通常是一条JE,JNE,JZ或JNZ等条件转移指令。
2. 循环指令
循环指令的执行本身并不影响任何标志位,以CX或ECX做为计数器。
LOOP/LOOPW/LOOPD 标号
LOOP: 在实地址模式下使用CX寄存器而不是ECX寄存器。
LOOPW:在任何模式下都会使用CX寄存器作为计数器。
LOOPD:在任何模式下都会使用ECX寄存器作为计数器。
3. 转移指令
转移指令是汇编语言程序员经常使用的一组指令。在高级语言中,时常有“尽量不要使用转移语句”的劝告,但如果在汇编语言的程序中也尽量 不用转移语句,那么该程序要么无法编写,要么没有多少功能,所以,在汇编语言中,不但要使用转移指令,而且还要灵活运用,因为指令系统中有大量的转移指令。
转移指令分无条件转移指令和有条件转移指令两大类。
无条件转移指令:包括JMP指令,子程序的调用和返回,中断的调用和返回。
JMP 标号/reg/mem
JMP指令是从程序当前执行的地方无条件转移到另外一个地方执行,这种执行可以是一个短(short)转移(偏移量在-128到127范围内),近(near)转移(偏移量在正负32K范围内),或者远(FAR)转移(在不同的代码段内跳转。)
短转移和近转移只是代码段内的转移,仅仅需要把偏移量入栈,远转移需要把要跳转的代码段的地址和偏移量入栈。
转移指令本身不影响标志位。
1. 子程序的定义
如同C语言一样,汇编语言也具备结构化的能力。
首先说子程序的定义格式:
子程序名 PROC [NEAR | FAR]
… ;子程序体
子程序名 ENDP
PROC和ENDP是子程序的首尾标识,子程序名必须是相同的,子程序可以分为NEAR近调用和FAR远调用,所谓近调用就是只能在同一段内的其他程序调用,而远待用可以被不同段的程序调用。
2. 子程序的调用
所以,根据NEAR和FAR,也有相应的调用方式,NEAR的子程序调用的时候只需要把偏移量压栈即可,FAR的子程序要把段地址和偏移量压栈(分别压入CS和IP寄存器),然后即可调用。
调用的方式:
|
CALL DISPLAY |
;DISPLAY是子程序名 |
|
CALL BX |
;BX的内容是子程序的偏移量 |
|
CALL WORD1 |
;WORD1是内存字变量,其值是子程序的偏移量 |
|
CALL DWORD1 |
;DWORD1是双字变量,其值是子程序的偏移量和段值 |
|
CALL word ptr [BX] |
;BX所指内存字单元的值是子程序的偏移量 |
|
CALL dword ptr [BX] |
;BX所指内存双字单元的值是子程序的偏移量和段值 |
3. 子程序的返回
当子程序执行完了之后,需要返回到主调指令上下文中,并不像跳转指令那样不会返回,为了实现这个功能,指令系统提供了一条专用指令RET.
RET/RETN/RETF [imm]
子程序的返回在功能上来说是子程序的调用的逆操作,为了与子程序的远/近调用相对应,子程序的返回也分为远返回和近返回。
返回指令在堆栈方面是调用指令的逆操作。
在近类型的子程序中,返回指令RET是近返回,功能是把栈顶的值弹出到指令指针寄存器IP中,SP会被加2。
在远类型的子程序中,返回指令RET是远返回,功能是:先弹出栈顶的值到IP中,再弹出栈顶的值到CS中,SP总共向后移动了4位(当然80386+的CPU是ESP向后移动了两个四位)
如果返回指令后面加入了立即数,那么表示返回地址之后,SP还要增加的偏移量。并不是高级语言中return的值。
在masm5.0+的环境中可以直接使用RETN和RETF来显示告诉编译器本子程序是近返回还是远返回。
|
| |
|
RET |
;可能是近返回,也可能是远返回 |
|
RETN |
;近返回指令 |
|
RETF |
;远返回指令 |
|
RET 6 |
;子程序返回后,(SP)←(SP) + 6 |
先是汇编的预备知识:
1. 汇编语言的主要特点
一般情况下一条汇编语句由汇编指令和操作数构成,比如MOV AX, BX,意思是把BX寄存器中的值赋予AX寄存器,MOV是汇编指令,AX和BX都是操作数,BX是源操作数,AX是目的操作数。由于CPU的不同,所支持 的指令集也不同,所以汇编语言是依赖于具体的CPU的。
汇编语言和机器可以执行的机器语言是一一对应的,由汇编程序进行翻译。比如MASM,TASM,DEBUG等。
汇编语言的主要特点就是更加贴近硬件,执行效率更高。
2. 汇编语言适用领域
汇编语言适用与工业控制或要求效率非常高的系统核心模块,不适合大型软件系统的开发。
3. 在汇编语言中如何表示二进制,八进制,十进制和十六进制的数值
二进制:用B结尾。逢二进一。
八进制:用Q结尾。逢八进一。
十进制:用D结尾。逢十进一。
十六进制:用H结尾。逢十六进一。
注意:在二进制和十进制的转换中,有一种转换方法叫做BCD编码方法(Binary-Coded Dicimal),其实就是用二进制编码形式保存十进制数字。
BCD编码是把每个十进制数的数码用四个二进制位元来表示,最常用的是8421,比如900用二进制表示就是1001 0000 0000。
4. 如何表示正负数,在保持数值大小不变的情况下,如何把位数少的二进制数值扩展成位数较多的二进制数值
在计算机内,有符号数使用二进制的补码形式表示,补码的最高为用来表示正负数形式,0正数,1负数。
正数的补码就是二进制形式,负数的补码是把其正数的二进制编码变反后加一所得。
比如5的二进制是0000 0101 -5的二进制是1111 1010 + 1 = 1111 1011
对于位数的扩展,根据补码的原理,扩展方法就是使用原最高位填充扩展出来的数位,比如1111 1011扩展成字之后,高字节位就全部为1,为1111 1111 1111 1011。对于0000 1010扩展后就是 0000 0000 0000 1010。
5. 在ASCII表,字符'0-9'和数值0-9之间编码规律是怎么样的,大写字母和小写字母的编码规律是怎样的
在ASCII表,字符'0-9'就是按顺序排列的,大写字母在小写字母之前20H个位置处。
6. 汇编语言中的基本数据类型有哪些,他们与高级设计语言中的数据类型的对应的关系怎么样
字节:8个二进制位。
字:16个二进制位,从前往后分为高字节8位,低字节8位。
双字:32个二进制位,分为高字,低字。
另外还有较少使用的四字,十字。
使用汇编编程时要直接操作硬件资源,如CPU内部资源,存储器和I/O端口,所以一定要学习并记住CPU内部寄存器的命名,功能以及常见的用途,感谢老天,寄存器并不是太多。还有要明白存储器的分段管理模式,存储单元的地址的表示法和物理地址的形成方式,当然也不难理解。
一 寄存器组
寄存器是CPU的内部数据存储资源,是汇编程序能用到的硬件资源之一,由于存取速度比内存快很多,所以在汇编写程序时,要尽可能充分利用寄存器的存储功能。
寄存器一般存储运算过程的中间结果,为随后的指令快速读取,避免压入堆栈再弹出带来的开销。
另外,由于寄存器的数量和容量都有限制,不可能把所有的中间结果都存入寄存器,所以要对寄存器进行适当的调整,根据指令的顺序,如何安排适当的寄存器,避免操作数过多的传送操作是一项很缜密的工作,有一些专门的寄存器的分配策略的文章,在《编译原理》一书中详细阐述。
16/32位CPU在PC中很常用,所以介绍这两种CPU的内部寄存器的名称和具体作用。
1. 16位CPU寄存器组
4个数据寄存器(AX,BX,CX,DX) 2个变址和指针寄存器(SI,DI) 2个指针寄存器(SP,BP)
4个段寄存器(ES,CS,SS,DS) 1个指令指针寄存器(IP) 1个标志寄存器(Flags:16个二进制位只用了9个二进制位作为标志位,下章会讲)
数据寄存器,变址和指针寄存器,指针寄存器都属于通用寄存器,因为具有良好的通用性,数据寄存器既能被当作一个16位寄存器使用,又能拆分成俩8位寄存器使用。
在某些CPU指令中,通用寄存器可能被当作隐含的操作数,比如我在第六章介绍的LOOP指令,就把ECX当作了隐含操作数,使它成为循环计数器。
2. 32位CPU寄存器组
包含16位CPU的所有寄存器,把通用寄存器,指令指针和标志寄存器都扩充到32位,另外增加了2个段寄存器FS,GS。
4个数据寄存器(EAX,EBX,ECX,EDX) 2个变址和指针寄存器(ESI,EDI) 2个指针寄存器(ESP,EBP)
6个段寄存器(ES,CS,SS,DS,FS,GS) 1个指令指针寄存器(EIP) 1个标志寄存器(EFlags:32位只使用了13位作为标志位)
通用寄存器的作用
数据寄存器不讲,简单的说,段寄存器(ES,CS,SS,DS,FS,GS)和变址寄存器(SI,DI)是配合使用访问段数据的,指针寄存器(BP,SP)是用来操作堆栈的,BP指向栈的基址,SP则永远指向栈顶。
另外指令指针EIP存放的是要执行的下一条指令在代码段里的偏移量,在实方式下,每个段的最大范围都是64K,所以EIP的高16位都是0。
|
寄存器的分类 |
寄存器 |
主 要 用 途 | |
|
通 用 寄 存 器 |
数据
寄存器 |
AX |
乘、除运算,字的输入输出,中间结果的缓存 |
|
AL |
字节的乘、除运算,字节的输入输出,十进制算术运算 | ||
|
AH |
字节的乘、除运算,存放中断的功能号 | ||
|
BX |
存储器指针 | ||
|
CX |
串操作、循环控制的计数器 | ||
|
CL |
移位操作的计数器 | ||
|
DX |
字的乘、除运算,间接的输入输出 | ||
|
变址 寄存器 |
SI |
存储器指针、串指令中的源操作数指针 | |
|
DI |
存储器指针、串指令中的目的操作数指针 | ||
|
变址 寄存器 |
BP |
存储器指针、存取堆栈的指针 | |
|
SP |
堆栈的栈顶指针 | ||
|
指令指针 |
IP/EIP |
| |
|
标志位寄存器 |
Flag/EFlag |
| |
|
32位 CPU的 段寄存器 |
16位CPU的 段寄存器 |
ES |
附加段寄存器 |
|
CS |
代码段寄存器 | ||
|
SS |
堆栈段寄存器 | ||
|
DS |
数据段寄存器 | ||
|
新增加的 段寄存器 |
FS |
附加段寄存器 | |
|
GS |
附加段寄存器 | ||
专用寄存器有标志位寄存器Flag/EFlag, 段寄存器ES,CS,SS,DS,FS,GS。
标志位寄存器(Flag/EFlag)
16位CPU标志位寄存器有9个二进制位发挥了作用,分别是:
|
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
|
|
|
|
OF |
DF |
IF |
TF |
SF |
ZF |
|
AF |
|
PF |
|
CF |
32位CPU标志位寄存器有13个二进制位发挥了作用,分别是:
|
31 |
… |
17 |
16 |
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
… |
… |
VM |
RF |
|
NT |
IOPL |
OF |
DF |
IF |
TF |
SF |
ZF |
|
AF |
|
PF |
|
CF |
这些标志位的作用是反映处理器的状态和运算结果的某些特征。标志位对指令的执行会产生影响,指令的执行也会影响标志位。
一 运算结果标志位
1. CF 进位标志位 Carry Flag
反映运算是否产生进位或者借位的操作,如果运算结果的最高位产生了一个进位或错位,那么,其值为1,否则为0。
使用该标志为的情况:多字(字节)数的加减运算,无符号数的大小比较运算,移位操作,字(字节)之间移位,专门改变CF值的指令等
2. PF 奇偶标志位 Parity Flag
奇偶标志PF用于反映运算结果中"1"的个数的奇偶性。如果"1"的个数为偶数,则PF为1,否则为0。
利用PF可进行奇偶效验检查,或产生奇偶效验位。在数据传输的过程中,为了提供传送的可靠性,如采用奇偶效验的方法,可使用该标志位。
3. AF 辅助进位标志 Auxiliary Carry Flag
发生以下情况时辅助进位标志为1,否则为0:
1) 字操作时,发生低字节向高字节进位或借位。
2)字节操作时,发生低4位向高4位进位或借位。
4. ZF 零标志 Zero Flag
零标志用来反映运算结果是否为0,如果结果为0,其值为1,否则其值为0,在判断运算结果时,可以使用该标志位。
5. SF 符号标志 Sign Flag
符号标志SF用来反映运算结果的符号位,与运算结果的最高位是相同的,在PC系统中,有符号数用补码表示,所以SF也就反映运算结果的正负号。运算结果为整数,SF为0,负数SF为1。
6. OF 溢出标志 Overflow Flag
溢出标志OF用于反映有符号数加减运算所得结果是否溢出,如果运算结果超过当前运算位数所能表示的范围,则成为溢出,OF的值被设置为1,否则OF的值被清为0。 溢出和进位是两个不同的概念。
以上六个标志位中,PF,AF很少使用,ZF OF CF SF常用。
二 状态控制标志位
状态控制标志位是用来控制CPU操作的,要通过专门的指令才能改变这些标志位。
1. TF 追踪标志 Trap Flag
当追踪标志TF设置为1时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求,这种方式主要用于程序的调试。
指令系统中没有专门的指令来改变标志位TF的值,但可以通过其它方法改变。
2. IF 中断允许标志 Interrupt-enable Flag
中断允许标志IF是用来决定CPU是否响应CPU外部的可屏蔽中断发出的请求。当IF为1时,CPU可以响应CPU外部的可屏蔽中断请求,当IF为0时,CPU不响应CPU外部的可屏蔽中断请求。
对于不可屏蔽的CPU外部中断以及CPU内部产生的中断,无论IF标志位为何值CPU都会响应。
3. DF 方向标志 Direction Flag
方向标志DF用来决定在串操作指令执行时有关指针寄存器发生调整的方向。
三 32位寄存器增加的标志位
1. IOPL I/O特权标志 Input/Output Privilege Level
I/O特权标志用两位二进制位表示,也成为I/O特权级字段。该字段指定了要求执行I/O指令的特权级。如果当前的特权级别在数值上小于等于IOPL的值该I/O指令可执行,否则将引发一个保护异常。
2. NT 嵌套任务标志 Nested Task
嵌套任务标志NT用来控制中断返回指令IRET的执行,具体规定如下:
1) 当NT=0时,用堆栈中保存的值恢复EFLAGS,CS和EIP,执行常规的中断返回操作;
2) 当NT=1时,通过任务转换实现中断返回。
3. RF 重启动标志位 Restart Flag
重启动标志位RF用来控制是否接受调试故障,规定:RF=0时,表示接受调试故障,否则拒绝,在成功执行完一条指令后,处理机制把RF设置为0,当接受到一个非调试故障时,处理机就把他设置为1。
4. VM 8086方式标志 Virtual 8086 Mode
如果该标志为1,则表示处理器处于虚拟的8086方式下的工作状态,否则,处理器处于一般保护方式的工作状态。
存储器的管理模式
intel公司的80x86系列的CPU对内存的管理采用的是内存分段的方式。
1. 存储器的分段
计算机的内存管理单元是以“字节”为最小单位进行线性编址的,字节是80x86CPU对内存管理的基元。为了标识每个存储单元,就给每个存储单元规定一个编号,该编号就是内存单元的物理地址。
存储单元的物理地址是一个16位的二进制数,物理地址通常采用16进制书写。
16位CPU内部拥有20根地址线,它的寻址范围就是2的20次方,也就是1M的内存空间。
但是16位CPU存放存储单元偏移量的寄存器(IP,SP,BP,SI,DI,BX)的编码范围仅为:00000H - 0FFFFH,也就是只能访问65536个存储单元,64K。
为了能够访问1M的内存空间,CPU就采用了内存分段的管理模式,并且在CPU内部加入了段寄存器。
16位CPU把内存空间分为若干个逻辑段,每个逻辑段的要求如下:
1) 逻辑段的起始地址(段地址)必须是16的倍数,即最后4个二进制位必须全为0。
2) 逻辑段的最大容量为64K,这由16位寄存器的寻址空间所决定。
2. 物理地址的形成方式
由于段地址必须是16的倍数,所以值的一般形式为XXXX0H,即前16位二进制位是变化的,后四位是固定的0,鉴于段地址的这种特性,可以只保存前16位二进制位来保存整个段地址,所以每次使用时要用段寄存器左移补4个0(乘以16)来得到实际的段地址。
在确定了某个存储单元所属的内存段后,我们也只是知道该存储单元所属的范围(段地址 到 段地址+65536),如果想确定内存单元的具体位置,还必须知道该单元距离该段地址有多远。我们通常把这个存储单元的实际地址和段地址之间的范围称为段内偏移,也称为有效地址(EF-Effective Address)或偏移量(Offset),有了段地址和偏移量,就可以唯一的确定某一内存单元在存储器的具体位置。
存储单元的逻辑地址分为两个部分 段地址:偏移量
由逻辑地址得到物理地址(PA - Physical Address)的公式为: PA = 段寄存器的值 * 16 + 偏移量(为什么要乘16,因为段寄存器没有保存段地址的后4位,所以要*16,或者用左移4位)。
实际在计算的时候,是用 PA = 段寄存器 << 4 + 偏移量。 因为左移操作比乘法操作快。
3. 段寄存器的引用
段寄存器就是为了对内存进行分段管理而增加的,16位CPU有四个段寄存器(而1M内存可分成16个互不重复的段),所以程序可同时访问四个不同含义的段。
1) CS + IP 代码段的访问,指向用于存放程序的内存段,寄存器IP指向下条要执行的指令在CS段的偏移量,用这两个寄存器就可以得到一个内存物理地址,该地址存放着一条要执行的指令: CS << 4 + IP。
2) SS + BP / SS + SP 堆栈段的访问,指向用于存放堆栈的内存段,SP一直指向栈顶,可以通过SS和SP两个寄存器直接访问栈顶单元的内存物理位置,另外,当偏移量使用了 指针寄存器BP,缺省的段寄存器也是SS,并且可以用BP访问整个堆栈,而不只是访问栈顶数据。(这是绕过压栈弹栈机制直接操作堆栈段的方式)
3) DS + BX 数据段的访问。 DS中的值左移四位得到附加段起始地址,再加上BX中的偏移量,得到一个存储单元的物理地址。
4) ES + BX 附加段的访问。 ES中的值左移四位得到附加段起始地址,再加上BX中的偏移量,得到一个存储单元的物理地址。
段寄存器和指针寄存器的引用关系:
|
访问存储器方式 |
缺省的段寄存器 |
可选用的段寄存器 |
偏移量 | |
|
取指令 |
CS |
|
IP | |
|
堆栈操作 |
SS |
|
SP | |
|
一般取操作数 |
DS |
CS、ES、SS |
有效地址 | |
|
串操作 |
源操作数 |
DS |
CS、ES、SS |
SI |
|
目标操作数 |
ES |
|
DI | |
|
使用指针寄存器BP |
SS |
CS、DS、ES |
有效地址 | |
16位CPU在段寄存器的引用方面有如下约定:
1). 取指令所用的段寄存器和偏移量一定用CS和IP
2). 堆栈操作所使用的段寄存器和偏移量一定用SS和SP
3). 串操作的目标操作数所用的寄存器和偏移量一定是ES和DI
4). 其他情况 段寄存器除了默认引用的寄存器外 还可以强行改变为其他段寄存器。
4. 存储单元的内容
上面三章内容已经让我们知道内存单元物理地址的计算方法,使我们很容易的指定我们要访问的存储单元,但是我们还要知道存储单元的内容是如何存放的。
存储单元所存放的二进制信息通常称为该存储单元的内容或值,并规定:
1) 一个字节的内容是该字节单元内存放的二进制信息
2) 一个字的内容是该字地址所指向的单元及其后继一个单元的内容拼接而成
3) 一个双字的内容是该双字地址所指向的单元及其后继三个单元的内容拼接而成
在拼接字单元时 我们按高高低低的原则来处理,即高存储单元(地址大的存储单元)的值是“字内容”的高8位,低存储单元(地址小的存储单元)的值是“字内容”的低8位。
5. 32位微机的内存管理模式
32位pc的内存管理仍然采用“分段”的管理模式,存储器的逻辑地址同样由段地址和偏移量两部分组成,32位pc的内存管理和16位pc的内存管理有相同之处也有不同之处,因为32位pc采用了两种不同的工作方式:实方式和保护方式。
(1) 物理地址的计算方式
1) 实模式下
在实模式下段地址仍然都是16的倍数,每个段的最大容量仍然是64K,段寄存器的值*16是起始地址,存储单元的物理地址仍然是段地址 + 段内偏移量,在实模式下,32位微机的内存管理与16位微机是相一致的。
2) 保护模式下
段地址可以长达32位,其值可以不再是16的倍数,每个段的最大容量可达4G,段寄存器的值是表示段地址的“选择器”(Selector),用该“选择器 ”可以从内存中得到一个32位的段地址,存储单元的物理地址就是该段地址加上段内偏移量,这与16位微机的物理地址计算方式完全不同。
(2) 段寄存器的引用
32位cpu内有6个段寄存器,程序在某一时刻可以同时访问6个不同的段。其段地址的值在不同的方式下具有不同的含义:
1) 在实方式下 段寄存器的值*16就是段地址
2) 在保护方式下 段寄存器的值是一个选择器,可以间接指出一个32位的段地址。
代码段寄存器:32位Pc在取指令的时候,系统自动引用CS和EIP来取出下条指令,在实方式下,由于段的最大容量不超过64K,所以EIP的高16位全部是0,也就是说在实方式下EIP与IP是相同的。
堆栈段寄存器:32位Pc在访问堆栈段的时候,总是引用堆栈段寄存器SS。但在不同的方式下堆栈指针有所不同:
1)在实方式下 32位Pc把ESP的低16位SP作为指向堆栈的指针,所以我们可以认为栈顶单元是用SS和SP寄存器共同指定的,与16位Pc模式下访问栈顶单元的方法一致。
2)在保护方式下,堆栈指针可以用32位的ESP和16位的SP
数据段寄存器:DS是主要的数据段寄存器,通常他是访问堆栈以外数据的主要寄存器,在某些串操作中,其目的操作数的段寄存器被指定为ES是一个例外。
另外 段寄存器CS,SS,ES,FS和GS也都可以作为访问数据时的寄存器,但它们必须用段超越前缀的方式直接在指令中写出,这种方式会增加指令的长度,指令的执行时间也有所延长。
一般情况下,程序频繁使用的数据段用DS来指向,不太常用的数据段可用ES,FS和GS来指向。
(3) 存储单元的内容
32位微机存储单元内容的存储格式与16位微机完全一致,也都用“高高低低”的原则存放数据。
操作数就是指令或者程序的主要处理对象,只有很少数的指令不需要操作数,因为他们不具备数据处理功能,比如NOP空指令和HLT停机指令。
大多数指令的执行会涉及到操作数,所以指令中如何表达操作数或操作数所在位置是正确运用汇编指令的重要因素。
在指令中指令操作数或操作数存放位置的方法称为寻址方式。操作数的寻址方式是汇编语言进行程序设计的基础。
1. 立即数寻址方式
操作数作为指令的一部分直接写在指令中,这种操作数称为立即数,这种寻址方式称为立即数寻址方式。
立即数可以是8位,16位,32位,该数值紧跟在操作码之后,如果是16位或32位,按“高高低低”的方式存储。
如果有两个操作数,操作数不能作为第一个操作数,就像高级语言中常数不能放在等号左边一样。
2. 寄存器寻址方式
指令所要的操作数已经储存在寄存器中,或把目标操作数存入寄存器。把在指令中指出所使用的寄存器的寻址方式称为寄存器寻址方式。
指令中可以引用的寄存器及其符号如下:
8位寄存器有 AH AL BH BL CH CL DH DL
16位有 AX BX CX DX SI DI SP BP 和段寄存器
32位有EAX EBX ECX EDX ESI EDI ESP EBP
寄存器寻址方式也是简单便捷的寻址方式,源和目的操作数都可以是寄存器。
1) 源操作数是寄存器寻址方式
如 ADD VARD, EAX ADD VARW, AX ADD VARB, BH
其中VARD VARW VARB分别是双字 单字 字节的内存变量。
2) 目的操作数是寄存器的寻址方式
如 ADD BH, 78h ADD AX, 1234h MOV EBX, 12345678H等
3) 源和目的操作数都是寄存器寻址方式
如 MOV EAX, EBX MOV AX, BX MOV DH, BL
由于指令所需的操作数已存储在寄存器中,或操作的结果存入寄存器,这样,在指令执行过程中,会减少读写存储器单元的次数,所以,使用寄存器寻址方式的指令具有较快的执行速度,通常情况下,应该尽可能的充分利用寄存器。
3. 直接寻址方式
指令所需操作数已经存放在内存单元中,在指令中直接给出有效地址,这种寻址方式称为直接寻址方式。
在通常情况下,操作数存放在数据段中,所以其物理地址将由数据段寄存器DS和指令中给出的有效地址计算得出,但如果使用段超越前缀,操作数可以存放在其他段。
比如指令MOV BX, [1234H],会执行的操作是这样:
(1) 将寄存器DS的值向左移四位,然后加上1234H,求出实际的内存物理地址(为什么左移4位上一章《80x86CPU的内存管理》已经说过)
(2) 将实际地址中存放的值,放入寄存器BX中。
也可以在指令中采用段前缀方式指定使用其他的段,而不是默认段DS。
比如 MOV ES:[1000H], AX
直接寻址方式常用于处理内存单元的数据,其操作数是内存变量的值,该寻址方式可在64K字节的段内寻址。
注意:一定要带有[]号,否则会认为是立即数而不是内存单元。 实际运用时,一般不会直接写地址值,而是用内存变量名来表示,比如 MOV BX, VARW,其中的VARW就是内存字变量。
例子:
MOV AX, 1234H //立即数寻址
MOV AX, [1234H] //直接寻址
MOV AX, VARW //直接寻址
MOV AX, [VARW] //直接寻址
4. 间接寻址方式
操作数在内存中,操作数的有效地址用SI,DI,BX或BP寄存器保存着,计算公式如下:
PA = DS/ES/SS/CS * 16 + SI/DI/BX/BP
如果有效地址用SI/DI/BX的话,默认的段寄存器是DS(默认取数据段)
如果有效地址用BP的话,默认的段寄存器是SS(因为是栈操作嘛)
其实和直接寻址方式只有一点不同,直接寻址方式是给出的内存地址,或代表内存地址的变量,而间接寻址方式给出的是装有内存地址的寄存器。
5. 寄存器相对寻址方式
操作数在内存中,有效地址是一个基址寄存器(BX,BP)或变址寄存器(SI,DI)的内容和指令中的8位/16位偏移量之和。
如果有效地址用SI/DI/BX的话,默认的段寄存器是DS(默认取数据段)
如果有效地址用BP的话,默认的段寄存器是SS(因为是栈操作嘛)
指令中给出的8位/16位偏移量用补码表示,在计算有效地址时,如果偏移量是8位,则进行符号扩展成16位,当所得的有效地址超过0FFFFH,则取其64K的模。
比如 MOV BX, [SI+100H] 执行时是 DS * 16 + (SI + 100H)
6. 基址+变址寻址方式
操作数在内存中,其有效地址是一个基址寄存器(BX,BP)和一个变址寄存器(SI,DI)的内容的和,有效地址的计算公式为:
EA = BX/BP + SI/DI
其物理地址的计算公式仍然是:
PA = DS/SS * 16 + EA(当EA中含有BP的话段寄存器为SS,否则使用DS)
7. 相对基址 + 变址寻址方式
有效地址的公式为:
EA = BX/BP + SI/DI + 偏移量
物理地址的计算公式仍然是:
PA = DS/SS * 16 + EA
标识符和表达式是程序设计经常用到的两个概念,在用高级语言编程的时候,如果程序要对某个变化的量进行处理时,通常要对该变化的量定义一个具有某种数据类型的符号名,用该符号名也就是使用了该变化量,在汇编语言中也是这样,不同的只是说明和引用的方式。
一 标识符
在汇编语言中,标号,内存变量名,子程序名和宏名等都是标识符,一般最多由31个字母,数字以及规定的特殊字符(? @ _ $)等组成,并且不能用数字开头,通常情况下汇编语言并不区分标识符中字母的大小写。
和高级语言的变量名一样,一般要求标识符的名字比较有意义,会改善程序的可读性,并且有助于对程序的理解,但是标识符不能是汇编语言的保留字,汇编语言的保留字主要是:指令助记符,伪指令定义符,寄存器名以及一些具有特殊含义的字符串等。
二 简单内存变量的定义
在编程时,往往要根据程序的需要定义一些内存单元。在高级语言中要给存储单元一个符号名,然后通过引用该符号名来访问其所对应的存储单 元,而在汇编语言程序中要灵活一些,它可以给存储单元取符号名,也可以不取符号名。当给存储单元取符号名,则可通过该符号名来访问其对应的存储单元,当不给存储单元取符号名时,则可通过存储单元的偏移量来访问它。
在汇编语言中,常用的数据类型有字节,字和双字等。
1. 内存变量定义的一般形式
定义数据变量语句是在程序中经常使用的伪指令语句,其一般的格式为:
[变量名] 定义符 表达式1[, 表达式2] ;注释
(1) 变量名必须是一个合法的标识符 可以写,也可以不写(不写的时候只能通过内存单元的偏移量来访问)
(2) 数据定义符用于确定内存单元的数据类型 常用的定义符有 DB DW DD等
(3) 表达式是定义内存单元时的初值表达式 一个定义语句可以有多个初值表达式 各个表达式之间用,分隔开,如果某个存储单元没有初值也必须用一个问号?来表示
(4)在定义语句的后面可以书写注释内容 当然也是可选的
在定义变量时,虽然变量名是可选的,但一定要写,否则寻址很不方便(用偏移量肯定不如直接用标识符方便)
2 字节变量
定义字节变量的定义符是DB/BYTE,每个字节只占用一个字节单元。其中BYTE是MASM6.0及其以后的版本的数据类型说明符。
例如:
COUNTER DB 6
DB 'A', 'D', 0Dh, '$'
TABLE DB 1, 3, 5, 7, 9, 11
上面的定义语句经过汇编后产生的内存单元如下:
|
… |
06 |
41 |
44 |
0D |
24 |
01 |
03 |
05 |
07 |
09 |
0B |
… |
COUNTER指向第一个格子06,'A'的ASCII码是41,所以第二个内存单元是41,TABLE指向01的格子。
定义语句的第二行没有给出变量名,访问这些存储单元的时候可以用COUNTER向后偏移,或者用TABLE向前偏移。这点和高级语言不一样,高级语言要用某个标识符来说明变量,也必须用该变量名来访问某个存储单元。
用定义符DB还可以定义字符串,在定义字符串的时候,必须用单引号或双引号把所要的字符括起来,引号内的字符一次放在字节单元里。
比如:
MSG1 DB "I am a student."
汇编后产生的内存单元:
|
… |
'I' |
' ' |
'a' |
'm' |
' ' |
'a' |
' ' |
's' |
't' |
'u' |
'd' |
'e' |
'n' |
't' |
… |
(实际上上面的格子里都应该是ASCII码,这样写只是比较直观)
3. 字变量
定义字变量的定义符为DW/WORD,每个字占用连续的两个内存空间。
比如:
Word1 DW 89H,1989H, -1
DW 0abcdH, ?, 0
内存分配如下:
|
89 |
00 |
09 |
89 |
FF |
FF |
CD |
AB |
-- |
-- |
00 |
00 |
… |
4. 双字变量
定义双字变量的定义符是DD/DWORD,每个双字变量占用四个内存空间。
比如:
DW1 DD 12345678H,?
DW2 DD 0abcd1243H
|
78 |
56 |
34 |
12 |
-- |
-- |
-- |
-- |
43 |
12 |
CD |
AB |
5. 多字变量
六字变量:DF
八字变量:DQ
十字变量:DT
调整偏移量需要涉及到三个伪指令EVEN,ALIGN,ORG,还有一个偏移量计数器的值“$”。
首先要明白什么是偏移量,偏移量大多是指从一个数据段的起始地址到我们定义的内存单元之间相隔的距离。
调整偏移量的目的在于快速的得到一个字的内容。
EVEN是偶对齐伪指令
作用是让下一个定义的内存变量从下一个偶地址单元开始分配,如果下一个偏移量是偶地址,该指令没有发生任何效用,否则汇编程序将空出一个字节,从下一个偶数地址来分配内存变量。
为什么偶数位置的字访问速度快呢?
比如访问5h位置上的字
16位cpu每次读两个字节
0h 1h
2h 3h
4h 5h
6h 7h,
这个时候cpu要读4h,5h,再读6h,7h,然后将5h上的内容和6h上的内容拼接,就相当于读两次,拼接一次,所以不如一个字处于偶数位置上访问来的快。
ALIGN是对齐伪指令,他的作用和EVEN偶对齐伪指令差不多,也是为了提高效率的,但是ALGIN对其伪指令更加灵活,并不是指定下一个变量从偶地址单元开始分配,而是从下一个能够整除操作数的内存单元开始分配。
比如
ALIGN 8
如果当前的内存单元分配到13H的话,下一个内存单元将从16H开始分配,13H-16H之间将留空。
ORG是调整偏移量伪指令,格式位
ORG 数值表达式
作用是告诉汇编编译器,接下来的内存变量从该数值表达式所指定的地址开始分配。
如果用ORG指定了一个开始分配的地址,可能会造成变量重叠的问题。
偏移量计数器的值
前面的3个伪指令都是改变偏移量计数器的值的方法,但是程序中如何引用偏移量计数器的值呢,汇编语言提供了一个符号$来引用偏移量计数器的值。
例如:
W1 DW $, $
ORG $+3
B1 DB 43h
第13章是简单内存变量的定义的学习笔记,中间经过了调整偏移量的伪指令的学习,再继续学习复合内存变量的学习。
简单内存变量回顾:
DB/BYTE(字节变量)
DW/WORD(字变量)
DD/DWORD(DoubleWord,双字,四字节)
DF/FWORD(Farword,三字,六字节)
DQ/QWORD(Quadword,四字,八字节)
DT/TBYTE(Tenbytes,十字节)
1. 重复说明符DUP
对于少量的内存变量,可以使用简单内存变量的定义方式,但如果我们现在要定义一万个byte类型的内存变量呢?
这时候就可以使用重复说明符DUP了,形式如下:
count DUP (表达式,表达式……,表达式)
count是重复次数,后面括号内的内容是重复的内容,比如:
BUFFER DB 100 DUP (?) //这个是100个byte
STRING DB 120 DUP ('ABCDE'),0 //这个是120个串
DATA1 DW 50 DUP (10H,20 DUP(1,2,3), 20H) //这个包含了一个子重复说明符,重复20次1,2,3
POINTS DD 12,30 DUP (0) //第一个内存单元是12,然后跟着30个0
2. 结构类型说明符STRUC
DUP只是重复一种类型,汇编还支持扩展自己的数据类型,可以把几种不同的基础数据类型组合在一起。
复合数据类型的定义使用结构类型说明符STRUC。
1). 结构类型的定义
用STRUC和ENDS可以把一系列的数据定义语句括起来组成新的,用户定义的结构类型,一般说明格式如下:
结构名 STRUC [Alignment][, NONUNIQUE]
数据定义语句序列
结构名 ENDS
结构名是一个合法标识符,且具有唯一性,前后结构名相一致。结构内被定义的变量称为结构字段,变量名就是字段名。
一个结构中可以有任意多个字段,字段名在结构内不可重复。
Alignment是对齐方式,可选的,可以用1,2,4来指定结构中字段的字节边界,缺省值是1。
NonUnique要求结构中的字段必须用全名才能访问。
例如:
COURSE STRUC
NO DD ?
CNAME DB 'Assember'
DW 0
COURSE ENDS
结构Course有4 + 8 + 2 = 14个字节,前俩字段有名称,第三个字段没字段名,可以用偏移量来访问。
2). 结构类型变量的定义
在定义某个结构类型后,程序员就可以说明该结构类型的内存变量。和简单数据类型变量的说明差不多。
[变量名] 结构名 <[字段值表]>
变量名是可选的,不写的话,只能用偏移量了。
结构名是定义的
字段值表用逗号分隔开各个字段,不写的话将使用定义结构体时候的缺省值,但是必须保留<>该括号。
3). 结构类型字段的引用
定义结构类型的变量后,如果要访问结构中的某个字段,可以使用.操作符
结构变量名.字段名
和高级语言的引用方式是一样的,不过还可以通过偏移量来引用,但是很不直观,比如
EXAM1 STRUC
F1 DW ?
F2 DB ?
EVEN ;偶对齐伪指令
F3 DW ?
EXAM1 ENDS
E1 EXAM1 <1234h,'A',2234h>
假如要引用E1中的F3,可以这样引用:
MOV AX, E1.F3
也可以通过偏移量
LEA SI, E1
MOV AX, [SI + 4] ;如果没有上面的偶对齐指令,就是加三了 呵呵 偶对齐 是为了更快的读取内存单元数据的
一 联合类型
1 联合类型的说明
联合数据类型在内存中占用的内存单元的位数是联合数据类型中最大的字段的位数。
联合数据类型通过不同字段的引用,可以获取不同的值。但是他们共享整个数据区域,字段之间相互覆盖。
联合数据类型的各字段的偏移量都是0。
说明格式:
[联合类型名] UNION [Alignment][, NONUNIQUE]
数据定义语句
[联合类型名] ENDS
比如
DATATYPE UNION
BB DB ?
WW DW ?
DD DD ?
DATATYPE ENDS
这个时候DATATYPE类型的变量将拥有4个字节,因为最大的字段DD拥有四个字节。
2 联合变量的定义
联合数据类型的变量只能用第一个字段进行初始化。
比如上面的DATATYPE:
U1 DATATYPE <12h> ;正确
U2 DATATYPE <1234h> ;错误,因为第一个字段是一个字节
U3 DATATYPE <'J'> ;也正确
3 联合变量的引用
定义后,可以根据不同的需要以不同的数据类型或者字段名来存取联合类型中的数据。
联合变量名.字段名
如:
MOV U1.WW, 1234H ;给联合类型变量赋予字数据
MOV AL, U1.BB ;AL=34H
MOV BX, U1.WW ;BX=1234H
MOV U1.BB, 'A' ;U1.WW=1241H,41H是'A'的ASCII码65
总结:联合类型现在已经不再常用,只有在一些内存资源非常紧张的工业设备上使用
=======================================================
二 记录类型
1 记录类型的说明
汇编语言的记录类型与高级语言的记录类型不同,是为二进制位存取数据提供方便的,记录类型的说明要用到另一个保留字RECORD,说明格式为:
记录名 RECORD 字段名:宽度[=初值表达式],……
1) 记录名代表记录类型
2) 记录类型可以由多个字段组成,每个字段用,隔开
3) 字段的宽度,代表该字段占据的二进制位数,必须是一个常数,所有字段之和不能大于16,大于8的话会自动占用两个字节,否则使用一个字节,记录中的字节以从右向左的方式分配
4) 初值是该字段的缺省值,没有初值默认是0,初值超过指定的二进制位个数所能表示的范围 编译器报错
如
COLOR RECORD BLINK:1, BACK:3=0, INTENSE:1=1, FORE:3
2 记录类型的定义
[变量名] 记录名 <[字段值表]>
1 变量名可选,没有的话要用偏移量
2 字段值以逗号分隔,字段值可省略,但如果要定义后面的字段,前面必须有空的逗号
3 所有字段都省略 要保存<>
如
COLOR1 COLOR <1,7,0,5>
COLOR2 COLOR <1,,,5>
CoLOR3 COLOR <>
3 记录的专用操作符
操作符WIDTH和MASK是用于记录类型的两个专用保留字,可以得到记录类型的不同属性
操作符WIDTH
返回记录或其字段的宽度,形式为:
WIDTH 记录名
WIDTH 记录字段名
操作符MASK
操作符MASK返回一个8位或16位的二进制数,在该二进制数中,被指定记录或字段使用的对应位为1,其余为0,形式如下:
MASK 记录名
MASK 记录字段名
记录字段
记录字段是一个特殊的操作符,本身也是操作数,返回值是该字段移到所在记录的最低位数所需要的位数,即该字段最低位在记录中的位置。
三 数据类型的自定义
在有了数据类型后,还可以定义这些数据类型的别名或指针类型,表达这种定义的伪指令是TYPEDEF 形式如下:
新数据类型名 TYPEDEF [位距] [PTR] 数据类型
位居可以是NEAR FAR 或者 PROC
例如
CHAR TYPEDEF BYTE ;给BYTE定义个别名CHAR
PCHAR TYPEDEF PTR CHAR ;定义一个字符指针数据类型PCHAR
使用:
CH1 CHAR 'ABCDEFG' ;定义一个字符串常量
PCH1 PCHAR CH1 ;定义一个指向字符串常量CH1的变量
一 标号
标号是特殊的标识符,代表代码段中某个具体位置,主要用于跳转指令的目标位置。说明形式如下:
标号: 汇编语句 ;注释
标号必须是合法标识符,在后面跟一个冒号,冒号和汇编语句用TAB或空格分隔开。
二 内存变量和标号的属性
变量是一个符号地址,其值会根据它代表的数据类型来对应从该地址以后若干个存储单元的数值。
标号也是一个符号地址,它所对应的存储单元中存放的是指令代码,虽然他们在性质上不同,但是他们都是代表了一个内存地址,所以他们都具有存储单元的属性。
下面介绍内存变量和标号的属性及其操作符。
1 段属性操作符
段属性操作符(SEG)返回该标识符所在段的段地址。我们一般只会取内存变量所在段的段地址,很少取标号所在段的段地址。
使用方法如下:
SCOPE DW ?
NAME DB 10 DUP(10)
MOV AX, SEG SCOPE
MOV BX, SEG NAME
现在AX的值等于BX的值,因为NAME和SCOPE在同一个段中定义。
2 偏移量属性操作符
偏移量属性操作符(OFFSET)返回该标识符离它所在段的基址有多少字节。一般情况下,程序员只会关注内存变量的偏移量,而很少关注标号的偏移量。
比如:
FIRST DD 12345678h,0
SCOPE DW ?,12h
NAME DB 10 DUP(10)
MOV AX, OFFSET SCOPE
MOV BX, OFFSET NAME
如果FIRST是第一个被定义的内存变量,这个时候AX为8,然后是两个WORD(4个字节),BX为12。
3 类型属性操作符
类型属性操作符(TYPE)可以返回该变量所占字节数,或者标号的远近类型。
4 长度属性操作符
长度属性操作符(LENGTH)是针对内存变量的操作符,返回重复操作符DUP中的重复数。
5 容量属性操作符
容量属性操作符(SIZE)是Length * Type。
6 强制属性操作符
在程序中,有时需要对同一个存储单元以不同的属性来访问,或者对一些不确定的存储属性需要显示指定的时候,就需要强制属性操作符PTR。
比如有一个指令: MOV [BX], 1h 这个指令是间接寻址方式,将1h存放到bx中所指的寄存器单元中,在实际传送操作中,是把"1H"扩展成8位做字节传送,还是扩展成16位做字传送呢,这 样指令有了二义性,因为[BX]指向的存储单元可以是字节或字的首地址,含有该指令的代码在汇编时会出错。
为了使指令中存储单元操作数具有明确的属性,可以使用强制属性操作符PTR。格式为:
数据类型 PTR 地址表达式
为了明确指令中存储单元的属性,可以把指令 MOV [BX], 1h改写成
MOV byte ptr [BX], 1H
MOV word ptr [BX], 1h
在指令中使用操作符PTR指定一个内存地址及之后的数据元素的类型后,在该指令中以PTR指定的类型为准,该强制属性只在该指令中有效,并不影响原内存单元的定义属性。
比如:
W1 DW 1234H, 5678H
B1 DB 2
DB 5
D1 DD 23456789H
MOV AX, word ptr B1 ;执行后 AX = 0502H
MOV BH, byte ptr w1 ;执行后 BH = 34H
MOV CH, byte ptr w1+1 ;执行后 CH = 12H
MOV word ptr D1, 12H ;执行后 D1 = 23456712H
7 存储单元的别名操作符
使用PTR强制属性操作符可以去以特定数据类型形式访问一个内存地址及其之后的几个内存单元,但是每次都使用PTR强制类型操作符比较麻烦,这个使用可以给一个内存变量声明一个其他类型的别名,以后即可使用该别名的数据类型来访问该内存变量。
格式为:
THIS 数据类型
比如:
WBUFFER EQU THIS WORD ;字类型变量WBUFFER
BUFFER DB 20 DUP(?) ;字节类型变量BUFFER
这个时候可以看出,使用了THIS声明的内存变量,并没有开辟内存空间,而是表示该内存变量以指定的数据类型引用这个内存地址,在下面的数据定义语句,开辟实际的内存空间
表达式是程序设计中一个重要的基本概念,由运算符,操作符,括号,常量和一些符号连在一起的式子,在汇编语言中表达式分为:数值表达式,地址表达式。
一 进制伪指令RADIX
伪指令RADIX用来设置整数的缺省机制,宏汇编开始时所默认的整数进制为10进制,格式如下:
.RADIX exp
伪指令要用.开始,exp是[2-16]之间的一个数字。
该伪指令指定的是默认进制,但是如果一个整数已经指定了进制,比如后缀h,则使用指定的进制,在源文件中,可以使用多个.RADIX改变其后整数的进制,但是为了避免代码混乱,不提倡这样做。
如:
.radix 8
B1 DB 10, 11, 12 ;这三个数都是八进制
DB 10D ;这个数是10进制
.radix 10
MOV AX, 1234 ;1234是十进制数
MOV AX, 1234H ;1234H是十六进制
一个容易混淆的地方:
.radix 16
DW 90D, 101B
哈哈 90D和101B是不是10进制和二进制呢? 按照后缀优先的规则,90D, 101B分别是10进制和二进制。
二 数值表达式
数值表达式是在汇编过程中能够由汇编程序确定其值的表达式,其组成部分在汇编时就能完全确定。所以它们是一些常量的运算组合。
1 常量
常量是一个立即数,直接写在汇编语句中,在程序的执行过程中,它不可能发生变化。一般用二进制 八进制 十进制 十六进制来书写。
在程序中还可以用.RADIX来改变数据的基数。
2 算术运算符
算术运算符包括正负号, + - * / 和 MOD(取模)。这些运算符和常量,括号,可以组成数值表达式。
比如 123 * 345 mod 3
3 关系运算符
关系运算符包括符号:EQ(相等),NE(不等),LT(小于),GT(大于),LE(小等于)和GE(大等于)。这些关系运算符和常量,括号也能组成数值表达式。
如果关系不成立,该数值表达式的结果是0,否则是0FFFFH。
如 120H LT 100H+3 的结果就是0
4 逻辑运算符
逻辑运算符包括按位操作符和移位操作符。
包括AND(逻辑与),OR(逻辑或),NOT(逻辑非),XOR(异或),SHL(左移位)和SHR(右移位)。
这些逻辑运算符和常量,括号可以组成数值表达式。
比如 1 SHL 3的结果是8。
5 表达式的其他操作符
在汇编语言中还有一些其他的操作符。
HIGH(高8位) LOW(低8位)
SEG(段地址) OFFSET(偏移量)
TYPE(标识符类型) LENGTH(变量长度) SIZE(变量容量)
WIDTH(记录/记录字段宽度) MASK(记录/记录字段的屏蔽位)
以上的几个操作符,只有HIGH和LOW以前没介绍过,他们的作用是选取表达式结果的高8位和低8位,使用格式如下:
HIGH 表达式
LOW 表达式
如:
HIGH (1234H + 100H)结果是13H
6 运算符和操作符的优先级
在汇编语言中的优先级如下:
LENGTH SIZE WIDTH MASK () [] . <>
PTR SEG OFFSET TYPE THIS :
* / MOD SHL SHR
HIGH LOW
+ -
EQ NE LT LE GT GE
NOT
AND
OR XOR
SHORT
三 地址表达式
地址表达式是计算存储单元地址的表达式,可由标号,变量名和由括号括起来的基址或变址寄存器组成。计算结果表示一个存储单元的地址,而不是该存储单元的值。
比如
B1 DB 11H,12H
MOV AX, B1 + 1 ;此时AX中存储的是12H的地址值
MOV BX, [B1 + 1] ;此时BX中是12H
MOV CX, [B1] + 1 ;此时CX中是13H
符号定义语句的目的是把一些经常使用的常量或数值表达式,使用一个符号来代替,这样不用在代码中逐个修改了,只需修改符号定义语句即可。
符号定义语句分为
1 等价语句 EQU
2 等号语句 =
3 符号名定义语句 LABEL
1 等价语句
格式为
符号名 EQU 表达式
比如可以用符号等价语句来代表常量或者表达式
NUMBER EQU 100
BUFF_LEN EQU NUMBER+2
还可以用符号等价语句来定义一个较长的字符串常量
MSG EQU "Hallo World"
还可以用符号等价语句定义一个指令或寄存器
MOVE EQU MOV
COUNTER EQU CX
还可以用符号等价语句对一片内存区域起一个另一个数据类型的别名,不过要配合THIS使用
WORD1 EQU THIS WORD
BYTE1 DB 12h,21h
这个时候WORD1就是2112H
2 等号语句
汇编语言提供了用等号来定义符号常数的方法,即可以用符号名来代表一个常数。格式为
符号名 = 数值表达式
比如
ABC = 100 * 5
DEF = ABC * 5
3 符号名定义语句
符号名定义语句LABEL与THIS的功能相似,该语句有一个指定的符号名,该符号名的段地址和偏移量与下面紧跟存储单元的相应属性相同,但该符号的类型是新指定的。LABEL语句的一般格式为:
符号名 LABEL 类型
常用类型有BYTE WORD DWORD NEAR FAR
如
WBUFFER LABEL WORD
BUFFER DB DUP(0)
WBUFFER和BUFFER拥有相同的段地址和偏移量,但是他们的数据类型不同
NEXT1 LABEL FAR
NEXT: MOV BX, AX
NEXT1和NEXT具有相同的段地址和偏移量,但NEXT1是远标号,NEXT是近标号。在同一个模块内转移可以使用NEXT在不同模块间转移,使用NEXT1。
指令系统的意义
指令系统确定了CPU所能完成的功能,是用汇编语言进行程序设计的最基本部分。如果不熟悉汇编指令的功能及其有关规定,那肯定不能灵活使用汇编语言。
汇编语言的指令格式
指令助记符 [操作数1 [,操作数2]] [;注释]
指令助记符决定了指令的功能,对应一条二进制编码的机器指令。
指令的操作数个数由该指令确定,可以没有操作数,也可以有一个或多个操作数,大多数指令要显示写出来,还有些操作数是隐含的。
当指令包含操作数的时候,书写时必须遵守:
指令助记符和操作数之间有分隔符,比如几个空格或tab符;
如果含有多个操作数,操作数之间用逗号分隔。
指令的使用规则
在学习汇编语言时,指令的功能是学习和掌握的重点,要准确有效并合理的使用这些指令,必须了解一些使用的规则。
归纳起来有三点:
1 要求指令操作数的寻址方式
2 指令对标志位的影响和标志位对指令的影响
3 指令的执行时间,对可完成同样功能的指令,要选用执行时间短的指令
指令系统的定义
指令系统是CPU指令的集合,CPU除了具有计算功能的指令外,还有实现其他功能的指令,也有为某种特殊应用而增设的指令。
指令系统的分类
1 数据传送指令
2 标志位操作指令
3 算术运算指令
4 逻辑运算指令
5 移位运算指令
6 位操作指令
7 比较运算指令
8 循环指令
9 转移指令
10 条件设置字节指令
11 字符串操作指令
12 ASCII-BCD码运算调整指令
13 处理器指令
9325

被折叠的 条评论
为什么被折叠?



