├── .gitignore ├── 1.clang-format ├── HwBp.sln ├── HwBpLib ├── inc │ └── HwBpLib.h └── test │ ├── test.cpp │ └── test.vcxproj ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | /build/ 3 | /HwBpLib/Build/x64/ 4 | *.vcxproj.user 5 | /HwBpLib/test/x64/ 6 | /x64/ 7 | -------------------------------------------------------------------------------- /1.clang-format: -------------------------------------------------------------------------------- 1 | # Visual Studio generated .clang-format file 2 | 3 | # The style options in this file are a best effort attempt to replicate the 4 | # current IDE formatting configuration from Tools > Options. The following 5 | # style options, however, should be verified: 6 | # AfterClass, AfterControlStatement, AfterEnum, AfterFunction, AfterNamespace, 7 | # AfterStruct, AfterUnion 8 | 9 | AccessModifierOffset: -4 10 | AlignAfterOpenBracket: DontAlign 11 | AllowShortBlocksOnASingleLine: true 12 | AllowShortFunctionsOnASingleLine: All 13 | BasedOnStyle: LLVM 14 | BraceWrapping: 15 | AfterClass: false # TODO: verify 16 | AfterControlStatement: false # TODO: verify 17 | AfterEnum: false # TODO: verify 18 | AfterFunction: false # TODO: verify 19 | AfterNamespace: false # TODO: verify 20 | AfterStruct: false # TODO: verify 21 | AfterUnion: false # TODO: verify 22 | BeforeCatch: true 23 | BeforeElse: true 24 | IndentBraces: false 25 | SplitEmptyFunction: true 26 | SplitEmptyRecord: true 27 | BreakBeforeBraces: Custom 28 | ColumnLimit: 0 29 | Cpp11BracedListStyle: true 30 | FixNamespaceComments: false 31 | IndentCaseLabels: false 32 | IndentPPDirectives: None 33 | IndentWidth: 4 34 | MaxEmptyLinesToKeep: 10 35 | NamespaceIndentation: All 36 | PointerAlignment: Left 37 | SortIncludes: false 38 | SortUsingDeclarations: false 39 | SpaceAfterCStyleCast: false 40 | SpaceBeforeAssignmentOperators: true 41 | SpaceBeforeParens: ControlStatements 42 | SpaceInEmptyParentheses: false 43 | SpacesInCStyleCastParentheses: false 44 | SpacesInParentheses: false 45 | SpacesInSquareBrackets: false 46 | TabWidth: 4 47 | UseTab: false 48 | -------------------------------------------------------------------------------- /HwBp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29123.88 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "HwBpLib\test\test.vcxproj", "{A714E2D0-D74F-4753-8501-E4EDFACA1377}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {A714E2D0-D74F-4753-8501-E4EDFACA1377}.Debug|x64.ActiveCfg = Debug|x64 17 | {A714E2D0-D74F-4753-8501-E4EDFACA1377}.Debug|x64.Build.0 = Debug|x64 18 | {A714E2D0-D74F-4753-8501-E4EDFACA1377}.Debug|x86.ActiveCfg = Debug|x64 19 | {A714E2D0-D74F-4753-8501-E4EDFACA1377}.Release|x64.ActiveCfg = Release|x64 20 | {A714E2D0-D74F-4753-8501-E4EDFACA1377}.Release|x64.Build.0 = Release|x64 21 | {A714E2D0-D74F-4753-8501-E4EDFACA1377}.Release|x86.ActiveCfg = Release|x64 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | GlobalSection(ExtensibilityGlobals) = postSolution 27 | SolutionGuid = {5C430EB4-692E-4597-80E9-061055FEA86E} 28 | EndGlobalSection 29 | EndGlobal 30 | -------------------------------------------------------------------------------- /HwBpLib/inc/HwBpLib.h: -------------------------------------------------------------------------------- 1 | // HwBpLib.h : Include file for standard system include files, 2 | // or project specific include files. 3 | 4 | #pragma once 5 | #pragma warning(push) 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #pragma warning(pop) 13 | 14 | namespace HwBp 15 | { 16 | enum class Result 17 | { 18 | Success, 19 | CantGetThreadContext, 20 | CantSetThreadContext, 21 | NoAvailableRegisters, 22 | BadWhen, // Unsupported value of When passed 23 | BadSize // Size can only be 1, 2, 4, 8 24 | }; 25 | 26 | enum class When 27 | { 28 | ReadOrWritten, 29 | Written, 30 | Executed 31 | }; 32 | 33 | struct Breakpoint 34 | { 35 | static constexpr Breakpoint MakeFailed(Result result) 36 | { 37 | return { 38 | 0, 39 | result 40 | }; 41 | } 42 | 43 | const std::uint8_t m_registerIndex; 44 | const Result m_error; 45 | }; 46 | 47 | namespace Detail 48 | { 49 | template 50 | auto UpdateThreadContext(TAction action, TFailure failure) 51 | { 52 | CONTEXT ctx{0}; 53 | ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; 54 | if (::GetThreadContext(::GetCurrentThread(), &ctx) == FALSE) 55 | { 56 | return failure(Result::CantGetThreadContext); 57 | } 58 | 59 | std::array busyDebugRegister{{false, false, false, false}}; 60 | auto checkBusyRegister = [&](std::size_t index, DWORD64 mask) 61 | { 62 | if (ctx.Dr7 & mask) 63 | busyDebugRegister[index] = true; 64 | }; 65 | 66 | checkBusyRegister(0, 1); 67 | checkBusyRegister(1, 4); 68 | checkBusyRegister(2, 16); 69 | checkBusyRegister(3, 64); 70 | 71 | const auto actionResult = action(ctx, busyDebugRegister); 72 | 73 | if (::SetThreadContext(::GetCurrentThread(), &ctx) == FALSE) 74 | { 75 | return failure(Result::CantSetThreadContext); 76 | } 77 | 78 | return actionResult; 79 | } 80 | } 81 | 82 | Breakpoint Set(const void* onPointer, std::uint8_t size, When when) 83 | { 84 | return Detail::UpdateThreadContext( 85 | [&](CONTEXT& ctx, const std::array & busyDebugRegister) -> Breakpoint 86 | { 87 | const auto found = std::find(begin(busyDebugRegister), end(busyDebugRegister), false); 88 | if (found == end(busyDebugRegister)) 89 | { 90 | return Breakpoint::MakeFailed(Result::NoAvailableRegisters); 91 | } 92 | 93 | const auto registerIndex = static_cast(std::distance(begin(busyDebugRegister), found)); 94 | 95 | switch (registerIndex) 96 | { 97 | case 0: 98 | ctx.Dr0 = reinterpret_cast(const_cast(onPointer)); 99 | break; 100 | case 1: 101 | ctx.Dr1 = reinterpret_cast(const_cast(onPointer)); 102 | break; 103 | case 2: 104 | ctx.Dr2 = reinterpret_cast(const_cast(onPointer)); 105 | break; 106 | case 3: 107 | ctx.Dr3 = reinterpret_cast(const_cast(onPointer)); 108 | break; 109 | default: 110 | assert(!"Impossible happened - searching in array of 4 got index < 0 or > 3"); 111 | std::exit(EXIT_FAILURE); 112 | } 113 | 114 | std::bitset dr7; 115 | std::memcpy(&dr7, &ctx.Dr7, sizeof(ctx.Dr7)); 116 | 117 | dr7.set(registerIndex * 2); // Flag to enable 'local' debugging for each of 4 registers. Second bit is for global debugging, not working. 118 | 119 | switch (when) 120 | { 121 | case When::ReadOrWritten: 122 | dr7.set(16 + registerIndex * 4 + 1, true); 123 | dr7.set(16 + registerIndex * 4, true); 124 | break; 125 | 126 | case When::Written: 127 | dr7.set(16 + registerIndex * 4 + 1, false); 128 | dr7.set(16 + registerIndex * 4, true); 129 | break; 130 | 131 | case When::Executed: 132 | dr7.set(16 + registerIndex * 4 + 1, false); 133 | dr7.set(16 + registerIndex * 4, false); 134 | break; 135 | 136 | default: 137 | return Breakpoint::MakeFailed(Result::BadWhen); 138 | } 139 | 140 | switch (size) 141 | { 142 | case 1: 143 | dr7.set(16 + registerIndex * 4 + 3, false); 144 | dr7.set(16 + registerIndex * 4 + 2, false); 145 | break; 146 | 147 | case 2: 148 | dr7.set(16 + registerIndex * 4 + 3, false); 149 | dr7.set(16 + registerIndex * 4 + 2, true); 150 | break; 151 | 152 | case 8: 153 | dr7.set(16 + registerIndex * 4 + 3, true); 154 | dr7.set(16 + registerIndex * 4 + 2, false); 155 | break; 156 | 157 | case 4: 158 | dr7.set(16 + registerIndex * 4 + 3, true); 159 | dr7.set(16 + registerIndex * 4 + 2, true); 160 | break; 161 | 162 | default: 163 | return Breakpoint::MakeFailed(Result::BadSize); 164 | } 165 | 166 | std::memcpy(&ctx.Dr7, &dr7, sizeof(ctx.Dr7)); 167 | 168 | return Breakpoint{ static_cast(registerIndex), Result::Success }; 169 | }, 170 | [](auto failureCode) 171 | { 172 | return Breakpoint::MakeFailed(failureCode); 173 | } 174 | ); 175 | } 176 | 177 | void Remove(const Breakpoint& bp) 178 | { 179 | if (bp.m_error != Result::Success) 180 | { 181 | return; 182 | } 183 | 184 | Detail::UpdateThreadContext( 185 | [&](CONTEXT& ctx, const std::array&) -> Breakpoint 186 | { 187 | std::bitset dr7; 188 | std::memcpy(&dr7, &ctx.Dr7, sizeof(ctx.Dr7)); 189 | 190 | dr7.set(bp.m_registerIndex * 2, false); // Flag to enable 'local' debugging for each of 4 registers. Second bit is for global debugging, not working. 191 | 192 | std::memcpy(&ctx.Dr7, &dr7, sizeof(ctx.Dr7)); 193 | 194 | return Breakpoint{}; 195 | }, 196 | [](auto failureCode) 197 | { 198 | return Breakpoint::MakeFailed(failureCode); 199 | } 200 | ); 201 | } 202 | } -------------------------------------------------------------------------------- /HwBpLib/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include "HwBpLib.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace 10 | { 11 | struct GlobalState 12 | { 13 | static GlobalState& Get() 14 | { 15 | static GlobalState state; 16 | return state; 17 | } 18 | 19 | void Reset() 20 | { 21 | ResetBreakpoints(); 22 | } 23 | 24 | void BreakpointHit() 25 | { 26 | ++m_breakPointFound; 27 | } 28 | 29 | int GetBp() 30 | { 31 | return m_breakPointFound; 32 | } 33 | 34 | private: 35 | GlobalState() 36 | { 37 | ResetBreakpoints(); 38 | } 39 | 40 | void ResetBreakpoints() 41 | { 42 | m_breakPointFound = 0; 43 | } 44 | 45 | int m_breakPointFound; 46 | }; 47 | 48 | struct Test 49 | { 50 | using TestFunc = void (*)(); 51 | 52 | TestFunc m_func = nullptr; 53 | std::vector m_output; 54 | bool m_success = true; 55 | }; 56 | 57 | std::unordered_map g_tests; 58 | Test* g_currentTest; 59 | 60 | struct Registrar 61 | { 62 | Registrar(const char* name, Test::TestFunc func) 63 | { 64 | g_tests[name].m_func = func; 65 | } 66 | }; 67 | 68 | struct ExceptionHandler 69 | { 70 | static constexpr ULONG c_callThisHandlerFirst = 1; 71 | 72 | ExceptionHandler(PVECTORED_EXCEPTION_HANDLER handler) 73 | { 74 | m_handle = ::AddVectoredExceptionHandler(c_callThisHandlerFirst, handler); 75 | } 76 | 77 | bool IsValid() const noexcept 78 | { 79 | return m_handle != nullptr; 80 | } 81 | 82 | ~ExceptionHandler() 83 | { 84 | if (m_handle) 85 | { 86 | ::RemoveVectoredExceptionHandler(m_handle); 87 | } 88 | } 89 | 90 | void* m_handle; 91 | }; 92 | 93 | LONG MyHandler(_EXCEPTION_POINTERS* ExceptionInfo) 94 | { 95 | if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) 96 | { 97 | GlobalState::Get().BreakpointHit(); 98 | } 99 | 100 | return EXCEPTION_CONTINUE_EXECUTION; 101 | } 102 | 103 | void AssertTrue(const char* str, bool result, int line, const char* file) 104 | { 105 | if (!result) 106 | { 107 | g_currentTest->m_success = false; 108 | g_currentTest->m_output.emplace_back(); 109 | g_currentTest->m_output.back() << "Assertion failed (" << file << ": " << line << "): " << str; 110 | } 111 | } 112 | 113 | #define ASSERT(Expr_) \ 114 | AssertTrue(#Expr_, (Expr_), __LINE__, __FILE__); 115 | 116 | #define REQUIRE(Expr_) \ 117 | AssertTrue(#Expr_, (Expr_), __LINE__, __FILE__); \ 118 | ReportResults(); \ 119 | std::exit(EXIT_FAILURE); 120 | 121 | #define TEST(Name_) \ 122 | void Name_##TestFunc(); \ 123 | Registrar Name_##Registrar{#Name_, Name_##TestFunc}; \ 124 | void Name_##TestFunc() \ 125 | 126 | int ReportResults() 127 | { 128 | int testsFailed = 0; 129 | for (auto&& t : g_tests) 130 | { 131 | if (!t.second.m_success) 132 | { 133 | std::cout << "Test '" << t.first << "' failed:\n"; 134 | for (auto&& error : t.second.m_output) 135 | { 136 | std::cout << " '" << error.str() << "'\n"; 137 | } 138 | ++testsFailed; 139 | } 140 | } 141 | 142 | if (testsFailed == 0) 143 | { 144 | std::cout << "Success!\n"; 145 | return EXIT_SUCCESS; 146 | } 147 | else 148 | { 149 | std::cout << testsFailed << " tests failed!\n"; 150 | return EXIT_FAILURE; 151 | } 152 | } 153 | } 154 | 155 | TEST(OneBp) 156 | { 157 | GlobalState::Get().Reset(); 158 | 159 | int var = 42; 160 | auto bp = HwBp::Set(&var, sizeof(42), HwBp::When::Written); 161 | 162 | { 163 | ExceptionHandler h{MyHandler}; 164 | ASSERT(h.IsValid()); 165 | 166 | var = 33; 167 | 168 | ASSERT(GlobalState::Get().GetBp() == 1); 169 | } 170 | 171 | HwBp::Remove(bp); 172 | } 173 | 174 | TEST(FourBps) 175 | { 176 | GlobalState::Get().Reset(); 177 | 178 | int var[4] = {42, 43, 44, 45}; 179 | HwBp::Breakpoint bps[4] = { 180 | HwBp::Set(&var[0], sizeof(var[0]), HwBp::When::Written), 181 | HwBp::Set(&var[1], sizeof(var[0]), HwBp::When::Written), 182 | HwBp::Set(&var[2], sizeof(var[0]), HwBp::When::Written), 183 | HwBp::Set(&var[3], sizeof(var[0]), HwBp::When::Written) 184 | }; 185 | 186 | { 187 | ExceptionHandler h { MyHandler }; 188 | 189 | var[0] = 30; 190 | ASSERT(bps[0].m_registerIndex == 0); 191 | ASSERT(bps[0].m_error == HwBp::Result::Success); 192 | ASSERT(GlobalState::Get().GetBp() == 1); 193 | 194 | var[1] = 31; 195 | ASSERT(bps[1].m_registerIndex == 1); 196 | ASSERT(GlobalState::Get().GetBp() == 2); 197 | 198 | var[2] = 32; 199 | ASSERT(bps[2].m_registerIndex == 2); 200 | ASSERT(GlobalState::Get().GetBp() == 3); 201 | 202 | var[3] = 33; 203 | ASSERT(bps[3].m_registerIndex == 3); 204 | ASSERT(GlobalState::Get().GetBp() == 4); 205 | } 206 | 207 | for (auto&& bp : bps) 208 | { 209 | HwBp::Remove(bp); 210 | } 211 | } 212 | 213 | TEST(FifthBpFails_ButWorksAfter3rdWasReleased) 214 | { 215 | int var{0}; 216 | const HwBp::Breakpoint bps[5] = { 217 | HwBp::Set(&var, sizeof(var), HwBp::When::Written), 218 | HwBp::Set(&var, sizeof(var), HwBp::When::Written), 219 | HwBp::Set(&var, sizeof(var), HwBp::When::Written), 220 | HwBp::Set(&var, sizeof(var), HwBp::When::Written), 221 | HwBp::Set(&var, sizeof(var), HwBp::When::Written) 222 | }; 223 | 224 | ASSERT(bps[4].m_error == HwBp::Result::NoAvailableRegisters); 225 | 226 | HwBp::Remove(bps[2]); 227 | 228 | auto bp5Retry = HwBp::Set(&var, sizeof(var), HwBp::When::Written); 229 | ASSERT(bp5Retry.m_error == HwBp::Result::Success); 230 | ASSERT(bp5Retry.m_registerIndex == bps[2].m_registerIndex); 231 | 232 | HwBp::Remove(bps[0]); 233 | HwBp::Remove(bps[1]); 234 | HwBp::Remove(bp5Retry); 235 | HwBp::Remove(bps[3]); 236 | } 237 | 238 | int main() 239 | { 240 | for (auto&& t : g_tests) 241 | { 242 | g_currentTest = &t.second; 243 | t.second.m_func(); 244 | g_currentTest = nullptr; 245 | } 246 | 247 | return ReportResults(); 248 | } -------------------------------------------------------------------------------- /HwBpLib/test/test.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 16.0 15 | {A714E2D0-D74F-4753-8501-E4EDFACA1377} 16 | Win32Proj 17 | test 18 | 10.0 19 | 20 | 21 | 22 | Application 23 | true 24 | v142 25 | Unicode 26 | 27 | 28 | Application 29 | false 30 | v142 31 | true 32 | Unicode 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | true 48 | 49 | 50 | false 51 | 52 | 53 | 54 | Level4 55 | true 56 | %(PreprocessorDefinitions) 57 | ..\inc\;%(AdditionalIncludeDirectories) 58 | true 59 | 60 | 61 | 62 | 63 | Disabled 64 | true 65 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 66 | 67 | 68 | Console 69 | true 70 | 71 | 72 | 73 | 74 | MaxSpeed 75 | true 76 | true 77 | true 78 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 79 | 80 | 81 | Console 82 | true 83 | true 84 | true 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Artem Tokmakov 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 | # Library to programmatically set hardware breakpoints 2 | Include `HwBpLib\inc\HwBpLib.h`. 3 | 4 | Use `HwBp::Set(pointer, size, condition)` to set a breakpoint on Write/ReadWrite or Execution of a memory at a certain address, with specified size. 5 | 6 | Use `HwBp::Remove()` to remove breakpoint. 7 | 8 | This library was inspired by https://www.codeproject.com/Articles/28071/Toggle-hardware-data-read-execute-breakpoints-prog. 9 | It only sets breakpoints on current thread, but does it faster (as it doesn't spin up threads to do that). 10 | 11 | ## Building tests 12 | The library is header-only, but you can build and run test file by building HwBp.sln. 13 | --------------------------------------------------------------------------------