다음을 통해 공유


연습 2 - 사용자 모드 프로세스 할당 추적

힙 할당 API(HeapAlloc, HeapReallocnew, alloc, realloc, calloc과 같은 C/C++ 할당)를 통해 직접 수행되며 세 가지 형식의 힙을 사용하여 처리됩니다.

  1. 기본 줄 NT 힙 – 크기가 64KB 미만인 서비스 할당 요청입니다.

  2. 낮은 조각화 힙 – 고정 크기 블록의 할당 요청을 서비스하는 하위 세그먼트로 구성됩니다.

  3. VirtualAlloc – 크기가 64KB를 초과하는 서비스 할당 요청입니다.

VirtualAllocVirtualAlloc API를 통해 직접 수행되는 대규모 동적 메모리 할당에 사용됩니다. 일반적으로는 비트맵 또는 버퍼에 사용됩니다. VirtualAlloc을 사용하여 페이지 블록을 예약한 다음 VirtualAlloc을 추가로 호출하여 예약된 블록에서 개별 페이지를 커밋할 수 있습니다. 이렇게 하면 프로세스가 필요할 때까지 실제 스토리지를 사용하지 않고도 해당 가상 주소 공간의 범위를 예약할 수 있습니다.

이 영역에서 이해해야 할 두 가지 개념이 있습니다.

  1. 예약된 메모리: 사용량에 대한 주소 범위를 예약하지만 메모리 리소스를 확보하지는 않습니다.

  2. 커밋된 메모리: 주소가 참조되는 경우 실제 메모리 또는 페이지 파일 공간을 사용할 수 있는지 확인합니다.

이 연습에서는 추적을 수집하여 사용자 모드 프로세스가 메모리를 할당하는 방법을 조사하는 방법을 알아봅니다.

이 연습에서는 다음을 통해 메모리를 할당하는 MemoryTestApp.exe라는 더미 테스트 프로세스에 중점을 둡니다.

  1. 큰 메모리 버퍼를 커밋하는 VirtualAlloc API입니다.

  2. 작은 개체를 인스턴스화하는 C++ 연산자입니다.

여기에서 MemoryTestApp.exe를 다운로드할 수 있습니다.

1단계: WPR을 사용하여 virtualAlloc/힙 추적 수집

대규모 메모리 할당은 일반적으로 프로세스의 공간에 영향을 미치며 VirtualAlloc API에 의해 처리됩니다. 여기에서 모든 조사가 시작되어야 하지만 프로세스가 더 작은 할당으로 잘못 동작할 수도 있습니다(예: C++에서 연산자를 사용하는 메모리 누수 등). 힙 추적은 이러한 상황이 발생할 때 유용합니다.

1.1단계: 힙 추적을 위한 시스템 준비

힙 추적은 선택 사항으로 간주해야 하며 VirtualAlloc 분석이 메모리 사용 문제에 대한 관련 설명을 제공하지 않을 때 수행해야 합니다. 힙 추적은 더 큰 추적을 생성하는 경향이 있으며 조사 중인 개별 프로세스에 대해서만 추적을 사용하도록 설정하는 것이 좋습니다.

관심 있는 프로세스(이 경우에는 MemoryTestApp.exe)에 대한 레지스트리 키를 추가합니다. 그러면 모든 후속 프로세스 생성에 대해 힙 추적이 사용되도록 설정됩니다.

reg add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\MemoryTestApp.exe" /v TracingFlags /t REG_DWORD /d 1 /f

1.2단계: WPR을 사용하여 추적 캡처

이 단계에서는 VirtualAlloc 데이터가 포함된 WPR을 사용하여 추적을 수집합니다.

  1. WPR을 열고 추적 구성을 수정합니다.

    1. VirtualAlloc 공급자를 선택합니다.

    2. 성능 시나리오일반을 선택합니다.

    3. 로깅 모드일반을 선택합니다.

      WPR 추적 옵션 메뉴의 스크린샷

  2. 시작을 클릭하여 추적을 시작합니다.

  3. MemoryTestApp.exe를 실행하고 프로세스가 종료될 때까지 기다립니다(약 30초 소요).

  4. WPR로 돌아가서 추적을 저장하고 WPA(Windows Performance Analyzer)를 사용하여 엽니다.

  5. 추적 메뉴를 열고 기호 경로 구성을 선택합니다.

    • 기호 캐시의 경로를 지정합니다. 기호에 대한 자세한 내용은 MSDN의 기호 지원 페이지를 참조하세요.
  6. 추적 메뉴를 열고 기호 로드를 선택합니다.

이제 수명 동안 MemoryTestApp.exe 프로세스에 대한 모든 메모리 할당 패턴을 포함하는 추적이 있습니다.

2단계: VirtualAlloc 동적 할당 검토

자세한 VirtualAlloc 데이터는 WPA의 ‘VirtualAlloc 커밋 수명’ 그래프를 통해 노출됩니다. 관심 있는 주요 열은 다음과 같습니다.

설명
Process

VirtualAlloc을 통해 메모리 할당을 수행하는 프로세스의 이름입니다.

커밋 스택

할당되는 메모리로 이어지는 코드 경로를 보여 주는 호출 스택입니다.

커밋 시간

메모리가 할당된 시점의 타임스탬프입니다.

커밋 해제 시간

메모리가 해제된 시점의 타임스탬프입니다.

영향을 미치는 크기

미해결 할당의 크기 또는 선택한 시간 간격의 시작과 종료 사이의 크기 차이입니다. 이 크기는 선택한 뷰포트에 따라 조정됩니다.

프로세스에 의해 할당된 모든 메모리가 WPA의 시각화된 간격이 종료될 때까지 해제되는 경우 영향을 미치는 크기 값은 0이 됩니다.

크기

선택한 시간 간격 동안 모든 할당의 누적 합계입니다.

다음 단계에 따라 MemoryTestApp.exe를 분석합니다.

  1. Graph 탐색기메모리 범주에서 VirtualAlloc 커밋 수명 그래프를 찾습니다.

  2. VirtualAlloc 커밋 수명분석 탭으로 끌어다 놓습니다.

  3. 이러한 열을 표시하도록 테이블을 구성합니다. 열 헤더를 마우스 오른쪽 단추로 클릭하여 열을 추가하거나 제거합니다.

    1. 처리

    2. 영향을 미치는 형식

    3. 커밋 스택

    4. 커밋 시간커밋 해제 시간

    5. 개수

    6. 영향을 미치는 크기크기

  4. 프로세스 목록에서 MemoryTestApp.exe를 찾습니다.

  5. 그래프에 MemoryTestApp.exe만 유지하도록 필터를 적용합니다.

    • 마우스 오른쪽 단추를 클릭하고 선택 항목에 대한 필터를 선택합니다.

결과를 필터링하는 방법을 보여 주는 스크린샷

분석 뷰포트는 다음과 유사합니다.

필터링 시 데이터가 어떻게 보이는지 보여 주는 예제 그래프

앞의 예제에서 두 값이 중요합니다.

  • 126MB의 크기: 이는 MemoryTestApp.exe가 수명 동안 총 125MB를 할당했음을 나타냅니다. 프로세스 및 해당 종속성에 의해 수행된 모든 VirtualAlloc API 호출의 누적 합계를 나타냅니다.

  • 0MB의 영향을 미치는 크기: 이는 현재 분석 중인 시간 간격이 종료될 때까지 프로세스에 의해 할당된 모든 메모리가 해제되었음을 나타냅니다. 시스템은 안정적인 상태 메모리 사용량의 증가로 어려움을 겪지 않았습니다.

2.1단계: 안정적인 상태 메모리 사용량 분석

메모리 할당을 조사할 때 “이 시나리오에서 안정적인 상태 메모리 사용량이 증가하는 이유는 무엇인가요?”라는 질문에 답변해야 합니다. MemoryTestApp.exe 예제에서 처음에는 약 10MB의 안정적인 상태 메모리가 할당되었다가 중간에 20MB로 증가하는 것을 볼 수 있습니다.

메모리 사용량을 보여 주는 샘플 데이터 스크린샷

이 동작을 조사하려면 추적 중간에 급격한 증가가 발생한 시간 간격으로 확대/축소 범위를 좁혀야 합니다.

데이터를 확대하는 방법을 보여 주는 스크린샷

뷰포트는 다음과 같이 표시되어야 합니다.

VirtualAlloc 커밋 수명 및 Outstanding Commit by Process로 그래프를 보여 주는 확대/축소 옵션을 적용한 후의 샘플 데이터 스크린샷

이와 같이 영향을 미치는 크기는 이제 10MB입니다. 즉, 분석 중인 시간 간격의 시작과 종료 사이에 안정적인 상태 메모리 사용량이 10MB 증가합니다.

  1. 열 헤더를 클릭하여 영향을 미치는 크기에 따라 정렬합니다.

  2. 프로세스 열에서 MemoryTestApp.exe 행을 확장합니다.

  3. 영향을 미치는 형식 열에서 영향을 미치는 행을 확장합니다.

  4. 10MB의 메모리를 할당한 함수를 찾을 때까지 커밋 스택 프로세스를 탐색합니다.

    줄 번호, 프로세스, 영향을 미치는 형식, 커밋 스택, 커밋 시간, 커밋 해제 시간, 개수, 영향을 미치는 크기를 보여 주는 샘플 데이터 테이블의 스크린샷

이 예제에서 MemoryTestApp.exeMain 함수는 VirtualAlloc을 직접 호출하여 워크로드 중간에 10MB의 메모리를 할당합니다. 실제 환경에서 애플리케이션 개발자는 할당이 적절한지 또는 안정적인 상태 메모리 사용 증가를 최소화하기 위해 코드를 다시 정렬할 수 있는지를 결정해야 합니다.

이제 WPA에서 뷰포트를 확대/축소 해제할 수 있습니다.

확대/축소 해제 메뉴 스크린샷

2.2단계: 일시적인(또는 최대) 메모리 사용량 분석

메모리 할당을 조사할 때 “시나리오의 이 부분에 대한 메모리 사용량이 일시적으로 최대인 이유는 무엇인가요?”라는 질문에 답변해야 합니다. 일시적인 할당은 메모리 사용량이 급증하게 하고 메모리 압력이 있을 때 조각화를 유도하고 시스템 대기 캐시에서 중요한 콘텐츠를 푸시할 수 있습니다.

MemoryTest 예제에서는 추적에 균등하게 분산된 메모리 사용량(10MB)의 10번의 급등이 있음을 확인할 수 있습니다.

메모리 사용량 데이터를 보여 주는 그래프 스크린샷

확대/축소를 마지막 네 번의 급증으로 좁혀서 더 작은 관심 영역에 중점을 두고 관련 없는 동작으로 인한 노이즈를 줄입니다.

확대/축소 옵션의 스크린샷

뷰포트는 다음과 같이 표시되어야 합니다.

확대/축소 옵션을 사용하여 메모리 사용량 데이터를 보여 주는 그래프의 스크린샷

  1. 열 헤더를 클릭하여 크기에 따라 정렬합니다.

  2. 프로세스 열에서 MemoryTestApp.exe 행을 확장합니다.

  3. 영향을 미치는 형식 열에서 일시적인 행을 클릭합니다.

    • 뷰포트의 메모리 사용량 급증이 모두 파란색으로 강조 표시되어야 합니다.
  4. 다른 열의 값을 확인합니다.

    1. Count = 4: 해당 시간 간격 동안 4번의 일시적인 메모리 할당이 수행되었음을 나타냅니다.

    2. Impacting Size = 0 MB: 시간 간격이 종료될 때까지 4번의 일시적인 메모리 할당이 모두 해제되었음을 나타냅니다.

    3. Size = 40 MB: 4번의 일시적인 메모리 할당 합계가 40MB임을 나타냅니다.

  5. 40MB의 메모리를 할당한 함수를 찾을 때까지 커밋 스택 프로세스를 탐색합니다.

    메모리 사용량 데이터의 스크린샷

이 예에서 MemoryTestApp.exeMain 함수는 Operation1이라는 함수를 호출하고 이 함수는 ManipulateTemporaryBuffer라는 함수를 호출합니다. 그런 다음 이 ManipulateTemporaryBuffer 함수는 VirtualAlloc을 네 번 직접 호출하여 매번 10MB 메모리 버퍼를 만들고 해제합니다. 버퍼는 각각 100마이크로초만 지속됩니다. 버퍼의 할당 및 사용 시간은 커밋 시간커밋 해제 시간 열로 표시됩니다.

실제 환경에서 애플리케이션 개발자는 수명이 짧은 일시적인 임시 버퍼 할당이 필요한지 또는 작업에 영구 메모리 버퍼를 사용하여 바꿀 수 있는지를 결정합니다.

이제 WPA에서 뷰포트를 확대/축소 해제할 수 있습니다.

3단계: 힙 동적 할당 검토

지금까지 분석은 VirtualAlloc API에서 처리되는 대규모 메모리 할당에만 중점을 두었습니다. 다음 단계는 처음에 수집된 힙 데이터를 사용하여 프로세스에서 수행한 다른 작은 할당에 문제가 있는지 확인하는 것입니다.

자세한 힙 데이터는 WPA의 “힙 할당” 그래프를 통해 노출됩니다. 관심 있는 주요 열은 다음과 같습니다.

설명
Process 메모리 할당을 수행하는 프로세스의 이름입니다.
Handle

할당을 서비스하는 데 사용되는 힙의 식별자입니다.

힙을 만들 수 있으므로 프로세스에 대한 힙 핸들이 여러 개 있을 수 있습니다.

스택 메모리가 할당되는 코드 경로를 보여 주는 호출 스택입니다.
할당 시간 메모리가 할당된 시점의 타임스탬프입니다.
영향을 미치는 크기 미해결 할당의 크기 또는 선택한 뷰포트의 시작과 종료 사이의 차이입니다. 이 크기는 선택한 시간 간격에 따라 조정됩니다.
크기 모든 할당/할당 취소의 누적 합계입니다.

다음 단계에 따라 MemoryTestApp.exe를 분석합니다.

  1. Graph 탐색기메모리 범주에서 힙 할당 그래프를 찾습니다.

  2. 힙 할당분석 탭으로 끌어다 놓습니다.

  3. 다음 열을 표시하도록 테이블을 구성합니다.

    1. 처리

    2. Handle

    3. 영향을 미치는 형식

    4. 스택

    5. 할당 시간

    6. 개수

    7. 영향을 미치는 크기크기

  4. 프로세스 목록에서 MemoryTestApp.exe를 찾습니다.

  5. 그래프에 MemoryTestApp.exe만 유지하도록 필터를 적용합니다.

    • 마우스 오른쪽 단추를 클릭하고 선택 항목에 대한 필터를 선택합니다.

뷰포트는 다음과 같이 표시되어야 합니다.

프로세스 및 핸들별 미해결 크기의 힙 할당 그래프를 보여 주는 샘플 데이터 스크린샷

이 예제에서는 힙 중 하나가 일정한 속도로 시간이 지남에 따라 크기가 꾸준히 증가하는 것을 볼 수 있습니다. 해당 힙에는 1,200개의 메모리 할당이 있으며 간격이 종료될 때까지 사용된 메모리의 130KB를 차지합니다.

  1. 추적 중간에 더 짧은 간격(예: 10초)으로 확대합니다.

  2. 가장 많은 할당량을 표시하는 헤드 핸들을 확장합니다(영향을 미치는 크기 열 참조).

  3. 영향을 미치는 형식을 확장합니다.

  4. 이 모든 메모리 할당을 담당하는 함수를 찾을 때까지 스택 프로세스를 탐색합니다.

    두 행이 선택된 프로세스, 핸들, 영향을 미치는 형식, 스택, 할당 시간, 개수, 영향을 미치는 크기, 크기를 보여 주는 샘플 데이터 테이블의 스크린샷

이 예제에서 MemoryTestApp.exeMain 함수는 InnerLoopOperation이라는 함수를 호출합니다. 그런 다음 이 InnerLoopOperation 함수는 C++ 연산자를 통해 40바이트의 메모리를 319번 할당합니다. 이 메모리는 프로세스가 종료될 때까지 할당된 상태로 유지됩니다.

실제 환경에서 애플리케이션 개발자는 이 동작이 메모리 누수 가능성을 의미하는지 확인하고 문제를 해결해야 합니다.

4단계: 테스트 시스템 정리

분석이 완료되면 레지스트리를 정리하여 프로세스에 대해 힙 추적을 사용하지 않도록 설정해야 합니다. 관리자 권한 명령 프롬프트에서 다음 명령을 실행합니다.

reg delete "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\MemoryTestApp.exe" /v TracingFlags /f