C# - ETW 관련 Win32 API 사용 예제 코드 (2) NT Kernel Logger
지난 글에 이어,
C# - ETW 관련 Win32 API 사용 예제 코드 (1)
; https://www.sysnet.pe.kr/2/0/12292
이번에는 ETW Consumer를 Win32 API를 이용해 작성해 보겠습니다.
우선 세션을 먼저 생성해야 하는데 이를 위해 StartTrace API를 사용할 수 있습니다.
string sessionName = NativeMethods.KERNEL_LOGGER_NAME; // KERNEL_LOGGER_NAME == "NT Kernel Logger"
EventTraceProperties prop = new EventTraceProperties(true, sessionName, NativeMethods.SystemTraceControlGuid); // SystemTraceControlGuid == 9e814aad-3204-11d2-9a82-006008a86939
result = NativeMethods.StartTrace(out sessionHandle, sessionName, ref prop);
위의 코드는 (
xperf -providers 출력 결과의) ETW Provider 중 "Windows Kernel Trace" (9e814aad-3204-11d2-9a82-006008a86939)를 사용하는 코드로 예를 든 것입니다. 주의할 것은, SystemTraceControlGuid의 경우 반드시 세션 이름을 "NT Kernel Logger"로 해야 한다는 건데, 이런 사실은 다음의 글을 통해 알게 되었습니다.
THE WORST API EVER MADE
; https://caseymuratori.com/blog_0025
Configuring and Starting the NT Kernel Logger Session
; https://learn.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-the-nt-kernel-logger-session
(제목에서 볼 수 있는 것처럼, 저 개인적으로도 ETW 관련 API의 이름이나 사용법이 체계적이지 않음을 100% 공감합니다. ^^;)
저렇게 세션을 생성했으면, (이전에 StartTrace라는 이름의 API를 호출했음에도) "Trace"를 "시작"하기 위해 OpenTrace API를 호출해야 합니다.
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);
static private void EventRecordCallback([In] ref EventRecord eventRecord)
{
Console.WriteLine(DateTime.Now + ": " + eventRecord.EventHeader.ProcessId);
}
위의 코드는 이벤트가 발생했을 시 콜백을 실행하라고 등록하는데 원한다면 파일로도 지정할 수 있습니다. 하지만, 이렇게 등록했다고 해서 trace로 인한 콜백이 호출되는 것은 아닙니다. 콜백이 호출되려면 이후
ProcessTrace API를 호출해야 하는데, 이 함수는 호출 스레드를 블록시키기 때문에 보통 새로운 스레드를 생성해 호출하는 식으로 구현합니다.
new Thread((ThreadStart)(
() =>
{
ProcessTrace(traceHandle);
})).Start();
Console.WriteLine("Press ENTER key to exit...");
Console.ReadLine();
public static int ProcessTrace(ulong traceHandle)
{
ulong[] handles = { traceHandle };
int result = NativeMethods.ProcessTrace(handles, (uint)handles.Length, IntPtr.Zero, IntPtr.Zero);
return result;
}
마지막으로, ProcessTrace의 blocking 상태를 해제하려면 CloseTrace를 호출하고,
NativeMethods.CloseTrace(traceHandle);
세션까지 완전히 종료하려면 ControlTrace 호출로 마무리를 합니다.
// https://www.sysnet.pe.kr/2/0/12292#control_trace
EtwInterop.CloseActiveSession(sessionName);
"Windows Kernel Trace"의 경우, 기본적인 사용법은 위와 같은 순서로 구현할 수 있지만 한 가지 설명해야 할 것이 더 있습니다. 이에 대해서는 EVENT_TRACE_PROPERTIES 구조체 문서를 확인해야 하는데,
EVENT_TRACE_PROPERTIES structure
; https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties
위의 글에 보면, "system logger" 유형인 경우 "EnableFlags"에 설정할 수 있는 값들을 설명하고 있습니다.
//
// Enable flags for Kernel Events
//
#define EVENT_TRACE_FLAG_PROCESS 0x00000001 // process start & end
#define EVENT_TRACE_FLAG_THREAD 0x00000002 // thread start & end
#define EVENT_TRACE_FLAG_IMAGE_LOAD 0x00000004 // image load
#define EVENT_TRACE_FLAG_DISK_IO 0x00000100 // physical disk IO
#define EVENT_TRACE_FLAG_DISK_FILE_IO 0x00000200 // requires disk IO
#define EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS 0x00001000 // all page faults
#define EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS 0x00002000 // hard faults only
#define EVENT_TRACE_FLAG_NETWORK_TCPIP 0x00010000 // tcpip send & receive
#define EVENT_TRACE_FLAG_REGISTRY 0x00020000 // registry calls
#define EVENT_TRACE_FLAG_DBGPRINT 0x00040000 // DbgPrint(ex) Calls
//
// Enable flags for Kernel Events on Vista and above
//
#define EVENT_TRACE_FLAG_PROCESS_COUNTERS 0x00000008 // process perf counters
#define EVENT_TRACE_FLAG_CSWITCH 0x00000010 // context switches
#define EVENT_TRACE_FLAG_DPC 0x00000020 // deferred procedure calls
#define EVENT_TRACE_FLAG_INTERRUPT 0x00000040 // interrupts
#define EVENT_TRACE_FLAG_SYSTEMCALL 0x00000080 // system calls
#define EVENT_TRACE_FLAG_DISK_IO_INIT 0x00000400 // physical disk IO initiation
#define EVENT_TRACE_FLAG_ALPC 0x00100000 // ALPC traces
#define EVENT_TRACE_FLAG_SPLIT_IO 0x00200000 // split io traces (VolumeManager)
#define EVENT_TRACE_FLAG_DRIVER 0x00800000 // driver delays
#define EVENT_TRACE_FLAG_PROFILE 0x01000000 // sample based profiling
#define EVENT_TRACE_FLAG_FILE_IO 0x02000000 // file IO
#define EVENT_TRACE_FLAG_FILE_IO_INIT 0x04000000 // file IO initiation
//
// Enable flags for Kernel Events on Win7 and above
//
#define EVENT_TRACE_FLAG_DISPATCHER 0x00000800 // scheduler (ReadyThread)
#define EVENT_TRACE_FLAG_VIRTUAL_ALLOC 0x00004000 // VM operations
//
// Enable flags for Kernel Events on Win8 and above
//
#define EVENT_TRACE_FLAG_VAMAP 0x00008000 // map/unmap (excluding images)
#define EVENT_TRACE_FLAG_NO_SYSCONFIG 0x10000000 // Do not do sys config rundown
//
// Enable flags for Kernel Events on Threshold and above
//
#define EVENT_TRACE_FLAG_JOB 0x00080000 // job start & end
#define EVENT_TRACE_FLAG_DEBUG_EVENTS 0x00400000 // debugger events (break/continue/...)
예를 들어, 이 중에서 EVENT_TRACE_FLAG_PROCESS 플래그는 프로세스의 생성/종료에 대한 이벤트를 받을 수 있게 합니다. 따라서, 다음과 같이 설정해 주면,
EventTraceProperties prop = new EventTraceProperties(true, sessionName, NativeMethods.SystemTraceControlGuid);
prop.SetFlags(NativeMethods.EVENT_TRACE_FLAG_PROCESS);
result = NativeMethods.StartTrace(out sessionHandle, sessionName, ref prop);
이후 (EXE) 프로세스 생성/종료마다 이벤트 콜백 함수가 실행됩니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
참고로 아래의 문서에도 나오지만,
Controlling Event Tracing Sessions
; https://learn.microsoft.com/en-us/windows/win32/etw/controlling-event-tracing-sessions
ETW 세션을 다루는 방식이 여러 가지이므로 자신이 사용하려는 Provider에 따라 저 문서를 보고 어느 유형에 속하는지 선택해 코딩을 해야 합니다.
이 글에서 EVENT_TRACE_FLAG_PROCESS를 설정한 "Windows Kernel Trace" 동작은 지난 글에서 설명한,
C# - 특정 EXE 프로세스를 종료시킨 EXE를 찾아내는 방법
; https://www.sysnet.pe.kr/2/0/11172
"Microsoft-Windows-Kernel-Audit-API-Calls" ETW provider를 통해서도 유사하게 구현할 수 있습니다. 이에 대해서는 다음번 글에서 살펴보겠습니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]