pretty much implemented setting/deleting memory breakpoints

This commit is contained in:
mrexodia 2016-08-19 00:32:23 +02:00
parent ca254d1e1f
commit 83854fb4a7
5 changed files with 317 additions and 6 deletions

View File

@ -44,9 +44,9 @@ namespace GleeBug
enum class MemoryType enum class MemoryType
{ {
Acess, Access = 1,
Write, Write = 2,
Execute Execute = 4
}; };
/** /**
@ -88,6 +88,17 @@ namespace GleeBug
BreakpointType type; BreakpointType type;
BreakpointInternalInfo internal; BreakpointInternalInfo internal;
}; };
/**
\brief Structure for memory breakpoint management.
*/
struct MemoryBreakpointData
{
uint32 Refcount;
uint32 Type;
DWORD OldProtect;
DWORD NewProtect;
};
}; };
#endif //DEBUGGER_BREAKPOINT_H #endif //DEBUGGER_BREAKPOINT_H

View File

@ -16,6 +16,7 @@ namespace GleeBug
class Thread; class Thread;
enum class BreakpointType; enum class BreakpointType;
struct BreakpointInfo; struct BreakpointInfo;
struct MemoryBreakpointData;
//constants //constants
const int HWBP_COUNT = GLEEBUG_HWBP_COUNT; const int HWBP_COUNT = GLEEBUG_HWBP_COUNT;
@ -36,6 +37,7 @@ namespace GleeBug
typedef std::map<BreakpointKey, BreakpointCallback> BreakpointCallbackMap; typedef std::map<BreakpointKey, BreakpointCallback> BreakpointCallbackMap;
typedef std::unordered_map<ptr, BreakpointMap::iterator> SoftwareBreakpointMap; typedef std::unordered_map<ptr, BreakpointMap::iterator> SoftwareBreakpointMap;
typedef std::set<Range, RangeCompare> MemoryBreakpointSet; typedef std::set<Range, RangeCompare> MemoryBreakpointSet;
typedef std::unordered_map<ptr, MemoryBreakpointData> MemoryBreakpointMap;
//vector typedefs //vector typedefs
typedef std::vector<StepCallback> StepCallbackVector; typedef std::vector<StepCallback> StepCallbackVector;

View File

@ -176,6 +176,227 @@ namespace GleeBug
return success; return success;
} }
#define PAGE_SHIFT (12)
#define PAGE_ALIGN(Va) ((ULONG_PTR)((ULONG_PTR)(Va) & ~(PAGE_SIZE - 1)))
#define BYTES_TO_PAGES(Size) (((Size) >> PAGE_SHIFT) + (((Size) & (PAGE_SIZE - 1)) != 0))
#define ROUND_TO_PAGES(Size) (((ULONG_PTR)(Size) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))
/*
#define PAGE_NOACCESS 0x01
#define PAGE_READONLY 0x02
#define PAGE_READWRITE 0x04
#define PAGE_WRITECOPY 0x08 <- not supported
#define PAGE_EXECUTE 0x10
#define PAGE_EXECUTE_READ 0x20
#define PAGE_EXECUTE_READWRITE 0x40
#define PAGE_EXECUTE_WRITECOPY 0x80 <- not supported
#define PAGE_GUARD 0x100 <- not supported with PAGE_NOACCESS
#define PAGE_NOCACHE 0x200 <- not supported with PAGE_GUARD or PAGE_WRITECOMBINE
#define PAGE_WRITECOMBINE 0x400 <- not supported with PAGE_GUARD or PAGE_NOCACHE
*/
static DWORD RemoveExecuteAccess(DWORD dwAccess)
{
DWORD dwBase = dwAccess & 0xFF;
DWORD dwHigh = dwAccess & 0xFFFFFF00;
switch (dwBase)
{
case PAGE_EXECUTE:
return dwHigh | PAGE_READONLY;
case PAGE_EXECUTE_READ:
case PAGE_EXECUTE_READWRITE:
case PAGE_EXECUTE_WRITECOPY:
return dwHigh | (dwBase >> 4);
default:
return dwAccess;
}
}
static DWORD RemoveWriteAccess(DWORD dwAccess)
{
DWORD dwBase = dwAccess & 0xFF;
switch (dwBase)
{
case PAGE_READWRITE:
case PAGE_EXECUTE_READWRITE:
return (dwAccess & 0xFFFFFF00) | (dwBase >> 1);
default:
return dwAccess;
}
}
bool Process::SetNewPageProtection(ptr page, MemoryBreakpointData & data, MemoryType type)
{
//TODO: handle PAGE_NOACCESS and such correctly (since it cannot be combined with PAGE_GUARD)
auto found = memoryBreakpointPages.find(page);
if (found == memoryBreakpointPages.end())
{
data.Refcount = 1;
switch (type)
{
case MemoryType::Access:
data.NewProtect = data.OldProtect | PAGE_GUARD;
break;
case MemoryType::Write:
data.NewProtect = RemoveWriteAccess(data.OldProtect);
break;
case MemoryType::Execute:
data.NewProtect = permanentDep ? RemoveExecuteAccess(data.OldProtect) : data.OldProtect | PAGE_GUARD;
break;
}
}
else
{
auto & oldData = found->second;
data.Type = oldData.Type | uint32(type);
data.OldProtect = oldData.OldProtect;
data.Refcount = oldData.Refcount + 1;
if (data.Type & uint32(MemoryType::Access)) //Access always becomes PAGE_GUARD
data.NewProtect = data.OldProtect | PAGE_GUARD;
else if (data.Type & (uint32(MemoryType::Write) | uint32(MemoryType::Execute))) //Write + Execute becomes either PAGE_GUARD or both write and execute flags removed
data.NewProtect = permanentDep ? RemoveExecuteAccess(RemoveWriteAccess(data.OldProtect)) : data.OldProtect | PAGE_GUARD;
}
return false;
}
bool Process::SetMemoryBreakpoint(ptr address, ptr size, MemoryType type, bool singleshoot)
{
//TODO: error reporting
//basic checks
if (!MemIsValidPtr(address) || !size)
return false;
//check if the range is unused for any previous memory breakpoints
auto range = Range(address, address + size - 1);
if (memoryBreakpointRanges.find(range) != memoryBreakpointRanges.end())
return false;
//change page protections
bool success = true;
struct TempMemoryBreakpointData
{
ptr addr;
DWORD OldProtect;
MemoryBreakpointData data;
};
std::vector<TempMemoryBreakpointData> breakpointData;
{
breakpointData.reserve(size / PAGE_SIZE);
TempMemoryBreakpointData tempData;
MemoryBreakpointData data;
data.Type = uint32(type);
auto alignedAddress = PAGE_ALIGN(address);
for (auto page = alignedAddress; page < alignedAddress + BYTES_TO_PAGES(size); page += PAGE_SIZE)
{
MEMORY_BASIC_INFORMATION mbi;
if (!VirtualQueryEx(hProcess, LPCVOID(page), &mbi, sizeof(mbi)))
{
success = false;
break;
}
data.OldProtect = mbi.Protect;
if (!SetNewPageProtection(page, data, type))
{
success = false;
break;
}
tempData.addr = page;
tempData.OldProtect = mbi.Protect;
tempData.data = data;
breakpointData.push_back(tempData);
}
}
//if changing the page protections failed, attempt to revert all protection changes
if (!success)
{
for (const auto & page : breakpointData)
{
DWORD oldProtect;
VirtualProtectEx(hProcess, LPVOID(page.addr), PAGE_SIZE, page.OldProtect, &oldProtect);
}
return false;
}
//set the page data
for (const auto & page : breakpointData)
memoryBreakpointPages[page.addr] = page.data;
//setup the breakpoint information struct
BreakpointInfo info = {};
info.address = address;
info.enabled = true;
info.singleshoot = singleshoot;
info.type = BreakpointType::Memory;
info.internal.memory.type = type;
info.internal.memory.size = size;
//insert in the breakpoint map
breakpoints.insert({ { info.type, info.address }, info });
memoryBreakpointRanges.insert(range);
return true;
}
bool Process::SetMemoryBreakpoint(ptr address, ptr size, const BreakpointCallback & cbBreakpoint, MemoryType type, bool singleshoot)
{
//check if a callback on this address was already found
if (breakpointCallbacks.find({ BreakpointType::Memory, address }) != breakpointCallbacks.end())
return false;
//set the memory breakpoint
if (!SetMemoryBreakpoint(address, size, type, singleshoot))
return false;
//insert the callback
breakpointCallbacks.insert({ { BreakpointType::Memory, address }, cbBreakpoint });
return true;
}
bool Process::DeleteMemoryBreakpoint(ptr address)
{
//find the hardware breakpoint
auto found = breakpoints.find({ BreakpointType::Hardware, address });
if (found == breakpoints.end())
return false;
const auto & info = found->second;
//delete the memory breakpoint from the pages
bool success = true;
auto alignedAddress = PAGE_ALIGN(info.address);
for (auto page = alignedAddress; page < alignedAddress + BYTES_TO_PAGES(info.internal.memory.size); page += PAGE_SIZE)
{
auto foundData = memoryBreakpointPages.find(page);
if (foundData == memoryBreakpointPages.end())
continue; //TODO: error reporting
auto & data = foundData->second;
DWORD Protect;
data.Refcount--;
if (data.Refcount)
{
//TODO: properly determine the new protection flag
if (data.Type & ~uint32(info.type))
data.NewProtect = data.OldProtect | PAGE_GUARD;
Protect = data.NewProtect;
}
else
Protect = data.OldProtect;
DWORD oldProtect;
if (!VirtualProtectEx(hProcess, LPVOID(page), PAGE_SIZE, Protect, &oldProtect))
success = false;
if (!data.Refcount)
memoryBreakpointPages.erase(foundData);
}
//delete the breakpoint from the maps
breakpoints.erase(found);
breakpointCallbacks.erase({ BreakpointType::Hardware, address });
memoryBreakpointRanges.erase(Range(address, address));
return success;
}
bool Process::DeleteGenericBreakpoint(const BreakpointInfo & info) bool Process::DeleteGenericBreakpoint(const BreakpointInfo & info)
{ {
switch (info.type) switch (info.type)
@ -185,7 +406,7 @@ namespace GleeBug
case BreakpointType::Hardware: case BreakpointType::Hardware:
return DeleteHardwareBreakpoint(info.address); return DeleteHardwareBreakpoint(info.address);
case BreakpointType::Memory: case BreakpointType::Memory:
return false; //TODO implement this return DeleteMemoryBreakpoint(info.address);
default: default:
return false; return false;
} }

View File

@ -8,10 +8,30 @@ namespace GleeBug
dwMainThreadId(dwMainThreadId), dwMainThreadId(dwMainThreadId),
createProcessInfo(createProcessInfo), createProcessInfo(createProcessInfo),
thread(nullptr), thread(nullptr),
systemBreakpoint(false) systemBreakpoint(false),
permanentDep(false)
{ {
for (int i = 0; i < HWBP_COUNT; i++) for (int i = 0; i < HWBP_COUNT; i++)
hardwareBreakpoints[i].enabled = false; hardwareBreakpoints[i].enabled = false;
// DEP is disabled if lpFlagsDep == 0
typedef BOOL(WINAPI * GETPROCESSDEPPOLICY)(
_In_ HANDLE /*hProcess*/,
_Out_ LPDWORD /*lpFlags*/,
_Out_ PBOOL /*lpPermanent*/
);
static auto GPDP = GETPROCESSDEPPOLICY(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetProcessDEPPolicy"));
if (GPDP)
{
DWORD lpFlags;
BOOL bPermanent;
if (GPDP(hProcess, &lpFlags, &bPermanent))
permanentDep = lpFlags && bPermanent;
#ifdef _WIN64
else if (GetLastError() == ERROR_NOT_SUPPORTED)
permanentDep = true;
#endif
}
} }
void Process::StepOver(const StepCallback & cbStep) void Process::StepOver(const StepCallback & cbStep)

View File

@ -23,6 +23,7 @@ namespace GleeBug
Thread* thread; Thread* thread;
bool systemBreakpoint; bool systemBreakpoint;
bool permanentDep;
ThreadMap threads; //DO NOT COPY THESE OBJECTS! ThreadMap threads; //DO NOT COPY THESE OBJECTS!
DllMap dlls; DllMap dlls;
@ -30,7 +31,8 @@ namespace GleeBug
SoftwareBreakpointMap softwareBreakpointReferences; SoftwareBreakpointMap softwareBreakpointReferences;
BreakpointCallbackMap breakpointCallbacks; BreakpointCallbackMap breakpointCallbacks;
BreakpointInfo hardwareBreakpoints[4]; BreakpointInfo hardwareBreakpoints[4];
MemoryBreakpointSet memoryBreakpoints; MemoryBreakpointSet memoryBreakpointRanges;
MemoryBreakpointMap memoryBreakpointPages;
/** /**
\brief Constructor. \brief Constructor.
@ -301,6 +303,61 @@ namespace GleeBug
*/ */
bool DeleteHardwareBreakpoint(ptr address); bool DeleteHardwareBreakpoint(ptr address);
/**
\brief Sets new page protection to trigger an exception for certain memory breakpoint types.
\param page The page address.
\param data The current protection of the page.
\param type The memory breakpoint type to trigger an exception for.
\return true if it succeeds, false if it fails.
*/
bool SetNewPageProtection(ptr page, MemoryBreakpointData & data, MemoryType type);
/**
\brief Sets a memory breakpoint.
\param address The address to set the memory breakpoint on.
\param size Size of the memory breakpoint (in bytes).
\param type (Optional) The memory breakpoint type.
\param singleshoot (Optional) True to remove the breakpoint after the first hit.
\return true if the memory breakpoint was set, false otherwise.
*/
bool SetMemoryBreakpoint(ptr address, ptr size, MemoryType type = MemoryType::Access, bool singleshoot = false);
/**
\brief Sets a memory breakpoint.
\param address The address to set the memory breakpoint on.
\param size Size of the memory breakpoint (in bytes).
\param cbBreakpoint The breakpoint callback. Can be written using BIND1(this, MyDebugger::cb).
\param type (Optional) The memory breakpoint type.
\param singleshoot (Optional) True to remove the breakpoint after the first hit.
\return true if the memory breakpoint was set, false otherwise.
*/
bool SetMemoryBreakpoint(ptr address, ptr size, const BreakpointCallback & cbBreakpoint, MemoryType type = MemoryType::Access, bool singleshoot = false);
/**
\brief Sets a hardware breakpoint.
\tparam T Generic type parameter. Must be a subclass of Debugger.
\param address The address to set the hardware breakpoint on.
\param size Size of the memory breakpoint (in bytes).
\param debugger This pointer to a subclass of Debugger.
\param callback Pointer to the callback. Written like: &MyDebugger::cb
\param type (Optional) The memory breakpoint type.
\param singleshoot (Optional) True to remove the breakpoint after the first hit.
\return true if the memory breakpoint was set, false otherwise.
*/
template <typename T>
bool SetMemoryBreakpoint(ptr address, ptr size, T* debugger, void(T::*callback)(const BreakpointInfo & info), MemoryType type = MemoryType::Access, bool singleshoot = false)
{
static_cast<void>(static_cast<Debugger*>(debugger));
return SetMemoryBreakpoint(address, size, std::bind(callback, debugger, std::placeholders::_1), type, singleshoot);
}
/**
\brief Deletes a hardware breakpoint.
\param address The address the hardware breakpoint is set on.
\return true if the hardware breakpoint was deleted, false otherwise.
*/
bool DeleteMemoryBreakpoint(ptr address);
/** /**
\brief Deletes a breakpoint. \brief Deletes a breakpoint.
\param info The breakpoint information. \param info The breakpoint information.