簡介 :
分析下面的程序 , 在運(yùn)行前思考 , 這個(gè)程序可以正常返回嗎 ?
運(yùn)行后再思考 , 為什么是這種結(jié)果 ?
通過這個(gè)程序加深對相關(guān)內(nèi)容的理解
assume cs:code
code segment
mov ax, 4C00H
int 21H
start:
mov ax, 0000H
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, 0000H
int 21H
mov ax, 0000H
s2:
jmp short s1
nop
code ends
end start
分析 :
程序的執(zhí)行流程是這樣的 :
; 從 start 標(biāo)號開始
1. mov ax, 0000H
2. nop
3. nop
4. mov di, offset s
5. mov si, offset s2
6. mov ax, cs:[si]
7. mov cs:[di], ax
8. jmp short s
9. jmp short si ; 這句 jmp short s1 , 根據(jù)我們之前的分析 , 指令是用相對偏移來表示的
; 因此執(zhí)行的操作并不是真的跳轉(zhuǎn)到 s1 這個(gè)標(biāo)號 , 而是跳轉(zhuǎn)編譯時(shí)確定的 該指令到 s1 標(biāo)號的偏移
; 所以我們要分析接下來程序的流程的話 , 就必須先編譯程序 , 然后要知道到底偏移是多少
; 然后再根據(jù)這個(gè)偏移確定程序下一步應(yīng)該執(zhí)行哪里的指令
; 根據(jù)下圖的編譯結(jié)果 , 可以發(fā)現(xiàn) , jmp short s1 在編譯后得到的指令是 :
; 偏移是 : EB F6
; 這個(gè)數(shù)據(jù)是使用 補(bǔ)碼 來表示的 , 也就是說 , 是一個(gè)負(fù)數(shù) , 然后符號位不變 , 其他位取反 , 然后加 1
; 然后 , 我們現(xiàn)在就知道了 , 這條指令是將 ip 的值加上 -10
; 我們再看看 ip - 10 指向的地址是哪里 ?
; 對 , 剛好就是 code segment 開始的位置
10. mov ax, 4C00H
11. int 21H
; 這樣程序就實(shí)現(xiàn)了正常的返回
反編譯結(jié)果 :
注意這里使用 debug 的 u 命令進(jìn)行反匯編的時(shí)候要指定代碼段的偏移地址為 0
否則 debug 會自動從 start 標(biāo)號的地方開始反匯編
可以看到 :
jmp short s1 ; 這句匯編指令被翻譯成了 : EB F6 , 其中 EB 表示的是跳轉(zhuǎn) , F6 表示偏移
F6 怎么理解呢 ?
1111 0110 (使用補(bǔ)碼來表示)
補(bǔ)碼轉(zhuǎn)換成原碼 , 符號位不變 , 其他位取反 , 然后加 1
1000 1001
1000 1010 ; 也就是 -10
也就是上面我們分析的讓 (ip) = (ip) - 0x0A
然后 , 這句指令被復(fù)制到 s 標(biāo)號的開頭處
由于 nop 只占一個(gè)字節(jié) , 因此兩個(gè) nop 被完全替代
然后程序執(zhí)行到 s0 , 又跳轉(zhuǎn)到 s 開始的地方
這個(gè)時(shí)候就要執(zhí)行 : (這個(gè)時(shí)候 ip = 8)
EB F6
首先讀取這條指令到指令緩存器里
接下來 , (ip) = (ip) + len(EB F6) = (ip) + 8 = 10
然后執(zhí)行這條指令 , 即為 (ip) = (ip) - 10 = 0
這樣 ip 就回到了 code segment 的起始處
這樣繼續(xù)執(zhí)行
mov ax, 4C00H
int 21H
就實(shí)現(xiàn)了程序的正常返回