Implement safe stepping with DBG_REPLY_LATER

This commit is contained in:
Duncan Ogilvie 2022-09-09 22:56:39 +02:00
parent 660619edf3
commit 39934ea3ae
4 changed files with 91 additions and 0 deletions

View File

@ -1,6 +1,10 @@
#include "Debugger.h" #include "Debugger.h"
#include "Debugger.Thread.Registers.h" #include "Debugger.Thread.Registers.h"
#ifndef DBG_REPLY_LATER
#define DBG_REPLY_LATER ((NTSTATUS)0x40010001L)
#endif // DBG_REPLY_LATER
namespace GleeBug namespace GleeBug
{ {
void Debugger::Start() void Debugger::Start()
@ -24,6 +28,18 @@ namespace GleeBug
return; return;
} }
DWORD ThreadBeingProcessed = 0;
std::unordered_map<DWORD, HANDLE> SuspendedThreads;
bool IsDbgReplyLaterSupported = false;
// Check if DBG_REPLY_LATER is supported based on Windows version (Windows 10, version 1507 or above)
// https://www.gaijin.at/en/infos/windows-version-numbers
const uint32_t NtBuildNumber = *(uint32_t*)(0x7FFE0000 + 0x260);
if (NtBuildNumber != 0 && NtBuildNumber >= 10240)
{
IsDbgReplyLaterSupported = mSafeStep;
}
while(!mBreakDebugger) while(!mBreakDebugger)
{ {
//wait for a debug event //wait for a debug event
@ -53,6 +69,38 @@ namespace GleeBug
continue; continue;
} }
} }
// Handle safe stepping
if (IsDbgReplyLaterSupported)
{
if (mDebugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
{
// Check if there is a thread processing a single step
if (ThreadBeingProcessed != 0 && mDebugEvent.dwThreadId != ThreadBeingProcessed)
{
// Reply to the event later
if (!ContinueDebugEvent(mDebugEvent.dwProcessId, mDebugEvent.dwThreadId, DBG_REPLY_LATER))
break;
// Wait for the next event
continue;
}
}
else if (mDebugEvent.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT)
{
if (ThreadBeingProcessed != 0 && mDebugEvent.dwThreadId == ThreadBeingProcessed)
{
// Resume the other threads since the thread being processed is exiting
for (auto& itr : SuspendedThreads)
ResumeThread(itr.second);
SuspendedThreads.clear();
ThreadBeingProcessed = 0;
}
}
}
// Signal we are currently paused
mIsRunning = false; mIsRunning = false;
//set default continue status //set default continue status
@ -108,6 +156,15 @@ namespace GleeBug
unloadDllEvent(mDebugEvent.u.UnloadDll); unloadDllEvent(mDebugEvent.u.UnloadDll);
break; break;
case EXCEPTION_DEBUG_EVENT: case EXCEPTION_DEBUG_EVENT:
if (IsDbgReplyLaterSupported && mDebugEvent.u.Exception.ExceptionRecord.ExceptionCode == STATUS_SINGLE_STEP)
{
// Resume the other threads since we are done processing the single step
for (auto& itr : SuspendedThreads)
ResumeThread(itr.second);
SuspendedThreads.clear();
ThreadBeingProcessed = 0;
}
exceptionEvent(mDebugEvent.u.Exception); exceptionEvent(mDebugEvent.u.Exception);
break; break;
case OUTPUT_DEBUG_STRING_EVENT: case OUTPUT_DEBUG_STRING_EVENT:
@ -139,6 +196,33 @@ namespace GleeBug
Registers(mThread->hThread, CONTEXT_CONTROL).TrapFlag = false; Registers(mThread->hThread, CONTEXT_CONTROL).TrapFlag = false;
} }
// Handle safe stepping
if (IsDbgReplyLaterSupported && mDebugEvent.dwDebugEventCode != EXIT_THREAD_DEBUG_EVENT)
{
// If TF is set (single step), then suspend all the other threads
if (mThread && mThread->isInternalStepping)
{
ThreadBeingProcessed = mDebugEvent.dwThreadId;
for (auto& Thread : mProcess->threads)
{
auto dwThreadId = Thread.first;
auto hThread = Thread.second->hThread;
// Do not suspend the current thread
if (ThreadBeingProcessed == dwThreadId)
continue;
// Check if the thread is already suspended
if (SuspendedThreads.count(dwThreadId) != 0)
continue;
if (SuspendThread(hThread) != -1)
SuspendedThreads.emplace(dwThreadId, hThread);
}
}
}
//continue the debug event //continue the debug event
if(!ContinueDebugEvent(mDebugEvent.dwProcessId, mDebugEvent.dwThreadId, mContinueStatus)) if(!ContinueDebugEvent(mDebugEvent.dwProcessId, mDebugEvent.dwThreadId, mContinueStatus))
break; break;

View File

@ -292,6 +292,7 @@ namespace GleeBug
bool mDetach = false; bool mDetach = false;
bool mDetachAndBreak = false; bool mDetachAndBreak = false;
bool mAttachedToProcess = false; bool mAttachedToProcess = false;
bool mSafeStep = true;
/** /**
\brief The current process (can be null in some cases). \brief The current process (can be null in some cases).

View File

@ -191,6 +191,9 @@ public:
case UE_ENGINE_SAFE_ATTACH: case UE_ENGINE_SAFE_ATTACH:
mSafeAttach = VariableSet; mSafeAttach = VariableSet;
break; break;
case UE_ENGINE_SAFE_STEP:
mSafeStep = VariableSet;
break;
} }
} }

View File

@ -57,6 +57,9 @@
#define UE_ENGINE_CALL_PLUGIN_DEBUG_CALLBACK 8 #define UE_ENGINE_CALL_PLUGIN_DEBUG_CALLBACK 8
#define UE_ENGINE_SET_DEBUG_PRIVILEGE 9 #define UE_ENGINE_SET_DEBUG_PRIVILEGE 9
#define UE_ENGINE_SAFE_ATTACH 10 #define UE_ENGINE_SAFE_ATTACH 10
#define UE_ENGINE_MEMBP_ALT 11
#define UE_ENGINE_DISABLE_ASLR 12
#define UE_ENGINE_SAFE_STEP 13
#define UE_OPTION_REMOVEALL 1 #define UE_OPTION_REMOVEALL 1
#define UE_OPTION_DISABLEALL 2 #define UE_OPTION_DISABLEALL 2