성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
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 사용 예제 코드 (1)</h1> <p> 예전에 ETW의 Provider 측 코드를 작성하는 방법을 소개했고,<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)를 C#에서 사용하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1804'>https://www.sysnet.pe.kr/2/0/1804</a> </pre> <br /> 닷넷 프레임워크 관련한 ETW 이벤트 활용과 함께 Consumer 측에 대해서도 다뤘으니,<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 /> ETW 관련한 명령어들을 Win32 API 수준으로 내려 살펴보는 시간을 갖겠습니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> <a name='etw_list'></a> 우선, ETW provider를 나열하는 "xperf -providers" 명령어를 코드로 구현해 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ETW provider 목록 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/10909'>https://www.sysnet.pe.kr/2/0/10909</a> </pre> <a name='trace_event_cs'></a> <br /> 정식 BCL에 이 기능은 없지만 "<a target='tab' href='https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/'>Microsoft.Diagnostics.Tracing.TraceEvent</a>" 패키지의 소스 코드를 보면 Win23 API와 Interop하는 소스 코드를 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // perfview/src/TraceEvent/ // ; <a target='tab' href='https://github.com/microsoft/perfview/blob/master/src/TraceEvent/TraceEventSession.cs#L1533'>https://github.com/microsoft/perfview/blob/master/src/TraceEvent/TraceEventSession.cs#L1533</a> static unsafe void PrintProviders() { var providersByName = new SortedDictionary<string, Guid>(StringComparer.OrdinalIgnoreCase); int buffSize = 0; <span style='color: blue; font-weight: bold'>var hr = NativeMethods.TdhEnumerateProviders(null, ref buffSize);</span> Debug.Assert(hr == 122); // <a target='tab' href='https://www.sysnet.pe.kr/2/0/13206#17132'>ERROR_INSUFFICIENT_BUFFER</a> var buffer = stackalloc byte[buffSize]; var providersDesc = (PROVIDER_ENUMERATION_INFO*)buffer; <span style='color: blue; font-weight: bold'>hr = NativeMethods.<a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/tdh/nf-tdh-tdhenumerateproviders'>TdhEnumerateProviders</a>(providersDesc, ref buffSize);</span> if (hr != 0) { Trace.WriteLine("TdhEnumerateProviders failed HR = " + hr); providersDesc->NumberOfProviders = 0; } var providers = (TRACE_PROVIDER_INFO*)&providersDesc[1]; for (int i = 0; i < providersDesc->NumberOfProviders; i++) { var name = new string((char*)&buffer[providers[i].ProviderNameOffset]); providersByName[name] = providers[i].ProviderGuid; } foreach (var item in providersByName) { Console.WriteLine(item.Key + ": " + item.Value); } } internal struct PROVIDER_ENUMERATION_INFO { public int NumberOfProviders; public int Padding; } unsafe class NativeMethods { [DllImport("tdh.dll")] internal static extern int TdhEnumerateProviders(PROVIDER_ENUMERATION_INFO* pBuffer, ref int pBufferSize); } </pre> <br /> 그러니까, TdhEnumerateProviders Win32 API 하나로 ETW provider 목록을 모두 열람할 수 있습니다. (참고로, ETW Provider 목록은 레지스트리에 등록되므로 "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Publishers\"의 하위 키를 열람해도 됩니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> "logman query -ets" 명령을 실행하면 현재 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-stoptracew'>StartTrace</a>로 실행해 둔 ETW 세션이 있는지 확인할 수 있습니다. 제 PC의 경우 최초 로그인 시 다음과 같은 세션들이 있었습니다.<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\System32> <span style='color: blue; font-weight: bold'>logman query -ets</span> Data Collector Set Type Status ------------------------------------------------------------------------------- Circular Kernel Context Logger Trace Running AppModel Trace Running DiagLog Trace Running Diagtrack-Listener Trace Running EventLog-Application Trace Running EventLog-Microsoft-Windows-Sysmon-Operational Trace Running EventLog-RemoteDesktopServices-RemoteFX-SessionLicensing-Debug Trace Running EventLog-System Trace Running LwtNetLog Trace Running Microsoft-Windows-Rdp-Graphics-RdpIdd-Trace Trace Running NetCore Trace Running NtfsLog Trace Running RadioMgr Trace Running UBPM Trace Running WdiContextLog Trace Running WiFiSession Trace Running SgrmEtwSession Trace Running UserNotPresentTraceSession Trace Running CldFltLog Trace Running Admin_PS_Provider Trace Running NetCfgTrace Trace Running WindowsUpdate_trace_log Trace Running MpWppTracing-20200819-152610-00000003-ffffffff Trace Running ScreenOnPowerStudyTraceSession Trace Running SHS-08192020-152637-7-7f Trace Running Cloud Files Diagnostic Event Listener Trace Running 8696EAC4-1288-4288-A4EE-49EE431B0AD9 Trace Running Microsoft-VisualStudio-Telemetry-PerfWatson2-19040 Trace Running Microsoft-VisualStudio-Telemetry-PerfWatson2-18384 Trace Running The command completed successfully. </pre> <br /> 이 목록은, Win32 API로는 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-queryalltracesw'>QueryAllTraces</a>로 구할 수 있는데 Interop 과정이 좀 복잡한 것을 제외하고는 API 문서에 나온 기능을 C#으로도 다음과 같이 옮길 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static List<EventTraceProperitesManaged> QueryAllSessions() { List<EventTraceProperitesManaged> list = new List<EventTraceProperitesManaged>(); unsafe { IntPtr ptrBuf = Marshal.AllocHGlobal(EventTraceProperties.MaxSessionBufferSize); try { IntPtr[] arrayBuf = new IntPtr[EventTraceProperties.MAX_SESSIONS]; for (int i = 0; i < EventTraceProperties.MAX_SESSIONS; i++) { EventTraceProperties* pProp = (EventTraceProperties*)(((byte*)ptrBuf.ToPointer()) + EventTraceProperties.RecordSize * i); IntPtr elemPtr = new IntPtr(pProp); arrayBuf[i] = elemPtr; pProp->Initialize(); } GCHandle gcHandle = GCHandle.Alloc(arrayBuf, GCHandleType.Pinned); try { IntPtr elem0 = gcHandle.AddrOfPinnedObject(); uint activeSessionCount = 0; int status = <span style='color: blue; font-weight: bold'>NativeMethods.QueryAllTraces</span>(elem0, EventTraceProperties.MAX_SESSIONS, ref activeSessionCount); if (status == 0) { for (int i = 0; i < activeSessionCount; i++) { EventTraceProperties* pProp = (EventTraceProperties*)(((byte*)ptrBuf.ToPointer()) + EventTraceProperties.RecordSize * i); list.Add(pProp->ReadAsManaged()); } } else { Console.WriteLine(status); } } finally { gcHandle.Free(); } } finally { Marshal.FreeHGlobal(ptrBuf); } } return list; } </pre> <br /> 대개의 경우, ETW Consumer를 만들 때 저런 식으로 열람할 일은 없을 것입니다. 대신 자신이 만드는 세션 이름으로 이미 등록된 적이 있는지 알아야 할 필요는 있는데 이를 위해 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-querytracew'>QueryTrace</a> Win32 API를 사용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // EventTraceWatcher // ; <a target='tab' href='https://www.nuget.org/packages/EventTraceWatcher/'>https://www.nuget.org/packages/EventTraceWatcher/</a> // ; ./src/EventTraceWatcher.cs LoadExistingEventTraceProperties public static bool IsSessionActive(string sessionName) { EventTraceProperties prop = new EventTraceProperties(true); int status = <span style='color: blue; font-weight: bold'>NativeMethods.QueryTrace</span>(0, sessionName, ref prop); if (status == 0) { return true; } else if (status == EventTraceProperties.ERROR_WMI_INSTANCE_NOT_FOUND) { // The instance name passed was not recognized as valid by a WMI data provider. return false; } throw new System.ComponentModel.Win32Exception(status); } /* Console.WriteLine("Active == " + IsSessionActive("EventLog-Application")); // Active == True */ </pre> <br /> <hr style='width: 50%' /><br /> <a name='etw_src'></a> <br /> <a target='tab' href='https://www.sysnet.pe.kr/2/0/1804'>예전에 썼던 글</a>에서 EventTraceWatcher를 소개한 적이 있는데요, 해당 클래스를 사용해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // How to consume ETW events from C# // ; <a target='tab' href='https://learn.microsoft.com/en-us/archive/blogs/danielvl/how-to-consume-etw-events-from-c'>https://learn.microsoft.com/en-us/archive/blogs/danielvl/how-to-consume-etw-events-from-c</a> using System; using Microsoft.Samples.Eventing; class Program { static void Main() { try { new Program().Run(); } catch (Exception ex) { Console.Error.WriteLine(ex); } } private void Run() { Guid RewriteProviderId = new Guid("0469abfa-1bb2-466a-b645-e3e15a02f38b"); <span style='color: blue; font-weight: bold'>using (EventTraceWatcher watcher = new EventTraceWatcher("Rewrite", RewriteProviderId))</span> { watcher.EventArrived += delegate (object sender, EventArrivedEventArgs e) { if (e.Error != null) { Console.Error.WriteLine(e.Error); Environment.Exit(-1); } Console.WriteLine("Event Name: " + e.EventId); foreach (var p in e.Properties) { Console.WriteLine("\t" + p.Key + " -- " + p.Value); } Console.WriteLine(); }; <span style='color: blue; font-weight: bold'>watcher.Start();</span> Console.WriteLine("Press <Enter> to exit"); Console.ReadLine(); <span style='color: blue; font-weight: bold'>watcher.Stop();</span> } } } </pre> <br /> 프로세스가 정상 종료하면서 watcher.Stop() 코드가 실행되었음에도 여전히 "Rewrite"라는 이름의 세션이 살아 있는 것을 "logman query -ets" 명령어로 확인할 수 있습니다. 사실 다른 커널 리소스 핸들과 달리, ETW 핸들은 (EXE) 프로세스의 종료에 상관이 없어야 하는 것이 맞습니다. 하지만 위의 경우에는 프로세스가 종료하면 ETW 세션도 닫히기를 원하는 상황인데, 게다가 소스 코드로 디버깅해 보면 분명히 using 문으로 인한 finally에 의해 Dispose가 실행되었고, 그에 따라 StopTrace와 CloseTrace가 실행되었지만 여전히 ETW 세션이 안 닫힙니다. <br /> <br /> 이 경우 StopTrace의 반환값은, 0x1069(0n4201) 값으로 C++ 헤더 파일에서 다음과 같이 설명하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // // MessageId: ERROR_WMI_INSTANCE_NOT_FOUND // // MessageText: // // The instance name passed was not recognized as valid by a WMI data provider. // #define ERROR_WMI_INSTANCE_NOT_FOUND 4201L </pre> <br /> 어쨌든, 이렇게 (의도치 않게) 살아 있는 세션을 닫으려면 logman을 이용해 다음과 같은 식의 명령어를 실행해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > logman stop <SessionName> -ets 예) logman stop Rewrite -ets </pre> <br /> <a name='control_trace'></a> 코드로 이것을 구현하려면 ControlTrace API를 사용하는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ControlTraceW function // ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-controltracew'>https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-controltracew</a> public const uint EVENT_TRACE_CONTROL_QUERY = 0; public const uint EVENT_TRACE_CONTROL_STOP = 1; public const uint EVENT_TRACE_CONTROL_UPDATE = 2; public static void CloseActiveSession(string sessionName) { if (IsSessionActive(sessionName, out _) == true) { EventTraceProperties prop = new EventTraceProperties(); prop.Initialize(); <span style='color: blue; font-weight: bold'>NativeMethods.ControlTrace</span>(0, sessionName, ref prop, EVENT_TRACE_CONTROL_STOP); } } public static int CloseActiveSession(ulong sessionHandle) { EventTraceProperties prop = new EventTraceProperties(); prop.Initialize(); return NativeMethods.ControlTrace(sessionHandle, null, ref prop, EVENT_TRACE_CONTROL_STOP); } </pre> <br /> 문서에 보면 ControlTrace가 StopTrace 함수를 대체한다고 쓰여있기는 합니다. 하지만 그렇다고 해도 StopTrace 호출이 세션을 닫지 못한다는 것은 이해가 안 됩니다. 참고로, 보다 더 최근에 나왔던 "<a target='tab' href='https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/'>Microsoft.Diagnostics.Tracing.TraceEvent</a>" 라이브러리에서는 StopTrace는 아예 호출도 안 하고 ControlTrace로 세션을 닫고 있습니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1621&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 규칙을 알 수 없지만, 가끔씩 logman query에 오류가 발생하는 경우가 있습니다.<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\System32> <span style='color: blue; font-weight: bold'>logman query -ets</span> Data Collector Set Type Status ------------------------------------------------------------------------------- Error: The GUID passed was not recognized as valid by a WMI data provider. </pre> <br /> 그래서 그런지, 저렇게 열거하는 QueryAllTraces API도 가끔씩 호출이 실패하는 경우가 있는데 ^^; 원인을 모르겠습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> ETW 관련 오류 코드들은 "C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared\winerror.h" 헤더 파일에서 찾을 수 있으며 주로 다음의 오류코드들을 볼 수 있을 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int result = NativeMethods.CloseTrace(this.traceHandle); // // MessageId: ERROR_CTX_CLOSE_PENDING // // MessageText: // // A close operation is pending on the session. // #define ERROR_CTX_CLOSE_PENDING 7007L </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int result = NativeMethods.StopTrace(this.sessionHandle, this.loggerName, out properties /*as statistics*/); // // MessageId: ERROR_BAD_LENGTH // // MessageText: // // The program issued a command but the command length is incorrect. // #define ERROR_BAD_LENGTH 24L // // MessageId: ERROR_INVALID_HANDLE // // MessageText: // // The handle is invalid. // #define ERROR_INVALID_HANDLE 6L // // MessageId: ERROR_INVALID_PARAMETER // // MessageText: // // The parameter is incorrect. // #define ERROR_INVALID_PARAMETER 87L // dderror </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1519
(왼쪽의 숫자를 입력해야 합니다.)