鱼C论坛

 找回密码
 立即注册

Windows内核学习-采用ReactOS源码(5)

热度 2已有 852 次阅读2014-11-11 23:08 |个人分类:Windows

_KiSystemService()

_KiSystemService()是所有的系统调用入口函数。对应于中断向量0x2e。所以,当中间函数执行int 0x2e时,CPU就进入系统空间中的这个函数。

进入系统空间时,CPU自动将下列信息压入系统空间堆栈:

①用户空间的堆栈位置,包括SS和ESP的内容

②EFLAGS的内容

③用户空间指令位置,包括CS和EIP的内容

这些信息是将来返回用户空间所必须的。

系统空间堆栈又在哪里呢?每个线程都有自己的系统空间堆栈,器堆栈段寄存器SS和堆栈指针ESP的内容保存在一个称为“任务状态段”即TSS的数据结构里面。与此对应,CPU中有个称为“任务寄存器”即TR的段寄存器。每当从用户空间进入系统空间时,CPU会根据TR的指引从TSS中获取当前进程的SS和ESP的值。与普通的段寄存器不同,对于寄存器TR的内容装入和保存都有专门的命令,即ltr和str。这两条命令均属于特权命令,用户空间的程序是不能执行这两条命令的。由此可见,自陷命令所蕴含的操作序列是很长的,它要根据TR的指引从TSS获取当前线程的系统空间堆栈,再把好几个寄存器的内容压入这个堆栈,还要根据IDTR的指引从中断向量表中获取CS和EIP的值,这里面涉及的时钟周期显然不少。正因为这样,后来才有了“快速系统调用指令”sysenter和sysexit的出现。

在系统中,前缀Ke表示“内核管理层”,而前缀Ki表示内核中的底层,特别是中断、异常和自陷相关的函数。下面的代码位于(./ntoskrnl/ke/i386/trap.s)

EXTERN @KiSystemServiceHandler@8:PROC

PUBLIC _KiSystemService

.PROC _KiSystemService

    FPO 0, 0, 0, 0, 1, FRAME_TRAP

    KiEnterTrap (KI_PUSH_FAKE_ERROR_CODE OR KI_NONVOLATILES_ONLY OR KI_DONT_SAVE_SEGS)

    KiCallHandler @KiSystemServiceHandler@8

.ENDP

中断处理包含两个部分,第一步利用KiEnterTrap宏创建一个中断处理框架。在这个框架中利用堆栈保存必须保存的寄存器。第二步跳转到真正的中断处理函数中进行中断处理,这里真正的中断处理函数是KiSystemServiceHandler。下面这段代码在(./ntoskrnl/include/internal/i386/asmmacro.S)

MACRO(KiEnterTrap, Flags)

    LOCAL kernel_trap

    LOCAL not_v86_trap

    LOCAL set_sane_segs


    /* 检查此自陷需要何种自陷框架 */

    if (Flags AND KI_FAST_SYSTEM_CALL)


        /* SYSENTER要我们建立一个完整的ring转换自陷框架 */

        FrameSize = KTRAP_FRAME_EIP


        /* Fixup fs. cx is free to clobber */

        mov cx, KGDT_R0_PCR

        mov fs, cx


        /* 获取指向TSS的指针 */

        mov ecx, fs:[KPCR_TSS]


        /* 获取一个堆栈指针 */

        mov esp, [ecx + KTSS_ESP0]


        /* 建立一个虚假的硬件自陷框架 */

        push KGDT_R3_DATA or RPL_MASK

        push edx

        pushfd

        push KGDT_R3_CODE or RPL_MASK

        push dword ptr ds:[KUSER_SHARED_SYSCALL_RET]


    elseif (Flags AND KI_SOFTWARE_TRAP)


        /* Software traps need a complete non-ring transition trap frame */

        FrameSize = KTRAP_FRAME_ESP


        /* Software traps need to get their EIP from the caller's frame */

        pop eax


    elseif (Flags AND KI_PUSH_FAKE_ERROR_CODE)


        /* If the trap doesn't have an error code, we'll make space for it */

        FrameSize = KTRAP_FRAME_EIP


    else


        /* The trap already has an error code, so just make space for the rest */

        FrameSize = KTRAP_FRAME_ERROR_CODE


    endif


    /* 为这个框架留出空间 */

    sub esp, FrameSize


    /* 保存非易失性寄存器 */

    mov [esp + KTRAP_FRAME_EBP], ebp

    mov [esp + KTRAP_FRAME_EBX], ebx

    mov [esp + KTRAP_FRAME_ESI], esi

    mov [esp + KTRAP_FRAME_EDI], edi


    /* 为了系统调用而保存eax, 以便C语言的处理程序使用 */

    mov [esp + KTRAP_FRAME_EAX], eax


    /* caller是否只想要非易失性寄存器? */

    if (NOT (Flags AND KI_NONVOLATILES_ONLY))

        /* Otherwise, save the volatiles as well */

        mov [esp + KTRAP_FRAME_ECX], ecx

        mov [esp + KTRAP_FRAME_EDX], edx

    endif


    /* 保存段寄存器吗? */

    if (Flags AND KI_DONT_SAVE_SEGS)


        /* Initialize TrapFrame segment registers with sane values */

        mov eax, KGDT_R3_DATA OR 3

        mov ecx, fs

        mov [esp + KTRAP_FRAME_DS], eax

        mov [esp + KTRAP_FRAME_ES], eax

        mov [esp + KTRAP_FRAME_FS], ecx

        mov dword ptr [esp + KTRAP_FRAME_GS], 0


    else


        /* Check for V86 mode */

        test byte ptr [esp + KTRAP_FRAME_EFLAGS + 2], (EFLAGS_V86_MASK / HEX(10000))

        jz not_v86_trap


            /* Restore V8086 segments into Protected Mode segments */

            mov eax, [esp + KTRAP_FRAME_V86_DS]

            mov ecx, [esp + KTRAP_FRAME_V86_ES]

            mov [esp + KTRAP_FRAME_DS], eax

            mov [esp + KTRAP_FRAME_ES], ecx

            mov eax, [esp + KTRAP_FRAME_V86_FS]

            mov ecx, [esp + KTRAP_FRAME_V86_GS]

            mov [esp + KTRAP_FRAME_FS], eax

            mov [esp + KTRAP_FRAME_GS], ecx

            jmp set_sane_segs


        not_v86_trap:


            /* Save segment selectors */

            mov eax, ds

            mov ecx, es

            mov [esp + KTRAP_FRAME_DS], eax

            mov [esp + KTRAP_FRAME_ES], ecx

            mov eax, fs

            mov ecx, gs

            mov [esp + KTRAP_FRAME_FS], eax

            mov [esp + KTRAP_FRAME_GS], ecx


    endif


set_sane_segs:

    /* Load correct data segments */

    mov ax, KGDT_R3_DATA OR RPL_MASK

    mov ds, ax

    mov es, ax


    /* Fast system calls have fs already fixed */

    if (Flags AND KI_FAST_SYSTEM_CALL)


        /* Enable interrupts and set a sane FS value */

        or dword ptr [esp + KTRAP_FRAME_EFLAGS], EFLAGS_INTERRUPT_MASK

        mov dword ptr [esp + KTRAP_FRAME_FS], KGDT_R3_TEB or RPL_MASK


        /* Set sane active EFLAGS */

        push 2

        popfd


        /* Point edx to the usermode parameters */

        add edx, 8

    else

        /* Otherwise fix fs now */

        mov ax, KGDT_R0_PCR

        mov fs, ax

    endif


#if DBG

    /* Keep the frame chain intact */

    mov eax, [esp + KTRAP_FRAME_EIP]

    mov [esp + KTRAP_FRAME_DEBUGEIP], eax

    mov [esp + KTRAP_FRAME_DEBUGEBP], ebp

    mov ebp, esp

#endif


    /* Set parameter 1 (ECX) to point to the frame */

    mov ecx, esp


    /* Clear direction flag */

    cld


ENDM

由于系统在进入中断的时候自动保存用户堆栈段SS和堆栈指针ESP以及代码段CS和程序计数器EIP和标志寄存器,同时当系统从用户态转入到系统态时用户通过TR寄存器得到当前线程的内核堆栈段SS和堆栈指针ESP。所以在这里只需要保存相应的寄存器数据就可以了。同时由于V86模式下,属于独占任务,并没有用户堆栈和系统堆栈之分,所以需要对V86模式下的系统的SS和ESP进行调整。同时,还需要调整和切换代码段和数据段。到最后的宏结束的位置,将esp存入到ecx中,这样ecx就保存了堆栈框架,而EDX则用于保存之前保存在堆栈当中的用户参数。(具体可参见之前的系统调用宏的分析)。下面这段代码也在(./ntoskrnl/include/internal/i386/asmmacro.S)

MACRO(KiCallHandler, Handler)

#if DBG

    /* Use a call to get the return address for back traces */

    call Handler

#else

    /* 使用更快速的jmp */

    jmp Handler

#endif

    nop

ENDM

中断调用函数很简单,就是跳转到中断处理函数当中,实际上这里已经由保存了中断的返回地址。

DECLSPEC_NORETURN

VOID

FASTCALL

KiSystemServiceHandler(IN PKTRAP_FRAME TrapFrame,

                       IN PVOID Arguments)

{

    PKTHREAD Thread;

    PKSERVICE_TABLE_DESCRIPTOR DescriptorTable;

    ULONG Id, Offset, StackBytes, Result;

    PVOID Handler;

    ULONG SystemCallNumber = TrapFrame->Eax;


    /* 获取当前线程 */

    Thread = KeGetCurrentThread();


    /* Set debug header */

    KiFillTrapFrameDebug(TrapFrame);


    /* Chain trap frames */

    TrapFrame->Edx = (ULONG_PTR)Thread->TrapFrame;


    /* 无错误码 */

    TrapFrame->ErrCode = 0;


    /* 保存前面的模式 */

    TrapFrame->PreviousPreviousMode = Thread->PreviousMode;


    /* Save the SEH chain and terminate it for now */

    TrapFrame->ExceptionList = KeGetPcr()->NtTib.ExceptionList;

    KeGetPcr()->NtTib.ExceptionList = EXCEPTION_CHAIN_END;


    /* Default to debugging disabled */

    TrapFrame->Dr7 = 0;


    /* 检查此框架是否来自用户模式 */

    if (KiUserTrap(TrapFrame))

    {

        /* Check for active debugging */

        if (KeGetCurrentThread()->Header.DebugActive & 0xFF)

        {

            /* 处理 调试寄存器 */

            KiHandleDebugRegistersOnTrapEntry(TrapFrame);

        }

    }


    /* Set thread fields */

    Thread->TrapFrame = TrapFrame;

    Thread->PreviousMode = KiUserTrap(TrapFrame);


    /* 启用中断 */

    _enable();


    /* Decode the system call number */

    Offset = (SystemCallNumber >> SERVICE_TABLE_SHIFT) & SERVICE_TABLE_MASK;

    Id = SystemCallNumber & SERVICE_NUMBER_MASK;


    /* 获取描述表 */

    DescriptorTable = (PVOID)((ULONG_PTR)Thread->ServiceTable + Offset);


    /* Validate the system call number */

    if (__builtin_expect(Id >= DescriptorTable->Limit, 0))

    {

        /* Check if this is a GUI call */

        if (!(Offset & SERVICE_TABLE_TEST))

        {

            /* Fail the call */

            Result = STATUS_INVALID_SYSTEM_SERVICE;

            goto ExitCall;

        }


        /* Convert us to a GUI thread -- must wrap in ASM to get new EBP */

        Result = KiConvertToGuiThread();


        /* Reload trap frame and descriptor table pointer from new stack */

        TrapFrame = *(volatile PVOID*)&Thread->TrapFrame;

        DescriptorTable = (PVOID)(*(volatile ULONG_PTR*)&Thread->ServiceTable + Offset);


        if (!NT_SUCCESS(Result))

        {

            /* Set the last error and fail */

            //SetLastWin32Error(RtlNtStatusToDosError(Result));

            goto ExitCall;

        }


        /* Validate the system call number again */

        if (Id >= DescriptorTable->Limit)

        {

            /* Fail the call */

            Result = STATUS_INVALID_SYSTEM_SERVICE;

            goto ExitCall;

        }

    }


    /* Check if this is a GUI call */

    if (__builtin_expect(Offset & SERVICE_TABLE_TEST, 0))

    {

        /* Get the batch count and flush if necessary */

        if (NtCurrentTeb()->GdiBatchCount) KeGdiFlushUserBatch();

    }


    /* Increase system call count */

    KeGetCurrentPrcb()->KeSystemCalls++;


    /* FIXME: Increase individual counts on debug systems */

    //KiIncreaseSystemCallCount(DescriptorTable, Id);


    /* Get stack bytes */

    StackBytes = DescriptorTable->Number[Id];


    /* Probe caller stack */

    if (__builtin_expect((Arguments < (PVOID)MmUserProbeAddress) && !(KiUserTrap(TrapFrame)), 0))

    {

        /* Access violation */

        UNIMPLEMENTED_FATAL();

    }


    /* Call pre-service debug hook */

    KiDbgPreServiceHook(SystemCallNumber, Arguments);


    /* Get the handler and make the system call */

    Handler = (PVOID)DescriptorTable->Base[Id];

    Result = KiSystemCallTrampoline(Handler, Arguments, StackBytes);


    /* Call post-service debug hook */

    Result = KiDbgPostServiceHook(SystemCallNumber, Result);


    /* Make sure we're exiting correctly */

    KiExitSystemCallDebugChecks(Id, TrapFrame);


    /* Restore the old trap frame */

ExitCall:

    Thread->TrapFrame = (PKTRAP_FRAME)TrapFrame->Edx;


    /* Exit from system call */

    KiServiceExit(TrapFrame, Result);

}

首先TRAP_FRAME是在堆栈中的一个结构体,这个结构体在进入这个函数之前仅仅简单的填充寄存器的各个位并没有填充其他的信息位。所以先调用KiFillTrapFrameDebug进行补充填充。然后保存之前的TRAP_FRAME;之所以需要这个操作是因为在windows当中可以实现从内核发起系统调用。而EDX在TRAP_FRAME当中属于不必要保存的信息——EDX用于传递参数,所以再返回的时候EDX属于无用的信息,在这里借用EDX用于形成一个链式的系统调用。然后从EAX寄存器当中得到系统的调用号,通过这个调用号从线程的调用参数表得到调,用参数的格式来对EDX进行解析,并从系统调用表当中得到调用函数的地址。不过在简单的进入到系统调用之前需要一个对系统调用号的验证,因为windows系统的系统调用分为两个部分,第一部分是一直就有的系统调用,另一部分是GUI操作的系统调用。如果是GUI调用就需要调用KiConvertToGuiThread函数将当前的线程转换成一个GUI线程。并将系统调用表加上额外的GUI调用部分。

DECLSPEC_NORETURN

VOID

FASTCALL

KiServiceExit(IN PKTRAP_FRAME TrapFrame,

              IN NTSTATUS Status)

{

    ASSERT((TrapFrame->EFlags & EFLAGS_V86_MASK) == 0);

    ASSERT(!KiIsFrameEdited(TrapFrame));


    /* 将状态复制到EAX中 */

    TrapFrame->Eax = Status;


    /* Common trap exit code */

    KiCommonExit(TrapFrame, FALSE);


    /* 恢复前面的模式 */

    KeGetCurrentThread()->PreviousMode = (CCHAR)TrapFrame->PreviousPreviousMode;


    /* 检查用户模式出口 */

    if (KiUserTrap(TrapFrame))

    {

        /* 检查我们是否在单步执行 */

        if (TrapFrame->EFlags & EFLAGS_TF)

        {

            /* 必须使用IRET处理程序 */

            KiSystemCallTrapReturn(TrapFrame);

        }

        else

        {

            /* 我们可以使用sysexit处理程序 */

            KiFastCallExitHandler(TrapFrame);

        }

    }


    /* 内核模式的出口 */

    KiSystemCallReturn(TrapFrame);

}

经过上面的调整之后就可以调用KiSystemCallTrampoline进行真正的系统调用了。在系统调用之后就可以利用KiServiceExit函数进行返回。在KiServiceExit会调用KiCommonExit函数进行APC提交,然后根据之前进入系统调用的不同,而进行有区别的中断退出。这个过程实际上是进入中断的一个反过程。


路过

鸡蛋
2

鲜花

握手

雷人

刚表态过的朋友 (2 人)

评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 立即注册

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-4-19 16:24

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

返回顶部