summaryrefslogtreecommitdiffstats
path: root/third_party/rust/crash-context/src/mac/resource.rs
blob: c93acfe97a4085077708c4e0f0f8caaf9e7647f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
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 }
    }
}