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


Использование 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 
    
  • По умолчанию объем собираемых данных трассировки стека ограничен 32 МБ на процессоре x86 и 64 МБ на процессоре x64. Если необходимо увеличить размер этой базы данных, выберите вкладку "Файл изображения " в графическом интерфейсе GFlags, введите имя процесса, нажмите клавишу TAB, установите флажок Stack 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 (1). Сделайте этот параметр перед запуском приложения, выделение которого необходимо отслеживать.

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

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

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

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

  2. Используйте UMDH для анализа выделений памяти в куче для этого процесса и сохранения результатов в файле журнала. Используйте переключатель -p с PID и переключатель -f с именем файла журнала. Например, если идентификатор пин-кода равен 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++ для получения памяти из кучи.

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