ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

汇编上机实验全攻略:从Debug单步调试到子程序设计

汇编上机实验全攻略:从Debug单步调试到子程序设计 简介汇编语言程序设计是计算机专业本科阶段的基础实践课这份武汉理工大学汇编实验课程报告即围绕该课程四个实验整理而成适合正在学习汇编、需要参考实验报告写法或调试记录的同学使用。内容覆盖熟悉DOSBox与debug开发调试环境、顺序程序设计、循环程序设计、子程序设计四大部分涉及MOV/PUSH、ADD/SUB、MUL/IMUL、DIV/IDIV、DAA以及AND/OR、SHL等指令并记录了每条指令执行后寄存器与内存的变化。资源为单个docx文档压缩包约172KB小巧便于下载。已有1264人浏览学习。报告不仅包含实验目的、原理与设计步骤还提供详细的调试过程、结果分析和指令功能总结各实验均配有完整指令输入示例与单步调试记录可帮助读者理解CPU寄存器、堆栈、标志位等底层工作机制为后续汇编编程与实验报告撰写提供直观参考。1. 汇编上机实验报告从Debug到子程序的完整闭环这份武汉理工大学汇编实验课程报告核心不是理论讲义而是一份能跟着一步步复现的上机记录它把汇编语言程序设计这门课拆成了四个必须动手跑通的实验——用Debug单步观察指令、顺序程序设计、循环程序设计、子程序设计。适合正在修汇编课需要补实验报告的人也适合自学8086但卡在DOSBox和Debug环境里的初学者。拿到这份资源能省掉大量试错时间环境怎么搭、指令执行后寄存器该是什么值、masm和link怎么调报告里都留有原始记录。下面按我的拆解顺序把这四个实验的可复现路径和暗坑逐一讲清楚。2. 用Debug单步观察四类指令寄存器、栈顶与标志位的真实变化这个实验是整门课的地基全部在DOSBox 0.74-3里用Debug完成。操作方式是-a输入机器指令-t单步执行每走一步停一次看寄存器。核心价值在于强迫你建立“指令—寄存器—内存”的联动直觉。Debug里看到的每一个标志位都不是考试死记的而是指令执行后硬件真实算出来的结果。2.1 数据传送指令MOV、PUSH与栈的小端存储观察实验先从栈操作开始输入代码如下MOV SP, 00B0 MOV AX, 8057 MOV BX, 0F79 PUSH AX PUSH BX PUSH AX单步执行后SP和栈顶的变化是PUSH AX时SP先减2变成00AE然后把AX8057写进SS:00AE这个内存单元。由于8086是小端存储低字节57放在低地址00AE高字节80放在00AF。接着PUSH BX时SP再次减2到00ACBX0F79低字节79在00AC高字节0F在00AD。第三次PUSH AX后SP00AA栈顶又出现57和80。这段代码的关键不在指令本身而在“先减SP再写数据”这个顺序。很多人以为PUSH是把数据放到当前SP指向的位置实际上SP变化和写入是先后两步。观测方法是用-d ss:00a0看栈区连续字节再对照SP的值能直接看出低字节在低地址、高字节在高地址的排列。报告里还附加了一个MOV WORD PTR [BX]类型的代码段用WORD PTR指定操作数宽度这是8086里绕不开的语法细节——不加PTR编译器不知道你操作的是字节还是字。2.2 算术指令SUB、NEG、MUL、IMUL、DIV、IDIV与标志位联动这一组是实验里信息量最大的部分。以代码段一为例MOV BX, 9548 MOV CX, 28A7 SUB BX, CX9548h-28A7h6CA1h。结果非零所以ZF0NZ无符号角度没有借位所以CF0NC。但用有符号数眼光看9548h的符号位是1值是-2732028A7h是正数10407负减正得到-37727超出16位有符号范围-32768~32767所以OF1OV。结果6CA1h的最高位为0SF0PL。这个例子非常适合说明“同一组二进制数据无符号和有符号视角下标志位含义不同”。MUL和IMUL是一组对照组MOV AL, B4 MOV BL, 11 MUL BL ; 无符号乘法B4h180180×173060转十六进制是0BF4hAX0BF4h。结果的高8位BFh非零说明低8位装不下完整乘积因此CFOF1。MOV AL, B4 MOV BL, 11 IMUL BL ; 有符号乘法同样两个操作数在有符号世界里B4h-76-76×17-1292补码是0FAF4h。AX的值和MUL完全不同但CFOF同样为1。这里的区别是看结果的截断是否溢出IMUL按有符号规则检查高8位FAh不是低8位F4h的符号扩展应该是F4h扩展为FFF4h所以溢出位置1。DIV和IDIV对应展示无符号与有符号除法的商余数格式MOV AX, 400 MOV BL, B4 DIV BL ; 无符号除法AX0400h即1024BLB4h即1801024÷1805余124商在AL05余数在AH7C。MOV AX, 400 MOV BL, B4 IDIV BL ; 有符号除法IDIV版本里B4h-761024÷(-76)-13余36。商-13的补码是0F3h余数3624h所以ALF3、AH24。注意IDIV的余数符号和被除数一致这里被除数是正数余数也为正。如果被除数是负数余数要带负号这块容易忽略。2.3 DAA与逻辑移位指令把BCD调整和位操作看清DAA指令值得单独讲。实验要求“十进制286896”但直接ADD AL,68的结果是28h68h90hAL90h并不是BCD意义下的96。紧接着执行DAA后AL才变成96h。原理是28h和68h都是BCD编码每4位表示一位十进制0-9二进制加法里低4位8816产生进位AF1。DAA第一步检测AF1将AL加06h90h变成96h第二步检查高4位96h的高4位是9不大于9且CF0不加60h。最终得到正确的BCD结果96h。MOV AL, 28 ADD AL, 68 ; AL90h DAA ; AL96h逻辑运算和移位实验验证位操作的性质MOV AL, 48 AND AL, AA ; AL08 MOV BL, 39 OR BL, F0 ; BLF9 MOV AX, 1234 MOV CL, 4 SHL AX, CL ; AX2340AND以AAh为掩码把AL的0、2、4、6位清零48h与AAh与运算后是08hOR把BL高4位置139h或F0h等于F9h1234h左移4位移出的高4位丢弃、低位补0得到2340h。调试的重点是每执行一条-t停一次看标志位尤其关注CF和OF——它们是MUL/DIV结果的扩展位也是实验报告和考试里最容易填错的两个标志。提示DAA的调整条件是“低4位大于9或AF1”和“高4位大于9或CF1”两个独立判断低位的进位会先触发06h调整再去判断高位的60h调整顺序不能反。3. 顺序程序设计asm源文件到EXE的masm/link/debug四步闭环从这一章开始进入真正写源文件、编译、链接、调试的完整流程。和实验一直接在Debug里-a输入不同顺序程序必须走完masm → link → debug三步这也是后续所有实验的通用上机路径。3.1 从源代码到可执行文件masm和link的标准调用顺序在DOSBox里先挂载工作目录mount c d:\asm c: edit source.asm源文件写完后调用MASM汇编masm source.asm;分号表示所有后续提问都用默认项masm会生成source.obj。接着调用LINKlink source.obj;同样用分号跳过交互提示得到source.exe。如果不用分号masm会逐个询问Object Filename、Source Listing、Cross Referencelink会询问Object Modules、Run File、List File、Libraries——初学者最容易在这里迷路一路按回车也能过去但生成的中间文件会多出lst和map。一个容易被忽略的点源程序必须以end start收尾否则masm会提示缺少结束伪指令生成的目标文件可能不完整link阶段跟着报错。这是顺序程序设计里最常见的报错源头比指令写错更频繁。汇编源文件里大小写不敏感但标号名必须一致start:和end start要对应同一个入口标号。3.2 用Debug跟踪顺序程序-t单步与AX/BX寄存器的变化顺序程序实验给了两段代码。第一段与栈相关mov ax, 2000h mov ss, ax mov sp, 0 add sp, 10 pop ax pop bx push ax push bx pop ax pop bx mov ax, 4c00h int 21h调试时关注SS:SP变化轨迹。初始sp0add sp,10后sp10。先执行两次pop把20000h为首地址的栈内容弹给AX和BX再把它们压回去再弹出来。整个过程是入栈出栈的对称操作所以执行到int 21h前AX和BX都回到最初压栈前的值报告记录最终(AX)0、(BX)0。跟踪这类程序时建议用-t跑栈操作并每步记录一次SP和当前栈顶数据。因为栈内容始终跟随SP移动不记录SP就看不出数据流向。Debug里可以用-d ss:0000连续查看栈区内容对照-r显示的SP值入栈出栈过程一目了然。3.3 解密程序查表法的地址计算与结果验证第二个顺序程序是解密程序。密码表存在数据段data segment db 9,0,8,2,7,4,6,3,1,5 data ends题目要求把两位明码转成两位密文例如94→57。查表逻辑是取明码高位9作为表下标查表[9]5作为密文高位取明码低位4作为表下标查表[4]7作为密文低位。代码用si9、di4直接定位mov si, 9 mov di, 4 mov ah, [si] mov al, [di] mov ax, 4c00h int 21h结果AH5、AL7恰好对应密文57。注意这个写法是把明码数字当作偏移量直接查表不是算法变换。数据段里db定义的每个字节占一个偏移表首地址是ds:0表[9]的偏移就是9。这里有个边界值得说如果题目要严格做“密文→明码”的反向解密就需要遍历表找到值等于密文的下标报告里的代码更接近正向查表输出密文字符。实际使用中正反两个方向建议做成两个子程序避免混用。调试时在int 21h前用-r查看AX值高位AH和低位AL分别是密文十位和个位。4. 循环与子程序实战数据搬迁、人均收入计算和show_str显存操作循环实验和子程序实验是这份报告里最有分量的部分。前者考验多段数据结构和寻址方式后者考验栈保护和显存地址计算都值得花时间仔细拆。4.1 多段结构的循环程序data、table段与es段超越循环实验的数据结构定义如下data段连续存放21个年份字符串每个4字节、21个收入dword每个4字节、21个雇员数word每个2字节table段预定义21 dup (year summ ne ?? )每项16字节。区域偏移范围单个宽度数据内容年份0~834字节1975等ASCII字符串收入84~1674字节dword型收入数据雇员168~2092字节word型雇员人数table0~33516字节年份收入雇员人均收入布局是写循环的前提条件。代码段用es段超越写入tablemov ax, table mov es, ax mov si, 0 mov di, 0 mov cx, 21 s1: mov ax, [si] mov es:[di], ax mov ax, [si2] mov es:[di2], ax mov ax, [si84] mov es:[di5], ax mov dx, [si842] mov es:[di7], dx add si, 4 add di, 16 loop s1这个循环里si只负责年份和收入按4字节步进di按16字节步进跳到table下一项。第一版很容易犯的错误是试图在一个循环里同时取雇员数据si按4步进算到168时循环早已超出雇员区范围这就是报告里明确记录的“取雇员人数时出错”的翻车现场。正确的做法是把搬运拆成独立循环年份和收入用一个循环雇员数据从si168起用另一个循环按2字节步进搬运。4.2 人均收入计算与除法溢出边界人均收入收入/雇员数。收入是dword需要DX:AX组合成32位被除数雇员数是word作为除数商是16位存入table。代码分两段完成mov si, 168 ; 数据段雇员区起点 mov di, 10 ; table第一项雇员数字段偏移 mov cx, 21 s2: mov ax, [si] mov es:[di], ax add si, 2 add di, 16 loop s2 mov si, 84 ; 数据段收入区起点 mov di, 168 ; 数据段雇员区起点 mov bx, 13 ; table第一个人均收入字段偏移 mov cx, 21 s3: mov ax, [si] mov dx, [si2] div word ptr [di] mov es:[bx], ax add si, 4 add di, 2 add bx, 16 loop s3div word ptr [di]是典型的32位/16位无符号除法被除数高位在DX、低位在AX商在AX、余数在DX不需要管。这里被除数是收入dword除数是雇员数最大年份1995年收入5937000、雇员17800商约33316位范围内不会溢出。但如果把收入数据改大DIV会触发0号中断Debug里表现为“Divide overflow”程序直接终止。这个坑实验里不一定遇到但做扩展题时经常冒出来。4.3 子程序show_str行号列号到显存地址的换算show_str的目标是“在指定位置、用指定颜色显示0结尾字符串”。8086文本模式显存从B8000H开始每行80个字符每个字符占2字节ASCII码属性字节。因此显存偏移计算公式是显存偏移 (行号 × 80 列号) × 2子程序先把行号列号算成偏移再逐字符搬运show_str: push ax push bx push es mov ax, 0B800h mov es, ax mov al, 80 mul dh ; AX 行号 × 80 mov dh, 0 add ax, dx ; 加上列号 add ax, dx ; 列号×2因为每个字符占2字节 mov bx, ax s: mov al, [si] cmp al, 0 je exit mov es:[bx], al mov es:[bx1], cl add bx, 2 inc si jmp short s exit: pop es pop bx pop ax retmul dh是8位无符号乘法DX会被清零但保险起见主程序里最好先mov dh,0。列号偏移加两次dx等于乘以2避免手写乘法指令。颜色属性cl直接写入每个字符后面的属性字节主程序传0cah这类值时背景色和前景色就由这一个字节决定。报告里还处理了列号超过79的情况当dl≥80时自动转到下一行对应列这是很多教材示例不处理的边界但实际调用者传错列号是常事子程序内部兜底比调用端排查省事得多。进入子程序先push ax/bx/es、退出前对称pop是这里最容易漏但必须养成的习惯后面避坑章还会单独说。4.4 子程序dtoc除10取余法把数值转成ASCII字符串dtoc把二进制数值转成十进制字符串。最简单可靠的做法是反复除以10依次得到个位、十位、百位把每位加30h转成ASCII字符。除法得到的是逆序所以从字符串尾部往前填或者先压栈再出栈反转dtoc: push bx push dx mov bx, 10 d: mov dx, 0 div bx ; AX÷10商在AX余数在DX add dl, 30h ; 数字→ASCII mov [si], dl dec si cmp ax, 0 jne d pop dx pop bx ret每次div前把DX清零是必须的否则DX里残留的高位会被当成被除数的一部分商直接不对。字符串末尾要先放一个0字节作为结束符再从后往前填入字符。这个子程序和show_str配合就是“把数据用十进制形式显示”的完整链路dtoc负责转换show_str负责上屏。报告里对这两个子程序的接口设计说得比较清楚——对外只暴露行号、列号、颜色和字符串首地址调用者不需要知道显存结构这个思想在后面的子程序里值得延续。5. 汇编上机避坑五个易翻车的寄存器、除法与寻址细节下面把四个实验里最容易让新手白忙半小时的坑集中列出每条都按“现象→原因→解决”给这些记录来自报告里的调试过程和我的复现经验。5.1 现象循环里取雇员人数一直错位写循环实验第一版时搬完年份和收入后在同一个循环里继续取[si168]搬雇员结果table里雇员数字和人均收入全乱。原因收入是dword每项4字节雇员是word每项2字节同用一个si按4步进第一次循环si0取雇员区偏移168没问题但第二次si4偏移变172实际第二项雇员在170依次错位。解决把搬运拆成独立循环年份收入一个循环si步长4雇员一个循环si步长2人均计算再单独处理。血的教训是循环里每一个操作数的宽度都要单独验证步长不能想当然沿用前一个循环的下标。5.2 现象DIV/IDIV执行结果完全不对有同学直接mov ax, 0400h后执行div bl结果商和余数看起来像随机数。原因DIV的16位被除数必须放在AX如果被除数是32位高位必须放到DXDX不作为被除数一部分时也要显式清零IDIV还有符号要求负被除数在IDIV前要做CBWAL符号扩展到AX或CWDAX符号扩展到DX:AX否则DX为0负数被当成大正数参与除法。解决写除法前先明确“被除数是多少位、有没有符号”再决定要不要CBW/CWD。实验一里IDIV BL的B4h是负数如果漏掉前面对AX的符号处理AL的结果绝对不是F3。5.3 现象PUSH之后在内存里看到的顺序和预期相反用-d ss:00a0看栈区PUSH AX后发现[00AE]是57、[00AF]是80总觉得“装反了”。原因8086是小端序低字节先写低地址。PUSH AX的完整顺序是SP先减2再高字节进[SS:SP1]、低字节进[SS:SP]。解决看栈时养成“低地址看低字节”的习惯必要时用-d看连续16字节把寄存器值两个字节拼起来对照。这个坑在后续用栈传参、保存现场时会反复出现。5.4 现象子程序返回后主程序的变量全变了show_str跑完回到主程序一用AX发现不是调用前的值循环计数直接乱掉。原因子程序内部修改了AX、BX、ES而调用者默认这些寄存器在call前后保持一致——这是子程序接口的基本约定。解决在子程序入口统一push用到的所有寄存器ret前对称pop回来。报告里特意在show_str开头写“保存子程序中用到的相关寄存器”因为call/ret只保证指令指针能回来不保证任何寄存器能原样返回。5.5 现象字符显示到了奇怪的位置写show_str时如果把列号直接当偏移加上去字符会跑到屏幕中间偏外或者覆盖掉不该覆盖的区域。原因每列字符实际占2字节ASCII码属性偏移必须按(行×80列)×2计算而不是(行×80列)。解决把公式记成“一列占两格”先算字符位再乘2。报告里add ax,dx执行两次就是在做列号×2这一步漏掉所有字符都会错位半个屏幕宽度。6. 上机前先心算结果用十六进制预判每个实验的最终值调试最耗时间的不是写代码而是不知道答案对不对。我的习惯是上机前把所有能算的结果先在草稿上算一遍再开DOSBox验证。这个习惯帮我躲过了好几次“代码跑通但结果错”的假成功。以实验一为例上机前先算MUL BL那一组B4h×11h先转十进制180×173060再转十六进制0BF4h同时知道结果高字节非零CFOF1IDIV BL那一组B4h是-761024÷(-76)-13余36商补码0F3h、余数24h所以ALF3、AH24。算完再跑用-t单步到指令边界看到AX0BF4h和标志位CF1时就能确认不是系统在乱跳。顺序程序实验里的栈操作先把SP推演表画出来PUSH AX后SP00AE、PUSH BX后SP00AC如果看到SP没变化说明指令根本没执行到。另一个省时间的验证手法是用-u反汇编核对输入。Debug的-a输入很容易打错操作数或写错立即数的进制暂停后用-u看机器码对应的助记符能立即发现“原来我写的是mov ax, b4而不是mov al, b4”。对于循环21次的长程序没必要全程单步可以先在int 21h处下断点用g命令跳过去再用-d查看table段和data段的最终数据一次确认整张表写入是否正确。从那以后我每次带人做汇编实验都强制要求先把结果心算一遍再碰键盘这个习惯比任何调试技巧都管用。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表