熟练掌握动态调试程序DEBUG的使用;
1、硬件环境:微机CPU 486以上,500MB以上硬盘,32M以上内存;
2、软件环境:装有MASM 5.0、Debug、LINK、EDIT、CREF.EXE和EXR2BIN.EXE等应用程序。
通过编辑、汇编和连接后的程序是可以执行的程序。但是,一个程序特别是比较复杂的程序不能保证没有一点错误。因此,在投入正式运行前必须进行调试,以检查程序的正确性。调试程序Debug就是用来调试汇编语言程序的一种工具。Debug的主要功能有显示和修改寄存器及内存单元的内容;按指定地址启动并运行程序;设置断点使程序分段运行,以便检查程序运行过程中的中间结果或确定程序出错的位置;反汇编被调试程序,它将一个可执行文件中的指令机器码反汇编成助记符指令并同时给出指令所在的内存地址;单条追踪或多条追踪被调试程序,它可以逐条指令执行或几条指令执行被调试程序,每执行一条(或几条)指令后,Debug程序将中断程序的运行并提供有关结果信息;汇编一段程序,在Debug的汇编命令下可以直接输入助记符指令,并将其汇编成可运行程序段。此外,Debug还可以将磁盘指定区的内容或一个文件装入到内存或将内存的信息写到磁盘上等等。
启动Debug程序:在DOS状态下可以用下面的命令启动Debug程序:
DEBUG [路径文件名.扩展名]
Debug后面的文件名及路径是指被调试程序的文件名及路径,Debug后面的文件必须是程序的可执行文件,其扩展名可以是.EXE 或.COM。在此命令后,DOS将调试程序Debug调入内存,Debug接着将被调程序送入内存。比如:DEBUG 123.EXE
调试程序Debug的主要命令如下:
(一)显示内存单元内容的命令D
格式(1):-D 地址
从指定地址开始,显示128个字节的内容,每一行的左边显示段内偏移地址,接着显示16个单元的内容,最右边区域则显示这一行的16个单元所对应的可显示的字符。若无可显示的字符,则用圆点(小数点)填充。
D命令中的地址可为段内偏移量,也可为段基址和段内偏移量两部分,中间用冒号隔开,如1680:0110,即指段基址为1680H,段内偏移量为0110H。Debug中所显示的数据均为十六进制数,且省去了后面的H标志。
格式(2):-D 范围
将显示指定地址范围内的内存单元的内容,起始地址可由段基址及段内偏移量两个部分组成,中间用冒号“:”隔开,也可以只指出段内偏移量,而此时的段基址在DS中。这里所说的范围包含起始地址和结束地址。
比如 –D DS:1000 1020
将显示数据段偏移地址为1000H到1020H的内容。
(二)修改内存单元内容的命令E
格式(1):-E 地址 内容表
它的功能是用给定的内容表去代替所指定的内存单元的内容。
例如:
E DS:0110 41 ‘CLOSE’ 41
该命令执行后,将用列表中的7个字符填入从DS:0110 到DS:0116 的7个存储单元中。
格式(2):E 地址
它的功能是可以连续地逐个修改内存单元的内容。当屏幕上显示指定单元的地址和内容之后,可采取下列办法:
①若指定单元的内容需要修改,则将新的内容的十六进制数输入,再按空格键,修改便告完成,然后显示下一个存储单元的地址及内容,若需要修改,可进行同样的操作。若某一个单元的内容不需要修改,而操作又要进行下去,则可直接按空格键。
②若需要显示前一个单元的地址和内容,则输入连接号′-′,若要修改,则输入新的内容;若显示前一个单元的地址和内容仍要修改,则可进行同样的操作;若显示的内容不需要修改,则可直接按′-′键,使该操作由高地址向低地址单元连续不断地进行。
③按
(三)检查和修改寄存器内容的命令R
格式(1):R
此时将显示所有寄存器的内容和全部标志位的状态,以及现行CS:IP所指的机器指令代码和反汇编符号。
格式(2):R 寄存器名
该格式可用于检查和修改指定寄存器的内容。若不修改其内容,可按
格式(3):RF
该格式可用于显示标志和修改标志位状态。
当系统给出标志位状态后,可采取下列办法:
①若不需要修改任一标志位,可按
②若需要修改一个或多个标志位,可输入其相反的值。各标志位之间可以无空格且与顺序无关,修改后按
由于标志位状态显示时,是用下列特殊符号表示的,因而修改时,只要输入规定的符号即可。下面是标志名和状态符号的对照表:
标志名置 位符号复 位符号
溢出标志OF(是/否) OV NV
方向标志DF(减/增) DN UP
中断标志IF(允许/禁止) EI DI
符号标志SF(负/正) NG PL
零标志ZF(是/否) ZR NZ
辅助进位标志AF(是/否) AC NA
奇偶校验标志PF(偶/奇) PE PO
进位标志CF(是/否) CY NC
只有追踪标志TF,不能用指令直接修改。
例如:输入RF命令,系统可能作出如下响应:
OV DN EI NG ZR AC PE CY-
若现在要修改奇偶、零、中断和溢出标志位,可在光标处输入:
PO NZ DI NV
(四)运行程序命令G
格式:G [=地址][地址[地址…]]
该命令可以在程序运行中设置断点。它是Debug程序进行程序调试的主要命令之一。
示例:-g 001a 则执行从当前cs:ip至001a的指令,注意:地址设置必须从指令的第一字节设起。
①第一个参数“=地址”规定了程序执行的起始地址,以CS内容作段地址,等号后面的地址只需给出地址偏移量。此时,命令G与地址之间的等号不能省去。
如果在G命令执行前,已经设置了CS值和IP值,则也可以直接用G命令,从指定地址执行程序。
②格式中后面给出的地址是指断点地址,最多可设置10个断点。当程序执行到一个断点时,就停下来,显示CPU各寄存器的内容和标志位的状态,以及下一条待执行的指令,被调试程序的所有断点全部被取消,并返回Debug。
③地址参数所指的单元,必须包含有有效的8088指令的第一个字节,否则将产生不可预料的结果。
④堆栈必须至少包含有6个可用字节,否则也将产生不可预料的结果。
⑤若断点地址只包括地址偏移量,则认为段地址在CS寄存器中。
(五)追踪命令T
格式(1):T [=地址]
该命令可以在指令执行中进行追踪,若略去地址,则从CS:IP现行值执行。每一次T命令都执行一条指令。
格式(2):T [=地址][值]
此时,它可对多条指令进行追踪,即在执行了由值所指定的若干条指令之后,停止执行并显示各寄存器的内容和各标志位,还指出下一条待执行的指令。
(六)汇编命令A
若在调试目标程序的过程中,要求改写或增添一段目标程序,则可以用A命令直接在Debug下实现。
格式:A [地址]
该命令可以从指定地址开始,将输入的汇编语言语句立即汇编成机器代码,连续存放在内存单元中。在程序输入完毕后,最后一行不输入内容,直接按回车键,即可返回DEBUG程序,还可用反汇编命令U对刚输入的内容进行反汇编,以验证输入的程序是否正确。
使用A命令应遵守以下规则:
① 所有输入数值,均为十六进制数。
② 前缀助记符,必须在相关指令的前面输入,可以在同一行,也可以在不同行输入。
③ 段超越助记符为CS:、 DS:、 ES:、 SS:。
④ 远调用时的返回指令助记符用RETF。
⑤ 使用串操作指令时,助记符中必须注明是字节还是字传送。
⑥ 汇编语言能自动汇编短、近和远的转移及近和远的调用,也能由NEAR和FAR前缀来超越。
例如:
0110:0600 JMP 602;短转移
0110:0602 JMP NEAR 605;近转移
0110:0605 JMP FAR 60A;远转移
第一条JMP指令中含有一个字节偏移量。
第二条JMP指令中含有两个字节偏移量。
第三条JMP指令中含有两个字节的偏移量及两个字节的段地址。
⑦ 当DEBUG不能确定某些操作数涉及的是字类型存储单元还是字节类型的存储单元时,在这种情况下,必须用前缀“WORD PTR”或“BYTE PTR”来加以说明。
例如:
NEG BYTE PTR [128]
DEC WORD [SI]
⑧ 当Debug不能确定一个操作数是立即数还是存储单元的地址时,可以把地址放在方括号中。
⑨ 两个最常用的伪指令DB和DW可以在A命令中使用,用来直接把字节或字的值送入相应的存储单元。
例如:
DB 2,5,3,4,′THIS IS AN EXAMPLE′
DW 6000, 2000, 7000,′BA′
⑩Debug支持所有形式的寄存器间接寻址命令。
例如:ADD BX,74[BP+3][SI-5]
POP[BX+DI]
(七)反汇编命令U
格式(1):U地址
该命令从指定的地址开始,反汇编32个字节。若略去指定地址,则以上一个U命令反汇编的最后一条指令地址的下一条指令地址作为起始地址;若没有用过U命令,则以由Debug初始化的段寄存器的值作段地址,以100作为地址偏移量。
格式(2):U范围这种格式的命令,可以对指定范围的内存单元进行反汇编,范围可以由起始地址、结束地址(只能包含地址偏移量)或起始地址及长度来指定。其命令格式如:
U 04BA:100 0108或 U 04BA:0100 L7
两者是等效的。
(八)输入命令I
格式:I端口地址
该命令从指定端口输入一个字节并显示。
例如: I 2E8
C C
它表示所显示的是从02E8 端口输入的一个字节为CC。
(九)输出命令O
格式:O端口地址 字节值
其功能是向指定的端口输出一个字节。
例如:O 2E8 12
它表示将一个字节12H送到输出端口2E8。
(十)命名命令N
格式:N文件标识符[文件标识符]
该命令用给定的两个文件标识符格式化在CS:5C和CS:6C的两个文件控制块中(若在调用Debug时具有一个文件标识符,则它已格式化在CS:5C的文件控制块中),文件控制块是将要介绍的装入命令L和写命令W所需要的。
N命令能把文件标识符和别的参数放至CS:81开始的参数保存区中。在CS:80中保存输入的字符个数, 寄存器AX保存前两个文件标识符中的驱动器标志。
例如:
A>DEBUG
N TEST
L
N命令后,用L命令可将TEST调入自己的CS:100开始的存储区中。若对正在调试的程序TEST进行调试时,需要用到其它的文件标识符及其它参数,也可用N命令加以实现。;
例如:
A>DEBUG TEST
N 文件1 文件2
(十一)装入命令L
格式1: L <地址> <驱动器号> <起始逻辑扇区> <所读扇区个数n>
其中<地址>的缺省值为CS:100。逻辑扇区可由物理扇区号换算得到,以双面双密度盘为例:物理扇区是按0面0道1区,0面0道2区,……,0面0道9区,0面1道1区,……,0面39道9区,1面0道1区,……,1面39道9区排列。而逻辑扇区与物理扇区号的对应关系为物理扇区0面0道1扇区至9扇区,逻辑扇区号为0—8;物理扇区1面0道1扇区至9扇区,逻辑扇区号为9—11H;物理扇区0面1道1扇区至9扇区,逻辑扇区号为12—1AH;……。这样每道先0面后1面一直排下去。
其中<驱动器号>为0、1或2,0表示A驱,1表示B驱,2表示硬盘。
功能: 将<驱动器号>指定的盘上,从<起始逻辑扇区>起,共n个逻辑扇区上的所有字节顺序读入指定内存地址开始的一片连续单元。当L后的参数缺省时,必须在L之前由N命令指定(或进入DEBUG时一并指出)所读驱动器文件名。此时L执行后将该文件装入内存。
例如:-N EXAMPLE
-L
将当前驱动器上的EXAMPLE文件装入CS:100起始的一片内存单元。
格式2:L地址或L
该命令把已在CS:5C中格式化的文件控制块所指定的文件装入到指定区域中。
若省略地址,则装入到CS:100开始的内存区域中。
若是带有扩展名.COM或.EXE文件,无论命令中是否指定了地址,一律装入到CS:100开始的内存区域中去。
通常在BX和CX中包含了所读入文件的字节数,但对具有扩展名.EXE文件,则BX和CX中还包含实际程序长度。
(十二)写命令W
功能: 为L/W命令指定待装入/写盘文件
格式1:W <地址> <盘号> <起始逻辑扇区> <所写逻辑扇区数n>
功能:与L命令不同的地方是将内存从<地址>起始的一片单元内容写入指定扇区。只有W而没有参数时,与N命令配合使用将文件写盘。该命令把由地址所指定的内存区域中的数据写入指定的驱动器。若地址中只包含偏移量,则段地址在CS中。
其中,扇区号决定了写入起始扇区;区段数决定了写入的区段个数;扇区号和区段数均用十六进制数表示。
格式2:W地址或W
该命令把指定内存区域中的数据,写入到由CS:5C处的文件控制块所规定的文件中去。若省略地址,则内存区域从CS:100 开始。
对于扩展名为.EXE或.HEX文件不能写入。因为这些文件的写入要用一种特殊格式,而此格式Debug程序不支持。
(十三)退出Debug命令Q
格式:Q
该命令退出Debug程序并返回DOS。
Q命令并不把内存中的文件存盘,若需要存盘的话,应在退出前用W命令写入磁盘。
1、实验内容
(1) 进入和退出Debug程序
(2) 学会Debug中的D命令、E命令、R命令、T命令、A命令、G命令、U命令、N命令、W命令等的使用。
(3) 利用Debug,验证乘法、除法、加法、减法、带进位加、带借位减、堆栈操作指令、串操作指令的功能。
(4)使用Debug调试程序调试汇编程序。
2、实验步骤
(1) 在DOS提示符下,进入Debug程序。
(2) 详细记录每一步所用的命令,以及查看结果的方法和具体结果。
(3)现有一个双字加法源程序如下,其中存在错误。现假设已汇编、连结生成了可执行文件HB.EXE,存放在d:\MASM目录下。请使用Debug对其进行调试。
Code SEGMENT
ASSUME CS:code,DS:code
ORG 100H ;从100H处开始存放下列指令
Start:MOV AX,code ;将DS置成code段的首地址
MOV DS,AX
MOV SI,200H ;取第一个数的首地址
MOV AX,[SI] ;将第一个数的低16位取到AX
MOV DI,204H ;取第二个数的首地址
ADD AX,[DI] ;第一个数和第二个数的低16应相加
MOV [SI+8],AX ;低16位相加的结果送到208H和209H单元
MOV AX,[SI+2] ;取第一个数的高16位送到AX中
ADD AX,[DI+2] ;两个数的高16位相加
MOV [SI+0AH],AX ;高16位相加的结果送到20AH,20BH单元
MOV AX,4C00H ;使用DOS的4CH号功能调用
INT 21H ;进入功能调用,返回DOS
ORG 200H ;从200H处开始存放下列数据
DD 12345678h,654387A9h,0h ;被加数、加数、和
Code ENDS
END start
调试过程:
① 进入Debug并装入可执行文件HB.EXE
D:\MASM>Debug HB.EXE
-
② 观察寄存器初始状态
-R
AX=0000 BX=0000 CX=020C DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=1892 ES=1892 SS=18A2 CS=18A2 IP=0100 NV UP EI PL NZ NA PO NC
18A2:0100 B8A218 MOV AX,18A2
注:(1)以上显示的寄存器值,可能和你的电脑显示的不一样。
(2)Debug中默认的进制是十六进制。
③ 以单步工作方式开始运行程序
首先用T命令顺序执行用户程序的前l两条指令,将段寄存器DS预置为用户的数据段。
-T
AX=18A2 BX=0000 CX=020C DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=1892 ES=1892 SS=18A2 CS=18A2 IP=0103 NV UP EI PL NZ NA PO NC
18A2:0103 8ED8 MOV DS,AX
-T
AX=18A2 BX=0000 CX=020C DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=18A2 ES=1892 SS=18A2 CS=18A2 IP=0105 NV UP EI PL NZ NA PO NC
18A2:0105 BE0002 MOV SI,0200
④ 观察用户程序数据段初始内容
-D 200 20F
18A2:0200 78 56 34 12 A9 87 43 65-00 00 00 00 00 74 13 50 xV4...Ce.....t.P
-
⑤ 连续工作方式运行程序至返回DOS前(设断点),查看运行结果。为此,现使用U命令反汇编。
-U 100
18A2:0100 B8A218 MOV AX,18A2
18A2:0103 8ED8 MOV DS,AX
18A2:0105 BE0002 MOV SI,0200
18A2:0108 8B04 MOV AX,[SI]
18A2:010A BF0402 MOV DI,0204
18A2:010D 0305 ADD AX,[DI]
18A2:010F 894408 MOV [SI+08],AX
18A2:0112 8B4402 MOV AX,[SI+02]
18A2:0115 034502 ADD AX,[DI+02]
18A2:0118 89440A MOV [SI+0A],AX
18A2:011B B8004C MOV AX,4C00
18A2:011E CD21 INT 21
-
可见,要执行10条指令,至011B处停止
-G=100,011B
AX=7777 BX=0000 CX=020C DX=0000 SP=0000 BP=0000 SI=0200 DI=0204
DS=18A2 ES=1892 SS=18A2 CS=18A2 IP=011B NV UP EI PL NZ NA PE NC
18A2:011B B8004C MOV AX,4C00
-D 200 20F
18A2:0200 78 56 34 12 A9 87 43 65-21 DE 77 77 43 43 83 06 xV4...Ce!.wwCC..
-
和为7777DE21H,正确。
⑥ 再取一组数据,查看运行结果。为此,首先用E命令修改数据。
-E 200 CD,AB,78,56,90,EF,34,12
-D 200 20F
18A2:0200 CD AB 78 56 90 EF 34 12-21 DE 77 77 43 43 83 06 ..xV..4.!.wwCC..
-G=100,11B
AX=68AC BX=0000 CX=020C DX=0000 SP=0000 BP=0000 SI=0200 DI=0204
DS=18A2 ES=1892 SS=18A2 CS=18A2 IP=011B NV UP EI PL NZ NA PE NC
18A2:011B B8004C MOV AX,4C00
-D 200 20F
18A2:0200 CD AB 78 56 90 EF 34 12-5D 9B AC 68 43 43 83 06 ..xV..4.]..hCC..
-
和为68AC9B5DH,错误。说明程序有问题。
⑦ 再将断点设在完成低位字加法后,查看运行结果。
-G=100,112
AX=9B5D BX=0000 CX=020C DX=0000 SP=0000 BP=0000 SI=0200 DI=0204
DS=18A2 ES=1892 SS=18A2 CS=18A2 IP=0112 NV UP EI NG NZ NA PO CY
18A2:0112 8B4402 MOV AX,[SI+02] DS:0202=5678
-D 200 20F
18A2:0200 CD AB 78 56 90 EF 34 12-5D 9B AC 68 43 43 83 06 ..xV..4.]..hCC..
-
低位和为9B5D,正确。说明错误可能出在后面
⑧ 使用T命令从刚才的断点处向后单步调试,查看运行结果。
-T=112
AX=5678 BX=0000 CX=020C DX=0000 SP=0000 BP=0000 SI=0200 DI=0204
DS=18A2 ES=1892 SS=18A2 CS=18A2 IP=0115 NV UP EI NG NZ NA PO CY
18A2:0115 034502 ADD AX,[DI+02] DS:0206=1234
-T
AX=68AC BX=0000 CX=020C DX=0000 SP=0000 BP=0000 SI=0200 DI=0204
DS=18A2 ES=1892 SS=18A2 CS=18A2 IP=0118 NV UP EI PL NZ NA PE NC
18A2:0118 89440A MOV [SI+0A],AX DS:020A=68AC
-
AX寄存器的结果为68AC,而应为68AD。可见是本条加法指令使用错误,这里应使用带进位加法指令。
⑨ 使用A命令装入正确指令后再次运行,察看结果。
-A 115
18A2:0115 ADC AX,[DI+02]
18A2:0118
-G=100,11B
AX=68AD BX=0000 CX=020C DX=0000 SP=0000 BP=0000 SI=0200 DI=0204
DS=18A2 ES=1892 SS=18A2 CS=18A2 IP=011B NV UP EI PL NZ NA PO NC
18A2:011B B8004C MOV AX,4C00
-D 200 20F
18A2:0200 CD AB 78 56 90 EF 34 12-5D 9B AD 68 43 43 83 06 ..xV..4.]..hCC..
-
和为68AD9B5DH,正确。对于这样一个简单程序一般来说不会再有问题。退出后修改源程序即可。
⑩ 退出
-Q
D:\>
需要说明的是此程序很简单,只需使用T命令逐条单步调试即可。本例采用的调试方法似乎过于繁琐,但这是为了说明程序调试的一般方法,以便读者调试复杂程序时借鉴。
1、实验要求
(1)熟练掌握Debug的命令;
(2)熟练掌握使用Debug调试汇编程序;
(3)回答思考问题;
(4)记录实验结果。
2、实验提示
一般使用Debug调试汇编程序的步筹如下:
(1) 调用DEBUG,装入用户程序
(2) 观察寄存器初始状态
(3) 以单步工作方式开始运行程序
(4) 观察用户程序数据段初始内容
(5) 继续以单步工作方式运行程序
(6) 连续工作方式运行程序
(7) 修改程序和数据
(8) 运用断点调试程序
(1) 如何启动和退出Debug程序。
(2) 整理每个Debug命令使用的方法,实际示例及执行结果。
(3) 启动Debug后,要装入某一个.EXE文件,应通过什么方法实现?
(4) 用Debug调试程序时,如何设置断点?
⑸编写计算下面函数值的程序:
设输入数据为X、输出数据Y,且皆为字节变量,使用Debug查看X、Y两个变量的数据。
⑹分类统计字数组data中正数、负数和零的个数,并分别存入内存字变量Positive、Negative和Zero中,数组元素个数保存在其第一个字中。使用Debug查看Positive、Negative和Zero三个变量的数据。
;CH1EX1.ASM
编写计算下面函数值的程序:
设输入数据为X、输出数据Y,且皆为字节变量程序如下:
源程序如下:
DATA SEGMENT
X DB -10
Y DB ?
DATA ENDS
STACK SEGMENT STACK
DB 200 DUP(0)
STACK ENDS
CODE SEGMENT
ASSUME DS:DATA,SS:STACK,CS:CODE
START: MOV AX,DATA
MOV DS,AX
CMP X,0 ;与0进行比较
JGE A1 ;X≥0转A1
MOV Y,-1 ;X <0时,-1→Y
JMP EXIT
A1: JG A2 ;X>0转A2
MOV Y,0 ;X=0时,0→Y
JMP EXIT
A2: MOV Y,1 ;X>0,1→Y
EXIT: MOV AH, 4CH
INT 21H
CODE ENDS
END START
***********************************
Debug ch1ex1.exe
-u
可以知道该程序执行的最后一条指令的地址是0B5B:0038
键入如下两条命令,第一条命令是使程序运行到0B5B:0038结束,第二条命令是显示数据段的内容,就可以显示X、Y的值了。
-G 0038
-D DS:0
;CH1EX2.ASM
;分类统计字数组data中正数、负数和零的个数,并分别存入内存字变量Positive、Negative和Zero中,数组元素个数保存在其第一个字中。
源程序如下:
DATA1 SEGMENT
Data DW 10
DW 2130, -43, 31, -321, -1234, 345, 0, 3213, 0, 5477
Positive DW 0
Negative DW 0
Zero DW 0
DATA1 ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA1
START:MOV AX, DATA1
MOV DS, AX
Mov AX,0 ;用AX来对正数计数
Mov BX,0 ;用BX来对负数计数
Mov DX,0 ;用DX来对零计数
MOV CX, data ;用CX来进行循环计数
JCXZ save ;考虑数组的元素个数为0的情况
LEA SI, data+2 ;用指针SI来访问整个数组
again: CMP word ptr [SI], 0
JL lower
JE equal
INC AX
JMP loop1
lower: INC BX
JMPloop1
Equal :INC DX
loop1:ADD SI, 2
LOOP again
Save :MOV Positive, AX ;把各类的统计数保存到内存单元中
MOV Negative, BX
MOV Zero, DX
MOV AH, 4CH
INT 21H
CODE ENDS
END START
使用Debug查看Positive、Negative和Zero三个变量的数据的方法,参照上面的方法就可以了。