/********************************************************************** * Windows Intel Processor Trace (PT) Driver * Filename: IntelPt.cpp * Implement the Intel Processor Trace driver * Last revision: 01/06/2016 * * Copyright© 2016 Andrea Allievi, Richard Johnson * Microsoft Ltd & TALOS Research and Intelligence Group * All right reserved **********************************************************************/ #include "stdafx.h" #include "DriverEntry.h" #include "IntelPt.h" #include "Debug.h" #include "UndocNt.h" #include "IntelPtXSave.h" #include #include #define DirectoryTableBaseOffset 0x28 #pragma region Intel PT management functions #pragma code_seg(".nonpaged") // Emit a REAL CpuId opcode (bypassing the Hypervisor if needed) void CPUIDEX(int CpuInfo[4], int Function, int Leaf) { NTSTATUS ntStatus = STATUS_UNSUCCESSFUL; if (g_pDrvData && g_pDrvData->IsUnderHyperV) { // If this is not the root partition, we normally do not have the // "CpuManagement" Partition privilege if (g_pDrvData->HyperV_Data.Info.Features.PartitionPrivilegeMask.CpuManagement != 0) ntStatus = HvCpuId(CpuInfo, Function, Leaf); else // Rely on the standard CPUID instruction ntStatus = STATUS_ACCESS_DENIED; } if (!NT_SUCCESS(ntStatus)) return __cpuidex(CpuInfo, Function, Leaf); } NTSTATUS CheckIntelPtSupport(INTEL_PT_CAPABILITIES * lpPtCap) { INTEL_PT_CAPABILITIES ptCap = { 0 }; // The processor PT capabilities MSR_IA32_VMX_MISC_CTLS_DESC VmxMisc = { 0 }; // The VMX miscellaneous data int cpuid_ctx[4] = { 0 }; // EAX, EBX, ECX, EDX KIRQL kIrql = KeGetCurrentIrql(); // Processor support for Intel Processor Trace is indicated by CPUID.(EAX=07H,ECX=0H):EBX[bit 25] = 1. CPUIDEX(cpuid_ctx, 0x07, 0); if ((cpuid_ctx[1] & (1 << 25)) == 0) return STATUS_NOT_SUPPORTED; // We can return now if capability struct was not requested if (!lpPtCap) return STATUS_SUCCESS; // Enumerate the Intel Processor Trace capabilities RtlZeroMemory(cpuid_ctx, sizeof(cpuid_ctx)); CPUIDEX(cpuid_ctx, 0x14, 0); ptCap.bCr3Filtering = (cpuid_ctx[1] & (1 << 0)) != 0; // EBX ptCap.bConfPsbAndCycSupported = (cpuid_ctx[1] & (1 << 1)) != 0; ptCap.bIpFiltering = (cpuid_ctx[1] & (1 << 2)) != 0; ptCap.bMtcSupport = (cpuid_ctx[1] & (1 << 3)) != 0; ptCap.bTopaOutput = (cpuid_ctx[2] & (1 << 0)) != 0; // ECX ptCap.bTopaMultipleEntries = (cpuid_ctx[2] & (1 << 1)) != 0; ptCap.bSingleRangeSupport = (cpuid_ctx[2] & (1 << 2)) != 0; ptCap.bTransportOutputSupport = (cpuid_ctx[2] & (1 << 3)) != 0; ptCap.bIpPcksAreLip = (cpuid_ctx[2] & (1 << 31)) != 0; // Enumerate secondary capabilities (sub-leaf 1) if (cpuid_ctx[0] != 0) { RtlZeroMemory(cpuid_ctx, sizeof(cpuid_ctx)); CPUIDEX(cpuid_ctx, 0x14, 1); ptCap.numOfAddrRanges = (BYTE)(cpuid_ctx[0] & 0x7); ptCap.mtcPeriodBmp = (SHORT)((cpuid_ctx[0] >> 16) & 0xFFFF); ptCap.cycThresholdBmp = (SHORT)(cpuid_ctx[1] & 0xFFFF); ptCap.psbFreqBmp = (SHORT)((cpuid_ctx[1] >> 16) & 0xFFFF); } // Get if the PT is compatible with the Hypervisors here: if (kIrql < DISPATCH_LEVEL) { __try { VmxMisc.All = __readmsr(MSR_IA32_VMX_MISC_CTLS); } __except (EXCEPTION_EXECUTE_HANDLER) { // Some Hypervisors (like HyperV) detect the read to VMX_MISC_CTLS and raise an exception VmxMisc.All = 0; } } ptCap.bVmxSupport = VmxMisc.Fields.IntelPtVmxSupport; if (VmxMisc.Fields.IntelPtVmxSupport == 1) DrvDbgPrint("CheckIntelPtSupport - The processor supports Intel PT in VMX operations.\r\n"); *lpPtCap = ptCap; return STATUS_SUCCESS; } // Enable the Intel PT trace for current processor NTSTATUS StartCpuTrace(PT_TRACE_DESC desc, PT_BUFFER_DESCRIPTOR * pPtBuffDesc) { NTSTATUS ntStatus = STATUS_NOT_SUPPORTED; // Returned NTSTATUS value INTEL_PT_CAPABILITIES ptCap = { 0 }; // The per-processor PT capabilities PER_PROCESSOR_PT_DATA * lpProcPtData = NULL; // The per processor data structure ULONG_PTR targetCr3 = 0; // The target CR3 value KIRQL kOldIrql = KeGetCurrentIrql(); // The current IRQL ULONG curProcId = KeGetCurrentProcessorNumber(); // Current processor number if (!pPtBuffDesc) return STATUS_INVALID_PARAMETER; if (!g_pDrvData) return STATUS_INTERNAL_ERROR; // PT data structures MSR_RTIT_CTL_DESC rtitCtlDesc = { 0 }; MSR_RTIT_STATUS_DESC rtitStatusDesc = { 0 }; MSR_RTIT_OUTPUTBASE_DESC rtitOutBaseDesc = { 0 }; MSR_RTIT_OUTPUT_MASK_PTRS_DESC rtitOutMasksDesc = { 0 }; if (!pPtBuffDesc || !pPtBuffDesc->qwBuffSize) return STATUS_INVALID_PARAMETER_2; ntStatus = CheckIntelPtSupport(&ptCap); if (!NT_SUCCESS(ntStatus)) return ntStatus; // Check here the support based on the Trace structure if (desc.peProc != NULL) // Check the support for CR3 filtering if (!ptCap.bCr3Filtering) return STATUS_NOT_SUPPORTED; if (desc.dwNumOfRanges > 0) { if (!ptCap.bIpFiltering) return STATUS_NOT_SUPPORTED; if (desc.dwNumOfRanges > 4) return STATUS_INVALID_PARAMETER_1; if ((DWORD)ptCap.numOfAddrRanges < desc.dwNumOfRanges) return STATUS_NOT_SUPPORTED; } // Now check the output mode if (!ptCap.bSingleRangeSupport && !ptCap.bTopaOutput) return STATUS_NOT_SUPPORTED; // To proper read the value of the CR3 register of a target process, the KiSwapProcess routines does this: // From KTHREAD go to ETHREAD, then use the ApcState field to return back to a EPROCESS // Finally grab it from peProc->DirectoryTableBase (offset + 0x28) if (desc.peProc) { targetCr3 = ((ULONG_PTR *)desc.peProc)[5]; // Check the found target CR3 (it should have the last 12 bits set to 0, due to the PFN standard) if ((targetCr3 & 0xFFF) != 0) return STATUS_INVALID_ADDRESS; DrvDbgPrint("[" DRV_NAME "] Starting Intel Processor Trace for processor %i. Target CR3: 0x%llX\r\n", curProcId, targetCr3); } else if (desc.bTraceKernel) DrvDbgPrint("[" DRV_NAME "] Starting Intel Processor Trace for processor %i. Tracing Kernel address space...\r\n", curProcId); else DrvDbgPrint("[" DRV_NAME "] Starting Intel Processor Trace for processor %i. Tracing all user mode processes.\r\n", curProcId); if (desc.dwNumOfRanges > 0) DrvDbgPrint("[" DRV_NAME "] Enabled %i filtering windows. IP range 1. Start VA: 0x%llX, Size 0x%08X\r\n ", desc.dwNumOfRanges, (LPVOID)desc.Ranges[0].lpStartVa, (LPVOID)((DWORD)((QWORD)desc.Ranges[0].lpEndVa - (QWORD)desc.Ranges[0].lpStartVa))); // Check if the passed data structure that describe the buffer is valid if ((pPtBuffDesc->bUseTopa && !ptCap.bTopaOutput) || pPtBuffDesc->qwBuffSize < PAGE_SIZE || (!pPtBuffDesc->bUseTopa && !ptCap.bSingleRangeSupport) || !pPtBuffDesc->u.Simple.lpTraceBuffPhysAddr) return STATUS_INVALID_PARAMETER_2; // Initially set up all the descriptor data in the Per-processor control structure lpProcPtData = &g_pDrvData->procData[curProcId]; lpProcPtData->lpTargetProcCr3 = targetCr3; lpProcPtData->lpTargetProc = desc.peProc; if (desc.dwNumOfRanges) { RtlZeroMemory(lpProcPtData->IpRanges, sizeof(lpProcPtData->IpRanges)); RtlCopyMemory(lpProcPtData->IpRanges, desc.Ranges, desc.dwNumOfRanges * sizeof(PT_TRACE_RANGE)); lpProcPtData->dwNumOfActiveRanges = desc.dwNumOfRanges; } // Check if the options have been initialized if (!lpProcPtData->TraceOptions.Fields.bInitialized) SetDefaultTraceOptions(curProcId); // Raise the IRQL (we don't want to be swapped out) if (kOldIrql < DISPATCH_LEVEL) KeRaiseIrql(DISPATCH_LEVEL, &kOldIrql); // Step 1. Disable all the previous PT flags DBG_BREAK(); rtitCtlDesc.All = __readmsr(MSR_IA32_RTIT_CTL); rtitCtlDesc.Fields.TraceEn = 0; __writemsr(MSR_IA32_RTIT_CTL, rtitCtlDesc.All); ASSERT(__readmsr(MSR_IA32_RTIT_CTL) == rtitCtlDesc.All); // Clear IA32_RTIT_STATUS MSR rtitStatusDesc.All = __readmsr(MSR_IA32_RTIT_STATUS); rtitStatusDesc.Fields.Error = 0; // See Intel's manuals, section 36.3.2.1 rtitStatusDesc.Fields.Stopped = 0; rtitStatusDesc.Fields.ContextEn = 0; rtitStatusDesc.Fields.PacketByteCnt = 0; // Restore the Byte counter to 0 lpProcPtData->PacketByteCount = 0; // In both values __writemsr(MSR_IA32_RTIT_STATUS, rtitStatusDesc.All); // Set the IA32_RTIT_OUTPUT and IA32_RTIT_OUTPUT_MASK_PTRS MSRs if (pPtBuffDesc->bUseTopa) { // Use Table of Physical Addresses rtitCtlDesc.Fields.ToPA = 1; // Set the proc_trace_table_base rtitOutBaseDesc.All = (ULONGLONG)pPtBuffDesc->u.ToPA.lpTopaPhysAddr; __writemsr(MSR_IA32_RTIT_OUTPUT_BASE, rtitOutBaseDesc.All); ASSERT(__readmsr(MSR_IA32_RTIT_OUTPUT_BASE) == rtitOutBaseDesc.All); // Set the proc_trace_table_offset: indicates the entry of the current table that is currently in use rtitOutMasksDesc.Fields.LowerMask = 0x7F; rtitOutMasksDesc.Fields.MaskOrTableOffset = 0; // Start from the first entry in the table rtitOutMasksDesc.Fields.OutputOffset = 0; // Start at offset 0 __writemsr(MSR_IA32_RTIT_OUTPUT_MASK_PTRS, rtitOutMasksDesc.All); ASSERT(__readmsr(MSR_IA32_RTIT_OUTPUT_MASK_PTRS) == rtitOutMasksDesc.All); } else { // Use the single range output implementation rtitCtlDesc.Fields.ToPA = 0; // We use the single-range output scheme rtitOutBaseDesc.All = (ULONGLONG)pPtBuffDesc->u.Simple.lpTraceBuffPhysAddr; __writemsr(MSR_IA32_RTIT_OUTPUT_BASE, rtitOutBaseDesc.All); ASSERT(__readmsr(MSR_IA32_RTIT_OUTPUT_BASE) == rtitOutBaseDesc.All); rtitOutMasksDesc.All = (1 << PAGE_SHIFT) - 1; // The physical page always has low 12 bits NULL __writemsr(MSR_IA32_RTIT_OUTPUT_MASK_PTRS, rtitOutMasksDesc.All); ASSERT(__readmsr(MSR_IA32_RTIT_OUTPUT_MASK_PTRS) == rtitOutMasksDesc.All); } // Set the TRACE options: TRACE_OPTIONS & options = lpProcPtData->TraceOptions; rtitCtlDesc.Fields.FabricEn = 0; rtitCtlDesc.Fields.Os = (desc.bTraceKernel ? 1 : 0); // Trace Kernel address space rtitCtlDesc.Fields.User = (desc.bTraceUser ? 1 : 0); // Trace the user mode process rtitCtlDesc.Fields.BranchEn = options.Fields.bTraceBranchPcks; if (lpProcPtData->lpTargetProcCr3) { // Set the page table filter for the target process __writemsr(MSR_IA32_RTIT_CR3_MATCH, (ULONGLONG)targetCr3); ASSERT(__readmsr(MSR_IA32_RTIT_CR3_MATCH) == (ULONGLONG)targetCr3); rtitCtlDesc.Fields.CR3Filter = 1; } else { // Set the register to 0 __writemsr(MSR_IA32_RTIT_CR3_MATCH, 0); ASSERT(__readmsr(MSR_IA32_RTIT_CR3_MATCH) == 0); rtitCtlDesc.Fields.CR3Filter = 0; } // Set the IP range flags and registers to 0 rtitCtlDesc.Fields.Addr0Cfg = 0; rtitCtlDesc.Fields.Addr1Cfg = 0; rtitCtlDesc.Fields.Addr2Cfg = 0; rtitCtlDesc.Fields.Addr3Cfg = 0; // Now set them to the proper values (see Intel Manuals, chapter 36.2.5.2 - IA32_RTIT_CTL MSR) if (lpProcPtData->dwNumOfActiveRanges > 0) { if (lpProcPtData->IpRanges[0].bStopTrace) rtitCtlDesc.Fields.Addr0Cfg = 2; else rtitCtlDesc.Fields.Addr0Cfg = 1; __writemsr(MSR_IA32_RTIT_ADDR0_START, (QWORD)lpProcPtData->IpRanges[0].lpStartVa); ASSERT(__readmsr(MSR_IA32_RTIT_ADDR0_START) == (QWORD)lpProcPtData->IpRanges[0].lpStartVa); __writemsr(MSR_IA32_RTIT_ADDR0_END, (QWORD)lpProcPtData->IpRanges[0].lpEndVa); ASSERT(__readmsr(MSR_IA32_RTIT_ADDR0_END) == (QWORD)lpProcPtData->IpRanges[0].lpEndVa); } if (lpProcPtData->dwNumOfActiveRanges > 1) { if (lpProcPtData->IpRanges[1].bStopTrace) rtitCtlDesc.Fields.Addr1Cfg = 2; else rtitCtlDesc.Fields.Addr1Cfg = 1; __writemsr(MSR_IA32_RTIT_ADDR1_START, (QWORD)lpProcPtData->IpRanges[1].lpStartVa); __writemsr(MSR_IA32_RTIT_ADDR1_END, (QWORD)lpProcPtData->IpRanges[1].lpEndVa); } if (lpProcPtData->dwNumOfActiveRanges > 2) { if (lpProcPtData->IpRanges[2].bStopTrace) rtitCtlDesc.Fields.Addr2Cfg = 2; else rtitCtlDesc.Fields.Addr2Cfg = 1; __writemsr(MSR_IA32_RTIT_ADDR2_START, (QWORD)lpProcPtData->IpRanges[2].lpStartVa); __writemsr(MSR_IA32_RTIT_ADDR2_END, (QWORD)lpProcPtData->IpRanges[2].lpEndVa); } if (lpProcPtData->dwNumOfActiveRanges > 3) { if (lpProcPtData->IpRanges[3].bStopTrace) rtitCtlDesc.Fields.Addr3Cfg = 2; else rtitCtlDesc.Fields.Addr3Cfg = 1; __writemsr(MSR_IA32_RTIT_ADDR3_START, (QWORD)lpProcPtData->IpRanges[3].lpStartVa); __writemsr(MSR_IA32_RTIT_ADDR3_END, (QWORD)lpProcPtData->IpRanges[3].lpEndVa); } if (ptCap.bMtcSupport) { rtitCtlDesc.Fields.MTCEn = options.Fields.bTraceMtcPcks; if ((1 << options.Fields.MTCFreq) & ptCap.mtcPeriodBmp) rtitCtlDesc.Fields.MTCFreq = options.Fields.MTCFreq; } if (ptCap.bConfPsbAndCycSupported) { rtitCtlDesc.Fields.CycEn = options.Fields.bTraceCycPcks; if ((1 << options.Fields.CycThresh) & ptCap.cycThresholdBmp) rtitCtlDesc.Fields.CycThresh = options.Fields.CycThresh; if ((1 << options.Fields.PSBFreq) & ptCap.psbFreqBmp) rtitCtlDesc.Fields.PSBFreq = options.Fields.PSBFreq; } rtitCtlDesc.Fields.DisRETC = (options.Fields.bEnableRetCompression == 0); rtitCtlDesc.Fields.TSCEn = options.Fields.bTraceTscPcks; // Switch the tracing to ON dude :-) rtitCtlDesc.Fields.TraceEn = 1; __writemsr(MSR_IA32_RTIT_CTL, rtitCtlDesc.All); ASSERT(__readmsr(MSR_IA32_RTIT_CTL) == rtitCtlDesc.All); // Read the status register rtitStatusDesc.All = __readmsr(MSR_IA32_RTIT_STATUS); // Finally lower the IRQL if (kOldIrql < DISPATCH_LEVEL) KeLowerIrql(kOldIrql); if (rtitStatusDesc.Fields.TriggerEn) { DbgPrint("[" DRV_NAME "] Successfully enabled Intel PT tracing for processor %i. Log Virtual Address: 0x%llX. :-)\r\n", curProcId, pPtBuffDesc->bUseTopa ? pPtBuffDesc->u.ToPA.lpTopaVa : pPtBuffDesc->u.Simple.lpTraceBuffVa); lpProcPtData->curState = PT_PROCESSOR_STATE_TRACING; // Set the PT buffer as current lpProcPtData->pPtBuffDesc = pPtBuffDesc; return STATUS_SUCCESS; } else { DbgPrint("[" DRV_NAME "] Error: unable to successfully enable Intel PT tracing for processor %i.", curProcId); //__writemsr(MSR_IA32_RTIT_STATUS, 0); lpProcPtData->curState = PT_PROCESSOR_STATE_ERROR; lpProcPtData->lpTargetProc = NULL; lpProcPtData->lpTargetProcCr3 = NULL; lpProcPtData->dwNumOfActiveRanges = 0; RtlZeroMemory(lpProcPtData->IpRanges, sizeof(lpProcPtData->IpRanges)); return STATUS_UNSUCCESSFUL; } } // Enable the Intel PT trace for current processor (allocate the needed buffer) NTSTATUS StartCpuTrace(PT_TRACE_DESC desc, QWORD qwBuffSize) { NTSTATUS ntStatus = STATUS_SUCCESS; BOOLEAN bBuffAllocated = FALSE; // TRUE if buffer for current CPU has been allocated DWORD dwCurCpu = KeGetCurrentProcessorNumber(); if (!g_pDrvData) return STATUS_INTERNAL_ERROR; ntStatus = CheckIntelPtSupport(NULL); if (!NT_SUCCESS(ntStatus)) return ntStatus; // Grab the memory descriptor PER_PROCESSOR_PT_DATA & cpuPtData = g_pDrvData->procData[dwCurCpu]; // Check if the options have been initialized if (!cpuPtData.TraceOptions.Fields.bInitialized) SetDefaultTraceOptions(dwCurCpu); // Allocate the physical memory if (!cpuPtData.pPtBuffDesc || !cpuPtData.pPtBuffDesc->qwBuffSize || cpuPtData.pPtBuffDesc->qwBuffSize != qwBuffSize) { BOOLEAN bUseTopa = (cpuPtData.TraceOptions.Fields.bUseTopa == 1); ntStatus = AllocCpuPtBuffer(dwCurCpu, qwBuffSize, bUseTopa); if (!NT_SUCCESS(ntStatus)) { DbgPrint("[" DRV_NAME "] Error: unable to allocate the trace buffer.\r\n"); cpuPtData.lpTargetProcCr3 = NULL; cpuPtData.lpTargetProc = NULL; return STATUS_INVALID_PARAMETER_2; } bBuffAllocated = TRUE; } ntStatus = StartCpuTrace(desc, cpuPtData.pPtBuffDesc); if (!NT_SUCCESS(ntStatus) && bBuffAllocated) FreeCpuResources(dwCurCpu); return ntStatus; } // Start the Tracing of a particular usermode process NTSTATUS StartProcessTrace(DWORD dwProcId, QWORD qwBuffSize) { NTSTATUS ntStatus = 0; PEPROCESS peProc = NULL; PT_TRACE_DESC ptDesc = { 0 }; // The kernel tracing data structure if (!g_pDrvData) return STATUS_INTERNAL_ERROR; // PsLookupProcessByProcessId should be executed at IRQL < DISPATCH_LEVEL ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); ntStatus = PsLookupProcessByProcessId((HANDLE)dwProcId, &peProc); if (!NT_SUCCESS(ntStatus)) return ntStatus; else { // Compose the right data structure and pass the control to the main function ptDesc.bTraceKernel = FALSE; ptDesc.bTraceUser = TRUE; ptDesc.dwNumOfRanges = 0; ptDesc.peProc = peProc; return StartCpuTrace(ptDesc, qwBuffSize); } } // Put the tracing in PAUSE mode NTSTATUS PauseResumeTrace(BOOLEAN bPause) { MSR_RTIT_CTL_DESC rtitCtlDesc = { 0 }; // The RTIT MSR descriptor MSR_RTIT_STATUS_DESC rtitStatusDesc = { 0 }; // The Status MSR descriptor MSR_RTIT_OUTPUTBASE_DESC rtitOutBaseDesc = { 0 }; // IA32_RTIT_OUTPUT_BASE Model specific Register MSR_RTIT_OUTPUT_MASK_PTRS_DESC rtitOutMasksDesc = { 0 };// IA32_RTIT_OUTPUT_MASK_PTRS Model specific Register DWORD dwCurCpu = 0; // Current running CPU NTSTATUS ntStatus = STATUS_NOT_SUPPORTED; // Returned NTSTATUS value if (!g_pDrvData) return STATUS_INTERNAL_ERROR; ntStatus = CheckIntelPtSupport(NULL); if (!NT_SUCCESS(ntStatus)) return ntStatus; dwCurCpu = KeGetCurrentProcessorNumber(); PER_PROCESSOR_PT_DATA & curCpuData = g_pDrvData->procData[dwCurCpu]; if (curCpuData.curState != PT_PROCESSOR_STATE_TRACING && bPause) return STATUS_SUCCESS; if (curCpuData.curState != PT_PROCESSOR_STATE_PAUSED && bPause == FALSE) return STATUS_INVALID_DEVICE_REQUEST; // Read the current state rtitCtlDesc.All = __readmsr(MSR_IA32_RTIT_CTL); rtitStatusDesc.All = __readmsr(MSR_IA32_RTIT_STATUS); // XXX: This seems unnecessary // Update the STATUS register if (rtitCtlDesc.Fields.TraceEn == 0) { rtitStatusDesc.Fields.Stopped = 0; rtitStatusDesc.Fields.Error = 0; __writemsr(MSR_IA32_RTIT_STATUS, rtitStatusDesc.All); } if (bPause) { // Pause Intel PT tracing rtitCtlDesc.Fields.TraceEn = 0; } else { PT_BUFFER_DESCRIPTOR * ptBuffDesc = curCpuData.pPtBuffDesc; // If we paused to dump buffer lets reset it if (ptBuffDesc && ptBuffDesc->bUseTopa && ptBuffDesc->bBuffIsFull) { // Restore the Topa Buffer, Set the proc_trace_table_base rtitOutBaseDesc.All = (ULONGLONG)ptBuffDesc->u.ToPA.lpTopaPhysAddr; __writemsr(MSR_IA32_RTIT_OUTPUT_BASE, rtitOutBaseDesc.All); // Set the proc_trace_table_offset: indicates the entry of the table that is currently in use rtitOutMasksDesc.Fields.LowerMask = 0x7F; rtitOutMasksDesc.Fields.MaskOrTableOffset = 0; // Start from the first entry in the table rtitOutMasksDesc.Fields.OutputOffset = 0; // Start at offset 0 __writemsr(MSR_IA32_RTIT_OUTPUT_MASK_PTRS, rtitOutMasksDesc.All); ptBuffDesc->bBuffIsFull = FALSE; } // Resume Intel PT tracing rtitCtlDesc.Fields.TraceEn = 1; } // Update the Control register __writemsr(MSR_IA32_RTIT_CTL, rtitCtlDesc.All); /* XXX: should not be needed if (kIrql <= DISPATCH_LEVEL) { // STALL the execution for a little time KeStallExecutionProcessor(42); } // else ... Interrupt routine should be VERY FAST */ // Read the final status rtitStatusDesc.All = __readmsr(MSR_IA32_RTIT_STATUS); if (rtitStatusDesc.Fields.Error) { curCpuData.curState = PT_PROCESSOR_STATE_ERROR; return STATUS_UNSUCCESSFUL; } if (bPause) { // Copy and reset the current number of packets curCpuData.PacketByteCount += (QWORD)rtitStatusDesc.Fields.PacketByteCnt; rtitStatusDesc.Fields.PacketByteCnt = 0; __writemsr(MSR_IA32_RTIT_STATUS, rtitStatusDesc.All); curCpuData.curState = PT_PROCESSOR_STATE_PAUSED; } else curCpuData.curState = PT_PROCESSOR_STATE_TRACING; return STATUS_SUCCESS; } // Disable Intel PT for the current processor NTSTATUS StopAndDisablePt() { NTSTATUS ntStatus = STATUS_NOT_SUPPORTED; // Returned NTSTATUS value INTEL_PT_CAPABILITIES ptCap = { 0 }; // Intel Processor Tracing capabilities PER_PROCESSOR_PT_DATA * lpProcPtData = NULL; // The per processor data structure MSR_RTIT_CTL_DESC rtitCtlDesc = { 0 }; MSR_RTIT_STATUS_DESC rtitStatusDesc = { 0 }; // The Status MSR descriptor ULONG dwCurProc = 0; if (!g_pDrvData) return STATUS_INTERNAL_ERROR; ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); dwCurProc = KeGetCurrentProcessorNumber(); lpProcPtData = &g_pDrvData->procData[dwCurProc]; ntStatus = CheckIntelPtSupport(&ptCap); if (!NT_SUCCESS(ntStatus)) return ntStatus; #if ENABLE_EXPERIMENTAL_XSAVE ntStatus = SavePtData((PXSAVE_AREA_EX)lpProcPtData->lpXSaveArea, lpProcPtData->dwXSaveAreaSize); #endif // Stop and disable the Intel PT rtitCtlDesc.All = __readmsr(MSR_IA32_RTIT_CTL); rtitCtlDesc.Fields.TraceEn = 0; __writemsr(MSR_IA32_RTIT_CTL, rtitCtlDesc.All); // Copy the final number of Acquired packets rtitStatusDesc.All = __readmsr(MSR_IA32_RTIT_STATUS); lpProcPtData->PacketByteCount += (QWORD)rtitStatusDesc.Fields.PacketByteCnt; // Reset all the configuration registers __writemsr(MSR_IA32_RTIT_OUTPUT_BASE, 0); __writemsr(MSR_IA32_RTIT_OUTPUT_MASK_PTRS, 0); if (ptCap.numOfAddrRanges > 0) { __writemsr(MSR_IA32_RTIT_ADDR0_START, 0); __writemsr(MSR_IA32_RTIT_ADDR0_END, 0); } if (ptCap.numOfAddrRanges > 1) { __writemsr(MSR_IA32_RTIT_ADDR1_START, 0); __writemsr(MSR_IA32_RTIT_ADDR1_END, 0); } if (ptCap.numOfAddrRanges > 2) { __writemsr(MSR_IA32_RTIT_ADDR2_START, 0); __writemsr(MSR_IA32_RTIT_ADDR2_END, 0); } if (ptCap.numOfAddrRanges > 3) { __writemsr(MSR_IA32_RTIT_ADDR3_START, 0); __writemsr(MSR_IA32_RTIT_ADDR3_END, 0); } if (ptCap.bCr3Filtering) __writemsr(MSR_IA32_RTIT_CR3_MATCH, 0); // Set the new processor State lpProcPtData->curState = PT_PROCESSOR_STATE_STOPPED; lpProcPtData->lpTargetProcCr3 = NULL; lpProcPtData->lpTargetProc = NULL; lpProcPtData->dwNumOfActiveRanges = 0; RtlZeroMemory(lpProcPtData->IpRanges, sizeof(lpProcPtData->IpRanges)); return STATUS_SUCCESS; } // Get the active Trace options for a particular CPU NTSTATUS GetTraceOptions(DWORD dwCpuId, TRACE_OPTIONS * pOptions) { DWORD dwNumCpus = KeQueryActiveProcessorCount(NULL); if (!g_pDrvData) return STATUS_INTERNAL_ERROR; if (dwCpuId >= dwNumCpus) return STATUS_INVALID_PARAMETER; // Initialize the default trace options if not any is set if (g_pDrvData->procData[dwCpuId].TraceOptions.Fields.bInitialized == FALSE) SetDefaultTraceOptions(dwCpuId); if (pOptions) *pOptions = g_pDrvData->procData[dwCpuId].TraceOptions; return STATUS_SUCCESS; } // Set the trace options for a particular CPU NTSTATUS SetTraceOptions(DWORD dwCpuId, TRACE_OPTIONS opts) { KAFFINITY curCpuAffinity = 0; DWORD dwNumCpus = 0; INTEL_PT_CAPABILITIES ptCap = { 0 }; NTSTATUS ntStatus = 0; dwNumCpus = KeQueryActiveProcessorCount(&curCpuAffinity); if (!g_pDrvData) return STATUS_INTERNAL_ERROR; if (dwCpuId >= dwNumCpus) return STATUS_INVALID_PARAMETER; PER_PROCESSOR_PT_DATA & cpuData = g_pDrvData->procData[dwCpuId]; ntStatus = CheckIntelPtSupport(&ptCap); if (!NT_SUCCESS(ntStatus)) return ntStatus; // Check the options now if (opts.Fields.bTraceMtcPcks && (ptCap.bMtcSupport == 0)) return STATUS_NOT_SUPPORTED; if (opts.Fields.bTraceCycPcks && (ptCap.bConfPsbAndCycSupported == 0)) return STATUS_NOT_SUPPORTED; if (opts.Fields.bUseTopa && !(ptCap.bTopaOutput && ptCap.bTopaMultipleEntries)) return STATUS_NOT_SUPPORTED; // Check now the frequency bitmaps: if (opts.Fields.MTCFreq && ((1 << opts.Fields.MTCFreq) & (ptCap.mtcPeriodBmp == 0))) return STATUS_NOT_SUPPORTED; if (opts.Fields.PSBFreq && (ptCap.bConfPsbAndCycSupported == 0)) return STATUS_NOT_SUPPORTED; if (opts.Fields.PSBFreq && ((1 << opts.Fields.PSBFreq) & (ptCap.psbFreqBmp == 0))) return STATUS_NOT_SUPPORTED; if (opts.Fields.CycThresh && (ptCap.bConfPsbAndCycSupported == 0)) return STATUS_NOT_SUPPORTED; if (opts.Fields.CycThresh && ((1 << opts.Fields.CycThresh) & (ptCap.cycThresholdBmp == 0))) return STATUS_NOT_SUPPORTED; // Copy the options opts.Fields.bInitialized = 1; cpuData.TraceOptions = opts; return STATUS_SUCCESS; } // Set the default trace options for a particular CPU NTSTATUS SetDefaultTraceOptions(DWORD dwCpuId) { KAFFINITY curCpuAffinity = 0; DWORD dwNumCpus = 0; INTEL_PT_CAPABILITIES ptCap = { 0 }; NTSTATUS ntStatus = 0; dwNumCpus = KeQueryActiveProcessorCount(&curCpuAffinity); if (!g_pDrvData) return STATUS_INTERNAL_ERROR; if (dwCpuId >= dwNumCpus) return STATUS_INVALID_PARAMETER; PER_PROCESSOR_PT_DATA * lpProcPtData = &g_pDrvData->procData[dwCpuId]; ntStatus = CheckIntelPtSupport(&ptCap); if (!NT_SUCCESS(ntStatus)) return ntStatus; // Set the default trace options if needed if (lpProcPtData->TraceOptions.All == 0) { lpProcPtData->TraceOptions.Fields.bTraceBranchPcks = TRUE; if (ptCap.bTopaOutput) lpProcPtData->TraceOptions.Fields.bUseTopa = TRUE; lpProcPtData->TraceOptions.Fields.bEnableRetCompression = TRUE; } lpProcPtData->TraceOptions.Fields.bInitialized = 1; return STATUS_SUCCESS; } #pragma endregion #pragma region Trace Buffer memory management Code /* BRIEF EXPLANATION HERE * What is the difference between AllocCpuPtBuffer/FreeCpuResources and AllocPtBuffer/FreePtBuffer??? * The 2 functions perform more or less the same work BUT in different ways: * AllocCpuPtBuffer/FreeCpuResources verify if the ONLY buffer associated with the CPU is legal and mapped * to some User-mode address space. THERE IS ONLY ONE BUFFER PER CPU for the driver * * AllocPtBuffer/FreePtBuffer doesn't suffer for this limitations and are used even from external kernel modules. * It's duty of the External module to decide what to do with the buffer descriptor. */ // Allocate a Trace buffer for a specific CPU NTSTATUS AllocCpuPtBuffer(DWORD dwCpuId, QWORD qwSize, BOOLEAN bUseToPA) { NTSTATUS ntStatus = STATUS_SUCCESS; // Returned NTSTATUS value INTEL_PT_CAPABILITIES ptCap = { 0 }; // Current processor capabilities PT_BUFFER_DESCRIPTOR * pPtNewBuffDesc = NULL; // The NEW Buffer descriptor if (dwCpuId > KeQueryActiveProcessorCount(NULL)) return STATUS_INVALID_PARAMETER_3; PER_PROCESSOR_PT_DATA & perCpuData = g_pDrvData->procData[dwCpuId]; ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); // Get this processor capabilities ntStatus = CheckIntelPtSupport(&ptCap); if (!NT_SUCCESS(ntStatus)) return ntStatus; if (bUseToPA && !(ptCap.bTopaOutput && ptCap.bTopaMultipleEntries)) return STATUS_NOT_SUPPORTED; if (!bUseToPA && !ptCap.bSingleRangeSupport) return STATUS_NOT_SUPPORTED; if (perCpuData.curState >= PT_PROCESSOR_STATE_TRACING) return STATUS_INVALID_DEVICE_REQUEST; if (bUseToPA) { if (perCpuData.pPtBuffDesc && perCpuData.pPtBuffDesc->u.ToPA.lpTopaPhysAddr) ntStatus = FreeCpuResources(dwCpuId); if (!NT_SUCCESS(ntStatus)) return ntStatus; // Table of Physical Address usage ntStatus = AllocAndSetTopa(&pPtNewBuffDesc, qwSize); if (NT_SUCCESS(ntStatus)) // Enable the default PMI handler pPtNewBuffDesc->bDefaultPmiSet = TRUE; } else { if (perCpuData.pPtBuffDesc && perCpuData.pPtBuffDesc->u.Simple.lpTraceBuffVa) ntStatus = FreeCpuResources(dwCpuId); if (!NT_SUCCESS(ntStatus)) return ntStatus; ntStatus = AllocPtBuffer(&pPtNewBuffDesc, qwSize, FALSE); } if (NT_SUCCESS(ntStatus)) // Set the current descriptor as default one for current CPU perCpuData.pPtBuffDesc = pPtNewBuffDesc; else ExFreePool(pPtNewBuffDesc); return ntStatus; } // Free the resources used by a CPU NTSTATUS FreeCpuResources(DWORD dwCpuId) { NTSTATUS ntStatus = STATUS_SUCCESS; DWORD dwCurProcId = 0, dwTargetPid = 0; // Current and target Process ID BOOLEAN bExited = FALSE; // True if the target process has already exited KIRQL kIrql = KeGetCurrentIrql(); KAFFINITY curCpuAffinity = 0; DWORD dwNumCpus = 0; dwNumCpus = KeQueryActiveProcessorCount(&curCpuAffinity); if (dwCpuId >= dwNumCpus) return STATUS_INVALID_PARAMETER; PER_PROCESSOR_PT_DATA & perCpuData = g_pDrvData->procData[dwCpuId]; if (perCpuData.curState >= PT_PROCESSOR_STATE_TRACING) return STATUS_INVALID_DEVICE_REQUEST; // Very important: Check the user-mode process here: if (perCpuData.lpUserVa) { dwCurProcId = (DWORD)PsGetCurrentProcessId(); if (perCpuData.lpMappedProc) dwTargetPid = (DWORD)PsGetProcessId(perCpuData.lpMappedProc); bExited = PsGetProcessExitProcessCalled(perCpuData.lpMappedProc); if ((!dwTargetPid || (dwTargetPid == dwCurProcId) || bExited) && kIrql <= APC_LEVEL) { // We can safely unmap the PT buffer here ntStatus = UnmapTraceBuffToUserVa(dwCpuId); if (!NT_SUCCESS(ntStatus)) { DbgPrint("[" DRV_NAME "] Error: Unable to unmap the trace buffer for process %i.\r\n", dwTargetPid); return ntStatus; } } else { DbgPrint("[" DRV_NAME "] Warning: Unable to free the the allocated physical memory for processor %i. The process with PID %i has still not unmapped the buffer. " "Base VA: 0x%llX, physical address: 0x%llX.\r\n", dwCpuId, dwTargetPid, perCpuData.lpUserVa, perCpuData.pPtBuffDesc ? perCpuData.pPtBuffDesc->u.Simple.lpTraceBuffPhysAddr : 0); return STATUS_CONTEXT_MISMATCH; } if (bExited) g_pDrvData->bManualAllocBuff = FALSE; } // Now finally release the buffer PT_BUFFER_DESCRIPTOR * ptBuffDesc = perCpuData.pPtBuffDesc; if (ptBuffDesc) { perCpuData.pPtBuffDesc = NULL; ntStatus = FreePtBuffer(ptBuffDesc); } return ntStatus; } // Allocate and set a buffer for Intel Processor Trace NTSTATUS AllocPtBuffer(PT_BUFFER_DESCRIPTOR ** lppBuffDesc, QWORD qwSize, BOOLEAN bUseTopa) { PHYSICAL_ADDRESS MaxAddr; MaxAddr.QuadPart = -1ll; // Maximum physical address PT_BUFFER_DESCRIPTOR * pBuffDesc = NULL; if (bUseTopa) return AllocAndSetTopa(lppBuffDesc, qwSize, TRUE); // Simple output range implementation LPVOID lpBuffVa = MmAllocateContiguousMemory(qwSize, MaxAddr); if (!lpBuffVa) return STATUS_INSUFFICIENT_RESOURCES; RtlZeroMemory(lpBuffVa, qwSize); // Grab the physical address: PHYSICAL_ADDRESS physAddr = MmGetPhysicalAddress(lpBuffVa); // Allocate the relative MDL PMDL pPtMdl = IoAllocateMdl(lpBuffVa, (ULONG)qwSize, FALSE, FALSE, NULL); if (pPtMdl && lppBuffDesc) { pBuffDesc = (PT_BUFFER_DESCRIPTOR*)ExAllocatePoolWithTag(NonPagedPool, sizeof(PT_BUFFER_DESCRIPTOR), MEMTAG); RtlZeroMemory(pBuffDesc, sizeof(PT_BUFFER_DESCRIPTOR)); pBuffDesc->pTraceMdl = pPtMdl; pBuffDesc->u.Simple.lpTraceBuffVa = lpBuffVa; pBuffDesc->qwBuffSize = qwSize; pBuffDesc->u.Simple.lpTraceBuffPhysAddr = (ULONG_PTR)physAddr.QuadPart; *lppBuffDesc = pBuffDesc; } return STATUS_SUCCESS; } // Free a PT trace buffer (use with caution, avoid BSOD please) NTSTATUS FreePtBuffer(PT_BUFFER_DESCRIPTOR * ptBuffDesc) { //ULONG dwCurCpu = 0; // Current CPU number KIRQL kIrql = KeGetCurrentIrql(); ASSERT(kIrql <= DISPATCH_LEVEL); if (!ptBuffDesc) return STATUS_INVALID_PARAMETER; if (ptBuffDesc->qwBuffSize < PAGE_SIZE) return STATUS_INVALID_BUFFER_SIZE; if (ptBuffDesc->bUseTopa) { // Free the ToPA table if (ptBuffDesc->u.ToPA.lpTopaVa) { MmFreeContiguousMemory(ptBuffDesc->u.ToPA.lpTopaVa); ptBuffDesc->u.ToPA.lpTopaVa = NULL; ptBuffDesc->u.ToPA.lpTopaPhysAddr = NULL; } // Free the actual physical memory if (ptBuffDesc->pTraceMdl) { // Free the used pages MmFreePagesFromMdl(ptBuffDesc->pTraceMdl); ExFreePool(ptBuffDesc->pTraceMdl); ptBuffDesc->pTraceMdl = NULL; } } else { // Free the simple output region if (ptBuffDesc->u.Simple.lpTraceBuffVa) MmFreeContiguousMemory(ptBuffDesc->u.Simple.lpTraceBuffVa); if (ptBuffDesc->pTraceMdl) { IoFreeMdl(ptBuffDesc->pTraceMdl); ptBuffDesc->pTraceMdl = NULL; } ptBuffDesc->u.Simple.lpTraceBuffVa = NULL; ptBuffDesc->u.Simple.lpTraceBuffPhysAddr = NULL; } // Free the data structure itself ExFreePool(ptBuffDesc); return STATUS_SUCCESS; } // Allocate and set a ToPA (with the Windows API) NTSTATUS AllocAndSetTopa(PT_BUFFER_DESCRIPTOR ** lppBuffDesc, QWORD qwReqBuffSize, BOOLEAN bSetPmiAndStop) { NTSTATUS ntStatus = STATUS_SUCCESS; // Returned NTSTATUS DWORD dwNumEntriesInMdl = 0; // Number of entries in the MDL DWORD dwTopaSize = 0; // Size of the ToPa TOPA_TABLE_ENTRY * pTopa = NULL; // Pointer to the ToPa PHYSICAL_ADDRESS highPhysAddr = { (ULONG)-1, -1 }; // Highest physical memory address PHYSICAL_ADDRESS lowPhysAddr = { 0i64 }; // Lowest physical memory address PHYSICAL_ADDRESS topaPhysAddr = { 0i64 }; // The ToPA physical address PMDL pTraceBuffMdl = NULL; PT_BUFFER_DESCRIPTOR * pBuffDesc = NULL; ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); if (qwReqBuffSize % PAGE_SIZE) return STATUS_INVALID_PARAMETER_2; // Allocate the needed physical memory pTraceBuffMdl = MmAllocatePagesForMdlEx(lowPhysAddr, highPhysAddr, lowPhysAddr, (SIZE_T)qwReqBuffSize + PAGE_SIZE, MmCached, MM_ALLOCATE_FULLY_REQUIRED); if (!pTraceBuffMdl) return STATUS_INSUFFICIENT_RESOURCES; // Get the PFN array dwNumEntriesInMdl = ADDRESS_AND_SIZE_TO_SPAN_PAGES(MmGetMdlVirtualAddress(pTraceBuffMdl), MmGetMdlByteCount(pTraceBuffMdl)); PPFN_NUMBER pfnArray = MmGetMdlPfnArray(pTraceBuffMdl); // Allocate the ToPA dwTopaSize = (dwNumEntriesInMdl + 1) * 8; dwTopaSize = ROUND_TO_PAGES(dwTopaSize); pTopa = (TOPA_TABLE_ENTRY *)MmAllocateContiguousMemory(dwTopaSize, highPhysAddr); topaPhysAddr = MmGetPhysicalAddress(pTopa); if (!pTopa) { MmFreePagesFromMdl(pTraceBuffMdl); ExFreePool(pTraceBuffMdl); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(pTopa, dwTopaSize); // Create the ToPA for (DWORD i = 0; i < dwNumEntriesInMdl; i++) { pTopa[i].Fields.BaseAddr = pfnArray[i]; // Pfn array contains the PFN offset, not the actual Physical address pTopa[i].Fields.Size = 0; // Encoding: 0 - 4K pages } // LVT interrupt entry (if any) if (bSetPmiAndStop) { pTopa[dwNumEntriesInMdl - 1].Fields.Int = 1; pTopa[dwNumEntriesInMdl - 1].Fields.Stop = 1; } // END entries RtlZeroMemory(&pTopa[dwNumEntriesInMdl], sizeof(TOPA_TABLE_ENTRY)); pTopa[dwNumEntriesInMdl].Fields.BaseAddr = (ULONG_PTR)(topaPhysAddr.QuadPart >> 0xC); pTopa[dwNumEntriesInMdl].Fields.End = 1; // Now create the descriptor and set the ToPA data if (lppBuffDesc) { pBuffDesc = (PT_BUFFER_DESCRIPTOR*)ExAllocatePoolWithTag(NonPagedPool, sizeof(PT_BUFFER_DESCRIPTOR), MEMTAG); RtlZeroMemory(pBuffDesc, sizeof(PT_BUFFER_DESCRIPTOR)); pBuffDesc->bUseTopa = TRUE; pBuffDesc->u.ToPA.lpTopaPhysAddr = (ULONG_PTR)topaPhysAddr.QuadPart; pBuffDesc->u.ToPA.lpTopaVa = pTopa; pBuffDesc->qwBuffSize = qwReqBuffSize; pBuffDesc->pTraceMdl = pTraceBuffMdl; pBuffDesc->bDefaultPmiSet = bSetPmiAndStop; *lppBuffDesc = pBuffDesc; } return ntStatus; } // Get if the PT buffer is allocated and valid for a particular processor QWORD IsPtBufferAllocatedAndValid(DWORD dwCpuId, BOOLEAN bTestUserVa) { PER_PROCESSOR_PT_DATA * pPerCpuData = NULL; if (dwCpuId > KeQueryActiveProcessorCount(NULL)) return FALSE; pPerCpuData = &g_pDrvData->procData[dwCpuId]; if (bTestUserVa) if (!pPerCpuData->lpUserVa) return 0; if (!(pPerCpuData->pPtBuffDesc && pPerCpuData->pPtBuffDesc->u.Simple.lpTraceBuffPhysAddr)) return 0; // Return the size of the buffer return pPerCpuData->pPtBuffDesc->qwBuffSize; } // Clear the PT buffer NTSTATUS ClearCpuPtBuffer(DWORD dwCpuId) { PER_PROCESSOR_PT_DATA * pPerCpuData = NULL; LPVOID lpKernelVa = NULL; // The kernel VA if (dwCpuId > KeQueryActiveProcessorCount(NULL)) return FALSE; pPerCpuData = &g_pDrvData->procData[dwCpuId]; if (!pPerCpuData->pPtBuffDesc || !pPerCpuData->pPtBuffDesc->u.Simple.lpTraceBuffPhysAddr) return STATUS_NOT_FOUND; if (!pPerCpuData->pPtBuffDesc->pTraceMdl) return STATUS_INTERNAL_ERROR; if (!pPerCpuData->pPtBuffDesc->lpKernelVa) { // We do not want to map the MDL in Kernel Address space, try to use the User-mode VA if (pPerCpuData->lpUserVa && PsGetCurrentProcess() == pPerCpuData->lpMappedProc) { RtlZeroMemory(pPerCpuData->lpUserVa, (DWORD)pPerCpuData->pPtBuffDesc->qwBuffSize); return STATUS_SUCCESS; } else { DrvDbgPrint("[" DRV_NAME "] Warning, the ClearCpuPtBuffer routine is re-mapping the PT buffer (size 0x%08X) for CPU %i in Kernel mode. " "This could be very time consuming. Are you sure that is needed?\r\n", (DWORD)pPerCpuData->pPtBuffDesc->qwBuffSize, dwCpuId); lpKernelVa = MmGetSystemAddressForMdlSafe(pPerCpuData->pPtBuffDesc->pTraceMdl, NormalPagePriority); if (!lpKernelVa) return STATUS_INTERNAL_ERROR; RtlZeroMemory(lpKernelVa, (DWORD)pPerCpuData->pPtBuffDesc->qwBuffSize); MmUnmapLockedPages(lpKernelVa, pPerCpuData->pPtBuffDesc->pTraceMdl); } } return STATUS_SUCCESS; } #pragma code_seg() // Map a physical page buffer to a User-mode process // Only one PT buffer per CPU supported in Usermode NTSTATUS MapTracePhysBuffToUserVa(DWORD dwCpuId) { PMDL pMdl = NULL; // The new MDL describing the physical memory LPVOID lpUserBuff = NULL; // The user-mode accessible buffer PEPROCESS pCurProc = NULL; // The current EPROCESS target if (!g_pDrvData->procData[dwCpuId].pPtBuffDesc) return STATUS_NO_MEMORY; PT_BUFFER_DESCRIPTOR * pPtBuffDesc = g_pDrvData->procData[dwCpuId].pPtBuffDesc; // This should be executed at IRQL level <= APC for MmMapLockedPagesSpecifyCache ASSERT(KeGetCurrentIrql() <= APC_LEVEL); if (!pPtBuffDesc->u.Simple.lpTraceBuffVa || !pPtBuffDesc->qwBuffSize) return STATUS_INVALID_PARAMETER; if (pPtBuffDesc->bUseTopa) { // Table of Physical Address Implementation pMdl = pPtBuffDesc->pTraceMdl; if (!pMdl) return STATUS_INTERNAL_ERROR; } else { // Simple-output scheme implementation if (!pPtBuffDesc->u.Simple.lpTraceBuffPhysAddr) { PHYSICAL_ADDRESS physAddr = MmGetPhysicalAddress(pPtBuffDesc->u.Simple.lpTraceBuffVa); pPtBuffDesc->u.Simple.lpTraceBuffPhysAddr = (ULONG_PTR)physAddr.QuadPart; } if (pPtBuffDesc->pTraceMdl) pMdl = pPtBuffDesc->pTraceMdl; else pMdl = IoAllocateMdl(pPtBuffDesc->u.Simple.lpTraceBuffVa, (ULONG)pPtBuffDesc->qwBuffSize, FALSE, FALSE, NULL); // Update this MDL to describe the underlying already-locked physical pages MmBuildMdlForNonPagedPool(pMdl); // do this only here and nowhere else pPtBuffDesc->pTraceMdl = pMdl; if (!pMdl) return STATUS_INSUFFICIENT_RESOURCES; } pCurProc = PsGetCurrentProcess(); // Now map the MDL to the current user-mode process // If AccessMode is Usermode, the caller must be running at IRQL <= APC_LEVEL lpUserBuff = MmMapLockedPagesSpecifyCache(pMdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority); if (lpUserBuff) { g_pDrvData->procData[dwCpuId].lpUserVa = lpUserBuff; g_pDrvData->procData[dwCpuId].lpMappedProc = pCurProc; ObReferenceObject(pCurProc); // prevent process termination without freeing the resource return STATUS_SUCCESS; } else return STATUS_UNSUCCESSFUL; } // Unmap the memory-mapped physical memory from usermode // Only one PT buffer per CPU supported in USER-mode NTSTATUS UnmapTraceBuffToUserVa(DWORD dwCpuId) { PEPROCESS pCurProc = NULL; // The current EPROCESS target PER_PROCESSOR_PT_DATA * pPerCpuData = &g_pDrvData->procData[dwCpuId]; pCurProc = PsGetCurrentProcess(); if (pPerCpuData->lpUserVa) { BOOLEAN bExited = FALSE; PEPROCESS pMappedProc = pPerCpuData->lpMappedProc; if (!pPerCpuData->pPtBuffDesc || !pPerCpuData->pPtBuffDesc->pTraceMdl) return STATUS_INTERNAL_ERROR; // THIS SHOULD NEVER HAPPEN // Get if the mapped process is already terminated if (pMappedProc) bExited = PsGetProcessExitProcessCalled(pMappedProc); if (pMappedProc && (bExited == FALSE) && (pCurProc != pMappedProc)) return STATUS_CONTEXT_MISMATCH; if (!bExited) MmUnmapLockedPages(pPerCpuData->lpUserVa, pPerCpuData->pPtBuffDesc->pTraceMdl); pPerCpuData->lpUserVa = NULL; pPerCpuData->lpMappedProc = NULL; ObDereferenceObject(pMappedProc); } return STATUS_SUCCESS; } #pragma endregion #pragma region PMI Interrupt management code #pragma code_seg(".nonpaged") // Register the LVT (Local Vector Table) PMI interrupt NTSTATUS RegisterPmiInterrupt() { NTSTATUS ntStatus = STATUS_SUCCESS; // Returned NTSTATUS PMIHANDLER pNewPmiHandler = NULL; // The new PMI handler routine INT CpuIdData[4] = { 0 }; // The CPUID data //PMIHANDLER pOldPmiHandler = NULL; // The old PMI handler (currently not implemented) BOOLEAN bX2ApicCompat = FALSE; BYTE lpBuff[0x20] = { 0 }; //XXX ULONG dwBytesIo = 0; // Number of I/O bytes // First of all we need to check if this system supports APIC or x2Apic __cpuidex(CpuIdData, 1, 0); bX2ApicCompat = (CpuIdData[2] & (1 < 21)) == 1; MSR_IA32_APIC_BASE_DESC ApicBase = { 0 }; // In Multi-processors systems this address could change ApicBase.All = __readmsr(MSR_IA32_APIC_BASE); // In Windows systems all the processors LVT are mapped at the same physical address if (!ApicBase.Fields.EXTD) { LPDWORD lpdwApicBase = NULL; PHYSICAL_ADDRESS apicPhys = { 0 }; // Map the APIC I/O space (each DWORD is a LVT_Entry structure) apicPhys.QuadPart = ApicBase.All & (~0xFFFi64); lpdwApicBase = (LPDWORD)MmMapIoSpace(apicPhys, 0x1000, MmNonCached); if (lpdwApicBase) { DrvDbgPrint("[" DRV_NAME "] Successfully mapped the local APIC to 0x%llX.\r\n", lpdwApicBase); g_pDrvData->lpApicBase = lpdwApicBase; } else return STATUS_NOT_SUPPORTED; // Now read the entry 0x340 (not really needed) g_pDrvData->pmiVectDesc.All = lpdwApicBase[0x340 / 4]; } else { // Current system uses x2APIC mode, no need to map anything g_pDrvData->bCpuX2ApicMode = TRUE; // From Intel Manual: In x2APIC mode, system software uses RDMSR and WRMSR to access the APIC registers. } // The following functions must be stored in HalDispatchTable // TODO: Find a way to proper get the old PMI interrupt handler routine. Search inside the HAL code? // ntStatus = HalQuerySystemInformation(HalProfileSourceInformation, COUNTOF(lpBuff), (LPVOID)lpBuff, &dwBytesIo); // Now set the new PMI handler, WARNING: we do not save and restore old handler pNewPmiHandler = IntelPtPmiHandler; ntStatus = HalSetSystemInformation(HalProfileSourceInterruptHandler, sizeof(PMIHANDLER), (LPVOID)&pNewPmiHandler); if (NT_SUCCESS(ntStatus)) { DrvDbgPrint("[" DRV_NAME "] Successfully registered system PMI handler to function 0x%llX.\r\n", (LPVOID)pNewPmiHandler); g_pDrvData->bPmiInstalled = TRUE; } return ntStatus; } // Unregister and remove the LVT PMI interrupt NTSTATUS UnregisterPmiInterrupt() { NTSTATUS ntStatus = STATUS_SUCCESS; // Returned NTSTATUS PMIHANDLER pOldPmiHandler = g_pDrvData->pOldPmiHandler; // The old PMI handler // This is currently not restoring old PMI handler since we don't know how to retrieve it, just nulling it out ntStatus = HalSetSystemInformation(HalProfileSourceInterruptHandler, sizeof(PMIHANDLER), (LPVOID)&pOldPmiHandler); if (NT_SUCCESS(ntStatus)) { g_pDrvData->bPmiInstalled = FALSE; if (g_pDrvData->lpApicBase) MmUnmapIoSpace(g_pDrvData->lpApicBase, 0x1000); } return ntStatus; } // The PMI LVT handler routine (Warning! This should run at very high IRQL) VOID IntelPtPmiHandler(PKTRAP_FRAME pTrapFrame) { PKDPC pProcDpc = NULL; // This processor DPC MSR_IA32_PERF_GLOBAL_STATUS_DESC pmiDesc = { 0 }; // The PMI Interrupt descriptor LVT_Entry perfMonDesc = { 0 }; // The LVT Performance Monitoring register LPDWORD lpdwApicBase = g_pDrvData->lpApicBase; // The LVT Apic I/O space base address (if not in x2Apic mode) DWORD dwCurCpu = 0; PER_PROCESSOR_PT_DATA * pCurCpuData = NULL; // The Per-Processor data structure PT_BUFFER_DESCRIPTOR * ptBuffDesc = NULL; // The PT Buffer descriptor UNREFERENCED_PARAMETER(pTrapFrame); ASSERT(KeGetCurrentIrql() > DISPATCH_LEVEL); dwCurCpu = KeGetCurrentProcessorNumber(); pCurCpuData = &g_pDrvData->procData[dwCurCpu]; ptBuffDesc = g_pDrvData->procData[dwCurCpu].pPtBuffDesc; // Check if the interrupt is mine pmiDesc.All = __readmsr(MSR_IA32_PERF_GLOBAL_STATUS); if (pmiDesc.Fields.TraceToPAPMI == 0) return; // Pause the Tracing. From Intel's Manual: "Software can minimize the likelihood of the second case by clearing // TraceEn at the beginning of the PMI handler" PauseResumeTrace(TRUE); // Check the Intel PT status MSR_RTIT_STATUS_DESC traceStatusDesc = { 0 }; traceStatusDesc.All = __readmsr(MSR_IA32_RTIT_STATUS); if (traceStatusDesc.Fields.Error) DrvDbgPrint("[" DRV_NAME "] Warning: Intel PT Pmi has raised, but the PT Status register indicates an error!\r\n"); if (ptBuffDesc && ptBuffDesc->bDefaultPmiSet) { // Queue a DPC only if the Default PMI handler is set ptBuffDesc->bBuffIsFull = TRUE; // The IRQL is too high so we use DPC pProcDpc = (PKDPC)ExAllocatePoolWithTag(NonPagedPool, sizeof(KDPC), MEMTAG); KeInitializeDpc(pProcDpc, IntelPmiDpc, NULL); KeSetTargetProcessorDpc(pProcDpc, (CCHAR)dwCurCpu); KeInsertQueueDpc(pProcDpc, (LPVOID)dwCurCpu, NULL); } MSR_IA32_PERF_GLOBAL_OVF_CTRL_DESC globalResetMsrDesc = { 0 }; // Set the PMI Reset: Once the ToPA PMI handler has serviced the relevant buffer, writing 1 to bit 55 of the MSR at 390H // (IA32_GLOBAL_STATUS_RESET)clears IA32_PERF_GLOBAL_STATUS.TraceToPAPMI. globalResetMsrDesc.Fields.ClrTraceToPA_PMI = 1; __writemsr(MSR_IA32_PERF_GLOBAL_OVF_CTRL, globalResetMsrDesc.All); // Call the External PMI handler (if any) if (g_pDrvData->pCustomPmiIsr) { g_pDrvData->pCustomPmiIsr(dwCurCpu, ptBuffDesc); } // Re-enable the PMI if (g_pDrvData->bCpuX2ApicMode) { // Check Intel Manuals, Vol. 3A section 10-12 // The local APIC registers can be accessed via the MSR interface only when the local APIC has // been switched to the x2APIC mode as described ULONGLONG perfMonEntry = __readmsr(MSR_IA32_X2APIC_LVT_PMI); perfMonDesc.All = (ULONG)perfMonEntry; perfMonDesc.Fields.Masked = 0; perfMonEntry = (ULONGLONG)perfMonDesc.All; __writemsr(MSR_IA32_X2APIC_LVT_PMI, perfMonEntry); } else { if (!lpdwApicBase) // XXX: Not sure how to continue, No MmMapIoSpace at this IRQL (should not happen) KeBugCheckEx(INTERRUPT_EXCEPTION_NOT_HANDLED, NULL, NULL, NULL, NULL); // Access the APIC register through the I/O port perfMonDesc.All = lpdwApicBase[0x340 / 4]; perfMonDesc.Fields.Masked = 0; lpdwApicBase[0x340 / 4] = perfMonDesc.All; } }; // The Kernel mode PMI callback APC VOID ApcKernelRoutine(PKAPC pApc, PKNORMAL_ROUTINE *NormalRoutine, PVOID *NormalContext, PVOID *SystemArgument1, PVOID *SystemArgument2) { UNREFERENCED_PARAMETER(NormalRoutine); UNREFERENCED_PARAMETER(NormalContext); UNREFERENCED_PARAMETER(SystemArgument1); UNREFERENCED_PARAMETER(SystemArgument2); // ??? What to do here?? // Simple only free the APC structure if (pApc) ExFreePool(pApc); } // Check and clean the dead PMI callbacks NTSTATUS CheckUserPmiCallbackList() { KIRQL kOldIrql = KeGetCurrentIrql(); PLIST_ENTRY pNextEntry = NULL, pCurEntry = NULL; if (IsListEmpty(&g_pDrvData->userCallbackList)) return STATUS_NOT_FOUND; KeAcquireSpinLock(&g_pDrvData->userCallbackListLock, &kOldIrql); pNextEntry = g_pDrvData->userCallbackList.Flink; while (pNextEntry != &g_pDrvData->userCallbackList) { PPMI_USER_CALLBACK_DESC pCurPmiDesc = NULL; pCurEntry = pNextEntry; pCurPmiDesc = CONTAINING_RECORD(pCurEntry, PMI_USER_CALLBACK_DESC, entry); if (PsIsThreadTerminating(pCurPmiDesc->pTargetThread)) { // Auto delete this damn entry pNextEntry = pCurEntry->Flink; RemoveEntryList(pCurEntry); DrvDbgPrint("[" DRV_NAME "] Successfully removed dead user-mode PMI Callback (Thread ID: %i, Address: 0x%llX).\r\n", PsGetThreadId(pCurPmiDesc->pTargetThread), pCurPmiDesc->lpUserAddress); ObDereferenceObject(pCurPmiDesc->pTargetThread); ExFreePool(pCurPmiDesc); continue; } pNextEntry = pCurEntry->Flink; } KeReleaseSpinLock(&g_pDrvData->userCallbackListLock, kOldIrql); return STATUS_SUCCESS; } // Clear the user PMI Callback list and free the memory NTSTATUS ClearAndFreePmiCallbackList() { KIRQL kOldIrql = KeGetCurrentIrql(); PLIST_ENTRY pCurEntry = NULL; PPMI_USER_CALLBACK_DESC pCurCallback = NULL; if (IsListEmpty(&g_pDrvData->userCallbackList)) return STATUS_NOT_FOUND; KeAcquireSpinLock(&g_pDrvData->userCallbackListLock, &kOldIrql); while (TRUE) { pCurEntry = RemoveHeadList(&g_pDrvData->userCallbackList); if (pCurEntry == &g_pDrvData->userCallbackList) break; pCurCallback = CONTAINING_RECORD(pCurEntry, PMI_USER_CALLBACK_DESC, entry); ObDereferenceObject(pCurCallback->pTargetThread); ExFreePool(pCurCallback); } KeReleaseSpinLock(&g_pDrvData->userCallbackListLock, kOldIrql); return STATUS_SUCCESS; } // The PMI DPC routine VOID IntelPmiDpc(struct _KDPC *pDpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2) { UNREFERENCED_PARAMETER(DeferredContext); UNREFERENCED_PARAMETER(SystemArgument1); UNREFERENCED_PARAMETER(SystemArgument2); DWORD dwCpuNum = KeGetCurrentProcessorNumber(); // This CPU number ULONGLONG targetCr3 = 0ui64; // The target CR3 register value KIRQL kOldIrql = KeGetCurrentIrql(); // A quick integrity check ASSERT(dwCpuNum == (DWORD)SystemArgument1); PER_PROCESSOR_PT_DATA & curCpuData = g_pDrvData->procData[dwCpuNum]; // This processor DPC data if (curCpuData.lpTargetProc) { // Verify that the Target CR3 still matches targetCr3 = ((ULONGLONG*)curCpuData.lpTargetProc)[5]; ASSERT(targetCr3 == curCpuData.lpTargetProcCr3); // queue work item to suspend the target process PWORK_QUEUE_ITEM pWorkItem = (PWORK_QUEUE_ITEM)ExAllocatePoolWithTag(NonPagedPool, sizeof(WORK_QUEUE_ITEM) + sizeof(LPVOID), MEMTAG); if (pWorkItem) { ExInitializeWorkItem(pWorkItem, IntelPmiWorkItem, (PVOID)pWorkItem); *((LPVOID*)(LPBYTE(pWorkItem) + sizeof(WORK_QUEUE_ITEM))) = (LPVOID)curCpuData.lpTargetProc; ExQueueWorkItem(pWorkItem, CriticalWorkQueue); } } // Set the Buffer full Event (if any) if (g_pDrvData->pPmiEvent) KeSetEvent(g_pDrvData->pPmiEvent, IO_NO_INCREMENT, FALSE); // Queue the User-mode APC and call the User-mode Callbacks if (!IsListEmpty(&g_pDrvData->userCallbackList)) { PLIST_ENTRY pNextEntry = NULL, // Next entry pCurEntry = NULL; // Current entry PRKAPC pkApc = NULL; KeAcquireSpinLock(&g_pDrvData->userCallbackListLock, &kOldIrql); pNextEntry = g_pDrvData->userCallbackList.Flink; while (pNextEntry != &g_pDrvData->userCallbackList) { PPMI_USER_CALLBACK_DESC pCurPmiDesc = NULL; BOOLEAN bApcInserted = FALSE; pCurEntry = pNextEntry; pCurPmiDesc = CONTAINING_RECORD(pCurEntry, PMI_USER_CALLBACK_DESC, entry); if ((1i64 << dwCpuNum) & pCurPmiDesc->kAffinity) { // Found a valid User-mode callback, verify it and call it if (PsIsThreadTerminating(pCurPmiDesc->pTargetThread)) { // Auto delete this damn entry pNextEntry = pCurEntry->Flink; RemoveEntryList(pCurEntry); ObDereferenceObject(pCurPmiDesc->pTargetThread); ExFreePool(pCurPmiDesc); continue; } pkApc = (PRKAPC)ExAllocatePoolWithTag(NonPagedPool, sizeof(KAPC), MEMTAG); if (pkApc) { KeInitializeApc(pkApc, (PRKTHREAD)pCurPmiDesc->pTargetThread, CurrentApcEnvironment, &ApcKernelRoutine, NULL, (PKNORMAL_ROUTINE)pCurPmiDesc->lpUserAddress, UserMode, (PVOID)dwCpuNum); bApcInserted = KeInsertQueueApc(pkApc, (LPVOID)curCpuData.lpUserVa, (LPVOID)curCpuData.pPtBuffDesc->qwBuffSize, IO_NO_INCREMENT); } ASSERT(pkApc != NULL && bApcInserted == TRUE); } pNextEntry = pCurEntry->Flink; } KeReleaseSpinLock(&g_pDrvData->userCallbackListLock, kOldIrql); } ExFreePool(pDpc); } // The PMI Work Item VOID IntelPmiWorkItem(PVOID Parameter) { PWORK_QUEUE_ITEM pWorkItem = NULL; // This work item PEPROCESS pTargetProc = NULL; // The Target Process NTSTATUS ntStatus = STATUS_ABANDONED; // The returned NTSTATUS DWORD dwProcId = 0; // The target process ID if (!Parameter) return; pWorkItem = (PWORK_QUEUE_ITEM)Parameter; pTargetProc = *(PEPROCESS*)((LPBYTE)Parameter + sizeof(WORK_QUEUE_ITEM)); dwProcId = (DWORD)PsGetProcessId(pTargetProc); ntStatus = PsSuspendProcess(pTargetProc); if (NT_SUCCESS(ntStatus)) DrvDbgPrint("[" DRV_NAME "] Successfully suspended process ID: %i.\r\n", dwProcId); ExFreePool(pWorkItem); } #pragma code_seg() #pragma endregion