다음을 통해 공유


최적화된 코드 및 인라인 함수 디버깅

Windows 8 경우 최적화된 코드를 디버그하고 인라인 함수를 디버그할 수 있도록 디버거 및 Windows 컴파일러가 향상되었습니다. 디버거는 레지스터 또는 스택에 저장되었는지 여부에 관계없이 매개 변수 및 지역 변수를 표시합니다. 디버거는 호출 스택에 인라인 함수도 표시합니다. 인라인 함수의 경우 디버거는 지역 변수를 표시하지만 매개 변수는 표시하지 않습니다.

코드가 최적화되면 더 빠르게 실행되고 메모리를 적게 사용하도록 변환됩니다. 경우에 따라 데드 코드 제거, 코드 병합 또는 인라인으로 배치되는 함수의 결과로 함수가 제거됩니다. 지역 변수 및 매개 변수도 제거할 수 있습니다. 많은 코드 최적화는 필요하지 않거나 사용되지 않는 지역 변수를 제거합니다. 다른 최적화는 루프에서 유도 변수를 제거합니다. 공통 하위 식 제거는 지역 변수를 함께 병합합니다.

Windows의 소매 빌드가 최적화되어 있습니다. 따라서 Windows의 소매 빌드를 실행하는 경우 최적화된 코드와 잘 작동하도록 설계된 디버거를 사용하는 것이 특히 유용합니다. 최적화된 코드를 효과적으로 디버깅하려면 두 가지 주요 기능이 필요합니다. 1) 지역 변수의 정확한 표시 및 2) 호출 스택에 인라인 함수 표시.

지역 변수 및 매개 변수의 정확한 표시

지역 변수 및 매개 변수의 정확한 표시를 용이하게 하기 위해 컴파일러는 PDB(기호) 파일의 지역 변수 및 매개 변수 위치에 대한 정보를 기록합니다. 이러한 위치 레코드는 변수의 스토리지 위치 및 이러한 위치가 유효한 특정 코드 범위를 추적합니다. 이러한 레코드는 변수의 위치(레지스터 또는 스택 슬롯)를 추적하는 데 도움이 될 뿐만 아니라 변수의 이동도 추적할 수 있습니다. 예를 들어 매개 변수는 먼저 레지스터 RCX에 있을 수 있지만 RCX를 해제하기 위해 스택 슬롯으로 이동한 다음 루프에서 많이 사용되는 경우 R8을 등록하도록 이동한 다음 코드가 루프에서 벗어날 때 다른 스택 슬롯으로 이동합니다. Windows 디버거는 PDB 파일의 풍부한 위치 레코드를 사용하고 현재 명령 포인터를 사용하여 지역 변수 및 매개 변수에 적합한 위치 레코드를 선택합니다.

Visual Studio의 Locals 창 스크린샷은 최적화된 64비트 애플리케이션의 함수에 대한 매개 변수 및 지역 변수를 보여 줍니다. 함수가 인라인이 아니므로 매개 변수와 지역 변수가 모두 표시됩니다.

최적화된 64비트 애플리케이션에서 함수에 대한 매개 변수 및 지역 변수를 표시하는 Visual Studio의 로컬 창 스크린샷

dv -v 명령을 사용하여 매개 변수 및 지역 변수의 위치를 볼 수 있습니다.

dv -v 명령을 사용하여 매개 변수 및 지역 변수의 위치를 표시하는 명령 출력의 스크린샷

지역 창에는 매개 변수가 레지스터에 저장되어 있더라도 매개 변수가 올바르게 표시됩니다.

위치 레코드는 기본 형식을 사용하여 변수를 추적하는 것 외에도 로컬 구조 및 클래스의 데이터 멤버를 추적합니다. 다음 디버거 출력은 로컬 구조를 표시합니다.

0:000> dt My1
Local var Type _LocalStruct
   +0x000 i1               : 0n0 (edi)
   +0x004 i2               : 0n1 (rsp+0x94)
   +0x008 i3               : 0n2 (rsp+0x90)
   +0x00c i4               : 0n3 (rsp+0x208)
   +0x010 i5               : 0n4 (r10d)
   +0x014 i6               : 0n7 (rsp+0x200)

0:000> dt My2
Local var @ 0xefa60 Type _IntSum
   +0x000 sum1             : 0n4760 (edx)
   +0x004 sum2             : 0n30772 (ecx)
   +0x008 sum3             : 0n2 (r12d)
   +0x00c sum4             : 0n0

다음은 이전 디버거 출력에 대한 몇 가지 관찰입니다.

  • 로컬 구조 My1 은 컴파일러가 로컬 구조 데이터 멤버를 레지스터 및 인접하지 않은 스택 슬롯으로 분산할 수 있음을 보여 줍니다.
  • dt My2 명령의 출력은 dt _IntSum 0xefa60 명령의 출력과 다릅니다. 로컬 구조체가 스택 메모리의 연속 블록을 차지한다고 가정할 수 없습니다. My2의 경우 원래 스택 블록에만 sum4 유지되고 다른 세 개의 데이터 멤버는 레지스터로 이동됩니다.
  • 일부 데이터 멤버에는 여러 위치가 있을 수 있습니다. 예를 들어 My2.sum2 에는 두 위치가 있습니다. 하나는 등록 ECX(Windows 디버거가 선택하는 위치)이고 다른 위치는 0xefa60+0x4(원래 스택 슬롯)입니다. 이는 기본 형식 지역 변수에도 발생할 수 있으며 Windows 디버거는 사용할 위치를 결정하기 위해 선례 추론을 적용합니다. 예를 들어 등록 위치는 항상 스택 위치를 능가합니다.

호출 스택에 인라인 함수 표시

코드 최적화 중에 일부 함수가 줄에 배치됩니다. 즉, 함수의 본문은 매크로 확장과 같은 코드에 직접 배치됩니다. 함수 호출이 없고 호출자에게 반환되지 않습니다. 인라인 함수의 표시를 용이하게 하기 위해 컴파일러는 인라인 함수(즉, 인라인으로 배치되는 호출 수신자 함수에 속하는 호출자 함수의 코드 블록 시퀀스)와 지역 변수(해당 코드 블록의 범위가 지정된 지역 변수)에 대한 코드 청크를 디코딩하는 데 도움이 되는 데이터를 PDB 파일에 저장합니다. 이 데이터는 디버거가 스택 해제의 일부로 인라인 함수를 포함하는 데 도움이 됩니다.

애플리케이션을 컴파일하고 라는 func1 함수를 인라인으로 강제 적용한다고 가정합니다.

__forceinline int func1(int p1, int p2, int p3)
{
   int num1 = 0;
   int num2 = 0;
   int num3 = 0;
   ...
}

bm 명령을 사용하여 에서 func1중단점을 설정할 수 있습니다.

0:000> bm MyApp!func1
  1: 000007f6`8d621088 @!"MyApp!func1" (MyApp!func1 inlined in MyApp!main+0x88)
0:000> g

Breakpoint 1 hit
MyApp!main+0x88:
000007f6`8d621088 488d0d21110000  lea     rcx,[MyApp!`string' (000007f6`8d6221b0)]

한 단계씩 으로 들어가 func1k 명령을 사용하여 호출 스택에서 볼 func1 수 있습니다. dv 명령을 사용하여 에 대한 func1지역 변수를 볼 수 있습니다. 지역 변수 num3 는 사용할 수 없음으로 표시됩니다. 여러 가지 이유로 최적화된 코드에서 지역 변수를 사용할 수 없습니다. 변수가 최적화된 코드에 없는 것일 수 있습니다. 변수가 아직 초기화되지 않았거나 변수가 더 이상 사용되지 않을 수 있습니다.

0:000> p
MyApp!func1+0x7:
000007f6`8d62108f 8d3c33          lea     edi,[rbx+rsi]

0:000> knL
# Child-SP          RetAddr           Call Site
00 (Inline Function) --------`-------- MyApp!func1+0x7
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f
02 00000000`0050fcf0 000007ff`c6af0f7d MyApp!__tmainCRTStartup+0x10f
03 00000000`0050fd20 000007ff`c7063d6d KERNEL32!BaseThreadInitThunk+0xd
04 00000000`0050fd50 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

0:000> dv -v
00000000`0050fcb0            num1 = 0n0
00000000`0050fcb4            num2 = 0n0
<unavailable>                num3 = <value unavailable>

스택 추적에서 프레임 1을 보면 함수의 지역 변수를 main 볼 수 있습니다. 두 개의 변수가 레지스터에 저장됩니다.

0:000> .frame 1
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f

0:000> dv -v
00000000`0050fd08               c = 0n7
@ebx                            b = 0n13
@esi                            a = 0n6

Windows 디버거는 PDB 파일의 데이터를 집계하여 특정 함수가 인라인으로 배치된 모든 위치를 찾습니다. x 명령을 사용하여 인라인 함수의 모든 호출자 사이트를 나열할 수 있습니다.

0:000> x simple!MoreCalculate
00000000`ff6e1455 simple!MoreCalculate =  (inline caller) simple!wmain+8d
00000000`ff6e1528 simple!MoreCalculate =  (inline caller) simple!wmain+160

0:000> x simple!Calculate
00000000`ff6e141b simple!Calculate =  (inline caller) simple!wmain+53

Windows 디버거는 인라인 함수의 모든 호출자 사이트를 열거할 수 있으므로 호출자 사이트의 오프셋을 계산하여 인라인 함수 내부에 중단점을 설정할 수 있습니다. bm 명령(정규식 패턴과 일치하는 중단점을 설정하는 데 사용됨)을 사용하여 인라인 함수에 대한 중단점을 설정할 수 있습니다.

Windows 디버거는 특정 인라인 함수에 대해 설정된 모든 중단점을 중단점 컨테이너로 그룹화합니다. bd, bc와 같은 명령을 사용하여 중단점 컨테이너 전체를 조작 수 있습니다. 다음 bd 3bc 3 명령 예제를 참조하세요. 개별 중단점을 조작할 수도 있습니다. 다음 2 명령 예제를 참조하세요.

0:000> bm simple!MoreCalculate
  2: 00000000`ff6e1455 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x8d)
  4: 00000000`ff6e1528 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x160)

0:000> bl
 0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52]    0001 (0001)  0:**** simple!wmain
 3 e <inline function>     0001 (0001)  0:**** {simple!MoreCalculate}
     2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58]    0001 (0001)  0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
     4 e 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72]    0001 (0001)  0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)

0:000> bd 3
0:000> be 2

0:000> bl
 0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52]    0001 (0001)  0:**** simple!wmain
 3 d <inline function>     0001 (0001)  0:**** {simple!MoreCalculate}
     2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58]    0001 (0001)  0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
     4 d 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72]    0001 (0001)  0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)

0:000> bc 3

0:000> bl
 0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52]    0001 (0001)  0:**** simple!wmain

인라인 함수에 대한 명시적 호출 또는 반환 지침이 없으므로 디버거의 경우 소스 수준 단계별 실행이 특히 어렵습니다. 예를 들어 의도치 않게 인라인 함수를 한 단계씩 실행하거나(다음 명령이 인라인 함수의 일부인 경우) 동일한 인라인 함수를 한 단계씩 실행하여 한 단계씩 실행할 수 있습니다(인라인 함수에 대한 코드 블록이 컴파일러에 의해 분할 및 이동되었기 때문). 친숙한 단계별 실행 환경을 유지하기 위해 Windows 디버거는 모든 코드 명령 주소에 대해 작은 개념적 호출 스택을 유지하고 내부 상태 컴퓨터를 빌드하여 단계별 실행, 단계별 실행 및 단계별 작업을 실행합니다. 이렇게 하면 비인라인 함수에 대한 단계별 실행 환경에 합리적으로 정확한 근사치를 제공합니다.

추가 정보

참고.inline 0 명령을 사용하여 인라인 함수 디버깅을 사용하지 않도록 설정할 수 있습니다. .inline 1 명령을 사용하면 인라인 함수 디버깅을 사용할 수 있습니다. 표준 디버깅 기술

참고 항목

표준 디버깅 기술