성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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# - ETW 관련 Win32 API 사용 예제 코드 (4) CLR ETW Consumer</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;' > C# - ETW 관련 Win32 API 사용 예제 코드 (1) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12292'>https://www.sysnet.pe.kr/2/0/12292</a> C# - ETW 관련 Win32 API 사용 예제 코드 (2) NT Kernel Logger ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12296'>https://www.sysnet.pe.kr/2/0/12296</a> C# - ETW 관련 Win32 API 사용 예제 코드 (3) ETW Consumer 구현 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12299'>https://www.sysnet.pe.kr/2/0/12299</a> </pre> <br /> 실습한 내용을 바탕으로 이제 CLR 관련 ETW 이벤트를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12244'>https://www.sysnet.pe.kr/2/0/12244</a> </pre> <br /> Win32 API를 이용한 방법으로 재구성해 보겠습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선, CLR을 위한 ETW Provider를 찾아야겠지요. <a target='tab' href='https://www.sysnet.pe.kr/2/0/12292#etw_list'>ETW Provider 목록</a>에 보면 당연히 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/12244'>ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용</a>" 글에서 다룬 "Microsoft-Windows-DotNETRuntime"과 그것의 GUID 값인 "{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}"도 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CLR ETW Providers ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/performance/clr-etw-providers'>https://learn.microsoft.com/en-us/dotnet/framework/performance/clr-etw-providers</a> </pre> <br /> 따라서, 지난 글의 소스 코드에서 단순히 ETW Provider GUID 값만 바꿔 다음과 같이 구현할 수 있습니다.<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.Samples.Eventing.Interop; using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; namespace ConsoleApp1 { class Program { static bool _disposed = false; static int _processId = 0; static void Main(string[] _) { _processId = Process.GetCurrentProcess().Id; string sessionName = "clrETWSession"; <span style='color: blue; font-weight: bold'>Guid clrProvider = new Guid("{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}");</span> int result = 0; ulong traceHandle = 0; ulong sessionHandle = 0; try { if (EtwInterop.IsSessionActive(sessionName, out var _) == true) { } else { EventTraceProperties prop = new EventTraceProperties(true, sessionName); result = NativeMethods.StartTrace(out sessionHandle, sessionName, ref prop); Console.WriteLine(result); if (result == 0) { ENABLE_TRACE_PARAMETERS enableParameters = new ENABLE_TRACE_PARAMETERS(); enableParameters.Version = 1; enableParameters.EnableProperty = (uint)EventEnableProperty.Sid; result = NativeMethods.EnableTraceEx2(sessionHandle, ref clrProvider, NativeMethods.EVENT_CONTROL_CODE_ENABLE_PROVIDER, (byte)TraceEventLevel.Informational, 0, 0, 0, ref enableParameters); Console.WriteLine(result); } } if (result == 0) { EventTraceLogfile logFile = new EventTraceLogfile(); logFile.LoggerName = sessionName; logFile.EventRecordCallback = EventRecordCallback; logFile.ProcessTraceMode = NativeMethods.PROCESS_TRACE_MODE_EVENT_RECORD | NativeMethods.PROCESS_TRACE_MODE_REAL_TIME | NativeMethods.PROCESS_TRACE_MODE_RAW_TIMESTAMP; traceHandle = NativeMethods.OpenTrace(ref logFile); } if (traceHandle != 0) { Thread t = new Thread((ThreadStart)( () => { Console.WriteLine("PrcoessTrace: " + EtwInterop.ProcessTrace(traceHandle)); })); t.Start(); Console.WriteLine("Press ENTER key to exit..."); Console.ReadLine(); // Console.WriteLine("Disabled: " + EtwInterop.DisableProvider(sessionHandle, ref auditApiCalls)); Console.WriteLine("Closed: " + (NativeMethods.CloseTrace(traceHandle) == 0)); t.Join(); } } finally { _disposed = true; EtwInterop.CloseActiveSession(sessionName); } } static private void EventRecordCallback([In] ref EventRecord eventRecord) { if (_processId != eventRecord.EventHeader.ProcessId) { return; } Console.WriteLine(DateTime.Now + ": " + eventRecord.EventHeader.ProcessId); } } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 위와 같이 실행하면 현재 프로세스에서 발생하는 CLR 이벤트를 종류에 상관없이 모두 콜백을 받는데요, "<a target='tab' href='https://www.sysnet.pe.kr/2/0/12244'>ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용</a>" 글에서는 이벤트 종류를 선택할 수 있는 코드가,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > userSession.EnableProvider( <span style='color: blue; font-weight: bold'>ClrTraceEventParser.ProviderGuid</span>, TraceEventLevel.Verbose, (ulong)( <span style='color: blue; font-weight: bold'>ClrTraceEventParser.Keywords.Contention | // thread contention timing ClrTraceEventParser.Keywords.Threading | // threadpool events ClrTraceEventParser.Keywords.Exception | // get the first chance exceptions ClrTraceEventParser.Keywords.GCHeapAndTypeNames | ClrTraceEventParser.Keywords.Type | // for finalizer and exceptions type names ClrTraceEventParser.Keywords.GC // garbage collector details</span> ) ); </pre> <br /> 있었으므로 이 부분에 대해서도 좀 더 봐야 할 필요가 있습니다. 우선, 위의 코드에서 "Keywords"에 담긴 값들을 ETW 이벤트에 대한 필터링 조건으로 주고 있는데 역어셈블을 해 보면 다음과 같이 값을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [Flags] public enum Keywords : long { None = 0L, All = -65L, GC = 1L, GCHandle = 2L, Binder = 4L, Loader = 8L, Jit = 0x10L, NGen = 0x20L, StartEnumeration = 0x40L, StopEnumeration = 0x80L, Security = 0x400L, AppDomainResourceManagement = 0x800L, JitTracing = 0x1000L, Interop = 0x2000L, Contention = 0x4000L, Exception = 0x8000L, Threading = 0x10000L, JittedMethodILToNativeMap = 0x20000L, OverrideAndSuppressNGenEvents = 0x40000L, Type = 0x80000L, GCHeapDump = 0x100000L, GCSampledObjectAllocationHigh = 0x200000L, GCHeapSurvivalAndMovement = 0x400000L, GCHeapCollect = 0x800000L, GCHeapAndTypeNames = 0x1000000L, GCSampledObjectAllocationLow = 0x2000000L, GCAllObjectAllocation = 0x2200000L, SupressNGen = 0x40000L, PerfTrack = 0x20000000L, Stack = 0x40000000L, ThreadTransfer = 0x80000000L, Debugger = 0x100000000L, Monitoring = 0x200000000L, Codesymbols = 0x400000000L, Compilation = 0x1000000000L, CompilationDiagnostic = 0x2000000000L, Default = 0x14c14fccbdL, JITSymbols = 0x60098L, GCHeapSnapshot = 0x1980001L } </pre> <br /> 그런데, 이 값들은 Microsoft.Diagnostics.Tracing.TraceEvent 라이브러리 제작자가 임의로 결정한 것이 아니고 ETW manifest 파일, 즉 공급자가 정해 놓은 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\Windows\Microsoft.NET\Framework\v4.0.30319\CLR-ETW.man C:\Windows\Microsoft.NET\Framework64\v4.0.30319\CLR-ETW.man </pre> <br /> 실제로 CLR-ETW.man 파일을 보면 다음과 같은 내용들을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[생략]... <keywords> <keyword name="GCKeyword" mask="0x1" message="$(string.RuntimePublisher.GCKeywordMessage)" symbol="CLR_GC_KEYWORD"/> <keyword name="GCHandleKeyword" mask="0x2" message="$(string.RuntimePublisher.GCHandleKeywordMessage)" symbol="CLR_GCHANDLE_KEYWORD"/> <keyword name="FusionKeyword" mask="0x4" message="$(string.RuntimePublisher.FusionKeywordMessage)" symbol="CLR_FUSION_KEYWORD"/> <keyword name="LoaderKeyword" mask="0x8" message="$(string.RuntimePublisher.LoaderKeywordMessage)" symbol="CLR_LOADER_KEYWORD"/> <keyword name="JitKeyword" mask="0x10" message="$(string.RuntimePublisher.JitKeywordMessage)" symbol="CLR_JIT_KEYWORD"/> <keyword name="NGenKeyword" mask="0x20" message="$(string.RuntimePublisher.NGenKeywordMessage)" symbol="CLR_NGEN_KEYWORD"/> <keyword name="StartEnumerationKeyword" mask="0x40" message="$(string.RuntimePublisher.StartEnumerationKeywordMessage)" symbol="CLR_STARTENUMERATION_KEYWORD"/> <keyword name="EndEnumerationKeyword" mask="0x80" message="$(string.RuntimePublisher.EndEnumerationKeywordMessage)" symbol="CLR_ENDENUMERATION_KEYWORD"/> <!-- Keyword mask 0x100 is now defunct --> <!-- Keyword mask 0x200 is now defunct --> <keyword name="SecurityKeyword" mask="0x400" message="$(string.RuntimePublisher.SecurityKeywordMessage)" symbol="CLR_SECURITY_KEYWORD"/> <keyword name="AppDomainResourceManagementKeyword" mask="0x800" message="$(string.RuntimePublisher.AppDomainResourceManagementKeywordMessage)" symbol="CLR_APPDOMAINRESOURCEMANAGEMENT_KEYWORD"/> <keyword name="JitTracingKeyword" mask="0x1000" message="$(string.RuntimePublisher.JitTracingKeywordMessage)" symbol="CLR_JITTRACING_KEYWORD"/> <keyword name="InteropKeyword" mask="0x2000" message="$(string.RuntimePublisher.InteropKeywordMessage)" symbol="CLR_INTEROP_KEYWORD"/> <keyword name="ContentionKeyword" mask="0x4000" message="$(string.RuntimePublisher.ContentionKeywordMessage)" symbol="CLR_CONTENTION_KEYWORD"/> <keyword name="ExceptionKeyword" mask="0x8000" message="$(string.RuntimePublisher.ExceptionKeywordMessage)" symbol="CLR_EXCEPTION_KEYWORD"/> <keyword name="ThreadingKeyword" mask="0x10000" message="$(string.RuntimePublisher.ThreadingKeywordMessage)" symbol="CLR_THREADING_KEYWORD"/> <keyword name="JittedMethodILToNativeMapKeyword" mask="0x20000" message="$(string.RuntimePublisher.JittedMethodILToNativeMapKeywordMessage)" symbol="CLR_JITTEDMETHODILTONATIVEMAP_KEYWORD"/> <keyword name="OverrideAndSuppressNGenEventsKeyword" mask="0x40000" message="$(string.RuntimePublisher.OverrideAndSuppressNGenEventsKeywordMessage)" symbol="CLR_OVERRIDEANDSUPPRESSNGENEVENTS_KEYWORD"/> <keyword name="TypeKeyword" mask="0x80000" message="$(string.RuntimePublisher.TypeKeywordMessage)" symbol="CLR_TYPE_KEYWORD"/> <keyword name="GCHeapDumpKeyword" mask="0x100000" message="$(string.RuntimePublisher.GCHeapDumpKeywordMessage)" symbol="CLR_GCHEAPDUMP_KEYWORD"/> <keyword name="GCSampledObjectAllocationHighKeyword" mask="0x200000" message="$(string.RuntimePublisher.GCSampledObjectAllocationHighKeywordMessage)" symbol="CLR_GCHEAPALLOCHIGH_KEYWORD"/> <keyword name="GCHeapSurvivalAndMovementKeyword" mask="0x400000" message="$(string.RuntimePublisher.GCHeapSurvivalAndMovementKeywordMessage)" symbol="CLR_GCHEAPSURVIVALANDMOVEMENT_KEYWORD"/> <keyword name="GCHeapCollectKeyword" mask="0x800000" message="$(string.RuntimePublisher.GCHeapCollectKeyword)" symbol="CLR_GCHEAPCOLLECT_KEYWORD"/> <keyword name="GCHeapAndTypeNamesKeyword" mask="0x1000000" message="$(string.RuntimePublisher.GCHeapAndTypeNamesKeyword)" symbol="CLR_GCHEAPANDTYPENAMES_KEYWORD"/> <keyword name="GCSampledObjectAllocationLowKeyword" mask="0x2000000" message="$(string.RuntimePublisher.GCSampledObjectAllocationLowKeywordMessage)" symbol="CLR_GCHEAPALLOCLOW_KEYWORD"/> <keyword name="PerfTrackKeyword" mask="0x20000000" message="$(string.RuntimePublisher.PerfTrackKeywordMessage)" symbol="CLR_PERFTRACK_KEYWORD"/> <keyword name="StackKeyword" mask="0x40000000" message="$(string.RuntimePublisher.StackKeywordMessage)" symbol="CLR_STACK_KEYWORD"/> <keyword name="ThreadTransferKeyword" mask="0x80000000" message="$(string.RuntimePublisher.ThreadTransferKeywordMessage)" symbol="CLR_THREADTRANSFER_KEYWORD"/> <keyword name="DebuggerKeyword" mask="0x100000000" message="$(string.RuntimePublisher.DebuggerKeywordMessage)" symbol="CLR_DEBUGGER_KEYWORD"/> <keyword name="MonitoringKeyword" mask="0x200000000" message="$(string.RuntimePublisher.MonitoringKeywordMessage)" symbol="CLR_MONITORING_KEYWORD"/> </keywords> ...[생략]... </pre> <br /> 이런 식으로 ETW Provider 측에서 제공하는 "Keywords"에 기반을 둔 필터링은 EnableProvider2 메서드의 5번째 인자에 전달하면 되고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>ulong matchAnyKeywords = (ulong)ClrProviderKeywords.Exception;</span> result = NativeMethods.EnableTraceEx2(sessionHandle, ref clrProvider, NativeMethods.EVENT_CONTROL_CODE_ENABLE_PROVIDER, (byte)TraceEventLevel.Informational, <span style='color: blue; font-weight: bold'>matchAnyKeywords</span>, 0, 0, ref enableParameters); </pre> <br /> 따라서 위의 코드는 Exception에 대한 조건만을 전달했으므로 이후 콜백 함수는 Exception이 발생했을 때에만 호출되는 것을 확인할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> EnableTraceEx2의 6번째 인자를 보면 또 다른 필터링 조건이 있는데, 다음의 글에서 자세한 설명을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Tampering with Windows Event Tracing: Background, Offense, and Defense ; <a target='tab' href='https://medium.com/palantir/tampering-with-windows-event-tracing-background-offense-and-defense-4be7ac62ac63'>https://medium.com/palantir/tampering-with-windows-event-tracing-background-offense-and-defense-4be7ac62ac63</a> </pre> <br /> Microsoft.Diagnostics.Tracing.TraceEvent 라이브러리에는 다음과 같이 정의되어 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Used int the EVENT_FILTER_DESCRIPTOR.Type field internal const int EVENT_FILTER_TYPE_NONE = (0x00000000); internal const int EVENT_FILTER_TYPE_SCHEMATIZED = unchecked((int)(0x80000000)); internal const int EVENT_FILTER_TYPE_SYSTEM_FLAGS = unchecked((int)(0x80000001)); internal const int EVENT_FILTER_TYPE_TRACEHANDLE = unchecked((int)(0x80000002)); // Used with CAPTURE_STATE to get a rundown delivered only to your session internal const int EVENT_FILTER_TYPE_PID = unchecked((int)(0x80000004)); // Ptr points at array of ints. (Size determined by byteSize/sizeof(int) internal const int EVENT_FILTER_TYPE_EXECUTABLE_NAME = unchecked((int)(0x80000008)); // Ptr points at string, can have ';' to separate names. internal const int EVENT_FILTER_TYPE_PACKAGE_ID = unchecked((int)(0x80000010)); // Ptr points at string, can have ';' to separate names. internal const int EVENT_FILTER_TYPE_PACKAGE_APP_ID = unchecked((int)(0x80000020)); // Package Relative App Id = (PRAID); internal const int EVENT_FILTER_TYPE_PAYLOAD = unchecked((int)(0x80000100)); // Can filter on internal const int EVENT_FILTER_TYPE_EVENT_ID = unchecked((int)(0x80000200)); // Ptr points at EVENT_FILTER_EVENT_ID internal const int EVENT_FILTER_TYPE_STACKWALK = unchecked((int)(0x80001000)); // Ptr points at EVENT_FILTER_EVENT_ID </pre> <br /> 가장 관심 있는 항목이 PID와 같은 조건이 될 수 있습니다. 사실 시스템 전체에 걸쳐서 이벤트가 오기 때문에 잦은 콜백 함수는 시스템 부하를 늘리므로, 가령 Process ID를 필터링 조건으로 지정해 특정 프로세스에 해당하는 이벤트만 받게 해도 부담이 줄기 때문입니다.<br /> <br /> 단지, 6번째 인자에 해당하는 필터링 조건은 운영체제 제한이 있는 듯한데, <a target='tab' href='https://github.com/microsoft/perfview/blob/master/src/TraceEvent/TraceEventSession.cs#L2774'>Microsoft.Diagnostics.Tracing.TraceEvent</a>의 소스 코드에 윈도우 8.1부터 가능하다고 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > /// <summary> /// This return true on OS version beyond 8.1 (windows Version 6.3). It means most of the /// per-event filtering is supported. /// </summary> public static bool FilteringSupported { get { if (!s_IsEtwFilteringSupported.HasValue) { var ret = false; // For Windows Versions above windows 8, OSVersion lies and returns 6.2 (window 8) even though // the windows version is higher. We have to try harder to figure out whether we are windows 8 or something // later. Currently we look at the file version number of an OS DLL. // There is probably a better way. var winDir = Environment.GetEnvironmentVariable("WinDir"); var kernel32 = Path.Combine(winDir, @"system32\Kernel32.dll"); if (File.Exists(kernel32)) { using (var kernel32PE = new PEFile.PEFile(kernel32)) { var versionInfo = kernel32PE.GetFileVersionInfo(); if (versionInfo != null) { // versionInfo.FileVersion is now the real version number we want but it is a string, not a // number. Our tests is if version number bigger than 6.3 (as a string) or a two or more digit // major version. if (string.Compare("6.3", versionInfo.FileVersion) <= 0 || 2 <= versionInfo.FileVersion.IndexOf('.')) { ret = true; } } } } s_IsEtwFilteringSupported = ret; } return s_IsEtwFilteringSupported.Value; } } </pre> <br /> 이에 대한 구현 코드는, 사실상 <a target='tab' href='https://github.com/microsoft/perfview/blob/master/src/TraceEvent/TraceEventSession.cs#L199'>Microsoft.Diagnostics.Tracing.TraceEvent</a> 소스 코드를 그대로 베끼는 것이 더 나을 정도이므로 여기서는 생략하겠습니다. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1625&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
3316
(왼쪽의 숫자를 입력해야 합니다.)