Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 95. Windbg - .foreach 사용법 [링크 복사], [링크+제목 복사]
조회: 12814
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)

Windbg - .foreach 사용법

다음의 문서에도 나오지만,

Debugger Command Program Examples
; https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-command-program-examples

Commands Output Using .foreach
; https://blogs.msdn.microsoft.com/debuggingtoolbox/2009/03/11/special-commandparsing-strings-files-and-commands-output-using-foreach/

그래도 한글 설명을 해볼까 해서 ^^ 기록으로 남깁니다.

우선, 다음의 명령어를 볼까요?

0:000> .foreach (place { s-[1]w 77000000 L?4000000 5a4d }) { dc place L8 }

명령어 구조는 이렇습니다.

.foreach ([변수] { 명령어1 }) { 명령어2 }

// 명령어1을 실행하고 그 결과를 공백/개행 기준 split 시킨 후 변수에 담아 명령어2를 루프 돌면서 실행
// 따라서,

변수 == place
명령어1 == { s-[1]w 77000000 L?4000000 5a4d }
명령어2 == { dc place L8 }

명령어1의 출력 결과는 다음과 같은 식입니다.

0x77002f96
0x77002f9e
0x77002fa6
0x77002fa8
0x77002faa
0x77002fac
0x77002fae
0x77002fb0
0x77002fb2
0x77002fb4
0x77002fb6

place는 변수, 즉 windbg의 s 명령어로 77000000에서 4000000만큼의 메모리 범위 내에 5a4d를 검색한 결과를 토큰별로 place로 변수를 받고, 이후 dc place L8에서 place 부분이 s 검색으로 찾은 토큰으로 대체되는 것입니다. C# 구문으로 변환해 보자면 다음과 같은 의미와 같습니다.

foreach (var place in s(77000000, 4000000, "5a4d"))
{
    dc place L8;
}

다른 사례를 한번 볼까요? 다음은 sos의 !dso 명령어 출력입니다.

0:000> !dso
OS Thread Id: 0x70ac (0)
ESP/REG  Object   Name
0133EE30 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EE54 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EE60 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EE88 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EEB0 035657a8 System.IO.StreamReader
0133EEB4 035657a8 System.IO.StreamReader
0133EECC 03565b2c System.IO.TextReader+SyncTextReader
0133EEE8 03565b2c System.IO.TextReader+SyncTextReader
0133EF14 03563110 System.String[]
0133EF84 03563110 System.String[]
0133F0DC 03563110 System.String[]
0133F0F8 03563110 System.String[]
0133F630 03561238 System.SharedStatics
0133F644 03561238 System.SharedStatics

여기서 우리가 관심 있는 것은 "Object" 칼럼에 있는 값들입니다. 그런데 이번엔 이전 예제와는 달리 다른 값들도 섞여 있기 때문에 다른 옵션이 필요합니다. 이를 위해 저 출력 결과를 string.Split 메서드로 공백/개행 문자로 분리했다고 가정하시면 됩니다. 그럼 처음 9개의 토큰을 건너뛰어야 합니다.

0:000> !dso
OS Thread Id: 0x70ac (0)
ESP/REG  Object   Name
0133EE30 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
...[생략]...
0133F644 03561238 System.SharedStatics

그러니까, 다음의 9개 토큰을 그냥 지나가야 합니다.

1) OS
2) Thread
3) Id:
4) 0x70ac
5) (0)
6) ESP/REG
7) Object
8) Name
9) 0133EE30

그런 다음, 원하는 토큰이 나오고 이후 다시 2개의 토큰 값들을 건너뛰는 식으로 열람할 수 있습니다.

0:000> !dso
OS Thread Id: 0x70ac (0)
ESP/REG  Object   Name
0133EE30 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EE54 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EE60 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EE88 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EEB0 035657a8 System.IO.StreamReader
0133EEB4 035657a8 System.IO.StreamReader
0133EECC 03565b2c System.IO.TextReader+SyncTextReader
0133EEE8 03565b2c System.IO.TextReader+SyncTextReader
0133EF14 03563110 System.String[]
0133EF84 03563110 System.String[]
0133F0DC 03563110 System.String[]
0133F0F8 03563110 System.String[]
0133F630 03561238 System.SharedStatics
0133F644 03561238 System.SharedStatics

이렇게 처음 토큰들을 건너뛰도록 /pS 옵션이 제공되고, 이후 반복적으로 건너뛸 토큰들을 /ps 옵션으로 제공하면 됩니다. 따라서 다음과 같이 실행하면,

.foreach /pS 9 /ps 2 ( obj { !dso } ){ .echo ${obj} }

화면에는 Object 칼럼에 해당하는 값들이 "obj" 변수에 할당되어 .echo 출력으로 전달되고 이런 출력 결과를 얻게 됩니다.

0356576c
0356576c
0356576c
0356576c
035657a8
035657a8
03565b2c
03565b2c
03563110
03563110
03563110
03563110
03561238
03561238

그렇습니다. .foreach가 제공하는 루프 문 규칙은 꽤나 원시(?)적인 수준입니다. 그래도, 확장의 여지가 있긴 한데 이를 위해 console 응용 프로그램을 이용할 수 있습니다. 가령 !dso 출력 결과에서 "System."으로 시작하는 행의 Object 칼럼만 반환받고 싶다면, 기존의 /pS, /ps 옵션으로는 불가능한데 shell의 도움을 받으면 가능합니다.

이해를 돕기 위해 !dso의 출력 결과를 test.txt 파일로 저장해 보겠습니다.

F:\temp>type test.txt
OS Thread Id: 0x70ac (0)
ESP/REG  Object   Name
0133EE30 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EE54 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EE60 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EE88 0356576c Microsoft.Win32.SafeHandles.SafeFileHandle
0133EEB0 035657a8 System.IO.StreamReader
0133EEB4 035657a8 System.IO.StreamReader
0133EECC 03565b2c System.IO.TextReader+SyncTextReader
0133EEE8 03565b2c System.IO.TextReader+SyncTextReader
0133EF14 03563110 System.String[]
0133EF84 03563110 System.String[]
0133F0DC 03563110 System.String[]
0133F0F8 03563110 System.String[]
0133F630 03561238 System.SharedStatics
0133F644 03561238 System.SharedStatics

이 출력에서 "System."으로 시작하는 행은 다음과 같이 find 명령어를 이용해서 필터링할 수 있습니다.

F:\temp>type test.txt | find "System."
0133EEB0 035657a8 System.IO.StreamReader
0133EEB4 035657a8 System.IO.StreamReader
0133EECC 03565b2c System.IO.TextReader+SyncTextReader
0133EEE8 03565b2c System.IO.TextReader+SyncTextReader
0133EF14 03563110 System.String[]
0133EF84 03563110 System.String[]
0133F0DC 03563110 System.String[]
0133F0F8 03563110 System.String[]
0133F630 03561238 System.SharedStatics
0133F644 03561238 System.SharedStatics

위의 출력 결과를 .foreach로 순회한다고 하면 우선 첫 번째 0133EEB0 항목을 건너뛰고, 035657a8 값을 읽은 후 그 이후부터는 2개의 토큰씩 건너뛰면 됩니다. 위와 같은 과정을 다음과 같은 명령어로 실행할 수 있습니다.

.foreach /pS 1 /ps 2 (obj {.shell -i - -ci "!dso" FIND "System."}){ .echo ${obj}}

명령어가 눈에 안 익을 수 있는데 정형화된 패턴이라고 보시면 됩니다. ".shell -i - -ci" 이후에 windbg 명령을 쓰고 그것의 출력 결과물을 이후의 Shell 명령어(FIND "System.")에 전달해 처리한 결과를 얻는 식입니다.

참고로 위의 명령어 수행 결과로 얻은 출력은 이렇게 됩니다.

035657a8
035657a8
03565b2c
03565b2c
03563110
03563110
03563110
03563110
03561238
03561238
Process

그런데 마지막 "Process"가 왜 나왔을까요? 그 이유는, windbg의 .shell 명령어는 지정된 명령어를 실행 후 ".shell: Process exited" 출력이 붙기 때문입니다. 따라서 {.shell -i - -ci "!dso" FIND "System."} 명령어의 수행 결과는 실제로 다음과 같이 출력되었던 것입니다.

0133EEB0 035657a8 System.IO.StreamReader
0133EEB4 035657a8 System.IO.StreamReader
0133EECC 03565b2c System.IO.TextReader+SyncTextReader
0133EEE8 03565b2c System.IO.TextReader+SyncTextReader
0133EF14 03563110 System.String[]
0133EF84 03563110 System.String[]
0133F0DC 03563110 System.String[]
0133F0F8 03563110 System.String[]
0133F630 03561238 System.SharedStatics
0133F644 03561238 System.SharedStatics
.shell: Process exited

그렇기 때문에 마지막 03561238 토큰을 읽은 후 2개의 토큰(System.SharedStatics, .shell:)을 건너뛰고 "Process"라는 값이 읽혔던 것입니다. 그래도 대개의 경우 마지막에 저렇게 예상치 않은 문자열이 붙는 것은 결과에 크게 영향이 없습니다. 예를 들어, 수행하는 명령어가 ".echo ${obj}"가 아니라 "!do ${obj}"여도 명령어에서 오류만 발생할 뿐 이미 우리가 원한 객체들의 값은 모두 출력했기 때문에 신경 쓸 필요가 없는 것입니다.


이 정도면, 대충 .foreach 사용법이 눈에 들어오시죠! ^^




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 4/30/2018]

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)
12854정성태11/13/20217792개발 환경 구성: 604. Azure - 윈도우 VM에서 FTP 여는 방법
12853정성태11/10/20216141오류 유형: 766. Azure App Service - JBoss 호스팅 생성 시 "This region has quota of 0 PremiumV3 instances for your subscription. Try selecting different region or SKU."
12851정성태11/1/20217539스크립트: 34. 파이썬 - MySQLdb 기본 예제 코드
12850정성태10/27/20218688오류 유형: 765. 우분투에서 pip install mysqlclient 실행 시 "OSError: mysql_config not found" 오류
12849정성태10/17/20217818스크립트: 33. JavaScript와 C#의 시간 변환 [1]
12848정성태10/17/20218798스크립트: 32. 파이썬 - sqlite3 기본 예제 코드 [1]
12847정성태10/14/20218649스크립트: 31. 파이썬 gunicorn - WORKER TIMEOUT 오류 발생
12846정성태10/7/20218397스크립트: 30. 파이썬 __debug__ 플래그 변수에 따른 코드 실행 제어
12845정성태10/6/20218227.NET Framework: 1120. C# - BufferBlock<T> 사용 예제 [5]파일 다운로드1
12844정성태10/3/20216237오류 유형: 764. MSI 설치 시 "... is accessible and not read-only." 오류 메시지
12843정성태10/3/20216683스크립트: 29. 파이썬 - fork 시 기존 클라이언트 소켓 및 스레드의 동작파일 다운로드1
12842정성태10/1/202125034오류 유형: 763. 파이썬 오류 - AttributeError: type object '...' has no attribute '...'
12841정성태10/1/20218510스크립트: 28. 모든 파이썬 프로세스에 올라오는 특별한 파일 - sitecustomize.py
12840정성태9/30/20218607.NET Framework: 1119. Entity Framework의 Join 사용 시 다중 칼럼에 대한 OR 조건 쿼리파일 다운로드1
12839정성태9/15/20219658.NET Framework: 1118. C# 11 - 제네릭 타입의 특성 적용파일 다운로드1
12838정성태9/13/20219302.NET Framework: 1117. C# - Task에 전달한 Action, Func 유형에 따라 달라지는 async/await 비동기 처리 [2]파일 다운로드1
12837정성태9/11/20218212VC++: 151. Golang - fmt.Errorf, errors.Is, errors.As 설명
12836정성태9/10/20217818Linux: 45. 리눅스 - 실행 중인 다른 프로그램의 출력을 확인하는 방법
12835정성태9/7/20219067.NET Framework: 1116. C# 10 - (15) CallerArgumentExpression 특성 추가 [2]파일 다운로드1
12834정성태9/7/20217453오류 유형: 762. Visual Studio 2019 Build Tools - 'C:\Program' is not recognized as an internal or external command, operable program or batch file.
12833정성태9/6/20216903VC++: 150. Golang - TCP client/server echo 예제 코드파일 다운로드1
12832정성태9/6/20217765VC++: 149. Golang - 인터페이스 포인터가 의미 있을까요?
12831정성태9/6/20216295VC++: 148. Golang - 채널에 따른 다중 작업 처리파일 다운로드1
12830정성태9/6/20218561오류 유형: 761. Internet Explorer에서 파일 다운로드 시 "Your current security settings do not allow this file to be downloaded." 오류
12829정성태9/5/202110196.NET Framework: 1115. C# 10 - (14) 구조체 타입에 기본 생성자 정의 가능파일 다운로드1
12828정성태9/4/20218331.NET Framework: 1114. C# 10 - (13) 단일 파일 내에 적용되는 namespace 선언파일 다운로드1
... [31]  32  33  34  35  36  37  38  39  40  41  42  43  44  45  ...