한 가지만하는 프록시 DLL 라이브러리를 사용하여 장치에 직접 작성하는 소스 코드가없는 응용 프로그램이 있는데 내 보낸 함수 "GetDataPointer" . 이 라이브러리를 대체하고 각 수신 메모리에 대한 쓰기 간 변경 사항을보고 싶습니다. 메모리 크기는 페이지 크기 (4096)의 배수입니다. 내가이 방법을 시도했습니다보호 된 페이지의 사용을 모니터링하기 위해 구조화 된 예외 처리 (SEH) 사용하기
: DllMain에서
- 은 데이터를 비교, 페이지 가드를 제거 VirtualAlloc를 통해 GetDataPointer의 ALLOC 메모리에 SetUnhandledExceptionFilter
- 를 통해 예외 핸들러를 설정 예외 처리기에서 가드 페이지
- 설정 이전 복사본으로 페이지를 보호하십시오.
이 응용 프로그램은이 메모리 포인터에 쓰는 스레드를 사용합니다. 응용 프로그램이이 메모리 포인터에 주 스레드에서 세 번 씁니다.이 포인터에 세 번 쓰는 다른 스레드를 생성합니다. 스레드가 없어도 스레드가 있으면 페이지 보호 기능이 누락되어 예외를 발생시키지 않고 메모리 포인터에 직접 씁니다. 따라서 예외 처리기는 메모리에 6 번 쓰기를 캡처해야하지만 페이지 보호 기능이 없기 때문에 그 수가 더 적습니다. 안타깝게도 필자는이 방법으로해야하며 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 예, 올바르게 작동하려면 모든 단일 메모리 쓰기를 알아야합니다. 메모리 덩어리는 구조체로 해석 될 수 있으며 변경된 요소를 알아야합니다. 예를 들어, 첫 번째 쓰기는 4 개의 정적 오프셋 중 하나에서 작성된 명령이 될 것이고 다음에 잡힌 쓰기는 정적 오프셋이 아닌 params가 될 것이므로 변경된 내용과 그 값이 무엇인지 알아야합니다. 더구나 명령에 대한 매개 변수는 한 번에 두 번 같은 오프셋으로 겹쳐 쓸 수 있습니다. –
SetUnhandledExceptionFilter에 대한 문서를 읽는 방법은 잡히지 않은 예외 만 트래핑하는 것입니다. 따라서 응용 프로그램의 스레드가 먼저 예외를 포착하면이를 볼 수 없습니다. – Brad