mirror of
https://github.com/HyperDbg/HyperDbg.git
synced 2026-07-10 01:29:59 +00:00
1610 lines
49 KiB
C++
1610 lines
49 KiB
C++
/**
|
|
* @file pt.cpp
|
|
* @author Masoud Rahimi Jafari (Masoodrahimy1379@gmail.com)
|
|
* @author Sina Karvandi (sina@hyperdbg.org)
|
|
* @brief !pt command
|
|
* @details
|
|
* @version 0.19
|
|
* @date 2026-04-29
|
|
*
|
|
* @copyright This project is released under the GNU Public License v3.
|
|
*
|
|
*/
|
|
#include "pch.h"
|
|
|
|
//
|
|
// Global Variables
|
|
//
|
|
extern BOOLEAN g_IsHyperTraceModuleLoaded;
|
|
extern BOOLEAN g_IsSerialConnectedToRemoteDebuggee;
|
|
|
|
/**
|
|
* @brief help of the !pt command
|
|
*
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
CommandPtHelp()
|
|
{
|
|
ShowMessages("!pt : enables, disables and configures Intel Processor Trace (PT).\n\n");
|
|
|
|
ShowMessages("syntax : \t!pt enable\n");
|
|
ShowMessages("syntax : \t!pt enable [size BufferSize (hex)]\n");
|
|
ShowMessages("syntax : \t!pt enable [pid ProcessId (hex)] [size BufferSize (hex)] [core CoreId (hex)]\n");
|
|
ShowMessages("syntax : \t!pt enable [tid ThreadId (hex)] [size BufferSize (hex)] [core CoreId (hex)]\n");
|
|
ShowMessages("syntax : \t!pt enable [pname ProcessName (string)] [size BufferSize (hex)] [core CoreId (hex)]\n");
|
|
ShowMessages("syntax : \t!pt enable [path Path (string)] [size BufferSize (hex)] [core CoreId (hex)]\n");
|
|
ShowMessages("syntax : \t!pt enable [cr3 Cr3Value (hex)] [size BufferSize (hex)]\n");
|
|
ShowMessages("syntax : \t!pt disable\n");
|
|
ShowMessages("syntax : \t!pt pause\n");
|
|
ShowMessages("syntax : \t!pt resume\n");
|
|
ShowMessages("syntax : \t!pt flush\n");
|
|
ShowMessages("syntax : \t!pt dump print [type TypeOfDump (string)]\n");
|
|
ShowMessages("syntax : \t!pt dump path [type TypeOfDump (string)]\n");
|
|
ShowMessages("syntax : \t!pt filter [Mode (string)]\n");
|
|
ShowMessages("syntax : \t!pt filter [range1 FromAddress (hex) ToAddress (hex)] [range2 FromAddress (hex) ToAddress (hex)] [range3 FromAddress (hex) ToAddress (hex)] [range4 FromAddress (hex) ToAddress (hex)]\n");
|
|
ShowMessages("syntax : \t!pt filter [range1 module ModuleName (string)] [range2 module ModuleName (string)] [range3 module ModuleName (string)] [range4 module ModuleName (string)]\n");
|
|
ShowMessages("syntax : \t!pt filter [stoprange1 FromAddress (hex) ToAddress (hex)] [stoprange2 FromAddress (hex) ToAddress (hex)] [stoprange3 FromAddress (hex) ToAddress (hex)] [stoprange4 FromAddress (hex) ToAddress (hex)]\n");
|
|
ShowMessages("syntax : \t!pt filter [stoprange1 module ModuleName (string)] [stoprange2 module ModuleName (string)] [stoprange3 module ModuleName (string)] [stoprange4 module ModuleName (string)]\n");
|
|
ShowMessages("syntax : \t!pt packet [PacketType (string)]\n");
|
|
|
|
ShowMessages("\n");
|
|
ShowMessages("\t\te.g : !pt enable\n");
|
|
ShowMessages("\t\te.g : !pt enable size 0x200000\n");
|
|
ShowMessages("\t\te.g : !pt enable pid 0x4a8\n");
|
|
ShowMessages("\t\te.g : !pt enable pname notepad.exe\n");
|
|
ShowMessages("\t\te.g : !pt enable tid 0x1234 core 3\n");
|
|
ShowMessages("\t\te.g : !pt enable cr3 0x1aabb000\n");
|
|
ShowMessages("\t\te.g : !pt enable pid 0x4a8 size 0x200000\n");
|
|
ShowMessages("\t\te.g : !pt enable path \"c:\\programs\\my exe file.exe\" size 0x200000 core 3\n");
|
|
ShowMessages("\t\te.g : !pt disable\n");
|
|
ShowMessages("\t\te.g : !pt pause\n");
|
|
ShowMessages("\t\te.g : !pt resume\n");
|
|
ShowMessages("\t\te.g : !pt flush\n");
|
|
ShowMessages("\t\te.g : !pt dump print type instruction\n");
|
|
ShowMessages("\t\te.g : !pt dump print type packet\n");
|
|
ShowMessages("\t\te.g : !pt dump path C:\\trace.txt type instruction\n");
|
|
ShowMessages("\t\te.g : !pt filter user\n");
|
|
ShowMessages("\t\te.g : !pt filter user kernel\n");
|
|
ShowMessages("\t\te.g : !pt filter range1 0x140001000 0x140002000\n");
|
|
ShowMessages("\t\te.g : !pt filter range1 module main\n");
|
|
ShowMessages("\t\te.g : !pt filter range1 module ntdll range2 module nt\n");
|
|
ShowMessages("\t\te.g : !pt filter stoprange1 0x140003000 0x140004000\n");
|
|
ShowMessages("\t\te.g : !pt packet psb pip tsc\n");
|
|
|
|
ShowMessages("\n");
|
|
ShowMessages("Where:\n");
|
|
ShowMessages("\t[Mode (string)] could be 'kernel' or/and 'user'\n");
|
|
ShowMessages("\t[TypeOfDump (string)] could be 'instruction' or 'packet'\n");
|
|
ShowMessages("\t[ModuleName (string)] could be 'main' (the main module of the process), 'nt', 'win32k', or any other module name\n");
|
|
ShowMessages("\t[PacketType (string)] could be either or a combination of 'psb', 'pip', 'tsc', 'mtc', 'cyc', 'tnt', 'tip', 'fup', or 'mode'\n");
|
|
ShowMessages("\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Send PT requests
|
|
*
|
|
* @param PtRequest
|
|
*
|
|
* @return VOID
|
|
*/
|
|
BOOLEAN
|
|
CommandPtSendRequest(HYPERTRACE_PT_OPERATION_PACKETS * PtRequest)
|
|
{
|
|
BOOL Status;
|
|
ULONG ReturnedLength;
|
|
|
|
if (g_IsSerialConnectedToRemoteDebuggee)
|
|
{
|
|
//
|
|
// Send the request over serial kernel debugger
|
|
//
|
|
if (!KdSendHyperTracePtPacketsToDebuggee(PtRequest, SIZEOF_HYPERTRACE_PT_OPERATION_PACKETS))
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AssertShowMessageReturnStmt(g_IsHyperTraceModuleLoaded, g_DeviceHandle, ASSERT_MESSAGE_HYPERTRACE_NOT_LOADED, ASSERT_MESSAGE_DRIVER_NOT_LOADED, AssertReturnFalse);
|
|
|
|
//
|
|
// Send IOCTL
|
|
//
|
|
Status = DeviceIoControl(
|
|
g_DeviceHandle, // Handle to device
|
|
IOCTL_PERFORM_HYPERTRACE_PT_OPERATION, // IO Control Code (IOCTL)
|
|
PtRequest, // Input Buffer to driver.
|
|
SIZEOF_HYPERTRACE_PT_OPERATION_PACKETS, // Input buffer length
|
|
PtRequest, // Output Buffer from driver.
|
|
SIZEOF_HYPERTRACE_PT_OPERATION_PACKETS, // Length of output buffer in bytes.
|
|
&ReturnedLength, // Bytes placed in buffer.
|
|
NULL // synchronous call
|
|
);
|
|
|
|
if (!Status)
|
|
{
|
|
ShowMessages("ioctl failed with code 0x%x\n", GetLastError());
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (PtRequest->KernelStatus == DEBUGGER_OPERATION_WAS_SUCCESSFUL)
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Request to perform an PT operation
|
|
*
|
|
* @param PtRequest
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
HyperDbgPerformPtOperation(HYPERTRACE_PT_OPERATION_PACKETS * PtRequest)
|
|
{
|
|
return CommandPtSendRequest(PtRequest);
|
|
}
|
|
|
|
/**
|
|
* @brief Send pt enable command
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
static BOOLEAN
|
|
CommandPtSendEnable()
|
|
{
|
|
HYPERTRACE_PT_OPERATION_PACKETS PtRequest = {};
|
|
|
|
//
|
|
// Set the PtRequest structure for the operation
|
|
//
|
|
PtRequest.PtOperationType = HYPERTRACE_PT_OPERATION_REQUEST_TYPE_ENABLE;
|
|
|
|
//
|
|
// Send the request to perform the operation
|
|
//
|
|
if (!HyperDbgPerformPtOperation(&PtRequest))
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Send pt disable command
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
static BOOLEAN
|
|
CommandPtSendDisable()
|
|
{
|
|
HYPERTRACE_PT_OPERATION_PACKETS PtRequest = {};
|
|
|
|
//
|
|
// Set the PtRequest structure for the operation
|
|
//
|
|
PtRequest.PtOperationType = HYPERTRACE_PT_OPERATION_REQUEST_TYPE_DISABLE;
|
|
|
|
//
|
|
// Send the request to perform the operation
|
|
//
|
|
if (!HyperDbgPerformPtOperation(&PtRequest))
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Send pt pause command
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
static BOOLEAN
|
|
CommandPtSendPause()
|
|
{
|
|
HYPERTRACE_PT_OPERATION_PACKETS PtRequest = {};
|
|
|
|
//
|
|
// Set the PtRequest structure for the operation
|
|
//
|
|
PtRequest.PtOperationType = HYPERTRACE_PT_OPERATION_REQUEST_TYPE_PAUSE;
|
|
|
|
//
|
|
// Send the request to perform the operation
|
|
//
|
|
if (!HyperDbgPerformPtOperation(&PtRequest))
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Send pt resume command
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
static BOOLEAN
|
|
CommandPtSendResume()
|
|
{
|
|
HYPERTRACE_PT_OPERATION_PACKETS PtRequest = {};
|
|
|
|
//
|
|
// Set the PtRequest structure for the operation
|
|
//
|
|
PtRequest.PtOperationType = HYPERTRACE_PT_OPERATION_REQUEST_TYPE_RESUME;
|
|
|
|
//
|
|
// Send the request to perform the operation
|
|
//
|
|
if (!HyperDbgPerformPtOperation(&PtRequest))
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Send pt flush command
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
static BOOLEAN
|
|
CommandPtSendFlush()
|
|
{
|
|
HYPERTRACE_PT_OPERATION_PACKETS PtRequest = {};
|
|
|
|
//
|
|
// Set the PtRequest structure for the operation
|
|
//
|
|
PtRequest.PtOperationType = HYPERTRACE_PT_OPERATION_REQUEST_TYPE_FLUSH;
|
|
|
|
//
|
|
// Send the request to perform the operation
|
|
//
|
|
if (!HyperDbgPerformPtOperation(&PtRequest))
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Send pt filter command
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
static BOOLEAN
|
|
CommandPtSendFilterByPid(UINT32 ProcessId,
|
|
BOOLEAN IsUserMode,
|
|
BOOLEAN IsKernelMode,
|
|
UINT64 StartRange1,
|
|
UINT64 EndRange1,
|
|
UINT64 StartRange2,
|
|
UINT64 EndRange2,
|
|
UINT64 StartRange3,
|
|
UINT64 EndRange3,
|
|
UINT64 StartRange4,
|
|
UINT64 EndRange4)
|
|
{
|
|
HYPERTRACE_PT_OPERATION_PACKETS Op = {};
|
|
UINT8 NumberOfRanges = 0;
|
|
|
|
//
|
|
// Set the Op structure for the operation
|
|
//
|
|
Op.PtOperationType = HYPERTRACE_PT_OPERATION_REQUEST_TYPE_FILTER;
|
|
|
|
//
|
|
// Set execution modes
|
|
//
|
|
Op.FilterOptions.TraceUser = IsUserMode ? 1 : 0;
|
|
Op.FilterOptions.TraceKernel = IsKernelMode ? 1 : 0;
|
|
|
|
//
|
|
// Set the process ID
|
|
//
|
|
Op.EnableOptions.Pid = ProcessId;
|
|
|
|
//
|
|
// Set the first range if provided
|
|
//
|
|
if (StartRange1 != NULL && EndRange1 != NULL)
|
|
{
|
|
Op.FilterOptions.AddrRanges[NumberOfRanges].Start = StartRange1;
|
|
Op.FilterOptions.AddrRanges[NumberOfRanges].End = EndRange1;
|
|
NumberOfRanges++;
|
|
}
|
|
|
|
//
|
|
// Set the second range if provided
|
|
//
|
|
if (StartRange2 != NULL && EndRange2 != NULL)
|
|
{
|
|
Op.FilterOptions.AddrRanges[NumberOfRanges].Start = StartRange2;
|
|
Op.FilterOptions.AddrRanges[NumberOfRanges].End = EndRange2;
|
|
NumberOfRanges++;
|
|
}
|
|
|
|
//
|
|
// Set the third range if provided
|
|
//
|
|
if (StartRange3 != NULL && EndRange3 != NULL)
|
|
{
|
|
Op.FilterOptions.AddrRanges[NumberOfRanges].Start = StartRange3;
|
|
Op.FilterOptions.AddrRanges[NumberOfRanges].End = EndRange3;
|
|
NumberOfRanges++;
|
|
}
|
|
|
|
//
|
|
// Set the fourth range if provided
|
|
//
|
|
if (StartRange4 != NULL && EndRange4 != NULL)
|
|
{
|
|
Op.FilterOptions.AddrRanges[NumberOfRanges].Start = StartRange4;
|
|
Op.FilterOptions.AddrRanges[NumberOfRanges].End = EndRange4;
|
|
NumberOfRanges++;
|
|
}
|
|
|
|
//
|
|
// Set number of ranges
|
|
//
|
|
Op.FilterOptions.NumAddrRanges = NumberOfRanges;
|
|
|
|
//
|
|
// Send the request to perform the operation
|
|
//
|
|
if (!HyperDbgPerformPtOperation(&Op))
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("[+] PT Filter: cr3=0x%llx, pid=0x%x, user=%x, kernel=%x, ranges=%x\n",
|
|
Op.EnableOptions.Cr3,
|
|
Op.EnableOptions.Pid,
|
|
Op.FilterOptions.TraceUser,
|
|
Op.FilterOptions.TraceKernel,
|
|
Op.FilterOptions.NumAddrRanges);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Shared core: enable PT, wait for the target, decode and disable
|
|
*
|
|
* @details Called by the three specialised helpers after they have filled
|
|
* Process with a valid hProcess (and optionally hThread).
|
|
* Owns and closes all handles in Process before returning.
|
|
*
|
|
* @param Process Pointer to a PROCESS_INFORMATION whose hProcess (and
|
|
* optionally hThread) have already been opened/created
|
|
* @param Path Executable path used for symbol resolution, or NULL
|
|
* @param Function Symbol name to narrow the IP filter, or NULL
|
|
* @param Packets TRUE → decode raw packets; FALSE → decode instructions
|
|
* @param PinCore ≥ 0 → pin the target to that logical core; < 0 → free
|
|
* @param LaunchedNew TRUE → process was launched suspended here (will be
|
|
* resumed, and terminated on error); FALSE → the
|
|
* process was already running (never terminated)
|
|
*
|
|
* @return VOID
|
|
*/
|
|
static VOID
|
|
CommandPtRunAndTraceCore(PROCESS_INFORMATION * Process,
|
|
const CHAR * Path,
|
|
const CHAR * Function,
|
|
BOOLEAN Packets,
|
|
int PinCore,
|
|
BOOLEAN LaunchedNew)
|
|
{
|
|
DWORD_PTR Mask;
|
|
HYPERTRACE_PT_MMAP_PACKETS Mmap = {};
|
|
HYPERTRACE_PT_OPERATION_PACKETS Sizes = {};
|
|
IMAGE_SYMBOL_CONTEXT Ctx = {};
|
|
UINT64 TextStart = 0;
|
|
UINT64 TextEnd = 0;
|
|
UINT64 FilterStart = 0;
|
|
UINT64 FilterEnd = 0;
|
|
UINT64 Total = 0;
|
|
|
|
if (PinCore < 0)
|
|
{
|
|
PinCore = PT_DEFAULT_PINNING_CORE;
|
|
}
|
|
|
|
Mask = (DWORD_PTR)1 << PinCore;
|
|
|
|
//
|
|
// Set process affinity to the specified core so that all PT trace will land on that core
|
|
//
|
|
if (SetProcessAffinityMask(Process->hProcess, Mask))
|
|
{
|
|
ShowMessages("[+] pinned target to core %d (all trace should land on this core)\n", PinCore);
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("[!] could not pin to core %d (error 0x%x); running unpinned\n", PinCore, GetLastError());
|
|
}
|
|
|
|
//
|
|
// Capture the .text section of the target process image for symbol resolution
|
|
//
|
|
if (!PtHelperCaptureImage(Process->hProcess, &TextStart, &TextEnd, &Ctx))
|
|
{
|
|
ShowMessages("[-] cannot read target image / .text section\n");
|
|
|
|
if (LaunchedNew)
|
|
TerminateProcess(Process->hProcess, 1);
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
ShowMessages("[+] image base 0x%llx, .text 0x%llx-0x%llx (%llu bytes)\n",
|
|
(UINT64)Ctx.ImageBase,
|
|
(UINT64)TextStart,
|
|
(UINT64)TextEnd,
|
|
(UINT64)Ctx.CodeSize);
|
|
|
|
FilterStart = TextStart;
|
|
FilterEnd = TextEnd;
|
|
|
|
//
|
|
// Narrow the IP filter to the specified function if possible
|
|
//
|
|
if (Function != NULL && Path != NULL &&
|
|
PtHelperResolveFunction(Process->hProcess, Path, Function, Ctx.ImageBase, &FilterStart, &FilterEnd))
|
|
{
|
|
ShowMessages("[+] IP filter narrowed to '%s' 0x%llx-0x%llx (%llu bytes)\n",
|
|
Function,
|
|
(UINT64)FilterStart,
|
|
(UINT64)FilterEnd,
|
|
(UINT64)(FilterEnd - FilterStart + 1));
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("[!] IP filter: whole .text (symbol '%s' not found or not applicable)\n",
|
|
Function ? Function : "(none)");
|
|
}
|
|
|
|
//
|
|
// Enable PT with the specified filter and wait for the target process to exit
|
|
//
|
|
if (!CommandPtSendFilterByPid(Process->dwProcessId, TRUE, FALSE, FilterStart, FilterEnd, NULL, NULL, NULL, NULL, NULL, NULL) ||
|
|
!CommandPtSendEnable())
|
|
{
|
|
ShowMessages("[-] cannot enable Intel PT\n");
|
|
|
|
if (LaunchedNew)
|
|
{
|
|
TerminateProcess(Process->hProcess, 1);
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Request the PT buffers to be mapped into user space so that we can decode them after the target exits
|
|
//
|
|
if (!HyperDbgPtMmapSendRequest(&Mmap))
|
|
{
|
|
ShowMessages("[-] MMAP failed\n");
|
|
CommandPtSendDisable();
|
|
if (LaunchedNew)
|
|
TerminateProcess(Process->hProcess, 1);
|
|
goto Cleanup;
|
|
}
|
|
|
|
ShowMessages("[+] PT enabled, %u per-core buffers mapped\n", Mmap.NumCpus);
|
|
|
|
if (LaunchedNew)
|
|
{
|
|
ShowMessages("[*] resuming target and waiting for it to exit...\n");
|
|
ResumeThread(Process->hThread);
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("[*] waiting for target process to exit...\n");
|
|
}
|
|
|
|
//
|
|
// Wait for the target process to exit before decoding the trace
|
|
//
|
|
WaitForSingleObject(Process->hProcess, INFINITE);
|
|
|
|
ShowMessages("[+] target exited, decoding trace\n");
|
|
|
|
//
|
|
// Pause PT so that the buffers are not being written to while we decode them
|
|
//
|
|
CommandPtSendPause();
|
|
|
|
Sizes.PtOperationType = HYPERTRACE_PT_OPERATION_REQUEST_TYPE_SIZE;
|
|
|
|
//
|
|
// Request the sizes of the PT buffers for each core so that we know how much to decode
|
|
//
|
|
if (!CommandPtSendRequest(&Sizes))
|
|
{
|
|
ShowMessages("[-] cannot query PT sizes\n");
|
|
CommandPtSendDisable();
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Decode the trace for each core that has a non-zero buffer size
|
|
//
|
|
for (UINT32 i = 0; i < Mmap.NumCpus; i++)
|
|
{
|
|
UINT32 Cpu = Mmap.Cpus[i].CpuId;
|
|
UINT64 Bytes = (Cpu < Sizes.NumCpus) ? Sizes.BytesPerCpu[Cpu] : 0;
|
|
|
|
if (Bytes == 0)
|
|
continue;
|
|
|
|
if (Bytes > Mmap.Cpus[i].Size)
|
|
Bytes = Mmap.Cpus[i].Size;
|
|
|
|
ShowMessages("\n[*] core %u: %llu bytes of trace\n", Cpu, (UINT64)Bytes);
|
|
Total += Packets
|
|
? PtHelperDecodeCorePackets(Cpu, (const UINT8 *)(ULONG_PTR)Mmap.Cpus[i].UserVa, Bytes, Ctx.ImageBase)
|
|
: PtHelperDecodeCore(Cpu, (const UINT8 *)(ULONG_PTR)Mmap.Cpus[i].UserVa, Bytes, &Ctx);
|
|
}
|
|
|
|
ShowMessages("\n[+] decoded %llu %s total\n", (UINT64)Total, Packets ? "packet(s)" : "instruction(s)");
|
|
|
|
//
|
|
// Disable PT now that we are done decoding the trace
|
|
//
|
|
CommandPtSendDisable();
|
|
|
|
Cleanup:
|
|
|
|
if (Ctx.Code != NULL)
|
|
{
|
|
free(Ctx.Code);
|
|
Ctx.Code = NULL;
|
|
}
|
|
|
|
if (Process->hThread != NULL)
|
|
CloseHandle(Process->hThread);
|
|
if (Process->hProcess != NULL)
|
|
CloseHandle(Process->hProcess);
|
|
}
|
|
|
|
/**
|
|
* @brief Launch an executable at Path as a suspended process, enable PT, and
|
|
* decode the trace after the process exits
|
|
*
|
|
* @param Path Full path to the executable to launch
|
|
* @param Function Symbol name to narrow the IP filter, or NULL for whole .text
|
|
* @param Packets TRUE → decode raw packets; FALSE → decode instructions
|
|
* @param PinCore ≥ 0 → pin to that logical core; < 0 → unpinned
|
|
*
|
|
* @return VOID
|
|
*/
|
|
static VOID
|
|
CommandPtRunAndTraceByPath(const CHAR * Path, const CHAR * Function, BOOLEAN Packets, int PinCore)
|
|
{
|
|
STARTUPINFOA Startup = {};
|
|
PROCESS_INFORMATION Process = {};
|
|
|
|
Startup.cb = sizeof(Startup);
|
|
|
|
if (!CreateProcessA(Path, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &Startup, &Process))
|
|
{
|
|
ShowMessages("[-] cannot launch '%s' (error 0x%x)\n", Path, GetLastError());
|
|
return;
|
|
}
|
|
|
|
ShowMessages("[+] launched '%s' (pid %u, suspended)\n", Path, Process.dwProcessId);
|
|
|
|
CommandPtRunAndTraceCore(&Process, Path, Function, Packets, PinCore, TRUE);
|
|
}
|
|
|
|
/**
|
|
* @brief Open an existing process by PID, enable PT, and decode the trace
|
|
* after the process exits
|
|
*
|
|
* @param ProcessId PID of the target process
|
|
* @param Function Symbol name to narrow the IP filter, or NULL for whole .text
|
|
* @param Packets TRUE → decode raw packets; FALSE → decode instructions
|
|
* @param PinCore ≥ 0 → pin to that logical core; < 0 → unpinned
|
|
*
|
|
* @return VOID
|
|
*/
|
|
static VOID
|
|
CommandPtRunAndTraceByPid(UINT32 ProcessId, const CHAR * Function, BOOLEAN Packets, int PinCore)
|
|
{
|
|
PROCESS_INFORMATION Process = {};
|
|
|
|
Process.hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)ProcessId);
|
|
|
|
if (Process.hProcess == NULL)
|
|
{
|
|
ShowMessages("[-] cannot open process 0x%x (error 0x%x)\n", ProcessId, GetLastError());
|
|
return;
|
|
}
|
|
|
|
Process.dwProcessId = ProcessId;
|
|
|
|
ShowMessages("[+] attached to pid 0x%x\n", ProcessId);
|
|
|
|
CommandPtRunAndTraceCore(&Process, NULL, Function, Packets, PinCore, FALSE);
|
|
}
|
|
|
|
/**
|
|
* @brief Open an existing thread by TID, derive its owning process, enable PT,
|
|
* and decode the trace after the process exits
|
|
*
|
|
* @param ThreadId TID of the target thread
|
|
* @param Function Symbol name to narrow the IP filter, or NULL for whole .text
|
|
* @param Packets TRUE → decode raw packets; FALSE → decode instructions
|
|
* @param PinCore ≥ 0 → pin to that logical core; < 0 → unpinned
|
|
*
|
|
* @return VOID
|
|
*/
|
|
static VOID
|
|
CommandPtRunAndTraceByTid(UINT32 ThreadId, const CHAR * Function, BOOLEAN Packets, int PinCore)
|
|
{
|
|
PROCESS_INFORMATION Process = {};
|
|
HANDLE ThreadHandle = NULL;
|
|
DWORD OwningPid = 0;
|
|
THREAD_BASIC_INFO_EX Tbi = {0};
|
|
ULONG RetTid = 0;
|
|
HMODULE NtdllTid = GetModuleHandleA("ntdll.dll");
|
|
PFN_NT_QIT NtQit = NtdllTid ? (PFN_NT_QIT)GetProcAddress(NtdllTid, "NtQueryInformationThread") : NULL;
|
|
|
|
ThreadHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD)ThreadId);
|
|
|
|
if (ThreadHandle == NULL)
|
|
{
|
|
ShowMessages("[-] cannot open thread 0x%x (error 0x%x)\n", ThreadId, GetLastError());
|
|
return;
|
|
}
|
|
|
|
if (NtQit == NULL || NtQit(ThreadHandle, 0, &Tbi, sizeof(Tbi), &RetTid) < 0 || Tbi.ClientId.UniqueProcess == NULL)
|
|
{
|
|
ShowMessages("[-] cannot get owning PID for thread 0x%x\n", ThreadId);
|
|
CloseHandle(ThreadHandle);
|
|
return;
|
|
}
|
|
|
|
OwningPid = (DWORD)(ULONG_PTR)Tbi.ClientId.UniqueProcess;
|
|
|
|
if (OwningPid == 0)
|
|
{
|
|
ShowMessages("[-] cannot get owning PID for thread 0x%x\n", ThreadId);
|
|
CloseHandle(ThreadHandle);
|
|
return;
|
|
}
|
|
|
|
Process.hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, OwningPid);
|
|
|
|
if (Process.hProcess == NULL)
|
|
{
|
|
ShowMessages("[-] cannot open process %u (error 0x%x)\n", OwningPid, GetLastError());
|
|
CloseHandle(ThreadHandle);
|
|
return;
|
|
}
|
|
|
|
Process.hThread = ThreadHandle;
|
|
Process.dwProcessId = OwningPid;
|
|
Process.dwThreadId = ThreadId;
|
|
|
|
ShowMessages("[+] attached to tid 0x%x (owning pid %u)\n", ThreadId, OwningPid);
|
|
|
|
CommandPtRunAndTraceCore(&Process, NULL, Function, Packets, PinCore, FALSE);
|
|
}
|
|
|
|
/**
|
|
* @brief Wrapper: dispatch to the appropriate trace helper based on whichever
|
|
* selector (Path, ProcessId, ThreadId) is provided
|
|
*
|
|
* @param Path Executable path, or NULL
|
|
* @param Function Symbol name for IP filter, or NULL
|
|
* @param Packets TRUE → raw packets; FALSE → instructions
|
|
* @param PinCore Logical core to pin the target to, or < 0 for unpinned
|
|
* @param ProcessId PID of an existing process, or 0
|
|
* @param ThreadId TID of an existing thread, or 0
|
|
*
|
|
* @return VOID
|
|
*/
|
|
static VOID
|
|
CommandPtRunAndTrace(const CHAR * Path, const CHAR * Function, BOOLEAN Packets, int PinCore, UINT32 ProcessId, UINT32 ThreadId)
|
|
{
|
|
if (Path != NULL)
|
|
CommandPtRunAndTraceByPath(Path, Function, Packets, PinCore);
|
|
else if (ThreadId != 0)
|
|
CommandPtRunAndTraceByTid(ThreadId, Function, Packets, PinCore);
|
|
else if (ProcessId != 0)
|
|
CommandPtRunAndTraceByPid(ProcessId, Function, Packets, PinCore);
|
|
else
|
|
ShowMessages("[-] no path, PID, or TID specified\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Map the per-CPU PT output buffers into the current process
|
|
*
|
|
* @details On success MmapRequest->Cpus[0..NumCpus) hold one { UserVa, Size }
|
|
* per CPU, valid in this process until PT is disabled / flushed.
|
|
* Only meaningful in local (VMI) mode.
|
|
*
|
|
* @param MmapRequest
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
HyperDbgPtMmapSendRequest(HYPERTRACE_PT_MMAP_PACKETS * MmapRequest)
|
|
{
|
|
BOOL Status;
|
|
ULONG ReturnedLength;
|
|
|
|
if (g_IsSerialConnectedToRemoteDebuggee)
|
|
{
|
|
//
|
|
// The mmap surface maps into the caller's address space, which only
|
|
// makes sense in local mode (no remote-debuggee transport for it).
|
|
//
|
|
ShowMessages("err, PT mmap is only available in local (VMI) mode\n");
|
|
return FALSE;
|
|
}
|
|
|
|
AssertShowMessageReturnStmt(g_IsHyperTraceModuleLoaded, g_DeviceHandle, ASSERT_MESSAGE_HYPERTRACE_NOT_LOADED, ASSERT_MESSAGE_DRIVER_NOT_LOADED, AssertReturnFalse);
|
|
|
|
Status = DeviceIoControl(
|
|
g_DeviceHandle, // Handle to device
|
|
IOCTL_PERFORM_HYPERTRACE_PT_MMAP, // IO Control Code (IOCTL)
|
|
MmapRequest, // Input Buffer to driver.
|
|
SIZEOF_HYPERTRACE_PT_MMAP_PACKETS, // Input buffer length
|
|
MmapRequest, // Output Buffer from driver.
|
|
SIZEOF_HYPERTRACE_PT_MMAP_PACKETS, // Length of output buffer in bytes.
|
|
&ReturnedLength, // Bytes placed in buffer.
|
|
NULL // synchronous call
|
|
);
|
|
|
|
if (!Status)
|
|
{
|
|
ShowMessages("ioctl failed with code 0x%x\n", GetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
return MmapRequest->KernelStatus == DEBUGGER_OPERATION_WAS_SUCCESSFUL;
|
|
}
|
|
|
|
/**
|
|
* @brief Parse and display enable options for !pt enable command
|
|
*
|
|
* @param CommandTokens The command tokens to parse
|
|
* @param PtRequest The PT request structure to fill with parsed options
|
|
*
|
|
* @return VOID
|
|
*/
|
|
static VOID
|
|
CommandPtParseEnable(vector<CommandToken> & CommandTokens, HYPERTRACE_PT_OPERATION_PACKETS * PtRequest)
|
|
{
|
|
BOOLEAN HasPid = FALSE;
|
|
BOOLEAN HasPname = FALSE;
|
|
BOOLEAN HasPath = FALSE;
|
|
BOOLEAN HasTid = FALSE;
|
|
BOOLEAN HasCr3 = FALSE;
|
|
BOOLEAN HasSize = FALSE;
|
|
BOOLEAN HasCore = FALSE;
|
|
UINT64 Pid = 0;
|
|
UINT64 Tid = 0;
|
|
UINT64 Cr3 = 0;
|
|
UINT64 Size = 0;
|
|
UINT32 Core = 0;
|
|
string Pname;
|
|
string Path;
|
|
|
|
for (SIZE_T i = 2; i < CommandTokens.size(); i++)
|
|
{
|
|
if (CompareLowerCaseStrings(CommandTokens.at(i), "pid"))
|
|
{
|
|
if (i + 1 >= CommandTokens.size())
|
|
{
|
|
ShowMessages("err, 'pid' expects a hex process ID\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
i++;
|
|
if (!ConvertTokenToUInt64(CommandTokens.at(i), &Pid))
|
|
{
|
|
ShowMessages("err, '%s' is not a valid hex process ID\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
HasPid = TRUE;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "pname"))
|
|
{
|
|
if (i + 1 >= CommandTokens.size())
|
|
{
|
|
ShowMessages("err, 'pname' expects a process name\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
i++;
|
|
Pname = GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i));
|
|
HasPname = TRUE;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "path"))
|
|
{
|
|
if (i + 1 >= CommandTokens.size())
|
|
{
|
|
ShowMessages("err, 'path' expects a process executable path\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
i++;
|
|
Path = GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i));
|
|
HasPath = TRUE;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "tid"))
|
|
{
|
|
if (i + 1 >= CommandTokens.size())
|
|
{
|
|
ShowMessages("err, 'tid' expects a hex thread ID\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
i++;
|
|
if (!ConvertTokenToUInt64(CommandTokens.at(i), &Tid))
|
|
{
|
|
ShowMessages("err, '%s' is not a valid hex thread ID\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
HasTid = TRUE;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "cr3"))
|
|
{
|
|
if (i + 1 >= CommandTokens.size())
|
|
{
|
|
ShowMessages("err, 'cr3' expects a hex CR3 value\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
i++;
|
|
if (!ConvertTokenToUInt64(CommandTokens.at(i), &Cr3))
|
|
{
|
|
ShowMessages("err, '%s' is not a valid hex CR3 value\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
HasCr3 = TRUE;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "size"))
|
|
{
|
|
if (i + 1 >= CommandTokens.size())
|
|
{
|
|
ShowMessages("err, 'size' expects a hex buffer size\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
i++;
|
|
if (!ConvertTokenToUInt64(CommandTokens.at(i), &Size))
|
|
{
|
|
ShowMessages("err, '%s' is not a valid hex size\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
HasSize = TRUE;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "core"))
|
|
{
|
|
if (i + 1 >= CommandTokens.size())
|
|
{
|
|
ShowMessages("err, 'core' expects a hex value as the core number\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
i++;
|
|
if (!ConvertTokenToUInt32(CommandTokens.at(i), &Core))
|
|
{
|
|
ShowMessages("err, '%s' is not a valid hex size\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
HasCore = TRUE;
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("err, unknown 'enable' option '%s'\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// pid, pname, tid, cr3 are mutually exclusive target selectors
|
|
//
|
|
INT32 SelectorCount = (HasPid ? 1 : 0) + (HasPname ? 1 : 0) + (HasPath ? 1 : 0) + (HasTid ? 1 : 0) + (HasCr3 ? 1 : 0);
|
|
if (SelectorCount > 1)
|
|
{
|
|
ShowMessages("err, only one of 'pid', 'pname', 'path', 'tid', 'cr3' may be specified at a time\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Show parsed enable options
|
|
//
|
|
ShowMessages("PT enable:\n");
|
|
|
|
if (HasPid)
|
|
{
|
|
ShowMessages(" target pid : 0x%llx\n", Pid);
|
|
|
|
PtRequest->EnableOptions.EnableByPid = 1;
|
|
PtRequest->EnableOptions.Pid = (UINT32)Pid;
|
|
}
|
|
else if (HasPname)
|
|
{
|
|
ShowMessages(" target pname : %s\n", Pname.c_str());
|
|
|
|
PtRequest->EnableOptions.EnableByCr3 = 1;
|
|
strcpy_s(PtRequest->EnableOptions.ProcessName, sizeof(PtRequest->EnableOptions.ProcessName), Pname.c_str());
|
|
}
|
|
else if (HasPath)
|
|
{
|
|
ShowMessages(" target path : %s\n", Path.c_str());
|
|
|
|
PtRequest->EnableOptions.EnableByPid = 1; // Path is resolved to a PID
|
|
}
|
|
else if (HasTid)
|
|
{
|
|
ShowMessages(" target tid : 0x%llx\n", Tid);
|
|
|
|
PtRequest->EnableOptions.EnableByTid = 1;
|
|
PtRequest->EnableOptions.Tid = (UINT32)Tid;
|
|
}
|
|
else if (HasCr3)
|
|
{
|
|
ShowMessages(" target cr3 : 0x%llx\n", Cr3);
|
|
|
|
PtRequest->EnableOptions.EnableByCr3 = 1;
|
|
PtRequest->EnableOptions.Cr3 = Cr3;
|
|
}
|
|
else
|
|
{
|
|
ShowMessages(" target : all (no process/thread filter)\n");
|
|
}
|
|
|
|
//
|
|
// Check for size options
|
|
//
|
|
if (HasSize)
|
|
{
|
|
ShowMessages(" buffer size : 0x%llx bytes\n", Size);
|
|
|
|
PtRequest->BufferSize = Size;
|
|
}
|
|
else
|
|
{
|
|
ShowMessages(" buffer size : default (%llx bytes)\n", PT_DEFAULT_BUFFER_SIZE);
|
|
|
|
PtRequest->BufferSize = PT_DEFAULT_BUFFER_SIZE;
|
|
}
|
|
|
|
//
|
|
// Check for core options
|
|
//
|
|
if (HasCore)
|
|
{
|
|
ShowMessages(" core : 0x%x\n", Core);
|
|
PtRequest->CoreId = Core;
|
|
}
|
|
else
|
|
{
|
|
ShowMessages(" core : default (0x%x)\n", PT_DEFAULT_PINNING_CORE);
|
|
PtRequest->CoreId = PT_DEFAULT_PINNING_CORE;
|
|
}
|
|
|
|
//
|
|
// Fill the PtRequest structure with parsed options
|
|
//
|
|
PtRequest->PtOperationType = HYPERTRACE_PT_OPERATION_REQUEST_TYPE_ENABLE;
|
|
|
|
//
|
|
// Temporary workaround for testing:
|
|
// If a path, PID, or TID is specified, invoke CommandPtRunAndTrace directly
|
|
// to launch/attach and decode the trace in one step.
|
|
// TODO: Should be removed once the kernel-side enable/filter path is complete.
|
|
//
|
|
if (HasPath)
|
|
{
|
|
ShowMessages(" Running '%s' on core: %llx\n", Path.c_str(), PtRequest->CoreId);
|
|
CommandPtRunAndTrace(Path.c_str(), NULL, FALSE, PtRequest->CoreId, 0, 0);
|
|
}
|
|
else if (HasPid)
|
|
{
|
|
ShowMessages(" Tracing pid 0x%llx on core: %llx\n", Pid, PtRequest->CoreId);
|
|
CommandPtRunAndTrace(NULL, NULL, FALSE, PtRequest->CoreId, (UINT32)Pid, 0);
|
|
}
|
|
else if (HasTid)
|
|
{
|
|
ShowMessages(" Tracing tid 0x%llx on core: %llx\n", Tid, PtRequest->CoreId);
|
|
CommandPtRunAndTrace(NULL, NULL, FALSE, PtRequest->CoreId, 0, (UINT32)Tid);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Parse and display !pt dump parameters
|
|
*
|
|
* @param CommandTokens The command tokens to parse
|
|
* @param PtRequest The PT request structure to fill with parsed options
|
|
*
|
|
* @return VOID
|
|
*/
|
|
static VOID
|
|
CommandPtParseDump(vector<CommandToken> & CommandTokens, HYPERTRACE_PT_OPERATION_PACKETS * PtRequest)
|
|
{
|
|
if (CommandTokens.size() < 3)
|
|
{
|
|
ShowMessages("err, 'dump' requires additional options\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
if (CompareLowerCaseStrings(CommandTokens.at(2), "print"))
|
|
{
|
|
//
|
|
// !pt dump print type <instruction|packet>
|
|
//
|
|
if (CommandTokens.size() != 5 || !CompareLowerCaseStrings(CommandTokens.at(3), "type"))
|
|
{
|
|
ShowMessages("err, syntax: !pt dump print type <instruction|packet>\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
if (!CompareLowerCaseStrings(CommandTokens.at(4), "instruction") &&
|
|
!CompareLowerCaseStrings(CommandTokens.at(4), "packet"))
|
|
{
|
|
ShowMessages("err, dump type must be 'instruction' or 'packet', got '%s'\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(4)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
ShowMessages("PT dump to console:\n");
|
|
ShowMessages(" output : console (print)\n");
|
|
ShowMessages(" format : %s\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(4)).c_str());
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(2), "path"))
|
|
{
|
|
//
|
|
// !pt dump path <path-string> type <instruction|packet>
|
|
//
|
|
if (CommandTokens.size() != 6 || !CompareLowerCaseStrings(CommandTokens.at(4), "type"))
|
|
{
|
|
ShowMessages("err, syntax: !pt dump path <path> type <instruction|packet>\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
if (!CompareLowerCaseStrings(CommandTokens.at(5), "instruction") &&
|
|
!CompareLowerCaseStrings(CommandTokens.at(5), "packet"))
|
|
{
|
|
ShowMessages("err, dump type must be 'instruction' or 'packet', got '%s'\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(5)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
ShowMessages("PT dump to file:\n");
|
|
ShowMessages(" output : file\n");
|
|
ShowMessages(" path : %s\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(3)).c_str());
|
|
ShowMessages(" format : %s\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(5)).c_str());
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("err, unknown 'dump' sub-option '%s'\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(2)).c_str());
|
|
CommandPtHelp();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Parse and display !pt filter parameters
|
|
*
|
|
* @param CommandTokens The command tokens to parse
|
|
* @param PtRequest The PT request structure to fill with parsed options
|
|
*
|
|
* @return VOID
|
|
*/
|
|
static VOID
|
|
CommandPtParseFilter(vector<CommandToken> & CommandTokens, HYPERTRACE_PT_OPERATION_PACKETS * PtRequest)
|
|
{
|
|
struct PtRangeEntry
|
|
{
|
|
BOOLEAN Active;
|
|
BOOLEAN IsModule;
|
|
UINT64 Start;
|
|
UINT64 End;
|
|
string ModuleName;
|
|
};
|
|
|
|
PtRangeEntry Ranges[4] = {};
|
|
PtRangeEntry StopRanges[4] = {};
|
|
BOOLEAN TraceUser = FALSE;
|
|
BOOLEAN TraceKernel = FALSE;
|
|
|
|
for (SIZE_T i = 2; i < CommandTokens.size(); i++)
|
|
{
|
|
if (CompareLowerCaseStrings(CommandTokens.at(i), "user"))
|
|
{
|
|
TraceUser = TRUE;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "kernel"))
|
|
{
|
|
TraceKernel = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Resolve range1..range4 / stoprange1..stoprange4
|
|
//
|
|
BOOLEAN IsStop = FALSE;
|
|
INT32 RangeIdx = -1;
|
|
|
|
if (CompareLowerCaseStrings(CommandTokens.at(i), "range1"))
|
|
{
|
|
IsStop = FALSE;
|
|
RangeIdx = 0;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "range2"))
|
|
{
|
|
IsStop = FALSE;
|
|
RangeIdx = 1;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "range3"))
|
|
{
|
|
IsStop = FALSE;
|
|
RangeIdx = 2;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "range4"))
|
|
{
|
|
IsStop = FALSE;
|
|
RangeIdx = 3;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "stoprange1"))
|
|
{
|
|
IsStop = TRUE;
|
|
RangeIdx = 0;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "stoprange2"))
|
|
{
|
|
IsStop = TRUE;
|
|
RangeIdx = 1;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "stoprange3"))
|
|
{
|
|
IsStop = TRUE;
|
|
RangeIdx = 2;
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "stoprange4"))
|
|
{
|
|
IsStop = TRUE;
|
|
RangeIdx = 3;
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("err, unknown filter option '%s'\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
PtRangeEntry * Entry = IsStop ? &StopRanges[RangeIdx] : &Ranges[RangeIdx];
|
|
|
|
if (i + 1 >= CommandTokens.size())
|
|
{
|
|
ShowMessages("err, '%s' expects <start> <end> or 'module <name>'\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
if (CompareLowerCaseStrings(CommandTokens.at(i + 1), "module"))
|
|
{
|
|
//
|
|
// range<N> module <module-name>
|
|
//
|
|
if (i + 2 >= CommandTokens.size())
|
|
{
|
|
ShowMessages("err, 'module' expects a module name\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
i += 2;
|
|
Entry->IsModule = TRUE;
|
|
Entry->ModuleName = GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i));
|
|
Entry->Active = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// range<N> <start> <end>
|
|
//
|
|
if (i + 2 >= CommandTokens.size())
|
|
{
|
|
ShowMessages("err, '%s' expects <start> <end>\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
i++;
|
|
if (!ConvertTokenToUInt64(CommandTokens.at(i), &Entry->Start))
|
|
{
|
|
ShowMessages("err, '%s' is not a valid address\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
i++;
|
|
if (!ConvertTokenToUInt64(CommandTokens.at(i), &Entry->End))
|
|
{
|
|
ShowMessages("err, '%s' is not a valid address\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
Entry->IsModule = FALSE;
|
|
Entry->Active = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Show parsed filter options
|
|
//
|
|
ShowMessages("PT filter:\n");
|
|
|
|
if (TraceUser)
|
|
{
|
|
ShowMessages(" privilege : user (CPL > 0)\n");
|
|
|
|
PtRequest->FilterOptions.TraceUser = 1;
|
|
}
|
|
if (TraceKernel)
|
|
{
|
|
ShowMessages(" privilege : kernel (CPL == 0)\n");
|
|
|
|
PtRequest->FilterOptions.TraceKernel = 1;
|
|
}
|
|
if (!TraceUser && !TraceKernel)
|
|
{
|
|
ShowMessages(" privilege : (default - user + kernel)\n");
|
|
|
|
PtRequest->FilterOptions.TraceUser = 1;
|
|
PtRequest->FilterOptions.TraceKernel = 1;
|
|
}
|
|
|
|
for (INT32 r = 0; r < 4; r++)
|
|
{
|
|
if (Ranges[r].Active)
|
|
{
|
|
if (Ranges[r].IsModule)
|
|
ShowMessages(" range%d : module '%s'\n", r + 1, Ranges[r].ModuleName.c_str());
|
|
else
|
|
ShowMessages(" range%d : 0x%llx - 0x%llx\n", r + 1, Ranges[r].Start, Ranges[r].End);
|
|
}
|
|
|
|
//
|
|
// Set the PtRequest structure for filter operation
|
|
//
|
|
PtRequest->FilterOptions.AddrRanges[r].IsStopRange = FALSE;
|
|
|
|
PtRequest->FilterOptions.AddrRanges[r].Start = Ranges[r].Start;
|
|
PtRequest->FilterOptions.AddrRanges[r].End = Ranges[r].End;
|
|
}
|
|
|
|
for (INT32 r = 0; r < 4; r++)
|
|
{
|
|
if (StopRanges[r].Active)
|
|
{
|
|
if (StopRanges[r].IsModule)
|
|
ShowMessages(" stoprange%d : module '%s'\n", r + 1, StopRanges[r].ModuleName.c_str());
|
|
else
|
|
ShowMessages(" stoprange%d : 0x%llx - 0x%llx\n", r + 1, StopRanges[r].Start, StopRanges[r].End);
|
|
}
|
|
|
|
//
|
|
// Set the PtRequest structure for filter operation
|
|
//
|
|
PtRequest->FilterOptions.AddrRanges[r].IsStopRange = TRUE;
|
|
|
|
PtRequest->FilterOptions.AddrRanges[r].Start = Ranges[r].Start;
|
|
PtRequest->FilterOptions.AddrRanges[r].End = Ranges[r].End;
|
|
}
|
|
|
|
//
|
|
// Set the PtRequest structure for filter operation
|
|
//
|
|
PtRequest->PtOperationType = HYPERTRACE_PT_OPERATION_REQUEST_TYPE_FILTER;
|
|
}
|
|
|
|
/**
|
|
* @brief Parse and display !pt packet parameters
|
|
* @param CommandTokens The command tokens to parse
|
|
* @param PtRequest The PT request structure to fill with parsed options
|
|
*
|
|
* @return VOID
|
|
*/
|
|
static VOID
|
|
CommandPtParsePacket(vector<CommandToken> & CommandTokens, HYPERTRACE_PT_OPERATION_PACKETS * PtRequest)
|
|
{
|
|
if (CommandTokens.size() < 3)
|
|
{
|
|
ShowMessages("err, 'packet' requires at least one option\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
BOOLEAN PktPsb = FALSE;
|
|
BOOLEAN PktPip = FALSE;
|
|
BOOLEAN PktTsc = FALSE;
|
|
BOOLEAN PktMtc = FALSE;
|
|
BOOLEAN PktCyc = FALSE;
|
|
BOOLEAN PktTnt = FALSE;
|
|
BOOLEAN PktTip = FALSE;
|
|
BOOLEAN PktFup = FALSE;
|
|
BOOLEAN PktMode = FALSE;
|
|
|
|
for (SIZE_T i = 2; i < CommandTokens.size(); i++)
|
|
{
|
|
if (CompareLowerCaseStrings(CommandTokens.at(i), "psb"))
|
|
PktPsb = TRUE;
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "pip"))
|
|
PktPip = TRUE;
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "tsc"))
|
|
PktTsc = TRUE;
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "mtc"))
|
|
PktMtc = TRUE;
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "cyc"))
|
|
PktCyc = TRUE;
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "tnt"))
|
|
PktTnt = TRUE;
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "tip"))
|
|
PktTip = TRUE;
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "fup"))
|
|
PktFup = TRUE;
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(i), "mode"))
|
|
PktMode = TRUE;
|
|
else
|
|
{
|
|
ShowMessages("err, unknown 'packet' option '%s'\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(i)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Show parsed packet options
|
|
//
|
|
ShowMessages("PT packet filter:\n");
|
|
|
|
if (PktPsb)
|
|
{
|
|
ShowMessages(" packet type: PSB (Packet Stream Boundary)\n");
|
|
|
|
PtRequest->PacketOptions.PSB = 1;
|
|
}
|
|
if (PktPip)
|
|
{
|
|
ShowMessages(" packet type: PIP (Paging Information Packet)\n");
|
|
|
|
PtRequest->PacketOptions.PIP = 1;
|
|
}
|
|
if (PktTsc)
|
|
{
|
|
ShowMessages(" packet type: TSC (Timestamp Counter)\n");
|
|
|
|
PtRequest->PacketOptions.TSC = 1;
|
|
}
|
|
if (PktMtc)
|
|
{
|
|
ShowMessages(" packet type: MTC (Mini Timestamp Counter)\n");
|
|
|
|
PtRequest->PacketOptions.MTC = 1;
|
|
}
|
|
if (PktCyc)
|
|
{
|
|
ShowMessages(" packet type: CYC (Cycle Counter)\n");
|
|
|
|
PtRequest->PacketOptions.CYC = 1;
|
|
}
|
|
if (PktTnt)
|
|
{
|
|
ShowMessages(" packet type: TNT (Taken/Not-Taken)\n");
|
|
|
|
PtRequest->PacketOptions.TNT = 1;
|
|
}
|
|
if (PktTip)
|
|
{
|
|
ShowMessages(" packet type: TIP (Target IP)\n");
|
|
|
|
PtRequest->PacketOptions.TNT = 1;
|
|
}
|
|
if (PktFup)
|
|
{
|
|
ShowMessages(" packet type: FUP (Flow Update Packet)\n");
|
|
|
|
PtRequest->PacketOptions.FUP = 1;
|
|
}
|
|
if (PktMode)
|
|
{
|
|
ShowMessages(" packet type: MODE (Mode packet)\n");
|
|
|
|
PtRequest->PacketOptions.MODE = 1;
|
|
}
|
|
|
|
//
|
|
// Set the PtRequest structure for packet operation
|
|
//
|
|
PtRequest->PtOperationType = HYPERTRACE_PT_OPERATION_REQUEST_TYPE_PACKET;
|
|
}
|
|
|
|
/**
|
|
* @brief !pt command handler
|
|
*
|
|
* @param CommandTokens
|
|
* @param Command
|
|
*
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
CommandPt(vector<CommandToken> CommandTokens, string Command)
|
|
{
|
|
HYPERTRACE_PT_OPERATION_PACKETS PtRequest = {};
|
|
|
|
if (CommandTokens.size() == 1)
|
|
{
|
|
ShowMessages("incorrect use of the '%s'\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(0)).c_str());
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Parse subcommands
|
|
//
|
|
if (CompareLowerCaseStrings(CommandTokens.at(1), "enable"))
|
|
{
|
|
//
|
|
// Parse and display enable options for !pt enable command
|
|
//
|
|
CommandPtParseEnable(CommandTokens, &PtRequest);
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(1), "disable"))
|
|
{
|
|
if (CommandTokens.size() != 2)
|
|
{
|
|
ShowMessages("err, 'disable' takes no arguments\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Parse and display disable options for !pt disable command
|
|
//
|
|
CommandPtSendDisable();
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(1), "pause"))
|
|
{
|
|
if (CommandTokens.size() != 2)
|
|
{
|
|
ShowMessages("err, 'pause' takes no arguments\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Parse and display pause options for !pt pause command
|
|
//
|
|
CommandPtSendPause();
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(1), "resume"))
|
|
{
|
|
if (CommandTokens.size() != 2)
|
|
{
|
|
ShowMessages("err, 'resume' takes no arguments\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Parse and display resume options for !pt resume command
|
|
//
|
|
CommandPtSendResume();
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(1), "flush"))
|
|
{
|
|
if (CommandTokens.size() != 2)
|
|
{
|
|
ShowMessages("err, 'flush' takes no arguments\n\n");
|
|
CommandPtHelp();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Parse and display flush options for !pt flush command
|
|
//
|
|
CommandPtSendFlush();
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(1), "dump"))
|
|
{
|
|
//
|
|
// Parse and display dump options for !pt dump command
|
|
//
|
|
CommandPtParseDump(CommandTokens, &PtRequest);
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(1), "filter"))
|
|
{
|
|
//
|
|
// Parse and display filter options for !pt filter command
|
|
//
|
|
CommandPtParseFilter(CommandTokens, &PtRequest);
|
|
}
|
|
else if (CompareLowerCaseStrings(CommandTokens.at(1), "packet"))
|
|
{
|
|
//
|
|
// Parse and display packet options for !pt packet command
|
|
//
|
|
CommandPtParsePacket(CommandTokens, &PtRequest);
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("incorrect use of the '%s'\n\n",
|
|
GetCaseSensitiveStringFromCommandToken(CommandTokens.at(0)).c_str());
|
|
CommandPtHelp();
|
|
}
|
|
|
|
//
|
|
// Send the PT request to the debugger
|
|
//
|
|
CommandPtSendRequest(&PtRequest);
|
|
}
|