-
Notifications
You must be signed in to change notification settings - Fork 80
/
Tracer.pas
139 lines (114 loc) · 3.57 KB
/
Tracer.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
unit Tracer;
interface
uses Windows, SysUtils, Utils;
type
TTracer = class;
TTracePredicate = function(Tracer: TTracer; var C: TContext): Boolean of object;
TTracer = class
private
FProcessID, FThreadID: Cardinal;
FThreadHandle: THandle;
FPredicate: TTracePredicate;
FCounter, FLimit: Cardinal;
FLimitReached: Boolean;
Log: TLogProc;
FStartAddress: NativeUInt;
function OnSingleStep(const Ev: TDebugEvent): Cardinal;
public
constructor Create(AProcessID, AThreadID: Cardinal; AThreadHandle: THandle;
APredicate: TTracePredicate; ALog: TLogProc);
procedure Trace(AAddress: NativeUInt; ALimit: Cardinal);
property StartAddress: NativeUInt read FStartAddress;
property Counter: Cardinal read FCounter;
property LimitReached: Boolean read FLimitReached;
end;
implementation
{ TTracer }
constructor TTracer.Create(AProcessID, AThreadID: Cardinal; AThreadHandle: THandle;
APredicate: TTracePredicate; ALog: TLogProc);
begin
FProcessID := AProcessID;
FThreadID := AThreadID;
FThreadHandle := AThreadHandle;
FPredicate := APredicate;
Log := ALog;
end;
procedure TTracer.Trace(AAddress: NativeUInt; ALimit: Cardinal);
var
C: TContext;
Ev: TDebugEvent;
Status: Cardinal;
hThread: THandle;
begin
FCounter := 0;
FLimit := ALimit;
FLimitReached := False;
FStartAddress := AAddress;
C.ContextFlags := CONTEXT_CONTROL;
if not GetThreadContext(FThreadHandle, C) then
RaiseLastOSError;
C.Eip := AAddress;
C.EFlags := C.EFlags or $100; // Trap
if not SetThreadContext(FThreadHandle, C) then
RaiseLastOSError;
if not ContinueDebugEvent(FProcessID, FThreadID, DBG_CONTINUE) then
Exit;
Status := DBG_EXCEPTION_NOT_HANDLED;
while WaitForDebugEvent(Ev, INFINITE) do
begin
if Ev.dwThreadId <> FThreadID then
begin
Log(ltInfo, Format('Suspending spurious thread %d', [Ev.dwThreadId]));
hThread := OpenThread(2, False, Ev.dwThreadId); // THREAD_SUSPEND_RESUME
if hThread <> INVALID_HANDLE_VALUE then
begin
SuspendThread(hThread);
CloseHandle(hThread);
end;
ContinueDebugEvent(Ev.dwProcessId, Ev.dwThreadId, DBG_CONTINUE);
Continue;
end;
case Ev.dwDebugEventCode of
EXCEPTION_DEBUG_EVENT:
begin
if Ev.Exception.ExceptionRecord.ExceptionCode = EXCEPTION_SINGLE_STEP then
begin
Status := OnSingleStep(Ev);
if Status = DBG_CONTROL_BREAK then
Break;
end
else
begin
Log(ltFatal, Format('Unexpected exception during tracing: %.8X at %p in thread %d', [Ev.Exception.ExceptionRecord.ExceptionCode, Ev.Exception.ExceptionRecord.ExceptionAddress, Ev.dwThreadId]));
Exit;
end;
end;
else
Status := DBG_CONTINUE;
end;
ContinueDebugEvent(Ev.dwProcessId, Ev.dwThreadId, Status);
end;
end;
function TTracer.OnSingleStep(const Ev: TDebugEvent): Cardinal;
var
C: TContext;
begin
Inc(FCounter);
if (FLimit <> 0) and (FCounter > FLimit) then
begin
FLimitReached := True;
Log(ltInfo, 'Giving up trace due to instruction limit');
Exit(DBG_CONTROL_BREAK);
end;
C.ContextFlags := CONTEXT_CONTROL;
if not GetThreadContext(FThreadHandle, C) then
RaiseLastOSError;
if FPredicate(Self, C) then
Result := DBG_CONTROL_BREAK
else
Result := DBG_CONTINUE;
C.EFlags := C.EFlags or $100;
if not SetThreadContext(FThreadHandle, C) then
RaiseLastOSError;
end;
end.