Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 216. WinDbg - 2가지 유형의 식 평가 방법(MASM, C++) [링크 복사], [링크+제목 복사],
조회: 3818
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 2개 있습니다.)
디버깅 기술: 216. WinDbg - 2가지 유형의 식 평가 방법(MASM, C++)
; https://www.sysnet.pe.kr/2/0/13860

디버깅 기술: 219. WinDbg - 명령어 내에서 환경 변수 사용법
; https://www.sysnet.pe.kr/2/0/13910




WinDbg - 2가지 유형의 식 평가 방법(MASM, C++)

이번 글은 아래의 내용을 그대로 참조한 것입니다. ^^

WinDBG expression evaluation tutorial
; https://codemachine.com/articles/windbg_expressions.html

Evaluating Expressions
; https://learn.microsoft.com/en-us/windows-hardware/drivers/debuggercmds/evaluating-expressions

WinDbg. From A to Z!
; http://windbg.info/download/doc/pdf/WinDbg_A_to_Z_color.pdf

별도 설정을 하지 않았다면, WinDbg는 기본적으로 MASM 방식으로 식을 평가합니다.

6: kd> .expr
Current expression evaluator: MASM - Microsoft Assembler expressions

물론 기본값을 /s 옵션을 통해 바꾸는 것도 가능합니다.

// C++ 방식으로 변경
6: kd> .expr /s c++
Current expression evaluator: C++ - C++ source expressions

// MASM 방식으로 변경
6: kd> .expr /s masm
Current expression evaluator: MASM - Microsoft Assembler expressions

또는, 명시적으로 방법을 지정할 수 있는데, "?" 물음표 1개 명령어는 기본 설정된 방식의 평가를 하는 반면 "??" 2개 명령어는 언제나 "C++" 방식으로 평가합니다. 이후의 모든 예제는 기본 방식이 MASM으로 설정된 것을 가정하기 때문에 "?" 물음표 한 개를 사용했을 시 MASM 방식의 평가가 됩니다.




두 가지 평가 방식의 주요 차이점을 살펴볼까요? ^^


1) 심벌의 주소 평가

우선, MASM에서는 모든 심벌에 대해 주솟값으로 평가를 하는 반면 C++은 그런 해석을 하지 않습니다.

// MASM 방식에서는 nt 모듈을 나타내는 심벌을 그것의 로딩 주소로 평가
6: kd> ? nt
Evaluate expression: -8786477580288 = fffff802`3d200000

// 반면 C++ 방식에서는 오류
6: kd> ?? nt
Couldn't resolve error at 'nt'

재미있는 건 표현식 내에서 특정 영역을 MASM 또는 C++ 방식으로 해석을 바꿀 수 있는 "@@" 기호가 제공되므로, 이것을 도입하면 혼용이 가능합니다.

// 괄호 안의 식을 MASM 방식으로 평가
@@masm( ...[식]...)

// 괄호 안의 식을 C++ 방식으로 평가
@@c++( ...[식]...)

가령, 위에서 nt 심벌을 C++ 평가식에서 주소 참조를 하고 싶다면 다음과 같이 할 수 있습니다.

6: kd> ?? @@masm(nt)
unsigned int64 0xfffff802`3d200000

참고로 masm, c++을 지정하지 않고 단독으로 "@@" 기호만을 사용하면 현재 표현식의 평가 방식에서 무조건 다른 평가 방식을 사용하는 것으로 간주합니다. 즉, MASM 방식이었다면 C++ 방식으로, C++ 방식이었다면 MASM 방식으로 평가를 합니다.

// C++ 평가 방식에서 "@@" 기호를 통해 MASM 방식으로 평가
6: kd> ?? @@(nt)
unsigned int64 0xfffff802`3d200000

// MASM 방식에서, "@@" 기호를 통해 C++ 방식으로, 다시 "@@" 기호를 통해 MASM 방식으로 평가
6: kd> ? @@( @@(nt) )
Evaluate expression: -8786477580288 = fffff802`3d200000

마지막으로, MASM 평가 방식에서만 사용할 수 있는 @!"...[심벌 문자열]..." 구문이 있는데요, 심벌 내에 "<", ">"이나 공백과 같은 특수 문자가 포함된 경우에 사용할 수 있습니다.

// 사용자 모드의 C++ 프로그램을 디버깅하는 상황으로 가정

0:000> bu @!"ExecutableName!std::pair<unsigned int,std::basic_string<unsigned short,std::char_traits<unsigned short>,std::allocator<unsigned short> > >::operator="


2) 기본 진수(radix)

MASM 방식의 경우 별다른 접두사가 없다면 숫자에 대해 16진수로 평가하지만, C++ 방식에서는 10진수로 평가합니다.

// MASM 방식에서는 16진수로 평가
6: kd> n
base is 16

6: kd> ? 10
Evaluate expression: 16 = 00000000`00000010

// 기본 진법을 바꾸고 싶다면 n 명령어 사용
6: kd> n 10
base is 10

6: kd> ? 10
Evaluate expression: 10 = 00000000`0000000a

// C++ 방식에서는 10진수로 평가
6: kd> ?? 10
int 0n10

// 따라서, C++ 방식의 경우 16진수로 평가하고 싶다면 명시적으로 0x 접두사를 붙여야 함
6: kd> ?? 0x10
int 0n16


3) 레지스터 접근

이름에서 이미 유추할 수 있듯이, MASM 방식은 어셈블리어의 문법을 기반으로 식 평가를 지원하기 때문에 레지스터의 경우 이름을 그대로 사용할 수 있습니다.

// MASM 방식에서 rbx 레지스터 값을 평가
6: kd> ? rbx
Evaluate expression: -87374944424720 = ffffb088`6f79acf0

// C++ 방식에서는 오류
6: kd> ?? rbx
Couldn't resolve error at 'rbx'

// 대신, pseudo 레지스터 문법으로 접근
6: kd> ?? @rbx
unsigned int64 0xffffb088`6f79acf0

// MASM 방식에서도 '@' 기호를 명시해 로컬/전역 변수와 이름이 같은 경우를 위해 구분 용도로 사용 가능
// 예를 들어 로컬 변수로 rbx가 있다면, 아래의 식은 명식적으로 레지스터 rbx를 평가
6: kd> ? @rbx
Evaluate expression: -87374944424720 = ffffb088`6f79acf0

기타 WinDbg가 지원하는 사용자 정의 가상 레지스터에 대해서도 MASM 방식이 더 편리합니다.

// 사용자 정의 레지스터인 $t0에 0x1064 값을 설정
6: kd> r $t0 = 0x1064

// MASM 방식에서는 바로 평가
6: kd> ? $t0
Evaluate expression: 4196 = 00000000`00001064

// C++ 방식에서는 "@" 기호를 붙여야 함
6: kd> ?? $t0
Unexpected character in '$t0'

6: kd> ?? @$t0
unsigned int64 0x1064


4) 연산자 및 고유 언어별 확장

기타 연산자의 경우에는 2가지 방식 모두 동일하게 사용할 수 있습니다.

  • arithmetic (+ - * / %)
  • shift (<< >>)
  • bitwise ( & | ^)
  • comparison (== < > <= >= !=)

6: kd> ? (efl & ( 1 << 6)) != 0
Evaluate expression: 0 = 00000000`00000000

6: kd> ?? (@efl & ( 1 << 6)) != 0
bool false

단지 각자의 이름이 의미하는 것처럼 언어 차원에서 지원하거나 별도로 확장한 연산자가 있는 정도인데요, 가령 signed right shift 연산자(>>>)는 MASM에서만 지원합니다.

6: kd> ? 0xfa >>> 4
Evaluate expression: 15 = 00000000`0000000f

6: kd> ?? 0xfa >>> 4
Syntax error at '> 4'

// 그 외에 $scmp(), $sicmp(), $spat(), $vvalid 등의 확장 연산자도 MASM 방식에서만 지원

반면, sizeof, 구조체 및 배열 접근 연산자(-> . [])와 형변환 연산자 등은 C++ 방식에서만 지원합니다.

6: kd> ? sizeof(nt!_EPROCESS)
Couldn't resolve error at 'sizeof(nt!_EPROCESS)'

6: kd> ?? sizeof(nt!_EPROCESS)
unsigned int64 0xb80

6: kd> !process 0 0 notepad.exe
PROCESS ffffb0888f7bd0c0
    SessionId: 2  Cid: c508    Peb: 7f16d76000  ParentCid: 1b38
    DirBase: a75e6000  ObjectTable: ffffa08bafb18540  HandleCount: 929.
    Image: Notepad.exe

// MASM 방식에서는 구조체 멤버 접근을 위한 문법이 없으므로 dt 명령어 자체에서 제공하는 기능 사용
6: kd> dt _EPROCESS ffffb0888f7bd0c0 UniqueProcessId
nt!_EPROCESS
   +0x440 UniqueProcessId : 0x00000000`0000c508 Void

// C++ 방식에서는 구조체로의 형변환이 가능하고, 그것의 멤버 접근 가능
6: kd> ?? ((nt!_EPROCESS *)0xffffb0888f7bd0c0)->UniqueProcessId
void * 0x00000000`0000c508

6: kd> ?? @$proc->UniqueProcessId
void * 0x00000000`0000c508

이 외에도 C++ 방식에선 그 나름대로 잘 알려진 매크로 함수에 준하는 #CONTAINING_RECORD(), #FIELD_OFFSET(), #RTL_CONTAINS_FIELD(), #RTL_FIELD_SIZE(), #RTL_NUMBER_OF(), #RTL_SIZEOF_THROUGH_FIELD() 등의 몇 가지 함수를 지원합니다.

// C++ 방식에서만 지원
6: kd> ?? #FIELD_OFFSET(nt!_EPROCESS, UniqueProcessId)
long 0n1088




자, 그럼 여기까지 잘 이해했다면 지난 글의 마지막에 "win32k 서비스 함수"를 WinDbg 수식 한 줄로 찾는 실습을 해보겠습니다. ^^

// 실습 환경: Windows 10 (버전 10.0.19045.5247)
// 
// 함수표에서 NtGdiInvertRgn의 함수 번호는 Windows 10 환경에서 0x1064

// 1) 0x1064 함수 번호에서 0x1000을 뺀 오프셋을 구하고,
2: kd> dd /c1 win32k!W32pServiceTable + 4*(0x1064 - 0x1000) L1
ffffe8d1`72e59190  ff934140

// 2) 8바이트로 부호 확장
ff934140 ==> ffffffff`ff934140

// 3) 4비트 signed 우측 시프트
ffffffff`ff934140 ==> ffffffff`fff93414

// 4) 최종 주소 평가
2: kd> u win32k!W32pServiceTable + ffffffff`fff93414 L1
win32k!NtGdiInvertRgn:
ffffe8d1`72dec414 4883ec28        sub     rsp,28h

우선, MASM 방식으로 평가를 해볼까요? ^^

1) 단계는 해당 포인터가 가리키는 값에서 4바이트를 취해야 하는데요, 이를 위해 dwo 명령을 사용해 다음과 같이 작성할 수 있습니다.

// W32pServiceTable 심벌 풀이를 위해 프로세스 문맥으로 변경

// 대상 주소에서 4바이트 값을 반환
6: kd> ? dwo(win32k!W32pServiceTable + 4*(0x1064 - 0x1000))
Evaluate expression: 4287840576 = 00000000`ff934140

// poi, qwo 명령어의 경우 8바이트를 읽어 반환
6: kd> ? poi(win32k!W32pServiceTable + 4*(0x1064 - 0x1000))
Evaluate expression: -30098576766713536 = ff951180`ff934140

6: kd> ? qwo(win32k!W32pServiceTable + 4*(0x1064 - 0x1000))
Evaluate expression: -30098576766713536 = ff951180`ff934140

위의 값을 이제 2) 단계로 8바이트 부호 확장을 해야 하는데, 이게 좀 어렵습니다. 왜냐하면, dwo는 4바이트를 값을 주소로부터 읽어내기는 하지만 값 자체는 8바이트로 반환하기 때문에 부호 확장을 하려고 해도 상위 4바이트가 0으로 채워져 있어 음수 값으로 인식되지 않습니다.

어쩔 수 없이, 이런 경우에는 좌측 shift 연산자 + signed 우측 shift 연산자를 혼합해 동일한 효과를 내야 합니다.

// 4바이트 값을 8바이트로 부호 확장하는 효과

6: kd> ? dwo(win32k!W32pServiceTable + 4*(0x1064 - 0x1000)) << 0n32 >>> 0n32
Evaluate expression: -7126720 = ffffffff`ff934140

그럼 자연스럽게 3) 단계도 shift 크기만 늘리는 것으로 해결됩니다.

6: kd> ? dwo(win32k!W32pServiceTable + 4*(0x1064 - 0x1000)) << 0n32 >>> 0n36
Evaluate expression: -445420 = ffffffff`fff93414

마지막으로 4) 단계에서는 위에서 구한 주솟값을 그대로 "u" 명령어에 적용해 완성할 수 있습니다.

// Windows 10의 경우 win32k의 0x1064 함수는 NtGdiInvertRgn

0: kd> u win32k!W32pServiceTable + (dwo(win32k!W32pServiceTable + 4*(0x1064 - 0x1000)) << 0n32 >>> 0n36) L1
win32k!NtGdiInvertRgn:
fffffb93`0e2ac414 4883ec28        sub     rsp,28h

이제 동일한 과정을 C++ 방식으로 해볼까요? ^^

1) 단계는 심벌 해석을 MASM 방식으로 바꿔서 처리하는 것만 신경 쓰면 되는데요,

6: kd> ?? *(long *)(@@(win32k!W32pServiceTable) + 4*(0x1064 - 0x1000))
long 0n-7126720

6: kd> ? 0n-7126720
Evaluate expression: -7126720 = ffffffff`ff934140

재미있게도 MASM 방식과는 달리 4바이트 값을 읽기 위해 (long *) 형변환으로 처리했기 때문에 자연스럽게 8바이트로 부호 확장까지 이뤄져 2) 단계가 필요 없게 되었습니다. 따라서 곧바로 3) 단계 처리로 갈 수 있는데요, 문제는 C++ 방식의 경우 signed right shift 연산을 지원하지 않기 때문에 여기서 다시 MASM 방식과 혼용을 해야 합니다.

6: kd> ?? @@( @@( *(long *)(@@(win32k!W32pServiceTable) + 4*(0x1064 - 0x1000)) ) >>> 4 )
unsigned int64 0xffffffff`fff93414

==> ?? @@masm( @@c++( *(long *)(@@masm(win32k!W32pServiceTable) + 4*(0x1064 - 0x1000)) ) >>> 4 )

갑자기 복잡해졌죠? ^^ 어쨌든, 저렇게 평가한 주솟값을 4) 단계에서 u 명령어로 적용하면 되는데요, 이 과정에서 다시 식 평가 방식을 고려해야 합니다.

왜냐하면, "??" 명령어와 "u" 명령어는 각자 고유의 명령어이기 때문에 "?? u ..."와 같은 식으로 명령을 사용할 수는 없습니다. 즉, 독자적으로 "u" 명령어만 사용해야 하는데요, 그렇다면 이제 u 명령어 내부에서 식 평가를 하는 것이기 때문에 WinDbg의 기본 방식인 MASM 방식으로 평가가 됩니다.

따라서 최종 식은 다음과 같이 작성하면 됩니다.

6: kd> u win32k!W32pServiceTable + ( @@( *(long *)( @@(win32k!W32pServiceTable) + 4*(0x1064 - 0x1000) ) ) >>> 4 ) L1
win32k!NtGdiInvertRgn:
fffffb93`0e2ac414 4883ec28        sub     rsp,28h

==> u win32k!W32pServiceTable + ( @@c++( *(long *)( @@masm(win32k!W32pServiceTable) + 4*(0x1064 - 0x1000) ) ) >>> 4 ) L1

물론, 기본 평가 방식이 C++로 설정됐다면 이렇게 바꿔서 처리할 수 있고!

6: kd> .expr /s c++
Current expression evaluator: C++ - C++ source expressions

6: kd> u @@(win32k!W32pServiceTable) + @@( @@( *(long *)(@@(win32k!W32pServiceTable) + 4*(0x1064 - 0x1000)) ) >>> 4 ) L1
win32k!NtGdiInvertRgn:
fffffb93`0e2ac414 4883ec28        sub     rsp,28h

음... ^^; 의외로 복잡하죠? 왠지 저런 수식을 단 번에 만드는 것은 쉽지 않을 듯합니다. ^^ 어쨌든 저렇게 완성했으면, 마지막으로 사용자 정의 가상 레지스터를 곁들이면 그럴싸하게 공식처럼 사용하는 것도 가능합니다.

// 0x1064 함수 번호의 win32k 서비스 함수를 찾고 싶다면?
0: kd> r $t0 = 0x1059

6: kd> u win32k!W32pServiceTable + (dwo(win32k!W32pServiceTable + 4*($t0 - 0x1000)) << 0n32 >>> 0n36) L1
win32k!NtGdiPatBlt:
fffffb93`0e2ac67c 4883ec48        sub     rsp,48h

6: kd> u win32k!W32pServiceTable + ( @@( *(long *)(@@(win32k!W32pServiceTable) + 4*(@@($t0) - 0x1000)) ) >>> 4 ) L1
win32k!NtGdiPatBlt:
fffffb93`0e2ac67c 4883ec48        sub     rsp,48h




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 1/11/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...
NoWriterDateCnt.TitleFile(s)
1303정성태6/26/201227400개발 환경 구성: 152. sysnet DB를 SQL Azure 데이터베이스로 마이그레이션
1302정성태6/25/201229429개발 환경 구성: 151. Azure 웹 사이트에 사용자 도메인 네임 연결하는 방법
1301정성태6/20/201225764오류 유형: 156. KB2667402 윈도우 업데이트 실패 및 마이크로소프트 Answers 웹 사이트 대응
1300정성태6/20/201231771.NET Framework: 329. C# - Rabin-Miller 소수 생성방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 [1]파일 다운로드2
1299정성태6/18/201232888제니퍼 .NET: 21. 제니퍼 닷넷 - Ninject DI 프레임워크의 성능 분석 [2]파일 다운로드2
1298정성태6/14/201234405VS.NET IDE: 72. Visual Studio에서 pfx 파일로 서명한 경우, 암호는 어디에 저장될까? [2]
1297정성태6/12/201231053VC++: 63. 다른 프로세스에 환경 변수 설정하는 방법파일 다운로드1
1296정성태6/5/201227682.NET Framework: 328. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 - 두 번째 이야기 [4]파일 다운로드1
1295정성태6/5/201225083.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기파일 다운로드1
1294정성태5/27/201248527.NET Framework: 326. 유니코드와 한글 - 유니코드와 닷넷을 이용한 한글 처리 [7]파일 다운로드2
1293정성태5/24/201229774.NET Framework: 325. System.Drawing.Bitmap 데이터를 Parallel.For로 처리하는 방법 [2]파일 다운로드1
1292정성태5/24/201223754.NET Framework: 324. First-chance exception에 대해 조건에 따라 디버거가 멈추게 할 수는 없을까? [1]파일 다운로드1
1291정성태5/23/201230276VC++: 62. 배열 초기화를 위한 기계어 코드 확인 [2]
1290정성태5/18/201235081.NET Framework: 323. 관리자 권한이 필요한 작업을 COM+에 대행 [7]파일 다운로드1
1289정성태5/17/201239239.NET Framework: 322. regsvcs.exe로 어셈블리 등록 시 시스템 변경 사항 [5]파일 다운로드2
1288정성태5/17/201226463.NET Framework: 321. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (3) - Type Library파일 다운로드1
1287정성태5/17/201229299.NET Framework: 320. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0 [2]
1286정성태5/17/201238221.NET Framework: 319. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (1) - .NET 2.0 + x86/x64/AnyCPU [5]
1285정성태5/16/201233264.NET Framework: 318. gacutil.exe로 어셈블리 등록 시 시스템 변경 사항파일 다운로드1
1284정성태5/15/201225695오류 유형: 155. Windows Phone 연결 상태에서 DRIVER POWER STATE FAILURE 블루 스크린 뜨는 현상
1283정성태5/12/201233307.NET Framework: 317. C# 관점에서의 Observer 패턴 구현 [1]파일 다운로드1
1282정성태5/12/201226106Phone: 6. Windows Phone 7 Silverlight에서 Google Map 사용하는 방법 [3]파일 다운로드1
1281정성태5/9/201233190.NET Framework: 316. WPF/Silverlight의 그래픽 단위와 Anti-aliasing 처리를 이해하자 [1]파일 다운로드1
1280정성태5/9/201226157오류 유형: 154. Could not load type 'System.ServiceModel.Activation.HttpModule' from assembly 'System.ServiceModel, ...'.
1279정성태5/9/201224918.NET Framework: 315. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 [1]파일 다운로드1
1278정성태5/8/201226147오류 유형: 153. Visual Studio 디버깅 - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...