다음을 통해 공유


UMDH를 사용하여 User-Mode 메모리 누수 찾기

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에서 수집하는 스택 추적 데이터의 양은 x86 프로세서의 경우 32MB, x64 프로세서에서는 64MB로 제한됩니다. 이 데이터베이스의 크기를 늘려야 하는 경우 GFlags 그래픽 인터페이스에서 이미지 파일 탭을 선택하고, 프로세스 이름을 입력하고, TAB 키를 누르고, Stack Backtrace(Megs) 검사 상자를 검사, 관련 텍스트 상자에 값(MB)을 입력한 다음 적용을 선택합니다. 제한된 Windows 리소스가 고갈될 수 있으므로 필요한 경우에만 이 데이터베이스를 늘입니다. 더 이상 더 큰 크기가 필요하지 않은 경우 이 설정을 원래 값으로 반환합니다.

  • 시스템 레지스트리 탭에서 플래그를 변경한 경우 이러한 변경 내용을 적용하려면 Windows를 다시 시작해야 합니다. 이미지 파일 탭에서 플래그를 변경한 경우 변경 내용을 적용하려면 프로세스를 다시 시작해야 합니다. 커널 플래그 탭에 대한 변경 내용은 즉시 적용되지만 다음에 Windows를 다시 시작할 때 손실됩니다.

UMDH를 사용하기 전에 애플리케이션에 적합한 기호에 액세스할 수 있어야 합니다. UMDH는 환경 변수 _NT_SYMBOL_PATH 지정된 기호 경로를 사용합니다. 이 변수를 애플리케이션의 기호를 포함하는 경로와 동일하게 설정합니다. Windows 기호에 대한 경로도 포함하는 경우 분석이 더 완료될 수 있습니다. 이 기호 경로의 구문은 디버거에서 사용하는 구문과 동일합니다. 자세한 내용은 기호 경로를 참조하세요.

예를 들어 애플리케이션의 기호가 C:\MySymbols에 있고 Windows 기호에 대해 공용 Microsoft 기호 저장소를 사용하려는 경우 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(프로세스 ID) 를 결정합니다.

  2. UMDH를 사용하여 이 프로세스에 대한 힙 메모리 할당을 분석하고 로그 파일에 저장합니다. PID와 함께 -p 스위치를 사용하고 로그 파일의 이름으로 -f 스위치를 사용합니다. 예를 들어 PID가 124이고 로그 파일의 이름을 Log1.txt 경우 다음 명령을 사용합니다.

    umdh -p:124 -f:log1.txt 
    
  3. 메모장 또는 다른 프로그램을 사용하여 로그 파일을 엽니다. 이 파일에는 각 힙 할당에 대한 호출 스택, 해당 호출 스택을 통해 수행된 할당 수 및 해당 호출 스택을 통해 사용된 바이트 수가 포함됩니다.

  4. 메모리 누수는 찾고 있으므로 단일 로그 파일의 내용이 충분하지 않습니다. 다른 시간에 기록된 로그 파일을 비교하여 증가하는 할당을 결정해야 합니다.

    UMDH는 두 개의 서로 다른 로그 파일을 비교하고 해당 할당 크기의 변경 사항을 표시할 수 있습니다. 보다 큼 기호(>)를 사용하여 결과를 세 번째 텍스트 파일로 리디렉션할 수 있습니다. 바이트 및 할당 수를 16진수에서 10진수로 변환하는 -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 
    

    UMDH 로그 파일의 각 호출 스택("BackTrace"이라는 레이블)에 대해 두 로그 파일 간에 비교가 이루어집니다. 이 예제에서 첫 번째 로그 파일(Log1.txt)은 BackTrace00B53에 할당된 0x9DF0 바이트를 기록하고 두 번째 로그 파일은 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(10진수 21280)이 있음을 보여 줍니다. 이러한 바이트는 각각 0x428(1064) 바이트의 0x14(10진수 20)의 별도 할당에서 할당되었습니다.

    호출 스택에는 "BackTrace00B53"의 식별자가 지정되고 이 스택의 호출이 표시됩니다. 호출 스택을 검토할 때 DisplayMyGraphics 루틴이 연산자를 통해 메모리를 할당하는 것을 볼 수 있습니다. 이 연산자는 Visual C++ 런타임 라이브러리를 사용하여 힙에서 메모리를 가져오는 루틴 malloc을 호출합니다.

    이러한 호출 중 소스 코드에 명시적으로 표시할 마지막 호출을 결정합니다. 이 경우 malloc에 대한 호출이 별도의 할당이 아닌 구현의 일부로 발생했기 때문에 새 연산자일 수 있습니다. 따라서 DisplayMyGraphics 루틴에서 연산자의 이 instance 해제되지 않는 메모리를 반복적으로 할당합니다.