Доброго времени суток!
Столкнулся со странным поведением объекта "событие" при установленном хуке WH_CBT в момент вставки флешки в USB порт.
Глюк проявляется только при компиляции в VC6+SP6 и никак не хочет проявляться в Visual Studio 2008.
Итак , код:
#pragma data_seg(".shared")
HHOOK g_hook=NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.shared,RWS")
HINSTANCE g_hInstance;
LRESULT CALLBACK HookFunc(int nCode, WPARAM wParam,LPARAM lParam){
return CallNextHookEx(g_hook, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void InitHook(){
g_hook=SetWindowsHookEx(WH_CBT,HookFunc,g_hInstance,0);
MSG msg;
int ret;
// ставим цикл обработки сообщений
while((ret=GetMessage(&msg,0,0,0))!=0){
if(ret==-1){
} else {
DispatchMessage(&msg);
}
}
}
unsigned WINAPI MyThread(void *p){
HANDLE hEvent=CreateEvent(NULL,false,false,"myevent");
DWORD dwErr=GetLastError();
if(dwErr==ERROR_ALREADY_EXISTS){ // если убрать эту ветку, то
SetEvent(hEvent); // глюка
Sleep(3000); // не происходит
}
WaitForSingleObject(hEvent,INFINITE); // ожидаем бесконечно
CloseHandle(hEvent);
ExitProcess(0); // завершаемся
return 0;
}
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
if(ul_reason_for_call==DLL_PROCESS_ATTACH){
g_hInstance=(HINSTANCE)hModule;
char szFileName[MAX_PATH];
GetModuleFileName(NULL,szFileName,MAX_PATH);
strlwr(szFileName);
BOOL bOk=false;
if(strstr(szFileName,"notepad")!=NULL || strstr(szFileName,"rundll32")!=NULL) bOk=true;
if(bOk){
UINT uid;
HANDLE hTh=(HANDLE)_beginthreadex(NULL,0,MyThread,NULL,0,&uid);
CloseHandle(hTh);
}
return bOk;
}
else
if(ul_reason_for_call==DLL_PROCESS_DETACH){
UnhookWindowsHookEx(g_hook);
}
return TRUE;
}
Вкратце суть приложения:
— это длл-ка, которая запускается через rundll32.exe и устанавливает хук WH_CBT хук;
— чтобы процесс rundll32 не завершился, устанавливается цикл приема сообщений(GetMessage\DispatchMessage);
— из DllMain порождается поток в котором создается некое событие, а после ставится бесконечное ожидание на нем;
— при запуске блокнота, в процесс notepad.exe внедрится наша длл.
Обратите внимание на эту часть кода функции потока:
DWORD dwErr=GetLastError();
if(dwErr==ERROR_ALREADY_EXISTS){ // если убрать эту ветку, то
SetEvent(hEvent); // глюка
Sleep(3000); // не происходит
}
Не важно, какую смысловую нагрузку несет данный код. Важно то, что по логике при запуске единственного экземпляра rundll32 (событие с именем myevent больше нигде не открывается и не создается) функция GetLastError никак не может вернуть ERROR_ALREADY_EXISTS, поэтому два вызова — SetEvent и Sleep никогда не выполнятся. Так и есть, эти два вызова не происходят, выполнение сразу переходит на WaitForSingleObject. Здесь идет бесконечное ожидание. И вот ! Как только мы вставляем флешку в USB порт, каким-то невообразимым образом, событие hEvent стает сигнальным и вызов WaitForSingleObject завершается с кодом WAIT_OBJECT_0.
В сущности, смысл выше приведенного участка прост: если запустили еще один rundll32, прогрузив туда нашу длл, то нужно завершить предыдущий — там ведь идет ожидание события hEvent, которое стает сигнальным, если GetLastError возвращает ERROR_ALREADY_EXISTS. После, уже в новом экземпляре rundll32 будет происходить бесконечное ожидание на событии hEvent c именем myevent до того момента, как кто-то снова не запустит rundll32, прогрузив нашу длл-ку — предыдущий завершится. И так далее.
Самое забавное, что глюка при вставке флешки не происходит, если код откомпилировать в VS2008. Попробуйте компильнуть код в VC6 (у меня sp6) и после запустить длл-ку из командной строки: rundll32.exe c:\project\some\debug\prg.dll, InitHook
И вставьте флешку и увидите, что процесс завершится.
Как это пояснить с позиции логики, пока не пойму. Я так понимаю, что всему виной цикл обработки сообщений GetMessage\DispatchMessage, ведь именно GetMessage принимает событие с кодом 0x219 (WM_DEVICECHANGE) и параметром 7 (DBT_DEVNODES_CHANGED) в момент вставки флешки. Но выхода-то из цикла не происходит, GetMessage как бы даже не успевает возвратиться даже, как WaitForSingleObject завершает ожидание (событие hEvent стало сигнальным). Кстати если заменить цикл обработки
while((ret=GetMessage(&msg,0,0,0))!=0){
if(ret==-1){
} else {
DispatchMessage(&msg);
}
}
на
Sleep(INFINITE);
Глюк будет тот же — при вставке флешки событие стает сигнальным и все тут!
А теперь фокус.
Убираем эту часть кода:
DWORD dwErr=GetLastError();
if(dwErr==ERROR_ALREADY_EXISTS){ // если убрать эту ветку, то
SetEvent(hEvent); // глюка
Sleep(3000); // не происходит
}
И вуаля! Глюк исчезает. Получается, будто какой-то невидимый фантомный код запускает эту ветку постфактум (или функцию потока заново) — и, так как событие myevent уже есть, GetLastError возвратит ERROR_ALREADY_EXISTS и вызов SetEvent(hEvent) cделает свое черное дело. Кстати , если убрать этот вызов SetEvent(hEvent), то глюк исчезает. Также , если убрать хуки, то глюк исчезнет. Но как? Почему? В чем конфликт? Почему 2008 студия не подвержена этому?
Заранее благодарен за ответы!
G>Доброго времени суток!
G>Столкнулся со странным поведением объекта "событие" при установленном хуке WH_CBT в момент вставки флешки в USB порт.
G>Глюк проявляется только при компиляции в VC6+SP6 и никак не хочет проявляться в Visual Studio 2008.
G>И вуаля! Глюк исчезает. Получается, будто какой-то невидимый фантомный код запускает эту ветку постфактум (или функцию потока заново) — и, так как событие myevent уже есть, GetLastError возвратит ERROR_ALREADY_EXISTS и вызов SetEvent(hEvent) cделает свое черное дело. Кстати , если убрать этот вызов SetEvent(hEvent), то глюк исчезает. Также , если убрать хуки, то глюк исчезнет. Но как? Почему? В чем конфликт? Почему 2008 студия не подвержена этому?
Сравните зависимости и наличие манифеста. Добейтесь одинаковых зависимостей (т.е. избавтесь в хуке от рантайма), уберите добавление манифеста в 2008-й. Сравните зависимости и наличие манифеста. Убедитесь, что различий нет. Добавьте трассировку старта и завершения потока в файл с указанием ID процесса и трасссировку функции инициализации.
Воспроизводите.
PS Вот этот код доставляет

:
if(ul_reason_for_call==DLL_PROCESS_DETACH){
UnhookWindowsHookEx(g_hook);