Skip to content

Commit a82c99a

Browse files
authored
Fix Schema cache for manifest-free events using EventName (n4r1b#120)
* Add event_name to Schema to differentia events in manifest-free providers * Add Keyword to event_record * Add test for TraceLogging with multiple events * Remove unnecessary TODO comment * Disable flaky tlg test
1 parent b7ba232 commit a82c99a

File tree

6 files changed

+245
-10
lines changed

6 files changed

+245
-10
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ log = "0.4"
4747
env_logger = "0.11" # used in examples
4848
serde_json = "1.0"
4949
flexbuffers = "2.0"
50+
tracelogging = "1.2"

src/native/etw_types/event_record.rs

+24
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use windows::core::GUID;
44
use windows::Win32::System::Diagnostics::Etw::EVENT_RECORD;
55

66
use crate::native::etw_types::extended_data::EventHeaderExtendedDataItem;
7+
use crate::native::ExtendedDataItem;
78

89
use super::EVENT_HEADER_FLAG_32_BIT_HEADER;
910

@@ -65,6 +66,11 @@ impl EventRecord {
6566
self.0.EventHeader.EventDescriptor.Level
6667
}
6768

69+
/// The `Keyword` field from the wrapped `EVENT_RECORD`
70+
pub fn keyword(&self) -> u64 {
71+
self.0.EventHeader.EventDescriptor.Keyword
72+
}
73+
6874
/// The `Flags` field from the wrapped `EVENT_RECORD`
6975
pub fn event_flags(&self) -> u16 {
7076
self.0.EventHeader.Flags
@@ -154,4 +160,22 @@ impl EventRecord {
154160
)
155161
}
156162
}
163+
164+
/// Returns the `eventName` for manifest-free events
165+
pub fn event_name(&self) -> String {
166+
if self.event_id() != 0 {
167+
return String::new();
168+
}
169+
170+
if let Some(ExtendedDataItem::TraceLogging(name)) = self
171+
.extended_data()
172+
.iter()
173+
.find(|ext_data| ext_data.is_tlg())
174+
.map(|ext_data| ext_data.to_extended_data_item())
175+
{
176+
name
177+
} else {
178+
String::new()
179+
}
180+
}
157181
}

src/native/etw_types/extended_data.rs

+89-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
//! A module to handle Extended Data from ETW traces
22
3+
use std::{ffi::CStr, mem};
34
use windows::core::GUID;
45
use windows::Win32::Security::SID;
56
use windows::Win32::System::Diagnostics::Etw::{
67
EVENT_EXTENDED_ITEM_RELATED_ACTIVITYID, EVENT_EXTENDED_ITEM_TS_ID,
78
};
89
use windows::Win32::System::Diagnostics::Etw::{
910
EVENT_HEADER_EXTENDED_DATA_ITEM, EVENT_HEADER_EXT_TYPE_EVENT_KEY,
10-
EVENT_HEADER_EXT_TYPE_INSTANCE_INFO, EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY,
11-
EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID, EVENT_HEADER_EXT_TYPE_SID,
12-
EVENT_HEADER_EXT_TYPE_STACK_TRACE32, EVENT_HEADER_EXT_TYPE_STACK_TRACE64,
13-
EVENT_HEADER_EXT_TYPE_TS_ID,
11+
EVENT_HEADER_EXT_TYPE_EVENT_SCHEMA_TL, EVENT_HEADER_EXT_TYPE_INSTANCE_INFO,
12+
EVENT_HEADER_EXT_TYPE_PROCESS_START_KEY, EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID,
13+
EVENT_HEADER_EXT_TYPE_SID, EVENT_HEADER_EXT_TYPE_STACK_TRACE32,
14+
EVENT_HEADER_EXT_TYPE_STACK_TRACE64, EVENT_HEADER_EXT_TYPE_TS_ID,
1415
};
1516

1617
// These types are returned by our public API. Let's use their re-exported versions
@@ -28,7 +29,7 @@ pub struct EventHeaderExtendedDataItem(EVENT_HEADER_EXTENDED_DATA_ITEM);
2829
/// See <https://docs.microsoft.com/en-us/windows/win32/api/relogger/ns-relogger-event_header_extended_data_item>
2930
#[derive(Debug)]
3031
pub enum ExtendedDataItem {
31-
/// Unexpected or invalid (or not implemented yet in Ferrisetw) extended data type
32+
/// Unexpected, invalid or not implemented yet
3233
Unsupported,
3334
/// Related activity identifier
3435
RelatedActivityId(GUID),
@@ -41,9 +42,8 @@ pub enum ExtendedDataItem {
4142
StackTrace32(EVENT_EXTENDED_ITEM_STACK_TRACE32),
4243
/// Call stack (if the event is captured on a 64-bit computer)
4344
StackTrace64(EVENT_EXTENDED_ITEM_STACK_TRACE64),
44-
// TODO: implement them, but the documentation does not clearly define what they are supposed to contain
45-
// /// TraceLogging event metadata information
46-
// SchemaTl,
45+
/// TraceLogging event metadata information
46+
TraceLogging(String),
4747
// /// Provider traits data
4848
// /// (for example traits set through EventSetInformation(EventProviderSetTraits) or specified through EVENT_DATA_DESCRIPTOR_TYPE_PROVIDER_METADATA)
4949
// ProvTraits,
@@ -61,7 +61,12 @@ impl EventHeaderExtendedDataItem {
6161
self.0.ExtType
6262
}
6363

64+
pub fn is_tlg(&self) -> bool {
65+
self.0.ExtType as u32 == EVENT_HEADER_EXT_TYPE_EVENT_SCHEMA_TL
66+
}
67+
6468
/// Returns this extended data as a variant of a Rust enum.
69+
// TODO: revisit this function
6570
pub fn to_extended_data_item(&self) -> ExtendedDataItem {
6671
let data_ptr = self.0.DataPtr as *const std::ffi::c_void;
6772
if data_ptr.is_null() {
@@ -109,7 +114,83 @@ impl EventHeaderExtendedDataItem {
109114
ExtendedDataItem::EventKey(unsafe { *data_ptr })
110115
}
111116

117+
EVENT_HEADER_EXT_TYPE_EVENT_SCHEMA_TL => {
118+
ExtendedDataItem::TraceLogging(unsafe { self.get_event_name().unwrap_or_default() })
119+
}
120+
112121
_ => ExtendedDataItem::Unsupported,
113122
}
114123
}
124+
125+
///
126+
/// This function will parse the `_tlgEventMetadata_t` to retrieve the EventName
127+
///
128+
/// For more info see `_tlgEventMetadata_t` in `TraceLoggingProvider.h` (Windows SDK)
129+
///
130+
/// ```cpp
131+
/// struct _tlgEventMetadata_t
132+
/// {
133+
/// UINT8 Type; // = _TlgBlobEvent4
134+
/// UCHAR Channel;
135+
/// UCHAR Level;
136+
/// UCHAR Opcode;
137+
/// ULONGLONG Keyword;
138+
/// UINT16 RemainingSize; // = sizeof(RemainingSize + Tags + EventName + Fields)
139+
/// UINT8 Tags[]; // 1 or more bytes. Read until you hit a byte with high bit unset.
140+
/// char EventName[sizeof("eventName")]; // UTF-8 nul-terminated event name
141+
/// for each field {
142+
/// char FieldName[sizeof("fieldName")];
143+
/// UINT8 InType;
144+
/// UINT8 OutType;
145+
/// UINT8 Tags[];
146+
/// UINT16 ValueCount;
147+
/// UINT16 TypeInfoSize;
148+
/// char TypeInfo[TypeInfoSize];
149+
/// }
150+
/// }
151+
/// ```
152+
///
153+
/// We are only interested on `EventName` so we will only consider the first three members.
154+
///
155+
/// # Safety
156+
///
157+
/// As per the MS header 'This structure may change in future revisions of this header.'
158+
/// **Keep an eye on it!**
159+
///
160+
// TODO: Make this function more robust
161+
unsafe fn get_event_name(&self) -> Option<String> {
162+
const TAGS_SIZE: usize = 1;
163+
debug_assert!(self.is_tlg());
164+
165+
let mut data_ptr = self.0.DataPtr as *const u8;
166+
if data_ptr.is_null() {
167+
return None;
168+
}
169+
170+
let size = data_ptr.read_unaligned() as u16;
171+
data_ptr = data_ptr.add(mem::size_of::<u16>());
172+
173+
let mut n = 0;
174+
while n < size {
175+
// Read until you hit a byte with high bit unset.
176+
let tag = data_ptr.read_unaligned();
177+
data_ptr = data_ptr.add(TAGS_SIZE);
178+
179+
if tag & 0b1000_0000 == 0 {
180+
break;
181+
}
182+
183+
n += 1;
184+
}
185+
186+
// If debug let's assert here since this is a case we want to investigate
187+
debug_assert!(n != size);
188+
if n == size {
189+
return None;
190+
}
191+
192+
Some(String::from(
193+
CStr::from_ptr(data_ptr as *const _).to_string_lossy(),
194+
))
195+
}
115196
}

src/schema_locator.rs

+8
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ struct SchemaKey {
5050
// see https://github.com/microsoft/krabsetw/issues/195
5151
opcode: u8,
5252
level: u8,
53+
//
54+
// From MS documentation `evntprov.h`
55+
// For manifest-free events (i.e. TraceLogging), Event.Id and Event.Version are not useful
56+
// and should be ignored. Use Event name, level, keyword, and opcode for event filtering and
57+
// identification.
58+
//
59+
event_name: String,
5360
}
5461

5562
impl SchemaKey {
@@ -60,6 +67,7 @@ impl SchemaKey {
6067
opcode: event.opcode(),
6168
version: event.version(),
6269
level: event.level(),
70+
event_name: event.event_name(),
6371
}
6472
}
6573
}

src/trace/callback_data.rs

-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ use crate::schema_locator::SchemaLocator;
99
use crate::trace::RealTimeTraceTrait;
1010
use crate::EtwCallback;
1111

12-
pub use crate::native::etw_types::LoggingMode;
13-
1412
/// Data used by callbacks when the trace is running
1513
// NOTE: this structure is accessed in an unsafe block in a separate thread (see the `trace_callback_thunk` function)
1614
// Thus, this struct must not be mutated (outside of interior mutability and/or using Mutex and other synchronization mechanisms) when the associated trace is running.

tests/tlg.rs

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use tracelogging as tlg;
2+
3+
use ferrisetw::parser::Parser;
4+
use ferrisetw::provider::Provider;
5+
use ferrisetw::schema_locator::SchemaLocator;
6+
use ferrisetw::trace::TraceTrait;
7+
use ferrisetw::trace::UserTrace;
8+
use ferrisetw::EventRecord;
9+
10+
mod utils;
11+
use utils::{Status, TestKind};
12+
13+
const EVENT1_COUNT: u32 = 1;
14+
const EVENT2_COUNT: u32 = 5;
15+
const TEST_STRING_VALUE: &'static str = "TestString";
16+
const PROVIDER_NAME: &'static str = "ferrisETW.TraceLoggingTest";
17+
18+
tlg::define_provider!(FERRIS_PROVIDER, "ferrisETW.TraceLoggingTest");
19+
20+
#[ignore]
21+
#[test]
22+
fn tlg_tests() {
23+
unsafe {
24+
FERRIS_PROVIDER.register();
25+
}
26+
27+
let binding = tlg::Guid::from_name(PROVIDER_NAME).to_utf8_bytes();
28+
let guid = std::str::from_utf8(&binding).unwrap();
29+
30+
tlg_multiple_events(guid);
31+
32+
FERRIS_PROVIDER.unregister();
33+
}
34+
35+
fn generate_tlg_events() {
36+
for _i in 0..EVENT1_COUNT {
37+
tlg::write_event!(
38+
FERRIS_PROVIDER,
39+
"Event1",
40+
level(Warning),
41+
keyword(0x13),
42+
str8("String", TEST_STRING_VALUE),
43+
);
44+
}
45+
46+
for i in 0..EVENT2_COUNT {
47+
tlg::write_event!(
48+
FERRIS_PROVIDER,
49+
"Event2",
50+
level(Informational),
51+
keyword(0x6),
52+
u32("Integer", &i),
53+
);
54+
}
55+
}
56+
57+
fn tlg_multiple_events(provider_guid: &str) {
58+
let passed = Status::new(TestKind::ExpectSuccess);
59+
let notifier = passed.notifier();
60+
61+
let mut event1_count = 0;
62+
let mut event2_count = 0;
63+
64+
let tlg_provider = Provider::by_guid(provider_guid)
65+
.add_callback(
66+
move |record: &EventRecord, schema_locator: &SchemaLocator| {
67+
let schema = schema_locator.event_schema(record).unwrap();
68+
let parser = Parser::create(record, &schema);
69+
70+
// Test event_name function is working as expected & we can handle multiple
71+
// different events.
72+
if record.event_name() == "Event1" {
73+
println!(
74+
"Received Event1({}) from ferrisETW.TraceLoggingTest",
75+
event1_count
76+
);
77+
78+
assert_eq!(record.level(), tlg::Level::Warning.as_int());
79+
assert_eq!(record.keyword(), 0x13);
80+
81+
// Tracelogging crate sets OutTypeUtf8 for str8 which we don't handle at the
82+
// moment.
83+
let _data = parser.try_parse::<String>("String");
84+
// assert!(data.is_ok());
85+
// assert_eq!(data, TEST_STRING_VALUE);
86+
87+
event1_count = event1_count + 1;
88+
} else if record.event_name() == "Event2" {
89+
println!(
90+
"Received Event2({}) from ferrisETW.TraceLoggingTest",
91+
event2_count
92+
);
93+
94+
assert_eq!(record.level(), tlg::Level::Informational.as_int());
95+
assert_eq!(record.keyword(), 0x6);
96+
97+
let data = parser.try_parse::<u32>("Integer");
98+
99+
assert!(data.is_ok());
100+
assert_eq!(data.unwrap(), event2_count);
101+
102+
event2_count = event2_count + 1;
103+
}
104+
105+
if event1_count == EVENT1_COUNT && event2_count == EVENT2_COUNT {
106+
notifier.notify_success();
107+
}
108+
},
109+
)
110+
.build();
111+
112+
let tlg_trace = UserTrace::new()
113+
.enable(tlg_provider)
114+
.start_and_process()
115+
.unwrap();
116+
117+
generate_tlg_events();
118+
119+
passed.assert_passed();
120+
assert!(tlg_trace.events_handled() > 0);
121+
tlg_trace.stop().unwrap();
122+
println!("tlg_multiple_events passed");
123+
}

0 commit comments

Comments
 (0)