HyperDbg/hyperdbg/hypertrace/code/lbr/Lbr.c
2026-05-31 16:38:21 +02:00

1266 lines
33 KiB
C

/**
* @file Lbr.c
* @author Hari Mishal (harimishal6@gmail.com)
* @brief Last Branch Record (LBR) tracing implementation for HyperTrace module
* @details Modified from LIBIHT project (Thomasaon Zhao et al) with Windows style updates.
* @version 0.18
* @date 2025-12-02
*
* @copyright This project is released under the GNU Public License v3.
*/
#include "pch.h"
//////////////////////////////////////////////////
// Global Definitions //
//////////////////////////////////////////////////
//
// Typical Intel LBR capacities based on CPU model
// This is a subset; you can expand this as needed
//
CPU_LBR_MAP CPU_LBR_MAPS[] = {
{0x5c, 32},
{0x5f, 32},
{0x4e, 32},
{0x5e, 32},
{0x8e, 32},
{0x9e, 32},
{0x55, 32},
{0x66, 32},
{0x7a, 32},
{0x67, 32},
{0x6a, 32},
{0x6c, 32},
{0x7d, 32},
{0x7e, 32},
{0x8c, 32},
{0x8d, 32},
{0xa5, 32},
{0xa6, 32},
{0xa7, 32},
{0xa8, 32},
{0x86, 32},
{0x8a, 32},
{0x96, 32},
{0x9c, 32},
{0x3d, 16},
{0x47, 16},
{0x4f, 16},
{0x56, 16},
{0x3c, 16},
{0x45, 16},
{0x46, 16},
{0x3f, 16},
{0x2a, 16},
{0x2d, 16},
{0x3a, 16},
{0x3e, 16},
{0x1a, 16},
{0x1e, 16},
{0x1f, 16},
{0x2e, 16},
{0x25, 16},
{0x2c, 16},
{0x2f, 16},
{0x17, 4},
{0x1d, 4},
{0x0f, 4},
{0x37, 8},
{0x4a, 8},
{0x4c, 8},
{0x4d, 8},
{0x5a, 8},
{0x5d, 8},
{0x1c, 8},
{0x26, 8},
{0x27, 8},
{0x35, 8},
{0x36, 8}};
/**
* @brief Check if the current CPU supports architectural LBR
*
* @return BOOLEAN
*/
BOOLEAN
LbrCheckAndReadArchitecturalLbrDetails()
{
ULONG a, b, c, d;
CPUID_EAX_07 Edx07 = {0};
CPUID28_EAX Eax1c = {0};
CPUID28_EBX Ebx1c = {0};
CPUID28_ECX Ecx1c = {0};
//
// Check for Architectural LBR support
//
//
xcpuidex(CPUID_STRUCTURED_EXTENDED_FEATURE_FLAGS, 0x00, &a, &b, &c, &d);
Edx07.Edx.AsUInt = d;
//
// CPUID.07H.00H:EDX[19] == 1 means arch LBR is supported
//
if (Edx07.Edx.AsUInt & (1 << 19))
{
g_ArchBasedLastBranchRecord = TRUE;
}
else
{
//
// Architectural LBR is not supported by the CPU
//
g_ArchBasedLastBranchRecord = FALSE;
return FALSE;
}
//
// Being here means the CPU supports architectural LBR, we can read the LBR capabilities from CPUID 0x1c leaf
//
xcpuidex(CPUID_ARCH_LAST_BRANCH_RECORD_INFORMATION, 0x00, &a, &b, &c, &d);
//
// Assign LBR leafs to structure for easier access
//
Eax1c.AsUInt = a;
Ebx1c.AsUInt = b;
Ecx1c.AsUInt = c;
//
// Store the CPUID.1CH leaf information in a global structure for later use
//
g_Cpuid28Leafs.Eax = Eax1c;
g_Cpuid28Leafs.Ebx = Ebx1c;
g_Cpuid28Leafs.Ecx = Ecx1c;
g_Cpuid28Leafs.Edx = d;
//
// Read LBR capacity from CPUID.1CH.00H:EAX[7:0]
// Based on Intel SDM: For each bit n set in this field, the IA32_LBR_DEPTH.DEPTH value 8 * (n + 1) is supported
//
if (Eax1c.LbrDepthMask)
{
//
// Get the highest set bit in LbrDepthMask to determine the maximum supported LBR depth
//
ULONG HighestSetBit = 0;
for (ULONG i = 0; i < 8; i++)
{
if (Eax1c.LbrDepthMask & (1 << i))
{
HighestSetBit = i;
}
}
g_LbrCapacity = 8 * (HighestSetBit + 1);
}
else
{
//
// If LbrDepthMask is 0, it means the CPU supports architectural LBR but does not specify the depth, we can assume a default value (e.g., 16 or 32) or treat it as unsupported
//
g_LbrCapacity = MAXIMUM_LBR_CAPACITY; // Assuming a default capacity of MAXIMUM_LBR_CAPACITY if not specified
}
return TRUE;
}
/**
* @brief Check if the current CPU supports LBR by examining the CPU family and model and looking up the corresponding LBR capacity
*
* @return BOOLEAN
*/
BOOLEAN
LbrCheckAndReadLegacyLbrDetails()
{
ULONG a, b, c, d;
ULONG Family, Model;
ULONGLONG i;
xcpuid(1, &a, &b, &c, &d);
Family = ((a >> 8) & 0xF) + ((a >> 20) & 0xFF);
Model = ((a >> 4) & 0xF) | ((a >> 12) & 0xF0);
for (i = 0; i < sizeof(CPU_LBR_MAPS) / sizeof(CPU_LBR_MAPS[0]); ++i)
{
if (Model == CPU_LBR_MAPS[i].Model)
{
g_LbrCapacity = CPU_LBR_MAPS[i].LbrCapacity;
break;
}
}
if (g_LbrCapacity == 0)
{
return FALSE;
}
return TRUE;
}
/**
* @brief Check and adjust compatibility of LBR filterings for ARCH based LBR
*
* @return VOID
*/
VOID
LbrAdjustArchBasedFilteringCompatibility(IA32_LBR_CTL_REGISTER * Ia32LbrCtl)
{
//
// Check capabilities and apply necessary adjustments based on CPU support
//
if (!g_Cpuid28Leafs.Ebx.LbrCpl)
{
//
// If CPL filtering is not supported
// we cannot filter kernel vs user mode branches, so we clear those filter bits to avoid confusion
//
Ia32LbrCtl->Bits.OS = 0;
Ia32LbrCtl->Bits.USR = 0;
}
if (!g_Cpuid28Leafs.Ebx.LbrFilter)
{
//
// If branch filtering is not supported, we cannot filter by branch type, so we clear all the specific branch type filter bits
//
Ia32LbrCtl->Bits.JCC = 0;
Ia32LbrCtl->Bits.NearRelJmp = 0;
Ia32LbrCtl->Bits.NearIndJmp = 0;
Ia32LbrCtl->Bits.NearRelCall = 0;
Ia32LbrCtl->Bits.NearIndCall = 0;
Ia32LbrCtl->Bits.NearRet = 0;
Ia32LbrCtl->Bits.OtherBranch = 0;
}
if (!g_Cpuid28Leafs.Ebx.LbrCallStack)
{
//
// If call-stack mode is not supported, we cannot filter by call stack, so we clear that filter bit
//
Ia32LbrCtl->Bits.CallStack = 0;
}
}
/**
* @brief Convert a filter options bitmask into IA32_LBR_CTL format for architectural LBR
*
* @param FilterOptions A bitmask of filter options (e.g., LBR_KERNEL, LBR_USER, LBR_JCC, etc.)
* @param Ia32LbrCtl Pointer to the IA32_LBR_CTL_REGISTER to populate with the converted filter bits
* @return VOID
*/
VOID
LbrBuildArchBasedFilterOptions(UINT64 FilterOptions, IA32_LBR_CTL_REGISTER * Ia32LbrCtl)
{
//
// By default, we are enabling all of them
//
Ia32LbrCtl->Bits.OS = 1;
Ia32LbrCtl->Bits.USR = 1;
Ia32LbrCtl->Bits.JCC = 1;
Ia32LbrCtl->Bits.NearRelCall = 1;
Ia32LbrCtl->Bits.NearIndCall = 1;
Ia32LbrCtl->Bits.NearRet = 1;
Ia32LbrCtl->Bits.NearIndJmp = 1;
Ia32LbrCtl->Bits.NearRelJmp = 1;
Ia32LbrCtl->Bits.OtherBranch = 1;
Ia32LbrCtl->Bits.CallStack = 1;
//
// Perform filtering the results
//
if (FilterOptions & LBR_KERNEL)
{
Ia32LbrCtl->Bits.OS = 0;
}
if (FilterOptions & LBR_USER)
{
Ia32LbrCtl->Bits.USR = 0;
}
if (FilterOptions & LBR_JCC)
{
Ia32LbrCtl->Bits.JCC = 0;
}
if (FilterOptions & LBR_REL_CALL)
{
Ia32LbrCtl->Bits.NearRelCall = 0;
}
if (FilterOptions & LBR_IND_CALL)
{
Ia32LbrCtl->Bits.NearIndCall = 0;
}
if (FilterOptions & LBR_RETURN)
{
Ia32LbrCtl->Bits.NearRet = 0;
}
if (FilterOptions & LBR_IND_JMP)
{
Ia32LbrCtl->Bits.NearIndJmp = 0;
}
if (FilterOptions & LBR_REL_JMP)
{
Ia32LbrCtl->Bits.NearRelJmp = 0;
}
if (FilterOptions & LBR_FAR_OTHER_BRANCHES)
{
Ia32LbrCtl->Bits.OtherBranch = 0;
}
if (FilterOptions & LBR_CALL_STACK)
{
Ia32LbrCtl->Bits.CallStack = 0;
}
//
// Adjust LBR fitlering options based on the CPU capabilities to ensure we are not setting unsupported filter bits
//
LbrAdjustArchBasedFilteringCompatibility(Ia32LbrCtl);
}
/**
* @brief Set the LBR select filter MSR (MSR_LEGACY_LBR_SELECT) for legacy LBR,
* dispatching via VMCALL when in VMX non-root mode
*
* @details This function is only relevant for legacy LBR. Architectural LBR encodes
* filter options directly in IA32_LBR_CTL and does not use MSR_LEGACY_LBR_SELECT.
* Note: Even though MSR_LEGACY_LBR_SELECT is a plain MSR (not a VMCS field), it
* must be set from VMX-root mode for the LBR MSRs to work correctly.
*
* @param FilterOptions The raw filter options bitmask to write into MSR_LEGACY_LBR_SELECT
* @return VOID
*/
VOID
LbrSetLbrSelectFilter(UINT64 FilterOptions)
{
BOOLEAN IsOnVmxRootMode;
if (g_RunningOnHypervisorEnvironment)
{
IsOnVmxRootMode = g_Callbacks.VmFuncVmxGetCurrentExecutionMode();
//
// If we don't set it on VMX-root mode, the LBR MSRs won't work based on our tests
// even though it is just a plain MSR (and not a VMCS field)
//
if (IsOnVmxRootMode)
{
//
// It is on VMX-root mode, run it directly to set the MSR_LEGACY_LBR_SELECT MSR value
//
g_Callbacks.VmFuncSetLbrSelect(FilterOptions);
}
else
{
//
// It is not on VMX-root mode, so we need to perform a VMCALL to set the MSR_LEGACY_LBR_SELECT MSR value on the target core
//
g_Callbacks.VmFuncSetLbrSelectVmcallOnTargetCore(FilterOptions);
}
}
else
{
xwrmsr(MSR_LEGACY_LBR_SELECT, FilterOptions);
}
}
/**
* @brief Zero out the LBR hardware state (TOS and all from/to MSR pairs)
*
* @details For architectural LBR the TOS index is not maintained by software and the CPU
* shifts entries automatically, so MSR_LBR_TOS is skipped in that case.
*
* @return VOID
*/
VOID
LbrClearHardwareState()
{
//
// For architectural LBR, the TOS is not used; the CPU shifts LBR entries automatically
// and does not update the TOS index
//
if (!g_ArchBasedLastBranchRecord)
{
xwrmsr(MSR_LBR_TOS, 0);
}
//
// Clearing LBR TO and FROM MSRs
//
for (ULONG i = 0; i < (ULONG)g_LbrCapacity; i++)
{
if (g_ArchBasedLastBranchRecord)
{
xwrmsr(IA32_LBR_0_FROM_IP + i, 0);
xwrmsr(IA32_LBR_0_TO_IP + i, 0);
}
else
{
xwrmsr(MSR_LASTBRANCH_0_FROM_IP + i, 0);
xwrmsr(MSR_LASTBRANCH_0_TO_IP + i, 0);
}
}
}
/**
* @brief Enable LBR collection on architectural (ARCH) based LBR
*
* @param Ia32LbrCtl Pointer to the IA32_LBR_CTL_REGISTER whose LBREn bit will be set
* @return VOID
*/
VOID
LbrEnableArchBased(IA32_LBR_CTL_REGISTER * Ia32LbrCtl)
{
Ia32LbrCtl->Bits.LBREn = 1;
}
/**
* @brief Enable LBR collection on legacy based LBR
*
* @param DbgCtlMsr Pointer to the IA32_DEBUGCTL MSR value to be modified
* @return VOID
*/
VOID
LbrEnableLegacyBased(ULONGLONG * DbgCtlMsr)
{
//
// Enable LBR and CLEAR 'Freeze LBRs on PMI' (Bit 11)
// If Bit 11 is set, the LBR stops recording as soon as a single PMI interrupt fires
//
*DbgCtlMsr |= IA32_DEBUGCTL_LBR_FLAG; // Bit 0 = 1
*DbgCtlMsr &= ~(1ULL << IA32_DEBUGCTL_FREEZE_LBRS_ON_PMI_BIT); // Bit 11 = 0
}
/**
* @brief Start LBR collection when running natively (outside any hypervisor environment)
*
* @param Ia32LbrCtl The pre-built IA32_LBR_CTL_REGISTER value carrying the filter bits (arch LBR)
* @return VOID
*/
VOID
LbrStartOnNativeMode(IA32_LBR_CTL_REGISTER Ia32LbrCtl)
{
ULONGLONG DbgCtlMsr = 0;
if (g_ArchBasedLastBranchRecord)
{
//
// No need to read the existing control since we are replacing it entirely for ARCH LBR
//
LbrEnableArchBased(&Ia32LbrCtl);
xwrmsr(IA32_LBR_CTL, Ia32LbrCtl.AsUInt);
}
else
{
xrdmsr(IA32_DEBUGCTL, &DbgCtlMsr);
LbrEnableLegacyBased(&DbgCtlMsr);
xwrmsr(IA32_DEBUGCTL, DbgCtlMsr);
}
}
/**
* @brief Read current LBR control register values while in VMX root-mode
*
* @param DbgCtlMsr Pointer to receive the IA32_DEBUGCTL MSR value (legacy LBR)
* @param Ia32LbrCtl Pointer to receive the IA32_LBR_CTL register value (arch LBR)
* @return VOID
*/
VOID
LbrGetValuesOnVmxRootMode(ULONGLONG * DbgCtlMsr, IA32_LBR_CTL_REGISTER * Ia32LbrCtl)
{
if (g_ArchBasedLastBranchRecord)
{
//
// It is on VMX-root mode, run it directly to get the IA32_LBR_CTL value in VMCS
//
Ia32LbrCtl->AsUInt = g_Callbacks.VmFuncGetGuestIa32LbrCtl();
}
else
{
//
// It is on VMX-root mode, run it directly to get the IA32_DEBUGCTL MSR value in VMCS
//
*DbgCtlMsr = g_Callbacks.VmFuncGetDebugctl();
}
}
/**
* @brief Read current LBR control register values while in VMX non-root mode (via VMCALL)
*
* @param DbgCtlMsr Pointer to receive the IA32_DEBUGCTL MSR value (legacy LBR)
* @param Ia32LbrCtl Pointer to receive the IA32_LBR_CTL register value (arch LBR)
* @return VOID
*/
VOID
LbrGetValuesOnVmxNonRootMode(ULONGLONG * DbgCtlMsr, IA32_LBR_CTL_REGISTER * Ia32LbrCtl)
{
if (g_ArchBasedLastBranchRecord)
{
//
// It is not on VMX-root mode, so we need to perform a VMCALL to get the IA32_LBR_CTL value on the target core
//
Ia32LbrCtl->AsUInt = g_Callbacks.VmFuncGetGuestIa32LbrCtlVmcallOnTargetCore();
}
else
{
//
// It is not on VMX-root mode, so we need to perform a VMCALL to get the IA32_DEBUGCTL MSR value on the target core
//
*DbgCtlMsr = g_Callbacks.VmFuncGetDebugctlVmcallOnTargetCore();
}
}
/**
* @brief Disable LBR collection on architectural (ARCH) based LBR
*
* @param Ia32LbrCtl Pointer to the IA32_LBR_CTL register whose LBREn bit will be cleared
* @return VOID
*/
VOID
LbrDisableArchBased(IA32_LBR_CTL_REGISTER * Ia32LbrCtl)
{
Ia32LbrCtl->Bits.LBREn = 0;
}
/**
* @brief Disable LBR collection on legacy based LBR
*
* @param DbgCtlMsr Pointer to the IA32_DEBUGCTL MSR value whose LBR flag will be cleared
* @return VOID
*/
VOID
LbrDisableLegacyBased(ULONGLONG * DbgCtlMsr)
{
*DbgCtlMsr &= ~IA32_DEBUGCTL_LBR_FLAG;
}
/**
* @brief Write back modified LBR control register values while in VMX root-mode
*
* @param DbgCtlMsr The updated IA32_DEBUGCTL MSR value to apply (legacy LBR)
* @param Ia32LbrCtl The updated IA32_LBR_CTL register value to apply (arch LBR)
* @return VOID
*/
VOID
LbrSetValuesOnVmxRootMode(ULONGLONG DbgCtlMsr, IA32_LBR_CTL_REGISTER Ia32LbrCtl)
{
if (g_ArchBasedLastBranchRecord)
{
//
// It is on VMX-root mode, run it directly to set the IA32_LBR_CTL value in VMCS
//
g_Callbacks.VmFuncSetGuestIa32LbrCtl(Ia32LbrCtl.AsUInt);
}
else
{
//
// It is on VMX-root mode, run it directly to set the IA32_DEBUGCTL MSR value in VMCS
//
g_Callbacks.VmFuncSetDebugctl(DbgCtlMsr);
}
}
/**
* @brief Write back modified LBR control register values while in VMX non-root mode (via VMCALL)
*
* @param DbgCtlMsr The updated IA32_DEBUGCTL MSR value to apply (legacy LBR)
* @param Ia32LbrCtl The updated IA32_LBR_CTL register value to apply (arch LBR)
* @return VOID
*/
VOID
LbrSetValuesOnVmxNonRootMode(ULONGLONG DbgCtlMsr, IA32_LBR_CTL_REGISTER Ia32LbrCtl)
{
if (g_ArchBasedLastBranchRecord)
{
//
// It is not on VMX-root mode, so we need to perform a VMCALL to set the IA32_LBR_CTL value on the target core
//
g_Callbacks.VmFuncSetGuestIa32LbrCtlVmcallOnTargetCore(Ia32LbrCtl.AsUInt);
}
else
{
//
// It is not on VMX-root mode, so we need to perform a VMCALL to set the IA32_DEBUGCTL MSR value on the target core
//
g_Callbacks.VmFuncSetDebugctlVmcallOnTargetCore(DbgCtlMsr);
}
}
/**
* @brief Stop LBR collection when running natively (outside any hypervisor environment)
*
* @return VOID
*/
VOID
LbrStopOnNativeMode()
{
ULONGLONG DbgCtlMsr = NULL64_ZERO;
IA32_LBR_CTL_REGISTER Ia32LbrCtl = {0};
if (g_ArchBasedLastBranchRecord)
{
xrdmsr(IA32_LBR_CTL, &Ia32LbrCtl.AsUInt);
LbrDisableArchBased(&Ia32LbrCtl);
xwrmsr(IA32_LBR_CTL, Ia32LbrCtl);
}
else
{
xrdmsr(IA32_DEBUGCTL, &DbgCtlMsr);
LbrDisableLegacyBased(&DbgCtlMsr);
xwrmsr(IA32_DEBUGCTL, DbgCtlMsr);
}
}
/**
* @brief Zero the ARCH LBR control register while in VMX root-mode
*
* @details Setting IA32_LBR_CTL to 0 simultaneously clears all filter bits and
* disables collection (LBREn = 0), which is the correct flush state.
*
* @return VOID
*/
VOID
LbrResetArchControlOnVmxRootMode()
{
//
// It is on VMX-root mode, set the entire IA32_LBR_CTL to 0 directly in VMCS
// (this also disables LBR since LBREn is cleared)
//
g_Callbacks.VmFuncSetGuestIa32LbrCtl(0);
}
/**
* @brief Zero the ARCH LBR control register while in VMX non-root mode (via VMCALL)
*
* @details Setting IA32_LBR_CTL to 0 simultaneously clears all filter bits and
* disables collection (LBREn = 0), which is the correct flush state.
*
* @return VOID
*/
VOID
LbrResetArchControlOnVmxNonRootMode()
{
//
// It is not on VMX-root mode, perform a VMCALL to set the entire IA32_LBR_CTL to 0 on the target core
// (this also disables LBR since LBREn is cleared)
//
g_Callbacks.VmFuncSetGuestIa32LbrCtlVmcallOnTargetCore(0);
}
/**
* @brief Reset the LBR control registers to zero, covering both ARCH and legacy LBR
* across all execution environments (native, VMX root-mode, VMX non-root mode)
*
* @details For architectural LBR, IA32_LBR_CTL is zeroed entirely (clearing filter bits
* and disabling collection in one write). For legacy LBR, MSR_LEGACY_LBR_SELECT
* is zeroed to reset the branch filter to its default capture-all state.
* Note: Even though MSR_LEGACY_LBR_SELECT is a plain MSR (not a VMCS field),
* it must be written from VMX-root mode for the change to take effect.
*
* @return VOID
*/
VOID
LbrResetControlRegisters()
{
BOOLEAN IsOnVmxRootMode;
if (g_RunningOnHypervisorEnvironment)
{
IsOnVmxRootMode = g_Callbacks.VmFuncVmxGetCurrentExecutionMode();
//
// If we don't set it on VMX-root mode, the LBR MSRs won't work based on our tests
// even though it is just a plain MSR (and not a VMCS field)
//
if (IsOnVmxRootMode)
{
if (g_ArchBasedLastBranchRecord)
{
LbrResetArchControlOnVmxRootMode();
}
else
{
//
// Reuse LbrSetLbrSelectFilter: writing 0 resets MSR_LEGACY_LBR_SELECT to
// its default state (capture all branch types) from VMX-root mode
//
g_Callbacks.VmFuncSetLbrSelect(0);
}
}
else
{
if (g_ArchBasedLastBranchRecord)
{
LbrResetArchControlOnVmxNonRootMode();
}
else
{
//
// Reuse LbrSetLbrSelectFilter: writing 0 resets MSR_LEGACY_LBR_SELECT to
// its default state (capture all branch types) via VMCALL on the target core
//
g_Callbacks.VmFuncSetLbrSelectVmcallOnTargetCore(0);
}
}
}
else
{
if (g_ArchBasedLastBranchRecord)
{
xwrmsr(IA32_LBR_CTL, 0);
}
else
{
xwrmsr(MSR_LEGACY_LBR_SELECT, 0);
}
}
}
/**
* @brief Adjust filter options for call stack
*
* @param FilterOptions A bitmask of filter options
* @return VOID
*/
VOID
LbrAdjustFilterOptionsForCallStack(UINT64 * FilterOptions)
{
//
// Call-stack mode should be used with branch type enabling configured to capture only CALLs (NEAR_REL_CALL and
// NEAR_IND_CALL) and RETs (NEAR_RET). When configured in this manner, the LBR array emulates a call stack,
// where CALLs are "pushed" and RETs "pop" them off the stack. If other branch types (JCC, NEAR_*_JMP, or
// OTHER_BRANCH) are enabled for recording with call-stack mode, LBR behavior may be undefined, so we will
// mask out any branch type filters that are not CALLs or RETs when call-stack mode is requested to ensure
// we are correctly emulating a call stack and avoiding undefined behavior
//
//
// If it is call_stack then we only keep the user and kernel bit and filter all branch types
// except calls and rets to ensure we are only capturing call stack profile
//
if (*FilterOptions & LBR_CALL_STACK)
{
//
// Preserve only the user/kernel privilege bits from the original options,
// then apply the mandatory call stack base flags
//
*FilterOptions = LBR_CALL_STACK_BASE_FLAGS | (*FilterOptions & (LBR_KERNEL | LBR_USER));
}
}
/**
* @brief Adjust filter options
*
* @param FilterOptions A bitmask of filter options
* @param Ia32LbrCtl Pointer to the IA32_LBR_CTL_REGISTER to adjust based on the filter options and CPU capabilities
*
* @return VOID
*/
VOID
LbrAdjustFilterOptions(UINT64 FilterOptions, IA32_LBR_CTL_REGISTER * Ia32LbrCtl)
{
//
// Adjust filter options in case of call_stack
//
LbrAdjustFilterOptionsForCallStack(&FilterOptions);
//
// Save the LBR filter options to a global variable for later use
//
g_LbrFilterOptions = FilterOptions;
//
// For architectural LBR, convert filter options into IA32_LBR_CTL bit fields
//
if (g_ArchBasedLastBranchRecord)
{
LbrBuildArchBasedFilterOptions(FilterOptions, Ia32LbrCtl);
}
//
// For legacy LBR, write the raw filter options to MSR_LEGACY_LBR_SELECT
// Architectural LBR encodes its filters in IA32_LBR_CTL and does not use this MSR
//
if (!g_ArchBasedLastBranchRecord)
{
LbrSetLbrSelectFilter(FilterOptions);
}
}
/**
* @brief Check if architectural LBR is enabled based on the IA32_LBR_CTL register value
*
* @param Ia32LbrCtl The IA32_LBR_CTL register value to inspect
* @return BOOLEAN
*/
BOOLEAN
LbrCheckArchBased(IA32_LBR_CTL_REGISTER Ia32LbrCtl)
{
return Ia32LbrCtl.Bits.LBREn ? TRUE : FALSE;
}
/**
* @brief Check if legacy LBR is enabled based on the IA32_DEBUGCTL MSR value
*
* @param DbgCtlMsr The IA32_DEBUGCTL MSR value to inspect
* @return BOOLEAN
*/
BOOLEAN
LbrCheckLegacyBased(ULONGLONG DbgCtlMsr)
{
return (DbgCtlMsr & IA32_DEBUGCTL_LBR_FLAG) ? TRUE : FALSE;
}
/**
* @brief Check if LBR is enabled while in VMX root-mode
*
* @return BOOLEAN
*/
BOOLEAN
LbrCheckOnVmxRootMode()
{
ULONGLONG DbgCtlMsr = 0;
IA32_LBR_CTL_REGISTER Ia32LbrCtl = {0};
if (g_ArchBasedLastBranchRecord)
{
Ia32LbrCtl.AsUInt = g_Callbacks.VmFuncGetGuestIa32LbrCtl();
return LbrCheckArchBased(Ia32LbrCtl);
}
else
{
DbgCtlMsr = g_Callbacks.VmFuncGetDebugctl();
return LbrCheckLegacyBased(DbgCtlMsr);
}
}
/**
* @brief Check if LBR is enabled while in native mode or VMX non-root mode
*
* @return BOOLEAN
*/
BOOLEAN
LbrCheckOnNativeOrVmxNonRootMode()
{
ULONGLONG DbgCtlMsr = 0;
IA32_LBR_CTL_REGISTER Ia32LbrCtl = {0};
if (g_ArchBasedLastBranchRecord)
{
xrdmsr(IA32_LBR_CTL, &Ia32LbrCtl.AsUInt);
return LbrCheckArchBased(Ia32LbrCtl);
}
else
{
xrdmsr(IA32_DEBUGCTL, &DbgCtlMsr);
return LbrCheckLegacyBased(DbgCtlMsr);
}
}
/**
* @brief Check if LBR is enabled or not
*
* @return BOOLEAN
*/
BOOLEAN
LbrCheck()
{
BOOLEAN IsOnVmxRootMode = FALSE;
if (g_RunningOnHypervisorEnvironment)
{
IsOnVmxRootMode = g_Callbacks.VmFuncVmxGetCurrentExecutionMode();
}
if (IsOnVmxRootMode)
{
return LbrCheckOnVmxRootMode();
}
else
{
return LbrCheckOnNativeOrVmxNonRootMode();
}
}
/**
* @brief Start collecting LBR branches
*
* @param FilterOptions A bitmask of filter options to apply to the LBR branches (e.g., filtering by branch type, privilege level, etc.)
*
* @return BOOLEAN
*/
BOOLEAN
LbrStart(UINT64 FilterOptions)
{
BOOLEAN IsOnVmxRootMode;
IA32_LBR_CTL_REGISTER Ia32LbrCtl = {0};
ULONGLONG DbgCtlMsr = 0;
if (g_LbrCapacity == 0)
{
LogInfo("Err, LBR aborting, CPU model not supported\n");
return FALSE;
}
//
// Adjust and set filter options
//
LbrAdjustFilterOptions(FilterOptions, &Ia32LbrCtl);
//
// Clear hardware state before enabling LBR
//
LbrClearHardwareState();
if (g_RunningOnHypervisorEnvironment)
{
IsOnVmxRootMode = g_Callbacks.VmFuncVmxGetCurrentExecutionMode();
//
// DEBUGCTL is not involved in ARCH LBR - it has its own dedicated control register
// So for ARCH LBR we skip reading the previous value and build the register from scratch (replacing it)
//
if (!g_ArchBasedLastBranchRecord)
{
if (IsOnVmxRootMode)
{
LbrGetValuesOnVmxRootMode(&DbgCtlMsr, &Ia32LbrCtl);
}
else
{
LbrGetValuesOnVmxNonRootMode(&DbgCtlMsr, &Ia32LbrCtl);
}
}
//
// Apply the appropriate bit to enable LBR based on whether it is architectural LBR or legacy LBR
//
if (g_ArchBasedLastBranchRecord)
{
LbrEnableArchBased(&Ia32LbrCtl);
}
else
{
LbrEnableLegacyBased(&DbgCtlMsr);
}
//
// Write the updated LBR control register values back based on the current VMX execution mode
//
if (IsOnVmxRootMode)
{
LbrSetValuesOnVmxRootMode(DbgCtlMsr, Ia32LbrCtl);
}
else
{
LbrSetValuesOnVmxNonRootMode(DbgCtlMsr, Ia32LbrCtl);
}
}
else
{
LbrStartOnNativeMode(Ia32LbrCtl);
}
return TRUE;
}
/**
* @brief Stop collecting LBR branches
*
* @return VOID
*/
VOID
LbrStop()
{
BOOLEAN IsOnVmxRootMode;
ULONGLONG DbgCtlMsr = NULL64_ZERO;
IA32_LBR_CTL_REGISTER Ia32LbrCtl = {0};
if (g_RunningOnHypervisorEnvironment)
{
IsOnVmxRootMode = g_Callbacks.VmFuncVmxGetCurrentExecutionMode();
//
// Read the current LBR control register values based on the current VMX execution mode
//
if (IsOnVmxRootMode)
{
LbrGetValuesOnVmxRootMode(&DbgCtlMsr, &Ia32LbrCtl);
}
else
{
LbrGetValuesOnVmxNonRootMode(&DbgCtlMsr, &Ia32LbrCtl);
}
//
// Apply the appropriate bit to disable LBR based on whether it is architectural LBR or legacy LBR
//
if (g_ArchBasedLastBranchRecord)
{
LbrDisableArchBased(&Ia32LbrCtl);
}
else
{
LbrDisableLegacyBased(&DbgCtlMsr);
}
//
// Write the updated LBR control register values back based on the current VMX execution mode
//
if (IsOnVmxRootMode)
{
LbrSetValuesOnVmxRootMode(DbgCtlMsr, Ia32LbrCtl);
}
else
{
LbrSetValuesOnVmxNonRootMode(DbgCtlMsr, Ia32LbrCtl);
}
}
else
{
LbrStopOnNativeMode();
}
}
/**
* @brief Flush LBR MSRs by disabling LBR and clearing all LBR entries
*
* @return VOID
*/
VOID
LbrFlush()
{
// LogInfo("Flush LBR on cpu core: %d\n", KeGetCurrentProcessorNumberEx(NULL));
//
// Stop LBR collection and save any remaining entries
//
LbrStop();
//
// Reset the LBR control registers to zero:
// - ARCH LBR: zeroes IA32_LBR_CTL entirely (clears filters and disables collection)
// - Legacy LBR: zeroes MSR_LEGACY_LBR_SELECT (resets branch filter to capture-all)
//
LbrResetControlRegisters();
//
// Clear all remaining hardware state (TOS for legacy LBR, and all from/to MSR pairs)
//
LbrClearHardwareState();
}
/**
* @brief Filter LBR branches based on the provided options
* @param FilterOptions A bitmask of filter options to apply to the LBR branches
*
* @return VOID
*/
VOID
LbrFilter(UINT64 FilterOptions)
{
// LogInfo("Updating LBR filter options: 0x%llx\n", FilterOptions);
//
// First, we flush the LBR to clear out any existing entries that may not meet the new filter criteria
//
LbrFlush();
//
// Then we apply the new filter options and re-enable LBR with the updated filter settings
//
LbrStart(FilterOptions);
}
/**
* @brief Save LBR branches
*
* @return VOID
*/
VOID
LbrSave()
{
UINT64 LbrTos;
LBR_STACK_ENTRY * State;
UINT32 CurrentCore = 0;
//
// Get the current core id
//
CurrentCore = KeGetCurrentProcessorNumberEx(NULL);
//
// Get the current processor LBR stack
//
State = &g_LbrStateList[CurrentCore];
//
// Read and store the current TOS index to know where the most recent branch is stored
// Note that there is no TOS index in ARCH LBR since everything is in order
//
if (!g_ArchBasedLastBranchRecord)
{
xrdmsr(MSR_LBR_TOS, &LbrTos);
State->Tos = (UINT8)LbrTos;
}
//
// Dump LBR entries into the current core's state structure
//
for (ULONG i = 0; i < (ULONG)g_LbrCapacity; i++)
{
if (g_ArchBasedLastBranchRecord)
{
xrdmsr(IA32_LBR_0_FROM_IP + i, &State->BranchEntry[i].From);
xrdmsr(IA32_LBR_0_TO_IP + i, &State->BranchEntry[i].To);
xrdmsr(IA32_LBR_0_INFO + i, &State->LastBranchInfo[i].AsUInt);
}
else
{
xrdmsr(MSR_LASTBRANCH_0_FROM_IP + i, &State->BranchEntry[i].From);
xrdmsr(MSR_LASTBRANCH_0_TO_IP + i, &State->BranchEntry[i].To);
xrdmsr(MSR_LASTBRANCH_INFO_0 + i, &State->LastBranchInfo[i].AsUInt);
}
}
}
/**
* @brief Get the branch type name based on the LBR branch type value (only applicable for architectural LBR)
*
* @details THIS FUNCTION IS ALSO IMPLEMENTED IN THE USER MODE
*
* @param BrType The raw branch type value from the LBR info MSR
* @param BrTypeName A character buffer to receive the branch type name string
*
* @return VOID
*/
VOID
LbrGetArchBranchType(UINT32 BrType, CHAR * BrTypeName)
{
if (BrType == LBR_BR_TYPE_COND)
{
strncpy(BrTypeName, "COND ", LBR_BR_TYPE_NAME_MAX_LEN);
}
else if (BrType == LBR_BR_TYPE_JMP_INDIRECT)
{
strncpy(BrTypeName, "JMP Indirect ", LBR_BR_TYPE_NAME_MAX_LEN);
}
else if (BrType == LBR_BR_TYPE_JMP_DIRECT)
{
strncpy(BrTypeName, "JMP Direct ", LBR_BR_TYPE_NAME_MAX_LEN);
}
else if (BrType == LBR_BR_TYPE_CALL_INDIRECT)
{
strncpy(BrTypeName, "CALL Indirect", LBR_BR_TYPE_NAME_MAX_LEN);
}
else if (BrType == LBR_BR_TYPE_CALL_DIRECT)
{
strncpy(BrTypeName, "CALL Direct ", LBR_BR_TYPE_NAME_MAX_LEN);
}
else if (BrType == LBR_BR_TYPE_RET)
{
strncpy(BrTypeName, "RET ", LBR_BR_TYPE_NAME_MAX_LEN);
}
else if (BrType >= LBR_BR_TYPE_RESERVED_MIN && BrType <= LBR_BR_TYPE_RESERVED_MAX)
{
strncpy(BrTypeName, "Reserved ", LBR_BR_TYPE_NAME_MAX_LEN);
}
else if (BrType >= LBR_BR_TYPE_OTHER_MIN && BrType <= LBR_BR_TYPE_OTHER_MAX)
{
strncpy(BrTypeName, "Other Branch ", LBR_BR_TYPE_NAME_MAX_LEN);
}
else
{
strncpy(BrTypeName, "Unknown ", LBR_BR_TYPE_NAME_MAX_LEN);
}
}
/**
* @brief Print collected LBR branches
*
* @return VOID
*/
VOID
LbrPrint()
{
ULONG CurrentIdx;
LBR_STACK_ENTRY * State;
UINT32 CurrentCore = 0;
CHAR BrTypeName[LBR_BR_TYPE_NAME_MAX_LEN] = {0};
UINT32 BrType = 0;
//
// Get the current core id
//
CurrentCore = KeGetCurrentProcessorNumberEx(NULL);
//
// Get the current processor LBR stack
//
State = &g_LbrStateList[CurrentCore];
Log("LBR Chronological Trace on core : 0x%x\n\n", CurrentCore);
for (ULONG i = 1; i <= g_LbrCapacity; i++)
{
if (g_ArchBasedLastBranchRecord)
{
//
// In ARCH LBR, there is not TOS index and everything is in order
//
CurrentIdx = i - 1;
}
else
{
CurrentIdx = (ULONG)(State->Tos + i) % (ULONG)g_LbrCapacity;
}
if (State->BranchEntry[CurrentIdx].From == 0)
{
continue;
}
if (g_ArchBasedLastBranchRecord)
{
BrType = (UINT32)State->LastBranchInfo[CurrentIdx].BrType_OnlyArchLbr;
//
// Get the branch type name for better readability when printing
//
LbrGetArchBranchType(BrType, BrTypeName);
//
// Architectural LBR
//
Log("\t [%2u] Branch Mispredicted: %s, Branch type: %s, Cycle Count (Decimal): %04d (is valid? %s) - From: %016llx To: %016llx\n",
CurrentIdx,
State->LastBranchInfo[CurrentIdx].Mispred ? "true " : "false",
BrTypeName,
State->LastBranchInfo[CurrentIdx].CycleCount,
State->LastBranchInfo[CurrentIdx].CycCntValid_OnlyArchLbr ? "true " : "false",
State->BranchEntry[CurrentIdx].From,
State->BranchEntry[CurrentIdx].To);
}
else
{
//
// Legacy LBR
//
Log("\t [%2u] Branch Mispredicted: %s, Cycle Count (Decimal): %04d - From: %016llx To: %016llx\n",
CurrentIdx,
State->LastBranchInfo[CurrentIdx].Mispred ? "true " : "false",
State->LastBranchInfo[CurrentIdx].CycleCount,
State->BranchEntry[CurrentIdx].From,
State->BranchEntry[CurrentIdx].To);
}
}
}