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 <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "base/files/file_path.h"
|
2017-04-27 15:08:17 -04:00
|
|
|
#include "base/strings/stringprintf.h"
|
2017-07-11 08:59:24 -07:00
|
|
|
#include "build/build_config.h"
|
2017-04-25 08:12:49 -07:00
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "test/errors.h"
|
|
|
|
#include "test/file.h"
|
2018-04-03 15:01:44 -07:00
|
|
|
#include "test/linux/fake_ptrace_connection.h"
|
2017-04-25 08:12:49 -07:00
|
|
|
#include "test/multiprocess.h"
|
|
|
|
#include "test/scoped_temp_dir.h"
|
2019-12-05 12:36:26 -08:00
|
|
|
#include "third_party/lss/lss.h"
|
2017-04-25 08:12:49 -07:00
|
|
|
#include "util/file/file_io.h"
|
2019-12-05 12:36:26 -08:00
|
|
|
#include "util/file/scoped_remove_file.h"
|
2018-04-03 15:01:44 -07:00
|
|
|
#include "util/linux/direct_ptrace_connection.h"
|
2017-04-25 08:12:49 -07:00
|
|
|
#include "util/misc/clock.h"
|
2017-04-27 15:08:17 -04:00
|
|
|
#include "util/misc/from_pointer_cast.h"
|
2017-04-25 08:12:49 -07:00
|
|
|
#include "util/posix/scoped_mmap.h"
|
|
|
|
|
|
|
|
namespace crashpad {
|
|
|
|
namespace test {
|
|
|
|
namespace {
|
|
|
|
|
2019-12-05 12:36:26 -08:00
|
|
|
TEST(MemoryMap, SelfLargeFiles) {
|
|
|
|
// This test is meant to test the handler's ability to understand files
|
|
|
|
// mapped from large offsets, even if the handler wasn't built with
|
|
|
|
// _FILE_OFFSET_BITS=64. ScopedTempDir needs to stat files to determine
|
|
|
|
// whether to recurse into directories, which may will fail without large file
|
|
|
|
// support. ScopedRemoveFile doesn't have that restriction.
|
|
|
|
ScopedTempDir dir;
|
|
|
|
ScopedRemoveFile large_file_path(dir.path().Append("crashpad_test_file"));
|
|
|
|
ScopedFileHandle handle(
|
|
|
|
LoggingOpenFileForReadAndWrite(large_file_path.get(),
|
|
|
|
FileWriteMode::kCreateOrFail,
|
|
|
|
FilePermissions::kWorldReadable));
|
|
|
|
ASSERT_TRUE(handle.is_valid());
|
|
|
|
|
|
|
|
// sys_fallocate supports large files as long as the kernel supports them,
|
|
|
|
// regardless of _FILE_OFFSET_BITS.
|
|
|
|
off64_t off = 1llu + UINT32_MAX;
|
|
|
|
ASSERT_EQ(sys_fallocate(handle.get(), 0, off, getpagesize()), 0)
|
|
|
|
<< ErrnoMessage("fallocate");
|
|
|
|
|
|
|
|
ScopedMmap mapping;
|
|
|
|
void* addr = sys_mmap(
|
|
|
|
nullptr, getpagesize(), PROT_READ, MAP_SHARED, handle.get(), off);
|
|
|
|
ASSERT_TRUE(addr);
|
|
|
|
ASSERT_TRUE(mapping.ResetAddrLen(addr, getpagesize()));
|
|
|
|
|
|
|
|
FakePtraceConnection connection;
|
|
|
|
ASSERT_TRUE(connection.Initialize(getpid()));
|
|
|
|
MemoryMap map;
|
|
|
|
ASSERT_TRUE(map.Initialize(&connection));
|
|
|
|
}
|
|
|
|
|
2017-04-25 08:12:49 -07:00
|
|
|
TEST(MemoryMap, SelfBasic) {
|
|
|
|
ScopedMmap mmapping;
|
|
|
|
ASSERT_TRUE(mmapping.ResetMmap(nullptr,
|
|
|
|
getpagesize(),
|
|
|
|
PROT_EXEC | PROT_READ,
|
|
|
|
MAP_SHARED | MAP_ANON,
|
|
|
|
-1,
|
|
|
|
0));
|
2018-04-03 15:01:44 -07:00
|
|
|
|
|
|
|
FakePtraceConnection connection;
|
|
|
|
ASSERT_TRUE(connection.Initialize(getpid()));
|
|
|
|
|
2017-04-25 08:12:49 -07:00
|
|
|
MemoryMap map;
|
2018-04-03 15:01:44 -07:00
|
|
|
ASSERT_TRUE(map.Initialize(&connection));
|
2017-04-25 08:12:49 -07:00
|
|
|
|
2017-04-27 15:08:17 -04:00
|
|
|
auto stack_address = FromPointerCast<LinuxVMAddress>(&map);
|
2017-04-25 08:12:49 -07:00
|
|
|
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);
|
|
|
|
|
2017-04-27 15:08:17 -04:00
|
|
|
auto code_address = FromPointerCast<LinuxVMAddress>(getpid);
|
2017-04-25 08:12:49 -07:00
|
|
|
mapping = map.FindMapping(code_address);
|
|
|
|
ASSERT_TRUE(mapping);
|
|
|
|
EXPECT_GE(code_address, mapping->range.Base());
|
|
|
|
EXPECT_LT(code_address, mapping->range.End());
|
2019-12-13 10:23:12 -08:00
|
|
|
#if !defined(OS_ANDROID)
|
|
|
|
// Android Q+ supports execute only memory.
|
2017-04-25 08:12:49 -07:00
|
|
|
EXPECT_TRUE(mapping->readable);
|
2019-12-13 10:23:12 -08:00
|
|
|
#endif
|
2017-04-25 08:12:49 -07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-07-11 08:59:24 -07:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2017-04-25 08:12:49 -07:00
|
|
|
class MapChildTest : public Multiprocess {
|
|
|
|
public:
|
|
|
|
MapChildTest() : Multiprocess(), page_size_(getpagesize()) {}
|
2021-09-20 12:55:12 -07:00
|
|
|
|
|
|
|
MapChildTest(const MapChildTest&) = delete;
|
|
|
|
MapChildTest& operator=(const MapChildTest&) = delete;
|
|
|
|
|
2017-04-25 08:12:49 -07:00
|
|
|
~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);
|
|
|
|
|
2018-04-03 15:01:44 -07:00
|
|
|
DirectPtraceConnection connection;
|
|
|
|
ASSERT_TRUE(connection.Initialize(ChildPID()));
|
2017-05-02 10:07:21 -07:00
|
|
|
|
2017-04-25 08:12:49 -07:00
|
|
|
MemoryMap map;
|
2018-04-03 15:01:44 -07:00
|
|
|
ASSERT_TRUE(map.Initialize(&connection));
|
2017-04-25 08:12:49 -07:00
|
|
|
|
|
|
|
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());
|
2019-12-13 10:23:12 -08:00
|
|
|
#if !defined(OS_ANDROID)
|
|
|
|
// Android Q+ supports execute only memory.
|
2017-04-25 08:12:49 -07:00
|
|
|
EXPECT_TRUE(mapping->readable);
|
2019-12-13 10:23:12 -08:00
|
|
|
#endif
|
2017-04-25 08:12:49 -07:00
|
|
|
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);
|
2017-06-13 08:39:46 -07:00
|
|
|
EXPECT_EQ(map.FindMappingWithName(mapping->name), mapping);
|
2017-04-25 08:12:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void MultiprocessChild() override {
|
2017-04-27 15:08:17 -04:00
|
|
|
auto code_address = FromPointerCast<LinuxVMAddress>(getpid);
|
2017-04-25 08:12:49 -07:00
|
|
|
CheckedWriteFile(WritePipeHandle(), &code_address, sizeof(code_address));
|
|
|
|
|
2017-04-27 15:08:17 -04:00
|
|
|
auto stack_address = FromPointerCast<LinuxVMAddress>(&code_address);
|
2017-04-25 08:12:49 -07:00
|
|
|
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"));
|
2017-07-11 08:59:24 -07:00
|
|
|
ScopedFileHandle handle;
|
|
|
|
ASSERT_NO_FATAL_FAILURE(InitializeFile(path, page_size_ * 2, &handle));
|
2017-04-25 08:12:49 -07:00
|
|
|
|
|
|
|
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));
|
2017-07-11 08:59:24 -07:00
|
|
|
LinuxVMSize path_length = path.value().size();
|
2017-04-25 08:12:49 -07:00
|
|
|
CheckedWriteFile(WritePipeHandle(), &path_length, sizeof(path_length));
|
2017-07-11 08:59:24 -07:00
|
|
|
CheckedWriteFile(WritePipeHandle(), path.value().c_str(), path_length);
|
2017-04-25 08:12:49 -07:00
|
|
|
|
|
|
|
CheckedReadFileAtEOF(ReadPipeHandle());
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t page_size_;
|
|
|
|
};
|
|
|
|
|
|
|
|
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) {
|
2017-04-27 15:08:17 -04:00
|
|
|
SCOPED_TRACE(base::StringPrintf("index %zu", index));
|
|
|
|
|
2017-04-25 08:12:49 -07:00
|
|
|
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;
|
2017-04-25 08:12:49 -07:00
|
|
|
const size_t page_size = getpagesize();
|
|
|
|
ScopedMmap mappings;
|
|
|
|
|
|
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
|
|
InitializeMappings(&mappings, kNumMappings, page_size));
|
|
|
|
|
2018-04-03 15:01:44 -07:00
|
|
|
FakePtraceConnection connection;
|
|
|
|
ASSERT_TRUE(connection.Initialize(getpid()));
|
|
|
|
|
2017-04-25 08:12:49 -07:00
|
|
|
MemoryMap map;
|
2018-04-03 15:01:44 -07:00
|
|
|
ASSERT_TRUE(map.Initialize(&connection));
|
2017-04-25 08:12:49 -07:00
|
|
|
|
|
|
|
ExpectMappings(
|
|
|
|
map, mappings.addr_as<LinuxVMAddress>(), kNumMappings, page_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
class MapRunningChildTest : public Multiprocess {
|
|
|
|
public:
|
|
|
|
MapRunningChildTest() : Multiprocess(), page_size_(getpagesize()) {}
|
2021-09-20 12:55:12 -07:00
|
|
|
|
|
|
|
MapRunningChildTest(const MapRunningChildTest&) = delete;
|
|
|
|
MapRunningChildTest& operator=(const MapRunningChildTest&) = delete;
|
|
|
|
|
2017-04-25 08:12:49 -07:00
|
|
|
~MapRunningChildTest() {}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void MultiprocessParent() override {
|
|
|
|
// Let the child get started
|
|
|
|
LinuxVMAddress region_addr;
|
|
|
|
CheckedReadFileExactly(ReadPipeHandle(), ®ion_addr, sizeof(region_addr));
|
|
|
|
|
|
|
|
for (int iter = 0; iter < 8; ++iter) {
|
2017-04-27 15:08:17 -04:00
|
|
|
SCOPED_TRACE(base::StringPrintf("iter %d", iter));
|
|
|
|
|
|
|
|
// Let the child get back to its work
|
|
|
|
SleepNanoseconds(1000);
|
|
|
|
|
2018-04-03 15:01:44 -07:00
|
|
|
DirectPtraceConnection connection;
|
|
|
|
ASSERT_TRUE(connection.Initialize(ChildPID()));
|
2017-05-02 10:07:21 -07:00
|
|
|
|
2017-04-25 08:12:49 -07:00
|
|
|
MemoryMap map;
|
2018-04-03 15:01:44 -07:00
|
|
|
ASSERT_TRUE(map.Initialize(&connection));
|
2017-04-25 08:12:49 -07:00
|
|
|
|
|
|
|
// We should at least find the original mappings. The extra mappings may
|
2017-04-25 14:21:58 -04:00
|
|
|
// or not be found depending on scheduling.
|
2017-04-25 08:12:49 -07:00
|
|
|
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!
|
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;
|
2017-04-25 08:12:49 -07:00
|
|
|
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;
|
2017-04-25 08:12:49 -07:00
|
|
|
const size_t page_size_;
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST(MemoryMap, MapRunningChild) {
|
|
|
|
MapRunningChildTest test;
|
|
|
|
test.Run();
|
|
|
|
}
|
|
|
|
|
2017-07-11 08:59:24 -07:00
|
|
|
// Expects first and third pages from mapping_start to refer to the same mapped
|
|
|
|
// file. The second page should not.
|
2018-07-26 08:27:31 -07:00
|
|
|
void ExpectFindFilePossibleMmapStarts(LinuxVMAddress mapping_start,
|
|
|
|
LinuxVMSize page_size) {
|
2018-04-03 15:01:44 -07:00
|
|
|
FakePtraceConnection connection;
|
|
|
|
ASSERT_TRUE(connection.Initialize(getpid()));
|
|
|
|
|
2017-07-11 08:59:24 -07:00
|
|
|
MemoryMap map;
|
2018-04-03 15:01:44 -07:00
|
|
|
ASSERT_TRUE(map.Initialize(&connection));
|
2017-07-11 08:59:24 -07:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2018-12-17 14:27:46 -08:00
|
|
|
auto mappings = map.FindFilePossibleMmapStarts(*mapping1);
|
|
|
|
ASSERT_EQ(mappings->Count(), 1u);
|
|
|
|
EXPECT_EQ(mappings->Next(), mapping1);
|
2018-07-26 08:27:31 -07:00
|
|
|
|
|
|
|
mappings = map.FindFilePossibleMmapStarts(*mapping2);
|
2018-12-17 14:27:46 -08:00
|
|
|
ASSERT_EQ(mappings->Count(), 1u);
|
|
|
|
EXPECT_EQ(mappings->Next(), mapping2);
|
2018-07-26 08:27:31 -07:00
|
|
|
|
|
|
|
mappings = map.FindFilePossibleMmapStarts(*mapping3);
|
2018-09-20 10:13:58 -07:00
|
|
|
#if defined(OS_ANDROID)
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(mappings->Count(), 2u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#else
|
2018-12-17 14:27:46 -08:00
|
|
|
ASSERT_EQ(mappings->Count(), 1u);
|
|
|
|
EXPECT_EQ(mappings->Next(), mapping1);
|
2018-09-20 10:13:58 -07:00
|
|
|
#endif
|
2017-07-11 08:59:24 -07:00
|
|
|
}
|
|
|
|
|
2018-07-26 08:27:31 -07:00
|
|
|
TEST(MemoryMap, FindFilePossibleMmapStarts) {
|
2017-07-11 08:59:24 -07:00
|
|
|
const size_t page_size = getpagesize();
|
|
|
|
|
|
|
|
ScopedTempDir temp_dir;
|
2018-07-26 08:27:31 -07:00
|
|
|
base::FilePath path = temp_dir.path().Append(
|
|
|
|
FILE_PATH_LITERAL("FindFilePossibleMmapStartsTestFile"));
|
2017-07-11 08:59:24 -07:00
|
|
|
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
|
|
|
|
{
|
2018-04-03 15:01:44 -07:00
|
|
|
FakePtraceConnection connection;
|
|
|
|
ASSERT_TRUE(connection.Initialize(getpid()));
|
|
|
|
|
2017-07-11 08:59:24 -07:00
|
|
|
MemoryMap map;
|
2018-04-03 15:01:44 -07:00
|
|
|
ASSERT_TRUE(map.Initialize(&connection));
|
2017-07-11 08:59:24 -07:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2018-09-20 10:13:58 -07:00
|
|
|
#if defined(OS_ANDROID)
|
2018-12-17 14:27:46 -08:00
|
|
|
auto mappings = map.FindFilePossibleMmapStarts(*mapping1);
|
|
|
|
EXPECT_EQ(mappings->Count(), 1u);
|
|
|
|
EXPECT_EQ(mappings->Next(), mapping1);
|
|
|
|
EXPECT_EQ(mappings->Next(), nullptr);
|
2018-09-20 10:13:58 -07:00
|
|
|
|
|
|
|
mappings = map.FindFilePossibleMmapStarts(*mapping2);
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(mappings->Count(), 2u);
|
2018-09-20 10:13:58 -07:00
|
|
|
|
|
|
|
mappings = map.FindFilePossibleMmapStarts(*mapping3);
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(mappings->Count(), 3u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#else
|
2018-12-17 14:27:46 -08:00
|
|
|
auto mappings = map.FindFilePossibleMmapStarts(*mapping1);
|
|
|
|
ASSERT_EQ(mappings->Count(), 1u);
|
|
|
|
EXPECT_EQ(mappings->Next(), mapping1);
|
|
|
|
EXPECT_EQ(mappings->Next(), nullptr);
|
2018-07-26 08:27:31 -07:00
|
|
|
|
|
|
|
mappings = map.FindFilePossibleMmapStarts(*mapping2);
|
2018-12-17 14:27:46 -08:00
|
|
|
ASSERT_EQ(mappings->Count(), 1u);
|
|
|
|
EXPECT_EQ(mappings->Next(), mapping1);
|
2018-07-26 08:27:31 -07:00
|
|
|
|
|
|
|
mappings = map.FindFilePossibleMmapStarts(*mapping3);
|
2018-12-17 14:27:46 -08:00
|
|
|
ASSERT_EQ(mappings->Count(), 1u);
|
|
|
|
EXPECT_EQ(mappings->Next(), mapping1);
|
2018-09-20 10:13:58 -07:00
|
|
|
#endif
|
2017-07-11 08:59:24 -07:00
|
|
|
|
|
|
|
#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);
|
2018-12-17 14:27:46 -08:00
|
|
|
mappings = map.FindFilePossibleMmapStarts(bad_mapping);
|
|
|
|
EXPECT_EQ(mappings->Count(), 0u);
|
|
|
|
EXPECT_EQ(mappings->Next(), nullptr);
|
2017-07-11 08:59:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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));
|
2018-07-26 08:27:31 -07:00
|
|
|
ExpectFindFilePossibleMmapStarts(mapping_start, page_size);
|
2017-07-11 08:59:24 -07:00
|
|
|
|
|
|
|
// Map the second page to another file.
|
|
|
|
ScopedFileHandle handle2;
|
2018-07-26 08:27:31 -07:00
|
|
|
base::FilePath path2 = temp_dir.path().Append(
|
|
|
|
FILE_PATH_LITERAL("FindFilePossibleMmapStartsTestFile2"));
|
2017-07-11 08:59:24 -07:00
|
|
|
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);
|
2018-07-26 08:27:31 -07:00
|
|
|
ExpectFindFilePossibleMmapStarts(mapping_start, page_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(MemoryMap, FindFilePossibleMmapStarts_MultipleStarts) {
|
|
|
|
ScopedTempDir temp_dir;
|
|
|
|
base::FilePath path =
|
|
|
|
temp_dir.path().Append(FILE_PATH_LITERAL("MultipleStartsTestFile"));
|
|
|
|
const size_t page_size = getpagesize();
|
|
|
|
ScopedFileHandle handle;
|
|
|
|
ASSERT_NO_FATAL_FAILURE(InitializeFile(path, page_size * 2, &handle));
|
|
|
|
|
|
|
|
// Locate a sequence of pages to setup a test in.
|
|
|
|
char* seq_addr;
|
|
|
|
{
|
|
|
|
ScopedMmap whole_mapping;
|
|
|
|
ASSERT_TRUE(whole_mapping.ResetMmap(
|
|
|
|
nullptr, page_size * 8, PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0));
|
|
|
|
seq_addr = whole_mapping.addr_as<char*>();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Arrange file and anonymous mappings in the sequence.
|
|
|
|
ScopedMmap file_mapping0;
|
|
|
|
ASSERT_TRUE(file_mapping0.ResetMmap(seq_addr,
|
|
|
|
page_size,
|
|
|
|
PROT_READ,
|
|
|
|
MAP_PRIVATE | MAP_FIXED,
|
|
|
|
handle.get(),
|
|
|
|
page_size));
|
|
|
|
|
|
|
|
ScopedMmap file_mapping1;
|
|
|
|
ASSERT_TRUE(file_mapping1.ResetMmap(seq_addr + page_size,
|
|
|
|
page_size * 2,
|
|
|
|
PROT_READ,
|
|
|
|
MAP_PRIVATE | MAP_FIXED,
|
|
|
|
handle.get(),
|
|
|
|
0));
|
|
|
|
|
|
|
|
ScopedMmap file_mapping2;
|
|
|
|
ASSERT_TRUE(file_mapping2.ResetMmap(seq_addr + page_size * 3,
|
|
|
|
page_size,
|
|
|
|
PROT_READ,
|
|
|
|
MAP_PRIVATE | MAP_FIXED,
|
|
|
|
handle.get(),
|
|
|
|
0));
|
|
|
|
|
|
|
|
// Skip a page
|
|
|
|
|
|
|
|
ScopedMmap file_mapping3;
|
|
|
|
ASSERT_TRUE(file_mapping3.ResetMmap(seq_addr + page_size * 5,
|
|
|
|
page_size,
|
|
|
|
PROT_READ,
|
|
|
|
MAP_PRIVATE | MAP_FIXED,
|
|
|
|
handle.get(),
|
|
|
|
0));
|
|
|
|
|
|
|
|
ScopedMmap anon_mapping;
|
|
|
|
ASSERT_TRUE(anon_mapping.ResetMmap(seq_addr + page_size * 6,
|
|
|
|
page_size,
|
|
|
|
PROT_READ,
|
|
|
|
MAP_PRIVATE | MAP_ANON | MAP_FIXED,
|
|
|
|
-1,
|
|
|
|
0));
|
|
|
|
|
|
|
|
ScopedMmap file_mapping4;
|
|
|
|
ASSERT_TRUE(file_mapping4.ResetMmap(seq_addr + page_size * 7,
|
|
|
|
page_size,
|
|
|
|
PROT_READ,
|
|
|
|
MAP_PRIVATE | MAP_FIXED,
|
|
|
|
handle.get(),
|
|
|
|
0));
|
|
|
|
|
|
|
|
FakePtraceConnection connection;
|
|
|
|
ASSERT_TRUE(connection.Initialize(getpid()));
|
|
|
|
MemoryMap map;
|
|
|
|
ASSERT_TRUE(map.Initialize(&connection));
|
|
|
|
|
|
|
|
auto mapping = map.FindMapping(file_mapping0.addr_as<VMAddress>());
|
|
|
|
ASSERT_TRUE(mapping);
|
|
|
|
auto possible_starts = map.FindFilePossibleMmapStarts(*mapping);
|
2018-09-20 10:13:58 -07:00
|
|
|
#if defined(OS_ANDROID)
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(possible_starts->Count(), 1u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#else
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(possible_starts->Count(), 0u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#endif
|
2018-07-26 08:27:31 -07:00
|
|
|
|
|
|
|
mapping = map.FindMapping(file_mapping1.addr_as<VMAddress>());
|
|
|
|
ASSERT_TRUE(mapping);
|
|
|
|
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
|
2018-09-20 10:13:58 -07:00
|
|
|
#if defined(OS_ANDROID)
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(possible_starts->Count(), 2u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#else
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(possible_starts->Count(), 1u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#endif
|
2018-07-26 08:27:31 -07:00
|
|
|
|
|
|
|
mapping = map.FindMapping(file_mapping2.addr_as<VMAddress>());
|
|
|
|
ASSERT_TRUE(mapping);
|
|
|
|
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
|
2018-09-20 10:13:58 -07:00
|
|
|
#if defined(OS_ANDROID)
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(possible_starts->Count(), 3u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#else
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(possible_starts->Count(), 2u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#endif
|
2018-07-26 08:27:31 -07:00
|
|
|
|
|
|
|
mapping = map.FindMapping(file_mapping3.addr_as<VMAddress>());
|
|
|
|
ASSERT_TRUE(mapping);
|
|
|
|
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
|
2018-09-20 10:13:58 -07:00
|
|
|
#if defined(OS_ANDROID)
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(possible_starts->Count(), 4u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#else
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(possible_starts->Count(), 3u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#endif
|
2018-07-26 08:27:31 -07:00
|
|
|
|
|
|
|
mapping = map.FindMapping(file_mapping4.addr_as<VMAddress>());
|
|
|
|
ASSERT_TRUE(mapping);
|
|
|
|
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
|
2018-09-20 10:13:58 -07:00
|
|
|
#if defined(OS_ANDROID)
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(possible_starts->Count(), 5u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#else
|
2018-12-17 14:27:46 -08:00
|
|
|
EXPECT_EQ(possible_starts->Count(), 4u);
|
2018-09-20 10:13:58 -07:00
|
|
|
#endif
|
2017-07-11 08:59:24 -07:00
|
|
|
}
|
|
|
|
|
2017-04-25 08:12:49 -07:00
|
|
|
} // namespace
|
|
|
|
} // namespace test
|
|
|
|
} // namespace crashpad
|