汇编语言超浓缩教程 下载本文

.汇编语言超浓缩教程

对初学者而言,汇编的许多命令太复杂,往往学习很长时间也写不出一个漂漂亮亮的程序,以致妨碍了我们学习汇编的兴趣,不少人就此放弃。所以我个人看法学汇编,不一定要写程序,写程序确实不是汇编的强项,大家不妨玩玩DEBUG,有时CRACK出一个小软件比完成一个程序更有成就感(就像学电脑先玩游戏一样)。某些高深的指令事实上只对有经验的汇编程序员有用,对我们而言,太过高深了。为了使学习汇编语言有个好的开始,你必须要先排除那些华丽复杂的命令,将注意力集中在最重要的几个指令上(CMP LOOP MOV JNZ……)。但是想在啰里吧嗦的教科书中完成上述目标,谈何容易,所以本人整理了这篇超浓缩(用WINZIP、WINRAR…依次压迫,)教程。大言不惭的说,看通本文,你完全可以“不经意”间在前辈或是后生卖弄一下DEBUG,很有成就感的,试试看!那么――这个接下来呢?―― Here we go!(阅读时看不懂不要紧,下文必有分解)

因为汇编是通过CPU和内存跟硬件对话的,所以我们不得不先了解一下CPU和内存:(关于数的进制问题在此不提)

CPU是可以执行电脑所有算术╱逻辑运算与基本 I/O 控制功能的一块芯片。一种汇编语言只能用于特定的CPU。也就是说,不同的CPU其汇编语言的指令语法亦不相同。个人电脑由1981年推出至今,其CPU发展过程为:8086→80286→80386→80486→PENTIUM →……,还有AMD、CYRIX等旁支。后面兼容前面CPU的功能,只不过多了些指令(如多能奔腾的MMX指令集)、增大了寄存器(如386的32位EAX)、增多了寄存器(如486的FS)。为确保汇编程序可以适用于各种机型,所以推荐使用8086汇编语言,其兼容性最佳。本文所提均为8086汇编语言。寄存器(Register)是CPU内部的元件,所以在寄存器之间的数据传送非常快。用途:1.可将寄存器内的数据执行算术及逻辑运算。2.存于寄存器内的地址可用来指向内存的某个位置,即寻址。3.可以用来读写数据到电脑的周边设备。8086 有8个8位数据寄存器,这些8位寄存器可分别组成16位寄存器:AH&AL=AX:累加寄存器,常用于运算;BH&BL=BX:基址寄存器,常用于地址索引;CH&CL=CX:计数寄存器,常用于计数;DH&DL=DX:数据寄存器,常用于数据传递。为了运用所有的内存空间,8086设定了四个段寄存器,专门用来保存段地址:CS(Code Segment):代码段寄存器;DS(Data Segment):数据段寄存器;SS(Stack Segment):堆栈段寄存器;ES(Extra Segment):附加段寄存器。当一个程序要执行时,就要决定程序代码、数据和堆栈各要用到内存的哪些位置,通过设定段寄存器 CS,DS,SS 来指向这些起始位置。通常是将DS固定,而根据需要修改CS。所以,程序可以在可寻址空间小于64K的情况下被写成任意大小。 所以,程序和其数据组合起来的大小,限制在DS 所指的64K内,这就是COM文件不得大于64K的原因。8086以内存做为战场,用寄存器做为军事基地,以加速工作。除了前面所提的寄存器外,还有一些特殊功能的寄存器:IP(Intruction Pointer):指令指针寄存器,与CS配合使用,可跟踪程序的执行过程;SP(Stack Pointer):堆栈指针,与SS配合使用,可指向目前的堆栈位置。BP(Base Pointer):基址指针寄存器,可用作SS的一个相对基址位置;SI(Source Index):源变址寄存器可用来存放相对于DS段之源变址指针;DI(Destination Index):目的变址寄存器,可用来存放相对于 ES 段之目的变址指针。还有一个标志寄存器FR(Flag Register),有九个有意义的标志,将在下文用到时详细说明。

内存是电脑运作中的关键部分,也是电脑在工作中储存信息的地方。内存组织有许多可存放数值的储存位置,叫“地址”。8086地址总线有20位,所以CPU拥有达1M的寻址空间,这也是DOS的有效控制范围,而8086能做的运算仅限于处理16位数据,即只有0到64K,所以,必须用分段寻址才能控制整个内存地址。完整的20位地址可分成两部份:1.段基址

(Segment):16位二进制数后面加上四个二进制0,即一个16进制0,变成20位二进制数,可设定1M中任何一个64K段,通常记做16位二进制数;2.偏移量(Offset):直接使用16位二进制数,指向段基址中的任何一个地址。如:2222(段基址):3333(偏移量),其实际的20位地址值为:25553。除了上述营养要充分吸收外,你还要知道什么是DOS、BIOS功能调用,简单的说,功能调用类似于WIN95 API,相当于子程序。汇编写程序已经够要命了,如果不用MS、IBM的子程序,这日子真是没法过了(关于功能调用详见《电脑爱好者》98年11期)。

编写汇编语言有两种主要的方法:1.使用MASM或TASM等编译器;2.使用除错程序DEBUG.COM。DEBUG其实并不能算是一个编译器,它的主要用途在于除错,即修正汇编程序中的错误。不过,也可以用来写短的汇编程序,尤其对初学者而言,DEBUG 更是最佳的入门工具。因为DEBUG操作容易:只要键入DEBUG回车,A回车即可进行汇编,过程简单,而使用编译器时,必须用到文本编辑器、编译器本身、LINK以及EXE2BIN等程序,其中每一个程序都必须用到一系列相当复杂的命令才能工作,而且用编译器处理源程序,必须加入许多与指令语句无关的指示性语句,以供编译器识别,使用 DEBUG 可以避免一开始就碰到许多难以理解的程序行。DEBUG 除了能够汇编程序之外,还可用来检查和修改内存位置、载入储存和执行程序、以及检查和修改寄存器,换句话说,DEBUG是为了让我们接触硬件而设计的。(8086常用指令用法将在每个汇编程序中讲解,限于篇幅,不可能将所有指令列出)。

DEBUG的的A命令可以汇编出简单的COM文件,所以DEBUG编写的程序一定要由地址 100h(COM文件要求)开始才合法。FOLLOW ME,SETP BY SETP(步步回车): 输入 A100 ; 从DS:100开始汇编

2.输入 MOV DL,1 ; 将数值 01h 装入 DL 寄存器 3.输入 MOV AH,2 ; 将数值 02h 装入 DL 寄存器

4.输入 INT 21 ; 调用DOS 21号中断2号功能,用来逐个显示装入DL的字符 5.输入 INT 20 ; 调用DOS 20号中断,终止程序,将控制权交回给 DEBUG 6.请按 Enter 键

7.现在已将汇编语言程序放入内存中了,输入 G(运行) 8.出现结果:输出一个符号。

ㄖ ←输出结果其实不是它,因WORD97无法显示原结果,故找一赝品将就着。 Program terminated normally

我们可以用U命令将十六进制的机器码反汇编(Unassemble)成汇编指令。你将发现每一行右边的汇编指令就是被汇编成相应的机器码,而8086实际上就是以机器码来执行程序。 1.输入 U100,106

1FED:0100 B201 MOV DL,01 1FED:0102 B402 MOV AH,02 1FED:0104 CD21 INT 21 1FED:0106 CD20 INT 20

DEBUG可以用R命令来查看、改变寄存器内容。CS:IP寄存器,保存了将执行指令地址。

1.输入R

AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=1FED ES=1FED SS=1FED CS=1FED IP=0100 NV UP EI PL NZ NA PO NC 1FED:0100 B201 MOV DL,01

当程序由DS:100开始执行,那么终止程序时,DEBUG会自动将IP内容重新设定为

100。当你要将此程序做成一个独立的可执行文件,则可以用N命令对该程序命名。但一定要为COM文件,否则无法以DEBUG载入。

输入N SMILE.COM ;我们得告诉DEBUG程序长度:程序从100开始到106,故占用7字节。我们利用BX存放长度值高位部分,而以CX存放低位部分。

2.输入RBX ;查看 BX 寄存器的内容,本程序只有7个字节,故本步可省略 3.输入 RCX ;查看 CX 寄存器的内容 4.输入 7 ;程序的字节数

5.输入 W ;用W命令将该程序写入(Write)磁盘中

修行至此,我们便可以真正接触8086汇编指令了。 当我们写汇编语言程序的时候,通常不会直接将机器码放入内存中,而是打入一串助记符号(Mnemonic Symbols),这些符号比十六进制机器码更容易记住,此之谓汇编指令。助记符号,告诉CPU应执行何种运算。 也就是说,助忆符号所构成的汇编语言是为人设计的,而机器语言是对PC设计的。 现在,我们再来剖析一个可以将所有ASCII码显示出来的程序。 1. 输入 DEBUG 2. 输入 A100

3.输入 MOV CX,0100 ;装入循环次数

MOV DL,00 ;装入第一个ASCII码,随后每次循环装入新码 MOV AH,02 INT 21

INC DL ;INC:递增指令,每次将数据寄存器 DL 内的数值加 1

LOOP 0105 ;LOOP:循环指令,每执行一次LOOP,CX值减1,并跳 ;到循环的起始地址105,直到CX为0,循环停止 INT 20

4.输入 G即可显示所有ASCII码

当我们想任意显示字符串,如:UNDERSTAND?,则可以使用DOS21H号中断9H号功能。输入下行程序,存盘并执行看看: 1.输入 A100

MOV DX,109 ;DS:DX = 字符串的起始地址 MOV AH,9 ;DOS的09h功能调用 INT 21 ;字符串输出 INT 20

DB 'UNDERSTAND?$';定义字符串

在汇编语言中,有两种不同的指令:1.正规指令:如 MOV 等,是属于CPU的指令,用来告诉CPU在程序执行时应做些什么,所以它会以运算码(OP-code)的方式存入内存中;2.伪指令:如DB等,是属于DEBUG等编译器的指令,用来告诉编译器在编译时应做些什么。DB(Define Byte)指令用来告诉DEBUG 将单引号内的所有ASCII 码放入内存中。使用 9H 功能的字符串必须以$结尾。用D命令可用来查看DB伪指令将那些内容放入内存。 6.输入 D100

1975:0100 BA 09 01 B4 09 CD 21 CD-20 75 6E 64 65 72 73 74 ......!. underst 1975:0110 61 6E 64 24 8B 46 F8 89-45 04 8B 46 34 00 64 19 and$.F..E..F4.d. 1975:0120 89 45 02 33 C0 5E 5F C9-C3 00 C8 04 00 00 57 56 .E.3.^_.......WV 1975:0130 6B F8 0E 81 C7 FE 53 8B-DF 8B C2 E8 32 FE 0B C0 k.....S.....2... 1975:0140 74 05 33 C0 99 EB 17 8B-45 0C E8 D4 97 8B F0 89 t.3.....E....... 1975:0150 56 FE 0B D0 74 EC 8B 45-08 03 C6 8B 56 FE 5E 5F V...t..E....V.^_

1975:0160 C9 C3 C8 02 00 00 6B D8-0E 81 C3 FE 53 89 5E FE ......k.....S.^. 1975:0170 8B C2 E8 FB FD 0B C0 75-09 8B 5E FE 8B 47 0C E8 .......u..^..G..

现在,我们来剖析另一个程序:由键盘输入任意字符串,然后显示出来。db 20指示DEBUG保留20h个未用的内存空间供缓冲区使用。 输入A100

MOV DX,0116 ;DS:DX = 缓冲区地址,由DB伪指令确定缓冲区地址 MOV AH,0A ;0Ah 号功能调用 INT 21 ;键盘输入缓冲区

MOV DL,0A ;由于功能Ah在每个字符串最后加一个归位码(0Dh由 Enter MOV AH,02 ;产生),使光标自动回到输入行的最前端,为了使新输出的 INT 21 ;字符串不会盖掉原来输入的字符串,所以利用功能2h加一 ;个换行码(OAh),使得光标移到下一行的的最前端。 MOV DX,0118 ;装入字符串的起始位置

MOV AH,09 ;9h功能遇到$符号才会停止输出,故字符串最后必须加上 INT 21 ;$,否则9h功能会继续将内存中的无用数据胡乱显示出来 INT 20

DB 20 ;定义缓冲区

送你一句话:学汇编切忌心浮气燥。

客套话就不讲了。工欲善其事,必先利其器。与其说DEBUG 是编译器,倒不如说它是“直译器”,DEBUG的A命令只可将一行汇编指令转成机器语言,且立刻执行。真正编译器(MASM)的运作是利用文本编辑器(EDIT等)将汇编指令建成一个独立且附加名为.ASM的文本文件,称源程序。它是MASM 程序的输入部分。MASM将输入的ASM文件,编译成.OBJ文件,称为目标程序。OBJ文件仅包含有关程序各部份要载入何处及如何与其他程序合并的信息,无法直接载入内存执行。链结程序LINK则可将OBJ文件转换成可载入内存执行(EXEcute)的EXE文件。还可以用EXE2BIN,将符合条件的EXE文件转成COM文件(COM 文件不但占用的内存最少,而且运行速度最快)。

下面我们用MASM写一个与用DEBUG写的第一个程序功能一样的程序。 用EDIT编辑一个SMILE.ASM的源程序文件。 源程序 DEBUG 程序 prognam segment assume cs:prognam org 100h A100 mov dl,1 mov dl,1 mov ah,2 mov ah,2 int 21h int 21 int 20h int 20 prognam ends end

比较一下:1.因为MASM会将所有的数值假设为十进制,而DEBUG则只使用十六进制,所以在源程序中,我们必须在有关数字后加上代表进制的字母,如H代表十六进制,D代表十进制。若是以字母开头的十六进制数字,还必须在字母前加个0,以表示它是数,如0AH。2.源程序增加五行叙述:prognam segment 与 prognam ends 是成对的,用来告诉 MASM 及LINK,此程序将放在一个称为PROGNAM(PROGram NAMe)的程序段内,其中段名(PROGNAM)可以任取,但其位置必须固定。assume cs:prognam 必须在程序的开头,

用来告诉编译器此程序所在段的位置放在CS寄存器中。end用来告诉MASM,程序到此结束, ORG 100H作用相当于DEBUG的A100,从偏移量100开始汇编。COM 文件的所有源程序都必须包含这五行,且必须依相同的次序及位置出现,这点东西记下就行,千篇一律。接着,我们用MASM编译SMILE.ASM。

输入 MASM SMILE ←不用打入附加名.ASM。 Microsoft (R) Macro Assembler Version 5.10

Copyright (C) Microsoft Corp 1981, 1988. All rights reserved.

Object filename [SMILE.OBJ]: ←是否改动输出OBJ文件名,如不改就ENTER Source listing [NUL.LST]: ← 是否需要列表文件(LST),不需要就ENTER Cross-reference [NUL.CRF]: ←是否需要对照文件(CRF),不需要则ENTER 50162 + 403867 Bytes symbol space free

0 Warning Errors ←警告错误,表示编译器对某些语句不理解,通常是输入错误。 0 Severe Errors ←严重错误,会造成程序无法执行,通常是语法结构错误。

如果没有一个错误存在,即可生成OBJ文件。OBJ中包含的是编译后的二进制结果,它还无法被 DOS载入内存中加以执行,必须加以链结(Linking)。以LINK将OBJ文件(SMILE.OBJ)链结成 EXE 文件(SMILE.EXE)时,。 1.输入 LINK SMILE ←不用附加名OBJ Microsoft (R) Overlay Linker Version 3.64

Copyright (C) Microsoft Corp 1981, 1988. All rights reserved.

Run File [SMILE.EXE]: ← 是否改动输出EXE文件名,如不改就ENTER List File [NUL.MAP]: ← 是否需要列表文件(MAP),不需要则ENTER Libraries [.LIB]: ←是否需要库文件,要就键入文件名,不要则ENTER

LINK : warning L4021: no stack segment← 由于COM文件不使用堆栈段,所以错误信息

←\并不影响程序正常执行

至此已经生成EXE文件,我们还须使用EXE2BIN 将EXE文件(SMILE.EXE),转换成COM文件(SMILE.COM)。输入EXE2BIN SMILE产生 BIN 文件(SMILE.BIN)。其实 BIN 文件与 COM 文件是完全相同的,但由于DOS只认COM、EXE及BAT文件,所以BIN文件无法被正确执行,改名或直接输入 EXE2BIN SMILE SMILE.COM即可。现在,磁盘上应该有 SMILE.COM 文件了,你只要在提示符号C:>下,直接输入文件名称 SMILE ,就可以执行这个程序了。

你是否觉得用编译器产生程序的方法,比 DEBUG 麻烦多了!以小程序而言,的确是如此,但对于较大的程序,你就会发现其优点了。我们再将ASCII程序以编译器方式再做一次,看看有无差异。首先,用EDIT.COM建立 ASCII.ASM 文件。 prognam segment ;定义段

assume cs:prognam ;把上面定义段的段基址放入 CS mov cx,100h ; 装入循环次数

mov dl,0 ; 装入第一个ASCII码,随后每次循环装入新码 next: mov ah,2 int 21h

inc dl ;INC:递增指令,每次将数据寄存器 DL 内的数值加 1

loop next ; 循环指令,执行一次,CX减1,直到CX为0,循环停止 int 20h

prognam ends ;段终止

end ;汇编终止

在汇编语言的源程序中,每一个程序行都包含三项元素:

start: mov dl,1 ;装入第一个ASCII码,随后每次循环装入新码 标识符 表达式 注解

在原始文件中加上注解可使程序更易理解,便于以后参考。每行注解以“;”与程序行分离。编译器对注解不予理会,注解的数据不会出现在OBJ、EXE或COM文件中。由于我们在写源程序时,并不知道每一程序行的地址,所以必须以符号名称来代表相对地址,称为“标识符”。我们通常在适当行的适当位置上,键入标识符。标识符(label)最长可达31 个字节,因此我们在程序中,尽量以简洁的文字做为标识符。现在,你可以将此ASCII.ASM 文件编译成 ASCII.COM 了。1.MASM ASCII,2.LINK ASCII,3.EXE2BIN ASCII ASCII.COM。 注意:当你以编译器汇编你设计的程序时,常会发生打字错误、标识符名称拼错、十六进制数少了h、逻辑错误等。汇编老手常给新人的忠告是:最好料到自己所写的程序一定会有些错误(别人告诉我的);如果第一次执行程序后,就得到期望的结果,你最好还是在检查一遍,因为它可能是错的。原则上,只要大体的逻辑架构正确,查找程序中错误的过程,与写程序本身相比甚至更有意思。写大程序时,最好能分成许多模块,如此可使程序本身的目的较单纯,易于撰写与查错,另外也可让程序中不同部份之间的界限较清楚,节省编译的时间。如果读程序有读不懂的地方最好用纸笔记下有关寄存器、内存等内容,在纸上慢慢比划,就豁然开朗了。

下面我们将写一个能从键盘取得一个十进制的数值,并将其转换成十六进制数值而显示于屏幕上的“大程序”。前言:要让8086执行这样的功能,我们必须先将此问题分解成一连串的步骤,称为程序规划。首先,以流程图的方式,来确保整个程序在逻辑上没有问题(不用说了吧!什么语言都要有此步骤)。这种模块化的规划方式,称之为“由上而下的程序规划”。而在真正写程序时,却是从最小的单位模块(子程序)开始,当每个模块都完成之后,再合并成大程序;这种大处著眼,小处著手的方式称为“由下而上的程序设计”。

我们的第一个模块是BINIHEX,其主要用途是从8086的BX寄存器中取出二进制数,并以十六进制方式显示在屏幕上。注意:子程序如不能独立运行,实属正常。 binihex segment assume cs:binihex

mov ch,4 ;记录转换后的十六进制位数(四位)

rotate: mov cl,4 ;利用CL当计数器,记录寄存器数位移动次数 rol bx,cl ;循环寄存器BX的内容,以便依序处理4个十六进制数 mov al,bl ;把bx低八位bl内数据转移至al and al,0fh ;把无用位清零

add al,30h ;把AL内数据加30H,并存入al cmp al,3ah ;与3ah比较 jl printit ;小于3ah则转移

add al,7h ;把AL内数据加30H,并存入al printit:mov dl,al ;把ASCII码装入DL mov ah,2 int 21h

dec ch ;ch减一,减到零时,零标志置1

jnz rotate ;JNZ:当零标志未置1,则跳到指定地址。即:不等,则转移 int 20h ;从子程序退回主程序 binihex ends

end

利用循环左移指令ROL循环寄存器BX(BX内容将由第二个子程序提供)的内容,以便依序处理4个十六进制数:1. 利用CL当计数器,记录寄存器移位的次数。2.将BX的第一个十六进制值移到最右边。利用 AND (逻辑“与”运算:对应位都为1时,其结果为1,其余情况为零)把不要的部份清零,得到结果:先将BL值存入AL中,再利用AND以0Fh(00001111)将AL的左边四位清零。由于0到9的ASCII码为30h到39h,而A到F之ASCII码为41h到46h,间断了7h,所以得到结果:若AL之内容小于3Ah,则AL值只加30h,否则AL再加7h。ADD指令会将两个表达式相加,其结果存于左边表达式内。标志寄存器(Flag Register)是一个单独的十六位寄存器,有9个标志位,某些汇编指令(大部份是涉及比较、算术或逻辑运算的指令)执行时,会将相关标志位置1或清0, 常碰到的标志位有零标志(ZF)、符号标志(SF)、溢出标志(OF)和进位标志(CF)。 标志位保存了某个指令执行后对它的影响,可用其他相关指令,查出标志的状态,根据状态产生动作。CMP指令很像减法,是将两个表达式的值相减,但寄存器或内存的内容并未改变,只是相对的标志位发生改变而已:若 AL 值小于 3Ah,则正负号标志位会置0,反之则置1。 JL指令可解释为:小于就转移到指定位置,大于、等于则向下执行。CMP和JG 、JL等条件转移指令一起使用,可以形成程序的分支结构,是写汇编程序常用技巧。

第二个模块DECIBIN 用来接收键盘打入的十进制数,并将它转换成二进制数放于BX 寄存器中,供模块1 BINIHEX使用。 decibin segment assume cs:decibin mov bx,0 ;BX清零 newchar:mov ah,1 ;

int 21h ;读一个键盘输入符号入al,并显示

sub al,30h ;al减去30H,结果存于al中,完成ASCII码转二进制码 jl exit ;小于零则转移 cmp al,9d

jg exit ;左>右则转移

cbw ;8位al转换成16位ax

xchg ax,bx ;互换ax和bx内数据 mov cx,10d ;十进制数10入cx

mul cx ;表达式的值与ax内容相乘,并将结果存于ax xchg ax,bx add bx,ax

jmp newchar ;无条件转移 exit: int 20 ;回主程序 decibin ends end

CBW 实际结果是:若AL中的值为正,则AH填入00h;反之,则AH填入FFh。XCHG常用于需要暂时保留某个寄存器中的内容时。

当然,还得一个子程序(CRLF)使后显示的十六进制数不会盖掉先输入的十进制数。 crlf segment assume cs:crlf

mov dl,0dh ;回车的ASCII码0DH入DL mov ah,2

int 21h

mov dl,0ah ;换行的ASSII码0AH入AH mov ah,2 int 21h

int 20 ;回主程序 crlf ends end

现在我们就可以将BINIHEX、DECIBIN及CRLF等模块合并成一个大程序了。首先,我们要将这三个模块子程序略加改动。然后,再写一段程序来调用每一个子程序。 crlf proc near; mov dl,0dh mov ah,2 int 21h mov dl,0ah mov ah,2 int 21h ret

crlf endp

类似SEGMENT与ENDS的伪指令,PROC与ENDP也是成对出现,用来识别并定义一个程序。其实,PROC 真正的作用只是告诉编译器:所调用的程序是属于近程(NEAR)或远程(FAR)。 一般的程序是由 DEBUG 直接调用的,所以用 INT 20 返回,用 CALL 指令所调用的程序则改用返回指令RET,RET会把控制权转移到栈顶所指的地址,而该地址是由调用此程序的 CALL指令所放入的。

各模块都搞定了,然后我们把子程序组合起来就大功告成 decihex segment ;主程序 assume cs:decihex org 100h

mov cx,4 ;循环次数入cx;由于子程序要用到cx,故子程序要将cx入栈 repeat: call decibin;调用十进制转二进制子程序 call crlf ;调用添加回、换行符子程序

call binihex ;调用二进制转十六进制并显示子程序 call crlf

loop repeat ;循环4次,可连续运算4次

mov ah,4ch ; 调用DOS21号中断4c号功能,退出程序,作用跟INT 20H int 21H ; 一样,但适用面更广,INT20H退不出时,试一下它 decibin proc near push cx ;将cx压入堆栈,; ┇ exit: pop cx ;将cx还原; retdecibin endp binihex proc near push cx ┇ pop cx retbinihex endp crlf proc near push cx ┇ pop cx retcrlf endpdecihex ends end CALL指令用来调用子程序,并将控制权转移到子程序地址,同时将CALL的下行一指令地址定为返回地址,并压入堆栈中。CALL 可分为近程(NEAR)及远程(FAR)两种:1.NEAR:IP的内容被压入堆栈中,用于程序与程序在同一段中。2.FAR:CS 、IP寄存器的内容依次压入堆栈中,用于程序与程序在不同段中。PUSH、POP又是一对指令用于将寄存

器内容压入、弹出,用来保护寄存器数据,子程序调用中运用较多。堆栈指针有个“后进先出”原则,像PUSH AX,PUSH BX…POP BX,POP AX这样才能作到保护数据丝毫不差。 汇编语言超浓缩教程到这要告一段落了,希望能奠定你独立设计的基础。而更多更好的技巧则全依赖你平时的积累了。祝你成功!

汇编语言程序设计习题

设计:吴启武 习题一

1、将下列二进制数化为十进制数和十六进制数:

1) 11010011B (2) 11100100B (3) 11111111B (4) 10000000B

2、试说明16位二进制表示的无符号整数和有符号整数所能表示的数值范围。 3、将下列十六进制数化为十进制数和二进制数: (1) 0D742H (2) 8765H (3) 0FFDCH (4) 2468H 4、设变量A的内容是8位二进制数 (1) 将变量A所有位清零和置1。

(2) 将变量A的第4位清零,第2位置1。 (3) 将变量A的各位求反。

5、将下列十进制数分别转换为非压缩的BCD码和压缩的BCD码: (1) 46 (2) 52 (3) 99 (4) 37

6、求下列十进制数对应的8位二进制补码的表示形式: (1) -50 (2) 30 (3) -58 (4) -128 习题二

1、指出下列指令的寻址方式:

(1) MOV CX,100 (2) MOV AX,25[SI]

(3) MOV [DI+BX],AX (4) ADD AX,ADDR (5) MUL BL (6) INC WORD PTR [BX+25] (7) SUB AX,[BP+6] (8) JMP BX (9) IN AL,20H (10) STI

2、指出下列指令中存储器操作数的物理地址的计算表达式: (1) MOV AL,[SI] (2) MOV AX,[BP+6]

(3) MOV 5[BX+DI],AX (4) INC BYTE PTR [BX+SI] (5) ADD AL,ES:[BX] (6) SUB AX,ALFA[SI] (7) JNC NEXT (8) MUL ALFA

习题三

1、判别下列语句是否有错并说明理由: (1) MOV [SI],?A? (2) MOV AL,BX (3) MOV BL,SI+2 (4) INC [BX]

(5) MOV 256,AL (6) MOV AX,BYTE PTR ALFA (7) MOV ALFA,BATA (8) MUL -25 (9) PUSH 20A0H (10) POP CS

2、请执行下段程序,给出各寄存器的内容:

MOV AX,0A0BH DEC AX

SUB AX,0FFH AND AX,00FFH MOV CL,3 SAL AL,CL ADD AL,25H XCHG AL,AH PUSH AX POP BX INC BL MUL BL

3、已知AX=003AH,请根据AX值用指令实现:使BL=03H,BH=0AH,CX=03H+0AH,DX=2*3AH,SI=0A3H,DI=0A03H。

4、在A地址处有100个数据,今要求传送到B地址处,请编程实现。

5、使AL高4位置1,判断低4位是否大于9,如大于9,则使低4位变反,否则将低4位置成9,试编程实现之。

6、在A、B地址起各有4个字节单元的无符号数,试编程实现二个无符号数的和,并将值存于C址起的单元中。

7、阅读下面程序并说明其功能: LEA SI,A

LP: IN AL,20H AND AL,0FH OR AL,30H CMP AL,?*? JZ ENDPR MOV [SI],AL INC SI

OUT 30H,AL JMP LP

ENDPR: HLT

8、在A字单元有一个有符号被除数,在B字单元有一个有符号除数,求其商存于C字单元中,余数存D字单元中,试编程实现之。

9、在A址起有一个50字节长的字符串,请查找串中含有最后一个“?”字符字节相对A址的距离(设串中含有多个“?”号)。 习题四

1、阅读下面数据搬移程序段,改正使用不当的语句。 A DB 35,47,2AH,?XYZ? B DB N DUP(0) N EQU $-A MOV SI,A MOV DI,B

MOV CX,LENGTH A LP: MOV AX,[SI]

MOV [DI],AX INC SI INC DI DEC CX LOOP LP

2、下面为一个定义数据的段,请图示它们在存储器中的存放形式。 DATA SEGMENT A DB 1,2,3,4 B DB ?ABCD? C DW 4 DUP(0) N EQU 12

X DW 33020AH Y DD 0ABCDH DATA ENDS

3、按上题给出的数据结构,求下面表达式的值: SEG A、OFFSET A、TYPE A、SIZE A、LENGTH A SEG C、OFFSET C、TYPE C、SIZE C、LENGTH C 4、请编程实现由键盘输入你的名字(拼音名),并把它显示在屏幕上,在你的名字两端各加上三个“*”号,如“***LIMING***”。

5、对下面程序进行注释,并说明其所实现的功能。 DATA SEGMENT A DB ?123ABC? DATA ENDS

CODE SEGMENT

ASSUME CS:CODE,DS:DATA ST: MOV AX,DATA MOV DS,AX LEA BX,A MOV CX,6 MOV AH,2

LP: MOV AL,[BX] XCHG AL,DL INC BX INT 21H LOOP LP

MOV AH,4CH INT 21H CODE ENDS END ST

6、实现满足下面要求瓣各宏定义:

(1) 可对任一寄存器实现任意次数的左移操作。 (2) 任意两个单元中的数据相加存于第三个单元中。

(3) 对任意8位寄存器中的数据转为ASCII码并在屏幕上显示。 习题五

1、将在A单元中的二位16进制数拆成二个16位数,并分别转换为相应的ASCII码存于B及C单元中。

2、将A单元的内容求补,并与原内容相“与”,结果存入B单元中。

3、在ABC起的连续4个单元中放有4个无符号数。试编程实现第1、4二数求和,再减去第2数和第3数,结果存入XYZ起的单元中。

4、判PAR单元数据的奇偶性,当为奇数时置MARK单元为1,当为偶数时置MARK单元为0。

5、将A单元起的100个数据移到B单元起的存储区中,试用三种方法实现数据的转移。 6、把A单元起三个单元中的无符号数,按递增顺序重新排序,并放回原存储单元中。 7、在MN起的三个单元中存有三个字符,要求将第一个字符高4位清零,第二个字符右移4位,且移入第三个字符的低4位而保持第三个字符的高4位不变,试编程实现。 8、从键盘输入一个字符,根据其内容对X单元按下式赋值,请编程实现。

9、在A、B单元各有一个有符号数,从键盘输入一个字符,当其为“A”时,表示将该二个有符号数相加,结果存入C单元中。当收到“S”时,表示将该二个有符号数相减,结果也存入C单元中。当为非“A”或“S”时,则不做运算,置C单元为0FFH。 10、在数据段中有5组字符串(分别有不同含义)。根据键入序号(1-5)输出对应序号的字符串到屏幕。要求编写为循环程序,可键入不同序号并显示相应字符串。

11、在A数据区有一个25个字符的字符串,试编程实现按ASCII码值进行升序的排序。 12、在ARRAY址起有20个有符号数。试编程将其正数存于A起的单元中。负数存于B起的单元中,且显示正数及负数的个数。

13、有一个50个字符的串,试把其中小写字母改为大写字母,把数字改为“*”,其它字符不变。

14、统计一班30名学生成绩的等级(A:90-100,B:80-89,C:70-79,D:60-69,E:60以下)。统计结果分别存入A,B,C,D,E单元中。

15、从键盘上输入二个字符存A,B单元中,比较它们的大小写,并在屏幕显示:A>B或B>A。

16、对双字变量DV中的值为1的位进行统计,统计结果存入XN单元中。

17、有一个50个数字的数据区,统计其为偶数和奇数的数字各为多少,分别存入A和B单元中。

18、对A址起的30个字节长的字符串,删除其中的数字符,后续字符向前递补,剩余字符补空格字符,编程实现之。

19、将CSTRN起的50个字符的串,统计相同字符的字符数,找出相同字符数最多的字符,存于CMORE单元中。

20、数组A和B,各有20个数据,它们各已按升序排放。现要求将这两个数组合并为一个数组C,且要求其数据也按升序排放,试编程实现。

21、已知两组字符串,各有50个字符。找出同在两串中的字符存于第三组字符串中,并统计串长存于SL单元中。

22、在NUM单元起,存放有a,b,c,d 4个数,求((a*b+10)*10+c)*10+d运算,结果存入RESL字单元中(设结果小于16位数)。

23、通过平方表,完成下式运算:x=a2+b2。式中a、b为1-9间的数。

24、编程:(1) 将A址起的20个无符号数按升序排放在原址处。(2) 将已排好序的20个数,查找有否与AL中数值相同的数。(3) 有相同的数,显示“YES”,无相同的数,显示“NOT”。编写上述任务为子程序,并由主程序调用相应子程序完成。

25、在A址起有10个压缩型BCD数。(1) 将BCD数转为的ASCII码。(2) 将ASCII码在

屏幕上显示。试编写子程序及调用程序。 26、编程:(1) 键入某班学生(30人)的计算机考试成绩。成绩按学号(1-30)排放在SCORE数据区中。(2) 按考分降序排列,存到ORDER数据区中。(3) 在屏幕显示前三名学生的成绩。请编写主程序及三个子程序,实现调用关系。 习题六

1、写出将一个字节数据A输出到端口25H的指令。

2、编一段程序,它轮流测试两个设备的状态寄存器,只要一个状态寄存器的第0位为1,与其相应的设备就输入一个字符;如果其中任一状态寄存器的第3位为1,则整个输入过程就结束。两个状态寄存器的端口地址是0024H和0036H,与其相应的数据输入寄存器的端口为0026H和0038H,输入字符分别存入地址为BUFFER1和BUFFER2的存储区中。 3、指出类型15H的中断向量在存储器的地址单元。

4、CPU与输入输出设备之间的数据传送方式有哪几种?它们各有什么特点? 习题七

1、完成下面操作:

(1) 读当前光标位置,改变光标属性为反相显示。

(2) 移光标到屏幕左上角,且以闪烁方式显示一个梅花字符。

2、利用DOS功能调用,实现键盘屏幕人机对话。对话内容中的屏幕问话如下:“你叫什么名字?”、“你几岁了?”。再根据年龄进行分支,30岁以下为青年,可输出信息“啊!你很年青,前途无量!”。50岁以下为中年,可输出信息“啊!大展宏图的年纪,祝您事业成功!”。对50岁以上的人,可输出信息“硕果累累,祝你永远健康!”。

3、编程实现一个电子表。要求在屏幕的右上角开窗并显示时、分、秒。 4、在屏幕上显示一个“*”字符。要求背景颜色不断改变(间隔0.5秒),且“*”字符可在屏幕上无规则移动(速度0.1秒)。试编程实现。

5、利用ASCII码表,编制一架小飞机图形,在屏幕上自左向右不断地飞行的程序,要控制飞行的速度,在屏幕上每飞过一次约为5秒种。

6、试在图形方式下编写一个在屏幕上可绘制你的汉字名字的程序,并用不同的颜色显示每个汉字。

汇编语言程序设计习题参考答案 习题一

1、解:(1) 11010011B=211D=D3H (2) 11100100B=228D=E4H (3) 11111111B=255D=FFH (4) 10000000B=128D=80H 2、解:无符号整数范围:0-216-1,即0-65535。 有符号整数范围:-215-215-1,即-32768-32767 3、解:(1) 0D742H=1498178D=1101011101000010B (2) 8765H=1387109D=1000011101100101B (3) 0FFDCH=12030940D=1111111111011100B (4) 2468H=202856D=0010010001101000B 4、解:(1) 所有位清零: AND A,00H 所有位置1: OR A,0FFH (2) 第4位清零(0-7位): AND A,0EFH 第2位置1(0-7位): OR A,04H

(3) 各位求反: NOT A 或 XOR A,0FFH

5、解:(1) 46 非压缩的BCD码为:0406H;压缩的BCD码为:46H。

(2) 52 非压缩的BCD码为:0502H;压缩的BCD码为:52H。 (3) 99非压缩的BCD码为:0909H;压缩的BCD码为:99H。 (4) 37非压缩的BCD码为:0307H;压缩的BCD码为:37H。 6、解:(1) -50的8位二进制补码为: 11001110B或CEH (2) 30的8位二进制补码为: 00011110B或1EH (3) -58的8位二进制补码为: 11000110B或C6H (4) -128的8位二进制补码为: 10000000B或80H 习题二

1、解: (1) MOV CX,100 立即数寻址 (源) (2) MOV AX,25[SI] 变址寻址 (源)

(3) MOV [DI+BX],AX 基址变址寻址 (目的)

(4) ADD AX,ADDR ADDR为符号常量时,是立即数寻址; 为变量时,是直接寻址 (源) (5) MUL BL 寄存器寻址

(6) INC WORD PTR [BX+25] 基址寻址 (7) SUB AX,[BP+6] 基址寻址

(8) JMP BX 寄存器寻址(相对寻址) (9) IN AL,20H 端口寻址 (10) STI 隐含寻址

2、解: (1) MOV AL,[SI] PA=(DS)*10H+(SI) (2) MOV AX,[BP+6]

(2) MOV AX,[BP+6] PA=(SS)*10H+(BP)+6

(3) MOV 5[BX+DI],AX PA=(DS)*10H+(BX)+(DI)+5 (4) INC BYTE PTR [BX+SI] PA=(DS)*10H+(BX)+(SI) (5) ADD AL,ES:[BX] PA=(ES)*10H+(BX)

(6) SUB AX,ALFA[SI] PA=(DS)*10H+(SI)+ALFA (7) JNC NEXT PA=(CS)*10H+NEXT (8) MUL ALFA PA=(DS)*10H+ALFA 习题三

1、解: (1) MOV [SI],'A' 错,类型不明确,应改为: MOV BYTE PTR [SI],'A'

(2) MOV AL,BX 错,类型不匹配 (8位与16位) (3) MOV BL,SI+2 错,应改为 MOV BL,[SI+2] (4) INC [BX] 错,类型不明确,应改为: INC BYTE PTR [BX] 或 INC BX

(5) MOV 256,AL 错,立即数只能作源操作数, 且256也超出了一个字节范围

(6) MOV AX,BYTE PTR ALFA 错,类型不一致

(7) MOV ALFA,BATA 错,MOV指令不允许两个存储器间直接传送 (8) MUL -25 错,不能为有符号数;也不能为立即数 (9) PUSH 20A0H 错,不能为立即数 (10) POP CS 错,POP CS 非法

2、解: MOV AX,0A0BH (AX)=0A0BH

DEC AX (AX)=0A0AH

SUB AX,0FFH (AX)=090BH AND AX,00FFH (AX)=000BH MOV CL,3 (CL)=03H, (AL)=0BH SAL AL,CL (AL)=58H ADD AL,25H (AL)=7DH

XCHG AL,AH (AH)=7DH, (AL)=00H PUSH AX (AX)=7D00H POP BX (BX)=7D00H

INC BL (BL)=01H, (AL)=00H MUL BL (AX)=(AL)*(BL)=0000H

3、解: (1) MOV CL,4 MOV DX,AX SHR AX,CL (5) MOV SI,AX MOV BL,AL MOV CL,4

(2) MOV BX,AX SHR AX,CL AND BX,0FF0FH AND SI,0FF0FH XCHG BH,BL SHL SI,CL (3) MOV BX,AX ADD SI,AX MOV CL,4 (6) MOV DI,AX SHR BX,CL MOV CL,4 MOV CX,BX SHR AX,CL

AND AX,0FF0FH AND DI,0FF0FH ADD CX,AX MOV CL,08H (4) MOV BL,2 SHL DI,CL

MUL BL ADD DI,AX 4、解法一: 解法二:

dseg segment dseg segment

a db 1,2,3,4,5,6,7,8,9,10 a db 1,2,3,4,5,6,7,8,9,10 b db 10 dup(?) b db 10 dup(?) n equ 10 n equ 10 dseg ends dseg ends

cseg segment cseg segment

assume cs:cseg,ds:dseg assume cs:cseg,ds:dseg start: mov ax,dseg start: mov ax,dseg mov ds,ax mov ds,ax mov es,ax lea si,a lea si,a lea di,b lea di,b mov cx,n

mov cx,n atob: mov al,[si] cld mov [di],al rep movsb inc si

exit: mov ah,4ch inc di int 21h loop atob

cseg ends exit: mov ah,4ch

end start int 21h cseg ends end start 5、解:

dseg segment a db 0ah b db ? dseg ends cseg segment

assume cs:cseg,ds:dseg start: mov ax,dseg mov ds,ax mov al,a

or al,0f0h ; 高四位置1

cmp al,0f9h ; 判低四位是否大于9 ja a9 ; 是,转A9

mov al,0f9h ; 不是,将低四位置成9 jmp next

a9: xor al,0fh ; 使低四位变反 next: mov b,al ; 结果存B单元 exit: mov ah,4ch int 21h cseg ends end start 6、解:

dseg segment

a db 11h,22h,33h,44h b db 33h,0ffh,11h,00h c dw 2 dup(?) dseg ends cseg segment

assume cs:cseg,ds:dseg start: mov ax,dseg mov ds,ax

mov ax,word ptr a ; 取A单元低位字 add ax,word ptr b ; 加上B单元低位字 mov c,ax ; 将和低位字存于C单元

mov ax,word ptr a+2 ; 取A单元高位字

adc ax,word ptr B+2 ; 带进位加上B单元高位字 mov c+2,ax ; 将和高位字存于C+2单元 exit: mov ah,4ch int 21h cseg ends end start

7、解:

LEA SI,A ; A的首址送SI

LP: IN AL,20H ; 取20H端口地址的字节到AL AND AL,0FH ; 清AL的高4位 OR AL,30H ; 转换为ASCII码 CMP AL,'*' ; 是字符'*'吗? JZ EDNPR ; 是,则转ENDPR

MOV [SI],AL ; 不是,将AL中的字符送以A为起址的存储单元 INC SI ; 指向下一存储单元

OUT 30H,AL ; 将AL的字节数据送30H端口 JMP LP ; 转LP重复 ENDPR: HLT ; 停机

功能:取端口20H的数据,将其转换成ASCII码送地址A开始的单元及 端口30H,直至遇'*'结束。 8、解:

dseg segment a dw -58 b dw 7 c dw ? d dw ? dseg ends

sseg segment stack db 200 dup(?) sseg ends cseg segment

assume cs:cseg,ds:dseg,ss:sseg start: mov ax,dseg mov ds,ax mov ax,a

cwd ; 扩展运算符号位到DX idiv b

mov c,ax ; 存商 mov d,dx ; 存余数 exit: mov ah,4ch int 21h cseg ends end start 9、解:

dseg segment

a db 'abcde?12345?fghij?67890?klmno?09876?pqrst?54321?uv' n equ 50

addr db ? dseg ends

cseg segment

assume cs:cseg,ds:dseg start: mov ax,dseg mov ds,ax mov es,ax

lea di,a ; 置目的串指针 mov cx,n ; 置串长计数器 mov al,'?' ; 找字符'?' cld ; 清DF,增量方式 agn: scasb ; 查找

jz found ; 找到转FOUND

loop agn ; 未找到又没找完,再找 jmp ovr ; 找完又未找到,转OVR found: mov bx,n

sub bx,cx ; 找到后,将'?'值距A址值存BX loop agn ; 未找完,继续找

ovr: mov addr,bl ; 最后一个'?'距A址值趣ADDR exit: mov ah,4ch int 21h cseg ends end start 习题四

1、解: 改正后的程序段如下: a db 35,47,2ah,'XYZ' n equ $-a b db n dup(0) ......

mov si,offset a mov di,offset b mov cx,n lp: mov al,[si] mov [di],al inc si inc di loop lp

2、解:

┌────┐ ┌────┐

A │ 01H │ │ 00H │ ├────┤ ├────┤

│ 02H │ │ 00H │ ├────┤ ├────┤

│ 03H │ │ 00H │

├────┤ ├────┤

│ 04H │ │ 00H │ ├────┤ ├────┤

B │ 41H │ X │ 21H │ ├────┤ ├────┤

│ 42H │ │ 00H │ ├────┤ ├────┤

│ 43H │ │ 0AH │ ├────┤ ├────┤

│ 44H │ │ 02H │ ├────┤ ├────┤

C │ 00H │ Y │ CDH │ ├────┤ ├────┤

│ 00H │ │ ABH │ ├────┤ ├────┤

│ 00H │ │ 00H │ ├────┤ ├────┤

│ 00H │ │ 00H │ └────┘ └────┘

3、解: SEG A=DATA SEG C=DATA OFFSET A=0000H OFFSET C=0008H TYPE A=1 TYPE C=2 SIZE A=1 SIZE C=8

LENGTH A=1 LENGTH C=4 4、解: dseg segment

star db '***$' ; 以'$'结尾的字串'***' nam db 21 ; 缓冲区大小

db ? ; 存放实际输入字符的个数 db 20 dup(0) ; 存放输入的名字串 dseg ends

sseg segment stack db 200 dup(?) sseg ends

cseg segment

assume cs:cseg,ds:dseg,ss:sseg start: mov ax,dseg mov ds,ax

lea dx,nam ; 输入名字串到NAM缓冲区 mov ah,10 int 21h

lea si,nam+2 ; 取名字串在缓冲区的地址 mov cl,nam+1 ; 取名字串的长度

mov ch,0

lea dx,star ; 显示三个'*' mov ah,9 int 21h

lp: mov dl,[si] ; 显示名字 mov ah,2 int 21h inc si loop lp

lea dx,star ; 显示三个'*' mov ah,9 int 21h mov ah,4ch int 21h cseg ends end start

exit: mov ah,4ch int 21h cseg ends end start

5、解:

DATA SEGMENT