Compare commits
4 Commits
linux-dwar
...
processor-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b114d00ad | ||
|
|
eeae662157 | ||
|
|
b8af9814ba | ||
|
|
37e2d8391e |
25
Makefile.am
25
Makefile.am
@@ -62,6 +62,7 @@ src_libbreakpad_la_SOURCES = \
|
||||
src/google_breakpad/processor/minidump_processor.h \
|
||||
src/google_breakpad/processor/process_state.h \
|
||||
src/google_breakpad/processor/source_line_resolver_interface.h \
|
||||
src/google_breakpad/processor/source_line_resolver_module_cache_interface.h \
|
||||
src/google_breakpad/processor/stack_frame.h \
|
||||
src/google_breakpad/processor/stack_frame_cpu.h \
|
||||
src/google_breakpad/processor/stackwalker.h \
|
||||
@@ -76,6 +77,8 @@ src_libbreakpad_la_SOURCES = \
|
||||
src/processor/call_stack.cc \
|
||||
src/processor/contained_range_map-inl.h \
|
||||
src/processor/contained_range_map.h \
|
||||
src/processor/disk_module_cache.cc \
|
||||
src/processor/disk_module_cache.h \
|
||||
src/processor/linked_ptr.h \
|
||||
src/processor/logging.h \
|
||||
src/processor/logging.cc \
|
||||
@@ -117,7 +120,8 @@ check_PROGRAMS = \
|
||||
src/processor/minidump_processor_unittest \
|
||||
src/processor/pathname_stripper_unittest \
|
||||
src/processor/postfix_evaluator_unittest \
|
||||
src/processor/range_map_unittest
|
||||
src/processor/range_map_unittest \
|
||||
src/processor/module_serialize_unittest
|
||||
|
||||
if SELFTEST
|
||||
check_PROGRAMS += \
|
||||
@@ -152,14 +156,7 @@ src_processor_contained_range_map_unittest_LDADD = \
|
||||
src/processor/pathname_stripper.lo
|
||||
|
||||
src_processor_minidump_processor_unittest_SOURCES = \
|
||||
src/processor/minidump_processor_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_minidump_processor_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src/processor/minidump_processor_unittest.cc
|
||||
src_processor_minidump_processor_unittest_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
@@ -192,6 +189,15 @@ src_processor_range_map_unittest_LDADD = \
|
||||
src/processor/logging.lo \
|
||||
src/processor/pathname_stripper.lo
|
||||
|
||||
src_processor_module_serialize_unittest_SOURCES = \
|
||||
src/processor/module_serialize_unittest.cc
|
||||
src_processor_module_serialize_unittest_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
src/processor/call_stack.lo \
|
||||
src/processor/logging.lo \
|
||||
src/processor/pathname_stripper.lo
|
||||
|
||||
src_processor_stackwalker_selftest_SOURCES = \
|
||||
src/processor/stackwalker_selftest.cc
|
||||
src_processor_stackwalker_selftest_LDADD = \
|
||||
@@ -225,6 +231,7 @@ src_processor_minidump_stackwalk_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
src/processor/call_stack.lo \
|
||||
src/processor/disk_module_cache.lo \
|
||||
src/processor/logging.lo \
|
||||
src/processor/minidump.lo \
|
||||
src/processor/minidump_processor.lo \
|
||||
|
||||
148
Makefile.in
148
Makefile.in
@@ -72,7 +72,9 @@ check_PROGRAMS = src/processor/address_map_unittest$(EXEEXT) \
|
||||
src/processor/minidump_processor_unittest$(EXEEXT) \
|
||||
src/processor/pathname_stripper_unittest$(EXEEXT) \
|
||||
src/processor/postfix_evaluator_unittest$(EXEEXT) \
|
||||
src/processor/range_map_unittest$(EXEEXT) $(am__EXEEXT_1)
|
||||
src/processor/range_map_unittest$(EXEEXT) \
|
||||
src/processor/module_serialize_unittest$(EXEEXT) \
|
||||
$(am__EXEEXT_1)
|
||||
@SELFTEST_TRUE@am__append_1 = \
|
||||
@SELFTEST_TRUE@ src/processor/stackwalker_selftest
|
||||
|
||||
@@ -107,8 +109,9 @@ src_libbreakpad_la_LIBADD =
|
||||
am__dirstamp = $(am__leading_dot)dirstamp
|
||||
am_src_libbreakpad_la_OBJECTS = src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
src/processor/call_stack.lo src/processor/logging.lo \
|
||||
src/processor/minidump.lo src/processor/minidump_processor.lo \
|
||||
src/processor/call_stack.lo src/processor/disk_module_cache.lo \
|
||||
src/processor/logging.lo src/processor/minidump.lo \
|
||||
src/processor/minidump_processor.lo \
|
||||
src/processor/pathname_stripper.lo \
|
||||
src/processor/process_state.lo \
|
||||
src/processor/simple_symbol_supplier.lo \
|
||||
@@ -147,9 +150,8 @@ src_processor_minidump_dump_OBJECTS = \
|
||||
src_processor_minidump_dump_DEPENDENCIES = \
|
||||
src/processor/basic_code_modules.lo src/processor/logging.lo \
|
||||
src/processor/minidump.lo src/processor/pathname_stripper.lo
|
||||
am_src_processor_minidump_processor_unittest_OBJECTS = src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.$(OBJEXT) \
|
||||
src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.$(OBJEXT) \
|
||||
src/testing/src/src_processor_minidump_processor_unittest-gmock-all.$(OBJEXT)
|
||||
am_src_processor_minidump_processor_unittest_OBJECTS = \
|
||||
src/processor/minidump_processor_unittest.$(OBJEXT)
|
||||
src_processor_minidump_processor_unittest_OBJECTS = \
|
||||
$(am_src_processor_minidump_processor_unittest_OBJECTS)
|
||||
src_processor_minidump_processor_unittest_DEPENDENCIES = \
|
||||
@@ -170,8 +172,9 @@ src_processor_minidump_stackwalk_OBJECTS = \
|
||||
src_processor_minidump_stackwalk_DEPENDENCIES = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
src/processor/call_stack.lo src/processor/logging.lo \
|
||||
src/processor/minidump.lo src/processor/minidump_processor.lo \
|
||||
src/processor/call_stack.lo src/processor/disk_module_cache.lo \
|
||||
src/processor/logging.lo src/processor/minidump.lo \
|
||||
src/processor/minidump_processor.lo \
|
||||
src/processor/pathname_stripper.lo \
|
||||
src/processor/process_state.lo \
|
||||
src/processor/simple_symbol_supplier.lo \
|
||||
@@ -180,6 +183,15 @@ src_processor_minidump_stackwalk_DEPENDENCIES = \
|
||||
src/processor/stackwalker_ppc.lo \
|
||||
src/processor/stackwalker_sparc.lo \
|
||||
src/processor/stackwalker_x86.lo
|
||||
am_src_processor_module_serialize_unittest_OBJECTS = \
|
||||
src/processor/module_serialize_unittest.$(OBJEXT)
|
||||
src_processor_module_serialize_unittest_OBJECTS = \
|
||||
$(am_src_processor_module_serialize_unittest_OBJECTS)
|
||||
src_processor_module_serialize_unittest_DEPENDENCIES = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
src/processor/call_stack.lo src/processor/logging.lo \
|
||||
src/processor/pathname_stripper.lo
|
||||
am_src_processor_pathname_stripper_unittest_OBJECTS = \
|
||||
src/processor/pathname_stripper_unittest.$(OBJEXT)
|
||||
src_processor_pathname_stripper_unittest_OBJECTS = \
|
||||
@@ -241,6 +253,7 @@ SOURCES = $(src_libbreakpad_la_SOURCES) \
|
||||
$(src_processor_minidump_dump_SOURCES) \
|
||||
$(src_processor_minidump_processor_unittest_SOURCES) \
|
||||
$(src_processor_minidump_stackwalk_SOURCES) \
|
||||
$(src_processor_module_serialize_unittest_SOURCES) \
|
||||
$(src_processor_pathname_stripper_unittest_SOURCES) \
|
||||
$(src_processor_postfix_evaluator_unittest_SOURCES) \
|
||||
$(src_processor_range_map_unittest_SOURCES) \
|
||||
@@ -252,6 +265,7 @@ DIST_SOURCES = $(src_libbreakpad_la_SOURCES) \
|
||||
$(src_processor_minidump_dump_SOURCES) \
|
||||
$(src_processor_minidump_processor_unittest_SOURCES) \
|
||||
$(src_processor_minidump_stackwalk_SOURCES) \
|
||||
$(src_processor_module_serialize_unittest_SOURCES) \
|
||||
$(src_processor_pathname_stripper_unittest_SOURCES) \
|
||||
$(src_processor_postfix_evaluator_unittest_SOURCES) \
|
||||
$(src_processor_range_map_unittest_SOURCES) \
|
||||
@@ -290,7 +304,6 @@ CXXFLAGS = @CXXFLAGS@
|
||||
CYGPATH_W = @CYGPATH_W@
|
||||
DEFS = @DEFS@
|
||||
DEPDIR = @DEPDIR@
|
||||
DSYMUTIL = @DSYMUTIL@
|
||||
ECHO = @ECHO@
|
||||
ECHO_C = @ECHO_C@
|
||||
ECHO_N = @ECHO_N@
|
||||
@@ -314,7 +327,6 @@ LN_S = @LN_S@
|
||||
LTLIBOBJS = @LTLIBOBJS@
|
||||
MAKEINFO = @MAKEINFO@
|
||||
MKDIR_P = @MKDIR_P@
|
||||
NMEDIT = @NMEDIT@
|
||||
OBJEXT = @OBJEXT@
|
||||
PACKAGE = @PACKAGE@
|
||||
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
|
||||
@@ -324,7 +336,6 @@ PACKAGE_TARNAME = @PACKAGE_TARNAME@
|
||||
PACKAGE_VERSION = @PACKAGE_VERSION@
|
||||
PATH_SEPARATOR = @PATH_SEPARATOR@
|
||||
RANLIB = @RANLIB@
|
||||
SED = @SED@
|
||||
SET_MAKE = @SET_MAKE@
|
||||
SHELL = @SHELL@
|
||||
STRIP = @STRIP@
|
||||
@@ -405,6 +416,7 @@ src_libbreakpad_la_SOURCES = \
|
||||
src/google_breakpad/processor/minidump_processor.h \
|
||||
src/google_breakpad/processor/process_state.h \
|
||||
src/google_breakpad/processor/source_line_resolver_interface.h \
|
||||
src/google_breakpad/processor/source_line_resolver_module_cache_interface.h \
|
||||
src/google_breakpad/processor/stack_frame.h \
|
||||
src/google_breakpad/processor/stack_frame_cpu.h \
|
||||
src/google_breakpad/processor/stackwalker.h \
|
||||
@@ -419,6 +431,8 @@ src_libbreakpad_la_SOURCES = \
|
||||
src/processor/call_stack.cc \
|
||||
src/processor/contained_range_map-inl.h \
|
||||
src/processor/contained_range_map.h \
|
||||
src/processor/disk_module_cache.cc \
|
||||
src/processor/disk_module_cache.h \
|
||||
src/processor/linked_ptr.h \
|
||||
src/processor/logging.h \
|
||||
src/processor/logging.cc \
|
||||
@@ -475,15 +489,7 @@ src_processor_contained_range_map_unittest_LDADD = \
|
||||
src/processor/pathname_stripper.lo
|
||||
|
||||
src_processor_minidump_processor_unittest_SOURCES = \
|
||||
src/processor/minidump_processor_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
|
||||
src_processor_minidump_processor_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src/processor/minidump_processor_unittest.cc
|
||||
|
||||
src_processor_minidump_processor_unittest_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
@@ -520,6 +526,16 @@ src_processor_range_map_unittest_LDADD = \
|
||||
src/processor/logging.lo \
|
||||
src/processor/pathname_stripper.lo
|
||||
|
||||
src_processor_module_serialize_unittest_SOURCES = \
|
||||
src/processor/module_serialize_unittest.cc
|
||||
|
||||
src_processor_module_serialize_unittest_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
src/processor/call_stack.lo \
|
||||
src/processor/logging.lo \
|
||||
src/processor/pathname_stripper.lo
|
||||
|
||||
src_processor_stackwalker_selftest_SOURCES = \
|
||||
src/processor/stackwalker_selftest.cc
|
||||
|
||||
@@ -553,6 +569,7 @@ src_processor_minidump_stackwalk_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
src/processor/call_stack.lo \
|
||||
src/processor/disk_module_cache.lo \
|
||||
src/processor/logging.lo \
|
||||
src/processor/minidump.lo \
|
||||
src/processor/minidump_processor.lo \
|
||||
@@ -790,6 +807,8 @@ src/processor/basic_source_line_resolver.lo: \
|
||||
src/processor/$(DEPDIR)/$(am__dirstamp)
|
||||
src/processor/call_stack.lo: src/processor/$(am__dirstamp) \
|
||||
src/processor/$(DEPDIR)/$(am__dirstamp)
|
||||
src/processor/disk_module_cache.lo: src/processor/$(am__dirstamp) \
|
||||
src/processor/$(DEPDIR)/$(am__dirstamp)
|
||||
src/processor/logging.lo: src/processor/$(am__dirstamp) \
|
||||
src/processor/$(DEPDIR)/$(am__dirstamp)
|
||||
src/processor/minidump.lo: src/processor/$(am__dirstamp) \
|
||||
@@ -883,27 +902,9 @@ src/processor/minidump_dump.$(OBJEXT): src/processor/$(am__dirstamp) \
|
||||
src/processor/minidump_dump$(EXEEXT): $(src_processor_minidump_dump_OBJECTS) $(src_processor_minidump_dump_DEPENDENCIES) src/processor/$(am__dirstamp)
|
||||
@rm -f src/processor/minidump_dump$(EXEEXT)
|
||||
$(CXXLINK) $(src_processor_minidump_dump_OBJECTS) $(src_processor_minidump_dump_LDADD) $(LIBS)
|
||||
src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.$(OBJEXT): \
|
||||
src/processor/minidump_processor_unittest.$(OBJEXT): \
|
||||
src/processor/$(am__dirstamp) \
|
||||
src/processor/$(DEPDIR)/$(am__dirstamp)
|
||||
src/testing/gtest/src/$(am__dirstamp):
|
||||
@$(MKDIR_P) src/testing/gtest/src
|
||||
@: > src/testing/gtest/src/$(am__dirstamp)
|
||||
src/testing/gtest/src/$(DEPDIR)/$(am__dirstamp):
|
||||
@$(MKDIR_P) src/testing/gtest/src/$(DEPDIR)
|
||||
@: > src/testing/gtest/src/$(DEPDIR)/$(am__dirstamp)
|
||||
src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.$(OBJEXT): \
|
||||
src/testing/gtest/src/$(am__dirstamp) \
|
||||
src/testing/gtest/src/$(DEPDIR)/$(am__dirstamp)
|
||||
src/testing/src/$(am__dirstamp):
|
||||
@$(MKDIR_P) src/testing/src
|
||||
@: > src/testing/src/$(am__dirstamp)
|
||||
src/testing/src/$(DEPDIR)/$(am__dirstamp):
|
||||
@$(MKDIR_P) src/testing/src/$(DEPDIR)
|
||||
@: > src/testing/src/$(DEPDIR)/$(am__dirstamp)
|
||||
src/testing/src/src_processor_minidump_processor_unittest-gmock-all.$(OBJEXT): \
|
||||
src/testing/src/$(am__dirstamp) \
|
||||
src/testing/src/$(DEPDIR)/$(am__dirstamp)
|
||||
src/processor/minidump_processor_unittest$(EXEEXT): $(src_processor_minidump_processor_unittest_OBJECTS) $(src_processor_minidump_processor_unittest_DEPENDENCIES) src/processor/$(am__dirstamp)
|
||||
@rm -f src/processor/minidump_processor_unittest$(EXEEXT)
|
||||
$(CXXLINK) $(src_processor_minidump_processor_unittest_OBJECTS) $(src_processor_minidump_processor_unittest_LDADD) $(LIBS)
|
||||
@@ -913,6 +914,12 @@ src/processor/minidump_stackwalk.$(OBJEXT): \
|
||||
src/processor/minidump_stackwalk$(EXEEXT): $(src_processor_minidump_stackwalk_OBJECTS) $(src_processor_minidump_stackwalk_DEPENDENCIES) src/processor/$(am__dirstamp)
|
||||
@rm -f src/processor/minidump_stackwalk$(EXEEXT)
|
||||
$(CXXLINK) $(src_processor_minidump_stackwalk_OBJECTS) $(src_processor_minidump_stackwalk_LDADD) $(LIBS)
|
||||
src/processor/module_serialize_unittest.$(OBJEXT): \
|
||||
src/processor/$(am__dirstamp) \
|
||||
src/processor/$(DEPDIR)/$(am__dirstamp)
|
||||
src/processor/module_serialize_unittest$(EXEEXT): $(src_processor_module_serialize_unittest_OBJECTS) $(src_processor_module_serialize_unittest_DEPENDENCIES) src/processor/$(am__dirstamp)
|
||||
@rm -f src/processor/module_serialize_unittest$(EXEEXT)
|
||||
$(CXXLINK) $(src_processor_module_serialize_unittest_OBJECTS) $(src_processor_module_serialize_unittest_LDADD) $(LIBS)
|
||||
src/processor/pathname_stripper_unittest.$(OBJEXT): \
|
||||
src/processor/$(am__dirstamp) \
|
||||
src/processor/$(DEPDIR)/$(am__dirstamp)
|
||||
@@ -949,6 +956,8 @@ mostlyclean-compile:
|
||||
-rm -f src/processor/call_stack.$(OBJEXT)
|
||||
-rm -f src/processor/call_stack.lo
|
||||
-rm -f src/processor/contained_range_map_unittest.$(OBJEXT)
|
||||
-rm -f src/processor/disk_module_cache.$(OBJEXT)
|
||||
-rm -f src/processor/disk_module_cache.lo
|
||||
-rm -f src/processor/logging.$(OBJEXT)
|
||||
-rm -f src/processor/logging.lo
|
||||
-rm -f src/processor/minidump.$(OBJEXT)
|
||||
@@ -956,7 +965,9 @@ mostlyclean-compile:
|
||||
-rm -f src/processor/minidump_dump.$(OBJEXT)
|
||||
-rm -f src/processor/minidump_processor.$(OBJEXT)
|
||||
-rm -f src/processor/minidump_processor.lo
|
||||
-rm -f src/processor/minidump_processor_unittest.$(OBJEXT)
|
||||
-rm -f src/processor/minidump_stackwalk.$(OBJEXT)
|
||||
-rm -f src/processor/module_serialize_unittest.$(OBJEXT)
|
||||
-rm -f src/processor/pathname_stripper.$(OBJEXT)
|
||||
-rm -f src/processor/pathname_stripper.lo
|
||||
-rm -f src/processor/pathname_stripper_unittest.$(OBJEXT)
|
||||
@@ -966,7 +977,6 @@ mostlyclean-compile:
|
||||
-rm -f src/processor/range_map_unittest.$(OBJEXT)
|
||||
-rm -f src/processor/simple_symbol_supplier.$(OBJEXT)
|
||||
-rm -f src/processor/simple_symbol_supplier.lo
|
||||
-rm -f src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.$(OBJEXT)
|
||||
-rm -f src/processor/stackwalker.$(OBJEXT)
|
||||
-rm -f src/processor/stackwalker.lo
|
||||
-rm -f src/processor/stackwalker_amd64.$(OBJEXT)
|
||||
@@ -978,8 +988,6 @@ mostlyclean-compile:
|
||||
-rm -f src/processor/stackwalker_sparc.lo
|
||||
-rm -f src/processor/stackwalker_x86.$(OBJEXT)
|
||||
-rm -f src/processor/stackwalker_x86.lo
|
||||
-rm -f src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.$(OBJEXT)
|
||||
-rm -f src/testing/src/src_processor_minidump_processor_unittest-gmock-all.$(OBJEXT)
|
||||
|
||||
distclean-compile:
|
||||
-rm -f *.tab.c
|
||||
@@ -990,26 +998,26 @@ distclean-compile:
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/basic_source_line_resolver_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/call_stack.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/contained_range_map_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/disk_module_cache.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/logging.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/minidump.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/minidump_dump.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/minidump_processor.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/minidump_processor_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/minidump_stackwalk.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/module_serialize_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/pathname_stripper.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/pathname_stripper_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/postfix_evaluator_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/process_state.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/range_map_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/simple_symbol_supplier.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/src_processor_minidump_processor_unittest-minidump_processor_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/stackwalker.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/stackwalker_amd64.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/stackwalker_ppc.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/stackwalker_selftest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/stackwalker_sparc.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/stackwalker_x86.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/testing/gtest/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gtest-all.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/testing/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gmock-all.Po@am__quote@
|
||||
|
||||
.cc.o:
|
||||
@am__fastdepCXX_TRUE@ depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
|
||||
@@ -1035,48 +1043,6 @@ distclean-compile:
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(LTCXXCOMPILE) -c -o $@ $<
|
||||
|
||||
src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.o: src/processor/minidump_processor_unittest.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.o -MD -MP -MF src/processor/$(DEPDIR)/src_processor_minidump_processor_unittest-minidump_processor_unittest.Tpo -c -o src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.o `test -f 'src/processor/minidump_processor_unittest.cc' || echo '$(srcdir)/'`src/processor/minidump_processor_unittest.cc
|
||||
@am__fastdepCXX_TRUE@ mv -f src/processor/$(DEPDIR)/src_processor_minidump_processor_unittest-minidump_processor_unittest.Tpo src/processor/$(DEPDIR)/src_processor_minidump_processor_unittest-minidump_processor_unittest.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/processor/minidump_processor_unittest.cc' object='src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.o' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.o `test -f 'src/processor/minidump_processor_unittest.cc' || echo '$(srcdir)/'`src/processor/minidump_processor_unittest.cc
|
||||
|
||||
src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.obj: src/processor/minidump_processor_unittest.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.obj -MD -MP -MF src/processor/$(DEPDIR)/src_processor_minidump_processor_unittest-minidump_processor_unittest.Tpo -c -o src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.obj `if test -f 'src/processor/minidump_processor_unittest.cc'; then $(CYGPATH_W) 'src/processor/minidump_processor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/processor/minidump_processor_unittest.cc'; fi`
|
||||
@am__fastdepCXX_TRUE@ mv -f src/processor/$(DEPDIR)/src_processor_minidump_processor_unittest-minidump_processor_unittest.Tpo src/processor/$(DEPDIR)/src_processor_minidump_processor_unittest-minidump_processor_unittest.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/processor/minidump_processor_unittest.cc' object='src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.obj' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/processor/src_processor_minidump_processor_unittest-minidump_processor_unittest.obj `if test -f 'src/processor/minidump_processor_unittest.cc'; then $(CYGPATH_W) 'src/processor/minidump_processor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/processor/minidump_processor_unittest.cc'; fi`
|
||||
|
||||
src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.o: src/testing/gtest/src/gtest-all.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.o -MD -MP -MF src/testing/gtest/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gtest-all.Tpo -c -o src/testing/gtest/src/src_processor_minidump_processor_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@ mv -f src/testing/gtest/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gtest-all.Tpo src/testing/gtest/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gtest-all.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/testing/gtest/src/gtest-all.cc' object='src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.o' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.o `test -f 'src/testing/gtest/src/gtest-all.cc' || echo '$(srcdir)/'`src/testing/gtest/src/gtest-all.cc
|
||||
|
||||
src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.obj: src/testing/gtest/src/gtest-all.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.obj -MD -MP -MF src/testing/gtest/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gtest-all.Tpo -c -o src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.obj `if test -f 'src/testing/gtest/src/gtest-all.cc'; then $(CYGPATH_W) 'src/testing/gtest/src/gtest-all.cc'; else $(CYGPATH_W) '$(srcdir)/src/testing/gtest/src/gtest-all.cc'; fi`
|
||||
@am__fastdepCXX_TRUE@ mv -f src/testing/gtest/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gtest-all.Tpo src/testing/gtest/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gtest-all.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/testing/gtest/src/gtest-all.cc' object='src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.obj' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/testing/gtest/src/src_processor_minidump_processor_unittest-gtest-all.obj `if test -f 'src/testing/gtest/src/gtest-all.cc'; then $(CYGPATH_W) 'src/testing/gtest/src/gtest-all.cc'; else $(CYGPATH_W) '$(srcdir)/src/testing/gtest/src/gtest-all.cc'; fi`
|
||||
|
||||
src/testing/src/src_processor_minidump_processor_unittest-gmock-all.o: src/testing/src/gmock-all.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/testing/src/src_processor_minidump_processor_unittest-gmock-all.o -MD -MP -MF src/testing/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gmock-all.Tpo -c -o src/testing/src/src_processor_minidump_processor_unittest-gmock-all.o `test -f 'src/testing/src/gmock-all.cc' || echo '$(srcdir)/'`src/testing/src/gmock-all.cc
|
||||
@am__fastdepCXX_TRUE@ mv -f src/testing/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gmock-all.Tpo src/testing/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gmock-all.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/testing/src/gmock-all.cc' object='src/testing/src/src_processor_minidump_processor_unittest-gmock-all.o' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/testing/src/src_processor_minidump_processor_unittest-gmock-all.o `test -f 'src/testing/src/gmock-all.cc' || echo '$(srcdir)/'`src/testing/src/gmock-all.cc
|
||||
|
||||
src/testing/src/src_processor_minidump_processor_unittest-gmock-all.obj: src/testing/src/gmock-all.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/testing/src/src_processor_minidump_processor_unittest-gmock-all.obj -MD -MP -MF src/testing/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gmock-all.Tpo -c -o src/testing/src/src_processor_minidump_processor_unittest-gmock-all.obj `if test -f 'src/testing/src/gmock-all.cc'; then $(CYGPATH_W) 'src/testing/src/gmock-all.cc'; else $(CYGPATH_W) '$(srcdir)/src/testing/src/gmock-all.cc'; fi`
|
||||
@am__fastdepCXX_TRUE@ mv -f src/testing/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gmock-all.Tpo src/testing/src/$(DEPDIR)/src_processor_minidump_processor_unittest-gmock-all.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/testing/src/gmock-all.cc' object='src/testing/src/src_processor_minidump_processor_unittest-gmock-all.obj' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_processor_minidump_processor_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/testing/src/src_processor_minidump_processor_unittest-gmock-all.obj `if test -f 'src/testing/src/gmock-all.cc'; then $(CYGPATH_W) 'src/testing/src/gmock-all.cc'; else $(CYGPATH_W) '$(srcdir)/src/testing/src/gmock-all.cc'; fi`
|
||||
|
||||
mostlyclean-libtool:
|
||||
-rm -f *.lo
|
||||
|
||||
@@ -1385,10 +1351,6 @@ distclean-generic:
|
||||
-rm -f src/$(am__dirstamp)
|
||||
-rm -f src/processor/$(DEPDIR)/$(am__dirstamp)
|
||||
-rm -f src/processor/$(am__dirstamp)
|
||||
-rm -f src/testing/gtest/src/$(DEPDIR)/$(am__dirstamp)
|
||||
-rm -f src/testing/gtest/src/$(am__dirstamp)
|
||||
-rm -f src/testing/src/$(DEPDIR)/$(am__dirstamp)
|
||||
-rm -f src/testing/src/$(am__dirstamp)
|
||||
|
||||
maintainer-clean-generic:
|
||||
@echo "This command is intended for maintainers to use"
|
||||
@@ -1401,7 +1363,7 @@ clean-am: clean-binPROGRAMS clean-checkPROGRAMS clean-generic \
|
||||
|
||||
distclean: distclean-am
|
||||
-rm -f $(am__CONFIG_DISTCLEAN_FILES)
|
||||
-rm -rf src/processor/$(DEPDIR) src/testing/gtest/src/$(DEPDIR) src/testing/src/$(DEPDIR)
|
||||
-rm -rf src/processor/$(DEPDIR)
|
||||
-rm -f Makefile
|
||||
distclean-am: clean-am distclean-compile distclean-generic \
|
||||
distclean-hdr distclean-libtool distclean-tags
|
||||
@@ -1437,7 +1399,7 @@ installcheck-am:
|
||||
maintainer-clean: maintainer-clean-am
|
||||
-rm -f $(am__CONFIG_DISTCLEAN_FILES)
|
||||
-rm -rf $(top_srcdir)/autom4te.cache
|
||||
-rm -rf src/processor/$(DEPDIR) src/testing/gtest/src/$(DEPDIR) src/testing/src/$(DEPDIR)
|
||||
-rm -rf src/processor/$(DEPDIR)
|
||||
-rm -f Makefile
|
||||
maintainer-clean-am: distclean-am maintainer-clean-generic
|
||||
|
||||
|
||||
755
aclocal.m4
vendored
755
aclocal.m4
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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 BREAKPAD_GOOGLETEST_INCLUDES_H__
|
||||
#define BREAKPAD_GOOGLETEST_INCLUDES_H__
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "testing/include/gmock/gmock.h"
|
||||
|
||||
#endif // BREAKPAD_GOOGLETEST_INCLUDES_H__
|
||||
@@ -1,3 +0,0 @@
|
||||
MODULE Linux x86 B8CFDE93002D54DA1900A40AA1BD67690 linux-gate.so
|
||||
PUBLIC 400 0 __kernel_vsyscall
|
||||
STACK WIN 4 400 100 1 1 0 0 0 0 0 1
|
||||
@@ -1,3 +0,0 @@
|
||||
MODULE Linux x86 4FBDA58B5A1DF5A379E3CF19A235EA090 linux-gate.so
|
||||
PUBLIC 400 0 __kernel_vsyscall
|
||||
STACK WIN 4 400 200 3 3 0 0 0 0 0 1
|
||||
@@ -1,45 +1,55 @@
|
||||
CXX=g++
|
||||
CC=gcc
|
||||
|
||||
CXXFLAGS=-gstabs+ -I../../../ -I../../../testing/gtest/include -I../../../testing/include -I../../../testing/gtest -D_REENTRANT -m32
|
||||
CFLAGS=$(CXXFLAGS)
|
||||
CXXFLAGS=-gstabs -I../../.. -Wall -D_REENTRANT
|
||||
LDFLAGS=-lpthread
|
||||
|
||||
OBJ_DIR=.
|
||||
BIN_DIR=.
|
||||
|
||||
TEST_CC_SRC=exception_handler_unittest.cc \
|
||||
exception_handler.cc \
|
||||
../../../testing/gtest/src/gtest-all.cc \
|
||||
../../../common/linux/guid_creator.cc \
|
||||
../minidump_writer/minidump_writer.cc \
|
||||
../../minidump_file_writer.cc \
|
||||
../minidump_writer/linux_dumper.cc \
|
||||
../../../testing/gtest/src/gtest_main.cc \
|
||||
../../../common/string_conversion.cc \
|
||||
../minidump_writer/directory_reader_unittest.cc \
|
||||
../minidump_writer/line_reader_unittest.cc \
|
||||
../minidump_writer/linux_dumper_unittest.cc \
|
||||
../minidump_writer/minidump_writer_unittest.cc
|
||||
THREAD_SRC=linux_thread.cc
|
||||
SHARE_SRC=../../minidump_file_writer.cc\
|
||||
../../../common/string_conversion.cc\
|
||||
../../../common/linux/file_id.cc\
|
||||
minidump_generator.cc
|
||||
HANDLER_SRC=exception_handler.cc\
|
||||
../../../common/linux/guid_creator.cc
|
||||
SHARE_C_SRC=../../../common/convert_UTF.c
|
||||
|
||||
TEST_C_SRC = ../../../common/convert_UTF.c
|
||||
THREAD_TEST_SRC=linux_thread_test.cc
|
||||
MINIDUMP_TEST_SRC=minidump_test.cc
|
||||
EXCEPTION_TEST_SRC=exception_handler_test.cc
|
||||
|
||||
TEST_CC_OBJ=$(patsubst %.cc, $(OBJ_DIR)/%.o,$(TEST_CC_SRC))
|
||||
TEST_C_OBJ=$(patsubst %.c, $(OBJ_DIR)/%.o, $(TEST_C_SRC))
|
||||
THREAD_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(THREAD_SRC))
|
||||
SHARE_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(SHARE_SRC))
|
||||
HANDLER_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(HANDLER_SRC))
|
||||
SHARE_C_OBJ=$(patsubst %.c,$(OBJ_DIR)/%.o,$(SHARE_C_SRC)) md5.o
|
||||
THREAD_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(THREAD_TEST_SRC))\
|
||||
$(THREAD_OBJ)
|
||||
MINIDUMP_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(MINIDUMP_TEST_SRC))\
|
||||
$(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ)
|
||||
EXCEPTION_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(EXCEPTION_TEST_SRC))\
|
||||
$(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ) $(HANDLER_SRC)
|
||||
|
||||
LINUX_CLIENT_BIN=$(BIN_DIR)/linux_client_test
|
||||
|
||||
BIN=$(LINUX_CLIENT_BIN)
|
||||
BIN=$(BIN_DIR)/minidump_test\
|
||||
$(BIN_DIR)/linux_thread_test\
|
||||
$(BIN_DIR)/exception_handler_test
|
||||
|
||||
.PHONY:all clean
|
||||
|
||||
check:$(BIN)
|
||||
$(LINUX_CLIENT_BIN)
|
||||
|
||||
all:$(BIN)
|
||||
|
||||
$(BIN_DIR)/linux_client_test:$(TEST_CC_OBJ) $(TEST_C_OBJ)
|
||||
$(BIN_DIR)/linux_thread_test:$(THREAD_TEST_OBJ)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
|
||||
|
||||
$(BIN_DIR)/minidump_test:$(MINIDUMP_TEST_OBJ)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
|
||||
|
||||
$(BIN_DIR)/exception_handler_test:$(EXCEPTION_TEST_OBJ)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
|
||||
|
||||
md5.o:../../../common/md5.c
|
||||
$(CC) $(CXXFLAGS) -c $^
|
||||
|
||||
clean:
|
||||
rm -f $(BIN) $(TEST_CC_OBJ) $(TEST_C_OBJ)
|
||||
rm -f $(BIN) *.o *.dmp
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
@@ -27,87 +29,47 @@
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// The ExceptionHandler object installs signal handlers for a number of
|
||||
// signals. We rely on the signal handler running on the thread which crashed
|
||||
// in order to identify it. This is true of the synchronous signals (SEGV etc),
|
||||
// but not true of ABRT. Thus, if you send ABRT to yourself in a program which
|
||||
// uses ExceptionHandler, you need to use tgkill to direct it to the current
|
||||
// thread.
|
||||
//
|
||||
// The signal flow looks like this:
|
||||
//
|
||||
// SignalHandler (uses a global stack of ExceptionHandler objects to find
|
||||
// | one to handle the signal. If the first rejects it, try
|
||||
// | the second etc...)
|
||||
// V
|
||||
// HandleSignal ----------------------------| (clones a new process which
|
||||
// | | shares an address space with
|
||||
// (wait for cloned | the crashed process. This
|
||||
// process) | allows us to ptrace the crashed
|
||||
// | | process)
|
||||
// V V
|
||||
// (set signal handler to ThreadEntry (static function to bounce
|
||||
// SIG_DFL and rethrow, | back into the object)
|
||||
// killing the crashed |
|
||||
// process) V
|
||||
// DoDump (writes minidump)
|
||||
// |
|
||||
// V
|
||||
// sys_exit
|
||||
//
|
||||
|
||||
// This code is a little fragmented. Different functions of the ExceptionHandler
|
||||
// class run in a number of different contexts. Some of them run in a normal
|
||||
// context and are easy to code, others run in a compromised context and the
|
||||
// restrictions at the top of minidump_writer.cc apply: no libc and use the
|
||||
// alternative malloc. Each function should have comment above it detailing the
|
||||
// context which it runs in.
|
||||
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/signal.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/ucontext.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
#include "common/linux/memory.h"
|
||||
#include "client/linux/minidump_writer//minidump_writer.h"
|
||||
#include "common/linux/guid_creator.h"
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <linux/limits.h>
|
||||
|
||||
// A wrapper for the tgkill syscall: send a signal to a specific thread.
|
||||
static int tgkill(pid_t tgid, pid_t tid, int sig) {
|
||||
syscall(__NR_tgkill, tgid, tid, sig);
|
||||
}
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "common/linux/guid_creator.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// The list of signals which we consider to be crashes. The default action for
|
||||
// all these signals must be Core (see man 7 signal) because we rethrow the
|
||||
// signal after handling it and expect that it'll be fatal.
|
||||
static const int kExceptionSignals[] = {
|
||||
SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, -1
|
||||
// Signals that we are interested.
|
||||
int SigTable[] = {
|
||||
#if defined(SIGSEGV)
|
||||
SIGSEGV,
|
||||
#endif
|
||||
#ifdef SIGABRT
|
||||
SIGABRT,
|
||||
#endif
|
||||
#ifdef SIGFPE
|
||||
SIGFPE,
|
||||
#endif
|
||||
#ifdef SIGILL
|
||||
SIGILL,
|
||||
#endif
|
||||
#ifdef SIGBUS
|
||||
SIGBUS,
|
||||
#endif
|
||||
};
|
||||
|
||||
// We can stack multiple exception handlers. In that case, this is the global
|
||||
// which holds the stack.
|
||||
std::vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL;
|
||||
unsigned ExceptionHandler::handler_stack_index_ = 0;
|
||||
std::vector<ExceptionHandler*> *ExceptionHandler::handler_stack_ = NULL;
|
||||
int ExceptionHandler::handler_stack_index_ = 0;
|
||||
pthread_mutex_t ExceptionHandler::handler_stack_mutex_ =
|
||||
PTHREAD_MUTEX_INITIALIZER;
|
||||
PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
// Runs before crashing: normal context.
|
||||
ExceptionHandler::ExceptionHandler(const std::string &dump_path,
|
||||
ExceptionHandler::ExceptionHandler(const string &dump_path,
|
||||
FilterCallback filter,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context,
|
||||
@@ -116,207 +78,189 @@ ExceptionHandler::ExceptionHandler(const std::string &dump_path,
|
||||
callback_(callback),
|
||||
callback_context_(callback_context),
|
||||
dump_path_(),
|
||||
handler_installed_(install_handler),
|
||||
crash_handler_(NULL) {
|
||||
installed_handler_(install_handler) {
|
||||
set_dump_path(dump_path);
|
||||
|
||||
if (install_handler) {
|
||||
InstallHandlers();
|
||||
|
||||
SetupHandler();
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
if (handler_stack_ == NULL)
|
||||
handler_stack_ = new std::vector<ExceptionHandler *>;
|
||||
handler_stack_->push_back(this);
|
||||
if (handler_stack_ == NULL)
|
||||
handler_stack_ = new std::vector<ExceptionHandler *>;
|
||||
handler_stack_->push_back(this);
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
}
|
||||
}
|
||||
|
||||
// Runs before crashing: normal context.
|
||||
ExceptionHandler::~ExceptionHandler() {
|
||||
UninstallHandlers();
|
||||
TeardownAllHandler();
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
if (handler_stack_->back() == this) {
|
||||
handler_stack_->pop_back();
|
||||
} else {
|
||||
fprintf(stderr, "warning: removing Breakpad handler out of order\n");
|
||||
for (std::vector<ExceptionHandler *>::iterator iterator =
|
||||
handler_stack_->begin();
|
||||
iterator != handler_stack_->end();
|
||||
++iterator) {
|
||||
if (*iterator == this) {
|
||||
handler_stack_->erase(iterator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (handler_stack_->empty()) {
|
||||
// When destroying the last ExceptionHandler that installed a handler,
|
||||
// clean up the handler stack.
|
||||
delete handler_stack_;
|
||||
handler_stack_ = NULL;
|
||||
}
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
}
|
||||
|
||||
// Runs before crashing: normal context.
|
||||
bool ExceptionHandler::InstallHandlers() {
|
||||
// We run the signal handlers on an alternative stack because we might have
|
||||
// crashed because of a stack overflow.
|
||||
bool ExceptionHandler::WriteMinidump() {
|
||||
return InternalWriteMinidump(0, 0, NULL);
|
||||
}
|
||||
|
||||
// We use this value rather than SIGSTKSZ because we would end up overrunning
|
||||
// such a small stack.
|
||||
static const unsigned kSigStackSize = 8192;
|
||||
// static
|
||||
bool ExceptionHandler::WriteMinidump(const string &dump_path,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context) {
|
||||
ExceptionHandler handler(dump_path, NULL, callback,
|
||||
callback_context, false);
|
||||
return handler.InternalWriteMinidump(0, 0, NULL);
|
||||
}
|
||||
|
||||
signal_stack = malloc(kSigStackSize);
|
||||
stack_t stack;
|
||||
memset(&stack, 0, sizeof(stack));
|
||||
stack.ss_sp = signal_stack;
|
||||
stack.ss_size = kSigStackSize;
|
||||
void ExceptionHandler::SetupHandler() {
|
||||
// Signal on a different stack to avoid using the stack
|
||||
// of the crashing thread.
|
||||
struct sigaltstack sig_stack;
|
||||
sig_stack.ss_sp = malloc(MINSIGSTKSZ);
|
||||
if (sig_stack.ss_sp == NULL)
|
||||
return;
|
||||
sig_stack.ss_size = MINSIGSTKSZ;
|
||||
sig_stack.ss_flags = 0;
|
||||
|
||||
if (sigaltstack(&stack, NULL) == -1)
|
||||
if (sigaltstack(&sig_stack, NULL) < 0)
|
||||
return;
|
||||
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i)
|
||||
SetupHandler(SigTable[i]);
|
||||
}
|
||||
|
||||
void ExceptionHandler::SetupHandler(int signo) {
|
||||
struct sigaction act, old_act;
|
||||
act.sa_handler = HandleException;
|
||||
act.sa_flags = SA_ONSTACK;
|
||||
if (sigaction(signo, &act, &old_act) < 0)
|
||||
return;
|
||||
old_handlers_[signo] = old_act.sa_handler;
|
||||
}
|
||||
|
||||
void ExceptionHandler::TeardownHandler(int signo) {
|
||||
if (old_handlers_.find(signo) != old_handlers_.end()) {
|
||||
struct sigaction act;
|
||||
act.sa_handler = old_handlers_[signo];
|
||||
act.sa_flags = 0;
|
||||
sigaction(signo, &act, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ExceptionHandler::TeardownAllHandler() {
|
||||
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) {
|
||||
TeardownHandler(SigTable[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void ExceptionHandler::HandleException(int signo) {
|
||||
// In Linux, the context information about the signal is put on the stack of
|
||||
// the signal handler frame as value parameter. For some reasons, the
|
||||
// prototype of the handler doesn't declare this information as parameter, we
|
||||
// will do it by hand. It is the second parameter above the signal number.
|
||||
// However, if we are being called by another signal handler passing the
|
||||
// signal up the chain, then we may not have this random extra parameter,
|
||||
// so we may have to walk the stack to find it. We do the actual work
|
||||
// on another thread, where it's a little safer, but we want the ebp
|
||||
// from this frame to find it.
|
||||
uintptr_t current_ebp = 0;
|
||||
asm volatile ("movl %%ebp, %0"
|
||||
:"=m"(current_ebp));
|
||||
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
ExceptionHandler *current_handler =
|
||||
handler_stack_->at(handler_stack_->size() - ++handler_stack_index_);
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
|
||||
// Restore original handler.
|
||||
current_handler->TeardownHandler(signo);
|
||||
|
||||
struct sigcontext *sig_ctx = NULL;
|
||||
if (current_handler->InternalWriteMinidump(signo, current_ebp, &sig_ctx)) {
|
||||
// Fully handled this exception, safe to exit.
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
// Exception not fully handled, will call the next handler in stack to
|
||||
// process it.
|
||||
typedef void (*SignalHandler)(int signo, struct sigcontext);
|
||||
SignalHandler old_handler =
|
||||
reinterpret_cast<SignalHandler>(current_handler->old_handlers_[signo]);
|
||||
if (old_handler != NULL && sig_ctx != NULL)
|
||||
old_handler(signo, *sig_ctx);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
current_handler->SetupHandler(signo);
|
||||
--handler_stack_index_;
|
||||
// All the handlers in stack have been invoked to handle the exception,
|
||||
// normally the process should be terminated and should not reach here.
|
||||
// In case we got here, ask the OS to handle it to avoid endless loop,
|
||||
// normally the OS will generate a core and termiate the process. This
|
||||
// may be desired to debug the program.
|
||||
if (handler_stack_index_ == 0)
|
||||
signal(signo, SIG_DFL);
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
}
|
||||
|
||||
bool ExceptionHandler::InternalWriteMinidump(int signo,
|
||||
uintptr_t sighandler_ebp,
|
||||
struct sigcontext **sig_ctx) {
|
||||
if (filter_ && !filter_(callback_context_))
|
||||
return false;
|
||||
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
// mask all exception signals when we're handling one of them.
|
||||
for (unsigned i = 0; kExceptionSignals[i] != -1; ++i)
|
||||
sigaddset(&sa.sa_mask, kExceptionSignals[i]);
|
||||
|
||||
sa.sa_sigaction = SignalHandler;
|
||||
sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||
|
||||
for (unsigned i = 0; kExceptionSignals[i] != -1; ++i) {
|
||||
struct sigaction* old = new struct sigaction;
|
||||
if (sigaction(kExceptionSignals[i], &sa, old) == -1)
|
||||
return false;
|
||||
old_handlers_.push_back(std::make_pair(kExceptionSignals[i], old));
|
||||
}
|
||||
}
|
||||
|
||||
// Runs before crashing: normal context.
|
||||
void ExceptionHandler::UninstallHandlers() {
|
||||
for (unsigned i = 0; i < old_handlers_.size(); ++i) {
|
||||
struct sigaction *action =
|
||||
reinterpret_cast<struct sigaction*>(old_handlers_[i].second);
|
||||
sigaction(old_handlers_[i].first, action, NULL);
|
||||
delete action;
|
||||
}
|
||||
|
||||
old_handlers_.clear();
|
||||
}
|
||||
|
||||
// Runs before crashing: normal context.
|
||||
void ExceptionHandler::UpdateNextID() {
|
||||
GUID guid;
|
||||
bool success = false;;
|
||||
char guid_str[kGUIDStringLength + 1];
|
||||
if (CreateGUID(&guid) && GUIDToString(&guid, guid_str, sizeof(guid_str))) {
|
||||
next_minidump_id_ = guid_str;
|
||||
next_minidump_id_c_ = next_minidump_id_.c_str();
|
||||
|
||||
char minidump_path[PATH_MAX];
|
||||
snprintf(minidump_path, sizeof(minidump_path), "%s/%s.dmp",
|
||||
dump_path_c_,
|
||||
guid_str);
|
||||
|
||||
next_minidump_path_ = minidump_path;
|
||||
next_minidump_path_c_ = next_minidump_path_.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
// Runs on the crashing thread.
|
||||
// static
|
||||
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
|
||||
// All the exception signals are blocked at this point.
|
||||
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
|
||||
if (!handler_stack_->size()) {
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = handler_stack_->size() - 1; i >= 0; --i) {
|
||||
if ((*handler_stack_)[i]->HandleSignal(sig, info, uc)) {
|
||||
// successfully handled: We are in an invalid state since an exception
|
||||
// signal has been delivered. We don't call the exit handlers because
|
||||
// they could end up corrupting on-disk state.
|
||||
break;
|
||||
// Block all the signals we want to process when writting minidump.
|
||||
// We don't want it to be interrupted.
|
||||
sigset_t sig_blocked, sig_old;
|
||||
bool blocked = true;
|
||||
sigfillset(&sig_blocked);
|
||||
for (size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i)
|
||||
sigdelset(&sig_blocked, SigTable[i]);
|
||||
if (sigprocmask(SIG_BLOCK, &sig_blocked, &sig_old) != 0) {
|
||||
blocked = false;
|
||||
fprintf(stderr, "google_breakpad::ExceptionHandler::HandleException: "
|
||||
"failed to block signals.\n");
|
||||
}
|
||||
|
||||
success = minidump_generator_.WriteMinidumpToFile(
|
||||
minidump_path, signo, sighandler_ebp, sig_ctx);
|
||||
|
||||
// Unblock the signals.
|
||||
if (blocked) {
|
||||
sigprocmask(SIG_SETMASK, &sig_old, &sig_old);
|
||||
}
|
||||
|
||||
if (callback_)
|
||||
success = callback_(dump_path_c_, guid_str,
|
||||
callback_context_, success);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
|
||||
// Terminate ourselves with the same signal so that our parent knows that we
|
||||
// crashed. The default action for all the signals which we catch is Core, so
|
||||
// this is the end of us.
|
||||
signal(sig, SIG_DFL);
|
||||
tgkill(getpid(), sys_gettid(), sig);
|
||||
|
||||
// not reached.
|
||||
}
|
||||
|
||||
struct ThreadArgument {
|
||||
pid_t pid; // the crashing process
|
||||
ExceptionHandler* handler;
|
||||
const void* context; // a CrashContext structure
|
||||
size_t context_size;
|
||||
};
|
||||
|
||||
// This is the entry function for the cloned process. We are in a compromised
|
||||
// context here: see the top of the file.
|
||||
// static
|
||||
int ExceptionHandler::ThreadEntry(void *arg) {
|
||||
const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg);
|
||||
return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context,
|
||||
thread_arg->context_size) == false;
|
||||
}
|
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
// Runs on the crashing thread.
|
||||
bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
|
||||
if (filter_ && !filter_(callback_context_))
|
||||
return false;
|
||||
|
||||
// Allow ourselves to be dumped.
|
||||
sys_prctl(PR_SET_DUMPABLE, 1);
|
||||
|
||||
CrashContext context;
|
||||
memcpy(&context.siginfo, info, sizeof(siginfo_t));
|
||||
memcpy(&context.context, uc, sizeof(struct ucontext));
|
||||
memcpy(&context.float_state, ((struct ucontext *)uc)->uc_mcontext.fpregs,
|
||||
sizeof(context.float_state));
|
||||
context.tid = sys_gettid();
|
||||
|
||||
if (crash_handler_ && crash_handler_(&context, sizeof(context),
|
||||
callback_context_))
|
||||
return true;
|
||||
|
||||
static const unsigned kChildStackSize = 8000;
|
||||
PageAllocator allocator;
|
||||
uint8_t* stack = (uint8_t*) allocator.Alloc(kChildStackSize);
|
||||
if (!stack)
|
||||
return false;
|
||||
// clone() needs the top-most address. (scrub just to be safe)
|
||||
stack += kChildStackSize;
|
||||
my_memset(stack - 16, 0, 16);
|
||||
|
||||
ThreadArgument thread_arg;
|
||||
thread_arg.handler = this;
|
||||
thread_arg.pid = getpid();
|
||||
thread_arg.context = &context;
|
||||
thread_arg.context_size = sizeof(context);
|
||||
|
||||
const pid_t child = sys_clone(
|
||||
ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
||||
&thread_arg, NULL, NULL, NULL);
|
||||
int r, status;
|
||||
do {
|
||||
r = sys_waitpid(child, &status, __WALL);
|
||||
} while (r == -1 && errno == EINTR);
|
||||
|
||||
if (r == -1) {
|
||||
static const char msg[] = "ExceptionHandler::HandleSignal: waitpid failed:";
|
||||
sys_write(2, msg, sizeof(msg) - 1);
|
||||
sys_write(2, strerror(errno), strlen(strerror(errno)));
|
||||
sys_write(2, "\n", 1);
|
||||
}
|
||||
|
||||
bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
||||
|
||||
if (callback_)
|
||||
success = callback_(dump_path_c_, next_minidump_id_c_,
|
||||
callback_context_, success);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
// Runs on the cloned process.
|
||||
bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
|
||||
size_t context_size) {
|
||||
return google_breakpad::WriteMinidump(
|
||||
next_minidump_path_c_, crashing_process, context, context_size);
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
@@ -27,16 +29,25 @@
|
||||
// (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_EXCEPTION_HANDLER_H_
|
||||
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
||||
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
||||
|
||||
#include <vector>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <signal.h>
|
||||
#include "client/linux/handler/minidump_generator.h"
|
||||
|
||||
// Context information when exception occured.
|
||||
struct sigcontex;
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::string;
|
||||
|
||||
//
|
||||
// ExceptionHandler
|
||||
//
|
||||
// ExceptionHandler can write a minidump file when an exception occurs,
|
||||
@@ -61,6 +72,7 @@ namespace google_breakpad {
|
||||
//
|
||||
// Caller should try to make the callbacks as crash-friendly as possible,
|
||||
// it should avoid use heap memory allocation as much as possible.
|
||||
//
|
||||
class ExceptionHandler {
|
||||
public:
|
||||
// A callback function to run before Breakpad performs any substantial
|
||||
@@ -95,15 +107,6 @@ class ExceptionHandler {
|
||||
void *context,
|
||||
bool succeeded);
|
||||
|
||||
// In certain cases, a user may wish to handle the generation of the minidump
|
||||
// themselves. In this case, they can install a handler callback which is
|
||||
// called when a crash has occured. If this function returns true, no other
|
||||
// processing of occurs and the process will shortly be crashed. If this
|
||||
// returns false, the normal processing continues.
|
||||
typedef bool (*HandlerCallback)(const void* crash_context,
|
||||
size_t crash_context_size,
|
||||
void* context);
|
||||
|
||||
// Creates a new ExceptionHandler instance to handle writing minidumps.
|
||||
// Before writing a minidump, the optional filter callback will be called.
|
||||
// Its return value determines whether or not Breakpad should write a
|
||||
@@ -112,22 +115,17 @@ class ExceptionHandler {
|
||||
// If install_handler is true, then a minidump will be written whenever
|
||||
// an unhandled exception occurs. If it is false, minidumps will only
|
||||
// be written when WriteMinidump is called.
|
||||
ExceptionHandler(const std::string &dump_path,
|
||||
ExceptionHandler(const string &dump_path,
|
||||
FilterCallback filter, MinidumpCallback callback,
|
||||
void *callback_context,
|
||||
bool install_handler);
|
||||
~ExceptionHandler();
|
||||
|
||||
// Get and set the minidump path.
|
||||
std::string dump_path() const { return dump_path_; }
|
||||
void set_dump_path(const std::string &dump_path) {
|
||||
string dump_path() const { return dump_path_; }
|
||||
void set_dump_path(const string &dump_path) {
|
||||
dump_path_ = dump_path;
|
||||
dump_path_c_ = dump_path_.c_str();
|
||||
UpdateNextID();
|
||||
}
|
||||
|
||||
void set_crash_handler(HandlerCallback callback) {
|
||||
crash_handler_ = callback;
|
||||
}
|
||||
|
||||
// Writes a minidump immediately. This can be used to capture the
|
||||
@@ -136,63 +134,68 @@ class ExceptionHandler {
|
||||
|
||||
// Convenience form of WriteMinidump which does not require an
|
||||
// ExceptionHandler instance.
|
||||
static bool WriteMinidump(const std::string &dump_path,
|
||||
static bool WriteMinidump(const string &dump_path,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context);
|
||||
|
||||
// This structure is passed to minidump_writer.h:WriteMinidump via an opaque
|
||||
// blob. It shouldn't be needed in any user code.
|
||||
struct CrashContext {
|
||||
siginfo_t siginfo;
|
||||
pid_t tid; // the crashing thread.
|
||||
struct ucontext context;
|
||||
struct _libc_fpstate float_state;
|
||||
};
|
||||
private:
|
||||
// Setup crash handler.
|
||||
void SetupHandler();
|
||||
// Setup signal handler for a signal.
|
||||
void SetupHandler(int signo);
|
||||
// Teardown the handler for a signal.
|
||||
void TeardownHandler(int signo);
|
||||
// Teardown all handlers.
|
||||
void TeardownAllHandler();
|
||||
|
||||
// Signal handler.
|
||||
static void HandleException(int signo);
|
||||
|
||||
// If called from a signal handler, sighandler_ebp is the ebp of
|
||||
// that signal handler's frame, and sig_ctx is an out parameter
|
||||
// that will be set to point at the sigcontext that was placed
|
||||
// on the stack by the kernel. You can pass zero and NULL
|
||||
// for the second and third parameters if you are not calling
|
||||
// this from a signal handler.
|
||||
bool InternalWriteMinidump(int signo, uintptr_t sighandler_ebp,
|
||||
struct sigcontext **sig_ctx);
|
||||
|
||||
private:
|
||||
bool InstallHandlers();
|
||||
void UninstallHandlers();
|
||||
void PreresolveSymbols();
|
||||
FilterCallback filter_;
|
||||
MinidumpCallback callback_;
|
||||
void *callback_context_;
|
||||
|
||||
void UpdateNextID();
|
||||
static void SignalHandler(int sig, siginfo_t* info, void* uc);
|
||||
bool HandleSignal(int sig, siginfo_t* info, void* uc);
|
||||
static int ThreadEntry(void* arg);
|
||||
bool DoDump(pid_t crashing_process, const void* context,
|
||||
size_t context_size);
|
||||
// The directory in which a minidump will be written, set by the dump_path
|
||||
// argument to the constructor, or set_dump_path.
|
||||
string dump_path_;
|
||||
// C style dump path. Keep this when setting dump path, since calling
|
||||
// c_str() of std::string when crashing may not be safe.
|
||||
const char *dump_path_c_;
|
||||
|
||||
const FilterCallback filter_;
|
||||
const MinidumpCallback callback_;
|
||||
void* const callback_context_;
|
||||
// True if the ExceptionHandler installed an unhandled exception filter
|
||||
// when created (with an install_handler parameter set to true).
|
||||
bool installed_handler_;
|
||||
|
||||
std::string dump_path_;
|
||||
std::string next_minidump_path_;
|
||||
std::string next_minidump_id_;
|
||||
|
||||
// Pointers to C-string representations of the above. These are set
|
||||
// when the above are set so we can avoid calling c_str during
|
||||
// an exception.
|
||||
const char* dump_path_c_;
|
||||
const char* next_minidump_path_c_;
|
||||
const char* next_minidump_id_c_;
|
||||
|
||||
const bool handler_installed_;
|
||||
void* signal_stack; // the handler stack.
|
||||
HandlerCallback crash_handler_;
|
||||
// Keep the previous handlers for the signal.
|
||||
typedef void (*sighandler_t)(int);
|
||||
std::map<int, sighandler_t> old_handlers_;
|
||||
|
||||
// The global exception handler stack. This is need becuase there may exist
|
||||
// multiple ExceptionHandler instances in a process. Each will have itself
|
||||
// registered in this stack.
|
||||
static std::vector<ExceptionHandler*> *handler_stack_;
|
||||
static std::vector<ExceptionHandler *> *handler_stack_;
|
||||
// The index of the handler that should handle the next exception.
|
||||
static unsigned handler_stack_index_;
|
||||
static int handler_stack_index_;
|
||||
static pthread_mutex_t handler_stack_mutex_;
|
||||
|
||||
// A vector of the old signal handlers. The void* is a pointer to a newly
|
||||
// allocated sigaction structure to avoid pulling in too many includes.
|
||||
std::vector<std::pair<int, void *> > old_handlers_;
|
||||
// The minidump generator.
|
||||
MinidumpGenerator minidump_generator_;
|
||||
|
||||
// disallow copy ctor and operator=
|
||||
explicit ExceptionHandler(const ExceptionHandler &);
|
||||
void operator=(const ExceptionHandler &);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||
#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H__
|
||||
|
||||
124
src/client/linux/handler/exception_handler_test.cc
Normal file
124
src/client/linux/handler/exception_handler_test.cc
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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 <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/handler/linux_thread.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
// Thread use this to see if it should stop working.
|
||||
static bool should_exit = false;
|
||||
|
||||
static int foo2(int arg) {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
/*DDDebug*/printf("%s:%d\n", __FUNCTION__, __LINE__);
|
||||
int c = 0xcccccccc;
|
||||
fprintf(stderr, "Thread trying to crash: %x\n", getpid());
|
||||
c = *reinterpret_cast<int *>(0x5);
|
||||
return c;
|
||||
}
|
||||
|
||||
static int foo(int arg) {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
int b = 0xbbbbbbbb;
|
||||
b = foo2(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
static void *thread_crash(void *) {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
int a = 0xaaaaaaaa;
|
||||
sleep(1);
|
||||
a = foo(a);
|
||||
printf("%x\n", a);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *thread_main(void *) {
|
||||
while (!should_exit)
|
||||
sleep(1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void CreateCrashThread() {
|
||||
pthread_t h;
|
||||
pthread_create(&h, NULL, thread_crash, NULL);
|
||||
pthread_detach(h);
|
||||
}
|
||||
|
||||
// Create working threads.
|
||||
static void CreateThread(int num) {
|
||||
pthread_t h;
|
||||
for (int i = 0; i < num; ++i) {
|
||||
pthread_create(&h, NULL, thread_main, NULL);
|
||||
pthread_detach(h);
|
||||
}
|
||||
}
|
||||
|
||||
// Callback when minidump written.
|
||||
static bool MinidumpCallback(const char *dump_path,
|
||||
const char *minidump_id,
|
||||
void *context,
|
||||
bool succeeded) {
|
||||
int index = reinterpret_cast<int>(context);
|
||||
printf("%d %s: %s is dumped\n", index, __FUNCTION__, minidump_id);
|
||||
if (index == 0) {
|
||||
should_exit = true;
|
||||
return true;
|
||||
}
|
||||
// Don't process it.
|
||||
return false;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int handler_index = 0;
|
||||
ExceptionHandler handler_ignore(".", NULL, MinidumpCallback,
|
||||
(void*)handler_index, true);
|
||||
++handler_index;
|
||||
ExceptionHandler handler_process(".", NULL, MinidumpCallback,
|
||||
(void*)handler_index, true);
|
||||
CreateCrashThread();
|
||||
CreateThread(10);
|
||||
|
||||
while (true)
|
||||
sleep(1);
|
||||
should_exit = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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 <string>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include "client/linux/handler//exception_handler.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
// This provides a wrapper around system calls which may be
|
||||
// interrupted by a signal and return EINTR. See man 7 signal.
|
||||
#define HANDLE_EINTR(x) ({ \
|
||||
typeof(x) __eintr_result__; \
|
||||
do { \
|
||||
__eintr_result__ = x; \
|
||||
} while (__eintr_result__ == -1 && errno == EINTR); \
|
||||
__eintr_result__;\
|
||||
})
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
static void sigchld_handler(int signo) { }
|
||||
|
||||
class ExceptionHandlerTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() {
|
||||
// We need to be able to wait for children, so SIGCHLD cannot be SIG_IGN.
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = sigchld_handler;
|
||||
ASSERT_NE(sigaction(SIGCHLD, &sa, &old_action), -1);
|
||||
}
|
||||
|
||||
void TearDown() {
|
||||
sigaction(SIGCHLD, &old_action, NULL);
|
||||
}
|
||||
|
||||
struct sigaction old_action;
|
||||
};
|
||||
|
||||
TEST(ExceptionHandlerTest, Simple) {
|
||||
ExceptionHandler handler("/tmp", NULL, NULL, NULL, true);
|
||||
}
|
||||
|
||||
static bool DoneCallback(const char* dump_path,
|
||||
const char* minidump_id,
|
||||
void* context,
|
||||
bool succeeded) {
|
||||
if (!succeeded)
|
||||
return succeeded;
|
||||
|
||||
int fd = (int) context;
|
||||
uint32_t len = my_strlen(minidump_id);
|
||||
HANDLE_EINTR(sys_write(fd, &len, sizeof(len)));
|
||||
HANDLE_EINTR(sys_write(fd, minidump_id, len));
|
||||
sys_close(fd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(ExceptionHandlerTest, ChildCrash) {
|
||||
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);
|
||||
*reinterpret_cast<int*>(NULL) = 0;
|
||||
}
|
||||
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)), sizeof(len));
|
||||
ASSERT_LT(len, 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);
|
||||
unlink(minidump_filename.c_str());
|
||||
}
|
||||
|
||||
static const unsigned kControlMsgSize =
|
||||
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred));
|
||||
|
||||
static bool
|
||||
CrashHandler(const void* crash_context, size_t crash_context_size,
|
||||
void* context) {
|
||||
const int fd = (int) context;
|
||||
int fds[2];
|
||||
pipe(fds);
|
||||
|
||||
struct kernel_msghdr msg = {0};
|
||||
struct kernel_iovec iov;
|
||||
iov.iov_base = const_cast<void*>(crash_context);
|
||||
iov.iov_len = crash_context_size;
|
||||
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
char cmsg[kControlMsgSize];
|
||||
memset(cmsg, 0, kControlMsgSize);
|
||||
msg.msg_control = cmsg;
|
||||
msg.msg_controllen = sizeof(cmsg);
|
||||
|
||||
struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg);
|
||||
hdr->cmsg_level = SOL_SOCKET;
|
||||
hdr->cmsg_type = SCM_RIGHTS;
|
||||
hdr->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
*((int*) CMSG_DATA(hdr)) = fds[1];
|
||||
hdr = CMSG_NXTHDR((struct msghdr*) &msg, hdr);
|
||||
hdr->cmsg_level = SOL_SOCKET;
|
||||
hdr->cmsg_type = SCM_CREDENTIALS;
|
||||
hdr->cmsg_len = CMSG_LEN(sizeof(struct ucred));
|
||||
struct ucred *cred = reinterpret_cast<struct ucred*>(CMSG_DATA(hdr));
|
||||
cred->uid = getuid();
|
||||
cred->gid = getgid();
|
||||
cred->pid = getpid();
|
||||
|
||||
HANDLE_EINTR(sys_sendmsg(fd, &msg, 0));
|
||||
sys_close(fds[1]);
|
||||
|
||||
char b;
|
||||
HANDLE_EINTR(sys_read(fds[0], &b, 1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(ExceptionHandlerTest, ExternalDumper) {
|
||||
int fds[2];
|
||||
ASSERT_NE(socketpair(AF_UNIX, SOCK_DGRAM, 0, fds), -1);
|
||||
static const int on = 1;
|
||||
setsockopt(fds[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
|
||||
setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler("/tmp", NULL, NULL, (void*) fds[1], true);
|
||||
handler.set_crash_handler(CrashHandler);
|
||||
*reinterpret_cast<int*>(NULL) = 0;
|
||||
}
|
||||
|
||||
close(fds[1]);
|
||||
struct msghdr msg = {0};
|
||||
struct iovec iov;
|
||||
static const unsigned kCrashContextSize =
|
||||
sizeof(ExceptionHandler::CrashContext);
|
||||
char context[kCrashContextSize];
|
||||
char control[kControlMsgSize];
|
||||
iov.iov_base = context;
|
||||
iov.iov_len = kCrashContextSize;
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = kControlMsgSize;
|
||||
|
||||
const ssize_t n = HANDLE_EINTR(recvmsg(fds[0], &msg, 0));
|
||||
ASSERT_EQ(n, kCrashContextSize);
|
||||
ASSERT_EQ(msg.msg_controllen, kControlMsgSize);
|
||||
ASSERT_EQ(msg.msg_flags, 0);
|
||||
|
||||
pid_t crashing_pid = -1;
|
||||
int signal_fd = -1;
|
||||
for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr;
|
||||
hdr = CMSG_NXTHDR(&msg, hdr)) {
|
||||
if (hdr->cmsg_level != SOL_SOCKET)
|
||||
continue;
|
||||
if (hdr->cmsg_type == SCM_RIGHTS) {
|
||||
const unsigned len = hdr->cmsg_len -
|
||||
(((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr);
|
||||
ASSERT_EQ(len, sizeof(int));
|
||||
signal_fd = *((int *) CMSG_DATA(hdr));
|
||||
} else if (hdr->cmsg_type == SCM_CREDENTIALS) {
|
||||
const struct ucred *cred =
|
||||
reinterpret_cast<struct ucred*>(CMSG_DATA(hdr));
|
||||
crashing_pid = cred->pid;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_NE(crashing_pid, -1);
|
||||
ASSERT_NE(signal_fd, -1);
|
||||
|
||||
char templ[] = "/tmp/exception-handler-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
ASSERT_TRUE(WriteMinidump(templ, crashing_pid, context,
|
||||
kCrashContextSize));
|
||||
static const char b = 0;
|
||||
HANDLE_EINTR(write(signal_fd, &b, 1));
|
||||
|
||||
int status;
|
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
ASSERT_EQ(WTERMSIG(status), SIGSEGV);
|
||||
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(templ, &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
unlink(templ);
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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 <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
|
||||
#include "client/linux/handler/linux_thread.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
// This unamed namespace contains helper function.
|
||||
namespace {
|
||||
|
||||
// Context information for the callbacks when validating address by listing
|
||||
// modules.
|
||||
struct AddressValidatingContext {
|
||||
uintptr_t address;
|
||||
bool is_mapped;
|
||||
|
||||
AddressValidatingContext() : address(0UL), is_mapped(false) {
|
||||
}
|
||||
};
|
||||
|
||||
// Convert from string to int.
|
||||
bool LocalAtoi(char *s, int *r) {
|
||||
assert(s != NULL);
|
||||
assert(r != NULL);
|
||||
char *endptr = NULL;
|
||||
int ret = strtol(s, &endptr, 10);
|
||||
if (endptr == s)
|
||||
return false;
|
||||
*r = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fill the proc path of a thread given its id.
|
||||
void FillProcPath(int pid, char *path, int path_size) {
|
||||
char pid_str[32];
|
||||
snprintf(pid_str, sizeof(pid_str), "%d", pid);
|
||||
snprintf(path, path_size, "/proc/%s/", pid_str);
|
||||
}
|
||||
|
||||
// Read thread info from /proc/$pid/status.
|
||||
bool ReadThreadInfo(int pid, ThreadInfo *info) {
|
||||
assert(info != NULL);
|
||||
char status_path[80];
|
||||
// Max size we want to read from status file.
|
||||
static const int kStatusMaxSize = 1024;
|
||||
char status_content[kStatusMaxSize];
|
||||
|
||||
FillProcPath(pid, status_path, sizeof(status_path));
|
||||
strcat(status_path, "status");
|
||||
int fd = open(status_path, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
int num_read = read(fd, status_content, kStatusMaxSize - 1);
|
||||
if (num_read < 0) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
close(fd);
|
||||
status_content[num_read] = '\0';
|
||||
|
||||
char *tgid_start = strstr(status_content, "Tgid:");
|
||||
if (tgid_start)
|
||||
sscanf(tgid_start, "Tgid:\t%d\n", &(info->tgid));
|
||||
else
|
||||
// tgid not supported by kernel??
|
||||
info->tgid = 0;
|
||||
|
||||
tgid_start = strstr(status_content, "Pid:");
|
||||
if (tgid_start) {
|
||||
sscanf(tgid_start, "Pid:\t%d\n" "PPid:\t%d\n", &(info->pid),
|
||||
&(info->ppid));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Callback invoked for each mapped module.
|
||||
// It use the module's adderss range to validate the address.
|
||||
bool IsAddressInModuleCallback(const ModuleInfo &module_info,
|
||||
void *context) {
|
||||
AddressValidatingContext *addr =
|
||||
reinterpret_cast<AddressValidatingContext *>(context);
|
||||
addr->is_mapped = ((addr->address >= module_info.start_addr) &&
|
||||
(addr->address <= module_info.start_addr +
|
||||
module_info.size));
|
||||
return !addr->is_mapped;
|
||||
}
|
||||
|
||||
#if defined(__i386__) && !defined(NO_FRAME_POINTER)
|
||||
void *GetNextFrame(void **last_ebp) {
|
||||
void *sp = *last_ebp;
|
||||
if ((unsigned long)sp == (unsigned long)last_ebp)
|
||||
return NULL;
|
||||
if ((unsigned long)sp & (sizeof(void *) - 1))
|
||||
return NULL;
|
||||
if ((unsigned long)sp - (unsigned long)last_ebp > 100000)
|
||||
return NULL;
|
||||
return sp;
|
||||
}
|
||||
#else
|
||||
void *GetNextFrame(void **last_ebp) {
|
||||
return reinterpret_cast<void*>(last_ebp);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Suspend a thread by attaching to it.
|
||||
bool SuspendThread(int pid, void *context) {
|
||||
// This may fail if the thread has just died or debugged.
|
||||
errno = 0;
|
||||
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
|
||||
errno != 0) {
|
||||
return false;
|
||||
}
|
||||
while (waitpid(pid, NULL, __WALL) < 0) {
|
||||
if (errno != EINTR) {
|
||||
ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Resume a thread by detaching from it.
|
||||
bool ResumeThread(int pid, void *context) {
|
||||
return ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
|
||||
}
|
||||
|
||||
// Callback to get the thread information.
|
||||
// Will be called for each thread found.
|
||||
bool ThreadInfoCallback(int pid, void *context) {
|
||||
CallbackParam<ThreadCallback> *thread_callback =
|
||||
reinterpret_cast<CallbackParam<ThreadCallback> *>(context);
|
||||
ThreadInfo thread_info;
|
||||
if (ReadThreadInfo(pid, &thread_info) && thread_callback) {
|
||||
// Invoke callback from caller.
|
||||
return (thread_callback->call_back)(thread_info, thread_callback->context);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
LinuxThread::LinuxThread(int pid) : pid_(pid) , threads_suspened_(false) {
|
||||
}
|
||||
|
||||
LinuxThread::~LinuxThread() {
|
||||
if (threads_suspened_)
|
||||
ResumeAllThreads();
|
||||
}
|
||||
|
||||
int LinuxThread::SuspendAllThreads() {
|
||||
CallbackParam<PidCallback> callback_param(SuspendThread, NULL);
|
||||
int thread_count = 0;
|
||||
if ((thread_count = IterateProcSelfTask(pid_, &callback_param)) > 0)
|
||||
threads_suspened_ = true;
|
||||
return thread_count;
|
||||
}
|
||||
|
||||
void LinuxThread::ResumeAllThreads() const {
|
||||
CallbackParam<PidCallback> callback_param(ResumeThread, NULL);
|
||||
IterateProcSelfTask(pid_, &callback_param);
|
||||
}
|
||||
|
||||
int LinuxThread::GetThreadCount() const {
|
||||
return IterateProcSelfTask(pid_, NULL);
|
||||
}
|
||||
|
||||
int LinuxThread::ListThreads(
|
||||
CallbackParam<ThreadCallback> *thread_callback_param) const {
|
||||
CallbackParam<PidCallback> callback_param(ThreadInfoCallback,
|
||||
thread_callback_param);
|
||||
return IterateProcSelfTask(pid_, &callback_param);
|
||||
}
|
||||
|
||||
bool LinuxThread::GetRegisters(int pid, user_regs_struct *regs) const {
|
||||
assert(regs);
|
||||
return (regs != NULL &&
|
||||
(ptrace(PTRACE_GETREGS, pid, NULL, regs) == 0) &&
|
||||
errno == 0);
|
||||
}
|
||||
|
||||
// Get the floating-point registers of a thread.
|
||||
// The caller must get the thread pid by ListThreads.
|
||||
bool LinuxThread::GetFPRegisters(int pid, user_fpregs_struct *regs) const {
|
||||
assert(regs);
|
||||
return (regs != NULL &&
|
||||
(ptrace(PTRACE_GETREGS, pid, NULL, regs) ==0) &&
|
||||
errno == 0);
|
||||
}
|
||||
|
||||
bool LinuxThread::GetFPXRegisters(int pid, user_fpxregs_struct *regs) const {
|
||||
assert(regs);
|
||||
return (regs != NULL &&
|
||||
(ptrace(PTRACE_GETFPREGS, pid, NULL, regs) != 0) &&
|
||||
errno == 0);
|
||||
}
|
||||
|
||||
bool LinuxThread::GetDebugRegisters(int pid, DebugRegs *regs) const {
|
||||
assert(regs);
|
||||
|
||||
#define GET_DR(name, num)\
|
||||
name->dr##num = ptrace(PTRACE_PEEKUSER, pid,\
|
||||
offsetof(struct user, u_debugreg[num]), NULL)
|
||||
GET_DR(regs, 0);
|
||||
GET_DR(regs, 1);
|
||||
GET_DR(regs, 2);
|
||||
GET_DR(regs, 3);
|
||||
GET_DR(regs, 4);
|
||||
GET_DR(regs, 5);
|
||||
GET_DR(regs, 6);
|
||||
GET_DR(regs, 7);
|
||||
return true;
|
||||
}
|
||||
|
||||
int LinuxThread::GetThreadStackDump(uintptr_t current_ebp,
|
||||
uintptr_t current_esp,
|
||||
void *buf,
|
||||
int buf_size) const {
|
||||
assert(buf);
|
||||
assert(buf_size > 0);
|
||||
|
||||
uintptr_t stack_bottom = GetThreadStackBottom(current_ebp);
|
||||
int size = stack_bottom - current_esp;
|
||||
size = buf_size > size ? size : buf_size;
|
||||
if (size > 0)
|
||||
memcpy(buf, reinterpret_cast<void*>(current_esp), size);
|
||||
return size;
|
||||
}
|
||||
|
||||
// Get the stack bottom of a thread by stack walking. It works
|
||||
// unless the stack has been corrupted or the frame pointer has been omited.
|
||||
// This is just a temporary solution before we get better ideas about how
|
||||
// this can be done.
|
||||
//
|
||||
// We will check each frame address by checking into module maps.
|
||||
// TODO(liuli): Improve it.
|
||||
uintptr_t LinuxThread::GetThreadStackBottom(uintptr_t current_ebp) const {
|
||||
void **sp = reinterpret_cast<void **>(current_ebp);
|
||||
void **previous_sp = sp;
|
||||
while (sp && IsAddressMapped((uintptr_t)sp)) {
|
||||
previous_sp = sp;
|
||||
sp = reinterpret_cast<void **>(GetNextFrame(sp));
|
||||
}
|
||||
return (uintptr_t)previous_sp;
|
||||
}
|
||||
|
||||
int LinuxThread::GetModuleCount() const {
|
||||
return ListModules(NULL);
|
||||
}
|
||||
|
||||
int LinuxThread::ListModules(
|
||||
CallbackParam<ModuleCallback> *callback_param) const {
|
||||
char line[512];
|
||||
const char *maps_path = "/proc/self/maps";
|
||||
|
||||
int module_count = 0;
|
||||
FILE *fp = fopen(maps_path, "r");
|
||||
if (fp == NULL)
|
||||
return -1;
|
||||
|
||||
uintptr_t start_addr;
|
||||
uintptr_t end_addr;
|
||||
while (fgets(line, sizeof(line), fp) != NULL) {
|
||||
if (sscanf(line, "%x-%x", &start_addr, &end_addr) == 2) {
|
||||
ModuleInfo module;
|
||||
memset(&module, 0, sizeof(module));
|
||||
module.start_addr = start_addr;
|
||||
module.size = end_addr - start_addr;
|
||||
char *name = NULL;
|
||||
assert(module.size > 0);
|
||||
// Only copy name if the name is a valid path name.
|
||||
if ((name = strchr(line, '/')) != NULL) {
|
||||
// Get rid of the last '\n' in line
|
||||
char *last_return = strchr(line, '\n');
|
||||
if (last_return != NULL)
|
||||
*last_return = '\0';
|
||||
// Keep a space for the ending 0.
|
||||
strncpy(module.name, name, sizeof(module.name) - 1);
|
||||
++module_count;
|
||||
}
|
||||
if (callback_param &&
|
||||
!(callback_param->call_back(module, callback_param->context)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
return module_count;
|
||||
}
|
||||
|
||||
// Parse /proc/$pid/tasks to list all the threads of the process identified by
|
||||
// pid.
|
||||
int LinuxThread::IterateProcSelfTask(int pid,
|
||||
CallbackParam<PidCallback> *callback_param) const {
|
||||
char task_path[80];
|
||||
FillProcPath(pid, task_path, sizeof(task_path));
|
||||
strcat(task_path, "task");
|
||||
|
||||
DIR *dir = opendir(task_path);
|
||||
if (dir == NULL)
|
||||
return -1;
|
||||
|
||||
int pid_number = 0;
|
||||
// Record the last pid we've found. This is used for duplicated thread
|
||||
// removal. Duplicated thread information can be found in /proc/$pid/tasks.
|
||||
int last_pid = -1;
|
||||
struct dirent *entry = NULL;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(entry->d_name, ".") &&
|
||||
strcmp(entry->d_name, "..")) {
|
||||
int tpid = 0;
|
||||
if (LocalAtoi(entry->d_name, &tpid) &&
|
||||
last_pid != tpid) {
|
||||
last_pid = tpid;
|
||||
++pid_number;
|
||||
// Invoke the callback.
|
||||
if (callback_param &&
|
||||
!(callback_param->call_back)(tpid, callback_param->context))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
return pid_number;
|
||||
}
|
||||
|
||||
// Check if the address is a valid virtual address.
|
||||
// If the address is in any of the mapped modules, we take it as valid.
|
||||
// Otherwise it is invalid.
|
||||
bool LinuxThread::IsAddressMapped(uintptr_t address) const {
|
||||
AddressValidatingContext addr;
|
||||
addr.address = address;
|
||||
CallbackParam<ModuleCallback> callback_param(IsAddressInModuleCallback,
|
||||
&addr);
|
||||
ListModules(&callback_param);
|
||||
return addr.is_mapped;
|
||||
}
|
||||
|
||||
bool LinuxThread::FindSigContext(uintptr_t sighandler_ebp,
|
||||
struct sigcontext **sig_ctx) {
|
||||
uintptr_t previous_ebp;
|
||||
const int MAX_STACK_DEPTH = 10;
|
||||
int depth_counter = 0;
|
||||
|
||||
do {
|
||||
// We're looking for a |struct sigcontext| as the second parameter
|
||||
// to a signal handler function call. Luckily, the sigcontext
|
||||
// has an ebp member which should match the ebp pointed to
|
||||
// by the ebp of the signal handler frame.
|
||||
previous_ebp = reinterpret_cast<uintptr_t>(GetNextFrame(
|
||||
reinterpret_cast<void**>(sighandler_ebp)));
|
||||
// The stack looks like this:
|
||||
// | previous ebp | previous eip | first param | second param |,
|
||||
// so we need to offset by 3 to get to the second parameter.
|
||||
*sig_ctx = reinterpret_cast<struct sigcontext*>(sighandler_ebp +
|
||||
3 * sizeof(uintptr_t));
|
||||
sighandler_ebp = previous_ebp;
|
||||
depth_counter++;
|
||||
} while(previous_ebp != (*sig_ctx)->ebp && sighandler_ebp != 0 &&
|
||||
IsAddressMapped(sighandler_ebp) && depth_counter < MAX_STACK_DEPTH);
|
||||
|
||||
return previous_ebp == (*sig_ctx)->ebp && previous_ebp != 0;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
204
src/client/linux/handler/linux_thread.h
Normal file
204
src/client/linux/handler/linux_thread.h
Normal file
@@ -0,0 +1,204 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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_LINUX_THREAD_H__
|
||||
#define CLIENT_LINUX_HANDLER_LINUX_THREAD_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Max module path name length.
|
||||
#define kMaxModuleNameLength 256
|
||||
|
||||
// Holding information about a thread in the process.
|
||||
struct ThreadInfo {
|
||||
// Id of the thread group.
|
||||
int tgid;
|
||||
// Id of the thread.
|
||||
int pid;
|
||||
// Id of the parent process.
|
||||
int ppid;
|
||||
};
|
||||
|
||||
// Holding infomaton about a module in the process.
|
||||
struct ModuleInfo {
|
||||
char name[kMaxModuleNameLength];
|
||||
uintptr_t start_addr;
|
||||
int size;
|
||||
};
|
||||
|
||||
// Holding debug registers.
|
||||
struct DebugRegs {
|
||||
int dr0;
|
||||
int dr1;
|
||||
int dr2;
|
||||
int dr3;
|
||||
int dr4;
|
||||
int dr5;
|
||||
int dr6;
|
||||
int dr7;
|
||||
};
|
||||
|
||||
// A callback to run when got a thread in the process.
|
||||
// Return true will go on to the next thread while return false will stop the
|
||||
// iteration.
|
||||
typedef bool (*ThreadCallback)(const ThreadInfo &thread_info, void *context);
|
||||
|
||||
// A callback to run when a new module is found in the process.
|
||||
// Return true will go on to the next module while return false will stop the
|
||||
// iteration.
|
||||
typedef bool (*ModuleCallback)(const ModuleInfo &module_info, void *context);
|
||||
|
||||
// Holding the callback information.
|
||||
template<class CallbackFunc>
|
||||
struct CallbackParam {
|
||||
// Callback function address.
|
||||
CallbackFunc call_back;
|
||||
// Callback context;
|
||||
void *context;
|
||||
|
||||
CallbackParam() : call_back(NULL), context(NULL) {
|
||||
}
|
||||
|
||||
CallbackParam(CallbackFunc func, void *func_context) :
|
||||
call_back(func), context(func_context) {
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//
|
||||
// LinuxThread
|
||||
//
|
||||
// Provides handy support for operation on linux threads.
|
||||
// It uses ptrace to get thread registers. Since ptrace only works in a
|
||||
// different process other than the one being ptraced, user of this class
|
||||
// should create another process before using the class.
|
||||
//
|
||||
// The process should be created in the following way:
|
||||
// int cloned_pid = clone(ProcessEntryFunction, stack_address,
|
||||
// CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
||||
// (void*)&arguments);
|
||||
// waitpid(cloned_pid, NULL, __WALL);
|
||||
//
|
||||
// If CLONE_VM is not used, GetThreadStackBottom, GetThreadStackDump
|
||||
// will not work since it just use memcpy to get the stack dump.
|
||||
//
|
||||
class LinuxThread {
|
||||
public:
|
||||
// Create a LinuxThread instance to list all the threads in a process.
|
||||
explicit LinuxThread(int pid);
|
||||
~LinuxThread();
|
||||
|
||||
// Stop all the threads in the process.
|
||||
// Return the number of stopped threads in the process.
|
||||
// Return -1 means failed to stop threads.
|
||||
int SuspendAllThreads();
|
||||
|
||||
// Resume all the suspended threads.
|
||||
void ResumeAllThreads() const;
|
||||
|
||||
// Get the count of threads in the process.
|
||||
// Return -1 means error.
|
||||
int GetThreadCount() const;
|
||||
|
||||
// List the threads of process.
|
||||
// Whenever there is a thread found, the callback will be invoked to process
|
||||
// the information.
|
||||
// Return number of threads listed.
|
||||
int ListThreads(CallbackParam<ThreadCallback> *thread_callback_param) const;
|
||||
|
||||
// Get the general purpose registers of a thread.
|
||||
// The caller must get the thread pid by ListThreads.
|
||||
bool GetRegisters(int pid, user_regs_struct *regs) const;
|
||||
|
||||
// Get the floating-point registers of a thread.
|
||||
// The caller must get the thread pid by ListThreads.
|
||||
bool GetFPRegisters(int pid, user_fpregs_struct *regs) const;
|
||||
|
||||
// Get all the extended floating-point registers. May not work on all
|
||||
// machines.
|
||||
// The caller must get the thread pid by ListThreads.
|
||||
bool GetFPXRegisters(int pid, user_fpxregs_struct *regs) const;
|
||||
|
||||
// Get the debug registers.
|
||||
// The caller must get the thread pid by ListThreads.
|
||||
bool GetDebugRegisters(int pid, DebugRegs *regs) const;
|
||||
|
||||
// Get the stack memory dump.
|
||||
int GetThreadStackDump(uintptr_t current_ebp,
|
||||
uintptr_t current_esp,
|
||||
void *buf,
|
||||
int buf_size) const;
|
||||
|
||||
// Get the module count of the current process.
|
||||
int GetModuleCount() const;
|
||||
|
||||
// Get the mapped modules in the address space.
|
||||
// Whenever a module is found, the callback will be invoked to process the
|
||||
// information.
|
||||
// Return how may modules are found.
|
||||
int ListModules(CallbackParam<ModuleCallback> *callback_param) const;
|
||||
|
||||
// Get the bottom of the stack from ebp.
|
||||
uintptr_t GetThreadStackBottom(uintptr_t current_ebp) const;
|
||||
|
||||
// Finds a sigcontext on the stack given the ebp of our signal handler.
|
||||
bool FindSigContext(uintptr_t sighandler_ebp, struct sigcontext **sig_ctx);
|
||||
|
||||
private:
|
||||
// This callback will run when a new thread has been found.
|
||||
typedef bool (*PidCallback)(int pid, void *context);
|
||||
|
||||
// Read thread information from /proc/$pid/task.
|
||||
// Whenever a thread has been found, and callback will be invoked with
|
||||
// the pid of the thread.
|
||||
// Return number of threads found.
|
||||
// Return -1 means the directory doesn't exist.
|
||||
int IterateProcSelfTask(int pid,
|
||||
CallbackParam<PidCallback> *callback_param) const;
|
||||
|
||||
// Check if the address is a valid virtual address.
|
||||
bool IsAddressMapped(uintptr_t address) const;
|
||||
|
||||
private:
|
||||
// The pid of the process we are listing threads.
|
||||
int pid_;
|
||||
|
||||
// Mark if we have suspended the threads.
|
||||
bool threads_suspened_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_LINUX_THREAD_H__
|
||||
224
src/client/linux/handler/linux_thread_test.cc
Normal file
224
src/client/linux/handler/linux_thread_test.cc
Normal file
@@ -0,0 +1,224 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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 <pthread.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "client/linux/handler/linux_thread.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
// Thread use this to see if it should stop working.
|
||||
static bool should_exit = false;
|
||||
|
||||
static void foo2(int *a) {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
int c = 0xcccccccc;
|
||||
c = c;
|
||||
while (!should_exit)
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
static void foo() {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
int a = 0xaaaaaaaa;
|
||||
foo2(&a);
|
||||
}
|
||||
|
||||
static void *thread_main(void *) {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
int b = 0xbbbbbbbb;
|
||||
b = b;
|
||||
while (!should_exit) {
|
||||
foo();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void CreateThreads(int num) {
|
||||
pthread_t handle;
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (0 != pthread_create(&handle, NULL, thread_main, NULL))
|
||||
fprintf(stderr, "Failed to create thread.\n");
|
||||
else
|
||||
pthread_detach(handle);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ProcessOneModule(const struct ModuleInfo &module_info,
|
||||
void *context) {
|
||||
printf("0x%x[%8d] %s\n", module_info.start_addr, module_info.size,
|
||||
module_info.name);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ProcessOneThread(const struct ThreadInfo &thread_info,
|
||||
void *context) {
|
||||
printf("\n\nPID: %d, TGID: %d, PPID: %d\n",
|
||||
thread_info.pid,
|
||||
thread_info.tgid,
|
||||
thread_info.ppid);
|
||||
|
||||
struct user_regs_struct regs;
|
||||
struct user_fpregs_struct fp_regs;
|
||||
struct user_fpxregs_struct fpx_regs;
|
||||
struct DebugRegs dbg_regs;
|
||||
|
||||
LinuxThread *threads = reinterpret_cast<LinuxThread *>(context);
|
||||
memset(®s, 0, sizeof(regs));
|
||||
if (threads->GetRegisters(thread_info.pid, ®s)) {
|
||||
printf(" gs = 0x%lx\n", regs.xgs);
|
||||
printf(" fs = 0x%lx\n", regs.xfs);
|
||||
printf(" es = 0x%lx\n", regs.xes);
|
||||
printf(" ds = 0x%lx\n", regs.xds);
|
||||
printf(" edi = 0x%lx\n", regs.edi);
|
||||
printf(" esi = 0x%lx\n", regs.esi);
|
||||
printf(" ebx = 0x%lx\n", regs.ebx);
|
||||
printf(" edx = 0x%lx\n", regs.edx);
|
||||
printf(" ecx = 0x%lx\n", regs.ecx);
|
||||
printf(" eax = 0x%lx\n", regs.eax);
|
||||
printf(" ebp = 0x%lx\n", regs.ebp);
|
||||
printf(" eip = 0x%lx\n", regs.eip);
|
||||
printf(" cs = 0x%lx\n", regs.xcs);
|
||||
printf(" eflags = 0x%lx\n", regs.eflags);
|
||||
printf(" esp = 0x%lx\n", regs.esp);
|
||||
printf(" ss = 0x%lx\n", regs.xss);
|
||||
} else {
|
||||
fprintf(stderr, "ERROR: Failed to get general purpose registers\n");
|
||||
}
|
||||
memset(&fp_regs, 0, sizeof(fp_regs));
|
||||
if (threads->GetFPRegisters(thread_info.pid, &fp_regs)) {
|
||||
printf("\n Floating point registers:\n");
|
||||
printf(" fctl = 0x%lx\n", fp_regs.cwd);
|
||||
printf(" fstat = 0x%lx\n", fp_regs.swd);
|
||||
printf(" ftag = 0x%lx\n", fp_regs.twd);
|
||||
printf(" fioff = 0x%lx\n", fp_regs.fip);
|
||||
printf(" fiseg = 0x%lx\n", fp_regs.fcs);
|
||||
printf(" fooff = 0x%lx\n", fp_regs.foo);
|
||||
printf(" foseg = 0x%lx\n", fp_regs.fos);
|
||||
int st_space_size = sizeof(fp_regs.st_space) / sizeof(fp_regs.st_space[0]);
|
||||
printf(" st_space[%2d] = 0x", st_space_size);
|
||||
for (int i = 0; i < st_space_size; ++i)
|
||||
printf("%02lx", fp_regs.st_space[i]);
|
||||
printf("\n");
|
||||
} else {
|
||||
fprintf(stderr, "ERROR: Failed to get floating-point registers\n");
|
||||
}
|
||||
memset(&fpx_regs, 0, sizeof(fpx_regs));
|
||||
if (threads->GetFPXRegisters(thread_info.pid, &fpx_regs)) {
|
||||
printf("\n Extended floating point registers:\n");
|
||||
printf(" fctl = 0x%x\n", fpx_regs.cwd);
|
||||
printf(" fstat = 0x%x\n", fpx_regs.swd);
|
||||
printf(" ftag = 0x%x\n", fpx_regs.twd);
|
||||
printf(" fioff = 0x%lx\n", fpx_regs.fip);
|
||||
printf(" fiseg = 0x%lx\n", fpx_regs.fcs);
|
||||
printf(" fooff = 0x%lx\n", fpx_regs.foo);
|
||||
printf(" foseg = 0x%lx\n", fpx_regs.fos);
|
||||
printf(" fop = 0x%x\n", fpx_regs.fop);
|
||||
printf(" mxcsr = 0x%lx\n", fpx_regs.mxcsr);
|
||||
int space_size = sizeof(fpx_regs.st_space) / sizeof(fpx_regs.st_space[0]);
|
||||
printf(" st_space[%2d] = 0x", space_size);
|
||||
for (int i = 0; i < space_size; ++i)
|
||||
printf("%02lx", fpx_regs.st_space[i]);
|
||||
printf("\n");
|
||||
space_size = sizeof(fpx_regs.xmm_space) / sizeof(fpx_regs.xmm_space[0]);
|
||||
printf(" xmm_space[%2d] = 0x", space_size);
|
||||
for (int i = 0; i < space_size; ++i)
|
||||
printf("%02lx", fpx_regs.xmm_space[i]);
|
||||
printf("\n");
|
||||
}
|
||||
if (threads->GetDebugRegisters(thread_info.pid, &dbg_regs)) {
|
||||
printf("\n Debug registers:\n");
|
||||
printf(" dr0 = 0x%x\n", dbg_regs.dr0);
|
||||
printf(" dr1 = 0x%x\n", dbg_regs.dr1);
|
||||
printf(" dr2 = 0x%x\n", dbg_regs.dr2);
|
||||
printf(" dr3 = 0x%x\n", dbg_regs.dr3);
|
||||
printf(" dr4 = 0x%x\n", dbg_regs.dr4);
|
||||
printf(" dr5 = 0x%x\n", dbg_regs.dr5);
|
||||
printf(" dr6 = 0x%x\n", dbg_regs.dr6);
|
||||
printf(" dr7 = 0x%x\n", dbg_regs.dr7);
|
||||
printf("\n");
|
||||
}
|
||||
if (regs.esp != 0) {
|
||||
// Print the stack content.
|
||||
int size = 1024 * 2;
|
||||
char *buf = new char[size];
|
||||
size = threads->GetThreadStackDump(regs.ebp,
|
||||
regs.esp,
|
||||
(void*)buf, size);
|
||||
printf(" Stack content: = 0x");
|
||||
size /= sizeof(unsigned long);
|
||||
unsigned long *p_buf = (unsigned long *)(buf);
|
||||
for (int i = 0; i < size; i += 1)
|
||||
printf("%.8lx ", p_buf[i]);
|
||||
delete []buf;
|
||||
printf("\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int PrintAllThreads(void *argument) {
|
||||
int pid = (int)argument;
|
||||
|
||||
LinuxThread threads(pid);
|
||||
int total_thread = threads.SuspendAllThreads();
|
||||
printf("There are %d threads in the process: %d\n", total_thread, pid);
|
||||
int total_module = threads.GetModuleCount();
|
||||
printf("There are %d modules in the process: %d\n", total_module, pid);
|
||||
CallbackParam<ModuleCallback> module_callback(ProcessOneModule, &threads);
|
||||
threads.ListModules(&module_callback);
|
||||
CallbackParam<ThreadCallback> thread_callback(ProcessOneThread, &threads);
|
||||
threads.ListThreads(&thread_callback);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int pid = getpid();
|
||||
printf("Main thread is %d\n", pid);
|
||||
CreateThreads(1);
|
||||
// Create stack for the process.
|
||||
char *stack = new char[1024 * 100];
|
||||
int cloned_pid = clone(PrintAllThreads, stack + 1024 * 100,
|
||||
CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
||||
(void*)getpid());
|
||||
waitpid(cloned_pid, NULL, __WALL);
|
||||
should_exit = true;
|
||||
printf("Test finished.\n");
|
||||
|
||||
delete []stack;
|
||||
return 0;
|
||||
}
|
||||
815
src/client/linux/handler/minidump_generator.cc
Normal file
815
src/client/linux/handler/minidump_generator.cc
Normal file
@@ -0,0 +1,815 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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 <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/linux/file_id.h"
|
||||
#include "client/linux/handler/linux_thread.h"
|
||||
#include "client/minidump_file_writer.h"
|
||||
#include "client/minidump_file_writer-inl.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
#include "client/linux/handler/minidump_generator.h"
|
||||
|
||||
#ifndef CLONE_UNTRACED
|
||||
#define CLONE_UNTRACED 0x00800000
|
||||
#endif
|
||||
|
||||
// This unnamed namespace contains helper functions.
|
||||
namespace {
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
// Argument for the writer function.
|
||||
struct WriterArgument {
|
||||
MinidumpFileWriter *minidump_writer;
|
||||
|
||||
// Context for the callback.
|
||||
void *version_context;
|
||||
|
||||
// Pid of the thread who called WriteMinidumpToFile
|
||||
int requester_pid;
|
||||
|
||||
// The stack bottom of the thread which caused the dump.
|
||||
// Mainly used to find the thread id of the crashed thread since signal
|
||||
// handler may not be called in the thread who caused it.
|
||||
uintptr_t crashed_stack_bottom;
|
||||
|
||||
// Pid of the crashing thread.
|
||||
int crashed_pid;
|
||||
|
||||
// Signal number when crash happed. Can be 0 if this is a requested dump.
|
||||
int signo;
|
||||
|
||||
// The ebp of the signal handler frame. Can be zero if this
|
||||
// is a requested dump.
|
||||
uintptr_t sighandler_ebp;
|
||||
|
||||
// Signal context when crash happed. Can be NULL if this is a requested dump.
|
||||
// This is actually an out parameter, but it will be filled in at the start
|
||||
// of the writer thread.
|
||||
struct sigcontext *sig_ctx;
|
||||
|
||||
// Used to get information about the threads.
|
||||
LinuxThread *thread_lister;
|
||||
};
|
||||
|
||||
// Holding context information for the callback of finding the crashing thread.
|
||||
struct FindCrashThreadContext {
|
||||
const LinuxThread *thread_lister;
|
||||
uintptr_t crashing_stack_bottom;
|
||||
int crashing_thread_pid;
|
||||
|
||||
FindCrashThreadContext() :
|
||||
thread_lister(NULL),
|
||||
crashing_stack_bottom(0UL),
|
||||
crashing_thread_pid(-1) {
|
||||
}
|
||||
};
|
||||
|
||||
// Callback for list threads.
|
||||
// It will compare the stack bottom of the provided thread with the stack
|
||||
// bottom of the crashed thread, it they are eqaul, this is thread is the one
|
||||
// who crashed.
|
||||
bool IsThreadCrashedCallback(const ThreadInfo &thread_info, void *context) {
|
||||
FindCrashThreadContext *crashing_context =
|
||||
static_cast<FindCrashThreadContext *>(context);
|
||||
const LinuxThread *thread_lister = crashing_context->thread_lister;
|
||||
struct user_regs_struct regs;
|
||||
if (thread_lister->GetRegisters(thread_info.pid, ®s)) {
|
||||
uintptr_t last_ebp = regs.ebp;
|
||||
uintptr_t stack_bottom = thread_lister->GetThreadStackBottom(last_ebp);
|
||||
if (stack_bottom > last_ebp &&
|
||||
stack_bottom == crashing_context->crashing_stack_bottom) {
|
||||
// Got it. Stop iteration.
|
||||
crashing_context->crashing_thread_pid = thread_info.pid;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find the crashing thread id.
|
||||
// This is done based on stack bottom comparing.
|
||||
int FindCrashingThread(uintptr_t crashing_stack_bottom,
|
||||
int requester_pid,
|
||||
const LinuxThread *thread_lister) {
|
||||
FindCrashThreadContext context;
|
||||
context.thread_lister = thread_lister;
|
||||
context.crashing_stack_bottom = crashing_stack_bottom;
|
||||
CallbackParam<ThreadCallback> callback_param(IsThreadCrashedCallback,
|
||||
&context);
|
||||
thread_lister->ListThreads(&callback_param);
|
||||
return context.crashing_thread_pid;
|
||||
}
|
||||
|
||||
// Write the thread stack info minidump.
|
||||
bool WriteThreadStack(uintptr_t last_ebp,
|
||||
uintptr_t last_esp,
|
||||
const LinuxThread *thread_lister,
|
||||
UntypedMDRVA *memory,
|
||||
MDMemoryDescriptor *loc) {
|
||||
// Maximum stack size for a thread.
|
||||
uintptr_t stack_bottom = thread_lister->GetThreadStackBottom(last_ebp);
|
||||
if (stack_bottom > last_esp) {
|
||||
int size = stack_bottom - last_esp;
|
||||
if (size > 0) {
|
||||
if (!memory->Allocate(size))
|
||||
return false;
|
||||
memory->Copy(reinterpret_cast<void*>(last_esp), size);
|
||||
loc->start_of_memory_range = 0 | last_esp;
|
||||
loc->memory = memory->location();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write CPU context based on signal context.
|
||||
bool WriteContext(MDRawContextX86 *context, const struct sigcontext *sig_ctx,
|
||||
const DebugRegs *debug_regs) {
|
||||
assert(sig_ctx != NULL);
|
||||
context->context_flags = MD_CONTEXT_X86_FULL;
|
||||
context->gs = sig_ctx->gs;
|
||||
context->fs = sig_ctx->fs;
|
||||
context->es = sig_ctx->es;
|
||||
context->ds = sig_ctx->ds;
|
||||
context->cs = sig_ctx->cs;
|
||||
context->ss = sig_ctx->ss;
|
||||
context->edi = sig_ctx->edi;
|
||||
context->esi = sig_ctx->esi;
|
||||
context->ebp = sig_ctx->ebp;
|
||||
context->esp = sig_ctx->esp;
|
||||
context->ebx = sig_ctx->ebx;
|
||||
context->edx = sig_ctx->edx;
|
||||
context->ecx = sig_ctx->ecx;
|
||||
context->eax = sig_ctx->eax;
|
||||
context->eip = sig_ctx->eip;
|
||||
context->eflags = sig_ctx->eflags;
|
||||
if (sig_ctx->fpstate != NULL) {
|
||||
context->context_flags = MD_CONTEXT_X86_FULL |
|
||||
MD_CONTEXT_X86_FLOATING_POINT;
|
||||
context->float_save.control_word = sig_ctx->fpstate->cw;
|
||||
context->float_save.status_word = sig_ctx->fpstate->sw;
|
||||
context->float_save.tag_word = sig_ctx->fpstate->tag;
|
||||
context->float_save.error_offset = sig_ctx->fpstate->ipoff;
|
||||
context->float_save.error_selector = sig_ctx->fpstate->cssel;
|
||||
context->float_save.data_offset = sig_ctx->fpstate->dataoff;
|
||||
context->float_save.data_selector = sig_ctx->fpstate->datasel;
|
||||
memcpy(context->float_save.register_area, sig_ctx->fpstate->_st,
|
||||
sizeof(context->float_save.register_area));
|
||||
}
|
||||
|
||||
if (debug_regs != NULL) {
|
||||
context->context_flags |= MD_CONTEXT_X86_DEBUG_REGISTERS;
|
||||
context->dr0 = debug_regs->dr0;
|
||||
context->dr1 = debug_regs->dr1;
|
||||
context->dr2 = debug_regs->dr2;
|
||||
context->dr3 = debug_regs->dr3;
|
||||
context->dr6 = debug_regs->dr6;
|
||||
context->dr7 = debug_regs->dr7;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write CPU context based on provided registers.
|
||||
bool WriteContext(MDRawContextX86 *context,
|
||||
const struct user_regs_struct *regs,
|
||||
const struct user_fpregs_struct *fp_regs,
|
||||
const DebugRegs *dbg_regs) {
|
||||
if (!context || !regs)
|
||||
return false;
|
||||
|
||||
context->context_flags = MD_CONTEXT_X86_FULL;
|
||||
|
||||
context->cs = regs->xcs;
|
||||
context->ds = regs->xds;
|
||||
context->es = regs->xes;
|
||||
context->fs = regs->xfs;
|
||||
context->gs = regs->xgs;
|
||||
context->ss = regs->xss;
|
||||
context->edi = regs->edi;
|
||||
context->esi = regs->esi;
|
||||
context->ebx = regs->ebx;
|
||||
context->edx = regs->edx;
|
||||
context->ecx = regs->ecx;
|
||||
context->eax = regs->eax;
|
||||
context->ebp = regs->ebp;
|
||||
context->eip = regs->eip;
|
||||
context->esp = regs->esp;
|
||||
context->eflags = regs->eflags;
|
||||
|
||||
if (dbg_regs != NULL) {
|
||||
context->context_flags |= MD_CONTEXT_X86_DEBUG_REGISTERS;
|
||||
context->dr0 = dbg_regs->dr0;
|
||||
context->dr1 = dbg_regs->dr1;
|
||||
context->dr2 = dbg_regs->dr2;
|
||||
context->dr3 = dbg_regs->dr3;
|
||||
context->dr6 = dbg_regs->dr6;
|
||||
context->dr7 = dbg_regs->dr7;
|
||||
}
|
||||
|
||||
if (fp_regs != NULL) {
|
||||
context->context_flags |= MD_CONTEXT_X86_FLOATING_POINT;
|
||||
context->float_save.control_word = fp_regs->cwd;
|
||||
context->float_save.status_word = fp_regs->swd;
|
||||
context->float_save.tag_word = fp_regs->twd;
|
||||
context->float_save.error_offset = fp_regs->fip;
|
||||
context->float_save.error_selector = fp_regs->fcs;
|
||||
context->float_save.data_offset = fp_regs->foo;
|
||||
context->float_save.data_selector = fp_regs->fos;
|
||||
context->float_save.data_selector = fp_regs->fos;
|
||||
|
||||
memcpy(context->float_save.register_area, fp_regs->st_space,
|
||||
sizeof(context->float_save.register_area));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write information about a crashed thread.
|
||||
// When a thread crash, kernel will write something on the stack for processing
|
||||
// signal. This makes the current stack not reliable, and our stack walker
|
||||
// won't figure out the whole call stack for this. So we write the stack at the
|
||||
// time of the crash into the minidump file, not the current stack.
|
||||
bool WriteCrashedThreadStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
const ThreadInfo &thread_info,
|
||||
MDRawThread *thread) {
|
||||
assert(writer_args->sig_ctx != NULL);
|
||||
|
||||
thread->thread_id = thread_info.pid;
|
||||
|
||||
UntypedMDRVA memory(minidump_writer);
|
||||
if (!WriteThreadStack(writer_args->sig_ctx->ebp,
|
||||
writer_args->sig_ctx->esp,
|
||||
writer_args->thread_lister,
|
||||
&memory,
|
||||
&thread->stack))
|
||||
return false;
|
||||
|
||||
TypedMDRVA<MDRawContextX86> context(minidump_writer);
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
thread->thread_context = context.location();
|
||||
memset(context.get(), 0, sizeof(MDRawContextX86));
|
||||
return WriteContext(context.get(), writer_args->sig_ctx, NULL);
|
||||
}
|
||||
|
||||
// Write information about a thread.
|
||||
// This function only processes thread running normally at the crash.
|
||||
bool WriteThreadStream(MinidumpFileWriter *minidump_writer,
|
||||
const LinuxThread *thread_lister,
|
||||
const ThreadInfo &thread_info,
|
||||
MDRawThread *thread) {
|
||||
thread->thread_id = thread_info.pid;
|
||||
|
||||
struct user_regs_struct regs;
|
||||
memset(®s, 0, sizeof(regs));
|
||||
if (!thread_lister->GetRegisters(thread_info.pid, ®s)) {
|
||||
perror(NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
UntypedMDRVA memory(minidump_writer);
|
||||
if (!WriteThreadStack(regs.ebp,
|
||||
regs.esp,
|
||||
thread_lister,
|
||||
&memory,
|
||||
&thread->stack))
|
||||
return false;
|
||||
|
||||
struct user_fpregs_struct fp_regs;
|
||||
DebugRegs dbg_regs;
|
||||
memset(&fp_regs, 0, sizeof(fp_regs));
|
||||
// Get all the registers.
|
||||
thread_lister->GetFPRegisters(thread_info.pid, &fp_regs);
|
||||
thread_lister->GetDebugRegisters(thread_info.pid, &dbg_regs);
|
||||
|
||||
// Write context
|
||||
TypedMDRVA<MDRawContextX86> context(minidump_writer);
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
thread->thread_context = context.location();
|
||||
memset(context.get(), 0, sizeof(MDRawContextX86));
|
||||
return WriteContext(context.get(), ®s, &fp_regs, &dbg_regs);
|
||||
}
|
||||
|
||||
bool WriteCPUInformation(MDRawSystemInfo *sys_info) {
|
||||
const char *proc_cpu_path = "/proc/cpuinfo";
|
||||
char line[128];
|
||||
char vendor_id[13];
|
||||
const char vendor_id_name[] = "vendor_id";
|
||||
const size_t vendor_id_name_length = sizeof(vendor_id_name) - 1;
|
||||
|
||||
struct CpuInfoEntry {
|
||||
const char *info_name;
|
||||
int value;
|
||||
} cpu_info_table[] = {
|
||||
{ "processor", -1 },
|
||||
{ "model", 0 },
|
||||
{ "stepping", 0 },
|
||||
{ "cpuid level", 0 },
|
||||
{ NULL, -1 },
|
||||
};
|
||||
|
||||
memset(vendor_id, 0, sizeof(vendor_id));
|
||||
|
||||
FILE *fp = fopen(proc_cpu_path, "r");
|
||||
if (fp != NULL) {
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
CpuInfoEntry *entry = &cpu_info_table[0];
|
||||
while (entry->info_name != NULL) {
|
||||
if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
|
||||
char *value = strchr(line, ':');
|
||||
value++;
|
||||
if (value != NULL)
|
||||
sscanf(value, " %d", &(entry->value));
|
||||
}
|
||||
entry++;
|
||||
}
|
||||
|
||||
// special case for vendor_id
|
||||
if (!strncmp(line, vendor_id_name, vendor_id_name_length)) {
|
||||
char *value = strchr(line, ':');
|
||||
if (value == NULL)
|
||||
continue;
|
||||
|
||||
value++;
|
||||
while (*value && isspace(*value))
|
||||
value++;
|
||||
if (*value) {
|
||||
size_t length = strlen(value);
|
||||
// we don't want the trailing newline
|
||||
if (value[length - 1] == '\n')
|
||||
length--;
|
||||
// ensure we have space for the value
|
||||
if (length < sizeof(vendor_id))
|
||||
strncpy(vendor_id, value, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
// /proc/cpuinfo contains cpu id, change it into number by adding one.
|
||||
cpu_info_table[0].value++;
|
||||
|
||||
sys_info->number_of_processors = cpu_info_table[0].value;
|
||||
sys_info->processor_level = cpu_info_table[3].value;
|
||||
sys_info->processor_revision = cpu_info_table[1].value << 8 |
|
||||
cpu_info_table[2].value;
|
||||
|
||||
sys_info->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN;
|
||||
struct utsname uts;
|
||||
if (uname(&uts) == 0) {
|
||||
// Match i*86 and x86* as X86 architecture.
|
||||
if ((strstr(uts.machine, "x86") == uts.machine) ||
|
||||
(strlen(uts.machine) == 4 &&
|
||||
uts.machine[0] == 'i' &&
|
||||
uts.machine[2] == '8' &&
|
||||
uts.machine[3] == '6')) {
|
||||
sys_info->processor_architecture = MD_CPU_ARCHITECTURE_X86;
|
||||
if (vendor_id[0] != '\0')
|
||||
memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
|
||||
sizeof(sys_info->cpu.x86_cpu_info.vendor_id));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteOSInformation(MinidumpFileWriter *minidump_writer,
|
||||
MDRawSystemInfo *sys_info) {
|
||||
sys_info->platform_id = MD_OS_LINUX;
|
||||
|
||||
struct utsname uts;
|
||||
if (uname(&uts) == 0) {
|
||||
char os_version[512];
|
||||
size_t space_left = sizeof(os_version);
|
||||
memset(os_version, 0, space_left);
|
||||
const char *os_info_table[] = {
|
||||
uts.sysname,
|
||||
uts.release,
|
||||
uts.version,
|
||||
uts.machine,
|
||||
"GNU/Linux",
|
||||
NULL
|
||||
};
|
||||
for (const char **cur_os_info = os_info_table;
|
||||
*cur_os_info != NULL;
|
||||
cur_os_info++) {
|
||||
if (cur_os_info != os_info_table && space_left > 1) {
|
||||
strcat(os_version, " ");
|
||||
space_left--;
|
||||
}
|
||||
if (space_left > strlen(*cur_os_info)) {
|
||||
strcat(os_version, *cur_os_info);
|
||||
space_left -= strlen(*cur_os_info);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MDLocationDescriptor location;
|
||||
if (!minidump_writer->WriteString(os_version, 0, &location))
|
||||
return false;
|
||||
sys_info->csd_version_rva = location.rva;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Callback context for get writting thread information.
|
||||
struct ThreadInfoCallbackCtx {
|
||||
MinidumpFileWriter *minidump_writer;
|
||||
const WriterArgument *writer_args;
|
||||
TypedMDRVA<MDRawThreadList> *list;
|
||||
int thread_index;
|
||||
};
|
||||
|
||||
// Callback run for writing threads information in the process.
|
||||
bool ThreadInfomationCallback(const ThreadInfo &thread_info,
|
||||
void *context) {
|
||||
ThreadInfoCallbackCtx *callback_context =
|
||||
static_cast<ThreadInfoCallbackCtx *>(context);
|
||||
bool success = true;
|
||||
MDRawThread thread;
|
||||
memset(&thread, 0, sizeof(MDRawThread));
|
||||
if (thread_info.pid != callback_context->writer_args->crashed_pid ||
|
||||
callback_context->writer_args->sig_ctx == NULL) {
|
||||
success = WriteThreadStream(callback_context->minidump_writer,
|
||||
callback_context->writer_args->thread_lister,
|
||||
thread_info, &thread);
|
||||
} else {
|
||||
success = WriteCrashedThreadStream(callback_context->minidump_writer,
|
||||
callback_context->writer_args,
|
||||
thread_info, &thread);
|
||||
}
|
||||
if (success) {
|
||||
callback_context->list->CopyIndexAfterObject(
|
||||
callback_context->thread_index++,
|
||||
&thread, sizeof(MDRawThread));
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// Stream writers
|
||||
bool WriteThreadListStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
// Get the thread information.
|
||||
const LinuxThread *thread_lister = writer_args->thread_lister;
|
||||
int thread_count = thread_lister->GetThreadCount();
|
||||
if (thread_count < 0)
|
||||
return false;
|
||||
TypedMDRVA<MDRawThreadList> list(minidump_writer);
|
||||
if (!list.AllocateObjectAndArray(thread_count, sizeof(MDRawThread)))
|
||||
return false;
|
||||
dir->stream_type = MD_THREAD_LIST_STREAM;
|
||||
dir->location = list.location();
|
||||
list.get()->number_of_threads = thread_count;
|
||||
|
||||
ThreadInfoCallbackCtx context;
|
||||
context.minidump_writer = minidump_writer;
|
||||
context.writer_args = writer_args;
|
||||
context.list = &list;
|
||||
context.thread_index = 0;
|
||||
CallbackParam<ThreadCallback> callback_param(ThreadInfomationCallback,
|
||||
&context);
|
||||
int written = thread_lister->ListThreads(&callback_param);
|
||||
return written == thread_count;
|
||||
}
|
||||
|
||||
bool WriteCVRecord(MinidumpFileWriter *minidump_writer,
|
||||
MDRawModule *module,
|
||||
const char *module_path) {
|
||||
TypedMDRVA<MDCVInfoPDB70> cv(minidump_writer);
|
||||
|
||||
// Only return the last path component of the full module path
|
||||
const char *module_name = strrchr(module_path, '/');
|
||||
// Increment past the slash
|
||||
if (module_name)
|
||||
++module_name;
|
||||
else
|
||||
module_name = "<Unknown>";
|
||||
|
||||
size_t module_name_length = strlen(module_name);
|
||||
if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(u_int8_t)))
|
||||
return false;
|
||||
if (!cv.CopyIndexAfterObject(0, const_cast<char *>(module_name),
|
||||
module_name_length))
|
||||
return false;
|
||||
|
||||
module->cv_record = cv.location();
|
||||
MDCVInfoPDB70 *cv_ptr = cv.get();
|
||||
memset(cv_ptr, 0, sizeof(MDCVInfoPDB70));
|
||||
cv_ptr->cv_signature = MD_CVINFOPDB70_SIGNATURE;
|
||||
cv_ptr->age = 0;
|
||||
|
||||
// Get the module identifier
|
||||
FileID file_id(module_path);
|
||||
unsigned char identifier[16];
|
||||
|
||||
if (file_id.ElfFileIdentifier(identifier)) {
|
||||
cv_ptr->signature.data1 = (uint32_t)identifier[0] << 24 |
|
||||
(uint32_t)identifier[1] << 16 | (uint32_t)identifier[2] << 8 |
|
||||
(uint32_t)identifier[3];
|
||||
cv_ptr->signature.data2 = (uint32_t)identifier[4] << 8 | identifier[5];
|
||||
cv_ptr->signature.data3 = (uint32_t)identifier[6] << 8 | identifier[7];
|
||||
cv_ptr->signature.data4[0] = identifier[8];
|
||||
cv_ptr->signature.data4[1] = identifier[9];
|
||||
cv_ptr->signature.data4[2] = identifier[10];
|
||||
cv_ptr->signature.data4[3] = identifier[11];
|
||||
cv_ptr->signature.data4[4] = identifier[12];
|
||||
cv_ptr->signature.data4[5] = identifier[13];
|
||||
cv_ptr->signature.data4[6] = identifier[14];
|
||||
cv_ptr->signature.data4[7] = identifier[15];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct ModuleInfoCallbackCtx {
|
||||
MinidumpFileWriter *minidump_writer;
|
||||
const WriterArgument *writer_args;
|
||||
TypedMDRVA<MDRawModuleList> *list;
|
||||
int module_index;
|
||||
};
|
||||
|
||||
bool ModuleInfoCallback(const ModuleInfo &module_info,
|
||||
void *context) {
|
||||
ModuleInfoCallbackCtx *callback_context =
|
||||
static_cast<ModuleInfoCallbackCtx *>(context);
|
||||
// Skip those modules without name, or those that are not modules.
|
||||
if (strlen(module_info.name) == 0 ||
|
||||
!strchr(module_info.name, '/'))
|
||||
return true;
|
||||
|
||||
MDRawModule module;
|
||||
memset(&module, 0, sizeof(module));
|
||||
MDLocationDescriptor loc;
|
||||
if (!callback_context->minidump_writer->WriteString(module_info.name, 0,
|
||||
&loc))
|
||||
return false;
|
||||
module.base_of_image = (u_int64_t)module_info.start_addr;
|
||||
module.size_of_image = module_info.size;
|
||||
module.module_name_rva = loc.rva;
|
||||
|
||||
if (!WriteCVRecord(callback_context->minidump_writer, &module,
|
||||
module_info.name))
|
||||
return false;
|
||||
callback_context->list->CopyIndexAfterObject(
|
||||
callback_context->module_index++, &module, MD_MODULE_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteModuleListStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawModuleList> list(minidump_writer);
|
||||
int module_count = writer_args->thread_lister->GetModuleCount();
|
||||
if (module_count <= 0 ||
|
||||
!list.AllocateObjectAndArray(module_count, MD_MODULE_SIZE))
|
||||
return false;
|
||||
dir->stream_type = MD_MODULE_LIST_STREAM;
|
||||
dir->location = list.location();
|
||||
list.get()->number_of_modules = module_count;
|
||||
ModuleInfoCallbackCtx context;
|
||||
context.minidump_writer = minidump_writer;
|
||||
context.writer_args = writer_args;
|
||||
context.list = &list;
|
||||
context.module_index = 0;
|
||||
CallbackParam<ModuleCallback> callback(ModuleInfoCallback, &context);
|
||||
return writer_args->thread_lister->ListModules(&callback) == module_count;
|
||||
}
|
||||
|
||||
bool WriteSystemInfoStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawSystemInfo> sys_info(minidump_writer);
|
||||
if (!sys_info.Allocate())
|
||||
return false;
|
||||
dir->stream_type = MD_SYSTEM_INFO_STREAM;
|
||||
dir->location = sys_info.location();
|
||||
|
||||
return WriteCPUInformation(sys_info.get()) &&
|
||||
WriteOSInformation(minidump_writer, sys_info.get());
|
||||
}
|
||||
|
||||
bool WriteExceptionStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
// This happenes when this is not a crash, but a requested dump.
|
||||
if (writer_args->sig_ctx == NULL)
|
||||
return false;
|
||||
|
||||
TypedMDRVA<MDRawExceptionStream> exception(minidump_writer);
|
||||
if (!exception.Allocate())
|
||||
return false;
|
||||
|
||||
dir->stream_type = MD_EXCEPTION_STREAM;
|
||||
dir->location = exception.location();
|
||||
exception.get()->thread_id = writer_args->crashed_pid;
|
||||
exception.get()->exception_record.exception_code = writer_args->signo;
|
||||
exception.get()->exception_record.exception_flags = 0;
|
||||
if (writer_args->sig_ctx != NULL) {
|
||||
exception.get()->exception_record.exception_address =
|
||||
writer_args->sig_ctx->eip;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write context of the exception.
|
||||
TypedMDRVA<MDRawContextX86> context(minidump_writer);
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
exception.get()->thread_context = context.location();
|
||||
memset(context.get(), 0, sizeof(MDRawContextX86));
|
||||
return WriteContext(context.get(), writer_args->sig_ctx, NULL);
|
||||
}
|
||||
|
||||
bool WriteMiscInfoStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawMiscInfo> info(minidump_writer);
|
||||
if (!info.Allocate())
|
||||
return false;
|
||||
|
||||
dir->stream_type = MD_MISC_INFO_STREAM;
|
||||
dir->location = info.location();
|
||||
info.get()->size_of_info = sizeof(MDRawMiscInfo);
|
||||
info.get()->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID;
|
||||
info.get()->process_id = writer_args->requester_pid;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteBreakpadInfoStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawBreakpadInfo> info(minidump_writer);
|
||||
if (!info.Allocate())
|
||||
return false;
|
||||
|
||||
dir->stream_type = MD_BREAKPAD_INFO_STREAM;
|
||||
dir->location = info.location();
|
||||
|
||||
info.get()->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID |
|
||||
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID;
|
||||
info.get()->dump_thread_id = getpid();
|
||||
info.get()->requesting_thread_id = writer_args->requester_pid;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prototype of writer functions.
|
||||
typedef bool (*WriteStringFN)(MinidumpFileWriter *,
|
||||
const WriterArgument *,
|
||||
MDRawDirectory *);
|
||||
|
||||
// Function table to writer a full minidump.
|
||||
WriteStringFN writers[] = {
|
||||
WriteThreadListStream,
|
||||
WriteModuleListStream,
|
||||
WriteSystemInfoStream,
|
||||
WriteExceptionStream,
|
||||
WriteMiscInfoStream,
|
||||
WriteBreakpadInfoStream,
|
||||
};
|
||||
|
||||
// Will call each writer function in the writers table.
|
||||
// It runs in a different process from the crashing process, but sharing
|
||||
// the same address space. This enables it to use ptrace functions.
|
||||
int Write(void *argument) {
|
||||
WriterArgument *writer_args =
|
||||
static_cast<WriterArgument *>(argument);
|
||||
|
||||
if (!writer_args->thread_lister->SuspendAllThreads())
|
||||
return -1;
|
||||
|
||||
if (writer_args->sighandler_ebp != 0 &&
|
||||
writer_args->thread_lister->FindSigContext(writer_args->sighandler_ebp,
|
||||
&writer_args->sig_ctx)) {
|
||||
writer_args->crashed_stack_bottom =
|
||||
writer_args->thread_lister->GetThreadStackBottom(
|
||||
writer_args->sig_ctx->ebp);
|
||||
int crashed_pid = FindCrashingThread(writer_args->crashed_stack_bottom,
|
||||
writer_args->requester_pid,
|
||||
writer_args->thread_lister);
|
||||
if (crashed_pid > 0)
|
||||
writer_args->crashed_pid = crashed_pid;
|
||||
}
|
||||
|
||||
|
||||
MinidumpFileWriter *minidump_writer = writer_args->minidump_writer;
|
||||
TypedMDRVA<MDRawHeader> header(minidump_writer);
|
||||
TypedMDRVA<MDRawDirectory> dir(minidump_writer);
|
||||
if (!header.Allocate())
|
||||
return 0;
|
||||
|
||||
int writer_count = sizeof(writers) / sizeof(writers[0]);
|
||||
// Need directory space for all writers.
|
||||
if (!dir.AllocateArray(writer_count))
|
||||
return 0;
|
||||
header.get()->signature = MD_HEADER_SIGNATURE;
|
||||
header.get()->version = MD_HEADER_VERSION;
|
||||
header.get()->time_date_stamp = time(NULL);
|
||||
header.get()->stream_count = writer_count;
|
||||
header.get()->stream_directory_rva = dir.position();
|
||||
|
||||
int dir_index = 0;
|
||||
MDRawDirectory local_dir;
|
||||
for (int i = 0; i < writer_count; ++i) {
|
||||
if (writers[i](minidump_writer, writer_args, &local_dir))
|
||||
dir.CopyIndex(dir_index++, &local_dir);
|
||||
}
|
||||
|
||||
writer_args->thread_lister->ResumeAllThreads();
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
MinidumpGenerator::MinidumpGenerator() {
|
||||
AllocateStack();
|
||||
}
|
||||
|
||||
MinidumpGenerator::~MinidumpGenerator() {
|
||||
}
|
||||
|
||||
void MinidumpGenerator::AllocateStack() {
|
||||
stack_.reset(new char[kStackSize]);
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteMinidumpToFile(const char *file_pathname,
|
||||
int signo,
|
||||
uintptr_t sighandler_ebp,
|
||||
struct sigcontext **sig_ctx) const {
|
||||
assert(file_pathname != NULL);
|
||||
assert(stack_ != NULL);
|
||||
|
||||
if (stack_ == NULL || file_pathname == NULL)
|
||||
return false;
|
||||
|
||||
MinidumpFileWriter minidump_writer;
|
||||
if (minidump_writer.Open(file_pathname)) {
|
||||
WriterArgument argument;
|
||||
memset(&argument, 0, sizeof(argument));
|
||||
LinuxThread thread_lister(getpid());
|
||||
argument.thread_lister = &thread_lister;
|
||||
argument.minidump_writer = &minidump_writer;
|
||||
argument.requester_pid = getpid();
|
||||
argument.crashed_pid = getpid();
|
||||
argument.signo = signo;
|
||||
argument.sighandler_ebp = sighandler_ebp;
|
||||
argument.sig_ctx = NULL;
|
||||
|
||||
int cloned_pid = clone(Write, stack_.get() + kStackSize,
|
||||
CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
||||
(void*)&argument);
|
||||
waitpid(cloned_pid, NULL, __WALL);
|
||||
if (sig_ctx != NULL)
|
||||
*sig_ctx = argument.sig_ctx;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
@@ -27,27 +29,45 @@
|
||||
// (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_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||
#ifndef CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
||||
#define CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "google_breakpad/common/breakpad_types.h"
|
||||
#include "processor/scoped_ptr.h"
|
||||
|
||||
struct sigcontext;
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Write a minidump to the filesystem. This function does not malloc nor use
|
||||
// libc functions which may. Thus, it can be used in contexts where the state
|
||||
// of the heap may be corrupt.
|
||||
// filename: the filename to write to. This is opened O_EXCL and fails if
|
||||
// open fails.
|
||||
// crashing_process: the pid of the crashing process. This must be trusted.
|
||||
// blob: a blob of data from the crashing process. See exception_handler.h
|
||||
// blob_size: the length of |blob|, in bytes
|
||||
//
|
||||
// Returns true iff successful.
|
||||
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size);
|
||||
// MinidumpGenerator
|
||||
//
|
||||
// Write a minidump to file based on the signo and sig_ctx.
|
||||
// A minidump generator should be created before any exception happen.
|
||||
//
|
||||
class MinidumpGenerator {
|
||||
public:
|
||||
MinidumpGenerator();
|
||||
|
||||
~MinidumpGenerator();
|
||||
|
||||
// Write minidump.
|
||||
bool WriteMinidumpToFile(const char *file_pathname,
|
||||
int signo,
|
||||
uintptr_t sighandler_ebp,
|
||||
struct sigcontext **sig_ctx) const;
|
||||
private:
|
||||
// Allocate memory for stack.
|
||||
void AllocateStack();
|
||||
|
||||
private:
|
||||
// Stack size of the writer thread.
|
||||
static const int kStackSize = 1024 * 1024;
|
||||
scoped_array<char> stack_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||
#endif // CLIENT_LINUX_HANDLER_MINIDUMP_GENERATOR_H__
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Author: Li Liu
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
@@ -27,69 +29,58 @@
|
||||
// (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 <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#import "TestClass.h"
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
struct AStruct {
|
||||
int x;
|
||||
float y;
|
||||
double z;
|
||||
};
|
||||
#include "client/linux/handler/minidump_generator.h"
|
||||
|
||||
class InternalTestClass {
|
||||
public:
|
||||
InternalTestClass(int a) : a_(a) {}
|
||||
~InternalTestClass() {}
|
||||
using namespace google_breakpad;
|
||||
|
||||
void snooze(float a);
|
||||
void snooze(int a);
|
||||
int snooze(int a, float b);
|
||||
// Thread use this to see if it should stop working.
|
||||
static bool should_exit = false;
|
||||
|
||||
protected:
|
||||
int a_;
|
||||
AStruct s_;
|
||||
|
||||
static void InternalFunction(AStruct &s);
|
||||
static float kStaticFloatValue;
|
||||
};
|
||||
|
||||
void InternalTestClass::snooze(float a) {
|
||||
InternalFunction(s_);
|
||||
sleep(a_ * a);
|
||||
static void foo2(int arg) {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
int c = arg;
|
||||
c = 0xcccccccc;
|
||||
while (!should_exit)
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
void InternalTestClass::snooze(int a) {
|
||||
InternalFunction(s_);
|
||||
sleep(a_ * a);
|
||||
static void foo(int arg) {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
int b = arg;
|
||||
b = 0xbbbbbbbb;
|
||||
foo2(b);
|
||||
}
|
||||
|
||||
int InternalTestClass::snooze(int a, float b) {
|
||||
InternalFunction(s_);
|
||||
sleep(a_ * a * b);
|
||||
|
||||
return 33;
|
||||
static void *thread_main(void *) {
|
||||
// Stack variable, used for debugging stack dumps.
|
||||
int a = 0xaaaaaaaa;
|
||||
foo(a);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void InternalTestClass::InternalFunction(AStruct &s) {
|
||||
s.x = InternalTestClass::kStaticFloatValue;
|
||||
}
|
||||
|
||||
float InternalTestClass::kStaticFloatValue = 42;
|
||||
|
||||
static float PlainOldFunction() {
|
||||
return 3.14145;
|
||||
}
|
||||
|
||||
@implementation TestClass
|
||||
|
||||
- (void)wait {
|
||||
InternalTestClass t(10);
|
||||
float z = PlainOldFunction();
|
||||
|
||||
while (1) {
|
||||
t.snooze(z);
|
||||
static void CreateThread(int num) {
|
||||
pthread_t h;
|
||||
for (int i = 0; i < num; ++i) {
|
||||
pthread_create(&h, NULL, thread_main, NULL);
|
||||
pthread_detach(h);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
int main(int argc, char *argv[]) {
|
||||
CreateThread(10);
|
||||
google_breakpad::MinidumpGenerator mg;
|
||||
if (mg.WriteMinidumpToFile("minidump_test.out", -1, 0, NULL))
|
||||
printf("Succeeded written minidump\n");
|
||||
else
|
||||
printf("Failed to write minidump\n");
|
||||
should_exit = true;
|
||||
return 0;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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_MINIDUMP_WRITER_DIRECTORY_READER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// A class for enumerating a directory without using diropen/readdir or other
|
||||
// functions which may allocate memory.
|
||||
class DirectoryReader {
|
||||
public:
|
||||
DirectoryReader(int fd)
|
||||
: fd_(fd),
|
||||
buf_used_(0) {
|
||||
}
|
||||
|
||||
// Return the next entry from the directory
|
||||
// name: (output) the NUL terminated entry name
|
||||
//
|
||||
// Returns true iff successful (false on EOF).
|
||||
//
|
||||
// After calling this, one must call |PopEntry| otherwise you'll get the same
|
||||
// entry over and over.
|
||||
bool GetNextEntry(const char** name) {
|
||||
struct kernel_dirent* const dent =
|
||||
reinterpret_cast<kernel_dirent*>(buf_);
|
||||
|
||||
if (buf_used_ == 0) {
|
||||
// need to read more entries.
|
||||
const int n = sys_getdents(fd_, dent, sizeof(buf_));
|
||||
if (n < 0) {
|
||||
return false;
|
||||
} else if (n == 0) {
|
||||
hit_eof_ = true;
|
||||
} else {
|
||||
buf_used_ += n;
|
||||
}
|
||||
}
|
||||
|
||||
if (buf_used_ == 0 && hit_eof_)
|
||||
return false;
|
||||
|
||||
assert(buf_used_ > 0);
|
||||
|
||||
*name = dent->d_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PopEntry() {
|
||||
if (!buf_used_)
|
||||
return;
|
||||
|
||||
const struct kernel_dirent* const dent =
|
||||
reinterpret_cast<kernel_dirent*>(buf_);
|
||||
|
||||
buf_used_ -= dent->d_reclen;
|
||||
memmove(buf_, buf_ + dent->d_reclen, buf_used_);
|
||||
}
|
||||
|
||||
private:
|
||||
const int fd_;
|
||||
bool hit_eof_;
|
||||
unsigned buf_used_;
|
||||
uint8_t buf_[sizeof(struct kernel_dirent) + NAME_MAX + 1];
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
|
||||
@@ -1,130 +0,0 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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_MINIDUMP_WRITER_LINE_READER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// A class for reading a file, line by line, without using fopen/fgets or other
|
||||
// functions which may allocate memory.
|
||||
class LineReader {
|
||||
public:
|
||||
LineReader(int fd)
|
||||
: fd_(fd),
|
||||
hit_eof_(false),
|
||||
buf_used_(0) {
|
||||
}
|
||||
|
||||
// The maximum length of a line.
|
||||
static const size_t kMaxLineLen = 512;
|
||||
|
||||
// Return the next line from the file.
|
||||
// line: (output) a pointer to the start of the line. The line is NUL
|
||||
// terminated.
|
||||
// len: (output) the length of the line (not inc the NUL byte)
|
||||
//
|
||||
// Returns true iff successful (false on EOF).
|
||||
//
|
||||
// One must call |PopLine| after this function, otherwise you'll continue to
|
||||
// get the same line over and over.
|
||||
bool GetNextLine(const char **line, unsigned *len) {
|
||||
for (;;) {
|
||||
if (buf_used_ == 0 && hit_eof_)
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < buf_used_; ++i) {
|
||||
if (buf_[i] == '\n' || buf_[i] == 0) {
|
||||
buf_[i] = 0;
|
||||
*len = i;
|
||||
*line = buf_;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (buf_used_ == sizeof(buf_)) {
|
||||
// we scanned the whole buffer and didn't find an end-of-line marker.
|
||||
// This line is too long to process.
|
||||
return false;
|
||||
}
|
||||
|
||||
// We didn't find any end-of-line terminators in the buffer. However, if
|
||||
// this is the last line in the file it might not have one:
|
||||
if (hit_eof_) {
|
||||
assert(buf_used_);
|
||||
// There's room for the NUL because of the buf_used_ == sizeof(buf_)
|
||||
// check above.
|
||||
buf_[buf_used_] = 0;
|
||||
*len = buf_used_;
|
||||
buf_used_ += 1; // since we appended the NUL.
|
||||
*line = buf_;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, we should pull in more data from the file
|
||||
const ssize_t n = sys_read(fd_, buf_ + buf_used_,
|
||||
sizeof(buf_) - buf_used_);
|
||||
if (n < 0) {
|
||||
return false;
|
||||
} else if (n == 0) {
|
||||
hit_eof_ = true;
|
||||
} else {
|
||||
buf_used_ += n;
|
||||
}
|
||||
|
||||
// At this point, we have either set the hit_eof_ flag, or we have more
|
||||
// data to process...
|
||||
}
|
||||
}
|
||||
|
||||
void PopLine(unsigned len) {
|
||||
// len doesn't include the NUL byte at the end.
|
||||
|
||||
assert(buf_used_ >= len + 1);
|
||||
buf_used_ -= len + 1;
|
||||
memmove(buf_, buf_ + len + 1, buf_used_);
|
||||
}
|
||||
|
||||
private:
|
||||
const int fd_;
|
||||
|
||||
bool hit_eof_;
|
||||
unsigned buf_used_;
|
||||
char buf_[kMaxLineLen];
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
|
||||
@@ -1,184 +0,0 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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 <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
static int TemporaryFile() {
|
||||
static const char templ[] = "/tmp/line-reader-unittest-XXXXXX";
|
||||
char templ_copy[sizeof(templ)];
|
||||
memcpy(templ_copy, templ, sizeof(templ));
|
||||
const int fd = mkstemp(templ_copy);
|
||||
if (fd >= 0)
|
||||
unlink(templ_copy);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
namespace {
|
||||
typedef testing::Test LineReaderTest;
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, EmptyFile) {
|
||||
const int fd = TemporaryFile();
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, OneLineTerminated) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a\n", 2);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, OneLine) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a", 1);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, TwoLinesTerminated) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a\nb\n", 4);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(line[0], 'b');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, TwoLines) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a\nb", 3);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(line[0], 'b');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, MaxLength) {
|
||||
const int fd = TemporaryFile();
|
||||
char l[LineReader::kMaxLineLen - 1];
|
||||
memset(l, 'a', sizeof(l));
|
||||
write(fd, l, sizeof(l));
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, sizeof(l));
|
||||
ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0);
|
||||
ASSERT_EQ(line[len], 0);
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, TooLong) {
|
||||
const int fd = TemporaryFile();
|
||||
char l[LineReader::kMaxLineLen];
|
||||
memset(l, 'a', sizeof(l));
|
||||
write(fd, l, sizeof(l));
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
@@ -1,419 +0,0 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// This code deals with the mechanics of getting information about a crashed
|
||||
// process. Since this code may run in a compromised address space, the same
|
||||
// rules apply as detailed at the top of minidump_writer.h: no libc calls and
|
||||
// use the alternative allocator.
|
||||
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "client/linux/minidump_writer/directory_reader.h"
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
|
||||
// Suspend a thread by attaching to it.
|
||||
static bool SuspendThread(pid_t pid) {
|
||||
// This may fail if the thread has just died or debugged.
|
||||
errno = 0;
|
||||
if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
|
||||
errno != 0) {
|
||||
return false;
|
||||
}
|
||||
while (sys_waitpid(pid, NULL, __WALL) < 0) {
|
||||
if (errno != EINTR) {
|
||||
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Resume a thread by detaching from it.
|
||||
static bool ResumeThread(pid_t pid) {
|
||||
return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
|
||||
}
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
LinuxDumper::LinuxDumper(int pid)
|
||||
: pid_(pid),
|
||||
threads_suspened_(false),
|
||||
threads_(&allocator_, 8),
|
||||
mappings_(&allocator_) {
|
||||
}
|
||||
|
||||
bool LinuxDumper::Init() {
|
||||
return EnumerateThreads(&threads_) &&
|
||||
EnumerateMappings(&mappings_);
|
||||
}
|
||||
|
||||
bool LinuxDumper::ThreadsSuspend() {
|
||||
if (threads_suspened_)
|
||||
return true;
|
||||
bool good = true;
|
||||
for (size_t i = 0; i < threads_.size(); ++i)
|
||||
good &= SuspendThread(threads_[i]);
|
||||
threads_suspened_ = true;
|
||||
return good;
|
||||
}
|
||||
|
||||
bool LinuxDumper::ThreadsResume() {
|
||||
if (!threads_suspened_)
|
||||
return false;
|
||||
bool good = true;
|
||||
for (size_t i = 0; i < threads_.size(); ++i)
|
||||
good &= ResumeThread(threads_[i]);
|
||||
threads_suspened_ = false;
|
||||
return good;
|
||||
}
|
||||
|
||||
void
|
||||
LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const {
|
||||
assert(path);
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
path[0] = '\0';
|
||||
|
||||
const unsigned pid_len = my_int_len(pid);
|
||||
|
||||
assert(node);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t node_len = my_strlen(node);
|
||||
assert(node_len < NAME_MAX);
|
||||
if (node_len >= NAME_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(node_len > 0);
|
||||
if (node_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(pid > 0);
|
||||
if (pid <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t total_length = 6 + pid_len + 1 + node_len;
|
||||
|
||||
assert(total_length < NAME_MAX);
|
||||
if (total_length >= NAME_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(path, "/proc/", 6);
|
||||
my_itos(path + 6, pid, pid_len);
|
||||
memcpy(path + 6 + pid_len, "/", 1);
|
||||
memcpy(path + 6 + pid_len + 1, node, node_len);
|
||||
memcpy(path + total_length, "\0", 1);
|
||||
}
|
||||
|
||||
void*
|
||||
LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
|
||||
char auxv_path[80];
|
||||
BuildProcPath(auxv_path, pid, "auxv");
|
||||
|
||||
// If BuildProcPath errors out due to invalid input, we'll handle it when
|
||||
// we try to sys_open the file.
|
||||
|
||||
// Find the AT_SYSINFO_EHDR entry for linux-gate.so
|
||||
// See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
|
||||
// information.
|
||||
int fd = sys_open(auxv_path, O_RDONLY, 0);
|
||||
if (fd < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
elf_aux_entry one_aux_entry;
|
||||
while (sys_read(fd,
|
||||
&one_aux_entry,
|
||||
sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) &&
|
||||
one_aux_entry.a_type != AT_NULL) {
|
||||
if (one_aux_entry.a_type == AT_SYSINFO_EHDR) {
|
||||
close(fd);
|
||||
return reinterpret_cast<void*>(one_aux_entry.a_un.a_val);
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
||||
char maps_path[80];
|
||||
BuildProcPath(maps_path, pid_, "maps");
|
||||
|
||||
// linux_gate_loc is the beginning of the kernel's mapping of
|
||||
// linux-gate.so in the process. It doesn't actually show up in the
|
||||
// maps list as a filename, so we use the aux vector to find it's
|
||||
// load location and special case it's entry when creating the list
|
||||
// of mappings.
|
||||
const void* linux_gate_loc;
|
||||
linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_);
|
||||
|
||||
const int fd = sys_open(maps_path, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
LineReader* const line_reader = new(allocator_) LineReader(fd);
|
||||
|
||||
const char* line;
|
||||
unsigned line_len;
|
||||
while (line_reader->GetNextLine(&line, &line_len)) {
|
||||
uintptr_t start_addr, end_addr, offset;
|
||||
|
||||
const char* i1 = my_read_hex_ptr(&start_addr, line);
|
||||
if (*i1 == '-') {
|
||||
const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1);
|
||||
if (*i2 == ' ') {
|
||||
const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */);
|
||||
if (*i3 == ' ') {
|
||||
MappingInfo* const module = new(allocator_) MappingInfo;
|
||||
memset(module, 0, sizeof(MappingInfo));
|
||||
module->start_addr = start_addr;
|
||||
module->size = end_addr - start_addr;
|
||||
module->offset = offset;
|
||||
const char* name = NULL;
|
||||
// Only copy name if the name is a valid path name, or if
|
||||
// we've found the VDSO image
|
||||
if ((name = my_strchr(line, '/')) != NULL) {
|
||||
const unsigned l = my_strlen(name);
|
||||
if (l < sizeof(module->name))
|
||||
memcpy(module->name, name, l);
|
||||
} else if (linux_gate_loc &&
|
||||
reinterpret_cast<void*>(module->start_addr) ==
|
||||
linux_gate_loc) {
|
||||
memcpy(module->name,
|
||||
kLinuxGateLibraryName,
|
||||
my_strlen(kLinuxGateLibraryName));
|
||||
module->offset = 0;
|
||||
}
|
||||
result->push_back(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
line_reader->PopLine(line_len);
|
||||
}
|
||||
|
||||
sys_close(fd);
|
||||
|
||||
return result->size() > 0;
|
||||
}
|
||||
|
||||
// Parse /proc/$pid/task to list all the threads of the process identified by
|
||||
// pid.
|
||||
bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const {
|
||||
char task_path[80];
|
||||
BuildProcPath(task_path, pid_, "task");
|
||||
|
||||
const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd);
|
||||
|
||||
// The directory may contain duplicate entries which we filter by assuming
|
||||
// that they are consecutive.
|
||||
int last_tid = -1;
|
||||
const char* dent_name;
|
||||
while (dir_reader->GetNextEntry(&dent_name)) {
|
||||
if (my_strcmp(dent_name, ".") &&
|
||||
my_strcmp(dent_name, "..")) {
|
||||
int tid = 0;
|
||||
if (my_strtoui(&tid, dent_name) &&
|
||||
last_tid != tid) {
|
||||
last_tid = tid;
|
||||
result->push_back(tid);
|
||||
}
|
||||
}
|
||||
dir_reader->PopEntry();
|
||||
}
|
||||
|
||||
sys_close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read thread info from /proc/$pid/status.
|
||||
// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailible,
|
||||
// these members are set to -1. Returns true iff all three members are
|
||||
// availible.
|
||||
bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) {
|
||||
assert(info != NULL);
|
||||
char status_path[80];
|
||||
BuildProcPath(status_path, tid, "status");
|
||||
|
||||
const int fd = open(status_path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
LineReader* const line_reader = new(allocator_) LineReader(fd);
|
||||
const char* line;
|
||||
unsigned line_len;
|
||||
|
||||
info->ppid = info->tgid = -1;
|
||||
|
||||
while (line_reader->GetNextLine(&line, &line_len)) {
|
||||
if (my_strncmp("Tgid:\t", line, 6) == 0) {
|
||||
my_strtoui(&info->tgid, line + 6);
|
||||
} else if (my_strncmp("PPid:\t", line, 6) == 0) {
|
||||
my_strtoui(&info->ppid, line + 6);
|
||||
}
|
||||
|
||||
line_reader->PopLine(line_len);
|
||||
}
|
||||
|
||||
if (info->ppid == -1 || info->tgid == -1)
|
||||
return false;
|
||||
|
||||
if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1 ||
|
||||
sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) {
|
||||
if (sys_ptrace(
|
||||
PTRACE_PEEKUSER, tid,
|
||||
reinterpret_cast<void*> (offsetof(struct user,
|
||||
u_debugreg[0]) + i *
|
||||
sizeof(debugreg_t)),
|
||||
&info->dregs[i]) == -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const uint8_t* stack_pointer;
|
||||
#if defined(__i386)
|
||||
memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
|
||||
#elif defined(__x86_64)
|
||||
memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
|
||||
#else
|
||||
#error "This code hasn't been ported to your platform yet."
|
||||
#endif
|
||||
|
||||
if (!GetStackInfo(&info->stack, &info->stack_len,
|
||||
(uintptr_t) stack_pointer))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get information about the stack, given the stack pointer. We don't try to
|
||||
// walk the stack since we might not have all the information needed to do
|
||||
// unwind. So we just grab, up to, 32k of stack.
|
||||
bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
|
||||
uintptr_t int_stack_pointer) {
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
static const bool stack_grows_down = true;
|
||||
static const uintptr_t page_size = 4096;
|
||||
#else
|
||||
#error "This code has not been ported to your platform yet."
|
||||
#endif
|
||||
// Move the stack pointer to the bottom of the page that it's in.
|
||||
uint8_t* const stack_pointer =
|
||||
reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1));
|
||||
|
||||
// The number of bytes of stack which we try to capture.
|
||||
static unsigned kStackToCapture = 32 * 1024;
|
||||
|
||||
const MappingInfo* mapping = FindMapping(stack_pointer);
|
||||
if (!mapping)
|
||||
return false;
|
||||
if (stack_grows_down) {
|
||||
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
|
||||
const ptrdiff_t distance_to_end =
|
||||
static_cast<ptrdiff_t>(mapping->size) - offset;
|
||||
*stack_len = distance_to_end > kStackToCapture ?
|
||||
kStackToCapture : distance_to_end;
|
||||
*stack = stack_pointer;
|
||||
} else {
|
||||
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
|
||||
*stack_len = offset > kStackToCapture ? kStackToCapture : offset;
|
||||
*stack = stack_pointer - *stack_len;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||
size_t length) {
|
||||
unsigned long tmp;
|
||||
size_t done = 0;
|
||||
static const size_t word_size = sizeof(tmp);
|
||||
uint8_t* const local = (uint8_t*) dest;
|
||||
uint8_t* const remote = (uint8_t*) src;
|
||||
|
||||
while (done < length) {
|
||||
const size_t l = length - done > word_size ? word_size : length - done;
|
||||
if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1)
|
||||
tmp = 0;
|
||||
memcpy(local + done, &tmp, l);
|
||||
done += l;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the mapping which the given memory address falls in.
|
||||
const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
|
||||
const uintptr_t addr = (uintptr_t) address;
|
||||
|
||||
for (size_t i = 0; i < mappings_.size(); ++i) {
|
||||
const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr);
|
||||
if (addr >= start && addr - start < mappings_[i]->size)
|
||||
return mappings_[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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_MINIDUMP_WRITER_LINUX_DUMPER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
|
||||
|
||||
#include <elf.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/user.h>
|
||||
#include <linux/limits.h>
|
||||
|
||||
#include "common/linux/memory.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
typedef typeof(((struct user*) 0)->u_debugreg[0]) debugreg_t;
|
||||
|
||||
// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
|
||||
#if defined(__i386)
|
||||
typedef Elf32_auxv_t elf_aux_entry;
|
||||
#elif defined(__x86_64__)
|
||||
typedef Elf64_auxv_t elf_aux_entry;
|
||||
#endif
|
||||
// When we find the VDSO mapping in the process's address space, this
|
||||
// is the name we use for it when writing it to the minidump.
|
||||
// This should always be less than NAME_MAX!
|
||||
const char kLinuxGateLibraryName[] = "linux-gate.so";
|
||||
|
||||
// We produce one of these structures for each thread in the crashed process.
|
||||
struct ThreadInfo {
|
||||
pid_t tgid; // thread group id
|
||||
pid_t ppid; // parent process
|
||||
|
||||
// Even on platforms where the stack grows down, the following will point to
|
||||
// the smallest address in the stack.
|
||||
const void* stack; // pointer to the stack area
|
||||
size_t stack_len; // length of the stack to copy
|
||||
|
||||
user_regs_struct regs;
|
||||
user_fpregs_struct fpregs;
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
user_fpxregs_struct fpxregs;
|
||||
|
||||
static const unsigned kNumDebugRegisters = 8;
|
||||
debugreg_t dregs[8];
|
||||
#endif
|
||||
};
|
||||
|
||||
// One of these is produced for each mapping in the process (i.e. line in
|
||||
// /proc/$x/maps).
|
||||
struct MappingInfo {
|
||||
uintptr_t start_addr;
|
||||
size_t size;
|
||||
size_t offset; // offset into the backed file.
|
||||
char name[NAME_MAX];
|
||||
};
|
||||
|
||||
class LinuxDumper {
|
||||
public:
|
||||
explicit LinuxDumper(pid_t pid);
|
||||
|
||||
// Parse the data for |threads| and |mappings|.
|
||||
bool Init();
|
||||
|
||||
// Suspend/resume all threads in the given process.
|
||||
bool ThreadsSuspend();
|
||||
bool ThreadsResume();
|
||||
|
||||
// Read information about the given thread. Returns true on success. One must
|
||||
// have called |ThreadsSuspend| first.
|
||||
bool ThreadInfoGet(pid_t tid, ThreadInfo* info);
|
||||
|
||||
// These are only valid after a call to |Init|.
|
||||
const wasteful_vector<pid_t> &threads() { return threads_; }
|
||||
const wasteful_vector<MappingInfo*> &mappings() { return mappings_; }
|
||||
const MappingInfo* FindMapping(const void* address) const;
|
||||
|
||||
// Find a block of memory to take as the stack given the top of stack pointer.
|
||||
// stack: (output) the lowest address in the memory area
|
||||
// stack_len: (output) the length of the memory area
|
||||
// stack_top: the current top of the stack
|
||||
bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top);
|
||||
|
||||
PageAllocator* allocator() { return &allocator_; }
|
||||
|
||||
// memcpy from a remote process.
|
||||
static void CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||
size_t length);
|
||||
|
||||
// Builds a proc path for a certain pid for a node. path is a
|
||||
// character array that is overwritten, and node is the final node
|
||||
// without any slashes.
|
||||
void BuildProcPath(char* path, pid_t pid, const char* node) const;
|
||||
|
||||
// Utility method to find the location of where the kernel has
|
||||
// mapped linux-gate.so in memory(shows up in /proc/pid/maps as
|
||||
// [vdso], but we can't guarantee that it's the only virtual dynamic
|
||||
// shared object. Parsing the auxilary vector for AT_SYSINFO_EHDR
|
||||
// is the safest way to go.)
|
||||
void* FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const;
|
||||
private:
|
||||
bool EnumerateMappings(wasteful_vector<MappingInfo*>* result) const;
|
||||
bool EnumerateThreads(wasteful_vector<pid_t>* result) const;
|
||||
|
||||
const pid_t pid_;
|
||||
|
||||
mutable PageAllocator allocator_;
|
||||
|
||||
bool threads_suspened_;
|
||||
wasteful_vector<pid_t> threads_; // the ids of all the threads
|
||||
wasteful_vector<MappingInfo*> mappings_; // info from /proc/<pid>/maps
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_
|
||||
@@ -1,118 +0,0 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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 <unistd.h>
|
||||
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
namespace {
|
||||
typedef testing::Test LinuxDumperTest;
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, Setup) {
|
||||
LinuxDumper dumper(getpid());
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, FindMappings) {
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
|
||||
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
|
||||
ASSERT_FALSE(dumper.FindMapping(NULL));
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, ThreadList) {
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
ASSERT_GE(dumper.threads().size(), 1);
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < dumper.threads().size(); ++i) {
|
||||
if (dumper.threads()[i] == getpid()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, BuildProcPath) {
|
||||
const pid_t pid = getpid();
|
||||
LinuxDumper dumper(pid);
|
||||
|
||||
char maps_path[256] = "dummymappath";
|
||||
char maps_path_expected[256];
|
||||
snprintf(maps_path_expected, sizeof(maps_path_expected),
|
||||
"/proc/%d/maps", pid);
|
||||
dumper.BuildProcPath(maps_path, pid, "maps");
|
||||
ASSERT_STREQ(maps_path, maps_path_expected);
|
||||
|
||||
// In release mode, we expect BuildProcPath to handle the invalid
|
||||
// parameters correctly and fill map_path with an empty
|
||||
// NULL-terminated string.
|
||||
#ifdef NDEBUG
|
||||
snprintf(maps_path, sizeof(maps_path), "dummymappath");
|
||||
dumper.BuildProcPath(maps_path, 0, "maps");
|
||||
EXPECT_STREQ(maps_path, "");
|
||||
|
||||
snprintf(maps_path, sizeof(maps_path), "dummymappath");
|
||||
dumper.BuildProcPath(maps_path, getpid(), "");
|
||||
EXPECT_STREQ(maps_path, "");
|
||||
|
||||
snprintf(maps_path, sizeof(maps_path), "dummymappath");
|
||||
dumper.BuildProcPath(maps_path, getpid(), NULL);
|
||||
EXPECT_STREQ(maps_path, "");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
|
||||
if (linux_gate_loc) {
|
||||
bool found_linux_gate = false;
|
||||
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||
const MappingInfo* mapping;
|
||||
for (unsigned i = 0; i < mappings.size(); ++i) {
|
||||
mapping = mappings[i];
|
||||
if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
|
||||
found_linux_gate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found_linux_gate);
|
||||
EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
|
||||
EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
|
||||
}
|
||||
}
|
||||
@@ -1,872 +0,0 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// This code writes out minidump files:
|
||||
// http://msdn.microsoft.com/en-us/library/ms680378(VS.85,loband).aspx
|
||||
//
|
||||
// Minidumps are a Microsoft format which Breakpad uses for recording crash
|
||||
// dumps. This code has to run in a compromised environment (the address space
|
||||
// may have received SIGSEGV), thus the following rules apply:
|
||||
// * You may not enter the dynamic linker. This means that we cannot call
|
||||
// any symbols in a shared library (inc libc). Because of this we replace
|
||||
// libc functions in linux_libc_support.h.
|
||||
// * You may not call syscalls via the libc wrappers. This rule is a subset
|
||||
// of the first rule but it bears repeating. We have direct wrappers
|
||||
// around the system calls in linux_syscall_support.h.
|
||||
// * You may not malloc. There's an alternative allocator in memory.h and
|
||||
// a canonical instance in the LinuxDumper object. We use the placement
|
||||
// new form to allocate objects and we don't delete them.
|
||||
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "client/minidump_file_writer-inl.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ucontext.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include "client/minidump_file_writer.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
#include "google_breakpad/common/minidump_cpu_amd64.h"
|
||||
#include "google_breakpad/common/minidump_cpu_x86.h"
|
||||
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "client/linux/minidump_writer//linux_dumper.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
|
||||
// These are additional minidump stream values which are specific to the linux
|
||||
// breakpad implementation.
|
||||
enum {
|
||||
MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */
|
||||
MD_LINUX_PROC_STATUS = 0x47670004, /* /proc/$x/status */
|
||||
MD_LINUX_LSB_RELEASE = 0x47670005, /* /etc/lsb-release */
|
||||
MD_LINUX_CMD_LINE = 0x47670006, /* /proc/$x/cmdline */
|
||||
MD_LINUX_ENVIRON = 0x47670007, /* /proc/$x/environ */
|
||||
MD_LINUX_AUXV = 0x47670008, /* /proc/$x/auxv */
|
||||
};
|
||||
|
||||
// Minidump defines register structures which are different from the raw
|
||||
// structures which we get from the kernel. These are platform specific
|
||||
// functions to juggle the ucontext and user structures into minidump format.
|
||||
#if defined(__i386)
|
||||
typedef MDRawContextX86 RawContextCPU;
|
||||
|
||||
// Write a uint16_t to memory
|
||||
// out: memory location to write to
|
||||
// v: value to write.
|
||||
static void U16(void* out, uint16_t v) {
|
||||
memcpy(out, &v, sizeof(v));
|
||||
}
|
||||
|
||||
// Write a uint32_t to memory
|
||||
// out: memory location to write to
|
||||
// v: value to write.
|
||||
static void U32(void* out, uint32_t v) {
|
||||
memcpy(out, &v, sizeof(v));
|
||||
}
|
||||
|
||||
// Juggle an x86 user_(fp|fpx|)regs_struct into minidump format
|
||||
// out: the minidump structure
|
||||
// info: the collection of register structures.
|
||||
static void CPUFillFromThreadInfo(MDRawContextX86 *out,
|
||||
const google_breakpad::ThreadInfo &info) {
|
||||
out->context_flags = MD_CONTEXT_X86_ALL;
|
||||
|
||||
out->dr0 = info.dregs[0];
|
||||
out->dr1 = info.dregs[1];
|
||||
out->dr2 = info.dregs[2];
|
||||
out->dr3 = info.dregs[3];
|
||||
// 4 and 5 deliberatly omitted because they aren't included in the minidump
|
||||
// format.
|
||||
out->dr6 = info.dregs[6];
|
||||
out->dr7 = info.dregs[7];
|
||||
|
||||
out->gs = info.regs.xgs;
|
||||
out->fs = info.regs.xfs;
|
||||
out->es = info.regs.xes;
|
||||
out->ds = info.regs.xds;
|
||||
|
||||
out->edi = info.regs.edi;
|
||||
out->esi = info.regs.esi;
|
||||
out->ebx = info.regs.ebx;
|
||||
out->edx = info.regs.edx;
|
||||
out->ecx = info.regs.ecx;
|
||||
out->eax = info.regs.eax;
|
||||
|
||||
out->ebp = info.regs.ebp;
|
||||
out->eip = info.regs.eip;
|
||||
out->cs = info.regs.xcs;
|
||||
out->eflags = info.regs.eflags;
|
||||
out->esp = info.regs.esp;
|
||||
out->ss = info.regs.xss;
|
||||
|
||||
out->float_save.control_word = info.fpregs.cwd;
|
||||
out->float_save.status_word = info.fpregs.swd;
|
||||
out->float_save.tag_word = info.fpregs.twd;
|
||||
out->float_save.error_offset = info.fpregs.fip;
|
||||
out->float_save.error_selector = info.fpregs.fcs;
|
||||
out->float_save.data_offset = info.fpregs.foo;
|
||||
out->float_save.data_selector = info.fpregs.fos;
|
||||
|
||||
// 8 registers * 10 bytes per register.
|
||||
memcpy(out->float_save.register_area, info.fpregs.st_space, 10 * 8);
|
||||
|
||||
// This matches the Intel fpsave format.
|
||||
U16(out->extended_registers + 0, info.fpregs.cwd);
|
||||
U16(out->extended_registers + 2, info.fpregs.swd);
|
||||
U16(out->extended_registers + 4, info.fpregs.twd);
|
||||
U16(out->extended_registers + 6, info.fpxregs.fop);
|
||||
U32(out->extended_registers + 8, info.fpxregs.fip);
|
||||
U16(out->extended_registers + 12, info.fpxregs.fcs);
|
||||
U32(out->extended_registers + 16, info.fpregs.foo);
|
||||
U16(out->extended_registers + 20, info.fpregs.fos);
|
||||
U32(out->extended_registers + 24, info.fpxregs.mxcsr);
|
||||
|
||||
memcpy(out->extended_registers + 32, &info.fpxregs.st_space, 128);
|
||||
memcpy(out->extended_registers + 160, &info.fpxregs.xmm_space, 128);
|
||||
}
|
||||
|
||||
// Juggle an x86 ucontext into minidump format
|
||||
// out: the minidump structure
|
||||
// info: the collection of register structures.
|
||||
static void CPUFillFromUContext(MDRawContextX86 *out, const ucontext *uc,
|
||||
const struct _libc_fpstate* fp) {
|
||||
const greg_t* regs = uc->uc_mcontext.gregs;
|
||||
|
||||
out->context_flags = MD_CONTEXT_X86_FULL |
|
||||
MD_CONTEXT_X86_FLOATING_POINT;
|
||||
|
||||
out->gs = regs[REG_GS];
|
||||
out->fs = regs[REG_FS];
|
||||
out->es = regs[REG_ES];
|
||||
out->ds = regs[REG_DS];
|
||||
|
||||
out->edi = regs[REG_EDI];
|
||||
out->esi = regs[REG_ESI];
|
||||
out->ebx = regs[REG_EBX];
|
||||
out->edx = regs[REG_EDX];
|
||||
out->ecx = regs[REG_ECX];
|
||||
out->eax = regs[REG_EAX];
|
||||
|
||||
out->ebp = regs[REG_EBP];
|
||||
out->eip = regs[REG_EIP];
|
||||
out->cs = regs[REG_CS];
|
||||
out->eflags = regs[REG_EFL];
|
||||
out->esp = regs[REG_UESP];
|
||||
out->ss = regs[REG_SS];
|
||||
|
||||
out->float_save.control_word = fp->cw;
|
||||
out->float_save.status_word = fp->sw;
|
||||
out->float_save.tag_word = fp->tag;
|
||||
out->float_save.error_offset = fp->ipoff;
|
||||
out->float_save.error_selector = fp->cssel;
|
||||
out->float_save.data_offset = fp->dataoff;
|
||||
out->float_save.data_selector = fp->datasel;
|
||||
|
||||
// 8 registers * 10 bytes per register.
|
||||
memcpy(out->float_save.register_area, fp->_st, 10 * 8);
|
||||
}
|
||||
|
||||
#elif defined(__x86_64)
|
||||
typedef MDRawContextAMD64 RawContextCPU;
|
||||
|
||||
static void CPUFillFromThreadInfo(MDRawContextAMD64 *out,
|
||||
const google_breakpad::ThreadInfo &info) {
|
||||
out->context_flags = MD_CONTEXT_AMD64_FULL |
|
||||
MD_CONTEXT_AMD64_SEGMENTS;
|
||||
|
||||
out->cs = info.regs.cs;
|
||||
|
||||
out->ds = info.regs.ds;
|
||||
out->es = info.regs.es;
|
||||
out->fs = info.regs.fs;
|
||||
out->gs = info.regs.gs;
|
||||
|
||||
out->ss = info.regs.ss;
|
||||
out->eflags = info.regs.eflags;
|
||||
|
||||
out->dr0 = info.dregs[0];
|
||||
out->dr1 = info.dregs[1];
|
||||
out->dr2 = info.dregs[2];
|
||||
out->dr3 = info.dregs[3];
|
||||
// 4 and 5 deliberatly omitted because they aren't included in the minidump
|
||||
// format.
|
||||
out->dr6 = info.dregs[6];
|
||||
out->dr7 = info.dregs[7];
|
||||
|
||||
out->rax = info.regs.rax;
|
||||
out->rcx = info.regs.rcx;
|
||||
out->rdx = info.regs.rdx;
|
||||
out->rbx = info.regs.rbx;
|
||||
|
||||
out->rsp = info.regs.rsp;
|
||||
|
||||
out->rbp = info.regs.rbp;
|
||||
out->rsi = info.regs.rsi;
|
||||
out->rdi = info.regs.rdi;
|
||||
out->r8 = info.regs.r8;
|
||||
out->r9 = info.regs.r9;
|
||||
out->r10 = info.regs.r10;
|
||||
out->r11 = info.regs.r11;
|
||||
out->r12 = info.regs.r12;
|
||||
out->r13 = info.regs.r13;
|
||||
out->r14 = info.regs.r14;
|
||||
out->r15 = info.regs.r15;
|
||||
|
||||
out->rip = info.regs.rip;
|
||||
|
||||
out->flt_save.control_word = info.fpregs.cwd;
|
||||
out->flt_save.status_word = info.fpregs.swd;
|
||||
out->flt_save.tag_word = info.fpregs.twd;
|
||||
out->flt_save.error_opcode = info.fpregs.fop;
|
||||
out->flt_save.error_offset = info.fpregs.rip;
|
||||
out->flt_save.error_selector = 0; // We don't have this.
|
||||
out->flt_save.data_offset = info.fpregs.rdp;
|
||||
out->flt_save.data_selector = 0; // We don't have this.
|
||||
out->flt_save.mx_csr = info.fpregs.mxcsr;
|
||||
out->flt_save.mx_csr_mask = info.fpregs.mxcsr_mask;
|
||||
memcpy(&out->flt_save.float_registers, &info.fpregs.st_space, 8 * 16);
|
||||
memcpy(&out->flt_save.xmm_registers, &info.fpregs.xmm_space, 16 * 16);
|
||||
}
|
||||
|
||||
static void CPUFillFromUContext(MDRawContextAMD64 *out, const ucontext *uc,
|
||||
const struct _libc_fpstate* fpregs) {
|
||||
const greg_t* regs = uc->gregs;
|
||||
|
||||
out->context_flags = MD_CONTEXT_AMD64_FULL;
|
||||
|
||||
out->cs = regs[REG_CSGSFS] & 0xffff;
|
||||
|
||||
out->fs = (regs[REG_CSGSFS] >> 32) & 0xffff;
|
||||
out->gs = (regs[REG_CSGSFS] >> 16) & 0xffff;
|
||||
|
||||
out->eflags = regs[REG_EFL];
|
||||
|
||||
out->rax = regs[REG_RAX];
|
||||
out->rcx = regs[REG_RCX];
|
||||
out->rdx = regs[REG_RDX];
|
||||
out->rbx = regs[REG_RBX];
|
||||
|
||||
out->rsp = regs[REG_RSP];
|
||||
out->rbp = regs[REG_RBP];
|
||||
out->rsi = regs[REG_RSI];
|
||||
out->rdi = regs[REG_RDI];
|
||||
out->r8 = regs[REG_R8];
|
||||
out->r9 = regs[REG_R9];
|
||||
out->r10 = regs[REG_R10];
|
||||
out->r11 = regs[REG_R11];
|
||||
out->r12 = regs[REG_R12];
|
||||
out->r13 = regs[REG_R13];
|
||||
out->r14 = regs[REG_R14];
|
||||
out->r15 = regs[REG_R15];
|
||||
|
||||
out->rip = regs[REG_RIP];
|
||||
|
||||
out->flt_save.control_word = fpregs->cwd;
|
||||
out->flt_save.status_word = fpregs->swd;
|
||||
out->flt_save.tag_word = fpregs->ftw;
|
||||
out->flt_save.error_opcode = fpregs->fop;
|
||||
out->flt_save.error_offset = fpregs->rip;
|
||||
out->flt_save.data_offset = fpregs->rdp;
|
||||
out->flt_save.error_selector = 0; // We don't have this.
|
||||
out->flt_save.data_selector = 0; // We don't have this.
|
||||
out->flt_save.mx_csr = fpregs->mxcsr;
|
||||
out->flt_save.mx_csr_mask = fpregs->mxcsr_mask;
|
||||
memcpy(&out->flt_save.float_registers, &fpregs->_st, 8 * 16);
|
||||
memcpy(&out->flt_save.xmm_registers, &fpregs->_xmm, 16 * 16);
|
||||
}
|
||||
|
||||
#else
|
||||
#error "This code has not been ported to your platform yet."
|
||||
#endif
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class MinidumpWriter {
|
||||
public:
|
||||
MinidumpWriter(const char* filename,
|
||||
pid_t crashing_pid,
|
||||
const ExceptionHandler::CrashContext* context)
|
||||
: filename_(filename),
|
||||
siginfo_(&context->siginfo),
|
||||
ucontext_(&context->context),
|
||||
float_state_(&context->float_state),
|
||||
crashing_tid_(context->tid),
|
||||
dumper_(crashing_pid) {
|
||||
}
|
||||
|
||||
bool Init() {
|
||||
return dumper_.Init() && minidump_writer_.Open(filename_) &&
|
||||
dumper_.ThreadsSuspend();
|
||||
}
|
||||
|
||||
~MinidumpWriter() {
|
||||
minidump_writer_.Close();
|
||||
dumper_.ThreadsResume();
|
||||
}
|
||||
|
||||
bool Dump() {
|
||||
// A minidump file contains a number of tagged streams. This is the number
|
||||
// of stream which we write.
|
||||
static const unsigned kNumWriters = 11;
|
||||
|
||||
TypedMDRVA<MDRawHeader> header(&minidump_writer_);
|
||||
TypedMDRVA<MDRawDirectory> dir(&minidump_writer_);
|
||||
if (!header.Allocate())
|
||||
return false;
|
||||
if (!dir.AllocateArray(kNumWriters))
|
||||
return false;
|
||||
memset(header.get(), 0, sizeof(MDRawHeader));
|
||||
|
||||
header.get()->signature = MD_HEADER_SIGNATURE;
|
||||
header.get()->version = MD_HEADER_VERSION;
|
||||
header.get()->time_date_stamp = time(NULL);
|
||||
header.get()->stream_count = kNumWriters;
|
||||
header.get()->stream_directory_rva = dir.position();
|
||||
|
||||
unsigned dir_index = 0;
|
||||
MDRawDirectory dirent;
|
||||
|
||||
if (!WriteThreadListStream(&dirent))
|
||||
return false;
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
if (!WriteMappings(&dirent))
|
||||
return false;
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
if (!WriteExceptionStream(&dirent))
|
||||
return false;
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
if (!WriteSystemInfoStream(&dirent))
|
||||
return false;
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_CPU_INFO;
|
||||
if (!WriteFile(&dirent.location, "/proc/cpuinfo"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_PROC_STATUS;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "status"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_LSB_RELEASE;
|
||||
if (!WriteFile(&dirent.location, "/etc/lsb-release"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_CMD_LINE;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "cmdline"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_ENVIRON;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "environ"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_AUXV;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "auxv"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_AUXV;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "maps"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
// If you add more directory entries, don't forget to update kNumWriters,
|
||||
// above.
|
||||
|
||||
dumper_.ThreadsResume();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write information about the threads.
|
||||
bool WriteThreadListStream(MDRawDirectory* dirent) {
|
||||
const unsigned num_threads = dumper_.threads().size();
|
||||
|
||||
TypedMDRVA<uint32_t> list(&minidump_writer_);
|
||||
if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThread)))
|
||||
return false;
|
||||
|
||||
dirent->stream_type = MD_THREAD_LIST_STREAM;
|
||||
dirent->location = list.location();
|
||||
|
||||
*list.get() = num_threads;
|
||||
|
||||
for (unsigned i = 0; i < num_threads; ++i) {
|
||||
MDRawThread thread;
|
||||
my_memset(&thread, 0, sizeof(thread));
|
||||
thread.thread_id = dumper_.threads()[i];
|
||||
// We have a different source of information for the crashing thread. If
|
||||
// we used the actual state of the thread we would find it running in the
|
||||
// signal handler with the alternative stack, which would be deeply
|
||||
// unhelpful.
|
||||
if (thread.thread_id == crashing_tid_) {
|
||||
const void* stack;
|
||||
size_t stack_len;
|
||||
if (!dumper_.GetStackInfo(&stack, &stack_len, GetStackPointer()))
|
||||
return false;
|
||||
UntypedMDRVA memory(&minidump_writer_);
|
||||
if (!memory.Allocate(stack_len))
|
||||
return false;
|
||||
uint8_t* stack_copy = (uint8_t*) dumper_.allocator()->Alloc(stack_len);
|
||||
dumper_.CopyFromProcess(stack_copy, thread.thread_id, stack, stack_len);
|
||||
memory.Copy(stack_copy, stack_len);
|
||||
thread.stack.start_of_memory_range = (uintptr_t) (stack);
|
||||
thread.stack.memory = memory.location();
|
||||
TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
|
||||
if (!cpu.Allocate())
|
||||
return false;
|
||||
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
|
||||
CPUFillFromUContext(cpu.get(), ucontext_, float_state_);
|
||||
thread.thread_context = cpu.location();
|
||||
crashing_thread_context_ = cpu.location();
|
||||
} else {
|
||||
ThreadInfo info;
|
||||
if (!dumper_.ThreadInfoGet(dumper_.threads()[i], &info))
|
||||
return false;
|
||||
UntypedMDRVA memory(&minidump_writer_);
|
||||
if (!memory.Allocate(info.stack_len))
|
||||
return false;
|
||||
uint8_t* stack_copy =
|
||||
(uint8_t*) dumper_.allocator()->Alloc(info.stack_len);
|
||||
dumper_.CopyFromProcess(stack_copy, thread.thread_id, info.stack,
|
||||
info.stack_len);
|
||||
memory.Copy(stack_copy, info.stack_len);
|
||||
thread.stack.start_of_memory_range = (uintptr_t)(info.stack);
|
||||
thread.stack.memory = memory.location();
|
||||
TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
|
||||
if (!cpu.Allocate())
|
||||
return false;
|
||||
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
|
||||
CPUFillFromThreadInfo(cpu.get(), info);
|
||||
thread.thread_context = cpu.location();
|
||||
}
|
||||
|
||||
list.CopyIndexAfterObject(i, &thread, sizeof(thread));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ShouldIncludeMapping(const MappingInfo& mapping) {
|
||||
if (mapping.name[0] == 0 || // we only want modules with filenames.
|
||||
mapping.offset || // we only want to include one mapping per shared lib.
|
||||
mapping.size < 4096) { // too small to get a signature for.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write information about the mappings in effect. Because we are using the
|
||||
// minidump format, the information about the mappings is pretty limited.
|
||||
// Because of this, we also include the full, unparsed, /proc/$x/maps file in
|
||||
// another stream in the file.
|
||||
bool WriteMappings(MDRawDirectory* dirent) {
|
||||
const unsigned num_mappings = dumper_.mappings().size();
|
||||
unsigned num_output_mappings = 0;
|
||||
|
||||
for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
|
||||
const MappingInfo& mapping = *dumper_.mappings()[i];
|
||||
if (ShouldIncludeMapping(mapping))
|
||||
num_output_mappings++;
|
||||
}
|
||||
|
||||
TypedMDRVA<uint32_t> list(&minidump_writer_);
|
||||
if (!list.AllocateObjectAndArray(num_output_mappings, sizeof(MDRawModule)))
|
||||
return false;
|
||||
|
||||
dirent->stream_type = MD_MODULE_LIST_STREAM;
|
||||
dirent->location = list.location();
|
||||
*list.get() = num_output_mappings;
|
||||
|
||||
for (unsigned i = 0, j = 0; i < num_mappings; ++i) {
|
||||
const MappingInfo& mapping = *dumper_.mappings()[i];
|
||||
if (!ShouldIncludeMapping(mapping))
|
||||
continue;
|
||||
|
||||
MDRawModule mod;
|
||||
my_memset(&mod, 0, sizeof(mod));
|
||||
mod.base_of_image = mapping.start_addr;
|
||||
mod.size_of_image = mapping.size;
|
||||
const size_t filepath_len = my_strlen(mapping.name);
|
||||
|
||||
// Figure out file name from path
|
||||
const char* filename_ptr = mapping.name + filepath_len - 1;
|
||||
while (filename_ptr >= mapping.name) {
|
||||
if (*filename_ptr == '/')
|
||||
break;
|
||||
filename_ptr--;
|
||||
}
|
||||
filename_ptr++;
|
||||
const size_t filename_len = mapping.name + filepath_len - filename_ptr;
|
||||
|
||||
uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX];
|
||||
uint8_t* cv_ptr = cv_buf;
|
||||
UntypedMDRVA cv(&minidump_writer_);
|
||||
if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1))
|
||||
return false;
|
||||
|
||||
const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE;
|
||||
memcpy(cv_ptr, &cv_signature, sizeof(cv_signature));
|
||||
cv_ptr += sizeof(cv_signature);
|
||||
|
||||
{
|
||||
// We XOR the first page of the file to get a signature for it.
|
||||
uint8_t xor_buf[sizeof(MDGUID)];
|
||||
size_t done = 0;
|
||||
uint8_t* signature = cv_ptr;
|
||||
cv_ptr += sizeof(xor_buf);
|
||||
|
||||
my_memset(signature, 0, sizeof(xor_buf));
|
||||
while (done < 4096) {
|
||||
dumper_.CopyFromProcess(xor_buf, crashing_tid_,
|
||||
(void *) (mod.base_of_image + done),
|
||||
sizeof(xor_buf));
|
||||
for (unsigned i = 0; i < sizeof(xor_buf); ++i)
|
||||
signature[i] ^= xor_buf[i];
|
||||
done += sizeof(xor_buf);
|
||||
}
|
||||
my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
|
||||
cv_ptr += sizeof(uint32_t);
|
||||
}
|
||||
|
||||
// Write pdb_file_name
|
||||
memcpy(cv_ptr, filename_ptr, filename_len + 1);
|
||||
cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1);
|
||||
|
||||
mod.cv_record = cv.location();
|
||||
|
||||
MDLocationDescriptor ld;
|
||||
if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld))
|
||||
return false;
|
||||
mod.module_name_rva = ld.rva;
|
||||
|
||||
list.CopyIndexAfterObject(j++, &mod, sizeof(mod));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteExceptionStream(MDRawDirectory* dirent) {
|
||||
TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_);
|
||||
if (!exc.Allocate())
|
||||
return false;
|
||||
my_memset(exc.get(), 0, sizeof(MDRawExceptionStream));
|
||||
|
||||
dirent->stream_type = MD_EXCEPTION_STREAM;
|
||||
dirent->location = exc.location();
|
||||
|
||||
exc.get()->thread_id = crashing_tid_;
|
||||
exc.get()->exception_record.exception_code = siginfo_->si_signo;
|
||||
exc.get()->exception_record.exception_address =
|
||||
(uintptr_t) siginfo_->si_addr;
|
||||
exc.get()->thread_context = crashing_thread_context_;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteSystemInfoStream(MDRawDirectory* dirent) {
|
||||
TypedMDRVA<MDRawSystemInfo> si(&minidump_writer_);
|
||||
if (!si.Allocate())
|
||||
return false;
|
||||
my_memset(si.get(), 0, sizeof(MDRawSystemInfo));
|
||||
|
||||
dirent->stream_type = MD_SYSTEM_INFO_STREAM;
|
||||
dirent->location = si.location();
|
||||
|
||||
WriteCPUInformation(si.get());
|
||||
WriteOSInformation(si.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
#if defined(__i386)
|
||||
uintptr_t GetStackPointer() {
|
||||
return ucontext_->uc_mcontext.gregs[REG_ESP];
|
||||
}
|
||||
#elif defined(__x86_64)
|
||||
uintptr_t GetStackPointer() {
|
||||
return ucontext_->uc_mcontext.gregs[REG_RSP];
|
||||
}
|
||||
#else
|
||||
#error "This code has not been ported to your platform yet."
|
||||
#endif
|
||||
|
||||
void NullifyDirectoryEntry(MDRawDirectory* dirent) {
|
||||
dirent->stream_type = 0;
|
||||
dirent->location.data_size = 0;
|
||||
dirent->location.rva = 0;
|
||||
}
|
||||
|
||||
bool WriteCPUInformation(MDRawSystemInfo* sys_info) {
|
||||
char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0};
|
||||
static const char vendor_id_name[] = "vendor_id";
|
||||
static const size_t vendor_id_name_length = sizeof(vendor_id_name) - 1;
|
||||
|
||||
struct CpuInfoEntry {
|
||||
const char* info_name;
|
||||
int value;
|
||||
bool found;
|
||||
} cpu_info_table[] = {
|
||||
{ "processor", -1, false },
|
||||
{ "model", 0, false },
|
||||
{ "stepping", 0, false },
|
||||
{ "cpuid level", 0, false },
|
||||
};
|
||||
|
||||
// processor_architecture should always be set, do this first
|
||||
sys_info->processor_architecture =
|
||||
#if defined(__i386)
|
||||
MD_CPU_ARCHITECTURE_X86;
|
||||
#elif defined(__x86_64)
|
||||
MD_CPU_ARCHITECTURE_AMD64;
|
||||
#else
|
||||
#error "Unknown CPU arch"
|
||||
#endif
|
||||
|
||||
const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
{
|
||||
PageAllocator allocator;
|
||||
LineReader* const line_reader = new(allocator) LineReader(fd);
|
||||
const char* line;
|
||||
unsigned line_len;
|
||||
while (line_reader->GetNextLine(&line, &line_len)) {
|
||||
for (size_t i = 0;
|
||||
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
|
||||
i++) {
|
||||
CpuInfoEntry* entry = &cpu_info_table[i];
|
||||
if (entry->found)
|
||||
continue;
|
||||
if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
|
||||
const char* value = strchr(line, ':');
|
||||
if (!value)
|
||||
continue;
|
||||
|
||||
// the above strncmp only matches the prefix, it might be the wrong
|
||||
// line. i.e. we matched "model name" instead of "model".
|
||||
// check and make sure there is only spaces between the prefix and
|
||||
// the colon.
|
||||
const char* space_ptr = line + strlen(entry->info_name);
|
||||
for (; space_ptr < value; space_ptr++) {
|
||||
if (!isspace(*space_ptr)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (space_ptr != value)
|
||||
continue;
|
||||
|
||||
sscanf(++value, " %d", &(entry->value));
|
||||
entry->found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// special case for vendor_id
|
||||
if (!strncmp(line, vendor_id_name, vendor_id_name_length)) {
|
||||
const char* value = strchr(line, ':');
|
||||
if (!value)
|
||||
goto popline;
|
||||
|
||||
// skip ':" and all the spaces that follows
|
||||
do {
|
||||
value++;
|
||||
} while (isspace(*value));
|
||||
|
||||
if (*value) {
|
||||
size_t length = strlen(value);
|
||||
if (length == 0)
|
||||
goto popline;
|
||||
// we don't want the trailing newline
|
||||
if (value[length - 1] == '\n')
|
||||
length--;
|
||||
// ensure we have space for the value
|
||||
if (length < sizeof(vendor_id))
|
||||
strncpy(vendor_id, value, length);
|
||||
}
|
||||
}
|
||||
|
||||
popline:
|
||||
line_reader->PopLine(line_len);
|
||||
}
|
||||
sys_close(fd);
|
||||
}
|
||||
|
||||
// make sure we got everything we wanted
|
||||
for (size_t i = 0;
|
||||
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
|
||||
i++) {
|
||||
if (!cpu_info_table[i].found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// /proc/cpuinfo contains cpu id, change it into number by adding one.
|
||||
cpu_info_table[0].value++;
|
||||
|
||||
sys_info->number_of_processors = cpu_info_table[0].value;
|
||||
sys_info->processor_level = cpu_info_table[3].value;
|
||||
sys_info->processor_revision = cpu_info_table[1].value << 8 |
|
||||
cpu_info_table[2].value;
|
||||
|
||||
if (vendor_id[0] != '\0') {
|
||||
memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
|
||||
sizeof(sys_info->cpu.x86_cpu_info.vendor_id));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteFile(MDLocationDescriptor* result, const char* filename) {
|
||||
const int fd = sys_open(filename, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
// We can't stat the files because several of the files that we want to
|
||||
// read are kernel seqfiles, which always have a length of zero. So we have
|
||||
// to read as much as we can into a buffer.
|
||||
static const unsigned kMaxFileSize = 1024;
|
||||
uint8_t* data = (uint8_t*) dumper_.allocator()->Alloc(kMaxFileSize);
|
||||
|
||||
size_t done = 0;
|
||||
while (done < kMaxFileSize) {
|
||||
ssize_t r;
|
||||
do {
|
||||
r = sys_read(fd, data + done, kMaxFileSize - done);
|
||||
} while (r == -1 && errno == EINTR);
|
||||
|
||||
if (r < 1)
|
||||
break;
|
||||
done += r;
|
||||
}
|
||||
sys_close(fd);
|
||||
|
||||
if (!done)
|
||||
return false;
|
||||
|
||||
UntypedMDRVA memory(&minidump_writer_);
|
||||
if (!memory.Allocate(done))
|
||||
return false;
|
||||
memory.Copy(data, done);
|
||||
*result = memory.location();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteOSInformation(MDRawSystemInfo* sys_info) {
|
||||
sys_info->platform_id = MD_OS_LINUX;
|
||||
|
||||
struct utsname uts;
|
||||
if (uname(&uts))
|
||||
return false;
|
||||
|
||||
static const size_t buf_len = 512;
|
||||
char buf[buf_len] = {0};
|
||||
size_t space_left = buf_len - 1;
|
||||
const char* info_table[] = {
|
||||
uts.sysname,
|
||||
uts.release,
|
||||
uts.version,
|
||||
uts.machine,
|
||||
NULL
|
||||
};
|
||||
bool first_item = true;
|
||||
for (const char** cur_info = info_table; *cur_info; cur_info++) {
|
||||
static const char* separator = " ";
|
||||
size_t separator_len = strlen(separator);
|
||||
size_t info_len = strlen(*cur_info);
|
||||
if (info_len == 0)
|
||||
continue;
|
||||
|
||||
if (space_left < info_len + (first_item ? 0 : separator_len))
|
||||
break;
|
||||
|
||||
if (!first_item) {
|
||||
strcat(buf, separator);
|
||||
space_left -= separator_len;
|
||||
}
|
||||
|
||||
first_item = false;
|
||||
strcat(buf, *cur_info);
|
||||
space_left -= info_len;
|
||||
}
|
||||
|
||||
MDLocationDescriptor location;
|
||||
if (!minidump_writer_.WriteString(buf, 0, &location))
|
||||
return false;
|
||||
sys_info->csd_version_rva = location.rva;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteProcFile(MDLocationDescriptor* result, pid_t pid,
|
||||
const char* filename) {
|
||||
char buf[80];
|
||||
memcpy(buf, "/proc/", 6);
|
||||
const unsigned pid_len = my_int_len(pid);
|
||||
my_itos(buf + 6, pid, pid_len);
|
||||
buf[6 + pid_len] = '/';
|
||||
memcpy(buf + 6 + pid_len + 1, filename, my_strlen(filename) + 1);
|
||||
return WriteFile(result, buf);
|
||||
}
|
||||
|
||||
const char* const filename_; // output filename
|
||||
const siginfo_t* const siginfo_; // from the signal handler (see sigaction)
|
||||
const struct ucontext* const ucontext_; // also from the signal handler
|
||||
const struct _libc_fpstate* const float_state_; // ditto
|
||||
const pid_t crashing_tid_; // the process which actually crashed
|
||||
LinuxDumper dumper_;
|
||||
MinidumpFileWriter minidump_writer_;
|
||||
MDLocationDescriptor crashing_thread_context_;
|
||||
};
|
||||
|
||||
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size) {
|
||||
if (blob_size != sizeof(ExceptionHandler::CrashContext))
|
||||
return false;
|
||||
const ExceptionHandler::CrashContext* context =
|
||||
reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
|
||||
MinidumpWriter writer(filename, crashing_process, context);
|
||||
if (!writer.Init())
|
||||
return false;
|
||||
return writer.Dump();
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
@@ -1,79 +0,0 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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 <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
// This provides a wrapper around system calls which may be
|
||||
// interrupted by a signal and return EINTR. See man 7 signal.
|
||||
#define HANDLE_EINTR(x) ({ \
|
||||
typeof(x) __eintr_result__; \
|
||||
do { \
|
||||
__eintr_result__ = x; \
|
||||
} while (__eintr_result__ == -1 && errno == EINTR); \
|
||||
__eintr_result__;\
|
||||
})
|
||||
|
||||
namespace {
|
||||
typedef testing::Test MinidumpWriterTest;
|
||||
}
|
||||
|
||||
TEST(MinidumpWriterTest, Setup) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
char templ[] = "/tmp/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(templ, &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
unlink(templ);
|
||||
|
||||
close(fds[1]);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,310 +0,0 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Framework to provide a simple C API to crash reporting for
|
||||
// applications. By default, if any machine-level exception (e.g.,
|
||||
// EXC_BAD_ACCESS) occurs, it will be handled by the BreakpadRef
|
||||
// object as follows:
|
||||
//
|
||||
// 1. Create a minidump file (see Breakpad for details)
|
||||
// 2. Prompt the user (using CFUserNotification)
|
||||
// 3. Invoke a command line reporting tool to send the minidump to a
|
||||
// server
|
||||
//
|
||||
// By specifying parameters to the BreakpadCreate function, you can
|
||||
// modify the default behavior to suit your needs and wants and
|
||||
// desires.
|
||||
|
||||
typedef void *BreakpadRef;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
// Keys for configuration file
|
||||
#define kReporterMinidumpDirectoryKey "MinidumpDir"
|
||||
#define kReporterMinidumpIDKey "MinidumpID"
|
||||
|
||||
// The default subdirectory of the Library to put crash dumps in
|
||||
// The subdirectory is
|
||||
// ~/Library/<kDefaultLibrarySubdirectory>/<GoogleBreakpadProduct>
|
||||
#define kDefaultLibrarySubdirectory "Breakpad"
|
||||
|
||||
// Specify some special keys to be used in the configuration file that is
|
||||
// generated by Breakpad and consumed by the crash_sender.
|
||||
#define BREAKPAD_PRODUCT "BreakpadProduct"
|
||||
#define BREAKPAD_PRODUCT_DISPLAY "BreakpadProductDisplay"
|
||||
#define BREAKPAD_VERSION "BreakpadVersion"
|
||||
#define BREAKPAD_VENDOR "BreakpadVendor"
|
||||
#define BREAKPAD_URL "BreakpadURL"
|
||||
#define BREAKPAD_REPORT_INTERVAL "BreakpadReportInterval"
|
||||
#define BREAKPAD_SKIP_CONFIRM "BreakpadSkipConfirm"
|
||||
#define BREAKPAD_CONFIRM_TIMEOUT "BreakpadConfirmTimeout"
|
||||
#define BREAKPAD_SEND_AND_EXIT "BreakpadSendAndExit"
|
||||
#define BREAKPAD_DUMP_DIRECTORY "BreakpadMinidumpLocation"
|
||||
#define BREAKPAD_INSPECTOR_LOCATION "BreakpadInspectorLocation"
|
||||
#define BREAKPAD_REPORTER_EXE_LOCATION \
|
||||
"BreakpadReporterExeLocation"
|
||||
#define BREAKPAD_LOGFILES "BreakpadLogFiles"
|
||||
#define BREAKPAD_LOGFILE_UPLOAD_SIZE "BreakpadLogFileTailSize"
|
||||
#define BREAKPAD_REQUEST_COMMENTS "BreakpadRequestComments"
|
||||
#define BREAKPAD_COMMENTS "BreakpadComments"
|
||||
#define BREAKPAD_REQUEST_EMAIL "BreakpadRequestEmail"
|
||||
#define BREAKPAD_EMAIL "BreakpadEmail"
|
||||
#define BREAKPAD_SERVER_TYPE "BreakpadServerType"
|
||||
#define BREAKPAD_SERVER_PARAMETER_DICT "BreakpadServerParameters"
|
||||
|
||||
// The keys below are NOT user supplied, and are used internally.
|
||||
#define BREAKPAD_PROCESS_START_TIME "BreakpadProcStartTime"
|
||||
#define BREAKPAD_PROCESS_UP_TIME "BreakpadProcessUpTime"
|
||||
#define BREAKPAD_PROCESS_CRASH_TIME "BreakpadProcessCrashTime"
|
||||
#define BREAKPAD_LOGFILE_KEY_PREFIX "BreakpadAppLogFile"
|
||||
#define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_"
|
||||
#define BREAKPAD_ON_DEMAND "BreakpadOnDemand"
|
||||
|
||||
// Optional user-defined function to dec to decide if we should handle
|
||||
// this crash or forward it along.
|
||||
// Return true if you want Breakpad to handle it.
|
||||
// Return false if you want Breakpad to skip it
|
||||
// The exception handler always returns false, as if SEND_AND_EXIT were false
|
||||
// (which means the next exception handler will take the exception)
|
||||
typedef bool (*BreakpadFilterCallback)(int exception_type,
|
||||
int exception_code,
|
||||
mach_port_t crashing_thread,
|
||||
void *context);
|
||||
|
||||
// Create a new BreakpadRef object and install it as an exception
|
||||
// handler. The |parameters| will typically be the contents of your
|
||||
// bundle's Info.plist.
|
||||
//
|
||||
// You can also specify these additional keys for customizable behavior:
|
||||
// Key: Value:
|
||||
// BREAKPAD_PRODUCT Product name (e.g., "MyAwesomeProduct")
|
||||
// This one is used as the key to identify
|
||||
// the product when uploading
|
||||
// REQUIRED
|
||||
//
|
||||
// BREAKPAD_PRODUCT_DISPLAY This is the display name, e.g. a pretty
|
||||
// name for the product when the crash_sender
|
||||
// pops up UI for the user. Falls back to
|
||||
// BREAKPAD_PRODUCT if not specified.
|
||||
//
|
||||
// BREAKPAD_VERSION Product version (e.g., 1.2.3), used
|
||||
// as metadata for crash report
|
||||
// REQUIRED
|
||||
//
|
||||
// BREAKPAD_VENDOR Vendor name, used in UI (e.g. "A report has
|
||||
// been created that you can send to <vendor>")
|
||||
//
|
||||
// BREAKPAD_URL URL destination for reporting
|
||||
// REQUIRED
|
||||
//
|
||||
// BREAKPAD_REPORT_INTERVAL # of seconds between sending
|
||||
// reports. If an additional report is
|
||||
// generated within this time, it will
|
||||
// be ignored. Default: 3600sec.
|
||||
// Specify 0 to send all reports.
|
||||
//
|
||||
// BREAKPAD_SKIP_CONFIRM If true, the reporter will send the report
|
||||
// without any user intervention.
|
||||
// Defaults to NO
|
||||
//
|
||||
// BREAKPAD_CONFIRM_TIMEOUT Number of seconds before the upload
|
||||
// confirmation dialog will be automatically
|
||||
// dismissed (cancelling the upload).
|
||||
// Default: 300 seconds (min of 60).
|
||||
// Specify 0 to prevent timeout.
|
||||
//
|
||||
// BREAKPAD_SEND_AND_EXIT If true, the handler will exit after sending.
|
||||
// This will prevent any other handler (e.g.,
|
||||
// CrashReporter) from getting the crash.
|
||||
// Defaults TO YES
|
||||
//
|
||||
// BREAKPAD_DUMP_DIRECTORY The directory to store crash-dumps
|
||||
// in. By default, we use
|
||||
// ~/Library/Breakpad/<BREAKPAD_PRODUCT>
|
||||
// The path you specify here is tilde-expanded.
|
||||
//
|
||||
// BREAKPAD_INSPECTOR_LOCATION The full path to the Inspector executable.
|
||||
// Defaults to <Framework resources>/Inspector
|
||||
//
|
||||
// BREAKPAD_REPORTER_EXE_LOCATION The full path to the Reporter/sender
|
||||
// executable.
|
||||
// Default:
|
||||
// <Framework Resources>/crash_report_sender.app
|
||||
//
|
||||
// BREAKPAD_LOGFILES Indicates an array of log file paths that
|
||||
// should be uploaded at crash time.
|
||||
//
|
||||
// BREAKPAD_REQUEST_COMMENTS If true, the message dialog will have a
|
||||
// text box for the user to enter comments.
|
||||
// Default: NO
|
||||
//
|
||||
// BREAKPAD_REQUEST_EMAIL If true and BREAKPAD_REQUEST_COMMENTS is also
|
||||
// true, the message dialog will have a text
|
||||
// box for the user to enter their email address.
|
||||
// Default: NO
|
||||
//
|
||||
// BREAKPAD_SERVER_TYPE A parameter that tells Breakpad how to
|
||||
// rewrite the upload parameters for a specific
|
||||
// server type. The currently valid values are
|
||||
// 'socorro' or 'google'. If you want to add
|
||||
// other types, see the function in
|
||||
// crash_report_sender.m that maps parameters to
|
||||
// URL parameters. Defaults to 'google'.
|
||||
//
|
||||
// BREAKPAD_SERVER_PARAMETER_DICT A plist dictionary of static
|
||||
// parameters that are uploaded to the
|
||||
// server. The parameters are sent as
|
||||
// is to the crash server. Their
|
||||
// content isn't added to the minidump
|
||||
// but pass as URL parameters when
|
||||
// uploading theminidump to the crash
|
||||
// server.
|
||||
//=============================================================================
|
||||
// The BREAKPAD_PRODUCT, BREAKPAD_VERSION and BREAKPAD_URL are
|
||||
// required to have non-NULL values. By default, the BREAKPAD_PRODUCT
|
||||
// will be the CFBundleName and the BREAKPAD_VERSION will be the
|
||||
// CFBundleVersion when these keys are present in the bundle's
|
||||
// Info.plist, which is usually passed in to BreakpadCreate() as an
|
||||
// NSDictionary (you could also pass in another dictionary that had
|
||||
// the same keys configured). If the BREAKPAD_PRODUCT or
|
||||
// BREAKPAD_VERSION are ultimately undefined, BreakpadCreate() will
|
||||
// fail. You have been warned.
|
||||
//
|
||||
// If you are running in a debugger, Breakpad will not install, unless the
|
||||
// BREAKPAD_IGNORE_DEBUGGER envionment variable is set and/or non-zero.
|
||||
//
|
||||
// The BREAKPAD_SKIP_CONFIRM and BREAKPAD_SEND_AND_EXIT default
|
||||
// values are NO and YES. However, they can be controlled by setting their
|
||||
// values in a user or global plist.
|
||||
//
|
||||
// It's easiest to use Breakpad via the Framework, but if you're compiling the
|
||||
// code in directly, BREAKPAD_INSPECTOR_LOCATION and
|
||||
// BREAKPAD_REPORTER_EXE_LOCATION allow you to specify custom paths
|
||||
// to the helper executables.
|
||||
//
|
||||
//=============================================================================
|
||||
// The following are NOT user-supplied but are documented here for
|
||||
// completeness. They are calculated by Breakpad during initialization &
|
||||
// crash-dump generation, or entered in by the user.
|
||||
//
|
||||
// BREAKPAD_PROCESS_START_TIME The time the process started.
|
||||
//
|
||||
// BREAKPAD_PROCESS_CRASH_TIME The time the process crashed.
|
||||
//
|
||||
// BREAKPAD_PROCESS_UP_TIME The total time the process has been
|
||||
// running. This parameter is not set
|
||||
// until the crash-dump-generation phase.
|
||||
//
|
||||
// BREAKPAD_LOGFILE_KEY_PREFIX Used to find out which parameters in the
|
||||
// parameter dictionary correspond to log
|
||||
// file paths.
|
||||
//
|
||||
// BREAKPAD_SERVER_PARAMETER_PREFIX This prefix is used by Breakpad
|
||||
// internally, because Breakpad uses
|
||||
// the same dictionary internally to
|
||||
// track both its internal
|
||||
// configuration parameters and
|
||||
// parameters meant to be uploaded
|
||||
// to the server. This string is
|
||||
// used internally by Breakpad to
|
||||
// prefix user-supplied parameter
|
||||
// names so those can be sent to the
|
||||
// server without leaking Breakpad's
|
||||
// internal values.
|
||||
//
|
||||
// BREAKPAD_ON_DEMAND Used internally to indicate to the
|
||||
// Reporter that we're sending on-demand,
|
||||
// not as result of a crash.
|
||||
//
|
||||
// BREAKPAD_COMMENTS The text the user provided as comments.
|
||||
// Only used in crash_report_sender.
|
||||
|
||||
// Returns a new BreakpadRef object on success, NULL otherwise.
|
||||
BreakpadRef BreakpadCreate(NSDictionary *parameters);
|
||||
|
||||
// Uninstall and release the data associated with |ref|.
|
||||
void BreakpadRelease(BreakpadRef ref);
|
||||
|
||||
// Clients may set an optional callback which gets called when a crash
|
||||
// occurs. The callback function should return |true| if we should
|
||||
// handle the crash, generate a crash report, etc. or |false| if we
|
||||
// should ignore it and forward the crash (normally to CrashReporter).
|
||||
// Context is a pointer to arbitrary data to make the callback with.
|
||||
void BreakpadSetFilterCallback(BreakpadRef ref,
|
||||
BreakpadFilterCallback callback,
|
||||
void *context);
|
||||
|
||||
// User defined key and value string storage. Generally this is used
|
||||
// to configure Breakpad's internal operation, such as whether the
|
||||
// crash_sender should prompt the user, or the filesystem location for
|
||||
// the minidump file. See Breakpad.h for some parameters that can be
|
||||
// set. Anything longer than 255 bytes will be truncated. Note that
|
||||
// the string is converted to UTF8 before truncation, so any multibyte
|
||||
// character that straddles the 255(256 - 1 for terminator) byte limit
|
||||
// will be mangled.
|
||||
//
|
||||
// A maximum number of 64 key/value pairs are supported. An assert()
|
||||
// will fire if more than this number are set. Unfortunately, right
|
||||
// now, the same dictionary is used for both Breakpad's parameters AND
|
||||
// the Upload parameters.
|
||||
//
|
||||
// TODO (nealsid): Investigate how necessary this is if we don't
|
||||
// automatically upload parameters to the server anymore.
|
||||
// TODO (nealsid): separate server parameter dictionary from the
|
||||
// dictionary used to configure Breakpad, and document limits for each
|
||||
// independently.
|
||||
void BreakpadSetKeyValue(BreakpadRef ref, NSString *key, NSString *value);
|
||||
NSString *BreakpadKeyValue(BreakpadRef ref, NSString *key);
|
||||
void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key);
|
||||
|
||||
// You can use this method to specify parameters that will be uploaded
|
||||
// to the crash server. They will be automatically encoded as
|
||||
// necessary. Note that as mentioned above there are limits on both
|
||||
// the number of keys and their length.
|
||||
void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key,
|
||||
NSString *value);
|
||||
|
||||
// This method will remove a previously-added parameter from the
|
||||
// upload parameter set.
|
||||
void BreakpadRemoveUploadParameter(BreakpadRef ref, NSString *key);
|
||||
|
||||
// Add a log file for Breakpad to read and send upon crash dump
|
||||
void BreakpadAddLogFile(BreakpadRef ref, NSString *logPathname);
|
||||
|
||||
// Generate a minidump and send
|
||||
void BreakpadGenerateAndSendReport(BreakpadRef ref);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,999 +0,0 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
|
||||
#define VERBOSE 0
|
||||
|
||||
#if VERBOSE
|
||||
static bool gDebugLog = true;
|
||||
#else
|
||||
static bool gDebugLog = false;
|
||||
#endif
|
||||
|
||||
#define DEBUGLOG if (gDebugLog) fprintf
|
||||
#define IGNORE_DEBUGGER "BREAKPAD_IGNORE_DEBUGGER"
|
||||
|
||||
#import "common/mac/MachIPC.h"
|
||||
#import "common/mac/SimpleStringDictionary.h"
|
||||
|
||||
#import "client/mac/crash_generation/Inspector.h"
|
||||
#import "client/mac/handler/exception_handler.h"
|
||||
#import "client/mac/Framework/Breakpad.h"
|
||||
#import "client/mac/Framework/OnDemandServer.h"
|
||||
#import "client/mac/handler/protected_memory_allocator.h"
|
||||
|
||||
#import <sys/stat.h>
|
||||
#import <sys/sysctl.h>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
using google_breakpad::KeyValueEntry;
|
||||
using google_breakpad::SimpleStringDictionary;
|
||||
using google_breakpad::SimpleStringDictionaryIterator;
|
||||
|
||||
//=============================================================================
|
||||
// We want any memory allocations which are used by breakpad during the
|
||||
// exception handling process (after a crash has happened) to be read-only
|
||||
// to prevent them from being smashed before a crash occurs. Unfortunately
|
||||
// we cannot protect against smashes to our exception handling thread's
|
||||
// stack.
|
||||
//
|
||||
// NOTE: Any memory allocations which are not used during the exception
|
||||
// handling process may be allocated in the normal ways.
|
||||
//
|
||||
// The ProtectedMemoryAllocator class provides an Allocate() method which
|
||||
// we'll using in conjunction with placement operator new() to control
|
||||
// allocation of C++ objects. Note that we don't use operator delete()
|
||||
// but instead call the objects destructor directly: object->~ClassName();
|
||||
//
|
||||
ProtectedMemoryAllocator *gMasterAllocator = NULL;
|
||||
ProtectedMemoryAllocator *gKeyValueAllocator = NULL;
|
||||
ProtectedMemoryAllocator *gBreakpadAllocator = NULL;
|
||||
|
||||
// Mutex for thread-safe access to the key/value dictionary used by breakpad.
|
||||
// It's a global instead of an instance variable of Breakpad
|
||||
// since it can't live in a protected memory area.
|
||||
pthread_mutex_t gDictionaryMutex;
|
||||
|
||||
//=============================================================================
|
||||
// Stack-based object for thread-safe access to a memory-protected region.
|
||||
// It's assumed that normally the memory block (allocated by the allocator)
|
||||
// is protected (read-only). Creating a stack-based instance of
|
||||
// ProtectedMemoryLocker will unprotect this block after taking the lock.
|
||||
// Its destructor will first re-protect the memory then release the lock.
|
||||
class ProtectedMemoryLocker {
|
||||
public:
|
||||
// allocator may be NULL, in which case no Protect() or Unprotect() calls
|
||||
// will be made, but a lock will still be taken
|
||||
ProtectedMemoryLocker(pthread_mutex_t *mutex,
|
||||
ProtectedMemoryAllocator *allocator)
|
||||
: mutex_(mutex), allocator_(allocator) {
|
||||
// Lock the mutex
|
||||
assert(pthread_mutex_lock(mutex_) == 0);
|
||||
|
||||
// Unprotect the memory
|
||||
if (allocator_ ) {
|
||||
allocator_->Unprotect();
|
||||
}
|
||||
}
|
||||
|
||||
~ProtectedMemoryLocker() {
|
||||
// First protect the memory
|
||||
if (allocator_) {
|
||||
allocator_->Protect();
|
||||
}
|
||||
|
||||
// Then unlock the mutex
|
||||
assert(pthread_mutex_unlock(mutex_) == 0);
|
||||
};
|
||||
|
||||
private:
|
||||
// Keep anybody from ever creating one of these things not on the stack.
|
||||
ProtectedMemoryLocker() { }
|
||||
ProtectedMemoryLocker(const ProtectedMemoryLocker&);
|
||||
ProtectedMemoryLocker & operator=(ProtectedMemoryLocker&);
|
||||
|
||||
pthread_mutex_t *mutex_;
|
||||
ProtectedMemoryAllocator *allocator_;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
class Breakpad {
|
||||
public:
|
||||
// factory method
|
||||
static Breakpad *Create(NSDictionary *parameters) {
|
||||
// Allocate from our special allocation pool
|
||||
Breakpad *breakpad =
|
||||
new (gBreakpadAllocator->Allocate(sizeof(Breakpad)))
|
||||
Breakpad();
|
||||
|
||||
if (!breakpad)
|
||||
return NULL;
|
||||
|
||||
if (!breakpad->Initialize(parameters)) {
|
||||
// Don't use operator delete() here since we allocated from special pool
|
||||
breakpad->~Breakpad();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return breakpad;
|
||||
}
|
||||
|
||||
~Breakpad();
|
||||
|
||||
void SetKeyValue(NSString *key, NSString *value);
|
||||
NSString *KeyValue(NSString *key);
|
||||
void RemoveKeyValue(NSString *key);
|
||||
|
||||
void GenerateAndSendReport();
|
||||
|
||||
void SetFilterCallback(BreakpadFilterCallback callback, void *context) {
|
||||
filter_callback_ = callback;
|
||||
filter_callback_context_ = context;
|
||||
}
|
||||
|
||||
private:
|
||||
Breakpad()
|
||||
: handler_(NULL),
|
||||
config_params_(NULL),
|
||||
send_and_exit_(true),
|
||||
filter_callback_(NULL),
|
||||
filter_callback_context_(NULL) {
|
||||
inspector_path_[0] = 0;
|
||||
}
|
||||
|
||||
bool Initialize(NSDictionary *parameters);
|
||||
|
||||
bool ExtractParameters(NSDictionary *parameters);
|
||||
|
||||
// Dispatches to HandleException()
|
||||
static bool ExceptionHandlerDirectCallback(void *context,
|
||||
int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t crashing_thread);
|
||||
|
||||
bool HandleException(int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t crashing_thread);
|
||||
|
||||
// Since ExceptionHandler (w/o namespace) is defined as typedef in OSX's
|
||||
// MachineExceptions.h, we have to explicitly name the handler.
|
||||
google_breakpad::ExceptionHandler *handler_; // The actual handler (STRONG)
|
||||
|
||||
char inspector_path_[PATH_MAX]; // Path to inspector tool
|
||||
|
||||
SimpleStringDictionary *config_params_; // Create parameters (STRONG)
|
||||
|
||||
OnDemandServer inspector_;
|
||||
|
||||
bool send_and_exit_; // Exit after sending, if true
|
||||
|
||||
BreakpadFilterCallback filter_callback_;
|
||||
void *filter_callback_context_;
|
||||
};
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Helper functions
|
||||
|
||||
//=============================================================================
|
||||
// Helper functions
|
||||
|
||||
//=============================================================================
|
||||
static BOOL IsDebuggerActive() {
|
||||
BOOL result = NO;
|
||||
NSUserDefaults *stdDefaults = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
// We check both defaults and the environment variable here
|
||||
|
||||
BOOL ignoreDebugger = [stdDefaults boolForKey:@IGNORE_DEBUGGER];
|
||||
|
||||
if (!ignoreDebugger) {
|
||||
char *ignoreDebuggerStr = getenv(IGNORE_DEBUGGER);
|
||||
ignoreDebugger = (ignoreDebuggerStr ? strtol(ignoreDebuggerStr, NULL, 10) : 0) != 0;
|
||||
}
|
||||
|
||||
if (!ignoreDebugger) {
|
||||
pid_t pid = getpid();
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
|
||||
int mibSize = sizeof(mib) / sizeof(int);
|
||||
size_t actualSize;
|
||||
|
||||
if (sysctl(mib, mibSize, NULL, &actualSize, NULL, 0) == 0) {
|
||||
struct kinfo_proc *info = (struct kinfo_proc *)malloc(actualSize);
|
||||
|
||||
if (info) {
|
||||
// This comes from looking at the Darwin xnu Kernel
|
||||
if (sysctl(mib, mibSize, info, &actualSize, NULL, 0) == 0)
|
||||
result = (info->kp_proc.p_flag & P_TRACED) ? YES : NO;
|
||||
|
||||
free(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
bool Breakpad::ExceptionHandlerDirectCallback(void *context,
|
||||
int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t crashing_thread) {
|
||||
Breakpad *breakpad = (Breakpad *)context;
|
||||
|
||||
// If our context is damaged or something, just return false to indicate that
|
||||
// the handler should continue without us.
|
||||
if (!breakpad)
|
||||
return false;
|
||||
|
||||
return breakpad->HandleException( exception_type,
|
||||
exception_code,
|
||||
exception_subcode,
|
||||
crashing_thread);
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
#pragma mark -
|
||||
|
||||
#include <mach-o/dyld.h>
|
||||
|
||||
//=============================================================================
|
||||
// Returns the pathname to the Resources directory for this version of
|
||||
// Breakpad which we are now running.
|
||||
//
|
||||
// Don't make the function static, since _dyld_lookup_and_bind_fully needs a
|
||||
// simple non-static C name
|
||||
//
|
||||
extern "C" {
|
||||
NSString * GetResourcePath();
|
||||
NSString * GetResourcePath() {
|
||||
NSString *resourcePath = nil;
|
||||
|
||||
// If there are multiple breakpads installed then calling bundleWithIdentifier
|
||||
// will not work properly, so only use that as a backup plan.
|
||||
// We want to find the bundle containing the code where this function lives
|
||||
// and work from there
|
||||
//
|
||||
|
||||
// Get the pathname to the code which contains this function
|
||||
void *address = nil;
|
||||
NSModule module = nil;
|
||||
_dyld_lookup_and_bind_fully("_GetResourcePath",
|
||||
&address,
|
||||
&module);
|
||||
|
||||
if (module && address) {
|
||||
const char* moduleName = NSNameOfModule(module);
|
||||
if (moduleName) {
|
||||
// The "Resources" directory should be in the same directory as the
|
||||
// executable code, since that's how the Breakpad framework is built.
|
||||
resourcePath = [NSString stringWithUTF8String:moduleName];
|
||||
resourcePath = [resourcePath stringByDeletingLastPathComponent];
|
||||
resourcePath = [resourcePath stringByAppendingPathComponent:@"Resources/"];
|
||||
} else {
|
||||
DEBUGLOG(stderr, "Missing moduleName\n");
|
||||
}
|
||||
} else {
|
||||
DEBUGLOG(stderr, "Could not find GetResourcePath\n");
|
||||
// fallback plan
|
||||
NSBundle *bundle =
|
||||
[NSBundle bundleWithIdentifier:@"com.Google.BreakpadFramework"];
|
||||
resourcePath = [bundle resourcePath];
|
||||
}
|
||||
|
||||
return resourcePath;
|
||||
}
|
||||
} // extern "C"
|
||||
|
||||
//=============================================================================
|
||||
bool Breakpad::Initialize(NSDictionary *parameters) {
|
||||
// Initialize
|
||||
config_params_ = NULL;
|
||||
handler_ = NULL;
|
||||
|
||||
// Check for debugger
|
||||
if (IsDebuggerActive()) {
|
||||
DEBUGLOG(stderr, "Debugger is active: Not installing handler\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Gather any user specified parameters
|
||||
if (!ExtractParameters(parameters)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get path to Inspector executable.
|
||||
NSString *inspectorPathString = KeyValue(@BREAKPAD_INSPECTOR_LOCATION);
|
||||
|
||||
// Standardize path (resolve symlinkes, etc.) and escape spaces
|
||||
inspectorPathString = [inspectorPathString stringByStandardizingPath];
|
||||
inspectorPathString = [[inspectorPathString componentsSeparatedByString:@" "]
|
||||
componentsJoinedByString:@"\\ "];
|
||||
|
||||
// Create an on-demand server object representing the Inspector.
|
||||
// In case of a crash, we simply need to call the LaunchOnDemand()
|
||||
// method on it, then send a mach message to its service port.
|
||||
// It will then launch and perform a process inspection of our crashed state.
|
||||
// See the HandleException() method for the details.
|
||||
#define RECEIVE_PORT_NAME "com.Breakpad.Inspector"
|
||||
|
||||
name_t portName;
|
||||
snprintf(portName, sizeof(name_t), "%s%d", RECEIVE_PORT_NAME, getpid());
|
||||
|
||||
// Save the location of the Inspector
|
||||
strlcpy(inspector_path_, [inspectorPathString fileSystemRepresentation],
|
||||
sizeof(inspector_path_));
|
||||
|
||||
// Append a single command-line argument to the Inspector path
|
||||
// representing the bootstrap name of the launch-on-demand receive port.
|
||||
// When the Inspector is launched, it can use this to lookup the port
|
||||
// by calling bootstrap_check_in().
|
||||
strlcat(inspector_path_, " ", sizeof(inspector_path_));
|
||||
strlcat(inspector_path_, portName, sizeof(inspector_path_));
|
||||
|
||||
kern_return_t kr = inspector_.Initialize(inspector_path_,
|
||||
portName,
|
||||
true); // shutdown on exit
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the handler (allocating it in our special protected pool)
|
||||
handler_ =
|
||||
new (gBreakpadAllocator->Allocate(sizeof(google_breakpad::ExceptionHandler)))
|
||||
google_breakpad::ExceptionHandler(
|
||||
Breakpad::ExceptionHandlerDirectCallback, this, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
Breakpad::~Breakpad() {
|
||||
// Note that we don't use operator delete() on these pointers,
|
||||
// since they were allocated by ProtectedMemoryAllocator objects.
|
||||
//
|
||||
if (config_params_) {
|
||||
config_params_->~SimpleStringDictionary();
|
||||
}
|
||||
|
||||
if (handler_)
|
||||
handler_->~ExceptionHandler();
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
NSUserDefaults *stdDefaults = [NSUserDefaults standardUserDefaults];
|
||||
NSString *skipConfirm = [stdDefaults stringForKey:@BREAKPAD_SKIP_CONFIRM];
|
||||
NSString *sendAndExit = [stdDefaults stringForKey:@BREAKPAD_SEND_AND_EXIT];
|
||||
|
||||
NSString *serverType = [parameters objectForKey:@BREAKPAD_SERVER_TYPE];
|
||||
NSString *display = [parameters objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
||||
NSString *product = [parameters objectForKey:@BREAKPAD_PRODUCT];
|
||||
NSString *version = [parameters objectForKey:@BREAKPAD_VERSION];
|
||||
NSString *urlStr = [parameters objectForKey:@BREAKPAD_URL];
|
||||
NSString *interval = [parameters objectForKey:@BREAKPAD_REPORT_INTERVAL];
|
||||
NSString *inspectorPathString =
|
||||
[parameters objectForKey:@BREAKPAD_INSPECTOR_LOCATION];
|
||||
NSString *reporterPathString =
|
||||
[parameters objectForKey:@BREAKPAD_REPORTER_EXE_LOCATION];
|
||||
NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT];
|
||||
NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES];
|
||||
NSString *logFileTailSize = [parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE];
|
||||
NSString *requestUserText =
|
||||
[parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS];
|
||||
NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL];
|
||||
NSString *vendor =
|
||||
[parameters objectForKey:@BREAKPAD_VENDOR];
|
||||
NSString *dumpSubdirectory =
|
||||
[parameters objectForKey:@BREAKPAD_DUMP_DIRECTORY];
|
||||
|
||||
NSDictionary *serverParameters =
|
||||
[parameters objectForKey:@BREAKPAD_SERVER_PARAMETER_DICT];
|
||||
|
||||
// These may have been set above as user prefs, which take priority.
|
||||
if (!skipConfirm) {
|
||||
skipConfirm = [parameters objectForKey:@BREAKPAD_SKIP_CONFIRM];
|
||||
}
|
||||
if (!sendAndExit) {
|
||||
sendAndExit = [parameters objectForKey:@BREAKPAD_SEND_AND_EXIT];
|
||||
}
|
||||
|
||||
if (!product)
|
||||
product = [parameters objectForKey:@"CFBundleName"];
|
||||
|
||||
if (!display)
|
||||
display = product;
|
||||
|
||||
if (!version)
|
||||
version = [parameters objectForKey:@"CFBundleVersion"];
|
||||
|
||||
if (!interval)
|
||||
interval = @"3600";
|
||||
|
||||
if (!timeout)
|
||||
timeout = @"300";
|
||||
|
||||
if (!logFileTailSize)
|
||||
logFileTailSize = @"200000";
|
||||
|
||||
if (!vendor) {
|
||||
vendor = @"Vendor not specified";
|
||||
}
|
||||
|
||||
// Normalize the values.
|
||||
if (skipConfirm) {
|
||||
skipConfirm = [skipConfirm uppercaseString];
|
||||
|
||||
if ([skipConfirm isEqualToString:@"YES"] ||
|
||||
[skipConfirm isEqualToString:@"TRUE"] ||
|
||||
[skipConfirm isEqualToString:@"1"])
|
||||
skipConfirm = @"YES";
|
||||
else
|
||||
skipConfirm = @"NO";
|
||||
} else {
|
||||
skipConfirm = @"NO";
|
||||
}
|
||||
|
||||
send_and_exit_ = true;
|
||||
if (sendAndExit) {
|
||||
sendAndExit = [sendAndExit uppercaseString];
|
||||
|
||||
if ([sendAndExit isEqualToString:@"NO"] ||
|
||||
[sendAndExit isEqualToString:@"FALSE"] ||
|
||||
[sendAndExit isEqualToString:@"0"])
|
||||
send_and_exit_ = false;
|
||||
}
|
||||
|
||||
if (requestUserText) {
|
||||
requestUserText = [requestUserText uppercaseString];
|
||||
|
||||
if ([requestUserText isEqualToString:@"YES"] ||
|
||||
[requestUserText isEqualToString:@"TRUE"] ||
|
||||
[requestUserText isEqualToString:@"1"])
|
||||
requestUserText = @"YES";
|
||||
else
|
||||
requestUserText = @"NO";
|
||||
} else {
|
||||
requestUserText = @"NO";
|
||||
}
|
||||
|
||||
// Find the helper applications if not specified in user config.
|
||||
NSString *resourcePath = nil;
|
||||
if (!inspectorPathString || !reporterPathString) {
|
||||
resourcePath = GetResourcePath();
|
||||
if (!resourcePath) {
|
||||
DEBUGLOG(stderr, "Could not get resource path\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Find Inspector.
|
||||
if (!inspectorPathString) {
|
||||
inspectorPathString =
|
||||
[resourcePath stringByAppendingPathComponent:@"Inspector"];
|
||||
}
|
||||
|
||||
// Verify that there is an Inspector tool.
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) {
|
||||
DEBUGLOG(stderr, "Cannot find Inspector tool\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find Reporter.
|
||||
if (!reporterPathString) {
|
||||
reporterPathString =
|
||||
[resourcePath stringByAppendingPathComponent:@"crash_report_sender.app"];
|
||||
reporterPathString = [[NSBundle bundleWithPath:reporterPathString] executablePath];
|
||||
}
|
||||
|
||||
// Verify that there is a Reporter application.
|
||||
if (![[NSFileManager defaultManager]
|
||||
fileExistsAtPath:reporterPathString]) {
|
||||
DEBUGLOG(stderr, "Cannot find Reporter tool\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!dumpSubdirectory) {
|
||||
dumpSubdirectory = @"";
|
||||
}
|
||||
|
||||
// The product, version, and URL are required values.
|
||||
if (![product length]) {
|
||||
DEBUGLOG(stderr, "Missing required product key.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (![version length]) {
|
||||
DEBUGLOG(stderr, "Missing required version key.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (![urlStr length]) {
|
||||
DEBUGLOG(stderr, "Missing required URL key.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
config_params_ =
|
||||
new (gKeyValueAllocator->Allocate(sizeof(SimpleStringDictionary)) )
|
||||
SimpleStringDictionary();
|
||||
|
||||
SimpleStringDictionary &dictionary = *config_params_;
|
||||
|
||||
dictionary.SetKeyValue(BREAKPAD_SERVER_TYPE, [serverType UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_PRODUCT_DISPLAY, [display UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_PRODUCT, [product UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_VERSION, [version UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_URL, [urlStr UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_REPORT_INTERVAL, [interval UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_SKIP_CONFIRM, [skipConfirm UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_CONFIRM_TIMEOUT, [timeout UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_INSPECTOR_LOCATION,
|
||||
[inspectorPathString fileSystemRepresentation]);
|
||||
dictionary.SetKeyValue(BREAKPAD_REPORTER_EXE_LOCATION,
|
||||
[reporterPathString fileSystemRepresentation]);
|
||||
dictionary.SetKeyValue(BREAKPAD_LOGFILE_UPLOAD_SIZE,
|
||||
[logFileTailSize UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_REQUEST_COMMENTS,
|
||||
[requestUserText UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_REQUEST_EMAIL, [requestEmail UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_VENDOR, [vendor UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_DUMP_DIRECTORY,
|
||||
[dumpSubdirectory UTF8String]);
|
||||
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
char timeStartedString[32];
|
||||
sprintf(timeStartedString, "%d", tv.tv_sec);
|
||||
dictionary.SetKeyValue(BREAKPAD_PROCESS_START_TIME,
|
||||
timeStartedString);
|
||||
|
||||
if (logFilePaths) {
|
||||
char logFileKey[255];
|
||||
for(unsigned int i = 0; i < [logFilePaths count]; i++) {
|
||||
sprintf(logFileKey,"%s%d", BREAKPAD_LOGFILE_KEY_PREFIX, i);
|
||||
dictionary.SetKeyValue(logFileKey,
|
||||
[[logFilePaths objectAtIndex:i]
|
||||
fileSystemRepresentation]);
|
||||
}
|
||||
}
|
||||
|
||||
if (serverParameters) {
|
||||
// For each key-value pair, call BreakpadAddUploadParameter()
|
||||
NSEnumerator *keyEnumerator = [serverParameters keyEnumerator];
|
||||
NSString *aParameter;
|
||||
while (aParameter = [keyEnumerator nextObject]) {
|
||||
BreakpadAddUploadParameter(this, aParameter,
|
||||
[serverParameters objectForKey:aParameter]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void Breakpad::SetKeyValue(NSString *key, NSString *value) {
|
||||
// We allow nil values. This is the same as removing the keyvalue.
|
||||
if (!config_params_ || !key)
|
||||
return;
|
||||
|
||||
config_params_->SetKeyValue([key UTF8String], [value UTF8String]);
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
NSString * Breakpad::KeyValue(NSString *key) {
|
||||
if (!config_params_ || !key)
|
||||
return nil;
|
||||
|
||||
const char *value = config_params_->GetValueForKey([key UTF8String]);
|
||||
return value ? [NSString stringWithUTF8String:value] : nil;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void Breakpad::RemoveKeyValue(NSString *key) {
|
||||
if (!config_params_ || !key)
|
||||
return;
|
||||
|
||||
config_params_->RemoveKey([key UTF8String]);
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void Breakpad::GenerateAndSendReport() {
|
||||
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "YES");
|
||||
HandleException(0, 0, 0, mach_thread_self());
|
||||
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "NO");
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
bool Breakpad::HandleException(int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t crashing_thread) {
|
||||
DEBUGLOG(stderr, "Breakpad: an exception occurred\n");
|
||||
|
||||
if (filter_callback_) {
|
||||
bool should_handle = filter_callback_(exception_type,
|
||||
exception_code,
|
||||
crashing_thread,
|
||||
filter_callback_context_);
|
||||
if (!should_handle) return false;
|
||||
}
|
||||
|
||||
// We need to reset the memory protections to be read/write,
|
||||
// since LaunchOnDemand() requires changing state.
|
||||
gBreakpadAllocator->Unprotect();
|
||||
// Configure the server to launch when we message the service port.
|
||||
// The reason we do this here, rather than at startup, is that we
|
||||
// can leak a bootstrap service entry if this method is called and
|
||||
// there never ends up being a crash.
|
||||
inspector_.LaunchOnDemand();
|
||||
gBreakpadAllocator->Protect();
|
||||
|
||||
// The Inspector should send a message to this port to verify it
|
||||
// received our information and has finished the inspection.
|
||||
ReceivePort acknowledge_port;
|
||||
|
||||
// Send initial information to the Inspector.
|
||||
MachSendMessage message(kMsgType_InspectorInitialInfo);
|
||||
message.AddDescriptor(mach_task_self()); // our task
|
||||
message.AddDescriptor(crashing_thread); // crashing thread
|
||||
message.AddDescriptor(mach_thread_self()); // exception-handling thread
|
||||
message.AddDescriptor(acknowledge_port.GetPort());// message receive port
|
||||
|
||||
InspectorInfo info;
|
||||
info.exception_type = exception_type;
|
||||
info.exception_code = exception_code;
|
||||
info.exception_subcode = exception_subcode;
|
||||
info.parameter_count = config_params_->GetCount();
|
||||
message.SetData(&info, sizeof(info));
|
||||
|
||||
MachPortSender sender(inspector_.GetServicePort());
|
||||
|
||||
kern_return_t result = sender.SendMessage(message, 2000);
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
// Now, send a series of key-value pairs to the Inspector.
|
||||
const KeyValueEntry *entry = NULL;
|
||||
SimpleStringDictionaryIterator iter(*config_params_);
|
||||
|
||||
while ( (entry = iter.Next()) ) {
|
||||
KeyValueMessageData keyvalue_data(*entry);
|
||||
|
||||
MachSendMessage keyvalue_message(kMsgType_InspectorKeyValuePair);
|
||||
keyvalue_message.SetData(&keyvalue_data, sizeof(keyvalue_data));
|
||||
|
||||
result = sender.SendMessage(keyvalue_message, 2000);
|
||||
|
||||
if (result != KERN_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
// Wait for acknowledgement that the inspection has finished.
|
||||
MachReceiveMessage acknowledge_messsage;
|
||||
result = acknowledge_port.WaitForMessage(&acknowledge_messsage, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
#if VERBOSE
|
||||
PRINT_MACH_RESULT(result, "Breakpad: SendMessage ");
|
||||
printf("Breakpad: Inspector service port = %#x\n",
|
||||
inspector_.GetServicePort());
|
||||
#endif
|
||||
|
||||
// If we don't want any forwarding, return true here to indicate that we've
|
||||
// processed things as much as we want.
|
||||
if (send_and_exit_)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
//=============================================================================
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Public API
|
||||
|
||||
//=============================================================================
|
||||
BreakpadRef BreakpadCreate(NSDictionary *parameters) {
|
||||
try {
|
||||
// This is confusing. Our two main allocators for breakpad memory are:
|
||||
// - gKeyValueAllocator for the key/value memory
|
||||
// - gBreakpadAllocator for the Breakpad, ExceptionHandler, and other
|
||||
// breakpad allocations which are accessed at exception handling time.
|
||||
//
|
||||
// But in order to avoid these two allocators themselves from being smashed,
|
||||
// we'll protect them as well by allocating them with gMasterAllocator.
|
||||
//
|
||||
// gMasterAllocator itself will NOT be protected, but this doesn't matter,
|
||||
// since once it does its allocations and locks the memory, smashes to itself
|
||||
// don't affect anything we care about.
|
||||
gMasterAllocator =
|
||||
new ProtectedMemoryAllocator(sizeof(ProtectedMemoryAllocator) * 2);
|
||||
|
||||
gKeyValueAllocator =
|
||||
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
|
||||
ProtectedMemoryAllocator(sizeof(SimpleStringDictionary));
|
||||
|
||||
// Create a mutex for use in accessing the SimpleStringDictionary
|
||||
int mutexResult = pthread_mutex_init(&gDictionaryMutex, NULL);
|
||||
if (mutexResult != 0) {
|
||||
throw mutexResult; // caught down below
|
||||
}
|
||||
|
||||
// With the current compiler, gBreakpadAllocator is allocating 1444 bytes.
|
||||
// Let's round up to the nearest page size.
|
||||
//
|
||||
int breakpad_pool_size = 4096;
|
||||
|
||||
/*
|
||||
sizeof(Breakpad)
|
||||
+ sizeof(google_breakpad::ExceptionHandler)
|
||||
+ sizeof( STUFF ALLOCATED INSIDE ExceptionHandler )
|
||||
*/
|
||||
|
||||
gBreakpadAllocator =
|
||||
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
|
||||
ProtectedMemoryAllocator(breakpad_pool_size);
|
||||
|
||||
// Stack-based autorelease pool for Breakpad::Create() obj-c code.
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
Breakpad *breakpad = Breakpad::Create(parameters);
|
||||
|
||||
if (breakpad) {
|
||||
// Make read-only to protect against memory smashers
|
||||
gMasterAllocator->Protect();
|
||||
gKeyValueAllocator->Protect();
|
||||
gBreakpadAllocator->Protect();
|
||||
} else {
|
||||
[pool release];
|
||||
#ifdef __EXCEPTIONS
|
||||
throw(-1);
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Can uncomment this line to figure out how much space was actually
|
||||
// allocated using this allocator
|
||||
// printf("gBreakpadAllocator allocated size = %d\n",
|
||||
// gBreakpadAllocator->GetAllocatedSize() );
|
||||
|
||||
[pool release];
|
||||
return (BreakpadRef)breakpad;
|
||||
} catch(...) { // don't let exceptions leave this C API
|
||||
if (gKeyValueAllocator) {
|
||||
gKeyValueAllocator->~ProtectedMemoryAllocator();
|
||||
gKeyValueAllocator = NULL;
|
||||
}
|
||||
|
||||
if (gBreakpadAllocator) {
|
||||
gBreakpadAllocator->~ProtectedMemoryAllocator();
|
||||
gBreakpadAllocator = NULL;
|
||||
}
|
||||
|
||||
delete gMasterAllocator;
|
||||
gMasterAllocator = NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void BreakpadRelease(BreakpadRef ref) {
|
||||
try {
|
||||
Breakpad *breakpad = (Breakpad *)ref;
|
||||
|
||||
if (gMasterAllocator) {
|
||||
gMasterAllocator->Unprotect();
|
||||
gKeyValueAllocator->Unprotect();
|
||||
gBreakpadAllocator->Unprotect();
|
||||
|
||||
breakpad->~Breakpad();
|
||||
|
||||
// Unfortunately, it's not possible to deallocate this stuff
|
||||
// because the exception handling thread is still finishing up
|
||||
// asynchronously at this point... OK, it could be done with
|
||||
// locks, etc. But since BreakpadRelease() should usually only
|
||||
// be called right before the process exits, it's not worth
|
||||
// deallocating this stuff.
|
||||
#if 0
|
||||
gKeyValueAllocator->~ProtectedMemoryAllocator();
|
||||
gBreakpadAllocator->~ProtectedMemoryAllocator();
|
||||
delete gMasterAllocator;
|
||||
|
||||
gMasterAllocator = NULL;
|
||||
gKeyValueAllocator = NULL;
|
||||
gBreakpadAllocator = NULL;
|
||||
#endif
|
||||
|
||||
pthread_mutex_destroy(&gDictionaryMutex);
|
||||
}
|
||||
} catch(...) { // don't let exceptions leave this C API
|
||||
fprintf(stderr, "BreakpadRelease() : error\n");
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void BreakpadSetKeyValue(BreakpadRef ref, NSString *key, NSString *value) {
|
||||
try {
|
||||
// Not called at exception time
|
||||
Breakpad *breakpad = (Breakpad *)ref;
|
||||
|
||||
if (breakpad && key && gKeyValueAllocator) {
|
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
||||
|
||||
breakpad->SetKeyValue(key, value);
|
||||
}
|
||||
} catch(...) { // don't let exceptions leave this C API
|
||||
fprintf(stderr, "BreakpadSetKeyValue() : error\n");
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpadAddUploadParameter(BreakpadRef ref,
|
||||
NSString *key,
|
||||
NSString *value) {
|
||||
// The only difference, internally, between an upload parameter and
|
||||
// a key value one that is set with BreakpadSetKeyValue is that we
|
||||
// prepend the keyname with a special prefix. This informs the
|
||||
// crash sender that the parameter should be sent along with the
|
||||
// POST of the crash dump upload.
|
||||
try {
|
||||
Breakpad *breakpad = (Breakpad *)ref;
|
||||
|
||||
if (breakpad && key && gKeyValueAllocator) {
|
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
||||
|
||||
NSString *prefixedKey = [@BREAKPAD_SERVER_PARAMETER_PREFIX
|
||||
stringByAppendingString:key];
|
||||
breakpad->SetKeyValue(prefixedKey, value);
|
||||
}
|
||||
} catch(...) { // don't let exceptions leave this C API
|
||||
fprintf(stderr, "BreakpadSetKeyValue() : error\n");
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpadRemoveUploadParameter(BreakpadRef ref,
|
||||
NSString *key) {
|
||||
try {
|
||||
// Not called at exception time
|
||||
Breakpad *breakpad = (Breakpad *)ref;
|
||||
|
||||
if (breakpad && key && gKeyValueAllocator) {
|
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
||||
|
||||
NSString *prefixedKey = [NSString stringWithFormat:@"%@%@",
|
||||
@BREAKPAD_SERVER_PARAMETER_PREFIX, key];
|
||||
breakpad->RemoveKeyValue(prefixedKey);
|
||||
}
|
||||
} catch(...) { // don't let exceptions leave this C API
|
||||
fprintf(stderr, "BreakpadRemoveKeyValue() : error\n");
|
||||
}
|
||||
}
|
||||
//=============================================================================
|
||||
NSString *BreakpadKeyValue(BreakpadRef ref, NSString *key) {
|
||||
NSString *value = nil;
|
||||
|
||||
try {
|
||||
// Not called at exception time
|
||||
Breakpad *breakpad = (Breakpad *)ref;
|
||||
|
||||
if (!breakpad || !key || !gKeyValueAllocator)
|
||||
return nil;
|
||||
|
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
||||
|
||||
value = breakpad->KeyValue(key);
|
||||
} catch(...) { // don't let exceptions leave this C API
|
||||
fprintf(stderr, "BreakpadKeyValue() : error\n");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key) {
|
||||
try {
|
||||
// Not called at exception time
|
||||
Breakpad *breakpad = (Breakpad *)ref;
|
||||
|
||||
if (breakpad && key && gKeyValueAllocator) {
|
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
||||
|
||||
breakpad->RemoveKeyValue(key);
|
||||
}
|
||||
} catch(...) { // don't let exceptions leave this C API
|
||||
fprintf(stderr, "BreakpadRemoveKeyValue() : error\n");
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void BreakpadGenerateAndSendReport(BreakpadRef ref) {
|
||||
try {
|
||||
Breakpad *breakpad = (Breakpad *)ref;
|
||||
|
||||
if (breakpad && gKeyValueAllocator) {
|
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
||||
|
||||
gBreakpadAllocator->Unprotect();
|
||||
breakpad->GenerateAndSendReport();
|
||||
gBreakpadAllocator->Protect();
|
||||
}
|
||||
} catch(...) { // don't let exceptions leave this C API
|
||||
fprintf(stderr, "BreakpadGenerateAndSendReport() : error\n");
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void BreakpadSetFilterCallback(BreakpadRef ref,
|
||||
BreakpadFilterCallback callback,
|
||||
void *context) {
|
||||
|
||||
try {
|
||||
Breakpad *breakpad = (Breakpad *)ref;
|
||||
|
||||
if (breakpad && gBreakpadAllocator) {
|
||||
// share the dictionary mutex here (we really don't need a mutex)
|
||||
ProtectedMemoryLocker locker(&gDictionaryMutex, gBreakpadAllocator);
|
||||
|
||||
breakpad->SetFilterCallback(callback, context);
|
||||
}
|
||||
} catch(...) { // don't let exceptions leave this C API
|
||||
fprintf(stderr, "BreakpadSetFilterCallback() : error\n");
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
void BreakpadAddLogFile(BreakpadRef ref, NSString *logPathname) {
|
||||
int logFileCounter = 0;
|
||||
|
||||
NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
|
||||
@BREAKPAD_LOGFILE_KEY_PREFIX,
|
||||
logFileCounter];
|
||||
|
||||
NSString *existingLogFilename = nil;
|
||||
existingLogFilename = BreakpadKeyValue(ref, logFileKey);
|
||||
// Find the first log file key that we can use by testing for existence
|
||||
while (existingLogFilename) {
|
||||
if ([existingLogFilename isEqualToString:logPathname]) {
|
||||
return;
|
||||
}
|
||||
logFileCounter++;
|
||||
logFileKey = [NSString stringWithFormat:@"%@%d",
|
||||
@BREAKPAD_LOGFILE_KEY_PREFIX,
|
||||
logFileCounter];
|
||||
existingLogFilename = BreakpadKeyValue(ref, logFileKey);
|
||||
}
|
||||
|
||||
BreakpadSetKeyValue(ref, logFileKey, logPathname);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
//
|
||||
// Prefix header for all source files of the 'Breakpad' target in the
|
||||
// 'Breakpad' project.
|
||||
//
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#endif
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.googlecode.google-breakpad</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,146 +0,0 @@
|
||||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <iostream>
|
||||
#import <mach/mach.h>
|
||||
#import <servers/bootstrap.h>
|
||||
#import <stdio.h>
|
||||
#import <stdlib.h>
|
||||
#import <sys/stat.h>
|
||||
#import <unistd.h>
|
||||
|
||||
//==============================================================================
|
||||
// class OnDemandServer :
|
||||
// A basic on-demand server launcher supporting a single named service port
|
||||
//
|
||||
// Example Usage :
|
||||
//
|
||||
// kern_return_t result;
|
||||
// OnDemandServer *server = OnDemandServer::Create("/tmp/myserver",
|
||||
// "com.MyCompany.MyServiceName",
|
||||
// true,
|
||||
// &result);
|
||||
//
|
||||
// if (server) {
|
||||
// server->LaunchOnDemand();
|
||||
// mach_port_t service_port = GetServicePort();
|
||||
//
|
||||
// // Send a mach message to service_port and "myserver" will be launched
|
||||
// }
|
||||
//
|
||||
//
|
||||
// ---- Now in the server code ----
|
||||
//
|
||||
// // "myserver" should get the service port and read the message which
|
||||
// // launched it:
|
||||
// mach_port_t service_rcv_port_;
|
||||
// kern_return_t kr = bootstrap_check_in(bootstrap_port,
|
||||
// "com.MyCompany.MyServiceName",
|
||||
// &service_rcv_port_);
|
||||
// // mach_msg() read service_rcv_port_ ....
|
||||
//
|
||||
// ....
|
||||
//
|
||||
// // Later "myserver" may want to unregister the service if it doesn't
|
||||
// // want its bootstrap service to stick around after it exits.
|
||||
//
|
||||
// // DO NOT use mach_port_deallocate() here -- it will fail and the
|
||||
// // following bootstrap_register() will also fail leaving our service
|
||||
// // name hanging around forever (until reboot)
|
||||
// kern_return_t kr = mach_port_destroy(mach_task_self(), service_rcv_port_);
|
||||
//
|
||||
// kr = bootstrap_register(bootstrap_port,
|
||||
// "com.MyCompany.MyServiceName",
|
||||
// MACH_PORT_NULL);
|
||||
|
||||
class OnDemandServer {
|
||||
public:
|
||||
// must call Initialize() to be useful
|
||||
OnDemandServer()
|
||||
: server_port_(MACH_PORT_NULL),
|
||||
service_port_(MACH_PORT_NULL),
|
||||
unregister_on_cleanup_(true) {
|
||||
}
|
||||
|
||||
// Creates the bootstrap server and service
|
||||
kern_return_t Initialize(const char *server_command,
|
||||
const char *service_name,
|
||||
bool unregister_on_cleanup);
|
||||
|
||||
// Returns an OnDemandServer object if successful, or NULL if there's
|
||||
// an error. The error result will be returned in out_result.
|
||||
//
|
||||
// server_command : the full path name including optional command-line
|
||||
// arguments to the executable representing the server
|
||||
//
|
||||
// service_name : represents service name
|
||||
// something like "com.company.ServiceName"
|
||||
//
|
||||
// unregister_on_cleanup : if true, unregisters the service name
|
||||
// when the OnDemandServer is deleted -- unregistering will
|
||||
// ONLY be possible if LaunchOnDemand() has NOT been called.
|
||||
// If false, then the service will continue to be registered
|
||||
// even after the current process quits.
|
||||
//
|
||||
// out_result : if non-NULL, returns the result
|
||||
// this value will be KERN_SUCCESS if Create() returns non-NULL
|
||||
//
|
||||
static OnDemandServer *Create(const char *server_command,
|
||||
const char *service_name,
|
||||
bool unregister_on_cleanup,
|
||||
kern_return_t *out_result);
|
||||
|
||||
// Cleans up and if LaunchOnDemand() has not yet been called then
|
||||
// the bootstrap service will be unregistered.
|
||||
~OnDemandServer();
|
||||
|
||||
// This must be called if we intend to commit to launching the server
|
||||
// by sending a mach message to our service port. Do not call it otherwise
|
||||
// or it will be difficult (impossible?) to unregister the service name.
|
||||
void LaunchOnDemand();
|
||||
|
||||
// This is the port we need to send a mach message to after calling
|
||||
// LaunchOnDemand(). Sending a message causing an immediate launch
|
||||
// of the server
|
||||
mach_port_t GetServicePort() { return service_port_; };
|
||||
|
||||
private:
|
||||
// Disallow copy constructor
|
||||
OnDemandServer(const OnDemandServer&);
|
||||
|
||||
// Cleans up and if LaunchOnDemand() has not yet been called then
|
||||
// the bootstrap service will be unregistered.
|
||||
void Unregister();
|
||||
|
||||
name_t service_name_;
|
||||
|
||||
mach_port_t server_port_;
|
||||
mach_port_t service_port_;
|
||||
bool unregister_on_cleanup_;
|
||||
};
|
||||
@@ -1,145 +0,0 @@
|
||||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import "OnDemandServer.h"
|
||||
|
||||
#if DEBUG
|
||||
#define PRINT_MACH_RESULT(result_, message_) \
|
||||
printf(message_"%s (%d)\n", mach_error_string(result_), result_ );
|
||||
#else
|
||||
#define PRINT_MACH_RESULT(result_, message_)
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
OnDemandServer *OnDemandServer::Create(const char *server_command,
|
||||
const char *service_name,
|
||||
bool unregister_on_cleanup,
|
||||
kern_return_t *out_result) {
|
||||
OnDemandServer *server = new OnDemandServer();
|
||||
|
||||
if (!server) return NULL;
|
||||
|
||||
kern_return_t result = server->Initialize(server_command,
|
||||
service_name,
|
||||
unregister_on_cleanup);
|
||||
|
||||
if (out_result) {
|
||||
*out_result = result;
|
||||
}
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
return server;
|
||||
}
|
||||
|
||||
delete server;
|
||||
return NULL;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
kern_return_t OnDemandServer::Initialize(const char *server_command,
|
||||
const char *service_name,
|
||||
bool unregister_on_cleanup) {
|
||||
unregister_on_cleanup_ = unregister_on_cleanup;
|
||||
|
||||
kern_return_t kr =
|
||||
bootstrap_create_server(bootstrap_port,
|
||||
const_cast<char*>(server_command),
|
||||
geteuid(), // server uid
|
||||
true,
|
||||
&server_port_);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
PRINT_MACH_RESULT(kr, "bootstrap_create_server() : ");
|
||||
return kr;
|
||||
}
|
||||
|
||||
strlcpy(service_name_, service_name, sizeof(service_name_));
|
||||
|
||||
// Create a service called service_name, and return send rights to
|
||||
// that port in service_port_.
|
||||
kr = bootstrap_create_service(server_port_,
|
||||
const_cast<char*>(service_name),
|
||||
&service_port_);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
PRINT_MACH_RESULT(kr, "bootstrap_create_service() : ");
|
||||
|
||||
// perhaps the service has already been created - try to look it up
|
||||
kr = bootstrap_look_up(bootstrap_port, (char*)service_name, &service_port_);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
PRINT_MACH_RESULT(kr, "bootstrap_look_up() : ");
|
||||
Unregister(); // clean up server port
|
||||
return kr;
|
||||
}
|
||||
}
|
||||
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
OnDemandServer::~OnDemandServer() {
|
||||
if (unregister_on_cleanup_) {
|
||||
Unregister();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void OnDemandServer::LaunchOnDemand() {
|
||||
// We need to do this, since the launched server is another process
|
||||
// and holding on to this port delays launching until the current process
|
||||
// exits!
|
||||
mach_port_deallocate(mach_task_self(), server_port_);
|
||||
server_port_ = NULL;
|
||||
|
||||
// Now, the service is still registered and all we need to do is send
|
||||
// a mach message to the service port in order to launch the server.
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void OnDemandServer::Unregister() {
|
||||
if (service_port_ != MACH_PORT_NULL) {
|
||||
mach_port_deallocate(mach_task_self(), service_port_);
|
||||
service_port_ = MACH_PORT_NULL;
|
||||
}
|
||||
|
||||
if (server_port_ != MACH_PORT_NULL) {
|
||||
// unregister the service
|
||||
kern_return_t kr = bootstrap_register(server_port_,
|
||||
service_name_,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
PRINT_MACH_RESULT(kr, "Breakpad UNREGISTER : bootstrap_register() : ");
|
||||
}
|
||||
|
||||
mach_port_deallocate(mach_task_self(), server_port_);
|
||||
server_port_ = MACH_PORT_NULL;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.yourcompany.${PRODUCT_NAME:identifier}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,193 +0,0 @@
|
||||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Interface file between the Breakpad.framework and
|
||||
// the Inspector process.
|
||||
|
||||
#import "common/mac/SimpleStringDictionary.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "client/mac/handler/minidump_generator.h"
|
||||
|
||||
#define VERBOSE 0
|
||||
|
||||
extern bool gDebugLog;
|
||||
|
||||
#define DEBUGLOG if (gDebugLog) fprintf
|
||||
|
||||
// Types of mach messsages (message IDs)
|
||||
enum {
|
||||
kMsgType_InspectorInitialInfo = 0, // data is InspectorInfo
|
||||
kMsgType_InspectorKeyValuePair = 1, // data is KeyValueMessageData
|
||||
kMsgType_InspectorAcknowledgement = 2 // no data sent
|
||||
};
|
||||
|
||||
// Initial information sent from the crashed process by
|
||||
// Breakpad.framework to the Inspector process
|
||||
// The mach message with this struct as data will also include
|
||||
// several descriptors for sending mach port rights to the crashed
|
||||
// task, etc.
|
||||
struct InspectorInfo {
|
||||
int exception_type;
|
||||
int exception_code;
|
||||
int exception_subcode;
|
||||
unsigned int parameter_count; // key-value pairs
|
||||
};
|
||||
|
||||
// Key/value message data to be sent to the Inspector
|
||||
struct KeyValueMessageData {
|
||||
public:
|
||||
KeyValueMessageData() {}
|
||||
KeyValueMessageData(const google_breakpad::KeyValueEntry &inEntry) {
|
||||
strlcpy(key, inEntry.GetKey(), sizeof(key) );
|
||||
strlcpy(value, inEntry.GetValue(), sizeof(value) );
|
||||
}
|
||||
|
||||
char key[google_breakpad::KeyValueEntry::MAX_STRING_STORAGE_SIZE];
|
||||
char value[google_breakpad::KeyValueEntry::MAX_STRING_STORAGE_SIZE];
|
||||
};
|
||||
|
||||
using std::string;
|
||||
using google_breakpad::MinidumpGenerator;
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
static BOOL EnsureDirectoryPathExists(NSString *dirPath);
|
||||
|
||||
//=============================================================================
|
||||
class ConfigFile {
|
||||
public:
|
||||
ConfigFile() {
|
||||
config_file_ = -1;
|
||||
config_file_path_[0] = 0;
|
||||
has_created_file_ = false;
|
||||
};
|
||||
|
||||
~ConfigFile() {
|
||||
};
|
||||
|
||||
void WriteFile(const SimpleStringDictionary *configurationParameters,
|
||||
const char *dump_dir,
|
||||
const char *minidump_id);
|
||||
|
||||
const char *GetFilePath() { return config_file_path_; }
|
||||
|
||||
void Unlink() {
|
||||
if (config_file_ != -1)
|
||||
unlink(config_file_path_);
|
||||
|
||||
config_file_ = -1;
|
||||
}
|
||||
|
||||
private:
|
||||
BOOL WriteData(const void *data, size_t length);
|
||||
|
||||
BOOL AppendConfigData(const char *key,
|
||||
const void *data,
|
||||
size_t length);
|
||||
|
||||
BOOL AppendConfigString(const char *key,
|
||||
const char *value);
|
||||
|
||||
int config_file_; // descriptor for config file
|
||||
char config_file_path_[PATH_MAX]; // Path to configuration file
|
||||
bool has_created_file_;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
class MinidumpLocation {
|
||||
public:
|
||||
MinidumpLocation(const NSString *minidumpDir) {
|
||||
// Ensure that the path exists. Fallback to /tmp if unable to locate path.
|
||||
assert(minidumpDir);
|
||||
if (!EnsureDirectoryPathExists(minidumpDir)) {
|
||||
DEBUGLOG(stderr, "Unable to create: %s\n", [minidumpDir UTF8String]);
|
||||
minidumpDir = @"/tmp";
|
||||
}
|
||||
|
||||
strlcpy(minidump_dir_path_, [minidumpDir fileSystemRepresentation],
|
||||
sizeof(minidump_dir_path_));
|
||||
|
||||
// now generate a unique ID
|
||||
string dump_path(minidump_dir_path_);
|
||||
string next_minidump_id;
|
||||
|
||||
string next_minidump_path_ =
|
||||
(MinidumpGenerator::UniqueNameInDirectory(dump_path, &next_minidump_id));
|
||||
|
||||
strlcpy(minidump_id_, next_minidump_id.c_str(), sizeof(minidump_id_));
|
||||
};
|
||||
|
||||
const char *GetPath() { return minidump_dir_path_; }
|
||||
const char *GetID() { return minidump_id_; }
|
||||
|
||||
private:
|
||||
char minidump_dir_path_[PATH_MAX]; // Path to minidump directory
|
||||
char minidump_id_[128];
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
class Inspector {
|
||||
public:
|
||||
Inspector() {};
|
||||
|
||||
// given a bootstrap service name, receives mach messages
|
||||
// from a crashed process, then inspects it, creates a minidump file
|
||||
// and asks the user if he wants to upload it to a server.
|
||||
void Inspect(const char *receive_port_name);
|
||||
|
||||
private:
|
||||
kern_return_t ServiceCheckIn(const char *receive_port_name);
|
||||
kern_return_t ServiceCheckOut(const char *receive_port_name);
|
||||
|
||||
kern_return_t ReadMessages();
|
||||
|
||||
bool InspectTask();
|
||||
kern_return_t SendAcknowledgement();
|
||||
void LaunchReporter(const char *inConfigFilePath);
|
||||
|
||||
void SetCrashTimeParameters();
|
||||
|
||||
mach_port_t service_rcv_port_;
|
||||
|
||||
int exception_type_;
|
||||
int exception_code_;
|
||||
int exception_subcode_;
|
||||
mach_port_t remote_task_;
|
||||
mach_port_t crashing_thread_;
|
||||
mach_port_t handler_thread_;
|
||||
mach_port_t ack_port_;
|
||||
|
||||
SimpleStringDictionary config_params_;
|
||||
|
||||
ConfigFile config_file_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace google_breakpad
|
||||
@@ -1,553 +0,0 @@
|
||||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Utility that can inspect another process and write a crash dump
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <sys/time.h>
|
||||
|
||||
#import "client/mac/crash_generation/Inspector.h"
|
||||
|
||||
#import "client/mac/Framework/Breakpad.h"
|
||||
#import "client/mac/handler/minidump_generator.h"
|
||||
|
||||
#import "common/mac/SimpleStringDictionary.h"
|
||||
#import "common/mac/MachIPC.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if VERBOSE
|
||||
bool gDebugLog = true;
|
||||
#else
|
||||
bool gDebugLog = false;
|
||||
#endif
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
//=============================================================================
|
||||
static BOOL EnsureDirectoryPathExists(NSString *dirPath) {
|
||||
NSFileManager *mgr = [NSFileManager defaultManager];
|
||||
|
||||
// If we got a relative path, prepend the current directory
|
||||
if (![dirPath isAbsolutePath])
|
||||
dirPath = [[mgr currentDirectoryPath] stringByAppendingPathComponent:dirPath];
|
||||
|
||||
NSString *path = dirPath;
|
||||
|
||||
// Ensure that no file exists within the path which would block creation
|
||||
while (1) {
|
||||
BOOL isDir;
|
||||
if ([mgr fileExistsAtPath:path isDirectory:&isDir]) {
|
||||
if (isDir)
|
||||
break;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
path = [path stringByDeletingLastPathComponent];
|
||||
}
|
||||
|
||||
// Path now contains the first valid directory (or is empty)
|
||||
if (![path length])
|
||||
return NO;
|
||||
|
||||
NSString *common =
|
||||
[dirPath commonPrefixWithString:path options:NSLiteralSearch];
|
||||
|
||||
// If everything is good
|
||||
if ([common isEqualToString:dirPath])
|
||||
return YES;
|
||||
|
||||
// Break up the difference into components
|
||||
NSString *diff = [dirPath substringFromIndex:[common length] + 1];
|
||||
NSArray *components = [diff pathComponents];
|
||||
unsigned count = [components count];
|
||||
|
||||
// Rebuild the path one component at a time
|
||||
NSDictionary *attrs =
|
||||
[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:0750]
|
||||
forKey:NSFilePosixPermissions];
|
||||
path = common;
|
||||
for (unsigned i = 0; i < count; ++i) {
|
||||
path = [path stringByAppendingPathComponent:[components objectAtIndex:i]];
|
||||
|
||||
if (![mgr createDirectoryAtPath:path attributes:attrs])
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
BOOL ConfigFile::WriteData(const void *data, size_t length) {
|
||||
size_t result = write(config_file_, data, length);
|
||||
|
||||
return result == length;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
BOOL ConfigFile::AppendConfigData(const char *key,
|
||||
const void *data, size_t length) {
|
||||
assert(config_file_ != -1);
|
||||
|
||||
if (!key) {
|
||||
DEBUGLOG(stderr, "Breakpad: Missing Key\n");
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
DEBUGLOG(stderr, "Breakpad: Missing data for key: %s\n", key ? key :
|
||||
"<Unknown Key>");
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Write the key, \n, length of data (ascii integer), \n, data
|
||||
char buffer[16];
|
||||
char nl = '\n';
|
||||
BOOL result = WriteData(key, strlen(key));
|
||||
|
||||
snprintf(buffer, sizeof(buffer) - 1, "\n%lu\n", length);
|
||||
result &= WriteData(buffer, strlen(buffer));
|
||||
result &= WriteData(data, length);
|
||||
result &= WriteData(&nl, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
BOOL ConfigFile::AppendConfigString(const char *key,
|
||||
const char *value) {
|
||||
return AppendConfigData(key, value, strlen(value));
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void ConfigFile::WriteFile(const SimpleStringDictionary *configurationParameters,
|
||||
const char *dump_dir,
|
||||
const char *minidump_id) {
|
||||
|
||||
assert(config_file_ == -1);
|
||||
|
||||
// Open and write out configuration file preamble
|
||||
strlcpy(config_file_path_, "/tmp/Config-XXXXXX",
|
||||
sizeof(config_file_path_));
|
||||
config_file_ = mkstemp(config_file_path_);
|
||||
|
||||
if (config_file_ == -1) {
|
||||
DEBUGLOG(stderr,
|
||||
"mkstemp(config_file_path_) == -1 (%s)\n",
|
||||
strerror(errno));
|
||||
return;
|
||||
}
|
||||
else {
|
||||
DEBUGLOG(stderr, "Writing config file to (%s)\n", config_file_path_);
|
||||
}
|
||||
|
||||
has_created_file_ = true;
|
||||
|
||||
// Add the minidump dir
|
||||
AppendConfigString(kReporterMinidumpDirectoryKey, dump_dir);
|
||||
AppendConfigString(kReporterMinidumpIDKey, minidump_id);
|
||||
|
||||
// Write out the configuration parameters
|
||||
BOOL result = YES;
|
||||
const SimpleStringDictionary &dictionary = *configurationParameters;
|
||||
|
||||
const KeyValueEntry *entry = NULL;
|
||||
SimpleStringDictionaryIterator iter(dictionary);
|
||||
|
||||
while ((entry = iter.Next())) {
|
||||
DEBUGLOG(stderr,
|
||||
"config: (%s) -> (%s)\n",
|
||||
entry->GetKey(),
|
||||
entry->GetValue());
|
||||
result = AppendConfigString(entry->GetKey(), entry->GetValue());
|
||||
|
||||
if (!result)
|
||||
break;
|
||||
}
|
||||
|
||||
close(config_file_);
|
||||
config_file_ = -1;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void Inspector::Inspect(const char *receive_port_name) {
|
||||
kern_return_t result = ServiceCheckIn(receive_port_name);
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
result = ReadMessages();
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
// Inspect the task and write a minidump file.
|
||||
bool wrote_minidump = InspectTask();
|
||||
|
||||
// Send acknowledgement to the crashed process that the inspection
|
||||
// has finished. It will then be able to cleanly exit.
|
||||
// The return value is ignored because failure isn't fatal. If the process
|
||||
// didn't get the message there's nothing we can do, and we still want to
|
||||
// send the report.
|
||||
SendAcknowledgement();
|
||||
|
||||
if (wrote_minidump) {
|
||||
// Ask the user if he wants to upload the crash report to a server,
|
||||
// and do so if he agrees.
|
||||
LaunchReporter(config_file_.GetFilePath());
|
||||
} else {
|
||||
fprintf(stderr, "Inspection of crashed process failed\n");
|
||||
}
|
||||
|
||||
// Now that we're done reading messages, cleanup the service, but only
|
||||
// if there was an actual exception
|
||||
// Otherwise, it means the dump was generated on demand and the process
|
||||
// lives on, and we might be needed again in the future.
|
||||
if (exception_code_) {
|
||||
ServiceCheckOut(receive_port_name);
|
||||
}
|
||||
} else {
|
||||
PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) {
|
||||
// We need to get the mach port representing this service, so we can
|
||||
// get information from the crashed process.
|
||||
kern_return_t kr = bootstrap_check_in(bootstrap_port,
|
||||
(char*)receive_port_name,
|
||||
&service_rcv_port_);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
#if VERBOSE
|
||||
PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()");
|
||||
#endif
|
||||
}
|
||||
|
||||
return kr;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) {
|
||||
// We're done receiving mach messages from the crashed process,
|
||||
// so clean up a bit.
|
||||
kern_return_t kr;
|
||||
|
||||
// DO NOT use mach_port_deallocate() here -- it will fail and the
|
||||
// following bootstrap_register() will also fail leaving our service
|
||||
// name hanging around forever (until reboot)
|
||||
kr = mach_port_destroy(mach_task_self(), service_rcv_port_);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
PRINT_MACH_RESULT(kr,
|
||||
"Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()");
|
||||
return kr;
|
||||
}
|
||||
|
||||
// Unregister the service associated with the receive port.
|
||||
kr = bootstrap_register(bootstrap_port,
|
||||
(char*)receive_port_name,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()");
|
||||
}
|
||||
|
||||
return kr;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
kern_return_t Inspector::ReadMessages() {
|
||||
// Wait for an initial message from the crashed process containing basic
|
||||
// information about the crash.
|
||||
ReceivePort receive_port(service_rcv_port_);
|
||||
|
||||
MachReceiveMessage message;
|
||||
kern_return_t result = receive_port.WaitForMessage(&message, 1000);
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
InspectorInfo &info = (InspectorInfo &)*message.GetData();
|
||||
exception_type_ = info.exception_type;
|
||||
exception_code_ = info.exception_code;
|
||||
exception_subcode_ = info.exception_subcode;
|
||||
|
||||
#if VERBOSE
|
||||
printf("message ID = %d\n", message.GetMessageID());
|
||||
#endif
|
||||
|
||||
remote_task_ = message.GetTranslatedPort(0);
|
||||
crashing_thread_ = message.GetTranslatedPort(1);
|
||||
handler_thread_ = message.GetTranslatedPort(2);
|
||||
ack_port_ = message.GetTranslatedPort(3);
|
||||
|
||||
#if VERBOSE
|
||||
printf("exception_type = %d\n", exception_type_);
|
||||
printf("exception_code = %d\n", exception_code_);
|
||||
printf("exception_subcode = %d\n", exception_subcode_);
|
||||
printf("remote_task = %d\n", remote_task_);
|
||||
printf("crashing_thread = %d\n", crashing_thread_);
|
||||
printf("handler_thread = %d\n", handler_thread_);
|
||||
printf("ack_port_ = %d\n", ack_port_);
|
||||
printf("parameter count = %d\n", info.parameter_count);
|
||||
#endif
|
||||
|
||||
// In certain situations where multiple crash requests come
|
||||
// through quickly, we can end up with the mach IPC messages not
|
||||
// coming through correctly. Since we don't know what parameters
|
||||
// we've missed, we can't do much besides abort the crash dump
|
||||
// situation in this case.
|
||||
unsigned int parameters_read = 0;
|
||||
// The initial message contains the number of key value pairs that
|
||||
// we are expected to read.
|
||||
// Read each key/value pair, one mach message per key/value pair.
|
||||
for (unsigned int i = 0; i < info.parameter_count; ++i) {
|
||||
MachReceiveMessage message;
|
||||
result = receive_port.WaitForMessage(&message, 1000);
|
||||
|
||||
if(result == KERN_SUCCESS) {
|
||||
KeyValueMessageData &key_value_data =
|
||||
(KeyValueMessageData&)*message.GetData();
|
||||
// If we get a blank key, make sure we don't increment the
|
||||
// parameter count; in some cases (notably on-demand generation
|
||||
// many times in a short period of time) caused the Mach IPC
|
||||
// messages to not come through correctly.
|
||||
if (strlen(key_value_data.key) == 0) {
|
||||
continue;
|
||||
}
|
||||
parameters_read++;
|
||||
|
||||
config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
|
||||
} else {
|
||||
PRINT_MACH_RESULT(result, "Inspector: key/value message");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parameters_read != info.parameter_count) {
|
||||
DEBUGLOG(stderr, "Only read %d parameters instead of %d, aborting crash "
|
||||
"dump generation.", parameters_read, info.parameter_count);
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Sets keys in the parameters dictionary that are specific to process uptime.
|
||||
// The two we set are process up time, and process crash time.
|
||||
void Inspector::SetCrashTimeParameters() {
|
||||
// Set process uptime parameter
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
|
||||
char processUptimeString[32], processCrashtimeString[32];
|
||||
const char *processStartTimeString =
|
||||
config_params_.GetValueForKey(BREAKPAD_PROCESS_START_TIME);
|
||||
|
||||
// Set up time if we've received the start time.
|
||||
if (processStartTimeString) {
|
||||
time_t processStartTime = strtol(processStartTimeString, NULL, 10);
|
||||
time_t processUptime = tv.tv_sec - processStartTime;
|
||||
sprintf(processUptimeString, "%d", processUptime);
|
||||
config_params_.SetKeyValue(BREAKPAD_PROCESS_UP_TIME, processUptimeString);
|
||||
}
|
||||
|
||||
sprintf(processCrashtimeString, "%d", tv.tv_sec);
|
||||
config_params_.SetKeyValue(BREAKPAD_PROCESS_CRASH_TIME,
|
||||
processCrashtimeString);
|
||||
}
|
||||
|
||||
bool Inspector::InspectTask() {
|
||||
// keep the task quiet while we're looking at it
|
||||
task_suspend(remote_task_);
|
||||
DEBUGLOG(stderr, "Suspended Remote task\n");
|
||||
|
||||
NSString *minidumpDir;
|
||||
|
||||
const char *minidumpDirectory =
|
||||
config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY);
|
||||
|
||||
SetCrashTimeParameters();
|
||||
// If the client app has not specified a minidump directory,
|
||||
// use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
|
||||
if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) {
|
||||
NSArray *libraryDirectories =
|
||||
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
|
||||
NSUserDomainMask,
|
||||
YES);
|
||||
|
||||
NSString *applicationSupportDirectory =
|
||||
[libraryDirectories objectAtIndex:0];
|
||||
NSString *library_subdirectory = [NSString
|
||||
stringWithUTF8String:kDefaultLibrarySubdirectory];
|
||||
NSString *breakpad_product = [NSString
|
||||
stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)];
|
||||
|
||||
NSArray *path_components = [NSArray
|
||||
arrayWithObjects:applicationSupportDirectory,
|
||||
library_subdirectory,
|
||||
breakpad_product,
|
||||
nil];
|
||||
|
||||
minidumpDir = [NSString pathWithComponents:path_components];
|
||||
} else {
|
||||
minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory]
|
||||
stringByExpandingTildeInPath];
|
||||
}
|
||||
DEBUGLOG(stderr,
|
||||
"Writing minidump to directory (%s)\n",
|
||||
[minidumpDir UTF8String]);
|
||||
|
||||
MinidumpLocation minidumpLocation(minidumpDir);
|
||||
|
||||
// Obscure bug alert:
|
||||
// Don't use [NSString stringWithFormat] to build up the path here since it
|
||||
// assumes system encoding and in RTL locales will prepend an LTR override
|
||||
// character for paths beginning with '/' which fileSystemRepresentation does
|
||||
// not remove. Filed as rdar://6889706 .
|
||||
NSString *path_ns = [NSString
|
||||
stringWithUTF8String:minidumpLocation.GetPath()];
|
||||
NSString *pathid_ns = [NSString
|
||||
stringWithUTF8String:minidumpLocation.GetID()];
|
||||
NSString *minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns];
|
||||
minidumpPath = [minidumpPath
|
||||
stringByAppendingPathExtension:@"dmp"];
|
||||
|
||||
DEBUGLOG(stderr,
|
||||
"minidump path (%s)\n",
|
||||
[minidumpPath UTF8String]);
|
||||
|
||||
|
||||
config_file_.WriteFile( &config_params_,
|
||||
minidumpLocation.GetPath(),
|
||||
minidumpLocation.GetID());
|
||||
|
||||
|
||||
MinidumpGenerator generator(remote_task_, handler_thread_);
|
||||
|
||||
if (exception_type_ && exception_code_) {
|
||||
generator.SetExceptionInformation(exception_type_,
|
||||
exception_code_,
|
||||
exception_subcode_,
|
||||
crashing_thread_);
|
||||
}
|
||||
|
||||
|
||||
bool result = generator.Write([minidumpPath fileSystemRepresentation]);
|
||||
|
||||
if (result) {
|
||||
DEBUGLOG(stderr, "Wrote minidump - OK\n");
|
||||
} else {
|
||||
DEBUGLOG(stderr, "Error writing minidump - errno=%s\n", strerror(errno));
|
||||
}
|
||||
|
||||
// let the task continue
|
||||
task_resume(remote_task_);
|
||||
DEBUGLOG(stderr, "Resumed remote task\n");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// The crashed task needs to be told that the inspection has finished.
|
||||
// It will wait on a mach port (with timeout) until we send acknowledgement.
|
||||
kern_return_t Inspector::SendAcknowledgement() {
|
||||
if (ack_port_ != MACH_PORT_DEAD) {
|
||||
MachPortSender sender(ack_port_);
|
||||
MachSendMessage ack_message(kMsgType_InspectorAcknowledgement);
|
||||
|
||||
DEBUGLOG(stderr, "Inspector: trying to send acknowledgement to port %d\n",
|
||||
ack_port_);
|
||||
|
||||
kern_return_t result = sender.SendMessage(ack_message, 2000);
|
||||
|
||||
#if VERBOSE
|
||||
PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement");
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DEBUGLOG(stderr, "Inspector: port translation failure!\n");
|
||||
return KERN_INVALID_NAME;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void Inspector::LaunchReporter(const char *inConfigFilePath) {
|
||||
// Extract the path to the reporter executable.
|
||||
const char *reporterExecutablePath =
|
||||
config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION);
|
||||
DEBUGLOG(stderr, "reporter path = %s\n", reporterExecutablePath);
|
||||
|
||||
// Setup and launch the crash dump sender.
|
||||
const char *argv[3];
|
||||
argv[0] = reporterExecutablePath;
|
||||
argv[1] = inConfigFilePath;
|
||||
argv[2] = NULL;
|
||||
|
||||
// Launch the reporter
|
||||
pid_t pid = fork();
|
||||
|
||||
// If we're in the child, load in our new executable and run.
|
||||
// The parent will not wait for the child to complete.
|
||||
if (pid == 0) {
|
||||
execv(argv[0], (char * const *)argv);
|
||||
config_file_.Unlink(); // launch failed - get rid of config file
|
||||
DEBUGLOG(stderr, "Inspector: unable to launch reporter app\n");
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Wait until the Reporter child process exits.
|
||||
//
|
||||
|
||||
// We'll use a timeout of one minute.
|
||||
int timeoutCount = 60; // 60 seconds
|
||||
|
||||
while (timeoutCount-- > 0) {
|
||||
int status;
|
||||
pid_t result = waitpid(pid, &status, WNOHANG);
|
||||
|
||||
if (result == 0) {
|
||||
// The child has not yet finished.
|
||||
sleep(1);
|
||||
} else if (result == -1) {
|
||||
DEBUGLOG(stderr, "Inspector: waitpid error (%d) waiting for reporter app\n",
|
||||
errno);
|
||||
break;
|
||||
} else {
|
||||
// child has finished
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,258 +0,0 @@
|
||||
#ifndef _exc_user_
|
||||
#define _exc_user_
|
||||
|
||||
/* Module exc */
|
||||
|
||||
#include <string.h>
|
||||
#include <mach/ndr.h>
|
||||
#include <mach/boolean.h>
|
||||
#include <mach/kern_return.h>
|
||||
#include <mach/notify.h>
|
||||
#include <mach/mach_types.h>
|
||||
#include <mach/message.h>
|
||||
#include <mach/mig_errors.h>
|
||||
#include <mach/port.h>
|
||||
|
||||
#ifdef AUTOTEST
|
||||
#ifndef FUNCTION_PTR_T
|
||||
#define FUNCTION_PTR_T
|
||||
typedef void (*function_ptr_t)(mach_port_t, char *, mach_msg_type_number_t);
|
||||
typedef struct {
|
||||
char *name;
|
||||
function_ptr_t function;
|
||||
} function_table_entry;
|
||||
typedef function_table_entry *function_table_t;
|
||||
#endif /* FUNCTION_PTR_T */
|
||||
#endif /* AUTOTEST */
|
||||
|
||||
#ifndef exc_MSG_COUNT
|
||||
#define exc_MSG_COUNT 3
|
||||
#endif /* exc_MSG_COUNT */
|
||||
|
||||
#include <mach/std_types.h>
|
||||
#include <mach/mig.h>
|
||||
#include <mach/mig.h>
|
||||
#include <mach/mach_types.h>
|
||||
|
||||
#ifdef __BeforeMigUserHeader
|
||||
__BeforeMigUserHeader
|
||||
#endif /* __BeforeMigUserHeader */
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__BEGIN_DECLS
|
||||
|
||||
|
||||
/* Routine exception_raise */
|
||||
#ifdef mig_external
|
||||
mig_external
|
||||
#else
|
||||
extern
|
||||
#endif /* mig_external */
|
||||
kern_return_t exception_raise
|
||||
(
|
||||
mach_port_t exception_port,
|
||||
mach_port_t thread,
|
||||
mach_port_t task,
|
||||
exception_type_t exception,
|
||||
exception_data_t code,
|
||||
mach_msg_type_number_t codeCnt
|
||||
);
|
||||
|
||||
/* Routine exception_raise_state */
|
||||
#ifdef mig_external
|
||||
mig_external
|
||||
#else
|
||||
extern
|
||||
#endif /* mig_external */
|
||||
kern_return_t exception_raise_state
|
||||
(
|
||||
mach_port_t exception_port,
|
||||
exception_type_t exception,
|
||||
const exception_data_t code,
|
||||
mach_msg_type_number_t codeCnt,
|
||||
int *flavor,
|
||||
const thread_state_t old_state,
|
||||
mach_msg_type_number_t old_stateCnt,
|
||||
thread_state_t new_state,
|
||||
mach_msg_type_number_t *new_stateCnt
|
||||
);
|
||||
|
||||
/* Routine exception_raise_state_identity */
|
||||
#ifdef mig_external
|
||||
mig_external
|
||||
#else
|
||||
extern
|
||||
#endif /* mig_external */
|
||||
kern_return_t exception_raise_state_identity
|
||||
(
|
||||
mach_port_t exception_port,
|
||||
mach_port_t thread,
|
||||
mach_port_t task,
|
||||
exception_type_t exception,
|
||||
exception_data_t code,
|
||||
mach_msg_type_number_t codeCnt,
|
||||
int *flavor,
|
||||
thread_state_t old_state,
|
||||
mach_msg_type_number_t old_stateCnt,
|
||||
thread_state_t new_state,
|
||||
mach_msg_type_number_t *new_stateCnt
|
||||
);
|
||||
|
||||
__END_DECLS
|
||||
|
||||
/********************** Caution **************************/
|
||||
/* The following data types should be used to calculate */
|
||||
/* maximum message sizes only. The actual message may be */
|
||||
/* smaller, and the position of the arguments within the */
|
||||
/* message layout may vary from what is presented here. */
|
||||
/* For example, if any of the arguments are variable- */
|
||||
/* sized, and less than the maximum is sent, the data */
|
||||
/* will be packed tight in the actual message to reduce */
|
||||
/* the presence of holes. */
|
||||
/********************** Caution **************************/
|
||||
|
||||
/* typedefs for all requests */
|
||||
|
||||
#ifndef __Request__exc_subsystem__defined
|
||||
#define __Request__exc_subsystem__defined
|
||||
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack(4)
|
||||
#endif
|
||||
typedef struct {
|
||||
mach_msg_header_t Head;
|
||||
/* start of the kernel processed data */
|
||||
mach_msg_body_t msgh_body;
|
||||
mach_msg_port_descriptor_t thread;
|
||||
mach_msg_port_descriptor_t task;
|
||||
/* end of the kernel processed data */
|
||||
NDR_record_t NDR;
|
||||
exception_type_t exception;
|
||||
mach_msg_type_number_t codeCnt;
|
||||
integer_t code[2];
|
||||
} __Request__exception_raise_t;
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack()
|
||||
#endif
|
||||
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack(4)
|
||||
#endif
|
||||
typedef struct {
|
||||
mach_msg_header_t Head;
|
||||
NDR_record_t NDR;
|
||||
exception_type_t exception;
|
||||
mach_msg_type_number_t codeCnt;
|
||||
integer_t code[2];
|
||||
int flavor;
|
||||
mach_msg_type_number_t old_stateCnt;
|
||||
natural_t old_state[144];
|
||||
} __Request__exception_raise_state_t;
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack()
|
||||
#endif
|
||||
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack(4)
|
||||
#endif
|
||||
typedef struct {
|
||||
mach_msg_header_t Head;
|
||||
/* start of the kernel processed data */
|
||||
mach_msg_body_t msgh_body;
|
||||
mach_msg_port_descriptor_t thread;
|
||||
mach_msg_port_descriptor_t task;
|
||||
/* end of the kernel processed data */
|
||||
NDR_record_t NDR;
|
||||
exception_type_t exception;
|
||||
mach_msg_type_number_t codeCnt;
|
||||
integer_t code[2];
|
||||
int flavor;
|
||||
mach_msg_type_number_t old_stateCnt;
|
||||
natural_t old_state[144];
|
||||
} __Request__exception_raise_state_identity_t;
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack()
|
||||
#endif
|
||||
#endif /* !__Request__exc_subsystem__defined */
|
||||
|
||||
/* union of all requests */
|
||||
|
||||
#ifndef __RequestUnion__exc_subsystem__defined
|
||||
#define __RequestUnion__exc_subsystem__defined
|
||||
union __RequestUnion__exc_subsystem {
|
||||
__Request__exception_raise_t Request_exception_raise;
|
||||
__Request__exception_raise_state_t Request_exception_raise_state;
|
||||
__Request__exception_raise_state_identity_t Request_exception_raise_state_identity;
|
||||
};
|
||||
#endif /* !__RequestUnion__exc_subsystem__defined */
|
||||
/* typedefs for all replies */
|
||||
|
||||
#ifndef __Reply__exc_subsystem__defined
|
||||
#define __Reply__exc_subsystem__defined
|
||||
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack(4)
|
||||
#endif
|
||||
typedef struct {
|
||||
mach_msg_header_t Head;
|
||||
NDR_record_t NDR;
|
||||
kern_return_t RetCode;
|
||||
} __Reply__exception_raise_t;
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack()
|
||||
#endif
|
||||
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack(4)
|
||||
#endif
|
||||
typedef struct {
|
||||
mach_msg_header_t Head;
|
||||
NDR_record_t NDR;
|
||||
kern_return_t RetCode;
|
||||
int flavor;
|
||||
mach_msg_type_number_t new_stateCnt;
|
||||
natural_t new_state[144];
|
||||
} __Reply__exception_raise_state_t;
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack()
|
||||
#endif
|
||||
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack(4)
|
||||
#endif
|
||||
typedef struct {
|
||||
mach_msg_header_t Head;
|
||||
NDR_record_t NDR;
|
||||
kern_return_t RetCode;
|
||||
int flavor;
|
||||
mach_msg_type_number_t new_stateCnt;
|
||||
natural_t new_state[144];
|
||||
} __Reply__exception_raise_state_identity_t;
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack()
|
||||
#endif
|
||||
#endif /* !__Reply__exc_subsystem__defined */
|
||||
|
||||
/* union of all replies */
|
||||
|
||||
#ifndef __ReplyUnion__exc_subsystem__defined
|
||||
#define __ReplyUnion__exc_subsystem__defined
|
||||
union __ReplyUnion__exc_subsystem {
|
||||
__Reply__exception_raise_t Reply_exception_raise;
|
||||
__Reply__exception_raise_state_t Reply_exception_raise_state;
|
||||
__Reply__exception_raise_state_identity_t Reply_exception_raise_state_identity;
|
||||
};
|
||||
#endif /* !__RequestUnion__exc_subsystem__defined */
|
||||
|
||||
#ifndef subsystem_to_name_map_exc
|
||||
#define subsystem_to_name_map_exc \
|
||||
{ "exception_raise", 2401 },\
|
||||
{ "exception_raise_state", 2402 },\
|
||||
{ "exception_raise_state_identity", 2403 }
|
||||
#endif
|
||||
|
||||
#ifdef __AfterMigUserHeader
|
||||
__AfterMigUserHeader
|
||||
#endif /* __AfterMigUserHeader */
|
||||
|
||||
#endif /* _exc_user_ */
|
||||
@@ -1,381 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 1989, 1993
|
||||
* The Regents of the University of California. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. All advertising materials mentioning features or use of this software
|
||||
* must display the following acknowledgement:
|
||||
* This product includes software developed by the University of
|
||||
* California, Berkeley and its contributors.
|
||||
* 4. Neither the name of the University nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
/* nealsid:
|
||||
* This file was copied from libc/gen/nlist.c from Darwin's source code
|
||||
* The version of nlist used as a base is from 10.5.2, libc-498
|
||||
* http://www.opensource.apple.com/darwinsource/10.5.2/Libc-498/gen/nlist.c
|
||||
*
|
||||
* The full tarball is at:
|
||||
* http://www.opensource.apple.com/darwinsource/tarballs/apsl/Libc-498.tar.gz
|
||||
*
|
||||
* I've modified it to be compatible with 64-bit images. However,
|
||||
* 32-bit compatibility has not been retained.
|
||||
*/
|
||||
|
||||
#ifdef __LP64__
|
||||
|
||||
#include <mach-o/nlist.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/fat.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
#include "breakpad_nlist_64.h"
|
||||
#include <TargetConditionals.h>
|
||||
#include <stdio.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
/* Stuff lifted from <a.out.h> and <sys/exec.h> since they are gone */
|
||||
/*
|
||||
* Header prepended to each a.out file.
|
||||
*/
|
||||
struct exec {
|
||||
unsigned short a_machtype; /* machine type */
|
||||
unsigned short a_magic; /* magic number */
|
||||
unsigned long a_text; /* size of text segment */
|
||||
unsigned long a_data; /* size of initialized data */
|
||||
unsigned long a_bss; /* size of uninitialized data */
|
||||
unsigned long a_syms; /* size of symbol table */
|
||||
unsigned long a_entry; /* entry point */
|
||||
unsigned long a_trsize; /* size of text relocation */
|
||||
unsigned long a_drsize; /* size of data relocation */
|
||||
};
|
||||
|
||||
#define OMAGIC 0407 /* old impure format */
|
||||
#define NMAGIC 0410 /* read-only text */
|
||||
#define ZMAGIC 0413 /* demand load format */
|
||||
|
||||
#define N_BADMAG(x) \
|
||||
(((x).a_magic)!=OMAGIC && ((x).a_magic)!=NMAGIC && ((x).a_magic)!=ZMAGIC)
|
||||
#define N_TXTOFF(x) \
|
||||
((x).a_magic==ZMAGIC ? 0 : sizeof (struct exec))
|
||||
#define N_SYMOFF(x) \
|
||||
(N_TXTOFF(x) + (x).a_text+(x).a_data + (x).a_trsize+(x).a_drsize)
|
||||
|
||||
int
|
||||
__breakpad_fdnlist_64(int fd, breakpad_nlist *list, const char **symbolNames);
|
||||
|
||||
/*
|
||||
* nlist - retreive attributes from name list (string table version)
|
||||
*/
|
||||
|
||||
int
|
||||
breakpad_nlist_64(const char *name,
|
||||
breakpad_nlist *list,
|
||||
const char **symbolNames) {
|
||||
int fd, n;
|
||||
|
||||
fd = open(name, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return (-1);
|
||||
n = __breakpad_fdnlist_64(fd, list, symbolNames);
|
||||
(void)close(fd);
|
||||
return (n);
|
||||
}
|
||||
|
||||
/* Note: __fdnlist() is called from kvm_nlist in libkvm's kvm.c */
|
||||
|
||||
int
|
||||
__breakpad_fdnlist_64(int fd, breakpad_nlist *list, const char **symbolNames) {
|
||||
register breakpad_nlist *p, *q;
|
||||
breakpad_nlist space[BUFSIZ/sizeof (breakpad_nlist)];
|
||||
|
||||
const register char *s1, *s2;
|
||||
register int n, m;
|
||||
int maxlen, nreq;
|
||||
off_t sa; /* symbol address */
|
||||
off_t ss; /* start of strings */
|
||||
struct exec buf;
|
||||
unsigned arch_offset = 0;
|
||||
|
||||
maxlen = 500;
|
||||
for (q = list, nreq = 0;
|
||||
symbolNames[q-list] && symbolNames[q-list][0];
|
||||
q++, nreq++) {
|
||||
|
||||
q->n_type = 0;
|
||||
q->n_value = 0;
|
||||
q->n_desc = 0;
|
||||
q->n_sect = 0;
|
||||
q->n_un.n_strx = 0;
|
||||
}
|
||||
|
||||
if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf) ||
|
||||
(N_BADMAG(buf) && *((long *)&buf) != MH_MAGIC &&
|
||||
NXSwapBigLongToHost(*((long *)&buf)) != FAT_MAGIC) &&
|
||||
/* nealsid: The following is the big-endian ppc64 check */
|
||||
(*((uint32_t*)&buf)) != FAT_MAGIC) {
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/* Deal with fat file if necessary */
|
||||
if (NXSwapBigLongToHost(*((long *)&buf)) == FAT_MAGIC ||
|
||||
/* nealsid: The following is the big-endian ppc64 check */
|
||||
*((int*)&buf) == FAT_MAGIC) {
|
||||
struct host_basic_info hbi;
|
||||
struct fat_header fh;
|
||||
struct fat_arch *fat_archs, *fap;
|
||||
unsigned i;
|
||||
host_t host;
|
||||
|
||||
/* Get our host info */
|
||||
host = mach_host_self();
|
||||
i = HOST_BASIC_INFO_COUNT;
|
||||
kern_return_t kr;
|
||||
if ((kr=host_info(host, HOST_BASIC_INFO,
|
||||
(host_info_t)(&hbi), &i)) != KERN_SUCCESS) {
|
||||
return (-1);
|
||||
}
|
||||
mach_port_deallocate(mach_task_self(), host);
|
||||
|
||||
/* Read in the fat header */
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
if (read(fd, (char *)&fh, sizeof(fh)) != sizeof(fh)) {
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/* Convert fat_narchs to host byte order */
|
||||
fh.nfat_arch = NXSwapBigLongToHost(fh.nfat_arch);
|
||||
|
||||
/* Read in the fat archs */
|
||||
fat_archs = (struct fat_arch *)malloc(fh.nfat_arch *
|
||||
sizeof(struct fat_arch));
|
||||
if (fat_archs == NULL) {
|
||||
return (-1);
|
||||
}
|
||||
if (read(fd, (char *)fat_archs,
|
||||
sizeof(struct fat_arch) * fh.nfat_arch) !=
|
||||
sizeof(struct fat_arch) * fh.nfat_arch) {
|
||||
free(fat_archs);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert archs to host byte ordering (a constraint of
|
||||
* cpusubtype_getbestarch()
|
||||
*/
|
||||
for (i = 0; i < fh.nfat_arch; i++) {
|
||||
fat_archs[i].cputype =
|
||||
NXSwapBigLongToHost(fat_archs[i].cputype);
|
||||
fat_archs[i].cpusubtype =
|
||||
NXSwapBigLongToHost(fat_archs[i].cpusubtype);
|
||||
fat_archs[i].offset =
|
||||
NXSwapBigLongToHost(fat_archs[i].offset);
|
||||
fat_archs[i].size =
|
||||
NXSwapBigLongToHost(fat_archs[i].size);
|
||||
fat_archs[i].align =
|
||||
NXSwapBigLongToHost(fat_archs[i].align);
|
||||
}
|
||||
|
||||
fap = NULL;
|
||||
for (i = 0; i < fh.nfat_arch; i++) {
|
||||
/* nealsid: Although the original Apple code uses host_info */
|
||||
/* to retrieve the CPU type, the host_info will still return */
|
||||
/* CPU_TYPE_X86 even if running as an x86_64 binary. Given that */
|
||||
/* this code isn't necessary on i386, I've decided to hardcode */
|
||||
/* looking for a 64-bit binary */
|
||||
#if TARGET_CPU_X86_64
|
||||
if (fat_archs[i].cputype == CPU_TYPE_X86_64) {
|
||||
#elif TARGET_CPU_PPC64
|
||||
if (fat_archs[i].cputype == CPU_TYPE_POWERPC64) {
|
||||
#else
|
||||
#error undefined cpu!
|
||||
{
|
||||
#endif
|
||||
fap = &fat_archs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fap) {
|
||||
free(fat_archs);
|
||||
return (-1);
|
||||
}
|
||||
arch_offset = fap->offset;
|
||||
free(fat_archs);
|
||||
|
||||
/* Read in the beginning of the architecture-specific file */
|
||||
lseek(fd, arch_offset, SEEK_SET);
|
||||
if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf)) {
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
|
||||
if (*((int *)&buf) == MH_MAGIC_64) {
|
||||
struct mach_header_64 mh;
|
||||
struct load_command *load_commands, *lcp;
|
||||
struct symtab_command *stp;
|
||||
long i;
|
||||
|
||||
lseek(fd, arch_offset, SEEK_SET);
|
||||
if (read(fd, (char *)&mh, sizeof(mh)) != sizeof(mh)) {
|
||||
return (-1);
|
||||
}
|
||||
load_commands = (struct load_command *)malloc(mh.sizeofcmds);
|
||||
if (load_commands == NULL) {
|
||||
return (-1);
|
||||
}
|
||||
if (read(fd, (char *)load_commands, mh.sizeofcmds) !=
|
||||
mh.sizeofcmds) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
stp = NULL;
|
||||
lcp = load_commands;
|
||||
// nealsid:iterate through all load commands, looking for
|
||||
// LC_SYMTAB load command
|
||||
for (i = 0; i < mh.ncmds; i++) {
|
||||
if (lcp->cmdsize % sizeof(long) != 0 ||
|
||||
lcp->cmdsize <= 0 ||
|
||||
(char *)lcp + lcp->cmdsize >
|
||||
(char *)load_commands + mh.sizeofcmds) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
if (lcp->cmd == LC_SYMTAB) {
|
||||
if (lcp->cmdsize !=
|
||||
sizeof(struct symtab_command)) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
stp = (struct symtab_command *)lcp;
|
||||
break;
|
||||
}
|
||||
lcp = (struct load_command *)
|
||||
((char *)lcp + lcp->cmdsize);
|
||||
}
|
||||
if (stp == NULL) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
// sa points to the beginning of the symbol table
|
||||
sa = stp->symoff + arch_offset;
|
||||
// ss points to the beginning of the string table
|
||||
ss = stp->stroff + arch_offset;
|
||||
// n is the number of bytes in the symbol table
|
||||
// each symbol table entry is an nlist structure
|
||||
n = stp->nsyms * sizeof(breakpad_nlist);
|
||||
free(load_commands);
|
||||
}
|
||||
else {
|
||||
sa = N_SYMOFF(buf) + arch_offset;
|
||||
ss = sa + buf.a_syms + arch_offset;
|
||||
n = buf.a_syms;
|
||||
}
|
||||
|
||||
lseek(fd, sa, SEEK_SET);
|
||||
|
||||
// the algorithm here is to read the nlist entries in m-sized
|
||||
// chunks into q. q is then iterated over. for each entry in q,
|
||||
// use the string table index(q->n_un.n_strx) to read the symbol
|
||||
// name, then scan the nlist entries passed in by the user(via p),
|
||||
// and look for a match
|
||||
while (n) {
|
||||
long savpos;
|
||||
|
||||
m = sizeof (space);
|
||||
if (n < m)
|
||||
m = n;
|
||||
if (read(fd, (char *)space, m) != m)
|
||||
break;
|
||||
n -= m;
|
||||
savpos = lseek(fd, 0, SEEK_CUR);
|
||||
for (q = space; (m -= sizeof(breakpad_nlist)) >= 0; q++) {
|
||||
char nambuf[BUFSIZ];
|
||||
|
||||
if (q->n_un.n_strx == 0 || q->n_type & N_STAB)
|
||||
continue;
|
||||
|
||||
// seek to the location in the binary where the symbol
|
||||
// name is stored & read it into memory
|
||||
lseek(fd, ss+q->n_un.n_strx, SEEK_SET);
|
||||
read(fd, nambuf, maxlen+1);
|
||||
s2 = nambuf;
|
||||
for (p = list;
|
||||
symbolNames[p-list] &&
|
||||
symbolNames[p-list][0];
|
||||
p++) {
|
||||
// get the symbol name the user has passed in that
|
||||
// corresponds to the nlist entry that we're looking at
|
||||
s1 = symbolNames[p - list];
|
||||
while (*s1) {
|
||||
if (*s1++ != *s2++)
|
||||
goto cont;
|
||||
}
|
||||
if (*s2)
|
||||
goto cont;
|
||||
|
||||
p->n_value = q->n_value;
|
||||
p->n_type = q->n_type;
|
||||
p->n_desc = q->n_desc;
|
||||
p->n_sect = q->n_sect;
|
||||
p->n_un.n_strx = q->n_un.n_strx;
|
||||
if (--nreq == 0)
|
||||
return (nreq);
|
||||
|
||||
break;
|
||||
cont: ;
|
||||
}
|
||||
}
|
||||
lseek(fd, savpos, SEEK_SET);
|
||||
}
|
||||
return (nreq);
|
||||
}
|
||||
|
||||
#endif /* __LP64__ */
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// breakpad_nlist.h
|
||||
//
|
||||
// This file is meant to provide a header for clients of the modified
|
||||
// nlist function implemented to work on 64-bit.
|
||||
|
||||
#ifndef CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__
|
||||
|
||||
typedef struct nlist_64 breakpad_nlist;
|
||||
|
||||
int
|
||||
breakpad_nlist_64(const char *name,
|
||||
breakpad_nlist *list,
|
||||
const char **symbolNames);
|
||||
|
||||
#endif /* CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__ */
|
||||
@@ -33,14 +33,11 @@ extern "C" { // needed to compile on Leopard
|
||||
#include <stdio.h>
|
||||
}
|
||||
|
||||
#include "breakpad_nlist_64.h"
|
||||
#include <dlfcn.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <algorithm>
|
||||
#include "client/mac/handler/dynamic_images.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
|
||||
//==============================================================================
|
||||
// Returns the size of the memory region containing |address| and the
|
||||
// number of bytes from |address| to the end of the region.
|
||||
@@ -49,30 +46,27 @@ namespace google_breakpad {
|
||||
// first in order to handle cases when we're reading strings and they
|
||||
// straddle two vm regions.
|
||||
//
|
||||
static mach_vm_size_t GetMemoryRegionSize(task_port_t target_task,
|
||||
const void* address,
|
||||
mach_vm_size_t *size_to_end) {
|
||||
mach_vm_address_t region_base = (mach_vm_address_t)address;
|
||||
mach_vm_size_t region_size;
|
||||
static vm_size_t GetMemoryRegionSize(task_port_t target_task,
|
||||
const void* address,
|
||||
vm_size_t *size_to_end) {
|
||||
vm_address_t region_base = (vm_address_t)address;
|
||||
vm_size_t region_size;
|
||||
natural_t nesting_level = 0;
|
||||
vm_region_submap_info_64 submap_info;
|
||||
mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
|
||||
|
||||
vm_region_submap_info submap_info;
|
||||
mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT;
|
||||
|
||||
// Get information about the vm region containing |address|
|
||||
vm_region_recurse_info_t region_info;
|
||||
region_info = reinterpret_cast<vm_region_recurse_info_t>(&submap_info);
|
||||
|
||||
kern_return_t result =
|
||||
mach_vm_region_recurse(target_task,
|
||||
®ion_base,
|
||||
®ion_size,
|
||||
&nesting_level,
|
||||
region_info,
|
||||
&info_count);
|
||||
|
||||
kern_return_t result =
|
||||
vm_region_recurse(target_task,
|
||||
®ion_base,
|
||||
®ion_size,
|
||||
&nesting_level,
|
||||
reinterpret_cast<vm_region_recurse_info_t>(&submap_info),
|
||||
&info_count);
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
// Get distance from |address| to the end of this region
|
||||
*size_to_end = region_base + region_size -(mach_vm_address_t)address;
|
||||
*size_to_end = region_base + region_size -(vm_address_t)address;
|
||||
|
||||
// If we want to handle strings as long as 4096 characters we may need
|
||||
// to check if there's a vm region immediately following the first one.
|
||||
@@ -80,19 +74,20 @@ static mach_vm_size_t GetMemoryRegionSize(task_port_t target_task,
|
||||
// of the second region.
|
||||
if (*size_to_end < 4096) {
|
||||
// Second region starts where the first one ends
|
||||
mach_vm_address_t region_base2 =
|
||||
(mach_vm_address_t)(region_base + region_size);
|
||||
mach_vm_size_t region_size2;
|
||||
vm_address_t region_base2 =
|
||||
(vm_address_t)(region_base + region_size);
|
||||
vm_size_t region_size2;
|
||||
|
||||
// Get information about the following vm region
|
||||
result =
|
||||
mach_vm_region_recurse(target_task,
|
||||
®ion_base2,
|
||||
®ion_size2,
|
||||
&nesting_level,
|
||||
region_info,
|
||||
&info_count);
|
||||
|
||||
result =
|
||||
vm_region_recurse(
|
||||
target_task,
|
||||
®ion_base2,
|
||||
®ion_size2,
|
||||
&nesting_level,
|
||||
reinterpret_cast<vm_region_recurse_info_t>(&submap_info),
|
||||
&info_count);
|
||||
|
||||
// Extend region_size to go all the way to the end of the 2nd region
|
||||
if (result == KERN_SUCCESS
|
||||
&& region_base2 == region_base + region_size) {
|
||||
@@ -100,13 +95,13 @@ static mach_vm_size_t GetMemoryRegionSize(task_port_t target_task,
|
||||
}
|
||||
}
|
||||
|
||||
*size_to_end = region_base + region_size -(mach_vm_address_t)address;
|
||||
*size_to_end = region_base + region_size -(vm_address_t)address;
|
||||
} else {
|
||||
region_size = 0;
|
||||
*size_to_end = 0;
|
||||
}
|
||||
|
||||
return region_size;
|
||||
|
||||
return region_size;
|
||||
}
|
||||
|
||||
#define kMaxStringLength 8192
|
||||
@@ -120,18 +115,17 @@ static void* ReadTaskString(task_port_t target_task,
|
||||
// The problem is we don't know how much to read until we know how long
|
||||
// the string is. And we don't know how long the string is, until we've read
|
||||
// the memory! So, we'll try to read kMaxStringLength bytes
|
||||
// (or as many bytes as we can until we reach the end of the vm region).
|
||||
mach_vm_size_t size_to_end;
|
||||
// (or as many bytes as we can until we reach the end of the vm region).
|
||||
vm_size_t size_to_end;
|
||||
GetMemoryRegionSize(target_task, address, &size_to_end);
|
||||
|
||||
|
||||
if (size_to_end > 0) {
|
||||
mach_vm_size_t size_to_read =
|
||||
vm_size_t size_to_read =
|
||||
size_to_end > kMaxStringLength ? kMaxStringLength : size_to_end;
|
||||
|
||||
kern_return_t kr;
|
||||
return ReadTaskMemory(target_task, address, size_to_read, &kr);
|
||||
return ReadTaskMemory(target_task, address, size_to_read);
|
||||
}
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -140,46 +134,29 @@ static void* ReadTaskString(task_port_t target_task,
|
||||
// and should be freed by the caller.
|
||||
void* ReadTaskMemory(task_port_t target_task,
|
||||
const void* address,
|
||||
size_t length,
|
||||
kern_return_t *kr) {
|
||||
size_t length) {
|
||||
void* result = NULL;
|
||||
int systemPageSize = getpagesize();
|
||||
|
||||
// use the negative of the page size for the mask to find the page address
|
||||
mach_vm_address_t page_address =
|
||||
reinterpret_cast<mach_vm_address_t>(address) & (-systemPageSize);
|
||||
|
||||
mach_vm_address_t last_page_address =
|
||||
(reinterpret_cast<mach_vm_address_t>(address) + length +
|
||||
(systemPageSize - 1)) & (-systemPageSize);
|
||||
|
||||
mach_vm_size_t page_size = last_page_address - page_address;
|
||||
vm_address_t page_address = reinterpret_cast<vm_address_t>(address) & (-4096);
|
||||
vm_address_t last_page_address =
|
||||
(reinterpret_cast<vm_address_t>(address) + length + 4095) & (-4096);
|
||||
vm_size_t page_size = last_page_address - page_address;
|
||||
uint8_t* local_start;
|
||||
uint32_t local_length;
|
||||
|
||||
kern_return_t r;
|
||||
|
||||
r = mach_vm_read(target_task,
|
||||
page_address,
|
||||
page_size,
|
||||
reinterpret_cast<vm_offset_t*>(&local_start),
|
||||
&local_length);
|
||||
|
||||
|
||||
if (kr != NULL) {
|
||||
*kr = r;
|
||||
}
|
||||
kern_return_t r = vm_read(target_task,
|
||||
page_address,
|
||||
page_size,
|
||||
reinterpret_cast<vm_offset_t*>(&local_start),
|
||||
&local_length);
|
||||
|
||||
if (r == KERN_SUCCESS) {
|
||||
result = malloc(length);
|
||||
if (result != NULL) {
|
||||
memcpy(result,
|
||||
&local_start[(mach_vm_address_t)address - page_address],
|
||||
length);
|
||||
memcpy(result, &local_start[(uint32_t)address - page_address], length);
|
||||
}
|
||||
mach_vm_deallocate(mach_task_self(), (uintptr_t)local_start, local_length);
|
||||
vm_deallocate(mach_task_self(), (uintptr_t)local_start, local_length);
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -187,75 +164,37 @@ void* ReadTaskMemory(task_port_t target_task,
|
||||
|
||||
//==============================================================================
|
||||
// Initializes vmaddr_, vmsize_, and slide_
|
||||
void DynamicImage::CalculateMemoryAndVersionInfo() {
|
||||
breakpad_mach_header *header = GetMachHeader();
|
||||
|
||||
// unless we can process the header, ensure that calls to
|
||||
// IsValid() will return false
|
||||
vmaddr_ = 0;
|
||||
vmsize_ = 0;
|
||||
slide_ = 0;
|
||||
version_ = 0;
|
||||
|
||||
bool foundTextSection = false;
|
||||
bool foundDylibIDCommand = false;
|
||||
|
||||
#if __LP64__
|
||||
if(header->magic != MH_MAGIC_64) {
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if(header->magic != MH_MAGIC) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __LP64__
|
||||
const uint32_t segmentLoadCommand = LC_SEGMENT_64;
|
||||
#else
|
||||
const uint32_t segmentLoadCommand = LC_SEGMENT;
|
||||
#endif
|
||||
void DynamicImage::CalculateMemoryInfo() {
|
||||
mach_header *header = GetMachHeader();
|
||||
|
||||
const struct load_command *cmd =
|
||||
reinterpret_cast<const struct load_command *>(header + 1);
|
||||
|
||||
for (unsigned int i = 0; cmd && (i < header->ncmds); ++i) {
|
||||
if (!foundTextSection) {
|
||||
if (cmd->cmd == segmentLoadCommand) {
|
||||
const breakpad_mach_segment_command *seg =
|
||||
reinterpret_cast<const breakpad_mach_segment_command *>(cmd);
|
||||
if (cmd->cmd == LC_SEGMENT) {
|
||||
const struct segment_command *seg =
|
||||
reinterpret_cast<const struct segment_command *>(cmd);
|
||||
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
vmaddr_ = seg->vmaddr;
|
||||
vmsize_ = seg->vmsize;
|
||||
slide_ = 0;
|
||||
|
||||
if (seg->fileoff == 0 && seg->filesize != 0) {
|
||||
slide_ = (uintptr_t)GetLoadAddress() - (uintptr_t)seg->vmaddr;
|
||||
}
|
||||
foundTextSection = true;
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
vmaddr_ = seg->vmaddr;
|
||||
vmsize_ = seg->vmsize;
|
||||
slide_ = 0;
|
||||
|
||||
if (seg->fileoff == 0 && seg->filesize != 0) {
|
||||
slide_ = (uintptr_t)GetLoadAddress() - (uintptr_t)seg->vmaddr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundDylibIDCommand) {
|
||||
if (cmd->cmd == LC_ID_DYLIB) {
|
||||
const struct dylib_command *dc =
|
||||
reinterpret_cast<const struct dylib_command *>(cmd);
|
||||
|
||||
version_ = dc->dylib.current_version;
|
||||
foundDylibIDCommand = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundDylibIDCommand && foundTextSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
cmd = reinterpret_cast<const struct load_command *>
|
||||
(reinterpret_cast<const char *>(cmd) + cmd->cmdsize);
|
||||
}
|
||||
|
||||
|
||||
// we failed - a call to IsValid() will return false
|
||||
vmaddr_ = 0;
|
||||
vmsize_ = 0;
|
||||
slide_ = 0;
|
||||
}
|
||||
|
||||
void DynamicImage::Print() {
|
||||
@@ -264,11 +203,11 @@ void DynamicImage::Print() {
|
||||
path = "(unknown)";
|
||||
}
|
||||
printf("%p: %s\n", GetLoadAddress(), path);
|
||||
breakpad_mach_header *header = GetMachHeader();
|
||||
mach_header *header = GetMachHeader();
|
||||
MachHeader(*header).Print();
|
||||
printf("vmaddr\t\t: %p\n", reinterpret_cast<void*>(GetVMAddr()));
|
||||
printf("vmsize\t\t: %llu\n", GetVMSize());
|
||||
printf("slide\t\t: %td\n", GetVMAddrSlide());
|
||||
printf("vmsize\t\t: %d\n", GetVMSize());
|
||||
printf("slide\t\t: %d\n", GetVMAddrSlide());
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
@@ -280,12 +219,9 @@ DynamicImages::DynamicImages(mach_port_t task)
|
||||
ReadImageInfoForTask();
|
||||
}
|
||||
|
||||
void* DynamicImages::GetDyldAllImageInfosPointer()
|
||||
{
|
||||
|
||||
const char *imageSymbolName = "_dyld_all_image_infos";
|
||||
const char *dyldPath = "/usr/lib/dyld";
|
||||
#ifndef __LP64__
|
||||
//==============================================================================
|
||||
// This code was written using dyld_debug.c (from Darwin) as a guide.
|
||||
void DynamicImages::ReadImageInfoForTask() {
|
||||
struct nlist l[8];
|
||||
memset(l, 0, sizeof(l) );
|
||||
|
||||
@@ -293,38 +229,10 @@ void* DynamicImages::GetDyldAllImageInfosPointer()
|
||||
// which lives in "dyld". This structure contains information about all
|
||||
// of the loaded dynamic images.
|
||||
struct nlist &list = l[0];
|
||||
list.n_un.n_name = const_cast<char *>(imageSymbolName);
|
||||
nlist(dyldPath,&list);
|
||||
if(list.n_value) {
|
||||
return reinterpret_cast<void*>(list.n_value);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
#else
|
||||
struct nlist_64 l[8];
|
||||
struct nlist_64 &list = l[0];
|
||||
|
||||
memset(l, 0, sizeof(l) );
|
||||
|
||||
const char *symbolNames[2] = { imageSymbolName, "\0" };
|
||||
|
||||
int invalidEntriesCount = breakpad_nlist_64(dyldPath,&list,symbolNames);
|
||||
|
||||
if(invalidEntriesCount != 0) {
|
||||
return NULL;
|
||||
}
|
||||
assert(list.n_value);
|
||||
return reinterpret_cast<void*>(list.n_value);
|
||||
#endif
|
||||
|
||||
}
|
||||
//==============================================================================
|
||||
// This code was written using dyld_debug.c (from Darwin) as a guide.
|
||||
void DynamicImages::ReadImageInfoForTask() {
|
||||
void *imageList = GetDyldAllImageInfosPointer();
|
||||
|
||||
if (imageList) {
|
||||
kern_return_t kr;
|
||||
list.n_un.n_name = const_cast<char *>("_dyld_all_image_infos");
|
||||
nlist("/usr/lib/dyld", &list);
|
||||
|
||||
if (list.n_value) {
|
||||
// Read the structure inside of dyld that contains information about
|
||||
// loaded images. We're reading from the desired task's address space.
|
||||
|
||||
@@ -333,8 +241,8 @@ void DynamicImages::ReadImageInfoForTask() {
|
||||
// "dyld_debug.c" and is said to be nearly always valid.
|
||||
dyld_all_image_infos *dyldInfo = reinterpret_cast<dyld_all_image_infos*>
|
||||
(ReadTaskMemory(task_,
|
||||
reinterpret_cast<void*>(imageList),
|
||||
sizeof(dyld_all_image_infos), &kr));
|
||||
reinterpret_cast<void*>(list.n_value),
|
||||
sizeof(dyld_all_image_infos)));
|
||||
|
||||
if (dyldInfo) {
|
||||
// number of loaded images
|
||||
@@ -345,7 +253,7 @@ void DynamicImages::ReadImageInfoForTask() {
|
||||
dyld_image_info *infoArray = reinterpret_cast<dyld_image_info*>
|
||||
(ReadTaskMemory(task_,
|
||||
dyldInfo->infoArray,
|
||||
count*sizeof(dyld_image_info), &kr));
|
||||
count*sizeof(dyld_image_info)));
|
||||
|
||||
image_list_.reserve(count);
|
||||
|
||||
@@ -353,24 +261,20 @@ void DynamicImages::ReadImageInfoForTask() {
|
||||
dyld_image_info &info = infoArray[i];
|
||||
|
||||
// First read just the mach_header from the image in the task.
|
||||
breakpad_mach_header *header = reinterpret_cast<breakpad_mach_header*>
|
||||
(ReadTaskMemory(task_,
|
||||
info.load_address_,
|
||||
sizeof(breakpad_mach_header), &kr));
|
||||
mach_header *header = reinterpret_cast<mach_header*>
|
||||
(ReadTaskMemory(task_, info.load_address_, sizeof(mach_header)));
|
||||
|
||||
if (!header)
|
||||
break; // bail on this dynamic image
|
||||
|
||||
|
||||
// Now determine the total amount we really want to read based on the
|
||||
// size of the load commands. We need the header plus all of the
|
||||
// size of the load commands. We need the header plus all of the
|
||||
// load commands.
|
||||
unsigned int header_size =
|
||||
sizeof(breakpad_mach_header) + header->sizeofcmds;
|
||||
|
||||
unsigned int header_size = sizeof(mach_header) + header->sizeofcmds;
|
||||
free(header);
|
||||
|
||||
header = reinterpret_cast<breakpad_mach_header*>
|
||||
(ReadTaskMemory(task_, info.load_address_, header_size, &kr));
|
||||
header = reinterpret_cast<mach_header*>
|
||||
(ReadTaskMemory(task_, info.load_address_, header_size));
|
||||
|
||||
// Read the file name from the task's memory space.
|
||||
char *file_path = NULL;
|
||||
@@ -381,52 +285,43 @@ void DynamicImages::ReadImageInfoForTask() {
|
||||
file_path = reinterpret_cast<char*>
|
||||
(ReadTaskString(task_, info.file_path_));
|
||||
}
|
||||
|
||||
|
||||
// Create an object representing this image and add it to our list.
|
||||
DynamicImage *new_image;
|
||||
new_image = new DynamicImage(header,
|
||||
header_size,
|
||||
(breakpad_mach_header*)info.load_address_,
|
||||
file_path,
|
||||
info.file_mod_date_,
|
||||
task_);
|
||||
DynamicImage *new_image = new DynamicImage(header,
|
||||
header_size,
|
||||
info.load_address_,
|
||||
file_path,
|
||||
info.file_mod_date_,
|
||||
task_);
|
||||
|
||||
if (new_image->IsValid()) {
|
||||
image_list_.push_back(DynamicImageRef(new_image));
|
||||
} else {
|
||||
delete new_image;
|
||||
}
|
||||
|
||||
|
||||
if (file_path) {
|
||||
free(file_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
free(dyldInfo);
|
||||
free(infoArray);
|
||||
|
||||
|
||||
// sorts based on loading address
|
||||
sort(image_list_.begin(), image_list_.end() );
|
||||
// remove duplicates - this happens in certain strange cases
|
||||
// You can see it in DashboardClient when Google Gadgets plugin
|
||||
// is installed. Apple's crash reporter log and gdb "info shared"
|
||||
// both show the same library multiple times at the same address
|
||||
|
||||
vector<DynamicImageRef>::iterator it = unique(image_list_.begin(),
|
||||
image_list_.end() );
|
||||
image_list_.erase(it, image_list_.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
DynamicImage *DynamicImages::GetExecutableImage() {
|
||||
int executable_index = GetExecutableImageIndex();
|
||||
|
||||
|
||||
if (executable_index >= 0) {
|
||||
return GetImage(executable_index);
|
||||
}
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,22 +68,13 @@ typedef struct dyld_all_image_infos {
|
||||
bool processDetachedFromSharedRegion;
|
||||
} dyld_all_image_infos;
|
||||
|
||||
// some typedefs to isolate 64/32 bit differences
|
||||
#ifdef __LP64__
|
||||
typedef mach_header_64 breakpad_mach_header;
|
||||
typedef segment_command_64 breakpad_mach_segment_command;
|
||||
#else
|
||||
typedef mach_header breakpad_mach_header;
|
||||
typedef segment_command breakpad_mach_segment_command;
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
// A simple wrapper for a mach_header
|
||||
//
|
||||
// This could be fleshed out with some more interesting methods.
|
||||
class MachHeader {
|
||||
public:
|
||||
explicit MachHeader(const breakpad_mach_header &header) : header_(header) {}
|
||||
explicit MachHeader(const mach_header &header) : header_(header) {}
|
||||
|
||||
void Print() {
|
||||
printf("magic\t\t: %4x\n", header_.magic);
|
||||
@@ -95,16 +86,16 @@ class MachHeader {
|
||||
printf("flags\t\t: %d\n", header_.flags);
|
||||
}
|
||||
|
||||
breakpad_mach_header header_;
|
||||
mach_header header_;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Represents a single dynamically loaded mach-o image
|
||||
class DynamicImage {
|
||||
public:
|
||||
DynamicImage(breakpad_mach_header *header, // we take ownership
|
||||
int header_size, // includes load commands
|
||||
breakpad_mach_header *load_address,
|
||||
DynamicImage(mach_header *header, // we take ownership
|
||||
int header_size, // includes load commands
|
||||
mach_header *load_address,
|
||||
char *inFilePath,
|
||||
uintptr_t image_mod_date,
|
||||
mach_port_t task)
|
||||
@@ -114,7 +105,7 @@ class DynamicImage {
|
||||
file_mod_date_(image_mod_date),
|
||||
task_(task) {
|
||||
InitializeFilePath(inFilePath);
|
||||
CalculateMemoryAndVersionInfo();
|
||||
CalculateMemoryInfo();
|
||||
}
|
||||
|
||||
~DynamicImage() {
|
||||
@@ -125,7 +116,7 @@ class DynamicImage {
|
||||
}
|
||||
|
||||
// Returns pointer to a local copy of the mach_header plus load commands
|
||||
breakpad_mach_header *GetMachHeader() {return header_;}
|
||||
mach_header *GetMachHeader() {return header_;}
|
||||
|
||||
// Size of mach_header plus load commands
|
||||
int GetHeaderSize() const {return header_size_;}
|
||||
@@ -136,21 +127,20 @@ class DynamicImage {
|
||||
uintptr_t GetModDate() const {return file_mod_date_;}
|
||||
|
||||
// Actual address where the image was loaded
|
||||
breakpad_mach_header *GetLoadAddress() const {return load_address_;}
|
||||
mach_header *GetLoadAddress() const {return load_address_;}
|
||||
|
||||
// Address where the image should be loaded
|
||||
mach_vm_address_t GetVMAddr() const {return vmaddr_;}
|
||||
uint32_t GetVMAddr() const {return vmaddr_;}
|
||||
|
||||
// Difference between GetLoadAddress() and GetVMAddr()
|
||||
ptrdiff_t GetVMAddrSlide() const {return slide_;}
|
||||
|
||||
// Size of the image
|
||||
mach_vm_size_t GetVMSize() const {return vmsize_;}
|
||||
uint32_t GetVMSize() const {return vmsize_;}
|
||||
|
||||
// Task owning this loaded image
|
||||
mach_port_t GetTask() {return task_;}
|
||||
|
||||
uint32_t GetVersion() {return version_;}
|
||||
// For sorting
|
||||
bool operator<(const DynamicImage &inInfo) {
|
||||
return GetLoadAddress() < inInfo.GetLoadAddress();
|
||||
@@ -177,19 +167,39 @@ class DynamicImage {
|
||||
}
|
||||
|
||||
// Initializes vmaddr_, vmsize_, and slide_
|
||||
void CalculateMemoryAndVersionInfo();
|
||||
void CalculateMemoryInfo();
|
||||
|
||||
breakpad_mach_header *header_; // our local copy of the header
|
||||
int header_size_; // mach_header plus load commands
|
||||
breakpad_mach_header *load_address_; // base address image is mapped into
|
||||
mach_vm_address_t vmaddr_;
|
||||
mach_vm_size_t vmsize_;
|
||||
ptrdiff_t slide_;
|
||||
uint32_t version_; // Dylib version
|
||||
char *file_path_; // path dyld used to load the image
|
||||
uintptr_t file_mod_date_; // time_t of image file
|
||||
#if 0 // currently not needed
|
||||
// Copy constructor: we don't want this to be invoked,
|
||||
// but here's the code in case we need to make it public some day.
|
||||
DynamicImage(DynamicImage &inInfo)
|
||||
: load_address_(inInfo.load_address_),
|
||||
vmaddr_(inInfo.vmaddr_),
|
||||
vmsize_(inInfo.vmsize_),
|
||||
slide_(inInfo.slide_),
|
||||
file_mod_date_(inInfo.file_mod_date_),
|
||||
task_(inInfo.task_) {
|
||||
// copy file path string
|
||||
InitializeFilePath(inInfo.GetFilePath());
|
||||
|
||||
mach_port_t task_;
|
||||
// copy mach_header and load commands
|
||||
header_ = reinterpret_cast<mach_header*>(malloc(inInfo.header_size_));
|
||||
memcpy(header_, inInfo.header_, inInfo.header_size_);
|
||||
header_size_ = inInfo.header_size_;
|
||||
}
|
||||
#endif
|
||||
|
||||
mach_header *header_; // our local copy of the header
|
||||
int header_size_; // mach_header plus load commands
|
||||
mach_header *load_address_; // base address image is mapped into
|
||||
uint32_t vmaddr_;
|
||||
uint32_t vmsize_;
|
||||
ptrdiff_t slide_;
|
||||
|
||||
char *file_path_; // path dyld used to load the image
|
||||
uintptr_t file_mod_date_; // time_t of image file
|
||||
|
||||
mach_port_t task_;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
@@ -201,19 +211,13 @@ class DynamicImage {
|
||||
class DynamicImageRef {
|
||||
public:
|
||||
explicit DynamicImageRef(DynamicImage *inP) : p(inP) {}
|
||||
// The copy constructor is required by STL
|
||||
DynamicImageRef(const DynamicImageRef &inRef) : p(inRef.p) {}
|
||||
DynamicImageRef(const DynamicImageRef &inRef) : p(inRef.p) {} // STL required
|
||||
|
||||
bool operator<(const DynamicImageRef &inRef) const {
|
||||
return (*const_cast<DynamicImageRef*>(this)->p)
|
||||
< (*const_cast<DynamicImageRef&>(inRef).p);
|
||||
}
|
||||
|
||||
bool operator==(const DynamicImageRef &inInfo) const {
|
||||
return (*const_cast<DynamicImageRef*>(this)->p).GetLoadAddress() ==
|
||||
(*const_cast<DynamicImageRef&>(inInfo)).GetLoadAddress();
|
||||
}
|
||||
|
||||
// Be just like DynamicImage*
|
||||
DynamicImage *operator->() {return p;}
|
||||
operator DynamicImage*() {return p;}
|
||||
@@ -262,14 +266,10 @@ class DynamicImages {
|
||||
}
|
||||
|
||||
void TestPrint() {
|
||||
const breakpad_mach_header *header;
|
||||
for (int i = 0; i < (int)image_list_.size(); ++i) {
|
||||
printf("dyld: %p: name = %s\n", _dyld_get_image_header(i),
|
||||
_dyld_get_image_name(i) );
|
||||
|
||||
const void *imageHeader = _dyld_get_image_header(i);
|
||||
header = reinterpret_cast<const breakpad_mach_header*>(imageHeader);
|
||||
|
||||
_dyld_get_image_name(i) );
|
||||
const mach_header *header = _dyld_get_image_header(i);
|
||||
MachHeader(*header).Print();
|
||||
}
|
||||
}
|
||||
@@ -279,7 +279,6 @@ class DynamicImages {
|
||||
|
||||
// Initialization
|
||||
void ReadImageInfoForTask();
|
||||
void* GetDyldAllImageInfosPointer();
|
||||
|
||||
mach_port_t task_;
|
||||
vector<DynamicImageRef> image_list_;
|
||||
@@ -287,10 +286,7 @@ class DynamicImages {
|
||||
|
||||
// Returns a malloced block containing the contents of memory at a particular
|
||||
// location in another task.
|
||||
void* ReadTaskMemory(task_port_t target_task,
|
||||
const void* address,
|
||||
size_t len,
|
||||
kern_return_t *kr);
|
||||
void* ReadTaskMemory(task_port_t target_task, const void* address, size_t len);
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ struct ExceptionReplyMessage {
|
||||
|
||||
// Only catch these three exceptions. The other ones are nebulously defined
|
||||
// and may result in treating a non-fatal exception as fatal.
|
||||
exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS |
|
||||
exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS |
|
||||
EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT;
|
||||
|
||||
extern "C"
|
||||
@@ -137,83 +137,8 @@ extern "C"
|
||||
mach_msg_type_number_t thread_state_count,
|
||||
thread_state_t thread_state,
|
||||
mach_msg_type_number_t *thread_state_count);
|
||||
|
||||
kern_return_t breakpad_exception_raise_state(mach_port_t exception_port,
|
||||
exception_type_t exception,
|
||||
const exception_data_t code,
|
||||
mach_msg_type_number_t codeCnt,
|
||||
int *flavor,
|
||||
const thread_state_t old_state,
|
||||
mach_msg_type_number_t old_stateCnt,
|
||||
thread_state_t new_state,
|
||||
mach_msg_type_number_t *new_stateCnt
|
||||
);
|
||||
|
||||
kern_return_t breakpad_exception_raise_state_identity(mach_port_t exception_port,
|
||||
mach_port_t thread,
|
||||
mach_port_t task,
|
||||
exception_type_t exception,
|
||||
exception_data_t code,
|
||||
mach_msg_type_number_t codeCnt,
|
||||
int *flavor,
|
||||
thread_state_t old_state,
|
||||
mach_msg_type_number_t old_stateCnt,
|
||||
thread_state_t new_state,
|
||||
mach_msg_type_number_t *new_stateCnt
|
||||
);
|
||||
|
||||
kern_return_t breakpad_exception_raise(mach_port_t port, mach_port_t failed_thread,
|
||||
mach_port_t task,
|
||||
exception_type_t exception,
|
||||
exception_data_t code,
|
||||
mach_msg_type_number_t code_count);
|
||||
}
|
||||
|
||||
|
||||
|
||||
kern_return_t breakpad_exception_raise_state(mach_port_t exception_port,
|
||||
exception_type_t exception,
|
||||
const exception_data_t code,
|
||||
mach_msg_type_number_t codeCnt,
|
||||
int *flavor,
|
||||
const thread_state_t old_state,
|
||||
mach_msg_type_number_t old_stateCnt,
|
||||
thread_state_t new_state,
|
||||
mach_msg_type_number_t *new_stateCnt
|
||||
)
|
||||
{
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
kern_return_t breakpad_exception_raise_state_identity(mach_port_t exception_port,
|
||||
mach_port_t thread,
|
||||
mach_port_t task,
|
||||
exception_type_t exception,
|
||||
exception_data_t code,
|
||||
mach_msg_type_number_t codeCnt,
|
||||
int *flavor,
|
||||
thread_state_t old_state,
|
||||
mach_msg_type_number_t old_stateCnt,
|
||||
thread_state_t new_state,
|
||||
mach_msg_type_number_t *new_stateCnt
|
||||
)
|
||||
{
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
kern_return_t breakpad_exception_raise(mach_port_t port, mach_port_t failed_thread,
|
||||
mach_port_t task,
|
||||
exception_type_t exception,
|
||||
exception_data_t code,
|
||||
mach_msg_type_number_t code_count) {
|
||||
|
||||
if (task != mach_task_self()) {
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
return ForwardException(task, failed_thread, exception, code, code_count);
|
||||
}
|
||||
|
||||
|
||||
ExceptionHandler::ExceptionHandler(const string &dump_path,
|
||||
FilterCallback filter,
|
||||
MinidumpCallback callback,
|
||||
@@ -225,7 +150,7 @@ ExceptionHandler::ExceptionHandler(const string &dump_path,
|
||||
callback_context_(callback_context),
|
||||
directCallback_(NULL),
|
||||
handler_thread_(NULL),
|
||||
handler_port_(MACH_PORT_NULL),
|
||||
handler_port_(0),
|
||||
previous_(NULL),
|
||||
installed_exception_handler_(false),
|
||||
is_in_teardown_(false),
|
||||
@@ -248,7 +173,7 @@ ExceptionHandler::ExceptionHandler(DirectCallback callback,
|
||||
callback_context_(callback_context),
|
||||
directCallback_(callback),
|
||||
handler_thread_(NULL),
|
||||
handler_port_(MACH_PORT_NULL),
|
||||
handler_port_(0),
|
||||
previous_(NULL),
|
||||
installed_exception_handler_(false),
|
||||
is_in_teardown_(false),
|
||||
@@ -280,7 +205,7 @@ bool ExceptionHandler::WriteMinidump() {
|
||||
// the mutex when completed
|
||||
pthread_mutex_lock(&minidump_write_mutex_);
|
||||
}
|
||||
|
||||
|
||||
use_minidump_write_mutex_ = false;
|
||||
UpdateNextID();
|
||||
return last_minidump_write_result_;
|
||||
@@ -296,7 +221,6 @@ bool ExceptionHandler::WriteMinidump(const string &dump_path,
|
||||
|
||||
bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t thread_name) {
|
||||
bool result = false;
|
||||
|
||||
@@ -304,7 +228,6 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
|
||||
if (directCallback_(callback_context_,
|
||||
exception_type,
|
||||
exception_code,
|
||||
exception_subcode,
|
||||
thread_name) ) {
|
||||
if (exception_type && exception_code)
|
||||
_exit(exception_type);
|
||||
@@ -322,8 +245,7 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
|
||||
if (filter_ && !filter_(callback_context_))
|
||||
return false;
|
||||
|
||||
md.SetExceptionInformation(exception_type, exception_code,
|
||||
exception_subcode, thread_name);
|
||||
md.SetExceptionInformation(exception_type, exception_code, thread_name);
|
||||
}
|
||||
|
||||
result = md.Write(next_minidump_path_c_);
|
||||
@@ -334,14 +256,14 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
|
||||
// If the user callback returned true and we're handling an exception
|
||||
// (rather than just writing out the file), then we should exit without
|
||||
// forwarding the exception to the next handler.
|
||||
if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
|
||||
if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
|
||||
result)) {
|
||||
if (exception_type && exception_code)
|
||||
_exit(exception_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -350,20 +272,20 @@ kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread,
|
||||
exception_data_t code,
|
||||
mach_msg_type_number_t code_count) {
|
||||
// At this time, we should have called Uninstall() on the exception handler
|
||||
// so that the current exception ports are the ones that we should be
|
||||
// so that the current exception ports are the ones that we should be
|
||||
// forwarding to.
|
||||
ExceptionParameters current;
|
||||
|
||||
|
||||
current.count = EXC_TYPES_COUNT;
|
||||
mach_port_t current_task = mach_task_self();
|
||||
kern_return_t result = task_get_exception_ports(current_task,
|
||||
kern_return_t result = task_get_exception_ports(current_task,
|
||||
s_exception_mask,
|
||||
current.masks,
|
||||
¤t.count,
|
||||
current.ports,
|
||||
current.behaviors,
|
||||
current.flavors);
|
||||
|
||||
|
||||
// Find the first exception handler that matches the exception
|
||||
unsigned int found;
|
||||
for (found = 0; found < current.count; ++found) {
|
||||
@@ -453,12 +375,12 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
|
||||
sizeof(receive), self->handler_port_,
|
||||
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
||||
|
||||
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
// Uninstall our handler so that we don't get in a loop if the process of
|
||||
// writing out a minidump causes an exception. However, if the exception
|
||||
// was caused by a fork'd process, don't uninstall things
|
||||
|
||||
if (receive.task.name == mach_task_self())
|
||||
// If the actual exception code is zero, then we're calling this handler
|
||||
// in a way that indicates that we want to either exit this thread or
|
||||
// generate a minidump
|
||||
@@ -479,7 +401,7 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
|
||||
|
||||
// Write out the dump and save the result for later retrieval
|
||||
self->last_minidump_write_result_ =
|
||||
self->WriteMinidumpWithException(0, 0, 0, 0);
|
||||
self->WriteMinidumpWithException(0, 0, 0);
|
||||
|
||||
self->UninstallHandler(false);
|
||||
|
||||
@@ -493,48 +415,45 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
|
||||
if (self->use_minidump_write_mutex_)
|
||||
pthread_mutex_unlock(&self->minidump_write_mutex_);
|
||||
} else {
|
||||
|
||||
// When forking a child process with the exception handler installed,
|
||||
// if the child crashes, it will send the exception back to the parent
|
||||
// process. The check for task == self_task() ensures that only
|
||||
// exceptions that occur in the parent process are caught and
|
||||
// processed. If the exception was not caused by this task, we
|
||||
// still need to call into the exception server and have it return
|
||||
// KERN_FAILURE (see breakpad_exception_raise) in order for the kernel
|
||||
// to move onto the host exception handler for the child task
|
||||
// process. The check for task == self_task() ensures that only
|
||||
// exceptions that occur in the parent process are caught and
|
||||
// processed.
|
||||
if (receive.task.name == mach_task_self()) {
|
||||
self->SuspendThreads();
|
||||
|
||||
|
||||
#if USE_PROTECTED_ALLOCATIONS
|
||||
if(gBreakpadAllocator)
|
||||
gBreakpadAllocator->Unprotect();
|
||||
#endif
|
||||
|
||||
int subcode = 0;
|
||||
if (receive.exception == EXC_BAD_ACCESS && receive.code_count > 1)
|
||||
subcode = receive.code[1];
|
||||
|
||||
// Generate the minidump with the exception data.
|
||||
self->WriteMinidumpWithException(receive.exception, receive.code[0],
|
||||
subcode, receive.thread.name);
|
||||
|
||||
self->UninstallHandler(true);
|
||||
// Generate the minidump with the exception data.
|
||||
self->WriteMinidumpWithException(receive.exception, receive.code[0],
|
||||
receive.thread.name);
|
||||
|
||||
self->UninstallHandler(true);
|
||||
|
||||
#if USE_PROTECTED_ALLOCATIONS
|
||||
if(gBreakpadAllocator)
|
||||
gBreakpadAllocator->Protect();
|
||||
#endif
|
||||
}
|
||||
// Pass along the exception to the server, which will setup the
|
||||
// message and call breakpad_exception_raise() and put the return
|
||||
// code into the reply.
|
||||
ExceptionReplyMessage reply;
|
||||
if (!exc_server(&receive.header, &reply.header))
|
||||
exit(1);
|
||||
|
||||
// Send a reply and exit
|
||||
result = mach_msg(&(reply.header), MACH_SEND_MSG,
|
||||
reply.header.msgh_size, 0, MACH_PORT_NULL,
|
||||
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
||||
// Pass along the exception to the server, which will setup the
|
||||
// message and call catch_exception_raise() and put the KERN_SUCCESS
|
||||
// into the reply.
|
||||
ExceptionReplyMessage reply;
|
||||
if (!exc_server(&receive.header, &reply.header))
|
||||
exit(1);
|
||||
|
||||
// Send a reply and exit
|
||||
result = mach_msg(&(reply.header), MACH_SEND_MSG,
|
||||
reply.header.msgh_size, 0, MACH_PORT_NULL,
|
||||
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
||||
} else {
|
||||
// An exception occurred in a child process
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -546,11 +465,11 @@ bool ExceptionHandler::InstallHandler() {
|
||||
try {
|
||||
#if USE_PROTECTED_ALLOCATIONS
|
||||
previous_ = new (gBreakpadAllocator->Allocate(sizeof(ExceptionParameters)) )
|
||||
ExceptionParameters();
|
||||
ExceptionParameters();
|
||||
#else
|
||||
previous_ = new ExceptionParameters();
|
||||
#endif
|
||||
|
||||
|
||||
}
|
||||
catch (std::bad_alloc) {
|
||||
return false;
|
||||
@@ -559,14 +478,14 @@ bool ExceptionHandler::InstallHandler() {
|
||||
// Save the current exception ports so that we can forward to them
|
||||
previous_->count = EXC_TYPES_COUNT;
|
||||
mach_port_t current_task = mach_task_self();
|
||||
kern_return_t result = task_get_exception_ports(current_task,
|
||||
kern_return_t result = task_get_exception_ports(current_task,
|
||||
s_exception_mask,
|
||||
previous_->masks,
|
||||
&previous_->count,
|
||||
previous_->ports,
|
||||
previous_->behaviors,
|
||||
previous_->flavors);
|
||||
|
||||
|
||||
// Setup the exception ports on this task
|
||||
if (result == KERN_SUCCESS)
|
||||
result = task_set_exception_ports(current_task, s_exception_mask,
|
||||
@@ -580,10 +499,10 @@ bool ExceptionHandler::InstallHandler() {
|
||||
|
||||
bool ExceptionHandler::UninstallHandler(bool in_exception) {
|
||||
kern_return_t result = KERN_SUCCESS;
|
||||
|
||||
|
||||
if (installed_exception_handler_) {
|
||||
mach_port_t current_task = mach_task_self();
|
||||
|
||||
|
||||
// Restore the previous ports
|
||||
for (unsigned int i = 0; i < previous_->count; ++i) {
|
||||
result = task_set_exception_ports(current_task, previous_->masks[i],
|
||||
@@ -593,20 +512,20 @@ bool ExceptionHandler::UninstallHandler(bool in_exception) {
|
||||
if (result != KERN_SUCCESS)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// this delete should NOT happen if an exception just occurred!
|
||||
if (!in_exception) {
|
||||
#if USE_PROTECTED_ALLOCATIONS
|
||||
previous_->~ExceptionParameters();
|
||||
#else
|
||||
delete previous_;
|
||||
delete previous_;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
previous_ = NULL;
|
||||
installed_exception_handler_ = false;
|
||||
}
|
||||
|
||||
|
||||
return result == KERN_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -633,7 +552,7 @@ bool ExceptionHandler::Setup(bool install_handler) {
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
int thread_create_result = pthread_create(&handler_thread_, &attr,
|
||||
int thread_create_result = pthread_create(&handler_thread_, &attr,
|
||||
&WaitForMessage, this);
|
||||
pthread_attr_destroy(&attr);
|
||||
result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS;
|
||||
@@ -648,7 +567,7 @@ bool ExceptionHandler::Teardown() {
|
||||
|
||||
if (!UninstallHandler(false))
|
||||
return false;
|
||||
|
||||
|
||||
// Send an empty message so that the handler_thread exits
|
||||
if (SendEmptyMachMessage()) {
|
||||
mach_port_t current_task = mach_task_self();
|
||||
@@ -658,7 +577,7 @@ bool ExceptionHandler::Teardown() {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
handler_thread_ = NULL;
|
||||
handler_port_ = NULL;
|
||||
pthread_mutex_destroy(&minidump_write_mutex_);
|
||||
@@ -703,7 +622,7 @@ bool ExceptionHandler::SuspendThreads() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -721,7 +640,7 @@ bool ExceptionHandler::ResumeThreads() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,14 +58,14 @@ class ExceptionHandler {
|
||||
// will immediately report the exception as unhandled without writing a
|
||||
// minidump, allowing another handler the opportunity to handle it.
|
||||
typedef bool (*FilterCallback)(void *context);
|
||||
|
||||
|
||||
// A callback function to run after the minidump has been written.
|
||||
// |minidump_id| is a unique id for the dump, so the minidump
|
||||
// file is <dump_dir>/<minidump_id>.dmp.
|
||||
// |context| is the value passed into the constructor.
|
||||
// |context| is the value passed into the constructor.
|
||||
// |succeeded| indicates whether a minidump file was successfully written.
|
||||
// Return true if the exception was fully handled and breakpad should exit.
|
||||
// Return false to allow any other exception handlers to process the
|
||||
// Return false to allow any other exception handlers to process the
|
||||
// exception.
|
||||
typedef bool (*MinidumpCallback)(const char *dump_dir,
|
||||
const char *minidump_id,
|
||||
@@ -77,7 +77,6 @@ class ExceptionHandler {
|
||||
typedef bool (*DirectCallback)( void *context,
|
||||
int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t thread_name);
|
||||
|
||||
// Creates a new ExceptionHandler instance to handle writing minidumps.
|
||||
@@ -86,7 +85,7 @@ class ExceptionHandler {
|
||||
// If install_handler is true, then a minidump will be written whenever
|
||||
// an unhandled exception occurs. If it is false, minidumps will only
|
||||
// be written when WriteMinidump is called.
|
||||
ExceptionHandler(const string &dump_path,
|
||||
ExceptionHandler(const string &dump_path,
|
||||
FilterCallback filter, MinidumpCallback callback,
|
||||
void *callback_context, bool install_handler);
|
||||
|
||||
@@ -105,7 +104,7 @@ class ExceptionHandler {
|
||||
dump_path_c_ = dump_path_.c_str();
|
||||
UpdateNextID(); // Necessary to put dump_path_ in next_minidump_path_.
|
||||
}
|
||||
|
||||
|
||||
// Writes a minidump immediately. This can be used to capture the
|
||||
// execution state independently of a crash. Returns true on success.
|
||||
bool WriteMinidump();
|
||||
@@ -121,11 +120,11 @@ class ExceptionHandler {
|
||||
|
||||
// Uninstall the mach exception handler (if any)
|
||||
bool UninstallHandler(bool in_exception);
|
||||
|
||||
|
||||
// Setup the handler thread, and if |install_handler| is true, install the
|
||||
// mach exception port handler
|
||||
bool Setup(bool install_handler);
|
||||
|
||||
|
||||
// Uninstall the mach exception handler (if any) and terminate the helper
|
||||
// thread
|
||||
bool Teardown();
|
||||
@@ -136,7 +135,7 @@ class ExceptionHandler {
|
||||
|
||||
// All minidump writing goes through this one routine
|
||||
bool WriteMinidumpWithException(int exception_type, int exception_code,
|
||||
int exception_subcode, mach_port_t thread_name);
|
||||
mach_port_t thread_name);
|
||||
|
||||
// When installed, this static function will be call from a newly created
|
||||
// pthread with |this| as the argument
|
||||
@@ -150,20 +149,20 @@ class ExceptionHandler {
|
||||
// path of the next minidump to be written in next_minidump_path_.
|
||||
void UpdateNextID();
|
||||
|
||||
// These functions will suspend/resume all threads except for the
|
||||
// These functions will suspend/resume all threads except for the
|
||||
// reporting thread
|
||||
bool SuspendThreads();
|
||||
bool ResumeThreads();
|
||||
|
||||
|
||||
// The destination directory for the minidump
|
||||
string dump_path_;
|
||||
|
||||
|
||||
// The basename of the next minidump w/o extension
|
||||
string next_minidump_id_;
|
||||
|
||||
|
||||
// The full path to the next minidump to be written, including extension
|
||||
string next_minidump_path_;
|
||||
|
||||
|
||||
// Pointers to the UTF-8 versions of above
|
||||
const char *dump_path_c_;
|
||||
const char *next_minidump_id_c_;
|
||||
@@ -192,18 +191,18 @@ class ExceptionHandler {
|
||||
|
||||
// True, if we've installed the exception handler
|
||||
bool installed_exception_handler_;
|
||||
|
||||
|
||||
// True, if we're in the process of uninstalling the exception handler and
|
||||
// the thread.
|
||||
bool is_in_teardown_;
|
||||
|
||||
|
||||
// Save the last result of the last minidump
|
||||
bool last_minidump_write_result_;
|
||||
|
||||
// A mutex for use when writing out a minidump that was requested on a
|
||||
|
||||
// A mutex for use when writing out a minidump that was requested on a
|
||||
// thread other than the exception handler.
|
||||
pthread_mutex_t minidump_write_mutex_;
|
||||
|
||||
|
||||
// True, if we're using the mutext to indicate when mindump writing occurs
|
||||
bool use_minidump_write_mutex_;
|
||||
};
|
||||
|
||||
@@ -28,7 +28,15 @@
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
/*
|
||||
g++ -framework CoreFoundation -I../../.. ../../minidump_file_writer.cc ../../../common/convert_UTF.c ../../../common/string_conversion.cc ../../../common/mac/string_utilities.cc exception_handler.cc minidump_generator.cc exception_handler_test.cc -o exception_handler_test -mmacosx-version-min=10.4 ../../../common/mac/file_id.cc dynamic_images.cc ../../../common/mac/macho_id.cc ../../../common/mac/macho_walker.cc -lcrypto ../../../common/mac/macho_utilities.cc
|
||||
g++ -framework CoreFoundation -I../../.. \
|
||||
../../minidump_file_writer.cc \
|
||||
../../../common/convert_UTF.c \
|
||||
../../../common/string_conversion.cc \
|
||||
../../../common/mac/string_utilities.cc \
|
||||
exception_handler.cc \
|
||||
minidump_generator.cc \
|
||||
exception_handler_test.cc \
|
||||
-o exception_handler_test
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
@@ -47,11 +55,10 @@ static void *SleepyFunction(void *) {
|
||||
while (1) {
|
||||
sleep(10000);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void Crasher() {
|
||||
int *a = (int*)0x42;
|
||||
int *a = NULL;
|
||||
|
||||
fprintf(stdout, "Going to crash...\n");
|
||||
fprintf(stdout, "A = %d", *a);
|
||||
@@ -70,14 +77,15 @@ bool MDCallback(const char *dump_dir, const char *file_name,
|
||||
|
||||
fprintf(stdout, "Minidump: %s\n", path.c_str());
|
||||
// Indicate that we've handled the callback
|
||||
exit(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char * const argv[]) {
|
||||
char buffer[PATH_MAX];
|
||||
struct passwd *user = getpwuid(getuid());
|
||||
|
||||
// Home dir
|
||||
snprintf(buffer, sizeof(buffer), "/tmp/");
|
||||
snprintf(buffer, sizeof(buffer), "/Users/%s/Desktop/", user->pw_name);
|
||||
|
||||
string path(buffer);
|
||||
ExceptionHandler eh(path, NULL, MDCallback, NULL, true);
|
||||
@@ -89,8 +97,8 @@ int main(int argc, char * const argv[]) {
|
||||
perror("pthread_create");
|
||||
}
|
||||
|
||||
// // Dump a test
|
||||
// eh.WriteMinidump();
|
||||
// Dump a test
|
||||
eh.WriteMinidump();
|
||||
|
||||
// Test the handler
|
||||
SoonToCrash();
|
||||
|
||||
@@ -30,13 +30,11 @@
|
||||
#include <cstdio>
|
||||
|
||||
#include <mach/host_info.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <mach/vm_statistics.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <mach/mach_vm.h>
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
@@ -50,11 +48,10 @@ using MacStringUtils::IntegerValueAtIndex;
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// constructor when generating from within the crashed process
|
||||
// constructor when generating from within the crashed process
|
||||
MinidumpGenerator::MinidumpGenerator()
|
||||
: exception_type_(0),
|
||||
exception_code_(0),
|
||||
exception_subcode_(0),
|
||||
exception_thread_(0),
|
||||
crashing_task_(mach_task_self()),
|
||||
handler_thread_(mach_thread_self()),
|
||||
@@ -62,13 +59,10 @@ MinidumpGenerator::MinidumpGenerator()
|
||||
GatherSystemInformation();
|
||||
}
|
||||
|
||||
// constructor when generating from a different process than the
|
||||
// crashed process
|
||||
MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task,
|
||||
mach_port_t handler_thread)
|
||||
// constructor when generating from a different process than the crashed process
|
||||
MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task, mach_port_t handler_thread)
|
||||
: exception_type_(0),
|
||||
exception_code_(0),
|
||||
exception_subcode_(0),
|
||||
exception_thread_(0),
|
||||
crashing_task_(crashing_task),
|
||||
handler_thread_(handler_thread) {
|
||||
@@ -77,7 +71,7 @@ MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task,
|
||||
} else {
|
||||
dynamic_images_ = NULL;
|
||||
}
|
||||
|
||||
|
||||
GatherSystemInformation();
|
||||
}
|
||||
|
||||
@@ -95,29 +89,26 @@ void MinidumpGenerator::GatherSystemInformation() {
|
||||
// If this is non-zero, then we've already gathered the information
|
||||
if (os_major_version_)
|
||||
return;
|
||||
|
||||
|
||||
// This code extracts the version and build information from the OS
|
||||
CFStringRef vers_path =
|
||||
CFSTR("/System/Library/CoreServices/SystemVersion.plist");
|
||||
CFURLRef sys_vers =
|
||||
CFURLCreateWithFileSystemPath(NULL,
|
||||
vers_path,
|
||||
kCFURLPOSIXPathStyle,
|
||||
false);
|
||||
CFURLCreateWithFileSystemPath(NULL, vers_path, kCFURLPOSIXPathStyle, false);
|
||||
CFDataRef data;
|
||||
SInt32 error;
|
||||
CFURLCreateDataAndPropertiesFromResource(NULL, sys_vers, &data, NULL, NULL,
|
||||
&error);
|
||||
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
|
||||
CFDictionaryRef list = static_cast<CFDictionaryRef>
|
||||
(CFPropertyListCreateFromXMLData(NULL, data, kCFPropertyListImmutable,
|
||||
NULL));
|
||||
if (!list)
|
||||
return;
|
||||
|
||||
|
||||
CFStringRef build_version = static_cast<CFStringRef>
|
||||
(CFDictionaryGetValue(list, CFSTR("ProductBuildVersion")));
|
||||
CFStringRef product_version = static_cast<CFStringRef>
|
||||
@@ -174,7 +165,7 @@ bool MinidumpGenerator::Write(const char *path) {
|
||||
// exception.
|
||||
&MinidumpGenerator::WriteExceptionStream,
|
||||
};
|
||||
bool result = false;
|
||||
bool result = true;
|
||||
|
||||
// If opening was successful, create the header, directory, and call each
|
||||
// writer. The destructor for the TypedMDRVAs will cause the data to be
|
||||
@@ -205,7 +196,6 @@ bool MinidumpGenerator::Write(const char *path) {
|
||||
header_ptr->stream_directory_rva = dir.position();
|
||||
|
||||
MDRawDirectory local_dir;
|
||||
result = true;
|
||||
for (int i = 0; (result) && (i < writer_count); ++i) {
|
||||
result = (this->*writers[i])(&local_dir);
|
||||
|
||||
@@ -213,161 +203,102 @@ bool MinidumpGenerator::Write(const char *path) {
|
||||
dir.CopyIndex(i, &local_dir);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t MinidumpGenerator::CalculateStackSize(mach_vm_address_t start_addr) {
|
||||
mach_vm_address_t stack_region_base = start_addr;
|
||||
mach_vm_size_t stack_region_size;
|
||||
size_t MinidumpGenerator::CalculateStackSize(vm_address_t start_addr) {
|
||||
vm_address_t stack_region_base = start_addr;
|
||||
vm_size_t stack_region_size;
|
||||
natural_t nesting_level = 0;
|
||||
vm_region_submap_info_64 submap_info;
|
||||
mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
|
||||
vm_region_submap_info submap_info;
|
||||
mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT;
|
||||
kern_return_t result =
|
||||
vm_region_recurse(crashing_task_, &stack_region_base, &stack_region_size,
|
||||
&nesting_level,
|
||||
reinterpret_cast<vm_region_recurse_info_t>(&submap_info),
|
||||
&info_count);
|
||||
|
||||
vm_region_recurse_info_t region_info;
|
||||
region_info = reinterpret_cast<vm_region_recurse_info_t>(&submap_info);
|
||||
if ((stack_region_base + stack_region_size) == 0xbffff000) {
|
||||
// The stack for thread 0 needs to extend all the way to 0xc0000000
|
||||
// For many processes the stack is first created in one page
|
||||
// from 0xbffff000 - 0xc0000000 and is then later extended to
|
||||
// a much larger size by creating a new VM region immediately below
|
||||
// the initial page
|
||||
|
||||
if (start_addr == 0) {
|
||||
return 0;
|
||||
// include the original stack frame page (0xbffff000 - 0xc0000000)
|
||||
stack_region_size += 0x1000;
|
||||
}
|
||||
|
||||
kern_return_t result =
|
||||
mach_vm_region_recurse(crashing_task_, &stack_region_base,
|
||||
&stack_region_size, &nesting_level,
|
||||
region_info,
|
||||
&info_count);
|
||||
|
||||
if (start_addr < stack_region_base) {
|
||||
// probably stack corruption, since mach_vm_region had to go
|
||||
// higher in the process address space to find a valid region.
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if ((stack_region_base + stack_region_size) == TOP_OF_THREAD0_STACK) {
|
||||
// The stack for thread 0 needs to extend all the way to
|
||||
// 0xc0000000 on 32 bit and 00007fff5fc00000 on 64bit. HOWEVER,
|
||||
// for many processes, the stack is first created in one page
|
||||
// below this, and is then later extended to a much larger size by
|
||||
// creating a new VM region immediately below the initial page.
|
||||
|
||||
// You can see this for yourself by running vmmap on a "hello,
|
||||
// world" program
|
||||
|
||||
// Because of the above, we'll add 4k to include the original
|
||||
// stack frame page.
|
||||
// This method of finding the stack region needs to be done in
|
||||
// a better way; the breakpad issue 247 is tracking this.
|
||||
stack_region_size += 0x1000;
|
||||
}
|
||||
|
||||
return result == KERN_SUCCESS ?
|
||||
return result == KERN_SUCCESS ?
|
||||
stack_region_base + stack_region_size - start_addr : 0;
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteStackFromStartAddress(
|
||||
mach_vm_address_t start_addr,
|
||||
vm_address_t start_addr,
|
||||
MDMemoryDescriptor *stack_location) {
|
||||
UntypedMDRVA memory(&writer_);
|
||||
|
||||
bool result = false;
|
||||
size_t size = CalculateStackSize(start_addr);
|
||||
|
||||
if (size == 0) {
|
||||
// In some situations the stack address for the thread can come back 0.
|
||||
// In these cases we skip over the threads in question and stuff the
|
||||
// stack with a clearly borked value.
|
||||
start_addr = 0xDEADBEEF;
|
||||
size = 16;
|
||||
if (!memory.Allocate(size))
|
||||
return false;
|
||||
// If there's an error in the calculation, return at least the current
|
||||
// stack information
|
||||
if (size == 0)
|
||||
size = 16;
|
||||
|
||||
unsigned long long dummy_stack[2]; // Fill dummy stack with 16 bytes of
|
||||
// junk.
|
||||
dummy_stack[0] = 0xDEADBEEF;
|
||||
dummy_stack[1] = 0xDEADBEEF;
|
||||
if (!memory.Allocate(size))
|
||||
return false;
|
||||
|
||||
result = memory.Copy(dummy_stack, size);
|
||||
bool result;
|
||||
if (dynamic_images_) {
|
||||
void *stack_memory = ReadTaskMemory(crashing_task_, (void*)start_addr, size);
|
||||
result = memory.Copy(stack_memory, size);
|
||||
free(stack_memory);
|
||||
} else {
|
||||
|
||||
if (!memory.Allocate(size))
|
||||
return false;
|
||||
|
||||
if (dynamic_images_) {
|
||||
|
||||
kern_return_t kr;
|
||||
|
||||
void *stack_memory = ReadTaskMemory(crashing_task_,
|
||||
(void*)start_addr,
|
||||
size,
|
||||
&kr);
|
||||
|
||||
if (stack_memory == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result = memory.Copy(stack_memory, size);
|
||||
free(stack_memory);
|
||||
} else {
|
||||
result = memory.Copy(reinterpret_cast<const void *>(start_addr), size);
|
||||
}
|
||||
result = memory.Copy(reinterpret_cast<const void *>(start_addr), size);
|
||||
}
|
||||
|
||||
|
||||
stack_location->start_of_memory_range = start_addr;
|
||||
stack_location->memory = memory.location();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if TARGET_CPU_PPC || TARGET_CPU_PPC64
|
||||
#if TARGET_CPU_PPC
|
||||
bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location) {
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
#if TARGET_CPU_PPC
|
||||
mach_vm_address_t start_addr = machine_state->r1;
|
||||
#else
|
||||
mach_vm_address_t start_addr = machine_state->__r1;
|
||||
#endif
|
||||
ppc_thread_state_t *machine_state =
|
||||
reinterpret_cast<ppc_thread_state_t *>(state);
|
||||
vm_address_t start_addr = machine_state->r1;
|
||||
return WriteStackFromStartAddress(start_addr, stack_location);
|
||||
}
|
||||
|
||||
u_int64_t
|
||||
MinidumpGenerator::CurrentPCForStack(breakpad_thread_state_data_t state) {
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
u_int64_t MinidumpGenerator::CurrentPCForStack(breakpad_thread_state_data_t state) {
|
||||
ppc_thread_state_t *machine_state =
|
||||
reinterpret_cast<ppc_thread_state_t *>(state);
|
||||
|
||||
#if TARGET_CPU_PPC
|
||||
return machine_state->srr0;
|
||||
#else
|
||||
return machine_state->__srr0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location) {
|
||||
TypedMDRVA<MinidumpContext> context(&writer_);
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
TypedMDRVA<MDRawContextPPC> context(&writer_);
|
||||
ppc_thread_state_t *machine_state =
|
||||
reinterpret_cast<ppc_thread_state_t *>(state);
|
||||
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
|
||||
*register_location = context.location();
|
||||
MinidumpContext *context_ptr = context.get();
|
||||
MDRawContextPPC *context_ptr = context.get();
|
||||
context_ptr->context_flags = MD_CONTEXT_PPC_BASE;
|
||||
|
||||
#if TARGET_CPU_PPC64
|
||||
#define AddReg(a) context_ptr->a = machine_state->__ ## a
|
||||
#define AddGPR(a) context_ptr->gpr[a] = machine_state->__r ## a
|
||||
#else
|
||||
#define AddReg(a) context_ptr->a = machine_state->a
|
||||
#define AddGPR(a) context_ptr->gpr[a] = machine_state->r ## a
|
||||
#endif
|
||||
|
||||
AddReg(srr0);
|
||||
AddReg(cr);
|
||||
AddReg(xer);
|
||||
AddReg(ctr);
|
||||
AddReg(mq);
|
||||
AddReg(lr);
|
||||
AddReg(vrsave);
|
||||
|
||||
@@ -403,67 +334,38 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
||||
AddGPR(29);
|
||||
AddGPR(30);
|
||||
AddGPR(31);
|
||||
|
||||
#if TARGET_CPU_PPC
|
||||
/* The mq register is only for PPC */
|
||||
AddReg(mq);
|
||||
#endif
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#elif TARGET_CPU_X86 || TARGET_CPU_X86_64
|
||||
|
||||
#elif TARGET_CPU_X86
|
||||
bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location) {
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
|
||||
#if TARGET_CPU_X86_64
|
||||
mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, rsp);
|
||||
#else
|
||||
mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, esp);
|
||||
#endif
|
||||
i386_thread_state_t *machine_state =
|
||||
reinterpret_cast<i386_thread_state_t *>(state);
|
||||
vm_address_t start_addr = machine_state->esp;
|
||||
return WriteStackFromStartAddress(start_addr, stack_location);
|
||||
}
|
||||
|
||||
u_int64_t
|
||||
MinidumpGenerator::CurrentPCForStack(breakpad_thread_state_data_t state) {
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
u_int64_t MinidumpGenerator::CurrentPCForStack(breakpad_thread_state_data_t state) {
|
||||
i386_thread_state_t *machine_state =
|
||||
reinterpret_cast<i386_thread_state_t *>(state);
|
||||
|
||||
#if TARGET_CPU_X86_64
|
||||
return REGISTER_FROM_THREADSTATE(machine_state, rip);
|
||||
#else
|
||||
return REGISTER_FROM_THREADSTATE(machine_state, eip);
|
||||
#endif
|
||||
return machine_state->eip;
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location) {
|
||||
TypedMDRVA<MinidumpContext> context(&writer_);
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
TypedMDRVA<MDRawContextX86> context(&writer_);
|
||||
i386_thread_state_t *machine_state =
|
||||
reinterpret_cast<i386_thread_state_t *>(state);
|
||||
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
|
||||
*register_location = context.location();
|
||||
MinidumpContext *context_ptr = context.get();
|
||||
|
||||
#define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a)
|
||||
#if TARGET_CPU_X86
|
||||
MDRawContextX86 *context_ptr = context.get();
|
||||
context_ptr->context_flags = MD_CONTEXT_X86;
|
||||
AddReg(eax);
|
||||
AddReg(ebx);
|
||||
AddReg(ecx);
|
||||
AddReg(edx);
|
||||
AddReg(esi);
|
||||
AddReg(edi);
|
||||
AddReg(ebp);
|
||||
AddReg(esp);
|
||||
|
||||
#define AddReg(a) context_ptr->a = machine_state->a
|
||||
AddReg(cs);
|
||||
AddReg(ds);
|
||||
AddReg(ss);
|
||||
@@ -473,36 +375,14 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
||||
AddReg(eflags);
|
||||
|
||||
AddReg(eip);
|
||||
#else
|
||||
context_ptr->context_flags = MD_CONTEXT_AMD64;
|
||||
AddReg(rax);
|
||||
AddReg(rbx);
|
||||
AddReg(rcx);
|
||||
AddReg(rdx);
|
||||
AddReg(rdi);
|
||||
AddReg(rsi);
|
||||
AddReg(rbp);
|
||||
AddReg(rsp);
|
||||
AddReg(r8);
|
||||
AddReg(r9);
|
||||
AddReg(r10);
|
||||
AddReg(r11);
|
||||
AddReg(r12);
|
||||
AddReg(r13);
|
||||
AddReg(r14);
|
||||
AddReg(r15);
|
||||
AddReg(rip);
|
||||
// according to AMD's software developer guide, bits above 18 are
|
||||
// not used in the flags register. Since the minidump format
|
||||
// specifies 32 bits for the flags register, we can truncate safely
|
||||
// with no loss.
|
||||
context_ptr->eflags = machine_state->__rflags;
|
||||
AddReg(cs);
|
||||
AddReg(fs);
|
||||
AddReg(gs);
|
||||
#endif
|
||||
#undef AddReg(a)
|
||||
|
||||
AddReg(eax);
|
||||
AddReg(ebx);
|
||||
AddReg(ecx);
|
||||
AddReg(edx);
|
||||
AddReg(esi);
|
||||
AddReg(edi);
|
||||
AddReg(ebp);
|
||||
AddReg(esp);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -567,8 +447,7 @@ bool MinidumpGenerator::WriteThreadListStream(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) {
|
||||
bool MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) {
|
||||
TypedMDRVA<MDRawExceptionStream> exception(&writer_);
|
||||
|
||||
if (!exception.Allocate())
|
||||
@@ -587,19 +466,14 @@ MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) {
|
||||
breakpad_thread_state_data_t state;
|
||||
mach_msg_type_number_t stateCount = sizeof(state);
|
||||
|
||||
if (thread_get_state(exception_thread_,
|
||||
BREAKPAD_MACHINE_THREAD_STATE,
|
||||
state,
|
||||
if (thread_get_state(exception_thread_, BREAKPAD_MACHINE_THREAD_STATE, state,
|
||||
&stateCount) != KERN_SUCCESS)
|
||||
return false;
|
||||
|
||||
if (!WriteContext(state, &exception_ptr->thread_context))
|
||||
return false;
|
||||
|
||||
if (exception_type_ == EXC_BAD_ACCESS)
|
||||
exception_ptr->exception_record.exception_address = exception_subcode_;
|
||||
else
|
||||
exception_ptr->exception_record.exception_address = CurrentPCForStack(state);
|
||||
exception_ptr->exception_record.exception_address = CurrentPCForStack(state);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -634,13 +508,13 @@ bool MinidumpGenerator::WriteSystemInfoStream(
|
||||
// to preserve it.
|
||||
#define cpuid(op,eax,ebx,ecx,edx) \
|
||||
asm ("pushl %%ebx \n\t" \
|
||||
"cpuid \n\t" \
|
||||
"movl %%ebx,%1 \n\t" \
|
||||
"popl %%ebx" \
|
||||
: "=a" (eax), \
|
||||
"=g" (ebx), \
|
||||
"=c" (ecx), \
|
||||
"=d" (edx) \
|
||||
"cpuid \n\t" \
|
||||
"movl %%ebx,%1 \n\t" \
|
||||
"popl %%ebx" \
|
||||
: "=a" (eax), \
|
||||
"=g" (ebx), \
|
||||
"=c" (ecx), \
|
||||
"=d" (edx) \
|
||||
: "0" (op))
|
||||
int unused, unused2;
|
||||
// get vendor id
|
||||
@@ -690,7 +564,7 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
||||
if (!image)
|
||||
return false;
|
||||
|
||||
const breakpad_mach_header *header = image->GetMachHeader();
|
||||
const mach_header *header = image->GetMachHeader();
|
||||
|
||||
if (!header)
|
||||
return false;
|
||||
@@ -709,49 +583,16 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
||||
module->size_of_image = image->GetVMSize();
|
||||
module->module_name_rva = string_location.rva;
|
||||
|
||||
// We'll skip the executable module, because they don't have
|
||||
// LC_ID_DYLIB load commands, and the crash processing server gets
|
||||
// version information from the Plist file, anyway.
|
||||
if (index != (uint32_t)FindExecutableModule()) {
|
||||
module->version_info.signature = MD_VSFIXEDFILEINFO_SIGNATURE;
|
||||
module->version_info.struct_version |= MD_VSFIXEDFILEINFO_VERSION;
|
||||
// Convert MAC dylib version format, which is a 32 bit number, to the
|
||||
// format used by minidump. The mac format is <16 bits>.<8 bits>.<8 bits>
|
||||
// so it fits nicely into the windows version with some massaging
|
||||
// The mapping is:
|
||||
// 1) upper 16 bits of MAC version go to lower 16 bits of product HI
|
||||
// 2) Next most significant 8 bits go to upper 16 bits of product LO
|
||||
// 3) Least significant 8 bits go to lower 16 bits of product LO
|
||||
uint32_t modVersion = image->GetVersion();
|
||||
module->version_info.file_version_hi = 0;
|
||||
module->version_info.file_version_hi = modVersion >> 16;
|
||||
module->version_info.file_version_lo |= (modVersion & 0xff00) << 8;
|
||||
module->version_info.file_version_lo |= (modVersion & 0xff);
|
||||
}
|
||||
|
||||
if (!WriteCVRecord(module, cpu_type, name)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// we're getting module info in the crashed process
|
||||
const struct mach_header *header = _dyld_get_image_header(index);
|
||||
|
||||
const breakpad_mach_header *header;
|
||||
header = (breakpad_mach_header*)_dyld_get_image_header(index);
|
||||
if (!header)
|
||||
return false;
|
||||
|
||||
#ifdef __LP64__
|
||||
assert(header->magic == MH_MAGIC_64);
|
||||
|
||||
if(header->magic != MH_MAGIC_64)
|
||||
return false;
|
||||
#else
|
||||
assert(header->magic == MH_MAGIC);
|
||||
|
||||
if(header->magic != MH_MAGIC)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
int cpu_type = header->cputype;
|
||||
unsigned long slide = _dyld_get_image_vmaddr_slide(index);
|
||||
const char* name = _dyld_get_image_name(index);
|
||||
@@ -762,10 +603,8 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
||||
|
||||
for (unsigned int i = 0; cmd && (i < header->ncmds); i++) {
|
||||
if (cmd->cmd == LC_SEGMENT) {
|
||||
|
||||
const breakpad_mach_segment_command *seg =
|
||||
reinterpret_cast<const breakpad_mach_segment_command *>(cmd);
|
||||
|
||||
const struct segment_command *seg =
|
||||
reinterpret_cast<const struct segment_command *>(cmd);
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
MDLocationDescriptor string_location;
|
||||
|
||||
@@ -783,10 +622,10 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
||||
}
|
||||
}
|
||||
|
||||
cmd = reinterpret_cast<struct load_command*>((char *)cmd + cmd->cmdsize);
|
||||
cmd = reinterpret_cast<struct load_command *>((char *)cmd + cmd->cmdsize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -808,7 +647,7 @@ int MinidumpGenerator::FindExecutableModule() {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// failed - just use the first image
|
||||
return 0;
|
||||
}
|
||||
@@ -842,9 +681,9 @@ bool MinidumpGenerator::WriteCVRecord(MDRawModule *module, int cpu_type,
|
||||
// Get the module identifier
|
||||
FileID file_id(module_path);
|
||||
unsigned char identifier[16];
|
||||
|
||||
|
||||
if (file_id.MachoIdentifier(cpu_type, identifier)) {
|
||||
cv_ptr->signature.data1 = (uint32_t)identifier[0] << 24 |
|
||||
cv_ptr->signature.data1 = (uint32_t)identifier[0] << 24 |
|
||||
(uint32_t)identifier[1] << 16 | (uint32_t)identifier[2] << 8 |
|
||||
(uint32_t)identifier[3];
|
||||
cv_ptr->signature.data2 = (uint32_t)identifier[4] << 8 | identifier[5];
|
||||
@@ -866,6 +705,9 @@ bool MinidumpGenerator::WriteModuleListStream(
|
||||
MDRawDirectory *module_list_stream) {
|
||||
TypedMDRVA<MDRawModuleList> list(&writer_);
|
||||
|
||||
if (!_dyld_present())
|
||||
return false;
|
||||
|
||||
int image_count = dynamic_images_ ?
|
||||
dynamic_images_->GetImageCount() : _dyld_image_count();
|
||||
|
||||
@@ -928,15 +770,12 @@ bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *misc_info_stream) {
|
||||
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, info_ptr->process_id };
|
||||
size_t size;
|
||||
if (!sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &size, NULL, 0)) {
|
||||
mach_vm_address_t addr;
|
||||
if (mach_vm_allocate(mach_task_self(),
|
||||
&addr,
|
||||
size,
|
||||
true) == KERN_SUCCESS) {
|
||||
vm_address_t addr;
|
||||
if (vm_allocate(mach_task_self(), &addr, size, true) == KERN_SUCCESS) {
|
||||
struct kinfo_proc *proc = (struct kinfo_proc *)addr;
|
||||
if (!sysctl(mib, sizeof(mib) / sizeof(mib[0]), proc, &size, NULL, 0))
|
||||
info_ptr->process_create_time = proc->kp_proc.p_starttime.tv_sec;
|
||||
mach_vm_deallocate(mach_task_self(), addr, size);
|
||||
vm_deallocate(mach_task_self(), addr, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,38 +46,6 @@ namespace google_breakpad {
|
||||
|
||||
using std::string;
|
||||
|
||||
#if TARGET_CPU_X86_64 || TARGET_CPU_PPC64
|
||||
#define TOP_OF_THREAD0_STACK 0x00007fff5fbff000
|
||||
#else
|
||||
#define TOP_OF_THREAD0_STACK 0xbffff000
|
||||
#endif
|
||||
|
||||
#if TARGET_CPU_X86_64
|
||||
typedef x86_thread_state64_t breakpad_thread_state_t;
|
||||
typedef MDRawContextAMD64 MinidumpContext;
|
||||
#elif TARGET_CPU_X86
|
||||
typedef i386_thread_state_t breakpad_thread_state_t;
|
||||
typedef MDRawContextX86 MinidumpContext;
|
||||
#elif TARGET_CPU_PPC64
|
||||
typedef ppc_thread_state64_t breakpad_thread_state_t;
|
||||
typedef MDRawContextPPC64 MinidumpContext;
|
||||
#elif TARGET_CPU_PPC
|
||||
typedef ppc_thread_state_t breakpad_thread_state_t;
|
||||
typedef MDRawContextPPC MinidumpContext;
|
||||
#endif
|
||||
|
||||
// Use the REGISTER_FROM_THREADSTATE to access a register name from the
|
||||
// breakpad_thread_state_t structure.
|
||||
#if __DARWIN_UNIX03 || !TARGET_CPU_X86 || TARGET_CPU_X86_64
|
||||
// In The 10.5 SDK Headers Apple prepended __ to the variable names in the
|
||||
// i386_thread_state_t structure. There's no good way to tell what version of
|
||||
// the SDK we're compiling against so we just toggle on the same preprocessor
|
||||
// symbol Apple's headers use.
|
||||
#define REGISTER_FROM_THREADSTATE(a, b) ((a)->__ ## b)
|
||||
#else
|
||||
#define REGISTER_FROM_THREADSTATE(a, b) (a->b)
|
||||
#endif
|
||||
|
||||
// Creates a minidump file of the current process. If there is exception data,
|
||||
// use SetExceptionInformation() to add this to the minidump. The minidump
|
||||
// file is generated by the Write() function.
|
||||
@@ -102,11 +70,9 @@ class MinidumpGenerator {
|
||||
bool Write(const char *path);
|
||||
|
||||
// Specify some exception information, if applicable
|
||||
void SetExceptionInformation(int type, int code, int subcode,
|
||||
mach_port_t thread_name) {
|
||||
void SetExceptionInformation(int type, int code, mach_port_t thread_name) {
|
||||
exception_type_ = type;
|
||||
exception_code_ = code;
|
||||
exception_subcode_ = subcode;
|
||||
exception_thread_ = thread_name;
|
||||
}
|
||||
|
||||
@@ -127,7 +93,7 @@ class MinidumpGenerator {
|
||||
|
||||
// Helpers
|
||||
u_int64_t CurrentPCForStack(breakpad_thread_state_data_t state);
|
||||
bool WriteStackFromStartAddress(mach_vm_address_t start_addr,
|
||||
bool WriteStackFromStartAddress(vm_address_t start_addr,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
bool WriteStack(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
@@ -137,9 +103,7 @@ class MinidumpGenerator {
|
||||
bool WriteCVRecord(MDRawModule *module, int cpu_type,
|
||||
const char *module_path);
|
||||
bool WriteModuleStream(unsigned int index, MDRawModule *module);
|
||||
|
||||
size_t CalculateStackSize(mach_vm_address_t start_addr);
|
||||
|
||||
size_t CalculateStackSize(vm_address_t start_addr);
|
||||
int FindExecutableModule();
|
||||
|
||||
// disallow copy ctor and operator=
|
||||
@@ -152,7 +116,6 @@ class MinidumpGenerator {
|
||||
// Exception information
|
||||
int exception_type_;
|
||||
int exception_code_;
|
||||
int exception_subcode_;
|
||||
mach_port_t exception_thread_;
|
||||
mach_port_t crashing_task_;
|
||||
mach_port_t handler_thread_;
|
||||
|
||||
@@ -45,15 +45,12 @@ static bool doneWritingReport = false;
|
||||
static void *Reporter(void *) {
|
||||
char buffer[PATH_MAX];
|
||||
MinidumpGenerator md;
|
||||
struct passwd *user = getpwuid(getuid());
|
||||
|
||||
// Write it to the desktop
|
||||
snprintf(buffer,
|
||||
sizeof(buffer),
|
||||
"/tmp/test.dmp");
|
||||
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "/Users/%s/Desktop/test.dmp", user->pw_name);
|
||||
fprintf(stdout, "Writing %s\n", buffer);
|
||||
unlink(buffer);
|
||||
|
||||
md.Write(buffer);
|
||||
doneWritingReport = true;
|
||||
|
||||
|
||||
@@ -49,24 +49,6 @@
|
||||
D2F6511E0BEF973600920385 /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FC0BEF947200920385 /* macho_id.cc */; };
|
||||
D2F6511F0BEF973900920385 /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FE0BEF947200920385 /* macho_utilities.cc */; };
|
||||
D2F651210BEF975400920385 /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F6510C0BEF94EB00920385 /* macho_walker.cc */; };
|
||||
F93A887D0E8B4C8C0026AF89 /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F6510C0BEF94EB00920385 /* macho_walker.cc */; };
|
||||
F93A887E0E8B4C8C0026AF89 /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FC0BEF947200920385 /* macho_id.cc */; };
|
||||
F93A887F0E8B4C8C0026AF89 /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FE0BEF947200920385 /* macho_utilities.cc */; };
|
||||
F93A88800E8B4C8C0026AF89 /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FA0BEF947200920385 /* file_id.cc */; };
|
||||
F93A88860E8B4C9A0026AF89 /* dwarftests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F9721F310E8B07E800D7E813 /* dwarftests.mm */; };
|
||||
F93A88870E8B4C9A0026AF89 /* dump_syms.mm in Sources */ = {isa = PBXBuildFile; fileRef = F9721F390E8B0D0D00D7E813 /* dump_syms.mm */; };
|
||||
F93A88880E8B4C9A0026AF89 /* bytereader.cc in Sources */ = {isa = PBXBuildFile; fileRef = F9721F760E8B0DC700D7E813 /* bytereader.cc */; };
|
||||
F93A88890E8B4C9A0026AF89 /* dwarf2reader.cc in Sources */ = {isa = PBXBuildFile; fileRef = F9721F770E8B0DC700D7E813 /* dwarf2reader.cc */; };
|
||||
F93A888A0E8B4C9A0026AF89 /* functioninfo.cc in Sources */ = {isa = PBXBuildFile; fileRef = F9721F780E8B0DC700D7E813 /* functioninfo.cc */; };
|
||||
F93A888B0E8B4C9A0026AF89 /* md5.c in Sources */ = {isa = PBXBuildFile; fileRef = F9721FA80E8B0E4800D7E813 /* md5.c */; };
|
||||
F9721F6C0E8B0D7000D7E813 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */; };
|
||||
F9721FA20E8B0E2300D7E813 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */; };
|
||||
F982089C0DB3280D0017AECA /* breakpad_nlist_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F982089B0DB3280D0017AECA /* breakpad_nlist_test.cc */; };
|
||||
F98208A30DB32CAE0017AECA /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */; };
|
||||
F9AE5B390DBFDBDB00505983 /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F651070BEF949A00920385 /* dynamic_images.cc */; };
|
||||
F9AE5B3A0DBFDBDB00505983 /* DynamicImagesTests.cc in Sources */ = {isa = PBXBuildFile; fileRef = F9C5A4210DB82DD800209C76 /* DynamicImagesTests.cc */; };
|
||||
F9B34E870DBC1E1600306484 /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F651070BEF949A00920385 /* dynamic_images.cc */; };
|
||||
F9C5A4220DB82DD800209C76 /* DynamicImagesTests.cc in Sources */ = {isa = PBXBuildFile; fileRef = F9C5A4210DB82DD800209C76 /* DynamicImagesTests.cc */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -124,30 +106,6 @@
|
||||
D2F651080BEF949A00920385 /* dynamic_images.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = dynamic_images.h; sourceTree = "<group>"; };
|
||||
D2F6510C0BEF94EB00920385 /* macho_walker.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = macho_walker.cc; path = ../../../common/mac/macho_walker.cc; sourceTree = SOURCE_ROOT; };
|
||||
D2F6510D0BEF94EB00920385 /* macho_walker.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = macho_walker.h; path = ../../../common/mac/macho_walker.h; sourceTree = SOURCE_ROOT; };
|
||||
F917C4F70E03265A00F86017 /* breakpad_exc_server.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = breakpad_exc_server.c; sourceTree = "<group>"; };
|
||||
F917C4F80E03265A00F86017 /* breakpad_exc_server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = breakpad_exc_server.h; sourceTree = "<group>"; };
|
||||
F93A88750E8B4C700026AF89 /* octestcases.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = octestcases.octest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F93A88760E8B4C700026AF89 /* obj-cTestCases-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "obj-cTestCases-Info.plist"; sourceTree = "<group>"; };
|
||||
F9721F300E8B07E800D7E813 /* dwarftests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dwarftests.h; sourceTree = "<group>"; };
|
||||
F9721F310E8B07E800D7E813 /* dwarftests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = dwarftests.mm; sourceTree = "<group>"; };
|
||||
F9721F380E8B0CFC00D7E813 /* dump_syms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dump_syms.h; path = ../../../common/mac/dump_syms.h; sourceTree = SOURCE_ROOT; };
|
||||
F9721F390E8B0D0D00D7E813 /* dump_syms.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = dump_syms.mm; path = ../../../common/mac/dump_syms.mm; sourceTree = SOURCE_ROOT; };
|
||||
F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
|
||||
F9721F760E8B0DC700D7E813 /* bytereader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bytereader.cc; path = ../../../common/dwarf/bytereader.cc; sourceTree = SOURCE_ROOT; };
|
||||
F9721F770E8B0DC700D7E813 /* dwarf2reader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dwarf2reader.cc; path = ../../../common/dwarf/dwarf2reader.cc; sourceTree = SOURCE_ROOT; };
|
||||
F9721F780E8B0DC700D7E813 /* functioninfo.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = functioninfo.cc; path = ../../../common/dwarf/functioninfo.cc; sourceTree = SOURCE_ROOT; };
|
||||
F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = /System/Library/Frameworks/SenTestingKit.framework; sourceTree = "<absolute>"; };
|
||||
F9721FA80E8B0E4800D7E813 /* md5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = md5.c; path = ../../../common/md5.c; sourceTree = SOURCE_ROOT; };
|
||||
F982089A0DB3280D0017AECA /* breakpad_nlist_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = breakpad_nlist_test.h; sourceTree = "<group>"; };
|
||||
F982089B0DB3280D0017AECA /* breakpad_nlist_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = breakpad_nlist_test.cc; sourceTree = "<group>"; };
|
||||
F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = breakpad_nlist_64.cc; sourceTree = "<group>"; };
|
||||
F98208A20DB32CAE0017AECA /* breakpad_nlist_64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = breakpad_nlist_64.h; sourceTree = "<group>"; };
|
||||
F9AE19B50DB040E300C98454 /* minidump_tests32-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "minidump_tests32-Info.plist"; sourceTree = "<group>"; };
|
||||
F9AE19C30DB04A9500C98454 /* minidump_tests64.cptest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = minidump_tests64.cptest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F9AE5B330DBFDBA300505983 /* minidump_tests32.cptest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = minidump_tests32.cptest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F9AE5B340DBFDBA300505983 /* minidump_tests64-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "minidump_tests64-Info.plist"; sourceTree = "<group>"; };
|
||||
F9C5A4200DB82DD800209C76 /* DynamicImagesTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DynamicImagesTests.h; sourceTree = "<group>"; };
|
||||
F9C5A4210DB82DD800209C76 /* DynamicImagesTests.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DynamicImagesTests.cc; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -174,45 +132,12 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F93A88720E8B4C700026AF89 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F9AE19C00DB04A9500C98454 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F9AE5B300DBFDBA300505983 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F9721F6C0E8B0D7000D7E813 /* Cocoa.framework in Frameworks */,
|
||||
F9721FA20E8B0E2300D7E813 /* SenTestingKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
08FB7794FE84155DC02AAC07 /* MinidumpWriter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F9721FA80E8B0E4800D7E813 /* md5.c */,
|
||||
F9721F760E8B0DC700D7E813 /* bytereader.cc */,
|
||||
F9721F770E8B0DC700D7E813 /* dwarf2reader.cc */,
|
||||
F9721F780E8B0DC700D7E813 /* functioninfo.cc */,
|
||||
F9721F390E8B0D0D00D7E813 /* dump_syms.mm */,
|
||||
F9721F380E8B0CFC00D7E813 /* dump_syms.h */,
|
||||
F917C4F70E03265A00F86017 /* breakpad_exc_server.c */,
|
||||
F917C4F80E03265A00F86017 /* breakpad_exc_server.h */,
|
||||
F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */,
|
||||
F98208A20DB32CAE0017AECA /* breakpad_nlist_64.h */,
|
||||
D2F6510C0BEF94EB00920385 /* macho_walker.cc */,
|
||||
D2F6510D0BEF94EB00920385 /* macho_walker.h */,
|
||||
D2F651070BEF949A00920385 /* dynamic_images.cc */,
|
||||
@@ -223,14 +148,10 @@
|
||||
D2F650FD0BEF947200920385 /* macho_id.h */,
|
||||
D2F650FE0BEF947200920385 /* macho_utilities.cc */,
|
||||
D2F650FF0BEF947200920385 /* macho_utilities.h */,
|
||||
F9C5A41F0DB82DB000209C76 /* testcases */,
|
||||
9BD82C040B0133420055103E /* Breakpad */,
|
||||
08FB7795FE84155DC02AAC07 /* Source */,
|
||||
9B37CEEA0AF98EB600FA4BD4 /* Frameworks */,
|
||||
1AB674ADFE9D54B511CA2CBB /* Products */,
|
||||
F9AE19B50DB040E300C98454 /* minidump_tests32-Info.plist */,
|
||||
F9AE5B340DBFDBA300505983 /* minidump_tests64-Info.plist */,
|
||||
F93A88760E8B4C700026AF89 /* obj-cTestCases-Info.plist */,
|
||||
);
|
||||
name = MinidumpWriter;
|
||||
sourceTree = "<group>";
|
||||
@@ -251,9 +172,6 @@
|
||||
8DD76F6C0486A84900D96B5E /* generator_test */,
|
||||
9BD82A9B0B00267E0055103E /* handler_test */,
|
||||
9B7CA84E0B1297F200CD3A1D /* unit_test */,
|
||||
F9AE19C30DB04A9500C98454 /* minidump_tests64.cptest */,
|
||||
F9AE5B330DBFDBA300505983 /* minidump_tests32.cptest */,
|
||||
F93A88750E8B4C700026AF89 /* octestcases.octest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -261,8 +179,6 @@
|
||||
9B37CEEA0AF98EB600FA4BD4 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */,
|
||||
F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */,
|
||||
9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
@@ -288,19 +204,6 @@
|
||||
name = Breakpad;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F9C5A41F0DB82DB000209C76 /* testcases */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F982089A0DB3280D0017AECA /* breakpad_nlist_test.h */,
|
||||
F982089B0DB3280D0017AECA /* breakpad_nlist_test.cc */,
|
||||
F9C5A4200DB82DD800209C76 /* DynamicImagesTests.h */,
|
||||
F9C5A4210DB82DD800209C76 /* DynamicImagesTests.cc */,
|
||||
F9721F300E8B07E800D7E813 /* dwarftests.h */,
|
||||
F9721F310E8B07E800D7E813 /* dwarftests.mm */,
|
||||
);
|
||||
path = testcases;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -354,148 +257,23 @@
|
||||
productReference = 9BD82A9B0B00267E0055103E /* handler_test */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
F93A88740E8B4C700026AF89 /* obj-c_TestCases */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = F93A88790E8B4C700026AF89 /* Build configuration list for PBXNativeTarget "obj-c_TestCases" */;
|
||||
buildPhases = (
|
||||
F93A88700E8B4C700026AF89 /* Resources */,
|
||||
F93A88710E8B4C700026AF89 /* Sources */,
|
||||
F93A88720E8B4C700026AF89 /* Frameworks */,
|
||||
F93A88730E8B4C700026AF89 /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "obj-c_TestCases";
|
||||
productName = octestcases;
|
||||
productReference = F93A88750E8B4C700026AF89 /* octestcases.octest */;
|
||||
productType = "com.apple.product-type.bundle";
|
||||
};
|
||||
F9AE19C20DB04A9500C98454 /* minidump_tests64 */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = F9AE19C70DB04AA200C98454 /* Build configuration list for PBXNativeTarget "minidump_tests64" */;
|
||||
buildPhases = (
|
||||
F9AE19BE0DB04A9500C98454 /* Resources */,
|
||||
F9AE19BF0DB04A9500C98454 /* Sources */,
|
||||
F9AE19C00DB04A9500C98454 /* Frameworks */,
|
||||
F9AE19C10DB04A9500C98454 /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = minidump_tests64;
|
||||
productName = minidump_tests;
|
||||
productReference = F9AE19C30DB04A9500C98454 /* minidump_tests64.cptest */;
|
||||
productType = "com.apple.product-type.bundle";
|
||||
};
|
||||
F9AE5B320DBFDBA300505983 /* minidump_tests32 */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = F9AE5B380DBFDBA300505983 /* Build configuration list for PBXNativeTarget "minidump_tests32" */;
|
||||
buildPhases = (
|
||||
F9AE5B2E0DBFDBA300505983 /* Resources */,
|
||||
F9AE5B2F0DBFDBA300505983 /* Sources */,
|
||||
F9AE5B300DBFDBA300505983 /* Frameworks */,
|
||||
F9AE5B310DBFDBA300505983 /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = minidump_tests32;
|
||||
productName = Untitled;
|
||||
productReference = F9AE5B330DBFDBA300505983 /* minidump_tests32.cptest */;
|
||||
productType = "com.apple.product-type.bundle";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
08FB7793FE84155DC02AAC07 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "minidump_test" */;
|
||||
compatibilityVersion = "Xcode 2.4";
|
||||
hasScannedForEncodings = 1;
|
||||
mainGroup = 08FB7794FE84155DC02AAC07 /* MinidumpWriter */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
8DD76F620486A84900D96B5E /* generator_test */,
|
||||
9BD82A9A0B00267E0055103E /* handler_test */,
|
||||
9B7CA84D0B1297F200CD3A1D /* unit_test */,
|
||||
F9AE19C20DB04A9500C98454 /* minidump_tests64 */,
|
||||
F9AE5B320DBFDBA300505983 /* minidump_tests32 */,
|
||||
F93A88740E8B4C700026AF89 /* obj-c_TestCases */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
F93A88700E8B4C700026AF89 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F9AE19BE0DB04A9500C98454 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F9AE5B2E0DBFDBA300505983 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
F93A88730E8B4C700026AF89 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n";
|
||||
};
|
||||
F9AE19C10DB04A9500C98454 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n# Run gcov on the framework getting tested\nif [ \"${CONFIGURATION}\" = 'Coverage' ];\nthen\n FRAMEWORK_NAME=minidump_tests64\n FRAMEWORK_OBJ_DIR=${OBJROOT}/${PROJECT_NAME}.build/${CONFIGURATION}/${FRAMEWORK_NAME}.build/Objects-normal/${NATIVE_ARCH_ACTUAL}\n mkdir -p coverage\n pushd coverage\n echo find ${OBJROOT} -name *.gcda -exec gcov -o ${FRAMEWORK_OBJ_DIR} {} \\;\n find ${OBJROOT} -name *.gcda -exec gcov -o ${FRAMEWORK_OBJ_DIR} {} \\;\n popd\nfi ";
|
||||
};
|
||||
F9AE5B310DBFDBA300505983 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
8DD76F640486A84900D96B5E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
@@ -546,43 +324,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F93A88710E8B4C700026AF89 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F93A88860E8B4C9A0026AF89 /* dwarftests.mm in Sources */,
|
||||
F93A88870E8B4C9A0026AF89 /* dump_syms.mm in Sources */,
|
||||
F93A88880E8B4C9A0026AF89 /* bytereader.cc in Sources */,
|
||||
F93A88890E8B4C9A0026AF89 /* dwarf2reader.cc in Sources */,
|
||||
F93A888A0E8B4C9A0026AF89 /* functioninfo.cc in Sources */,
|
||||
F93A888B0E8B4C9A0026AF89 /* md5.c in Sources */,
|
||||
F93A887D0E8B4C8C0026AF89 /* macho_walker.cc in Sources */,
|
||||
F93A887E0E8B4C8C0026AF89 /* macho_id.cc in Sources */,
|
||||
F93A887F0E8B4C8C0026AF89 /* macho_utilities.cc in Sources */,
|
||||
F93A88800E8B4C8C0026AF89 /* file_id.cc in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F9AE19BF0DB04A9500C98454 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F9B34E870DBC1E1600306484 /* dynamic_images.cc in Sources */,
|
||||
F982089C0DB3280D0017AECA /* breakpad_nlist_test.cc in Sources */,
|
||||
F98208A30DB32CAE0017AECA /* breakpad_nlist_64.cc in Sources */,
|
||||
F9C5A4220DB82DD800209C76 /* DynamicImagesTests.cc in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F9AE5B2F0DBFDBA300505983 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F9AE5B390DBFDBDB00505983 /* dynamic_images.cc in Sources */,
|
||||
F9AE5B3A0DBFDBDB00505983 /* DynamicImagesTests.cc in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -715,182 +456,6 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F93A88770E8B4C700026AF89 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_CHAR_IS_UNSIGNED_CHAR = YES;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
INFOPLIST_FILE = "obj-cTestCases-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
OTHER_LDFLAGS = (
|
||||
"-lcrypto",
|
||||
"-framework",
|
||||
Cocoa,
|
||||
"-framework",
|
||||
SenTestingKit,
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = octestcases;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../..//**";
|
||||
WRAPPER_EXTENSION = octest;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F93A88780E8B4C700026AF89 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_CHAR_IS_UNSIGNED_CHAR = YES;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
INFOPLIST_FILE = "obj-cTestCases-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
OTHER_LDFLAGS = (
|
||||
"-lcrypto",
|
||||
"-framework",
|
||||
Cocoa,
|
||||
"-framework",
|
||||
SenTestingKit,
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = octestcases;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../..//**";
|
||||
WRAPPER_EXTENSION = octest;
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F9AE19C40DB04A9500C98454 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = (
|
||||
"$(NATIVE_ARCH_64_BIT)",
|
||||
ppc64,
|
||||
);
|
||||
COPY_PHASE_STRIP = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Carbon.framework/Headers/Carbon.h";
|
||||
INFOPLIST_FILE = "minidump_tests64-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.5;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
Carbon,
|
||||
"-framework",
|
||||
CPlusTest,
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = minidump_tests64;
|
||||
SDKROOT = /Developer/SDKs/MacOSX10.5.sdk;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../**";
|
||||
WRAPPER_EXTENSION = cptest;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F9AE19C50DB04A9500C98454 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = (
|
||||
"$(NATIVE_ARCH_64_BIT)",
|
||||
ppc64,
|
||||
);
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Carbon.framework/Headers/Carbon.h";
|
||||
INFOPLIST_FILE = "minidump_tests64-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.5;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
Carbon,
|
||||
"-framework",
|
||||
CPlusTest,
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = minidump_tests64;
|
||||
SDKROOT = /Developer/SDKs/MacOSX10.5.sdk;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../**";
|
||||
WRAPPER_EXTENSION = cptest;
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F9AE5B350DBFDBA300505983 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(NATIVE_ARCH)";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Carbon.framework/Headers/Carbon.h";
|
||||
INFOPLIST_FILE = "minidump_tests32-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
Carbon,
|
||||
"-framework",
|
||||
CPlusTest,
|
||||
"-lcrypto",
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = minidump_tests32;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../**";
|
||||
WRAPPER_EXTENSION = cptest;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F9AE5B370DBFDBA300505983 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(NATIVE_ARCH)";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Carbon.framework/Headers/Carbon.h";
|
||||
INFOPLIST_FILE = "minidump_tests32-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
OTHER_LDFLAGS = (
|
||||
"-lcrypto",
|
||||
"-framework",
|
||||
Carbon,
|
||||
"-framework",
|
||||
CPlusTest,
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = minidump_tests32;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../**";
|
||||
WRAPPER_EXTENSION = cptest;
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -930,33 +495,6 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
F93A88790E8B4C700026AF89 /* Build configuration list for PBXNativeTarget "obj-c_TestCases" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F93A88770E8B4C700026AF89 /* Debug */,
|
||||
F93A88780E8B4C700026AF89 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
F9AE19C70DB04AA200C98454 /* Build configuration list for PBXNativeTarget "minidump_tests64" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F9AE19C40DB04A9500C98454 /* Debug */,
|
||||
F9AE19C50DB04A9500C98454 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
F9AE5B380DBFDBA300505983 /* Build configuration list for PBXNativeTarget "minidump_tests32" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F9AE5B350DBFDBA300505983 /* Debug */,
|
||||
F9AE5B370DBFDBA300505983 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 08FB7793FE84155DC02AAC07 /* Project object */;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.google.breakpad.minidump_tests32</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.google.breakpad.minidump_tests64</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<string>yes</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.yourcompany.${PRODUCT_NAME:identifier}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// DynamicImagesTests.cpp
|
||||
// minidump_test
|
||||
//
|
||||
// Created by Neal Sidhwaney on 4/17/08.
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include "client/mac/handler/testcases/DynamicImagesTests.h"
|
||||
#include "client/mac/handler/dynamic_images.h"
|
||||
|
||||
DynamicImagesTests test2(TEST_INVOCATION(DynamicImagesTests,
|
||||
ReadTaskMemoryTest));
|
||||
DynamicImagesTests test3(TEST_INVOCATION(DynamicImagesTests,
|
||||
ReadLibrariesFromLocalTaskTest));
|
||||
|
||||
DynamicImagesTests::DynamicImagesTests(TestInvocation *invocation)
|
||||
: TestCase(invocation) {
|
||||
}
|
||||
|
||||
DynamicImagesTests::~DynamicImagesTests() {
|
||||
}
|
||||
|
||||
void DynamicImagesTests::ReadTaskMemoryTest() {
|
||||
kern_return_t kr;
|
||||
|
||||
// pick test2 as a symbol we know to be valid to read
|
||||
// anything will work, really
|
||||
void *addr = reinterpret_cast<void*>(&test2);
|
||||
void *buf;
|
||||
|
||||
fprintf(stderr, "reading 0x%p\n", addr);
|
||||
buf = google_breakpad::ReadTaskMemory(mach_task_self(),
|
||||
addr,
|
||||
getpagesize(),
|
||||
&kr);
|
||||
|
||||
CPTAssert(kr == KERN_SUCCESS);
|
||||
|
||||
CPTAssert(buf != NULL);
|
||||
|
||||
CPTAssert(0 == memcmp(buf, (const void*)addr, getpagesize()));
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
void DynamicImagesTests::ReadLibrariesFromLocalTaskTest() {
|
||||
|
||||
mach_port_t me = mach_task_self();
|
||||
google_breakpad::DynamicImages *d = new google_breakpad::DynamicImages(me);
|
||||
|
||||
fprintf(stderr,"Local task image count: %d\n", d->GetImageCount());
|
||||
|
||||
d->TestPrint();
|
||||
|
||||
CPTAssert(d->GetImageCount() > 0);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// DynamicImagesTests.h
|
||||
// minidump_test
|
||||
//
|
||||
// Created by Neal Sidhwaney on 4/17/08.
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef _CLIENT_MAC_HANDLER_TESTCASES_DYNAMICIMAGESTESTS_H__
|
||||
#define _CLIENT_MAC_HANDLER_TESTCASES_DYNAMICIMAGESTESTS_H__
|
||||
|
||||
#include <CPlusTest/CPlusTest.h>
|
||||
|
||||
class DynamicImagesTests : public TestCase {
|
||||
public:
|
||||
explicit DynamicImagesTests(TestInvocation* invocation);
|
||||
virtual ~DynamicImagesTests();
|
||||
|
||||
void ReadTaskMemoryTest();
|
||||
void ReadLibrariesFromLocalTaskTest();
|
||||
};
|
||||
|
||||
#endif /* _CLIENT_MAC_HANDLER_TESTCASES_DYNAMICIMAGESTESTS_H__ */
|
||||
@@ -1,106 +0,0 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// breakpad_nlist_test.cc
|
||||
// minidump_test
|
||||
//
|
||||
// Created by Neal Sidhwaney on 4/13/08.
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include "client/mac/handler/testcases/breakpad_nlist_test.h"
|
||||
#include <mach-o/nlist.h>
|
||||
#include "client/mac/handler/breakpad_nlist_64.h"
|
||||
|
||||
BreakpadNlistTest test1(TEST_INVOCATION(BreakpadNlistTest, CompareToNM));
|
||||
|
||||
BreakpadNlistTest::BreakpadNlistTest(TestInvocation *invocation)
|
||||
: TestCase(invocation) {
|
||||
}
|
||||
|
||||
|
||||
BreakpadNlistTest::~BreakpadNlistTest() {
|
||||
}
|
||||
|
||||
void BreakpadNlistTest::CompareToNM() {
|
||||
#if TARGET_CPU_X86_64
|
||||
system("/usr/bin/nm -arch x86_64 /usr/lib/dyld > /tmp/dyld-namelist.txt");
|
||||
#elif TARGET_CPU_PPC64
|
||||
system("/usr/bin/nm -arch ppc64 /usr/lib/dyld > /tmp/dyld-namelist.txt");
|
||||
#endif
|
||||
|
||||
FILE *fd = fopen("/tmp/dyld-namelist.txt", "rt");
|
||||
|
||||
char oneNMAddr[30];
|
||||
char symbolType;
|
||||
char symbolName[500];
|
||||
while (!feof(fd)) {
|
||||
fscanf(fd, "%s %c %s", oneNMAddr, &symbolType, symbolName);
|
||||
breakpad_nlist symbolList[2];
|
||||
breakpad_nlist &list = symbolList[0];
|
||||
|
||||
memset(symbolList, 0, sizeof(breakpad_nlist)*2);
|
||||
const char *symbolNames[2];
|
||||
symbolNames[0] = (const char*)symbolName;
|
||||
symbolNames[1] = "\0";
|
||||
breakpad_nlist_64("/usr/lib/dyld", &list, symbolNames);
|
||||
uint64_t nmAddr = strtol(oneNMAddr, NULL, 16);
|
||||
if (!IsSymbolMoreThanOnceInDyld(symbolName)) {
|
||||
CPTAssert(nmAddr == symbolList[0].n_value);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fd);
|
||||
}
|
||||
|
||||
bool BreakpadNlistTest::IsSymbolMoreThanOnceInDyld(const char *symbolName) {
|
||||
// These are the symbols that occur more than once when nm dumps
|
||||
// the symbol table of /usr/lib/dyld. Our nlist program returns
|
||||
// the first address because it's doing a search so we need to exclude
|
||||
// these from causing the test to fail
|
||||
const char *multipleSymbols[] = {
|
||||
"__Z41__static_initialization_and_destruction_0ii",
|
||||
"___tcf_0",
|
||||
"___tcf_1",
|
||||
"_read_encoded_value_with_base",
|
||||
"_read_sleb128",
|
||||
"_read_uleb128",
|
||||
"\0"};
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (int i = 0; multipleSymbols[i][0]; i++) {
|
||||
if (!strcmp(multipleSymbols[i], symbolName)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// breakpad_nlist_test.h
|
||||
// minidump_test
|
||||
//
|
||||
// Created by Neal Sidhwaney on 4/13/08.
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef CLIENT_MAC_HANDLER_TESTCASES_BREAKPAD_NLIST_TEST_H__
|
||||
#define CLIENT_MAC_HANDLER_TESTCASES_BREAKPAD_NLIST_TEST_H__
|
||||
|
||||
#include <CPlusTest/CPlusTest.h>
|
||||
|
||||
class BreakpadNlistTest : public TestCase {
|
||||
private:
|
||||
|
||||
// nm dumps multiple addresses for the same symbol in
|
||||
// /usr/lib/dyld. So we track those so we don't report failures
|
||||
// in mismatches between what our nlist returns and what nm has
|
||||
// for the duplicate symbols.
|
||||
bool IsSymbolMoreThanOnceInDyld(const char *symbolName);
|
||||
|
||||
public:
|
||||
explicit BreakpadNlistTest(TestInvocation* invocation);
|
||||
virtual ~BreakpadNlistTest();
|
||||
|
||||
|
||||
/* This test case runs nm on /usr/lib/dyld and then compares the
|
||||
output of every symbol to what our nlist implementation returns */
|
||||
void CompareToNM();
|
||||
};
|
||||
|
||||
#endif /* CLIENT_MAC_HANDLER_TESTCASES_BREAKPAD_NLIST_TEST_H__*/
|
||||
@@ -1,46 +0,0 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// dwarftests.h
|
||||
// minidump_test
|
||||
//
|
||||
// Created by Neal Sidhwaney on 9/24/08.
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <SenTestingKit/SenTestingKit.h>
|
||||
|
||||
|
||||
@interface dwarftests : SenTestCase {
|
||||
|
||||
}
|
||||
|
||||
- (void) testDWARFSymbolFileGeneration;
|
||||
|
||||
@end
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
//
|
||||
// dwarftests.m
|
||||
// minidump_test
|
||||
//
|
||||
// Created by Neal Sidhwaney on 9/24/08.
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import "dwarftests.h"
|
||||
#import "dump_syms.h"
|
||||
|
||||
@implementation dwarftests
|
||||
- (void) testDWARFSymbolFileGeneration {
|
||||
NSString *inputBreakpadSymbolFile = @"testcases/testdata/dump_syms_i386_breakpad.sym";
|
||||
NSString *outputBreakpadSymbolFile = @"/tmp/dump_syms_i386.breakpad";
|
||||
|
||||
DumpSymbols *dump = [[DumpSymbols alloc] initWithContentsOfFile:@"testcases/testdata/dump_syms_dwarf_data"];
|
||||
|
||||
STAssertNotNil(dump, @"DumpSymbols is nil");
|
||||
[dump setArchitecture:@"i386"];
|
||||
[dump writeSymbolFile:outputBreakpadSymbolFile];
|
||||
|
||||
NSData *d = [[NSData alloc] initWithContentsOfFile:inputBreakpadSymbolFile];
|
||||
STAssertNotNil(d, @"Input breakpad symbol file not found");
|
||||
|
||||
NSData *d1 = [[NSData alloc] initWithContentsOfFile:outputBreakpadSymbolFile];
|
||||
STAssertNotNil(d1, @"Output breakpad symbol file not found");
|
||||
|
||||
STAssertTrue([d isEqualToData:d1],
|
||||
@"Symbol files were not equal!",nil);
|
||||
}
|
||||
@end
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
69
src/client/mac/sender/Breakpad.nib/classes.nib
generated
69
src/client/mac/sender/Breakpad.nib/classes.nib
generated
@@ -1,69 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IBClasses</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CLASS</key>
|
||||
<string>LengthLimitingTextField</string>
|
||||
<key>LANGUAGE</key>
|
||||
<string>ObjC</string>
|
||||
<key>SUPERCLASS</key>
|
||||
<string>NSTextField</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ACTIONS</key>
|
||||
<dict>
|
||||
<key>cancel</key>
|
||||
<string>id</string>
|
||||
<key>sendReport</key>
|
||||
<string>id</string>
|
||||
<key>showPrivacyPolicy</key>
|
||||
<string>id</string>
|
||||
</dict>
|
||||
<key>CLASS</key>
|
||||
<string>Reporter</string>
|
||||
<key>LANGUAGE</key>
|
||||
<string>ObjC</string>
|
||||
<key>OUTLETS</key>
|
||||
<dict>
|
||||
<key>alertWindow_</key>
|
||||
<string>NSWindow</string>
|
||||
<key>cancelButton_</key>
|
||||
<string>NSButton</string>
|
||||
<key>commentMessage_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>commentsEntryField_</key>
|
||||
<string>LengthLimitingTextField</string>
|
||||
<key>countdownLabel_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>dialogTitle_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>emailEntryField_</key>
|
||||
<string>LengthLimitingTextField</string>
|
||||
<key>emailLabel_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>emailMessage_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>emailSectionBox_</key>
|
||||
<string>NSBox</string>
|
||||
<key>headerBox_</key>
|
||||
<string>NSBox</string>
|
||||
<key>preEmailBox_</key>
|
||||
<string>NSBox</string>
|
||||
<key>privacyLinkArrow_</key>
|
||||
<string>NSView</string>
|
||||
<key>privacyLinkLabel_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>sendButton_</key>
|
||||
<string>NSButton</string>
|
||||
</dict>
|
||||
<key>SUPERCLASS</key>
|
||||
<string>NSObject</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>IBVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
20
src/client/mac/sender/Breakpad.nib/info.nib
generated
20
src/client/mac/sender/Breakpad.nib/info.nib
generated
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IBFramework Version</key>
|
||||
<string>676</string>
|
||||
<key>IBLastKnownRelativeProjectPath</key>
|
||||
<string>../Breakpad.xcodeproj</string>
|
||||
<key>IBOldestOS</key>
|
||||
<integer>5</integer>
|
||||
<key>IBOpenObjects</key>
|
||||
<array>
|
||||
<integer>132</integer>
|
||||
</array>
|
||||
<key>IBSystem Version</key>
|
||||
<string>9J61</string>
|
||||
<key>targetFramework</key>
|
||||
<string>IBCocoaFramework</string>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
src/client/mac/sender/Breakpad.nib/keyedobjects.nib
generated
BIN
src/client/mac/sender/Breakpad.nib/keyedobjects.nib
generated
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>crash_report_sender</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.Breakpad.${PRODUCT_NAME:identifier}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>LSHasLocalizedDisplayName</key>
|
||||
<true/>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,139 +0,0 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// This component uses the HTTPMultipartUpload of the breakpad project to send
|
||||
// the minidump and associated data to the crash reporting servers.
|
||||
// It will perform throttling based on the parameters passed to it and will
|
||||
// prompt the user to send the minidump.
|
||||
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
#include "client/mac/Framework/Breakpad.h"
|
||||
|
||||
#define kClientIdPreferenceKey @"clientid"
|
||||
|
||||
extern NSString *const kGoogleServerType;
|
||||
extern NSString *const kSocorroServerType;
|
||||
extern NSString *const kDefaultServerType;
|
||||
|
||||
// We're sublcassing NSTextField in order to override a particular
|
||||
// method (see the implementation) that lets us reject changes if they
|
||||
// are longer than a particular length. Bindings would normally solve
|
||||
// this problem, but when we implemented a validation method, and
|
||||
// returned NO for strings that were too long, the UI was not updated
|
||||
// right away, which was a poor user experience. The UI would be
|
||||
// updated as soon as the text field lost first responder status,
|
||||
// which isn't soon enough. It is a known bug that the UI KVO didn't
|
||||
// work in the middle of a validation.
|
||||
@interface LengthLimitingTextField : NSTextField {
|
||||
@private
|
||||
unsigned int maximumLength_;
|
||||
}
|
||||
|
||||
- (void) setMaximumLength:(unsigned int)maxLength;
|
||||
@end
|
||||
|
||||
@interface Reporter : NSObject {
|
||||
@public
|
||||
IBOutlet NSWindow *alertWindow_; // The alert window
|
||||
|
||||
// Grouping boxes used for resizing.
|
||||
IBOutlet NSBox *headerBox_;
|
||||
IBOutlet NSBox *preEmailBox_;
|
||||
IBOutlet NSBox *emailSectionBox_;
|
||||
// Localized elements (or things that need to be moved during localization).
|
||||
IBOutlet NSTextField *dialogTitle_;
|
||||
IBOutlet NSTextField *commentMessage_;
|
||||
IBOutlet NSTextField *emailMessage_;
|
||||
IBOutlet NSTextField *emailLabel_;
|
||||
IBOutlet NSTextField *privacyLinkLabel_;
|
||||
IBOutlet NSButton *sendButton_;
|
||||
IBOutlet NSButton *cancelButton_;
|
||||
IBOutlet LengthLimitingTextField *emailEntryField_;
|
||||
IBOutlet LengthLimitingTextField *commentsEntryField_;
|
||||
IBOutlet NSTextField *countdownLabel_;
|
||||
IBOutlet NSView *privacyLinkArrow_;
|
||||
|
||||
// Text field bindings, for user input.
|
||||
NSString *commentsValue_; // Comments from the user
|
||||
NSString *emailValue_; // Email from the user
|
||||
NSString *countdownMessage_; // Message indicating time
|
||||
// left for input.
|
||||
@private
|
||||
int configFile_; // File descriptor for config file
|
||||
NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
|
||||
NSData *minidumpContents_; // The data in the minidump (STRONG)
|
||||
NSData *logFileData_; // An NSdata for the tar,
|
||||
// bz2'd log file.
|
||||
NSTimeInterval remainingDialogTime_; // Keeps track of how long
|
||||
// we have until we cancel
|
||||
// the dialog
|
||||
NSTimer *messageTimer_; // Timer we use to update
|
||||
// the dialog
|
||||
NSMutableDictionary *serverDictionary_; // The dictionary mapping a
|
||||
// server type name to a
|
||||
// dictionary of server
|
||||
// parameter names.
|
||||
NSMutableDictionary *socorroDictionary_; // The dictionary for
|
||||
// Socorro.
|
||||
NSMutableDictionary *googleDictionary_; // The dictionary for
|
||||
// Google.
|
||||
NSMutableDictionary *extraServerVars_; // A dictionary containing
|
||||
// extra key/value pairs
|
||||
// that are uploaded to the
|
||||
// crash server with the
|
||||
// minidump.
|
||||
}
|
||||
|
||||
// Stops the modal panel with an NSAlertDefaultReturn value. This is the action
|
||||
// invoked by the "Send Report" button.
|
||||
- (IBAction)sendReport:(id)sender;
|
||||
// Stops the modal panel with an NSAlertAlternateReturn value. This is the
|
||||
// action invoked by the "Cancel" button.
|
||||
- (IBAction)cancel:(id)sender;
|
||||
// Opens the Privacy Policy url in the default web browser.
|
||||
- (IBAction)showPrivacyPolicy:(id)sender;
|
||||
|
||||
// Delegate methods for the NSTextField for comments. We want to capture the
|
||||
// Return key and use it to send the message when no text has been entered.
|
||||
// Otherwise, we want Return to add a carriage return to the comments field.
|
||||
- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView
|
||||
doCommandBySelector:(SEL)commandSelector;
|
||||
|
||||
// Accessors to make bindings work
|
||||
- (NSString *)commentsValue;
|
||||
- (void)setCommentsValue:(NSString *)value;
|
||||
|
||||
- (NSString *)emailValue;
|
||||
- (void)setEmailValue:(NSString *)value;
|
||||
|
||||
- (NSString *)countdownMessage;
|
||||
- (void)setCountdownMessage:(NSString *)value;
|
||||
|
||||
@end
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,65 +0,0 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <Breakpad/Breakpad.h>
|
||||
|
||||
enum BreakpadForkBehavior {
|
||||
DONOTHING = 0,
|
||||
UNINSTALL,
|
||||
RESETEXCEPTIONPORT
|
||||
};
|
||||
|
||||
enum BreakpadForkTestCrashPoint {
|
||||
DURINGLAUNCH = 5,
|
||||
AFTERLAUNCH = 6,
|
||||
BETWEENFORKEXEC = 7
|
||||
};
|
||||
|
||||
@interface Controller : NSObject {
|
||||
IBOutlet NSWindow *window_;
|
||||
IBOutlet NSWindow *forkTestOptions_;
|
||||
|
||||
BreakpadRef breakpad_;
|
||||
|
||||
enum BreakpadForkBehavior bpForkOption;
|
||||
|
||||
BOOL useVFork;
|
||||
enum BreakpadForkTestCrashPoint progCrashPoint;
|
||||
}
|
||||
|
||||
- (IBAction)crash:(id)sender;
|
||||
- (IBAction)forkTestOptions:(id)sender;
|
||||
- (IBAction)forkTestGo:(id)sender;
|
||||
- (IBAction)showForkTestWindow:(id) sender;
|
||||
- (void)generateReportWithoutCrash:(id)sender;
|
||||
- (void)awakeFromNib;
|
||||
|
||||
@end
|
||||
@@ -1,260 +0,0 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Breakpad/Breakpad.h>
|
||||
|
||||
#import "Controller.h"
|
||||
#import "TestClass.h"
|
||||
#include <unistd.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
@implementation Controller
|
||||
|
||||
- (void)causeCrash {
|
||||
float *aPtr = nil;
|
||||
NSLog(@"Crash!");
|
||||
NSLog(@"Bad programmer: %f", *aPtr);
|
||||
}
|
||||
|
||||
- (void)generateReportWithoutCrash:(id)sender {
|
||||
BreakpadGenerateAndSendReport(breakpad_);
|
||||
}
|
||||
|
||||
- (IBAction)showForkTestWindow:(id) sender {
|
||||
[forkTestOptions_ setIsVisible:YES];
|
||||
}
|
||||
|
||||
- (IBAction)forkTestOptions:(id)sender {
|
||||
int tag = [[sender selectedCell] tag];
|
||||
NSLog(@"sender tag: %d", tag);
|
||||
if (tag <= 2) {
|
||||
bpForkOption = tag;
|
||||
}
|
||||
|
||||
if (tag == 3) {
|
||||
useVFork = NO;
|
||||
}
|
||||
|
||||
if (tag == 4) {
|
||||
useVFork = YES;
|
||||
}
|
||||
|
||||
if (tag >= 5 && tag <= 7) {
|
||||
progCrashPoint = tag;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (IBAction)forkTestGo:(id)sender {
|
||||
|
||||
NSString *resourcePath = [[NSBundle bundleForClass:
|
||||
[self class]] resourcePath];
|
||||
NSString *execProgname;
|
||||
if (progCrashPoint == DURINGLAUNCH) {
|
||||
execProgname = [resourcePath stringByAppendingString:@"/crashduringload"];
|
||||
} else if (progCrashPoint == AFTERLAUNCH) {
|
||||
execProgname = [resourcePath stringByAppendingString:@"/crashInMain"];
|
||||
}
|
||||
|
||||
const char *progName = NULL;
|
||||
if (progCrashPoint != BETWEENFORKEXEC) {
|
||||
progName = [execProgname UTF8String];
|
||||
}
|
||||
|
||||
int pid;
|
||||
|
||||
if (bpForkOption == UNINSTALL) {
|
||||
BreakpadRelease(breakpad_);
|
||||
}
|
||||
|
||||
if (useVFork) {
|
||||
pid = vfork();
|
||||
} else {
|
||||
pid = fork();
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
sleep(3);
|
||||
NSLog(@"Child continuing");
|
||||
FILE *fd = fopen("/tmp/childlog.txt","wt");
|
||||
kern_return_t kr;
|
||||
if (bpForkOption == RESETEXCEPTIONPORT) {
|
||||
kr = task_set_exception_ports(mach_task_self(),
|
||||
EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION |
|
||||
EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT,
|
||||
MACH_PORT_NULL,
|
||||
EXCEPTION_DEFAULT,
|
||||
THREAD_STATE_NONE);
|
||||
fprintf(fd,"task_set_exception_ports returned %d\n", kr);
|
||||
}
|
||||
|
||||
if (progCrashPoint == BETWEENFORKEXEC) {
|
||||
fprintf(fd,"crashing post-fork\n");
|
||||
int *a = NULL;
|
||||
printf("%d\n",*a++);
|
||||
}
|
||||
|
||||
fprintf(fd,"about to call exec with %s\n", progName);
|
||||
fclose(fd);
|
||||
int i = execl(progName, progName, NULL);
|
||||
fprintf(fd, "exec returned! %d\n", i);
|
||||
fclose(fd);
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)crash:(id)sender {
|
||||
int tag = [sender tag];
|
||||
|
||||
if (tag == 1) {
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self];
|
||||
[self performSelector:@selector(causeCrash) withObject:nil afterDelay:10];
|
||||
[sender setState:NSOnState];
|
||||
return;
|
||||
}
|
||||
|
||||
if (tag == 2 && breakpad_) {
|
||||
BreakpadRelease(breakpad_);
|
||||
breakpad_ = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
[self causeCrash];
|
||||
}
|
||||
|
||||
- (void)anotherThread {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
TestClass *tc = [[TestClass alloc] init];
|
||||
|
||||
[tc wait];
|
||||
|
||||
[pool release];
|
||||
}
|
||||
|
||||
- (void)awakeFromNib {
|
||||
NSBundle *bundle = [NSBundle mainBundle];
|
||||
NSDictionary *info = [bundle infoDictionary];
|
||||
|
||||
|
||||
breakpad_ = BreakpadCreate(info);
|
||||
|
||||
// Do some unit tests with keys
|
||||
// first a series of bogus values
|
||||
BreakpadSetKeyValue(breakpad_, nil, @"bad2");
|
||||
BreakpadSetKeyValue(nil, @"bad3", @"bad3");
|
||||
|
||||
// Now some good ones
|
||||
BreakpadSetKeyValue(breakpad_,@"key1", @"value1");
|
||||
BreakpadSetKeyValue(breakpad_,@"key2", @"value2");
|
||||
BreakpadSetKeyValue(breakpad_,@"key3", @"value3");
|
||||
|
||||
// Look for a bogus one that we didn't try to set
|
||||
NSString *test = BreakpadKeyValue(breakpad_, @"bad4");
|
||||
if (test) {
|
||||
NSLog(@"Bad BreakpadKeyValue (bad4)");
|
||||
}
|
||||
|
||||
// Look for a bogus one we did try to set
|
||||
test = BreakpadKeyValue(breakpad_, @"bad1");
|
||||
if (test) {
|
||||
NSLog(@"Bad BreakpadKeyValue (bad1)");
|
||||
}
|
||||
|
||||
// Test some bad args for BreakpadKeyValue
|
||||
test = BreakpadKeyValue(nil, @"bad5");
|
||||
if (test) {
|
||||
NSLog(@"Bad BreakpadKeyValue (bad5)");
|
||||
}
|
||||
|
||||
test = BreakpadKeyValue(breakpad_, nil);
|
||||
if (test) {
|
||||
NSLog(@"Bad BreakpadKeyValue (nil)");
|
||||
}
|
||||
|
||||
// Find some we did set
|
||||
test = BreakpadKeyValue(breakpad_, @"key1");
|
||||
if (![test isEqualToString:@"value1"]) {
|
||||
NSLog(@"Can't find BreakpadKeyValue (key1)");
|
||||
}
|
||||
test = BreakpadKeyValue(breakpad_, @"key2");
|
||||
if (![test isEqualToString:@"value2"]) {
|
||||
NSLog(@"Can't find BreakpadKeyValue (key2)");
|
||||
}
|
||||
test = BreakpadKeyValue(breakpad_, @"key3");
|
||||
if (![test isEqualToString:@"value3"]) {
|
||||
NSLog(@"Can't find BreakpadKeyValue (key3)");
|
||||
}
|
||||
|
||||
// Bad args for BreakpadRemoveKeyValue
|
||||
BreakpadRemoveKeyValue(nil, @"bad6");
|
||||
BreakpadRemoveKeyValue(breakpad_, nil);
|
||||
|
||||
// Remove one that is valid
|
||||
BreakpadRemoveKeyValue(breakpad_, @"key3");
|
||||
|
||||
// Try and find it
|
||||
test = BreakpadKeyValue(breakpad_, @"key3");
|
||||
if (test) {
|
||||
NSLog(@"Shouldn't find BreakpadKeyValue (key3)");
|
||||
}
|
||||
|
||||
// Try and remove it again
|
||||
BreakpadRemoveKeyValue(breakpad_, @"key3");
|
||||
|
||||
// Try removal by setting to nil
|
||||
BreakpadSetKeyValue(breakpad_,@"key2", nil);
|
||||
// Try and find it
|
||||
test = BreakpadKeyValue(breakpad_, @"key2");
|
||||
if (test) {
|
||||
NSLog(@"Shouldn't find BreakpadKeyValue (key2)");
|
||||
}
|
||||
|
||||
BreakpadAddUploadParameter(breakpad_,
|
||||
@"MeaningOfLife",
|
||||
@"42");
|
||||
[NSThread detachNewThreadSelector:@selector(anotherThread)
|
||||
toTarget:self withObject:nil];
|
||||
|
||||
NSUserDefaults *args = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
// If the user specified autocrash on the command line, toggle
|
||||
// Breakpad to not confirm and crash immediately. This is for
|
||||
// automated testing.
|
||||
if ([args boolForKey:@"autocrash"]) {
|
||||
BreakpadSetKeyValue(breakpad_,
|
||||
@BREAKPAD_SKIP_CONFIRM,
|
||||
@"YES");
|
||||
[self causeCrash];
|
||||
}
|
||||
|
||||
progCrashPoint = DURINGLAUNCH;
|
||||
[window_ center];
|
||||
[window_ makeKeyAndOrderFront:self];
|
||||
}
|
||||
|
||||
@end
|
||||
Binary file not shown.
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IBClasses</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>ACTIONS</key>
|
||||
<dict>
|
||||
<key>crash</key>
|
||||
<string>id</string>
|
||||
<key>forkTestGo</key>
|
||||
<string>id</string>
|
||||
<key>forkTestOptions</key>
|
||||
<string>id</string>
|
||||
<key>generateReportWithoutCrash</key>
|
||||
<string>id</string>
|
||||
<key>showForkTestWindow</key>
|
||||
<string>id</string>
|
||||
</dict>
|
||||
<key>CLASS</key>
|
||||
<string>Controller</string>
|
||||
<key>LANGUAGE</key>
|
||||
<string>ObjC</string>
|
||||
<key>OUTLETS</key>
|
||||
<dict>
|
||||
<key>forkTestOptions_</key>
|
||||
<string>NSWindow</string>
|
||||
<key>window_</key>
|
||||
<string>NSWindow</string>
|
||||
</dict>
|
||||
<key>SUPERCLASS</key>
|
||||
<string>NSObject</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CLASS</key>
|
||||
<string>FirstResponder</string>
|
||||
<key>LANGUAGE</key>
|
||||
<string>ObjC</string>
|
||||
<key>SUPERCLASS</key>
|
||||
<string>NSObject</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>IBVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IBFramework Version</key>
|
||||
<string>670</string>
|
||||
<key>IBLastKnownRelativeProjectPath</key>
|
||||
<string>../GoogleBreakpadTest.xcodeproj</string>
|
||||
<key>IBOldestOS</key>
|
||||
<integer>5</integer>
|
||||
<key>IBOpenObjects</key>
|
||||
<array>
|
||||
<integer>221</integer>
|
||||
<integer>29</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>IBSystem Version</key>
|
||||
<string>9F33</string>
|
||||
<key>targetFramework</key>
|
||||
<string>IBCocoaFramework</string>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
@@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>bomb</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.Google.BreakpadTest</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>BreakpadProductDisplay</key>
|
||||
<string>Breakpad Tester</string>
|
||||
<key>BreakpadProduct</key>
|
||||
<string>Breakpad_Tester</string>
|
||||
<key>BreakpadVersion</key>
|
||||
<string>1.2.3.4</string>
|
||||
<key>BreakpadReportInterval</key>
|
||||
<string>10</string>
|
||||
<key>BreakpadSkipConfirm</key>
|
||||
<string>NO</string>
|
||||
<key>BreakpadSendAndExit</key>
|
||||
<string>YES</string>
|
||||
<key>BreakpadRequestEmail</key>
|
||||
<string>YES</string>
|
||||
<key>BreakpadRequestComments</key>
|
||||
<string>YES</string>
|
||||
<key>BreakpadVendor</key>
|
||||
<string>Foo Bar Corp, Incorporated, LTD, LLC</string>
|
||||
<key>BreakpadServerParameters</key>
|
||||
<dict>
|
||||
<key>Param1</key>
|
||||
<string>Value1</string>
|
||||
<key>Param2</key>
|
||||
<string>Value2</string>
|
||||
</dict>
|
||||
<key>LSUIElement</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface TestClass : NSObject {
|
||||
}
|
||||
|
||||
- (void)wait;
|
||||
|
||||
@end
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
return NSApplicationMain(argc, (const char **) argv);
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// BreakpadFramework_Test.mm
|
||||
// Test case file for Breakpad.h/mm.
|
||||
//
|
||||
|
||||
#import "GTMSenTestCase.h"
|
||||
#import "Breakpad.h"
|
||||
|
||||
#include <mach/mach.h>
|
||||
|
||||
@interface BreakpadFramework_Test : GTMTestCase {
|
||||
@private
|
||||
int last_exception_code_;
|
||||
int last_exception_type_;
|
||||
mach_port_t last_exception_thread_;
|
||||
// We're not using Obj-C BOOL because we need to interop with
|
||||
// Breakpad's callback.
|
||||
bool shouldHandleException_;
|
||||
}
|
||||
|
||||
// This method is used by a callback used by test cases to determine
|
||||
// whether to return true or false to Breakpad when handling an
|
||||
// exception.
|
||||
- (bool)shouldHandleException;
|
||||
// This method returns a minimal dictionary that has what
|
||||
// Breakpad needs to initialize.
|
||||
- (NSMutableDictionary *)breakpadInitializationDictionary;
|
||||
// This method is used by the exception handling callback
|
||||
// to communicate to test cases the properites of the last
|
||||
// exception.
|
||||
- (void)setLastExceptionType:(int)type andCode:(int)code
|
||||
andThread:(mach_port_t)thread;
|
||||
@end
|
||||
|
||||
// Callback for Breakpad exceptions
|
||||
bool myBreakpadCallback(int exception_type,
|
||||
int exception_code,
|
||||
mach_port_t crashing_thread,
|
||||
void *context);
|
||||
|
||||
bool myBreakpadCallback(int exception_type,
|
||||
int exception_code,
|
||||
mach_port_t crashing_thread,
|
||||
void *context) {
|
||||
BreakpadFramework_Test *testCaseClass =
|
||||
(BreakpadFramework_Test *)context;
|
||||
[testCaseClass setLastExceptionType:exception_type
|
||||
andCode:exception_code
|
||||
andThread:crashing_thread];
|
||||
bool shouldHandleException =
|
||||
[testCaseClass shouldHandleException];
|
||||
NSLog(@"Callback returning %d", shouldHandleException);
|
||||
return shouldHandleException;
|
||||
}
|
||||
const int kNoLastExceptionCode = -1;
|
||||
const int kNoLastExceptionType = -1;
|
||||
const mach_port_t kNoLastExceptionThread = MACH_PORT_NULL;
|
||||
|
||||
@implementation BreakpadFramework_Test
|
||||
- (void) initializeExceptionStateVariables {
|
||||
last_exception_code_ = kNoLastExceptionCode;
|
||||
last_exception_type_ = kNoLastExceptionType;
|
||||
last_exception_thread_ = kNoLastExceptionThread;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)breakpadInitializationDictionary {
|
||||
NSMutableDictionary *breakpadParams =
|
||||
[NSMutableDictionary dictionaryWithCapacity:3];
|
||||
|
||||
[breakpadParams setObject:@"UnitTests" forKey:@BREAKPAD_PRODUCT];
|
||||
[breakpadParams setObject:@"1.0" forKey:@BREAKPAD_VERSION];
|
||||
[breakpadParams setObject:@"http://staging" forKey:@BREAKPAD_URL];
|
||||
return breakpadParams;
|
||||
}
|
||||
|
||||
- (bool)shouldHandleException {
|
||||
return shouldHandleException_;
|
||||
}
|
||||
|
||||
- (void)setLastExceptionType:(int)type
|
||||
andCode:(int)code
|
||||
andThread:(mach_port_t)thread {
|
||||
last_exception_type_ = type;
|
||||
last_exception_code_ = code;
|
||||
last_exception_thread_ = thread;
|
||||
}
|
||||
|
||||
// Test that the parameters mark required actually enable Breakpad to
|
||||
// be initialized.
|
||||
- (void)testBreakpadInstantiationWithRequiredParameters {
|
||||
BreakpadRef b = BreakpadCreate([self breakpadInitializationDictionary]);
|
||||
STAssertNotNULL(b, @"BreakpadCreate failed with required parameters");
|
||||
BreakpadRelease(b);
|
||||
}
|
||||
|
||||
// Test that Breakpad fails to initialize cleanly when required
|
||||
// parameters are not present.
|
||||
- (void)testBreakpadInstantiationWithoutRequiredParameters {
|
||||
NSMutableDictionary *breakpadDictionary =
|
||||
[self breakpadInitializationDictionary];
|
||||
|
||||
// Skip setting version, so that BreakpadCreate fails.
|
||||
[breakpadDictionary removeObjectForKey:@BREAKPAD_VERSION];
|
||||
BreakpadRef b = BreakpadCreate(breakpadDictionary);
|
||||
STAssertNULL(b, @"BreakpadCreate did not fail when missing a required"
|
||||
" parameter!");
|
||||
|
||||
breakpadDictionary = [self breakpadInitializationDictionary];
|
||||
// Now test with no product
|
||||
[breakpadDictionary removeObjectForKey:@BREAKPAD_PRODUCT];
|
||||
b = BreakpadCreate(breakpadDictionary);
|
||||
STAssertNULL(b, @"BreakpadCreate did not fail when missing a required"
|
||||
" parameter!");
|
||||
|
||||
breakpadDictionary = [self breakpadInitializationDictionary];
|
||||
// Now test with no URL
|
||||
[breakpadDictionary removeObjectForKey:@BREAKPAD_URL];
|
||||
b = BreakpadCreate(breakpadDictionary);
|
||||
STAssertNULL(b, @"BreakpadCreate did not fail when missing a required"
|
||||
" parameter!");
|
||||
BreakpadRelease(b);
|
||||
}
|
||||
|
||||
// Test to ensure that when we call BreakpadAddUploadParameter,
|
||||
// it's added to the dictionary correctly(this test depends on
|
||||
// some internal details of Breakpad, namely, the special prefix
|
||||
// that it uses to figure out which key/value pairs to upload).
|
||||
- (void)testAddingBreakpadServerVariable {
|
||||
NSMutableDictionary *breakpadDictionary =
|
||||
[self breakpadInitializationDictionary];
|
||||
|
||||
BreakpadRef b = BreakpadCreate(breakpadDictionary);
|
||||
STAssertNotNULL(b, @"BreakpadCreate failed with valid dictionary!");
|
||||
|
||||
BreakpadAddUploadParameter(b,
|
||||
@"key",
|
||||
@"value");
|
||||
|
||||
// Test that it did not add the key/value directly, e.g. without
|
||||
// prepending the key with the prefix.
|
||||
STAssertNil(BreakpadKeyValue(b, @"key"),
|
||||
@"AddUploadParameter added key directly to dictionary"
|
||||
" instead of prepending it!");
|
||||
|
||||
NSString *prependedKeyname =
|
||||
[@BREAKPAD_SERVER_PARAMETER_PREFIX stringByAppendingString:@"key"];
|
||||
|
||||
STAssertEqualStrings(BreakpadKeyValue(b, prependedKeyname),
|
||||
@"value",
|
||||
@"Calling BreakpadAddUploadParameter did not prepend "
|
||||
"key name");
|
||||
BreakpadRelease(b);
|
||||
}
|
||||
|
||||
// Test that when we do on-demand minidump generation,
|
||||
// the exception code/type/thread are set properly.
|
||||
- (void)testFilterCallbackReturnsFalse {
|
||||
NSMutableDictionary *breakpadDictionary =
|
||||
[self breakpadInitializationDictionary];
|
||||
|
||||
BreakpadRef b = BreakpadCreate(breakpadDictionary);
|
||||
STAssertNotNULL(b, @"BreakpadCreate failed with valid dictionary!");
|
||||
BreakpadSetFilterCallback(b, &myBreakpadCallback, self);
|
||||
|
||||
// This causes the callback to return false, meaning
|
||||
// Breakpad won't take the exception
|
||||
shouldHandleException_ = false;
|
||||
|
||||
[self initializeExceptionStateVariables];
|
||||
STAssertEquals(last_exception_type_, kNoLastExceptionType,
|
||||
@"Last exception type not initialized correctly.");
|
||||
STAssertEquals(last_exception_code_, kNoLastExceptionCode,
|
||||
@"Last exception code not initialized correctly.");
|
||||
STAssertEquals(last_exception_thread_, kNoLastExceptionThread,
|
||||
@"Last exception thread is not initialized correctly.");
|
||||
|
||||
// Cause Breakpad's exception handler to be invoked.
|
||||
BreakpadGenerateAndSendReport(b);
|
||||
|
||||
STAssertEquals(last_exception_type_, 0,
|
||||
@"Last exception type is not 0 for on demand");
|
||||
STAssertEquals(last_exception_code_, 0,
|
||||
@"Last exception code is not 0 for on demand");
|
||||
STAssertEquals(last_exception_thread_, mach_thread_self(),
|
||||
@"Last exception thread is not mach_thread_self() "
|
||||
"for on demand");
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <GTMSenTestCase.h>
|
||||
#import "SimpleStringDictionary.h"
|
||||
|
||||
@interface SimpleStringDictionaryTest : GTMTestCase {
|
||||
|
||||
}
|
||||
|
||||
- (void)testKeyValueEntry;
|
||||
- (void)testSimpleStringDictionary;
|
||||
- (void)testSimpleStringDictionaryIterator;
|
||||
@end
|
||||
@@ -1,243 +0,0 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import "SimpleStringDictionaryTest.h"
|
||||
#import "SimpleStringDictionary.h"
|
||||
|
||||
using google_breakpad::KeyValueEntry;
|
||||
using google_breakpad::SimpleStringDictionary;
|
||||
using google_breakpad::SimpleStringDictionaryIterator;
|
||||
|
||||
@implementation SimpleStringDictionaryTest
|
||||
|
||||
//==============================================================================
|
||||
- (void)testKeyValueEntry {
|
||||
KeyValueEntry entry;
|
||||
|
||||
// Verify that initial state is correct
|
||||
STAssertFalse(entry.IsActive(), @"Initial key value entry is active!");
|
||||
STAssertEquals(strlen(entry.GetKey()), (size_t)0, @"Empty key value did not "
|
||||
@"have length 0");
|
||||
STAssertEquals(strlen(entry.GetValue()), (size_t)0, @"Empty key value did not "
|
||||
@"have length 0");
|
||||
|
||||
// Try setting a key/value and then verify
|
||||
entry.SetKeyValue("key1", "value1");
|
||||
STAssertEqualCStrings(entry.GetKey(), "key1", @"key was not equal to key1");
|
||||
STAssertEqualCStrings(entry.GetValue(), "value1", @"value was not equal");
|
||||
|
||||
// Try setting a new value
|
||||
entry.SetValue("value3");
|
||||
|
||||
// Make sure the new value took
|
||||
STAssertEqualCStrings(entry.GetValue(), "value3", @"value was not equal");
|
||||
|
||||
// Make sure the key didn't change
|
||||
STAssertEqualCStrings(entry.GetKey(), "key1", @"key changed after setting "
|
||||
@"value!");
|
||||
|
||||
// Try setting a new key/value and then verify
|
||||
entry.SetKeyValue("key2", "value2");
|
||||
STAssertEqualCStrings(entry.GetKey(), "key2", @"New key was not equal to "
|
||||
@"key2");
|
||||
STAssertEqualCStrings(entry.GetValue(), "value2", @"New value was not equal "
|
||||
@"to value2");
|
||||
|
||||
// Clear the entry and verify the key and value are empty strings
|
||||
entry.Clear();
|
||||
STAssertFalse(entry.IsActive(), @"Key value clear did not clear object");
|
||||
STAssertEquals(strlen(entry.GetKey()), (size_t)0, @"Length of cleared key "
|
||||
@"was not 0");
|
||||
STAssertEquals(strlen(entry.GetValue()), (size_t)0, @"Length of cleared "
|
||||
@"value was not 0!");
|
||||
}
|
||||
|
||||
- (void)testEmptyKeyValueCombos {
|
||||
KeyValueEntry entry;
|
||||
entry.SetKeyValue(NULL, NULL);
|
||||
STAssertEqualCStrings(entry.GetKey(), "", @"Setting NULL key did not return "
|
||||
@"empty key!");
|
||||
STAssertEqualCStrings(entry.GetValue(), "", @"Setting NULL value did not "
|
||||
@"set empty string value!");
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
- (void)testSimpleStringDictionary {
|
||||
// Make a new dictionary
|
||||
SimpleStringDictionary *dict = new SimpleStringDictionary();
|
||||
STAssertTrue(dict != NULL, nil);
|
||||
|
||||
// try passing in NULL for key
|
||||
//dict->SetKeyValue(NULL, "bad"); // causes assert() to fire
|
||||
|
||||
// Set three distinct values on three keys
|
||||
dict->SetKeyValue("key1", "value1");
|
||||
dict->SetKeyValue("key2", "value2");
|
||||
dict->SetKeyValue("key3", "value3");
|
||||
|
||||
STAssertTrue(!strcmp(dict->GetValueForKey("key1"), "value1"), nil);
|
||||
STAssertTrue(!strcmp(dict->GetValueForKey("key2"), "value2"), nil);
|
||||
STAssertTrue(!strcmp(dict->GetValueForKey("key3"), "value3"), nil);
|
||||
STAssertEquals(dict->GetCount(), 3, @"GetCount did not return 3");
|
||||
// try an unknown key
|
||||
STAssertTrue(dict->GetValueForKey("key4") == NULL, nil);
|
||||
|
||||
// try a NULL key
|
||||
//STAssertTrue(dict->GetValueForKey(NULL) == NULL, nil); // asserts
|
||||
|
||||
// Remove a key
|
||||
dict->RemoveKey("key3");
|
||||
|
||||
// Now make sure it's not there anymore
|
||||
STAssertTrue(dict->GetValueForKey("key3") == NULL, nil);
|
||||
|
||||
// Remove a NULL key
|
||||
//dict->RemoveKey(NULL); // will cause assert() to fire
|
||||
|
||||
// Remove by setting value to NULL
|
||||
dict->SetKeyValue("key2", NULL);
|
||||
|
||||
// Now make sure it's not there anymore
|
||||
STAssertTrue(dict->GetValueForKey("key2") == NULL, nil);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// The idea behind this test is to add a bunch of values to the dictionary,
|
||||
// remove some in the middle, then add a few more in. We then create a
|
||||
// SimpleStringDictionaryIterator and iterate through the dictionary, taking
|
||||
// note of the key/value pairs we see. We then verify that it iterates
|
||||
// through exactly the number of key/value pairs we expect, and that they
|
||||
// match one-for-one with what we would expect. In all cases we're setting
|
||||
// key value pairs of the form:
|
||||
//
|
||||
// key<n>/value<n> (like key0/value0, key17,value17, etc.)
|
||||
//
|
||||
- (void)testSimpleStringDictionaryIterator {
|
||||
SimpleStringDictionary *dict = new SimpleStringDictionary();
|
||||
STAssertTrue(dict != NULL, nil);
|
||||
|
||||
char key[KeyValueEntry::MAX_STRING_STORAGE_SIZE];
|
||||
char value[KeyValueEntry::MAX_STRING_STORAGE_SIZE];
|
||||
|
||||
const int kDictionaryCapacity = SimpleStringDictionary::MAX_NUM_ENTRIES;
|
||||
const int kPartitionIndex = kDictionaryCapacity - 5;
|
||||
|
||||
// We assume at least this size in the tests below
|
||||
STAssertTrue(kDictionaryCapacity >= 64, nil);
|
||||
|
||||
// We'll keep track of the number of key/value pairs we think should
|
||||
// be in the dictionary
|
||||
int expectedDictionarySize = 0;
|
||||
|
||||
// Set a bunch of key/value pairs like key0/value0, key1/value1, ...
|
||||
for (int i = 0; i < kPartitionIndex; ++i) {
|
||||
sprintf(key, "key%d", i);
|
||||
sprintf(value, "value%d", i);
|
||||
dict->SetKeyValue(key, value);
|
||||
}
|
||||
expectedDictionarySize = kPartitionIndex;
|
||||
|
||||
// set a couple of the keys twice (with the same value) - should be nop
|
||||
dict->SetKeyValue("key2", "value2");
|
||||
dict->SetKeyValue("key4", "value4");
|
||||
dict->SetKeyValue("key15", "value15");
|
||||
|
||||
// Remove some random elements in the middle
|
||||
dict->RemoveKey("key7");
|
||||
dict->RemoveKey("key18");
|
||||
dict->RemoveKey("key23");
|
||||
dict->RemoveKey("key31");
|
||||
expectedDictionarySize -= 4; // we just removed four key/value pairs
|
||||
|
||||
// Set some more key/value pairs like key59/value59, key60/value60, ...
|
||||
for (int i = kPartitionIndex; i < kDictionaryCapacity; ++i) {
|
||||
sprintf(key, "key%d", i);
|
||||
sprintf(value, "value%d", i);
|
||||
dict->SetKeyValue(key, value);
|
||||
}
|
||||
expectedDictionarySize += kDictionaryCapacity - kPartitionIndex;
|
||||
|
||||
// Now create an iterator on the dictionary
|
||||
SimpleStringDictionaryIterator iter(*dict);
|
||||
|
||||
// We then verify that it iterates through exactly the number of
|
||||
// key/value pairs we expect, and that they match one-for-one with what we
|
||||
// would expect. The ordering of the iteration does not matter...
|
||||
|
||||
// used to keep track of number of occurrences found for key/value pairs
|
||||
int count[kDictionaryCapacity];
|
||||
memset(count, 0, sizeof(count));
|
||||
|
||||
int totalCount = 0;
|
||||
|
||||
const KeyValueEntry *entry;
|
||||
|
||||
while ((entry = iter.Next())) {
|
||||
totalCount++;
|
||||
|
||||
// Extract keyNumber from a string of the form key<keyNumber>
|
||||
int keyNumber;
|
||||
sscanf(entry->GetKey(), "key%d", &keyNumber);
|
||||
|
||||
// Extract valueNumber from a string of the form value<valueNumber>
|
||||
int valueNumber;
|
||||
sscanf(entry->GetValue(), "value%d", &valueNumber);
|
||||
|
||||
// The value number should equal the key number since that's how we set them
|
||||
STAssertTrue(keyNumber == valueNumber, nil);
|
||||
|
||||
// Key and value numbers should be in proper range:
|
||||
// 0 <= keyNumber < kDictionaryCapacity
|
||||
bool isKeyInGoodRange =
|
||||
(keyNumber >= 0 && keyNumber < kDictionaryCapacity);
|
||||
bool isValueInGoodRange =
|
||||
(valueNumber >= 0 && valueNumber < kDictionaryCapacity);
|
||||
STAssertTrue(isKeyInGoodRange, nil);
|
||||
STAssertTrue(isValueInGoodRange, nil);
|
||||
|
||||
if (isKeyInGoodRange && isValueInGoodRange) {
|
||||
++count[keyNumber];
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure each of the key/value pairs showed up exactly one time, except
|
||||
// for the ones which we removed.
|
||||
for (int i = 0; i < kDictionaryCapacity; ++i) {
|
||||
// Skip over key7, key18, key23, and key31, since we removed them
|
||||
if (!(i == 7 || i == 18 || i == 23 || i == 31)) {
|
||||
STAssertTrue(count[i] == 1, nil);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the number of iterations matches the expected dictionary size.
|
||||
STAssertTrue(totalCount == expectedDictionarySize, nil);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -37,8 +37,6 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "client/minidump_file_writer-inl.h"
|
||||
#include "common/string_conversion.h"
|
||||
|
||||
@@ -55,11 +53,7 @@ MinidumpFileWriter::~MinidumpFileWriter() {
|
||||
|
||||
bool MinidumpFileWriter::Open(const char *path) {
|
||||
assert(file_ == -1);
|
||||
#if __linux__
|
||||
file_ = sys_open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
||||
#else
|
||||
file_ = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
||||
#endif
|
||||
|
||||
return file_ != -1;
|
||||
}
|
||||
@@ -69,11 +63,7 @@ bool MinidumpFileWriter::Close() {
|
||||
|
||||
if (file_ != -1) {
|
||||
ftruncate(file_, position_);
|
||||
#if __linux__
|
||||
result = (sys_close(file_) == 0);
|
||||
#else
|
||||
result = (close(file_) == 0);
|
||||
#endif
|
||||
file_ = -1;
|
||||
}
|
||||
|
||||
@@ -237,16 +227,9 @@ bool MinidumpFileWriter::Copy(MDRVA position, const void *src, ssize_t size) {
|
||||
return false;
|
||||
|
||||
// Seek and write the data
|
||||
#if __linux__
|
||||
if (sys_lseek(file_, position, SEEK_SET) == static_cast<off_t>(position)) {
|
||||
if (sys_write(file_, src, size) == size) {
|
||||
#else
|
||||
if (lseek(file_, position, SEEK_SET) == static_cast<off_t>(position)) {
|
||||
if (write(file_, src, size) == size) {
|
||||
#endif
|
||||
if (lseek(file_, position, SEEK_SET) == static_cast<off_t>(position))
|
||||
if (write(file_, src, size) == size)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -126,22 +126,17 @@ static bool CompareFile(const char *path) {
|
||||
0x00000007, 0x06000007, 0x00000008, 0x07000008, 0x00000009, 0x08000009,
|
||||
0x0000000a, 0x0900000a, 0x0000000b, 0x00000000
|
||||
#else
|
||||
0x0000beef, 0x0000001e, 0x00000018, 0x00000020,
|
||||
0x00000038, 0x00000000, 0x00000018, 0x00690046,
|
||||
0x00730072, 0x00200074, 0x00740053, 0x00690072,
|
||||
0x0067006e, 0x00000000, 0x0000001a, 0x00650053,
|
||||
0x006f0063, 0x0064006e, 0x00530020, 0x00720074,
|
||||
0x006e0069, 0x00000067, 0x00011e00, 0x00000002,
|
||||
0x00021e01, 0x00000003, 0x00031e02, 0x00000004,
|
||||
0x00041e03, 0x00000005, 0x00051e04, 0x00000006,
|
||||
0x00061e05, 0x00000007, 0x00071e06, 0x00000008,
|
||||
0x00081e07, 0x00000009, 0x00091e08, 0x0000000a,
|
||||
0x000a1e09, 0x0000000b, 0x0000000a, 0x00011c00,
|
||||
0x00000002, 0x00021c01, 0x00000003, 0x00031c02,
|
||||
0x00000004, 0x00041c03, 0x00000005, 0x00051c04,
|
||||
0x00000006, 0x00061c05, 0x00000007, 0x00071c06,
|
||||
0x00000008, 0x00081c07, 0x00000009, 0x00091c08,
|
||||
0x0000000a, 0x000a1c09, 0x0000000b, 0x00000000,
|
||||
0x0000beef, 0x0000001e, 0x00000018, 0x00000020, 0x00000038, 0x00000000,
|
||||
0x00000018, 0x00690046, 0x00730072, 0x00200074, 0x00740053, 0x00690072,
|
||||
0x0067006e, 0x00000000, 0x0000001a, 0x00650053, 0x006f0063, 0x0064006e,
|
||||
0x00530020, 0x00720074, 0x006e0069, 0x00000067, 0x0001da00, 0x00000002,
|
||||
0x0002da01, 0x00000003, 0x0003da02, 0x00000004, 0x0004da03, 0x00000005,
|
||||
0x0005da04, 0x00000006, 0x0006da05, 0x00000007, 0x0007da06, 0x00000008,
|
||||
0x0008da07, 0x00000009, 0x0009da08, 0x0000000a, 0x000ada09, 0x0000000b,
|
||||
0x0000000a, 0x00018700, 0x00000002, 0x00028701, 0x00000003, 0x00038702,
|
||||
0x00000004, 0x00048703, 0x00000005, 0x00058704, 0x00000006, 0x00068705,
|
||||
0x00000007, 0x00078706, 0x00000008, 0x00088707, 0x00000009, 0x00098708,
|
||||
0x0000000a, 0x000a8709, 0x0000000b, 0x00000000,
|
||||
#endif
|
||||
};
|
||||
unsigned int expected_byte_count = sizeof(expected);
|
||||
@@ -150,17 +145,6 @@ static bool CompareFile(const char *path) {
|
||||
ASSERT_NE(fd, -1);
|
||||
ASSERT_TRUE(buffer);
|
||||
ASSERT_EQ(read(fd, buffer, expected_byte_count), expected_byte_count);
|
||||
|
||||
char *b1, *b2;
|
||||
b1 = (char*)buffer;
|
||||
b2 = (char*)expected;
|
||||
while (*b1 == *b2) {
|
||||
b1++;
|
||||
b2++;
|
||||
}
|
||||
|
||||
printf("%d\n",b1 - (char*)buffer);
|
||||
|
||||
ASSERT_EQ(memcmp(buffer, expected, expected_byte_count), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -42,9 +42,12 @@
|
||||
#include "common/solaris/guid_creator.h"
|
||||
#include "common/solaris/message_output.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
#include "processor/scoped_ptr.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
static const int kStackSize = 1024 * 1024;
|
||||
|
||||
// Signals that we are interested.
|
||||
static const int kSigTable[] = {
|
||||
SIGSEGV,
|
||||
@@ -65,9 +68,10 @@ ExceptionHandler::ExceptionHandler(const string &dump_path,
|
||||
void *callback_context,
|
||||
bool install_handler)
|
||||
: filter_(filter),
|
||||
handler_thread_(0),
|
||||
handler_return_value_(false),
|
||||
callback_(callback),
|
||||
callback_context_(callback_context),
|
||||
dump_path_(),
|
||||
installed_handler_(install_handler) {
|
||||
set_dump_path(dump_path);
|
||||
|
||||
@@ -75,6 +79,19 @@ ExceptionHandler::ExceptionHandler(const string &dump_path,
|
||||
SetupHandler();
|
||||
}
|
||||
|
||||
sem_init(&handler_start_semaphore_, 0, 0);
|
||||
sem_init(&handler_finish_semaphore_, 0, 0);
|
||||
pthread_attr_t attr;
|
||||
scoped_array<char> thread_stack;
|
||||
|
||||
pthread_attr_init(&attr);
|
||||
thread_stack.reset(new char[kStackSize]);
|
||||
pthread_attr_setstackaddr(&attr, thread_stack.get());
|
||||
pthread_attr_setstacksize(&attr, kStackSize);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
pthread_create(&handler_thread_, &attr, ExceptionHandlerThreadMain, this);
|
||||
pthread_attr_destroy(&attr);
|
||||
|
||||
if (install_handler) {
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
|
||||
@@ -108,11 +125,34 @@ ExceptionHandler::~ExceptionHandler() {
|
||||
delete handler_stack_;
|
||||
handler_stack_ = NULL;
|
||||
}
|
||||
pthread_exit((void *)handler_thread_);
|
||||
sem_destroy(&handler_start_semaphore_);
|
||||
sem_destroy(&handler_finish_semaphore_);
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
}
|
||||
|
||||
// static
|
||||
void* ExceptionHandler::ExceptionHandlerThreadMain(void *lpParameter) {
|
||||
ExceptionHandler *self = reinterpret_cast<ExceptionHandler *>(lpParameter);
|
||||
assert(self);
|
||||
|
||||
while (true) {
|
||||
if (!sem_wait(&(self->handler_start_semaphore_))) {
|
||||
// Perform the requested action.
|
||||
self->handler_return_value_ = self->InternalWriteMinidump();
|
||||
|
||||
// Allow the requesting thread to proceed.
|
||||
sem_post(&(self->handler_finish_semaphore_));
|
||||
}
|
||||
}
|
||||
|
||||
// Not reached. This thread will be terminated by ExceptionHandler's
|
||||
// destructor.
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ExceptionHandler::WriteMinidump() {
|
||||
return InternalWriteMinidump(0, 0, NULL);
|
||||
return WriteMinidumpOnHandlerThread(0);
|
||||
}
|
||||
|
||||
// static
|
||||
@@ -121,7 +161,7 @@ bool ExceptionHandler::WriteMinidump(const string &dump_path,
|
||||
void *callback_context) {
|
||||
ExceptionHandler handler(dump_path, NULL, callback,
|
||||
callback_context, false);
|
||||
return handler.InternalWriteMinidump(0, 0, NULL);
|
||||
return handler.WriteMinidumpOnHandlerThread(0);
|
||||
}
|
||||
|
||||
void ExceptionHandler::SetupHandler() {
|
||||
@@ -164,22 +204,22 @@ void ExceptionHandler::TeardownAllHandlers() {
|
||||
}
|
||||
}
|
||||
|
||||
bool ExceptionHandler::WriteMinidumpOnHandlerThread(int signo) {
|
||||
// Set up data to be passed in to the handler thread.
|
||||
signo_ = signo;
|
||||
|
||||
// This causes the handler thread to call InternalWriteMinidump.
|
||||
sem_post(&handler_start_semaphore_);
|
||||
|
||||
// Wait until InternalWriteMinidump is done and collect its return value.
|
||||
sem_wait(&handler_finish_semaphore_);
|
||||
bool status = handler_return_value_;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// static
|
||||
void ExceptionHandler::HandleException(int signo) {
|
||||
//void ExceptionHandler::HandleException(int signo, siginfo_t *sip, ucontext_t *sig_ctx) {
|
||||
// The context information about the signal is put on the stack of
|
||||
// the signal handler frame as value parameter. For some reasons, the
|
||||
// prototype of the handler doesn't declare this information as parameter, we
|
||||
// will do it by hand. The stack layout for a signal handler frame is here:
|
||||
// http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/lib/libproc/common/Pstack.c#81
|
||||
//
|
||||
// However, if we are being called by another signal handler passing the
|
||||
// signal up the chain, then we may not have this random extra parameter,
|
||||
// so we may have to walk the stack to find it. We do the actual work
|
||||
// on another thread, where it's a little safer, but we want the ebp
|
||||
// from this frame to find it.
|
||||
uintptr_t current_ebp = (uintptr_t)_getfp();
|
||||
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
ExceptionHandler *current_handler =
|
||||
handler_stack_->at(handler_stack_->size() - ++handler_stack_index_);
|
||||
@@ -187,10 +227,7 @@ void ExceptionHandler::HandleException(int signo) {
|
||||
|
||||
// Restore original handler.
|
||||
current_handler->TeardownHandler(signo);
|
||||
|
||||
ucontext_t *sig_ctx = NULL;
|
||||
if (current_handler->InternalWriteMinidump(signo, current_ebp, &sig_ctx)) {
|
||||
// if (current_handler->InternalWriteMinidump(signo, &sig_ctx)) {
|
||||
if (current_handler->WriteMinidumpOnHandlerThread(signo)) {
|
||||
// Fully handled this exception, safe to exit.
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
@@ -216,9 +253,7 @@ void ExceptionHandler::HandleException(int signo) {
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
}
|
||||
|
||||
bool ExceptionHandler::InternalWriteMinidump(int signo,
|
||||
uintptr_t sighandler_ebp,
|
||||
ucontext_t **sig_ctx) {
|
||||
bool ExceptionHandler::InternalWriteMinidump() {
|
||||
if (filter_ && !filter_(callback_context_))
|
||||
return false;
|
||||
|
||||
@@ -242,8 +277,7 @@ bool ExceptionHandler::InternalWriteMinidump(int signo,
|
||||
print_message1(2, "HandleException: failed to block signals.\n");
|
||||
}
|
||||
|
||||
success = minidump_generator_.WriteMinidumpToFile(
|
||||
minidump_path, signo, sighandler_ebp, sig_ctx);
|
||||
success = minidump_generator_.WriteMinidumpToFile(minidump_path, signo_);
|
||||
|
||||
// Unblock the signals.
|
||||
if (blocked)
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
#ifndef CLIENT_SOLARIS_HANDLER_EXCEPTION_HANDLER_H__
|
||||
#define CLIENT_SOLARIS_HANDLER_EXCEPTION_HANDLER_H__
|
||||
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -116,11 +119,9 @@ class ExceptionHandler {
|
||||
bool install_handler);
|
||||
~ExceptionHandler();
|
||||
|
||||
// Get and Set the minidump path.
|
||||
string dump_path() const { return dump_path_; }
|
||||
// Set the minidump path.
|
||||
void set_dump_path(const string &dump_path) {
|
||||
dump_path_ = dump_path;
|
||||
dump_path_c_ = dump_path_.c_str();
|
||||
dump_path_c_ = dump_path.c_str();
|
||||
}
|
||||
|
||||
// Writes a minidump immediately. This can be used to capture the
|
||||
@@ -149,25 +150,36 @@ class ExceptionHandler {
|
||||
// Signal handler.
|
||||
static void HandleException(int signo);
|
||||
|
||||
// Trigger the call to InternalWriteMinidump and wait for the return value.
|
||||
bool WriteMinidumpOnHandlerThread(int signo);
|
||||
|
||||
// Write all the information to the dump file.
|
||||
// If called from a signal handler, sighandler_ebp is the ebp of
|
||||
// that signal handler's frame, and sig_ctx is an out parameter
|
||||
// that will be set to point at the ucontext_t that was placed
|
||||
// on the stack by the kernel. You can pass zero and NULL
|
||||
// for the second and third parameters if you are not calling
|
||||
// this from a signal handler.
|
||||
bool InternalWriteMinidump(int signo, uintptr_t sighandler_ebp,
|
||||
ucontext_t **sig_ctx);
|
||||
bool InternalWriteMinidump();
|
||||
|
||||
private:
|
||||
// Signal number when crash happed. Can be 0 if this is a requested dump.
|
||||
int signo_;
|
||||
|
||||
// The exception handler thread.
|
||||
pthread_t handler_thread_;
|
||||
|
||||
// Semaphores used to move exception handling between the exception thread
|
||||
// and the handler thread. handler_start_semaphore_ is signalled by the
|
||||
// exception thread to wake up the handler thread when an exception occurs.
|
||||
// handler_finish_semaphore_ is signalled by the handler thread to wake up
|
||||
// the exception thread when handling is complete.
|
||||
sem_t handler_start_semaphore_;
|
||||
sem_t handler_finish_semaphore_;
|
||||
|
||||
// The return value of the handler, passed from the handler thread back to
|
||||
// the requesting thread.
|
||||
bool handler_return_value_;
|
||||
|
||||
// The callbacks before and after writing the dump file.
|
||||
FilterCallback filter_;
|
||||
MinidumpCallback callback_;
|
||||
void *callback_context_;
|
||||
|
||||
// The directory in which a minidump will be written, set by the dump_path
|
||||
// argument to the constructor, or set_dump_path.
|
||||
string dump_path_;
|
||||
// C style dump path. Keep this when setting dump path, since calling
|
||||
// c_str() of std::string when crashing may not be safe.
|
||||
const char *dump_path_c_;
|
||||
|
||||
@@ -30,11 +30,12 @@
|
||||
// Author: Alfred Peng
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/frame.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <sys/wait.h>
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdlib>
|
||||
@@ -44,98 +45,21 @@
|
||||
#include "client/minidump_file_writer-inl.h"
|
||||
#include "common/solaris/file_id.h"
|
||||
|
||||
namespace {
|
||||
namespace google_breakpad {
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
// Argument for the writer function.
|
||||
struct WriterArgument {
|
||||
MinidumpFileWriter *minidump_writer;
|
||||
|
||||
// Pid of the lwp who called WriteMinidumpToFile
|
||||
int requester_pid;
|
||||
|
||||
// The stack bottom of the lwp which caused the dump.
|
||||
// Mainly used to find the lwp id of the crashed lwp since signal
|
||||
// handler may not be called in the lwp who caused it.
|
||||
uintptr_t crashed_stack_bottom;
|
||||
|
||||
// Id of the crashing lwp.
|
||||
int crashed_lwpid;
|
||||
|
||||
// Signal number when crash happened. Can be 0 if this is a requested dump.
|
||||
int signo;
|
||||
|
||||
// The ebp of the signal handler frame on x86. Can be 0 if this is a
|
||||
// requested dump.
|
||||
uintptr_t sighandler_ebp;
|
||||
|
||||
// User context when crash happens. Can be NULL if this is a requested dump.
|
||||
// This is actually an out parameter, but it will be filled in at the start
|
||||
// of the writer LWP.
|
||||
ucontext_t *sig_ctx;
|
||||
|
||||
// Used to get information about the lwps.
|
||||
SolarisLwp *lwp_lister;
|
||||
};
|
||||
|
||||
// Holding context information for the callback of finding the crashing lwp.
|
||||
struct FindCrashLwpContext {
|
||||
const SolarisLwp *lwp_lister;
|
||||
uintptr_t crashing_stack_bottom;
|
||||
int crashing_lwpid;
|
||||
|
||||
FindCrashLwpContext() :
|
||||
lwp_lister(NULL),
|
||||
crashing_stack_bottom(0UL),
|
||||
crashing_lwpid(-1) {
|
||||
}
|
||||
};
|
||||
|
||||
// Callback for list lwps.
|
||||
// It will compare the stack bottom of the provided lwp with the stack
|
||||
// bottom of the crashed lwp, it they are eqaul, this lwp is the one
|
||||
// who crashed.
|
||||
bool IsLwpCrashedCallback(lwpstatus_t *lsp, void *context) {
|
||||
FindCrashLwpContext *crashing_context =
|
||||
static_cast<FindCrashLwpContext *>(context);
|
||||
const SolarisLwp *lwp_lister = crashing_context->lwp_lister;
|
||||
const prgregset_t *gregs = &(lsp->pr_reg);
|
||||
#if TARGET_CPU_SPARC
|
||||
uintptr_t last_ebp = (*gregs)[R_FP];
|
||||
#elif TARGET_CPU_X86
|
||||
uintptr_t last_ebp = (*gregs)[EBP];
|
||||
#endif
|
||||
uintptr_t stack_bottom = lwp_lister->GetLwpStackBottom(last_ebp);
|
||||
if (stack_bottom > last_ebp &&
|
||||
stack_bottom == crashing_context->crashing_stack_bottom) {
|
||||
// Got it. Stop iteration.
|
||||
crashing_context->crashing_lwpid = lsp->pr_lwpid;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
MinidumpGenerator::MinidumpGenerator()
|
||||
: requester_pid_(0),
|
||||
signo_(0),
|
||||
lwp_lister_(NULL) {
|
||||
}
|
||||
|
||||
// Find the crashing lwpid.
|
||||
// This is done based on stack bottom comparing.
|
||||
int FindCrashingLwp(uintptr_t crashing_stack_bottom,
|
||||
int requester_pid,
|
||||
const SolarisLwp *lwp_lister) {
|
||||
FindCrashLwpContext context;
|
||||
context.lwp_lister = lwp_lister;
|
||||
context.crashing_stack_bottom = crashing_stack_bottom;
|
||||
CallbackParam<LwpCallback> callback_param(IsLwpCrashedCallback,
|
||||
&context);
|
||||
lwp_lister->Lwp_iter_all(lwp_lister->getpid(), &callback_param);
|
||||
return context.crashing_lwpid;
|
||||
MinidumpGenerator::~MinidumpGenerator() {
|
||||
}
|
||||
|
||||
bool WriteLwpStack(const SolarisLwp *lwp_lister,
|
||||
uintptr_t last_esp,
|
||||
UntypedMDRVA *memory,
|
||||
MDMemoryDescriptor *loc) {
|
||||
uintptr_t stack_bottom = lwp_lister->GetLwpStackBottom(last_esp);
|
||||
bool MinidumpGenerator::WriteLwpStack(uintptr_t last_esp,
|
||||
UntypedMDRVA *memory,
|
||||
MDMemoryDescriptor *loc) {
|
||||
uintptr_t stack_bottom = lwp_lister_->GetLwpStackBottom(last_esp);
|
||||
if (stack_bottom >= last_esp) {
|
||||
int size = stack_bottom - last_esp;
|
||||
if (size > 0) {
|
||||
@@ -151,52 +75,29 @@ bool WriteLwpStack(const SolarisLwp *lwp_lister,
|
||||
}
|
||||
|
||||
#if TARGET_CPU_SPARC
|
||||
bool WriteContext(MDRawContextSPARC *context, ucontext_t *sig_ctx) {
|
||||
assert(sig_ctx != NULL);
|
||||
int* regs = sig_ctx->uc_mcontext.gregs;
|
||||
context->context_flags = MD_CONTEXT_SPARC_FULL;
|
||||
|
||||
context->ccr = (unsigned int)(regs[0]);
|
||||
context->pc = (unsigned int)(regs[REG_PC]);
|
||||
context->npc = (unsigned int)(regs[REG_nPC]);
|
||||
context->y = (unsigned int)(regs[REG_Y]);
|
||||
context->asi = (unsigned int)(regs[19]);
|
||||
context->fprs = (unsigned int)(regs[20]);
|
||||
|
||||
for ( int i = 0 ; i < 32; ++i ) {
|
||||
context->g_r[i] = 0;
|
||||
}
|
||||
|
||||
for ( int i = 1 ; i < 16; ++i ) {
|
||||
context->g_r[i] = (uintptr_t)(sig_ctx->uc_mcontext.gregs[i + 3]);
|
||||
}
|
||||
context->g_r[30] = (uintptr_t)(((struct frame *)context->g_r[14])->fr_savfp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteContext(MDRawContextSPARC *context, prgregset_t regs,
|
||||
prfpregset_t *fp_regs) {
|
||||
bool MinidumpGenerator::WriteContext(MDRawContextSPARC *context, prgregset_t regs,
|
||||
prfpregset_t *fp_regs) {
|
||||
if (!context || !regs)
|
||||
return false;
|
||||
|
||||
context->context_flags = MD_CONTEXT_SPARC_FULL;
|
||||
|
||||
context->ccr = (uintptr_t)(regs[32]);
|
||||
context->pc = (uintptr_t)(regs[R_PC]);
|
||||
context->npc = (uintptr_t)(regs[R_nPC]);
|
||||
context->y = (uintptr_t)(regs[R_Y]);
|
||||
context->asi = (uintptr_t)(regs[36]);
|
||||
context->fprs = (uintptr_t)(regs[37]);
|
||||
context->ccr = (unsigned int)(regs[32]);
|
||||
context->pc = (unsigned int)(regs[R_PC]);
|
||||
context->npc = (unsigned int)(regs[R_nPC]);
|
||||
context->y = (unsigned int)(regs[R_Y]);
|
||||
context->asi = (unsigned int)(regs[36]);
|
||||
context->fprs = (unsigned int)(regs[37]);
|
||||
|
||||
for ( int i = 0 ; i < 32 ; ++i ){
|
||||
context->g_r[i] = (uintptr_t)(regs[i]);
|
||||
context->g_r[i] = (unsigned int)(regs[i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#elif TARGET_CPU_X86
|
||||
bool WriteContext(MDRawContextX86 *context, prgregset_t regs,
|
||||
prfpregset_t *fp_regs) {
|
||||
bool MinidumpGenerator::WriteContext(MDRawContextX86 *context, prgregset_t regs,
|
||||
prfpregset_t *fp_regs) {
|
||||
if (!context || !regs)
|
||||
return false;
|
||||
|
||||
@@ -223,67 +124,18 @@ bool WriteContext(MDRawContextX86 *context, prgregset_t regs,
|
||||
}
|
||||
#endif /* TARGET_CPU_XXX */
|
||||
|
||||
// Write information about a crashed Lwp.
|
||||
// When a lwp crash, kernel will write something on the stack for processing
|
||||
// signal. This makes the current stack not reliable, and our stack walker
|
||||
// won't figure out the whole call stack for this. So we write the stack at the
|
||||
// time of the crash into the minidump file, not the current stack.
|
||||
bool WriteCrashedLwpStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
const lwpstatus_t *lsp,
|
||||
MDRawThread *lwp) {
|
||||
assert(writer_args->sig_ctx != NULL);
|
||||
|
||||
lwp->thread_id = lsp->pr_lwpid;
|
||||
|
||||
#if TARGET_CPU_SPARC
|
||||
UntypedMDRVA memory(minidump_writer);
|
||||
if (!WriteLwpStack(writer_args->lwp_lister,
|
||||
writer_args->sig_ctx->uc_mcontext.gregs[REG_O6],
|
||||
&memory,
|
||||
&lwp->stack))
|
||||
return false;
|
||||
|
||||
TypedMDRVA<MDRawContextSPARC> context(minidump_writer);
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
lwp->thread_context = context.location();
|
||||
memset(context.get(), 0, sizeof(MDRawContextSPARC));
|
||||
return WriteContext(context.get(), writer_args->sig_ctx);
|
||||
#elif TARGET_CPU_X86
|
||||
UntypedMDRVA memory(minidump_writer);
|
||||
if (!WriteLwpStack(writer_args->lwp_lister,
|
||||
writer_args->sig_ctx->uc_mcontext.gregs[UESP],
|
||||
&memory,
|
||||
&lwp->stack))
|
||||
return false;
|
||||
|
||||
TypedMDRVA<MDRawContextX86> context(minidump_writer);
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
lwp->thread_context = context.location();
|
||||
memset(context.get(), 0, sizeof(MDRawContextX86));
|
||||
return WriteContext(context.get(),
|
||||
(int *)&writer_args->sig_ctx->uc_mcontext.gregs,
|
||||
&writer_args->sig_ctx->uc_mcontext.fpregs);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WriteLwpStream(MinidumpFileWriter *minidump_writer,
|
||||
const SolarisLwp *lwp_lister,
|
||||
const lwpstatus_t *lsp, MDRawThread *lwp) {
|
||||
bool MinidumpGenerator::WriteLwpStream(lwpstatus_t *lsp, MDRawThread *lwp) {
|
||||
prfpregset_t fp_regs = lsp->pr_fpreg;
|
||||
const prgregset_t *gregs = &(lsp->pr_reg);
|
||||
UntypedMDRVA memory(minidump_writer);
|
||||
prgregset_t *gregs = &(lsp->pr_reg);
|
||||
UntypedMDRVA memory(&writer_);
|
||||
#if TARGET_CPU_SPARC
|
||||
if (!WriteLwpStack(lwp_lister,
|
||||
(*gregs)[R_SP],
|
||||
if (!WriteLwpStack((*gregs)[R_SP],
|
||||
&memory,
|
||||
&lwp->stack))
|
||||
return false;
|
||||
|
||||
// Write context
|
||||
TypedMDRVA<MDRawContextSPARC> context(minidump_writer);
|
||||
TypedMDRVA<MDRawContextSPARC> context(&writer_);
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
// should be the thread_id
|
||||
@@ -291,14 +143,13 @@ bool WriteLwpStream(MinidumpFileWriter *minidump_writer,
|
||||
lwp->thread_context = context.location();
|
||||
memset(context.get(), 0, sizeof(MDRawContextSPARC));
|
||||
#elif TARGET_CPU_X86
|
||||
if (!WriteLwpStack(lwp_lister,
|
||||
(*gregs)[UESP],
|
||||
if (!WriteLwpStack((*gregs)[UESP],
|
||||
&memory,
|
||||
&lwp->stack))
|
||||
return false;
|
||||
|
||||
// Write context
|
||||
TypedMDRVA<MDRawContextX86> context(minidump_writer);
|
||||
TypedMDRVA<MDRawContextX86> context(&writer_);
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
// should be the thread_id
|
||||
@@ -309,7 +160,7 @@ bool WriteLwpStream(MinidumpFileWriter *minidump_writer,
|
||||
return WriteContext(context.get(), (int *)gregs, &fp_regs);
|
||||
}
|
||||
|
||||
bool WriteCPUInformation(MDRawSystemInfo *sys_info) {
|
||||
bool MinidumpGenerator::WriteCPUInformation(MDRawSystemInfo *sys_info) {
|
||||
struct utsname uts;
|
||||
char *major, *minor, *build;
|
||||
|
||||
@@ -337,8 +188,7 @@ bool WriteCPUInformation(MDRawSystemInfo *sys_info) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteOSInformation(MinidumpFileWriter *minidump_writer,
|
||||
MDRawSystemInfo *sys_info) {
|
||||
bool MinidumpGenerator::WriteOSInformation(MDRawSystemInfo *sys_info) {
|
||||
sys_info->platform_id = MD_OS_SOLARIS;
|
||||
|
||||
struct utsname uts;
|
||||
@@ -370,7 +220,7 @@ bool WriteOSInformation(MinidumpFileWriter *minidump_writer,
|
||||
}
|
||||
|
||||
MDLocationDescriptor location;
|
||||
if (!minidump_writer->WriteString(os_version, 0, &location))
|
||||
if (!writer_.WriteString(os_version, 0, &location))
|
||||
return false;
|
||||
sys_info->csd_version_rva = location.rva;
|
||||
}
|
||||
@@ -379,34 +229,21 @@ bool WriteOSInformation(MinidumpFileWriter *minidump_writer,
|
||||
|
||||
// Callback context for get writting lwp information.
|
||||
struct LwpInfoCallbackCtx {
|
||||
MinidumpFileWriter *minidump_writer;
|
||||
const WriterArgument *writer_args;
|
||||
MinidumpGenerator *generator;
|
||||
TypedMDRVA<MDRawThreadList> *list;
|
||||
int lwp_index;
|
||||
};
|
||||
|
||||
bool LwpInformationCallback(lwpstatus_t *lsp, void *context) {
|
||||
bool success = true;
|
||||
LwpInfoCallbackCtx *callback_context =
|
||||
static_cast<LwpInfoCallbackCtx *>(context);
|
||||
|
||||
// The current lwp is the one to handle the crash. Ignore it.
|
||||
// The current thread is the one to handle the crash. Ignore it.
|
||||
if (lsp->pr_lwpid != pthread_self()) {
|
||||
LwpInfoCallbackCtx *callback_context =
|
||||
static_cast<LwpInfoCallbackCtx *>(context);
|
||||
MDRawThread lwp;
|
||||
memset(&lwp, 0, sizeof(MDRawThread));
|
||||
|
||||
if (lsp->pr_lwpid != callback_context->writer_args->crashed_lwpid ||
|
||||
callback_context->writer_args->sig_ctx == NULL) {
|
||||
success = WriteLwpStream(callback_context->minidump_writer,
|
||||
callback_context->writer_args->lwp_lister,
|
||||
lsp, &lwp);
|
||||
} else {
|
||||
success = WriteCrashedLwpStream(callback_context->minidump_writer,
|
||||
callback_context->writer_args,
|
||||
lsp, &lwp);
|
||||
}
|
||||
success = callback_context->generator->WriteLwpStream(lsp, &lwp);
|
||||
if (success) {
|
||||
callback_context->list->CopyIndexAfterObject(
|
||||
callback_context->lwp_index++,
|
||||
@@ -417,15 +254,12 @@ bool LwpInformationCallback(lwpstatus_t *lsp, void *context) {
|
||||
return success;
|
||||
}
|
||||
|
||||
bool WriteLwpListStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
bool MinidumpGenerator::WriteLwpListStream(MDRawDirectory *dir) {
|
||||
// Get the lwp information.
|
||||
const SolarisLwp *lwp_lister = writer_args->lwp_lister;
|
||||
int lwp_count = lwp_lister->GetLwpCount();
|
||||
int lwp_count = lwp_lister_->GetLwpCount();
|
||||
if (lwp_count < 0)
|
||||
return false;
|
||||
TypedMDRVA<MDRawThreadList> list(minidump_writer);
|
||||
TypedMDRVA<MDRawThreadList> list(&writer_);
|
||||
if (!list.AllocateObjectAndArray(lwp_count - 1, sizeof(MDRawThread)))
|
||||
return false;
|
||||
dir->stream_type = MD_THREAD_LIST_STREAM;
|
||||
@@ -433,32 +267,31 @@ bool WriteLwpListStream(MinidumpFileWriter *minidump_writer,
|
||||
list.get()->number_of_threads = lwp_count - 1;
|
||||
|
||||
LwpInfoCallbackCtx context;
|
||||
context.minidump_writer = minidump_writer;
|
||||
context.writer_args = writer_args;
|
||||
context.generator = this;
|
||||
context.list = &list;
|
||||
context.lwp_index = 0;
|
||||
CallbackParam<LwpCallback> callback_param(LwpInformationCallback,
|
||||
&context);
|
||||
int written =
|
||||
lwp_lister->Lwp_iter_all(lwp_lister->getpid(), &callback_param);
|
||||
lwp_lister_->Lwp_iter_all(lwp_lister_->getpid(), &callback_param);
|
||||
return written == lwp_count;
|
||||
}
|
||||
|
||||
bool WriteCVRecord(MinidumpFileWriter *minidump_writer,
|
||||
MDRawModule *module,
|
||||
const char *module_path,
|
||||
char *realname) {
|
||||
TypedMDRVA<MDCVInfoPDB70> cv(minidump_writer);
|
||||
bool MinidumpGenerator::WriteCVRecord(MDRawModule *module,
|
||||
const char *module_path) {
|
||||
TypedMDRVA<MDCVInfoPDB70> cv(&writer_);
|
||||
|
||||
char path[PATH_MAX];
|
||||
const char *module_name = module_path ? module_path : "<Unknown>";
|
||||
snprintf(path, sizeof(path), "/proc/self/object/%s", module_name);
|
||||
|
||||
size_t module_name_length = strlen(realname);
|
||||
size_t module_name_length = strlen(module_name);
|
||||
if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(u_int8_t)))
|
||||
return false;
|
||||
if (!cv.CopyIndexAfterObject(0, realname, module_name_length))
|
||||
if (!cv.CopyIndexAfterObject(0, const_cast<char *>(module_name),
|
||||
module_name_length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
module->cv_record = cv.location();
|
||||
MDCVInfoPDB70 *cv_ptr = cv.get();
|
||||
@@ -489,8 +322,8 @@ bool WriteCVRecord(MinidumpFileWriter *minidump_writer,
|
||||
}
|
||||
|
||||
struct ModuleInfoCallbackCtx {
|
||||
MinidumpGenerator *generator;
|
||||
MinidumpFileWriter *minidump_writer;
|
||||
const WriterArgument *writer_args;
|
||||
TypedMDRVA<MDRawModuleList> *list;
|
||||
int module_index;
|
||||
};
|
||||
@@ -505,29 +338,16 @@ bool ModuleInfoCallback(const ModuleInfo &module_info, void *context) {
|
||||
MDRawModule module;
|
||||
memset(&module, 0, sizeof(module));
|
||||
MDLocationDescriptor loc;
|
||||
char path[PATH_MAX];
|
||||
char buf[PATH_MAX];
|
||||
char *realname;
|
||||
int count;
|
||||
|
||||
snprintf(path, sizeof (path), "/proc/self/path/%s", module_info.name);
|
||||
if ((count = readlink(path, buf, PATH_MAX)) < 0)
|
||||
return false;
|
||||
buf[count] = '\0';
|
||||
|
||||
if ((realname = strrchr(buf, '/')) == NULL)
|
||||
return false;
|
||||
realname++;
|
||||
|
||||
if (!callback_context->minidump_writer->WriteString(realname, 0, &loc))
|
||||
if (!callback_context->minidump_writer->WriteString(module_info.name,
|
||||
0, &loc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
module.base_of_image = (u_int64_t)module_info.start_addr;
|
||||
module.size_of_image = module_info.size;
|
||||
module.module_name_rva = loc.rva;
|
||||
|
||||
if (!WriteCVRecord(callback_context->minidump_writer, &module,
|
||||
module_info.name, realname))
|
||||
if (!callback_context->generator->WriteCVRecord(&module, module_info.name))
|
||||
return false;
|
||||
|
||||
callback_context->list->CopyIndexAfterObject(
|
||||
@@ -535,11 +355,9 @@ bool ModuleInfoCallback(const ModuleInfo &module_info, void *context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteModuleListStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawModuleList> list(minidump_writer);
|
||||
int module_count = writer_args->lwp_lister->GetModuleCount();
|
||||
bool MinidumpGenerator::WriteModuleListStream(MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawModuleList> list(&writer_);
|
||||
int module_count = lwp_lister_->GetModuleCount();
|
||||
|
||||
if (module_count <= 0 ||
|
||||
!list.AllocateObjectAndArray(module_count, MD_MODULE_SIZE)) {
|
||||
@@ -550,18 +368,16 @@ bool WriteModuleListStream(MinidumpFileWriter *minidump_writer,
|
||||
dir->location = list.location();
|
||||
list.get()->number_of_modules = module_count;
|
||||
ModuleInfoCallbackCtx context;
|
||||
context.minidump_writer = minidump_writer;
|
||||
context.writer_args = writer_args;
|
||||
context.generator = this;
|
||||
context.minidump_writer = &writer_;
|
||||
context.list = &list;
|
||||
context.module_index = 0;
|
||||
CallbackParam<ModuleCallback> callback(ModuleInfoCallback, &context);
|
||||
return writer_args->lwp_lister->ListModules(&callback) == module_count;
|
||||
return lwp_lister_->ListModules(&callback) == module_count;
|
||||
}
|
||||
|
||||
bool WriteSystemInfoStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawSystemInfo> sys_info(minidump_writer);
|
||||
bool MinidumpGenerator::WriteSystemInfoStream(MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawSystemInfo> sys_info(&writer_);
|
||||
|
||||
if (!sys_info.Allocate())
|
||||
return false;
|
||||
@@ -570,65 +386,69 @@ bool WriteSystemInfoStream(MinidumpFileWriter *minidump_writer,
|
||||
dir->location = sys_info.location();
|
||||
|
||||
return WriteCPUInformation(sys_info.get()) &&
|
||||
WriteOSInformation(minidump_writer, sys_info.get());
|
||||
WriteOSInformation(sys_info.get());
|
||||
}
|
||||
|
||||
bool WriteExceptionStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
// This happenes when this is not a crash, but a requested dump.
|
||||
if (writer_args->sig_ctx == NULL)
|
||||
bool MinidumpGenerator::WriteExceptionStream(MDRawDirectory *dir) {
|
||||
ucontext_t uc;
|
||||
gregset_t *gregs;
|
||||
fpregset_t fp_regs;
|
||||
|
||||
if (getcontext(&uc) != 0)
|
||||
return false;
|
||||
|
||||
TypedMDRVA<MDRawExceptionStream> exception(minidump_writer);
|
||||
TypedMDRVA<MDRawExceptionStream> exception(&writer_);
|
||||
if (!exception.Allocate())
|
||||
return false;
|
||||
|
||||
dir->stream_type = MD_EXCEPTION_STREAM;
|
||||
dir->location = exception.location();
|
||||
exception.get()->thread_id = writer_args->crashed_lwpid;
|
||||
exception.get()->exception_record.exception_code = writer_args->signo;
|
||||
exception.get()->thread_id = requester_pid_;
|
||||
exception.get()->exception_record.exception_code = signo_;
|
||||
exception.get()->exception_record.exception_flags = 0;
|
||||
|
||||
gregs = &(uc.uc_mcontext.gregs);
|
||||
fp_regs = uc.uc_mcontext.fpregs;
|
||||
#if TARGET_CPU_SPARC
|
||||
if (writer_args->sig_ctx != NULL) {
|
||||
exception.get()->exception_record.exception_address =
|
||||
writer_args->sig_ctx->uc_mcontext.gregs[REG_PC];
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
exception.get()->exception_record.exception_address = ((unsigned int *)gregs)[1];
|
||||
// Write context of the exception.
|
||||
TypedMDRVA<MDRawContextSPARC> context(minidump_writer);
|
||||
TypedMDRVA<MDRawContextSPARC> context(&writer_);
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
exception.get()->thread_context = context.location();
|
||||
memset(context.get(), 0, sizeof(MDRawContextSPARC));
|
||||
return WriteContext(context.get(), writer_args->sig_ctx);
|
||||
#elif TARGET_CPU_X86
|
||||
if (writer_args->sig_ctx != NULL) {
|
||||
exception.get()->exception_record.exception_address =
|
||||
writer_args->sig_ctx->uc_mcontext.gregs[EIP];
|
||||
} else {
|
||||
return true;
|
||||
|
||||
// On Solaris i386, gregset_t = prgregset_t, fpregset_t = prfpregset_t
|
||||
// But on Solaris Sparc are diffrent, see sys/regset.h and sys/procfs_isa.h
|
||||
context.get()->context_flags = MD_CONTEXT_SPARC_FULL;
|
||||
context.get()->ccr = ((unsigned int *)gregs)[0];
|
||||
context.get()->pc = ((unsigned int *)gregs)[1];
|
||||
context.get()->npc = ((unsigned int *)gregs)[2];
|
||||
context.get()->y = ((unsigned int *)gregs)[3];
|
||||
context.get()->asi = ((unsigned int *)gregs)[19];
|
||||
context.get()->fprs = ((unsigned int *)gregs)[20];
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
context.get()->g_r[i] = 0;
|
||||
}
|
||||
|
||||
for (int i = 1; i < 16; ++i) {
|
||||
context.get()->g_r[i] = ((unsigned int *)gregs)[i + 3];
|
||||
}
|
||||
|
||||
return true;
|
||||
#elif TARGET_CPU_X86
|
||||
exception.get()->exception_record.exception_address = (*gregs)[EIP];
|
||||
// Write context of the exception.
|
||||
TypedMDRVA<MDRawContextX86> context(minidump_writer);
|
||||
TypedMDRVA<MDRawContextX86> context(&writer_);
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
exception.get()->thread_context = context.location();
|
||||
memset(context.get(), 0, sizeof(MDRawContextX86));
|
||||
return WriteContext(context.get(),
|
||||
(int *)&writer_args->sig_ctx->uc_mcontext.gregs,
|
||||
NULL);
|
||||
#endif
|
||||
return WriteContext(context.get(), (int *)gregs, &fp_regs);
|
||||
#endif /* TARGET_CPU_XXX */
|
||||
}
|
||||
|
||||
bool WriteMiscInfoStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawMiscInfo> info(minidump_writer);
|
||||
bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawMiscInfo> info(&writer_);
|
||||
|
||||
if (!info.Allocate())
|
||||
return false;
|
||||
@@ -637,15 +457,13 @@ bool WriteMiscInfoStream(MinidumpFileWriter *minidump_writer,
|
||||
dir->location = info.location();
|
||||
info.get()->size_of_info = sizeof(MDRawMiscInfo);
|
||||
info.get()->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID;
|
||||
info.get()->process_id = writer_args->requester_pid;
|
||||
info.get()->process_id = requester_pid_;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteBreakpadInfoStream(MinidumpFileWriter *minidump_writer,
|
||||
const WriterArgument *writer_args,
|
||||
MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawBreakpadInfo> info(minidump_writer);
|
||||
bool MinidumpGenerator::WriteBreakpadInfoStream(MDRawDirectory *dir) {
|
||||
TypedMDRVA<MDRawBreakpadInfo> info(&writer_);
|
||||
|
||||
if (!info.Allocate())
|
||||
return false;
|
||||
@@ -656,7 +474,7 @@ bool WriteBreakpadInfoStream(MinidumpFileWriter *minidump_writer,
|
||||
info.get()->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID |
|
||||
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID;
|
||||
info.get()->dump_thread_id = getpid();
|
||||
info.get()->requesting_thread_id = writer_args->requester_pid;
|
||||
info.get()->requesting_thread_id = requester_pid_;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -668,53 +486,25 @@ class AutoLwpResumer {
|
||||
SolarisLwp *lwp_;
|
||||
};
|
||||
|
||||
// Prototype of writer functions.
|
||||
typedef bool (*WriteStreamFN)(MinidumpFileWriter *,
|
||||
const WriterArgument *,
|
||||
MDRawDirectory *);
|
||||
|
||||
// Function table to writer a full minidump.
|
||||
const WriteStreamFN writers[] = {
|
||||
WriteLwpListStream,
|
||||
WriteModuleListStream,
|
||||
WriteSystemInfoStream,
|
||||
WriteExceptionStream,
|
||||
WriteMiscInfoStream,
|
||||
WriteBreakpadInfoStream,
|
||||
};
|
||||
|
||||
// Will call each writer function in the writers table.
|
||||
//void* MinidumpGenerator::Write(void *argument) {
|
||||
void* Write(void *argument) {
|
||||
WriterArgument *writer_args = static_cast<WriterArgument *>(argument);
|
||||
void* MinidumpGenerator::Write() {
|
||||
// Function table to writer a full minidump.
|
||||
const WriteStreamFN writers[] = {
|
||||
&MinidumpGenerator::WriteLwpListStream,
|
||||
&MinidumpGenerator::WriteModuleListStream,
|
||||
&MinidumpGenerator::WriteSystemInfoStream,
|
||||
&MinidumpGenerator::WriteExceptionStream,
|
||||
&MinidumpGenerator::WriteMiscInfoStream,
|
||||
&MinidumpGenerator::WriteBreakpadInfoStream,
|
||||
};
|
||||
|
||||
if (!writer_args->lwp_lister->ControlAllLwps(true))
|
||||
if (!lwp_lister_->ControlAllLwps(true))
|
||||
return NULL;
|
||||
|
||||
AutoLwpResumer lwpResumer(writer_args->lwp_lister);
|
||||
AutoLwpResumer lwpResumer(lwp_lister_);
|
||||
|
||||
if (writer_args->sighandler_ebp != 0 &&
|
||||
writer_args->lwp_lister->FindSigContext(writer_args->sighandler_ebp,
|
||||
&writer_args->sig_ctx)) {
|
||||
writer_args->crashed_stack_bottom =
|
||||
writer_args->lwp_lister->GetLwpStackBottom(
|
||||
#if TARGET_CPU_SPARC
|
||||
writer_args->sig_ctx->uc_mcontext.gregs[REG_O6]
|
||||
#elif TARGET_CPU_X86
|
||||
writer_args->sig_ctx->uc_mcontext.gregs[UESP]
|
||||
#endif
|
||||
);
|
||||
|
||||
int crashed_lwpid = FindCrashingLwp(writer_args->crashed_stack_bottom,
|
||||
writer_args->requester_pid,
|
||||
writer_args->lwp_lister);
|
||||
if (crashed_lwpid > 0)
|
||||
writer_args->crashed_lwpid = crashed_lwpid;
|
||||
}
|
||||
|
||||
MinidumpFileWriter *minidump_writer = writer_args->minidump_writer;
|
||||
TypedMDRVA<MDRawHeader> header(minidump_writer);
|
||||
TypedMDRVA<MDRawDirectory> dir(minidump_writer);
|
||||
TypedMDRVA<MDRawHeader> header(&writer_);
|
||||
TypedMDRVA<MDRawDirectory> dir(&writer_);
|
||||
if (!header.Allocate())
|
||||
return 0;
|
||||
|
||||
@@ -731,53 +521,29 @@ void* Write(void *argument) {
|
||||
int dir_index = 0;
|
||||
MDRawDirectory local_dir;
|
||||
for (int i = 0; i < writer_count; ++i) {
|
||||
if ((*writers[i])(minidump_writer, writer_args, &local_dir))
|
||||
if ((this->*writers[i])(&local_dir))
|
||||
dir.CopyIndex(dir_index++, &local_dir);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
MinidumpGenerator::MinidumpGenerator() {
|
||||
}
|
||||
|
||||
MinidumpGenerator::~MinidumpGenerator() {
|
||||
}
|
||||
|
||||
// Write minidump into file.
|
||||
// It runs in a different thread from the crashing thread.
|
||||
bool MinidumpGenerator::WriteMinidumpToFile(const char *file_pathname,
|
||||
int signo,
|
||||
uintptr_t sighandler_ebp,
|
||||
ucontext_t **sig_ctx) const {
|
||||
// The exception handler thread.
|
||||
pthread_t handler_thread;
|
||||
|
||||
int signo) {
|
||||
assert(file_pathname != NULL);
|
||||
|
||||
if (file_pathname == NULL)
|
||||
return false;
|
||||
|
||||
MinidumpFileWriter minidump_writer;
|
||||
if (minidump_writer.Open(file_pathname)) {
|
||||
WriterArgument argument;
|
||||
memset(&argument, 0, sizeof(argument));
|
||||
if (writer_.Open(file_pathname)) {
|
||||
SolarisLwp lwp_lister(getpid());
|
||||
argument.lwp_lister = &lwp_lister;
|
||||
argument.minidump_writer = &minidump_writer;
|
||||
argument.requester_pid = getpid();
|
||||
argument.crashed_lwpid = pthread_self();
|
||||
argument.signo = signo;
|
||||
argument.sighandler_ebp = sighandler_ebp;
|
||||
argument.sig_ctx = NULL;
|
||||
|
||||
pthread_create(&handler_thread, NULL, Write, (void *)&argument);
|
||||
pthread_join(handler_thread, NULL);
|
||||
return true;
|
||||
lwp_lister_ = &lwp_lister;
|
||||
requester_pid_ = getpid();
|
||||
signo_ = signo;
|
||||
if (Write())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -32,7 +32,13 @@
|
||||
#ifndef CLIENT_SOLARIS_HANDLER_MINIDUMP_GENERATOR_H__
|
||||
#define CLIENT_SOLARIS_HANDLER_MINIDUMP_GENERATOR_H__
|
||||
|
||||
#include <ucontext.h>
|
||||
#if defined(sparc) || defined(__sparc__)
|
||||
#define TARGET_CPU_SPARC 1
|
||||
#elif defined(i386) || defined(__i386__)
|
||||
#define TARGET_CPU_X86 1
|
||||
#else
|
||||
#error "cannot determine cpu type"
|
||||
#endif
|
||||
|
||||
#include "client/minidump_file_writer.h"
|
||||
#include "client/solaris/handler/solaris_lwp.h"
|
||||
@@ -60,9 +66,59 @@ class MinidumpGenerator {
|
||||
|
||||
// Write minidump.
|
||||
bool WriteMinidumpToFile(const char *file_pathname,
|
||||
int signo,
|
||||
uintptr_t sighandler_ebp,
|
||||
ucontext_t **sig_ctx) const;
|
||||
int signo);
|
||||
|
||||
private:
|
||||
// Helpers
|
||||
bool WriteCVRecord(MDRawModule *module, const char *module_path);
|
||||
|
||||
// Write the lwp stack information to dump file.
|
||||
bool WriteLwpStack(uintptr_t last_esp, UntypedMDRVA *memory,
|
||||
MDMemoryDescriptor *loc);
|
||||
|
||||
// Write CPU context based on provided registers.
|
||||
#if TARGET_CPU_SPARC
|
||||
bool WriteContext(MDRawContextSPARC *context, prgregset_t regs,
|
||||
prfpregset_t *fp_regs);
|
||||
#elif TARGET_CPU_X86
|
||||
bool WriteContext(MDRawContextX86 *context, prgregset_t regs,
|
||||
prfpregset_t *fp_regs);
|
||||
#endif /* TARGET_CPU_XXX */
|
||||
|
||||
// Write information about a lwp.
|
||||
// Only processes lwp running normally at the crash.
|
||||
bool WriteLwpStream(lwpstatus_t *lsp, MDRawThread *lwp);
|
||||
|
||||
// Write the CPU information to the dump file.
|
||||
bool WriteCPUInformation(MDRawSystemInfo *sys_info);
|
||||
|
||||
//Write the OS information to the dump file.
|
||||
bool WriteOSInformation(MDRawSystemInfo *sys_info);
|
||||
|
||||
typedef bool (MinidumpGenerator::*WriteStreamFN)(MDRawDirectory *);
|
||||
|
||||
// Write all the information to the dump file.
|
||||
void *Write();
|
||||
|
||||
// Stream writers
|
||||
bool WriteLwpListStream(MDRawDirectory *dir);
|
||||
bool WriteModuleListStream(MDRawDirectory *dir);
|
||||
bool WriteSystemInfoStream(MDRawDirectory *dir);
|
||||
bool WriteExceptionStream(MDRawDirectory *dir);
|
||||
bool WriteMiscInfoStream(MDRawDirectory *dir);
|
||||
bool WriteBreakpadInfoStream(MDRawDirectory *dir);
|
||||
|
||||
private:
|
||||
MinidumpFileWriter writer_;
|
||||
|
||||
// Pid of the lwp who called WriteMinidumpToFile
|
||||
int requester_pid_;
|
||||
|
||||
// Signal number when crash happed. Can be 0 if this is a requested dump.
|
||||
int signo_;
|
||||
|
||||
// Used to get information about the lwps.
|
||||
SolarisLwp *lwp_lister_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
@@ -48,7 +48,7 @@ static void *Reporter(void *) {
|
||||
snprintf(buffer, sizeof(buffer), "./minidump_test.out");
|
||||
fprintf(stdout, "Writing %s\n", buffer);
|
||||
|
||||
md.WriteMinidumpToFile(buffer, 0, 0, NULL);
|
||||
md.WriteMinidumpToFile(buffer, 0);
|
||||
doneWritingReport = true;
|
||||
|
||||
return NULL;
|
||||
|
||||
@@ -30,11 +30,9 @@
|
||||
// Author: Alfred Peng
|
||||
|
||||
#include <dirent.h>
|
||||
#include <elf.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <sys/frame.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
@@ -58,16 +56,6 @@ uintptr_t stack_base_address = 0;
|
||||
static const int HEADER_MAX = 2000;
|
||||
static const int MAP_MAX = 1000;
|
||||
|
||||
// Context information for the callbacks when validating address by listing
|
||||
// modules.
|
||||
struct AddressValidatingContext {
|
||||
uintptr_t address;
|
||||
bool is_mapped;
|
||||
|
||||
AddressValidatingContext() : address(0UL), is_mapped(false) {
|
||||
}
|
||||
};
|
||||
|
||||
// Convert from string to int.
|
||||
static bool LocalAtoi(char *s, int *r) {
|
||||
assert(s != NULL);
|
||||
@@ -81,19 +69,18 @@ static bool LocalAtoi(char *s, int *r) {
|
||||
}
|
||||
|
||||
// Callback invoked for each mapped module.
|
||||
// It uses the module's adderss range to validate the address.
|
||||
// It use the module's adderss range to validate the address.
|
||||
static bool AddressNotInModuleCallback(const ModuleInfo &module_info,
|
||||
void *context) {
|
||||
AddressValidatingContext *addr =
|
||||
reinterpret_cast<AddressValidatingContext *>(context);
|
||||
if (addr->is_mapped = ((module_info.start_addr > 0) &&
|
||||
(addr->address >= module_info.start_addr) &&
|
||||
(addr->address <= module_info.start_addr +
|
||||
module_info.size))) {
|
||||
uintptr_t addr = reinterpret_cast<uintptr_t>(context);
|
||||
if ((module_info.start_addr > 0) &&
|
||||
(addr >= module_info.start_addr) &&
|
||||
(addr <= module_info.start_addr + module_info.size)) {
|
||||
stack_base_address = module_info.start_addr + module_info.size;
|
||||
return false;
|
||||
}
|
||||
|
||||
return !addr->is_mapped;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int IterateLwpAll(int pid,
|
||||
@@ -127,28 +114,6 @@ static int IterateLwpAll(int pid,
|
||||
return count;
|
||||
}
|
||||
|
||||
#if defined(__i386) && !defined(NO_FRAME_POINTER)
|
||||
void *GetNextFrame(void **last_ebp) {
|
||||
void *sp = *last_ebp;
|
||||
if ((unsigned long)sp == (unsigned long)last_ebp)
|
||||
return NULL;
|
||||
if ((unsigned long)sp & (sizeof(void *) - 1))
|
||||
return NULL;
|
||||
if ((unsigned long)sp - (unsigned long)last_ebp > 100000)
|
||||
return NULL;
|
||||
return sp;
|
||||
}
|
||||
#elif defined(__sparc)
|
||||
void *GetNextFrame(void *last_ebp) {
|
||||
return reinterpret_cast<struct frame *>(last_ebp)->fr_savfp;
|
||||
}
|
||||
#else
|
||||
void *GetNextFrame(void **last_ebp) {
|
||||
return reinterpret_cast<void*>(last_ebp);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
class AutoCloser {
|
||||
public:
|
||||
AutoCloser(int fd) : fd_(fd) {}
|
||||
@@ -285,10 +250,8 @@ int SolarisLwp::Lwp_iter_all(int pid,
|
||||
}
|
||||
|
||||
uintptr_t SolarisLwp::GetLwpStackBottom(uintptr_t current_esp) const {
|
||||
AddressValidatingContext addr;
|
||||
addr.address = current_esp;
|
||||
CallbackParam<ModuleCallback> callback_param(AddressNotInModuleCallback,
|
||||
&addr);
|
||||
(void *)current_esp);
|
||||
ListModules(&callback_param);
|
||||
return stack_base_address;
|
||||
}
|
||||
@@ -350,28 +313,7 @@ int SolarisLwp::ListModules(
|
||||
memset(&module, 0, sizeof (module));
|
||||
module.start_addr = _maps->pr_vaddr;
|
||||
module.size = _maps->pr_size;
|
||||
if (strlen(name) > 0) {
|
||||
int objectfd = 0;
|
||||
char path[PATH_MAX];
|
||||
char buf[SELFMAG];
|
||||
|
||||
snprintf(path, sizeof (path), "/proc/self/object/%s", name);
|
||||
if ((objectfd = open(path, O_RDONLY)) < 0) {
|
||||
print_message1(2, "can't open module file\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
AutoCloser autocloser(objectfd);
|
||||
|
||||
if (read(objectfd, buf, SELFMAG) != SELFMAG) {
|
||||
print_message1(2, "can't read module file\n");
|
||||
continue;
|
||||
}
|
||||
if (buf[0] != ELFMAG0 || buf[1] != ELFMAG1 ||
|
||||
buf[2] != ELFMAG2 || buf[3] != ELFMAG3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((strlen(name) > 0) && (strcmp(name, "a.out") != 0)) {
|
||||
strncpy(module.name, name, sizeof (module.name) - 1);
|
||||
++module_count;
|
||||
}
|
||||
@@ -384,53 +326,4 @@ int SolarisLwp::ListModules(
|
||||
return module_count;
|
||||
}
|
||||
|
||||
// Check if the address is a valid virtual address.
|
||||
// If the address is in any of the mapped modules, we take it as valid.
|
||||
// Otherwise it is invalid.
|
||||
bool SolarisLwp::IsAddressMapped(uintptr_t address) const {
|
||||
AddressValidatingContext addr;
|
||||
addr.address = address;
|
||||
CallbackParam<ModuleCallback> callback_param(AddressNotInModuleCallback,
|
||||
&addr);
|
||||
ListModules(&callback_param);
|
||||
return addr.is_mapped;
|
||||
}
|
||||
|
||||
// We're looking for a ucontext_t as the second parameter
|
||||
// to a signal handler function call. Luckily, the ucontext_t
|
||||
// has an ebp(fp on SPARC) member which should match the ebp(fp)
|
||||
// pointed to by the ebp(fp) of the signal handler frame.
|
||||
// The Solaris stack looks like this:
|
||||
// http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/lib/libproc/common/Pstack.c#81
|
||||
bool SolarisLwp::FindSigContext(uintptr_t sighandler_ebp,
|
||||
ucontext_t **sig_ctx) {
|
||||
uintptr_t previous_ebp;
|
||||
uintptr_t sig_ebp;
|
||||
const int MAX_STACK_DEPTH = 50;
|
||||
int depth_counter = 0;
|
||||
|
||||
do {
|
||||
#if TARGET_CPU_SPARC
|
||||
previous_ebp = reinterpret_cast<uintptr_t>(GetNextFrame(
|
||||
reinterpret_cast<void*>(sighandler_ebp)));
|
||||
*sig_ctx = reinterpret_cast<ucontext_t*>(sighandler_ebp + sizeof (struct frame));
|
||||
uintptr_t sig_esp = (*sig_ctx)->uc_mcontext.gregs[REG_O6];
|
||||
if (sig_esp < previous_ebp && sig_esp > sighandler_ebp)
|
||||
sig_ebp = (uintptr_t)(((struct frame *)sig_esp)->fr_savfp);
|
||||
|
||||
#elif TARGET_CPU_X86
|
||||
previous_ebp = reinterpret_cast<uintptr_t>(GetNextFrame(
|
||||
reinterpret_cast<void**>(sighandler_ebp)));
|
||||
*sig_ctx = reinterpret_cast<ucontext_t*>(sighandler_ebp + sizeof (struct frame) +
|
||||
3 * sizeof(uintptr_t));
|
||||
sig_ebp = (*sig_ctx)->uc_mcontext.gregs[EBP];
|
||||
#endif
|
||||
sighandler_ebp = previous_ebp;
|
||||
depth_counter++;
|
||||
} while(previous_ebp != sig_ebp && sighandler_ebp != 0 &&
|
||||
IsAddressMapped(sighandler_ebp) && depth_counter < MAX_STACK_DEPTH);
|
||||
|
||||
return previous_ebp == sig_ebp && previous_ebp != 0;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
@@ -32,18 +32,9 @@
|
||||
#ifndef CLIENT_SOLARIS_HANDLER_SOLARIS_LWP_H__
|
||||
#define CLIENT_SOLARIS_HANDLER_SOLARIS_LWP_H__
|
||||
|
||||
#if defined(sparc) || defined(__sparc)
|
||||
#define TARGET_CPU_SPARC 1
|
||||
#elif defined(i386) || defined(__i386)
|
||||
#define TARGET_CPU_X86 1
|
||||
#else
|
||||
#error "cannot determine cpu type"
|
||||
#endif
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/user.h>
|
||||
#include <ucontext.h>
|
||||
|
||||
#ifndef _KERNEL
|
||||
#define _KERNEL
|
||||
@@ -143,13 +134,6 @@ class SolarisLwp {
|
||||
// Get the bottom of the stack from esp.
|
||||
uintptr_t GetLwpStackBottom(uintptr_t current_esp) const;
|
||||
|
||||
// Finds a signal context on the stack given the ebp of our signal handler.
|
||||
bool FindSigContext(uintptr_t sighandler_ebp, ucontext_t **sig_ctx);
|
||||
|
||||
private:
|
||||
// Check if the address is a valid virtual address.
|
||||
bool IsAddressMapped(uintptr_t address) const;
|
||||
|
||||
private:
|
||||
// The pid of the process we are listing lwps.
|
||||
int pid_;
|
||||
|
||||
@@ -1,59 +1,46 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 9.00
|
||||
# Visual Studio 2005
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "exception_handler", "handler\exception_handler.vcproj", "{B55CA863-B374-4BAF-95AC-539E4FA4C90C}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4} = {A820AF62-6239-4693-8430-4F516C1838F4}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "crash_report_sender", "sender\crash_report_sender.vcproj", "{9946A048-043B-4F8F-9E07-9297B204714C}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "crash_generation", "crash_generation\crash_generation.vcproj", "{A820AF62-6239-4693-8430-4F516C1838F4}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "exception_handler_test", "handler\exception_handler_test\exception_handler_test.vcproj", "{89094A11-CF25-4037-AF43-EACFA751405E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
DebugStaticCRT|Win32 = DebugStaticCRT|Win32
|
||||
Release|Win32 = Release|Win32
|
||||
ReleaseStaticCRT|Win32 = ReleaseStaticCRT|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.DebugStaticCRT|Win32.ActiveCfg = DebugStaticCRT|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.DebugStaticCRT|Win32.Build.0 = DebugStaticCRT|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.Release|Win32.Build.0 = Release|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.ReleaseStaticCRT|Win32.ActiveCfg = ReleaseStaticCRT|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.ReleaseStaticCRT|Win32.Build.0 = ReleaseStaticCRT|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.DebugStaticCRT|Win32.ActiveCfg = DebugStaticCRT|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.DebugStaticCRT|Win32.Build.0 = DebugStaticCRT|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.Release|Win32.Build.0 = Release|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.ReleaseStaticCRT|Win32.ActiveCfg = ReleaseStaticCRT|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.ReleaseStaticCRT|Win32.Build.0 = ReleaseStaticCRT|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.DebugStaticCRT|Win32.ActiveCfg = DebugStaticCRT|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.DebugStaticCRT|Win32.Build.0 = DebugStaticCRT|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.Release|Win32.Build.0 = Release|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.ReleaseStaticCRT|Win32.ActiveCfg = ReleaseStaticCRT|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.ReleaseStaticCRT|Win32.Build.0 = ReleaseStaticCRT|Win32
|
||||
{89094A11-CF25-4037-AF43-EACFA751405E}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{89094A11-CF25-4037-AF43-EACFA751405E}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{89094A11-CF25-4037-AF43-EACFA751405E}.DebugStaticCRT|Win32.ActiveCfg = Debug|Win32
|
||||
{89094A11-CF25-4037-AF43-EACFA751405E}.DebugStaticCRT|Win32.Build.0 = Debug|Win32
|
||||
{89094A11-CF25-4037-AF43-EACFA751405E}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{89094A11-CF25-4037-AF43-EACFA751405E}.Release|Win32.Build.0 = Release|Win32
|
||||
{89094A11-CF25-4037-AF43-EACFA751405E}.ReleaseStaticCRT|Win32.ActiveCfg = Release|Win32
|
||||
{89094A11-CF25-4037-AF43-EACFA751405E}.ReleaseStaticCRT|Win32.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 9.00
|
||||
# Visual Studio 2005
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "exception_handler", "handler\exception_handler.vcproj", "{B55CA863-B374-4BAF-95AC-539E4FA4C90C}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "crash_report_sender", "sender\crash_report_sender.vcproj", "{9946A048-043B-4F8F-9E07-9297B204714C}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "crash_generation", "crash_generation\crash_generation.vcproj", "{A820AF62-6239-4693-8430-4F516C1838F4}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
DebugStaticCRT|Win32 = DebugStaticCRT|Win32
|
||||
Release|Win32 = Release|Win32
|
||||
ReleaseStaticCRT|Win32 = ReleaseStaticCRT|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.DebugStaticCRT|Win32.ActiveCfg = DebugStaticCRT|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.DebugStaticCRT|Win32.Build.0 = DebugStaticCRT|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.Release|Win32.Build.0 = Release|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.ReleaseStaticCRT|Win32.ActiveCfg = ReleaseStaticCRT|Win32
|
||||
{B55CA863-B374-4BAF-95AC-539E4FA4C90C}.ReleaseStaticCRT|Win32.Build.0 = ReleaseStaticCRT|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.DebugStaticCRT|Win32.ActiveCfg = DebugStaticCRT|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.DebugStaticCRT|Win32.Build.0 = DebugStaticCRT|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.Release|Win32.Build.0 = Release|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.ReleaseStaticCRT|Win32.ActiveCfg = ReleaseStaticCRT|Win32
|
||||
{9946A048-043B-4F8F-9E07-9297B204714C}.ReleaseStaticCRT|Win32.Build.0 = ReleaseStaticCRT|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.DebugStaticCRT|Win32.ActiveCfg = Debug|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.DebugStaticCRT|Win32.Build.0 = Debug|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.Release|Win32.Build.0 = Release|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.ReleaseStaticCRT|Win32.ActiveCfg = Release|Win32
|
||||
{A820AF62-6239-4693-8430-4F516C1838F4}.ReleaseStaticCRT|Win32.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__
|
||||
#define CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Automatically enters the critical section in the constructor and leaves
|
||||
// the critical section in the destructor.
|
||||
class AutoCriticalSection {
|
||||
public:
|
||||
// Creates a new instance with the given critical section object
|
||||
// and enters the critical section immediately.
|
||||
explicit AutoCriticalSection(CRITICAL_SECTION* cs) : cs_(cs) {
|
||||
assert(cs_);
|
||||
EnterCriticalSection(cs_);
|
||||
}
|
||||
|
||||
// Destructor: leaves the critical section.
|
||||
~AutoCriticalSection() {
|
||||
LeaveCriticalSection(cs_);
|
||||
}
|
||||
|
||||
private:
|
||||
// Disable copy ctor and operator=.
|
||||
AutoCriticalSection(const AutoCriticalSection&);
|
||||
AutoCriticalSection& operator=(const AutoCriticalSection&);
|
||||
|
||||
CRITICAL_SECTION* cs_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__
|
||||
#define CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Automatically enters the critical section in the constructor and leaves
|
||||
// the critical section in the destructor.
|
||||
class AutoCriticalSection {
|
||||
public:
|
||||
// Creates a new instance with the given critical section object
|
||||
// and enters the critical section immediately.
|
||||
explicit AutoCriticalSection(CRITICAL_SECTION* cs) : cs_(cs) {
|
||||
assert(cs_);
|
||||
EnterCriticalSection(cs_);
|
||||
}
|
||||
|
||||
// Destructor: leaves the critical section.
|
||||
~AutoCriticalSection() {
|
||||
LeaveCriticalSection(cs_);
|
||||
}
|
||||
|
||||
private:
|
||||
// Disable copy ctor and operator=.
|
||||
AutoCriticalSection(const AutoCriticalSection&);
|
||||
AutoCriticalSection& operator=(const AutoCriticalSection&);
|
||||
|
||||
CRITICAL_SECTION* cs_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__
|
||||
|
||||
@@ -1,179 +1,122 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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_WINDOWS_COMMON_IPC_PROTOCOL_H__
|
||||
#define CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__
|
||||
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include "common/windows/string_utils-inl.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Name/value pair for custom client information.
|
||||
struct CustomInfoEntry {
|
||||
// Maximum length for name and value for client custom info.
|
||||
static const int kNameMaxLength = 64;
|
||||
static const int kValueMaxLength = 64;
|
||||
|
||||
CustomInfoEntry() {
|
||||
// Putting name and value in initializer list makes VC++ show warning 4351.
|
||||
set_name(NULL);
|
||||
set_value(NULL);
|
||||
}
|
||||
|
||||
CustomInfoEntry(const wchar_t* name_arg, const wchar_t* value_arg) {
|
||||
set_name(name_arg);
|
||||
set_value(value_arg);
|
||||
}
|
||||
|
||||
void set_name(const wchar_t* name_arg) {
|
||||
if (!name_arg) {
|
||||
name[0] = L'\0';
|
||||
return;
|
||||
}
|
||||
WindowsStringUtils::safe_wcscpy(name, kNameMaxLength, name_arg);
|
||||
}
|
||||
|
||||
void set_value(const wchar_t* value_arg) {
|
||||
if (!value_arg) {
|
||||
value[0] = L'\0';
|
||||
return;
|
||||
}
|
||||
|
||||
WindowsStringUtils::safe_wcscpy(value, kValueMaxLength, value_arg);
|
||||
}
|
||||
|
||||
void set(const wchar_t* name_arg, const wchar_t* value_arg) {
|
||||
set_name(name_arg);
|
||||
set_value(value_arg);
|
||||
}
|
||||
|
||||
wchar_t name[kNameMaxLength];
|
||||
wchar_t value[kValueMaxLength];
|
||||
};
|
||||
|
||||
// Constants for the protocol between client and the server.
|
||||
|
||||
// Tags sent with each message indicating the purpose of
|
||||
// the message.
|
||||
enum MessageTag {
|
||||
MESSAGE_TAG_NONE = 0,
|
||||
MESSAGE_TAG_REGISTRATION_REQUEST = 1,
|
||||
MESSAGE_TAG_REGISTRATION_RESPONSE = 2,
|
||||
MESSAGE_TAG_REGISTRATION_ACK = 3
|
||||
};
|
||||
|
||||
struct CustomClientInfo {
|
||||
const CustomInfoEntry* entries;
|
||||
int count;
|
||||
};
|
||||
|
||||
// Message structure for IPC between crash client and crash server.
|
||||
struct ProtocolMessage {
|
||||
ProtocolMessage()
|
||||
: tag(MESSAGE_TAG_NONE),
|
||||
pid(0),
|
||||
dump_type(MiniDumpNormal),
|
||||
thread_id(0),
|
||||
exception_pointers(NULL),
|
||||
assert_info(NULL),
|
||||
custom_client_info(),
|
||||
dump_request_handle(NULL),
|
||||
dump_generated_handle(NULL),
|
||||
server_alive_handle(NULL) {
|
||||
}
|
||||
|
||||
// Creates an instance with the given parameters.
|
||||
ProtocolMessage(MessageTag arg_tag,
|
||||
DWORD arg_pid,
|
||||
MINIDUMP_TYPE arg_dump_type,
|
||||
DWORD* arg_thread_id,
|
||||
EXCEPTION_POINTERS** arg_exception_pointers,
|
||||
MDRawAssertionInfo* arg_assert_info,
|
||||
const CustomClientInfo& custom_info,
|
||||
HANDLE arg_dump_request_handle,
|
||||
HANDLE arg_dump_generated_handle,
|
||||
HANDLE arg_server_alive)
|
||||
: tag(arg_tag),
|
||||
pid(arg_pid),
|
||||
dump_type(arg_dump_type),
|
||||
thread_id(arg_thread_id),
|
||||
exception_pointers(arg_exception_pointers),
|
||||
assert_info(arg_assert_info),
|
||||
custom_client_info(custom_info),
|
||||
dump_request_handle(arg_dump_request_handle),
|
||||
dump_generated_handle(arg_dump_generated_handle),
|
||||
server_alive_handle(arg_server_alive) {
|
||||
}
|
||||
|
||||
// Tag in the message.
|
||||
MessageTag tag;
|
||||
|
||||
// Process id.
|
||||
DWORD pid;
|
||||
|
||||
// Dump type requested.
|
||||
MINIDUMP_TYPE dump_type;
|
||||
|
||||
// Client thread id pointer.
|
||||
DWORD* thread_id;
|
||||
|
||||
// Exception information.
|
||||
EXCEPTION_POINTERS** exception_pointers;
|
||||
|
||||
// Assert information in case of an invalid parameter or
|
||||
// pure call failure.
|
||||
MDRawAssertionInfo* assert_info;
|
||||
|
||||
// Custom client information.
|
||||
CustomClientInfo custom_client_info;
|
||||
|
||||
// Handle to signal the crash event.
|
||||
HANDLE dump_request_handle;
|
||||
|
||||
// Handle to check if server is done generating crash.
|
||||
HANDLE dump_generated_handle;
|
||||
|
||||
// Handle to a mutex that becomes signaled (WAIT_ABANDONED)
|
||||
// if server process goes down.
|
||||
HANDLE server_alive_handle;
|
||||
|
||||
private:
|
||||
// Disable copy ctor and operator=.
|
||||
ProtocolMessage(const ProtocolMessage& msg);
|
||||
ProtocolMessage& operator=(const ProtocolMessage& msg);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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_WINDOWS_COMMON_IPC_PROTOCOL_H__
|
||||
#define CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__
|
||||
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Constants for the protocol between client and the server.
|
||||
|
||||
// Tags sent with each message indicating the purpose of
|
||||
// the message.
|
||||
enum MessageTag {
|
||||
MESSAGE_TAG_NONE = 0,
|
||||
MESSAGE_TAG_REGISTRATION_REQUEST = 1,
|
||||
MESSAGE_TAG_REGISTRATION_RESPONSE = 2,
|
||||
MESSAGE_TAG_REGISTRATION_ACK = 3
|
||||
};
|
||||
|
||||
// Message structure for IPC between crash client and crash server.
|
||||
struct ProtocolMessage {
|
||||
ProtocolMessage()
|
||||
: tag(MESSAGE_TAG_NONE),
|
||||
pid(0),
|
||||
dump_type(MiniDumpNormal),
|
||||
thread_id(0),
|
||||
exception_pointers(NULL),
|
||||
assert_info(NULL),
|
||||
dump_request_handle(NULL),
|
||||
dump_generated_handle(NULL),
|
||||
server_alive_handle(NULL) {
|
||||
}
|
||||
|
||||
// Creates an instance with the given parameters.
|
||||
ProtocolMessage(MessageTag arg_tag,
|
||||
DWORD arg_pid,
|
||||
MINIDUMP_TYPE arg_dump_type,
|
||||
DWORD* arg_thread_id,
|
||||
EXCEPTION_POINTERS** arg_exception_pointers,
|
||||
MDRawAssertionInfo* arg_assert_info,
|
||||
HANDLE arg_dump_request_handle,
|
||||
HANDLE arg_dump_generated_handle,
|
||||
HANDLE arg_server_alive)
|
||||
: tag(arg_tag),
|
||||
pid(arg_pid),
|
||||
dump_type(arg_dump_type),
|
||||
thread_id(arg_thread_id),
|
||||
exception_pointers(arg_exception_pointers),
|
||||
assert_info(arg_assert_info),
|
||||
dump_request_handle(arg_dump_request_handle),
|
||||
dump_generated_handle(arg_dump_generated_handle),
|
||||
server_alive_handle(arg_server_alive) {
|
||||
}
|
||||
|
||||
// Tag in the message.
|
||||
MessageTag tag;
|
||||
|
||||
// Process id.
|
||||
DWORD pid;
|
||||
|
||||
// Dump type requested.
|
||||
MINIDUMP_TYPE dump_type;
|
||||
|
||||
// Client thread id pointer.
|
||||
DWORD* thread_id;
|
||||
|
||||
// Exception information.
|
||||
EXCEPTION_POINTERS** exception_pointers;
|
||||
|
||||
// Assert information in case of an invalid parameter or
|
||||
// pure call failure.
|
||||
MDRawAssertionInfo* assert_info;
|
||||
|
||||
// Handle to signal the crash event.
|
||||
HANDLE dump_request_handle;
|
||||
|
||||
// Handle to check if server is done generating crash.
|
||||
HANDLE dump_generated_handle;
|
||||
|
||||
// Handle to a mutex that becomes signaled (WAIT_ABANDONED)
|
||||
// if server process goes down.
|
||||
HANDLE server_alive_handle;
|
||||
|
||||
private:
|
||||
// Disable copy ctor and operator=.
|
||||
ProtocolMessage(const ProtocolMessage& msg);
|
||||
ProtocolMessage& operator=(const ProtocolMessage& msg);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
=========================================================================
|
||||
State machine transitions for the Crash Generation Server
|
||||
=========================================================================
|
||||
|
||||
=========================================================================
|
||||
|
|
||||
STATE | ACTIONS
|
||||
|
|
||||
=========================================================================
|
||||
ERROR | Clean up resources used to serve clients.
|
||||
| Always remain in ERROR state.
|
||||
-------------------------------------------------------------------------
|
||||
INITIAL | Connect to the pipe asynchronously.
|
||||
| If connection is successfully queued up asynchronously,
|
||||
| go into CONNECTING state.
|
||||
| If connection is done synchronously, go into CONNECTED
|
||||
| state.
|
||||
| For any unexpected problems, go into ERROR state.
|
||||
-------------------------------------------------------------------------
|
||||
CONNECTING | Get the result of async connection request.
|
||||
| If I/O is still incomplete, remain in the CONNECTING
|
||||
| state.
|
||||
| If connection is complete, go into CONNECTED state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
CONNECTED | Read from the pipe asynchronously.
|
||||
| If read request is successfully queued up asynchronously,
|
||||
| go into READING state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
READING | Get the result of async read request.
|
||||
| If read is done, go into READ_DONE state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
READ_DONE | Register the client, prepare the reply and write the
|
||||
| reply to the pipe asynchronously.
|
||||
| If write request is successfully queued up asynchronously,
|
||||
| go into WRITING state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
WRITING | Get the result of the async write request.
|
||||
| If write is done, go into WRITE_DONE state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
WRITE_DONE | Read from the pipe asynchronously (for an ACK).
|
||||
| If read request is successfully queued up asynchonously,
|
||||
| go into READING_ACK state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
READING_ACK | Get the result of the async read request.
|
||||
| If read is done, perform action for successful client
|
||||
| connection.
|
||||
| Go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
DISCONNECTING | Disconnect from the pipe, reset the event and go into
|
||||
| INITIAL state and signal the event again. If anything
|
||||
| fails, go into ERROR state.
|
||||
=========================================================================
|
||||
=========================================================================
|
||||
State machine transitions for the Crash Generation Server
|
||||
=========================================================================
|
||||
|
||||
=========================================================================
|
||||
|
|
||||
STATE | ACTIONS
|
||||
|
|
||||
=========================================================================
|
||||
ERROR | Clean up resources used to serve clients.
|
||||
| Always remain in ERROR state.
|
||||
-------------------------------------------------------------------------
|
||||
INITIAL | Connect to the pipe asynchronously.
|
||||
| If connection is successfully queued up asynchronously,
|
||||
| go into CONNECTING state.
|
||||
| If connection is done synchronously, go into CONNECTED
|
||||
| state.
|
||||
| For any unexpected problems, go into ERROR state.
|
||||
-------------------------------------------------------------------------
|
||||
CONNECTING | Get the result of async connection request.
|
||||
| If I/O is still incomplete, remain in the CONNECTING
|
||||
| state.
|
||||
| If connection is complete, go into CONNECTED state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
CONNECTED | Read from the pipe asynchronously.
|
||||
| If read request is successfully queued up asynchronously,
|
||||
| go into READING state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
READING | Get the result of async read request.
|
||||
| If read is done, go into READ_DONE state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
READ_DONE | Register the client, prepare the reply and write the
|
||||
| reply to the pipe asynchronously.
|
||||
| If write request is successfully queued up asynchronously,
|
||||
| go into WRITING state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
WRITING | Get the result of the async write request.
|
||||
| If write is done, go into WRITE_DONE state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
WRITE_DONE | Read from the pipe asynchronously (for an ACK).
|
||||
| If read request is successfully queued up asynchonously,
|
||||
| go into READING_ACK state.
|
||||
| For any unexpected problems, go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
READING_ACK | Get the result of the async read request.
|
||||
| If read is done, perform action for successful client
|
||||
| connection.
|
||||
| Go into DISCONNECTING state.
|
||||
-------------------------------------------------------------------------
|
||||
DISCONNECTING | Disconnect from the pipe, reset the event and go into
|
||||
| INITIAL state and signal the event again. If anything
|
||||
| fails, go into ERROR state.
|
||||
=========================================================================
|
||||
|
||||
@@ -1,210 +1,147 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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 "client/windows/crash_generation/client_info.h"
|
||||
#include "client/windows/common/ipc_protocol.h"
|
||||
|
||||
static const wchar_t kCustomInfoProcessUptimeName[] = L"ptime";
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
ClientInfo::ClientInfo(CrashGenerationServer* crash_server,
|
||||
DWORD pid,
|
||||
MINIDUMP_TYPE dump_type,
|
||||
DWORD* thread_id,
|
||||
EXCEPTION_POINTERS** ex_info,
|
||||
MDRawAssertionInfo* assert_info,
|
||||
const CustomClientInfo& custom_client_info)
|
||||
: crash_server_(crash_server),
|
||||
pid_(pid),
|
||||
dump_type_(dump_type),
|
||||
ex_info_(ex_info),
|
||||
assert_info_(assert_info),
|
||||
custom_client_info_(custom_client_info),
|
||||
thread_id_(thread_id),
|
||||
process_handle_(NULL),
|
||||
dump_requested_handle_(NULL),
|
||||
dump_generated_handle_(NULL),
|
||||
dump_request_wait_handle_(NULL),
|
||||
process_exit_wait_handle_(NULL) {
|
||||
GetSystemTimeAsFileTime(&start_time_);
|
||||
}
|
||||
|
||||
bool ClientInfo::Initialize() {
|
||||
process_handle_ = OpenProcess(GENERIC_ALL, FALSE, pid_);
|
||||
if (!process_handle_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dump_requested_handle_ = CreateEvent(NULL, // Security attributes.
|
||||
TRUE, // Manual reset.
|
||||
FALSE, // Initial state.
|
||||
NULL); // Name.
|
||||
if (!dump_requested_handle_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dump_generated_handle_ = CreateEvent(NULL, // Security attributes.
|
||||
TRUE, // Manual reset.
|
||||
FALSE, // Initial state.
|
||||
NULL); // Name.
|
||||
return dump_generated_handle_ != NULL;
|
||||
}
|
||||
|
||||
ClientInfo::~ClientInfo() {
|
||||
if (dump_request_wait_handle_) {
|
||||
// Wait for callbacks that might already be running to finish.
|
||||
UnregisterWaitEx(dump_request_wait_handle_, INVALID_HANDLE_VALUE);
|
||||
}
|
||||
|
||||
if (process_exit_wait_handle_) {
|
||||
// Wait for the callback that might already be running to finish.
|
||||
UnregisterWaitEx(process_exit_wait_handle_, INVALID_HANDLE_VALUE);
|
||||
}
|
||||
|
||||
if (process_handle_) {
|
||||
CloseHandle(process_handle_);
|
||||
}
|
||||
|
||||
if (dump_requested_handle_) {
|
||||
CloseHandle(dump_requested_handle_);
|
||||
}
|
||||
|
||||
if (dump_generated_handle_) {
|
||||
CloseHandle(dump_generated_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientInfo::UnregisterWaits() {
|
||||
bool success = true;
|
||||
|
||||
if (dump_request_wait_handle_) {
|
||||
if (!UnregisterWait(dump_request_wait_handle_)) {
|
||||
success = false;
|
||||
} else {
|
||||
dump_request_wait_handle_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (process_exit_wait_handle_) {
|
||||
if (!UnregisterWait(process_exit_wait_handle_)) {
|
||||
success = false;
|
||||
} else {
|
||||
process_exit_wait_handle_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ClientInfo::GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const {
|
||||
SIZE_T bytes_count = 0;
|
||||
if (!ReadProcessMemory(process_handle_,
|
||||
ex_info_,
|
||||
ex_info,
|
||||
sizeof(*ex_info),
|
||||
&bytes_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bytes_count == sizeof(*ex_info);
|
||||
}
|
||||
|
||||
bool ClientInfo::GetClientThreadId(DWORD* thread_id) const {
|
||||
SIZE_T bytes_count = 0;
|
||||
if (!ReadProcessMemory(process_handle_,
|
||||
thread_id_,
|
||||
thread_id,
|
||||
sizeof(*thread_id),
|
||||
&bytes_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bytes_count == sizeof(*thread_id);
|
||||
}
|
||||
|
||||
void ClientInfo::SetProcessUptime() {
|
||||
FILETIME now = {0};
|
||||
GetSystemTimeAsFileTime(&now);
|
||||
|
||||
ULARGE_INTEGER time_start;
|
||||
time_start.HighPart = start_time_.dwHighDateTime;
|
||||
time_start.LowPart = start_time_.dwLowDateTime;
|
||||
|
||||
ULARGE_INTEGER time_now;
|
||||
time_now.HighPart = now.dwHighDateTime;
|
||||
time_now.LowPart = now.dwLowDateTime;
|
||||
|
||||
// Calculate the delay and convert it from 100-nanoseconds to milliseconds.
|
||||
__int64 delay = (time_now.QuadPart - time_start.QuadPart) / 10 / 1000;
|
||||
|
||||
// Convert it to a string.
|
||||
wchar_t* value = custom_info_entries_.get()[custom_client_info_.count].value;
|
||||
_i64tow_s(delay, value, CustomInfoEntry::kValueMaxLength, 10);
|
||||
}
|
||||
|
||||
bool ClientInfo::PopulateCustomInfo() {
|
||||
SIZE_T bytes_count = 0;
|
||||
SIZE_T read_count = sizeof(CustomInfoEntry) * custom_client_info_.count;
|
||||
|
||||
// If the scoped array for custom info already has an array, it will be
|
||||
// the same size as what we need. This is because the number of custom info
|
||||
// entries is always the same. So allocate memory only if scoped array has
|
||||
// a NULL pointer.
|
||||
if (!custom_info_entries_.get()) {
|
||||
// Allocate an extra entry for reporting uptime for the client process.
|
||||
custom_info_entries_.reset(
|
||||
new CustomInfoEntry[custom_client_info_.count + 1]);
|
||||
// Use the last element in the array for uptime.
|
||||
custom_info_entries_.get()[custom_client_info_.count].set_name(
|
||||
kCustomInfoProcessUptimeName);
|
||||
}
|
||||
|
||||
if (!ReadProcessMemory(process_handle_,
|
||||
custom_client_info_.entries,
|
||||
custom_info_entries_.get(),
|
||||
read_count,
|
||||
&bytes_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetProcessUptime();
|
||||
return (bytes_count != read_count);
|
||||
}
|
||||
|
||||
CustomClientInfo ClientInfo::GetCustomInfo() const {
|
||||
CustomClientInfo custom_info;
|
||||
custom_info.entries = custom_info_entries_.get();
|
||||
// Add 1 to the count from the client process to account for extra entry for
|
||||
// process uptime.
|
||||
custom_info.count = custom_client_info_.count + 1;
|
||||
return custom_info;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (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 "client/windows/crash_generation/client_info.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
ClientInfo::ClientInfo(CrashGenerationServer* crash_server,
|
||||
DWORD pid,
|
||||
MINIDUMP_TYPE dump_type,
|
||||
DWORD* thread_id,
|
||||
EXCEPTION_POINTERS** ex_info,
|
||||
MDRawAssertionInfo* assert_info)
|
||||
: crash_server_(crash_server),
|
||||
pid_(pid),
|
||||
dump_type_(dump_type),
|
||||
ex_info_(ex_info),
|
||||
assert_info_(assert_info),
|
||||
thread_id_(thread_id),
|
||||
process_handle_(NULL),
|
||||
dump_requested_handle_(NULL),
|
||||
dump_generated_handle_(NULL),
|
||||
dump_request_wait_handle_(NULL),
|
||||
process_exit_wait_handle_(NULL) {
|
||||
}
|
||||
|
||||
bool ClientInfo::Initialize() {
|
||||
process_handle_ = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid_);
|
||||
if (!process_handle_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dump_requested_handle_ = CreateEvent(NULL, // Security attributes.
|
||||
TRUE, // Manual reset.
|
||||
FALSE, // Initial state.
|
||||
NULL); // Name.
|
||||
if (!dump_requested_handle_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dump_generated_handle_ = CreateEvent(NULL, // Security attributes.
|
||||
TRUE, // Manual reset.
|
||||
FALSE, // Initial state.
|
||||
NULL); // Name.
|
||||
return dump_generated_handle_ != NULL;
|
||||
}
|
||||
|
||||
ClientInfo::~ClientInfo() {
|
||||
if (process_handle_) {
|
||||
CloseHandle(process_handle_);
|
||||
}
|
||||
|
||||
if (dump_requested_handle_) {
|
||||
CloseHandle(dump_requested_handle_);
|
||||
}
|
||||
|
||||
if (dump_generated_handle_) {
|
||||
CloseHandle(dump_generated_handle_);
|
||||
}
|
||||
|
||||
if (dump_request_wait_handle_) {
|
||||
// Wait for callbacks that might already be running to finish.
|
||||
UnregisterWaitEx(dump_request_wait_handle_, INVALID_HANDLE_VALUE);
|
||||
}
|
||||
|
||||
if (process_exit_wait_handle_) {
|
||||
// Wait for the callback that might already be running to finish.
|
||||
UnregisterWaitEx(process_exit_wait_handle_, INVALID_HANDLE_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientInfo::UnregisterWaits() {
|
||||
bool success = true;
|
||||
|
||||
if (dump_request_wait_handle_) {
|
||||
if (!UnregisterWait(dump_request_wait_handle_)) {
|
||||
success = false;
|
||||
} else {
|
||||
dump_request_wait_handle_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (process_exit_wait_handle_) {
|
||||
if (!UnregisterWait(process_exit_wait_handle_)) {
|
||||
success = false;
|
||||
} else {
|
||||
process_exit_wait_handle_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ClientInfo::GetClientExceptionInfo(
|
||||
EXCEPTION_POINTERS** ex_info) const {
|
||||
SIZE_T bytes_count = 0;
|
||||
if (!ReadProcessMemory(process_handle_,
|
||||
ex_info_,
|
||||
ex_info,
|
||||
sizeof(*ex_info),
|
||||
&bytes_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bytes_count == sizeof(*ex_info);
|
||||
}
|
||||
|
||||
bool ClientInfo::GetClientThreadId(DWORD* thread_id) const {
|
||||
SIZE_T bytes_count = 0;
|
||||
if (!ReadProcessMemory(process_handle_,
|
||||
thread_id_,
|
||||
thread_id,
|
||||
sizeof(*thread_id),
|
||||
&bytes_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bytes_count == sizeof(*thread_id);
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user