Source code

Revision control

Copy as Markdown

Other Tools

use super::*;
use format::{MiscInfoFlags, MINIDUMP_MISC_INFO_2 as MDRawMiscInfo};
use std::time::Duration;
/// From <usr/include/mach/time_value.h>
#[repr(C)]
#[derive(Copy, Clone)]
struct TimeValue {
seconds: i32,
microseconds: i32,
}
impl From<TimeValue> for Duration {
fn from(tv: TimeValue) -> Self {
let mut seconds = tv.seconds as u64;
let mut microseconds = tv.microseconds as u32;
// This _probably_ will never happen, but this will avoid a panic in
// Duration::new() if it does
if tv.microseconds >= 1000000 {
seconds += 1;
microseconds -= 1000000;
}
Duration::new(seconds, microseconds * 1000)
}
}
/// From <usr/include/mach/task_info.h>, this includes basic information about
/// a task.
#[repr(C, packed(4))]
struct MachTaskBasicInfo {
/// Virtual memory size in bytes
virtual_size: u64,
/// Resident memory size in bytes
resident_size: u64,
/// Maximum resident memory size in bytes
resident_size_max: u64,
/// Total user run time for terminated threads
user_time: TimeValue,
/// Total system run time for terminated threads
system_time: TimeValue,
/// Default policy for new threads
policy: i32,
/// Suspend count for task
suspend_count: i32,
}
impl mach::TaskInfo for MachTaskBasicInfo {
const FLAVOR: u32 = mach::task_info::MACH_TASK_BASIC_INFO;
}
/// From <usr/include/mach/task_info.h>, this includes times for currently
/// live threads in the task.
#[repr(C, packed(4))]
struct TaskThreadsTimeInfo {
/// Total user run time for live threads
user_time: TimeValue,
/// total system run time for live threads
system_time: TimeValue,
}
impl mach::TaskInfo for TaskThreadsTimeInfo {
const FLAVOR: u32 = mach::task_info::TASK_THREAD_TIMES_INFO;
}
impl MinidumpWriter {
/// Writes the [`MDStreamType::MiscInfoStream`] stream.
///
/// On MacOS, we write a [`minidump_common::format::MINIDUMP_MISC_INFO_2`]
/// to this stream, which includes the start time of the process at second
/// granularity, and the (approximate) amount of time spent in user and
/// system (kernel) time for the lifetime of the task. We attempt to also
/// retrieve power ie CPU usage statistics, though this information is only
/// currently available on x86_64, not aarch64 at the moment.
pub(crate) fn write_misc_info(
&mut self,
buffer: &mut DumpBuf,
dumper: &TaskDumper,
) -> Result<MDRawDirectory, WriterError> {
let mut info_section = MemoryWriter::<MDRawMiscInfo>::alloc(buffer)?;
let dirent = MDRawDirectory {
stream_type: MDStreamType::MiscInfoStream as u32,
location: info_section.location(),
};
let pid = dumper.pid_for_task()?;
let mut misc_info = MDRawMiscInfo {
size_of_info: std::mem::size_of::<MDRawMiscInfo>() as u32,
flags1: MiscInfoFlags::MINIDUMP_MISC1_PROCESS_ID.bits()
| MiscInfoFlags::MINIDUMP_MISC1_PROCESS_TIMES.bits()
| MiscInfoFlags::MINIDUMP_MISC1_PROCESSOR_POWER_INFO.bits(),
process_id: pid as u32,
process_create_time: 0,
process_user_time: 0,
process_kernel_time: 0,
processor_max_mhz: 0,
processor_current_mhz: 0,
processor_mhz_limit: 0,
processor_max_idle_state: 0,
processor_current_idle_state: 0,
};
// Note that both Breakpad and Crashpad use `sysctl CTL_KERN, KERN_PROC, KERN_PROC_PID`
// to retrieve the process start time, but none of the structures that
// are filled in by that call are in libc at the moment, and `proc_pidinfo`
// seems to work just fine, so using that instead.
//
// SAFETY: syscall
misc_info.process_create_time = unsafe {
// Breakpad was using an old method to retrieve this, let's try the
// BSD method instead which is already implemented in libc
let mut proc_info = std::mem::MaybeUninit::<libc::proc_bsdinfo>::uninit();
let size = std::mem::size_of::<libc::proc_bsdinfo>() as i32;
if libc::proc_pidinfo(
pid,
libc::PROC_PIDTBSDINFO,
0,
proc_info.as_mut_ptr().cast(),
size,
) == size
{
let proc_info = proc_info.assume_init();
proc_info.pbi_start_tvsec as u32
} else {
0
}
};
// Note that Breakpad is using `getrusage` to retrieve this information,
// however that is wrong, as it can only retrieve the process usage information
// for the current or children processes, not an external process, so
// we use the Crashpad method, which is itself based off of the XNU
// method of retrieving the process times
// The basic task info keeps the timings for all of the terminated threads
let basic_info = dumper.task_info::<MachTaskBasicInfo>().ok();
// THe thread times info keeps the timings for all of the living threads
let thread_times_info = dumper.task_info::<TaskThreadsTimeInfo>().ok();
let user_time = basic_info
.as_ref()
.map(|bi| Duration::from(bi.user_time))
.unwrap_or_default()
+ thread_times_info
.as_ref()
.map(|tt| Duration::from(tt.user_time))
.unwrap_or_default();
let system_time = basic_info
.as_ref()
.map(|bi| Duration::from(bi.system_time))
.unwrap_or_default()
+ thread_times_info
.as_ref()
.map(|tt| Duration::from(tt.system_time))
.unwrap_or_default();
misc_info.process_user_time = user_time.as_secs() as u32;
misc_info.process_kernel_time = system_time.as_secs() as u32;
// Note that neither of these two keys are present on aarch64, at least atm
let max: u64 = mach::sysctl_by_name(b"hw.cpufrequency_max\0");
let freq: u64 = mach::sysctl_by_name(b"hw.cpufrequency\0");
let max = (max / 1000 * 1000) as u32;
let current = (freq / 1000 * 1000) as u32;
misc_info.processor_max_mhz = max;
misc_info.processor_mhz_limit = max;
misc_info.processor_current_mhz = current;
info_section.set_value(buffer, misc_info)?;
Ok(dirent)
}
}