實(shí)驗(yàn)全攻略:從Debug單步調(diào)試到子程序設(shè)計(jì))
簡介匯編語言程序設(shè)計(jì)是計(jì)算機(jī)專業(yè)本科階段的基礎(chǔ)實(shí)踐課這份武漢理工大學(xué)匯編實(shí)驗(yàn)課程報(bào)告即圍繞該課程四個(gè)實(shí)驗(yàn)整理而成適合正在學(xué)習(xí)匯編、需要參考實(shí)驗(yàn)報(bào)告寫法或調(diào)試記錄的同學(xué)使用。內(nèi)容覆蓋熟悉DOSBox與debug開發(fā)調(diào)試環(huán)境、順序程序設(shè)計(jì)、循環(huán)程序設(shè)計(jì)、子程序設(shè)計(jì)四大部分涉及MOV/PUSH、ADD/SUB、MUL/IMUL、DIV/IDIV、DAA以及AND/OR、SHL等指令并記錄了每條指令執(zhí)行后寄存器與內(nèi)存的變化。資源為單個(gè)docx文檔壓縮包約172KB小巧便于下載。已有1264人瀏覽學(xué)習(xí)。報(bào)告不僅包含實(shí)驗(yàn)?zāi)康摹⒃砼c設(shè)計(jì)步驟還提供詳細(xì)的調(diào)試過程、結(jié)果分析和指令功能總結(jié)各實(shí)驗(yàn)均配有完整指令輸入示例與單步調(diào)試記錄可幫助讀者理解CPU寄存器、堆棧、標(biāo)志位等底層工作機(jī)制為后續(xù)匯編編程與實(shí)驗(yàn)報(bào)告撰寫提供直觀參考。1. 匯編上機(jī)實(shí)驗(yàn)報(bào)告從Debug到子程序的完整閉環(huán)這份武漢理工大學(xué)匯編實(shí)驗(yàn)課程報(bào)告核心不是理論講義而是一份能跟著一步步復(fù)現(xiàn)的上機(jī)記錄它把匯編語言程序設(shè)計(jì)這門課拆成了四個(gè)必須動(dòng)手跑通的實(shí)驗(yàn)——用Debug單步觀察指令、順序程序設(shè)計(jì)、循環(huán)程序設(shè)計(jì)、子程序設(shè)計(jì)。適合正在修匯編課需要補(bǔ)實(shí)驗(yàn)報(bào)告的人也適合自學(xué)8086但卡在DOSBox和Debug環(huán)境里的初學(xué)者。拿到這份資源能省掉大量試錯(cuò)時(shí)間環(huán)境怎么搭、指令執(zhí)行后寄存器該是什么值、masm和link怎么調(diào)報(bào)告里都留有原始記錄。下面按我的拆解順序把這四個(gè)實(shí)驗(yàn)的可復(fù)現(xiàn)路徑和暗坑逐一講清楚。2. 用Debug單步觀察四類指令寄存器、棧頂與標(biāo)志位的真實(shí)變化這個(gè)實(shí)驗(yàn)是整門課的地基全部在DOSBox 0.74-3里用Debug完成。操作方式是-a輸入機(jī)器指令-t單步執(zhí)行每走一步停一次看寄存器。核心價(jià)值在于強(qiáng)迫你建立“指令—寄存器—內(nèi)存”的聯(lián)動(dòng)直覺。Debug里看到的每一個(gè)標(biāo)志位都不是考試死記的而是指令執(zhí)行后硬件真實(shí)算出來的結(jié)果。2.1 數(shù)據(jù)傳送指令MOV、PUSH與棧的小端存儲(chǔ)觀察實(shí)驗(yàn)先從棧操作開始輸入代碼如下MOV SP, 00B0 MOV AX, 8057 MOV BX, 0F79 PUSH AX PUSH BX PUSH AX單步執(zhí)行后SP和棧頂?shù)淖兓荘USH AX時(shí)SP先減2變成00AE然后把AX8057寫進(jìn)SS:00AE這個(gè)內(nèi)存單元。由于8086是小端存儲(chǔ)低字節(jié)57放在低地址00AE高字節(jié)80放在00AF。接著PUSH BX時(shí)SP再次減2到00ACBX0F79低字節(jié)79在00AC高字節(jié)0F在00AD。第三次PUSH AX后SP00AA棧頂又出現(xiàn)57和80。這段代碼的關(guān)鍵不在指令本身而在“先減SP再寫數(shù)據(jù)”這個(gè)順序。很多人以為PUSH是把數(shù)據(jù)放到當(dāng)前SP指向的位置實(shí)際上SP變化和寫入是先后兩步。觀測方法是用-d ss:00a0看棧區(qū)連續(xù)字節(jié)再對(duì)照SP的值能直接看出低字節(jié)在低地址、高字節(jié)在高地址的排列。報(bào)告里還附加了一個(gè)MOV WORD PTR [BX]類型的代碼段用WORD PTR指定操作數(shù)寬度這是8086里繞不開的語法細(xì)節(jié)——不加PTR編譯器不知道你操作的是字節(jié)還是字。2.2 算術(shù)指令SUB、NEG、MUL、IMUL、DIV、IDIV與標(biāo)志位聯(lián)動(dòng)這一組是實(shí)驗(yàn)里信息量最大的部分。以代碼段一為例MOV BX, 9548 MOV CX, 28A7 SUB BX, CX9548h-28A7h6CA1h。結(jié)果非零所以ZF0NZ無符號(hào)角度沒有借位所以CF0NC。但用有符號(hào)數(shù)眼光看9548h的符號(hào)位是1值是-2732028A7h是正數(shù)10407負(fù)減正得到-37727超出16位有符號(hào)范圍-32768~32767所以O(shè)F1OV。結(jié)果6CA1h的最高位為0SF0PL。這個(gè)例子非常適合說明“同一組二進(jìn)制數(shù)據(jù)無符號(hào)和有符號(hào)視角下標(biāo)志位含義不同”。MUL和IMUL是一組對(duì)照組MOV AL, B4 MOV BL, 11 MUL BL ; 無符號(hào)乘法B4h180180×173060轉(zhuǎn)十六進(jìn)制是0BF4hAX0BF4h。結(jié)果的高8位BFh非零說明低8位裝不下完整乘積因此CFOF1。MOV AL, B4 MOV BL, 11 IMUL BL ; 有符號(hào)乘法同樣兩個(gè)操作數(shù)在有符號(hào)世界里B4h-76-76×17-1292補(bǔ)碼是0FAF4h。AX的值和MUL完全不同但CFOF同樣為1。這里的區(qū)別是看結(jié)果的截?cái)嗍欠褚绯鯥MUL按有符號(hào)規(guī)則檢查高8位FAh不是低8位F4h的符號(hào)擴(kuò)展應(yīng)該是F4h擴(kuò)展為FFF4h所以溢出位置1。DIV和IDIV對(duì)應(yīng)展示無符號(hào)與有符號(hào)除法的商余數(shù)格式MOV AX, 400 MOV BL, B4 DIV BL ; 無符號(hào)除法AX0400h即1024BLB4h即1801024÷1805余124商在AL05余數(shù)在AH7C。MOV AX, 400 MOV BL, B4 IDIV BL ; 有符號(hào)除法IDIV版本里B4h-761024÷(-76)-13余36。商-13的補(bǔ)碼是0F3h余數(shù)3624h所以ALF3、AH24。注意IDIV的余數(shù)符號(hào)和被除數(shù)一致這里被除數(shù)是正數(shù)余數(shù)也為正。如果被除數(shù)是負(fù)數(shù)余數(shù)要帶負(fù)號(hào)這塊容易忽略。2.3 DAA與邏輯移位指令把BCD調(diào)整和位操作看清DAA指令值得單獨(dú)講。實(shí)驗(yàn)要求“十進(jìn)制286896”但直接ADD AL,68的結(jié)果是28h68h90hAL90h并不是BCD意義下的96。緊接著執(zhí)行DAA后AL才變成96h。原理是28h和68h都是BCD編碼每4位表示一位十進(jìn)制0-9二進(jìn)制加法里低4位8816產(chǎn)生進(jìn)位AF1。DAA第一步檢測AF1將AL加06h90h變成96h第二步檢查高4位96h的高4位是9不大于9且CF0不加60h。最終得到正確的BCD結(jié)果96h。MOV AL, 28 ADD AL, 68 ; AL90h DAA ; AL96h邏輯運(yùn)算和移位實(shí)驗(yàn)驗(yàn)證位操作的性質(zhì)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與運(yùn)算后是08hOR把BL高4位置139h或F0h等于F9h1234h左移4位移出的高4位丟棄、低位補(bǔ)0得到2340h。調(diào)試的重點(diǎn)是每執(zhí)行一條-t停一次看標(biāo)志位尤其關(guān)注CF和OF——它們是MUL/DIV結(jié)果的擴(kuò)展位也是實(shí)驗(yàn)報(bào)告和考試?yán)镒钊菀滋铄e(cuò)的兩個(gè)標(biāo)志。提示DAA的調(diào)整條件是“低4位大于9或AF1”和“高4位大于9或CF1”兩個(gè)獨(dú)立判斷低位的進(jìn)位會(huì)先觸發(fā)06h調(diào)整再去判斷高位的60h調(diào)整順序不能反。3. 順序程序設(shè)計(jì)asm源文件到EXE的masm/link/debug四步閉環(huán)從這一章開始進(jìn)入真正寫源文件、編譯、鏈接、調(diào)試的完整流程。和實(shí)驗(yàn)一直接在Debug里-a輸入不同順序程序必須走完masm → link → debug三步這也是后續(xù)所有實(shí)驗(yàn)的通用上機(jī)路徑。3.1 從源代碼到可執(zhí)行文件masm和link的標(biāo)準(zhǔn)調(diào)用順序在DOSBox里先掛載工作目錄mount c d:\asm c: edit source.asm源文件寫完后調(diào)用MASM匯編masm source.asm;分號(hào)表示所有后續(xù)提問都用默認(rèn)項(xiàng)masm會(huì)生成source.obj。接著調(diào)用LINKlink source.obj;同樣用分號(hào)跳過交互提示得到source.exe。如果不用分號(hào)masm會(huì)逐個(gè)詢問Object Filename、Source Listing、Cross Referencelink會(huì)詢問Object Modules、Run File、List File、Libraries——初學(xué)者最容易在這里迷路一路按回車也能過去但生成的中間文件會(huì)多出lst和map。一個(gè)容易被忽略的點(diǎn)源程序必須以end start收尾否則masm會(huì)提示缺少結(jié)束偽指令生成的目標(biāo)文件可能不完整link階段跟著報(bào)錯(cuò)。這是順序程序設(shè)計(jì)里最常見的報(bào)錯(cuò)源頭比指令寫錯(cuò)更頻繁。匯編源文件里大小寫不敏感但標(biāo)號(hào)名必須一致start:和end start要對(duì)應(yīng)同一個(gè)入口標(biāo)號(hào)。3.2 用Debug跟蹤順序程序-t單步與AX/BX寄存器的變化順序程序?qū)嶒?yàn)給了兩段代碼。第一段與棧相關(guān)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調(diào)試時(shí)關(guān)注SS:SP變化軌跡。初始sp0add sp,10后sp10。先執(zhí)行兩次pop把20000h為首地址的棧內(nèi)容彈給AX和BX再把它們壓回去再彈出來。整個(gè)過程是入棧出棧的對(duì)稱操作所以執(zhí)行到int 21h前AX和BX都回到最初壓棧前的值報(bào)告記錄最終(AX)0、(BX)0。跟蹤這類程序時(shí)建議用-t跑棧操作并每步記錄一次SP和當(dāng)前棧頂數(shù)據(jù)。因?yàn)闂?nèi)容始終跟隨SP移動(dòng)不記錄SP就看不出數(shù)據(jù)流向。Debug里可以用-d ss:0000連續(xù)查看棧區(qū)內(nèi)容對(duì)照-r顯示的SP值入棧出棧過程一目了然。3.3 解密程序查表法的地址計(jì)算與結(jié)果驗(yàn)證第二個(gè)順序程序是解密程序。密碼表存在數(shù)據(jù)段data segment db 9,0,8,2,7,4,6,3,1,5 data ends題目要求把兩位明碼轉(zhuǎn)成兩位密文例如94→57。查表邏輯是取明碼高位9作為表下標(biāo)查表[9]5作為密文高位取明碼低位4作為表下標(biāo)查表[4]7作為密文低位。代碼用si9、di4直接定位mov si, 9 mov di, 4 mov ah, [si] mov al, [di] mov ax, 4c00h int 21h結(jié)果AH5、AL7恰好對(duì)應(yīng)密文57。注意這個(gè)寫法是把明碼數(shù)字當(dāng)作偏移量直接查表不是算法變換。數(shù)據(jù)段里db定義的每個(gè)字節(jié)占一個(gè)偏移表首地址是ds:0表[9]的偏移就是9。這里有個(gè)邊界值得說如果題目要嚴(yán)格做“密文→明碼”的反向解密就需要遍歷表找到值等于密文的下標(biāo)報(bào)告里的代碼更接近正向查表輸出密文字符。實(shí)際使用中正反兩個(gè)方向建議做成兩個(gè)子程序避免混用。調(diào)試時(shí)在int 21h前用-r查看AX值高位AH和低位AL分別是密文十位和個(gè)位。4. 循環(huán)與子程序?qū)崙?zhàn)數(shù)據(jù)搬遷、人均收入計(jì)算和show_str顯存操作循環(huán)實(shí)驗(yàn)和子程序?qū)嶒?yàn)是這份報(bào)告里最有分量的部分。前者考驗(yàn)多段數(shù)據(jù)結(jié)構(gòu)和尋址方式后者考驗(yàn)棧保護(hù)和顯存地址計(jì)算都值得花時(shí)間仔細(xì)拆。4.1 多段結(jié)構(gòu)的循環(huán)程序data、table段與es段超越循環(huán)實(shí)驗(yàn)的數(shù)據(jù)結(jié)構(gòu)定義如下data段連續(xù)存放21個(gè)年份字符串每個(gè)4字節(jié)、21個(gè)收入dword每個(gè)4字節(jié)、21個(gè)雇員數(shù)word每個(gè)2字節(jié)table段預(yù)定義21 dup (year summ ne ?? )每項(xiàng)16字節(jié)。區(qū)域偏移范圍單個(gè)寬度數(shù)據(jù)內(nèi)容年份0~834字節(jié)1975等ASCII字符串收入84~1674字節(jié)dword型收入數(shù)據(jù)雇員168~2092字節(jié)word型雇員人數(shù)table0~33516字節(jié)年份收入雇員人均收入布局是寫循環(huán)的前提條件。代碼段用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這個(gè)循環(huán)里si只負(fù)責(zé)年份和收入按4字節(jié)步進(jìn)di按16字節(jié)步進(jìn)跳到table下一項(xiàng)。第一版很容易犯的錯(cuò)誤是試圖在一個(gè)循環(huán)里同時(shí)取雇員數(shù)據(jù)si按4步進(jìn)算到168時(shí)循環(huán)早已超出雇員區(qū)范圍這就是報(bào)告里明確記錄的“取雇員人數(shù)時(shí)出錯(cuò)”的翻車現(xiàn)場。正確的做法是把搬運(yùn)拆成獨(dú)立循環(huán)年份和收入用一個(gè)循環(huán)雇員數(shù)據(jù)從si168起用另一個(gè)循環(huán)按2字節(jié)步進(jìn)搬運(yùn)。4.2 人均收入計(jì)算與除法溢出邊界人均收入收入/雇員數(shù)。收入是dword需要DX:AX組合成32位被除數(shù)雇員數(shù)是word作為除數(shù)商是16位存入table。代碼分兩段完成mov si, 168 ; 數(shù)據(jù)段雇員區(qū)起點(diǎn) mov di, 10 ; table第一項(xiàng)雇員數(shù)字段偏移 mov cx, 21 s2: mov ax, [si] mov es:[di], ax add si, 2 add di, 16 loop s2 mov si, 84 ; 數(shù)據(jù)段收入?yún)^(qū)起點(diǎn) mov di, 168 ; 數(shù)據(jù)段雇員區(qū)起點(diǎn) mov bx, 13 ; table第一個(gè)人均收入字段偏移 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位無符號(hào)除法被除數(shù)高位在DX、低位在AX商在AX、余數(shù)在DX不需要管。這里被除數(shù)是收入dword除數(shù)是雇員數(shù)最大年份1995年收入5937000、雇員17800商約33316位范圍內(nèi)不會(huì)溢出。但如果把收入數(shù)據(jù)改大DIV會(huì)觸發(fā)0號(hào)中斷Debug里表現(xiàn)為“Divide overflow”程序直接終止。這個(gè)坑實(shí)驗(yàn)里不一定遇到但做擴(kuò)展題時(shí)經(jīng)常冒出來。4.3 子程序show_str行號(hào)列號(hào)到顯存地址的換算show_str的目標(biāo)是“在指定位置、用指定顏色顯示0結(jié)尾字符串”。8086文本模式顯存從B8000H開始每行80個(gè)字符每個(gè)字符占2字節(jié)ASCII碼屬性字節(jié)。因此顯存偏移計(jì)算公式是顯存偏移 (行號(hào) × 80 列號(hào)) × 2子程序先把行號(hào)列號(hào)算成偏移再逐字符搬運(yùn)show_str: push ax push bx push es mov ax, 0B800h mov es, ax mov al, 80 mul dh ; AX 行號(hào) × 80 mov dh, 0 add ax, dx ; 加上列號(hào) add ax, dx ; 列號(hào)×2因?yàn)槊總€(gè)字符占2字節(jié) 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位無符號(hào)乘法DX會(huì)被清零但保險(xiǎn)起見主程序里最好先mov dh,0。列號(hào)偏移加兩次dx等于乘以2避免手寫乘法指令。顏色屬性cl直接寫入每個(gè)字符后面的屬性字節(jié)主程序傳0cah這類值時(shí)背景色和前景色就由這一個(gè)字節(jié)決定。報(bào)告里還處理了列號(hào)超過79的情況當(dāng)dl≥80時(shí)自動(dòng)轉(zhuǎn)到下一行對(duì)應(yīng)列這是很多教材示例不處理的邊界但實(shí)際調(diào)用者傳錯(cuò)列號(hào)是常事子程序內(nèi)部兜底比調(diào)用端排查省事得多。進(jìn)入子程序先push ax/bx/es、退出前對(duì)稱pop是這里最容易漏但必須養(yǎng)成的習(xí)慣后面避坑章還會(huì)單獨(dú)說。4.4 子程序dtoc除10取余法把數(shù)值轉(zhuǎn)成ASCII字符串dtoc把二進(jìn)制數(shù)值轉(zhuǎn)成十進(jìn)制字符串。最簡單可靠的做法是反復(fù)除以10依次得到個(gè)位、十位、百位把每位加30h轉(zhuǎn)成ASCII字符。除法得到的是逆序所以從字符串尾部往前填或者先壓棧再出棧反轉(zhuǎn)dtoc: push bx push dx mov bx, 10 d: mov dx, 0 div bx ; AX÷10商在AX余數(shù)在DX add dl, 30h ; 數(shù)字→ASCII mov [si], dl dec si cmp ax, 0 jne d pop dx pop bx ret每次div前把DX清零是必須的否則DX里殘留的高位會(huì)被當(dāng)成被除數(shù)的一部分商直接不對(duì)。字符串末尾要先放一個(gè)0字節(jié)作為結(jié)束符再從后往前填入字符。這個(gè)子程序和show_str配合就是“把數(shù)據(jù)用十進(jìn)制形式顯示”的完整鏈路dtoc負(fù)責(zé)轉(zhuǎn)換show_str負(fù)責(zé)上屏。報(bào)告里對(duì)這兩個(gè)子程序的接口設(shè)計(jì)說得比較清楚——對(duì)外只暴露行號(hào)、列號(hào)、顏色和字符串首地址調(diào)用者不需要知道顯存結(jié)構(gòu)這個(gè)思想在后面的子程序里值得延續(xù)。5. 匯編上機(jī)避坑五個(gè)易翻車的寄存器、除法與尋址細(xì)節(jié)下面把四個(gè)實(shí)驗(yàn)里最容易讓新手白忙半小時(shí)的坑集中列出每條都按“現(xiàn)象→原因→解決”給這些記錄來自報(bào)告里的調(diào)試過程和我的復(fù)現(xiàn)經(jīng)驗(yàn)。5.1 現(xiàn)象循環(huán)里取雇員人數(shù)一直錯(cuò)位寫循環(huán)實(shí)驗(yàn)第一版時(shí)搬完年份和收入后在同一個(gè)循環(huán)里繼續(xù)取[si168]搬雇員結(jié)果table里雇員數(shù)字和人均收入全亂。原因收入是dword每項(xiàng)4字節(jié)雇員是word每項(xiàng)2字節(jié)同用一個(gè)si按4步進(jìn)第一次循環(huán)si0取雇員區(qū)偏移168沒問題但第二次si4偏移變172實(shí)際第二項(xiàng)雇員在170依次錯(cuò)位。解決把搬運(yùn)拆成獨(dú)立循環(huán)年份收入一個(gè)循環(huán)si步長4雇員一個(gè)循環(huán)si步長2人均計(jì)算再單獨(dú)處理。血的教訓(xùn)是循環(huán)里每一個(gè)操作數(shù)的寬度都要單獨(dú)驗(yàn)證步長不能想當(dāng)然沿用前一個(gè)循環(huán)的下標(biāo)。5.2 現(xiàn)象DIV/IDIV執(zhí)行結(jié)果完全不對(duì)有同學(xué)直接mov ax, 0400h后執(zhí)行div bl結(jié)果商和余數(shù)看起來像隨機(jī)數(shù)。原因DIV的16位被除數(shù)必須放在AX如果被除數(shù)是32位高位必須放到DXDX不作為被除數(shù)一部分時(shí)也要顯式清零IDIV還有符號(hào)要求負(fù)被除數(shù)在IDIV前要做CBWAL符號(hào)擴(kuò)展到AX或CWDAX符號(hào)擴(kuò)展到DX:AX否則DX為0負(fù)數(shù)被當(dāng)成大正數(shù)參與除法。解決寫除法前先明確“被除數(shù)是多少位、有沒有符號(hào)”再?zèng)Q定要不要CBW/CWD。實(shí)驗(yàn)一里IDIV BL的B4h是負(fù)數(shù)如果漏掉前面對(duì)AX的符號(hào)處理AL的結(jié)果絕對(duì)不是F3。5.3 現(xiàn)象PUSH之后在內(nèi)存里看到的順序和預(yù)期相反用-d ss:00a0看棧區(qū)PUSH AX后發(fā)現(xiàn)[00AE]是57、[00AF]是80總覺得“裝反了”。原因8086是小端序低字節(jié)先寫低地址。PUSH AX的完整順序是SP先減2再高字節(jié)進(jìn)[SS:SP1]、低字節(jié)進(jìn)[SS:SP]。解決看棧時(shí)養(yǎng)成“低地址看低字節(jié)”的習(xí)慣必要時(shí)用-d看連續(xù)16字節(jié)把寄存器值兩個(gè)字節(jié)拼起來對(duì)照。這個(gè)坑在后續(xù)用棧傳參、保存現(xiàn)場時(shí)會(huì)反復(fù)出現(xiàn)。5.4 現(xiàn)象子程序返回后主程序的變量全變了show_str跑完回到主程序一用AX發(fā)現(xiàn)不是調(diào)用前的值循環(huán)計(jì)數(shù)直接亂掉。原因子程序內(nèi)部修改了AX、BX、ES而調(diào)用者默認(rèn)這些寄存器在call前后保持一致——這是子程序接口的基本約定。解決在子程序入口統(tǒng)一push用到的所有寄存器ret前對(duì)稱pop回來。報(bào)告里特意在show_str開頭寫“保存子程序中用到的相關(guān)寄存器”因?yàn)閏all/ret只保證指令指針能回來不保證任何寄存器能原樣返回。5.5 現(xiàn)象字符顯示到了奇怪的位置寫show_str時(shí)如果把列號(hào)直接當(dāng)偏移加上去字符會(huì)跑到屏幕中間偏外或者覆蓋掉不該覆蓋的區(qū)域。原因每列字符實(shí)際占2字節(jié)ASCII碼屬性偏移必須按(行×80列)×2計(jì)算而不是(行×80列)。解決把公式記成“一列占兩格”先算字符位再乘2。報(bào)告里add ax,dx執(zhí)行兩次就是在做列號(hào)×2這一步漏掉所有字符都會(huì)錯(cuò)位半個(gè)屏幕寬度。6. 上機(jī)前先心算結(jié)果用十六進(jìn)制預(yù)判每個(gè)實(shí)驗(yàn)的最終值調(diào)試最耗時(shí)間的不是寫代碼而是不知道答案對(duì)不對(duì)。我的習(xí)慣是上機(jī)前把所有能算的結(jié)果先在草稿上算一遍再開DOSBox驗(yàn)證。這個(gè)習(xí)慣幫我躲過了好幾次“代碼跑通但結(jié)果錯(cuò)”的假成功。以實(shí)驗(yàn)一為例上機(jī)前先算MUL BL那一組B4h×11h先轉(zhuǎn)十進(jìn)制180×173060再轉(zhuǎn)十六進(jìn)制0BF4h同時(shí)知道結(jié)果高字節(jié)非零CFOF1IDIV BL那一組B4h是-761024÷(-76)-13余36商補(bǔ)碼0F3h、余數(shù)24h所以ALF3、AH24。算完再跑用-t單步到指令邊界看到AX0BF4h和標(biāo)志位CF1時(shí)就能確認(rèn)不是系統(tǒng)在亂跳。順序程序?qū)嶒?yàn)里的棧操作先把SP推演表畫出來PUSH AX后SP00AE、PUSH BX后SP00AC如果看到SP沒變化說明指令根本沒執(zhí)行到。另一個(gè)省時(shí)間的驗(yàn)證手法是用-u反匯編核對(duì)輸入。Debug的-a輸入很容易打錯(cuò)操作數(shù)或?qū)戝e(cuò)立即數(shù)的進(jìn)制暫停后用-u看機(jī)器碼對(duì)應(yīng)的助記符能立即發(fā)現(xiàn)“原來我寫的是mov ax, b4而不是mov al, b4”。對(duì)于循環(huán)21次的長程序沒必要全程單步可以先在int 21h處下斷點(diǎn)用g命令跳過去再用-d查看table段和data段的最終數(shù)據(jù)一次確認(rèn)整張表寫入是否正確。從那以后我每次帶人做匯編實(shí)驗(yàn)都強(qiáng)制要求先把結(jié)果心算一遍再碰鍵盤這個(gè)習(xí)慣比任何調(diào)試技巧都管用。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取