blob: 2c18f30b6e49be41e4080b7a4b756b40ae7085ca [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 "util/linux/memory_map.h"
#include <stdio.h>
#include <string.h>
#include <sys/sysmacros.h>
#include "base/bit_cast.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "build/build_config.h"
#include "util/file/delimited_file_reader.h"
#include "util/file/file_io.h"
#include "util/file/string_file.h"
#include "util/stdlib/string_number_conversion.h"
namespace crashpad {
namespace {
template <typename Type>
bool HexStringToNumber(const std::string& string, Type* number) {
return StringToNumber("0x" + string, number);
}
// The result from parsing a line from the maps file.
enum class ParseResult {
// A line was successfully parsed.
kSuccess = 0,
// The end of the file was successfully reached.
kEndOfFile,
// There was an error in the file, likely because it was read non-atmoically.
// We should try to read it again.
kRetry,
// An error with a message logged.
kError
};
// Reads a line from a maps file being read by maps_file_reader and extends
// mappings with a new MemoryMap::Mapping describing the line.
ParseResult ParseMapsLine(DelimitedFileReader* maps_file_reader,
std::vector<MemoryMap::Mapping>* mappings) {
std::string field;
LinuxVMAddress start_address;
switch (maps_file_reader->GetDelim('-', &field)) {
case DelimitedFileReader::Result::kError:
return ParseResult::kError;
case DelimitedFileReader::Result::kEndOfFile:
return ParseResult::kEndOfFile;
case DelimitedFileReader::Result::kSuccess:
field.pop_back();
if (!HexStringToNumber(field, &start_address)) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
if (!mappings->empty() && start_address < mappings->back().range.End()) {
return ParseResult::kRetry;
}
}
LinuxVMAddress end_address;
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), !HexStringToNumber(field, &end_address))) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
if (end_address < start_address) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
// Skip zero-length mappings.
if (end_address == start_address) {
std::string rest_of_line;
if (maps_file_reader->GetLine(&rest_of_line) !=
DelimitedFileReader::Result::kSuccess) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
return ParseResult::kSuccess;
}
// TODO(jperaza): set bitness properly
#if defined(ARCH_CPU_64_BITS)
constexpr bool is_64_bit = true;
#else
constexpr bool is_64_bit = false;
#endif
MemoryMap::Mapping mapping;
mapping.range.SetRange(is_64_bit, start_address, end_address - start_address);
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), field.size() != 4)) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
#define SET_FIELD(actual_c, outval, true_chars, false_chars) \
do { \
if (strchr(true_chars, actual_c)) { \
*outval = true; \
} else if (strchr(false_chars, actual_c)) { \
*outval = false; \
} else { \
LOG(ERROR) << "format error"; \
return ParseResult::kError; \
} \
} while (false)
SET_FIELD(field[0], &mapping.readable, "r", "-");
SET_FIELD(field[1], &mapping.writable, "w", "-");
SET_FIELD(field[2], &mapping.executable, "x", "-");
SET_FIELD(field[3], &mapping.shareable, "sS", "p");
#undef SET_FIELD
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), !HexStringToNumber(field, &mapping.offset))) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
uint32_t major;
if (maps_file_reader->GetDelim(':', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), field.size()) < 2 ||
!HexStringToNumber(field, &major)) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
uint32_t minor;
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), field.size()) < 2 ||
!HexStringToNumber(field, &minor)) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
mapping.device = makedev(major, minor);
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), !StringToNumber(field, &mapping.inode))) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
if (maps_file_reader->GetDelim('\n', &field) !=
DelimitedFileReader::Result::kSuccess) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
if (field.back() != '\n') {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
field.pop_back();
mappings->push_back(mapping);
size_t path_start = field.find_first_not_of(' ');
if (path_start != std::string::npos) {
mappings->back().name = field.substr(path_start);
}
return ParseResult::kSuccess;
}
class SparseReverseIterator : public MemoryMap::Iterator {
public:
SparseReverseIterator(const std::vector<const MemoryMap::Mapping*>& mappings)
: mappings_(mappings), riter_(mappings_.rbegin()) {}
SparseReverseIterator() : mappings_(), riter_(mappings_.rend()) {}
SparseReverseIterator(const SparseReverseIterator&) = delete;
SparseReverseIterator& operator=(const SparseReverseIterator&) = delete;
// Iterator:
const MemoryMap::Mapping* Next() override {
return riter_ == mappings_.rend() ? nullptr : *(riter_++);
}
unsigned int Count() override { return mappings_.rend() - riter_; }
private:
std::vector<const MemoryMap::Mapping*> mappings_;
std::vector<const MemoryMap::Mapping*>::reverse_iterator riter_;
};
class FullReverseIterator : public MemoryMap::Iterator {
public:
FullReverseIterator(
std::vector<MemoryMap::Mapping>::const_reverse_iterator rbegin,
std::vector<MemoryMap::Mapping>::const_reverse_iterator rend)
: riter_(rbegin), rend_(rend) {}
FullReverseIterator(const FullReverseIterator&) = delete;
FullReverseIterator& operator=(const FullReverseIterator&) = delete;
// Iterator:
const MemoryMap::Mapping* Next() override {
return riter_ == rend_ ? nullptr : &*riter_++;
}
unsigned int Count() override { return rend_ - riter_; }
private:
std::vector<MemoryMap::Mapping>::const_reverse_iterator riter_;
std::vector<MemoryMap::Mapping>::const_reverse_iterator rend_;
};
// Faster than a CheckedRange, for temporary values.
struct FastRange {
VMAddress base;
VMSize size;
};
} // namespace
MemoryMap::Mapping::Mapping()
: name(),
range(false, 0, 0),
offset(0),
device(0),
inode(0),
readable(false),
writable(false),
executable(false),
shareable(false) {}
MemoryMap::MemoryMap() : mappings_(), connection_(nullptr), initialized_() {}
MemoryMap::~MemoryMap() {}
bool MemoryMap::Mapping::Equals(const Mapping& other) const {
DCHECK_EQ(range.Is64Bit(), other.range.Is64Bit());
return range.Base() == other.range.Base() &&
range.Size() == other.range.Size() && name == other.name &&
offset == other.offset && device == other.device &&
inode == other.inode && readable == other.readable &&
writable == other.writable && executable == other.executable &&
shareable == other.shareable;
}
bool MemoryMap::Initialize(PtraceConnection* connection) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
connection_ = connection;
// If the maps file is not read atomically, entries can be read multiple times
// or missed entirely. The kernel reads entries from this file into a page
// sized buffer, so maps files larger than a page require multiple reads.
// Attempt to reduce the time between reads by reading the entire file into a
// StringFile before attempting to parse it. If ParseMapsLine detects
// duplicate, overlapping, or out-of-order entries, it will trigger restarting
// the read up to |attempts| times.
int attempts = 3;
do {
std::string contents;
char path[32];
snprintf(path, sizeof(path), "/proc/%d/maps", connection_->GetProcessID());
if (!connection_->ReadFileContents(base::FilePath(path), &contents)) {
return false;
}
StringFile maps_file;
maps_file.SetString(contents);
DelimitedFileReader maps_file_reader(&maps_file);
ParseResult result;
while ((result = ParseMapsLine(&maps_file_reader, &mappings_)) ==
ParseResult::kSuccess) {
}
if (result == ParseResult::kEndOfFile) {
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
}
if (result == ParseResult::kError) {
return false;
}
DCHECK(result == ParseResult::kRetry);
} while (--attempts > 0);
LOG(ERROR) << "retry count exceeded";
return false;
}
const MemoryMap::Mapping* MemoryMap::FindMapping(LinuxVMAddress address) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
address = connection_->Memory()->PointerToAddress(address);
for (const auto& mapping : mappings_) {
if (mapping.range.Base() <= address && mapping.range.End() > address) {
return &mapping;
}
}
return nullptr;
}
const MemoryMap::Mapping* MemoryMap::FindMappingWithName(
const std::string& name) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
for (const auto& mapping : mappings_) {
if (mapping.name == name) {
return &mapping;
}
}
return nullptr;
}
std::vector<CheckedRange<VMAddress>> MemoryMap::GetReadableRanges(
const CheckedRange<VMAddress, VMSize>& range) const {
using Range = CheckedRange<VMAddress, VMSize>;
VMAddress range_base = range.base();
VMAddress range_end = range.end();
std::vector<FastRange> overlapping;
// Find all readable ranges overlapping the target range, maintaining order.
for (const auto& mapping : mappings_) {
if (!mapping.readable)
continue;
if (mapping.range.End() < range_base)
continue;
if (mapping.range.Base() >= range_end)
continue;
// Special case: the "[vvar]" region is marked readable, but we can't
// access it.
if (mapping.inode == 0 && mapping.name == "[vvar]")
continue;
overlapping.push_back({mapping.range.Base(), mapping.range.Size()});
}
if (overlapping.empty())
return std::vector<Range>();
// For the first and last, trim to the boundary of the incoming range.
FastRange& front = overlapping.front();
VMAddress original_front_base = front.base;
front.base = std::max(front.base, range_base);
front.size = (original_front_base + front.size) - front.base;
FastRange& back = overlapping.back();
VMAddress back_end = back.base + back.size;
back.size = std::min(range_end, back_end) - back.base;
// Coalesce, and convert to return type.
std::vector<Range> result;
result.push_back({overlapping[0].base, overlapping[0].size});
DCHECK(result.back().IsValid());
for (size_t i = 1; i < overlapping.size(); ++i) {
if (result.back().end() == overlapping[i].base) {
result.back().SetRange(result.back().base(),
result.back().size() + overlapping[i].size);
} else {
result.push_back({overlapping[i].base, overlapping[i].size});
}
DCHECK(result.back().IsValid());
}
return result;
}
std::unique_ptr<MemoryMap::Iterator> MemoryMap::FindFilePossibleMmapStarts(
const Mapping& mapping) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
std::vector<const Mapping*> possible_starts;
// If the mapping is anonymous, as is for the VDSO, there is no mapped file to
// find the start of, so just return the input mapping.
if (mapping.device == 0 && mapping.inode == 0) {
for (const auto& candidate : mappings_) {
if (mapping.Equals(candidate)) {
possible_starts.push_back(&candidate);
return std::make_unique<SparseReverseIterator>(possible_starts);
}
}
LOG(ERROR) << "mapping not found";
return std::make_unique<SparseReverseIterator>();
}
#if BUILDFLAG(IS_ANDROID)
// The Android Chromium linker uses ashmem to share RELRO segments between
// processes. The original RELRO segment has been unmapped and replaced with a
// mapping named "/dev/ashmem/RELRO:<libname>" where <libname> is the base
// library name (e.g. libchrome.so) sans any preceding path that may be
// present in other mappings for the library.
// https://crashpad.chromium.org/bug/253
static constexpr char kRelro[] = "/dev/ashmem/RELRO:";
if (mapping.name.compare(0, strlen(kRelro), kRelro, 0, strlen(kRelro)) == 0) {
// The kernel appends "(deleted)" to ashmem mappings because there isn't
// any corresponding file on the filesystem.
static constexpr char kDeleted[] = " (deleted)";
size_t libname_end = mapping.name.rfind(kDeleted);
DCHECK_NE(libname_end, std::string::npos);
if (libname_end == std::string::npos) {
libname_end = mapping.name.size();
}
std::string libname =
mapping.name.substr(strlen(kRelro), libname_end - strlen(kRelro));
for (const auto& candidate : mappings_) {
if (candidate.name.rfind(libname) != std::string::npos) {
possible_starts.push_back(&candidate);
}
if (mapping.Equals(candidate)) {
return std::make_unique<SparseReverseIterator>(possible_starts);
}
}
}
#endif // BUILDFLAG(IS_ANDROID)
for (const auto& candidate : mappings_) {
if (candidate.device == mapping.device &&
candidate.inode == mapping.inode
#if !BUILDFLAG(IS_ANDROID)
// Libraries on Android may be mapped from zipfiles (APKs), in which
// case the offset is not 0.
&& candidate.offset == 0
#endif // !BUILDFLAG(IS_ANDROID)
) {
possible_starts.push_back(&candidate);
}
if (mapping.Equals(candidate)) {
return std::make_unique<SparseReverseIterator>(possible_starts);
}
}
LOG(ERROR) << "mapping not found";
return std::make_unique<SparseReverseIterator>();
}
std::unique_ptr<MemoryMap::Iterator> MemoryMap::ReverseIteratorFrom(
const Mapping& target) const {
for (auto riter = mappings_.crbegin(); riter != mappings_.rend(); ++riter) {
if (riter->Equals(target)) {
return std::make_unique<FullReverseIterator>(riter, mappings_.rend());
}
}
return std::make_unique<FullReverseIterator>(mappings_.rend(),
mappings_.rend());
}
} // namespace crashpad