1 // Copyright 2014 The Crashpad Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "snapshot/mac/system_snapshot_mac.h"
16 
17 #include <Availability.h>
18 #include <stddef.h>
19 #include <sys/sysctl.h>
20 #include <sys/types.h>
21 #include <sys/utsname.h>
22 
23 #include <algorithm>
24 
25 #include "base/logging.h"
26 #include "base/notreached.h"
27 #include "base/scoped_clear_last_error.h"
28 #include "base/strings/stringprintf.h"
29 #include "build/build_config.h"
30 #include "snapshot/cpu_context.h"
31 #include "snapshot/mac/process_reader_mac.h"
32 #include "snapshot/posix/timezone.h"
33 #include "util/mac/mac_util.h"
34 #include "util/mac/sysctl.h"
35 #include "util/numeric/in_range_cast.h"
36 
37 namespace crashpad {
38 
39 namespace {
40 
41 template <typename T>
ReadIntSysctlByName_NoLog(const char * name,T * value)42 int ReadIntSysctlByName_NoLog(const char* name, T* value) {
43   size_t value_len = sizeof(*value);
44   return sysctlbyname(name, value, &value_len, nullptr, 0);
45 }
46 
47 template <typename T>
ReadIntSysctlByName(const char * name,T default_value)48 T ReadIntSysctlByName(const char* name, T default_value) {
49   T value;
50   if (ReadIntSysctlByName_NoLog(name, &value) != 0) {
51     PLOG(WARNING) << "sysctlbyname " << name;
52     return default_value;
53   }
54 
55   return value;
56 }
57 
58 template <typename T>
CastIntSysctlByName(const char * name,T default_value)59 T CastIntSysctlByName(const char* name, T default_value) {
60   int int_value = ReadIntSysctlByName<int>(name, default_value);
61   return InRangeCast<T>(int_value, default_value);
62 }
63 
64 #if defined(ARCH_CPU_X86_FAMILY)
CallCPUID(uint32_t leaf,uint32_t * eax,uint32_t * ebx,uint32_t * ecx,uint32_t * edx)65 void CallCPUID(uint32_t leaf,
66                uint32_t* eax,
67                uint32_t* ebx,
68                uint32_t* ecx,
69                uint32_t* edx) {
70   asm("cpuid"
71       : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
72       : "a"(leaf), "b"(0), "c"(0), "d"(0));
73 }
74 #endif
75 
76 }  // namespace
77 
78 namespace internal {
79 
SystemSnapshotMac()80 SystemSnapshotMac::SystemSnapshotMac()
81     : SystemSnapshot(),
82       os_version_full_(),
83       os_version_build_(),
84       process_reader_(nullptr),
85       snapshot_time_(nullptr),
86       os_version_major_(0),
87       os_version_minor_(0),
88       os_version_bugfix_(0),
89       os_server_(false),
90       initialized_() {
91 }
92 
~SystemSnapshotMac()93 SystemSnapshotMac::~SystemSnapshotMac() {
94 }
95 
Initialize(ProcessReaderMac * process_reader,const timeval * snapshot_time)96 void SystemSnapshotMac::Initialize(ProcessReaderMac* process_reader,
97                                    const timeval* snapshot_time) {
98   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
99 
100   process_reader_ = process_reader;
101   snapshot_time_ = snapshot_time;
102 
103   // MacOSVersionComponents() logs its own warnings if it can’t figure anything
104   // out. It’s not fatal if this happens. The default values are reasonable.
105   std::string os_version_string;
106   MacOSVersionComponents(&os_version_major_,
107                          &os_version_minor_,
108                          &os_version_bugfix_,
109                          &os_version_build_,
110                          &os_server_,
111                          &os_version_string);
112 
113   std::string uname_string;
114   utsname uts;
115   if (uname(&uts) != 0) {
116     PLOG(WARNING) << "uname";
117   } else {
118     uname_string = base::StringPrintf(
119         "%s %s %s %s", uts.sysname, uts.release, uts.version, uts.machine);
120   }
121 
122   if (!os_version_string.empty()) {
123     if (!uname_string.empty()) {
124       os_version_full_ = base::StringPrintf(
125           "%s; %s", os_version_string.c_str(), uname_string.c_str());
126     } else {
127       os_version_full_ = os_version_string;
128     }
129   } else {
130     os_version_full_ = uname_string;
131   }
132 
133   INITIALIZATION_STATE_SET_VALID(initialized_);
134 }
135 
GetCPUArchitecture() const136 CPUArchitecture SystemSnapshotMac::GetCPUArchitecture() const {
137   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
138 
139 #if defined(ARCH_CPU_X86_FAMILY)
140   return process_reader_->Is64Bit() ? kCPUArchitectureX86_64
141                                     : kCPUArchitectureX86;
142 #elif defined(ARCH_CPU_ARM64)
143   return kCPUArchitectureARM64;
144 #else
145 #error port to your architecture
146 #endif
147 }
148 
CPURevision() const149 uint32_t SystemSnapshotMac::CPURevision() const {
150   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
151 
152 #if defined(ARCH_CPU_X86_FAMILY)
153   // machdep.cpu.family and machdep.cpu.model already take the extended family
154   // and model IDs into account. See 10.9.2 xnu-2422.90.20/osfmk/i386/cpuid.c
155   // cpuid_set_generic_info().
156   uint16_t family = CastIntSysctlByName<uint16_t>("machdep.cpu.family", 0);
157   uint8_t model = CastIntSysctlByName<uint8_t>("machdep.cpu.model", 0);
158   uint8_t stepping = CastIntSysctlByName<uint8_t>("machdep.cpu.stepping", 0);
159 
160   return (family << 16) | (model << 8) | stepping;
161 #elif defined(ARCH_CPU_ARM64)
162   // TODO(macos_arm64): Verify and test.
163   return CastIntSysctlByName<uint32_t>("hw.cpufamily", 0);
164 #else
165 #error port to your architecture
166 #endif
167 }
168 
CPUCount() const169 uint8_t SystemSnapshotMac::CPUCount() const {
170   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
171   return CastIntSysctlByName<uint8_t>("hw.ncpu", 1);
172 }
173 
CPUVendor() const174 std::string SystemSnapshotMac::CPUVendor() const {
175   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
176 
177 #if defined(ARCH_CPU_X86_FAMILY)
178   return ReadStringSysctlByName("machdep.cpu.vendor", true);
179 #elif defined(ARCH_CPU_ARM64)
180   return ReadStringSysctlByName("machdep.cpu.brand_string", true);
181 #else
182 #error port to your architecture
183 #endif
184 }
185 
CPUFrequency(uint64_t * current_hz,uint64_t * max_hz) const186 void SystemSnapshotMac::CPUFrequency(
187     uint64_t* current_hz, uint64_t* max_hz) const {
188   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
189 #if defined(ARCH_CPU_X86_FAMILY)
190   *current_hz = ReadIntSysctlByName<uint64_t>("hw.cpufrequency", 0);
191   *max_hz = ReadIntSysctlByName<uint64_t>("hw.cpufrequency_max", 0);
192 #elif defined(ARCH_CPU_ARM64)
193   // TODO(https://crashpad.chromium.org/bug/352): When production arm64
194   // hardware is available, determine whether CPU frequency is visible anywhere
195   // (likely via a sysctl or via IOKit) and use it if feasible.
196   *current_hz = 0;
197   *max_hz = 0;
198 #else
199 #error port to your architecture
200 #endif
201 }
202 
CPUX86Signature() const203 uint32_t SystemSnapshotMac::CPUX86Signature() const {
204   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
205 
206 #if defined(ARCH_CPU_X86_FAMILY)
207   return ReadIntSysctlByName<uint32_t>("machdep.cpu.signature", 0);
208 #else
209   NOTREACHED();
210   return 0;
211 #endif
212 }
213 
CPUX86Features() const214 uint64_t SystemSnapshotMac::CPUX86Features() const {
215   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
216 
217 #if defined(ARCH_CPU_X86_FAMILY)
218   return ReadIntSysctlByName<uint64_t>("machdep.cpu.feature_bits", 0);
219 #else
220   NOTREACHED();
221   return 0;
222 #endif
223 }
224 
CPUX86ExtendedFeatures() const225 uint64_t SystemSnapshotMac::CPUX86ExtendedFeatures() const {
226   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
227 
228 #if defined(ARCH_CPU_X86_FAMILY)
229   return ReadIntSysctlByName<uint64_t>("machdep.cpu.extfeature_bits", 0);
230 #else
231   NOTREACHED();
232   return 0;
233 #endif
234 }
235 
CPUX86Leaf7Features() const236 uint32_t SystemSnapshotMac::CPUX86Leaf7Features() const {
237   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
238 
239 #if defined(ARCH_CPU_X86_FAMILY)
240   // The machdep.cpu.leaf7_feature_bits sysctl isn’t supported prior to OS X
241   // 10.7, so read this by calling cpuid directly.
242   //
243   // machdep.cpu.max_basic could be used to check whether to read the leaf, but
244   // that sysctl isn’t supported prior to Mac OS X 10.6, so read the maximum
245   // basic leaf by calling cpuid directly as well. All CPUs that Apple is known
246   // to have shipped should support a maximum basic leaf value of at least 0xa.
247   uint32_t eax, ebx, ecx, edx;
248   CallCPUID(0, &eax, &ebx, &ecx, &edx);
249   if (eax < 7) {
250     return 0;
251   }
252 
253   CallCPUID(7, &eax, &ebx, &ecx, &edx);
254   return ebx;
255 #else
256   NOTREACHED();
257   return 0;
258 #endif
259 }
260 
CPUX86SupportsDAZ() const261 bool SystemSnapshotMac::CPUX86SupportsDAZ() const {
262   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
263 
264 #if defined(ARCH_CPU_X86_FAMILY)
265   // The correct way to check for denormals-as-zeros (DAZ) support is to examine
266   // mxcsr mask, which can be done with fxsave. See Intel Software Developer’s
267   // Manual, Volume 1: Basic Architecture (253665-051), 11.6.3 “Checking for the
268   // DAZ Flag in the MXCSR Register”. Note that since this function tests for
269   // DAZ support in the CPU, it checks the mxcsr mask. Testing mxcsr would
270   // indicate whether DAZ is actually enabled, which is a per-thread context
271   // concern.
272   //
273   // All CPUs that Apple is known to have shipped should support DAZ.
274 
275   // Test for fxsave support.
276   uint64_t features = CPUX86Features();
277   if (!(features & (UINT64_C(1) << 24))) {
278     return false;
279   }
280 
281   // Call fxsave.
282 #if defined(ARCH_CPU_X86)
283   CPUContextX86::Fxsave fxsave __attribute__((aligned(16))) = {};
284 #elif defined(ARCH_CPU_X86_64)
285   CPUContextX86_64::Fxsave fxsave __attribute__((aligned(16))) = {};
286 #endif
287   static_assert(sizeof(fxsave) == 512, "fxsave size");
288   static_assert(offsetof(decltype(fxsave), mxcsr_mask) == 28,
289                 "mxcsr_mask offset");
290   asm("fxsave %0" : "=m"(fxsave));
291 
292   // Test the DAZ bit.
293   return fxsave.mxcsr_mask & (1 << 6);
294 #else
295   NOTREACHED();
296   return false;
297 #endif
298 }
299 
GetOperatingSystem() const300 SystemSnapshot::OperatingSystem SystemSnapshotMac::GetOperatingSystem() const {
301   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
302   return kOperatingSystemMacOSX;
303 }
304 
OSServer() const305 bool SystemSnapshotMac::OSServer() const {
306   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
307   return os_server_;
308 }
309 
OSVersion(int * major,int * minor,int * bugfix,std::string * build) const310 void SystemSnapshotMac::OSVersion(int* major,
311                                   int* minor,
312                                   int* bugfix,
313                                   std::string* build) const {
314   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
315   *major = os_version_major_;
316   *minor = os_version_minor_;
317   *bugfix = os_version_bugfix_;
318   build->assign(os_version_build_);
319 }
320 
OSVersionFull() const321 std::string SystemSnapshotMac::OSVersionFull() const {
322   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
323   return os_version_full_;
324 }
325 
MachineDescription() const326 std::string SystemSnapshotMac::MachineDescription() const {
327   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
328 
329   std::string model;
330   std::string board_id;
331   MacModelAndBoard(&model, &board_id);
332 
333   if (!model.empty()) {
334     if (!board_id.empty()) {
335       return base::StringPrintf("%s (%s)", model.c_str(), board_id.c_str());
336     }
337     return model;
338   }
339   if (!board_id.empty()) {
340     return base::StringPrintf("(%s)", board_id.c_str());
341   }
342   return std::string();
343 }
344 
NXEnabled() const345 bool SystemSnapshotMac::NXEnabled() const {
346   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
347 
348   int value;
349   if (ReadIntSysctlByName_NoLog("kern.nx", &value) != 0) {
350     {
351       // Support for the kern.nx sysctlbyname is compiled out of production
352       // kernels on macOS 10.14.5 and later, although it’s available in
353       // development and debug kernels. Compare 10.14.3
354       // xnu-4903.241.1/bsd/kern/kern_sysctl.c to 10.15.0
355       // xnu-6153.11.26/bsd/kern/kern_sysctl.c (10.14.4 and 10.14.5 xnu source
356       // are not yet available). In newer production kernels, NX is always
357       // enabled. See 10.15.0 xnu-6153.11.26/osfmk/x86_64/pmap.c nx_enabled.
358 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_14
359       const bool nx_always_enabled = true;
360 #else  // DT >= 10.14
361       base::ScopedClearLastError reset_errno;
362       const bool nx_always_enabled = MacOSVersionNumber() >= 10'14'00;
363 #endif  // DT >= 10.14
364       if (nx_always_enabled) {
365         return true;
366       }
367     }
368 
369     // Even if sysctlbyname should have worked, NX is enabled by default in all
370     // supported configurations, so return true even while warning.
371     PLOG(WARNING) << "sysctlbyname kern.nx";
372     return true;
373   }
374 
375   return value;
376 }
377 
TimeZone(DaylightSavingTimeStatus * dst_status,int * standard_offset_seconds,int * daylight_offset_seconds,std::string * standard_name,std::string * daylight_name) const378 void SystemSnapshotMac::TimeZone(DaylightSavingTimeStatus* dst_status,
379                                  int* standard_offset_seconds,
380                                  int* daylight_offset_seconds,
381                                  std::string* standard_name,
382                                  std::string* daylight_name) const {
383   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
384 
385   internal::TimeZone(*snapshot_time_,
386                      dst_status,
387                      standard_offset_seconds,
388                      daylight_offset_seconds,
389                      standard_name,
390                      daylight_name);
391 }
392 
393 }  // namespace internal
394 }  // namespace crashpad
395