如何用写入地址捕捉内存写入和调用函数

我想抓住内存写入特定的内存范围,并调用正在写入内存位置的地址的函数。 优选地,在写入存储器之后已经发生。

我知道这可以由操作系统通过页表条目来完成。 但是,如何从一个想要做到这一点的应用程序中实现类似的function呢?

Solutions Collecting From Web of "如何用写入地址捕捉内存写入和调用函数"

那么,你可以做这样的事情:

// 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, AlignedRangeSize, 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, AlignedRangeSize, PAGE_READWRITE, &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, AlignedRangeSize, PAGE_READONLY, &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[0]); printf("blah[1] = %d\n", blah[1]); printf("blah[2] = %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 ++异常(如果适用的话)一起工作。

当然,如果你真的想要的话,可以在写入完成之后调用写监听回调函数。 为此,您需要将STATUS_ACCESS_VIOLATION情况下的内存地址( TLS ?)保存起来,以便STATUS_SINGLE_STEP情况稍后可以提取并传递给函数。

或者,您也可以使用页面卫士 ,这些卫士在访问时同样会导致异常,但会被系统自动清除(一次性)。 这些应该也适用于只读内存。

在你的情况下,你仍然需要单步陷阱技巧来重新启用页面防护。

例如使用vkTrace ,也可能使用OpenGL / Vulkan持久映射缓冲区驱动程序实现。 vkTrace源代码也展示了如何在Linux和Android上做这种事情。