ferrisetw/
trace.rs

1//! ETW Tracing/Session abstraction
2//!
3//! Provides both a Kernel and User trace that allows to start an ETW session
4use std::ffi::OsString;
5use std::marker::PhantomData;
6use std::path::PathBuf;
7use std::sync::Arc;
8use std::time::Duration;
9
10use widestring::U16CString;
11use windows::core::GUID;
12use windows::Win32::System::Diagnostics::Etw;
13
14use self::private::{PrivateRealTimeTraceTrait, PrivateTraceTrait};
15
16use crate::native::etw_types::{EventTraceProperties, SubscriptionSource};
17use crate::native::evntrace::{
18    close_trace, control_trace, control_trace_by_name, enable_provider, open_trace, process_trace,
19    start_trace, ControlHandle, TraceHandle,
20};
21use crate::native::version_helper;
22use crate::provider::Provider;
23use crate::utils;
24use crate::EventRecord;
25use crate::SchemaLocator;
26
27pub use crate::native::etw_types::DumpFileLoggingMode;
28pub use crate::native::etw_types::LoggingMode;
29
30pub(crate) mod callback_data;
31use callback_data::CallbackData;
32use callback_data::CallbackDataFromFile;
33use callback_data::RealTimeCallbackData;
34
35const KERNEL_LOGGER_NAME: &str = "NT Kernel Logger";
36const SYSTEM_TRACE_CONTROL_GUID: &str = "9e814aad-3204-11d2-9a82-006008a86939";
37const EVENT_TRACE_SYSTEM_LOGGER_MODE: u32 = 0x02000000;
38
39/// Trace module errors
40#[derive(Debug)]
41pub enum TraceError {
42    InvalidTraceName,
43    /// Wrapper over an internal [EvntraceNativeError](crate::native::EvntraceNativeError)
44    EtwNativeError(crate::native::EvntraceNativeError),
45}
46
47impl From<crate::native::EvntraceNativeError> for TraceError {
48    fn from(err: crate::native::EvntraceNativeError) -> Self {
49        TraceError::EtwNativeError(err)
50    }
51}
52
53type TraceResult<T> = Result<T, TraceError>;
54
55/// Trace Properties struct
56///
57/// 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)
58///
59/// [More info](https://docs.microsoft.com/en-us/message-analyzer/specifying-advanced-etw-session-configuration-settings#configuring-the-etw-session)
60#[derive(Debug, Copy, Clone)]
61pub struct TraceProperties {
62    /// Represents the ETW Session in KB
63    pub buffer_size: u32,
64    /// Represents the ETW Session minimum number of buffers to use
65    pub min_buffer: u32,
66    /// Represents the ETW Session maximum number of buffers in the buffer pool
67    pub max_buffer: u32,
68    /// Represents the ETW Session flush interval.
69    ///
70    /// This duration will be rounded to the closest second (and 0 will be translated as 1 second)
71    pub flush_timer: Duration,
72    /// Represents the ETW Session [Logging Mode](https://docs.microsoft.com/en-us/windows/win32/etw/logging-mode-constants)
73    pub log_file_mode: LoggingMode,
74}
75
76impl Default for TraceProperties {
77    fn default() -> Self {
78        // Sane defaults, inspired by https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties
79        TraceProperties {
80            buffer_size: 32,
81            min_buffer: 0,
82            max_buffer: 0,
83            flush_timer: Duration::from_secs(1),
84            log_file_mode: LoggingMode::EVENT_TRACE_REAL_TIME_MODE
85                | LoggingMode::EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING,
86        }
87    }
88}
89
90/// Trait for common methods to user, kernel and file traces
91pub trait TraceTrait: private::PrivateTraceTrait + Sized {
92    // This must be implemented for every trace, as this getter is needed by other methods from this trait
93    fn trace_handle(&self) -> TraceHandle;
94
95    // This utility function should be implemented for every trace
96    fn events_handled(&self) -> usize;
97
98    // The following are default implementations, that work on both user and kernel traces
99
100    /// This is blocking and starts triggerring the callbacks.
101    ///
102    /// Because this call is blocking, you probably want to call this from a background thread.<br/>
103    /// See [`TraceBuilder::start`] for alternative and more convenient ways to start a trace.
104    fn process(&mut self) -> TraceResult<()> {
105        process_trace(self.trace_handle()).map_err(|e| e.into())
106    }
107
108    /// Process a trace given its handle.
109    ///
110    /// See [`TraceBuilder::start`] for alternative and more convenient ways to start a trace.
111    fn process_from_handle(handle: TraceHandle) -> TraceResult<()> {
112        process_trace(handle).map_err(|e| e.into())
113    }
114
115    /// Stops the trace
116    ///
117    /// This consumes the trace, that can no longer be used afterwards.
118    /// The same result is achieved by dropping `Self`
119    fn stop(mut self) -> TraceResult<()> {
120        self.non_consuming_stop()
121    }
122}
123
124/// Trait for common methods to real-time traces
125pub trait RealTimeTraceTrait: TraceTrait + private::PrivateRealTimeTraceTrait {
126    // This differs between UserTrace and KernelTrace
127    fn trace_guid() -> GUID;
128
129    // This utility function should be implemented for every trace
130    fn trace_name(&self) -> OsString;
131}
132
133impl TraceTrait for UserTrace {
134    fn trace_handle(&self) -> TraceHandle {
135        self.trace_handle
136    }
137
138    fn events_handled(&self) -> usize {
139        self.callback_data.events_handled()
140    }
141}
142
143impl RealTimeTraceTrait for UserTrace {
144    fn trace_guid() -> GUID {
145        GUID::new().unwrap_or(GUID::zeroed())
146    }
147
148    fn trace_name(&self) -> OsString {
149        self.properties.name()
150    }
151}
152
153// TODO: Implement enable_provider function for providers that require call to TraceSetInformation with extended PERFINFO_GROUPMASK
154impl TraceTrait for KernelTrace {
155    fn trace_handle(&self) -> TraceHandle {
156        self.trace_handle
157    }
158
159    fn events_handled(&self) -> usize {
160        self.callback_data.events_handled()
161    }
162}
163
164impl RealTimeTraceTrait for KernelTrace {
165    fn trace_guid() -> GUID {
166        if version_helper::is_win8_or_greater() {
167            GUID::new().unwrap_or(GUID::zeroed())
168        } else {
169            GUID::from(SYSTEM_TRACE_CONTROL_GUID)
170        }
171    }
172
173    fn trace_name(&self) -> OsString {
174        self.properties.name()
175    }
176}
177
178impl TraceTrait for FileTrace {
179    fn trace_handle(&self) -> TraceHandle {
180        self.trace_handle
181    }
182
183    fn events_handled(&self) -> usize {
184        self.callback_data.events_handled()
185    }
186}
187
188/// A real-time trace session to collect events from user-mode applications
189///
190/// To stop the session, you can drop this instance
191#[derive(Debug)]
192#[allow(clippy::redundant_allocation)] // see https://github.com/n4r1b/ferrisetw/issues/72
193pub struct UserTrace {
194    properties: EventTraceProperties,
195    control_handle: ControlHandle,
196    trace_handle: TraceHandle,
197    // CallbackData is
198    // * `Arc`ed, so that dropping a Trace while a callback is still running is not an issue
199    // * `Boxed`, so that the `UserTrace` can be moved around the stack (e.g. returned by a function) but the pointers to the `CallbackData` given to Windows ETW API stay valid
200    callback_data: Box<Arc<CallbackData>>,
201}
202
203/// A real-time trace session to collect events from kernel-mode drivers
204///
205/// To stop the session, you can drop this instance
206#[derive(Debug)]
207#[allow(clippy::redundant_allocation)] // see https://github.com/n4r1b/ferrisetw/issues/72
208pub struct KernelTrace {
209    properties: EventTraceProperties,
210    control_handle: ControlHandle,
211    trace_handle: TraceHandle,
212    // CallbackData is
213    // * `Arc`ed, so that dropping a Trace while a callback is still running is not an issue
214    // * `Boxed`, so that the `UserTrace` can be moved around the stack (e.g. returned by a function) but the pointers to the `CallbackData` given to Windows ETW API stay valid
215    callback_data: Box<Arc<CallbackData>>,
216}
217
218/// A trace session that reads events from an ETL file
219///
220/// To stop the session, you can drop this instance
221#[derive(Debug)]
222#[allow(clippy::redundant_allocation)] // see https://github.com/n4r1b/ferrisetw/issues/72
223pub struct FileTrace {
224    trace_handle: TraceHandle,
225    // CallbackData is
226    // * `Arc`ed, so that dropping a Trace while a callback is still running is not an issue
227    // * `Boxed`, so that the `UserTrace` can be moved around the stack (e.g. returned by a function) but the pointers to the `CallbackData` given to Windows ETW API stay valid
228    callback_data: Box<Arc<CallbackData>>,
229}
230
231/// Various parameters related to an ETL dump file
232#[derive(Clone, Default)]
233pub struct DumpFileParams {
234    pub file_path: PathBuf,
235    /// Options that control how the file is written. If you're not sure, you can use [`DumpFileLoggingMode::default()`].
236    pub file_logging_mode: DumpFileLoggingMode,
237    /// Maximum size of the dump file. This is expressed in MB, unless `file_logging_mode` requires it otherwise.
238    pub max_size: Option<u32>,
239}
240
241/// Provides a way to crate Trace objects.
242///
243/// These builders are created using [`UserTrace::new`] or [`KernelTrace::new`]
244pub struct TraceBuilder<T: RealTimeTraceTrait> {
245    name: String,
246    etl_dump_file: Option<DumpFileParams>,
247    properties: TraceProperties,
248    rt_callback_data: RealTimeCallbackData,
249    trace_kind: PhantomData<T>,
250}
251
252pub struct FileTraceBuilder {
253    etl_file_path: PathBuf,
254    callback: crate::EtwCallback,
255}
256
257impl UserTrace {
258    /// Create a UserTrace builder
259    pub fn new() -> TraceBuilder<UserTrace> {
260        let name = format!("n4r1b-trace-{}", utils::rand_string());
261        TraceBuilder {
262            name,
263            etl_dump_file: None,
264            rt_callback_data: RealTimeCallbackData::new(),
265            properties: TraceProperties::default(),
266            trace_kind: PhantomData,
267        }
268    }
269
270    /// Stops the trace
271    ///
272    /// This consumes the trace, that can no longer be used afterwards.
273    /// The same result is achieved by dropping `Self`
274    pub fn stop(mut self) -> TraceResult<()> {
275        self.non_consuming_stop()
276    }
277}
278
279impl KernelTrace {
280    /// Create a KernelTrace builder
281    pub fn new() -> TraceBuilder<KernelTrace> {
282        let builder = TraceBuilder {
283            name: String::new(),
284            etl_dump_file: None,
285            rt_callback_data: RealTimeCallbackData::new(),
286            properties: TraceProperties::default(),
287            trace_kind: PhantomData,
288        };
289        // Not all names are valid. Let's use the setter to check them for us
290        builder.named(format!("n4r1b-trace-{}", utils::rand_string()))
291    }
292
293    /// Stops the trace
294    ///
295    /// This consumes the trace, that can no longer be used afterwards.
296    /// The same result is achieved by dropping `Self`
297    pub fn stop(mut self) -> TraceResult<()> {
298        self.non_consuming_stop()
299    }
300}
301
302mod private {
303    //! The only reason for this private module is to have a "private" trait in an otherwise publicly exported type (`TraceBuilder`)
304    //!
305    //! See <https://github.com/rust-lang/rust/issues/34537>
306    use super::*;
307
308    #[derive(Debug, PartialEq, Eq)]
309    pub enum TraceKind {
310        User,
311        Kernel,
312    }
313
314    pub trait PrivateRealTimeTraceTrait: PrivateTraceTrait {
315        const TRACE_KIND: TraceKind;
316        #[allow(clippy::redundant_allocation)] // Being Boxed is really important, let's keep the Box<...> in the function signature to make the intent clearer (see https://github.com/n4r1b/ferrisetw/issues/72)
317        fn build(
318            properties: EventTraceProperties,
319            control_handle: ControlHandle,
320            trace_handle: TraceHandle,
321            callback_data: Box<Arc<CallbackData>>,
322        ) -> Self;
323        fn augmented_file_mode() -> u32;
324        fn enable_flags(_providers: &[Provider]) -> u32;
325    }
326
327    pub trait PrivateTraceTrait {
328        // This function aims at de-deduplicating code called by `impl Drop` and `Trace::stop`.
329        // It is basically [`Self::stop`], without consuming self (because the `impl Drop` only has a `&mut self`, not a `self`)
330        fn non_consuming_stop(&mut self) -> TraceResult<()>;
331    }
332}
333
334impl private::PrivateRealTimeTraceTrait for UserTrace {
335    const TRACE_KIND: private::TraceKind = private::TraceKind::User;
336
337    fn build(
338        properties: EventTraceProperties,
339        control_handle: ControlHandle,
340        trace_handle: TraceHandle,
341        callback_data: Box<Arc<CallbackData>>,
342    ) -> Self {
343        UserTrace {
344            properties,
345            control_handle,
346            trace_handle,
347            callback_data,
348        }
349    }
350
351    fn augmented_file_mode() -> u32 {
352        0
353    }
354    fn enable_flags(_providers: &[Provider]) -> u32 {
355        0
356    }
357}
358
359impl private::PrivateTraceTrait for UserTrace {
360    fn non_consuming_stop(&mut self) -> TraceResult<()> {
361        close_trace(self.trace_handle, &self.callback_data)?;
362        control_trace(
363            &mut self.properties,
364            self.control_handle,
365            Etw::EVENT_TRACE_CONTROL_STOP,
366        )?;
367        Ok(())
368    }
369}
370
371impl private::PrivateRealTimeTraceTrait for KernelTrace {
372    const TRACE_KIND: private::TraceKind = private::TraceKind::Kernel;
373
374    fn build(
375        properties: EventTraceProperties,
376        control_handle: ControlHandle,
377        trace_handle: TraceHandle,
378        callback_data: Box<Arc<CallbackData>>,
379    ) -> Self {
380        KernelTrace {
381            properties,
382            control_handle,
383            trace_handle,
384            callback_data,
385        }
386    }
387
388    fn augmented_file_mode() -> u32 {
389        if version_helper::is_win8_or_greater() {
390            EVENT_TRACE_SYSTEM_LOGGER_MODE
391        } else {
392            0
393        }
394    }
395
396    fn enable_flags(providers: &[Provider]) -> u32 {
397        providers.iter().fold(0, |acc, x| acc | x.kernel_flags())
398    }
399}
400
401impl private::PrivateTraceTrait for KernelTrace {
402    fn non_consuming_stop(&mut self) -> TraceResult<()> {
403        close_trace(self.trace_handle, &self.callback_data)?;
404        control_trace(
405            &mut self.properties,
406            self.control_handle,
407            Etw::EVENT_TRACE_CONTROL_STOP,
408        )?;
409        Ok(())
410    }
411}
412
413impl private::PrivateTraceTrait for FileTrace {
414    fn non_consuming_stop(&mut self) -> TraceResult<()> {
415        close_trace(self.trace_handle, &self.callback_data)?;
416        Ok(())
417    }
418}
419
420impl<T: RealTimeTraceTrait + PrivateRealTimeTraceTrait> TraceBuilder<T> {
421    /// Define the trace name
422    ///
423    /// For kernel traces on Windows Versions older than Win8, this method won't change the trace name. In those versions the trace name will be set to "NT Kernel Logger".
424    ///
425    /// Note: this trace name may be truncated to a few hundred characters if it is too long.
426    pub fn named(mut self, name: String) -> Self {
427        if T::TRACE_KIND == private::TraceKind::Kernel && !version_helper::is_win8_or_greater() {
428            self.name = String::from(KERNEL_LOGGER_NAME);
429        } else {
430            self.name = name;
431        };
432
433        self
434    }
435
436    /// Define several low-level properties of the trace at once.
437    ///
438    /// These are part of [`EVENT_TRACE_PROPERTIES`](https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties)
439    pub fn set_trace_properties(mut self, props: TraceProperties) -> Self {
440        self.properties = props;
441        self
442    }
443
444    /// Define a dump file for the events.
445    ///
446    /// If set, events will be dumped to a file on disk.<br/>
447    /// Such files usually have a `.etl` extension.<br/>
448    /// Dumped events will also be processed by the callbacks you'll specify with [`crate::provider::ProviderBuilder::add_callback`].
449    ///
450    /// It is possible to control many aspects of the logging file (whether its size is limited, whether it should be a circular buffer file, etc.).
451    /// If you're not sure, `params` has a safe [`default` value](`DumpFileParams::default`).
452    ///
453    /// Note: the file name may be truncated to a few hundred characters if it is too long.
454    pub fn set_etl_dump_file(mut self, params: DumpFileParams) -> Self {
455        self.etl_dump_file = Some(params);
456        self
457    }
458
459    /// Enable a Provider for this trace
460    ///
461    /// This will invoke the provider's callback whenever an event is available
462    ///
463    /// # Note
464    /// Windows API seems to support removing providers, or changing its properties when the session is processing events (see <https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2#remarks>)    /// Currently, this crate only supports defining Providers and their settings when building the trace, because it is easier to ensure memory-safety this way.
465    /// It probably would be possible to support changing Providers when the trace is processing, but this is left as a TODO (see <https://github.com/n4r1b/ferrisetw/issues/54>)
466    pub fn enable(mut self, provider: Provider) -> Self {
467        self.rt_callback_data.add_provider(provider);
468        self
469    }
470
471    /// Build the `UserTrace` and start the trace session
472    ///
473    /// Internally, this calls the `StartTraceW`, `EnableTraceEx2` and `OpenTraceW`.
474    ///
475    /// To start receiving events, you'll still have to call either:
476    /// * Worst option: `process()` on the returned `T`. This will block the current thread until the trace is stopped.<br/>
477    ///   This means you'll probably want to call this on a spawned thread, where the `T` must be moved to. This will prevent you from re-using it from the another thread.<br/>
478    ///   This means you will not be able to explicitly stop the trace, because you'll no longer have a `T` to drop or to call `stop` on. The trace will stop when the program exits, or when the ETW API hits an error.<br/>
479    /// * Most powerful option: `T::process_from_handle()` with the returned [`TraceHandle`].<br/>
480    ///   This will block, so this also has to be run in a spawned thread. But, as this does not "consume" the `T`, you'll be able to call `stop` on it (or to drop it) to explicitly close the trace. Stopping a trace will make the `process` function return.
481    /// * Easiest option: [`TraceBuilder::start_and_process()`].<br/>
482    ///   This convenience function spawns a thread for you, call [`TraceBuilder::start`] on the trace, and returns immediately.<br/>
483    ///   This option returns a `T`, so you can explicitly stop the trace, but there is no way to get the status code of the ProcessTrace API.
484    pub fn start(self) -> TraceResult<(T, TraceHandle)> {
485        // Prepare a wide version of the trace name
486        let trace_wide_name = U16CString::from_str_truncate(self.name);
487        let mut trace_wide_vec = trace_wide_name.into_vec();
488        trace_wide_vec.truncate(crate::native::etw_types::TRACE_NAME_MAX_CHARS);
489        let trace_wide_name = U16CString::from_vec_truncate(trace_wide_vec);
490
491        // Prepare a wide version of the ETL dump file path
492        let wide_etl_dump_file = match self.etl_dump_file {
493            None => None,
494            Some(DumpFileParams {
495                file_path,
496                file_logging_mode,
497                max_size,
498            }) => {
499                let wide_path = U16CString::from_os_str_truncate(file_path.as_os_str());
500                let mut wide_path_vec = wide_path.into_vec();
501                wide_path_vec.truncate(crate::native::etw_types::TRACE_NAME_MAX_CHARS);
502                Some((
503                    U16CString::from_vec_truncate(wide_path_vec),
504                    file_logging_mode,
505                    max_size,
506                ))
507            }
508        };
509
510        let flags = self.rt_callback_data.provider_flags::<T>();
511        let (full_properties, control_handle) = start_trace::<T>(
512            &trace_wide_name,
513            wide_etl_dump_file
514                .as_ref()
515                .map(|(path, params, max_size)| (path.as_ucstr(), *params, *max_size)),
516            &self.properties,
517            flags,
518        )?;
519
520        // TODO: For kernel traces, implement enable_provider function for providers that require call to TraceSetInformation with extended PERFINFO_GROUPMASK
521
522        if T::TRACE_KIND == private::TraceKind::User {
523            for prov in self.rt_callback_data.providers() {
524                enable_provider(control_handle, prov)?;
525            }
526        }
527
528        let callback_data = Box::new(Arc::new(CallbackData::RealTime(self.rt_callback_data)));
529        let trace_handle = open_trace(
530            SubscriptionSource::RealTimeSession(trace_wide_name),
531            &callback_data,
532        )?;
533
534        Ok((
535            T::build(full_properties, control_handle, trace_handle, callback_data),
536            trace_handle,
537        ))
538    }
539
540    /// Convenience method that calls [`TraceBuilder::start`] then `process`
541    ///
542    /// # Notes
543    /// * See the documentation of [`TraceBuilder::start`] for more info
544    /// * `process` is called on a spawned thread, and thus this method does not give any way to retrieve the error of `process` (if any)
545    pub fn start_and_process(self) -> TraceResult<T> {
546        let (trace, trace_handle) = self.start()?;
547
548        std::thread::spawn(move || UserTrace::process_from_handle(trace_handle));
549
550        Ok(trace)
551    }
552}
553
554impl FileTrace {
555    /// Create a trace that will read events from a file
556    #[allow(clippy::new_ret_no_self)]
557    pub fn new<T>(path: PathBuf, callback: T) -> FileTraceBuilder
558    where
559        T: FnMut(&EventRecord, &SchemaLocator) + Send + Sync + 'static,
560    {
561        FileTraceBuilder {
562            etl_file_path: path,
563            callback: Box::new(callback),
564        }
565    }
566
567    fn non_consuming_stop(&mut self) -> TraceResult<()> {
568        close_trace(self.trace_handle, &self.callback_data)?;
569        Ok(())
570    }
571}
572
573impl FileTraceBuilder {
574    /// Build the `FileTrace` and start the trace session
575    ///
576    /// See the documentation for [`TraceBuilder::start`] for more information.
577    pub fn start(self) -> TraceResult<(FileTrace, TraceHandle)> {
578        // Prepare a wide version of the source ETL file path
579        let wide_etl_file_path = U16CString::from_os_str_truncate(self.etl_file_path.as_os_str());
580
581        let from_file_cb = CallbackDataFromFile::new(self.callback);
582        let callback_data = Box::new(Arc::new(CallbackData::FromFile(from_file_cb)));
583        let trace_handle = open_trace(
584            SubscriptionSource::FromFile(wide_etl_file_path),
585            &callback_data,
586        )?;
587
588        Ok((
589            FileTrace {
590                trace_handle,
591                callback_data,
592            },
593            trace_handle,
594        ))
595    }
596
597    /// Convenience method that calls [`TraceBuilder::start`] then `process`
598    ///
599    /// # Notes
600    /// * See the documentation of [`TraceBuilder::start`] for more info
601    /// * `process` is called on a spawned thread, and thus this method does not give any way to retrieve the error of `process` (if any)
602    pub fn start_and_process(self) -> TraceResult<FileTrace> {
603        let (trace, trace_handle) = self.start()?;
604
605        std::thread::spawn(move || FileTrace::process_from_handle(trace_handle));
606
607        Ok(trace)
608    }
609}
610
611impl Drop for UserTrace {
612    fn drop(&mut self) {
613        let _ignored_error_in_drop = self.non_consuming_stop();
614    }
615}
616
617impl Drop for KernelTrace {
618    fn drop(&mut self) {
619        let _ignored_error_in_drop = self.non_consuming_stop();
620    }
621}
622
623impl Drop for FileTrace {
624    fn drop(&mut self) {
625        let _ignored_error_in_drop = self.non_consuming_stop();
626    }
627}
628
629/// Stop a trace given its name.
630///
631/// This function is intended to close a trace you did not start yourself.
632/// Otherwise, you should prefer [`UserTrace::stop()`] or [`KernelTrace::stop()`]
633pub fn stop_trace_by_name(trace_name: &str) -> TraceResult<()> {
634    let trace_properties = TraceProperties::default();
635    let flags = Etw::EVENT_TRACE_FLAG::default();
636    let wide_name = U16CString::from_str(trace_name).map_err(|_| TraceError::InvalidTraceName)?;
637
638    let mut properties = EventTraceProperties::new::<UserTrace>(
639        // for EVENT_TRACE_CONTROL_STOP, we don't really care about most of the contents of the EventTraceProperties, so using new::<UserTrace>() is fine, even when stopping a kernel trace
640        &wide_name,
641        None, // MSDN says the dump file name (if any) must be populated for a EVENT_TRACE_CONTROL_STOP, but experience shows this is not necessary.
642        &trace_properties,
643        flags,
644    );
645
646    control_trace_by_name(&mut properties, &wide_name, Etw::EVENT_TRACE_CONTROL_STOP)?;
647
648    Ok(())
649}
650
651#[cfg(test)]
652mod test {
653    use super::*;
654
655    #[test]
656    fn test_enable_multiple_providers() {
657        let prov = Provider::by_guid("22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716").build();
658        let prov1 = Provider::by_guid("A0C1853B-5C40-4B15-8766-3CF1C58F985A").build();
659
660        let trace_builder = UserTrace::new().enable(prov).enable(prov1);
661
662        assert_eq!(trace_builder.rt_callback_data.providers().len(), 2);
663    }
664}