文章目錄
- 前言
- 1. 編程處理0號中斷
- 1.1 效果演示
- 1.2 分析所要編寫的中斷處理程序
- 1.2.1 引發中斷
- 1.2.2 中斷處理程序
- 1.2.3 中斷處理程序do0應該存放的位置
- 1.2.4 中斷向量表的修改
- 1.2.5 總結
- 1.3 程序框架
- 1.4 注意事項
- 1.5 從CPU的角度看中斷處理程序
- 1.6 一些問題的思考與解答
- 2. 安裝
- 2.1 使用movsb指令
- 2.2 明確執行rep movsb前所設置的信息
- 2.3 do0代碼的長度
- 結語
前言
📌
匯編語言是很多相關課程(如數據結構、操作系統、微機原理)的重要基礎。但僅僅從課程的角度出發就太片面了,其實學習匯編語言可以深入理解計算機底層工作原理,提升代碼效率,尤其在嵌入式系統和性能優化方面有重要作用。此外,它在逆向工程和安全領域不可或缺,幫助分析軟件運行機制并增強漏洞修復能力。
本專欄的匯編語言學習章節主要是依據王爽老師的《匯編語言》來寫的,和書中一樣為了使學習的過程容易展開,我們采用以8086CPU為中央處理器的PC機來進行學習。
1. 編程處理0號中斷
1.1 效果演示
現在我們考慮改變一下0號中斷處理程序的功能,即重新編寫一個0號中斷處理程序,它的功能是在屏幕中間顯示“overflow!”然后返回到操作系統,如下圖所示。
當CPU 執行div bh
后,發生了除法溢出錯誤,產生0號中斷信息,引發中斷過程,CPU 執行我們編寫的0號中斷處理程序。在屏幕中間顯示提示信息“overflow!”后,返回到操作系統中。
1.2 分析所要編寫的中斷處理程序
1.2.1 引發中斷
當發生除法溢出的時候,產生0號中斷信息,從而引發中斷過程。
此時,CPU將進行以下工作:
① 取得中斷類型碼0;
② 標志寄存器入棧,TF、IF設置為0;
③ CS、IP入棧;
④ (IP) = (0*4),(CS) = (0*4+2)
1.2.2 中斷處理程序
可見 ,當中斷 0 發生時,CPU將轉去執行中斷處理程序。
只要按如下步驟編寫中斷處理程序,當中斷0發生時,即可顯示“overfow!”。
① 相關處理。
② 向顯示緩沖區送字符串“overfow!”。
③ 返回DOS
我們將這段程序稱為do0。
1.2.3 中斷處理程序do0應該存放的位置
現在的問題是:do0 應放在內存中。
因為除法溢出隨時可能發生,CPU隨時都可能將 CS:IP指向 do0的入口,執行程序。
那么do0應該放在哪里呢?
由于我們是在操作系統之上使用計算機,所有的硬件資源都在操作系統的管理之下,所以我們要想得到一塊內存存放do0,應該向操作系統申請。
但在這里出于兩個原因我們不想這樣做:
-
原因之一:過多地討論申請內存將偏離問題主線;
-
原因之二:我們學習匯編的一個重要目的就是要獲得對計算機底層的編程體驗。所以,在可能的情況下,我們不去理會操作系統,而直接面向硬件資源。
問題變得簡單而直接,我們只需找到一塊別的程序不會用到的內存區,將do0傳送到其中即可。
前面講到,內存0000:0000~0000:03FF,大小為1KB的空間是系統存放中斷處理程序入口地址的中斷向量表。8086支持256個中斷,但是,實際上,系統中要處理的中斷事件遠沒有達到256個。所以在中斷向量表中,有許多單元是空的。
中斷向量表是PC系統中最重要的內存區,只用來存放中斷處理程序的入口地址,DOS系統和其他應用程序都不會隨便使用這段空間。可以利用中斷向量表中的空閑單元來存放我們的程序。
一般情況下,從0000:0200至0000:02FF的256個字節的空間所對應的中斷向量表項都是空的,操作系統和其他應用程序都不占用。我們在前面的內容中使用過這段空間。
根據以前的編程經驗,我們可以估計出,do0的長度不可能超過256個字節。
結論:我們可以將do0傳送到內存0000:0200處。
1.2.4 中斷向量表的修改
我們將中斷處理程序do0放到 0000:0200 后,若要使得除法溢出發生的時候,CPU轉去執行do0,則必須將do0的入口地址。即0000:0200登記在中斷向量表的對應表項中。
因為除法溢出對應的中斷類型碼為0,它的中斷處理程序的入口地址應該從0×4地址單元開始存放,段地址存放在 0×4+2 字單元中,偏移地址存放在0×4字單元中。
也就是說要將do0的段地址0存放在 0000:0002 字單元中,將偏移地址200H存放在0000:0000字單元中。
1.2.5 總結
總結上面的分析,我們要做以下幾件事情:
(1)編寫可以顯示“overfow!”的中斷處理程序:do0
(2)將do0送入內存0000:0200處
(3)將do0的入口地址0000:0200存儲在中斷向量表0號表項中
1.3 程序框架
程序的框架如下:
assume cs:code
code segmentstart: do0安裝程序設置中斷向量表 mov ax,4c00hint 21hdo0: 顯示字符串“Welcome to Fishc.com!” mov ax,4c00hint 21h
code ends
end start
我們可以看到,上面的程序分為兩部分:
-
(1)安裝do0,設置中斷向量的程序
-
(2)do0
1.4 注意事項
上面的程序框架執行時,do0的代碼是不執行的,它只是作為do0安裝程序所要傳送的數據。
執行do0安裝程序,將 do0 的代碼拷貝到內存 0:200處,然后設置中斷向量表,即偏移地址200H和段地址0,保存在0號表項中。
這兩部分工作完成后,程序就返回了。
程序的目的就是在內存0:200處安裝do0 的代碼,將0號中斷處理程序的入口地址設置為0:200。
do0的代碼雖然在程序中,卻不在程序執行的時候執行。它是在除法溢出發生的時候才得以執行的中斷處理程序。
do0部分代碼的最后兩條指令是依照我們的編程要求,用來返回DOS的。
1.5 從CPU的角度看中斷處理程序
現在,我們在反過來從CPU的角度看一下,什么是中斷處理程序?
do0變成0號中斷的中斷處理程序的過程:
-
(1)這個程序框架在執行時,被加載到內存中,此時do0的代碼在程序所在的內存空間中,它只是存放在程序的代碼段中的一段要被傳送到其他單元中的數據,我們不能說它是0號中斷的中斷處理程序;
-
(2)程序中安裝do0 的代碼執行完后,do0的代碼被從程序的代碼段中拷貝到0:200處。此時,我們也還不能說它是0號中斷的中斷處理程序,它只不過是存放在0:200處的一些數據;
-
(3)程序中設置中斷向量表的代碼執行完后,在0號表項中填入了do0的入口地址0:200,此時0:200 處的信息,即do0 的代碼,就變成了0號中斷的中斷處理程序。
1.6 一些問題的思考與解答
我們如何讓一個內存單元成為棧頂?
答:將它的地址放入SS、SP中;
我們如何讓一個內存單元中的信息被CPU當作指令來執行?
答:將它的地址放入CS、IP 中;
那么,我們如何讓一段程序成為N號中斷的中斷處理程序?
答:將它的入口地址放入中斷向量表的N號表項中。
2. 安裝
下面的內容中,我們討論每一部分程序的具體編寫方法。
2.1 使用movsb指令
我們可以使用movsb指令,將do0的代碼送入0:0200處。
程序如下:
assume cs:code
code segmentstart: 設置es:di指向目的地址設置ds:si指向源地址設置cx為傳輸長度設置傳輸方向為正rep movsb設置中斷向量表mov ax,4c00hint 21hdo0: 顯示字符串“overflow!” mov ax,4c00hint 21hcode ends
end start
2.2 明確執行rep movsb前所設置的信息
我們來看一下,用rep movsb
指令的時候需要確定的信息:
-
(1)傳送的原始位置,段地址:code,偏移地址:
offset do0
; -
(2)傳送的目的位置:0:200;
-
(3)傳送的長度:do0部分代碼的長度;
-
(4)傳送的方向:正向。
更明確的程序如下:
assume cs:codecode segment
start: mov ax, csmov ds, axmov si, offset do0 ;設置ds:si指向源地址mov ax, 0mov es, axmov di, 200h ;設置es:di指向目的地址mov cx, do0部分代碼的長度 ;設置cx為傳輸長度cld ;設置傳輸方向為正rep movsb;設置中斷向量表 mov ax, 4c00hint 21hdo0: ;顯示字符串“Welcome to Fishc.com!” mov ax, 4c00hint 21hcode ends
end start
2.3 do0代碼的長度
問題是,我們如何知道do0代碼的長度?
最簡單的方法是,計算一下do0 所有指令碼的字節數。但是這樣做太麻煩了,因為只要do0的內容發生了改變,我們都要重新計算它的長度。
其實,我們可以利用編譯器來計算do0的長度,具體做法如下:
assume cs:codecode segmentstart: mov ax,csmov ds,axmov si,offset do0 ;設置ds:si指向源地址mov ax,0mov es,axmov di,200h ;設置es:di指向目的地址mov cx,offset do0end-offset do0 ;設置cx為傳輸長度cld ;設置傳輸方向為正rep movsb;設置中斷向量表 mov ax,4c00hint 21hdo0: ;顯示字符串“overflow!” mov ax,4c00hint 21h
do0end: nopcode ends
end start
“-”是編譯器識別的運算符號,編譯器可以用它來進行兩個常數的減法。
比如:mov ax,8-4
,被編譯器處理為指令: mov ax,4
。
另外,編譯器還可以處理表達式。
比如指令: mov ax,(5+3)\*5/10
,被編譯器處理為指令: mov ax,4
。
好了,知道了“-”的含義,對于用 ofset do0end-ofset do0
,得到 do0 代碼的長度的原理,這里就不再多說了,相信到了現在,大家已經可以自己進行分析了。
后面的文章中我們將編寫 do0 程序。
結語
今天的分享到這里就結束啦!如果覺得文章還不錯的話,可以三連支持一下。
也可以點點關注,避免以后找不到我哦!
Crossoads主頁還有很多有趣的文章,歡迎小伙伴們前去點評,您的支持就是作者前進的動力!