寫這個系列文章的主要目的是記錄書中重要的知識點,並和大家分享一些個人理解與實踐。由於筆記中的知識點比較零散,而書中系統的介紹了一個 x86-16 處理器在實模式下的工作原理以及如何使用匯編語言與其進行“溝通”,所以推薦想要系統學習的朋友們去學習這本書。當我們掌握了實模式的工作原理之後,就可以進一步研究後來出現的其他運行模式(如保護模式)。除此之外,熟悉彙編語言有助於我們掌握上層語言(如 C)的執行原理,因為它們都要對彙編(機器碼)進行抽象,而彙編程序就是基於 CPU 的執行機理寫出來的。
第九章
轉移行為分類
- 段內轉移(只修改
IP, 根據轉移的範圍不同又分為:短轉移 和 近轉移) - 段間轉移(同時修改
CS和IP)
jmp 指令的兩種實現差異
在一般的彙編指令中,立即數不論表示一個數據還是內存單元的偏移地址,都會在對應的機器指令中出現。
-
基於轉移的距離——指令中不包含目的地址,而包含的是位移量(向前或向後移動的距離)
- 優點,可浮動裝配,即位移量不變的情況下,這段程序可在任何地方執行
- 基於要轉移的目的地址
以上兩個維度的彙總
| 基於轉移的距離(間接轉移) | 基於要轉移的目的地址(直接轉移) | |
|---|---|---|
| 段內轉移 | jmp short Label
jmp Label、jmp near ptr Label |
jmp 某一合法16位寄存器——將寄存器中的內容寫入 IP
jmp word ptr 地址——轉移到目的偏移地址 |
| 段間轉移 | jmp far ptr Label(注:它也能夠實現段內轉移的效果)
jmp dword ptr 地址(h_word=段地址,l_word=目的偏移地址) |
-
jmp short Label: 翻譯成的機器碼為EB??H, ?? 代表 8 位的位移量(這種方式是短轉移)本質為:IP = IP + 8位的位移量 【重點(容易被忽略):CPU 在執行該指令時,是使用這個位移量來計算 IP 的】 - short 指明此處的位移為 8 位位移,範圍是 -128~127, 用【補碼】表示 - 8位的位移量 = 標號處的地址 - jmp指令後的第一個字節的地址,編譯器在【編譯時】算出-
為什麼是
jmp指令後的第一個字節的地址aj?為什麼這麼設計呢?因為取址之後(執行之前)IP寄存器中的地址會被設置為該地址aj,這樣在執行轉移指令時,可以直接參與目的偏移地址的計算!- 編譯時:
disp=as-aj, 執行指令時:IP(as) = IP(aj) + disp(位移量)即直接完成該指令的功能
- 編譯時:
-
jmp near ptr Label(近轉移,本質與短轉移相同,區別僅僅是它支持16位的位移量)-
jmp far ptr Label(遠轉移)CS 設置為 標號所在段的【段地址】 IP 設置為 標號在該段中的【偏移地址】
masm 對 jmp 內部轉移的編譯實現
【條件轉移】和【循環指令】是對【短轉移】的功能擴展
-
jcxz指令的偽代碼:...... if ((cx) == 0) { jmp short s } s: ...... -
loop指令的偽代碼s: ...... (cx)--; if ((cx) != 0) { jmp short s } ......
實驗八:分析一個奇怪的程序
注意:是將 jmp short s1 的機器碼 EBF6 拷貝至 076C:0008~076C:0009 兩個存儲單元中,F6 是 -10 的補碼,表示位移量
其實補碼 F6 表示什麼有符號數,可以不用圖片中的方法計算,而使用以下方法計算更為簡單:
-
因為補碼
1111 1111表示十進制有符號數-1, 這個-1的計算方式是:$$ -1 = -1*2^7 + 1*2^6 + 1*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 1*2^1 + 1*2^0 $$
$$ = -128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 $$
-
那麼補碼
1111 0110(0xF6)就等於:$$ -1*2^7 + 1*2^6 + 1*2^5 + 1*2^4 + 0*2^3 + 1*2^2 + 1*2^1 + 0*2^0 $$
$$ (-1*2^7+1*2^6+1*2^5+1*2^4+1*2^3+1*2^2+1*2^1+1*2^0) - 1*2^3 - 1*2^0 = -1 - 8 - 1 = -10 $$
實驗九:80x25 彩色字符模式緩衝區輸出程序
- 顯示緩衝區:
B8000H~BFFFFH(共32KB, 8頁,每頁4KB) 80x25——每頁由 25 行組成,每行由 80 個字符(160byte)組成- 注:dosbox 中執行完可執行文件,輸出結果會被向上頂一行(彈出輸入提示符導致),所以以下程序的輸出就顯示在 11,12,13 行了
; 在屏幕中間分別顯示黑底綠色、綠底紅色、白底藍色的字符串'welcome to masm!'
; 題目要求屏幕中間顯示?那就 12,13,14 行顯示
assume cs:codesg, ds:datasg, ss:stacksg
datasg segment
db 'welcome to masm!'
db 02H, 24H, 71H ; 三種屬性:黑底綠色、綠底紅色、白底藍色
datasg ends
stacksg segment
dd 0,0,0,0
stacksg ends
codesg segment
start: mov ax, datasg
mov ds, ax
mov ax, stacksg
mov ss, ax
mov sp, 10H
mov ax, 0B800H ; masm 彙編器要求立即數不能以字母開頭
mov es, ax
mov bx, 0
mov bp, 16
mov cx, 3 ; 共寫入3行
s0: mov si, 0
mov di, 1824 ; di=1760+64, 1760是12行第一字節的offset, 每行中間位置的offset=64
add di, bx ; 計算每一行開始寫入的偏移地址
mov ah, ds:[bp] ; 該行要顯示的屬性值
push cx ; 保存
mov cx, 16
s: mov al, [si]
mov es:[di], al ; 顯存區域:字符低字節表示 ASCII
inc di
mov es:[di], ah ; 字符高字節對應顏色屬性
inc si
inc di
loop s
add bx, 160 ; 0-160-320 (控制寫入下一行)
inc bp ; 16-17-18(控制獲取下一行的屬性值)
pop cx ; 恢復
loop s0
mov ax, 4c00H
int 21H
codesg ends
end start
第十章
模塊化程序設計
-
call和ret指令配合使用(近轉移)call等價於:push IP + jmp ...- ret等價於:
pop IP
-
call和retf指令配合使用(遠轉移)
- call等價於:
push CS + push IP + jmp ... - retf等價於:
pop IP + pop CS(注意這個順序)
- call等價於:
call指令
相當於將call指令之後的那一個指令的IP或CS+IP壓棧之後,進行jmp轉移。
支持近轉移或遠轉移,不支持短轉移。
-
call Labelpush IPjmp near ptr Label
-
call far ptr Labelpush CSpush IPjmp far ptr Label
-
call 16位寄存器push IPjmp 16位寄存器
-
call word ptr 內存地址push IPjmp word ptr 內存地址
-
call dword ptr 內存地址push CSpush IPjmp dword ptr 內存地址
總結——【目前已經介紹的】轉移指令對應的機器碼
| call 轉移指令 | 機器碼 | jmp 轉移指令 | 機器碼 |
|---|---|---|---|
call L |
E8 ????(16位位移量) | jmp near ptr L |
E9 ???? |
call far ptr L |
9A ???? ????<br/>標號在段中的偏移地址 標號所在段地址 | jmp far ptr L |
EA ???? ???? |
call ax、call bx |
FFD0、FFD3 | jmp ax、jmp bx |
FFE0、FFE3 |
call word ptr addr |
FF160000 (todo) | jmp word ptr addr |
FF260000 (todo) |
call dword ptr addr |
FF1E0000 (todo) | jmp dword ptr addr |
FF2E0000 (todo) |
8086 中乘法的計算規則
彙編指令:mul 乘數(在內存或某個寄存器中),【被乘數和乘數】要麼都是 8 位,要麼都是 16 位
| 8位乘數 | 16位乘數 | |
|---|---|---|
| 被乘數 | 8位——在 al 中 |
16位——在 ax 中 |
| 結果 | ax 中 |
dx 存高16位、ax 存低 16 位 |
實驗十:編寫子程序
(1)指定位置打印字符串
exp10_1.asm
(2)解決除法溢出的問題
exp10_2.asm
(3)二進制轉十進制,打印
exp10_3.asm