summaryrefslogtreecommitdiffstats
path: root/third_party/rust/crash-context/src/mac/resource.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/crash-context/src/mac/resource.rs')
-rw-r--r--third_party/rust/crash-context/src/mac/resource.rs516
1 files changed, 516 insertions, 0 deletions
diff --git a/third_party/rust/crash-context/src/mac/resource.rs b/third_party/rust/crash-context/src/mac/resource.rs
new file mode 100644
index 0000000000..c93acfe97a
--- /dev/null
+++ b/third_party/rust/crash-context/src/mac/resource.rs
@@ -0,0 +1,516 @@
+//! Contains types and helpers for dealing with `EXC_RESOURCE` exceptions.
+//!
+//! `EXC_RESOURCE` exceptions embed details about the resource and the limits
+//! it exceeded within the `code` and, in some cases `subcode`, fields of the exception
+//!
+//! See <https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/kern/exc_resource.h>
+//! for the various constants and decoding of exception information wrapped in
+//! this module.
+
+use mach2::exception_types::EXC_RESOURCE;
+use std::time::Duration;
+
+/// The details for an `EXC_RESOURCE` exception as retrieved from the exception's
+/// code and subcode
+pub enum ResourceException {
+ /// This is sent by the kernel when the CPU usage monitor is tripped. Possibly fatal.
+ Cpu(CpuResourceException),
+ /// This is sent by the kernel when the platform idle wakeups monitor is tripped. Possibly fatal.
+ Wakeups(WakeupsResourceException),
+ /// This is sent by the kernel when a task crosses its high watermark memory limit. Never fatal at least on current MacOS versions.
+ Memory(MemoryResourceException),
+ /// This is sent by the kernel when a task crosses its I/O limits. Never fatal.
+ Io(IoResourceException),
+ /// This is sent by the kernel when a task crosses its thread limit. Always fatal.
+ Threads(ThreadsResourceException),
+ /// This is sent by the kernel when the process is leaking ipc ports and has
+ /// filled its port space. Always fatal.
+ Ports(PortsResourceException),
+ /// An unknown resource kind due to an addition to the set of possible
+ /// resource exception kinds in exc_resource.h
+ Unknown { kind: u8, flavor: u8 },
+}
+
+/// Each different resource exception type has 1 or more flavors that it can be,
+/// and while these most likely don't change often, we try to be forward
+/// compatible by not failing if a particular flavor is unknown
+#[derive(Copy, Clone, Debug)]
+pub enum Flavor<T: Copy + Clone + std::fmt::Debug> {
+ Known(T),
+ Unknown(u8),
+}
+
+impl<T: TryFrom<u8> + Copy + Clone + std::fmt::Debug> From<u64> for Flavor<T> {
+ #[inline]
+ fn from(code: u64) -> Self {
+ let flavor = resource_exc_flavor(code);
+ if let Ok(known) = T::try_from(flavor) {
+ Self::Known(known)
+ } else {
+ Self::Unknown(flavor)
+ }
+ }
+}
+
+impl<T: PartialEq + Copy + Clone + std::fmt::Debug> PartialEq<T> for Flavor<T> {
+ fn eq(&self, o: &T) -> bool {
+ match self {
+ Self::Known(flavor) => flavor == o,
+ Self::Unknown(_) => false,
+ }
+ }
+}
+
+/// Retrieves the resource exception kind from an exception code
+#[inline]
+pub fn resource_exc_kind(code: u64) -> u8 {
+ ((code >> 61) & 0x7) as u8
+}
+
+/// Retrieves the resource exception flavor from an exception code
+#[inline]
+pub fn resource_exc_flavor(code: u64) -> u8 {
+ ((code >> 58) & 0x7) as u8
+}
+
+impl super::ExceptionInfo {
+ /// If this is an `EXC_RESOURCE` exception, retrieves the exception metadata
+ /// from the code, otherwise returns `None`
+ pub fn resource_exception(&self) -> Option<ResourceException> {
+ if self.kind != EXC_RESOURCE {
+ return None;
+ }
+
+ let kind = resource_exc_kind(self.code);
+
+ let res_exc = if kind == ResourceKind::Cpu as u8 {
+ ResourceException::Cpu(CpuResourceException::from_exc_info(self.code, self.subcode))
+ } else if kind == ResourceKind::Wakeups as u8 {
+ ResourceException::Wakeups(WakeupsResourceException::from_exc_info(
+ self.code,
+ self.subcode,
+ ))
+ } else if kind == ResourceKind::Memory as u8 {
+ ResourceException::Memory(MemoryResourceException::from_exc_info(self.code))
+ } else if kind == ResourceKind::Io as u8 {
+ ResourceException::Io(IoResourceException::from_exc_info(self.code, self.subcode))
+ } else if kind == ResourceKind::Threads as u8 {
+ ResourceException::Threads(ThreadsResourceException::from_exc_info(self.code))
+ } else if kind == ResourceKind::Ports as u8 {
+ ResourceException::Ports(PortsResourceException::from_exc_info(self.code))
+ } else {
+ ResourceException::Unknown {
+ kind,
+ flavor: resource_exc_flavor(self.code),
+ }
+ };
+
+ Some(res_exc)
+ }
+}
+
+/// The types of resources that an `EXC_RESOURCE` exception can pertain to
+#[repr(u8)]
+pub enum ResourceKind {
+ Cpu = 1,
+ Wakeups = 2,
+ Memory = 3,
+ Io = 4,
+ Threads = 5,
+ Ports = 6,
+}
+
+/// The flavors for a [`CpuResourceException`]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[repr(u8)]
+pub enum CpuFlavor {
+ /// The process has surpassed its CPU limit
+ Monitor = 1,
+ /// The process has surpassed its CPU limit, and the process has been configured
+ /// to make this exception fatal
+ MonitorFatal = 2,
+}
+
+impl TryFrom<u8> for CpuFlavor {
+ type Error = ();
+
+ fn try_from(flavor: u8) -> Result<Self, Self::Error> {
+ match flavor {
+ 1 => Ok(Self::Monitor),
+ 2 => Ok(Self::MonitorFatal),
+ _ => Err(()),
+ }
+ }
+}
+
+/// These exceptions _may_ be fatal. They are not fatal by default at task
+/// creation but can be made fatal by calling `proc_rlimit_control` with
+/// `RLIMIT_CPU_USAGE_MONITOR` as the second argument and `CPUMON_MAKE_FATAL`
+/// set in the flags. The flavor extracted from the exception code determines if
+/// the exception is fatal.
+///
+/// [Kernel code](https://github.com/apple-oss-distributions/xnu/blob/e7776783b89a353188416a9a346c6cdb4928faad/osfmk/kern/thread.c#L2475-L2616)
+#[derive(Copy, Clone, Debug)]
+pub struct CpuResourceException {
+ pub flavor: Flavor<CpuFlavor>,
+ /// If the exception is fatal. Currently only true if the flavor is [`CpuFlavor::MonitorFatal`]
+ pub is_fatal: bool,
+ /// The time period in which the CPU limit was surpassed
+ pub observation_interval: Duration,
+ /// The CPU % limit
+ pub limit: u8,
+ /// The CPU % consumed by the task
+ pub consumed: u8,
+}
+
+impl CpuResourceException {
+ /*
+ * code:
+ * +-----------------------------------------------+
+ * |[63:61] RESOURCE |[60:58] FLAVOR_CPU_ |[57:32] |
+ * |_TYPE_CPU |MONITOR[_FATAL] |Unused |
+ * +-----------------------------------------------+
+ * |[31:7] Interval (sec) | [6:0] CPU limit (%)|
+ * +-----------------------------------------------+
+ *
+ * subcode:
+ * +-----------------------------------------------+
+ * | | [6:0] % of CPU |
+ * | | actually consumed |
+ * +-----------------------------------------------+
+ *
+ */
+ #[inline]
+ pub fn from_exc_info(code: u64, subcode: Option<u64>) -> Self {
+ debug_assert_eq!(resource_exc_kind(code), ResourceKind::Cpu as u8);
+
+ let flavor = Flavor::from(code);
+ let interval_seconds = (code >> 7) & 0x1ffffff;
+ let limit = (code & 0x7f) as u8;
+ let consumed = subcode.map_or(0, |sc| sc & 0x7f) as u8;
+
+ // The default is that cpu resource exceptions are not fatal, so
+ // we only check the flavor against the (currently) one known value
+ // that indicates the exception is fatal
+ Self {
+ flavor,
+ is_fatal: flavor == CpuFlavor::MonitorFatal,
+ observation_interval: Duration::from_secs(interval_seconds),
+ limit,
+ consumed,
+ }
+ }
+}
+
+/// The flavors for a [`WakeupsResourceException`]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[repr(u8)]
+pub enum WakeupsFlavor {
+ Monitor = 1,
+}
+
+impl TryFrom<u8> for WakeupsFlavor {
+ type Error = ();
+
+ fn try_from(flavor: u8) -> Result<Self, Self::Error> {
+ match flavor {
+ 1 => Ok(Self::Monitor),
+ _ => Err(()),
+ }
+ }
+}
+
+/// These exceptions may be fatal. They are not fatal by default at task
+/// creation, but can be made fatal by calling `proc_rlimit_control` with
+/// `RLIMIT_WAKEUPS_MONITOR` as the second argument and `WAKEMON_MAKE_FATAL`
+/// set in the flags. Calling [`proc_get_wakemon_params`](https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/libsyscall/wrappers/libproc/libproc.c#L592-L608)
+/// determines whether these exceptions are fatal.
+///
+/// [Kernel source](https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/kern/task.c#L7501-L7580)
+pub struct WakeupsResourceException {
+ pub flavor: Flavor<WakeupsFlavor>,
+ /// The time period in which the number of wakeups was surpassed
+ pub observation_interval: Duration,
+ /// The number of wakeups permitted per second
+ pub permitted: u32,
+ /// The number of wakeups observed per second
+ pub observed: u32,
+}
+
+impl WakeupsResourceException {
+ /*
+ * code:
+ * +-----------------------------------------------+
+ * |[63:61] RESOURCE |[60:58] FLAVOR_ |[57:32] |
+ * |_TYPE_WAKEUPS |WAKEUPS_MONITOR |Unused |
+ * +-----------------------------------------------+
+ * | [31:20] Observation | [19:0] # of wakeups |
+ * | interval (sec) | permitted (per sec) |
+ * +-----------------------------------------------+
+ *
+ * subcode:
+ * +-----------------------------------------------+
+ * | | [19:0] # of wakeups |
+ * | | observed (per sec) |
+ * +-----------------------------------------------+
+ *
+ */
+ #[inline]
+ pub fn from_exc_info(code: u64, subcode: Option<u64>) -> Self {
+ debug_assert_eq!(resource_exc_kind(code), ResourceKind::Wakeups as u8);
+
+ let flavor = Flavor::from(code);
+ // Note that Apple has a bug in exc_resource.h where the masks in the
+ // decode macros for the interval and the permitted wakeups have been swapped
+ let interval_seconds = (code >> 20) & 0xfff;
+ let permitted = (code & 0xfffff) as u32;
+ let observed = subcode.map_or(0, |sc| sc & 0xfffff) as u32;
+
+ Self {
+ flavor,
+ observation_interval: Duration::from_secs(interval_seconds),
+ permitted,
+ observed,
+ }
+ }
+}
+
+/// The flavors for a [`MemoryResourceException`]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[repr(u8)]
+pub enum MemoryFlavor {
+ HighWatermark = 1,
+}
+
+impl TryFrom<u8> for MemoryFlavor {
+ type Error = ();
+
+ fn try_from(flavor: u8) -> Result<Self, Self::Error> {
+ match flavor {
+ 1 => Ok(Self::HighWatermark),
+ _ => Err(()),
+ }
+ }
+}
+
+/// These exceptions, as of this writing, are never fatal.
+///
+/// While memory exceptions _can_ be fatal, this appears to only be possible if
+/// the kernel is built with `CONFIG_JETSAM` or in `DEVELOPMENT` or `DEBUG` modes,
+/// so as of now, they should never be considered fatal, at least on `MacOS`
+///
+/// [Kernel source](https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/kern/task.c#L6767-L6874)
+pub struct MemoryResourceException {
+ pub flavor: Flavor<MemoryFlavor>,
+ /// The limit in MiB of the high watermark
+ pub limit_mib: u16,
+}
+
+impl MemoryResourceException {
+ /*
+ * code:
+ * +------------------------------------------------+
+ * |[63:61] RESOURCE |[60:58] FLAVOR_HIGH_ |[57:32] |
+ * |_TYPE_MEMORY |WATERMARK |Unused |
+ * +------------------------------------------------+
+ * | | [12:0] HWM limit (MB)|
+ * +------------------------------------------------+
+ *
+ * subcode:
+ * +------------------------------------------------+
+ * | unused |
+ * +------------------------------------------------+
+ *
+ */
+ #[inline]
+ pub fn from_exc_info(code: u64) -> Self {
+ debug_assert_eq!(resource_exc_kind(code), ResourceKind::Memory as u8);
+
+ let flavor = Flavor::from(code);
+ let limit_mib = (code & 0x1fff) as u16;
+
+ Self { flavor, limit_mib }
+ }
+}
+
+/// The flavors for an [`IoResourceException`]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[repr(u8)]
+pub enum IoFlavor {
+ PhysicalWrites = 1,
+ LogicalWrites = 2,
+}
+
+impl TryFrom<u8> for IoFlavor {
+ type Error = ();
+
+ fn try_from(flavor: u8) -> Result<Self, Self::Error> {
+ match flavor {
+ 1 => Ok(Self::PhysicalWrites),
+ 2 => Ok(Self::LogicalWrites),
+ _ => Err(()),
+ }
+ }
+}
+
+/// These exceptions are never fatal.
+///
+/// [Kernel source](https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/kern/task.c#L7739-L7792)
+pub struct IoResourceException {
+ pub flavor: Flavor<MemoryFlavor>,
+ /// The time period in which the I/O limit was surpassed
+ pub observation_interval: Duration,
+ /// The I/O limit in MiB of the high watermark
+ pub limit_mib: u16,
+ /// The observed I/O in MiB
+ pub observed_mib: u16,
+}
+
+impl IoResourceException {
+ /*
+ * code:
+ * +-----------------------------------------------+
+ * |[63:61] RESOURCE |[60:58] FLAVOR_IO_ |[57:32] |
+ * |_TYPE_IO |PHYSICAL/LOGICAL |Unused |
+ * +-----------------------------------------------+
+ * |[31:15] Interval (sec) | [14:0] Limit (MB) |
+ * +-----------------------------------------------+
+ *
+ * subcode:
+ * +-----------------------------------------------+
+ * | | [14:0] I/O Count |
+ * | | (in MB) |
+ * +-----------------------------------------------+
+ *
+ */
+ #[inline]
+ pub fn from_exc_info(code: u64, subcode: Option<u64>) -> Self {
+ debug_assert_eq!(resource_exc_kind(code), ResourceKind::Io as u8);
+
+ let flavor = Flavor::from(code);
+ let interval_seconds = (code >> 15) & 0x1ffff;
+ let limit_mib = (code & 0x7fff) as u16;
+ let observed_mib = subcode.map_or(0, |sc| sc & 0x7fff) as u16;
+
+ Self {
+ flavor,
+ observation_interval: Duration::from_secs(interval_seconds),
+ limit_mib,
+ observed_mib,
+ }
+ }
+}
+
+/// The flavors for a [`ThreadsResourceException`]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[repr(u8)]
+pub enum ThreadsFlavor {
+ HighWatermark = 1,
+}
+
+impl TryFrom<u8> for ThreadsFlavor {
+ type Error = ();
+
+ fn try_from(flavor: u8) -> Result<Self, Self::Error> {
+ match flavor {
+ 1 => Ok(Self::HighWatermark),
+ _ => Err(()),
+ }
+ }
+}
+
+/// This exception is provided for completeness sake, but is only possible if
+/// the kernel is built in `DEVELOPMENT` or `DEBUG` modes.
+///
+/// [Kernel source](https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/kern/thread.c#L2575-L2620)
+pub struct ThreadsResourceException {
+ pub flavor: Flavor<ThreadsFlavor>,
+ /// The thread limit
+ pub limit: u16,
+}
+
+impl ThreadsResourceException {
+ /*
+ * code:
+ * +--------------------------------------------------+
+ * |[63:61] RESOURCE |[60:58] FLAVOR_ |[57:32] |
+ * |_TYPE_THREADS |THREADS_HIGH_WATERMARK |Unused |
+ * +--------------------------------------------------+
+ * |[31:15] Unused | [14:0] Limit |
+ * +--------------------------------------------------+
+ *
+ * subcode:
+ * +-----------------------------------------------+
+ * | | Unused |
+ * | | |
+ * +-----------------------------------------------+
+ *
+ */
+ #[inline]
+ pub fn from_exc_info(code: u64) -> Self {
+ debug_assert_eq!(resource_exc_kind(code), ResourceKind::Threads as u8);
+
+ let flavor = Flavor::from(code);
+ let limit = (code & 0x7fff) as u16;
+
+ Self { flavor, limit }
+ }
+}
+
+/// The flavors for a [`PortsResourceException`]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[repr(u8)]
+pub enum PortsFlavor {
+ SpaceFull = 1,
+}
+
+impl TryFrom<u8> for PortsFlavor {
+ type Error = ();
+
+ fn try_from(flavor: u8) -> Result<Self, Self::Error> {
+ match flavor {
+ 1 => Ok(Self::SpaceFull),
+ _ => Err(()),
+ }
+ }
+}
+
+/// This exception is always fatal, and in fact I'm unsure if this exception
+/// is even observable, as the kernel will kill the offending process if
+/// the port space is full
+///
+/// [Kernel source](https://github.com/apple-oss-distributions/xnu/blob/e7776783b89a353188416a9a346c6cdb4928faad/osfmk/kern/task.c#L7907-L7969)
+pub struct PortsResourceException {
+ pub flavor: Flavor<ThreadsFlavor>,
+ /// The number of allocated ports
+ pub allocated: u32,
+}
+
+impl PortsResourceException {
+ /*
+ * code:
+ * +-----------------------------------------------+
+ * |[63:61] RESOURCE |[60:58] FLAVOR_ |[57:32] |
+ * |_TYPE_PORTS |PORT_SPACE_FULL |Unused |
+ * +-----------------------------------------------+
+ * | [31:24] Unused | [23:0] # of ports |
+ * | | allocated |
+ * +-----------------------------------------------+
+ *
+ * subcode:
+ * +-----------------------------------------------+
+ * | | Unused |
+ * | | |
+ * +-----------------------------------------------+
+ *
+ */
+ #[inline]
+ pub fn from_exc_info(code: u64) -> Self {
+ debug_assert_eq!(resource_exc_kind(code), ResourceKind::Ports as u8);
+
+ let flavor = Flavor::from(code);
+ let allocated = (code & 0xffffff) as u32;
+
+ Self { flavor, allocated }
+ }
+}