【发布时间】:2016-08-24 18:05:22
【问题描述】:
我有一个没有源代码的应用程序,它使用代理 DLL 库直接写入设备,该库只做一件事,通过调用导出的函数“GetDataPointer”返回内存指针。我想替换这个库,看看每次写入接收到的内存之间的变化。内存大小是页面大小 (4096) 的倍数。
我试过这样:
- 在 DllMain 中通过 SetUnhandledExceptionFilter 设置异常处理程序
- 在GetDataPointer中通过VirtualAlloc分配内存,设置保护页
- 在异常处理程序中删除页面保护,将数据与以前的副本进行比较,使页面受到保护
一切都会好的,但是这个应用程序使用了写入这个内存指针的线程。假设应用程序在主线程中向这个内存指针写入了 3 次,在这之间产生了其他线程,对这个指针进行了 3 次写入。虽然没有线程这将起作用,但使用线程有时它确实会错过页面保护,因此它直接写入内存指针而不会引发异常。所以异常处理程序应该捕获对内存的六次写入,但由于缺少页面保护,这个数字较低。可悲的是,我需要这样做并且我需要 100% 的准确度,因为我计划为更晦涩的设备(显卡)制作一个晦涩的 API 包装器,以便在现代系统中使用此应用程序。
我尝试在每个附加线程中使用全局临界区并为 SEH 设置异常处理程序,但问题仍然存在。
是否有可能解决此问题?还是有更好的方法来实现这一点?如果有人需要代码来解释它是如何工作的,我可以准备代码来模拟我的工作环境(应用程序和库)。
提前谢谢你。
更新
不加锁部分的源代码(伪代码)是这样的(为这个帖子匆忙写的,可能不完整):
应用:
extern "C" __declspec(dllimport) void* GetDataPointer(int size);
extern "C" __declspec(dllimport) int GetCallCount();
#define THREAD_COUNT 32
DWORD WINAPI ThreadEntryPoint(void* param) {
unsigned int* data = (unsigned int*)param;
data[0] = 0xEEFF0011;
return 0;
}
int main(int argc, char* argv[]) {
unsigned int* data = (unsigned int*)GetDataPointer();
std::vector<HANDLE> threads;
data[0] = 0xAABBCCDD;
for(int i = 0; i<THREAD_COUNT; i++) {
DWORD threadID;
HANDLE hThread = CreateThread(NULL,0,ThreadEntryPoint,(void*)data,0,&threadID);
threads.push_back(hThread);
}
for(int i = 0; i<threads.size(); i++)
WaitForSingleObject(threads.at(i),INFINITE);
printf("Should be %i calls, was %i.\n",(THREAD_COUNT+1),GetCallCount());
return 0;
}
图书馆:
#define DATA_SIZE 4096
void* data;
int callCount;
LONG WINAPI ExceptionHandler(LPEXCEPTION_POINTERS ExceptionInfo) {
LONG ret = EXCEPTION_CONTINUE_SEARCH;
switch(ExceptionInfo->ExceptionRecord->ExceptionCode) {
case STATUS_GUARD_PAGE_VIOLATION: {
ExceptionInfo->ContextRecord->EFlags |= 0x100;
ret = EXCEPTION_CONTINUE_EXECUTION;
break;
}
case EXCEPTION_SINGLE_STEP: {
DWORD old;
callCount++;
VirtualProtect(data,DATA_SIZE,PAGE_READWRITE,&old);
// Find what was changed in data...
VirtualProtect(data,DATA_SIZE,PAGE_GUARD | PAGE_READWRITE,&old);
ret = EXCEPTION_CONTINUE_EXECUTION;
break;
}
}
return ret;
}
extern "C" void* __declspec(dllexport) GetDataPointer() {
data = (void*)VirtualAlloc((PVOID)data,DATA_SIZE,MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE);
memset(data,0,DATA_SIZE);
DWORD old;
VirtualProtect(data,DATA_SIZE,PAGE_GUARD | PAGE_READWRITE,&old);
return data;
}
extern "C" int __declspec(dllexport) GetCallCount() {
return callCount;
}
extern "C" BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
switch(fdwReason) {
case DLL_PROCESS_ATTACH: {
data = NULL;
callCount = 0;
SetUnhandledExceptionFilter(ExceptionHandler);
break;
}
case DLL_PROCESS_DETACH: {
if(data)
VirtualFree(data,data_size,MEM_RELEASE | MEM_DECOMMIT);
break;
}
case DLL_THREAD_ATTACH: {
SetUnhandledExceptionFilter(ExceptionHandler);
break;
}
case DLL_THREAD_DETACH: {
break;
}
}
return TRUE;
}
我希望这会更清楚。
【问题讨论】:
-
每次写入都需要一个例外以使其正常工作??在您执行任何其他操作之前分析此代码。
-
@hans-passant 是的,我需要知道每一次内存写入才能使其正常工作。内存块可以解释为一个结构,我需要知道哪些元素被改变了。例如,第一次写入将是在四个静态偏移量之一处写入的命令,而下一次捕获的写入将是不是静态偏移量的参数,因此我需要知道更改了什么以及它具有什么值。更重要的是,命令的参数可以连续两次以相同的偏移量写入(覆盖)。
-
我阅读 SetUnhandledExceptionFilter 文档的方式是它只捕获未捕获的异常。因此,如果应用程序中的线程首先捕获异常,您将看不到它。