출처 : http://msdn.microsoft.com/ko-kr/magazine/dd252945.aspx

 

Windows With C++
의사 변수와 형식 지정자를 사용한 X64 디버깅
Kenny Kerr
여러 해 동안 Visual C++에는 디버깅에 사용하기 위한 의사 변수와 형식 지정자가 추가되었습니다. 여기에서 의사 변수란 디버거 조사 창에 입력하여 C++ 변수와 연관되지 않은 값도 표시할 수 있는 변수를 의미합니다. 그러나 의사 변수에 대한 구체적인 정보는 공개되지 않았습니다. 필자도 이러한 정보를 대신 제공할 수 있을 만큼 충분한 내부 자료는 없지만 경험을 통해 알아낸 유용한 몇 가지 의사 변수와 형식 지정자를 소개하고자 합니다. 이 기사를 통해서 이 주제에 대한 흥미를 가질 수 있기를 기대합니다.
의사 변수(종종 의사 레지스터라고도 불림)를 소개하기에 앞서 프로세서 아키텍처와 레지스터에 대해 간단히 설명하겠습니다. 이러한 약간의 배경 정보를 이해하면 의사 변수를 더 유용하게 활용할 수 있습니다. 또한 응용 프로그램의 64비트 버전이 기존 32비트 응용 프로그램과 어떻게 다른지 이해하는 데도 도움이 됩니다. 다만 이 칼럼은 프로세서 아키텍처의 세부적인 부분을 다루는 것이 아니라 Visual C++에 대한 것이므로 x86 및 x64 프로세서 아키텍처에 대한 내용으로 제한할 것입니다. Itanium 프로세서에 추가된 차이점에 대해서는 MSDN Library에서 관련 설명서를 읽어 보십시오.


 

프로세서 아키텍처
일반인이 프로세서 아키텍처를 완전하게 이해하기란 쉽지 않습니다. C 언어와 후속 언어가 소프트웨어 업계에 큰 영향을 준 것도 이 때문입니다. 최소한 현재 가장 많이 사용되고 있는 프로세서인 x86 및 x64 프로세서 아키텍처가 무엇을 나타내는지를 이해하면 됩니다. Windows 및 Visual C++는 Itanium 프로세서를 지원하지만 이를 대상으로 개발하는 개발자는 많지 않습니다.
8비트 Intel 8080 프로세서에 기반을 두는 x86은 지난 몇십 년 동안 컴퓨터 시장을 지배해왔습니다. AMD와 Intel은 x64에서 x86에 대한 하위 버전과의 호환성을 제공하기 위해 실용적인 선택을 했습니다. 반면에 Itanium의 경우에는 기존의 x86에 제한되지 않는 강력한 새로운 아키텍처를 도입했습니다. 물론 결과적으로 지원되는 응용 프로그램의 수는 크게 줄었지만 Windows NT 개발을 이끌었던 David Cutler는 이식 가능한 운영 체제에 대한 그의 비전을 통해 시간에 지남에 따라 점차 적은 노력으로 새로운 아키텍처에 Windows를 적용할 수 있을 것이라는 약속을 했습니다.
프로세서 아키텍처의 차이점 중 많은 부분이 C++ 컴파일러에 의해 내부적으로 감춰지고 있지만 단일 호출 규칙으로 전환하는 데 따르는 장점은 개발자가 확실하게 확인할 수 있습니다. x86 컴파일러는 여러 호출 규칙을 지원하는 반면 x64 버전의 C++ 컴파일러는 단일 호출 규칙을 지원합니다. 이것은 환영할 만한 변화이며 특히 관리 코드 interop에서 C# 및 Visual Basic .NET과 같은 Microsoft .NET Framework 컴파일러가 C++ 헤더 파일의 원래 호출 규칙을 확인할 수 없는 상황에 발생하는 호출 규칙 불일치로 인한 여러 잠재적인 버그를 깔끔하게 해결할 수 있습니다. 기존에는 잘못 정의되는 경우가 많은 수동으로 만든 특성에 의존해야 했기 때문에 다양한 스택 손상 버그가 발생했습니다.
예를 들어 네이티브 C++ 호출 규칙에서는 멤버 함수를 호출하기 전에 thiscall이라고 불리는 "this" 포인터를 저장하기 위해 ecx 레지스터를 사용했습니다. 이러한 정보는 디버깅에 유용하지만 호출 규칙에 따라 차이가 있으며 어셈블러 코드와 레지스터 값을 보는 것만으로는 확실하게 알기 어려웠습니다. x64에서는 "this" 포인터가 항상 첫 번째 매개 변수로 삽입되며 이에 따라 rcx 레지스터에 저장되므로 훨씬 간단합니다.
자연스럽게 디버깅 시에 레지스터를 사용하는 방법은 프로세서에 따라 다르지만 다행스럽게도 x86과 x64 간의 관계를 활용할 수 있습니다. x86 아키텍처에서는 상위 레지스터의 하위 절반으로 구성된 하위 레지스터라는 개념이 도입되었습니다. 예를 들어 ax는 eax 레지스터의 하위 절반으로 구성된 하위 레지스터입니다. x64 아키텍처에서는 이를 확장하여 32비트 x86 레지스터의 상위 개념인 64비트 레지스터를 도입했습니다. 예를 들어 x64에서 eax는 64비트 rax 레지스터의 하위 절반으로 구성된 하위 레지스터입니다. eax와 rax는 각각 x86 및 x64에서 함수에서 반환하는 포인터나 정수로 사용되므로 흥미로울 것입니다. 자연스럽게 x86에서 64비트 값을 반환하는 함수는 한쌍의 레지스터를 사용해야 합니다.


 

의사 변수
가장 일반적인 의사 변수는 SetLastError 함수로 설정한 마지막 오류 값을 표시하는 $err일 것입니다. 표시되는 값은 GetLastError 함수에서 반환할 값을 나타냅니다.
의사 변수는 프로세서 레지스터 값을 표시하는 데도 사용할 수 있습니다. 예를 들어 $ecx는 x86 아키텍처에서 exc 레지스터의 값을 표시합니다. 프로세서 레지스터 값을 살펴보는 작업은 신세대 .NET 개발자에게는 다소 난해하게 보이겠지만 이해하기 어려운 버그를 진단하거나 프로그램의 런타임 동작을 이해하는 데 큰 도움이 됩니다. x86에서 네이티브 C++ 호출 규칙을 사용하는 경우에는 $ecx를 사용하여 "this" 포인터의 주소를 표시할 수 있습니다. 또한 두 아키텍처에서 모두 $eax를 사용하여 32비트 반환 값을 표시할 수 있습니다. 이 밖에도 사용자의 디버깅 요구에 따라 유용하게 사용할 수 있는 레지스터가 많이 있습니다.
다른 유용한 의사 변수에는 프로세스에서 커널 개체에 대한 열려 있는 핸들의 수를 표시하는 $handles와 현재 프로세스 및 스레드 토큰에 대한 매우 자세한 정보를 표시하는 $user가 있습니다. 후자는 가장 관련 코드를 디버깅하는 데 유용합니다. 그림 1에는 일반적인 의사 변수가 나와 있습니다.
의사 변수 설명
$handles 커널 개체에 대한 핸들의 수
$vframe 현재 스택 프레임 주소
$TID 현재 스레드 식별자
$registername 지정된 레지스터의 내용
$clk 시간(클록 주기)
$user 프로세스 및 스레드 토큰 정보


 

형식 지정자
형식 지정자는 조사 창에서 변수, 의사 변수 또는 식의 값이 표시되는 방법을 변경하려는 경우에 유용합니다. 대부분의 경우 조사 창은 값의 형식을 바탕으로 최적의 형식을 알아내지만 이를 변경하고 싶은 경우가 있습니다. 디버거가 변수의 형식을 유추하지 못하거나 자연적으로 형식 정보가 없는 메모리 주소를 사용하는 경우가 이에 해당합니다. 예를 들어 hr 형식 지정자는 값에 해당하는 Win32 또는 HRESULT를 표시합니다. 다른 예로는 null로 끝나는 유니코드 문자열로 값을 표시하는 su가 있습니다.
물론 형식이 지정된 텍스트의 실제 값을 얻으려는 경우가 있으며 이때는 !를 사용하면 됩니다. 마지막으로 조사 창에서 쉼표 뒤에 표시할 요소의 수를 지정하면 지정된 포인터를 n 개의 요소가 있는 배열로 처리할 수 있습니다. 그림 2에는 형식 지정자 목록이 나와 있습니다.
지정자 설명
D 십진수
U 부호 없는 십진수
O 8진수
X 16진수
F 부동 소수점
E 과학적 표기법
C 문자
S 문자열
Su 유니코드 문자열
s8 UTF-8 문자열
Hr HRESULT 또는 Win32 오류 코드
wc Windows 클래스
wm Windows 메시지
! 원시 형식


 

호출 규칙의 시각화
이러한 배경 정보를 바탕으로 몇 가지 예를 살펴보겠습니다. 다음과 같은 코드 조각이 있다고 가정해 보겠습니다. x86 컴파일러는 멤버 함수에 기본적으로 __thiscall 호출 규칙을 사용합니다. 이것은 매개 변수가 스택으로 푸시되고 "this" 포인터가 ecx 레지스터에 저장된다는 의미입니다.
struct Sample
{
    size_t m;
    Sample() : m(0x11223344) {}

    size_t Method(size_t p1, size_t p2)
    {
        return 0x44556677;
    }
};
그림 3에서는 이러한 사실과 몇 가지 추가적인 사실을 확인할 수 있습니다. 조사 창에 흥미로운 변수가 표시됩니다. $vframe은 현재 스택 프레임의 주소를 제공합니다. 이 의사 변수는 스택 프레임의 주소를 얻는 프로세서에 중립적인 방법을 제공합니다. 다음은 두 매개 변수의 주소가 나오며 각각 0x22334455 및 0x33445566입니다.
그림 3 32비트 스택 및 레지스터 (더 크게 보려면 이미지를 클릭하십시오.)
첫 번째 메모리 창도 현재 스택 프레임을 표시합니다. 스택에서 이러한 매개 변수의 값을 볼 수 있습니다. 다음은 ecx 레지스터이며 여기에서는 이를 개체의 멤버를 표시하도록 Sample에 대한 포인터로 캐스팅했습니다. 두 번째 메모리 창도 메모리의 개체를 표시합니다. 스택에서 아래쪽에 있는 멤버 변수를 볼 수 있습니다. 다음으로 $eax 변수는 함수 호출의 결과를 저장하는 레지스터의 값을 표시합니다.
같은 C++ 코드를 x64 컴파일러로 컴파일하면 상당히 다른 결과를 얻게 됩니다. 컴파일러는 x64에서 수가 크게 늘어난 레지스터를 활용하여 불필요하게 스택에 값을 푸시하는 횟수를 줄입니다. 구체적으로 살펴보면 처음 포인터나 정수 매개 변수 4개가 각각 rcx, rdx, r8 및 r9 레지스터에 저장됩니다. 멤버 함수의 경우 "this" 포인터가 첫 번째 매개 변수로 취급됩니다. 함수가 스택에 임의의 매개 변수를 임시로 저장해야 하는 경우 공간이 예약됩니다. 마지막으로 모든 포인터나 정수 결과는 rax 레지스터에 반환됩니다.
그림 4에는 이러한 예가 나와 있습니다. 이번에도 $vframe은 현재 스택 프레임의 주소를 표시하지만 이제는 모든 값이 64비트인 것을 볼 수 있습니다. 다음 두 매개 변수의 주소가 나오며 매개 변수는 호출된 함수에 레지스터로 전달되었지만 현재 스택 프레임에 이러한 매개 변수가 있는 것을 알 수 있습니다. 이것은 디버그를 위한 동작으로서 함수가 스택에 매개 변수를 저장한 결과입니다. 다음 세 개의 변수 $rcx, $r8 및 $rdx는 앞서 설명한 "this" 포인터를 포함하여 세 매개 변수의 값을 제공합니다. 마지막으로 $rax는 함수 호출의 결과를 저장하는 레지스터의 값을 표시합니다.
그림 4 64비트 스택 및 레지스터 (더 크게 보려면 이미지를 클릭하십시오.)


 

오류 코드
의사 변수와 형식 지정자는 다양한 형식과 위치의 오류 코드를 살펴보는 데 매우 유용합니다. 그림 5에는 마지막 오류 코드의 값과 설명을 제공하는 $err,hr과 같은 몇 가지 예가 나와 있습니다. 이러한 예는 볼륨 섀도 복사본 서비스의 ERROR_VOLMGR_DISK_NOT_EMPTY 오류 코드와 동일합니다. $err 의사 변수는 값을 제공하고 hr 형식 지정자는 조사 창에 이를 오류 코드로 형식을 지정하도록 지시합니다. hresult는 C++ 변수이며 조사 창은 자동으로 이를 나타내는 잘 알려진 상수를 표시합니다. E_INVALIDARG,x는 자동 형식 지정을 덮어쓰는 방법을 보여 줍니다. 이 경우에는 x 형식 지정자가 값을 16진수 값으로 표시하도록 조사 창에 지시합니다. 마지막 예는 거의 모든 주소를 배열로 변환하는 방법을 보여 줍니다.
그림 5 오류 코드(더 크게 보려면 이미지를 클릭하십시오.)


 

보안 컨텍스트 디버깅
그림 6에는 인상적인 $user 의사 변수가 나와 있습니다. 여기에서 Bob이 Alice를 가장하고 있음을 알 수 있습니다! 이 의사 변수는 프로세스와 스레드 토큰에 대한 많은 상세 정보를 제공하며 클라이언트/서버 응용 프로그램에서 가장 문제를 디버깅할 때 유용합니다.
그림 6 $user 의사 변수 (더 크게 보려면 이미지를 클릭하십시오.)
이 밖에도 Visual Studio 조사 창에서 사용할 수 있는 트릭과 C# 코드를 디버깅하는 데 도움이 되는 트릭이 있습니다. 또한 Debugging Tools for Windows에는 확장된 의사 변수 집합이 포함되어 있습니다. 추가적인 디버깅 관련 팁은 Advanced Windows Debugging을 참조하십시오.


 

질문이나 의견이 있으시면 mmwincpp@microsoft.com으로 보내시기 바랍니다.


 

Kenny Kerr는 Windows용 소프트웨어를 전문적으로 개발하는 소프트웨어 개발자로, 프로그래밍과 소프트웨어 설계 분야에서 왕성한 집필 및 교육 활동을 하고 있습니다. 문의 사항이 있으면 weblogs.asp.net/kennykerr를 방문하시기 바랍니다.
안정적인 DNS서비스 DNSEver DNS server, DNS service
Posted by 키르히아이스
,