forked from microsoft/Xbox-ATG-Samples
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDataBreak.cpp
219 lines (204 loc) · 11.2 KB
/
DataBreak.cpp
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//--------------------------------------------------------------------------------------
// DataBreak.cpp
//
// Advanced Technology Group (ATG)
// Copyright (C) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------
#include "pch.h"
#include "DataBreak.h"
#include <thread>
#include "windows.h"
namespace ATG
{
namespace DataBreak
{
struct DataBreakThreadParams
{
HANDLE threadHandle; // Which thread to set the breakpoint on
uintptr_t address; // The location in memory for the breakpoint
DEBUG_REGISTER debugRegister; // Which of the four register slots to modify
ADDRESS_SIZE addressSize; // How many bytes for the range used by the breakpoint
ADDRESS_OPERATION addressOperation; // Should this be an execution, read/write, or write breakpoint
bool addDebugUsage; // Add the breakpoint or clear the breakpoint
bool success; // Was the operation successful
};
// The debug registers can only be accessed from Ring 0 which not available to user mode code
// The trick is to get the kernel to modify the registers for user mode code.
// This is done through modifying the thread context. The context can only be modified when the thread is suspended
// This sample creates a unique thread to perform the modification and DataBreakThread is the thread function
void DataBreakThread(DataBreakThreadParams& params)
{
CONTEXT threadContext;
threadContext.ContextFlags = CONTEXT_DEBUG_REGISTERS; // We're only interested in the debug registers
params.success = false;
if (SuspendThread(params.threadHandle) == ((DWORD)-1)) // The thread must be suspended to query its context without getting bogus data
return;
if (GetThreadContext(params.threadHandle, &threadContext) == 0)
{
ResumeThread(params.threadHandle);
return;
}
// debug registers 0,1,2,3 hold the address for slot 1,2,3,4
// debug register 7 contains the control data for how to interpret each slot
if (!params.addDebugUsage) // Clearing the debug register slot
{
switch (params.debugRegister) // This code uses local breakpoints which are bits 0,2,4,6 for slot 1,2,3,4
{ // On a task switch these flags are swapped with the value from the new task
case DEBUG_REGISTER::REGISTER_1: // Bits 1,3,5,7 represent global breakpoint mode. This enables breakpoints for all tasks
threadContext.Dr7 &= ~(1 << 0); // However Windows sanitizes these flags when setting the thread context and clears the global bits
threadContext.Dr0 = 0;
break;
case DEBUG_REGISTER::REGISTER_2:
threadContext.Dr7 &= ~(1 << 2);
threadContext.Dr1 = 0;
break;
case DEBUG_REGISTER::REGISTER_3:
threadContext.Dr7 &= ~(1 << 4);
threadContext.Dr2 = 0;
break;
case DEBUG_REGISTER::REGISTER_4:
threadContext.Dr7 &= ~(1 << 6);
threadContext.Dr3 = 0;
break;
}
}
else
{
if (params.addressOperation == ADDRESS_OPERATION::EXECUTION) // all execution breakpoints require length 1 for later flags
params.addressSize = ADDRESS_SIZE::SIZE_1;
switch (params.addressSize) // The address for a breakpoint needs to be aligned to the requested size
{
case ADDRESS_SIZE::SIZE_2:
params.address &= ~0x01;
break;
case ADDRESS_SIZE::SIZE_4:
params.address &= ~0x03;
break;
case ADDRESS_SIZE::SIZE_8:
params.address &= ~0x07;
break;
}
threadContext.Dr6 = 0; // debug register 6 represents the status when an exception is fired. These flags should be cleared in this case
threadContext.Dr7 |= (1 << 8); // Bits 8 tells the processor to report the exact instruction that triggered the breakpoint for local breakpoints
uint32_t registerIndex = 0;
switch (params.debugRegister) // This code uses local breakpoints which are bits 0,2,4,6 for slot 1,2,3,4
{ // On a task switch these flags are swapped with the value from the new task
case DEBUG_REGISTER::REGISTER_1: // Bits 1,3,5,7 represent global breakpoint mode. This enables breakpoints for all tasks
threadContext.Dr7 |= (1 << 0); // However Windows sanitizes these flags when setting the thread context and clears the global bits
threadContext.Dr0 = (DWORD64)params.address;
registerIndex = 0;
break;
case DEBUG_REGISTER::REGISTER_2:
threadContext.Dr7 |= (1 << 2);
threadContext.Dr1 = (DWORD64)params.address;
registerIndex = 1;
break;
case DEBUG_REGISTER::REGISTER_3:
threadContext.Dr7 |= (1 << 4);
threadContext.Dr2 = (DWORD64)params.address;
registerIndex = 2;
break;
case DEBUG_REGISTER::REGISTER_4:
threadContext.Dr7 |= (1 << 6);
threadContext.Dr3 = (DWORD64)params.address;
registerIndex = 3;
break;
}
threadContext.Dr7 &= ~((3 << 18) << (registerIndex * 4)); // Bits 18-19, 22-23, 26-27, 30-31 represent the address length for slot 1,2,3,4
switch (params.addressSize)
{
case ADDRESS_SIZE::SIZE_1:
threadContext.Dr7 |= ((0 << 18) << (registerIndex * 4));
break;
case ADDRESS_SIZE::SIZE_2:
threadContext.Dr7 |= ((1 << 18) << (registerIndex * 4));
break;
case ADDRESS_SIZE::SIZE_4:
threadContext.Dr7 |= ((3 << 18) << (registerIndex * 4));
break;
case ADDRESS_SIZE::SIZE_8:
threadContext.Dr7 |= ((2 << 18) << (registerIndex * 4));
break;
}
threadContext.Dr7 &= ~((3 << 16) << (registerIndex * 4)); // Bits 16-17, 20-21, 24-25, 28-29 represent the type of breakpoint for slot 1,2,3,4
switch (params.addressOperation)
{
case ADDRESS_OPERATION::EXECUTION:
threadContext.Dr7 |= ((0 << 16) << (registerIndex * 4));
break;
case ADDRESS_OPERATION::WRITE:
threadContext.Dr7 |= ((1 << 16) << (registerIndex * 4));
break;
case ADDRESS_OPERATION::READ_WRITE:
threadContext.Dr7 |= ((3 << 16) << (registerIndex * 4));
break;
}
}
// only if we were able to set the new thread context then it was a success
if (SetThreadContext(params.threadHandle, &threadContext) != 0)
params.success = true;
ResumeThread(params.threadHandle);
}
//************************************
// Method: SetHardwareBreakPointForThread
// FullName: ATG::DataBreak::SetHardwareBreakPointForThread
// Returns: bool indicating success or failure
// Parameter: void * thread Which thread to set the breakpoint for, can be the current thread, Windows HANDLE object
// Parameter: void * address The location in memory of the breakpoint
// Parameter: DEBUG_REGISTER debugRegister Which debug register slot to use on the processor, only 4 hardware breakpoints are supported
// Parameter: ADDRESS_OPERATION addressOperation What type of operation to break on
// Parameter: ADDRESS_SIZE addressSize How big is the memory location in questions, 1 byte, 2 bytes, etc.
//************************************
bool SetHardwareBreakPointForThread(HANDLE thread, void *address, DEBUG_REGISTER debugRegister, ADDRESS_OPERATION addressOperation, ADDRESS_SIZE addressSize)
{
DataBreakThreadParams params;
params.threadHandle = thread;
params.address = reinterpret_cast<uintptr_t> (address);
params.addDebugUsage = true;
params.debugRegister = debugRegister;
params.addressSize = addressSize;
params.addressOperation = addressOperation;
params.success = false;
if (GetCurrentThreadId() == GetThreadId(thread)) // Setting the breakpoint for the current thread requires another thread to perform the action
{
std::thread *dataBreakThread;
dataBreakThread = new std::thread(DataBreakThread, std::ref(params));
dataBreakThread->join();
}
else
{
DataBreakThread(params);
}
return params.success;
}
//************************************
// Method: ClearHardwareBreakPointForThread
// FullName: ATG::DataBreak::ClearHardwareBreakPointForThread
// Returns: bool indicating success or failure
// Parameter: void * thread Which thread to clear the breakpoint for, can be the current thread
// Parameter: DEBUG_REGISTER debugRegister Which debug register to use on the processor, only 4 hardware breakpoints are supported
//************************************
bool ClearHardwareBreakPointForThread(HANDLE thread, DEBUG_REGISTER debugRegister)
{
DataBreakThreadParams params;
params.threadHandle = thread;
params.address = 0;
params.addDebugUsage = false;
params.debugRegister = debugRegister;
params.addressSize = ADDRESS_SIZE::SIZE_1;
params.addressOperation = ADDRESS_OPERATION::READ_WRITE;
params.success = false;
if (GetCurrentThreadId() == GetThreadId(thread)) // clearing the breakpoint for the current thread requires another thread to perform the action
{
std::thread *dataBreakThread;
dataBreakThread = new std::thread(DataBreakThread, std::ref(params));
dataBreakThread->join();
}
else
{
DataBreakThread(params);
}
return params.success;
}
}
}