1、第三章 汇编语言程序设计 第一节 机器语言、汇编语言与高级语言一、机器语言与汇编语言 计算机完成任何一个特定的功能都是通过执行特定的程序来实现的,程序是一系列指令组成的,计算机通过对每条指令的译码和执行来完成一系列操作。计算机最终能理解并执行的是以二进制代码表示的机器语言。例31 在内存中有一个数据块,其首地址为Buffer(3000H:0200H),其中存放16位的符号数20个。现要找出其中的最大值,并将其存入MAX字单元(其偏移地址为0228H)。借助8086汇编指令我们可以编写出如下程序:;exm3_1 find the largest number and store in the m
2、ax unit MOV AX,3000H MOV DS,AX MOV SI,0200H MOV CX,14H DEC CX MOV AX,SICHKMAX:ADD SI,2 CMP SI,AX JLE NEXT MOV AX,SINEXT:LOOP CHKMAX MOV 0288H,AX INT 20H 汇编以后机器码在内存中的形式如表31所示。表中第三列是汇编语言指令,它是用便于记忆的符号和格式来表示每一条实际的机器指令代码。用机器语言来编写程序很不直观,也是不现实的,特别是对于复杂问题。PC机有专门的汇编程序,可以用汇编语言直接编程。二、汇编语言与高级语言 汇编语言程序设计的基本单位仍然是
3、机器指令,只是采用助记符表示,便于人们记忆。因此汇编语言对机器的依赖性很大,称为面向机器的语言。每种机器都有它专用的汇编语言,在一种机器上用的汇编语言程序很难搬到另一种机器上使用,即不具备通用性和可移植性。汇编语言程序设计员必须对机器的硬件及软件资源有足够的了解才能设计程序。尽管如此,各种汇编语言在基本原理、基本概念和基本编程方法等方面是相同或相近的。高级语言,如BASIC、FORTRAN、C语言等是进一步发展了的计算机语言。它们是面向过程的语言,不依赖于机器,因而有很好的通用性和可移植性,而且用高级语言设计具有很高的程序设计效率。高级语言优点很多,还要学习汇编语言的理由如下:(1)汇编语言仍
4、然是各种系统软件(如操作系统)设计的基本语言。虽然有人用C语言来编写系统软件,但是要将它翻译成机器语言,还必须用到汇编语言。(2)用汇编语言编写的程序一般比用高级语言编写的程序执行得快,且所占内存较少。(3)在许多实时控制系统中,高级语言并不完全适合,汇编语言就是不可缺少的了。特别是在直接有效地利用机器硬件功能方面,比如中断等就都少不了汇编语言。(4)用高级语言开发中,有时需要编写一些非标准过程,而高级语言可能并不支持。这时就需用汇编语言来补充高级语言在某些特定领域中的功能不足。(5)学习汇编语言对于学习计算机硬件组成及工作原理是十分重要的。三、汇编与连接 用汇编语言设计程序首先应根据任务编写
5、汇编语言源程序,文件类型一般标为ASM文件。汇编语言源程序编写有许多规则和方法,本章将予以介绍。可以用各种文本编辑软件建立源文件,如全屏幕编辑软件EDIT,还有WORD等,ASM文件采用ASCII码。通常文件名的后缀是.ASM。汇编语言源程序必须经过翻译才能变为二进制机器代码。在计算机中完成这种翻译工作的软件叫汇编程序或汇编器(Assembler)。MASM.EXE就是汇编程序。如果源程序名为:SAMPLE.ASM则用命令:C:MASM SAMPLE.ASM 就可产生目标程序,文件类型一般标为OBJ。为了便于程序调试,同时还产生列表文件,一般标为LST。目标文件一般还不是可执行的程序,目标文件
6、并没有最终解决机器的寻址问题。程序究竟装在内存的哪个区域?目标文件没有指定。另外,多个不同的目标文件以及库文件LIB可以组合在一起形成一个更大的文件。连接装配程序LINK.EXE则用来把指定的目标文件和库文件组装成一个完整的程序文件,并且完成相对地址的调整和对变量引用的处理。在汇编后再用LINK命令:C:LINK SAMPLE则产生SAMPLE.EXE,这是一个可执行文件。EXE文件除了程序、数据等代码之外,还包含一个文件头。这个文件头也称程序段前缀PSP,占256个字节。文件头中包含了操作系统中的装入程序在把该EXE文件装入内存时所需要的有关信息,如重定位表和初始化有关段寄存器信息等。PSP
7、的头两个字节是一条INT 20H指令,即返回DOS指令。当一个用户程序运行结束时,可以通过把控制返回给PSP中的这条指令而终止自已的进程。这是由用户程序返回操作系统的传统方法。当DOS加载一个可执行文件的程序代码到内存中去时,它首先为该程序建立一个程序段前缀PSP,然后把可执行的程序代码加载到PSP后续的地址上。(即CS指向地点),而DS和ES初始化在PSP的起始地址。DOS装入EXE文件后的内行分配如图所示。第二节 汇编语言源程序的结构 如果把例31改写为标准的汇编语言源程序,就可以用MASM命令进行汇编,则源程序如下:;exm3_1.asm find the largest number
8、and;store in the max unit data segment org 0200h buffer dw 0,1,-5,10,256,-128,-100,45,6 dw 3,-15,-67,39,4,20,-1668,-32766 dw 32765,-525,300 count equ ($-buffer)/2 max dw?data ends stack segment stack stack db 100 dup (s)stack ends code segment para code assume cs:code,ds:data,ss:stack sta proc far p
9、ush ds xor ax,ax push ax mov ax,data mov ds,ax lea si,buffer mov cx,count dec cx mov ax,si chkmax:add si,2 cmp si,ax jle next mov ax,si next:loop chkmax mov max,ax ret sta endpcode ends end sta一、汇编语言的语句格式下面分析汇编语言源程序的结构。汇编语言包含两类语句:CPU指令语句和汇编语言伪指令语句。指令语句指定CPU做什么操作,如第二章所述;伪指令语句指定汇编器作何种操作,这是这一章要学习的。指令语句
10、都有对应的机器码,而伪指令语句一般不产生机器码(只有少数一些数据定义伪指令会产生机器代码)。1指令语句的格式 汇编语言的CPU指令语句格式如下:label:memonic operand ,operand ;comments标号域 助记符域 操作数域 注释域 四个域中只有助记符域是必不可缺的,其他的域都是可选的。助记符域是指令的操作码助记符,如MOV、ADD、SUB等等。助记符域与操作数域之间至少应留有一个空格。两个操作数之间要有逗号分开。标号一般为转移指令提供目标地址的符号名,例如上例中的CHKMAX。程序员不必去计算相对转移的地址偏移量,汇编器会自动完成这一工作。标号后面跟一个冒号。伪指令
11、前的名字不能后跟冒号。注释域以分号打头。汇编语言中的标号或名字或其他变量名必须是由字母或特殊字符打头的字母数字串,中间不能有空格。合法的字符包括:字母:AZ 或 az 数字:09 特殊字符:问号(?)、圆点()、下横线(_)和美元符号(),圆点只能作为第一个字符。标号和名字的长度不超过31个字符,超过部分均被删去。2汇编伪指令的格式 伪指令是针对汇编程序的命令。它可以用来定义段和过程、定义语句、分配内存空间以及完成各种与程序设计有关的重要说明。汇编伪指令也有四个域:名字 伪指令 操作数 注释 一般来说,只有伪指令域是必须的。对于某些伪指令,名字域也是必须的,但是要注意的是名字域后面不能用冒号(
12、:)相随。域与域之间用空格隔开。伪指令的操作数域是可选的,它可以有多个操作数,只受行长度的限制。有的伪指令操作数域部分的各操作数之间要求用逗号(,)分开,而有些伪指令则要求用空格分开,必须严格遵循有关规定。二、汇编语言源程序的段定义 汇编语言源程序的段定义与内存的分段组织直接相关。典型的程序包含代码段、数据段和堆栈段。SEGMENT和ENDS伪指令用于定义各种段。它们的语句格式如下:段名字 SEGMENT 可选项 (段模块)段名字 ENDS 段名字必须在两处出现,而且必须一致。SEGMENT和ENDS必须成对出现。SEGMENT定义一个段的开始,ENDS定义一个段的结束。SEGMENT语句可以
13、有三种可选项:定位类型、连接方式和类别。可选项之间用空格符或制表符TAB分开。格式如下:段名 SEGMENT 定位类型 连接方式 类别 1定位类型 定位类型指明该段进入内存时从何种类型的边界开始。有四种定位类型:PAGE、PARA、WORD、BYTE。起始地址分别是:PAGE:0 0 0 0 0 0 0 0 B PARA:0 0 0 0 BWORD:0 B BYTE:B 分别表示以页、段、字、字节为起始地址。若缺省则隐含为PARA。2连接方式 连接方式告诉链接程序本段与其他段的关系。共有六种方式。NONE 表示本段与其他段在逻辑上没有关系,每段都有自己的基址。这是隐含方式。PUBLIC 链接程
14、序把本段与同名同类别的其他段连接成一个段。STACK 表示此段为堆栈段。连接时将所有STACK连接方式的同名段连接成一个段。程序中必须至少有一个STACK段,否则用户必须指令初始化SS和SP;若有多个,初始化时SS指向第一个STACK段。COMMON 链接程序为本段和同名同类型的其他段指定相同的基址,因而本段将与同名同类别的其他段相覆盖。段的长度为最长的COMMON段的长度。AT 表达式 链接程序把本段装在表达式的值所指定 的段地址上。例:AT 1234H,该段的首地址为12340H。MCODE SEGMENT AT 2050H ;该段从20508H开始,ORG 0008H ;ORG 伪指令给
15、定偏移地址值 START:MOV AX,0 ;该指令与段基址偏移 8 个字节 .MCODE ENDS MEMORY 链接程序把本段定位在其他所有同名段之上(即地址较大的区域)。若有多个MEMORY段,则第一个按MEMORY方式处理,其余均按COMMON方式处理。3类别 类别必须用单引号括起来。类别指定同样只在模块连接时才需要。一般对于堆栈段总定义类别为STACK;对代码段通常指定类型为CODE;对数据段则指定为DATA。如果一个程序不准备和其他程序组合,也可以不指定类别。类别名可由用户任意设定。链接程序把类别名相同的段(段名未必相同)放在连续的存储区间内。但仍为不同的段(连接方式为PUBLIC
16、、COMMON的段除外)。在前面例子中的堆栈段定义如下:STACK SEGMENT PARA STACK STACK堆栈段名 定位 连接方式 类别 在堆栈段里一般只要简单地定义一下堆栈段空间大小就行了。如DB 100 DUP(?)语句为堆栈分配了100个字节的空间。堆栈空间的大小取决于程序如何使用堆栈。最大不能超过64KB。定义堆栈空间时还可以给字节以初始化,例如:DB 100 DUP (STACK)不仅分配了500个字节的堆栈空间。而且还把这个空间初始化为重复100次的STACK字符串。其目的是为了在查看内存分配以调试程序时可以方便地找到堆栈空间。一般应直接定义为字空间:DW 20 DUP(
17、?)三、汇编语言的过程定义 代码段的内容主要是程序的可执行码。一个代码段可以由一个或几个过程(子程序)组成,仅由一个过程组成的代码段其形式如下:段名 SEGMENT 过程名 PROC FAR ;过程定义语句 ;过程体 RET 过程名 ENDP ;过程定义结束 段名 ENDS 过程定义由伪指令PROC和ENDP完成。PROC后面的参数指定该过程的属性。属性为FAR代表这个过程是段际过程,属性为NEAR表示该过程是段内过程,省缺时代表NEAR过程。一个程序的主程序通常是FAR过程,如前面例子。但一般主程序不写成过程的形式,举例见后。PROC和ENDP必须成对出现,而且前面的过程名必须一致。例如对上
18、例中过程可定义为STA,则有:STA PROC FAR RET STA ENDP RET在FAR过程中被汇编为段际返回指令,在NEAR过程被汇编为段内返回指令。两者的机器码是不同的。同一段内的过程一般总是定义为NEAR过程,过程一经定义就可以用CALL语句调用:CALL 过程名 如果过程名是FAR属性,则是段际调用,产生双字构成的目标地址,并将双字的返回地址压入堆栈。四、汇编语言的段寻址 用SEGMENT 定义完各段后,还要用ASSUME伪指令来说明段寄存器与段名之间的对应关系。ASSUME语句的一般格式为:ASSUME 段寄存器名:段名 ,其中段寄存器有CS、DS、ES和SS。每个指定之间用
19、逗号分开。上例中ASSUME语句为:ASSUME SS:STACK,CS:CODE,DS:DATA 由于该例中没有用到ES,可以不写。ASSUME语句必须写在代码段中,一般情况下放在码段定义语句之后。ASSUME语句给出了段名与段寄存器的对应关系,但并没有真正给段寄存器赋值。段寄存器的赋值还要由程序本身来完成,例如上例中用了:MOV AX,DATA MOV DS,AX 给DS赋值。注意第一条指令:当MOV指令源操作数出现段名时,把段基址送给目的操作数,这是传送指令中的一个特殊情况。为什么只对DS赋值呢?这是因为DOS环境下运行程序时,DOS的装入程序已对CS:IP和SS:SP作了正确的初始化,
20、而DS、ES初始化为程序段前缀PSP的起点,而非用户所需的地址,见图31。五、标准程序前奏 DOS加载一个外部(EXE)文件时首先要建立一个PSP,其头两个字节是一条INT 20H指令。执行INT 20H指令是把控制返回给DOS的传统方法。由于DOS的装入程序在加载一个程序时把DS和ES定位在PSP的起点上,于是我们应在程序一开始通过下面三条指令把PSP的起点地址压入堆栈:PUSH DS SUB AX,AX PUSH AX 这样当程序执行到最后一条RET指令时,它将从堆栈顶部弹出PSP的起点地址送CS:IP,使得INT 20H指令得以执行,从而把控制权交还给DOS。因此常把这三条指令称为标准程
21、序前奏。六、汇编结束语句 END 汇编语言源程序的最后一个语句是汇编结束语句,即END语句,其格式是:END 表达式 其中表达式必须产生一个存储器地址,这个地址是当程序执行时,第一条要执行的指令的地址。在上例中,结束语句为:END STASTA是过程名,也即是第一条要执行的指令的地址。END语句通知汇编程序,汇编到此结束。END后面的表达式是可选的。如果一个模块是主模块,那么表达式通常是过程名,或者是第一条指令前的标号,它告诉汇编程序程序的入口点所在。如果不是主模块,而只是要把它和另外一个主模块连接,那么就应该用不带表达式的END语句。七、汇编语言源程序结构 我们看到一个汇编语言的源程序可以由
22、几个段组成,其中包括堆栈段、数据段和代码段等,这种分段结构的程序称为EXE程序。另有一种程序,其规模较小,而且只有一个用户使用,也无需与外部的其他模块进行装配,则可以把数据、代码连同堆栈段都包括在代码段内。这种程序称为COM程序。ASSUME只有一个数据项,即CS:CODE。由于DOS的装入程序在加载这种COM程序时,把4个段寄存器都初始化在PSP的起点上,所以把IP初始化在0100H,SP初始化在整个段的高端,如图32所示。把例31改写为COM程序的形式,则程序如下;code segment assume cs:codesta proc far org 0100hstart:jmp init
23、 org 0200hbuffer dw 0,1,-5,10,256,-128,-100,45,6 dw 3,-15,-67,39,4,20,-1668,-32766 dw 32765,-525,300count equ ($-buffer)/2max dw?Init:mov ax,cs mov ds,ax lea si,buffer mov cx,count dec cx mov ax,sichkmax:add si,2cmp si,axjle nextmov ax,sinext:loop chkmaxmov max,ax retsta endpcode ends end sta 伪指令ORG
24、 0100H通知汇编器,程序起始地址是0100H。这是因为前256个字节是DOS建立的PSP区,DOS装入COM程序时总把IP定在0100H,所以在0100H处必须是可执行的指令代码。COM程序不允许定义堆栈段。所以汇编时会产生一个“没有堆栈段”的出错信息,对此可不必理会。只要没有其他错误,便可以连接产生EXE文件,并且可以使用DOS的EXE2BIN实用程序把这个EXE文件转化为COM文件。与EXE程序相比,COM程序更紧凑,执行起来更快一些。但COM程序(数码、代码、堆栈合起来),不能超过64KH。第三节 数据定义 任何一个程序都要使用数据,本节讨论汇编语言的数据定义方法。一、常量、变量和标
25、号 汇编语言中数据项有常量、变量和标号三种类型。1常量 一个常量用二进制表示时,该数据须用字母B结尾;用十进制表示常量时可用D结尾,也可以不用;用十六进制表示常量时要以字母H结尾,规定十六进制数第一个字符必须是数字,如:0F12BH,1234H;如果用八进制表示常量则以字母Q结尾或者以字母O结尾。字符串必须用单引号或双引号括起来,汇编语言把它们汇编成相应的ASCII码。例如:字符串AB被汇编为41H,42H;对字符串12,则被汇编为31H,32H。2变量 变量实际上代表着内存单元,因此有时也称为内存变量。上例中的MAX单元就是一个变量,它是16位的。既然变量是内存单元,它必然具有段地址和段内偏
26、移量。所以变量有三个属性:段基址、段内偏移量以及类型。变量的类型是指变量具有的字节数。字节变量表示一个8位数据,其类型为1;字变量表示一个十六位数据其类型为2;双字变量表示一个32位的数据,其类型为4。3标号 标号实际上是代码段中的某一指令的地址。它也有三个属性:段地址、段内偏移量和类型。标号的类型有两种:NEAR标号,它只能在定义它的段内被引用,其类型为1(0FFFFH),它代表一个程序地址的偏移量;FAR标号,它既可以在定义它的段内被引用,也可以在其他段内被引用,其类型为2(0FFFEH),它代表了指令的段地址和偏移量。标号可以在各种转移指令中作为操作数使用。它只能定义在可执行的码段中。如
27、果在定义标号时使用了冒号(:),则汇编程序确定它是NEAR标号。远标号的定义见运算符PTR一节。常量和变量的名字及标号不能用宏汇编中的保留字。二、数据定义或分配数据单元的伪操作语句 汇编语言提供两种定义数据或分配数据单元的语句格式。1第一种格式 名字 伪操作 表达式其中名字域是可选的,但如果程序中要引用,则名字必须给出。伪操作域可以是DB、DW、DD。其意义为:DB 定义字节 DW定义字 DD 定义双字 表达式域可以是常数,也可以是表达式,还可以是一个问号。如果是问号,表示该数据定义语句用于保留(或称分配)内存空间,但不对该空间初始化。否则不仅为数据分配了内存空间,而且还把该空间初始化为表达式
28、所指定的值。例如:MAX DW?语句分配了一个字单元以备存放最大值(例31)。BUFFER DW 0,1,5 语句分配了三个字单元,并初始化为0,1,5,即00H,00H,01H,00H,0FBH,0FFH。A0 DB 1,23,45 使用了表达式。A1 DD 12345H 语句分配一个双字单元,并初始化为:45H,23H,01H,00H。2第二种格式 名字 伪操作 DUP(表达式)此格式可定义一些重复的数据或分配一数据块空间。例如:DATA1 DW 10 DUP(?);分配十个字的空间。DATA2 DB 20 DUP (5);分配20个字节,初始化为5。DB 100 DUP (STACK);
29、分配500字节,并重复填入53H、54H、41H、43H、4BH。三、等值伪操作语句 1EQU 语句 使用EQU可以用一个名字来代表一个常数或者表达式,例如:COUNT EQU 20 ;COUNT就等于20。BLOCK DB Read after me!NUM EQU BLOCK 后两句表示使NUM获得BLOCK块的字节效,即字符串的长度。表示汇编程序的汇编地址计数器的当前值。在这里等于字符串中最后一个字符“!”所在单元的下一个字节的地址的偏移量,BLOCK是字符串第一个字符R所在的单元的偏移量,BLOCK就得到字符串的长度。使用EQU应注意一点,使用EQU对某个名字赋值后,不能再用EQU伪操
30、作对该名字重新赋值。另一点,名字后面不要加冒号(:)。2等号伪操作语句(赋值语句)等号伪操作也可以把一个常数或表达式指定给一个名字。用等号伪操作定义的名字可以重新定义。B16 ;B1定义为6 B110 ;重定义B1为10 B1 EQU 20 ;出错,因为B1已经定义,不能用EQU重新定义。第四节 汇编语言的运算符一、算术运算符 算术运算符有加()、减()、乘()、除()、模(MOD)、左移(SHL)和右移(SHR)七种。除法返回的是商,而MOD操作返回除法操作的余数,例如:PI_INT EQU 3141610000 ;PI_INT3PI_REM EQU 31416 MOD 10000 ;N_R
31、EM1416 SHL和SHR是移位操作,一般在建立屏蔽字时使用。例如:MASKB EQU 00110010BMASKB1 EQU MASKB SHL 2 ;MASKB111001000BMASKB2MASKB SHR 2 ;MASKB200001100B二、逻辑运算符汇编语言的逻辑运算符有:AND 逻辑与OR 逻辑或XOR 逻辑异或NOT 逻辑非 逻辑运算符与逻辑运算指令的区别在于,前者在汇编时完成逻辑运算,而后者在执行指令时完成逻辑运算。MASKB EQU 00101011B MOV AL,5EH AND AL,MASKB AND 0FH 在汇编时汇编程序计算出MASKB AND 0FH为0
32、BH,按AND AL,0BH汇编第三条指令。当指令执行时AL才能得到0AH。三、关系运算符关系运算符有:EQ 等于NE 不等LT 小于GT 大于LE 小于等于GE 大于等于 关系运算符比较两个操作数并产生一个逻辑值。如果关系成立,则结果为真(0FFFFH);否则为假(0000H)。由于关系运算符只能产生两个值,因此很少单独使用。一般都同其他操作结合以构成一个判断表达式。例如要实现 5 如果 CHOICE EDIT E1.ASM 回车 也可使用其它文本编辑软件,但程序要以一般文本方式保存,程序后缀名为.ASM。二、汇编程序:在MSDOS方式下使用MASM.EXE程序:MASM /HE 回车 显示
33、命令选项信息 MASM /ZI E1.ASM;回车 生成E1.OBJ文件 /ZI 命令选项的功能是指示MASM在汇编时将符号和行号等信息加入到生成的目标文件中,这些信息在使用CV调试时要使用。后加分号(;)可使MASM只生成E1.OBJ文件。如果程序有语法等错误,汇编时将显示错误信息,并给出错误所在的行号,以便进行修改,如:SHR DL,4显示:e1.asm(52):error A2050:Improper operand type 0 Warning Errors 1 Severe Errors三、连接程序:在MSDOS方式下使用LINK.EXE程序:LINK /CO E1;回车 生成E1.
34、EXE /CO 命令选项是指示LINK程序生成支持CodeView调试的可执行程序。分号(;)使得LINK只生成E1.EXE文件。如果没有错误LINK执行后显示如下信息:Microsoft(R)Overlay Linker Version 3.64 Copyright(C)Microsoft Corp 1983-1988.All rights reserved.四、调试程序:在MSDOS方式下使用CodeView程序CV.EXE:CV E1 回车 或 CV E1.EXE 回车 如果被调试程序中没有CodeView所需要的调试符号、行号等信息,则显示:No Symbolic Informatio
35、n 如果程序调试运行正确后,则应不加/ZI和/CO选项,再将程序编译连接生成最终产品E1.EXE,此时程序应比调试用的E1.EXE小。五、CodeView的使用简介 1.CodeView的窗口:程序窗口 命令行窗口 寄存器窗口 观察窗口有菜单操作,也可利用快捷键,参见HELP帮助说明。2.快捷键:观察窗口程序窗口寄存器窗口命令行窗口 F2寄存器窗口显示;F3源程序、汇编显示转换;F4用户输出界面显示;F6当前窗口转换;。3.?命令 显示表达式、变量值 格式:?Expression ,format format:d或i有正负号的十进制整数 u无正负号的十进制整数 o无正负号的八进制整数 X十六进
36、制整数(大写形式表示)x十六进制整数(小写形式表示)f带符号十进制浮点数,小数位数6位 e以科学记号格式表示小数位数6位例如:显示程序数据段中的x变量。?X 回车?x,d 回车?x5,x 回车 0 x0011 17 000c4.X 命令 显示符号(变量)的地址 例如:X 回车 显示程序中所有符号(变量)的地址 X?m1 回车 x?S1 回车 显示指定符号的地址 X*显示所有模块名 E1.OBJ5.DUMP 命令 显示内存数据,与SYMDEB中的D命令相似。例如:DB m1 回车 DA S1 回车 DW Y 回车 DB DS:0000 L20 回车6.设置观察命令 Watch 用于打开观察窗口,
37、实时观察变量、内存等。例如:WB m1 L3 WB m2 L3 WB x WW y WA S1 L18 WA S2 L8 WB DS:0000 L10 观察指定内存 WW SS:0000 L8 对堆栈区观察7.删除观察命令 Watch Delete从窗口中删除由Watch命令设置的观察变量、内存等。例如:Y1 删除编号1号观察量 Y*全部删除,关闭Watch窗口8.利用快捷键执行调试命令 F8程序单步跟踪,可随CALL 过程指令进入子程序。F10程序逐步执行,可将CALL指令一次执行完,不进入子程序。F7程序执行到光标所在行,光标所在行应在当前程序执行位置之后。F5程序从当前位置执行到断点或执
38、行到结束,即GO命令。F9在光标所在位置设置或清楚断点,断点行高亮显示。用F8执行CALL、RET、PUSH、POP指令时注意观察堆栈变化。9.其它命令内存修改命令:EB DS:0000 回车 观察窗口中数据变化,操作同SYMDEB。反汇编命令:A 0000 回车 如可将程序第一个指令修改为MOV BX,4681。寄存器命令:R 或 RF 同SYMDEB,RF可列出标位的所有代表符号,方便修改,而且置位时为红色。修改基数命令:N10 设为十进制 N16 设为十六进制CodeView 开始默认为十六进制。.命令:.可将程序当前执行所在行显示在屏幕中,特别是找不到程序当前位置时,可用此命令。Cod
39、eView调试程序的调试指令非常丰富,详细使用说明参阅软件本身HELP的内容。第五节 基本结构程序设计一、顺序结构 程序的基本结构有四种:顺序结构、分支结构、循环结构以及子程序结构。本节将分别介绍这四种基本结构,并举例说明基本结构程序的设计方法。顺序结构是最基本的结构。其特点是语句按顺序逐步执行,无分支、循环和转移。例32 两个32位无符号数乘法程序。在8086指令系统中,数据是16位的且最长只有16位数的乘法指令。所以32位乘法需要做4次16位乘法,每次都要将部分积相加来实现,相加时则要注意位数对齐。我们用连续的四个字单元来存放乘积,各部分积应加到适当单元。程序如下:;exm3_2.asm
40、32 bits multiplydata segment num1 dw 8000h,8000h num2 dw 8008h,8000h mut dw 4 dup(0)data endsstack segment stack stack db 100 dup (s)stack endscode segment para codeassume cs:code,ds:data,ss:stack proc far push ds xor ax,ax push axmov ax,datamov ds,axlea bx,num1mov ax,bx ;baxmov si,bx+4 ;dsimov di,b
41、x+6 ;cdimul si ;b*dmov bx+8,ax ;部分积1存于积单元中。mov bx+10,dx mov ax,bx+2 ;aax mul si ;a*d add bx+10,ax adc bx+12,dx ;带进位加入积2单元中 mov ax,bx ;bax mul di ;b*c add bx+10,ax ;b*c 加入积单元 adc bx+12,dx ;带进位加至部分积3 adc word ptr bx+14,0 ;进位加至部分积4 mov ax,bx+2 ;aax mul di ;a*c add bx+12,ax adc bx+14,dx retsta endpcode
42、end end sta二、分支结构 始终是顺序结构的程序实际上并不多见,经常都会碰到有分支的情况。典型的分支结构如图33所示。分支结构程序通常采用条件转移指令或者转移表来实现。下面举例说明。YN 1 当 x 0例33 符号函数y 0 当 x=0 1 当 x bottom of array shr cx,1 ;cx:element num dec cx agan:mov ax,buff si cmp ax,buff si2 jge next xchg ax,buff si2 mov buff si,ax mov bl,1next:dec si dec si loop agan cmp bl,0
43、jne coti ret sort endpcode ends end sort四、子程序结构 若一段指令在一个程序中多处使用,或在多个程序中用到,则通常把这段指令当作一个独立的模块处理,称为子程序(8088称为过程)。主、子程序关系 在许多情况下,希望子程序有通用性,希望子程序除有使用说明外,还能接受主程序传来的入口参数,在子程序执行完后把出口参数传递给主程序。例如两个多字节数相加的子程序,调用时允许主程序给出两个加数(或它们的地址),即入口参数;子程序返回时应能返回“和”或者“和”的地址,这个“和”或“和”的地址便是出口参数。参数的传递通常有三种方式:(1)用寄存器传递,适用于参数个数少的
44、情况。(2)用程序存储器中的参数表传递,这个参数表紧跟在调用指令的后面。(3)利用堆栈传递参数,适用于参数较多且子程序有嵌套、递归调用的情况。(一)用程序存储器中的参数表传递参数 在主程序中把要传送的参数直接放在调用指令的后面。例如:CALL ADDSUB NUM1 DD 12345678H ;定义被加数 NUM2 DD 5678H ;定义加数 BUFFER DD?;保留 “和”的存放单元 在子程序中,到堆栈中去取返回地址(CALL指令执行时压入堆栈的NUM1的地址,若是NEAR型调用,即是NUM1的偏移地址),进而获得参数。应该注意的是,从子程序返回时应该修改返回地址,跨过参数,返回到真正的
45、指令地址。子程序中相应指令如下:PUSH BPMOV BP,SP MOV BX,BP2 ;从堆栈中取出返回的偏移(近调)MOV CX,CS:BX ;地址,即参数所在地取参数MOV SI,CS:BX2 ADD BX,12 ;修改返回地址,使子程序返回MOV BP2,BX ;到参数后面的指令 RET 如果传送的是变量(即参数的地址)不是参数的数值,则传送的参数是参数的地址。这时在主程序中CALL指令后面用的是:NUM1 DW OFFSET VAL1,SEG VAL1类型的语句,在子程序中要用 LDS SI,DWORD PTR CS:BX类型的语句取参数的地址。例37 64位二进制数相加(不考虑进位
46、、即和不超过64位),采用在CALL指令后定义加数及和的偏移地址和段地址的参数传递方法。程序如下:;exm3_7.asm 64bits additiondata segmentval1 dd 10h,12345678hval2 dd 20h,34567890hbuffer dd?,?data endsstack segment stack stack db 100 dup(s)stack endscode segment para code assume cs:code,ds:data,ss:stackproc farpush dsxor ax,axpush axmov ax,datamov
47、ds,axmov es,axcall add64num1 dw offset val1,seg val1num2 dw offset val2,seg val2result dw offset buffer,seg buffer retsta endpadd64 proc push bp mov bp,sp pushf push ax push cx push bx push si push di mov bx,bp2 ;get ret address lds si,dword ptr cs:bx ;get oprand address lds dx,dword ptr cs:bx4 les
48、di,dword ptr cs:bx8 xchg dx,bx mov cx,4 clcagain:lodsw adc ax,bx stosw inc bx inc bx loop again mov bx,bp2 add bx,0ch ;adjust ret address mov bp2,bx pop di pop si pop bx pop cx pop ax popf pop bp retadd64 endpcode ends end sta例37在码段和堆栈段情况如图310所示。(二)用堆栈传递参数 堆栈传递适用于参数较多且子程序有嵌套、递归调用的情况。在主程序中把参数压入堆栈后调用子
49、程序,子程序中将参数从堆栈中弹出。例38 数组求和子程序。假设有一数组求和子程序SUM,试用这个子程序分别求出ARY1和ARY2两个数组的和,结果分别存入SUM1和SUM2字单元中。SUM子程序的说明见程序中注释部分。第一次调用子程序后堆栈的情况如图311所示。程序如下:;exm3_8.asm data array summationdata segment aryl db 03h,07h,50h,06h,23h,45h,0f6h,0dfh len1 equ$ary1 sum1 dw?ary2 db 33h,44h,55h,12h,78h,89h,0feh,0cdh len2 equ$ary2
50、 sum2 dw?data endsstack segment stack stack db 100 dup(s)stack endscode segment para code assume cs:codc,ds:data,ss:stacksta proc far push ds xor ax,ax push ax mov ax,data mov ds,ax mov ax,len1 push ax lea ax,ary1 push ax call sum mov ax,len2 push ax lea ax,ary2 push ax call sum retsta endpsum proc