Поделиться через


Использование UMDH для поиска утечки памяти User-Mode

Служебная программа UMDH (UMDH) в пользовательском режиме работает с операционной системой для анализа выделений кучи Windows для определенного процесса. UMDH определяет, какая подпрограмма в определенном процессе приводит к утечке памяти.

UMDH входит в состав средств отладки для Windows. Полные сведения см. в разделе UMDH.

Подготовка к использованию UMDH

Если вы еще не определили, какой процесс приводит к утечке памяти, сначала сделайте это. Дополнительные сведения см. в статье Использование Монитор производительности для поиска утечек памяти User-Mode.

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

Прежде чем использовать UMDH для отображения данных трассировки стека, необходимо использовать GFlags для правильной настройки системы. GFlags входит в состав средств отладки для Windows.

Следующие параметры GFlags позволяют выполнять трассировку стека UMDH:

  • В графическом интерфейсе GFlags выберите вкладку Файл изображения, введите имя процесса (включая расширение имени файла), нажмите клавишу TAB, выберите Создать базу данных трассировки стека пользовательского режима и нажмите кнопку Применить.

    Или, эквивалентно, используйте следующую командную строку GFlags, где ImageName — это имя процесса (включая расширение имени файла):

    gflags /i ImageName +ust 
    

    Используйте эту команду, чтобы очистить параметры GFlag после завершения работы. Дополнительные сведения см. в разделе Команды GFlags.

    gflags /i ImageName -ust 
    
  • По умолчанию объем данных трассировки стека, собираемых Windows, ограничен 32 МБ на процессоре x86 и 64 МБ на процессоре x64. Если необходимо увеличить размер этой базы данных, выберите вкладку Файл изображения в графическом интерфейсе GFlags, введите имя процесса, нажмите клавишу TAB, проверка поле Проверка стек Backtrace (Megs) введите значение (в МБ) в связанном текстовом поле, а затем нажмите кнопку Применить. Увеличивайте эту базу данных только при необходимости, так как это может привести к истощению ограниченных ресурсов Windows. Если больше размер больше не нужен, верните этот параметр в исходное значение.

  • Если вы изменили флаги на вкладке Системный реестр , необходимо перезапустить Windows, чтобы эти изменения вступили в силу. Если вы изменили флаги на вкладке Файл изображения , необходимо перезапустить процесс, чтобы изменения вступили в силу. Изменения на вкладке Флаги ядра вступает в силу немедленно, но теряются при следующем перезапуске Windows.

Перед использованием UMDH необходимо иметь доступ к соответствующим символам для приложения. UMDH использует путь к символам, заданный переменной среды _NT_SYMBOL_PATH. Задайте для этой переменной путь, содержащий символы для приложения. Если также включить путь к символам Windows, анализ может быть более полным. Синтаксис для этого пути к символам совпадает с синтаксисом, используемым отладчиком; Дополнительные сведения см. в разделе Путь к символам.

Например, если символы для вашего приложения находятся в папке C:\MySymbols и вы хотите использовать общедоступное хранилище символов Майкрософт для символов Windows, используя C:\MyCache в качестве нижестоящего хранилища, используйте следующую команду, чтобы задать путь к символам:

set _NT_SYMBOL_PATH=c:\mysymbols;srv*c:\mycache*https://msdl.microsoft.com/download/symbols 

Кроме того, чтобы обеспечить точные результаты, необходимо отключить кэширование BSTR. Для этого задайте для переменной среды OANOCACHE значение 1. Установите этот параметр перед запуском приложения, выделения которого необходимо отследить.

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

Обнаружение увеличения выделений кучи с помощью UMDH

После выполнения этих подготовительных действий можно использовать UMDH для сбора сведений о выделении кучи процесса. Для этого выполните следующую процедуру:

  1. Определите идентификатор процесса (PID) для процесса, который требуется изучить.

  2. Используйте UMDH для анализа выделений памяти кучи для этого процесса и сохраните его в файл журнала. Используйте параметр -p с PID, а параметр -f — с именем файла журнала. Например, если piD имеет значение 124 и вы хотите присвоить файлу журнала имя Log1.txt, используйте следующую команду:

    umdh -p:124 -f:log1.txt 
    
  3. Откройте файл журнала с помощью Блокнота или другой программы. Этот файл содержит стек вызовов для каждого выделения кучи, количество выделений, выполненных через этот стек вызовов, и количество байтов, использованных через этот стек вызовов.

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

    UMDH может сравнивать два разных файла журнала и отображать изменения в соответствующих размерах выделения. Для перенаправления результатов в третий текстовый файл можно использовать символ> больше (). Также может потребоваться включить параметр -d, который преобразует число байтов и выделений из шестнадцатеричного в десятичное. Например, чтобы сравнить Log1.txt и Log2.txt, сохранив результаты сравнения в LogCompare.txt файла, используйте следующую команду:

    umdh log1.txt log2.txt > logcompare.txt 
    
  5. Откройте файл LogCompare.txt. Его содержимое выглядит следующим образом:

    + 5320 ( f110 - 9df0) 3a allocs BackTrace00B53 
    Total increase == 5320 
    

    Для каждого стека вызовов (с меткой BackTrace) в файлах журнала UMDH выполняется сравнение двух файлов журнала. В этом примере первый файл журнала (Log1.txt) записал 0x9DF0 байтов, выделенных для BackTrace00B53, а второй — 0xF110 байтов. Это означает, что между моментом записи двух журналов было выделено 0x5320 дополнительных байтов. Байты поступили из стека вызовов, определяемого backTrace00B53.

  6. Чтобы определить, что находится в этой обратной трассировке, откройте один из исходных файлов журнала (например, Log2.txt) и выполните поиск по запросу BackTrace00B53. Результаты аналогичны этим данным:

    00005320 bytes in 0x14 allocations (@ 0x00000428) by: BackTrace00B53
    ntdll!RtlDebugAllocateHeap+0x000000FD
    ntdll!RtlAllocateHeapSlowly+0x0000005A
    ntdll!RtlAllocateHeap+0x00000808
    MyApp!_heap_alloc_base+0x00000069
    MyApp!_heap_alloc_dbg+0x000001A2
    MyApp!_nh_malloc_dbg+0x00000023
    MyApp!_nh_malloc+0x00000016
    MyApp!operator new+0x0000000E
    MyApp!DisplayMyGraphics+0x0000001E
    MyApp!main+0x0000002C
    MyApp!mainCRTStartup+0x000000FC
    KERNEL32!BaseProcessStart+0x0000003D 
    

    Эти выходные данные UMDH показывают, что из стека вызовов было выделено 0x5320 (десятичное число 21280) байтов. Эти байты были выделены из 0x14 (десятичных 20) отдельных выделений 0x428 (десятичных 1064) байтов каждый.

    Стеку вызовов присваивается идентификатор "BackTrace00B53", и вызовы в этом стеке отображаются. При просмотре стека вызовов вы увидите, что подпрограмма DisplayMyGraphics выделяет память с помощью нового оператора, который вызывает подпрограмму malloc, которая использует библиотеку времени выполнения Visual C++ для получения памяти из кучи.

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