This commit is contained in:
Duncan Ogilvie 2020-02-08 22:05:42 +01:00
parent 15d7924e88
commit 9f4ddfad9e
No known key found for this signature in database
GPG Key ID: FC89E0AAA0C1AAD8
11 changed files with 261 additions and 5 deletions

View File

@ -1,8 +1,112 @@
#include "_scriptapi_debug.h"
#include "threading.h"
#include "debugger.h"
#include <map>
class DebugContext
{
using CallbackUserdata = std::pair<Script::Debug::CallbackPtr, void*>;
DWORD threadId = 0;
std::map<std::pair<BPXTYPE, duint>, CallbackUserdata> callbacks;
std::vector<CallbackUserdata> waitstack;
std::vector<CallbackUserdata> finalizers;
private:
DebugContext(const DebugContext &) = default;
DebugContext & operator=(const DebugContext &) = default;
public:
DebugContext(DWORD threadId) : threadId(threadId) { }
DebugContext(DebugContext &&) = delete;
~DebugContext()
{
for(const auto & finalizer : finalizers)
finalizer.first(finalizer.second);
}
static void ctxassert(bool condition, const char* message)
{
if(!condition)
MessageBoxA(GuiGetWindowHandle(), message, "Script::Debug Assertion", MB_ICONERROR);
}
void create()
{
ctxassert(threadId == 0, "You cannot call CreateContext with an existing context!");
*this = DebugContext(GetCurrentThreadId());
}
void destroy()
{
check();
ctxassert(threadId != 0, "You cannot call DestroyContext without CreateContext first!");
*this = DebugContext(0);
}
void check() const
{
SHARED_ACQUIRE(LockScriptDebugContext);
auto currentThreadId = GetCurrentThreadId();
ctxassert(currentThreadId != dbggetdebugloopthreadid(), "You cannot call Script::Debug functions in the debug loop!");
ctxassert(threadId == 0 || threadId == currentThreadId, "You cannot mix Script::Debug contexts!");
}
void postwait()
{
EXCLUSIVE_ACQUIRE(LockScriptDebugContext);
if(waitstack.empty())
return;
auto waitCallback = waitstack.back();
waitstack.pop_back();
EXCLUSIVE_RELEASE();
waitCallback.first(waitCallback.second);
}
void setBreakpointCallback(BPXTYPE type, duint address, Script::Debug::CallbackPtr callback, void* userdata)
{
EXCLUSIVE_ACQUIRE(LockScriptDebugContext);
check();
callbacks[ {type, address}] = { callback, userdata };
}
void deleteBreakpointCallback(BPXTYPE type, duint address)
{
callbacks.erase({ type, address });
}
void handleBreakpoint(const BRIDGEBP & bp)
{
EXCLUSIVE_ACQUIRE(LockScriptDebugContext);
auto itr = callbacks.find({ bp.type, bp.addr });
if(itr == callbacks.end())
return;
waitstack.push_back(itr->second);
}
void addFinalizer(Script::Debug::CallbackPtr callback, void* userdata)
{
EXCLUSIVE_ACQUIRE(LockScriptDebugContext);
check();
ctxassert(threadId != 0, "Finalizers cannot be added without a context");
CallbackUserdata finalizerCallback;
finalizers.emplace_back(callback, userdata);
}
};
static DebugContext ctx(0);
static void cbFlatten(void* userdata)
{
((Script::Debug::Callback)userdata)();
}
SCRIPT_EXPORT void Script::Debug::Wait()
{
ctx.check();
_plugin_waituntilpaused();
ctx.postwait();
}
SCRIPT_EXPORT void Script::Debug::Run()
@ -48,13 +152,29 @@ SCRIPT_EXPORT bool Script::Debug::SetBreakpoint(duint address)
return DbgCmdExecDirect(command);
}
SCRIPT_EXPORT bool Script::Debug::SetBreakpoint(duint address, Callback callback)
{
return SetBreakpoint(address, cbFlatten, callback);
}
SCRIPT_EXPORT bool Script::Debug::SetBreakpoint(duint address, CallbackPtr callback, void* userdata)
{
if(!SetBreakpoint(address))
return false;
ctx.setBreakpointCallback(bp_normal, address, callback, userdata);
return true;
}
SCRIPT_EXPORT bool Script::Debug::DeleteBreakpoint(duint address)
{
char command[128] = "";
sprintf_s(command, "bc %p", address);
return DbgCmdExecDirect(command);
if(!DbgCmdExecDirect(command))
return false;
ctx.deleteBreakpointCallback(bp_normal, address);
return true;
}
SCRIPT_EXPORT bool Script::Debug::DisableBreakpoint(duint address)
{
char command[128] = "";
@ -70,9 +190,48 @@ SCRIPT_EXPORT bool Script::Debug::SetHardwareBreakpoint(duint address, HardwareT
return DbgCmdExecDirect(command);
}
namespace Script
{
namespace Debug
{
SCRIPT_EXPORT bool SetHardwareBreakpoint(duint address, HardwareType type) // binary-compatibility
{
return SetHardwareBreakpoint(address, HardwareExecute,)
}
}
}
SCRIPT_EXPORT bool Script::Debug::DeleteHardwareBreakpoint(duint address)
{
char command[128] = "";
sprintf_s(command, "bphwc %p", address);
return DbgCmdExecDirect(command);
}
}
SCRIPT_EXPORT void Script::Debug::SetBreakpointCallback(BPXTYPE type, duint address, CallbackPtr callback, void* userdata)
{
ctx.setBreakpointCallback(type, address, callback, userdata);
}
SCRIPT_EXPORT void Script::Debug::Context::Create()
{
EXCLUSIVE_ACQUIRE(LockScriptDebugContext);
ctx.create();
}
SCRIPT_EXPORT void Script::Debug::Context::Destroy()
{
EXCLUSIVE_ACQUIRE(LockScriptDebugContext);
ctx.destroy();
}
SCRIPT_EXPORT void Script::Debug::Context::AddFinalizer(CallbackPtr callback, void* userdata)
{
ctx.addFinalizer(callback, userdata);
}
void Script::Debug::Internal::BreakpointHandler(const BRIDGEBP & bp)
{
ctx.handleBreakpoint(bp);
}

View File

@ -2,6 +2,7 @@
#define _SCRIPTAPI_DEBUG_H
#include "_scriptapi.h"
#include <functional>
namespace Script
{
@ -14,6 +15,17 @@ namespace Script
HardwareExecute
};
enum HardwareSize
{
HardwareByte,
HardwareWord,
HardwareDword,
HardwareQword
};
using Callback = void (*)();
using CallbackPtr = void (*)(void*);
SCRIPT_EXPORT void Wait();
SCRIPT_EXPORT void Run();
SCRIPT_EXPORT void Pause();
@ -22,10 +34,25 @@ namespace Script
SCRIPT_EXPORT void StepOver();
SCRIPT_EXPORT void StepOut();
SCRIPT_EXPORT bool SetBreakpoint(duint address);
SCRIPT_EXPORT bool SetBreakpoint(duint address, Callback callback);
SCRIPT_EXPORT bool SetBreakpoint(duint address, CallbackPtr callback, void* userdata);
SCRIPT_EXPORT bool DeleteBreakpoint(duint address);
SCRIPT_EXPORT bool DisableBreakpoint(duint address);
SCRIPT_EXPORT bool SetHardwareBreakpoint(duint address, HardwareType type = HardwareExecute);
SCRIPT_EXPORT bool SetHardwareBreakpoint(duint address, HardwareType type = HardwareExecute, HardwareSize size = HardwareByte); // binary-compatibility
SCRIPT_EXPORT bool DeleteHardwareBreakpoint(duint address);
SCRIPT_EXPORT void SetBreakpointCallback(BPXTYPE type, duint address, CallbackPtr callback, void* userdata);
namespace Context
{
SCRIPT_EXPORT void Create();
SCRIPT_EXPORT void Destroy();
SCRIPT_EXPORT void AddFinalizer(CallbackPtr callback, void* userdata);
} // Context
namespace Internal
{
void BreakpointHandler(const BRIDGEBP & bp);
}
}; //Debug
}; //Script

View File

@ -501,4 +501,50 @@ bool cbInstrAnimateWait(int argc, char* argv[])
Sleep(1);
}
return true;
}
#include <_scriptapi_debug.h>
static DWORD WINAPI CoroutineThread(LPVOID)
{
using namespace Script::Debug;
Context::Create();
#define Do(action) do { MessageBoxA(0, "pre-" #action, "Do", MB_ICONINFORMATION); action(); } while(0)
auto adjustMeTopLevel = DbgValFromString("adjustMeTopLevel");
SetBreakpoint(adjustMeTopLevel, []
{
Do(StepOver);
Do(StepOver);
Do(StepOver);
Do(Run);
});
auto adjustMeLayer = DbgValFromString("adjustMeLayer");
SetBreakpoint(adjustMeLayer, []
{
Do(StepOver);
DbgCmdExecDirect("_zf=1");
Do(Run);
});
auto main = DbgValFromString("main");
SetBreakpoint(main, []
{
Do(StepOut);
DbgCmdExecDirect("cax=1");
});
Run();
Context::Destroy();
return 0;
}
bool cbInstrCoroutine(int argc, char* argv[])
{
CloseHandle(CreateThread(nullptr, 0, CoroutineThread, nullptr, 0, nullptr));
return true;
}

View File

@ -13,4 +13,5 @@ bool cbInstrMeminfo(int argc, char* argv[]);
bool cbInstrBriefcheck(int argc, char* argv[]);
bool cbInstrFocusinfo(int argc, char* argv[]);
bool cbInstrFlushlog(int argc, char* argv[]);
bool cbInstrAnimateWait(int argc, char* argv[]);
bool cbInstrAnimateWait(int argc, char* argv[]);
bool cbInstrCoroutine(int argc, char* argv[]);

4
src/dbg/cpp.hint Normal file
View File

@ -0,0 +1,4 @@
// Hint files help the Visual Studio IDE interpret Visual C++ identifiers
// such as names of functions and macros.
// For more information see https://go.microsoft.com/fwlink/?linkid=865984
#define SCRIPT_EXPORT

View File

@ -36,6 +36,7 @@
#include "exprfunc.h"
#include "debugger_cookie.h"
#include "debugger_tracing.h"
#include <_scriptapi_debug.h>
// Debugging variables
static PROCESS_INFORMATION g_pi = {0, 0, 0, 0};
@ -71,6 +72,7 @@ static CookieQuery cookie;
static bool bDatabaseLoaded = false;
static duint exceptionDispatchAddr = 0;
static bool bPausedOnException = false;
static DWORD dwDebugLoopThreadId = 0;
char szProgramDir[MAX_PATH] = "";
char szFileName[MAX_PATH] = "";
char szSymbolCachePath[MAX_PATH] = "";
@ -417,6 +419,11 @@ bool dbghandledllbreakpoint(const char* mod, bool loadDll)
return shouldBreak;
}
DWORD dbggetdebugloopthreadid()
{
return dwDebugLoopThreadId;
}
static DWORD WINAPI updateCallStackThread(duint ptr)
{
stackupdatecallstack(ptr);
@ -892,6 +899,7 @@ static void cbGenericBreakpoint(BP_TYPE bptype, void* ExceptionAddress = nullptr
bpInfo.breakpoint = &bridgebp;
BpToBridge(&bp, &bridgebp);
plugincbcall(CB_BREAKPOINT, &bpInfo);
Script::Debug::Internal::BreakpointHandler(bridgebp);
// Trace record
_dbg_dbgtraceexecute(CIP);
@ -2558,6 +2566,7 @@ static void debugLoopFunction(void* lpParameter, bool attach)
dbgsetskipexceptions(false);
bFreezeStack = false;
bDatabaseLoaded = false;
dwDebugLoopThreadId = GetCurrentThreadId();
//prepare attach/createprocess
DWORD pid;
@ -2766,6 +2775,7 @@ static void debugLoopFunction(void* lpParameter, bool attach)
DeleteFileW(gDllLoader.c_str());
gDllLoader.clear();
}
dwDebugLoopThreadId = 0;
}
void dbgsetdebuggeeinitscript(const char* fileName)

View File

@ -81,6 +81,7 @@ bool dbggetwintext(std::vector<std::string>* winTextList, const DWORD dwProcessI
void dbgtracebrowserneedsupdate();
bool dbgsetdllbreakpoint(const char* mod, DWORD type, bool singleshoot);
bool dbgdeletedllbreakpoint(const char* mod, DWORD type);
DWORD dbggetdebugloopthreadid();
void cbStep();
void cbRtrStep();

View File

@ -73,6 +73,7 @@ enum SectionLock
LockModuleHashes,
LockFormatFunctions,
LockDllBreakpoints,
LockScriptDebugContext,
// Number of elements in this enumeration. Must always be the last index.
LockLast

View File

@ -442,6 +442,7 @@ static void registercommands()
dbgcmdnew("printstack,logstack", cbInstrPrintStack, true); //print the call stack
dbgcmdnew("flushlog", cbInstrFlushlog, false); //flush the log
dbgcmdnew("AnimateWait", cbInstrAnimateWait, true); //Wait for the debuggee to pause.
dbgcmdnew("coroutine", cbInstrCoroutine, true);
};
bool cbCommandProvider(char* cmd, int maxlen)

View File

@ -293,6 +293,9 @@
<UseLibraryDependencyInputs>false</UseLibraryDependencyInputs>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{E6548308-401E-3A8A-5819-905DB90522A6}</ProjectGuid>
<Keyword>Win32Proj</Keyword>

View File

@ -896,4 +896,7 @@
<Filter>Header Files\Symbols</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />
</ItemGroup>
</Project>