Add SystemSnapshotLinux

Bug: crashpad:30
Change-Id: Ic1162c6637708492a5a9903a221cdd9266d3fd97
Reviewed-on: https://chromium-review.googlesource.com/601028
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
diff --git a/build/gyp_crashpad_android.py b/build/gyp_crashpad_android.py
index a6ad1b4..4461425 100755
--- a/build/gyp_crashpad_android.py
+++ b/build/gyp_crashpad_android.py
@@ -46,8 +46,8 @@
   ARCH_TRIPLET_TO_ARCH = {
     'arm-linux-androideabi': 'arm',
     'aarch64-linux-android': 'arm64',
-    'i686-linux-android': 'x86',
-    'x86_64-linux-android': 'x86_64',
+    'i686-linux-android': 'ia32',
+    'x86_64-linux-android': 'x64',
     'mipsel-linux-android': 'mips',
     'mips64el-linux-android': 'mips64',
   }
diff --git a/snapshot/linux/system_snapshot_linux.cc b/snapshot/linux/system_snapshot_linux.cc
new file mode 100644
index 0000000..f3ed99b
--- /dev/null
+++ b/snapshot/linux/system_snapshot_linux.cc
@@ -0,0 +1,397 @@
+// Copyright 2017 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/linux/system_snapshot_linux.h"
+
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+
+#include <algorithm>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "snapshot/cpu_context.h"
+#include "snapshot/posix/timezone.h"
+#include "util/file/file_io.h"
+#include "util/numeric/in_range_cast.h"
+#include "util/string/split_string.h"
+
+#if defined(OS_ANDROID)
+#include <sys/system_properties.h>
+#endif
+
+namespace crashpad {
+namespace internal {
+
+namespace {
+
+bool ReadCPUsOnline(uint32_t* first_cpu, uint8_t* cpu_count) {
+  std::string contents;
+  if (!LoggingReadEntireFile(base::FilePath("/sys/devices/system/cpu/online"),
+                             &contents)) {
+    return false;
+  }
+  if (contents.back() != '\n') {
+    LOG(ERROR) << "format error";
+    return false;
+  }
+  contents.pop_back();
+
+  unsigned int count = 0;
+  unsigned int first = 0;
+  bool have_first = false;
+  std::vector<std::string> ranges = SplitString(contents, ',');
+  for (const auto& range : ranges) {
+    std::string left, right;
+    if (SplitStringFirst(range, '-', &left, &right)) {
+      unsigned int start, end;
+      if (!StringToUint(base::StringPiece(left), &start) ||
+          !StringToUint(base::StringPiece(right), &end) || end <= start) {
+        LOG(ERROR) << "format error: " << range;
+        return false;
+      }
+      if (end <= start) {
+        LOG(ERROR) << "format error";
+        return false;
+      }
+      count += end - start + 1;
+      if (!have_first) {
+        first = start;
+        have_first = true;
+      }
+    } else {
+      unsigned int cpuno;
+      if (!StringToUint(base::StringPiece(range), &cpuno)) {
+        LOG(ERROR) << "format error";
+        return false;
+      }
+      if (!have_first) {
+        first = cpuno;
+        have_first = true;
+      }
+      ++count;
+    }
+  }
+  if (!have_first) {
+    LOG(ERROR) << "no cpus online";
+    return false;
+  }
+  *cpu_count = InRangeCast<uint8_t>(count, std::numeric_limits<uint8_t>::max());
+  *first_cpu = first;
+  return true;
+}
+
+bool ReadFreqFile(const std::string& filename, uint64_t* hz) {
+  std::string contents;
+  if (!LoggingReadEntireFile(base::FilePath(filename), &contents)) {
+    return false;
+  }
+  if (contents.back() != '\n') {
+    LOG(ERROR) << "format error";
+    return false;
+  }
+  contents.pop_back();
+
+  uint64_t khz;
+  if (!base::StringToUint64(base::StringPiece(contents), &khz)) {
+    LOG(ERROR) << "format error";
+    return false;
+  }
+
+  *hz = khz * 1000;
+  return true;
+}
+
+#if defined(OS_ANDROID)
+bool ReadProperty(const char* property, std::string* value) {
+  char value_buffer[PROP_VALUE_MAX];
+  int length = __system_property_get(property, value_buffer);
+  if (length <= 0) {
+    LOG(ERROR) << "Couldn't read property " << property;
+    return false;
+  }
+  *value = value_buffer;
+  return true;
+}
+#endif  // OS_ANDROID
+
+}  // namespace
+
+SystemSnapshotLinux::SystemSnapshotLinux()
+    : SystemSnapshot(),
+      os_version_full_(),
+      os_version_build_(),
+      process_reader_(nullptr),
+      snapshot_time_(nullptr),
+#if defined(ARCH_CPU_X86_FAMILY)
+      cpuid_(),
+#endif  // ARCH_CPU_X86_FAMILY
+      os_version_major_(-1),
+      os_version_minor_(-1),
+      os_version_bugfix_(-1),
+      target_cpu_(0),
+      cpu_count_(0),
+      initialized_() {
+}
+
+SystemSnapshotLinux::~SystemSnapshotLinux() {}
+
+void SystemSnapshotLinux::Initialize(ProcessReader* process_reader,
+                                     const timeval* snapshot_time) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+  process_reader_ = process_reader;
+  snapshot_time_ = snapshot_time;
+
+#if defined(OS_ANDROID)
+  std::string build_string;
+  if (ReadProperty("ro.build.fingerprint", &build_string)) {
+    os_version_build_ = build_string;
+    os_version_full_ = build_string;
+  }
+#endif  // OS_ANDROID
+
+  utsname uts;
+  if (uname(&uts) != 0) {
+    PLOG(WARNING) << "uname";
+  } else {
+    if (!os_version_full_.empty()) {
+      os_version_full_.push_back(' ');
+    }
+    os_version_full_ += base::StringPrintf(
+        "%s %s %s %s", uts.sysname, uts.release, uts.version, uts.machine);
+  }
+  ReadKernelVersion(uts.release);
+
+  if (!os_version_build_.empty()) {
+    os_version_build_.push_back(' ');
+  }
+  os_version_build_ += uts.version;
+  os_version_build_.push_back(' ');
+  os_version_build_ += uts.machine;
+
+  if (!ReadCPUsOnline(&target_cpu_, &cpu_count_)) {
+    target_cpu_ = 0;
+    cpu_count_ = 0;
+  }
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+}
+
+CPUArchitecture SystemSnapshotLinux::GetCPUArchitecture() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+#if defined(ARCH_CPU_X86_FAMILY)
+  return process_reader_->Is64Bit() ? kCPUArchitectureX86_64
+                                    : kCPUArchitectureX86;
+#else
+#error port to your architecture
+#endif
+}
+
+uint32_t SystemSnapshotLinux::CPURevision() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+#if defined(ARCH_CPU_X86_FAMILY)
+  return cpuid_.Revision();
+#else
+#error port to your architecture
+#endif
+}
+
+uint8_t SystemSnapshotLinux::CPUCount() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return cpu_count_;
+}
+
+std::string SystemSnapshotLinux::CPUVendor() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+#if defined(ARCH_CPU_X86_FAMILY)
+  return cpuid_.Vendor();
+#else
+#error port to your architecture
+#endif
+}
+
+void SystemSnapshotLinux::CPUFrequency(uint64_t* current_hz,
+                                       uint64_t* max_hz) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  *current_hz = 0;
+  *max_hz = 0;
+
+  ReadFreqFile(base::StringPrintf(
+                   "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq",
+                   target_cpu_),
+               current_hz);
+
+  ReadFreqFile(base::StringPrintf(
+                   "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_max_freq",
+                   target_cpu_),
+               max_hz);
+}
+
+uint32_t SystemSnapshotLinux::CPUX86Signature() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+#if defined(ARCH_CPU_X86_FAMILY)
+  return cpuid_.Signature();
+#else
+  NOTREACHED();
+  return 0;
+#endif
+}
+
+uint64_t SystemSnapshotLinux::CPUX86Features() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+#if defined(ARCH_CPU_X86_FAMILY)
+  return cpuid_.Features();
+#else
+  NOTREACHED();
+  return 0;
+#endif
+}
+
+uint64_t SystemSnapshotLinux::CPUX86ExtendedFeatures() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return cpuid_.ExtendedFeatures();
+}
+
+uint32_t SystemSnapshotLinux::CPUX86Leaf7Features() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+#if defined(ARCH_CPU_X86_FAMILY)
+  return cpuid_.Leaf7Features();
+#else
+  NOTREACHED();
+  return 0;
+#endif
+}
+
+bool SystemSnapshotLinux::CPUX86SupportsDAZ() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+#if defined(ARCH_CPU_X86_FAMILY)
+  return cpuid_.SupportsDAZ();
+#else
+  NOTREACHED();
+  return false;
+#endif  // ARCH_CPU_X86_FMAILY
+}
+
+SystemSnapshot::OperatingSystem SystemSnapshotLinux::GetOperatingSystem()
+    const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+#if defined(OS_ANDROID)
+  return kOperatingSystemAndroid;
+#else
+  return kOperatingSystemLinux;
+#endif  // OS_ANDROID
+}
+
+bool SystemSnapshotLinux::OSServer() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return false;
+}
+
+void SystemSnapshotLinux::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 SystemSnapshotLinux::OSVersionFull() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return os_version_full_;
+}
+
+std::string SystemSnapshotLinux::MachineDescription() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+#if defined(OS_ANDROID)
+  std::string description;
+  std::string prop;
+  if (ReadProperty("ro.product.model", &prop)) {
+    description += prop;
+  }
+  if (ReadProperty("ro.product.board", &prop)) {
+    if (!description.empty()) {
+      description.push_back(' ');
+    }
+    description += prop;
+  }
+  return description;
+#else
+  return std::string();
+#endif  // OS_ANDROID
+}
+
+bool SystemSnapshotLinux::NXEnabled() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return cpuid_.NXEnabled();
+}
+
+void SystemSnapshotLinux::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);
+}
+
+void SystemSnapshotLinux::ReadKernelVersion(const std::string& version_string) {
+  std::vector<std::string> versions = SplitString(version_string, '.');
+  if (versions.size() < 3) {
+    LOG(WARNING) << "format error";
+    return;
+  }
+
+  if (!StringToInt(base::StringPiece(versions[0]), &os_version_major_)) {
+    LOG(WARNING) << "no kernel version";
+    return;
+  }
+  DCHECK_GE(os_version_major_, 3);
+
+  if (!StringToInt(base::StringPiece(versions[1]), &os_version_minor_)) {
+    LOG(WARNING) << "no major revision";
+    return;
+  }
+  DCHECK_GE(os_version_minor_, 0);
+
+  size_t minor_rev_end = versions[2].find_first_not_of("0123456789");
+  if (minor_rev_end == std::string::npos) {
+    minor_rev_end = versions[2].size();
+  }
+  if (!StringToInt(base::StringPiece(versions[2].c_str(), minor_rev_end),
+                   &os_version_bugfix_)) {
+    LOG(WARNING) << "no minor revision";
+    return;
+  }
+  DCHECK_GE(os_version_bugfix_, 0);
+
+  if (!os_version_build_.empty()) {
+    os_version_build_.push_back(' ');
+  }
+  os_version_build_ += versions[2].substr(minor_rev_end);
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/linux/system_snapshot_linux.h b/snapshot/linux/system_snapshot_linux.h
new file mode 100644
index 0000000..a991450
--- /dev/null
+++ b/snapshot/linux/system_snapshot_linux.h
@@ -0,0 +1,112 @@
+// Copyright 2017 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_LINUX_SYSTEM_SNAPSHOT_LINUX_H_
+#define CRASHPAD_SNAPSHOT_LINUX_SYSTEM_SNAPSHOT_LINUX_H_
+
+#include <stdint.h>
+#include <time.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "snapshot/linux/process_reader.h"
+#include "snapshot/system_snapshot.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+#if defined(ARCH_CPU_X86_FAMILY)
+#include "snapshot/x86/cpuid_reader.h"
+#endif  // ARCH_CPU_X86_FAMILY
+
+namespace crashpad {
+namespace internal {
+
+//! \brief A SystemSnapshot of the running system, when the system runs Linux.
+class SystemSnapshotLinux final : public SystemSnapshot {
+ public:
+  SystemSnapshotLinux();
+  ~SystemSnapshotLinux() override;
+
+  //! \brief Initializes the object.
+  //!
+  //! \param[in] process_reader A reader for the process being snapshotted.
+  //!     \n\n
+  //!     It seems odd that a system snapshot implementation would need a
+  //!     ProcessReader, 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.
+  //! \param[in] snapshot_time The time of the snapshot being taken.
+  //!     \n\n
+  //!     This parameter is necessary for TimeZone() to determine whether
+  //!     daylight saving time was in effect at the time the snapshot was taken.
+  //!     Otherwise, it would need to base its determination on the current
+  //!     time, which may be different than the snapshot time for snapshots
+  //!     generated around the daylight saving transition time.
+  void Initialize(ProcessReader* process_reader, const timeval* snapshot_time);
+
+  // 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:
+  void ReadKernelVersion(const std::string& version_string);
+
+  std::string os_version_full_;
+  std::string os_version_build_;
+  ProcessReader* process_reader_;  // weak
+  const timeval* snapshot_time_;  // weak
+#if defined(ARCH_CPU_X86_FAMILY)
+  CpuidReader cpuid_;
+#endif  // ARCH_CPU_X86_FAMILY
+  int os_version_major_;
+  int os_version_minor_;
+  int os_version_bugfix_;
+  uint32_t target_cpu_;
+  uint8_t cpu_count_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(SystemSnapshotLinux);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_LINUX_SYSTEM_SNAPSHOT_LINUX_H_
diff --git a/snapshot/linux/system_snapshot_linux_test.cc b/snapshot/linux/system_snapshot_linux_test.cc
new file mode 100644
index 0000000..f55036e
--- /dev/null
+++ b/snapshot/linux/system_snapshot_linux_test.cc
@@ -0,0 +1,85 @@
+// Copyright 2017 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/linux/system_snapshot_linux.h"
+
+#include <sys/time.h>
+
+#include <string>
+
+#include "build/build_config.h"
+#include "gtest/gtest.h"
+#include "snapshot/linux/process_reader.h"
+#include "test/errors.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+TEST(SystemSnapshotLinux, Basic) {
+  ProcessReader process_reader;
+  ASSERT_TRUE(process_reader.Initialize(getpid()));
+
+  timeval snapshot_time;
+  ASSERT_EQ(gettimeofday(&snapshot_time, nullptr), 0)
+      << ErrnoMessage("gettimeofday");
+
+  internal::SystemSnapshotLinux system;
+  system.Initialize(&process_reader, &snapshot_time);
+
+  EXPECT_GT(system.CPUCount(), 0u);
+
+  uint64_t current_hz, max_hz;
+  system.CPUFrequency(&current_hz, &max_hz);
+  EXPECT_GE(max_hz, current_hz);
+
+  int major, minor, bugfix;
+  std::string build;
+  system.OSVersion(&major, &minor, &bugfix, &build);
+  EXPECT_GE(major, 3);
+  EXPECT_GE(minor, 0);
+  EXPECT_GE(bugfix, 0);
+  EXPECT_FALSE(build.empty());
+
+  EXPECT_FALSE(system.OSVersionFull().empty());
+
+  // No expectations; just make sure these can be called successfully.
+  system.CPURevision();
+  system.NXEnabled();
+
+#if defined(OS_ANDROID)
+  EXPECT_FALSE(system.MachineDescription().empty());
+#else
+  system.MachineDescription();
+#endif  // OS_ANDROID
+
+#if defined(ARCH_CPU_X86_FAMILY)
+  system.CPUX86Signature();
+  system.CPUX86Features();
+  system.CPUX86ExtendedFeatures();
+  system.CPUX86Leaf7Features();
+
+  EXPECT_PRED1(
+      [](std::string vendor) {
+        return vendor == "GenuineIntel" || vendor == "AuthenticAMD";
+      },
+      system.CPUVendor());
+
+  EXPECT_TRUE(system.CPUX86SupportsDAZ());
+#endif  // ARCH_CPU_X86_FAMILY
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/snapshot/mac/system_snapshot_mac.cc b/snapshot/mac/system_snapshot_mac.cc
index 2bafb27..140e7f4 100644
--- a/snapshot/mac/system_snapshot_mac.cc
+++ b/snapshot/mac/system_snapshot_mac.cc
@@ -18,7 +18,6 @@
 #include <sys/sysctl.h>
 #include <sys/types.h>
 #include <sys/utsname.h>
-#include <time.h>
 
 #include <algorithm>
 
@@ -27,6 +26,7 @@
 #include "build/build_config.h"
 #include "snapshot/cpu_context.h"
 #include "snapshot/mac/process_reader.h"
+#include "snapshot/posix/timezone.h"
 #include "util/mac/mac_util.h"
 #include "util/numeric/in_range_cast.h"
 
@@ -348,64 +348,12 @@
                                  std::string* daylight_name) const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
-  tm local;
-  PCHECK(localtime_r(&snapshot_time_->tv_sec, &local)) << "localtime_r";
-
-  *standard_name = tzname[0];
-
-  bool found_transition = false;
-  long probe_gmtoff = local.tm_gmtoff;
-  if (daylight) {
-    // Scan forward and backward, one month at a time, looking for an instance
-    // when the observance of daylight saving time is different than it is in
-    // |local|. It’s possible that no such instance will be found even with
-    // |daylight| set. This can happen in locations where daylight saving time
-    // was once observed or is expected to be observed in the future, but where
-    // no transitions to or from daylight saving time occurred or will occur
-    // within a year of the current date. Arizona, which last observed daylight
-    // saving time in 1967, is an example.
-    static constexpr int kMonthDeltas[] =
-        {0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6,
-         7, -7, 8, -8, 9, -9, 10, -10, 11, -11, 12, -12};
-    for (size_t index = 0;
-         index < arraysize(kMonthDeltas) && !found_transition;
-         ++index) {
-      // Look at a day of each month at local noon. Set tm_isdst to -1 to avoid
-      // giving mktime() any hints about whether to consider daylight saving
-      // time in effect. mktime() accepts values of tm_mon that are outside of
-      // its normal range and behaves as expected: if tm_mon is -1, it
-      // references December of the preceding year, and if it is 12, it
-      // references January of the following year.
-      tm probe_tm = {};
-      probe_tm.tm_hour = 12;
-      probe_tm.tm_mday = std::min(local.tm_mday, 28);
-      probe_tm.tm_mon = local.tm_mon + kMonthDeltas[index];
-      probe_tm.tm_year = local.tm_year;
-      probe_tm.tm_isdst = -1;
-      if (mktime(&probe_tm) != -1 && probe_tm.tm_isdst != local.tm_isdst) {
-        found_transition = true;
-        probe_gmtoff = probe_tm.tm_gmtoff;
-      }
-    }
-  }
-
-  if (found_transition) {
-    *daylight_name = tzname[1];
-    if (!local.tm_isdst) {
-      *dst_status = kObservingStandardTime;
-      *standard_offset_seconds = local.tm_gmtoff;
-      *daylight_offset_seconds = probe_gmtoff;
-    } else {
-      *dst_status = kObservingDaylightSavingTime;
-      *standard_offset_seconds = probe_gmtoff;
-      *daylight_offset_seconds = local.tm_gmtoff;
-    }
-  } else {
-    *daylight_name = tzname[0];
-    *dst_status = kDoesNotObserveDaylightSavingTime;
-    *standard_offset_seconds = local.tm_gmtoff;
-    *daylight_offset_seconds = local.tm_gmtoff;
-  }
+  internal::TimeZone(*snapshot_time_,
+                     dst_status,
+                     standard_offset_seconds,
+                     daylight_offset_seconds,
+                     standard_name,
+                     daylight_name);
 }
 
 }  // namespace internal
diff --git a/snapshot/mac/system_snapshot_mac_test.cc b/snapshot/mac/system_snapshot_mac_test.cc
index fb4166f..646021b 100644
--- a/snapshot/mac/system_snapshot_mac_test.cc
+++ b/snapshot/mac/system_snapshot_mac_test.cc
@@ -14,13 +14,10 @@
 
 #include "snapshot/mac/system_snapshot_mac.h"
 
-#include <stdlib.h>
 #include <sys/time.h>
-#include <time.h>
 
 #include <string>
 
-#include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
 #include "snapshot/mac/process_reader.h"
@@ -129,152 +126,6 @@
   EXPECT_FALSE(system_snapshot().MachineDescription().empty());
 }
 
-class ScopedSetTZ {
- public:
-  ScopedSetTZ(const std::string& tz) {
-    const char* old_tz = getenv(kTZ);
-    old_tz_set_ = old_tz;
-    if (old_tz_set_) {
-      old_tz_.assign(old_tz);
-    }
-
-    EXPECT_EQ(setenv(kTZ, tz.c_str(), 1), 0) << ErrnoMessage("setenv");
-    tzset();
-  }
-
-  ~ScopedSetTZ() {
-    if (old_tz_set_) {
-      EXPECT_EQ(setenv(kTZ, old_tz_.c_str(), 1), 0) << ErrnoMessage("setenv");
-    } else {
-      EXPECT_EQ(unsetenv(kTZ), 0) << ErrnoMessage("unsetenv");
-    }
-    tzset();
-  }
-
- private:
-  std::string old_tz_;
-  bool old_tz_set_;
-
-  static constexpr char kTZ[] = "TZ";
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedSetTZ);
-};
-
-constexpr char ScopedSetTZ::kTZ[];
-
-TEST_F(SystemSnapshotMacTest, 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(standard_offset_seconds, -timezone);
-
-  // 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(standard_offset_seconds % (15 * 60), 0)
-      << "standard_offset_seconds " << standard_offset_seconds;
-
-  if (dst_status == SystemSnapshot::kDoesNotObserveDaylightSavingTime) {
-    EXPECT_EQ(daylight_offset_seconds, standard_offset_seconds);
-    EXPECT_EQ(daylight_name, standard_name);
-  } else {
-    EXPECT_EQ(daylight_offset_seconds % (15 * 60), 0)
-        << "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);
-  }
-
-  // Test a variety of time zones. Some of these observe daylight saving time,
-  // some don’t. Some used to but no longer do. Some have uncommon UTC offsets.
-  // standard_name and daylight_name can be nullptr where no name exists to
-  // verify, as may happen when some versions of the timezone database carry
-  // invented names and others do not.
-  static constexpr struct {
-    const char* tz;
-    bool observes_dst;
-    float standard_offset_hours;
-    float daylight_offset_hours;
-    const char* standard_name;
-    const char* daylight_name;
-  } kTestTimeZones[] = {
-      {"America/Anchorage", true, -9, -8, "AKST", "AKDT"},
-      {"America/Chicago", true, -6, -5, "CST", "CDT"},
-      {"America/Denver", true, -7, -6, "MST", "MDT"},
-      {"America/Halifax", true, -4, -3, "AST", "ADT"},
-      {"America/Los_Angeles", true, -8, -7, "PST", "PDT"},
-      {"America/New_York", true, -5, -4, "EST", "EDT"},
-      {"America/Phoenix", false, -7, -7, "MST", "MST"},
-      {"Asia/Karachi", false, 5, 5, "PKT", "PKT"},
-      {"Asia/Kolkata", false, 5.5, 5.5, "IST", "IST"},
-      {"Asia/Shanghai", false, 8, 8, "CST", "CST"},
-      {"Asia/Tokyo", false, 9, 9, "JST", "JST"},
-      {"Australia/Adelaide", true, 9.5, 10.5, "ACST", "ACDT"},
-      {"Australia/Brisbane", false, 10, 10, "AEST", "AEST"},
-      {"Australia/Darwin", false, 9.5, 9.5, "ACST", "ACST"},
-      {"Australia/Eucla", false, 8.75, 8.75, nullptr, nullptr},
-      {"Australia/Lord_Howe", true, 10.5, 11, nullptr, nullptr},
-      {"Australia/Perth", false, 8, 8, "AWST", "AWST"},
-      {"Australia/Sydney", true, 10, 11, "AEST", "AEDT"},
-      {"Europe/Bucharest", true, 2, 3, "EET", "EEST"},
-      {"Europe/London", true, 0, 1, "GMT", "BST"},
-      {"Europe/Moscow", false, 3, 3, "MSK", "MSK"},
-      {"Europe/Paris", true, 1, 2, "CET", "CEST"},
-      {"Europe/Reykjavik", false, 0, 0, "UTC", "UTC"},
-      {"Pacific/Auckland", true, 12, 13, "NZST", "NZDT"},
-      {"Pacific/Honolulu", false, -10, -10, "HST", "HST"},
-      {"UTC", false, 0, 0, "UTC", "UTC"},
-  };
-
-  for (size_t index = 0; index < arraysize(kTestTimeZones); ++index) {
-    const auto& test_time_zone = kTestTimeZones[index];
-    const char* tz = test_time_zone.tz;
-    SCOPED_TRACE(base::StringPrintf("index %zu, tz %s", index, tz));
-
-    {
-      ScopedSetTZ set_tz(tz);
-      system_snapshot().TimeZone(&dst_status,
-                                 &standard_offset_seconds,
-                                 &daylight_offset_seconds,
-                                 &standard_name,
-                                 &daylight_name);
-    }
-
-    EXPECT_EQ(dst_status != SystemSnapshot::kDoesNotObserveDaylightSavingTime,
-              test_time_zone.observes_dst);
-    EXPECT_EQ(standard_offset_seconds,
-              test_time_zone.standard_offset_hours * 60 * 60);
-    EXPECT_EQ(daylight_offset_seconds,
-              test_time_zone.daylight_offset_hours * 60 * 60);
-    if (test_time_zone.standard_name) {
-      EXPECT_EQ(standard_name, test_time_zone.standard_name);
-    }
-    if (test_time_zone.daylight_name) {
-      EXPECT_EQ(daylight_name, test_time_zone.daylight_name);
-    }
-  }
-}
-
 }  // namespace
 }  // namespace test
 }  // namespace crashpad
diff --git a/snapshot/posix/timezone.cc b/snapshot/posix/timezone.cc
new file mode 100644
index 0000000..47c6ecf
--- /dev/null
+++ b/snapshot/posix/timezone.cc
@@ -0,0 +1,118 @@
+// Copyright 2017 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/posix/timezone.h"
+
+#include <stddef.h>
+#include <time.h>
+
+#include "base/logging.h"
+#include "build/build_config.h"
+
+namespace crashpad {
+namespace internal {
+
+void TimeZone(const timeval& snapshot_time,
+              SystemSnapshot::DaylightSavingTimeStatus* dst_status,
+              int* standard_offset_seconds,
+              int* daylight_offset_seconds,
+              std::string* standard_name,
+              std::string* daylight_name) {
+  tzset();
+
+  tm local;
+  PCHECK(localtime_r(&snapshot_time.tv_sec, &local)) << "localtime_r";
+
+  *standard_name = tzname[0];
+
+  bool found_transition = false;
+  long probe_gmtoff = local.tm_gmtoff;
+#if defined(OS_ANDROID)
+  // Some versions of the timezone database on Android have incorrect
+  // information (e.g. Asia/Kolkata and Pacific/Honolulu). These timezones set
+  // daylight to a non-zero value and return incorrect, >= 0 values for tm_isdst
+  // in the probes below. If tzname[1] is set to a bogus value, assume the
+  // timezone does not actually use daylight saving time.
+  if (daylight && strncmp(tzname[1], "_TZif", 5) != 0) {
+#else
+  if (daylight) {
+#endif
+    // Scan forward and backward, one month at a time, looking for an instance
+    // when the observance of daylight saving time is different than it is in
+    // |local|. It’s possible that no such instance will be found even with
+    // |daylight| set. This can happen in locations where daylight saving time
+    // was once observed or is expected to be observed in the future, but where
+    // no transitions to or from daylight saving time occurred or will occur
+    // within a year of the current date. Arizona, which last observed daylight
+    // saving time in 1967, is an example.
+    static constexpr int kMonthDeltas[] =
+        {0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6,
+         7, -7, 8, -8, 9, -9, 10, -10, 11, -11, 12, -12};
+    for (size_t index = 0;
+         index < arraysize(kMonthDeltas) && !found_transition;
+         ++index) {
+      // Look at a day of each month at local noon. Set tm_isdst to -1 to avoid
+      // giving mktime() any hints about whether to consider daylight saving
+      // time in effect. mktime() accepts values of tm_mon that are outside of
+      // its normal range and behaves as expected: if tm_mon is -1, it
+      // references December of the preceding year, and if it is 12, it
+      // references January of the following year.
+      tm probe_tm = {};
+      probe_tm.tm_hour = 12;
+      probe_tm.tm_mday = std::min(local.tm_mday, 28);
+      probe_tm.tm_mon = local.tm_mon + kMonthDeltas[index];
+      probe_tm.tm_year = local.tm_year;
+      probe_tm.tm_isdst = -1;
+      if (mktime(&probe_tm) == -1) {
+        PLOG(WARNING) << "mktime";
+        continue;
+      }
+      if (probe_tm.tm_isdst < 0 || local.tm_isdst < 0) {
+        LOG(WARNING) << "dst status not available";
+        continue;
+      }
+      if (probe_tm.tm_isdst != local.tm_isdst) {
+        found_transition = true;
+        probe_gmtoff = probe_tm.tm_gmtoff;
+      }
+    }
+  }
+
+  if (found_transition) {
+    *daylight_name = tzname[1];
+    if (!local.tm_isdst) {
+      *dst_status = SystemSnapshot::kObservingStandardTime;
+      *standard_offset_seconds = local.tm_gmtoff;
+      *daylight_offset_seconds = probe_gmtoff;
+    } else {
+      *dst_status = SystemSnapshot::kObservingDaylightSavingTime;
+      *standard_offset_seconds = probe_gmtoff;
+      *daylight_offset_seconds = local.tm_gmtoff;
+    }
+  } else {
+    *daylight_name = tzname[0];
+    *dst_status = SystemSnapshot::kDoesNotObserveDaylightSavingTime;
+#if defined(OS_ANDROID)
+    // timezone is more reliably set correctly on Android.
+    *standard_offset_seconds = -timezone;
+    *daylight_offset_seconds = -timezone;
+#else
+    *standard_offset_seconds = local.tm_gmtoff;
+    *daylight_offset_seconds = local.tm_gmtoff;
+#endif  // OS_ANDROID
+  }
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/posix/timezone.h b/snapshot/posix/timezone.h
new file mode 100644
index 0000000..bd404a3
--- /dev/null
+++ b/snapshot/posix/timezone.h
@@ -0,0 +1,55 @@
+// Copyright 2017 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_POSIX_TIMEZONE_H_
+#define CRASHPAD_SNAPSHOT_POSIX_TIMEZONE_H_
+
+#include <sys/time.h>
+
+#include <string>
+
+#include "snapshot/system_snapshot.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief Returns time zone information from the snapshot system, based on
+//!     its locale configuration and \a snapshot_time.
+//!
+//! \param[in] snapshot_time The time to use collect daylight saving time status
+//!     for, given in time since Epoch.
+//! \param[out] dst_status Whether the location observes daylight saving time,
+//!     and if so, whether it or standard time is currently being observed.
+//! \param[out] standard_offset_seconds The number of seconds that the
+//!     location’s time zone is east (ahead) of UTC during standard time.
+//! \param[out] daylight_offset_seconds The number of seconds that the
+//!     location’s time zone is east (ahead) of UTC during daylight saving.
+//!     time.
+//! \param[out] standard_name The name of the time zone while standard time is
+//!     being observed.
+//! \param[out] daylight_name The name of the time zone while daylight saving
+//!     time is being observed.
+//!
+//! \sa SystemSnapshot::TimeZone
+void TimeZone(const timeval& snapshot_time,
+              SystemSnapshot::DaylightSavingTimeStatus* dst_status,
+              int* standard_offset_seconds,
+              int* daylight_offset_seconds,
+              std::string* standard_name,
+              std::string* daylight_name);
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_POSIX_TIMEZONE_H_
diff --git a/snapshot/posix/timezone_test.cc b/snapshot/posix/timezone_test.cc
new file mode 100644
index 0000000..01bdff5
--- /dev/null
+++ b/snapshot/posix/timezone_test.cc
@@ -0,0 +1,196 @@
+// Copyright 2017 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/posix/timezone.h"
+
+#include <stdlib.h>
+#include <sys/cdefs.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "gtest/gtest.h"
+#include "test/errors.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+class ScopedSetTZ {
+ public:
+  ScopedSetTZ(const std::string& tz) {
+    const char* old_tz = getenv(kTZ);
+    old_tz_set_ = old_tz;
+    if (old_tz_set_) {
+      old_tz_.assign(old_tz);
+    }
+
+    EXPECT_EQ(setenv(kTZ, tz.c_str(), 1), 0) << ErrnoMessage("setenv");
+    tzset();
+  }
+
+  ~ScopedSetTZ() {
+    if (old_tz_set_) {
+      EXPECT_EQ(setenv(kTZ, old_tz_.c_str(), 1), 0) << ErrnoMessage("setenv");
+    } else {
+      EXPECT_EQ(unsetenv(kTZ), 0) << ErrnoMessage("unsetenv");
+    }
+    tzset();
+  }
+
+ private:
+  std::string old_tz_;
+  bool old_tz_set_;
+
+  static constexpr char kTZ[] = "TZ";
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedSetTZ);
+};
+
+constexpr char ScopedSetTZ::kTZ[];
+
+TEST(TimeZone, Basic) {
+  SystemSnapshot::DaylightSavingTimeStatus dst_status;
+  int standard_offset_seconds;
+  int daylight_offset_seconds;
+  std::string standard_name;
+  std::string daylight_name;
+
+  timeval snapshot_time;
+  ASSERT_EQ(gettimeofday(&snapshot_time, nullptr), 0);
+
+  internal::TimeZone(snapshot_time,
+                     &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(standard_offset_seconds, -timezone);
+
+  // 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(standard_offset_seconds % (15 * 60), 0)
+      << "standard_offset_seconds " << standard_offset_seconds;
+
+  if (dst_status == SystemSnapshot::kDoesNotObserveDaylightSavingTime) {
+    EXPECT_EQ(daylight_offset_seconds, standard_offset_seconds);
+    EXPECT_EQ(daylight_name, standard_name);
+  } else {
+    EXPECT_EQ(daylight_offset_seconds % (15 * 60), 0)
+        << "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);
+  }
+
+  // Test a variety of time zones. Some of these observe daylight saving time,
+  // some don’t. Some used to but no longer do. Some have uncommon UTC offsets.
+  // standard_name and daylight_name can be nullptr where no name exists to
+  // verify, as may happen when some versions of the timezone database carry
+  // invented names and others do not.
+  static constexpr struct {
+    const char* tz;
+    bool observes_dst;
+    float standard_offset_hours;
+    float daylight_offset_hours;
+    const char* standard_name;
+    const char* daylight_name;
+  } kTestTimeZones[] = {
+      {"America/Anchorage", true, -9, -8, "AKST", "AKDT"},
+      {"America/Chicago", true, -6, -5, "CST", "CDT"},
+      {"America/Denver", true, -7, -6, "MST", "MDT"},
+      {"America/Halifax", true, -4, -3, "AST", "ADT"},
+      {"America/Los_Angeles", true, -8, -7, "PST", "PDT"},
+      {"America/New_York", true, -5, -4, "EST", "EDT"},
+      {"America/Phoenix", false, -7, -7, "MST", "MST"},
+      {"Asia/Karachi", false, 5, 5, "PKT", "PKT"},
+      {"Asia/Kolkata", false, 5.5, 5.5, "IST", "IST"},
+      {"Asia/Shanghai", false, 8, 8, "CST", "CST"},
+      {"Asia/Tokyo", false, 9, 9, "JST", "JST"},
+
+      // Australian timezone names have an optional "A" prefix, which is
+      // present for glibc and macOS, but missing on Android.
+      {"Australia/Adelaide", true, 9.5, 10.5, nullptr, nullptr},
+      {"Australia/Brisbane", false, 10, 10, nullptr, nullptr},
+      {"Australia/Darwin", false, 9.5, 9.5, nullptr, nullptr},
+      {"Australia/Eucla", false, 8.75, 8.75, nullptr, nullptr},
+      {"Australia/Lord_Howe", true, 10.5, 11, nullptr, nullptr},
+      {"Australia/Perth", false, 8, 8, nullptr, nullptr},
+      {"Australia/Sydney", true, 10, 11, nullptr, nullptr},
+
+      {"Europe/Bucharest", true, 2, 3, "EET", "EEST"},
+      {"Europe/London", true, 0, 1, "GMT", "BST"},
+      {"Europe/Paris", true, 1, 2, "CET", "CEST"},
+      {"Europe/Reykjavik", false, 0, 0, nullptr, nullptr},
+      {"Pacific/Auckland", true, 12, 13, "NZST", "NZDT"},
+      {"Pacific/Honolulu", false, -10, -10, "HST", "HST"},
+      {"UTC", false, 0, 0, "UTC", "UTC"},
+  };
+
+  for (size_t index = 0; index < arraysize(kTestTimeZones); ++index) {
+    const auto& test_time_zone = kTestTimeZones[index];
+    const char* tz = test_time_zone.tz;
+    SCOPED_TRACE(base::StringPrintf("index %zu, tz %s", index, tz));
+
+    {
+      ScopedSetTZ set_tz(tz);
+      internal::TimeZone(snapshot_time,
+                         &dst_status,
+                         &standard_offset_seconds,
+                         &daylight_offset_seconds,
+                         &standard_name,
+                         &daylight_name);
+    }
+
+    EXPECT_PRED2(
+        [](SystemSnapshot::DaylightSavingTimeStatus dst, bool observes) {
+          return (dst != SystemSnapshot::kDoesNotObserveDaylightSavingTime) ==
+                 observes;
+        },
+        dst_status,
+        test_time_zone.observes_dst);
+
+    EXPECT_EQ(standard_offset_seconds,
+              test_time_zone.standard_offset_hours * 60 * 60);
+    EXPECT_EQ(daylight_offset_seconds,
+              test_time_zone.daylight_offset_hours * 60 * 60);
+    if (test_time_zone.standard_name) {
+      EXPECT_EQ(standard_name, test_time_zone.standard_name);
+    }
+    if (test_time_zone.daylight_name) {
+      EXPECT_EQ(daylight_name, test_time_zone.daylight_name);
+    }
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp
index 24ab7a7..1c63be2 100644
--- a/snapshot/snapshot.gyp
+++ b/snapshot/snapshot.gyp
@@ -57,6 +57,8 @@
         'linux/process_reader.cc',
         'linux/process_reader.h',
         'linux/signal_context.h',
+        'linux/system_snapshot_linux.cc',
+        'linux/system_snapshot_linux.h',
         'linux/thread_snapshot_linux.cc',
         'linux/thread_snapshot_linux.h',
         'mac/cpu_context_mac.cc',
@@ -107,6 +109,8 @@
         'minidump/process_snapshot_minidump.cc',
         'minidump/process_snapshot_minidump.h',
         'module_snapshot.h',
+        'posix/timezone.cc',
+        'posix/timezone.h',
         'process_snapshot.h',
         'system_snapshot.h',
         'thread_snapshot.h',
@@ -140,6 +144,8 @@
         'win/system_snapshot_win.h',
         'win/thread_snapshot_win.cc',
         'win/thread_snapshot_win.h',
+        'x86/cpuid_reader.cc',
+        'x86/cpuid_reader.h',
       ],
       'conditions': [
         ['OS=="win"', {
@@ -155,6 +161,11 @@
             'capture_memory.h',
           ],
         }],
+        ['target_arch!="ia32" and target_arch!="x64"', {
+          'sources/': [
+            ['exclude', '^x86/'],
+          ],
+        }],
       ],
       'target_conditions': [
         ['OS=="android"', {
diff --git a/snapshot/snapshot_test.gyp b/snapshot/snapshot_test.gyp
index d81c4b5..80a66f4 100644
--- a/snapshot/snapshot_test.gyp
+++ b/snapshot/snapshot_test.gyp
@@ -74,6 +74,7 @@
         'linux/elf_image_reader_test.cc',
         'linux/exception_snapshot_linux_test.cc',
         'linux/process_reader_test.cc',
+        'linux/system_snapshot_linux_test.cc',
         'mac/cpu_context_mac_test.cc',
         'mac/mach_o_image_annotations_reader_test.cc',
         'mac/mach_o_image_reader_test.cc',
@@ -82,6 +83,7 @@
         'mac/process_types_test.cc',
         'mac/system_snapshot_mac_test.cc',
         'minidump/process_snapshot_minidump_test.cc',
+        'posix/timezone_test.cc',
         'win/cpu_context_win_test.cc',
         'win/exception_snapshot_win_test.cc',
         'win/extra_memory_ranges_test.cc',
diff --git a/snapshot/system_snapshot.h b/snapshot/system_snapshot.h
index 549f0b6..6da3d37 100644
--- a/snapshot/system_snapshot.h
+++ b/snapshot/system_snapshot.h
@@ -41,6 +41,12 @@
 
     //! \brief Windows.
     kOperatingSystemWindows,
+
+    //! \brief Linux.
+    kOperatingSystemLinux,
+
+    //! \brief Android.
+    kOperatingSystemAndroid,
   };
 
   //! \brief A system’s daylight saving time status.
diff --git a/snapshot/x86/cpuid_reader.cc b/snapshot/x86/cpuid_reader.cc
new file mode 100644
index 0000000..ccedf92
--- /dev/null
+++ b/snapshot/x86/cpuid_reader.cc
@@ -0,0 +1,132 @@
+// Copyright 2017 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/x86/cpuid_reader.h"
+
+#include "build/build_config.h"
+#include "snapshot/cpu_context.h"
+
+#if defined(OS_WIN)
+#include <immintrin.h>
+#include <intrin.h>
+#endif  // OS_WIN
+
+namespace crashpad {
+namespace internal {
+
+CpuidReader::CpuidReader()
+    : features_(0),
+      extended_features_(0),
+      vendor_(),
+      max_leaf_(0),
+      signature_(0) {
+  uint32_t cpuinfo[4];
+  Cpuid(cpuinfo, 0);
+  max_leaf_ = cpuinfo[0];
+  vendor_.append(reinterpret_cast<char*>(&cpuinfo[1]), 4);
+  vendor_.append(reinterpret_cast<char*>(&cpuinfo[3]), 4);
+  vendor_.append(reinterpret_cast<char*>(&cpuinfo[2]), 4);
+
+  Cpuid(cpuinfo, 1);
+  signature_ = cpuinfo[0];
+  features_ = (static_cast<uint64_t>(cpuinfo[2]) << 32) |
+              static_cast<uint64_t>(cpuinfo[3]);
+
+  Cpuid(cpuinfo, 0x80000001);
+  extended_features_ = (static_cast<uint64_t>(cpuinfo[2]) << 32) |
+                       static_cast<uint64_t>(cpuinfo[3]);
+}
+
+CpuidReader::~CpuidReader() {}
+
+uint32_t CpuidReader::Revision() const {
+  uint8_t stepping = signature_ & 0xf;
+  uint8_t model = (signature_ & 0xf0) >> 4;
+  uint8_t family = (signature_ & 0xf00) >> 8;
+  uint8_t extended_model = static_cast<uint8_t>((signature_ & 0xf0000) >> 16);
+  uint16_t extended_family = (signature_ & 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;
+}
+
+uint32_t CpuidReader::Leaf7Features() const {
+  if (max_leaf_ < 7) {
+    return 0;
+  }
+  uint32_t cpuinfo[4];
+  Cpuid(cpuinfo, 7);
+  return cpuinfo[1];
+}
+
+bool CpuidReader::SupportsDAZ() const {
+  // 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.
+  if (!(features_ & (UINT64_C(1) << 24))) {
+    return false;
+  }
+
+#if defined(ARCH_CPU_X86)
+  using Fxsave = CPUContextX86::Fxsave;
+#elif defined(ARCH_CPU_X86_64)
+  using Fxsave = CPUContextX86_64::Fxsave;
+#endif
+
+#if defined(OS_WIN)
+  __declspec(align(16)) Fxsave fxsave = {};
+#else
+  Fxsave fxsave __attribute__((aligned(16))) = {};
+#endif
+
+  static_assert(sizeof(fxsave) == 512, "fxsave size");
+  static_assert(offsetof(decltype(fxsave), mxcsr_mask) == 28,
+                "mxcsr_mask offset");
+
+#if defined(OS_WIN)
+  _fxsave(&fxsave);
+#else
+  asm("fxsave %0" : "=m"(fxsave));
+#endif
+
+  // Test the DAZ bit.
+  return (fxsave.mxcsr_mask & (1 << 6)) != 0;
+}
+
+void CpuidReader::Cpuid(uint32_t cpuinfo[4], uint32_t leaf) const {
+#if defined(OS_WIN)
+  __cpuid(reinterpret_cast<int*>(cpuinfo), leaf);
+#else
+  asm("cpuid"
+      : "=a"(cpuinfo[0]), "=b"(cpuinfo[1]), "=c"(cpuinfo[2]), "=d"(cpuinfo[3])
+      : "a"(leaf), "b"(0), "c"(0), "d"(0));
+#endif  // OS_WIN
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/x86/cpuid_reader.h b/snapshot/x86/cpuid_reader.h
new file mode 100644
index 0000000..0fd02cf
--- /dev/null
+++ b/snapshot/x86/cpuid_reader.h
@@ -0,0 +1,63 @@
+// Copyright 2017 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 <stdint.h>
+
+#include <string>
+
+namespace crashpad {
+namespace internal {
+
+//! \brief Reads x86-family CPU information by calling `cpuid`.
+class CpuidReader {
+ public:
+  CpuidReader();
+  ~CpuidReader();
+
+  //! \see SystemSnapshot::CPURevision
+  uint32_t Revision() const;
+
+  //! \see SystemSnapshot::CPUVendor
+  std::string Vendor() const { return vendor_; }
+
+  //! \see SystemSnapshot::CPUX86Signature
+  uint32_t Signature() const { return signature_; }
+
+  //! \see SystemSnapshot::CPUX86Features
+  uint64_t Features() const { return features_; }
+
+  //! \see SystemSnapshot::CPUX86ExtendedFeatures
+  uint64_t ExtendedFeatures() const { return extended_features_; }
+
+  //! \see SystemSnapshot::CPUX86Leaf7Features
+  uint32_t Leaf7Features() const;
+
+  //! \see SystemSnapshot::NXEnabled
+  bool NXEnabled() const { return (ExtendedFeatures() & (1 << 20)) != 0; }
+
+  //! \see SystemSnapshot::CPUX86SupportsDAZ
+  bool SupportsDAZ() const;
+
+ private:
+  void Cpuid(uint32_t cpuinfo[4], uint32_t leaf) const;
+
+  uint64_t features_;
+  uint64_t extended_features_;
+  std::string vendor_;
+  uint32_t max_leaf_;
+  uint32_t signature_;
+};
+
+}  // namespace internal
+}  // namespace crashpad