From a6bf4871f4ff36642519bc6007a56376723e0574 Mon Sep 17 00:00:00 2001 From: Jake Hillion Date: Wed, 27 Apr 2022 13:06:26 +0100 Subject: [PATCH] debug helper --- README.md | 14 ++++++++++++++ src/lib.rs | 8 +++++++- src/spawner/mod.rs | 46 ++++++++++++++++++++++++++++++++++++++++++---- src/void.rs | 14 +++++++++++--- 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a545446..ed5245d 100644 --- a/README.md +++ b/README.md @@ -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! diff --git a/src/lib.rs b/src/lib.rs index 57bd2b2..f7c6477 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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> { diff --git a/src/spawner/mod.rs b/src/spawner/mod.rs index 5232266..8de004a 100644 --- a/src/spawner/mod.rs +++ b/src/spawner/mod.rs @@ -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, pub sockets: HashMap, @@ -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, diff --git a/src/void.rs b/src/void.rs index d2f2967..751141e 100644 --- a/src/void.rs +++ b/src/void.rs @@ -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, @@ -90,7 +98,7 @@ impl VoidBuilder { // be a problem. std::mem::forget(child_fn); - Ok(VoidHandle {}) + Ok(VoidHandle { pid: child }) } // per-namespace void creation