blob: 3eb91b4f4001643a37131eb39675790b6fb2e44c [file] [log] [blame]
// 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