Проверка и обнаружение зависших приложений

Иногда для работы требуется определение зависших приложений, окна которых не отвечают на сообщения. Для этого есть два способа. Первый - официально документированный, через функцию SendMessageTimeOut. Особенность ее работы заключается в том, что после отправки сообщения окну она ждет ответ заданное время, и, если ответа от приложения не последовало, то возвращает FALSE. Вот пример использования функции. Нужные константы, как обычно, в FASM не определены, пришлось брать их из других источников.
        ...
        ; Определить таймаут 50 миллисекунд
        TIMEOUT = 50
        ; Определить константу SMTO_ABORTIFHUNG
        SMTO_ABORTIFHUNG = 2
        ; hwnd - хэндл проверяемого окна
        invoke  SendMessageTimeout,[hwnd],NULL,0,0,SMTO_ABORTIFHUNG,TIMEOUT,NULL
        ; Если вернулся 0, то приложение "висит"
        or      eax,eax
        jz      app_hung_up
        ...

Минус использования этой функции в том, что при частой проверке большого количества окон зависших приложений, каждый раз будет отрабатываться таймаут для каждого такого окна, что суммарно может дать снижение производительности вашего приложения. Но повторюсь, Microsoft официально рекомендует к использованию именно эту функцию.

Второй способ - недокументированный. В Windows есть особые функции, которые используются, например, во встроенном Диспетчере задач. Для операционных систем Windows 2000 и выше это IsHungAppWindow, а для систем Windows 9x и Windows ME - функция IsHungThread (она вообще никак не документирована на MSDN). Разница между ними в том, что IsHungAppWindow работает с хэндлом окна, а IsHungThread с хэндлом потока окна. Поскольку в системной библиотеке user32.dll одновменно может быть только одна из этих функций, то включать их в секцию импорта нельзя, иначе ваше приложение вылетит с ошибкой. Также есть информация, что они вообще недоступны через экспорт. Стало быть воспользуемся штатными функциями API GetModuleHandle и GetProcAddress, но предварительно в секции данных определим нужные нам переменные:
section '.data' data readable writeable
        ...
hUser   dd ?    ; Хэндл библиотеки user32.dll
hIHW    dd ?    ; Адрес функции IsHungAppWindow (Win2k и выше)
hIHT    dd ?    ; Адрес функции IsHungThread (Win9x)
 
strUser db      'user32.dll',0
strIHW  db      'IsHungAppWindow',0
strIHT  db      'IsHungThread',0
        ...
Перед использованием надо получить все необходимые адреса функций:
Code (Assembler) : Убрать нумерацию
section '.code' code readable executable
        ...
        mov     [hIHW],0          ; Инициализировать переменные
        mov     [hIHT],0
 
        ; Получить хэндл библиотеки user32.dll
        invoke  GetModuleHandle,strUser
        mov     [hUser],eax
 
chk_WinNT:
        ; Попытаться получить адрес функции IsHungAppWindow
        invoke  GetProcAddress,[hUser],strIHW
        or      eax,eax
        jz      chk_Win9X
        mov     [hIHW],eax        ; Сохранить адрес функции
        jmp     @f
 
chk_Win9X:
        ; Попытаться получить адрес функции IsHungThread
        invoke  GetProcAddress,[hUser],strIHT
        or      eax,eax
        jz      loc_fatal_error
        mov     [hIHT],eax        ; Сохранить адрес функции
        jmp     @f
 
loc_fatal_error:
        ; По какой-то немыслимой причине не найдено ни одной функции 
        ...
@@:
        ; Продолжить обработку
        ...

И теперь сама функция проверки окна приложения на предмет его зависания. Для большей универсальности я дополнил ее кодом первого документированного способа, он будет вызываться в случае, когда не определена ни одна из функций второго способа. Если время выполнения функции имеет критическое значение, например, при частом обновлении большого списка процессов или окон, то предпочтительнее воспользоваться именно этим ее вариантом.
;----------------------------------------------------------------
; Процедура проверки окна на зависание его родительского приложения
; Предварительно должны быть определены переменные hIHW или hIHT
; с адресами функций IsHungAppWindow или IsHungThread
;
; На входе:
;   hwnd - хэндл проверяемого окна
; На выходе:
;   EAX = 0 - приложение работает или такое окно не найдено
;   EAX = 1 - приложение зависло и не отвечает
;----------------------------------------------------------------
proc IsAppHung hwnd:dword
        local   tmp:DWORD     ; Временная переменная
 
        pusha
 
        ; Такое окно есть?
        invoke  IsWindow,[hwnd]
        or      eax,eax
        jz      .loc_ret
.chk_WinNT:
        ; Адрес функции IsHungAppWindow известен?
        cmp     [hIHW],0
        je      .chk_Win9x
        stdcall [hIHW],[hwnd]
        jmp     .loc_ret
.chk_Win9x:
        ; Адрес функции IsHungThread известен?
        cmp     [hIHT],0
        je      .chk_All
        invoke  GetWindowThreadProcessId,[hwnd],NULL
        stdcall [hIHT],eax
        jmp     .loc_ret
.chk_All:
        ; Определить таймаут 50 миллисекунд
        TIMEOUT = 50
        ; Определить константу SMTO_ABORTIFHUNG
        SMTO_ABORTIFHUNG = 2
        invoke  SendMessageTimeout,[hwnd],NULL,0,0,SMTO_ABORTIFHUNG,TIMEOUT,NULL
        sub     eax,1
        neg     eax
.loc_ret:
        ; Сохранить значение
        mov     [tmp],eax
 
        ; Восстановить регистры и сохраненное значение
        popa
        mov     eax,[tmp]
 
        ret
endp

Если очень частый вызов проверки не требуется, то код инициализации можно включить в саму функцию IsAppHung. В результате получится универсальная и самодостаточная функция, не требующая для работы вообще никаких сторонних переменных.
;----------------------------------------------------------------
; Процедура проверки окна на зависание его родительского приложения
;
; На входе:
;   hwnd - хэндл проверяемого окна
; На выходе:
;   EAX = 0 - приложение работает или такое окно не найдено
;   EAX = 1 - приложение зависло и не отвечает
;----------------------------------------------------------------
proc IsAppHung hwnd:dword
        local   tmp:DWORD     ; Временная переменная
 
        pusha
 
        ; Такое окно есть?
        invoke  IsWindow,[hwnd]
        or      eax,eax
        jz      .loc_ret
 
        ; Получить хэндл библиотеки user32.dll
        invoke  GetModuleHandle,strUser
        mov     [tmp],eax
 
.chk_WinNT:
        invoke  GetProcAddress,[tmp],strIHW
        or      eax,eax
        jz      .chk_Win9x
        stdcall eax,[hwnd]
        jmp     .loc_ret
.chk_Win9x:
        invoke  GetProcAddress,[tmp],strIHT
        or      eax,eax
        jz      .chk_All
        mov     [tmp],eax
        invoke  GetWindowThreadProcessId,[hwnd],NULL
        stdcall [tmp],eax
        jmp     .loc_ret
.chk_All:
        ; Определить таймаут 50 миллисекунд
        TIMEOUT = 50
        ; Определить константу SMTO_ABORTIFHUNG
        SMTO_ABORTIFHUNG = 2
        invoke  SendMessageTimeout,[hwnd],NULL,0,0,SMTO_ABORTIFHUNG,TIMEOUT,NULL
        sub     eax,1
        neg     eax
.loc_ret:
        ; Сохранить значение
        mov     [tmp],eax
 
        ; Восстановить регистры и сохраненное значение
        popa
        mov     eax,[tmp]
 
        ret
 
strUser db      'user32.dll',0
strIHW  db      'IsHungAppWindow',0
strIHT  db      'IsHungThread',0
 
endp

По результатам полевых испытаний функции выявлены некоторые интересные факты. В частности выполнялся полный перебор всех top-level окон с проверкой их на предмет зависания. В процессе перебора на рабочей системе Windows XP были пойманы два системных окна с неизменными заголовками "M" и "Default IME", которые определялись как зависшие. Однако это не так, просто эти окна принадлежат к критическим системным процессам. Поэтому если ваша программа также выполняет проверку всех окон, то эти два окна в обработчике зависших процессов надо будет просто пропускать после проверки их заголовка.

Для имитации зависшего приложения можно воспользоваться программой Bad Application или программой hangup.exe из архива. В архиве примеры программ для обнаружения зависших приложений всеми тремя способами и программа-имитатор зависшего приложения. Имитаторы зависших приложений придется снимать только через менеджер процессов.
Теги:
приложения
Добавлено: 10 Апреля 2018 19:59:01 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...