Skip to content

Commit

Permalink
feat(inoculate): add subcommand for launching gdb (#119)
Browse files Browse the repository at this point in the history
This branch adds a separate subcommand to `inoculate` for launching
`gdb` and connecting to a running QEMU on the default port. This is
intended for when the QEMU has already been launched, and you want to
connect to it a second terminal. I've found this useful for running a
debug session while looking at serial output in the QEMU terminal.

Signed-off-by: Eliza Weisman <[email protected]>
  • Loading branch information
hawkw authored Sep 27, 2021
1 parent 858c15b commit 289083c
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .cargo/config
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ runner = "cargo run --package inoculate --"

[alias]
run-x64 = "run -Z build-std=core,alloc --target=x86_64-mycelium.json -- run"
debug-x64 = "run -Z build-std=core,alloc --target=x86_64-mycelium.json -- run -- -gdb tcp::1234 -S"
debug-x64 = "run -Z build-std=core,alloc --target=x86_64-mycelium.json -- run --serial --gdb"
test-x64 = "test -Z build-std=core,alloc --target=x86_64-mycelium.json -- test"
clippy-x64 = "clippy -Z build-std=core,alloc --target=x86_64-mycelium.json"
build-x64 = "run -Z build-std=core,alloc --target=x86_64-mycelium.json --"
Expand Down
49 changes: 49 additions & 0 deletions inoculate/src/gdb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use crate::{cargo_log, Result};
use color_eyre::eyre::WrapErr;
use std::{
path::Path,
process::{Command, ExitStatus},
};

#[tracing::instrument]
pub fn run_gdb(binary: &Path, gdb_port: u16) -> Result<ExitStatus> {
let gdb_path = wheres_gdb().context("failed to find gdb executable")?;
cargo_log!("Found", "{}", gdb_path);
let mut gdb = Command::new(gdb_path);

// Set the file, and connect to the given remote, then advance to `kernel_main`.
//
// The `-ex` command provides a gdb command to which is run by gdb
// before handing control over to the user, and we can use it to
// configure the gdb session to connect to the gdb remote.
gdb.arg("-ex").arg(format!("file {}", binary.display()));
gdb.arg("-ex").arg(format!("target remote :{}", gdb_port));

// Set a temporary breakpoint on `kernel_main` and continue to it to
// skip the non-mycelium boot process.
// XXX: Add a flag to skip doing this to allow debugging the boot process.
gdb.arg("-ex").arg("tbreak mycelium_kernel::kernel_main");
gdb.arg("-ex").arg("continue");

cargo_log!("Running", "{:?}", gdb);
// Try to run gdb.
let status = gdb.status().context("failed to run gdb")?;

tracing::debug!("gdb exited with status {:?}", status);
Ok(status)
}

fn wheres_gdb() -> Result<String> {
let output = Command::new("which")
.arg("rust-gdb")
.output()
.context("running `which rust-gdb` failed")?;
let mut which_gdb =
String::from_utf8(output.stdout).context("`which rust-gdb` output was not a string")?;
which_gdb.truncate(which_gdb.trim_end().len());
if which_gdb.ends_with("not found") {
return Ok(String::from("gdb"));
}

Ok(which_gdb)
}
26 changes: 24 additions & 2 deletions inoculate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use structopt::StructOpt;
pub use color_eyre::eyre::Result;

pub mod cargo;
pub mod gdb;
pub mod qemu;
pub mod term;

Expand All @@ -24,7 +25,7 @@ pub struct Options {
///
/// By default, an image is built but not run.
#[structopt(subcommand)]
pub qemu: Option<qemu::Cmd>,
pub cmd: Option<Subcommand>,

/// Configures build logging.
#[structopt(short, long, env = "RUST_LOG", default_value = "warn")]
Expand Down Expand Up @@ -62,9 +63,30 @@ pub struct Options {
pub color: term::ColorMode,
}

#[derive(Debug, StructOpt)]
pub enum Subcommand {
#[structopt(flatten)]
Qemu(qemu::Cmd),
/// Run `gdb` without launching the kernel in QEMU.
///
/// This assumes QEMU was already started by a separate `cargo inoculate`
/// invocation, and that invocation was configured to listen for a GDB
/// connection on the default port.
Gdb,
}

impl Subcommand {
pub fn run(&self, image: &Path, kernel_bin: &Path) -> Result<()> {
match self {
Subcommand::Qemu(qemu) => qemu.run_qemu(image, kernel_bin),
Subcommand::Gdb => crate::gdb::run_gdb(kernel_bin, 1234).map(|_| ()),
}
}
}

impl Options {
pub fn is_test(&self) -> bool {
matches!(self.qemu, Some(qemu::Cmd::Test { .. }))
matches!(self.cmd, Some(Subcommand::Qemu(qemu::Cmd::Test { .. })))
}

pub fn wheres_bootloader(&self) -> Result<PathBuf> {
Expand Down
6 changes: 3 additions & 3 deletions inoculate/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn main() -> Result<()> {
.init();

tracing::info! {
?opts.qemu,
?opts.cmd,
?opts.kernel_bin,
?opts.bootloader_manifest,
?opts.kernel_manifest,
Expand Down Expand Up @@ -44,8 +44,8 @@ fn main() -> Result<()> {
.note("this sucks T_T")?;
tracing::info!(image = %image.display());

if let Some(qemu) = opts.qemu {
return qemu.run_qemu(image.as_ref(), kernel_bin.as_ref());
if let Some(cmd) = opts.cmd {
return cmd.run(image.as_ref(), kernel_bin.as_ref());
}

Ok(())
Expand Down
28 changes: 3 additions & 25 deletions inoculate/src/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,32 +118,10 @@ impl Cmd {

// If the `--gdb` flag was passed, try to run gdb & connect to the kernel.
if qemu_settings.gdb {
let mut gdb = Command::new("gdb");

// Set the file, and connect to the given remote, then advance to `kernel_main`.
//
// The `-ex` command provides a gdb command to which is run by gdb
// before handing control over to the user, and we can use it to
// configure the gdb session to connect to the gdb remote.
gdb.arg("-ex").arg(format!("file {}", binary.display()));
gdb.arg("-ex")
.arg(format!("target remote :{}", qemu_settings.gdb_port));

// Set a temporary breakpoint on `kernel_main` and continue to it to
// skip the non-mycelium boot process.
// XXX: Add a flag to skip doing this to allow debugging the boot process.
gdb.arg("-ex").arg("tbreak mycelium_kernel::kernel_main");
gdb.arg("-ex").arg("continue");

// Try to run gdb, and immediately kill qemu after gdb either exits
// or fails to spawn.
let status = gdb.status();
if let Err(_) = child.kill() {
tracing::error!("failed to kill qemu");
crate::gdb::run_gdb(binary, qemu_settings.gdb_port)?;
if let Err(error) = child.kill() {
tracing::error!(?error, "failed to kill qemu");
}

let status = status.context("starting gdb failed")?;
tracing::debug!("gdb exited with status {:?}", status);
}

Ok(child)
Expand Down

0 comments on commit 289083c

Please sign in to comment.