diff --git a/README.md b/README.md index 8e328b6..ad81b8f 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.1.11", default-features = false, features = "windows_drivers" } +win_etw_provider = { version = "^0.1.11", default-features = false, features = "windows_drivers" } +``` + ## Ideas for improvement * Better handling of per-event overrides, rather than using `Option<&EventOptions>`. diff --git a/win_etw_macros/Cargo.toml b/win_etw_macros/Cargo.toml index 719407b..5b5b9e9 100644 --- a/win_etw_macros/Cargo.toml +++ b/win_etw_macros/Cargo.toml @@ -20,3 +20,8 @@ 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_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 b3f9643..8f945ae 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.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]`. +//! //! # 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) @@ -224,11 +233,26 @@ pub fn trace_logging_provider( 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()); + let output = trace_logging_events_core::(attr.into(), input.into()); output.into() } -fn trace_logging_events_core(attr: TokenStream, item_tokens: TokenStream) -> TokenStream { +/// 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(all(not(feature = "windows_apps"), feature = "windows_drivers"))] +#[proc_macro_attribute] +pub fn trace_logging_provider_kernel( + attr: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + 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) { @@ -603,10 +627,16 @@ fn trace_logging_events_core(attr: TokenStream, item_tokens: TokenStream) -> Tok 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::EtwProvider>, + provider: ::core::option::Option<#provider_type>, } impl #provider_ident { @@ -621,7 +651,7 @@ fn trace_logging_events_core(attr: TokenStream, item_tokens: TokenStream) -> Tok /// 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) { + let provider = match #provider_type::new(&Self::PROVIDER_GUID) { Ok(mut provider) => { #[cfg(target_os = "windows")] { @@ -647,7 +677,7 @@ fn trace_logging_events_core(attr: TokenStream, item_tokens: TokenStream) -> Tok /// 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)?), + provider: Some(#provider_type::new(&Self::PROVIDER_GUID)?), }) } 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 diff --git a/win_etw_provider/Cargo.toml b/win_etw_provider/Cargo.toml index 1635435..1a72547 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"] } @@ -27,7 +27,9 @@ 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 -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..782b4c0 --- /dev/null +++ b/win_etw_provider/src/driver_provider.rs @@ -0,0 +1,341 @@ +//! 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; +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; + +use win_support::*; + +/// Generates a new activity ID. +/// +/// This function is only implemented on Windows. +pub fn new_activity_id() -> Result { + win_support::new_activity_id() +} + +/// Implements `Provider` by registering with ETW. +pub struct EtwDriverProvider { + handle: wdk_sys::REGHANDLE, + + stable: Pin>, +} + +impl Provider for EtwDriverProvider { + #[inline(always)] + fn write( + &self, + options: Option<&crate::EventOptions>, + descriptor: &EventDescriptor, + data: &[EventDataDescriptor<'_>], + ) { + 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 { + unsafe { wdk_sys::ntddk::EtwProviderEnabled(self.handle, level, keyword) != 0 } + } + + fn is_event_enabled(&self, event_descriptor: &EventDescriptor) -> bool { + 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 + } + } +} + +#[inline(never)] +fn write_failed(_error: u32) { + #[cfg(feature = "dev")] + { + eprintln!("EventWrite failed: {}", _error); + } +} + +mod win_support { + + 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. + /// See `EventRegister` and the "enable callback". + pub(crate) struct StableProviderData { + pub(crate) max_level: AtomicU8, + } + + /// 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, + 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( + wdk_sys::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 `[EtwRegister](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-etwregister)`. + pub fn new(provider_id: &GUID) -> Result { + 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 }) + } + } + } + + /// See TraceLoggingRegisterEx in traceloggingprovider.h. + /// This registers provider metadata. + pub fn register_provider_metadata(&mut self, provider_metadata: &[u8]) -> Result<(), Error> { + 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(()) + } + } + } + + /// 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> { + 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(()) + } +} + +impl Drop for EtwDriverProvider { + fn drop(&mut self) { + 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 `[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 { + 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 +} + +struct RestoreActivityHolder { + previous_activity_id: Option, +} + +impl Drop for RestoreActivityHolder { + fn drop(&mut self) { + 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/guid.rs b/win_etw_provider/src/guid.rs index f656440..aba7d56 100644 --- a/win_etw_provider/src/guid.rs +++ b/win_etw_provider/src/guid.rs @@ -97,6 +97,22 @@ impl From for GUID { } } +#[cfg(all( + target_os = "windows", + not(feature = "windows_apps"), + 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..6ae2303 100644 --- a/win_etw_provider/src/lib.rs +++ b/win_etw_provider/src/lib.rs @@ -8,8 +8,20 @@ extern crate alloc; +#[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", + target_os = "windows" +))] +pub use driver_provider::EtwDriverProvider; pub mod types;