/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ use bytes::{Buf, BufMut, Bytes, BytesMut, TryGetError}; #[cfg(any(target_os = "android", target_os = "linux"))] use minidump_writer::minidump_writer::{AuxvType, DirectAuxvDumpInfo}; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::FromPrimitive; use std::{ array::TryFromSliceError, ffi::{CString, FromBytesWithNulError, NulError, OsString}, mem::size_of, }; use thiserror::Error; #[cfg(target_os = "windows")] use windows_sys::Win32::System::Diagnostics::Debug::{CONTEXT, EXCEPTION_RECORD}; use crate::{ breakpad::Pid, ipc_connector::{AncillaryData, CONNECTOR_ANCILLARY_DATA_LEN}, BreakpadString, GeckoChildId, }; #[derive(Debug, Error)] pub enum MessageError { #[error("Nul terminator found within a string")] InteriorNul(#[from] NulError), #[error("The message contained an invalid payload")] InvalidData, #[error("Message kind is invalid")] InvalidKind, #[error("Invalid message size")] InvalidSize(#[from] TryFromSliceError), #[error("Missing ancillary data")] MissingAncillary, #[error("Missing nul terminator")] MissingNul(#[from] FromBytesWithNulError), #[error("Truncated message")] Truncated(#[from] TryGetError), #[error("Unexpected ancillary data")] UnexpectedAncillaryData, } #[repr(u8)] #[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive, PartialEq)] pub enum Kind { /// Changes the folder where crash reports are generated SetCrashReportPath = 1, /// Request the transfer of an already generated minidump for the specified /// PID back to the client. The message type is followed by a 32-bit /// integer containing the PID. TransferMinidump = 2, TransferMinidumpReply = 3, /// Request the generation of a minidump of the specified process. GenerateMinidump = 4, GenerateMinidumpReply = 5, /// Request the generation of a minidump based on data obtained via the /// Windows Error Reporting runtime exception module. The reply is empty /// and only used to inform the WER module that it's time to shut down the /// crashed process. This is only enabled on Windows. #[cfg(target_os = "windows")] WindowsErrorReporting = 6, #[cfg(target_os = "windows")] WindowsErrorReportingReply = 7, /// Register and unregister additional information for the auxiliary /// vector of a process. #[cfg(any(target_os = "android", target_os = "linux"))] RegisterAuxvInfo = 8, #[cfg(any(target_os = "android", target_os = "linux"))] UnregisterAuxvInfo = 9, /// Register a new child process and carry over the IPC channel it will use /// to talk to the crash helper. This is sent from the main process to the /// crash helper. RegisterChildProcess = 10, /// Message sent by the crash helper to rendez-vous with a newly-createdù /// child process. ChildProcessRendezVous = 11, /// Reply to a `ChildProcessRendezVous` message sent by a child after /// preparing itself for being dumped. ChildProcessRendezVousReply = 12, } // Bytes helpers to serialize/deserialize values not supported directly by the // `bytes` crate. Note that all of these could be implemented by simply // transmuting a generic type T into an appropriate byte-sized slice. However // doing so for arbitrary objects would also copy whatever data might be in // the padding between the fields or at the end of the object, potentially // creating a channel to leak information from a process, which is why we only // have helpers for types that we know are not padded. trait BytesMutExtensions { fn put_usize_ne(&mut self, n: usize); fn put_pid_ne(&mut self, pid: Pid); #[cfg(target_os = "windows")] fn put_context(&mut self, contextref: &CONTEXT); #[cfg(target_os = "windows")] fn put_exception_record(&mut self, recordref: &EXCEPTION_RECORD); } impl BytesMutExtensions for BytesMut { fn put_usize_ne(&mut self, n: usize) { #[cfg(target_pointer_width = "32")] self.put_u32_ne(n as u32); #[cfg(target_pointer_width = "64")] self.put_u64_ne(n as u64); } fn put_pid_ne(&mut self, pid: Pid) { #[cfg(target_os = "windows")] self.put_u32_ne(pid); #[cfg(not(target_os = "windows"))] self.put_i32_ne(pid); } #[cfg(target_os = "windows")] fn put_context(&mut self, contextref: &CONTEXT) { // SAFETY: The `CONTEXT` structure does not contain padding and can // thus be stored as a simple slice of bytes of the same size. let bytes: [u8; size_of::()] = unsafe { std::mem::transmute(*contextref) }; self.put_slice(&bytes); } #[cfg(target_os = "windows")] fn put_exception_record(&mut self, recordref: &EXCEPTION_RECORD) { self.put_i32_ne(recordref.ExceptionCode); self.put_u32_ne(recordref.ExceptionFlags); // We skip the `ExceptionRecord` field because it's a pointer and it // would be invalid in the destination process, we rebuild it there. self.put_usize_ne(recordref.ExceptionAddress as usize); self.put_u32_ne(recordref.NumberParameters); for info in recordref.ExceptionInformation { self.put_usize_ne(info); } } } trait BytesExtensions { fn try_get_usize_ne(&mut self) -> Result; fn try_get_pid_ne(&mut self) -> Result; fn try_get_vec(&mut self, len: usize) -> Result, TryGetError>; #[cfg(target_os = "windows")] fn try_get_context(&mut self) -> Result; #[cfg(target_os = "windows")] fn try_get_exception_record(&mut self) -> Result; } impl BytesExtensions for Bytes { fn try_get_usize_ne(&mut self) -> Result { #[cfg(target_pointer_width = "32")] return self.try_get_u32_ne().map(|v| v as usize); #[cfg(target_pointer_width = "64")] return self.try_get_u64_ne().map(|v| v as usize); } fn try_get_pid_ne(&mut self) -> Result { #[cfg(target_os = "windows")] return self.try_get_u32_ne(); #[cfg(not(target_os = "windows"))] return self.try_get_i32_ne(); } fn try_get_vec(&mut self, len: usize) -> Result, TryGetError> { let mut buffer = vec![0u8; len]; self.try_copy_to_slice(&mut buffer)?; Ok(buffer) } #[cfg(target_os = "windows")] fn try_get_context(&mut self) -> Result { let buffer = self.try_get_vec(size_of::())?; // SAFETY: The `CONTEXT` structure has no padding, so it can be // populated by copying over its contents from a slice of bytes. // Unrwapping the result of the slice `try_into()` will also never // fail as the size is guaranteed to be right. let context = unsafe { std::mem::transmute::<[u8; size_of::()], CONTEXT>( (*buffer.as_slice()).try_into().unwrap(), ) }; Ok(context) } #[cfg(target_os = "windows")] fn try_get_exception_record(&mut self) -> Result { let exception_code = self.try_get_i32_ne()?; let exception_flags = self.try_get_u32_ne()?; let exception_address = self.try_get_usize_ne()?; let number_parameters = self.try_get_u32_ne()?; let mut exception_information: [usize; 15] = [0usize; 15]; for ei in exception_information.iter_mut() { *ei = self.try_get_usize_ne()?; } Ok(EXCEPTION_RECORD { ExceptionCode: exception_code, ExceptionFlags: exception_flags, ExceptionRecord: std::ptr::null_mut(), ExceptionAddress: exception_address as *mut std::ffi::c_void, NumberParameters: number_parameters, ExceptionInformation: exception_information, }) } } pub trait Message { fn kind() -> Kind; fn payload_size(&self) -> usize; fn ancillary_data_len(&self) -> usize; fn encode(self) -> (Bytes, Bytes, Vec); fn decode(data: Vec, ancillary_data: Vec) -> Result where Self: Sized; } /* Message header, all messages are prefixed with this. The header is sent as * a single message over the underlying transport and contains the size of the * message payload as well as the type of the message. This allows the receiver * to validate and prepare for the reception of the payload. */ pub const HEADER_SIZE: usize = size_of::() + size_of::(); pub struct Header { pub kind: Kind, pub size: usize, } impl Header { fn encode(kind: Kind, size: usize) -> Bytes { let mut buffer = BytesMut::with_capacity(HEADER_SIZE); buffer.put_u8(kind as u8); buffer.put_usize_ne(size); debug_assert!(buffer.len() == HEADER_SIZE, "Header size mismatch"); buffer.freeze() } pub fn decode(buffer: Vec) -> Result { let mut buffer = Bytes::from(buffer); let kind = buffer.try_get_u8()?; let kind = Kind::from_u8(kind).ok_or(MessageError::InvalidKind)?; let size = buffer.try_get_usize_ne()?; Ok(Header { kind, size }) } } /* Message used to change the path where crash reports are generated. */ pub struct SetCrashReportPath { pub path: OsString, } impl SetCrashReportPath { pub fn new(path: OsString) -> SetCrashReportPath { SetCrashReportPath { path } } } impl Message for SetCrashReportPath { fn kind() -> Kind { Kind::SetCrashReportPath } fn payload_size(&self) -> usize { let path_len = self.path.clone().serialize().len(); size_of::().checked_add(path_len).unwrap() } fn ancillary_data_len(&self) -> usize { 0 } fn encode(self) -> (Bytes, Bytes, Vec) { let header = Header::encode(Self::kind(), self.payload_size()); let mut payload = BytesMut::with_capacity(self.payload_size()); let path = self.path.serialize(); payload.put_usize_ne(path.len()); payload.put(path); (header, payload.freeze(), vec![]) } fn decode(data: Vec, ancillary_data: Vec) -> Result { if !ancillary_data.is_empty() { return Err(MessageError::UnexpectedAncillaryData); } let mut data = Bytes::from(data); let path_len = data.try_get_usize_ne()?; let path = data.try_get_vec(path_len)?; let path = ::deserialize(path) .map_err(|_| MessageError::InvalidData)?; Ok(SetCrashReportPath { path }) } } /* Transfer minidump message, used to request the minidump which has been * generated for the specified pid. */ pub struct TransferMinidump { pub id: GeckoChildId, } impl TransferMinidump { pub fn new(id: GeckoChildId) -> TransferMinidump { TransferMinidump { id } } } impl Message for TransferMinidump { fn kind() -> Kind { Kind::TransferMinidump } fn payload_size(&self) -> usize { size_of::() } fn ancillary_data_len(&self) -> usize { 0 } fn encode(self) -> (Bytes, Bytes, Vec) { let header = Header::encode(Self::kind(), self.payload_size()); let mut payload = BytesMut::with_capacity(self.payload_size()); payload.put_i32_ne(self.id); (header, payload.freeze(), vec![]) } fn decode(data: Vec, ancillary_data: Vec) -> Result { if !ancillary_data.is_empty() { return Err(MessageError::UnexpectedAncillaryData); } let mut data = Bytes::from(data); let id = data.try_get_i32_ne()?; Ok(TransferMinidump { id }) } } /* Transfer minidump reply, received from the server after having sent a * TransferMinidump message. */ pub struct TransferMinidumpReply { pub path: OsString, pub error: Option, } impl TransferMinidumpReply { pub fn new(path: OsString, error: Option) -> TransferMinidumpReply { TransferMinidumpReply { path, error } } } impl Message for TransferMinidumpReply { fn kind() -> Kind { Kind::TransferMinidumpReply } fn payload_size(&self) -> usize { let path_len = self.path.clone().serialize().len(); (size_of::() * 2) .checked_add(path_len) .and_then(|l| { l.checked_add( self.error .as_ref() .map_or(0, |error| error.as_bytes().len()), ) }) .unwrap() } fn ancillary_data_len(&self) -> usize { 0 } fn encode(self) -> (Bytes, Bytes, Vec) { let header = Header::encode(Self::kind(), self.payload_size()); let mut payload = BytesMut::with_capacity(self.payload_size()); let path_bytes = self.path.serialize(); let error_bytes = self .error .as_ref() .map_or(Vec::new(), |error| Vec::from(error.as_bytes())); payload.put_usize_ne(path_bytes.len()); payload.put_usize_ne(error_bytes.len()); payload.put(path_bytes); payload.put(error_bytes.as_slice()); (header, payload.freeze(), vec![]) } fn decode( data: Vec, ancillary_data: Vec, ) -> Result { if !ancillary_data.is_empty() { return Err(MessageError::UnexpectedAncillaryData); } let mut data = Bytes::from(data); let path_len = data.try_get_usize_ne()?; let error_len = data.try_get_usize_ne()?; let path = data.try_get_vec(path_len)?; let path = ::deserialize(path) .map_err(|_| MessageError::InvalidData)?; let error = if error_len > 0 { Some(CString::new(data.try_get_vec(error_len)?)?) } else { None }; Ok(TransferMinidumpReply::new(path, error)) } } /* Generate a minidump based on information captured by the Windows Error Reporting runtime exception module. */ #[cfg(target_os = "windows")] pub struct WindowsErrorReportingMinidump { pub process: AncillaryData, pub thread: AncillaryData, pub exception_records: Vec, pub context: CONTEXT, } #[cfg(target_os = "windows")] impl WindowsErrorReportingMinidump { pub fn new( process: AncillaryData, thread: AncillaryData, exception_records: Vec, context: CONTEXT, ) -> WindowsErrorReportingMinidump { WindowsErrorReportingMinidump { process, thread, exception_records, context, } } } // The size of the structure minus the `ExceptionRecord` pointer and the // 4-bytes padding after the `NumberParameters` field. #[cfg(all(target_os = "windows", target_pointer_width = "64"))] const EXCEPTION_RECORD_SERIALIZED_SIZE: usize = size_of::() - (size_of::() + size_of::()); // The size of the structure minus the `ExceptionRecord` pointer. #[cfg(all(target_os = "windows", target_pointer_width = "32"))] const EXCEPTION_RECORD_SERIALIZED_SIZE: usize = size_of::() - size_of::(); #[cfg(target_os = "windows")] const WINDOWS_ERROR_REPORTING_MINIDUMP_ANCILLARY_DATA_LEN: usize = 2; #[cfg(target_os = "windows")] impl Message for WindowsErrorReportingMinidump { fn kind() -> Kind { Kind::WindowsErrorReporting } fn payload_size(&self) -> usize { size_of::() + (EXCEPTION_RECORD_SERIALIZED_SIZE * self.exception_records.len()) + size_of::() } fn ancillary_data_len(&self) -> usize { WINDOWS_ERROR_REPORTING_MINIDUMP_ANCILLARY_DATA_LEN } fn encode(self) -> (Bytes, Bytes, Vec) { let header = Header::encode(Self::kind(), self.payload_size()); let mut payload = BytesMut::with_capacity(self.payload_size()); payload.put_usize_ne(self.exception_records.len()); for exception_record in self.exception_records.iter() { payload.put_exception_record(exception_record); } payload.put_context(&self.context); (header, payload.freeze(), vec![]) } fn decode( data: Vec, ancillary_data: Vec, ) -> Result { if ancillary_data.len() < WINDOWS_ERROR_REPORTING_MINIDUMP_ANCILLARY_DATA_LEN { return Err(MessageError::MissingAncillary); } else if ancillary_data.len() > WINDOWS_ERROR_REPORTING_MINIDUMP_ANCILLARY_DATA_LEN { return Err(MessageError::UnexpectedAncillaryData); } let mut bytes = Bytes::from(data); let exception_records_n = bytes.try_get_usize_ne()?; let mut exception_records = Vec::::with_capacity(exception_records_n); for _i in 0..exception_records_n { let exception_record = bytes.try_get_exception_record()?; exception_records.push(exception_record); } let context = bytes.try_get_context()?; let mut iter = ancillary_data.into_iter(); let process = iter.next().unwrap(); let thread = iter.next().unwrap(); Ok(WindowsErrorReportingMinidump { process, thread, exception_records, context, }) } } /* Windows Error Reporting minidump reply, received from the server after * having sent a WindowsErrorReportingMinidumpReply. Informs the client that * it can tear down the crashed process. */ #[cfg(target_os = "windows")] pub struct WindowsErrorReportingMinidumpReply {} #[cfg(target_os = "windows")] impl Default for WindowsErrorReportingMinidumpReply { fn default() -> Self { Self::new() } } #[cfg(target_os = "windows")] impl WindowsErrorReportingMinidumpReply { pub fn new() -> WindowsErrorReportingMinidumpReply { WindowsErrorReportingMinidumpReply {} } } #[cfg(target_os = "windows")] impl Message for WindowsErrorReportingMinidumpReply { fn kind() -> Kind { Kind::WindowsErrorReportingReply } fn payload_size(&self) -> usize { 0 } fn ancillary_data_len(&self) -> usize { 0 } fn encode(self) -> (Bytes, Bytes, Vec) { let header = Header::encode(Self::kind(), self.payload_size()); (header, Bytes::new(), vec![]) } fn decode( data: Vec, ancillary_data: Vec, ) -> Result { if !ancillary_data.is_empty() { return Err(MessageError::UnexpectedAncillaryData); } if !data.is_empty() { return Err(MessageError::InvalidData); } Ok(WindowsErrorReportingMinidumpReply::new()) } } /* Message used to send information about a process' auxiliary vector. */ #[cfg(any(target_os = "android", target_os = "linux"))] pub struct RegisterAuxvInfo { pub id: GeckoChildId, pub auxv_info: DirectAuxvDumpInfo, } #[cfg(any(target_os = "android", target_os = "linux"))] impl RegisterAuxvInfo { pub fn new(id: GeckoChildId, auxv_info: DirectAuxvDumpInfo) -> RegisterAuxvInfo { RegisterAuxvInfo { id, auxv_info } } } #[cfg(any(target_os = "android", target_os = "linux"))] impl Message for RegisterAuxvInfo { fn kind() -> Kind { Kind::RegisterAuxvInfo } fn payload_size(&self) -> usize { // A bit hacky but we'll change this when we make // serialization/deserialization later. size_of::() + (size_of::() * 4) } fn ancillary_data_len(&self) -> usize { 0 } fn encode(self) -> (Bytes, Bytes, Vec) { let header = Header::encode(Self::kind(), self.payload_size()); let mut payload = BytesMut::with_capacity(self.payload_size()); payload.put_i32_ne(self.id); // AuxvType is the size of a pointer payload.put_usize_ne(self.auxv_info.program_header_count as usize); payload.put_usize_ne(self.auxv_info.program_header_address as usize); payload.put_usize_ne(self.auxv_info.linux_gate_address as usize); payload.put_usize_ne(self.auxv_info.entry_address as usize); (header, payload.freeze(), vec![]) } fn decode( data: Vec, ancillary_data: Vec, ) -> Result { if !ancillary_data.is_empty() { return Err(MessageError::UnexpectedAncillaryData); } let mut data = Bytes::from(data); let id = data.try_get_i32_ne()?; let program_header_count = data.try_get_usize_ne()? as AuxvType; let program_header_address = data.try_get_usize_ne()? as AuxvType; let linux_gate_address = data.try_get_usize_ne()? as AuxvType; let entry_address = data.try_get_usize_ne()? as AuxvType; let auxv_info = DirectAuxvDumpInfo { program_header_count, program_header_address, entry_address, linux_gate_address, }; Ok(RegisterAuxvInfo { id, auxv_info }) } } /* Message used to inform the crash helper that a process' auxiliary vector * information is not needed anymore. */ #[cfg(any(target_os = "android", target_os = "linux"))] pub struct UnregisterAuxvInfo { pub id: GeckoChildId, } #[cfg(any(target_os = "android", target_os = "linux"))] impl UnregisterAuxvInfo { pub fn new(id: GeckoChildId) -> UnregisterAuxvInfo { UnregisterAuxvInfo { id } } } #[cfg(any(target_os = "android", target_os = "linux"))] impl Message for UnregisterAuxvInfo { fn kind() -> Kind { Kind::UnregisterAuxvInfo } fn payload_size(&self) -> usize { size_of::() } fn ancillary_data_len(&self) -> usize { 0 } fn encode(self) -> (Bytes, Bytes, Vec) { let header = Header::encode(Self::kind(), self.payload_size()); let mut payload = BytesMut::with_capacity(self.payload_size()); payload.put_i32_ne(self.id); (header, payload.freeze(), vec![]) } fn decode( data: Vec, ancillary_data: Vec, ) -> Result { if !ancillary_data.is_empty() { return Err(MessageError::UnexpectedAncillaryData); } let mut data = Bytes::from(data); let id = data.try_get_i32_ne()?; Ok(UnregisterAuxvInfo { id }) } } /* Message sent from the main process to the crash helper to register a new * child process which is about to be spawned. This message contains the IPC * endpoint which the crash helper will use to talk to the child. */ pub struct RegisterChildProcess { pub ancillary_data: [AncillaryData; CONNECTOR_ANCILLARY_DATA_LEN], } impl RegisterChildProcess { pub fn new( ancillary_data: [AncillaryData; CONNECTOR_ANCILLARY_DATA_LEN], ) -> RegisterChildProcess { RegisterChildProcess { ancillary_data } } } impl Message for RegisterChildProcess { fn kind() -> Kind { Kind::RegisterChildProcess } fn payload_size(&self) -> usize { 0 } fn ancillary_data_len(&self) -> usize { self.ancillary_data.len() } fn encode(self) -> (Bytes, Bytes, Vec) { let header = Header::encode(Self::kind(), self.payload_size()); let payload = Bytes::new(); (header, payload, self.ancillary_data.into()) } fn decode( _data: Vec, mut ancillary_data: Vec, ) -> Result { #[cfg(any(target_os = "ios", target_os = "macos"))] let ancillary_data: [AncillaryData; CONNECTOR_ANCILLARY_DATA_LEN] = { let receive_right = ancillary_data.pop().ok_or(MessageError::MissingAncillary)?; let send_right = ancillary_data.pop().ok_or(MessageError::MissingAncillary)?; [send_right, receive_right] }; #[cfg(not(any(target_os = "ios", target_os = "macos")))] let ancillary_data: [AncillaryData; CONNECTOR_ANCILLARY_DATA_LEN] = [ancillary_data.pop().ok_or(MessageError::MissingAncillary)?]; Ok(RegisterChildProcess { ancillary_data }) } } /* Message sent from the crash helper process to a newly registered child * process. The child will prepare itself for being dumped by the crash helper * after receiving this message, and then reply to inform the crash helper * that it is now possible to dump it. */ pub struct ChildProcessRendezVous { pub crash_helper_pid: Pid, } impl ChildProcessRendezVous { pub fn new(pid: Pid) -> ChildProcessRendezVous { ChildProcessRendezVous { crash_helper_pid: pid, } } } impl Message for ChildProcessRendezVous { fn kind() -> Kind { Kind::ChildProcessRendezVous } fn payload_size(&self) -> usize { size_of::() } fn ancillary_data_len(&self) -> usize { 0 } fn encode(self) -> (Bytes, Bytes, Vec) { let header = Header::encode(Self::kind(), self.payload_size()); let mut payload = BytesMut::with_capacity(self.payload_size()); payload.put_pid_ne(self.crash_helper_pid); (header, payload.freeze(), vec![]) } fn decode( data: Vec, ancillary_data: Vec, ) -> Result { if !ancillary_data.is_empty() { return Err(MessageError::UnexpectedAncillaryData); } let mut data = Bytes::from(data); let pid = data.try_get_pid_ne()?; Ok(ChildProcessRendezVous { crash_helper_pid: pid, }) } } /* Reply sent by a child process to the crash helper process after receiving * a ChildProcessRendezVous message. The message contains information on * whether the child process managed to set itself up for being dumped, its * PID plus platform-specific information that might be needed by the parent to * dump it. */ pub struct ChildProcessRendezVousReply { pub dumpable: bool, pub child_pid: Pid, pub id: GeckoChildId, } impl ChildProcessRendezVousReply { pub fn new(dumpable: bool, child_pid: Pid, id: GeckoChildId) -> ChildProcessRendezVousReply { ChildProcessRendezVousReply { dumpable, child_pid, id, } } } impl Message for ChildProcessRendezVousReply { fn kind() -> Kind { Kind::ChildProcessRendezVousReply } fn payload_size(&self) -> usize { size_of::() + size_of::() + size_of::() } fn ancillary_data_len(&self) -> usize { 0 } fn encode(self) -> (Bytes, Bytes, Vec) { let header = Header::encode(Self::kind(), self.payload_size()); let mut payload = BytesMut::with_capacity(self.payload_size()); payload.put_u8(self.dumpable.into()); payload.put_pid_ne(self.child_pid); payload.put_i32_ne(self.id); (header, payload.freeze(), vec![]) } fn decode( data: Vec, ancillary_data: Vec, ) -> Result { if !ancillary_data.is_empty() { return Err(MessageError::UnexpectedAncillaryData); } let mut data = Bytes::from(data); let dumpable = data.try_get_u8()? != 0; let child_pid = data.try_get_pid_ne()?; let id = data.try_get_i32_ne()?; Ok(ChildProcessRendezVousReply { dumpable, child_pid, id, }) } }