《80X86汇编语言程序设计教程》八 80386程序设计基础

时间:2022-09-01 07:59:48

1、  80386是x86系列发展中的里程碑,有两种模式:实模式和保护模式(其实还有虚拟8086模式,但是它实质上可归纳到保护模式)。实模式下相当于一块可进行32位处理的快速8086(之前设计的8086程序均可运行)。电脑开机时,处于实模式下,完成必要初始化工作,随后切换到保护模式,进入操作系统。

 

 

2、  80386寄存器

  分如下几组:

    a)通用寄存器:EAX、EBX、ECX、EDX、ESP、EIP、ESI、EDI(32位)

    b)段寄存器:ES、CS、SS、DS、FS、GS(16位)

    c)指令指针及标志寄存器:EIP、EFLAGS(32位)

    d)系统地址寄存器:GDTR(48位)、LDTR(32位)、IDTR(48位)、TR(32位)

    e)控制寄存器:CR0~CR3(32位)

    f)调试寄存器:DR0~DR7(32位)

    g)测试寄存器:TR6~TR7(32位)

  运用程序一般使用前3组,只有系统与内核程序可能使用到所有寄存器。80386包含了8086所有的寄存器,它们是8086寄存器的超集(8086寄存器参考“《80x86汇编语言程序设计教程》一 8086寻址方式与指令系统 ”)。

  1)  通用寄存器

    8个,32位。通用寄存器的低16位对应与8086的通用寄存器,存取它们时,相应的高16位不受影响,同时,低16位也可分为高8位与低8位,机制相仿。80386的所有通用寄存器都可以作为指针寄存器使用,即可用来存放基址,或可用来存放变址(ESP不能用来存放变址),可见,更具通用性。

  2)  段寄存器

    6个,16位。可同时访问6个段。实模式下与8086段寄存器功能相同(FS、GS也可用),物理地址计算方式也相同;保护模式下,段寄存器装载的不是段值,而是段选择子。

  3)  指令指针和标志寄存器

    a)指令指针寄存器

      同样,8086的IP寄存器对应EIP低16位。实模式下段最大为64K,EIP高16位为0。

    b)标志寄存器

 

31~18

17

16

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

全0

VM

RF

0

NT

IOPL

OF

DF

IF

TF

SF

ZF

0

AF

0

PF

1

CF

                           

      32位。除了8086FLAGS寄存器定义的标识位(位置和意义均不改变)以外,还增加了4个控制标志(在实模式下不使用):

      i) IO特权标志IOPL(I/O Privilege Level)

        有2位宽(12~13位),也叫I/O特权级字段,指定要求执行I/O指令的特权级。如果当前特权级在数值上小于或等于IOPL(数值越小,特权越高),那么I/O指令可执行,否则发生一个保护异常。

      ii)嵌套任务标志NT(Nested Task)

        14位,控制中断返回指令IRET的执行。如果NT = 0,用堆栈中保持的值恢复EFLAGS、CS和EIP(常规)。如果NT = 1,则通过任务转换实现中断返回。

      iii)重启动标志RF(Restart Flag)

        16位,标志是否接受调试故障(为0则接受,为1时忽略)。成功执行完成每一条指令后,处理器把RF清0。而接收到一个非调试故障时,把RF置1。

      iv)虚拟8086方式标志VM(Virtual 8086 Mode)

        17位,为1则处理器工作在虚拟的8086方式下,如果清0,则工作在保护模式。

  3)  系统地址寄存器

    a)全局描述符表寄存器GDTR

      48位,高32位为GDT基地址,低16位为GDT段界限,它是GDT(全局描述符表)系统段的一个伪描述符。32位基地址就是GDT的内存地址(线性地址),16位界限以字节为单位,由于段选择子中只有13位作为GDT中的描述符索引,而每个描述符长8个字节,那么16个位的界限刚刚好(2^13 * 8 = 2^16)。

    b)局部描述符表寄存器LDTR

      规定当前任务使用的局部描述符表LDT。类似于段寄存器,由程序员可见的16位寄存器(高13位为段选择子)和程序员不可见的高速缓存寄存器组成。在初始化或切换任务时,把当前任务对应的LDT描述符在GDT中的段选择子装入LDTR(可为空,表示当前任务没有LDT),处理器根据LDTR可见部分的段选择子,从GDT中取出对应的描述符,并把LDT基地址、界限和属性等信息保存到LDTR不可见高速缓存寄存器中。随后在任务的时间片内,对LDT的访问,就可以根据高速缓存寄存器中的有关信息进行合法性检查。段选择子中的TI位必须为0,其中的类型字段所表示的类型必须为LDT(DT = 1)。

    c)中断描述符表寄存器IDTR

      48位,指向中断描述符表IDT,高32位为IDT基地址,低16位为IDT段界限。由于80386只支持256个中断/异常,所以IDT最大长度为2K,以字节为单位的段界限为7FFH。IDTR指示IDT的方式与GDTR指示GDT的方式相同。

    d)任务状态段寄存器TR

      包含指示描述当前任务状态段的描述符选择子,从而指定当前任务的状态段。TR也分为两个部分,类似于LDTR。与上面不同的是,装入TR的选择子不可以为空,它必须索引到GDT中的描述符,且描述符的类型必须是TSS。

  4)  控制寄存器

    a)CR0:指示处理器工作方式

      i)  保护控制位

        CR0的位0为PE位,位31为PG位,分别控制分段(PE = 0为实模式)和分页管理机制(PG = 0位禁止分页管理机制):

 

PG

PE

处理器工作方式

0

0

实模式

0

1

保护模式,禁用分页机制,线性地址直接作为物理地址

1

0

非法组合,引起通用保护异常

1

1

保护模式,启用分页机制,线性地址经分页管理机制转为物理地址

 

        只有当所执行的程序的代码和至少一部分数据在线性地址空间和物理地址空间具有相同地址的情况下,才能改变PG位。

      ii)  协处理器控制位

        位1~位4分别为MP(算术存在位)、EM(模拟位)、TS(任务切换位)和ET(扩展类型位),它们控制浮点协处理器的操作。

      iii)  其他位

        位5~位30保留,必须为0

    b)CR1:保留

    c)CR2:由分页管理机制使用,用于发生页异常时报告出错信息。当发生页异常时(如缺页时),处理器把引起缺页异常的线性地址保存在CR2。操作系统中的页异常处理程序可以检查CR2的内容,从而查出线性地址空间中哪一页引起了本次异常。

    d)CR3:由分页管理机制使用,用于保存页目录表的起始物理地址。页目录为页对齐,所以高20位有效,低12位保留(必须为0)。CR3在实模式下也可以进行设置,以便进行分页机制的初始化。任务切换时,CR3中的内容要改变。

      有关分页管理机制的更多内容参考“《天书夜读:从汇编语言到windows内核编程》十四 CPU权限级与分页机制”。

  5)  调试寄存器

    a)DR0~DR3:线性断点地址寄存器

      用于设置硬件断点,可见最多只能设置4个bmp断点。断点的比对在物理地址转换之前(线性地址转换为物理地址之前)。

    b)DR4~DR5:保留

    c)DR6:调试状态寄存器

      主要是在调试异常产生后,报告调试异常的相关信息。

    d)DR7:调试控制寄存器

      主要是设置断点作用范围,类型等等一些具体的操作

  6)  测试寄存器

    有TR6和TR7,这两个寄存器用于在转换旁视缓冲器中测试随机存储器(RAM)和相联存储器(CAM)。TR6是测试命令寄存器,其内存放测试控制命令。TR7是数据寄存器,其内保存转换旁路缓冲器测试的数据。

 

 

3、  80386存储器寻址

  1)  存储器寻址基本概念

    实模式下存储器寻址方式同8086。保护模式下,段寄存器中存放的是段选择子(16位),它间接指示了段基地址(32)位,段基地址无需16的整数倍,存储单元地址是段基地址加上偏移地址。默认下引用情况位:CS:EIP、SS:ESP、SS:EBP,其它默认引用DS,ES、FS和GS要显示指定。数据存储仍然采用小端模式。

  2)  灵活的寻址方式

    各种存储器表示的是有效地址,即段内偏移。80386除了支持8086的各种寻址方式以外,还支持32位的存储器寻址方式。它允许内存地址的偏移可以由三部分内容相加构成:一个32位基址寄存器,一个可乘上比例因子1、2、4或8的32位变址寄存器,及一个8位或者32位的常数偏移量。并且这3部分可任意省去两部分。8个32位通用寄存器中,除了ESP寄存器,其余7个均可做变址寄存器使用。当EBP作为变址寄存器使用时(ESP不能),不影响默认段的选择---它只受所选基址寄存器所影响。如:“mov  al,[ebx + ebp*2]”默认段寄存器是DS,而“mov  [esp + ebp *2],ecx”默认段寄存器是SS。须注意的是:实模式下也可以采用32位存储器寻址方式,只不过偏移不应该超过0FFFFH,而且操作数的最高字节单元地址偏移不能超过0FFFFH。如果某一存储器操作数的地址是该操作数尺寸(长度)的倍数,那么称该操作数是对齐的,访问存储器的数据最快。

 

 

4、  80386指令集

  80386包含了8086指令集。可分如下几大类:数据传送指令、算术运算指令、逻辑运算和位移指令、控制转移指令、串操作指令、高级语言支持指令、条件字节设置指令、位操作指令、处理器控制指令和保护方式指令。其中很多指令都兼容8086指令,相同部分参考“《80x86汇编语言程序设计教程》一 8086寻址方式与指令系统”。

  1)  数据传送指令:通用数据传送指令、累加器专用传送指令、地址传送指令和标志传送指令

    a)通用数据传送指令组

      i)  数值传送指令MOV:同8086,可以为8位,16位和32位数据

      ii)  符号扩展指令MOVSX和零扩展指令MOVZX:把源操作数(8或16位寄存器或者存储器操作数)送入目的操作数(16或者32位寄存器),目的操作数空出的位用源操作数的符号位(MOVSX)或者零(MOVZX)填补。不影响标志位。一般MOVSX用来对有符号数进行扩展,而MOVZX用于对无符号数进行扩展。

      iii)  交互指令XCHG:同8086,可以为8位,16位和32位数据

      iv)  进栈指令PUSH:功能增强,除了16位寄存器,还可以是立即数,存储单元,长度可以为32位。须注意:在SP或ESP入栈时,8086入栈的是减操作以后的数(SP已经调整),而80386入栈的是减操作之前的数(SP或者ESP入栈后再调整)。

      v)  出栈指令POP:同8086,可弹出16位、32位数据。

      vi)  16位通用寄存器全进栈指令PUSHA和全出栈指令POPA:PUSHA依次将AX、CX、DX、BX、SP、BP、SI、DI等8个通用寄存器压入堆栈,然后SP减去16(所以,压入的是SP调整之前的值);而POPA分别弹出,SP的值不是弹出来的值,而是通过增加16来恢复。不影响标志位。

      vii)  32位通用寄存器全进栈指令PUSHAD和全出栈指令POPAD:类似PUSHA和POPA

    b)地址传送指令组

      i)  装入有效地址指令LEA:源操作数必须是存储器操作数,目的操作数为16位或32位通用寄存器。当为16位时,只装入低16位。

      ii)  装入指针指令组LDS、LES、LFS、LGS、LSS:将源操作数OPRD所指内存单元的4个或者6个连续字节单元的内容送入指令助记符给定的段寄存器和目的操作数REG中。不影响标志位。用LSS指令装载堆栈指针是简单安全的方法,它确保在一条指令中使SS和SP都被重置。

      iii)  标志传送指令组:LAHF、SAHF、PUSHF、POPF、PUSHFD、POPFD。前4者同8086,后2者数据为32位,PUSHFD不影响标志位,而POPFD影响。

      iv)  累加器专用传送指令组:IN、OUT、XLAT。同8086,不过均支持32位。XLAT指令以EBX为存放基址的寄存器。

  2)  算术运算指令

    a)加法减法指令组:ADD、ADC、INC、SUB、SBB、DEC、CMP和NEG,同8086,只不过扩充到了32位

    b)乘法除法指令组:MUL、DIV、IMUL和IDIV,同8086,扩充到32位,此外,提供新的有符号乘法指令:

1 IMUL  DST,SRC
2 IMUL DST,SRC1,SRC2

      第一种格式是将目的操作数DST与源操作数SRC相乘,结果送DST中;第二种格式是将SRC1与SRC2相乘,送入DST。其中目的操作数DST必须为16位或者32位通用寄存器,第一种格式中SRC长度必须与DST相同(8位立即数除外),可以是通用寄存器、存储单元或立即数。第二种格式中,SRC1只能是通用寄存器或存储单元,长度与DST相同,而SRC2只能是立即数。如果溢出,置OF和CF为1。

    c)符号扩展指令组:CBW、CWD、CWDE、CDQ。其中CBW与CWD同8086。而CWDE将16位寄存器AX的符号位扩展到32位寄存器EAX的高16位,是CBW的扩展。CDQ指令将寄存器EAX的符号位扩展到EDX的所有位,该指令是CWD的扩展。

    d)十进制调整指令组:DAA、DAS、AAA、AAS、AAM和AAD,同8086,参考“《80X86汇编语言程序设计教程》五 简单运用程序设计”。

  3)  逻辑运算和位移指令

    a)逻辑运算指令组:NOT、AND、OR、XOR和TEST,同8086,不过扩展到32位。

    b)一般位移指令组:SAL/SHL、SAR和SHR。移位位数可以位1、CL,也可以是8位立即数(低5位,范围0~31)。

    c)循环位移指令组:ROL、ROR、RCL和RCR:同一般移位指令,只不过对于ROL与ROR实际移位位数为操作数长度对应取8、16、32的模,而对于RCL与RCR先取低5位,然后再根据操作数长度对应取9、17、32的模

    d)双精度移位指令组:SHLD和SHRD。格式为:助记符  OPRD1,OPRD2,m。其中,操作数OPRD1是16位或32位的通用寄存器与存储单元;OPRD2长度必须与OPRD1一致,并且只能是寄存器。m为移位位数,或8位立即数或CL。

      SHLD功能是把操作数PRRD1左移指定的m位,空出的位用操作数OPRD2高端的m位填补(类似基于OPRD1:OPRD2的左移),但OPRD2内容不变,最后移除的位保留进CF。但移位位数为1时,OF标志受影响,否则清OF。而SHRD区别在于右移,空出位用OPRD2低端m位填补(类似基于OPRD2:OPRD1的右移),其它的雷同。该指令除了OF和CF,还影响ZF、SF和PF。注意:m取低5位(范围0~31),m = 0时本指令相当于NOP指令。m大于了OPRD1长度时,OPRD1与各标志位均无定义。

  4)  控制转移指令

    控制转移指令扩展后可达32位,在采用32位表示段内偏移时,段间转移的目的地址采用48位全指针形式。但是在实模式下,段最大长度位64KB,即使用32位表示段内偏移,实际偏移地址也禁止大于64KB。

    a)转移指令组

      i)无条件转移指令JMP

        分为段内直接、段内间接、段间直接、段间间接四类转移,在扩展形式中,段内转移采用32位地址偏移,而段间转移采用48位全指针形式表示。

      ii)条件转移指令

        80386条件转移指令(除JCXZ和JECXZ外---JECXZ是8086指令JCXZ的扩展)允许多个字节来表示转移目的地址与当前地址偏移之差,所以范围已经突破-128~127。但是,如果程序员能够计算是这个范围之类,可指定SHORT,而只产生一个字节地址差的调整指令。须注意的是,JCXZ与JECXZ只能用一个字节表示地址差值,所以范围仍然是-128~127。

    b)循环指令组

      包括LOOP、LOOPZ/LOOPE、LOOPNZ/LOOPNE。它们的非扩展形式保持原有功能,而扩展形式计数器从CX扩展位ECX。使用LOOPD、LOOPZD/LOOPED、LOOPNZD/LOOPNED指定使用ECX为计数器,而使用LOOPW、LOOPZW/LOOPEW、LOOPNZW/LOOPNEW指定使用CX为计数器。它们的转移范围仍然是-128~127。

    c)过程调用和返回指令组

      同样分为8086时的4种类型,同时具有扩展形式。扩展形式的地址偏移采用32位表示,段间调用采用48位全指针形式,而且把返回地址的CS压入堆栈时扩展成高16位为0的双字,这样栈内被压入两个双字。实模式下维持原样,保护模式下段内调用与返回转移方式同8086,段间调用与返回较位复杂,后续。

    d)中断调用与中断返回指令组

      实模式下同8086,保护模式下,中断调用指令INT把扩展的EFLAGS、CS和EIP压入堆栈(3个双字),细节后续。中断返回指令与溢出中断调用指令情形类似。

  5)  串操作指令

    a)基本串操作指令

      对应与字节与字的基本串操作指令无变化(参考“《80X86汇编语言程序设计教程》五 简单运用程序设计”),对应于双字元素的指令格式如下:

1 LODSD                ;串装入指令
2 STOSD ;串存储指令
3 MOVSD ;串传送指令
4 SCANSD ;串扫描指令
5 CMPSD ;串比较指令

      它们是对应与字位元素的串操作指令的扩展(AX扩展为EAX等),注意:可通过段前缀超越方式改变源串采用的段寄存器,但是不能改变目的串段寄存器。

    b)重复前缀

      有REP、REPZ/REPE、REPNZ/REPNE。实模式下同8086,保护模式下位其扩展形式,采用32位扩展偏移,ECX作为循环计数器。

    c)串输入指令

      3种格式:

1 INSB                ;输入字节(Byte)
2 INSW    ;输入字(Word)
3 INSD ;输入双字(Dword)

      从由DX给出的端口地址的端口读入一个字符,并送入 ES:DI(或EDI)所指向的目的串中,同时根据方向标志DF和字符类型(INSB为Byte、INSW为Word、INSB为Dword)调整DI(或EDI)(1、2或者4)。汇编语言可使用统一格式:

      INS    DSTS,DX

      根据DSTS类型决定使用上述指令中的哪一条。须注意的是,使用前,必须先给目的串ES:DI(或EDI)赋值。串输入指令不影响标志位。可使用重复前缀,但必须注意端口数据能够连续准备。

    d)串输出指令

      一切雷同串输入时的情形。指令格式有OUTSB、OUTSW和OUTSD。DX指定端口,DS:SI(或ESI)指定源串,统一格式为“OUTS    DX,SRCS”。

  6)  高级语言支持指令

    a)建立与释放堆栈框架指令

      C等语言中使用堆栈传递参数以及安排局部变量(参考“《天书夜读:从汇编语言到windows内核编程》一 汇编指令与C语言”)。在函数内部的实现时,开头与结尾在保存BP(或EBP)的时候都有一个固定模式,叫堆栈框架。

      i)  建立堆栈框架指令ENTER

        一般格式:ENTER    CNT1,CNT2

        CNT1为16位立即数,表示框架大小,也就是子程序要安排在堆栈中的局部变量所需要的字节数;CNT2是8位立即数,表示子程序嵌套级别,也就是需要从外层框架复制到当前框架的指针数(这个暂时不理解)。ENTERW与ENTERD分别是ENTER的非扩展与扩展形式。不影响标志位。

        “ENTER    4,0”指令操作如下:

          BP(非扩展)或EBP(实模式或保护模式)进栈,即保存原堆栈框架指针

          BP(非扩展)或EBP(实模式或保护模式) = SP(非扩展)或EBP(实模式或保护模式)

          SP(非扩展或者实模式)或ESP(保护模式)= SP(非扩展或者实模式)或ESP(保护模式)- 16位立即数(CNT1)

      ii)  释放堆栈框架指令LEAVE

        一般格式:LEAVE

        与ENTER指令功能相反:释放当前子程序(过程)在堆栈中的局部变量,使BP和SP恢复到最近一次ENTER指令被执行前的值。LEAVE同样有LEAVEW和LEAVED两种分别表示非扩展与扩展形式的指令。 它们做的操作就两个:一个是SP = BP(或ESP = EBP)以及BP(或EBP)退栈。不影响标志位。注意:LEAVE只负责释放堆栈框架,并不返回,函数返回仍然需要RET指令。

  7)检查数组下标界限指令BOUND

    一般格式:BOUND    OPRD1,OPRD2

    OPRD1为16位(或32位)寄存器操作数,用于给出要检查的数组下标;OPRD2是32位(或64位)存储器操作数,低字(或低双字)含起始下标,高字(或高双字)含结尾下标。该指令检查OPRD1给出的有符号数是否在由操作数OPRD2给出的数组界限之内,如果越界,那么产生5号异常(中断)。该指令不影响标志位。

  8)  条件字节设置指令

    这些指令根据一些标志位设置某个字节的内容为1或者0。一般格式为:SETcc    OPRD

    其中cc位助记符一部分,用于表示条件,这些条件与条件转移指令中的条件相同(如“SETNZ”对应于“JNZ”,更多参考“《80x86汇编》一 8086寻址方式与指令系统(12):http://user.qzone.qq.com/703016035/blog/1382101279”);操作数OPRD是8位寄存器或者存储单元,用于存放测试的结果,如果测试条件为真,那么OPRD操作数置为1,测试方法与条件跳转指令的测试方法相同。不影响标志位。

  9)  位操作指令

    a)位测试及设置指令组

      4条:位测试(Bit Test)指令BT(把测试结果送CF)、位测试并取反(Bit Test and Complement)指令BTC(先把测试结果送CF,再对测试位取反)、位测试并复位(Bit Test and Rest)BTR(先把测试结果送CF,再对测试位复位)、位测试并置位(Bit Test and Set)指令BTS(先把测试结果送CF,再对测试位置位)。都是带两个操作数OPRD1与OPRD2。其中,OPRD1是16位或32位通用寄存器或存储单元操作数,用于指定要测试的内容;OPRD2必须是8位立即数或者与操作数OPRD1长度相等的通用寄存器操作数,用于指定测试的位。

      测试方法:设OPRD2除以OPRD1的位长度后所得的商为disp,余数位offset。如果OPRD1是寄存器,那么offset是要测试位的位号;如果OPRD1是存储单元,那么OPRD1的偏移与disp相加之和为实际测试的存储单元的偏移,offset是该存储单元要测试的位号。OPRD2取有符号数的整数值,为16位和32位时,可测试位串范围分别为-32K至32K-1与-2G至2G-1。除了CF不影响其它标志位。

      须注意:如果OPRD2位立即数,那么其值不应该超过OPRD1的位长度(限制寄存器或存储器单元类型解释的范围之内),但是,汇编程序对存储器操作数支持更大的立即数。

    b)位扫描指令组

      2条:顺向位扫描(Bit Scan Forward)指令BSF、逆向位扫描(Bit Scan Reverse)指令BSR。它们都接收两个操作数OPRD1与OPRD2。两者均为16位或32位通用寄存器或存储单元。但OPRD1与OPRD2操作数的位长度必须相等(及OPRD1必须能够容纳OPRD2位长度数值)。

      顺向扫描功能是从右向左(位0到位15或者位0到位31)扫描字或者双字OPRD2,把OPRD2首次遇到的位“1”所在的位号送入OPRD1。逆向扫描类似,只不过扫描方向相反(位15到位0或者位31到位0)。

如果OPRD2为0,那么ZF被置1,OPRD1值不确定;否则ZF被清0。不影响其它标志位。

  10)  处理器控制指令

    a)设置标志指令组

      设置进位标志CF指令:CLC、STC和CMC与8086相同

      设置方向标志DF指令:CLD和STD与8086相同

      设置中断允许标志IF指令:CLI和STI在实模式下同8086,在保护模式下是I/O敏感指令,后续。

    b)空操作指令

      指令格式为“NOP”,什么也不做,一个字节机器码,主要用于预留代码空间或者延时之类。

    c)外同步指令和前缀

      i )等待指令WAIT

        格式“WAIT”,功能是等待直到BUSY引脚为高电平。BUSY由数值协处理器控制,所以该指令功能是等待数值协处理器,以便与它同步。后续。

      ii)*前缀LOCK

        锁定其后指令的目的操作数确定的存储单元,通过使LOCK信号在指令期间一直保持有效来实现。在多处理器环境中,使用这种方式可保证指令执行时独占共享内存。只有下述指令可用*前缀LOCK,并且目的操作数为存储器操作数:XCHG、ADD、ADC、INC、SUB、SBB、DEC、NEG、OR、AND、XOR、NOT、BT、BTS、BTR、BTC

 

 

5、  实模式下的程序设计

  实模式下,80386相当于一个可进行32位处理的8086处理器。可利用32位通用寄存器,可使用新增指令,可使用扩展寻址方式。段最大长度为64KB,但不像真正的8086处理器,段内偏移大于0FFFFH使,不会对0FFFFH进行取模,从而没有对64KB的反绕,而是导致段越界异常。

  1)  说明处理器类型的伪指令

    告诉编译器使用到的指令集(确实实只使用8086指令集),有如下指令集:

1 .8086                ;只支持对8086指令的汇编
2 .186 ;只支持对80186指令的汇编
3 .286 ;支持对非特权80286指令的汇编
4 .286C ;支持对非特权80286指令的汇编
5 .286P ;支持对80286所有指令的汇编
6 .386 ;支持对非特权80386指令的汇编
7 .386C ;支持对非特权80386指令的汇编
8 .386P ;支持对80386所有指令的汇编

  2)  关于段属性类型的说明

    实模式下同8086,64KB,称为16位段。保护模式下段长度可达4GB,称为32位段。保护模式下也可使用16位段。同8086,80386的段也有定位类型,组合类型,类别,此外,还多了一个可选的属性类型。属性类型说明符位“USE16”或者“USE32”,在使用“.386”等伪指令以后,缺省的属性类型是“USE32”,如果不指定,则为“USE16”

  3)  操作数和地址长度前缀

    实模式下只能使用16位段,但是可使用32位操作数,也可使用32位形式表达的地址,这是利用操作数长度前缀66H和存储器地址长度前缀67H来表达的。同样,在保护模式下,32位段中,正常使用的是32位和8位操作数,但是可使用66H和67H表示使用16位操作数。须注意的是,这两个前缀不需要我们显示指定,汇编编译器会自动判别,在翻译机器码的时候指定前缀。在《天书夜读》中曾提过,寄存器超越前缀(66H)与地址超越前缀(67H)表示在32位模式下使用16位地址或者寄存器,以及在16位模式下使用32位地址或寄存器。参考“《天书夜读:从汇编语言到windows内核编程》十三 机器码与反汇编引擎”。

 

 

6、  实模式程序设计举例

  1)  以十进制、十六进制和二进制形式显示双字存储单元F000:1234H的内容(去前导0)

 

  1 ;以十进制、十六进制和二进制形式显示双字存储单元F000:1234H的内容(去前导0)
2 .386    ;支持对80386非特权指令的反汇编
3
4 cseg segment use16 ;16位段
5 assume cs:cseg
6
7 start:
8 mov ax,0f000h
9 mov fs,ax
10 mov dword ptr fs:[1234h],7a8b9ch;随便赋值一个,否则我测试时总是为0
11 mov eax,fs:[1234h]
12
13 ;以十进制形式显示
14 call   TODEC
15 call   NEWLINE
16 ;以十六进制形式显示
17 call   TOHEX
18 mov   al,'H'
19 call   MYECHO
20 call   NEWLINE
21 ;以二进制形式显示
22 mov eax,fs:[1234h]
23 call   TOBIN
24 mov al,'B'
25 call   MYECHO
26 call   NEWLINE
27 ;结束
28
29 mov ah,07h
30 int 21h
31
32 mov ah,4ch
33 int 21h
34 cseg ends
35
36
37 ;各个子程序定义
38 ;段名与上相同,汇编时组合到同一个段
39 ;故全部可采用near调用
40 cseg segment use16
41 assume cs:cseg
42 ;子程序名: TODEC
43 ;功 能: 以十进制数形式显示32位值
44 ;入口参数: EAX = 要显示的值
45 ;出口参数: 无
46 ;说 明:不考虑算法效率,目的是熟悉指令运用,否则采用BCD码调整比较合适
47 TODEC proc near
48
49 pushad    ;32位通用寄存器全部进栈
50 mov ebx,10
51 xor cx,cx
52 DEC1:
53 xor edx,edx ;edx清0,那么edx:eax实际上等于eax
54 div ebx ;无符号除:edx:eax除以ebx,商在eax,余数在edx
55 push   dx ;edx存余数,由于余数最高为9,dx足够
56 inc cx ;记录位数
57 or eax,eax
58 jnz DEC1 ;商不为0则继续
59 DEC2:
60 pop ax ;依次从堆栈中取出余数输出
61 call   TOASC ;转对应ASCII码
62 call   MYECHO ;从高位到底位逐位输出
63 loop   DEC2
64 popad    ;32位通用寄存器全部出栈
65 ret
66
67 TODEC endp
68
69 ;子程序名: TOBIN
70 ;功 能: 以二进制数形式显示32位值
71 ;入口参数: EAX = 要显示的值
72 ;出口参数: 无
73 TOBIN proc near
74
75 push    eax ;逐个寄存器入栈来保护现场
76 push   ecx
77 push    edx
78 bsr edx,eax ;逆向位扫描eax,找到第一个为“1”的位号给edx(dl)
79 jnz BIN1   ;eax不等于0则跳
80 xor dx,dx ;否则记录位号为0
81
82 BIN1:
83 mov cl,31
84 sub cl,dl ;计算要左移的位数,
85 shl eax,cl ;算术左移,过滤前导0
86 mov cx,dx
87 inc cx ;要输出的循环字符个数
88 mov edx,eax ;eax暂存到edx
89 BIN2:
90 rol edx,1 ;循环左移,依次取最高位到cf
91 mov al,'0'
92 adc al,0 ;带进位加法,将cf转ASCII码
93 call   MYECHO ;输出
94 loop   BIN2
95 pop edx
96 pop ecx
97 pop eax
98 ret
99
100 TOBIN endp
101
102 ;子程序名: TOHEX
103 ;功 能: 以十六进制数形式显示32位值
104 ;入口参数: EAX = 要显示的值
105 ;出口参数: 无
106 ;说 明:不考虑算法效率,目的是熟悉指令运用,否则可以不使用局部存储区
107 TOHEX proc near
108
109 COUNTB = 8 ;8个字节的局部变量区域,用于保存转换结果
110 enter COUNTB,0 ;构建堆栈框架
111 movzx ebp,bp ;清ebp高16位,16位段只使用了bp
112 mov ecx,COUNTB ;循环8次,dowrd类型转换后有8个十六进制字符
113 mov edx,eax ;暂存eax到edx
114
115 HEX1: ;复制edx的dword数值到局部区域,每4位扩展到一个字节
116 mov al,dl ;从低向高逐4位4位的取
117 and al,0fh
118 mov [ebp + ecx - COUNTB - 1],al ;ebp - (9 - ecx)即ebp向上偏移9 - ecx(1,2,...,8)
119 ror edx,4 ;edx循环右移4位,接下来取较高的4位
120 loop HEX1 ;循环结束时,栈局部区低字节对应eax高字节的高4位
121 mov cx,COUNTB
122 xor ebx,ebx ;ebx = 0
123 HEX2: ;从栈区低字节取向高字节即从eax高字节4位4位的输出十六进制ASCII码
124 cmp byte ptr [ebp + ebx - COUNTB],0 ;ebp - (8 - ebx),即ebp向上偏移8 - ebx(8,7,...,1)
125 jnz HEX3 ;不是0则跳
126 inc ebx ;跳过前导0
127 loop HEX2
128 dec ebx ;如果全部为0
129 mov cx,1 ;输出一位字符,即0
130 HEX3:
131 mov al,[ebp + ebx - COUNTB] ;取4位值
132 inc ebx
133 call   TOASC ;输出字符
134 call   MYECHO
135 loop   HEX3
136 leave
137 ret
138
139 TOHEX endp
140 ;子程序名: TOASC
141 ;功 能: 把一位十六进制数转换位对应的ASCII码
142 ;入口参数: AL = 要显示的值
143 ;出口参数: AL = ASCII码
144 TOASC proc near
145
146 and al,0fh
147 add al,'0' ;转ASCII码
148 cmp al,'9' ;是否位数字字符
149 seta   dl ;如果为'A'~'F',则dl = 1,否则dl = 0
150 movzx dx,dl ;dl扩展到dx
151 imul dx,7 ;新的有符号数乘法指令:dx = dx * 7
152 add al,dl
153 ret
154
155 TOASC endp
156
157 ;子程序名: NEWLINE
158 ;功 能: 换行
159 ;入口参数: 无
160 ;出口参数: 无
161 ;说 明:影响dl
162 NEWLINE proc near
163 push   eax
164 push   edx
165
166 mov ah,02h
167 mov dl,0ah
168 int 21h
169 mov dl,0dh
170 int 21h
171
172 pop edx
173 pop eax
174 ret
175 NEWLINE endp
176
177 ;子程序名: MYECHO
178 ;功 能:显示字符
179 ;入口参数: AL = 要输出的字符
180 ;出口参数: 无
181 ;说 明:影响AH,DL
182 MYECHO proc near
183
184 mov dl,al
185 mov ah,02h
186 int 21h
187 ret
188
189 MYECHO endp
190
191
192 cseg ends
193
194 end start

 

  输出效果:

 

 《80X86汇编语言程序设计教程》八 80386程序设计基础

八(6)实模式下程序设计例1

 

  2)  接收十进制(’D’、’d’结尾或者不带字母)、十六进制(’H’或’h’结尾)、二进制数(’B’或’b’结尾)形式输入的两个无符号数,将它们相乘并把结果以十进制数输出。输入数值大于32位二进制数表示数值时为溢出,溢出和输入数字无效时须提示’x’。这个程序有两个亮点:一个是大小写字母互转的方式,另一个是64位数除以32位数的方式。

 

  1 ;接收十进制('D'、'd'结尾或者不带字母)、十六进制('H'或'h'结尾)、二进制数('B'或'b'结尾)形式输入的两个无符号数,
2 ;将它们相乘并把结果以十进制数输出。输入数值大于32位二进制数表示数值时为溢出,溢出和输入数字无效时须提示'x'。
3 ;这个程序有两个亮点:一个是大小写字母互转的方式,另一个是64位数除以32位数的方式
4
5 .386
6 cseg segment use16
7 assume cs:cseg
8
9 ;子程序名: LTODEC
10 ;功 能: 以十进制数码显示一个64位无符号数
11 ;入口参数: EDX:EAX = 64位二进制数
12 ;出口参数: 无
13 LTODEC proc near
14 xor cx,cx
15 LTDEC1:
16 mov ebx,10 ;除数为10
17 call DIVX ;EDX:EAX / EBX = EDX:EAX ... EBX
18 push   bx ;暂存余数
19 inc cx ;最终结果的位数记录
20 mov ebx,edx ;被除数高32位移入ebx
21 or ebx,eax ;判断被除数是否为0
22 jnz LTDEC1 ;继续除
23 LTDEC2:
24 pop ax ;取结果,逐位输出
25 and al,0fh
26 add al,'0' ;转ASCII码
27 call   MYECHO ;输出
28 loop   LTDEC2 ;cx已经存入了结果的数值位数
29 ret
30
31 LTODEC endp
32
33 ;子程序名: DIVX
34 ;功 能: 64位数除以32位数,商用64位表示
35 ;入口参数: EDX:EAX = 被除数
36 ; EBX = 除数
37 ;出口参数: EDX:EAX = 商
38 ; EBX = 余数
39 ;说 明:这个应该就是仿硬件执行除法时的步骤,与人算除法步骤相仿
40 DIVX proc near
41 push   ecx
42 push   esi
43 mov cx,64 ;循环64次
44 xor esi,esi ;esi暂存中间结果
45 DIVX1: ;以下2条指令将被除数乘以2
46 shl eax,1 ;逻辑左移,最高位进入cf
47 rcl edx,1 ;带进位循环左移,最高位进cf,cf位到最低位
48 rcl esi,1 ;左移溢出位存入esi
49 jc short DIVX2 ;溢出,这里溢出仍然可以继续,不会出错
50 cmp esi,ebx ;高位移出的数值是否大于被除数
51 jb short DIVX3 ;低于则被除数继续左移
52 DIVX2: ;esi >= ebx
53 sub esi,ebx ;中间余数
54 bts ax,0 ;上商
55 DIVX3:
56 loop   DIVX1
57 mov ebx,esi ;保存余数
58 pop esi
59 pop ecx
60 ret
61
62 DIVX endp
63
64 ;子程序名: GETVAL
65 ;功 能: 接收一个最大为32位表示的无符号整数(可用三种进制表示)
66 ;入口参数: 无
67 ;出口参数: EAX = 接收到的无符号整数
68 ; BL = 0 表示没有接收到数
69 ; 1 表示接收到的数符合要求
70 ; -1 表示接收到的数无效
71 ;说 明:
72 GETVAL proc near
73 COUNTL = 36
74 enter COUNTL,0
75 push   ecx
76 push   edx
77 push   ds
78 push   es
79 ;置段寄存器
80 mov ax,ss
81 mov ds,ax
82 mov es,ax
83 ;接收一个字符串
84 lea edx,[bp - COUNTL] ;缓冲区首地址DS:DX
85 mov byte ptr [edx],COUNTL - 2 ;缓冲区最大容量
86 mov ah,10
87 int 21h
88 call NEWLINE
89 ;取字符串长度
90 inc edx ;缓冲区第二字节存放接收到的字符长度(不含回车键)
91 mov cl,[edx]
92 xor ch,ch
93 movzx ecx,cx
94 ;去掉前导空格
95 inc ecx
96 GVAL1:
97 dec ecx
98 inc edx
99 cmp byte ptr [edx],' '
100 jz GVAL1
101 mov bl,0 ;bl = 0表示没有接收到数
102 jecxz GVAL5 ;输入的字符串为空
103 ;去掉尾部空格
104 GVAL2:
105 cmp byte ptr[edx + ecx - 1],' '
106 loopz GVAL2
107 setnz al
108 add cl,al
109 jecxz GVAL5 ;字符串为空
110 ;处理可能的十进制数串
111 mov bl,-1
112 mov al,[edx + ecx -1]
113 cmp al,'0'
114 jb short GVAL5 ;非法字符
115 cmp al,'9'
116 ja short GVAL3 ;大于'9'
117 GVAL2A:
118 call   DSTOV ;判断为10进制数
119 jmp short GVAL5
120 GVAL3:
121 btr ax,5 ;清5位,小写字母转大写44H
122 cmp al,'D'
123 jnz GVAL3A
124 dec ecx
125 jmp GVAL2A
126 ;处理可能的十六进制串
127 GVAL3A:
128 cmp al,'H'
129 jnz GVAL4
130 dec ecx
131 call   HSTOV ;判断为16进制数
132 jmp short GVAL5
133 ;处理可能的二进制串
134 GVAL4:
135 cmp al,'B'
136 jnz short GVAL5
137 dec ecx
138 call   BSTOV ;判断为2进制数
139 GVAL5:
140 pop es
141 pop ds
142 pop edx
143 pop ecx
144 leave
145 ret
146 GETVAL endp
147
148
149 ;子程序名: DSTOV
150 ;功 能: 把一个十进制数码的ASCII码串转为对应数值
151 ;入口参数: EDX = 字符串开始地址偏移
152 ; CX = 字符串长度
153 ;出口参数: EAX = 转化结果(无符号数)
154 ; BL = 0 表示没有接收到数
155 ; 1 表示接收到的数符合要求
156 ; -1 表示接收到的数无效
157 DSTOV proc near
158 push   esi
159 mov esi,edx
160 mov bl,0 ;bl = 0表示没有接收到数
161 jcxz   DTV3
162 mov bl,-1 ;bl = -1表示数字无效
163 xor eax,eax ;存结果
164 mov edx,10
165 push   edx ;压入一个双字10,作为后面的乘数
166 DTV1:
167 mov dl,[esi] ;esi作为指针
168 inc esi
169 cmp dl,'0'
170 jb short DTV2 ;非法字符
171 cmp dl,'9'
172 ja short DTV2 ;非法字符
173 and dl,0fh
174 push   edx
175 mul dword ptr [esp + 4] ;eax = eax * 10
176 or dl,dl
177 pop edx ;不影响标志位
178 jnz short DTV2 ;dl在pop前不为0则转,说明乘法溢出了32位
179 add eax,edx ;eax = eax * 10 + edx
180 jc DTV2 ;进位了,溢出
181 loop   DTV1
182 mov bl,1 ;bl = 1表示转换成功
183 DTV2:
184 pop edx
185 DTV3:
186 pop esi
187 ret
188 DSTOV endp
189
190 ;子程序名: HSTOV
191 ;功 能:把一个十六进制数码的ASCII码串转为对应数值
192 ;入口参数: EDX = 字符串开始地址偏移
193 ; CX = 字符串长度
194 ;出口参数:EAX = 转化结果(无符号数)
195 ; BL = 0 表示没有接收到数
196 ; 1 表示接收到的数符合要求
197 ; -1 表示接收到的数无效
198 HSTOV proc near
199 push   esi
200 mov bl,0 ;bl = 0表示没有接收到数
201 jcxz   HTV4
202 mov bl,-1 ;bl = -1表示数字无效
203 xor eax,eax ;存结果
204 xor esi,esi ;存中间结果
205 HTV1:
206 mov al,[edx] ;edx作为指针
207 inc edx
208 bts ax,5 ;大写字母转小写
209 cmp al,'0'
210 jb short HTV2 ;非数字字符
211 cmp al,'9'
212 ja HTV2 ;非数字字符
213 and al,0fh
214 jmp short HTV3
215 HTV2:
216 cmp al,'a'
217 jb short HTV4 ;非法字符
218 cmp al,'f'
219 ja short HTV4 ;非法字符
220 sub al,'a' - 10 ;al = 10 + (al -'a'),即转为对应数字
221 HTV3:
222 test   esi,0F0000000h ;即将溢出
223 jnz short HTV4
224 shl esi,4 ;esi = esi * 16
225 add esi,eax ;esi = esi * 16 + eax
226 loop   HTV1
227 mov bl,1 ;bl = 1表示转换成功
228 HTV4:
229 mov eax,esi ;eax位最终结果
230 pop esi
231 ret
232 HSTOV endp
233
234 ;子程序名: BSTOV
235 ;功 能: 把二进制数码的ASCII码串转为对应数值
236 ;入口参数: EDX = 字符串开始地址偏移
237 ; CX = 字符串长度
238 ;出口参数: EAX = 转化结果(无符号数)
239 ; BL = 0 表示没有接收到数
240 ; 1 表示接收到的数符合要求
241 ; -1 表示接收到的数无效
242 BSTOV proc near
243 push   esi
244 mov bl,0 ;bl = 0表示没有接收到数
245 jcxz   BTV2
246 mov bl,-1 ;bl = -1表示数字无效
247 xor eax,eax
248 xor esi,esi ;esi作为指针
249 xchg   edx,esi ;edx暂存中间结果
250 BTV1:
251 mov al,[esi]
252 inc esi
253 cmp al,'0'
254 jb short BTV2 ;非法字符
255 cmp al,'1'
256 ja short BTV2 ;非法字符
257 and al,0fh
258 bt edx,31
259 jc short BTV2 ;结果即将溢出
260 shl edx,1 ;edx = edx * 2
261 add edx,eax ;edx = edx * 2 + eax
262 loop   BTV1
263 mov bl,1
264 BTV2:
265 mov eax,edx
266 pop esi
267 ret
268 BSTOV endp
269
270
271 ;子程序名: TOASC
272 ;功 能: 把一位十六进制数转换位对应的ASCII码
273 ;入口参数: AL = 要显示的值
274 ;出口参数: AL = ASCII码
275 TOASC proc near
276
277 and al,0fh
278 add al,'0' ;转ASCII码
279 cmp al,'9' ;是否位数字字符
280 seta   dl ;如果为'A'~'F',则dl = 1,否则dl = 0
281 movzx dx,dl ;dl扩展到dx
282 imul   dx,7 ;新的有符号数乘法指令:dx = dx * 7
283 add al,dl
284 ret
285
286 TOASC endp
287
288
289 ;子程序名: NEWLINE
290 ;功 能:换行
291 ;入口参数: 无
292 ;出口参数: 无
293 NEWLINE proc near
294 push   eax
295 push   edx
296
297 mov ah,02h
298 mov dl,0ah
299 int 21h
300 mov dl,0dh
301 int 21h
302
303 pop edx
304 pop eax
305 ret
306 NEWLINE endp
307
308 ;子程序名: MYECHO
309 ;功 能:显示字符
310 ;入口参数: AL = 要输出的字符
311 ;出口参数: 无
312 ;说 明:影响AH,DL
313 MYECHO proc near
314
315 mov dl,al
316 mov ah,02h
317 int 21h
318 ret
319 MYECHO endp
320
321 cseg ends
322
323
324 cseg segment use16
325 assume cs:cseg
326
327 start:
328 mov cx,2 ;接收2个数
329 @@1:
330 mov al,':'
331 call   MYECHO ;显示冒号
332 call   GETVAL ;获取数值
333 cmp bl,0
334 je OVER ;数值为空则转,程序结束
335 cmp bl,1
336 je short @@2 ;数值合法则转
337 mov al,'x' ;否则为数值无效
338 call   MYECHO ;显示'x'
339 call   NEWLINE ;换行
340 jmp @@1 ;继续接受输入
341 @@2:
342 push   eax ;暂存第一个数
343 loop   @@1 ;再接收一个数
344 mov al,'='
345 call   MYECHO ;显示'='
346 pop eax
347 pop edx ;取两个数到eax,edx
348 mul edx ;无符号数相乘
349 call   LTODEC ;结果转ASCII码并输出
350 call   NEWLINE
351 call   NEWLINE ;换行
352 jmp start ;开始新一轮输入
353 OVER:    ;程序结束
354 mov ah,0ah
355 int 21h
356
357 mov ah,4ch
358 int 21h
359 cseg ends
360 end start

 

输出效果:

 

 《80X86汇编语言程序设计教程》八 80386程序设计基础

八(6)实模式下程序设计例2