From e10e9ac7caf7559e0727c93ef7bf83fde9291778 Mon Sep 17 00:00:00 2001
From: "ted.mielczarek@gmail.com"
 <ted.mielczarek@gmail.com@4c0a9323-5329-0410-9bdc-e9ce6186880e>
Date: Thu, 6 Jun 2013 13:15:54 +0000
Subject: [PATCH] Make all linux ptrace dumper tests use a subprocess Patch by
 Mike Hommey <mh@glandium.org>, R=ted at https://breakpad.appspot.com/550002/

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1190 4c0a9323-5329-0410-9bdc-e9ce6186880e
---
 .../linux_ptrace_dumper_unittest.cc           | 426 +++++++++---------
 1 file changed, 219 insertions(+), 207 deletions(-)

diff --git a/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc
index 04da30b4..ec00255e 100644
--- a/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc
+++ b/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc
@@ -41,6 +41,7 @@
 #include <stdint.h>
 #include <string.h>
 #include <sys/mman.h>
+#include <sys/prctl.h>
 #include <sys/poll.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -63,14 +64,60 @@ namespace {
 
 typedef testing::Test LinuxPtraceDumperTest;
 
+/* Fixture for running tests in a child process. */
+class LinuxPtraceDumperChildTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    child_pid_ = fork();
+    prctl(PR_SET_PTRACER, child_pid_);
+  }
+
+  /* Gtest is calling TestBody from this class, which sets up a child
+   * process in which the RealTestBody virtual member is called.
+   * As such, TestBody is not supposed to be overridden in derived classes.
+   */
+  virtual void TestBody() /* final */ {
+    if (child_pid_ == 0) {
+      // child process
+      RealTestBody();
+      exit(HasFatalFailure() ? kFatalFailure :
+           (HasNonfatalFailure() ? kNonFatalFailure : 0));
+    }
+
+    ASSERT_TRUE(child_pid_ > 0);
+    int status;
+    waitpid(child_pid_, &status, 0);
+    if (WEXITSTATUS(status) == kFatalFailure) {
+      GTEST_FATAL_FAILURE_("Test failed in child process");
+    } else if (WEXITSTATUS(status) == kNonFatalFailure) {
+      GTEST_NONFATAL_FAILURE_("Test failed in child process");
+    }
+  }
+
+  /* Gtest defines TestBody functions through its macros, but classes
+   * derived from this one need to define RealTestBody instead.
+   * This is achieved by defining a TestBody macro further below.
+   */
+  virtual void RealTestBody() = 0;
+ private:
+  static const int kFatalFailure = 1;
+  static const int kNonFatalFailure = 2;
+
+  pid_t child_pid_;
+};
+
 }  // namespace
 
-TEST(LinuxPtraceDumperTest, Setup) {
-  LinuxPtraceDumper dumper(getpid());
+/* Replace TestBody declarations within TEST*() with RealTestBody
+ * declarations */
+#define TestBody RealTestBody
+
+TEST_F(LinuxPtraceDumperChildTest, Setup) {
+  LinuxPtraceDumper dumper(getppid());
 }
 
-TEST(LinuxPtraceDumperTest, FindMappings) {
-  LinuxPtraceDumper dumper(getpid());
+TEST_F(LinuxPtraceDumperChildTest, FindMappings) {
+  LinuxPtraceDumper dumper(getppid());
   ASSERT_TRUE(dumper.Init());
 
   ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
@@ -78,14 +125,14 @@ TEST(LinuxPtraceDumperTest, FindMappings) {
   ASSERT_FALSE(dumper.FindMapping(NULL));
 }
 
-TEST(LinuxPtraceDumperTest, ThreadList) {
-  LinuxPtraceDumper dumper(getpid());
+TEST_F(LinuxPtraceDumperChildTest, ThreadList) {
+  LinuxPtraceDumper dumper(getppid());
   ASSERT_TRUE(dumper.Init());
 
   ASSERT_GE(dumper.threads().size(), (size_t)1);
   bool found = false;
   for (size_t i = 0; i < dumper.threads().size(); ++i) {
-    if (dumper.threads()[i] == getpid()) {
+    if (dumper.threads()[i] == getppid()) {
       ASSERT_FALSE(found);
       found = true;
     }
@@ -97,12 +144,22 @@ TEST(LinuxPtraceDumperTest, ThreadList) {
 // a mmap'ed mapping.
 class StackHelper {
  public:
-  StackHelper(int fd, char* mapping, size_t size)
-    : fd_(fd), mapping_(mapping), size_(size) {}
+  StackHelper()
+    : fd_(-1), mapping_(NULL), size_(0) {}
   ~StackHelper() {
-    munmap(mapping_, size_);
-    close(fd_);
+    if (size_)
+      munmap(mapping_, size_);
+    if (fd_ >= 0)
+      close(fd_);
   }
+  void Init(int fd, char* mapping, size_t size) {
+    fd_ = fd;
+    mapping_ = mapping;
+    size_ = size;
+  }
+
+  char* mapping() const { return mapping_; }
+  size_t size() const { return size_; }
 
  private:
   int fd_;
@@ -110,19 +167,28 @@ class StackHelper {
   size_t size_;
 };
 
-TEST(LinuxPtraceDumperTest, MergedMappings) {
-  string helper_path(GetHelperBinary());
-  if (helper_path.empty()) {
+class LinuxPtraceDumperMappingsTest : public LinuxPtraceDumperChildTest {
+ protected:
+  virtual void SetUp();
+
+  string helper_path_;
+  size_t page_size_;
+  StackHelper helper_;
+};
+
+void LinuxPtraceDumperMappingsTest::SetUp() {
+  helper_path_ = GetHelperBinary();
+  if (helper_path_.empty()) {
     FAIL() << "Couldn't find helper binary";
     exit(1);
   }
 
   // mmap two segments out of the helper binary, one
   // enclosed in the other, but with different protections.
-  const size_t kPageSize = sysconf(_SC_PAGESIZE);
-  const size_t kMappingSize = 3 * kPageSize;
-  int fd = open(helper_path.c_str(), O_RDONLY);
-  ASSERT_NE(-1, fd) << "Failed to open file: " << helper_path
+  page_size_ = sysconf(_SC_PAGESIZE);
+  const size_t kMappingSize = 3 * page_size_;
+  int fd = open(helper_path_.c_str(), O_RDONLY);
+  ASSERT_NE(-1, fd) << "Failed to open file: " << helper_path_
                     << ", Error: " << strerror(errno);
   char* mapping =
     reinterpret_cast<char*>(mmap(NULL,
@@ -133,34 +199,37 @@ TEST(LinuxPtraceDumperTest, MergedMappings) {
                                  0));
   ASSERT_TRUE(mapping);
 
-  const uintptr_t kMappingAddress = reinterpret_cast<uintptr_t>(mapping);
-
   // Ensure that things get cleaned up.
-  StackHelper helper(fd, mapping, kMappingSize);
+  helper_.Init(fd, mapping, kMappingSize);
 
   // Carve a page out of the first mapping with different permissions.
   char* inside_mapping =  reinterpret_cast<char*>(
-      mmap(mapping + 2 *kPageSize,
-           kPageSize,
+      mmap(mapping + 2 * page_size_,
+           page_size_,
            PROT_NONE,
            MAP_SHARED | MAP_FIXED,
            fd,
            // Map a different offset just to
            // better test real-world conditions.
-           kPageSize));
+           page_size_));
   ASSERT_TRUE(inside_mapping);
 
+  LinuxPtraceDumperChildTest::SetUp();
+}
+
+TEST_F(LinuxPtraceDumperMappingsTest, MergedMappings) {
   // Now check that LinuxPtraceDumper interpreted the mappings properly.
-  LinuxPtraceDumper dumper(getpid());
+  LinuxPtraceDumper dumper(getppid());
   ASSERT_TRUE(dumper.Init());
   int mapping_count = 0;
   for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
     const MappingInfo& mapping = *dumper.mappings()[i];
-    if (strcmp(mapping.name, helper_path.c_str()) == 0) {
+    if (strcmp(mapping.name, this->helper_path_.c_str()) == 0) {
       // This mapping should encompass the entire original mapped
       // range.
-      EXPECT_EQ(kMappingAddress, mapping.start_addr);
-      EXPECT_EQ(kMappingSize, mapping.size);
+      EXPECT_EQ(reinterpret_cast<uintptr_t>(this->helper_.mapping()),
+                mapping.start_addr);
+      EXPECT_EQ(this->helper_.size(), mapping.size);
       EXPECT_EQ(0U, mapping.offset);
       mapping_count++;
     }
@@ -168,6 +237,124 @@ TEST(LinuxPtraceDumperTest, MergedMappings) {
   EXPECT_EQ(1, mapping_count);
 }
 
+TEST_F(LinuxPtraceDumperChildTest, BuildProcPath) {
+  const pid_t pid = getppid();
+  LinuxPtraceDumper dumper(pid);
+
+  char maps_path[NAME_MAX] = "";
+  char maps_path_expected[NAME_MAX];
+  snprintf(maps_path_expected, sizeof(maps_path_expected),
+           "/proc/%d/maps", pid);
+  EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps"));
+  EXPECT_STREQ(maps_path_expected, maps_path);
+
+  EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps"));
+  EXPECT_FALSE(dumper.BuildProcPath(maps_path, 0, "maps"));
+  EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, ""));
+  EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL));
+
+  char long_node[NAME_MAX];
+  size_t long_node_len = NAME_MAX - strlen("/proc/123") - 1;
+  memset(long_node, 'a', long_node_len);
+  long_node[long_node_len] = '\0';
+  EXPECT_FALSE(dumper.BuildProcPath(maps_path, 123, long_node));
+}
+
+#if !defined(__ARM_EABI__)
+// Ensure that the linux-gate VDSO is included in the mapping list.
+TEST_F(LinuxPtraceDumperChildTest, MappingsIncludeLinuxGate) {
+  LinuxPtraceDumper dumper(getppid());
+  ASSERT_TRUE(dumper.Init());
+
+  void* linux_gate_loc =
+    reinterpret_cast<void *>(dumper.auxv()[AT_SYSINFO_EHDR]);
+  ASSERT_TRUE(linux_gate_loc);
+  bool found_linux_gate = false;
+
+  const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
+  const MappingInfo* mapping;
+  for (unsigned i = 0; i < mappings.size(); ++i) {
+    mapping = mappings[i];
+    if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
+      found_linux_gate = true;
+      break;
+    }
+  }
+  EXPECT_TRUE(found_linux_gate);
+  EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
+  EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
+}
+
+// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
+TEST_F(LinuxPtraceDumperChildTest, LinuxGateMappingID) {
+  LinuxPtraceDumper dumper(getppid());
+  ASSERT_TRUE(dumper.Init());
+
+  bool found_linux_gate = false;
+  const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
+  unsigned index = 0;
+  for (unsigned i = 0; i < mappings.size(); ++i) {
+    if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
+      found_linux_gate = true;
+      index = i;
+      break;
+    }
+  }
+  ASSERT_TRUE(found_linux_gate);
+
+  // Need to suspend the child so ptrace actually works.
+  ASSERT_TRUE(dumper.ThreadsSuspend());
+  uint8_t identifier[sizeof(MDGUID)];
+  ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
+                                                 true,
+                                                 index,
+                                                 identifier));
+  uint8_t empty_identifier[sizeof(MDGUID)];
+  memset(empty_identifier, 0, sizeof(empty_identifier));
+  EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
+  EXPECT_TRUE(dumper.ThreadsResume());
+}
+#endif
+
+TEST_F(LinuxPtraceDumperChildTest, FileIDsMatch) {
+  // Calculate the File ID of our binary using both
+  // FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
+  // and ensure that we get the same result from both.
+  char exe_name[PATH_MAX];
+  ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name));
+
+  LinuxPtraceDumper dumper(getppid());
+  ASSERT_TRUE(dumper.Init());
+  const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
+  bool found_exe = false;
+  unsigned i;
+  for (i = 0; i < mappings.size(); ++i) {
+    const MappingInfo* mapping = mappings[i];
+    if (!strcmp(mapping->name, exe_name)) {
+      found_exe = true;
+      break;
+    }
+  }
+  ASSERT_TRUE(found_exe);
+
+  uint8_t identifier1[sizeof(MDGUID)];
+  uint8_t identifier2[sizeof(MDGUID)];
+  EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
+                                                 identifier1));
+  FileID fileid(exe_name);
+  EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
+  char identifier_string1[37];
+  char identifier_string2[37];
+  FileID::ConvertIdentifierToString(identifier1, identifier_string1,
+                                    37);
+  FileID::ConvertIdentifierToString(identifier2, identifier_string2,
+                                    37);
+  EXPECT_STREQ(identifier_string1, identifier_string2);
+}
+
+/* Get back to normal behavior of TEST*() macros wrt TestBody. */
+#undef TestBody
+
 TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
   static const int kNumberOfThreadsInHelperProgram = 5;
   char kNumberOfThreadsArgument[2];
@@ -213,7 +400,7 @@ TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
     ASSERT_EQ(1, r);
     ASSERT_TRUE(pfd.revents & POLLIN);
     uint8_t junk;
-    ASSERT_EQ(read(fds[0], &junk, sizeof(junk)), 
+    ASSERT_EQ(read(fds[0], &junk, sizeof(junk)),
               static_cast<ssize_t>(sizeof(junk)));
   }
   close(fds[0]);
@@ -239,11 +426,11 @@ TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
     // In the helper program, we stored a pointer to the thread id in a
     // specific register. Check that we can recover its value.
 #if defined(__ARM_EABI__)
-    pid_t *process_tid_location = (pid_t *)(one_thread.regs.uregs[3]);
+    pid_t* process_tid_location = (pid_t*)(one_thread.regs.uregs[3]);
 #elif defined(__i386)
-    pid_t *process_tid_location = (pid_t *)(one_thread.regs.ecx);
+    pid_t* process_tid_location = (pid_t*)(one_thread.regs.ecx);
 #elif defined(__x86_64)
-    pid_t *process_tid_location = (pid_t *)(one_thread.regs.rcx);
+    pid_t* process_tid_location = (pid_t*)(one_thread.regs.rcx);
 #else
 #error This test has not been ported to this platform.
 #endif
@@ -263,178 +450,3 @@ TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
   ASSERT_TRUE(WIFSIGNALED(status));
   ASSERT_EQ(SIGKILL, WTERMSIG(status));
 }
-
-TEST(LinuxPtraceDumperTest, BuildProcPath) {
-  const pid_t pid = getpid();
-  LinuxPtraceDumper dumper(pid);
-
-  char maps_path[NAME_MAX] = "";
-  char maps_path_expected[NAME_MAX];
-  snprintf(maps_path_expected, sizeof(maps_path_expected),
-           "/proc/%d/maps", pid);
-  EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps"));
-  EXPECT_STREQ(maps_path_expected, maps_path);
-
-  EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps"));
-  EXPECT_FALSE(dumper.BuildProcPath(maps_path, 0, "maps"));
-  EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, ""));
-  EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL));
-
-  char long_node[NAME_MAX];
-  size_t long_node_len = NAME_MAX - strlen("/proc/123") - 1;
-  memset(long_node, 'a', long_node_len);
-  long_node[long_node_len] = '\0';
-  EXPECT_FALSE(dumper.BuildProcPath(maps_path, 123, long_node));
-}
-
-#if !defined(__ARM_EABI__)
-// Ensure that the linux-gate VDSO is included in the mapping list.
-TEST(LinuxPtraceDumperTest, MappingsIncludeLinuxGate) {
-  LinuxPtraceDumper dumper(getpid());
-  ASSERT_TRUE(dumper.Init());
-
-  void* linux_gate_loc =
-    reinterpret_cast<void *>(dumper.auxv()[AT_SYSINFO_EHDR]);
-  ASSERT_TRUE(linux_gate_loc);
-  bool found_linux_gate = false;
-
-  const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
-  const MappingInfo* mapping;
-  for (unsigned i = 0; i < mappings.size(); ++i) {
-    mapping = mappings[i];
-    if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
-      found_linux_gate = true;
-      break;
-    }
-  }
-  EXPECT_TRUE(found_linux_gate);
-  EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
-  EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
-}
-
-// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
-TEST(LinuxPtraceDumperTest, LinuxGateMappingID) {
-  LinuxPtraceDumper dumper(getpid());
-  ASSERT_TRUE(dumper.Init());
-
-  bool found_linux_gate = false;
-  const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
-  unsigned index = 0;
-  for (unsigned i = 0; i < mappings.size(); ++i) {
-    if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
-      found_linux_gate = true;
-      index = i;
-      break;
-    }
-  }
-  ASSERT_TRUE(found_linux_gate);
-
-  uint8_t identifier[sizeof(MDGUID)];
-  ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
-                                                 true,
-                                                 index,
-                                                 identifier));
-  uint8_t empty_identifier[sizeof(MDGUID)];
-  memset(empty_identifier, 0, sizeof(empty_identifier));
-  EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
-}
-
-// Ensure that the linux-gate VDSO can generate a non-zeroed File ID
-// from a child process.
-TEST(LinuxPtraceDumperTest, LinuxGateMappingIDChild) {
-  int fds[2];
-  ASSERT_NE(-1, pipe(fds));
-
-  // Fork a child so ptrace works.
-  const pid_t child = fork();
-  if (child == 0) {
-    close(fds[1]);
-    // Now wait forever for the parent.
-    char b;
-    IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
-    close(fds[0]);
-    syscall(__NR_exit);
-  }
-  close(fds[0]);
-
-  LinuxPtraceDumper dumper(child);
-  ASSERT_TRUE(dumper.Init());
-
-  bool found_linux_gate = false;
-  const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
-  unsigned index = 0;
-  for (unsigned i = 0; i < mappings.size(); ++i) {
-    if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
-      found_linux_gate = true;
-      index = i;
-      break;
-    }
-  }
-  ASSERT_TRUE(found_linux_gate);
-
-  // Need to suspend the child so ptrace actually works.
-  ASSERT_TRUE(dumper.ThreadsSuspend());
-  uint8_t identifier[sizeof(MDGUID)];
-  ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
-                                                 true,
-                                                 index,
-                                                 identifier));
-  uint8_t empty_identifier[sizeof(MDGUID)];
-  memset(empty_identifier, 0, sizeof(empty_identifier));
-  EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
-  EXPECT_TRUE(dumper.ThreadsResume());
-  close(fds[1]);
-}
-#endif
-
-TEST(LinuxPtraceDumperTest, FileIDsMatch) {
-  // Calculate the File ID of our binary using both
-  // FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
-  // and ensure that we get the same result from both.
-  char exe_name[PATH_MAX];
-  ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name));
-
-  int fds[2];
-  ASSERT_NE(-1, pipe(fds));
-
-  // Fork a child so ptrace works.
-  const pid_t child = fork();
-  if (child == 0) {
-    close(fds[1]);
-    // Now wait forever for the parent.
-    char b;
-    IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
-    close(fds[0]);
-    syscall(__NR_exit);
-  }
-  close(fds[0]);
-
-  LinuxPtraceDumper dumper(child);
-  ASSERT_TRUE(dumper.Init());
-  const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
-  bool found_exe = false;
-  unsigned i;
-  for (i = 0; i < mappings.size(); ++i) {
-    const MappingInfo* mapping = mappings[i];
-    if (!strcmp(mapping->name, exe_name)) {
-      found_exe = true;
-      break;
-    }
-  }
-  ASSERT_TRUE(found_exe);
-
-  uint8_t identifier1[sizeof(MDGUID)];
-  uint8_t identifier2[sizeof(MDGUID)];
-  EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
-                                                 identifier1));
-  FileID fileid(exe_name);
-  EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
-  char identifier_string1[37];
-  char identifier_string2[37];
-  FileID::ConvertIdentifierToString(identifier1, identifier_string1,
-                                    37);
-  FileID::ConvertIdentifierToString(identifier2, identifier_string2,
-                                    37);
-  EXPECT_STREQ(identifier_string1, identifier_string2);
-  close(fds[1]);
-}