Сигнализация события при вставке флешки
От: groomy  
Дата: 29.06.10 12:32
Оценка:
Доброго времени суток!
Столкнулся со странным поведением объекта "событие" при установленном хуке 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 студия не подвержена этому?
Заранее благодарен за ответы!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.