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