Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an EtwDriverProvider for Windows kernel-mode support. #39

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>`.
Expand Down
5 changes: 5 additions & 0 deletions win_etw_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
48 changes: 39 additions & 9 deletions win_etw_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]`
Expand Down Expand Up @@ -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]`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having a different proc macro, I think it would be better to have a single proc macro that generated code that would work for either user-mode or kernel-mode. If necessary, that code could use macros (not proc macros, ordinary macros) that are defined in win_etw_provider to handle some specialization for the environment.

I haven't skimmed the PR enough to see how different the output code is per environment, but my hope is that this can be unified.

//!
//! # 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)
Expand Down Expand Up @@ -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::<false>(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::<true>(attr.into(), input.into());
output.into()
}

fn trace_logging_events_core<const KERNEL_MODE: bool>(
attr: TokenStream,
item_tokens: TokenStream,
) -> TokenStream {
let mut errors: Vec<Error> = Vec::new();

let logging_trait: syn::ItemTrait = match syn::parse2(item_tokens) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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")]
{
Expand All @@ -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<Self, ::win_etw_provider::Error> {
Ok(Self {
provider: Some(::win_etw_provider::EtwProvider::new(&Self::PROVIDER_GUID)?),
provider: Some(#provider_type::new(&Self::PROVIDER_GUID)?),
})
}

Expand Down
2 changes: 1 addition & 1 deletion win_etw_macros/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<false>(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
Expand Down
8 changes: 5 additions & 3 deletions win_etw_provider/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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"] }
Expand All @@ -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 = []
Loading
Loading