mirror of
https://github.com/chromium/crashpad.git
synced 2025-01-14 01:08:01 +08:00
Add ProcessMemoryWin and re-factor tests
Currently, ProcessMemory is only implemented for Linux and Fuchsia. Implement the interface for Windows as well and re-factor tests to support it, mostly this consists of using a new ScopedGuardedPage class instead of ScopedMmap in the ProcessMemory tests. BUG=crashpad:262 Change-Id: I1b42718972be5ad838d12356d09f764053f09e4f Reviewed-on: https://chromium-review.googlesource.com/c/1278829 Commit-Queue: Vlad Tsyrklevich <vtsyrklevich@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
parent
131dd81d4c
commit
a9be1b1403
@ -36,6 +36,7 @@ static_library("test") {
|
||||
"multiprocess_exec.h",
|
||||
"process_type.cc",
|
||||
"process_type.h",
|
||||
"scoped_guarded_page.h",
|
||||
"scoped_module_handle.cc",
|
||||
"scoped_module_handle.h",
|
||||
"scoped_temp_dir.cc",
|
||||
@ -45,7 +46,10 @@ static_library("test") {
|
||||
]
|
||||
|
||||
if (crashpad_is_posix || crashpad_is_fuchsia) {
|
||||
sources += [ "scoped_temp_dir_posix.cc" ]
|
||||
sources += [
|
||||
"scoped_guarded_page_posix.cc",
|
||||
"scoped_temp_dir_posix.cc",
|
||||
]
|
||||
|
||||
if (!crashpad_is_fuchsia) {
|
||||
sources += [
|
||||
@ -81,6 +85,7 @@ static_library("test") {
|
||||
if (crashpad_is_win) {
|
||||
sources += [
|
||||
"multiprocess_exec_win.cc",
|
||||
"scoped_guarded_page_win.cc",
|
||||
"scoped_temp_dir_win.cc",
|
||||
"win/child_launcher.cc",
|
||||
"win/child_launcher.h",
|
||||
@ -144,6 +149,7 @@ source_set("test_test") {
|
||||
"hex_string_test.cc",
|
||||
"main_arguments_test.cc",
|
||||
"multiprocess_exec_test.cc",
|
||||
"scoped_guarded_page_test.cc",
|
||||
"scoped_temp_dir_test.cc",
|
||||
"test_paths_test.cc",
|
||||
]
|
||||
|
49
test/scoped_guarded_page.h
Normal file
49
test/scoped_guarded_page.h
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2018 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_TEST_SCOPED_GUARDED_PAGE_
|
||||
#define CRASHPAD_TEST_SCOPED_GUARDED_PAGE_
|
||||
|
||||
#include "base/macros.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
||||
//! \brief A RAII object that allocates a read-write page with an inacessible
|
||||
//! page following it.
|
||||
//!
|
||||
//! Upon construction, a mapping will be created. Failure to create the mapping
|
||||
//! is fatal. On destruction, the mapping is freed.
|
||||
//!
|
||||
//! This object should not be used in multi-threded contexts, the POSIX
|
||||
//! implementation can not be made thread-safe.
|
||||
class ScopedGuardedPage {
|
||||
public:
|
||||
ScopedGuardedPage();
|
||||
~ScopedGuardedPage();
|
||||
|
||||
//! \brief Returns the address of the read-write page.
|
||||
//!
|
||||
//! \return The address of the read-write page.
|
||||
void* Pointer() const { return ptr_; }
|
||||
|
||||
private:
|
||||
void* ptr_;
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedGuardedPage);
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_TEST_SCOPED_GUARDED_PAGE_
|
47
test/scoped_guarded_page_posix.cc
Normal file
47
test/scoped_guarded_page_posix.cc
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2018 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 "test/scoped_guarded_page.h"
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/process/process_metrics.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
||||
ScopedGuardedPage::ScopedGuardedPage() {
|
||||
ptr_ = mmap(nullptr,
|
||||
base::GetPageSize() * 2,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
-1,
|
||||
0);
|
||||
PCHECK(ptr_ != MAP_FAILED) << "mmap";
|
||||
|
||||
// Simply mprotect()ing the guard page PROT_NONE does not make it inaccessible
|
||||
// using ptrace() or /proc/$pid/mem so we munmap() the following page instead.
|
||||
// Unfortunately, this means that the guarded page is not thread safe from
|
||||
// other threads mapping a single page into the empty region.
|
||||
char* second_page = static_cast<char*>(ptr_) + base::GetPageSize();
|
||||
PCHECK(munmap(second_page, base::GetPageSize()) >= 0) << "munmap";
|
||||
}
|
||||
|
||||
ScopedGuardedPage::~ScopedGuardedPage() {
|
||||
PCHECK(munmap(ptr_, base::GetPageSize()) >= 0) << "munmap";
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
35
test/scoped_guarded_page_test.cc
Normal file
35
test/scoped_guarded_page_test.cc
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2018 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 "test/scoped_guarded_page.h"
|
||||
|
||||
#include "base/process/process_metrics.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
TEST(ScopedGuardedPage, BasicFunctionality) {
|
||||
ScopedGuardedPage page;
|
||||
char* address = (char*)page.Pointer();
|
||||
EXPECT_NE(address, nullptr);
|
||||
address[0] = 0;
|
||||
address[base::GetPageSize() - 1] = 0;
|
||||
EXPECT_DEATH({ address[base::GetPageSize()] = 0; }, "");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
39
test/scoped_guarded_page_win.cc
Normal file
39
test/scoped_guarded_page_win.cc
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2018 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 "test/scoped_guarded_page.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/process/process_metrics.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
||||
ScopedGuardedPage::ScopedGuardedPage() {
|
||||
const size_t page_size = base::GetPageSize();
|
||||
ptr_ = VirtualAlloc(nullptr, page_size * 2, MEM_RESERVE, PAGE_NOACCESS);
|
||||
PCHECK(ptr_ != nullptr) << "VirtualAlloc";
|
||||
|
||||
PCHECK(VirtualAlloc(ptr_, page_size, MEM_COMMIT, PAGE_READWRITE) != nullptr)
|
||||
<< "VirtualAlloc";
|
||||
}
|
||||
|
||||
ScopedGuardedPage::~ScopedGuardedPage() {
|
||||
PCHECK(VirtualFree(ptr_, 0, MEM_RELEASE)) << "VirtualFree";
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
@ -311,7 +311,8 @@ static_library("util") {
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) {
|
||||
if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia ||
|
||||
crashpad_is_win) {
|
||||
sources += [
|
||||
"process/process_memory.cc",
|
||||
"process/process_memory.h",
|
||||
@ -332,6 +333,8 @@ static_library("util") {
|
||||
"misc/paths_win.cc",
|
||||
"misc/time_win.cc",
|
||||
"net/http_transport_win.cc",
|
||||
"process/process_memory_win.cc",
|
||||
"process/process_memory_win.h",
|
||||
"synchronization/semaphore_win.cc",
|
||||
"thread/thread_win.cc",
|
||||
"win/address_types.h",
|
||||
@ -622,7 +625,8 @@ source_set("util_test") {
|
||||
sources += [ "misc/capture_context_test_util_fuchsia.cc" ]
|
||||
}
|
||||
|
||||
if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) {
|
||||
if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia ||
|
||||
crashpad_is_win) {
|
||||
sources += [
|
||||
# TODO: Port to all platforms.
|
||||
"process/process_memory_range_test.cc",
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
#include "util/process/process_memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
@ -19,8 +19,14 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "util/misc/address_types.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include <basetsd.h>
|
||||
typedef SSIZE_T ssize_t;
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Abstract base class for accessing the memory of another process.
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include "util/process/process_memory_fuchsia.h"
|
||||
#elif defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
#include "util/process/process_memory_linux.h"
|
||||
#elif defined(OS_WIN)
|
||||
#include "util/process/process_memory_win.h"
|
||||
#endif
|
||||
|
||||
namespace crashpad {
|
||||
@ -27,6 +29,8 @@ namespace crashpad {
|
||||
using ProcessMemoryNative = ProcessMemoryFuchsia;
|
||||
#elif defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
using ProcessMemoryNative = ProcessMemoryLinux;
|
||||
#elif defined(OS_WIN)
|
||||
using ProcessMemoryNative = ProcessMemoryWin;
|
||||
#else
|
||||
#error Port.
|
||||
#endif
|
||||
|
@ -86,7 +86,7 @@ bool ProcessMemoryRange::ReadCStringSizeLimited(VMAddress address,
|
||||
LOG(ERROR) << "read out of range";
|
||||
return false;
|
||||
}
|
||||
size = std::min(static_cast<VMSize>(size), range_.End() - address);
|
||||
size = std::min(size, base::checked_cast<size_t>(range_.End() - address));
|
||||
return memory_->ReadCStringSizeLimited(address, size, string);
|
||||
}
|
||||
|
||||
|
@ -14,8 +14,6 @@
|
||||
|
||||
#include "util/process/process_memory_range.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/logging.h"
|
||||
@ -27,7 +25,11 @@
|
||||
#include <lib/zx/process.h>
|
||||
|
||||
#include "util/process/process_memory_fuchsia.h"
|
||||
#elif defined(OS_WIN)
|
||||
#include "util/process/process_memory_win.h"
|
||||
#else
|
||||
#include <unistd.h>
|
||||
|
||||
#include "util/process/process_memory_linux.h"
|
||||
#endif
|
||||
|
||||
@ -41,20 +43,21 @@ struct TestObject {
|
||||
} kTestObject = {"string1", "string2"};
|
||||
|
||||
TEST(ProcessMemoryRange, Basic) {
|
||||
#if defined(OS_FUCHSIA)
|
||||
ProcessMemoryFuchsia memory;
|
||||
ASSERT_TRUE(memory.Initialize(*zx::process::self()));
|
||||
constexpr bool is_64_bit = true;
|
||||
#else
|
||||
pid_t pid = getpid();
|
||||
#if defined(ARCH_CPU_64_BITS)
|
||||
constexpr bool is_64_bit = true;
|
||||
#else
|
||||
constexpr bool is_64_bit = false;
|
||||
#endif // ARCH_CPU_64_BITS
|
||||
|
||||
#if defined(OS_FUCHSIA)
|
||||
ProcessMemoryFuchsia memory;
|
||||
ASSERT_TRUE(memory.Initialize(*zx::process::self()));
|
||||
#elif defined(OS_WIN)
|
||||
ProcessMemoryWin memory;
|
||||
ASSERT_TRUE(memory.Initialize(GetCurrentProcess()));
|
||||
#elif defined(OS_LINUX)
|
||||
ProcessMemoryLinux memory;
|
||||
ASSERT_TRUE(memory.Initialize(pid));
|
||||
ASSERT_TRUE(memory.Initialize(getpid()));
|
||||
#endif // OS_FUCHSIA
|
||||
|
||||
ProcessMemoryRange range;
|
||||
|
@ -15,19 +15,18 @@
|
||||
#include "util/process/process_memory.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/process/process_metrics.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/errors.h"
|
||||
#include "test/multiprocess.h"
|
||||
#include "test/multiprocess_exec.h"
|
||||
#include "test/process_type.h"
|
||||
#include "test/scoped_guarded_page.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/misc/from_pointer_cast.h"
|
||||
#include "util/posix/scoped_mmap.h"
|
||||
#include "util/process/process_memory_native.h"
|
||||
|
||||
namespace crashpad {
|
||||
@ -36,7 +35,7 @@ namespace {
|
||||
|
||||
void DoChildReadTestSetup(size_t* region_size,
|
||||
std::unique_ptr<char[]>* region) {
|
||||
*region_size = 4 * getpagesize();
|
||||
*region_size = 4 * base::GetPageSize();
|
||||
region->reset(new char[*region_size]);
|
||||
for (size_t index = 0; index < *region_size; ++index) {
|
||||
(*region)[index] = index % 256;
|
||||
@ -120,7 +119,7 @@ class ReadTest : public MultiprocessExec {
|
||||
}
|
||||
|
||||
// Ensure that a read of exactly one page works.
|
||||
size_t page_size = getpagesize();
|
||||
size_t page_size = base::GetPageSize();
|
||||
ASSERT_GE(region_size, page_size + page_size);
|
||||
ASSERT_TRUE(memory.Read(address + page_size, page_size, result.get()));
|
||||
for (size_t i = 0; i < page_size; ++i) {
|
||||
@ -154,7 +153,7 @@ constexpr char kConstCharShort[] = "A short const char[]";
|
||||
|
||||
std::string MakeLongString() {
|
||||
std::string long_string;
|
||||
const size_t kStringLongSize = 4 * getpagesize();
|
||||
const size_t kStringLongSize = 4 * base::GetPageSize();
|
||||
for (size_t index = 0; index < kStringLongSize; ++index) {
|
||||
long_string.push_back((index % 255) + 1);
|
||||
}
|
||||
@ -322,101 +321,60 @@ TEST(ProcessMemory, ReadCStringSizeLimitedChild) {
|
||||
test.RunAgainstChild();
|
||||
}
|
||||
|
||||
void DoReadUnmappedChildMainSetup(ScopedMmap* pages,
|
||||
VMAddress* address,
|
||||
size_t* page_size,
|
||||
size_t* region_size) {
|
||||
*page_size = getpagesize();
|
||||
*region_size = 2 * (*page_size);
|
||||
if (!pages->ResetMmap(nullptr,
|
||||
*region_size,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
-1,
|
||||
0)) {
|
||||
ADD_FAILURE();
|
||||
return;
|
||||
}
|
||||
|
||||
*address = pages->addr_as<VMAddress>();
|
||||
|
||||
char* region = pages->addr_as<char*>();
|
||||
for (size_t index = 0; index < *region_size; ++index) {
|
||||
void DoReadUnmappedChildMainSetup(void* page) {
|
||||
char* region = reinterpret_cast<char*>(page);
|
||||
for (size_t index = 0; index < base::GetPageSize(); ++index) {
|
||||
region[index] = index % 256;
|
||||
}
|
||||
|
||||
EXPECT_TRUE(pages->ResetAddrLen(region, *page_size));
|
||||
}
|
||||
|
||||
CRASHPAD_CHILD_TEST_MAIN(ReadUnmappedChildMain) {
|
||||
ScopedMmap pages;
|
||||
VMAddress address = 0;
|
||||
size_t page_size, region_size;
|
||||
DoReadUnmappedChildMainSetup(&pages, &address, &page_size, ®ion_size);
|
||||
ScopedGuardedPage pages;
|
||||
VMAddress address = reinterpret_cast<VMAddress>(pages.Pointer());
|
||||
DoReadUnmappedChildMainSetup(pages.Pointer());
|
||||
FileHandle out = StdioFileHandle(StdioStream::kStandardOutput);
|
||||
CheckedWriteFile(out, &address, sizeof(address));
|
||||
CheckedWriteFile(out, &page_size, sizeof(page_size));
|
||||
CheckedWriteFile(out, ®ion_size, sizeof(region_size));
|
||||
CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This test only supports running against a child process because
|
||||
// ScopedGuardedPage is not thread-safe.
|
||||
class ReadUnmappedTest : public MultiprocessExec {
|
||||
public:
|
||||
ReadUnmappedTest() : MultiprocessExec() {
|
||||
SetChildTestMainFunction("ReadUnmappedChildMain");
|
||||
}
|
||||
|
||||
void RunAgainstSelf() {
|
||||
ScopedMmap pages;
|
||||
VMAddress address = 0;
|
||||
size_t page_size, region_size;
|
||||
DoReadUnmappedChildMainSetup(&pages, &address, &page_size, ®ion_size);
|
||||
DoTest(GetSelfProcess(), address, page_size, region_size);
|
||||
}
|
||||
|
||||
void RunAgainstChild() { Run(); }
|
||||
|
||||
private:
|
||||
void MultiprocessParent() override {
|
||||
VMAddress address = 0;
|
||||
size_t page_size, region_size;
|
||||
ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &address, sizeof(address)));
|
||||
ASSERT_TRUE(
|
||||
ReadFileExactly(ReadPipeHandle(), &page_size, sizeof(page_size)));
|
||||
ASSERT_TRUE(
|
||||
ReadFileExactly(ReadPipeHandle(), ®ion_size, sizeof(region_size)));
|
||||
DoTest(ChildProcess(), address, page_size, region_size);
|
||||
DoTest(ChildProcess(), address);
|
||||
}
|
||||
|
||||
void DoTest(ProcessType process,
|
||||
VMAddress address,
|
||||
size_t page_size,
|
||||
size_t region_size) {
|
||||
void DoTest(ProcessType process, VMAddress address) {
|
||||
ProcessMemoryNative memory;
|
||||
ASSERT_TRUE(memory.Initialize(process));
|
||||
|
||||
VMAddress page_addr1 = address;
|
||||
VMAddress page_addr2 = page_addr1 + page_size;
|
||||
VMAddress page_addr2 = page_addr1 + base::GetPageSize();
|
||||
|
||||
std::unique_ptr<char[]> result(new char[region_size]);
|
||||
EXPECT_TRUE(memory.Read(page_addr1, page_size, result.get()));
|
||||
std::unique_ptr<char[]> result(new char[base::GetPageSize() * 2]);
|
||||
EXPECT_TRUE(memory.Read(page_addr1, base::GetPageSize(), result.get()));
|
||||
EXPECT_TRUE(memory.Read(page_addr2 - 1, 1, result.get()));
|
||||
|
||||
EXPECT_FALSE(memory.Read(page_addr1, region_size, result.get()));
|
||||
EXPECT_FALSE(memory.Read(page_addr2, page_size, result.get()));
|
||||
EXPECT_FALSE(
|
||||
memory.Read(page_addr1, base::GetPageSize() * 2, result.get()));
|
||||
EXPECT_FALSE(memory.Read(page_addr2, base::GetPageSize(), result.get()));
|
||||
EXPECT_FALSE(memory.Read(page_addr2 - 1, 2, result.get()));
|
||||
}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ReadUnmappedTest);
|
||||
};
|
||||
|
||||
TEST(ProcessMemory, ReadUnmappedSelf) {
|
||||
ReadUnmappedTest test;
|
||||
ASSERT_FALSE(testing::Test::HasFailure());
|
||||
test.RunAgainstSelf();
|
||||
}
|
||||
|
||||
TEST(ProcessMemory, ReadUnmappedChild) {
|
||||
ReadUnmappedTest test;
|
||||
ASSERT_FALSE(testing::Test::HasFailure());
|
||||
@ -428,9 +386,13 @@ constexpr size_t kChildProcessStringLength = 10;
|
||||
class StringDataInChildProcess {
|
||||
public:
|
||||
// This constructor only makes sense in the child process.
|
||||
explicit StringDataInChildProcess(const char* cstring)
|
||||
explicit StringDataInChildProcess(const char* cstring, bool valid)
|
||||
: address_(FromPointerCast<VMAddress>(cstring)) {
|
||||
memcpy(expected_value_, cstring, kChildProcessStringLength + 1);
|
||||
if (valid) {
|
||||
memcpy(expected_value_, cstring, kChildProcessStringLength + 1);
|
||||
} else {
|
||||
memset(expected_value_, 0xff, kChildProcessStringLength + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Write(FileHandle out) {
|
||||
@ -457,22 +419,10 @@ class StringDataInChildProcess {
|
||||
};
|
||||
|
||||
void DoCStringUnmappedTestSetup(
|
||||
ScopedMmap* pages,
|
||||
void* page,
|
||||
std::vector<StringDataInChildProcess>* strings) {
|
||||
const size_t page_size = getpagesize();
|
||||
const size_t region_size = 2 * page_size;
|
||||
if (!pages->ResetMmap(nullptr,
|
||||
region_size,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
-1,
|
||||
0)) {
|
||||
ADD_FAILURE();
|
||||
return;
|
||||
}
|
||||
|
||||
char* region = pages->addr_as<char*>();
|
||||
for (size_t index = 0; index < region_size; ++index) {
|
||||
char* region = reinterpret_cast<char*>(page);
|
||||
for (size_t index = 0; index < base::GetPageSize(); ++index) {
|
||||
region[index] = 1 + index % 255;
|
||||
}
|
||||
|
||||
@ -481,29 +431,25 @@ void DoCStringUnmappedTestSetup(
|
||||
string1[kChildProcessStringLength] = '\0';
|
||||
|
||||
// A string near the end of the mapped region
|
||||
char* string2 = region + page_size - kChildProcessStringLength * 2;
|
||||
char* string2 = region + base::GetPageSize() - kChildProcessStringLength * 2;
|
||||
string2[kChildProcessStringLength] = '\0';
|
||||
|
||||
// A string that crosses from the mapped into the unmapped region
|
||||
char* string3 = region + page_size - kChildProcessStringLength + 1;
|
||||
string3[kChildProcessStringLength] = '\0';
|
||||
char* string3 = region + base::GetPageSize() - kChildProcessStringLength + 1;
|
||||
|
||||
// A string entirely in the unmapped region
|
||||
char* string4 = region + page_size + 10;
|
||||
string4[kChildProcessStringLength] = '\0';
|
||||
char* string4 = region + base::GetPageSize() + 10;
|
||||
|
||||
strings->push_back(StringDataInChildProcess(string1));
|
||||
strings->push_back(StringDataInChildProcess(string2));
|
||||
strings->push_back(StringDataInChildProcess(string3));
|
||||
strings->push_back(StringDataInChildProcess(string4));
|
||||
|
||||
EXPECT_TRUE(pages->ResetAddrLen(region, page_size));
|
||||
strings->push_back(StringDataInChildProcess(string1, true));
|
||||
strings->push_back(StringDataInChildProcess(string2, true));
|
||||
strings->push_back(StringDataInChildProcess(string3, false));
|
||||
strings->push_back(StringDataInChildProcess(string4, false));
|
||||
}
|
||||
|
||||
CRASHPAD_CHILD_TEST_MAIN(ReadCStringUnmappedChildMain) {
|
||||
ScopedMmap pages;
|
||||
ScopedGuardedPage pages;
|
||||
std::vector<StringDataInChildProcess> strings;
|
||||
DoCStringUnmappedTestSetup(&pages, &strings);
|
||||
DoCStringUnmappedTestSetup(pages.Pointer(), &strings);
|
||||
FileHandle out = StdioFileHandle(StdioStream::kStandardOutput);
|
||||
strings[0].Write(out);
|
||||
strings[1].Write(out);
|
||||
@ -513,6 +459,8 @@ CRASHPAD_CHILD_TEST_MAIN(ReadCStringUnmappedChildMain) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This test only supports running against a child process because
|
||||
// ScopedGuardedPage is not thread-safe.
|
||||
class ReadCStringUnmappedTest : public MultiprocessExec {
|
||||
public:
|
||||
ReadCStringUnmappedTest(bool limit_size)
|
||||
@ -520,13 +468,6 @@ class ReadCStringUnmappedTest : public MultiprocessExec {
|
||||
SetChildTestMainFunction("ReadCStringUnmappedChildMain");
|
||||
}
|
||||
|
||||
void RunAgainstSelf() {
|
||||
ScopedMmap pages;
|
||||
std::vector<StringDataInChildProcess> strings;
|
||||
DoCStringUnmappedTestSetup(&pages, &strings);
|
||||
DoTest(GetSelfProcess(), strings);
|
||||
}
|
||||
|
||||
void RunAgainstChild() { Run(); }
|
||||
|
||||
private:
|
||||
@ -536,8 +477,7 @@ class ReadCStringUnmappedTest : public MultiprocessExec {
|
||||
strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
|
||||
strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
|
||||
strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
|
||||
ASSERT_NO_FATAL_FAILURE();
|
||||
DoTest(ChildProcess(), strings);
|
||||
ASSERT_NO_FATAL_FAILURE(DoTest(ChildProcess(), strings));
|
||||
}
|
||||
|
||||
void DoTest(ProcessType process,
|
||||
@ -574,24 +514,12 @@ class ReadCStringUnmappedTest : public MultiprocessExec {
|
||||
DISALLOW_COPY_AND_ASSIGN(ReadCStringUnmappedTest);
|
||||
};
|
||||
|
||||
TEST(ProcessMemory, ReadCStringUnmappedSelf) {
|
||||
ReadCStringUnmappedTest test(/* limit_size= */ false);
|
||||
ASSERT_FALSE(testing::Test::HasFailure());
|
||||
test.RunAgainstSelf();
|
||||
}
|
||||
|
||||
TEST(ProcessMemory, ReadCStringUnmappedChild) {
|
||||
ReadCStringUnmappedTest test(/* limit_size= */ false);
|
||||
ASSERT_FALSE(testing::Test::HasFailure());
|
||||
test.RunAgainstChild();
|
||||
}
|
||||
|
||||
TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedSelf) {
|
||||
ReadCStringUnmappedTest test(/* limit_size= */ true);
|
||||
ASSERT_FALSE(testing::Test::HasFailure());
|
||||
test.RunAgainstSelf();
|
||||
}
|
||||
|
||||
TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedChild) {
|
||||
ReadCStringUnmappedTest test(/* limit_size= */ true);
|
||||
ASSERT_FALSE(testing::Test::HasFailure());
|
||||
|
118
util/process/process_memory_win.cc
Normal file
118
util/process/process_memory_win.cc
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright 2018 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/process/process_memory_win.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/process/process_metrics.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
ProcessMemoryWin::ProcessMemoryWin()
|
||||
: ProcessMemory(), handle_(), process_info_(), initialized_() {}
|
||||
|
||||
ProcessMemoryWin::~ProcessMemoryWin() {}
|
||||
|
||||
bool ProcessMemoryWin::Initialize(HANDLE handle) {
|
||||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||
|
||||
handle_ = handle;
|
||||
if (!process_info_.Initialize(handle)) {
|
||||
LOG(ERROR) << "Failed to initialize ProcessInfo.";
|
||||
return false;
|
||||
}
|
||||
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t ProcessMemoryWin::ReadUpTo(VMAddress address,
|
||||
size_t size,
|
||||
void* buffer) const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
DCHECK_LE(size, (size_t)std::numeric_limits<ssize_t>::max());
|
||||
|
||||
SIZE_T size_out = 0;
|
||||
BOOL success = ReadProcessMemory(
|
||||
handle_, reinterpret_cast<void*>(address), buffer, size, &size_out);
|
||||
if (success)
|
||||
return base::checked_cast<ssize_t>(size_out);
|
||||
|
||||
if (GetLastError() == ERROR_PARTIAL_COPY) {
|
||||
// If we can not read the entire section, perform a short read of the first
|
||||
// page instead. This is necessary to support ReadCString().
|
||||
size_t short_read =
|
||||
base::GetPageSize() - (address & (base::GetPageSize() - 1));
|
||||
success = ReadProcessMemory(handle_,
|
||||
reinterpret_cast<void*>(address),
|
||||
buffer,
|
||||
short_read,
|
||||
&size_out);
|
||||
if (success)
|
||||
return base::checked_cast<ssize_t>(size_out);
|
||||
}
|
||||
|
||||
PLOG(ERROR) << "ReadMemory at 0x" << std::hex << address << std::dec << " of "
|
||||
<< size << " bytes failed";
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t ProcessMemoryWin::ReadAvailableMemory(VMAddress address,
|
||||
size_t size,
|
||||
void* buffer) const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
DCHECK_LE(size, (size_t)std::numeric_limits<ssize_t>::max());
|
||||
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
auto ranges = process_info_.GetReadableRanges(
|
||||
CheckedRange<WinVMAddress, WinVMSize>(address, size));
|
||||
|
||||
// We only read up until the first unavailable byte, so we only read from the
|
||||
// first range. If we have no ranges, then no bytes were accessible anywhere
|
||||
// in the range.
|
||||
if (ranges.empty()) {
|
||||
LOG(ERROR) << base::StringPrintf(
|
||||
"range at 0x%llx, size 0x%llx completely inaccessible", address, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If the start address was adjusted, we couldn't read even the first
|
||||
// requested byte.
|
||||
if (ranges.front().base() != address) {
|
||||
LOG(ERROR) << base::StringPrintf(
|
||||
"start of range at 0x%llx, size 0x%llx inaccessible", address, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DCHECK_LE(ranges.front().size(), size);
|
||||
|
||||
ssize_t result = ReadUpTo(ranges.front().base(),
|
||||
base::checked_cast<size_t>(ranges.front().size()),
|
||||
buffer);
|
||||
if (result < 0)
|
||||
return 0;
|
||||
|
||||
return base::checked_cast<size_t>(result);
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
66
util/process/process_memory_win.h
Normal file
66
util/process/process_memory_win.h
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2018 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_PROCESS_PROCESS_MEMORY_WIN_H_
|
||||
#define CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_WIN_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "util/misc/address_types.h"
|
||||
#include "util/misc/initialization_state_dcheck.h"
|
||||
#include "util/process/process_memory.h"
|
||||
#include "util/win/process_info.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Accesses the memory of another Windows process.
|
||||
class ProcessMemoryWin final : public ProcessMemory {
|
||||
public:
|
||||
ProcessMemoryWin();
|
||||
~ProcessMemoryWin();
|
||||
|
||||
//! \brief Initializes this object to read the memory of a process with the
|
||||
//! provided handle.
|
||||
//!
|
||||
//! This method must be called successfully prior to calling any other method
|
||||
//! in this class.
|
||||
//!
|
||||
//! \param[in] handle The HANDLE of a target process.
|
||||
//!
|
||||
//! \return `true` on success, `false` on failure with a message logged.
|
||||
bool Initialize(HANDLE handle);
|
||||
|
||||
//! \brief Attempts to read \a size bytes from the target process starting at
|
||||
//! address \a address into \a buffer. If some of the specified range is
|
||||
//! not accessible, reads up to the first inaccessible byte.
|
||||
//!
|
||||
//! \return The actual number of bytes read.
|
||||
size_t ReadAvailableMemory(VMAddress address,
|
||||
size_t num_bytes,
|
||||
void* buffer) const;
|
||||
|
||||
private:
|
||||
ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override;
|
||||
|
||||
HANDLE handle_;
|
||||
ProcessInfo process_info_;
|
||||
InitializationStateDcheck initialized_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ProcessMemoryWin);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_WIN_H_
|
Loading…
x
Reference in New Issue
Block a user