寫在前面
此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,方便學習本教程。
看此教程之前,問幾個問題,基礎知識儲備好了嗎?保護模式篇學會了嗎?練習做完了嗎?沒有的話就不要繼續了。
? 華麗的分割線 ?
概述
異常產生後,首先是要記錄異常資訊。異常分為CPU
產生的異常和軟體模擬產生的異常。廢話不多說,下面我們開始介紹:
CPU 異常
當出現CPU
異常時,比如除以零的操作。我們就以該異常(中斷號為0)為例,出現除零異常之後,CPU
就會在IDT
查詢,執行中斷處理函式,那麼是如何記錄異常的呢?下面我們來簡單通過反彙編瞭解一下:
_KiTrap00 proc near ; DATA XREF: INIT:_IDT↓o
var_2 = word ptr -2
arg_4 = dword ptr 8
; FUNCTION CHUNK AT .text:00467013 SIZE 00000021 BYTES
push 0
mov word ptr [esp+2], 0
push ebp
push ebx
push esi
push edi
push fs
mov ebx, 30h ; '0'
mov fs, bx
assume fs:nothing
mov ebx, large fs:_KPCR
push ebx
sub esp, 4
push eax
push ecx
push edx
push ds
push es
push gs
mov ax, 23h ; '#'
sub esp, 30h
mov ds, ax
assume ds:nothing
mov es, ax
assume es:nothing
mov ebp, esp
test [esp+_KTRAP_FRAME.EFlags], 20000h
jnz short V86_kit0_a
loc_4671DE: ; CODE XREF: V86_kit0_a+25↑j
cld
mov ebx, [ebp+_KTRAP_FRAME._Ebp]
mov edi, [ebp+_KTRAP_FRAME._Eip]
mov [ebp+_KTRAP_FRAME.DbgArgPointer], edx
mov [ebp+_KTRAP_FRAME.DbgArgMark], 0BADB0D00h
mov [ebp+_KTRAP_FRAME.DbgEbp], ebx
mov [ebp+_KTRAP_FRAME.DbgEip], edi
test byte ptr ds:0FFDFF050h, 0FFh ; KPCR.DebugActive
jnz Dr_kit0_a
loc_467202: ; CODE XREF: Dr_kit0_a+10↑j
; Dr_kit0_a+7C↑j
test [ebp+_KTRAP_FRAME.EFlags], 20000h
jnz short isVM8086
test byte ptr [ebp+_KTRAP_FRAME.SegCs], 1
jz short isKernelMode
cmp word ptr [ebp+_KTRAP_FRAME.SegCs], 1Bh
jnz short loc_467235
isKernelMode: ; CODE XREF: _KiTrap00+73↑j
sti
push ebp
call _Ki386CheckDivideByZeroTrap@4 ; Ki386CheckDivideByZeroTrap(x)
mov ebx, [ebp+_KTRAP_FRAME._Eip]
jmp loc_467013
; ---------------------------------------------------------------------------
loc_467227: ; CODE XREF: _KiTrap00+A9↓j
; _KiTrap00+B4↓j
sti
mov ebx, [ebp+_KTRAP_FRAME._Eip]
mov eax, STATUS_INTEGER_DIVIDE_BY_ZERO
jmp loc_467013
; ---------------------------------------------------------------------------
loc_467235: ; CODE XREF: _KiTrap00+7A↑j
mov ebx, ds:0FFDFF124h
mov ebx, [ebx+_KTHREAD.ApcState.Process]
cmp [ebx+_EPROCESS.VdmObjects], 0
jz short loc_467227
isVM8086: ; CODE XREF: _KiTrap00+6D↑j
push 0
call _Ki386VdmReflectException_A@4 ; Ki386VdmReflectException_A(x)
or al, al
jz short loc_467227
jmp Kei386EoiHelper@0 ; Kei386EoiHelper()
_KiTrap00 endp
可以看出最後執行到如下程式碼:
loc_467227: ; CODE XREF: _KiTrap00+A9↓j
; _KiTrap00+B4↓j
sti
mov ebx, [ebp+_KTRAP_FRAME._Eip]
mov eax, STATUS_INTEGER_DIVIDE_BY_ZERO
jmp loc_467013
最後跳到loc_467013
,我們來看看它的彙編程式碼:
loc_467013: ; CODE XREF: _KiTrap00+86↓j
; _KiTrap00+94↓j ...
xor ecx, ecx
call CommonDispatchException
發現它會呼叫CommonDispatchException
函式,這個函式就是用來派發異常的,我們繼續分析流程:
CommonDispatchException proc near ; CODE XREF: _KiTrap00-187↑p
; _KiTrap00-17B↑p ...
var_50 = dword ptr -50h
var_4C = dword ptr -4Ch
var_48 = dword ptr -48h
var_44 = dword ptr -44h
var_40 = dword ptr -40h
var_3C = byte ptr -3Ch
sub esp, EXCEPTION_RECORD_LENGTH
mov [esp+_EXCEPTION_RECORD32.ExceptionCode], eax
xor eax, eax
mov [esp+_EXCEPTION_RECORD32.ExceptionFlags], eax
mov [esp+_EXCEPTION_RECORD32.ExceptionRecord], eax
mov [esp+_EXCEPTION_RECORD32.ExceptionAddress], ebx
mov [esp+_EXCEPTION_RECORD32.NumberParameters], ecx
cmp ecx, 0
jz short loc_46705D
lea ebx, [esp+_EXCEPTION_RECORD32.ExceptionInformation]
mov [ebx], edx
mov [ebx+4], esi
mov [ebx+8], edi
loc_46705D: ; CODE XREF: CommonDispatchException+1B↑j
mov ecx, esp
test [ebp+_KTRAP_FRAME.EFlags], 20000h
jz short loc_46706F
mov eax, 0FFFFh
jmp short loc_467072
; ---------------------------------------------------------------------------
loc_46706F: ; CODE XREF: CommonDispatchException+32↑j
mov eax, [ebp+_KTRAP_FRAME.SegCs]
loc_467072: ; CODE XREF: CommonDispatchException+39↑j
and eax, 1
push 1 ; FirstChance
push eax ; PreviousMode
push ebp ; TrapFrame
push 0 ; ExceptionFrame
push ecx ; ExceptionRecord
call _KiDispatchException@20 ; KiDispatchException(x,x,x,x,x)
mov esp, ebp
jmp Kei386EoiHelper@0 ; Kei386EoiHelper()
CommonDispatchException endp
可以看出它會提升堆疊提供EXCEPTION_RECORD
結構體,儲存一些異常資訊,這就是CPU
的異常記錄流程。如下就是該結構體的成員:
type struct _EXCEPTION_RECORD
{
DWORD ExceptionCode; //異常程式碼
DWORD ExceptionFlags; //異常狀態
struct _EXCEPTION_RECORD* ExceptionRecord; //下一個異常
PVOID ExceptionAddress; //異常發生地址
DWORD NumberParameters; //附加引數個數
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //附加引數指標
}
不同的CPU
異常對應不同的IDT
索引,與此同時Windows
自己定義了不同的錯誤碼標識不同的錯誤:
CommonDispatchException
最後呼叫KiDispatchException
函式真正地派發異常,有關CPU
異常記錄就介紹到這裡。
軟體模擬異常
對於軟體模擬的異常,我們以C++
的為例,如下是測試程式碼:
#include "stdafx.h"
int main(int argc, char* argv[])
{
throw;
return 0;
}
程式碼很簡單,我們生成的反彙編如下:
#include "stdafx.h"
int main(int argc, char* argv[])
{
00401010 push ebp
00401011 mov ebp,esp
00401013 sub esp,40h
00401016 push ebx
00401017 push esi
00401018 push edi
00401019 lea edi,[ebp-40h]
0040101C mov ecx,10h
00401021 mov eax,0CCCCCCCCh
00401026 rep stos dword ptr [edi]
throw;
00401028 push 0
0040102A push 0
0040102C call __CxxThrowException@8 (00401090)
return 0;
}
00401031 pop edi
00401032 pop esi
00401033 pop ebx
00401034 add esp,40h
00401037 cmp ebp,esp
00401039 call __chkesp (00401050)
0040103E mov esp,ebp
00401040 pop ebp
00401041 ret
可以發現,丟擲一個異常是用CxxThrowException
函式實現的,我們檢視它的反彙編:
__CxxThrowException@8:
00401090 push ebp
00401091 mov ebp,esp
00401093 sub esp,20h
00401096 push esi
00401097 push edi
00401098 mov ecx,8
0040109D mov esi,offset string "The value of ESP was not properl"...+0E0h (00422110)
004010A2 lea edi,[ebp-20h]
004010A5 rep movs dword ptr [edi],dword ptr [esi]
004010A7 mov eax,dword ptr [ebp+8]
004010AA mov dword ptr [ebp-8],eax
004010AD mov ecx,dword ptr [ebp+0Ch]
004010B0 mov dword ptr [ebp-4],ecx
004010B3 lea edx,[ebp-0Ch]
004010B6 push edx
004010B7 mov eax,dword ptr [ebp-10h]
004010BA push eax
004010BB mov ecx,dword ptr [ebp-1Ch]
004010BE push ecx
004010BF mov edx,dword ptr [ebp-20h]
004010C2 push edx
004010C3 call dword ptr [__imp__RaiseException@16 (0042a154)]
004010C9 pop edi
004010CA pop esi
004010CB mov esp,ebp
004010CD pop ebp
004010CE ret 8
該函式又是呼叫RaiseException
函式實現功能,傳入的異常號為E06D7363
,注意不同的編譯器模擬異常實現,這個異常號是不同的。我們繼續檢視RaiseException
函式,由於反編譯的結果十分好,為了節省篇幅虛擬碼如下:
void __stdcall RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR *lpArguments)
{
DWORD v4; // ecx
struct _EXCEPTION_RECORD ExceptionRecord; // [esp+4h] [ebp-50h] BYREF
ExceptionRecord.ExceptionRecord = 0;
ExceptionRecord.ExceptionCode = dwExceptionCode;
ExceptionRecord.ExceptionFlags = dwExceptionFlags & 1;
ExceptionRecord.ExceptionAddress = RaiseException;
if ( lpArguments )
{
v4 = nNumberOfArguments;
if ( nNumberOfArguments > 0xF )
v4 = 15;
ExceptionRecord.NumberParameters = v4;
if ( v4 )
qmemcpy(ExceptionRecord.ExceptionInformation, lpArguments, 4 * v4);
}
else
{
ExceptionRecord.NumberParameters = 0;
}
RtlRaiseException(&ExceptionRecord);
}
這個函式只是構造了一個結構體用來記錄,最後又會呼叫RtlRaiseException
函式實現功能,其彙編程式碼如下:
; void __stdcall RtlRaiseException(PEXCEPTION_RECORD ExceptionRecord)
public _RtlRaiseException@4
_RtlRaiseException@4 proc near ; CODE XREF: RtlRaiseException(x)+B3↓p
; RtlDispatchException(x,x)+D3↓p ...
var_2F4 = dword ptr -2F4h
var_2F0 = dword ptr -2F0h
var_2EC = dword ptr -2ECh
var_2E4 = dword ptr -2E4h
Context = CONTEXT ptr -2D4h
var_4 = dword ptr -4
var_s0 = dword ptr 0
ExceptionRecord = dword ptr 8
arg_4 = byte ptr 0Ch
push ebp
mov ebp, esp
pushf
sub esp, 2D0h
mov [ebp+Context._Eax], eax
mov [ebp+Context._Ecx], ecx
mov eax, [ebp+ExceptionRecord]
mov ecx, [ebp+4] ; 獲取呼叫該函式的地址
mov [eax+_EXCEPTION_RECORD.ExceptionAddress], ecx
lea eax, [ebp+Context]
mov [eax+_CONTEXT._Eip], ecx
mov [eax+_CONTEXT._Ebx], ebx
mov [eax+_CONTEXT._Edx], edx
mov [eax+_CONTEXT._Esi], esi
mov [eax+_CONTEXT._Edi], edi
lea ecx, [ebp+0Ch] ; 呼叫該函式之前的堆疊棧頂
mov [eax+_CONTEXT._Esp], ecx
mov ecx, [ebp+0] ; 獲取儲存的 ebp
mov [eax+_CONTEXT._Ebp], ecx
mov ecx, [ebp-4] ; 獲取儲存的 eflag
mov [eax+_CONTEXT.EFlags], ecx
mov word ptr [eax+_CONTEXT.SegCs], cs
mov word ptr [eax+_CONTEXT.SegDs], ds
mov word ptr [eax+_CONTEXT.SegEs], es
mov word ptr [eax+_CONTEXT.SegFs], fs
mov word ptr [eax+_CONTEXT.SegGs], gs
mov word ptr [eax+_CONTEXT.SegSs], ss
mov [eax+_CONTEXT.ContextFlags], 10007h
push 1 ; SearchFrames
push eax ; Context
push [ebp+ExceptionRecord] ; ExceptionRecord
call _ZwRaiseException@12 ; ZwRaiseException(x,x,x)
sub esp, 20h
mov [esp], eax
mov dword ptr [esp+4], 1
mov dword ptr [esp+10h], 0
mov eax, [ebp+ExceptionRecord]
mov [esp+8], eax
mov eax, esp
push eax ; ExceptionRecord
call _RtlRaiseException@4 ; RtlRaiseException(x)
_RtlRaiseException@4 endp
然後又呼叫ZwRaiseException
,通過系統呼叫最終呼叫NtRaiseException
實現:
; NTSTATUS __stdcall NtRaiseException(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT Context, BOOLEAN SearchFrames)
_NtRaiseException@12 proc near ; DATA XREF: .text:0042AE60↑o
var_s0 = dword ptr 0
ExceptionRecord = dword ptr 8
Context = dword ptr 0Ch
FirstChance = byte ptr 10h
arg_34 = dword ptr 3Ch
push ebp
mov ebx, ds:0FFDFF124h
mov edx, [ebp+3Ch]
mov [ebx+_KTHREAD.TrapFrame], edx
mov ebp, esp
mov ebx, [ebp+0]
mov edx, dword ptr [ebp+FirstChance]
mov eax, [ebx+_KTRAP_FRAME.ExceptionList]
mov ecx, [ebp+Context]
mov ds:0FFDFF000h, eax
mov eax, [ebp+ExceptionRecord]
push edx ; FirstChance
push ebx ; TrapFrame
push 0 ; ExceptionFrame
push ecx ; ContextRecord
push eax ; ExceptionRecord
call _KiRaiseException@20 ; KiRaiseException(x,x,x,x,x)
pop ebp
mov esp, ebp
or eax, eax
jnz _KiServiceExit
jmp _KiServiceExit2
_NtRaiseException@12 endp
這個函式又會呼叫KiRaiseException
實現:
NTSTATUS __stdcall KiRaiseException(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord, _KTRAP_FRAME *ExceptionFrame, _KTRAP_FRAME *TrapFrame, BOOLEAN FirstChance)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
ExceptionRecord_1 = ExceptionRecord;
ContextFrame = ContextRecord;
ExceptionFrame_1 = ExceptionFrame;
TrapFrame_1 = TrapFrame;
ms_exc.registration.TryLevel = 0;
currentThread = KeGetCurrentThread();
LOBYTE(PreviousMode) = currentThread->PreviousMode;
if ( !PreviousMode )
{
LABEL_19:
ms_exc.registration.TryLevel = -1;
KeContextToKframes(TrapFrame_1, ExceptionFrame_1, ContextFrame, ContextFrame->ContextFlags, PreviousMode);
HIBYTE(ExceptionRecord_1->ExceptionCode) &= 0xEFu;
KiDispatchException(ExceptionRecord_1, ExceptionFrame_1, TrapFrame_1, PreviousMode, FirstChance);
goto LABEL_20;
}
if ( (ContextRecord & 3) != 0 )
ExRaiseDatatypeMisalignment();
if ( ContextFrame >= MmUserProbeAddress )
*MmUserProbeAddress = 0;
if ( (ExceptionRecord & 3) != 0 )
ExRaiseDatatypeMisalignment();
if ( ExceptionRecord >= MmUserProbeAddress )
*MmUserProbeAddress = 0;
NumberParameters = ExceptionRecord->NumberParameters;
NumberParameters_1 = NumberParameters;
if ( NumberParameters <= 0xF )
{
v13 = 4 * NumberParameters + 20;
if ( 4 * NumberParameters != 4294967276 )
{
if ( (ExceptionRecord & 3) != 0 )
ExRaiseDatatypeMisalignment();
v7 = &ExceptionRecord->ExceptionInformation[NumberParameters];
if ( v7 < ExceptionRecord || v7 > MmUserProbeAddress )
ExRaiseAccessViolation();
}
qmemcpy(&context, ContextFrame, sizeof(context));
qmemcpy(&exRecord, ExceptionRecord, v13);
ContextFrame = &context;
ExceptionRecord_1 = &exRecord;
v10 = &exRecord;
exRecord.NumberParameters = NumberParameters;
goto LABEL_19;
}
ms_exc.registration.TryLevel = -1;
LABEL_20:
xHalReferenceHandler(v19);
return result;
}
這個函式呼叫KiDispatchException
進行異常派發。軟體丟擲模擬的異常記錄流程就到此結束了。
小結
最後我們簡單的用流程圖表示一下CPU
異常和軟體模擬異常的記錄流程:
下一篇
異常篇——異常處理