| // Copyright 2017 The Crashpad Authors |
| // |
| // 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/check_op.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/stringprintf.h" |
| #include "build/build_config.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 BUILDFLAG(IS_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 (!base::StringToUint(base::StringPiece(left), &start) || |
| !base::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 (!base::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 BUILDFLAG(IS_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 // BUILDFLAG(IS_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(ProcessReaderLinux* process_reader, |
| const timeval* snapshot_time) { |
| INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| process_reader_ = process_reader; |
| snapshot_time_ = snapshot_time; |
| |
| #if BUILDFLAG(IS_ANDROID) |
| std::string build_string; |
| if (ReadProperty("ro.build.fingerprint", &build_string)) { |
| os_version_build_ = build_string; |
| os_version_full_ = build_string; |
| } |
| #endif // BUILDFLAG(IS_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; |
| #elif defined(ARCH_CPU_ARM_FAMILY) |
| return process_reader_->Is64Bit() ? kCPUArchitectureARM64 |
| : kCPUArchitectureARM; |
| #elif defined(ARCH_CPU_MIPS_FAMILY) |
| return process_reader_->Is64Bit() ? kCPUArchitectureMIPS64EL |
| : kCPUArchitectureMIPSEL; |
| #elif defined(ARCH_CPU_RISCV64) |
| return kCPUArchitectureRISCV64; |
| #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(); |
| #elif defined(ARCH_CPU_ARM_FAMILY) |
| // TODO(jperaza): do this. https://crashpad.chromium.org/bug/30 |
| return 0; |
| #elif defined(ARCH_CPU_MIPS_FAMILY) |
| // Not implementable on MIPS |
| return 0; |
| #elif defined(ARCH_CPU_RISCV64) |
| // Not implemented |
| return 0; |
| #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(); |
| #elif defined(ARCH_CPU_ARM_FAMILY) |
| // TODO(jperaza): do this. https://crashpad.chromium.org/bug/30 |
| return std::string(); |
| #elif defined(ARCH_CPU_MIPS_FAMILY) |
| // Not implementable on MIPS |
| return std::string(); |
| #elif defined(ARCH_CPU_RISCV64) |
| // Not implemented |
| return std::string(); |
| #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_IN_MIGRATION(); |
| return 0; |
| #endif |
| } |
| |
| uint64_t SystemSnapshotLinux::CPUX86Features() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| #if defined(ARCH_CPU_X86_FAMILY) |
| return cpuid_.Features(); |
| #else |
| NOTREACHED_IN_MIGRATION(); |
| return 0; |
| #endif |
| } |
| |
| uint64_t SystemSnapshotLinux::CPUX86ExtendedFeatures() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| #if defined(ARCH_CPU_X86_FAMILY) |
| return cpuid_.ExtendedFeatures(); |
| #else |
| NOTREACHED_IN_MIGRATION(); |
| return 0; |
| #endif |
| } |
| |
| uint32_t SystemSnapshotLinux::CPUX86Leaf7Features() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| #if defined(ARCH_CPU_X86_FAMILY) |
| return cpuid_.Leaf7Features(); |
| #else |
| NOTREACHED_IN_MIGRATION(); |
| return 0; |
| #endif |
| } |
| |
| bool SystemSnapshotLinux::CPUX86SupportsDAZ() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| #if defined(ARCH_CPU_X86_FAMILY) |
| return cpuid_.SupportsDAZ(); |
| #else |
| NOTREACHED_IN_MIGRATION(); |
| return false; |
| #endif // ARCH_CPU_X86_FMAILY |
| } |
| |
| SystemSnapshot::OperatingSystem SystemSnapshotLinux::GetOperatingSystem() |
| const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| #if BUILDFLAG(IS_ANDROID) |
| return kOperatingSystemAndroid; |
| #else |
| return kOperatingSystemLinux; |
| #endif // BUILDFLAG(IS_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 BUILDFLAG(IS_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 // BUILDFLAG(IS_ANDROID) |
| } |
| |
| bool SystemSnapshotLinux::NXEnabled() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| #if defined(ARCH_CPU_X86_FAMILY) |
| return cpuid_.NXEnabled(); |
| #elif defined(ARCH_CPU_ARM_FAMILY) |
| // TODO(jperaza): do this. https://crashpad.chromium.org/bug/30 |
| return false; |
| #elif defined(ARCH_CPU_MIPS_FAMILY) |
| // Not implementable on MIPS |
| return false; |
| #elif defined(ARCH_CPU_RISCV64) |
| // Not implemented |
| return false; |
| #else |
| #error Port. |
| #endif // ARCH_CPU_X86_FAMILY |
| } |
| |
| 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 (!base::StringToInt(base::StringPiece(versions[0]), &os_version_major_)) { |
| LOG(WARNING) << "no kernel version"; |
| return; |
| } |
| DCHECK_GE(os_version_major_, 3); |
| |
| if (!base::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 (!base::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 |