2017-04-25 08:12:49 -07:00
|
|
|
// 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 <stdio.h>
|
|
|
|
#include <string.h>
|
2017-10-23 13:58:42 -04:00
|
|
|
#include <sys/sysmacros.h>
|
2017-04-25 08:12:49 -07:00
|
|
|
|
|
|
|
#include "base/files/file_path.h"
|
|
|
|
#include "base/logging.h"
|
|
|
|
#include "build/build_config.h"
|
|
|
|
#include "util/file/delimited_file_reader.h"
|
2017-05-02 10:07:21 -07:00
|
|
|
#include "util/file/file_io.h"
|
|
|
|
#include "util/file/string_file.h"
|
2017-04-25 08:12:49 -07:00
|
|
|
#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>
|
2018-03-02 21:45:29 -05:00
|
|
|
bool LocalStringToNumber(const std::string& string, Type* number) {
|
2017-04-25 08:12:49 -07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
// The result from parsing a line from the maps file.
|
|
|
|
enum class ParseResult {
|
|
|
|
// A line was successfully parsed.
|
|
|
|
kSuccess = 0,
|
2017-04-25 08:12:49 -07:00
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
// The end of the file was successfully reached.
|
|
|
|
kEndOfFile,
|
2017-04-25 08:12:49 -07:00
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
// There was an error in the file, likely because it was read non-atmoically.
|
|
|
|
// We should try to read it again.
|
|
|
|
kRetry,
|
2017-04-25 08:12:49 -07:00
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
// An error with a message logged.
|
|
|
|
kError
|
|
|
|
};
|
2017-04-25 08:12:49 -07:00
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
// 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) {
|
2017-04-25 08:12:49 -07:00
|
|
|
std::string field;
|
2017-05-02 10:07:21 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2017-04-25 08:12:49 -07:00
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
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;
|
|
|
|
}
|
2017-04-25 08:12:49 -07:00
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
// TODO(jperaza): set bitness properly
|
2017-04-25 08:12:49 -07:00
|
|
|
#if defined(ARCH_CPU_64_BITS)
|
2017-07-25 19:15:48 -04:00
|
|
|
constexpr bool is_64_bit = true;
|
2017-04-25 08:12:49 -07:00
|
|
|
#else
|
2017-07-25 19:15:48 -04:00
|
|
|
constexpr bool is_64_bit = false;
|
2017-04-25 08:12:49 -07:00
|
|
|
#endif
|
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
MemoryMap::Mapping mapping;
|
|
|
|
mapping.range.SetRange(is_64_bit, start_address, end_address - start_address);
|
2017-04-25 08:12:49 -07:00
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
if (maps_file_reader->GetDelim(' ', &field) !=
|
|
|
|
DelimitedFileReader::Result::kSuccess ||
|
|
|
|
(field.pop_back(), field.size() != 4)) {
|
|
|
|
LOG(ERROR) << "format error";
|
|
|
|
return ParseResult::kError;
|
|
|
|
}
|
2017-04-25 08:12:49 -07:00
|
|
|
#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"; \
|
2017-05-02 10:07:21 -07:00
|
|
|
return ParseResult::kError; \
|
2017-04-25 08:12:49 -07:00
|
|
|
} \
|
|
|
|
} while (false)
|
2017-05-02 10:07:21 -07:00
|
|
|
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");
|
2017-04-25 08:12:49 -07:00
|
|
|
#undef SET_FIELD
|
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
if (maps_file_reader->GetDelim(' ', &field) !=
|
|
|
|
DelimitedFileReader::Result::kSuccess ||
|
|
|
|
(field.pop_back(), !HexStringToNumber(field, &mapping.offset))) {
|
|
|
|
LOG(ERROR) << "format error";
|
|
|
|
return ParseResult::kError;
|
|
|
|
}
|
2017-04-25 08:12:49 -07:00
|
|
|
|
2017-10-23 13:58:42 -04:00
|
|
|
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;
|
2017-05-02 10:07:21 -07:00
|
|
|
if (maps_file_reader->GetDelim(' ', &field) !=
|
|
|
|
DelimitedFileReader::Result::kSuccess ||
|
2017-10-23 13:58:42 -04:00
|
|
|
(field.pop_back(), field.size()) < 2 ||
|
|
|
|
!HexStringToNumber(field, &minor)) {
|
2017-05-02 10:07:21 -07:00
|
|
|
LOG(ERROR) << "format error";
|
|
|
|
return ParseResult::kError;
|
|
|
|
}
|
2017-10-23 13:58:42 -04:00
|
|
|
|
|
|
|
mapping.device = makedev(major, minor);
|
2017-04-25 08:12:49 -07:00
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
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() {}
|
|
|
|
|
2017-07-11 08:59:24 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-04-03 15:01:44 -07:00
|
|
|
bool MemoryMap::Initialize(PtraceConnection* connection) {
|
2017-05-02 10:07:21 -07:00
|
|
|
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];
|
2018-04-03 15:01:44 -07:00
|
|
|
snprintf(path, sizeof(path), "/proc/%d/maps", connection->GetProcessID());
|
|
|
|
if (!connection->ReadFileContents(base::FilePath(path), &contents)) {
|
2017-04-25 08:12:49 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
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;
|
2017-04-25 08:12:49 -07:00
|
|
|
}
|
2017-05-02 10:07:21 -07:00
|
|
|
if (result == ParseResult::kError) {
|
2017-04-25 08:12:49 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
DCHECK(result == ParseResult::kRetry);
|
|
|
|
} while (--attempts > 0);
|
2017-04-25 08:12:49 -07:00
|
|
|
|
2017-05-02 10:07:21 -07:00
|
|
|
LOG(ERROR) << "retry count exceeded";
|
|
|
|
return false;
|
2017-04-25 08:12:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-06-13 08:39:46 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-07-11 08:59:24 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-04-25 08:12:49 -07:00
|
|
|
} // namespace crashpad
|