성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[양승조] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
[정성태] The Windows Registry Adventure #1: ...
[정성태] systemd for Developers I ; https:/...
글쓰기
제목
이름
암호
전자우편
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# - DbgEng.dll을 이용한 간단한 디버거 제작</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;' > Writing a Simple Debugger with DbgEng.Dll ; http://blogs.microsoft.co.il/pavely/2015/07/27/writing-a-simple-debugger-with-dbgeng-dll/ </pre> <br /> 사실 DbgEng.dll의 기능이 워낙 훌륭해서 조금만 코드를 추가하면 cdb 정도의,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CDB Command-Line Options ; <a target='tab' href='https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/cdb-command-line-options'>https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/cdb-command-line-options</a> </pre> <br /> 디버거는 쉽게 만들 수 있습니다. 어느 정도로 쉬운지, ^^ C#으로 간단하게 구현해 보겠습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선 필요한 Win32 구조체/인터페이스들은 이미 다음의 프로젝트에 구현되어 있으니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DbgShell/ClrMemDiag/Debugger/ ; <a target='tab' href='https://github.com/microsoft/DbgShell/tree/master/ClrMemDiag/Debugger'>https://github.com/microsoft/DbgShell/tree/master/ClrMemDiag/Debugger</a> </pre> <br /> 그대로 다운로드해 프로젝트에 추가한 다음, "Writing a Simple Debugger with DbgEng.Dll" 글에서 정리한 내용에 따라 Debugger.cs 소스 코드를 다음과 같이 간단하게 구성할 수 있습니다.<br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using Microsoft.Diagnostics.Runtime.Interop; using System; using System.Runtime.InteropServices; using System.Threading; namespace SimpleDebugger { // DbgShell/ClrMemDiag/Debugger/ // <a target='tab' href='https://github.com/microsoft/DbgShell/tree/master/ClrMemDiag/Debugger'>https://github.com/microsoft/DbgShell/tree/master/ClrMemDiag/Debugger</a> // Writing a Simple Debugger with DbgEng.Dll // http://blogs.microsoft.co.il/pavely/2015/07/27/writing-a-simple-debugger-with-dbgeng-dll/ <span style='color: blue; font-weight: bold'>public class UserDebugger : IDebugOutputCallbacks, IDebugEventCallbacksWide</span>, IDisposable { [DllImport("dbgeng.dll")] internal static extern int DebugCreate(ref Guid InterfaceId, [MarshalAs(UnmanagedType.IUnknown)] out object Interface); IDebugClient5 _client; IDebugControl4 _control; public bool BreakpointHit { get; set; } public bool StateChanged { get; set; } public delegate void ModuleLoadedDelegate(ModuleInfo modInfo); public ModuleLoadedDelegate ModuleLoaded; public delegate void ExceptionOccurredDelegate(ExceptionInfo exInfo); public ExceptionOccurredDelegate ExceptionOccurred; public UserDebugger() { Guid guid = new Guid("27fe5639-8407-4f47-8364-ee118fb08ac8"); object obj = null; int hr = DebugCreate(ref guid, out obj); if (hr < 0) { Console.WriteLine("SourceFix: Unable to acquire client interface"); return; } _client = obj as IDebugClient5; _control = _client as IDebugControl4; _client.SetOutputCallbacks(this); _client.SetEventCallbacksWide(this); } public bool AttachTo(int pid) { int hr = _client.AttachProcess(0, (uint)pid, DEBUG_ATTACH.DEFAULT); return hr >= 0; } public int GetExecutionStatus(out DEBUG_STATUS status) { return _control.GetExecutionStatus(out status); } public void OutputCurrentState(DEBUG_OUTCTL outputControl, DEBUG_CURRENT flags) { _control.OutputCurrentState(outputControl, flags); } public void OutputPromptWide(DEBUG_OUTCTL outputControl, string format) { _control.OutputPromptWide(outputControl, format); } public int Execute(DEBUG_OUTCTL outputControl, string command, DEBUG_EXECUTE flags) { return _control.Execute(DEBUG_OUTCTL.THIS_CLIENT, command, DEBUG_EXECUTE.NOT_LOGGED); } public int ExecuteWide(DEBUG_OUTCTL outputControl, string command, DEBUG_EXECUTE flags) { return _control.ExecuteWide(outputControl, command, flags); } public int WaitForEvent(DEBUG_WAIT flag = DEBUG_WAIT.DEFAULT, int timeout = Timeout.Infinite) { unchecked { return _control.WaitForEvent(flag, (uint)timeout); } } public void SetInterrupt(DEBUG_INTERRUPT flag = DEBUG_INTERRUPT.ACTIVE) { _control.SetInterrupt(flag); } public void Detach() { _client.DetachProcesses(); } public void Dispose() { if (_control != null) { Marshal.ReleaseComObject(_control); _control = null; } if (_client != null) { Marshal.ReleaseComObject(_client); _client = null; } } public int Output([In] DEBUG_OUTPUT Mask, [In, MarshalAs(UnmanagedType.LPStr)] string Text) { switch (Mask) { case DEBUG_OUTPUT.DEBUGGEE: Console.ForegroundColor = ConsoleColor.Gray; break; case DEBUG_OUTPUT.PROMPT: Console.ForegroundColor = ConsoleColor.Magenta; break; case DEBUG_OUTPUT.ERROR: Console.ForegroundColor = ConsoleColor.Red; break; case DEBUG_OUTPUT.EXTENSION_WARNING: case DEBUG_OUTPUT.WARNING: Console.ForegroundColor = ConsoleColor.Yellow; break; case DEBUG_OUTPUT.SYMBOLS: Console.ForegroundColor = ConsoleColor.Cyan; break; default: Console.ForegroundColor = ConsoleColor.White; break; } Console.Write(Text); return 0; } public int GetInterestMask([Out] out DEBUG_EVENT Mask) { Mask = DEBUG_EVENT.BREAKPOINT | DEBUG_EVENT.CHANGE_DEBUGGEE_STATE | DEBUG_EVENT.CHANGE_ENGINE_STATE | DEBUG_EVENT.CHANGE_SYMBOL_STATE | DEBUG_EVENT.CREATE_PROCESS | DEBUG_EVENT.CREATE_THREAD | DEBUG_EVENT.EXCEPTION | DEBUG_EVENT.EXIT_PROCESS | DEBUG_EVENT.EXIT_THREAD | DEBUG_EVENT.LOAD_MODULE | DEBUG_EVENT.SESSION_STATUS | DEBUG_EVENT.SYSTEM_ERROR | DEBUG_EVENT.UNLOAD_MODULE; return 0; } public int Breakpoint([In, MarshalAs(UnmanagedType.Interface)] IDebugBreakpoint2 Bp) { BreakpointHit = true; StateChanged = true; return (int)DEBUG_STATUS.BREAK; } public int Breakpoint([In, MarshalAs(UnmanagedType.Interface)] IDebugBreakpoint Bp) { BreakpointHit = true; StateChanged = true; return (int)DEBUG_STATUS.BREAK; } public int Exception([In] ref EXCEPTION_RECORD64 Exception, [In] uint FirstChance) { if (ExceptionOccurred != null) { ExceptionInfo exInfo = new ExceptionInfo(Exception, FirstChance); ExceptionOccurred(exInfo); } return (int)DEBUG_STATUS.BREAK; } public int CreateThread([In] ulong Handle, [In] ulong DataOffset, [In] ulong StartOffset) { return 0; } public int ExitThread([In] uint ExitCode) { return 0; } public int CreateProcess([In] ulong ImageFileHandle, [In] ulong Handle, [In] ulong BaseOffset, [In] uint ModuleSize, [In, MarshalAs(UnmanagedType.LPStr)] string ModuleName, [In, MarshalAs(UnmanagedType.LPStr)] string ImageName, [In] uint CheckSum, [In] uint TimeDateStamp, [In] ulong InitialThreadHandle, [In] ulong ThreadDataOffset, [In] ulong StartOffset) { return (int)DEBUG_STATUS.NO_CHANGE; } public int ExitProcess([In] uint ExitCode) { return 0; } public int LoadModule([In] ulong ImageFileHandle, [In] ulong BaseOffset, [In] uint ModuleSize, [In, MarshalAs(UnmanagedType.LPStr)] string ModuleName, [In, MarshalAs(UnmanagedType.LPStr)] string ImageName, [In] uint CheckSum, [In] uint TimeDateStamp) { if (ModuleLoaded != null) { ModuleInfo modInfo = new ModuleInfo(ImageFileHandle, BaseOffset, ModuleSize, ModuleName, ImageName, CheckSum, TimeDateStamp); ModuleLoaded(modInfo); } return 0; } public int UnloadModule([In, MarshalAs(UnmanagedType.LPStr)] string ImageBaseName, [In] ulong BaseOffset) { return 0; } public int SystemError([In] uint Error, [In] uint Level) { return 0; } public int SessionStatus([In] DEBUG_SESSION Status) { return 0; } public int ChangeDebuggeeState([In] DEBUG_CDS Flags, [In] ulong Argument) { return 0; } public int ChangeEngineState([In] DEBUG_CES Flags, [In] ulong Argument) { return 0; } public int ChangeSymbolState([In] DEBUG_CSS Flags, [In] ulong Argument) { return 0; } } } </pre> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > IDebugControl interface ; <a target='tab' href='https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/dbgeng/nn-dbgeng-idebugcontrol'>https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/dbgeng/nn-dbgeng-idebugcontrol</a> </pre> <br /> 위의 UserDebugger 타입을 이용하면 이제 간단한 명령행 디버거로 작동하는 콘솔 프로젝트를 구현할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using Microsoft.Diagnostics.Runtime.Interop; using SimpleDebugger; using System; using System.Diagnostics; namespace DbgShell { // DbgShell/ClrMemDiag/Debugger/ // <a target='tab' href='https://github.com/microsoft/DbgShell/tree/master/ClrMemDiag/Debugger'>https://github.com/microsoft/DbgShell/tree/master/ClrMemDiag/Debugger</a> // Writing a Simple Debugger with DbgEng.Dll // http://blogs.microsoft.co.il/pavely/2015/07/27/writing-a-simple-debugger-with-dbgeng-dll/ class Program { static void Main(string[] args) { using (UserDebugger debugger = new UserDebugger()) { Console.CancelKeyPress += (s, e) => { e.Cancel = true; debugger.SetInterrupt(); }; ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = "DummyApp.exe"; psi.UseShellExecute = true; Process child = Process.Start(psi); try { if (debugger.AttachTo(child.Id) == false) { Console.WriteLine("Failed to attach"); return; } int hr = debugger.WaitForEvent(); while (true) { hr = debugger.GetExecutionStatus(out DEBUG_STATUS status); if (hr != (int)HResult.S_OK) { break; } if (status == DEBUG_STATUS.NO_DEBUGGEE) { Console.WriteLine("No Target"); break; } if (status == DEBUG_STATUS.GO || status == DEBUG_STATUS.STEP_BRANCH || status == DEBUG_STATUS.STEP_INTO || status == DEBUG_STATUS.STEP_OVER) { hr = debugger.WaitForEvent(); continue; } if (debugger.StateChanged) { Console.WriteLine(); debugger.StateChanged = false; if (debugger.BreakpointHit) { debugger.OutputCurrentState(DEBUG_OUTCTL.THIS_CLIENT, DEBUG_CURRENT.DEFAULT); debugger.BreakpointHit = false; } } debugger.OutputPromptWide(DEBUG_OUTCTL.THIS_CLIENT, null); Console.Write(" "); Console.ForegroundColor = ConsoleColor.Gray; string command = Console.ReadLine(); debugger.ExecuteWide(DEBUG_OUTCTL.THIS_CLIENT, command, DEBUG_EXECUTE.DEFAULT); } } finally { try { debugger.Detach(); } catch { } try { child.Kill(); } catch { } } } } } } </pre> <br /> 참고로, 디버거 대상 프로세스 예제로 실행하는 DummayApp.exe의 소스 코드는 이렇습니다.<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 DummyApp { class Program { static void Main(string[] args) { while (true) { Console.ReadLine(); try { throw new ApplicationException("TEST"); } catch (Exception e) { } } } } } </pre> <br /> 따라서 DbgShell.exe를 실행하면 다음과 같은 출력 결과로 실행되고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > No .natvis files found at C:\WINDOWS\SYSTEM32\Visualizers. No .natvis files found at C:\Users\%USERPROFILE%\AppData\Local\Dbg\Visualizers. Microsoft (R) Windows Debugger Version 10.0.18362.1 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. *** wait with pending attach ************* Path validation summary ************** Response Time (ms) Location Deferred SRV*c:\Symbols*http://msdl.microsoft.com/download/symbols Symbol search path is: SRV*c:\Symbols*http://msdl.microsoft.com/download/symbols Executable search path is: ModLoad: 00000000`00550000 00000000`00558000 c:\DotNetSamples\WinConsole\Debugger\DbgShell\bin\Debug\DummyApp.exe ModLoad: 00007ffe`19c60000 00007ffe`19e50000 C:\WINDOWS\SYSTEM32\ntdll.dll ModLoad: 00007ffe`022f0000 00007ffe`02354000 C:\WINDOWS\SYSTEM32\MSCOREE.DLL ModLoad: 00007ffe`18e20000 00007ffe`18ed2000 C:\WINDOWS\System32\KERNEL32.dll ModLoad: 00007ffe`16e70000 00007ffe`17113000 C:\WINDOWS\System32\KERNELBASE.dll (54d8.860c): Break instruction exception - code 80000003 (first chance) <span style='color: blue; font-weight: bold'>0:001></span> </pre> <br /> 보는 바와 같이 "0:001>"이라는 명령행 프롬프트까지 <a target='tab' href='https://learn.microsoft.com/is-is/windows-hardware/drivers/ddi/dbgeng/nf-dbgeng-idebugcontrol4-outputpromptwide'>IDebugControl4::OutputPromptWide</a>에 의해 제공되어 windbg에서의 경험 그대로 명령을 실행할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:001> <span style='color: blue; font-weight: bold'>kv</span> Child-SP RetAddr : Args to Child : Call Site 00000000`00c5f858 00007ffe`19d2d4db : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!DbgBreakPoint 00000000`00c5f860 00007ffe`18e37bd4 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!DbgUiRemoteBreakin+0x4b 00000000`00c5f890 00007ffe`19ccced1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 00000000`00c5f8c0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21 0:001> <span style='color: blue; font-weight: bold'>~0s</span> ntdll!RtlNtStatusToDosError+0x86: 00007ffe`19cb3696 8b8cc300601200 mov ecx,dword ptr [rbx+rax*8+126000h] ds:00007ffe`19d864b8=c000a2a1 0:000> <span style='color: blue; font-weight: bold'>kv</span> Child-SP RetAddr : Args to Child : Call Site 00000000`008ff360 00007ffe`16e959ed : 00000000`c0000100 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlNtStatusToDosError+0x86 00000000`008ff390 00007ffe`16e95399 : 00000000`00000000 00000000`00000000 00000000`00992c02 00000000`00000000 : KERNELBASE!SleepConditionVariableSRW+0x7d 00000000`008ff3c0 00007ffe`022fb133 : 00000000`00000000 00007ffe`02339c08 00000200`002b0000 00000000`00000000 : KERNELBASE!GetEnvironmentVariableW+0x59 00000000`008ff400 00007ffe`022fb243 : 00000000`00000000 00000000`008ff820 00000000`00000000 00000000`00000000 : MSCOREE!CLRCreateInstance+0x7b3 00000000`008ff4c0 00007ffe`022f12bc : 00000000`008ff820 00000000`008ff620 00000000`00000000 00000000`00000000 : MSCOREE!CLRCreateInstance+0x8c3 00000000`008ff520 00007ffe`022f1574 : 00000000`008ff820 00000000`00000000 00000000`00000000 00000000`00000000 : MSCOREE!Ordinal141+0x12bc 00000000`008ff7c0 00007ffe`022fa516 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : MSCOREE!Ordinal141+0x1574 00000000`008ff7f0 00007ffe`18e37bd4 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : MSCOREE!CorExeMain+0x16 00000000`008ff820 00007ffe`19ccced1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 00000000`008ff850 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21 </pre> <br /> 정말 쉽죠?!!! ^^<br /> <br /> 이 글의 완전한 예제 코드는 다음의 github에 실려 있습니다.<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/Debugger/SimpleDebugger ; <a target='tab' href='https://github.com/stjeong/DotNetSamples/tree/master/WinConsole/Debugger/SimpleDebugger'>https://github.com/stjeong/DotNetSamples/tree/master/WinConsole/Debugger/SimpleDebugger</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 물론, "!threads"와 같은 확장 명령어를 실행하려면 ext.dll, exts.dll, uext.dll, ntsdexts.dll 등의 모듈을 필요로 합니다. 그리고 (심벌 파일이 아닌) 심벌 서버와 연동을 하려면 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/12091'>pdb 파일을 다운로드하기 위한 symchk.exe 실행에 필요한 최소 파일</a>" 글에서 정리한 DLL들이 필요합니다.<br /> <br /> 달리 말하면, 확장 DLL이라고 공개된 것들은 DbgEng.dll을 이용한 여러분들의 사용자 정의 디버거에서도 잘 동작한다는 것입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
8994
(왼쪽의 숫자를 입력해야 합니다.)