From bcc580e561fe89fbf078a83b3ea96cb8b3091865 Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Mon, 2 Mar 2015 13:06:34 -0800 Subject: [PATCH] win: Add implementation of system_snapshot for Windows ProcessReaderWin only a stub for now. R=mark@chromium.org BUG=crashpad:1 Review URL: https://codereview.chromium.org/936333004 --- compat/compat.gyp | 1 + compat/win/winnt.h | 32 ++ snapshot/snapshot.gyp | 15 + snapshot/system_snapshot.h | 3 + snapshot/win/process_reader_win.cc | 34 +++ snapshot/win/process_reader_win.h | 53 ++++ snapshot/win/system_snapshot_win.cc | 356 +++++++++++++++++++++++ snapshot/win/system_snapshot_win.h | 96 ++++++ snapshot/win/system_snapshot_win_test.cc | 156 ++++++++++ 9 files changed, 746 insertions(+) create mode 100644 compat/win/winnt.h create mode 100644 snapshot/win/process_reader_win.cc create mode 100644 snapshot/win/process_reader_win.h create mode 100644 snapshot/win/system_snapshot_win.cc create mode 100644 snapshot/win/system_snapshot_win.h create mode 100644 snapshot/win/system_snapshot_win_test.cc diff --git a/compat/compat.gyp b/compat/compat.gyp index e58f5e42..231dfe00 100644 --- a/compat/compat.gyp +++ b/compat/compat.gyp @@ -31,6 +31,7 @@ 'non_win/windows.h', 'non_win/winnt.h', 'win/sys/types.h', + 'win/winnt.h', ], 'conditions': [ ['OS=="mac"', { diff --git a/compat/win/winnt.h b/compat/win/winnt.h new file mode 100644 index 00000000..2c5ac662 --- /dev/null +++ b/compat/win/winnt.h @@ -0,0 +1,32 @@ +// Copyright 2015 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. + +#ifndef CRASHPAD_COMPAT_WIN_WINNT_H_ +#define CRASHPAD_COMPAT_WIN_WINNT_H_ + +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa373184.aspx: +// "Note that this structure definition was accidentally omitted from WinNT.h." +struct PROCESSOR_POWER_INFORMATION { + ULONG Number; + ULONG MaxMhz; + ULONG CurrentMhz; + ULONG MhzLimit; + ULONG MaxIdleState; + ULONG CurrentIdleState; +}; + +// include_next +#include <../um/winnt.h> + +#endif // CRASHPAD_COMPAT_WIN_WINNT_H_ diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp index 0ec007b1..f6e53724 100644 --- a/snapshot/snapshot.gyp +++ b/snapshot/snapshot.gyp @@ -78,7 +78,21 @@ 'process_snapshot.h', 'system_snapshot.h', 'thread_snapshot.h', + 'win/process_reader_win.cc', + 'win/process_reader_win.h', + 'win/system_snapshot_win.cc', + 'win/system_snapshot_win.h', ], + 'conditions': [ + ['OS=="win"', { + 'link_settings': { + 'libraries': [ + '-lpowrprof.lib', + '-lversion.lib', + ], + }, + }], + ] }, { 'target_name': 'snapshot_test_lib', @@ -135,6 +149,7 @@ 'mac/process_types_test.cc', 'mac/system_snapshot_mac_test.cc', 'minidump/process_snapshot_minidump_test.cc', + 'win/system_snapshot_win_test.cc', ], }, ], diff --git a/snapshot/system_snapshot.h b/snapshot/system_snapshot.h index 7a41227c..82f5d480 100644 --- a/snapshot/system_snapshot.h +++ b/snapshot/system_snapshot.h @@ -38,6 +38,9 @@ class SystemSnapshot { //! \brief Mac OS X. kOperatingSystemMacOSX, + + //! \brief Windows. + kOperatingSystemWindows, }; //! \brief A system’s daylight saving time status. diff --git a/snapshot/win/process_reader_win.cc b/snapshot/win/process_reader_win.cc new file mode 100644 index 00000000..77fe0e24 --- /dev/null +++ b/snapshot/win/process_reader_win.cc @@ -0,0 +1,34 @@ +// Copyright 2015 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/win/process_reader_win.h" + +namespace crashpad { + +ProcessReaderWin::ProcessReaderWin() + : initialized_(), + is_64_bit_(false) { +} + +ProcessReaderWin::~ProcessReaderWin() { +} + +bool ProcessReaderWin::Initialize(HANDLE process) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +} // namespace crashpad diff --git a/snapshot/win/process_reader_win.h b/snapshot/win/process_reader_win.h new file mode 100644 index 00000000..58a06815 --- /dev/null +++ b/snapshot/win/process_reader_win.h @@ -0,0 +1,53 @@ +// Copyright 2015 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. + +#ifndef CRASHPAD_SNAPSHOT_WIN_PROCESS_READER_WIN_H_ +#define CRASHPAD_SNAPSHOT_WIN_PROCESS_READER_WIN_H_ + +#include + +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +//! \brief Accesses information about another process, identified by a HANDLE. +class ProcessReaderWin { + public: + ProcessReaderWin(); + ~ProcessReaderWin(); + + //! \brief Initializes this object. This method must be called before any + //! other. + //! + //! \param[in] process Process handle, must have PROCESS_QUERY_INFORMATION, + //! PROCESS_VM_READ, and PROCESS_DUP_HANDLE access. + //! + //! \return `true` on success, indicating that this object will respond + //! validly to further method calls. `false` on failure. On failure, no + //! further method calls should be made. + bool Initialize(HANDLE process); + + //! \return `true` if the target task is a 64-bit process. + bool Is64Bit() const { return is_64_bit_; } + + private: + bool is_64_bit_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessReaderWin); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_WIN_PROCESS_READER_WIN_H_ diff --git a/snapshot/win/system_snapshot_win.cc b/snapshot/win/system_snapshot_win.cc new file mode 100644 index 00000000..5e24211a --- /dev/null +++ b/snapshot/win/system_snapshot_win.cc @@ -0,0 +1,356 @@ +// Copyright 2015 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/win/system_snapshot_win.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "base/memory/scoped_ptr.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" + +namespace crashpad { + +namespace { + +//! \brief Gets a string representation for a VS_FIXEDFILEINFO.dwFileFlags +//! value. +std::string GetStringForFileFlags(uint32_t file_flags) { + std::string result; + DCHECK_EQ(file_flags & VS_FF_INFOINFERRED, 0u); + if (file_flags & VS_FF_DEBUG) + result += "Debug,"; + if (file_flags & VS_FF_PATCHED) + result += "Patched,"; + if (file_flags & VS_FF_PRERELEASE) + result += "Prerelease,"; + if (file_flags & VS_FF_PRIVATEBUILD) + result += "Private,"; + if (file_flags & VS_FF_SPECIALBUILD) + result += "Special,"; + if (!result.empty()) + return result.substr(0, result.size() - 1); // Remove trailing comma. + return result; +} + +//! \brief Gets a string representation for a VS_FIXEDFILEINFO.dwFileOS value. +std::string GetStringForFileOS(uint32_t file_os) { + // There are a variety of ancient things this could theoretically be. In + // practice, we're always going to get VOS_NT_WINDOWS32 here. + if ((file_os & VOS_NT_WINDOWS32) == VOS_NT_WINDOWS32) + return "Windows NT"; + else + return "Unknown"; +} + +} // namespace + +namespace internal { + +SystemSnapshotWin::SystemSnapshotWin() + : SystemSnapshot(), + os_version_full_(), + os_version_build_(), + process_reader_(nullptr), + os_version_major_(0), + os_version_minor_(0), + os_version_bugfix_(0), + os_server_(false), + initialized_() { +} + +SystemSnapshotWin::~SystemSnapshotWin() { +} + +void SystemSnapshotWin::Initialize(ProcessReaderWin* process_reader) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + process_reader_ = process_reader; + + // We use both GetVersionEx and VerQueryValue. GetVersionEx is not trustworthy + // after Windows 8 (depending on the application manifest) so its data is used + // only to fill the os_server_ field, and the rest comes from the version + // information stamped on kernel32.dll. + OSVERSIONINFOEX version_info = {sizeof(version_info)}; + if (!GetVersionEx(reinterpret_cast(&version_info))) { + PLOG(WARNING) << "GetVersionEx"; + } else { + const wchar_t kSystemDll[] = L"kernel32.dll"; + DWORD size = GetFileVersionInfoSize(kSystemDll, nullptr); + if (!size) { + PLOG(WARNING) << "GetFileVersionInfoSize"; + } else { + scoped_ptr data(new uint8_t[size]); + if (!GetFileVersionInfo(kSystemDll, 0, size, data.get())) { + PLOG(WARNING) << "GetFileVersionInfo"; + } else { + VS_FIXEDFILEINFO* fixed_file_info; + UINT size; + if (!VerQueryValue(data.get(), + L"\\", + reinterpret_cast(&fixed_file_info), + &size)) { + PLOG(WARNING) << "VerQueryValue"; + } else { + uint32_t valid_flags = + fixed_file_info->dwFileFlags & fixed_file_info->dwFileFlagsMask; + std::string flags_string = GetStringForFileFlags(valid_flags); + os_version_major_ = + (fixed_file_info->dwFileVersionMS & 0xffff0000) >> 16; + os_version_minor_ = fixed_file_info->dwFileVersionMS & 0xffff; + os_version_bugfix_ = + (fixed_file_info->dwFileVersionLS & 0xffff0000) >> 16; + os_version_build_ = base::StringPrintf( + "%d", fixed_file_info->dwFileVersionLS & 0xffff); + os_server_ = version_info.wProductType != VER_NT_WORKSTATION; + std::string os_name = GetStringForFileOS(fixed_file_info->dwFileOS); + os_version_full_ = base::StringPrintf( + "%s %d.%d.%d.%s%s", + os_name.c_str(), + os_version_major_, + os_version_minor_, + os_version_bugfix_, + os_version_build_.c_str(), + flags_string.empty() ? "" : (std::string(" (") + flags_string + + ")").c_str()); + } + } + } + } + + INITIALIZATION_STATE_SET_VALID(initialized_); +} + +CPUArchitecture SystemSnapshotWin::GetCPUArchitecture() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + return process_reader_->Is64Bit() ? kCPUArchitectureX86_64 + : kCPUArchitectureX86; +} + +uint32_t SystemSnapshotWin::CPURevision() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + uint32_t raw = CPUX86Signature(); + uint8_t stepping = raw & 0xf; + uint8_t model = (raw & 0xf0) >> 4; + uint8_t family = (raw & 0xf00) >> 8; + uint8_t extended_model = static_cast((raw & 0xf0000) >> 16); + uint16_t extended_family = (raw & 0xff00000) >> 20; + + // For families before 15, extended_family are simply reserved bits. + if (family < 15) + extended_family = 0; + // extended_model is only used for families 6 and 15. + if (family != 6 && family != 15) + extended_model = 0; + + uint16_t adjusted_family = family + extended_family; + uint8_t adjusted_model = model + (extended_model << 4); + return (adjusted_family << 16) | (adjusted_model << 8) | stepping; +} + +uint8_t SystemSnapshotWin::CPUCount() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + if (!base::IsValueInRangeForNumericType( + system_info.dwNumberOfProcessors)) { + LOG(WARNING) << "dwNumberOfProcessors exceeds uint8_t storage"; + } + return base::saturated_cast(system_info.dwNumberOfProcessors); +} + +std::string SystemSnapshotWin::CPUVendor() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + int cpu_info[4]; + __cpuid(cpu_info, 0); + char vendor[12]; + *reinterpret_cast(vendor) = cpu_info[1]; + *reinterpret_cast(vendor + 4) = cpu_info[3]; + *reinterpret_cast(vendor + 8) = cpu_info[2]; + return std::string(vendor, sizeof(vendor)); +} + +void SystemSnapshotWin::CPUFrequency(uint64_t* current_hz, + uint64_t* max_hz) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + int num_cpus = CPUCount(); + DCHECK_GT(num_cpus, 0); + std::vector info(num_cpus); + if (CallNtPowerInformation(ProcessorInformation, + nullptr, + 0, + &info[0], + sizeof(PROCESSOR_POWER_INFORMATION) * num_cpus) != + 0) { + *current_hz = 0; + *max_hz = 0; + return; + } + const uint64_t kMhzToHz = static_cast(1E6); + *current_hz = std::max_element(info.begin(), + info.end(), + [](const PROCESSOR_POWER_INFORMATION& a, + const PROCESSOR_POWER_INFORMATION& b) { + return a.CurrentMhz < b.CurrentMhz; + })->CurrentMhz * + kMhzToHz; + *max_hz = std::max_element(info.begin(), + info.end(), + [](const PROCESSOR_POWER_INFORMATION& a, + const PROCESSOR_POWER_INFORMATION& b) { + return a.MaxMhz < b.MaxMhz; + })->MaxMhz * + kMhzToHz; +} + +uint32_t SystemSnapshotWin::CPUX86Signature() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + int cpu_info[4]; + // We will never run on any processors that don't support at least function 1. + __cpuid(cpu_info, 1); + return cpu_info[0]; +} + +uint64_t SystemSnapshotWin::CPUX86Features() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + int cpu_info[4]; + // We will never run on any processors that don't support at least function 1. + __cpuid(cpu_info, 1); + return (static_cast(cpu_info[2]) << 32) | + static_cast(cpu_info[3]); +} + +uint64_t SystemSnapshotWin::CPUX86ExtendedFeatures() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + int cpu_info[4]; + // We will never run on any processors that don't support at least extended + // function 1. + __cpuid(cpu_info, 0x80000001); + return (static_cast(cpu_info[2]) << 32) | + static_cast(cpu_info[3]); +} + +uint32_t SystemSnapshotWin::CPUX86Leaf7Features() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + int cpu_info[4]; + + // Make sure leaf 7 can be called. + __cpuid(cpu_info, 0); + if (cpu_info[0] < 7) + return 0; + + __cpuidex(cpu_info, 7, 0); + return cpu_info[1]; +} + +bool SystemSnapshotWin::CPUX86SupportsDAZ() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + // 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. + + // Test for fxsave support. + uint64_t features = CPUX86Features(); + if (!(features & (UINT64_C(1) << 24))) { + return false; + } + + // Call fxsave. + __declspec(align(16)) uint32_t extended_registers[128]; + _fxsave(&extended_registers); + uint32_t mxcsr_mask = extended_registers[7]; + + // Test the DAZ bit. + return mxcsr_mask & (1 << 6); +} + +SystemSnapshot::OperatingSystem SystemSnapshotWin::GetOperatingSystem() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return kOperatingSystemWindows; +} + +bool SystemSnapshotWin::OSServer() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return os_server_; +} + +void SystemSnapshotWin::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 SystemSnapshotWin::OSVersionFull() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return os_version_full_; +} + +std::string SystemSnapshotWin::MachineDescription() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(scottmg): Not sure if there's anything sensible to put here. + return std::string(); +} + +bool SystemSnapshotWin::NXEnabled() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return IsProcessorFeaturePresent(PF_NX_ENABLED); +} + +void SystemSnapshotWin::TimeZone(DaylightSavingTimeStatus* dst_status, + int* standard_offset_seconds, + int* daylight_offset_seconds, + std::string* standard_name, + std::string* daylight_name) const { + // This returns the current time zone status rather than the status at the + // time of the snapshot. This differs from the Mac implementation. + TIME_ZONE_INFORMATION time_zone_information; + *dst_status = static_cast( + GetTimeZoneInformation(&time_zone_information)); + *standard_offset_seconds = + (time_zone_information.Bias + time_zone_information.StandardBias) * -60; + *daylight_offset_seconds = + (time_zone_information.Bias + time_zone_information.DaylightBias) * -60; + *standard_name = base::UTF16ToUTF8(time_zone_information.StandardName); + *daylight_name = base::UTF16ToUTF8(time_zone_information.DaylightName); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/win/system_snapshot_win.h b/snapshot/win/system_snapshot_win.h new file mode 100644 index 00000000..b7a22c6a --- /dev/null +++ b/snapshot/win/system_snapshot_win.h @@ -0,0 +1,96 @@ +// Copyright 2015 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. + +#ifndef CRASHPAD_SNAPSHOT_WIN_SYSTEM_SNAPSHOT_WIN_H_ +#define CRASHPAD_SNAPSHOT_WIN_SYSTEM_SNAPSHOT_WIN_H_ + +#include +#include + +#include + +#include "base/basictypes.h" +#include "snapshot/system_snapshot.h" +#include "snapshot/win/process_reader_win.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +class ProcessReaderWin; + +namespace internal { + +//! \brief A SystemSnapshot of the running system, when the system runs Windows. +class SystemSnapshotWin final : public SystemSnapshot { + public: + SystemSnapshotWin(); + ~SystemSnapshotWin() override; + + //! \brief Initializes the object. + //! + //! \param[in] process_reader A reader for the process being snapshotted. + //! + //! It seems odd that a system snapshot implementation would need a + //! ProcessReaderWin, but some of the information reported about the + //! system depends on the process it's being reported for. For example, + //! the architecture returned by GetCPUArchitecture() should be the + //! architecture of the process, which may be different than the native + //! architecture of the system: an x86_64 system can run both x86_64 and + //! 32-bit x86 processes. + void Initialize(ProcessReaderWin* process_reader); + + // SystemSnapshot: + + CPUArchitecture GetCPUArchitecture() const override; + uint32_t CPURevision() const override; + uint8_t CPUCount() const override; + std::string CPUVendor() const override; + void CPUFrequency(uint64_t* current_hz, uint64_t* max_hz) const override; + uint32_t CPUX86Signature() const override; + uint64_t CPUX86Features() const override; + uint64_t CPUX86ExtendedFeatures() const override; + uint32_t CPUX86Leaf7Features() const override; + bool CPUX86SupportsDAZ() const override; + OperatingSystem GetOperatingSystem() const override; + bool OSServer() const override; + void OSVersion(int* major, + int* minor, + int* bugfix, + std::string* build) const override; + std::string OSVersionFull() const override; + bool NXEnabled() const override; + std::string MachineDescription() const override; + void TimeZone(DaylightSavingTimeStatus* dst_status, + int* standard_offset_seconds, + int* daylight_offset_seconds, + std::string* standard_name, + std::string* daylight_name) const override; + + private: + std::string os_version_full_; + std::string os_version_build_; + ProcessReaderWin* process_reader_; // weak + int os_version_major_; + int os_version_minor_; + int os_version_bugfix_; + bool os_server_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(SystemSnapshotWin); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_WIN_SYSTEM_SNAPSHOT_WIN_H_ diff --git a/snapshot/win/system_snapshot_win_test.cc b/snapshot/win/system_snapshot_win_test.cc new file mode 100644 index 00000000..7552ca56 --- /dev/null +++ b/snapshot/win/system_snapshot_win_test.cc @@ -0,0 +1,156 @@ +// Copyright 2015 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/win/system_snapshot_win.h" + +#include +#include + +#include + +#include "build/build_config.h" +#include "gtest/gtest.h" +#include "snapshot/win/process_reader_win.h" + +namespace crashpad { +namespace test { +namespace { + +class SystemSnapshotWinTest : public testing::Test { + public: + SystemSnapshotWinTest() + : Test(), + process_reader_(), + system_snapshot_() { + } + + const internal::SystemSnapshotWin& system_snapshot() const { + return system_snapshot_; + } + + // testing::Test: + void SetUp() override { + ASSERT_TRUE(process_reader_.Initialize(GetCurrentProcess())); + system_snapshot_.Initialize(&process_reader_); + } + + private: + ProcessReaderWin process_reader_; + internal::SystemSnapshotWin system_snapshot_; + + DISALLOW_COPY_AND_ASSIGN(SystemSnapshotWinTest); +}; + +TEST_F(SystemSnapshotWinTest, GetCPUArchitecture) { + CPUArchitecture cpu_architecture = system_snapshot().GetCPUArchitecture(); + +#if defined(ARCH_CPU_X86) + EXPECT_EQ(kCPUArchitectureX86, cpu_architecture); +#elif defined(ARCH_CPU_X86_64) + EXPECT_EQ(kCPUArchitectureX86_64, cpu_architecture); +#endif +} + +TEST_F(SystemSnapshotWinTest, CPUCount) { + EXPECT_GE(system_snapshot().CPUCount(), 1); +} + +TEST_F(SystemSnapshotWinTest, CPUVendor) { + std::string cpu_vendor = system_snapshot().CPUVendor(); + + // There are a variety of other values, but we don't expect to run our tests + // on them. + EXPECT_TRUE(cpu_vendor == "GenuineIntel" || cpu_vendor == "AuthenticAMD"); +} + +TEST_F(SystemSnapshotWinTest, CPUX86SupportsDAZ) { + // Most SSE2+ machines support Denormals-Are-Zero. This may fail if run on + // older machines. + EXPECT_TRUE(system_snapshot().CPUX86SupportsDAZ()); +} + +TEST_F(SystemSnapshotWinTest, GetOperatingSystem) { + EXPECT_EQ(SystemSnapshot::kOperatingSystemWindows, + system_snapshot().GetOperatingSystem()); +} + +TEST_F(SystemSnapshotWinTest, OSVersion) { + int major; + int minor; + int bugfix; + std::string build; + system_snapshot().OSVersion(&major, &minor, &bugfix, &build); + + EXPECT_GE(major, 5); + if (major == 5) + EXPECT_GE(minor, 1); + if (major == 6) + EXPECT_TRUE(minor >= 0 && minor <= 3); +} + +TEST_F(SystemSnapshotWinTest, OSVersionFull) { + EXPECT_FALSE(system_snapshot().OSVersionFull().empty()); +} + +TEST_F(SystemSnapshotWinTest, MachineDescription) { + EXPECT_TRUE(system_snapshot().MachineDescription().empty()); +} + +TEST_F(SystemSnapshotWinTest, TimeZone) { + SystemSnapshot::DaylightSavingTimeStatus dst_status; + int standard_offset_seconds; + int daylight_offset_seconds; + std::string standard_name; + std::string daylight_name; + + system_snapshot().TimeZone(&dst_status, + &standard_offset_seconds, + &daylight_offset_seconds, + &standard_name, + &daylight_name); + + // |standard_offset_seconds| gives seconds east of UTC, and |timezone| gives + // seconds west of UTC. + EXPECT_EQ(-timezone, standard_offset_seconds); + + // In contemporary usage, most time zones have an integer hour offset from + // UTC, although several are at a half-hour offset, and two are at 15-minute + // offsets. Throughout history, other variations existed. See + // http://www.timeanddate.com/time/time-zones-interesting.html. + EXPECT_EQ(0, standard_offset_seconds % (15 * 60)) + << "standard_offset_seconds " << standard_offset_seconds; + + if (dst_status == SystemSnapshot::kDoesNotObserveDaylightSavingTime) { + EXPECT_EQ(standard_offset_seconds, daylight_offset_seconds); + EXPECT_EQ(standard_name, daylight_name); + } else { + EXPECT_EQ(0, daylight_offset_seconds % (15 * 60)) + << "daylight_offset_seconds " << daylight_offset_seconds; + + // In contemporary usage, dst_delta_seconds will almost always be one hour, + // except for Lord Howe Island, Australia, which uses a 30-minute + // delta. Throughout history, other variations existed. See + // http://www.timeanddate.com/time/dst/#brief. + int dst_delta_seconds = daylight_offset_seconds - standard_offset_seconds; + if (dst_delta_seconds != 60 * 60 && dst_delta_seconds != 30 * 60) { + FAIL() << "dst_delta_seconds " << dst_delta_seconds; + } + + EXPECT_NE(standard_name, daylight_name); + } +} + +} // namespace +} // namespace test +} // namespace crashpad