성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
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'>C# - PDB 파일로부터 심벌(Symbol) 및 타입(Type) 정보 열거</h1> <p> 예전에 PDB 파일을 이용해 소스 코드 라인 정보를 다루는 방법을 설명한 적이 있는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > PDB 파일을 연동해 소스 코드 라인 정보를 알아내는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1763'>http://www.sysnet.pe.kr/2/0/1763</a> </pre> <br /> 이번에는 PDB 파일로부터 Type 정보 및 Symbol을 열거해 보겠습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> windbg를 사용하다 보면 dt 명령어로 구조체 정의를 확인하는 경우가 종종 있습니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:007> <span style='color: blue; font-weight: bold'>dt _PEB</span> <span style='color: blue; font-weight: bold'>ntdll!_PEB</span> +0x000 InheritedAddressSpace : UChar +0x001 ReadImageFileExecOptions : UChar +0x002 BeingDebugged : UChar +0x003 BitField : UChar +0x003 ImageUsesLargePages : Pos 0, 1 Bit ...[생략]... </pre> <br /> 위의 출력 결과로부터, _PEB 타입은 ntdll 모듈에 정의되어 있는 것인데 이렇게 windbg가 출력해 줄 수 있는 것은 타입 정보가 PDB 파일에 보관되어 있기 때문입니다. 예전에 소개한 DbgOffset 타입도,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - 커널 구조체의 Offset 값을 하드 코딩하지 않고 사용하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12098'>http://www.sysnet.pe.kr/2/0/12098</a> </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:007> <span style='color: blue; font-weight: bold'>dt _KPROCESS</span> ntdll!_KPROCESS +0x000 Header : _DISPATCHER_HEADER +0x018 ProfileListHead : _LIST_ENTRY +0x028 DirectoryTableBase : Uint8B +0x030 ThreadListHead : _LIST_ENTRY ...[생략]... +0x288 AddressPolicy : UChar +0x289 Spare2 : [71] UChar +0x2d0 InstrumentationCallback : Ptr64 Void <span style='color: blue; font-weight: bold'>+0x2d8 SecureState : <anonymous-tag></span> </pre> <br /> 마지막 필드의 크기를 가늠할 수 없습니다. 이런 경우에는 어쩔 수 없이 PDB 파일로부터 타입을 열거해 크기 정보를 받아와야 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> Type 정보에 관해 알아봤으니 이번에는 Symbol 정보에 대해 설명해 보겠습니다. 역시 windbg를 사용하다 보면 "dps" 명령어를 통해 특정 모듈이 제공하는 Symbol의 값을 확인하는 경우가 있습니다. 자주 사용하는 사례로는 stack의 값들 중에서 연관된 symbol을 보여주고 싶은 경우일 텐데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:007> <span style='color: blue; font-weight: bold'>dps @rsp</span> <span style='color: blue; font-weight: bold'>0000003d`74affa48 00007ffb`649ad4db ntdll!DbgUiRemoteBreakin+0x4b</span> 0000003d`74affa50 00000000`00000000 0000003d`74affa58 00000000`00000000 0000003d`74affa60 00000000`00000000 0000003d`74affa68 00000000`00000000 0000003d`74affa70 00000000`00000000 <span style='color: blue; font-weight: bold'>0000003d`74affa78 00007ffb`63977bd4 KERNEL32!BaseThreadInitThunk+0x14</span> 0000003d`74affa80 00000000`00000000 0000003d`74affa88 00000000`00000000 0000003d`74affa90 00000000`00000000 0000003d`74affa98 00000000`00000000 0000003d`74affaa0 00000000`00000000 <span style='color: blue; font-weight: bold'>0000003d`74affaa8 00007ffb`6494ced1 ntdll!RtlUserThreadStart+0x21</span> 0000003d`74affab0 00000000`00000000 0000003d`74affab8 00000000`00000000 0000003d`74affac0 00000000`00000000 </pre> <br /> 위의 출력에서는 <a target='tab' href='http://www.sysnet.pe.kr/2/0/12093'>dll이 export하고 있는 함수에 대해서만 출력</a>이 되었지만, 명시적인 export 이외에 내부 전역 변수나 함수 등의 symbol 정보도 출력하는 것이 가능합니다. 가령, _PEB에서 소유하고 있는 KernelCallbackTable 필드의 주소를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:007> <span style='color: blue; font-weight: bold'>dt _PEB @$peb</span> ntdll!_PEB +0x000 InheritedAddressSpace : 0 '' +0x001 ReadImageFileExecOptions : 0 '' +0x002 BeingDebugged : 0x1 '' ...[생략]... +0x050 ProcessImagesHotPatched : 0y0 +0x050 ReservedBits0 : 0y000000000000000000000000 (0) +0x054 Padding1 : [4] "" <span style='color: blue; font-weight: bold'>+0x058 KernelCallbackTable : 0x00007ffb`63537330 Void</span> ...[생략]... </pre> <br /> dps로 덤프해 보면 user32.dll에 포함된 symbol의 주소와 연결된 것을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:007> <span style='color: blue; font-weight: bold'>dps 0x00007ffb`63537330 L5</span> 00007ffb`63537330 00007ffb`634b5160 <span style='color: blue; font-weight: bold'>USER32</span>!_fnCOPYDATA 00007ffb`63537338 00007ffb`6352ec60 <span style='color: blue; font-weight: bold'>USER32</span>!_fnCOPYGLOBALDATA 00007ffb`63537340 00007ffb`634d2720 <span style='color: blue; font-weight: bold'>USER32</span>!_fnDWORD 00007ffb`63537348 00007ffb`634d61d0 <span style='color: blue; font-weight: bold'>USER32</span>!_fnNCDESTROY 00007ffb`63537350 00007ffb`634dc830 <span style='color: blue; font-weight: bold'>USER32</span>!_fnDWORDOPTINLPMSG </pre> <br /> 즉, 우리가 원하는 것은 바로 저 symbol 목록을 얻고 싶은 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 사실, 이에 관해 검색해 보면 C/C++ 소스 코드로 다음과 같이 친절하게 ^^ 구현되어 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > mridgers/pdbdump.c - Small tool to list and query symbols in PDB files. ; <a target='tab' href='https://gist.github.com/mridgers/2968595'>https://gist.github.com/mridgers/2968595</a> </pre> <br /> 소스 코드가 상당히 간결하기 때문에 C#으로의 마이그레이션도 어렵지 않은데요, 위의 코드에 따라 SymInitialize까지 호출하는 코드를 다음과 같이 작성하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > uint options = NativeMethods.<span style='color: blue; font-weight: bold'>SymGetOptions</span>(); Console.WriteLine($"SymGetOptions: {options}"); options &= ~(uint)SymOpt.SYMOPT_DEFERRED_LOADS; options |= (uint)SymOpt.SYMOPT_LOAD_LINES; options |= (uint)SymOpt.SYMOPT_IGNORE_NT_SYMPATH; #if ENABLE_DEBUG_OUTPUT options |= (uint)SymOpt.SYMOPT_DEBUG; #endif options |= (uint)SymOpt.SYMOPT_UNDNAME; NativeMethods.<span style='color: blue; font-weight: bold'>SymSetOptions</span>(options); int pid = Process.GetCurrentProcess().Id; IntPtr processHandle = NativeMethods.OpenProcess(ProcessAccessRights.PROCESS_QUERY_INFORMATION | ProcessAccessRights.PROCESS_VM_READ, false, pid); if (NativeMethods.<span style='color: blue; font-weight: bold'>SymInitialize</span>(processHandle, null, false) == false) { return; } </pre> <br /> SymLoadModuleEx를 호출하면 되는데, 이때 PDB 파일의 경로가 필요합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > IntPtr baseAddress = new IntPtr((long)NativeMethods.<span style='color: blue; font-weight: bold'>SymLoadModuleEx</span>(processHandle, IntPtr.Zero, <span style='color: blue; font-weight: bold'>pdbFilePath</span>, null, baseAddress.ToInt64(), moduleSize, null, 0)); </pre> <br /> 해보진 않았지만 pdbFilePath 인자에 dll 경로를 넣는 경우 <a target='tab' href='http://www.sysnet.pe.kr/2/0/12091'>symchk.exe가 가졌던 DLL들이 필요</a>할 것이므로 그런 의존성을 제거하고 싶다면 직접 PDB 파일을 다운로드하는 방법을 쓰면 됩니다. 그러고 보니 ^^ 저번에 이런 기능을 하는 코드를 작성해 두었었죠.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - 코드를 통해 PDB 심벌 파일 다운로드 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12094'>http://www.sysnet.pe.kr/2/0/12094</a> </pre> <br /> 따라서 이 코드를 곁들이면 다음과 같이 SymLoadModuleEx 처리가 완료됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > PEImage pe = null; string moduleName = "ntdll.dll"; string pdbFilePath = null; { string rootPathToSave = Path.Combine(Environment.CurrentDirectory, "sym"); pe = PEImage.FromLoadedModule(moduleName); pdbFilePath = pe.<span style='color: blue; font-weight: bold'>DownloadPdb</span>(pe.ModulePath, rootPathToSave); // ntdll.dll에 대한 pdb 파일을 다운로드 하고, } IntPtr baseAddress = new IntPtr((long)NativeMethods.<span style='color: blue; font-weight: bold'>SymLoadModuleEx</span>(processHandle, IntPtr.Zero, <span style='color: blue; font-weight: bold'>pdbFilePath</span>, null, pe.BaseAddress.ToInt64(), pe.MemorySize, null, 0)); </pre> <br /> 이쯤에서 "<a target='tab' href='https://gist.github.com/mridgers/2968595'>mridgers/pdbdump.c</a>" 코드를 보면 재미있는 것이 하나 있는데, 위에서 사용한 processHandle, pe.BaseAddress, pe.MemorySize들이 꼭 정확할 필요는 없다는 것입니다. 실제로 원 저작자의 코드에서는 관련 값들을 다음과 같이 하드 코딩했고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HANDLE g_handle = (HANDLE)<span style='color: blue; font-weight: bold'>0x493</span>; uintptr_t base_addr = <span style='color: blue; font-weight: bold'>0x400000</span>; base_addr = (size_t)SymLoadModuleEx(g_handle, NULL, buffer, NULL, base_addr, <span style='color: blue; font-weight: bold'>0x7fffffff</span>, NULL, 0) </pre> <br /> 그래도 잘 동작하는 것을 확인할 수 있습니다. 하지만, 제 경우에는 그냥 PEImage 정보가 있기 때문에 그걸 이용해 정확한 값을 설정했습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 일단, 여기까지 마무리되었으면 이제 남은 작업은 심벌과 타입 정보를 열거하는 것입니다. 이 작업은 각각에 해당하는 함수를 호출하는 것으로 간단하게 해결됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Symbol 열거 NativeMethods.<span style='color: blue; font-weight: bold'>SymEnumSymbols</span>(processHandle, (ulong)_baseAddress.ToInt64(), "*", <span style='color: blue; font-weight: bold'>enum_proc</span>, IntPtr.Zero); // 타입 열거 NativeMethods.<span style='color: blue; font-weight: bold'>SymEnumTypes</span>(processHandle, (ulong)baseAddress.ToInt64(), <span style='color: blue; font-weight: bold'>enum_proc</span>, IntPtr.Zero); private static unsafe bool <span style='color: blue; font-weight: bold'>enum_proc</span>(IntPtr pinfo, uint size, IntPtr pUserContext) { SYMBOL_INFO info = SYMBOL_INFO.Create(pinfo); // info.Name == symbol 이름 // info.Size == 구조체 열거인 경우 크기 정보 // info.Address == Symbol 열거인 경우 메모리 상의 위치 return true; } </pre> <br /> 끝입니다. 위와 같이 실행하면 Symbol 또는 Type 하나마다 enum_proc 메서드가 callback 방식으로 호출되고 첫 번째 인자로 들어오는 pinfo 정보로부터 <a target='tab' href='https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info'>SYMBOL_INFO 구조체</a>의 값을 구하면 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 위의 내용들을 종합해 github에 PdbDump.cs 파일을 추가했으니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DotNetSamples/WinConsole/PEFormat/WindowsPE/PdbDump.cs ; <a target='tab' href='https://github.com/stjeong/DotNetSamples/blob/master/WinConsole/PEFormat/WindowsPE/PdbDump.cs'>https://github.com/stjeong/DotNetSamples/blob/master/WinConsole/PEFormat/WindowsPE/PdbDump.cs</a> </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;' > using KernelStructOffset; using System; using System.IO; using WindowsPE; namespace ConsoleApp1 { // mridgers/pdbdump.c // <a target='tab' href='https://gist.github.com/mridgers/2968595'>https://gist.github.com/mridgers/2968595</a> // Install-Package WindowsPE class Program { static void Main(string[] args) { PEImage pe = null; string moduleName = "ntdll.dll"; string pdbFilePath = null; { string rootPathToSave = Path.Combine(Environment.CurrentDirectory, "sym"); pe = PEImage.FromLoadedModule(moduleName); pdbFilePath = pe.DownloadPdb(pe.ModulePath, rootPathToSave); } { PdbStore symbolStore = PdbDump.CreateSymbolStore(pdbFilePath, pe.BaseAddress, pe.MemorySize); foreach (SYMBOL_INFO si in symbolStore.Enumerate()) { Console.WriteLine(si.Name + " at " + si.Address.ToString("x")); } } { PdbStore typeStore = PdbDump.CreateTypeStore(pdbFilePath, pe.BaseAddress, pe.MemorySize); foreach (SYMBOL_INFO si in typeStore.Enumerate()) { Console.WriteLine(si.Name + ", sizeof() == " + si.Size); } } } } } </pre> <br /> 첨부 파일은 위의 <a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1525&boardid=331301885'>샘플 코드</a> 및 <a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1526&boardid=331301885'>ntdll.dll의 Symbol 목록</a>과 <a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1527&boardid=331301885'>Type 목록</a>이 출력된 결과입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
5454
(왼쪽의 숫자를 입력해야 합니다.)