mirror of
https://github.com/cheat-engine/cheat-engine
synced 2026-08-15 02:26:08 -04:00
739 lines
No EOL
18 KiB
C
739 lines
No EOL
18 KiB
C
/*
|
|
Ultimap implements the recording of all the branches in the target process
|
|
Requires dbvm for process selection
|
|
*/
|
|
|
|
#pragma warning( disable: 4100 4101 4213)
|
|
|
|
#include <ntifs.h>
|
|
#include "ultimap.h"
|
|
#include "vmxhelper.h"
|
|
#include "DBKFunc.h"
|
|
#include <windef.h>
|
|
|
|
#include "ultimap2\apic.h"
|
|
|
|
|
|
JUMPBACK perfmonJumpBackLocation;
|
|
|
|
|
|
/*
|
|
#ifdef AMD64
|
|
volatile PAPIC APIC_BASE=0; //(PAPIC)0xfffffffffffe0000;
|
|
#else
|
|
volatile PAPIC APIC_BASE=0; //(PAPIC)0xfffe0000;
|
|
#endif
|
|
*/
|
|
|
|
BOOL SaveToFile; //If set it will save the results to a file instead of sending a message to the usermode app that is watching the data
|
|
HANDLE FileHandle;
|
|
|
|
int MaxDataBlocks=1;
|
|
|
|
KSEMAPHORE DataBlockSemaphore; //governs how many events can be active at a time
|
|
FAST_MUTEX DataBlockMutex; //when a thread passes the semaphore this is used to pick a DataBlock
|
|
|
|
|
|
typedef struct
|
|
{
|
|
BOOL Available;
|
|
PBTS Data;
|
|
int DataSize;
|
|
int CpuID;
|
|
KEVENT DataReady;
|
|
} _DataBlock;
|
|
|
|
_DataBlock *DataBlock;
|
|
PVOID *DataReadyPointerList;
|
|
|
|
int perfmon_interrupt_centry(void);
|
|
|
|
/* use apic.* now
|
|
#define MSR_IA32_APICBASE 0x0000001b
|
|
|
|
void setup_APIC_BASE(void)
|
|
{
|
|
PHYSICAL_ADDRESS Physical_APIC_BASE;
|
|
DbgPrint("Fetching the APIC base\n");
|
|
|
|
Physical_APIC_BASE.QuadPart=readMSR(MSR_IA32_APICBASE) & 0xFFFFFFFFFFFFF000ULL;
|
|
|
|
|
|
DbgPrint("Physical_APIC_BASE=%p\n", Physical_APIC_BASE.QuadPart);
|
|
|
|
APIC_BASE = (PAPIC)MmMapIoSpace(Physical_APIC_BASE, sizeof(APIC), MmNonCached);
|
|
|
|
|
|
DbgPrint("APIC_BASE at %p\n", APIC_BASE);
|
|
|
|
}
|
|
|
|
void clean_APIC_BASE(void)
|
|
{
|
|
if (APIC_BASE)
|
|
MmUnmapIoSpace((PVOID)APIC_BASE, sizeof(APIC));
|
|
}*/
|
|
|
|
void ultimap_flushBuffers_all(UINT_PTR param)
|
|
{
|
|
DbgPrint("Calling perfmon_interrupt_centry() manually\n");
|
|
if (DS_AREA[cpunr()]) //don't call if ultimap has been disabled
|
|
{
|
|
perfmon_interrupt_centry();
|
|
enableInterrupts(); //the handler disables it on exit so re-enable it
|
|
}
|
|
}
|
|
|
|
void ultimap_flushBuffers(void)
|
|
{
|
|
//call this when the buffer of the current cpu needs to be flushed and handled
|
|
int i;
|
|
int count;
|
|
|
|
DbgPrint("ultimap_flushBuffers\n");
|
|
|
|
//what it does:
|
|
//for each cpu emulate a "buffer filled" event.
|
|
//the handler then copies all the current data to a temporary buffer and signals the worker thread to deal with it. If there is no available worker thread it waits
|
|
forEachCpuPassive(ultimap_flushBuffers_all,0);
|
|
|
|
DbgPrint("ultimap_flushBuffers_all has returned\n");
|
|
//it returned and all worker thread are currently working on this data (it only returns when it has send a worker to work)
|
|
|
|
|
|
//now wait for all workers to finish
|
|
//do this by aquiring all semaphore slots and waiting for them to return again
|
|
//forEachCpuPassive(ultimap_flushBuffers_all,0);
|
|
//DbgPrint("ultimap_flushBuffers_all has returned a second time\n"); //this means that the previous blocks have been dealt with
|
|
|
|
|
|
//actually... no, this is no guarantee. Now that the buffers are empty handling is so fast that while block 2,3,4,5 and 6 are still being handled block 1 can become available multiple times
|
|
|
|
}
|
|
|
|
|
|
NTSTATUS ultimap_continue(PULTIMAPDATAEVENT data)
|
|
/*
|
|
Called from usermode to signal that the data has been handled
|
|
*/
|
|
{
|
|
DbgPrint("ultimap_continue\n");
|
|
MmUnmapLockedPages((PVOID)(UINT_PTR)data->Address, (PMDL)(UINT_PTR)data->Mdl);
|
|
IoFreeMdl((PMDL)(UINT_PTR)data->Mdl);
|
|
|
|
ExFreePool((PVOID)(UINT_PTR)data->KernelAddress); //this memory is not needed anymore
|
|
|
|
|
|
if (DataBlock)
|
|
DataBlock[data->Block].Available=TRUE;
|
|
|
|
|
|
KeReleaseSemaphore(&DataBlockSemaphore, 1, 1, FALSE); //Let the next block go through
|
|
DbgPrint("Released semaphore\n");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS ultimap_waitForData(ULONG timeout, PULTIMAPDATAEVENT data)
|
|
/*
|
|
Called from usermode to wait for data
|
|
*/
|
|
{
|
|
NTSTATUS r;
|
|
LARGE_INTEGER wait;
|
|
|
|
PKWAIT_BLOCK waitblock;
|
|
|
|
if (DataBlock)
|
|
{
|
|
waitblock=ExAllocatePool(NonPagedPool, MaxDataBlocks*sizeof(KWAIT_BLOCK));
|
|
|
|
|
|
wait.QuadPart=-10000LL * timeout;
|
|
|
|
//Wait for the events in the list
|
|
//If an event is triggered find out which one is triggered, then map that block into the usermode space and return the address and block
|
|
//That block will be needed to continue
|
|
|
|
if (timeout==0xffffffff) //infinite wait
|
|
r=KeWaitForMultipleObjects(MaxDataBlocks, DataReadyPointerList, WaitAny, UserRequest, UserMode, TRUE, NULL, waitblock);
|
|
else
|
|
r=KeWaitForMultipleObjects(MaxDataBlocks, DataReadyPointerList, WaitAny, UserRequest, UserMode, TRUE, &wait, waitblock);
|
|
|
|
ExFreePool(waitblock);
|
|
|
|
data->Block=r-STATUS_WAIT_0;
|
|
|
|
if (data->Block <= MaxDataBlocks)
|
|
{
|
|
//Map this block to usermode
|
|
|
|
|
|
ExAcquireFastMutex(&DataBlockMutex);
|
|
if (DataBlock)
|
|
{
|
|
data->KernelAddress=(UINT64)DataBlock[data->Block].Data;
|
|
data->Mdl=(UINT64)IoAllocateMdl(DataBlock[data->Block].Data, DataBlock[data->Block].DataSize, FALSE, FALSE, NULL);
|
|
if (data->Mdl)
|
|
{
|
|
MmBuildMdlForNonPagedPool((PMDL)(UINT_PTR)data->Mdl);
|
|
|
|
data->Address=(UINT_PTR)MmMapLockedPagesSpecifyCache((PMDL)(UINT_PTR)data->Mdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority);
|
|
if (data->Address)
|
|
{
|
|
data->Size=DataBlock[data->Block].DataSize;
|
|
data->CpuID=DataBlock[data->Block].CpuID;
|
|
r=STATUS_SUCCESS;
|
|
}
|
|
else
|
|
r=STATUS_UNSUCCESSFUL;
|
|
}
|
|
else
|
|
r=STATUS_UNSUCCESSFUL;
|
|
}
|
|
else
|
|
r=STATUS_UNSUCCESSFUL;
|
|
|
|
ExReleaseFastMutex(&DataBlockMutex);
|
|
|
|
return r;
|
|
}
|
|
else
|
|
return STATUS_UNSUCCESSFUL;
|
|
|
|
}
|
|
else
|
|
return STATUS_UNSUCCESSFUL;
|
|
|
|
|
|
|
|
}
|
|
|
|
/*
|
|
void apic_clearPerfmon()
|
|
{
|
|
APIC_BASE->LVT_Performance_Monitor.a = APIC_BASE->LVT_Performance_Monitor.a & 0xff;
|
|
APIC_BASE->EOI.a = 0;
|
|
}
|
|
*/
|
|
void ultimap_cleanstate()
|
|
{
|
|
apic_clearPerfmon();
|
|
}
|
|
|
|
int perfmon_interrupt_centry(void)
|
|
{
|
|
|
|
KIRQL old = PASSIVE_LEVEL;
|
|
int changedIRQL = 0;
|
|
|
|
void *temp;
|
|
int causedbyme=(DS_AREA[cpunr()]->BTS_IndexBaseAddress>=DS_AREA[cpunr()]->BTS_InterruptThresholdAddress);
|
|
UINT_PTR blocksize;
|
|
|
|
DbgPrint("perfmon_interrupt_centry\n", cpunr());
|
|
|
|
|
|
if (causedbyme)
|
|
ultimap_cleanstate();
|
|
|
|
blocksize=(UINT_PTR)(DS_AREA[cpunr()]->BTS_IndexBaseAddress-DS_AREA[cpunr()]->BTS_BufferBaseAddress);
|
|
|
|
{
|
|
if (KeGetCurrentIrql() < DISPATCH_LEVEL)
|
|
{
|
|
//When called by the pre-emptive caller
|
|
changedIRQL = 1;
|
|
old = KeRaiseIrqlToDpcLevel();
|
|
}
|
|
|
|
|
|
DbgPrint("Entry cpunr=%d\n", cpunr());
|
|
DbgPrint("Entry threadid=%d\n", PsGetCurrentThreadId());
|
|
|
|
|
|
temp=ExAllocatePool(NonPagedPool, blocksize);
|
|
if (temp)
|
|
{
|
|
RtlCopyMemory(temp, (PVOID *)(UINT_PTR)DS_AREA[cpunr()]->BTS_BufferBaseAddress, blocksize);
|
|
|
|
DbgPrint("temp=%p\n", temp);
|
|
|
|
|
|
DS_AREA[cpunr()]->BTS_IndexBaseAddress=DS_AREA[cpunr()]->BTS_BufferBaseAddress; //don't reset on alloc error
|
|
}
|
|
else
|
|
{
|
|
DbgPrint("ExAllocatePool has failed\n");
|
|
KeLowerIrql(old);
|
|
disableInterrupts();
|
|
return causedbyme;
|
|
}
|
|
|
|
if (changedIRQL)
|
|
KeLowerIrql(old);
|
|
//should be passive mode, taskswitches and cpu switches will happen now (When this returns, I may not be on the same interrupt as I was when I started)
|
|
|
|
|
|
if (SaveToFile)
|
|
{
|
|
IO_STATUS_BLOCK iosb;
|
|
NTSTATUS r;
|
|
|
|
//Instead of sending the data to a usermode app it was chosen to store the data to a file for later usage
|
|
DbgPrint("Writing buffer to disk\n");
|
|
r=ZwWriteFile(FileHandle, NULL, NULL, NULL, &iosb, temp, (ULONG)blocksize, NULL, NULL);
|
|
DbgPrint("Done Writing. Result=%x\n", r);
|
|
}
|
|
else
|
|
{
|
|
DbgPrint("Waiting till there is a block free\n");
|
|
//When all workers are busy do not continue
|
|
if ((DataBlock) && (KeWaitForSingleObject(&DataBlockSemaphore, Executive, KernelMode, FALSE, NULL) == STATUS_SUCCESS))
|
|
{
|
|
int currentblock;
|
|
int i;
|
|
|
|
//Enter a critical section and choose a block
|
|
DbgPrint("Acquired semaphore. Now picking a usable datablock\n");
|
|
|
|
|
|
ExAcquireFastMutex(&DataBlockMutex);
|
|
DbgPrint("Acquired mutex. Looking for a Datablock that can be used\n");
|
|
|
|
if (DataBlock)
|
|
{
|
|
currentblock=-1;
|
|
for (i=0; i< MaxDataBlocks; i++)
|
|
{
|
|
if (DataBlock[i].Available) //look for a block that is set as available
|
|
{
|
|
currentblock=i;
|
|
DataBlock[currentblock].Available=FALSE; //not available anymore
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else currentblock=-1;
|
|
|
|
|
|
|
|
if (currentblock>=0)
|
|
{
|
|
DbgPrint("Using datablock %d\n", currentblock);
|
|
DataBlock[currentblock].Data=temp;
|
|
DataBlock[currentblock].DataSize=(int)blocksize;
|
|
DataBlock[currentblock].CpuID=cpunr();
|
|
|
|
DbgPrint("Calling KeSetEvent/KeWaitForSingleObject\n");
|
|
KeSetEvent(&DataBlock[currentblock].DataReady, 1, FALSE); //Trigger a worker thread to start working
|
|
}
|
|
ExReleaseFastMutex(&DataBlockMutex);
|
|
//DbgPrint("Released mutex\n");
|
|
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
DbgPrint("if ((DataBlock) && (KeWaitForSingleObject(&DataBlockSemaphore, Executive, KernelMode, FALSE, NULL) == STATUS_SUCCESS)) failed\n");
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//and return to the caller process
|
|
disableInterrupts();
|
|
return causedbyme;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef AMD64
|
|
extern void perfmon_interrupt();
|
|
#else
|
|
_declspec( naked ) void perfmon_interrupt( void )
|
|
{
|
|
__asm{
|
|
cld
|
|
|
|
push ebp
|
|
mov ebp,esp
|
|
|
|
//store state
|
|
pushad
|
|
xor eax,eax
|
|
mov ax,ds
|
|
push eax
|
|
|
|
mov ax,es
|
|
push eax
|
|
|
|
mov ax,fs
|
|
push eax
|
|
|
|
mov ax,gs
|
|
push eax
|
|
|
|
mov ax,0x23 //0x10 should work too, but even windows itself is using 0x23
|
|
mov ds,ax
|
|
mov es,ax
|
|
mov gs,ax
|
|
mov ax,0x30
|
|
mov fs,ax
|
|
|
|
call perfmon_interrupt_centry
|
|
cmp eax,1 //set flag
|
|
|
|
|
|
//restore state
|
|
pop gs
|
|
pop fs
|
|
pop es
|
|
pop ds
|
|
popad
|
|
|
|
pop ebp
|
|
|
|
je skip_original_perfmon
|
|
|
|
jmp far [perfmonJumpBackLocation]
|
|
|
|
skip_original_perfmon:
|
|
// commented out: I don't think a APIC interrupt has an errorcode.... add esp,4 //undo errorcode push
|
|
iretd
|
|
}
|
|
}
|
|
#endif
|
|
|
|
VOID ultimap_pause_dpc(IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgumen1, IN PVOID SystemArgument2)
|
|
{
|
|
vmx_ultimap_pause();
|
|
}
|
|
|
|
void ultimap_pause(void)
|
|
{
|
|
forEachCpu(ultimap_pause_dpc, NULL, NULL, NULL, NULL);
|
|
}
|
|
|
|
VOID ultimap_resume_dpc(IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgumen1, IN PVOID SystemArgument2)
|
|
{
|
|
vmx_ultimap_resume();
|
|
}
|
|
|
|
void ultimap_resume(void)
|
|
{
|
|
forEachCpu(ultimap_resume_dpc, NULL, NULL, NULL, NULL);
|
|
}
|
|
|
|
VOID ultimap_disable_dpc(IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgumen1, IN PVOID SystemArgument2)
|
|
{
|
|
int i;
|
|
//DbgPrint("ultimap_disable_dpc()\n");
|
|
|
|
if (vmxusable)
|
|
{
|
|
i=vmx_ultimap_disable();
|
|
|
|
if (DS_AREA[cpunr()])
|
|
{
|
|
ExFreePool(DS_AREA[cpunr()]);
|
|
DS_AREA[cpunr()]=NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ultimap_disable(void)
|
|
{
|
|
void *clear = NULL;
|
|
|
|
if (DataBlock)
|
|
{
|
|
int i;
|
|
|
|
forEachCpu(ultimap_disable_dpc, NULL, NULL, NULL, NULL);
|
|
|
|
if (SaveToFile && FileHandle)
|
|
{
|
|
ZwClose(FileHandle);
|
|
FileHandle=NULL;
|
|
}
|
|
|
|
//all logging should have stopped now
|
|
|
|
//Trigger all events waking up each thread that was waiting for the events
|
|
|
|
ExAcquireFastMutex(&DataBlockMutex);
|
|
|
|
for (i=0; i<MaxDataBlocks; i++)
|
|
KeSetEvent(&DataBlock[i].DataReady,0, FALSE);
|
|
|
|
ExFreePool(DataBlock);
|
|
DataBlock=NULL;
|
|
|
|
if (DataReadyPointerList)
|
|
{
|
|
ExFreePool(DataReadyPointerList);
|
|
DataReadyPointerList=NULL;
|
|
}
|
|
ExReleaseFastMutex(&DataBlockMutex);
|
|
|
|
HalSetSystemInformation(HalProfileSourceInterruptHandler, sizeof(PVOID*), &clear); //unhook the perfmon interrupt
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
VOID ultimap_setup_dpc(IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2)
|
|
/*
|
|
initializes ultimap. If the DS_AREA_SIZE is bigger than 0 it will allocate the required region (the usual option, but if not used it could be a LBR only thing)
|
|
Call this for each processor
|
|
*/
|
|
{
|
|
struct
|
|
{
|
|
UINT64 cr3;
|
|
UINT64 dbgctl_msr;
|
|
int DS_AREA_SIZE;
|
|
} *params;
|
|
|
|
params=DeferredContext;
|
|
|
|
DS_AREA_SIZE=params->DS_AREA_SIZE;
|
|
if (DS_AREA_SIZE == 0)
|
|
{
|
|
DbgPrint("DS_AREA_SIZE==0\n");
|
|
return;
|
|
}
|
|
|
|
|
|
DbgPrint("ultimap(%I64x, %I64x, %d)", (UINT64)params->cr3, (UINT64)params->dbgctl_msr, params->DS_AREA_SIZE);
|
|
DS_AREA[cpunr()]=NULL;
|
|
|
|
if (params->DS_AREA_SIZE)
|
|
{
|
|
DS_AREA[cpunr()]=ExAllocatePool(NonPagedPool, params->DS_AREA_SIZE);
|
|
|
|
if (DS_AREA[cpunr()] == NULL)
|
|
{
|
|
DbgPrint("ExAllocatePool failed\n");
|
|
return;
|
|
}
|
|
|
|
RtlZeroMemory(DS_AREA[cpunr()], params->DS_AREA_SIZE);
|
|
|
|
DbgPrint("DS_AREA[%d]=%p", cpunr(), DS_AREA[cpunr()]);
|
|
|
|
//Initialize the DS_AREA
|
|
|
|
DS_AREA[cpunr()]->BTS_BufferBaseAddress=(QWORD)(UINT_PTR)DS_AREA[cpunr()]+sizeof(DS_AREA_MANAGEMENT);
|
|
DS_AREA[cpunr()]->BTS_BufferBaseAddress+=sizeof(BTS);
|
|
|
|
DS_AREA[cpunr()]->BTS_BufferBaseAddress-=DS_AREA[cpunr()]->BTS_BufferBaseAddress % sizeof(BTS);
|
|
|
|
DS_AREA[cpunr()]->BTS_IndexBaseAddress=DS_AREA[cpunr()]->BTS_BufferBaseAddress;
|
|
DS_AREA[cpunr()]->BTS_AbsoluteMaxAddress=(QWORD)(UINT_PTR)DS_AREA[cpunr()]+params->DS_AREA_SIZE-sizeof(BTS);
|
|
DS_AREA[cpunr()]->BTS_AbsoluteMaxAddress-=DS_AREA[cpunr()]->BTS_AbsoluteMaxAddress % sizeof(BTS);
|
|
DS_AREA[cpunr()]->BTS_AbsoluteMaxAddress++;
|
|
|
|
DS_AREA[cpunr()]->BTS_InterruptThresholdAddress=(DS_AREA[cpunr()]->BTS_AbsoluteMaxAddress-1) - 4*sizeof(BTS);
|
|
}
|
|
|
|
|
|
if (params->dbgctl_msr & (1 << 8))
|
|
{
|
|
//hook the perfmon interrupt. First get the interrupt assigned (usually 0xfe, but let's be sure and read it from the apic)
|
|
|
|
int perfmonIVT=(APIC_BASE->LVT_Performance_Monitor.a) & 0xff;
|
|
|
|
DbgPrint("APIC_BASE->LVT_Performance_Monitor.a=%x\n", APIC_BASE->LVT_Performance_Monitor.a);
|
|
if (perfmonIVT==0) //if not setup at all then set it up now
|
|
perfmonIVT=0xfe;
|
|
|
|
APIC_BASE->LVT_Performance_Monitor.a=perfmonIVT; //clear mask flag if it was set
|
|
|
|
DbgPrint("APIC_BASE->LVT_Performance_Monitor.a=%x\n", APIC_BASE->LVT_Performance_Monitor.a);
|
|
|
|
|
|
/*
|
|
|
|
if (inthook_HookInterrupt((unsigned char)perfmonIVT, getCS(), (ULONG_PTR)perfmon_interrupt, &perfmonJumpBackLocation))
|
|
DbgPrint("Interrupt hooked\n");
|
|
else
|
|
DbgPrint("Failed to hook interrupt\n");
|
|
*/
|
|
|
|
}
|
|
|
|
//and finally activate the mapping
|
|
if (vmxusable)
|
|
{
|
|
vmx_ultimap((UINT_PTR)params->cr3, params->dbgctl_msr, DS_AREA[cpunr()]);
|
|
}
|
|
else
|
|
{
|
|
DbgPrint("vmxusable is false. So no ultimap for you!!!\n");
|
|
}
|
|
}
|
|
|
|
void ultimapapc(PKAPC Apc, PKNORMAL_ROUTINE NormalRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
|
|
{
|
|
EFLAGS e = getEflags();
|
|
DbgPrint("ultimapapc call for cpu %d ( IF=%d IRQL=%d)\n", KeGetCurrentProcessorNumber(), e.IF, KeGetCurrentIrql());
|
|
DbgPrint("SystemArgument1=%x\n", *(PULONG)SystemArgument1);
|
|
DbgPrint("tid=%x\n", PsGetCurrentThreadId());
|
|
DbgPrint("Apc=%p\n", Apc);
|
|
}
|
|
|
|
void ultimapapcnormal(PVOID arg1, PVOID arg2, PVOID arg3)
|
|
{
|
|
EFLAGS e = getEflags();
|
|
DbgPrint("ultimapapcnormal call for cpu %d ( IF=%d IRQL=%d)\n", KeGetCurrentProcessorNumber(), e.IF, KeGetCurrentIrql());
|
|
DbgPrint("tid=%x\n", PsGetCurrentThreadId());
|
|
|
|
ultimap_flushBuffers();
|
|
|
|
return;
|
|
}
|
|
|
|
KAPC kApc[128];
|
|
volatile LONG apcnr = 0;
|
|
|
|
void perfmon_hook(__in struct _KINTERRUPT *Interrupt, __in PVOID ServiceContext)
|
|
{
|
|
|
|
int i = InterlockedIncrement(&apcnr) % 128;
|
|
|
|
|
|
EFLAGS e = getEflags();
|
|
DbgPrint("permon_hook call for cpu %d ( IF=%d IRQL=%d)\n", KeGetCurrentProcessorNumber(), e.IF, KeGetCurrentIrql());
|
|
|
|
DbgPrint("kApc=%p\n", &kApc);
|
|
|
|
|
|
//switch buffer pointers
|
|
|
|
//call DPC for ultimap for this cpu
|
|
|
|
|
|
//todo: if this is buggy use a dpc instead to create the apc. (slower)
|
|
KeInitializeApc(&kApc[i],
|
|
(PKTHREAD)PsGetCurrentThread(),
|
|
0,
|
|
(PKKERNEL_ROUTINE)ultimapapc,
|
|
NULL,
|
|
(PKNORMAL_ROUTINE)ultimapapcnormal,
|
|
KernelMode,
|
|
0
|
|
);
|
|
|
|
KeInsertQueueApc(&kApc[i], NULL, NULL, 0);
|
|
|
|
|
|
|
|
DbgPrint("after KeInsertQueueApc");
|
|
|
|
|
|
|
|
|
|
//perfmon_interrupt_centry();
|
|
ultimap_cleanstate();
|
|
|
|
DbgPrint("permon_return");
|
|
}
|
|
|
|
void *pperfmon_hook = (void*)perfmon_hook;
|
|
|
|
NTSTATUS ultimap(UINT64 cr3, UINT64 dbgctl_msr, int _DS_AREA_SIZE, BOOL savetofile, WCHAR *filename, int handlerCount)
|
|
{
|
|
struct
|
|
{
|
|
UINT64 cr3;
|
|
UINT64 dbgctl_msr;
|
|
int DS_AREA_SIZE;
|
|
} params;
|
|
int i;
|
|
|
|
if (handlerCount>64)
|
|
return STATUS_UNSUCCESSFUL;
|
|
|
|
|
|
|
|
//create file
|
|
SaveToFile=savetofile;
|
|
|
|
if (SaveToFile)
|
|
{
|
|
UNICODE_STRING usFile;
|
|
OBJECT_ATTRIBUTES oaFile;
|
|
IO_STATUS_BLOCK iosb;
|
|
NTSTATUS r;
|
|
|
|
RtlInitUnicodeString(&usFile, filename);
|
|
InitializeObjectAttributes(&oaFile,&usFile, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL,NULL);
|
|
|
|
DbgPrint("Creating file %S", usFile.Buffer);
|
|
|
|
FileHandle=0;
|
|
r=ZwCreateFile(&FileHandle,SYNCHRONIZE|FILE_READ_DATA|FILE_APPEND_DATA | GENERIC_ALL,&oaFile,&iosb,0,FILE_ATTRIBUTE_NORMAL,0,FILE_SUPERSEDE, FILE_SEQUENTIAL_ONLY | FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);
|
|
DbgPrint("ZwCreateFile=%x\n", r);
|
|
|
|
|
|
}
|
|
|
|
MaxDataBlocks=handlerCount;
|
|
KeInitializeSemaphore(&DataBlockSemaphore, MaxDataBlocks, MaxDataBlocks);
|
|
ExInitializeFastMutex(&DataBlockMutex);
|
|
|
|
//Datablock inits
|
|
|
|
DataBlock=ExAllocatePool(NonPagedPool, sizeof(_DataBlock) * MaxDataBlocks);
|
|
DataReadyPointerList=ExAllocatePool(NonPagedPool, sizeof(PVOID) * MaxDataBlocks);
|
|
|
|
RtlZeroMemory(DataBlock, sizeof(_DataBlock) * MaxDataBlocks);
|
|
RtlZeroMemory(DataReadyPointerList, sizeof(PVOID) * MaxDataBlocks);
|
|
|
|
|
|
if ((DataBlock) && (DataReadyPointerList))
|
|
{
|
|
NTSTATUS r;
|
|
|
|
|
|
for (i=0; i< MaxDataBlocks; i++)
|
|
{
|
|
//DataBlock[i]->
|
|
DataBlock[i].Data=NULL;
|
|
|
|
KeInitializeEvent(&DataBlock[i].DataReady, SynchronizationEvent , FALSE);
|
|
|
|
DataBlock[i].Available=TRUE;
|
|
|
|
DataReadyPointerList[i]=&DataBlock[i].DataReady;
|
|
}
|
|
|
|
|
|
params.cr3=cr3;
|
|
params.dbgctl_msr=dbgctl_msr;
|
|
params.DS_AREA_SIZE=_DS_AREA_SIZE;
|
|
|
|
r=HalSetSystemInformation(HalProfileSourceInterruptHandler, sizeof(PVOID*), &pperfmon_hook); //hook the perfmon interrupt
|
|
|
|
DbgPrint("HalSetSystemInformation returned %x\n", r);
|
|
|
|
forEachCpu(ultimap_setup_dpc, ¶ms, NULL, NULL, NULL);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
DbgPrint("Failure allocating DataBlock and DataReadyPointerList\n");
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
}
|
|
|
|
|
|
} |