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 #include #include +#include #include #include #include @@ -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(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(mmap(NULL, @@ -133,34 +199,37 @@ TEST(LinuxPtraceDumperTest, MergedMappings) { 0)); ASSERT_TRUE(mapping); - const uintptr_t kMappingAddress = reinterpret_cast(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( - 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(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(dumper.auxv()[AT_SYSINFO_EHDR]); + ASSERT_TRUE(linux_gate_loc); + bool found_linux_gate = false; + + const wasteful_vector 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(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 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 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(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(dumper.auxv()[AT_SYSINFO_EHDR]); - ASSERT_TRUE(linux_gate_loc); - bool found_linux_gate = false; - - const wasteful_vector 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(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 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 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 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]); -}