From 552aa926375391d607912c7efe9579749b25902f Mon Sep 17 00:00:00 2001 From: AzuLX Date: Mon, 5 Jan 2026 15:18:30 +0000 Subject: [PATCH 1/3] fix software breakpoint handling issues --- GleeBug/Debugger.Loop.Exception.cpp | 37 +++++++++++++++++--- GleeBug/Debugger.Process.Memory.cpp | 54 +++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/GleeBug/Debugger.Loop.Exception.cpp b/GleeBug/Debugger.Loop.Exception.cpp index eba6cbe..411455c 100644 --- a/GleeBug/Debugger.Loop.Exception.cpp +++ b/GleeBug/Debugger.Loop.Exception.cpp @@ -6,7 +6,8 @@ namespace GleeBug void Debugger::exceptionBreakpoint(const EXCEPTION_RECORD & exceptionRecord, const bool firstChance) { //check if the breakpoint exists - auto foundInfo = mProcess->breakpoints.find({ BreakpointType::Software, ptr(exceptionRecord.ExceptionAddress) }); + auto exceptionAddress = ptr(exceptionRecord.ExceptionAddress); + auto foundInfo = mProcess->breakpoints.find({ BreakpointType::Software, exceptionAddress }); if(foundInfo == mProcess->breakpoints.end()) { if(!this->mAttachedToProcess && !mProcess->systemBreakpoint) //handle system breakpoint @@ -18,6 +19,20 @@ namespace GleeBug //call the callback cbSystemBreakpoint(); } + else + { + //check if this was a deleted breakpoint + //if the byte at the exception address is not 0xCC, our breakpoint was deleted + //and we should set IP back and continue execution + uint8 currentByte = 0xCC; + if(mThread && mProcess->MemReadUnsafe(exceptionAddress, ¤tByte, 1) && currentByte != 0xCC) + { + //this was our deleted breakpoint, set IP back and continue + Registers(mThread->hThread, CONTEXT_CONTROL).Gip = exceptionAddress; + mContinueStatus = DBG_CONTINUE; + } + //else: byte is 0xCC, this is a real int3 in original code, let debuggee handle it + } return; } @@ -30,12 +45,26 @@ namespace GleeBug Registers(mThread->hThread, CONTEXT_CONTROL).Gip = info.address; //restore the original breakpoint byte and do an internal step - mProcess->MemWriteUnsafe(info.address, info.internal.software.oldbytes, info.internal.software.size); + if(!mProcess->MemWriteUnsafe(info.address, info.internal.software.oldbytes, info.internal.software.size)) + { + //failed to restore original byte, pass exception to debuggee + mContinueStatus = DBG_EXCEPTION_NOT_HANDLED; + return; + } mProcess->StepInternal([this, info]() { //only restore the bytes if the breakpoint still exists - if(mProcess->breakpoints.find({ BreakpointType::Software, info.address }) != mProcess->breakpoints.end()) - mProcess->MemWriteUnsafe(info.address, info.internal.software.newbytes, info.internal.software.size); + auto foundBreakpoint = mProcess->breakpoints.find({ BreakpointType::Software, info.address }); + if(foundBreakpoint != mProcess->breakpoints.end()) + { + if(!mProcess->MemWriteUnsafe(info.address, info.internal.software.newbytes, info.internal.software.size)) + { + //failed to restore breakpoint byte, remove from maps to stay consistent + mProcess->softwareBreakpointReferences.erase(info.address); + mProcess->breakpoints.erase(foundBreakpoint); + mProcess->breakpointCallbacks.erase({ BreakpointType::Software, info.address }); + } + } }); //call the generic callback diff --git a/GleeBug/Debugger.Process.Memory.cpp b/GleeBug/Debugger.Process.Memory.cpp index 0df18dd..6fe01f5 100644 --- a/GleeBug/Debugger.Process.Memory.cpp +++ b/GleeBug/Debugger.Process.Memory.cpp @@ -65,8 +65,58 @@ namespace GleeBug bool Process::MemWriteSafe(ptr address, const void* buffer, ptr size, ptr* bytesWritten) { - //TODO: correctly implement this - return MemWrite(address, buffer, size, bytesWritten, false); + if(size == 0) + { + if(bytesWritten) + *bytesWritten = 0; + return true; + } + + std::vector copy((const uint8*)buffer, (const uint8*)buffer + size); + + auto start = address; + auto end = start + size; + + //find overlapping software breakpoints and preserve their 0xCC bytes in the copy (so write doesn't remove breakpoints) + //as well as track what oldbytes values need updating after successful write + std::vector> pendingUpdates; //tuple: (pointer to oldbyte, new value, offset from start for partial write handling) + + for(auto & breakpoint : breakpoints) + { + if(breakpoint.first.first != BreakpointType::Software) + continue; + auto & info = breakpoint.second; + auto curAddress = info.address; + for(ptr j = 0; j < info.internal.software.size; j++) + { + if(curAddress + j >= start && curAddress + j < end) + { + auto offset = curAddress + j - start; + pendingUpdates.emplace_back(&info.internal.software.oldbytes[j], copy[offset], offset); + copy[offset] = info.internal.software.newbytes[j]; + } + } + } + + //write to memory (breakpoint bytes are preserved in the copy) + ptr written = 0; + if(!MemWriteUnsafe(address, copy.data(), size, &written)) + { + if(bytesWritten) + *bytesWritten = written; + return false; + } + + //apply oldbytes updates only for bytes that were actually written + for(const auto & update : pendingUpdates) + { + if(std::get<2>(update) < written) + *std::get<0>(update) = std::get<1>(update); + } + + if(bytesWritten) + *bytesWritten = written; + return true; } bool Process::MemIsValidPtr(ptr address) const From 1533cc3e84a4244ef06929a798be5dff15c4a227 Mon Sep 17 00:00:00 2001 From: AzuLX Date: Fri, 9 Jan 2026 19:08:26 +0000 Subject: [PATCH 2/3] track deleted breakpoints to handle stale events safely --- GleeBug/Debugger.Loop.Exception.cpp | 11 ++++------- GleeBug/Debugger.Loop.cpp | 11 ++++++++++- GleeBug/Debugger.Process.Breakpoint.cpp | 2 ++ GleeBug/Debugger.Process.h | 2 ++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/GleeBug/Debugger.Loop.Exception.cpp b/GleeBug/Debugger.Loop.Exception.cpp index 411455c..00fe270 100644 --- a/GleeBug/Debugger.Loop.Exception.cpp +++ b/GleeBug/Debugger.Loop.Exception.cpp @@ -21,17 +21,14 @@ namespace GleeBug } else { - //check if this was a deleted breakpoint - //if the byte at the exception address is not 0xCC, our breakpoint was deleted - //and we should set IP back and continue execution - uint8 currentByte = 0xCC; - if(mThread && mProcess->MemReadUnsafe(exceptionAddress, ¤tByte, 1) && currentByte != 0xCC) + //check if this address had a breakpoint that was recently deleted + auto& deletedBps = mProcess->recentlyDeletedSwbp; + auto foundIt = std::find(deletedBps.begin(), deletedBps.end(), exceptionAddress); + if(foundIt != deletedBps.end() && mThread) { - //this was our deleted breakpoint, set IP back and continue Registers(mThread->hThread, CONTEXT_CONTROL).Gip = exceptionAddress; mContinueStatus = DBG_CONTINUE; } - //else: byte is 0xCC, this is a real int3 in original code, let debuggee handle it } return; } diff --git a/GleeBug/Debugger.Loop.cpp b/GleeBug/Debugger.Loop.cpp index 6f54e69..954da2e 100644 --- a/GleeBug/Debugger.Loop.cpp +++ b/GleeBug/Debugger.Loop.cpp @@ -40,6 +40,8 @@ namespace GleeBug IsDbgReplyLaterSupported = mSafeStep; } + uint32 consecutiveTimeouts = 0; + while(!mBreakDebugger) { //wait for a debug event @@ -65,11 +67,18 @@ namespace GleeBug #endif else { - // Regular timeout, wait again + //after 2 consecutive timeouts, clear recently deleted breakpoints + //any stale events would have been delivered by now + consecutiveTimeouts++; + if(consecutiveTimeouts >= 2 && mProcess) + mProcess->recentlyDeletedSwbp.clear(); continue; } } + //event received, reset timeout counter + consecutiveTimeouts = 0; + // Handle safe stepping if(IsDbgReplyLaterSupported) { diff --git a/GleeBug/Debugger.Process.Breakpoint.cpp b/GleeBug/Debugger.Process.Breakpoint.cpp index 65e046b..e67d35a 100644 --- a/GleeBug/Debugger.Process.Breakpoint.cpp +++ b/GleeBug/Debugger.Process.Breakpoint.cpp @@ -68,6 +68,8 @@ namespace GleeBug return false; FlushInstructionCache(hProcess, nullptr, 0); + recentlyDeletedSwbp.push_back(address); + //remove the breakpoint from the maps softwareBreakpointReferences.erase(info.address); breakpoints.erase(found); diff --git a/GleeBug/Debugger.Process.h b/GleeBug/Debugger.Process.h index fd604f1..3459fcf 100644 --- a/GleeBug/Debugger.Process.h +++ b/GleeBug/Debugger.Process.h @@ -32,6 +32,8 @@ namespace GleeBug MemoryBreakpointSet memoryBreakpointRanges; MemoryBreakpointMap memoryBreakpointPages; + std::vector recentlyDeletedSwbp; + /** \brief Constructor. \param hProcess Process handle. From 604ce8967382beae24cb839b82ea5881c8fc29e1 Mon Sep 17 00:00:00 2001 From: AzuLX Date: Fri, 9 Jan 2026 19:22:00 +0000 Subject: [PATCH 3/3] use unordered_map instead of vector --- GleeBug/Debugger.Loop.Exception.cpp | 2 +- GleeBug/Debugger.Process.Breakpoint.cpp | 2 +- GleeBug/Debugger.Process.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GleeBug/Debugger.Loop.Exception.cpp b/GleeBug/Debugger.Loop.Exception.cpp index 00fe270..992918f 100644 --- a/GleeBug/Debugger.Loop.Exception.cpp +++ b/GleeBug/Debugger.Loop.Exception.cpp @@ -23,7 +23,7 @@ namespace GleeBug { //check if this address had a breakpoint that was recently deleted auto& deletedBps = mProcess->recentlyDeletedSwbp; - auto foundIt = std::find(deletedBps.begin(), deletedBps.end(), exceptionAddress); + auto foundIt = deletedBps.find(exceptionAddress); if(foundIt != deletedBps.end() && mThread) { Registers(mThread->hThread, CONTEXT_CONTROL).Gip = exceptionAddress; diff --git a/GleeBug/Debugger.Process.Breakpoint.cpp b/GleeBug/Debugger.Process.Breakpoint.cpp index e67d35a..bcad659 100644 --- a/GleeBug/Debugger.Process.Breakpoint.cpp +++ b/GleeBug/Debugger.Process.Breakpoint.cpp @@ -68,7 +68,7 @@ namespace GleeBug return false; FlushInstructionCache(hProcess, nullptr, 0); - recentlyDeletedSwbp.push_back(address); + recentlyDeletedSwbp.insert(address); //remove the breakpoint from the maps softwareBreakpointReferences.erase(info.address); diff --git a/GleeBug/Debugger.Process.h b/GleeBug/Debugger.Process.h index 3459fcf..f17d6c1 100644 --- a/GleeBug/Debugger.Process.h +++ b/GleeBug/Debugger.Process.h @@ -32,7 +32,7 @@ namespace GleeBug MemoryBreakpointSet memoryBreakpointRanges; MemoryBreakpointMap memoryBreakpointPages; - std::vector recentlyDeletedSwbp; + std::unordered_set recentlyDeletedSwbp; /** \brief Constructor.