public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "duntan" <dun.tan@intel.com>
To: devel@edk2.groups.io
Cc: Eric Dong <eric.dong@intel.com>, Ray Ni <ray.ni@intel.com>,
	Rahul Kumar <rahul1.kumar@intel.com>
Subject: [Patch V3 1/4] UefiCpuPkg: Add Unit tests for DxeCpuExceptionHandlerLib
Date: Fri, 14 Oct 2022 17:19:28 +0800	[thread overview]
Message-ID: <20221014091931.847-2-dun.tan@intel.com> (raw)
In-Reply-To: <20221014091931.847-1-dun.tan@intel.com>

Add target based unit tests for the DxeCpuExceptionHandlerLib.
A DXE driver is created to test DxeCpuExceptionHandlerLib.

Four test cases are created in this Unit Test module:
a.Test if exception handler can be registered/unregistered
for no error code exception.In the test case, only no error
code exception is triggered and tested by INTn instruction.

b.Test if exception handler can be registered/unregistered
for GP and PF. In the test case, GP exception is triggered
and tested by setting CR4_RESERVED_BIT to 1. PF exception
is triggered by writting to not-present or RO address.

c.Test if CpuContext is consistent before and after exception.
In this test case:
1.Set Cpu register to mExpectedContextInHandler before
exception. 2.Trigger exception specified by ExceptionType.
3.Store SystemContext in mActualContextInHandler and set
SystemContext to mExpectedContextAfterException in handler.
4.After return from exception, store Cpu registers in
mActualContextAfterException.
The expectation is:
1.Register values in mActualContextInHandler are the same
with register values in mExpectedContextInHandler.
2.Register values in mActualContextAfterException are the
same with register values mActualContextAfterException.

d.Test if stack overflow can be captured by CpuStackGuard
in both Bsp and AP. In this test case, stack overflow is
triggered by a funtion which calls itself continuously.
This test case triggers stack overflow in both BSP and AP.
All AP use same Idt with Bsp. The expectation is:
1. PF exception is triggered (leading to a DF if sepereated
stack is not prepared for PF) when Rsp<=StackBase+SIZE_4KB
since [StackBase, StackBase + SIZE_4KB] is marked as not
present in page table when PcdCpuStackGuard is TRUE.
2. Stack for PF/DF exception handler in both Bsp and AP is
succussfully switched by InitializeSeparateExceptionStacks.

Signed-off-by: Dun Tan <dun.tan@intel.com>
Cc: Eric Dong <eric.dong@intel.com>
Cc: Ray Ni <ray.ni@intel.com>
Cc: Rahul Kumar <rahul1.kumar@intel.com>
---
 UefiCpuPkg/CpuExceptionHandlerUnitTest/CpuExceptionHandlerTest.h             | 336 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 UefiCpuPkg/CpuExceptionHandlerUnitTest/CpuExceptionHandlerTestCommon.c       | 852 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 UefiCpuPkg/CpuExceptionHandlerUnitTest/DxeCpuExceptionHandlerLibUnitTest.inf |  58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 UefiCpuPkg/CpuExceptionHandlerUnitTest/DxeCpuExceptionHandlerUnitTest.c      | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 UefiCpuPkg/CpuExceptionHandlerUnitTest/X64/ArchExceptionHandlerTest.c        | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 UefiCpuPkg/CpuExceptionHandlerUnitTest/X64/ArchExceptionHandlerTestAsm.nasm  | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1864 insertions(+)

diff --git a/UefiCpuPkg/CpuExceptionHandlerUnitTest/CpuExceptionHandlerTest.h b/UefiCpuPkg/CpuExceptionHandlerUnitTest/CpuExceptionHandlerTest.h
new file mode 100644
index 0000000000..936098fde8
--- /dev/null
+++ b/UefiCpuPkg/CpuExceptionHandlerUnitTest/CpuExceptionHandlerTest.h
@@ -0,0 +1,336 @@
+/** @file
+
+  Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+
+  Four test cases are created in this Unit Test module.
+  a.Test if exception handler can be registered/unregistered for no error code exception
+    In this test case, only no error code exception is triggered and tested by INTn instruction.
+    The special hanlder for these exception will modify a global variable for check.
+
+  b.Test if exception handler can be registered/unregistered for GP and PF.
+    In this test case, GP exception is triggered and tested by setting CR4_RESERVED_BIT to 1.
+    PF exception is triggered and tested by writting to not-present or RO addres.
+    The special hanlder for these exceptions will set a global vartiable for check and adjust Rip to return from fault exception.
+
+  c.Test if Cpu Context is consistent before and after exception.
+    In this test case:
+      1. Set Cpu register to mExpectedContextInHandler before exception.
+      2. Trigger exception specified by ExceptionType.
+      3. Store SystemContext in mActualContextInHandler and set SystemContext to mExpectedContextAfterException in handler.
+      4. After return from exception, store Cpu registers in mActualContextAfterException.
+    The expectation is:
+      1. Register values in mActualContextInHandler are the same with register values in mExpectedContextInHandler.
+      2. Register values in mActualContextAfterException are the same with register values mActualContextAfterException.
+
+  d.Test if stack overflow can be captured by CpuStackGuard in both Bsp and AP.
+    In this test case, stack overflow is triggered by a funtion which calls itself continuously. This test case triggers stack
+    overflow in both BSP and AP. All AP use same Idt with Bsp. The expectation is:
+      1. PF exception is triggered (leading to a DF if sepereated stack is not prepared for PF) when Rsp <= StackBase + SIZE_4KB
+         since [StackBase, StackBase + SIZE_4KB] is marked as not present in page table when PcdCpuStackGuard is TRUE.
+      2. Stack for PF/DF exception handler in both Bsp and AP is succussfully switched by InitializeSeparateExceptionStacks.
+
+**/
+
+#ifndef CPU_EXCEPTION_HANDLER_TEST_H_
+#define CPU_EXCEPTION_HANDLER_TEST_H_
+
+#include <Uefi.h>
+#include <Library/BaseLib.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/UnitTestLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/UnitTestHostBaseLib.h>
+#include <Library/CpuExceptionHandlerLib.h>
+#include <Library/UefiLib.h>
+#include <Library/SerialPortLib.h>
+#include <Library/HobLib.h>
+#include <Library/CpuPageTableLib.h>
+#include <Guid/MemoryAllocationHob.h>
+#include <Protocol/MpService.h>
+#include <PiPei.h>
+#include <Ppi/MpServices2.h>
+
+#define UNIT_TEST_APP_NAME     "Cpu Exception Handler Lib Unit Tests"
+#define UNIT_TEST_APP_VERSION  "1.0"
+
+#define  CPU_INTERRUPT_NUM       256
+#define  SPEC_MAX_EXCEPTION_NUM  22
+#define  CR4_RESERVED_BIT        BIT15
+
+typedef struct {
+  IA32_DESCRIPTOR    OriginalGdtr;
+  IA32_DESCRIPTOR    OriginalIdtr;
+  UINT16             Tr;
+} CPU_REGISTER_BUFFER;
+
+typedef union {
+  EDKII_PEI_MP_SERVICES2_PPI    *Ppi;
+  EFI_MP_SERVICES_PROTOCOL      *Protocol;
+} MP_SERVICES;
+
+typedef struct {
+  VOID          *Buffer;
+  UINTN         BufferSize;
+  EFI_STATUS    Status;
+} EXCEPTION_STACK_SWITCH_CONTEXT;
+
+typedef struct {
+  UINT64    Rdi;
+  UINT64    Rsi;
+  UINT64    Rbx;
+  UINT64    Rdx;
+  UINT64    Rcx;
+  UINT64    Rax;
+  UINT64    R8;
+  UINT64    R9;
+  UINT64    R10;
+  UINT64    R11;
+  UINT64    R12;
+  UINT64    R13;
+  UINT64    R14;
+  UINT64    R15;
+} GENERAL_REGISTER;
+
+extern UINTN               mFaultInstructionLength;
+extern EFI_EXCEPTION_TYPE  mExceptionType;
+extern UINTN               mRspAddress[];
+
+/**
+  Initialize Bsp Idt with a new Idt table and return the IA32_DESCRIPTOR buffer.
+  In PEIM, store original PeiServicePointer before new Idt table.
+
+  @return Pointer to the allocated IA32_DESCRIPTOR buffer.
+**/
+VOID *
+InitializeBspIdt (
+  VOID
+  );
+
+/**
+  Trigger no error code exception by INT n instruction.
+
+  @param[in]  ExceptionType  No error code exception type.
+**/
+VOID
+EFIAPI
+TriggerINTnException (
+  IN  EFI_EXCEPTION_TYPE  ExceptionType
+  );
+
+/**
+  Trigger GP exception by setting CR4_RESERVED_BIT to 1.
+
+  @param[in]  Cr4ReservedBit  Cr4 reserved bit.
+**/
+VOID
+EFIAPI
+TriggerGPException (
+  UINTN  Cr4ReservedBit
+  );
+
+/**
+  Trigger PF exception by write to not present or ReadOnly address.
+
+  @param[in]  PFAddress  Not present or ReadOnly address in page table.
+**/
+VOID
+EFIAPI
+TriggerPFException (
+  UINTN  PFAddress
+  );
+
+/**
+  Special handler for fault exception.
+  This handler sets Rip/Eip in SystemContext to the instruction address after the exception instruction.
+
+  @param ExceptionType  Exception type.
+  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.
+**/
+VOID
+EFIAPI
+AdjustRipForFaultHandler (
+  IN EFI_EXCEPTION_TYPE  ExceptionType,
+  IN EFI_SYSTEM_CONTEXT  SystemContext
+  );
+
+/**
+  Test consistency of Cpu context. Four steps:
+  1. Set Cpu register to mExpectedContextInHandler before exception.
+  2. Trigger exception specified by ExceptionType.
+  3. Store SystemContext in mActualContextInHandler and set SystemContext to mExpectedContextAfterException in handler.
+  4. After return from exception, store Cpu registers in mActualContextAfterException.
+
+  Rcx/Ecx in mExpectedContextInHandler is decided by different exception type runtime since Rcx/Ecx is needed in assembly code.
+  For GP and PF, Rcx/Ecx is set to FaultParameter. For other exception triggered by INTn, Rcx/Ecx is set to ExceptionType.
+
+  @param[in] ExceptionType   Exception type.
+  @param[in] FaultParameter  Parameter for GP and PF. OPTIONAL
+**/
+VOID
+EFIAPI
+AsmTestConsistencyOfCpuContext (
+  IN  EFI_EXCEPTION_TYPE  ExceptionType,
+  IN  UINTN               FaultParameter   OPTIONAL
+  );
+
+/**
+  Special handler for ConsistencyOfCpuContext test case. General register in SystemContext
+  is modified to mExpectedContextInHandler in this handler.
+
+  @param ExceptionType  Exception type.
+  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.
+**/
+VOID
+EFIAPI
+AdjustCpuContextHandler (
+  IN EFI_EXCEPTION_TYPE  ExceptionType,
+  IN EFI_SYSTEM_CONTEXT  SystemContext
+  );
+
+/**
+  Compare cpu context in ConsistencyOfCpuContext test case.
+  1.Compare mActualContextInHandler with mExpectedContextInHandler.
+  2.Compare mActualContextAfterException with mActualContextAfterException.
+
+  @retval  UNIT_TEST_PASSED             The Unit test has completed and it was successful.
+  @retval  UNIT_TEST_ERROR_TEST_FAILED  A test case assertion has failed.
+**/
+UNIT_TEST_STATUS
+CompareCpuContext (
+  VOID
+  );
+
+/**
+  Get EFI_MP_SERVICES_PROTOCOL/EDKII_PEI_MP_SERVICES2_PPI pointer.
+
+  @param[out] MpServices    Pointer to the MP_SERVICES buffer
+
+  @retval EFI_SUCCESS       EFI_MP_SERVICES_PROTOCOL/PPI interface is returned
+  @retval EFI_NOT_FOUND     EFI_MP_SERVICES_PROTOCOL/PPI interface is not found
+**/
+EFI_STATUS
+GetMpServices (
+  OUT MP_SERVICES  *MpServices
+  );
+
+/**
+  Create CpuExceptionLibUnitTestSuite and add test case.
+
+  @param[in]  FrameworkHandle    Unit test framework.
+
+  @return  EFI_SUCCESS           The unit test suite was created.
+  @retval  EFI_OUT_OF_RESOURCES  There are not enough resources available to
+                                 initialize the unit test suite.
+**/
+EFI_STATUS
+AddCommonTestCase (
+  IN  UNIT_TEST_FRAMEWORK_HANDLE  Framework
+  );
+
+/**
+  Execute a caller provided function on all enabled APs.
+
+  @param[in]  MpServices    MP_SERVICES structure.
+  @param[in]  Procedure     Pointer to the function to be run on enabled APs of the system.
+  @param[in]  SingleThread  If TRUE, then all the enabled APs execute the function specified by Procedure
+                            one by one, in ascending order of processor handle number.
+                            If FALSE, then all the enabled APs execute the function specified by Procedure
+                            simultaneously.
+  @param[in]  TimeoutInMicroseconds Indicates the time limit in microseconds for APs to return from Procedure,
+                                    for blocking mode only. Zero means infinity.
+  @param[in]  ProcedureArgument     The parameter passed into Procedure for all APs.
+
+  @retval EFI_SUCCESS       Execute a caller provided function on all enabled APs successfully
+  @retval Others            Execute a caller provided function on all enabled APs unsuccessfully
+**/
+EFI_STATUS
+MpServicesUnitTestStartupAllAPs (
+  IN MP_SERVICES       MpServices,
+  IN EFI_AP_PROCEDURE  Procedure,
+  IN BOOLEAN           SingleThread,
+  IN UINTN             TimeoutInMicroSeconds,
+  IN VOID              *ProcedureArgument
+  );
+
+/**
+  Caller gets one enabled AP to execute a caller-provided function.
+
+  @param[in]  MpServices    MP_SERVICES structure.
+  @param[in]  Procedure     Pointer to the function to be run on enabled APs of the system.
+  @param[in]  ProcessorNumber       The handle number of the AP.
+  @param[in]  TimeoutInMicroseconds Indicates the time limit in microseconds for APs to return from Procedure,
+                                    for blocking mode only. Zero means infinity.
+  @param[in]  ProcedureArgument     The parameter passed into Procedure for all APs.
+
+
+  @retval EFI_SUCCESS       Caller gets one enabled AP to execute a caller-provided function successfully
+  @retval Others            Caller gets one enabled AP to execute a caller-provided function unsuccessfully
+**/
+EFI_STATUS
+MpServicesUnitTestStartupThisAP (
+  IN MP_SERVICES       MpServices,
+  IN EFI_AP_PROCEDURE  Procedure,
+  IN UINTN             ProcessorNumber,
+  IN UINTN             TimeoutInMicroSeconds,
+  IN VOID              *ProcedureArgument
+  );
+
+/**
+  Get the handle number for the calling processor.
+
+  @param[in]  MpServices      MP_SERVICES structure.
+  @param[out] ProcessorNumber The handle number for the calling processor.
+
+  @retval EFI_SUCCESS       Get the handle number for the calling processor successfully.
+  @retval Others            Get the handle number for the calling processor unsuccessfully.
+**/
+EFI_STATUS
+MpServicesUnitTestWhoAmI (
+  IN MP_SERVICES  MpServices,
+  OUT UINTN       *ProcessorNumber
+  );
+
+/**
+  Retrieve the number of logical processor in the platform and the number of those logical processors that
+  are enabled on this boot.
+
+  @param[in]  MpServices          MP_SERVICES structure.
+  @param[out] NumberOfProcessors  Pointer to the total number of logical processors in the system, including
+                                  the BSP and disabled APs.
+  @param[out] NumberOfEnabledProcessors Pointer to the number of processors in the system that are enabled.
+
+  @retval EFI_SUCCESS       Retrieve the number of logical processor successfully
+  @retval Others            Retrieve the number of logical processor unsuccessfully
+**/
+EFI_STATUS
+MpServicesUnitTestGetNumberOfProcessors (
+  IN MP_SERVICES  MpServices,
+  OUT UINTN       *NumberOfProcessors,
+  OUT UINTN       *NumberOfEnabledProcessors
+  );
+
+/**
+  Trigger stack overflow by calling itself continuously.
+**/
+VOID
+EFIAPI
+TriggerStackOverflow (
+  VOID
+  );
+
+/**
+  Special handler for CpuStackGuard test case.
+
+  @param ExceptionType  Exception type.
+  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.
+**/
+VOID
+EFIAPI
+CpuStackGuardExceptionHandler (
+  IN EFI_EXCEPTION_TYPE  ExceptionType,
+  IN EFI_SYSTEM_CONTEXT  SystemContext
+  );
+
+#endif
diff --git a/UefiCpuPkg/CpuExceptionHandlerUnitTest/CpuExceptionHandlerTestCommon.c b/UefiCpuPkg/CpuExceptionHandlerUnitTest/CpuExceptionHandlerTestCommon.c
new file mode 100644
index 0000000000..17afb592d3
--- /dev/null
+++ b/UefiCpuPkg/CpuExceptionHandlerUnitTest/CpuExceptionHandlerTestCommon.c
@@ -0,0 +1,852 @@
+/** @file
+  Unit tests of the CpuExceptionHandlerLib.
+
+  Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include "CpuExceptionHandlerTest.h"
+
+//
+// Length of the assembly falut instruction.
+//
+UINTN               mFaultInstructionLength = 0;
+EFI_EXCEPTION_TYPE  mExceptionType          = 256;
+UINTN               mNumberOfProcessors     = 1;
+UINTN               mRspAddress[2]          = { 0 };
+
+//
+// Error code flag indicating whether or not an error code will be
+// pushed on the stack if an exception occurs.
+//
+// 1 means an error code will be pushed, otherwise 0
+//
+CONST UINT32  mErrorCodeExceptionFlag = 0x20227d00;
+
+/**
+  Special handler for exception triggered by INTn instruction.
+  This hanlder only modifies a global variable for check.
+
+  @param ExceptionType  Exception type.
+  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.
+**/
+VOID
+EFIAPI
+INTnExceptionHandler (
+  IN EFI_EXCEPTION_TYPE  ExceptionType,
+  IN EFI_SYSTEM_CONTEXT  SystemContext
+  )
+{
+  mExceptionType = ExceptionType;
+}
+
+/**
+  Restore cpu original registers before exit test case.
+
+  @param[in] Buffer  Argument of the procedure.
+**/
+VOID
+EFIAPI
+RestoreRegistersPerCpu (
+  IN VOID  *Buffer
+  )
+{
+  CPU_REGISTER_BUFFER  *CpuOriginalRegisterBuffer;
+  UINT16               Tr;
+  IA32_TSS_DESCRIPTOR  *Tss;
+
+  CpuOriginalRegisterBuffer = (CPU_REGISTER_BUFFER *)Buffer;
+
+  AsmWriteGdtr (&(CpuOriginalRegisterBuffer->OriginalGdtr));
+  AsmWriteIdtr (&(CpuOriginalRegisterBuffer->OriginalIdtr));
+  Tr = CpuOriginalRegisterBuffer->Tr;
+  if ((Tr != 0) && (Tr < CpuOriginalRegisterBuffer->OriginalGdtr.Limit)) {
+    Tss = (IA32_TSS_DESCRIPTOR *)(CpuOriginalRegisterBuffer->OriginalGdtr.Base + Tr);
+    if (Tss->Bits.P == 1) {
+      //
+      // Clear busy bit of TSS before write Tr
+      //
+      Tss->Bits.Type &= 0xD;
+      AsmWriteTr (Tr);
+    }
+  }
+}
+
+/**
+  Restore cpu original registers before exit test case.
+
+  @param[in] MpServices                 MpServices.
+  @param[in] CpuOriginalRegisterBuffer  Address of CpuOriginalRegisterBuffer.
+  @param[in] BspProcessorNum            Bsp processor number.
+**/
+VOID
+RestoreAllCpuRegisters (
+  MP_SERVICES *MpServices, OPTIONAL
+  CPU_REGISTER_BUFFER  *CpuOriginalRegisterBuffer,
+  UINTN                         BspProcessorNum
+  )
+{
+  UINTN       Index;
+  EFI_STATUS  Status;
+
+  for (Index = 0; Index < mNumberOfProcessors; ++Index) {
+    if (Index == BspProcessorNum) {
+      RestoreRegistersPerCpu ((VOID *)&CpuOriginalRegisterBuffer[Index]);
+      continue;
+    }
+
+    ASSERT (MpServices != NULL);
+    Status = MpServicesUnitTestStartupThisAP (
+               *MpServices,
+               (EFI_AP_PROCEDURE)RestoreRegistersPerCpu,
+               Index,
+               0,
+               (VOID *)&CpuOriginalRegisterBuffer[Index]
+               );
+    ASSERT_EFI_ERROR (Status);
+  }
+}
+
+/**
+  Store cpu registers before the test case starts.
+
+  @param[in] Buffer  Argument of the procedure.
+**/
+VOID
+EFIAPI
+SaveRegisterPerCpu (
+  IN VOID  *Buffer
+  )
+{
+  CPU_REGISTER_BUFFER  *CpuOriginalRegisterBuffer;
+  IA32_DESCRIPTOR      Gdtr;
+  IA32_DESCRIPTOR      Idtr;
+
+  CpuOriginalRegisterBuffer = (CPU_REGISTER_BUFFER *)Buffer;
+
+  AsmReadGdtr (&Gdtr);
+  AsmReadIdtr (&Idtr);
+  CpuOriginalRegisterBuffer->OriginalGdtr.Base  = Gdtr.Base;
+  CpuOriginalRegisterBuffer->OriginalGdtr.Limit = Gdtr.Limit;
+  CpuOriginalRegisterBuffer->OriginalIdtr.Base  = Idtr.Base;
+  CpuOriginalRegisterBuffer->OriginalIdtr.Limit = Idtr.Limit;
+  CpuOriginalRegisterBuffer->Tr                 = AsmReadTr ();
+}
+
+/**
+  Store cpu registers before the test case starts.
+
+  @param[in] MpServices       MpServices.
+  @param[in] BspProcessorNum  Bsp processor number.
+
+  @return Pointer to the allocated CPU_REGISTER_BUFFER.
+**/
+CPU_REGISTER_BUFFER *
+SaveAllCpuRegisters (
+  MP_SERVICES *MpServices, OPTIONAL
+  UINTN       BspProcessorNum
+  )
+{
+  CPU_REGISTER_BUFFER  *CpuOriginalRegisterBuffer;
+  EFI_STATUS           Status;
+  UINTN                Index;
+
+  CpuOriginalRegisterBuffer = AllocateZeroPool (mNumberOfProcessors * sizeof (CPU_REGISTER_BUFFER));
+  ASSERT (CpuOriginalRegisterBuffer != NULL);
+
+  for (Index = 0; Index < mNumberOfProcessors; ++Index) {
+    if (Index == BspProcessorNum) {
+      SaveRegisterPerCpu ((VOID *)&CpuOriginalRegisterBuffer[Index]);
+      continue;
+    }
+
+    ASSERT (MpServices != NULL);
+    Status = MpServicesUnitTestStartupThisAP (
+               *MpServices,
+               (EFI_AP_PROCEDURE)SaveRegisterPerCpu,
+               Index,
+               0,
+               (VOID *)&CpuOriginalRegisterBuffer[Index]
+               );
+    ASSERT_EFI_ERROR (Status);
+  }
+
+  return CpuOriginalRegisterBuffer;
+}
+
+/**
+  Initialize Ap Idt Procedure.
+
+  @param[in] Buffer  Argument of the procedure.
+**/
+VOID
+EFIAPI
+InitializeIdtPerAp (
+  IN VOID  *Buffer
+  )
+{
+  AsmWriteIdtr (Buffer);
+}
+
+/**
+  Initialize all Ap Idt.
+
+  @param[in] MpServices MpServices.
+  @param[in] BspIdtr    Pointer to IA32_DESCRIPTOR allocated by Bsp.
+**/
+VOID
+InitializeApIdt (
+  MP_SERVICES  MpServices,
+  VOID         *BspIdtr
+  )
+{
+  EFI_STATUS  Status;
+
+  Status = MpServicesUnitTestStartupAllAPs (
+             MpServices,
+             (EFI_AP_PROCEDURE)InitializeIdtPerAp,
+             FALSE,
+             0,
+             BspIdtr
+             );
+  ASSERT_EFI_ERROR (Status);
+}
+
+/**
+  Check if exception handler can registered/unregistered for no error code exception.
+
+  @param[in]  Context    [Optional] An optional parameter that enables:
+                         1) test-case reuse with varied parameters and
+                         2) test-case re-entry for Target tests that need a
+                         reboot.  This parameter is a VOID* and it is the
+                         responsibility of the test author to ensure that the
+                         contents are well understood by all test cases that may
+                         consume it.
+
+  @retval  UNIT_TEST_PASSED             The Unit test has completed and the test
+                                        case was successful.
+  @retval  UNIT_TEST_ERROR_TEST_FAILED  A test case assertion has failed.
+**/
+UNIT_TEST_STATUS
+EFIAPI
+TestRegisterHandlerForNoErrorCodeException (
+  IN UNIT_TEST_CONTEXT  Context
+  )
+{
+  EFI_STATUS           Status;
+  UINTN                Index;
+  CPU_REGISTER_BUFFER  *CpuOriginalRegisterBuffer;
+  VOID                 *NewIdtr;
+
+  CpuOriginalRegisterBuffer = SaveAllCpuRegisters (NULL, 0);
+  NewIdtr                   = InitializeBspIdt ();
+  Status                    = InitializeCpuExceptionHandlers (NULL);
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+
+  for (Index = 0; Index < SPEC_MAX_EXCEPTION_NUM; Index++) {
+    //
+    // Only test no error code exception by INT n instruction.
+    //
+    if ((mErrorCodeExceptionFlag & (1 << Index)) != 0) {
+      continue;
+    }
+
+    DEBUG ((DEBUG_INFO, "TestCase1: ExceptionType is %d\n", Index));
+    Status = RegisterCpuInterruptHandler (Index, INTnExceptionHandler);
+    UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+
+    TriggerINTnException (Index);
+    UT_ASSERT_EQUAL (mExceptionType, Index);
+    Status = RegisterCpuInterruptHandler (Index, NULL);
+    UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+  }
+
+  RestoreAllCpuRegisters (NULL, CpuOriginalRegisterBuffer, 0);
+  FreePool (CpuOriginalRegisterBuffer);
+  FreePool (NewIdtr);
+  return UNIT_TEST_PASSED;
+}
+
+/**
+  Get Bsp stack base.
+
+  @param[out] StackBase  Pointer to stack base of BSP.
+**/
+VOID
+GetBspStackBase (
+  OUT UINTN  *StackBase
+  )
+{
+  EFI_PEI_HOB_POINTERS       Hob;
+  EFI_HOB_MEMORY_ALLOCATION  *MemoryHob;
+
+  //
+  // Get the base of stack from Hob.
+  //
+  ASSERT (StackBase != NULL);
+  Hob.Raw = GetHobList ();
+  while ((Hob.Raw = GetNextHob (EFI_HOB_TYPE_MEMORY_ALLOCATION, Hob.Raw)) != NULL) {
+    MemoryHob = Hob.MemoryAllocation;
+    if (CompareGuid (&gEfiHobMemoryAllocStackGuid, &MemoryHob->AllocDescriptor.Name)) {
+      DEBUG ((
+        DEBUG_INFO,
+        "%a: Bsp StackBase = 0x%016lx  StackSize = 0x%016lx\n",
+        __FUNCTION__,
+        MemoryHob->AllocDescriptor.MemoryBaseAddress,
+        MemoryHob->AllocDescriptor.MemoryLength
+        ));
+
+      *StackBase = (UINTN)MemoryHob->AllocDescriptor.MemoryBaseAddress;
+      //
+      // Ensure the base of the stack is page-size aligned.
+      //
+      ASSERT ((*StackBase & EFI_PAGE_MASK) == 0);
+      break;
+    }
+
+    Hob.Raw = GET_NEXT_HOB (Hob);
+  }
+
+  ASSERT (*StackBase != 0);
+}
+
+/**
+  Get Ap stack base procedure.
+
+  @param[out] ApStackBase  Pointer to Ap stack base.
+**/
+VOID
+EFIAPI
+GetStackBasePerAp (
+  OUT VOID  *ApStackBase
+  )
+{
+  UINTN  ApTopOfStack;
+
+  ApTopOfStack          = ALIGN_VALUE ((UINTN)&ApTopOfStack, (UINTN)PcdGet32 (PcdCpuApStackSize));
+  *(UINTN *)ApStackBase = ApTopOfStack - (UINTN)PcdGet32 (PcdCpuApStackSize);
+}
+
+/**
+  Get all Cpu stack base.
+
+  @param[in] MpServices       MpServices.
+  @param[in] BspProcessorNum  Bsp processor number.
+
+  @return Pointer to the allocated CpuStackBaseBuffer.
+**/
+UINTN *
+GetAllCpuStackBase (
+  MP_SERVICES  *MpServices,
+  UINTN        BspProcessorNum
+  )
+{
+  UINTN       *CpuStackBaseBuffer;
+  EFI_STATUS  Status;
+  UINTN       Index;
+
+  CpuStackBaseBuffer = AllocateZeroPool (mNumberOfProcessors * sizeof (UINTN));
+  ASSERT (CpuStackBaseBuffer != NULL);
+
+  for (Index = 0; Index < mNumberOfProcessors; ++Index) {
+    if (Index == BspProcessorNum) {
+      GetBspStackBase (&CpuStackBaseBuffer[Index]);
+      continue;
+    }
+
+    ASSERT (MpServices != NULL);
+    Status = MpServicesUnitTestStartupThisAP (
+               *MpServices,
+               (EFI_AP_PROCEDURE)GetStackBasePerAp,
+               Index,
+               0,
+               (VOID *)&CpuStackBaseBuffer[Index]
+               );
+    ASSERT_EFI_ERROR (Status);
+    DEBUG ((DEBUG_INFO, "AP[%d] StackBase = 0x%x\n", Index, CpuStackBaseBuffer[Index]));
+  }
+
+  return CpuStackBaseBuffer;
+}
+
+/**
+  Find not present or ReadOnly address in page table.
+
+  @param[out] PFAddress  Access to the address which is not permitted will trigger PF exceptions.
+
+  @retval TRUE   Found not present or ReadOnly address in page table.
+  @retval FALSE  Failed to found PFAddress in page table.
+**/
+BOOLEAN
+FindPFAddressInPageTable (
+  OUT UINTN  *PFAddress
+  )
+{
+  IA32_CR0        Cr0;
+  IA32_CR4        Cr4;
+  UINTN           PageTable;
+  PAGING_MODE     PagingMode;
+  BOOLEAN         Enable5LevelPaging;
+  RETURN_STATUS   Status;
+  IA32_MAP_ENTRY  *Map;
+  UINTN           MapCount;
+  UINTN           Index;
+  UINTN           PreviousAddress;
+
+  ASSERT (PFAddress != NULL);
+
+  Cr0.UintN = AsmReadCr0 ();
+  if (Cr0.Bits.PG == 0) {
+    return FALSE;
+  }
+
+  PageTable = AsmReadCr3 ();
+  Cr4.UintN = AsmReadCr4 ();
+  if (sizeof (UINTN) == sizeof (UINT32)) {
+    ASSERT (Cr4.Bits.PAE == 1);
+    PagingMode = PagingPae;
+  } else {
+    Enable5LevelPaging = (BOOLEAN)(Cr4.Bits.LA57 == 1);
+    PagingMode         = Enable5LevelPaging ? Paging5Level : Paging4Level;
+  }
+
+  MapCount = 0;
+  Status   = PageTableParse (PageTable, PagingMode, NULL, &MapCount);
+  ASSERT (Status == RETURN_BUFFER_TOO_SMALL);
+  Map    = AllocatePages (EFI_SIZE_TO_PAGES (MapCount * sizeof (IA32_MAP_ENTRY)));
+  Status = PageTableParse (PageTable, PagingMode, Map, &MapCount);
+  ASSERT (Status == RETURN_SUCCESS);
+
+  PreviousAddress = 0;
+  for (Index = 0; Index < MapCount; Index++) {
+    DEBUG ((
+      DEBUG_ERROR,
+      "%02d: %016lx - %016lx, %016lx\n",
+      Index,
+      Map[Index].LinearAddress,
+      Map[Index].LinearAddress + Map[Index].Length,
+      Map[Index].Attribute.Uint64
+      ));
+
+    //
+    // Not present address in page table.
+    //
+    if (Map[Index].LinearAddress > PreviousAddress) {
+      *PFAddress = PreviousAddress;
+      return TRUE;
+    }
+
+    PreviousAddress = (UINTN)(Map[Index].LinearAddress + Map[Index].Length);
+
+    //
+    // ReadOnly address in page table.
+    //
+    if ((Cr0.Bits.WP != 0) && (Map[Index].Attribute.Bits.ReadWrite == 0)) {
+      *PFAddress = (UINTN)Map[Index].LinearAddress;
+      return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+
+/**
+  Test if exception handler can registered/unregistered for GP and PF.
+
+  @param[in]  Context    [Optional] An optional parameter that enables:
+                         1) test-case reuse with varied parameters and
+                         2) test-case re-entry for Target tests that need a
+                         reboot.  This parameter is a VOID* and it is the
+                         responsibility of the test author to ensure that the
+                         contents are well understood by all test cases that may
+                         consume it.
+
+  @retval  UNIT_TEST_PASSED             The Unit test has completed and the test
+                                        case was successful.
+  @retval  UNIT_TEST_ERROR_TEST_FAILED  A test case assertion has failed.
+**/
+UNIT_TEST_STATUS
+EFIAPI
+TestRegisterHandlerForGPAndPF (
+  IN UNIT_TEST_CONTEXT  Context
+  )
+{
+  EFI_STATUS           Status;
+  CPU_REGISTER_BUFFER  *CpuOriginalRegisterBuffer;
+  UINTN                PFAddress;
+  VOID                 *NewIdtr;
+
+  PFAddress                 = 0;
+  CpuOriginalRegisterBuffer = SaveAllCpuRegisters (NULL, 0);
+  NewIdtr                   = InitializeBspIdt ();
+  Status                    = InitializeCpuExceptionHandlers (NULL);
+
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+
+  //
+  // GP exception.
+  //
+  DEBUG ((DEBUG_INFO, "TestCase2: ExceptionType is %d\n", EXCEPT_IA32_GP_FAULT));
+  Status = RegisterCpuInterruptHandler (EXCEPT_IA32_GP_FAULT, AdjustRipForFaultHandler);
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+
+  TriggerGPException (CR4_RESERVED_BIT);
+  UT_ASSERT_EQUAL (mExceptionType, EXCEPT_IA32_GP_FAULT);
+  Status = RegisterCpuInterruptHandler (EXCEPT_IA32_GP_FAULT, NULL);
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+
+  //
+  // PF exception.
+  //
+  if (FindPFAddressInPageTable (&PFAddress)) {
+    DEBUG ((DEBUG_INFO, "TestCase2: ExceptionType is %d\n", EXCEPT_IA32_PAGE_FAULT));
+    Status = RegisterCpuInterruptHandler (EXCEPT_IA32_PAGE_FAULT, AdjustRipForFaultHandler);
+    UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+    TriggerPFException (PFAddress);
+
+    UT_ASSERT_EQUAL (mExceptionType, EXCEPT_IA32_PAGE_FAULT);
+    Status = RegisterCpuInterruptHandler (EXCEPT_IA32_PAGE_FAULT, NULL);
+    UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+  }
+
+  RestoreAllCpuRegisters (NULL, CpuOriginalRegisterBuffer, 0);
+  FreePool (CpuOriginalRegisterBuffer);
+  FreePool (NewIdtr);
+  return UNIT_TEST_PASSED;
+}
+
+/**
+  Test if Cpu Context is consistent before and after exception.
+
+  @param[in]  Context    [Optional] An optional parameter that enables:
+                         1) test-case reuse with varied parameters and
+                         2) test-case re-entry for Target tests that need a
+                         reboot.  This parameter is a VOID* and it is the
+                         responsibility of the test author to ensure that the
+                         contents are well understood by all test cases that may
+                         consume it.
+
+  @retval  UNIT_TEST_PASSED             The Unit test has completed and the test
+                                        case was successful.
+  @retval  UNIT_TEST_ERROR_TEST_FAILED  A test case assertion has failed.
+**/
+UNIT_TEST_STATUS
+EFIAPI
+TestCpuContextConsistency (
+  IN UNIT_TEST_CONTEXT  Context
+  )
+{
+  EFI_STATUS           Status;
+  UINTN                Index;
+  CPU_REGISTER_BUFFER  *CpuOriginalRegisterBuffer;
+  UINTN                FaultParameter;
+  VOID                 *NewIdtr;
+
+  FaultParameter            = 0;
+  CpuOriginalRegisterBuffer = SaveAllCpuRegisters (NULL, 0);
+  NewIdtr                   = InitializeBspIdt ();
+  Status                    = InitializeCpuExceptionHandlers (NULL);
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+
+  for (Index = 0; Index < 22; Index++) {
+    if (Index == EXCEPT_IA32_PAGE_FAULT) {
+      if (!FindPFAddressInPageTable (&FaultParameter)) {
+        continue;
+      }
+    } else if (Index == EXCEPT_IA32_GP_FAULT) {
+      FaultParameter = CR4_RESERVED_BIT;
+    } else {
+      if ((mErrorCodeExceptionFlag & (1 << Index)) != 0) {
+        continue;
+      }
+    }
+
+    DEBUG ((DEBUG_INFO, "TestCase3: ExceptionType is %d\n", Index));
+    Status = RegisterCpuInterruptHandler (Index, AdjustCpuContextHandler);
+    UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+
+    //
+    // Trigger different type exception and compare different stage cpu context.
+    //
+    AsmTestConsistencyOfCpuContext (Index, FaultParameter);
+    CompareCpuContext ();
+    Status = RegisterCpuInterruptHandler (Index, NULL);
+    UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+  }
+
+  RestoreAllCpuRegisters (NULL, CpuOriginalRegisterBuffer, 0);
+  FreePool (CpuOriginalRegisterBuffer);
+  FreePool (NewIdtr);
+  return UNIT_TEST_PASSED;
+}
+
+/**
+  Initializes CPU exceptions handlers for the sake of stack switch requirement.
+
+  This function is a wrapper of InitializeSeparateExceptionStacks. It's mainly
+  for the sake of AP's init because of EFI_AP_PROCEDURE API requirement.
+
+  @param[in,out] Buffer  The pointer to private data buffer.
+
+**/
+VOID
+EFIAPI
+InitializeExceptionStackSwitchHandlersPerAp (
+  IN OUT VOID  *Buffer
+  )
+{
+  EXCEPTION_STACK_SWITCH_CONTEXT  *CpuSwitchStackData;
+
+  CpuSwitchStackData = (EXCEPTION_STACK_SWITCH_CONTEXT *)Buffer;
+
+  //
+  // This may be called twice for each Cpu. Only run InitializeSeparateExceptionStacks
+  // if this is the first call or the first call failed because of size too small.
+  //
+  if ((CpuSwitchStackData->Status == EFI_NOT_STARTED) || (CpuSwitchStackData->Status == EFI_BUFFER_TOO_SMALL)) {
+    CpuSwitchStackData->Status = InitializeSeparateExceptionStacks (CpuSwitchStackData->Buffer, &CpuSwitchStackData->BufferSize);
+  }
+}
+
+/**
+  Initializes MP exceptions handlers for the sake of stack switch requirement.
+
+  This function will allocate required resources required to setup stack switch
+  and pass them through SwitchStackData to each logic processor.
+
+  @param[in, out] MpServices       MpServices.
+  @param[in, out] BspProcessorNum  Bsp processor number.
+
+  @return Pointer to the allocated SwitchStackData.
+**/
+EXCEPTION_STACK_SWITCH_CONTEXT  *
+InitializeMpExceptionStackSwitchHandlers (
+  MP_SERVICES  MpServices,
+  UINTN        BspProcessorNum
+  )
+{
+  UINTN                           Index;
+  EXCEPTION_STACK_SWITCH_CONTEXT  *SwitchStackData;
+  UINTN                           BufferSize;
+  EFI_STATUS                      Status;
+  UINT8                           *Buffer;
+
+  SwitchStackData = AllocateZeroPool (mNumberOfProcessors * sizeof (EXCEPTION_STACK_SWITCH_CONTEXT));
+  ASSERT (SwitchStackData != NULL);
+  for (Index = 0; Index < mNumberOfProcessors; ++Index) {
+    //
+    // Because the procedure may runs multiple times, use the status EFI_NOT_STARTED
+    // to indicate the procedure haven't been run yet.
+    //
+    SwitchStackData[Index].Status = EFI_NOT_STARTED;
+    if (Index == BspProcessorNum) {
+      InitializeExceptionStackSwitchHandlersPerAp ((VOID *)&SwitchStackData[Index]);
+      continue;
+    }
+
+    Status = MpServicesUnitTestStartupThisAP (
+               MpServices,
+               InitializeExceptionStackSwitchHandlersPerAp,
+               Index,
+               0,
+               (VOID *)&SwitchStackData[Index]
+               );
+    ASSERT_EFI_ERROR (Status);
+  }
+
+  BufferSize = 0;
+  for (Index = 0; Index < mNumberOfProcessors; ++Index) {
+    if (SwitchStackData[Index].Status == EFI_BUFFER_TOO_SMALL) {
+      ASSERT (SwitchStackData[Index].BufferSize != 0);
+      BufferSize += SwitchStackData[Index].BufferSize;
+    } else {
+      ASSERT (SwitchStackData[Index].Status == EFI_SUCCESS);
+      ASSERT (SwitchStackData[Index].BufferSize == 0);
+    }
+  }
+
+  if (BufferSize != 0) {
+    Buffer = AllocateZeroPool (BufferSize);
+    ASSERT (Buffer != NULL);
+    BufferSize = 0;
+    for (Index = 0; Index < mNumberOfProcessors; ++Index) {
+      if (SwitchStackData[Index].Status == EFI_BUFFER_TOO_SMALL) {
+        SwitchStackData[Index].Buffer = (VOID *)(&Buffer[BufferSize]);
+        BufferSize                   += SwitchStackData[Index].BufferSize;
+        DEBUG ((
+          DEBUG_INFO,
+          "Buffer[cpu%lu] for InitializeExceptionStackSwitchHandlersPerAp: 0x%lX with size 0x%lX\n",
+          (UINT64)(UINTN)Index,
+          (UINT64)(UINTN)SwitchStackData[Index].Buffer,
+          (UINT64)(UINTN)SwitchStackData[Index].BufferSize
+          ));
+      }
+    }
+
+    for (Index = 0; Index < mNumberOfProcessors; ++Index) {
+      if (Index == BspProcessorNum) {
+        InitializeExceptionStackSwitchHandlersPerAp ((VOID *)&SwitchStackData[Index]);
+        continue;
+      }
+
+      Status = MpServicesUnitTestStartupThisAP (
+                 MpServices,
+                 InitializeExceptionStackSwitchHandlersPerAp,
+                 Index,
+                 0,
+                 (VOID *)&SwitchStackData[Index]
+                 );
+      ASSERT_EFI_ERROR (Status);
+    }
+
+    for (Index = 0; Index < mNumberOfProcessors; ++Index) {
+      ASSERT (SwitchStackData[Index].Status == EFI_SUCCESS);
+    }
+  }
+
+  return SwitchStackData;
+}
+
+/**
+  Test if stack overflow is captured by CpuStackGuard in both Bsp and AP.
+
+  @param[in]  Context    [Optional] An optional parameter that enables:
+                         1) test-case reuse with varied parameters and
+                         2) test-case re-entry for Target tests that need a
+                         reboot.  This parameter is a VOID* and it is the
+                         responsibility of the test author to ensure that the
+                         contents are well understood by all test cases that may
+                         consume it.
+
+  @retval  UNIT_TEST_PASSED             The Unit test has completed and the test
+                                        case was successful.
+  @retval  UNIT_TEST_ERROR_TEST_FAILED  A test case assertion has failed.
+**/
+UNIT_TEST_STATUS
+EFIAPI
+TestCpuStackGuardInBspAndAp (
+  IN UNIT_TEST_CONTEXT  Context
+  )
+{
+  EFI_STATUS                      Status;
+  UINTN                           OriginalStackBase;
+  UINTN                           NewStackTop;
+  UINTN                           NewStackBase;
+  EXCEPTION_STACK_SWITCH_CONTEXT  *SwitchStackData;
+  MP_SERVICES                     MpServices;
+  UINTN                           ProcessorNumber;
+  UINTN                           EnabledProcessorNum;
+  CPU_REGISTER_BUFFER             *CpuOriginalRegisterBuffer;
+  UINTN                           Index;
+  UINTN                           BspProcessorNum;
+  VOID                            *NewIdtr;
+  UINTN                           *CpuStackBaseBuffer;
+
+  if (!PcdGetBool (PcdCpuStackGuard)) {
+    return UNIT_TEST_PASSED;
+  }
+
+  //
+  // Get MP Service Protocol
+  //
+  Status = GetMpServices (&MpServices);
+  Status = MpServicesUnitTestGetNumberOfProcessors (MpServices, &ProcessorNumber, &EnabledProcessorNum);
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+  Status = MpServicesUnitTestWhoAmI (MpServices, &BspProcessorNum);
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+  mNumberOfProcessors = ProcessorNumber;
+
+  CpuOriginalRegisterBuffer = SaveAllCpuRegisters (&MpServices, BspProcessorNum);
+
+  //
+  // Initialize Bsp and AP Idt.
+  // Idt buffer should not be empty or it will hang in MP API.
+  //
+  NewIdtr = InitializeBspIdt ();
+  Status  = InitializeCpuExceptionHandlers (NULL);
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+  InitializeApIdt (MpServices, NewIdtr);
+
+  //
+  // Get BSP and AP original stack base.
+  //
+  CpuStackBaseBuffer = GetAllCpuStackBase (&MpServices, BspProcessorNum);
+
+  //
+  // InitializeMpExceptionStackSwitchHandlers and register exception handler.
+  //
+  SwitchStackData = InitializeMpExceptionStackSwitchHandlers (MpServices, BspProcessorNum);
+  Status          = RegisterCpuInterruptHandler (EXCEPT_IA32_PAGE_FAULT, CpuStackGuardExceptionHandler);
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+  Status = RegisterCpuInterruptHandler (EXCEPT_IA32_DOUBLE_FAULT, AdjustRipForFaultHandler);
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+
+  for (Index = 0; Index < mNumberOfProcessors; Index++) {
+    OriginalStackBase = CpuStackBaseBuffer[Index];
+    NewStackTop       = (UINTN)(SwitchStackData[Index].Buffer) + SwitchStackData[Index].BufferSize;
+    NewStackBase      = (UINTN)(SwitchStackData[Index].Buffer);
+    if (Index == BspProcessorNum) {
+      TriggerStackOverflow ();
+    } else {
+      MpServicesUnitTestStartupThisAP (
+        MpServices,
+        (EFI_AP_PROCEDURE)TriggerStackOverflow,
+        Index,
+        0,
+        NULL
+        );
+    }
+
+    DEBUG ((DEBUG_INFO, "TestCase4: mRspAddress[0] is 0x%x, mRspAddress[1] is 0x%x\n", mRspAddress[0], mRspAddress[1]));
+    UT_ASSERT_TRUE ((mRspAddress[0] >= OriginalStackBase) && (mRspAddress[0] <= (OriginalStackBase + SIZE_4KB)));
+    UT_ASSERT_TRUE ((mRspAddress[1] >= NewStackBase) && (mRspAddress[1] < NewStackTop));
+  }
+
+  Status = RegisterCpuInterruptHandler (EXCEPT_IA32_PAGE_FAULT, NULL);
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+  Status = RegisterCpuInterruptHandler (EXCEPT_IA32_DOUBLE_FAULT, NULL);
+  UT_ASSERT_EQUAL (Status, EFI_SUCCESS);
+  RestoreAllCpuRegisters (&MpServices, CpuOriginalRegisterBuffer, BspProcessorNum);
+  FreePool (SwitchStackData);
+  FreePool (CpuOriginalRegisterBuffer);
+  FreePool (NewIdtr);
+
+  return UNIT_TEST_PASSED;
+}
+
+/**
+  Create CpuExceptionLibUnitTestSuite and add test case.
+
+  @param[in]  FrameworkHandle    Unit test framework.
+
+  @return  EFI_SUCCESS           The unit test suite was created.
+  @retval  EFI_OUT_OF_RESOURCES  There are not enough resources available to
+                                 initialize the unit test suite.
+**/
+EFI_STATUS
+AddCommonTestCase (
+  IN  UNIT_TEST_FRAMEWORK_HANDLE  Framework
+  )
+{
+  EFI_STATUS              Status;
+  UNIT_TEST_SUITE_HANDLE  CpuExceptionLibUnitTestSuite;
+
+  //
+  // Populate the Manual Test Cases.
+  //
+  Status = CreateUnitTestSuite (&CpuExceptionLibUnitTestSuite, Framework, "Test CpuExceptionHandlerLib", "CpuExceptionHandlerLib.Manual", NULL, NULL);
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for CpuExceptionHandlerLib Test Cases\n"));
+    Status = EFI_OUT_OF_RESOURCES;
+    return Status;
+  }
+
+  AddTestCase (CpuExceptionLibUnitTestSuite, "Check if exception handler can be registered/unregistered for no error code exception", "TestRegisterHandlerForNoErrorCodeException", TestRegisterHandlerForNoErrorCodeException, NULL, NULL, NULL);
+  AddTestCase (CpuExceptionLibUnitTestSuite, "Check if exception handler can be registered/unregistered for GP and PF", "TestRegisterHandlerForGPAndPF", TestRegisterHandlerForGPAndPF, NULL, NULL, NULL);
+
+  AddTestCase (CpuExceptionLibUnitTestSuite, "Check if Cpu Context is consistent before and after exception.", "TestCpuContextConsistency", TestCpuContextConsistency, NULL, NULL, NULL);
+  AddTestCase (CpuExceptionLibUnitTestSuite, "Check if stack overflow is captured by CpuStackGuard in Bsp and AP", "TestCpuStackGuardInBspAndAp", TestCpuStackGuardInBspAndAp, NULL, NULL, NULL);
+
+  return EFI_SUCCESS;
+}
diff --git a/UefiCpuPkg/CpuExceptionHandlerUnitTest/DxeCpuExceptionHandlerLibUnitTest.inf b/UefiCpuPkg/CpuExceptionHandlerUnitTest/DxeCpuExceptionHandlerLibUnitTest.inf
new file mode 100644
index 0000000000..e3dbe7b9ab
--- /dev/null
+++ b/UefiCpuPkg/CpuExceptionHandlerUnitTest/DxeCpuExceptionHandlerLibUnitTest.inf
@@ -0,0 +1,58 @@
+## @file
+# Unit tests of the DxeCpuExceptionHandlerLib instance.
+#
+# Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+[Defines]
+  INF_VERSION                    = 0x00010005
+  BASE_NAME                      = CpuExceptionHandlerDxeTest
+  FILE_GUID                      = D76BFD9C-0B6D-46BD-AD66-2BBB6FA7031A
+  MODULE_TYPE                    = DXE_DRIVER
+  VERSION_STRING                 = 1.0
+  ENTRY_POINT                    = CpuExceptionHandlerTestEntry
+
+#
+# The following information is for reference only and not required by the build tools.
+#
+#  VALID_ARCHITECTURES           = X64
+#
+[Sources.X64]
+  X64/ArchExceptionHandlerTestAsm.nasm
+  X64/ArchExceptionHandlerTest.c
+
+[Sources.common]
+  CpuExceptionHandlerTest.h
+  CpuExceptionHandlerTestCommon.c
+  DxeCpuExceptionHandlerUnitTest.c
+
+[Packages]
+  MdePkg/MdePkg.dec
+  MdeModulePkg/MdeModulePkg.dec
+  UefiCpuPkg/UefiCpuPkg.dec
+
+[LibraryClasses]
+  BaseLib
+  BaseMemoryLib
+  DebugLib
+  UnitTestLib
+  MemoryAllocationLib
+  CpuExceptionHandlerLib
+  UefiDriverEntryPoint
+  HobLib
+  UefiBootServicesTableLib
+  CpuPageTableLib
+
+[Guids]
+  gEfiHobMemoryAllocStackGuid
+
+[Pcd]
+  gEfiMdeModulePkgTokenSpaceGuid.PcdCpuStackGuard       ## CONSUMES
+  gUefiCpuPkgTokenSpaceGuid.PcdCpuApStackSize           ## CONSUMES
+
+[Protocols]
+  gEfiMpServiceProtocolGuid
+
+[Depex]
+  gEfiMpServiceProtocolGuid
diff --git a/UefiCpuPkg/CpuExceptionHandlerUnitTest/DxeCpuExceptionHandlerUnitTest.c b/UefiCpuPkg/CpuExceptionHandlerUnitTest/DxeCpuExceptionHandlerUnitTest.c
new file mode 100644
index 0000000000..917fc549bf
--- /dev/null
+++ b/UefiCpuPkg/CpuExceptionHandlerUnitTest/DxeCpuExceptionHandlerUnitTest.c
@@ -0,0 +1,196 @@
+/** @file
+  Unit tests of the CpuExceptionHandlerLib.
+
+  Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include "CpuExceptionHandlerTest.h"
+#include <Library/UefiBootServicesTableLib.h>
+
+/**
+  Initialize Bsp Idt with a new Idt table and return the IA32_DESCRIPTOR buffer.
+  In PEIM, store original PeiServicePointer before new Idt table.
+
+  @return Pointer to the allocated IA32_DESCRIPTOR buffer.
+**/
+VOID *
+InitializeBspIdt (
+  VOID
+  )
+{
+  UINTN            *NewIdtTable;
+  IA32_DESCRIPTOR  *Idtr;
+
+  Idtr = AllocateZeroPool (sizeof (IA32_DESCRIPTOR));
+  ASSERT (Idtr != NULL);
+  NewIdtTable = AllocateZeroPool (sizeof (IA32_IDT_GATE_DESCRIPTOR) * CPU_INTERRUPT_NUM);
+  ASSERT (NewIdtTable != NULL);
+  Idtr->Base  = (UINTN)NewIdtTable;
+  Idtr->Limit = (UINT16)(sizeof (IA32_IDT_GATE_DESCRIPTOR) * CPU_INTERRUPT_NUM - 1);
+
+  AsmWriteIdtr (Idtr);
+  return Idtr;
+}
+
+/**
+  Retrieve the number of logical processor in the platform and the number of those logical processors that
+  are enabled on this boot.
+
+  @param[in]  MpServices          MP_SERVICES structure.
+  @param[out] NumberOfProcessors  Pointer to the total number of logical processors in the system, including
+                                  the BSP and disabled APs.
+  @param[out] NumberOfEnabledProcessors Pointer to the number of processors in the system that are enabled.
+
+  @retval EFI_SUCCESS       Retrieve the number of logical processor successfully
+  @retval Others            Retrieve the number of logical processor unsuccessfully
+**/
+EFI_STATUS
+MpServicesUnitTestGetNumberOfProcessors (
+  IN MP_SERVICES  MpServices,
+  OUT UINTN       *NumberOfProcessors,
+  OUT UINTN       *NumberOfEnabledProcessors
+  )
+{
+  return MpServices.Protocol->GetNumberOfProcessors (MpServices.Protocol, NumberOfProcessors, NumberOfEnabledProcessors);
+}
+
+/**
+  Get the handle number for the calling processor.
+
+  @param[in]  MpServices      MP_SERVICES structure.
+  @param[out] ProcessorNumber The handle number for the calling processor.
+
+  @retval EFI_SUCCESS       Get the handle number for the calling processor successfully.
+  @retval Others            Get the handle number for the calling processor unsuccessfully.
+**/
+EFI_STATUS
+MpServicesUnitTestWhoAmI (
+  IN MP_SERVICES  MpServices,
+  OUT UINTN       *ProcessorNumber
+  )
+{
+  return MpServices.Protocol->WhoAmI (MpServices.Protocol, ProcessorNumber);
+}
+
+/**
+  Caller gets one enabled AP to execute a caller-provided function.
+
+  @param[in]  MpServices    MP_SERVICES structure.
+  @param[in]  Procedure     Pointer to the function to be run on enabled APs of the system.
+  @param[in]  ProcessorNumber       The handle number of the AP.
+  @param[in]  TimeoutInMicroSeconds Indicates the time limit in microseconds for APs to return from Procedure,
+                                    for blocking mode only. Zero means infinity.
+  @param[in]  ProcedureArgument     The parameter passed into Procedure for all APs.
+
+
+  @retval EFI_SUCCESS       Caller gets one enabled AP to execute a caller-provided function successfully
+  @retval Others            Caller gets one enabled AP to execute a caller-provided function unsuccessfully
+**/
+EFI_STATUS
+MpServicesUnitTestStartupThisAP (
+  IN MP_SERVICES       MpServices,
+  IN EFI_AP_PROCEDURE  Procedure,
+  IN UINTN             ProcessorNumber,
+  IN UINTN             TimeoutInMicroSeconds,
+  IN VOID              *ProcedureArgument
+  )
+{
+  return MpServices.Protocol->StartupThisAP (MpServices.Protocol, Procedure, ProcessorNumber, NULL, TimeoutInMicroSeconds, ProcedureArgument, NULL);
+}
+
+/**
+  Execute a caller provided function on all enabled APs.
+
+  @param[in]  MpServices    MP_SERVICES structure.
+  @param[in]  Procedure     Pointer to the function to be run on enabled APs of the system.
+  @param[in]  SingleThread  If TRUE, then all the enabled APs execute the function specified by Procedure
+                            one by one, in ascending order of processor handle number.
+                            If FALSE, then all the enabled APs execute the function specified by Procedure
+                            simultaneously.
+  @param[in]  TimeoutInMicroSeconds Indicates the time limit in microseconds for APs to return from Procedure,
+                                    for blocking mode only. Zero means infinity.
+  @param[in]  ProcedureArgument     The parameter passed into Procedure for all APs.
+
+  @retval EFI_SUCCESS       Execute a caller provided function on all enabled APs successfully
+  @retval Others            Execute a caller provided function on all enabled APs unsuccessfully
+**/
+EFI_STATUS
+MpServicesUnitTestStartupAllAPs (
+  IN MP_SERVICES       MpServices,
+  IN EFI_AP_PROCEDURE  Procedure,
+  IN BOOLEAN           SingleThread,
+  IN UINTN             TimeoutInMicroSeconds,
+  IN VOID              *ProcedureArgument
+  )
+{
+  return MpServices.Protocol->StartupAllAPs (MpServices.Protocol, Procedure, SingleThread, NULL, TimeoutInMicroSeconds, ProcedureArgument, NULL);
+}
+
+/**
+  Get EFI_MP_SERVICES_PROTOCOL pointer.
+
+  @param[out] MpServices    Pointer to the buffer where EFI_MP_SERVICES_PROTOCOL is stored
+
+  @retval EFI_SUCCESS       EFI_MP_SERVICES_PROTOCOL interface is returned
+  @retval EFI_NOT_FOUND     EFI_MP_SERVICES_PROTOCOL interface is not found
+**/
+EFI_STATUS
+GetMpServices (
+  OUT MP_SERVICES  *MpServices
+  )
+{
+  return gBS->LocateProtocol (&gEfiMpServiceProtocolGuid, NULL, (VOID **)&MpServices->Protocol);
+}
+
+/**
+  Entry for CpuExceptionHandlerDxeTest driver.
+
+  @param ImageHandle     Image handle this driver.
+  @param SystemTable     Pointer to the System Table.
+
+  @retval EFI_SUCCESS    The driver executed normally.
+
+**/
+EFI_STATUS
+EFIAPI
+CpuExceptionHandlerTestEntry (
+  IN EFI_HANDLE        ImageHandle,
+  IN EFI_SYSTEM_TABLE  *SystemTable
+  )
+{
+  EFI_STATUS                  Status;
+  UNIT_TEST_FRAMEWORK_HANDLE  Framework;
+
+  Framework = NULL;
+
+  DEBUG ((DEBUG_INFO, "%a v%a\n", UNIT_TEST_APP_NAME, UNIT_TEST_APP_VERSION));
+
+  //
+  // Start setting up the test framework for running the tests.
+  //
+  Status = InitUnitTestFramework (&Framework, UNIT_TEST_APP_NAME, gEfiCallerBaseName, UNIT_TEST_APP_VERSION);
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "Failed in InitUnitTestFramework. Status = %r\n", Status));
+    goto EXIT;
+  }
+
+  Status = AddCommonTestCase (Framework);
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "Failed in AddCommonTestCase. Status = %r\n", Status));
+    goto EXIT;
+  }
+
+  //
+  // Execute the tests.
+  //
+  Status = RunAllTestSuites (Framework);
+
+EXIT:
+  if (Framework) {
+    FreeUnitTestFramework (Framework);
+  }
+
+  return Status;
+}
diff --git a/UefiCpuPkg/CpuExceptionHandlerUnitTest/X64/ArchExceptionHandlerTest.c b/UefiCpuPkg/CpuExceptionHandlerUnitTest/X64/ArchExceptionHandlerTest.c
new file mode 100644
index 0000000000..c0d962f26d
--- /dev/null
+++ b/UefiCpuPkg/CpuExceptionHandlerUnitTest/X64/ArchExceptionHandlerTest.c
@@ -0,0 +1,166 @@
+/** @file
+  Unit tests of the CpuExceptionHandlerLib.
+
+  Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include "CpuExceptionHandlerTest.h"
+
+GENERAL_REGISTER  mActualContextInHandler;
+GENERAL_REGISTER  mActualContextAfterException;
+
+//
+// In TestCpuContextConsistency, Cpu registers will be set to mExpectedContextInHandler/mExpectedContextAfterException.
+// Rcx in mExpectedContextInHandler is set runtime since Rcx is needed in assembly code.
+// For GP and PF, Rcx is set to FaultParameter. For other exception triggered by INTn, Rcx is set to ExceptionType.
+//
+GENERAL_REGISTER  mExpectedContextInHandler      = { 1, 2, 3, 4, 5, 0, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
+GENERAL_REGISTER  mExpectedContextAfterException = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e };
+
+/**
+  Special handler for fault exception.
+  Rip/Eip in SystemContext will be modified to the instruction after the exception instruction.
+
+  @param ExceptionType  Exception type.
+  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.
+**/
+VOID
+EFIAPI
+AdjustRipForFaultHandler (
+  IN EFI_EXCEPTION_TYPE  ExceptionType,
+  IN EFI_SYSTEM_CONTEXT  SystemContext
+  )
+{
+  mExceptionType                       = ExceptionType;
+  SystemContext.SystemContextX64->Rip += mFaultInstructionLength;
+}
+
+/**
+  Special handler for ConsistencyOfCpuContext test case.
+
+  @param ExceptionType  Exception type.
+  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.
+**/
+VOID
+EFIAPI
+AdjustCpuContextHandler (
+  IN EFI_EXCEPTION_TYPE  ExceptionType,
+  IN EFI_SYSTEM_CONTEXT  SystemContext
+  )
+{
+  //
+  // Store SystemContext in mActualContextInHandler.
+  //
+  mActualContextInHandler.Rdi = SystemContext.SystemContextX64->Rdi;
+  mActualContextInHandler.Rsi = SystemContext.SystemContextX64->Rsi;
+  mActualContextInHandler.Rbx = SystemContext.SystemContextX64->Rbx;
+  mActualContextInHandler.Rdx = SystemContext.SystemContextX64->Rdx;
+  mActualContextInHandler.Rcx = SystemContext.SystemContextX64->Rcx;
+  mActualContextInHandler.Rax = SystemContext.SystemContextX64->Rax;
+  mActualContextInHandler.R8  = SystemContext.SystemContextX64->R8;
+  mActualContextInHandler.R9  = SystemContext.SystemContextX64->R9;
+  mActualContextInHandler.R10 = SystemContext.SystemContextX64->R10;
+  mActualContextInHandler.R11 = SystemContext.SystemContextX64->R11;
+  mActualContextInHandler.R12 = SystemContext.SystemContextX64->R12;
+  mActualContextInHandler.R13 = SystemContext.SystemContextX64->R13;
+  mActualContextInHandler.R14 = SystemContext.SystemContextX64->R14;
+  mActualContextInHandler.R15 = SystemContext.SystemContextX64->R15;
+
+  //
+  // Modify cpu context. These registers will be stored in mActualContextAfterException.
+  // Do not handle Rsp and Rbp. CpuExceptionHandlerLib doesn't set Rsp and Rbp register
+  // to the value in SystemContext.
+  //
+  SystemContext.SystemContextX64->Rdi = mExpectedContextAfterException.Rdi;
+  SystemContext.SystemContextX64->Rsi = mExpectedContextAfterException.Rsi;
+  SystemContext.SystemContextX64->Rbx = mExpectedContextAfterException.Rbx;
+  SystemContext.SystemContextX64->Rdx = mExpectedContextAfterException.Rdx;
+  SystemContext.SystemContextX64->Rcx = mExpectedContextAfterException.Rcx;
+  SystemContext.SystemContextX64->Rax = mExpectedContextAfterException.Rax;
+  SystemContext.SystemContextX64->R8  = mExpectedContextAfterException.R8;
+  SystemContext.SystemContextX64->R9  = mExpectedContextAfterException.R9;
+  SystemContext.SystemContextX64->R10 = mExpectedContextAfterException.R10;
+  SystemContext.SystemContextX64->R11 = mExpectedContextAfterException.R11;
+  SystemContext.SystemContextX64->R12 = mExpectedContextAfterException.R12;
+  SystemContext.SystemContextX64->R13 = mExpectedContextAfterException.R13;
+  SystemContext.SystemContextX64->R14 = mExpectedContextAfterException.R14;
+  SystemContext.SystemContextX64->R15 = mExpectedContextAfterException.R15;
+
+  //
+  // When fault exception happens, eip/rip points to the faulting instruction.
+  // For now, olny GP and PF are tested in fault exception.
+  //
+  if ((ExceptionType == EXCEPT_IA32_PAGE_FAULT) || (ExceptionType == EXCEPT_IA32_GP_FAULT)) {
+    AdjustRipForFaultHandler (ExceptionType, SystemContext);
+  }
+}
+
+/**
+  Compare cpu context in ConsistencyOfCpuContext test case.
+  1.Compare mActualContextInHandler with mExpectedContextInHandler.
+  2.Compare mActualContextAfterException with mActualContextAfterException.
+
+  @retval  UNIT_TEST_PASSED             The Unit test has completed and it was successful.
+  @retval  UNIT_TEST_ERROR_TEST_FAILED  A test case assertion has failed.
+**/
+UNIT_TEST_STATUS
+CompareCpuContext (
+  VOID
+  )
+{
+  UT_ASSERT_EQUAL (mActualContextInHandler.Rdi, mExpectedContextInHandler.Rdi);
+  UT_ASSERT_EQUAL (mActualContextInHandler.Rsi, mExpectedContextInHandler.Rsi);
+  UT_ASSERT_EQUAL (mActualContextInHandler.Rbx, mExpectedContextInHandler.Rbx);
+  UT_ASSERT_EQUAL (mActualContextInHandler.Rdx, mExpectedContextInHandler.Rdx);
+  UT_ASSERT_EQUAL (mActualContextInHandler.Rcx, mExpectedContextInHandler.Rcx);
+  UT_ASSERT_EQUAL (mActualContextInHandler.Rax, mExpectedContextInHandler.Rax);
+  UT_ASSERT_EQUAL (mActualContextInHandler.R8, mExpectedContextInHandler.R8);
+  UT_ASSERT_EQUAL (mActualContextInHandler.R9, mExpectedContextInHandler.R9);
+  UT_ASSERT_EQUAL (mActualContextInHandler.R10, mExpectedContextInHandler.R10);
+  UT_ASSERT_EQUAL (mActualContextInHandler.R11, mExpectedContextInHandler.R11);
+  UT_ASSERT_EQUAL (mActualContextInHandler.R12, mExpectedContextInHandler.R12);
+  UT_ASSERT_EQUAL (mActualContextInHandler.R13, mExpectedContextInHandler.R13);
+  UT_ASSERT_EQUAL (mActualContextInHandler.R14, mExpectedContextInHandler.R14);
+  UT_ASSERT_EQUAL (mActualContextInHandler.R15, mExpectedContextInHandler.R15);
+
+  UT_ASSERT_EQUAL (mActualContextAfterException.Rdi, mExpectedContextAfterException.Rdi);
+  UT_ASSERT_EQUAL (mActualContextAfterException.Rsi, mExpectedContextAfterException.Rsi);
+  UT_ASSERT_EQUAL (mActualContextAfterException.Rbx, mExpectedContextAfterException.Rbx);
+  UT_ASSERT_EQUAL (mActualContextAfterException.Rdx, mExpectedContextAfterException.Rdx);
+  UT_ASSERT_EQUAL (mActualContextAfterException.Rcx, mExpectedContextAfterException.Rcx);
+  UT_ASSERT_EQUAL (mActualContextAfterException.Rax, mExpectedContextAfterException.Rax);
+  UT_ASSERT_EQUAL (mActualContextAfterException.R8, mExpectedContextAfterException.R8);
+  UT_ASSERT_EQUAL (mActualContextAfterException.R9, mExpectedContextAfterException.R9);
+  UT_ASSERT_EQUAL (mActualContextAfterException.R10, mExpectedContextAfterException.R10);
+  UT_ASSERT_EQUAL (mActualContextAfterException.R11, mExpectedContextAfterException.R11);
+  UT_ASSERT_EQUAL (mActualContextAfterException.R12, mExpectedContextAfterException.R12);
+  UT_ASSERT_EQUAL (mActualContextAfterException.R13, mExpectedContextAfterException.R13);
+  UT_ASSERT_EQUAL (mActualContextAfterException.R14, mExpectedContextAfterException.R14);
+  UT_ASSERT_EQUAL (mActualContextAfterException.R15, mExpectedContextAfterException.R15);
+  return UNIT_TEST_PASSED;
+}
+
+/**
+  Special handler for CpuStackGuard test case.
+
+  @param ExceptionType  Exception type.
+  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.
+
+**/
+VOID
+EFIAPI
+CpuStackGuardExceptionHandler (
+  IN EFI_EXCEPTION_TYPE  ExceptionType,
+  IN EFI_SYSTEM_CONTEXT  SystemContext
+  )
+{
+  UINTN  LocalVariable;
+
+  AdjustRipForFaultHandler (ExceptionType, SystemContext);
+  mRspAddress[0] = (UINTN)SystemContext.SystemContextX64->Rsp;
+  mRspAddress[1] = (UINTN)(&LocalVariable);
+
+  return;
+}
diff --git a/UefiCpuPkg/CpuExceptionHandlerUnitTest/X64/ArchExceptionHandlerTestAsm.nasm b/UefiCpuPkg/CpuExceptionHandlerUnitTest/X64/ArchExceptionHandlerTestAsm.nasm
new file mode 100644
index 0000000000..e229dbed00
--- /dev/null
+++ b/UefiCpuPkg/CpuExceptionHandlerUnitTest/X64/ArchExceptionHandlerTestAsm.nasm
@@ -0,0 +1,256 @@
+;------------------------------------------------------------------------------
+;
+; Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
+; SPDX-License-Identifier: BSD-2-Clause-Patent
+;
+; Module Name:
+;
+;   ArchExceptionHandlerTestAsm.nasm
+;
+; Abstract:
+;
+;   x64 CPU Exception Handler Lib Unit test
+;
+;------------------------------------------------------------------------------
+
+    DEFAULT REL
+    SECTION .text
+
+struc GENERAL_REGISTER
+  .Rdi:    resq    1
+  .Rsi:    resq    1
+  .Rbx:    resq    1
+  .Rdx:    resq    1
+  .Rcx:    resq    1
+  .Rax:    resq    1
+  .R8:     resq    1
+  .R9:     resq    1
+  .R10:    resq    1
+  .R11:    resq    1
+  .R12:    resq    1
+  .R13:    resq    1
+  .R14:    resq    1
+  .R15:    resq    1
+
+endstruc
+
+extern ASM_PFX(mExpectedContextInHandler)
+extern ASM_PFX(mActualContextAfterException)
+extern ASM_PFX(mFaultInstructionLength)
+
+;------------------------------------------------------------------------------
+; VOID
+; EFIAPI
+; TriggerGPException (
+;  UINTN  Cr4ReservedBit
+;  );
+;------------------------------------------------------------------------------
+global ASM_PFX(TriggerGPException)
+ASM_PFX(TriggerGPException):
+    ;
+    ; Set reserved bit 15 of cr4 to 1
+    ;
+    push rcx
+    lea  rcx, [ASM_PFX(mFaultInstructionLength)]
+    mov  qword[rcx], TriggerGPExceptionAfter - TriggerGPExceptionBefore
+    pop  rcx
+TriggerGPExceptionBefore:
+    mov  cr4, rcx
+TriggerGPExceptionAfter:
+    ret
+
+;------------------------------------------------------------------------------
+; VOID
+; EFIAPI
+; TriggerPFException (
+;  UINTN  PFAddress
+;  );
+;------------------------------------------------------------------------------
+global ASM_PFX(TriggerPFException)
+ASM_PFX(TriggerPFException):
+    push rcx
+    lea  rcx, [ASM_PFX(mFaultInstructionLength)]
+    mov  qword[rcx], TriggerPFExceptionAfter - TriggerPFExceptionBefore
+    pop  rcx
+TriggerPFExceptionBefore:
+    mov  qword[rcx], 0x1
+TriggerPFExceptionAfter:
+    ret
+
+;------------------------------------------------------------------------------
+; ModifyRcxInGlobalBeforeException;
+; This function is writed by assebly code because it's only called in this file.
+; It's used to set Rcx in mExpectedContextInHandler for different exception.
+;------------------------------------------------------------------------------
+global ASM_PFX(ModifyRcxInGlobalBeforeException)
+ASM_PFX(ModifyRcxInGlobalBeforeException):
+    push rax
+    lea  rax, [ASM_PFX(mExpectedContextInHandler)]
+    mov  [rax + GENERAL_REGISTER.Rcx], rcx
+    pop  rax
+    ret
+
+;------------------------------------------------------------------------------
+;VOID
+;EFIAPI
+;AsmTestConsistencyOfCpuContext (
+;  IN  EFI_EXCEPTION_TYPE ExceptionType
+;  IN  UINTN              FaultParameter   OPTIONAL
+;  );
+;------------------------------------------------------------------------------
+global ASM_PFX(AsmTestConsistencyOfCpuContext)
+ASM_PFX(AsmTestConsistencyOfCpuContext):
+    ;
+    ; Push original register
+    ;
+    push r15
+    push r14
+    push r13
+    push r12
+    push r11
+    push r10
+    push r9
+    push r8
+    push rax
+    push rcx
+    push rbx
+    push rsi
+    push rdi
+    push rdx
+    push rdx
+
+    ;
+    ; Modify registers to mExpectedContextInHandler. Do not handle Rsp and Rbp.
+    ; CpuExceptionHandlerLib doesn't set Rsp and Rsp register to the value in SystemContext.
+    ;
+    lea r15, [ASM_PFX(mExpectedContextInHandler)]
+    mov rdi, [r15 + GENERAL_REGISTER.Rdi]
+    mov rsi, [r15 + GENERAL_REGISTER.Rsi]
+    mov rbx, [r15 + GENERAL_REGISTER.Rbx]
+    mov rdx, [r15 + GENERAL_REGISTER.Rdx]
+    mov rax, [r15 + GENERAL_REGISTER.Rax]
+    mov r8,  [r15 + GENERAL_REGISTER.R8]
+    mov r9,  [r15 + GENERAL_REGISTER.R9]
+    mov r10, [r15 + GENERAL_REGISTER.R10]
+    mov r11, [r15 + GENERAL_REGISTER.R11]
+    mov r12, [r15 + GENERAL_REGISTER.R12]
+    mov r13, [r15 + GENERAL_REGISTER.R13]
+    mov r14, [r15 + GENERAL_REGISTER.R14]
+    mov r15, [r15 + GENERAL_REGISTER.R15]
+
+    cmp  rcx, 0xd
+    jz   GPException
+    cmp  rcx, 0xe
+    jz   PFException
+    jmp  INTnException
+
+PFException:
+    pop  rcx                                       ; Pop rdx(PFAddress) to rcx.
+    call ASM_PFX(ModifyRcxInGlobalBeforeException) ; Set mExpectedContextInHandler.Rcx to PFAddress.
+    call ASM_PFX(TriggerPFException)
+    jmp  AfterException
+
+GPException:
+    pop  rcx                                       ; Pop rdx(Cr4ReservedBit) to rcx.
+    call ASM_PFX(ModifyRcxInGlobalBeforeException) ; Set mExpectedContextInHandler.Rcx to Cr4ReservedBit.
+    call ASM_PFX(TriggerGPException)
+    jmp  AfterException
+
+INTnException:
+    ;
+    ; Modify Rcx in mExpectedContextInHandler.
+    ;
+    add  Rsp, 8                                    ; Discard the extra Rdx in stack. Rcx is ExceptionType now.
+    call ASM_PFX(ModifyRcxInGlobalBeforeException) ; Set mExpectedContextInHandler.Rcx to ExceptionType.
+    call ASM_PFX(TriggerINTnException)
+
+AfterException:
+    ;
+    ; Save registers in mActualContextAfterException
+    ;
+    push rax
+    lea  rax, [ASM_PFX(mActualContextAfterException)]
+    mov  [rax + GENERAL_REGISTER.Rdi], rdi
+    mov  [rax + GENERAL_REGISTER.Rsi], rsi
+    mov  [rax + GENERAL_REGISTER.Rbx], rbx
+    mov  [rax + GENERAL_REGISTER.Rdx], rdx
+    mov  [rax + GENERAL_REGISTER.Rcx], rcx
+    pop  rcx
+    mov  [rax + GENERAL_REGISTER.Rax], rcx
+    mov  [rax + GENERAL_REGISTER.R8],  r8
+    mov  [rax + GENERAL_REGISTER.R9],  r9
+    mov  [rax + GENERAL_REGISTER.R10], r10
+    mov  [rax + GENERAL_REGISTER.R11], r11
+    mov  [rax + GENERAL_REGISTER.R12], r12
+    mov  [rax + GENERAL_REGISTER.R13], r13
+    mov  [rax + GENERAL_REGISTER.R14], r14
+    mov  [rax + GENERAL_REGISTER.R15], r15
+
+    ;
+    ; restore original register
+    ;
+    pop rdx
+    pop rdi
+    pop rsi
+    pop rbx
+    pop rcx
+    pop rax
+    pop r8
+    pop r9
+    pop r10
+    pop r11
+    pop r12
+    pop r13
+    pop r14
+    pop r15
+
+    ret
+
+;------------------------------------------------------------------------------
+; VOID
+; EFIAPI
+; TriggerStackOverflow (
+;  VOID
+;  );
+;------------------------------------------------------------------------------
+global ASM_PFX(TriggerStackOverflow)
+ASM_PFX(TriggerStackOverflow):
+    push rcx
+    lea  rcx, [ASM_PFX(mFaultInstructionLength)]
+    mov  qword[rcx], TriggerCpuStackGuardAfter - TriggerCpuStackGuardBefore
+    pop  rcx
+TriggerCpuStackGuardBefore:
+    call TriggerCpuStackGuardBefore
+TriggerCpuStackGuardAfter:
+    ret
+
+;------------------------------------------------------------------------------
+; VOID
+; EFIAPI
+; TriggerINTnException (
+;  IN  EFI_EXCEPTION_TYPE ExceptionType
+;  );
+;------------------------------------------------------------------------------
+global ASM_PFX(TriggerINTnException)
+ASM_PFX(TriggerINTnException):
+    push rax
+    push rdx
+    push rcx
+    lea  rax, [AsmTriggerException1 - AsmTriggerException0]
+    mul  rcx
+    mov  rcx, AsmTriggerException0
+    add  rax, rcx
+    pop  rcx
+    pop  rdx
+    jmp  rax
+    ;
+    ; rax = AsmTriggerException0 + (AsmTriggerException1 - AsmTriggerException0) * rcx
+    ;
+%assign Vector 0
+%rep  22
+AsmTriggerException %+ Vector:
+    pop rax
+    INT Vector
+    ret
+%assign Vector Vector+1
+%endrep
-- 
2.31.1.windows.1


  reply	other threads:[~2022-10-14  9:20 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-10-14  9:19 [Patch V3 0/4] Add Unit tests for Pei/DxeCpuExceptionHandlerLib duntan
2022-10-14  9:19 ` duntan [this message]
2022-10-14 11:57   ` [Patch V3 1/4] UefiCpuPkg: Add Unit tests for DxeCpuExceptionHandlerLib Ni, Ray
2022-10-14  9:19 ` [Patch V3 2/4] UefiCpuPkg: Add Unit tests for PeiCpuExceptionHandlerLib duntan
2022-10-14 11:58   ` Ni, Ray
2022-10-14  9:19 ` [Patch V3 3/4] UefiCpuPkg: Add Pei/DxeCpuExceptionHandlerLibUnitTest in dsc duntan
2022-10-14  9:19 ` [Patch V3 4/4] UefiCpuPkg: Add CpuExceptionHandlerTest.h in ECC IgnoreFile duntan
2022-10-14  9:39   ` Ni, Ray
2022-10-14 10:01     ` duntan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20221014091931.847-2-dun.tan@intel.com \
    --to=devel@edk2.groups.io \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox