1
0
Fork 0

fix: restored original bpm behaviour and added tests

- Fixed 'range-delete' test.
- Added 'range-heap' test to check that memory bps in the heap work.
- Disabled 'range-write' test.
- Reintegrated original 'bpm' behaviour.
- 'findMemoryBreakpoint' is more compact and maintainable.
This commit is contained in:
Rafael Ribassin Gonçalves Ferreira de Freitas 2026-03-23 16:57:08 +00:00
parent 22882bff77
commit de26be0682
13 changed files with 507 additions and 480 deletions

View File

@ -56,15 +56,7 @@ static void setBpActive(BREAKPOINT & bp, duint addrAdjust = 0)
static BREAKPOINT* findMemoryBreakpoint(duint Address)
{
duint targetKeyAddr;
auto currentMod = ModInfoFromAddr(Address);
if(currentMod)
targetKeyAddr = currentMod->hash + (Address - currentMod->base);
else
targetKeyAddr = Address; // Breakpoints that are put outside modules (heap, stack, etc), use the actual address and not RVA.
auto it = breakpoints.upper_bound(BreakpointKey(BPMEMORY, targetKeyAddr));
auto it = breakpoints.upper_bound(BreakpointKey(BPMEMORY, ModHashFromAddr(Address)));
if(it == breakpoints.begin())
return nullptr;
@ -75,7 +67,7 @@ static BREAKPOINT* findMemoryBreakpoint(duint Address)
{
auto & bp = it->second;
duint bpStart = currentMod ? (currentMod->base + bp.addr) : bp.addr;
duint bpStart = ModBaseFromAddr(Address) + bp.addr; // Breakpoints that are put outside modules (heap, stack, etc), use the actual address and not RVA
duint bpEnd = bpStart + bp.memsize;
if(Address >= bpStart && Address < bpEnd)

View File

@ -642,7 +642,7 @@ static bool cbDeleteAllMemoryBreakpoints(const BREAKPOINT* bp)
dprintf(QT_TRANSLATE_NOOP("DBG", "Delete memory breakpoint failed (BpDelete): %p\n"), bp->addr);
return false;
}
if(bp->enabled && bp->active && !RemoveMemoryBPX(bp->addr, bp->memsize))
if(bp->enabled && bp->active && !RemoveMemoryBPX(bp->addr, size))
{
dprintf(QT_TRANSLATE_NOOP("DBG", "Delete memory breakpoint failed (RemoveMemoryBPX): %p\n"), bp->addr);
return false;
@ -662,7 +662,7 @@ static bool cbEnableAllMemoryBreakpoints(const BREAKPOINT* bp)
dprintf(QT_TRANSLATE_NOOP("DBG", "Could not enable memory breakpoint %p (BpEnable)\n"), bp->addr);
return false;
}
if(!SetMemoryBPXEx(bp->addr, bp->memsize, (TitanMemoryBreakpointType)bp->titantype, !bp->singleshoot, cbMemoryBreakpoint))
if(!SetMemoryBPXEx(bp->addr, size, (TitanMemoryBreakpointType)bp->titantype, !bp->singleshoot, cbMemoryBreakpoint))
{
dprintf(QT_TRANSLATE_NOOP("DBG", "Could not enable memory breakpoint %p (SetMemoryBPXEx)\n"), bp->addr);
return false;
@ -732,32 +732,28 @@ bool cbDebugSetMemoryBpx(int argc, char* argv[])
duint size = 0;
duint base = MemFindBaseAddr(addr, &size, true);
duint page = addr & ~(PAGE_SIZE - 1); // aligns given address down to page start
size -= (page - base);
bool singleshoot = false;
if(!restore)
singleshoot = true;
BREAKPOINT bp;
if(BpGet(page, BPMEMORY, 0, &bp))
if(BpGet(base, BPMEMORY, 0, &bp))
{
if(!bp.enabled)
return DbgCmdExecDirect(StringUtils::sprintf("bpme %p", bp.addr).c_str());
dputs(QT_TRANSLATE_NOOP("DBG", "Memory breakpoint already set!"));
return true;
}
if(!BpNew(page, true, singleshoot, 0, BPMEMORY, type, 0, size))
if(!BpNew(base, true, singleshoot, 0, BPMEMORY, type, 0, size))
{
dputs(QT_TRANSLATE_NOOP("DBG", "Error setting memory breakpoint! (BpNew)"));
return false;
}
if(!SetMemoryBPXEx(page, size, type, restore, cbMemoryBreakpoint))
if(!SetMemoryBPXEx(base, size, type, restore, cbMemoryBreakpoint))
{
dputs(QT_TRANSLATE_NOOP("DBG", "Error setting memory breakpoint! (SetMemoryBPXEx)"));
return false;
}
dprintf(QT_TRANSLATE_NOOP("DBG", "Memory breakpoint at %p[%p] set!\n"), page, size);
dprintf(QT_TRANSLATE_NOOP("DBG", "Memory breakpoint at %p[%p] set!\n"), base, size);
GuiUpdateAllViews();
return true;
}

View File

@ -724,8 +724,8 @@ static bool scriptRun(int destline, bool gui)
scriptResetInterruptState();
// the script fully executed (which means scriptIp is reset to the first line), without any errors
return !scriptIsAbortReason(interruptReason)
&& scriptIp == scriptNextIp(0)
&& (scriptLastError == STATUS_EXIT || scriptLastError == STATUS_CONTINUE || scriptLastError == STATUS_CONTINUE_BRANCH);
&& scriptIp == scriptNextIp(0)
&& (scriptLastError == STATUS_EXIT || scriptLastError == STATUS_CONTINUE || scriptLastError == STATUS_CONTINUE_BRANCH);
}
static bool scriptLoad(const char* filename, bool gui)

View File

@ -32,10 +32,12 @@ The directory now uses multiple scripts handled by `src/tests/run.py`:
- `test.txt` -> `membp`
- `test.write.txt` -> `membp/write`
- `test.range-read.txt` -> `membp/range-read`
- `test.range-write.txt` -> `membp/range-write`
- [DISABLED] `test.range-write.txt` -> `membp/range-write`
- `test.range-execute.txt` -> `membp/range-execute`
- `test.range-delete.txt` -> `membp/range-delete`
- `test.range-reenable.txt` -> `membp/range-reenable`
- `test.range-reinit.txt` -> `membp/range-reinit`
- `test.exitprocess-assert.txt` -> `membp/exitprocess-assert`
- `test.range-heap.txt` -> `membp/range-heap`
The plugin in `plugin.cpp` provides shared assertions for installation, hits, deletion, re-enable, and reinit scenarios.

View File

@ -1,266 +1,269 @@
#include <Windows.h>
#include <atomic>
#include <cstring>
#include <string>
#include "_plugins.h"
#include "bridgemain.h"
namespace
{
int gPluginHandle = 0;
std::atomic<unsigned int> gMemoryHitCount{ 0 };
std::atomic<duint> gLastBpAddr{ 0 };
std::atomic<duint> gLastCip{ 0 };
std::atomic<bool> gExitObserved{ false };
std::atomic<unsigned long> gExitCode{ 0 };
std::atomic<duint> gCachedBpAddr{ 0 };
std::atomic<bool> gExpectExitHit{ false };
std::atomic<duint> gExpectedExitBpAddr{ 0 };
void resetState()
{
gMemoryHitCount = 0;
gLastBpAddr = 0;
gLastCip = 0;
gExitObserved = false;
gExitCode = 0;
gCachedBpAddr = 0;
gExpectExitHit = false;
gExpectedExitBpAddr = 0;
}
duint evalExpr(const char* expr)
{
return expr ? DbgValFromString(expr) : 0;
}
duint evalWrapped(const char* prefix, const char* expr, const char* suffix)
{
std::string wrapped = std::string(prefix) + expr + suffix;
return DbgValFromString(wrapped.c_str());
}
bool findBpByStart(duint expectedStart, duint & size)
{
BPMAP list{};
if(DbgGetBpList(bp_memory, &list) == 0)
return false;
bool found = false;
for(int i = 0; i < list.count; i++)
{
const auto & bp = list.bp[i];
if(bp.type != bp_memory)
continue;
if(bp.addr != expectedStart)
continue;
size = DbgFunctions()->MemBpSize(bp.addr);
found = true;
break;
}
BridgeFree(list.bp);
return found;
}
bool assertExpectedHit(duint expectedBp, bool requireProcessAlive);
void cbPlugin(CBTYPE cbType, void* callbackInfo)
{
if(cbType == CB_INITDEBUG)
{
resetState();
return;
}
if(cbType == CB_BREAKPOINT)
{
const auto info = static_cast<PLUG_CB_BREAKPOINT*>(callbackInfo);
if(info && info->breakpoint && info->breakpoint->type == bp_memory)
{
gMemoryHitCount.fetch_add(1);
gLastBpAddr = info->breakpoint->addr;
gLastCip = DbgValFromString("cip");
}
return;
}
if(cbType == CB_EXITPROCESS)
{
const auto info = static_cast<PLUG_CB_EXITPROCESS*>(callbackInfo);
if(info && info->ExitProcess)
{
gExitObserved = true;
gExitCode = info->ExitProcess->dwExitCode;
if(gExpectExitHit.load())
assertExpectedHit(gExpectedExitBpAddr.load(), false);
}
}
}
bool cbReset(int, char**)
{
resetState();
return _plugin_testassert(true, "state reset");
}
bool cbAssertRegion(int argc, char** argv)
{
if(argc < 2)
return false;
const duint target = evalExpr(argv[1]);
if(!_plugin_testassert(target != 0, "failed to resolve target expression '%s'", argv[1]))
return false;
const duint expectedStart = evalWrapped("mem.base(", argv[1], ")");
const duint expectedSize = evalWrapped("mem.size(", argv[1], ")");
if(!_plugin_testassert(expectedStart != 0, "failed to resolve mem.base(%s)", argv[1]))
return false;
if(!_plugin_testassert(expectedSize != 0, "failed to resolve mem.size(%s)", argv[1]))
return false;
duint actualSize = 0;
if(!_plugin_testassert(findBpByStart(expectedStart, actualSize), "failed to find memory breakpoint starting at 0x%llX", static_cast<unsigned long long>(expectedStart)))
return false;
return _plugin_testassert(
actualSize == expectedSize,
"expected region breakpoint [%0llX, 0x%llX), got [0x%llX, 0x%llX) for target %s",
static_cast<unsigned long long>(expectedStart),
static_cast<unsigned long long>(expectedStart + expectedSize),
static_cast<unsigned long long>(expectedStart),
static_cast<unsigned long long>(expectedStart + actualSize),
argv[1]
);
}
bool cbAssertExact(int argc, char** argv)
{
if(argc < 3)
return false;
const duint expectedStart = evalExpr(argv[1]);
const duint expectedSize = evalExpr(argv[2]);
if(!_plugin_testassert(expectedStart != 0, "failed to resolve target expression '%s'", argv[1]))
return false;
if(!_plugin_testassert(expectedSize != 0, "failed to resolve size expression '%s'", argv[2]))
return false;
duint actualSize = 0;
if(!_plugin_testassert(findBpByStart(expectedStart, actualSize), "failed to find memory breakpoint starting at 0x%llX", static_cast<unsigned long long>(expectedStart)))
return false;
return _plugin_testassert(
actualSize == expectedSize,
"expected exact breakpoint [%0llX, 0x%llX), got [0x%llX, 0x%llX)",
static_cast<unsigned long long>(expectedStart),
static_cast<unsigned long long>(expectedStart + expectedSize),
static_cast<unsigned long long>(expectedStart),
static_cast<unsigned long long>(expectedStart + actualSize)
);
}
bool cbCacheBp(int argc, char** argv)
{
if(argc < 2)
return false;
const duint expectedBp = evalExpr(argv[1]);
if(!_plugin_testassert(expectedBp != 0, "failed to resolve breakpoint expression '%s'", argv[1]))
return false;
gCachedBpAddr = expectedBp;
return _plugin_testassert(true, "cached breakpoint address 0x%llX", static_cast<unsigned long long>(expectedBp));
}
bool assertExpectedHit(duint expectedBp, bool requireProcessAlive)
{
if(!_plugin_testassert(expectedBp != 0, "failed to resolve expected breakpoint address"))
return false;
if(!_plugin_testassert(gMemoryHitCount.load() == 1, "expected exactly 1 memory breakpoint callback, got %u", gMemoryHitCount.load()))
return false;
if(!_plugin_testassert(gLastBpAddr.load() == expectedBp, "expected memory breakpoint address 0x%llX, got 0x%llX", static_cast<unsigned long long>(expectedBp), static_cast<unsigned long long>(gLastBpAddr.load())))
return false;
if(!requireProcessAlive)
return true;
return _plugin_testassert(!gExitObserved.load(), "process exited before the memory breakpoint assertion, exitCode=%lu, lastCip=0x%llX", gExitCode.load(), static_cast<unsigned long long>(gLastCip.load()));
}
bool cbAssertHit(int argc, char** argv)
{
duint expectedBp = 0;
if(argc >= 2)
expectedBp = evalExpr(argv[1]);
if(expectedBp == 0)
expectedBp = gCachedBpAddr.load();
return assertExpectedHit(expectedBp, true);
}
bool cbAssertNoHit(int argc, char** argv)
{
if(argc < 2)
return false;
const duint expectedExitCode = evalExpr(argv[1]);
if(!_plugin_testassert(gMemoryHitCount.load() == 0, "expected no memory breakpoint callbacks, got %u", gMemoryHitCount.load()))
return false;
if(!_plugin_testassert(gExitObserved.load(), "expected process exit to be observed"))
return false;
return _plugin_testassert(gExitCode.load() == expectedExitCode, "expected exit code 0x%llX, got 0x%lX", static_cast<unsigned long long>(expectedExitCode), gExitCode.load());
}
bool cbExpectExitHit(int argc, char** argv)
{
duint expectedBp = 0;
if(argc >= 2)
expectedBp = evalExpr(argv[1]);
if(expectedBp == 0)
expectedBp = gCachedBpAddr.load();
if(!_plugin_testassert(expectedBp != 0, "failed to resolve expected exit breakpoint address"))
return false;
gExpectedExitBpAddr = expectedBp;
gExpectExitHit = true;
return _plugin_testassert(true, "expecting exit-time memory breakpoint assertion for 0x%llX", static_cast<unsigned long long>(expectedBp));
}
bool cbReenable(int argc, char** argv)
{
if(argc < 2)
return false;
const auto disableCommand = std::string("membpd ") + argv[1];
const auto enableCommand = std::string("membpe ") + argv[1];
if(!_plugin_testassert(DbgCmdExecDirect(disableCommand.c_str()), "membpd failed for %s", argv[1]))
return false;
return _plugin_testassert(DbgCmdExecDirect(enableCommand.c_str()), "membpe failed for %s", argv[1]);
}
}
extern "C" __declspec(dllexport) bool pluginit(PLUG_INITSTRUCT* initStruct)
{
initStruct->pluginVersion = 1;
initStruct->sdkVersion = PLUG_SDKVERSION;
strncpy_s(initStruct->pluginName, sizeof(initStruct->pluginName), "MembpRegression", _TRUNCATE);
gPluginHandle = initStruct->pluginHandle;
_plugin_registercallback(gPluginHandle, CB_INITDEBUG, cbPlugin);
_plugin_registercallback(gPluginHandle, CB_BREAKPOINT, cbPlugin);
_plugin_registercallback(gPluginHandle, CB_EXITPROCESS, cbPlugin);
_plugin_registercommand(gPluginHandle, "mbreset", cbReset, false);
_plugin_registercommand(gPluginHandle, "mbassertregion", cbAssertRegion, false);
_plugin_registercommand(gPluginHandle, "mbassertexact", cbAssertExact, false);
_plugin_registercommand(gPluginHandle, "mbcachebp", cbCacheBp, false);
_plugin_registercommand(gPluginHandle, "mbasserthit", cbAssertHit, false);
_plugin_registercommand(gPluginHandle, "mbassertnohit", cbAssertNoHit, false);
_plugin_registercommand(gPluginHandle, "mbexpectexithit", cbExpectExitHit, false);
_plugin_registercommand(gPluginHandle, "mbreenable", cbReenable, false);
return true;
}
extern "C" __declspec(dllexport) void plugstop()
{
}
extern "C" __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT*)
{
}
#include <Windows.h>
#include <atomic>
#include <cstring>
#include <string>
#include "_plugins.h"
#include "bridgemain.h"
namespace
{
int gPluginHandle = 0;
std::atomic<unsigned int> gMemoryHitCount{ 0 };
std::atomic<duint> gLastBpAddr{ 0 };
std::atomic<duint> gLastCip{ 0 };
std::atomic<bool> gExitObserved{ false };
std::atomic<unsigned long> gExitCode{ 0 };
std::atomic<duint> gCachedBpAddr{ 0 };
std::atomic<bool> gExpectExitHit{ false };
std::atomic<duint> gExpectedExitBpAddr{ 0 };
void resetState()
{
gMemoryHitCount = 0;
gLastBpAddr = 0;
gLastCip = 0;
gExitObserved = false;
gExitCode = 0;
gCachedBpAddr = 0;
gExpectExitHit = false;
gExpectedExitBpAddr = 0;
}
duint evalExpr(const char* expr)
{
return expr ? DbgValFromString(expr) : 0;
}
duint evalWrapped(const char* prefix, const char* expr, const char* suffix)
{
std::string wrapped = std::string(prefix) + expr + suffix;
return DbgValFromString(wrapped.c_str());
}
bool findBpByStart(duint expectedStart, duint & size)
{
BPMAP list{};
if(DbgGetBpList(bp_memory, &list) == 0)
return false;
bool found = false;
for(int i = 0; i < list.count; i++)
{
const auto & bp = list.bp[i];
if(bp.type != bp_memory)
continue;
if(bp.addr != expectedStart)
continue;
size = DbgFunctions()->MemBpSize(bp.addr);
found = true;
break;
}
BridgeFree(list.bp);
return found;
}
bool assertExpectedHit(duint expectedBp, bool requireProcessAlive, duint count);
void cbPlugin(CBTYPE cbType, void* callbackInfo)
{
if(cbType == CB_INITDEBUG)
{
resetState();
return;
}
if(cbType == CB_BREAKPOINT)
{
const auto info = static_cast<PLUG_CB_BREAKPOINT*>(callbackInfo);
if(info && info->breakpoint && info->breakpoint->type == bp_memory)
{
gMemoryHitCount.fetch_add(1);
gLastBpAddr = info->breakpoint->addr;
gLastCip = DbgValFromString("cip");
}
return;
}
if(cbType == CB_EXITPROCESS)
{
const auto info = static_cast<PLUG_CB_EXITPROCESS*>(callbackInfo);
if(info && info->ExitProcess)
{
gExitObserved = true;
gExitCode = info->ExitProcess->dwExitCode;
if(gExpectExitHit.load())
assertExpectedHit(gExpectedExitBpAddr.load(), false, 1);
}
}
}
bool cbReset(int, char**)
{
resetState();
return _plugin_testassert(true, "state reset");
}
bool cbAssertRegion(int argc, char** argv)
{
if(argc < 2)
return false;
const duint target = evalExpr(argv[1]);
if(!_plugin_testassert(target != 0, "failed to resolve target expression '%s'", argv[1]))
return false;
const duint expectedStart = evalWrapped("mem.base(", argv[1], ")");
const duint expectedSize = evalWrapped("mem.size(", argv[1], ")");
if(!_plugin_testassert(expectedStart != 0, "failed to resolve mem.base(%s)", argv[1]))
return false;
if(!_plugin_testassert(expectedSize != 0, "failed to resolve mem.size(%s)", argv[1]))
return false;
duint actualSize = 0;
if(!_plugin_testassert(findBpByStart(expectedStart, actualSize), "failed to find memory breakpoint starting at 0x%llX", static_cast<unsigned long long>(expectedStart)))
return false;
return _plugin_testassert(
actualSize == expectedSize,
"expected region breakpoint [%0llX, 0x%llX), got [0x%llX, 0x%llX) for target %s",
static_cast<unsigned long long>(expectedStart),
static_cast<unsigned long long>(expectedStart + expectedSize),
static_cast<unsigned long long>(expectedStart),
static_cast<unsigned long long>(expectedStart + actualSize),
argv[1]
);
}
bool cbAssertExact(int argc, char** argv)
{
if(argc < 3)
return false;
const duint expectedStart = evalExpr(argv[1]);
const duint expectedSize = evalExpr(argv[2]);
if(!_plugin_testassert(expectedStart != 0, "failed to resolve target expression '%s'", argv[1]))
return false;
if(!_plugin_testassert(expectedSize != 0, "failed to resolve size expression '%s'", argv[2]))
return false;
duint actualSize = 0;
if(!_plugin_testassert(findBpByStart(expectedStart, actualSize), "failed to find memory breakpoint starting at 0x%llX", static_cast<unsigned long long>(expectedStart)))
return false;
return _plugin_testassert(
actualSize == expectedSize,
"expected exact breakpoint [%0llX, 0x%llX), got [0x%llX, 0x%llX)",
static_cast<unsigned long long>(expectedStart),
static_cast<unsigned long long>(expectedStart + expectedSize),
static_cast<unsigned long long>(expectedStart),
static_cast<unsigned long long>(expectedStart + actualSize)
);
}
bool cbCacheBp(int argc, char** argv)
{
if(argc < 2)
return false;
const duint expectedBp = evalExpr(argv[1]);
if(!_plugin_testassert(expectedBp != 0, "failed to resolve breakpoint expression '%s'", argv[1]))
return false;
gCachedBpAddr = expectedBp;
return _plugin_testassert(true, "cached breakpoint address 0x%llX", static_cast<unsigned long long>(expectedBp));
}
bool assertExpectedHit(duint expectedBp, bool requireProcessAlive, duint count)
{
if(!_plugin_testassert(expectedBp != 0, "failed to resolve expected breakpoint address"))
return false;
if(!_plugin_testassert(gMemoryHitCount.load() == count, "expected exactly %lu memory breakpoint callback, got %u", count, gMemoryHitCount.load()))
return false;
if(!_plugin_testassert(gLastBpAddr.load() == expectedBp, "expected memory breakpoint address 0x%llX, got 0x%llX", static_cast<unsigned long long>(expectedBp), static_cast<unsigned long long>(gLastBpAddr.load())))
return false;
if(!requireProcessAlive)
return true;
return _plugin_testassert(!gExitObserved.load(), "process exited before the memory breakpoint assertion, exitCode=%lu, lastCip=0x%llX", gExitCode.load(), static_cast<unsigned long long>(gLastCip.load()));
}
bool cbAssertHit(int argc, char** argv)
{
duint expectedBp = 0;
duint count = 1;
if(argc >= 2)
expectedBp = evalExpr(argv[1]);
if(argc >= 3)
count = evalExpr(argv[2]);
if(expectedBp == 0)
expectedBp = gCachedBpAddr.load();
return assertExpectedHit(expectedBp, true, count);
}
bool cbAssertNoHit(int argc, char** argv)
{
if(argc < 2)
return false;
const duint expectedExitCode = evalExpr(argv[1]);
if(!_plugin_testassert(gMemoryHitCount.load() == 0, "expected no memory breakpoint callbacks, got %u", gMemoryHitCount.load()))
return false;
if(!_plugin_testassert(gExitObserved.load(), "expected process exit to be observed"))
return false;
return _plugin_testassert(gExitCode.load() == expectedExitCode, "expected exit code 0x%llX, got 0x%lX", static_cast<unsigned long long>(expectedExitCode), gExitCode.load());
}
bool cbExpectExitHit(int argc, char** argv)
{
duint expectedBp = 0;
if(argc >= 2)
expectedBp = evalExpr(argv[1]);
if(expectedBp == 0)
expectedBp = gCachedBpAddr.load();
if(!_plugin_testassert(expectedBp != 0, "failed to resolve expected exit breakpoint address"))
return false;
gExpectedExitBpAddr = expectedBp;
gExpectExitHit = true;
return _plugin_testassert(true, "expecting exit-time memory breakpoint assertion for 0x%llX", static_cast<unsigned long long>(expectedBp));
}
bool cbReenable(int argc, char** argv)
{
if(argc < 2)
return false;
const auto disableCommand = std::string("membpd ") + argv[1];
const auto enableCommand = std::string("membpe ") + argv[1];
if(!_plugin_testassert(DbgCmdExecDirect(disableCommand.c_str()), "membpd failed for %s", argv[1]))
return false;
return _plugin_testassert(DbgCmdExecDirect(enableCommand.c_str()), "membpe failed for %s", argv[1]);
}
}
extern "C" __declspec(dllexport) bool pluginit(PLUG_INITSTRUCT* initStruct)
{
initStruct->pluginVersion = 1;
initStruct->sdkVersion = PLUG_SDKVERSION;
strncpy_s(initStruct->pluginName, sizeof(initStruct->pluginName), "MembpRegression", _TRUNCATE);
gPluginHandle = initStruct->pluginHandle;
_plugin_registercallback(gPluginHandle, CB_INITDEBUG, cbPlugin);
_plugin_registercallback(gPluginHandle, CB_BREAKPOINT, cbPlugin);
_plugin_registercallback(gPluginHandle, CB_EXITPROCESS, cbPlugin);
_plugin_registercommand(gPluginHandle, "mbreset", cbReset, false);
_plugin_registercommand(gPluginHandle, "mbassertregion", cbAssertRegion, false);
_plugin_registercommand(gPluginHandle, "mbassertexact", cbAssertExact, false);
_plugin_registercommand(gPluginHandle, "mbcachebp", cbCacheBp, false);
_plugin_registercommand(gPluginHandle, "mbasserthit", cbAssertHit, false);
_plugin_registercommand(gPluginHandle, "mbassertnohit", cbAssertNoHit, false);
_plugin_registercommand(gPluginHandle, "mbexpectexithit", cbExpectExitHit, false);
_plugin_registercommand(gPluginHandle, "mbreenable", cbReenable, false);
return true;
}
extern "C" __declspec(dllexport) void plugstop()
{
}
extern "C" __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT*)
{
}

View File

@ -1,35 +1,51 @@
#include <Windows.h>
#pragma section(".mbd2", read, write)
#pragma section(".mbd3", read, write)
extern "C"
{
__declspec(allocate(".mbd2")) volatile unsigned char Padding[0x2000] = { 2 };
__declspec(allocate(".mbd3")) __declspec(dllexport) volatile signed char ReadTarget = -1;
__declspec(allocate(".mbd3")) __declspec(dllexport) volatile signed char WriteTarget = 0;
#pragma code_seg(push, membp_code, ".mbc")
__declspec(dllexport) __declspec(noinline) void ReadSequence()
{
const auto value = ReadTarget;
ExitProcess(static_cast<UINT>(static_cast<unsigned char>(value)));
}
__declspec(dllexport) __declspec(noinline) void WriteSequence()
{
WriteTarget = 0x5A;
ExitProcess(0x5A);
}
__declspec(dllexport) __declspec(noinline) void ExecSequence()
{
ExitProcess(0x33);
}
__declspec(noinline) void start()
{
ExitProcess(0);
}
#pragma code_seg(pop, membp_code)
}
#include <Windows.h>
#pragma section(".mbd2", read, write)
#pragma section(".mbd3", read, write)
extern "C"
{
__declspec(allocate(".mbd2")) volatile unsigned char Padding[0x2000] = { 2 };
__declspec(allocate(".mbd3")) __declspec(dllexport) volatile signed char ReadTarget = -1;
__declspec(allocate(".mbd3")) __declspec(dllexport) volatile signed char WriteTarget = 0;
__declspec(allocate(".mbd3")) __declspec(dllexport) volatile unsigned char* HeapPointer = nullptr;
#pragma code_seg(push, membp_code, ".mbc")
__declspec(dllexport) __declspec(noinline) void ReadSequence()
{
const auto value = ReadTarget;
ExitProcess(static_cast<UINT>(static_cast<unsigned char>(value)));
}
__declspec(dllexport) __declspec(noinline) void WriteSequence()
{
WriteTarget = 0x5A;
ExitProcess(0x5A);
}
__declspec(dllexport) __declspec(noinline) void ExecSequence()
{
ExitProcess(0x33);
}
__declspec(noinline) void start()
{
ExitProcess(0);
}
__declspec(dllexport) __declspec(noinline) void WriteHeap()
{
*HeapPointer = 0xBC;
ExitProcess(static_cast<UINT>(*HeapPointer));
}
__declspec(dllexport) __declspec(noinline) void StartHeap()
{
HANDLE hHeap = GetProcessHeap();
HeapPointer = (unsigned char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 1024);
WriteHeap();
}
#pragma code_seg(pop, membp_code)
}

View File

@ -1,5 +1,6 @@
settingset Events, EntryBreakpoint, 0
settingset Events, EntryBreakpoint, 1
init tests/membp.exe
run
mbreset
cip=membp:WriteSequence
bpmrange membp:WriteTarget, 1, w

View File

@ -0,0 +1,15 @@
settingset Events, EntryBreakpoint, 1
init tests/membp.exe
run
mbreset
cip=membp:StartHeap
bpmrange membp:WriteHeap, 1, x
run
bpmc membp:WriteHeap
push r12
mov r12, [membp:HeapPointer]
bpmrange r12, 1, a
run
mbasserthit r12, 2
pop r12
stop

View File

@ -1,5 +1,6 @@
settingset Events, EntryBreakpoint, 0
settingset Events, EntryBreakpoint, 1
init tests/membp.exe
run
mbreset
mbcachebp membp:WriteTarget
cip=membp:WriteSequence
@ -7,3 +8,4 @@ bpmrange membp:WriteTarget, 1, w
mbassertexact membp:WriteTarget, 1
run
mbasserthit
stop

View File

@ -1,7 +1,7 @@
#include <windows.h>
int main()
{
Sleep(10);
return 0;
}
#include <windows.h>
int main()
{
Sleep(10);
return 0;
}

View File

@ -1,83 +1,83 @@
#include <Windows.h>
#include <atomic>
#include <cstring>
#include <string>
#include "_plugins.h"
#include "bridgemain.h"
namespace
{
int gPluginHandle = 0;
std::atomic<unsigned int> gOuterDispatchCount{ 0 };
std::atomic<unsigned int> gInnerDispatchCount{ 0 };
bool pluginDirectModeEnabled()
{
return DbgValFromString("$plugin_direct_mode") != 0;
}
duint resolveAddress(const char* expression)
{
return DbgValFromString(expression);
}
bool dispatchScriptCallback(const char* label, std::atomic<unsigned int>& counter)
{
counter.fetch_add(1);
const auto command = std::string("scriptcmd call ") + label;
return _plugin_testassert(DbgCmdExecDirect(command.c_str()), "DbgCmdExecDirect failed for %s", label);
}
void cbPlugin(CBTYPE cbType, void* callbackInfo)
{
if(cbType == CB_INITDEBUG)
{
gOuterDispatchCount = 0;
gInnerDispatchCount = 0;
return;
}
if(cbType != CB_BREAKPOINT || !pluginDirectModeEnabled())
return;
const auto info = static_cast<PLUG_CB_BREAKPOINT*>(callbackInfo);
if(info == nullptr || info->breakpoint == nullptr || info->breakpoint->type != bp_normal)
return;
const auto outerAddress = resolveAddress("scriptcmd_call:OuterHit");
const auto innerAddress = resolveAddress("scriptcmd_call:InnerHit");
if(info->breakpoint->addr == outerAddress)
dispatchScriptCallback("onouter", gOuterDispatchCount);
else if(info->breakpoint->addr == innerAddress)
dispatchScriptCallback("oninner", gInnerDispatchCount);
}
bool cbPluginDirectAssert(int, char**)
{
if(!_plugin_testassert(gOuterDispatchCount.load() == 1, "expected exactly 1 plugin-direct outer dispatch, got %u", gOuterDispatchCount.load()))
return false;
return _plugin_testassert(gInnerDispatchCount.load() == 1, "expected exactly 1 plugin-direct inner dispatch, got %u", gInnerDispatchCount.load());
}
}
extern "C" __declspec(dllexport) bool pluginit(PLUG_INITSTRUCT* initStruct)
{
initStruct->pluginVersion = 1;
initStruct->sdkVersion = PLUG_SDKVERSION;
strncpy_s(initStruct->pluginName, sizeof(initStruct->pluginName), "ScriptCmdCallPlugin", _TRUNCATE);
gPluginHandle = initStruct->pluginHandle;
_plugin_registercallback(gPluginHandle, CB_INITDEBUG, cbPlugin);
_plugin_registercallback(gPluginHandle, CB_BREAKPOINT, cbPlugin);
_plugin_registercommand(gPluginHandle, "plugindirectassert", cbPluginDirectAssert, false);
return true;
}
extern "C" __declspec(dllexport) void plugstop()
{
}
extern "C" __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT*)
{
}
#include <Windows.h>
#include <atomic>
#include <cstring>
#include <string>
#include "_plugins.h"
#include "bridgemain.h"
namespace
{
int gPluginHandle = 0;
std::atomic<unsigned int> gOuterDispatchCount{ 0 };
std::atomic<unsigned int> gInnerDispatchCount{ 0 };
bool pluginDirectModeEnabled()
{
return DbgValFromString("$plugin_direct_mode") != 0;
}
duint resolveAddress(const char* expression)
{
return DbgValFromString(expression);
}
bool dispatchScriptCallback(const char* label, std::atomic<unsigned int> & counter)
{
counter.fetch_add(1);
const auto command = std::string("scriptcmd call ") + label;
return _plugin_testassert(DbgCmdExecDirect(command.c_str()), "DbgCmdExecDirect failed for %s", label);
}
void cbPlugin(CBTYPE cbType, void* callbackInfo)
{
if(cbType == CB_INITDEBUG)
{
gOuterDispatchCount = 0;
gInnerDispatchCount = 0;
return;
}
if(cbType != CB_BREAKPOINT || !pluginDirectModeEnabled())
return;
const auto info = static_cast<PLUG_CB_BREAKPOINT*>(callbackInfo);
if(info == nullptr || info->breakpoint == nullptr || info->breakpoint->type != bp_normal)
return;
const auto outerAddress = resolveAddress("scriptcmd_call:OuterHit");
const auto innerAddress = resolveAddress("scriptcmd_call:InnerHit");
if(info->breakpoint->addr == outerAddress)
dispatchScriptCallback("onouter", gOuterDispatchCount);
else if(info->breakpoint->addr == innerAddress)
dispatchScriptCallback("oninner", gInnerDispatchCount);
}
bool cbPluginDirectAssert(int, char**)
{
if(!_plugin_testassert(gOuterDispatchCount.load() == 1, "expected exactly 1 plugin-direct outer dispatch, got %u", gOuterDispatchCount.load()))
return false;
return _plugin_testassert(gInnerDispatchCount.load() == 1, "expected exactly 1 plugin-direct inner dispatch, got %u", gInnerDispatchCount.load());
}
}
extern "C" __declspec(dllexport) bool pluginit(PLUG_INITSTRUCT* initStruct)
{
initStruct->pluginVersion = 1;
initStruct->sdkVersion = PLUG_SDKVERSION;
strncpy_s(initStruct->pluginName, sizeof(initStruct->pluginName), "ScriptCmdCallPlugin", _TRUNCATE);
gPluginHandle = initStruct->pluginHandle;
_plugin_registercallback(gPluginHandle, CB_INITDEBUG, cbPlugin);
_plugin_registercallback(gPluginHandle, CB_BREAKPOINT, cbPlugin);
_plugin_registercommand(gPluginHandle, "plugindirectassert", cbPluginDirectAssert, false);
return true;
}
extern "C" __declspec(dllexport) void plugstop()
{
}
extern "C" __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT*)
{
}

View File

@ -1,20 +1,20 @@
#include <windows.h>
static volatile int gValue = 0;
extern "C" __declspec(dllexport) __declspec(noinline) void OuterHit()
{
gValue += 1;
}
extern "C" __declspec(dllexport) __declspec(noinline) void InnerHit()
{
gValue += 2;
}
int main()
{
OuterHit();
InnerHit();
return gValue == 3 ? 0 : 1;
}
#include <windows.h>
static volatile int gValue = 0;
extern "C" __declspec(dllexport) __declspec(noinline) void OuterHit()
{
gValue += 1;
}
extern "C" __declspec(dllexport) __declspec(noinline) void InnerHit()
{
gValue += 2;
}
int main()
{
OuterHit();
InnerHit();
return gValue == 3 ? 0 : 1;
}

View File

@ -1,44 +1,44 @@
#include <windows.h>
static HANDLE gGoEvent = nullptr;
static volatile LONG gMainCounter = 0;
static volatile LONG gWorkerCounter = 0;
extern "C" __declspec(dllexport) __declspec(noinline) void MainThreadOuterHit()
{
InterlockedIncrement(&gMainCounter);
}
extern "C" __declspec(dllexport) __declspec(noinline) void WorkerThreadInnerHit()
{
InterlockedExchangeAdd(&gWorkerCounter, 2);
}
static DWORD WINAPI WorkerThreadProc(void*)
{
WaitForSingleObject(gGoEvent, INFINITE);
WorkerThreadInnerHit();
return 0;
}
int main()
{
gGoEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
if(gGoEvent == nullptr)
return 1;
auto thread = CreateThread(nullptr, 0, WorkerThreadProc, nullptr, 0, nullptr);
if(thread == nullptr)
{
CloseHandle(gGoEvent);
return 1;
}
MainThreadOuterHit();
SetEvent(gGoEvent);
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
CloseHandle(gGoEvent);
return 0;
}
#include <windows.h>
static HANDLE gGoEvent = nullptr;
static volatile LONG gMainCounter = 0;
static volatile LONG gWorkerCounter = 0;
extern "C" __declspec(dllexport) __declspec(noinline) void MainThreadOuterHit()
{
InterlockedIncrement(&gMainCounter);
}
extern "C" __declspec(dllexport) __declspec(noinline) void WorkerThreadInnerHit()
{
InterlockedExchangeAdd(&gWorkerCounter, 2);
}
static DWORD WINAPI WorkerThreadProc(void*)
{
WaitForSingleObject(gGoEvent, INFINITE);
WorkerThreadInnerHit();
return 0;
}
int main()
{
gGoEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
if(gGoEvent == nullptr)
return 1;
auto thread = CreateThread(nullptr, 0, WorkerThreadProc, nullptr, 0, nullptr);
if(thread == nullptr)
{
CloseHandle(gGoEvent);
return 1;
}
MainThreadOuterHit();
SetEvent(gGoEvent);
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
CloseHandle(gGoEvent);
return 0;
}