DOS汇编DEBUG基本命令及其功能详解

2022-07-20 14:31:25 浏览数 (1)

【目的】

掌握DEBUG 的基本命令及其功能掌握win7 win8 使用DEBUG 功能

【调试步骤】

  1. 使用Debug,将程序段写入内存,逐条执行,观察每条指令后cpu 中相关寄存器内容 的变化
  2. 将指令写入内存单元中,计算2 的8 次方。
  3. 查看内存中的内容,并试图修改
  4. 用机器指令和汇编指令编程

【具体内容】

【一】查看 CPU 和内存,用机器指令和汇编指令编程

1.预备知识:

**Debug总结:**它是DOS、Windows 都提供的实模式(8086 方式)程序的调试工具。使用它,可以查看CPU 各种寄存器中的内容、内存的情况和在机器码级跟踪程序的运行。

常用的Debug功能总结:

命令

功能

R 命令

查看、改变CPU 寄存器的内容

D 命令

查看内存中的内容

E 命令

改写内存中的内容

U 命令

将内存中的机器指令翻译成汇编指令

T 命令

执行一条机器指令

A 命令

以汇编指令的格式在内存中写入一条机器指令

Debug的方法总结:

(1) Debug之前需要进入到DOS方式:

a.重新启动计算机,进入DOS 方式,此时进入的是实模式的DOS。

b.在Windows 中进入DOS 方式,此时进入的是虚拟8086 模式的DOS。

(2)用R命令查看、改变CPU寄存器的内容

a. 显示CPU内部所有寄存器内容和标志位状态;格式为:-R

首先输入debug进入debug模式,然后输入命令-R查看CPU内部所有寄存器内容和标志位状态如上图所示。

b. 显示和修改某个指定寄存器内容,格式为:-R 寄存器名 若要修改一个寄存器中的值,比如AX中的值,可用R命令后加寄存器名来进行,输入“r ax”后按Enter键,将出现“:”作为输入提示,在后面输入要写入的数据后按Enter键,即完成了对AX中内容的修改。若想看一下修改的结果,可再用R命令查看

这里我修改了寄存器AX的值,令其值为0110,再使用R命令查看各个寄存器中的内容,结果证明修改成功。

(3)用Debug的D命令查看内存中的内容

a.格式:-d 段地址:偏移地址,Debug将列出从指定内存单元开始的128个内存单元的内容。

使用D命令,Debug将输出3部分内容,如图所示。

通过查阅可知,中间是部分从指定地址开始的128个内存单元的内容,用十六进制的格式输出,每行的输出从16的整数倍的地址开始,最多输出16个单元的内容。左边是每行的起始地址。右边是每个内存单元中的数据对应的可显示的ASCII码字符。

b.格式:-d 段地址:起始偏移地址 结尾偏移地址,Debug 将列出指定范围的内存单元的 内容。

此处使用命令-D 073F:0100 0136,将起始偏移地址定义为0100,结尾偏移地址定义为0136,查询到了指定范围的内存单元的内容。

(4)用Debug 的E 命令改写内存中的内容

a.格式:-e 起始地址 数据 数据 数据,如要将1000:0 开始的10 个内存单元修改为0~9,可以用”-e 1000:0 0 1 2 3 4 5 6 7 8 9”

使用命令 -E 073F :0110 00 01 02 20 10 20 30,修改了指定位置的内存中的数据。

b.格式:-e 起始地址,逐个单元相继地修改。 如:-e 1000:10 1000:0010 6D.0 61.1 72.2 6B.1c 输入e 1000:10 ,Debug 显示起始地址1000:0010,和1000:0010单元的原始内容:6D,然后光标停在“.”的后面提示输入想要写入数据,输入数据0,然后按空格键,即用输入的数据0改写了当前的内存单元。当前单元处理完成后,Debug将接着显示下一个内存单元的原始内容,并提示读者进行修改,可以用同样的方法处理。改写完毕后,按Enter键,E命令操作结束。

​ 可以用E命令向内存中写入字符,比如:用E命令从内存1000:0开始写入:数值1、字符“a”,数值2,字符“b”,数值3,字符“c”,可以用:“-e 1000:0 1 „a‟2 „b‟3 „c‟”, 修改的结果是,向1000:0、1000:2、1000:4单元中写入数值1、2、3,向1000:1、1000:3、1000: 5单元中写入字符“a”、“b”、“c”的ASCII码值:61H、62H、63H。

在这里我犯了一个错误,我直接将字母A、B、C输入,这里机器将它自动识别成为十六进制的符号了,而并没有将它的ASCII码值转换。

随后,我使用‘ ’将字符转义输入,最后显示其ASCII码值:41H,42H,43H.

​ 也可以用E命令向内存中写入字符串,比如:用E命令从内存1000:0开始写入:数值1、字符串“a b”、数值2、字符串“c ”、字符3、字符串“IBM”。 可以用:‘-e 1000:0 1 “a b” 2 “c ” 3 “IBM” ’。

从右边是每个内存单元中的数据对应的可显示的ASCII码字符中可证明,字符串最终以ASCII码的形式存储进内存单元中。

可以用E命令向内存中写入机器码 比如要从内存1000:0单元开始写入这样一段机器码:

代码语言:javascript复制
;机器码 对应的汇编指令
b80100 mov ax,0001
b90200 mov cx,0002
01c8   add ax, cx
;可用:-e 1000:0 b8 01 00 b9 02 00 01 c8

由结果可知,该机器码可以生效。

(5)用U命令查看写入的或内存中原有的机器码所对应的汇编指令

a. -U 段地址:偏移地址

该命令从指定地址开始,反汇编32个字节,若地址省略,则从上一个U命令的最后一条指令的下一个单元开始显示32个字节。

通过-U命令查看反汇编指令如上。

b. -U地址范围

该命令对指定范围的内存单元进行反汇编

在这里指定反汇编从073F:010E开始,通过与上图的对照可知,反汇编结果正确。

(6)使用T命令,可以执行CS:IP指向的指令,格式:-t,指令执行后,Debug显示输出CPU中寄存器的状态。

通过命令-t,执行该指令并显示输出CPU中寄存器的状态。

(7) 用Debug的A命令以汇编指令的形式在内存中写入机器指令。

a.格式:-A 段地址:偏移地址 该命令从指定地址开始允许输入汇编语句,把它们汇编成机器代码相继存放在从指定地址开始的存储器中。

通过-A指令,允许从073F:011F开始输入汇编语句,通过-U指令证明,该语句汇编成机器代码相继存放在从指定地址开始的存储器中。

常用汇编程序指令总结:

指令

含义

mov

mov A,B 即B 值赋给A

add

add ax,bx 语意是ax = ax bx

sub

sub ax,bx 语意是ax = bx - ax

jmp

jmp 无条件跳转

2.源代码:

(1):使用Debug,将下面的程序段写入内存,逐条执行,观察每条指令执行后,CPU中相关寄存器中内容的变化。

代码语言:javascript复制
机器码        汇编指令
b8 20 4e    mov ax,4E20H ;将4E20H存入ax
05 16 14    add ax,1416H ;将ax中的值 1416H存入ax
bb 00 20    mov bx,2000H ;将2000H存入bx
01 d8       add ax,bx ;将ax bx的值存入ax
89 c3       mov bx,ax ;将ax中的值存入bx
01 d8       add ax,bx ;将ax bx的值存入ax
b8 1a 00    mov ax,001AH ;将001AH存入ax
bb 26 00    mov bx,0026H ;将0026H存入bx
00 d8       add al,bl ;将al bl的值存入al
00 dc       add ah,bl ;将ah bl的值存入ah
00 c7       add bh,al ;将bh al的值存入bh
b4 00       mov ah,0 ;将0存入ah
00 d8       add al,bl ;将al bl的值存入al
04 9c       add al,9CH ;将al 9CH的值存入al

(2)将下面3条指令写入从2000:0开始的内存单元中,利用这3条指令计算2的8次方。

代码语言:javascript复制
mov ax,1   ;将1存入ax(从2000:0开始的内存单元)
add ax,ax ;将ax ax的值存入ax
jmp 2000:0003 ; 跳转至地址2000:0003,即第二条指令
3.实验代码、过程、相应结果的说明和分析:
1.使用Debug,将源程序段写入内存,逐条执行,观察每条指令执行后,CPU中相关寄存器中内容的变化。

(1)首先我进入了debug,并将源代码写入内存,设置起始地址为073F:0100,如下所示:

其中不可忽视的是ip地址,由ip地址变化可知,IP=IP 所读指令的字节数。不同的指令对ip地址变化的影响程度不同。

(2)输入指令-t,执行CS:IP指向的指令,指令执行后,Debug显示输出CPU中寄存器的状态。如下图所示,可见已经执行第一条语句mov ax,4E20H,将4E20H存入ax。并准备执行add ax,1416H语句。

(3)输入指令-t,执行CS:IP指向的指令,指令执行后,由Debug显示输出CPU中寄存器的状态可知,执行了add ax,1416H语句。可以计算得知:1416 4E20=6236,即将ax中的值 1416H存入ax。

(4)执行mov bx,2000H ,即将2000H存入bx。如下图所示:

(5)执行add ax,bx ,即将ax bx的值存入ax。通过计算可知ax 中的值是6236 2000=8236,且bx 中的值不变。

(6)输入指令-t,执行mov bx,ax ;将ax中的值存入bx,且ax中的值不变。

(7)同样,执行add ax,bx ;将ax bx的值存入ax,这里ax=8236 8236=046C

(8)这几步,执行mov ax,001AH (将001AH存入ax),mov bx,0026H (将0026H存入bx)。

(10)这四步分别执行add al,bl (将al bl的值存入al),此时ax为0040,再执行add ah,bl (将ah bl的值存入ah),00 26=26,此时ax为2640,再通过add bh,al (将bh al的值存入bh),40 00=40,此时bx为4026,再通过mov ah,0 (将0存入ah),ax的状态为0040。

(14)继续通过-t命令,执行add al,bl (将al bl的值存入al)最后执行add al,9CH (将al 9CH的值存入al),但是在此过程中发生了溢出,66 9CH=102,但是此时AL仅能显示两位,故最高位发生了溢出,ax显示为0002。

2.将源代码第二部分中的3条指令写入从2000:0开始的内存单元中,利用这3条指令计算2的8次方。

(1)首先,我进入了debug模式,然后以2000:0为起始地址,使用-a命令,开始输入指令。输入结束后我用-u命令查看了写入的汇编指令。但是接着使用-t执行指令时却没有出现预想之中的结果,然后我使用-r命令查看了CPU寄存器的内容,发现cs:ip地址没有从2000:0开始。

(2)接着我使用-r命令修改了cs:ip,使其指向2000:0。

(3)然后我使用-t执行命令,先执行了mov ax,1 ,后执行add ax,ax,将ax中的值 ax中的值的结果保存在ax中, 然后发现执行jmp时发生了无条件的跳转,ip又返回了0003,然后接下来继续执行add命令。

(4)每跳转一次后执行add ax,ax都相当于是一次翻倍的操作,执行8次后即可求出2的八次方。得到结果为100H,即256。如下所示:

3.查看内存中的内容

PC机主板上的ROM中写有一个生产日期,在内存FFF00HFFFFFH的某几个单元中,请找到这个生产日期并试图改变它。(内存ffff:0005ffff:000C(共8个字节单元中)处)

这里使用-d ffff:0005查看了该指定地址开始的128个内存单元的内容,找到了ASCII码为01/01/92的字样,可知其生产日期为1992年1月1日。尝试用-e ffff:0005改变内存值,但是实际并没有改变。

在DOS环境下 使用wmic bios get description,结果显示命令不合法,说明在DOSBOX虚拟环境下是无法读取bios的配置信息的。我想这也从侧面证明了DOSBOX虚拟环境中的主板ROM信息并不是真正的PC机主板信息,而只是作为软件内置的参数而已。

【二】用机器指令和汇编指令编程

1.Debug命令的补充

(1)Debug的T命令在执行修改寄存器SS的指令时,下一条指令也紧接着被执行。

通过资料查询,我找到了SS:SP的具体解释,其中SS 段寄存器和寄存器SP 用来指示栈,其中栈顶的段地址存放在SS 中,而相对于栈顶的偏移地址存放在SP 中。并且在任意时刻,SS:SP 都指向栈顶元素。

(2)在D命令中使用段寄存器

格式:“d 段寄存器:偏移地址”,以段寄存器中的数据为段地址SA,列出从SA:偏移地址开始的内存区间中的数据

(3)在E、A、U命令中使用段寄存器 在E、A、U这些可以带有内存单元地址的命令中,也可以同D命令一样,用段寄存器表示内存单元的段地址。

2.源代码:
代码语言:javascript复制
mov ax,ffff ;将ffffH存入ax 中
mov ds,ax ;将ax作为数据段的段地址
mov ax,2200 ;将2200H覆盖ax中原有的数据
mov ss,ax ;将ax中的数据放入ss中作为栈段的地址
mov sp,0100 ;将0010H数据覆盖sp中原有的数据
mov ax,[0] ;将偏移量为0 的数据存入ax
add ax,[2] ;将ax 与偏移量为2 的内容相加并且存入ax
mov bx,[4] ;将偏移量为4 的内容放进bx
add bx,[6] ;将bx 与偏移量为6 的内容相加并将结果放入bx
push ax ;将ax 中的内容压入栈
push bx ;将bx 中的内容压入栈
pop ax ;将栈顶内容放入ax 中
pop bx ;将栈顶内容放入bx 中
push [4] ;将偏移量为4 的地址的内容压入栈
push [6] ;将偏移量为4 的地址的内容压入栈


mov ax,1000H ;将1000H存入ax
mov ds,ax ;将ax中的数据赋给ds
mov ds,[0] ;将偏移量为0的数据赋给ds
add ds,ax ;将ax 和ds中的数据相加并且将结果存入ds中
3.代码、过程、相应结果的说明和分析:

(1)使用Debug,将上面的程序段写入内存,逐条执行,根据指令执行后的实际运行情况填空。

执行命令,首先将将ffffH赋值给ax中,然后将ax中的值覆盖ds中的值,再将2200H赋值再ax中,然后将ax中的值赋值给ss中,同时sp改为0100,代表着开辟了一块栈空间。

将偏移地址为0 的单元的内容放入ax 中,此时ax 的值变为c0ea,然后将ax 与偏移地址为2 的数据相加并将结果放在ax 中,此时ax 中数据变为C0FC,然后将偏移地址为4 的单元的数据 放入bx 中,由-r查看状态可知,这时ds:0004 =30F0,因此bx =30F0,然后将bx 和偏移地址为6 的数据相加并将结果存在bx 中,此时ds:0006 =2F31,于是bx =6021。然后将ax 中的数据压入栈,此时sp =00FE ,然后将bx =6021 压入栈中,此时sp =00FC。

接着出栈并将数据存在ax 中,此时sp 为00FE,然后继续出栈,并将数据存放在bx 中。然后将偏移地址为4 的数据压入栈,此时ds:0004 =30F0,而sp =00FE,然后将偏移地址为6 的 内容压入栈,此时ds:0006 =2F31,sp =00FC。

综上:填写空格如下:

代码语言:javascript复制
mov ax,ffff
mov ds,ax
mov ax,2200
mov ss,ax
mov sp,0100
mov ax,[0] ;ax= C0EA
add ax,[2] ;ax= C0FC
mov bx,[4] ;bx= 30F0
add bx,[6] ;bx= 6021
push ax ;sp= 00FE ;修改的内存单元的地址是00FE 内容为	C0FC
push bx ;sp= 00FC ;修改的内存单元的地址是00FC 内容为6021
pop ax ;sp= 00FE ;ax= 6021
pop bx ;sp= 0100 ;bx= C0FC
push [4] ;sp= 00FE ;修改的内存单元的地址是00FE 内容为30F0
push [6] ;sp= 00FC ;修改的内存单元的地址是00FC 内容为2F31

(2)使用Debug,将程序段(二)写入内存,逐条执行,观察每条指令执行后,CPU 中相关寄存器中内容的变化。

如上图所示,程序报错,对于这种现象的解释是DS 为数据段寄存器,无法用在算数运算指令中,不能与通用寄存器进行运算。

(3)仔细观察下图中的实验过程,然后分析:为什么2000:0~2000:f 中的内容会发生改 变?

a.首先出现的第一个问题与在知识准备板块出现的问题一样:该图代码在执行完mov ss,ax后,下一步的指令应该是movsp,10,但实际上却是mov ax,3123。在用T命令单步执行mov ss,ax 前,ax=0000,ss=0b39,sp=ffee,而执行后 ss=2000,sp=0010。ss变为2000是正常的,这正是mov ss,ax的执行结果。而能够将sp设为0010的只有指令mov sp,10,看来,mov sp,10一定是得到了执行。 在用T命令执行mov ss,ax的时候,它的下一条指令mov sp,10也紧接着执行了。 整理一下我们分析的结果:在用T命令执行mov ss,ax的时候,它的下一条指令mov sp,10也紧接着执行了。一般情况下,用T命令执行一条指令后,会停止继续执行,显示出当前CPU各个寄存器的状态和下一步要执行的指令,但T命令执行mov ss,ax的时候,没有做到这一点。不单是mov ss,ax,对于如:mov ss,bx,mov ss,[0],pop ss等指令都会发生上面的情况,这些指令有哪些共性呢?它们都是修改栈段寄存器SS的指令。 结论:Debug的T命令在执行修改寄存器SS的指令时,下一条指令也紧接着被执行。

b.针对2000:0~2000:f 中的内容会发生改变这一问题,我通过查询资料之后得知,之所以靠近栈顶的10 个字节中值有了变化,是因为对定义栈段时部分运行环境变量进行暂存,靠近栈顶的10 个字节中的暂存数据分别是SS、IP、CS 等的值,在用T 指令在进行调试时,会产生中断。而为了保护现场,CPU 先把标志寄存器入栈,再把CS IP 分别入栈。

【总结心得】

​ 我对于汇编语言的认识进一步加深了,也在这过程中有了自己的体会。首先,眼看千遍不如手过一遍,对于预备知识里的内容,我没有松懈,将里面的每一条命令都认真地写了一遍,并且记录了下来,这让我对于汇编debug的常用命令较为熟悉,印象也较为深刻,这对我后面的实验帮助非常大。通过实验,我也了解到查看、改变CPU 寄存器的内容,查看、改写内存中的内容,将内存中的机器指令翻译成汇编指令,执行一条机器指令,以及反汇编的一些指令。

​ 其次,我发现实验后面出现的一些问题,往往都能在实验开始时的部分找到解答,因此,整个实验的每一部分都是不能轻视的。比如在实验过程中起初输入完命令之后,用T指令开始执行时,显示的结果却并未执行,这在最初困扰着我,之后再查看CPU 寄存器的内容时发现原来CS:IP 并没有指向指令起始的地址区域,因此在执行时并未执行所编写的区域。之后我想到可以通过修改CS:IP 的值来指向指令的起始地址,也可以通过jmp指令跳转到对应的区域。包括后来在分析:2000:0~2000:f的过程中,突然发现指令自动执行了一步,然后就回想到最开始的预备知识部分做过相关的探究了,Debug的T命令在执行修改寄存器SS的指令时,下一条指令也紧接着被执行。

​ 在实验中也结合到C#里以及小甲鱼的课程中学过的有关于栈的知识,这对于我理解实验帮助非常大。因此,我觉得知识的积累是十分有必要的。

​ 综上,通过实验,我认为多实践以及多读多想是十分关键的,而且许多知识之间都是相通的,只有在积累了足够多的知识以后,在处理解决问题以及学习新知识时才能更好的达到期望的目标。

0 人点赞