├── .github
├── dependabot.yml
└── workflows
│ └── rust.yml
├── .gitignore
├── Cargo.toml
├── License.md
├── README.md
├── examples
├── dns.rs
├── file_trace.rs
├── kernel_trace.rs
├── multiple_providers.rs
├── sys_info.rs
└── user_trace.rs
├── src
├── lib.rs
├── native
│ ├── etw_types.rs
│ ├── etw_types
│ │ ├── event_record.rs
│ │ └── extended_data.rs
│ ├── evntrace.rs
│ ├── mod.rs
│ ├── pla.rs
│ ├── sddl.rs
│ ├── tdh.rs
│ ├── tdh_types.rs
│ ├── time.rs
│ └── version_helper.rs
├── parser.rs
├── property.rs
├── provider.rs
├── provider
│ ├── event_filter.rs
│ ├── kernel_providers.rs
│ └── trace_flags.rs
├── query.rs
├── schema.rs
├── schema_locator.rs
├── ser.rs
├── trace.rs
├── trace
│ └── callback_data.rs
├── traits.rs
└── utils.rs
└── tests
├── dns.rs
├── file_trace.rs
├── kernel_trace.rs
├── serialize.rs
├── tlg.rs
├── trace_lifetime.rs
└── utils
└── mod.rs
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "cargo" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on:
4 | push:
5 | branches: [ "master", "next_major_version" ]
6 | pull_request:
7 | branches: [ "master", "next_major_version" ]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | build-and-test:
14 | runs-on: windows-2022
15 | # Necessary so that doc issues (warnings) are catched as hard errors
16 | env:
17 | RUSTDOCFLAGS: -D warnings
18 | steps:
19 | - uses: actions/checkout@v3
20 | - uses: actions-rs/toolchain@v1
21 | with:
22 | profile: minimal
23 | toolchain: stable
24 | override: true
25 | # Cargo fmt check
26 | - uses: actions-rs/cargo@v1
27 | with:
28 | command: fmt
29 | args: --check
30 | # Cargo check
31 | - uses: actions-rs/cargo@v1
32 | with:
33 | command: check
34 | # Cargo doc
35 | - uses: actions-rs/cargo@v1
36 | with:
37 | command: doc
38 | args: --no-deps
39 | # Cargo test
40 | - uses: actions-rs/cargo@v1
41 | with:
42 | command: test
43 |
44 | clippy-on-diffs:
45 | runs-on: windows-2022
46 | steps:
47 | - uses: actions/checkout@v3
48 | - uses: actions-rs/toolchain@v1
49 | with:
50 | profile: minimal
51 | toolchain: stable
52 | override: true
53 | - uses: actions-rs/clippy-check@v1
54 | with:
55 | token: ${{ secrets.GITHUB_TOKEN }}
56 |
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | Cargo.lock
3 | /.idea
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "ferrisetw"
3 | version = "1.2.0"
4 | license = "MIT OR Apache-2.0"
5 | description = "Basically a KrabsETW rip-off written in Rust"
6 | keywords = ["etw", "krabsetw", "event", "tracing", "windows"]
7 | categories = ["api-bindings", "parsing"]
8 | authors = ["n4r1b", "daladim"]
9 | edition = "2018"
10 | repository = "https://github.com/n4r1b/ferrisetw"
11 |
12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13 |
14 | [features]
15 | # Enable the conversion of timestamps to time::OffsetDateTime
16 | time_rs = ["time"]
17 | serde = [ "dep:serde", "time?/serde", "time?/serde-human-readable" ]
18 |
19 | [dependencies]
20 | windows = { version = "0.57.0", features = [
21 | "Win32_Foundation",
22 | "Win32_Security_Authorization",
23 | "Win32_System_Com",
24 | "Win32_System_Diagnostics_Etw",
25 | "Win32_System_LibraryLoader",
26 | "Win32_System_Memory",
27 | "Win32_System_Performance",
28 | "Win32_System_SystemInformation",
29 | "Win32_System_SystemServices",
30 | "Win32_System_Time",
31 | ]}
32 | memoffset = "0.9"
33 | rand = "~0.8.0"
34 | once_cell = "1.14"
35 | num = "0.4"
36 | num-traits = "0.2"
37 | num-derive = "0.4"
38 | bitflags = "1.3.2"
39 | widestring = "1.0"
40 | zerocopy = "0.7"
41 | time = { version = "0.3", features = ["large-dates"], optional = true }
42 | serde = { version = "1.0", optional = true }
43 | # thiserror = "~1.0"
44 | # anyhow = "~1.0"
45 | log = "0.4"
46 |
47 | [dev-dependencies]
48 | env_logger = "0.11" # used in examples
49 | serde_json = "1.0"
50 | flexbuffers = "2.0"
51 | tracelogging = "1.2"
52 |
--------------------------------------------------------------------------------
/License.md:
--------------------------------------------------------------------------------
1 | Licensed uder the MIT OR Apache-2.0 license.
2 | Users may choose either license.
3 |
4 |
5 | # MIT License
6 |
7 | Copyright 2021-2022, authors of ferrisetw
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14 |
15 |
16 | # Apache-2.0 license
17 |
18 | Copyright 2021-2022, authors of ferrisetw
19 |
20 | Licensed under the Apache License, Version 2.0 (the "License");
21 | you may not use this file except in compliance with the License.
22 | You may obtain a copy of the License at
23 |
24 | http://www.apache.org/licenses/LICENSE-2.0
25 |
26 | Unless required by applicable law or agreed to in writing, software
27 | distributed under the License is distributed on an "AS IS" BASIS,
28 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29 | See the License for the specific language governing permissions and
30 | limitations under the License.
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FerrisETW 🦀
2 |
3 | This crate provides safe Rust abstractions over the ETW consumer APIs.
4 |
5 | It started as a [KrabsETW](https://github.com/microsoft/krabsetw/) rip-off written in Rust (hence the name [`Ferris`](https://rustacean.net/) 🦀).
6 | All credits go to the team at Microsoft who develop KrabsEtw, without it, this project probably wouldn't be a thing.
7 | Since version 1.0, the API and internal architecture of this crate is slightly diverging from `krabsetw`, so that it is more Rust-idiomatic.
8 |
9 | ## Examples
10 | You can find a examples within the
11 | [crate documentation on doc.rs](https://docs.rs/ferrisetw),
12 | as well as the [examples](./examples) and the [tests](./tests) folders.
13 |
14 | If you are familiar with KrabsETW you'll see that is very similar.
15 | In case you've never used KrabsETW before, the examples are very straight forward and should be easy to follow. If you have any issues don't hesitate in asking.
16 |
17 | ## Documentation
18 | This crate is documented at [docs.rs](https://docs.rs/crate/ferrisetw/latest).
19 |
20 | ## Notes
21 | - The project is still WIP.
22 | Feel free to report bugs, issues, feature requests, etc.
23 | Of course, contributing will be happily accepted!
24 |
25 |
26 | - The types available for parsing are those that implement the trait TryParse for Parser, basic types are already
27 | implemented. In the near future I'll add more :)
28 |
29 |
30 | - I tried to keep dependencies as minimal as possible, also you'll see I went with the new [windows-rs](https://github.com/microsoft/windows-rs) instead of
31 | using the [winapi](https://docs.rs/winapi/0.3.9/winapi/). This is a personal decision mainly because I believe the
32 | Windows bindings is going to be the "standard" to interact with the Windows API in the near future.
33 |
34 |
35 | ### Acknowledgments
36 | - First of all, the team at MS who develop KrabsETW!!
37 | - [Shaddy](https://github.com/Shaddy) for, pretty much, teaching me all the Rust I know 😃
38 | - [n4r1b](https://github.com/n4r1b) for creating this great crate
39 | - [daladim](https://github.com/daladim) for adding even more features
40 |
--------------------------------------------------------------------------------
/examples/dns.rs:
--------------------------------------------------------------------------------
1 | use std::sync::atomic::AtomicU32;
2 | use std::sync::atomic::Ordering;
3 | use std::time::Duration;
4 |
5 | use ferrisetw::parser::Parser;
6 | use ferrisetw::provider::Provider;
7 | use ferrisetw::provider::TraceFlags;
8 | use ferrisetw::schema::Schema;
9 | use ferrisetw::schema_locator::SchemaLocator;
10 | use ferrisetw::trace::UserTrace;
11 | use ferrisetw::EventRecord;
12 |
13 | static N_EVENTS: AtomicU32 = AtomicU32::new(0);
14 |
15 | fn dns_etw_callback(record: &EventRecord, schema_locator: &SchemaLocator) {
16 | N_EVENTS.fetch_add(1, Ordering::SeqCst);
17 |
18 | match schema_locator.event_schema(record) {
19 | Err(err) => {
20 | println!("Unable to get the ETW schema for a DNS event: {:?}", err);
21 | }
22 |
23 | Ok(schema) => {
24 | parse_etw_event(&schema, record);
25 | }
26 | }
27 | }
28 |
29 | fn parse_etw_event(schema: &Schema, record: &EventRecord) {
30 | let parser = Parser::create(record, schema);
31 | // let event_timestamp = filetime_to_datetime(schema.timestamp());
32 |
33 | let requested_fqdn: Option = parser.try_parse("QueryName").ok();
34 | let query_type: Option = parser.try_parse("QueryType").ok();
35 | let query_options: Option = parser.try_parse("QueryOptions").ok();
36 | let query_status: Option = parser
37 | .try_parse("QueryStatus")
38 | .or_else(|_err| parser.try_parse("Status"))
39 | .ok();
40 | let query_results: Option = parser.try_parse("QueryResults").ok();
41 |
42 | println!(
43 | "{:4} {:4} {:16} {:2} {:10} {}",
44 | record.event_id(),
45 | query_status.map(|u| u.to_string()).unwrap_or_default(),
46 | query_options
47 | .map(|u| format!("{:16x}", u))
48 | .unwrap_or_default(),
49 | query_type.map(|u| format!("{:2}", u)).unwrap_or_default(),
50 | requested_fqdn
51 | .map(|s| truncate(&s, 10).to_owned())
52 | .unwrap_or_default(),
53 | query_results
54 | .map(|s| truncate(&s, 30).to_owned())
55 | .unwrap_or_default(),
56 | );
57 | }
58 |
59 | fn main() {
60 | env_logger::init(); // this is optional. This makes the (rare) error logs of ferrisetw to be printed to stderr
61 |
62 | let dns_provider = Provider::by_guid("1c95126e-7eea-49a9-a3fe-a378b03ddb4d") // Microsoft-Windows-DNS-Client
63 | .add_callback(dns_etw_callback)
64 | .trace_flags(TraceFlags::EVENT_ENABLE_PROPERTY_PROCESS_START_KEY)
65 | .build();
66 |
67 | let trace = UserTrace::new()
68 | .enable(dns_provider)
69 | .start_and_process()
70 | .unwrap();
71 |
72 | println!("ID Status Options Ty Name Results");
73 |
74 | std::thread::sleep(Duration::new(20, 0));
75 |
76 | trace.stop().unwrap(); // This is not required, as it will automatically be stopped on Drop
77 | println!("Done: {:?} events", N_EVENTS);
78 | }
79 |
80 | fn truncate(s: &str, n: usize) -> &str {
81 | match s.get(..n) {
82 | Some(x) => x,
83 | None => s,
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/examples/file_trace.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | println!("See tests/file_trace.rs for an example that dumps to and reads from an ETL file");
3 | }
4 |
--------------------------------------------------------------------------------
/examples/kernel_trace.rs:
--------------------------------------------------------------------------------
1 | use ferrisetw::parser::Parser;
2 | use ferrisetw::provider::*;
3 | use ferrisetw::schema_locator::SchemaLocator;
4 | use ferrisetw::trace::*;
5 | use ferrisetw::EventRecord;
6 | use std::time::Duration;
7 |
8 | fn main() {
9 | env_logger::init(); // this is optional. This makes the (rare) error logs of ferrisetw to be printed to stderr
10 |
11 | let image_load_callback =
12 | |record: &EventRecord, schema_locator: &SchemaLocator| match schema_locator
13 | .event_schema(record)
14 | {
15 | Ok(schema) => {
16 | let opcode = record.opcode();
17 | if opcode == 10 {
18 | let name = schema.provider_name();
19 | println!("ProviderName: {}", name);
20 | let parser = Parser::create(record, &schema);
21 | // Fully Qualified Syntax for Disambiguation
22 | match parser.try_parse::("FileName") {
23 | Ok(filename) => println!("FileName: {}", filename),
24 | Err(err) => println!("Error: {:?} getting Filename", err),
25 | };
26 | }
27 | }
28 | Err(err) => println!("Error {:?}", err),
29 | };
30 |
31 | let provider = Provider::kernel(&kernel_providers::IMAGE_LOAD_PROVIDER)
32 | .add_callback(image_load_callback)
33 | .build();
34 |
35 | let kernel_trace = KernelTrace::new()
36 | .named(String::from("MyKernelProvider"))
37 | .enable(provider)
38 | .start_and_process()
39 | .unwrap();
40 |
41 | std::thread::sleep(Duration::new(20, 0));
42 | kernel_trace.stop().unwrap(); // This is not required, as it will automatically be stopped on Drop
43 | }
44 |
--------------------------------------------------------------------------------
/examples/multiple_providers.rs:
--------------------------------------------------------------------------------
1 | use ferrisetw::parser::{Parser, Pointer};
2 | use ferrisetw::provider::*;
3 | use ferrisetw::schema_locator::SchemaLocator;
4 | use ferrisetw::trace::*;
5 | use ferrisetw::EventRecord;
6 | use std::net::{IpAddr, Ipv4Addr};
7 | use std::time::Duration;
8 |
9 | fn registry_callback(record: &EventRecord, schema_locator: &SchemaLocator) {
10 | match schema_locator.event_schema(record) {
11 | Ok(schema) => {
12 | if record.event_id() == 7 {
13 | let parser = Parser::create(record, &schema);
14 | let pid = record.process_id();
15 | let key_obj: Pointer = parser.try_parse("KeyObject").unwrap_or(Pointer::default());
16 | let status: u32 = parser.try_parse("Status").unwrap_or(0);
17 | let value_name: String = parser.try_parse("ValueName").unwrap_or(String::from(""));
18 | println!(
19 | "QueryValueKey (PID: {}) -> KeyObj: {:#08x}, ValueName: {}, Status: {:#04X}",
20 | pid, key_obj, value_name, status,
21 | );
22 | }
23 | }
24 | Err(err) => println!("Error {:?}", err),
25 | };
26 | }
27 |
28 | fn tcpip_callback(record: &EventRecord, schema_locator: &SchemaLocator) {
29 | match schema_locator.event_schema(record) {
30 | Ok(schema) => {
31 | if record.event_id() == 11 {
32 | let parser = Parser::create(record, &schema);
33 | let size: u32 = parser.try_parse("size").unwrap_or(0);
34 | let daddr: IpAddr = parser
35 | .try_parse("daddr")
36 | .unwrap_or(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
37 | let dport: u16 = parser.try_parse("dport").unwrap_or(0);
38 | let saddr: IpAddr = parser
39 | .try_parse("saddr")
40 | .unwrap_or(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
41 | let sport: u16 = parser.try_parse("sport").unwrap_or(0);
42 | println!(
43 | "{} bytes received from {}:{} to {}:{}",
44 | size, saddr, sport, daddr, dport
45 | );
46 | }
47 | }
48 | Err(err) => println!("Error {:?}", err),
49 | };
50 | }
51 |
52 | fn main() {
53 | env_logger::init(); // this is optional. This makes the (rare) error logs of ferrisetw to be printed to stderr
54 |
55 | let tcpip_provider = Provider::by_guid("7dd42a49-5329-4832-8dfd-43d979153a88") // Microsoft-Windows-Kernel-Network
56 | .add_callback(tcpip_callback)
57 | .build();
58 |
59 | let process_provider = Provider::by_guid("70eb4f03-c1de-4f73-a051-33d13d5413bd") // Microsoft-Windows-Kernel-Registry
60 | .add_callback(registry_callback)
61 | .build();
62 |
63 | let user_trace = UserTrace::new()
64 | .enable(process_provider)
65 | .enable(tcpip_provider)
66 | .start_and_process()
67 | .unwrap();
68 |
69 | std::thread::sleep(Duration::new(10, 0));
70 |
71 | user_trace.stop().unwrap(); // optional. Simply dropping user_trace has the same effect
72 | }
73 |
--------------------------------------------------------------------------------
/examples/sys_info.rs:
--------------------------------------------------------------------------------
1 | use ferrisetw::query::*;
2 |
3 | fn main() {
4 | println!("Max PMC: {}", SessionlessInfo::max_pmc().unwrap());
5 | println!(
6 | "Profile Interval: {}",
7 | SessionlessInfo::sample_interval(ProfileSource::ProfileTime).unwrap()
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/examples/user_trace.rs:
--------------------------------------------------------------------------------
1 | use ferrisetw::parser::Parser;
2 | use ferrisetw::provider::*;
3 | use ferrisetw::schema_locator::SchemaLocator;
4 | use ferrisetw::trace::*;
5 | use ferrisetw::EventRecord;
6 | use std::time::Duration;
7 |
8 | fn main() {
9 | env_logger::init(); // this is optional. This makes the (rare) error logs of ferrisetw to be printed to stderr
10 |
11 | let process_callback =
12 | |record: &EventRecord, schema_locator: &SchemaLocator| match schema_locator
13 | .event_schema(record)
14 | {
15 | Ok(schema) => {
16 | let event_id = record.event_id();
17 | if event_id == 2 {
18 | let name = schema.provider_name();
19 | println!("Name: {}", name);
20 | let parser = Parser::create(record, &schema);
21 | let process_id: u32 = parser.try_parse("ProcessID").unwrap();
22 | let exit_code: u32 = parser.try_parse("ExitCode").unwrap();
23 | let image_name: String = parser.try_parse("ImageName").unwrap();
24 | println!(
25 | "PID: {}, ExitCode: {}, ImageName: {}",
26 | process_id, exit_code, image_name
27 | );
28 | }
29 | }
30 | Err(err) => println!("Error {:?}", err),
31 | };
32 |
33 | let process_provider = Provider::by_guid("22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716") // Microsoft-Windows-Kernel-Process
34 | .add_callback(process_callback)
35 | .build();
36 |
37 | let (_user_trace, handle) = UserTrace::new()
38 | .named(String::from("MyTrace"))
39 | .enable(process_provider)
40 | .start()
41 | .unwrap();
42 |
43 | // This example uses `process_from_handle` rather than the more convient `start_and_process`, because why not.
44 | std::thread::spawn(move || {
45 | let status = UserTrace::process_from_handle(handle);
46 | // This code will be executed when the trace stops. Examples:
47 | // * when it is dropped
48 | // * when it is manually stopped (either by user_trace.stop, or by the `logman stop -ets MyTrace` command)
49 | println!("Trace ended with status {:?}", status);
50 | });
51 |
52 | std::thread::sleep(Duration::new(20, 0));
53 |
54 | // user_trace will be dropped (and stopped) here
55 | }
56 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! # Event Windows Tracing FTW!
2 | //! This crate provides safe Rust abstractions over the ETW consumer APIs.
3 | //!
4 | //! It started as a [KrabsETW](https://github.com/microsoft/krabsetw/) rip-off written in Rust (hence the name [`Ferris`](https://rustacean.net/) 🦀).
5 | //! All credits go to the team at Microsoft who develop KrabsEtw, without it, this project probably wouldn't be a thing.
6 | //! Since version 1.0, the API and internal architecture of this crate is slightly diverging from `krabsetw`, so that it is more Rust-idiomatic.
7 | //!
8 | //! # What's ETW
9 | //! Event Tracing for Windows (ETW) is an efficient kernel-level tracing facility that lets you log
10 | //! kernel or application-defined events to a log file. You can consume the events in real time or
11 | //! from a log file and use them to debug an application or to determine where performance issues
12 | //! are occurring in the application. [Source]
13 | //!
14 | //! ETW is made out of three components:
15 | //! * Controllers
16 | //! * Providers
17 | //! * Consumers
18 | //!
19 | //! This crate provides the means to start and stop a controller, enable/disable providers and
20 | //! finally to consume the events within our own defined callback.
21 | //! It is also able to process events from a file instead of a real-time trace session.
22 | //!
23 | //! # Motivation
24 | //! Even though ETW is a extremely powerful tracing mechanism, interacting with it is not easy by any
25 | //! means. There's a lot of details and caveats that have to be taken into consideration in order
26 | //! to make it work. On the other hand, once we manage to start consuming a trace session in real-time
27 | //! we have to deal with the process of finding the Schema and parsing the properties. All this process
28 | //! can be tedious and cumbersome, therefore tools like KrabsETW come in very handy to simplify the
29 | //! interaction with ETW.
30 | //!
31 | //! Since lately I've been working very closely with ETW and Rust, I thought that having a tool that
32 | //! would simplify ETW management written in Rust and available as a crate for other to consume would
33 | //! be pretty neat and that's where this crate comes into play 🔥
34 | //!
35 | //! # Getting started
36 | //! If you are familiar with KrabsEtw you'll see using the crate is very similar, in case you are not
37 | //! familiar with it the following example shows the basics on how to build a provider, start a trace
38 | //! and handle the Event in the callback
39 | //!
40 | //! ```
41 | //! use ferrisetw::EventRecord;
42 | //! use ferrisetw::schema_locator::SchemaLocator;
43 | //! use ferrisetw::parser::Parser;
44 | //! use ferrisetw::provider::Provider;
45 | //! use ferrisetw::trace::{UserTrace, TraceTrait};
46 | //!
47 | //! fn process_callback(record: &EventRecord, schema_locator: &SchemaLocator) {
48 | //! // Basic event scrutinizing can be done directly from the `EventRecord`
49 | //! if record.event_id() == 2 {
50 | //! // More advanced info can be retrieved from the event schema
51 | //! // (the SchemaLocator caches the schema for a given kind of event, so this call is cheap in case you've already encountered the same event kind previously)
52 | //! match schema_locator.event_schema(record) {
53 | //! Err(err) => println!("Error {:?}", err),
54 | //! Ok(schema) => {
55 | //! println!("Received an event from provider {}", schema.provider_name());
56 | //!
57 | //! // Finally, properties for a given event can be retrieved using a Parser
58 | //! let parser = Parser::create(record, &schema);
59 | //!
60 | //! // You'll need type inference to tell ferrisetw what type you want to parse into
61 | //! // In actual code, be sure to correctly handle Err values!
62 | //! let process_id: u32 = parser.try_parse("ProcessID").unwrap();
63 | //! let image_name: String = parser.try_parse("ImageName").unwrap();
64 | //! println!("PID: {} ImageName: {}", process_id, image_name);
65 | //! }
66 | //! }
67 | //! }
68 | //! }
69 | //!
70 | //! fn main() {
71 | //! // First we build a Provider
72 | //! let process_provider = Provider
73 | //! ::by_guid("22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716") // Microsoft-Windows-Kernel-Process
74 | //! .add_callback(process_callback)
75 | //! // .add_callback(process_callback) // it is possible to add multiple callbacks for a given provider
76 | //! // .add_filter(event_filters) // it is possible to filter by event ID, process ID, etc.
77 | //! .build();
78 | //!
79 | //! // We start a real-time trace session for the previously registered provider
80 | //! // Callbacks will be run in a separate thread.
81 | //! let mut trace = UserTrace::new()
82 | //! .named(String::from("MyTrace"))
83 | //! .enable(process_provider)
84 | //! // .enable(other_provider) // It is possible to enable multiple providers on the same trace.
85 | //! // .set_etl_dump_file(...) // It is possible to dump the events that the callbacks are processing into a file
86 | //! .start_and_process() // This call will spawn the thread for you.
87 | //! // See the doc for alternative ways of processing the trace,
88 | //! // with more or less flexibility regarding this spawned thread.
89 | //! .unwrap();
90 | //!
91 | //! std::thread::sleep(std::time::Duration::from_secs(3));
92 | //!
93 | //! // We stop the trace
94 | //! trace.stop();
95 | //! }
96 | //! ```
97 | //!
98 | //! [KrabsETW]: https://github.com/microsoft/krabsetw/
99 | //! [Source]: https://docs.microsoft.com/en-us/windows/win32/etw/about-event-tracing
100 | //!
101 | //! # Log messages
102 | //! ferrisetw may (very) occasionally write error log messages using the [`log`](https://docs.rs/log/latest/log/) crate.
103 | //! In case you want them to be printed to the console, your binary should use one of the various logger implementations. [`env_logger`](https://docs.rs/env_logger/latest/env_logger/) is one of them.
104 | //! You can have a look at how to use it in the `examples/` folder in the GitHub repository.
105 |
106 | #[macro_use]
107 | extern crate memoffset;
108 |
109 | #[macro_use]
110 | extern crate bitflags;
111 |
112 | #[macro_use]
113 | extern crate num_derive;
114 | extern crate num_traits;
115 |
116 | pub mod native;
117 | pub mod parser;
118 | mod property;
119 | pub mod provider;
120 | pub mod query;
121 | pub mod schema;
122 | pub mod schema_locator;
123 | pub mod ser;
124 | pub mod trace;
125 | mod traits;
126 | mod utils;
127 |
128 | pub(crate) type EtwCallback = Box;
129 |
130 | // Convenience re-exports.
131 | pub use crate::native::etw_types::event_record::EventRecord;
132 | pub use crate::schema_locator::SchemaLocator;
133 | #[cfg(feature = "serde")]
134 | pub use crate::ser::{EventSerializer, EventSerializerOptions};
135 | pub use crate::trace::FileTrace;
136 | pub use crate::trace::KernelTrace;
137 | pub use crate::trace::UserTrace;
138 |
139 | // These types are returned by some public APIs of this crate.
140 | // They must be re-exported, so that users of the crate have a way to avoid version conflicts
141 | // (see https://github.com/n4r1b/ferrisetw/issues/46)
142 | /// Re-exported `GUID` from `windows-rs`, which is used in return values for some functions of this crate
143 | pub use windows::core::GUID;
144 | /// Re-exported `SID` from `windows-rs`, which is used in return values for some functions of this crate
145 | pub use windows::Win32::Security::SID;
146 |
--------------------------------------------------------------------------------
/src/native/etw_types/event_record.rs:
--------------------------------------------------------------------------------
1 | //! Safe wrappers over the EVENT_RECORD type
2 |
3 | use windows::core::GUID;
4 | use windows::Win32::System::Diagnostics::Etw::EVENT_RECORD;
5 |
6 | use crate::native::etw_types::extended_data::EventHeaderExtendedDataItem;
7 | use crate::native::ExtendedDataItem;
8 |
9 | use super::EVENT_HEADER_FLAG_32_BIT_HEADER;
10 |
11 | /// A read-only wrapper over an [EVENT_RECORD](https://docs.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_record)
12 | #[repr(transparent)]
13 | pub struct EventRecord(pub(crate) EVENT_RECORD);
14 |
15 | impl EventRecord {
16 | /// Create a `&self` from a Windows pointer.
17 | ///
18 | /// # Safety
19 | ///
20 | /// 1. Once an instance of `Self` is created, one should make sure the pointed data does not get modified (or dealloc'ed).
21 | /// 2. The returned lifetime is arbitray. To restrict the use of the returned reference (and to ensure the first safety guarantee), simply pass it to a sub-function whose signature has no explicit lifetime.
22 | /// Thus, the sub-function will not be able to leak this reference.
23 | pub(crate) unsafe fn from_ptr<'a>(p: *const EVENT_RECORD) -> Option<&'a Self> {
24 | let s = p as *const Self;
25 | s.as_ref()
26 | }
27 |
28 | /// Get the wrapped `EVENT_RECORD` (usually to feed Windows API functions)
29 | ///
30 | /// # Safety
31 | ///
32 | /// Obviously, the returned pointer is only valid as long `self` is valid and not modified.
33 | pub(crate) fn as_raw_ptr(&self) -> *const EVENT_RECORD {
34 | &self.0 as *const EVENT_RECORD
35 | }
36 |
37 | /// The `UserContext` field from the wrapped `EVENT_RECORD`
38 | ///
39 | /// In this crate, it is always populated to point to a valid [`CallbackData`](crate::trace::CallbackData)
40 | pub(crate) fn user_context(&self) -> *const std::ffi::c_void {
41 | self.0.UserContext as *const _
42 | }
43 |
44 | /// The `ProviderId` field from the wrapped `EVENT_RECORD`
45 | pub fn provider_id(&self) -> GUID {
46 | self.0.EventHeader.ProviderId
47 | }
48 |
49 | /// The `Id` field from the wrapped `EVENT_RECORD`
50 | pub fn event_id(&self) -> u16 {
51 | self.0.EventHeader.EventDescriptor.Id
52 | }
53 |
54 | /// The `Opcode` field from the wrapped `EVENT_RECORD`
55 | pub fn opcode(&self) -> u8 {
56 | self.0.EventHeader.EventDescriptor.Opcode
57 | }
58 |
59 | /// The `Version` field from the wrapped `EVENT_RECORD`
60 | pub fn version(&self) -> u8 {
61 | self.0.EventHeader.EventDescriptor.Version
62 | }
63 |
64 | /// The `Level` field from the wrapped `EVENT_RECORD`
65 | pub fn level(&self) -> u8 {
66 | self.0.EventHeader.EventDescriptor.Level
67 | }
68 |
69 | /// The `Keyword` field from the wrapped `EVENT_RECORD`
70 | pub fn keyword(&self) -> u64 {
71 | self.0.EventHeader.EventDescriptor.Keyword
72 | }
73 |
74 | /// The `Flags` field from the wrapped `EVENT_RECORD`
75 | pub fn event_flags(&self) -> u16 {
76 | self.0.EventHeader.Flags
77 | }
78 |
79 | /// The `ProcessId` field from the wrapped `EVENT_RECORD`
80 | pub fn process_id(&self) -> u32 {
81 | self.0.EventHeader.ProcessId
82 | }
83 |
84 | /// The `ThreadId` field from the wrapped `EVENT_RECORD`
85 | pub fn thread_id(&self) -> u32 {
86 | self.0.EventHeader.ThreadId
87 | }
88 |
89 | /// The `ActivityId` field from the wrapped `EVENT_RECORD`
90 | pub fn activity_id(&self) -> GUID {
91 | self.0.EventHeader.ActivityId
92 | }
93 |
94 | /// The `TimeStamp` field from the wrapped `EVENT_RECORD`
95 | ///
96 | /// As per [Microsoft's documentation](https://docs.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_header):
97 | /// > Contains the time that the event occurred.
98 | /// > The resolution is system time unless the `ProcessTraceMode member` of `EVENT_TRACE_LOGFILE`
99 | /// > contains the `PROCESS_TRACE_MODE_RAW_TIMESTAMP` flag, in which case the resolution depends
100 | /// > on the value of the `Wnode.ClientContext` member of `EVENT_TRACE_PROPERTIES` at the time
101 | /// > the controller created the session.
102 | ///
103 | /// Note: the `time_rs` Cargo feature enables to convert this into strongly-typed values
104 | pub fn raw_timestamp(&self) -> i64 {
105 | self.0.EventHeader.TimeStamp
106 | }
107 |
108 | /// The `TimeStamp` field from the wrapped `EVENT_RECORD`, as a strongly-typed `time::OffsetDateTime`
109 | #[cfg(feature = "time_rs")]
110 | pub fn timestamp(&self) -> time::OffsetDateTime {
111 | crate::native::time::FileTime::from_quad(self.0.EventHeader.TimeStamp).into()
112 | }
113 |
114 | pub(crate) fn user_buffer(&self) -> &[u8] {
115 | unsafe {
116 | std::slice::from_raw_parts(self.0.UserData as *mut _, self.0.UserDataLength.into())
117 | }
118 | }
119 |
120 | pub(crate) fn pointer_size(&self) -> usize {
121 | if self.event_flags() & EVENT_HEADER_FLAG_32_BIT_HEADER != 0 {
122 | 4
123 | } else {
124 | 8
125 | }
126 | }
127 |
128 | /// Returns the `ExtendedData` from the ETW Event
129 | ///
130 | /// Their availability is mostly determined by the flags passed to [`Provider::trace_flags`](crate::provider::Provider::trace_flags)
131 | ///
132 | /// # Example
133 | /// ```
134 | /// # use ferrisetw::EventRecord;
135 | /// # use ferrisetw::schema_locator::SchemaLocator;
136 | /// use windows::Win32::System::Diagnostics::Etw::EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID;
137 | ///
138 | /// let my_callback = |record: &EventRecord, schema_locator: &SchemaLocator| {
139 | /// let schema = schema_locator.event_schema(record).unwrap();
140 | /// let activity_id = record
141 | /// .extended_data()
142 | /// .iter()
143 | /// .find(|edata| edata.data_type() as u32 == EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID)
144 | /// .map(|edata| edata.to_extended_data_item());
145 | /// };
146 | /// ```
147 | pub fn extended_data(&self) -> &[EventHeaderExtendedDataItem] {
148 | let n_extended_data = self.0.ExtendedDataCount;
149 | let p_ed_array = self.0.ExtendedData;
150 | if n_extended_data == 0 || p_ed_array.is_null() {
151 | return &[];
152 | }
153 |
154 | // Safety: * we're building a slice from an array pointer size given by Windows
155 | // * the pointed data is not supposed to be mutated during the lifetime of `Self`
156 | unsafe {
157 | std::slice::from_raw_parts(
158 | p_ed_array as *const EventHeaderExtendedDataItem,
159 | n_extended_data as usize,
160 | )
161 | }
162 | }
163 |
164 | /// Returns the `eventName` for manifest-free events
165 | pub fn event_name(&self) -> String {
166 | if self.event_id() != 0 {
167 | return String::new();
168 | }
169 |
170 | if let Some(ExtendedDataItem::TraceLogging(name)) = self
171 | .extended_data()
172 | .iter()
173 | .find(|ext_data| ext_data.is_tlg())
174 | .map(|ext_data| ext_data.to_extended_data_item())
175 | {
176 | name
177 | } else {
178 | String::new()
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/native/etw_types/extended_data.rs:
--------------------------------------------------------------------------------
1 | //! A module to handle Extended Data from ETW traces
2 |
3 | use std::{ffi::CStr, mem};
4 | use windows::core::GUID;
5 | use windows::Win32::Security::SID;
6 | use windows::Win32::System::Diagnostics::Etw::{
7 | EVENT_EXTENDED_ITEM_RELATED_ACTIVITYID, EVENT_EXTENDED_ITEM_TS_ID,
8 | };
9 | use windows::Win32::System::Diagnostics::Etw::{
10 | EVENT_HEADER_EXTENDED_DATA_ITEM, EVENT_HEADER_EXT_TYPE_EVENT_KEY,
11 | EVENT_HEADER_EXT_TYPE_EVENT_SCHEMA_TL, EVENT_HEADER_EXT_TYPE_INSTANCE_INFO,
12 | EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY, EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID,
13 | EVENT_HEADER_EXT_TYPE_SID, EVENT_HEADER_EXT_TYPE_STACK_TRACE32,
14 | EVENT_HEADER_EXT_TYPE_STACK_TRACE64, EVENT_HEADER_EXT_TYPE_TS_ID,
15 | };
16 |
17 | // These types are returned by our public API. Let's use their re-exported versions
18 | use crate::native::{
19 | EVENT_EXTENDED_ITEM_INSTANCE, EVENT_EXTENDED_ITEM_STACK_TRACE32,
20 | EVENT_EXTENDED_ITEM_STACK_TRACE64,
21 | };
22 |
23 | const OFFSET_OF_ADDRESS_IN_ITEM: usize = offset_of!(EVENT_EXTENDED_ITEM_STACK_TRACE64, Address);
24 | const _: () =
25 | assert!(OFFSET_OF_ADDRESS_IN_ITEM == offset_of!(EVENT_EXTENDED_ITEM_STACK_TRACE32, Address));
26 |
27 | /// A fixed-size representation of EVENT_EXTENDED_ITEM_STACK_TRACE32 (if Address is u32)
28 | /// and EVENT_EXTENDED_ITEM_STACK_TRACE64 (if Address is u64)
29 | ///
30 | /// See
31 | /// See
32 | #[derive(Debug)]
33 | pub struct StackTraceItem
34 | where
35 | Address: Copy,
36 | {
37 | match_id: u64,
38 | addresses: Box<[Address]>,
39 | }
40 |
41 | impl StackTraceItem
42 | where
43 | Address: Copy,
44 | {
45 | /// Accessor for the MatchId field
46 | pub fn match_id(&self) -> u64 {
47 | self.match_id
48 | }
49 |
50 | /// Accessor for the ANYSIZE_ARRAY Address field
51 | pub fn addresses(&self) -> &[Address] {
52 | self.addresses.as_ref()
53 | }
54 |
55 | unsafe fn from_raw(
56 | match_id: u64,
57 | first_address: *const Address,
58 | item_size: usize,
59 | ) -> StackTraceItem {
60 | let array_size_in_bytes = item_size
61 | .checked_sub(OFFSET_OF_ADDRESS_IN_ITEM)
62 | .unwrap_or(0);
63 | let array_size = array_size_in_bytes / core::mem::size_of::();
64 | let addresses = unsafe { std::slice::from_raw_parts(first_address, array_size) }.into();
65 | StackTraceItem {
66 | match_id,
67 | addresses,
68 | }
69 | }
70 | }
71 |
72 | /// A wrapper over [`windows::Win32::System::Diagnostics::Etw::EVENT_HEADER_EXTENDED_DATA_ITEM`]
73 | #[repr(transparent)]
74 | pub struct EventHeaderExtendedDataItem(EVENT_HEADER_EXTENDED_DATA_ITEM);
75 |
76 | /// A safe representation of an ExtendedDataItem
77 | ///
78 | /// See
79 | #[derive(Debug)]
80 | pub enum ExtendedDataItem {
81 | /// Unexpected, invalid or not implemented yet
82 | Unsupported,
83 | /// Related activity identifier
84 | RelatedActivityId(GUID),
85 | /// Security identifier (SID) of the user that logged the event
86 | Sid(SID),
87 | /// Terminal session identifier
88 | TsId(u32),
89 | InstanceInfo(EVENT_EXTENDED_ITEM_INSTANCE),
90 | /// Call stack (if the event is captured on a 32-bit computer)
91 | StackTrace32(StackTraceItem),
92 | /// Call stack (if the event is captured on a 64-bit computer)
93 | StackTrace64(StackTraceItem),
94 | /// TraceLogging event metadata information
95 | TraceLogging(String),
96 | // /// Provider traits data
97 | // /// (for example traits set through EventSetInformation(EventProviderSetTraits) or specified through EVENT_DATA_DESCRIPTOR_TYPE_PROVIDER_METADATA)
98 | // ProvTraits,
99 | /// Unique event identifier
100 | EventKey(u64),
101 | /// Unique process identifier (unique across the boot session)
102 | ProcessStartKey(u64),
103 | }
104 |
105 | impl EventHeaderExtendedDataItem {
106 | /// Returns the `ExtType` of this extended data.
107 | ///
108 | /// See for possible values
109 | pub fn data_type(&self) -> u16 {
110 | self.0.ExtType
111 | }
112 |
113 | pub fn is_tlg(&self) -> bool {
114 | self.0.ExtType as u32 == EVENT_HEADER_EXT_TYPE_EVENT_SCHEMA_TL
115 | }
116 |
117 | /// Returns this extended data as a variant of a Rust enum.
118 | // TODO: revisit this function
119 | pub fn to_extended_data_item(&self) -> ExtendedDataItem {
120 | let data_ptr = self.0.DataPtr as *const std::ffi::c_void;
121 | if data_ptr.is_null() {
122 | return ExtendedDataItem::Unsupported;
123 | }
124 |
125 | match self.0.ExtType as u32 {
126 | EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID => {
127 | let data_ptr = data_ptr as *const EVENT_EXTENDED_ITEM_RELATED_ACTIVITYID;
128 | ExtendedDataItem::RelatedActivityId(unsafe { *data_ptr }.RelatedActivityId)
129 | }
130 |
131 | EVENT_HEADER_EXT_TYPE_SID => {
132 | let data_ptr = data_ptr as *const SID;
133 | ExtendedDataItem::Sid(unsafe { *data_ptr })
134 | }
135 |
136 | EVENT_HEADER_EXT_TYPE_TS_ID => {
137 | let data_ptr = data_ptr as *const EVENT_EXTENDED_ITEM_TS_ID;
138 | ExtendedDataItem::TsId(unsafe { *data_ptr }.SessionId)
139 | }
140 |
141 | EVENT_HEADER_EXT_TYPE_INSTANCE_INFO => {
142 | let data_ptr = data_ptr as *const EVENT_EXTENDED_ITEM_INSTANCE;
143 | ExtendedDataItem::InstanceInfo(unsafe { *data_ptr })
144 | }
145 |
146 | EVENT_HEADER_EXT_TYPE_STACK_TRACE32 => {
147 | let data_ptr = data_ptr as *const EVENT_EXTENDED_ITEM_STACK_TRACE32;
148 | ExtendedDataItem::StackTrace32(unsafe {
149 | let match_id = (*data_ptr).MatchId;
150 | let first_address = &(*data_ptr).Address[0] as *const _;
151 | let item_size = self.0.DataSize as usize;
152 | StackTraceItem::from_raw(match_id, first_address, item_size)
153 | })
154 | }
155 |
156 | EVENT_HEADER_EXT_TYPE_STACK_TRACE64 => {
157 | let data_ptr = data_ptr as *const EVENT_EXTENDED_ITEM_STACK_TRACE64;
158 | ExtendedDataItem::StackTrace64(unsafe {
159 | let match_id = (*data_ptr).MatchId;
160 | let first_address = &(*data_ptr).Address[0] as *const _;
161 | let item_size = self.0.DataSize as usize;
162 | StackTraceItem::from_raw(match_id, first_address, item_size)
163 | })
164 | }
165 |
166 | EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY => {
167 | let data_ptr = data_ptr as *const u64;
168 | ExtendedDataItem::ProcessStartKey(unsafe { *data_ptr })
169 | }
170 |
171 | EVENT_HEADER_EXT_TYPE_EVENT_KEY => {
172 | let data_ptr = data_ptr as *const u64;
173 | ExtendedDataItem::EventKey(unsafe { *data_ptr })
174 | }
175 |
176 | EVENT_HEADER_EXT_TYPE_EVENT_SCHEMA_TL => {
177 | ExtendedDataItem::TraceLogging(unsafe { self.get_event_name().unwrap_or_default() })
178 | }
179 |
180 | _ => ExtendedDataItem::Unsupported,
181 | }
182 | }
183 |
184 | ///
185 | /// This function will parse the `_tlgEventMetadata_t` to retrieve the EventName
186 | ///
187 | /// For more info see `_tlgEventMetadata_t` in `TraceLoggingProvider.h` (Windows SDK)
188 | ///
189 | /// ```cpp
190 | /// struct _tlgEventMetadata_t
191 | /// {
192 | /// UINT8 Type; // = _TlgBlobEvent4
193 | /// UCHAR Channel;
194 | /// UCHAR Level;
195 | /// UCHAR Opcode;
196 | /// ULONGLONG Keyword;
197 | /// UINT16 RemainingSize; // = sizeof(RemainingSize + Tags + EventName + Fields)
198 | /// UINT8 Tags[]; // 1 or more bytes. Read until you hit a byte with high bit unset.
199 | /// char EventName[sizeof("eventName")]; // UTF-8 nul-terminated event name
200 | /// for each field {
201 | /// char FieldName[sizeof("fieldName")];
202 | /// UINT8 InType;
203 | /// UINT8 OutType;
204 | /// UINT8 Tags[];
205 | /// UINT16 ValueCount;
206 | /// UINT16 TypeInfoSize;
207 | /// char TypeInfo[TypeInfoSize];
208 | /// }
209 | /// }
210 | /// ```
211 | ///
212 | /// We are only interested on `EventName` so we will only consider the first three members.
213 | ///
214 | /// # Safety
215 | ///
216 | /// As per the MS header 'This structure may change in future revisions of this header.'
217 | /// **Keep an eye on it!**
218 | ///
219 | // TODO: Make this function more robust
220 | unsafe fn get_event_name(&self) -> Option {
221 | const TAGS_SIZE: usize = 1;
222 | debug_assert!(self.is_tlg());
223 |
224 | let mut data_ptr = self.0.DataPtr as *const u8;
225 | if data_ptr.is_null() {
226 | return None;
227 | }
228 |
229 | let size = data_ptr.read_unaligned() as u16;
230 | data_ptr = data_ptr.add(mem::size_of::());
231 |
232 | let mut n = 0;
233 | while n < size {
234 | // Read until you hit a byte with high bit unset.
235 | let tag = data_ptr.read_unaligned();
236 | data_ptr = data_ptr.add(TAGS_SIZE);
237 |
238 | if tag & 0b1000_0000 == 0 {
239 | break;
240 | }
241 |
242 | n += 1;
243 | }
244 |
245 | // If debug let's assert here since this is a case we want to investigate
246 | debug_assert!(n != size);
247 | if n == size {
248 | return None;
249 | }
250 |
251 | Some(String::from(
252 | CStr::from_ptr(data_ptr as *const _).to_string_lossy(),
253 | ))
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/src/native/evntrace.rs:
--------------------------------------------------------------------------------
1 | //! Safe wrappers for the native ETW API
2 | //!
3 | //! This module makes sure the calls are safe memory-wise, but does not attempt to ensure they are called in the right order.
4 | //! Thus, you should prefer using `UserTrace`s, `KernelTrace`s and `TraceBuilder`s, that will ensure these API are correctly used.
5 | use std::collections::HashSet;
6 | use std::ffi::c_void;
7 | use std::panic::AssertUnwindSafe;
8 | use std::sync::Arc;
9 | use std::sync::Mutex;
10 |
11 | use once_cell::sync::Lazy;
12 |
13 | use widestring::U16CStr;
14 | use windows::core::GUID;
15 | use windows::core::PCWSTR;
16 | use windows::Win32::Foundation::ERROR_ALREADY_EXISTS;
17 | use windows::Win32::Foundation::ERROR_CTX_CLOSE_PENDING;
18 | use windows::Win32::Foundation::ERROR_SUCCESS;
19 | use windows::Win32::Foundation::FILETIME;
20 | use windows::Win32::System::Diagnostics::Etw;
21 | use windows::Win32::System::Diagnostics::Etw::EVENT_CONTROL_CODE_ENABLE_PROVIDER;
22 | use windows::Win32::System::Diagnostics::Etw::TRACE_QUERY_INFO_CLASS;
23 |
24 | use super::etw_types::*;
25 | use crate::native::etw_types::event_record::EventRecord;
26 | use crate::provider::event_filter::EventFilterDescriptor;
27 | use crate::provider::Provider;
28 | use crate::trace::callback_data::CallbackData;
29 | use crate::trace::{RealTimeTraceTrait, TraceProperties};
30 |
31 | pub type TraceHandle = Etw::PROCESSTRACE_HANDLE;
32 | pub type ControlHandle = Etw::CONTROLTRACE_HANDLE;
33 |
34 | /// Evntrace native module errors
35 | #[derive(Debug)]
36 | pub enum EvntraceNativeError {
37 | /// Represents an Invalid Handle Error
38 | InvalidHandle,
39 | /// Represents an ERROR_ALREADY_EXISTS
40 | AlreadyExist,
41 | /// Represents an standard IO Error
42 | IoError(std::io::Error),
43 | }
44 |
45 | pub(crate) type EvntraceNativeResult = Result;
46 |
47 | /// When a trace is closing, it is possible that every past events have not been processed yet.
48 | /// These events will still be fed to the callback, **after** the trace has been closed
49 | /// (see `ERROR_CTX_CLOSE_PENDING` in https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-closetrace#remarks)
50 | /// Also, there is no way to tell which callback invocation is the last one.
51 | ///
52 | /// But, we would like to free memory used by the callbacks when we're done!
53 | /// Since that is not possible, let's discard every callback run after we've called `CloseTrace`.
54 | /// That's the purpose of this set.
55 | ///
56 | /// TODO: it _might_ be possible to know whether we've processed the last buffered event, as
57 | /// ControlTraceW(EVENT_TRACE_CONTROL_QUERY) _might_ tell us if the buffers are empty or not.
58 | /// In case the trace is in ERROR_CTX_CLOSE_PENDING state, we could call this after every
59 | /// callback so that we know when to actually free memory used by the (now useless) callback.
60 | /// Maybe also setting the BufferCallback in EVENT_TRACE_LOGFILEW may help us.
61 | /// That's
62 | static UNIQUE_VALID_CONTEXTS: UniqueValidContexts = UniqueValidContexts::new();
63 | struct UniqueValidContexts(Lazy>>);
64 | enum ContextError {
65 | AlreadyExist,
66 | }
67 |
68 | impl UniqueValidContexts {
69 | pub const fn new() -> Self {
70 | Self(Lazy::new(|| Mutex::new(HashSet::new())))
71 | }
72 | /// Insert if it did not exist previously
73 | fn insert(&self, ctx_ptr: *const c_void) -> Result<(), ContextError> {
74 | match self.0.lock().unwrap().insert(ctx_ptr as u64) {
75 | true => Ok(()),
76 | false => Err(ContextError::AlreadyExist),
77 | }
78 | }
79 |
80 | fn remove(&self, ctx_ptr: *const c_void) {
81 | self.0.lock().unwrap().remove(&(ctx_ptr as u64));
82 | }
83 |
84 | pub fn is_valid(&self, ctx_ptr: *const c_void) -> bool {
85 | self.0.lock().unwrap().contains(&(ctx_ptr as u64))
86 | }
87 | }
88 |
89 | /// This will be called by the ETW framework whenever an ETW event is available
90 | extern "system" fn trace_callback_thunk(p_record: *mut Etw::EVENT_RECORD) {
91 | match std::panic::catch_unwind(AssertUnwindSafe(|| {
92 | let record_from_ptr = unsafe {
93 | // Safety: lifetime is valid at least until the end of the callback. A correct lifetime will be attached when we pass the reference to the child function
94 | EventRecord::from_ptr(p_record)
95 | };
96 |
97 | if let Some(event_record) = record_from_ptr {
98 | let p_user_context = event_record.user_context();
99 | if !UNIQUE_VALID_CONTEXTS.is_valid(p_user_context) {
100 | return;
101 | }
102 | let p_callback_data = p_user_context.cast::>();
103 | let callback_data = unsafe {
104 | // Safety:
105 | // * the API of this create guarantees this points to a `CallbackData` already allocated and created
106 | // * we've just checked using UNIQUE_VALID_CONTEXTS that this `CallbackData` has not been dropped
107 | // * the API of this crate guarantees this `CallbackData` is not mutated from another thread during the trace:
108 | // * we're the only one to change CallbackData::events_handled (and that's an atomic, so it's fine)
109 | // * the list of Providers is a constant (may change in the future with #54)
110 | // * the schema_locator only has interior mutability
111 | p_callback_data.as_ref()
112 | };
113 | if let Some(callback_data) = callback_data {
114 | // The UserContext is owned by the `Trace` object. When it is dropped, so will the UserContext.
115 | // We clone it now, so that the original Arc can be safely dropped at all times, but the callback data (including the closure captured context) will still be alive until the callback ends.
116 | let cloned_arc = Arc::clone(callback_data);
117 | cloned_arc.on_event(event_record);
118 | }
119 | }
120 | })) {
121 | Ok(_) => {}
122 | Err(e) => {
123 | log::error!("UNIMPLEMENTED PANIC: {e:?}");
124 | std::process::exit(1);
125 | }
126 | }
127 | }
128 |
129 | fn filter_invalid_trace_handles(h: TraceHandle) -> Option {
130 | // See https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-opentracew#return-value
131 | // We're conservative and we always filter out u32::MAX, although it could be valid on 64-bit setups.
132 | // But it turns out runtime detection of the current OS bitness is not that easy. Plus, it is not clear whether this depends on how the architecture the binary is compiled for, or the actual OS architecture.
133 | if h.Value == u64::MAX || h.Value == u32::MAX as u64 {
134 | None
135 | } else {
136 | Some(h)
137 | }
138 | }
139 |
140 | fn filter_invalid_control_handle(h: ControlHandle) -> Option {
141 | // The control handle is 0 if the handle is not valid.
142 | // (https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-starttracew)
143 | if h.Value == 0 {
144 | None
145 | } else {
146 | Some(h)
147 | }
148 | }
149 |
150 | /// Create a new session.
151 | ///
152 | /// This builds an `EventTraceProperties`, calls `StartTraceW` and returns the built `EventTraceProperties` as well as the trace ControlHandle
153 | pub(crate) fn start_trace(
154 | trace_name: &U16CStr,
155 | etl_dump_file: Option<(&U16CStr, DumpFileLoggingMode, Option)>,
156 | trace_properties: &TraceProperties,
157 | enable_flags: Etw::EVENT_TRACE_FLAG,
158 | ) -> EvntraceNativeResult<(EventTraceProperties, ControlHandle)>
159 | where
160 | T: RealTimeTraceTrait,
161 | {
162 | let mut properties =
163 | EventTraceProperties::new::(trace_name, etl_dump_file, trace_properties, enable_flags);
164 |
165 | let mut control_handle = ControlHandle::default();
166 | let status = unsafe {
167 | // Safety:
168 | // * first argument points to a valid and allocated address (this is an output and will be modified)
169 | // * second argument is a valid, null terminated widestring (note that it will be copied to the EventTraceProperties...from where it already comes. This will probably be overwritten by Windows, but heck.)
170 | // * third argument is a valid, allocated EVENT_TRACE_PROPERTIES (and will be mutated)
171 | // * Note: the string (that will be overwritten to itself) ends with a null widechar before the end of its buffer (see EventTraceProperties::new())
172 | Etw::StartTraceW(
173 | &mut control_handle,
174 | PCWSTR::from_raw(properties.trace_name_array().as_ptr()),
175 | properties.as_mut_ptr(),
176 | )
177 | }
178 | .ok();
179 |
180 | if let Err(status) = status {
181 | let code = status.code();
182 |
183 | if code == ERROR_ALREADY_EXISTS.to_hresult() {
184 | return Err(EvntraceNativeError::AlreadyExist);
185 | } else if code != ERROR_SUCCESS.to_hresult() {
186 | return Err(EvntraceNativeError::IoError(
187 | std::io::Error::from_raw_os_error(code.0),
188 | ));
189 | }
190 | }
191 |
192 | match filter_invalid_control_handle(control_handle) {
193 | None => Err(EvntraceNativeError::InvalidHandle),
194 | Some(handle) => Ok((properties, handle)),
195 | }
196 | }
197 |
198 | /// Subscribe to a started trace
199 | ///
200 | /// Microsoft calls this "opening" the trace (and this calls `OpenTraceW`)
201 | #[allow(clippy::borrowed_box)] // Being Boxed is really important, let's keep the Box<...> in the function signature to make the intent clearer
202 | pub(crate) fn open_trace(
203 | subscription_source: SubscriptionSource,
204 | callback_data: &Box>,
205 | ) -> EvntraceNativeResult {
206 | let mut log_file =
207 | EventTraceLogfile::create(callback_data, subscription_source, trace_callback_thunk);
208 |
209 | if let Err(ContextError::AlreadyExist) = UNIQUE_VALID_CONTEXTS.insert(log_file.context_ptr()) {
210 | // That's probably possible to get multiple handles to the same trace, by opening them multiple times.
211 | // But that's left as a future TODO. Making things right and safe is difficult enough with a single opening of the trace already.
212 | return Err(EvntraceNativeError::AlreadyExist);
213 | }
214 |
215 | let trace_handle = unsafe {
216 | // This function modifies the data pointed to by log_file.
217 | // This is fine because there is currently no other ref `self` (the current function takes a `&mut self`, and `self` is not used anywhere else in the current function)
218 | //
219 | // > On success, OpenTrace will update the structure with information from the opened file or session.
220 | // https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-opentracea
221 | Etw::OpenTraceW(log_file.as_mut_ptr())
222 | };
223 |
224 | if filter_invalid_trace_handles(trace_handle).is_none() {
225 | Err(EvntraceNativeError::IoError(std::io::Error::last_os_error()))
226 | } else {
227 | Ok(trace_handle)
228 | }
229 | }
230 |
231 | /// Attach a provider to a trace
232 | pub(crate) fn enable_provider(
233 | control_handle: ControlHandle,
234 | provider: &Provider,
235 | ) -> EvntraceNativeResult<()> {
236 | match filter_invalid_control_handle(control_handle) {
237 | None => Err(EvntraceNativeError::InvalidHandle),
238 | Some(handle) => {
239 | let owned_event_filter_descriptors: Vec = provider
240 | .filters()
241 | .iter()
242 | .filter_map(|filter| filter.to_event_filter_descriptor().ok()) // Silently ignoring invalid filters (basically, empty ones)
243 | .collect();
244 |
245 | let parameters = EnableTraceParameters::create(
246 | provider.guid(),
247 | provider.trace_flags(),
248 | &owned_event_filter_descriptors,
249 | );
250 |
251 | let res = unsafe {
252 | Etw::EnableTraceEx2(
253 | handle,
254 | &provider.guid() as *const GUID,
255 | EVENT_CONTROL_CODE_ENABLE_PROVIDER.0,
256 | provider.level(),
257 | provider.any(),
258 | provider.all(),
259 | 0,
260 | Some(parameters.as_ptr()),
261 | )
262 | }
263 | .ok();
264 |
265 | res.map_err(|err| {
266 | EvntraceNativeError::IoError(std::io::Error::from_raw_os_error(err.code().0))
267 | })
268 | }
269 | }
270 | }
271 |
272 | /// Start processing a trace (this call is blocking until the trace is stopped)
273 | ///
274 | /// You probably want to spawn a thread that will block on this call.
275 | pub(crate) fn process_trace(trace_handle: TraceHandle) -> EvntraceNativeResult<()> {
276 | if filter_invalid_trace_handles(trace_handle).is_none() {
277 | Err(EvntraceNativeError::InvalidHandle)
278 | } else {
279 | let result = unsafe {
280 | // We want to start processing events as soon as January 1601.
281 | // * for ETL file traces, this is fine, this means "process everything from the file"
282 | // * for real-time traces, this means we might process a few events already waiting in the buffers when the processing is starting. This is fine, I suppose.
283 | let mut start = FILETIME::default();
284 | Etw::ProcessTrace(&[trace_handle], Some(&mut start as *mut FILETIME), None)
285 | }
286 | .ok();
287 |
288 | result.map_err(|err| {
289 | EvntraceNativeError::IoError(std::io::Error::from_raw_os_error(err.code().0))
290 | })
291 | }
292 | }
293 |
294 | /// Call `ControlTraceW` on the trace
295 | ///
296 | /// # Notes
297 | ///
298 | /// In case you want to stop the trace, you probably want to drop the instance rather than calling `control(EVENT_TRACE_CONTROL_STOP)` yourself,
299 | /// because stop the trace makes the trace handle invalid.
300 | /// A stopped trace could theoretically(?) be re-used, but the trace handle should be re-created, so `open` should be called again.
301 | pub(crate) fn control_trace(
302 | properties: &mut EventTraceProperties,
303 | control_handle: ControlHandle,
304 | control_code: Etw::EVENT_TRACE_CONTROL,
305 | ) -> EvntraceNativeResult<()> {
306 | match filter_invalid_control_handle(control_handle) {
307 | None => Err(EvntraceNativeError::InvalidHandle),
308 | Some(handle) => {
309 | let result = unsafe {
310 | // Safety:
311 | // * the trace handle is valid (by construction)
312 | // * depending on the control code, the `Properties` can be mutated. This is fine because properties is declared as `&mut` in this function, which means no other Rust function has a reference to it, and the mutation can only happen in the call to `ControlTraceW`, which returns immediately.
313 | Etw::ControlTraceW(
314 | handle,
315 | PCWSTR::null(),
316 | properties.as_mut_ptr(),
317 | control_code,
318 | )
319 | }
320 | .ok();
321 |
322 | result.map_err(|err| {
323 | EvntraceNativeError::IoError(std::io::Error::from_raw_os_error(err.code().0))
324 | })
325 | }
326 | }
327 | }
328 |
329 | /// Similar to [`control_trace`], but using a trace name instead of a handle
330 | pub(crate) fn control_trace_by_name(
331 | properties: &mut EventTraceProperties,
332 | trace_name: &U16CStr,
333 | control_code: Etw::EVENT_TRACE_CONTROL,
334 | ) -> EvntraceNativeResult<()> {
335 | let result = unsafe {
336 | // Safety:
337 | // * depending on the control code, the `Properties` can be mutated. This is fine because properties is declared as `&mut` in this function, which means no other Rust function has a reference to it, and the mutation can only happen in the call to `ControlTraceW`, which returns immediately.
338 | Etw::ControlTraceW(
339 | Etw::CONTROLTRACE_HANDLE { Value: 0 },
340 | PCWSTR::from_raw(trace_name.as_ptr()),
341 | properties.as_mut_ptr(),
342 | control_code,
343 | )
344 | }
345 | .ok();
346 |
347 | result.map_err(|err| {
348 | EvntraceNativeError::IoError(std::io::Error::from_raw_os_error(err.code().0))
349 | })
350 | }
351 |
352 | /// Close the trace
353 | ///
354 | /// It is suggested to stop the trace immediately after `close`ing it (that's what it done in the `impl Drop`), because I'm not sure how sensible it is to call other methods (apart from `stop`) afterwards
355 | ///
356 | /// In case ETW reports there are still events in the queue that are still to trigger callbacks, this returns Ok(true).
357 | /// If no further event callback will be invoked, this returns Ok(false)
358 | /// On error, this returns an `Err`
359 | #[allow(clippy::borrowed_box)] // Being Boxed is really important, let's keep the Box<...> in the function signature to make the intent clearer
360 | pub(crate) fn close_trace(
361 | trace_handle: TraceHandle,
362 | callback_data: &Box>,
363 | ) -> EvntraceNativeResult {
364 | match filter_invalid_trace_handles(trace_handle) {
365 | None => Err(EvntraceNativeError::InvalidHandle),
366 | Some(handle) => {
367 | // By contruction, only one Provider used this context in its callback. It is safe to remove it, it won't be used by anyone else.
368 | UNIQUE_VALID_CONTEXTS
369 | .remove(callback_data.as_ref() as *const Arc as *const c_void);
370 |
371 | let status = unsafe { Etw::CloseTrace(handle) }.ok();
372 |
373 | match status {
374 | Ok(()) => Ok(false),
375 | Err(err) if err.code() == ERROR_CTX_CLOSE_PENDING.to_hresult() => Ok(true),
376 | Err(err) => Err(EvntraceNativeError::IoError(
377 | std::io::Error::from_raw_os_error(err.code().0),
378 | )),
379 | }
380 | }
381 | }
382 | }
383 |
384 | /// Queries the system for system-wide ETW information (that does not require an active session).
385 | pub(crate) fn query_info(class: TraceInformation, buf: &mut [u8]) -> EvntraceNativeResult<()> {
386 | let result = unsafe {
387 | Etw::TraceQueryInformation(
388 | Etw::CONTROLTRACE_HANDLE { Value: 0 },
389 | TRACE_QUERY_INFO_CLASS(class as i32),
390 | buf.as_mut_ptr().cast(),
391 | buf.len() as u32,
392 | None,
393 | )
394 | }
395 | .ok();
396 |
397 | result.map_err(|err| {
398 | EvntraceNativeError::IoError(std::io::Error::from_raw_os_error(err.code().0))
399 | })
400 | }
401 |
--------------------------------------------------------------------------------
/src/native/mod.rs:
--------------------------------------------------------------------------------
1 | //! Abstraction layer for Native functions and types
2 | //!
3 | //! This module interacts with the Windows native functions and should abstract all `unsafe` calls
4 | pub(crate) mod etw_types;
5 | pub(crate) mod evntrace;
6 | pub(crate) mod pla;
7 | pub(crate) mod sddl;
8 | pub(crate) mod tdh;
9 | pub(crate) mod tdh_types;
10 | pub mod time;
11 | pub(crate) mod version_helper;
12 |
13 | // These are used in our custom error types, and must be part of the public API
14 | pub use evntrace::EvntraceNativeError;
15 | pub use pla::PlaError;
16 | pub use sddl::SddlNativeError;
17 | pub use tdh::TdhNativeError;
18 |
19 | // These are returned by some of our public APIs
20 | pub use etw_types::extended_data::EventHeaderExtendedDataItem;
21 | pub use etw_types::extended_data::ExtendedDataItem;
22 | pub use etw_types::DecodingSource;
23 | pub use evntrace::ControlHandle;
24 | pub use evntrace::TraceHandle;
25 | pub use windows::Win32::System::Diagnostics::Etw::{
26 | EVENT_EXTENDED_ITEM_INSTANCE, EVENT_EXTENDED_ITEM_STACK_TRACE32,
27 | EVENT_EXTENDED_ITEM_STACK_TRACE64,
28 | };
29 |
--------------------------------------------------------------------------------
/src/native/pla.rs:
--------------------------------------------------------------------------------
1 | //! Native API - Performance Logs and Alerts COM
2 | //!
3 | //! The `pla` module is an abstraction layer for the Windows evntrace library. This module act as a
4 | //! internal API that holds all `unsafe` calls to functions exported by the `evntrace` Windows library.
5 | //!
6 | //! This module shouldn't be accessed directly. Modules from the the crate level provide a safe API to interact
7 | //! with the crate
8 | use windows::{
9 | core::{GUID, VARIANT},
10 | Win32::System::{
11 | Com::{CoCreateInstance, CoInitializeEx, CLSCTX_ALL, COINIT_MULTITHREADED},
12 | Performance::{ITraceDataProviderCollection, TraceDataProviderCollection},
13 | },
14 | };
15 |
16 | /// Pla native module errors
17 | #[derive(Debug, PartialEq, Eq)]
18 | pub enum PlaError {
19 | /// Represents a Provider not found Error
20 | NotFound,
21 | /// Represents an HRESULT common error
22 | ComError(windows::core::Error),
23 | }
24 |
25 | impl From for PlaError {
26 | fn from(val: windows::core::Error) -> PlaError {
27 | PlaError::ComError(val)
28 | }
29 | }
30 |
31 | pub(crate) type ProvidersComResult = Result;
32 |
33 | // https://github.com/microsoft/krabsetw/blob/31679cf84bc85360158672699f2f68a821e8a6d0/krabs/krabs/provider.hpp#L487
34 | pub(crate) unsafe fn get_provider_guid(name: &str) -> ProvidersComResult {
35 | // FIXME: This is not paired with a call to CoUninitialize, so this will leak COM resources.
36 | unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }.ok()?;
37 |
38 | let all_providers: ITraceDataProviderCollection =
39 | unsafe { CoCreateInstance(&TraceDataProviderCollection, None, CLSCTX_ALL) }?;
40 |
41 | all_providers.GetTraceDataProviders(None)?;
42 |
43 | let count = all_providers.Count()? as u32;
44 |
45 | let mut index = 0u32;
46 | let mut guid = None;
47 |
48 | while index < count as u32 {
49 | let provider = all_providers.get_Item(&VARIANT::from(index))?;
50 | let raw_name = provider.DisplayName()?;
51 |
52 | let prov_name = String::from_utf16_lossy(raw_name.as_wide());
53 |
54 | index += 1;
55 | // check if matches, if it does get guid and break
56 | if prov_name.eq(name) {
57 | guid = Some(provider.Guid()?);
58 | break;
59 | }
60 | }
61 |
62 | if index == count as u32 {
63 | return Err(PlaError::NotFound);
64 | }
65 |
66 | Ok(guid.unwrap())
67 | }
68 |
69 | #[cfg(test)]
70 | mod test {
71 | use super::*;
72 | #[test]
73 | pub fn test_get_provider() {
74 | unsafe {
75 | let guid =
76 | get_provider_guid("Microsoft-Windows-Kernel-Process").expect("Error Getting GUID");
77 |
78 | assert_eq!(GUID::from("22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716"), guid);
79 | }
80 | }
81 |
82 | #[test]
83 | pub fn test_provider_not_found() {
84 | unsafe {
85 | let err = get_provider_guid("Not-A-Real-Provider");
86 |
87 | assert_eq!(err, Err(PlaError::NotFound));
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/native/sddl.rs:
--------------------------------------------------------------------------------
1 | use core::ffi::c_void;
2 | use std::str::Utf8Error;
3 | use windows::core::PSTR;
4 | use windows::Win32::Foundation::{LocalFree, HLOCAL, PSID};
5 | use windows::Win32::Security::Authorization::ConvertSidToStringSidA;
6 |
7 | /// SDDL native error
8 | #[derive(Debug)]
9 | pub enum SddlNativeError {
10 | /// Represents an error parsing the SID into a String
11 | SidParseError(Utf8Error),
12 | /// Represents an standard IO Error
13 | IoError(std::io::Error),
14 | }
15 |
16 | impl From for SddlNativeError {
17 | fn from(err: Utf8Error) -> Self {
18 | SddlNativeError::SidParseError(err)
19 | }
20 | }
21 |
22 | impl std::fmt::Display for SddlNativeError {
23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 | match self {
25 | Self::SidParseError(e) => write!(f, "sid parse error {}", e),
26 | Self::IoError(e) => write!(f, "i/o error {}", e),
27 | }
28 | }
29 | }
30 |
31 | pub(crate) type SddlResult = Result;
32 |
33 | pub fn convert_sid_to_string(sid: *const c_void) -> SddlResult {
34 | let mut tmp = PSTR::null();
35 | unsafe {
36 | let not_really_mut_sid = sid.cast_mut(); // That's OK to widely change the constness here, because it will be given as an _input_ of ConvertSidToStringSidA and will not be modified
37 | if ConvertSidToStringSidA(PSID(not_really_mut_sid), &mut tmp).is_err() {
38 | return Err(SddlNativeError::IoError(std::io::Error::last_os_error()));
39 | }
40 |
41 | let sid_string = std::ffi::CStr::from_ptr(tmp.0.cast()).to_str()?.to_owned();
42 |
43 | if LocalFree(HLOCAL(tmp.0.cast())) != HLOCAL(std::ptr::null_mut()) {
44 | return Err(SddlNativeError::IoError(std::io::Error::last_os_error()));
45 | }
46 |
47 | Ok(sid_string)
48 | }
49 | }
50 |
51 | #[cfg(test)]
52 | mod test {
53 | use super::*;
54 |
55 | #[test]
56 | fn test_convert_string_to_sid() {
57 | let sid: Vec = vec![1, 2, 0, 0, 0, 0, 0, 5, 0x20, 0, 0, 0, 0x20, 2, 0, 0];
58 | if let Ok(string_sid) = convert_sid_to_string(sid.as_ptr() as *const c_void) {
59 | assert_eq!(string_sid, "S-1-5-32-544");
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/native/tdh.rs:
--------------------------------------------------------------------------------
1 | //! Native API - Event Tracing tdh header
2 | //!
3 | //! The `tdh` module is an abstraction layer for the Windows tdh library. This module act as a
4 | //! internal API that holds all `unsafe` calls to functions exported by the `tdh` Windows library.
5 | //!
6 | //! This module shouldn't be accessed directly. Modules from the the crate level provide a safe API to interact
7 | //! with the crate
8 | use std::alloc::Layout;
9 |
10 | use super::etw_types::*;
11 | use crate::native::etw_types::event_record::EventRecord;
12 | use crate::native::tdh_types::Property;
13 | use crate::traits::*;
14 | use widestring::U16CStr;
15 | use windows::core::GUID;
16 | use windows::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
17 | use windows::Win32::System::Diagnostics::Etw::{self, EVENT_PROPERTY_INFO, TRACE_EVENT_INFO};
18 |
19 | /// Tdh native module errors
20 | #[derive(Debug)]
21 | pub enum TdhNativeError {
22 | /// Represents an allocation error
23 | AllocationError,
24 | /// Represents an standard IO Error
25 | IoError(std::io::Error),
26 | }
27 |
28 | pub type TdhNativeResult = Result;
29 |
30 | impl std::fmt::Display for TdhNativeError {
31 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 | match self {
33 | Self::AllocationError => write!(f, "allocation error"),
34 | Self::IoError(e) => write!(f, "i/o error {}", e),
35 | }
36 | }
37 | }
38 |
39 | /// Read-only wrapper over an [TRACE_EVENT_INFO]
40 | ///
41 | /// [TRACE_EVENT_INFO]: https://docs.microsoft.com/en-us/windows/win32/api/tdh/ns-tdh-trace_event_info
42 | pub struct TraceEventInfo {
43 | /// Pointer to a valid TRACE_EVENT_INFO buffer
44 | data: *const u8,
45 | /// Pointer to the same buffer, but mutable (used only when deallocating the data)
46 | mut_data_for_dealloc: *mut u8,
47 | /// Layout used to allocate the TRACE_EVENT_INFO buffer
48 | layout: Layout,
49 | }
50 |
51 | // Safety: TraceEventInfo contains a pointer to data that is never mutated (except on deallocation), and that itself does not contain pointers
52 | unsafe impl Send for TraceEventInfo {}
53 | // Safety: see above
54 | unsafe impl Sync for TraceEventInfo {}
55 |
56 | macro_rules! extract_utf16_string {
57 | ($self: ident, $member_name: ident) => {
58 | let provider_name_offset = $self.as_raw().$member_name;
59 | let provider_name_ptr = unsafe {
60 | // Safety: we trust Microsoft for providing correctly aligned data
61 | $self.data.offset(provider_name_offset as isize)
62 | };
63 | if provider_name_offset == 0 || provider_name_ptr.is_null() {
64 | return String::new();
65 | }
66 | let provider_name = unsafe {
67 | // Safety:
68 | // * we trust Microsoft for providing correctly aligned data
69 | // * we will copy into a String before the buffer gets invalid
70 | U16CStr::from_ptr_str(provider_name_ptr as *const u16)
71 | };
72 | return provider_name.to_string_lossy();
73 | };
74 | }
75 |
76 | impl TraceEventInfo {
77 | /// Create a instance of `Self` suitable for the given event
78 | pub fn build_from_event(event: &EventRecord) -> TdhNativeResult {
79 | let mut buffer_size = 0;
80 | let status = unsafe {
81 | // Safety:
82 | // * the `EVENT_RECORD` was passed by Microsoft and has not been modified: it is thus valid and correctly aligned
83 | Etw::TdhGetEventInformation(event.as_raw_ptr(), None, None, &mut buffer_size)
84 | };
85 | if status != ERROR_INSUFFICIENT_BUFFER.0 {
86 | return Err(TdhNativeError::IoError(std::io::Error::from_raw_os_error(
87 | status as i32,
88 | )));
89 | }
90 |
91 | if buffer_size == 0 {
92 | return Err(TdhNativeError::AllocationError);
93 | }
94 |
95 | let layout = Layout::from_size_align(
96 | buffer_size as usize,
97 | std::mem::align_of::(),
98 | )
99 | .map_err(|_| TdhNativeError::AllocationError)?;
100 | let data = unsafe {
101 | // Safety: size is not zero
102 | std::alloc::alloc(layout)
103 | };
104 | if data.is_null() {
105 | return Err(TdhNativeError::AllocationError);
106 | }
107 |
108 | let status = unsafe {
109 | // Safety:
110 | // * the `EVENT_RECORD` was passed by Microsoft and has not been modified: it is thus valid and correctly aligned
111 | // * `data` has been successfully allocated, with the required size and the correct alignment
112 | Etw::TdhGetEventInformation(
113 | event.as_raw_ptr(),
114 | None,
115 | Some(data.cast::()),
116 | &mut buffer_size,
117 | )
118 | };
119 |
120 | if status != 0 {
121 | return Err(TdhNativeError::IoError(std::io::Error::from_raw_os_error(
122 | status as i32,
123 | )));
124 | }
125 |
126 | Ok(Self {
127 | data,
128 | mut_data_for_dealloc: data,
129 | layout,
130 | })
131 | }
132 |
133 | fn as_raw(&self) -> &TRACE_EVENT_INFO {
134 | let p = self.data.cast::();
135 | unsafe {
136 | // Safety: the API enforces self.data to point to a valid, allocated TRACE_EVENT_INFO
137 | p.as_ref().unwrap()
138 | }
139 | }
140 |
141 | pub fn provider_guid(&self) -> GUID {
142 | self.as_raw().ProviderGuid
143 | }
144 |
145 | pub fn event_id(&self) -> u16 {
146 | self.as_raw().EventDescriptor.Id
147 | }
148 |
149 | pub fn event_version(&self) -> u8 {
150 | self.as_raw().EventDescriptor.Version
151 | }
152 |
153 | pub fn decoding_source(&self) -> DecodingSource {
154 | let ds = self.as_raw().DecodingSource;
155 | DecodingSource::from(ds)
156 | }
157 |
158 | pub fn provider_name(&self) -> String {
159 | extract_utf16_string!(self, ProviderNameOffset);
160 | }
161 |
162 | pub fn task_name(&self) -> String {
163 | extract_utf16_string!(self, TaskNameOffset);
164 | }
165 |
166 | pub fn opcode_name(&self) -> String {
167 | extract_utf16_string!(self, OpcodeNameOffset);
168 | }
169 |
170 | pub fn properties(&self) -> PropertyIterator {
171 | PropertyIterator::new(self)
172 | }
173 | }
174 |
175 | impl Drop for TraceEventInfo {
176 | fn drop(&mut self) {
177 | unsafe {
178 | // Safety:
179 | // * ptr is a block of memory currently allocated via alloc::alloc
180 | // * layout is th one that was used to allocate that block of memory
181 | std::alloc::dealloc(self.mut_data_for_dealloc, self.layout);
182 | }
183 | }
184 | }
185 |
186 | pub struct PropertyIterator<'info> {
187 | next_index: u32,
188 | count: u32,
189 | te_info: &'info TraceEventInfo,
190 | }
191 |
192 | impl<'info> PropertyIterator<'info> {
193 | fn new(te_info: &'info TraceEventInfo) -> Self {
194 | let count = te_info.as_raw().PropertyCount;
195 | Self {
196 | next_index: 0,
197 | count,
198 | te_info,
199 | }
200 | }
201 | }
202 |
203 | impl<'info> Iterator for PropertyIterator<'info> {
204 | type Item = Result;
205 |
206 | fn next(&mut self) -> Option {
207 | if self.next_index == self.count {
208 | return None;
209 | }
210 |
211 | let properties_array = &self.te_info.as_raw().EventPropertyInfoArray;
212 | let properties_array = properties_array as *const EVENT_PROPERTY_INFO;
213 | let cur_property_ptr = unsafe {
214 | // Safety:
215 | // * index being in the right bounds, this guarantees the resulting pointer lies in the same allocated object
216 | properties_array.offset(self.next_index as isize) // we assume there will not be more than 2 billion properties for an event
217 | };
218 | let curr_prop = unsafe {
219 | // Safety:
220 | // * this pointer has been allocated by a Microsoft API
221 | match cur_property_ptr.as_ref() {
222 | None => {
223 | // This should not happen, as there is no reason the Microsoft API has put a null pointer at an index below self.count
224 | // Ideally, I probably should return an `Err` here. But I prefer keeping a simple return type, and stop the iteration here in case this (normally impossible error) happens
225 | return None;
226 | }
227 | Some(r) => r,
228 | }
229 | };
230 |
231 | let te_info_data = self.te_info.as_raw() as *const TRACE_EVENT_INFO as *const u8;
232 | let property_name_offset = curr_prop.NameOffset;
233 | let property_name_ptr = unsafe {
234 | // Safety: offset comes from a Microsoft API
235 | te_info_data.offset(property_name_offset as isize)
236 | };
237 | if property_name_ptr.is_null() {
238 | // This is really a safety net, there is no reason the offset nullifies the base pointer
239 | // This is not supposed to happen, so a simple `None` (instead of a proper `Err`) will do
240 | return None;
241 | }
242 |
243 | let property_name = unsafe {
244 | // Safety:
245 | // * we trust Microsoft for providing correctly aligned data
246 | // * we will copy into a String before the buffer gets invalid
247 | U16CStr::from_ptr_str(property_name_ptr as *const u16)
248 | };
249 | let property_name = property_name.to_string_lossy();
250 |
251 | self.next_index += 1;
252 | Some(Property::new(property_name, curr_prop))
253 | }
254 | }
255 |
256 | pub fn property_size(event: &EventRecord, name: &str) -> TdhNativeResult {
257 | let mut property_size = 0;
258 |
259 | let name = name.into_utf16();
260 | let desc = Etw::PROPERTY_DATA_DESCRIPTOR {
261 | ArrayIndex: u32::MAX,
262 | PropertyName: name.as_ptr() as u64,
263 | ..Default::default()
264 | };
265 |
266 | unsafe {
267 | let status = Etw::TdhGetPropertySize(event.as_raw_ptr(), None, &[desc], &mut property_size);
268 | if status != 0 {
269 | return Err(TdhNativeError::IoError(std::io::Error::from_raw_os_error(
270 | status as i32,
271 | )));
272 | }
273 | }
274 |
275 | Ok(property_size)
276 | }
277 |
--------------------------------------------------------------------------------
/src/native/tdh_types.rs:
--------------------------------------------------------------------------------
1 | //! Basic TDH types
2 | //!
3 | //! The `tdh_type` module provides an abstraction over the basic TDH types, this module act as a
4 | //! helper for the parser to determine which IN and OUT type are expected from a property within an
5 | //! event
6 | //!
7 | //! This is a bit extra but is basically a redefinition of the In an Out TDH types following the
8 | //! rust naming convention, it can also come in handy when implementing the `TryParse` trait for a type
9 | //! to determine how to handle a [Property] based on this values
10 | //!
11 | //! [Property]: crate::native::tdh_types::Property
12 | use num_traits::FromPrimitive;
13 |
14 | use windows::Win32::System::Diagnostics::Etw;
15 |
16 | #[derive(Debug, Clone)]
17 | pub enum PropertyError {
18 | /// Parsing complex types in properties is not supported in this crate
19 | /// (yet? See )
20 | UnimplementedType(&'static str),
21 | }
22 |
23 | impl std::fmt::Display for PropertyError {
24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 | match self {
26 | Self::UnimplementedType(s) => write!(f, "unimplemented type: {}", s),
27 | }
28 | }
29 | }
30 |
31 | /// Notes if the property count is a concrete length or an index into another property.
32 | #[derive(Debug, Clone, Copy, PartialEq)]
33 | pub enum PropertyCount {
34 | Count(u16),
35 | Index(u16),
36 | }
37 |
38 | impl Default for PropertyCount {
39 | fn default() -> Self {
40 | PropertyCount::Count(0)
41 | }
42 | }
43 |
44 | /// Notes if the property length is a concrete length or an index to another property
45 | /// which contains the length.
46 | #[derive(Debug, Clone, Copy, PartialEq)]
47 | pub enum PropertyLength {
48 | Length(u16),
49 | Index(u16),
50 | }
51 |
52 | impl Default for PropertyLength {
53 | fn default() -> Self {
54 | PropertyLength::Length(0)
55 | }
56 | }
57 |
58 | #[derive(Debug, Clone)]
59 | pub enum PropertyInfo {
60 | Value {
61 | /// TDH In type of the property
62 | in_type: TdhInType,
63 | /// TDH Out type of the property
64 | out_type: TdhOutType,
65 | /// The length of the property
66 | length: PropertyLength,
67 | },
68 | Array {
69 | /// TDH In type of the property
70 | in_type: TdhInType,
71 | /// TDH Out type of the property
72 | out_type: TdhOutType,
73 | /// The length of the property
74 | length: PropertyLength,
75 | /// Number of elements.
76 | count: PropertyCount,
77 | },
78 | }
79 |
80 | impl Default for PropertyInfo {
81 | fn default() -> Self {
82 | PropertyInfo::Value {
83 | in_type: Default::default(),
84 | out_type: Default::default(),
85 | length: Default::default(),
86 | }
87 | }
88 | }
89 |
90 | /// Attributes of a property
91 | #[derive(Debug, Clone, Default)]
92 | pub struct Property {
93 | /// Name of the Property
94 | pub name: String,
95 | /// Represent the [PropertyFlags]
96 | pub flags: PropertyFlags,
97 | /// Information about the property.
98 | pub info: PropertyInfo,
99 | }
100 |
101 | #[doc(hidden)]
102 | impl Property {
103 | pub fn new(name: String, property: &Etw::EVENT_PROPERTY_INFO) -> Result {
104 | let flags = PropertyFlags::from(property.Flags);
105 |
106 | if flags.contains(PropertyFlags::PROPERTY_STRUCT) {
107 | Err(PropertyError::UnimplementedType("structure"))
108 | } else if flags.contains(PropertyFlags::PROPERTY_HAS_CUSTOM_SCHEMA) {
109 | Err(PropertyError::UnimplementedType("has custom schema"))
110 | } else {
111 | // The property is a non-struct type. It makes sense to access these fields of the unions
112 | let ot = unsafe { property.Anonymous1.nonStructType.OutType };
113 | let it = unsafe { property.Anonymous1.nonStructType.InType };
114 |
115 | let length = if flags.contains(PropertyFlags::PROPERTY_PARAM_LENGTH) {
116 | // The property length is stored in another property, this is the index of that property
117 | PropertyLength::Index(unsafe { property.Anonymous3.lengthPropertyIndex })
118 | } else {
119 | // The property has no param for its length, it makes sense to access this field of the union
120 | PropertyLength::Length(unsafe { property.Anonymous3.length })
121 | };
122 |
123 | let count = if flags.contains(PropertyFlags::PROPERTY_PARAM_COUNT) {
124 | unsafe {
125 | if property.Anonymous2.countPropertyIndex > 1 {
126 | Some(PropertyCount::Index(property.Anonymous2.countPropertyIndex))
127 | } else {
128 | None
129 | }
130 | }
131 | } else {
132 | unsafe {
133 | if property.Anonymous2.count > 1 {
134 | Some(PropertyCount::Count(property.Anonymous2.count))
135 | } else {
136 | None
137 | }
138 | }
139 | };
140 |
141 | let out_type = FromPrimitive::from_u16(ot).unwrap_or(TdhOutType::OutTypeNull);
142 |
143 | let in_type = FromPrimitive::from_u16(it).unwrap_or(TdhInType::InTypeNull);
144 |
145 | match count {
146 | Some(c) => Ok(Property {
147 | name,
148 | flags,
149 | info: PropertyInfo::Array {
150 | in_type,
151 | out_type,
152 | length,
153 | count: c,
154 | },
155 | }),
156 | None => Ok(Property {
157 | name,
158 | flags,
159 | info: PropertyInfo::Value {
160 | in_type,
161 | out_type,
162 | length,
163 | },
164 | }),
165 | }
166 | }
167 | }
168 | }
169 |
170 | /// Represent a TDH_IN_TYPE
171 | #[repr(u16)]
172 | #[derive(Debug, Clone, Copy, FromPrimitive, ToPrimitive, PartialEq, Eq, Default)]
173 | pub enum TdhInType {
174 | // Deprecated values are not defined
175 | #[default]
176 | InTypeNull,
177 | InTypeUnicodeString,
178 | InTypeAnsiString,
179 | InTypeInt8, // Field size is 1 byte
180 | InTypeUInt8, // Field size is 1 byte
181 | InTypeInt16, // Field size is 2 bytes
182 | InTypeUInt16, // Field size is 2 bytes
183 | InTypeInt32, // Field size is 4 bytes
184 | InTypeUInt32, // Field size is 4 bytes
185 | InTypeInt64, // Field size is 8 bytes
186 | InTypeUInt64, // Field size is 8 bytes
187 | InTypeFloat, // Field size is 4 bytes
188 | InTypeDouble, // Field size is 8 bytes
189 | InTypeBoolean, // Field size is 4 bytes
190 | InTypeBinary, // Depends on the OutType
191 | InTypeGuid,
192 | InTypePointer,
193 | InTypeFileTime, // Field size is 8 bytes
194 | InTypeSystemTime, // Field size is 16 bytes
195 | InTypeSid, // Field size determined by the first few bytes of the field
196 | InTypeHexInt32,
197 | InTypeHexInt64,
198 | InTypeCountedString = 300,
199 | }
200 |
201 | /// Represent a TDH_OUT_TYPE
202 | #[repr(u16)]
203 | #[derive(Debug, Clone, Copy, FromPrimitive, ToPrimitive, PartialEq, Eq, Default)]
204 | pub enum TdhOutType {
205 | #[default]
206 | OutTypeNull,
207 | OutTypeString,
208 | OutTypeDateTime,
209 | OutTypeInt8, // Field size is 1 byte
210 | OutTypeUInt8, // Field size is 1 byte
211 | OutTypeInt16, // Field size is 2 bytes
212 | OutTypeUInt16, // Field size is 2 bytes
213 | OutTypeInt32, // Field size is 4 bytes
214 | OutTypeUInt32, // Field size is 4 bytes
215 | OutTypeInt64, // Field size is 8 bytes
216 | OutTypeUInt64, // Field size is 8 bytes
217 | OutTypeFloat, // Field size is 4 bytes
218 | OutTypeDouble, // Field size is 8 bytes
219 | OutTypeBoolean, // Field size is 4 bytes
220 | OutTypeGuid,
221 | OutTypeHexBinary,
222 | OutTypeHexInt8,
223 | OutTypeHexInt16,
224 | OutTypeHexInt32,
225 | OutTypeHexInt64,
226 | OutTypePid,
227 | OutTypeTid,
228 | OutTypePort,
229 | OutTypeIpv4,
230 | OutTypeIpv6,
231 | OutTypeWin32Error = 30,
232 | OutTypeNtStatus = 31,
233 | OutTypeHResult = 32,
234 | OutTypeJson = 34,
235 | OutTypeUtf8 = 35,
236 | OutTypePkcs7 = 36,
237 | OutTypeCodePointer = 37,
238 | OutTypeDatetimeUtc = 38,
239 | }
240 |
241 | bitflags! {
242 | /// Represents the Property flags
243 | ///
244 | /// See: [Property Flags enum](https://docs.microsoft.com/en-us/windows/win32/api/tdh/ne-tdh-property_flags)
245 | #[derive(Default)]
246 | pub struct PropertyFlags: u32 {
247 | const PROPERTY_STRUCT = 0x1;
248 | const PROPERTY_PARAM_LENGTH = 0x2;
249 | const PROPERTY_PARAM_COUNT = 0x4;
250 | const PROPERTY_WBEMXML_FRAGMENT = 0x8;
251 | const PROPERTY_PARAM_FIXED_LENGTH = 0x10;
252 | const PROPERTY_PARAM_FIXED_COUNT = 0x20;
253 | const PROPERTY_HAS_TAGS = 0x40;
254 | const PROPERTY_HAS_CUSTOM_SCHEMA = 0x80;
255 | }
256 | }
257 |
258 | impl From for PropertyFlags {
259 | fn from(val: Etw::PROPERTY_FLAGS) -> Self {
260 | let flags: i32 = val.0;
261 | // Should be a safe cast
262 | PropertyFlags::from_bits_truncate(flags as u32)
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/src/native/time.rs:
--------------------------------------------------------------------------------
1 | //! Implements wrappers for various Windows time structures.
2 | use windows::Win32::{
3 | Foundation::{FILETIME, SYSTEMTIME},
4 | System::Time::SystemTimeToFileTime,
5 | };
6 |
7 | /// Wrapper for [FILETIME](https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime)
8 | #[derive(Copy, Clone, Default)]
9 | #[repr(transparent)]
10 | pub struct FileTime(pub(crate) FILETIME);
11 |
12 | const SECONDS_BETWEEN_1601_AND_1970: i64 = 11_644_473_600;
13 | const NS_IN_SECOND: i64 = 1_000_000_000;
14 | const MS_IN_SECOND: i64 = 1_000;
15 |
16 | impl FileTime {
17 | /// Converts to a unix timestamp with millisecond granularity.
18 | pub fn as_unix_timestamp(&self) -> i64 {
19 | self.as_quad() / 10_000 - (SECONDS_BETWEEN_1601_AND_1970 * MS_IN_SECOND)
20 | }
21 |
22 | /// Converts to a unix timestamp with nanosecond granularity.
23 | pub fn as_unix_timestamp_nanos(&self) -> i128 {
24 | self.as_quad() as i128 * 100
25 | - (SECONDS_BETWEEN_1601_AND_1970 as i128 * NS_IN_SECOND as i128)
26 | }
27 |
28 | /// Converts to OffsetDateTime
29 | #[cfg(feature = "time_rs")]
30 | pub fn as_date_time(&self) -> time::OffsetDateTime {
31 | time::OffsetDateTime::from_unix_timestamp_nanos(self.as_unix_timestamp_nanos()).unwrap()
32 | }
33 |
34 | fn as_quad(&self) -> i64 {
35 | let mut quad = self.0.dwHighDateTime as i64;
36 | quad <<= 32;
37 | quad |= self.0.dwHighDateTime as i64;
38 | quad
39 | }
40 |
41 | #[cfg(any(feature = "time_rs", feature = "serde"))]
42 | pub(crate) fn from_quad(quad: i64) -> Self {
43 | let mut file_time: FileTime = Default::default();
44 | file_time.0.dwHighDateTime = (quad >> 32) as u32;
45 | file_time.0.dwLowDateTime = (quad & 0xffffffff) as u32;
46 | file_time
47 | }
48 |
49 | pub(crate) fn from_slice(slice: &[u8; std::mem::size_of::()]) -> Self {
50 | let ptr = slice.as_ptr() as *const FileTime;
51 | let mut file_time: FileTime = Default::default();
52 | unsafe {
53 | file_time.0.dwHighDateTime = (*ptr).0.dwHighDateTime;
54 | file_time.0.dwLowDateTime = (*ptr).0.dwLowDateTime;
55 | }
56 | file_time
57 | }
58 | }
59 |
60 | #[cfg(feature = "time_rs")]
61 | impl From for time::OffsetDateTime {
62 | fn from(file_time: FileTime) -> Self {
63 | file_time.as_date_time()
64 | }
65 | }
66 |
67 | #[cfg(feature = "serde")]
68 | impl serde::ser::Serialize for FileTime {
69 | #[cfg(feature = "time_rs")]
70 | fn serialize(&self, serializer: S) -> Result
71 | where
72 | S: serde::Serializer,
73 | {
74 | self.as_date_time().serialize(serializer)
75 | }
76 |
77 | #[cfg(not(feature = "time_rs"))]
78 | fn serialize(&self, serializer: S) -> Result
79 | where
80 | S: serde::Serializer,
81 | {
82 | self.as_unix_timestamp().serialize(serializer)
83 | }
84 | }
85 |
86 | /// Wrapper for [SYSTEMTIME](https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime)
87 | #[derive(Copy, Clone, Default)]
88 | #[repr(transparent)]
89 | pub struct SystemTime(pub(crate) SYSTEMTIME);
90 |
91 | impl SystemTime {
92 | /// Converts to a unix timestamp with millisecond granularity.
93 | pub fn as_unix_timestamp(&self) -> i64 {
94 | let file_time: FileTime = Default::default();
95 | unsafe {
96 | _ = SystemTimeToFileTime(&self.0 as *const _, &file_time.0 as *const _ as *mut _);
97 | }
98 | file_time.as_unix_timestamp()
99 | }
100 |
101 | /// Converts to a unix timestamp with nanosecond granularity.
102 | pub fn as_unix_timestamp_nanos(&self) -> i128 {
103 | let file_time: FileTime = Default::default();
104 | unsafe {
105 | _ = SystemTimeToFileTime(&self.0 as *const _, &file_time.0 as *const _ as *mut _);
106 | }
107 | file_time.as_unix_timestamp_nanos()
108 | }
109 |
110 | /// Converts to OffsetDateTime
111 | #[cfg(feature = "time_rs")]
112 | pub fn as_date_time(&self) -> time::OffsetDateTime {
113 | time::OffsetDateTime::from_unix_timestamp_nanos(self.as_unix_timestamp_nanos()).unwrap()
114 | }
115 |
116 | pub(crate) fn from_slice(slice: &[u8; std::mem::size_of::()]) -> Self {
117 | let ptr = slice.as_ptr() as *const SystemTime;
118 | let mut system_time: SystemTime = Default::default();
119 | unsafe {
120 | system_time.0.wYear = (*ptr).0.wYear;
121 | system_time.0.wMonth = (*ptr).0.wMonth;
122 | system_time.0.wDayOfWeek = (*ptr).0.wDayOfWeek;
123 | system_time.0.wDay = (*ptr).0.wDay;
124 | system_time.0.wHour = (*ptr).0.wHour;
125 | system_time.0.wMinute = (*ptr).0.wMinute;
126 | system_time.0.wMilliseconds = (*ptr).0.wMilliseconds;
127 | }
128 | system_time
129 | }
130 | }
131 |
132 | #[cfg(feature = "time_rs")]
133 | impl From for time::OffsetDateTime {
134 | fn from(file_time: SystemTime) -> Self {
135 | file_time.as_date_time()
136 | }
137 | }
138 |
139 | #[cfg(feature = "serde")]
140 | impl serde::ser::Serialize for SystemTime {
141 | #[cfg(feature = "time_rs")]
142 | fn serialize(&self, serializer: S) -> Result
143 | where
144 | S: serde::Serializer,
145 | {
146 | self.as_date_time().serialize(serializer)
147 | }
148 |
149 | #[cfg(not(feature = "time_rs"))]
150 | fn serialize(&self, serializer: S) -> Result
151 | where
152 | S: serde::Serializer,
153 | {
154 | self.as_unix_timestamp().serialize(serializer)
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/native/version_helper.rs:
--------------------------------------------------------------------------------
1 | //! Native API - Version Helper
2 | //!
3 | //! The `version_helper` module is an abstraction layer over the Version Helper API/Macro which allow
4 | //! us to determine the Windows OS system version
5 | //!
6 | //! At the moment the only option available is to check if the actual System Version is greater than
7 | //! Win8, is the only check we need for the crate to work as expected
8 | use windows::core::HRESULT;
9 | use windows::Win32::Foundation::GetLastError;
10 | use windows::Win32::Foundation::ERROR_OLD_WIN_VERSION;
11 | use windows::Win32::System::SystemInformation::{VerSetConditionMask, VerifyVersionInfoA};
12 | use windows::Win32::System::SystemInformation::{
13 | OSVERSIONINFOEXA, VER_MAJORVERSION, VER_MINORVERSION, VER_SERVICEPACKMAJOR,
14 | };
15 |
16 | /// Version Helper native error
17 | #[derive(Debug)]
18 | pub enum VersionHelperError {
19 | /// Represents an standard IO Error
20 | IoError(std::io::Error),
21 | }
22 |
23 | pub(crate) type VersionHelperResult = Result;
24 |
25 | type OsVersionInfo = OSVERSIONINFOEXA;
26 | // Safe cast, we now the value fits in a u8 (VER_GREATER_EQUAL == 3)
27 | const VER_GREATER_OR_EQUAL: u8 = windows::Win32::System::SystemServices::VER_GREATER_EQUAL as u8;
28 |
29 | fn verify_system_version(major: u8, minor: u8, sp_major: u16) -> VersionHelperResult {
30 | let mut os_version = OsVersionInfo {
31 | dwOSVersionInfoSize: std::mem::size_of::() as u32,
32 | dwMajorVersion: major as u32,
33 | dwMinorVersion: minor as u32,
34 | wServicePackMajor: sp_major,
35 | ..Default::default()
36 | };
37 |
38 | let mut condition_mask = 0;
39 | let res = unsafe {
40 | condition_mask =
41 | VerSetConditionMask(condition_mask, VER_MAJORVERSION, VER_GREATER_OR_EQUAL);
42 | condition_mask =
43 | VerSetConditionMask(condition_mask, VER_MINORVERSION, VER_GREATER_OR_EQUAL);
44 | condition_mask =
45 | VerSetConditionMask(condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_OR_EQUAL);
46 |
47 | VerifyVersionInfoA(
48 | &mut os_version,
49 | VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR,
50 | condition_mask,
51 | )
52 | };
53 |
54 | // See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-verifyversioninfoa#return-value
55 | match res {
56 | Ok(_) => Ok(true),
57 | Err(e) => match e.code() {
58 | e if e == HRESULT::from_win32(ERROR_OLD_WIN_VERSION.0) => Ok(false),
59 | _ => Err(VersionHelperError::IoError(
60 | std::io::Error::from_raw_os_error(unsafe { GetLastError() }.0 as i32),
61 | )),
62 | },
63 | }
64 | }
65 |
66 | ///
67 | /// # Remarks
68 | ///
69 | pub fn is_win8_or_greater() -> bool {
70 | // Lazy way, let's hardcode this...
71 | match verify_system_version(6, 2, 0) {
72 | Ok(res) => res,
73 | Err(err) => {
74 | log::warn!("Unable ro verify system version: {:?}", err);
75 | true
76 | }
77 | }
78 | }
79 |
80 | #[cfg(test)]
81 | mod test {
82 | use super::*;
83 |
84 | #[test]
85 | // Let's assume this test won't be run on a version of Windows older than XP :D
86 | fn test_verify_system_version() {
87 | match verify_system_version(5, 1, 0) {
88 | Ok(res) => assert!(res),
89 | Err(err) => panic!("VersionHelper error: {:?}", err),
90 | };
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/property.rs:
--------------------------------------------------------------------------------
1 | //! ETW Event Property information
2 | //!
3 | //! The `property` module expose the basic structures that represent the `Properties` an Event contains,
4 | //! based on its [`Schema`](crate::schema::Schema). These `Properties` can then be used to parse accordingly their values.
5 | use crate::native::tdh_types::Property;
6 |
7 | /// A slice to the data of a `Property` for a given ETW record.
8 | #[derive(Clone, Copy, Debug)]
9 | pub struct PropertySlice<'property, 'record> {
10 | /// Property attributes
11 | pub property: &'property Property,
12 | /// Buffer with the Property data
13 | pub buffer: &'record [u8],
14 | }
15 |
--------------------------------------------------------------------------------
/src/provider.rs:
--------------------------------------------------------------------------------
1 | //! ETW Providers abstraction.
2 | //!
3 | //! Provides an abstraction over an [ETW Provider](https://docs.microsoft.com/en-us/windows/win32/etw/about-event-tracing#providers)
4 | use crate::native::etw_types::event_record::EventRecord;
5 | use crate::native::pla;
6 | use crate::schema_locator::SchemaLocator;
7 |
8 | use std::sync::{Arc, RwLock};
9 | use windows::core::GUID;
10 |
11 | pub(crate) mod event_filter;
12 | pub use event_filter::EventFilter;
13 |
14 | pub mod kernel_providers;
15 | mod trace_flags;
16 | pub use trace_flags::TraceFlags;
17 |
18 | /// Provider module errors
19 | #[derive(Debug)]
20 | pub enum ProviderError {
21 | /// Wrapper over an internal [PlaError](crate::native::PlaError)
22 | ComProvider(crate::native::PlaError),
23 | }
24 |
25 | impl From for ProviderError {
26 | fn from(err: crate::native::PlaError) -> Self {
27 | ProviderError::ComProvider(err)
28 | }
29 | }
30 |
31 | /// Describes an ETW Provider to use, along with its options
32 | pub struct Provider {
33 | /// Provider GUID
34 | guid: GUID,
35 | /// Provider Any keyword
36 | any: u64,
37 | /// Provider All keyword
38 | all: u64,
39 | /// Provider level flag
40 | level: u8,
41 | /// Provider trace flags
42 | ///
43 | /// Used as `EnableParameters.EnableProperty` when starting the trace (using [EnableTraceEx2](https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2))
44 | trace_flags: TraceFlags,
45 | /// Provider kernel flags, only apply to KernelProvider
46 | kernel_flags: u32,
47 | /// Provider filters
48 | filters: Vec,
49 | /// Callbacks that will receive events from this Provider
50 | callbacks: Arc>>,
51 | }
52 |
53 | /// A Builder for a `Provider`
54 | ///
55 | /// See [`Provider`] for various functions that create `ProviderBuilder`s.
56 | pub struct ProviderBuilder {
57 | guid: GUID,
58 | any: u64,
59 | all: u64,
60 | level: u8,
61 | trace_flags: TraceFlags,
62 | kernel_flags: u32,
63 | filters: Vec,
64 | callbacks: Arc>>,
65 | }
66 |
67 | impl std::fmt::Debug for ProviderBuilder {
68 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 | f.debug_struct("ProviderBuilder")
70 | .field("guid", &self.guid)
71 | .field("any", &self.any)
72 | .field("all", &self.all)
73 | .field("level", &self.level)
74 | .field("trace_flags", &self.trace_flags)
75 | .field("kernel_flags", &self.kernel_flags)
76 | .field("filters", &self.filters)
77 | .field("n_callbacks", &self.callbacks.read().unwrap().len())
78 | .finish()
79 | }
80 | }
81 |
82 | // Create builders
83 | impl Provider {
84 | /// Create a Provider defined by its GUID
85 | ///
86 | /// Many types [implement `Into`](https://microsoft.github.io/windows-docs-rs/doc/windows/core/struct.GUID.html#trait-implementations)
87 | /// and are acceptable as argument: `GUID` themselves, but also `&str`, etc.
88 | pub fn by_guid>(guid: G) -> ProviderBuilder {
89 | ProviderBuilder {
90 | guid: guid.into(),
91 | any: 0,
92 | all: 0,
93 | level: 5,
94 | trace_flags: TraceFlags::empty(),
95 | kernel_flags: 0,
96 | filters: Vec::new(),
97 | callbacks: Arc::new(RwLock::new(Vec::new())),
98 | }
99 | }
100 |
101 | /// Create a Kernel Provider
102 | ///
103 | /// You can pass either a KernelProvider you have created yourself, or one of the standard providers from [`crate::provider::kernel_providers`].
104 | pub fn kernel(kernel_provider: &kernel_providers::KernelProvider) -> ProviderBuilder {
105 | let mut builder = Self::by_guid(kernel_provider.guid);
106 | builder.kernel_flags = kernel_provider.flags;
107 | builder
108 | }
109 |
110 | /// Create a Provider defined by its name.
111 | ///
112 | /// This function will look for the Provider GUID by means of the [ITraceDataProviderCollection](https://docs.microsoft.com/en-us/windows/win32/api/pla/nn-pla-itracedataprovidercollection)
113 | /// interface.
114 | ///
115 | /// # Remark
116 | /// This function is considerably slow, prefer using the `by_guid` function when possible
117 | ///
118 | /// # Example
119 | /// ```
120 | /// # use ferrisetw::provider::Provider;
121 | /// let my_provider = Provider::by_name("Microsoft-Windows-WinINet").unwrap().build();
122 | /// ```
123 | pub fn by_name(name: &str) -> Result {
124 | let guid = unsafe { pla::get_provider_guid(name) }?;
125 | Ok(Self::by_guid(guid))
126 | }
127 | }
128 |
129 | // Actually use the Provider
130 | impl Provider {
131 | pub fn guid(&self) -> GUID {
132 | self.guid
133 | }
134 | pub fn any(&self) -> u64 {
135 | self.any
136 | }
137 | pub fn all(&self) -> u64 {
138 | self.all
139 | }
140 | pub fn level(&self) -> u8 {
141 | self.level
142 | }
143 | pub fn trace_flags(&self) -> TraceFlags {
144 | self.trace_flags
145 | }
146 | pub fn kernel_flags(&self) -> u32 {
147 | self.kernel_flags
148 | }
149 | pub fn filters(&self) -> &[EventFilter] {
150 | &self.filters
151 | }
152 |
153 | pub(crate) fn on_event(&self, record: &EventRecord, locator: &SchemaLocator) {
154 | if let Ok(mut callbacks) = self.callbacks.write() {
155 | callbacks.iter_mut().for_each(|cb| cb(record, locator))
156 | };
157 | }
158 | }
159 |
160 | impl std::fmt::Debug for Provider {
161 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 | f.debug_struct("Provider")
163 | .field("guid", &self.guid)
164 | .field("any", &self.any)
165 | .field("all", &self.all)
166 | .field("level", &self.level)
167 | .field("trace_flags", &self.trace_flags)
168 | .field("kernel_flags", &self.kernel_flags)
169 | .field("filters", &self.filters)
170 | .field("callbacks", &self.callbacks.read().unwrap().len())
171 | .finish()
172 | }
173 | }
174 |
175 | impl ProviderBuilder {
176 | /// Set the `any` flag in the Provider instance
177 | /// [More info](https://docs.microsoft.com/en-us/message-analyzer/system-etw-provider-event-keyword-level-settings#filtering-with-system-etw-provider-event-keywords-and-levels)
178 | ///
179 | /// # Example
180 | /// ```
181 | /// # use ferrisetw::provider::Provider;
182 | /// let my_provider = Provider::by_guid("1EDEEE53-0AFE-4609-B846-D8C0B2075B1F").any(0xf0010000000003ff).build();
183 | /// ```
184 | pub fn any(mut self, any: u64) -> Self {
185 | self.any = any;
186 | self
187 | }
188 |
189 | /// Set the `all` flag in the Provider instance
190 | /// [More info](https://docs.microsoft.com/en-us/message-analyzer/system-etw-provider-event-keyword-level-settings#filtering-with-system-etw-provider-event-keywords-and-levels)
191 | ///
192 | /// # Example
193 | /// ```
194 | /// # use ferrisetw::provider::Provider;
195 | /// let my_provider = Provider::by_guid("1EDEEE53-0AFE-4609-B846-D8C0B2075B1F").all(0x4000000000000000).build();
196 | /// ```
197 | pub fn all(mut self, all: u64) -> Self {
198 | self.all = all;
199 | self
200 | }
201 |
202 | /// Set the `level` flag in the Provider instance
203 | ///
204 | /// # Example
205 | /// ```
206 | /// # use ferrisetw::provider::{Provider};
207 | /// // LogAlways (0x0)
208 | /// // Critical (0x1)
209 | /// // Error (0x2)
210 | /// // Warning (0x3)
211 | /// // Information (0x4)
212 | /// // Verbose (0x5)
213 | /// let my_provider = Provider::by_guid("1EDEEE53-0AFE-4609-B846-D8C0B2075B1F").level(0x5).build();
214 | /// ```
215 | pub fn level(mut self, level: u8) -> Self {
216 | self.level = level;
217 | self
218 | }
219 |
220 | /// Set the `trace_flags` flag in the Provider instance
221 | /// [More info](https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/trace-flags)
222 | ///
223 | /// # Example
224 | /// ```
225 | /// # use ferrisetw::provider::{Provider, TraceFlags};
226 | /// let my_provider = Provider::by_guid("1EDEEE53-0AFE-4609-B846-D8C0B2075B1F").trace_flags(TraceFlags::EVENT_ENABLE_PROPERTY_SID).build();
227 | /// ```
228 | pub fn trace_flags(mut self, trace_flags: TraceFlags) -> Self {
229 | self.trace_flags = trace_flags;
230 | self
231 | }
232 |
233 | /// Add a callback function that will be called when the Provider generates an Event
234 | ///
235 | /// # Notes
236 | ///
237 | /// The callback will be run on a background thread (the one that is blocked on the `process` function).
238 | ///
239 | /// # Example
240 | /// ```
241 | /// # use ferrisetw::provider::Provider;
242 | /// # use ferrisetw::trace::UserTrace;
243 | /// # use ferrisetw::EventRecord;
244 | /// # use ferrisetw::schema_locator::SchemaLocator;
245 | /// let provider = Provider::by_guid("1EDEEE53-0AFE-4609-B846-D8C0B2075B1F").add_callback(|record: &EventRecord, schema_locator: &SchemaLocator| {
246 | /// // Handle Event
247 | /// }).build();
248 | /// UserTrace::new().enable(provider).start().unwrap();
249 | /// ```
250 | ///
251 | /// [SchemaLocator]: crate::schema_locator::SchemaLocator
252 | pub fn add_callback(self, callback: T) -> Self
253 | where
254 | T: FnMut(&EventRecord, &SchemaLocator) + Send + Sync + 'static,
255 | {
256 | if let Ok(mut callbacks) = self.callbacks.write() {
257 | callbacks.push(Box::new(callback));
258 | }
259 | self
260 | }
261 |
262 | /// Add a filter to this Provider.
263 | ///
264 | /// Adding multiple filters will bind them with an `AND` relationship.
265 | /// If you want an `OR` relationship, include them in the same `EventFilter`.
266 | ///
267 | /// # Example
268 | /// ```
269 | /// # use ferrisetw::provider::{EventFilter, Provider};
270 | /// let only_events_18_or_42 = EventFilter::ByEventIds(vec![18, 42]);
271 | /// let only_pid_1234 = EventFilter::ByPids(vec![1234]);
272 | ///
273 | /// Provider::by_guid("22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716")
274 | /// .add_filter(only_events_18_or_42)
275 | /// .add_filter(only_pid_1234)
276 | /// .build();
277 | /// ```
278 | pub fn add_filter(mut self, filter: EventFilter) -> Self {
279 | self.filters.push(filter);
280 | self
281 | }
282 |
283 | /// Build the provider
284 | ///
285 | /// # Example
286 | /// ```
287 | /// # use ferrisetw::provider::Provider;
288 | /// # use ferrisetw::EventRecord;
289 | /// # use ferrisetw::schema_locator::SchemaLocator;
290 | /// # let process_callback = |_event: &EventRecord, _locator: &SchemaLocator| {};
291 | /// Provider::by_guid("22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716") // Microsoft-Windows-Kernel-Process
292 | /// .add_callback(process_callback)
293 | /// .build();
294 | /// ```
295 | // TODO: should we check if callbacks is empty ???
296 | pub fn build(self) -> Provider {
297 | Provider {
298 | guid: self.guid,
299 | any: self.any,
300 | all: self.all,
301 | level: self.level,
302 | trace_flags: self.trace_flags,
303 | kernel_flags: self.kernel_flags,
304 | filters: self.filters,
305 | callbacks: self.callbacks,
306 | }
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/src/provider/event_filter.rs:
--------------------------------------------------------------------------------
1 | use std::alloc::Layout;
2 | use std::error::Error;
3 |
4 | use windows::Win32::Foundation::BOOLEAN;
5 | use windows::Win32::System::Diagnostics::Etw::{
6 | EVENT_FILTER_DESCRIPTOR, EVENT_FILTER_EVENT_ID, EVENT_FILTER_TYPE_EVENT_ID,
7 | EVENT_FILTER_TYPE_PID,
8 | };
9 | use windows::Win32::System::Diagnostics::Etw::{
10 | MAX_EVENT_FILTER_EVENT_ID_COUNT, MAX_EVENT_FILTER_PID_COUNT,
11 | };
12 |
13 | /// Specifies how this provider will filter its events
14 | ///
15 | /// Some filters are not effective prior to Windows 8.1 ([source](https://learn.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_filter_descriptor#remarks))
16 | #[derive(Debug)]
17 | pub enum EventFilter {
18 | /// Filter by PID.
19 | /// This is only effective on kernel mode logger session.
20 | /// TODO: even for `KernelTrace`, this does not seem to work.
21 | /// Maybe there's a distinction between "a trace run in kernel-mode" and a "System trace"?
22 | /// See
23 | ByPids(Vec),
24 | /// Filter by ETW Event ID.
25 | ByEventIds(Vec),
26 | // TODO: see https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_filter_descriptor
27 | // and https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2#remarks
28 | // other filter types are possible
29 | // I'm not always sure what they mean though
30 | }
31 |
32 | impl EventFilter {
33 | /// Builds an EventFilterDescriptor (which can in turn generate an EVENT_FILTER_DESCRIPTOR)
34 | pub fn to_event_filter_descriptor(&self) -> Result> {
35 | match self {
36 | EventFilter::ByPids(pids) => EventFilterDescriptor::try_new_by_process_ids(pids),
37 | EventFilter::ByEventIds(ids) => EventFilterDescriptor::try_new_by_event_ids(ids),
38 | }
39 | }
40 | }
41 |
42 | /// Similar to windows' `EVENT_FILTER_DESCRIPTOR`, but with owned data
43 | ///
44 | /// See [`Self::as_event_filter_descriptor`] to get a Windows-rs-compatible type
45 | #[derive(Debug)]
46 | pub struct EventFilterDescriptor {
47 | data: *mut u8,
48 | layout: Layout,
49 | ty: u32,
50 | }
51 |
52 | impl EventFilterDescriptor {
53 | /// Allocates a new instance, where the included data is `data_size` bytes, and is suitably aligned for type `T`
54 | fn try_new(data_size: usize) -> Result> {
55 | let data_size = match data_size {
56 | 0 => return Err("Filter must not be empty".into()),
57 | 1..=1024 => data_size as u32,
58 | _ => {
59 | // See https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_filter_descriptor
60 | return Err("Exceeded filter size limits".into());
61 | }
62 | };
63 |
64 | let layout = Layout::from_size_align(data_size as usize, std::mem::align_of::())?;
65 | let data = unsafe {
66 | // Safety: layout size is non-zero
67 | std::alloc::alloc(layout)
68 | };
69 | if data.is_null() {
70 | return Err("Invalid allocation".into());
71 | }
72 | Ok(Self {
73 | data,
74 | layout,
75 | ty: 0,
76 | })
77 | }
78 |
79 | /// Build a new instance that will filter by event ID.
80 | ///
81 | /// Returns an `Err` in case the allocation failed, or if either zero or too many filter items were given
82 | pub fn try_new_by_event_ids(eids: &[u16]) -> Result> {
83 | if eids.len() > MAX_EVENT_FILTER_EVENT_ID_COUNT as usize {
84 | // See https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_filter_descriptor
85 | return Err("Too many event IDs are filtered".into());
86 | }
87 |
88 | let data_size = std::mem::size_of::()
89 | + ((eids.len().saturating_sub(1)) * std::mem::size_of::());
90 | let mut s = Self::try_new::(data_size)?;
91 | s.ty = EVENT_FILTER_TYPE_EVENT_ID;
92 |
93 | // Fill the data with an array of `EVENT_FILTER_EVENT_ID`s
94 | let p = s.data.cast::();
95 | unsafe {
96 | (*p).FilterIn = BOOLEAN(1);
97 | (*p).Reserved = 0;
98 | (*p).Count = eids.len() as u16; // we've checked the array was less than 1024 items
99 | }
100 |
101 | let evts = unsafe {
102 | std::slice::from_raw_parts_mut(
103 | &((*p).Events[0]) as *const u16 as *mut u16,
104 | std::cmp::max(1, eids.len()),
105 | )
106 | };
107 |
108 | if eids.is_empty() {
109 | // Just to avoid an unintialized data, but should never be accessed anyway since p->Count = 0
110 | evts[0] = 0;
111 | return Ok(s);
112 | }
113 |
114 | evts.copy_from_slice(eids);
115 | Ok(s)
116 | }
117 |
118 | /// Build a new instance that will filter by PIDs.
119 | ///
120 | /// Returns an `Err` in case the allocation failed, or if either zero or too many filter items were given
121 | pub fn try_new_by_process_ids(pids: &[u16]) -> Result> {
122 | if pids.len() > MAX_EVENT_FILTER_PID_COUNT as usize {
123 | // See https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_filter_descriptor
124 | return Err("Too many PIDs are filtered".into());
125 | }
126 |
127 | let data_size = std::mem::size_of_val(pids); // PIDs are WORD, i.e. 16bits
128 |
129 | let mut s = Self::try_new::(data_size)?;
130 | s.ty = EVENT_FILTER_TYPE_PID;
131 |
132 | if pids.is_empty() {
133 | s.data = std::ptr::null_mut();
134 | } else {
135 | let mut p = s.data.cast::();
136 | for pid in pids {
137 | unsafe {
138 | *p = *pid;
139 | };
140 |
141 | p = unsafe {
142 | // Safety:
143 | // * both the starting and resulting pointer are within the same allocated object
144 | // (except for the very last item, but that will not be written to)
145 | // * thus, the offset is smaller than an isize
146 | p.offset(1)
147 | };
148 | }
149 | }
150 |
151 | Ok(s)
152 | }
153 |
154 | /// Returns the EVENT_FILTER_DESCRIPTOR from this [`EventFilterDescriptor`]
155 | ///
156 | /// # Safety
157 | ///
158 | /// This will often be fed to an unsafe Windows function (e.g. [EnableTraceEx2](https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2)).
159 | /// Note that this contains pointers to the current `EventFilterDescriptor`, that must remain valid until the called function is done.
160 | pub fn as_event_filter_descriptor(&self) -> EVENT_FILTER_DESCRIPTOR {
161 | EVENT_FILTER_DESCRIPTOR {
162 | Ptr: self.data as u64,
163 | Size: self.layout.size() as u32,
164 | Type: self.ty,
165 | }
166 | }
167 | }
168 |
169 | impl Drop for EventFilterDescriptor {
170 | fn drop(&mut self) {
171 | unsafe {
172 | // Safety:
173 | // * ptr is a block of memory currently allocated via alloc::alloc
174 | // * layout is th one that was used to allocate that block of memory
175 | std::alloc::dealloc(self.data, self.layout);
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/provider/kernel_providers.rs:
--------------------------------------------------------------------------------
1 | //! Kernel Providers module
2 | //!
3 | //! Provides an easy way to create a Kernel Provider. Multiple providers are pre-created statically with
4 | //! their appropriate GUID and flags
5 | //! Credits: [KrabsETW::kernel_providers](https://github.com/microsoft/krabsetw/blob/master/krabs/krabs/kernel_providers.hpp)
6 | // TODO: Extremely Verbose and cumbersome, think a way to do this in a more clean way
7 | #![allow(dead_code)]
8 |
9 | use super::GUID;
10 |
11 | /// List of Kernel Providers GUIDs
12 | ///
13 | /// Credits: [KrabsETW::kernel_guids](https://github.com/microsoft/krabsetw/blob/master/krabs/krabs/kernel_guids.hpp)
14 | mod kernel_guids {
15 | use super::GUID;
16 | pub const ALPC_GUID: GUID = GUID::from_values(
17 | 0x45d8cccd,
18 | 0x539f,
19 | 0x4b72,
20 | [0xa8, 0xb7, 0x5c, 0x68, 0x31, 0x42, 0x60, 0x9a],
21 | );
22 | pub const POWER_GUID: GUID = GUID::from_values(
23 | 0xe43445e0,
24 | 0x0903,
25 | 0x48c3,
26 | [0xb8, 0x78, 0xff, 0x0f, 0xcc, 0xeb, 0xdd, 0x04],
27 | );
28 | pub const DEBUG_GUID: GUID = GUID::from_values(
29 | 0x13976d09,
30 | 0xa327,
31 | 0x438c,
32 | [0x95, 0x0b, 0x7f, 0x03, 0x19, 0x28, 0x15, 0xc7],
33 | );
34 | pub const TCP_IP_GUID: GUID = GUID::from_values(
35 | 0x9a280ac0,
36 | 0xc8e0,
37 | 0x11d1,
38 | [0x84, 0xe2, 0x00, 0xc0, 0x4f, 0xb9, 0x98, 0xa2],
39 | );
40 | pub const UDP_IP_GUID: GUID = GUID::from_values(
41 | 0xbf3a50c5,
42 | 0xa9c9,
43 | 0x4988,
44 | [0xa0, 0x05, 0x2d, 0xf0, 0xb7, 0xc8, 0x0f, 0x80],
45 | );
46 | pub const THREAD_GUID: GUID = GUID::from_values(
47 | 0x3d6fa8d1,
48 | 0xfe05,
49 | 0x11d0,
50 | [0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c],
51 | );
52 | pub const DISK_IO_GUID: GUID = GUID::from_values(
53 | 0x3d6fa8d4,
54 | 0xfe05,
55 | 0x11d0,
56 | [0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c],
57 | );
58 | pub const FILE_IO_GUID: GUID = GUID::from_values(
59 | 0x90cbdc39,
60 | 0x4a3e,
61 | 0x11d1,
62 | [0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3],
63 | );
64 | pub const PROCESS_GUID: GUID = GUID::from_values(
65 | 0x3d6fa8d0,
66 | 0xfe05,
67 | 0x11d0,
68 | [0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c],
69 | );
70 | pub const REGISTRY_GUID: GUID = GUID::from_values(
71 | 0xAE53722E,
72 | 0xC863,
73 | 0x11d2,
74 | [0x86, 0x59, 0x00, 0xC0, 0x4F, 0xA3, 0x21, 0xA1],
75 | );
76 | pub const SPLIT_IO_GUID: GUID = GUID::from_values(
77 | 0xd837ca92,
78 | 0x12b9,
79 | 0x44a5,
80 | [0xad, 0x6a, 0x3a, 0x65, 0xb3, 0x57, 0x8a, 0xa8],
81 | );
82 | pub const OB_TRACE_GUID: GUID = GUID::from_values(
83 | 0x89497f50,
84 | 0xeffe,
85 | 0x4440,
86 | [0x8c, 0xf2, 0xce, 0x6b, 0x1c, 0xdc, 0xac, 0xa7],
87 | );
88 | pub const UMS_EVENT_GUID: GUID = GUID::from_values(
89 | 0x9aec974b,
90 | 0x5b8e,
91 | 0x4118,
92 | [0x9b, 0x92, 0x31, 0x86, 0xd8, 0x00, 0x2c, 0xe5],
93 | );
94 | pub const PERF_INFO_GUID: GUID = GUID::from_values(
95 | 0xce1dbfb4,
96 | 0x137e,
97 | 0x4da6,
98 | [0x87, 0xb0, 0x3f, 0x59, 0xaa, 0x10, 0x2c, 0xbc],
99 | );
100 | pub const PAGE_FAULT_GUID: GUID = GUID::from_values(
101 | 0x3d6fa8d3,
102 | 0xfe05,
103 | 0x11d0,
104 | [0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c],
105 | );
106 | pub const IMAGE_LOAD_GUID: GUID = GUID::from_values(
107 | 0x2cb15d1d,
108 | 0x5fc1,
109 | 0x11d2,
110 | [0xab, 0xe1, 0x00, 0xa0, 0xc9, 0x11, 0xf5, 0x18],
111 | );
112 | pub const POOL_TRACE_GUID: GUID = GUID::from_values(
113 | 0x0268a8b6,
114 | 0x74fd,
115 | 0x4302,
116 | [0x9d, 0xd0, 0x6e, 0x8f, 0x17, 0x95, 0xc0, 0xcf],
117 | );
118 | pub const LOST_EVENT_GUID: GUID = GUID::from_values(
119 | 0x6a399ae0,
120 | 0x4bc6,
121 | 0x4de9,
122 | [0x87, 0x0b, 0x36, 0x57, 0xf8, 0x94, 0x7e, 0x7e],
123 | );
124 | pub const STACK_WALK_GUID: GUID = GUID::from_values(
125 | 0xdef2fe46,
126 | 0x7bd6,
127 | 0x4b80,
128 | [0xbd, 0x94, 0xf5, 0x7f, 0xe2, 0x0d, 0x0c, 0xe3],
129 | );
130 | pub const EVENT_TRACE_GUID: GUID = GUID::from_values(
131 | 0x68fdd900,
132 | 0x4a3e,
133 | 0x11d1,
134 | [0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3],
135 | );
136 | pub const MMCSS_TRACE_GUID: GUID = GUID::from_values(
137 | 0xf8f10121,
138 | 0xb617,
139 | 0x4a56,
140 | [0x86, 0x8b, 0x9d, 0xf1, 0xb2, 0x7f, 0xe3, 0x2c],
141 | );
142 | pub const SYSTEM_TRACE_GUID: GUID = GUID::from_values(
143 | 0x9e814aad,
144 | 0x3204,
145 | 0x11d2,
146 | [0x9a, 0x82, 0x00, 0x60, 0x08, 0xa8, 0x69, 0x39],
147 | );
148 | pub const EVENT_TRACE_CONFIG_GUID: GUID = GUID::from_values(
149 | 0x01853a65,
150 | 0x418f,
151 | 0x4f36,
152 | [0xae, 0xfc, 0xdc, 0x0f, 0x1d, 0x2f, 0xd2, 0x35],
153 | );
154 | }
155 |
156 | /// List of Kernel Providers flags
157 | ///
158 | /// More info: [EVENT_TRACE_PROPERTIES->EnableFlags](https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties)
159 | mod kernel_flags {
160 | pub const EVENT_TRACE_FLAG_PROCESS: u32 = 0x00000001;
161 | pub const EVENT_TRACE_FLAG_THREAD: u32 = 0x00000002;
162 | pub const EVENT_TRACE_FLAG_IMAGE_LOAD: u32 = 0x00000004;
163 | pub const EVENT_TRACE_FLAG_PROCESS_COUNTERS: u32 = 0x00000008;
164 | pub const EVENT_TRACE_FLAG_CSWITCH: u32 = 0x00000010;
165 | pub const EVENT_TRACE_FLAG_DPC: u32 = 0x00000020;
166 | pub const EVENT_TRACE_FLAG_INTERRUPT: u32 = 0x00000040;
167 | pub const EVENT_TRACE_FLAG_SYSTEMCALL: u32 = 0x00000080;
168 | pub const EVENT_TRACE_FLAG_DISK_IO: u32 = 0x00000100;
169 | pub const EVENT_TRACE_FLAG_DISK_FILE_IO: u32 = 0x00000200;
170 | pub const EVENT_TRACE_FLAG_DISK_IO_INIT: u32 = 0x00000400;
171 | pub const EVENT_TRACE_FLAG_DISPATCHER: u32 = 0x00000800;
172 | pub const EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS: u32 = 0x00001000;
173 | pub const EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS: u32 = 0x00002000;
174 | pub const EVENT_TRACE_FLAG_VIRTUAL_ALLOC: u32 = 0x00004000;
175 | pub const EVENT_TRACE_FLAG_VAMAP: u32 = 0x00008000;
176 | pub const EVENT_TRACE_FLAG_NETWORK_TCPIP: u32 = 0x00010000;
177 | pub const EVENT_TRACE_FLAG_REGISTRY: u32 = 0x00020000;
178 | pub const EVENT_TRACE_FLAG_DBGPRINT: u32 = 0x00040000;
179 | pub const EVENT_TRACE_FLAG_ALPC: u32 = 0x00100000;
180 | pub const EVENT_TRACE_FLAG_SPLIT_IO: u32 = 0x00200000;
181 | pub const EVENT_TRACE_FLAG_DRIVER: u32 = 0x00800000;
182 | pub const EVENT_TRACE_FLAG_PROFILE: u32 = 0x01000000;
183 | pub const EVENT_TRACE_FLAG_FILE_IO: u32 = 0x02000000;
184 | pub const EVENT_TRACE_FLAG_FILE_IO_INIT: u32 = 0x04000000;
185 | }
186 |
187 | /// Contains kernel provider identifiers.
188 | ///
189 | /// You'll need to use it with [`crate::provider::Provider::kernel`]
190 | #[derive(Debug)]
191 | pub struct KernelProvider {
192 | /// Kernel Provider GUID
193 | pub guid: GUID,
194 | /// Kernel Provider Flags
195 | pub flags: u32,
196 | }
197 |
198 | impl KernelProvider {
199 | /// Use the `new` function to create a Kernel Provider which can be then tied into a Provider
200 | pub const fn new(guid: GUID, flags: u32) -> KernelProvider {
201 | KernelProvider { guid, flags }
202 | }
203 | }
204 |
205 | /// Represents the VirtualAlloc Kernel Provider
206 | pub static VIRTUAL_ALLOC_PROVIDER: KernelProvider = KernelProvider::new(
207 | kernel_guids::PAGE_FAULT_GUID,
208 | kernel_flags::EVENT_TRACE_FLAG_VIRTUAL_ALLOC,
209 | );
210 | /// Represents the VA Map Kernel Provider
211 | pub static VAMAP_PROVIDER: KernelProvider = KernelProvider::new(
212 | kernel_guids::FILE_IO_GUID,
213 | kernel_flags::EVENT_TRACE_FLAG_VAMAP,
214 | );
215 | /// Represents the Thread Kernel Provider
216 | pub static THREAD_PROVIDER: KernelProvider = KernelProvider::new(
217 | kernel_guids::THREAD_GUID,
218 | kernel_flags::EVENT_TRACE_FLAG_THREAD,
219 | );
220 | /// Represents the Split IO Kernel Provider
221 | pub static SPLIT_IO_PROVIDER: KernelProvider = KernelProvider::new(
222 | kernel_guids::SPLIT_IO_GUID,
223 | kernel_flags::EVENT_TRACE_FLAG_SPLIT_IO,
224 | );
225 | /// Represents the SystemCall Kernel Provider
226 | pub static SYSTEM_CALL_PROVIDER: KernelProvider = KernelProvider::new(
227 | kernel_guids::PERF_INFO_GUID,
228 | kernel_flags::EVENT_TRACE_FLAG_SYSTEMCALL,
229 | );
230 | /// Represents the Registry Kernel Provider
231 | pub static REGISTRY_PROVIDER: KernelProvider = KernelProvider::new(
232 | kernel_guids::REGISTRY_GUID,
233 | kernel_flags::EVENT_TRACE_FLAG_REGISTRY,
234 | );
235 | /// Represents the Profile Kernel Provider
236 | pub static PROFILE_PROVIDER: KernelProvider = KernelProvider::new(
237 | kernel_guids::PERF_INFO_GUID,
238 | kernel_flags::EVENT_TRACE_FLAG_PROFILE,
239 | );
240 | /// Represents the Process Counter Kernel Provider
241 | pub static PROCESS_COUNTER_PROVIDER: KernelProvider = KernelProvider::new(
242 | kernel_guids::PROCESS_GUID,
243 | kernel_flags::EVENT_TRACE_FLAG_PROCESS_COUNTERS,
244 | );
245 | /// Represents the Process Kernel Provider
246 | pub static PROCESS_PROVIDER: KernelProvider = KernelProvider::new(
247 | kernel_guids::PROCESS_GUID,
248 | kernel_flags::EVENT_TRACE_FLAG_PROCESS,
249 | );
250 | /// Represents the TCP-IP Kernel Provider
251 | pub static TCP_IP_PROVIDER: KernelProvider = KernelProvider::new(
252 | kernel_guids::TCP_IP_GUID,
253 | kernel_flags::EVENT_TRACE_FLAG_NETWORK_TCPIP,
254 | );
255 | /// Represents the Memory Page Fault Kernel Provider
256 | pub static MEMORY_PAGE_FAULT_PROVIDER: KernelProvider = KernelProvider::new(
257 | kernel_guids::PAGE_FAULT_GUID,
258 | kernel_flags::EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS,
259 | );
260 | /// Represents the Memory Hard Fault Kernel Provider
261 | pub static MEMORY_HARD_FAULT_PROVIDER: KernelProvider = KernelProvider::new(
262 | kernel_guids::PAGE_FAULT_GUID,
263 | kernel_flags::EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS,
264 | );
265 | /// Represents the Interrupt Kernel Provider
266 | pub static INTERRUPT_PROVIDER: KernelProvider = KernelProvider::new(
267 | kernel_guids::PERF_INFO_GUID,
268 | kernel_flags::EVENT_TRACE_FLAG_INTERRUPT,
269 | );
270 | /// Represents the Driver Kernel Provider
271 | pub static DRIVER_PROVIDER: KernelProvider = KernelProvider::new(
272 | kernel_guids::DISK_IO_GUID,
273 | kernel_flags::EVENT_TRACE_FLAG_DISK_IO,
274 | );
275 | /// Represents the DPC Kernel Provider
276 | pub static DPC_PROVIDER: KernelProvider = KernelProvider::new(
277 | kernel_guids::PERF_INFO_GUID,
278 | kernel_flags::EVENT_TRACE_FLAG_DPC,
279 | );
280 | /// Represents the Image Load Kernel Provider
281 | pub static IMAGE_LOAD_PROVIDER: KernelProvider = KernelProvider::new(
282 | kernel_guids::IMAGE_LOAD_GUID,
283 | kernel_flags::EVENT_TRACE_FLAG_IMAGE_LOAD,
284 | );
285 | /// Represents the Thread Dispatcher Kernel Provider
286 | pub static THREAD_DISPATCHER_PROVIDER: KernelProvider = KernelProvider::new(
287 | kernel_guids::THREAD_GUID,
288 | kernel_flags::EVENT_TRACE_FLAG_DISPATCHER,
289 | );
290 | /// Represents the File Init IO Kernel Provider
291 | pub static FILE_INIT_IO_PROVIDER: KernelProvider = KernelProvider::new(
292 | kernel_guids::FILE_IO_GUID,
293 | kernel_flags::EVENT_TRACE_FLAG_FILE_IO_INIT,
294 | );
295 | /// Represents the File IO Kernel Provider
296 | pub static FILE_IO_PROVIDER: KernelProvider = KernelProvider::new(
297 | kernel_guids::FILE_IO_GUID,
298 | kernel_flags::EVENT_TRACE_FLAG_FILE_IO,
299 | );
300 | /// Represents the Disk IO Init Kernel Provider
301 | pub static DISK_IO_INIT_PROVIDER: KernelProvider = KernelProvider::new(
302 | kernel_guids::DISK_IO_GUID,
303 | kernel_flags::EVENT_TRACE_FLAG_DISK_IO_INIT,
304 | );
305 | /// Represents the Disk IO Kernel Provider
306 | pub static DISK_IO_PROVIDER: KernelProvider = KernelProvider::new(
307 | kernel_guids::DISK_IO_GUID,
308 | kernel_flags::EVENT_TRACE_FLAG_DISK_IO,
309 | );
310 | /// Represents the Disk File IO Kernel Provider
311 | pub static DISK_FILE_IO_PROVIDER: KernelProvider = KernelProvider::new(
312 | kernel_guids::DISK_IO_GUID,
313 | kernel_flags::EVENT_TRACE_FLAG_DISK_FILE_IO,
314 | );
315 | /// Represents the Dbg Pring Kernel Provider
316 | pub static DEBUG_PRINT_PROVIDER: KernelProvider = KernelProvider::new(
317 | kernel_guids::DEBUG_GUID,
318 | kernel_flags::EVENT_TRACE_FLAG_DBGPRINT,
319 | );
320 | /// Represents the Context Swtich Kernel Provider
321 | pub static CONTEXT_SWITCH_PROVIDER: KernelProvider = KernelProvider::new(
322 | kernel_guids::THREAD_GUID,
323 | kernel_flags::EVENT_TRACE_FLAG_CSWITCH,
324 | );
325 | /// Represents the ALPC Kernel Provider
326 | pub static ALPC_PROVIDER: KernelProvider =
327 | KernelProvider::new(kernel_guids::ALPC_GUID, kernel_flags::EVENT_TRACE_FLAG_ALPC);
328 |
329 | #[cfg(test)]
330 | mod test {
331 | use super::kernel_flags::*;
332 | use super::kernel_guids::*;
333 | use super::*;
334 |
335 | use crate::provider::Provider;
336 |
337 | #[test]
338 | fn test_kernel_provider_struct() {
339 | let kernel_provider =
340 | KernelProvider::new("D396B546-287D-4712-A7F5-8BE226A8C643".into(), 0x10000);
341 |
342 | assert_eq!(0x10000, kernel_provider.flags);
343 | assert_eq!(
344 | GUID::from("D396B546-287D-4712-A7F5-8BE226A8C643"),
345 | kernel_provider.guid
346 | );
347 | }
348 |
349 | #[test]
350 | fn test_kernel_provider_is_binded_to_provider() {
351 | let kernel_provider = Provider::kernel(&IMAGE_LOAD_PROVIDER).build();
352 |
353 | assert_eq!(EVENT_TRACE_FLAG_IMAGE_LOAD, kernel_provider.kernel_flags());
354 | assert_eq!(IMAGE_LOAD_GUID, kernel_provider.guid());
355 | }
356 |
357 | #[test]
358 | fn test_kernel_provider_guids_correct() {
359 | assert_eq!(
360 | ALPC_GUID,
361 | GUID::from("45d8cccd-539f-4b72-a8b7-5c683142609a")
362 | );
363 | assert_eq!(
364 | POWER_GUID,
365 | GUID::from("e43445e0-0903-48c3-b878-ff0fccebdd04")
366 | );
367 | assert_eq!(
368 | DEBUG_GUID,
369 | GUID::from("13976d09-a327-438c-950b-7f03192815c7")
370 | );
371 | assert_eq!(
372 | TCP_IP_GUID,
373 | GUID::from("9a280ac0-c8e0-11d1-84e2-00c04fb998a2")
374 | );
375 | assert_eq!(
376 | UDP_IP_GUID,
377 | GUID::from("bf3a50c5-a9c9-4988-a005-2df0b7c80f80")
378 | );
379 | assert_eq!(
380 | THREAD_GUID,
381 | GUID::from("3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c")
382 | );
383 | assert_eq!(
384 | DISK_IO_GUID,
385 | GUID::from("3d6fa8d4-fe05-11d0-9dda-00c04fd7ba7c")
386 | );
387 | assert_eq!(
388 | FILE_IO_GUID,
389 | GUID::from("90cbdc39-4a3e-11d1-84f4-0000f80464e3")
390 | );
391 | assert_eq!(
392 | PROCESS_GUID,
393 | GUID::from("3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c")
394 | );
395 | assert_eq!(
396 | REGISTRY_GUID,
397 | GUID::from("AE53722E-C863-11d2-8659-00C04FA321A1")
398 | );
399 | assert_eq!(
400 | SPLIT_IO_GUID,
401 | GUID::from("d837ca92-12b9-44a5-ad6a-3a65b3578aa8")
402 | );
403 | assert_eq!(
404 | OB_TRACE_GUID,
405 | GUID::from("89497f50-effe-4440-8cf2-ce6b1cdcaca7")
406 | );
407 | assert_eq!(
408 | UMS_EVENT_GUID,
409 | GUID::from("9aec974b-5b8e-4118-9b92-3186d8002ce5")
410 | );
411 | assert_eq!(
412 | PERF_INFO_GUID,
413 | GUID::from("ce1dbfb4-137e-4da6-87b0-3f59aa102cbc")
414 | );
415 | assert_eq!(
416 | PAGE_FAULT_GUID,
417 | GUID::from("3d6fa8d3-fe05-11d0-9dda-00c04fd7ba7c")
418 | );
419 | assert_eq!(
420 | IMAGE_LOAD_GUID,
421 | GUID::from("2cb15d1d-5fc1-11d2-abe1-00a0c911f518")
422 | );
423 | assert_eq!(
424 | POOL_TRACE_GUID,
425 | GUID::from("0268a8b6-74fd-4302-9dd0-6e8f1795c0cf")
426 | );
427 | assert_eq!(
428 | LOST_EVENT_GUID,
429 | GUID::from("6a399ae0-4bc6-4de9-870b-3657f8947e7e")
430 | );
431 | assert_eq!(
432 | STACK_WALK_GUID,
433 | GUID::from("def2fe46-7bd6-4b80-bd94-f57fe20d0ce3")
434 | );
435 | assert_eq!(
436 | EVENT_TRACE_GUID,
437 | GUID::from("68fdd900-4a3e-11d1-84f4-0000f80464e3")
438 | );
439 | assert_eq!(
440 | MMCSS_TRACE_GUID,
441 | GUID::from("f8f10121-b617-4a56-868b-9df1b27fe32c")
442 | );
443 | assert_eq!(
444 | SYSTEM_TRACE_GUID,
445 | GUID::from("9e814aad-3204-11d2-9a82-006008a86939")
446 | );
447 | assert_eq!(
448 | EVENT_TRACE_CONFIG_GUID,
449 | GUID::from("01853a65-418f-4f36-aefc-dc0f1d2fd235")
450 | );
451 | }
452 | }
453 |
--------------------------------------------------------------------------------
/src/provider/trace_flags.rs:
--------------------------------------------------------------------------------
1 | use bitflags::bitflags;
2 |
3 | use windows::Win32::System::Diagnostics::Etw;
4 |
5 | bitflags! {
6 | pub struct TraceFlags: u32 {
7 | const EVENT_ENABLE_PROPERTY_IGNORE_KEYWORD_0 = Etw::EVENT_ENABLE_PROPERTY_IGNORE_KEYWORD_0;
8 | const EVENT_ENABLE_PROPERTY_PROVIDER_GROUP = Etw::EVENT_ENABLE_PROPERTY_PROVIDER_GROUP;
9 | const EVENT_ENABLE_PROPERTY_PROCESS_START_KEY = Etw::EVENT_ENABLE_PROPERTY_PROCESS_START_KEY;
10 | const EVENT_ENABLE_PROPERTY_EVENT_KEY = Etw::EVENT_ENABLE_PROPERTY_EVENT_KEY;
11 | const EVENT_ENABLE_PROPERTY_EXCLUDE_INPRIVATE = Etw::EVENT_ENABLE_PROPERTY_EXCLUDE_INPRIVATE;
12 | const EVENT_ENABLE_PROPERTY_SID = Etw::EVENT_ENABLE_PROPERTY_SID;
13 | const EVENT_ENABLE_PROPERTY_TS_ID = Etw::EVENT_ENABLE_PROPERTY_TS_ID;
14 | const EVENT_ENABLE_PROPERTY_STACK_TRACE = Etw::EVENT_ENABLE_PROPERTY_STACK_TRACE;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/query.rs:
--------------------------------------------------------------------------------
1 | //! ETW information classes wrapper
2 |
3 | use windows::Win32::System::Diagnostics::Etw::TRACE_PROFILE_INTERVAL;
4 | use zerocopy::AsBytes;
5 |
6 | use crate::{
7 | native::{etw_types::TraceInformation, evntrace},
8 | trace::TraceError,
9 | };
10 |
11 | type TraceResult = Result;
12 |
13 | #[repr(u32)]
14 | pub enum ProfileSource {
15 | ProfileTime = 0,
16 | }
17 |
18 | pub struct SessionlessInfo;
19 |
20 | impl SessionlessInfo {
21 | pub fn sample_interval(source: ProfileSource) -> TraceResult {
22 | let mut info = TRACE_PROFILE_INTERVAL {
23 | Source: source as u32,
24 | Interval: 0,
25 | };
26 |
27 | evntrace::query_info(
28 | TraceInformation::TraceSampledProfileIntervalInfo,
29 | // SAFETY: TRACE_PROFILE_INTERVAL is `#[repr(C)]` and uses only POD
30 | unsafe {
31 | std::slice::from_raw_parts_mut(
32 | &mut info as *mut _ as *mut u8,
33 | std::mem::size_of::(),
34 | )
35 | },
36 | )?;
37 |
38 | Ok(info.Interval)
39 | }
40 |
41 | pub fn max_pmc() -> TraceResult {
42 | let mut max_pmc = 0u32;
43 |
44 | evntrace::query_info(
45 | TraceInformation::TraceMaxPmcCounterQuery,
46 | max_pmc.as_bytes_mut(),
47 | )?;
48 |
49 | Ok(max_pmc)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/schema.rs:
--------------------------------------------------------------------------------
1 | //! ETW Event Schema and handler
2 | //!
3 | //! This module contains the means needed to interact with the Schema of an ETW event
4 | use crate::native::etw_types::DecodingSource;
5 | use crate::native::tdh::TraceEventInfo;
6 | use crate::native::tdh_types::{Property, PropertyError};
7 | use once_cell::sync::OnceCell;
8 |
9 | /// A schema suitable for parsing a given kind of event.
10 | ///
11 | /// It is usually retrieved from [`crate::schema_locator::SchemaLocator::event_schema`].
12 | ///
13 | /// This structure is basically a wrapper over a [TraceEventInfo](https://docs.microsoft.com/en-us/windows/win32/api/tdh/ns-tdh-trace_event_info),
14 | /// with a few info parsed (and cached) out of it
15 | pub struct Schema {
16 | te_info: TraceEventInfo,
17 | cached_properties: OnceCell, PropertyError>>,
18 | }
19 |
20 | impl Schema {
21 | pub(crate) fn new(te_info: TraceEventInfo) -> Self {
22 | Schema {
23 | te_info,
24 | cached_properties: OnceCell::new(),
25 | }
26 | }
27 |
28 | /// Use the `decoding_source` function to obtain the [DecodingSource] from the `TRACE_EVENT_INFO`
29 | ///
30 | /// This getter returns the DecodingSource from the event, this value identifies the source used
31 | /// parse the event data
32 | ///
33 | /// # Example
34 | /// ```
35 | /// # use ferrisetw::EventRecord;
36 | /// # use ferrisetw::schema_locator::SchemaLocator;
37 |
38 | /// let my_callback = |record: &EventRecord, schema_locator: &SchemaLocator| {
39 | /// let schema = schema_locator.event_schema(record).unwrap();
40 | /// let decoding_source = schema.decoding_source();
41 | /// };
42 | /// ```
43 | pub fn decoding_source(&self) -> DecodingSource {
44 | self.te_info.decoding_source()
45 | }
46 |
47 | /// Use the `provider_name` function to obtain the Provider name from the `TRACE_EVENT_INFO`
48 | ///
49 | /// # Example
50 | /// ```
51 | /// # use ferrisetw::EventRecord;
52 | /// # use ferrisetw::schema_locator::SchemaLocator;
53 | /// let my_callback = |record: &EventRecord, schema_locator: &SchemaLocator| {
54 | /// let schema = schema_locator.event_schema(record).unwrap();
55 | /// let provider_name = schema.provider_name();
56 | /// };
57 | /// ```
58 | /// [TraceEventInfo]: crate::native::tdh::TraceEventInfo
59 | pub fn provider_name(&self) -> String {
60 | self.te_info.provider_name()
61 | }
62 |
63 | /// Use the `task_name` function to obtain the Task name from the `TRACE_EVENT_INFO`
64 | ///
65 | /// See: [TaskType](https://docs.microsoft.com/en-us/windows/win32/wes/eventmanifestschema-tasktype-complextype)
66 | /// # Example
67 | /// ```
68 | /// # use ferrisetw::EventRecord;
69 | /// # use ferrisetw::schema_locator::SchemaLocator;
70 | /// let my_callback = |record: &EventRecord, schema_locator: &SchemaLocator| {
71 | /// let schema = schema_locator.event_schema(record).unwrap();
72 | /// let task_name = schema.task_name();
73 | /// };
74 | /// ```
75 | /// [TraceEventInfo]: crate::native::tdh::TraceEventInfo
76 | pub fn task_name(&self) -> String {
77 | self.te_info.task_name()
78 | }
79 |
80 | /// Use the `opcode_name` function to obtain the Opcode name from the `TRACE_EVENT_INFO`
81 | ///
82 | /// See: [OpcodeType](https://docs.microsoft.com/en-us/windows/win32/wes/eventmanifestschema-opcodetype-complextype)
83 | /// # Example
84 | /// ```
85 | /// # use ferrisetw::EventRecord;
86 | /// # use ferrisetw::schema_locator::SchemaLocator;
87 | /// let my_callback = |record: &EventRecord, schema_locator: &SchemaLocator| {
88 | /// let schema = schema_locator.event_schema(record).unwrap();
89 | /// let opcode_name = schema.opcode_name();
90 | /// };
91 | /// ```
92 | /// [TraceEventInfo]: crate::native::tdh::TraceEventInfo
93 | pub fn opcode_name(&self) -> String {
94 | self.te_info.opcode_name()
95 | }
96 |
97 | /// Parses the list of properties of the wrapped `TRACE_EVENT_INFO`
98 | ///
99 | /// This is parsed on first call, and cached for later use
100 | pub(crate) fn properties(&self) -> &[Property] {
101 | match self.try_properties() {
102 | Err(PropertyError::UnimplementedType(_)) => {
103 | log::error!("Unable to list properties: a type is not implemented");
104 | &[]
105 | }
106 | Ok(p) => p,
107 | }
108 | }
109 |
110 | pub(crate) fn try_properties(&self) -> Result<&[Property], PropertyError> {
111 | let cache = self.cached_properties.get_or_init(|| {
112 | let mut cache = Vec::new();
113 | for property in self.te_info.properties() {
114 | cache.push(property?)
115 | }
116 | Ok(cache)
117 | });
118 |
119 | match cache {
120 | Err(e) => Err(e.clone()),
121 | Ok(cache) => Ok(cache.as_slice()),
122 | }
123 | }
124 | }
125 |
126 | impl PartialEq for Schema {
127 | fn eq(&self, other: &Self) -> bool {
128 | self.te_info.event_id() == other.te_info.event_id()
129 | && self.te_info.provider_guid() == other.te_info.provider_guid()
130 | && self.te_info.event_version() == other.te_info.event_version()
131 | }
132 | }
133 |
134 | impl Eq for Schema {}
135 |
--------------------------------------------------------------------------------
/src/schema_locator.rs:
--------------------------------------------------------------------------------
1 | //! A way to cache and retrieve Schemas
2 |
3 | use std::collections::HashMap;
4 | use std::sync::{Arc, Mutex};
5 |
6 | use windows::core::GUID;
7 |
8 | use crate::native::etw_types::event_record::EventRecord;
9 | use crate::native::tdh;
10 | use crate::native::tdh::TraceEventInfo;
11 | use crate::schema::Schema;
12 |
13 | /// Schema module errors
14 | #[derive(Debug)]
15 | pub enum SchemaError {
16 | /// Represents an internal [TdhNativeError]
17 | ///
18 | /// [TdhNativeError]: tdh::TdhNativeError
19 | TdhNativeError(tdh::TdhNativeError),
20 | }
21 |
22 | impl From for SchemaError {
23 | fn from(err: tdh::TdhNativeError) -> Self {
24 | SchemaError::TdhNativeError(err)
25 | }
26 | }
27 |
28 | pub(crate) type SchemaResult = Result;
29 |
30 | /// A way to group events that share the same [`Schema`]
31 | ///
32 | /// From the [docs](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_descriptor):
33 | /// > For manifest-based ETW, the combination Provider.DecodeGuid + Event.Id + Event.Version should uniquely identify an event,
34 | /// > i.e. all events with the same DecodeGuid, Id, and Version should have the same set of fields with no changes in field names, field types, or field ordering.
35 | #[derive(Debug, Eq, PartialEq, Hash)]
36 | struct SchemaKey {
37 | provider: GUID,
38 | /// From the [docs](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_descriptor): A 16-bit number used to identify manifest-based events
39 | id: u16,
40 | /// From the [docs](https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_descriptor): An 8-bit number used to specify the version of a manifest-based event.
41 | // The version indicates a revision to the definition of an event with a particular Id.
42 | // All events with a given Id should have similar semantics, but a change in version
43 | // can be used to indicate a minor modification of the event details, e.g. a change to
44 | // the type of a field or the addition of a new field.
45 | version: u8,
46 |
47 | // TODO: not sure why these ones are required in a SchemaKey. If they are, document why.
48 | // note that krabsetw also uses these fields (without an explanation)
49 | // however, krabsetw's `schema::operator==` do not use them to compare schemas for equality.
50 | // see https://github.com/microsoft/krabsetw/issues/195
51 | opcode: u8,
52 | level: u8,
53 | //
54 | // From MS documentation `evntprov.h`
55 | // For manifest-free events (i.e. TraceLogging), Event.Id and Event.Version are not useful
56 | // and should be ignored. Use Event name, level, keyword, and opcode for event filtering and
57 | // identification.
58 | //
59 | event_name: String,
60 | }
61 |
62 | impl SchemaKey {
63 | pub fn new(event: &EventRecord) -> Self {
64 | SchemaKey {
65 | provider: event.provider_id(),
66 | id: event.event_id(),
67 | opcode: event.opcode(),
68 | version: event.version(),
69 | level: event.level(),
70 | event_name: event.event_name(),
71 | }
72 | }
73 | }
74 |
75 | /// Represents a cache of Schemas already located
76 | ///
77 | /// This cache is implemented as a [HashMap] where the key is a combination of the following elements
78 | /// of an [Event Record](https://docs.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_record)
79 | /// * EventHeader.ProviderId
80 | /// * EventHeader.EventDescriptor.Id
81 | /// * EventHeader.EventDescriptor.Opcode
82 | /// * EventHeader.EventDescriptor.Version
83 | /// * EventHeader.EventDescriptor.Level
84 | ///
85 | /// Credits: [KrabsETW::schema_locator](https://github.com/microsoft/krabsetw/blob/master/krabs/krabs/schema_locator.hpp).
86 | /// See also the code of `SchemaKey` for more info
87 | #[derive(Default)]
88 | pub struct SchemaLocator {
89 | schemas: Mutex>>,
90 | }
91 |
92 | impl std::fmt::Debug for SchemaLocator {
93 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 | f.debug_struct("SchemaLocator")
95 | .field("len", &self.schemas.try_lock().map(|guard| guard.len()))
96 | .finish()
97 | }
98 | }
99 |
100 | impl SchemaLocator {
101 | pub(crate) fn new() -> Self {
102 | SchemaLocator {
103 | schemas: Mutex::new(HashMap::new()),
104 | }
105 | }
106 |
107 | /// Retrieve the Schema of an ETW Event
108 | ///
109 | /// # Arguments
110 | /// * `event` - The [EventRecord] that's passed to the callback
111 | ///
112 | /// # Example
113 | /// ```
114 | /// # use ferrisetw::EventRecord;
115 | /// # use ferrisetw::schema_locator::SchemaLocator;
116 | /// let my_callback = |record: &EventRecord, schema_locator: &SchemaLocator| {
117 | /// let schema = schema_locator.event_schema(record).unwrap();
118 | /// };
119 | /// ```
120 | pub fn event_schema(&self, event: &EventRecord) -> SchemaResult> {
121 | let key = SchemaKey::new(event);
122 |
123 | let mut schemas = self.schemas.lock().unwrap();
124 | match schemas.get(&key) {
125 | Some(s) => Ok(Arc::clone(s)),
126 | None => {
127 | let tei = TraceEventInfo::build_from_event(event)?;
128 | let new_schema = Arc::from(Schema::new(tei));
129 | schemas.insert(key, Arc::clone(&new_schema));
130 | Ok(new_schema)
131 | }
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/ser.rs:
--------------------------------------------------------------------------------
1 | //! Integrates with [serde](https://serde.rs/) enabling ['EventRecord`](crate::EventRecord) to be serialized to various formats.
2 | //!
3 | //! Requires the `serde` feature be enabled.
4 | //!
5 | //! If the `time_rs` feature is enabled, then time stamps are serialized per the serialization format
6 | //! of the time crate. Otherwise, if `time_rs` is not enabled, then timestamps are serialized as 64bit
7 | //! unix timestamps.
8 | //!
9 | //! ```
10 | //! use ferrisetw::schema_locator::SchemaLocator;
11 | //! use ferrisetw::{EventRecord, EventSerializer};
12 | //! extern crate serde_json;
13 | //!
14 | //! fn event_callback(record: &EventRecord, schema_locator: &SchemaLocator) {
15 | //! match schema_locator.event_schema(record) {
16 | //! Err(err) => println!("Error {:?}", err),
17 | //! Ok(schema) => {
18 | //! // Generate a serializer for the record using the schema
19 | //! let ser = EventSerializer::new(record, &schema, Default::default());
20 | //! // Pass the serializer to any serde compatible serializer
21 | //! match serde_json::to_value(ser) {
22 | //! Err(err) => println!("Error {:?}", err),
23 | //! Ok(json) => println!("{}", json),
24 | //! }
25 | //! }
26 | //! }
27 | //! }
28 | //! ```
29 | #![cfg(feature = "serde")]
30 |
31 | use crate::native::etw_types::event_record::EventRecord;
32 | use crate::native::tdh_types::{Property, PropertyInfo, TdhInType, TdhOutType};
33 | use crate::native::time::{FileTime, SystemTime};
34 | use crate::parser::Parser;
35 | use crate::schema::Schema;
36 | use crate::GUID;
37 | use serde::ser::{SerializeMap, SerializeStruct};
38 | use std::net::IpAddr;
39 | use windows::Win32::System::Diagnostics::Etw::{EVENT_DESCRIPTOR, EVENT_HEADER};
40 |
41 | /// Serialization options for EventSerializer
42 | #[derive(Clone, Copy)]
43 | pub struct EventSerializerOptions {
44 | /// Includes information from the schema in the serialized output such as the provider, opcode, and task names.
45 | pub include_schema: bool,
46 | /// Includes the [EVENT_HEADER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/fa4f7836-06ee-4ab6-8688-386a5a85f8c5) in the serialized output.
47 | pub include_header: bool,
48 | /// Includes the set of [EVENT_HEADER_EXTENDED_DATA_ITEM](https://learn.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_header_extended_data_item) in the serialized output.
49 | pub include_extended_data: bool,
50 | /// When `true` unimplemented serialization fails with an error, otherwise unimplemented serialization is skipped and will not be present in the serialized output.
51 | pub fail_unimplemented: bool,
52 | }
53 |
54 | impl core::default::Default for EventSerializerOptions {
55 | fn default() -> Self {
56 | Self {
57 | include_schema: true,
58 | include_header: true,
59 | include_extended_data: false,
60 | fail_unimplemented: false,
61 | }
62 | }
63 | }
64 |
65 | /// Used to serialize ['EventRecord`](crate::EventRecord) using [serde](https://serde.rs/)
66 | pub struct EventSerializer<'a> {
67 | pub(crate) record: &'a EventRecord,
68 | pub(crate) schema: &'a Schema,
69 | pub(crate) parser: Parser<'a, 'a>,
70 | pub(crate) options: EventSerializerOptions,
71 | }
72 |
73 | impl<'a> EventSerializer<'a> {
74 | /// Creates an event serializer object.
75 | pub fn new(
76 | record: &'a EventRecord,
77 | schema: &'a Schema,
78 | options: EventSerializerOptions,
79 | ) -> Self {
80 | Self {
81 | record,
82 | schema,
83 | parser: Parser::create(record, schema),
84 | options,
85 | }
86 | }
87 | }
88 |
89 | impl serde::ser::Serialize for EventSerializer<'_> {
90 | fn serialize(&self, serializer: S) -> Result
91 | where
92 | S: serde::ser::Serializer,
93 | {
94 | let mut state = serializer.serialize_struct("Record", 4)?;
95 |
96 | if self.options.include_schema {
97 | let schema = SchemaSer::new(self.schema);
98 | state.serialize_field("Schema", &schema)?;
99 | } else {
100 | state.skip_field("Schema")?;
101 | }
102 |
103 | if self.options.include_header {
104 | let header = HeaderSer::new(&self.record.0.EventHeader);
105 | state.serialize_field("Header", &header)?;
106 | } else {
107 | state.skip_field("Header")?;
108 | }
109 |
110 | if self.options.include_extended_data && self.options.fail_unimplemented {
111 | // TODO
112 | return Err(serde::ser::Error::custom(
113 | "not implemented for extended data",
114 | ));
115 | } else {
116 | state.skip_field("Extended")?;
117 | }
118 |
119 | let event = EventSer::new(self.record, self.schema, &self.parser, &self.options);
120 | state.serialize_field("Event", &event)?;
121 |
122 | state.end()
123 | }
124 | }
125 |
126 | struct GUIDExt(GUID);
127 |
128 | impl serde::ser::Serialize for GUIDExt {
129 | fn serialize(&self, serializer: S) -> Result
130 | where
131 | S: serde::ser::Serializer,
132 | {
133 | if serializer.is_human_readable() {
134 | return serializer.serialize_str(&format!("{:?}", self.0));
135 | }
136 |
137 | (self.0.data1, self.0.data2, self.0.data3, self.0.data4).serialize(serializer)
138 | }
139 | }
140 |
141 | struct SchemaSer<'a> {
142 | schema: &'a Schema,
143 | }
144 |
145 | impl<'a> SchemaSer<'a> {
146 | fn new(schema: &'a Schema) -> Self {
147 | Self { schema }
148 | }
149 | }
150 |
151 | impl serde::ser::Serialize for SchemaSer<'_> {
152 | fn serialize(&self, serializer: S) -> Result
153 | where
154 | S: serde::Serializer,
155 | {
156 | let mut state = serializer.serialize_struct("Schema", 3)?;
157 | state.serialize_field("Provider", &self.schema.provider_name().trim())?;
158 | state.serialize_field("Opcode", &self.schema.opcode_name().trim())?;
159 | state.serialize_field("Task", &self.schema.task_name().trim())?;
160 | state.end()
161 | }
162 | }
163 |
164 | struct HeaderSer<'a> {
165 | header: &'a EVENT_HEADER,
166 | }
167 |
168 | impl<'a> HeaderSer<'a> {
169 | fn new(header: &'a EVENT_HEADER) -> Self {
170 | Self { header }
171 | }
172 | }
173 |
174 | impl serde::ser::Serialize for HeaderSer<'_> {
175 | fn serialize(&self, serializer: S) -> Result
176 | where
177 | S: serde::ser::Serializer,
178 | {
179 | let mut state = serializer.serialize_struct("Header", 10)?;
180 | state.serialize_field("Size", &self.header.Size)?;
181 | state.serialize_field("HeaderType", &self.header.HeaderType)?;
182 | state.serialize_field("Flags", &self.header.Flags)?;
183 | state.serialize_field("EventProperty", &self.header.Flags)?;
184 | state.serialize_field("ThreadId", &self.header.ThreadId)?;
185 | state.serialize_field("ProcessId", &self.header.ProcessId)?;
186 | state.serialize_field("TimeStamp", &FileTime::from_quad(self.header.TimeStamp))?;
187 | state.serialize_field("ProviderId", &GUIDExt(self.header.ProviderId))?;
188 | state.serialize_field("ActivityId", &GUIDExt(self.header.ActivityId))?;
189 | let descriptor = DescriptorSer::new(&self.header.EventDescriptor);
190 | state.serialize_field("Descriptor", &descriptor)?;
191 | state.end()
192 | }
193 | }
194 |
195 | struct DescriptorSer<'a> {
196 | descriptor: &'a EVENT_DESCRIPTOR,
197 | }
198 |
199 | impl<'a> DescriptorSer<'a> {
200 | fn new(descriptor: &'a EVENT_DESCRIPTOR) -> Self {
201 | Self { descriptor }
202 | }
203 | }
204 |
205 | impl serde::ser::Serialize for DescriptorSer<'_> {
206 | fn serialize(&self, serializer: S) -> Result
207 | where
208 | S: serde::ser::Serializer,
209 | {
210 | let mut state = serializer.serialize_struct("Descriptor", 7)?;
211 | state.serialize_field("Id", &self.descriptor.Id)?;
212 | state.serialize_field("Version", &self.descriptor.Version)?;
213 | state.serialize_field("Channel", &self.descriptor.Channel)?;
214 | state.serialize_field("Level", &self.descriptor.Level)?;
215 | state.serialize_field("Opcode", &self.descriptor.Opcode)?;
216 | state.serialize_field("Task", &self.descriptor.Task)?;
217 | state.serialize_field("Keyword", &self.descriptor.Keyword)?;
218 | state.end()
219 | }
220 | }
221 |
222 | struct EventSer<'a, 'b> {
223 | record: &'a EventRecord,
224 | schema: &'a Schema,
225 | parser: &'a Parser<'b, 'b>,
226 | options: &'a EventSerializerOptions,
227 | }
228 |
229 | impl<'a, 'b> EventSer<'a, 'b> {
230 | fn new(
231 | record: &'a EventRecord,
232 | schema: &'a Schema,
233 | parser: &'a Parser<'b, 'b>,
234 | options: &'a EventSerializerOptions,
235 | ) -> Self {
236 | Self {
237 | record,
238 | schema,
239 | parser,
240 | options,
241 | }
242 | }
243 | }
244 |
245 | impl serde::ser::Serialize for EventSer<'_, '_> {
246 | fn serialize(&self, serializer: S) -> Result
247 | where
248 | S: serde::Serializer,
249 | {
250 | let mut len: usize = 0;
251 | let props = match self
252 | .schema
253 | .try_properties()
254 | .map_err(serde::ser::Error::custom)
255 | {
256 | Err(e) if self.options.fail_unimplemented => return Err(e),
257 | Ok(p) => p,
258 | _ => &[],
259 | };
260 |
261 | for prop in props {
262 | if prop.get_parser().is_some() {
263 | len += 1;
264 | } else if self.options.fail_unimplemented {
265 | match prop.info {
266 | PropertyInfo::Value {
267 | in_type, out_type, ..
268 | } => {
269 | return Err(serde::ser::Error::custom(format!(
270 | "not implemented {} in_type: {:?} out_type: {:?}",
271 | prop.name, in_type, out_type,
272 | )));
273 | }
274 | PropertyInfo::Array {
275 | in_type,
276 | out_type,
277 | count,
278 | ..
279 | } => {
280 | return Err(serde::ser::Error::custom(format!(
281 | "not implemented {} in_type: {:?} out_type: {:?} count: {:?}",
282 | prop.name, in_type, out_type, count
283 | )));
284 | }
285 | }
286 | }
287 | }
288 |
289 | let mut state = serializer.serialize_map(Some(len))?;
290 | for prop in props {
291 | if let Some(s) = prop.get_parser() {
292 | s.0.ser::(&mut state, prop, self.parser, self.record)?;
293 | }
294 | }
295 | state.end()
296 | }
297 | }
298 |
299 | struct PropSer(PropHandler);
300 |
301 | trait PropSerable {
302 | fn get_parser(&self) -> Option;
303 | }
304 |
305 | enum PropHandler {
306 | Null,
307 | Bool,
308 | Int8,
309 | UInt8,
310 | Int16,
311 | UInt16,
312 | Int32,
313 | UInt32,
314 | Int64,
315 | UInt64,
316 | Pointer,
317 | Float,
318 | Double,
319 | String,
320 | FileTime,
321 | SystemTime,
322 | Guid,
323 | Binary,
324 | IpAddr,
325 | ArrayInt16,
326 | ArrayUInt16,
327 | ArrayInt32,
328 | ArrayUInt32,
329 | ArrayInt64,
330 | ArrayUInt64,
331 | ArrayPointer,
332 | }
333 |
334 | macro_rules! prop_ser_type {
335 | ($typ:ty, $map:expr, $prop:expr, $parser:expr) => {{
336 | let v = $parser
337 | .try_parse::<$typ>(&$prop.name)
338 | .map_err(serde::ser::Error::custom)?;
339 | $map.serialize_entry(&$prop.name, &v)
340 | }};
341 | }
342 |
343 | impl PropHandler {
344 | fn ser(
345 | &self,
346 | map: &mut S::SerializeMap,
347 | prop: &Property,
348 | parser: &Parser,
349 | record: &EventRecord,
350 | ) -> Result<(), S::Error>
351 | where
352 | S: serde::ser::Serializer,
353 | {
354 | match self {
355 | PropHandler::Bool => prop_ser_type!(bool, map, prop, parser),
356 | PropHandler::Int8 => prop_ser_type!(i8, map, prop, parser),
357 | PropHandler::UInt8 => prop_ser_type!(u8, map, prop, parser),
358 | PropHandler::Int16 => prop_ser_type!(i16, map, prop, parser),
359 | PropHandler::UInt16 => prop_ser_type!(u16, map, prop, parser),
360 | PropHandler::Int32 => prop_ser_type!(i32, map, prop, parser),
361 | PropHandler::UInt32 => prop_ser_type!(u32, map, prop, parser),
362 | PropHandler::Int64 => prop_ser_type!(i64, map, prop, parser),
363 | PropHandler::UInt64 => prop_ser_type!(u64, map, prop, parser),
364 | PropHandler::Float => prop_ser_type!(f32, map, prop, parser),
365 | PropHandler::Double => prop_ser_type!(f64, map, prop, parser),
366 | PropHandler::String => prop_ser_type!(String, map, prop, parser),
367 | PropHandler::Binary => prop_ser_type!(Vec, map, prop, parser),
368 | PropHandler::IpAddr => prop_ser_type!(IpAddr, map, prop, parser),
369 | PropHandler::FileTime => prop_ser_type!(FileTime, map, prop, parser),
370 | PropHandler::SystemTime => prop_ser_type!(SystemTime, map, prop, parser),
371 | PropHandler::ArrayInt16 => prop_ser_type!(&[i16], map, prop, parser),
372 | PropHandler::ArrayUInt16 => prop_ser_type!(&[u16], map, prop, parser),
373 | PropHandler::ArrayInt32 => prop_ser_type!(&[i32], map, prop, parser),
374 | PropHandler::ArrayUInt32 => prop_ser_type!(&[u32], map, prop, parser),
375 | PropHandler::ArrayInt64 => prop_ser_type!(&[i64], map, prop, parser),
376 | PropHandler::ArrayUInt64 => prop_ser_type!(&[u64], map, prop, parser),
377 | PropHandler::Null => {
378 | let value: Option = None;
379 | map.serialize_entry(&prop.name, &value)
380 | }
381 | PropHandler::Pointer => {
382 | if record.pointer_size() == 4 {
383 | prop_ser_type!(u32, map, prop, parser)
384 | } else {
385 | prop_ser_type!(u64, map, prop, parser)
386 | }
387 | }
388 | PropHandler::ArrayPointer => {
389 | if record.pointer_size() == 4 {
390 | prop_ser_type!(&[u32], map, prop, parser)
391 | } else {
392 | prop_ser_type!(&[u64], map, prop, parser)
393 | }
394 | }
395 | PropHandler::Guid => {
396 | let guid = parser
397 | .try_parse::(&prop.name)
398 | .map_err(serde::ser::Error::custom)?;
399 | map.serialize_entry(&prop.name, &GUIDExt(guid))
400 | }
401 | }
402 | }
403 | }
404 |
405 | impl PropSerable for PropertyInfo {
406 | fn get_parser(&self) -> Option {
407 | // give the output type parser first if there is one, otherwise use the input type
408 | match self {
409 | PropertyInfo::Value {
410 | in_type, out_type, ..
411 | } => {
412 | match out_type {
413 | TdhOutType::OutTypeIpv4 => Some(PropSer(PropHandler::IpAddr)),
414 | TdhOutType::OutTypeIpv6 => Some(PropSer(PropHandler::IpAddr)),
415 | _ => match in_type {
416 | TdhInType::InTypeNull => Some(PropSer(PropHandler::Null)),
417 | TdhInType::InTypeUnicodeString => Some(PropSer(PropHandler::String)),
418 | TdhInType::InTypeAnsiString => Some(PropSer(PropHandler::String)),
419 | TdhInType::InTypeInt8 => Some(PropSer(PropHandler::Int8)),
420 | TdhInType::InTypeUInt8 => Some(PropSer(PropHandler::UInt8)),
421 | TdhInType::InTypeInt16 => Some(PropSer(PropHandler::Int16)),
422 | TdhInType::InTypeUInt16 => Some(PropSer(PropHandler::UInt16)),
423 | TdhInType::InTypeInt32 => Some(PropSer(PropHandler::Int32)),
424 | TdhInType::InTypeUInt32 => Some(PropSer(PropHandler::UInt32)),
425 | TdhInType::InTypeInt64 => Some(PropSer(PropHandler::Int64)),
426 | TdhInType::InTypeUInt64 => Some(PropSer(PropHandler::UInt64)),
427 | TdhInType::InTypeFloat => Some(PropSer(PropHandler::Float)),
428 | TdhInType::InTypeDouble => Some(PropSer(PropHandler::Double)),
429 | TdhInType::InTypeBoolean => Some(PropSer(PropHandler::Bool)),
430 | TdhInType::InTypeBinary => Some(PropSer(PropHandler::Binary)),
431 | TdhInType::InTypeGuid => Some(PropSer(PropHandler::Guid)),
432 | TdhInType::InTypePointer => Some(PropSer(PropHandler::Pointer)),
433 | TdhInType::InTypeFileTime => Some(PropSer(PropHandler::FileTime)),
434 | TdhInType::InTypeSystemTime => Some(PropSer(PropHandler::SystemTime)),
435 | TdhInType::InTypeSid => Some(PropSer(PropHandler::String)),
436 | TdhInType::InTypeHexInt32 => Some(PropSer(PropHandler::Int32)),
437 | TdhInType::InTypeHexInt64 => Some(PropSer(PropHandler::Int64)),
438 | TdhInType::InTypeCountedString => None, // TODO
439 | },
440 | }
441 | }
442 | PropertyInfo::Array { in_type, .. } => {
443 | match in_type {
444 | TdhInType::InTypeInt16 => Some(PropSer(PropHandler::ArrayInt16)),
445 | TdhInType::InTypeUInt16 => Some(PropSer(PropHandler::ArrayUInt16)),
446 | TdhInType::InTypeInt32 => Some(PropSer(PropHandler::ArrayInt32)),
447 | TdhInType::InTypeUInt32 => Some(PropSer(PropHandler::ArrayUInt32)),
448 | TdhInType::InTypeInt64 => Some(PropSer(PropHandler::ArrayInt64)),
449 | TdhInType::InTypeUInt64 => Some(PropSer(PropHandler::ArrayUInt64)),
450 | TdhInType::InTypePointer => Some(PropSer(PropHandler::ArrayPointer)),
451 | _ => None, // TODO
452 | }
453 | }
454 | }
455 | }
456 | }
457 |
458 | impl PropSerable for Property {
459 | fn get_parser(&self) -> Option {
460 | self.info.get_parser()
461 | }
462 | }
463 |
--------------------------------------------------------------------------------
/src/trace/callback_data.rs:
--------------------------------------------------------------------------------
1 | use std::sync::atomic::{AtomicUsize, Ordering};
2 | use std::sync::RwLock;
3 |
4 | use windows::Win32::System::Diagnostics::Etw;
5 |
6 | use crate::native::etw_types::event_record::EventRecord;
7 | use crate::provider::Provider;
8 | use crate::schema_locator::SchemaLocator;
9 | use crate::trace::RealTimeTraceTrait;
10 | use crate::EtwCallback;
11 |
12 | /// Data used by callbacks when the trace is running
13 | // NOTE: this structure is accessed in an unsafe block in a separate thread (see the `trace_callback_thunk` function)
14 | // Thus, this struct must not be mutated (outside of interior mutability and/or using Mutex and other synchronization mechanisms) when the associated trace is running.
15 | #[derive(Debug)]
16 | pub enum CallbackData {
17 | RealTime(RealTimeCallbackData),
18 | FromFile(CallbackDataFromFile),
19 | }
20 |
21 | #[derive(Debug)]
22 | pub struct RealTimeCallbackData {
23 | /// Represents how many events have been handled so far
24 | events_handled: AtomicUsize,
25 | schema_locator: SchemaLocator,
26 | /// List of Providers associated with the Trace. This also owns the callback closures and their state
27 | providers: Vec,
28 | }
29 |
30 | pub struct CallbackDataFromFile {
31 | /// Represents how many events have been handled so far
32 | events_handled: AtomicUsize,
33 | schema_locator: SchemaLocator,
34 | /// This trace is reading from an ETL file, and has a single callback
35 | callback: RwLock,
36 | }
37 |
38 | impl CallbackData {
39 | pub fn on_event(&self, record: &EventRecord) {
40 | match self {
41 | CallbackData::RealTime(rt_cb) => rt_cb.on_event(record),
42 | CallbackData::FromFile(f_cb) => f_cb.on_event(record),
43 | }
44 | }
45 |
46 | pub fn events_handled(&self) -> usize {
47 | match self {
48 | CallbackData::RealTime(rt_cb) => rt_cb.events_handled(),
49 | CallbackData::FromFile(f_cb) => f_cb.events_handled(),
50 | }
51 | }
52 | }
53 |
54 | impl std::default::Default for RealTimeCallbackData {
55 | fn default() -> Self {
56 | Self {
57 | events_handled: AtomicUsize::new(0),
58 | schema_locator: SchemaLocator::new(),
59 | providers: Vec::new(),
60 | }
61 | }
62 | }
63 |
64 | impl RealTimeCallbackData {
65 | pub fn new() -> Self {
66 | Default::default()
67 | }
68 |
69 | pub fn add_provider(&mut self, provider: Provider) {
70 | self.providers.push(provider)
71 | }
72 |
73 | pub fn providers(&self) -> &[Provider] {
74 | &self.providers
75 | }
76 |
77 | /// How many events have been handled since this instance was created
78 | pub fn events_handled(&self) -> usize {
79 | self.events_handled.load(Ordering::Relaxed)
80 | }
81 |
82 | pub fn provider_flags(&self) -> Etw::EVENT_TRACE_FLAG {
83 | Etw::EVENT_TRACE_FLAG(T::enable_flags(&self.providers))
84 | }
85 |
86 | pub fn on_event(&self, record: &EventRecord) {
87 | self.events_handled.fetch_add(1, Ordering::Relaxed);
88 |
89 | for prov in &self.providers {
90 | if prov.guid() == record.provider_id() {
91 | prov.on_event(record, &self.schema_locator);
92 | }
93 | }
94 | }
95 | }
96 |
97 | impl CallbackDataFromFile {
98 | pub fn new(callback: EtwCallback) -> Self {
99 | Self {
100 | events_handled: AtomicUsize::new(0),
101 | schema_locator: SchemaLocator::new(),
102 | callback: RwLock::new(callback),
103 | }
104 | }
105 |
106 | /// How many events have been handled since this instance was created
107 | pub fn events_handled(&self) -> usize {
108 | self.events_handled.load(Ordering::Relaxed)
109 | }
110 |
111 | pub fn on_event(&self, record: &EventRecord) {
112 | self.events_handled.fetch_add(1, Ordering::Relaxed);
113 | if let Ok(mut cb) = self.callback.write() {
114 | cb(record, &self.schema_locator);
115 | }
116 | }
117 | }
118 |
119 | impl std::fmt::Debug for CallbackDataFromFile {
120 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 | f.debug_struct("CallbackDataFromFile")
122 | .field("events_handled", &self.events_handled)
123 | .field("schema_locator", &self.schema_locator)
124 | .finish()
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/traits.rs:
--------------------------------------------------------------------------------
1 | use std::iter;
2 |
3 | pub trait EncodeUtf16 {
4 | fn into_utf16(self) -> Vec;
5 | }
6 |
7 | impl EncodeUtf16 for &str {
8 | fn into_utf16(self) -> Vec {
9 | self.encode_utf16() // Make a UTF-16 iterator
10 | .chain(iter::once(0)) // Append a null
11 | .collect() // Collect the iterator into a vector
12 | }
13 | }
14 |
15 | impl EncodeUtf16 for String {
16 | fn into_utf16(self) -> Vec {
17 | self.as_str().into_utf16()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/utils.rs:
--------------------------------------------------------------------------------
1 | use rand::distributions::Alphanumeric;
2 | use rand::{thread_rng, Rng};
3 |
4 | pub fn rand_string() -> String {
5 | thread_rng()
6 | .sample_iter(&Alphanumeric)
7 | .take(10)
8 | .map(char::from)
9 | .collect()
10 | }
11 |
--------------------------------------------------------------------------------
/tests/dns.rs:
--------------------------------------------------------------------------------
1 | //! Use the DNS provider to test a few things regarding user traces
2 |
3 | use std::process::Command;
4 | use std::time::Duration;
5 |
6 | use ferrisetw::parser::Parser;
7 | use ferrisetw::provider::{EventFilter, Provider};
8 | use ferrisetw::schema::Schema;
9 | use ferrisetw::schema_locator::SchemaLocator;
10 | use ferrisetw::trace::TraceTrait;
11 | use ferrisetw::trace::UserTrace;
12 | use ferrisetw::EventRecord;
13 |
14 | mod utils;
15 | use utils::{Status, TestKind};
16 |
17 | const TEST_DOMAIN_NAME: &str = "www.github.com";
18 |
19 | const EVENT_ID_DNS_QUERY_INITIATED: u16 = 3006;
20 | const EVENT_ID_DNS_QUERY_COMPLETED: u16 = 3008;
21 |
22 | #[test]
23 | fn dns_tests() {
24 | // These tests must be consecutive, as they share the same DNS provider
25 | simple_user_dns_trace();
26 | test_event_id_filter();
27 | }
28 |
29 | fn simple_user_dns_trace() {
30 | let passed = Status::new(TestKind::ExpectSuccess);
31 | let notifier = passed.notifier();
32 |
33 | let dns_provider = Provider::by_guid("1c95126e-7eea-49a9-a3fe-a378b03ddb4d") // Microsoft-Windows-DNS-Client
34 | .add_callback(
35 | move |record: &EventRecord, schema_locator: &SchemaLocator| {
36 | let schema = schema_locator.event_schema(record).unwrap();
37 | let parser = Parser::create(record, &schema);
38 |
39 | // While we're at it, let's check a few more-or-less unrelated things on an actual ETW event
40 | check_a_few_cases(record, &parser, &schema);
41 |
42 | if has_seen_resolution_to_test_domain(record, &parser) {
43 | notifier.notify_success();
44 | }
45 | },
46 | )
47 | .build();
48 |
49 | let dns_trace = UserTrace::new()
50 | .enable(dns_provider)
51 | .start_and_process()
52 | .unwrap();
53 |
54 | generate_dns_events();
55 |
56 | passed.assert_passed();
57 | assert!(dns_trace.events_handled() > 0);
58 | dns_trace.stop().unwrap();
59 | println!("simple_user_dns_trace passed");
60 | }
61 |
62 | fn test_event_id_filter() {
63 | let passed1 = Status::new(TestKind::ExpectSuccess);
64 | let passed2 = Status::new(TestKind::ExpectNoFailure);
65 | let passed3 = Status::new(TestKind::ExpectSuccess);
66 |
67 | let notifier1 = passed1.notifier();
68 | let notifier2 = passed2.notifier();
69 | let notifier3 = passed3.notifier();
70 |
71 | let filter = EventFilter::ByEventIds(vec![EVENT_ID_DNS_QUERY_COMPLETED]);
72 |
73 | let dns_provider = Provider::by_guid("1c95126e-7eea-49a9-a3fe-a378b03ddb4d") // Microsoft-Windows-DNS-Client
74 | .add_filter(filter)
75 | .add_callback(
76 | move |record: &EventRecord, _schema_locator: &SchemaLocator| {
77 | // We want at least one event, but only for the filtered kind
78 | if record.event_id() == EVENT_ID_DNS_QUERY_COMPLETED {
79 | notifier1.notify_success();
80 | } else {
81 | notifier2.notify_failure();
82 | }
83 | },
84 | )
85 | .add_callback(
86 | move |record: &EventRecord, _schema_locator: &SchemaLocator| {
87 | // This secondary callback basically tests all callbacks are run
88 | if record.event_id() == EVENT_ID_DNS_QUERY_COMPLETED {
89 | notifier3.notify_success();
90 | }
91 | },
92 | )
93 | .build();
94 |
95 | let _trace = UserTrace::new()
96 | .enable(dns_provider)
97 | .start_and_process()
98 | .unwrap();
99 |
100 | generate_dns_events();
101 |
102 | passed1.assert_passed();
103 | passed2.assert_passed();
104 | passed3.assert_passed();
105 | // Not calling .stop() here, let's just rely on the `impl Drop`
106 |
107 | println!("test_event_id_filter passed");
108 | }
109 |
110 | fn generate_dns_events() {
111 | std::thread::sleep(Duration::from_secs(1));
112 | // Unfortunately, `&str::to_socket_addrs()` does not use Microsoft APIs, and hence does not trigger a DNS ETW event
113 | // Let's use ping.exe instead
114 | println!("Resolving {}...", TEST_DOMAIN_NAME);
115 | let _output = Command::new("ping.exe")
116 | .arg("-n")
117 | .arg("1")
118 | .arg(TEST_DOMAIN_NAME)
119 | .output()
120 | .unwrap();
121 | println!("Resolution done.");
122 | }
123 |
124 | fn check_a_few_cases(record: &EventRecord, parser: &Parser, schema: &Schema) {
125 | // Parsing with a wrong type should properly error out
126 | if record.event_id() == EVENT_ID_DNS_QUERY_INITIATED {
127 | let _right_type: String = parser.try_parse("QueryName").unwrap();
128 | let wrong_type = parser.try_parse::("QueryName");
129 | assert!(wrong_type.is_err());
130 | }
131 |
132 | // Giving an unknown property should properly error out
133 | let wrong_name = parser.try_parse::("NoSuchProperty");
134 | assert!(wrong_name.is_err());
135 |
136 | assert_eq!(&schema.provider_name(), "Microsoft-Windows-DNS-Client");
137 | }
138 |
139 | fn has_seen_resolution_to_test_domain(record: &EventRecord, parser: &Parser) -> bool {
140 | if record.event_id() == EVENT_ID_DNS_QUERY_INITIATED {
141 | let query_name: String = parser.try_parse("QueryName").unwrap();
142 | #[allow(unused_parens)]
143 | return (query_name == TEST_DOMAIN_NAME);
144 | }
145 | false
146 | }
147 |
--------------------------------------------------------------------------------
/tests/file_trace.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 | use std::time::Duration;
3 |
4 | use ferrisetw::provider::Provider;
5 | use ferrisetw::schema_locator::SchemaLocator;
6 | use ferrisetw::trace::DumpFileParams;
7 | use ferrisetw::trace::TraceTrait;
8 | use ferrisetw::EventRecord;
9 | use ferrisetw::{FileTrace, UserTrace};
10 |
11 | #[test]
12 | fn etl_file() {
13 | env_logger::init(); // this is optional. This makes the (rare) error logs of ferrisetw to be printed to stderr
14 |
15 | let dump_file = DumpFileParams {
16 | file_path: PathBuf::from("etw-dump-file.etl"),
17 | ..Default::default()
18 | };
19 | let events_processes = save_a_trace(dump_file.clone());
20 | let events_read = process_from_file(dump_file.file_path);
21 |
22 | assert!(events_processes > 0); // otherwise this test will not test much
23 | assert!(events_read > events_processes); // The ETW framework can insert synthetic events, e.g. to give info about the current trace status. So, there may not be a perfec equality here
24 | }
25 |
26 | fn empty_callback(_record: &EventRecord, _schema_locator: &SchemaLocator) {}
27 |
28 | fn save_a_trace(dump_file: DumpFileParams) -> usize {
29 | let process_provider = Provider::by_guid("22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716") // Microsoft-Windows-Kernel-Process
30 | .add_callback(empty_callback)
31 | .build();
32 |
33 | let trace = UserTrace::new()
34 | .named(String::from("MyTrace"))
35 | .enable(process_provider)
36 | .set_etl_dump_file(dump_file)
37 | .start_and_process()
38 | .unwrap();
39 |
40 | std::thread::sleep(Duration::from_secs(10));
41 |
42 | let n_events = trace.events_handled();
43 | println!("Processed {} events", n_events);
44 | n_events
45 | }
46 |
47 | fn process_from_file(input_file: PathBuf) -> usize {
48 | let (trace, handle) = FileTrace::new(input_file, empty_callback).start().unwrap();
49 |
50 | FileTrace::process_from_handle(handle).unwrap();
51 |
52 | let n_events = trace.events_handled();
53 | println!("Read {} events from file", n_events);
54 | n_events
55 | }
56 |
--------------------------------------------------------------------------------
/tests/kernel_trace.rs:
--------------------------------------------------------------------------------
1 | //! Use the DNS provider to test a few things regarding user traces
2 |
3 | use std::time::Duration;
4 |
5 | use ferrisetw::parser::Parser;
6 | use ferrisetw::provider::kernel_providers;
7 | use ferrisetw::provider::{EventFilter, Provider};
8 | use ferrisetw::schema_locator::SchemaLocator;
9 | use ferrisetw::trace::KernelTrace;
10 | use ferrisetw::EventRecord;
11 |
12 | use windows::core::HSTRING;
13 | use windows::Win32::Foundation::HANDLE;
14 | use windows::Win32::System::LibraryLoader::{LoadLibraryExW, LOAD_LIBRARY_FLAGS};
15 |
16 | mod utils;
17 | use utils::{Status, StatusNotifier, TestKind};
18 |
19 | const TEST_LIBRARY_NAME: &str = "crypt32.dll"; // this DLL is available on all Windows versions (so that the test can run everywhere)
20 |
21 | #[test]
22 | fn kernel_trace_tests() {
23 | let passed1 = Status::new(TestKind::ExpectSuccess);
24 | let notifier1 = passed1.notifier();
25 |
26 | // Calling a sub-function, and getting the trace back. This ensures we are able to move the Trace around the stack
27 | // (see https://github.com/n4r1b/ferrisetw/pull/28)
28 | let moved_trace = create_simple_kernel_trace_trace(notifier1);
29 |
30 | generate_image_load_events();
31 |
32 | passed1.assert_passed();
33 | moved_trace.stop().unwrap();
34 | println!("Test passed");
35 | }
36 |
37 | fn create_simple_kernel_trace_trace(notifier: StatusNotifier) -> KernelTrace {
38 | println!("We are process {}", std::process::id());
39 | let our_process_only = EventFilter::ByPids(vec![std::process::id() as _]);
40 |
41 | let kernel_provider = Provider::kernel(&kernel_providers::IMAGE_LOAD_PROVIDER)
42 | .add_filter(our_process_only)
43 | .add_callback(
44 | move |record: &EventRecord, schema_locator: &SchemaLocator| {
45 | let schema = schema_locator.event_schema(record).unwrap();
46 | let parser = Parser::create(record, &schema);
47 |
48 | // By-PID filters are not working (yet?)
49 | // See See https://github.com/n4r1b/ferrisetw/issues/51
50 | // if has_seen_other_pid(record) {
51 | // notifier2.notify_failure();
52 | // }
53 | if has_seen_dll_load(record, &parser) {
54 | notifier.notify_success();
55 | }
56 | },
57 | )
58 | .build();
59 |
60 | KernelTrace::new()
61 | .enable(kernel_provider)
62 | .start_and_process()
63 | .unwrap()
64 | }
65 |
66 | fn load_library(libname: &str) {
67 | let widename = HSTRING::from(libname);
68 |
69 | // Safety: LoadLibraryExW expects a valid string in lpLibFileName.
70 | let res =
71 | unsafe { LoadLibraryExW(&widename, HANDLE::default(), LOAD_LIBRARY_FLAGS::default()) };
72 |
73 | res.unwrap();
74 | }
75 |
76 | fn generate_image_load_events() {
77 | std::thread::sleep(Duration::from_secs(1));
78 | println!("Will load a specific DLL...");
79 | load_library(TEST_LIBRARY_NAME);
80 | println!("Loading done.");
81 | }
82 |
83 | fn has_seen_dll_load(record: &EventRecord, parser: &Parser) -> bool {
84 | if record.process_id() == std::process::id() {
85 | let filename = parser.try_parse::("FileName");
86 | println!(" this one's for us: {:?}", filename);
87 | if let Ok(filename) = filename {
88 | if filename.ends_with(TEST_LIBRARY_NAME) {
89 | return true;
90 | }
91 | }
92 | }
93 |
94 | false
95 | }
96 |
--------------------------------------------------------------------------------
/tests/serialize.rs:
--------------------------------------------------------------------------------
1 | #![cfg(feature = "serde")]
2 |
3 | use ferrisetw::provider::Provider;
4 | use ferrisetw::schema_locator::SchemaLocator;
5 | use ferrisetw::trace::{stop_trace_by_name, TraceBuilder, TraceTrait, UserTrace};
6 | use ferrisetw::{EventRecord, EventSerializer, EventSerializerOptions};
7 | use serde::Serialize;
8 | use std::sync::atomic::{AtomicU64, Ordering};
9 | use std::sync::Arc;
10 | use std::time::{Duration, Instant};
11 |
12 | static BENCHMARK_PROVIDERS: &[&str] = &[
13 | "C514638F-7723-485B-BCFC-96565D735D4A",
14 | "16A1ADC1-9B7F-4CD9-94B3-D8296AB1B130",
15 | "E02A841C-75A3-4FA7-AFC8-AE09CF9B7F23",
16 | "15CA44FF-4D7A-4BAA-BBA5-0998955E531E",
17 | "96AC7637-5950-4A30-B8F7-E07E8E5734C1",
18 | "A2D34BF1-70AB-5B21-C819-5A0DD42748FD",
19 | "7F54CA8A-6C72-5CBC-B96F-D0EF905B8BCE",
20 | "C7BDE69A-E1E0-4177-B6EF-283AD1525271",
21 | "17D2A329-4539-5F4D-3435-F510634CE3B9",
22 | "B675EC37-BDB6-4648-BC92-F3FDC74D3CA2",
23 | "EDD08927-9CC4-4E65-B970-C2560FB5C289",
24 | "A68CA8B7-004F-D7B6-A698-07E2DE0F1F5D",
25 | "951B41EA-C830-44DC-A671-E2C9958809B8",
26 | "ABF1F586-2E50-4BA8-928D-49044E6F0DB7",
27 | "A103CABD-8242-4A93-8DF5-1CDF3B3F26A6",
28 | "A0AF438F-4431-41CB-A675-A265050EE947",
29 | "BEF2AA8E-81CD-11E2-A7BB-5EAC6188709B",
30 | "D1D93EF7-E1F2-4F45-9943-03D245FE6C00",
31 | "7DD42A49-5329-4832-8DFD-43D979153A88",
32 | "5412704E-B2E1-4624-8FFD-55777B8F7373",
33 | "9C205A39-1250-487D-ABD7-E831C6290539",
34 | "B3A0C2C8-83BB-4DDF-9F8D-4B22D3C38AD7",
35 | "331C3B3A-2005-44C2-AC5E-77220C37D6B4",
36 | "AA1F73E8-15FD-45D2-ABFD-E7F64F78EB11",
37 | "5322D61A-9EFA-4BC3-A3F9-14BE95C144F8",
38 | "B931ED29-66F4-576E-0579-0B8818A5DC6B",
39 | "22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716",
40 | "0F67E49F-FE51-4E9F-B490-6F2948CC6027",
41 | "70EB4F03-C1DE-4F73-A051-33D13D5413BD",
42 | "0BF2FB94-7B60-4B4D-9766-E82F658DF540",
43 | "A6AD76E3-867A-4635-91B3-4904BA6374D7",
44 | "548C4417-CE45-41FF-99DD-528F01CE0FE1",
45 | "4CEC9C95-A65F-4591-B5C4-30100E51D870",
46 | "2FF3E6B7-CB90-4700-9621-443F389734ED",
47 | "7B563579-53C8-44E7-8236-0F87B9FE6594",
48 | "F029AC39-38F0-4A40-B7DE-404D244004CB",
49 | "84DE80EB-86E8-4FF6-85A6-9319ABD578A4",
50 | "8939299F-2315-4C5C-9B91-ABB86AA0627D",
51 | "85FE7609-FF4A-48E9-9D50-12918E43E1DA",
52 | "CB070027-1534-4CF3-98EA-B9751F508376",
53 | "7237FFF9-A08A-4804-9C79-4A8704B70B87",
54 | "4FCC72A9-D7CA-5DD2-8D34-6F41A0CDB7E0",
55 | "099614A5-5DD7-4788-8BC9-E29F43DB28FC",
56 | "73AA0094-FACB-4AEB-BD1D-A7B98DD5C799",
57 | "DCBFB8F0-CD19-4F1C-A27D-23AC706DED72",
58 | "05F02597-FE85-4E67-8542-69567AB8FD4F",
59 | "CCC64809-6B5F-4C1B-AB39-336904DA9B3B",
60 | "0741C7BE-DAAC-4A5B-B00A-4BD9A2D89D0E",
61 | "E159FC63-02FE-42F3-A234-028B9B8561CB",
62 | "93C05D69-51A3-485E-877F-1806A8731346",
63 | "C882FF1D-7585-4B33-B135-95C577179137",
64 | "A329CF81-57EC-46ED-AB7C-261A52B0754A",
65 | ];
66 |
67 | struct BenchmarkStatistics {
68 | success_count: AtomicU64,
69 | error_count: AtomicU64,
70 | byte_count: AtomicU64,
71 | }
72 |
73 | impl BenchmarkStatistics {
74 | fn new() -> Self {
75 | Self {
76 | success_count: AtomicU64::new(0),
77 | error_count: AtomicU64::new(0),
78 | byte_count: AtomicU64::new(0),
79 | }
80 | }
81 |
82 | fn snap(&self) -> (u64, u64, u64) {
83 | (
84 | self.success_count.load(Ordering::Acquire),
85 | self.error_count.load(Ordering::Acquire),
86 | self.byte_count.load(Ordering::Acquire),
87 | )
88 | }
89 |
90 | fn json_callback(
91 | &self,
92 | record: &EventRecord,
93 | schema_locator: &SchemaLocator,
94 | options: EventSerializerOptions,
95 | ) {
96 | let res = schema_locator.event_schema(record);
97 | if res.is_err() {
98 | self.error_count.fetch_add(1, Ordering::AcqRel);
99 | return;
100 | }
101 | let schema = res.unwrap();
102 |
103 | let event = EventSerializer::new(record, &schema, options);
104 | let res = serde_json::to_value(event);
105 | if res.is_err() {
106 | println!("{:?}", res);
107 | self.error_count.fetch_add(1, Ordering::AcqRel);
108 | return;
109 | }
110 |
111 | let json_string = res.unwrap().to_string();
112 | //println!("{}", json_string);
113 | self.success_count.fetch_add(1, Ordering::AcqRel);
114 | self.byte_count
115 | .fetch_add(json_string.len() as u64, Ordering::AcqRel);
116 | }
117 |
118 | fn flexbuffer_callback(
119 | &self,
120 | record: &EventRecord,
121 | schema_locator: &SchemaLocator,
122 | options: EventSerializerOptions,
123 | ) {
124 | let res = schema_locator.event_schema(record);
125 | if res.is_err() {
126 | self.error_count.fetch_add(1, Ordering::AcqRel);
127 | return;
128 | }
129 | let schema = res.unwrap();
130 |
131 | let event = EventSerializer::new(record, &schema, options);
132 | let mut ser = flexbuffers::FlexbufferSerializer::new();
133 | let res = event.serialize(&mut ser);
134 | if res.is_err() {
135 | println!("{:?}", res);
136 | self.error_count.fetch_add(1, Ordering::AcqRel);
137 | return;
138 | }
139 |
140 | self.success_count.fetch_add(1, Ordering::AcqRel);
141 | self.byte_count
142 | .fetch_add(ser.view().len() as u64, Ordering::AcqRel);
143 | }
144 | }
145 |
146 | fn do_benchmark(
147 | name: &str,
148 | stats: Arc,
149 | trace_builder: TraceBuilder,
150 | seconds_to_run: u64,
151 | ) {
152 | let (trace, trace_handle) = trace_builder.start().expect("unable to start trace");
153 |
154 | let thread = std::thread::spawn(move || UserTrace::process_from_handle(trace_handle));
155 |
156 | let clock = Instant::now();
157 | let mut now: Option = None;
158 | let (mut last_s, mut last_e, mut last_b) = stats.snap();
159 | loop {
160 | let (s, e, b) = stats.snap();
161 |
162 | if let Some(now) = now {
163 | let micros = now.elapsed().as_micros();
164 | println!(
165 | "{:<32}: {} b/s {} s/s {} e/s",
166 | name,
167 | (((b - last_b) * 1_000_000) as u128) / micros,
168 | (((s - last_s) * 1_000_000) as u128) / micros,
169 | (((e - last_e) * 1_000_000) as u128) / micros,
170 | );
171 | }
172 |
173 | (last_s, last_e, last_b) = (s, e, b);
174 | now = Some(Instant::now());
175 |
176 | if clock.elapsed().as_secs() > seconds_to_run {
177 | break;
178 | }
179 |
180 | std::thread::sleep(Duration::from_secs(1));
181 | }
182 |
183 | trace.stop().expect("unable to stop trace");
184 | thread
185 | .join()
186 | .expect("thread panic")
187 | .expect("trace processing error");
188 |
189 | println!("{:<32}: {} b {} s {} e", name, last_b, last_s, last_e);
190 | assert_eq!(last_e, 0, "encountered errors when benchmarking");
191 | }
192 |
193 | fn ser_json_test(name: &'static str, options: EventSerializerOptions, seconds_to_run: u64) {
194 | if stop_trace_by_name(name).is_ok() {
195 | println!("Trace was running, it has been stopped before starting it again.");
196 | }
197 |
198 | let stats = Arc::new(BenchmarkStatistics::new());
199 |
200 | let mut trace_builder = UserTrace::new().named(name.to_string());
201 | for guid in BENCHMARK_PROVIDERS {
202 | let s = stats.clone();
203 | let opts = options;
204 | trace_builder = trace_builder.enable(
205 | Provider::by_guid(*guid)
206 | .add_callback(move |record, schema_locator| {
207 | s.json_callback(record, schema_locator, opts)
208 | })
209 | .build(),
210 | );
211 | }
212 |
213 | do_benchmark(name, stats, trace_builder, seconds_to_run)
214 | }
215 |
216 | fn ser_flexbuffer_test(name: &'static str, options: EventSerializerOptions, seconds_to_run: u64) {
217 | if stop_trace_by_name(name).is_ok() {
218 | println!("Trace was running, it has been stopped before starting it again.");
219 | }
220 |
221 | let stats = Arc::new(BenchmarkStatistics::new());
222 |
223 | let mut trace_builder = UserTrace::new().named(name.to_string());
224 | for guid in BENCHMARK_PROVIDERS {
225 | let s = stats.clone();
226 | let opts = options;
227 | trace_builder = trace_builder.enable(
228 | Provider::by_guid(*guid)
229 | .add_callback(move |record, schema_locator| {
230 | s.flexbuffer_callback(record, schema_locator, opts)
231 | })
232 | .build(),
233 | );
234 | }
235 |
236 | do_benchmark(name, stats, trace_builder, seconds_to_run)
237 | }
238 |
239 | const SECONDS_TO_RUN: u64 = 5;
240 |
241 | #[test]
242 | fn serialize_json() {
243 | ser_json_test(
244 | "ferrisetw-json",
245 | EventSerializerOptions {
246 | //include_schema: false,
247 | //include_header: false,
248 | //include_extended_data: false,
249 | //fail_unimplemented: true,
250 | ..Default::default()
251 | },
252 | SECONDS_TO_RUN,
253 | );
254 | }
255 |
256 | #[test]
257 | fn serialize_flexbuffer() {
258 | ser_flexbuffer_test(
259 | "ferrisetw-flex",
260 | EventSerializerOptions {
261 | //include_schema: false,
262 | //include_header: false,
263 | //include_extended_data: false,
264 | //fail_unimplemented: true,
265 | ..Default::default()
266 | },
267 | SECONDS_TO_RUN,
268 | );
269 | }
270 |
--------------------------------------------------------------------------------
/tests/tlg.rs:
--------------------------------------------------------------------------------
1 | use tracelogging as tlg;
2 |
3 | use ferrisetw::parser::Parser;
4 | use ferrisetw::provider::Provider;
5 | use ferrisetw::schema_locator::SchemaLocator;
6 | use ferrisetw::trace::TraceTrait;
7 | use ferrisetw::trace::UserTrace;
8 | use ferrisetw::EventRecord;
9 |
10 | mod utils;
11 | use utils::{Status, TestKind};
12 |
13 | const EVENT1_COUNT: u32 = 1;
14 | const EVENT2_COUNT: u32 = 5;
15 | const TEST_STRING_VALUE: &'static str = "TestString";
16 | const PROVIDER_NAME: &'static str = "ferrisETW.TraceLoggingTest";
17 |
18 | tlg::define_provider!(FERRIS_PROVIDER, "ferrisETW.TraceLoggingTest");
19 |
20 | #[ignore]
21 | #[test]
22 | fn tlg_tests() {
23 | unsafe {
24 | FERRIS_PROVIDER.register();
25 | }
26 |
27 | let binding = tlg::Guid::from_name(PROVIDER_NAME).to_utf8_bytes();
28 | let guid = std::str::from_utf8(&binding).unwrap();
29 |
30 | tlg_multiple_events(guid);
31 |
32 | FERRIS_PROVIDER.unregister();
33 | }
34 |
35 | fn generate_tlg_events() {
36 | for _i in 0..EVENT1_COUNT {
37 | tlg::write_event!(
38 | FERRIS_PROVIDER,
39 | "Event1",
40 | level(Warning),
41 | keyword(0x13),
42 | str8("String", TEST_STRING_VALUE),
43 | );
44 | }
45 |
46 | for i in 0..EVENT2_COUNT {
47 | tlg::write_event!(
48 | FERRIS_PROVIDER,
49 | "Event2",
50 | level(Informational),
51 | keyword(0x6),
52 | u32("Integer", &i),
53 | );
54 | }
55 | }
56 |
57 | fn tlg_multiple_events(provider_guid: &str) {
58 | let passed = Status::new(TestKind::ExpectSuccess);
59 | let notifier = passed.notifier();
60 |
61 | let mut event1_count = 0;
62 | let mut event2_count = 0;
63 |
64 | let tlg_provider = Provider::by_guid(provider_guid)
65 | .add_callback(
66 | move |record: &EventRecord, schema_locator: &SchemaLocator| {
67 | let schema = schema_locator.event_schema(record).unwrap();
68 | let parser = Parser::create(record, &schema);
69 |
70 | // Test event_name function is working as expected & we can handle multiple
71 | // different events.
72 | if record.event_name() == "Event1" {
73 | println!(
74 | "Received Event1({}) from ferrisETW.TraceLoggingTest",
75 | event1_count
76 | );
77 |
78 | assert_eq!(record.level(), tlg::Level::Warning.as_int());
79 | assert_eq!(record.keyword(), 0x13);
80 |
81 | // Tracelogging crate sets OutTypeUtf8 for str8 which we don't handle at the
82 | // moment.
83 | let _data = parser.try_parse::("String");
84 | // assert!(data.is_ok());
85 | // assert_eq!(data, TEST_STRING_VALUE);
86 |
87 | event1_count = event1_count + 1;
88 | } else if record.event_name() == "Event2" {
89 | println!(
90 | "Received Event2({}) from ferrisETW.TraceLoggingTest",
91 | event2_count
92 | );
93 |
94 | assert_eq!(record.level(), tlg::Level::Informational.as_int());
95 | assert_eq!(record.keyword(), 0x6);
96 |
97 | let data = parser.try_parse::("Integer");
98 |
99 | assert!(data.is_ok());
100 | assert_eq!(data.unwrap(), event2_count);
101 |
102 | event2_count = event2_count + 1;
103 | }
104 |
105 | if event1_count == EVENT1_COUNT && event2_count == EVENT2_COUNT {
106 | notifier.notify_success();
107 | }
108 | },
109 | )
110 | .build();
111 |
112 | let tlg_trace = UserTrace::new()
113 | .enable(tlg_provider)
114 | .start_and_process()
115 | .unwrap();
116 |
117 | generate_tlg_events();
118 |
119 | passed.assert_passed();
120 | assert!(tlg_trace.events_handled() > 0);
121 | tlg_trace.stop().unwrap();
122 | println!("tlg_multiple_events passed");
123 | }
124 |
--------------------------------------------------------------------------------
/tests/trace_lifetime.rs:
--------------------------------------------------------------------------------
1 | //! Test that traces are started and stopped as expected
2 |
3 | use std::process::Command;
4 |
5 | use ferrisetw::provider::Provider;
6 | use ferrisetw::schema_locator::SchemaLocator;
7 | use ferrisetw::trace::RealTimeTraceTrait;
8 | use ferrisetw::trace::TraceTrait;
9 | use ferrisetw::trace::UserTrace;
10 | use ferrisetw::EventRecord;
11 |
12 | #[derive(Clone, Copy, Debug)]
13 | enum HowToProcess {
14 | StartOnly,
15 | ProcessFromHandle,
16 | StartAndProcess,
17 | }
18 |
19 | #[test]
20 | fn trace_lifetime() {
21 | // List of (names to request, ASCII part to look for)
22 | const NAME_EXAMPLES: [(&str, &str); 4] = [
23 | ("simple-trace-name", "simple-trace-name"),
24 | ("998877", "998877"),
25 | ("My Ütf-8 tråce", "tf-8 tr"),
26 | ("My Ütf-8 tråce name, that has quite a løøøøøøøøøøøøøøøøøøøøøng name, 😎 a very λονɣ name indeed (which is even longer than TRACE_NAME_MAX_CHARS). My Ütf-8 tråce name, that has quite a løøøøøøøøøøøøøøøøøøøøøng name, 😎 a very λονɣ name indeed (which is even longer than TRACE_NAME_MAX_CHARS).", "that has quite a"),
27 | ];
28 |
29 | const HOW_TO_PROCESS: [HowToProcess; 3] = [
30 | HowToProcess::StartOnly,
31 | HowToProcess::ProcessFromHandle,
32 | HowToProcess::StartAndProcess,
33 | ];
34 |
35 | // Setup: make sure no trace is still running from an older interrupted test
36 | for (requested_trace_name, _ascii_part_to_look_for) in NAME_EXAMPLES {
37 | let _output = Command::new("logman")
38 | .arg("stop")
39 | .arg("-ets")
40 | .arg(requested_trace_name)
41 | .output()
42 | .unwrap();
43 | }
44 |
45 | for provider_count in 0..2 {
46 | for (requested_trace_name, ascii_part_to_look_for) in NAME_EXAMPLES {
47 | for explicit_stop in [true, false] {
48 | for how_to_process in HOW_TO_PROCESS {
49 | test_wordpad_trace(
50 | provider_count,
51 | requested_trace_name,
52 | ascii_part_to_look_for,
53 | explicit_stop,
54 | how_to_process,
55 | );
56 |
57 | // Regardless of whether we explicitly stopped it, trace has been dropped and must no longer run
58 | assert_trace_exists(ascii_part_to_look_for, false);
59 | }
60 | }
61 | }
62 | }
63 | }
64 |
65 | fn test_wordpad_trace(
66 | provider_count: usize,
67 | requested_trace_name: &str,
68 | ascii_part_of_the_trace_name: &str,
69 | explicit_stop: bool,
70 | how_to_process: HowToProcess,
71 | ) {
72 | println!(
73 | "Testing a trace with {} providers, processed as {:?}, stopped:{}, name contains {}...",
74 | provider_count, how_to_process, explicit_stop, ascii_part_of_the_trace_name
75 | );
76 |
77 | // Create a provider
78 | let mut provider_builder = Provider::by_guid("54FFD262-99FE-4576-96E7-1ADB500370DC"); // Microsoft-Windows-Wordpad
79 | for _i in 0..provider_count {
80 | provider_builder =
81 | provider_builder.add_callback(|_record: &EventRecord, _locator: &SchemaLocator| {})
82 | }
83 | let wordpad_provider = provider_builder.build();
84 | assert_trace_exists(requested_trace_name, false);
85 |
86 | // Create a trace
87 | let trace_builder = UserTrace::new()
88 | .named(String::from(requested_trace_name))
89 | .enable(wordpad_provider);
90 |
91 | let trace = match how_to_process {
92 | HowToProcess::StartOnly => {
93 | let (trace, _handle) = trace_builder.start().unwrap();
94 | trace // the trace is running, but not processing anything
95 | }
96 | HowToProcess::ProcessFromHandle => {
97 | let (trace, handle) = trace_builder.start().unwrap();
98 | std::thread::spawn(move || UserTrace::process_from_handle(handle));
99 | trace
100 | }
101 | HowToProcess::StartAndProcess => trace_builder.start_and_process().unwrap(),
102 | };
103 |
104 | let actual_trace_name = trace.trace_name().to_string_lossy().to_string();
105 | assert!(actual_trace_name.contains(ascii_part_of_the_trace_name));
106 | assert_trace_exists(ascii_part_of_the_trace_name, true);
107 |
108 | if explicit_stop {
109 | trace.stop().unwrap();
110 | assert_trace_exists(ascii_part_of_the_trace_name, false);
111 | }
112 | }
113 |
114 | /// Call `logman` and check if the expected trace is part of the output
115 | ///
116 | /// This is limited to the ASCII part of the trace name, because Windows really sucks when it comes to encodings from sub processes (codepage issues, etc.)
117 | #[track_caller]
118 | fn assert_trace_exists(ascii_part_of_the_trace_name: &str, expected: bool) {
119 | for _attempt in 0..3 {
120 | let output = Command::new("logman")
121 | .arg("query")
122 | .arg("-ets")
123 | .output()
124 | .unwrap();
125 |
126 | let stdout_u8 = output.stdout;
127 | let stdout = String::from_utf8_lossy(&stdout_u8);
128 | let status = output.status;
129 |
130 | let res = stdout
131 | .split('\n')
132 | .any(|line| line.contains(ascii_part_of_the_trace_name));
133 |
134 | if status.success() {
135 | if res != expected {
136 | println!("logman output (returned {}): {}", status, stdout);
137 | unreachable!();
138 | }
139 | } else {
140 | // Not sure why, but logman sometimes fails to list current traces (with "The GUID passed was not recognized as valid by a WMI data provider.")
141 | println!("logman hit an error (returned {}).", status);
142 | println!("logman output: {}", stdout);
143 | println!("Let's try again");
144 | std::thread::sleep(std::time::Duration::from_millis(100));
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/tests/utils/mod.rs:
--------------------------------------------------------------------------------
1 | use std::sync::mpsc;
2 | use std::sync::mpsc::{RecvTimeoutError, TrySendError};
3 | use std::time::Duration;
4 |
5 | #[derive(Clone, Debug, PartialEq)]
6 | pub enum TestKind {
7 | /// Test will pass if a success has been notified in the test duration
8 | ExpectSuccess,
9 | /// Test will pass if no failure has been notified in the test duration
10 | ExpectNoFailure,
11 | }
12 |
13 | #[derive(Clone, Debug)] // mpsc channels are clone-able to be shared between threads
14 | pub struct StatusNotifier {
15 | kind: TestKind,
16 | tx: mpsc::SyncSender<()>,
17 | }
18 |
19 | impl StatusNotifier {
20 | pub fn notify_success(&self) {
21 | if self.kind == TestKind::ExpectSuccess {
22 | match self.tx.try_send(()) {
23 | Ok(()) => (),
24 | Err(TrySendError::Full(_)) => (), // this means we've sent a success signal already, we don't care
25 | Err(TrySendError::Disconnected(_)) => (), // Receiver disconnected when the test was still running. That's usually expected, since the callback can outlive the function that started the trace
26 | }
27 | }
28 | }
29 |
30 | #[allow(dead_code)]
31 | pub fn notify_failure(&self) {
32 | if self.kind == TestKind::ExpectNoFailure {
33 | match self.tx.try_send(()) {
34 | Ok(()) => (),
35 | Err(TrySendError::Full(_)) => (), // this means we've sent a failure signal already, we don't care
36 | Err(TrySendError::Disconnected(_)) => (), // Receiver disconnected when the test was still running. That's usually expected, since the callback can outlive the function that started the trace
37 | }
38 | }
39 | }
40 | }
41 |
42 | #[derive(Debug)]
43 | pub struct Status {
44 | notifier: StatusNotifier,
45 | rx: mpsc::Receiver<()>,
46 | }
47 |
48 | impl Status {
49 | pub fn new(kind: TestKind) -> Self {
50 | let (tx, rx) = mpsc::sync_channel(1);
51 | Self {
52 | notifier: StatusNotifier { kind, tx },
53 | rx,
54 | }
55 | }
56 |
57 | pub fn notifier(&self) -> StatusNotifier {
58 | self.notifier.clone()
59 | }
60 |
61 | pub fn assert_passed(&self) {
62 | let timeout = Duration::from_secs(10);
63 |
64 | match self.notifier.kind {
65 | TestKind::ExpectSuccess => match self.rx.recv_timeout(timeout) {
66 | Ok(()) => {}
67 | Err(RecvTimeoutError::Timeout) => {
68 | panic!("Test did not pass within the allowed timeout");
69 | }
70 | _ => panic!("Should not happen, the sending end has not hung up."),
71 | },
72 |
73 | TestKind::ExpectNoFailure => match self.rx.recv_timeout(timeout) {
74 | Ok(()) => {
75 | panic!("Test failed within the allowed timeout");
76 | }
77 | Err(RecvTimeoutError::Timeout) => {}
78 | _ => panic!("Should not happen, the sending end has not hung up."),
79 | },
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------