├── .gitignore ├── LICENSE.md ├── README.md ├── inc └── dubstep.h ├── project ├── dubstep.sln ├── dubstep.vcxproj └── dubstep.vcxproj.filters └── test └── test.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.sdf 3 | *.suo 4 | *.opensdf 5 | project/Debug 6 | project/Release -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Justin Boswell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DuBStep (Dynamic Breakpoint API) 2 | ##### A Library for creating hardware execution and data breakpoints at runtime on Win32/Win64 3 | 4 | ### Purpose 5 | 6 | Sometimes you need to set breakpoints from code. For example, trapping a buffer overrun by watching the memory after the end of your buffer. Or setting an instruction breakpoint after conditions have been met at runtime. Dubstep lets you do this. 7 | 8 | Once you set a breakpoint, they will trigger in Visual Studio or CDB. If you have data breakpoints set in Visual Studio, the debugger and your code will fight. It's generally best to keep breakpoints to a minimum in VS when trying to use Dubstep to hunt something down. Visual Studio 2012's debugger appears to check the debug registers before stomping on them, but previous versions will just happily overwrite. 9 | 10 | Works on: Visual Studio 2008 and newer. Should work on earlier versions, but they haven't been tested. 11 | 12 | ### Usage 13 | Everything is implemented in a single header, dubstep.h, for ease of integration. 14 | Simply include [dubstep.h](https://github.com/justinboswell/dubstep/raw/master/inc/dubstep.h) into your project somewhere. Note that it will include ``. 15 | See the test source for example usage. 16 | 17 | ### API 18 | 19 | * `dubstep::BreakpointHandle dubstep::SetBreakpoint(dubstep::BreakpointType type, void* address, dubstep::BreakpointSize size)` 20 | * Types: 21 | * dubstep::TYPE_Exec: trap when the PC hits this address 22 | * dubstep::TYPE_Access: trap data reads and writes 23 | * dubstep::TYPE_Write: trap data writes 24 | * Sizes: 25 | * dubstep::SIZE_1 26 | * dubstep::SIZE_2 27 | * dubstep::SIZE_4 28 | * dubstep::SIZE_8 29 | * Must be called from the thread you wish to monitor. You can have up to 4 active breakpoints per thread. 30 | * Returns a HANDLE to the breakpoint which can be passed to `ClearBreakpoint` to cancel it. A return value of 0 indicates that the breakpoint could not be created. Causes: 31 | * You do not have permission to `OpenThread` with `THREAD_ALL_ACCESS` 32 | * There are no available breakpoint registers 33 | 34 | * `bool dubstep::ClearBreakpoint(dubstep::BreakpointHandle breakpoint)` 35 | * Cancels a breakpoint set by `SetBreakpoint`. 36 | 37 | * `void dubstep::SetBreakpointHandler(BreakpointHandler handler)` 38 | * Handler signature: `void MyHandler(void* address)` 39 | * Installs a callback that will notify you when a breakpoint is hit. Note that this will make use of `SetUnhandledExceptionFilter()` and will override your filter if you have one. Also, you may stomp the filter if you install it after your first breakpoint after installing the breakpoint handler. Vectored exception handlers do not work on x64, so those are not employed here. 40 | 41 | ### Debug Registers Reference 42 | * DR0, DR1, DR2, DR3 breakpoint address 43 | * DR6: Low 4 bits contain the 1-based index of the debug register that tripped 44 | * 0x1: DR0 45 | * 0x2: DR1 46 | * 0x4: DR2 47 | * 0x8: DR3 48 | * DR7: flags: 49 | * Bits 0-7: Flags for each of the 4 debug registers (2 for each). 50 | * (1 << (reg * 2)) = Local/Process, 51 | * (1 << (reg * 2 + 1)) = Global. Global requires kernel privileges. If you are reading this, you don't have them. 52 | * Bits 16-23 : 2 bits for each register, breakpoint type: 53 | * 0x0: Code 54 | * 0x1: Write 55 | * 0x2: Reserved 56 | * 0x3: Triggers when data is read or written 57 | * Bits 24-31: 2 bits for each register, data size in bytes: 58 | * 0x0: 1 59 | * 0x1: 2 60 | * 0x2: 8 61 | * 0x3: 4 62 | 63 | ### References 64 | * http://en.wikipedia.org/wiki/X86_debug_register 65 | * [ThreadContext breakpoints in Windows] (http://www.codeproject.com/Articles/28071/Toggle-hardware-data-read-execute-breakpoints-prog) 66 | * [Mac OSX] (http://stackoverflow.com/questions/2604439/how-do-i-write-x86-debug-registers-from-user-space-on-osx) 67 | * [Linux] (http://www.alexonlinux.com/how-debugger-works) 68 | -------------------------------------------------------------------------------- /inc/dubstep.h: -------------------------------------------------------------------------------- 1 | 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Justin Boswell 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #ifndef DUBSTEP_H_INC 25 | #define DUBSTEP_H_INC 26 | 27 | #include 28 | 29 | namespace dubstep { 30 | 31 | enum BreakpointType 32 | { 33 | TYPE_Exec = 0, 34 | TYPE_Access = 3, 35 | TYPE_Write = 1 36 | }; 37 | 38 | enum BreakpointSize 39 | { 40 | SIZE_1 = 0, 41 | SIZE_2 = 1, 42 | SIZE_4 = 3, 43 | SIZE_8 = 2 44 | }; 45 | 46 | typedef void (*BreakpointHandler)(void*); 47 | 48 | namespace internal 49 | { 50 | enum Scope 51 | { 52 | SCOPE_Local, 53 | SCOPE_Global, // Need kernel privileges, so unimplemented for now 54 | }; 55 | 56 | template 57 | class Breakpoint 58 | { 59 | public: 60 | BreakpointType Type; 61 | BreakpointSize Size; 62 | void* Address; 63 | HANDLE Thread; 64 | HANDLE Complete; 65 | volatile bool Enabled; 66 | int Register; 67 | 68 | Breakpoint(BreakpointType type, void* address, BreakpointSize size) 69 | : Type(type) 70 | , Size(size) 71 | , Address(address) 72 | , Thread(INVALID_HANDLE_VALUE) 73 | , Complete(INVALID_HANDLE_VALUE) 74 | , Register(-1) 75 | , Enabled(false) 76 | { 77 | } 78 | 79 | ~Breakpoint() 80 | { 81 | } 82 | 83 | // Get access to the thread's context with full privileges 84 | bool OpenThread() 85 | { 86 | Thread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, GetCurrentThreadId()); 87 | return Thread != 0; 88 | } 89 | 90 | void CloseThread() 91 | { 92 | ::CloseHandle(Thread); 93 | } 94 | 95 | bool Attach() 96 | { 97 | Enabled = true; 98 | 99 | return WriteThreadContext(&AddToThreadContext); 100 | } 101 | 102 | bool Detach() 103 | { 104 | Enabled = false; 105 | 106 | return WriteThreadContext(&RemoveFromThreadContext); 107 | } 108 | 109 | // Thread context cannot be written for a thread that is currently running, 110 | // therefore spawn a new thread whose only job is to suspend the current 111 | // thread, update its context, and resume it. 112 | bool WriteThreadContext(LPTHREAD_START_ROUTINE threadProc) 113 | { 114 | if (OpenThread()) 115 | { 116 | // Create event to signal that context write is complete, create a thread and then wait until 117 | // it adds the breakpoint to this thread's context 118 | Complete = ::CreateEvent(NULL, FALSE, FALSE, NULL); 119 | ::CreateThread(NULL, 0, threadProc, static_cast(this), 0, NULL); 120 | ::WaitForSingleObject(Complete, INFINITE); 121 | ::CloseHandle(Complete); 122 | Complete = INVALID_HANDLE_VALUE; 123 | 124 | CloseThread(); 125 | 126 | return Register != -1; 127 | } 128 | 129 | return false; 130 | } 131 | 132 | static DWORD WINAPI AddToThreadContext(LPVOID param) 133 | { 134 | Breakpoint* breakpoint = static_cast(param); 135 | 136 | // Stop the thread 137 | ::SuspendThread(breakpoint->Thread); 138 | 139 | // read the current context so we can find an open register 140 | CONTEXT ctx = {0}; 141 | ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; 142 | ::GetThreadContext(breakpoint->Thread, &ctx); 143 | 144 | // find the first available register 145 | int regIdx = -1; 146 | for (int idx = 0; idx < 4; ++idx) 147 | { 148 | const unsigned regFlag = (1 << (idx * 2)); 149 | if ((ctx.Dr7 & regFlag) == 0) 150 | { 151 | regIdx = idx; 152 | break; 153 | } 154 | } 155 | 156 | if (regIdx != -1) 157 | { 158 | breakpoint->Register = regIdx; 159 | switch (regIdx) 160 | { 161 | case 0: ctx.Dr0 = reinterpret_cast(breakpoint->Address); break; 162 | case 1: ctx.Dr1 = reinterpret_cast(breakpoint->Address); break; 163 | case 2: ctx.Dr2 = reinterpret_cast(breakpoint->Address); break; 164 | case 3: ctx.Dr3 = reinterpret_cast(breakpoint->Address); break; 165 | } 166 | 167 | // Compute register flags 168 | const unsigned typeShift = 16 + (regIdx * 4); 169 | const unsigned sizeShift = 18 + (regIdx * 4); 170 | const unsigned typeFlags = (breakpoint->Type << typeShift); 171 | const unsigned sizeFlags = (breakpoint->Size << sizeShift); 172 | const unsigned regFlags = (1 << (regIdx*2)); 173 | const unsigned mask = (0x3 << typeShift) | (0x3 << sizeShift); 174 | 175 | // Update debug register flags 176 | ctx.Dr7 = (ctx.Dr7 & ~mask) | (regFlags | typeFlags | sizeFlags); 177 | 178 | // write the context back to the thread 179 | ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; 180 | ::SetThreadContext(breakpoint->Thread, &ctx); 181 | } 182 | else 183 | { 184 | breakpoint->Enabled = false; 185 | } 186 | 187 | // resume the thread and notify the caller that work is complete 188 | ::ResumeThread(breakpoint->Thread); 189 | ::SetEvent(breakpoint->Complete); 190 | 191 | return 0; 192 | } 193 | 194 | static DWORD WINAPI RemoveFromThreadContext(LPVOID param) 195 | { 196 | Breakpoint* breakpoint = static_cast(param); 197 | 198 | // Stop the thread 199 | ::SuspendThread(breakpoint->Thread); 200 | 201 | CONTEXT ctx = {0}; 202 | ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; 203 | 204 | // Disable the breakpoint 205 | unsigned regFlag = (1 << (breakpoint->Register * 2)); 206 | ctx.Dr7 &= ~regFlag; 207 | 208 | // write the debug registers back to the thread context 209 | ::SetThreadContext(breakpoint->Thread, &ctx); 210 | 211 | // restart the thread and signal that work is complete 212 | ::ResumeThread(breakpoint->Thread); 213 | ::SetEvent(breakpoint->Complete); 214 | 215 | return 0; 216 | } 217 | 218 | static LONG WINAPI FilterException(LPEXCEPTION_POINTERS ex) 219 | { 220 | if (ex->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) 221 | { 222 | if (Handler) 223 | { 224 | // Dr6's low bits (0-3) will contain the debug register that tripped 225 | unsigned regBit = ex->ContextRecord->Dr6 & 0x0f; 226 | void* address = NULL; 227 | switch (regBit) 228 | { 229 | case 1: address = reinterpret_cast(ex->ContextRecord->Dr0); break; 230 | case 2: address = reinterpret_cast(ex->ContextRecord->Dr1); break; 231 | case 4: address = reinterpret_cast(ex->ContextRecord->Dr2); break; 232 | case 8: address = reinterpret_cast(ex->ContextRecord->Dr3); break; 233 | } 234 | (*Handler)(address); 235 | } 236 | return EXCEPTION_CONTINUE_EXECUTION; 237 | } 238 | 239 | return EXCEPTION_CONTINUE_SEARCH; 240 | } 241 | 242 | static BreakpointHandler Handler; 243 | }; 244 | } // namespace dubstep::internal 245 | 246 | typedef internal::Breakpoint Breakpoint; 247 | typedef DWORD_PTR BreakpointHandle; 248 | 249 | template 250 | BreakpointHandler internal::Breakpoint::Handler = NULL; 251 | 252 | void SetBreakpointHandler(BreakpointHandler handler) 253 | { 254 | Breakpoint::Handler = handler; 255 | 256 | // install the exception filter if user has requested notification, 257 | // otherwise clear it 258 | if (handler) 259 | ::SetUnhandledExceptionFilter(Breakpoint::FilterException); 260 | else 261 | ::SetUnhandledExceptionFilter(NULL); 262 | } 263 | 264 | BreakpointHandle SetBreakpoint(BreakpointType type, void *address, BreakpointSize size) 265 | { 266 | Breakpoint* breakpoint = new Breakpoint(type, address, size); 267 | if (!breakpoint->Attach()) 268 | { 269 | delete breakpoint; 270 | return 0; 271 | } 272 | 273 | return reinterpret_cast(breakpoint); 274 | } 275 | 276 | bool ClearBreakpoint(BreakpointHandle bph) 277 | { 278 | Breakpoint* breakpoint = reinterpret_cast(bph); 279 | 280 | bool detached = breakpoint->Detach(); 281 | delete breakpoint; 282 | 283 | return detached; 284 | } 285 | 286 | } // namespace dubstep 287 | 288 | #endif // DUBSTEP_H_INC 289 | -------------------------------------------------------------------------------- /project/dubstep.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Express 2012 for Windows Desktop 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dubstep", "dubstep.vcxproj", "{0F88EE3D-3457-434A-B82A-FA7BB46C47EE}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {0F88EE3D-3457-434A-B82A-FA7BB46C47EE}.Debug|Win32.ActiveCfg = Debug|Win32 13 | {0F88EE3D-3457-434A-B82A-FA7BB46C47EE}.Debug|Win32.Build.0 = Debug|Win32 14 | {0F88EE3D-3457-434A-B82A-FA7BB46C47EE}.Release|Win32.ActiveCfg = Release|Win32 15 | {0F88EE3D-3457-434A-B82A-FA7BB46C47EE}.Release|Win32.Build.0 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /project/dubstep.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {0F88EE3D-3457-434A-B82A-FA7BB46C47EE} 15 | Win32Proj 16 | dubstep 17 | 18 | 19 | 20 | Application 21 | true 22 | v110 23 | Unicode 24 | 25 | 26 | Application 27 | false 28 | v110 29 | true 30 | Unicode 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | true 44 | $(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSDK_IncludePath);..\inc 45 | 46 | 47 | false 48 | $(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSDK_IncludePath);..\inc 49 | 50 | 51 | 52 | 53 | 54 | Level3 55 | Disabled 56 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 57 | 58 | 59 | Console 60 | true 61 | 62 | 63 | 64 | 65 | Level3 66 | 67 | 68 | MaxSpeed 69 | true 70 | true 71 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 72 | 73 | 74 | Console 75 | true 76 | true 77 | true 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /project/dubstep.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | 23 | 24 | Source Files 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "dubstep.h" 6 | 7 | void BreakpointHandler(void* address) 8 | { 9 | std::cout << "Breakpoint hit @ 0x" << std::hex << reinterpret_cast(address) << std::endl; 10 | } 11 | 12 | int main(int argc, char* argv[]) 13 | { 14 | wchar_t buf[32]; 15 | lstrcpy(buf, L"This is a TEST"); 16 | 17 | dubstep::SetBreakpointHandler(BreakpointHandler); 18 | 19 | dubstep::BreakpointHandle bp = dubstep::SetBreakpoint(dubstep::TYPE_Access, buf, dubstep::SIZE_4); 20 | assert(bp != 0); 21 | 22 | #ifdef DEBUG_EXCEPTION_FILTER 23 | __try 24 | { 25 | buf[1] = L'f'; 26 | } 27 | __except (dubstep::Breakpoint::FilterException(GetExceptionInformation())) 28 | { 29 | std::cout << "write trapped" << std::endl; 30 | } 31 | #else 32 | buf[1] = L'f'; 33 | #endif 34 | 35 | bool cleared = dubstep::ClearBreakpoint(bp); 36 | assert(cleared); 37 | 38 | return 0; 39 | } 40 | --------------------------------------------------------------------------------