// Copyright 2014 The Crashpad Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "snapshot/mac/system_snapshot_mac.h" #include #include #include #include #include #include #include "base/logging.h" #include "base/notreached.h" #include "base/scoped_clear_last_error.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "snapshot/cpu_context.h" #include "snapshot/mac/process_reader_mac.h" #include "snapshot/posix/timezone.h" #include "util/mac/mac_util.h" #include "util/mac/sysctl.h" #include "util/numeric/in_range_cast.h" namespace crashpad { namespace { template int ReadIntSysctlByName_NoLog(const char* name, T* value) { size_t value_len = sizeof(*value); return sysctlbyname(name, value, &value_len, nullptr, 0); } template T ReadIntSysctlByName(const char* name, T default_value) { T value; if (ReadIntSysctlByName_NoLog(name, &value) != 0) { PLOG(WARNING) << "sysctlbyname " << name; return default_value; } return value; } template T CastIntSysctlByName(const char* name, T default_value) { int int_value = ReadIntSysctlByName(name, default_value); return InRangeCast(int_value, default_value); } #if defined(ARCH_CPU_X86_FAMILY) void CallCPUID(uint32_t leaf, uint32_t* eax, uint32_t* ebx, uint32_t* ecx, uint32_t* edx) { asm("cpuid" : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx) : "a"(leaf), "b"(0), "c"(0), "d"(0)); } #endif } // namespace namespace internal { SystemSnapshotMac::SystemSnapshotMac() : SystemSnapshot(), os_version_full_(), os_version_build_(), process_reader_(nullptr), snapshot_time_(nullptr), os_version_major_(0), os_version_minor_(0), os_version_bugfix_(0), os_server_(false), initialized_() { } SystemSnapshotMac::~SystemSnapshotMac() { } void SystemSnapshotMac::Initialize(ProcessReaderMac* process_reader, const timeval* snapshot_time) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); process_reader_ = process_reader; snapshot_time_ = snapshot_time; // MacOSVersionComponents() logs its own warnings if it can’t figure anything // out. It’s not fatal if this happens. The default values are reasonable. std::string os_version_string; MacOSVersionComponents(&os_version_major_, &os_version_minor_, &os_version_bugfix_, &os_version_build_, &os_server_, &os_version_string); std::string uname_string; utsname uts; if (uname(&uts) != 0) { PLOG(WARNING) << "uname"; } else { uname_string = base::StringPrintf( "%s %s %s %s", uts.sysname, uts.release, uts.version, uts.machine); } if (!os_version_string.empty()) { if (!uname_string.empty()) { os_version_full_ = base::StringPrintf( "%s; %s", os_version_string.c_str(), uname_string.c_str()); } else { os_version_full_ = os_version_string; } } else { os_version_full_ = uname_string; } INITIALIZATION_STATE_SET_VALID(initialized_); } CPUArchitecture SystemSnapshotMac::GetCPUArchitecture() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); #if defined(ARCH_CPU_X86_FAMILY) return process_reader_->Is64Bit() ? kCPUArchitectureX86_64 : kCPUArchitectureX86; #elif defined(ARCH_CPU_ARM64) return kCPUArchitectureARM64; #else #error port to your architecture #endif } uint32_t SystemSnapshotMac::CPURevision() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); #if defined(ARCH_CPU_X86_FAMILY) // machdep.cpu.family and machdep.cpu.model already take the extended family // and model IDs into account. See 10.9.2 xnu-2422.90.20/osfmk/i386/cpuid.c // cpuid_set_generic_info(). uint16_t family = CastIntSysctlByName("machdep.cpu.family", 0); uint8_t model = CastIntSysctlByName("machdep.cpu.model", 0); uint8_t stepping = CastIntSysctlByName("machdep.cpu.stepping", 0); return (family << 16) | (model << 8) | stepping; #elif defined(ARCH_CPU_ARM64) // TODO(macos_arm64): Verify and test. return CastIntSysctlByName("hw.cpufamily", 0); #else #error port to your architecture #endif } uint8_t SystemSnapshotMac::CPUCount() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return CastIntSysctlByName("hw.ncpu", 1); } std::string SystemSnapshotMac::CPUVendor() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); #if defined(ARCH_CPU_X86_FAMILY) return ReadStringSysctlByName("machdep.cpu.vendor", true); #elif defined(ARCH_CPU_ARM64) return ReadStringSysctlByName("machdep.cpu.brand_string", true); #else #error port to your architecture #endif } void SystemSnapshotMac::CPUFrequency( uint64_t* current_hz, uint64_t* max_hz) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); #if defined(ARCH_CPU_X86_FAMILY) *current_hz = ReadIntSysctlByName("hw.cpufrequency", 0); *max_hz = ReadIntSysctlByName("hw.cpufrequency_max", 0); #elif defined(ARCH_CPU_ARM64) // TODO(https://crashpad.chromium.org/bug/352): When production arm64 // hardware is available, determine whether CPU frequency is visible anywhere // (likely via a sysctl or via IOKit) and use it if feasible. *current_hz = 0; *max_hz = 0; #else #error port to your architecture #endif } uint32_t SystemSnapshotMac::CPUX86Signature() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); #if defined(ARCH_CPU_X86_FAMILY) return ReadIntSysctlByName("machdep.cpu.signature", 0); #else NOTREACHED(); return 0; #endif } uint64_t SystemSnapshotMac::CPUX86Features() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); #if defined(ARCH_CPU_X86_FAMILY) return ReadIntSysctlByName("machdep.cpu.feature_bits", 0); #else NOTREACHED(); return 0; #endif } uint64_t SystemSnapshotMac::CPUX86ExtendedFeatures() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); #if defined(ARCH_CPU_X86_FAMILY) return ReadIntSysctlByName("machdep.cpu.extfeature_bits", 0); #else NOTREACHED(); return 0; #endif } uint32_t SystemSnapshotMac::CPUX86Leaf7Features() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); #if defined(ARCH_CPU_X86_FAMILY) // The machdep.cpu.leaf7_feature_bits sysctl isn’t supported prior to OS X // 10.7, so read this by calling cpuid directly. // // machdep.cpu.max_basic could be used to check whether to read the leaf, but // that sysctl isn’t supported prior to Mac OS X 10.6, so read the maximum // basic leaf by calling cpuid directly as well. All CPUs that Apple is known // to have shipped should support a maximum basic leaf value of at least 0xa. uint32_t eax, ebx, ecx, edx; CallCPUID(0, &eax, &ebx, &ecx, &edx); if (eax < 7) { return 0; } CallCPUID(7, &eax, &ebx, &ecx, &edx); return ebx; #else NOTREACHED(); return 0; #endif } bool SystemSnapshotMac::CPUX86SupportsDAZ() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); #if defined(ARCH_CPU_X86_FAMILY) // The correct way to check for denormals-as-zeros (DAZ) support is to examine // mxcsr mask, which can be done with fxsave. See Intel Software Developer’s // Manual, Volume 1: Basic Architecture (253665-051), 11.6.3 “Checking for the // DAZ Flag in the MXCSR Register”. Note that since this function tests for // DAZ support in the CPU, it checks the mxcsr mask. Testing mxcsr would // indicate whether DAZ is actually enabled, which is a per-thread context // concern. // // All CPUs that Apple is known to have shipped should support DAZ. // Test for fxsave support. uint64_t features = CPUX86Features(); if (!(features & (UINT64_C(1) << 24))) { return false; } // Call fxsave. #if defined(ARCH_CPU_X86) CPUContextX86::Fxsave fxsave __attribute__((aligned(16))) = {}; #elif defined(ARCH_CPU_X86_64) CPUContextX86_64::Fxsave fxsave __attribute__((aligned(16))) = {}; #endif static_assert(sizeof(fxsave) == 512, "fxsave size"); static_assert(offsetof(decltype(fxsave), mxcsr_mask) == 28, "mxcsr_mask offset"); asm("fxsave %0" : "=m"(fxsave)); // Test the DAZ bit. return fxsave.mxcsr_mask & (1 << 6); #else NOTREACHED(); return false; #endif } SystemSnapshot::OperatingSystem SystemSnapshotMac::GetOperatingSystem() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return kOperatingSystemMacOSX; } bool SystemSnapshotMac::OSServer() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return os_server_; } void SystemSnapshotMac::OSVersion(int* major, int* minor, int* bugfix, std::string* build) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); *major = os_version_major_; *minor = os_version_minor_; *bugfix = os_version_bugfix_; build->assign(os_version_build_); } std::string SystemSnapshotMac::OSVersionFull() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return os_version_full_; } std::string SystemSnapshotMac::MachineDescription() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::string model; std::string board_id; MacModelAndBoard(&model, &board_id); if (!model.empty()) { if (!board_id.empty()) { return base::StringPrintf("%s (%s)", model.c_str(), board_id.c_str()); } return model; } if (!board_id.empty()) { return base::StringPrintf("(%s)", board_id.c_str()); } return std::string(); } bool SystemSnapshotMac::NXEnabled() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); int value; if (ReadIntSysctlByName_NoLog("kern.nx", &value) != 0) { { // Support for the kern.nx sysctlbyname is compiled out of production // kernels on macOS 10.14.5 and later, although it’s available in // development and debug kernels. Compare 10.14.3 // xnu-4903.241.1/bsd/kern/kern_sysctl.c to 10.15.0 // xnu-6153.11.26/bsd/kern/kern_sysctl.c (10.14.4 and 10.14.5 xnu source // are not yet available). In newer production kernels, NX is always // enabled. See 10.15.0 xnu-6153.11.26/osfmk/x86_64/pmap.c nx_enabled. #if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_14 const bool nx_always_enabled = true; #else // DT >= 10.14 base::ScopedClearLastError reset_errno; const bool nx_always_enabled = MacOSVersionNumber() >= 10'14'00; #endif // DT >= 10.14 if (nx_always_enabled) { return true; } } // Even if sysctlbyname should have worked, NX is enabled by default in all // supported configurations, so return true even while warning. PLOG(WARNING) << "sysctlbyname kern.nx"; return true; } return value; } void SystemSnapshotMac::TimeZone(DaylightSavingTimeStatus* dst_status, int* standard_offset_seconds, int* daylight_offset_seconds, std::string* standard_name, std::string* daylight_name) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); internal::TimeZone(*snapshot_time_, dst_status, standard_offset_seconds, daylight_offset_seconds, standard_name, daylight_name); } } // namespace internal } // namespace crashpad