Index: third_party/crashpad/crashpad/util/linux/process_memory_test.cc |
diff --git a/third_party/crashpad/crashpad/util/linux/process_memory_test.cc b/third_party/crashpad/crashpad/util/linux/process_memory_test.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e00134a75e0e924dfb18bdd0d83773b99681d821 |
--- /dev/null |
+++ b/third_party/crashpad/crashpad/util/linux/process_memory_test.cc |
@@ -0,0 +1,403 @@ |
+// 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/process_memory.h" |
+ |
+#include <string.h> |
+#include <sys/mman.h> |
+#include <unistd.h> |
+ |
+#include <memory> |
+ |
+#include "gtest/gtest.h" |
+#include "test/errors.h" |
+#include "test/multiprocess.h" |
+#include "util/file/file_io.h" |
+#include "util/posix/scoped_mmap.h" |
+ |
+namespace crashpad { |
+namespace test { |
+namespace { |
+ |
+class TargetProcessTest : public Multiprocess { |
+ public: |
+ TargetProcessTest() : Multiprocess() {} |
+ ~TargetProcessTest() {} |
+ |
+ void RunAgainstSelf() { DoTest(getpid()); } |
+ |
+ void RunAgainstForked() { Run(); } |
+ |
+ private: |
+ void MultiprocessParent() override { DoTest(ChildPID()); } |
+ |
+ void MultiprocessChild() override { CheckedReadFileAtEOF(ReadPipeHandle()); } |
+ |
+ virtual void DoTest(pid_t pid) = 0; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TargetProcessTest); |
+}; |
+ |
+class ReadTest : public TargetProcessTest { |
+ public: |
+ ReadTest() |
+ : TargetProcessTest(), |
+ page_size_(getpagesize()), |
+ region_size_(4 * page_size_), |
+ region_(new char[region_size_]) { |
+ for (size_t index = 0; index < region_size_; ++index) { |
+ region_[index] = index % 256; |
+ } |
+ } |
+ |
+ private: |
+ void DoTest(pid_t pid) override { |
+ ProcessMemory memory; |
+ ASSERT_TRUE(memory.Initialize(pid)); |
+ |
+ LinuxVMAddress address = reinterpret_cast<LinuxVMAddress>(region_.get()); |
+ std::unique_ptr<char[]> result(new char[region_size_]); |
+ |
+ // Ensure that the entire region can be read. |
+ ASSERT_TRUE(memory.Read(address, region_size_, result.get())); |
+ EXPECT_EQ(memcmp(region_.get(), result.get(), region_size_), 0); |
+ |
+ // Ensure that a read of length 0 succeeds and doesn’t touch the result. |
+ memset(result.get(), '\0', region_size_); |
+ ASSERT_TRUE(memory.Read(address, 0, result.get())); |
+ for (size_t i = 0; i < region_size_; ++i) { |
+ EXPECT_EQ(result[i], 0); |
+ } |
+ |
+ // Ensure that a read starting at an unaligned address works. |
+ ASSERT_TRUE(memory.Read(address + 1, region_size_ - 1, result.get())); |
+ EXPECT_EQ(memcmp(region_.get() + 1, result.get(), region_size_ - 1), 0); |
+ |
+ // Ensure that a read ending at an unaligned address works. |
+ ASSERT_TRUE(memory.Read(address, region_size_ - 1, result.get())); |
+ EXPECT_EQ(memcmp(region_.get(), result.get(), region_size_ - 1), 0); |
+ |
+ // Ensure that a read starting and ending at unaligned addresses works. |
+ ASSERT_TRUE(memory.Read(address + 1, region_size_ - 2, result.get())); |
+ EXPECT_EQ(memcmp(region_.get() + 1, result.get(), region_size_ - 2), 0); |
+ |
+ // Ensure that a read of exactly one page works. |
+ ASSERT_TRUE(memory.Read(address + page_size_, page_size_, result.get())); |
+ EXPECT_EQ(memcmp(region_.get() + page_size_, result.get(), page_size_), 0); |
+ |
+ // Ensure that reading exactly a single byte works. |
+ result[1] = 'J'; |
+ ASSERT_TRUE(memory.Read(address + 2, 1, result.get())); |
+ EXPECT_EQ(result[0], region_[2]); |
+ EXPECT_EQ(result[1], 'J'); |
+ } |
+ |
+ const size_t page_size_; |
+ const size_t region_size_; |
+ std::unique_ptr<char[]> region_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ReadTest); |
+}; |
+ |
+TEST(ProcessMemory, ReadSelf) { |
+ ReadTest test; |
+ test.RunAgainstSelf(); |
+} |
+ |
+TEST(ProcessMemory, ReadForked) { |
+ ReadTest test; |
+ test.RunAgainstForked(); |
+} |
+ |
+bool ReadCString(const ProcessMemory& memory, |
+ const char* pointer, |
+ std::string* result) { |
+ return memory.ReadCString(reinterpret_cast<LinuxVMAddress>(pointer), result); |
+} |
+ |
+bool ReadCStringSizeLimited(const ProcessMemory& memory, |
+ const char* pointer, |
+ size_t size, |
+ std::string* result) { |
+ return memory.ReadCStringSizeLimited( |
+ reinterpret_cast<LinuxVMAddress>(pointer), size, result); |
+} |
+ |
+const char kConstCharEmpty[] = ""; |
+const char kConstCharShort[] = "A short const char[]"; |
+ |
+class ReadCStringTest : public TargetProcessTest { |
+ public: |
+ ReadCStringTest(bool limit_size) |
+ : TargetProcessTest(), |
+ member_char_empty_(""), |
+ member_char_short_("A short member char[]"), |
+ limit_size_(limit_size) { |
+ const size_t kStringLongSize = 4 * getpagesize(); |
+ for (size_t index = 0; index < kStringLongSize; ++index) { |
+ string_long_.push_back((index % 255) + 1); |
+ } |
+ EXPECT_EQ(string_long_.size(), kStringLongSize); |
+ } |
+ |
+ private: |
+ void DoTest(pid_t pid) override { |
+ ProcessMemory memory; |
+ ASSERT_TRUE(memory.Initialize(pid)); |
+ |
+ std::string result; |
+ |
+ if (limit_size_) { |
+ ASSERT_TRUE(ReadCStringSizeLimited( |
+ memory, kConstCharEmpty, arraysize(kConstCharEmpty), &result)); |
+ EXPECT_EQ(result, kConstCharEmpty); |
+ |
+ ASSERT_TRUE(ReadCStringSizeLimited( |
+ memory, kConstCharShort, arraysize(kConstCharShort), &result)); |
+ EXPECT_EQ(result, kConstCharShort); |
+ EXPECT_FALSE(ReadCStringSizeLimited( |
+ memory, kConstCharShort, arraysize(kConstCharShort) - 1, &result)); |
+ |
+ ASSERT_TRUE(ReadCStringSizeLimited( |
+ memory, member_char_empty_, strlen(member_char_empty_) + 1, &result)); |
+ EXPECT_EQ(result, member_char_empty_); |
+ |
+ ASSERT_TRUE(ReadCStringSizeLimited( |
+ memory, member_char_short_, strlen(member_char_short_) + 1, &result)); |
+ EXPECT_EQ(result, member_char_short_); |
+ EXPECT_FALSE(ReadCStringSizeLimited( |
+ memory, member_char_short_, strlen(member_char_short_), &result)); |
+ |
+ ASSERT_TRUE(ReadCStringSizeLimited( |
+ memory, string_long_.c_str(), string_long_.size() + 1, &result)); |
+ EXPECT_EQ(result, string_long_); |
+ EXPECT_FALSE(ReadCStringSizeLimited( |
+ memory, string_long_.c_str(), string_long_.size(), &result)); |
+ } else { |
+ ASSERT_TRUE(ReadCString(memory, kConstCharEmpty, &result)); |
+ EXPECT_EQ(result, kConstCharEmpty); |
+ |
+ ASSERT_TRUE(ReadCString(memory, kConstCharShort, &result)); |
+ EXPECT_EQ(result, kConstCharShort); |
+ |
+ ASSERT_TRUE(ReadCString(memory, member_char_empty_, &result)); |
+ EXPECT_EQ(result, member_char_empty_); |
+ |
+ ASSERT_TRUE(ReadCString(memory, member_char_short_, &result)); |
+ EXPECT_EQ(result, member_char_short_); |
+ |
+ ASSERT_TRUE(ReadCString(memory, string_long_.c_str(), &result)); |
+ EXPECT_EQ(result, string_long_); |
+ } |
+ } |
+ |
+ std::string string_long_; |
+ const char* member_char_empty_; |
+ const char* member_char_short_; |
+ const bool limit_size_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ReadCStringTest); |
+}; |
+ |
+TEST(ProcessMemory, ReadCStringSelf) { |
+ ReadCStringTest test(/* limit_size= */ false); |
+ test.RunAgainstSelf(); |
+} |
+ |
+TEST(ProcessMemory, ReadCStringForked) { |
+ ReadCStringTest test(/* limit_size= */ false); |
+ test.RunAgainstForked(); |
+} |
+ |
+TEST(ProcessMemory, ReadCStringSizeLimitedSelf) { |
+ ReadCStringTest test(/* limit_size= */ true); |
+ test.RunAgainstSelf(); |
+} |
+ |
+TEST(ProcessMemory, ReadCStringSizeLimitedForked) { |
+ ReadCStringTest test(/* limit_size= */ true); |
+ test.RunAgainstForked(); |
+} |
+ |
+class ReadUnmappedTest : public TargetProcessTest { |
+ public: |
+ ReadUnmappedTest() |
+ : TargetProcessTest(), |
+ page_size_(getpagesize()), |
+ region_size_(2 * page_size_), |
+ result_(new char[region_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) { |
+ region[index] = index % 256; |
+ } |
+ |
+ EXPECT_TRUE(pages_.ResetAddrLen(region, page_size_)); |
+ } |
+ |
+ private: |
+ void DoTest(pid_t pid) override { |
+ ProcessMemory memory; |
+ ASSERT_TRUE(memory.Initialize(pid)); |
+ |
+ LinuxVMAddress page_addr1 = pages_.addr_as<LinuxVMAddress>(); |
+ LinuxVMAddress page_addr2 = page_addr1 + page_size_; |
+ |
+ EXPECT_TRUE(memory.Read(page_addr1, page_size_, 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_addr2 - 1, 2, result_.get())); |
+ } |
+ |
+ ScopedMmap pages_; |
+ const size_t page_size_; |
+ const size_t region_size_; |
+ std::unique_ptr<char[]> result_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ReadUnmappedTest); |
+}; |
+ |
+TEST(ProcessMemory, ReadUnmappedSelf) { |
+ ReadUnmappedTest test; |
+ ASSERT_FALSE(testing::Test::HasFailure()); |
+ test.RunAgainstSelf(); |
+} |
+ |
+TEST(ProcessMemory, ReadUnmappedForked) { |
+ ReadUnmappedTest test; |
+ ASSERT_FALSE(testing::Test::HasFailure()); |
+ test.RunAgainstForked(); |
+} |
+ |
+class ReadCStringUnmappedTest : public TargetProcessTest { |
+ public: |
+ ReadCStringUnmappedTest(bool limit_size) |
+ : TargetProcessTest(), |
+ page_size_(getpagesize()), |
+ region_size_(2 * page_size_), |
+ limit_size_(limit_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) { |
+ region[index] = 1 + index % 255; |
+ } |
+ |
+ // A string at the start of the mapped region |
+ string1_ = region; |
+ string1_[expected_length_] = '\0'; |
+ |
+ // A string near the end of the mapped region |
+ string2_ = region + page_size_ - expected_length_ * 2; |
+ string2_[expected_length_] = '\0'; |
+ |
+ // A string that crosses from the mapped into the unmapped region |
+ string3_ = region + page_size_ - expected_length_ + 1; |
+ string3_[expected_length_] = '\0'; |
+ |
+ // A string entirely in the unmapped region |
+ string4_ = region + page_size_ + 10; |
+ string4_[expected_length_] = '\0'; |
+ |
+ result_.reserve(expected_length_ + 1); |
+ |
+ EXPECT_TRUE(pages_.ResetAddrLen(region, page_size_)); |
+ } |
+ |
+ private: |
+ void DoTest(pid_t pid) { |
+ ProcessMemory memory; |
+ ASSERT_TRUE(memory.Initialize(pid)); |
+ |
+ if (limit_size_) { |
+ ASSERT_TRUE(ReadCStringSizeLimited( |
+ memory, string1_, expected_length_ + 1, &result_)); |
+ EXPECT_EQ(result_, string1_); |
+ ASSERT_TRUE(ReadCStringSizeLimited( |
+ memory, string2_, expected_length_ + 1, &result_)); |
+ EXPECT_EQ(result_, string2_); |
+ EXPECT_FALSE(ReadCStringSizeLimited( |
+ memory, string3_, expected_length_ + 1, &result_)); |
+ EXPECT_FALSE(ReadCStringSizeLimited( |
+ memory, string4_, expected_length_ + 1, &result_)); |
+ } else { |
+ ASSERT_TRUE(ReadCString(memory, string1_, &result_)); |
+ EXPECT_EQ(result_, string1_); |
+ ASSERT_TRUE(ReadCString(memory, string2_, &result_)); |
+ EXPECT_EQ(result_, string2_); |
+ EXPECT_FALSE(ReadCString(memory, string3_, &result_)); |
+ EXPECT_FALSE(ReadCString(memory, string4_, &result_)); |
+ } |
+ } |
+ |
+ std::string result_; |
+ ScopedMmap pages_; |
+ const size_t page_size_; |
+ const size_t region_size_; |
+ static const size_t expected_length_ = 10; |
+ char* string1_; |
+ char* string2_; |
+ char* string3_; |
+ char* string4_; |
+ const bool limit_size_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ReadCStringUnmappedTest); |
+}; |
+ |
+TEST(ProcessMemory, ReadCStringUnmappedSelf) { |
+ ReadCStringUnmappedTest test(/* limit_size= */ false); |
+ ASSERT_FALSE(testing::Test::HasFailure()); |
+ test.RunAgainstSelf(); |
+} |
+ |
+TEST(ProcessMemory, ReadCStringUnmappedForked) { |
+ ReadCStringUnmappedTest test(/* limit_size= */ false); |
+ ASSERT_FALSE(testing::Test::HasFailure()); |
+ test.RunAgainstForked(); |
+} |
+ |
+TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedSelf) { |
+ ReadCStringUnmappedTest test(/* limit_size= */ true); |
+ ASSERT_FALSE(testing::Test::HasFailure()); |
+ test.RunAgainstSelf(); |
+} |
+ |
+TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedForked) { |
+ ReadCStringUnmappedTest test(/* limit_size= */ true); |
+ ASSERT_FALSE(testing::Test::HasFailure()); |
+ test.RunAgainstForked(); |
+} |
+ |
+} // namespace |
+} // namespace test |
+} // namespace crashpad |