summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/src/mac/mach.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/minidump-writer/src/mac/mach.rs')
-rw-r--r--third_party/rust/minidump-writer/src/mac/mach.rs670
1 files changed, 670 insertions, 0 deletions
diff --git a/third_party/rust/minidump-writer/src/mac/mach.rs b/third_party/rust/minidump-writer/src/mac/mach.rs
new file mode 100644
index 0000000000..f95211dc64
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/mac/mach.rs
@@ -0,0 +1,670 @@
+//! Contains various helpers to improve and expand on the bindings provided
+//! by `mach2`
+
+// Just exports all of the mach functions we use into a flat list
+pub use mach2::{
+ kern_return::{kern_return_t, KERN_SUCCESS},
+ port::mach_port_name_t,
+ task::{self, task_threads},
+ task_info,
+ thread_act::thread_get_state,
+ traps::mach_task_self,
+ vm::{mach_vm_deallocate, mach_vm_read, mach_vm_region_recurse},
+ vm_region::vm_region_submap_info_64,
+};
+
+/// A Mach kernel error.
+///
+/// See <usr/include/mach/kern_return.h>.
+#[derive(thiserror::Error, Debug)]
+pub enum KernelError {
+ #[error("specified address is not currently valid")]
+ InvalidAddress = 1,
+ #[error("specified memory is valid, but does not permit the required forms of access")]
+ ProtectionFailure = 2,
+ #[error("the address range specified is already in use, or no address range of the size specified could be found")]
+ NoSpace = 3,
+ #[error("the function requested was not applicable to this type of argument, or an argument is invalid")]
+ InvalidArgument = 4,
+ #[error("the function could not be performed")]
+ Failure = 5,
+ #[error("system resource could not be allocated to fulfill this request")]
+ ResourceShortage = 6,
+ #[error("the task in question does not hold receive rights for the port argument")]
+ NotReceiver = 7,
+ #[error("bogus access restriction")]
+ NoAccess = 8,
+ #[error(
+ "during a page fault, the target address refers to a memory object that has been destroyed"
+ )]
+ MemoryFailure = 9,
+ #[error(
+ "during a page fault, the memory object indicated that the data could not be returned"
+ )]
+ MemoryError = 10,
+ #[error("the receive right is already a member of the portset")]
+ AlreadyInSet = 11,
+ #[error("the receive right is not a member of a port set")]
+ NotInSet = 12,
+ #[error("the name already denotes a right in the task")]
+ NameExists = 13,
+ #[error("the operation was aborted")]
+ Aborted = 14,
+ #[error("the name doesn't denote a right in the task")]
+ InvalidName = 15,
+ #[error("target task isn't an active task")]
+ InvalidTask = 16,
+ #[error("the name denotes a right, but not an appropriate right")]
+ InvalidRight = 17,
+ #[error("a blatant range error")]
+ InvalidValue = 18,
+ #[error("operation would overflow limit on user-references")]
+ UserRefsOverflow = 19,
+ #[error("the supplied port capability is improper")]
+ InvalidCapability = 20,
+ #[error("the task already has send or receive rights for the port under another name")]
+ RightExists = 21,
+ #[error("target host isn't actually a host")]
+ InvalidHost = 22,
+ #[error("an attempt was made to supply 'precious' data for memory that is already present in a memory object")]
+ MemoryPresent = 23,
+ // These 2 are errors which should only ever be seen by the kernel itself
+ //MemoryDataMoved = 24,
+ //MemoryRestartCopy = 25,
+ #[error("an argument applied to assert processor set privilege was not a processor set control port")]
+ InvalidProcessorSet = 26,
+ #[error("the specified scheduling attributes exceed the thread's limits")]
+ PolicyLimit = 27,
+ #[error("the specified scheduling policy is not currently enabled for the processor set")]
+ InvalidPolicy = 28,
+ #[error("the external memory manager failed to initialize the memory object")]
+ InvalidObject = 29,
+ #[error(
+ "a thread is attempting to wait for an event for which there is already a waiting thread"
+ )]
+ AlreadyWaiting = 30,
+ #[error("an attempt was made to destroy the default processor set")]
+ DefaultSet = 31,
+ #[error("an attempt was made to fetch an exception port that is protected, or to abort a thread while processing a protected exception")]
+ ExceptionProtected = 32,
+ #[error("a ledger was required but not supplied")]
+ InvalidLedger = 33,
+ #[error("the port was not a memory cache control port")]
+ InvalidMemoryControl = 34,
+ #[error("an argument supplied to assert security privilege was not a host security port")]
+ InvalidSecurity = 35,
+ #[error("thread_depress_abort was called on a thread which was not currently depressed")]
+ NotDepressed = 36,
+ #[error("object has been terminated and is no longer available")]
+ Terminated = 37,
+ #[error("lock set has been destroyed and is no longer available")]
+ LockSetDestroyed = 38,
+ #[error("the thread holding the lock terminated before releasing the lock")]
+ LockUnstable = 39,
+ #[error("the lock is already owned by another thread")]
+ LockOwned = 40,
+ #[error("the lock is already owned by the calling thread")]
+ LockOwnedSelf = 41,
+ #[error("semaphore has been destroyed and is no longer available")]
+ SemaphoreDestroyed = 42,
+ #[error("return from RPC indicating the target server was terminated before it successfully replied")]
+ RpcServerTerminated = 43,
+ #[error("terminate an orphaned activation")]
+ RpcTerminateOrphan = 44,
+ #[error("allow an orphaned activation to continue executing")]
+ RpcContinueOrphan = 45,
+ #[error("empty thread activation (No thread linked to it)")]
+ NotSupported = 46,
+ #[error("remote node down or inaccessible")]
+ NodeDown = 47,
+ #[error("a signalled thread was not actually waiting")]
+ NotWaiting = 48,
+ #[error("some thread-oriented operation (semaphore_wait) timed out")]
+ OperationTimedOut = 49,
+ #[error("during a page fault, indicates that the page was rejected as a result of a signature check")]
+ CodesignError = 50,
+ #[error("the requested property cannot be changed at this time")]
+ PoicyStatic = 51,
+ #[error("the provided buffer is of insufficient size for the requested data")]
+ InsufficientBufferSize = 52,
+ #[error("denied by security policy")]
+ Denied = 53,
+ #[error("the KC on which the function is operating is missing")]
+ MissingKC = 54,
+ #[error("the KC on which the function is operating is invalid")]
+ InvalidKC = 55,
+ #[error("a search or query operation did not return a result")]
+ NotFound = 56,
+}
+
+impl From<mach2::kern_return::kern_return_t> for KernelError {
+ fn from(kr: mach2::kern_return::kern_return_t) -> Self {
+ use mach2::kern_return::*;
+
+ match kr {
+ KERN_INVALID_ADDRESS => Self::InvalidAddress,
+ KERN_PROTECTION_FAILURE => Self::ProtectionFailure,
+ KERN_NO_SPACE => Self::NoSpace,
+ KERN_INVALID_ARGUMENT => Self::InvalidArgument,
+ KERN_FAILURE => Self::Failure,
+ KERN_RESOURCE_SHORTAGE => Self::ResourceShortage,
+ KERN_NOT_RECEIVER => Self::NotReceiver,
+ KERN_NO_ACCESS => Self::NoAccess,
+ KERN_MEMORY_FAILURE => Self::MemoryFailure,
+ KERN_MEMORY_ERROR => Self::MemoryError,
+ KERN_ALREADY_IN_SET => Self::AlreadyInSet,
+ KERN_NAME_EXISTS => Self::NameExists,
+ KERN_INVALID_NAME => Self::InvalidName,
+ KERN_INVALID_TASK => Self::InvalidTask,
+ KERN_INVALID_RIGHT => Self::InvalidRight,
+ KERN_INVALID_VALUE => Self::InvalidValue,
+ KERN_UREFS_OVERFLOW => Self::UserRefsOverflow,
+ KERN_INVALID_CAPABILITY => Self::InvalidCapability,
+ KERN_RIGHT_EXISTS => Self::RightExists,
+ KERN_INVALID_HOST => Self::InvalidHost,
+ KERN_MEMORY_PRESENT => Self::MemoryPresent,
+ KERN_INVALID_PROCESSOR_SET => Self::InvalidProcessorSet,
+ KERN_POLICY_LIMIT => Self::PolicyLimit,
+ KERN_INVALID_POLICY => Self::InvalidPolicy,
+ KERN_INVALID_OBJECT => Self::InvalidObject,
+ KERN_ALREADY_WAITING => Self::AlreadyWaiting,
+ KERN_DEFAULT_SET => Self::DefaultSet,
+ KERN_EXCEPTION_PROTECTED => Self::ExceptionProtected,
+ KERN_INVALID_LEDGER => Self::InvalidLedger,
+ KERN_INVALID_MEMORY_CONTROL => Self::InvalidMemoryControl,
+ KERN_INVALID_SECURITY => Self::InvalidSecurity,
+ KERN_NOT_DEPRESSED => Self::NotDepressed,
+ KERN_TERMINATED => Self::Terminated,
+ KERN_LOCK_SET_DESTROYED => Self::LockSetDestroyed,
+ KERN_LOCK_UNSTABLE => Self::LockUnstable,
+ KERN_LOCK_OWNED => Self::LockOwned,
+ KERN_LOCK_OWNED_SELF => Self::LockOwnedSelf,
+ KERN_SEMAPHORE_DESTROYED => Self::SemaphoreDestroyed,
+ KERN_RPC_SERVER_TERMINATED => Self::RpcServerTerminated,
+ KERN_RPC_TERMINATE_ORPHAN => Self::RpcTerminateOrphan,
+ KERN_RPC_CONTINUE_ORPHAN => Self::RpcContinueOrphan,
+ KERN_NOT_SUPPORTED => Self::NotSupported,
+ KERN_NODE_DOWN => Self::NodeDown,
+ KERN_NOT_WAITING => Self::NotWaiting,
+ KERN_OPERATION_TIMED_OUT => Self::OperationTimedOut,
+ KERN_CODESIGN_ERROR => Self::CodesignError,
+ KERN_POLICY_STATIC => Self::PoicyStatic,
+ 52 => Self::InsufficientBufferSize,
+ 53 => Self::Denied,
+ 54 => Self::MissingKC,
+ 55 => Self::InvalidKC,
+ 56 => Self::NotFound,
+ // This should never happen given a result from a mach call, but
+ // in that case we just use `Failure` as the mach header itself
+ // describes it as a catch all
+ _ => Self::Failure,
+ }
+ }
+}
+
+// From /usr/include/mach/machine/thread_state.h
+pub const THREAD_STATE_MAX: usize = 1296;
+
+cfg_if::cfg_if! {
+ if #[cfg(target_arch = "x86_64")] {
+ /// x86_THREAD_STATE64 in /usr/include/mach/i386/thread_status.h
+ pub const THREAD_STATE_FLAVOR: u32 = 4;
+
+ pub type ArchThreadState = mach2::structs::x86_thread_state64_t;
+ } else if #[cfg(target_arch = "aarch64")] {
+ /// ARM_THREAD_STATE64 in /usr/include/mach/arm/thread_status.h
+ pub const THREAD_STATE_FLAVOR: u32 = 6;
+
+ // Missing from mach2 atm
+ // _STRUCT_ARM_THREAD_STATE64 from /usr/include/mach/arm/_structs.h
+ #[repr(C)]
+ pub struct Arm64ThreadState {
+ pub x: [u64; 29],
+ pub fp: u64,
+ pub lr: u64,
+ pub sp: u64,
+ pub pc: u64,
+ pub cpsr: u32,
+ __pad: u32,
+ }
+
+ pub type ArchThreadState = Arm64ThreadState;
+ } else {
+ compile_error!("unsupported target arch");
+ }
+}
+
+#[repr(C, align(8))]
+pub struct ThreadState {
+ pub state: [u32; THREAD_STATE_MAX],
+ pub state_size: u32,
+}
+
+impl Default for ThreadState {
+ fn default() -> Self {
+ Self {
+ state: [0u32; THREAD_STATE_MAX],
+ state_size: (THREAD_STATE_MAX * std::mem::size_of::<u32>()) as u32,
+ }
+ }
+}
+
+impl ThreadState {
+ /// Gets the program counter
+ #[inline]
+ pub fn pc(&self) -> u64 {
+ cfg_if::cfg_if! {
+ if #[cfg(target_arch = "x86_64")] {
+ self.arch_state().__rip
+ } else if #[cfg(target_arch = "aarch64")] {
+ self.arch_state().pc
+ }
+ }
+ }
+
+ /// Gets the stack pointer
+ #[inline]
+ pub fn sp(&self) -> u64 {
+ cfg_if::cfg_if! {
+ if #[cfg(target_arch = "x86_64")] {
+ self.arch_state().__rsp
+ } else if #[cfg(target_arch = "aarch64")] {
+ self.arch_state().sp
+ }
+ }
+ }
+
+ /// Converts the raw binary blob into the architecture specific state
+ #[inline]
+ pub fn arch_state(&self) -> &ArchThreadState {
+ // SAFETY: hoping the kernel isn't lying
+ unsafe { &*(self.state.as_ptr().cast()) }
+ }
+}
+
+/// Minimal trait that just pairs a structure that can be filled out by
+/// [`mach2::task::task_info`] with the "flavor" that tells it the info we
+/// actually want to retrieve
+pub trait TaskInfo {
+ /// One of the `MACH_*_TASK` integers. I assume it's very bad if you implement
+ /// this trait and provide the wrong flavor for the struct
+ const FLAVOR: u32;
+}
+
+/// Minimal trait that just pairs a structure that can be filled out by
+/// [`thread_info`] with the "flavor" that tells it the info we
+/// actually want to retrieve
+pub trait ThreadInfo {
+ /// One of the `THREAD_*` integers. I assume it's very bad if you implement
+ /// this trait and provide the wrong flavor for the struct
+ const FLAVOR: u32;
+}
+
+/// <usr/include/mach-o/loader.h>, the file type for the main executable image
+pub const MH_EXECUTE: u32 = 0x2;
+/// <usr/include/mach-o/loader.h>, the file type dyld, the dynamic loader
+pub const MH_DYLINKER: u32 = 0x7;
+// usr/include/mach-o/loader.h, magic number for MachHeader
+pub const MH_MAGIC_64: u32 = 0xfeedfacf;
+
+/// Load command constants from usr/include/mach-o/loader.h
+#[repr(u32)]
+#[derive(Debug)]
+pub enum LoadCommandKind {
+ /// Command to map a segment
+ Segment = 0x19,
+ /// Dynamically linked shared lib ident
+ IdDylib = 0xd,
+ /// Image uuid
+ Uuid = 0x1b,
+ /// Load a dynamic linker. Should only be on MH_EXECUTE (main executable)
+ /// images when the dynamic linker is overriden
+ LoadDylinker = 0xe,
+ /// Dynamic linker identification
+ IdDylinker = 0xf,
+}
+
+impl LoadCommandKind {
+ #[inline]
+ fn from_u32(kind: u32) -> Option<Self> {
+ Some(if kind == Self::Segment as u32 {
+ Self::Segment
+ } else if kind == Self::IdDylib as u32 {
+ Self::IdDylib
+ } else if kind == Self::Uuid as u32 {
+ Self::Uuid
+ } else if kind == Self::LoadDylinker as u32 {
+ Self::LoadDylinker
+ } else if kind == Self::IdDylinker as u32 {
+ Self::IdDylinker
+ } else {
+ return None;
+ })
+ }
+}
+
+/// The header at the beginning of every (valid) Mach image
+///
+/// <usr/include/mach-o/loader.h>
+#[repr(C)]
+#[derive(Clone)]
+pub struct MachHeader {
+ /// Mach magic number identifier, this is used to validate the header is valid
+ pub magic: u32,
+ /// `cpu_type_t` cpu specifier
+ pub cpu_type: i32,
+ /// `cpu_subtype_t` machine specifier
+ pub cpu_sub_type: i32,
+ /// Type of file, eg. [`MH_EXECUTE`] for the main executable
+ pub file_type: u32,
+ /// Number of load commands for the image
+ pub num_commands: u32,
+ /// Size in bytes of all of the load commands
+ pub size_commands: u32,
+ pub flags: u32,
+ __reserved: u32,
+}
+
+/// Every load command is a variable sized struct depending on its type, but
+/// they all include the fields in this struct at the beginning
+///
+/// <usr/include/mach-o/loader.h>
+#[repr(C)]
+pub struct LoadCommandBase {
+ /// Type of load command `LC_*`
+ pub cmd: u32,
+ /// Total size of the command in bytes
+ pub cmd_size: u32,
+}
+
+/// The 64-bit segment load command indicates that a part of this file is to be
+/// mapped into a 64-bit task's address space. If the 64-bit segment has
+/// sections then section_64 structures directly follow the 64-bit segment
+/// command and their size is reflected in `cmdsize`.
+#[repr(C)]
+pub struct SegmentCommand64 {
+ cmd: u32,
+ pub cmd_size: u32,
+ /// String name of the section
+ pub segment_name: [u8; 16],
+ /// Memory address the segment is mapped to
+ pub vm_addr: u64,
+ /// Total size of the segment
+ pub vm_size: u64,
+ /// File offset of the segment
+ pub file_off: u64,
+ /// Amount mapped from the file
+ pub file_size: u64,
+ /// Maximum VM protection
+ pub max_prot: i32,
+ /// Initial VM protection
+ pub init_prot: i32,
+ /// Number of sections in the segment
+ pub num_sections: u32,
+ pub flags: u32,
+}
+
+/// Dynamically linked shared libraries are identified by two things. The
+/// pathname (the name of the library as found for execution), and the
+/// compatibility version number. The pathname must match and the compatibility
+/// number in the user of the library must be greater than or equal to the
+/// library being used. The time stamp is used to record the time a library was
+/// built and copied into user so it can be use to determined if the library used
+/// at runtime is exactly the same as used to built the program.
+#[repr(C)]
+#[derive(Debug)]
+pub struct Dylib {
+ /// Offset from the load command start to the pathname
+ pub name: u32,
+ /// Library's build time stamp
+ pub timestamp: u32,
+ /// Library's current version number
+ pub current_version: u32,
+ /// Library's compatibility version number
+ pub compatibility_version: u32,
+}
+
+/// A dynamically linked shared library (filetype == MH_DYLIB in the mach header)
+/// contains a dylib_command (cmd == LC_ID_DYLIB) to identify the library.
+/// An object that uses a dynamically linked shared library also contains a
+/// dylib_command (cmd == LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, or
+/// LC_REEXPORT_DYLIB) for each library it uses.
+#[repr(C)]
+pub struct DylibCommand {
+ cmd: u32,
+ /// Total size of the command in bytes, including pathname string
+ pub cmd_size: u32,
+ /// Library identification
+ pub dylib: Dylib,
+}
+
+/// A program that uses a dynamic linker contains a dylinker_command to identify
+/// the name of the dynamic linker (LC_LOAD_DYLINKER). And a dynamic linker
+/// contains a dylinker_command to identify the dynamic linker (LC_ID_DYLINKER).
+/// A file can have at most one of these.
+/// This struct is also used for the LC_DYLD_ENVIRONMENT load command and
+/// contains string for dyld to treat like environment variable.
+#[repr(C)]
+struct DylinkerCommandRepr {
+ /// LC_ID_DYLINKER, LC_LOAD_DYLINKER or LC_DYLD_ENVIRONMENT
+ cmd: u32,
+ /// includes pathname string
+ cmd_size: u32,
+ /// Dynamic linker's path name, an offset from the load command address
+ name: u32,
+}
+
+pub struct DylinkerCommand<'buf> {
+ /// LC_ID_DYLINKER, LC_LOAD_DYLINKER or LC_DYLD_ENVIRONMENT
+ pub cmd: u32,
+ /// includes pathname string
+ pub cmd_size: u32,
+ /// The offset from the load command where the path was read
+ pub name_offset: u32,
+ /// Dynamic linker's path name
+ pub name: &'buf str,
+}
+
+/// The uuid load command contains a single 128-bit unique random number that
+/// identifies an object produced by the static link editor.
+#[repr(C)]
+pub struct UuidCommand {
+ cmd: u32,
+ pub cmd_size: u32,
+ /// The UUID. The components are in big-endian regardless of the host architecture
+ pub uuid: [u8; 16],
+}
+
+/// A block of load commands for a particular image
+pub struct LoadCommands {
+ /// The block of memory containing all of the load commands
+ pub buffer: Vec<u8>,
+ /// The number of actual load commmands that _should_ be in the buffer
+ pub count: u32,
+}
+
+impl LoadCommands {
+ /// Retrieves an iterator over the load commands in the contained buffer
+ #[inline]
+ pub fn iter(&self) -> LoadCommandsIter<'_> {
+ LoadCommandsIter {
+ buffer: &self.buffer,
+ count: self.count,
+ }
+ }
+}
+
+/// A single load command
+pub enum LoadCommand<'buf> {
+ Segment(&'buf SegmentCommand64),
+ Dylib(&'buf DylibCommand),
+ Uuid(&'buf UuidCommand),
+ DylinkerCommand(DylinkerCommand<'buf>),
+}
+
+pub struct LoadCommandsIter<'buf> {
+ buffer: &'buf [u8],
+ count: u32,
+}
+
+impl<'buf> Iterator for LoadCommandsIter<'buf> {
+ type Item = LoadCommand<'buf>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // SAFETY: we're interpreting raw bytes as C structs, we try and be safe
+ unsafe {
+ loop {
+ if self.count == 0 || self.buffer.len() < std::mem::size_of::<LoadCommandBase>() {
+ return None;
+ }
+
+ let header = &*(self.buffer.as_ptr().cast::<LoadCommandBase>());
+
+ // This would mean we've been lied to by the MachHeader and either
+ // the size_commands field was too small, or the num_command was
+ // too large
+ if header.cmd_size as usize > self.buffer.len() {
+ return None;
+ }
+
+ let cmd = LoadCommandKind::from_u32(header.cmd).and_then(|kind| {
+ Some(match kind {
+ LoadCommandKind::Segment => LoadCommand::Segment(
+ &*(self.buffer.as_ptr().cast::<SegmentCommand64>()),
+ ),
+ LoadCommandKind::IdDylib => {
+ LoadCommand::Dylib(&*(self.buffer.as_ptr().cast::<DylibCommand>()))
+ }
+ LoadCommandKind::Uuid => {
+ LoadCommand::Uuid(&*(self.buffer.as_ptr().cast::<UuidCommand>()))
+ }
+ LoadCommandKind::LoadDylinker | LoadCommandKind::IdDylinker => {
+ let dcr = &*(self.buffer.as_ptr().cast::<DylinkerCommandRepr>());
+
+ let nul = self.buffer[dcr.name as usize..header.cmd_size as usize]
+ .iter()
+ .position(|c| *c == 0)?;
+
+ LoadCommand::DylinkerCommand(DylinkerCommand {
+ cmd: dcr.cmd,
+ cmd_size: dcr.cmd_size,
+ name_offset: dcr.name,
+ name: std::str::from_utf8(
+ &self.buffer[dcr.name as usize..dcr.name as usize + nul],
+ )
+ .ok()?,
+ })
+ }
+ })
+ });
+
+ self.count -= 1;
+ self.buffer = &self.buffer[header.cmd_size as usize..];
+
+ if let Some(cmd) = cmd {
+ return Some(cmd);
+ }
+ }
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ let sz = self.count as usize;
+ (sz, Some(sz))
+ }
+}
+
+/// Retrieves an integer sysctl by name. Returns the default value if retrieval
+/// fails.
+pub fn sysctl_by_name<T: Sized + Default>(name: &[u8]) -> T {
+ let mut out = T::default();
+ let mut len = std::mem::size_of_val(&out);
+
+ // SAFETY: syscall
+ unsafe {
+ if libc::sysctlbyname(
+ name.as_ptr().cast(),
+ (&mut out as *mut T).cast(),
+ &mut len,
+ std::ptr::null_mut(),
+ 0,
+ ) != 0
+ {
+ // log?
+ T::default()
+ } else {
+ out
+ }
+ }
+}
+
+/// Retrieves an `i32` sysctl by name and casts it to the specified integer type.
+/// Returns the default value if retrieval fails or the value is out of bounds of
+/// the specified integer type.
+pub fn int_sysctl_by_name<T: TryFrom<i32> + Default>(name: &[u8]) -> T {
+ let val = sysctl_by_name::<i32>(name);
+ T::try_from(val).unwrap_or_default()
+}
+
+/// Retrieves a string sysctl by name. Returns an empty string if the retrieval
+/// fails or the string can't be converted to utf-8.
+pub fn sysctl_string(name: &[u8]) -> String {
+ let mut buf_len = 0;
+
+ // SAFETY: syscalls
+ let string_buf = unsafe {
+ // Retrieve the size of the string (including null terminator)
+ if libc::sysctlbyname(
+ name.as_ptr().cast(),
+ std::ptr::null_mut(),
+ &mut buf_len,
+ std::ptr::null_mut(),
+ 0,
+ ) != 0
+ || buf_len <= 1
+ {
+ return String::new();
+ }
+
+ let mut buff = Vec::new();
+ buff.resize(buf_len, 0);
+
+ if libc::sysctlbyname(
+ name.as_ptr().cast(),
+ buff.as_mut_ptr().cast(),
+ &mut buf_len,
+ std::ptr::null_mut(),
+ 0,
+ ) != 0
+ {
+ return String::new();
+ }
+
+ buff.pop(); // remove null terminator
+ buff
+ };
+
+ String::from_utf8(string_buf).unwrap_or_default()
+}
+
+extern "C" {
+ /// From <usr/include/mach/mach_traps.h>, this retrieves the normal PID for
+ /// the specified task as the syscalls from BSD use PIDs, not mach ports.
+ ///
+ /// This seems to be marked as "obsolete" in the header, but of course being
+ /// Apple, there is no mention of a replacement function or when/if it might
+ /// eventually disappear.
+ pub fn pid_for_task(task: mach_port_name_t, pid: *mut i32) -> kern_return_t;
+
+ /// Fomr <user/include/mach/thread_act.h>, this retrieves thread info for the
+ /// for the specified thread.
+ ///
+ /// Note that the info_size parameter is actually the size of the thread_info / 4
+ /// as it is the number of words in the thread info
+ pub fn thread_info(
+ thread: u32,
+ flavor: u32,
+ thread_info: *mut i32,
+ info_size: *mut u32,
+ ) -> kern_return_t;
+}