성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] 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...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
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 파일 경로를 PE 파일로부터 얻는 방법</h1> <p> 아래와 같은 질문이 나온 김에,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 서드파티 dll 디버깅에 대해 질문드립니다. ; <a target='tab' href='http://www.sysnet.pe.kr/3/0/4852'>http://www.sysnet.pe.kr/3/0/4852</a> </pre> <br /> PE(Portable Executables) 포맷에 대한 지식도 넓힐 겸, PE 바이너리 파일로부터 연관된 PDB 파일 경로를 얻는 방법을 알아보겠습니다. ^^<br /> <br /> Visual Studio에서 다음과 같은 단순 프로젝트를 빌드한 후,<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; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Console.WriteLine(); } } } </pre> <br /> <a target='tab' href='https://www.aldeid.com/wiki/PEView'>PEViewer</a> 등을 통해 확인해 보면 "IMAGE_DEBUG_DIRECTORY"가 있는 것을 확인할 수 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='pe_debug_pdb_1.png' src='/SysWebRes/bbs/pe_debug_pdb_1.png' /><br /> <br /> 위의 내용에서 "Pointer to Raw Data" == "0x83C"이고 그 위치에는 "IMAGE_DEBUG_TYPE_CODEVIEW" 타입의 데이터가 위치해 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='pe_debug_pdb_2.png' src='/SysWebRes/bbs/pe_debug_pdb_2.png' /><br /> <br /> <hr style='width: 50%' /><br /> <br /> 그럼, 코드를 통해 이 값들을 추적해 볼까요? ^^<br /> <br /> 우선 PE 파일을 다룰 수 있는 라이브러리를 Nuget으로부터 추가한 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Install-Package Workshell.PE -Version 1.7.0 // 또는 직접 만들거나, libpe - PE32/PE32+ Binaries Viewer Library ; <a target='tab' href='https://www.codeproject.com/Articles/5205732/libpe-PE32-PE32plus-Binaries-Viewer-Library'>https://www.codeproject.com/Articles/5205732/libpe-PE32-PE32plus-Binaries-Viewer-Library</a> </pre> <br /> IMAGE_DEBUG_DIRECTORY를 다음과 같이 가져올 수 있지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Workshell.PE.ExecutableImage pe = Workshell.PE.ExecutableImage.FromFile("ConsoleApp1.exe"); foreach (var item in pe.NTHeaders.DataDirectories) { if (item.DirectoryType == Workshell.PE.DataDirectoryType.Debug) { ShowDebugDirectoryInfo(pe, item); return; } } private static void ShowDebugDirectoryInfo(Workshell.PE.ExecutableImage pe, DataDirectory item) { Console.WriteLine("Debug directory: "); Console.WriteLine("\t RVA: " + item.VirtualAddress.ToString("x")); Console.WriteLine("\t Size: " + item.Size.ToString("x")); LocationCalculator calc = pe.GetCalculator(); ulong offset = calc.RVAToOffset(item.VirtualAddress); // offset == IMAGE_DEBUG_TYPE_CODEVIEW 영역을 가리키는 파일의 위치 // var textSection = item.GetSection(); // offset = item.VirtualAddress - textSection.Location.RelativeVirtualAddress + textSection.Location.FileOffset; } </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;' > Workshell.PE.ExecutableImage pe = Workshell.PE.ExecutableImage.FromFile("ConsoleApp1.exe"); DebugDirectory debugDir = DebugDirectory.Get(pe); DebugDirectoryEntry debugEntry = debugDir[0]; // debugEntry == IMAGE_DEBUG_DIRECTORY 데이터 </pre> <br /> IMAGE_DEBUG_DIRECTORY가 가리키는 IMAGE_DEBUG_TYPE_CODEVIEW의 데이터를 구해오는 방법은 debugEntry.PointerToRawData가 가리키는 파일 위치의 데이터를 debugEntry.SizeOfData 만큼 읽어오면 됩니다. 하지만, Workshell.PE 라이브러리는 이에 대한 것도 다음과 같이 간단하게 가져오는 방법을 제공합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DebugData debugData = DebugData.Get(debugEntry); byte [] buf = debugData.GetBytes(); // buf == IMAGE_DEBUG_TYPE_CODEVIEW 데이터 </pre> <br /> 위의 buf 내용은 PEView 도구를 통해 본 IMAGE_DEBUG_TYPE_CODEVIEW의 데이터와 일치합니다.<br /> <br /> 그렇다면 이제부터 buf 내용을 분석해 봐야 할 텐데, 이에 대해서는 다음의 글에 포맷과 함께 자세한 설명이 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Matching debug information ; <a target='tab' href='http://www.debuginfo.com/articles/debuginfomatch.html'>http://www.debuginfo.com/articles/debuginfomatch.html</a> </pre> <br /> 위의 문서에 보면 IMAGE_DEBUG_DIRECTORY가 n 개의 IMAGE_DEBUG_TYPE_CODEVIEW를 가지고 있다는 것을 알 수 있습니다.<br /> <br /> [그림 출처: <a target='tab' href='http://www.debuginfo.com/articles/debuginfomatch.html'>http://www.debuginfo.com/articles/debuginfomatch.html</a>]<br /> <img alt='pe_debug_pdb_3.gif' src='/SysWebRes/bbs/pe_debug_pdb_3.gif' /><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;' > static void Main(string[] args) { Workshell.PE.ExecutableImage pe = Workshell.PE.ExecutableImage.FromFile("ConsoleApp1.exe"); DebugDirectory debugDir = DebugDirectory.Get(pe); foreach (var debugEntry in debugDir) { if (debugEntry.Type == 2) // 2 == IMAGE_DEBUG_TYPE_CODEVIEW { ShowDebugData(debugEntry); Console.WriteLine(); } } } private static void ShowDebugData(DebugDirectoryEntry debugEntry) { DebugData debugData = DebugData.Get(debugEntry); byte [] buf = debugData.GetBytes(); Console.WriteLine(debugEntry.GetEntryType() + ": Len == " + buf.Length); } </pre> <br /> IMAGE_DEBUG_DIRECTORY가 포함한 디버그 정보가 IMAGE_DEBUG_TYPE_CODEVIEW 타입인 경우 해당 CodeView 구조는 첫 번째 4바이트의 Signature로 구별할 수 있는 다양한 버전이 존재합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > "NB09" - CodeView 4.10 (디버그 정보가 실행 파일 안에 있는 경우) "NB11" - CodeView 5.0 (디버그 정보가 실행 파일 안에 있는 경우) "NB10" - PDB 2.0 파일을 가리키는 경우 "RSDS" - PDB 7.0 파일을 가리키는 경우 (참고로, PDB가 아닌 DBG 파일에 보관된 경우 디버그 정보의 타입은 IMAGE_DEBUG_TYPE_MISC) </pre> <br /> 그런데... NB10을 보니 어디선가 낯이 익습니다. 오호~~~ 예전에 한번 살펴본 글이 있습니다. ^^<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/2925'>http://www.sysnet.pe.kr/2/0/2925</a> </pre> <br /> 그러니까, 이 글에서 만들고 있는 코드들이 결국 위의 글에서 소개한 "<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=928&boardid=331301885'>debugdir.zip</a>"에 담긴 C++ 소스 코드의 C# 버전이었던 것입니다.<br /> <br /> C++ 소스 코드를 보면 CodeView 구조 중 NB10과 RSDS를 다음과 같이 포함하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #define CV_SIGNATURE_NB10 '01BN' #define CV_SIGNATURE_RSDS 'SDSR' // CodeView header struct CV_HEADER { DWORD CvSignature; // NBxx LONG Offset; // Always 0 for NB10 }; // CodeView NB10 debug information // (used when debug information is stored in a PDB 2.00 file) struct CV_INFO_PDB20 { CV_HEADER Header; DWORD Signature; // seconds since 01.01.1970 DWORD Age; // an always-incrementing value BYTE PdbFileName[1]; // zero terminated string with the name of the PDB file }; // CodeView RSDS debug information // (used when debug information is stored in a PDB 7.00 file) struct CV_INFO_PDB70 { DWORD CvSignature; GUID Signature; // unique identifier DWORD Age; // an always-incrementing value BYTE PdbFileName[1]; // zero terminated string with the name of the PDB file }; </pre> <br /> C# 코드도 저 규칙에 맞게 스트림 데이터를 읽어 변환해 주면 됩니다. 다음은, <a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1150&boardid=331301885'>이 글에서 첨부한 실행 파일</a>의 사용 예입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > PE File: ConsoleApp1.exe No IMAGE_DEBUG_DIRECTORY PE File: PDBInfoFromPE.exe CodeView Info Type: RSDS Signature: 65c91292-efaf-448f-ae22-c45caa840f59, Age: 1, Path: F:\cloud_drive\Dropbox\articles\pdb_create\PDBPathFromPE\PDBInfoFromPE\obj\Debug\PDBInfoFromPE.pdb PDB Download Path: \PDBInfoFromPE.pdb\65c91292efaf448fae22c45caa840f591\PDBInfoFromPE.pdb PE File: msvcirt.dll CodeView Info Type: NB10 Signature: 1048575954, Age: 1, Path: msvcirt.pdb PDB Download Path: \msvcirt.pdb\10485759541\msvcirt.pdb </pre> <br /> 위에서 ConsoleApp1.exe의 경우 IMAGE_DEBUG_DIRECTORY가 없다고 나오는데, C# 프로젝트 설정의 "Build" / "Advanced" - "Output" / "Debugging information" 옵션을 "none"으로 주면 "IMAGE_DEBUG_DIRECTORY"가 생성되지 않기 때문입니다.<br /> <br /> 또한, "PDB Donwload Path"라는 경로를 부가적으로 출력하고 있는데 이는 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/2925'>PDB 기호 파일의 경로 구성 방식</a>" 글에서 설명한 규칙을 반영한 것입니다.<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1150&boardid=331301885'>첨부 파일은 이 글의 완전한 코드를 포함</a>합니다.)<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;' > PDB Symbol 로드 오류 - Cannot find or openthe PDB file. ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/987'>http://www.sysnet.pe.kr/2/0/987</a> </pre> <br /> 결국, 이 글의 소스 코드와 함께 PDB 파일로부터 Signature/Age 정보를 읽어내는 코드를 추가하면 위의 글에서 소개한 ChkMatch 프로그램도 만들 수 있습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1016
(왼쪽의 숫자를 입력해야 합니다.)