debug helper
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing

This commit is contained in:
Jake Hillion 2022-04-27 13:06:26 +01:00
parent ff32b48633
commit a6bf4871f4
4 changed files with 74 additions and 8 deletions

View File

@ -21,3 +21,17 @@ To run this example:
cargo build
cargo build --example pipes
sudo target/debug/clone-shim -s examples/pipes/spec.json target/debug/examples/pipes
## Debugging the shim
The shim can be debugged as with most processes, but it is exceptionally forky. Breaking before a clone in `rust-gdb` then running `set follow-fork-mode child` is often necessary. The best approach is to go in with a plan of attack.
## Debugging the child
Debugging the child processes is vastly more difficult than in other more Linux-like containerisation solutions.
The `--debug` flag on the shim attempts to stop application spawned processes as soon as they are voided. This gives you a chance to attach with a debugger.
The debugger must be run from the ambient namespace and not within the void, as none of the prerequisites will exist within the void.
Good luck!

View File

@ -28,6 +28,7 @@ pub fn run() -> Result<()> {
.arg(clap::Arg::new("spec").long("specification").short('s').help("Provide the specification as an external JSON file.").takes_value(true))
.setting(AppSettings::TrailingVarArg)
.arg(clap::Arg::new("verbose").long("verbose").short('v').help("Use verbose logging.").takes_value(false))
.arg(clap::Arg::new("debug").long("debug").short('d').help("Stop each spawned application process so that it can be attached to.").takes_value(false))
.arg(clap::Arg::new("binary").index(1).help("Binary and arguments to launch with the shim").required(true).multiple_values(true))
.get_matches();
@ -74,15 +75,20 @@ pub fn run() -> Result<()> {
let sockets = create_sockets(sockets)?;
// spawn all processes
let debug = matches.is_present("debug");
Spawner {
spec: &spec,
binary,
trailing: &trailing,
debug,
pipes,
sockets,
}
.spawn()
.spawn()?;
Ok(())
}
fn create_pipes(names: Vec<&str>) -> Result<HashMap<String, PipePair>> {

View File

@ -16,8 +16,9 @@ use std::io::Read;
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
use std::path::PathBuf;
use nix::sys::signal::{kill, Signal};
use nix::sys::socket::{recvmsg, ControlMessageOwned, MsgFlags};
use nix::unistd;
use nix::unistd::{self, Pid};
const BUFFER_SIZE: usize = 1024;
@ -25,6 +26,7 @@ pub struct Spawner<'a> {
pub spec: &'a Specification,
pub binary: &'a str,
pub trailing: &'a Vec<&'a str>,
pub debug: bool,
pub pipes: HashMap<String, PipePair>,
pub sockets: HashMap<String, SocketPair>,
@ -72,6 +74,10 @@ impl<'a> Spawner<'a> {
PreparedArgs::prepare_ambient_mut(self, &mut builder, &entrypoint.args)?;
let closure = || {
if self.debug {
Self::stop_self(name).unwrap()
}
let args = args
.prepare_void(self, name, &mut TriggerData::None)
.unwrap();
@ -89,7 +95,8 @@ impl<'a> Spawner<'a> {
}
};
builder.spawn(closure)?;
let void = builder.spawn(closure)?;
info!("spawned entrypoint `{}` as {}", name.as_str(), void);
}
Trigger::Pipe(s) => {
@ -110,7 +117,12 @@ impl<'a> Spawner<'a> {
}
};
builder.spawn(closure)?;
let void = builder.spawn(closure)?;
info!(
"prepared pipe trigger for entrypoint `{}` as {}",
name.as_str(),
void
);
}
Trigger::FileSocket(s) => {
@ -131,7 +143,12 @@ impl<'a> Spawner<'a> {
}
};
builder.spawn(closure)?;
let void = builder.spawn(closure)?;
info!(
"prepared socket trigger for entrypoint `{}` as {}",
name.as_str(),
void
);
}
}
}
@ -167,6 +184,10 @@ impl<'a> Spawner<'a> {
let closure =
|| {
if self.debug {
Self::stop_self(name).unwrap()
}
let pipe_trigger = std::str::from_utf8(&buf[0..read_bytes]).unwrap();
let args = args
@ -230,6 +251,10 @@ impl<'a> Spawner<'a> {
let args = PreparedArgs::prepare_ambient(&mut builder, &spec.args)?;
let closure = || {
if self.debug {
Self::stop_self(name).unwrap()
}
let args = args
.prepare_void(self, name, &mut TriggerData::FileSocket(fds))
.unwrap();
@ -257,6 +282,19 @@ impl<'a> Spawner<'a> {
}
}
fn stop_self(name: &str) -> Result<()> {
let pid = Pid::this();
info!("stopping process `{}`", name);
kill(pid, Signal::SIGSTOP).map_err(|e| Error::Nix {
msg: "kill",
src: e,
})?;
info!("process `{}` resumed", name);
Ok(())
}
fn prepare_env<'b>(
&self,
builder: &mut VoidBuilder,

View File

@ -4,9 +4,9 @@ use crate::clone::{clone3, CloneArgs, CloneFlags};
use crate::{Error, Result};
use std::collections::{HashMap, HashSet};
use std::fs;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::{Path, PathBuf};
use std::{fmt, fs};
use nix::fcntl::{FcntlArg, FdFlag};
use nix::mount::{mount, umount2, MntFlags, MsFlags};
@ -16,7 +16,15 @@ use nix::unistd::{pivot_root, Pid};
use close_fds::CloseFdsBuilder;
pub struct VoidHandle {}
pub struct VoidHandle {
pid: Pid,
}
impl fmt::Display for VoidHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Void{{Pid:{}}}", self.pid)
}
}
pub struct VoidBuilder {
mounts: HashMap<PathBuf, PathBuf>,
@ -90,7 +98,7 @@ impl VoidBuilder {
// be a problem.
std::mem::forget(child_fn);
Ok(VoidHandle {})
Ok(VoidHandle { pid: child })
}
// per-namespace void creation