简介:这是一份面向北京交通大学《汇编与接口》课程实验的Debug调试实训报告,围绕leapYear.exe闰年判断程序,完整记录从编译、链接、运行到逐句分析代码逻辑的过程。内容涵盖Debug常用调试指令(如D显示内存、T单步执行、C继续至断点、P执行机器码指令)、输入“是闰年/不是闰年”两种场景下的执行路径观察,以及针对程序条件判断和输入处理问题的定位、修改与重新编译链接验证,适合正在学习汇编语言与微机接口、需要上手Debug调试的本科生作为操作参考。
包内目前包含1个docx格式实验文档,压缩包大小约78.94MB,文档目录清晰,按“编译链接运行—代码逻辑分析—Debug程序—代码错误修改”四个模块展开,既可作为上机实验的步骤复盘,也能作为遇到同类调试问题时的排错思路索引。该资源已有521人学习下载,对希望理解汇编程序底层执行机制、提升Debug调试能力的学习者具有实用参考价值。
1. 用 Debug 调通 leapYear.exe:从编译到“找茬”的一整条链路
很多人学汇编与接口课程时,最大的坎不是写不出代码,而是程序跑出错误结果后不知道去哪看。这份“汇编与接口 Debug 调试实验”正好卡在这个痛点上:它带着你从 masm 编译、链接生成 leapYear.exe 开始,到用 Debug 单步跟踪闰年判断的完整执行流程,最后还专门设计了一个“代码里藏着 bug”的环节,逼着你用调试手段把问题揪出来。适合两类人:一类是刚接触汇编、对段寄存器、INT 21H、标志位还发懵的初学者;另一类是已经能写简单汇编程序,但一遇到“结果不对”就只会瞎改代码、不会系统排查的同学。整个资源的核心价值不在于教你闰年怎么判断,而在于帮你建立“用 Debug 看寄存器、看内存、看标志位”的调试习惯——这才是后续所有汇编实验能不能顺利推进的分水岭。
2. 编译链接与代码逻辑:先把“程序应该怎么走”钉死
2.1 用 masm5 手动编译链接:每一步生成什么文件都要心里有数
实验的第一步不是写代码,而是把源代码变成可执行文件。资源里给出的典型流程是:先把 masm5 文件夹放到 C 盘根目录,然后打开命令行进入该目录,用 masm 命令对源码文件进行汇编,汇编通过后再用 link 命令链接。
cd C:\masm5 masm leapYear.asm link leapYear.obj汇编阶段输入源文件名 leapYear.asm,masm 会生成 leapYear.obj 目标文件;如果指定生成交叉索引文件,还会出现 .lst 列表文件和 .crf 交叉引用文件。链接阶段输入 leapYear.obj,link 会生成 leapYear.exe 可执行文件。此时你会看到 masm5 文件夹里同时出现 .obj、.lst、.ref、.map、.crf、.exe 等文件,这些文件各有用途:.lst 是把源程序、地址和机器码对照排列的列表文件,调试时想快速查某条指令的机器码就翻它;.map 是内存映像文件,记录了段的名称为起始地址及长度;.crf 是交叉引用文件,在大型程序里用来查某个符号在哪些地方被引用过。
这里有个新手容易忽略的点:masm 和 link 都是交互式命令,执行后会提示输入文件名,如果你直接回车,它会用默认名或者报错。习惯做法是在命令行里把源文件名作为参数一次带全,例如masm leapYear.asm;分号表示后面的提示都用默认值。链接时同样可以link leapYear.obj;省去逐项确认。生成 exe 后,直接输入leapYear回车即可运行,程序会先显示Please input a year:,输入 2020 输出This is a leap year!,输入 2026 和 2025 都输出This is not a leap year!,输入 2100 也输出不是闰年。这个结果验证了程序的闰年判断逻辑:能被 4 整除但不能被 100 整除,或者能被 400 整除才是闰年。2100 能被 4 和 100 整除但不能被 400 整除,所以不是闰年。
2.2 数据段与代码段的核心设计:DATACATE 和 IFYEARS 怎么分工
代码的段结构是典型的汇编程序模板:DATA 段放提示字符串和缓冲区,STACK 段放 200 字节堆栈,CODE 段放主程序和两个子程序。数据段里几个关键变量需要先记牢:
| 变量 | 大小 | 作用 |
|---|---|---|
| INFON | DB | 存放提示输入年份的字符串 |
| Y | DB | 存放“是闰年”的输出字符串 |
| N | DB | 存放“不是闰年”的输出字符串 |
| W | DW | 存放转换后的十六进制年份 |
| BUF | DB 8, ?, 8 DUP(?) | 输入缓冲区,第一个字节是最大长度 8,第二个字节是实际输入长度 |
主程序的执行路径很清晰。先MOV AX, DATA再MOV DS, AX,这里必须分两步,因为 CPU 不允许把立即数直接送入段寄存器,只能先送通用寄存器过渡。然后用LEA DX, INFON配合MOV AH, 9和INT 21H输出提示字符串,这一组指令是 DOS 显示字符串的标准套路:AH = 9 表示显示字符串,DX 指向以$结尾的字符串首地址。接着用LEA DX, BUF配合MOV AH, 10和INT 21H接收键盘输入,AH = 10 是带缓冲的键盘输入功能,会把输入的字符放入 BUF 指向的缓冲区,BUF+1 位置存放实际输入字符数,BUF+2 开始存放字符本身。
主程序里有三个关键细节值得展开讲。第一个是MOV CL, [BUF+1],它把实际输入字符数送入 CL,但这个操作只改了 CL 低 8 位,没有把 CH 高位清零——这正是后面 Debug 调出来翻车的根因,后面专门开一节讲。第二个是LEA DI, BUF+2,DI 指向输入字符串的首字符,后面的 DATACATE 子程序就是从这里开始逐字符转换。第三个是主程序调用 IFYEARS 后用JC A1判断进位标志 CF,CF = 1 跳去输出“是闰年”,CF = 0 则顺序执行输出“不是闰年”,这个设计把子程序的判断结果通过标志位传递出来,符合汇编里“子程序用寄存器或标志位返回结果”的常规做法。
2.3 DATACATE 字符串转数字:乘十累加法的逐句拆解
DATACATE 子程序的核心任务是把 BUF 里存的 ASCII 码字符串(比如'2020')转换成真正的十六进制数值存入 W。这个转换算法叫“乘十累加法”,流程是:每读到一个字符,先减 30H 把 ASCII 码变成数字,再把之前累加的结果乘 10,最后加上新读到的数字。整个过程循环四次,正好处理四位年份。
DATACATE PROC NEAR LEA SI, BUF+2 ; SI 指向输入字符串首字符 MOV BX, 0 ; BX 作为累加器,初始为 0 X3: MOV AL, [SI] ; 取一个字符的 ASCII 码 SUB AL, 30H ; ASCII 码减 30H 得到数字 MOV AH, 0 ; AX 高位清零,AX = 数字 XCHG AX, BX ; 暂存累加结果到 AX MOV DX, 10D ; DX = 10 MUL DX ; AX = AX * 10 XCHG AX, BX ; 乘完的 AX 与 BX 交换回来 ADD BX, AX ; BX = 旧累加值*10 + 新数字 INC SI ; SI 指向下一个字符 LOOP X3 ; CX 减 1,非零则继续循环 MOV W, BX ; 最终结果送入 W RET DATACATE ENDP这段代码在 Debug 里反复出现,必须理解每一步对寄存器的改变。第一次循环:SI = 0050,AL ='2'的 ASCII 码 32H,减 30H 后 AL = 02H,AX = 0002H。XCHG 后 AX 和 BX 交换,此时 BX = 0,AX = 2,所以交换后 AX = 0,BX = 2。MUL DX 后 AX = 0 * 10 = 0,再 XCHG 回去,AX = 2,BX = 0,ADD 后 BX = 0 + 2 = 2。第二次循环读到'0',BX 变成 (2 * 10) + 0 = 20,即十六进制 0014H。第三次循环后 BX = 202,十六进制 00CAH。第四次循环后 BX = 2020,十六进制 07E4H。到这里,字符串"2020"就变成了数值 07E4H 存到 W 中。
这里有个细节初学者容易卡住:为什么用 XCHG 而不是直接 MUL BX?因为 MUL 指令的乘数如果放在寄存器里,那被乘数默认在 AX 中,乘积的高 16 位存 DX、低 16 位存 AX,会覆盖 AX 里原有的累加值。所以代码先把累加值从 BX 换到 AX,乘完再把结果换回 BX,这个交换操作就是一种保护现场的手段。
2.4 IFYEARS 闰年判断:嵌套分支与标志位的传递
IFYEARS 子程序的逻辑是典型的嵌套条件判断。先把年份送入 CX 保存一份副本,因为后面连续做三次除法会反复覆盖 AX 和 DX。然后判断能否被 4 整除,不能就直接 CL C 清零退出;能的话判断能否被 100 整除,不能被 100 整除说明是闰年,STC 置位退出;能被 100 整除则还要看能否被 400 整除,能的话置 CF = 1,不能的话 CF 保持为 0。
IFYEARS PROC NEAR PUSH BX PUSH CX PUSH DX MOV AX, W ; 年份送入 AX MOV CX, AX ; CX 保存年份副本 MOV DX, 0 MOV BX, 4 DIV BX ; AX / 4,AX=商,DX=余数 CMP DX, 0 ; 比较余数和 0 JNZ LAB1 ; 余数不为 0,不能被 4 整除,CF 清零退出 MOV AX, CX MOV BX, 100 DIV BX CMP DX, 0 JNZ LAB2 ; 不能被 100 整除,是闰年,CF 置 1 退出 MOV AX, CX MOV BX, 400 DIV BX CMP DX, 0 JZ LAB2 ; 能被 400 整除,是闰年 LAB1: CLC JMP LAB3 LAB2: STC LAB3: POP DX POP CX POP BX RET IFYEARS ENDP这里有个容易误读的地方:DIV 指令对操作数长度的要求很严格。DIV BX 是字除法,被除数默认是 DX:AX 组成的 32 位数,所以执行前必须手动把 DX 清零,否则 DX 里残留的旧值会影响商和余数的正确性。资源里在每次 DIV 前都执行MOV DX, 0,这不是多余操作,而是字除法前的必要初始化。另外,LAB2 里没有 JMP 指令,而是直接“落入”LAB3,这也是汇编里常见的技巧——多个出口汇聚到统一的恢复现场代码,避免在每处都重复写 POP 和 RET。
这个子程序的两个关键设计点:一是用 CX 保存年份副本,因为第一次 DIV 后 AX 变成了商,第二次、第三次判断需要原始年份,没有副本就得重新从 W 里读;二是子程序开头 PUSH 三个寄存器、结尾 POP 三个寄存器,保持进入和退出时寄存器状态一致,避免破坏主程序正在使用的数据。判断结果通过 CF 传递,主程序一条JC A1就能完成分支,整个接口设计简洁且不易出错。
3. Debug 单步跟踪:从 T 指令翻车到抓住 CX 高位没清零
3.1 进入 Debug 与反汇编阅读:R、U、T、P 四条命令的正确用法
要调试 leapYear.exe,先确认 MASM 环境正常,然后输入debug leapYear.exe进入调试模式。资源里的实践顺序明确:先用 R 命令查看寄存器初始状态,CX 并不是 0,IP 指向 0000,CS:IP 构成了当前代码地址;再用 U 命令反汇编,把内存里的机器码翻译成汇编指令。
debug leapYear.exeR 命令显示寄存器时会看到CX = 0000之类的初始值,但这个值是调试器加载程序时设置的,不一定和真实运行环境一致。U 命令典型输出格式是地址 机器码 汇编指令,比如0C59:0000 B8XXXX MOV AX,XXXX,其中 B8 是 MOV AX, imm16 的机器码。Debug 里的所有地址都由段地址和偏移地址两部分组成,CS 提供段基址,IP 提供段内偏移,CPU 用CS * 16 + IP计算物理地址。
T 命令是单步执行,执行一条指令后停下来显示所有寄存器的当前值。P 命令也是单步执行,但遇到 CALL 或 INT 指令时不会进入内部,而是把它当作一步执行完毕。这个区别在调试时必须分清:想进入子程序逐条观察内部逻辑就用 T,想跳过子程序直接看调用结果就用 P。资源里记录了一个翻车经历:调试时用 T 命令执行 INT 21H 中断,结果程序进入 DOS 中断内部不再返回,调试会话直接卡死;改用 P 命令执行同样的 INT 21H,DOS 中断被整体执行完,程序停在下一指令,输出正常。这说明 P 命令的“过程调用不进入”特性对 INT 指令同样生效。
D 命令用来查看指定地址的内存内容,格式是D 段地址:偏移地址,显示结果左边是十六进制字节,右边是对应的 ASCII 字符。资源里用D 0C53:0050查看输入年份的存储情况,看到 2020 的 ASCII 码 32 30 32 30 存在连续四个字节里。对初学者来说,D 命令最大的价值是把“内存里到底存了什么”从抽象概念变成亲眼可见的字节序列。
3.2 DATACATE 的 Debug 现场:一步步从 ASCII 变 07E4H
调试 DATACATE 时,T 命令单步进入子程序。关键观察点有三个:第一个是进入子程序后,IP 指向的并不是第一条执行指令,而是跳过PROC伪指令直接到LEA SI, BUF+2;第二个是每执行一条指令,AX、BX、DX 都在变化,需要对照源码理解变化原因;第三个是 LOOP 跳回 X3 标签时 CX 减 1,从 0004 逐步减到 0000。
资源里完整记录了 2020 的转换过程。T 单步执行到MOV CL, [BUF+1]后,CL = 04,此时 CX = 0004,CH 为 0,一切正常。进入 DATACATE 后 SI = 0050,指向 BUF+2。第一次执行MOV AL, [SI],AX 低 8 位变为 32H('2'的 ASCII),执行SUB AL, 30H后 AL = 02H,AH = 00H,AX = 0002H。XCHG 交换后,BX 里暂存的就是这个数字 2。每次循环结束检查 BX:第一次后 BX = 2,第二次后 BX = 0014H(即 20),第三次后 BX = 00CAH(即 202),第四次后 BX = 07E4H(即 2020)。这个递增过程直观反映了乘十累加法的每一步。
用 T 单步执行 LOOP 指令时会发现一个细节:当 CX 减到 0 后,LOOP 指令不会再跳回 X3,而是顺序执行下一条指令MOV W, BX。但资源里记录了一次翻车:在某次调试中,T 命令执行完第四次循环后,CX 显示 0000,但 LOOP 仍然继续跳转回 X3,导致循环没有正常退出。原因在于调试过程中 CX 的值被外部因素改了,具体后面单独讲。如果用 P 命令调试,由于 P 不进入子程序,无法观察内部状态,所以调试 DATACATE 这类核心转换逻辑,T 命令更合适。
3.3 闰年输入的完整跟踪:从 DIV 到 CF 置位的标志位变化
调试 2020 这个闰年输入时,重点观察标志位的变化。进入 IFYEARS 子程序后,AX = 07E4H(2020),CX 保存同样的值,BX = 4,DX = 0。执行DIV BX后,AX = 07E4H / 4 = 01F9H(505),DX = 0。CMP DX, 0比较后 ZF = 1(Debug 显示为 ZR),JNZ 指令检查 ZF = 0 才跳转,此时 ZF = 1 所以不跳转,继续执行。第二次除法把 AX 恢复为 07E4H,BX = 100,DX = 0,执行后 AX = 07E4H / 100 = 0019H(25),DX = 0014H(20)。CMP DX, 0后 ZF = 0(Debug 显示为 NZ),JNZ LAB2 跳转,执行 STC 把 CF 置 1(Debug 显示 NC 变 CY)。继续执行落到 LAB3,POP 恢复三个寄存器,RET 返回主程序。主程序执行JC A1,CF = 1 所以跳转输出This is a leap year!。
这个跟踪过程有两个关键结论。第一,判断能否被 100 整除时得到的余数 DX,就是年份去掉百位和千位后的后两位(2020 的后两位是 20),这个余数是否为零决定了是否能被 100 整除。第二,三个子程序出口 LAB1、LAB2、LAB3 的 CF 状态分别是 0、1、保持原值,而 LAB1 和 LAB2 最终都会落到 LAB3 统一恢复现场再返回。
3.4 非闰年输入引出问题:循环为什么跳不出去
调试 2021 时,问题浮出水面。前面四位数转换的过程和 2020 完全一样,只是最后 BX = 07E5H(2021)。但第四次循环结束后,LOOP 仍然跳回 X3 继续执行,BX 不断累加,最终得到 71B0H,换算成十进制是 29104,这个值被存入了 W。而 29104 可以算出它能被 4 整除、也能被 100 整除,却也能被 400 整除,所以无论如何判断都显示闰年。这就是为什么在 Debug 中输入任何四位年份,结果都显示闰年的原因。
根因是 CX 高位的问题:主程序MOV CL, [BUF+1]只设置了 CL = 4,但没有设置 CH = 0。如果进入 DATACATE 之前 CX 恰好等于 0004,那循环四次后 CX = 0 正常退出;但如果调试过程中 CX 高位值不为 0,比如 CX = 0104H,那么 LOOP 每执行一次 CX 减 1,从 0104H 递减,要到 256 次循环才减到 0,这期间 BX 已经累加了远远超过四位年份的正确值,最终超过 FFFFH 只保留低 16 位,算出 71B0H。因为循环次数过多,高位溢出后的结果与输入年份无关,只与循环次数的低 16 位有关,所以不同输入年份在 Debug 下都是同样的错误结果。
验证方法在资源里给了两种:一种是用 E 命令直接修改 W 的内存值为 07E5H,然后继续运行,程序输出This is not a leap year!,说明 IFYEARS 本身逻辑没错;另一种是用 R 命令把 CX 改成 0000 再继续执行,程序也能得到正确结果。这两种验证都把问题定位到 CX 高位没清零,而不是闰年判断逻辑出错。
4. 避坑排查:Debug 调试里的五个经典翻车现场
4.1 现象:INT 21H 单步执行后程序卡死
用 T 命令单步执行到 INT 21H 指令时,程序进入中断服务程序后不再返回,调试界面卡住,无法继续按预期逐条执行后续指令。
原因:INT 21H 是 DOS 系统调用中断,T 命令会进入中断内部的代码,而这些代码属于 DOS 内核,不是用户程序的一部分,调试器无法用单步方式逐条跟踪系统内核指令,并且内核代码执行完后返回地址的衔接方式和普通 CALL 不同,导致跟踪状态错乱。
解决:遇到 INT 21H、INT 20H 这类软中断指令时改用 P 命令。P 会把整个中断调用当作一条指令执行,中断结束后自动停在 INT 21H 的下一条指令,程序流程清晰可控。从此以后我每次调试遇到 INT 指令,都强制走一遍“先看是不是中断,是就用 P”,不再碰 T。
4.2 现象:LOOP 循环不退出,BX 值疯狂增长到 71B0H
用 T 命令单步调试 DATACATE 时,循环执行完四次后 LOOP 仍然跳回循环体,BX 值从 07E4H 一路增大到 71B0H,循环次数远超过预期。
原因:LOOP 指令以整个 CX 作为循环计数器,执行一次减 1。主程序只给 CL 赋值,CH 高位没有被清零。如果 CH 不为 0,比如 CH = 01、CL = 04,循环要执行 0x0104 次才能退出,远超四位年份转换所需的 4 次。BX 每次循环累加后最终溢出,只保留低 16 位形成 71B0H。
解决:在MOV CL, [BUF+1]后加一条MOV CH, 0,把 CX 高位强制清零。修改后循环恰好执行四次,DATACATE 能正确转换年份。这个 bug 在直接运行程序时不会暴露,因为非调试环境下进入 DATACATE 前 CX 恰好是 0004H;但在 Debug 的 G 命令或其他场景下 CX 高位值可能是任意值,所以必须在代码层面根除,不能依赖调试环境的偶然正确性。
4.3 现象:G 命令运行程序结果错误,但单独运行 exe 结果正确
用 G 命令一次性运行整个程序并输入年份后,程序输出“是闰年”的错误结果;但退出 Debug 后直接运行 leapYear.exe,输入同样的年份,输出正确。
原因:Debug 加载程序时对寄存器做了初始化,CX 的值可能不为 0。G 命令开始执行后,主程序MOV CL, [BUF+1]覆盖了 CL,但 CH 保持 Debug 环境的值,导致 DATACATE 的循环次数出错。直接运行 exe 时,DOS 加载程序前的 CX 值恰好是合理的 0004H,所以不触发问题。
解决:不要在调试环境里依赖“恰好合理”的寄存器初始值。对于这类需要固定循环次数的代码,必须在进入循环前显式初始化完整的计数器寄存器,只设置低 8 位或高 8 位都是隐患。用 G 命令之前先执行 R 命令检查 CX,并把 CX 改为 0000,能临时规避问题,但正确做法是修改源码。
4.4 现象:DIV 指令计算结果完全错误,商和余数都对不上
在 IFYEARS 子程序里连续执行三次 DIV 指令,第二次、第三次算出的余数明显不对,比如 2020 除以 100 的余数不是 20 而是其他值。
原因:DIV BX 是 32 位除以 16 位的除法,被除数默认是 DX:AX。第一次 DIV 后 AX 变成商、DX 变成余数,但在第二次执行 DIV 前只重新加载了 AX 为年份值,没有把 DX 清零,DX 里残留上一次除法的余数,导致被除数变成了高 16 位不为零的非法值,商和余数自然全错。
解决:每次 DIV 之前明确执行MOV DX, 0,并且从 W 重新读取年份到 AX。资源里代码已经写好了这个初始化,但如果是自己手写同类代码,很容易漏掉。调试时用 R 命令观察执行 DIV 前后的 DX:AX 值,确认被除数是否符合预期,这是排查除法问题的通用办法。
4.5 现象:输出字符串内容乱码或者显示不完整
程序运行时提示信息正常,但输入年份后输出结果里出现乱码字符,或者字符串只显示一半就结束。
原因:INT 21H 的 AH = 9 功能以$字符作为字符串结束标志。如果字符串定义里遗漏了结尾的$,或者字符串中间不小心出现了$,显示就会提前终止,后续内容被系统当作乱码处理。另一个可能是 LEA DX 加载的地址不是字符串真正起始位置,比如误把 BUF+1 当成 BUF+2。
解决:检查所有字符串定义末尾是否都有$,Y 和 N 的定义要注意0DH, 0AH只是换行回车控制符,不能代替结束符。用 D 命令查看字符串所在内存区域,确认首地址到$之间的内容都是合法字符。如果地址偏移错了,先数清楚 BUF 缓冲区的字节布局,BUF+0 是最大长度、BUF+1 是实际长度、BUF+2 才开始是输入数据。
5. 进阶技巧与验证收尾:把调试能力沉淀成固定动作
5.1 用 E 命令做“代码级手术”:修改内存绕过坏逻辑
当代码里某个中间变量算错,但你又不想整个程序重跑时,用 E 命令直接修改内存里的数据是最快的验证手段。资源里反复用到这个技巧:算出 W 的值不对,就将 W 的内存强制改成 07E4H 或 07E5H,再继续执行,以此隔离问题范围——到底是转换环节坏了,还是判断环节坏了。实际命令格式:
-E 0C53:0050 0C53:0050 07E4.07E5-E后面跟段地址和偏移地址,回车后 Debug 显示当前字节内容,输入新值再回车完成修改。这个做法的边界是:修改的是内存中的临时数据,不是源文件,重启 Debug 后恢复原样。它能快速验证“假设某个变量正确,后续逻辑是否正常”,是做问题定位的高效手段。
5.2 自己动手改源码:加一条 MOV CH, 0 再走完整流程
找到根因后,修改源码是最直观的验证方式。原有代码里只有MOV CL, [BUF+1],改成下面这样:
MOV CL, [BUF+1] MOV CH, 0修改后重新执行编译和链接:
cd C:\masm5 masm leapYear.asm link leapYear.obj这里有个常见误区:只重新编译不重新链接,运行的是旧的 exe,修改根本不生效。masm 生成新的 obj 后,必须再 link 一遍覆盖旧 exe。改完重新运行,输入 2020、2021、2100 三个年份,分别得到“是闰年”“不是闰年”“不是闰年”,结果全部正确。再用 Debug 的 G 命令跑一遍,同样正确,说明修复在调试环境下也生效。
5.3 其他上机实践指令补全:A、G、H、F 的边界在哪
资源最后还整理了一组 Debug 的常用指令,对后续做复杂实验很有用。A 命令从指定地址开始输入汇编指令,Debug 会直接翻译成机器码存入内存,适合小段代码临时验证。H 命令计算两个十六进制数的和与差,比如-H 10 5输出0015 000B,快速心算校验。F 命令把一片内存区域填充成同一个数据值,如-F 0C53:0200 10 41,做缓冲区初始化很方便。G 命令一次性运行到指定地址,格式-G 地址,比逐条 T 快得多。
这些指令的组合使用才能真正提升调试效率:先用 U 反汇编找关键代码地址,再在合适位置用 G 跑到那里,然后改用 T 单步观察细节,发现内存异常用 D 查看、用 E 修改。这一套动作下来,定位 bug 的时间能缩短一半以上。
5.4 一个验证方法:R 修改寄存器制造极端场景
除了改内存,修改寄存器的值同样能构造测试场景。比如用 R CX 把 CX 改成 0000,验证循环是否会错误持续;或者故意把 CX 改成 FFFF,看程序如何崩溃,反而能帮你理解寄存器和指令的交互机制。这类验证的价值在于:你亲手制造了 bug,才能对 bug 的成因有肌肉记忆。从那以后我每次写汇编循环,都会强制走一遍“确认计数器高位清零、确认每次 DIV 前 DX 清零、确认字符串以$结尾”这三个检查,再交付编译链接,几乎没有再在调试上翻过车。希望这套 Debug 方法和排查思路对你有实际帮助。
本文还有配套的精品资源,点击获取