mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
linux: Add MemoryMap to collect information about mapped memory regions
Bug: crashpad:30 Change-Id: Id11d549829bd1a956d31991d4b829a43ce5696aa Reviewed-on: https://chromium-review.googlesource.com/477597 Commit-Queue: Joshua Peraza <jperaza@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
parent
fd8e2de0c5
commit
4036e2c9d9
207
util/linux/memory_map.cc
Normal file
207
util/linux/memory_map.cc
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
// 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_reader.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // 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::Initialize(pid_t pid) {
|
||||||
|
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||||
|
|
||||||
|
char path[32];
|
||||||
|
snprintf(path, sizeof(path), "/proc/%d/maps", pid);
|
||||||
|
FileReader maps_file;
|
||||||
|
if (!maps_file.Open(base::FilePath(path))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DelimitedFileReader maps_file_reader(&maps_file);
|
||||||
|
|
||||||
|
DelimitedFileReader::Result result;
|
||||||
|
std::string field;
|
||||||
|
while ((result = maps_file_reader.GetDelim('-', &field)) ==
|
||||||
|
DelimitedFileReader::Result::kSuccess) {
|
||||||
|
field.pop_back();
|
||||||
|
|
||||||
|
LinuxVMAddress start_address;
|
||||||
|
if (!HexStringToNumber(field, &start_address)) {
|
||||||
|
LOG(ERROR) << "format error";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinuxVMAddress end_address;
|
||||||
|
if (maps_file_reader.GetDelim(' ', &field) !=
|
||||||
|
DelimitedFileReader::Result::kSuccess ||
|
||||||
|
(field.pop_back(), !HexStringToNumber(field, &end_address))) {
|
||||||
|
LOG(ERROR) << "format error";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (end_address <= start_address) {
|
||||||
|
LOG(ERROR) << "format error";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
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 false;
|
||||||
|
}
|
||||||
|
#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 false; \
|
||||||
|
} \
|
||||||
|
} 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 false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 false;
|
||||||
|
}
|
||||||
|
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 false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maps_file_reader.GetDelim('\n', &field) !=
|
||||||
|
DelimitedFileReader::Result::kSuccess) {
|
||||||
|
LOG(ERROR) << "format error";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (field.back() != '\n') {
|
||||||
|
LOG(ERROR) << "format error";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result != DelimitedFileReader::Result::kEndOfFile) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crashpad
|
78
util/linux/memory_map.h
Normal file
78
util/linux/memory_map.h
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef CRASHPAD_UTIL_LINUX_MEMORY_MAP_H_
|
||||||
|
#define CRASHPAD_UTIL_LINUX_MEMORY_MAP_H_
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "util/linux/address_types.h"
|
||||||
|
#include "util/linux/checked_linux_address_range.h"
|
||||||
|
#include "util/misc/initialization_state_dcheck.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
//! \brief Accesses information about mapped memory in another process.
|
||||||
|
//!
|
||||||
|
//! The target process must be stopped to guarantee correct mappings. If the
|
||||||
|
//! target process is not stopped, mappings may be invalid after the return from
|
||||||
|
//! Initialize(), and even mappings existing at the time Initialize() was called
|
||||||
|
//! may not be found.
|
||||||
|
class MemoryMap {
|
||||||
|
public:
|
||||||
|
//! \brief Information about a mapped region of memory.
|
||||||
|
struct Mapping {
|
||||||
|
Mapping();
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
CheckedLinuxAddressRange range;
|
||||||
|
off_t offset;
|
||||||
|
dev_t device;
|
||||||
|
ino_t inode;
|
||||||
|
bool readable;
|
||||||
|
bool writable;
|
||||||
|
bool executable;
|
||||||
|
bool shareable;
|
||||||
|
};
|
||||||
|
|
||||||
|
MemoryMap();
|
||||||
|
~MemoryMap();
|
||||||
|
|
||||||
|
//! \brief Initializes this object with information about the mapped memory
|
||||||
|
//! regions in the process whose ID is \a pid.
|
||||||
|
//!
|
||||||
|
//! This method must be called successfully prior to calling any other method
|
||||||
|
//! in this class. This method may only be called once.
|
||||||
|
//!
|
||||||
|
//! \param[in] pid The process ID to obtain information for.
|
||||||
|
//!
|
||||||
|
//! \return `true` on success, `false` on failure with a message logged.
|
||||||
|
bool Initialize(pid_t pid);
|
||||||
|
|
||||||
|
//! \return The Mapping containing \a address. The caller does not take
|
||||||
|
//! ownership of this object. It is scoped to the lifetime of the
|
||||||
|
//! MemoryMap object that it was obtained from.
|
||||||
|
const Mapping* FindMapping(LinuxVMAddress address) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Mapping> mappings_;
|
||||||
|
InitializationStateDcheck initialized_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace crashpad
|
||||||
|
|
||||||
|
#endif // CRASHPAD_UTIL_LINUX_MEMORY_MAP_H_
|
329
util/linux/memory_map_test.cc
Normal file
329
util/linux/memory_map_test.cc
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
// 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 <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "base/files/file_path.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "test/errors.h"
|
||||||
|
#include "test/file.h"
|
||||||
|
#include "test/multiprocess.h"
|
||||||
|
#include "test/scoped_temp_dir.h"
|
||||||
|
#include "util/file/file_io.h"
|
||||||
|
#include "util/misc/clock.h"
|
||||||
|
#include "util/posix/scoped_mmap.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(MemoryMap, SelfBasic) {
|
||||||
|
ScopedMmap mmapping;
|
||||||
|
ASSERT_TRUE(mmapping.ResetMmap(nullptr,
|
||||||
|
getpagesize(),
|
||||||
|
PROT_EXEC | PROT_READ,
|
||||||
|
MAP_SHARED | MAP_ANON,
|
||||||
|
-1,
|
||||||
|
0));
|
||||||
|
MemoryMap map;
|
||||||
|
ASSERT_TRUE(map.Initialize(getpid()));
|
||||||
|
|
||||||
|
auto stack_address = reinterpret_cast<LinuxVMAddress>(&map);
|
||||||
|
const MemoryMap::Mapping* mapping = map.FindMapping(stack_address);
|
||||||
|
ASSERT_TRUE(mapping);
|
||||||
|
EXPECT_GE(stack_address, mapping->range.Base());
|
||||||
|
EXPECT_LT(stack_address, mapping->range.End());
|
||||||
|
EXPECT_TRUE(mapping->readable);
|
||||||
|
EXPECT_TRUE(mapping->writable);
|
||||||
|
|
||||||
|
auto code_address = reinterpret_cast<LinuxVMAddress>(getpid);
|
||||||
|
mapping = map.FindMapping(code_address);
|
||||||
|
ASSERT_TRUE(mapping);
|
||||||
|
EXPECT_GE(code_address, mapping->range.Base());
|
||||||
|
EXPECT_LT(code_address, mapping->range.End());
|
||||||
|
EXPECT_TRUE(mapping->readable);
|
||||||
|
EXPECT_FALSE(mapping->writable);
|
||||||
|
EXPECT_TRUE(mapping->executable);
|
||||||
|
|
||||||
|
auto mapping_address = mmapping.addr_as<LinuxVMAddress>();
|
||||||
|
mapping = map.FindMapping(mapping_address);
|
||||||
|
ASSERT_TRUE(mapping);
|
||||||
|
mapping = map.FindMapping(mapping_address + mmapping.len() - 1);
|
||||||
|
ASSERT_TRUE(mapping);
|
||||||
|
EXPECT_EQ(mapping_address, mapping->range.Base());
|
||||||
|
EXPECT_EQ(mapping_address + mmapping.len(), mapping->range.End());
|
||||||
|
EXPECT_TRUE(mapping->readable);
|
||||||
|
EXPECT_FALSE(mapping->writable);
|
||||||
|
EXPECT_TRUE(mapping->executable);
|
||||||
|
EXPECT_TRUE(mapping->shareable);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MapChildTest : public Multiprocess {
|
||||||
|
public:
|
||||||
|
MapChildTest() : Multiprocess(), page_size_(getpagesize()) {}
|
||||||
|
~MapChildTest() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void MultiprocessParent() override {
|
||||||
|
LinuxVMAddress code_address;
|
||||||
|
CheckedReadFileExactly(
|
||||||
|
ReadPipeHandle(), &code_address, sizeof(code_address));
|
||||||
|
|
||||||
|
LinuxVMAddress stack_address;
|
||||||
|
CheckedReadFileExactly(
|
||||||
|
ReadPipeHandle(), &stack_address, sizeof(stack_address));
|
||||||
|
|
||||||
|
LinuxVMAddress mapped_address;
|
||||||
|
CheckedReadFileExactly(
|
||||||
|
ReadPipeHandle(), &mapped_address, sizeof(mapped_address));
|
||||||
|
|
||||||
|
LinuxVMAddress mapped_file_address;
|
||||||
|
CheckedReadFileExactly(
|
||||||
|
ReadPipeHandle(), &mapped_file_address, sizeof(mapped_file_address));
|
||||||
|
LinuxVMSize path_length;
|
||||||
|
CheckedReadFileExactly(ReadPipeHandle(), &path_length, sizeof(path_length));
|
||||||
|
std::string mapped_file_name(path_length, std::string::value_type());
|
||||||
|
CheckedReadFileExactly(ReadPipeHandle(), &mapped_file_name[0], path_length);
|
||||||
|
|
||||||
|
MemoryMap map;
|
||||||
|
ASSERT_TRUE(map.Initialize(ChildPID()));
|
||||||
|
|
||||||
|
const MemoryMap::Mapping* mapping = map.FindMapping(code_address);
|
||||||
|
ASSERT_TRUE(mapping);
|
||||||
|
EXPECT_GE(code_address, mapping->range.Base());
|
||||||
|
EXPECT_LT(code_address, mapping->range.End());
|
||||||
|
EXPECT_TRUE(mapping->readable);
|
||||||
|
EXPECT_TRUE(mapping->executable);
|
||||||
|
EXPECT_FALSE(mapping->writable);
|
||||||
|
|
||||||
|
mapping = map.FindMapping(stack_address);
|
||||||
|
ASSERT_TRUE(mapping);
|
||||||
|
EXPECT_GE(stack_address, mapping->range.Base());
|
||||||
|
EXPECT_LT(stack_address, mapping->range.End());
|
||||||
|
EXPECT_TRUE(mapping->readable);
|
||||||
|
EXPECT_TRUE(mapping->writable);
|
||||||
|
|
||||||
|
mapping = map.FindMapping(mapped_address);
|
||||||
|
ASSERT_TRUE(mapping);
|
||||||
|
EXPECT_EQ(mapped_address, mapping->range.Base());
|
||||||
|
EXPECT_EQ(mapped_address + page_size_, mapping->range.End());
|
||||||
|
EXPECT_FALSE(mapping->readable);
|
||||||
|
EXPECT_FALSE(mapping->writable);
|
||||||
|
EXPECT_FALSE(mapping->executable);
|
||||||
|
EXPECT_TRUE(mapping->shareable);
|
||||||
|
|
||||||
|
mapping = map.FindMapping(mapped_file_address);
|
||||||
|
ASSERT_TRUE(mapping);
|
||||||
|
EXPECT_EQ(mapped_file_address, mapping->range.Base());
|
||||||
|
EXPECT_EQ(mapping->offset, static_cast<int64_t>(page_size_));
|
||||||
|
EXPECT_TRUE(mapping->readable);
|
||||||
|
EXPECT_TRUE(mapping->writable);
|
||||||
|
EXPECT_FALSE(mapping->executable);
|
||||||
|
EXPECT_FALSE(mapping->shareable);
|
||||||
|
EXPECT_EQ(mapping->name, mapped_file_name);
|
||||||
|
struct stat file_stat;
|
||||||
|
ASSERT_EQ(stat(mapped_file_name.c_str(), &file_stat), 0)
|
||||||
|
<< ErrnoMessage("stat");
|
||||||
|
EXPECT_EQ(mapping->device, file_stat.st_dev);
|
||||||
|
EXPECT_EQ(mapping->inode, file_stat.st_ino);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiprocessChild() override {
|
||||||
|
auto code_address = reinterpret_cast<LinuxVMAddress>(getpid);
|
||||||
|
CheckedWriteFile(WritePipeHandle(), &code_address, sizeof(code_address));
|
||||||
|
|
||||||
|
auto stack_address = reinterpret_cast<LinuxVMAddress>(&code_address);
|
||||||
|
CheckedWriteFile(WritePipeHandle(), &stack_address, sizeof(stack_address));
|
||||||
|
|
||||||
|
ScopedMmap mapping;
|
||||||
|
ASSERT_TRUE(mapping.ResetMmap(
|
||||||
|
nullptr, page_size_, PROT_NONE, MAP_SHARED | MAP_ANON, -1, 0));
|
||||||
|
auto mapped_address = mapping.addr_as<LinuxVMAddress>();
|
||||||
|
CheckedWriteFile(
|
||||||
|
WritePipeHandle(), &mapped_address, sizeof(mapped_address));
|
||||||
|
|
||||||
|
ScopedTempDir temp_dir;
|
||||||
|
base::FilePath path =
|
||||||
|
temp_dir.path().Append(FILE_PATH_LITERAL("test_file"));
|
||||||
|
ASSERT_FALSE(FileExists(path));
|
||||||
|
std::string path_string = path.value();
|
||||||
|
ScopedFileHandle handle(LoggingOpenFileForReadAndWrite(
|
||||||
|
path, FileWriteMode::kReuseOrCreate, FilePermissions::kOwnerOnly));
|
||||||
|
ASSERT_TRUE(handle.is_valid());
|
||||||
|
std::unique_ptr<char[]> file_contents(new char[page_size_ * 2]);
|
||||||
|
CheckedWriteFile(handle.get(), file_contents.get(), page_size_ * 2);
|
||||||
|
|
||||||
|
ScopedMmap file_mapping;
|
||||||
|
ASSERT_TRUE(file_mapping.ResetMmap(nullptr,
|
||||||
|
page_size_,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE,
|
||||||
|
handle.get(),
|
||||||
|
page_size_));
|
||||||
|
|
||||||
|
auto mapped_file_address = file_mapping.addr_as<LinuxVMAddress>();
|
||||||
|
CheckedWriteFile(
|
||||||
|
WritePipeHandle(), &mapped_file_address, sizeof(mapped_file_address));
|
||||||
|
LinuxVMSize path_length = path_string.size();
|
||||||
|
CheckedWriteFile(WritePipeHandle(), &path_length, sizeof(path_length));
|
||||||
|
CheckedWriteFile(WritePipeHandle(), path_string.c_str(), path_length);
|
||||||
|
|
||||||
|
CheckedReadFileAtEOF(ReadPipeHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t page_size_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(MapChildTest);
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(MemoryMap, MapChild) {
|
||||||
|
MapChildTest test;
|
||||||
|
test.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some systems optimize mappings by allocating new mappings inside existing
|
||||||
|
// mappings with matching permissions. Work around this by allocating one large
|
||||||
|
// mapping and then switching up the permissions of individual pages to force
|
||||||
|
// populating more entries in the maps file.
|
||||||
|
void InitializeMappings(ScopedMmap* mappings,
|
||||||
|
size_t num_mappings,
|
||||||
|
size_t mapping_size) {
|
||||||
|
ASSERT_TRUE(mappings->ResetMmap(nullptr,
|
||||||
|
mapping_size * num_mappings,
|
||||||
|
PROT_READ,
|
||||||
|
MAP_PRIVATE | MAP_ANON,
|
||||||
|
-1,
|
||||||
|
0));
|
||||||
|
|
||||||
|
auto region = mappings->addr_as<LinuxVMAddress>();
|
||||||
|
for (size_t index = 0; index < num_mappings; index += 2) {
|
||||||
|
ASSERT_EQ(mprotect(reinterpret_cast<void*>(region + index * mapping_size),
|
||||||
|
mapping_size,
|
||||||
|
PROT_READ | PROT_WRITE),
|
||||||
|
0)
|
||||||
|
<< ErrnoMessage("mprotect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExpectMappings(const MemoryMap& map,
|
||||||
|
LinuxVMAddress region_addr,
|
||||||
|
size_t num_mappings,
|
||||||
|
size_t mapping_size) {
|
||||||
|
for (size_t index = 0; index < num_mappings; ++index) {
|
||||||
|
auto mapping_address = region_addr + index * mapping_size;
|
||||||
|
const MemoryMap::Mapping* mapping = map.FindMapping(mapping_address);
|
||||||
|
ASSERT_TRUE(mapping);
|
||||||
|
EXPECT_EQ(mapping_address, mapping->range.Base());
|
||||||
|
EXPECT_EQ(mapping_address + mapping_size, mapping->range.End());
|
||||||
|
EXPECT_TRUE(mapping->readable);
|
||||||
|
EXPECT_FALSE(mapping->shareable);
|
||||||
|
EXPECT_FALSE(mapping->executable);
|
||||||
|
if (index % 2 == 0) {
|
||||||
|
EXPECT_TRUE(mapping->writable);
|
||||||
|
} else {
|
||||||
|
EXPECT_FALSE(mapping->writable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MemoryMap, SelfLargeMapFile) {
|
||||||
|
constexpr size_t kNumMappings = 4096;
|
||||||
|
const size_t page_size = getpagesize();
|
||||||
|
ScopedMmap mappings;
|
||||||
|
|
||||||
|
ASSERT_NO_FATAL_FAILURE(
|
||||||
|
InitializeMappings(&mappings, kNumMappings, page_size));
|
||||||
|
|
||||||
|
MemoryMap map;
|
||||||
|
ASSERT_TRUE(map.Initialize(getpid()));
|
||||||
|
|
||||||
|
ExpectMappings(
|
||||||
|
map, mappings.addr_as<LinuxVMAddress>(), kNumMappings, page_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MapRunningChildTest : public Multiprocess {
|
||||||
|
public:
|
||||||
|
MapRunningChildTest() : Multiprocess(), page_size_(getpagesize()) {}
|
||||||
|
~MapRunningChildTest() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void MultiprocessParent() override {
|
||||||
|
// Let the child get started
|
||||||
|
LinuxVMAddress region_addr;
|
||||||
|
CheckedReadFileExactly(ReadPipeHandle(), ®ion_addr, sizeof(region_addr));
|
||||||
|
|
||||||
|
// Let the child get back to its work
|
||||||
|
SleepNanoseconds(1000);
|
||||||
|
|
||||||
|
for (int iter = 0; iter < 8; ++iter) {
|
||||||
|
MemoryMap map;
|
||||||
|
ASSERT_TRUE(map.Initialize(ChildPID()));
|
||||||
|
|
||||||
|
// We should at least find the original mappings. The extra mappings may
|
||||||
|
// or
|
||||||
|
// not be found depending on scheduling.
|
||||||
|
ExpectMappings(map, region_addr, kNumMappings, page_size_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiprocessChild() override {
|
||||||
|
ASSERT_EQ(fcntl(ReadPipeHandle(), F_SETFL, O_NONBLOCK), 0)
|
||||||
|
<< ErrnoMessage("fcntl");
|
||||||
|
|
||||||
|
ScopedMmap mappings;
|
||||||
|
ASSERT_NO_FATAL_FAILURE(
|
||||||
|
InitializeMappings(&mappings, kNumMappings, page_size_));
|
||||||
|
|
||||||
|
// Let the parent start mapping us
|
||||||
|
auto region_addr = mappings.addr_as<LinuxVMAddress>();
|
||||||
|
CheckedWriteFile(WritePipeHandle(), ®ion_addr, sizeof(region_addr));
|
||||||
|
|
||||||
|
// But don't stop there!
|
||||||
|
constexpr size_t kNumExtraMappings = 1024;
|
||||||
|
ScopedMmap extra_mappings;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
ASSERT_NO_FATAL_FAILURE(
|
||||||
|
InitializeMappings(&extra_mappings, kNumExtraMappings, page_size_));
|
||||||
|
|
||||||
|
// Quit when the parent is done
|
||||||
|
char c;
|
||||||
|
FileOperationResult res = ReadFile(ReadPipeHandle(), &c, sizeof(c));
|
||||||
|
if (res == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ASSERT_EQ(errno, EAGAIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr size_t kNumMappings = 4096;
|
||||||
|
const size_t page_size_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(MapRunningChildTest);
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(MemoryMap, MapRunningChild) {
|
||||||
|
MapRunningChildTest test;
|
||||||
|
test.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
@ -46,6 +46,8 @@
|
|||||||
'file/string_file.h',
|
'file/string_file.h',
|
||||||
'linux/address_types.h',
|
'linux/address_types.h',
|
||||||
'linux/checked_address_range.h',
|
'linux/checked_address_range.h',
|
||||||
|
'linux/memory_map.cc',
|
||||||
|
'linux/memory_map.h',
|
||||||
'linux/process_memory.cc',
|
'linux/process_memory.cc',
|
||||||
'linux/process_memory.h',
|
'linux/process_memory.h',
|
||||||
'linux/scoped_ptrace_attach.cc',
|
'linux/scoped_ptrace_attach.cc',
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
'file/file_io_test.cc',
|
'file/file_io_test.cc',
|
||||||
'file/file_reader_test.cc',
|
'file/file_reader_test.cc',
|
||||||
'file/string_file_test.cc',
|
'file/string_file_test.cc',
|
||||||
|
'linux/memory_map_test.cc',
|
||||||
'linux/process_memory_test.cc',
|
'linux/process_memory_test.cc',
|
||||||
'linux/scoped_ptrace_attach_test.cc',
|
'linux/scoped_ptrace_attach_test.cc',
|
||||||
'mac/launchd_test.mm',
|
'mac/launchd_test.mm',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user