《汇编语言与接口技术》-第4 章_第1页
《汇编语言与接口技术》-第4 章_第2页
《汇编语言与接口技术》-第4 章_第3页
《汇编语言与接口技术》-第4 章_第4页
《汇编语言与接口技术》-第4 章_第5页
已阅读5页,还剩114页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

4.1汇编语言基本知识4.1.1汇编语言概述计算机软件实际上就是一些程序的集合,编写程序的工作过程称为程序设计,编写程序时所用的语言称为程序设计语言。程序设计语言可以分为三大类:机器语言、汇编语言和高级语言。编写程序时,必须选择合适的语言,既便于程序员编写程序,又能使计算机按照程序的控制完成所需要的功能。1.机器语言机器语言是计算机能够直接识别的语言,它直接用二进制代码的机器指令表示。机器指令通常由操作码和操作数组成,每条机器指令都由CPU执行,控制计算机完成一个基本操作。下一页返回4.1汇编语言基本知识用机器语言书写的程序叫作机器语言程序,它是计算机唯一能够识别并直接执行的程序,而用其他语言编写的程序必须经过翻译转换成机器语言程序。虽然机器语言程序可以被计算机直接执行,但它要求程序员将所有的指令和数据按照二进制字节流的形式来编写,效率极低,可读性和可移植性差。因此,这种编程方法只在计算机发展的早期没有编译程序时被使用,现在只用于介绍计算机组织与结构中有关指令设计,并不作为编程语言使用。2.汇编语言上一页下一页返回4.1汇编语言基本知识汇编语言是介于机器语言和高级语言之间的编程语言,是用指令助记符、符号地址、标号等符号书写程序的语言,它的指令语句与机器指令一一对应,所以说,它是面向机器的。用汇编语言书写的程序叫作汇编语言源程序,计算机不能直接运行汇编语言源程序,要想执行它们,必须翻译成机器指令。使用汇编语言编程能够充分利用计算机的硬件特性和操作系统底层功能,它直接利用CPU的指令系统编写程序。与高级语言相比,汇编语言占用存储空间少,执行速度快。通过学习使用汇编语言,可以加深对计算机的软硬件系统的理解,编写高效率的核心代码,掌握更多的程序设计技巧,同时,更加了解高级语言程序的工作原理与编程方法。上一页下一页返回4.1汇编语言基本知识3.高级语言简单地说,高级语言是一种类似于自然语言和数学语言的语言。虽然汇编语言和机器语言的执行效率高,但由于它们是面向机器的,可移植性差,且难以编写和阅读,因此人们发明了各种高级语言,例如Basic语言、C语言、Java语言等。用高级语言书写的程序叫作高级语言源程序,例如C语言源程序。计算机不能直接运行高级语言源程序,必须由编译程序将它们翻译成机器指令之后才能执行。用高级语言编写的程序与所解决的问题及计算过程相关,而与计算机的内部逻辑结构无关。因此,在使用高级语言编程时,人们不必陷入机器内部细节,可集中精力于解决问题本身。上一页下一页返回4.1汇编语言基本知识显然,高级语言更符合人们的思维习惯,易为人们所理解和学习,用高级语言编写的程序通用性强。4.1.2汇编语言编程环境汇编语言.asm源程序通过汇编器进行汇编,生成.obj文件后,通过链接器链接生成.exe的可执行文件。汇编和链接的过程可以通过控制台的方式进行,也可以将汇编和链接程序与集成开发环境相结合,与开发高级语言程序一样实现汇编语言程序的开发。1.MASM汇编器在汇编和链接时,也可选用不同的软件。汇编语言的各种汇编器见表4−1。上一页下一页返回4.1汇编语言基本知识使用频率较高的是微软公司的MASM。MASM的版本经过了很多次的升级,每次升级都增加了一些新的功能。MASM汇编器的命令行用法为:ml[/选项]汇编程序源文件[/link链接选项]常用的选项见表4−2。2.LINK链接器用ml.exe处理的COFF格式的.obj文件可以用link.exe链接成可执行的.pe文件。微软的link.exe有两个系列的版本,用于链接DOS程序的链接器为SegmentedExecutableLinker;可以链接Win32PE文件的链接器为IncrementalLinker。上一页下一页返回4.1汇编语言基本知识这里使用的是后面一种。LINK编译器的命令行用法为:link[选项][文件列表]命令行参数中的文件列表用来列出所有需要链接到可执行文件中的模块,可以指定多个.obj文件、.res资源文件及导入库文件。LINK选项很多,常用的选项见表4−3。3.汇编链接步骤以某源程序文件hello.asm为例,对它进行汇编链接,最后运行。(1)用MASM汇编hello.asm上一页下一页返回4.1汇编语言基本知识ml/c/coffhello.asm这里使用/c选项表示只生成.obj文件而不直接产生.exe文件;/coff选项要求MASM生成链接器所需要的COFF格式的.obj文件。(2)用LINK链接hello.objlink/subsystem:consolehello.obj/subsytem选项表示程序的运行环境,一般指定为“windows”;当编写控制台(Console)程序的时候,要指定为“console”。控制台程序是指那些仅仅显示文本字符的程序。控制台程序可以在DOS状态下执行,执行后自动转入Windows保护模式下执行。上一页下一页返回4.1汇编语言基本知识控制台程序不是DOS程序,它能够使用Windows的高级功能,是32位的程序;而DOS程序不能调用Windows的函数,是16位的程序。也可以将汇编和链接两个步骤合二为一:ml/coffhello.asm/link/subsystem:console(3)运行hello.exeC:\>helloHelloWorld!4.集成开发环境上一页下一页返回4.1汇编语言基本知识可以将ml.exe和link.exe(32位)加载至VisualStudio执行环境,实现在VisualStudio2017环境下编写汇编语言程序,详细步骤请参考本书附录。4.1.3汇编语言语句格式汇编语言程序中的语句可以分为指令、伪指令和宏指令3种。上一章介绍了80x86系列中的指令语句,每一条指令语句都要生成机器代码,各对应一种CPU操作,在程序运行时执行。伪指令语句(简称伪指令)提供汇编程序信息,它由汇编程序在汇编过程中执行,除了数据定义语句分配存储空间外,其他伪指令不生成目标码。上一页下一页返回4.1汇编语言基本知识宏指令是由用户按照宏定义格式编写的一段程序,其中可以包含指令、伪指令,甚至另一条宏指令。汇编语言语句对大小写不敏感,它由名字、助记符、参数和注释4部分组成,其格式如下:[名字]助记符<操作数>[;注释]其中带[]的内容是可选的。名字域是语句的符号地址,可以由26个大小写英文字母、0~9数字、_(下划线)、$、@、?等字符组成,数字不能出现在名字的第一个字符位置。指令的名字叫作标号,必须以冒号(:)结束。上一页下一页返回4.1汇编语言基本知识并不是每条指令前都需要标号,只有在循环入口、分支入口的指令,标号才是必需的,它提供循环或转移指令的转向地址。伪指令的名字可以是变量名、过程名、段名、符号名等。名字作为符号地址,具有3个属性,即段基址、偏移量和类型。标号的类型有NEAR型和FAR型,变量的类型有字节、字、双字、四字等。助记符域给出操作的符号表示,可以是指令助记符、伪指令助记符等。例如,加法指令的操作码助记符是ADD。操作数域为操作提供必要的信息。上一页下一页返回4.1汇编语言基本知识每条指令语句的操作数个数已由系统确定,例如,加法、减法指令有两个操作数,而标志入栈指令没有显式操作数。本章将会介绍各种伪指令的操作数。注释域用以说明本条语句在程序中的功能,要简单明了。注释以分号(;)开始。上一页返回4.2常用伪指令4.2.1数据定义伪指令数据定义,伪指令用于定义程序中使用的数据。格式:[变量名]助记符操作数功能:为变量分配单元,并为其初始化或者只预留空间。说明:①变量名是可选的,需要时由用户自己起。它是该数据区的符号地址,也是其中第一个数据项的偏移量。程序通过变量名引用其中的数据。②助记符是数据类型的符号表示,不区分大小写,有表4−4所示的各种数据类型。下一页返回4.2常用伪指令③操作数可以是数字常量、数值表达式、字符串常量、地址表达式、?、<n>DUP(操作数,…)形式。●数字常量及数值表达式。可以有十进制、二进制、十六进制、八进制数字常量,常用前3种格式。操作数也可以是数值表达式,可使用的操作符见4.2.3节。在数字中若出现字母形式,则不区分大小写。常用数据格式如下。十进制数:以D结尾,汇编语言中默认值是十进制数,所以D可以省略不写。有效数字是0~9。二进制数:以B结尾,有效数字是0、1。十六进制数:以H结尾,有效数字是0~9和A~F。若第一位数字是字母形式,则必须在前边加上0。上一页下一页返回4.2常用伪指令八进制数:以Q或O结尾,有效数字是0~7。●字符串常量。在汇编语言中,字符需要用单引号括起来,其值为字符的ASCII码。因为每个字符占用一个字节,所以最好用DB助记符定义字符串。●地址表达式。操作数可以是地址符号。若只定义符号的16位偏移量,则使用DW助记符;若要定义它的双字长地址指针(既含16位偏移量,又含段基址),则使用DD助记符,其中低字中存放偏移量,高字中存放段基址;若要定义它的48位全地址指针(既含32位偏移量,又含段选择符),则使用DF助记符,其中低32位存放偏移量,高16位存放段选择符。上一页下一页返回4.2常用伪指令●?。在程序中使用“?”为变量预留空间而不赋初值。●<n>DUP(操作数,…)。若要对某些数据重复多次,可以使用这种格式。其功能是把()中的内容复制n次。DUP可以嵌套。4.2.2符号定义伪指令1.等值伪指令程序中有时会多次出现同一个表达式,为方便起见,可以用符号定义伪指令给该表达式定义一个符号,以便于引用及减少程序修改量,并提高程序的可读性。汇编后,该符号代表一个确定的值,该符号定义的伪指令称为等值伪指令EQU。上一页下一页返回4.2常用伪指令格式:符号名EQU表达式功能:用符号名代表表达式或表达式的值。说明:表达式可以是任何有效的操作数格式。它可以是常数、数值表达式、另一符号名或助记符。注意,用EQU定义的符号在同一个程序中不能再定义。2.等号伪指令格式:符号名=数值表达式功能:用符号名代替数值表达式的值。上一页下一页返回4.2常用伪指令说明:等号伪指令“=”与EQU伪指令功能相似,其区别是等号伪指令的表达式只能是常数或数值表达式,另外,已被定义的符号在同一个程序中可以再被定义。通常在程序中用“=”定义常数。4.2.3操作符伪指令1.$操作符功能:$在程序中表示当前地址计数器的值。程序中的每一行都有一个地址,从程序的第一行到最后一行,地址计数器在不断地增加。如果一行语句占用了存储空间,如定义变量或者是指令,则地址计数器就会增加,增加的字节数就是变量或指令所占的字节数。程序中一般不直接使用$的值,而是使用它来计算变量占用的空间。上一页下一页返回4.2常用伪指令2.ORG操作符格式:ORG数值表达式功能:设置地址计数器内容为数值表达式的值。说明:在汇编程序对源程序汇编的过程中,使用地址计数器保存当前正在汇编的语句地址(段内偏移量),汇编语言允许用户直接用“$”引用地址计数器的当前值。3.OFFSET操作符格式:OFFSET[变量|标号]功能:OFFSET操作符用来取出变量或标号的地址(在段中的偏移量)。在32位编程环境中,地址是一个32位的数。上一页下一页返回4.2常用伪指令4.算术操作符算术操作符包括+(加)、−(减)、*(乘)、/(除)和MOD(模)操作符。其中/表示整除,即只取商的整数部分;而MOD则表示只取余数。算术操作符的结果是常量,计算过程是在程序被编译时(而不是在程序运行时)完成的。5.逻辑操作符逻辑操作符包括AND(逻辑与)、OR(逻辑或)、XOR(逻辑异或)和NOT(逻辑非)。逻辑操作符是按位操作的,它只能用在数值表达式中。上一页下一页返回4.2常用伪指令同算术操作符一样,表达式中的逻辑操作是在程序被编译时完成的。6.关系操作符关系操作符包括EQ(等于)、NE(不等于)、LT(小于)、LE(小于等于)、GT(大于)、GE(大于等于)。这些操作符对其前后的两个操作数进行比较,其操作结果为一个逻辑值,若关系成立,结果为真(全部二进制位为1),否则结果为假(0)。其中的操作数必须是常数或常数表达式。同算术操作符一样,表达式中的关系操作是在程序被编译时完成的。上一页下一页返回4.2常用伪指令4.2.4框架定义伪指令本节介绍的伪指令以“.”开始,一般用在32位汇编语言程序框架中。1.微处理器伪指令使用微处理器伪指令说明本程序使用哪一种CPU的指令集。汇编程序在默认情况下只接受8086的指令系统,即使在Pentium上也是如此,因此,在8086上编写的程序在较新的处理器上都可以顺利执行。为了能够使用其他微处理器或协处理器的指令系统编写软件,需要在程序中增加选择微处理器的伪指令。较常使用的选择微处理器伪指令见表4−5。上一页下一页返回4.2常用伪指令一般的32位汇编语言程序中使用80386的指令集就可以了,所以在程序的开头写上.386。如果要使用属于80486或Pentium的指令集,就应该写上.486或.586。Pentium的指令集包括了80486的指令集,而80486的指令集又包括了80386的指令集。如果用汇编语言编写的是驱动程序或者驱动程序的一个小模块,因为驱动程序是在特权级0上运行的,因此就需要使用.386p。因为.486及其以上的微处理器内置有完整的协处理器的寄存器,使用.486或.586伪指令就可以使微处理器及它的协处理器指令被汇编,因此不再提供类似于.487这样的伪指令。上一页下一页返回4.2常用伪指令从PentiumMMX开始增加了MMX指令集,因此,若在程序中使用MMX指令,则应在.586之后跟一个.mmx伪指令。2.框架定义伪指令在较高版本程序框架中,除了使用上节介绍的微处理器伪指令之外,还经常使用表4−6中的伪指令。在Windows中,实际上只有代码区和数据区之分,.DATA、.DATA?、.CONST、.STACK都视为数据区。用.DATA、.DATA?、.CONST定义的都是数据段,分别对应不同方式的数据定义,在最后生成的可执行文件中,分别放在不同的节区(Section)。通常,可以用.CODE定义代码区,用.DATA定义数据区。上一页下一页返回4.2常用伪指令堆栈空间一般是系统自动分配的,用户程序不必考虑,但若确需指定堆栈空间时,则应用.STACK定义。程序内存模式的定义影响最后生成的可执行文件,可执行文件的规模可以有很多种类型,在DOS的可执行程序中,有大小限制在64KB以下的.com文件,也有可以超过64KB的.exe文件。到了Windows环境下,.pe格式的可执行文件最大可以用4GB内存。编写不同类型的可执行文件,要用.MODEL语句定义不同的参数。上一页返回4.3汇编源程序格式4.3.1用户界面用户界面(UserInterface)提供了用户与计算机进行交互的操作方式,即用户与计算机互相传递信息,其中包括信息的输入和输出,早期称为人机界面。用户界面存在于用户与硬件之间,更主要体现在用户与软件之间,因为用户更经常与软件打交道。自计算机诞生以来,最先广泛应用的是字符用户界面(CharacteralUserInterface,CUI)。当时计算机应用还不普及,技术人员主要致力于计算机性能的改进,因此对用户界面考虑不多。CUI要求用户逐个输入字符来执行命令,操作繁杂,命令多,并且不易记忆,使用困难;执行结果也是通过字符显示的,单调且难以读懂。下一页返回4.3汇编源程序格式为了普及计算机的应用,需要提高它的易用性,技术人员开发出了目前广泛使用的图形用户界面(GraphicUserInterface,GUI)。GUI最大的贡献是创造出“桌面”和“图标”。通过“桌面”显示计算机所有资源,以“图标”表示其中的每一个具体对象,清晰易懂,加上鼠标的配合使用,极大地简化了一般操作。Windows操作系统是GUI的典型代表。然而,GUI的发展并不能完全代替CUI,这是因为CUI界面的程序有占用空间少且运行速度快的优点。即使在Windows操作系统中绝大多数的程序已经采用了GUI界面,仍然有相当多的程序还在使用CUI,如Windows系统所带的ping、format、net、telnet、ipconfig等工具程序。所以,这二者是和平共处的关系,只是应用范围不同。上一页下一页返回4.3汇编源程序格式1.GUI程序GUI程序通常都创建一个窗口,使用鼠标及键盘输入,可使用丰富的Windows控件,如按钮、列表框、编辑框、菜单等。程序是通过调用Windows系统提供的种类丰富、功能强大的API来完成这些界面操作的。GUI程序一般需要和user32.lib链接,有些程序还需要和gdi32.lib链接。在生成.EXE文件时,程序的运行环境要设置为Windows。2.CUI程序上一页下一页返回4.3汇编源程序格式Windows环境下的CUI程序通常在控制台下运行(注意,不是在DOS中运行,DOS环境下只能运行16位程序),主要使用键盘输入。在生成CUI结构下的.exe文件时,程序的运行环境要设置为Console。4.3.2控制台界面的汇编源程序C程序中字符串自动由编译器加上一个0字符作为字符串的结束,而汇编程序中必须在字符串后面明确地定义一个值为0的字节。删除源程序hello.asm中的注释行后,程序的结构如图4−2所示。可以看出这是一个十分简单的程序,其中许多语句是程序框架中所必需的,本节将逐一介绍。上一页下一页返回4.3汇编源程序格式1.模式定义程序的第一部分是有关模式定义的3条语句:.386.modelflat,stdcalloptioncasemap:none①.386语句,定义了程序使用80386指令集。②.modelflat,stdcall语句。此处指定内存模式为flat模式。在DOS下的汇编语言程序中,常常有这样的程序片段:上一页下一页返回4.3汇编源程序格式MOVAX,DATAMOVDS,AX其作用是给数据段寄存器DS赋值,指向程序自己的名为DATA的数据段。每一个程序都有它自己的数据段。在DOS下编程时,必须考虑DS、ES、SS等段寄存器是否正确设置。③option语句。option语句有许多选项,例如optionlanguage、optionsegment等,在Win32中需要定义optioncasemap:none,用以说明程序中的变量和子程序名是否对大小写敏感。上一页下一页返回4.3汇编源程序格式在对大小写敏感情况下,XYZ和xyz是两个不同的变量;在对大小写不敏感情况下,则不区分大写、小写形式,XYZ和xyz表示同一个变量。由于WindowsAPI中的函数名称是区分大小写的,所以应该指定选项“casemap:none”,否则,在调用函数的时候会出现问题。2.includelib语句和C程序一样,在汇编程序中也需要调用一些外部模块(子程序/函数)来完成部分功能。printf函数属于C语言的库函数。它的执行代码放在一个动态连接库DLL中,这个动态库的名字叫msvcrt.dll,ms代表Microsoft,vc代表VisualC/C++,rt代表runtime。上一页下一页返回4.3汇编源程序格式msvcrt.dll是Windows自带的,很多程序都需要使用这个DLL。由于汇编程序中调用了DLL文件中的函数,在连接时,LINK就必须知道这个库函数属于哪一个DLL;否则,LINK就会报告以下出错信息,因为它不知道在哪里能找到printf函数。为了指出库函数的位置,VisualC/C++提供了一个和msvcrt.dll相配套的库文件,叫msvcrt.lib。这个文件列出了msvcrt.dll中包括的所有库函数的名称,以及这个库函数的序号。连接程序读取库文件msvcrt.lib,就知道msvcrt.dll中存在printf函数。在汇编源程序中,用includelib语句指出库文件的名称,连接时,LINK就从库文件中找出了函数的位置,避免出现上面的错误提示。这种库文件也叫导入库(ImportLibrary)。上一页下一页返回4.3汇编源程序格式一个DLL文件对应一个导入库,如msvcrt.dll的导入库是msvcrt.lib、kernel32.dll的导入库是kernel32.lib、user32.dll的导入库是user32.lib等。导入库文件在VisualC/C++的库文件目录中,在连接生成可执行文件时使用。可执行文件执行时,只需要DLL文件,不需要导入库。DLL文件一般放在Windows的system32目录中。Windows自带很多DLL,包括msvcrt.dll、kernel32.dll、user32.dll等。这种连接方式是动态连接,printf函数的代码并没有包括到可执行文件hello.exe中。在可执行文件中保存的是DLL文件的名称(msvcrt.dll)和该函数的编号(741),也就是函数的定位信息。上一页下一页返回4.3汇编源程序格式在运行可执行文件时,Windows系统找到printf函数的地址,hello.exe随后调用这个函数。还有一种方式是静态连接,在连接时,将库函数的执行代码复制到可执行文件中,因此采用这种连接方式的可执行文件的体积较大。DOS不支持动态连接,只能使用静态连接。这种库文件叫静态库,它是一组已经编写好的代码模块,在程序中可以调用。在源程序编译成目标文件,最后要连接成可执行文件的时候,由LINK程序从库中找出相应的函数代码,存放到最后的可执行文件中。Windows中也可以指定使用静态连接。上一页下一页返回4.3汇编源程序格式如图4−3所示,静态连接的缺点是每个可执行文件中都包含了要用到的相同函数的代码,占用了大量的磁盘空间,在内存中执行的时候,这些代码同样重复占用了宝贵的内存。而动态连接就不会浪费磁盘空间和内存空间。3.函数声明语句对于所有要用到的库函数,在程序的开始部分必须预先声明。包括函数的名称、参数的类型等,如:在汇编语言源程序中,函数声明为:函数名称PROTO[调用规则]:[第一个参数类型][,:后续参数类型]上一页下一页返回4.3汇编源程序格式printf的函数声明:_CRTIMPint__cdeclprintf(constchar*,...);可知printf函数的调用规则为C调用规则(__cdecl,即cdeclare),第一个参数是字符串指针,后面的参数数量及类型不定。如果函数使用C调用规则,则PROTO后跟一个C。接下来是参数的说明。如果参数个数、类型不定,则用VARARG说明(varibleargument)。4.include语句上一页下一页返回4.3汇编源程序格式对于所有要用到的库函数及WindowsAPI函数,在程序的开始部分都必须预先声明,这显然比较麻烦。在汇编语言编程中,也可以采用C语言办法,就是把所有的函数声明及常量定义等公用部分预先放在一个头文件中。一些汇编语言工具包,如MASM32等,提供了这样一些头文件。需要使用这些函数声明或常量定义的时候,再用include语句将其包含进来即可。include语句格式:include文件名5.数据和代码部分程序中的数据部分从.data语句开始定义,代码部分从.code语句开始定义,所有的指令都必须写在代码区中。上一页下一页返回4.3汇编源程序格式对于运行在特权级3的应用程序来说,代码区是不可写的。在编程时,不能把那些需要修改的变量放到.code部分。6.程序结束与DOS程序相同,Win32程序在遇到end语句时结束。end语句后面跟的标号指出了程序执行的入口点,即装入执行的第一条指令的位置,表示源程序结束。include语句格式:END[过程名]过程名指示程序执行的起始地址。[]中的过程名是可选的。只有主过程模块的END后可以带过程名,并且这个过程名必须是主程序的名字。上一页下一页返回4.3汇编源程序格式若一个程序由多个模块组成,则除主模块外,其他模块的END语句不能带过程名。7.跨行语句当源程序的某一语句过长,不利于书写和阅读时,可以用反斜杠“\”做换行符,将这条语句分为几行来写。每一行的最后加上一个反斜杠,说明下面一行是当前行的继续。语句的最后一行不要加反斜杠。反斜杠后面还可以加空格和注释.8.程序中的数据归类上一页下一页返回4.3汇编源程序格式用.data、.data?、.const定义的都是数据段,分别对应不同方式的数据定义,程序中的数据定义一般可以归纳为如下3类。(1)可读可写的已定义初始变量这些数据在源程序中给出初始值,在程序的执行中可能被更改。这些数据必须定义在.data区中。在程序装入内存准备执行时,这些变量就已经具有初始值了。这些变量的初始值是保存在可执行文件中的,在程序装入时,初始值从文件装入内存。因此,在程序执行以前,内存中的变量就具有在源程序中指定的初始值。(2)可读可写的未定义初始变量上一页下一页返回4.3汇编源程序格式这些变量一般是当作缓冲区或者在程序执行后才开始使用的,在程序中不需要给它们指定初值。这些数据可以定义在.data节中,也可以定义在.data?节中。(3)常量数据如一些要显示的字符串信息,它们在程序装入的时候已具有初值,在整个程序执行过程中不需要修改,这些数据最好放在.const部分中。在这个部分的变量,只能读,不能写。在程序中出现了对.const部分的变量做写操作的指令,会引起异常,操作系统会报告并结束程序。这有助于找出程序中的编程错误。9.invoke伪指令上一页下一页返回4.3汇编源程序格式格式:invoke函数名[,参数1][,参数2]…功能:调用函数或子程序。与在DOS中使用中断调用方式调用系统功能一样,在Windows中用API方式调用存放在DLL中的函数。由于API函数的参数较多,为了简化工作,可以使用invoke伪指令。注意:invoke不是指令,而是伪指令!是MASM为了方便调用而提供的,是一种主程序调用子程序的简化方法。在汇编时,会把它按照调用规则展开成相应的指令,通常展开成几条PUSH指令(有几个参数就展开几条)和一条CALL指令。对于像C那样要求调用程序清除堆栈中的参数者,最后还会展开一条addesp,n指令。上一页下一页返回4.3汇编源程序格式在子程序调用不带参数的情况下,它等价于CALL指令。4.3.3Windows界面的汇编源程序上一小节的程序hello.asm使用了控制台界面,本小节给出Windows界面的汇编程序。例4.17编写汇编程序,使用Windows风格的界面,在Windows消息框中显示一个字符串“HelloWorld!”。上一页下一页返回4.3汇编源程序格式上一页下一页返回4.3汇编源程序格式程序中使用的MessageBox属于user32.dll,是Windows的一个API函数。其功能是显示一个消息框。它的第1个参数是一个窗口句柄,即消息框的父窗口。这里使用NULL表示它没有父窗口。第2个参数是一个字符串指针,指向在消息框中显示的正文。第3个参数也是一个字符串指针,指向在消息框的窗口标题。第4个参数是一个整数,指定消息框的类型,这里使用MB_OK,消息框中显示一个“OK”(“确定”)按钮。Windows中用于显示消息框的API函数有MessageBoxA和MessageBoxW。MessageBoxA接收的参数是ASCII字符串形式的,也是例4.17程序中使用的形式。上一页下一页返回4.3汇编源程序格式MessageBoxW接收的参数是Unicode字符串形式的,每一个字符要占两个字节。按照下节介绍的汇编、连接过程,输入以下命令生成可执行文件hellow.exe:ml/coffhellow.asm/link/subsystem:windows注意,在subsystem选项中必须指定windows,而不是console。运行这个程序的结果如图4−4所示。4.3.4输入/输出有关的WindowsAPI函数上一页下一页返回4.3汇编源程序格式API可以看作特殊的子程序,在源程序中只需要声明这个子程序的类型,说明这个子程序所在的库文件,就可以调用子程序了。API提供的功能是在其他模块(大都是用DLL的形式)中实现的,不像通常意义的子程序那样,需要将子程序要实现的功能用汇编指令来实现。本节仅介绍几个与数据输入/输出有关的WindowsAPI函数,其他众多的函数可查阅MSDN、MicrosoftWin32Programmer’sReference等,这些文件中定义了常用API的函数声明和参数类型,以及这些函数所需要的库文件。对于所有要用到的库函数(或WindowsAPI函数),在程序的开始部分必须预先声明。上一页下一页返回4.3汇编源程序格式包括函数名称、参数类型等。在汇编语言源程序中,函数声明为:函数名称PROTO[调用规则]:[第一个参数类型][,:后续参数类型]其中,调用规则是可选项,可以是stdcall,也可以是C等。缺省时,使用model语句中指定的调用规则。如果函数使用C调用规则,则PROTO后跟一个C。接下来是参数的说明。如果参数个数、类型不定,则用VARARG说明(variableargument)。使用C规则调用子程序时,堆栈平衡由调用者(主程序)完成,在CALL指令执行后,用ADDESP,n指令把n个字节的参数空间清除,以保证堆栈平衡。上一页下一页返回4.3汇编源程序格式以下介绍的输入/输出函数中,printf和scanf适用于控制台程序(连接选项为/subsystem:console);MessageBox适用于Windows风格的窗口界面程序(连接选项为/subsystem:windows)。1.printfprintf是一个实现输出的API函数。在程序调用时,应指明printf的调用规则,以及它的参数类型。在C语言头文件stdio.h中,printf的函数声明为:_CRTIMPint__cdeclprintf(constchar*,...);可知printf函数的调用规则为C调用规则(__cdecl,即cdeclare),第一个参数是字符串指针,后面的参数数量及类型不定。上一页下一页返回4.3汇编源程序格式在汇编语言中,用ptrsbyte代表constchar*。2.scanfscanf函数实现从控制台读取用户的输入,将输入读入整数、字符、字符串等变量中。scanf的连接信息也包括在msvcrt.lib库文件中。scanf的调用规则和参数类型说明为:scanfPROTOC:dword,:vararg第1个参数是格式字符串的地址,后面的参数个数可变,可以一个没有,也可以跟多个参数。3.MessageBoxA上一页下一页返回4.3汇编源程序格式API函数MessageBoxA适用于Windows风格的窗口界面程序(连接选项为/subsystem:windows),实现在窗口中显示信息。MessageBoxA的C语言原型在VC附带的“winuser.h”中,其函数声明为:#defineWINAPI__stdcallintWINAPIMessageBoxA(HWNDhWnd,//窗口句柄上一页下一页返回4.3汇编源程序格式LPCSTRlpText,//消息框正文的指针LPCSTRlpCaption,//消息框窗口标题的指针UINTuType);//消息框类型它的调用规则和参数类型说明为:MessageBoxAPROTO:DWORD,:DWORD,:DWORD,:DWORD4.函数声明语句和库文件在编程中,应尽可能地利用已有的C库函数和WindowsAPI函数,一般都可以转换为API函数来实现。通常,C库函数使用C调用规则,WindowsAPI采用stdcall调用规则。上一页下一页返回4.3汇编源程序格式在MSDN或VC中可以查到函数的名称、参数的个数和类型,然后再确定调用规则,根据这些信息,就可以写出这样的声明语句:printfPROTOC:dword,:vararg接下来再确定它的库文件,常用的库文件有msvcrt.lib、kernel32.lib、user32.lib等。通过查阅资料和帮助文件、阅读示例程序等方法,弄清楚函数的功能及入口/出口参数,乃至每一个参数的用法。函数可能返回的是一个整数或者是一个指针。无论是什么,返回值都在EAX中。上一页下一页返回4.3汇编源程序格式要注意有些函数是通过传递地址指针的方式来修改参数的值的,如scanf。MASM32软件包会自动地生成所需要的API函数声明,以.inc的形式提供。在使用时只需要包含这些.inc文件即可。上一页返回4.4分支与循环程序设计汇编语言程序设计采用结构化程序设计的方法。按照这种方法设计出的程序,其每一部分由若干个单元组成,而每个单元包含一个有限结构集,每个结构有一个入口和一个出口。这种结构便于查错及调试。结构程序有3种基本结构,它们是顺序结构、分支结构和循环结构,相应的程序叫作简单程序、分支程序和循环程序,本节主要介绍分支与循环程序设计。4.4.1分支程序设计许多实际问题很复杂,有时需要根据不同条件进行不同的处理,用简单程序无法完成这样的功能,这时就需要用分支程序设计。本节介绍分支结构及其程序实现。下一页返回4.4分支与循环程序设计1.IF_THEN_ELSE结构分支程序设计用汇编语言程序实现IF_THEN_ELSE分支结构,如图4−5所示。图4−5(a)为IF结构,图4−5(b)为IF_THEN_ELSE结构,这两种结构的程序都要使用条件转移指令,判断条件是否满足,以此来决定程序的执行流程。对于具有图4−5(a)结构的程序,当条件满足时,跳过指令序列实现转移;条件不满足时,继续向下执行指令序列1。对于具有图4−5(b)结构的程序,当条件满足时,转去执行指令序列2;条件不满足时,继续向下执行指令序列1。无论执行哪个分支,最终都会到同一个出口。上一页下一页返回4.4分支与循环程序设计这和C语言的IF语句不同,C语言中,IF语句在条件满足时才执行分支程序,而条件转移指令则是在条件满足时转移了。这一点要特别注意。2.SWITCH_CASE结构分支程序设计基于条件跳转指令的汇编语句相当于C程序中的IF_THEN_ELSE语句,如果程序中的分支较多,使用条件转移的程序结构会变得比较复杂,不易读懂、扩充。在C程序中,多分支情况下使用SWITCH_CASE语句比IF语句要简洁、高效。同样,在汇编程序中,可以采用基于跳转表的分支结构。上一页下一页返回4.4分支与循环程序设计4.4.2循环程序设计在日常生活中经常会遇到某件工作需要重复做的情况,在计算机中对这种情况的处理可以用循环程序实现。循环程序是具有循环结构的程序。循环有两种基本结构,它们是DO_WHILE和DO_UNTIL结构,如图4−9所示。DO_WHILE结构是先判断后执行的结构,它把对循环控制条件的判断放在循环的入口,先判断控制条件,若满足控制条件(例如循环次数不为0),就执行循环体,否则,退出循环。DO_UNTIL结构则是先执行后判断的结构,它先执行循环体,然后再判断控制条件,若满足控制条件,则继续执行循环体,否则,退出循环。上一页下一页返回4.4分支与循环程序设计这两种结构一般可以随习惯使用,但在初始循环次数可能为0的情况下,则必须使用DO_WHILE结构。无论使用哪种循环结构,循环程序一般应包括以下几个部分。①循环初始化。它包括设置循环次数的初始值、地址指针的初始设置等。②循环体。这是循环工作的主体,包括要重复执行的操作,以及循环的修改部分。修改部分包括地址指针的修改、循环控制条件的修改等。③循环控制部分。它是控制循环的关键,判断循环条件满足与否。例如判断循环次数是否为0等。上一页下一页返回4.4分支与循环程序设计循环次数有时是已知的,有时是不确定的,此时需要根据某种条件判断是否循环。对于确切的循环次数已送入CX或ECX寄存器且通过LOOP指令控制循环的情况,其循环次数的修改及控制条件的判断都由LOOP指令完成。在设计循环程序时,特别要注意循环入口和循环次数的正确设置、地址指针及循环控制条件的修改等,否则,会得不到期望的结果。1.单重循环程序设计某些循环的执行次数预先并不能完全确定,而是根据执行的情况来决定是否继续循环或退出循环。这时,就不再适合用LOOP指令来构造循环了,而应该利用条件转移指令来构造和控制循环。上一页下一页返回4.4分支与循环程序设计2.多重循环程序设计有些比较复杂的问题使用一重循环可能无法解决,此时就需要设计多重循环程序。在多重循环的程序中,内层循环嵌套于外层循环中,循环的嵌套层次没有限制。各层循环都有各自的循环次数、循环体、循环结束条件,相互之间不能干扰、交叉。上一页返回4.5浮点运算x86指令系统除了第3章所介绍的常规通用指令外,还包括用于数值计算的浮点运算指令,包括浮点数的传送、浮点算术运算、浮点比较与控制等。Intel80x87是与Intel80x86处理器相配合使用的浮点处理器,80486及以后的IA−32处理器中已经集成了浮点处理单元,统称为x87FPU。4.5.1浮点数的表示与存储x86处理器所使用的三种浮点数二进制存储格式单精度、双精度和扩展精度都是由IEEE标准754—1985所规定的,分别适用于不同的计算要求。一般而言,单精度适合一般计算,双精度适合科学计算,扩展双精度适合高精度计算。表4−7列出了其格式及说明。下一页返回4.5浮点运算需要说明的是,若不对浮点数的表示做出明确规定,同一个浮点数的表示就不是唯一的。为了提高数据的表示精度,当尾数的值不为0时,尾数域的最高有效位应为1,这称为浮点数的规格化表示。否则,以修改阶码同时左右移小数点位置的办法,使其变为规格化数的形式。因此,规格化浮点数的尾数域最左位(最高有效位)总是1,故这一位经常不予存储,而认为隐藏在小数点的左边。在表4−7的浮点数格式中,扩展双精度类型没有隐含位,因此它的有效位数与尾数位数一致,而单精度类型和双精度类型均有一个隐含整数位,因此它的有效位数比位数多一个。上一页下一页返回4.5浮点运算4.5.2浮点寄存器FPU不使用通用寄存器(EAX、EBX等),FPU在执行浮点运算时有自己的一组寄存器,称为寄存器栈(registerstack)。数值从内存加载到寄存器栈,然后执行计算,再将寄存器栈中的数据保存到内存。寄存器栈中有8个独立寻址的80位寄存器,名称分别为FPR0,FPR1,…,FPR7,它们以堆栈形式组织在一起,在指令中,栈顶也写作st(0),最后一个寄存器写作st(7)。FPU另有3个16位的寄存器,分别为控制寄存器、状态寄存器、标志寄存器。上一页下一页返回4.5浮点运算1.浮点寄存器栈浮点寄存器栈由8个可以直接进行浮点运算的寄存器组成,CPU在处理浮点运算的时候,将这些寄存器作为一个栈来使用,按照顺序编号为0~7,分别命名为ST(0)~ST(7),它是一个向下扩展的循环栈,栈顶指针指向ST(0),如图4−13所示。浮点寄存器栈的工作原理如下:①当程序需要向寄存器栈中装入数据的时候,栈顶指针的值减1,然后将数据压入栈顶指针指向的浮点寄存器中。上一页下一页返回4.5浮点运算②浮点寄存器栈是一个循环栈,当栈顶指针指向的地址值为0的时候,下一次入栈操作(FLD指令)则将数据压入地址值为7的浮点寄存器中,栈顶指针地址值为7。③当需要将堆栈中的数据保存到内存中的时候(FST指令),则进行出栈操作。出栈操作与入栈顺序相反,栈顶指针加1,若出栈前栈顶地址值为7,则出栈后栈顶值为0。④在用户通过指令对浮点数据操作的时候,这些浮点寄存器所使用的名称分别为ST(0)、ST(1)、ST(2)、ST(3)、ST(4)、ST(5)、ST(6)、ST(7),这里的ST(i)中的i不是寄存器的地址,而是距离栈顶的长度。上一页下一页返回4.5浮点运算也就是说,ST(i)表示距离栈顶的第i个单元的寄存器。按照图4−13所示的状态,ST(0)为栈顶元素,为第5号寄存器,ST(1)为第6号寄存器,依此类推。2.标志寄存器为了表示浮点数据寄存器中数据的性质,对应每个FPR寄存器,都有一个两位的标志(Tag)位,这8个标志tag0~tag7组成一个16位的标志寄存器。标志寄存器记录了每个浮点寄存器的状态,当装入数据时,硬件会将寄存器中相应的tag置为有效,反之,置为空。当入栈时,遇到已经标为有效的寄存器时,产生上溢;如果出栈时,遇到相应tag为空时,则产生下溢,处理器会触发相应的异常进行处理。上一页下一页返回4.5浮点运算标志寄存器如图4−15所示。3.状态寄存器状态寄存器16位,表明浮点处理单元当前的各种操作状态,每条浮点指令运算后,都会更新状态寄存器,以反映执行结果情况,与整数处理单元的EFLAGS作用功能类似,如图4−16所示。(1)堆栈标志①TOP(bit11~bit13):表明浮点数据寄存器中的栈顶位置,即ST(0)所在的FPR地址,三位编码(000~111)指向8个数据寄存器栈中的某一个寄存器。上一页下一页返回4.5浮点运算②SF(bit6):表明堆栈是否发生溢出,为1时,表示发生溢出错误。③C1(bit9):表明溢出情况,C1=1为上溢,C1=0为下溢。④C3(bit14)、C2(bit10)、C0(bit8):保存浮点比较指令的比较结果。(2)异常标志状态寄存器的低6位反映了浮点运算可能出现的6种异常。①PE(bit5,PrecisionException,精度异常):为1表示结果或者操作数超出指定的精度范围,出现不准确的结果。上一页下一页返回4.5浮点运算②UE(bit4,UnderflowException,下溢异常):为1表示非0的结果太小,出现下溢。③OE(bit3,OverflowException,上溢异常):为1表示结果过大,出现上溢。④ZE(bit2,ZerodivideException,除数为0异常):为1表示除数为0错误。⑤DE(bit1,DenormalizedoperandException,非规格化操作数异常):为1表示至少有一个非规格化操作数。⑥IE(bit0,InvalidoperationException,非法操作异常):为1表示操作非法。上一页下一页返回4.5浮点运算(3)其他标志①ES(bit7,ErrorSummary,错误总结):当系统中任何一个未被屏蔽的异常发生时,该位置1,表明系统中是否出现异常。②B(bit15,FPUBusy,浮点处理单元忙):表示浮点处理单元的工作状态,为0表示空闲,为1表示正在执行浮点指令。4.控制寄存器16位浮点控制寄存器用于控制浮点处理单元的异常屏蔽、精度及舍入操作,如图4−17所示。(1)异常屏蔽控制(ExceptionMaskControl)上一页下一页返回4.5浮点运算①PM(bit5,PrecisionMask,精度异常屏蔽):为1表示屏蔽精度异常。②UM(bit4,UnderflowMask,下溢异常屏蔽):为1表示屏蔽下溢异常。③OM(bit3,OverflowMask,上溢异常屏蔽):为1表示屏蔽上溢异常。④ZM(bit2,ZerodivideMask,除数为0异常屏蔽):为1表示屏蔽除数为0异常。⑤DM(bit1,DenormalizedoperandMask,非规格化操作数异常屏蔽):为1表示屏蔽非规格化操作数异常。上一页下一页返回4.5浮点运算⑥IM(bit0,InvalidoperationMask,非法操作异常屏蔽):为1表示屏蔽非法操作异常。(2)精度控制PC(bit8~bit9,PrecisionControl,精度控制):用于控制浮点计算结果的精度。PC=00时,32位单精度浮点数;PC=01时,保留;PC=10时,64位双精度浮点数;PC=11时,80位扩展精度浮点数。(3)舍入控制RC(bit10~bit11,RoundingControl,舍入控制):浮点处理单元无法产生要求的精确值时,需要进行舍入操作,以使得结果接近于精确值。见表4−8。上一页下一页返回4.5浮点运算(4)无限大控制IC(bit12,InfinityControl,无限大控制):用于兼容其他协处理器。4.5.3浮点指令及其编程1.浮点指令概述浮点处理单元具有自己的指令系统,指令助记符均以F开头。浮点指令系统主要包括以下几类指令类型:①浮点数据传送指令:完成内存与栈顶st(0)、数据寄存器st(i)与栈顶之间的浮点格式数据的传送。②常数加载指令:实现将特定常数加载到堆栈。上一页下一页返回4.5浮点运算③浮点算术运算指令:算术运算指令实现浮点数的加、减、乘、除运算,它们支持的寻址方式相同。这组指令还包括有关算术运算的指令,例如求绝对值、取整等。④浮点超越函数指令:超越函数指令实现三角函数、指数和对数运算的操作。⑤浮点比较指令:比较栈顶数据与指定的源操作数,比较结果通过浮点状态寄存器反映。⑥FPU控制指令:用于控制和检测浮点处理单元FPU的状态及操作方式。2.数据定义上一页下一页返回4.5浮点运算数据定义伪指令dd(dword)/dq(qword)/dt(tbyte),依次定义32/64/80位数据,它们可以用于定义单精度、双精度和扩展精度浮点数。为了区别于整数定义,MASM6.11建议采用REAL4、REAL8、REAL10定义单、双、扩展精度浮点数,但不能出现纯整数形式(整数后面补小数点即可)。相应的数据属性依次是dword、qword、tbyte。另外,实常数可以用E/e表示10的幂。3.寻址方式浮点指令一般需要1个或者2个操作数,数据存储于浮点寄存器或主存中,主要包括如下两种寻址方式:上一页下一页返回4.5浮点运算①寄存器寻址:操作数保存在指定的数据寄存器栈中,用ST(i)表示。②存储器寻址:操作数在内存中,内存中的数据可以采用与数据有关的存储器寻址方式访问。4.浮点指令(1)数据传送指令实现浮点数据在寄存器栈及存储器之间的传送,见表4−9。(2)常数加载指令该类指令将某些常数加载到寄存器栈中,见表4−10。(3)算术运算指令上一页下一页返回4.5浮点运算该类指令实现基本的算术运算,见表4−11。(4)浮点比较指令浮点数的比较不同于整数,可以使用CMP指令比较,通过判断EFLAGS的值,得到其大小关系。对于浮点数比较来说,FPU提供了独立的比较机制和指令,采用浮点数独有的比较指令,比较结果通过状态寄存器中的C0、C2和C3标志位给出。由于所有的浮点数都是带符号数,因此浮点比较指令是执行的带符号数比较,见表4−12。FPU状态寄存器中的C0、C2和C3标志位不同组合对应了比较的结果,可以根据比较结果选择相应的指令实现程序的转移,见表4−13。上一页下一页返回4.5浮点运算(5)超越函数指令浮点超越函数指令可对实数求三角函数、指数和对数等运算,见表4−14。(6)FPU控制指令该类指令实现针对FPU的操作控制,见表4−15。上一页返回4.6程序优化评价一个程序的优劣,程序的执行效率是一个重要因素。尤其是在以执行效率见长的汇编编程中,效率的高低尤为重要。执行效率分为两个方面:程序在多长的时间内能够完成;程序需要多大的存储空间。也就是程序的运行时间和占用空间。4.6.1运行时间优化完成同样一个功能,可以选择不同的指令来完成。它们的执行效率是有所区别的,在对程序进行优化时,应该选取那些占用空间少、执行速度快的指令。1.选择执行速度快的指令下一页返回4.6程序优化(1)寄存器清零将寄存器清零,有以下几种指令:MOVEAX,0SUBEAX,EAXXOREAX,EAX从执行速度看,SUB、XOR指令执行速度比MOV指令快,并且所需程序空间少。MOV指令需5字节,而SUB、XOR指令只需2字节。00401277B800000000MOVEAX,00040127C2BC0SUBEAX,EAX0040127E33C0XOREAX,EAX上一页下一页返回4.6程序优化所以,应该使用SUB、XOR指令。XOR指令更常用一些。(2)加减要使EBX=EAX−30,直观的做法是:MOVEBX,EAXSUBEBX,30而使用LEA指令完成同样功能的方法为:LEAEBX,[EAX−30]其执行代码为:004012258BD8MOVEBX,EAX上一页下一页返回4.6程序优化0040122783EB1ESUBEBX,1EH0040122A8D58E2LEAEBX,[EAX−1Eh](3)乘除求EAX=EAX/16,可以用除法指令:XOREDX,EDXMOVEBX,16DIVEBX然而,可以用SHR指令达到同样的效果,但执行速度更快!SHREAX,4上一页下一页返回4.6程序优化求EAX=EAX*8,可以用乘法指令:MOVEBX,8MULEAX然而,可以用SHL指令达到同样的效果,但执行速度更

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论