From cae88c0fe0da3529b7541d417c841bce57de6db1 Mon Sep 17 00:00:00 2001 From: NateD-MSFT <34494373+NateD-MSFT@users.noreply.github.com> Date: Wed, 10 Jul 2024 18:35:24 -0700 Subject: [PATCH 1/8] First stab at windows_drivers feature. --- win_etw_macros/Cargo.toml | 4 + win_etw_macros/src/lib.rs | 450 ++++++++++++++++++++++++ win_etw_provider/Cargo.toml | 5 +- win_etw_provider/src/driver_provider.rs | 404 +++++++++++++++++++++ win_etw_provider/src/guid.rs | 13 + win_etw_provider/src/lib.rs | 4 + 6 files changed, 878 insertions(+), 2 deletions(-) create mode 100644 win_etw_provider/src/driver_provider.rs diff --git a/win_etw_macros/Cargo.toml b/win_etw_macros/Cargo.toml index 719407b..e984c48 100644 --- a/win_etw_macros/Cargo.toml +++ b/win_etw_macros/Cargo.toml @@ -20,3 +20,7 @@ quote = "^1.0" win_etw_metadata = { version = "0.1.2", path = "../win_etw_metadata" } uuid = { version = "^1.3", features = ["v5"]} sha1_smol = "1.0.0" + +[features] +default = [] +windows_drivers = [] \ No newline at end of file diff --git a/win_etw_macros/src/lib.rs b/win_etw_macros/src/lib.rs index b3f9643..e49fd39 100644 --- a/win_etw_macros/src/lib.rs +++ b/win_etw_macros/src/lib.rs @@ -224,7 +224,10 @@ pub fn trace_logging_provider( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { // let logging_trait = parse_macro_input!(input as syn::ItemTrait); + #[cfg(not(feature = "windows_drivers"))] let output = trace_logging_events_core(attr.into(), input.into()); + #[cfg(feature = "windows_drivers")] + let output = trace_logging_events_core_drivers(attr.into(), input.into()); output.into() } @@ -673,6 +676,453 @@ fn trace_logging_events_core(attr: TokenStream, item_tokens: TokenStream) -> Tok output } + +fn trace_logging_events_core_drivers(attr: TokenStream, item_tokens: TokenStream) -> TokenStream { + let mut errors: Vec = Vec::new(); + + let logging_trait: syn::ItemTrait = match syn::parse2(item_tokens) { + Err(e) => { + return e.to_compile_error(); + } + Ok(syn::Item::Trait(t)) => t, + Ok(syn::Item::Mod(m)) => { + return Error::new_spanned(&m, "Modules are not yet supported, but thanks for asking.") + .to_compile_error(); + } + Ok(unrecognized) => { + return Error::new_spanned( + &unrecognized, + "The #[trace_logging_provider] attribute cannot be used with this kind of item.", + ) + .to_compile_error(); + } + }; + + let provider_attrs: ProviderAttributes = match syn::parse2(attr) { + Ok(p) => p, + Err(e) => { + errors.push(e); + ProviderAttributes::default() + } + }; + + // provider_ident is the identifier used in Rust source code for the generated provider type. + let provider_ident = &logging_trait.ident; + let provider_ident_string = provider_ident.to_string(); + + let wk = WellKnownTypes::new(); + + let mut output = TokenStream::new(); + + let provider_metadata_ident = Ident::new( + &format!("{}_PROVIDER_METADATA", provider_ident_string), + provider_ident.span(), + ); + + // Create the provider metadata. + // provider_ident is the identifier used in Rust source code for the generated code. + // When writing the provider metadata, we allow the user to specify a different name to ETW. + let provider_name = provider_attrs + .provider_name + .as_ref() + .unwrap_or(&provider_ident_string); + output.extend(create_provider_metadata( + provider_name, + &provider_metadata_ident, + )); + + // Definitions that go inside the "impl MyProvider { ... }" block. + let mut provider_impl_items = TokenStream::new(); + + // To make this simple, we either require all events to be labeled + // with an event id or autogenerated. + let mut event_ids_auto_generated = true; + let mut event_id_mappings = HashMap::new(); + + for (method_index, method) in logging_trait + .items + .iter() + .filter_map(|item| { + if let syn::TraitItem::Method(m) = item { + Some(m) + } else { + None + } + }) + .enumerate() + { + let event_index = method_index as u16; + + // Check requirements for the method signature. If the requirements are not met, we + // emit an error but keep going. This allows us to report as many errors as possible in + // each build, rather than having errors "unlocked" one by one. + if method.sig.asyncness.is_some() { + errors.push(Error::new_spanned( + method, + "Async event methods are not supported.", + )); + } + if method.sig.unsafety.is_some() { + errors.push(Error::new_spanned( + method, + "Event methods should not be marked unsafe.", + )); + } + if !method.sig.generics.params.is_empty() { + errors.push(Error::new_spanned( + method, + "Generic event methods are not supported.", + )); + } + match &method.sig.output { + syn::ReturnType::Default => {} + _ => { + errors.push(Error::new_spanned( + method, + "Event methods must not return data.", + )); + } + } + if let Some(block) = method.default.as_ref() { + errors.push(Error::new_spanned( + block, + "Event methods must not contain an implementation.", + )); + } + + let event_name: String = method.sig.ident.to_string(); + + // Here we build the data descriptor array. The data descriptor array is constructed on + // the stack, and has a statically-known size. It contains pointers to data fields. The + // event metadata describes the order and type of the data pointed-to by the data + // descriptors. + // + // For self-describing events (TraceLogging), the first two items in the data descriptor + // array identify the provider metadata and the event metadata. + let mut data_descriptor_array = TokenStream::new(); + + // See comments in traceloggingprovider.h, around line 2300, which describe the + // encoding of the event mdata. + let mut event_metadata: Vec = Vec::new(); + event_metadata.push(parse_quote! { 0 }); // reserve space for the size (byte 0) + event_metadata.push(parse_quote! { 0 }); // reserve space for the size (byte 1) + event_metadata.push(parse_quote! { 0 }); // no extensions + append_utf8_str_chars(&mut event_metadata, &event_name); + + // Some fields require running some code before building the data descriptors, so we + // collect statements here. + let mut statements = TokenStream::new(); + + // Each parameter (except for &self) becomes an event field. + let mut found_receiver = false; + + // sig is the function signature for the function that we will generate for this event. + let mut sig = method.sig.clone(); + + for param in sig.inputs.iter_mut() { + let param_span = param.span(); + match param { + FnArg::Receiver(_) => { + errors.push(Error::new_spanned(param, "Event methods should not provide any receiver arguments (&self, &mut self, etc.).")); + found_receiver = true; + } + + FnArg::Typed(param_typed) => { + let mut event_attr: Option = None; + param_typed.attrs.retain(|a| { + if a.path == parse_quote!(event) { + event_attr = Some(a.clone()); + false + } else if a.path == parse_quote!(doc) { + true + } else { + errors.push(Error::new_spanned( + a, + "This attribute is not permitted on event fields.", + )); + true + } + }); + let param_name: &Ident = match &*param_typed.pat { + syn::Pat::Ident(ref name) => &name.ident, + _ => { + errors.push(Error::new( + param.span(), + "Only ordinary parameter patterns are supported on event methods.", + )); + continue; + } + }; + + if parse_event_field( + &mut errors, + &wk, + event_attr.as_ref(), + param_span, + param_name, + &mut param_typed.ty, + &mut data_descriptor_array, + &mut event_metadata, + &mut statements, + ) + .is_err() + { + errors.push(Error::new_spanned( + ¶m, + "This type is not supported for event parameters.", + )); + } + } + } + } + + // We require that every function declare a '&self' receiver parameter. + if !found_receiver { + sig.inputs.insert(0, parse_quote!(&self)); + } + + // Insert the "options" argument. + sig.inputs.insert( + 1, + parse_quote!(options: core::option::Option<&::win_etw_provider::EventOptions>), + ); + + // Now that we have processed all parameters ("fields"), we can finish constructing + // the per-event metadata. + let event_metadata_len = event_metadata.len(); + if event_metadata_len > 0xffff { + errors.push(Error::new( + method.span(), + "Event metadata is too large to encode; reduce the complexity of this event.", + )); + continue; + } + let event_metadata_len_b0 = (event_metadata_len & 0xff) as u8; + let event_metadata_len_b1 = (event_metadata_len >> 8) as u8; + event_metadata[0] = parse_quote! { #event_metadata_len_b0 }; + event_metadata[1] = parse_quote! { #event_metadata_len_b1 }; + + let event_attrs = parse_event_attributes(&mut errors, &method.sig.ident, &method.attrs); + + // Generate the event descriptor for this event. + // This is a static variable. The name is exactly the name of the event. + let event_level = event_attrs.level; + let event_opcode = event_attrs.opcode; + let event_task = event_attrs.task; + let potential_event_id = event_attrs.event_id; + let event_keyword = event_attrs + .keyword + .as_ref() + .cloned() + .unwrap_or_else(|| parse_quote!(0)); + + // We use the first entry to see if we have user provided IDs + // or we are generating one. + if event_index == 0 { + event_ids_auto_generated = potential_event_id.is_none(); + } + + // We have some events with user provided id and some without. + if event_ids_auto_generated != potential_event_id.is_none() { + errors.push(Error::new( + method.span(), + "Event ids must be set for all events, or for none.", + )); + } + + let event_id = potential_event_id.unwrap_or(event_index); + + // Only care about #[event(id = #)] types so we don't get + // confusing messages when forget to add an id for some + // event. + if potential_event_id.is_some() { + let identifier = method.sig.ident.to_string(); + if let Some(previous) = event_id_mappings.get(&event_id) { + errors.push(Error::new( + method.span(), + format!( + "Event id {} has already been defined on {}.", + event_id, previous + ), + )); + } + event_id_mappings.insert(event_id, identifier); + } + + // an expression which generates EventDescriptor + let event_descriptor = quote! { + ::win_etw_provider::EventDescriptor { + id: #event_id, + version: 0, + channel: 11, + level: #event_level, + opcode: #event_opcode, + task: #event_task, + keyword: #event_keyword, + }; + }; + + let event_attrs_method_attrs = &event_attrs.method_attrs; + + // Generate the `${name}_is_enabled` function for this event. + // We do not use ident_suffix() because this is not a private identifier. + let event_is_enabled_name = Ident::new( + &format!("{}_is_enabled", method.sig.ident), + method.sig.ident.span(), + ); + + // Build the method that implements this event. + provider_impl_items.extend(quote!{ + #( #event_attrs_method_attrs )* + #[cfg_attr(not(target_os = "windows"), allow(unused_variables))] + pub #sig + { + #[cfg(target_os = "windows")] + { + use ::win_etw_provider::EventDataDescriptor; + + // This places the EVENT_METADATA into a read-only linker section, properly + // ordered with respect to TRACE_LOGGING_METADATA and other related sections. + #[link_section = ".rdata$etw1"] + #[used] + static EVENT_METADATA: [u8; #event_metadata_len] = [ #( #event_metadata, )* ]; + + let mut event_descriptor: ::win_etw_provider::EventDescriptor = #event_descriptor; + + if let Some(opts) = options { + if let Some(level) = opts.level { + event_descriptor.level = level; + } + } + + #statements + + let data_descriptors = [ + EventDataDescriptor::for_provider_metadata(&#provider_metadata_ident[..]), + EventDataDescriptor::for_event_metadata(&EVENT_METADATA[..]), + #data_descriptor_array + ]; + ::win_etw_provider::Provider::write(&self.provider, + options, + &event_descriptor, + &data_descriptors, + ); + } + } + + pub fn #event_is_enabled_name(&self, level: ::core::option::Option<::win_etw_provider::Level>) -> bool { + #[cfg(target_os = "windows")] + { + let mut event_descriptor: ::win_etw_provider::EventDescriptor = #event_descriptor; + if let Some(level) = level { + event_descriptor.level = level; + } + + ::win_etw_provider::Provider::is_event_enabled( + &self.provider, + &event_descriptor) + } + #[cfg(not(target_os = "windows"))] + { + false + } + } + }); + } + + // We propagate the visibility of the trait definition to the structure definition. + let vis = logging_trait.vis.clone(); + let provider_guid = match provider_attrs.uuid { + Some(uuid) => uuid, + None => etw_event_source_guid(provider_name), + }; + let provider_guid_const = uuid_to_expr(&provider_guid); + + // If the input item has doc attributes, then carry them over to the output type. + let doc_path: syn::Path = parse_quote!(doc); + let provider_doc_attrs = logging_trait + .attrs + .iter() + .filter(|a| a.path == doc_path) + .collect::>(); + + // Build a code fragment that registers the provider traits. + let register_traits: TokenStream = create_register_provider_traits( + &provider_name, + provider_attrs.provider_group_guid.as_ref(), + ); + + output.extend(quote! { + #( #provider_doc_attrs )* + #vis struct #provider_ident { + provider: ::core::option::Option<::win_etw_provider::EtwDriverProvider>, + } + + impl #provider_ident { + /// Creates (registers) a new instance of this provider. If registration fails, + /// returns a "null" provider. This prevents problems with event logging from + /// disrupting the normal operation of applications. + /// + /// On non-Windows platforms, this function always returns a null provider. + /// + /// Creating an event source is a costly operation, because it requires contacting the + /// event manager, allocating event buffers, potentially receiving callbacks from + /// event consumers, etc. Applications should only create event sources during process + /// initialization, and should always reuse them, never re-creating them. + pub fn new() -> Self { + let provider = match ::win_etw_provider::EtwDriverProvider::new(&Self::PROVIDER_GUID) { + Ok(mut provider) => { + #[cfg(target_os = "windows")] + { + #register_traits + } + + Some(provider) + } + Err(_) => None, + }; + Self { provider } + } + + /// Creates (registers) a new instance of this provider. If registration fails, then + /// this method returns a "null" provider. + /// + /// On non-Windows platforms, this function always returns `Ok`, containing a null + /// provider. + /// + /// Creating an event source is a costly operation, because it requires contacting the + /// event manager, allocating event buffers, potentially receiving callbacks from + /// event consumers, etc. Applications should only create event sources during process + /// initialization, and should always reuse them, never re-creating them. + pub fn new_err() -> ::core::result::Result { + Ok(Self { + provider: Some(::win_etw_provider::EtwDriverProvider::new(&Self::PROVIDER_GUID)?), + }) + } + + /// Creates a new "null" instance of the provider. All events written to this provider + /// are discarded. + pub fn null() -> Self { + Self { provider: None } + } + + #[allow(unused_variable)] + pub const PROVIDER_GUID: ::win_etw_provider::GUID = #provider_guid_const; + pub const PROVIDER_NAME: &'static str = #provider_name; + } + + // We intentionally generate identifiers that are not snake-case. + #[allow(non_snake_case)] + impl #provider_ident { + #provider_impl_items + } + }); + + output.extend(errors.into_iter().map(|e| e.to_compile_error())); + output +} + + /// Creates a fragment of code (statements) which will register the /// provider traits for this provider. // diff --git a/win_etw_provider/Cargo.toml b/win_etw_provider/Cargo.toml index 1635435..ac1a02b 100644 --- a/win_etw_provider/Cargo.toml +++ b/win_etw_provider/Cargo.toml @@ -7,7 +7,6 @@ description = "Enables apps to report events to Event Tracing for Windows (ETW). license = "Apache-2.0 OR MIT" homepage = "https://github.com/microsoft/rust_win_etw" readme = "../README.md" -rust-version = "1.77" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,6 +15,7 @@ widestring = {version = "^1.0", default-features = false, features = ["alloc"]} zerocopy = { version = "0.7.32", features = ["derive"] } win_etw_metadata = { version = "^0.1.2", path = "../win_etw_metadata" } uuid = {version = "1", optional = true} +wdk-sys = { version = "^0.2.0", optional = true } [target.'cfg(windows)'.dependencies] winapi = { version = "^0.3", features = ["evntprov", "winerror", "evntrace"] } @@ -29,5 +29,6 @@ uuid = "1" std = [] default = ["no_std"] no_std = [] +windows_drivers = ["wdk-sys", "no_std"] # dev is used only for development -dev = [] +dev = [] \ No newline at end of file diff --git a/win_etw_provider/src/driver_provider.rs b/win_etw_provider/src/driver_provider.rs new file mode 100644 index 0000000..4c5e362 --- /dev/null +++ b/win_etw_provider/src/driver_provider.rs @@ -0,0 +1,404 @@ +use crate::guid::GUID; +use crate::EventDescriptor; +use crate::Level; +use crate::Provider; +use crate::{Error, EventDataDescriptor}; +use alloc::boxed::Box; +use core::convert::TryFrom; +use core::pin::Pin; +use core::ptr::null; +use core::sync::atomic::{AtomicU8, Ordering::SeqCst}; +use wdk_sys::NT_SUCCESS; + +#[cfg(target_os = "windows")] +use win_support::*; + +/// Generates a new activity ID. +/// +/// This function is only implemented on Windows. On other platforms, it will always return `Err`. +pub fn new_activity_id() -> Result { + #[cfg(target_os = "windows")] + { + win_support::new_activity_id() + } + + #[cfg(not(target_os = "windows"))] + { + Err(Error::NotSupported) + } +} + +/// Implements `Provider` by registering with ETW. +pub struct EtwDriverProvider { + #[cfg(target_os = "windows")] + handle: wdk_sys::REGHANDLE, + + #[cfg(target_os = "windows")] + // #[allow(dead_code)] // Needed for lifetime control + stable: Pin>, +} + +impl Provider for EtwDriverProvider { + #[inline(always)] + fn write( + &self, + options: Option<&crate::EventOptions>, + descriptor: &EventDescriptor, + data: &[EventDataDescriptor<'_>], + ) { + #[cfg(target_os = "windows")] + { + unsafe { + let mut activity_id_ptr = null(); + let mut related_activity_id_ptr = null(); + + let mut event_descriptor = wdk_sys::EVENT_DESCRIPTOR { + Id: descriptor.id, + Version: descriptor.version, + Channel: descriptor.channel, + Level: descriptor.level.0, + Opcode: descriptor.opcode, + Task: descriptor.task, + Keyword: descriptor.keyword, + }; + + if let Some(options) = options { + if let Some(id) = options.activity_id.as_ref() { + activity_id_ptr = id as *const GUID as *const wdk_sys::GUID; + } + if let Some(id) = options.related_activity_id.as_ref() { + related_activity_id_ptr = id as *const GUID as *const wdk_sys::GUID; + } + if let Some(level) = options.level { + event_descriptor.Level = level.0; + } + } + + let error = wdk_sys::ntddk::EtwWriteEx( + self.handle, + &event_descriptor as *const wdk_sys::_EVENT_DESCRIPTOR, + 0, // filter + 0, // flags + activity_id_ptr, // activity id + related_activity_id_ptr, // related activity id + data.len() as u32, + data.as_ptr() as *mut wdk_sys::_EVENT_DATA_DESCRIPTOR, + ); + if !NT_SUCCESS(error) { + write_failed(error as u32) + } + } + } + } + + // write_ex + // write_transfer + + fn is_enabled(&self, level: u8, keyword: u64) -> bool { + #[cfg(target_os = "windows")] + { + unsafe { wdk_sys::ntddk::EtwProviderEnabled(self.handle, level, keyword) != 0 } + } + #[cfg(not(target_os = "windows"))] + { + false + } + } + + fn is_event_enabled(&self, event_descriptor: &EventDescriptor) -> bool { + #[cfg(target_os = "windows")] + { + if false { + unsafe { + wdk_sys::ntddk::EtwEventEnabled( + self.handle, + event_descriptor as *const _ as *const wdk_sys::EVENT_DESCRIPTOR, + ) != 0 + } + } else { + let max_level = self.stable.as_ref().max_level.load(SeqCst); + event_descriptor.level.0 <= max_level + } + } + #[cfg(not(target_os = "windows"))] + { + false + } + } +} + +#[inline(never)] +fn write_failed(_error: u32) { + #[cfg(feature = "dev")] + { + eprintln!("EventWrite failed: {}", _error); + } +} + +#[cfg(target_os = "windows")] +mod win_support { + + pub use winapi::shared::evntprov; + pub use winapi::shared::evntrace; + pub use winapi::shared::winerror; + + use super::*; + + /// This data is stored in a Box, so that it has a stable address. + /// It is used to coordinate with ETW; ETW runs callbacks that need a stable pointer. + /// See `EventRegister` and the "enable callback". + pub(crate) struct StableProviderData { + pub(crate) max_level: AtomicU8, + } + + /// See [PENABLECALLBACK](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/nc-evntprov-penablecallback). + pub(crate) unsafe extern "C" fn enable_callback( + _source_id: *const wdk_sys::GUID, + is_enabled_code: u32, + level: u8, + _match_any_keyword: u64, + _match_all_keyword: u64, + _filter_data: *mut wdk_sys::EVENT_FILTER_DESCRIPTOR, + context: wdk_sys::PVOID, + ) { + // This should never happen. + if context.is_null() { + return; + } + let stable_data: &StableProviderData = &*(context as *const _ as *const StableProviderData); + + let _source_id: GUID = if _source_id.is_null() { + GUID::default() + } else { + (*(_source_id as *const GUID)).clone() + }; + #[cfg(feature = "dev")] + { + eprintln!( + "enable_callback: source_id {} is_enabled {}, level {}, any {:#x} all {:#x} filter? {:?}", + _source_id, is_enabled_code, level, _match_any_keyword, _match_all_keyword, + !_filter_data.is_null() + ); + } + + match is_enabled_code { + evntrace::EVENT_CONTROL_CODE_ENABLE_PROVIDER => { + #[cfg(feature = "dev")] + { + eprintln!("ETW is ENABLING this provider. setting level: {}", level); + } + stable_data.max_level.store(level, SeqCst); + } + evntrace::EVENT_CONTROL_CODE_DISABLE_PROVIDER => { + #[cfg(feature = "dev")] + { + eprintln!("ETW is DISABLING this provider. setting level: {}", level); + } + stable_data.max_level.store(level, SeqCst); + } + evntrace::EVENT_CONTROL_CODE_CAPTURE_STATE => { + // ETW is requesting that the provider log its state information. The meaning of this + // is provider-dependent. Currently, this functionality is not exposed to Rust apps. + #[cfg(feature = "dev")] + { + eprintln!("EVENT_CONTROL_CODE_CAPTURE_STATE"); + } + } + _ => { + // The control code is unrecognized. + #[cfg(feature = "dev")] + { + eprintln!( + "enable_callback: control code {} is not recognized", + is_enabled_code + ); + } + } + } + } + + pub fn new_activity_id() -> Result { + unsafe { + let mut guid: wdk_sys::GUID = core::mem::zeroed(); + let error = wdk_sys::ntddk::EtwActivityIdControl( + evntprov::EVENT_ACTIVITY_CTRL_CREATE_ID, + &mut guid, + ); + if error == 0 { + Ok(guid.into()) + } else { + Err(Error::WindowsError(error as u32)) + } + } + } +} + +impl EtwDriverProvider { + /// Registers an event provider with ETW. + /// + /// The implementation uses `[EventWriteEx](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventwriteex)`. + pub fn new(provider_id: &GUID) -> Result { + #[cfg(target_os = "windows")] + { + unsafe { + let mut stable = Box::pin(StableProviderData { + max_level: AtomicU8::new(0), + }); + let mut handle: wdk_sys::REGHANDLE = 0; + let stable_ptr: &mut StableProviderData = &mut stable; + let error = wdk_sys::ntddk::EtwRegister( + provider_id as *const _ as *const wdk_sys::GUID, + Some(enable_callback), + stable_ptr as *mut StableProviderData as wdk_sys::PVOID, + &mut handle, + ); + if error != 0 { + Err(Error::WindowsError(error as u32)) + } else { + Ok(EtwDriverProvider { handle, stable }) + } + } + } + #[cfg(not(target_os = "windows"))] + { + Ok(EtwDriverProvider {}) + } + } + + /// See TraceLoggingRegisterEx in traceloggingprovider.h. + /// This registers provider metadata. + pub fn register_provider_metadata(&mut self, provider_metadata: &[u8]) -> Result<(), Error> { + #[cfg(target_os = "windows")] + { + unsafe { + let error = wdk_sys::ntddk::EtwSetInformation( + self.handle, + 2, + provider_metadata.as_ptr() as wdk_sys::PVOID, + u32::try_from(provider_metadata.len()).unwrap(), + ); + if error != 0 { + Err(Error::WindowsError(error as u32)) + } else { + #[cfg(feature = "dev")] + { + eprintln!("register_provider_metadata: succeeded"); + } + Ok(()) + } + } + } + #[cfg(not(target_os = "windows"))] + { + Ok(()) + } + } + + /// Registers provider traits for a provider. + /// + /// ETW providers should not call this function directly. It is automatically + /// called by the provider code that is generated by `win_etw_macros`. + /// + /// See [Provider Traits](https://docs.microsoft.com/en-us/windows/win32/etw/provider-traits). + pub fn set_provider_traits(&mut self, provider_traits: &[u8]) -> Result<(), Error> { + #[cfg(target_os = "windows")] + { + unsafe { + let error = wdk_sys::ntddk::EtwSetInformation( + self.handle, + wdk_sys::_EVENT_INFO_CLASS::EventProviderSetTraits, + provider_traits.as_ptr() as *mut u8 as wdk_sys::PVOID, + u32::try_from(provider_traits.len()).unwrap(), + ); + if error != 0 { + #[cfg(feature = "dev")] + { + eprintln!("EventSetInformation failed for provider traits"); + } + return Err(Error::WindowsError(error as u32)); + } + } + Ok(()) + } + #[cfg(not(target_os = "windows"))] + { + Ok(()) + } + } +} + +impl Drop for EtwDriverProvider { + fn drop(&mut self) { + #[cfg(target_os = "windows")] + { + unsafe { + // Nothing we can do if this fails. + let _ = wdk_sys::ntddk::EtwUnregister(self.handle); + } + } + } +} + +unsafe impl Send for EtwDriverProvider {} +unsafe impl Sync for EtwDriverProvider {} + +/// Allows an application to enter a nested activity scope. This creates a new activity ID, +/// sets this activity ID as the current activity ID of the current thread, and then runs the +/// provided function. After the function finishes, it restores the activity ID of the calling +/// thread (even if a panic occurs). +/// +/// See `[EventActivityIdControl](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventactivityidcontrol)`. +#[inline(always)] +pub fn with_activity R, R>(f: F) -> R { + #[cfg(target_os = "windows")] + { + let mut previous_activity_id: GUID = Default::default(); + + let mut restore = RestoreActivityHolder { + previous_activity_id: None, + }; + + unsafe { + let result = wdk_sys::ntddk::EtwActivityIdControl( + evntprov::EVENT_ACTIVITY_CTRL_CREATE_SET_ID, + &mut previous_activity_id as *mut _ as *mut wdk_sys::GUID, + ); + if NT_SUCCESS(result) { + restore.previous_activity_id = Some(previous_activity_id); + } else { + // Failed to create/replace the activity ID. There is not much we can do about this. + } + } + + let result = f(); + // RestoreActivityHolder::drop() will run, even if f() panics, and will restore the + // activity ID of the current thread. + drop(restore); + result + } + + #[cfg(not(target_os = "windows"))] + { + f() + } +} + +struct RestoreActivityHolder { + previous_activity_id: Option, +} + +impl Drop for RestoreActivityHolder { + fn drop(&mut self) { + #[cfg(target_os = "windows")] + { + unsafe { + if let Some(previous_activity_id) = self.previous_activity_id.as_ref() { + let _ = wdk_sys::ntddk::EtwActivityIdControl( + evntprov::EVENT_ACTIVITY_CTRL_SET_ID, + previous_activity_id as *const GUID as *mut wdk_sys::GUID, + ); + } + } + } + } +} diff --git a/win_etw_provider/src/guid.rs b/win_etw_provider/src/guid.rs index f656440..68a2a95 100644 --- a/win_etw_provider/src/guid.rs +++ b/win_etw_provider/src/guid.rs @@ -97,6 +97,19 @@ impl From for GUID { } } +#[cfg(feature = "windows_drivers")] +impl From for GUID { + fn from(value: wdk_sys::GUID) -> Self { + Self { + data1: value.Data1, + data2: value.Data2, + data3: value.Data3, + data4: value.Data4, + } + } +} + + #[cfg(feature = "uuid")] impl From for GUID { fn from(value: uuid::Uuid) -> Self { diff --git a/win_etw_provider/src/lib.rs b/win_etw_provider/src/lib.rs index f7a71da..08c45ae 100644 --- a/win_etw_provider/src/lib.rs +++ b/win_etw_provider/src/lib.rs @@ -10,6 +10,10 @@ extern crate alloc; mod guid; mod provider; +#[cfg(feature = "windows_drivers")] +mod driver_provider; +#[cfg(feature = "windows_drivers")] +pub use driver_provider::EtwDriverProvider; pub mod types; From 1d5ead32c1a931ce5b73b5a88eb2e473bcaae175 Mon Sep 17 00:00:00 2001 From: NateD-MSFT <34494373+NateD-MSFT@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:09:36 -0700 Subject: [PATCH 2/8] Remove duplicate trace_logging_events function --- win_etw_macros/src/lib.rs | 478 ++---------------------------------- win_etw_macros/src/tests.rs | 2 +- 2 files changed, 26 insertions(+), 454 deletions(-) diff --git a/win_etw_macros/src/lib.rs b/win_etw_macros/src/lib.rs index e49fd39..3505b15 100644 --- a/win_etw_macros/src/lib.rs +++ b/win_etw_macros/src/lib.rs @@ -224,460 +224,27 @@ pub fn trace_logging_provider( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { // let logging_trait = parse_macro_input!(input as syn::ItemTrait); - #[cfg(not(feature = "windows_drivers"))] - let output = trace_logging_events_core(attr.into(), input.into()); - #[cfg(feature = "windows_drivers")] - let output = trace_logging_events_core_drivers(attr.into(), input.into()); + let output = trace_logging_events_core::(attr.into(), input.into()); output.into() } -fn trace_logging_events_core(attr: TokenStream, item_tokens: TokenStream) -> TokenStream { - let mut errors: Vec = Vec::new(); - - let logging_trait: syn::ItemTrait = match syn::parse2(item_tokens) { - Err(e) => { - return e.to_compile_error(); - } - Ok(syn::Item::Trait(t)) => t, - Ok(syn::Item::Mod(m)) => { - return Error::new_spanned(&m, "Modules are not yet supported, but thanks for asking.") - .to_compile_error(); - } - Ok(unrecognized) => { - return Error::new_spanned( - &unrecognized, - "The #[trace_logging_provider] attribute cannot be used with this kind of item.", - ) - .to_compile_error(); - } - }; - - let provider_attrs: ProviderAttributes = match syn::parse2(attr) { - Ok(p) => p, - Err(e) => { - errors.push(e); - ProviderAttributes::default() - } - }; - - // provider_ident is the identifier used in Rust source code for the generated provider type. - let provider_ident = &logging_trait.ident; - let provider_ident_string = provider_ident.to_string(); - - let wk = WellKnownTypes::new(); - - let mut output = TokenStream::new(); - - let provider_metadata_ident = Ident::new( - &format!("{}_PROVIDER_METADATA", provider_ident_string), - provider_ident.span(), - ); - - // Create the provider metadata. - // provider_ident is the identifier used in Rust source code for the generated code. - // When writing the provider metadata, we allow the user to specify a different name to ETW. - let provider_name = provider_attrs - .provider_name - .as_ref() - .unwrap_or(&provider_ident_string); - output.extend(create_provider_metadata( - provider_name, - &provider_metadata_ident, - )); - - // Definitions that go inside the "impl MyProvider { ... }" block. - let mut provider_impl_items = TokenStream::new(); - - // To make this simple, we either require all events to be labeled - // with an event id or autogenerated. - let mut event_ids_auto_generated = true; - let mut event_id_mappings = HashMap::new(); - - for (method_index, method) in logging_trait - .items - .iter() - .filter_map(|item| { - if let syn::TraitItem::Method(m) = item { - Some(m) - } else { - None - } - }) - .enumerate() - { - let event_index = method_index as u16; - - // Check requirements for the method signature. If the requirements are not met, we - // emit an error but keep going. This allows us to report as many errors as possible in - // each build, rather than having errors "unlocked" one by one. - if method.sig.asyncness.is_some() { - errors.push(Error::new_spanned( - method, - "Async event methods are not supported.", - )); - } - if method.sig.unsafety.is_some() { - errors.push(Error::new_spanned( - method, - "Event methods should not be marked unsafe.", - )); - } - if !method.sig.generics.params.is_empty() { - errors.push(Error::new_spanned( - method, - "Generic event methods are not supported.", - )); - } - match &method.sig.output { - syn::ReturnType::Default => {} - _ => { - errors.push(Error::new_spanned( - method, - "Event methods must not return data.", - )); - } - } - if let Some(block) = method.default.as_ref() { - errors.push(Error::new_spanned( - block, - "Event methods must not contain an implementation.", - )); - } - - let event_name: String = method.sig.ident.to_string(); - - // Here we build the data descriptor array. The data descriptor array is constructed on - // the stack, and has a statically-known size. It contains pointers to data fields. The - // event metadata describes the order and type of the data pointed-to by the data - // descriptors. - // - // For self-describing events (TraceLogging), the first two items in the data descriptor - // array identify the provider metadata and the event metadata. - let mut data_descriptor_array = TokenStream::new(); - - // See comments in traceloggingprovider.h, around line 2300, which describe the - // encoding of the event mdata. - let mut event_metadata: Vec = Vec::new(); - event_metadata.push(parse_quote! { 0 }); // reserve space for the size (byte 0) - event_metadata.push(parse_quote! { 0 }); // reserve space for the size (byte 1) - event_metadata.push(parse_quote! { 0 }); // no extensions - append_utf8_str_chars(&mut event_metadata, &event_name); - - // Some fields require running some code before building the data descriptors, so we - // collect statements here. - let mut statements = TokenStream::new(); - - // Each parameter (except for &self) becomes an event field. - let mut found_receiver = false; - - // sig is the function signature for the function that we will generate for this event. - let mut sig = method.sig.clone(); - - for param in sig.inputs.iter_mut() { - let param_span = param.span(); - match param { - FnArg::Receiver(_) => { - errors.push(Error::new_spanned(param, "Event methods should not provide any receiver arguments (&self, &mut self, etc.).")); - found_receiver = true; - } - - FnArg::Typed(param_typed) => { - let mut event_attr: Option = None; - param_typed.attrs.retain(|a| { - if a.path == parse_quote!(event) { - event_attr = Some(a.clone()); - false - } else if a.path == parse_quote!(doc) { - true - } else { - errors.push(Error::new_spanned( - a, - "This attribute is not permitted on event fields.", - )); - true - } - }); - let param_name: &Ident = match &*param_typed.pat { - syn::Pat::Ident(ref name) => &name.ident, - _ => { - errors.push(Error::new( - param.span(), - "Only ordinary parameter patterns are supported on event methods.", - )); - continue; - } - }; - - if parse_event_field( - &mut errors, - &wk, - event_attr.as_ref(), - param_span, - param_name, - &mut param_typed.ty, - &mut data_descriptor_array, - &mut event_metadata, - &mut statements, - ) - .is_err() - { - errors.push(Error::new_spanned( - ¶m, - "This type is not supported for event parameters.", - )); - } - } - } - } - - // We require that every function declare a '&self' receiver parameter. - if !found_receiver { - sig.inputs.insert(0, parse_quote!(&self)); - } - - // Insert the "options" argument. - sig.inputs.insert( - 1, - parse_quote!(options: core::option::Option<&::win_etw_provider::EventOptions>), - ); - - // Now that we have processed all parameters ("fields"), we can finish constructing - // the per-event metadata. - let event_metadata_len = event_metadata.len(); - if event_metadata_len > 0xffff { - errors.push(Error::new( - method.span(), - "Event metadata is too large to encode; reduce the complexity of this event.", - )); - continue; - } - let event_metadata_len_b0 = (event_metadata_len & 0xff) as u8; - let event_metadata_len_b1 = (event_metadata_len >> 8) as u8; - event_metadata[0] = parse_quote! { #event_metadata_len_b0 }; - event_metadata[1] = parse_quote! { #event_metadata_len_b1 }; - - let event_attrs = parse_event_attributes(&mut errors, &method.sig.ident, &method.attrs); - - // Generate the event descriptor for this event. - // This is a static variable. The name is exactly the name of the event. - let event_level = event_attrs.level; - let event_opcode = event_attrs.opcode; - let event_task = event_attrs.task; - let potential_event_id = event_attrs.event_id; - let event_keyword = event_attrs - .keyword - .as_ref() - .cloned() - .unwrap_or_else(|| parse_quote!(0)); - - // We use the first entry to see if we have user provided IDs - // or we are generating one. - if event_index == 0 { - event_ids_auto_generated = potential_event_id.is_none(); - } - - // We have some events with user provided id and some without. - if event_ids_auto_generated != potential_event_id.is_none() { - errors.push(Error::new( - method.span(), - "Event ids must be set for all events, or for none.", - )); - } - - let event_id = potential_event_id.unwrap_or(event_index); - - // Only care about #[event(id = #)] types so we don't get - // confusing messages when forget to add an id for some - // event. - if potential_event_id.is_some() { - let identifier = method.sig.ident.to_string(); - if let Some(previous) = event_id_mappings.get(&event_id) { - errors.push(Error::new( - method.span(), - format!( - "Event id {} has already been defined on {}.", - event_id, previous - ), - )); - } - event_id_mappings.insert(event_id, identifier); - } - - // an expression which generates EventDescriptor - let event_descriptor = quote! { - ::win_etw_provider::EventDescriptor { - id: #event_id, - version: 0, - channel: 11, - level: #event_level, - opcode: #event_opcode, - task: #event_task, - keyword: #event_keyword, - }; - }; - - let event_attrs_method_attrs = &event_attrs.method_attrs; - - // Generate the `${name}_is_enabled` function for this event. - // We do not use ident_suffix() because this is not a private identifier. - let event_is_enabled_name = Ident::new( - &format!("{}_is_enabled", method.sig.ident), - method.sig.ident.span(), - ); - - // Build the method that implements this event. - provider_impl_items.extend(quote!{ - #( #event_attrs_method_attrs )* - #[cfg_attr(not(target_os = "windows"), allow(unused_variables))] - pub #sig - { - #[cfg(target_os = "windows")] - { - use ::win_etw_provider::EventDataDescriptor; - - // This places the EVENT_METADATA into a read-only linker section, properly - // ordered with respect to TRACE_LOGGING_METADATA and other related sections. - #[link_section = ".rdata$etw1"] - #[used] - static EVENT_METADATA: [u8; #event_metadata_len] = [ #( #event_metadata, )* ]; - - let mut event_descriptor: ::win_etw_provider::EventDescriptor = #event_descriptor; - - if let Some(opts) = options { - if let Some(level) = opts.level { - event_descriptor.level = level; - } - } - - #statements - - let data_descriptors = [ - EventDataDescriptor::for_provider_metadata(&#provider_metadata_ident[..]), - EventDataDescriptor::for_event_metadata(&EVENT_METADATA[..]), - #data_descriptor_array - ]; - ::win_etw_provider::Provider::write(&self.provider, - options, - &event_descriptor, - &data_descriptors, - ); - } - } - - pub fn #event_is_enabled_name(&self, level: ::core::option::Option<::win_etw_provider::Level>) -> bool { - #[cfg(target_os = "windows")] - { - let mut event_descriptor: ::win_etw_provider::EventDescriptor = #event_descriptor; - if let Some(level) = level { - event_descriptor.level = level; - } - - ::win_etw_provider::Provider::is_event_enabled( - &self.provider, - &event_descriptor) - } - #[cfg(not(target_os = "windows"))] - { - false - } - } - }); - } - - // We propagate the visibility of the trait definition to the structure definition. - let vis = logging_trait.vis.clone(); - let provider_guid = match provider_attrs.uuid { - Some(uuid) => uuid, - None => etw_event_source_guid(provider_name), - }; - let provider_guid_const = uuid_to_expr(&provider_guid); - - // If the input item has doc attributes, then carry them over to the output type. - let doc_path: syn::Path = parse_quote!(doc); - let provider_doc_attrs = logging_trait - .attrs - .iter() - .filter(|a| a.path == doc_path) - .collect::>(); - - // Build a code fragment that registers the provider traits. - let register_traits: TokenStream = create_register_provider_traits( - &provider_name, - provider_attrs.provider_group_guid.as_ref(), - ); - - output.extend(quote! { - #( #provider_doc_attrs )* - #vis struct #provider_ident { - provider: ::core::option::Option<::win_etw_provider::EtwProvider>, - } - - impl #provider_ident { - /// Creates (registers) a new instance of this provider. If registration fails, - /// returns a "null" provider. This prevents problems with event logging from - /// disrupting the normal operation of applications. - /// - /// On non-Windows platforms, this function always returns a null provider. - /// - /// Creating an event source is a costly operation, because it requires contacting the - /// event manager, allocating event buffers, potentially receiving callbacks from - /// event consumers, etc. Applications should only create event sources during process - /// initialization, and should always reuse them, never re-creating them. - pub fn new() -> Self { - let provider = match ::win_etw_provider::EtwProvider::new(&Self::PROVIDER_GUID) { - Ok(mut provider) => { - #[cfg(target_os = "windows")] - { - #register_traits - } - - Some(provider) - } - Err(_) => None, - }; - Self { provider } - } - - /// Creates (registers) a new instance of this provider. If registration fails, then - /// this method returns a "null" provider. - /// - /// On non-Windows platforms, this function always returns `Ok`, containing a null - /// provider. - /// - /// Creating an event source is a costly operation, because it requires contacting the - /// event manager, allocating event buffers, potentially receiving callbacks from - /// event consumers, etc. Applications should only create event sources during process - /// initialization, and should always reuse them, never re-creating them. - pub fn new_err() -> ::core::result::Result { - Ok(Self { - provider: Some(::win_etw_provider::EtwProvider::new(&Self::PROVIDER_GUID)?), - }) - } - - /// Creates a new "null" instance of the provider. All events written to this provider - /// are discarded. - pub fn null() -> Self { - Self { provider: None } - } - - #[allow(unused_variable)] - pub const PROVIDER_GUID: ::win_etw_provider::GUID = #provider_guid_const; - pub const PROVIDER_NAME: &'static str = #provider_name; - } - - // We intentionally generate identifiers that are not snake-case. - #[allow(non_snake_case)] - impl #provider_ident { - #provider_impl_items - } - }); - - output.extend(errors.into_iter().map(|e| e.to_compile_error())); - output +/// Allows you to create ETW Trace Logging Providers in a Windows driver kernel-mode context. See the module docs for more detailed +/// instructions for this macro. +#[cfg(feature = "windows_drivers")] +#[proc_macro_attribute] +pub fn trace_logging_provider_kernel( + attr: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + // let logging_trait = parse_macro_input!(input as syn::ItemTrait); + let output = trace_logging_events_core::(attr.into(), input.into()); + output.into() } - -fn trace_logging_events_core_drivers(attr: TokenStream, item_tokens: TokenStream) -> TokenStream { +fn trace_logging_events_core( + attr: TokenStream, + item_tokens: TokenStream, +) -> TokenStream { let mut errors: Vec = Vec::new(); let logging_trait: syn::ItemTrait = match syn::parse2(item_tokens) { @@ -1052,10 +619,16 @@ fn trace_logging_events_core_drivers(attr: TokenStream, item_tokens: TokenStream provider_attrs.provider_group_guid.as_ref(), ); + let provider_type = if KERNEL_MODE { + quote! { win_etw_provider::EtwDriverProvider } + } else { + quote! { win_etw_provider::EtwProvider } + }; + output.extend(quote! { #( #provider_doc_attrs )* #vis struct #provider_ident { - provider: ::core::option::Option<::win_etw_provider::EtwDriverProvider>, + provider: ::core::option::Option<#provider_type>, } impl #provider_ident { @@ -1070,7 +643,7 @@ fn trace_logging_events_core_drivers(attr: TokenStream, item_tokens: TokenStream /// event consumers, etc. Applications should only create event sources during process /// initialization, and should always reuse them, never re-creating them. pub fn new() -> Self { - let provider = match ::win_etw_provider::EtwDriverProvider::new(&Self::PROVIDER_GUID) { + let provider = match #provider_type::new(&Self::PROVIDER_GUID) { Ok(mut provider) => { #[cfg(target_os = "windows")] { @@ -1096,7 +669,7 @@ fn trace_logging_events_core_drivers(attr: TokenStream, item_tokens: TokenStream /// initialization, and should always reuse them, never re-creating them. pub fn new_err() -> ::core::result::Result { Ok(Self { - provider: Some(::win_etw_provider::EtwDriverProvider::new(&Self::PROVIDER_GUID)?), + provider: Some(#provider_type::new(&Self::PROVIDER_GUID)?), }) } @@ -1122,7 +695,6 @@ fn trace_logging_events_core_drivers(attr: TokenStream, item_tokens: TokenStream output } - /// Creates a fragment of code (statements) which will register the /// provider traits for this provider. // diff --git a/win_etw_macros/src/tests.rs b/win_etw_macros/src/tests.rs index fbd8f30..e73c4d5 100644 --- a/win_etw_macros/src/tests.rs +++ b/win_etw_macros/src/tests.rs @@ -47,7 +47,7 @@ impl syn::parse::Parse for CompileErrors { } fn test_worker(attrs: TokenStream, input: TokenStream, expected_errors: &[&'static str]) { - let output = trace_logging_events_core(attrs, input); + let output = trace_logging_events_core::(attrs, input); // Set WIN_ETW_SHOW_OUTPUT=1 (or = anything at all) to see the output of // the trace_logging_provider macro for unit tests. This is useful during From ddf90d8db5647cf661a4697ba02adf22a0607f72 Mon Sep 17 00:00:00 2001 From: NateD-MSFT <34494373+NateD-MSFT@users.noreply.github.com> Date: Thu, 11 Jul 2024 16:03:21 -0700 Subject: [PATCH 3/8] Add default "windows_apps" feature Without this, building/testing with --all-features fails. TBD if there is a better solution. --- win_etw_macros/Cargo.toml | 3 ++- win_etw_macros/src/lib.rs | 2 +- win_etw_provider/Cargo.toml | 3 ++- win_etw_provider/src/guid.rs | 2 +- win_etw_provider/src/lib.rs | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/win_etw_macros/Cargo.toml b/win_etw_macros/Cargo.toml index e984c48..5b5b9e9 100644 --- a/win_etw_macros/Cargo.toml +++ b/win_etw_macros/Cargo.toml @@ -22,5 +22,6 @@ uuid = { version = "^1.3", features = ["v5"]} sha1_smol = "1.0.0" [features] -default = [] +default = ["windows_apps"] +windows_apps = [] windows_drivers = [] \ No newline at end of file diff --git a/win_etw_macros/src/lib.rs b/win_etw_macros/src/lib.rs index 3505b15..3624a2b 100644 --- a/win_etw_macros/src/lib.rs +++ b/win_etw_macros/src/lib.rs @@ -230,7 +230,7 @@ pub fn trace_logging_provider( /// Allows you to create ETW Trace Logging Providers in a Windows driver kernel-mode context. See the module docs for more detailed /// instructions for this macro. -#[cfg(feature = "windows_drivers")] +#[cfg(all(not(feature = "windows_apps"), feature = "windows_drivers"))] #[proc_macro_attribute] pub fn trace_logging_provider_kernel( attr: proc_macro::TokenStream, diff --git a/win_etw_provider/Cargo.toml b/win_etw_provider/Cargo.toml index ac1a02b..1a72547 100644 --- a/win_etw_provider/Cargo.toml +++ b/win_etw_provider/Cargo.toml @@ -27,7 +27,8 @@ uuid = "1" [features] std = [] -default = ["no_std"] +default = ["no_std", "windows_apps"] +windows_apps = [] no_std = [] windows_drivers = ["wdk-sys", "no_std"] # dev is used only for development diff --git a/win_etw_provider/src/guid.rs b/win_etw_provider/src/guid.rs index 68a2a95..f915cc6 100644 --- a/win_etw_provider/src/guid.rs +++ b/win_etw_provider/src/guid.rs @@ -97,7 +97,7 @@ impl From for GUID { } } -#[cfg(feature = "windows_drivers")] +#[cfg(all(not(feature = "windows_apps"), feature = "windows_drivers"))] impl From for GUID { fn from(value: wdk_sys::GUID) -> Self { Self { diff --git a/win_etw_provider/src/lib.rs b/win_etw_provider/src/lib.rs index 08c45ae..1bdc56d 100644 --- a/win_etw_provider/src/lib.rs +++ b/win_etw_provider/src/lib.rs @@ -10,9 +10,9 @@ extern crate alloc; mod guid; mod provider; -#[cfg(feature = "windows_drivers")] +#[cfg(all(not(feature = "windows_apps"), feature = "windows_drivers"))] mod driver_provider; -#[cfg(feature = "windows_drivers")] +#[cfg(all(not(feature = "windows_apps"), feature = "windows_drivers"))] pub use driver_provider::EtwDriverProvider; pub mod types; From 185b9482f35b2ef6c30b06bf37364e5d5e09cf1d Mon Sep 17 00:00:00 2001 From: NateD-MSFT <34494373+NateD-MSFT@users.noreply.github.com> Date: Fri, 12 Jul 2024 14:54:58 -0700 Subject: [PATCH 4/8] Add some documentation --- win_etw_macros/src/lib.rs | 18 +++++++++++++----- win_etw_provider/src/driver_provider.rs | 4 ++++ win_etw_provider/src/guid.rs | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/win_etw_macros/src/lib.rs b/win_etw_macros/src/lib.rs index 3624a2b..43c3815 100644 --- a/win_etw_macros/src/lib.rs +++ b/win_etw_macros/src/lib.rs @@ -25,10 +25,6 @@ //! win_etw_provider = "0.1.*" //! ``` //! -//! `win_etw_macros` contains the procedural macro that generates eventing code. -//! `win_etw_provider` contains library code that is called by the code that is generated by -//! `win_etw_macros`. -//! //! ### Define the event provider and its events //! //! Add a trait definition to your source code and annotate it with the `#[trace_logging_provider]` @@ -182,6 +178,19 @@ //! //! There are other tools, such as the Windows Performance Recorder, which can capture ETW events. //! +//! # Using this crate for kernel-mode driver development +//! +//! This crate can be used when developing Windows device drivers in conjunction with the [wdk](https://crates.io/crates/wdk) crate. +//! +//! If you are developing for kernel-mode, you should add these dependencies to your crate: +//! ```text +//! [dependencies] +//! win_etw_macros = {version = "0.1.*", default-features = false, features = ["windows_drivers"]} +//! win_etw_provider = {"0.1.*", default-features = false, features = ["windows_drivers"]} +//! ``` +//! +//! Then apply the `#[trace_logging_provider_kernel]` macro rather than `#[trace_logging_provider]`. +//! //! # References //! * [Event Tracing for Windows (ETW) Simplified](https://support.microsoft.com/en-us/help/2593157/event-tracing-for-windows-etw-simplified) //! * [TraceLogging for Event Tracing for Windows (ETW)](https://docs.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-portal) @@ -236,7 +245,6 @@ pub fn trace_logging_provider_kernel( attr: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - // let logging_trait = parse_macro_input!(input as syn::ItemTrait); let output = trace_logging_events_core::(attr.into(), input.into()); output.into() } diff --git a/win_etw_provider/src/driver_provider.rs b/win_etw_provider/src/driver_provider.rs index 4c5e362..1eae934 100644 --- a/win_etw_provider/src/driver_provider.rs +++ b/win_etw_provider/src/driver_provider.rs @@ -1,3 +1,7 @@ +//! Provides an ETW provider that uses Windows kernel-mode APIs and types to implement ETW tracing. +//! +//! This provider is designed to match the behavior of the standard user-mode ETW provider. + use crate::guid::GUID; use crate::EventDescriptor; use crate::Level; diff --git a/win_etw_provider/src/guid.rs b/win_etw_provider/src/guid.rs index f915cc6..317ae4a 100644 --- a/win_etw_provider/src/guid.rs +++ b/win_etw_provider/src/guid.rs @@ -97,7 +97,7 @@ impl From for GUID { } } -#[cfg(all(not(feature = "windows_apps"), feature = "windows_drivers"))] +#[cfg(all(target_os = "windows", not(feature = "windows_apps"), feature = "windows_drivers"))] impl From for GUID { fn from(value: wdk_sys::GUID) -> Self { Self { From 09e6f41904ac4196481bed863eb5a58bfbe7ee85 Mon Sep 17 00:00:00 2001 From: NateD-MSFT <34494373+NateD-MSFT@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:14:59 -0700 Subject: [PATCH 5/8] Format fixes --- win_etw_provider/src/driver_provider.rs | 2 +- win_etw_provider/src/guid.rs | 7 +++++-- win_etw_provider/src/lib.rs | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/win_etw_provider/src/driver_provider.rs b/win_etw_provider/src/driver_provider.rs index 1eae934..57faf14 100644 --- a/win_etw_provider/src/driver_provider.rs +++ b/win_etw_provider/src/driver_provider.rs @@ -1,5 +1,5 @@ //! Provides an ETW provider that uses Windows kernel-mode APIs and types to implement ETW tracing. -//! +//! //! This provider is designed to match the behavior of the standard user-mode ETW provider. use crate::guid::GUID; diff --git a/win_etw_provider/src/guid.rs b/win_etw_provider/src/guid.rs index 317ae4a..aba7d56 100644 --- a/win_etw_provider/src/guid.rs +++ b/win_etw_provider/src/guid.rs @@ -97,7 +97,11 @@ impl From for GUID { } } -#[cfg(all(target_os = "windows", not(feature = "windows_apps"), feature = "windows_drivers"))] +#[cfg(all( + target_os = "windows", + not(feature = "windows_apps"), + feature = "windows_drivers" +))] impl From for GUID { fn from(value: wdk_sys::GUID) -> Self { Self { @@ -109,7 +113,6 @@ impl From for GUID { } } - #[cfg(feature = "uuid")] impl From for GUID { fn from(value: uuid::Uuid) -> Self { diff --git a/win_etw_provider/src/lib.rs b/win_etw_provider/src/lib.rs index 1bdc56d..74ea693 100644 --- a/win_etw_provider/src/lib.rs +++ b/win_etw_provider/src/lib.rs @@ -8,10 +8,10 @@ extern crate alloc; -mod guid; -mod provider; #[cfg(all(not(feature = "windows_apps"), feature = "windows_drivers"))] mod driver_provider; +mod guid; +mod provider; #[cfg(all(not(feature = "windows_apps"), feature = "windows_drivers"))] pub use driver_provider::EtwDriverProvider; From 417b9024ff0199f68cdb4cc2d4467135dd49d806 Mon Sep 17 00:00:00 2001 From: NateD-MSFT <34494373+NateD-MSFT@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:38:34 -0700 Subject: [PATCH 6/8] Additional cleanup and removal of user-mode dependencies --- win_etw_provider/src/driver_provider.rs | 325 ++++++++++-------------- win_etw_provider/src/lib.rs | 12 +- 2 files changed, 139 insertions(+), 198 deletions(-) diff --git a/win_etw_provider/src/driver_provider.rs b/win_etw_provider/src/driver_provider.rs index 57faf14..782b4c0 100644 --- a/win_etw_provider/src/driver_provider.rs +++ b/win_etw_provider/src/driver_provider.rs @@ -1,6 +1,7 @@ -//! Provides an ETW provider that uses Windows kernel-mode APIs and types to implement ETW tracing. -//! -//! This provider is designed to match the behavior of the standard user-mode ETW provider. +//! Provides an ETW provider that uses Windows kernel-mode APIs and types to implement ETW tracing. +//! The `windows-driver` feature must be activated to compile. +//! This provider is designed to match the behavior of the standard user-mode ETW provider and is +//! largely copied from that module, with changes to use wdk_sys equivalent functions and values. use crate::guid::GUID; use crate::EventDescriptor; @@ -14,31 +15,19 @@ use core::ptr::null; use core::sync::atomic::{AtomicU8, Ordering::SeqCst}; use wdk_sys::NT_SUCCESS; -#[cfg(target_os = "windows")] use win_support::*; /// Generates a new activity ID. /// -/// This function is only implemented on Windows. On other platforms, it will always return `Err`. +/// This function is only implemented on Windows. pub fn new_activity_id() -> Result { - #[cfg(target_os = "windows")] - { - win_support::new_activity_id() - } - - #[cfg(not(target_os = "windows"))] - { - Err(Error::NotSupported) - } + win_support::new_activity_id() } /// Implements `Provider` by registering with ETW. pub struct EtwDriverProvider { - #[cfg(target_os = "windows")] handle: wdk_sys::REGHANDLE, - #[cfg(target_os = "windows")] - // #[allow(dead_code)] // Needed for lifetime control stable: Pin>, } @@ -50,48 +39,45 @@ impl Provider for EtwDriverProvider { descriptor: &EventDescriptor, data: &[EventDataDescriptor<'_>], ) { - #[cfg(target_os = "windows")] - { - unsafe { - let mut activity_id_ptr = null(); - let mut related_activity_id_ptr = null(); - - let mut event_descriptor = wdk_sys::EVENT_DESCRIPTOR { - Id: descriptor.id, - Version: descriptor.version, - Channel: descriptor.channel, - Level: descriptor.level.0, - Opcode: descriptor.opcode, - Task: descriptor.task, - Keyword: descriptor.keyword, - }; - - if let Some(options) = options { - if let Some(id) = options.activity_id.as_ref() { - activity_id_ptr = id as *const GUID as *const wdk_sys::GUID; - } - if let Some(id) = options.related_activity_id.as_ref() { - related_activity_id_ptr = id as *const GUID as *const wdk_sys::GUID; - } - if let Some(level) = options.level { - event_descriptor.Level = level.0; - } + unsafe { + let mut activity_id_ptr = null(); + let mut related_activity_id_ptr = null(); + + let mut event_descriptor = wdk_sys::EVENT_DESCRIPTOR { + Id: descriptor.id, + Version: descriptor.version, + Channel: descriptor.channel, + Level: descriptor.level.0, + Opcode: descriptor.opcode, + Task: descriptor.task, + Keyword: descriptor.keyword, + }; + + if let Some(options) = options { + if let Some(id) = options.activity_id.as_ref() { + activity_id_ptr = id as *const GUID as *const wdk_sys::GUID; } - - let error = wdk_sys::ntddk::EtwWriteEx( - self.handle, - &event_descriptor as *const wdk_sys::_EVENT_DESCRIPTOR, - 0, // filter - 0, // flags - activity_id_ptr, // activity id - related_activity_id_ptr, // related activity id - data.len() as u32, - data.as_ptr() as *mut wdk_sys::_EVENT_DATA_DESCRIPTOR, - ); - if !NT_SUCCESS(error) { - write_failed(error as u32) + if let Some(id) = options.related_activity_id.as_ref() { + related_activity_id_ptr = id as *const GUID as *const wdk_sys::GUID; + } + if let Some(level) = options.level { + event_descriptor.Level = level.0; } } + + let error = wdk_sys::ntddk::EtwWriteEx( + self.handle, + &event_descriptor as *const wdk_sys::_EVENT_DESCRIPTOR, + 0, // filter + 0, // flags + activity_id_ptr, // activity id + related_activity_id_ptr, // related activity id + data.len() as u32, + data.as_ptr() as *mut wdk_sys::_EVENT_DATA_DESCRIPTOR, + ); + if !NT_SUCCESS(error) { + write_failed(error as u32) + } } } @@ -99,34 +85,20 @@ impl Provider for EtwDriverProvider { // write_transfer fn is_enabled(&self, level: u8, keyword: u64) -> bool { - #[cfg(target_os = "windows")] - { - unsafe { wdk_sys::ntddk::EtwProviderEnabled(self.handle, level, keyword) != 0 } - } - #[cfg(not(target_os = "windows"))] - { - false - } + unsafe { wdk_sys::ntddk::EtwProviderEnabled(self.handle, level, keyword) != 0 } } fn is_event_enabled(&self, event_descriptor: &EventDescriptor) -> bool { - #[cfg(target_os = "windows")] - { - if false { - unsafe { - wdk_sys::ntddk::EtwEventEnabled( - self.handle, - event_descriptor as *const _ as *const wdk_sys::EVENT_DESCRIPTOR, - ) != 0 - } - } else { - let max_level = self.stable.as_ref().max_level.load(SeqCst); - event_descriptor.level.0 <= max_level + if false { + unsafe { + wdk_sys::ntddk::EtwEventEnabled( + self.handle, + event_descriptor as *const _ as *const wdk_sys::EVENT_DESCRIPTOR, + ) != 0 } - } - #[cfg(not(target_os = "windows"))] - { - false + } else { + let max_level = self.stable.as_ref().max_level.load(SeqCst); + event_descriptor.level.0 <= max_level } } } @@ -139,14 +111,10 @@ fn write_failed(_error: u32) { } } -#[cfg(target_os = "windows")] mod win_support { - pub use winapi::shared::evntprov; - pub use winapi::shared::evntrace; - pub use winapi::shared::winerror; - use super::*; + pub use winapi::shared::evntrace; /// This data is stored in a Box, so that it has a stable address. /// It is used to coordinate with ETW; ETW runs callbacks that need a stable pointer. @@ -155,7 +123,7 @@ mod win_support { pub(crate) max_level: AtomicU8, } - /// See [PENABLECALLBACK](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/nc-evntprov-penablecallback). + /// See [ETWENABLECALLBACK](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nc-wdm-etwenablecallback). pub(crate) unsafe extern "C" fn enable_callback( _source_id: *const wdk_sys::GUID, is_enabled_code: u32, @@ -225,7 +193,7 @@ mod win_support { unsafe { let mut guid: wdk_sys::GUID = core::mem::zeroed(); let error = wdk_sys::ntddk::EtwActivityIdControl( - evntprov::EVENT_ACTIVITY_CTRL_CREATE_ID, + wdk_sys::EVENT_ACTIVITY_CTRL_CREATE_ID, &mut guid, ); if error == 0 { @@ -240,62 +208,48 @@ mod win_support { impl EtwDriverProvider { /// Registers an event provider with ETW. /// - /// The implementation uses `[EventWriteEx](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventwriteex)`. + /// The implementation uses `[EtwRegister](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etwregister)`. pub fn new(provider_id: &GUID) -> Result { - #[cfg(target_os = "windows")] - { - unsafe { - let mut stable = Box::pin(StableProviderData { - max_level: AtomicU8::new(0), - }); - let mut handle: wdk_sys::REGHANDLE = 0; - let stable_ptr: &mut StableProviderData = &mut stable; - let error = wdk_sys::ntddk::EtwRegister( - provider_id as *const _ as *const wdk_sys::GUID, - Some(enable_callback), - stable_ptr as *mut StableProviderData as wdk_sys::PVOID, - &mut handle, - ); - if error != 0 { - Err(Error::WindowsError(error as u32)) - } else { - Ok(EtwDriverProvider { handle, stable }) - } + unsafe { + let mut stable = Box::pin(StableProviderData { + max_level: AtomicU8::new(0), + }); + let mut handle: wdk_sys::REGHANDLE = 0; + let stable_ptr: &mut StableProviderData = &mut stable; + let error = wdk_sys::ntddk::EtwRegister( + provider_id as *const _ as *const wdk_sys::GUID, + Some(enable_callback), + stable_ptr as *mut StableProviderData as wdk_sys::PVOID, + &mut handle, + ); + if error != 0 { + Err(Error::WindowsError(error as u32)) + } else { + Ok(EtwDriverProvider { handle, stable }) } } - #[cfg(not(target_os = "windows"))] - { - Ok(EtwDriverProvider {}) - } } /// See TraceLoggingRegisterEx in traceloggingprovider.h. /// This registers provider metadata. pub fn register_provider_metadata(&mut self, provider_metadata: &[u8]) -> Result<(), Error> { - #[cfg(target_os = "windows")] - { - unsafe { - let error = wdk_sys::ntddk::EtwSetInformation( - self.handle, - 2, - provider_metadata.as_ptr() as wdk_sys::PVOID, - u32::try_from(provider_metadata.len()).unwrap(), - ); - if error != 0 { - Err(Error::WindowsError(error as u32)) - } else { - #[cfg(feature = "dev")] - { - eprintln!("register_provider_metadata: succeeded"); - } - Ok(()) + unsafe { + let error = wdk_sys::ntddk::EtwSetInformation( + self.handle, + 2, + provider_metadata.as_ptr() as wdk_sys::PVOID, + u32::try_from(provider_metadata.len()).unwrap(), + ); + if error != 0 { + Err(Error::WindowsError(error as u32)) + } else { + #[cfg(feature = "dev")] + { + eprintln!("register_provider_metadata: succeeded"); } + Ok(()) } } - #[cfg(not(target_os = "windows"))] - { - Ok(()) - } } /// Registers provider traits for a provider. @@ -305,40 +259,30 @@ impl EtwDriverProvider { /// /// See [Provider Traits](https://docs.microsoft.com/en-us/windows/win32/etw/provider-traits). pub fn set_provider_traits(&mut self, provider_traits: &[u8]) -> Result<(), Error> { - #[cfg(target_os = "windows")] - { - unsafe { - let error = wdk_sys::ntddk::EtwSetInformation( - self.handle, - wdk_sys::_EVENT_INFO_CLASS::EventProviderSetTraits, - provider_traits.as_ptr() as *mut u8 as wdk_sys::PVOID, - u32::try_from(provider_traits.len()).unwrap(), - ); - if error != 0 { - #[cfg(feature = "dev")] - { - eprintln!("EventSetInformation failed for provider traits"); - } - return Err(Error::WindowsError(error as u32)); + unsafe { + let error = wdk_sys::ntddk::EtwSetInformation( + self.handle, + wdk_sys::_EVENT_INFO_CLASS::EventProviderSetTraits, + provider_traits.as_ptr() as *mut u8 as wdk_sys::PVOID, + u32::try_from(provider_traits.len()).unwrap(), + ); + if error != 0 { + #[cfg(feature = "dev")] + { + eprintln!("EventSetInformation failed for provider traits"); } + return Err(Error::WindowsError(error as u32)); } - Ok(()) - } - #[cfg(not(target_os = "windows"))] - { - Ok(()) } + Ok(()) } } impl Drop for EtwDriverProvider { fn drop(&mut self) { - #[cfg(target_os = "windows")] - { - unsafe { - // Nothing we can do if this fails. - let _ = wdk_sys::ntddk::EtwUnregister(self.handle); - } + unsafe { + // Nothing we can do if this fails. + let _ = wdk_sys::ntddk::EtwUnregister(self.handle); } } } @@ -351,40 +295,32 @@ unsafe impl Sync for EtwDriverProvider {} /// provided function. After the function finishes, it restores the activity ID of the calling /// thread (even if a panic occurs). /// -/// See `[EventActivityIdControl](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventactivityidcontrol)`. +/// See `[EtwActivityIdControl](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etwactivityidcontrol)`. #[inline(always)] pub fn with_activity R, R>(f: F) -> R { - #[cfg(target_os = "windows")] - { - let mut previous_activity_id: GUID = Default::default(); - - let mut restore = RestoreActivityHolder { - previous_activity_id: None, - }; - - unsafe { - let result = wdk_sys::ntddk::EtwActivityIdControl( - evntprov::EVENT_ACTIVITY_CTRL_CREATE_SET_ID, - &mut previous_activity_id as *mut _ as *mut wdk_sys::GUID, - ); - if NT_SUCCESS(result) { - restore.previous_activity_id = Some(previous_activity_id); - } else { - // Failed to create/replace the activity ID. There is not much we can do about this. - } + let mut previous_activity_id: GUID = Default::default(); + + let mut restore = RestoreActivityHolder { + previous_activity_id: None, + }; + + unsafe { + let result = wdk_sys::ntddk::EtwActivityIdControl( + wdk_sys::EVENT_ACTIVITY_CTRL_CREATE_SET_ID, + &mut previous_activity_id as *mut _ as *mut wdk_sys::GUID, + ); + if NT_SUCCESS(result) { + restore.previous_activity_id = Some(previous_activity_id); + } else { + // Failed to create/replace the activity ID. There is not much we can do about this. } - - let result = f(); - // RestoreActivityHolder::drop() will run, even if f() panics, and will restore the - // activity ID of the current thread. - drop(restore); - result } - #[cfg(not(target_os = "windows"))] - { - f() - } + let result = f(); + // RestoreActivityHolder::drop() will run, even if f() panics, and will restore the + // activity ID of the current thread. + drop(restore); + result } struct RestoreActivityHolder { @@ -393,15 +329,12 @@ struct RestoreActivityHolder { impl Drop for RestoreActivityHolder { fn drop(&mut self) { - #[cfg(target_os = "windows")] - { - unsafe { - if let Some(previous_activity_id) = self.previous_activity_id.as_ref() { - let _ = wdk_sys::ntddk::EtwActivityIdControl( - evntprov::EVENT_ACTIVITY_CTRL_SET_ID, - previous_activity_id as *const GUID as *mut wdk_sys::GUID, - ); - } + unsafe { + if let Some(previous_activity_id) = self.previous_activity_id.as_ref() { + let _ = wdk_sys::ntddk::EtwActivityIdControl( + wdk_sys::EVENT_ACTIVITY_CTRL_SET_ID, + previous_activity_id as *const GUID as *mut wdk_sys::GUID, + ); } } } diff --git a/win_etw_provider/src/lib.rs b/win_etw_provider/src/lib.rs index 74ea693..6ae2303 100644 --- a/win_etw_provider/src/lib.rs +++ b/win_etw_provider/src/lib.rs @@ -8,11 +8,19 @@ extern crate alloc; -#[cfg(all(not(feature = "windows_apps"), feature = "windows_drivers"))] +#[cfg(all( + not(feature = "windows_apps"), + feature = "windows_drivers", + target_os = "windows" +))] mod driver_provider; mod guid; mod provider; -#[cfg(all(not(feature = "windows_apps"), feature = "windows_drivers"))] +#[cfg(all( + not(feature = "windows_apps"), + feature = "windows_drivers", + target_os = "windows" +))] pub use driver_provider::EtwDriverProvider; pub mod types; From 0782b440815552e7aa28802bc2a78e69d5f148c3 Mon Sep 17 00:00:00 2001 From: NateD-MSFT <34494373+NateD-MSFT@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:18:20 -0700 Subject: [PATCH 7/8] Update README.md --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 8e328b6..4b130a5 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,22 @@ These tools can also be used to capture ETW events: There are other tools, such as the Windows Performance Recorder, which can capture ETW events. +## Building and using tracing for Windows kernel-mode drivers + +This repo has experimental support for ETW tracing in Windows kernel-mode drivers written in Rust (see the [windows-drivers-rs](https://github.com/microsoft/windows-drivers-rs) repo.) To allow this repo to be built without requiring the Windows Driver Kit in all scenarios, this `windows_drivers` feature - present in both the `win_etw_provider` and `win_etw_macros` crates - is **mutually exclusive** with the default `windows_apps` feature. + +To build this feature, you must install the Windows Driver Kit and LLVM to enable linking to driver DDIs. See [the windows-drivers-rs README](https://github.com/microsoft/windows-drivers-rs?tab=readme-ov-file#build-requirements) for details on on how to do this. + +Once this is done, the `windows_drivers` feature can be built. + +To use this feature in a Rust driver, when adding the `win_etw_provider` and `win_etw_macros` crates to your Cargo.toml file, make sure you set `default-features` to **false** in your cargo.toml file and activate the `windows_drivers` feature for both: + +```toml +[dependencies] +win_etw_macros = { version = "^0.2.1", default-features = false, features = "windows_drivers" } +win_etw_provider = { version = "^0.2.1", default-features = false, features = "windows_drivers" } +``` + ## Ideas for improvement * Better handling of per-event overrides, rather than using `Option<&EventOptions>`. From 25348e3abf80ea0393b1820ec4445b8570b31675 Mon Sep 17 00:00:00 2001 From: NateD-MSFT <34494373+NateD-MSFT@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:30:18 -0700 Subject: [PATCH 8/8] Fix some versions in readmes --- README.md | 4 ++-- win_etw_macros/src/lib.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4b130a5..ad81b8f 100644 --- a/README.md +++ b/README.md @@ -168,8 +168,8 @@ To use this feature in a Rust driver, when adding the `win_etw_provider` and `wi ```toml [dependencies] -win_etw_macros = { version = "^0.2.1", default-features = false, features = "windows_drivers" } -win_etw_provider = { version = "^0.2.1", default-features = false, features = "windows_drivers" } +win_etw_macros = { version = "^0.1.11", default-features = false, features = "windows_drivers" } +win_etw_provider = { version = "^0.1.11", default-features = false, features = "windows_drivers" } ``` ## Ideas for improvement diff --git a/win_etw_macros/src/lib.rs b/win_etw_macros/src/lib.rs index 43c3815..8f945ae 100644 --- a/win_etw_macros/src/lib.rs +++ b/win_etw_macros/src/lib.rs @@ -184,9 +184,9 @@ //! //! If you are developing for kernel-mode, you should add these dependencies to your crate: //! ```text -//! [dependencies] -//! win_etw_macros = {version = "0.1.*", default-features = false, features = ["windows_drivers"]} -//! win_etw_provider = {"0.1.*", default-features = false, features = ["windows_drivers"]} +//![dependencies] +//!win_etw_macros = { version = "^0.1.11", default-features = false, features = "windows_drivers" } +//!win_etw_provider = { version = "^0.1.11", default-features = false, features = "windows_drivers" } //! ``` //! //! Then apply the `#[trace_logging_provider_kernel]` macro rather than `#[trace_logging_provider]`.