성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>windbg/sos - Hashtable의 buckets 배열 내용을 모두 덤프하는 방법 (do_hashtable.py)</h1> <p> windbg에서 프로그램을 분석할 때 Hashtable이 나오면 기본 windbg + sos 명령어로는 분석이 (어려운 게 아니고) 지겹습니다. 예를 하나 들어 볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections; class Program { static unsafe void Main(string[] args) { Hashtable hash = new Hashtable(); hash["TEST"] = "1abc"; hash["qwer"] = "2def"; TypedReference tr = __makeref(hash); IntPtr ptr = **(IntPtr**)(&tr); <span style='color: blue; font-weight: bold'>Console.WriteLine(ptr.ToInt64().ToString("x"));</span> // 1bfd8672f40 Console.WriteLine(hash); Console.WriteLine("debug this..."); Console.ReadLine(); } } </pre> <br /> 저렇게 <a target='tab' href='http://www.sysnet.pe.kr/2/0/11529'>TypedReference를 활용하면 객체의 GC Heap 위치(주소)</a>를 알 수 있습니다. 물론 현실적으로 메모리 덤프 파일을 분석하는 경우에는 직접 저 주소를 구해야 하지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > windbg - x64 덤프 분석 시 메서드의 인자 또는 로컬 변수의 값을 확인하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12069'>http://www.sysnet.pe.kr/2/0/12069</a> </pre> <br /> 이번에는 어찌어찌 구했다고 가정하고 진행해 보겠습니다. 일단 windbg로 (1bfd8672f40로 출력된) 객체를 덤프해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:005> <span style='color: blue; font-weight: bold'>.loadby sos clr</span> 0:005> <span style='color: blue; font-weight: bold'>!do 1bfd8672f40</span> Name: System.Collections.Hashtable MethodTable: 00007ffdc3079118 EEClass: 00007ffdc3184b88 Size: 80(0x50) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields: MT Field Offset Type VT Attr Value Name <span style='color: blue; font-weight: bold'>00007ffdc3074628 4001830 8 ...ashtable+bucket[] 0 instance 000001bfd8672f90 buckets</span> 00007ffdc30780f8 4001831 30 System.Int32 1 instance 2 count 00007ffdc30780f8 4001832 34 System.Int32 1 instance 1 occupancy 00007ffdc30780f8 4001833 38 System.Int32 1 instance 2 loadsize 00007ffdc307e7f0 4001834 3c System.Single 1 instance 0.720000 loadFactor 00007ffdc30780f8 4001835 40 System.Int32 1 instance 2 version 00007ffdc307e598 4001836 44 System.Boolean 1 instance 0 isWriterInProgress 00007ffdc3079aa8 4001837 10 ...tions.ICollection 0 instance 0000000000000000 keys 00007ffdc3079aa8 4001838 18 ...tions.ICollection 0 instance 0000000000000000 values 00007ffdc2fede18 4001839 20 ...IEqualityComparer 0 instance 0000000000000000 _keycomparer 00007ffdc3075f88 400183a 28 System.Object 0 instance 0000000000000000 _syncRoot </pre> <br /> Hashtable에 저장된 buckets 필드를 확인할 수 있습니다. 그리고 그 값을 다시 덤프해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:005> <span style='color: blue; font-weight: bold'>!DumpObj /d 000001bfd8672f90</span> Name: System.Collections.Hashtable+bucket[] MethodTable: 00007ffdc3074628 EEClass: 00007ffdc3183448 Size: 96(0x60) bytes Array: Rank 1, Number of elements 3, Type VALUETYPE (<span style='color: blue; font-weight: bold'>Print Array</span>) Fields: None </pre> <br /> 위와 같이 나오는데 원래 windbg에서는 "<a name='DML' target='tab' href='https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-markup-language-commands'>Debugger Markup Language</a>" 덕분에 "Print Array"가 링크로 제공되므로 사용자는 이후의 작업을 그냥 "Print Array"를 눌러 다음과 같이 bucket [] 배열의 원소 값을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:005> <span style='color: blue; font-weight: bold'>!DumpArray /d 000001bfd8672f90</span> Name: System.Collections.Hashtable+bucket[] MethodTable: 00007ffdc3074628 EEClass: 00007ffdc3183448 Size: 96(0x60) bytes Array: Rank 1, Number of elements 3, Type VALUETYPE <span style='color: blue; font-weight: bold'>Element Methodtable: 00007ffdc30746a8</span> [0] 000001bfd8672fa0 [1] 000001bfd8672fb8 [2] 000001bfd8672fd0 </pre> <br /> 그러니까, 다음과 같은 식으로 링크를 따라 이동하므로 편리하게 디버깅을 할 수 있습니다.<br /> <br /> <img alt='dump_hashtable_1.png' src='/SysWebRes/bbs/dump_hashtable_1.png' /><br /> <br /> <a name='dumpvc'></a> 그런데, 저 링크에서 배열 요소의 값을 확인하기 위해 링크를 타고 들어가도 "Fields:" 영역은 비어서 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:005> <span style='color: blue; font-weight: bold'>!DumpVC /d 00007ffdc3074628 000001bfd8672fa0</span> Name: System.Collections.Hashtable+bucket[] MethodTable: 00007ffdc3074628 EEClass: 00007ffdc3183448 Size: 24(0x18) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll <span style='color: blue; font-weight: bold'>Fields:</span> </pre> <br /> 왜냐하면, 올바르지 않게 DML이 구성되어 있기 때문입니다. <a target='tab' href='https://www.sysnet.pe.kr/2/0/11588#dumpvc'>!DumpVC 명령어</a>는 원래 !DumpObject 명령과 출력은 비슷하지만 부가적으로 "MethodTable"을 함께 지정해 주소의 값을 해석하는 기능을 가집니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > !dumpvc [methodtable] [object_address] </pre> <br /> 그런데 위의 DumpVC 명령어가 받아들인 methodtable == 00007ffdc3074628 값은 "System.Collections.Hashtable+bucket"이 아닌 "System.Collections.Hashtable+bucket[]" 타입을 가리키기 때문에 정상적으로 배열 요소의 값이 해석이 안 된 것입니다. 따라서 이것을 제대로 해석하려면 "!DumpArray"의 출력 결과에 "Element Methodtable: ...." 값이 나오는데 바로 그 값(예제의 경우 00007ffdc30746a8)이 System.Collections.Hashtable+bucket 타입의 MethodTable이므로 다음과 같이 명령을 내리면 요솟값을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:005> <span style='color: blue; font-weight: bold'>!DumpVC /d 00007ffdc30746a8 000001bfd8672fa0</span> Name: System.Collections.Hashtable+bucket MethodTable: 00007ffdc30746a8 EEClass: 00007ffdc32b42c0 Size: 40(0x28) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields: MT Field Offset Type VT Attr Value Name 00007ffdc3075f88 4003483 0 System.Object 0 instance 000001bfd8672e48 key 00007ffdc3075f88 4003484 8 System.Object 0 instance 000001bfd8672e70 val 00007ffdc30780f8 4003485 10 System.Int32 1 instance -1371534266 hash_coll </pre> <br /> 그리고 출력된 key나 val의 값을 덤프해 들어가면 저장한 값을 개별적으로 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:005> <span style='color: blue; font-weight: bold'>!DumpObj /d 000001bfd8672e48</span> Name: System.String MethodTable: 00007ffdc3075b70 EEClass: 00007ffdc2fd6808 Size: 34(0x22) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll <span style='color: blue; font-weight: bold'>String: TEST</span> Fields: MT Field Offset Type VT Attr Value Name 00007ffdc30780f8 4000283 8 System.Int32 1 instance 4 m_stringLength 00007ffdc30769e8 4000284 c System.Char 1 instance 54 m_firstChar 00007ffdc3075b70 4000288 e0 System.String 0 shared static Empty >> Domain:Value 000001bfd69d5d50:NotInit << 0:005> <span style='color: blue; font-weight: bold'>!DumpObj /d 000001bfd8672e70</span> Name: System.String MethodTable: 00007ffdc3075b70 EEClass: 00007ffdc2fd6808 Size: 34(0x22) bytes File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll <span style='color: blue; font-weight: bold'>String: 1abc</span> Fields: MT Field Offset Type VT Attr Value Name 00007ffdc30780f8 4000283 8 System.Int32 1 instance 4 m_stringLength 00007ffdc30769e8 4000284 c System.Char 1 instance 31 m_firstChar 00007ffdc3075b70 4000288 e0 System.String 0 shared static Empty >> Domain:Value 000001bfd69d5d50:NotInit << </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그런데, Hashtable에 보관한 값이 몇 개 이하라면 저런 식으로 일일이 확인을 해보겠지만 수십 개 이상이 되면 저렇게 확인하는 것이 매우 불편합니다. 따라서 이런 경우에는 <a target='tab' href='https://githomelab.ru/pykd/pykd'>pykd</a>를 이용해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > windbg에서 python 스크립트 실행하는 방법 - pykd ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11227'>http://www.sysnet.pe.kr/2/0/11227</a> </pre> <br /> 자동화할 수 있을 것입니다. 그전에, GC Heap 객체가 저장된 특성을 이용하면 pykd를 이용한 텍스트 파싱 작업이 꽤나(그나마) 간단해질 수 있습니다. 우선, DumpArray의 명령에서 본 것처럼,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:005> <span style='color: blue; font-weight: bold'>!DumpArray /d 000001bfd8672f90</span> Name: System.Collections.Hashtable+bucket[] MethodTable: 00007ffdc3074628 EEClass: 00007ffdc3183448 Size: 96(0x60) bytes Array: Rank 1, Number of elements 3, Type <span style='color: blue; font-weight: bold'>VALUETYPE</span> Element Methodtable: 00007ffdc30746a8 [0] <span style='color: blue; font-weight: bold'>000001bfd8672fa0</span> [1] 000001bfd8672fb8 [2] 000001bfd8672fd0 </pre> <br /> bucket 타입은 struct 형이기 때문에 값의 요소가 GC Heap 메모리에 연속적으로 보관되어 있습니다. 따라서, bucket 타입의 3개 필드 값을 다음과 같은 식으로 한 라인에 요소 하나씩 출력할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:005> <span style='color: blue; font-weight: bold'>dq /c3 000001d500002fa0 L9</span> 000001d5`00002fa0 000001d5`00002e48 000001d5`00002e70 00000000`ae400c46 000001d5`00002fb8 00000000`00000000 00000000`00000000 00000000`00000000 000001d5`00002fd0 000001d5`00002e98 000001d5`00002ec0 00000000`34a6e611 </pre> <br /> 즉, 첫 번째 요소([1] 000001bfd8672fa0)의 값은 key == 000001d5`00002e48, value == 000001d5`00002e70이 됩니다.<br /> <br /> 그리고 key와 value가 string 타입이라는 것을 알고 있으면, 우리는 string 객체가 <a target='tab' href='http://www.sysnet.pe.kr/2/0/1175'>GC Heap에 저장될 때 sizeof(int) * 3 만큼의 값이 Object Header</a>에 해당한다는 것을 알고 있기 때문에 개별 문자열은 [주소 + 0x0c] 위치를 이용해 곧바로 덤프할 수 있습니다. 따라서 key, value의 문자열은 이렇게 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:005> du 000001d5`00002e48+c 000001d5`00002e54 "TEST" 0:005> du 000001d5`00002e70+c 000001d5`00002e7c "1abc" </pre> <br /> 이것을 종합하면, Hashtable의 요소가 key = string, value = string으로 이뤄져 있다면 다음의 python 스크립트를 이용해 일괄적으로 bucket 배열의 값을 덤프할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > import argparse from pykd import * def getItem(text, index): result = text.split(" ")[index + 1].strip() return result def isValidAddress(text): text = text.replace("`", "") if (int(text, 16) == 0): return False return True def ToValidText(output): if (len(output.split("\"??????????")) >= 2): return "(non-string)" return output def action(elem0Address, elemCount): totalCount = elemCount * 3 outputText = pykd.dbgCommand("dq /c3 " + elem0Address + " L" + hex(totalCount)) index = -1 for line in outputText.splitlines(): index = index + 1 keyAddress = getItem(line, 1) valueAddress = getItem(line, 2) if (isValidAddress(keyAddress) == False or isValidAddress(valueAddress) == False): print("[" + str(index) + "] (empty)") continue keyText = pykd.dbgCommand("du " + keyAddress + "+c").split(" ")[1].strip() valueText = pykd.dbgCommand("du " + valueAddress + "+c").split(" ")[1].strip() keyText = ToValidText(keyText).split("\n")[0] valueText = ToValidText(valueText).split("\n")[0] dprintln("[" + str(index) + "] key: <link cmd=\"!do " + keyAddress + " \">" + keyText + "</link>, value: <link cmd=\"!do " + valueAddress + " \">" + valueText + "</link>", True) def main(): parser = argparse.ArgumentParser() parser.add_argument('address', type=str, help='hashtable.Element[0] address') parser.add_argument('count', type=int, help='# of Element') args = parser.parse_args() action(args.address, args.count) if __name__ == "__main__": main() </pre> <br /> 다음은 위의 스크립트를 이용해 덤프한 결과입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:005> <span style='color: blue; font-weight: bold'>.load d:\pykd\x64\pykd.dll</span> 0:005> <span style='color: blue; font-weight: bold'>!DumpArray /d 000001bfd8672f90</span> Name: System.Collections.Hashtable+bucket[] MethodTable: 00007ffdc3074628 EEClass: 00007ffdc3183448 Size: 96(0x60) bytes Array: Rank 1, Number of elements 3, Type VALUETYPE Element Methodtable: 00007ffdc30746a8 [0] <span style='color: blue; font-weight: bold'>000001bfd8672fa0</span> [1] 000001bfd8672fb8 [2] 000001bfd8672fd0 0:005> <span style='color: blue; font-weight: bold'>!py d:\pykd\do_hashtable.py 000001bfd8672fa0 3</span> [0] key: "TEST", value: "1abc" [1] (empty) [2] key: "qwer", value: "2def" </pre> <br /> 게다가 출력 결과에 DML을 사용했기 때문에 다음과 같이 개별 값에 대한 "!do"를 실행할 수 있습니다.<br /> <br /> <img alt='dump_hashtable_2.png' src='/SysWebRes/bbs/dump_hashtable_2.png' /><br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
7166
(왼쪽의 숫자를 입력해야 합니다.)