來看三段程式
學習任何一門語言都不能少的了 debug ,彙編也是。
我前面這篇文章給大家寫了如何在 windows 、mac 等作業系統的機器上使用 dosbox 的 masm、link、debug 功能編譯、解釋、執行一個彙編程式,文章連結如下:
手把手教你彙編 Debug
debug 程式執行過程
下面我們就依據這幾個功能來跟蹤一下程式的執行過程。
debug 對我們來說非常重要,有很多程式碼細節和問題透過肉眼是觀察出來的,我們肉眼可能能夠判斷一些簡單的程式問題,但是對於很多隱藏較深的問題,還是要依據 debug 才能發現。
下面是一段彙編程式碼,這段彙編程式碼我之前的文章中也給大家寫過。
assume cs:codesg
codesg segment
mov ax,0123h
mov bx,0456h
add ax,bx
add ax,ax
mov ax,4c00h
int 21h
codesg ends
end
新建文字檔案,把程式碼 cv 過去,然後右鍵儲存,使用 dosbox 將其編譯為 1.obj 檔案,連結為 1.exe 檔案後,我們使用 debug 1.exe
命令來分析一下這段程式,並用 -r 命令來看一下初始的暫存器情況。
程式初始狀態下,可以看到 CX 中的資料為 000F,這也表示著程式的長度是 000F,1.exe 中共有 15 個位元組,CX 中的內容為 000FH。
好,現在我們已經知道程式被成功的載入記憶體並執行起來了,但是我們現在先不妨想一下,被連結成為 EXE 的程式會被裝入記憶體的哪個地方的呢?我們怎麼知道程式被裝入在哪裡呢?
程式裝載的過程分下面幾步:
首先程式會從記憶體中找到一塊區域,記為初始地址 SA,此時的偏移地址為 0 的這樣一塊足夠容量的記憶體區域。
在這段區域內的頭 256 個位元組中,會建立一塊稱為程式段字首(Program Segment Prefix ,PSP)的區域,這塊區域被 DOS 用來和被載入的程式進行通訊。
從這塊程式的 256 個位元組開始處,也就是在 PSP 程式段字首的後面,程式會被載入到這裡,此時程式的初始地址是 SA + 10H,偏移地址為 0 。也就是 SA + 10H : 0,所以程式的初始地址就是 CS = 076AH ,IP = 0000H。
程式被裝入記憶體後,由 DS 段暫存器存放著記憶體區的段地址,此時記憶體區域的偏移量為 0 ,所以此時的實體地址為 SA * 16:0,我們並不用知道真實的 DS 是多少,反正都是由作業系統和 DOS 分配的。
然後這個記憶體區域的前 256 個位元組被用於存放 PSP ,所以程式的實體地址為 SA * 16 + 256 : 0 。
SA * 16 + 256 = SA * 16 + 16 * 16 = (SA + 16) * 16 ,轉換為 16 進位制就是 SA + 10H,所以實體地址就是 SA + 10H : 0。
我們上面 debug 1.exe 之後可以看到,DS 段暫存器的值為 076AH ,而 CS 段暫存器的值為 076BH ,正好符合 076A * 16 + 10 = 076BH (注意這裡的 * 16 就是左移 4 位的意思,之前文章中也解釋過原因。)
我們使用 -u 指令可以看到完整的彙編原始碼。
上圖中用紅框圈出來的就是我們這段彙編程式的原始碼,可以看到這是一個程式段,程式段的段地址始終為 076A,偏移地址在不斷變化。
我們使用 -t 命令來單步執行以下這段程式,如下圖所示。
(為了連續的觀察一下程式的執行結果,我索性直接把主要的程式步驟執行完了。)
這段程式就是 mov 和 add 的基本使用,將 0123 送入 AX 暫存器,將 0456 送入 BX 暫存器,對 AX 暫存器執行 AX = AX + BX ,再對 AX 執行 AX = AX + AX。
程式繼續向下執行,當執行到 int 21H 處,程式執行完畢,此時要使用 -p 命令結束程式的執行,如下圖所示。
當顯示 Program terminated normally 時,表示程式正常結束,這裡大家先不用考慮為什麼執行到 int 21 處才執行 -p 命令,也不用關心 mov ax,4c00 和 int 21 是什麼意思,大家先記住就行。
由於程式裝載的過程是 command 將程式裝載進入記憶體,然後 debug 程式對 exe 程式其進行跟蹤,所以程式退出後也是先從 exe 程式退出到 debug 程式中,由 debug 程式再退回到 command 程式中。
下面再分析一段程式,彙編原始碼
assume cs:codesg
codesg segment
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
codesg ends
end
仍然是將其儲存為 test.txt,然後執行編譯和連結操作,將其生成可執行檔案 test.exe,觀察其執行過程。
我們先使用 -r 檢視一下初始暫存器的內容。
主要觀察一下 CX 、DS 、CS 和 IP 的值,是否和我們上面描述的一致,CX 存放程式長度,DS 存放程式段地址,CS 存放程式初始地址,IP 存放程式偏移地址。
再使用 -u 看一下 exe 程式的原始碼,這個 exe 程式是經過編譯和連結之後的程式。
我們來分析一下這段,這是一段棧段的入棧和出棧的程式,首先
mov ax,2000H
mov ss,ax
mov sp,0
是設定棧段的棧頂指令,執行完成後會設定棧頂的實體地址為 20000 H ,即 SS:SP = 2000:0000。
我們執行這個程式的過程中,發現 mov sp,0 這個指令為什麼沒有出現呢?難道是我們漏寫了?檢視了一下,原始碼確實是有這條指令的,難道是沒有執行?
為了驗證這個假設,我們重新 debug 一下這段程式,然後先把 SP 的值進行修改,如下圖所示。
剛開始,我們使用 -r 把 sp 的值改成 0002,然後單步執行,在執行到 mov ss,ax 之後,發現 SP 的值變為 0000,這也就是說 mov sp,0 這條指令其實是執行了的,只是 debug 模式下沒有顯示而已。
程式繼續向下執行,下面是兩個 pop 出棧操作。
pop ax 和 pop bx 做了兩件事:把暫存器清空;棧頂位置 + 2 ,所以 ax 和 bx 暫存器的內容為 0 ,並且 SP = SP + 2 ,執行後 SP = 000E。
之後是兩個 push 操作,把出棧的兩個暫存器再進行入棧,如下圖所示。
push 操作也做了兩件事情,將暫存器入棧,SP = SP - 2,由於 ax 和 bx 已經 pop 出棧了,所以暫存器內容為 0 ,最後再進行 pop 操作,然後再結束程式的執行過程。
我們再來看一下 PSP 的情況,由於程式被裝入的時候前 256 個位元組是 PSP 所佔用的,此時 DS(SA)處就是 PSP 的起始地址,而 CS = SA + 10H ,也就是 CS = 076AH。
debug 迴圈程式
下面我們來 debug 一下迴圈程式,看看有哪些有意思的細節。
現在有這樣一道問題,計算 ffff:0006 單元中的數乘 3 ,讓結果儲存在 dx 中。
針對這個問題,有幾個點需要思考:
我們知道 ,8086 組合語言中單個儲存單元所能儲存的最大值是 8 位,一個位元組長度,範圍是 0 - 255 之間,而一個暫存器 dx 中可容納的最大值是 16 位,兩個位元組長度,範圍是 0 - 65535,即使 255 * 3 也小於 65535,很顯然乘以 3 之後,dx 中能夠存放的下。 數乘 3 相當於是迴圈做 add 自身操作 3 次,所以需要用加法來實現乘法,可以直接使用 dx 進行累加,不過需要一個 ax 來進行中轉。 ffff:6 記憶體單元是一個位元組單元,而 ax 暫存器能容納的是一個字單元,無法直接賦值,該如何做呢?因為 ax 可以看做 al 和 ah ,而 al 和 ah 又是兩個單獨的暫存器,它們之間不會發生值溢位,所以讓 ah = 0 ,al = 記憶體單元的值即可。
所以這段彙編程式的程式碼如下
assume cs:codesg
codesg segment
mov ax,0ffffh
mov ds,ax
mov ah,0
mov al,[6]
mov cx,3
s: add dx,ax
loop s
mov ax,4c00h
int 21h
codesg ends
end
編寫完畢,編譯連結成 exe 程式後,對其進行 debug xxx.exe 操作。
我們來看下程式的執行過程。
前兩段沒毛病,設定 DS 段暫存器的值為 FFFF 。然後繼續向下執行
執行到 mov al,[6] 的時候我發現,怎麼 AX 暫存器中的內容變成 0006 了?我不是想要把 06 放入 ax 中啊,我是想把 ffff:06 記憶體單元中的值放入 ax 中啊,我突然意識到編譯器是個傻子。
經過我認真仔細細心耐心用心的排查了一番問題之後,我方才大悟,原來我是個傻子!不知道各位小夥伴們看出來我程式碼的問題了嗎?
我怎麼敢在源程式中把立即數當做記憶體偏移地址來用呢?必須要用 bx 中轉啊!
這也就是說,編譯器編譯完原始碼之後,會把 06 當做立即數使用,如果想要使 06 表示記憶體地址,必須要用 bx 進行中轉,修改之後的原始碼如下:
assume cs:codesg
codesg segment
mov ax,0ffffh
mov ds,ax
mov bx,6
mov ah,0
mov al,[bx] # 必須要用 bx 進行中轉,才能表示記憶體地址
mov dx,0 # 累加暫存器清 0
mov cx,3
s: add dx,ax
loop s
mov ax,4c00h
int 21h
codesg ends
end
然後再重新連結成為 exe 程式之後,我們一步一步 debug 看一下。
執行到 mov al,[bx] 的時候,我們發現,此時右側有個 ds:0006 = 31,這段程式碼表示的是 ds:0006 處記憶體單元的值是 31,這才表明我們的程式是正確的。
繼續向下執行程式。
前兩條指令執行完成後,(dx) = 0 ,(cx) = 3,完成對累加暫存器的清空和迴圈計數器的賦值操作。最後一條指令是第一次迴圈操作指令,此時 CS:IP 指向 076A:0012 ,繼續向下執行。
可以看到,第一次 add dx,ax 執行完成後 IP = 0014H ,此時指向的指令是 LOOP 0012,這條指令的意思是讓程式再執行一次 (IP) = 0012H 處的指令,也就是再執行一次 add dx,ax,可以看到 cx 的值變成了 0002,因為迴圈指令執行後 (cx) = (cx) - 2 ,然後再向下執行,發現後面的迴圈指令還是 LOOP 0012 ,再執行一次 add dx,ax,一直到 (cx) = 0 後結束程式執行,如下圖所示
可以發現,整個程式一共迴圈三次,最終 dx 中的值是 93 ,程式執行到 int 21H 處,使用 -p 命令結束程式的執行。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2933262/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 5G來了,普通人看熱鬧,程式設計師看顛覆程式設計師
- 從例子來看BFC
- 淺看小程式
- JDBC複寫上課程式碼(對岸的女生看過來篇)JDBC
- 業務流程管理三段論之規範化
- 11.第四章直言三段論
- 「訊息佇列」看過來!佇列
- 春招來了,看神仙打架
- 三段程式碼打造好看的流式佈局,flutter之wrap【flutter20個例項之七】Flutter
- 重灌Mac系統教程速度來看Mac
- 別進來看!Composer騷操作2.0
- verilog學習筆記——三段式狀態機筆記
- 三段實習經歷總結的血淚教訓
- 想進阿里、京東、美團面試的Java程式設計師看過來阿里面試Java程式設計師
- 剝開比原看程式碼08:比原的Dashboard是怎麼做出來的?
- 三段實習經歷告訴你找實習的真相
- 12.第四章直言三段論練習題
- 笑話幾隻,網上看來的
- 職場人都看過來,零程式設計的視覺化大屏軟體程式設計視覺化
- 再換一種角度來看遊戲,來看死亡擱淺遊戲
- 【底層原理】從快取來看區域性性提高程式執行效率的原因快取
- 最火的小程式開發需要什麼知識,你造麼?抓緊看過來
- 【C++】從設計原理來看string類C++
- 線上excel轉換成pdf,看過來!Excel
- 是的你沒看錯,HTTP3來了HTTP
- 未來雲原生 | CIF 論壇精彩看點
- 物聯網?快來看 Arduino 上雲啦UI
- 程式設計師正確看程式碼的方式程式設計師
- 從程式語言排行看程式設計師信仰程式設計師
- 2018 年第 46 周沸點看點:請用四個字來形容你的程式碼水平
- 阿里巴巴食堂:看菜名我跪了~程式設計師:給我來個“油炸產品經理”阿里程式設計師
- 換個視角來看git命令與程式碼庫發生網路互動報錯事件Git事件
- 從因果關係來看小樣本學習
- UI設計新手看過來,UI教程教學UI
- 勞動合同免費分享!需要的看過來
- 從 Discord 看未來社交的「超級群」模式模式
- 通過一道題來看React事件模型React事件模型
- 大郎!快起來看多執行緒啦!執行緒