天天看點

ReactOS SYSCALL_PROLOG/TRAP_EPILOG及相關代碼注釋 (1)

    ReactOS源碼中,通過系統調用/異常/中斷進入核心,首先會遇到SYSCALL_PROLOG/TRAP_PROLOG之類的入口函數;當調KiServiceExit退出核心空間時,又會調用SYSCALL_PROLOG之類的出口函數。這些代碼在R3-R0切換期間起到什麼作用?本文旨在注釋這部分及相關代碼作用。

    先看下在什麼語境下會調用這些宏:

1).系統調用時調用該宏:

.func KiSystemService
TRAP_FIXUPS kss_a, kss_t, DoNotFixupV86, DoNotFixupAbios
_KiSystemService:

    /* Enter the shared system call prolog */
    SYSCALL_PROLOG kss_a, kss_t

    /* Jump to the actual handler */
    /*整個SYSCALL_PROLOG沒修改eax*/
    jmp SharedCode
.endfunc      

再看看誰調用了_KiSystemService,以在使用者空間中調用ReadFile函數為例。

ReadFile内部調用樁函數NtReadFile [Ring3],然後樁函數通過int 0x2E進入核心 [Ring0],而int 0x2E對應的idt入口點為_KiSystemService。進入核心後先調用SYSCALL_PROLOG形成調用棧,然後再跳到ShareCode中去執行API調用分發,最終執行核心中的NtReadFile。這個流程中調用了_KiSystemService,點題一次!而從r3進入r0的整個過程遠不是幾行文字能描述完整,預計會另辟一文注釋其中過程。

2).發生異常時調用該宏:

.func KiTrap14
TRAP_FIXUPS kite_a, kite_t, DoFixupV86, DoNotFixupAbios
_KiTrap14:

    /* Enter trap */
    ;生成KTRAP_FRAME架構儲存類似中斷架構
    ;最先push的,在KTRAP_FRAME結構的位置越靠後
    ;進入TRAP_PROLOG時,用esp為目前中斷/異常開辟KTRAP_FRAME所需的棧空間,
    ;mov ebp,esp 使ebp指向KTRAP_FRAME,同時儲存本次中斷/異常的ebp于KTRAP_FRAME!ebp中
    ;由于可嵌套系統調用,将上一次發生中斷/異常的ebp儲存在目前KTRAP_FRAME!edx中
    TRAP_PROLOG kite_a, kite_t

    /* Check if we have a VDM alert */
    cmp dword ptr PCR[KPCR_VDM_ALERT], 0
    jnz VdmAlertGpf
    ...      

發生缺頁異常時,CPU到idt中尋找缺頁異常的處理函數,該處理函數是_kiTrap14。_KiTrap14第一條語句也是TRAP_PROLOG。

關于異常處理的流程,也得另辟一文注釋其中過程。

3).從核心退出時:

.func KiServiceExit
_KiServiceExit:
    /* Disable interrupts */
    cli

    /* Check for, and deliver, User-Mode APCs if needed */
    CHECK_FOR_APC_DELIVER 1

    /* Exit and cleanup */
    TRAP_EPILOG FromSystemCall, DoRestorePreviousMode, DoNotRestoreSegments, DoNotRestoreVolatiles, DoRestoreEverything
.endfunc      

先檢測是否有待處理的APC,然後調用TRAP_EPILOG

綜上所述,這幾個宏在r3與r0之間的切換起到了重要的作用。在展開介紹宏之前,先看個結構,這裡簡單注釋一下:

// Trap Frame Definition
//
typedef struct _KTRAP_FRAME
{
    ULONG DbgEbp;
    ULONG DbgEip;
    ULONG DbgArgMark;
    ULONG DbgArgPointer;
    ULONG TempSegCs;
    ULONG TempEsp;
    ULONG Dr0;
    ULONG Dr1;
    ULONG Dr2;
    ULONG Dr3;
    ULONG Dr6;
    ULONG Dr7; //往上是調試相關的寄存器
    ULONG SegGs;
    ULONG SegEs;
    ULONG SegDs;
    ULONG Edx; //這個域也比較重要,會儲存前一次進入中斷時儲存的KTRAP_FRAME棧頂,進而形成堆棧鍊
    ULONG Ecx;
    ULONG Eax;
    ULONG PreviousPreviousMode; //前一次mode,cs段寄存器低2位決定
    struct _EXCEPTION_REGISTRATION_RECORD FAR *ExceptionList; //異常處理
    ULONG SegFs;
    ULONG Edi;
    ULONG Esi;
    ULONG Ebx;
    ULONG Ebp;
    //以下幾個域比較重要,當中斷發生前CPU為R3,則CPU會自動壓入R3模式下的ss/esp,如果中斷發生在R0模式CPU不會壓入ss/esp的值,但是仍然會以此壓入eflags cs ip的
    //值,至于ErrCode,對于有些異常,CPU會自動壓入,有些則不會。為了統一處理,對于不會産生ErrCode的異常,由系統(OS)自動往這個域填入0替代
    ULONG ErrCode; //呼應下文觀察點1
    ULONG Eip;
    ULONG SegCs;
    ULONG EFlags;
    ULONG HardwareEsp;
    ULONG HardwareSegSs;
    //V86模式,不知道幹啥用的
    ULONG V86Es;
    ULONG V86Ds;
    ULONG V86Fs;
    ULONG V86Gs;
} KTRAP_FRAME, *PKTRAP_FRAME;      

經過SYSCALL_PPROLOG操作,就是在核心棧上開辟這個結構,然後把進入異常/中斷的值依次填入這個結構的各個域中。

其中注釋edx的功能,用來形成堆棧鍊,怎麼了解?

先看個例子:

int StackChain2()
{
  return 0;
}

int StackChain1()
{
  StackChain2();
  return 0;
}

int main()
{
  StackChain1();
}      

真是鐵索連環般的嵌套調用,看看反彙編:

1:    int StackChain2()
2:    {
00401020   push        ebp
00401021   mov         ebp,esp

6:    int StackChain1()
7:    {
00401050   push        ebp
00401051   mov         ebp,esp
00401053   sub         esp,40h

12:   int main()
13:   {
0040D480   push        ebp
0040D481   mov         ebp,esp      

每個函數的入口處,通過push ebp儲存調用者的棧底ebp,然後用本函數堆棧esp初始化本函數的棧幀ebp,形成堆棧鍊。這麼看堆棧鍊也許還不是很明了,還是看下記憶體值:

ReactOS SYSCALL_PROLOG/TRAP_EPILOG及相關代碼注釋 (1)

圖中每個函數可以通過儲存在本函數棧底的前一個函數的棧幀,找到前一個函數的堆底。圖上最左邊是嵌套最深處的函數ChainStack2,往右依次是該函數的調用者ChainStack1 main。StackChain2棧底為12FED8,從此找到StackChain1的棧為12FF2C,而從12FF2C可以找到main函數的棧:12FF80。再從12FF80找到main函數的調用者的棧:12FFC0。什麼main函數的調用者?淡定,這是crt庫的事,初始化程式運作的堆棧,然後把運作權交給我們的程式。這是題外話,詳見看雪的加密與解密。這樣通過函數堆棧回溯可以找到最開始時的堆棧,如果調試器支援的話。如果仔細觀察,棧頂一直是往減小的方向生長,開始時棧頂是FF80 然後FF2C 最後FED8。

至此,堆棧鍊的概念應該建立,然後,來看看ReactOS中怎麼建立堆棧鍊,上代碼:

//
// @name SYSCALL_PROLOG
// 
// This macro creates a system call entry prologue.
// It should be used for entry into any fast-system call (KiGetTickCount,
// KiCallbackReturn, KiRaiseAssertion) and the generic system call handler
// (KiSystemService)
//
// @param Label
//        Unique label identifying the name of the caller function; will be
//        used to append to the name of the DR helper function, which must
//        already exist.
//
// @remark None.
//
.macro SYSCALL_PROLOG Label EndLabel
    /* Create a trap frame */
    push 0 //觀察點1
    push ebp
    push ebx
    push esi
    push edi
    push fs //觀察點1結束

    /* Load PCR Selector into fs */
    mov ebx, KGDT_R0_PCR
    .byte 0x66
    mov fs, bx

    /* Get a pointer to the current thread */
    /*#define PCR fs*/
    mov esi, PCR[KPCR_CURRENT_THREAD]

    /* Save the previous exception list */

    /*
    _EXCEPTION_REGISTRATION struc
    prev    dd ;下一個_EXCEPTION_REGISTRATION結構
    handler dd ;異常處理函數位址
    _EXCEPTION_REGISTRATION ends
    儲存目前SEH節點
    */
    push PCR[KPCR_EXCEPTION_LIST]

    /* Set the exception handler chain terminator */
    mov dword ptr PCR[KPCR_EXCEPTION_LIST], -1

    /* Save the old previous mode */
    push [esi+KTHREAD_PREVIOUS_MODE]

    /* Skip the other registers */
    sub esp, 0x48

    /* Set the new previous mode based on the saved CS selector */
    mov ebx, [esp+0x6C]
    /*使用者态?核心态*/
    and ebx, 1
    mov byte ptr [esi+KTHREAD_PREVIOUS_MODE], bl

    /* Go on the Kernel stack frame */
    /*ebp指向KTRAP_FRAME頂部*/
    mov ebp, esp //=====a)

    /* Save the old trap frame pointer where EDX would be saved */
    /*
    後面第五條指令是mov [esi+KTHREAD_TRAP_FRAME], ebp  即本線程fs:[KTHREAD_TRAP_FRAME]用以
    儲存進入系統調用時的ebp的值;在沒有運作mov [esi+KTHREAD_TRAP_FRAME], ebp 前,[esi+KTHREAD_TRAP_FRAME]
    儲存上面mov ebp,esp後新生成的ebp。由于win支援嵌套系統調用,每次fs:[KTHREAD_TRAP_FRAME]都儲存ebp,如果
    沒有一個指針儲存前一次的[esi+KTHREAD_TRAP_FRAME],從系統調用退出後,會無法恢複上次一堆棧
    是以用[ebp+KTRAP_FRAME_EDX]作為指針,儲存上一個ebp,形成一條連結清單,以此為依據搜尋上一層堆棧架構
    */
    mov ebx, [esi+KTHREAD_TRAP_FRAME] //=====b)
    /*用ebp+KTRAP_FRAME_EDX中edx域儲存前一個堆棧架構*/
    mov [ebp+KTRAP_FRAME_EDX], ebx  //=====c)

    /* Flush DR7 */
    and dword ptr [ebp+KTRAP_FRAME_DR7], 0

    /* Check if the thread was being debugged */
    test byte ptr [esi+KTHREAD_DEBUG_ACTIVE], 0xFF

    /* Set the thread's trap frame and clear direction flag */
    /*mov ebp, esp 儲存此次系統調用時的堆棧*/
    mov [esi+KTHREAD_TRAP_FRAME], ebp //=====d)
    cld

    /* Save DR registers if needed */
    jnz Dr_&Label

    /* Set the trap frame debug header */
Dr_&EndLabel:
    SET_TF_DEBUG_HEADER

    /* Enable interrupts */
    sti //=====e)
.endm      

代碼中用abcde)标示處即是生成堆棧鍊的。why?

假設一個場景,使用者通過ReadFile進入核心空間,通過中斷異常進入R0時,都是關中斷的,能保證這個宏在一個CPU上完整執行,直到執行到e)處,開中斷。期間,在a)mov ebp,esp使ebp指向R0上的KTRAP_FRAME結構;然後在d)處mov [esi+KTHREAD_TRAP_FRAME],ebp使得線程TrapFrame指針指向這個剛形成的結構。這時,CPU接收到一個中斷或者執行遇到一個異常,CPU又會執行該宏,當再次執行到a)處時,核心上已經有個新的KTRAP_FRAME結構,但尚未儲存,如果直接存放到KTHREAD!TrapFrame域中,會覆寫原本指向系統調用的KTRAP_FRAME結構,是以用本次棧幀中的edx域儲存上次進入核心時的TRAP_FRAME結構指針,如此就不會丢失前後關系。當從中斷退出,就能回退到上次系統調用的語境中。

這裡有個疑問,代碼中沒有看到顯式的配置設定一個KTRAP_FRAME結構,系統是如何儲存這些寄存器值到KTRAP_FRAME中的?

回到代碼開始部分,我加了觀察點1的位置。仔細看push入棧的順序依次是0 ebp ebx esi edi和fs。這和KTRAP_FRAME結構定義中注釋有呼應觀察點1處有點相似?隻是順序好像反了。好,仔細想想KTRAP_FRAME結構中   

{    ...
    ULONG SegFs;
     ULONG Edi;
     ULONG Esi;
     ULONG Ebx;
     ULONG Ebp;
     ULONG ErrCode;...
}      

這幾個域位址值是不是依次增大?而宏入口處push 0 ebp ebx esi edi和fs 位址值是不是依次在減小?如果把KTRAP_FRMAE結構每一項當做一個函數局部變量,那麼進入函數時,要通過減小esp來一次性配置設定這兒多變量。減小esp的值最直接的辦法是sub esp 4*n,如果換個方式,怎麼表達這個減小的過程?執行push操作n次是不是也能達到等價的效果?為了在位址方向上保持結構中域增長關系,應該先配置設定結構尾部域然後逐漸減小esp的值配置設定結構中前部域。

回過頭來看下ReactOS中的代碼,push 0 push ebp 。。。正好對應這個關系。是以在ReactOS中,是通過這種方式在核心棧上配置設定KTRAP_FRAME結構變量。可是光push 0隻填充了ErrCode,誰填充了Eip/SegCs/Eflags域?這是經過idt門時用CPU自行壓入的。

前奏看完了,再看看後記TRAP_EPILOG:

這部分代碼是PROLOG的逆操作:

.macro TRAP_EPILOG SystemCall, RestorePreviousMode, RestoreSegments, RestoreVolatiles, RestoreAllRegs
//進入本宏前,通過mov esp,ebp 用進入異常/中斷時儲存的調用幀(ebp)恢複esp,使得esp指向KTRAP_FRAME
#ifdef DBG
    /* Assert the flags */
    pushfd
    pop edx
    test edx, EFLAGS_INTERRUPT_MASK
    jnz 6f

    /* Assert the stack */
    cmp esp, ebp
    jnz 6f

    /* Assert the trap frame */
#endif
5:
#ifdef DBG
    sub dword ptr [esp+KTRAP_FRAME_DEBUGARGMARK], 0xBADB0D00
    jnz 0f

    /* Assert FS */
    mov bx, fs
    cmp bx, KGDT_R0_PCR
    jnz 1f

    /* Assert exception list */
    cmp dword ptr PCR[KPCR_EXCEPTION_LIST], 0
    jnz 2f

1:
    push -1
    call _KeBugCheck@4
#endif

2:
    /* Get exception list */
    mov edx, [esp+KTRAP_FRAME_EXCEPTION_LIST]

#ifdef DBG
    /* Assert the saved exception list */
    or edx, edx
    jnz 1f
    UNHANDLED_PATH
1:
#endif

    /* Restore it */
    /*
    異常處理過程中,可能會添加新的SEH到fs:[0]中,這裡截斷新生成的SEH節點,恢複以前的SEH連結清單
    */
    mov PCR[KPCR_EXCEPTION_LIST], edx

.if \RestorePreviousMode
    /*發生系統調用時RestorePreviousMode==1*/
    /* Get previous mode */
    mov ecx, [esp+KTRAP_FRAME_PREVIOUS_MODE]

#ifdef DBG
    /* Assert the saved previous mode */
    cmp ecx, -1
    jnz 1f
    UNHANDLED_PATH
1:
#endif

    /* Restore the previous mode */
    mov esi, PCR[KPCR_CURRENT_THREAD]
    mov byte ptr [esi+KTHREAD_PREVIOUS_MODE], cl
.else

#ifdef DBG
    /* Assert the saved previous mode */
    mov ecx, [esp+KTRAP_FRAME_PREVIOUS_MODE]
    cmp ecx, -1
    jz 1f
    UNHANDLED_PATH
1:
#endif
.endif

    /* Check for debug registers */
    test dword ptr [esp+KTRAP_FRAME_DR7], ~DR7_RESERVED_MASK
    jnz 2f

    /* Check for V86 */
4:
    test dword ptr [esp+KTRAP_FRAME_EFLAGS], EFLAGS_V86_MASK
    jnz V86_Exit

    /* Check if the frame was edited */
    test word ptr [esp+KTRAP_FRAME_CS], FRAME_EDITED
    jz 7f

.if \RestoreAllRegs
    /* Check the old mode */
    cmp word ptr [esp+KTRAP_FRAME_CS], KGDT_R3_CODE + RPL_MASK
    bt word ptr [esp+KTRAP_FRAME_CS], 0
    cmc
    ja 8f
.endif

.if \RestoreVolatiles
    /* Restore volatiles */
    mov edx, [esp+KTRAP_FRAME_EDX]
    mov ecx, [esp+KTRAP_FRAME_ECX]
    mov eax, [esp+KTRAP_FRAME_EAX]
.endif

    /* Check if we were called from kernel mode */
    cmp word ptr [ebp+KTRAP_FRAME_CS], KGDT_R0_CODE
    jz 9f

.if \RestoreSegments
    /* Restore segment registers */
    /*跳過調試寄存器,直接從GS處恢複*/
    lea esp, [ebp+KTRAP_FRAME_GS]
    pop gs
    pop es
    pop ds
.endif

    /* Restore FS */
3:
    /*[ebp+KTRAP_FRAME_FS]存入棧頂,然後pop fs*/
    lea esp, [ebp+KTRAP_FRAME_FS]
    pop fs

9:
    /* Skip debug information and unsaved registers */
    /*跳過段,直接從GS處恢複*/
    lea esp, [ebp+KTRAP_FRAME_EDI]
    pop edi
    pop esi
    pop ebx
    pop ebp

    /* Check for ABIOS */
    cmp word ptr [esp+8], 0x80
    ja AbiosExit

    /* Pop error code */
    /*從Pop error code看出,至此,堆棧已經是發生系統調用時的堆棧:eflag eip cs*/
    add esp, 4

.if \SystemCall
    /* Check if previous CS is from user-mode */
    test dword ptr [esp+4], 1

    /* It is, so use Fast Exit */
    jnz FastExit

    /* Jump back to stub */
    /*
    上文Pop error code->add esp, 4,堆棧上還剩eip cs eflag,
    下面有條popf指令,猜測pop edx應該儲存發生調用的位址
    */
    pop edx //=====a)
    pop ecx
    popf
    /*
    結合注釋Jump back to stub,jmp edx就是跳回到調用發生處
    */
    jmp edx

.ret:
.endif
    //異常 就從此退出?
    iret      

繼續閱讀