aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rw-r--r--Makefile.in22
-rw-r--r--src/client/linux/handler/exception_handler.cc2
-rw-r--r--src/client/linux/handler/exception_handler_unittest.cc314
-rw-r--r--src/client/linux/minidump_writer/linux_dumper.h2
-rw-r--r--src/client/linux/minidump_writer/linux_dumper_unittest.cc2
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.cc9
-rw-r--r--src/client/mac/Breakpad.xcodeproj/project.pbxproj30
-rw-r--r--src/client/mac/handler/minidump_generator.cc122
-rw-r--r--src/client/mac/handler/minidump_generator.h13
-rw-r--r--src/client/mac/tests/exception_handler_test.cc405
-rw-r--r--src/client/mac/tests/testlogging.h9
-rw-r--r--src/common/memory.h (renamed from src/common/linux/memory.h)13
-rw-r--r--src/common/memory_unittest.cc (renamed from src/common/linux/memory_unittest.cc)4
14 files changed, 930 insertions, 18 deletions
diff --git a/Makefile.am b/Makefile.am
index 5fe64bc8..91b575f4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -208,6 +208,7 @@ src_client_linux_linux_client_unittest_SOURCES = \
src/client/linux/minidump_writer/line_reader_unittest.cc \
src/client/linux/minidump_writer/linux_dumper_unittest.cc \
src/client/linux/minidump_writer/minidump_writer_unittest.cc \
+ src/common/memory_unittest.cc \
src/testing/gtest/src/gtest-all.cc \
src/testing/gtest/src/gtest_main.cc \
src/testing/src/gmock-all.cc
diff --git a/Makefile.in b/Makefile.in
index b2b07c4a..b13df77a 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -199,6 +199,7 @@ am__src_client_linux_linux_client_unittest_SOURCES_DIST = \
src/client/linux/minidump_writer/line_reader_unittest.cc \
src/client/linux/minidump_writer/linux_dumper_unittest.cc \
src/client/linux/minidump_writer/minidump_writer_unittest.cc \
+ src/common/memory_unittest.cc \
src/testing/gtest/src/gtest-all.cc \
src/testing/gtest/src/gtest_main.cc \
src/testing/src/gmock-all.cc
@@ -207,6 +208,7 @@ am__src_client_linux_linux_client_unittest_SOURCES_DIST = \
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-line_reader_unittest.$(OBJEXT) \
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_dumper_unittest.$(OBJEXT) \
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-minidump_writer_unittest.$(OBJEXT) \
+@LINUX_HOST_TRUE@ src/common/src_client_linux_linux_client_unittest-memory_unittest.$(OBJEXT) \
@LINUX_HOST_TRUE@ src/testing/gtest/src/src_client_linux_linux_client_unittest-gtest-all.$(OBJEXT) \
@LINUX_HOST_TRUE@ src/testing/gtest/src/src_client_linux_linux_client_unittest-gtest_main.$(OBJEXT) \
@LINUX_HOST_TRUE@ src/testing/src/src_client_linux_linux_client_unittest-gmock-all.$(OBJEXT)
@@ -801,6 +803,7 @@ TESTS_ENVIRONMENT =
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/line_reader_unittest.cc \
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/linux_dumper_unittest.cc \
@LINUX_HOST_TRUE@ src/client/linux/minidump_writer/minidump_writer_unittest.cc \
+@LINUX_HOST_TRUE@ src/common/memory_unittest.cc \
@LINUX_HOST_TRUE@ src/testing/gtest/src/gtest-all.cc \
@LINUX_HOST_TRUE@ src/testing/gtest/src/gtest_main.cc \
@LINUX_HOST_TRUE@ src/testing/src/gmock-all.cc
@@ -1622,6 +1625,9 @@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-linux_du
src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-minidump_writer_unittest.$(OBJEXT): \
src/client/linux/minidump_writer/$(am__dirstamp) \
src/client/linux/minidump_writer/$(DEPDIR)/$(am__dirstamp)
+src/common/src_client_linux_linux_client_unittest-memory_unittest.$(OBJEXT): \
+ src/common/$(am__dirstamp) \
+ src/common/$(DEPDIR)/$(am__dirstamp)
src/testing/gtest/src/$(am__dirstamp):
@$(MKDIR_P) src/testing/gtest/src
@: > src/testing/gtest/src/$(am__dirstamp)
@@ -1944,6 +1950,7 @@ mostlyclean-compile:
-rm -f src/common/linux/guid_creator.lo
-rm -f src/common/md5.$(OBJEXT)
-rm -f src/common/md5.lo
+ -rm -f src/common/src_client_linux_linux_client_unittest-memory_unittest.$(OBJEXT)
-rm -f src/common/src_common_test_assembler_unittest-test_assembler.$(OBJEXT)
-rm -f src/common/src_common_test_assembler_unittest-test_assembler_unittest.$(OBJEXT)
-rm -f src/common/src_processor_minidump_unittest-test_assembler.$(OBJEXT)
@@ -2078,6 +2085,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@src/client/linux/minidump_writer/$(DEPDIR)/src_client_linux_linux_dumper_unittest_helper-linux_dumper_unittest_helper.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@src/common/$(DEPDIR)/convert_UTF.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@src/common/$(DEPDIR)/md5.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@src/common/$(DEPDIR)/src_client_linux_linux_client_unittest-memory_unittest.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@src/common/$(DEPDIR)/src_common_test_assembler_unittest-test_assembler.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@src/common/$(DEPDIR)/src_common_test_assembler_unittest-test_assembler_unittest.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@src/common/$(DEPDIR)/src_processor_minidump_unittest-test_assembler.Po@am__quote@
@@ -2292,6 +2300,20 @@ src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-minidump
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/client/linux/minidump_writer/src_client_linux_linux_client_unittest-minidump_writer_unittest.obj `if test -f 'src/client/linux/minidump_writer/minidump_writer_unittest.cc'; then $(CYGPATH_W) 'src/client/linux/minidump_writer/minidump_writer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/client/linux/minidump_writer/minidump_writer_unittest.cc'; fi`
+src/common/src_client_linux_linux_client_unittest-memory_unittest.o: src/common/memory_unittest.cc
+@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/src_client_linux_linux_client_unittest-memory_unittest.o -MD -MP -MF src/common/$(DEPDIR)/src_client_linux_linux_client_unittest-memory_unittest.Tpo -c -o src/common/src_client_linux_linux_client_unittest-memory_unittest.o `test -f 'src/common/memory_unittest.cc' || echo '$(srcdir)/'`src/common/memory_unittest.cc
+@am__fastdepCXX_TRUE@ $(am__mv) src/common/$(DEPDIR)/src_client_linux_linux_client_unittest-memory_unittest.Tpo src/common/$(DEPDIR)/src_client_linux_linux_client_unittest-memory_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/memory_unittest.cc' object='src/common/src_client_linux_linux_client_unittest-memory_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/src_client_linux_linux_client_unittest-memory_unittest.o `test -f 'src/common/memory_unittest.cc' || echo '$(srcdir)/'`src/common/memory_unittest.cc
+
+src/common/src_client_linux_linux_client_unittest-memory_unittest.obj: src/common/memory_unittest.cc
+@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/src_client_linux_linux_client_unittest-memory_unittest.obj -MD -MP -MF src/common/$(DEPDIR)/src_client_linux_linux_client_unittest-memory_unittest.Tpo -c -o src/common/src_client_linux_linux_client_unittest-memory_unittest.obj `if test -f 'src/common/memory_unittest.cc'; then $(CYGPATH_W) 'src/common/memory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/memory_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(am__mv) src/common/$(DEPDIR)/src_client_linux_linux_client_unittest-memory_unittest.Tpo src/common/$(DEPDIR)/src_client_linux_linux_client_unittest-memory_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/memory_unittest.cc' object='src/common/src_client_linux_linux_client_unittest-memory_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/src_client_linux_linux_client_unittest-memory_unittest.obj `if test -f 'src/common/memory_unittest.cc'; then $(CYGPATH_W) 'src/common/memory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/memory_unittest.cc'; fi`
+
src/testing/gtest/src/src_client_linux_linux_client_unittest-gtest-all.o: src/testing/gtest/src/gtest-all.cc
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_client_linux_linux_client_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/testing/gtest/src/src_client_linux_linux_client_unittest-gtest-all.o -MD -MP -MF src/testing/gtest/src/$(DEPDIR)/src_client_linux_linux_client_unittest-gtest-all.Tpo -c -o src/testing/gtest/src/src_client_linux_linux_client_unittest-gtest-all.o `test -f 'src/testing/gtest/src/gtest-all.cc' || echo '$(srcdir)/'`src/testing/gtest/src/gtest-all.cc
@am__fastdepCXX_TRUE@ $(am__mv) src/testing/gtest/src/$(DEPDIR)/src_client_linux_linux_client_unittest-gtest-all.Tpo src/testing/gtest/src/$(DEPDIR)/src_client_linux_linux_client_unittest-gtest-all.Po
diff --git a/src/client/linux/handler/exception_handler.cc b/src/client/linux/handler/exception_handler.cc
index 5af8f872..57da0c56 100644
--- a/src/client/linux/handler/exception_handler.cc
+++ b/src/client/linux/handler/exception_handler.cc
@@ -85,7 +85,7 @@
#include <vector>
#include "common/linux/linux_libc_support.h"
-#include "common/linux/memory.h"
+#include "common/memory.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "common/linux/guid_creator.h"
#include "common/linux/eintr_wrapper.h"
diff --git a/src/client/linux/handler/exception_handler_unittest.cc b/src/client/linux/handler/exception_handler_unittest.cc
index 3fea1159..c8879fcb 100644
--- a/src/client/linux/handler/exception_handler_unittest.cc
+++ b/src/client/linux/handler/exception_handler_unittest.cc
@@ -130,6 +130,8 @@ TEST(ExceptionHandlerTest, ChildCrash) {
unlink(minidump_filename.c_str());
}
+// Test that memory around the instruction pointer is written
+// to the dump as a MinidumpMemoryRegion.
TEST(ExceptionHandlerTest, InstructionPointerMemory) {
int fds[2];
ASSERT_NE(pipe(fds), -1);
@@ -252,6 +254,318 @@ TEST(ExceptionHandlerTest, InstructionPointerMemory) {
free(filename);
}
+// Test that the memory region around the instruction pointer is
+// bounded correctly on the low end.
+TEST(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
+ int fds[2];
+ ASSERT_NE(pipe(fds), -1);
+
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ const u_int32_t kMemorySize = 256; // bytes
+ const int kOffset = 0;
+ // This crashes with SIGILL on x86/x86-64/arm.
+ const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[0]);
+ ExceptionHandler handler("/tmp", NULL, DoneCallback, (void*) fds[1],
+ true);
+ // Get some executable memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ if (!memory)
+ exit(0);
+
+ // Write some instructions that will crash. Put them in the middle
+ // of the block of memory, because the minidump should contain 128
+ // bytes on either side of the instruction pointer.
+ memcpy(memory + kOffset, instructions, sizeof(instructions));
+
+ // Now execute the instructions, which should crash.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(memory + kOffset);
+ memory_function();
+ }
+ close(fds[1]);
+
+ int status;
+ ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
+ ASSERT_TRUE(WIFSIGNALED(status));
+ ASSERT_EQ(WTERMSIG(status), SIGILL);
+
+ struct pollfd pfd;
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fds[0];
+ pfd.events = POLLIN | POLLERR;
+
+ const int r = HANDLE_EINTR(poll(&pfd, 1, 0));
+ ASSERT_EQ(r, 1);
+ ASSERT_TRUE(pfd.revents & POLLIN);
+
+ uint32_t len;
+ ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len));
+ ASSERT_LT(len, (uint32_t)2048);
+ char* filename = reinterpret_cast<char*>(malloc(len + 1));
+ ASSERT_EQ(read(fds[0], filename, len), len);
+ filename[len] = 0;
+ close(fds[0]);
+
+ const std::string minidump_filename = std::string("/tmp/") + filename +
+ ".dmp";
+
+ struct stat st;
+ ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0);
+ ASSERT_GT(st.st_size, 0u);
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is a memory region
+ // in the memory list that covers the instruction pointer from
+ // the exception record.
+ Minidump minidump(minidump_filename);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_LT(0, memory_list->region_count());
+
+ MinidumpContext* context = exception->GetContext();
+ ASSERT_TRUE(context);
+
+ u_int64_t instruction_pointer;
+ switch (context->GetContextCPU()) {
+ case MD_CONTEXT_X86:
+ instruction_pointer = context->GetContextX86()->eip;
+ break;
+ case MD_CONTEXT_AMD64:
+ instruction_pointer = context->GetContextAMD64()->rip;
+ break;
+ case MD_CONTEXT_ARM:
+ instruction_pointer = context->GetContextARM()->iregs[15];
+ break;
+ default:
+ FAIL() << "Unknown context CPU: " << context->GetContextCPU();
+ break;
+ }
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ ASSERT_TRUE(region);
+
+ EXPECT_EQ(kMemorySize / 2, region->GetSize());
+ const u_int8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ u_int8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)];
+ memset(suffix_bytes, 0, sizeof(suffix_bytes));
+ EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0);
+ EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
+ suffix_bytes, sizeof(suffix_bytes)) == 0);
+
+ unlink(minidump_filename.c_str());
+ free(filename);
+}
+
+// Test that the memory region around the instruction pointer is
+// bounded correctly on the high end.
+TEST(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
+ int fds[2];
+ ASSERT_NE(pipe(fds), -1);
+
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ // Use 4k here because the OS will hand out a single page even
+ // if a smaller size is requested, and this test wants to
+ // test the upper bound of the memory range.
+ const u_int32_t kMemorySize = 4096; // bytes
+ // This crashes with SIGILL on x86/x86-64/arm.
+ const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
+ const int kOffset = kMemorySize - sizeof(instructions);
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[0]);
+ ExceptionHandler handler("/tmp", NULL, DoneCallback, (void*) fds[1],
+ true);
+ // Get some executable memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ if (!memory)
+ exit(0);
+
+ // Write some instructions that will crash. Put them in the middle
+ // of the block of memory, because the minidump should contain 128
+ // bytes on either side of the instruction pointer.
+ memcpy(memory + kOffset, instructions, sizeof(instructions));
+
+ // Now execute the instructions, which should crash.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(memory + kOffset);
+ memory_function();
+ }
+ close(fds[1]);
+
+ int status;
+ ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
+ ASSERT_TRUE(WIFSIGNALED(status));
+ ASSERT_EQ(WTERMSIG(status), SIGILL);
+
+ struct pollfd pfd;
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fds[0];
+ pfd.events = POLLIN | POLLERR;
+
+ const int r = HANDLE_EINTR(poll(&pfd, 1, 0));
+ ASSERT_EQ(r, 1);
+ ASSERT_TRUE(pfd.revents & POLLIN);
+
+ uint32_t len;
+ ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len));
+ ASSERT_LT(len, (uint32_t)2048);
+ char* filename = reinterpret_cast<char*>(malloc(len + 1));
+ ASSERT_EQ(read(fds[0], filename, len), len);
+ filename[len] = 0;
+ close(fds[0]);
+
+ const std::string minidump_filename = std::string("/tmp/") + filename +
+ ".dmp";
+
+ struct stat st;
+ ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0);
+ ASSERT_GT(st.st_size, 0u);
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is a memory region
+ // in the memory list that covers the instruction pointer from
+ // the exception record.
+ Minidump minidump(minidump_filename);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_LT(0, memory_list->region_count());
+
+ MinidumpContext* context = exception->GetContext();
+ ASSERT_TRUE(context);
+
+ u_int64_t instruction_pointer;
+ switch (context->GetContextCPU()) {
+ case MD_CONTEXT_X86:
+ instruction_pointer = context->GetContextX86()->eip;
+ break;
+ case MD_CONTEXT_AMD64:
+ instruction_pointer = context->GetContextAMD64()->rip;
+ break;
+ case MD_CONTEXT_ARM:
+ instruction_pointer = context->GetContextARM()->iregs[15];
+ break;
+ default:
+ FAIL() << "Unknown context CPU: " << context->GetContextCPU();
+ break;
+ }
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ ASSERT_TRUE(region);
+
+ const size_t kPrefixSize = 128; // bytes
+ EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize());
+ const u_int8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ u_int8_t prefix_bytes[kPrefixSize];
+ memset(prefix_bytes, 0, sizeof(prefix_bytes));
+ EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
+ EXPECT_TRUE(memcmp(bytes + kPrefixSize,
+ instructions, sizeof(instructions)) == 0);
+
+ unlink(minidump_filename.c_str());
+ free(filename);
+}
+
+// Ensure that an extra memory block doesn't get added when the
+// instruction pointer is not in mapped memory.
+TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) {
+ int fds[2];
+ ASSERT_NE(pipe(fds), -1);
+
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[0]);
+ ExceptionHandler handler("/tmp", NULL, DoneCallback, (void*) fds[1],
+ true);
+ // Try calling a NULL pointer.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(NULL);
+ memory_function();
+ }
+ close(fds[1]);
+
+ int status;
+ ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
+ ASSERT_TRUE(WIFSIGNALED(status));
+ ASSERT_EQ(WTERMSIG(status), SIGSEGV);
+
+ struct pollfd pfd;
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fds[0];
+ pfd.events = POLLIN | POLLERR;
+
+ const int r = HANDLE_EINTR(poll(&pfd, 1, 0));
+ ASSERT_EQ(r, 1);
+ ASSERT_TRUE(pfd.revents & POLLIN);
+
+ uint32_t len;
+ ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len));
+ ASSERT_LT(len, (uint32_t)2048);
+ char* filename = reinterpret_cast<char*>(malloc(len + 1));
+ ASSERT_EQ(read(fds[0], filename, len), len);
+ filename[len] = 0;
+ close(fds[0]);
+
+ const std::string minidump_filename = std::string("/tmp/") + filename +
+ ".dmp";
+
+ struct stat st;
+ ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0);
+ ASSERT_GT(st.st_size, 0u);
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is a memory region
+ // in the memory list that covers the instruction pointer from
+ // the exception record.
+ Minidump minidump(minidump_filename);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_EQ((unsigned int)1, memory_list->region_count());
+
+ unlink(minidump_filename.c_str());
+ free(filename);
+}
+
static const unsigned kControlMsgSize =
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred));
diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h
index 7a4cd3a3..b0c479a9 100644
--- a/src/client/linux/minidump_writer/linux_dumper.h
+++ b/src/client/linux/minidump_writer/linux_dumper.h
@@ -36,7 +36,7 @@
#include <sys/types.h>
#include <sys/user.h>
-#include "common/linux/memory.h"
+#include "common/memory.h"
#include "google_breakpad/common/minidump_format.h"
namespace google_breakpad {
diff --git a/src/client/linux/minidump_writer/linux_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_dumper_unittest.cc
index da454742..8cfb6004 100644
--- a/src/client/linux/minidump_writer/linux_dumper_unittest.cc
+++ b/src/client/linux/minidump_writer/linux_dumper_unittest.cc
@@ -35,7 +35,7 @@
#include "breakpad_googletest_includes.h"
#include "client/linux/minidump_writer/linux_dumper.h"
#include "common/linux/file_id.h"
-#include "common/linux/memory.h"
+#include "common/memory.h"
using namespace google_breakpad;
diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc
index 6b55ade0..d629ee81 100644
--- a/src/client/linux/minidump_writer/minidump_writer.cc
+++ b/src/client/linux/minidump_writer/minidump_writer.cc
@@ -662,12 +662,13 @@ class MinidumpWriter {
// Try to get 128 bytes before and after the IP, but
// settle for whatever's available.
ip_memory_d.start_of_memory_range =
- std::min(mapping.start_addr,
+ std::max(mapping.start_addr,
uintptr_t(ip - (kIPMemorySize / 2)));
+ uintptr_t end_of_range =
+ std::min(uintptr_t(ip + (kIPMemorySize / 2)),
+ uintptr_t(mapping.start_addr + mapping.size));
ip_memory_d.memory.data_size =
- std::min(ptrdiff_t(kIPMemorySize),
- ptrdiff_t(mapping.start_addr + mapping.size
- - ip_memory_d.start_of_memory_range));
+ end_of_range - ip_memory_d.start_of_memory_range;
break;
}
}
diff --git a/src/client/mac/Breakpad.xcodeproj/project.pbxproj b/src/client/mac/Breakpad.xcodeproj/project.pbxproj
index b6bb5fe7..3f18a726 100644
--- a/src/client/mac/Breakpad.xcodeproj/project.pbxproj
+++ b/src/client/mac/Breakpad.xcodeproj/project.pbxproj
@@ -51,6 +51,11 @@
8B4BDABE12012CEF009C7060 /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B4BDAA7120124EA009C7060 /* libcrypto.dylib */; };
8B4BDAC512012D05009C7060 /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B4BDAA7120124EA009C7060 /* libcrypto.dylib */; };
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; };
+ D244536A12426F00009BBCE0 /* logging.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535112426EBB009BBCE0 /* logging.cc */; };
+ D244536B12426F00009BBCE0 /* minidump.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535212426EBB009BBCE0 /* minidump.cc */; };
+ D244536C12426F00009BBCE0 /* pathname_stripper.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535312426EBB009BBCE0 /* pathname_stripper.cc */; };
+ D244536D12426F00009BBCE0 /* basic_code_modules.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244534F12426E98009BBCE0 /* basic_code_modules.cc */; };
+ D244540B12439BA0009BBCE0 /* memory_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244540A12439BA0009BBCE0 /* memory_unittest.cc */; };
D24BBBFD121050F000F3D417 /* breakpadUtilities.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */; };
D24BBD291211EDB100F3D417 /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; };
D24BBD321212CACF00F3D417 /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; };
@@ -507,6 +512,11 @@
8B31FFF611F0C90500FCF3E4 /* Breakpad.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Breakpad.xcconfig; path = ../../common/mac/Breakpad.xcconfig; sourceTree = SOURCE_ROOT; };
8B4BDAA7120124EA009C7060 /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = usr/lib/libcrypto.dylib; sourceTree = SDKROOT; };
8DC2EF5B0486A6940098B216 /* Breakpad.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Breakpad.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D244534F12426E98009BBCE0 /* basic_code_modules.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = basic_code_modules.cc; path = ../../processor/basic_code_modules.cc; sourceTree = SOURCE_ROOT; };
+ D244535112426EBB009BBCE0 /* logging.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = logging.cc; path = ../../processor/logging.cc; sourceTree = SOURCE_ROOT; };
+ D244535212426EBB009BBCE0 /* minidump.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump.cc; path = ../../processor/minidump.cc; sourceTree = SOURCE_ROOT; };
+ D244535312426EBB009BBCE0 /* pathname_stripper.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pathname_stripper.cc; path = ../../processor/pathname_stripper.cc; sourceTree = SOURCE_ROOT; };
+ D244540A12439BA0009BBCE0 /* memory_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = memory_unittest.cc; path = ../../common/memory_unittest.cc; sourceTree = SOURCE_ROOT; };
D2F9A3D41212F87C002747C1 /* exception_handler_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = exception_handler_test.cc; path = tests/exception_handler_test.cc; sourceTree = "<group>"; };
D2F9A41512131EF0002747C1 /* libgtest.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libgtest.a; sourceTree = BUILT_PRODUCTS_DIR; };
D2F9A43C12131F55002747C1 /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "../../testing/src/gmock-all.cc"; sourceTree = SOURCE_ROOT; };
@@ -759,6 +769,7 @@
32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */,
F92C538D0ECCE6F2009BE4BA /* client */,
F92C53600ECCE3D6009BE4BA /* common */,
+ D244536912426EE7009BBCE0 /* processor */,
0867D69AFE84028FC02AAC07 /* Frameworks */,
034768DFFF38A50411DB9C8B /* Products */,
F9C77DDB0F7DD5CF0045F7DB /* UnitTests-Info.plist */,
@@ -780,6 +791,17 @@
name = Frameworks;
sourceTree = "<group>";
};
+ D244536912426EE7009BBCE0 /* processor */ = {
+ isa = PBXGroup;
+ children = (
+ D244535112426EBB009BBCE0 /* logging.cc */,
+ D244535212426EBB009BBCE0 /* minidump.cc */,
+ D244535312426EBB009BBCE0 /* pathname_stripper.cc */,
+ D244534F12426E98009BBCE0 /* basic_code_modules.cc */,
+ );
+ name = processor;
+ sourceTree = "<group>";
+ };
D2F9A43812131F3B002747C1 /* gtest */ = {
isa = PBXGroup;
children = (
@@ -813,6 +835,7 @@
F92C53600ECCE3D6009BE4BA /* common */ = {
isa = PBXGroup;
children = (
+ D244540A12439BA0009BBCE0 /* memory_unittest.cc */,
F92C53870ECCE6C0009BE4BA /* convert_UTF.c */,
F92C53880ECCE6C0009BE4BA /* convert_UTF.h */,
F92C53850ECCE6AD009BE4BA /* string_conversion.cc */,
@@ -1632,6 +1655,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ D244536A12426F00009BBCE0 /* logging.cc in Sources */,
+ D244536B12426F00009BBCE0 /* minidump.cc in Sources */,
+ D244536C12426F00009BBCE0 /* pathname_stripper.cc in Sources */,
+ D244536D12426F00009BBCE0 /* basic_code_modules.cc in Sources */,
D2F9A4E112133AE2002747C1 /* crash_generation_client.cc in Sources */,
D2F9A4E212133AE2002747C1 /* crash_generation_server.cc in Sources */,
D24BBD321212CACF00F3D417 /* MachIPC.mm in Sources */,
@@ -1648,6 +1675,7 @@
F93DE33E0F82C66B00608B94 /* macho_walker.cc in Sources */,
F93DE33F0F82C66B00608B94 /* string_utilities.cc in Sources */,
D2F9A3D51212F87C002747C1 /* exception_handler_test.cc in Sources */,
+ D244540B12439BA0009BBCE0 /* memory_unittest.cc in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2100,8 +2128,10 @@
buildSettings = {
DEBUG_INFORMATION_FORMAT = dwarf;
GCC_INLINES_ARE_PRIVATE_EXTERN = NO;
+ GCC_PREPROCESSOR_DEFINITIONS = "BP_LOGGING_INCLUDE=\\\"client/mac/tests/testlogging.h\\\"";
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
HEADER_SEARCH_PATHS = (
+ ../../..,
../..,
../../testing,
../../testing/include,
diff --git a/src/client/mac/handler/minidump_generator.cc b/src/client/mac/handler/minidump_generator.cc
index 0c54cf42..73086743 100644
--- a/src/client/mac/handler/minidump_generator.cc
+++ b/src/client/mac/handler/minidump_generator.cc
@@ -27,6 +27,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#include <algorithm>
#include <cstdio>
#include <mach/host_info.h>
@@ -64,7 +65,8 @@ MinidumpGenerator::MinidumpGenerator()
exception_thread_(0),
crashing_task_(mach_task_self()),
handler_thread_(mach_thread_self()),
- dynamic_images_(NULL) {
+ dynamic_images_(NULL),
+ memory_blocks_(&allocator_) {
GatherSystemInformation();
}
@@ -79,7 +81,8 @@ MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task,
exception_thread_(0),
crashing_task_(crashing_task),
handler_thread_(handler_thread),
- dynamic_images_(NULL) {
+ dynamic_images_(NULL),
+ memory_blocks_(&allocator_) {
if (crashing_task != mach_task_self()) {
dynamic_images_ = new DynamicImages(crashing_task_);
} else {
@@ -173,6 +176,7 @@ string MinidumpGenerator::UniqueNameInDirectory(const string &dir,
bool MinidumpGenerator::Write(const char *path) {
WriteStreamFN writers[] = {
&MinidumpGenerator::WriteThreadListStream,
+ &MinidumpGenerator::WriteMemoryListStream,
&MinidumpGenerator::WriteSystemInfoStream,
&MinidumpGenerator::WriteModuleListStream,
&MinidumpGenerator::WriteMiscInfoStream,
@@ -514,6 +518,8 @@ bool MinidumpGenerator::WriteThreadStream(mach_port_t thread_id,
if (!WriteStack(state, &thread->stack))
return false;
+ memory_blocks_.push_back(thread->stack);
+
if (!WriteContext(state, &thread->thread_context))
return false;
@@ -566,6 +572,118 @@ bool MinidumpGenerator::WriteThreadListStream(
return true;
}
+bool MinidumpGenerator::WriteMemoryListStream(
+ MDRawDirectory *memory_list_stream) {
+ TypedMDRVA<MDRawMemoryList> list(&writer_);
+
+ // If the dump has an exception, include some memory around the
+ // instruction pointer.
+ const size_t kIPMemorySize = 256; // bytes
+ bool have_ip_memory = false;
+ MDMemoryDescriptor ip_memory_d;
+ if (exception_thread_ && exception_type_) {
+ breakpad_thread_state_data_t state;
+ mach_msg_type_number_t stateCount
+ = static_cast<mach_msg_type_number_t>(sizeof(state));
+
+ if (thread_get_state(exception_thread_,
+ BREAKPAD_MACHINE_THREAD_STATE,
+ state,
+ &stateCount) == KERN_SUCCESS) {
+ u_int64_t ip = CurrentPCForStack(state);
+ // Bound it to the upper and lower bounds of the region
+ // it's contained within. If it's not in a known memory region,
+ // don't bother trying to write it.
+ mach_vm_address_t addr = ip;
+ mach_vm_size_t size;
+ natural_t nesting_level = 0;
+ vm_region_submap_info_64 info;
+ mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
+
+ kern_return_t ret =
+ mach_vm_region_recurse(crashing_task_,
+ &addr,
+ &size,
+ &nesting_level,
+ (vm_region_recurse_info_t)&info,
+ &info_count);
+ if (ret == KERN_SUCCESS && ip >= addr && ip < (addr + size)) {
+ // Try to get 128 bytes before and after the IP, but
+ // settle for whatever's available.
+ ip_memory_d.start_of_memory_range =
+ std::max(uintptr_t(addr),
+ uintptr_t(ip - (kIPMemorySize / 2)));
+ uintptr_t end_of_range =
+ std::min(uintptr_t(ip + (kIPMemorySize / 2)),
+ uintptr_t(addr + size));
+ ip_memory_d.memory.data_size =
+ end_of_range - ip_memory_d.start_of_memory_range;
+ have_ip_memory = true;
+ // This needs to get appended to the list even though
+ // the memory bytes aren't filled in yet so the entire
+ // list can be written first. The memory bytes will get filled
+ // in after the memory list is written.
+ memory_blocks_.push_back(ip_memory_d);
+ }
+ }
+ }
+
+ // Now fill in the memory list and write it.
+ unsigned memory_count = memory_blocks_.size();
+ if (!list.AllocateObjectAndArray(memory_count,
+ sizeof(MDMemoryDescriptor)))
+ return false;
+
+ memory_list_stream->stream_type = MD_MEMORY_LIST_STREAM;
+ memory_list_stream->location = list.location();
+
+ list.get()->number_of_memory_ranges = memory_count;
+
+ unsigned int i;
+ for (i = 0; i < memory_count; ++i) {
+ list.CopyIndexAfterObject(i++, &memory_blocks_[i],
+ sizeof(MDMemoryDescriptor));
+ }
+
+ if (have_ip_memory) {
+ // Now read the memory around the instruction pointer.
+ UntypedMDRVA ip_memory(&writer_);
+ if (!ip_memory.Allocate(ip_memory_d.memory.data_size))
+ return false;
+
+ if (dynamic_images_) {
+ // Out-of-process.
+ kern_return_t kr;
+
+ void *memory =
+ ReadTaskMemory(
+ crashing_task_,
+ reinterpret_cast<const void *>(ip_memory_d.start_of_memory_range),
+ ip_memory_d.memory.data_size,
+ &kr);
+
+ if (memory == NULL) {
+ return false;
+ }
+
+ ip_memory.Copy(memory, ip_memory_d.memory.data_size);
+ free(memory);
+ } else {
+ // In-process, just copy from local memory.
+ ip_memory.Copy(
+ reinterpret_cast<const void *>(ip_memory_d.start_of_memory_range),
+ ip_memory_d.memory.data_size);
+ }
+
+ ip_memory_d.memory = ip_memory.location();
+ // Write this again now that the data location is filled in.
+ list.CopyIndexAfterObject(i - 1, &ip_memory_d,
+ sizeof(MDMemoryDescriptor));
+ }
+
+ return true;
+}
+
bool
MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) {
TypedMDRVA<MDRawExceptionStream> exception(&writer_);
diff --git a/src/client/mac/handler/minidump_generator.h b/src/client/mac/handler/minidump_generator.h
index b065fba9..39f28402 100644
--- a/src/client/mac/handler/minidump_generator.h
+++ b/src/client/mac/handler/minidump_generator.h
@@ -37,8 +37,9 @@
#include <string>
#include "client/minidump_file_writer.h"
-#include "google_breakpad/common/minidump_format.h"
+#include "common/memory.h"
#include "common/mac/macho_utilities.h"
+#include "google_breakpad/common/minidump_format.h"
#include "dynamic_images.h"
@@ -119,6 +120,7 @@ class MinidumpGenerator {
// Stream writers
bool WriteThreadListStream(MDRawDirectory *thread_list_stream);
+ bool WriteMemoryListStream(MDRawDirectory *memory_list_stream);
bool WriteExceptionStream(MDRawDirectory *exception_stream);
bool WriteSystemInfoStream(MDRawDirectory *system_info_stream);
bool WriteModuleListStream(MDRawDirectory *module_list_stream);
@@ -165,6 +167,15 @@ class MinidumpGenerator {
// Information about dynamically loaded code
DynamicImages *dynamic_images_;
+
+ // PageAllocator makes it possible to allocate memory
+ // directly from the system, even while handling an exception.
+ mutable PageAllocator allocator_;
+
+ // Blocks of memory written to the dump. These are all currently
+ // written while writing the thread list stream, but saved here
+ // so a memory list stream can be written afterwards.
+ wasteful_vector<MDMemoryDescriptor> memory_blocks_;
};
} // namespace google_breakpad
diff --git a/src/client/mac/tests/exception_handler_test.cc b/src/client/mac/tests/exception_handler_test.cc
index 09237313..4bdd1856 100644
--- a/src/client/mac/tests/exception_handler_test.cc
+++ b/src/client/mac/tests/exception_handler_test.cc
@@ -29,6 +29,7 @@
// exception_handler_test.cc: Unit tests for google_breakpad::ExceptionHandler
+#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
@@ -36,6 +37,14 @@
#include "client/mac/handler/exception_handler.h"
#include "client/mac/tests/auto_tempdir.h"
#include "common/mac/MachIPC.h"
+#include "google_breakpad/processor/minidump.h"
+
+namespace google_breakpad {
+// This acts as the log sink for INFO logging from the processor
+// logging code. The logging output confuses XCode and makes it think
+// there are unit test failures. testlogging.h handles the overriding.
+std::ostringstream info_log;
+}
namespace {
using std::string;
@@ -44,6 +53,11 @@ using google_breakpad::ExceptionHandler;
using google_breakpad::MachPortSender;
using google_breakpad::MachReceiveMessage;
using google_breakpad::MachSendMessage;
+using google_breakpad::Minidump;
+using google_breakpad::MinidumpContext;
+using google_breakpad::MinidumpException;
+using google_breakpad::MinidumpMemoryList;
+using google_breakpad::MinidumpMemoryRegion;
using google_breakpad::ReceivePort;
using testing::Test;
@@ -80,7 +94,6 @@ static bool MDCallback(const char *dump_dir, const char *file_name,
}
TEST_F(ExceptionHandlerTest, InProcess) {
- AutoTempDir tempDir;
// Give the child process a pipe to report back on.
int fds[2];
ASSERT_EQ(0, pipe(fds));
@@ -167,8 +180,8 @@ TEST_F(ExceptionHandlerTest, DumpChildProcess) {
parent_recv_port.WaitForMessage(&child_message, kTimeoutMs));
mach_port_t child_task = child_message.GetTranslatedPort(0);
mach_port_t child_thread = child_message.GetTranslatedPort(1);
- ASSERT_NE(MACH_PORT_NULL, child_task);
- ASSERT_NE(MACH_PORT_NULL, child_thread);
+ ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task);
+ ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_thread);
// Write a minidump of the child process.
bool result = ExceptionHandler::WriteMinidumpForChild(child_task,
@@ -195,4 +208,390 @@ TEST_F(ExceptionHandlerTest, DumpChildProcess) {
EXPECT_EQ(0, WEXITSTATUS(ret));
}
+// Test that memory around the instruction pointer is written
+// to the dump as a MinidumpMemoryRegion.
+TEST_F(ExceptionHandlerTest, InstructionPointerMemory) {
+ // Give the child process a pipe to report back on.
+ int fds[2];
+ ASSERT_EQ(0, pipe(fds));
+
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ const u_int32_t kMemorySize = 256; // bytes
+ const int kOffset = kMemorySize / 2;
+ // This crashes with SIGILL on x86/x86-64/arm.
+ const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ close(fds[0]);
+ ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
+ // Get some executable memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ if (!memory)
+ exit(0);
+
+ // Write some instructions that will crash. Put them in the middle
+ // of the block of memory, because the minidump should contain 128
+ // bytes on either side of the instruction pointer.
+ memcpy(memory + kOffset, instructions, sizeof(instructions));
+
+ // Now execute the instructions, which should crash.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(memory + kOffset);
+ memory_function();
+ // not reached
+ exit(1);
+ }
+ // In the parent process.
+ ASSERT_NE(-1, pid);
+ close(fds[1]);
+
+ // Wait for the background process to return the minidump file.
+ close(fds[1]);
+ char minidump_file[PATH_MAX];
+ ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
+ ASSERT_NE(0, nbytes);
+ // Ensure that minidump file exists and is > 0 bytes.
+ struct stat st;
+ ASSERT_EQ(0, stat(minidump_file, &st));
+ ASSERT_LT(0, st.st_size);
+
+ // Child process should have exited with a zero status.
+ int ret;
+ ASSERT_EQ(pid, waitpid(pid, &ret, 0));
+ EXPECT_NE(0, WIFEXITED(ret));
+ EXPECT_EQ(0, WEXITSTATUS(ret));
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is a memory region
+ // in the memory list that covers the instruction pointer from
+ // the exception record.
+ Minidump minidump(minidump_file);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_NE((unsigned int)0, memory_list->region_count());
+
+ MinidumpContext* context = exception->GetContext();
+ ASSERT_TRUE(context);
+
+ u_int64_t instruction_pointer;
+ switch (context->GetContextCPU()) {
+ case MD_CONTEXT_X86:
+ instruction_pointer = context->GetContextX86()->eip;
+ break;
+ case MD_CONTEXT_AMD64:
+ instruction_pointer = context->GetContextAMD64()->rip;
+ break;
+ case MD_CONTEXT_ARM:
+ instruction_pointer = context->GetContextARM()->iregs[15];
+ break;
+ default:
+ FAIL() << "Unknown context CPU: " << context->GetContextCPU();
+ break;
+ }
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ EXPECT_TRUE(region);
+
+ EXPECT_EQ(kMemorySize, region->GetSize());
+ const u_int8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ u_int8_t prefix_bytes[kOffset];
+ u_int8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)];
+ memset(prefix_bytes, 0, sizeof(prefix_bytes));
+ memset(suffix_bytes, 0, sizeof(suffix_bytes));
+ EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
+ EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0);
+ EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
+ suffix_bytes, sizeof(suffix_bytes)) == 0);
+}
+
+// Test that the memory region around the instruction pointer is
+// bounded correctly on the low end.
+TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
+ // Give the child process a pipe to report back on.
+ int fds[2];
+ ASSERT_EQ(0, pipe(fds));
+
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ const u_int32_t kMemorySize = 256; // bytes
+ const int kOffset = 0;
+ // This crashes with SIGILL on x86/x86-64/arm.
+ const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ close(fds[0]);
+ ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
+ // Get some executable memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ if (!memory)
+ exit(0);
+
+ // Write some instructions that will crash. Put them at the start
+ // of the block of memory, to ensure that the memory bounding
+ // works properly.
+ memcpy(memory + kOffset, instructions, sizeof(instructions));
+
+ // Now execute the instructions, which should crash.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(memory + kOffset);
+ memory_function();
+ // not reached
+ exit(1);
+ }
+ // In the parent process.
+ ASSERT_NE(-1, pid);
+ close(fds[1]);
+
+ // Wait for the background process to return the minidump file.
+ close(fds[1]);
+ char minidump_file[PATH_MAX];
+ ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
+ ASSERT_NE(0, nbytes);
+ // Ensure that minidump file exists and is > 0 bytes.
+ struct stat st;
+ ASSERT_EQ(0, stat(minidump_file, &st));
+ ASSERT_LT(0, st.st_size);
+
+ // Child process should have exited with a zero status.
+ int ret;
+ ASSERT_EQ(pid, waitpid(pid, &ret, 0));
+ EXPECT_NE(0, WIFEXITED(ret));
+ EXPECT_EQ(0, WEXITSTATUS(ret));
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is a memory region
+ // in the memory list that covers the instruction pointer from
+ // the exception record.
+ Minidump minidump(minidump_file);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_NE((unsigned int)0, memory_list->region_count());
+
+ MinidumpContext* context = exception->GetContext();
+ ASSERT_TRUE(context);
+
+ u_int64_t instruction_pointer;
+ switch (context->GetContextCPU()) {
+ case MD_CONTEXT_X86:
+ instruction_pointer = context->GetContextX86()->eip;
+ break;
+ case MD_CONTEXT_AMD64:
+ instruction_pointer = context->GetContextAMD64()->rip;
+ break;
+ case MD_CONTEXT_ARM:
+ instruction_pointer = context->GetContextARM()->iregs[15];
+ break;
+ default:
+ FAIL() << "Unknown context CPU: " << context->GetContextCPU();
+ break;
+ }
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ EXPECT_TRUE(region);
+
+ EXPECT_EQ(kMemorySize / 2, region->GetSize());
+ const u_int8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ u_int8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)];
+ memset(suffix_bytes, 0, sizeof(suffix_bytes));
+ EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0);
+ EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
+ suffix_bytes, sizeof(suffix_bytes)) == 0);
+}
+
+// Test that the memory region around the instruction pointer is
+// bounded correctly on the high end.
+TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
+ // Give the child process a pipe to report back on.
+ int fds[2];
+ ASSERT_EQ(0, pipe(fds));
+
+ // These are defined here so the parent can use them to check the
+ // data from the minidump afterwards.
+ // Use 4k here because the OS will hand out a single page even
+ // if a smaller size is requested, and this test wants to
+ // test the upper bound of the memory range.
+ const u_int32_t kMemorySize = 4096; // bytes
+ // This crashes with SIGILL on x86/x86-64/arm.
+ const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
+ const int kOffset = kMemorySize - sizeof(instructions);
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ close(fds[0]);
+ ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
+ // Get some executable memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ if (!memory)
+ exit(0);
+
+ // Write some instructions that will crash. Put them at the start
+ // of the block of memory, to ensure that the memory bounding
+ // works properly.
+ memcpy(memory + kOffset, instructions, sizeof(instructions));
+
+ // Now execute the instructions, which should crash.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(memory + kOffset);
+ memory_function();
+ // not reached
+ exit(1);
+ }
+ // In the parent process.
+ ASSERT_NE(-1, pid);
+ close(fds[1]);
+
+ // Wait for the background process to return the minidump file.
+ close(fds[1]);
+ char minidump_file[PATH_MAX];
+ ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
+ ASSERT_NE(0, nbytes);
+ // Ensure that minidump file exists and is > 0 bytes.
+ struct stat st;
+ ASSERT_EQ(0, stat(minidump_file, &st));
+ ASSERT_LT(0, st.st_size);
+
+ // Child process should have exited with a zero status.
+ int ret;
+ ASSERT_EQ(pid, waitpid(pid, &ret, 0));
+ EXPECT_NE(0, WIFEXITED(ret));
+ EXPECT_EQ(0, WEXITSTATUS(ret));
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is a memory region
+ // in the memory list that covers the instruction pointer from
+ // the exception record.
+ Minidump minidump(minidump_file);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_NE((unsigned int)0, memory_list->region_count());
+
+ MinidumpContext* context = exception->GetContext();
+ ASSERT_TRUE(context);
+
+ u_int64_t instruction_pointer;
+ switch (context->GetContextCPU()) {
+ case MD_CONTEXT_X86:
+ instruction_pointer = context->GetContextX86()->eip;
+ break;
+ case MD_CONTEXT_AMD64:
+ instruction_pointer = context->GetContextAMD64()->rip;
+ break;
+ case MD_CONTEXT_ARM:
+ instruction_pointer = context->GetContextARM()->iregs[15];
+ break;
+ default:
+ FAIL() << "Unknown context CPU: " << context->GetContextCPU();
+ break;
+ }
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ EXPECT_TRUE(region);
+
+ const size_t kPrefixSize = 128; // bytes
+ EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize());
+ const u_int8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ u_int8_t prefix_bytes[kPrefixSize];
+ memset(prefix_bytes, 0, sizeof(prefix_bytes));
+ EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
+ EXPECT_TRUE(memcmp(bytes + kPrefixSize,
+ instructions, sizeof(instructions)) == 0);
+}
+
+// Ensure that an extra memory block doesn't get added when the
+// instruction pointer is not in mapped memory.
+TEST_F(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) {
+ // Give the child process a pipe to report back on.
+ int fds[2];
+ ASSERT_EQ(0, pipe(fds));
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ close(fds[0]);
+ ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
+ // Try calling a NULL pointer.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(NULL);
+ memory_function();
+ // not reached
+ exit(1);
+ }
+ // In the parent process.
+ ASSERT_NE(-1, pid);
+ close(fds[1]);
+
+ // Wait for the background process to return the minidump file.
+ close(fds[1]);
+ char minidump_file[PATH_MAX];
+ ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
+ ASSERT_NE(0, nbytes);
+ // Ensure that minidump file exists and is > 0 bytes.
+ struct stat st;
+ ASSERT_EQ(0, stat(minidump_file, &st));
+ ASSERT_LT(0, st.st_size);
+
+ // Child process should have exited with a zero status.
+ int ret;
+ ASSERT_EQ(pid, waitpid(pid, &ret, 0));
+ EXPECT_NE(0, WIFEXITED(ret));
+ EXPECT_EQ(0, WEXITSTATUS(ret));
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is only one memory region
+ // in the memory list (the thread memory from the single thread).
+ Minidump minidump(minidump_file);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_EQ((unsigned int)1, memory_list->region_count());
+}
+
}
diff --git a/src/client/mac/tests/testlogging.h b/src/client/mac/tests/testlogging.h
new file mode 100644
index 00000000..c6b6be69
--- /dev/null
+++ b/src/client/mac/tests/testlogging.h
@@ -0,0 +1,9 @@
+// This file exists to override the processor logging for unit tests,
+// since it confuses XCode into thinking unit tests have failed.
+#include <sstream>
+
+namespace google_breakpad {
+extern std::ostringstream info_log;
+}
+
+#define BPLOG_INFO_STREAM google_breakpad::info_log
diff --git a/src/common/linux/memory.h b/src/common/memory.h
index 868c9cc0..a02ac578 100644
--- a/src/common/linux/memory.h
+++ b/src/common/memory.h
@@ -27,15 +27,22 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#ifndef CLIENT_LINUX_HANDLER_MEMORY_H_
-#define CLIENT_LINUX_HANDLER_MEMORY_H_
+#ifndef GOOGLE_BREAKPAD_COMMON_MEMORY_H_
+#define GOOGLE_BREAKPAD_COMMON_MEMORY_H_
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
+#ifdef __APPLE__
+#define sys_mmap mmap
+#define sys_mmap2 mmap
+#define sys_munmap munmap
+#define MAP_ANONYMOUS MAP_ANON
+#else
#include "third_party/lss/linux_syscall_support.h"
+#endif
namespace google_breakpad {
@@ -196,4 +203,4 @@ inline void* operator new(size_t nbytes,
return allocator.Alloc(nbytes);
}
-#endif // CLIENT_LINUX_HANDLER_MEMORY_H_
+#endif // GOOGLE_BREAKPAD_COMMON_MEMORY_H_
diff --git a/src/common/linux/memory_unittest.cc b/src/common/memory_unittest.cc
index 66c83465..8b2ec410 100644
--- a/src/common/linux/memory_unittest.cc
+++ b/src/common/memory_unittest.cc
@@ -27,7 +27,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#include "common/linux/memory.h"
+#include "common/memory.h"
#include "testing/gtest/include/gtest/gtest.h"
using namespace google_breakpad;
@@ -74,7 +74,7 @@ TEST(WastefulVectorTest, Setup) {
TEST(WastefulVectorTest, Simple) {
PageAllocator allocator_;
- wasteful_vector<int> v(&allocator_);
+ wasteful_vector<unsigned> v(&allocator_);
for (unsigned i = 0; i < 256; ++i)
v.push_back(i);