題目
分析程序,在運行前思考:這個程序是否能夠正確返回?
運行之后再思考:為什么是這種結果?
通過這個程序加深對相關內容的理解。
貼入代碼如下:
assueme cs:codesg
codesg segment
mov ax,4c00h
int 21h
start: mov ax,0
s: nop
nop
mov di,offset s
mov si,offset s2
mov ax,cs:[si]
mov cs:[di],ax
s0: jmp short s
s1: mov ax,0
int 21h
mov ax,0
s2: jmp short s1
nop
codesg ends
end start
分析
由我們之前學到的知識,我們知道這個程序從start標號的字段開始執行。
我們先來看一下程序的執行流程:
1. start: mov ax,0
2. s: nop ; nop標號語句,在運行時在代碼段中分配一個字節的空間,
3. nop ; 這個字節(空間)的值為90h。
; 操作符 `offset` 的功能是取得標號的偏移地址。
4. mov di,offset s ; 將 s 的偏移地址存到 di 寄存器中
5. mov si,offset s2 ; 將 s2 的偏移地址存到 si 寄存器中
6. mov ax,cs:[si] ; 此行是將cs:[si]內存中的機器碼存到ax寄存器中,
; 這個機器碼是由編譯器將 s2 標號字段中的指令編譯而成。
7. mov cs:[di],ax ; 將 ax 中的 s2 標號字段的機器碼存放到 s 標號字段中。
8. s0: jmp short s ; 跳轉到 s 標號字段處執行代碼。
9. s: jmp short s1 ; 根據我們之前的分析, 指令是用相對偏移來表示的
; 因此執行的操作並不是真的跳轉到 s1 這個標號,
; 而是跳轉編譯時確定的 該指令到 s1 標號的偏移量。
; 所以我們要分析接下來程序的流程的話 , 就必須先編譯程序 ,
; 通過查看這條指令的機器代碼,才知道偏移量是多少。
; 然后再根據這個偏移量確定程序下一步應該執行哪里的指令。
; 根據下圖的編譯結果 , 可以發現 ,
; jmp short s1 在編譯后得到的指令是 : EB F6
; 由上可知,偏移量是 :F6
; 偏移量是由 補碼 來表示的,由書中 附注二 ,
; 我們可以算出 F6對應的有符號十進制數為 -10。
; 從這里,我們可以知道,這條指令是將 ip 的值加上 -10。
; 那么,我們再看看 ip - 10 指向的地址是哪里呢 ?
; 由下圖的編譯結果,我們可以知道,
; 它指向的剛好就是 code segment 開始的位置.
10. mov ax,4c00h
11. int 21h ;看到這兩句,大家就知道,程序是可以正常返回了
反編譯
注意這里使用 debug 的
u
命令進行反匯編的時候要指定代碼段的偏移地址為 0 ,否則 debug 會自動從 start 標號的地方開始反匯編
有時候單純從
u 0
命令無法查看到jmp short s1
這條代碼。因為有的命令行工具可能不能夠顯示過多的代碼。 我們可以從上圖中找一個參照點,再次運用u
命令,就可以看到這行代碼了。從下圖中,我們可以看到,jmp short s1對應的機器代碼,正是EB F6
- jmp short s1的跳轉原理分析:
1. codesg segment
mov ax,4c00h ; 3字節
int 21h ; 2字節
2. start: mov ax,0 ; 3字節
3. s: jmp short s1 ; 2字節
; 上述4條指令總共加起來是10字節,即 10 個單位的偏移量!
; 由於 nop 只占一個字節 , 因此
; 原來 s 中的兩個 nop 被jmp short s1完全替代。
; CPU首先讀取這條指令到指令緩存器里
; 此時的ip為8(由上圖可以知道)
; 【文末的參考文章中的len(EB F6)解釋錯誤,應為2,此處已更正】
; 接下來 , (ip) = (ip) + len(EB F6) = (ip) + 2 = 10
; 然后執行這條指令 , 即為 (ip) = (ip) - 10 = 0
; 這樣 ip 就回到了 code segment 的起始處
; 這樣繼續執行
4. mov ax,4c00h
5. int 21h
; 這樣,程序就這樣神奇的執行成功啦!!!
總結
運用王爽老師在P179頁的話,CPU在執行jmp指令的時候並不需要轉移的目的地址,而包含的是轉移的位移。這個位移,是編譯器根據匯編指令中的“標號”計算出來的。
本博參考了他的文章:
鏈接:https://www.jianshu.com/p/7e5dfea72b65