diff --git a/Cargo.lock b/Cargo.lock index d631a627..366936df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -598,19 +598,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "etw-reader" -version = "0.1.0" -dependencies = [ - "bitflags", - "fxhash", - "memoffset", - "num-derive", - "num-traits", - "once_cell", - "windows 0.59.0", -] - [[package]] name = "fallible-iterator" version = "0.3.0" @@ -2107,7 +2094,6 @@ dependencies = [ "ctrlc", "debugid", "env_logger", - "etw-reader", "flate2", "framehop", "fs4", diff --git a/etw-reader/src/etw_types.rs b/etw-reader/src/etw_types.rs index fd073d3f..5fad717e 100644 --- a/etw-reader/src/etw_types.rs +++ b/etw-reader/src/etw_types.rs @@ -22,7 +22,7 @@ impl Deref for EventRecord { impl EventRecord { pub(crate) fn user_buffer(&self) -> &[u8] { - if self.UserData == std::ptr::null_mut() { + if self.UserData.is_null() { return &[]; } unsafe { std::slice::from_raw_parts(self.UserData as *mut _, self.UserDataLength.into()) } @@ -71,21 +71,21 @@ pub const EVENT_HEADER_FLAG_32_BIT_HEADER: u16 = Etw::EVENT_HEADER_FLAG_32_BIT_H /// [DECODING_SOURCE]: https://microsoft.github.io/windows-docs-rs/doc/bindings/Windows/Win32/Etw/struct.DECODING_SOURCE.html #[derive(Debug)] pub enum DecodingSource { - DecodingSourceXMLFile, - DecodingSourceWbem, - DecodingSourceWPP, - DecodingSourceTlg, - DecodingSourceMax, + XMLFile, + Wbem, + Wpp, + Tlg, + Max, } impl From for DecodingSource { fn from(val: Etw::DECODING_SOURCE) -> Self { match val { - Etw::DecodingSourceXMLFile => DecodingSource::DecodingSourceXMLFile, - Etw::DecodingSourceWbem => DecodingSource::DecodingSourceWbem, - Etw::DecodingSourceWPP => DecodingSource::DecodingSourceWPP, - Etw::DecodingSourceTlg => DecodingSource::DecodingSourceTlg, - _ => DecodingSource::DecodingSourceMax, + Etw::DecodingSourceXMLFile => DecodingSource::XMLFile, + Etw::DecodingSourceWbem => DecodingSource::Wbem, + Etw::DecodingSourceWPP => DecodingSource::Wpp, + Etw::DecodingSourceTlg => DecodingSource::Tlg, + _ => DecodingSource::Max, } } } diff --git a/etw-reader/src/lib.rs b/etw-reader/src/lib.rs index 18f27fa3..fba5b2e7 100644 --- a/etw-reader/src/lib.rs +++ b/etw-reader/src/lib.rs @@ -1,32 +1,23 @@ +use fxhash::FxHasher; +use memoffset::offset_of; +pub use windows::core::GUID; use windows::core::{h, HSTRING, PWSTR}; use windows::Win32::Foundation::{ GetLastError, ERROR_INSUFFICIENT_BUFFER, ERROR_MORE_DATA, MAX_PATH, }; +use windows::Win32::System::Diagnostics::Etw; use windows::Win32::System::Diagnostics::Etw::{ EnumerateTraceGuids, EnumerateTraceGuidsEx, TraceGuidQueryInfo, TraceGuidQueryList, CONTROLTRACE_HANDLE, EVENT_TRACE_FLAG, TRACE_GUID_INFO, TRACE_GUID_PROPERTIES, TRACE_PROVIDER_INSTANCE_INFO, }; -use crate::parser::{Parser, ParserError, TryParse}; -use crate::schema::SchemaLocator; -use crate::tdh_types::{PrimitiveDesc, PropertyDesc, TdhInType}; -use crate::traits::EncodeUtf16; - -#[macro_use] -extern crate memoffset; - use std::borrow::Cow; use std::collections::HashMap; use std::hash::BuildHasherDefault; use std::mem; use std::path::Path; -use etw_types::EventRecord; -use fxhash::FxHasher; -use tdh_types::{Property, TdhOutType}; -use windows::Win32::System::Diagnostics::Etw; - // typedef ULONG64 TRACEHANDLE, *PTRACEHANDLE; pub(crate) type TraceHandle = u64; pub const INVALID_TRACE_HANDLE: TraceHandle = u64::MAX; @@ -46,7 +37,12 @@ pub mod utils; //pub mod trace; //pub mod provider; -pub use windows::core::GUID; +use etw_types::EventRecord; +use parser::{Parser, ParserError, TryParse}; +use schema::SchemaLocator; +use tdh_types::{PrimitiveDesc, PropertyDesc, TdhInType}; +use tdh_types::{Property, TdhOutType}; +use traits::EncodeUtf16; pub type FastHashMap = HashMap>; #[repr(C)] diff --git a/etw-reader/src/parser.rs b/etw-reader/src/parser.rs index 9d574a6e..b661767a 100644 --- a/etw-reader/src/parser.rs +++ b/etw-reader/src/parser.rs @@ -179,9 +179,7 @@ impl<'a> Parser<'a> { // as the size of AppName // Fallback to Tdh - return Ok( - tdh::property_size(self.event.record(), &property.name).unwrap() as usize, - ); + Ok(tdh::property_size(self.event.record(), &property.name).unwrap() as usize) } PropertyLength::Length(length) => { // TODO: Study heuristic method used in krabsetw :) @@ -222,9 +220,7 @@ impl<'a> Parser<'a> { } } } - return Ok( - tdh::property_size(self.event.record(), &property.name).unwrap() as usize, - ); + Ok(tdh::property_size(self.event.record(), &property.name).unwrap() as usize) } } } diff --git a/etw-reader/src/schema.rs b/etw-reader/src/schema.rs index ae78056c..5daf47ea 100644 --- a/etw-reader/src/schema.rs +++ b/etw-reader/src/schema.rs @@ -466,7 +466,7 @@ impl<'a> TypedEvent<'a> { } } -impl<'a> PartialEq for TypedEvent<'a> { +impl PartialEq for TypedEvent<'_> { fn eq(&self, other: &Self) -> bool { self.schema.event_schema.event_id() == other.schema.event_schema.event_id() && self.schema.event_schema.provider_guid() == other.schema.event_schema.provider_guid() @@ -474,4 +474,4 @@ impl<'a> PartialEq for TypedEvent<'a> { } } -impl<'a> Eq for TypedEvent<'a> {} +impl Eq for TypedEvent<'_> {} diff --git a/samply/Cargo.toml b/samply/Cargo.toml index 3dcd8d53..461a9a23 100644 --- a/samply/Cargo.toml +++ b/samply/Cargo.toml @@ -84,7 +84,8 @@ num-derive = "0.4" runas = "1.2.0" which = "7.0.0" winver = "1" -etw-reader = { path = "../etw-reader" } + +# etw-reader = { path = "../etw-reader" } # linux-perf-data = "0.10.1" [target.'cfg(windows)'.dependencies.windows] diff --git a/samply/src/windows/coreclr.rs b/samply/src/windows/coreclr.rs index 2d9b341e..cec8b36c 100644 --- a/samply/src/windows/coreclr.rs +++ b/samply/src/windows/coreclr.rs @@ -3,9 +3,6 @@ use std::convert::TryInto; use std::fmt::Display; use bitflags::bitflags; -use etw_reader::parser::{Parser, TryParse}; -use etw_reader::schema::TypedEvent; -use etw_reader::{self, event_properties_to_string}; use fxprof_processed_profile::*; use num_derive::FromPrimitive; use num_traits::FromPrimitive; @@ -14,6 +11,10 @@ use super::elevated_helper::ElevatedRecordingProps; use crate::shared::recording_props::{CoreClrProfileProps, ProfileCreationProps}; use crate::windows::profile_context::{KnownCategory, ProfileContext}; +use super::etw_reader::event_properties_to_string; +use super::etw_reader::parser::{Parser, TryParse}; +use super::etw_reader::schema::TypedEvent; + struct SavedMarkerInfo { start_timestamp_raw: u64, name: String, diff --git a/samply/src/windows/elevated_helper.rs.orig b/samply/src/windows/elevated_helper.rs.orig deleted file mode 100644 index c512e888..00000000 --- a/samply/src/windows/elevated_helper.rs.orig +++ /dev/null @@ -1,214 +0,0 @@ -use std::error::Error; -use std::path::{Path, PathBuf}; - -use serde_derive::{Deserialize, Serialize}; - -use super::utility_process::{ - run_child, UtilityProcess, UtilityProcessChild, UtilityProcessParent, UtilityProcessSession, -}; -use super::xperf::Xperf; -use crate::shared::recording_props::{ - CoreClrProfileProps, ProfileCreationProps, RecordingMode, RecordingProps, -}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "t", content = "c")] -enum ElevatedHelperRequestMsg { - StartXperf(ElevatedRecordingProps), - StopXperf, - GetKernelModules, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ElevatedRecordingProps { - pub time_limit_seconds: Option, - pub interval_nanos: u64, - pub coreclr: CoreClrProfileProps, - pub vm_hack: bool, - pub is_attach: bool, - pub gfx: bool, - pub browsers: bool, -} - -impl ElevatedRecordingProps { - pub fn from_recording_props( - recording_props: &RecordingProps, - profile_creation_props: &ProfileCreationProps, - recording_mode: &RecordingMode, - ) -> Self { - Self { - time_limit_seconds: recording_props.time_limit.map(|l| l.as_secs_f64()), - interval_nanos: recording_props.interval.as_nanos().try_into().unwrap(), - coreclr: profile_creation_props.coreclr, - vm_hack: recording_props.vm_hack, - is_attach: recording_mode.is_attach_mode(), - gfx: recording_props.gfx, - browsers: recording_props.browsers, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "t", content = "c")] -#[allow(clippy::enum_variant_names)] -enum ElevatedHelperReplyMsg { - AckStartXperf, - AckStopXperf { - kernel_output_file: PathBuf, - user_output_file: Option, - }, - AckGetKernelModules, -} - -// Runs in the helper process which has Administrator privileges. -pub fn run_elevated_helper(ipc_directory: &Path, output_path: PathBuf) { - let child = ElevatedHelperChild::new(output_path); - run_child::(ipc_directory, child) -} - -pub struct ElevatedHelperSession { - elevated_session: UtilityProcessSession, -} - -impl ElevatedHelperSession { - pub fn new(output_path: PathBuf) -> Result> { - let parent = ElevatedHelperParent { output_path }; - let elevated_session = UtilityProcessSession::spawn_process(parent)?; - Ok(Self { elevated_session }) - } - - pub fn start_xperf( - &mut self, - recording_props: &RecordingProps, - profile_creation_props: &ProfileCreationProps, - recording_mode: &RecordingMode, - ) -> Result<(), Box> { - let xperf_args = ElevatedRecordingProps::from_recording_props( - recording_props, - profile_creation_props, - recording_mode, - ); - match self - .elevated_session - .send_msg_and_wait_for_response(ElevatedHelperRequestMsg::StartXperf(xperf_args)) - { - Ok(reply) => match reply { - ElevatedHelperReplyMsg::AckStartXperf => Ok(()), - other_msg => { - Err(format!("Unexpected reply to StartXperf msg: {other_msg:?}").into()) - } - }, - Err(err) => { - eprintln!("Could not start xperf: {err}"); - std::process::exit(1); - } - } - } - - pub fn stop_xperf( - &mut self, - ) -> Result<(PathBuf, Option), Box> { - let reply = self - .elevated_session - .send_msg_and_wait_for_response(ElevatedHelperRequestMsg::StopXperf)?; - match reply { - ElevatedHelperReplyMsg::AckStopXperf { - kernel_output_file, - user_output_file, - } => Ok((kernel_output_file, user_output_file)), - other_msg => Err(format!("Unexpected reply to StartXperf msg: {other_msg:?}").into()), - } - } - - pub fn shutdown(self) { - self.elevated_session.shutdown() - } -} - -struct ElevatedHelper; - -impl UtilityProcess for ElevatedHelper { - const PROCESS_TYPE: &'static str = "windows-elevated-helper"; - type Child = ElevatedHelperChild; - type Parent = ElevatedHelperParent; - type ParentToChildMsg = ElevatedHelperRequestMsg; - type ChildToParentMsg = ElevatedHelperReplyMsg; -} - -struct ElevatedHelperParent { - output_path: PathBuf, -} - -impl UtilityProcessParent for ElevatedHelperParent { - fn spawn_child(self, ipc_directory: &Path) { - let self_path = std::env::current_exe().expect("Couldn't obtain path of this binary"); - // eprintln!( - // "Run this: {} run-elevated-helper --ipc-directory {} --output-path {}", - // self_path.to_string_lossy(), - // ipc_directory.to_string_lossy(), - // self.output_path.to_string_lossy() - // ); - - // let mut cmd = std::process::Command::new(&self_path); - let mut cmd = runas::Command::new(self_path); - cmd.arg("run-elevated-helper"); - - cmd.arg("--ipc-directory"); - cmd.arg(ipc_directory); - cmd.arg("--output-path"); - cmd.arg(expand_full_filename_with_cwd(&self.output_path)); - - // Don't show a new Console window for this process. - cmd.show(false); - - let _ = cmd.status().expect("Failed to execute elevated helper"); - } -} - -pub fn expand_full_filename_with_cwd(filename: &Path) -> PathBuf { - if filename.is_absolute() { - filename.to_path_buf() - } else { - let mut fullpath = std::env::current_dir().unwrap(); - fullpath.push(filename); - fullpath - } -} - -struct ElevatedHelperChild { - output_path: PathBuf, - xperf: Xperf, -} - -impl ElevatedHelperChild { - // Runs in the helper process which has Administrator privileges. - pub fn new(output_path: PathBuf) -> Self { - Self { - xperf: Xperf::new(), - output_path, - } - } -} - -impl UtilityProcessChild for ElevatedHelperChild { - // Runs in the helper process which has Administrator privileges. - fn handle_message( - &mut self, - msg: ElevatedHelperRequestMsg, - ) -> Result> { - match msg { - ElevatedHelperRequestMsg::StartXperf(props) => { - self.xperf.start_xperf(&self.output_path, &props)?; - Ok(ElevatedHelperReplyMsg::AckStartXperf) - } - ElevatedHelperRequestMsg::StopXperf => { - let (kernel_output_file, user_output_file) = self.xperf.stop_xperf()?; - Ok(ElevatedHelperReplyMsg::AckStopXperf { - kernel_output_file, - user_output_file, - }) - } - ElevatedHelperRequestMsg::GetKernelModules => Err("todo".into()), - } - } -} diff --git a/samply/src/windows/etw_gecko.rs b/samply/src/windows/etw_gecko.rs index f213f04e..78a8788a 100644 --- a/samply/src/windows/etw_gecko.rs +++ b/samply/src/windows/etw_gecko.rs @@ -3,11 +3,6 @@ use std::path::{Path, PathBuf}; use std::time::Instant; use debugid::DebugId; -use etw_reader::parser::{Address, Parser, TryParse}; -use etw_reader::schema::SchemaLocator; -use etw_reader::{ - add_custom_schemas, event_properties_to_string, open_trace, print_property, GUID, -}; use fxprof_processed_profile::debugid; use uuid::Uuid; @@ -16,6 +11,12 @@ use super::profile_context::ProfileContext; use crate::windows::coreclr; use crate::windows::profile_context::{KnownCategory, PeInfo}; +use super::etw_reader::parser::{Address, Parser, TryParse}; +use super::etw_reader::schema::SchemaLocator; +use super::etw_reader::{ + add_custom_schemas, event_properties_to_string, open_trace, print_property, GUID, +}; + pub fn process_etl_files( context: &mut ProfileContext, etl_file: &Path, diff --git a/samply/src/windows/etw_reader/README.md b/samply/src/windows/etw_reader/README.md new file mode 100644 index 00000000..f831cc91 --- /dev/null +++ b/samply/src/windows/etw_reader/README.md @@ -0,0 +1,2 @@ +This is a temporary copy of /etw-reader, until the published version +of the crates.io etw-reader reflects what's in this repo. diff --git a/samply/src/windows/etw_reader/custom_schemas.rs b/samply/src/windows/etw_reader/custom_schemas.rs new file mode 100644 index 00000000..2c4191ef --- /dev/null +++ b/samply/src/windows/etw_reader/custom_schemas.rs @@ -0,0 +1,890 @@ +use windows::core::GUID; + +use super::etw_types::DecodingSource; +use super::schema::EventSchema; +use super::tdh_types::{ + PrimitiveDesc, Property, PropertyDesc, PropertyFlags, PropertyLength, TdhInType, TdhOutType, +}; + +struct PropDesc { + name: &'static str, + in_type: TdhInType, + out_type: TdhOutType, +} + +pub struct ImageID {} + +const ImageID_PROPS: [PropDesc; 5] = [ + PropDesc { + name: "ImageBase", + in_type: TdhInType::InTypePointer, + out_type: TdhOutType::OutTypeHexInt64, + }, + PropDesc { + name: "ImageSize", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt32, + }, + PropDesc { + name: "Unknown", + in_type: TdhInType::InTypePointer, + out_type: TdhOutType::OutTypeUInt32, + }, + PropDesc { + name: "TimeDateStamp", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt32, + }, + PropDesc { + name: "OriginalFileName", + in_type: TdhInType::InTypeUnicodeString, + out_type: TdhOutType::OutTypeString, + }, +]; + +impl EventSchema for ImageID { + fn provider_guid(&self) -> GUID { + GUID::try_from("b3e675d7-2554-4f18-830b-2762732560de").unwrap() + } + + fn event_id(&self) -> u16 { + 0 + } + + fn opcode(&self) -> u8 { + 0 + } + + fn event_version(&self) -> u8 { + 2 + } + + fn level(&self) -> u8 { + 0 + } + + fn decoding_source(&self) -> DecodingSource { + panic!() + } + + fn provider_name(&self) -> String { + "KernelTraceControl".to_owned() + } + + fn task_name(&self) -> String { + "ImageID".to_owned() + } + + fn opcode_name(&self) -> String { + "".to_string() + } + + fn property_count(&self) -> u32 { + ImageID_PROPS.len() as u32 + } + + fn property(&self, index: u32) -> Property { + let prop = &ImageID_PROPS[index as usize]; + Property { + name: prop.name.to_owned(), + desc: PropertyDesc::Primitive(PrimitiveDesc { + in_type: prop.in_type, + out_type: prop.out_type, + }), + length: PropertyLength::Length(0), + count: 1, + map_info: None, + flags: PropertyFlags::empty(), + } + } +} + +pub struct DbgID {} + +const DbgID_PROPS: [PropDesc; 5] = [ + PropDesc { + name: "ImageBase", + in_type: TdhInType::InTypeUInt64, + out_type: TdhOutType::OutTypeHexInt64, + }, + PropDesc { + name: "ProcessId", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt32, + }, + PropDesc { + name: "GuidSig", + in_type: TdhInType::InTypeGuid, + out_type: TdhOutType::OutTypeGuid, + }, + PropDesc { + name: "Age", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt32, + }, + PropDesc { + name: "PdbFileName", + in_type: TdhInType::InTypeAnsiString, + out_type: TdhOutType::OutTypeString, + }, +]; + +impl EventSchema for DbgID { + fn provider_guid(&self) -> GUID { + GUID::try_from("b3e675d7-2554-4f18-830b-2762732560de").unwrap() + } + + fn event_id(&self) -> u16 { + 0 + } + + fn opcode(&self) -> u8 { + 36 + } + + fn event_version(&self) -> u8 { + 2 + } + + fn level(&self) -> u8 { + 0 + } + + fn decoding_source(&self) -> DecodingSource { + panic!() + } + + fn provider_name(&self) -> String { + "KernelTraceControl".to_owned() + } + + fn task_name(&self) -> String { + "ImageID".to_owned() + } + + fn opcode_name(&self) -> String { + "DbgID_RSDS".to_string() + } + + fn property_count(&self) -> u32 { + DbgID_PROPS.len() as u32 + } + + fn property(&self, index: u32) -> Property { + let prop = &DbgID_PROPS[index as usize]; + Property { + name: prop.name.to_owned(), + desc: PropertyDesc::Primitive(PrimitiveDesc { + in_type: prop.in_type, + out_type: prop.out_type, + }), + count: 1, + length: PropertyLength::Length(0), + map_info: None, + flags: PropertyFlags::empty(), + } + } +} + +// Info about EventInfo comes from SymbolTraceEventParser.cs in PerfView +// It contains an EVENT_DESCRIPTOR +pub struct EventInfo {} +const EventInfo_PROPS: [PropDesc; 9] = [ + PropDesc { + name: "ProviderGuid", + in_type: TdhInType::InTypeGuid, + out_type: TdhOutType::OutTypeGuid, + }, + PropDesc { + name: "EventGuid", + in_type: TdhInType::InTypeGuid, + out_type: TdhOutType::OutTypeGuid, + }, + PropDesc { + name: "EventDescriptorId", + in_type: TdhInType::InTypeUInt16, + out_type: TdhOutType::OutTypeUInt16, + }, + PropDesc { + name: "EventDescriptor.Version", + in_type: TdhInType::InTypeUInt8, + out_type: TdhOutType::OutTypeUInt8, + }, + PropDesc { + name: "EventDescriptor.Channel", + in_type: TdhInType::InTypeUInt8, + out_type: TdhOutType::OutTypeUInt8, + }, + PropDesc { + name: "EventDescriptor.Level", + in_type: TdhInType::InTypeUInt8, + out_type: TdhOutType::OutTypeUInt8, + }, + PropDesc { + name: "EventDescriptor.Opcode", + in_type: TdhInType::InTypeUInt8, + out_type: TdhOutType::OutTypeUInt8, + }, + PropDesc { + name: "EventDescriptor.Task", + in_type: TdhInType::InTypeUInt16, + out_type: TdhOutType::OutTypeUInt16, + }, + PropDesc { + name: "EventDescriptor.Keyword", + in_type: TdhInType::InTypeUInt64, + out_type: TdhOutType::OutTypeHexInt64, + }, +]; +impl EventSchema for EventInfo { + fn provider_guid(&self) -> GUID { + GUID::try_from("bbccf6c1-6cd1-48c4-80ff-839482e37671").unwrap() + } + + fn event_id(&self) -> u16 { + 0 + } + + fn opcode(&self) -> u8 { + 32 + } + + fn event_version(&self) -> u8 { + 0 + } + + fn level(&self) -> u8 { + 0 + } + + fn decoding_source(&self) -> DecodingSource { + panic!() + } + + fn provider_name(&self) -> String { + "KernelTraceControl".to_owned() + } + + fn task_name(&self) -> String { + "MetaData".to_owned() + } + + fn opcode_name(&self) -> String { + "EventInfo".to_string() + } + + fn property_count(&self) -> u32 { + EventInfo_PROPS.len() as u32 + } + + fn is_event_metadata(&self) -> bool { + true + } + + fn property(&self, index: u32) -> Property { + let prop = &EventInfo_PROPS[index as usize]; + Property { + name: prop.name.to_owned(), + desc: PropertyDesc::Primitive(PrimitiveDesc { + in_type: prop.in_type, + out_type: prop.out_type, + }), + count: 1, + length: PropertyLength::Length(0), + map_info: None, + flags: PropertyFlags::empty(), + } + } +} +// We could override ThreadStop using the same properties to get a ThreadName there too. +pub struct ThreadStart {} + +const Thread_PROPS: [PropDesc; 15] = [ + PropDesc { + name: "ProcessId", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeHexInt32, + }, + PropDesc { + name: "TThreadId", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeHexInt32, + }, + PropDesc { + name: "StackBase", + in_type: TdhInType::InTypePointer, + out_type: TdhOutType::OutTypeNull, + }, + PropDesc { + name: "StackLimit", + in_type: TdhInType::InTypePointer, + out_type: TdhOutType::OutTypeNull, + }, + PropDesc { + name: "UserStackBase", + in_type: TdhInType::InTypePointer, + out_type: TdhOutType::OutTypeNull, + }, + PropDesc { + name: "UserStackLimit", + in_type: TdhInType::InTypePointer, + out_type: TdhOutType::OutTypeNull, + }, + PropDesc { + name: "Affinity", + in_type: TdhInType::InTypePointer, + out_type: TdhOutType::OutTypeNull, + }, + PropDesc { + name: "Win32StartAddr", + in_type: TdhInType::InTypePointer, + out_type: TdhOutType::OutTypeNull, + }, + PropDesc { + name: "TebBase", + in_type: TdhInType::InTypePointer, + out_type: TdhOutType::OutTypeNull, + }, + PropDesc { + name: "SubProcessTag", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeHexInt32, + }, + PropDesc { + name: "BasePriority", + in_type: TdhInType::InTypeUInt8, + out_type: TdhOutType::OutTypeNull, + }, + PropDesc { + name: "PagePriority", + in_type: TdhInType::InTypeUInt8, + out_type: TdhOutType::OutTypeNull, + }, + PropDesc { + name: "IoPriority", + in_type: TdhInType::InTypeUInt8, + out_type: TdhOutType::OutTypeNull, + }, + PropDesc { + name: "ThreadFlags", + in_type: TdhInType::InTypeUInt8, + out_type: TdhOutType::OutTypeNull, + }, + PropDesc { + name: "ThreadName", + in_type: TdhInType::InTypeUnicodeString, + out_type: TdhOutType::OutTypeString, + }, +]; + +impl EventSchema for ThreadStart { + fn provider_guid(&self) -> GUID { + GUID::try_from("3D6FA8D1-FE05-11D0-9DDA-00C04FD7BA7C").unwrap() + } + + fn event_id(&self) -> u16 { + 0 + } + + fn opcode(&self) -> u8 { + 3 + } + + fn event_version(&self) -> u8 { + 3 + } + + fn level(&self) -> u8 { + 0 + } + + fn decoding_source(&self) -> DecodingSource { + panic!() + } + + fn provider_name(&self) -> String { + "MSNT_SystemTrace".to_owned() + } + + fn task_name(&self) -> String { + "Thread".to_owned() + } + + fn opcode_name(&self) -> String { + "DCStart".to_string() + } + + fn property_count(&self) -> u32 { + Thread_PROPS.len() as u32 + } + + fn property(&self, index: u32) -> Property { + let prop = &Thread_PROPS[index as usize]; + Property { + name: prop.name.to_owned(), + desc: PropertyDesc::Primitive(PrimitiveDesc { + in_type: prop.in_type, + out_type: prop.out_type, + }), + length: PropertyLength::Length(0), + count: 1, + map_info: None, + flags: PropertyFlags::empty(), + } + } +} + +pub struct SampledProfile {} + +/// The schema for the SampledProfile event, version 2. +/// +/// This schema is hardcoded because, for a brief time +/// at the end of 2024, Windows was missing the schema +/// definition for this event type, so our queries to +/// look up the schema failed. One of the affected Windows +/// versions was 10.0.26100 24H2. +const SampledProfile_PROPS: [PropDesc; 4] = [ + PropDesc { + name: "InstructionPointer", + in_type: TdhInType::InTypePointer, + out_type: TdhOutType::OutTypeCodePointer, + }, + PropDesc { + name: "ThreadId", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt32, + }, + PropDesc { + name: "Count", + in_type: TdhInType::InTypeUInt16, + out_type: TdhOutType::OutTypeUInt16, + }, + PropDesc { + name: "Reserved", + in_type: TdhInType::InTypeUInt16, + out_type: TdhOutType::OutTypeUInt16, + }, +]; + +impl EventSchema for SampledProfile { + fn provider_guid(&self) -> GUID { + GUID::try_from("ce1dbfb4-137e-4da6-87b0-3f59aa102cbc").unwrap() + } + + fn event_id(&self) -> u16 { + 0 + } + + fn opcode(&self) -> u8 { + 46 + } + + fn event_version(&self) -> u8 { + 2 + } + + fn level(&self) -> u8 { + 0 + } + + fn decoding_source(&self) -> DecodingSource { + panic!() + } + + fn provider_name(&self) -> String { + "MSNT_SystemTrace".to_owned() + } + + fn task_name(&self) -> String { + "PerfInfo".to_owned() + } + + fn opcode_name(&self) -> String { + "SampleProf".to_string() + } + + fn property_count(&self) -> u32 { + SampledProfile_PROPS.len() as u32 + } + + fn property(&self, index: u32) -> Property { + let prop = &SampledProfile_PROPS[index as usize]; + Property { + name: prop.name.to_owned(), + desc: PropertyDesc::Primitive(PrimitiveDesc { + in_type: prop.in_type, + out_type: prop.out_type, + }), + count: 1, + length: PropertyLength::Length(0), + map_info: None, + flags: PropertyFlags::empty(), + } + } +} + +pub struct CSwitch {} + +/// The schema for the CSwitch event, version 4. +/// +/// This schema is hardcoded because, for a brief time +/// at the end of 2024, Windows was missing the schema +/// definition for this event type, so our queries to +/// look up the schema failed. One of the affected Windows +/// versions was 10.0.26100 24H2. +// +// ```mof +// [dynamic: ToInstance, EventType(36)] +// class CSwitch_V4 : Thread_V4 +// { +// [WmiDataId(1), format("x"), read] uint32 NewThreadId; +// [WmiDataId(2), format("x"), read] uint32 OldThreadId; +// [WmiDataId(3), read] sint8 NewThreadPriority; +// [WmiDataId(4), read] sint8 OldThreadPriority; +// [WmiDataId(5), read] uint8 PreviousCState; +// [WmiDataId(6), read] sint8 SpareByte; +// [WmiDataId(7), read] sint8 OldThreadWaitReason; +// [WmiDataId(8), read] sint8 ThreadFlags; +// [WmiDataId(9), read] sint8 OldThreadState; +// [WmiDataId(10), read] sint8 OldThreadWaitIdealProcessor; +// [WmiDataId(11), format("x"), read] uint32 NewThreadWaitTime; +// [WmiDataId(12), read] uint32 Reserved; +// }; +// ``` +const CSwitch_V4_PROPS: [PropDesc; 12] = [ + PropDesc { + name: "NewThreadId", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt32, + }, + PropDesc { + name: "OldThreadId", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt32, + }, + PropDesc { + name: "NewThreadPriority", + in_type: TdhInType::InTypeInt8, + out_type: TdhOutType::OutTypeInt8, + }, + PropDesc { + name: "OldThreadPriority", + in_type: TdhInType::InTypeInt8, + out_type: TdhOutType::OutTypeInt8, + }, + PropDesc { + name: "PreviousCState", + in_type: TdhInType::InTypeUInt8, + out_type: TdhOutType::OutTypeUInt8, + }, + PropDesc { + name: "SpareByte", + in_type: TdhInType::InTypeInt8, + out_type: TdhOutType::OutTypeInt8, + }, + PropDesc { + name: "OldThreadWaitReason", + in_type: TdhInType::InTypeInt8, + out_type: TdhOutType::OutTypeInt8, + }, + PropDesc { + name: "ThreadFlags", + in_type: TdhInType::InTypeInt8, + out_type: TdhOutType::OutTypeInt8, + }, + PropDesc { + name: "OldThreadState", + in_type: TdhInType::InTypeInt8, + out_type: TdhOutType::OutTypeInt8, + }, + PropDesc { + name: "OldThreadWaitIdealProcessor", + in_type: TdhInType::InTypeInt8, + out_type: TdhOutType::OutTypeInt8, + }, + PropDesc { + name: "NewThreadWaitTime", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt32, + }, + PropDesc { + name: "Reserved", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt32, + }, +]; + +impl EventSchema for CSwitch { + fn provider_guid(&self) -> GUID { + GUID::try_from("3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c").unwrap() // class Thread_V4 : MSNT_SystemTrace + } + + fn event_id(&self) -> u16 { + 0 + } + + fn opcode(&self) -> u8 { + 36 + } + + fn event_version(&self) -> u8 { + // Warning: We are pretending to be version 5, because that's + // what matches the events observed on Windows 10.0.26100 Build 26100, + // which is the Windows version where the schema was missing. + // But we don't actually know the correct V5 schema! There are likely + // one or two fields added at the end which our hardcoded V4 schema + // doesn't have. + 5 + } + + fn level(&self) -> u8 { + 0 + } + + fn decoding_source(&self) -> DecodingSource { + panic!() + } + + fn provider_name(&self) -> String { + "MSNT_SystemTrace".to_owned() + } + + fn task_name(&self) -> String { + "Thread".to_owned() + } + + fn opcode_name(&self) -> String { + "CSwitch".to_string() + } + + fn property_count(&self) -> u32 { + CSwitch_V4_PROPS.len() as u32 + } + + fn property(&self, index: u32) -> Property { + let prop = &CSwitch_V4_PROPS[index as usize]; + Property { + name: prop.name.to_owned(), + desc: PropertyDesc::Primitive(PrimitiveDesc { + in_type: prop.in_type, + out_type: prop.out_type, + }), + count: 1, + length: PropertyLength::Length(0), + map_info: None, + flags: PropertyFlags::empty(), + } + } +} + +// from umdprovider.h +pub struct D3DUmdLogging_MapAllocation {} + +const D3DUmdLogging_PROPS: [PropDesc; 6] = [ + PropDesc { + name: "hD3DAllocation", + in_type: TdhInType::InTypeUInt64, + out_type: TdhOutType::OutTypeHexInt64, + }, + PropDesc { + name: "hDxgAllocation", + in_type: TdhInType::InTypeUInt64, + out_type: TdhOutType::OutTypeHexInt64, + }, + PropDesc { + name: "Offset", + in_type: TdhInType::InTypeUInt64, + out_type: TdhOutType::OutTypeUInt64, + }, + PropDesc { + name: "Size", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt64, + }, + // XXX: use an enum for these + PropDesc { + name: "Usage", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt32, + }, + PropDesc { + name: "Semantic", + in_type: TdhInType::InTypeUInt32, + out_type: TdhOutType::OutTypeUInt32, + }, +]; + +impl EventSchema for D3DUmdLogging_MapAllocation { + fn provider_guid(&self) -> GUID { + GUID::try_from("A688EE40-D8D9-4736-B6F9-6B74935BA3B1").unwrap() + } + + fn event_id(&self) -> u16 { + 1 + } + + fn event_version(&self) -> u8 { + 0 + } + + fn opcode(&self) -> u8 { + 1 + } + + fn level(&self) -> u8 { + 0 + } + + fn decoding_source(&self) -> DecodingSource { + panic!() + } + + fn provider_name(&self) -> String { + "D3DUmdLogging".to_owned() + } + + fn task_name(&self) -> String { + "MapAllocation".to_owned() + } + + fn opcode_name(&self) -> String { + "Start".to_string() + } + + fn property_count(&self) -> u32 { + D3DUmdLogging_PROPS.len() as u32 + } + + fn property(&self, index: u32) -> Property { + let prop = &D3DUmdLogging_PROPS[index as usize]; + Property { + name: prop.name.to_owned(), + desc: PropertyDesc::Primitive(PrimitiveDesc { + in_type: prop.in_type, + out_type: prop.out_type, + }), + count: 1, + length: PropertyLength::Length(0), + map_info: None, + flags: PropertyFlags::empty(), + } + } +} + +pub struct D3DUmdLogging_RundownAllocation {} + +impl EventSchema for D3DUmdLogging_RundownAllocation { + fn provider_guid(&self) -> GUID { + GUID::try_from("A688EE40-D8D9-4736-B6F9-6B74935BA3B1").unwrap() + } + + fn event_id(&self) -> u16 { + 2 + } + + fn event_version(&self) -> u8 { + 0 + } + + fn opcode(&self) -> u8 { + 3 + } + + fn level(&self) -> u8 { + 0 + } + + fn decoding_source(&self) -> DecodingSource { + panic!() + } + + fn provider_name(&self) -> String { + "D3DUmdLogging".to_owned() + } + + fn task_name(&self) -> String { + "MapAllocation".to_owned() + } + + fn opcode_name(&self) -> String { + "DC Start".to_string() + } + + fn property_count(&self) -> u32 { + D3DUmdLogging_PROPS.len() as u32 + } + + fn property(&self, index: u32) -> Property { + let prop = &D3DUmdLogging_PROPS[index as usize]; + Property { + name: prop.name.to_owned(), + desc: PropertyDesc::Primitive(PrimitiveDesc { + in_type: prop.in_type, + out_type: prop.out_type, + }), + count: 1, + length: PropertyLength::Length(0), + map_info: None, + flags: PropertyFlags::empty(), + } + } +} + +pub struct D3DUmdLogging_UnmapAllocation {} + +impl EventSchema for D3DUmdLogging_UnmapAllocation { + fn provider_guid(&self) -> GUID { + GUID::try_from("A688EE40-D8D9-4736-B6F9-6B74935BA3B1").unwrap() + } + + fn event_id(&self) -> u16 { + 3 + } + + fn event_version(&self) -> u8 { + 0 + } + + fn opcode(&self) -> u8 { + 2 + } + + fn level(&self) -> u8 { + 0 + } + + fn decoding_source(&self) -> DecodingSource { + panic!() + } + + fn provider_name(&self) -> String { + "D3DUmdLogging".to_owned() + } + + fn task_name(&self) -> String { + "MapAllocation".to_owned() + } + + fn opcode_name(&self) -> String { + "End".to_string() + } + + fn property_count(&self) -> u32 { + D3DUmdLogging_PROPS.len() as u32 + } + + fn property(&self, index: u32) -> Property { + let prop = &D3DUmdLogging_PROPS[index as usize]; + Property { + name: prop.name.to_owned(), + desc: PropertyDesc::Primitive(PrimitiveDesc { + in_type: prop.in_type, + out_type: prop.out_type, + }), + count: 1, + length: PropertyLength::Length(0), + map_info: None, + flags: PropertyFlags::empty(), + } + } +} diff --git a/samply/src/windows/etw_reader/etw_types.rs b/samply/src/windows/etw_reader/etw_types.rs new file mode 100644 index 00000000..5fad717e --- /dev/null +++ b/samply/src/windows/etw_reader/etw_types.rs @@ -0,0 +1,313 @@ +use std::ops::Deref; +use std::rc::Rc; + +use once_cell::unsync::OnceCell; +use windows::core::{GUID, PCWSTR}; +use windows::Win32::System::Diagnostics::Etw::{self, PropertyStruct}; + +use super::schema::EventSchema; +use super::tdh_types::{Property, PropertyMapInfo}; +use super::utils; + +#[repr(transparent)] +pub struct EventRecord(Etw::EVENT_RECORD); + +impl Deref for EventRecord { + type Target = Etw::EVENT_RECORD; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl EventRecord { + pub(crate) fn user_buffer(&self) -> &[u8] { + if self.UserData.is_null() { + return &[]; + } + unsafe { std::slice::from_raw_parts(self.UserData as *mut _, self.UserDataLength.into()) } + } +} + +/// Newtype wrapper over an [EVENT_PROPERTY_INFO] +/// +/// [EVENT_PROPERTY_INFO]: https://microsoft.github.io/windows-docs-rs/doc/bindings/Windows/Win32/Etw/struct.EVENT_PROPERTY_INFO.html +#[repr(C)] +#[derive(Clone, Copy)] +pub struct EventPropertyInfo(Etw::EVENT_PROPERTY_INFO); + +impl std::ops::Deref for EventPropertyInfo { + type Target = Etw::EVENT_PROPERTY_INFO; + + fn deref(&self) -> &self::Etw::EVENT_PROPERTY_INFO { + &self.0 + } +} + +impl std::ops::DerefMut for EventPropertyInfo { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From<&[u8]> for EventPropertyInfo { + fn from(val: &[u8]) -> Self { + unsafe { *(val.as_ptr() as *mut EventPropertyInfo) } + } +} + +impl Default for EventPropertyInfo { + fn default() -> Self { + unsafe { std::mem::zeroed::() } + } +} + +// Safe cast (EVENT_HEADER_FLAG_32_BIT_HEADER = 32) +#[doc(hidden)] +pub const EVENT_HEADER_FLAG_32_BIT_HEADER: u16 = Etw::EVENT_HEADER_FLAG_32_BIT_HEADER as u16; + +/// Wrapper over the [DECODING_SOURCE] type +/// +/// [DECODING_SOURCE]: https://microsoft.github.io/windows-docs-rs/doc/bindings/Windows/Win32/Etw/struct.DECODING_SOURCE.html +#[derive(Debug)] +pub enum DecodingSource { + XMLFile, + Wbem, + Wpp, + Tlg, + Max, +} + +impl From for DecodingSource { + fn from(val: Etw::DECODING_SOURCE) -> Self { + match val { + Etw::DecodingSourceXMLFile => DecodingSource::XMLFile, + Etw::DecodingSourceWbem => DecodingSource::Wbem, + Etw::DecodingSourceWPP => DecodingSource::Wpp, + Etw::DecodingSourceTlg => DecodingSource::Tlg, + _ => DecodingSource::Max, + } + } +} + +/// Newtype wrapper over an [TRACE_EVENT_INFO] +/// +/// [TRACE_EVENT_INFO]: https://microsoft.github.io/windows-docs-rs/doc/bindings/Windows/Win32/Etw/struct.TRACE_EVENT_INFO.html +#[repr(C)] +#[derive(Clone, Copy)] +pub struct TraceEventInfo(Etw::TRACE_EVENT_INFO); + +impl std::ops::Deref for TraceEventInfo { + type Target = Etw::TRACE_EVENT_INFO; + + fn deref(&self) -> &self::Etw::TRACE_EVENT_INFO { + &self.0 + } +} + +#[repr(C)] +#[derive(Debug, Clone, Default)] +pub struct TraceEventInfoRaw { + pub(crate) info: Vec, + property_maps: OnceCell>>>>, +} + +impl std::ops::DerefMut for TraceEventInfo { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From<&TraceEventInfoRaw> for TraceEventInfo { + fn from(val: &TraceEventInfoRaw) -> Self { + unsafe { *(val.info.as_ptr() as *mut TraceEventInfo) } + } +} + +impl TraceEventInfoRaw { + pub(crate) fn new(info: Vec) -> Self { + TraceEventInfoRaw { + info, + property_maps: OnceCell::new(), + } + } + pub(crate) fn alloc(len: u32) -> Self { + TraceEventInfoRaw { + info: vec![0; len as usize], + property_maps: OnceCell::new(), + } + } + + pub fn info_as_ptr(&mut self) -> *mut u8 { + self.info.as_mut_ptr() + } + + fn property_map_info(&self, index: u32) -> Option> { + // let's make sure index is not bigger thant the PropertyCount + assert!(index <= TraceEventInfo::from(self).PropertyCount); + let property_maps = self.property_maps.get_or_init(|| { + vec![OnceCell::new(); TraceEventInfo::from(self).PropertyCount as usize] + }); + let map = property_maps[index as usize].get_or_init(|| { + // We need to subtract the sizeof(EVENT_PROPERTY_INFO) due to how TRACE_EVENT_INFO is declared + // in the bindings, the last field `EventPropertyInfoArray[ANYSIZE_ARRAY]` is declared as + // [EVENT_PROPERTY_INFO; 1] + // https://microsoft.github.io/windows-docs-rs/doc/bindings/Windows/Win32/Etw/struct.TRACE_EVENT_INFO.html#structfield.EventPropertyInfoArray + let curr_prop_offset = index as usize * std::mem::size_of::() + + (std::mem::size_of::() + - std::mem::size_of::()); + + let curr_prop = EventPropertyInfo::from(&self.info[curr_prop_offset..]); + if curr_prop.Flags.0 & PropertyStruct.0 == 0 { + // This property is a struct so it has no map info + return None; + } else { + unsafe { + if curr_prop.Anonymous1.nonStructType.MapNameOffset != 0 { + // build an empty event record that we can use to get the map info + let mut event: Etw::EVENT_RECORD = std::mem::zeroed(); + event.EventHeader.ProviderId = self.provider_guid(); + + let mut buffer_size = 0; + let map_name = PCWSTR( + self.info[curr_prop.Anonymous1.nonStructType.MapNameOffset as usize..] + .as_ptr() as *mut u16, + ); + use windows::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER; + // println!("map_name {}", utils::parse_unk_size_null_utf16_string(&self.info[curr_prop.Anonymous1.nonStructType.MapNameOffset as usize..])); + if Etw::TdhGetEventMapInformation(&event, map_name, None, &mut buffer_size) + != ERROR_INSUFFICIENT_BUFFER.0 + { + panic!("expected this to fail"); + } + + let mut buffer = vec![0; buffer_size as usize]; + if Etw::TdhGetEventMapInformation( + &event, + map_name, + Some(buffer.as_mut_ptr() as *mut _), + &mut buffer_size, + ) != 0 + { + panic!(); + } + + let map_info: &super::Etw::EVENT_MAP_INFO = &*(buffer.as_ptr() as *const _); + if map_info.Flag == super::Etw::EVENTMAP_INFO_FLAG_MANIFEST_VALUEMAP + || map_info.Flag == super::Etw::EVENTMAP_INFO_FLAG_MANIFEST_BITMAP + { + let is_bitmap = + map_info.Flag == super::Etw::EVENTMAP_INFO_FLAG_MANIFEST_BITMAP; + let mut map = super::FastHashMap::default(); + assert!( + map_info.Anonymous.MapEntryValueType + == super::Etw::EVENTMAP_ENTRY_VALUETYPE_ULONG + ); + let entries = std::slice::from_raw_parts( + map_info.MapEntryArray.as_ptr(), + map_info.EntryCount as usize, + ); + for e in entries { + let value = e.Anonymous.Value; + let name = utils::parse_unk_size_null_utf16_string( + &buffer[e.OutputOffset as usize..], + ); + // println!("{} -> {:?}", value, name); + map.insert(value, name); + } + return Some(Rc::new(PropertyMapInfo { is_bitmap, map })); + } else { + eprint!("unsupported map type {:?}", map_info.Flag); + } + } + } + } + None + }); + map.clone() + } +} + +impl EventSchema for TraceEventInfoRaw { + fn provider_guid(&self) -> GUID { + TraceEventInfo::from(self).ProviderGuid + } + + fn event_id(&self) -> u16 { + TraceEventInfo::from(self).EventDescriptor.Id + } + + fn opcode(&self) -> u8 { + TraceEventInfo::from(self).EventDescriptor.Opcode + } + + fn event_version(&self) -> u8 { + TraceEventInfo::from(self).EventDescriptor.Version + } + + fn level(&self) -> u8 { + TraceEventInfo::from(self).EventDescriptor.Level + } + + fn decoding_source(&self) -> DecodingSource { + DecodingSource::from(TraceEventInfo::from(self).DecodingSource) + } + + fn provider_name(&self) -> String { + let provider_name_offset = TraceEventInfo::from(self).ProviderNameOffset as usize; + // TODO: Evaluate performance, but this sounds better than creating a whole Vec and getting the string from the offset/2 + utils::parse_unk_size_null_utf16_string(&self.info[provider_name_offset..]) + } + + fn task_name(&self) -> String { + let task_name_offset = TraceEventInfo::from(self).TaskNameOffset as usize; + utils::parse_unk_size_null_utf16_string(&self.info[task_name_offset..]) + } + + fn opcode_name(&self) -> String { + let opcode_name_offset = TraceEventInfo::from(self).OpcodeNameOffset as usize; + if opcode_name_offset == 0 { + return String::from(match self.opcode() { + 0 => "Info", + 1 => "Start", + 2 => "Stop", + 3 => "DCStart", + 4 => "DCStop", + _ => "", + }); + } + utils::parse_unk_size_null_utf16_string(&self.info[opcode_name_offset..]) + } + + fn property_count(&self) -> u32 { + TraceEventInfo::from(self).TopLevelPropertyCount + } + + fn property(&self, index: u32) -> Property { + // let's make sure index is not bigger thant the PropertyCount + assert!(index <= TraceEventInfo::from(self).PropertyCount); + + // We need to subtract the sizeof(EVENT_PROPERTY_INFO) due to how TRACE_EVENT_INFO is declared + // in the bindings, the last field `EventPropertyInfoArray[ANYSIZE_ARRAY]` is declared as + // [EVENT_PROPERTY_INFO; 1] + // https://microsoft.github.io/windows-docs-rs/doc/bindings/Windows/Win32/Etw/struct.TRACE_EVENT_INFO.html#structfield.EventPropertyInfoArray + let curr_prop_offset = index as usize * std::mem::size_of::() + + (std::mem::size_of::() - std::mem::size_of::()); + + let curr_prop = EventPropertyInfo::from(&self.info[curr_prop_offset..]); + let name = + utils::parse_unk_size_null_utf16_string(&self.info[curr_prop.NameOffset as usize..]); + Property::new(name, &curr_prop, self.property_map_info(index)) + } + + fn event_message(&self) -> Option { + let offset = TraceEventInfo::from(self).EventMessageOffset; + if offset != 0 { + Some(utils::parse_unk_size_null_utf16_string( + &self.info[offset as usize..], + )) + } else { + None + } + } +} diff --git a/samply/src/windows/etw_reader/mod.rs b/samply/src/windows/etw_reader/mod.rs new file mode 100644 index 00000000..fba5b2e7 --- /dev/null +++ b/samply/src/windows/etw_reader/mod.rs @@ -0,0 +1,622 @@ +use fxhash::FxHasher; +use memoffset::offset_of; +pub use windows::core::GUID; +use windows::core::{h, HSTRING, PWSTR}; +use windows::Win32::Foundation::{ + GetLastError, ERROR_INSUFFICIENT_BUFFER, ERROR_MORE_DATA, MAX_PATH, +}; +use windows::Win32::System::Diagnostics::Etw; +use windows::Win32::System::Diagnostics::Etw::{ + EnumerateTraceGuids, EnumerateTraceGuidsEx, TraceGuidQueryInfo, TraceGuidQueryList, + CONTROLTRACE_HANDLE, EVENT_TRACE_FLAG, TRACE_GUID_INFO, TRACE_GUID_PROPERTIES, + TRACE_PROVIDER_INSTANCE_INFO, +}; + +use std::borrow::Cow; +use std::collections::HashMap; +use std::hash::BuildHasherDefault; +use std::mem; +use std::path::Path; + +// typedef ULONG64 TRACEHANDLE, *PTRACEHANDLE; +pub(crate) type TraceHandle = u64; +pub const INVALID_TRACE_HANDLE: TraceHandle = u64::MAX; +//, WindowsProgramming}; + +#[allow(non_upper_case_globals, non_camel_case_types)] +pub mod custom_schemas; +pub mod etw_types; +pub mod parser; +pub mod property; +pub mod schema; +pub mod sddl; +pub mod tdh; +pub mod tdh_types; +pub mod traits; +pub mod utils; +//pub mod trace; +//pub mod provider; + +use etw_types::EventRecord; +use parser::{Parser, ParserError, TryParse}; +use schema::SchemaLocator; +use tdh_types::{PrimitiveDesc, PropertyDesc, TdhInType}; +use tdh_types::{Property, TdhOutType}; +use traits::EncodeUtf16; + +pub type FastHashMap = HashMap>; +#[repr(C)] +#[derive(Clone)] +pub struct EventTraceLogfile(Etw::EVENT_TRACE_LOGFILEW); + +impl Default for EventTraceLogfile { + fn default() -> Self { + unsafe { std::mem::zeroed::() } + } +} + +impl std::ops::Deref for EventTraceLogfile { + type Target = Etw::EVENT_TRACE_LOGFILEW; + + fn deref(&self) -> &self::Etw::EVENT_TRACE_LOGFILEW { + &self.0 + } +} +impl std::ops::DerefMut for EventTraceLogfile { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +unsafe extern "system" fn trace_callback_thunk(event_record: *mut Etw::EVENT_RECORD) { + let f: &mut &mut dyn FnMut(&EventRecord) = &mut *((*event_record).UserContext + as *mut &mut dyn for<'a> std::ops::FnMut(&'a etw_types::EventRecord)); + f(&*(event_record as *const etw_types::EventRecord)) +} + +pub fn open_trace( + path: &Path, + mut callback: F, +) -> Result<(), std::io::Error> { + let mut log_file = EventTraceLogfile::default(); + + #[cfg(windows)] + let path = HSTRING::from(path.as_os_str()); + #[cfg(not(windows))] + let path = HSTRING::from(path.to_string_lossy().to_string()); + log_file.0.LogFileName = PWSTR(path.as_ptr() as *mut _); + log_file.0.Anonymous1.ProcessTraceMode = + Etw::PROCESS_TRACE_MODE_EVENT_RECORD | Etw::PROCESS_TRACE_MODE_RAW_TIMESTAMP; + let mut cb: &mut dyn FnMut(&EventRecord) = &mut callback; + log_file.0.Context = unsafe { std::mem::transmute(&mut cb) }; + log_file.0.Anonymous2.EventRecordCallback = Some(trace_callback_thunk); + + let session_handle = unsafe { Etw::OpenTraceW(&mut *log_file) }; + let result = unsafe { Etw::ProcessTrace(&[session_handle], None, None) }; + result + .ok() + .map_err(|e| std::io::Error::from_raw_os_error(e.code().0)) +} + +/// Complete Trace Properties struct +/// +/// The [EventTraceProperties] struct contains the information about a tracing session, this struct +/// also needs two buffers right after it to hold the log file name and the session name. This struct +/// provides the full definition of the properties plus the the allocation for both names +/// +/// See: [EVENT_TRACE_PROPERTIES](https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties) +#[repr(C)] +#[derive(Clone, Copy)] +pub struct TraceInfo { + pub properties: Etw::EVENT_TRACE_PROPERTIES, + // it's not clear that these need to be u16 if using with the Unicode versions of the functions ETW functions + trace_name: [u16; MAX_PATH as usize], + log_file_name: [u16; MAX_PATH as usize], +} + +impl Default for TraceInfo { + fn default() -> Self { + let properties = Etw::EVENT_TRACE_PROPERTIES::default(); + TraceInfo { + properties, + trace_name: [0; 260], + log_file_name: [0; 260], + } + } +} + +impl TraceInfo { + pub(crate) fn fill( + &mut self, + trace_name: &str, + //trace_properties: &TraceProperties, + ) { + self.properties.Wnode.BufferSize = std::mem::size_of::() as u32; + self.properties.Wnode.Guid = GUID::new().unwrap(); + self.properties.Wnode.Flags = Etw::WNODE_FLAG_TRACED_GUID; + self.properties.Wnode.ClientContext = 1; // QPC clock resolution + self.properties.FlushTimer = 1; + + self.properties.LogFileMode = + Etw::EVENT_TRACE_REAL_TIME_MODE | Etw::EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING; + + self.properties.EnableFlags = EVENT_TRACE_FLAG(0); + + //self.properties.LoggerNameOffset = offset_of!(TraceInfo, log_file_name) as u32; + //self.trace_name[..trace_name.len()].copy_from_slice(trace_name.as_bytes()) + + // it doesn't seem like it matters if we fill in trace_name + self.properties.LoggerNameOffset = offset_of!(TraceInfo, trace_name) as u32; + self.properties.LogFileNameOffset = offset_of!(TraceInfo, log_file_name) as u32; + self.trace_name[..trace_name.len() + 1].copy_from_slice(&trace_name.to_utf16()) + } +} + +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct EnableTraceParameters(Etw::ENABLE_TRACE_PARAMETERS); + +impl EnableTraceParameters { + pub fn create(guid: GUID, trace_flags: u32) -> Self { + let mut params = EnableTraceParameters::default(); + params.0.ControlFlags = 0; + params.0.Version = Etw::ENABLE_TRACE_PARAMETERS_VERSION_2; + params.0.SourceId = guid; + params.0.EnableProperty = trace_flags; + + // TODO: Add Filters option + params.0.EnableFilterDesc = std::ptr::null_mut(); + params.0.FilterDescCount = 0; + + params + } +} + +impl std::ops::Deref for EnableTraceParameters { + type Target = Etw::ENABLE_TRACE_PARAMETERS; + + fn deref(&self) -> &self::Etw::ENABLE_TRACE_PARAMETERS { + &self.0 + } +} + +impl std::ops::DerefMut for EnableTraceParameters { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Default for Provider { + fn default() -> Self { + Self::new() + } +} + +impl Provider { + /// Use the `new` function to create a Provider builder + /// + /// This function will create a by-default provider which can be tweaked afterwards + /// + /// # Example + /// ```rust + /// let my_provider = Provider::new(); + /// ``` + pub fn new() -> Self { + Provider { + guid: None, + any: 0, + all: 0, + level: 5, + trace_flags: 0, + flags: 0, + } + } + + pub fn by_guid(mut self, guid: &str) -> Self { + self.guid = Some(GUID::try_from(guid).unwrap()); + self + } +} + +pub struct Provider { + /// Option that represents a Provider GUID + pub guid: Option, + /// Provider Any keyword + pub any: u64, + /// Provider All keyword + pub all: u64, + /// Provider level flag + pub level: u8, + /// Provider trace flags + pub trace_flags: u32, + /// Provider kernel flags, only apply to KernelProvider + pub flags: u32, // Only applies to KernelProviders + // perfinfo + + // filters: RwLock>, +} + +pub fn start_trace(mut callback: F) { + // let guid_str = "22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716"; + let guid_str = "DB6F6DDB-AC77-4E88-8253-819DF9BBF140"; + let video_blt_guid = GUID::try_from(guid_str).unwrap(); //GUID::from("DB6F6DDB-AC77-4E88-8253-819DF9BBF140"); + + let session_name = h!("aaaaaa"); + + let mut info = TraceInfo::default(); + info.fill(&session_name.to_string()); + + let mut handle = CONTROLTRACE_HANDLE { Value: 0 }; + + unsafe { + let status = Etw::ControlTraceW( + handle, + session_name, + &info.properties as *const _ as *mut _, + Etw::EVENT_TRACE_CONTROL_STOP, + ); + println!("ControlTrace = {:?}", status); + let status = Etw::StartTraceW( + &mut handle, + session_name, + &info.properties as *const _ as *mut _, + ); + println!("StartTrace = {:?} handle {:?}", status, handle); + info.trace_name = [0; 260]; + + let status = Etw::ControlTraceW( + handle, + session_name, + &mut info.properties, + Etw::EVENT_TRACE_CONTROL_QUERY, + ); + println!( + "ControlTrace = {:?} {} {:?} {:?}", + status, info.properties.BufferSize, info.properties.LoggerThreadId, info.trace_name + ); + } + + let prov = Provider::new().by_guid(guid_str); + let parameters = EnableTraceParameters::create(video_blt_guid, prov.trace_flags); + /* + let status = unsafe { Etw::EnableTrace(1, 0xffffffff, Etw::TRACE_LEVEL_VERBOSE, &video_blt_guid, handle)}; + println!("EnableTrace = {}", status); + */ + unsafe { + let _ = Etw::EnableTraceEx2( + handle, + &video_blt_guid, + 1, // Fixme: EVENT_CONTROL_CODE_ENABLE_PROVIDER + prov.level, + prov.any, + prov.all, + 0, + Some(&*parameters), + ); + } + + let mut trace = EventTraceLogfile::default(); + trace.0.LoggerName = PWSTR(session_name.as_ptr() as *mut _); + trace.0.Anonymous1.ProcessTraceMode = + Etw::PROCESS_TRACE_MODE_REAL_TIME | Etw::PROCESS_TRACE_MODE_EVENT_RECORD; + let mut cb: &mut dyn FnMut(&EventRecord) = &mut callback; + trace.0.Context = unsafe { std::mem::transmute(&mut cb) }; + trace.0.Anonymous2.EventRecordCallback = Some(trace_callback_thunk); + + let session_handle = unsafe { Etw::OpenTraceW(&mut *trace) }; + if session_handle.Value == INVALID_TRACE_HANDLE { + println!( + "{:?} {:?}", + unsafe { GetLastError() }, + windows::core::Error::from_win32() + ); + + panic!("Invalid handle"); + } + println!("OpenTrace {:?}", session_handle); + let status = unsafe { Etw::ProcessTrace(&[session_handle], None, None) }; + println!("status: {:?}", status); +} + +pub fn event_properties_to_string( + s: &schema::TypedEvent, + parser: &mut Parser, + skip_properties: Option<&[&str]>, +) -> String { + let mut text = String::new(); + for i in 0..s.property_count() { + let property = s.property(i); + if let Some(propfilter) = skip_properties { + if propfilter.iter().any(|&s| s == property.name) { + continue; + } + } + + write_property(&mut text, parser, &property, false); + text += ", " + } + + text +} + +pub fn write_property( + output: &mut dyn std::fmt::Write, + parser: &mut Parser, + property: &Property, + write_types: bool, +) { + if write_types { + let type_name = if let PropertyDesc::Primitive(prim) = &property.desc { + format!("{:?}", prim.in_type) + } else { + format!("{:?}", property.desc) + }; + if property.flags.is_empty() { + write!(output, " {}: {} = ", property.name, type_name).unwrap(); + } else { + write!( + output, + " {}({:?}): {} = ", + property.name, property.flags, type_name + ) + .unwrap(); + } + } else { + write!(output, " {}= ", property.name).unwrap(); + } + if let Some(map_info) = &property.map_info { + let value = match property.desc { + PropertyDesc::Primitive(PrimitiveDesc { + in_type: TdhInType::InTypeUInt32, + .. + }) => TryParse::::parse(parser, &property.name), + PropertyDesc::Primitive(PrimitiveDesc { + in_type: TdhInType::InTypeUInt16, + .. + }) => TryParse::::parse(parser, &property.name) as u32, + PropertyDesc::Primitive(PrimitiveDesc { + in_type: TdhInType::InTypeUInt8, + .. + }) => TryParse::::parse(parser, &property.name) as u32, + _ => panic!("{:?}", property.desc), + }; + if map_info.is_bitmap { + let remaining_bits_str; + let mut matches: Vec<&str> = Vec::new(); + let mut cleared_value = value; + for (k, v) in &map_info.map { + if value & k != 0 { + matches.push(v.trim()); + cleared_value &= !k; + } + } + if cleared_value != 0 { + remaining_bits_str = format!("{:x}", cleared_value); + matches.push(&remaining_bits_str); + //println!("unnamed bits {:x} {:x} {:x?}", value, cleared_value, map_info.map); + } + write!(output, "{}", matches.join(" | ")).unwrap(); + } else { + write!( + output, + "{}", + map_info + .map + .get(&value) + .map(Cow::from) + .unwrap_or_else(|| Cow::from(format!("Unknown: {}", value))) + ) + .unwrap(); + } + } else { + let value = match &property.desc { + PropertyDesc::Primitive(desc) => { + // XXX: we should be using the out_type here instead of in_type + match desc.in_type { + TdhInType::InTypeUnicodeString => { + TryParse::::try_parse(parser, &property.name) + } + TdhInType::InTypeAnsiString => { + TryParse::::try_parse(parser, &property.name) + } + TdhInType::InTypeBoolean => { + TryParse::::try_parse(parser, &property.name).map(|x| x.to_string()) + } + TdhInType::InTypeHexInt32 => { + TryParse::::try_parse(parser, &property.name).map(|x| x.to_string()) + } + TdhInType::InTypeUInt32 => { + TryParse::::try_parse(parser, &property.name).map(|x| x.to_string()) + } + TdhInType::InTypeUInt16 => { + TryParse::::try_parse(parser, &property.name).map(|x| x.to_string()) + } + TdhInType::InTypeUInt8 => { + TryParse::::try_parse(parser, &property.name).map(|x| x.to_string()) + } + TdhInType::InTypeInt8 => { + TryParse::::try_parse(parser, &property.name).map(|x| x.to_string()) + } + TdhInType::InTypeInt64 => { + TryParse::::try_parse(parser, &property.name).map(|x| x.to_string()) + } + TdhInType::InTypeUInt64 => { + let i = TryParse::::try_parse(parser, &property.name); + if desc.out_type == TdhOutType::OutTypeHexInt64 { + i.map(|x| format!("0x{:x}", x)) + } else { + i.map(|x| x.to_string()) + } + } + TdhInType::InTypeHexInt64 => { + let i = TryParse::::try_parse(parser, &property.name); + i.map(|x| format!("0x{:x}", x)) + } + TdhInType::InTypePointer | TdhInType::InTypeSizeT => { + TryParse::::try_parse(parser, &property.name) + .map(|x| format!("0x{:x}", x)) + } + TdhInType::InTypeGuid => TryParse::::try_parse(parser, &property.name) + .map(|x| format!("{:?}", x)), + TdhInType::InTypeInt32 => { + TryParse::::try_parse(parser, &property.name).map(|x| x.to_string()) + } + TdhInType::InTypeFloat => { + TryParse::::try_parse(parser, &property.name).map(|x| x.to_string()) + } + _ => Ok(format!("Unknown {:?} -> {:?}", desc.in_type, desc.out_type)), + } + } + PropertyDesc::Struct(desc) => Ok(format!( + "unhandled struct {} {}", + desc.start_index, desc.num_members + )), + }; + let value = match value { + Ok(value) => value, + Err(ParserError::InvalidType) => format!("invalid type {:?}", property.desc), + Err(ParserError::LengthMismatch) => format!( + "Err(LengthMismatch) type: {:?}, flags: {:?}, buf: {}", + property.desc, + property.flags, + parser.buffer.len() + ), + Err(e) => format!("Err({:?}) type: {:?}", e, property.desc), + }; + write!(output, "{}", value).unwrap(); + } +} + +pub fn print_property(parser: &mut Parser, property: &Property, write_types: bool) { + let mut result = String::new(); + write_property(&mut result, parser, property, write_types); + println!("{}", result); +} + +pub fn add_custom_schemas(locator: &mut SchemaLocator) { + locator.add_custom_schema(Box::new(custom_schemas::ImageID {})); + locator.add_custom_schema(Box::new(custom_schemas::DbgID {})); + locator.add_custom_schema(Box::new(custom_schemas::EventInfo {})); + locator.add_custom_schema(Box::new(custom_schemas::ThreadStart {})); + locator.add_custom_schema(Box::new(custom_schemas::CSwitch {})); + locator.add_custom_schema(Box::new(custom_schemas::SampledProfile {})); + locator.add_custom_schema(Box::new(custom_schemas::D3DUmdLogging_MapAllocation {})); + locator.add_custom_schema(Box::new(custom_schemas::D3DUmdLogging_RundownAllocation {})); + locator.add_custom_schema(Box::new(custom_schemas::D3DUmdLogging_UnmapAllocation {})); +} + +pub fn enumerate_trace_guids() { + let mut count = 1; + loop { + let mut guids: Vec = + vec![unsafe { std::mem::zeroed() }; count as usize]; + let mut ptrs: Vec<*mut TRACE_GUID_PROPERTIES> = Vec::new(); + for guid in &mut guids { + ptrs.push(guid) + } + + let result = unsafe { EnumerateTraceGuids(ptrs.as_mut_slice(), &mut count) }; + match result.ok() { + Ok(()) => { + for guid in guids[..count as usize].iter() { + println!("{:?}", guid.Guid); + } + break; + } + Err(e) => { + if e.code() != ERROR_MORE_DATA.to_hresult() { + break; + } + } + } + } +} + +pub fn enumerate_trace_guids_ex(print_instances: bool) { + let mut required_size: u32 = 0; + + loop { + let mut guids: Vec = + vec![GUID::zeroed(); required_size as usize / mem::size_of::()]; + + let size = (guids.len() * mem::size_of::()) as u32; + println!("get {}", required_size); + + let result = unsafe { + EnumerateTraceGuidsEx( + TraceGuidQueryList, + None, + 0, + Some(guids.as_mut_ptr() as *mut _), + size, + &mut required_size as *mut _, + ) + }; + match result.ok() { + Ok(()) => { + for guid in guids.iter() { + println!("{:?}", guid); + let info = get_provider_info(guid); + let instance_count = + unsafe { *(info.as_ptr() as *const TRACE_GUID_INFO) }.InstanceCount; + let mut instance_ptr: *const TRACE_PROVIDER_INSTANCE_INFO = unsafe { + info.as_ptr().add(mem::size_of::()) + as *const TRACE_PROVIDER_INSTANCE_INFO + }; + + for _ in 0..instance_count { + let instance = unsafe { &*instance_ptr }; + if print_instances { + println!( + "enable_count {}, pid {}, flags {}", + instance.EnableCount, instance.Pid, instance.Flags, + ) + } + instance_ptr = unsafe { + (instance_ptr as *const u8).add(instance.NextOffset as usize) + as *const TRACE_PROVIDER_INSTANCE_INFO + }; + } + } + break; + } + Err(e) => { + if e.code() != ERROR_INSUFFICIENT_BUFFER.to_hresult() { + println!("some other error"); + break; + } + } + } + } +} + +pub fn get_provider_info(guid: &GUID) -> Vec { + let mut required_size: u32 = 0; + + loop { + let mut info: Vec = vec![0; required_size as usize]; + + let size = info.len() as u32; + + let result = unsafe { + EnumerateTraceGuidsEx( + TraceGuidQueryInfo, + Some(guid as *const GUID as *const _), + mem::size_of::() as u32, + Some(info.as_mut_ptr() as *mut _), + size, + &mut required_size as *mut _, + ) + }; + match result.ok() { + Ok(()) => { + return info; + } + Err(e) => { + if e.code() != ERROR_INSUFFICIENT_BUFFER.to_hresult() { + panic!("{:?}", e); + } + } + } + } +} diff --git a/samply/src/windows/etw_reader/parser.rs b/samply/src/windows/etw_reader/parser.rs new file mode 100644 index 00000000..b0a295c5 --- /dev/null +++ b/samply/src/windows/etw_reader/parser.rs @@ -0,0 +1,641 @@ +//! ETW Types Parser +//! +//! This module act as a helper to parse the Buffer from an ETW Event +use std::borrow::Borrow; +use std::convert::TryInto; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +use windows::core::GUID; + +use super::etw_types::EVENT_HEADER_FLAG_32_BIT_HEADER; +use super::property::{PropertyInfo, PropertyIter}; +use super::schema::TypedEvent; +use super::tdh_types::{Property, PropertyDesc, PropertyLength, TdhInType, TdhOutType}; +use super::{tdh, utils}; + +#[derive(Debug, Clone, Copy)] +pub enum Address { + Address64(u64), + Address32(u32), +} + +impl Address { + pub fn as_u64(&self) -> u64 { + match self { + Address::Address64(a) => *a, + Address::Address32(a) => *a as u64, + } + } +} + +/// Parser module errors +#[derive(Debug)] +pub enum ParserError { + /// An invalid type... + InvalidType, + /// Error parsing + ParseError, + /// Length mismatch when parsing a type + LengthMismatch, + PropertyError(String), + /// An error while transforming an Utf-8 buffer into String + Utf8Error(std::string::FromUtf8Error), + /// An error trying to get an slice as an array + SliceError(std::array::TryFromSliceError), + /// Represents an internal [SddlNativeError] + /// + /// [SddlNativeError]: sddl::SddlNativeError + //SddlNativeError(sddl::SddlNativeError), + /// Represents an internal [TdhNativeError] + /// + /// [TdhNativeError]: tdh::TdhNativeError + TdhNativeError(tdh::TdhNativeError), +} + +impl From for ParserError { + fn from(err: tdh::TdhNativeError) -> Self { + ParserError::TdhNativeError(err) + } +} +/* +impl From for ParserError { + fn from(err: sddl::SddlNativeError) -> Self { + ParserError::SddlNativeError(err) + } +}*/ + +impl From for ParserError { + fn from(err: std::string::FromUtf8Error) -> Self { + ParserError::Utf8Error(err) + } +} + +impl From for ParserError { + fn from(err: std::array::TryFromSliceError) -> Self { + ParserError::SliceError(err) + } +} + +type ParserResult = Result; + +/// Trait to try and parse a type +/// +/// This trait has to be implemented in order to be able to parse a type we want to retrieve from +/// within an Event. On success the parsed value will be returned within a Result, on error an Err +/// should be returned accordingly +/// +/// An implementation for most of the Primitive Types is created by using a Macro, any other needed type +/// requires this trait to be implemented +// TODO: Find a way to use turbofish operator +pub trait TryParse { + /// Implement the `try_parse` function to provide a way to Parse `T` from an ETW event or + /// return an Error in case the type `T` can't be parsed + /// + /// # Arguments + /// * `name` - Name of the property to be found in the Schema + fn try_parse(&mut self, name: &str) -> Result; + fn parse(&mut self, name: &str) -> T { + self.try_parse(name) + .unwrap_or_else(|e| panic!("{:?} name {} {:?}", e, std::any::type_name::(), name)) + } +} + +/// Represents a Parser +/// +/// This structure holds the necessary data to parse the ETW event and retrieve the data from the +/// event +#[allow(dead_code)] +pub struct Parser<'a> { + event: &'a TypedEvent<'a>, + properties: &'a PropertyIter, + pub buffer: &'a [u8], + last_property: u32, + offset: usize, + // a map from property indx to PropertyInfo + cache: Vec>, +} + +impl<'a> Parser<'a> { + /// Use the `create` function to create an instance of a Parser + /// + /// # Arguments + /// * `schema` - The [Schema] from the ETW Event we want to parse + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let parser = Parse::create(&schema); + /// }; + /// ``` + pub fn create(event: &'a TypedEvent) -> Self { + Parser { + event, + buffer: event.user_buffer(), + properties: event.schema.properties(), + last_property: 0, + offset: 0, + cache: Vec::new(), // We could fill the cache on creation + } + } + /* + #[allow(dead_code)] + fn fill_cache( + schema: &TypedEvent, + properties: &PropertyIter, + ) -> ParserResult> { + let user_buffer_len = schema.user_buffer().len(); + let mut prop_offset = 0; + panic!(); + Ok(properties.properties_iter().iter().try_fold( + HashMap::new(), + |mut cache, x| -> ParserResult> { + let prop_size = tdh::property_size(schema.record(), &x.name)? as usize; + + if user_buffer_len < prop_size { + return Err(ParserError::PropertyError( + "Property length out of buffer bounds".to_owned(), + )); + } + let prop_buffer = schema.user_buffer()[..prop_size] + .iter() + .take(prop_size) + .cloned() + .collect(); + + cache.insert(x.name.clone(), PropertyInfo::create(x.clone(), prop_offset, prop_buffer)); + prop_offset += prop_size; + + Ok(cache) + }, + )?) + }*/ + + // TODO: Find a cleaner way to do this, not very happy with it rn + fn find_property_size(&self, property: &Property) -> ParserResult { + match property.length { + PropertyLength::Index(_) => { + // e.g. Microsoft-Windows-Kernel-Power/SystemTimerResolutionStackRundown uses the AppNameLength property + // as the size of AppName + + // Fallback to Tdh + Ok(tdh::property_size(self.event.record(), &property.name).unwrap() as usize) + } + PropertyLength::Length(length) => { + // TODO: Study heuristic method used in krabsetw :) + if property.flags.is_empty() && length > 0 && property.count == 1 { + return Ok(length as usize); + } + if property.count == 1 { + if let PropertyDesc::Primitive(desc) = &property.desc { + match desc.in_type { + TdhInType::InTypeBoolean => return Ok(4), + TdhInType::InTypeInt32 + | TdhInType::InTypeUInt32 + | TdhInType::InTypeHexInt32 => return Ok(4), + TdhInType::InTypeInt64 + | TdhInType::InTypeUInt64 + | TdhInType::InTypeHexInt64 => return Ok(8), + TdhInType::InTypeInt8 | TdhInType::InTypeUInt8 => return Ok(1), + TdhInType::InTypeInt16 | TdhInType::InTypeUInt16 => return Ok(2), + TdhInType::InTypePointer => { + return Ok( + if (self.event.event_flags() & EVENT_HEADER_FLAG_32_BIT_HEADER) + != 0 + { + 4 + } else { + 8 + }, + ) + } + TdhInType::InTypeGuid => return Ok(std::mem::size_of::()), + TdhInType::InTypeUnicodeString => { + return Ok(utils::parse_unk_size_null_unicode_size(self.buffer)) + } + TdhInType::InTypeAnsiString => { + return Ok(utils::parse_unk_size_null_ansi_size(self.buffer)); + } + _ => {} + } + } + } + Ok(tdh::property_size(self.event.record(), &property.name).unwrap() as usize) + } + } + } + + pub fn find_property(&mut self, name: &str) -> ParserResult { + let indx = *self + .properties + .name_to_indx + .get(name) + .ok_or_else(|| ParserError::PropertyError(format!("Unknown property: {}", name)))?; + if indx < self.cache.len() { + return Ok(indx); + } + + // TODO: Find a way to do this with an iter, try_find looks promising but is not stable yet + // TODO: Clean this a bit, not a big fan of this loop + for i in self.cache.len()..=indx { + let curr_prop = self.properties.property(i).unwrap(); + + let prop_size = self.find_property_size(curr_prop)?; + + if self.buffer.len() < prop_size { + return Err(ParserError::PropertyError(format!( + "Property of {} bytes out of buffer bounds ({})", + prop_size, + self.buffer.len() + ))); + } + + // We split the buffer, if everything works correctly in the end the buffer will be empty + // and we should have all properties in the cache + let (prop_buffer, remaining) = self.buffer.split_at(prop_size); + self.buffer = remaining; + self.cache + .push(PropertyInfo::create(curr_prop, self.offset, prop_buffer)); + self.offset += prop_size; + } + Ok(indx) + } +} + +/* +impl<'a> std::fmt::Debug for Parser<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut s = f.debug_struct("ParsedEvent"); + for i in 0..self.event.property_count() { + let property = self.event.property(i); + let value = match property.in_type() { + TdhInType::InTypeUnicodeString => format!("{}", TryParse::::parse(self, &property.name)), + TdhInType::InTypeAnsiString => format!("{}", TryParse::::parse(self, &property.name)), + TdhInType::InTypeUInt32 => format!("{}", TryParse::::parse(self, &property.name)), + TdhInType::InTypeUInt8 => format!("{}", TryParse::::parse(self, &property.name)), + TdhInType::InTypePointer => format!("{}", TryParse::::parse(self, &property.name)), + TdhInType::InTypeInt64 => format!("{}", TryParse::::parse(self, &property.name)), + TdhInType::InTypeUInt64 => format!("{}", TryParse::::parse(self, &property.name)), + TdhInType::InTypeGuid => format!("{:?}", TryParse::::parse(self, &property.name)), + _ => panic!() + }; + s.field(&property.name, &value); + //dbg!(&property); + } + s.finish() + } +}*/ + +macro_rules! impl_try_parse_primitive { + ($T:ident, $ty:ident) => { + impl TryParse<$T> for Parser<'_> { + fn try_parse(&mut self, name: &str) -> ParserResult<$T> { + use TdhInType::*; + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + let prop_info: &PropertyInfo = prop_info.borrow(); + if let PropertyDesc::Primitive(desc) = &prop_info.property.desc { + if desc.in_type != $ty { + return Err(ParserError::InvalidType); + } + if std::mem::size_of::<$T>() != prop_info.buffer.len() { + return Err(ParserError::LengthMismatch); + } + return Ok($T::from_ne_bytes(prop_info.buffer.try_into()?)); + }; + Err(ParserError::InvalidType) + } + } + }; +} + +impl_try_parse_primitive!(u8, InTypeUInt8); +impl_try_parse_primitive!(i8, InTypeInt8); +impl_try_parse_primitive!(u16, InTypeUInt16); +impl_try_parse_primitive!(i16, InTypeInt16); +impl_try_parse_primitive!(u32, InTypeUInt32); +//impl_try_parse_primitive!(u64, InTypeUInt64); +//impl_try_parse_primitive!(i64, InTypeInt64); + +impl TryParse for Parser<'_> { + fn try_parse(&mut self, name: &str) -> ParserResult { + use TdhInType::*; + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + if let PropertyDesc::Primitive(desc) = &prop_info.property.desc { + if desc.in_type == InTypeUInt64 { + if std::mem::size_of::() != prop_info.buffer.len() { + return Err(ParserError::LengthMismatch); + } + return Ok(u64::from_ne_bytes(prop_info.buffer.try_into()?)); + } + if desc.in_type == InTypePointer || desc.in_type == InTypeSizeT { + if (self.event.event_flags() & EVENT_HEADER_FLAG_32_BIT_HEADER) != 0 { + if std::mem::size_of::() != prop_info.buffer.len() { + return Err(ParserError::LengthMismatch); + } + return Ok(u32::from_ne_bytes(prop_info.buffer.try_into()?) as u64); + } + if std::mem::size_of::() != prop_info.buffer.len() { + return Err(ParserError::LengthMismatch); + } + return Ok(u64::from_ne_bytes(prop_info.buffer.try_into()?)); + } + } + Err(ParserError::InvalidType) + } +} + +impl TryParse for Parser<'_> { + fn try_parse(&mut self, name: &str) -> ParserResult { + use TdhInType::*; + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + if let PropertyDesc::Primitive(desc) = &prop_info.property.desc { + if desc.in_type == InTypeInt64 || desc.in_type == InTypeHexInt64 { + if std::mem::size_of::() != prop_info.buffer.len() { + return Err(ParserError::LengthMismatch); + } + return Ok(i64::from_ne_bytes(prop_info.buffer.try_into()?)); + } + } + Err(ParserError::InvalidType) + } +} + +impl TryParse for Parser<'_> { + fn try_parse(&mut self, name: &str) -> ParserResult { + use TdhInType::*; + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + if let PropertyDesc::Primitive(desc) = &prop_info.property.desc { + if desc.in_type == InTypeInt32 || desc.in_type == InTypeHexInt32 { + if std::mem::size_of::() != prop_info.buffer.len() { + return Err(ParserError::LengthMismatch); + } + return Ok(i32::from_ne_bytes(prop_info.buffer.try_into()?)); + } + } + Err(ParserError::InvalidType) + } +} + +impl TryParse
for Parser<'_> { + fn try_parse(&mut self, name: &str) -> ParserResult
{ + use TdhInType::*; + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + + if let PropertyDesc::Primitive(desc) = &prop_info.property.desc { + if self.event.is_64bit() { + if desc.in_type == InTypeUInt64 + || desc.in_type == InTypePointer + || desc.in_type == InTypeHexInt64 + { + if std::mem::size_of::() != prop_info.buffer.len() { + return Err(ParserError::LengthMismatch); + } + return Ok(Address::Address64(u64::from_ne_bytes( + prop_info.buffer.try_into()?, + ))); + } + } else if desc.in_type == InTypeUInt32 + || desc.in_type == InTypePointer + || desc.in_type == InTypeHexInt32 + { + if std::mem::size_of::() != prop_info.buffer.len() { + return Err(ParserError::LengthMismatch); + } + return Ok(Address::Address32(u32::from_ne_bytes( + prop_info.buffer.try_into()?, + ))); + } + } + Err(ParserError::InvalidType) + } +} + +impl TryParse for Parser<'_> { + fn try_parse(&mut self, name: &str) -> ParserResult { + use TdhInType::*; + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + if let PropertyDesc::Primitive(desc) = &prop_info.property.desc { + if desc.in_type != InTypeBoolean { + return Err(ParserError::InvalidType); + } + if prop_info.buffer.len() != 4 { + return Err(ParserError::LengthMismatch); + } + return match u32::from_ne_bytes(prop_info.buffer.try_into()?) { + 1 => Ok(true), + 0 => Ok(false), + _ => Err(ParserError::InvalidType), + }; + }; + Err(ParserError::InvalidType) + } +} + +impl TryParse for Parser<'_> { + fn try_parse(&mut self, name: &str) -> ParserResult { + use TdhInType::*; + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + if let PropertyDesc::Primitive(desc) = &prop_info.property.desc { + if desc.in_type == InTypeFloat { + if std::mem::size_of::() != prop_info.buffer.len() { + return Err(ParserError::LengthMismatch); + } + return Ok(f32::from_ne_bytes(prop_info.buffer.try_into()?)); + } + } + Err(ParserError::InvalidType) + } +} + +/// The `String` impl of the `TryParse` trait should be used to retrieve the following [TdhInTypes]: +/// +/// * InTypeUnicodeString +/// * InTypeAnsiString +/// * InTypeCountedString +/// * InTypeGuid +/// +/// On success a `String` with the with the data from the `name` property will be returned +/// +/// # Arguments +/// * `name` - Name of the property to be found in the Schema +/// +/// # Example +/// ```rust +/// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { +/// let schema = schema_locator.event_schema(record)?; +/// let parser = Parse::create(&schema); +/// let image_name: String = parser.try_parse("ImageName")?; +/// }; +/// ``` +/// +/// [TdhInTypes]: TdhInType +impl TryParse for Parser<'_> { + fn try_parse(&mut self, name: &str) -> ParserResult { + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + + // TODO: Handle errors and type checking better + if let PropertyDesc::Primitive(desc) = &prop_info.property.desc { + let res = match desc.in_type { + TdhInType::InTypeUnicodeString => utils::parse_null_utf16_string(prop_info.buffer), + TdhInType::InTypeAnsiString => String::from_utf8(prop_info.buffer.to_vec())? + .trim_matches(char::default()) + .to_string(), + TdhInType::InTypeSid => { + panic!() + //sddl::convert_sid_to_string(prop_info.buffer.as_ptr() as isize)? + } + TdhInType::InTypeCountedString => unimplemented!(), + _ => return Err(ParserError::InvalidType), + }; + return Ok(res); + } + Err(ParserError::InvalidType) + } +} + +impl TryParse for Parser<'_> { + fn try_parse(&mut self, name: &str) -> Result { + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + if let PropertyDesc::Primitive(desc) = &prop_info.property.desc { + match desc.in_type { + TdhInType::InTypeUnicodeString => { + let guid_string = utils::parse_utf16_guid(prop_info.buffer); + + if guid_string.len() != 36 { + return Err(ParserError::LengthMismatch); + } + + return GUID::try_from(guid_string.as_str()).map_err(|_| { + ParserError::PropertyError(format!("Error parsing GUID {guid_string}")) + }); + } + TdhInType::InTypeGuid => { + return Ok(GUID::from_values( + u32::from_ne_bytes((&prop_info.buffer[0..4]).try_into()?), + u16::from_ne_bytes((&prop_info.buffer[4..6]).try_into()?), + u16::from_ne_bytes((&prop_info.buffer[6..8]).try_into()?), + [ + prop_info.buffer[8], + prop_info.buffer[9], + prop_info.buffer[10], + prop_info.buffer[11], + prop_info.buffer[12], + prop_info.buffer[13], + prop_info.buffer[14], + prop_info.buffer[15], + ], + )) + } + _ => return Err(ParserError::InvalidType), + } + }; + Err(ParserError::InvalidType) + } +} + +impl TryParse for Parser<'_> { + fn try_parse(&mut self, name: &str) -> ParserResult { + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + if let PropertyDesc::Primitive(desc) = &prop_info.property.desc { + if desc.out_type != TdhOutType::OutTypeIpv4 && desc.out_type != TdhOutType::OutTypeIpv6 + { + return Err(ParserError::InvalidType); + } + + // Hardcoded values for now + let res = match prop_info.property.length { + PropertyLength::Length(16) => { + let tmp: [u8; 16] = prop_info.buffer.try_into()?; + IpAddr::V6(Ipv6Addr::from(tmp)) + } + PropertyLength::Length(4) => { + let tmp: [u8; 4] = prop_info.buffer.try_into()?; + IpAddr::V4(Ipv4Addr::from(tmp)) + } + _ => return Err(ParserError::LengthMismatch), + }; + + return Ok(res); + } + Err(ParserError::InvalidType) + } +} + +#[derive(Clone, Default, Debug)] +pub struct Pointer(usize); + +impl std::ops::Deref for Pointer { + type Target = usize; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for Pointer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl std::fmt::LowerHex for Pointer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let val = self.0; + + std::fmt::LowerHex::fmt(&val, f) // delegate to u32/u64 implementation + } +} + +impl std::fmt::UpperHex for Pointer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let val = self.0; + + std::fmt::UpperHex::fmt(&val, f) // delegate to u32/u64 implementation + } +} + +impl std::fmt::Display for Pointer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let val = self.0; + + std::fmt::Display::fmt(&val, f) // delegate to u32/u64 implementation + } +} + +impl TryParse for Parser<'_> { + fn try_parse(&mut self, name: &str) -> ParserResult { + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + + let mut res = Pointer::default(); + if prop_info.buffer.len() == std::mem::size_of::() { + res.0 = TryParse::::try_parse(self, name)? as usize; + } else { + res.0 = TryParse::::try_parse(self, name)? as usize; + } + + Ok(res) + } +} + +impl TryParse> for Parser<'_> { + fn try_parse(&mut self, name: &str) -> Result, ParserError> { + let indx = self.find_property(name)?; + let prop_info = &self.cache[indx]; + + Ok(prop_info.buffer.to_vec()) + } +} + +// TODO: Implement SocketAddress +// TODO: Study if we can use primitive types for HexInt64, HexInt32 and Pointer diff --git a/samply/src/windows/etw_reader/property.rs b/samply/src/windows/etw_reader/property.rs new file mode 100644 index 00000000..22b6dca9 --- /dev/null +++ b/samply/src/windows/etw_reader/property.rs @@ -0,0 +1,54 @@ +//! ETW Event Property information +//! +//! The `property` module expose the basic structures that represent the Properties an Event contains +//! based on it's Schema. This Properties can then be used to parse accordingly their values. +use super::schema::Schema; +use super::tdh_types::Property; +use super::FastHashMap; + +/// Event Property information +#[derive(Clone, Debug)] +pub struct PropertyInfo<'a> { + /// Property attributes + pub property: &'a Property, + pub offset: usize, + /// Buffer with the Property data + pub buffer: &'a [u8], +} + +impl<'a> PropertyInfo<'a> { + pub fn create(property: &'a Property, offset: usize, buffer: &'a [u8]) -> Self { + PropertyInfo { + property, + offset, + buffer, + } + } +} + +pub(crate) struct PropertyIter { + properties: Vec, + pub(crate) name_to_indx: FastHashMap, +} + +impl PropertyIter { + pub fn new(schema: &Schema) -> Self { + let prop_count = schema.event_schema.property_count(); + let mut properties = Vec::new(); + let mut name_to_indx = FastHashMap::default(); + for i in 0..prop_count { + let prop = schema.event_schema.property(i); + name_to_indx.insert(prop.name.clone(), i as usize); + properties.push(prop); + } + + PropertyIter { + properties, + name_to_indx, + } + } + + pub fn property(&self, index: usize) -> Option<&Property> { + self.properties.get(index) + } +} diff --git a/samply/src/windows/etw_reader/schema.rs b/samply/src/windows/etw_reader/schema.rs new file mode 100644 index 00000000..5daf47ea --- /dev/null +++ b/samply/src/windows/etw_reader/schema.rs @@ -0,0 +1,477 @@ +//! ETW Event Schema locator and handler +//! +//! This module contains the means needed to locate and interact with the Schema of an ETW event +use std::collections::hash_map::Entry; +use std::rc::Rc; + +use once_cell::unsync::OnceCell; +use windows::core::GUID; +use windows::Win32::System::Diagnostics::Etw::{self, EVENT_HEADER_FLAG_64_BIT_HEADER}; + +use super::etw_types::{DecodingSource, EventRecord, TraceEventInfoRaw}; +use super::property::PropertyIter; +use super::tdh_types::Property; +use super::{tdh, FastHashMap}; + +/// Schema module errors +#[derive(Debug)] +pub enum SchemaError { + /// Represents a Parser error + ParseError, + /// Represents an internal [TdhNativeError] + /// + /// [TdhNativeError]: tdh::TdhNativeError + TdhNativeError(tdh::TdhNativeError), +} + +impl From for SchemaError { + fn from(err: tdh::TdhNativeError) -> Self { + SchemaError::TdhNativeError(err) + } +} + +type SchemaResult = Result; + +// TraceEvent::RegisteredTraceEventParser::ExternalTraceEventParserState::TraceEventComparer +// doesn't compare the version or level and does different things depending on the kind of event +// https://github.com/microsoft/perfview/blob/5c9f6059f54db41b4ac5c4fc8f57261779634489/src/TraceEvent/RegisteredTraceEventParser.cs#L1338 +#[derive(Debug, Eq, PartialEq, Hash)] +struct SchemaKey { + provider: GUID, + id: u16, + version: u8, + level: u8, + opcode: u8, +} + +// A map from tracelogging schema metdata to ids +struct TraceLoggingProviderIds { + ids: FastHashMap, u16>, + next_id: u16, +} + +impl TraceLoggingProviderIds { + fn new() -> Self { + // start the ids at 1 because of 0 is typically the value stored + TraceLoggingProviderIds { + ids: FastHashMap::default(), + next_id: 1, + } + } +} + +impl SchemaKey { + pub fn new(event: &EventRecord, locator: &mut SchemaLocator) -> Self { + // TraceLogging events all use the same id and are distinguished from each other using the metadata. + // Instead of storing the metadata in the SchemaKey we follow the approach of PerfView and have a side table of synthetic ids keyed on metadata. + // It might be better to store the metadata in the SchemaKey but then we may want to be careful not to allocate a fresh metadata for every event. + let mut id = event.EventHeader.EventDescriptor.Id; + if event.ExtendedDataCount > 0 { + let extended = unsafe { + std::slice::from_raw_parts(event.ExtendedData, event.ExtendedDataCount as usize) + }; + for e in extended { + if e.ExtType as u32 == Etw::EVENT_HEADER_EXT_TYPE_EVENT_SCHEMA_TL { + let provider = locator + .tracelogging_providers + .entry(event.EventHeader.ProviderId) + .or_insert(TraceLoggingProviderIds::new()); + let data = unsafe { + std::slice::from_raw_parts(e.DataPtr as *const u8, e.DataSize as usize) + }; + if let Some(metadata_id) = provider.ids.get(data) { + // we want to ensure that our synthetic ids don't overlap with any ids used in the events + assert_ne!(id, *metadata_id); + id = *metadata_id; + } else { + provider.ids.insert(data.to_vec(), provider.next_id); + id = provider.next_id; + provider.next_id += 1; + } + } + } + } + SchemaKey { + provider: event.EventHeader.ProviderId, + id, + version: event.EventHeader.EventDescriptor.Version, + level: event.EventHeader.EventDescriptor.Level, + opcode: event.EventHeader.EventDescriptor.Opcode, + } + } +} + +/// Represents a cache of Schemas already located +/// +/// This cache is implemented as a [HashMap] where the key is a combination of the following elements +/// of an [Event Record](https://docs.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_record) +/// * EventHeader.ProviderId +/// * EventHeader.EventDescriptor.Id +/// * EventHeader.EventDescriptor.Opcode +/// * EventHeader.EventDescriptor.Version +/// * EventHeader.EventDescriptor.Level +/// +/// Credits: [KrabsETW::schema_locator](https://github.com/microsoft/krabsetw/blob/master/krabs/krabs/schema_locator.hpp) +#[derive(Default)] +pub struct SchemaLocator { + schemas: FastHashMap>, + tracelogging_providers: FastHashMap, +} + +pub trait EventSchema { + fn decoding_source(&self) -> DecodingSource; + + fn provider_guid(&self) -> GUID; + fn event_id(&self) -> u16; + fn opcode(&self) -> u8; + fn event_version(&self) -> u8; + fn provider_name(&self) -> String; + fn task_name(&self) -> String; + fn opcode_name(&self) -> String; + fn level(&self) -> u8; + + fn property_count(&self) -> u32; + fn property(&self, index: u32) -> Property; + + fn event_message(&self) -> Option { + None + } + fn is_event_metadata(&self) -> bool { + false + } +} + +impl std::fmt::Debug for SchemaLocator { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + +impl SchemaLocator { + pub fn new() -> Self { + SchemaLocator { + schemas: FastHashMap::default(), + tracelogging_providers: FastHashMap::default(), + } + } + + pub fn add_custom_schema(&mut self, schema: Box) { + let key = SchemaKey { + provider: schema.provider_guid(), + id: schema.event_id(), + opcode: schema.opcode(), + version: schema.event_version(), + level: schema.level(), + }; + self.schemas.insert(key, Rc::new(Schema::new(schema))); + } + + /// Use the `event_schema` function to retrieve the Schema of an ETW Event + /// + /// # Arguments + /// * `event` - The [EventRecord] that's passed to the callback + /// + /// # Remark + /// This is the first function that should be called within a Provider callback, if everything + /// works as expected this function will return a Result with the [Schema] that represents + /// the ETW event that triggered the callback + /// + /// This function can fail, if it does it will return a [SchemaError] + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// }; + /// ``` + pub fn event_schema<'a>(&mut self, event: &'a EventRecord) -> SchemaResult> { + let key = SchemaKey::new(event, self); + let info = match self.schemas.entry(key) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let info = Box::new(tdh::schema_from_tdh(event)?); + // dbg!(info.provider_guid(), info.provider_name(), info.decoding_source()); + // TODO: Cloning for now, should be a reference at some point... + entry.insert(Rc::new(Schema::new(info))) + } + } + .clone(); + + // Some events contain schemas so add them when we find them. + if info.event_schema.is_event_metadata() { + let event_info = TraceEventInfoRaw::new(event.user_buffer().to_owned()); + // println!( + // "Adding custom schema for {}/{}/{}/{}", + // event_info.provider_name(), + // event_info.event_id(), + // event_info.task_name(), + // event_info.opcode_name() + // ); + self.add_custom_schema(Box::new(event_info)); + } + + Ok(TypedEvent::new(event, info)) + } +} + +pub struct Schema { + pub event_schema: Box, + properties: OnceCell, + name: OnceCell, +} + +impl Schema { + fn new(event_schema: Box) -> Self { + Schema { + event_schema, + properties: OnceCell::new(), + name: OnceCell::new(), + } + } + pub(crate) fn properties(&self) -> &PropertyIter { + self.properties.get_or_init(|| PropertyIter::new(self)) + } + pub(crate) fn name(&self) -> &str { + self.name.get_or_init(|| { + format!( + "{}/{}/{}", + self.event_schema.provider_name(), + self.event_schema.task_name(), + self.event_schema.opcode_name() + ) + }) + } +} + +pub struct TypedEvent<'a> { + record: &'a EventRecord, + pub(crate) schema: Rc, +} + +impl<'a> TypedEvent<'a> { + pub fn new(record: &'a EventRecord, schema: Rc) -> Self { + TypedEvent { record, schema } + } + + pub(crate) fn user_buffer(&self) -> &[u8] { + self.record.user_buffer() + } + + // Horrible getters FTW!! :D + // TODO: Not a big fan of this, think a better way.. + pub(crate) fn record(&self) -> &EventRecord { + self.record + } + + /// Use the `event_id` function to obtain the EventId of the Event Record + /// + /// This getter returns the EventId of the ETW Event that triggered the registered callback + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let event_id = schema.event_id(); + /// }; + /// ``` + pub fn event_id(&self) -> u16 { + self.record.EventHeader.EventDescriptor.Id + } + + /// Use the `opcode` function to obtain the Opcode of the Event Record + /// + /// This getter returns the opcode of the ETW Event that triggered the registered callback + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let event_id = schema.opcode(); + /// }; + /// ``` + pub fn opcode(&self) -> u8 { + self.record.EventHeader.EventDescriptor.Opcode + } + + /// Use the `event_flags` function to obtain the Event Flags of the [EventRecord] + /// + /// This getter returns the Event Flags of the ETW Event that triggered the registered callback + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let event_flags = schema.event_flags(); + /// }; + /// ``` + pub fn event_flags(&self) -> u16 { + self.record.EventHeader.Flags + } + + pub fn is_64bit(&self) -> bool { + (self.record.EventHeader.Flags & EVENT_HEADER_FLAG_64_BIT_HEADER as u16) != 0 + } + + /// Use the `event_version` function to obtain the Version of the [EventRecord] + /// + /// This getter returns the Version of the ETW Event that triggered the registered callback + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let event_version = schema.event_version(); + /// }; + /// ``` + pub fn event_version(&self) -> u8 { + self.record.EventHeader.EventDescriptor.Version + } + + /// Use the `process_id` function to obtain the ProcessId of the [EventRecord] + /// + /// This getter returns the ProcessId of the process that triggered the ETW Event + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let pid = schema.process_id(); + /// }; + /// ``` + pub fn process_id(&self) -> u32 { + self.record.EventHeader.ProcessId + } + + /// Use the `thread_id` function to obtain the ThreadId of the [EventRecord] + /// + /// This getter returns the ThreadId of the thread that triggered the ETW Event + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let tid = schema.thread_id(); + /// }; + /// ``` + pub fn thread_id(&self) -> u32 { + self.record.EventHeader.ThreadId + } + + /// Use the `timestamp` function to obtain the TimeStamp of the [EventRecord] + /// + /// This getter returns the TimeStamp of the ETW Event + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let timestamp = schema.timestamp(); + /// }; + /// ``` + pub fn timestamp(&self) -> i64 { + self.record.EventHeader.TimeStamp + } + + /// Use the `activity_id` function to obtain the ActivityId of the [EventRecord] + /// + /// This getter returns the ActivityId from the ETW Event, this value is used to related Two events + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let activity_id = schema.activity_id(); + /// }; + /// ``` + /// [TraceEventInfo]: super::native::etw_types::TraceEventInfo + pub fn activity_id(&self) -> GUID { + self.record.EventHeader.ActivityId + } + + /// Use the `decoding_source` function to obtain the [DecodingSource] from the [TraceEventInfo] + /// + /// This getter returns the DecodingSource from the event, this value identifies the source used + /// parse the event data + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let decoding_source = schema.decoding_source(); + /// }; + /// ``` + /// [TraceEventInfo]: super::native::etw_types::TraceEventInfo + pub fn decoding_source(&self) -> DecodingSource { + self.schema.event_schema.decoding_source() + } + + /// Use the `provider_name` function to obtain the Provider name from the [TraceEventInfo] + /// + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let provider_name = schema.provider_name(); + /// }; + /// ``` + /// [TraceEventInfo]: super::native::etw_types::TraceEventInfo + pub fn provider_name(&self) -> String { + self.schema.event_schema.provider_name() + } + + /// Use the `task_name` function to obtain the Task name from the [TraceEventInfo] + /// + /// See: [TaskType](https://docs.microsoft.com/en-us/windows/win32/wes/eventmanifestschema-tasktype-complextype) + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let task_name = schema.task_name(); + /// }; + /// ``` + /// [TraceEventInfo]: super::native::etw_types::TraceEventInfo + pub fn task_name(&self) -> String { + self.schema.event_schema.task_name() + } + + /// Use the `opcode_name` function to obtain the Opcode name from the [TraceEventInfo] + /// + /// See: [OpcodeType](https://docs.microsoft.com/en-us/windows/win32/wes/eventmanifestschema-opcodetype-complextype) + /// # Example + /// ```rust + /// let my_callback = |record: EventRecord, schema_locator: &mut SchemaLocator| { + /// let schema = schema_locator.event_schema(record)?; + /// let opcode_name = schema.opcode_name(); + /// }; + /// ``` + /// [TraceEventInfo]: super::native::etw_types::TraceEventInfo + pub fn opcode_name(&self) -> String { + self.schema.event_schema.opcode_name() + } + + pub fn property_count(&self) -> u32 { + self.schema.event_schema.property_count() + } + + pub fn property(&self, index: u32) -> Property { + self.schema.event_schema.property(index) + } + + pub fn name(&self) -> &str { + self.schema.name() + } + + pub fn event_message(&self) -> Option { + self.schema.event_schema.event_message() + } +} + +impl PartialEq for TypedEvent<'_> { + fn eq(&self, other: &Self) -> bool { + self.schema.event_schema.event_id() == other.schema.event_schema.event_id() + && self.schema.event_schema.provider_guid() == other.schema.event_schema.provider_guid() + && self.schema.event_schema.event_version() == other.schema.event_schema.event_version() + } +} + +impl Eq for TypedEvent<'_> {} diff --git a/samply/src/windows/etw_reader/sddl.rs b/samply/src/windows/etw_reader/sddl.rs new file mode 100644 index 00000000..48243bec --- /dev/null +++ b/samply/src/windows/etw_reader/sddl.rs @@ -0,0 +1,66 @@ +//use super::traits::*; +use std::str::Utf8Error; + +use windows::core::PSTR; +use windows::Win32::Foundation::{LocalFree, HLOCAL}; +use windows::Win32::Security::{self, PSID}; + +/// SDDL native error +#[derive(Debug)] +pub enum SddlNativeError { + /// Represents an error parsing the SID into a String + SidParseError(Utf8Error), + /// Represents an standard IO Error + IoError(std::io::Error), +} + +//impl LastOsError for SddlNativeError {} + +impl From for SddlNativeError { + fn from(err: std::io::Error) -> Self { + SddlNativeError::IoError(err) + } +} + +impl From for SddlNativeError { + fn from(err: Utf8Error) -> Self { + SddlNativeError::SidParseError(err) + } +} + +pub(crate) type SddlResult = Result; + +pub fn convert_sid_to_string(sid: *const u8) -> SddlResult { + let mut tmp = PSTR::null(); + unsafe { + if Security::Authorization::ConvertSidToStringSidA( + PSID(sid as *const _ as *mut _), + &mut tmp, + ) + .is_err() + { + return Err(SddlNativeError::IoError(std::io::Error::last_os_error())); + } + + let sid_string = std::ffi::CStr::from_ptr(tmp.0 as *mut _) + .to_str()? + .to_owned(); + + let _ = LocalFree(Some(HLOCAL(tmp.0 as *mut _))); + + Ok(sid_string) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_convert_string_to_sid() { + let sid: Vec = vec![1, 2, 0, 0, 0, 0, 0, 5, 0x20, 0, 0, 0, 0x20, 2, 0, 0]; + if let Ok(string_sid) = convert_sid_to_string(sid.as_ptr()) { + assert_eq!(string_sid, "S-1-5-32-544"); + } + } +} diff --git a/samply/src/windows/etw_reader/tdh.rs b/samply/src/windows/etw_reader/tdh.rs new file mode 100644 index 00000000..44ebe379 --- /dev/null +++ b/samply/src/windows/etw_reader/tdh.rs @@ -0,0 +1,134 @@ +use std::ops::Deref; + +use windows::Win32::Foundation::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS}; +use windows::Win32::System::Diagnostics::Etw; +use windows::Win32::System::Diagnostics::Etw::{TdhEnumerateProviders, PROVIDER_ENUMERATION_INFO}; + +use super::etw_types::*; +use super::traits::*; + +#[derive(Debug)] +pub enum TdhNativeError { + /// Represents an standard IO Error + IoError(std::io::Error), +} + +impl From for TdhNativeError { + fn from(err: std::io::Error) -> Self { + TdhNativeError::IoError(err) + } +} + +pub(crate) type TdhNativeResult = Result; + +pub fn schema_from_tdh(event: &Etw::EVENT_RECORD) -> TdhNativeResult { + let mut buffer_size = 0; + unsafe { + if Etw::TdhGetEventInformation(event, None, None, &mut buffer_size) + != ERROR_INSUFFICIENT_BUFFER.0 + { + return Err(TdhNativeError::IoError(std::io::Error::last_os_error())); + } + + let mut buffer = TraceEventInfoRaw::alloc(buffer_size); + if Etw::TdhGetEventInformation( + event, + None, + Some(buffer.info_as_ptr() as *mut _), + &mut buffer_size, + ) != 0 + { + return Err(TdhNativeError::IoError(std::io::Error::last_os_error())); + } + + Ok(buffer) + } +} + +pub(crate) fn property_size(event: &EventRecord, name: &str) -> TdhNativeResult { + let mut property_size = 0; + + let utf16_name = name.to_utf16(); + let desc = Etw::PROPERTY_DATA_DESCRIPTOR { + ArrayIndex: u32::MAX, + PropertyName: utf16_name.as_ptr() as u64, + ..Default::default() + }; + + unsafe { + let status = Etw::TdhGetPropertySize(event.deref(), None, &[desc], &mut property_size); + if status != 0 { + return Err(TdhNativeError::IoError(std::io::Error::from_raw_os_error( + status as i32, + ))); + } + } + + Ok(property_size) +} + +pub fn list_etw_providers() { + let mut buffer_size: u32 = 0; + let mut status: u32; + + // Query required buffer size + unsafe { + status = TdhEnumerateProviders(None, &mut buffer_size); + } + if status == ERROR_INSUFFICIENT_BUFFER.0 { + let mut provider_info = vec![0u8; buffer_size as usize]; + let mut buffer_size_copied = buffer_size; + + // Retrieve provider information + unsafe { + status = TdhEnumerateProviders( + Some(provider_info.as_mut_ptr() as *mut PROVIDER_ENUMERATION_INFO), + &mut buffer_size_copied, + ); + } + if status == ERROR_SUCCESS.0 { + let provider_info = + unsafe { &*(provider_info.as_ptr() as *const PROVIDER_ENUMERATION_INFO) }; + let provider_info_array = provider_info.TraceProviderInfoArray.as_ptr(); + + for i in 0..provider_info.NumberOfProviders { + // windows-rs defines TraceProviderInfoArray as a fixed size array of 1 so we need to use get_unchecked to get the other things + let provider_name_offset = + unsafe { *provider_info_array.offset(i as isize) }.ProviderNameOffset as usize; + let provider_name_ptr = provider_info as *const PROVIDER_ENUMERATION_INFO as usize + + provider_name_offset; + // Find the length of the null-terminated string + let mut len = 0; + while unsafe { *(provider_name_ptr as *const u16).add(len) } != 0 { + len += 1; + } + let provider_name = unsafe { + String::from_utf16(std::slice::from_raw_parts( + provider_name_ptr as *const u16, + len, + )) + .unwrap_or_else(|_| "Error converting to string".to_string()) + }; + + let provider_guid = + &unsafe { *provider_info_array.offset(i as isize) }.ProviderGuid; + let schema_source = unsafe { *provider_info_array.offset(i as isize) }.SchemaSource; + + println!( + " {:?} - {} - {}", + provider_guid, + provider_name, + if schema_source == 0 { + "XML manifest" + } else { + "MOF" + } + ); + } + } else { + println!("TdhEnumerateProviders failed with error code {:?}", status); + } + } else { + println!("TdhEnumerateProviders failed with error code {:?}", status); + } +} diff --git a/samply/src/windows/etw_reader/tdh_types.rs b/samply/src/windows/etw_reader/tdh_types.rs new file mode 100644 index 00000000..45e84165 --- /dev/null +++ b/samply/src/windows/etw_reader/tdh_types.rs @@ -0,0 +1,219 @@ +//! Basic TDH types +//! +//! The `tdh_type` module provides an abstraction over the basic TDH types, this module act as a +//! helper for the parser to determine which IN and OUT type are expected from a property within an +//! event +//! +//! This is a bit extra but is basically a redefinition of the In an Out TDH types following the +//! rust naming convention, it can also come in handy when implementing the [TryParse] trait for a type +//! to determine how to handle a [Property] based on this values +//! +//! [TryParse]: super::parser::TryParse +//! [Property]: super::native::tdh_types::Property +use std::rc::Rc; + +use bitflags::bitflags; +use num_derive::{FromPrimitive, ToPrimitive}; +use num_traits::FromPrimitive; +use windows::Win32::System::Diagnostics::Etw; + +use super::etw_types::EventPropertyInfo; + +#[derive(Debug, Clone, Default)] +pub struct PropertyMapInfo { + pub is_bitmap: bool, + pub map: super::FastHashMap, +} +#[derive(Debug, Clone)] +pub struct PrimitiveDesc { + pub in_type: TdhInType, + pub out_type: TdhOutType, +} + +#[derive(Debug, Clone, Default)] +pub struct StructDesc { + pub start_index: u16, + pub num_members: u16, +} + +#[derive(Debug, Clone)] +pub enum PropertyDesc { + Primitive(PrimitiveDesc), + Struct(StructDesc), +} + +/// Notes if the property length is a concrete length or an index to another property +/// which contains the length. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum PropertyLength { + Length(u16), + Index(u16), +} + +/// Attributes of a property +#[derive(Debug, Clone)] +pub struct Property { + /// Name of the Property + pub name: String, + /// Represent the [PropertyFlags] + pub flags: PropertyFlags, + pub length: PropertyLength, + pub desc: PropertyDesc, + pub map_info: Option>, + pub count: u16, +} + +#[doc(hidden)] +impl Property { + pub fn new( + name: String, + property: &EventPropertyInfo, + map_info: Option>, + ) -> Self { + let flags = PropertyFlags::from(property.Flags); + let length = if flags.contains(PropertyFlags::PROPERTY_PARAM_LENGTH) { + // The property length is stored in another property, this is the index of that property + PropertyLength::Index(unsafe { property.Anonymous3.lengthPropertyIndex }) + } else { + // The property has no param for its length, it makes sense to access this field of the union + PropertyLength::Length(unsafe { property.Anonymous3.length }) + }; + if property.Flags.0 & Etw::PropertyStruct.0 != 0 { + unsafe { + let start_index = property.Anonymous1.structType.StructStartIndex; + let num_members = property.Anonymous1.structType.NumOfStructMembers; + Property { + name, + flags: PropertyFlags::from(property.Flags), + length, + desc: PropertyDesc::Struct(StructDesc { + start_index, + num_members, + }), + map_info, + count: property.Anonymous2.count, + } + } + } else { + unsafe { + let out_type = FromPrimitive::from_u16(property.Anonymous1.nonStructType.OutType) + .unwrap_or(TdhOutType::OutTypeNull); + let in_type = FromPrimitive::from_u16(property.Anonymous1.nonStructType.InType) + .unwrap_or_else(|| panic!("{:?}", property.Anonymous1.nonStructType.InType)); + + Property { + name, + flags: PropertyFlags::from(property.Flags), + length, + desc: PropertyDesc::Primitive(PrimitiveDesc { in_type, out_type }), + map_info, + count: property.Anonymous2.count, + } + } + } + } +} + +/// Represent a TDH_IN_TYPE +#[repr(u16)] +#[derive(Debug, Clone, Copy, FromPrimitive, ToPrimitive, PartialEq)] +pub enum TdhInType { + // Deprecated values are not defined + InTypeNull, + InTypeUnicodeString, + InTypeAnsiString, + InTypeInt8, // Field size is 1 byte + InTypeUInt8, // Field size is 1 byte + InTypeInt16, // Field size is 2 bytes + InTypeUInt16, // Field size is 2 bytes + InTypeInt32, // Field size is 4 bytes + InTypeUInt32, // Field size is 4 bytes + InTypeInt64, // Field size is 8 bytes + InTypeUInt64, // Field size is 8 bytes + InTypeFloat, // Field size is 4 bytes + InTypeDouble, // Field size is 8 bytes + InTypeBoolean, // Field size is 4 bytes + InTypeBinary, // Depends on the OutType + InTypeGuid, + InTypePointer, + InTypeFileTime, // Field size is 8 bytes + InTypeSystemTime, // Field size is 16 bytes + InTypeSid, // Field size determined by the first few bytes of the field + InTypeHexInt32, + InTypeHexInt64, + InTypeCountedString = 300, + InTypeCountedAnsiString, + InTypeReverseCountedString, + InTypeReverseCountedAnsiString, + InTypeNonNullTerminatedString, + InTypeNonNullTerminatedAnsiString, + InTypeUnicodeChar, + InTypeAnsiChar, + InTypeSizeT, + InTypeHexdump, + InTypeWBEMSID, +} + +/// Represent a TDH_OUT_TYPE +#[repr(u16)] +#[derive(Debug, Clone, Copy, FromPrimitive, ToPrimitive, PartialEq, Default)] +pub enum TdhOutType { + #[default] + OutTypeNull, + OutTypeString, + OutTypeDateTime, + OutTypeInt8, // Field size is 1 byte + OutTypeUInt8, // Field size is 1 byte + OutTypeInt16, // Field size is 2 bytes + OutTypeUInt16, // Field size is 2 bytes + OutTypeInt32, // Field size is 4 bytes + OutTypeUInt32, // Field size is 4 bytes + OutTypeInt64, // Field size is 8 bytes + OutTypeUInt64, // Field size is 8 bytes + OutTypeFloat, // Field size is 4 bytes + OutTypeDouble, // Field size is 8 bytes + OutTypeBoolean, // Field size is 4 bytes + OutTypeGuid, + OutTypeHexBinary, + OutTypeHexInt8, + OutTypeHexInt16, + OutTypeHexInt32, + OutTypeHexInt64, + OutTypePid, + OutTypeTid, + OutTypePort, + OutTypeIpv4, + OutTypeIpv6, + OutTypeWin32Error = 30, + OutTypeNtStatus = 31, + OutTypeHResult = 32, + OutTypeJson = 34, + OutTypeUtf8 = 35, + OutTypePkcs7 = 36, + OutTypeCodePointer = 37, + OutTypeDatetimeUtc = 38, +} + +bitflags! { + /// Represents the Property flags + /// + /// See: [Property Flags enum](https://docs.microsoft.com/en-us/windows/win32/api/tdh/ne-tdh-property_flags) + #[derive(Default, Debug, Clone)] + pub struct PropertyFlags: u32 { + const PROPERTY_STRUCT = 0x1; + const PROPERTY_PARAM_LENGTH = 0x2; + const PROPERTY_PARAM_COUNT = 0x4; + const PROPERTY_WBEMXML_FRAGMENT = 0x8; + const PROPERTY_PARAM_FIXED_LENGTH = 0x10; + const PROPERTY_PARAM_FIXED_COUNT = 0x20; + const PROPERTY_HAS_TAGS = 0x40; + const PROPERTY_HAS_CUSTOM_SCHEMA = 0x80; + } +} + +impl From for PropertyFlags { + fn from(flags: Etw::PROPERTY_FLAGS) -> Self { + // Should be a safe cast + PropertyFlags::from_bits_truncate(flags.0 as u32) + } +} diff --git a/samply/src/windows/etw_reader/traits.rs b/samply/src/windows/etw_reader/traits.rs new file mode 100644 index 00000000..946ba9ab --- /dev/null +++ b/samply/src/windows/etw_reader/traits.rs @@ -0,0 +1,19 @@ +use std::iter; + +pub trait EncodeUtf16 { + fn to_utf16(self) -> Vec; +} + +impl EncodeUtf16 for &str { + fn to_utf16(self) -> Vec { + self.encode_utf16() // Make a UTF-16 iterator + .chain(iter::once(0)) // Append a null + .collect() // Collect the iterator into a vector + } +} + +impl EncodeUtf16 for String { + fn to_utf16(self) -> Vec { + self.as_str().to_utf16() + } +} diff --git a/samply/src/windows/etw_reader/utils.rs b/samply/src/windows/etw_reader/utils.rs new file mode 100644 index 00000000..be5c3164 --- /dev/null +++ b/samply/src/windows/etw_reader/utils.rs @@ -0,0 +1,87 @@ +fn is_aligned(ptr: *const T) -> bool +where + T: Sized, +{ + ptr as usize & (std::mem::align_of::() - 1) == 0 +} + +pub fn parse_unk_size_null_utf16_string(v: &[u8]) -> String { + // Instead of doing the following unsafe stuff ourselves we could + // use cast_slice from bytemuck. Unfortunately, it won't work if + // the length of the u8 is not a multiple of 2 vs. just truncating. + // Alternatively, using safe_transmute::transmute_many_permisive should work. + + let start: *const u16 = v.as_ptr().cast(); + if !is_aligned(start) { + panic!("Not aligned"); + } + + // safe because we not going past the end of the slice + let end: *const u16 = unsafe { v.as_ptr().add(v.len()) }.cast(); + + // find the null termination + let mut len = 0; + let mut ptr = start; + while unsafe { *ptr } != 0 && ptr < end { + len += 1; + ptr = unsafe { ptr.offset(1) }; + } + + let slice = unsafe { std::slice::from_raw_parts(start, len) }; + String::from_utf16_lossy(slice) +} + +pub fn parse_unk_size_null_unicode_size(v: &[u8]) -> usize { + // TODO: Make sure is aligned + v.chunks_exact(2) + .take_while(|&a| a != [0, 0]) // Take until null terminator + .count() + * 2 + + 2 +} + +pub fn parse_unk_size_null_unicode_vec(v: &[u8]) -> Vec { + // TODO: Make sure is aligned + v.chunks_exact(2) + .take_while(|&a| a != [0, 0]) // Take until null terminator + .map(|a| u16::from_ne_bytes([a[0], a[1]])) + .collect::>() +} + +pub fn parse_unk_size_null_ansi_size(v: &[u8]) -> usize { + v.iter() + .take_while(|&&a| a != 0) // Take until null terminator + .count() + + 1 +} + +pub fn parse_unk_size_null_ansi_vec(v: &[u8]) -> Vec { + v.iter() + .take_while(|&&a| a != 0) + .copied() // Take until null terminator + .collect::>() +} + +pub fn parse_null_utf16_string(v: &[u8]) -> String { + String::from_utf16_lossy( + v.chunks_exact(2) + .map(|a| u16::from_ne_bytes([a[0], a[1]])) + .collect::>() + .as_slice(), + ) + .trim_matches(char::default()) + .to_string() +} + +pub fn parse_utf16_guid(v: &[u8]) -> String { + String::from_utf16_lossy( + v.chunks_exact(2) + .map(|a| u16::from_ne_bytes([a[0], a[1]])) + .collect::>() + .as_slice(), + ) + .trim_matches(char::default()) + .trim_matches('{') + .trim_matches('}') + .to_string() +} diff --git a/samply/src/windows/mod.rs b/samply/src/windows/mod.rs index 5cdc7477..ced7c0b1 100644 --- a/samply/src/windows/mod.rs +++ b/samply/src/windows/mod.rs @@ -2,6 +2,8 @@ mod chrome; mod coreclr; mod elevated_helper; mod etw_gecko; +#[allow(dead_code)] +mod etw_reader; mod firefox; mod gfx; pub mod import;