Skip to content

Commit 1f99f56

Browse files
committed
[hyperlight-guest-tracing] Add crates that generate guest tracing records
- `hyperlight-guest-tracing` defines a `TraceBuffer` that keeps `TraceRecord`s that the guest issues. When the buffer capacity is reached, it automatically issues an `Out` instruction with the corresponding info for the host to retrieve the buffer. - The guest can issue `TraceRecord`s by using the `hyperlight-guest-tracing-macro` crate that pushes new records to the buffer. - `hyperlight_common` contains the definitions for the frame types a guest can send to the host using the `Out` instruction. Signed-off-by: Doru Blânzeanu <dblnz@pm.me>
1 parent f95ccbe commit 1f99f56

File tree

8 files changed

+365
-2
lines changed

8 files changed

+365
-2
lines changed

Cargo.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ members = [
1010
"src/hyperlight_guest",
1111
"src/hyperlight_host",
1212
"src/hyperlight_guest_capi",
13+
"src/hyperlight_guest_tracing",
14+
"src/hyperlight_guest_tracing_macro",
1315
"src/hyperlight_testing",
1416
"fuzz",
1517
"src/hyperlight_guest_bin",
@@ -40,6 +42,8 @@ hyperlight-host = { path = "src/hyperlight_host", version = "0.7.0", default-fea
4042
hyperlight-guest = { path = "src/hyperlight_guest", version = "0.7.0", default-features = false }
4143
hyperlight-guest-bin = { path = "src/hyperlight_guest_bin", version = "0.7.0", default-features = false }
4244
hyperlight-testing = { path = "src/hyperlight_testing", default-features = false }
45+
hyperlight-guest-tracing = { path = "src/hyperlight_guest_tracing", default-features = false }
46+
hyperlight-guest-tracing-macro = { path = "src/hyperlight_guest_tracing_macro", default-features = false }
4347
hyperlight-component-util = { path = "src/hyperlight_component_util", version = "0.7.0", default-features = false }
4448
hyperlight-component-macro = { path = "src/hyperlight_component_macro", version = "0.7.0", default-features = false }
4549

src/hyperlight_common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ spin = "0.10.0"
2525
[features]
2626
default = ["tracing"]
2727
fuzzing = ["dep:arbitrary"]
28+
trace_guest = []
2829
unwind_guest = []
2930
mem_profile = []
3031
std = []

src/hyperlight_common/src/outb.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ impl TryFrom<u8> for Exception {
9393
/// - TraceRecordStack: records the stack trace of the guest
9494
/// - TraceMemoryAlloc: records memory allocation events
9595
/// - TraceMemoryFree: records memory deallocation events
96+
/// - TraceRecord: records a trace event in the guest
9697
pub enum OutBAction {
9798
Log = 99,
9899
CallFunction = 101,
@@ -101,9 +102,11 @@ pub enum OutBAction {
101102
#[cfg(feature = "unwind_guest")]
102103
TraceRecordStack = 104,
103104
#[cfg(feature = "mem_profile")]
104-
TraceMemoryAlloc,
105+
TraceMemoryAlloc = 105,
105106
#[cfg(feature = "mem_profile")]
106-
TraceMemoryFree,
107+
TraceMemoryFree = 106,
108+
#[cfg(feature = "trace_guest")]
109+
TraceRecord = 107,
107110
}
108111

109112
impl TryFrom<u16> for OutBAction {
@@ -120,6 +123,8 @@ impl TryFrom<u16> for OutBAction {
120123
105 => Ok(OutBAction::TraceMemoryAlloc),
121124
#[cfg(feature = "mem_profile")]
122125
106 => Ok(OutBAction::TraceMemoryFree),
126+
#[cfg(feature = "trace_guest")]
127+
107 => Ok(OutBAction::TraceRecord),
123128
_ => Err(anyhow::anyhow!("Invalid OutBAction value: {}", val)),
124129
}
125130
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "hyperlight-guest-tracing"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
license.workspace = true
7+
homepage.workspace = true
8+
repository.workspace = true
9+
readme.workspace = true
10+
description = """Provides the tracing functionality for the hyperlight guest."""
11+
12+
[dependencies]
13+
hyperlight-common = { workspace = true, default-features = false, features = ["trace_guest"] }
14+
spin = "0.10.0"
15+
16+
[lints]
17+
workspace = true
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
Copyright 2025 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
#![no_std]
17+
18+
// === Dependencies ===
19+
extern crate alloc;
20+
21+
use core::mem::MaybeUninit;
22+
23+
use hyperlight_common::outb::OutBAction;
24+
use spin::Mutex;
25+
26+
/// Global trace buffer for storing trace records.
27+
static TRACE_BUFFER: Mutex<TraceBuffer> = Mutex::new(TraceBuffer::new());
28+
29+
/// Maximum number of entries in the trace buffer.
30+
const MAX_NO_OF_ENTRIES: usize = 16;
31+
32+
/// Maximum length of a trace message in bytes.
33+
pub const MAX_TRACE_MSG_LEN: usize = 64;
34+
35+
#[derive(Debug, Copy, Clone)]
36+
/// Represents a trace record of a guest with a number of cycles and a message.
37+
pub struct TraceRecord {
38+
/// The number of CPU cycles returned by the invariant TSC.
39+
pub cycles: u64,
40+
/// The length of the message in bytes.
41+
pub msg_len: usize,
42+
/// The message associated with the trace record.
43+
pub msg: [u8; MAX_TRACE_MSG_LEN],
44+
}
45+
46+
/// A buffer for storing trace records.
47+
struct TraceBuffer {
48+
/// The entries in the trace buffer.
49+
entries: [TraceRecord; MAX_NO_OF_ENTRIES],
50+
/// The index where the next entry will be written.
51+
write_index: usize,
52+
}
53+
54+
impl TraceBuffer {
55+
/// Creates a new `TraceBuffer` with uninitialized entries.
56+
const fn new() -> Self {
57+
Self {
58+
entries: unsafe { [MaybeUninit::zeroed().assume_init(); MAX_NO_OF_ENTRIES] },
59+
write_index: 0,
60+
}
61+
}
62+
63+
/// Push a new trace record into the buffer.
64+
/// If the buffer is full, it sends the records to the host.
65+
fn push(&mut self, entry: TraceRecord) {
66+
let mut write_index = self.write_index;
67+
68+
self.entries[write_index] = entry;
69+
write_index = (write_index + 1) % MAX_NO_OF_ENTRIES;
70+
71+
self.write_index = write_index;
72+
73+
if write_index == 0 {
74+
// If buffer is full send to host
75+
self.send_to_host(MAX_NO_OF_ENTRIES);
76+
}
77+
}
78+
79+
/// Flush the trace buffer, sending any remaining records to the host.
80+
fn flush(&mut self) {
81+
if self.write_index > 0 {
82+
self.send_to_host(self.write_index);
83+
self.write_index = 0; // Reset write index after flushing
84+
}
85+
}
86+
87+
/// Send the trace records to the host.
88+
fn send_to_host(&self, count: usize) {
89+
unsafe {
90+
core::arch::asm!("out dx, al",
91+
in("dx") OutBAction::TraceRecord as u16,
92+
in("rax") count as u64,
93+
in("rcx") &self.entries as * const _ as u64);
94+
}
95+
}
96+
}
97+
98+
/// Module for checking invariant TSC support and reading the timestamp counter
99+
pub mod invariant_tsc {
100+
use core::arch::x86_64::{__cpuid, _rdtsc};
101+
102+
/// Check if the processor supports invariant TSC
103+
///
104+
/// Returns true if CPUID.80000007H:EDX[8] is set, indicating invariant TSC support
105+
pub fn has_invariant_tsc() -> bool {
106+
// Check if extended CPUID functions are available
107+
let max_extended = unsafe { __cpuid(0x80000000) };
108+
if max_extended.eax < 0x80000007 {
109+
return false;
110+
}
111+
112+
// Query CPUID.80000007H for invariant TSC support
113+
let cpuid_result = unsafe { __cpuid(0x80000007) };
114+
115+
// Check bit 8 of EDX register for invariant TSC support
116+
(cpuid_result.edx & (1 << 8)) != 0
117+
}
118+
119+
/// Read the timestamp counter
120+
///
121+
/// This function provides a high-performance timestamp by reading the TSC.
122+
/// Should only be used when invariant TSC is supported for reliable timing.
123+
///
124+
/// # Safety
125+
/// This function uses unsafe assembly instructions but is safe to call.
126+
/// However, the resulting timestamp is only meaningful if invariant TSC is supported.
127+
pub fn read_tsc() -> u64 {
128+
unsafe { _rdtsc() }
129+
}
130+
}
131+
132+
/// Create a trace record with the given message.
133+
///
134+
/// Note: The message must not exceed `MAX_TRACE_MSG_LEN` bytes.
135+
/// If the message is too long, it will be skipped.
136+
pub fn create_trace_record(msg: &str) {
137+
if msg.len() > MAX_TRACE_MSG_LEN {
138+
return; // Message too long, skip tracing
139+
}
140+
141+
let cycles = invariant_tsc::read_tsc();
142+
143+
let entry = TraceRecord {
144+
cycles,
145+
msg: {
146+
let mut arr = [0u8; MAX_TRACE_MSG_LEN];
147+
arr[..msg.len()].copy_from_slice(msg.as_bytes());
148+
arr
149+
},
150+
msg_len: msg.len(),
151+
};
152+
153+
let mut buffer = TRACE_BUFFER.lock();
154+
buffer.push(entry);
155+
}
156+
157+
/// Flush the trace buffer to send any remaining trace records to the host.
158+
pub fn flush_trace_buffer() {
159+
let mut buffer = TRACE_BUFFER.lock();
160+
buffer.flush();
161+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "hyperlight-guest-tracing-macro"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
license.workspace = true
7+
homepage.workspace = true
8+
repository.workspace = true
9+
readme.workspace = true
10+
description = """Provides the tracing macros for the hyperlight guest, enabling structured logging and tracing capabilities."""
11+
12+
[dependencies]
13+
proc-macro2 = "1.0"
14+
quote = "1.0.40"
15+
syn = { version = "2.0.104", features = ["full"] }
16+
17+
[features]
18+
default = []
19+
20+
[lib]
21+
proc-macro = true
22+
23+
[lints]
24+
workspace = true

0 commit comments

Comments
 (0)