mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2024-11-24 06:35:46 +01:00
Write a window of memory around the instruction pointer from the crashing thread to the minidump on OS X.
R=nealsid at http://breakpad.appspot.com/200001/show git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@699 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
cec12872c4
commit
4621ee0691
@ -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
|
||||
|
22
Makefile.in
22
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
|
||||
|
@ -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"
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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_);
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
9
src/client/mac/tests/testlogging.h
Normal file
9
src/client/mac/tests/testlogging.h
Normal file
@ -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
|
@ -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_
|
@ -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);
|
Loading…
Reference in New Issue
Block a user