Skip to content

Commit

Permalink
Trace now has a Builder
Browse files Browse the repository at this point in the history
This largely refactors trace.rs and evntrace.rs
* evntrace now solely contains safe wrappers over Windows API functions, without any internal state (struct NativeEtw has been removed)
  that's the duty of trace.rs to handle these API correctly and in the right order.
* Traces instances are now created with a Builder pattern, to clearly mark which fields may be mutable and which will stay constant.
  This will make it very easy to fix races in issue n4r1b#45

Also, as minor improvements:
* the builder now enforces the trace name is truncated to TRACE_NAME_MAX_CHARS, so that both EVENT_TRACE_LOGFILEW and EVENT_TRACE_PROPERTIES have consistently truncated logger names
* TraceData is renamed CallbackData.
  That's mainly a matter of taste, even though it makes its intent clearer, and thus makes it easier to review the `unsafe` blocks
* errors from evntrace are now better forwarded to callers
* checks for invalid handles from the Windows API has been made more explicit
* the public API for traces (and trace builder) is now simplified, and hides some of the "really weird" (to say the least) design choices of ETW.
  Distinction between open/start/process is now clearer to the user
  Also, the `process` now exists in different flavours, that do not all hide the thread spawning. This offers more control to the end user.
* Traces can explictly be closed, and are closed if still open on Drop (maybe that was the case in ferrisetw 0.1, I'm not sure)
* This removes the distinction between TraceTrait and TraceBaseTrait

Sorry, I did not manage to split this large commit into smaller chunks.
It's probably easier to read only the result of it rather than the diffs, which do not make much sense since most of evntrace.rs and trace.rs are now diffs.
  • Loading branch information
daladim committed Nov 7, 2022
1 parent 3beb6f8 commit e446b3f
Show file tree
Hide file tree
Showing 12 changed files with 536 additions and 508 deletions.
9 changes: 4 additions & 5 deletions examples/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use ferrisetw::schema_locator::SchemaLocator;
use ferrisetw::native::etw_types::EventRecord;
use ferrisetw::trace::UserTrace;
use ferrisetw::parser::TryParse;
use ferrisetw::trace::TraceBaseTrait;
use ferrisetw::schema::Schema;


Expand Down Expand Up @@ -74,16 +73,16 @@ fn main() {
.trace_flags(TraceFlags::EVENT_ENABLE_PROPERTY_PROCESS_START_KEY)
.build();

let mut trace = UserTrace::new()
let trace = UserTrace::new()
.enable(dns_provider)
.start()
.start_and_process()
.unwrap();

println!("ID Status Options Ty Name Results");

std::thread::sleep(Duration::new(120, 0));
trace.stop();
std::thread::sleep(Duration::new(20, 0));

trace.stop().unwrap(); // This is not required, as it will automatically be stopped on Drop
println!("Done: {:?} events", N_EVENTS);
}

Expand Down
6 changes: 3 additions & 3 deletions examples/kernel_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ fn main() {
.add_callback(image_load_callback)
.build();

let mut trace = KernelTrace::new()
let kernel_trace = KernelTrace::new()
.named(String::from("MyKernelProvider"))
.enable(provider)
.start()
.start_and_process()
.unwrap();

std::thread::sleep(Duration::new(20, 0));
trace.stop();
kernel_trace.stop().unwrap(); // This is not required, as it will automatically be stopped on Drop
}
7 changes: 4 additions & 3 deletions examples/multiple_providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ fn main() {
.add_callback(registry_callback)
.build();

let mut trace = UserTrace::new()
let user_trace = UserTrace::new()
.enable(process_provider)
.enable(tcpip_provider)
.start()
.start_and_process()
.unwrap();

std::thread::sleep(Duration::new(10, 0));
trace.stop();

user_trace.stop().unwrap(); // optional. Simply dropping user_trace has the same effect
}
14 changes: 12 additions & 2 deletions examples/user_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,22 @@ fn main() {
.add_callback(process_callback)
.build();

let mut trace = UserTrace::new()
let (_user_trace, handle) = UserTrace::new()
.named(String::from("MyProvider"))
.enable(process_provider)
.start()
.unwrap();

// This example uses `process_from_handle` rather than the more convient `start_and_process`, because why not.
std::thread::spawn(move || {
let status = UserTrace::process_from_handle(handle);
// This code will be executed when the trace stops. Examples:
// * when it is dropped
// * when it is manually stopped (either by user_trace.stop, or by the `logman stop -ets MyProvider` command)
println!("Trace ended with status {:?}", status);
});

std::thread::sleep(Duration::new(20, 0));
trace.stop();

// user_trace will be dropped (and stopped) here
}
10 changes: 6 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
//! use ferrisetw::parser::Parser;
//! use ferrisetw::parser::TryParse;
//! use ferrisetw::provider::Provider;
//! use ferrisetw::trace::{UserTrace, TraceTrait, TraceBaseTrait};
//! use ferrisetw::trace::{UserTrace, TraceTrait};
//!
//! fn process_callback(record: &EventRecord, schema_locator: &SchemaLocator) {
//! // Within the callback we first locate the proper Schema for the event
Expand Down Expand Up @@ -84,12 +84,14 @@
//! .build();
//!
//! // We start a trace session for the previously registered provider
//! // This call will spawn a new thread which listens to the events
//! // Callbacks will be run in a separate thread.
//! let mut trace = UserTrace::new()
//! .named(String::from("MyProvider"))
//! .enable(process_provider)
//! // .enable(other_provider) // it is possible to enable multiple providers on the same trace
//! .start()
//! // .enable(other_provider) // It is possible to enable multiple providers on the same trace.
//! .start_and_process() // This call will spawn the thread for you.
//! // See the doc for alternative ways of processing the trace,
//! // with more or less flexibility regarding this spawned thread.
//! .unwrap();
//!
//! std::thread::sleep(std::time::Duration::from_secs(3));
Expand Down
36 changes: 15 additions & 21 deletions src/native/etw_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,23 @@
//! In most cases a user of the crate won't have to deal with this and can directly obtain the data
//! needed by using the functions exposed by the modules at the crate level
use crate::provider::event_filter::EventFilterDescriptor;
use crate::provider::{Provider, TraceFlags};
use crate::trace::{TraceData, TraceProperties, TraceTrait};
use crate::provider::TraceFlags;
use crate::trace::{CallbackData, TraceProperties, TraceTrait};
use std::ffi::c_void;
use std::fmt::Formatter;
use std::marker::PhantomData;
use windows::core::GUID;
use windows::core::PWSTR;
use windows::Win32::System::Diagnostics::Etw;
use windows::Win32::System::Diagnostics::Etw::EVENT_FILTER_DESCRIPTOR;
use widestring::ucstring::U16CString;
use widestring::{U16CStr, U16CString};

mod event_record;
pub use event_record::EventRecord;

mod extended_data;
pub use extended_data::{ExtendedDataItem, EventHeaderExtendedDataItem};

// typedef ULONG64 TRACEHANDLE, *PTRACEHANDLE;
pub(crate) type TraceHandle = u64;
pub(crate) type EvenTraceControl = Etw::EVENT_TRACE_CONTROL;

pub const INVALID_TRACE_HANDLE: TraceHandle = u64::MAX;

pub const TRACE_NAME_MAX_CHARS: usize = 200; // Microsoft documentation says the limit is 1024, but do not trust us. Experience shows that traces with names longer than ~240 character silently fail.

/// This enum is <https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ne-evntrace-trace_query_info_class>
Expand Down Expand Up @@ -166,9 +160,9 @@ impl EventTraceProperties {
/// # Notes
/// `trace_name` is limited to 200 characters.
pub(crate) fn new<T>(
trace_name: &str,
trace_name: &U16CStr,
trace_properties: &TraceProperties,
providers: &[Provider],
enable_flags: Etw::EVENT_TRACE_FLAG,
) -> Self
where
T: TraceTrait
Expand All @@ -192,7 +186,7 @@ impl EventTraceProperties {
}

etw_trace_properties.LogFileMode |= T::augmented_file_mode();
etw_trace_properties.EnableFlags = Etw::EVENT_TRACE_FLAG(T::enable_flags(providers));
etw_trace_properties.EnableFlags = enable_flags;

// etw_trace_properties.LogFileNameOffset must be 0, but this will change when https://github.com/n4r1b/ferrisetw/issues/7 is resolved
// > If you do not want to log events to a log file (for example, if you specify EVENT_TRACE_REAL_TIME_MODE only), set LogFileNameOffset to 0.
Expand All @@ -208,9 +202,8 @@ impl EventTraceProperties {
wide_trace_name: [0u16; TRACE_NAME_MAX_CHARS+1],
wide_log_file_name: [0u16; TRACE_NAME_MAX_CHARS+1],
};
let wide_trace_name = U16CString::from_str_truncate(trace_name);
let name_len = wide_trace_name.len().min(TRACE_NAME_MAX_CHARS);
s.wide_trace_name[..name_len].copy_from_slice(&wide_trace_name.as_slice()[..name_len]);
let name_len = trace_name.len().min(TRACE_NAME_MAX_CHARS);
s.wide_trace_name[..name_len].copy_from_slice(&trace_name.as_slice()[..name_len]);

s
}
Expand All @@ -234,28 +227,29 @@ impl EventTraceProperties {

/// Newtype wrapper over an [EVENT_TRACE_LOGFILEW]
///
/// Its lifetime is tied a to [`CallbackData`] because it contains raw pointers to it.
///
/// [EVENT_TRACE_LOGFILEW]: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Diagnostics/Etw/struct.EVENT_TRACE_LOGFILEW.html
#[repr(C)]
#[derive(Clone)]
pub struct EventTraceLogfile<'tracedata> {
pub struct EventTraceLogfile<'callbackdata> {
native: Etw::EVENT_TRACE_LOGFILEW,
wide_logger_name: U16CString,
lifetime: PhantomData<&'tracedata TraceData>,
lifetime: PhantomData<&'callbackdata CallbackData>,
}

impl<'tracedata> EventTraceLogfile<'tracedata> {
impl<'callbackdata> EventTraceLogfile<'callbackdata> {
/// Create a new instance
pub fn create(trace_data: &'tracedata Box<TraceData>, callback: unsafe extern "system" fn(*mut Etw::EVENT_RECORD)) -> Self {
pub fn create(callback_data: &'callbackdata Box<CallbackData>, mut wide_logger_name: U16CString, callback: unsafe extern "system" fn(*mut Etw::EVENT_RECORD)) -> Self {
let mut native = Etw::EVENT_TRACE_LOGFILEW::default();

let mut wide_logger_name = U16CString::from_str_truncate(&trace_data.name);
native.LoggerName = PWSTR(wide_logger_name.as_mut_ptr());
native.Anonymous1.ProcessTraceMode =
u32::from(ProcessTraceMode::RealTime) | u32::from(ProcessTraceMode::EventRecord);

native.Anonymous2.EventRecordCallback = Some(callback);

let not_really_mut_ptr = trace_data.as_ref() as *const TraceData as *const c_void as *mut c_void; // That's kind-of fine because the user context is _not supposed_ to be changed by Windows APIs
let not_really_mut_ptr = callback_data.as_ref() as *const CallbackData as *const c_void as *mut c_void; // That's kind-of fine because the user context is _not supposed_ to be changed by Windows APIs
native.Context = not_really_mut_ptr;

Self {
Expand Down
2 changes: 1 addition & 1 deletion src/native/etw_types/event_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl EventRecord {

/// The `UserContext` field from the wrapped `EVENT_RECORD`
///
/// In this crate, it is always populated to point to a valid [`TraceData`](crate::trace::TraceData)
/// In this crate, it is always populated to point to a valid [`CallbackData`](crate::trace::CallbackData)
pub fn user_context(&self) -> *const std::ffi::c_void {
self.0.UserContext as *const _
}
Expand Down
Loading

0 comments on commit e446b3f

Please sign in to comment.