在X86体系架构中,中断处理向量由一个中断描述符表保存。中断描述符表的每个描述符项包括一个中断处理函数的地址、一个32位的描述符属性和中断之后中断所在代码的特权级别。下面是一个描述符项的宏定义,每个处理器包含256个这样的宏定义用于组成一个数组变量kidt。在系统初始化时,kidt当中的元素经过一些处理之后会填充到IDT表项当中。其中,当bits被定义为INT_32_DPL0时,表明发生的是异常;而当bits被定义为INT_32_DPL3时,表明发生的是中断。
MACRO(idt, Handler, Bits) .long VAL(Handler) .short VAL(Bits) .short KGDT_R0_CODE ENDM
在中断处理当中有一个系统调用的中断定义,用于当用户层向系统层发出系统调用请求的时候进行特权切换。这个中断项的定义如下所示:
idt _KiSystemService, INT_32_DPL3
从上面的宏定义可以看出,中断处理函数是KiSystemService。
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。
MACRO(KiEnterTrap, Flags) LOCAL kernel_trap LOCAL not_v86_trap LOCAL set_sane_segs /* Check what kind of trap frame this trap requires */ if (Flags AND KI_FAST_SYSTEM_CALL) /* SYSENTER requires us to build a complete ring transition trap frame */ FrameSize = KTRAP_FRAME_V86_ES /* Fixup fs. cx is free to clobber */ mov cx, KGDT_R0_PCR mov fs, cx /* Get pointer to the TSS */ mov ecx, fs:[KPCR_TSS] /* Get a stack pointer */ mov esp, [ecx + KTSS_ESP0] 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 /* Make space for this frame */ sub esp, FrameSize /* Save nonvolatile registers */ mov [esp + KTRAP_FRAME_EBP], ebp mov [esp + KTRAP_FRAME_EBX], ebx mov [esp + KTRAP_FRAME_ESI], esi mov [esp + KTRAP_FRAME_EDI], edi /* Save eax for system calls, for use by the C handler */ mov [esp + KTRAP_FRAME_EAX], eax /* Does the caller want nonvolatiles only? */ 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 /* Save segment registers? */ 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 (NOT (Flags AND KI_FAST_SYSTEM_CALL)) /* Otherwise fix fs now */ mov ax, KGDT_R0_PCR mov fs, ax 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则用于保存之前保存在堆栈当中的用户参数。(具体可参见之前的系统调用宏的分析)。
MACRO(KiCallHandler, Handler) jmp Handler nop ENDM中断调用函数很简单,就是跳转到中断处理函数当中,实际上这里已经由保存了中断的返回地址。
DECLSPEC_NORETURN VOID FASTCALL KiSystemServiceHandler(IN PKTRAP_FRAME TrapFrame, IN PVOID Arguments) { /* Call the shared handler (inline) */ KiSystemCall(TrapFrame, Arguments); }
系统调用的服务函数定义为DECLSPEC_NORETURN表明这个函数不会返回,因为实际上在之后的返回函数中需要切换到用户的EIP,所以不存在常规的函数返回,也就是说这个函数不会返回。
DECLSPEC_NORETURN VOID FORCEINLINE KiSystemCall(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(); KiFillTrapFrameDebug(TrapFrame); /* Chain trap frames */ TrapFrame->Edx = (ULONG_PTR)Thread->TrapFrame; /* No error code */ TrapFrame->ErrCode = 0; /* Save previous mode */ TrapFrame->PreviousPreviousMode = Thread->PreviousMode; /* Save the SEH chain and terminate it for now */ TrapFrame->ExceptionList = KeGetPcr()->NtTib.ExceptionList; KeGetPcr()->NtTib.ExceptionList = EXCEPTION_CHAIN_END; /* Clear DR7 and check for debugging */ TrapFrame->Dr7 = 0; if (__builtin_expect(Thread->Header.DebugActive & 0xFF, 0)) { UNIMPLEMENTED; while (TRUE); } /* Set thread fields */ Thread->TrapFrame = TrapFrame; Thread->PreviousMode = KiUserTrap(TrapFrame); /* Enable interrupts */ _enable(); /* Decode the system call number */ Offset = (SystemCallNumber >> SERVICE_TABLE_SHIFT) & SERVICE_TABLE_MASK; Id = SystemCallNumber & SERVICE_NUMBER_MASK; /* Get descriptor table */ 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(); if (!NT_SUCCESS(Result)) { /* Set the last error and fail */ //SetLastWin32Error(RtlNtStatusToDosError(Result)); goto ExitCall; } /* Reload trap frame and descriptor table pointer from new stack */ TrapFrame = *(volatile PVOID*)&Thread->TrapFrame; DescriptorTable = (PVOID)(*(volatile ULONG_PTR*)&Thread->ServiceTable + Offset); /* 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; while (TRUE); } /* 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)); /* Copy the status into EAX */ TrapFrame->Eax = Status; /* Common trap exit code */ KiCommonExit(TrapFrame, 0); /* Restore previous mode */ KeGetCurrentThread()->PreviousMode = (CCHAR)TrapFrame->PreviousPreviousMode; /* Check for user mode exit */ if (TrapFrame->SegCs & MODE_MASK) { /* Check if we were single stepping */ if (TrapFrame->EFlags & EFLAGS_TF) { /* Must use the IRET handler */ KiSystemCallTrapReturn(TrapFrame); } else { /* We can use the sysexit handler */ KiFastCallExitHandler(TrapFrame); } } KiSystemCallReturn(TrapFrame); }
经过上面的调整之后就可以调用KiSystemCallTrampoline进行真正的系统调用了。在系统调用之后就可以利用KiServiceExit函数进行返回。在KiServiceExit会调用KiCommonExit函数进行APC提交,然后根据之前进入系统调用的不同,而进行有区别的中断退出。这个过程实际上是进入中断的一个反过程。