diff --git a/Cargo.toml b/Cargo.toml index 3221b9a..cc4bd1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,10 @@ repository = "https://github.com/n4r1b/ferrisetw" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +# Enable the conversion of timestamps to time::OffsetDateTime +time_rs = ["time"] + [dependencies] windows = { version = "0.39", features = [ "Win32_Foundation", @@ -30,6 +34,7 @@ num-derive = "0.3" bitflags = "1.3.2" widestring = "1.0" zerocopy = "0.6" +time = { version = "0.3", features = ["large-dates"], optional = true } # thiserror = "~1.0" # anyhow = "~1.0" diff --git a/src/native/etw_types.rs b/src/native/etw_types.rs index dbd215f..5375fec 100644 --- a/src/native/etw_types.rs +++ b/src/native/etw_types.rs @@ -77,58 +77,35 @@ pub(crate) enum ControlValues { Update = 2, } -#[allow(dead_code)] -enum LoggingMode { - None, - Sequential, - Circular, - Append, - NewFile, - NonStoppable, - Secure, - RealTime, - Buffering, - SystemLogger, - DelayOpenFile, - PrivateLogger, - NoPerProcBuffering, -} - -impl From for u32 { - fn from(val: LoggingMode) -> Self { - match val { - // Not all but pretty much... - LoggingMode::None => Etw::EVENT_TRACE_FILE_MODE_NONE, - LoggingMode::Sequential => Etw::EVENT_TRACE_FILE_MODE_SEQUENTIAL, - LoggingMode::Circular => Etw::EVENT_TRACE_FILE_MODE_CIRCULAR, - LoggingMode::Append => Etw::EVENT_TRACE_FILE_MODE_APPEND, - LoggingMode::NewFile => Etw::EVENT_TRACE_FILE_MODE_NEWFILE, - LoggingMode::NonStoppable => Etw::EVENT_TRACE_NONSTOPPABLE_MODE, - LoggingMode::Secure => Etw::EVENT_TRACE_SECURE_MODE, - LoggingMode::RealTime => Etw::EVENT_TRACE_REAL_TIME_MODE, - LoggingMode::DelayOpenFile => Etw::EVENT_TRACE_DELAY_OPEN_FILE_MODE, - LoggingMode::Buffering => Etw::EVENT_TRACE_BUFFERING_MODE, - LoggingMode::PrivateLogger => Etw::EVENT_TRACE_PRIVATE_LOGGER_MODE, - LoggingMode::SystemLogger => Etw::EVENT_TRACE_SYSTEM_LOGGER_MODE, - LoggingMode::NoPerProcBuffering => Etw::EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING, - } - } -} - -#[allow(dead_code)] -enum ProcessTraceMode { - RealTime, - EventRecord, - RawTimestamp, -} - -impl From for u32 { - fn from(val: ProcessTraceMode) -> Self { - match val { - ProcessTraceMode::RealTime => Etw::PROCESS_TRACE_MODE_EVENT_RECORD, - ProcessTraceMode::EventRecord => Etw::PROCESS_TRACE_MODE_REAL_TIME, - ProcessTraceMode::RawTimestamp => Etw::PROCESS_TRACE_MODE_RAW_TIMESTAMP, - } +bitflags! { + /// Logging Mode constants + /// + /// See + pub struct LoggingMode: u32 { + const EVENT_TRACE_FILE_MODE_NONE = Etw::EVENT_TRACE_FILE_MODE_NONE; + const EVENT_TRACE_FILE_MODE_SEQUENTIAL = Etw::EVENT_TRACE_FILE_MODE_SEQUENTIAL; + const EVENT_TRACE_FILE_MODE_CIRCULAR = Etw::EVENT_TRACE_FILE_MODE_CIRCULAR; + const EVENT_TRACE_FILE_MODE_APPEND = Etw::EVENT_TRACE_FILE_MODE_APPEND; + const EVENT_TRACE_FILE_MODE_NEWFILE = Etw::EVENT_TRACE_FILE_MODE_NEWFILE; + const EVENT_TRACE_FILE_MODE_PREALLOCATE = Etw::EVENT_TRACE_FILE_MODE_PREALLOCATE; + const EVENT_TRACE_NONSTOPPABLE_MODE = Etw::EVENT_TRACE_NONSTOPPABLE_MODE; + const EVENT_TRACE_SECURE_MODE = Etw::EVENT_TRACE_SECURE_MODE; + const EVENT_TRACE_REAL_TIME_MODE = Etw::EVENT_TRACE_REAL_TIME_MODE; + const EVENT_TRACE_DELAY_OPEN_FILE_MODE = Etw::EVENT_TRACE_DELAY_OPEN_FILE_MODE; + const EVENT_TRACE_BUFFERING_MODE = Etw::EVENT_TRACE_BUFFERING_MODE; + const EVENT_TRACE_PRIVATE_LOGGER_MODE = Etw::EVENT_TRACE_PRIVATE_LOGGER_MODE; + const EVENT_TRACE_USE_KBYTES_FOR_SIZE = Etw::EVENT_TRACE_USE_KBYTES_FOR_SIZE; + const EVENT_TRACE_USE_GLOBAL_SEQUENCE = Etw::EVENT_TRACE_USE_GLOBAL_SEQUENCE; + const EVENT_TRACE_USE_LOCAL_SEQUENCE = Etw::EVENT_TRACE_USE_LOCAL_SEQUENCE; + const EVENT_TRACE_PRIVATE_IN_PROC = Etw::EVENT_TRACE_PRIVATE_IN_PROC; + const EVENT_TRACE_MODE_RESERVED = Etw::EVENT_TRACE_MODE_RESERVED; + const EVENT_TRACE_STOP_ON_HYBRID_SHUTDOWN = Etw::EVENT_TRACE_STOP_ON_HYBRID_SHUTDOWN; + const EVENT_TRACE_PERSIST_ON_HYBRID_SHUTDOWN = Etw::EVENT_TRACE_PERSIST_ON_HYBRID_SHUTDOWN; + const EVENT_TRACE_USE_PAGED_MEMORY = Etw::EVENT_TRACE_USE_PAGED_MEMORY; + const EVENT_TRACE_SYSTEM_LOGGER_MODE = Etw::EVENT_TRACE_SYSTEM_LOGGER_MODE; + const EVENT_TRACE_INDEPENDENT_SESSION_MODE = Etw::EVENT_TRACE_INDEPENDENT_SESSION_MODE; + const EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING = Etw::EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING; + const EVENT_TRACE_ADDTO_TRIAGE_DUMP = Etw::EVENT_TRACE_ADDTO_TRIAGE_DUMP; } } @@ -178,13 +155,13 @@ impl EventTraceProperties { etw_trace_properties.BufferSize = trace_properties.buffer_size; etw_trace_properties.MinimumBuffers = trace_properties.min_buffer; etw_trace_properties.MaximumBuffers = trace_properties.max_buffer; - etw_trace_properties.FlushTimer = trace_properties.flush_timer; + etw_trace_properties.FlushTimer = trace_properties.flush_timer.as_secs().clamp(1, u32::MAX as u64) as u32; // See https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties - if trace_properties.log_file_mode != 0 { - etw_trace_properties.LogFileMode = trace_properties.log_file_mode; + if trace_properties.log_file_mode.is_empty() == false { + etw_trace_properties.LogFileMode = trace_properties.log_file_mode.bits(); } else { etw_trace_properties.LogFileMode = - u32::from(LoggingMode::RealTime) | u32::from(LoggingMode::NoPerProcBuffering); + (LoggingMode::EVENT_TRACE_REAL_TIME_MODE | LoggingMode::EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING).bits() } etw_trace_properties.LogFileMode |= T::augmented_file_mode(); @@ -252,7 +229,8 @@ impl<'callbackdata> EventTraceLogfile<'callbackdata> { native.LoggerName = PWSTR(wide_logger_name.as_mut_ptr()); native.Anonymous1.ProcessTraceMode = - u32::from(ProcessTraceMode::RealTime) | u32::from(ProcessTraceMode::EventRecord); + Etw::PROCESS_TRACE_MODE_REAL_TIME | Etw::PROCESS_TRACE_MODE_EVENT_RECORD; + // In case you really want to use PROCESS_TRACE_MODE_RAW_TIMESTAMP, please review EventRecord::timestamp(), which could not be valid anymore native.Anonymous2.EventRecordCallback = Some(callback); diff --git a/src/native/etw_types/event_record.rs b/src/native/etw_types/event_record.rs index 42f8cbe..5647d8f 100644 --- a/src/native/etw_types/event_record.rs +++ b/src/native/etw_types/event_record.rs @@ -91,10 +91,32 @@ impl EventRecord { /// > contains the `PROCESS_TRACE_MODE_RAW_TIMESTAMP` flag, in which case the resolution depends /// > on the value of the `Wnode.ClientContext` member of `EVENT_TRACE_PROPERTIES` at the time /// > the controller created the session. - pub fn timestamp(&self) -> i64 { + /// + /// Note: the `time_rs` Cargo feature enables to convert this into strongly-typed values + pub fn raw_timestamp(&self) -> i64 { self.0.EventHeader.TimeStamp } + /// The `TimeStamp` field from the wrapped `EVENT_RECORD`, as a strongly-typed `time::OffsetDateTime` + #[cfg(feature = "time_rs")] + pub fn timestamp(&self) -> time::OffsetDateTime { + // "system time" means the count of hundreds of nanoseconds since midnight, January 1, 1601 + let system_time = self.0.EventHeader.TimeStamp; + + const SECONDS_BETWEEN_1601_AND_1970: i128 = 11_644_473_600; + const HUNDREDS_OF_NANOS_IN_SECOND: i128 = 10_000_000; + const HUNDREDS_OF_NANOSECONDS_BETWEEN_1601_AND_1970: i128 = + SECONDS_BETWEEN_1601_AND_1970 * HUNDREDS_OF_NANOS_IN_SECOND; + + let unix_as_hundreds_of_nano_seconds = (system_time as i128) - HUNDREDS_OF_NANOSECONDS_BETWEEN_1601_AND_1970; + let unix_as_nano_seconds = unix_as_hundreds_of_nano_seconds * 100; + + // Can't panic. + // A filetime can go from 1601 to 30828. + // OffsetDateTime (with the 'large-dates' feature) can represent any time from year -999_999 to +999_999. Meanwhile, . + time::OffsetDateTime::from_unix_timestamp_nanos(unix_as_nano_seconds).unwrap() + } + pub(crate) fn user_buffer(&self) -> &[u8] { unsafe { std::slice::from_raw_parts( diff --git a/src/trace.rs b/src/trace.rs index bfb0ac9..00a2d07 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -5,10 +5,11 @@ use std::ffi::OsString; use std::marker::PhantomData; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; +use std::time::Duration; use self::private::PrivateTraceTrait; -use crate::native::etw_types::{EventRecord, EventTraceProperties}; +use crate::native::etw_types::{EventRecord, EventTraceProperties, LoggingMode}; use crate::native::{evntrace, version_helper}; use crate::native::evntrace::{ControlHandle, TraceHandle, start_trace, open_trace, process_trace, enable_provider, control_trace, close_trace}; use crate::provider::Provider; @@ -52,7 +53,7 @@ type TraceResult = Result; /// These are some configuration settings that will be included in an [`EVENT_TRACE_PROPERTIES`](https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties) /// /// [More info](https://docs.microsoft.com/en-us/message-analyzer/specifying-advanced-etw-session-configuration-settings#configuring-the-etw-session) -#[derive(Debug, Copy, Clone, Default)] +#[derive(Debug, Copy, Clone)] pub struct TraceProperties { /// Represents the ETW Session in KB pub buffer_size: u32, @@ -60,10 +61,25 @@ pub struct TraceProperties { pub min_buffer: u32, /// Represents the ETW Session maximum number of buffers in the buffer pool pub max_buffer: u32, - /// Represents the ETW Session flush interval in seconds - pub flush_timer: u32, + /// Represents the ETW Session flush interval. + /// + /// This duration will be rounded to the closest second (and 0 will be translated as 1 second) + pub flush_timer: Duration, /// Represents the ETW Session [Logging Mode](https://docs.microsoft.com/en-us/windows/win32/etw/logging-mode-constants) - pub log_file_mode: u32, + pub log_file_mode: LoggingMode, +} + +impl Default for TraceProperties { + fn default() -> Self { + // Sane defaults, inspired by https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties + TraceProperties { + buffer_size: 32, + min_buffer: 0, + max_buffer: 0, + flush_timer: Duration::from_secs(1), + log_file_mode: LoggingMode::EVENT_TRACE_REAL_TIME_MODE | LoggingMode::EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING, + } + } } /// Data used by callbacks when the trace is running