cheat-engine/Cheat Engine/VEHDebugger.pas

737 lines
21 KiB
ObjectPascal
Executable File

unit VEHDebugger;
{$mode delphi}
interface
{$ifdef windows}
uses
jwaNtStatus, Windows,
Classes, SysUtils,symbolhandler, symbolhandlerstructs,
VEHDebugSharedMem,cefuncproc, autoassembler,newkernelhandler,DebuggerInterface,
Clipbrd;
type
TVEHDebugInterface=class(TDebuggerInterface)
private
guid: TGUID; //to indentify this specific debugger
guidstring: string;
HasDebugEvent: THandle;
HasHandledDebugEvent: THandle;
ConfigFileMapping: THandle;
VEHDebugView: PVEHDebugSharedMem;
active: boolean;
is64bit: boolean; //stored local so it doesn't have to evaluate the property (saves some time)
hasPausedProcess: boolean;
fisInjectedEvent: boolean;
injectedEvents: TList;
lastthreadlist: TStringList;
lastthreadpoll: qword;
Heartbeat: TThread;
CurrentThread: THandle;
procedure SynchronizeNoBreakList;
procedure DoThreadPoll;
public
function isInjectedEvent: boolean; override;
function WaitForDebugEvent(var lpDebugEvent: TDebugEvent; dwMilliseconds: DWORD): BOOL; override;
function ContinueDebugEvent(dwProcessId: DWORD; dwThreadId: DWORD; dwContinueStatus: DWORD): BOOL; override;
function SetThreadContext(hThread: THandle; const lpContext: TContext; isFrozenThread: Boolean=false): BOOL; override;
function GetThreadContext(hThread: THandle; var lpContext: TContext; isFrozenThread: Boolean=false): BOOL; override;
function DebugActiveProcess(dwProcessId: DWORD): WINBOOL; override;
function DebugActiveProcessStop(dwProcessID: DWORD): WINBOOL; override;
procedure AddToNoBreakList(threadid: integer); override;
procedure RemoveFromNoBreakList(threadid: integer); override;
destructor destroy; override;
constructor create;
end;
{$endif}
implementation
{$ifdef windows}
uses ProcessHandlerUnit, Globals, dialogs;
resourcestring
rsErrorWhileTryingToCreateTheConfigurationStructure = 'Error while trying '
+'to create the configuration structure! (Which effectively renders this '
+'whole feature useless) Errorcode=%s';
rsCheatEngineFailedToGetIntoTheConfig = 'Cheat Engine failed to get into '
+'the config of the selected program. (Error=%s)';
rsFailureDuplicatingTheEventHandlesToTheOtherProcess = 'Failure duplicating '
+'the event handles to the other process';
rsVEHDebugError = 'VEH Debug error';
rsFailureDuplicatingTheFilemapping = 'Failure duplicating the filemapping';
rsTheVEHDllSeemsToHaveFailedToLoad = 'The VEH dll seems to have failed to load';
rsWrongVEHDllVersion = 'The version of the VEH dll inside the target process (%x) does not match what was expected %x';
type
TInjectedEvent=class
public
eventtype: (etThreadCreate,etThreadDestroy); //0=create thread, 1=destroythread
threadid: integer;
end;
THeartBeat=class(TThread)
private
owner: TVEHDebugInterface;
doVersionCheck: boolean;
procedure invalidVersionMessage;
procedure startVersionCheck;
protected
procedure execute; override;
end;
procedure THeartBeat.startVersionCheck;
begin
doVersionCheck:=true;
end;
procedure THeartBeat.invalidVersionMessage;
begin
if owner.VEHDebugView.VEHVersion=0 then
MessageDlg(rsTheVEHDllSeemsToHaveFailedToLoad, mtError, [mbok], 0)
else
MessageDlg(format(rsWrongVEHDllVersion, [owner.VEHDebugView.VEHVersion, DWORD($cece0000+VEHVERSION)]), mtWarning, [mbok], 0);
end;
procedure THeartBeat.execute;
var invalidversion: integer;
begin
invalidversion:=0;
while not terminated do
begin
inc(owner.VEHDebugView.heartbeat);
sleep(250);
if doVersionCheck and (owner.VEHDebugView.VEHVersion<>$cece0000+VEHVERSION) then
begin
inc(invalidversion);
if invalidversion=10 then //(10*500 ms=5 seconds);
queue(invalidVersionMessage);
end;
end;
end;
constructor TVEHDebugInterface.create;
begin
inherited create;
fDebuggerCapabilities:=[dbcSoftwareBreakpoint,dbcHardwareBreakpoint, dbcExceptionBreakpoint];
name:='VEH Debugger';
fmaxSharedBreakpointCount:=4;
InjectedEvents:=Tlist.create;
LastThreadList:=TStringList.create;
lastthreadlist.Sorted:=true;
lastthreadlist.Duplicates:=dupIgnore;
end;
destructor TVEHDebugInterface.destroy;
begin
if heartbeat<>nil then
begin
heartbeat.Terminate;
heartbeat.WaitFor;
freeandnil(heartbeat);
end;
if HasDebugEvent<>0 then
closehandle(HasDebugEvent);
if HasHandledDebugEvent<>0 then
closehandle(HasHandledDebugEvent);
if injectedEvents<>nil then
freeandnil(InjectedEvents);
inherited destroy;
end;
function TVEHDebugInterface.SetThreadContext(hThread: THandle; const lpContext: TContext; isFrozenThread: Boolean=false): BOOL;
var c: PContext;
{$ifdef cpu64}
c32: PContext32 absolute c;
{$endif}
begin
if isFrozenThread then //use the VEHDebugView context
begin
result:=true;
c:=@VEHDebugView.CurrentContext[0];
{$ifdef cpu64}
if not is64bit then
begin
//the given context needs to be converted to 32-bit
//c32.ContextFlags:=lpcontext.ContextFlags;
c32.Dr0:=lpcontext.Dr0;
c32.Dr1:=lpcontext.Dr1;
c32.Dr2:=lpcontext.Dr2;
c32.Dr3:=lpcontext.Dr3;
c32.Dr6:=lpcontext.Dr6;
c32.Dr7:=lpcontext.Dr7;
c32.SegGs:=lpcontext.SegGs;
c32.SegFs:=lpcontext.SegFs;
c32.SegEs:=lpcontext.SegEs;
c32.SegDs:=lpcontext.SegDs;
c32.edi:=lpcontext.rdi;
c32.esi:=lpcontext.rsi;
c32.ebx:=lpcontext.rbx;
c32.edx:=lpcontext.rdx;
c32.ecx:=lpcontext.rcx;
c32.eax:=lpcontext.rax;
c32.ebp:=lpcontext.rbp;
c32.eip:=lpcontext.rip;
c32.SegCs:=lpcontext.SegCs;
c32.EFlags:=lpcontext.EFlags;
c32.esp:=lpcontext.rsp;
c32.SegSs:=lpcontext.SegSs;
CopyMemory(@c32.ext, @lpContext.fltsave,sizeof(c32.ext));
CopyMemory(@c32.FloatSave.RegisterArea[0], @lpContext.fltsave.FloatRegisters[0], 10*8);
end else c^:=lpContext;
// end;// else lpContext:=c^;
{$else}
c^:=lpContext;
{$endif}
//Interesting effect: result:=NewKernelHandler.SetThreadContext(hThread,lpContext);
end
else
result:=NewKernelHandler.SetThreadContext(hThread,lpContext);
end;
function TVEHDebugInterface.GetThreadContext(hThread: THandle; var lpContext: TContext; isFrozenThread: Boolean=false): BOOL;
var c: PContext;
{$ifdef cpu64}
c32: PContext32 absolute c;
i: integer;
{$endif}
begin
if isFrozenThread then //use the VEHDebugView context
begin
OutputDebugString('VEH GetThreadContext. From frozen');
result:=true;
c:=@VEHDebugView.CurrentContext[0];
{$ifdef cpu64}
if not is64bit then
begin
ZeroMemory(@lpContext,sizeof(TContext));
lpContext.ContextFlags:=c32.ContextFlags;
lpContext.Dr0:=c32.Dr0;
lpContext.Dr1:=c32.Dr1;
lpContext.Dr2:=c32.Dr2;
lpContext.Dr3:=c32.Dr3;
lpContext.Dr6:=c32.Dr6;
lpContext.Dr7:=c32.Dr7;
lpContext.SegGs:=c32.SegGs;
lpContext.SegFs:=c32.SegFs;
lpContext.SegEs:=c32.SegEs;
lpContext.SegDs:=c32.SegDs;
lpContext.Rdi:=c32.Edi;
lpContext.Rsi:=c32.Esi;
lpContext.Rbx:=c32.Ebx;
lpContext.Rdx:=c32.Edx;
lpContext.Rcx:=c32.Ecx;
lpContext.Rax:=c32.Eax;
lpContext.Rbp:=c32.Ebp;
lpContext.Rip:=c32.Eip;
lpContext.SegCs:=c32.SegCs;
lpContext.EFlags:=c32.EFlags;
lpContext.Rsp:=c32.Esp;
lpContext.SegSs:=c32.SegSs;
CopyMemory(@lpcontext.FltSave, @c32.ext,sizeof(c32.ext));
end else lpContext:=c^;
{$else}
lpContext:=c^;
{$endif}
end
else
begin
OutputDebugString('VEH GetThreadContext. not frozen');
result:=NewKernelHandler.GetThreadContext(hThread,lpContext);
end;
end;
function TVEHDebugInterface.isInjectedEvent: boolean;
begin
result:=fisInjectedEvent;
end;
function TVEHDebugInterface.WaitForDebugEvent(var lpDebugEvent: TDebugEvent; dwMilliseconds: DWORD): BOOL;
var i: integer;
c: PContext;
{$ifdef cpu64}
c32: PContext32 absolute c;
{$endif}
inj: TInjectedEvent;
begin
currentThread:=0; //just making sure
if injectedEvents.count>0 then
begin
fisInjectedEvent:=true;
//fill in lpDebugEvent
inj:=TInjectedEvent(injectedEvents[0]);
lpDebugEvent.dwProcessId:=processid;
lpDebugEvent.dwThreadId:=inj.ThreadId;
lpDebugEvent.Exception.dwFirstChance:=1;
if inj.eventtype=etThreadCreate then
begin
//create thread
lpDebugEvent.dwDebugEventCode:=CREATE_THREAD_DEBUG_EVENT;
lpDebugEvent.CreateThread.hThread:=OpenThread(THREAD_ALL_ACCESS,false, inj.ThreadId);
lpDebugEvent.CreateThread.lpStartAddress:=nil;
lpDebugEvent.CreateThread.lpThreadLocalBase:=nil;
CurrentThread:=OpenThread(THREAD_ALL_ACCESS,false, lpDebugEvent.dwThreadId);
suspendThread(CurrentThread);
end
else
begin
//destroy thread
lpDebugEvent.dwDebugEventCode:=EXIT_THREAD_DEBUG_EVENT;
lpDebugEvent.ExitThread.dwExitCode:=0;
end;
inj.free;
injectedEvents.Delete(0);
exit(true);
end
else
fisInjectedEvent:=false;
result:=waitforsingleobject(HasDebugEvent, dwMilliseconds)=WAIT_OBJECT_0;
if result then
begin
ZeroMemory(@lpDebugEvent, sizeof(TdebugEvent));
// lpDebugEvent.dwDebugEventCode:=EXCEPTION_DEBUG_EVENT; //exception
lpDebugEvent.dwProcessId:=VEHDebugView.ProcessID;
lpDebugEvent.dwThreadId:=VEHDebugView.ThreadID;
lpDebugEvent.Exception.dwFirstChance:=1;
case VEHDebugView.Exception64.ExceptionCode of
$ce000000: //create process
begin
lpDebugEvent.dwDebugEventCode:=CREATE_PROCESS_DEBUG_EVENT;
lpDebugEvent.CreateProcessInfo.hFile:=0;
lpDebugEvent.CreateProcessInfo.hProcess:=processhandle;
lpDebugEvent.CreateProcessInfo.hThread:=OpenThread(THREAD_ALL_ACCESS,false, lpDebugEvent.dwThreadId);
end;
$ce000001: //create thread
begin
lpDebugEvent.dwDebugEventCode:=CREATE_THREAD_DEBUG_EVENT;
lpDebugEvent.CreateThread.hThread:=OpenThread(THREAD_ALL_ACCESS,false, lpDebugEvent.dwThreadId);
lpDebugEvent.CreateThread.lpStartAddress:=nil;
lpDebugEvent.CreateThread.lpThreadLocalBase:=nil;
lastthreadlist.Add(inttohex(lpDebugEvent.dwThreadId,1));
lastthreadpoll:=GetTickCount64;
end;
$ce000002: //destroy thread
begin
lpDebugEvent.dwDebugEventCode:=EXIT_THREAD_DEBUG_EVENT;
lpDebugEvent.ExitThread.dwExitCode:=0;
i:=lastthreadlist.indexof(inttohex(lpDebugEvent.dwThreadId,1));
if i<>-1 then
lastthreadlist.Delete(i);
end;
DBG_PRINTEXCEPTION_C:
begin
lpDebugEvent.dwDebugEventCode:=OUTPUT_DEBUG_STRING_EVENT;
lpDebugEvent.DebugString.fUnicode:=0;
if is64bit then
begin
lpDebugEvent.DebugString.nDebugStringLength:=VEHDebugView.Exception64.ExceptionInformation[0];
lpDebugEvent.DebugString.lpDebugStringData:=pointer(ptrUint(VEHDebugView.Exception64.ExceptionInformation[1]));
end
else
begin
lpDebugEvent.DebugString.nDebugStringLength:=VEHDebugView.Exception32.ExceptionInformation[0];
lpDebugEvent.DebugString.lpDebugStringData:=pointer(ptrUint(VEHDebugView.Exception32.ExceptionInformation[1]));
end;
end;
else
begin
//unknown event, standard exception
lpDebugEvent.dwDebugEventCode:=EXCEPTION_DEBUG_EVENT;
lpDebugEvent.Exception.ExceptionRecord.ExceptionRecord:=nil;
{$ifdef cpu64}
if is64bit then
begin
lpDebugEvent.Exception.ExceptionRecord.ExceptionCode:=VEHDebugView.Exception64.ExceptionCode;
lpDebugEvent.Exception.ExceptionRecord.ExceptionFlags:=VEHDebugView.Exception64.ExceptionFlags;
lpDebugEvent.Exception.ExceptionRecord.ExceptionRecord:=pointer(VEHDebugView.Exception64.ExceptionRecord);
lpDebugEvent.Exception.ExceptionRecord.ExceptionAddress:=pointer(VEHDebugView.Exception64.ExceptionAddress);
lpDebugEvent.Exception.ExceptionRecord.NumberParameters:=VEHDebugView.Exception64.NumberParameters;
for i:=0 to VEHDebugView.Exception64.NumberParameters-1 do
lpDebugEvent.Exception.ExceptionRecord.ExceptionInformation[i]:=VEHDebugView.Exception64.ExceptionInformation[i];
end
else
{$endif}
begin
lpDebugEvent.Exception.ExceptionRecord.ExceptionCode:=VEHDebugView.Exception32.ExceptionCode;
lpDebugEvent.Exception.ExceptionRecord.ExceptionFlags:=VEHDebugView.Exception32.ExceptionFlags;
lpDebugEvent.Exception.ExceptionRecord.ExceptionRecord:=pointer(ptrUint(VEHDebugView.Exception32.ExceptionRecord));
lpDebugEvent.Exception.ExceptionRecord.ExceptionAddress:=pointer(ptrUint(VEHDebugView.Exception32.ExceptionAddress));
lpDebugEvent.Exception.ExceptionRecord.NumberParameters:=VEHDebugView.Exception32.NumberParameters;
for i:=0 to VEHDebugView.Exception32.NumberParameters-1 do
lpDebugEvent.Exception.ExceptionRecord.ExceptionInformation[i]:=VEHDebugView.Exception32.ExceptionInformation[i];
end;
if lpdebugEvent.Exception.ExceptionRecord.ExceptionCode=EXCEPTION_BREAKPOINT then
begin
c:=@VEHDebugView.CurrentContext[0];
{$ifdef cpu64}
if is64bit then
c.Rip:=c.rip+1
else
c32.Eip:=c32.eip+1;
{$else}
c.eip:=c.eip+1
{$endif}
end;
end;
end;
hasPausedProcess:=true;
end;
if (lastthreadpoll>0) and (gettickcount64>lastthreadpoll+500) then
DoThreadPoll; //adds injected events if needed
end;
function TVEHDebugInterface.ContinueDebugEvent(dwProcessId: DWORD; dwThreadId: DWORD; dwContinueStatus: DWORD): BOOL;
begin
hasPausedProcess:=false;
VEHDebugView.ContinueMethod:=dwContinueStatus;
if fisInjectedEvent then
fisInjectedEvent:=false
else
SetEvent(HasHandledDebugEvent);
if currentthread<>0 then
begin
resumeThread(currentThread);
closeHandle(currentThread);
currentThread:=0;
end;
result:=true;
end;
function TVEHDebugInterface.DebugActiveProcess(dwProcessId: DWORD): WINBOOL;
var
s: tstringlist;
e: integer;
prefix: string;
testptr: ptruint;
mi: tmoduleinfo;
cfm: THandle;
err: boolean;
begin
try
processhandler.processid:=dwProcessID;
Open_Process;
symhandler.reinitialize;
is64bit:=processhandler.is64Bit;
if is64bit then
prefix:='-x86_64'
else
prefix:='-i386';
result:=false;
{ if symhandler.getmodulebyname('vehdebug'+prefix+'.dll',mi) then
exit; //no reattach supported right now }
CreateGUID(guid);
guidstring:='"'+GUIDToString(guid)+'"';
//guidstring:='Global\'+GUIDToString(guid);
OutputDebugString('Creating filemap with name '+pchar(guidstring));
ConfigFileMapping:=CreateFileMapping(INVALID_HANDLE_VALUE,nil,PAGE_READWRITE,0,sizeof(TVEHDebugSharedMem),pchar(guidstring));
if ConfigFileMapping=0 then
begin
e:=getlasterror;
OutPutDebugString('Failed:'+inttostr(e));
raise exception.Create(Format(
rsErrorWhileTryingToCreateTheConfigurationStructure, [inttostr(e)]));
end;
OutPutDebugString('Created the filemap');
VEHDebugView:=MapViewOfFile(ConfigFileMapping,FILE_MAP_READ or FILE_MAP_WRITE,0,0,sizeof(TVEHDebugSharedMem));
if (VEHDebugView=nil) then
begin
e:=GetLastError;
closehandle(ConfigFileMapping);
OutputDebugString('MapViewOfFile failed: '+inttostr(e));
raise exception.Create(Format(rsCheatEngineFailedToGetIntoTheConfig, [
inttostr(e)]));
end;
ZeroMemory(VEHDebugView,sizeof(TVEHDebugSharedMem));
Heartbeat:=THeartBeat.Create(true);
THeartBeat(Heartbeat).owner:=self;
HeartBeat.Priority:=tpHighest;
HeartBeat.Start;
VEHDebugView.ThreadWatchMethod:=0; //vehthreadwatchmethod;
if VEHRealContextOnThreadCreation then
VEHDebugView.ThreadWatchMethodConfig:=TPOLL_TCREATEREALCONTEXT
else
VEHDebugView.ThreadWatchMethodConfig:=0;;
HasDebugEvent:=CreateEvent(nil, false, false, nil);
HasHandledDebugEvent:=CreateEvent(nil, false, false, nil);
if not DuplicateHandle(GetCurrentProcess, HasDebugEvent, processhandle, @VEHDebugView^.HasDebugEvent, 0, false, DUPLICATE_SAME_ACCESS) then
raise exception.Create(
rsFailureDuplicatingTheEventHandlesToTheOtherProcess);
if not DuplicateHandle(GetCurrentProcess, HasHandledDebugEvent, processhandle, @VEHDebugView^.HasHandledDebugEvent, 0, false, DUPLICATE_SAME_ACCESS) then
raise exception.Create(
rsFailureDuplicatingTheEventHandlesToTheOtherProcess);
if not DuplicateHandle(GetCurrentProcess, ConfigFileMapping, processhandle, @cfm, 0, false, DUPLICATE_SAME_ACCESS) then
raise exception.Create(rsFailureDuplicatingTheFilemapping);
symhandler.waitforsymbolsloaded(true,'kernel32.dll');
testptr:=symhandler.getAddressFromName('"vehdebug'+prefix+'.InitializeVEH"',false,err);
if err or (testptr=0) then
begin
try
InjectDll(cheatenginedir+'vehdebug'+prefix+'.dll');
except
end;
end;
symhandler.reinitialize;
symhandler.waitforsymbolsloaded(true,'vehdebug'+prefix+'.dll');
testptr:=symhandler.getAddressFromName('"vehdebug'+prefix+'.InitializeVEH"');
s:=tstringlist.Create;
try
s.Clear;
s.Add('"vehdebug'+prefix+'.ConfigName":');
s.add('db '''+guidstring+''',0');
s.Add('"vehdebug'+prefix+'.fm":');
if processhandler.is64Bit then
s.add('dq '+inttohex(cfm,8))
else
s.add('dd '+inttohex(cfm,8));
s.Add('CreateThread("vehdebug'+prefix+'.InitializeVEH")');
//Clipboard.SetTextBuf(pchar(s.text));
if autoassemble(s,false) then
begin
//debugger is attached and ready to go
active:=true;
THeartBeat(Heartbeat).StartVersionCheck;
end
else
begin
// showmessage(s.text);
end;
result:=true;
finally
s.free;
end;
except
on e: exception do
begin
messagebox(0, pchar(e.message), pchar(utf8toansi(rsVEHDebugError)), MB_OK or MB_ICONERROR);
result:=false;
end;
end;
end;
function TVEHDebugInterface.DebugActiveProcessStop(dwProcessID: DWORD): WINBOOL;
var
prefix: string;
s: Tstringlist;
begin
result:=false;
if active and (processhandler.processid=processhandler.processid) then
begin
try
if is64Bit then
prefix:='-x86_64'
else
prefix:='-i386';
s:=tstringlist.Create;
try
s.Add('CreateThread("vehdebug'+prefix+'.UnloadVEH")');
if autoassemble(s,false) then
begin
active:=false;
result:=true;
end;
finally
s.free;
end;
except
end;
end;
if heartbeat<>nil then
begin
heartbeat.Terminate;
heartbeat.WaitFor;
freeandnil(heartbeat);
end;
end;
procedure TVEHDebugInterface.SynchronizeNoBreakList;
var i: integer;
begin
for i:=0 to min(63, length(noBreakList)-1) do
VEHDebugView.NoBreakList[i]:=noBreakList[i];
VEHDebugView.NoBreakListSize:=min(63, length(noBreakList)-1);
end;
procedure TVEHDebugInterface.AddToNoBreakList(threadid: integer);
begin
inherited AddToNoBreakList(threadid);
SynchronizeNoBreakList;
end;
procedure TVEHDebugInterface.RemoveFromNoBreakList(threadid: integer);
begin
inherited RemoveFromNoBreakList(threadid);
SynchronizeNoBreakList;
end;
procedure TVEHDebugInterface.DoThreadPoll;
var
currentlist: tstringlist;
i: integer;
inj: TInjectedEvent;
begin
currentlist:=tstringlist.create;
GetThreadList(currentlist);
for i:=0 to currentlist.count-1 do
begin
if lastthreadlist.IndexOf(currentlist[i])=-1 then
begin
//new thread event
inj:=TInjectedEvent.create;
inj.eventtype:=etThreadCreate;
inj.threadid:=strtoint('$'+currentlist[i]);
injectedEvents.Add(inj);
lastthreadlist.Add(currentlist[i]);
end;
end;
i:=0;
while i<=lastthreadlist.count-1 do
begin
if currentlist.IndexOf(lastthreadlist[i])=-1 then
begin
//destroyed thread event
inj:=TInjectedEvent.create;
inj.eventtype:=etThreadDestroy;
inj.threadid:=strtoint('$'+lastthreadlist[i]);
injectedEvents.Add(inj);
lastthreadlist.Delete(i);
end
else
inc(i);
end;
currentlist.free;
lastthreadpoll:=GetTickCount64;
end;
{$endif}
end.