Performance-Optimized 코드 디버깅

Microsoft에는 컴파일된 코드와 연결된 코드를 다시 정렬하여 효율성을 높이는 데 사용하는 특정 기술이 있습니다. 이러한 기술은 메모리 계층 구조에 대한 구성 요소를 최적화하고 학습 시나리오를 기반으로 합니다.

결과 최적화는 페이징(및 페이지 오류)을 줄이고 코드와 데이터 간의 공간 지역성을 증가시킵니다. 원래 코드의 잘못된 위치 지정으로 인해 발생하는 주요 성능 병목 상태를 해결합니다. 이 최적화를 통과한 구성 요소에는 함수 내의 코드 또는 데이터 블록이 이진 파일의 다른 위치로 이동되었을 수 있습니다.

이러한 기술로 최적화된 모듈에서는 코드 및 데이터 블록의 위치가 일반적인 컴파일 및 연결 후에 상주하는 위치와 다른 메모리 주소에서 종종 발견됩니다. 또한 가장 일반적으로 사용되는 코드 경로를 동일한 페이지에서 서로 가까이 배치할 수 있도록 함수가 연속되지 않은 여러 블록으로 분할되었을 수 있습니다.

따라서 함수(또는 기호)와 오프셋이 최적화되지 않은 코드에서 반드시 동일한 의미를 갖는 것은 아닙니다.

Performance-Optimized 코드 디버깅

디버깅할 때 기호가 로드된 모듈에서 !lmi 확장 명령을 사용하여 모듈이 성능 최적화되었는지 확인할 수 있습니다.

0:000> !lmi ntdll
Loaded Module Info: [ntdll]
         Module: ntdll
   Base Address: 77f80000
     Image Name: ntdll.dll
   Machine Type: 332 (I386)
     Time Stamp: 394193d2 Fri Jun 09 18:03:14 2000
       CheckSum: 861b1
Characteristics: 230e stripped perf
Debug Data Dirs: Type Size     VA  Pointer
                 MISC  110,     0,   76c00 [Data not mapped]
     Image Type: DBG      - Image read successfully from symbol server.
                 c:\symbols\dll\ntdll.dbg
    Symbol Type: DIA PDB  - Symbols loaded successfully from symbol server.
                 c:\symbols\dll\ntdll.pdb

이 출력에서는 "특성" 줄에서 용어 성능 에 주목합니다. 이는 이 성능 최적화가 ntdll.dll 적용되었음을 나타냅니다.

디버거는 오프셋 없이 함수 또는 다른 기호를 이해할 수 있습니다. 이렇게 하면 아무 문제 없이 함수 또는 다른 레이블에 중단점을 설정할 수 있습니다. 그러나 이 디스어셈블리는 최적화 프로그램의 변경 내용을 반영하므로 디스어셈블리 작업의 출력은 혼동될 수 있습니다.

디버거가 원래 코드에 가깝게 유지하려고 하므로 몇 가지 재미있는 결과가 표시될 수 있습니다. 성능 최적화 코드를 사용할 때는 최적화된 코드에서 신뢰할 수 있는 주소 산술 연산을 수행할 수 없다는 규칙이 있습니다.

예를 들면 다음과 같습니다.

kd> bl
 0 e f8640ca6     0001 (0001) tcpip!IPTransmit
 1 e f8672660     0001 (0001) tcpip!IPFragment

kd> u f864b4cb
tcpip!IPTransmit+e48:
f864b4cb f3a4             rep     movsb
f864b4cd 8b75cc           mov     esi,[ebp-0x34]
f864b4d0 8b4d10           mov     ecx,[ebp+0x10]
f864b4d3 8b7da4           mov     edi,[ebp-0x5c]
f864b4d6 8bc6             mov     eax,esi
f864b4d8 6a10             push    0x10
f864b4da 034114           add     eax,[ecx+0x14]
f864b4dd 57               push    edi

중단점 목록에서 IPTransmit 의 주소가 0xF8640CA6 것을 확인할 수 있습니다.

0xF864B4CB 이 함수 내의 코드 섹션을 언어셈블하면 출력은 함수의 시작 부분을 0xE48 바이트임을 나타냅니다. 그러나 이 주소에서 함수의 기본을 빼면 실제 오프셋이 0xA825 것처럼 보입니다.

무슨 일이 일어나고 있는지: 디버거는 실제로 0xF864B4CB 시작하는 이진 명령의 디스어셈블리를 표시하고 있습니다. 그러나 디버거는 간단한 빼기로 오프셋을 계산하는 대신 최적화를 수행하기 전에 원래 코드에 존재했던 것처럼 함수 항목에 대한 오프셋을 최대한 표시합니다. 해당 값은 0xE48.

반면에 IPTransmit+0xE48 확인하려고 하면 다음이 표시됩니다.

kd> u tcpip!iptransmit+e48
tcpip!ARPTransmit+d8:
f8641aee 0856ff           or      [esi-0x1],dl
f8641af1 75fc             jnz     tcpip!ARPTransmit+0xd9 (f8641aef)
f8641af3 57               push    edi
f8641af4 e828eeffff       call    tcpip!ARPSendData (f8640921)
f8641af9 5f               pop     edi
f8641afa 5e               pop     esi
f8641afb 5b               pop     ebx
f8641afc c9               leave

여기서 발생하는 일은 디버거가 IPTransmit 기호를 주소 0xF8640CA6 해당하는 것으로 인식하고 명령 파서는 간단한 추가를 수행하여 0xF8640CA6 + 0xE48 = 0xF8641AEE. 그런 다음 이 주소는 u(Unassemble) 명령에 대한 인수로 사용됩니다. 그러나 이 위치가 분석되면 디버거는 IPTransmit 와 0xE48 오프셋이 아니라는 것을 발견합니다. 실제로 이 함수의 일부가 아닙니다. 대신 ARPTransmit 함수와 0xD8 오프셋에 해당합니다.

이 문제는 주소 산술 연산을 통해 성능 최적화를 되돌릴 수 없기 때문입니다. 디버거는 주소를 가져와 원래 기호와 오프셋을 추론할 수 있지만 기호와 오프셋을 가져와 올바른 주소로 변환하기에 충분한 정보가 없습니다. 따라서 디스어셈블리는 이러한 경우에 유용하지 않습니다.