Skip to content

Commit 39e744b

Browse files
committed
[hyperlight_host/trace] Handle the trace records sent by a guest
- When an `Out` instruction is intercepted, the Hypervisor checks for the frame Id, to verify what type of exit it is. Based on this, when a trace record type is received, we copy the array of trace records from the guest's memory, calculate the timestamp based on the cycles returned by the guest's RDTSC and write the record to the trace file. Signed-off-by: Doru Blânzeanu <dblnz@pm.me>
1 parent 9073af7 commit 39e744b

File tree

7 files changed

+156
-12
lines changed

7 files changed

+156
-12
lines changed

src/hyperlight_host/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ tracing = { version = "0.1.41", features = ["log"] }
3939
tracing-log = "0.2.0"
4040
tracing-core = "0.1.34"
4141
hyperlight-common = { workspace = true, default-features = true, features = [ "std" ] }
42+
hyperlight-guest-tracing = { workspace = true, default-features = true, optional = true }
4243
vmm-sys-util = "0.14.0"
4344
crossbeam-channel = "0.5.15"
4445
thiserror = "2.0.12"
@@ -131,7 +132,7 @@ executable_heap = []
131132
print_debug = []
132133
# Dumps the VM state to a file on unexpected errors or crashes. The path of the file will be printed on stdout and logged.
133134
crashdump = ["dep:chrono"]
134-
trace_guest = []
135+
trace_guest = ["hyperlight-common/trace_guest", "dep:hyperlight-guest-tracing"]
135136
# This feature enables unwinding the guest stack from the host, in
136137
# order to produce stack traces for debugging or profiling.
137138
unwind_guest = [ "trace_guest", "dep:framehop", "dep:fallible-iterator", "hyperlight-common/unwind_guest" ]

src/hyperlight_host/src/hypervisor/hyperv_linux.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ use mshv_bindings::{
4848
hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES,
4949
hv_partition_synthetic_processor_features,
5050
};
51-
#[cfg(feature = "unwind_guest")]
51+
#[cfg(feature = "trace_guest")]
5252
use mshv_bindings::{
5353
hv_register_name, hv_register_name_HV_X64_REGISTER_RAX, hv_register_name_HV_X64_REGISTER_RBP,
5454
hv_register_name_HV_X64_REGISTER_RCX, hv_register_name_HV_X64_REGISTER_RSP,
@@ -58,7 +58,7 @@ use tracing::{Span, instrument};
5858
#[cfg(crashdump)]
5959
use {super::crashdump, std::path::Path};
6060

61-
#[cfg(feature = "unwind_guest")]
61+
#[cfg(feature = "trace_guest")]
6262
use super::TraceRegister;
6363
use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT};
6464
#[cfg(gdb)]
@@ -530,7 +530,7 @@ impl Debug for HypervLinuxDriver {
530530
}
531531
}
532532

533-
#[cfg(feature = "unwind_guest")]
533+
#[cfg(feature = "trace_guest")]
534534
impl From<TraceRegister> for hv_register_name {
535535
fn from(r: TraceRegister) -> Self {
536536
match r {
@@ -1055,7 +1055,7 @@ impl Hypervisor for HypervLinuxDriver {
10551055
Ok(())
10561056
}
10571057

1058-
#[cfg(feature = "unwind_guest")]
1058+
#[cfg(feature = "trace_guest")]
10591059
fn read_trace_reg(&self, reg: TraceRegister) -> Result<u64> {
10601060
let mut assoc = [hv_register_assoc {
10611061
name: reg.into(),
@@ -1070,6 +1070,10 @@ impl Hypervisor for HypervLinuxDriver {
10701070
fn trace_info_as_ref(&self) -> &TraceInfo {
10711071
&self.trace_info
10721072
}
1073+
#[cfg(feature = "trace_guest")]
1074+
fn trace_info_as_mut(&mut self) -> &mut TraceInfo {
1075+
&mut self.trace_info
1076+
}
10731077
}
10741078

10751079
impl Drop for HypervLinuxDriver {

src/hyperlight_host/src/hypervisor/hyperv_windows.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use {
4141
std::sync::Mutex,
4242
};
4343

44-
#[cfg(feature = "unwind_guest")]
44+
#[cfg(feature = "trace_guest")]
4545
use super::TraceRegister;
4646
use super::fpu::{FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT};
4747
use super::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper};
@@ -1022,7 +1022,7 @@ impl Hypervisor for HypervWindowsDriver {
10221022
Ok(())
10231023
}
10241024

1025-
#[cfg(feature = "unwind_guest")]
1025+
#[cfg(feature = "trace_guest")]
10261026
fn read_trace_reg(&self, reg: TraceRegister) -> Result<u64> {
10271027
let regs = self.processor.get_regs()?;
10281028
match reg {
@@ -1038,6 +1038,10 @@ impl Hypervisor for HypervWindowsDriver {
10381038
fn trace_info_as_ref(&self) -> &TraceInfo {
10391039
&self.trace_info
10401040
}
1041+
#[cfg(feature = "trace_guest")]
1042+
fn trace_info_as_mut(&mut self) -> &mut TraceInfo {
1043+
&mut self.trace_info
1044+
}
10411045
}
10421046

10431047
impl Drop for HypervWindowsDriver {

src/hyperlight_host/src/hypervisor/kvm.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use tracing::{Span, instrument};
2929
#[cfg(crashdump)]
3030
use {super::crashdump, std::path::Path};
3131

32-
#[cfg(feature = "unwind_guest")]
32+
#[cfg(feature = "trace_guest")]
3333
use super::TraceRegister;
3434
use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT};
3535
#[cfg(gdb)]
@@ -612,6 +612,13 @@ impl Hypervisor for KVMDriver {
612612
{
613613
Err(kvm_ioctls::Error::new(libc::EINTR))
614614
} else {
615+
#[cfg(feature = "trace_guest")]
616+
if self.trace_info.guest_start_epoch.is_none() {
617+
// Set the guest start epoch to the current time, before running the vcpu
618+
crate::debug!("KVM - Guest Start Epoch set");
619+
self.trace_info.guest_start_epoch = Some(std::time::Instant::now());
620+
}
621+
615622
// Note: if a `InterruptHandle::kill()` called while this thread is **here**
616623
// Then the vcpu will run, but we will keep sending signals to this thread
617624
// to interrupt it until `running` is set to false. The `vcpu_fd::run()` call will
@@ -934,7 +941,7 @@ impl Hypervisor for KVMDriver {
934941
Ok(())
935942
}
936943

937-
#[cfg(feature = "unwind_guest")]
944+
#[cfg(feature = "trace_guest")]
938945
fn read_trace_reg(&self, reg: TraceRegister) -> Result<u64> {
939946
let regs = self.vcpu_fd.get_regs()?;
940947
Ok(match reg {
@@ -950,6 +957,10 @@ impl Hypervisor for KVMDriver {
950957
fn trace_info_as_ref(&self) -> &TraceInfo {
951958
&self.trace_info
952959
}
960+
#[cfg(feature = "trace_guest")]
961+
fn trace_info_as_mut(&mut self) -> &mut TraceInfo {
962+
&mut self.trace_info
963+
}
953964
}
954965

955966
impl Drop for KVMDriver {

src/hyperlight_host/src/hypervisor/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,15 @@ pub(crate) trait Hypervisor: Debug + Sync + Send {
261261
}
262262

263263
/// Read a register for trace/unwind purposes
264-
#[cfg(feature = "unwind_guest")]
264+
#[cfg(feature = "trace_guest")]
265265
fn read_trace_reg(&self, reg: TraceRegister) -> Result<u64>;
266266

267267
/// Get a reference of the trace info for the guest
268268
#[cfg(feature = "trace_guest")]
269269
fn trace_info_as_ref(&self) -> &TraceInfo;
270+
/// Get a mutable reference of the trace info for the guest
271+
#[cfg(feature = "trace_guest")]
272+
fn trace_info_as_mut(&mut self) -> &mut TraceInfo;
270273
}
271274

272275
/// A virtual CPU that can be run until an exit occurs

src/hyperlight_host/src/sandbox/mod.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,17 @@ pub(crate) struct TraceInfo {
102102
/// early as the creation of the sandbox being traced.
103103
#[allow(dead_code)]
104104
pub epoch: std::time::Instant,
105+
/// The frequency of the timestamp counter.
106+
#[allow(dead_code)]
107+
pub tsc_freq: u64,
108+
/// The epoch at which the guest started, if it has started.
109+
/// This is used to calculate the time spent in the guest relative to the
110+
/// time of the host.
111+
#[allow(dead_code)]
112+
pub guest_start_epoch: Option<std::time::Instant>,
113+
/// The start guest time, in TSC cycles, for the current guest.
114+
#[allow(dead_code)]
115+
pub guest_start_tsc: Option<u64>,
105116
/// The file to which the trace is being written
106117
#[allow(dead_code)]
107118
pub file: Arc<Mutex<std::fs::File>>,
@@ -136,8 +147,13 @@ impl TraceInfo {
136147
let cache = framehop::x86_64::CacheX86_64::new();
137148
(unwinder, Arc::new(Mutex::new(cache)))
138149
};
150+
let tsc_freq = Self::calculate_tsc_freq()?;
151+
139152
let ret = Self {
140153
epoch: std::time::Instant::now(),
154+
tsc_freq,
155+
guest_start_epoch: None,
156+
guest_start_tsc: None,
141157
file: Arc::new(Mutex::new(std::fs::File::create_new(path)?)),
142158
#[cfg(feature = "unwind_guest")]
143159
unwind_module,
@@ -153,6 +169,24 @@ impl TraceInfo {
153169
})?;
154170
Ok(ret)
155171
}
172+
173+
/// Calculate the TSC frequency based on the RDTSC instruction.
174+
fn calculate_tsc_freq() -> crate::Result<u64> {
175+
if !hyperlight_guest_tracing::invariant_tsc::has_invariant_tsc() {
176+
return Err(crate::new_error!(
177+
"Invariant TSC is not supported on this platform"
178+
));
179+
}
180+
let start = hyperlight_guest_tracing::invariant_tsc::read_tsc();
181+
let start_time = std::time::Instant::now();
182+
// Sleep for 1 second to get a good sample
183+
std::thread::sleep(std::time::Duration::from_secs(1));
184+
let end = hyperlight_guest_tracing::invariant_tsc::read_tsc();
185+
let end_time = std::time::Instant::now();
186+
let elapsed = end_time.duration_since(start_time).as_secs_f64();
187+
188+
Ok(((end - start) as f64 / elapsed) as u64)
189+
}
156190
}
157191

158192
pub(crate) trait WrapperGetter {

src/hyperlight_host/src/sandbox/outb.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
#[cfg(feature = "unwind_guest")]
17+
#[cfg(feature = "trace_guest")]
1818
use std::io::Write;
1919
use std::sync::{Arc, Mutex};
2020

@@ -26,14 +26,16 @@ use hyperlight_common::flatbuffer_wrappers::function_types::ParameterValue;
2626
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
2727
use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData;
2828
use hyperlight_common::outb::{Exception, OutBAction};
29+
#[cfg(feature = "trace_guest")]
30+
use hyperlight_guest_tracing::TraceRecord;
2931
use log::{Level, Record};
3032
use tracing::{Span, instrument};
3133
use tracing_log::format_trace;
3234

3335
use super::host_funcs::FunctionRegistry;
3436
use super::mem_mgr::MemMgrWrapper;
3537
use crate::hypervisor::handlers::{OutBHandler, OutBHandlerFunction, OutBHandlerWrapper};
36-
#[cfg(feature = "unwind_guest")]
38+
#[cfg(feature = "trace_guest")]
3739
use crate::mem::layout::SandboxMemoryLayout;
3840
use crate::mem::mgr::SandboxMemoryManager;
3941
use crate::mem::shared_mem::HostSharedMemory;
@@ -211,6 +213,44 @@ pub(super) fn record_trace_frame<F: FnOnce(&mut std::fs::File)>(
211213
Ok(())
212214
}
213215

216+
#[cfg(feature = "trace_guest")]
217+
pub(super) fn record_guest_trace_frame<F: FnOnce(&mut std::fs::File)>(
218+
trace_info: &TraceInfo,
219+
frame_id: u64,
220+
cycles: u64,
221+
write_frame: F,
222+
) -> Result<()> {
223+
let Ok(mut out) = trace_info.file.lock() else {
224+
return Ok(());
225+
};
226+
// frame structure:
227+
// 16 bytes timestamp
228+
// calculate time relative to the host epoch
229+
230+
let cycles_spent = cycles
231+
- trace_info
232+
.guest_start_tsc
233+
.as_ref()
234+
.map_or_else(|| 0, |c| *c);
235+
let micros = cycles_spent as f64 / trace_info.tsc_freq as f64 * 1_000_000f64;
236+
let guest_duration = std::time::Duration::from_micros(micros as u64);
237+
238+
let guest_start_time = trace_info
239+
.guest_start_epoch
240+
.as_ref()
241+
.unwrap_or(&trace_info.epoch)
242+
.saturating_duration_since(trace_info.epoch)
243+
.checked_add(guest_duration)
244+
.unwrap_or(guest_duration);
245+
246+
let _ = out.write_all(&guest_start_time.as_micros().to_ne_bytes());
247+
// 8 bytes frame type id
248+
let _ = out.write_all(&frame_id.to_ne_bytes());
249+
// frame data
250+
write_frame(&mut out);
251+
Ok(())
252+
}
253+
214254
/// Handles OutB operations from the guest.
215255
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
216256
fn handle_outb_impl(
@@ -287,6 +327,53 @@ fn handle_outb_impl(
287327
write_stack(f, &stack);
288328
})
289329
}
330+
#[cfg(feature = "trace_guest")]
331+
OutBAction::TraceRecord => {
332+
let Ok(len) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RAX) else {
333+
return Ok(());
334+
};
335+
let Ok(ptr) = _hv.read_trace_reg(crate::hypervisor::TraceRegister::RCX) else {
336+
return Ok(());
337+
};
338+
let mut buffer = vec![0u8; len as usize * std::mem::size_of::<TraceRecord>()];
339+
let buffer = &mut buffer[..];
340+
341+
// Read the trace records from the guest memory
342+
// TODO: maybe this can be done without copying?
343+
mem_mgr
344+
.as_ref()
345+
.shared_mem
346+
.copy_to_slice(buffer, ptr as usize - SandboxMemoryLayout::BASE_ADDRESS)
347+
// .read::<u64>((addr - SandboxMemoryLayout::BASE_ADDRESS as u64) as usize)
348+
.map_err(|e| {
349+
new_error!(
350+
"Failed to copy trace records from guest memory to host: {:?}",
351+
e
352+
)
353+
})?;
354+
355+
let traces = unsafe {
356+
std::slice::from_raw_parts(buffer.as_ptr() as *const TraceRecord, len as usize)
357+
};
358+
359+
{
360+
let trace_info = _hv.trace_info_as_mut();
361+
// Store the start guest cycles if not already set
362+
// This is the `entrypoint` of the guest execution
363+
if trace_info.guest_start_tsc.is_none() && !traces.is_empty() {
364+
trace_info.guest_start_tsc = Some(traces[0].cycles);
365+
}
366+
}
367+
368+
for record in traces {
369+
record_guest_trace_frame(_hv.trace_info_as_ref(), 4u64, record.cycles, |f| {
370+
let _ = f.write_all(&record.msg_len.to_ne_bytes());
371+
let _ = f.write_all(&record.msg[..record.msg_len]);
372+
})?
373+
}
374+
375+
Ok(())
376+
}
290377
}
291378
}
292379

0 commit comments

Comments
 (0)