diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/CpuExceptionCommon.h b/UefiCpuPkg/Library/CpuExceptionHandlerLib/CpuExceptionCommon.h index 740a58828b..e10d9379d5 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/CpuExceptionCommon.h +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/CpuExceptionCommon.h @@ -48,6 +48,17 @@ 0xb21d9148, 0x9211, 0x4d8f, { 0xad, 0xd3, 0x66, 0xb1, 0x89, 0xc9, 0x2c, 0x83 } \ } +#define CPU_STACK_SWITCH_EXCEPTION_NUMBER \ + FixedPcdGetSize (PcdCpuStackSwitchExceptionList) + +#define CPU_STACK_SWITCH_EXCEPTION_LIST \ + FixedPcdGetPtr (PcdCpuStackSwitchExceptionList) + +#define CPU_KNOWN_GOOD_STACK_SIZE \ + FixedPcdGet32 (PcdCpuKnownGoodStackSize) + +#define CPU_TSS_GDT_SIZE (SIZE_2KB + CPU_TSS_DESC_SIZE + CPU_TSS_SIZE) + // // Record exception handler information // @@ -288,5 +299,33 @@ CommonExceptionHandlerWorker ( IN EXCEPTION_HANDLER_DATA *ExceptionHandlerData ); +/** + Setup separate stack for specific exceptions. + + @param[in] StackSwitchData Pointer to data required for setuping up + stack switch. + + @retval EFI_SUCCESS The exceptions have been successfully + initialized with new stack. + @retval EFI_INVALID_PARAMETER StackSwitchData contains invalid content. +**/ +EFI_STATUS +ArchSetupExcpetionStack ( + IN CPU_EXCEPTION_INIT_DATA *StackSwitchData + ); + +/** + Return address map of exception handler template so that C code can generate + exception tables. The template is only for exceptions using task gate instead + of interrupt gate. + + @param AddressMap Pointer to a buffer where the address map is returned. +**/ +VOID +EFIAPI +AsmGetTssTemplateMap ( + OUT EXCEPTION_HANDLER_TEMPLATE_MAP *AddressMap + ); + #endif diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeCpuExceptionHandlerLib.inf b/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeCpuExceptionHandlerLib.inf index f4a8d01c80..58e55a8a2e 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeCpuExceptionHandlerLib.inf +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeCpuExceptionHandlerLib.inf @@ -30,6 +30,7 @@ [Sources.Ia32] Ia32/ExceptionHandlerAsm.asm Ia32/ExceptionHandlerAsm.nasm + Ia32/ExceptionTssEntryAsm.nasm Ia32/ExceptionHandlerAsm.S Ia32/ArchExceptionHandler.c Ia32/ArchInterruptDefs.h @@ -47,6 +48,11 @@ PeiDxeSmmCpuException.c DxeException.c +[Pcd] + gEfiMdeModulePkgTokenSpaceGuid.PcdCpuStackGuard + gUefiCpuPkgTokenSpaceGuid.PcdCpuStackSwitchExceptionList + gUefiCpuPkgTokenSpaceGuid.PcdCpuKnownGoodStackSize + [Packages] MdePkg/MdePkg.dec MdeModulePkg/MdeModulePkg.dec diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeException.c b/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeException.c index 31febec976..9a72b37e77 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeException.c +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeException.c @@ -25,6 +25,10 @@ UINTN mEnabledInterruptNum = 0; EXCEPTION_HANDLER_DATA mExceptionHandlerData; +UINT8 mNewStack[CPU_STACK_SWITCH_EXCEPTION_NUMBER * + CPU_KNOWN_GOOD_STACK_SIZE]; +UINT8 mNewGdt[CPU_TSS_GDT_SIZE]; + /** Common exception handler. @@ -197,3 +201,83 @@ RegisterCpuInterruptHandler ( { return RegisterCpuInterruptHandlerWorker (InterruptType, InterruptHandler, &mExceptionHandlerData); } + +/** + Initializes CPU exceptions entries and setup stack switch for given exceptions. + + This method will call InitializeCpuExceptionHandlers() to setup default + exception handlers unless indicated not to do it explicitly. + + If InitData is passed with NULL, this method will use the resource reserved + by global variables to initialize it; Otherwise it will use data in InitData + to setup stack switch. This is for the different use cases in DxeCore and + Cpu MP exception initialization. + + @param[in] VectorInfo Pointer to reserved vector list. + @param[in] InitData Pointer to data required to setup stack switch for + given exceptions. + + @retval EFI_SUCCESS The exceptions have been successfully + initialized. + @retval EFI_INVALID_PARAMETER VectorInfo or InitData contains invalid + content. + +**/ +EFI_STATUS +EFIAPI +InitializeCpuExceptionHandlersEx ( + IN EFI_VECTOR_HANDOFF_INFO *VectorInfo OPTIONAL, + IN CPU_EXCEPTION_INIT_DATA *InitData OPTIONAL + ) +{ + EFI_STATUS Status; + CPU_EXCEPTION_INIT_DATA EssData; + IA32_DESCRIPTOR Idtr; + IA32_DESCRIPTOR Gdtr; + + // + // To avoid repeat initialization of default handlers, the caller should pass + // an extended init data with InitDefaultHandlers set to FALSE. There's no + // need to call this method to just initialize default handlers. Call non-ex + // version instead; or this method must be implemented as a simple wrapper of + // non-ex version of it, if this version has to be called. + // + if (InitData == NULL || InitData->X64.InitDefaultHandlers) { + Status = InitializeCpuExceptionHandlers (VectorInfo); + } else { + Status = EFI_SUCCESS; + } + + if (!EFI_ERROR (Status)) { + // + // Initializing stack switch is only necessary for Stack Guard functionality. + // + if (PcdGetBool (PcdCpuStackGuard)) { + if (InitData == NULL) { + SetMem (mNewGdt, sizeof (mNewGdt), 0); + + AsmReadIdtr (&Idtr); + AsmReadGdtr (&Gdtr); + + EssData.X64.Revision = CPU_EXCEPTION_INIT_DATA_REV; + EssData.X64.KnownGoodStackTop = (UINTN)mNewStack; + EssData.X64.KnownGoodStackSize = CPU_KNOWN_GOOD_STACK_SIZE; + EssData.X64.StackSwitchExceptions = CPU_STACK_SWITCH_EXCEPTION_LIST; + EssData.X64.StackSwitchExceptionNumber = CPU_STACK_SWITCH_EXCEPTION_NUMBER; + EssData.X64.IdtTable = (VOID *)Idtr.Base; + EssData.X64.IdtTableSize = Idtr.Limit + 1; + EssData.X64.GdtTable = mNewGdt; + EssData.X64.GdtTableSize = sizeof (mNewGdt); + EssData.X64.ExceptionTssDesc = mNewGdt + Gdtr.Limit + 1; + EssData.X64.ExceptionTssDescSize = CPU_TSS_DESC_SIZE; + EssData.X64.ExceptionTss = mNewGdt + Gdtr.Limit + 1 + CPU_TSS_DESC_SIZE; + EssData.X64.ExceptionTssSize = CPU_TSS_SIZE; + + InitData = &EssData; + } + Status = ArchSetupExcpetionStack (InitData); + } + } + + return Status; +} diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/Ia32/ArchExceptionHandler.c b/UefiCpuPkg/Library/CpuExceptionHandlerLib/Ia32/ArchExceptionHandler.c index f2c39eb193..6ac8549839 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/Ia32/ArchExceptionHandler.c +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/Ia32/ArchExceptionHandler.c @@ -107,6 +107,194 @@ ArchRestoreExceptionContext ( SystemContext.SystemContextIa32->ExceptionData = ReservedVectors[ExceptionType].ExceptionData; } +/** + Setup separate stack for given exceptions. + + @param[in] StackSwitchData Pointer to data required for setuping up + stack switch. + + @retval EFI_SUCCESS The exceptions have been successfully + initialized with new stack. + @retval EFI_INVALID_PARAMETER StackSwitchData contains invalid content. + +**/ +EFI_STATUS +ArchSetupExcpetionStack ( + IN CPU_EXCEPTION_INIT_DATA *StackSwitchData + ) +{ + IA32_DESCRIPTOR Gdtr; + IA32_DESCRIPTOR Idtr; + IA32_IDT_GATE_DESCRIPTOR *IdtTable; + IA32_TSS_DESCRIPTOR *TssDesc; + IA32_TASK_STATE_SEGMENT *Tss; + UINTN StackTop; + UINTN Index; + UINTN Vector; + UINTN TssBase; + UINTN GdtSize; + EXCEPTION_HANDLER_TEMPLATE_MAP TemplateMap; + + if (StackSwitchData == NULL || + StackSwitchData->Ia32.Revision != CPU_EXCEPTION_INIT_DATA_REV || + StackSwitchData->Ia32.KnownGoodStackTop == 0 || + StackSwitchData->Ia32.KnownGoodStackSize == 0 || + StackSwitchData->Ia32.StackSwitchExceptions == NULL || + StackSwitchData->Ia32.StackSwitchExceptionNumber == 0 || + StackSwitchData->Ia32.StackSwitchExceptionNumber > CPU_EXCEPTION_NUM || + StackSwitchData->Ia32.GdtTable == NULL || + StackSwitchData->Ia32.IdtTable == NULL || + StackSwitchData->Ia32.ExceptionTssDesc == NULL || + StackSwitchData->Ia32.ExceptionTss == NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // The caller is responsible for that the GDT table, no matter the existing + // one or newly allocated, has enough space to hold descriptors for exception + // task-state segments. + // + if (((UINTN)StackSwitchData->Ia32.GdtTable & (IA32_GDT_ALIGNMENT - 1)) != 0) { + return EFI_INVALID_PARAMETER; + } + + if ((UINTN)StackSwitchData->Ia32.ExceptionTssDesc < (UINTN)(StackSwitchData->Ia32.GdtTable)) { + return EFI_INVALID_PARAMETER; + } + + if ((UINTN)StackSwitchData->Ia32.ExceptionTssDesc + StackSwitchData->Ia32.ExceptionTssDescSize > + ((UINTN)(StackSwitchData->Ia32.GdtTable) + StackSwitchData->Ia32.GdtTableSize)) { + return EFI_INVALID_PARAMETER; + } + + // + // We need one descriptor and one TSS for current task and every exception + // specified. + // + if (StackSwitchData->Ia32.ExceptionTssDescSize < + sizeof (IA32_TSS_DESCRIPTOR) * (StackSwitchData->Ia32.StackSwitchExceptionNumber + 1)) { + return EFI_INVALID_PARAMETER; + } + if (StackSwitchData->Ia32.ExceptionTssSize < + sizeof (IA32_TASK_STATE_SEGMENT) * (StackSwitchData->Ia32.StackSwitchExceptionNumber + 1)) { + return EFI_INVALID_PARAMETER; + } + + TssDesc = StackSwitchData->Ia32.ExceptionTssDesc; + Tss = StackSwitchData->Ia32.ExceptionTss; + + // + // Initialize new GDT table and/or IDT table, if any + // + AsmReadIdtr (&Idtr); + AsmReadGdtr (&Gdtr); + + GdtSize = (UINTN)TssDesc + + sizeof (IA32_TSS_DESCRIPTOR) * + (StackSwitchData->Ia32.StackSwitchExceptionNumber + 1) - + (UINTN)(StackSwitchData->Ia32.GdtTable); + if ((UINTN)StackSwitchData->Ia32.GdtTable != Gdtr.Base) { + CopyMem (StackSwitchData->Ia32.GdtTable, (VOID *)Gdtr.Base, Gdtr.Limit + 1); + Gdtr.Base = (UINTN)StackSwitchData->Ia32.GdtTable; + Gdtr.Limit = (UINT16)GdtSize - 1; + } + + if ((UINTN)StackSwitchData->Ia32.IdtTable != Idtr.Base) { + Idtr.Base = (UINTN)StackSwitchData->Ia32.IdtTable; + } + if (StackSwitchData->Ia32.IdtTableSize > 0) { + Idtr.Limit = (UINT16)(StackSwitchData->Ia32.IdtTableSize - 1); + } + + // + // Fixup current task descriptor. Task-state segment for current task will + // be filled by processor during task switching. + // + TssBase = (UINTN)Tss; + + TssDesc->Bits.LimitLow = sizeof(IA32_TASK_STATE_SEGMENT) - 1; + TssDesc->Bits.BaseLow = (UINT16)TssBase; + TssDesc->Bits.BaseMid = (UINT8)(TssBase >> 16); + TssDesc->Bits.Type = IA32_GDT_TYPE_TSS; + TssDesc->Bits.P = 1; + TssDesc->Bits.LimitHigh = 0; + TssDesc->Bits.BaseHigh = (UINT8)(TssBase >> 24); + + // + // Fixup exception task descriptor and task-state segment + // + AsmGetTssTemplateMap (&TemplateMap); + StackTop = StackSwitchData->Ia32.KnownGoodStackTop - CPU_STACK_ALIGNMENT; + StackTop = (UINTN)ALIGN_POINTER (StackTop, CPU_STACK_ALIGNMENT); + IdtTable = StackSwitchData->Ia32.IdtTable; + for (Index = 0; Index < StackSwitchData->Ia32.StackSwitchExceptionNumber; ++Index) { + TssDesc += 1; + Tss += 1; + + // + // Fixup TSS descriptor + // + TssBase = (UINTN)Tss; + + TssDesc->Bits.LimitLow = sizeof(IA32_TASK_STATE_SEGMENT) - 1; + TssDesc->Bits.BaseLow = (UINT16)TssBase; + TssDesc->Bits.BaseMid = (UINT8)(TssBase >> 16); + TssDesc->Bits.Type = IA32_GDT_TYPE_TSS; + TssDesc->Bits.P = 1; + TssDesc->Bits.LimitHigh = 0; + TssDesc->Bits.BaseHigh = (UINT8)(TssBase >> 24); + + // + // Fixup TSS + // + Vector = StackSwitchData->Ia32.StackSwitchExceptions[Index]; + if (Vector >= CPU_EXCEPTION_NUM || + Vector >= (Idtr.Limit + 1) / sizeof (IA32_IDT_GATE_DESCRIPTOR)) { + continue; + } + + Tss->EIP = (UINT32)(TemplateMap.ExceptionStart + + Vector * TemplateMap.ExceptionStubHeaderSize); + Tss->EFLAGS = 0x2; + Tss->ESP = StackTop; + Tss->CR3 = AsmReadCr3 (); + Tss->ES = AsmReadEs (); + Tss->CS = AsmReadCs (); + Tss->SS = AsmReadSs (); + Tss->DS = AsmReadDs (); + Tss->FS = AsmReadFs (); + Tss->GS = AsmReadGs (); + + StackTop -= StackSwitchData->Ia32.KnownGoodStackSize; + + // + // Update IDT to use Task Gate for given exception + // + IdtTable[Vector].Bits.OffsetLow = 0; + IdtTable[Vector].Bits.Selector = (UINT16)((UINTN)TssDesc - Gdtr.Base); + IdtTable[Vector].Bits.Reserved_0 = 0; + IdtTable[Vector].Bits.GateType = IA32_IDT_GATE_TYPE_TASK; + IdtTable[Vector].Bits.OffsetHigh = 0; + } + + // + // Publish GDT + // + AsmWriteGdtr (&Gdtr); + + // + // Load current task + // + AsmWriteTr ((UINT16)((UINTN)StackSwitchData->Ia32.ExceptionTssDesc - Gdtr.Base)); + + // + // Publish IDT + // + AsmWriteIdtr (&Idtr); + + return EFI_SUCCESS; +} + /** Display processor context. diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/Ia32/ArchInterruptDefs.h b/UefiCpuPkg/Library/CpuExceptionHandlerLib/Ia32/ArchInterruptDefs.h index a8d3556a80..d9ded5977f 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/Ia32/ArchInterruptDefs.h +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/Ia32/ArchInterruptDefs.h @@ -41,4 +41,12 @@ typedef struct { UINT8 HookAfterStubHeaderCode[HOOKAFTER_STUB_SIZE]; } RESERVED_VECTORS_DATA; +#define CPU_TSS_DESC_SIZE \ + (sizeof (IA32_TSS_DESCRIPTOR) * \ + (PcdGetSize (PcdCpuStackSwitchExceptionList) + 1)) + +#define CPU_TSS_SIZE \ + (sizeof (IA32_TASK_STATE_SEGMENT) * \ + (PcdGetSize (PcdCpuStackSwitchExceptionList) + 1)) + #endif diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/Ia32/ExceptionTssEntryAsm.nasm b/UefiCpuPkg/Library/CpuExceptionHandlerLib/Ia32/ExceptionTssEntryAsm.nasm new file mode 100644 index 0000000000..62bcedea1a --- /dev/null +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/Ia32/ExceptionTssEntryAsm.nasm @@ -0,0 +1,398 @@ +;------------------------------------------------------------------------------ ; +; Copyright (c) 2017, Intel Corporation. All rights reserved.
+; This program and the accompanying materials +; are licensed and made available under the terms and conditions of the BSD License +; which accompanies this distribution. The full text of the license may be found at +; http://opensource.org/licenses/bsd-license.php. +; +; THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +; WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +; +; Module Name: +; +; ExceptionTssEntryAsm.Asm +; +; Abstract: +; +; IA32 CPU Exception Handler with Separate Stack +; +; Notes: +; +;------------------------------------------------------------------------------ + +; +; IA32 TSS Memory Layout Description +; +struc IA32_TSS + resw 1 + resw 1 + .ESP0: resd 1 + .SS0: resw 1 + resw 1 + .ESP1: resd 1 + .SS1: resw 1 + resw 1 + .ESP2: resd 1 + .SS2: resw 1 + resw 1 + ._CR3: resd 1 + .EIP: resd 1 + .EFLAGS: resd 1 + ._EAX: resd 1 + ._ECX: resd 1 + ._EDX: resd 1 + ._EBX: resd 1 + ._ESP: resd 1 + ._EBP: resd 1 + ._ESI: resd 1 + ._EDI: resd 1 + ._ES: resw 1 + resw 1 + ._CS: resw 1 + resw 1 + ._SS: resw 1 + resw 1 + ._DS: resw 1 + resw 1 + ._FS: resw 1 + resw 1 + ._GS: resw 1 + resw 1 + .LDT: resw 1 + resw 1 + resw 1 + resw 1 +endstruc + +; +; CommonExceptionHandler() +; +extern ASM_PFX(CommonExceptionHandler) + +SECTION .data + +SECTION .text + +ALIGN 8 + +; +; Exception handler stub table +; +AsmExceptionEntryBegin: +%assign Vector 0 +%rep 32 + +DoIret%[Vector]: + iretd +ASM_PFX(ExceptionTaskSwtichEntry%[Vector]): + db 0x6a ; push #VectorNum + db %[Vector] + mov eax, ASM_PFX(CommonTaskSwtichEntryPoint) + call eax + mov esp, eax ; Restore stack top + jmp DoIret%[Vector] + +%assign Vector Vector+1 +%endrep +AsmExceptionEntryEnd: + +; +; Common part of exception handler +; +global ASM_PFX(CommonTaskSwtichEntryPoint) +ASM_PFX(CommonTaskSwtichEntryPoint): + ; + ; Stack: + ; +---------------------+ <-- EBP - 8 + ; + TSS Base + + ; +---------------------+ <-- EBP - 4 + ; + CPUID.EDX + + ; +---------------------+ <-- EBP + ; + EIP + + ; +---------------------+ <-- EBP + 4 + ; + Vector Number + + ; +---------------------+ <-- EBP + 8 + ; + Error Code + + ; +---------------------+ + ; + + mov ebp, esp ; Stack frame + +; Use CPUID to determine if FXSAVE/FXRESTOR and DE are supported + mov eax, 1 + cpuid + push edx + +; Get TSS base of interrupted task through PreviousTaskLink field in +; current TSS base + sub esp, 8 + sgdt [esp + 2] + mov eax, [esp + 4] ; GDT base + add esp, 8 + + xor ebx, ebx + str bx ; Current TR + + mov ecx, [eax + ebx + 2] + shl ecx, 8 + mov cl, [eax + ebx + 7] + ror ecx, 8 ; ecx = Current TSS base + push ecx ; keep it in stack for later use + + movzx ebx, word [ecx] ; Previous Task Link + mov ecx, [eax + ebx + 2] + shl ecx, 8 + mov cl, [eax + ebx + 7] + ror ecx, 8 ; ecx = Previous TSS base + +; +; Align stack to make sure that EFI_FX_SAVE_STATE_IA32 of EFI_SYSTEM_CONTEXT_IA32 +; is 16-byte aligned +; + and esp, 0xfffffff0 + sub esp, 12 + +;; UINT32 Edi, Esi, Ebp, Esp, Ebx, Edx, Ecx, Eax; + push dword [ecx + IA32_TSS._EAX] + push dword [ecx + IA32_TSS._ECX] + push dword [ecx + IA32_TSS._EDX] + push dword [ecx + IA32_TSS._EBX] + push dword [ecx + IA32_TSS._ESP] + push dword [ecx + IA32_TSS._EBP] + push dword [ecx + IA32_TSS._ESI] + push dword [ecx + IA32_TSS._EDI] + +;; UINT32 Gs, Fs, Es, Ds, Cs, Ss; + movzx eax, word [ecx + IA32_TSS._SS] + push eax + movzx eax, word [ecx + IA32_TSS._CS] + push eax + movzx eax, word [ecx + IA32_TSS._DS] + push eax + movzx eax, word [ecx + IA32_TSS._ES] + push eax + movzx eax, word [ecx + IA32_TSS._FS] + push eax + movzx eax, word [ecx + IA32_TSS._GS] + push eax + +;; UINT32 Eip; + push dword [ecx + IA32_TSS.EIP] + +;; UINT32 Gdtr[2], Idtr[2]; + sub esp, 8 + sidt [esp] + mov eax, [esp + 2] + xchg eax, [esp] + and eax, 0xFFFF + mov [esp+4], eax + + sub esp, 8 + sgdt [esp] + mov eax, [esp + 2] + xchg eax, [esp] + and eax, 0xFFFF + mov [esp+4], eax + +;; UINT32 Ldtr, Tr; + mov eax, ebx ; ebx still keeps selector of interrupted task + push eax + movzx eax, word [ecx + IA32_TSS.LDT] + push eax + +;; UINT32 EFlags; + push dword [ecx + IA32_TSS.EFLAGS] + +;; UINT32 Cr0, Cr1, Cr2, Cr3, Cr4; + mov eax, cr4 + push eax ; push cr4 firstly + + mov edx, [ebp - 4] ; cpuid.edx + test edx, BIT24 ; Test for FXSAVE/FXRESTOR support + jz .1 + or eax, BIT9 ; Set CR4.OSFXSR +.1: + test edx, BIT2 ; Test for Debugging Extensions support + jz .2 + or eax, BIT3 ; Set CR4.DE +.2: + mov cr4, eax + + mov eax, cr3 + push eax + mov eax, cr2 + push eax + xor eax, eax + push eax + mov eax, cr0 + push eax + +;; UINT32 Dr0, Dr1, Dr2, Dr3, Dr6, Dr7; + mov eax, dr7 + push eax + mov eax, dr6 + push eax + mov eax, dr3 + push eax + mov eax, dr2 + push eax + mov eax, dr1 + push eax + mov eax, dr0 + push eax + +;; FX_SAVE_STATE_IA32 FxSaveState; +;; Clear TS bit in CR0 to avoid Device Not Available Exception (#NM) +;; when executing fxsave/fxrstor instruction + test edx, BIT24 ; Test for FXSAVE/FXRESTOR support. + ; edx still contains result from CPUID above + jz .3 + clts + sub esp, 512 + mov edi, esp + db 0xf, 0xae, 0x7 ;fxsave [edi] +.3: + +;; UINT32 ExceptionData; + push dword [ebp + 8] + +;; UEFI calling convention for IA32 requires that Direction flag in EFLAGs is clear + cld + +;; call into exception handler + mov esi, ecx ; Keep TSS base to avoid overwrite + mov eax, ASM_PFX(CommonExceptionHandler) + +;; Prepare parameter and call + mov edx, esp + push edx ; EFI_SYSTEM_CONTEXT + push dword [ebp + 4] ; EFI_EXCEPTION_TYPE (vector number) + + ; + ; Call External Exception Handler + ; + call eax + add esp, 8 ; Restore stack before calling + mov ecx, esi ; Restore TSS base + +;; UINT32 ExceptionData; + add esp, 4 + +;; FX_SAVE_STATE_IA32 FxSaveState; + mov edx, [ebp - 4] ; cpuid.edx + test edx, BIT24 ; Test for FXSAVE/FXRESTOR support + jz .4 + mov esi, esp + db 0xf, 0xae, 0xe ; fxrstor [esi] +.4: + add esp, 512 + +;; UINT32 Dr0, Dr1, Dr2, Dr3, Dr6, Dr7; +;; Skip restoration of DRx registers to support debuggers +;; that set breakpoints in interrupt/exception context + add esp, 4 * 6 + +;; UINT32 Cr0, Cr1, Cr2, Cr3, Cr4; + pop eax + mov cr0, eax + add esp, 4 ; not for Cr1 + pop eax + mov cr2, eax + pop eax + mov dword [ecx + IA32_TSS._CR3], eax + pop eax + mov cr4, eax + +;; UINT32 EFlags; + pop dword [ecx + IA32_TSS.EFLAGS] + mov ebx, dword [ecx + IA32_TSS.EFLAGS] + btr ebx, 9 ; Do 'cli' + mov dword [ecx + IA32_TSS.EFLAGS], ebx + +;; UINT32 Ldtr, Tr; +;; UINT32 Gdtr[2], Idtr[2]; +;; Best not let anyone mess with these particular registers... + add esp, 24 + +;; UINT32 Eip; + pop dword [ecx + IA32_TSS.EIP] + +;; UINT32 Gs, Fs, Es, Ds, Cs, Ss; +;; NOTE - modified segment registers could hang the debugger... We +;; could attempt to insulate ourselves against this possibility, +;; but that poses risks as well. +;; + pop eax +o16 mov [ecx + IA32_TSS._GS], ax + pop eax +o16 mov [ecx + IA32_TSS._FS], ax + pop eax +o16 mov [ecx + IA32_TSS._ES], ax + pop eax +o16 mov [ecx + IA32_TSS._DS], ax + pop eax +o16 mov [ecx + IA32_TSS._CS], ax + pop eax +o16 mov [ecx + IA32_TSS._SS], ax + +;; UINT32 Edi, Esi, Ebp, Esp, Ebx, Edx, Ecx, Eax; + pop dword [ecx + IA32_TSS._EDI] + pop dword [ecx + IA32_TSS._ESI] + add esp, 4 ; not for ebp + add esp, 4 ; not for esp + pop dword [ecx + IA32_TSS._EBX] + pop dword [ecx + IA32_TSS._EDX] + pop dword [ecx + IA32_TSS._ECX] + pop dword [ecx + IA32_TSS._EAX] + +; Set single step DB# to allow debugger to able to go back to the EIP +; where the exception is triggered. + +;; Create return context for iretd in stub function + mov eax, dword [ecx + IA32_TSS._ESP] ; Get old stack pointer + mov ebx, dword [ecx + IA32_TSS.EIP] + mov [eax - 0xc], ebx ; create EIP in old stack + movzx ebx, word [ecx + IA32_TSS._CS] + mov [eax - 0x8], ebx ; create CS in old stack + mov ebx, dword [ecx + IA32_TSS.EFLAGS] + bts ebx, 8 + mov [eax - 0x4], ebx ; create eflags in old stack + mov dword [ecx + IA32_TSS.EFLAGS], ebx ; update eflags in old TSS + mov eax, dword [ecx + IA32_TSS._ESP] ; Get old stack pointer + sub eax, 0xc ; minus 12 byte + mov dword [ecx + IA32_TSS._ESP], eax ; Set new stack pointer + +;; Replace the EIP of interrupted task with stub function + mov eax, ASM_PFX(SingleStepStubFunction) + mov dword [ecx + IA32_TSS.EIP], eax + + mov ecx, [ebp - 8] ; Get current TSS base + mov eax, dword [ecx + IA32_TSS._ESP] ; Return current stack top + mov esp, ebp + + ret + +global ASM_PFX(SingleStepStubFunction) +ASM_PFX(SingleStepStubFunction): +; +; we need clean TS bit in CR0 to execute +; x87 FPU/MMX/SSE/SSE2/SSE3/SSSE3/SSE4 instructions. +; + clts + iretd + +global ASM_PFX(AsmGetTssTemplateMap) +ASM_PFX(AsmGetTssTemplateMap): + push ebp ; C prolog + mov ebp, esp + pushad + + mov ebx, dword [ebp + 0x8] + mov dword [ebx], ASM_PFX(ExceptionTaskSwtichEntry0) + mov dword [ebx + 0x4], (AsmExceptionEntryEnd - AsmExceptionEntryBegin) / 32 + mov dword [ebx + 0x8], 0 + + popad + pop ebp + ret + diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/PeiCpuException.c b/UefiCpuPkg/Library/CpuExceptionHandlerLib/PeiCpuException.c index 8d8d16ecbd..6f271983f2 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/PeiCpuException.c +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/PeiCpuException.c @@ -177,4 +177,36 @@ RegisterCpuInterruptHandler ( ) { return EFI_UNSUPPORTED; -} \ No newline at end of file +} + +/** + Initializes all CPU exceptions entries with optional extra initializations. + + By default, this method should include all functionalities implemented by + InitializeCpuExceptionHandlers(), plus extra initialization works, if any. + This could be done by calling InitializeCpuExceptionHandlers() directly + in this method besides the extra works. + + InitData is optional and its use and content are processor arch dependent. + The typical usage of it is to convey resources which have to be reserved + elsewhere and are necessary for the extra initializations of exception. + + @param[in] VectorInfo Pointer to reserved vector list. + @param[in] InitData Pointer to data optional for extra initializations + of exception. + + @retval EFI_SUCCESS The exceptions have been successfully + initialized. + @retval EFI_INVALID_PARAMETER VectorInfo or InitData contains invalid + content. + +**/ +EFI_STATUS +EFIAPI +InitializeCpuExceptionHandlersEx ( + IN EFI_VECTOR_HANDOFF_INFO *VectorInfo OPTIONAL, + IN CPU_EXCEPTION_INIT_DATA *InitData OPTIONAL + ) +{ + return InitializeCpuExceptionHandlers (VectorInfo); +} diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/PeiCpuExceptionHandlerLib.inf b/UefiCpuPkg/Library/CpuExceptionHandlerLib/PeiCpuExceptionHandlerLib.inf index 75443288a9..4c0d435136 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/PeiCpuExceptionHandlerLib.inf +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/PeiCpuExceptionHandlerLib.inf @@ -30,6 +30,7 @@ [Sources.Ia32] Ia32/ExceptionHandlerAsm.asm Ia32/ExceptionHandlerAsm.nasm + Ia32/ExceptionTssEntryAsm.nasm Ia32/ExceptionHandlerAsm.S Ia32/ArchExceptionHandler.c Ia32/ArchInterruptDefs.h diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/SecPeiCpuException.c b/UefiCpuPkg/Library/CpuExceptionHandlerLib/SecPeiCpuException.c index af608bffb6..13bf20d143 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/SecPeiCpuException.c +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/SecPeiCpuException.c @@ -176,4 +176,36 @@ RegisterCpuInterruptHandler ( ) { return EFI_UNSUPPORTED; -} \ No newline at end of file +} + +/** + Initializes all CPU exceptions entries with optional extra initializations. + + By default, this method should include all functionalities implemented by + InitializeCpuExceptionHandlers(), plus extra initialization works, if any. + This could be done by calling InitializeCpuExceptionHandlers() directly + in this method besides the extra works. + + InitData is optional and its use and content are processor arch dependent. + The typical usage of it is to convey resources which have to be reserved + elsewhere and are necessary for the extra initializations of exception. + + @param[in] VectorInfo Pointer to reserved vector list. + @param[in] InitData Pointer to data optional for extra initializations + of exception. + + @retval EFI_SUCCESS The exceptions have been successfully + initialized. + @retval EFI_INVALID_PARAMETER VectorInfo or InitData contains invalid + content. + +**/ +EFI_STATUS +EFIAPI +InitializeCpuExceptionHandlersEx ( + IN EFI_VECTOR_HANDOFF_INFO *VectorInfo OPTIONAL, + IN CPU_EXCEPTION_INIT_DATA *InitData OPTIONAL + ) +{ + return InitializeCpuExceptionHandlers (VectorInfo); +} diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/SecPeiCpuExceptionHandlerLib.inf b/UefiCpuPkg/Library/CpuExceptionHandlerLib/SecPeiCpuExceptionHandlerLib.inf index d70a99c100..e5c03c16c9 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/SecPeiCpuExceptionHandlerLib.inf +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/SecPeiCpuExceptionHandlerLib.inf @@ -30,6 +30,7 @@ [Sources.Ia32] Ia32/ExceptionHandlerAsm.asm Ia32/ExceptionHandlerAsm.nasm + Ia32/ExceptionTssEntryAsm.nasm Ia32/ExceptionHandlerAsm.S Ia32/ArchExceptionHandler.c Ia32/ArchInterruptDefs.h diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/SmmCpuExceptionHandlerLib.inf b/UefiCpuPkg/Library/CpuExceptionHandlerLib/SmmCpuExceptionHandlerLib.inf index 634ffcb21d..56b875b7c8 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/SmmCpuExceptionHandlerLib.inf +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/SmmCpuExceptionHandlerLib.inf @@ -30,6 +30,7 @@ [Sources.Ia32] Ia32/ExceptionHandlerAsm.asm Ia32/ExceptionHandlerAsm.nasm + Ia32/ExceptionTssEntryAsm.nasm Ia32/ExceptionHandlerAsm.S Ia32/ArchExceptionHandler.c Ia32/ArchInterruptDefs.h diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/SmmException.c b/UefiCpuPkg/Library/CpuExceptionHandlerLib/SmmException.c index 7414d3f773..95f4ce51ea 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/SmmException.c +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/SmmException.c @@ -125,4 +125,36 @@ RegisterCpuInterruptHandler ( ) { return RegisterCpuInterruptHandlerWorker (InterruptType, InterruptHandler, &mExceptionHandlerData); -} \ No newline at end of file +} + +/** + Initializes all CPU exceptions entries with optional extra initializations. + + By default, this method should include all functionalities implemented by + InitializeCpuExceptionHandlers(), plus extra initialization works, if any. + This could be done by calling InitializeCpuExceptionHandlers() directly + in this method besides the extra works. + + InitData is optional and its use and content are processor arch dependent. + The typical usage of it is to convey resources which have to be reserved + elsewhere and are necessary for the extra initializations of exception. + + @param[in] VectorInfo Pointer to reserved vector list. + @param[in] InitData Pointer to data optional for extra initializations + of exception. + + @retval EFI_SUCCESS The exceptions have been successfully + initialized. + @retval EFI_INVALID_PARAMETER VectorInfo or InitData contains invalid + content. + +**/ +EFI_STATUS +EFIAPI +InitializeCpuExceptionHandlersEx ( + IN EFI_VECTOR_HANDOFF_INFO *VectorInfo OPTIONAL, + IN CPU_EXCEPTION_INIT_DATA *InitData OPTIONAL + ) +{ + return InitializeCpuExceptionHandlers (VectorInfo); +} diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/X64/ArchExceptionHandler.c b/UefiCpuPkg/Library/CpuExceptionHandlerLib/X64/ArchExceptionHandler.c index 65f0cff680..1dcf4277de 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/X64/ArchExceptionHandler.c +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/X64/ArchExceptionHandler.c @@ -112,6 +112,162 @@ ArchRestoreExceptionContext ( SystemContext.SystemContextX64->ExceptionData = ReservedVectors[ExceptionType].ExceptionData; } +/** + Setup separate stack for given exceptions. + + @param[in] StackSwitchData Pointer to data required for setuping up + stack switch. + + @retval EFI_SUCCESS The exceptions have been successfully + initialized with new stack. + @retval EFI_INVALID_PARAMETER StackSwitchData contains invalid content. + +**/ +EFI_STATUS +ArchSetupExcpetionStack ( + IN CPU_EXCEPTION_INIT_DATA *StackSwitchData + ) +{ + IA32_DESCRIPTOR Gdtr; + IA32_DESCRIPTOR Idtr; + IA32_IDT_GATE_DESCRIPTOR *IdtTable; + IA32_TSS_DESCRIPTOR *TssDesc; + IA32_TASK_STATE_SEGMENT *Tss; + UINTN StackTop; + UINTN Index; + UINTN Vector; + UINTN TssBase; + UINTN GdtSize; + + if (StackSwitchData == NULL || + StackSwitchData->Ia32.Revision != CPU_EXCEPTION_INIT_DATA_REV || + StackSwitchData->X64.KnownGoodStackTop == 0 || + StackSwitchData->X64.KnownGoodStackSize == 0 || + StackSwitchData->X64.StackSwitchExceptions == NULL || + StackSwitchData->X64.StackSwitchExceptionNumber == 0 || + StackSwitchData->X64.StackSwitchExceptionNumber > CPU_EXCEPTION_NUM || + StackSwitchData->X64.GdtTable == NULL || + StackSwitchData->X64.IdtTable == NULL || + StackSwitchData->X64.ExceptionTssDesc == NULL || + StackSwitchData->X64.ExceptionTss == NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // The caller is responsible for that the GDT table, no matter the existing + // one or newly allocated, has enough space to hold descriptors for exception + // task-state segments. + // + if (((UINTN)StackSwitchData->X64.GdtTable & (IA32_GDT_ALIGNMENT - 1)) != 0) { + return EFI_INVALID_PARAMETER; + } + + if ((UINTN)StackSwitchData->X64.ExceptionTssDesc < (UINTN)(StackSwitchData->X64.GdtTable)) { + return EFI_INVALID_PARAMETER; + } + + if (((UINTN)StackSwitchData->X64.ExceptionTssDesc + StackSwitchData->X64.ExceptionTssDescSize) > + ((UINTN)(StackSwitchData->X64.GdtTable) + StackSwitchData->X64.GdtTableSize)) { + return EFI_INVALID_PARAMETER; + } + + // + // One task gate descriptor and one task-state segment are needed. + // + if (StackSwitchData->X64.ExceptionTssDescSize < sizeof (IA32_TSS_DESCRIPTOR)) { + return EFI_INVALID_PARAMETER; + } + if (StackSwitchData->X64.ExceptionTssSize < sizeof (IA32_TASK_STATE_SEGMENT)) { + return EFI_INVALID_PARAMETER; + } + + // + // Interrupt stack table supports only 7 vectors. + // + TssDesc = StackSwitchData->X64.ExceptionTssDesc; + Tss = StackSwitchData->X64.ExceptionTss; + if (StackSwitchData->X64.StackSwitchExceptionNumber > ARRAY_SIZE (Tss->IST)) { + return EFI_INVALID_PARAMETER; + } + + // + // Initialize new GDT table and/or IDT table, if any + // + AsmReadIdtr (&Idtr); + AsmReadGdtr (&Gdtr); + + GdtSize = (UINTN)TssDesc + sizeof (IA32_TSS_DESCRIPTOR) - + (UINTN)(StackSwitchData->X64.GdtTable); + if ((UINTN)StackSwitchData->X64.GdtTable != Gdtr.Base) { + CopyMem (StackSwitchData->X64.GdtTable, (VOID *)Gdtr.Base, Gdtr.Limit + 1); + Gdtr.Base = (UINTN)StackSwitchData->X64.GdtTable; + Gdtr.Limit = (UINT16)GdtSize - 1; + } + + if ((UINTN)StackSwitchData->X64.IdtTable != Idtr.Base) { + Idtr.Base = (UINTN)StackSwitchData->X64.IdtTable; + } + if (StackSwitchData->X64.IdtTableSize > 0) { + Idtr.Limit = (UINT16)(StackSwitchData->X64.IdtTableSize - 1); + } + + // + // Fixup current task descriptor. Task-state segment for current task will + // be filled by processor during task switching. + // + TssBase = (UINTN)Tss; + + TssDesc->Bits.LimitLow = sizeof(IA32_TASK_STATE_SEGMENT) - 1; + TssDesc->Bits.BaseLow = (UINT16)TssBase; + TssDesc->Bits.BaseMidl = (UINT8)(TssBase >> 16); + TssDesc->Bits.Type = IA32_GDT_TYPE_TSS; + TssDesc->Bits.P = 1; + TssDesc->Bits.LimitHigh = 0; + TssDesc->Bits.BaseMidh = (UINT8)(TssBase >> 24); + TssDesc->Bits.BaseHigh = (UINT32)(TssBase >> 32); + + // + // Fixup exception task descriptor and task-state segment + // + StackTop = StackSwitchData->X64.KnownGoodStackTop - CPU_STACK_ALIGNMENT; + StackTop = (UINTN)ALIGN_POINTER (StackTop, CPU_STACK_ALIGNMENT); + IdtTable = StackSwitchData->X64.IdtTable; + for (Index = 0; Index < StackSwitchData->X64.StackSwitchExceptionNumber; ++Index) { + // + // Fixup IST + // + Tss->IST[Index] = StackTop; + StackTop -= StackSwitchData->X64.KnownGoodStackSize; + + // + // Set the IST field to enable corresponding IST + // + Vector = StackSwitchData->X64.StackSwitchExceptions[Index]; + if (Vector >= CPU_EXCEPTION_NUM || + Vector >= (Idtr.Limit + 1) / sizeof (IA32_IDT_GATE_DESCRIPTOR)) { + continue; + } + IdtTable[Vector].Bits.Reserved_0 = (UINT8)(Index + 1); + } + + // + // Publish GDT + // + AsmWriteGdtr (&Gdtr); + + // + // Load current task + // + AsmWriteTr ((UINT16)((UINTN)StackSwitchData->X64.ExceptionTssDesc - Gdtr.Base)); + + // + // Publish IDT + // + AsmWriteIdtr (&Idtr); + + return EFI_SUCCESS; +} + /** Display CPU information. diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/X64/ArchInterruptDefs.h b/UefiCpuPkg/Library/CpuExceptionHandlerLib/X64/ArchInterruptDefs.h index 906480134a..c88be46286 100644 --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/X64/ArchInterruptDefs.h +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/X64/ArchInterruptDefs.h @@ -43,4 +43,7 @@ typedef struct { UINT8 HookAfterStubHeaderCode[HOOKAFTER_STUB_SIZE]; } RESERVED_VECTORS_DATA; +#define CPU_TSS_DESC_SIZE sizeof (IA32_TSS_DESCRIPTOR) +#define CPU_TSS_SIZE sizeof (IA32_TASK_STATE_SEGMENT) + #endif