我想捕获特定内存范围的内存写入,并使用写入的内存位置的地址调用函数.优选地,在已经发生对存储器的写入之后.
我知道这可以通过操作系统来填充页表条目来完成.但是,如何在想要这样做的应用程序中完成类似的操作呢?
好吧,你可以这样做:
// 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案例可以在以后获取并传递给该函数.