I have an ETL file (e.g. sample.etl) to parse using the Native WinAPI. Using cpp I am able to parse and process an existing windows etl log. Now, I'm trying to use python to call into the same windows API for easier post-processing. I can open the ETL file using OpenTraceA but ProcessTrace does not call into the Callback function registered. Could python masters check what I might be doing wrong? Thanks.
import ctypes
from ctypes.wintypes import *
advapidll = "C:\\Windows\\System32\\advapi32.dll"
advapi32 = ctypes.WinDLL(advapidll)
ERROR_SUCCESS = 0
PROCESS_TRACE_MODE_EVENT_RECORD = int(hex(10000000), 16)
class _GUID(ctypes.Structure):
_fields_ = [
("Data1", ULONG),
("Data2", USHORT),
("Data3", USHORT),
("Data4", BYTE * 8),
]
class EVENT_TRACE_HEADER(ctypes.Structure):
_fields_ = [
# Define fields for EVENT_TRACE_HEADER here
]
class ETW_BUFFER_CONTEXT(ctypes.Structure):
_fields_ = [
("ProcessorIndex", USHORT),
("LoggerId", USHORT),
]
class EVENT_TRACE(ctypes.Structure):
_fields_ = [
("Header", EVENT_TRACE_HEADER),
("InstanceId", ULONG),
("ParentInstanceId", ULONG),
("ParentGuid", _GUID), # Using UUID to represent GUID
("MofData", LPVOID),
("MofLength", ULONG),
("DUMMYUNIONNAME", ULONG), # This can hold either ClientContext or BufferContext
]
class SYSTEMTIME(ctypes.Structure):
_fields_ = [
("wYear", WORD),
("wMonth", WORD),
("wDayOfWeek", WORD),
("wDay", WORD),
("wHour", WORD),
("wMinute", WORD),
("wSecond", WORD),
("wMilliseconds", WORD),
]
class TIME_ZONE_INFORMATION(ctypes.Structure):
_fields_ = [
("Bias", LONG),
("StandardName", WCHAR * 32), # WCHAR array for StandardName
("StandardDate", SYSTEMTIME),
("StandardBias", LONG),
("DaylightName", WCHAR * 32), # WCHAR array for DaylightName
("DaylightDate", SYSTEMTIME),
("DaylightBias", LONG),
]
class _TRACE_LOGFILE_HEADER(ctypes.Structure):
_fields_ = [
("BufferSize", ULONG),
("Version", ULONG), # Holds either Version or VersionDetail
("ProviderVersion", ULONG),
("NumberOfProcessors", ULONG),
("EndTime", LARGE_INTEGER),
("TimerResolution", ULONG),
("MaximumFileSize", ULONG),
("LogFileMode", ULONG),
("BuffersWritten", ULONG),
("LogInstanceGuid", _GUID), # Holds either LogInstanceGuid or DUMMYSTRUCTNAME
("LoggerName", LPWSTR), # Assuming wchar_t* for wide character string
("LogFileName", LPWSTR), # Assuming wchar_t* for wide character string
("TimeZone", TIME_ZONE_INFORMATION), # Use TIME_ZONE_INFORMATION as per your need
("BootTime", LARGE_INTEGER),
("PerfFreq", LARGE_INTEGER),
("StartTime", LARGE_INTEGER),
("ReservedFlags", ULONG),
("BuffersLost", ULONG),
]
class EVENT_DESCRIPTOR(ctypes.Structure):
_fields_ = [
("Id", USHORT), # USHORT
("Version", CHAR), # UCHAR
("Channel", CHAR), # UCHAR
("Level", CHAR), # UCHAR
("Opcode", CHAR), # UCHAR
("Task", USHORT), # USHORT
("Keyword", ULARGE_INTEGER) # ULONGLONG
]
class EVENT_HEADER(ctypes.Structure):
_fields_ = [
("Size", USHORT),
("HeaderType", USHORT),
("Flags", USHORT),
("EventProperty", USHORT),
("ThreadId", ULONG),
("ProcessId", ULONG),
("TimeStamp", LARGE_INTEGER),
("ProviderId", _GUID),
("EventDescriptor", EVENT_DESCRIPTOR),
("ProcessorTime", ULARGE_INTEGER),
("ActivityId", _GUID),
]
class EVENT_RECORD(ctypes.Structure):
_fields_ = [
("EventHeader", EVENT_HEADER),
("BufferContext", ETW_BUFFER_CONTEXT),
("ExtendedDataCount", USHORT),
("UserDataLength", USHORT),
("ExtendedData", ctypes.POINTER(ULONG)), # Assuming PEVENT_HEADER_EXTENDED_DATA_ITEM is a pointer to ULONG
("UserData", LPVOID),
("UserContext", LPVOID),
]
PEVENT_RECORD_CALLBACK = ctypes.WINFUNCTYPE(None, ctypes.POINTER(EVENT_RECORD))
class _EVENT_TRACE_LOGFILEA(ctypes.Structure):
_fields_ = [
("LogFileName", LPSTR),
("LoggerName", LPSTR),
("CurrentTime", LARGE_INTEGER),
("BuffersRead", ULONG),
("ProcessTraceMode", ULONG),
("CurrentEvent", EVENT_TRACE),
("LogfileHeader", _TRACE_LOGFILE_HEADER),
("BufferCallback", ctypes.WINFUNCTYPE(None)),
("BufferSize", ULONG),
("Filled", ULONG),
("EventsLost", ULONG),
("EventRecordCallback", PEVENT_RECORD_CALLBACK), # This can hold either EventCallback or EventRecordCallback
("IsKernelTrace", ULONG),
("Context", LPVOID),
]
@PEVENT_RECORD_CALLBACK
def handle_trace_event(event_record):
print("hi")
OpenTraceA = advapi32.OpenTraceA
OpenTraceA.argtypes = [ctypes.POINTER(_EVENT_TRACE_LOGFILEA)]
OpenTraceA.restype = HANDLE
ProcessTrace = advapi32.ProcessTrace
ProcessTrace.argtypes = [ctypes.POINTER(HANDLE), ULONG, LPVOID, LPVOID]
ProcessTrace.restype = DWORD
def main():
trace_logfile = _EVENT_TRACE_LOGFILEA()
trace_logfile.LogFileName = b"sample.etl"
trace_logfile.EventRecordCallback = handle_trace_event
trace_logfile.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD
trace_handle = OpenTraceA(ctypes.byref(trace_logfile))
if trace_handle == HANDLE(0):
print("Failed to open trace")
else:
print(f"Trace opened successfully: {trace_handle}")
status = ProcessTrace(ctypes.byref(HANDLE(trace_handle)), 1, 0, 0)
if (status != ERROR_SUCCESS):
print(f"Problem occurred during ProcessTrace Status: {status}")
if __name__ == '__main__':
main()
Listing [Python.Docs]: ctypes - A foreign function library for Python.
Notes:
I didn't carefully check all structure definitions (you're missing EVENT_TRACE_HEADER anyway), noticed that you've skipped some (inner) unions (for simplicity reasons I assume), here I can only suggest to double check you kept the largest variant (also taking memory alignment into consideration) and not the simplest
Just to be rigorous, when done with trace_handle, close it (CloseTrace)
According to (VStudion's, actually Windows Kits) evntcons.h:
// ...
#define PROCESS_TRACE_MODE_EVENT_RECORD 0x10000000
// ...
Your constant definition is not correct:
>>> correct = 0x10000000 >>> yours = int(hex(10000000), 16) >>> >>> correct, yours, correct == yours (268435456, 10000000, False)
This alone is a good enough reason for your actual behavior (but there might be other errors as well). In other words, simply do:
PROCESS_TRACE_MODE_EVENT_RECORD = 0x10000000