aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc394
1 files changed, 203 insertions, 191 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,104 +237,8 @@ TEST(LinuxPtraceDumperTest, MergedMappings) {
EXPECT_EQ(1, mapping_count);
}
-TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
- static const int kNumberOfThreadsInHelperProgram = 5;
- char kNumberOfThreadsArgument[2];
- sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
-
- int fds[2];
- ASSERT_NE(-1, pipe(fds));
-
- pid_t child_pid = fork();
- if (child_pid == 0) {
- // In child process.
- close(fds[0]);
-
- string helper_path(GetHelperBinary());
- if (helper_path.empty()) {
- FAIL() << "Couldn't find helper binary";
- exit(1);
- }
-
- // Pass the pipe fd and the number of threads as arguments.
- char pipe_fd_string[8];
- sprintf(pipe_fd_string, "%d", fds[1]);
- execl(helper_path.c_str(),
- "linux_dumper_unittest_helper",
- pipe_fd_string,
- kNumberOfThreadsArgument,
- NULL);
- // Kill if we get here.
- printf("Errno from exec: %d", errno);
- FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
- exit(0);
- }
- close(fds[1]);
-
- // Wait for all child threads to indicate that they have started
- for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) {
- struct pollfd pfd;
- memset(&pfd, 0, sizeof(pfd));
- pfd.fd = fds[0];
- pfd.events = POLLIN | POLLERR;
-
- const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
- ASSERT_EQ(1, r);
- ASSERT_TRUE(pfd.revents & POLLIN);
- uint8_t junk;
- ASSERT_EQ(read(fds[0], &junk, sizeof(junk)),
- static_cast<ssize_t>(sizeof(junk)));
- }
- close(fds[0]);
-
- // There is a race here because we may stop a child thread before
- // it is actually running the busy loop. Empirically this sleep
- // is sufficient to avoid the race.
- usleep(100000);
-
- // Children are ready now.
- LinuxPtraceDumper dumper(child_pid);
- ASSERT_TRUE(dumper.Init());
- EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
- EXPECT_TRUE(dumper.ThreadsSuspend());
-
- ThreadInfo one_thread;
- for (size_t i = 0; i < dumper.threads().size(); ++i) {
- EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread));
- const void* stack;
- size_t stack_len;
- EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len,
- one_thread.stack_pointer));
- // 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]);
-#elif defined(__i386)
- 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);
-#else
-#error This test has not been ported to this platform.
-#endif
- pid_t one_thread_id;
- dumper.CopyFromProcess(&one_thread_id,
- dumper.threads()[i],
- process_tid_location,
- 4);
- EXPECT_EQ(dumper.threads()[i], one_thread_id);
- }
- EXPECT_TRUE(dumper.ThreadsResume());
- kill(child_pid, SIGKILL);
-
- // Reap child
- int status;
- ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0)));
- ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_EQ(SIGKILL, WTERMSIG(status));
-}
-
-TEST(LinuxPtraceDumperTest, BuildProcPath) {
- const pid_t pid = getpid();
+TEST_F(LinuxPtraceDumperChildTest, BuildProcPath) {
+ const pid_t pid = getppid();
LinuxPtraceDumper dumper(pid);
char maps_path[NAME_MAX] = "";
@@ -289,8 +262,8 @@ TEST(LinuxPtraceDumperTest, BuildProcPath) {
#if !defined(__ARM_EABI__)
// Ensure that the linux-gate VDSO is included in the mapping list.
-TEST(LinuxPtraceDumperTest, MappingsIncludeLinuxGate) {
- LinuxPtraceDumper dumper(getpid());
+TEST_F(LinuxPtraceDumperChildTest, MappingsIncludeLinuxGate) {
+ LinuxPtraceDumper dumper(getppid());
ASSERT_TRUE(dumper.Init());
void* linux_gate_loc =
@@ -313,51 +286,8 @@ TEST(LinuxPtraceDumperTest, MappingsIncludeLinuxGate) {
}
// 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);
+TEST_F(LinuxPtraceDumperChildTest, LinuxGateMappingID) {
+ LinuxPtraceDumper dumper(getppid());
ASSERT_TRUE(dumper.Init());
bool found_linux_gate = false;
@@ -383,33 +313,17 @@ TEST(LinuxPtraceDumperTest, LinuxGateMappingIDChild) {
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) {
+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));
- 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);
+ LinuxPtraceDumper dumper(getppid());
ASSERT_TRUE(dumper.Init());
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
bool found_exe = false;
@@ -436,5 +350,103 @@ TEST(LinuxPtraceDumperTest, FileIDsMatch) {
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];
+ sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
+
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+ close(fds[0]);
+
+ string helper_path(GetHelperBinary());
+ if (helper_path.empty()) {
+ FAIL() << "Couldn't find helper binary";
+ exit(1);
+ }
+
+ // Pass the pipe fd and the number of threads as arguments.
+ char pipe_fd_string[8];
+ sprintf(pipe_fd_string, "%d", fds[1]);
+ execl(helper_path.c_str(),
+ "linux_dumper_unittest_helper",
+ pipe_fd_string,
+ kNumberOfThreadsArgument,
+ NULL);
+ // Kill if we get here.
+ printf("Errno from exec: %d", errno);
+ FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
+ exit(0);
+ }
close(fds[1]);
+
+ // Wait for all child threads to indicate that they have started
+ for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) {
+ struct pollfd pfd;
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fds[0];
+ pfd.events = POLLIN | POLLERR;
+
+ const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
+ ASSERT_EQ(1, r);
+ ASSERT_TRUE(pfd.revents & POLLIN);
+ uint8_t junk;
+ ASSERT_EQ(read(fds[0], &junk, sizeof(junk)),
+ static_cast<ssize_t>(sizeof(junk)));
+ }
+ close(fds[0]);
+
+ // There is a race here because we may stop a child thread before
+ // it is actually running the busy loop. Empirically this sleep
+ // is sufficient to avoid the race.
+ usleep(100000);
+
+ // Children are ready now.
+ LinuxPtraceDumper dumper(child_pid);
+ ASSERT_TRUE(dumper.Init());
+ EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
+ EXPECT_TRUE(dumper.ThreadsSuspend());
+
+ ThreadInfo one_thread;
+ for (size_t i = 0; i < dumper.threads().size(); ++i) {
+ EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread));
+ const void* stack;
+ size_t stack_len;
+ EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len,
+ one_thread.stack_pointer));
+ // 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]);
+#elif defined(__i386)
+ 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);
+#else
+#error This test has not been ported to this platform.
+#endif
+ pid_t one_thread_id;
+ dumper.CopyFromProcess(&one_thread_id,
+ dumper.threads()[i],
+ process_tid_location,
+ 4);
+ EXPECT_EQ(dumper.threads()[i], one_thread_id);
+ }
+ EXPECT_TRUE(dumper.ThreadsResume());
+ kill(child_pid, SIGKILL);
+
+ // Reap child
+ int status;
+ ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0)));
+ ASSERT_TRUE(WIFSIGNALED(status));
+ ASSERT_EQ(SIGKILL, WTERMSIG(status));
}