Skip to content

Instantly share code, notes, and snippets.

@Autoplay1999
Last active September 5, 2023 16:38
Show Gist options
  • Save Autoplay1999/8b90fdeab5775be47ebd18c3f628a007 to your computer and use it in GitHub Desktop.
Save Autoplay1999/8b90fdeab5775be47ebd18c3f628a007 to your computer and use it in GitHub Desktop.
Hardware Breakpoint
#include "hwbp.h"
#include <deque>
#include <set>
#include <assert.h>
#include <TlHelp32.h>
#define DEBUG_GET_LOCAL_ENABLE(i,dr7) ((dr7 >> (i * 2)) & 0x1)
#define DEBUG_GET_CONDITION(i,dr7) ((dr7 >> (16 + i * 4)) & 0x3)
#define DEBUG_GET_LENGTH(i,dr7) ((dr7 >> (18 + i * 4)) & 0x3)
#define DEBUG_SET_LOCAL_ENABLE(i,v,dr7) dr7 = (v ? dr7 | ((ULONG_PTR)1 << (i * 2)) : dr7 & ~((ULONG_PTR)1 << (i * 2)))
#define DEBUG_SET_CONDITION(i,v,dr7) dr7 = (v ? dr7 | ((ULONG_PTR)v << (16 + i * 4)) : dr7 & ~((ULONG_PTR)3 << (16 + i * 4)))
#define DEBUG_SET_LENGTH(i,v,dr7) dr7 = (v ? dr7 | ((ULONG_PTR)v << (18 + i * 4)) : dr7 & ~((ULONG_PTR)3 << (18 + i * 4)))
namespace hwbp {
typedef struct _DebugControl {
// Bits Description
unsigned int L0 : 1; // 00 - Local enable for breakpoint #0
unsigned int G0 : 1; // 01 - Global enable for breakpoint #0
unsigned int L1 : 1; // 02 - Local enable for breakpoint #1
unsigned int G1 : 1; // 03 - Global enable for breakpoint #1
unsigned int L2 : 1; // 04 - Local enable for breakpoint #2
unsigned int G2 : 1; // 05 - Global enable for breakpoint #2
unsigned int L3 : 1; // 06 - Local enable for breakpoint #3
unsigned int G3 : 1; // 07 - Global enable for breakpoint #3
unsigned int LE : 1; // 08 - (386 only) Local Exact Breakpoint Enable
unsigned int GE : 1; // 09 - (386 only) Global Exact Breakpoint Enable
unsigned int Reserved0 : 1; // 10 - Reserved, read-only, read as 1 and should be written as 1.
unsigned int RTM : 1; // 11 - (Processors with Intel TSX only) Enable advanced debugging of RTM transactions (only if DEBUGCTL bit 15 is also set) On other processors: reserved, read-only, read as 0 and should be written as 0.
unsigned int IRSIME : 1; // 12 - (386/486 processors only) Action on breakpoint match: 0 = INT 1 (#DB exception, default), 1 = Break to ICE/SMM[b] On other processors: Reserved, read-only, read as 0 and should be written as 0.
unsigned int GD : 1; // 13 - General Detect Enable. If set, will cause a debug exception on any attempt at accessing the DR0-DR7 registers
unsigned int Reserved1 : 2; // 14 - Reserved, should be written as all-0s.
unsigned int RW0 : 2; // 16 - Breakpoint condition for breakpoint #0
unsigned int LEN0 : 2; // 18 - Breakpoint length for breakpoint #0
unsigned int RW1 : 2; // 20 - Breakpoint condition for breakpoint #1
unsigned int LEN1 : 2; // 22 - Breakpoint length for breakpoint #1
unsigned int RW2 : 2; // 24 - Breakpoint condition for breakpoint #2
unsigned int LEN2 : 2; // 26 - Breakpoint length for breakpoint #2
unsigned int RW3 : 2; // 28 - Breakpoint condition for breakpoint #3
unsigned int LEN3 : 2; // 30 - Breakpoint length for breakpoint #3
#ifdef _WIN64
unsigned int Reserved2 : 32; // 32 - (x86-64 only) Reserved. Read as all-0s. Must be written as all-0s
#endif
}DEBUGCONTROL, *PDEBUGCONTROL;
typedef struct _BPState {
ULONG Index;
BOOL Enable;
CONDITION Condition;
LENGTH Length;
ULONG_PTR Address;
ULONG_PTR ThreadID;
_BPState() : Index(), Enable(), Condition(), Length(), Address(), ThreadID() {}
}BPSTATE, *PBPSTATE;
static std::deque<BPSTATE> gLastBPStates;
static std::deque<BPSTATE> gBPStates;
static std::set<DWORD_PTR> gThreadIds;
bool update_thread() {
HANDLE hSnapshot;
DWORD curPID = GetCurrentProcessId();
gThreadIds.clear();
if (!(hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, curPID)))
return false;
THREADENTRY32 threadEntry{sizeof(THREADENTRY32)};
if (Thread32First(hSnapshot, &threadEntry)) {
do {
if (threadEntry.th32OwnerProcessID != curPID)
continue;
gThreadIds.insert(threadEntry.th32ThreadID);
} while (Thread32Next(hSnapshot, &threadEntry));
}
CloseHandle(hSnapshot);
return true;
}
LONG commit(BOOL suspendThread) {
HANDLE hSnapshot, hThread;
ULONG_PTR curPID, curTID;
THREADENTRY32 threadEntry;
CONTEXT context;
LONG status = 0;
if (gBPStates.empty())
return 1;
curPID = GetCurrentProcessId();
curTID = GetCurrentThreadId();
if (!(hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, curPID)))
return 2;
threadEntry.dwSize = sizeof(THREADENTRY32);
if (!Thread32First(hSnapshot, &threadEntry)) {
CloseHandle(hSnapshot);
return 3;
}
do {
if (threadEntry.th32OwnerProcessID != curPID)
continue;
if (suspendThread && threadEntry.th32ThreadID == curTID)
continue;
if (!(hThread = OpenThread(THREAD_ALL_ACCESS, false, threadEntry.th32ThreadID))) {
status = 4;
break;
}
if (suspendThread && SuspendThread(hThread) == -1) {
CloseHandle(hThread);
status = 5;
break;
}
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
if (!GetThreadContext(hThread, &context)) {
if (suspendThread)
ResumeThread(hThread);
CloseHandle(hThread);
status = 6;
break;
}
auto bpAddr = &context.Dr0;
auto& bpState = context.Dr7;
for (auto& bp : gBPStates) {
bpAddr[bp.Index] = bp.Address;
DEBUG_SET_LOCAL_ENABLE(bp.Index, bp.Enable, bpState);
DEBUG_SET_CONDITION(bp.Index, bp.Condition, bpState);
DEBUG_SET_LENGTH(bp.Index, bp.Length, bpState);
}
if (!SetThreadContext(hThread, &context)) {
if (suspendThread)
ResumeThread(hThread);
CloseHandle(hThread);
status = 7;
break;
}
if (suspendThread)
ResumeThread(hThread);
CloseHandle(hThread);
} while (Thread32Next(hSnapshot, &threadEntry));
CloseHandle(hSnapshot);
gLastBPStates = gBPStates;
gBPStates.clear();
return status;
}
LONG re_commit(BOOL suspendThread) {
if (gLastBPStates.empty())
return 1;
for (auto& bp : gLastBPStates) {
if (bp.Enable)
gBPStates.push_back(bp);
}
return commit(suspendThread);
}
LONG set(ULONG index, ULONG_PTR addr, CONDITION condi, LENGTH length, ULONG threadId, BOOL setCommit, BOOL suspendThread) {
assert(addr);
assert(index < 4);
assert(condi < 4);
assert(length < 4);
auto& bp = gBPStates.emplace_back();
bp.Index = index;
bp.Condition = condi;
bp.Length = length;
bp.Address = addr;
bp.ThreadID = threadId;
bp.Enable = TRUE;
if (setCommit)
return commit(suspendThread);
return 0;
}
LONG unset(ULONG index, ULONG threadId, BOOL setCommit, BOOL suspendThread) {
assert(index < 4);
auto& bp = gBPStates.emplace_back();
bp.Index = index;
bp.Condition = C_EXECUTE;
bp.Length = L_BYTE;
bp.Address = 0;
bp.ThreadID = threadId;
bp.Enable = FALSE;
if (setCommit)
return commit(suspendThread);
return 0;
}
LONG unset_all(BOOL suspendThread) {
for (int i = 0; i < 4; ++i) {
auto& bp = gBPStates.emplace_back();
bp.Index = i;
bp.Condition = C_EXECUTE;
bp.Length = L_BYTE;
bp.Address = 0;
bp.ThreadID = 0;
bp.Enable = FALSE;
}
return commit(suspendThread);
}
}
#pragma once
#include <Windows.h>
namespace hwbp {
enum CONDITION_ {
C_EXECUTE,
C_WRITE,
C_IOREADWRITE,
C_READWRITE
};
enum LENGTH_ {
L_BYTE,
L_WORD,
L_QWORD,
L_DWORD
};
typedef ULONG CONDITION;
typedef ULONG LENGTH;
LONG set(ULONG index, ULONG_PTR addr, CONDITION condi = C_EXECUTE, LENGTH length = L_BYTE, ULONG threadId = 0, BOOL setCommit = FALSE, BOOL suspendThread = FALSE);
LONG unset(ULONG index, ULONG threadId = 0, BOOL setCommit = FALSE, BOOL suspendThread = FALSE);
LONG unset_all(BOOL suspendThread = FALSE);
LONG commit(BOOL suspendThread = FALSE);
LONG re_commit(BOOL suspendThread = FALSE);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment