use std::ffi::OsString;
use std::marker::PhantomData;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use widestring::U16CString;
use windows::core::GUID;
use windows::Win32::System::Diagnostics::Etw;
use self::private::{PrivateRealTimeTraceTrait, PrivateTraceTrait};
use crate::native::etw_types::{EventTraceProperties, SubscriptionSource};
use crate::native::evntrace::{
close_trace, control_trace, control_trace_by_name, enable_provider, open_trace, process_trace,
start_trace, ControlHandle, TraceHandle,
};
use crate::native::version_helper;
use crate::provider::Provider;
use crate::utils;
use crate::EventRecord;
use crate::SchemaLocator;
pub use crate::native::etw_types::DumpFileLoggingMode;
pub use crate::native::etw_types::LoggingMode;
pub(crate) mod callback_data;
use callback_data::CallbackData;
use callback_data::CallbackDataFromFile;
use callback_data::RealTimeCallbackData;
const KERNEL_LOGGER_NAME: &str = "NT Kernel Logger";
const SYSTEM_TRACE_CONTROL_GUID: &str = "9e814aad-3204-11d2-9a82-006008a86939";
const EVENT_TRACE_SYSTEM_LOGGER_MODE: u32 = 0x02000000;
#[derive(Debug)]
pub enum TraceError {
InvalidTraceName,
EtwNativeError(crate::native::EvntraceNativeError),
}
impl From<crate::native::EvntraceNativeError> for TraceError {
fn from(err: crate::native::EvntraceNativeError) -> Self {
TraceError::EtwNativeError(err)
}
}
type TraceResult<T> = Result<T, TraceError>;
#[derive(Debug, Copy, Clone)]
pub struct TraceProperties {
pub buffer_size: u32,
pub min_buffer: u32,
pub max_buffer: u32,
pub flush_timer: Duration,
pub log_file_mode: LoggingMode,
}
impl Default for TraceProperties {
fn default() -> Self {
TraceProperties {
buffer_size: 32,
min_buffer: 0,
max_buffer: 0,
flush_timer: Duration::from_secs(1),
log_file_mode: LoggingMode::EVENT_TRACE_REAL_TIME_MODE
| LoggingMode::EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING,
}
}
}
pub trait TraceTrait: private::PrivateTraceTrait + Sized {
fn trace_handle(&self) -> TraceHandle;
fn events_handled(&self) -> usize;
fn process(&mut self) -> TraceResult<()> {
process_trace(self.trace_handle()).map_err(|e| e.into())
}
fn process_from_handle(handle: TraceHandle) -> TraceResult<()> {
process_trace(handle).map_err(|e| e.into())
}
fn stop(mut self) -> TraceResult<()> {
self.non_consuming_stop()
}
}
pub trait RealTimeTraceTrait: TraceTrait + private::PrivateRealTimeTraceTrait {
fn trace_guid() -> GUID;
fn trace_name(&self) -> OsString;
}
impl TraceTrait for UserTrace {
fn trace_handle(&self) -> TraceHandle {
self.trace_handle
}
fn events_handled(&self) -> usize {
self.callback_data.events_handled()
}
}
impl RealTimeTraceTrait for UserTrace {
fn trace_guid() -> GUID {
GUID::new().unwrap_or(GUID::zeroed())
}
fn trace_name(&self) -> OsString {
self.properties.name()
}
}
impl TraceTrait for KernelTrace {
fn trace_handle(&self) -> TraceHandle {
self.trace_handle
}
fn events_handled(&self) -> usize {
self.callback_data.events_handled()
}
}
impl RealTimeTraceTrait for KernelTrace {
fn trace_guid() -> GUID {
if version_helper::is_win8_or_greater() {
GUID::new().unwrap_or(GUID::zeroed())
} else {
GUID::from(SYSTEM_TRACE_CONTROL_GUID)
}
}
fn trace_name(&self) -> OsString {
self.properties.name()
}
}
impl TraceTrait for FileTrace {
fn trace_handle(&self) -> TraceHandle {
self.trace_handle
}
fn events_handled(&self) -> usize {
self.callback_data.events_handled()
}
}
#[derive(Debug)]
#[allow(clippy::redundant_allocation)] pub struct UserTrace {
properties: EventTraceProperties,
control_handle: ControlHandle,
trace_handle: TraceHandle,
callback_data: Box<Arc<CallbackData>>,
}
#[derive(Debug)]
#[allow(clippy::redundant_allocation)] pub struct KernelTrace {
properties: EventTraceProperties,
control_handle: ControlHandle,
trace_handle: TraceHandle,
callback_data: Box<Arc<CallbackData>>,
}
#[derive(Debug)]
#[allow(clippy::redundant_allocation)] pub struct FileTrace {
trace_handle: TraceHandle,
callback_data: Box<Arc<CallbackData>>,
}
#[derive(Clone, Default)]
pub struct DumpFileParams {
pub file_path: PathBuf,
pub file_logging_mode: DumpFileLoggingMode,
pub max_size: Option<u32>,
}
pub struct TraceBuilder<T: RealTimeTraceTrait> {
name: String,
etl_dump_file: Option<DumpFileParams>,
properties: TraceProperties,
rt_callback_data: RealTimeCallbackData,
trace_kind: PhantomData<T>,
}
pub struct FileTraceBuilder {
etl_file_path: PathBuf,
callback: crate::EtwCallback,
}
impl UserTrace {
pub fn new() -> TraceBuilder<UserTrace> {
let name = format!("n4r1b-trace-{}", utils::rand_string());
TraceBuilder {
name,
etl_dump_file: None,
rt_callback_data: RealTimeCallbackData::new(),
properties: TraceProperties::default(),
trace_kind: PhantomData,
}
}
pub fn stop(mut self) -> TraceResult<()> {
self.non_consuming_stop()
}
}
impl KernelTrace {
pub fn new() -> TraceBuilder<KernelTrace> {
let builder = TraceBuilder {
name: String::new(),
etl_dump_file: None,
rt_callback_data: RealTimeCallbackData::new(),
properties: TraceProperties::default(),
trace_kind: PhantomData,
};
builder.named(format!("n4r1b-trace-{}", utils::rand_string()))
}
pub fn stop(mut self) -> TraceResult<()> {
self.non_consuming_stop()
}
}
mod private {
use super::*;
#[derive(Debug, PartialEq, Eq)]
pub enum TraceKind {
User,
Kernel,
}
pub trait PrivateRealTimeTraceTrait: PrivateTraceTrait {
const TRACE_KIND: TraceKind;
#[allow(clippy::redundant_allocation)] fn build(
properties: EventTraceProperties,
control_handle: ControlHandle,
trace_handle: TraceHandle,
callback_data: Box<Arc<CallbackData>>,
) -> Self;
fn augmented_file_mode() -> u32;
fn enable_flags(_providers: &[Provider]) -> u32;
}
pub trait PrivateTraceTrait {
fn non_consuming_stop(&mut self) -> TraceResult<()>;
}
}
impl private::PrivateRealTimeTraceTrait for UserTrace {
const TRACE_KIND: private::TraceKind = private::TraceKind::User;
fn build(
properties: EventTraceProperties,
control_handle: ControlHandle,
trace_handle: TraceHandle,
callback_data: Box<Arc<CallbackData>>,
) -> Self {
UserTrace {
properties,
control_handle,
trace_handle,
callback_data,
}
}
fn augmented_file_mode() -> u32 {
0
}
fn enable_flags(_providers: &[Provider]) -> u32 {
0
}
}
impl private::PrivateTraceTrait for UserTrace {
fn non_consuming_stop(&mut self) -> TraceResult<()> {
close_trace(self.trace_handle, &self.callback_data)?;
control_trace(
&mut self.properties,
self.control_handle,
Etw::EVENT_TRACE_CONTROL_STOP,
)?;
Ok(())
}
}
impl private::PrivateRealTimeTraceTrait for KernelTrace {
const TRACE_KIND: private::TraceKind = private::TraceKind::Kernel;
fn build(
properties: EventTraceProperties,
control_handle: ControlHandle,
trace_handle: TraceHandle,
callback_data: Box<Arc<CallbackData>>,
) -> Self {
KernelTrace {
properties,
control_handle,
trace_handle,
callback_data,
}
}
fn augmented_file_mode() -> u32 {
if version_helper::is_win8_or_greater() {
EVENT_TRACE_SYSTEM_LOGGER_MODE
} else {
0
}
}
fn enable_flags(providers: &[Provider]) -> u32 {
providers.iter().fold(0, |acc, x| acc | x.kernel_flags())
}
}
impl private::PrivateTraceTrait for KernelTrace {
fn non_consuming_stop(&mut self) -> TraceResult<()> {
close_trace(self.trace_handle, &self.callback_data)?;
control_trace(
&mut self.properties,
self.control_handle,
Etw::EVENT_TRACE_CONTROL_STOP,
)?;
Ok(())
}
}
impl private::PrivateTraceTrait for FileTrace {
fn non_consuming_stop(&mut self) -> TraceResult<()> {
close_trace(self.trace_handle, &self.callback_data)?;
Ok(())
}
}
impl<T: RealTimeTraceTrait + PrivateRealTimeTraceTrait> TraceBuilder<T> {
pub fn named(mut self, name: String) -> Self {
if T::TRACE_KIND == private::TraceKind::Kernel && !version_helper::is_win8_or_greater() {
self.name = String::from(KERNEL_LOGGER_NAME);
} else {
self.name = name;
};
self
}
pub fn set_trace_properties(mut self, props: TraceProperties) -> Self {
self.properties = props;
self
}
pub fn set_etl_dump_file(mut self, params: DumpFileParams) -> Self {
self.etl_dump_file = Some(params);
self
}
pub fn enable(mut self, provider: Provider) -> Self {
self.rt_callback_data.add_provider(provider);
self
}
pub fn start(self) -> TraceResult<(T, TraceHandle)> {
let trace_wide_name = U16CString::from_str_truncate(self.name);
let mut trace_wide_vec = trace_wide_name.into_vec();
trace_wide_vec.truncate(crate::native::etw_types::TRACE_NAME_MAX_CHARS);
let trace_wide_name = U16CString::from_vec_truncate(trace_wide_vec);
let wide_etl_dump_file = match self.etl_dump_file {
None => None,
Some(DumpFileParams {
file_path,
file_logging_mode,
max_size,
}) => {
let wide_path = U16CString::from_os_str_truncate(file_path.as_os_str());
let mut wide_path_vec = wide_path.into_vec();
wide_path_vec.truncate(crate::native::etw_types::TRACE_NAME_MAX_CHARS);
Some((
U16CString::from_vec_truncate(wide_path_vec),
file_logging_mode,
max_size,
))
}
};
let flags = self.rt_callback_data.provider_flags::<T>();
let (full_properties, control_handle) = start_trace::<T>(
&trace_wide_name,
wide_etl_dump_file
.as_ref()
.map(|(path, params, max_size)| (path.as_ucstr(), *params, *max_size)),
&self.properties,
flags,
)?;
if T::TRACE_KIND == private::TraceKind::User {
for prov in self.rt_callback_data.providers() {
enable_provider(control_handle, prov)?;
}
}
let callback_data = Box::new(Arc::new(CallbackData::RealTime(self.rt_callback_data)));
let trace_handle = open_trace(
SubscriptionSource::RealTimeSession(trace_wide_name),
&callback_data,
)?;
Ok((
T::build(full_properties, control_handle, trace_handle, callback_data),
trace_handle,
))
}
pub fn start_and_process(self) -> TraceResult<T> {
let (trace, trace_handle) = self.start()?;
std::thread::spawn(move || UserTrace::process_from_handle(trace_handle));
Ok(trace)
}
}
impl FileTrace {
#[allow(clippy::new_ret_no_self)]
pub fn new<T>(path: PathBuf, callback: T) -> FileTraceBuilder
where
T: FnMut(&EventRecord, &SchemaLocator) + Send + Sync + 'static,
{
FileTraceBuilder {
etl_file_path: path,
callback: Box::new(callback),
}
}
fn non_consuming_stop(&mut self) -> TraceResult<()> {
close_trace(self.trace_handle, &self.callback_data)?;
Ok(())
}
}
impl FileTraceBuilder {
pub fn start(self) -> TraceResult<(FileTrace, TraceHandle)> {
let wide_etl_file_path = U16CString::from_os_str_truncate(self.etl_file_path.as_os_str());
let from_file_cb = CallbackDataFromFile::new(self.callback);
let callback_data = Box::new(Arc::new(CallbackData::FromFile(from_file_cb)));
let trace_handle = open_trace(
SubscriptionSource::FromFile(wide_etl_file_path),
&callback_data,
)?;
Ok((
FileTrace {
trace_handle,
callback_data,
},
trace_handle,
))
}
pub fn start_and_process(self) -> TraceResult<FileTrace> {
let (trace, trace_handle) = self.start()?;
std::thread::spawn(move || FileTrace::process_from_handle(trace_handle));
Ok(trace)
}
}
impl Drop for UserTrace {
fn drop(&mut self) {
let _ignored_error_in_drop = self.non_consuming_stop();
}
}
impl Drop for KernelTrace {
fn drop(&mut self) {
let _ignored_error_in_drop = self.non_consuming_stop();
}
}
impl Drop for FileTrace {
fn drop(&mut self) {
let _ignored_error_in_drop = self.non_consuming_stop();
}
}
pub fn stop_trace_by_name(trace_name: &str) -> TraceResult<()> {
let trace_properties = TraceProperties::default();
let flags = Etw::EVENT_TRACE_FLAG::default();
let wide_name = U16CString::from_str(trace_name).map_err(|_| TraceError::InvalidTraceName)?;
let mut properties = EventTraceProperties::new::<UserTrace>(
&wide_name,
None, &trace_properties,
flags,
);
control_trace_by_name(&mut properties, &wide_name, Etw::EVENT_TRACE_CONTROL_STOP)?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_enable_multiple_providers() {
let prov = Provider::by_guid("22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716").build();
let prov1 = Provider::by_guid("A0C1853B-5C40-4B15-8766-3CF1C58F985A").build();
let trace_builder = UserTrace::new().enable(prov).enable(prov1);
assert_eq!(trace_builder.rt_callback_data.providers().len(), 2);
}
}