DOS 汇编 DEBUG 基本命令及其功能详解
注:本篇文档是转载,忘记转载链接了!!!
目前 Window 10 以上版本 已经不再使用 DEBUG 工具了,而是使用 WinDBG 工具,但是并不影响我们去了解它。
目的
掌握 DEBUG 的基本命令及其功能掌握 win7 win8 使用 DEBUG 功能
调试步骤
1. 使用 Debug,将程序段写入内存,逐条执行,观察每条指令后cpu 中相关寄存器内容的变化
2. 将指令写入内存单元中,计算 2 的 8 次方。
3. 查看内存中的内容,并试图修改
4. 用机器指令和汇编指令编程
具体内容
一、查看 CPU 和内存,用机器指令和汇编指令编程
1.预备知识:
Debug总结:它是DOS、Windows 都提供的实模式(8086 方式)程序的调试工具。使用它,可以查看CPU 各种寄存器中的内容、内存的情况和在机器码级跟踪程序的运行。
常用的Debug功能总结:
命令 | 含义 | 用法 |
---|---|---|
A | assemble | 在内存中直接编写代码 |
C | Compare | 比较内存 |
D | Dump | 查看内存内容 |
E | Enter | 修改内存内容 |
F | Full | 填充内存 |
G | Go | 执行代码 |
H | Hexarithmetic | 以 16 进制进行数学运算 |
I | Input | 从某个端口输入一个 16 进制并显示 |
L | load | 把文件或者绝对扇区内容读入内存 |
M | move | 数据传递(不同于汇编的 move ) |
N | name | 为读写磁盘文件定义文件名 |
O | output | 把指定字节发送到制定端口 |
Q | quit | 结束 debug 程序 |
R | Register | 寄存器命令 |
S | Search | 按照 list 清单查找内存 range 范围 |
T | track | 追踪命令 |
U | UNassemble | 反汇编内存中的指令 |
W | write | 把调试过的信息写到磁盘上 |
? | Help | 获取帮助信息,即现实所有支持的命令 |
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 单元开始写入这样一段机器码:
1 | ;机器码 对应的汇编指令 |
![10.png]
由结果可知,该机器码可以生效。
(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 中相关寄存器中内容的变化。
1 | 机器码 汇编指令 |
(2):将下面3条指令写入从 2000:0 开始的内存单元中,利用这 3 条指令计算 2 的 8 次方。
1 | mov ax,1 ;将1存入ax(从2000:0开始的内存单元) |
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.源代码:
1 | mov ax,ffff ;将ffffH存入ax 中 |
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 压入栈中,此时 p =00FC。
接着出栈并将数据存在 ax 中,此时 sp 为 00FE,然后继续出栈,并将数据存放在 bx 中。然后将偏移地址为 4 的数据压入栈,此时 ds:0004 =30F0,而 sp =00FE,然后将偏移地址为6 的 内容压入栈,此时 ds:0006 =2F31,sp =00FC。
综上:填写空格如下:
1 | mov ax,ffff |
(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的指令时,下一条指令也紧接着被执行。
综上,通过实验,我认为多实践以及多读多想是十分关键的,而且许多知识之间都是相通的,只有在积累了足够多的知识以后,在处理解决问题以及学习新知识时才能更好的达到期望的目标。