diff --git a/.github/workflows/dep_rust.yml b/.github/workflows/dep_rust.yml index ffa79aa8c..02756701d 100644 --- a/.github/workflows/dep_rust.yml +++ b/.github/workflows/dep_rust.yml @@ -157,8 +157,7 @@ jobs: RUST_LOG: debug run: just run-rust-examples-linux ${{ matrix.config }} ${{ matrix.hypervisor == 'mshv' && 'mshv2' || ''}} - - name: Run Rust Gdb tests - linux - if: runner.os == 'Linux' + - name: Run Rust Gdb tests env: CARGO_TERM_COLOR: always RUST_LOG: debug diff --git a/.gitignore b/.gitignore index 2abe2940f..dbd55529f 100644 --- a/.gitignore +++ b/.gitignore @@ -472,3 +472,4 @@ hyperlight_guest.h # gdb .gdbinit trace/* +.gdbguest diff --git a/.vscode/settings.json b/.vscode/settings.json index 3d6d12789..f8cea4474 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,10 @@ "Cargo.toml", // guest crates for testing, not part of the workspace "src/tests/rust_guests/simpleguest/Cargo.toml", - ] + ], + // Enable features in rust analyzer here + "rust-analyzer.cargo.features": [ + // "gdb", + // "kvm" + ], } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 12205f6a7..0456cba83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -921,6 +921,21 @@ dependencies = [ "pe-unwind-info", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -983,6 +998,7 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1470,6 +1486,7 @@ dependencies = [ "seccompiler", "serde", "serde_json", + "serial_test", "sha256", "signal-hook-registry", "tempfile", @@ -3003,6 +3020,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -3035,6 +3061,12 @@ dependencies = [ "syn", ] +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + [[package]] name = "seccompiler" version = "0.5.0" @@ -3114,6 +3146,31 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.10.9" diff --git a/Justfile b/Justfile index 9d7b62dfe..9c02585a6 100644 --- a/Justfile +++ b/Justfile @@ -126,8 +126,8 @@ like-ci config=default-target hypervisor="kvm": @# Run Rust examples - linux {{ if os() == "linux" { "just run-rust-examples-linux " + config + " " + if hypervisor == "mshv" { "mshv2" } else if hypervisor == "mshv3" { "mshv3" } else { "kvm" } } else { "" } }} - @# Run Rust Gdb tests - linux - {{ if os() == "linux" { "just test-rust-gdb-debugging " + config + " " + if hypervisor == "mshv" { "mshv2" } else if hypervisor == "mshv3" { "mshv3" } else { "kvm" } } else { "" } }} + @# Run Rust Gdb tests + just test-rust-gdb-debugging {{ config }} {{ if hypervisor == "mshv" { "mshv2" } else if hypervisor == "mshv3" { "mshv3" } else { "kvm" } }} @# Run Rust Crashdump tests just test-rust-crashdump {{config}} {{ if hypervisor == "mshv" { "mshv2" } else if hypervisor == "mshv3" { "mshv3" } else { "kvm" } }} diff --git a/fuzz/fuzz_targets/host_call.rs b/fuzz/fuzz_targets/host_call.rs index 8011c7314..2bf241673 100644 --- a/fuzz/fuzz_targets/host_call.rs +++ b/fuzz/fuzz_targets/host_call.rs @@ -57,21 +57,18 @@ fuzz_target!( sandbox.restore(&snapshot).unwrap(); host_func_params.insert(0, ParameterValue::String(host_func_name)); - match sandbox.call_type_erased_guest_function_by_name("FuzzHostFunc", host_func_return, host_func_params) { - Err(e) => { - match e { - // the following are expected errors and occur frequently since - // we are randomly generating the function name and parameters - // to call with. - HyperlightError::HostFunctionNotFound(_) => {} - HyperlightError::UnexpectedNoOfArguments(_, _) => {}, - HyperlightError::ParameterValueConversionFailure(_, _) => {}, + if let Err(e) = sandbox.call_type_erased_guest_function_by_name("FuzzHostFunc", host_func_return, host_func_params) { + match e { + // the following are expected errors and occur frequently since + // we are randomly generating the function name and parameters + // to call with. + HyperlightError::HostFunctionNotFound(_) => {} + HyperlightError::UnexpectedNoOfArguments(_, _) => {}, + HyperlightError::ParameterValueConversionFailure(_, _) => {}, - // any other error should be reported - _ => panic!("Guest Aborted with Unexpected Error: {:?}", e), - } + // any other error should be reported + _ => panic!("Guest Aborted with Unexpected Error: {:?}", e), } - _ => {} } } ); diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index 60585edb9..bce81f2e1 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -90,6 +90,7 @@ proptest = "1.8.0" tempfile = "3.23.0" crossbeam-queue = "0.3.12" tracing-serde = "0.2.0" +serial_test = "3.1.1" hyperlight-testing = { workspace = true } env_logger = "0.11.8" tracing-forest = { version = "0.2.0", features = ["uuid", "chrono", "smallvec", "serde", "env-filter"] } diff --git a/src/hyperlight_host/examples/guest-debugging/main.rs b/src/hyperlight_host/examples/guest-debugging/main.rs index e414b72d8..8dd0e3f79 100644 --- a/src/hyperlight_host/examples/guest-debugging/main.rs +++ b/src/hyperlight_host/examples/guest-debugging/main.rs @@ -72,6 +72,10 @@ fn main() -> hyperlight_host::Result<()> { let mut multi_use_sandbox: MultiUseSandbox = uninitialized_sandbox.evolve()?; // Call guest function + multi_use_sandbox_dbg + .call::<()>("UseSSE2Registers", ()) + .unwrap(); + let message = "Hello, World! I am executing inside of a VM with debugger attached :)\n".to_string(); multi_use_sandbox_dbg @@ -103,48 +107,34 @@ mod tests { use hyperlight_host::{Result, new_error}; use io::{BufReader, BufWriter, Read, Write}; + use serial_test::serial; use super::*; - fn write_cmds_file(cmd_file_path: &str, out_file_path: &str) -> io::Result<()> { - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get manifest dir"); + #[cfg(not(windows))] + const GDB_COMMAND: &str = "rust-gdb"; + #[cfg(windows)] + const GDB_COMMAND: &str = "gdb"; + + fn write_cmds_file(cmd_file_path: &str, cmd: &str) -> io::Result<()> { let file = File::create(cmd_file_path)?; let mut writer = BufWriter::new(file); // write from string to file - writer.write_all( - format!( - "file {manifest_dir}/../tests/rust_guests/bin/debug/simpleguest - target remote :8080 - - set pagination off - set logging file {out_file_path} - set logging on - - break hyperlight_main - commands - echo \"Stopped at hyperlight_main breakpoint\\n\" - backtrace - continue - end - - continue - - set logging off - quit - " - ) - .as_bytes(), - )?; + writer.write_all(cmd.as_bytes())?; writer.flush() } - fn run_guest_and_gdb(cmd_file_path: &str, out_file_path: &str) -> Result<()> { + fn run_guest_and_gdb( + cmd_file_path: &str, + out_file_path: &str, + cmd: &str, + checker: fn(String) -> bool, + ) -> Result<()> { // write gdb commands to file - write_cmds_file(&cmd_file_path, &out_file_path) - .expect("Failed to write gdb commands to file"); + write_cmds_file(&cmd_file_path, cmd).expect("Failed to write gdb commands to file"); #[cfg(mshv2)] // mshv3 is a default feature is mutually exclusive with the mshv2 feature let features = "gdb,mshv2"; @@ -165,7 +155,7 @@ mod tests { // wait 3 seconds for the gdb to connect thread::sleep(Duration::from_secs(3)); - let mut gdb = Command::new("rust-gdb") + let mut gdb = Command::new(GDB_COMMAND) .arg("--nw") .arg("--batch") .arg("-x") @@ -215,17 +205,17 @@ mod tests { } } - check_output(&out_file_path) + check_output(&out_file_path, checker) } - fn check_output(out_file_path: &str) -> Result<()> { + fn check_output(out_file_path: &str, checker: fn(contents: String) -> bool) -> Result<()> { let results = File::open(out_file_path) .map_err(|e| new_error!("Failed to open gdb.output file: {}", e))?; let mut reader = BufReader::new(results); let mut contents = String::new(); reader.read_to_string(&mut contents).unwrap(); - if contents.contains("Stopped at hyperlight_main breakpoint") { + if checker(contents) { Ok(()) } else { Err(new_error!( @@ -247,12 +237,95 @@ mod tests { } #[test] + #[serial] fn test_gdb_end_to_end() { let out_dir = std::env::var("OUT_DIR").expect("Failed to get out dir"); + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR") + .expect("Failed to get manifest dir") + .replace('\\', "/"); let out_file_path = format!("{out_dir}/gdb.output"); let cmd_file_path = format!("{out_dir}/gdb-commands.txt"); - let result = run_guest_and_gdb(&cmd_file_path, &out_file_path); + let cmd = format!( + "file {manifest_dir}/../tests/rust_guests/bin/debug/simpleguest + target remote :8080 + + set pagination off + set logging file {out_file_path} + set logging on + + break hyperlight_main + commands + echo \"Stopped at hyperlight_main breakpoint\\n\" + backtrace + + continue + end + + continue + + set logging off + quit + " + ); + + #[cfg(windows)] + let cmd = format!("set osabi none\n{}", cmd); + + let checker = |contents: String| contents.contains("Stopped at hyperlight_main breakpoint"); + + let result = run_guest_and_gdb(&cmd_file_path, &out_file_path, &cmd, checker); + + // cleanup + let cleanup_result = cleanup(&out_file_path, &cmd_file_path); + assert!(cleanup_result.is_ok(), "{}", cleanup_result.unwrap_err()); + // check if the test passed - done at the end to ensure cleanup is done + assert!(result.is_ok(), "{}", result.unwrap_err()); + } + + #[test] + #[serial] + fn test_gdb_sse_check() { + let out_dir = std::env::var("OUT_DIR").expect("Failed to get out dir"); + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR") + .expect("Failed to get manifest dir") + .replace('\\', "/"); + println!("manifest dir {manifest_dir}"); + let out_file_path = format!("{out_dir}/gdb-sse.output"); + let cmd_file_path = format!("{out_dir}/gdb-sse--commands.txt"); + + let cmd = format!( + "file {manifest_dir}/../tests/rust_guests/bin/debug/simpleguest + target remote :8080 + + set pagination off + set logging file {out_file_path} + set logging on + + break main.rs:simpleguest::use_sse2_registers + commands 1 + print $xmm1.v4_float + break +2 + commands 2 + print $xmm1.v4_float + continue + end + continue + end + + + continue + + set logging off + quit + " + ); + + #[cfg(windows)] + let cmd = format!("set osabi none\n{}", cmd); + + let checker = |contents: String| contents.contains("$2 = [1.20000005, 0, 0, 0]"); + let result = run_guest_and_gdb(&cmd_file_path, &out_file_path, &cmd, checker); // cleanup let cleanup_result = cleanup(&out_file_path, &cmd_file_path); diff --git a/src/hyperlight_host/src/hypervisor/gdb/hyperv_debug.rs b/src/hyperlight_host/src/hypervisor/gdb/hyperv_debug.rs index 7a902d6de..c6528f8ab 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/hyperv_debug.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/hyperv_debug.rs @@ -211,6 +211,18 @@ impl GuestDebug for HypervDebug { regs.rip = vcpu_regs.rip; regs.rflags = vcpu_regs.rflags; + // Fetch XMM from WHVP + if let Ok(fpu) = vcpu_fd.get_fpu() { + regs.xmm = [ + fpu.xmm0, fpu.xmm1, fpu.xmm2, fpu.xmm3, fpu.xmm4, fpu.xmm5, fpu.xmm6, fpu.xmm7, + fpu.xmm8, fpu.xmm9, fpu.xmm10, fpu.xmm11, fpu.xmm12, fpu.xmm13, fpu.xmm14, + fpu.xmm15, + ]; + regs.mxcsr = fpu.mxcsr; + } else { + log::warn!("Failed to read FPU/XMM via WHVP for debug registers"); + } + Ok(()) } @@ -226,7 +238,7 @@ impl GuestDebug for HypervDebug { fn write_regs(&self, vcpu_fd: &Self::Vcpu, regs: &X86_64Regs) -> Result<()> { log::debug!("Write registers"); - let regs = WHvGeneralRegisters { + let gprs = WHvGeneralRegisters { rax: regs.rax, rbx: regs.rbx, rcx: regs.rcx, @@ -249,7 +261,37 @@ impl GuestDebug for HypervDebug { }; vcpu_fd - .set_general_purpose_registers(®s) + .set_general_purpose_registers(&gprs) + .map_err(|e| new_error!("Could not write guest registers: {:?}", e))?; + + // Load existing FPU state, replace XMM and MXCSR, and write it back. + let mut fpu = match vcpu_fd.get_fpu() { + Ok(f) => f, + Err(e) => { + return Err(new_error!("Could not write guest registers: {:?}", e)); + } + }; + + fpu.xmm0 = regs.xmm[0]; + fpu.xmm1 = regs.xmm[1]; + fpu.xmm2 = regs.xmm[2]; + fpu.xmm3 = regs.xmm[3]; + fpu.xmm4 = regs.xmm[4]; + fpu.xmm5 = regs.xmm[5]; + fpu.xmm6 = regs.xmm[6]; + fpu.xmm7 = regs.xmm[7]; + fpu.xmm8 = regs.xmm[8]; + fpu.xmm9 = regs.xmm[9]; + fpu.xmm10 = regs.xmm[10]; + fpu.xmm11 = regs.xmm[11]; + fpu.xmm12 = regs.xmm[12]; + fpu.xmm13 = regs.xmm[13]; + fpu.xmm14 = regs.xmm[14]; + fpu.xmm15 = regs.xmm[15]; + fpu.mxcsr = regs.mxcsr; + + vcpu_fd + .set_fpu(&fpu) .map_err(|e| new_error!("Could not write guest registers: {:?}", e)) } } diff --git a/src/hyperlight_host/src/hypervisor/gdb/kvm_debug.rs b/src/hyperlight_host/src/hypervisor/gdb/kvm_debug.rs index c20b3b279..fdd6943fa 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/kvm_debug.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/kvm_debug.rs @@ -193,6 +193,30 @@ impl GuestDebug for KvmDebug { regs.rip = vcpu_regs.rip; regs.rflags = vcpu_regs.rflags; + // Read XMM registers from FPU state + // note kvm get_fpu doesn't actually set or read the mxcsr value + // https://elixir.bootlin.com/linux/v6.16/source/arch/x86/kvm/x86.c#L12229 + match vcpu_fd.get_fpu() { + Ok(fpu) => { + // Convert KVM XMM registers ([u8; 16] x 16) to [u128; 16] + regs.xmm = fpu.xmm.map(u128::from_le_bytes); + } + Err(e) => { + log::warn!("Failed to read FPU state for XMM registers: {:?}", e); + } + } + + // Read MXCSR from XSAVE (MXCSR is at byte offset 24 -> u32 index 6) + // 11.5.10 Mode-Specific XSAVE/XRSTOR State Management + match vcpu_fd.get_xsave() { + Ok(xsave) => { + regs.mxcsr = xsave.region[6]; + } + Err(e) => { + log::warn!("Failed to read XSAVE for MXCSR: {:?}", e); + } + } + Ok(()) } @@ -238,6 +262,38 @@ impl GuestDebug for KvmDebug { vcpu_fd .set_regs(&new_regs) - .map_err(|e| new_error!("Could not write guest registers: {:?}", e)) + .map_err(|e| new_error!("Could not write guest registers: {:?}", e))?; + + // load existing values and replace the xmm registers + let mut fpu = match vcpu_fd.get_fpu() { + Ok(fpu) => fpu, + Err(e) => { + return Err(new_error!("Could not write guest registers: {:?}", e)); + } + }; + + // Convert XMM registers from [u128; 16] (our internal representation) + // to [[u8; 16]; 16] (KVM FPU representation) using little-endian byte order. + fpu.xmm = regs.xmm.map(u128::to_le_bytes); + vcpu_fd + .set_fpu(&fpu) + .map_err(|e| new_error!("Could not write guest registers: {:?}", e))?; + + // Update MXCSR using XSAVE region entry 6 (MXCSR) if available. + let mut xsave = match vcpu_fd.get_xsave() { + Ok(xsave) => xsave, + Err(e) => { + return Err(new_error!("Could not write guest registers: {:?}", e)); + } + }; + + xsave.region[6] = regs.mxcsr; + unsafe { + vcpu_fd + .set_xsave(&xsave) + .map_err(|e| new_error!("Could not write guest registers: {:?}", e))? + }; + + Ok(()) } } diff --git a/src/hyperlight_host/src/hypervisor/gdb/mod.rs b/src/hyperlight_host/src/hypervisor/gdb/mod.rs index eac14f323..e8daf5aa9 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/mod.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/mod.rs @@ -109,6 +109,8 @@ pub(crate) struct X86_64Regs { pub(crate) r15: u64, pub(crate) rip: u64, pub(crate) rflags: u64, + pub(crate) xmm: [u128; 16], + pub(crate) mxcsr: u32, } /// Defines the possible reasons for which a vCPU can be stopped when debugging @@ -140,7 +142,7 @@ pub(crate) enum DebugMsg { RemoveSwBreakpoint(u64), Step, WriteAddr(u64, Vec), - WriteRegisters(X86_64Regs), + WriteRegisters(Box), } /// Enumerates the possible responses that a hypervisor can provide to a debugger @@ -155,7 +157,7 @@ pub(crate) enum DebugResponse { NotAllowed, InterruptHandle(Arc), ReadAddr(Vec), - ReadRegisters(X86_64Regs), + ReadRegisters(Box), RemoveHwBreakpoint(bool), RemoveSwBreakpoint(bool), Step, @@ -449,7 +451,7 @@ mod tests { let res = gdb_conn.try_recv(); assert!(res.is_err()); - let res = hyp_conn.send(DebugResponse::ReadRegisters(X86_64Regs::default())); + let res = hyp_conn.send(DebugResponse::ReadRegisters(Box::default())); assert!(res.is_ok()); let res = gdb_conn.recv(); diff --git a/src/hyperlight_host/src/hypervisor/gdb/mshv_debug.rs b/src/hyperlight_host/src/hypervisor/gdb/mshv_debug.rs index 9688d9ed1..540581426 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/mshv_debug.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/mshv_debug.rs @@ -220,6 +220,18 @@ impl GuestDebug for MshvDebug { regs.rip = vcpu_regs.rip; regs.rflags = vcpu_regs.rflags; + // Try to read XMM from the FPU state + match vcpu_fd.get_fpu() { + Ok(fpu) => { + // MSHV exposes XMM as [[u8; 16]; 16]. Convert to [u128; 16] + regs.xmm = fpu.xmm.map(u128::from_le_bytes); + regs.mxcsr = fpu.mxcsr; + } + Err(e) => { + log::warn!("Failed to read FPU state for XMM registers (MSHV): {:?}", e); + } + } + Ok(()) } @@ -262,6 +274,21 @@ impl GuestDebug for MshvDebug { vcpu_fd .set_regs(&new_regs) + .map_err(|e| new_error!("Could not write guest registers: {:?}", e))?; + + // Load existing FPU state, replace XMM and MXCSR, and write it back. + let mut fpu = match vcpu_fd.get_fpu() { + Ok(f) => f, + Err(e) => { + return Err(new_error!("Could not write guest registers: {:?}", e)); + } + }; + + fpu.xmm = regs.xmm.map(u128::to_le_bytes); + fpu.mxcsr = regs.mxcsr; + + vcpu_fd + .set_fpu(&fpu) .map_err(|e| new_error!("Could not write guest registers: {:?}", e)) } } diff --git a/src/hyperlight_host/src/hypervisor/gdb/x86_64_target.rs b/src/hyperlight_host/src/hypervisor/gdb/x86_64_target.rs index a3e36ebb7..3a67d3017 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/x86_64_target.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/x86_64_target.rs @@ -227,6 +227,8 @@ impl SingleThreadBase for HyperlightSandboxTarget { regs.regs[15] = read_regs.r15; regs.rip = read_regs.rip; regs.eflags = read_regs.rflags as u32; + regs.xmm = read_regs.xmm; + regs.mxcsr = read_regs.mxcsr; Ok(()) } @@ -267,9 +269,11 @@ impl SingleThreadBase for HyperlightSandboxTarget { r15: regs.regs[15], rip: regs.rip, rflags: u64::from(regs.eflags), + xmm: regs.xmm, + mxcsr: regs.mxcsr, }; - match self.send_command(DebugMsg::WriteRegisters(regs))? { + match self.send_command(DebugMsg::WriteRegisters(Box::new(regs)))? { DebugResponse::WriteRegisters => Ok(()), DebugResponse::NotAllowed => { log::error!("Action not allowed at this time, crash might have occurred"); @@ -482,7 +486,7 @@ mod tests { // Check response to read registers - send the response first to not be blocked // by the recv call in the target - let msg = DebugResponse::ReadRegisters(X86_64Regs::default()); + let msg = DebugResponse::ReadRegisters(Box::default()); let res = gdb_conn.send(msg); assert!(res.is_ok()); diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index aaef9316d..5a009823e 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -202,7 +202,7 @@ mod debug { e }) - .map(|_| DebugResponse::ReadRegisters(regs)) + .map(|_| DebugResponse::ReadRegisters(Box::new(regs))) } DebugMsg::RemoveHwBreakpoint(addr) => Ok(DebugResponse::RemoveHwBreakpoint( debug diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index 620910004..e9aa45703 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -185,7 +185,7 @@ mod debug { e }) - .map(|_| DebugResponse::ReadRegisters(regs)) + .map(|_| DebugResponse::ReadRegisters(Box::new(regs))) } DebugMsg::RemoveHwBreakpoint(addr) => Ok(DebugResponse::RemoveHwBreakpoint( debug diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index a69b51f55..36a7066b6 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -194,7 +194,7 @@ mod debug { e }) - .map(|_| DebugResponse::ReadRegisters(regs)) + .map(|_| DebugResponse::ReadRegisters(Box::new(regs))) } DebugMsg::RemoveHwBreakpoint(addr) => Ok(DebugResponse::RemoveHwBreakpoint( debug @@ -586,6 +586,9 @@ impl Hypervisor for KVMDriver { mxcsr: MXCSR_DEFAULT, ..Default::default() // zero out the rest }; + + // note kvm set_fpu doesn't actually set or read the mxcsr value + // https://elixir.bootlin.com/linux/v6.16/source/arch/x86/kvm/x86.c#L12229 self.vcpu_fd.set_fpu(&fpu)?; // run diff --git a/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs b/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs index 3d97de9ea..2212fd433 100644 --- a/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs +++ b/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs @@ -696,6 +696,125 @@ impl VMProcessor { Ok(()) } + #[cfg(gdb)] + pub(super) fn get_fpu(&self) -> Result { + use windows::Win32::System::Hypervisor::*; + + const LEN: usize = 26; + let names: [WHV_REGISTER_NAME; LEN] = [ + WHvX64RegisterXmm0, + WHvX64RegisterXmm1, + WHvX64RegisterXmm2, + WHvX64RegisterXmm3, + WHvX64RegisterXmm4, + WHvX64RegisterXmm5, + WHvX64RegisterXmm6, + WHvX64RegisterXmm7, + WHvX64RegisterXmm8, + WHvX64RegisterXmm9, + WHvX64RegisterXmm10, + WHvX64RegisterXmm11, + WHvX64RegisterXmm12, + WHvX64RegisterXmm13, + WHvX64RegisterXmm14, + WHvX64RegisterXmm15, + WHvX64RegisterFpMmx0, + WHvX64RegisterFpMmx1, + WHvX64RegisterFpMmx2, + WHvX64RegisterFpMmx3, + WHvX64RegisterFpMmx4, + WHvX64RegisterFpMmx5, + WHvX64RegisterFpMmx6, + WHvX64RegisterFpMmx7, + WHvX64RegisterFpControlStatus, + WHvX64RegisterXmmControlStatus, + ]; + + let mut out: [WHV_REGISTER_VALUE; LEN] = unsafe { std::mem::zeroed() }; + unsafe { + WHvGetVirtualProcessorRegisters( + self.get_partition_hdl(), + 0, + names.as_ptr(), + LEN as u32, + out.as_mut_ptr(), + )?; + + // Helper to read a WHV_UINT128 -> u128 + fn u128_from_whv(fp: WHV_REGISTER_VALUE) -> u128 { + unsafe { + let low = fp.Fp.AsUINT128.Anonymous.Low64 as u128; + let high = fp.Fp.AsUINT128.Anonymous.High64 as u128; + (high << 64) | low + } + } + + let xmm = [ + u128_from_whv(out[0]), + u128_from_whv(out[1]), + u128_from_whv(out[2]), + u128_from_whv(out[3]), + u128_from_whv(out[4]), + u128_from_whv(out[5]), + u128_from_whv(out[6]), + u128_from_whv(out[7]), + u128_from_whv(out[8]), + u128_from_whv(out[9]), + u128_from_whv(out[10]), + u128_from_whv(out[11]), + u128_from_whv(out[12]), + u128_from_whv(out[13]), + u128_from_whv(out[14]), + u128_from_whv(out[15]), + ]; + + let mmx = [ + out[16].Reg64, + out[17].Reg64, + out[18].Reg64, + out[19].Reg64, + out[20].Reg64, + out[21].Reg64, + out[22].Reg64, + out[23].Reg64, + ]; + + let fp_control_word = out[24].FpControlStatus.Anonymous.FpControl; + let fp_tag_word = out[24].FpControlStatus.Anonymous.FpTag; + let mxcsr = out[25].XmmControlStatus.Anonymous.XmmStatusControl; + + Ok(WHvFPURegisters { + xmm0: xmm[0], + xmm1: xmm[1], + xmm2: xmm[2], + xmm3: xmm[3], + xmm4: xmm[4], + xmm5: xmm[5], + xmm6: xmm[6], + xmm7: xmm[7], + xmm8: xmm[8], + xmm9: xmm[9], + xmm10: xmm[10], + xmm11: xmm[11], + xmm12: xmm[12], + xmm13: xmm[13], + xmm14: xmm[14], + xmm15: xmm[15], + mmx0: mmx[0], + mmx1: mmx[1], + mmx2: mmx[2], + mmx3: mmx[3], + mmx4: mmx[4], + mmx5: mmx[5], + mmx6: mmx[6], + mmx7: mmx[7], + fp_control_word, + fp_tag_word, + mxcsr, + }) + } + } + #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] pub(super) fn run(&mut self) -> Result { let partition_handle = self.get_partition_hdl(); diff --git a/src/tests/rust_guests/callbackguest/Cargo.lock b/src/tests/rust_guests/callbackguest/Cargo.lock new file mode 100644 index 000000000..21ba7c47d --- /dev/null +++ b/src/tests/rust_guests/callbackguest/Cargo.lock @@ -0,0 +1,270 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "buddy_system_allocator" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0108968a3a2dab95b089c0fc3f1afa7759aa5ebe6f1d86d206d6f7ba726eb" +dependencies = [ + "spin 0.9.8", +] + +[[package]] +name = "callbackguest" +version = "0.1.0" +dependencies = [ + "hyperlight-common", + "hyperlight-guest", + "hyperlight-guest-bin", + "hyperlight-guest-tracing", +] + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "flatbuffers" +version = "25.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1045398c1bfd89168b5fd3f1fc11f6e70b34f6f66300c87d44d3de849463abf1" +dependencies = [ + "bitflags", + "rustc_version", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hyperlight-common" +version = "0.8.0" +dependencies = [ + "anyhow", + "flatbuffers", + "log", + "spin 0.10.0", +] + +[[package]] +name = "hyperlight-guest" +version = "0.8.0" +dependencies = [ + "anyhow", + "hyperlight-common", + "hyperlight-guest-tracing", + "serde_json", +] + +[[package]] +name = "hyperlight-guest-bin" +version = "0.8.0" +dependencies = [ + "buddy_system_allocator", + "cc", + "cfg-if", + "glob", + "hyperlight-common", + "hyperlight-guest", + "hyperlight-guest-tracing", + "log", + "spin 0.10.0", +] + +[[package]] +name = "hyperlight-guest-tracing" +version = "0.8.0" +dependencies = [ + "hyperlight-common", + "hyperlight-guest-tracing-macro", + "spin 0.10.0", +] + +[[package]] +name = "hyperlight-guest-tracing-macro" +version = "0.8.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" diff --git a/src/tests/rust_guests/simpleguest/Cargo.lock b/src/tests/rust_guests/simpleguest/Cargo.lock index 9081da46b..a327588e9 100644 --- a/src/tests/rust_guests/simpleguest/Cargo.lock +++ b/src/tests/rust_guests/simpleguest/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "autocfg" diff --git a/src/tests/rust_guests/simpleguest/src/main.rs b/src/tests/rust_guests/simpleguest/src/main.rs index aabe74dc5..681165a60 100644 --- a/src/tests/rust_guests/simpleguest/src/main.rs +++ b/src/tests/rust_guests/simpleguest/src/main.rs @@ -787,6 +787,15 @@ fn call_given_paramless_hostfunc_that_returns_i64(function_call: &FunctionCall) } } +#[hyperlight_guest_tracing::trace_function] +fn use_sse2_registers(_: &FunctionCall) -> Result> { + unsafe { + let val: f32 = 1.2f32; + core::arch::asm!("movss xmm1, DWORD PTR [{0}]", in(reg) &val); + } + Ok(get_flatbuffer_result(())) +} + #[hyperlight_guest_tracing::trace_function] fn add(function_call: &FunctionCall) -> Result> { if let (ParameterValue::Int(a), ParameterValue::Int(b)) = ( @@ -947,7 +956,6 @@ pub extern "C" fn hyperlight_main() { ReturnType::VecBytes, read_from_user_memory as usize, ); - register_function(read_from_user_memory_def); let read_mapped_buffer_def = GuestFunctionDefinition::new( @@ -956,7 +964,6 @@ pub extern "C" fn hyperlight_main() { ReturnType::VecBytes, read_mapped_buffer as usize, ); - register_function(read_mapped_buffer_def); let write_mapped_buffer_def = GuestFunctionDefinition::new( @@ -965,7 +972,6 @@ pub extern "C" fn hyperlight_main() { ReturnType::Bool, write_mapped_buffer as usize, ); - register_function(write_mapped_buffer_def); let exec_mapped_buffer_def = GuestFunctionDefinition::new( @@ -974,7 +980,6 @@ pub extern "C" fn hyperlight_main() { ReturnType::Bool, exec_mapped_buffer as usize, ); - register_function(exec_mapped_buffer_def); let set_static_def = GuestFunctionDefinition::new( @@ -983,7 +988,6 @@ pub extern "C" fn hyperlight_main() { ReturnType::Int, set_static as usize, ); - register_function(set_static_def); let simple_print_output_def = GuestFunctionDefinition::new( @@ -1477,6 +1481,14 @@ pub extern "C" fn hyperlight_main() { call_given_paramless_hostfunc_that_returns_i64 as usize, ); register_function(call_given_hostfunc_def); + + let use_sse2_registers = GuestFunctionDefinition::new( + "UseSSE2Registers".to_string(), + Vec::new(), + ReturnType::Void, + use_sse2_registers as usize, + ); + register_function(use_sse2_registers); } #[hyperlight_guest_tracing::trace_function] diff --git a/src/trace_dump/main.rs b/src/trace_dump/main.rs index 4f8416769..e6d205eb9 100644 --- a/src/trace_dump/main.rs +++ b/src/trace_dump/main.rs @@ -120,10 +120,8 @@ fn dump_trace_record( // With the exception of `> halt`, which decreases the indent (because `> entrypoint` does not // have a corresponding `< entrypoint`) let msg = if msg.starts_with('>') { - if msg == "> halt" { - if *indent > 0 { - *indent -= 1; - } + if msg == "> halt" && *indent > 0 { + *indent -= 1; } let indent_str = " ".repeat(*indent as usize); let msg = format!("{}{}", indent_str, &msg); @@ -174,7 +172,7 @@ impl TraceTrie { let mut node = self; for frame in trace { f(&mut node.value); - node = (*node).children.entry(*frame).or_insert(Self::new()) + node = node.children.entry(*frame).or_insert(Self::new()) } f(&mut node.value); } @@ -185,7 +183,7 @@ struct SymbolCache { symbol_cache: HashMap)>>, } impl SymbolCache { - fn resolve_symbol<'c>(&'c mut self, addr: u64) -> &'c Option<(String, Option)> { + fn resolve_symbol(&mut self, addr: u64) -> &Option<(String, Option)> { self.symbol_cache.entry(addr).or_insert_with(|| { let frame = &self.loader.find_frames(addr).ok()?.next().ok()??; let function = frame.function.as_ref()?; @@ -382,7 +380,7 @@ impl ViewParams { bar_start: width / 4.0, bar_height: 12.0, bar_leading: 4.0, - bar_brush: bar_brush, + bar_brush, } } } @@ -391,7 +389,7 @@ struct BarRenderer<'r> { state: &'r mut State, now: Duration, } -impl<'r, 'a, 's> RenderWrapper for BarRenderer<'r> { +impl<'a, 's> RenderWrapper for BarRenderer<'_> { fn render(&mut self, ctx: &mut R, wd: u64, ht: u64) -> Option<()> { let v = ViewParams::new(ctx, ht, wd); draw_bg(ctx, &v); @@ -422,7 +420,7 @@ impl<'r, 'a, 's> RenderWrapper for BarRenderer<'r> { &v, false, (3 + i) as u64, - (&mut self.state.symbol_cache).format_symbol(**site), + self.state.symbol_cache.format_symbol(**site), **size, self.state.total, )?; @@ -488,7 +486,7 @@ fn draw_flame( } Some(()) } -impl<'r, 'a, 's> RenderWrapper for FlameRenderer<'r> { +impl<'a, 's> RenderWrapper for FlameRenderer<'_> { fn render(&mut self, ctx: &mut R, wd: u64, ht: u64) -> Option<()> { let mut v = ViewParams::new(ctx, ht, wd); v.bar_start = v.width / 8.0; @@ -745,7 +743,7 @@ fn spawn_render_thread( let mut bar_ffmpeg = ffmpeg_for(&out_dir, Visualisation::Bar, interval.0)?; let mut flame_ffmpeg = ffmpeg_for(&out_dir, Visualisation::Flame, interval.0)?; let mut job_state = State { - inf: inf, + inf, symbol_cache: SymbolCache { loader, symbol_cache: HashMap::new(), @@ -801,9 +799,9 @@ fn main() { }; let inf = File::open(args[2].clone()).expect("could not open trace file"); let state = State { - inf: inf, + inf, symbol_cache: SymbolCache { - loader: loader, + loader, symbol_cache: HashMap::new(), }, start_time: None, @@ -854,7 +852,6 @@ fn plot_mem(args: Vec, mut state: State) { Some(()) => (), None => { eprintln!("i/o error encountered"); - () } } eprintln!("max total memory used is {}", state.max_total);