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,形成堆栈链。这么看堆栈链也许还不是很明了,还是看下内存值:

图中每个函数可以通过保存在本函数栈底的前一个函数的栈帧,找到前一个函数的堆底。图上最左边是嵌套最深处的函数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

最终如果是异常或者中断通过iret返回到发生异常中断的位置,而对于系统调用a)处注释表明,经过前面的出栈操作,此时堆栈上只剩发生系统调用时的eip segcs和eflags,当执行pop edx时,edx中保存了eip,再执行jmp edx实际就是跳转到发生系统调用的位置。

你可能感兴趣的:(windows)