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>
This commit is contained in:
Joshua Peraza 2017-07-11 08:59:24 -07:00 committed by Commit Bot
parent 4224be41d7
commit a79791969d
4 changed files with 190 additions and 10 deletions

View File

@ -44,7 +44,10 @@ void LocateExecutable(pid_t pid, bool is_64_bit, LinuxVMAddress* elf_address) {
MemoryMap memory_map;
ASSERT_TRUE(memory_map.Initialize(pid));
const MemoryMap::Mapping* exe_mapping = memory_map.FindMapping(phdrs);
const MemoryMap::Mapping* phdr_mapping = memory_map.FindMapping(phdrs);
ASSERT_TRUE(phdr_mapping);
const MemoryMap::Mapping* exe_mapping =
memory_map.FindFileMmapStart(*phdr_mapping);
ASSERT_TRUE(exe_mapping);
*elf_address = exe_mapping->range.Base();
}

View File

@ -202,6 +202,16 @@ 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_);
@ -267,4 +277,40 @@ const MemoryMap::Mapping* MemoryMap::FindMappingWithName(
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

View File

@ -37,6 +37,7 @@ class MemoryMap {
//! \brief Information about a mapped region of memory.
struct Mapping {
Mapping();
bool Equals(const Mapping& other) const;
std::string name;
CheckedLinuxAddressRange range;
@ -74,6 +75,21 @@ class MemoryMap {
//! it was obtained from.
const Mapping* FindMappingWithName(const std::string& name) const;
//! \brief Find the first Mapping in a series of mappings for the same file.
//!
//! Executables and libaries are typically loaded into several mappings with
//! varying permissions for different segments. This method searches for the
//! mapping with the highest address at or below \a mapping, which maps the
//! same file as \a mapping from file offset 0.
//!
//! If \a mapping is not found, `nullptr` is returned. If \a mapping is found
//! but does not map a file, \a mapping is returned.
//!
//! \param[in] mapping A Mapping whose series to find the start of.
//! \return The first Mapping in the series or `nullptr` on failure with a
//! message logged.
const Mapping* FindFileMmapStart(const Mapping& mapping) const;
private:
std::vector<Mapping> mappings_;
InitializationStateDcheck initialized_;

View File

@ -22,6 +22,7 @@
#include "base/files/file_path.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/errors.h"
#include "test/file.h"
@ -78,6 +79,18 @@ TEST(MemoryMap, SelfBasic) {
EXPECT_TRUE(mapping->shareable);
}
void InitializeFile(const base::FilePath& path,
size_t size,
ScopedFileHandle* handle) {
ASSERT_FALSE(FileExists(path));
handle->reset(LoggingOpenFileForReadAndWrite(
path, FileWriteMode::kReuseOrCreate, FilePermissions::kOwnerOnly));
ASSERT_TRUE(handle->is_valid());
std::string file_contents(size, std::string::value_type());
CheckedWriteFile(handle->get(), file_contents.c_str(), file_contents.size());
}
class MapChildTest : public Multiprocess {
public:
MapChildTest() : Multiprocess(), page_size_(getpagesize()) {}
@ -169,13 +182,8 @@ class MapChildTest : public Multiprocess {
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::string file_contents(page_size_ * 2, std::string::value_type());
CheckedWriteFile(handle.get(), file_contents.c_str(), file_contents.size());
ScopedFileHandle handle;
ASSERT_NO_FATAL_FAILURE(InitializeFile(path, page_size_ * 2, &handle));
ScopedMmap file_mapping;
ASSERT_TRUE(file_mapping.ResetMmap(nullptr,
@ -188,9 +196,9 @@ class MapChildTest : public Multiprocess {
auto mapped_file_address = file_mapping.addr_as<LinuxVMAddress>();
CheckedWriteFile(
WritePipeHandle(), &mapped_file_address, sizeof(mapped_file_address));
LinuxVMSize path_length = path_string.size();
LinuxVMSize path_length = path.value().size();
CheckedWriteFile(WritePipeHandle(), &path_length, sizeof(path_length));
CheckedWriteFile(WritePipeHandle(), path_string.c_str(), path_length);
CheckedWriteFile(WritePipeHandle(), path.value().c_str(), path_length);
CheckedReadFileAtEOF(ReadPipeHandle());
}
@ -337,6 +345,113 @@ TEST(MemoryMap, MapRunningChild) {
test.Run();
}
// Expects first and third pages from mapping_start to refer to the same mapped
// file. The second page should not.
void ExpectFindFileMmapStart(LinuxVMAddress mapping_start,
LinuxVMSize page_size) {
MemoryMap map;
ASSERT_TRUE(map.Initialize(getpid()));
auto mapping1 = map.FindMapping(mapping_start);
ASSERT_TRUE(mapping1);
auto mapping2 = map.FindMapping(mapping_start + page_size);
ASSERT_TRUE(mapping2);
auto mapping3 = map.FindMapping(mapping_start + page_size * 2);
ASSERT_TRUE(mapping3);
ASSERT_NE(mapping1, mapping2);
ASSERT_NE(mapping2, mapping3);
EXPECT_EQ(map.FindFileMmapStart(*mapping1), mapping1);
EXPECT_EQ(map.FindFileMmapStart(*mapping2), mapping2);
EXPECT_EQ(map.FindFileMmapStart(*mapping3), mapping1);
}
TEST(MemoryMap, FindFileMmapStart) {
const size_t page_size = getpagesize();
ScopedTempDir temp_dir;
base::FilePath path =
temp_dir.path().Append(FILE_PATH_LITERAL("FindFileMmapStartTestFile"));
ScopedFileHandle handle;
size_t file_length = page_size * 3;
ASSERT_NO_FATAL_FAILURE(InitializeFile(path, file_length, &handle));
ScopedMmap file_mapping;
ASSERT_TRUE(file_mapping.ResetMmap(
nullptr, file_length, PROT_READ, MAP_PRIVATE, handle.get(), 0));
auto mapping_start = file_mapping.addr_as<LinuxVMAddress>();
// Change the permissions on the second page to split the mapping into three
// parts.
ASSERT_EQ(mprotect(file_mapping.addr_as<char*>() + page_size,
page_size,
PROT_READ | PROT_WRITE),
0);
// Basic
{
MemoryMap map;
ASSERT_TRUE(map.Initialize(getpid()));
auto mapping1 = map.FindMapping(mapping_start);
ASSERT_TRUE(mapping1);
auto mapping2 = map.FindMapping(mapping_start + page_size);
ASSERT_TRUE(mapping2);
auto mapping3 = map.FindMapping(mapping_start + page_size * 2);
ASSERT_TRUE(mapping3);
ASSERT_NE(mapping1, mapping2);
ASSERT_NE(mapping2, mapping3);
EXPECT_EQ(map.FindFileMmapStart(*mapping1), mapping1);
EXPECT_EQ(map.FindFileMmapStart(*mapping2), mapping1);
EXPECT_EQ(map.FindFileMmapStart(*mapping3), mapping1);
#if defined(ARCH_CPU_64_BITS)
constexpr bool is_64_bit = true;
#else
constexpr bool is_64_bit = false;
#endif
MemoryMap::Mapping bad_mapping;
bad_mapping.range.SetRange(is_64_bit, 0, 1);
EXPECT_EQ(map.FindFileMmapStart(bad_mapping), nullptr);
}
// Make the second page an anonymous mapping
file_mapping.ResetAddrLen(file_mapping.addr_as<void*>(), page_size);
ScopedMmap page2_mapping;
ASSERT_TRUE(page2_mapping.ResetMmap(file_mapping.addr_as<char*>() + page_size,
page_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED,
-1,
0));
ScopedMmap page3_mapping;
ASSERT_TRUE(
page3_mapping.ResetMmap(file_mapping.addr_as<char*>() + page_size * 2,
page_size,
PROT_READ,
MAP_PRIVATE | MAP_FIXED,
handle.get(),
page_size * 2));
ExpectFindFileMmapStart(mapping_start, page_size);
// Map the second page to another file.
ScopedFileHandle handle2;
base::FilePath path2 =
temp_dir.path().Append(FILE_PATH_LITERAL("FindFileMmapStartTestFile2"));
ASSERT_NO_FATAL_FAILURE(InitializeFile(path2, page_size, &handle2));
page2_mapping.ResetMmap(file_mapping.addr_as<char*>() + page_size,
page_size,
PROT_READ,
MAP_PRIVATE | MAP_FIXED,
handle2.get(),
0);
ExpectFindFileMmapStart(mapping_start, page_size);
}
} // namespace
} // namespace test
} // namespace crashpad