395 lines
12 KiB
C++
395 lines
12 KiB
C++
|
// 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 <Availability.h>
|
|||
|
#include <stddef.h>
|
|||
|
#include <sys/sysctl.h>
|
|||
|
#include <sys/types.h>
|
|||
|
#include <sys/utsname.h>
|
|||
|
|
|||
|
#include <algorithm>
|
|||
|
|
|||
|
#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 <typename T>
|
|||
|
int ReadIntSysctlByName_NoLog(const char* name, T* value) {
|
|||
|
size_t value_len = sizeof(*value);
|
|||
|
return sysctlbyname(name, value, &value_len, nullptr, 0);
|
|||
|
}
|
|||
|
|
|||
|
template <typename T>
|
|||
|
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 <typename T>
|
|||
|
T CastIntSysctlByName(const char* name, T default_value) {
|
|||
|
int int_value = ReadIntSysctlByName<int>(name, default_value);
|
|||
|
return InRangeCast<T>(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<uint16_t>("machdep.cpu.family", 0);
|
|||
|
uint8_t model = CastIntSysctlByName<uint8_t>("machdep.cpu.model", 0);
|
|||
|
uint8_t stepping = CastIntSysctlByName<uint8_t>("machdep.cpu.stepping", 0);
|
|||
|
|
|||
|
return (family << 16) | (model << 8) | stepping;
|
|||
|
#elif defined(ARCH_CPU_ARM64)
|
|||
|
// TODO(macos_arm64): Verify and test.
|
|||
|
return CastIntSysctlByName<uint32_t>("hw.cpufamily", 0);
|
|||
|
#else
|
|||
|
#error port to your architecture
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
uint8_t SystemSnapshotMac::CPUCount() const {
|
|||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|||
|
return CastIntSysctlByName<uint8_t>("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<uint64_t>("hw.cpufrequency", 0);
|
|||
|
*max_hz = ReadIntSysctlByName<uint64_t>("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<uint32_t>("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<uint64_t>("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<uint64_t>("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
|