Hello大家好,我是程序員cxuan!這篇文章我們來進行實際操作一下Debug。
我們以后將會用到很多 Debug 命令,這里我們先來熟悉一下它們。
Debug 是 Windows / Dos 操作系統(tǒng)提供的一種功能。使用 Debug 能讓我們方便查看 CPU 各種寄存器的值、內存情況,方便我們調試指令、跟蹤程序的運行過程。
接下來我們會用到很多 debug 命令,但是使用這些命令的前提是,你需要在電腦上安裝一下 debug,Windows/Mac 都可以安裝,獲取鏈接我已經給你找出來了。阿,忘記說了,我們這里使用的是 Dos box來模擬匯編的操作環(huán)境。
傳送門(Mac 和 Windows 都是):https://www.dosbox.com/download.php?main=1
下載完成后打開 DosBox ,打開之后是這樣的。
此時我們輸入 debug 命令應該提示的是
因為我們還沒有進行連接和掛載,此時我們執(zhí)行
mount c D:\debug
執(zhí)行這條命令時,你需要現在 D 盤下創(chuàng)建一個 debug 文件夾,然后我們掛載到 debug 下面。
并且執(zhí)行 C: 切換到 C 盤路徑下。
此時我們就可以執(zhí)行 debug 命令了。
這里需要注意一點,我在 Windows 10 系統(tǒng)下搭建 Debug 環(huán)境時,在掛載完成后輸入 debug ,還是提示 Illegal command:debug ,此時你需要再下載一個 debug.exe ,貼心的我也把下載地址給你了。
下載地址:https://pan.baidu.com/s/177arSA34plWqV-iyffWpEw#list/path=%2F 密碼:3akd
需要下載里面的 debug.exe,然后把它放在你掛載的路徑下,這里我掛載的路徑時 D 盤下的 debug 文件夾。
放置完成之后,再輸入 debug 就可以了。
因為每次打開 Dosbox 都會執(zhí)行上面這些命令,真的好煩,那怎么辦呢?一個簡單的辦法是在 Dosbox 安裝路徑下找到
打開之后,在末尾鍵入
就 OK 了,下次直接打開 Dosbox ,會默認執(zhí)行這三條命令,至此,就是我搭建 Dosbox 遇到的所有問題了。
玩兒匯編得學會用 Debug ,Debug 是一種調試程序,通過 Debug 能讓我們能夠看到內存值,跟蹤堆棧情況,看到寄存器所暫存的內容等,同時也能夠更好地幫助我們理解匯編代碼,所以學會 Debug ,非常重要,這是一種不可或缺的動手能力。
下面我們會用到幾種 Debug 命令,這里先簡單介紹下。
Debug 命令有很多,不過常用的一般就上面這幾個。
好了,現在我們直接進入正題,開始在 Dosbox 上正式進行 Debug 操作,首先打開 Dosbox。
嗯。。。。。。這個界面我們打開很多次了。
那我寫個命令呢?好吧,沒演示過,下面就來了!
親,用 Debug -r 就可以查看和修改 CPU 寄存器內容了呢。
查看寄存器內容。
這里需要注意一下 -r 大小寫的問題,Debug -r 是查看寄存器內容。而 -R 則是無效指令。
上圖列出來了很多寄存器,你可能覺得無從下手,不要亂,我們先從最基本的開始入手,也就是 CS 和 IP,CS(Code Segment)是代碼段寄存器,一般也被稱為段基址,可以認為是程序訪問的入口,CPU 需要從 CS 中找到從哪個位置開始取指執(zhí)行,但是我們還不知道要取哪一段,這時候 IP 的作用就體現出來了,IP(Instruction Pointer)就是指令指針寄存器,也叫做偏移地址,它會告訴我們從段基址開始,取哪一段的地址。
可以使用段基址:偏移地址來確定內存中的指定地址。
這里我們只是簡單聊一下這兩個寄存器的概念,要了解這兩個寄存器的具體作用,可以看筆者的上一篇文章
使用 -r 也能夠修改寄存器的內容,如下所示
-r 一般的格式是 -r 寄存器,然后系統(tǒng)會進行冒號提示,后面就是你要修改的內容。
使用 -d 指令可以查看內存中的內容。
輸出的內存值默認是按照 CS:IP 的地址開始的,由于 CS 的值默認是 073F,而 IP 默認是 0100,所以 -d 的內存值是 073F:0100 。
-d 的格式很多,下面只介紹一下常用的幾種格式。
形似 -d 1000:0 這種 -d 段基址 偏移地址的格式可以產生如下輸出。
如上圖所示,Debug 會列出指定內存單元中的的內容。上圖中的每一個 00 都表示 8 位,如果是 4A,那么這八位展開來說就是 0010 1011 。每一行有 16 個 8 位,所以構成了 128 位內存地址。
為什么都是 00 呢,因為內存單元的值沒有被改寫,說白了就是這塊內存區(qū)域沒有存值,如何改寫我們后面會說。
每一行的中間都有一個 -,這個是為了便于我們閱讀來設置的,- 號前后都有 8 個內存單元,這樣便于查看。
右側幾個 ...... 表示每個內存單元可顯示的 ASCII 碼字符,因為內存沒有值,所以也沒有對應的 ASCII 碼。我們可以數一下,每行有 16 個 . ,這表示每一個 00 都對應了一個 ASCII 碼。
我們可以使用 -d 1000:9 這種 -d 段基址:起始偏移地址 格式來顯示從 1000 的第幾位開始。
Debug 從 1000:9 開始,一直到 1000:88,一共是 128 個字節(jié),第一行中的 1000:0 ~ 1000:8 中的內容沒有顯示。
還可以使用 -d 1000:0 9 這種 -d 段基址:起始偏移地址 結尾偏移地址的格式來輸出。
還可以是使用 -d 偏移地址來在不指定段基址的情況下,查看內存值。
上面說的都是查看內存中指定位置或者區(qū)域的值,下面我們要來改寫一下內存值。
使用 -e 可以改寫內存值,比如我們想要改寫 1000:0 ~ 1000:f 中的內容,可以使用 -e 1000:0 0 1 2 3 4 5 6 7 8 9 0 a b c d e f 這種方式,如下圖所示。
這里需要注意下,在進行 -e 改寫的時候,每個值中間都有一個空格,如果沒有空格的話,會當做一個內存值來看待。
然后用 -d 1000:0 看到我們剛改寫的內存值。
還可以使用提問的方式來逐個修改從某一地址開始的內存單元的內容。
還是用 1000:100 來舉例子,輸出 -e 1000:100 后按下回車鍵。
如上圖所示,可以看到我們先輸入了一次 -e 1000:100 這個指令,然后按下了回車鍵。
注意,如果這里你按下了回車鍵,就相當于整個 -e 改寫的過程已經完成。
如果你想要繼續(xù)改寫后面內存中的值,你需要按下空格鍵。
我們改寫了 1000:100 之后的內存值,然后使用 -d 1000:100 查看我們改寫的內容是否生效。
-e 命令還可以支持寫入字符,比如我們可以向 1000:0 這個位置開始寫入數值和字符,-e 1000:0 1 'a' 2 'b' e 'c' 。
如上圖所示,當我們向內存寫入字符 'a' 'b' 'c' 的時候,會自動轉換為 ASCII 碼進行存儲,在最右側可以找到剛剛寫入的字符。
如何向內存中寫入一段機器碼呢?比如我們想要在內存中寫入一段機器碼。
我們可以使用 -e 來進行寫入,向內存中寫入 b8 01 00 b9 02 00 01 c8 這個機器碼,如下所示
我們使用 -e 寫入之后,使用 -d 查看內存值,可以發(fā)現我們剛剛寫入的值,但是卻看不到機器碼,所以機器碼該如何看呢?
別急,還有個 -u 命令,這個就是看機器碼的,如下圖所示,我們使用 -u 命令顯示我們寫入的機器碼。
可以看到 1000:0000 ~ 1000:0006 這個內存地址使我們寫入的機器碼,-u 這個命令就是將內存單元的內容翻譯為匯編指令并顯示。
-u 輸出的結果分為三部分顯示:
1000:0 處存放的是寫入的機器碼 B8 01 00 組成的機器指令,對應的匯編指令是 MOV AX,0001。
1000:0003 處存放的是寫入的機器碼 B9 02 00 組成的機器指令,對應的匯編指令是 MOV CX,0002。
1000:0006 處存放的是寫入的機器碼 C1 C8 所組成的機器指令,對應的匯編指令是 add ax,cx。
上面介紹的一系列指令包括我們上面提到的 Debug -e 機器碼都是向內存中進行寫入,那么如何執(zhí)行這些指令呢?
我們可以使用 Debug -t 來執(zhí)行寫入的指令。使用 Debug -t 可以執(zhí)行由 CS:IP 指向的指令。
既然是 -t 能夠執(zhí)行從 CS:IP 指向的命令,所以我們有必要將 CS:IP 指向 1000:0(因為我們前面將指令寫在了 1000:0 處)。
首先我們需要執(zhí)行 -r cs 1000 ,-r ip 0 把 CS:IP 賦值為 1000:0。
然后執(zhí)行 -t 指令,下圖是已經執(zhí)行過的指令截圖。
可以看到,執(zhí)行完 -t 指令之后,MOV AX,0001 這條指令被執(zhí)行,當前 AX 寄存器的內容變?yōu)榱?0001,這條匯編指令的意思就是把 0001 移動到 AX 寄存器中。
繼續(xù)執(zhí)行 -t 之后,我們可以看到寄存器的變化。
畢竟機器指令不是那么好懂,寫入很不方便,所以有沒有辦法能夠支持我們直接寫入匯編指令呢?還真有,Debug 提供了 -a 這種方式來實現匯編指令的寫入。如下圖所示
可以看到,我們使用了 -a 命令來對 1000:0 進行寫入,分別輸入 mov ax,1 mov bx,2 mov cx,3 add ax,bx add ax,cx add ax,ax 指令,然后按回車進行確定執(zhí)行。
我們使用 -d 1000:0 f 可以看到從偏移地址 0 處開始的第 f 個內存指令(因為最大寫入的地址只是 f)。
上圖中的 1000:000F 為什么有值呢,因為我們上面已經執(zhí)行過這個寫入了。
另外,使用 -a 可以從一個預設的地址處開始輸入指令。
今天和大家聊了一下 Debug 的基本用法,主要包括
匯編指令的選項有很多,上面介紹的這些屬于經常用到的指令,這些指令要能夠熟練使用。
1.使用Debug,將程序(見實驗源代碼)寫入內存,逐條執(zhí)行,觀察每條指令執(zhí)行后,CPU中相關寄存器中內存的變化。
2.將下面三條指令(見實驗源代碼)寫入從2000:0開始的內存單元中,利用這三條指令計算2的8次方。
3.查看內存中存有的PC機主板上的ROM生產日期并試圖修改。
4.觀察下圖中的實驗過程,分析為何2000:0~2000:f中的內容會發(fā)生改變
(1.1)實驗源代碼(粘貼源代碼):
機器碼 匯編指令
b8 20 4e mov ax,4E20H
05 16 14 add ax,1416H
bb 00 20 mov bx,2000H
01 d8 add ax,bx
89 c3 mov bx,ax
01 d8 add ax,bx
b8 1a 00 mov ax,001AH
bb 26 00 mov bx,0026H
00 d8 add al,bl
00 dc add ah,bl
00 c7 add bh,al
b4 00 mov ah,0
00 d8 add al,bl
04 9c add al,9CH
(1.2)實驗代碼、過程、相應結果(截圖)并對實驗進行說明和分析:
Win10下可以借用Dosbox調試Debug,如下圖
用Debug下的-d命令查看1000:0之后128個內存單元中的內容
用Debug下的-a命令以匯編語言的形式寫入從1000:0開始的內存單元中
使用Debug下的-d命令查看1000:0內存中的內容核對
在-t命令之前注意CS:IP當前指向,這里指向073F:0102,需要修改其指向
使用Debug下的-r命令修改CS:IP指向,如下圖
使用Debug下的-t命令逐次執(zhí)行1000:0中的指令,截圖如下
歷次-t命令下CS:IP指向和寄存器中內容如下:
CS:IP AX BX
1000:0000 0000 0000
1000:0003 4E20 0000
1000:0006 6236 0000
1000:0009 6236 2000
1000:000B 8236 2000
1000:000D 8236 8236
1000:000F 046C 8236
1000:0012 001A 8236
1000:0015 001A 0026
1000:0017 0040 0026
1000:0019 2640 0026
1000:001B 2640 4026
1000:001D 0040 4026
1000:001F 0066 4026
1000:0021 0002 4026
(2.1)實驗源代碼:
mov ax,1 (從2000:0開始的內存單元)
add ax,ax
jmp 2000:0003 (觀察跳到什么地方了?)
(2.2)實驗代碼、過程、相應結果(截圖)并對實驗進行說明和分析:
源代碼寫入過程類似上面1.2的過程,這里不再贅述,僅展示截圖
通過jmp指令,在執(zhí)行一次mov ax,1后程序將在add ax,ax和jmp 2000:0003之間循環(huán)往復,直到計算出2的8次方
(下面忽略中間過程,僅關注最終結果即可)
指令執(zhí)行過程紛繁復雜!
(3.1)實驗代碼、過程、相應結果(截圖)并對實驗進行說明和分析:
由于dosbox模擬一個真實的電腦環(huán)境,故01/01/92與主板ROM不同可以理解。
(1.1)實驗源代碼(粘貼源代碼):
mov ax,ffff
mov ds,ax
mov ax,2200
mov ss,ax
mov sp,0100
mov ax,[0] ;ax=C0EA
add ax,[2] ;ax=C0FC
mov bx,[4] ;bx=30F0
add bx,[6] ;bx=6021
push ax ;sp= 00FE ;修改的內存單元的地址是2200:00FE 內容為C0FC
push bx ;sp= 00FC ;修改的內存單元的地址是2200:00FC 內容為6021
pop ax ;sp= 00FE ;ax=6021
pop bx ;sp=0100 ;bx=C0FC
push [4] ;sp= 00FE ;修改的內存單元的地址是2200:00FE 內容為30F0
push [6] ;sp= 00FC ;修改的內存單元的地址是2200:00FC 內容為2F31
(1.2)實驗代碼、過程、相應結果(截圖)并對實驗進行說明和分析:
修改CS:IP,使之指向2000:0000,并查看當前寄存器中內容:
執(zhí)行mov ax,ffff
執(zhí)行mov ds,ax
執(zhí)行mov ax,220
執(zhí)行mov ss,ax,此處忘記截圖,可參考下圖
執(zhí)行mov sp,0100
執(zhí)行mov ax,[0]
執(zhí)行add ax,[2]
執(zhí)行mov bx,[4]
執(zhí)行add bx,[6]
執(zhí)行push ax
執(zhí)行push bx
執(zhí)行pop ax
執(zhí)行pop bx
執(zhí)行push [4]
執(zhí)行push [6]
相關填空見(1.1)
(2.1) 實驗源代碼(粘貼源代碼):
mov ax,1000H
mov ds,ax
mov ds,[0]
add ds,ax
(2.2)實驗代碼、過程、相應結果(截圖)并對實驗進行說明和分析:
輸入指令過程中,出現錯誤,是因為add,sub不能對段寄存器ds進行操作
運行前三條指令是正常的(*)
若一定要執(zhí)行add ds,ax,可以采用類似向ds輸入數據的方法,如下
運行結果如下
由于前面在運行不完整程序的前三條指令(*)后,寄存器ds的值發(fā)生變化,導致bx的值發(fā)生變化,進而導致add bx,ax運行后bx變化,最終導致ds變化,所以在-t命令后截圖中寄存器的值與之前不同,可以通過正式程序之前提前修改ds的值,使之與之前一致來消除影響。
(3.1)是因為中斷的影響
CS:IP指向指令的內存單元地址,CPU通過它區(qū)分指令和數據。DS存放要訪問數據的段地址,CPU是不是也可以通過它來區(qū)分指令和數據呢?SS:SP時刻指向棧頂,對于PUSH指令,先進行SP=SP-2再送數據入棧;POP指令相反,先取出棧內數據再SP=SP+2。