Skip to content

Commit 8883c80

Browse files
committed
Add gdb test for MultiUseSandbox from_snapshot
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent dcd58a6 commit 8883c80

1 file changed

Lines changed: 148 additions & 45 deletions

File tree

  • src/hyperlight_host/examples/guest-debugging

src/hyperlight_host/examples/guest-debugging/main.rs

Lines changed: 148 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,67 @@ mod tests {
115115
#[cfg(windows)]
116116
const GDB_COMMAND: &str = "gdb";
117117

118+
/// Construct the (out_file_path, cmd_file_path, manifest_dir)
119+
/// triple every gdb test needs.
120+
fn gdb_test_paths(name: &str) -> (String, String, String) {
121+
let out_dir = std::env::var("OUT_DIR").expect("Failed to get out dir");
122+
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
123+
.expect("Failed to get manifest dir")
124+
.replace('\\', "/");
125+
let out_file_path = format!("{out_dir}/{name}.output");
126+
let cmd_file_path = format!("{out_dir}/{name}-commands.txt");
127+
(out_file_path, cmd_file_path, manifest_dir)
128+
}
129+
130+
/// Build a gdb script that connects to `port`, sets a single
131+
/// breakpoint at `breakpoint`, prints `echo_msg` when hit, and
132+
/// continues to completion.
133+
fn single_breakpoint_script(
134+
manifest_dir: &str,
135+
port: u16,
136+
out_file_path: &str,
137+
breakpoint: &str,
138+
echo_msg: &str,
139+
) -> String {
140+
let cmd = format!(
141+
"file {manifest_dir}/../tests/rust_guests/bin/debug/simpleguest
142+
target remote :{port}
143+
144+
set pagination off
145+
set logging file {out_file_path}
146+
set logging enabled on
147+
148+
break {breakpoint}
149+
commands
150+
echo \"{echo_msg}\\n\"
151+
backtrace
152+
153+
continue
154+
end
155+
156+
continue
157+
158+
set logging enabled off
159+
quit
160+
"
161+
);
162+
#[cfg(windows)]
163+
let cmd = format!("set osabi none\n{cmd}");
164+
cmd
165+
}
166+
167+
/// Spawn the gdb client to execute the script in `cmd_file_path`.
168+
fn spawn_gdb_client(cmd_file_path: &str) -> std::process::Child {
169+
Command::new(GDB_COMMAND)
170+
.arg("-nx")
171+
.arg("--nw")
172+
.arg("--batch")
173+
.arg("-x")
174+
.arg(cmd_file_path)
175+
.spawn()
176+
.expect("Failed to start gdb")
177+
}
178+
118179
fn write_cmds_file(cmd_file_path: &str, cmd: &str) -> io::Result<()> {
119180
let file = File::create(cmd_file_path)?;
120181
let mut writer = BufWriter::new(file);
@@ -163,14 +224,7 @@ mod tests {
163224
// wait 3 seconds for the gdb to connect
164225
thread::sleep(Duration::from_secs(3));
165226

166-
let mut gdb = Command::new(GDB_COMMAND)
167-
.arg("-nx") // Don't load any .gdbinit files
168-
.arg("--nw")
169-
.arg("--batch")
170-
.arg("-x")
171-
.arg(cmd_file_path)
172-
.spawn()
173-
.map_err(|e| new_error!("Failed to start gdb process: {}", e))?;
227+
let mut gdb = spawn_gdb_client(cmd_file_path);
174228

175229
// wait 3 seconds for the gdb to connect
176230
thread::sleep(Duration::from_secs(10));
@@ -245,39 +299,16 @@ mod tests {
245299
#[test]
246300
#[serial]
247301
fn test_gdb_end_to_end() {
248-
let out_dir = std::env::var("OUT_DIR").expect("Failed to get out dir");
249-
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
250-
.expect("Failed to get manifest dir")
251-
.replace('\\', "/");
252-
let out_file_path = format!("{out_dir}/gdb.output");
253-
let cmd_file_path = format!("{out_dir}/gdb-commands.txt");
254-
255-
let cmd = format!(
256-
"file {manifest_dir}/../tests/rust_guests/bin/debug/simpleguest
257-
target remote :8080
258-
259-
set pagination off
260-
set logging file {out_file_path}
261-
set logging enabled on
262-
263-
break hyperlight_main
264-
commands
265-
echo \"Stopped at hyperlight_main breakpoint\\n\"
266-
backtrace
267-
268-
continue
269-
end
270-
271-
continue
272-
273-
set logging enabled off
274-
quit
275-
"
302+
let (out_file_path, cmd_file_path, manifest_dir) = gdb_test_paths("gdb");
303+
304+
let cmd = single_breakpoint_script(
305+
&manifest_dir,
306+
8080,
307+
&out_file_path,
308+
"hyperlight_main",
309+
"Stopped at hyperlight_main breakpoint",
276310
);
277311

278-
#[cfg(windows)]
279-
let cmd = format!("set osabi none\n{}", cmd);
280-
281312
let checker = |contents: String| contents.contains("Stopped at hyperlight_main breakpoint");
282313

283314
let result = run_guest_and_gdb(&cmd_file_path, &out_file_path, &cmd, checker);
@@ -289,13 +320,8 @@ mod tests {
289320
#[test]
290321
#[serial]
291322
fn test_gdb_sse_check() {
292-
let out_dir = std::env::var("OUT_DIR").expect("Failed to get out dir");
293-
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
294-
.expect("Failed to get manifest dir")
295-
.replace('\\', "/");
323+
let (out_file_path, cmd_file_path, manifest_dir) = gdb_test_paths("gdb-sse");
296324
println!("manifest dir {manifest_dir}");
297-
let out_file_path = format!("{out_dir}/gdb-sse.output");
298-
let cmd_file_path = format!("{out_dir}/gdb-sse--commands.txt");
299325

300326
let cmd = format!(
301327
"file {manifest_dir}/../tests/rust_guests/bin/debug/simpleguest
@@ -333,4 +359,81 @@ mod tests {
333359
cleanup(&out_file_path, &cmd_file_path);
334360
assert!(result.is_ok(), "{}", result.unwrap_err());
335361
}
362+
363+
#[test]
364+
#[serial]
365+
fn test_gdb_from_snapshot() {
366+
use std::sync::Arc;
367+
368+
use hyperlight_host::HostFunctions;
369+
use hyperlight_host::sandbox::snapshot::Snapshot;
370+
371+
const PORT: u16 = 8081;
372+
373+
let (out_file_path, cmd_file_path, manifest_dir) = gdb_test_paths("gdb-from-snapshot");
374+
let out_dir = std::env::var("OUT_DIR").unwrap();
375+
let snap_path = format!("{out_dir}/from-snapshot-debug.hls");
376+
377+
// Build a sandbox the normal way and persist its snapshot.
378+
let mut producer: MultiUseSandbox = UninitializedSandbox::new(
379+
hyperlight_host::GuestBinary::FilePath(
380+
hyperlight_testing::simple_guest_as_string().unwrap(),
381+
),
382+
None,
383+
)
384+
.unwrap()
385+
.evolve()
386+
.unwrap();
387+
producer.snapshot().unwrap().to_file(&snap_path).unwrap();
388+
389+
// Order matters. The gdb stub event loop must enter (i.e.
390+
// `VcpuStopped` must be sent on the channel) before the gdb
391+
// client connects, otherwise the wire protocol desyncs. The
392+
// evolve case gets this for free because `evolve()` runs
393+
// `vm.initialise()` which trips the entry breakpoint
394+
// immediately. For a `Call` snapshot `vm.initialise` is a
395+
// no-op, so we trigger the breakpoint by running `sbox.call`
396+
// here before the client is launched below.
397+
let snap_path_thread = snap_path.clone();
398+
let sandbox_thread = thread::spawn(move || -> Result<()> {
399+
let mut cfg = SandboxConfiguration::default();
400+
cfg.set_guest_debug_info(DebugInfo { port: PORT });
401+
402+
let loaded = Arc::new(Snapshot::from_file(&snap_path_thread)?);
403+
let mut sbox =
404+
MultiUseSandbox::from_snapshot(loaded, HostFunctions::default(), Some(cfg))?;
405+
sbox.call::<i32>(
406+
"PrintOutput",
407+
"Hello from a from_snapshot sandbox\n".to_string(),
408+
)?;
409+
Ok(())
410+
});
411+
412+
// Wait for the sandbox thread to bind the listener, install
413+
// the one-shot breakpoint, and trip it.
414+
thread::sleep(Duration::from_secs(3));
415+
416+
let cmd = single_breakpoint_script(
417+
&manifest_dir,
418+
PORT,
419+
&out_file_path,
420+
"print_output",
421+
"Stopped at print_output breakpoint",
422+
);
423+
write_cmds_file(&cmd_file_path, &cmd).expect("Failed to write gdb commands");
424+
425+
let mut gdb = spawn_gdb_client(&cmd_file_path);
426+
let _ = gdb.wait();
427+
let sandbox_result = sandbox_thread
428+
.join()
429+
.expect("from_snapshot sandbox thread panicked");
430+
let _ = std::fs::remove_file(&snap_path);
431+
432+
let checker = |contents: String| contents.contains("Stopped at print_output breakpoint");
433+
let result = check_output(&out_file_path, checker);
434+
435+
cleanup(&out_file_path, &cmd_file_path);
436+
sandbox_result.expect("from_snapshot sandbox returned error");
437+
result.expect("gdb output missing expected breakpoint hit");
438+
}
336439
}

0 commit comments

Comments
 (0)