Любой администратор Windows понимает, сколь важную роль играют инструменты диагностики и решения проблем, возникающих в системе. Ранее мы уже рассматривали два инструмента, позволяющие выявить утечки памяти, потребляемой процессами: утилиту UMDH (User-Mode Dump Heap) и DebugDiag. Однако иногда эти инструменты производят слишком много служебных данных, вызывая высокую загрузку процессора и создавая условия, в которых использование UMDH или DebugDiag становится нецелесообразным. Когда обычные средства диагностики неприменимы, решить проблему намного сложнее, поэтому важно иметь резервный план устранения неполадок.
Есть ли у вас альтернативный подход на случай, когда обычный набор инструментов диагностики не справляется с задачей? Цель данной статьи — показать хотя бы один резервный план и продемонстрировать важность изучения внутренней организации Windows и использования отладчика Windows для извлечения ценной диагностической информации.
Поиск «раздутого» процесса
Недавно мне пришлось решать проблему одного из клиентов, которая заключалась в чрезмерном росте объема памяти, потребляемой процессом wmiprvse.exe (WMI Provider Host). Для определения основной причины клиент предоставил файл дампа. Мы начали с запуска DebugDiag и UMDH по отдельности. Однако при выполнении каждой из этих утилит загрузка процессора постоянно вырастала до 100%, блокируя работу системы. Поскольку обычные средства диагностики вызывали перегрузку систему, пришлось обратиться к резервному плану.
Чтобы установить, почему процесс «раздувается» до такого размера и что можно сделать для уменьшения потребления памяти, мы располагали только файлом дампа процесса wmiprvse.exe размером в 185 Мбайт. Сначала мы открыли файл дампа с помощью отладчика windbg.exe, включенного в пакет Debugging Tools for Windows. Debugging Tools for Windows можно загрузить по ссылке www.microsoft.com/whdc/devtools/debugging/default.mspx. Этот набор инструментов отладки должен иметь под рукой каждый администратор, желающий более глубоко исследовать системные проблемы. С помощью отладчиков из файла дампа можно извлечь массу полезной информации, даже не будучи экспертом по поиску неисправностей и не зная досконально код.
Для анализа файла дампа мы выполнили следующие шаги.
-
Запустили windbg.exe.
-
Обеспечили корректность ввода пути к символам — щелкнуть File, Symbol File Path, после чего ввести следующий путь:
srv*c:symbols*http://msdl.microsoft.
com/download/symbols
Файлы символов — это файлы, переводящие машинный код в удобные для чтения вызовы функций.
-
Открыли файл wmiprvse.dmp в отладчике Windbg — выбрать File и щелкнуть Open Crash Dump.
В отладчике мы ввели следующую команду:
!address -summary
Эта удобная команда выдает сводную информацию о типах потребляемой процессом памяти (см. экран 1).
Самый важный столбец — Pct (Busy), где указан тип используемой памяти в процентах. Быстро просмотрев этот столбец, мы выяснили, что на самое большое потребление памяти (74%) указывает RegionUsageHeap — счетчик динамической памяти, резервируемой для процессов сохранения данных. Каждый процесс имеет память кучи, изначально составляющую 1 Мбайт по умолчанию. Однако эта область может увеличиваться, и для кучи может выделяться все больше памяти, если того требует процесс.
Ключевым моментом нашего исследования было понимание того, что память кучи — это место, где программа хранит свои данные. Память кучи можно расценивать как сегмент, представленный адресом, где приложения хранят данные, связанные с процессом. Процесс может располагать несколькими «сегментами» кучи, поэтому важно знать, который из них следует проанализировать, поскольку не каждый сегмент заполняется данными и потребляет много ресурсов. Мы должны были выявить сегмент кучи, занимающий наибольший объем памяти, чтобы получить ключ к разгадке «раздувания» процесса.
Обращение к файлу справки отладчика
Как уже говорилось, у процесса имеется несколько сегментов кучи, пригодных для анализа, поэтому нам понадобилось провести более прицельное исследование. Именно здесь пригодился файл справки отладчика (debugger.chm). Я знал, что для изучения проблемы процесса, связанной с памятью кучи, нужно воспользоваться командой !heap. Если же вы не знаете, какую команду отладчика следует использовать, выполните поиск в debugger.chm по термину, относящемуся к вашему исследованию. Например, в ответ на запрос по термину «куча» сразу же выдается команда !heap и все ее параметры.
В ответ на команду !heap выдается список адресов памяти, который сам по себе не несет достаточно информации, чтобы выявить сегмент кучи, занимающий наибольший объем памяти. При прокрутке вперед параметров !heap я заметил параметр -stat, предусматривающий выдачу статистики использования кучи. Помня о том, что наша цель состоит в выявлении кучи (или сегмента), потребляющей больше всего ресурсов, я запустил команду с этим параметром:
!heap -stat
Как показано на экране 2, в результате выполнения команды выдается список куч в порядке убывания потребляемой памяти. Первая куча в списке — это именно та, которую нужно анализировать.
Как мы видим, каждый сегмент кучи имеет конкретный адрес. Первый сегмент кучи имеет адрес 00700000; следующая куча — адрес 00090000. Важную информацию несет строка Committed bytes — количество байт памяти, выделенной для конкретной кучи. Мы знаем, что куча 00700000 потребляет наибольший объем памяти, поскольку параметр -stat предусматривает отображение куч в порядке убывания расходуемых ресурсов. Чтобы окончательно убедиться в своей правоте, можно преобразовать шестнадцатеричное число байтов выделенной памяти 06c03000 в десятичное с помощью calc.exe (т. е. калькулятора Windows) либо в отладчике с использованием команды ? (Evaluate Expression), как показано ниже:
0:000>?06c03000
Evaluate expression: 113258496 = 06c03000
Вторая строка — результат выполнения команды. Число 113258496 — это число 06c03000 в десятичном формате, и этот результат подтверждает, что куча по данному адресу занимает 113 Мбайт памяти. Учитывая, что весь процесс потребляет 185 Мбайт памяти, можно справедливо заключить, что мы на верном пути. Мы знаем, что процесс «раздувается» из-за большого объема кучи, и еще нам известно, что самая большая куча занимает 113 Мбайт. Теперь можно ввести команды отладчика, позволяющие вывести содержимое этой кучи, что может дать нам подсказку, почему процесс использует так много памяти. Простое отображение кучи может не дать явного указания, однако выданная информация может направить вас в верном направлении, как и произошло в нашем случае.
Для вывода содержимого кучи мы воспользовались командой dc (предусматривающей отображение значений в виде символов ASCII) с указанием адреса кучи. В нашем случае команда и частичный результат ее выполнения выглядят следующим образом (выходные данные на экране 3 относятся к начальной части кучи).
В большинстве случаев вам придется продолжать отображать участки кучи (нажатием клавиши Enter после выдачи первого результата выполнения команды dc) в поиске интересной информации. На экране 4 показан фрагмент информации, который помог нам прийти к выводу, что в данном случае память кучи потребляется информацией, сгенерированной программой трассировки — Event Tracing for Windows (ETW).
Заметим, что выдается даже имя файла и адрес: D:AppDataLogsMSTrace_20080615_221501_SERVER1.etl.
Проблема решена
На то, что данные, содержащиеся в куче, были информацией, относящейся к трассировке, указало расширение файла .etl, ассоциируемое с ETW. Как выяснилось, клиент не знал, что трассировка ETW была по-прежнему включена после того, как была разрешена предыдущая проблема несколькими месяцами раньше. Выключение трассировки ETW решило проблему клиента, и объем памяти, потребляемый процессом wmiprvse.exe, сразу же сократился. Таким образом, зная несколько команд отладчика и имея некоторое представление о внутренней организации операционной системы, вы можете с успехом осуществлять диагностику, когда привычные инструменты оказываются бесполезными.
Майкл Моралес (morales@microsoft.com) — старший инженер службы поддержки Microsoft Global Escalation Services. Специализируется на проблемах отладки и производительности Windows