HyperDbg/hyperdbg/hypertrace/code/api/PtApi.c
sina 5bccbff58b
Some checks are pending
vs2026-ci / win-amd64-build (debug, x64) (push) Waiting to run
vs2026-ci / win-amd64-build (release, x64) (push) Waiting to run
vs2026-ci / Deploy release (push) Blocked by required conditions
fix IPT kernel structure based on the new model
2026-07-01 19:44:15 +02:00

644 lines
17 KiB
C

/**
* @file PtApi.c
* @author Masoud Rahimi Jafari (Masoodrahimy1379@gmail.com)
* @brief Tracing routines for HyperTrace module (Intel Processor Trace)
* @details
* @version 0.19
* @date 2026-04-29
*
* @copyright This project is released under the GNU Public License v3.
*/
#include "pch.h"
/**
* @brief Translate a user-mode HYPERTRACE_PT_OPERATION_PACKETS into a
* kernel-side PT_TRACE_CONFIG, falling back to defaults when the
* caller left fields zero (e.g. a bare `!pt filter`).
*
* The returned config is value-typed; HyperTracePtFilter's slow
* path copies it into every per-CPU slot via
* HyperTracePtSeedConfigOnAllCpus.
* @return VOID
*/
static VOID
HyperTracePtBuildConfig(const HYPERTRACE_PT_OPERATION_PACKETS * Req,
PT_TRACE_CONFIG * OutCfg)
{
UINT32 Copy;
//
// Start from defaults; only the fields the user can drive are then
// overridden.
//
PtEngineInitDefaultConfig(OutCfg);
//
// If the request specifies neither user nor kernel, default to both
// (matches LBR behaviour when filter is empty).
//
if (Req->FilterOptions.TraceUser || Req->FilterOptions.TraceKernel)
{
OutCfg->TraceUser = (Req->FilterOptions.TraceUser != 0) ? TRUE : FALSE;
OutCfg->TraceKernel = (Req->FilterOptions.TraceKernel != 0) ? TRUE : FALSE;
}
OutCfg->TargetCr3 = Req->EnableOptions.Cr3;
if (Req->BufferSize != 0)
{
OutCfg->BufferSize = Req->BufferSize;
}
OutCfg->NumAddrRanges = Req->FilterOptions.NumAddrRanges;
if (OutCfg->NumAddrRanges > PT_MAX_ADDR_RANGES)
OutCfg->NumAddrRanges = PT_MAX_ADDR_RANGES;
for (Copy = 0; Copy < OutCfg->NumAddrRanges; Copy++)
{
OutCfg->AddrRanges[Copy] = Req->FilterOptions.AddrRanges[Copy];
}
}
/**
* @brief Push a fully-formed PT_TRACE_CONFIG into every active per-CPU
* slot. Used by the Enable path so PtAllocateAllCpuBuffers picks up
* the right BufferSize on every core. Runs at PASSIVE_LEVEL —
* does NOT start or stop tracing (callers handle that via DPC)
* @return VOID
*/
static VOID
HyperTracePtSeedConfigOnAllCpus(const PT_TRACE_CONFIG * Cfg)
{
UINT32 ProcessorsCount;
UINT32 i;
if (g_PtStateList == NULL || Cfg == NULL)
return;
ProcessorsCount = KeQueryActiveProcessorCount(0);
for (i = 0; i < ProcessorsCount; i++)
{
g_PtStateList[i].Config = *Cfg;
}
}
/**
* @brief Enable PT tracing for HyperTrace
*
* @param PtOperationRequest Pointer to the HyperTrace PT operation request packet
*
* @return BOOLEAN
*/
BOOLEAN
HyperTracePtEnable(HYPERTRACE_PT_OPERATION_PACKETS * PtOperationRequest)
{
//
// Check if PT is already enabled or not
//
if (g_ProcessorTraceEnabled)
{
PtOperationRequest->KernelStatus = DEBUGGER_ERROR_PT_ALREADY_ENABLED;
return FALSE;
}
//
// Check PT support on CPU
//
if (!PtCheck())
{
PtOperationRequest->KernelStatus = DEBUGGER_ERROR_PT_NOT_SUPPORTED;
return FALSE;
}
//
// Allocate per-CPU ToPA / output / overflow buffers at PASSIVE_LEVEL
// (the contiguous-memory allocator cannot run from a DPC).
// PtAllocateAllCpuBuffers reads Cpu->Config.BufferSize, which was set
// either at module init (default 2MB) or by a prior !pt filter — so
// any pre-enable filter settings are preserved here.
//
if (!PtAllocateAllCpuBuffers())
{
//
// Roll back any partial allocations so we don't leak on next try.
//
PtFreeAllCpuBuffers();
PtOperationRequest->KernelStatus = DEBUGGER_ERROR_PT_NOT_SUPPORTED;
return FALSE;
}
//
// Broadcast enabling PT on all cores
//
BroadcastEnablePtOnAllCores();
//
// Set the flag to indicate that PT tracing is enabled
//
g_ProcessorTraceEnabled = TRUE;
//
// Set successful status
//
PtOperationRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
return TRUE;
}
/**
* @brief Disable PT tracing for HyperTrace
*
* @param PtOperationRequest Pointer to the HyperTrace PT operation request packet
*
* @return BOOLEAN
*/
BOOLEAN
HyperTracePtDisable(HYPERTRACE_PT_OPERATION_PACKETS * PtOperationRequest)
{
//
// Check if PT is already disabled or not
//
if (!g_ProcessorTraceEnabled)
{
if (PtOperationRequest != NULL)
{
PtOperationRequest->KernelStatus = DEBUGGER_ERROR_PT_ALREADY_DISABLED;
}
return FALSE;
}
//
// Disabling PT
//
g_ProcessorTraceEnabled = FALSE;
//
// Broadcast disabling PT on all cores (DPC stops tracing per-core)
//
BroadcastDisablePtOnAllCores();
//
// Free per-CPU buffers at PASSIVE_LEVEL after the broadcast has stopped
// tracing on every core.
//
PtFreeAllCpuBuffers();
//
// Set successful status
//
if (PtOperationRequest != NULL)
{
PtOperationRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
}
return TRUE;
}
/**
* @brief Pause PT tracing on every core. Buffers stay allocated and the
* per-CPU CTL is preserved, so HyperTracePtResume can restart the
* trace exactly where it stopped.
*
* @param HyperTraceOperationRequest
*
* @return BOOLEAN
*/
BOOLEAN
HyperTracePtPause(HYPERTRACE_PT_OPERATION_PACKETS * HyperTraceOperationRequest)
{
if (!g_ProcessorTraceEnabled)
{
if (HyperTraceOperationRequest != NULL)
{
HyperTraceOperationRequest->KernelStatus = DEBUGGER_ERROR_PT_ALREADY_DISABLED;
}
return FALSE;
}
BroadcastPausePtOnAllCores();
if (HyperTraceOperationRequest != NULL)
{
HyperTraceOperationRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
}
return TRUE;
}
/**
* @brief Resume PT tracing on every core after a prior HyperTracePtPause.
*
* @param HyperTraceOperationRequest
*
* @return BOOLEAN
*/
BOOLEAN
HyperTracePtResume(HYPERTRACE_PT_OPERATION_PACKETS * HyperTraceOperationRequest)
{
if (!g_ProcessorTraceEnabled)
{
if (HyperTraceOperationRequest != NULL)
{
HyperTraceOperationRequest->KernelStatus = DEBUGGER_ERROR_PT_ALREADY_DISABLED;
}
return FALSE;
}
BroadcastResumePtOnAllCores();
if (HyperTraceOperationRequest != NULL)
{
HyperTraceOperationRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
}
return TRUE;
}
/**
* @brief Snapshot the current PT output position on every core and write
* the per-CPU byte counts into HyperTraceOperationRequest->BytesPerCpu.
* The returned counts are the decode window — bytes [0, BytesPerCpu[i])
* in CPU i's user mapping currently hold valid trace data.
*
* @param HyperTraceOperationRequest
*
* @return BOOLEAN
*/
BOOLEAN
HyperTracePtSize(HYPERTRACE_PT_OPERATION_PACKETS * HyperTraceOperationRequest)
{
UINT32 ProcessorsCount;
if (!g_ProcessorTraceEnabled)
{
if (HyperTraceOperationRequest != NULL)
{
HyperTraceOperationRequest->KernelStatus = DEBUGGER_ERROR_PT_ALREADY_DISABLED;
}
return FALSE;
}
if (HyperTraceOperationRequest == NULL)
return FALSE;
ProcessorsCount = KeQueryActiveProcessorCount(0);
if (ProcessorsCount > PT_MAX_CPUS_FOR_MMAP)
ProcessorsCount = PT_MAX_CPUS_FOR_MMAP;
RtlZeroMemory(HyperTraceOperationRequest->BytesPerCpu,
sizeof(HyperTraceOperationRequest->BytesPerCpu));
BroadcastSizePtOnAllCores(HyperTraceOperationRequest->BytesPerCpu);
HyperTraceOperationRequest->NumCpus = ProcessorsCount;
HyperTraceOperationRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
return TRUE;
}
/**
* @brief Dump PT trace state for HyperTrace
*
* @param HyperTraceOperationRequest
*
* @return BOOLEAN
*/
BOOLEAN
HyperTracePtDump(HYPERTRACE_PT_OPERATION_PACKETS * HyperTraceOperationRequest)
{
if (!g_ProcessorTraceEnabled)
{
if (HyperTraceOperationRequest != NULL)
{
HyperTraceOperationRequest->KernelStatus = DEBUGGER_ERROR_PT_ALREADY_DISABLED;
}
return FALSE;
}
LogInfo("Dumping PT trace summary...\n");
BroadcastDumpPtOnAllCores();
if (HyperTraceOperationRequest != NULL)
{
HyperTraceOperationRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
}
return TRUE;
}
/**
* @brief Flush PT trace state on all cores (free buffers)
*
* @param HyperTraceOperationRequest
*
* @return BOOLEAN
*/
BOOLEAN
HyperTracePtFlush(HYPERTRACE_PT_OPERATION_PACKETS * HyperTraceOperationRequest)
{
if (!g_ProcessorTraceEnabled)
{
if (HyperTraceOperationRequest != NULL)
{
HyperTraceOperationRequest->KernelStatus = DEBUGGER_ERROR_PT_ALREADY_DISABLED;
}
return FALSE;
}
//
// Stop tracing on all cores via DPC (so MSRs are quiesced)
//
BroadcastFlushPtOnAllCores();
//
// Then free contiguous buffers at PASSIVE_LEVEL
//
PtFreeAllCpuBuffers();
//
// Reallocate buffers so subsequent !pt save / !pt dump / !pt enable
// continue to work — Flush in LBR keeps tracing alive, so we mirror
// that by leaving PT primed for the next !pt enable.
//
if (!PtAllocateAllCpuBuffers())
{
PtFreeAllCpuBuffers();
if (HyperTraceOperationRequest != NULL)
{
HyperTraceOperationRequest->KernelStatus = DEBUGGER_ERROR_PT_NOT_SUPPORTED;
}
g_ProcessorTraceEnabled = FALSE;
return FALSE;
}
//
// Resume tracing on all cores
//
BroadcastEnablePtOnAllCores();
if (HyperTraceOperationRequest != NULL)
{
HyperTraceOperationRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
}
return TRUE;
}
/**
* @brief Apply a new PT trace configuration (TraceUser / TraceKernel /
* TargetCr3 / BufferSize / NumAddrRanges + AddrRanges) on all cores.
*
* Mirrors HyperTraceLbrUpdateFilterOptions / LbrFilter:
*
* - If PT is currently enabled, stop tracing on all cores, free
* and reallocate buffers (because BufferSize may have changed),
* push the new config into every per-CPU slot, and resume
* tracing on all cores.
* - If PT is currently disabled, only update the per-CPU config
* so that the next `!pt enable` picks it up.
*
* Must be called at IRQL == PASSIVE_LEVEL because of the
* contiguous-memory allocator.
*/
BOOLEAN
HyperTracePtFilter(HYPERTRACE_PT_OPERATION_PACKETS * Req)
{
PT_APPLY_CORE_FILTER_REQUEST ApplyCoreFilterReq = {0};
BOOLEAN WasEnabled = g_ProcessorTraceEnabled;
BOOLEAN BufferChanged = FALSE;
UINT64 ExistingSize = 0;
//
// Copy the user-mode request into a kernel-mode PT_APPLY_CORE_FILTER_REQUEST
//
memcpy(&ApplyCoreFilterReq.FilterOptions, &Req->FilterOptions, sizeof(PT_FILTER_OPTIONS));
memcpy(&ApplyCoreFilterReq.EnableOptions, &Req->EnableOptions, sizeof(PT_ENABLE_OPTIONS));
ApplyCoreFilterReq.BufferSize = Req->BufferSize;
//
// Translate the user-mode packet into PT_FILTER_OPTIONS - the narrow
// surface PtFilter operates on. Default to user+kernel when the
// caller specified neither (matches LBR's empty-filter behaviour).
//
if (Req->FilterOptions.TraceUser || Req->FilterOptions.TraceKernel)
{
ApplyCoreFilterReq.FilterOptions.TraceUser = (Req->FilterOptions.TraceUser != 0) ? TRUE : FALSE;
ApplyCoreFilterReq.FilterOptions.TraceKernel = (Req->FilterOptions.TraceKernel != 0) ? TRUE : FALSE;
}
else
{
ApplyCoreFilterReq.FilterOptions.TraceUser = TRUE;
ApplyCoreFilterReq.FilterOptions.TraceKernel = TRUE;
}
if (Req->FilterOptions.NumAddrRanges > PT_MAX_ADDR_RANGES)
ApplyCoreFilterReq.FilterOptions.NumAddrRanges = PT_MAX_ADDR_RANGES;
//
// Decide between fast (filter-only) and slow (buffer-resize) paths.
//
if (g_PtStateList != NULL)
{
ExistingSize = g_PtStateList[0].Config.BufferSize;
}
if (ApplyCoreFilterReq.BufferSize != 0 && ApplyCoreFilterReq.BufferSize != ExistingSize)
{
BufferChanged = TRUE;
}
if (!WasEnabled || BufferChanged)
{
//
// Slow path — PT is off and/or BufferSize changed. We have to
// free + reallocate per-CPU buffers at PASSIVE_LEVEL, so we
// can't go through DPC for the config seeding either.
//
PT_TRACE_CONFIG Cfg;
if (WasEnabled)
{
BroadcastFlushPtOnAllCores();
PtFreeAllCpuBuffers();
}
//
// Build a full PT_TRACE_CONFIG (defaults + the user-tunable
// fields from the request) and seed it on every CPU so the next
// PtAllocateAllCpuBuffers picks up the right BufferSize and
// PtEngineStart programs the right RTIT_CTL bits.
//
HyperTracePtBuildConfig(Req, &Cfg);
HyperTracePtSeedConfigOnAllCpus(&Cfg);
if (WasEnabled)
{
if (!PtAllocateAllCpuBuffers())
{
PtFreeAllCpuBuffers();
g_ProcessorTraceEnabled = FALSE;
if (Req != NULL)
{
Req->KernelStatus = DEBUGGER_ERROR_PT_NOT_SUPPORTED;
}
return FALSE;
}
BroadcastEnablePtOnAllCores();
}
}
else
{
//
// Fast filter-only path: PT is running and BufferSize is
// unchanged. Force FilterOptions.BufferSize=0 so PtFilter on each core
// keeps the buffer that's already allocated, then broadcast.
//
ApplyCoreFilterReq.BufferSize = 0;
BroadcastFilterPtOnAllCores(&ApplyCoreFilterReq);
}
if (Req != NULL)
{
Req->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
}
return TRUE;
}
/**
* @brief Map every per-CPU PT main output + overflow buffer into the
* calling user-mode process. See HYPERTRACE_PT_MMAP_PACKETS for
* the full lifetime / single-process contract.
*/
BOOLEAN
HyperTracePtMmap(HYPERTRACE_PT_MMAP_PACKETS * Req)
{
if (Req == NULL)
return FALSE;
Req->NumCpus = 0;
if (!g_HyperTraceCallbacksInitialized)
{
Req->KernelStatus = DEBUGGER_ERROR_HYPERTRACE_NOT_INITIALIZED;
return FALSE;
}
if (!g_ProcessorTraceEnabled)
{
Req->KernelStatus = DEBUGGER_ERROR_PT_ALREADY_DISABLED;
return FALSE;
}
if (PtMmapAllCpuBuffersToUser(Req->Cpus, PT_MAX_CPUS_FOR_MMAP, &Req->NumCpus) != 0)
{
Req->KernelStatus = DEBUGGER_ERROR_PT_NOT_SUPPORTED;
return FALSE;
}
Req->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
return TRUE;
}
/**
* @brief Perform actions related to HyperTrace PT
*
* @param PtOperationRequest
*
* @return BOOLEAN
*/
BOOLEAN
HyperTracePtPerformOperation(HYPERTRACE_PT_OPERATION_PACKETS * PtOperationRequest)
{
BOOLEAN Status = TRUE;
//
// Check if the hypertrace module is initialized before performing any operation
//
if (!g_HyperTraceCallbacksInitialized)
{
PtOperationRequest->KernelStatus = DEBUGGER_ERROR_HYPERTRACE_NOT_INITIALIZED;
return FALSE;
}
//
// Perform the requested operation
//
switch (PtOperationRequest->PtOperationType)
{
case HYPERTRACE_PT_OPERATION_REQUEST_TYPE_ENABLE:
LogInfo("HyperTrace: Enabling PT tracing...\n");
HyperTracePtEnable(PtOperationRequest);
break;
case HYPERTRACE_PT_OPERATION_REQUEST_TYPE_DISABLE:
LogInfo("HyperTrace: Disabling PT tracing...\n");
HyperTracePtDisable(PtOperationRequest);
break;
case HYPERTRACE_PT_OPERATION_REQUEST_TYPE_PAUSE:
LogInfo("HyperTrace: Pausing PT tracing...\n");
HyperTracePtPause(PtOperationRequest);
break;
case HYPERTRACE_PT_OPERATION_REQUEST_TYPE_RESUME:
LogInfo("HyperTrace: Resuming PT tracing...\n");
HyperTracePtResume(PtOperationRequest);
break;
case HYPERTRACE_PT_OPERATION_REQUEST_TYPE_SIZE:
LogInfo("HyperTrace: Snapshotting PT buffer sizes...\n");
HyperTracePtSize(PtOperationRequest);
break;
case HYPERTRACE_PT_OPERATION_REQUEST_TYPE_DUMP:
LogInfo("HyperTrace: Showing PT tracing...\n");
HyperTracePtDump(PtOperationRequest);
break;
case HYPERTRACE_PT_OPERATION_REQUEST_TYPE_FLUSH:
LogInfo("HyperTrace: Flushing PT tracing...\n");
HyperTracePtFlush(PtOperationRequest);
break;
case HYPERTRACE_PT_OPERATION_REQUEST_TYPE_FILTER:
LogInfo("HyperTrace: Updating PT filter / config...\n");
HyperTracePtFilter(PtOperationRequest);
break;
default:
Status = FALSE;
PtOperationRequest->KernelStatus = DEBUGGER_ERROR_INVALID_HYPERTRACE_OPERATION_TYPE;
break;
}
return Status;
}