From dc8d10b8956127babba9559b24ffe7705c7b04ae Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 10 Aug 2026 16:05:11 -0400 Subject: [PATCH] Isolated Processes 16: Implement Remote Backend --- Cargo.lock | 21 ++ Cargo.toml | 2 +- crates/linux/process-backend/Cargo.toml | 6 + crates/linux/process-backend/src/lib.rs | 5 + .../process-backend/src/remote/backend/mod.rs | 343 ++++++++++++++++++ .../src/remote/backend/transport.rs | 21 ++ .../src/remote/executor/handler.rs | 246 +++++++++++++ .../src/remote/executor/mod.rs | 61 ++++ .../src/remote/executor/resources.rs | 93 +++++ .../src/remote/executor/transport.rs | 21 ++ .../process-backend/src/remote/io/mod.rs | 22 ++ .../src/remote/io/unix_stream.rs | 67 ++++ .../linux/process-backend/src/remote/mod.rs | 5 + .../src/remote/wire/cstr_with_null.rs | 72 ++++ .../process-backend/src/remote/wire/mod.rs | 189 ++++++++++ .../src/remote/wire/transport/mod.rs | 25 ++ .../src/remote/wire/transport/postcard.rs | 289 +++++++++++++++ crates/linux/process-backend/tests/basic.rs | 99 +++++ src/bin/test.rs | 86 ++++- src/linux/minidump_writer/mod.rs | 14 + src/linux/mod.rs | 5 +- src/linux/process_inspection/mod.rs | 16 +- tests/common/mod.rs | 73 +++- tests/linux_minidump_writer.rs | 46 ++- tests/linux_minidump_writer_soft_error.rs | 21 +- tests/ptrace_dumper.rs | 9 +- 26 files changed, 1816 insertions(+), 41 deletions(-) create mode 100644 crates/linux/process-backend/src/remote/backend/mod.rs create mode 100644 crates/linux/process-backend/src/remote/backend/transport.rs create mode 100644 crates/linux/process-backend/src/remote/executor/handler.rs create mode 100644 crates/linux/process-backend/src/remote/executor/mod.rs create mode 100644 crates/linux/process-backend/src/remote/executor/resources.rs create mode 100644 crates/linux/process-backend/src/remote/executor/transport.rs create mode 100644 crates/linux/process-backend/src/remote/io/mod.rs create mode 100644 crates/linux/process-backend/src/remote/io/unix_stream.rs create mode 100644 crates/linux/process-backend/src/remote/mod.rs create mode 100644 crates/linux/process-backend/src/remote/wire/cstr_with_null.rs create mode 100644 crates/linux/process-backend/src/remote/wire/mod.rs create mode 100644 crates/linux/process-backend/src/remote/wire/transport/mod.rs create mode 100644 crates/linux/process-backend/src/remote/wire/transport/postcard.rs create mode 100644 crates/linux/process-backend/tests/basic.rs diff --git a/Cargo.lock b/Cargo.lock index 8cdfe142..4e25cf1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,6 +268,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea" +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.19", +] + [[package]] name = "compression-codecs" version = "0.4.38" @@ -1629,6 +1638,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "serde", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -1664,6 +1683,8 @@ name = "process-backend" version = "0.1.0" dependencies = [ "libc", + "postcard", + "process-backend", "process-reader", "serde", "thiserror 2.0.19", diff --git a/Cargo.toml b/Cargo.toml index 7020432a..88cb79d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ memmap2 = "0.9" byteorder = "1.4" error-graph = { version = "0.1.1", features = ["serde"] } failspot = "0.2.0" -process-backend = { version = "0.1.0", path = "crates/linux/process-backend", features = ["testing"] } +process-backend = { version = "0.1.0", path = "crates/linux/process-backend", features = ["postcard", "testing", "unix_stream"] } # Used for parsing procfs info. # default-features is disabled since it pulls in chrono diff --git a/crates/linux/process-backend/Cargo.toml b/crates/linux/process-backend/Cargo.toml index d2329ec2..9c8cc8bf 100644 --- a/crates/linux/process-backend/Cargo.toml +++ b/crates/linux/process-backend/Cargo.toml @@ -9,10 +9,16 @@ rust-version.workspace = true license.workspace = true [features] +postcard = ["dep:postcard"] testing = [] +unix_stream = [] [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] libc.workspace = true +postcard = { version = "1.1.3", default-features = false, optional = true } process-reader = { version = "0.1.0", path = "../../process-reader", features = ["serde"] } serde.workspace = true thiserror.workspace = true + +[dev-dependencies] +process-backend = { path = ".", features = ["postcard", "testing", "unix_stream"] } \ No newline at end of file diff --git a/crates/linux/process-backend/src/lib.rs b/crates/linux/process-backend/src/lib.rs index 38b513e2..7105ccab 100644 --- a/crates/linux/process-backend/src/lib.rs +++ b/crates/linux/process-backend/src/lib.rs @@ -12,6 +12,7 @@ mod drop_fail_handler; pub mod local; pub mod regs; +pub mod remote; mod wrapper; @@ -94,6 +95,10 @@ pub enum ProcessReaderKind { pub enum Error { #[error("there was a failure making a direct syscall")] Local(#[source] local::Error), + #[error("the remote executor returned an error")] + Executor(#[source] remote::executor::Error), + #[error("there was an error transporting the request to/from the remote executor")] + Transport(#[source] remote::backend::transport::Error), #[error("buffer was too small to contain the result")] BufferTooSmall, } diff --git a/crates/linux/process-backend/src/remote/backend/mod.rs b/crates/linux/process-backend/src/remote/backend/mod.rs new file mode 100644 index 00000000..0308c083 --- /dev/null +++ b/crates/linux/process-backend/src/remote/backend/mod.rs @@ -0,0 +1,343 @@ +use super as remote; +use crate::wrapper::Stat; +use crate::{Error, ProcessReaderKind, Result}; +use core::cell::{Cell, RefCell}; +use remote::wire; +use wire::{Operation, ops}; + +pub use transport::Transport; + +pub mod transport; + +#[derive(Debug)] +pub struct Backend { + transport_state: RefCell>, + pid: Cell>, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug)] +enum TransportState { + Ready(T), + Poisoned, +} + +impl TransportState { + fn expect_ready(&mut self) -> &mut T { + match self { + TransportState::Ready(transport) => transport, + TransportState::Poisoned => panic!("attempted to use poisoned transport"), + } + } +} + +impl Backend { + pub fn new(transport: T) -> Self { + Self { + transport_state: RefCell::new(TransportState::Ready(transport)), + pid: Cell::new(None), + } + } + fn empty_request(&self) -> Result + where + Op: for<'args, 'output> Operation = (), Output<'output> = Out>, + Out: 'static, + { + self.simple_request::(()) + } + fn simple_request(&self, args: Op::Args<'_>) -> Result + where + Op: for<'output> Operation = Out>, + Out: 'static, + { + self.mapped_request::(args, Ok) + } + fn mapped_request(&self, args: Op::Args<'_>, map_fn: F) -> Result + where + Op: Operation, + F: for<'output> FnOnce(Op::Output<'output>) -> Result, + { + let mut transport_state = self.transport_state.borrow_mut(); + let transport = transport_state.expect_ready(); + + let e = match Op::request(transport, args) { + Ok(response) => { + let output = response.map_err(Error::Executor)?; + return map_fn(output); + } + Err(e) => e, + }; + + *transport_state = TransportState::Poisoned; + Err(Error::Transport(e)) + } +} + +impl crate::Backend for Backend { + type ProcessReader<'a> + = ProcessReader<'a, T> + where + Self: 'a; + + type FileReader<'a> + = FileReader<'a, T> + where + Self: 'a; + + type DirReader<'a> + = DirReader<'a, T> + where + Self: 'a; + + type MappedModuleMemoryReader<'a> + = MappedModuleMemoryReader<'a, T> + where + Self: 'a; + + fn pid(&self) -> Result { + match self.pid.get() { + Some(pid) => Ok(pid), + None => { + let pid = self.empty_request::()?; + self.pid.set(Some(pid)); + Ok(pid) + } + } + } + + fn process_reader<'a>(&'a self) -> Self::ProcessReader<'a> { + ProcessReader { backend: self } + } + + fn stop_process(&self) -> Result<()> { + self.empty_request::() + } + + fn continue_process(&self) -> Result<()> { + self.empty_request::() + } + + fn suspend_thread(&self, tid: libc::pid_t) -> Result<()> { + self.simple_request::(tid) + } + + fn resume_thread(&self, tid: libc::pid_t) -> Result<()> { + self.simple_request::(tid) + } + + fn map_module_into_memory<'a>( + &'a self, + path: &core::ffi::CStr, + offset: u64, + ) -> Result> { + self.mapped_request::( + wire::MappedModuleMemoryReaderOpenArgs { + path: path.into(), + offset, + }, + |handle| { + Ok(MappedModuleMemoryReader { + backend: self, + handle, + }) + }, + ) + } + + fn stat_file(&self, path: &core::ffi::CStr) -> Result { + self.simple_request::(path.into()) + } + + fn read_file<'a>(&'a self, path: &core::ffi::CStr) -> Result> { + self.mapped_request::(path.into(), |handle| { + Ok(FileReader { + backend: self, + handle, + }) + }) + } + + fn read_dir<'a>(&'a self, path: &core::ffi::CStr) -> Result> { + self.mapped_request::(path.into(), |handle| { + Ok(DirReader { + backend: self, + handle, + }) + }) + } + + fn read_link(&self, path: &core::ffi::CStr, buf: &mut [u8]) -> Result { + self.mapped_request::( + wire::ReadLinkArgs { + path: path.into(), + requested_len: buf.len(), + }, + |bytes| { + buf[0..bytes.len()].copy_from_slice(bytes); + Ok(bytes.len()) + }, + ) + } + + fn get_gen_regs(&self, tid: libc::pid_t) -> Result { + self.simple_request::(tid) + } + + fn get_fp_regs(&self, tid: libc::pid_t) -> Result { + self.simple_request::(tid) + } + + #[cfg(target_arch = "x86")] + fn get_fpx_regs(&self, tid: libc::pid_t) -> Result { + self.simple_request::(tid) + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn ptrace_peekuser(&self, addr: usize) -> Result<[u8; crate::PTRACE_DATA_LEN]> { + self.simple_request::(addr) + } + + fn force_process_reader_kind(&mut self, kind: ProcessReaderKind) -> Result<()> { + self.simple_request::(kind) + } + + #[cfg(feature = "testing")] + fn fail_one_syscall_with(&self, errno: core::ffi::c_int) { + let _result = self.simple_request::(errno); + } +} + +impl Drop for Backend { + fn drop(&mut self) { + if let Err(e) = self.empty_request::() { + report_drop_failed!("failed to drop backend: {e:#?}"); + } + } +} + +#[derive(Debug)] +pub struct ProcessReader<'a, T: Transport> { + backend: &'a Backend, +} + +impl<'a, T: Transport> crate::ProcessReader for ProcessReader<'a, T> { + fn read_at(&self, address: usize, buf: &mut [u8]) -> Result { + self.backend + .mapped_request::( + wire::ProcessReaderReadAtArgs { + address, + requested_len: buf.len(), + }, + |bytes| { + buf[0..bytes.len()].copy_from_slice(bytes); + Ok(bytes.len()) + }, + ) + } +} + +#[derive(Debug)] +pub struct FileReader<'a, T: Transport> { + backend: &'a Backend, + handle: usize, +} + +impl<'a, T: Transport> crate::FileReader for FileReader<'a, T> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.backend.mapped_request::( + wire::FileReaderArgs { + handle: self.handle, + requested_len: buf.len(), + }, + |bytes| { + buf[0..bytes.len()].copy_from_slice(bytes); + Ok(bytes.len()) + }, + ) + } +} + +impl<'a, T: Transport> Drop for FileReader<'a, T> { + fn drop(&mut self) { + if let Err(e) = self + .backend + .simple_request::(self.handle) + { + report_drop_failed!("failed to close FileReader: {e:#?}"); + } + } +} + +#[derive(Debug)] +pub struct DirReader<'a, T: Transport> { + backend: &'a Backend, + handle: usize, +} + +impl<'a, T: Transport> crate::DirReader for DirReader<'a, T> { + fn read_next_name(&mut self, buf: &mut [u8]) -> Result { + self.backend + .mapped_request::(self.handle, |maybe_bytes| { + if let Some(bytes) = maybe_bytes { + buf[0..bytes.len()].copy_from_slice(bytes); + Ok(bytes.len()) + } else { + Ok(0) + } + }) + } +} + +impl<'a, T: Transport> Drop for DirReader<'a, T> { + fn drop(&mut self) { + if let Err(e) = self + .backend + .simple_request::(self.handle) + { + report_drop_failed!("failed to close DirReader: {e:#?}"); + } + } +} + +#[derive(Debug)] +pub struct MappedModuleMemoryReader<'a, T: Transport> { + backend: &'a Backend, + handle: usize, +} + +impl<'a, T: Transport> crate::MappedModuleMemoryReader for MappedModuleMemoryReader<'a, T> { + fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result { + self.backend + .mapped_request::( + wire::MappedModuleMemoryReaderReadAtArgs { + handle: self.handle, + offset, + requested_len: buf.len(), + }, + |bytes| { + buf[0..bytes.len()].copy_from_slice(bytes); + Ok(bytes.len()) + }, + ) + } + + fn len(&self) -> Result { + self.backend + .simple_request::(self.handle) + } + + fn is_empty(&self) -> Result { + self.len().map(|len| len == 0) + } +} + +impl<'a, T: Transport> Drop for MappedModuleMemoryReader<'a, T> { + fn drop(&mut self) { + if let Err(e) = self + .backend + .simple_request::(self.handle) + { + report_drop_failed!("failed to close MappedModuleMemoryReader: {e:#?}"); + } + } +} diff --git a/crates/linux/process-backend/src/remote/backend/transport.rs b/crates/linux/process-backend/src/remote/backend/transport.rs new file mode 100644 index 00000000..b4660baa --- /dev/null +++ b/crates/linux/process-backend/src/remote/backend/transport.rs @@ -0,0 +1,21 @@ +use super::remote; +use remote::wire; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "postcard")] +pub use wire::transport::postcard::Backend as Postcard; + +pub trait Transport: core::fmt::Debug { + fn send_request<'output, Req: Serialize, Resp: Deserialize<'output>>( + &'output mut self, + req: Req, + ) -> core::result::Result; +} + +#[derive(Debug, thiserror::Error, Deserialize, Serialize)] +pub enum Error { + #[error("an error occurred serializing the request")] + SerializeRequest(#[source] wire::transport::SerializeError), + #[error("an error occurred deserializing the response")] + DeserializeResponse(#[source] wire::transport::DeserializeError), +} diff --git a/crates/linux/process-backend/src/remote/executor/handler.rs b/crates/linux/process-backend/src/remote/executor/handler.rs new file mode 100644 index 00000000..0e058db3 --- /dev/null +++ b/crates/linux/process-backend/src/remote/executor/handler.rs @@ -0,0 +1,246 @@ +use super as executor; +use super::remote; +use crate::{ProcessReaderKind, regs::*, wrapper::Stat}; +use executor::{Error, Executor, Result}; +use remote::wire; +use wire::{CStrWithNull, HandlesRequests}; + +impl HandlesRequests for &mut Executor<'_> { + fn GetPid<'args, 'output>(self, _args: ()) -> Result + where + Self: 'output, + { + Ok(self.local_backend.pid()) + } + fn ProcessReaderReadAt<'args, 'output>( + self, + args: wire::ProcessReaderReadAtArgs, + ) -> Result<&'output [u8]> + where + Self: 'output, + { + let scratch_buf = borrow_with_limit(self.scratch_buf, args.requested_len); + + let bytes_read = self + .local_backend + .process_reader() + .read_at(args.address, scratch_buf) + .map_err(Error::Local)?; + + Ok(&scratch_buf[0..bytes_read]) + } + fn StopProcess<'args, 'output>(self, _args: ()) -> Result<()> + where + Self: 'output, + { + self.local_backend.stop_process().map_err(Error::Local) + } + fn ContinueProcess<'args, 'output>(self, _args: ()) -> Result<()> + where + Self: 'output, + { + self.local_backend.continue_process().map_err(Error::Local) + } + fn SuspendThread<'args, 'output>(self, tid: libc::pid_t) -> Result<()> + where + Self: 'output, + { + self.local_backend.suspend_thread(tid).map_err(Error::Local) + } + fn ResumeThread<'args, 'output>(self, tid: libc::pid_t) -> Result<()> + where + Self: 'output, + { + self.local_backend.resume_thread(tid).map_err(Error::Local) + } + fn MappedModuleMemoryReaderOpen<'args, 'output>( + self, + args: wire::MappedModuleMemoryReaderOpenArgs, + ) -> Result + where + Self: 'output, + { + let reader = self + .local_backend + .map_module_into_memory(&args.path, args.offset) + .map_err(Error::Local)?; + self.mapped_module_memory_readers + .assign_slot(reader) + .ok_or(Error::MappedModuleMemoryReaderSlotsExhausted) + } + fn MappedModuleMemoryReaderReadAt<'args, 'output>( + self, + args: wire::MappedModuleMemoryReaderReadAtArgs, + ) -> Result<&'output [u8]> + where + Self: 'output, + { + let reader = self + .mapped_module_memory_readers + .get_slot_mut(args.handle) + .ok_or(Error::EmptyMappedModuleMemoryReaderSlotRequested)?; + reader + .read_at(args.offset, args.requested_len) + .map_err(Error::Local) + } + fn MappedModuleMemoryReaderLen<'args, 'output>(self, handle: usize) -> Result + where + Self: 'output, + { + let reader = self + .mapped_module_memory_readers + .get_slot_mut(handle) + .ok_or(Error::EmptyMappedModuleMemoryReaderSlotRequested)?; + Ok(reader.len()) + } + fn MappedModuleMemoryReaderClose<'args, 'output>(self, handle: usize) -> Result<()> + where + Self: 'output, + { + let _reader = self + .mapped_module_memory_readers + .free_slot(handle) + .ok_or(Error::EmptyMappedModuleMemoryReaderSlotRequested)?; + Ok(()) + } + fn StatFile<'args, 'output>(self, path: CStrWithNull<'_>) -> Result + where + Self: 'output, + { + self.local_backend.stat_file(&path).map_err(Error::Local) + } + fn FileReaderOpen<'args, 'output>(self, path: CStrWithNull<'_>) -> Result + where + Self: 'output, + { + let reader = self.local_backend.read_file(&path).map_err(Error::Local)?; + self.file_readers + .assign_slot(reader) + .ok_or(Error::FileReaderSlotsExhausted) + } + fn FileReaderRead<'args, 'output>(self, args: wire::FileReaderArgs) -> Result<&'output [u8]> + where + Self: 'output, + { + let scratch_buf = borrow_with_limit(self.scratch_buf, args.requested_len); + + let reader = self + .file_readers + .get_slot_mut(args.handle) + .ok_or(Error::EmptyFileSlotRequested)?; + + let bytes_read = reader.read(scratch_buf).map_err(Error::Local)?; + + Ok(&scratch_buf[0..bytes_read]) + } + fn FileReaderClose<'args, 'output>(self, handle: usize) -> Result<()> + where + Self: 'output, + { + let _reader = self + .file_readers + .free_slot(handle) + .ok_or(Error::EmptyFileSlotRequested)?; + Ok(()) + } + fn DirReaderOpen<'args, 'output>(self, path: CStrWithNull<'_>) -> Result + where + Self: 'output, + { + let reader = self.local_backend.read_dir(&path).map_err(Error::Local)?; + self.dir_readers + .assign_slot(reader) + .ok_or(Error::DirReaderSlotsExhausted) + } + fn DirReaderRead<'args, 'output>(self, handle: usize) -> Result> + where + Self: 'output, + { + let reader = self + .dir_readers + .get_slot_mut(handle) + .ok_or(Error::EmptyDirSlotRequested)?; + + reader.read_next_name().map_err(Error::Local) + } + fn DirReaderClose<'args, 'output>(self, handle: usize) -> Result<()> + where + Self: 'output, + { + let _reader = self + .dir_readers + .free_slot(handle) + .ok_or(Error::EmptyDirSlotRequested)?; + Ok(()) + } + fn ReadLink<'args, 'output>(self, args: wire::ReadLinkArgs<'_>) -> Result<&'output [u8]> + where + Self: 'output, + { + let scratch_buf = borrow_with_limit(self.scratch_buf, args.requested_len); + + let bytes_read = self + .local_backend + .read_link(&args.path, scratch_buf) + .map_err(Error::Local)?; + + Ok(&scratch_buf[0..bytes_read]) + } + fn GetGenRegs<'args, 'output>(self, tid: libc::pid_t) -> Result + where + Self: 'output, + { + self.local_backend.get_gen_regs(tid).map_err(Error::Local) + } + fn GetFpRegs<'args, 'output>(self, tid: libc::pid_t) -> Result + where + Self: 'output, + { + self.local_backend.get_fp_regs(tid).map_err(Error::Local) + } + #[cfg(target_arch = "x86")] + fn GetFpxRegs<'args, 'output>(self, tid: libc::pid_t) -> Result + where + Self: 'output, + { + self.local_backend.get_fpx_regs(tid).map_err(Error::Local) + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn PtracePeekUser<'args, 'output>(self, addr: usize) -> Result<[u8; crate::PTRACE_DATA_LEN]> + where + Self: 'output, + { + self.local_backend + .ptrace_peekuser(addr) + .map_err(Error::Local) + } + fn ForceProcessReaderKind<'args, 'output>(self, kind: ProcessReaderKind) -> Result<()> + where + Self: 'output, + { + self.local_backend + .force_process_reader_kind(kind) + .map_err(Error::Local) + } + #[cfg(feature = "testing")] + fn FailOneSyscallWith<'args, 'output>(self, errno: core::ffi::c_int) -> Result<()> + where + Self: 'output, + { + self.local_backend.fail_one_syscall_with(errno); + Ok(()) + } + fn Quit<'args, 'output>(self, _args: ()) -> Result<()> + where + Self: 'output, + { + self.quit = true; + Ok(()) + } +} + +fn borrow_with_limit(buf: &mut [u8], limit: usize) -> &mut [u8] { + let len = usize::min(buf.len(), limit); + &mut buf[0..len] +} diff --git a/crates/linux/process-backend/src/remote/executor/mod.rs b/crates/linux/process-backend/src/remote/executor/mod.rs new file mode 100644 index 00000000..f15f5546 --- /dev/null +++ b/crates/linux/process-backend/src/remote/executor/mod.rs @@ -0,0 +1,61 @@ +use super as remote; +use crate::local; +use remote::wire; +use resources::{Pool, Resources}; + +pub use transport::Transport; + +pub mod resources; +pub mod transport; + +mod handler; + +pub type Result = core::result::Result; + +pub fn run( + pid: libc::pid_t, + mut transport: T, + resources: Resources<'_>, +) -> core::result::Result<(), transport::Error> { + let mut executor = Executor { + local_backend: local::Backend::new(pid), + quit: false, + file_readers: Pool::new(resources.file_readers), + dir_readers: Pool::new(resources.dir_readers), + mapped_module_memory_readers: Pool::new(resources.mapped_module_memory_readers), + scratch_buf: resources.scratch_buf, + }; + + while !executor.quit { + wire::serve_request(&mut transport, &mut executor)?; + } + + Ok(()) +} + +struct Executor<'a> { + local_backend: local::Backend, + quit: bool, + file_readers: Pool<'a, resources::FileReader>, + dir_readers: Pool<'a, resources::DirReader>, + mapped_module_memory_readers: Pool<'a, resources::MappedModuleMemoryReader>, + scratch_buf: &'a mut [u8], +} + +#[derive(Debug, thiserror::Error, serde::Deserialize, serde::Serialize)] +pub enum Error { + #[error("an error was returned by a local syscall")] + Local(#[source] local::Error), + #[error("ran out of slots for a new MappedModuleMemoryReader")] + MappedModuleMemoryReaderSlotsExhausted, + #[error("requested an empty MappedModuleMemoryReader slot")] + EmptyMappedModuleMemoryReaderSlotRequested, + #[error("ran out of slots for a new FileReader")] + FileReaderSlotsExhausted, + #[error("requested an empty file slot")] + EmptyFileSlotRequested, + #[error("ran out of slots for a new DirReader")] + DirReaderSlotsExhausted, + #[error("requested an empty directory slot")] + EmptyDirSlotRequested, +} diff --git a/crates/linux/process-backend/src/remote/executor/resources.rs b/crates/linux/process-backend/src/remote/executor/resources.rs new file mode 100644 index 00000000..0fa37ab7 --- /dev/null +++ b/crates/linux/process-backend/src/remote/executor/resources.rs @@ -0,0 +1,93 @@ +use crate::local; + +/// Resources required by the executor. +/// +/// The resources here place hard limits on how many simultaneous file, directory, and module +/// readers can be open, as well as a hard limit on the size of the buffer used by certain +/// operations. +/// +/// See remote/wire.rs `operations!()` macro for notes on buffer sizing +#[derive(Debug)] +pub struct Resources<'a> { + pub file_readers: &'a mut [FileReader], + pub dir_readers: &'a mut [DirReader], + pub mapped_module_memory_readers: &'a mut [MappedModuleMemoryReader], + pub scratch_buf: &'a mut [u8], +} + +#[derive(Debug)] +pub struct FileReader(Option); + +impl FileReader { + pub const fn new_array() -> [Self; N] { + [const { Self(None) }; N] + } +} + +#[derive(Debug)] +pub struct DirReader(Option); + +impl DirReader { + pub const fn new_array() -> [Self; N] { + [const { Self(None) }; N] + } +} + +#[derive(Debug)] +pub struct MappedModuleMemoryReader(Option); + +impl MappedModuleMemoryReader { + pub const fn new_array() -> [Self; N] { + [const { Self(None) }; N] + } +} + +#[derive(Debug)] +pub(super) struct Pool<'a, S>(&'a mut [S]); + +impl<'a, S: Slot> Pool<'a, S> { + pub(super) fn new(s: &'a mut [S]) -> Self { + Self(s) + } + pub(super) fn assign_slot(&mut self, resource: S::Resource) -> Option { + for (idx, slot) in self.0.iter_mut().map(Slot::inner).enumerate() { + if slot.is_none() { + *slot = Some(resource); + return Some(idx); + } + } + None + } + pub(super) fn get_slot_mut(&mut self, idx: usize) -> Option<&mut S::Resource> { + self.0.get_mut(idx)?.inner().as_mut() + } + pub(super) fn free_slot(&mut self, idx: usize) -> Option { + self.0.get_mut(idx)?.inner().take() + } +} + +pub(super) trait Slot { + type Resource; + fn inner(&mut self) -> &mut Option; +} + +impl Slot for FileReader { + type Resource = local::FileReader; + fn inner(&mut self) -> &mut Option { + &mut self.0 + } +} + +impl Slot for DirReader { + type Resource = local::DirReader; + fn inner(&mut self) -> &mut Option { + &mut self.0 + } +} + +impl Slot for MappedModuleMemoryReader { + type Resource = local::MappedModuleMemoryReader; + fn inner(&mut self) -> &mut Option { + &mut self.0 + } +} diff --git a/crates/linux/process-backend/src/remote/executor/transport.rs b/crates/linux/process-backend/src/remote/executor/transport.rs new file mode 100644 index 00000000..8f8dc459 --- /dev/null +++ b/crates/linux/process-backend/src/remote/executor/transport.rs @@ -0,0 +1,21 @@ +use super::remote; +use remote::wire; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "postcard")] +pub use wire::transport::postcard::Executor as Postcard; + +pub trait Transport: core::fmt::Debug { + fn read_request<'args, D: Deserialize<'args>>( + &'args mut self, + ) -> core::result::Result; + fn write_response(&mut self, resp: S) -> core::result::Result<(), Error>; +} + +#[derive(Debug, thiserror::Error, Deserialize, Serialize)] +pub enum Error { + #[error("an error occurred deserializing the request")] + DeserializeRequest(#[source] wire::transport::DeserializeError), + #[error("an error occurred serializing the response")] + SerializeResponse(#[source] wire::transport::SerializeError), +} diff --git a/crates/linux/process-backend/src/remote/io/mod.rs b/crates/linux/process-backend/src/remote/io/mod.rs new file mode 100644 index 00000000..b43b4f4c --- /dev/null +++ b/crates/linux/process-backend/src/remote/io/mod.rs @@ -0,0 +1,22 @@ +use core::ffi::c_int; + +#[cfg(feature = "unix_stream")] +pub use unix_stream::UnixStream; + +#[cfg(feature = "unix_stream")] +mod unix_stream; + +pub type Result = core::result::Result; + +pub trait Io: core::fmt::Debug { + fn read_exact(&mut self, buf: &mut [u8]) -> Result<()>; + fn write_all(&mut self, buf: &[u8]) -> Result<()>; +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, thiserror::Error)] +pub enum Error { + #[error("an OS error occurred. Errno: {0}")] + OsError(c_int), + #[error("unexpected end-of-file reached")] + UnexpectedEof, +} diff --git a/crates/linux/process-backend/src/remote/io/unix_stream.rs b/crates/linux/process-backend/src/remote/io/unix_stream.rs new file mode 100644 index 00000000..0bc373da --- /dev/null +++ b/crates/linux/process-backend/src/remote/io/unix_stream.rs @@ -0,0 +1,67 @@ +use super as io; +use crate::wrapper::{OwnedFd, errno}; +use core::ffi::c_int; +use io::{Error, Result}; + +#[derive(Debug)] +pub struct UnixStream(OwnedFd); + +impl UnixStream { + pub fn pair() -> Result<(UnixStream, UnixStream)> { + let mut fds = [0; 2]; + let rv = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }; + if rv == -1 { + return Err(Error::OsError(errno())); + } + let stream0 = unsafe { Self::from_raw_fd(fds[0]) }; + let stream1 = unsafe { Self::from_raw_fd(fds[1]) }; + Ok((stream0, stream1)) + } + /// # Safety + /// + /// 'fd' must be a valid UNIX stream socket + pub unsafe fn from_raw_fd(fd: c_int) -> Self { + unsafe { Self(OwnedFd::new(fd)) } + } + pub fn as_raw_fd(&self) -> c_int { + self.0.as_raw_fd() + } + fn read(&mut self, buf: &mut [u8]) -> Result { + let rv = unsafe { libc::read(self.0.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; + if rv == -1 { + return Err(Error::OsError(errno())); + } + let bytes_read = usize::try_from(rv).unwrap(); + Ok(bytes_read) + } + fn write(&mut self, buf: &[u8]) -> Result { + let rv = unsafe { libc::write(self.0.as_raw_fd(), buf.as_ptr().cast(), buf.len()) }; + if rv == -1 { + return Err(Error::OsError(errno())); + } + let bytes_written = usize::try_from(rv).unwrap(); + Ok(bytes_written) + } +} + +impl io::Io for UnixStream { + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> { + while !buf.is_empty() { + let bytes_read = self.read(buf)?; + if bytes_read == 0 { + return Err(Error::UnexpectedEof); + } + buf = &mut buf[bytes_read..]; + } + Ok(()) + } + + fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { + while !buf.is_empty() { + let bytes_written = self.write(buf)?; + assert_ne!(bytes_written, 0); + buf = &buf[bytes_written..]; + } + Ok(()) + } +} diff --git a/crates/linux/process-backend/src/remote/mod.rs b/crates/linux/process-backend/src/remote/mod.rs new file mode 100644 index 00000000..3346d08f --- /dev/null +++ b/crates/linux/process-backend/src/remote/mod.rs @@ -0,0 +1,5 @@ +pub mod backend; +pub mod executor; +pub mod io; + +mod wire; diff --git a/crates/linux/process-backend/src/remote/wire/cstr_with_null.rs b/crates/linux/process-backend/src/remote/wire/cstr_with_null.rs new file mode 100644 index 00000000..d6179e37 --- /dev/null +++ b/crates/linux/process-backend/src/remote/wire/cstr_with_null.rs @@ -0,0 +1,72 @@ +use core::ffi::CStr; + +/// A version of &CStr that is serialized and deserialized with null terminator +/// +/// The default Serde `Serialize` for this serializes it without the NULL terminator, and as a +/// result `Deserialize` is only implemented for `CString` but not `&CStr` (since you can't have +/// a `&CStr` with no null terminator. +/// +/// This is a wrapper around &CStr that serializes and deserializes the null terminator - which is +/// what we need for directly making syscalls. +#[derive(Debug)] +#[repr(transparent)] +pub(crate) struct CStrWithNull<'a>(&'a CStr); + +impl<'a> From> for &'a CStr { + fn from(value: CStrWithNull<'a>) -> Self { + value.0 + } +} + +impl<'a> From<&'a CStr> for CStrWithNull<'a> { + fn from(value: &'a CStr) -> Self { + Self(value) + } +} + +impl<'a> core::ops::Deref for CStrWithNull<'a> { + type Target = CStr; + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a> serde::Serialize for CStrWithNull<'a> { + fn serialize(&self, serializer: S) -> core::result::Result + where + S: serde::Serializer, + { + serializer.serialize_bytes(self.0.to_bytes_with_nul()) + } +} + +impl<'de: 'a, 'a> serde::Deserialize<'de> for CStrWithNull<'a> { + fn deserialize(deserializer: D) -> core::result::Result + where + D: serde::Deserializer<'de>, + { + struct Visitor<'a>(core::marker::PhantomData<&'a [u8]>); + + impl<'de: 'a, 'a> serde::de::Visitor<'de> for Visitor<'a> { + type Value = CStrWithNull<'a>; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + formatter, + "a null-terminated byte slice borrowed from the Deserializer" + ) + } + + fn visit_borrowed_bytes(self, v: &'de [u8]) -> core::result::Result + where + E: serde::de::Error, + { + CStr::from_bytes_with_nul(v).map(CStrWithNull).map_err(|_| { + serde::de::Error::invalid_value(serde::de::Unexpected::Bytes(v), &self) + }) + } + } + + deserializer.deserialize_bytes(Visitor(core::marker::PhantomData)) + } +} diff --git a/crates/linux/process-backend/src/remote/wire/mod.rs b/crates/linux/process-backend/src/remote/wire/mod.rs new file mode 100644 index 00000000..fd437e98 --- /dev/null +++ b/crates/linux/process-backend/src/remote/wire/mod.rs @@ -0,0 +1,189 @@ +use super as remote; +use crate::{ProcessReaderKind, regs::*, wrapper::Stat}; +use remote::{backend, executor}; +use serde::{Deserialize, Serialize}; + +pub(crate) use cstr_with_null::CStrWithNull; + +pub(crate) mod transport; + +mod cstr_with_null; + +pub(crate) trait Operation { + type Args<'args>: Deserialize<'args> + Serialize; + type Output<'output>: Deserialize<'output> + Serialize; + fn request<'output, T>( + transport: &'output mut T, + args: Self::Args<'_>, + ) -> Result>, backend::transport::Error> + where + T: backend::Transport; +} + +macro_rules! operations({ + $( + $(#[cfg($($cfg_tt: tt)*)])? + $name: ident($arg: ty) -> $output: ty, + )* +} => { + pub(crate) mod ops { + use super::*; + + $( + + $(#[cfg($($cfg_tt)*)])? + pub(crate) enum $name {} + + $(#[cfg($($cfg_tt)*)])? + impl Operation for $name { + type Args<'args> = $arg; + type Output<'output> = $output; + fn request<'output, T>( + transport: &'output mut T, + args: Self::Args<'_>, + ) -> Result>, backend::transport::Error> + where + T: backend::Transport, + { + transport.send_request::<'output, Request, executor::Result>>( + Request::$name(args), + ) + } + } + + )* + } + + pub(crate) trait HandlesRequests { + $( + $(#[cfg($($cfg_tt)*)])? + #[allow(non_snake_case)] + fn $name<'args, 'output>(self, args: $arg) -> executor::Result<$output> + where + Self: 'output; + )* + } + + pub(crate) fn serve_request( + transport: &mut T, + handler: H, + ) -> Result<(), executor::transport::Error> + where + T: executor::Transport, + H: HandlesRequests, + { + let request = transport.read_request::<'_, Request<'_>>()?; + match request { + $( + $(#[cfg($($cfg_tt)*)])? + Request::$name(args) => { + let response = HandlesRequests::$name(handler, args); + transport.write_response::::Output<'_>>>( + response, + ) + } + )* + } + } + + #[derive(Debug, Deserialize, Serialize)] + enum Request<'args> { + $( + $(#[cfg($($cfg_tt)*)])? + $name(#[serde(borrow)] ::Args<'args>), + )* + } +}); + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct FileReaderArgs { + pub(crate) handle: usize, + pub(crate) requested_len: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct ProcessReaderReadAtArgs { + pub(crate) address: usize, + pub(crate) requested_len: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct MappedModuleMemoryReaderOpenArgs<'args> { + #[serde(borrow)] + pub(crate) path: CStrWithNull<'args>, + pub(crate) offset: u64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct MappedModuleMemoryReaderReadAtArgs { + pub(crate) handle: usize, + pub(crate) offset: usize, + pub(crate) requested_len: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct ReadLinkArgs<'args> { + #[serde(borrow)] + pub(crate) path: CStrWithNull<'args>, + pub(crate) requested_len: usize, +} + +// Notes on buffer sizing: +// +// The size of buffer-based arguments in these operations are limited by the +// `executor::Transport` argument buffer, the `backend::Transport` output buffer, and +// the `Executor` scratch buffer. +// +// Currently: +// +// The length of the argument buffer limits the maximum path length for: +// read_file(), read_dir(), map_module_into_memory(), stat_file(), read_link() +// +// Sizing the Executor scratch buffer larger than the Backend output buffer is a bad idea - it +// means the Executor could request more bytes than the Backend is capable of holding, causing a +// `BufferTooSmall` error. Sizing the scratch buffer smaller is fine, but then the output buffer +// would never be totally utilized. +// +// So, in practice, the scratch buffer and output buffer should be the same size. +// +// The length of both limits the maximum readable path length for: +// DirReader::read(), read_link() +// +// TL;DR +// +// The argument buffer should be the maximum length of a path you'd ever request for any operation +// that takes a path. +// +// The output buffer should be the maximum length of any directory name you'd expect returned +// by read_dir() and any path you'd ever expect returned by read_link(). + +operations! { + GetPid(()) -> libc::pid_t, + ProcessReaderReadAt(ProcessReaderReadAtArgs) -> &'output [u8], + StopProcess(()) -> (), + ContinueProcess(()) -> (), + SuspendThread(libc::pid_t) -> (), + ResumeThread(libc::pid_t) -> (), + MappedModuleMemoryReaderOpen(MappedModuleMemoryReaderOpenArgs<'args>) -> usize, + MappedModuleMemoryReaderReadAt(MappedModuleMemoryReaderReadAtArgs) -> &'output [u8], + MappedModuleMemoryReaderLen(usize) -> usize, + MappedModuleMemoryReaderClose(usize) -> (), + StatFile(CStrWithNull<'args>) -> Stat, + FileReaderOpen(CStrWithNull<'args>) -> usize, + FileReaderRead(FileReaderArgs) -> &'output [u8], + FileReaderClose(usize) -> (), + DirReaderOpen(CStrWithNull<'args>) -> usize, + DirReaderRead(usize) -> Option<&'output [u8]>, + DirReaderClose(usize) -> (), + ReadLink(ReadLinkArgs<'args>) -> &'output [u8], + GetGenRegs(libc::pid_t) -> GenRegs, + GetFpRegs(libc::pid_t) -> FpRegs, + #[cfg(target_arch = "x86")] + GetFpxRegs(libc::pid_t) -> FpxRegs, + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + PtracePeekUser(usize) -> [u8; crate::PTRACE_DATA_LEN], + ForceProcessReaderKind(ProcessReaderKind) -> (), + #[cfg(feature = "testing")] + FailOneSyscallWith(core::ffi::c_int) -> (), + Quit(()) -> (), +} diff --git a/crates/linux/process-backend/src/remote/wire/transport/mod.rs b/crates/linux/process-backend/src/remote/wire/transport/mod.rs new file mode 100644 index 00000000..f23e0b90 --- /dev/null +++ b/crates/linux/process-backend/src/remote/wire/transport/mod.rs @@ -0,0 +1,25 @@ +use super::remote; +use remote::io; + +#[cfg(feature = "postcard")] +pub(crate) mod postcard; + +#[derive(Debug, thiserror::Error, serde::Deserialize, serde::Serialize)] +pub enum SerializeError { + #[cfg(feature = "postcard")] + #[error("a Postcard-specific serialize error occurred")] + Postcard(#[source] postcard::Error), + #[error("an I/O occurred during serialization")] + Io(#[source] io::Error), +} + +#[derive(Debug, thiserror::Error, serde::Deserialize, serde::Serialize)] +pub enum DeserializeError { + #[cfg(feature = "postcard")] + #[error("a Postcard-specific deserialize error occurred")] + Postcard(#[source] postcard::Error), + #[error("an I/O occurred during deserialization")] + Io(#[source] io::Error), + #[error("the I/O buffer was too small")] + BufferTooSmall, +} diff --git a/crates/linux/process-backend/src/remote/wire/transport/postcard.rs b/crates/linux/process-backend/src/remote/wire/transport/postcard.rs new file mode 100644 index 00000000..e8785775 --- /dev/null +++ b/crates/linux/process-backend/src/remote/wire/transport/postcard.rs @@ -0,0 +1,289 @@ +use super as transport; +use super::remote; +use core::{marker::PhantomData, ops::Range}; +use remote::{backend, executor, io}; +use serde::{Deserialize, Serialize}; +use transport::{DeserializeError, SerializeError}; + +pub use postcard::Error; + +// Postcard requires an extra scratch buffer for deserializing a few fixed-size types. +// f64 is the largest type it does this for. +const MAX_TEMP_LEN: usize = size_of::(); + +/// The backend side of a Postcard transport +/// +/// See remote/wire.rs `operations!()` macro for notes on buffer sizing +#[derive(Debug)] +pub struct Backend { + io: Io, + output_buf: Buf, +} + +impl Backend { + pub fn new(io: Io, output_buf: Buf) -> Self { + Self { io, output_buf } + } +} + +impl backend::Transport for Backend +where + Io: io::Io, + Buf: AsMut<[u8]> + core::fmt::Debug, +{ + fn send_request<'output, Req: Serialize, Resp: Deserialize<'output>>( + &'output mut self, + req: Req, + ) -> Result { + serialize_to_io(&mut self.io, req).map_err(backend::transport::Error::SerializeRequest)?; + deserialize_from_io(&mut self.io, self.output_buf.as_mut()) + .map_err(backend::transport::Error::DeserializeResponse) + } +} + +/// The executor side of a Postcard transport +/// +/// See remote/wire.rs `operations!()` macro for notes on buffer sizing +#[derive(Debug)] +pub struct Executor { + io: Io, + args_buf: Buf, +} + +impl Executor { + pub fn new(io: Io, args_buf: Buf) -> Self { + Self { io, args_buf } + } +} + +impl executor::Transport for Executor +where + Io: io::Io, + Buf: AsMut<[u8]> + core::fmt::Debug, +{ + fn read_request<'args, D: Deserialize<'args>>( + &'args mut self, + ) -> Result { + deserialize_from_io(&mut self.io, self.args_buf.as_mut()) + .map_err(executor::transport::Error::DeserializeRequest) + } + + fn write_response(&mut self, resp: S) -> Result<(), executor::transport::Error> { + serialize_to_io(&mut self.io, resp).map_err(executor::transport::Error::SerializeResponse) + } +} + +fn serialize_to_io(io: &mut Io, s: S) -> Result<(), SerializeError> { + let mut serializer = postcard::Serializer { + output: SerFlavor::new(io), + }; + match s.serialize(&mut serializer) { + Ok(()) => Ok(()), + Err(e) => { + let e = postcard::ser_flavors::Flavor::finalize(serializer.output) + .unwrap() + .unwrap_or(SerializeError::Postcard(e)); + Err(e) + } + } +} + +fn deserialize_from_io<'output, Io: io::Io, D: Deserialize<'output>>( + io: &'output mut Io, + buf: &'output mut [u8], +) -> Result { + let mut deserializer = postcard::Deserializer::from_flavor(DeFlavor::new(io, buf)); + match D::deserialize(&mut deserializer) { + Ok(d) => Ok(d), + Err(e) => { + let e = deserializer + .finalize() + .unwrap() + .unwrap_or(DeserializeError::Postcard(e)); + Err(e) + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// +// So... What's the deal with us returning `postcard::Error::NotYetImplemented` below? +// =================================================================================== +// +// The Postcard wire format is awesome, but the way it propagates errors is... Less awesome. +// (To be fair - the authors are aware and are trying to fix this in v2). +// +// We need a way to smuggle errors past Postcard's `Error` unit enum, so when an error +// occurs we record the real cause in a field of our flavor structs and return +// `NotYetImplemented` because it's an error Postcard would never actually return (since we don't +// use any of its unimplemented functionality). +// +// On the other end, after the Serde call completes, we call `finalize()` to unwrap our flavor +// object, allowing us to read the real error that occurred (if any). +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +struct SerFlavor<'io, Io> { + io: &'io mut Io, + error: Option, +} + +impl<'io, Io> SerFlavor<'io, Io> { + pub fn new(io: &'io mut Io) -> Self { + Self { io, error: None } + } +} + +impl<'io, Io: io::Io> postcard::ser_flavors::Flavor for SerFlavor<'io, Io> { + type Output = Option; + + fn try_push(&mut self, data: u8) -> postcard::Result<()> { + match self.io.write_all(&[data]) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Some(SerializeError::Io(e)); + Err(postcard::Error::NotYetImplemented) + } + } + } + + fn finalize(self) -> postcard::Result { + Ok(self.error) + } + + fn try_extend(&mut self, data: &[u8]) -> postcard::Result<()> { + match self.io.write_all(data) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Some(SerializeError::Io(e)); + Err(postcard::Error::NotYetImplemented) + } + } + } +} + +struct DeFlavor<'io, 'buf, Io> { + inner: DeFlavorInner<'io, 'buf, Io>, + error: Option, +} + +impl<'io, 'buf, Io> DeFlavor<'io, 'buf, Io> { + fn new(io: &'io mut Io, buf: &'buf mut [u8]) -> Self { + Self { + inner: DeFlavorInner { + io, + alloc_buf: BumpAllocator::new(buf), + temp_buf: [0u8; MAX_TEMP_LEN], + }, + error: None, + } + } + fn with_inner<'a, F, T>(&'a mut self, f: F) -> postcard::Result + where + F: FnOnce(&'a mut DeFlavorInner<'io, 'buf, Io>) -> Result, + { + let (inner, error) = (&mut self.inner, &mut self.error); + + match f(inner) { + Ok(t) => Ok(t), + Err(e) => { + *error = Some(e); + Err(Error::NotYetImplemented) + } + } + } +} + +impl<'io, 'buf, Io: io::Io> postcard::de_flavors::Flavor<'buf> for DeFlavor<'io, 'buf, Io> +where + Self: 'buf, +{ + type Remainder = Option; + + type Source = (); + + fn pop(&mut self) -> postcard::Result { + self.with_inner(|inner| inner.pop()) + } + + fn try_take_n(&mut self, ct: usize) -> postcard::Result<&'buf [u8]> { + self.with_inner(|inner| inner.try_take_n(ct)) + } + + fn finalize(self) -> postcard::Result { + Ok(self.error) + } + + fn try_take_n_temp<'a>(&'a mut self, ct: usize) -> postcard::Result<&'a [u8]> + where + 'buf: 'a, + { + self.with_inner(|inner| inner.try_take_n_temp(ct)) + } +} + +struct DeFlavorInner<'io, 'buf, Io> { + io: &'io mut Io, + alloc_buf: BumpAllocator<'buf>, + temp_buf: [u8; MAX_TEMP_LEN], +} + +impl<'io, 'buf, Io: io::Io> DeFlavorInner<'io, 'buf, Io> +where + Self: 'buf, +{ + fn pop(&mut self) -> Result { + let mut buf = [0u8]; + self.io.read_exact(&mut buf).map_err(DeserializeError::Io)?; + Ok(buf[0]) + } + + fn try_take_n(&mut self, ct: usize) -> Result<&'buf [u8], DeserializeError> { + let buf = self + .alloc_buf + .allocate(ct) + .ok_or(DeserializeError::BufferTooSmall)?; + self.io.read_exact(buf).map_err(DeserializeError::Io)?; + Ok(buf) + } + + fn try_take_n_temp<'a>(&'a mut self, ct: usize) -> Result<&'a [u8], DeserializeError> + where + 'buf: 'a, + { + let buf = self + .temp_buf + .get_mut(0..ct) + .ok_or(DeserializeError::BufferTooSmall)?; + self.io.read_exact(buf).map_err(DeserializeError::Io)?; + Ok(buf) + } +} + +struct BumpAllocator<'buf> { + start: *mut u8, + end: *mut u8, + phantom: PhantomData<&'buf mut [u8]>, +} + +impl<'buf> BumpAllocator<'buf> { + pub fn new(buf: &'buf mut [u8]) -> Self { + let Range { start, end } = buf.as_mut_ptr_range(); + Self { + start, + end, + phantom: PhantomData, + } + } + pub fn allocate(&mut self, n: usize) -> Option<&'buf mut [u8]> { + let bytes_left = (self.end as usize) - (self.start as usize); + if bytes_left < n { + return None; + } + unsafe { + let buf = core::slice::from_raw_parts_mut(self.start, n); + self.start = self.start.add(n); + Some(buf) + } + } +} diff --git a/crates/linux/process-backend/tests/basic.rs b/crates/linux/process-backend/tests/basic.rs new file mode 100644 index 00000000..814c61a1 --- /dev/null +++ b/crates/linux/process-backend/tests/basic.rs @@ -0,0 +1,99 @@ +#![cfg(any(target_os = "linux", target_os = "android"))] + +use process_backend::{Backend as _, DirReader, remote}; +use remote::{backend::Backend, executor, io::UnixStream}; +use std::{ + collections::HashSet, + ffi::CString, + fs::remove_file, + os::unix::{ffi::OsStringExt as _, fs::symlink}, + thread, +}; + +const MAX_INPUT_LEN: usize = 32; +const MAX_OUTPUT_LEN: usize = 65536; +const MAX_FILE_READERS: usize = 5; +const MAX_DIR_READERS: usize = 5; +const MAX_MAPPED_MODULE_MEMORY_READERS: usize = 5; + +#[test] +fn basic() { + let (backend_io, executor_io) = UnixStream::pair().unwrap(); + + let executor_thread = thread::spawn(move || { + let pid = unsafe { libc::getpid() }; + let mut args_buf = [0u8; MAX_INPUT_LEN]; + let executor_transport = executor::transport::Postcard::new(executor_io, &mut args_buf); + let mut file_readers = executor::resources::FileReader::new_array::(); + let mut dir_readers = executor::resources::DirReader::new_array::(); + let mut mapped_module_memory_readers = + executor::resources::MappedModuleMemoryReader::new_array::< + MAX_MAPPED_MODULE_MEMORY_READERS, + >(); + + let mut scratch_buf = [0u8; MAX_OUTPUT_LEN]; + let resources = executor::resources::Resources { + file_readers: &mut file_readers, + dir_readers: &mut dir_readers, + mapped_module_memory_readers: &mut mapped_module_memory_readers, + scratch_buf: &mut scratch_buf, + }; + executor::run(pid, executor_transport, resources) + }); + + const FAKE_TARGET: &str = "/foo/bar"; + + let temp_dir = std::env::temp_dir(); + + let link_path = temp_dir.join("process-backend.test.link"); + + if link_path.symlink_metadata().is_ok_and(|m| m.is_symlink()) { + remove_file(&link_path).unwrap(); + } + + symlink(FAKE_TARGET, &link_path).unwrap(); + + let output_buf = vec![0u8; MAX_OUTPUT_LEN]; + let backend_transport = remote::backend::transport::Postcard::new(backend_io, output_buf); + let backend = Backend::new(backend_transport); + + let mut link = vec![0u8; MAX_OUTPUT_LEN]; + + let link_path_cstr = CString::new(link_path.into_os_string().into_vec()).unwrap(); + + let len = backend.read_link(&link_path_cstr, &mut link).unwrap(); + link.truncate(len); + let link = String::from_utf8(link).unwrap(); + + assert_eq!(link, FAKE_TARGET); + + let stat = backend.stat_file(c"/").unwrap(); + + // Should be pretty safe for testing -- The root directory should definitely have rwx for + // owner and rx for everyone else. Group should at-least have r+x. + assert!(stat.st_mode & 0o757 == 0o755); + + // A set of names we'd generally expect to find in the root directory + let mut unencountered_dirs = HashSet::from(["bin", "dev", "etc", "lib", "opt", "tmp", "var"]); + + let mut output_buf = vec![0u8; MAX_OUTPUT_LEN]; + let mut dir_reader = backend.read_dir(c"/").unwrap(); + loop { + let len = dir_reader.read_next_name(&mut output_buf).unwrap(); + let name = &output_buf[0..len]; + if name.is_empty() { + break; + } + let Ok(name_str) = str::from_utf8(&name) else { + continue; + }; + unencountered_dirs.remove(name_str); + } + + assert!(unencountered_dirs.is_empty()); + + drop(dir_reader); + drop(backend); + + executor_thread.join().unwrap().unwrap(); +} diff --git a/src/bin/test.rs b/src/bin/test.rs index 36721d3d..d90f1813 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -10,8 +10,9 @@ mod linux { super::*, error_graph::ErrorList, minidump_writer::{ - LINUX_GATE_LIBRARY_NAME, ProcessReaderKind, + LINUX_GATE_LIBRARY_NAME, Pid, ProcessReaderKind, minidump_writer::{MinidumpWriter, MinidumpWriterConfig}, + remote_process_inspection::{backend, executor, io::UnixStream}, }, std::ptr, }; @@ -39,18 +40,20 @@ mod linux { fn test_setup() -> Result<()> { let ppid = getppid(); + let mut provider = RemoteConfigProvider::new(ppid, ppid); fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? + provider.provide().build_for_testing(&mut soft_errors)? ); Ok(()) } fn test_thread_list() -> Result<()> { let ppid = getppid(); + let mut provider = RemoteConfigProvider::new(ppid, ppid); let dumper = fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? + provider.provide().build_for_testing(&mut soft_errors)? ); test!(!dumper.threads.is_empty(), "No threads"); test!( @@ -73,9 +76,10 @@ mod linux { use minidump_writer::process_reader::ProcessReader; let ppid = getppid(); + let mut provider = RemoteConfigProvider::new(ppid, ppid); let mut dumper = fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? + provider.provide().build_for_testing(&mut soft_errors)? ); // We support 3 different methods of reading memory from another @@ -156,9 +160,10 @@ mod linux { fn test_find_mappings(addr1: usize, addr2: usize) -> Result<()> { let ppid = getppid(); + let mut provider = RemoteConfigProvider::new(ppid, ppid); let dumper = fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? + provider.provide().build_for_testing(&mut soft_errors)? ); dumper .find_mapping(addr1) @@ -177,9 +182,10 @@ mod linux { let exe_link = format!("/proc/{ppid}/exe"); let exe_name = std::fs::read_link(exe_link)?.into_os_string(); + let mut provider = RemoteConfigProvider::new(ppid, ppid); let dumper = fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? + provider.provide().build_for_testing(&mut soft_errors)? ); let exe_mapping = dumper @@ -198,9 +204,10 @@ mod linux { fn test_merged_mappings(path: String, mapped_mem: usize, mem_size: usize) -> Result<()> { // Now check that PtraceDumper interpreted the mappings properly. + let mut provider = RemoteConfigProvider::new(getppid(), getppid()); let dumper = fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(getppid(), getppid()).build_for_testing(&mut soft_errors)? + provider.provide().build_for_testing(&mut soft_errors)? ); let mut mapping_count = 0; for map in &dumper.mappings { @@ -223,9 +230,10 @@ mod linux { fn test_linux_gate_mapping_id() -> Result<()> { let ppid = getppid(); + let mut provider = RemoteConfigProvider::new(ppid, ppid); let dumper = fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? + provider.provide().build_for_testing(&mut soft_errors)? ); let linux_gate = dumper .mappings @@ -241,9 +249,10 @@ mod linux { fn test_mappings_include_linux_gate() -> Result<()> { let ppid = getppid(); + let mut provider = RemoteConfigProvider::new(ppid, ppid); let dumper = fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? + provider.provide().build_for_testing(&mut soft_errors)? ); let linux_gate_loc = dumper.auxv.get_linux_gate_address().unwrap(); test!(linux_gate_loc != 0, "linux_gate_loc == 0"); @@ -443,6 +452,65 @@ mod linux { _ => Err("Unknown test option".into()), } } + + pub struct RemoteConfigProvider { + config: Option, + join_handle: Option>, + } + + impl RemoteConfigProvider { + pub fn new(process_id: Pid, blamed_thread: Pid) -> RemoteConfigProvider { + const TEST_BUFFER_LEN: usize = 65536; + const MAX_FILE_READERS: usize = 5; + const MAX_DIR_READERS: usize = 5; + const MAX_MAPPED_MODULE_MEMORY_READERS: usize = 5; + + let (executor_io, backend_io) = UnixStream::pair().unwrap(); + let join_handle = std::thread::spawn(move || { + let mut args_buf = vec![0u8; TEST_BUFFER_LEN]; + let transport = executor::transport::Postcard::new(executor_io, &mut args_buf); + + let mut file_readers = + Box::new(executor::resources::FileReader::new_array::()); + let mut dir_readers = + Box::new(executor::resources::DirReader::new_array::()); + let mut mapped_module_memory_readers = + Box::new(executor::resources::MappedModuleMemoryReader::new_array::< + MAX_MAPPED_MODULE_MEMORY_READERS, + >()); + + let mut scratch_buf = vec![0u8; TEST_BUFFER_LEN]; + let resources = executor::resources::Resources { + file_readers: file_readers.as_mut_slice(), + dir_readers: dir_readers.as_mut_slice(), + mapped_module_memory_readers: mapped_module_memory_readers.as_mut_slice(), + scratch_buf: &mut scratch_buf, + }; + executor::run(process_id, transport, resources).unwrap(); + }); + + let output_buf = vec![0u8; TEST_BUFFER_LEN]; + let transport = backend::transport::Postcard::new(backend_io, output_buf); + let mut config = MinidumpWriterConfig::new(process_id, blamed_thread); + config.set_remote_transport(transport); + RemoteConfigProvider { + config: Some(config), + join_handle: Some(join_handle), + } + } + } + + impl RemoteConfigProvider { + pub fn provide(&mut self) -> MinidumpWriterConfig { + self.config.take().unwrap() + } + } + + impl Drop for RemoteConfigProvider { + fn drop(&mut self) { + self.join_handle.take().unwrap().join().unwrap(); + } + } } #[cfg(target_os = "windows")] diff --git a/src/linux/minidump_writer/mod.rs b/src/linux/minidump_writer/mod.rs index 8b85e258..56601cc0 100644 --- a/src/linux/minidump_writer/mod.rs +++ b/src/linux/minidump_writer/mod.rs @@ -8,6 +8,7 @@ use { dumper_cpu_info::CpuInfoError, maps_reader::{MappingInfo, MappingList, MapsReaderError}, process_inspection::{self, ProcessInspector, process_reader::CopyFromProcessError}, + remote_process_inspection, serializers::*, thread_info::{ThreadInfo, ThreadInfoError}, }, @@ -201,6 +202,19 @@ impl MinidumpWriterConfig { self } + /// Set the transport used to communicate with the remote executor + /// + /// The [`MinidumpWriter`] is created in local mode by default. Specifying a remote Transport + /// changes it to remote mode, where it will forward all process inspection syscalls to a + /// remote process for execution. + pub fn set_remote_transport( + &mut self, + transport: T, + ) -> &mut Self { + self.process_inspector = process_inspection::remote(transport); + self + } + /// Generates a minidump and writes to the destination provided. Returns the in-memory /// version of the minidump as well. pub fn write(self, destination: &mut (impl Write + Seek)) -> Result, WriterError> { diff --git a/src/linux/mod.rs b/src/linux/mod.rs index 4141d2a7..cfe07e09 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -3,7 +3,10 @@ pub use { maps_reader::LINUX_GATE_LIBRARY_NAME, - process_inspection::{Error as BackendError, ProcessReaderKind, process_reader}, + process_inspection::{ + Error as BackendError, ProcessReaderKind, process_reader, + remote_api as remote_process_inspection, + }, }; pub mod app_memory; diff --git a/src/linux/process_inspection/mod.rs b/src/linux/process_inspection/mod.rs index 97ea5bbb..3799a2fa 100644 --- a/src/linux/process_inspection/mod.rs +++ b/src/linux/process_inspection/mod.rs @@ -1,7 +1,7 @@ use super as linux; use crate::module_reader::{ModuleMemoryReadError, ReadError, ReadModuleMemory}; use linux::maps_reader; -use process_backend::{ProcessReader as _, Stat, local, regs::*}; +use process_backend::{ProcessReader as _, Stat, local, regs::*, remote}; use process_reader::{CopyFromProcessError, ProcessReader, ProcessReaderBackend}; use std::{ borrow::Cow, @@ -20,6 +20,13 @@ pub use process_backend::ProcessReaderKind; pub mod process_reader; +pub mod remote_api { + pub mod backend { + pub use process_backend::remote::backend::{Transport, transport}; + } + pub use process_backend::remote::{executor, io}; +} + pub(crate) type Result = core::result::Result; // These are both arbitrary choices and may need to be tweaked @@ -31,6 +38,13 @@ pub(crate) fn local(pid: libc::pid_t) -> Box { Box::new(local::Backend::new(pid)) } +pub(crate) fn remote<'t, T: remote::backend::Transport + 't>( + transport: T, +) -> Box { + set_process_backend_drop_fail_handler(); + Box::new(remote::backend::Backend::new(transport)) +} + pub trait ProcessInspector: core::fmt::Debug { fn process_reader<'a>(&'a self) -> ProcessReader<'a>; fn pid(&self) -> Result; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 74fc0f42..1fc000a5 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -258,13 +258,11 @@ pub use linux::*; #[cfg(any(target_os = "linux", target_os = "android"))] #[allow(unused)] mod linux { - use { - minidump_writer::{ - CrashContextExt, Pid, - module_reader::{self, ModuleMemoryReadError, ReadModuleMemory}, - }, - std::borrow::Cow, - }; + use minidump_writer::module_reader::{self, ModuleMemoryReadError, ReadModuleMemory}; + use minidump_writer::remote_process_inspection::{backend, executor, io::UnixStream}; + use minidump_writer::{CrashContextExt, Pid, minidump_writer::MinidumpWriterConfig}; + use std::borrow::Cow; + pub struct SliceModuleMemoryReader<'a>(pub &'a [u8]); impl<'a> ReadModuleMemory for SliceModuleMemoryReader<'a> { @@ -319,6 +317,67 @@ mod linux { }, } } + + pub struct RemoteConfigProvider { + config: Option, + join_handle: Option>, + } + + impl RemoteConfigProvider { + pub fn new(process_id: Pid, blamed_thread: Pid) -> RemoteConfigProvider { + const TEST_BUFFER_LEN: usize = 65536; + const MAX_PATH_LEN: usize = 65536; + const MAX_DIRECTORY_NAME_LENGTH: usize = 256; + const MAX_FILE_READERS: usize = 5; + const MAX_DIR_READERS: usize = 5; + const MAX_MAPPED_MODULE_MEMORY_READERS: usize = 5; + + let (executor_io, backend_io) = UnixStream::pair().unwrap(); + let join_handle = std::thread::spawn(move || { + let mut args_buf = vec![0u8; TEST_BUFFER_LEN]; + let transport = executor::transport::Postcard::new(executor_io, &mut args_buf); + + let mut file_readers = + Box::new(executor::resources::FileReader::new_array::()); + let mut dir_readers = + Box::new(executor::resources::DirReader::new_array::()); + let mut mapped_module_memory_readers = + Box::new(executor::resources::MappedModuleMemoryReader::new_array::< + MAX_MAPPED_MODULE_MEMORY_READERS, + >()); + + let mut scratch_buf = vec![0u8; TEST_BUFFER_LEN]; + let mut resources = executor::resources::Resources { + file_readers: file_readers.as_mut_slice(), + dir_readers: dir_readers.as_mut_slice(), + mapped_module_memory_readers: mapped_module_memory_readers.as_mut_slice(), + scratch_buf: &mut scratch_buf, + }; + executor::run(process_id, transport, resources).unwrap(); + }); + + let mut output_buf = vec![0u8; TEST_BUFFER_LEN]; + let transport = backend::transport::Postcard::new(backend_io, output_buf); + let mut config = MinidumpWriterConfig::new(process_id, blamed_thread); + config.set_remote_transport(transport); + RemoteConfigProvider { + config: Some(config), + join_handle: Some(join_handle), + } + } + pub fn provide(&mut self) -> MinidumpWriterConfig { + self.config.take().unwrap() + } + pub fn borrow_mut(&mut self) -> &mut MinidumpWriterConfig { + self.config.as_mut().unwrap() + } + } + + impl Drop for RemoteConfigProvider { + fn drop(&mut self) { + self.join_handle.take().unwrap().join().unwrap(); + } + } } fn print_stdio(bytes: &[u8]) { diff --git a/tests/linux_minidump_writer.rs b/tests/linux_minidump_writer.rs index 03104c50..0d7d6f87 100644 --- a/tests/linux_minidump_writer.rs +++ b/tests/linux_minidump_writer.rs @@ -9,7 +9,7 @@ use { CrashContextExt, Pid, app_memory::AppMemory, maps_reader::{MappingEntry, MappingInfo, SystemMappingInfo}, - minidump_writer::{MinidumpWriter, MinidumpWriterConfig, errors::WriterError}, + minidump_writer::{MinidumpWriter, errors::WriterError}, module_reader::{self}, }, procfs_core::process::MMPermissions, @@ -31,11 +31,11 @@ enum Context { } impl Context { - pub fn minidump_writer(&self, pid: Pid) -> MinidumpWriterConfig { - let mut mw = MinidumpWriterConfig::new(pid, pid); + pub fn minidump_writer(&self, pid: Pid) -> RemoteConfigProvider { + let mut mw = RemoteConfigProvider::new(pid, pid); if self == &Context::With { let crash_context = get_dummy_crash_context(pid); - mw.set_crash_context(crash_context); + mw.borrow_mut().set_crash_context(crash_context); } mw } @@ -74,7 +74,8 @@ contextual_test! { .tempfile() .unwrap(); - let tmp = context.minidump_writer(pid); + let mut tmp = context.minidump_writer(pid); + let tmp = tmp.provide(); let in_memory_buffer = tmp.write(&mut tmpfile).expect("Could not write minidump"); child.kill().expect("Failed to kill process"); @@ -143,6 +144,7 @@ contextual_test! { }; let mut tmp = context.minidump_writer(pid); + let mut tmp = tmp.provide(); tmp.set_user_mapping_list(vec![entry]); tmp @@ -217,7 +219,10 @@ fn module_list_properties() { .tempfile() .unwrap(); - MinidumpWriterConfig::new(pid, pid) + let mut provider = RemoteConfigProvider::new(pid, pid); + + provider + .provide() .write(&mut tmpfile) .expect("could not write minidump"); @@ -279,6 +284,7 @@ contextual_test! { }; let mut tmp = context.minidump_writer(pid); + let mut tmp = tmp.provide(); tmp.set_app_memory(vec![app_memory]); tmp @@ -329,6 +335,7 @@ contextual_test! { .unwrap(); let mut tmp = context.minidump_writer(pid); + let mut tmp = tmp.provide(); let pr_mapping_addr; #[cfg(target_pointer_width = "64")] @@ -376,6 +383,7 @@ contextual_test! { .unwrap(); let mut tmp = context.minidump_writer(pid); + let mut tmp = tmp.provide(); tmp.sanitize_stack(); tmp .write(&mut tmpfile) @@ -447,6 +455,7 @@ contextual_test! { }; let mut tmp = context.minidump_writer(pid); + let mut tmp = tmp.provide(); tmp.set_app_memory(vec![app_memory]); // This should fail, because during the dump an error is detected (try_from fails) @@ -484,7 +493,8 @@ contextual_test! { .tempfile() .unwrap(); - let tmp = context.minidump_writer(pid); + let mut tmp = context.minidump_writer(pid); + let tmp = tmp.provide(); let _ = tmp.write(&mut tmpfile).expect("Could not write minidump"); child.kill().expect("Failed to kill process"); @@ -529,7 +539,8 @@ contextual_test! { .tempfile() .unwrap(); - let tmp = context.minidump_writer(pid); + let mut tmp = context.minidump_writer(pid); + let tmp = tmp.provide(); let _ = tmp.write(&mut tmpfile).expect("Could not write minidump"); child.kill().expect("Failed to kill process"); @@ -584,7 +595,9 @@ fn minidump_size_limit() { .tempfile() .unwrap(); - MinidumpWriterConfig::new(pid, pid) + let mut provider = RemoteConfigProvider::new(pid, pid); + provider + .provide() .write(&mut tmpfile) .expect("Could not write minidump"); @@ -616,7 +629,8 @@ fn minidump_size_limit() { .tempfile() .unwrap(); - let mut tmp = MinidumpWriterConfig::new(pid, pid); + let mut tmp = RemoteConfigProvider::new(pid, pid); + let mut tmp = tmp.provide(); tmp.set_minidump_size_limit(minidump_size_limit); tmp.write(&mut tmpfile).expect("Could not write minidump"); @@ -662,7 +676,8 @@ fn minidump_size_limit() { .tempfile() .unwrap(); - let mut tmp = MinidumpWriterConfig::new(pid, pid); + let mut tmp = RemoteConfigProvider::new(pid, pid); + let mut tmp = tmp.provide(); tmp.set_minidump_size_limit(minidump_size_limit); tmp.write(&mut tmpfile).expect("Could not write minidump"); @@ -749,7 +764,9 @@ fn with_deleted_binary() { .tempfile() .unwrap(); - MinidumpWriterConfig::new(pid, pid) + let mut provider = RemoteConfigProvider::new(pid, pid); + provider + .provide() .write(&mut tmpfile) .expect("Could not write minidump"); @@ -809,8 +826,11 @@ fn memory_info_list_stream() { .tempfile() .unwrap(); + let mut provider = RemoteConfigProvider::new(pid, pid); + // Write a minidump - MinidumpWriterConfig::new(pid, pid) + provider + .provide() .write(&mut tmpfile) .expect("cound not write minidump"); child.kill().expect("Failed to kill process"); diff --git a/tests/linux_minidump_writer_soft_error.rs b/tests/linux_minidump_writer_soft_error.rs index d4a999d7..2bd46c59 100644 --- a/tests/linux_minidump_writer_soft_error.rs +++ b/tests/linux_minidump_writer_soft_error.rs @@ -2,7 +2,7 @@ use common::*; use minidump::Minidump; -use minidump_writer::{FailSpotName, minidump_writer::MinidumpWriterConfig}; +use minidump_writer::FailSpotName; mod common; @@ -20,7 +20,9 @@ fn soft_error_stream() { fail_client.set_enabled(FailSpotName::StopProcess, true); // Write a minidump - MinidumpWriterConfig::new(pid, pid) + let mut provider = RemoteConfigProvider::new(pid, pid); + provider + .provide() .write(&mut tmpfile) .expect("cound not write minidump"); child.kill().expect("Failed to kill process"); @@ -53,7 +55,9 @@ fn soft_error_stream_content() { } // Write a minidump - MinidumpWriterConfig::new(pid, pid) + let mut provider = RemoteConfigProvider::new(pid, pid); + provider + .provide() .write(&mut tmpfile) .expect("cound not write minidump"); child.kill().expect("Failed to kill process"); @@ -88,7 +92,9 @@ fn thread_stack_pointer_unmapped_soft_error() { let mut fail_client = FailSpotName::testing_client(); fail_client.set_enabled(FailSpotName::StackPointerMapping, true); - MinidumpWriterConfig::new(pid, pid) + let mut provider = RemoteConfigProvider::new(pid, pid); + provider + .provide() .write(&mut tmpfile) .expect("could not write minidump"); child.kill().expect("Failed to kill process"); @@ -111,7 +117,9 @@ fn thread_stack_unreadable_soft_error() { let mut fail_client = FailSpotName::testing_client(); fail_client.set_enabled(FailSpotName::ThreadStackCopy, true); - MinidumpWriterConfig::new(pid, pid) + let mut provider = RemoteConfigProvider::new(pid, pid); + provider + .provide() .write(&mut tmpfile) .expect("could not write minidump"); child.kill().expect("Failed to kill process"); @@ -134,7 +142,8 @@ fn crashing_thread_ip_memory_unreadable_soft_error() { let mut fail_client = FailSpotName::testing_client(); fail_client.set_enabled(FailSpotName::CrashingThreadIpCopy, true); - let mut config = MinidumpWriterConfig::new(pid, pid); + let mut provider = RemoteConfigProvider::new(pid, pid); + let mut config = provider.provide(); config.set_crash_context(get_dummy_crash_context(pid)); config .write(&mut tmpfile) diff --git a/tests/ptrace_dumper.rs b/tests/ptrace_dumper.rs index cdbb6f79..2ab1ac62 100644 --- a/tests/ptrace_dumper.rs +++ b/tests/ptrace_dumper.rs @@ -4,7 +4,6 @@ use { common::*, error_graph::ErrorList, - minidump_writer::minidump_writer::MinidumpWriterConfig, std::{ convert::TryInto, ffi::c_void, @@ -104,9 +103,11 @@ fn thread_list_from_parent() { let mut child = start_child_and_wait_for_threads(num_of_threads); let pid = child.id() as i32; + let mut provider = RemoteConfigProvider::new(pid, pid); + let dumper = assert_no_soft_errors!( soft_errors, - MinidumpWriterConfig::new(pid, pid).build_for_testing(&mut soft_errors) + provider.provide().build_for_testing(&mut soft_errors) ) .expect("Couldn't init dumper"); @@ -285,9 +286,11 @@ fn sanitizes_stack_copies() { let heap_addr = usize::from_str_radix(output.next().unwrap().trim_start_matches("0x"), 16) .expect("unable to parse mmap_addr"); + let mut provider = RemoteConfigProvider::new(pid, pid); + let dumper = assert_no_soft_errors!( soft_errors, - MinidumpWriterConfig::new(pid, pid).build_for_testing(&mut soft_errors) + provider.provide().build_for_testing(&mut soft_errors) ) .expect("Couldn't init dumper"); assert_eq!(dumper.threads.len(), num_of_threads);