use super::*; use format::{MiscInfoFlags, MINIDUMP_MISC_INFO_2 as MDRawMiscInfo}; use std::time::Duration; /// From #[repr(C)] #[derive(Copy, Clone)] struct TimeValue { seconds: i32, microseconds: i32, } impl From 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 , 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 , 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 { let mut info_section = MemoryWriter::::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::() 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::::uninit(); let size = std::mem::size_of::() 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 // https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/kern/kern_resource.c#L1215 // The basic task info keeps the timings for all of the terminated threads let basic_info = dumper.task_info::().ok(); // THe thread times info keeps the timings for all of the living threads let thread_times_info = dumper.task_info::().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) } }