Прибегает значит на днях сотрудник тестлаба с криками "Шеф, все пропало, все пропало! Гипс снимают, клиент уезжает!" (с). Тьфу ты - "Все зависло. Ничего не работает".
Во время первичного осмотра на месте происшествия картина была следующая: курсор мыши при наведении на главную форму приложения принимает вид "стрелка с кружочком" (дело было на Висте. На ХР это бы выглядело как "стрелка с песочными часами"). И при этом форма не была "зависшей" - контролы кликабельны, сама форма таскабельна и т.п.
Чтобы покурить такую ситуацию в спокойной обстановке и не отвлекать доблестный тестлаб от решения проблем насущных (точнее - от поиска таковых :)) был снят дамп с "зависшего" процесса [1].
Главный поток не завис (на что, впрочем, указывала работоспособность главной формы на месте происшествия):
ntdll.dll!KiFastSystemCallRet USER32.dll!NtUserWaitMessage+0xc vcl100.bpl!FormsTApplicationHandleMessage$qqrv+0x1c agent.exe!agent.agent+0x172 kernel32.dll!BaseProcessStart+0x23
Анализ стеков остальных потоков тоже не выявил аномалий. А может это и не зависание вовсе (мелькнула мысль)? Может это просто кто-то установил курсор мыши и забыл вернуть в нормальное состояние? Ответить на этот вопрос можно. Для этого надо определить, чему равно свойство Cursor глобального объекта Screen (именно оно определяет, как выглядит курсор мыши при наведении на окна приложения). Для этого есть все, что нужно: дамп, немного времени и кофе :)
Итак, приступим.
Для начало необходимо найти ссылку на объект Screen. Как известно, Screen - глобальный объект (объявлен в интерфейсной части модуля Forms). А т.к. приложение использует пакеты, то искать ссылку на него проще среди символов VCL.
При наличии отсутствия полноценных символов VCL, для него используются export symbols. Что нас вполне устраивает, т.к доступ ко всему, что объявлено в интерфейсных частях модулей пакета, осуществляется через таблицу экспорта.
[Лирическое отступление]
Эх, как бы сильно упростилась бы жизнь, имея возможность создавать private symbols... Ну да ладно - попробуем сварить кашу из того, что имеем.
Ищем "Screen" в символах VCL (часть вывода опущена):
0:000> x vcl100!*screen*
2013a758 vcl100!ControlsTControlClientToScreen$qqrrx12TypesTPoint (<no parameter info>)
2013a808 vcl100!ControlsTControlScreenToClient$qqrrx12TypesTPoint (<no parameter info>)
20155f48 vcl100!FormsTScreen (<no parameter info>)
20155f98 vcl100!$xp$13FormsTScreen (<no parameter info>)
2015f6bc vcl100!FormsTScreen$bctr$qqrp18ClassesTComponent (<no parameter info>)
...
201b02a0 vcl100!FormsTScreenGetPrimaryMonitor$qqrv (<no parameter info>)
201f01b0 vcl100!FormsScreen (<no parameter info>)
vcl100!FormsScreen очень сильно похоже на то, что мы ищем. Проверим эту гипотезу (учитываем, что символ должен указывать на адрес переменной, которая хранит ссылку на объект Screen):
0:000> !object poi(vcl100!FormsScreen) 02056690 "TScreen"
!object - это команда из extension-а dext.dll для WinDbg [2]. Она проверяет, является ли указанный адрес ссылкой на объект.
Ура, товарищи, - интуиция нас не подвела! :)
Теперь остается определить значение поля FCursor найденного объекта TScreen (а для этого необходимо определить смещение этого поля относительно адреса объекта).
Установка нового значения свойства Cursor осуществляется через метод SetCursor класса TScreen:
procedure TScreen.SetCursor(Value: TCursor); var P: TPoint; Handle: HWND; Code: Longint; begin if Value <> Cursor then begin FCursor := Value; if Value = crDefault then begin { Reset the cursor to the default by sending a WM_SETCURSOR to the window under the cursor } GetCursorPos(P); Handle := WindowFromPoint(P); if (Handle <> 0) and (GetWindowThreadProcessId(Handle, nil) = GetCurrentThreadId) then begin Code := SendMessage(Handle, WM_NCHITTEST, 0, LongInt(PointToSmallPoint(P))); SendMessage(Handle, WM_SETCURSOR, WPARAM(Handle), MakeLong(Code, WM_MOUSEMOVE)); Exit; end; end; Windows.SetCursor(Cursors[Value]); end; Inc(FCursorCount); end;
Как видно, в начале производится сравнение нового значение со старым и, если они не равны, сохранение нового значение в поле FCursor.
Теперь найдем адрес метода TScreen.SetCursor:
0:000> x vcl100!*SetCursor*
20113ab8 vcl100!ExtctrlsTHeaderWMSetCursor$qqrr21MessagesTWMSetCursor
2013af4c vcl100!ControlsTControlSetCursor$qqr16ControlsTCursor
201415e0 vcl100!ControlsTWinControlWMSetCursor$qqrr21MessagesTWMSetCursor
201487bc vcl100!ControlsTMouseSetCursorPos$qqrrx12TypesTPoint
2015fe9c vcl100!FormsTScreenSetCursor$qqr16ControlsTCursor
2015ff38 vcl100!FormsTScreenSetCursors$qqriui
20192ca0 vcl100!ComctrlsTCustomRichEditWMSetCursor$qqrr21MessagesTWMSetCursor
201a457c vcl100!ComctrlsTCoolBarWMSetCursor$qqrr21MessagesTWMSetCursor
201a9a0c vcl100!MaskTCustomMaskEditSetCursor$qqri
201b2fb0 vcl100!GridsTCustomGridWMSetCursor$qqrr21MessagesTWMSetCursor
201b5f90 vcl100!GridsTInplaceEditListWMSetCursor$qqrr21MessagesTWMSetCursor
Дизассемблируем начало и сравниваем с Delphi-кодом:
0:000> u 2015fe9c
vcl100!FormsTScreenSetCursor$qqr16ControlsTCursor:
; Параметры
; eax - Self
; edx - Value: TCursor
2015fe9c 53 push ebx
2015fe9d 56 push esi
2015fe9e 57 push edi
2015fe9f 55 push ebp
2015fea0 83c4f4 add esp,0FFFFFFF4h
2015fea3 8bf2 mov esi,edx ; Value -> esi
2015fea5 8bd8 mov ebx,eax ; Self -> ebx
2015fea7 663b7344 cmp si,word ptr [ebx+44h] ; if Value <> Cursor then
...
Таким образом, видно, что поле FCursor занимает в памяти 2 байта и располагается по смещению 0x44 относительно адреса объекта. Теперь можно посмотреть, чему оно равно:
0:000> dw poi(vcl100!FormsScreen)+0x44 l1
020566d4 ffed
А т.к. тип TCursor в модуле Controls объявлет так:
type TCursor = -32768..32767; ...
То 0xffed = TCursor(-19) = crAppStart. И "зависание" на самом деле оказалось вовсе не зависанием.
Ну а дальше все просто - поиск по исходникам, кто и где в использует такой код:
Screen.Cursor := crAppStart
Ссылки
[1] Как создать дамп процесса
[2] Delphi WinDbg Extension Dll
Комментариев нет:
Отправить комментарий