
简介北京交通大学汇编与接口课程配套的Debug调试实验资源面向初学汇编与微机接口的学生以闰年判断程序leapYear.exe为核心完整演示了从汇编源码、编译链接到生成可执行文件再通过Debug工具逐行观察寄存器、内存和程序流程的调试过程。资源以单个docx文档呈现容量约78.94MB报告按“编译链接运行、代码逻辑分析、Debug程序实践、代码错误修改”四个模块展开其中不仅包含整体逻辑梳理和逐句逐段分析还记录了断点设置、D/T/C/P等调试指令的用法以及针对闰年判断条件错误的定位与修改记录。目前已有520人学习浏览。对于需要做同类上机实验或想深入理解汇编程序执行细节的读者这份资源能帮助理清Debug调试流程掌握从发现问题、修改源代码到重新编译链接并验证结果的一整套排错思路适合作为实验报告或复习参考。1. Debug调试实验到底在练什么寄存器到外设端口的完整闭环“【汇编与接口】Debug调试实验”这门课核心不是学会敲几条debug命令而是让你在没有图形界面、没有语法高亮的条件下亲手把“CPU 如何执行指令、如何读写外设”这个过程从黑匣子变成透明管道。DOSBox 里启动 Debug 后你会面对一个-提示符它只认十六进制数字但正是这种简陋逼着你理解CS:IP指向哪条指令、ZF位怎么影响跳转、3FD端口里那一位表示“串口收到数据了”。这个实验面向正在学微机原理或汇编接口、整天和 8255/8250/8259 打交道、也想搞懂 KEIL 和 GDB 背后机制的人。它能解决的问题很具体程序为什么不按预期跑数据到底有没有进到寄存器里。2. 环境与第一段汇编用DOSBox把Debug跑起来的最小命令2.1 为什么还非要用DOSBox接口实验需要实模式内存模型现在的 Windows 和 Linux 都不会让你随意改写中断向量表、直接在 I/O 端口上做IN/OUT这是保护模式下的系统权限限制。而“汇编与接口”课里大量实验要求的就是这种“裸奔”能力把某一段内存当作显存直接写字、把一个端口当作开关来读、把中断向量改成自己写的服务程序入口。DOSBox 模拟的是 8086 的实模式在这些方面几乎接近当年真实 DOS 机器的行为所以到现在依然是这类实验最省事的载体。我一般会把它当作一个“纯 CPU 侧调试场”来用先在 DOSBox 里把汇编逻辑跑通、把每个寄存器和标志位看清楚再把同一段程序迁移到实验台的 Proteus 仿真或者 KEIL 工程里做外设联调。这样出了问题时至少能确定是 CPU 逻辑的问题还是接口硬件的问题。2.2 挂载目录与启动mount、盘符、进入DebugDOSBox 不会直接访问你 Windows 下的真实目录得先用mount把某个本地文件夹映射成它的一个虚拟盘符。常见做法是在 D 盘根目录建一个asmlab里面放好实验用的汇编源文件以及必要的DEBUG.EXE。默认情况下DOSBox 的 Z 盘是只读的不适合保存实验产物所以挂载自己的目录几乎是必须的第一步mount c d:\asmlab c: dir debug这里mount c d:\asmlab表示把 Windows 的d:\asmlab映射为 DOSBox 的C:盘随后c:切到虚拟 C 盘dir确认文件可见最后执行debug进入调试器。如果运行后没有出现-提示符说明当前 DOSBox 发行版没有内置 Debug 工具把课程资料里自带的DEBUG.EXE复制到asmlab目录再执行即可。目录路径里不要带空格和中文不然mount阶段容易翻车这是很多人第一次卡住的地方。2.3 A命令写入第一段程序U与T验证执行进入-提示符后先用A命令从内存地址CS:0100开始写入一小段汇编。Debug 的A命令自带一个极简汇编器不需要先写.asm文件再编译适合快速验证想法。下面这段代码的功能是在屏幕上输出一个字符A后退出-a 100 MOV AH,02 MOV DL,41 INT 21 INT 20 -u 100 10A -t-a 100的含义是“从当前代码段偏移 0100H 处开始汇编”这是 Debug 下默认的代码起点因为 DOS 的.COM程序就是从这里加载的。随后逐行输入指令注意这里立即数不要加H后缀Debug 默认所有数字都是十六进制写成MOV AH,02即可从 MASM 转过来的新手经常写MOV AH,02H标准 DEBUG.EXE 会直接报错。INT 21配合AH02H是 DOS 单字符显示功能DL41H是字符A的 ASCII 码最后的INT 20H是程序终止功能。输入完成后回车回到-提示符再用-u 100 10A反汇编能看到每一条指令对应的机器码用-t单步执行一次屏幕会列出各寄存器当前值以及下一条待执行指令。2.4 段地址体系CS:IP、DS:DX在调试界面上的含义Debug 显示的所有地址都是“段地址 : 偏移量”的形式比如CS0AFC IP0100实际物理地址是0AFC*16 0100 0BDC0H。这个换算关系在实模式下无处不在CS:IP指向下一条要取出的指令DS:DX常被用来给 DOS 中断传递内存缓冲区地址SS:SP则是当前栈顶。写程序时-a默认装配到CS段用-d或-e看内存时默认走DS段而 I/O 端口操作没有段地址因为IN/OUT指令采用独立于内存的端口地址空间。理解这套区别后你会发现“汇编与接口”课程里反复强调的“内存地址空间与 I/O 地址空间是两回事”这句话在 Debug 的调试界面里就是不同命令的默认行为差异非常直观。3. 寄存器、标志位与内存端口把接口数据通路看穿3.1 R命令读出全部寄存器rf修改标志位-r命令一次显示 CPU 的主要寄存器组和全部状态标志。刚进入 Debug 时执行会看到类似这样的输出-r AX0000 BX0000 CX0000 DX0000 SPFFEE BP0000 SI0000 DI0000 DS0AFC ES0AFC SS0AFC CS0AFC IP0100 NV UP EI PL NZ NA PO NC第二行末尾的八个缩写就是标志位的组合NV表示无溢出UP表示方向标志为递增EI表示中断允许PL表示运算结果为正NZ表示 ZF0NA表示无辅助进位PO表示奇校验NC表示无进位。在接口实验里最常用到的是ZF和CF串口状态查询时常做TEST AL,01如果数据就绪位为 1ZF0JNZ跳转到读取分支用CMP判断循环计数是否到头时则是ZF1触发JZ跳出。如果想硬改标志位来测试条件跳转分支用-rf命令回车后直接输入标志缩写比如把NZ改成ZR再单步执行就能看到跳转走向完全不同的分支。这在没法马上构造真实硬件条件时特别有用相当于给程序一个“模拟输入”。3.2 D与E命令内存读写与数据段查看接口实验里经常要往内存缓冲区填数据然后让某个中断服务程序去读取。Debug 的E命令负责往内存写D命令负责查看。下面这段演示会在DS:0200放一个以$结尾的字符串再用 DOS 中断把它输出到屏幕-e 200 HELLO$ -d 200 206 -a 100 MOV DX,0200 MOV AH,09 INT 21 INT 20 -g-e 200 HELLO$是把从DS:0200开始的连续内存区域写成这几个字符的 ASCII 码$是 DOS 字符串结束标记。-d 200 206显示这一段内存内容左侧是地址中间是十六进制字节右侧是对应 ASCII 字符。之后用-a写一段代码把字符串首地址放进DX调用INT 21H的09H功能-g直接运行到程序结束屏幕上就会打印HELLO。这里要特别注意-e默认写的是DS段如果之前程序已经改动过DS最好写成-e ds:200这样的显式段地址否则数据写到了你意想不到的段里随后怎么查都查不到。这也是“D 命令看到的数据跟源码对不上”最常见的起因。3.3 I/O端口读取用-I和-O直接访问串口与并口寄存器这是 Debug 调试实验里最靠近“接口”的部分。CPU 与外设芯片通信不是靠内存地址而是通过IN/OUT指令访问端口。Debug 把这两个指令封装成了I和O命令。以经典的 16550/8250 串口控制器为例寄存器组在 PC 机上有标准定义端口地址寄存器方向关键位3F8H数据寄存器DLAB0读写收发的数据本身3F9H中断使能寄存器写允许哪些中断3FBH线路控制寄存器写帧格式、DLAB 位3FDH线路状态寄存器读bit01 表示收到数据3FEH调制解调器状态寄存器读流控信号状态在 Debug 下可以直接探这些端口-i 3FD -i 3F8 -o 3F8 41-i 3FD读线路状态寄存器如果返回 01、03、05 这类奇数说明 bit0 为 1串口接收缓冲区里有数据待读再-i 3F8读数据寄存器就能取到那个字节。-o 3F8 41则是往发送保持寄存器写一个字节也就是向串口发出字符A。8255 并行接口的调试思路完全一样很多教材实验箱把 8255 译码在 60H~63H 这个地址区间常用控制字是80H表示 A/B/C 三组端口全部工作在方式 0 输出-o 63 80 -o 60 55第一行把控制字写入控制寄存器第二行向 A 口写55H于是 A 口 8 根引脚上就出现01010101的电平用示波器或逻辑分析仪能直接看到。如果你的实验箱基地址不是 60H 或 3F8H按原理图上的译码电路换算即可调试的本质不变就是“寄存器地图 端口地址”接口定义决定你该读哪个位置。3.4 端口数据通路验证Debug与串口调试助手的配合Debug 的I/O命令只能验证端口“此刻”的状态要验证一整条数据通路还需要从外界真正送一个字节进来。常见做法是把 DOSBox 的虚拟串口接到 Windows 的物理串口或虚拟串口对然后在另一侧用串口调试助手发数据。DOSBox 的配置文件dosbox.conf里[serial]段可以这样设置[serial] serial1directserial realport:com1这行配置把 DOSBox 客户机里的 COM1 映射到主机的物理 COM1。不过要注意主机上如果已经有串口调试助手占用了 COM1两者会冲突。我一般会用虚拟串口软件创建一对互联的串口比如 COM3 和 COM4让串口调试助手打开 COM3DOSBox 映射到 COM4这样两边可以同时工作。链路打通之后从串口调试助手的发送区用 HEX 方式发送41回到 DOSBox 的 Debug 里执行-i 3FD如果返回 01再执行-i 3F8看到AL41整条“外部设备 → 串口控制器 → CPU”的数据通路就验证完成了。这套方法同样适用于 Proteus 实验箱和真实串口模块区别只在于中间环节用的是虚拟串口还是物理连接线。4. 单步、断点与中断向量表跟踪程序与8259的调试方法4.1 T命令单步循环P命令跨过系统调用调试循环结构是T命令最典型的应用场景。下面这段代码让AL从30H加到33H然后退出-a 100 MOV CX,0003 MOV AL,30 ADD AL,01 DEC CX JNZ 0105 INT 20 -t 5各指令的装配地址依次是0100、0103、0105、0107、0108、010A。-t 5会连续执行 5 条指令后停下此时可以看到CX从 3 递减到 2AL从 30 变成 31ZF标志位的变化也显示在屏幕上。这里有个新手极易踩的坑如果单步执行到INT 20或INT 21T命令会试图跟踪进中断服务程序内部CS:IP瞬间跳到F000:xxxx之类的 BIOS/DOS 内部区域现场全乱。正确做法是用P命令代替TP把INT、CALL、LOOP当作一个整体执行完直接停在下一行指令不会陷入中断内部。P命令是我做这个实验时最后悔没早点知道的命令它可以省下大量无意义的单步操作。4.2 G命令设断点跳过重复的初始化段直击关键路径G命令是“运行到指定地址暂停”。语法是-g起始地址 断点地址可以同时跟多个断点。以刚才的循环为例如果不想一步步看完整个循环过程可以这样-g100 10A这条命令从CS:0100开始执行CPU 运行到010AINT 20所在处时暂停。Debug 会用一种相当朴素的方式实现断点在目标地址临时写入一个INT 3指令执行到这里产生断点中断调试器捕获到之后再把原来的指令字节恢复回去。理解了这一点你就能明白为什么断点地址必须落在指令边界上如果把断点设在一条指令的中间字节恢复现场后CS:IP指向的位置反汇编出来全是错乱的。调试带延时的接口程序时我一般会在延时子程序的RET后面设一个断点让它在几百毫秒的循环里自行旋转然后直接落到关键读数位置避免一遍遍人工单步。4.3 读取中断向量表验证8259中断服务程序是否挂上在“汇编与接口”实验里经常要自己写一个中断服务程序然后挂到 8259 中断控制器的某条 IRQ 上。怎么确认挂成功没有Debug 可以直接查看内存最低端的实模式中断向量表。表地址从0000:0000开始每个表项 4 个字节低位字节是偏移量IP高位字节是段地址CS。比如键盘中断是INT 09H对应 8259 的 IRQ1它的向量存放在0000:0024H定时器中断INT 08H是 IRQ0存放在0000:0020H-d 0000:0020 0027输出会显示四字节一组的数据例如20 00 40 00表示该中断向量为0040:0020。如果读者自己写的服务程序已经装好这里的CS:IP就会指向你放置服务例程的段地址如果看到的值没变说明INT 21H的25H功能没有生效或者段地址超过 8086 的 1MB 寻址范围被截断了。用这个方法再做 8259 实验比单纯看模拟波形更接近问题本质向量表是中断响应链条的第一环它错了后面的都无从谈起。4.4 与GDB、KEIL、J-Link对比调试架构其实一脉相承在 Debug 里学会看寄存器、下断点、查内存之后再接触现代调试器会发现思路完全一致。GDB 里的info registers对应R命令x/8bx对应D命令break对应G命令的断点地址step和next对应T和P。到了 ARM 单片机场景KEIL 里配合 ST-Link v2 或 J-Link 使用 JTAG/SWD 口下载调试界面上有 Watch 窗口和外设寄存器视图本质也是通过调试接口去读内核寄存器和内存而 SWD 接口定义里那两根线就是用来传输这些“读请求/写请求”的物理通道。还有同学在 ARM 调试时遇到过“调试器下载的是新代码断电后却跑旧代码”的怪现象那其实是 Flash 烧写缓冲没刷干净反过来也说明 Debug 这类 Ram 基于内存直接执行的方式反而没有这种困扰。甚至在ETM之类的硬件跟踪调试里本质也只是把T命令的“单步执行”做成了由硬件实时记录指令流。这套从 DEBUG 里建立起来的“执行控制 现场读取”两层调试架构换了工具也不会失效。5. 最容易翻车的5个调试时刻现象、原因、解决5.1 现象A命令输入立即数总报错想写MOV AX,1234HDebug 直接拒绝或装配出来的机器码不对。原因是 Debug 的汇编器约定所有数字立即数是十六进制不要也不能加H后缀H会被当作非法字符处理。解决方法是把MOV AX,1234H写成MOV AX,1234。从 MASM 或 Keil 转过来的同学需要反复提醒自己这一点否则每一条带立即数的指令都会让你怀疑人生。5.2 现象T命令单步走进INT后现场全乱CS:IP突然变成F000:xxxx寄存器和栈内容看起来完全失控。原因是T命令会跟踪进入每一次中断调用包括INT 21和INT 20而 DOS/BIOS 内部的服务例程并不适合单步跟入。解决方法是执行到这类指令前改用P命令让中断调用整体完成或者干脆把INT 21、INT 20放到代码段最末尾调试中间的计算逻辑时用不上它们。5.3 现象端口永远读不到数据-i 3FD返回的始终是00串口助手那边明明发了数据。原因通常是三个方向的问题端口基地址记错、串口参数没初始化、虚拟串口对配置反了。解决方法是先写线路控制寄存器比如-o 3FB 03把帧格式设为 8 数据位无校验 1 停止位再读3FD看 bit0如果还是 0回到串口助手重新发送一个字节并确认虚拟串口对的两端是否被两个软件分别占用。这类问题排查到最后往往发现是“硬件链路根本没通”玄学成分最少但最容易被忽略。5.4 现象G命令设断点后程序直接跑飞运行结果不是停在断点而是出现乱码指令或死循环。原因是断点地址没有落在指令边界或者设到了 ROM/只读区域Debug 无法写入INT 3指令。解决方法是先执行-u查看反汇编结果确认断点地址与某条指令的首字节对齐不要在中断向量表区域0000:0000附近设断点。断点位置选在JMP/JZ/JNZ的跳转目标上尤其危险一定先用-u核对。5.5 现象D命令显示的内存数据与源码对不上期望在某个偏移处看到自己写入的数据结果却是一堆无关字节。原因是D命令默认使用DS段而程序或手工操作可能已经修改过DS还有一种情况是变量缓冲区被后续代码越界覆盖。解决方法是查看前先-r确认当前DS值并在D命令里写全段地址例如-d ds:200排查越界时可以在数据写入前后各执行一次-d对比哪个地址范围的字节被异常改写通常很快能找到罪魁祸首。6. 进阶用Debug把一次串口轮询实验从头到尾验证6.1 先准备一条可见的串口数据链路把理论落实到具体实验最值得做的一件“小事”是用 Debug 手动完成一次串口轮询。前面提到过在dosbox.conf中把虚拟串口映射到主机串口并用串口调试助手从对端发送数据。链路就绪后在 Debug 里手工装配轮询代码反复读线路状态寄存器判断 bit0 是否为 1为 1 才去读数据寄存器。这一步比在真实 DOS 机器上做更可控因为虚拟串口的通断状态可以随时在助手里控制非常适合观察“状态位-数据”的因果关系。6.2 手动执行轮询代码并验证进入 Debug 后按下面这段汇编手工输入。它读取03FD线路状态寄存器到AL用TEST检查 bit0置 1 则顺序往下读数据否则跳回重读-a 100 MOV DX,03FD IN AL,DX TEST AL,01 JZ 0103 MOV DX,03F8 IN AL,DX INT 20 -g100 108 -i 3FD -i 3F8地址分布是0100装DX0103读状态0104做测试0106条件跳转回01030108读数据010B退出。-g100 108让它先跑起来如果串口助手里没有数据循环会一直卡在0103~0106一旦从助手发送字节3FD的 bit0 变 1JZ不跳转程序落到0108停下。停下来之后用-i 3FD和-i 3F8两条命令手工读取可以看到状态值和数据值。常见的 LSR 状态值大致如下3FD 值含义00无数据发送保持寄存器忙01接收数据就绪20发送保持寄存器空21数据已收到且可以发送下一字节我当年第一次调这个实验时总想用T命令一步步走进IN指令去看数据结果单步了几十次也等不到外部信号后来换了个思路用G命令停到读取之后再反复用I命令去读端口才发现轮询程序的本质不是“追指令执行”而是“等接口状态变化”。这个认知带我走过很多次调试验收也希望帮到你。本文还有配套的精品资源点击获取