crashpad/util/linux/memory_map.cc
Joshua Peraza a79791969d linux: Add MemoryMap::FindFileMmapStart
ELF executables and libraries may be loaded into memory in several
mappings, possibly with holes containing anonymous mappings
or mappings of other files. This method takes an input mapping and
attempts to find the mapping for file offset 0 of the same file.

Bug: crashpad:30
Change-Id: I79abf060b015d58ef0eba54a399a74315d7d2d77
Reviewed-on: https://chromium-review.googlesource.com/565223
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
2017-07-11 16:19:48 +00:00

317 lines
10 KiB
C++

// 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 "util/linux/memory_map.h"
#include <linux/kdev_t.h>
#include <stdio.h>
#include <string.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 {
// This function is used in this file specfically for signed or unsigned longs.
// longs are typically either int or int64 sized, but pointers to longs are not
// automatically coerced to pointers to ints when they are the same size.
// Simply adding a StringToNumber for longs doesn't work since sometimes long
// and int64_t are actually the same type, resulting in a redefinition error.
template <typename Type>
bool LocalStringToNumber(const base::StringPiece& string, Type* number) {
static_assert(sizeof(Type) == sizeof(int) || sizeof(Type) == sizeof(int64_t),
"Unexpected Type size");
if (sizeof(Type) == sizeof(int)) {
return std::numeric_limits<Type>::is_signed
? StringToNumber(string, reinterpret_cast<int*>(number))
: StringToNumber(string,
reinterpret_cast<unsigned int*>(number));
} else {
return std::numeric_limits<Type>::is_signed
? StringToNumber(string, reinterpret_cast<int64_t*>(number))
: StringToNumber(string, reinterpret_cast<uint64_t*>(number));
}
}
template <typename Type>
bool HexStringToNumber(const std::string& string, Type* number) {
return LocalStringToNumber("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;
}
// TODO(jperaza): set bitness properly
#if defined(ARCH_CPU_64_BITS)
const bool is_64_bit = true;
#else
const 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;
}
int major, minor;
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), field.size() != 5) ||
!HexStringToNumber(field.substr(0, 2), &major) ||
!HexStringToNumber(field.substr(3, 2), &minor)) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
mapping.device = MKDEV(major, minor);
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), !LocalStringToNumber(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;
}
} // 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_(), 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(pid_t pid) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
// 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", pid);
if (!LoggingReadEntireFile(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_);
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;
}
const MemoryMap::Mapping* MemoryMap::FindFileMmapStart(
const Mapping& mapping) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
size_t index = 0;
for (; index < mappings_.size(); ++index) {
if (mappings_[index].Equals(mapping)) {
break;
}
}
if (index >= mappings_.size()) {
LOG(ERROR) << "mapping not found";
return nullptr;
}
// 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) {
return &mappings_[index];
}
do {
// There may by anonymous mappings or other files mapped into the holes,
// so check that the mapping uses the same file as the input, but keep
// searching if it doesn't.
if (mappings_[index].device == mapping.device &&
mappings_[index].inode == mapping.inode &&
mappings_[index].offset == 0) {
return &mappings_[index];
}
} while (index--);
LOG(ERROR) << "mapping not found";
return nullptr;
}
} // namespace crashpad