성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
글쓰기
제목
이름
암호
전자우편
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# - 프로세스 스스로 풀 덤프 남기는 방법</h1> <p> 가끔 보면, 필요에 의해서 메모리 덤프를 남겨야 하는 경우가 있습니다. 이때 외부 도구(<a target='tab' href='https://www.microsoft.com/en-us/download/details.aspx?id=58210'>DebugDiag</a>, <a target='tab' href='https://docs.microsoft.com/en-us/sysinternals/downloads/procdump'>Process Dump</a>,...)를 쓰는 것도 가능하지만 최종 사용자에게 이런 도구를 사용하게 하는 것은 어찌 보면 현실성이 없습니다.<br /> <br /> 그냥 EXE 프로세스 내부에서 일정 조건을 체크하고 스스로 남기게 하거나, 아니면 고객 지원팀에서 쉽게 가이드할 수 있도록 숨겨진 단축키로 기능을 제공하는 것이 더 현실적입니다.<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;' > 코드(C#)를 통한 풀 덤프 만드는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/995'>http://www.sysnet.pe.kr/2/0/995</a> </pre> <br /> 그때는 외부 프로세스의 덤프를 뜨는 방법을 소개해서 더 복잡했지만, 프로세스 스스로 덤프를 남기는 것은 단지 덤프 파일이 남게 될 대상 폴더에 권한이 있는지 정도만 확인해 주면 됩니다.<br /> <br /> 소스 코드는 "<a target='tab' href='http://social.msdn.microsoft.com/Forums/is/clr/thread/321f8960-ccbb-49d0-b285-3d2bbf3d20d9'>How to write out a minidump from try/catch in C#?</a>" 글의 내용을 가져다 쓰면 되는데, 제 경우에는 MinidumpWriter 타입으로 정리해 보았습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana; height: 400px;' > using System; using System.Runtime.InteropServices; using System.IO; using System.Diagnostics; namespace Utility { public class MinidumpWriter { [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsWow64Process([In] IntPtr processHandle, [Out, MarshalAs(UnmanagedType.Bool)] out bool wow64Process); [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr hObject); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetCurrentProcess(); [DllImport("advapi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid); [DllImport("advapi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, [MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 Zero, IntPtr Null1, IntPtr Null2); [DllImport("DbgHelp.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)] private static extern Boolean MiniDumpWriteDump( IntPtr hProcess, Int32 processId, IntPtr fileHandle, MiniDumpType dumpType, IntPtr excepInfo, IntPtr userInfo, IntPtr extInfo); [StructLayout(LayoutKind.Sequential)] public struct LUID { public UInt32 LowPart; public Int32 HighPart; } public enum MiniDumpType { Normal = 0x00000000, WithDataSegs = 0x00000001, WithFullMemory = 0x00000002, WithHandleData = 0x00000004, FilterMemory = 0x00000008, ScanMemory = 0x00000010, WithUnloadedModules = 0x00000020, WithIndirectlyReferencedMemory = 0x00000040, FilterModulePaths = 0x00000080, WithProcessThreadData = 0x00000100, WithPrivateReadWriteMemory = 0x00000200, WithoutOptionalData = 0x00000400, WithFullMemoryInfo = 0x00000800, WithThreadInfo = 0x00001000, WithCodeSegs = 0x00002000, WithoutAuxiliaryState = 0x00004000, WithFullAuxiliaryState = 0x00008000 } private static uint <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20240411-00/?p=109634'>STANDARD_RIGHTS_REQUIRED</a> = 0x000F0000; private static uint <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20240411-00/?p=109634'>STANDARD_RIGHTS_READ</a> = 0x00020000; private static uint TOKEN_ASSIGN_PRIMARY = 0x0001; private static uint TOKEN_DUPLICATE = 0x0002; private static uint TOKEN_IMPERSONATE = 0x0004; private static uint TOKEN_QUERY = 0x0008; private static uint TOKEN_QUERY_SOURCE = 0x0010; private static uint TOKEN_ADJUST_PRIVILEGES = 0x0020; private static uint TOKEN_ADJUST_GROUPS = 0x0040; private static uint TOKEN_ADJUST_DEFAULT = 0x0080; private static uint TOKEN_ADJUST_SESSIONID = 0x0100; private static uint TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY); private static uint TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID); public const string SE_DEBUG_NAME = "SeDebugPrivilege"; public const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001; public const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002; public const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004; public const UInt32 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000; [StructLayout(LayoutKind.Sequential)] public struct TOKEN_PRIVILEGES { public UInt32 PrivilegeCount; public LUID Luid; public UInt32 Attributes; } [Flags] public enum ProcessAccessFlags : uint { All = 0x001F0FFF, Terminate = 0x00000001, CreateThread = 0x00000002, VMOperation = 0x00000008, VMRead = 0x00000010, VMWrite = 0x00000020, DupHandle = 0x00000040, SetInformation = 0x00000200, QueryInformation = 0x00000400, Synchronize = 0x00100000 } public static bool MakeDump(string dumpFilePath, int processId) { SetDumpPrivileges(); Process targetProcess = Process.GetProcessById(processId); using (FileStream stream = new FileStream(dumpFilePath, FileMode.Create)) { Boolean res = MiniDumpWriteDump( targetProcess.Handle, processId, stream.SafeFileHandle.DangerousGetHandle(), MiniDumpType.WithFullMemory, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); int dumpError = res ? 0 : Marshal.GetLastWin32Error(); Console.WriteLine(dumpError); } CloseHandle(targetProcess.Handle); return true; } private static void SetDumpPrivileges() { IntPtr hToken; LUID luidSEDebugNameValue; TOKEN_PRIVILEGES tkpPrivileges; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken)) { Console.WriteLine("OpenProcessToken() failed, error = {0} . SeDebugPrivilege is not available", Marshal.GetLastWin32Error()); return; } if (!LookupPrivilegeValue(null, SE_DEBUG_NAME, out luidSEDebugNameValue)) { Console.WriteLine("LookupPrivilegeValue() failed, error = {0} .SeDebugPrivilege is not available", Marshal.GetLastWin32Error()); CloseHandle(hToken); return; } tkpPrivileges.PrivilegeCount = 1; tkpPrivileges.Luid = luidSEDebugNameValue; tkpPrivileges.Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(hToken, false, ref tkpPrivileges, 0, IntPtr.Zero, IntPtr.Zero)) { Console.WriteLine("LookupPrivilegeValue() failed, error = {0} .SeDebugPrivilege is not available", Marshal.GetLastWin32Error()); return; } CloseHandle(hToken); } } } </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 System; using System.Windows.Forms; using System.Diagnostics; using Utility; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { <span style='color: blue; font-weight: bold'>int pid = Process.GetCurrentProcess().Id; MinidumpWriter.MakeDump(@"c:\temp\test.dmp", pid);</span> } } } </pre> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?wid=1485&boardid=331301885'>첨부된 파일은 위의 예제를 포함한 프로젝트</a>입니다.)<br /> <br /> 기왕이면 .dmp 파일을 압축해서 지정된 네트워크 서버로 자동 전송하는 것도 구현하면 금상첨화일 것입니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 덤프를 남겼으면 분석을 해야죠. ^^ 세상이 좋아져서 windbg가 없어도 DebugDiag를 이용하면 쉽게 덤프 분석을 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Debug Diagnostic Tool v1.2 ; <a target='tab' href='http://www.microsoft.com/en-us/download/details.aspx?id=26798'>http://www.microsoft.com/en-us/download/details.aspx?id=26798</a> </pre> <br /> 설치하고 나면 윈도우 탐색기에서 확장자가 .dmp인 경우 컨텍스트 메뉴를 이용해 곧바로 분석을 시도할 수 있습니다.<br /> <br /> <img alt='auto_full_dump_1.png' src='/SysWebRes/bbs/auto_full_dump_1.png' /><br /> <br /> 그런데, "Debug Diagnostic Tool (Analysis Only) GUID" 메뉴를 실행해서 분석을 완료하면 결과가 채 나오기도 전에 "ShellExecute failed to display the report. The returned code was 2." 오류 메시지가 나오는 경우도 있습니다. 이에 대한 원인은 다음의 글에 실려 있는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DebugDiag fails to open report (ShellExecute) ; <a target='tab' href='http://forums.iis.net/t/1146219.aspx'>http://forums.iis.net/t/1146219.aspx</a> </pre> <br /> 따라서, "C:\Program Files\DebugDiag\Reports" 폴더에 대한 "쓰기 권한"을 명시적으로 부여해 주시면 됩니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1897
(왼쪽의 숫자를 입력해야 합니다.)