Skip to content

Commit 621cc03

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

1 file changed

Lines changed: 157 additions & 44 deletions

File tree

  • src/hyperlight_host/examples/guest-debugging

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

Lines changed: 157 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,76 @@ 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+
/// detaches before quitting.
133+
///
134+
/// The breakpoint commands end with `detach` + `quit` instead of
135+
/// `continue`. The previous "inner continue, outer continue, quit"
136+
/// shape races with the inferior exit. After the breakpoint hits
137+
/// and the inner `continue` resumes the guest, the guest may run
138+
/// to completion and the gdb stub may close the remote before gdb
139+
/// has dispatched the outer `continue`, producing a non-zero exit
140+
/// with `Remote connection closed`. Detaching from the breakpoint
141+
/// commands removes that window. The host process keeps running
142+
/// the guest call to completion on its own after detach.
143+
fn single_breakpoint_script(
144+
manifest_dir: &str,
145+
port: u16,
146+
out_file_path: &str,
147+
breakpoint: &str,
148+
echo_msg: &str,
149+
) -> String {
150+
let cmd = format!(
151+
"file {manifest_dir}/../tests/rust_guests/bin/debug/simpleguest
152+
target remote :{port}
153+
154+
set pagination off
155+
set logging file {out_file_path}
156+
set logging enabled on
157+
158+
break {breakpoint}
159+
commands
160+
echo \"{echo_msg}\\n\"
161+
backtrace
162+
163+
set logging enabled off
164+
detach
165+
quit
166+
end
167+
168+
continue
169+
"
170+
);
171+
#[cfg(windows)]
172+
let cmd = format!("set osabi none\n{cmd}");
173+
cmd
174+
}
175+
176+
/// Spawn the gdb client to execute the script in `cmd_file_path`.
177+
fn spawn_gdb_client(cmd_file_path: &str) -> std::process::Child {
178+
Command::new(GDB_COMMAND)
179+
.arg("-nx")
180+
.arg("--nw")
181+
.arg("--batch")
182+
.arg("-x")
183+
.arg(cmd_file_path)
184+
.spawn()
185+
.expect("Failed to start gdb")
186+
}
187+
118188
fn write_cmds_file(cmd_file_path: &str, cmd: &str) -> io::Result<()> {
119189
let file = File::create(cmd_file_path)?;
120190
let mut writer = BufWriter::new(file);
@@ -163,14 +233,7 @@ mod tests {
163233
// wait 3 seconds for the gdb to connect
164234
thread::sleep(Duration::from_secs(3));
165235

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))?;
236+
let mut gdb = spawn_gdb_client(cmd_file_path);
174237

175238
// wait 3 seconds for the gdb to connect
176239
thread::sleep(Duration::from_secs(10));
@@ -245,38 +308,16 @@ mod tests {
245308
#[test]
246309
#[serial]
247310
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-
set logging enabled off
269-
detach
270-
quit
271-
end
272-
273-
continue
274-
"
311+
let (out_file_path, cmd_file_path, manifest_dir) = gdb_test_paths("gdb");
312+
313+
let cmd = single_breakpoint_script(
314+
&manifest_dir,
315+
8080,
316+
&out_file_path,
317+
"hyperlight_main",
318+
"Stopped at hyperlight_main breakpoint",
275319
);
276320

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

282323
let result = run_guest_and_gdb(&cmd_file_path, &out_file_path, &cmd, checker);
@@ -288,13 +329,8 @@ mod tests {
288329
#[test]
289330
#[serial]
290331
fn test_gdb_sse_check() {
291-
let out_dir = std::env::var("OUT_DIR").expect("Failed to get out dir");
292-
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
293-
.expect("Failed to get manifest dir")
294-
.replace('\\', "/");
332+
let (out_file_path, cmd_file_path, manifest_dir) = gdb_test_paths("gdb-sse");
295333
println!("manifest dir {manifest_dir}");
296-
let out_file_path = format!("{out_dir}/gdb-sse.output");
297-
let cmd_file_path = format!("{out_dir}/gdb-sse--commands.txt");
298334

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

0 commit comments

Comments
 (0)