#[derive(Debug)]
pub enum ReadError {
NoMechanism,
Os(i64),
Short {
wanted: usize,
got: usize,
},
}
impl core::fmt::Display for ReadError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NoMechanism => f.write_str("no self-image read mechanism on this target"),
Self::Os(status) => write!(f, "the operating system refused the read: status {status}"),
Self::Short { wanted, got } => {
write!(f, "short self-image read: wanted {wanted} bytes, got {got}")
}
}
}
}
impl std::error::Error for ReadError {}
#[must_use]
pub const fn available() -> bool {
cfg!(any(target_os = "macos", target_os = "ios", windows))
}
pub fn read_self(addr: usize, out: &mut [u8]) -> Result<(), ReadError> {
if out.is_empty() {
return Ok(());
}
imp::read_self(addr, out)
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod imp {
#![allow(unsafe_code)]
use super::ReadError;
type MachPortT = u32;
type KernReturnT = i32;
type MachVmAddressT = u64;
type MachVmSizeT = u64;
const KERN_SUCCESS: KernReturnT = 0;
unsafe extern "C" {
static mach_task_self_: MachPortT;
fn mach_vm_read_overwrite(
target_task: MachPortT,
address: MachVmAddressT,
size: MachVmSizeT,
data: MachVmAddressT,
out_size: *mut MachVmSizeT,
) -> KernReturnT;
}
pub(super) fn read_self(addr: usize, out: &mut [u8]) -> Result<(), ReadError> {
let wanted = out.len();
let mut got: MachVmSizeT = 0;
let status = unsafe {
mach_vm_read_overwrite(
mach_task_self_,
addr as MachVmAddressT,
wanted as MachVmSizeT,
out.as_mut_ptr() as MachVmAddressT,
&mut got,
)
};
if status != KERN_SUCCESS {
return Err(ReadError::Os(i64::from(status)));
}
let got = usize::try_from(got).unwrap_or(0);
if got != wanted {
return Err(ReadError::Short { wanted, got });
}
Ok(())
}
}
#[cfg(windows)]
mod imp {
#![allow(unsafe_code)]
use super::ReadError;
type Handle = *mut core::ffi::c_void;
unsafe extern "system" {
fn GetCurrentProcess() -> Handle;
fn ReadProcessMemory(
process: Handle,
base: *const core::ffi::c_void,
buffer: *mut core::ffi::c_void,
size: usize,
read: *mut usize,
) -> i32;
fn GetLastError() -> u32;
}
pub(super) fn read_self(addr: usize, out: &mut [u8]) -> Result<(), ReadError> {
let wanted = out.len();
let mut got: usize = 0;
let ok = unsafe {
ReadProcessMemory(
GetCurrentProcess(),
addr as *const core::ffi::c_void,
out.as_mut_ptr().cast::<core::ffi::c_void>(),
wanted,
&mut got,
)
};
if ok == 0 {
let code = unsafe { GetLastError() };
return Err(ReadError::Os(i64::from(code)));
}
if got != wanted {
return Err(ReadError::Short { wanted, got });
}
Ok(())
}
}
#[cfg(not(any(target_os = "macos", target_os = "ios", windows)))]
mod imp {
use super::ReadError;
pub(super) fn read_self(_addr: usize, _out: &mut [u8]) -> Result<(), ReadError> {
Err(ReadError::NoMechanism)
}
}