Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 216. WinDbg - 2가지 유형의 식 평가 방법(MASM, C++) [링크 복사], [링크+제목 복사],
조회: 3620
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 31  32  33  34  35  [36]  37  38  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
13036정성태4/24/202215182.NET Framework: 1997. C# - nano 시간을 가져오는 방법 [2]
13035정성태4/22/202216444Windows: 204. Windows 10부터 바뀐 QueryPerformanceFrequency, QueryPerformanceCounter
13034정성태4/21/202214016.NET Framework: 1996. C# XingAPI - 주식 종목에 따른 PBR, PER, ROE, ROA 구하는 방법(t3320, t8430 예제)파일 다운로드1
13033정성태4/18/202215129.NET Framework: 1195. C# - Thread.Yield와 Thread.Sleep(0)의 차이점(?)
13032정성태4/17/202215330오류 유형: 805. Github의 50MB 파일 크기 제한 - warning: GH001: Large files detected. You may want to try Git Large File Storage
13031정성태4/15/202214990.NET Framework: 1194. C# - IdealProcessor와 ProcessorAffinity의 차이점
13030정성태4/15/202213552오류 유형: 804. 정규 표현식 오류 - Quantifier {x,y} following nothing.
13029정성태4/14/202214941Windows: 203. iisreset 후에도 이전에 설정한 전역 환경 변수가 w3wp.exe에 적용되는 문제
13028정성태4/13/202215040.NET Framework: 1193. (appsettings.json처럼) web.config의 Debug/Release에 따른 설정 적용
13027정성태4/12/202214925.NET Framework: 1192. C# - 환경 변수의 변화를 알리는 WM_SETTINGCHANGE Win32 메시지 사용법파일 다운로드1
13026정성태4/11/202216310.NET Framework: 1191. C 언어로 작성된 FFmpeg Examples의 C# 포팅 전체 소스 코드 [3]
13025정성태4/11/202215577.NET Framework: 1190. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 vaapi_encode.c, vaapi_transcode.c 예제 포팅
13024정성태4/7/202213796.NET Framework: 1189. C# - 런타임 환경에 따라 달라진 AppDomain.GetCurrentThreadId 메서드
13023정성태4/6/202214388.NET Framework: 1188. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 transcoding.c 예제 포팅 [3]
13022정성태3/31/202213958Windows: 202. 윈도우 11 업그레이드 - "PC Health Check"를 통과했지만 여전히 업그레이드가 안 되는 경우 해결책
13021정성태3/31/202215780Windows: 201. Windows - INF 파일을 이용한 장치 제거 방법
13020정성태3/30/202214003.NET Framework: 1187. RDP 접속 시 WPF UserControl의 Unloaded 이벤트 발생파일 다운로드1
13019정성태3/30/202214474.NET Framework: 1186. Win32 Message를 Code로부터 메시지 이름 자체를 텍스트로 구하고 싶다면?파일 다운로드1
13018정성태3/29/202215227.NET Framework: 1185. C# - Unsafe.AsPointer가 반환한 포인터는 pinning 상태일까요? [5]
13017정성태3/28/202214193.NET Framework: 1184. C# - GC Heap에 위치한 참조 개체의 주소를 알아내는 방법 - 두 번째 이야기 [3]
13016정성태3/27/202215841.NET Framework: 1183. C# 11에 추가된 ref 필드의 (우회) 구현 방법파일 다운로드1
13015정성태3/26/202217534.NET Framework: 1182. C# 11 - ref struct에 ref 필드를 허용 [1]
13014정성태3/23/202215018VC++: 155. CComPtr/CComQIPtr과 Conformance mode 옵션의 충돌 [1]
13013정성태3/22/202212789개발 환경 구성: 641. WSL 우분투 인스턴스에 파이썬 2.7 개발 환경 구성하는 방법
13012정성태3/21/202212431오류 유형: 803. C# - Local '...' or its members cannot have their address taken and be used inside an anonymous method or lambda expression
13011정성태3/21/202215918오류 유형: 802. 윈도우 운영체제에서 웹캠 카메라 인식이 안 되는 경우
... 31  32  33  34  35  [36]  37  38  39  40  41  42  43  44  45  ...