我想捕获特定内存范围的内存写入,并使用写入的内存位置的地址调用函数.优选地,在已经发生对存储器的写入之后.
我知道这可以通过操作系统来填充页表条目来完成.但是,如何在想要这样做的应用程序中完成类似的操作呢?
好吧,你可以这样做:
// compile with Open Watcom 1.9: wcl386 wrtrap.c
#include <windows.h>
#include <stdio.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
UINT_PTR RangeStart = 0;
SIZE_T RangeSize = 0;
UINT_PTR AlignedRangeStart = 0;
SIZE_T AlignedRangeSize = 0;
void MonitorRange(void* Start,size_t Size)
{
DWORD dummy;
if (Start &&
Size &&
(AlignedRangeStart == 0) &&
(AlignedRangeSize == 0))
{
RangeStart = (UINT_PTR)Start;
RangeSize = Size;
// Page-align the range address and size
AlignedRangeStart = RangeStart & ~(UINT_PTR)(PAGE_SIZE - 1);
AlignedRangeSize = ((RangeStart + RangeSize - 1 + PAGE_SIZE) &
~(UINT_PTR)(PAGE_SIZE - 1)) -
AlignedRangeStart;
// Make the page range read-only
VirtualProtect((LPVOID)AlignedRangeStart,AlignedRangeSize,PAGE_READONLY,&dummy);
}
else if (((Start == NULL) || (Size == 0)) &&
AlignedRangeStart &&
AlignedRangeSize)
{
// Restore the original setting
// Make the page range read-write
VirtualProtect((LPVOID)AlignedRangeStart,PAGE_READWRITE,&dummy);
RangeStart = 0;
RangeSize = 0;
AlignedRangeStart = 0;
AlignedRangeSize = 0;
}
}
// This is where the magic happens...
int ExceptionFilter(LPEXCEPTION_POINTERS pEp,void (*pMonitorFxn)(LPEXCEPTION_POINTERS,void*))
{
CONTEXT* ctx = pEp->ContextRecord;
ULONG_PTR* info = pEp->ExceptionRecord->Exceptioninformation;
UINT_PTR addr = info[1];
DWORD dummy;
switch (pEp->ExceptionRecord->ExceptionCode)
{
case STATUS_ACCESS_VIOLATION:
// If it's a write to read-only memory,// to the pages that we made read-only...
if ((info[0] == 1) &&
(addr >= AlignedRangeStart) &&
(addr < AlignedRangeStart + AlignedRangeSize))
{
// Restore the original setting
// Make the page range read-write
VirtualProtect((LPVOID)AlignedRangeStart,&dummy);
// If the write is exactly within the requested range,// call our monitoring callback function
if ((addr >= RangeStart) && (addr < RangeStart + RangeSize))
{
pMonitorFxn(pEp,(void*)addr);
}
// Set FLAGS.TF to trigger a single-step trap after the
// next instruction,which is the instruction that has caused
// this page fault (AKA access violation)
ctx->EFlags |= (1 << 8);
// Execute the faulted instruction again
return EXCEPTION_CONTINUE_EXECUTION;
}
// Don't handle other AVs
goto ContinueSearch;
case STATUS_SINGLE_STEP:
// The instruction that caused the page fault
// has Now succeeded writing to memory.
// Make the page range read-only again
VirtualProtect((LPVOID)AlignedRangeStart,&dummy);
// Continue executing as usual until the next page fault
return EXCEPTION_CONTINUE_EXECUTION;
default:
ContinueSearch:
// Don't handle other exceptions
return EXCEPTION_CONTINUE_SEARCH;
}
}
// We'll monitor writes to blah[1].
// volatile is to ensure the memory writes aren't
// optimized away by the compiler.
volatile int blah[3] = { 3,2,1 };
void WritetoMonitoredMemory(void)
{
blah[0] = 5;
blah[0] = 6;
blah[0] = 7;
blah[0] = 8;
blah[1] = 1;
blah[1] = 2;
blah[1] = 3;
blah[1] = 4;
blah[2] = 10;
blah[2] = 20;
blah[2] = 30;
blah[2] = 40;
}
// This pointer is an attempt to ensure that the function's code isn't
// inlined. We want to see it's this function's code that modifies the
// monitored memory.
void (* volatile pWritetoMonitoredMemory)(void) = &WritetoMonitoredMemory;
void WriteMonitor(LPEXCEPTION_POINTERS pEp,void* Mem)
{
printf("We're about to write to 0x%X from EIP=0x%X...\n",Mem,pEp->ContextRecord->Eip);
}
int main(void)
{
printf("&WritetoMonitoredMemory() = 0x%X\n",pWritetoMonitoredMemory);
printf("&blah[1] = 0x%X\n",&blah[1]);
printf("\nstart\n\n");
__try
{
printf("blah[0] = %d\n",blah[0]);
printf("blah[1] = %d\n",blah[1]);
printf("blah[2] = %d\n",blah[2]);
// Start monitoring memory writes
MonitorRange((void*)&blah[1],sizeof(blah[1]));
// Write to monitored memory
pWritetoMonitoredMemory();
// Stop monitoring memory writes
MonitorRange(NULL,0);
printf("blah[0] = %d\n",blah[2]);
}
__except(ExceptionFilter(GetExceptioninformation(),&WriteMonitor)) // write monitor callback function
{
// never executed
}
printf("\nstop\n");
return 0;
}
输出(在Windows XP上运行):
&WritetoMonitoredMemory() = 0x401179 &blah[1] = 0x4080DC start blah[0] = 3 blah[1] = 2 blah[2] = 1 We're about to write to 0x4080DC from EIP=0x4011AB... We're about to write to 0x4080DC from EIP=0x4011B5... We're about to write to 0x4080DC from EIP=0x4011BF... We're about to write to 0x4080DC from EIP=0x4011C9... blah[0] = 8 blah[1] = 4 blah[2] = 40 stop
这就是主意.
您可能需要更改内容以使代码在多个线程中正常工作,使其与其他SEH代码(如果有)一起使用,具有C异常(如果适用).
当然,如果你真的想要它,你可以在写完成后调用write监视回调函数.为此,您需要在某处保存STATUS_ACCESS_VIOLATION案例中的内存地址(TLS?),以便STATUS_SINGLE_STEP案例可以在以后获取并传递给该函数.