Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 5.9. Microsoft의 PDB 파일 관리 [링크 복사], [링크+제목 복사],
조회: 31155
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
부모글 보이기/감추기
(연관된 글이 4개 있습니다.)

7. Microsoft의 PDB 파일 관리

지금까지의 글들을 읽어오시면서, PDB 파일의 유무로 인해 얼마나 디버깅이 쉽게 되어질 수 있음을 아시게 되었을 것입니다. (.NET 2.0의 sos가 좀 실망스럽긴 하지만.) 그렇다면, 이제 여러분들도 PDB 파일을 관리해야 할 필요가 생기셨을 텐데요.
과연 어떻게 관리해야 잘되었다고 할 수 있을까요?

사실, 이 문제는 무엇보다도 Microsoft에서 더욱 고민을 했을 것입니다. 여러 운영체제와 그에 따른 방대한 모듈들이, 수많은 클라이언트들의 서로 다른 하드웨어에서 운영되다 보니 Debugging에 관해서는 MS만큼 뼈저리게 느낀 업체도 없을 것이기 때문입니다.

MS는 이에 대해서 "Symbol Server"를 도입함으로써 훌륭하게 해결해 주고 있는데요. 그럼 어떤 것인지 직접 실습을 해보면서 설명을 해보겠습니다.

우선, PDB 파일 관리는 VS.NET 2005에서 하는 방법과 "_NT_SYMBOL_PATH" 환경 변수를 통해서 하는 방법이 있습니다. 개인적으로 WinDBG에서도 함께 사용할 수 있는 "_NT_SYMBOL_PATH" 환경 변수를 더 권장합니다. 환경 변수로 하면 얻어지는 또 다른 이점으로는 Symbol 파일들에 대한 캐시 폴더를 별도로 설정할 수 있다는 장점도 있습니다.

VS.NET 2005의 경우 다음과 같이 "Tools" / "Options" 메뉴를 통해 지정할 수 있습니다. (보시는 것처럼, 모든 심벌 서버에 대해서 단일한 클라이언트 캐시 폴더로만 받을 수 있습니다.)

VS.NET 2005에서 Symbol Server 설정

아래의 방법은 "_NT_SYMBOL_PATH" 환경 변수를 설정하는 방법을 보여주고 있습니다.

_NT_SYMBOL_PATH 설정

저 같은 경우, 환경 변수 값을 아래와 같이 설정하고 있습니다. (일단은 Microsoft 관련 Symbol server 설정만 주목하십시오.)

SRV*\\localhost\d$\Symbol\OSSymbols*http://msdl.microsoft.com/download/symbols;D:\Symbol\ProductSymbols;C:\Windows\system32;D:\Symbol\TestSymbols;

찬찬히 하나씩 뜯어 볼까요? ^^

1. SRV*\\localhost\d$\Symbol\OSSymbols*http://msdl.microsoft.com/download/symbols;
2. D:\Symbol\ProductSymbols;
3. C:\Windows\system32;
4. D:\Symbol\TestSymbols;

1번 항목을 통해서, Microsoft의 Symbol 파일에 대해서 로컬 PC의 "D:\Symbol\OSSymbols" 폴더로 다운로드하도록 합니다. 이 폴더는 상당히 커질 수 있는데요. native dll뿐만 아니라, .NET Framework BCL의 모든 PDB 파일들이 저장되게 됩니다.
물론, 경로에 보시는 것처럼 SRV* 다음에는 UNC 경로를 통해 네트워크 드라이브를 지정할 수 있으며 팀 간에 서로 공유할 수 있습니다. 공유라는 것이 좋긴 하지만, 개인적으로는 UNC 경로라는 것으로 인해 항상 그 컴퓨터를 접근할 수 있어야 하는데 그렇지 못하기 때문에 그냥 로컬 PC에 저장하는 것을 더 선호합니다. (만약 제가 주로 작업하는 컴퓨터가 노트북이 아닌 붙박이 데스크톱이었다면 공유 폴더를 지정했을 것입니다.)

2번 항목은, 제가 작업하는 회사 제품들에 대한 PDB 파일들을 보관하는 폴더입니다.

3번 항목은, system32 폴더에 기본적으로 설치되는 PDB 파일들을 이용할 수 있게 하도록 지정한 것입니다.

4번 항목은, 테스트 용도의 프로젝트에 대한 PDB 파일을 임시로 저장해 놓을 목적으로 만든 것입니다.

또 다른 팁을 하나 알려드리자면, 저 같은 경우 다음과 같이 환경 변수를 2개를 만들어서 경우에 따라 번갈아 가면서 설정을 합니다.

1번 유형. _NT_SYMBOL2_PATH : D:\Symbol\OSSymbols;D:\Symbol\ProductSymbols;C:\Windows\system32;D:\Symbol\TestSymbols;
2번 유형. _NT_SYMBOL_PATH : SRV*\\localhost\d$\Symbol\OSSymbols*http://msdl.microsoft.com/download/symbols;D:\Symbol\ProductSymbols;C:\Windows\system32;D:\Symbol\TestSymbols;

네트워크가 끊겨진 상태 또는 PDB 파일이 많이 누락된 프로젝트를 디버깅할 때는 1번 유형으로 설정해 놓고요. 새로운 유형의 프로젝트로 인해 PDB 파일을 다운로드하는 처음 시도에서는 2번 유형으로 해놓습니다.
예를 들어, Firefox 브라우저에 올라가는 DLL을 디버깅할 때가 있었는데요. Firefox 자체의 PDB 파일들이 없음으로 인해서 로드되는 Firefox 모듈들마다 "http://msdl.microsoft.com/download/symbols"에서 다운로드하는 시도로 인해 디버깅 시작이 느려지는 문제가 발생을 했었습니다. 따라서 이런 경우에는 1번 유형으로 설정을 해놓으시면 빠른 디버깅 시작을 할 수 있게 되지요. ^^

자, 이제 값 설정하는 방법을 알았으니 여러분들만의 경로를 적절하게 설정하십시오. 반드시 설정해야만 이후의 실습을 테스트해 보실 수 있습니다. 두 번 설정하지 않기 위해 가능한 VS.NET 2005 설정보다는 "_NT_SYMBOL_PATH"에다 지정하기를 권장합니다.



그럼, 실습 삼아서 지금까지 다뤄왔던 BaseLibrary를 참조하고 있던 WinFormApp를 로드해 봅니다. "F5" 를 눌러서 디버깅을 시작하면 평소보다 느린 디버깅 화면 전환을 경험하실 수가 있습니다. 기다리기 지루하신 분은 위에서 설정했던 "\\localhost\d$\Symbol\OSSymbols" 폴더를 살펴보시기 바랍니다. WinFormApp 프로젝트를 시작하기 위해서 필요한 DLL들이 Microsoft가 제공하는 Symbol Server로부터 다운로드와 함께 캐시되고 있는 것을 확인할 수 있습니다.
물론, VS.NET 2005 디버거에서는 이 PDB 파일들을 디버깅 시에 이용하게 되는데요. Symbol 파일들이 정상적으로 올라오고 있는지 확인하기 위해서 다음과 같이 "Modules" 창을 띄워 봅니다.

PDB 파일들이 로드된 상태

이 화면을 보면,,, 사실 박수를 치고픈 마음이 안 생기시나요? ^^ 저 같은 경우에는 그랬습니다. 레드몬드에 있는 이들에게 마음껏 박수를 쳐주고 싶었습니다. 그들의 노고에 감사할 따름입니다.

"Symbol Status"에 보면, "Symbols loaded (source information stripped)." 상태가 있는가 하면, 단순히 "Symbols loaded." 상태도 있는데요. 그 둘 간의 차이는 VC++을 하신 분들이라면 아실 수 있을 것입니다. VS.NET 2003/2005에서는 VC++ 프로젝트에 대해 소스 파일 및 라인 정보 등의 사적인(private) 정보를 제외시킨 PDB 파일을 지원하는 데 바로 거기에서 오는 차이입니다.
나중에 설명할 기회가 있겠지만, Microsoft는 "Code Premium"에 가입한 사람들을 대상으로 소스 정보 및 라인 정보까지 포함한 "Full PDB" 파일까지도 사용할 수 있도록 하는 방법을 제공합니다. (물론, 이걸 이용해서 CCP 사이트에 있는 Microsoft 모듈의 소스 파일까지 F11 디버깅이 가능하게 됩니다.)



VS.NET 2005뿐만 아니라, WinDBG에서도 심벌을 로드하는 화면을 확인해 보겠습니다. WinDBG를 실행시키고, "File" / "Open Executable..."을 선택해서 WinForm.exe를 로드합니다. 다음은 처음 로드된 모습을 보여주고 있습니다.

WinDBG

이 상태에서 하단의 command 창에, "LM" 명령을 입력하면 다음과 같은 결과를 볼 수 있습니다.

0:000> lm
start    end        module name
00400000 00408000   WinForm    (deferred)             
77e40000 77f42000   KERNEL32   (deferred)             
79000000 79045000   mscoree    (deferred)             
7c800000 7c8c0000   ntdll      (pdb symbols)          D:\Symbol\OSSymbols\ntdll.pdb\DCE823FCF71A4BF5AA489994520EA18F2\ntdll.pdb

WinForm.exe가 Loader에 의해서 로드되자마자 정지된 상태이므로 관련 DLL들이 몇개 되지 않는 것을 볼 수 있습니다. 그중에서 이미 ntdll.pdb는 기본적으로 올라와 있는 데요. 이처럼, WinDBG는 기본적으로 PDB 심벌 파일들을 로드하지는 않습니다. 개발자가 임의로 로드될 심벌들을 지정할 수 있는데요. 저 같은 경우에는 다음과 같이 전체 PDB를 로드하는 명령어를 즐겨 사용합니다.

0:000> .reload -f
Reloading current modules
.*** WARNING: Unable to verify checksum for WinForm.exe
...

경고 하나가 있긴 한데요. 역시 VC++ 하신 분들은 아실 텐데, 별로 중요한 것이 아니니 설명은 생략하겠습니다. 아무 이상이 없다고만 알고 넘어가겠습니다. 실제로 원하는 PDB 파일들이 올라왔는지 확인하기 위해서 다음과 같이 다시 "lm" 명령을 통해서 확인해 봅니다.

0:000> lm
start    end        module name
00400000 00408000   WinForm  C (private pdb symbols)  D:\temp2\PdbSample\WinFormApp\WinForm\bin\Debug\WinForm.pdb
77e40000 77f42000   KERNEL32   (pdb symbols)          D:\Symbol\OSSymbols\kernel32.pdb\75CFE96517E5450DA600C870E95399FF2\kernel32.pdb
79000000 79045000   mscoree    (pdb symbols)          D:\Symbol\OSSymbols\mscoree.pdb\0D7C30DDE4864A76BCE4B0CB18E63C4E2\mscoree.pdb
7c800000 7c8c0000   ntdll      (pdb symbols)          D:\Symbol\OSSymbols\ntdll.pdb\DCE823FCF71A4BF5AA489994520EA18F2\ntdll.pdb

확인이 잘 되었습니다. ^^ 잠깐 설명드리지만, 위에서 "private pdb symbols"는 사적인 정보까지 포함한 Full PDB 유형이라고 볼 수 있겠고, "pdb symbols"는 그런 정보들이 누락된 PDB 유형일 것입니다.
그리고, "WinForm C ( private pdb symbols )"에서 중간에 "C"는 Checksum을 찾을 수 없다는 의미합니다. VC++에서는 Checksum을 dll/exe에 기록하는 컴파일 옵션이 제공이 되는데요. .NET 같은 경우에는 런타임 시에 컴파일되는 유형이죠. 아마도 그래서 이 Checksum을 기록하는 컴파일 옵션이 .NET 프로젝트에는 없는 것 같습니다.



사실, PDB 파일 로드 유무는 Call Stack과 Disassembly 창에서 가장 큰 차이를 느낄 수 있습니다. 비록 Managed 환경에서는 중간 언어의 장점으로 인해 그런 부분들이 크게 두각을 나타내지는 못하지만, Win32 VC++ 같은 개발 환경에서는 그 차이가 확실하게 나타나게 됩니다.

일례로 간단하게 VC++ 8.0에서 MFC Dialog-Based 프로젝트를 기본 생성한 다음, ::OnInitDialog 메서드에서 다음과 같이 "MessageBox"를 띄우는 것을 예로 들어보겠습니다.

01: BOOL CTestDlg::OnInitDialog()
02: {
............. [중간 생략] ...
08:		SetIcon(m_hIcon, FALSE);		// Set small icon
09: 
10:		::MessageBox( NULL, L"Hello World!", L"Title", MB_OK );
11: 
12:		return TRUE;  // return TRUE  unless you set the focus to a control
13: }

우선, Symbol Server가 지정이 되지 않은 상태로 "라인 10"에 BP를 설정해서 "F5" 키로 디버깅을 시작합니다.
10번째 줄에서 디버깅이 멈췄으면 마우스 오른쪽 버튼을 이용해서 "Go To Disassembly" 창으로 이동합니다. 그럼, 다음과 같은 정보가 Disassembly 창에 보이게 됩니다.

    96: 
    97: 	::MessageBox( NULL, L"Hello World!", L"Title", MB_OK );
004128C2 8B F4            mov         esi,esp 
004128C4 6A 00            push        0    
004128C6 68 54 B9 41 00   push        offset string L"Title" (41B954h) 
004128CB 68 60 B9 41 00   push        offset string L"Hello World!" (41B960h) 
004128D0 6A 00            push        0    
004128D2 FF 15 D8 0A 42 00 call        dword ptr [__imp__MessageBoxW@16 (420AD8h)] 

"F11" 키로 "call dword ptr [__imp__MessageBoxW@16 (420AD8h)]" 지점까지 계속 추적해 들어갑니다. "call"이 실행된 이후에는 다음과 같은 정보가 나옵니다.

773B197B 8B FF            mov         edi,edi 
773B197D 55               push        ebp  
773B197E 8B EC            mov         ebp,esp 
773B1980 83 3D 18 21 3E 77 00 cmp         dword ptr ds:[773E2118h],0 
773B1987 0F 85 52 45 02 00 jne         773D5EDF 
773B198D 6A 00            push        0    
773B198F FF 75 14         push        dword ptr [ebp+14h] 
773B1992 FF 75 10         push        dword ptr [ebp+10h] 
773B1995 FF 75 0C         push        dword ptr [ebp+0Ch] 
773B1998 FF 75 08         push        dword ptr [ebp+8] 
773B199B E8 09 00 00 00   call        773B19A9 
773B19A0 5D               pop         ebp  
773B19A1 C2 10 00         ret         10h  

이제, "Call stack" 창을 띄워서 보게 되면 다음과 같이 되어 있는 것을 확인할 수 있습니다.

MS 관련 PDB 로딩이 되어 있지 않은 상태의 Call stack 창

그동안 특별히 심벌 서버 설정을 해보지 않은 분들이라면 평소에 보아왔던 지극히 당연한 화면입니다. ^^ 이제 이것이 PDB 파일 로드로 인해 어떻게 바뀌어지는 한번 확인해 볼까요! ^^
이제, 위에서 설명했던 내용으로 "_NT_SYMBOL_PATH" 환경 설정 변수에 다음과 같은 내용을 기록합니다.

SRV*\\localhost\d$\Symbol\OSSymbols*http://msdl.microsoft.com/download/symbols

그다음, 이미 실행되어 있던 VS.NET 2005 IDE를 종료하고 다시 실행한 후, 위에서 했던 MFC 프로젝트를 다시 로딩합니다. 절차는 MessageBox 안에 들어간 곳까지 다시 되풀이 합니다.
MessageBox 호출 전까지의 내용은 동일한데요. "F11" 키로 "call dword ptr [__imp__MessageBoxW@16 (420AD8h)]" 안으로 추적해 들어가는 순간부터 상황이 바뀌게 됩니다. 즉, PDB 파일이 있는 지금은 다음과 같은 disassembly 내용이 화면에 보여지게 됩니다.

_MessageBoxW@16:
773B197B 8B FF            mov         edi,edi 
773B197D 55               push        ebp  
773B197E 8B EC            mov         ebp,esp 
773B1980 83 3D 18 21 3E 77 00 cmp         dword ptr [_gfEMIEnable (773E2118h)],0 
773B1987 0F 85 52 45 02 00 jne         _MessageBoxW@16+0Eh (773D5EDFh) 
773B198D 6A 00            push        0    
773B198F FF 75 14         push        dword ptr [ebp+14h] 
773B1992 FF 75 10         push        dword ptr [ebp+10h] 
773B1995 FF 75 0C         push        dword ptr [ebp+0Ch] 
773B1998 FF 75 08         push        dword ptr [ebp+8] 
773B199B E8 09 00 00 00   call        _MessageBoxExW@20 (773B19A9h) 
773B19A0 5D               pop         ebp  
773B19A1 C2 10 00         ret         10h 

훨씬 더 자세한 정보를 볼 수가 있지요. Call stack 창도 알기 쉽게 다음과 같은 정보를 보여주고 있습니다.

MS 관련 PDB 로딩이 되어 있는 상태의 Call stack 창

바꿔 말하면, 여러분들이 스스로 개발한 모듈의 PDB 파일을 유지하고 있다면 디버깅 시에 위와 같은 정보를 얻을 수 있다는 것을 의미합니다.

여기서 마치고, 다음에는 자사 제품의 PDB 파일을 보관하는 Symbol Server를 구성하는 방법을 알아보겠습니다.




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






[최초 등록일: ]
[최종 수정일: 7/10/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13610정성태4/28/2024210닷넷: 2251. C# - 제네릭 인자를 가진 타입을 생성하는 방법 - 두 번째 이야기
13609정성태4/27/2024234닷넷: 2250. PInvoke 호출 시 참조 타입(class)을 마샬링하는 [IN], [OUT] 특성파일 다운로드1
13608정성태4/26/2024427닷넷: 2249. C# - 부모의 필드/프로퍼티에 대해 서로 다른 자식 클래스 간에 Reflection 접근이 동작할까요?파일 다운로드1
13607정성태4/25/2024487닷넷: 2248. C# - 인터페이스 타입의 다중 포인터를 인자로 갖는 C/C++ 함수 연동
13606정성태4/24/2024667닷넷: 2247. C# - tensorflow 연동 (MNIST 예제)파일 다운로드1
13605정성태4/23/2024789닷넷: 2246. C# - Python.NET을 이용한 파이썬 소스코드 연동파일 다운로드1
13604정성태4/22/2024838오류 유형: 901. Visual Studio - Unable to set the next statement. Set next statement cannot be used in '[Exception]' call stack frames.
13603정성태4/21/2024954닷넷: 2245. C# - IronPython을 이용한 파이썬 소스코드 연동파일 다운로드1
13602정성태4/20/2024972닷넷: 2244. C# - PCM 오디오 데이터를 연속(Streaming) 재생 (Windows Multimedia)파일 다운로드1
13601정성태4/19/2024993닷넷: 2243. C# - PCM 사운드 재생(NAudio)파일 다운로드1
13600정성태4/18/20241015닷넷: 2242. C# - 관리 스레드와 비관리 스레드
13599정성태4/17/2024950닷넷: 2241. C# - WAV 파일의 PCM 사운드 재생(Windows Multimedia)파일 다운로드1
13598정성태4/16/2024995닷넷: 2240. C# - WAV 파일 포맷 + LIST 헤더파일 다운로드2
13597정성태4/15/2024988닷넷: 2239. C# - WAV 파일의 PCM 데이터 생성 및 출력파일 다운로드1
13596정성태4/14/20241103닷넷: 2238. C# - WAV 기본 파일 포맷파일 다운로드1
13595정성태4/13/20241072닷넷: 2237. C# - Audio 장치 열기 (Windows Multimedia, NAudio)파일 다운로드1
13594정성태4/12/20241094닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio)파일 다운로드1
13593정성태4/8/20241094닷넷: 2235. MSBuild - AccelerateBuildsInVisualStudio 옵션
13592정성태4/2/20241232C/C++: 165. CLion으로 만든 Rust Win32 DLL을 C#과 연동
13591정성태4/2/20241209닷넷: 2234. C# - WPF 응용 프로그램에 Blazor App 통합파일 다운로드1
13590정성태3/31/20241091Linux: 70. Python - uwsgi 응용 프로그램이 k8s 환경에서 OOM 발생하는 문제
13589정성태3/29/20241196닷넷: 2233. C# - 프로세스 CPU 사용량을 나타내는 성능 카운터와 Win32 API파일 다운로드1
13588정성태3/28/20241543닷넷: 2232. C# - Unity + 닷넷 App(WinForms/WPF) 간의 Named Pipe 통신 [2]파일 다운로드1
13587정성태3/27/20241397오류 유형: 900. Windows Update 오류 - 8024402C, 80070643
13586정성태3/27/20241607Windows: 263. Windows - 복구 파티션(Recovery Partition) 용량을 늘리는 방법
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...