crashpad/util/linux/memory_map_test.cc

343 lines
12 KiB
C++
Raw Normal View History

// 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 "base/strings/stringprintf.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/linux/scoped_ptrace_attach.h"
#include "util/misc/clock.h"
#include "util/misc/from_pointer_cast.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 = FromPointerCast<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 = FromPointerCast<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);
ScopedPtraceAttach attachment;
attachment.ResetAttach(ChildPID());
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);
EXPECT_EQ(map.FindMappingWithName(mapping->name), mapping);
}
void MultiprocessChild() override {
auto code_address = FromPointerCast<LinuxVMAddress>(getpid);
CheckedWriteFile(WritePipeHandle(), &code_address, sizeof(code_address));
auto stack_address = FromPointerCast<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::string file_contents(page_size_ * 2, std::string::value_type());
CheckedWriteFile(handle.get(), file_contents.c_str(), file_contents.size());
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) {
SCOPED_TRACE(base::StringPrintf("index %zu", 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) {
linux: Make fewer (but still a lot of) regions in MemoryMap’s test The lots-of-regions tests in the MemoryMap test case were very time-consuming, particularly in debug mode. MemoryMap.MapRunningChild took as long as 15 seconds on-device (Nexus 5X), and the best result was in the neighborhood of 7 seconds. The bulk of the time spent in these tests was in ExpectMappings(), which calls MemoryMap::FindMapping() in a loop to verify each region. Each call to FindMapping() traverses the MemoryMap (internally, currently just a std::vector<>) from the beginning. With the need to verify 4,096 regions, a single call to ExpectMappings() had to perform over 8,000,000 checks to find the regions it needed. In turn, ExpectMappings() is called once by the SelfLargeMapFile test, and eight times by MapRunningChild. By reducing the number of regions to 1,024, each call to ExpectMappings() needs to perform “only” fewer than 600,000 checks. After this change, MemoryMap.MapRunningChild completes in about a half a second on-device. https://crashpad.chromium.org/bug/181 is concerned with implementing a RangeMap to serve MemoryMap and other similar code. After that’s done, it, it should be feasible to raise the number of regions used for these tests again. Bug: crashpad:30, crashpad:181 Test: crashpad_util_test MemoryMap.SelfLargeMapFile:MemoryMap.MapRunningChild Change-Id: I8ff88dac72a63c97ac937304b578fbe3b4ebf316 Reviewed-on: https://chromium-review.googlesource.com/494128 Reviewed-by: Joshua Peraza <jperaza@chromium.org>
2017-05-02 16:50:14 -04:00
constexpr size_t kNumMappings = 1024;
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(), &region_addr, sizeof(region_addr));
for (int iter = 0; iter < 8; ++iter) {
SCOPED_TRACE(base::StringPrintf("iter %d", iter));
// Let the child get back to its work
SleepNanoseconds(1000);
ScopedPtraceAttach attachment;
attachment.ResetAttach(ChildPID());
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(), &region_addr, sizeof(region_addr));
// But don't stop there!
linux: Make fewer (but still a lot of) regions in MemoryMap’s test The lots-of-regions tests in the MemoryMap test case were very time-consuming, particularly in debug mode. MemoryMap.MapRunningChild took as long as 15 seconds on-device (Nexus 5X), and the best result was in the neighborhood of 7 seconds. The bulk of the time spent in these tests was in ExpectMappings(), which calls MemoryMap::FindMapping() in a loop to verify each region. Each call to FindMapping() traverses the MemoryMap (internally, currently just a std::vector<>) from the beginning. With the need to verify 4,096 regions, a single call to ExpectMappings() had to perform over 8,000,000 checks to find the regions it needed. In turn, ExpectMappings() is called once by the SelfLargeMapFile test, and eight times by MapRunningChild. By reducing the number of regions to 1,024, each call to ExpectMappings() needs to perform “only” fewer than 600,000 checks. After this change, MemoryMap.MapRunningChild completes in about a half a second on-device. https://crashpad.chromium.org/bug/181 is concerned with implementing a RangeMap to serve MemoryMap and other similar code. After that’s done, it, it should be feasible to raise the number of regions used for these tests again. Bug: crashpad:30, crashpad:181 Test: crashpad_util_test MemoryMap.SelfLargeMapFile:MemoryMap.MapRunningChild Change-Id: I8ff88dac72a63c97ac937304b578fbe3b4ebf316 Reviewed-on: https://chromium-review.googlesource.com/494128 Reviewed-by: Joshua Peraza <jperaza@chromium.org>
2017-05-02 16:50:14 -04:00
constexpr size_t kNumExtraMappings = 256;
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);
}
}
linux: Make fewer (but still a lot of) regions in MemoryMap’s test The lots-of-regions tests in the MemoryMap test case were very time-consuming, particularly in debug mode. MemoryMap.MapRunningChild took as long as 15 seconds on-device (Nexus 5X), and the best result was in the neighborhood of 7 seconds. The bulk of the time spent in these tests was in ExpectMappings(), which calls MemoryMap::FindMapping() in a loop to verify each region. Each call to FindMapping() traverses the MemoryMap (internally, currently just a std::vector<>) from the beginning. With the need to verify 4,096 regions, a single call to ExpectMappings() had to perform over 8,000,000 checks to find the regions it needed. In turn, ExpectMappings() is called once by the SelfLargeMapFile test, and eight times by MapRunningChild. By reducing the number of regions to 1,024, each call to ExpectMappings() needs to perform “only” fewer than 600,000 checks. After this change, MemoryMap.MapRunningChild completes in about a half a second on-device. https://crashpad.chromium.org/bug/181 is concerned with implementing a RangeMap to serve MemoryMap and other similar code. After that’s done, it, it should be feasible to raise the number of regions used for these tests again. Bug: crashpad:30, crashpad:181 Test: crashpad_util_test MemoryMap.SelfLargeMapFile:MemoryMap.MapRunningChild Change-Id: I8ff88dac72a63c97ac937304b578fbe3b4ebf316 Reviewed-on: https://chromium-review.googlesource.com/494128 Reviewed-by: Joshua Peraza <jperaza@chromium.org>
2017-05-02 16:50:14 -04:00
static constexpr size_t kNumMappings = 1024;
const size_t page_size_;
DISALLOW_COPY_AND_ASSIGN(MapRunningChildTest);
};
TEST(MemoryMap, MapRunningChild) {
MapRunningChildTest test;
test.Run();
}
} // namespace
} // namespace test
} // namespace crashpad