ReactOS SYSCALL_PROLOG/TRAP_EPILOG及相关代码注释 (1)

来源:互联网 发布:编程之魂 编辑:程序博客网 时间:2024/05/18 02:31

    ReactOS源码中,通过系统调用/异常/中断进入内核,首先会遇到SYSCALL_PROLOG/TRAP_PROLOG之类的入口函数;当调KiServiceExit退出内核空间时,又会调用SYSCALL_PROLOG之类的出口函数。这些代码在R3-R0切换期间起到什么作用?本文旨在注释这部分及相关代码作用。

    先看下在什么语境下会调用这些宏:

1).系统调用时调用该宏:

.func KiSystemServiceTRAP_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 KiTrap14TRAP_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        ebp00401021   mov         ebp,esp6:    int StackChain1()7:    {00401050   push        ebp00401051   mov         ebp,esp00401053   sub         esp,40h12:   int main()13:   {0040D480   push        ebp0040D481   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 */#endif5:#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 2f1:    push -1    call _KeBugCheck@4#endif2:    /* Get exception list */    mov edx, [esp+KTRAP_FRAME_EXCEPTION_LIST]#ifdef DBG    /* Assert the saved exception list */    or edx, edx    jnz 1f    UNHANDLED_PATH1:#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_PATH1:#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_PATH1:#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 fs9:    /* 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实际就是跳转到发生系统调用的位置。

0 0
原创粉丝点击