Зависает процесс

Метки:  , , , ,

Сегодня мы поговорим о проблеме достаточно широко распространенной. Темой нашего разговора будут ситуации, когда в штатно функционирующей системе внезапно зависает процесс. Данная проблема, как мне кажется, является знакомой для каждого технического специалиста и, в то же время, крайне неудобной, поскольку вероятных проблем зависания может быть множество. Зависания процесса случаются с завидной регулярностью на широком круге клиентских систем Microsoft, работающих на базе разнообразного аппаратного обеспечения. Никто не застрахован от ошибок, где-то их меньше, где то больше, но присутствуют они абсолютно везде. В серверных конфигурациях, где набор рабочего ПО жестко ограничен и, как правило, достаточно хорошо оттестирован, ошибки встречаются значительно реже. Противоположностью этого являются клиентские операционные системы, где политики требования к программному обеспечению более мягкие, да и спектр ПО, работающего в системе шире. Зачастую возникновение ошибки в коде программы ведет к сбою, который выражается в "падении" (внезапном закрытии) программы, либо зависании. Ну и самый, пожалуй, неприятный вид проблем, это когда процесс зависает либо падает внезапно и с довольно высокой регулярностью.
Давайте немного отклонимся от основной линии повествования и потеоретизируем на тему зависания процесса. Какова её природа? Почему каким причинам программа может зависнуть:

  • Внутренняя причина: Ошибка разработчика/модификатора, допустившего ошибку в коде исполняемого модуля.
  • Внешняя причина: Бесконечное ожидание внешнего события, являющееся причиной аппаратного сбоя либо исчерпания ресурсов системы; ошибка стороннего исполняемого модуля, влияющего на
    процесс исполнения кода основной программы (вирус/антивирус/другое ПО, имеющее подобный функционал).

Пожалуй что все причины я сейчас перечислить не смогу, да оно и не надо. К чему я завел весь этот разговор? Дело в том, что недавно ко мне прилетела одна интересная заявка. Обращался пользователь, у которого периодически вис редактор Winword. Вис он абсолютно на разных стадиях, при редактировании файлов, содержащих большой объем, при создании новых файлов, при изменении маленьких файлов. Одним словом, никаких закономерностей в происходящем выявить пользователю не удавалось. Зависает процесс? Давайте принудительно завершим его и продолжим работу :) Да, действительно, процесс можно было просто убить, если бы это была "штучная" проблема, поскольку никто не застрахован от багов, и в пакетах Microsoft они время от времени всплывают в виде редчайших внезапных сбоев фактически "на отлаженных системах". Но неприятная отличительная особенность данного сбоя была в том, что сбой не был "редким", а происходил с завидной регулярностью, фактически уже на протяжении нескольких дней и по несколько в период рабочего времени.
Запустил Монитор ресурсов (resmon), увидел пустую цепочку ожидания. Значит, проблема, с большой вероятностью, все же внутри процесса, поэтому было принято решение впервые провести отладку зависшего процесса.

Настройка инструментов отладки

Установка Debugging Tools for Windows

Перед тем как приступить непосредственно к отладке зависшего процесса, нам потребуется установить инструментарий под названием Debugging Tools for Windows (DTW), который входит в состав Windows SDK. Процесс установки подробно описан в статье Установка Debugging Tools for Windows, Не упускайте из виду тот факт, что необходимо версия разрядности дистрибутива должна соответствовать разрядности Вашей операционной системы. По окончании инсталляции рабочие директории комплекта Debugging Tools for Windows будут следующими:

  • Для версии x86: C:\Program Files (x86)\Debugging Tools for Windows (x86)
  • Для версии x64: C:\Program Files\Debugging Tools for Windows (x64)

Но не исключено, что пути могут измениться в последующих версиях дистрибутива.

Настройка отладочных символов

Отладочные символы — информация, позволяющая использовать "символические" (понятные человеку) данные о бинарном файле, такие как имена процедур, функций и переменных, используемых в исходном коде программы.

В ситуации с 32-битным кодом я настоятельно рекомендую настраивать отладчик на работу с отладочными символами. В сети встречается мнение, что отладочные символы не нужны для вычисления сбойного драйвера/модуля (процесса, в контексте которого произошел сбой), но применительно к 32-битному коду часто наблюдал ситуацию, когда разбор стека отладчиком заканчивается результатами, далекими от истины. К тому же, при настроенных символах мы получаем дополнительную информацию по сбою и можем детальнее проанализировать, к примеру, стек вызовов момента сбоя на уровне процедур и функций.
Настроить отладчик на работу с отладочным символам можно несколькими способами. Первый, самый простой и традиционный, заключается в указании составного пути к символам в настройках отладчика WinDbg: srv*c:\symbols*http://msdl.microsoft.com/download/symbols. Для настройки отладочных символов выберем в меню пункт "File" - "Symbol File Path ..." и просто вставим указанное выше значение в поле ввода, затем нажмем клавишу ОК.

прописать символы в winDbg

Теперь при запуске процесса отладки, отладчик WinDbg сначала произведет поиск символов для модулей, участвующих в процессе отладки, в локальной папке c:\symbols, если там символов обнаружено не будет, то WinDbg попытается загрузить их из сети Интернет с сайта http://msdl.microsoft.com/download/symbols и закешировать (сохранить) в ту же папку c:\symbols. Отладчик WinDbg загружает символы только по мере необходимости и только к модулям, которые обнаруживаются в процессе анализа дампа. Причем директорию c:\symbols руками создавать не требуется, поскольку отладчик создаст её сам на стадии кеширования символов (в случае наличия у него соответствующих разрешений, конечно же).

Для того, чтобы не настраивать путь поиска символов каждый раз после запуска WinDbg, необходимо сохранить рабочее окружение отладчика. Для этого зайдите в меню "File" и выберите пункт "Save Workspace".

Отладка зависшего процесса

Теперь перейдем непосредственно к изучению подвисшего приложения. Отладку я производил непосредственно на клиентской рабочей машине, хотя тут есть некоторые пространство для маневров.

Все дальнейшие действия производятся от имени имени учетной записи с правами локального администратора.

Запускаем отладчик Windbg, либо непосредственным выбором из меню Пуск, либо можно просто ввести слово Windbg в строке поиска.
В интерфейса выбираем меню File, далее пункт Attach to a process (либо просто жмем клавишу F6):

windbg process attach

В открывшемся списке ищем зависший процесс (в нашем случае это WINWORD.EXE), маркируем его и нажимаем кнопку OK. Происходит подключение отладчика к процессу (WINWORD), после чего в основном окне вывода мы наблюдаем следующее:

Я намерено сократил вывод, поскольку после подключения к процессу отладчик начинает перечислять модули, загруженные в адресное пространство процесса. Это все те модули/библиотеки, функции которых необходимы для работы основного приложения WINWORD.EXE, либо те модули, которые через специальные системные механизмы были принудительно загружены в адресное пространство процесса. Информационная строка Symbol search path is: *** Invalid ***, а так же похожие строки ERROR: Symbol file could not be found для основных системных библиотек говорят нам о том, что отладчик не смог подгрузить символы с локального хранилища либо с сервера символов Microsoft. Это может быть из-за того, что путь к отладочным символам не сконфигурирован, либо доступа к серверу символов попросту нет.

Сразу оговорюсь, что на клиентской станции я прописал путь к символам в соответствии рекомендациями, описанными выше.

После чего сделал повторную попытку загрузки символов посредством ввода команды .reload:

Тем не менее, символы по прежнему не загружались, о чем красноречиво свидетельствовала ошибка ERROR: Symbol file could not be found.

Как выяснилась чуть позже, объяснение этому было достаточно простое: станция пользователя не имела выхода в сеть Интернет, поэтому и доступа к серверу символов Microsoft не было.

В подобной ситуации у нас остается два выхода:

  • поднять собственный сервер символов внутри локальной сети. но это отдельная, иногда весьма трудоемкая задача, заниматься которой я в тот момент не хотел.
  • продолжить отладку без использования отладочных символов. это существенно осложняло анализ.

К тому же я вспомнил, что получить символы к модулям установленной у клиента операционной системы Windows 7 мы сможем, они то в открытом доступе имеются, но вот отладочные символы к модулям (библиотекам) пакета Microsoft Office 2010 мы никогда не получим, поскольку Microsoft в открытый доступ их попросту не выкладывает. Поэтому я выбрал самый сложный из всех возможных сценариев и отказался от использования символов вообще!
Продолжаем. Наш отладчик в данный момент уже подключен к зависшему процессу WINWORD. И начинаем мы изучение процесса с вывода выполняющихся в его контексте потоков. Заодно данное действие даст нам понимание того, сколько же вообще потоков выполняется в рамках изучаемого нами процесса. Для вывода списка потоков используем команду ~*:

Символ . напротив 9го потока указывает нам на то, что в данный момент активным является контекст потока #9, в котором, судя по стартовой функции ntdll!DbgUiRemoteBreakin, у нас выполняется отладчик Windbg. Получается, что в контейнере процесса WINWORD у нас в момент сбоя выполнялось 8 собственных потоков.

Процесс это контейнер, сами по себе процессы не выполняются, всю вычислительную нагрузку несут потоки внутри процесса.

Принимая данный факт во внимание, осознаем, что изучать мы будем потоки внутри процесса. Но какой из восьми потоков завис, то есть какой из потоков является целью для отладки? Для этого давайте, для начала, проведем "беглый" анализ всех потоков процесса:

Поток№ Старт Комментарий
0 WINWORD+0x1b7c Нулевой поток всегда является основным (главным) потоком процесса (WINWORD). В пользу этого факта говорит название модуля, совпадающее с названием основного приложения (WINWORD).
1 ntdll!TpIsTimerSet+0x8a0 Судя по всему проверка статуса системного таймера через функцию TpIsTimerSet библиотеки ntdll.dll.
2 mso!Ordinal4349+0x25c Неизвестная внутренняя функция с ординалом 4349 библиотеки mso.dll. mso.dll является библиотекой в составе Microsoft Office разных версий, в которой сосредоточено большинство внутренних функций, используемых основными приложениями пакета.
3 mso!Ordinal4349+0x39c Неизвестная внутренняя функция с ординалом 4349 библиотеки mso.dll. mso.dll является библиотекой в составе Microsoft Office разных версий, в которой сосредоточено большинство внутренних функций, используемых основными приложениями пакета.
4 gdiplus!GdipPlayTSClientRecord+0x1593c Функция GdipPlayTSClientRecord библиотеки gdiplus.dll. Библиотека gdiplus.dll предоставляет доступ приложений к использованию функций поддержки графики и некоторых текстовых функций при работе с видеоадаптером и принтером.
5 mso!Ordinal4349+0x25c Неизвестная внутренняя функция с ординалом 4349 библиотеки mso.dll. mso.dll является библиотекой в составе Microsoft Office разных версий, в которой сосредоточено большинство внутренних функций, используемых основными приложениями пакета.
6 mso!Ordinal4349+0x25c Неизвестная внутренняя функция с ординалом 4349 библиотеки mso.dll. mso.dll является библиотекой в составе Microsoft Office разных версий, в которой сосредоточено большинство внутренних функций, используемых основными приложениями пакета.
7 mso!Ordinal4349+0x25c Неизвестная внутренняя функция с ординалом 4349 библиотеки mso.dll. mso.dll является библиотекой в составе Microsoft Office разных версий, в которой сосредоточено большинство внутренних функций, используемых основными приложениями пакета.
8 ntdll!RtlDestroyHandleTable+0x270 Функция RtlDestroyHandleTable библиотеки ntdll.dll. Функция уничтожает таблицу дескриптора и освобождает ассоциированные с ней ресурсы.
9 ntdll!DbgUiRemoteBreakin Функция DbgUiRemoteBreakin обрабатывает запуск отладчика. Если отладчик присоединяется к процессу, в этом процессе создается новый поток с функцией DbgUIRemoteBreakin в качестве точки входа. Собственно, наш отладчик Windbg.

Как думаете, какой нам выбрать поток? Лично мне моя интуиция кивает на поток #0, уже лишь по той причине, что код этого потока выполняется внутри основного модуля, на что красноречиво намекает название WINWORD. В действительности всё даже проще. Считается, что нулевой поток (поток #0) является главным потоком процесса, и если процесс виснет, то зависает всегда именно главный поток. Поэтому, в первую очередь, всегда обращайте внимание на нулевой поток (поток #0).
И так, начинаем анализ потока #0, для этого переключаемся на него (переключаем контекст выполнения) командой ~0s:

Отладчик привычно выдает нам предупреждение об отсутствии символов к библиотеке wwlib.dll. wwlib.dll это библиотека, входящая в состав пакета Microsoft Office 2010, а к этому пакету, как мы уже говорили, Microsoft не предоставляет файлов символов. После сообщения об отсутствии отладочных символов, мы наблюдаем относительное смещение инструкции wwlib!FMain+0x11a957, которая показывает нам, что инструкция находится по смещению 0x11a957 относительно начала функции FMain в библиотеке wwlib.dll. Далее следует адрес инструкции в виртуальном адресном пространстве процесса, опкод инструкции ассемблера (443bc5) и символическое имя инструкции (cmp r8d,ebp), на которой отладчик остановил выполнение потока. СТОИМ! Неплохо было бы получить стек вызовов потока.

Стек вызовов - цепочка вызовов процедур, начиная от момента запуска потока.

Для получения информации по стеку вызовов потока #0 я использовал команду kL:

Не очень то информативно, верно? Если бы у нас были файлы символов к модулям пакета Microsoft Office 2010, то вместо "голых" смещений вида FMain+0xXXXXXX, мы видели бы символические имена функций (например, что-то вроде wwlib!GetUserInput+0x1a), что сильно упростило хотя бы понимание того, кто кого и зачем вызывал и где искать проблему. С другой стороны, даже при наличии символов мы могли бы получить похожий, мало понятный, вывод, если бы в библиотеке wwlib.dll оказалась действительно одна функция FMain, одним словом тут уж как повезет. Ну как же быть если нет символов, не отлаживать код вообще? Нет, это не наш выбор. Видно, что практически со старта поток перешел от главного модуля WINWORD к функциям библиотеки wwlib.dll, которые поочередно вызывали друг-друга.

Стек вызовов читается снизу вверх.

По стеку вызовов видно, что на протяжении последних 14 вызовов функций вся работа шла между функциями библиотеки wwlib.dll и самой последней была некая функция wwlib!FMain+0x11a957 (указана на самой вершине стека).

Поскольку у нас нет символов к библиотеке, все смещения показаны относительно единственной обнаруженной отладчиком функции FMain, судя по всему, главной функции библиотеки.

УПЕРЛИСЬ? Имена функций нам ничего не дают, соответственно понять логически что именно происходило в момент зависания процесса мы не можем. Что же делать дальше? В такие моменты главное не падать духом.

Помните одно: неразрешимых ситуаций нет, если только ограничивающее нас собственное знание.

После того, как я успокоился, мысли мои потекли совершенно в другом направлении. Я ясно визуализировал перед собой поток данных, представляющий собой простой блок кода, исполняемых инструкций, каждая из которых на основе заданных правил исполняется процессором, и так команда за командой. Процессору не нужны символы, ему даже не нужно знать что именно он выполняет, он просто исполняет инструкцию за инструкцией. К тому же у нас в руках превосходный инструмент, отладчик, в котором сведено такое количество различных механизмов отладки, что обладая ими в совершенстве мы могли бы достичь невиданных результатов. В сознании постепенно начала разворачиваться концепция дальнейших действий. Попробуем совместить максимум механизмов, предоставляемых нам отладчиком с минимумом знаний. На ум мне пришла идея увидеть код глазами процессора, воспользовавшись пошаговой трассировкой. Смотрим документацию на предмет команды пошаговой трассировки и начинаем трассировать по одной инструкции посредством команды p:

Я просто раз за разом вводил одну и ту же команду p, просто изучая код, смотря, какие команды всплывают из-под нижней границы окна отладчика. Знаете ли, действо это завораживает, чувствуешь себя прикоснувшимся к некой тайне, частью чего то глобального, частью процесса. Смотришь так вот иногда на поток команд и перед глазами возникает во всей красе внутренний мир машины, начинаешь ощущать всю элегантность команд. Еще бы всегда осознавать, что происходит :) Шаг, инструкция jne, вероятный переход, шаг, инструкция mov, относящая в аккумулятор значение из ячейки памяти, шаг, переход на функцию, шаг, шаг, шаг.. Судя по всему это блок работы с какими то внутренними переменными. Тапая команду p я как-то ушел в себя, задумался, пытаясь осознать назначение кода, только через некоторое краем зрения заметив, что инструкции и адреса как-то разительно похожи друг на друга, я вышел из забытья и ползунком открутил вверх лог отладчика. Появляющиеся передо мной адреса повторялись, образуя программный цикл.

ЦИКЛ, КАРЛ!!

По блоку кода, приведенному выше, я увидел, что код стартовал с адреса 000007fee95ac97e и пришел к 000007fee95ac97e. После команды jmp wwlib!FMain+0x11a957, расположенной по адресу 000007fee95ac9a9 управление всегда передается на 000007fee95ac97b, ну термин "всегда" тут означает ровно то количество итераций, которое я прошел пошаговой трассировкой, скорее этак раз 5-10. По факту поток попал в бесконечный цикл, из которого моя трассировка не смогла выйти на протяжении нескольких итераций. В голове сразу же всплыло главное предположение: а не этот ли цикл всему виной? Очищенный от мусора этот участок кода выглядит следующим образом:

Если проанализировать участок кода, то можно выявить несколько возможных переходов, которые не осуществляются из-за невыполняющихся условий.
Это:

  • jne wwlib!FMain+0x11a960 (jne 000007fe`e95ac984) - не интересен, какой-то локальный переход. путем грубого предположения отметается;
  • jmp wwlib!FMain+0x11a963 (jmp 000007fe`e95ac987) - не интересен, поскольку является частью какого-то условия на языке высокого уровня (C/C++) внутри нашего глобального цикла;
  • jb wwlib!FMain+0x11a96f (jb 000007fe`e95ac993) - не интересен, опять локальный переход. переходит всего на несколько байт вперед. путем грубого предположения отметается;
  • jge wwlib!FMain+0x11a987 (jge 000007fe`e95ac9ab) - интересен! определенно разрывает цикл, в котором мы застряли, и уходит на дальнейший код.

Некоторые операнды у нас общаются с ячейками 0000000004da0d50, 0000000004da0d5c, 0000000004da0d60, 0000000004da0d74. Поэтому я решил посмотреть блок памяти по данным адресам:

Ну и чего мы здесь видим? Скорее всего какие-то переменные, для чего предназначающиеся не понятно. Код их проверяет, ну и что? Откуда мы знаем что они содержат? Лишний ход, мы все-равно не сможем определить принадлежность блока памяти.
По карте памяти процесса, полученной через команду !address, данный блок маркирован как:

Область, не закрепленная ни за каким модулем. Хотя тут моя логика может хромать, поскольку, вероятно, она и не должна быть закреплена, а может просто-напросто представлять собой регион, выделенный для локальных операций?

Одним словом, нам потребуется принудительно "разорвать" цикл, выйдя из него.
Поскольку нас заинтересовал лишь один переход:

который при нашей трассировке ни разу не выполнился, предположим что именно из-за отсутствия данного перехода у нас и образовался цикл. Поэтому мы его и используем. Для размыкания цикла необходимо сделать так, что бы условие выполнилось и переход осуществился. Сначала я попытался осуществить данный финт через непосредственное изменение флагов SF и OF с целью сделать условия перехода истинными, но у меня, в силу собственной специфики :) ничего не получилось, переход упорно не хотел выполняться. Плюнув на эту безнадежную затею, я решил попробовать изменить код операнда в памяти. Для этого мы просто вызываем Memory Window (окно памяти) кликом по соответствующему значку на панели инструментов или комбинацией клавиш Alt+5, после чего у нас открывается такое вот окно:

Windbg memory window

Находим адрес 000007fee95ac9a5, по которому у нас располагается опкод команды (7D04). На рисунке он маркирован мною красной рамкой. Напомню, что опкод 7D04 означает команду jge, который трактуется как "переход если больше или равно". Нам же можно попробовать поменять условие на противоположное. В документации к ассемблеру находим следующую инструкцию:

7CXX jl Прыжок если меньше (флаги: SF<>OF).

соответственно, нам надо заменить значение первого байта с 7D на 7C. После выполнения замены, изменения применяются мгновенно и можно закрыть окно дампа памяти. Выполнив несколько команд я убедился, что инструкция действительно изменилась и код ушел дальше. Ну что же, настает момент истины и нам требуется проверить правильность наших действий, поскольку в пошаговой трассировке более нет необходимости, я просто запущу поток на исполнение. В командной строке ввожу команду g и нажимаю на клавиатуре клавишу Enter. Через несколько секунд процесс WINWORD отвис, о чем красноречиво свидетельствовало ожившее окно программы:

winword

После чего пользователь смог сохранить свои данные, которых, к великому моему сожалению, оказалось совсем немного. И я завершил отладчик с закрытием отлаживаемого процесса.

Источник проблемы

Ну ладно, не мытьем так катаньем нам удалось решить проблему зависшего процесса. Но вся особенно именно этой проблемы заключалась в том, что зависания происходят с завидной регулярностью, до нескольких раз за рабочий день. По несколько раз на дню заниматься отладкой совершенно не хотелось, требовалось найти причину возникающих сбоев. Если повнимательнее присмотреть к пакету Microsoft Office 2010, выпущенного уже достаточно давно и успевшего обрасти уже не одним сервис паком и огромным количеством всевозможных хотфиксов, понимаешь, что вряд ли обнаруженная нами проблема является следствием бага в самом пакете, существование подобного сбоя возможно только, разве что, на этапе альфа-тестирования. Поэтому я сделал предположение, что виновником сбоя является что-то внешнее, работающее в контексте процесса WINWORD. Как то в процессе отладки и выводил карту памяти процесса по команде !address:

И после завершения отладки я решил просмотреть записанный мной ранее в отдельный текстовый файл журнал отладки, с целью более детально изучить карту памяти процесса. Дело в том, что карта может показать нам много интересных особенностей. В адресном пространстве содержится всё необходимое для полноценной работы приложения Winword. Тут можно найти и библиотеки, требуемые для работы, можно увидеть и сам редактируемый файл, различные временные файлы, системные структуры, такие как стек, кучи и прочее. Я умышленно вырезал из карты памяти процесса все лишнее, оставив только заинтересовавшие меня области. Что же мне, собственно, удалось обнаружить:

  • Надстройка к WINWORD под названием Консультант+ светилась библиотекой scons.dll.
  • В пространстве присутствовала некая программа Print Audit, функционал которой обеспечивался библиотекой pa6wtrak64.dll.
  • И наконец, самое интересное, своё присутствие засветил такой интересный компонент как Symantec Endpoint Agent (он же Symantec DLP), который был представлен библиотекой prntm64.dll.

Как Вы думаете, на какой из этих процессов я обратил внимание в первую очередь? Правильно, на Symantec DLP, потому как пару раз я уже подавал баг-репорты по обнаруженным внутри DLP проблемам через поставщиков, я уже имел некую накопившуюся базу знаний по данному программному продукту. Подобные, достаточно сложные, контролирующие системы, в силу особенностей архитектуры и необходимости постоянно внедряться везде, где только можно, идеально работать не могут, слишком сложны подобные алгоритмы. Не подумайте, я не хочу кидать камень в огород Symantec, просто выражаю своё собственное мнение на счет данного продукта. Надо заметить, что обнаруженная мной на станции клиента версия DLP не являлась последней, а выпущена была несколько месяцев назад. Вы понимаете, насколько важно поддерживать подобного рода программные средства на должном уровне обновления, поскольку продукт непрерывно буквально дорабатывается и тестируется после каждого серьезного системного обновления операционной системы. Поскольку в этот раз я не смог сопоставить обнаруженный глюк Winword'а непосредственно с какой-либо ошибкой в модуле Sumantec DLP, было принято решение просто удалить продукт. После удаления агента DLP клиентская система вот уже несколько дней работает без сбоев.

Выводы

Приведенный мной пример устранения причины зависшего процесса далек от идеала, поскольку, в некоторых ситуациях, логика зациклившегося кода может быть в разы более сложной. Более того, он может быть вовсе не показательным, поскольку я могу предположить, что "отвисание" зависшего процесса лично в данном эксперименте могло быть связано вовсе не с разомкнутым программным циклом. Однако, не смотря ни на что, данный пример показывает, что не стоит бояться проводить исследования работы кода, поскольку в некоторых случаях причины зависания процесса могут быть достаточно банальными.

Комментарии: 2

  1. Виталий

    Как же мне ещё да-а-а-а-леко до тебя.... :-(
    У меня периодически зависает процесс Explorer.exe при наличии в системе PuntoSwitcher (при создании, либо переименовании файлов и папок) на срок от 3-х сек до убийства процесса. Интересно? (для коллекции)

    1. einaare

      до меня? да брось..
      а глюк именно от puntoswitcher зависит? то есть при отключении/деинсталляции проблема уходит?
      можно подробности на почту?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *