зеркало из https://github.com/mozilla/pjs.git
Update breakpad to svn revision 350 + Linux build fix. b=510920 r=ted
This commit is contained in:
Родитель
c5d79decfe
Коммит
589944a6c6
|
@ -152,7 +152,14 @@ 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/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_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
|
|
|
@ -147,8 +147,9 @@ 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/minidump_processor_unittest.$(OBJEXT)
|
||||
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)
|
||||
src_processor_minidump_processor_unittest_OBJECTS = \
|
||||
$(am_src_processor_minidump_processor_unittest_OBJECTS)
|
||||
src_processor_minidump_processor_unittest_DEPENDENCIES = \
|
||||
|
@ -289,6 +290,7 @@ CXXFLAGS = @CXXFLAGS@
|
|||
CYGPATH_W = @CYGPATH_W@
|
||||
DEFS = @DEFS@
|
||||
DEPDIR = @DEPDIR@
|
||||
DSYMUTIL = @DSYMUTIL@
|
||||
ECHO = @ECHO@
|
||||
ECHO_C = @ECHO_C@
|
||||
ECHO_N = @ECHO_N@
|
||||
|
@ -312,6 +314,7 @@ LN_S = @LN_S@
|
|||
LTLIBOBJS = @LTLIBOBJS@
|
||||
MAKEINFO = @MAKEINFO@
|
||||
MKDIR_P = @MKDIR_P@
|
||||
NMEDIT = @NMEDIT@
|
||||
OBJEXT = @OBJEXT@
|
||||
PACKAGE = @PACKAGE@
|
||||
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
|
||||
|
@ -321,6 +324,7 @@ PACKAGE_TARNAME = @PACKAGE_TARNAME@
|
|||
PACKAGE_VERSION = @PACKAGE_VERSION@
|
||||
PATH_SEPARATOR = @PATH_SEPARATOR@
|
||||
RANLIB = @RANLIB@
|
||||
SED = @SED@
|
||||
SET_MAKE = @SET_MAKE@
|
||||
SHELL = @SHELL@
|
||||
STRIP = @STRIP@
|
||||
|
@ -471,7 +475,15 @@ 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/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_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
|
@ -871,9 +883,27 @@ 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/minidump_processor_unittest.$(OBJEXT): \
|
||||
src/processor/src_processor_minidump_processor_unittest-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)
|
||||
|
@ -926,7 +956,6 @@ 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/pathname_stripper.$(OBJEXT)
|
||||
-rm -f src/processor/pathname_stripper.lo
|
||||
|
@ -937,6 +966,7 @@ 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)
|
||||
|
@ -948,6 +978,8 @@ 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
|
||||
|
@ -962,7 +994,6 @@ distclean-compile:
|
|||
@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)/pathname_stripper.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/pathname_stripper_unittest.Po@am__quote@
|
||||
|
@ -970,12 +1001,15 @@ distclean-compile:
|
|||
@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$$||'`;\
|
||||
|
@ -1001,6 +1035,48 @@ 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
|
||||
|
||||
|
@ -1309,6 +1385,10 @@ 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"
|
||||
|
@ -1321,7 +1401,7 @@ clean-am: clean-binPROGRAMS clean-checkPROGRAMS clean-generic \
|
|||
|
||||
distclean: distclean-am
|
||||
-rm -f $(am__CONFIG_DISTCLEAN_FILES)
|
||||
-rm -rf src/processor/$(DEPDIR)
|
||||
-rm -rf src/processor/$(DEPDIR) src/testing/gtest/src/$(DEPDIR) src/testing/src/$(DEPDIR)
|
||||
-rm -f Makefile
|
||||
distclean-am: clean-am distclean-compile distclean-generic \
|
||||
distclean-hdr distclean-libtool distclean-tags
|
||||
|
@ -1357,7 +1437,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)
|
||||
-rm -rf src/processor/$(DEPDIR) src/testing/gtest/src/$(DEPDIR) src/testing/src/$(DEPDIR)
|
||||
-rm -f Makefile
|
||||
maintainer-clean-am: distclean-am maintainer-clean-generic
|
||||
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,36 @@
|
|||
// 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__
|
|
@ -32,10 +32,12 @@
|
|||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <linux/limits.h>
|
||||
|
||||
|
@ -81,6 +83,15 @@ ExceptionHandler::ExceptionHandler(const string &dump_path,
|
|||
installed_handler_(install_handler) {
|
||||
set_dump_path(dump_path);
|
||||
|
||||
act_.sa_handler = HandleException;
|
||||
act_.sa_flags = SA_ONSTACK;
|
||||
sigemptyset(&act_.sa_mask);
|
||||
// now, make sure we're blocking all the signals we are handling
|
||||
// when we're handling any of them
|
||||
for ( size_t i = 0; i < sizeof(SigTable) / sizeof(SigTable[0]); ++i) {
|
||||
sigaddset(&act_.sa_mask, SigTable[i]);
|
||||
}
|
||||
|
||||
if (install_handler) {
|
||||
SetupHandler();
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
|
@ -149,20 +160,26 @@ void ExceptionHandler::SetupHandler() {
|
|||
}
|
||||
|
||||
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;
|
||||
|
||||
// We're storing pointers to the old signal action
|
||||
// structure, rather than copying the structure
|
||||
// because we can't count on the sa_mask field to
|
||||
// be scalar.
|
||||
struct sigaction *old_act = &old_actions_[signo];
|
||||
|
||||
if (sigaction(signo, &act_, old_act) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
TeardownHandler(signo, NULL);
|
||||
}
|
||||
|
||||
void ExceptionHandler::TeardownHandler(int signo, struct sigaction *final_handler) {
|
||||
if (old_actions_[signo].sa_handler) {
|
||||
struct sigaction *act = &old_actions_[signo];
|
||||
sigaction(signo, act, final_handler);
|
||||
memset(&old_actions_[signo], 0x0, sizeof(struct sigaction));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,7 +210,8 @@ void ExceptionHandler::HandleException(int signo) {
|
|||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
|
||||
// Restore original handler.
|
||||
current_handler->TeardownHandler(signo);
|
||||
struct sigaction old_action;
|
||||
current_handler->TeardownHandler(signo, &old_action);
|
||||
|
||||
struct sigcontext *sig_ctx = NULL;
|
||||
if (current_handler->InternalWriteMinidump(signo, current_ebp, &sig_ctx)) {
|
||||
|
@ -202,11 +220,23 @@ void ExceptionHandler::HandleException(int signo) {
|
|||
} 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)
|
||||
if (old_action.sa_handler != NULL && sig_ctx != NULL) {
|
||||
|
||||
// Have our own typedef, because of the comment above w.r.t signal
|
||||
// context on the stack
|
||||
typedef void (*SignalHandler)(int signo, struct sigcontext);
|
||||
|
||||
SignalHandler old_handler =
|
||||
reinterpret_cast<SignalHandler>(old_action.sa_handler);
|
||||
|
||||
sigset_t old_set;
|
||||
// Use SIG_BLOCK here because we don't want to unblock a signal
|
||||
// that the signal handler we're currently in needs to block
|
||||
sigprocmask(SIG_BLOCK, &old_action.sa_mask, &old_set);
|
||||
old_handler(signo, *sig_ctx);
|
||||
sigprocmask(SIG_SETMASK, &old_set, NULL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
|
@ -247,7 +277,7 @@ bool ExceptionHandler::InternalWriteMinidump(int signo,
|
|||
|
||||
// Unblock the signals.
|
||||
if (blocked) {
|
||||
sigprocmask(SIG_SETMASK, &sig_old, &sig_old);
|
||||
sigprocmask(SIG_SETMASK, &sig_old, NULL);
|
||||
}
|
||||
|
||||
if (callback_)
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <signal.h>
|
||||
#include <vector>
|
||||
|
||||
#include "client/linux/handler/minidump_generator.h"
|
||||
|
@ -146,6 +147,8 @@ class ExceptionHandler {
|
|||
void SetupHandler(int signo);
|
||||
// Teardown the handler for a signal.
|
||||
void TeardownHandler(int signo);
|
||||
// Teardown the handler for a signal.
|
||||
void TeardownHandler(int signo, struct sigaction *old);
|
||||
// Teardown all handlers.
|
||||
void TeardownAllHandler();
|
||||
|
||||
|
@ -192,10 +195,6 @@ class ExceptionHandler {
|
|||
// when created (with an install_handler parameter set to true).
|
||||
bool installed_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.
|
||||
|
@ -210,6 +209,16 @@ class ExceptionHandler {
|
|||
// disallow copy ctor and operator=
|
||||
explicit ExceptionHandler(const ExceptionHandler &);
|
||||
void operator=(const ExceptionHandler &);
|
||||
|
||||
// The sigactions structure we use for each signal
|
||||
struct sigaction act_;
|
||||
|
||||
|
||||
// Keep the previous handlers for the signal.
|
||||
// We're wasting a bit of memory here since we only change
|
||||
// the handler for some signals but i want to avoid allocating
|
||||
// memory in the signal handler
|
||||
struct sigaction old_actions_[NSIG];
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include <sys/wait.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <string.h>
|
||||
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,303 @@
|
|||
// 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_"
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
// 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
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,8 @@
|
|||
//
|
||||
// Prefix header for all source files of the 'Breakpad' target in the
|
||||
// 'Breakpad' project.
|
||||
//
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#endif
|
|
@ -0,0 +1,26 @@
|
|||
<?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>
|
|
@ -0,0 +1,146 @@
|
|||
// 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_;
|
||||
};
|
|
@ -0,0 +1,145 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?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>
|
|
@ -0,0 +1,193 @@
|
|||
// 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
|
|
@ -0,0 +1,534 @@
|
|||
// 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
|
||||
|
||||
// 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();
|
||||
|
||||
config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
|
||||
} else {
|
||||
PRINT_MACH_RESULT(result, "Inspector: key/value message");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (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
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
// 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.
|
||||
//
|
||||
// Main driver for Inspector
|
||||
|
||||
#import "client/mac/crash_generation/Inspector.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
//=============================================================================
|
||||
extern "C" {
|
||||
|
||||
int main(int argc, char *const argv[]) {
|
||||
#if DEBUG
|
||||
// Since we're launched on-demand, this is necessary to see debugging
|
||||
// output in the console window.
|
||||
freopen("/dev/console", "w", stdout);
|
||||
freopen("/dev/console", "w", stderr);
|
||||
#endif
|
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
if (argc != 2) {
|
||||
exit(0);
|
||||
}
|
||||
// Our first command-line argument contains the name of the service
|
||||
// that we're providing.
|
||||
google_breakpad::Inspector inspector;
|
||||
inspector.Inspect(argv[1]);
|
||||
|
||||
[pool release];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
} // namespace google_breakpad
|
Двоичный файл не отображается.
|
@ -296,6 +296,7 @@ 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;
|
||||
|
||||
|
@ -303,6 +304,7 @@ 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);
|
||||
|
@ -320,7 +322,8 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
|
|||
if (filter_ && !filter_(callback_context_))
|
||||
return false;
|
||||
|
||||
md.SetExceptionInformation(exception_type, exception_code, thread_name);
|
||||
md.SetExceptionInformation(exception_type, exception_code,
|
||||
exception_subcode, thread_name);
|
||||
}
|
||||
|
||||
result = md.Write(next_minidump_path_c_);
|
||||
|
@ -476,7 +479,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);
|
||||
self->WriteMinidumpWithException(0, 0, 0, 0);
|
||||
|
||||
self->UninstallHandler(false);
|
||||
|
||||
|
@ -490,7 +493,6 @@ 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
|
||||
|
@ -507,11 +509,15 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
|
|||
gBreakpadAllocator->Unprotect();
|
||||
#endif
|
||||
|
||||
// Generate the minidump with the exception data.
|
||||
self->WriteMinidumpWithException(receive.exception, receive.code[0],
|
||||
receive.thread.name);
|
||||
int subcode = 0;
|
||||
if (receive.exception == EXC_BAD_ACCESS && receive.code_count > 1)
|
||||
subcode = receive.code[1];
|
||||
|
||||
self->UninstallHandler(true);
|
||||
// Generate the minidump with the exception data.
|
||||
self->WriteMinidumpWithException(receive.exception, receive.code[0],
|
||||
subcode, receive.thread.name);
|
||||
|
||||
self->UninstallHandler(true);
|
||||
|
||||
#if USE_PROTECTED_ALLOCATIONS
|
||||
if(gBreakpadAllocator)
|
||||
|
|
|
@ -77,6 +77,7 @@ 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.
|
||||
|
@ -135,7 +136,7 @@ class ExceptionHandler {
|
|||
|
||||
// All minidump writing goes through this one routine
|
||||
bool WriteMinidumpWithException(int exception_type, int exception_code,
|
||||
mach_port_t thread_name);
|
||||
int exception_subcode, mach_port_t thread_name);
|
||||
|
||||
// When installed, this static function will be call from a newly created
|
||||
// pthread with |this| as the argument
|
||||
|
|
|
@ -28,15 +28,7 @@
|
|||
// 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
|
||||
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
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
|
@ -55,10 +47,11 @@ static void *SleepyFunction(void *) {
|
|||
while (1) {
|
||||
sleep(10000);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void Crasher() {
|
||||
int *a = NULL;
|
||||
int *a = (int*)0x42;
|
||||
|
||||
fprintf(stdout, "Going to crash...\n");
|
||||
fprintf(stdout, "A = %d", *a);
|
||||
|
@ -77,15 +70,14 @@ 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
|
||||
return true;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char * const argv[]) {
|
||||
char buffer[PATH_MAX];
|
||||
struct passwd *user = getpwuid(getuid());
|
||||
|
||||
// Home dir
|
||||
snprintf(buffer, sizeof(buffer), "/Users/%s/Desktop/", user->pw_name);
|
||||
snprintf(buffer, sizeof(buffer), "/tmp/");
|
||||
|
||||
string path(buffer);
|
||||
ExceptionHandler eh(path, NULL, MDCallback, NULL, true);
|
||||
|
@ -97,8 +89,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();
|
||||
|
|
|
@ -54,6 +54,7 @@ namespace google_breakpad {
|
|||
MinidumpGenerator::MinidumpGenerator()
|
||||
: exception_type_(0),
|
||||
exception_code_(0),
|
||||
exception_subcode_(0),
|
||||
exception_thread_(0),
|
||||
crashing_task_(mach_task_self()),
|
||||
handler_thread_(mach_thread_self()),
|
||||
|
@ -67,6 +68,7 @@ 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) {
|
||||
|
@ -172,7 +174,7 @@ bool MinidumpGenerator::Write(const char *path) {
|
|||
// exception.
|
||||
&MinidumpGenerator::WriteExceptionStream,
|
||||
};
|
||||
bool result = true;
|
||||
bool result = false;
|
||||
|
||||
// If opening was successful, create the header, directory, and call each
|
||||
// writer. The destructor for the TypedMDRVAs will cause the data to be
|
||||
|
@ -203,6 +205,7 @@ 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);
|
||||
|
||||
|
@ -418,9 +421,9 @@ bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state,
|
|||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
|
||||
#if TARGET_CPU_X86_64
|
||||
mach_vm_address_t start_addr = machine_state->__rsp;
|
||||
mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, rsp);
|
||||
#else
|
||||
mach_vm_address_t start_addr = machine_state->esp;
|
||||
mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, esp);
|
||||
#endif
|
||||
return WriteStackFromStartAddress(start_addr, stack_location);
|
||||
}
|
||||
|
@ -431,9 +434,9 @@ MinidumpGenerator::CurrentPCForStack(breakpad_thread_state_data_t state) {
|
|||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
|
||||
#if TARGET_CPU_X86_64
|
||||
return machine_state->__rip;
|
||||
return REGISTER_FROM_THREADSTATE(machine_state, rip);
|
||||
#else
|
||||
return machine_state->eip;
|
||||
return REGISTER_FROM_THREADSTATE(machine_state, eip);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -449,10 +452,9 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
|||
*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
|
||||
context_ptr->context_flags = MD_CONTEXT_X86;
|
||||
|
||||
#define AddReg(a) context_ptr->a = machine_state->a
|
||||
AddReg(eax);
|
||||
AddReg(ebx);
|
||||
AddReg(ecx);
|
||||
|
@ -472,8 +474,6 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
|||
|
||||
AddReg(eip);
|
||||
#else
|
||||
|
||||
#define AddReg(a) context_ptr->a = machine_state->__ ## a
|
||||
context_ptr->context_flags = MD_CONTEXT_AMD64;
|
||||
AddReg(rax);
|
||||
AddReg(rbx);
|
||||
|
@ -501,6 +501,7 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
|||
AddReg(fs);
|
||||
AddReg(gs);
|
||||
#endif
|
||||
#undef AddReg(a)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -595,7 +596,10 @@ MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) {
|
|||
if (!WriteContext(state, &exception_ptr->thread_context))
|
||||
return false;
|
||||
|
||||
exception_ptr->exception_record.exception_address = CurrentPCForStack(state);
|
||||
if (exception_type_ == EXC_BAD_ACCESS)
|
||||
exception_ptr->exception_record.exception_address = exception_subcode_;
|
||||
else
|
||||
exception_ptr->exception_record.exception_address = CurrentPCForStack(state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -66,6 +66,18 @@ 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.
|
||||
|
@ -90,9 +102,11 @@ class MinidumpGenerator {
|
|||
bool Write(const char *path);
|
||||
|
||||
// Specify some exception information, if applicable
|
||||
void SetExceptionInformation(int type, int code, mach_port_t thread_name) {
|
||||
void SetExceptionInformation(int type, int code, int subcode,
|
||||
mach_port_t thread_name) {
|
||||
exception_type_ = type;
|
||||
exception_code_ = code;
|
||||
exception_subcode_ = subcode;
|
||||
exception_thread_ = thread_name;
|
||||
}
|
||||
|
||||
|
@ -138,6 +152,7 @@ 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,14 +45,13 @@ 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),
|
||||
"/Users/%s/Desktop/test.dmp",
|
||||
user->pw_name);
|
||||
|
||||
"/tmp/test.dmp");
|
||||
|
||||
|
||||
fprintf(stdout, "Writing %s\n", buffer);
|
||||
unlink(buffer);
|
||||
md.Write(buffer);
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
57
toolkit/crashreporter/google-breakpad/src/client/mac/sender/Breakpad.nib/classes.nib
сгенерированный
Normal file
57
toolkit/crashreporter/google-breakpad/src/client/mac/sender/Breakpad.nib/classes.nib
сгенерированный
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?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>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>dialogTitle_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>emailEntryField_</key>
|
||||
<string>NSView</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
toolkit/crashreporter/google-breakpad/src/client/mac/sender/Breakpad.nib/info.nib
сгенерированный
Normal file
20
toolkit/crashreporter/google-breakpad/src/client/mac/sender/Breakpad.nib/info.nib
сгенерированный
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?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>672</string>
|
||||
<key>IBLastKnownRelativeProjectPath</key>
|
||||
<string>../Breakpad.xcodeproj</string>
|
||||
<key>IBOldestOS</key>
|
||||
<integer>5</integer>
|
||||
<key>IBOpenObjects</key>
|
||||
<array>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>IBSystem Version</key>
|
||||
<string>9G55</string>
|
||||
<key>targetFramework</key>
|
||||
<string>IBCocoaFramework</string>
|
||||
</dict>
|
||||
</plist>
|
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/sender/Breakpad.nib/keyedobjects.nib
сгенерированный
Normal file
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/sender/Breakpad.nib/keyedobjects.nib
сгенерированный
Normal file
Двоичный файл не отображается.
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/sender/English.lproj/InfoPlist.strings
Normal file
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/sender/English.lproj/InfoPlist.strings
Normal file
Двоичный файл не отображается.
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/sender/English.lproj/Localizable.strings
Normal file
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/sender/English.lproj/Localizable.strings
Normal file
Двоичный файл не отображается.
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,32 @@
|
|||
<?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>
|
|
@ -0,0 +1,110 @@
|
|||
// 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;
|
||||
@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 NSView *emailEntryField_;
|
||||
IBOutlet NSView *privacyLinkArrow_;
|
||||
|
||||
// Text field bindings, for user input.
|
||||
NSString *commentsValue_; // Comments from the user
|
||||
NSString *emailValue_; // Email from the user
|
||||
|
||||
@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.
|
||||
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;
|
||||
|
||||
@end
|
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender.icns
Normal file
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender.icns
Normal file
Двоичный файл не отображается.
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,65 @@
|
|||
// 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
|
|
@ -0,0 +1,260 @@
|
|||
// 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
|
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/InfoPlist.strings
Normal file
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/InfoPlist.strings
Normal file
Двоичный файл не отображается.
47
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/MainMenu.nib/classes.nib
сгенерированный
Normal file
47
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/MainMenu.nib/classes.nib
сгенерированный
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?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>
|
22
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/MainMenu.nib/info.nib
сгенерированный
Normal file
22
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/MainMenu.nib/info.nib
сгенерированный
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?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>
|
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/MainMenu.nib/keyedobjects.nib
сгенерированный
Normal file
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/MainMenu.nib/keyedobjects.nib
сгенерированный
Normal file
Двоичный файл не отображается.
|
@ -0,0 +1,55 @@
|
|||
<?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>
|
|
@ -0,0 +1,37 @@
|
|||
// 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
|
|
@ -0,0 +1,95 @@
|
|||
// 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.
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#import "TestClass.h"
|
||||
|
||||
struct AStruct {
|
||||
int x;
|
||||
float y;
|
||||
double z;
|
||||
};
|
||||
|
||||
class InternalTestClass {
|
||||
public:
|
||||
InternalTestClass(int a) : a_(a) {}
|
||||
~InternalTestClass() {}
|
||||
|
||||
void snooze(float a);
|
||||
void snooze(int a);
|
||||
int snooze(int a, float b);
|
||||
|
||||
protected:
|
||||
int a_;
|
||||
AStruct s_;
|
||||
|
||||
static void InternalFunction(AStruct &s);
|
||||
static float kStaticFloatValue;
|
||||
};
|
||||
|
||||
void InternalTestClass::snooze(float a) {
|
||||
InternalFunction(s_);
|
||||
sleep(a_ * a);
|
||||
}
|
||||
|
||||
void InternalTestClass::snooze(int a) {
|
||||
InternalFunction(s_);
|
||||
sleep(a_ * a);
|
||||
}
|
||||
|
||||
int InternalTestClass::snooze(int a, float b) {
|
||||
InternalFunction(s_);
|
||||
sleep(a_ * a * b);
|
||||
|
||||
return 33;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
Двоичный файл не отображается.
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/crashInMain
Executable file
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/crashInMain
Executable file
Двоичный файл не отображается.
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/crashduringload
Executable file
Двоичные данные
toolkit/crashreporter/google-breakpad/src/client/mac/testapp/crashduringload
Executable file
Двоичный файл не отображается.
|
@ -0,0 +1,34 @@
|
|||
// 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);
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
// 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_port_t)0,
|
||||
@"Last exception thread is not 0 for on demand");
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,40 @@
|
|||
// 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
|
|
@ -0,0 +1,243 @@
|
|||
// 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
|
|
@ -126,17 +126,22 @@ 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, 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,
|
||||
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,
|
||||
#endif
|
||||
};
|
||||
unsigned int expected_byte_count = sizeof(expected);
|
||||
|
@ -156,7 +161,6 @@ static bool CompareFile(const char *path) {
|
|||
|
||||
printf("%d\n",b1 - (char*)buffer);
|
||||
|
||||
|
||||
ASSERT_EQ(memcmp(buffer, expected, expected_byte_count), 0);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -129,6 +129,19 @@ CrashGenerationServer::~CrashGenerationServer() {
|
|||
// Indicate to existing threads that server is shutting down.
|
||||
shutting_down_ = true;
|
||||
|
||||
// Even if there are no current worker threads running, it is possible that
|
||||
// an I/O request is pending on the pipe right now but not yet done. In fact,
|
||||
// it's very likely this is the case unless we are in an ERROR state. If we
|
||||
// don't wait for the pending I/O to be done, then when the I/O completes,
|
||||
// it may write to invalid memory. AppVerifier will flag this problem too.
|
||||
// So we disconnect from the pipe and then wait for the server to get into
|
||||
// error state so that the pending I/O will fail and get cleared.
|
||||
DisconnectNamedPipe(pipe_);
|
||||
int num_tries = 100;
|
||||
while (num_tries-- && server_state_ != IPC_SERVER_STATE_ERROR) {
|
||||
Sleep(10);
|
||||
}
|
||||
|
||||
// Unregister wait on the pipe.
|
||||
if (pipe_wait_handle_) {
|
||||
// Wait for already executing callbacks to finish.
|
||||
|
@ -629,6 +642,14 @@ bool CrashGenerationServer::RespondToClient(ClientInfo* client_info) {
|
|||
// implements the state machine described in ReadMe.txt along with the
|
||||
// helper methods HandleXXXState.
|
||||
void CrashGenerationServer::HandleConnectionRequest() {
|
||||
// If we are shutting doen then get into ERROR state, reset the event so more
|
||||
// workers don't run and return immediately.
|
||||
if (shutting_down_) {
|
||||
server_state_ = IPC_SERVER_STATE_ERROR;
|
||||
ResetEvent(overlapped_.hEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (server_state_) {
|
||||
case IPC_SERVER_STATE_ERROR:
|
||||
HandleErrorState();
|
||||
|
|
|
@ -241,7 +241,7 @@ class CrashGenerationServer {
|
|||
// Note that since we restrict the pipe to one instance, we
|
||||
// only need to keep one state of the server. Otherwise, server
|
||||
// would have one state per client it is talking to.
|
||||
IPCServerState server_state_;
|
||||
volatile IPCServerState server_state_;
|
||||
|
||||
// Whether the server is shutting down.
|
||||
volatile bool shutting_down_;
|
||||
|
|
|
@ -40,12 +40,13 @@
|
|||
|
||||
namespace google_breakpad {
|
||||
|
||||
static const int kWaitForHandlerThreadMs = 60000;
|
||||
static const int kExceptionHandlerThreadInitialStackSize = 64 * 1024;
|
||||
|
||||
vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL;
|
||||
LONG ExceptionHandler::handler_stack_index_ = 0;
|
||||
CRITICAL_SECTION ExceptionHandler::handler_stack_critical_section_;
|
||||
bool ExceptionHandler::handler_stack_critical_section_initialized_ = false;
|
||||
volatile LONG ExceptionHandler::instance_count_ = 0;
|
||||
|
||||
ExceptionHandler::ExceptionHandler(const wstring& dump_path,
|
||||
FilterCallback filter,
|
||||
|
@ -88,6 +89,7 @@ void ExceptionHandler::Initialize(const wstring& dump_path,
|
|||
MINIDUMP_TYPE dump_type,
|
||||
const wchar_t* pipe_name,
|
||||
const CustomClientInfo* custom_info) {
|
||||
LONG instance_count = InterlockedIncrement(&instance_count_);
|
||||
filter_ = filter;
|
||||
callback_ = callback;
|
||||
callback_context_ = callback_context;
|
||||
|
@ -106,6 +108,7 @@ void ExceptionHandler::Initialize(const wstring& dump_path,
|
|||
#endif // _MSC_VER >= 1400
|
||||
previous_pch_ = NULL;
|
||||
handler_thread_ = NULL;
|
||||
is_shutdown_ = false;
|
||||
handler_start_semaphore_ = NULL;
|
||||
handler_finish_semaphore_ = NULL;
|
||||
requesting_thread_id_ = 0;
|
||||
|
@ -177,12 +180,22 @@ void ExceptionHandler::Initialize(const wstring& dump_path,
|
|||
set_dump_path(dump_path);
|
||||
}
|
||||
|
||||
if (handler_types != HANDLER_NONE) {
|
||||
if (!handler_stack_critical_section_initialized_) {
|
||||
InitializeCriticalSection(&handler_stack_critical_section_);
|
||||
handler_stack_critical_section_initialized_ = true;
|
||||
}
|
||||
// There is a race condition here. If the first instance has not yet
|
||||
// initialized the critical section, the second (and later) instances may
|
||||
// try to use uninitialized critical section object. The feature of multiple
|
||||
// instances in one module is not used much, so leave it as is for now.
|
||||
// One way to solve this in the current design (that is, keeping the static
|
||||
// handler stack) is to use spin locks with volatile bools to synchronize
|
||||
// the handler stack. This works only if the compiler guarantees to generate
|
||||
// cache coherent code for volatile.
|
||||
// TODO(munjal): Fix this in a better way by changing the design if possible.
|
||||
|
||||
// Lazy initialization of the handler_stack_critical_section_
|
||||
if (instance_count == 1) {
|
||||
InitializeCriticalSection(&handler_stack_critical_section_);
|
||||
}
|
||||
|
||||
if (handler_types != HANDLER_NONE) {
|
||||
EnterCriticalSection(&handler_stack_critical_section_);
|
||||
|
||||
// The first time an ExceptionHandler that installs a handler is
|
||||
|
@ -259,12 +272,36 @@ ExceptionHandler::~ExceptionHandler() {
|
|||
// Some of the objects were only initialized if out of process
|
||||
// registration was not done.
|
||||
if (!IsOutOfProcess()) {
|
||||
// Clean up the handler thread and synchronization primitives.
|
||||
#ifdef BREAKPAD_NO_TERMINATE_THREAD
|
||||
// Clean up the handler thread and synchronization primitives. The handler
|
||||
// thread is either waiting on the semaphore to handle a crash or it is
|
||||
// handling a crash. Coming out of the wait is fast but wait more in the
|
||||
// eventuality a crash is handled. This compilation option results in a
|
||||
// deadlock if the exception handler is destroyed while executing code
|
||||
// inside DllMain.
|
||||
is_shutdown_ = true;
|
||||
ReleaseSemaphore(handler_start_semaphore_, 1, NULL);
|
||||
WaitForSingleObject(handler_thread_, kWaitForHandlerThreadMs);
|
||||
#else
|
||||
TerminateThread(handler_thread_, 1);
|
||||
#endif // BREAKPAD_NO_TERMINATE_THREAD
|
||||
|
||||
CloseHandle(handler_thread_);
|
||||
handler_thread_ = NULL;
|
||||
DeleteCriticalSection(&handler_critical_section_);
|
||||
CloseHandle(handler_start_semaphore_);
|
||||
CloseHandle(handler_finish_semaphore_);
|
||||
}
|
||||
|
||||
// There is a race condition in the code below: if this instance is
|
||||
// deleting the static critical section and a new instance of the class
|
||||
// is created, then there is a possibility that the critical section be
|
||||
// initialized while the same critical section is being deleted. Given the
|
||||
// usage pattern for the code, this race condition is unlikely to hit, but it
|
||||
// is a race condition nonetheless.
|
||||
if (InterlockedDecrement(&instance_count_) == 0) {
|
||||
DeleteCriticalSection(&handler_stack_critical_section_);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -278,16 +315,23 @@ DWORD ExceptionHandler::ExceptionHandlerThreadMain(void* lpParameter) {
|
|||
if (WaitForSingleObject(self->handler_start_semaphore_, INFINITE) ==
|
||||
WAIT_OBJECT_0) {
|
||||
// Perform the requested action.
|
||||
self->handler_return_value_ = self->WriteMinidumpWithException(
|
||||
self->requesting_thread_id_, self->exception_info_, self->assertion_);
|
||||
if (self->is_shutdown_) {
|
||||
// The instance of the exception handler is being destroyed.
|
||||
break;
|
||||
} else {
|
||||
self->handler_return_value_ =
|
||||
self->WriteMinidumpWithException(self->requesting_thread_id_,
|
||||
self->exception_info_,
|
||||
self->assertion_);
|
||||
}
|
||||
|
||||
// Allow the requesting thread to proceed.
|
||||
ReleaseSemaphore(self->handler_finish_semaphore_, 1, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Not reached. This thread will be terminated by ExceptionHandler's
|
||||
// destructor.
|
||||
// This statement is not reached when the thread is unconditionally
|
||||
// terminated by the ExceptionHandler destructor.
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -340,6 +340,12 @@ class ExceptionHandler {
|
|||
// The exception handler thread.
|
||||
HANDLE handler_thread_;
|
||||
|
||||
// True if the exception handler is being destroyed.
|
||||
// Starting with MSVC 2005, Visual C has stronger guarantees on volatile vars.
|
||||
// It has release semantics on write and acquire semantics on reads.
|
||||
// See the msdn documentation.
|
||||
volatile bool is_shutdown_;
|
||||
|
||||
// The critical section enforcing the requirement that only one exception be
|
||||
// handled by a handler at a time.
|
||||
CRITICAL_SECTION handler_critical_section_;
|
||||
|
@ -390,11 +396,12 @@ class ExceptionHandler {
|
|||
static LONG handler_stack_index_;
|
||||
|
||||
// handler_stack_critical_section_ guards operations on handler_stack_ and
|
||||
// handler_stack_index_.
|
||||
// handler_stack_index_. The critical section is initialized by the
|
||||
// first instance of the class and destroyed by the last instance of it.
|
||||
static CRITICAL_SECTION handler_stack_critical_section_;
|
||||
|
||||
// True when handler_stack_critical_section_ has been initialized.
|
||||
static bool handler_stack_critical_section_initialized_;
|
||||
// The number of instances of this class.
|
||||
volatile static LONG instance_count_;
|
||||
|
||||
// disallow copy ctor and operator=
|
||||
explicit ExceptionHandler(const ExceptionHandler &);
|
||||
|
|
|
@ -69,7 +69,7 @@ ReportResult CrashReportSender::SendCrashReport(
|
|||
|
||||
int http_response = 0;
|
||||
bool result = HTTPUpload::SendRequest(
|
||||
url, parameters, dump_file_name, L"upload_file_minidump", report_code,
|
||||
url, parameters, dump_file_name, L"upload_file_minidump", NULL, report_code,
|
||||
&http_response);
|
||||
|
||||
if (result) {
|
||||
|
|
|
@ -46,8 +46,8 @@
|
|||
BasicRuntimeChecks="3"
|
||||
RuntimeLibrary="3"
|
||||
UsePrecompiledHeader="0"
|
||||
WarningLevel="3"
|
||||
Detect64BitPortabilityProblems="true"
|
||||
WarningLevel="4"
|
||||
Detect64BitPortabilityProblems="false"
|
||||
DebugInformationFormat="4"
|
||||
/>
|
||||
<Tool
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <a.out.h>
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cxxabi.h>
|
||||
#include <elf.h>
|
||||
#include <errno.h>
|
||||
|
@ -168,18 +169,6 @@ static ElfW(Addr) GetLoadingAddress(const ElfW(Phdr) *program_headers,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool WriteFormat(int fd, const char *fmt, ...) {
|
||||
va_list list;
|
||||
char buffer[4096];
|
||||
ssize_t expected, written;
|
||||
va_start(list, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, list);
|
||||
expected = strlen(buffer);
|
||||
written = write(fd, buffer, strlen(buffer));
|
||||
va_end(list);
|
||||
return expected == written;
|
||||
}
|
||||
|
||||
static bool IsValidElf(const ElfW(Ehdr) *elf_header) {
|
||||
return memcmp(elf_header, ELFMAG, SELFMAG) == 0;
|
||||
}
|
||||
|
@ -592,7 +581,7 @@ static bool LoadSymbols(ElfW(Ehdr) *elf_header, struct SymbolInfo *symbols) {
|
|||
return LoadSymbols(stab_section, stabstr_section, loading_addr, symbols);
|
||||
}
|
||||
|
||||
static bool WriteModuleInfo(int fd,
|
||||
static bool WriteModuleInfo(FILE *file,
|
||||
ElfW(Half) arch,
|
||||
const std::string &obj_file) {
|
||||
const char *arch_name = NULL;
|
||||
|
@ -621,26 +610,26 @@ static bool WriteModuleInfo(int fd,
|
|||
size_t slash_pos = obj_file.find_last_of("/");
|
||||
if (slash_pos != std::string::npos)
|
||||
filename = obj_file.substr(slash_pos + 1);
|
||||
return WriteFormat(fd, "MODULE Linux %s %s %s\n", arch_name,
|
||||
id_no_dash, filename.c_str());
|
||||
return 0 <= fprintf(file, "MODULE Linux %s %s %s\n", arch_name,
|
||||
id_no_dash, filename.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool WriteSourceFileInfo(int fd, const struct SymbolInfo &symbols) {
|
||||
static bool WriteSourceFileInfo(FILE *file, const struct SymbolInfo &symbols) {
|
||||
for (SourceFileInfoList::const_iterator it =
|
||||
symbols.source_file_info.begin();
|
||||
it != symbols.source_file_info.end(); it++) {
|
||||
if (it->source_id != -1) {
|
||||
const char *name = it->name;
|
||||
if (!WriteFormat(fd, "FILE %d %s\n", it->source_id, name))
|
||||
if (0 > fprintf(file, "FILE %d %s\n", it->source_id, name))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool WriteOneFunction(int fd,
|
||||
static bool WriteOneFunction(FILE *file,
|
||||
const struct FuncInfo &func_info){
|
||||
// Discard the ending part of the name.
|
||||
std::string func_name(func_info.name);
|
||||
|
@ -652,19 +641,19 @@ static bool WriteOneFunction(int fd,
|
|||
if (func_info.size <= 0)
|
||||
return true;
|
||||
|
||||
if (WriteFormat(fd, "FUNC %lx %lx %d %s\n",
|
||||
func_info.rva_to_base,
|
||||
func_info.size,
|
||||
func_info.stack_param_size,
|
||||
func_name.c_str())) {
|
||||
if (0 <= fprintf(file, "FUNC %lx %lx %d %s\n",
|
||||
(unsigned long) func_info.rva_to_base,
|
||||
(unsigned long) func_info.size,
|
||||
func_info.stack_param_size,
|
||||
func_name.c_str())) {
|
||||
for (LineInfoList::const_iterator it = func_info.line_info.begin();
|
||||
it != func_info.line_info.end(); it++) {
|
||||
const struct LineInfo &line_info = *it;
|
||||
if (!WriteFormat(fd, "%lx %lx %d %d\n",
|
||||
line_info.rva_to_base,
|
||||
line_info.size,
|
||||
line_info.line_num,
|
||||
line_info.source_id))
|
||||
if (0 > fprintf(file, "%lx %lx %d %d\n",
|
||||
(unsigned long) line_info.rva_to_base,
|
||||
(unsigned long) line_info.size,
|
||||
line_info.line_num,
|
||||
line_info.source_id))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -672,7 +661,7 @@ static bool WriteOneFunction(int fd,
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool WriteFunctionInfo(int fd, const struct SymbolInfo &symbols) {
|
||||
static bool WriteFunctionInfo(FILE *file, const struct SymbolInfo &symbols) {
|
||||
for (SourceFileInfoList::const_iterator it =
|
||||
symbols.source_file_info.begin();
|
||||
it != symbols.source_file_info.end(); it++) {
|
||||
|
@ -680,16 +669,16 @@ static bool WriteFunctionInfo(int fd, const struct SymbolInfo &symbols) {
|
|||
for (FuncInfoList::const_iterator fiIt = file_info.func_info.begin();
|
||||
fiIt != file_info.func_info.end(); fiIt++) {
|
||||
const struct FuncInfo &func_info = *fiIt;
|
||||
if (!WriteOneFunction(fd, func_info))
|
||||
if (!WriteOneFunction(file, func_info))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DumpStabSymbols(int fd, const struct SymbolInfo &symbols) {
|
||||
return WriteSourceFileInfo(fd, symbols) &&
|
||||
WriteFunctionInfo(fd, symbols);
|
||||
static bool DumpStabSymbols(FILE *file, const struct SymbolInfo &symbols) {
|
||||
return WriteSourceFileInfo(file, symbols) &&
|
||||
WriteFunctionInfo(file, symbols);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -749,7 +738,7 @@ class MmapWrapper {
|
|||
namespace google_breakpad {
|
||||
|
||||
bool DumpSymbols::WriteSymbolFile(const std::string &obj_file,
|
||||
int sym_fd) {
|
||||
FILE *sym_file) {
|
||||
int obj_fd = open(obj_file.c_str(), O_RDONLY);
|
||||
if (obj_fd < 0)
|
||||
return false;
|
||||
|
@ -759,7 +748,7 @@ bool DumpSymbols::WriteSymbolFile(const std::string &obj_file,
|
|||
return false;
|
||||
void *obj_base = mmap(NULL, st.st_size,
|
||||
PROT_READ | PROT_WRITE, MAP_PRIVATE, obj_fd, 0);
|
||||
if (!obj_base)
|
||||
if (obj_base == MAP_FAILED)
|
||||
return false;
|
||||
MmapWrapper map_wrapper(obj_base, st.st_size);
|
||||
ElfW(Ehdr) *elf_header = reinterpret_cast<ElfW(Ehdr) *>(obj_base);
|
||||
|
@ -771,8 +760,8 @@ bool DumpSymbols::WriteSymbolFile(const std::string &obj_file,
|
|||
if (!LoadSymbols(elf_header, &symbols))
|
||||
return false;
|
||||
// Write to symbol file.
|
||||
if (WriteModuleInfo(sym_fd, elf_header->e_machine, obj_file) &&
|
||||
DumpStabSymbols(sym_fd, symbols))
|
||||
if (WriteModuleInfo(sym_file, elf_header->e_machine, obj_file) &&
|
||||
DumpStabSymbols(sym_file, symbols))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
|
|
@ -34,13 +34,14 @@
|
|||
#define COMMON_LINUX_DUMP_SYMBOLS_H__
|
||||
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class DumpSymbols {
|
||||
public:
|
||||
bool WriteSymbolFile(const std::string &obj_file,
|
||||
int sym_fd);
|
||||
FILE *sym_file);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
|
|
@ -101,7 +101,7 @@ bool FileID::ElfFileIdentifier(unsigned char identifier[16]) {
|
|||
}
|
||||
void *base = mmap(NULL, st.st_size,
|
||||
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||
if (!base) {
|
||||
if (base == MAP_FAILED) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
//
|
||||
// GTMDefines.h
|
||||
//
|
||||
// Copyright 2008 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy
|
||||
// of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
//
|
||||
|
||||
// ============================================================================
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
#include <TargetConditionals.h>
|
||||
|
||||
// Not all MAC_OS_X_VERSION_10_X macros defined in past SDKs
|
||||
#ifndef MAC_OS_X_VERSION_10_5
|
||||
#define MAC_OS_X_VERSION_10_5 1050
|
||||
#endif
|
||||
#ifndef MAC_OS_X_VERSION_10_6
|
||||
#define MAC_OS_X_VERSION_10_6 1060
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CPP symbols that can be overridden in a prefix to control how the toolbox
|
||||
// is compiled.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
// By setting the GTM_CONTAINERS_VALIDATION_FAILED_LOG and
|
||||
// GTM_CONTAINERS_VALIDATION_FAILED_ASSERT macros you can control what happens
|
||||
// when a validation fails. If you implement your own validators, you may want
|
||||
// to control their internals using the same macros for consistency.
|
||||
#ifndef GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
|
||||
#define GTM_CONTAINERS_VALIDATION_FAILED_ASSERT 0
|
||||
#endif
|
||||
|
||||
// Give ourselves a consistent way to do inlines. Apple's macros even use
|
||||
// a few different actual definitions, so we're based off of the foundation
|
||||
// one.
|
||||
#if !defined(GTM_INLINE)
|
||||
#if defined (__GNUC__) && (__GNUC__ == 4)
|
||||
#define GTM_INLINE static __inline__ __attribute__((always_inline))
|
||||
#else
|
||||
#define GTM_INLINE static __inline__
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Give ourselves a consistent way of doing externs that links up nicely
|
||||
// when mixing objc and objc++
|
||||
#if !defined (GTM_EXTERN)
|
||||
#if defined __cplusplus
|
||||
#define GTM_EXTERN extern "C"
|
||||
#else
|
||||
#define GTM_EXTERN extern
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Give ourselves a consistent way of exporting things if we have visibility
|
||||
// set to hidden.
|
||||
#if !defined (GTM_EXPORT)
|
||||
#define GTM_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
// _GTMDevLog & _GTMDevAssert
|
||||
//
|
||||
// _GTMDevLog & _GTMDevAssert are meant to be a very lightweight shell for
|
||||
// developer level errors. This implementation simply macros to NSLog/NSAssert.
|
||||
// It is not intended to be a general logging/reporting system.
|
||||
//
|
||||
// Please see http://code.google.com/p/google-toolbox-for-mac/wiki/DevLogNAssert
|
||||
// for a little more background on the usage of these macros.
|
||||
//
|
||||
// _GTMDevLog log some error/problem in debug builds
|
||||
// _GTMDevAssert assert if conditon isn't met w/in a method/function
|
||||
// in all builds.
|
||||
//
|
||||
// To replace this system, just provide different macro definitions in your
|
||||
// prefix header. Remember, any implementation you provide *must* be thread
|
||||
// safe since this could be called by anything in what ever situtation it has
|
||||
// been placed in.
|
||||
//
|
||||
|
||||
// We only define the simple macros if nothing else has defined this.
|
||||
#ifndef _GTMDevLog
|
||||
|
||||
#ifdef DEBUG
|
||||
#define _GTMDevLog(...) NSLog(__VA_ARGS__)
|
||||
#else
|
||||
#define _GTMDevLog(...) do { } while (0)
|
||||
#endif
|
||||
|
||||
#endif // _GTMDevLog
|
||||
|
||||
// Declared here so that it can easily be used for logging tracking if
|
||||
// necessary. See GTMUnitTestDevLog.h for details.
|
||||
@class NSString;
|
||||
GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...);
|
||||
|
||||
#ifndef _GTMDevAssert
|
||||
// we directly invoke the NSAssert handler so we can pass on the varargs
|
||||
// (NSAssert doesn't have a macro we can use that takes varargs)
|
||||
#if !defined(NS_BLOCK_ASSERTIONS)
|
||||
#define _GTMDevAssert(condition, ...) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
[[NSAssertionHandler currentHandler] \
|
||||
handleFailureInFunction:[NSString stringWithUTF8String:__PRETTY_FUNCTION__] \
|
||||
file:[NSString stringWithUTF8String:__FILE__] \
|
||||
lineNumber:__LINE__ \
|
||||
description:__VA_ARGS__]; \
|
||||
} \
|
||||
} while(0)
|
||||
#else // !defined(NS_BLOCK_ASSERTIONS)
|
||||
#define _GTMDevAssert(condition, ...) do { } while (0)
|
||||
#endif // !defined(NS_BLOCK_ASSERTIONS)
|
||||
|
||||
#endif // _GTMDevAssert
|
||||
|
||||
// _GTMCompileAssert
|
||||
// _GTMCompileAssert is an assert that is meant to fire at compile time if you
|
||||
// want to check things at compile instead of runtime. For example if you
|
||||
// want to check that a wchar is 4 bytes instead of 2 you would use
|
||||
// _GTMCompileAssert(sizeof(wchar_t) == 4, wchar_t_is_4_bytes_on_OS_X)
|
||||
// Note that the second "arg" is not in quotes, and must be a valid processor
|
||||
// symbol in it's own right (no spaces, punctuation etc).
|
||||
|
||||
// Wrapping this in an #ifndef allows external groups to define their own
|
||||
// compile time assert scheme.
|
||||
#ifndef _GTMCompileAssert
|
||||
// We got this technique from here:
|
||||
// http://unixjunkie.blogspot.com/2007/10/better-compile-time-asserts_29.html
|
||||
|
||||
#define _GTMCompileAssertSymbolInner(line, msg) _GTMCOMPILEASSERT ## line ## __ ## msg
|
||||
#define _GTMCompileAssertSymbol(line, msg) _GTMCompileAssertSymbolInner(line, msg)
|
||||
#define _GTMCompileAssert(test, msg) \
|
||||
typedef char _GTMCompileAssertSymbol(__LINE__, msg) [ ((test) ? 1 : -1) ]
|
||||
#endif // _GTMCompileAssert
|
||||
|
||||
// Macro to allow fast enumeration when building for 10.5 or later, and
|
||||
// reliance on NSEnumerator for 10.4. Remember, NSDictionary w/ FastEnumeration
|
||||
// does keys, so pick the right thing, nothing is done on the FastEnumeration
|
||||
// side to be sure you're getting what you wanted.
|
||||
#ifndef GTM_FOREACH_OBJECT
|
||||
#if TARGET_OS_IPHONE || (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
|
||||
#define GTM_FOREACH_OBJECT(element, collection) \
|
||||
for (element in collection)
|
||||
#define GTM_FOREACH_KEY(element, collection) \
|
||||
for (element in collection)
|
||||
#else
|
||||
#define GTM_FOREACH_OBJECT(element, collection) \
|
||||
for (NSEnumerator * _ ## element ## _enum = [collection objectEnumerator]; \
|
||||
(element = [_ ## element ## _enum nextObject]) != nil; )
|
||||
#define GTM_FOREACH_KEY(element, collection) \
|
||||
for (NSEnumerator * _ ## element ## _enum = [collection keyEnumerator]; \
|
||||
(element = [_ ## element ## _enum nextObject]) != nil; )
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CPP symbols defined based on the project settings so the GTM code has
|
||||
// simple things to test against w/o scattering the knowledge of project
|
||||
// setting through all the code.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Provide a single constant CPP symbol that all of GTM uses for ifdefing
|
||||
// iPhone code.
|
||||
#if TARGET_OS_IPHONE // iPhone SDK
|
||||
// For iPhone specific stuff
|
||||
#define GTM_IPHONE_SDK 1
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
#define GTM_IPHONE_SIMULATOR 1
|
||||
#else
|
||||
#define GTM_IPHONE_DEVICE 1
|
||||
#endif // TARGET_IPHONE_SIMULATOR
|
||||
#else
|
||||
// For MacOS specific stuff
|
||||
#define GTM_MACOS_SDK 1
|
||||
#endif
|
||||
|
||||
// Provide a symbol to include/exclude extra code for GC support. (This mainly
|
||||
// just controls the inclusion of finalize methods).
|
||||
#ifndef GTM_SUPPORT_GC
|
||||
#if GTM_IPHONE_SDK
|
||||
// iPhone never needs GC
|
||||
#define GTM_SUPPORT_GC 0
|
||||
#else
|
||||
// We can't find a symbol to tell if GC is supported/required, so best we
|
||||
// do on Mac targets is include it if we're on 10.5 or later.
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
|
||||
#define GTM_SUPPORT_GC 0
|
||||
#else
|
||||
#define GTM_SUPPORT_GC 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// To simplify support for 64bit (and Leopard in general), we provide the type
|
||||
// defines for non Leopard SDKs
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
|
||||
// NSInteger/NSUInteger and Max/Mins
|
||||
#ifndef NSINTEGER_DEFINED
|
||||
#if __LP64__ || NS_BUILD_32_LIKE_64
|
||||
typedef long NSInteger;
|
||||
typedef unsigned long NSUInteger;
|
||||
#else
|
||||
typedef int NSInteger;
|
||||
typedef unsigned int NSUInteger;
|
||||
#endif
|
||||
#define NSIntegerMax LONG_MAX
|
||||
#define NSIntegerMin LONG_MIN
|
||||
#define NSUIntegerMax ULONG_MAX
|
||||
#define NSINTEGER_DEFINED 1
|
||||
#endif // NSINTEGER_DEFINED
|
||||
// CGFloat
|
||||
#ifndef CGFLOAT_DEFINED
|
||||
#if defined(__LP64__) && __LP64__
|
||||
// This really is an untested path (64bit on Tiger?)
|
||||
typedef double CGFloat;
|
||||
#define CGFLOAT_MIN DBL_MIN
|
||||
#define CGFLOAT_MAX DBL_MAX
|
||||
#define CGFLOAT_IS_DOUBLE 1
|
||||
#else /* !defined(__LP64__) || !__LP64__ */
|
||||
typedef float CGFloat;
|
||||
#define CGFLOAT_MIN FLT_MIN
|
||||
#define CGFLOAT_MAX FLT_MAX
|
||||
#define CGFLOAT_IS_DOUBLE 0
|
||||
#endif /* !defined(__LP64__) || !__LP64__ */
|
||||
#define CGFLOAT_DEFINED 1
|
||||
#endif // CGFLOAT_DEFINED
|
||||
#endif // MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// GTMGarbageCollection.h
|
||||
//
|
||||
// Copyright 2007-2008 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy
|
||||
// of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "GTMDefines.h"
|
||||
|
||||
// This allows us to easily move our code from GC to non GC.
|
||||
// They are no-ops unless we are require Leopard or above.
|
||||
// See
|
||||
// http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/index.html
|
||||
// and
|
||||
// http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/Articles/gcCoreFoundation.html#//apple_ref/doc/uid/TP40006687-SW1
|
||||
// for details.
|
||||
|
||||
#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) && !GTM_IPHONE_SDK
|
||||
// General use would be to call this through GTMCFAutorelease
|
||||
// but there may be a reason the you want to make something collectable
|
||||
// but not autoreleased, especially in pure GC code where you don't
|
||||
// want to bother with the nop autorelease. Done as a define instead of an
|
||||
// inline so that tools like Clang's scan-build don't report code as leaking.
|
||||
#define GTMNSMakeCollectable(cf) ((id)NSMakeCollectable(cf))
|
||||
|
||||
// GTMNSMakeUncollectable is for global maps, etc. that we don't
|
||||
// want released ever. You should still retain these in non-gc code.
|
||||
GTM_INLINE void GTMNSMakeUncollectable(id object) {
|
||||
[[NSGarbageCollector defaultCollector] disableCollectorForPointer:object];
|
||||
}
|
||||
|
||||
// Hopefully no code really needs this, but GTMIsGarbageCollectionEnabled is
|
||||
// a common way to check at runtime if GC is on.
|
||||
// There are some places where GC doesn't work w/ things w/in Apple's
|
||||
// frameworks, so this is here so GTM unittests and detect it, and not run
|
||||
// individual tests to work around bugs in Apple's frameworks.
|
||||
GTM_INLINE BOOL GTMIsGarbageCollectionEnabled(void) {
|
||||
return ([NSGarbageCollector defaultCollector] != nil);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define GTMNSMakeCollectable(cf) ((id)(cf))
|
||||
|
||||
GTM_INLINE void GTMNSMakeUncollectable(id object) {
|
||||
}
|
||||
|
||||
GTM_INLINE BOOL GTMIsGarbageCollectionEnabled(void) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// GTMCFAutorelease makes a CF object collectable in GC mode, or adds it
|
||||
// to the autorelease pool in non-GC mode. Either way it is taken care
|
||||
// of. Done as a define instead of an inline so that tools like Clang's
|
||||
// scan-build don't report code as leaking.
|
||||
#define GTMCFAutorelease(cf) ([GTMNSMakeCollectable(cf) autorelease])
|
||||
|
|
@ -0,0 +1,458 @@
|
|||
//
|
||||
// GTMLogger.h
|
||||
//
|
||||
// Copyright 2007-2008 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy
|
||||
// of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
//
|
||||
|
||||
// Key Abstractions
|
||||
// ----------------
|
||||
//
|
||||
// This file declares multiple classes and protocols that are used by the
|
||||
// GTMLogger logging system. The 4 main abstractions used in this file are the
|
||||
// following:
|
||||
//
|
||||
// * logger (GTMLogger) - The main logging class that users interact with. It
|
||||
// has methods for logging at different levels and uses a log writer, a log
|
||||
// formatter, and a log filter to get the job done.
|
||||
//
|
||||
// * log writer (GTMLogWriter) - Writes a given string to some log file, where
|
||||
// a "log file" can be a physical file on disk, a POST over HTTP to some URL,
|
||||
// or even some in-memory structure (e.g., a ring buffer).
|
||||
//
|
||||
// * log formatter (GTMLogFormatter) - Given a format string and arguments as
|
||||
// a va_list, returns a single formatted NSString. A "formatted string" could
|
||||
// be a string with the date prepended, a string with values in a CSV format,
|
||||
// or even a string of XML.
|
||||
//
|
||||
// * log filter (GTMLogFilter) - Given a formatted log message as an NSString
|
||||
// and the level at which the message is to be logged, this class will decide
|
||||
// whether the given message should be logged or not. This is a flexible way
|
||||
// to filter out messages logged at a certain level, messages that contain
|
||||
// certain text, or filter nothing out at all. This gives the caller the
|
||||
// flexibility to dynamically enable debug logging in Release builds.
|
||||
//
|
||||
// This file also declares some classes to handle the common log writer, log
|
||||
// formatter, and log filter cases. Callers can also create their own writers,
|
||||
// formatters, and filters and they can even build them on top of the ones
|
||||
// declared here. Keep in mind that your custom writer/formatter/filter may be
|
||||
// called from multiple threads, so it must be thread-safe.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "GTMDefines.h"
|
||||
|
||||
// Predeclaration of used protocols that are declared later in this file.
|
||||
@protocol GTMLogWriter, GTMLogFormatter, GTMLogFilter;
|
||||
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
|
||||
#define CHECK_FORMAT_NSSTRING(a, b) __attribute__((format(__NSString__, a, b)))
|
||||
#else
|
||||
#define CHECK_FORMAT_NSSTRING(a, b)
|
||||
#endif
|
||||
|
||||
// GTMLogger
|
||||
//
|
||||
// GTMLogger is the primary user-facing class for an object-oriented logging
|
||||
// system. It is built on the concept of log formatters (GTMLogFormatter), log
|
||||
// writers (GTMLogWriter), and log filters (GTMLogFilter). When a message is
|
||||
// sent to a GTMLogger to log a message, the message is formatted using the log
|
||||
// formatter, then the log filter is consulted to see if the message should be
|
||||
// logged, and if so, the message is sent to the log writer to be written out.
|
||||
//
|
||||
// GTMLogger is intended to be a flexible and thread-safe logging solution. Its
|
||||
// flexibility comes from the fact that GTMLogger instances can be customized
|
||||
// with user defined formatters, filters, and writers. And these writers,
|
||||
// filters, and formatters can be combined, stacked, and customized in arbitrary
|
||||
// ways to suit the needs at hand. For example, multiple writers can be used at
|
||||
// the same time, and a GTMLogger instance can even be used as another
|
||||
// GTMLogger's writer. This allows for arbitrarily deep logging trees.
|
||||
//
|
||||
// A standard GTMLogger uses a writer that sends messages to standard out, a
|
||||
// formatter that smacks a timestamp and a few other bits of interesting
|
||||
// information on the message, and a filter that filters out debug messages from
|
||||
// release builds. Using the standard log settings, a log message will look like
|
||||
// the following:
|
||||
//
|
||||
// 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] foo=<Foo: 0x123>
|
||||
//
|
||||
// The output contains the date and time of the log message, the name of the
|
||||
// process followed by its process ID/thread ID, the log level at which the
|
||||
// message was logged (in the previous example the level was 1:
|
||||
// kGTMLoggerLevelDebug), and finally, the user-specified log message itself (in
|
||||
// this case, the log message was @"foo=%@", foo).
|
||||
//
|
||||
// Multiple instances of GTMLogger can be created, each configured their own
|
||||
// way. Though GTMLogger is not a singleton (in the GoF sense), it does provide
|
||||
// access to a shared (i.e., globally accessible) GTMLogger instance. This makes
|
||||
// it convenient for all code in a process to use the same GTMLogger instance.
|
||||
// The shared GTMLogger instance can also be configured in an arbitrary, and
|
||||
// these configuration changes will affect all code that logs through the shared
|
||||
// instance.
|
||||
|
||||
//
|
||||
// Log Levels
|
||||
// ----------
|
||||
// GTMLogger has 3 different log levels: Debug, Info, and Error. GTMLogger
|
||||
// doesn't take any special action based on the log level; it simply forwards
|
||||
// this information on to formatters, filters, and writers, each of which may
|
||||
// optionally take action based on the level. Since log level filtering is
|
||||
// performed at runtime, log messages are typically not filtered out at compile
|
||||
// time. The exception to this rule is that calls to the GTMLoggerDebug() macro
|
||||
// *ARE* filtered out of non-DEBUG builds. This is to be backwards compatible
|
||||
// with behavior that many developers are currently used to. Note that this
|
||||
// means that GTMLoggerDebug(@"hi") will be compiled out of Release builds, but
|
||||
// [[GTMLogger sharedLogger] logDebug:@"hi"] will NOT be compiled out.
|
||||
//
|
||||
// Standard loggers are created with the GTMLogLevelFilter log filter, which
|
||||
// filters out certain log messages based on log level, and some other settings.
|
||||
//
|
||||
// In addition to the -logDebug:, -logInfo:, and -logError: methods defined on
|
||||
// GTMLogger itself, there are also C macros that make usage of the shared
|
||||
// GTMLogger instance very convenient. These macros are:
|
||||
//
|
||||
// GTMLoggerDebug(...)
|
||||
// GTMLoggerInfo(...)
|
||||
// GTMLoggerError(...)
|
||||
//
|
||||
// Again, a notable feature of these macros is that GTMLogDebug() calls *will be
|
||||
// compiled out of non-DEBUG builds*.
|
||||
//
|
||||
// Standard Loggers
|
||||
// ----------------
|
||||
// GTMLogger has the concept of "standard loggers". A standard logger is simply
|
||||
// a logger that is pre-configured with some standard/common writer, formatter,
|
||||
// and filter combination. Standard loggers are created using the creation
|
||||
// methods beginning with "standard". The alternative to a standard logger is a
|
||||
// regular logger, which will send messages to stdout, with no special
|
||||
// formatting, and no filtering.
|
||||
//
|
||||
// How do I use GTMLogger?
|
||||
// ----------------------
|
||||
// The typical way you will want to use GTMLogger is to simply use the
|
||||
// GTMLogger*() macros for logging from code. That way we can easily make
|
||||
// changes to the GTMLogger class and simply update the macros accordingly. Only
|
||||
// your application startup code (perhaps, somewhere in main()) should use the
|
||||
// GTMLogger class directly in order to configure the shared logger, which all
|
||||
// of the code using the macros will be using. Again, this is just the typical
|
||||
// situation.
|
||||
//
|
||||
// To be complete, there are cases where you may want to use GTMLogger directly,
|
||||
// or even create separate GTMLogger instances for some reason. That's fine,
|
||||
// too.
|
||||
//
|
||||
// Examples
|
||||
// --------
|
||||
// The following show some common GTMLogger use cases.
|
||||
//
|
||||
// 1. You want to log something as simply as possible. Also, this call will only
|
||||
// appear in debug builds. In non-DEBUG builds it will be completely removed.
|
||||
//
|
||||
// GTMLoggerDebug(@"foo = %@", foo);
|
||||
//
|
||||
// 2. The previous example is similar to the following. The major difference is
|
||||
// that the previous call (example 1) will be compiled out of Release builds
|
||||
// but this statement will not be compiled out.
|
||||
//
|
||||
// [[GTMLogger sharedLogger] logDebug:@"foo = %@", foo];
|
||||
//
|
||||
// 3. Send all logging output from the shared logger to a file. We do this by
|
||||
// creating an NSFileHandle for writing associated with a file, and setting
|
||||
// that file handle as the logger's writer.
|
||||
//
|
||||
// NSFileHandle *f = [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log"
|
||||
// create:YES];
|
||||
// [[GTMLogger sharedLogger] setWriter:f];
|
||||
// GTMLoggerError(@"hi"); // This will be sent to /tmp/f.log
|
||||
//
|
||||
// 4. Create a new GTMLogger that will log to a file. This example differs from
|
||||
// the previous one because here we create a new GTMLogger that is different
|
||||
// from the shared logger.
|
||||
//
|
||||
// GTMLogger *logger = [GTMLogger standardLoggerWithPath:@"/tmp/temp.log"];
|
||||
// [logger logInfo:@"hi temp log file"];
|
||||
//
|
||||
// 5. Create a logger that writes to stdout and does NOT do any formatting to
|
||||
// the log message. This might be useful, for example, when writing a help
|
||||
// screen for a command-line tool to standard output.
|
||||
//
|
||||
// GTMLogger *logger = [GTMLogger logger];
|
||||
// [logger logInfo:@"%@ version 0.1 usage", progName];
|
||||
//
|
||||
// 6. Send log output to stdout AND to a log file. The trick here is that
|
||||
// NSArrays function as composite log writers, which means when an array is
|
||||
// set as the log writer, it forwards all logging messages to all of its
|
||||
// contained GTMLogWriters.
|
||||
//
|
||||
// // Create array of GTMLogWriters
|
||||
// NSArray *writers = [NSArray arrayWithObjects:
|
||||
// [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log" create:YES],
|
||||
// [NSFileHandle fileHandleWithStandardOutput], nil];
|
||||
//
|
||||
// GTMLogger *logger = [GTMLogger standardLogger];
|
||||
// [logger setWriter:writers];
|
||||
// [logger logInfo:@"hi"]; // Output goes to stdout and /tmp/f.log
|
||||
//
|
||||
// For futher details on log writers, formatters, and filters, see the
|
||||
// documentation below.
|
||||
//
|
||||
// NOTE: GTMLogger is application level logging. By default it does nothing
|
||||
// with _GTMDevLog/_GTMDevAssert (see GTMDefines.h). An application can choose
|
||||
// to bridge _GTMDevLog/_GTMDevAssert to GTMLogger by providing macro
|
||||
// definitions in its prefix header (see GTMDefines.h for how one would do
|
||||
// that).
|
||||
//
|
||||
@interface GTMLogger : NSObject {
|
||||
@private
|
||||
id<GTMLogWriter> writer_;
|
||||
id<GTMLogFormatter> formatter_;
|
||||
id<GTMLogFilter> filter_;
|
||||
}
|
||||
|
||||
//
|
||||
// Accessors for the shared logger instance
|
||||
//
|
||||
|
||||
// Returns a shared/global standard GTMLogger instance. Callers should typically
|
||||
// use this method to get a GTMLogger instance, unless they explicitly want
|
||||
// their own instance to configure for their own needs. This is the only method
|
||||
// that returns a shared instance; all the rest return new GTMLogger instances.
|
||||
+ (id)sharedLogger;
|
||||
|
||||
// Sets the shared logger instance to |logger|. Future calls to +sharedLogger
|
||||
// will return |logger| instead.
|
||||
+ (void)setSharedLogger:(GTMLogger *)logger;
|
||||
|
||||
//
|
||||
// Creation methods
|
||||
//
|
||||
|
||||
// Returns a new autoreleased GTMLogger instance that will log to stdout, using
|
||||
// the GTMLogStandardFormatter, and the GTMLogLevelFilter filter.
|
||||
+ (id)standardLogger;
|
||||
|
||||
// Same as +standardLogger, but logs to stderr.
|
||||
+ (id)standardLoggerWithStderr;
|
||||
|
||||
// Returns a new standard GTMLogger instance with a log writer that will
|
||||
// write to the file at |path|, and will use the GTMLogStandardFormatter and
|
||||
// GTMLogLevelFilter classes. If |path| does not exist, it will be created.
|
||||
+ (id)standardLoggerWithPath:(NSString *)path;
|
||||
|
||||
// Returns an autoreleased GTMLogger instance that will use the specified
|
||||
// |writer|, |formatter|, and |filter|.
|
||||
+ (id)loggerWithWriter:(id<GTMLogWriter>)writer
|
||||
formatter:(id<GTMLogFormatter>)formatter
|
||||
filter:(id<GTMLogFilter>)filter;
|
||||
|
||||
// Returns an autoreleased GTMLogger instance that logs to stdout, with the
|
||||
// basic formatter, and no filter. The returned logger differs from the logger
|
||||
// returned by +standardLogger because this one does not do any filtering and
|
||||
// does not do any special log formatting; this is the difference between a
|
||||
// "regular" logger and a "standard" logger.
|
||||
+ (id)logger;
|
||||
|
||||
// Designated initializer. This method returns a GTMLogger initialized with the
|
||||
// specified |writer|, |formatter|, and |filter|. See the setter methods below
|
||||
// for what values will be used if nil is passed for a parameter.
|
||||
- (id)initWithWriter:(id<GTMLogWriter>)writer
|
||||
formatter:(id<GTMLogFormatter>)formatter
|
||||
filter:(id<GTMLogFilter>)filter;
|
||||
|
||||
//
|
||||
// Logging methods
|
||||
//
|
||||
|
||||
// Logs a message at the debug level (kGTMLoggerLevelDebug).
|
||||
- (void)logDebug:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||
// Logs a message at the info level (kGTMLoggerLevelInfo).
|
||||
- (void)logInfo:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||
// Logs a message at the error level (kGTMLoggerLevelError).
|
||||
- (void)logError:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||
// Logs a message at the assert level (kGTMLoggerLevelAssert).
|
||||
- (void)logAssert:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||
|
||||
|
||||
//
|
||||
// Accessors
|
||||
//
|
||||
|
||||
// Accessor methods for the log writer. If the log writer is set to nil,
|
||||
// [NSFileHandle fileHandleWithStandardOutput] is used.
|
||||
- (id<GTMLogWriter>)writer;
|
||||
- (void)setWriter:(id<GTMLogWriter>)writer;
|
||||
|
||||
// Accessor methods for the log formatter. If the log formatter is set to nil,
|
||||
// GTMLogBasicFormatter is used. This formatter will format log messages in a
|
||||
// plain printf style.
|
||||
- (id<GTMLogFormatter>)formatter;
|
||||
- (void)setFormatter:(id<GTMLogFormatter>)formatter;
|
||||
|
||||
// Accessor methods for the log filter. If the log filter is set to nil,
|
||||
// GTMLogNoFilter is used, which allows all log messages through.
|
||||
- (id<GTMLogFilter>)filter;
|
||||
- (void)setFilter:(id<GTMLogFilter>)filter;
|
||||
|
||||
@end // GTMLogger
|
||||
|
||||
|
||||
// Helper functions that are used by the convenience GTMLogger*() macros that
|
||||
// enable the logging of function names.
|
||||
@interface GTMLogger (GTMLoggerMacroHelpers)
|
||||
- (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ...
|
||||
CHECK_FORMAT_NSSTRING(2, 3);
|
||||
- (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ...
|
||||
CHECK_FORMAT_NSSTRING(2, 3);
|
||||
- (void)logFuncError:(const char *)func msg:(NSString *)fmt, ...
|
||||
CHECK_FORMAT_NSSTRING(2, 3);
|
||||
- (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ...
|
||||
CHECK_FORMAT_NSSTRING(2, 3);
|
||||
@end // GTMLoggerMacroHelpers
|
||||
|
||||
|
||||
// Convenience macros that log to the shared GTMLogger instance. These macros
|
||||
// are how users should typically log to GTMLogger. Notice that GTMLoggerDebug()
|
||||
// calls will be compiled out of non-Debug builds.
|
||||
#define GTMLoggerDebug(...) \
|
||||
[[GTMLogger sharedLogger] logFuncDebug:__func__ msg:__VA_ARGS__]
|
||||
#define GTMLoggerInfo(...) \
|
||||
[[GTMLogger sharedLogger] logFuncInfo:__func__ msg:__VA_ARGS__]
|
||||
#define GTMLoggerError(...) \
|
||||
[[GTMLogger sharedLogger] logFuncError:__func__ msg:__VA_ARGS__]
|
||||
#define GTMLoggerAssert(...) \
|
||||
[[GTMLogger sharedLogger] logFuncAssert:__func__ msg:__VA_ARGS__]
|
||||
|
||||
// If we're not in a debug build, remove the GTMLoggerDebug statements. This
|
||||
// makes calls to GTMLoggerDebug "compile out" of Release builds
|
||||
#ifndef DEBUG
|
||||
#undef GTMLoggerDebug
|
||||
#define GTMLoggerDebug(...) do {} while(0)
|
||||
#endif
|
||||
|
||||
// Log levels.
|
||||
typedef enum {
|
||||
kGTMLoggerLevelUnknown,
|
||||
kGTMLoggerLevelDebug,
|
||||
kGTMLoggerLevelInfo,
|
||||
kGTMLoggerLevelError,
|
||||
kGTMLoggerLevelAssert,
|
||||
} GTMLoggerLevel;
|
||||
|
||||
|
||||
//
|
||||
// Log Writers
|
||||
//
|
||||
|
||||
// Protocol to be implemented by a GTMLogWriter instance.
|
||||
@protocol GTMLogWriter <NSObject>
|
||||
// Writes the given log message to where the log writer is configured to write.
|
||||
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level;
|
||||
@end // GTMLogWriter
|
||||
|
||||
|
||||
// Simple category on NSFileHandle that makes NSFileHandles valid log writers.
|
||||
// This is convenient because something like, say, +fileHandleWithStandardError
|
||||
// now becomes a valid log writer. Log messages are written to the file handle
|
||||
// with a newline appended.
|
||||
@interface NSFileHandle (GTMFileHandleLogWriter) <GTMLogWriter>
|
||||
// Opens the file at |path| in append mode, and creates the file with |mode|
|
||||
// if it didn't previously exist.
|
||||
+ (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode;
|
||||
@end // NSFileHandle
|
||||
|
||||
|
||||
// This category makes NSArray a GTMLogWriter that can be composed of other
|
||||
// GTMLogWriters. This is the classic Composite GoF design pattern. When the
|
||||
// GTMLogWriter -logMessage:level: message is sent to the array, the array
|
||||
// forwards the message to all of its elements that implement the GTMLogWriter
|
||||
// protocol.
|
||||
//
|
||||
// This is useful in situations where you would like to send log output to
|
||||
// multiple log writers at the same time. Simply create an NSArray of the log
|
||||
// writers you wish to use, then set the array as the "writer" for your
|
||||
// GTMLogger instance.
|
||||
@interface NSArray (GTMArrayCompositeLogWriter) <GTMLogWriter>
|
||||
@end // GTMArrayCompositeLogWriter
|
||||
|
||||
|
||||
// This category adapts the GTMLogger interface so that it can be used as a log
|
||||
// writer; it's an "adapter" in the GoF Adapter pattern sense.
|
||||
//
|
||||
// This is useful when you want to configure a logger to log to a specific
|
||||
// writer with a specific formatter and/or filter. But you want to also compose
|
||||
// that with a different log writer that may have its own formatter and/or
|
||||
// filter.
|
||||
@interface GTMLogger (GTMLoggerLogWriter) <GTMLogWriter>
|
||||
@end // GTMLoggerLogWriter
|
||||
|
||||
|
||||
//
|
||||
// Log Formatters
|
||||
//
|
||||
|
||||
// Protocol to be implemented by a GTMLogFormatter instance.
|
||||
@protocol GTMLogFormatter <NSObject>
|
||||
// Returns a formatted string using the format specified in |fmt| and the va
|
||||
// args specified in |args|.
|
||||
- (NSString *)stringForFunc:(NSString *)func
|
||||
withFormat:(NSString *)fmt
|
||||
valist:(va_list)args
|
||||
level:(GTMLoggerLevel)level;
|
||||
@end // GTMLogFormatter
|
||||
|
||||
|
||||
// A basic log formatter that formats a string the same way that NSLog (or
|
||||
// printf) would. It does not do anything fancy, nor does it add any data of its
|
||||
// own.
|
||||
@interface GTMLogBasicFormatter : NSObject <GTMLogFormatter>
|
||||
@end // GTMLogBasicFormatter
|
||||
|
||||
|
||||
// A log formatter that formats the log string like the basic formatter, but
|
||||
// also prepends a timestamp and some basic process info to the message, as
|
||||
// shown in the following sample output.
|
||||
// 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] log mesage here
|
||||
@interface GTMLogStandardFormatter : GTMLogBasicFormatter {
|
||||
@private
|
||||
NSDateFormatter *dateFormatter_; // yyyy-MM-dd HH:mm:ss.SSS
|
||||
NSString *pname_;
|
||||
pid_t pid_;
|
||||
}
|
||||
@end // GTMLogStandardFormatter
|
||||
|
||||
|
||||
//
|
||||
// Log Filters
|
||||
//
|
||||
|
||||
// Protocol to be imlemented by a GTMLogFilter instance.
|
||||
@protocol GTMLogFilter <NSObject>
|
||||
// Returns YES if |msg| at |level| should be filtered out; NO otherwise.
|
||||
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level;
|
||||
@end // GTMLogFilter
|
||||
|
||||
|
||||
// A log filter that filters messages at the kGTMLoggerLevelDebug level out of
|
||||
// non-debug builds. Messages at the kGTMLoggerLevelInfo level are also filtered
|
||||
// out of non-debug builds unless GTMVerboseLogging is set in the environment or
|
||||
// the processes's defaults. Messages at the kGTMLoggerLevelError level are
|
||||
// never filtered.
|
||||
@interface GTMLogLevelFilter : NSObject <GTMLogFilter>
|
||||
@end // GTMLogLevelFilter
|
||||
|
||||
|
||||
// A simple log filter that does NOT filter anything out;
|
||||
// -filterAllowsMessage:level will always return YES. This can be a convenient
|
||||
// way to enable debug-level logging in release builds (if you so desire).
|
||||
@interface GTMLogNoFilter : NSObject <GTMLogFilter>
|
||||
@end // GTMLogNoFilter
|
|
@ -0,0 +1,445 @@
|
|||
//
|
||||
// GTMLogger.m
|
||||
//
|
||||
// Copyright 2007-2008 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy
|
||||
// of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
//
|
||||
|
||||
#import "GTMLogger.h"
|
||||
#import "GTMGarbageCollection.h"
|
||||
#import <fcntl.h>
|
||||
#import <unistd.h>
|
||||
#import <stdlib.h>
|
||||
#import <pthread.h>
|
||||
|
||||
|
||||
// Define a trivial assertion macro to avoid dependencies
|
||||
#ifdef DEBUG
|
||||
#define GTMLOGGER_ASSERT(expr) assert(expr)
|
||||
#else
|
||||
#define GTMLOGGER_ASSERT(expr)
|
||||
#endif
|
||||
|
||||
|
||||
@interface GTMLogger (PrivateMethods)
|
||||
|
||||
- (void)logInternalFunc:(const char *)func
|
||||
format:(NSString *)fmt
|
||||
valist:(va_list)args
|
||||
level:(GTMLoggerLevel)level;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// Reference to the shared GTMLogger instance. This is not a singleton, it's
|
||||
// just an easy reference to one shared instance.
|
||||
static GTMLogger *gSharedLogger = nil;
|
||||
|
||||
|
||||
@implementation GTMLogger
|
||||
|
||||
// Returns a pointer to the shared logger instance. If none exists, a standard
|
||||
// logger is created and returned.
|
||||
+ (id)sharedLogger {
|
||||
@synchronized(self) {
|
||||
if (gSharedLogger == nil) {
|
||||
gSharedLogger = [[self standardLogger] retain];
|
||||
}
|
||||
GTMLOGGER_ASSERT(gSharedLogger != nil);
|
||||
}
|
||||
return [[gSharedLogger retain] autorelease];
|
||||
}
|
||||
|
||||
+ (void)setSharedLogger:(GTMLogger *)logger {
|
||||
@synchronized(self) {
|
||||
[gSharedLogger autorelease];
|
||||
gSharedLogger = [logger retain];
|
||||
}
|
||||
}
|
||||
|
||||
+ (id)standardLogger {
|
||||
id<GTMLogWriter> writer = [NSFileHandle fileHandleWithStandardOutput];
|
||||
id<GTMLogFormatter> fr = [[[GTMLogStandardFormatter alloc] init] autorelease];
|
||||
id<GTMLogFilter> filter = [[[GTMLogLevelFilter alloc] init] autorelease];
|
||||
return [self loggerWithWriter:writer formatter:fr filter:filter];
|
||||
}
|
||||
|
||||
+ (id)standardLoggerWithStderr {
|
||||
id me = [self standardLogger];
|
||||
[me setWriter:[NSFileHandle fileHandleWithStandardError]];
|
||||
return me;
|
||||
}
|
||||
|
||||
+ (id)standardLoggerWithPath:(NSString *)path {
|
||||
NSFileHandle *fh = [NSFileHandle fileHandleForLoggingAtPath:path mode:0644];
|
||||
if (fh == nil) return nil;
|
||||
id me = [self standardLogger];
|
||||
[me setWriter:fh];
|
||||
return me;
|
||||
}
|
||||
|
||||
+ (id)loggerWithWriter:(id<GTMLogWriter>)writer
|
||||
formatter:(id<GTMLogFormatter>)formatter
|
||||
filter:(id<GTMLogFilter>)filter {
|
||||
return [[[self alloc] initWithWriter:writer
|
||||
formatter:formatter
|
||||
filter:filter] autorelease];
|
||||
}
|
||||
|
||||
+ (id)logger {
|
||||
return [[[self alloc] init] autorelease];
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
return [self initWithWriter:nil formatter:nil filter:nil];
|
||||
}
|
||||
|
||||
- (id)initWithWriter:(id<GTMLogWriter>)writer
|
||||
formatter:(id<GTMLogFormatter>)formatter
|
||||
filter:(id<GTMLogFilter>)filter {
|
||||
if ((self = [super init])) {
|
||||
[self setWriter:writer];
|
||||
[self setFormatter:formatter];
|
||||
[self setFilter:filter];
|
||||
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||
GTMLOGGER_ASSERT(filter_ != nil);
|
||||
GTMLOGGER_ASSERT(writer_ != nil);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
GTMLOGGER_ASSERT(writer_ != nil);
|
||||
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||
GTMLOGGER_ASSERT(filter_ != nil);
|
||||
[writer_ release];
|
||||
[formatter_ release];
|
||||
[filter_ release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id<GTMLogWriter>)writer {
|
||||
GTMLOGGER_ASSERT(writer_ != nil);
|
||||
return [[writer_ retain] autorelease];
|
||||
}
|
||||
|
||||
- (void)setWriter:(id<GTMLogWriter>)writer {
|
||||
@synchronized(self) {
|
||||
[writer_ autorelease];
|
||||
if (writer == nil)
|
||||
writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain];
|
||||
else
|
||||
writer_ = [writer retain];
|
||||
}
|
||||
GTMLOGGER_ASSERT(writer_ != nil);
|
||||
}
|
||||
|
||||
- (id<GTMLogFormatter>)formatter {
|
||||
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||
return [[formatter_ retain] autorelease];
|
||||
}
|
||||
|
||||
- (void)setFormatter:(id<GTMLogFormatter>)formatter {
|
||||
@synchronized(self) {
|
||||
[formatter_ autorelease];
|
||||
if (formatter == nil)
|
||||
formatter_ = [[GTMLogBasicFormatter alloc] init];
|
||||
else
|
||||
formatter_ = [formatter retain];
|
||||
}
|
||||
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||
}
|
||||
|
||||
- (id<GTMLogFilter>)filter {
|
||||
GTMLOGGER_ASSERT(filter_ != nil);
|
||||
return [[filter_ retain] autorelease];
|
||||
}
|
||||
|
||||
- (void)setFilter:(id<GTMLogFilter>)filter {
|
||||
@synchronized(self) {
|
||||
[filter_ autorelease];
|
||||
if (filter == nil)
|
||||
filter_ = [[GTMLogNoFilter alloc] init];
|
||||
else
|
||||
filter_ = [filter retain];
|
||||
}
|
||||
GTMLOGGER_ASSERT(filter_ != nil);
|
||||
}
|
||||
|
||||
- (void)logDebug:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelDebug];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logInfo:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelInfo];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logError:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelError];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logAssert:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelAssert];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@end // GTMLogger
|
||||
|
||||
|
||||
@implementation GTMLogger (GTMLoggerMacroHelpers)
|
||||
|
||||
- (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelDebug];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelInfo];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logFuncError:(const char *)func msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelError];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelAssert];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@end // GTMLoggerMacroHelpers
|
||||
|
||||
|
||||
@implementation GTMLogger (PrivateMethods)
|
||||
|
||||
- (void)logInternalFunc:(const char *)func
|
||||
format:(NSString *)fmt
|
||||
valist:(va_list)args
|
||||
level:(GTMLoggerLevel)level {
|
||||
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||
GTMLOGGER_ASSERT(filter_ != nil);
|
||||
GTMLOGGER_ASSERT(writer_ != nil);
|
||||
|
||||
NSString *fname = func ? [NSString stringWithUTF8String:func] : nil;
|
||||
NSString *msg = [formatter_ stringForFunc:fname
|
||||
withFormat:fmt
|
||||
valist:args
|
||||
level:level];
|
||||
if (msg && [filter_ filterAllowsMessage:msg level:level])
|
||||
[writer_ logMessage:msg level:level];
|
||||
}
|
||||
|
||||
@end // PrivateMethods
|
||||
|
||||
|
||||
@implementation NSFileHandle (GTMFileHandleLogWriter)
|
||||
|
||||
+ (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode {
|
||||
int fd = -1;
|
||||
if (path) {
|
||||
int flags = O_WRONLY | O_APPEND | O_CREAT;
|
||||
fd = open([path fileSystemRepresentation], flags, mode);
|
||||
}
|
||||
if (fd == -1) return nil;
|
||||
return [[[self alloc] initWithFileDescriptor:fd
|
||||
closeOnDealloc:YES] autorelease];
|
||||
}
|
||||
|
||||
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||
@synchronized(self) {
|
||||
NSString *line = [NSString stringWithFormat:@"%@\n", msg];
|
||||
[self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
}
|
||||
}
|
||||
|
||||
@end // GTMFileHandleLogWriter
|
||||
|
||||
|
||||
@implementation NSArray (GTMArrayCompositeLogWriter)
|
||||
|
||||
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||
@synchronized(self) {
|
||||
id<GTMLogWriter> child = nil;
|
||||
GTM_FOREACH_OBJECT(child, self) {
|
||||
if ([child conformsToProtocol:@protocol(GTMLogWriter)])
|
||||
[child logMessage:msg level:level];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end // GTMArrayCompositeLogWriter
|
||||
|
||||
|
||||
@implementation GTMLogger (GTMLoggerLogWriter)
|
||||
|
||||
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||
switch (level) {
|
||||
case kGTMLoggerLevelDebug:
|
||||
[self logDebug:@"%@", msg];
|
||||
break;
|
||||
case kGTMLoggerLevelInfo:
|
||||
[self logInfo:@"%@", msg];
|
||||
break;
|
||||
case kGTMLoggerLevelError:
|
||||
[self logError:@"%@", msg];
|
||||
break;
|
||||
case kGTMLoggerLevelAssert:
|
||||
[self logAssert:@"%@", msg];
|
||||
break;
|
||||
default:
|
||||
// Ignore the message.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end // GTMLoggerLogWriter
|
||||
|
||||
|
||||
@implementation GTMLogBasicFormatter
|
||||
|
||||
- (NSString *)stringForFunc:(NSString *)func
|
||||
withFormat:(NSString *)fmt
|
||||
valist:(va_list)args
|
||||
level:(GTMLoggerLevel)level {
|
||||
// Performance note: since we always have to create a new NSString from the
|
||||
// returned CFStringRef, we may want to do a quick check here to see if |fmt|
|
||||
// contains a '%', and if not, simply return 'fmt'.
|
||||
CFStringRef cfmsg = NULL;
|
||||
cfmsg = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault,
|
||||
NULL, // format options
|
||||
(CFStringRef)fmt,
|
||||
args);
|
||||
return GTMCFAutorelease(cfmsg);
|
||||
}
|
||||
|
||||
@end // GTMLogBasicFormatter
|
||||
|
||||
|
||||
@implementation GTMLogStandardFormatter
|
||||
|
||||
- (id)init {
|
||||
if ((self = [super init])) {
|
||||
dateFormatter_ = [[NSDateFormatter alloc] init];
|
||||
[dateFormatter_ setFormatterBehavior:NSDateFormatterBehavior10_4];
|
||||
[dateFormatter_ setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"];
|
||||
pname_ = [[[NSProcessInfo processInfo] processName] copy];
|
||||
pid_ = [[NSProcessInfo processInfo] processIdentifier];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[dateFormatter_ release];
|
||||
[pname_ release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *)stringForFunc:(NSString *)func
|
||||
withFormat:(NSString *)fmt
|
||||
valist:(va_list)args
|
||||
level:(GTMLoggerLevel)level {
|
||||
GTMLOGGER_ASSERT(dateFormatter_ != nil);
|
||||
NSString *tstamp = nil;
|
||||
@synchronized (dateFormatter_) {
|
||||
tstamp = [dateFormatter_ stringFromDate:[NSDate date]];
|
||||
}
|
||||
return [NSString stringWithFormat:@"%@ %@[%d/%p] [lvl=%d] %@ %@",
|
||||
tstamp, pname_, pid_, pthread_self(),
|
||||
level, (func ? func : @"(no func)"),
|
||||
[super stringForFunc:func withFormat:fmt valist:args level:level]];
|
||||
}
|
||||
|
||||
@end // GTMLogStandardFormatter
|
||||
|
||||
|
||||
@implementation GTMLogLevelFilter
|
||||
|
||||
// Check the environment and the user preferences for the GTMVerboseLogging key
|
||||
// to see if verbose logging has been enabled. The environment variable will
|
||||
// override the defaults setting, so check the environment first.
|
||||
// COV_NF_START
|
||||
static BOOL IsVerboseLoggingEnabled(void) {
|
||||
static NSString *const kVerboseLoggingKey = @"GTMVerboseLogging";
|
||||
static char *env = NULL;
|
||||
if (env == NULL)
|
||||
env = getenv([kVerboseLoggingKey UTF8String]);
|
||||
|
||||
if (env && env[0]) {
|
||||
return (strtol(env, NULL, 10) != 0);
|
||||
}
|
||||
|
||||
return [[NSUserDefaults standardUserDefaults] boolForKey:kVerboseLoggingKey];
|
||||
}
|
||||
// COV_NF_END
|
||||
|
||||
// In DEBUG builds, log everything. If we're not in a debug build we'll assume
|
||||
// that we're in a Release build.
|
||||
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||
#if DEBUG
|
||||
return YES;
|
||||
#endif
|
||||
|
||||
BOOL allow = YES;
|
||||
|
||||
switch (level) {
|
||||
case kGTMLoggerLevelDebug:
|
||||
allow = NO;
|
||||
break;
|
||||
case kGTMLoggerLevelInfo:
|
||||
allow = (IsVerboseLoggingEnabled() == YES);
|
||||
break;
|
||||
case kGTMLoggerLevelError:
|
||||
allow = YES;
|
||||
break;
|
||||
case kGTMLoggerLevelAssert:
|
||||
allow = YES;
|
||||
break;
|
||||
default:
|
||||
allow = YES;
|
||||
break;
|
||||
}
|
||||
|
||||
return allow;
|
||||
}
|
||||
|
||||
@end // GTMLogLevelFilter
|
||||
|
||||
|
||||
@implementation GTMLogNoFilter
|
||||
|
||||
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||
return YES; // Allow everything through
|
||||
}
|
||||
|
||||
@end // GTMLogNoFilter
|
|
@ -0,0 +1,304 @@
|
|||
// 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.
|
||||
//
|
||||
// MachIPC.h
|
||||
//
|
||||
// Some helpful wrappers for using Mach IPC calls
|
||||
|
||||
#ifndef MACH_IPC_H__
|
||||
#define MACH_IPC_H__
|
||||
|
||||
#import <mach/mach.h>
|
||||
#import <mach/message.h>
|
||||
#import <servers/bootstrap.h>
|
||||
#import <sys/types.h>
|
||||
|
||||
#import <CoreServices/CoreServices.h>
|
||||
|
||||
//==============================================================================
|
||||
// DISCUSSION:
|
||||
//
|
||||
// The three main classes of interest are
|
||||
//
|
||||
// MachMessage: a wrapper for a mach message of the following form
|
||||
// mach_msg_header_t
|
||||
// mach_msg_body_t
|
||||
// optional descriptors
|
||||
// optional extra message data
|
||||
//
|
||||
// MachReceiveMessage and MachSendMessage subclass MachMessage
|
||||
// and are used instead of MachMessage which is an abstract base class
|
||||
//
|
||||
// ReceivePort:
|
||||
// Represents a mach port for which we have receive rights
|
||||
//
|
||||
// MachPortSender:
|
||||
// Represents a mach port for which we have send rights
|
||||
//
|
||||
// Here's an example to receive a message on a server port:
|
||||
//
|
||||
// // This creates our named server port
|
||||
// ReceivePort receivePort("com.Google.MyService");
|
||||
//
|
||||
// MachReceiveMessage message;
|
||||
// kern_return_t result = receivePort.WaitForMessage(&message, 0);
|
||||
//
|
||||
// if (result == KERN_SUCCESS && message.GetMessageID() == 57) {
|
||||
// mach_port_t task = message.GetTranslatedPort(0);
|
||||
// mach_port_t thread = message.GetTranslatedPort(1);
|
||||
//
|
||||
// char *messageString = message.GetData();
|
||||
//
|
||||
// printf("message string = %s\n", messageString);
|
||||
// }
|
||||
//
|
||||
// Here is an example of using these classes to send a message to this port:
|
||||
//
|
||||
// // send to already named port
|
||||
// MachPortSender sender("com.Google.MyService");
|
||||
// MachSendMessage message(57); // our message ID is 57
|
||||
//
|
||||
// // add some ports to be translated for us
|
||||
// message.AddDescriptor(mach_task_self()); // our task
|
||||
// message.AddDescriptor(mach_thread_self()); // this thread
|
||||
//
|
||||
// char messageString[] = "Hello server!\n";
|
||||
// message.SetData(messageString, strlen(messageString)+1);
|
||||
//
|
||||
// kern_return_t result = sender.SendMessage(message, 1000); // timeout 1000ms
|
||||
//
|
||||
|
||||
#define PRINT_MACH_RESULT(result_, message_) \
|
||||
printf(message_" %s (%d)\n", mach_error_string(result_), result_ );
|
||||
|
||||
//==============================================================================
|
||||
// A wrapper class for mach_msg_port_descriptor_t (with same memory layout)
|
||||
// with convenient constructors and accessors
|
||||
class MachMsgPortDescriptor : public mach_msg_port_descriptor_t {
|
||||
public:
|
||||
// General-purpose constructor
|
||||
MachMsgPortDescriptor(mach_port_t in_name,
|
||||
mach_msg_type_name_t in_disposition) {
|
||||
name = in_name;
|
||||
pad1 = 0;
|
||||
pad2 = 0;
|
||||
disposition = in_disposition;
|
||||
type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
}
|
||||
|
||||
// For passing send rights to a port
|
||||
MachMsgPortDescriptor(mach_port_t in_name) {
|
||||
name = in_name;
|
||||
pad1 = 0;
|
||||
pad2 = 0;
|
||||
disposition = MACH_MSG_TYPE_COPY_SEND;
|
||||
type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
MachMsgPortDescriptor(const MachMsgPortDescriptor& desc) {
|
||||
name = desc.name;
|
||||
pad1 = desc.pad1;
|
||||
pad2 = desc.pad2;
|
||||
disposition = desc.disposition;
|
||||
type = desc.type;
|
||||
}
|
||||
|
||||
mach_port_t GetMachPort() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
mach_msg_type_name_t GetDisposition() const {
|
||||
return disposition;
|
||||
}
|
||||
|
||||
// We're just a simple wrapper for mach_msg_port_descriptor_t
|
||||
// and have the same memory layout
|
||||
operator mach_msg_port_descriptor_t&() {
|
||||
return *this;
|
||||
}
|
||||
|
||||
// For convenience
|
||||
operator mach_port_t() const {
|
||||
return GetMachPort();
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// MachMessage: a wrapper for a mach message
|
||||
// (mach_msg_header_t, mach_msg_body_t, extra data)
|
||||
//
|
||||
// This considerably simplifies the construction of a message for sending
|
||||
// and the getting at relevant data and descriptors for the receiver.
|
||||
//
|
||||
// Currently the combined size of the descriptors plus data must be
|
||||
// less than 1024. But as a benefit no memory allocation is necessary.
|
||||
//
|
||||
// TODO: could consider adding malloc() support for very large messages
|
||||
//
|
||||
// A MachMessage object is used by ReceivePort::WaitForMessage
|
||||
// and MachPortSender::SendMessage
|
||||
//
|
||||
class MachMessage {
|
||||
public:
|
||||
|
||||
// The receiver of the message can retrieve the raw data this way
|
||||
u_int8_t *GetData() {
|
||||
return GetDataLength() > 0 ? GetDataPacket()->data : NULL;
|
||||
}
|
||||
|
||||
u_int32_t GetDataLength() {
|
||||
return EndianU32_LtoN(GetDataPacket()->data_length);
|
||||
}
|
||||
|
||||
// The message ID may be used as a code identifying the type of message
|
||||
void SetMessageID(int32_t message_id) {
|
||||
GetDataPacket()->id = EndianU32_NtoL(message_id);
|
||||
}
|
||||
|
||||
int32_t GetMessageID() { return EndianU32_LtoN(GetDataPacket()->id); }
|
||||
|
||||
// Adds a descriptor (typically a mach port) to be translated
|
||||
// returns true if successful, otherwise not enough space
|
||||
bool AddDescriptor(const MachMsgPortDescriptor &desc);
|
||||
|
||||
int GetDescriptorCount() const { return body.msgh_descriptor_count; }
|
||||
MachMsgPortDescriptor *GetDescriptor(int n);
|
||||
|
||||
// Convenience method which gets the mach port described by the descriptor
|
||||
mach_port_t GetTranslatedPort(int n);
|
||||
|
||||
// A simple message is one with no descriptors
|
||||
bool IsSimpleMessage() const { return GetDescriptorCount() == 0; }
|
||||
|
||||
// Sets raw data for the message (returns false if not enough space)
|
||||
bool SetData(void *data, int32_t data_length);
|
||||
|
||||
protected:
|
||||
// Consider this an abstract base class - must create an actual instance
|
||||
// of MachReceiveMessage or MachSendMessage
|
||||
|
||||
MachMessage() {
|
||||
memset(this, 0, sizeof(MachMessage));
|
||||
}
|
||||
|
||||
friend class ReceivePort;
|
||||
friend class MachPortSender;
|
||||
|
||||
// Represents raw data in our message
|
||||
struct MessageDataPacket {
|
||||
int32_t id; // little-endian
|
||||
int32_t data_length; // little-endian
|
||||
u_int8_t data[1]; // actual size limited by sizeof(MachMessage)
|
||||
};
|
||||
|
||||
MessageDataPacket* GetDataPacket();
|
||||
|
||||
void SetDescriptorCount(int n);
|
||||
void SetDescriptor(int n, const MachMsgPortDescriptor &desc);
|
||||
|
||||
// Returns total message size setting msgh_size in the header to this value
|
||||
int CalculateSize();
|
||||
|
||||
mach_msg_header_t head;
|
||||
mach_msg_body_t body;
|
||||
u_int8_t padding[1024]; // descriptors and data may be embedded here
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// MachReceiveMessage and MachSendMessage are useful to separate the idea
|
||||
// of a mach message being sent and being received, and adds increased type
|
||||
// safety:
|
||||
// ReceivePort::WaitForMessage() only accepts a MachReceiveMessage
|
||||
// MachPortSender::SendMessage() only accepts a MachSendMessage
|
||||
|
||||
//==============================================================================
|
||||
class MachReceiveMessage : public MachMessage {
|
||||
public:
|
||||
MachReceiveMessage() : MachMessage() {};
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class MachSendMessage : public MachMessage {
|
||||
public:
|
||||
MachSendMessage(int32_t message_id);
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Represents a mach port for which we have receive rights
|
||||
class ReceivePort {
|
||||
public:
|
||||
// Creates a new mach port for receiving messages and registers a name for it
|
||||
ReceivePort(const char *receive_port_name);
|
||||
|
||||
// Given an already existing mach port, use it. We take ownership of the
|
||||
// port and deallocate it in our destructor.
|
||||
ReceivePort(mach_port_t receive_port);
|
||||
|
||||
// Create a new mach port for receiving messages
|
||||
ReceivePort();
|
||||
|
||||
~ReceivePort();
|
||||
|
||||
// Waits on the mach port until message received or timeout
|
||||
kern_return_t WaitForMessage(MachReceiveMessage *out_message,
|
||||
mach_msg_timeout_t timeout);
|
||||
|
||||
// The underlying mach port that we wrap
|
||||
mach_port_t GetPort() const { return port_; }
|
||||
|
||||
private:
|
||||
ReceivePort(const ReceivePort&); // disable copy c-tor
|
||||
|
||||
mach_port_t port_;
|
||||
kern_return_t init_result_;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Represents a mach port for which we have send rights
|
||||
class MachPortSender {
|
||||
public:
|
||||
// get a port with send rights corresponding to a named registered service
|
||||
MachPortSender(const char *receive_port_name);
|
||||
|
||||
|
||||
// Given an already existing mach port, use it.
|
||||
MachPortSender(mach_port_t send_port);
|
||||
|
||||
kern_return_t SendMessage(MachSendMessage &message,
|
||||
mach_msg_timeout_t timeout);
|
||||
|
||||
private:
|
||||
MachPortSender(const MachPortSender&); // disable copy c-tor
|
||||
|
||||
mach_port_t send_port_;
|
||||
kern_return_t init_result_;
|
||||
};
|
||||
|
||||
#endif // MACH_IPC_H__
|
|
@ -0,0 +1,297 @@
|
|||
// 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.
|
||||
//
|
||||
// MachIPC.mm
|
||||
// Wrapper for mach IPC calls
|
||||
|
||||
#import <stdio.h>
|
||||
#import "MachIPC.h"
|
||||
|
||||
//==============================================================================
|
||||
MachSendMessage::MachSendMessage(int32_t message_id) : MachMessage() {
|
||||
head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
|
||||
|
||||
// head.msgh_remote_port = ...; // filled out in MachPortSender::SendMessage()
|
||||
head.msgh_local_port = MACH_PORT_NULL;
|
||||
head.msgh_reserved = 0;
|
||||
head.msgh_id = 0;
|
||||
|
||||
SetDescriptorCount(0); // start out with no descriptors
|
||||
|
||||
SetMessageID(message_id);
|
||||
SetData(NULL, 0); // client may add data later
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// returns true if successful
|
||||
bool MachMessage::SetData(void *data,
|
||||
int32_t data_length) {
|
||||
// first check to make sure we have enough space
|
||||
int size = CalculateSize();
|
||||
int new_size = size + data_length;
|
||||
|
||||
if ((unsigned)new_size > sizeof(MachMessage)) {
|
||||
return false; // not enough space
|
||||
}
|
||||
|
||||
GetDataPacket()->data_length = EndianU32_NtoL(data_length);
|
||||
if (data) memcpy(GetDataPacket()->data, data, data_length);
|
||||
|
||||
CalculateSize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// calculates and returns the total size of the message
|
||||
// Currently, the entire message MUST fit inside of the MachMessage
|
||||
// messsage size <= sizeof(MachMessage)
|
||||
int MachMessage::CalculateSize() {
|
||||
int size = sizeof(mach_msg_header_t) + sizeof(mach_msg_body_t);
|
||||
|
||||
// add space for MessageDataPacket
|
||||
int32_t alignedDataLength = (GetDataLength() + 3) & ~0x3;
|
||||
size += 2*sizeof(int32_t) + alignedDataLength;
|
||||
|
||||
// add space for descriptors
|
||||
size += GetDescriptorCount() * sizeof(MachMsgPortDescriptor);
|
||||
|
||||
head.msgh_size = size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MachMessage::MessageDataPacket *MachMessage::GetDataPacket() {
|
||||
int desc_size = sizeof(MachMsgPortDescriptor)*GetDescriptorCount();
|
||||
MessageDataPacket *packet =
|
||||
reinterpret_cast<MessageDataPacket*>(padding + desc_size);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MachMessage::SetDescriptor(int n,
|
||||
const MachMsgPortDescriptor &desc) {
|
||||
MachMsgPortDescriptor *desc_array =
|
||||
reinterpret_cast<MachMsgPortDescriptor*>(padding);
|
||||
desc_array[n] = desc;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// returns true if successful otherwise there was not enough space
|
||||
bool MachMessage::AddDescriptor(const MachMsgPortDescriptor &desc) {
|
||||
// first check to make sure we have enough space
|
||||
int size = CalculateSize();
|
||||
int new_size = size + sizeof(MachMsgPortDescriptor);
|
||||
|
||||
if ((unsigned)new_size > sizeof(MachMessage)) {
|
||||
return false; // not enough space
|
||||
}
|
||||
|
||||
// unfortunately, we need to move the data to allow space for the
|
||||
// new descriptor
|
||||
u_int8_t *p = reinterpret_cast<u_int8_t*>(GetDataPacket());
|
||||
bcopy(p, p+sizeof(MachMsgPortDescriptor), GetDataLength()+2*sizeof(int32_t));
|
||||
|
||||
SetDescriptor(GetDescriptorCount(), desc);
|
||||
SetDescriptorCount(GetDescriptorCount() + 1);
|
||||
|
||||
CalculateSize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MachMessage::SetDescriptorCount(int n) {
|
||||
body.msgh_descriptor_count = n;
|
||||
|
||||
if (n > 0) {
|
||||
head.msgh_bits |= MACH_MSGH_BITS_COMPLEX;
|
||||
} else {
|
||||
head.msgh_bits &= ~MACH_MSGH_BITS_COMPLEX;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MachMsgPortDescriptor *MachMessage::GetDescriptor(int n) {
|
||||
if (n < GetDescriptorCount()) {
|
||||
MachMsgPortDescriptor *desc =
|
||||
reinterpret_cast<MachMsgPortDescriptor*>(padding);
|
||||
return desc + n;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
mach_port_t MachMessage::GetTranslatedPort(int n) {
|
||||
if (n < GetDescriptorCount()) {
|
||||
return GetDescriptor(n)->GetMachPort();
|
||||
}
|
||||
return MACH_PORT_NULL;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
//==============================================================================
|
||||
// create a new mach port for receiving messages and register a name for it
|
||||
ReceivePort::ReceivePort(const char *receive_port_name) {
|
||||
mach_port_t current_task = mach_task_self();
|
||||
|
||||
init_result_ = mach_port_allocate(current_task,
|
||||
MACH_PORT_RIGHT_RECEIVE,
|
||||
&port_);
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
init_result_ = mach_port_insert_right(current_task,
|
||||
port_,
|
||||
port_,
|
||||
MACH_MSG_TYPE_MAKE_SEND);
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
mach_port_t bootstrap_port = 0;
|
||||
init_result_ = task_get_bootstrap_port(current_task, &bootstrap_port);
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
init_result_ = bootstrap_register(bootstrap_port,
|
||||
const_cast<char*>(receive_port_name),
|
||||
port_);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// create a new mach port for receiving messages
|
||||
ReceivePort::ReceivePort() {
|
||||
mach_port_t current_task = mach_task_self();
|
||||
|
||||
init_result_ = mach_port_allocate(current_task,
|
||||
MACH_PORT_RIGHT_RECEIVE,
|
||||
&port_);
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
init_result_ = mach_port_insert_right(current_task,
|
||||
port_,
|
||||
port_,
|
||||
MACH_MSG_TYPE_MAKE_SEND);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// Given an already existing mach port, use it. We take ownership of the
|
||||
// port and deallocate it in our destructor.
|
||||
ReceivePort::ReceivePort(mach_port_t receive_port)
|
||||
: port_(receive_port),
|
||||
init_result_(KERN_SUCCESS) {
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ReceivePort::~ReceivePort() {
|
||||
if (init_result_ == KERN_SUCCESS)
|
||||
mach_port_deallocate(mach_task_self(), port_);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
kern_return_t ReceivePort::WaitForMessage(MachReceiveMessage *out_message,
|
||||
mach_msg_timeout_t timeout) {
|
||||
if (!out_message) {
|
||||
return KERN_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
// return any error condition encountered in constructor
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return init_result_;
|
||||
|
||||
out_message->head.msgh_bits = 0;
|
||||
out_message->head.msgh_local_port = port_;
|
||||
out_message->head.msgh_remote_port = MACH_PORT_NULL;
|
||||
out_message->head.msgh_reserved = 0;
|
||||
out_message->head.msgh_id = 0;
|
||||
|
||||
kern_return_t result = mach_msg(&out_message->head,
|
||||
MACH_RCV_MSG | MACH_RCV_TIMEOUT,
|
||||
0,
|
||||
sizeof(MachMessage),
|
||||
port_,
|
||||
timeout, // timeout in ms
|
||||
MACH_PORT_NULL);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
//==============================================================================
|
||||
// get a port with send rights corresponding to a named registered service
|
||||
MachPortSender::MachPortSender(const char *receive_port_name) {
|
||||
mach_port_t bootstrap_port = 0;
|
||||
init_result_ = task_get_bootstrap_port(mach_task_self(), &bootstrap_port);
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
init_result_ = bootstrap_look_up(bootstrap_port,
|
||||
const_cast<char*>(receive_port_name),
|
||||
&send_port_);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MachPortSender::MachPortSender(mach_port_t send_port)
|
||||
: send_port_(send_port),
|
||||
init_result_(KERN_SUCCESS) {
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
kern_return_t MachPortSender::SendMessage(MachSendMessage &message,
|
||||
mach_msg_timeout_t timeout) {
|
||||
if (message.head.msgh_size == 0) {
|
||||
return KERN_INVALID_VALUE; // just for safety -- never should occur
|
||||
};
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return init_result_;
|
||||
|
||||
message.head.msgh_remote_port = send_port_;
|
||||
|
||||
kern_return_t result = mach_msg(&message.head,
|
||||
MACH_SEND_MSG | MACH_SEND_TIMEOUT,
|
||||
message.head.msgh_size,
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
timeout, // timeout in ms
|
||||
MACH_PORT_NULL);
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
// 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.
|
||||
//
|
||||
// SimpleStringDictionary.h
|
||||
//
|
||||
|
||||
#ifndef SimpleStringDictionary_H__
|
||||
#define SimpleStringDictionary_H__
|
||||
|
||||
#import <string>
|
||||
#import <vector>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
//==============================================================================
|
||||
// SimpleStringDictionary (and associated class KeyValueEntry) implement a very
|
||||
// basic dictionary container class. It has the property of not making any
|
||||
// memory allocations when getting and setting values. But it is not very
|
||||
// efficient, with calls to get and set values operating in linear time.
|
||||
// It has the additional limitation of having a fairly small fixed capacity of
|
||||
// SimpleStringDictionary::MAX_NUM_ENTRIES entries. An assert() will fire if
|
||||
// the client attempts to set more than this number of key/value pairs.
|
||||
// Ordinarilly a C++ programmer would use something like the std::map template
|
||||
// class, or on the Macintosh would often choose CFDictionary or NSDictionary.
|
||||
// But these dictionary classes may call malloc() during get and set operations.
|
||||
// Google Breakpad requires that no memory allocations be made in code running
|
||||
// in its exception handling thread, so it uses SimpleStringDictionary as the
|
||||
// underlying implementation for the GoogleBreakpad.framework APIs:
|
||||
// GoogleBreakpadSetKeyValue(), GoogleBreakpadKeyValue(), and
|
||||
// GoogleBreakpadRemoveKeyValue()
|
||||
//
|
||||
|
||||
//==============================================================================
|
||||
// KeyValueEntry
|
||||
//
|
||||
// A helper class used by SimpleStringDictionary representing a single
|
||||
// storage cell for a key/value pair. Each key and value string are
|
||||
// limited to MAX_STRING_STORAGE_SIZE-1 bytes (not glyphs). This class
|
||||
// performs no memory allocations. It has methods for setting and getting
|
||||
// key and value strings.
|
||||
//
|
||||
class KeyValueEntry {
|
||||
public:
|
||||
KeyValueEntry() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
KeyValueEntry(const char *key, const char *value) {
|
||||
SetKeyValue(key, value);
|
||||
}
|
||||
|
||||
void SetKeyValue(const char *key, const char *value) {
|
||||
if (!key) {
|
||||
key = "";
|
||||
}
|
||||
if (!value) {
|
||||
value = "";
|
||||
}
|
||||
|
||||
strlcpy(key_, key, sizeof(key_));
|
||||
strlcpy(value_, value, sizeof(value_));
|
||||
}
|
||||
|
||||
void SetValue(const char *value) {
|
||||
if (!value) {
|
||||
value = "";
|
||||
}
|
||||
strlcpy(value_, value, sizeof(value_));
|
||||
};
|
||||
|
||||
// Removes the key/value
|
||||
void Clear() {
|
||||
memset(key_, 0, sizeof(key_));
|
||||
memset(value_, 0, sizeof(value_));
|
||||
}
|
||||
|
||||
bool IsActive() const { return key_[0] != '\0'; }
|
||||
const char *GetKey() const { return key_; }
|
||||
const char *GetValue() const { return value_; }
|
||||
|
||||
// Don't change this without considering the fixed size
|
||||
// of MachMessage (in MachIPC.h)
|
||||
// (see also struct KeyValueMessageData in Inspector.h)
|
||||
enum {MAX_STRING_STORAGE_SIZE = 256};
|
||||
|
||||
private:
|
||||
char key_[MAX_STRING_STORAGE_SIZE];
|
||||
char value_[MAX_STRING_STORAGE_SIZE];
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// This class is not an efficient dictionary, but for the purposes of breakpad
|
||||
// will be just fine. We're just dealing with ten or so distinct
|
||||
// key/value pairs. The idea is to avoid any malloc() or free() calls
|
||||
// in certain important methods to be called when a process is in a
|
||||
// crashed state. Each key and value string are limited to
|
||||
// KeyValueEntry::MAX_STRING_STORAGE_SIZE-1 bytes (not glyphs). Strings passed
|
||||
// in exceeding this length will be truncated.
|
||||
//
|
||||
class SimpleStringDictionary {
|
||||
public:
|
||||
SimpleStringDictionary() {}; // entries will all be cleared
|
||||
|
||||
// Returns the number of active key/value pairs. The upper limit for this
|
||||
// is MAX_NUM_ENTRIES.
|
||||
int GetCount() const;
|
||||
|
||||
// Given |key|, returns its corresponding |value|.
|
||||
// If |key| is NULL, an assert will fire or NULL will be returned. If |key|
|
||||
// is not found or is an empty string, NULL is returned.
|
||||
const char *GetValueForKey(const char *key);
|
||||
|
||||
// Stores a string |value| represented by |key|. If |key| is NULL or an empty
|
||||
// string, this will assert (or do nothing). If |value| is NULL then
|
||||
// the |key| will be removed. An empty string is OK for |value|.
|
||||
void SetKeyValue(const char *key, const char *value);
|
||||
|
||||
// Given |key|, removes any associated value. It will assert (or do nothing)
|
||||
// if NULL is passed in. It will do nothing if |key| is not found.
|
||||
void RemoveKey(const char *key);
|
||||
|
||||
// This is the maximum number of key/value pairs which may be set in the
|
||||
// dictionary. An assert may fire if more values than this are set.
|
||||
// Don't change this without also changing comment in GoogleBreakpad.h
|
||||
enum {MAX_NUM_ENTRIES = 64};
|
||||
|
||||
private:
|
||||
friend class SimpleStringDictionaryIterator;
|
||||
|
||||
const KeyValueEntry *GetEntry(int i) const;
|
||||
|
||||
KeyValueEntry entries_[MAX_NUM_ENTRIES];
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class SimpleStringDictionaryIterator {
|
||||
public:
|
||||
SimpleStringDictionaryIterator(const SimpleStringDictionary &dict)
|
||||
: dict_(dict), i_(0) {
|
||||
}
|
||||
|
||||
// Initializes iterator to the beginning (may later call Next() )
|
||||
void Start() {
|
||||
i_ = 0;
|
||||
}
|
||||
|
||||
// like the nextObject method of NSEnumerator (in Cocoa)
|
||||
// returns NULL when there are no more entries
|
||||
//
|
||||
const KeyValueEntry* Next() {
|
||||
for (; i_ < SimpleStringDictionary::MAX_NUM_ENTRIES; ++i_) {
|
||||
const KeyValueEntry *entry = dict_.GetEntry(i_);
|
||||
if (entry->IsActive()) {
|
||||
i_++; // move to next entry for next time
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL; // reached end of array
|
||||
}
|
||||
|
||||
private:
|
||||
const SimpleStringDictionary& dict_;
|
||||
int i_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // SimpleStringDictionary_H__
|
|
@ -0,0 +1,131 @@
|
|||
// 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.
|
||||
//
|
||||
// SimpleStringDictionary.mm
|
||||
// Simple string dictionary that does not allocate memory
|
||||
//
|
||||
|
||||
#import "SimpleStringDictionary.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
//==============================================================================
|
||||
const KeyValueEntry *SimpleStringDictionary::GetEntry(int i) const {
|
||||
return (i >= 0 && i < MAX_NUM_ENTRIES) ? &entries_[i] : NULL;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int SimpleStringDictionary::GetCount() const {
|
||||
int count = 0;
|
||||
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||
if (entries_[i].IsActive() ) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const char *SimpleStringDictionary::GetValueForKey(const char *key) {
|
||||
assert(key);
|
||||
if (!key)
|
||||
return NULL;
|
||||
|
||||
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||
KeyValueEntry &entry = entries_[i];
|
||||
if (entry.IsActive() && !strcmp(entry.GetKey(), key)) {
|
||||
return entry.GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SimpleStringDictionary::SetKeyValue(const char *key,
|
||||
const char *value) {
|
||||
if (!value) {
|
||||
RemoveKey(key);
|
||||
return;
|
||||
}
|
||||
|
||||
// key must not be NULL
|
||||
assert(key);
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
// key must not be empty string
|
||||
assert(key[0] != '\0');
|
||||
if (key[0] == '\0')
|
||||
return;
|
||||
|
||||
int free_index = -1;
|
||||
|
||||
// check if key already exists
|
||||
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||
KeyValueEntry &entry = entries_[i];
|
||||
|
||||
if (entry.IsActive()) {
|
||||
if (!strcmp(entry.GetKey(), key)) {
|
||||
entry.SetValue(value);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Make a note of an empty slot
|
||||
if (free_index == -1) {
|
||||
free_index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if we've run out of space
|
||||
assert(free_index != -1);
|
||||
|
||||
// Put new key into an empty slot (if found)
|
||||
if (free_index != -1) {
|
||||
entries_[free_index].SetKeyValue(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SimpleStringDictionary::RemoveKey(const char *key) {
|
||||
assert(key);
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||
if (!strcmp(entries_[i].GetKey(), key)) {
|
||||
entries_[i].Clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,366 @@
|
|||
//
|
||||
// GTMSenTestCase.m
|
||||
//
|
||||
// Copyright 2007-2008 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy
|
||||
// of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
//
|
||||
|
||||
#import "GTMSenTestCase.h"
|
||||
#import <unistd.h>
|
||||
|
||||
#if !GTM_IPHONE_SDK
|
||||
#import "GTMGarbageCollection.h"
|
||||
#endif // !GTM_IPHONE_SDK
|
||||
|
||||
#if GTM_IPHONE_SDK
|
||||
#import <stdarg.h>
|
||||
|
||||
@interface NSException (GTMSenTestPrivateAdditions)
|
||||
+ (NSException *)failureInFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
reason:(NSString *)reason;
|
||||
@end
|
||||
|
||||
@implementation NSException (GTMSenTestPrivateAdditions)
|
||||
+ (NSException *)failureInFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
reason:(NSString *)reason {
|
||||
NSDictionary *userInfo =
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithInteger:lineNumber], SenTestLineNumberKey,
|
||||
filename, SenTestFilenameKey,
|
||||
nil];
|
||||
|
||||
return [self exceptionWithName:SenTestFailureException
|
||||
reason:reason
|
||||
userInfo:userInfo];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation NSException (GTMSenTestAdditions)
|
||||
|
||||
+ (NSException *)failureInFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason = testDescription;
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
+ (NSException *)failureInCondition:(NSString *)condition
|
||||
isTrue:(BOOL)isTrue
|
||||
inFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason = [NSString stringWithFormat:@"'%@' should be %s. %@",
|
||||
condition, isTrue ? "TRUE" : "FALSE", testDescription];
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
+ (NSException *)failureInEqualityBetweenObject:(id)left
|
||||
andObject:(id)right
|
||||
inFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason =
|
||||
[NSString stringWithFormat:@"'%@' should be equal to '%@'. %@",
|
||||
[left description], [right description], testDescription];
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
+ (NSException *)failureInEqualityBetweenValue:(NSValue *)left
|
||||
andValue:(NSValue *)right
|
||||
withAccuracy:(NSValue *)accuracy
|
||||
inFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason;
|
||||
if (accuracy) {
|
||||
reason =
|
||||
[NSString stringWithFormat:@"'%@' should be equal to '%@'. %@",
|
||||
left, right, testDescription];
|
||||
} else {
|
||||
reason =
|
||||
[NSString stringWithFormat:@"'%@' should be equal to '%@' +/-'%@'. %@",
|
||||
left, right, accuracy, testDescription];
|
||||
}
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
+ (NSException *)failureInRaise:(NSString *)expression
|
||||
inFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason = [NSString stringWithFormat:@"'%@' should raise. %@",
|
||||
expression, testDescription];
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
+ (NSException *)failureInRaise:(NSString *)expression
|
||||
exception:(NSException *)exception
|
||||
inFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason;
|
||||
if ([[exception name] isEqualToString:SenTestFailureException]) {
|
||||
// it's our exception, assume it has the right description on it.
|
||||
reason = [exception reason];
|
||||
} else {
|
||||
// not one of our exception, use the exceptions reason and our description
|
||||
reason = [NSString stringWithFormat:@"'%@' raised '%@'. %@",
|
||||
expression, [exception reason], testDescription];
|
||||
}
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NSString *STComposeString(NSString *formatString, ...) {
|
||||
NSString *reason = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
reason =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
|
||||
NSString *const SenTestFailureException = @"SenTestFailureException";
|
||||
NSString *const SenTestFilenameKey = @"SenTestFilenameKey";
|
||||
NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey";
|
||||
|
||||
@interface SenTestCase (SenTestCasePrivate)
|
||||
// our method of logging errors
|
||||
+ (void)printException:(NSException *)exception fromTestName:(NSString *)name;
|
||||
@end
|
||||
|
||||
@implementation SenTestCase
|
||||
- (void)failWithException:(NSException*)exception {
|
||||
[exception raise];
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
}
|
||||
|
||||
- (void)performTest:(SEL)sel {
|
||||
currentSelector_ = sel;
|
||||
@try {
|
||||
[self invokeTest];
|
||||
} @catch (NSException *exception) {
|
||||
[[self class] printException:exception
|
||||
fromTestName:NSStringFromSelector(sel)];
|
||||
[exception raise];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)printException:(NSException *)exception fromTestName:(NSString *)name {
|
||||
NSDictionary *userInfo = [exception userInfo];
|
||||
NSString *filename = [userInfo objectForKey:SenTestFilenameKey];
|
||||
NSNumber *lineNumber = [userInfo objectForKey:SenTestLineNumberKey];
|
||||
NSString *className = NSStringFromClass([self class]);
|
||||
if ([filename length] == 0) {
|
||||
filename = @"Unknown.m";
|
||||
}
|
||||
fprintf(stderr, "%s:%ld: error: -[%s %s] : %s\n",
|
||||
[filename UTF8String],
|
||||
(long)[lineNumber integerValue],
|
||||
[className UTF8String],
|
||||
[name UTF8String],
|
||||
[[exception reason] UTF8String]);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
- (void)invokeTest {
|
||||
NSException *e = nil;
|
||||
@try {
|
||||
// Wrap things in autorelease pools because they may
|
||||
// have an STMacro in their dealloc which may get called
|
||||
// when the pool is cleaned up
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
// We don't log exceptions here, instead we let the person that called
|
||||
// this log the exception. This ensures they are only logged once but the
|
||||
// outer layers get the exceptions to report counts, etc.
|
||||
@try {
|
||||
[self setUp];
|
||||
@try {
|
||||
[self performSelector:currentSelector_];
|
||||
} @catch (NSException *exception) {
|
||||
e = [exception retain];
|
||||
}
|
||||
[self tearDown];
|
||||
} @catch (NSException *exception) {
|
||||
e = [exception retain];
|
||||
}
|
||||
[pool release];
|
||||
} @catch (NSException *exception) {
|
||||
e = [exception retain];
|
||||
}
|
||||
if (e) {
|
||||
[e autorelease];
|
||||
[e raise];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
// This matches the description OCUnit would return to you
|
||||
return [NSString stringWithFormat:@"-[%@ %@]", [self class],
|
||||
NSStringFromSelector(currentSelector_)];
|
||||
}
|
||||
@end
|
||||
|
||||
#endif // GTM_IPHONE_SDK
|
||||
|
||||
@implementation GTMTestCase : SenTestCase
|
||||
- (void)invokeTest {
|
||||
Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog");
|
||||
if (devLogClass) {
|
||||
[devLogClass performSelector:@selector(enableTracking)];
|
||||
[devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
|
||||
|
||||
}
|
||||
[super invokeTest];
|
||||
if (devLogClass) {
|
||||
[devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
|
||||
[devLogClass performSelector:@selector(disableTracking)];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
// Leak detection
|
||||
#if !GTM_IPHONE_DEVICE
|
||||
// Don't want to get leaks on the iPhone Device as the device doesn't
|
||||
// have 'leaks'. The simulator does though.
|
||||
|
||||
// COV_NF_START
|
||||
// We don't have leak checking on by default, so this won't be hit.
|
||||
static void _GTMRunLeaks(void) {
|
||||
// This is an atexit handler. It runs leaks for us to check if we are
|
||||
// leaking anything in our tests.
|
||||
const char* cExclusionsEnv = getenv("GTM_LEAKS_SYMBOLS_TO_IGNORE");
|
||||
NSMutableString *exclusions = [NSMutableString string];
|
||||
if (cExclusionsEnv) {
|
||||
NSString *exclusionsEnv = [NSString stringWithUTF8String:cExclusionsEnv];
|
||||
NSArray *exclusionsArray = [exclusionsEnv componentsSeparatedByString:@","];
|
||||
NSString *exclusion;
|
||||
NSCharacterSet *wcSet = [NSCharacterSet whitespaceCharacterSet];
|
||||
GTM_FOREACH_OBJECT(exclusion, exclusionsArray) {
|
||||
exclusion = [exclusion stringByTrimmingCharactersInSet:wcSet];
|
||||
[exclusions appendFormat:@"-exclude \"%@\" ", exclusion];
|
||||
}
|
||||
}
|
||||
NSString *string
|
||||
= [NSString stringWithFormat:@"/usr/bin/leaks %@%d"
|
||||
@"| /usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'",
|
||||
exclusions, getpid()];
|
||||
int ret = system([string UTF8String]);
|
||||
if (ret) {
|
||||
fprintf(stderr, "%s:%d: Error: Unable to run leaks. 'system' returned: %d",
|
||||
__FILE__, __LINE__, ret);
|
||||
fflush(stderr);
|
||||
}
|
||||
}
|
||||
// COV_NF_END
|
||||
|
||||
static __attribute__((constructor)) void _GTMInstallLeaks(void) {
|
||||
BOOL checkLeaks = YES;
|
||||
#if !GTM_IPHONE_SDK
|
||||
checkLeaks = GTMIsGarbageCollectionEnabled() ? NO : YES;
|
||||
#endif // !GTM_IPHONE_SDK
|
||||
if (checkLeaks) {
|
||||
checkLeaks = getenv("GTM_ENABLE_LEAKS") ? YES : NO;
|
||||
if (checkLeaks) {
|
||||
// COV_NF_START
|
||||
// We don't have leak checking on by default, so this won't be hit.
|
||||
fprintf(stderr, "Leak Checking Enabled\n");
|
||||
fflush(stderr);
|
||||
int ret = atexit(&_GTMRunLeaks);
|
||||
_GTMDevAssert(ret == 0,
|
||||
@"Unable to install _GTMRunLeaks as an atexit handler (%d)",
|
||||
errno);
|
||||
// COV_NF_END
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !GTM_IPHONE_DEVICE
|
|
@ -176,7 +176,9 @@ inline void RecalculateOffset(struct slist* cur_list, char *stabstr) {
|
|||
// Demangle using demangle library on Solaris.
|
||||
std::string Demangle(const char *mangled) {
|
||||
int status = 0;
|
||||
std::string str(mangled);
|
||||
char *demangled = (char *)malloc(demangleLen);
|
||||
|
||||
if (!demangled) {
|
||||
fprintf(stderr, "no enough memory.\n");
|
||||
goto out;
|
||||
|
@ -188,12 +190,11 @@ std::string Demangle(const char *mangled) {
|
|||
goto out;
|
||||
}
|
||||
|
||||
std::string str(demangled);
|
||||
str = demangled;
|
||||
free(demangled);
|
||||
return str;
|
||||
|
||||
out:
|
||||
return std::string(mangled);
|
||||
return str;
|
||||
}
|
||||
|
||||
bool WriteFormat(int fd, const char *fmt, ...) {
|
||||
|
@ -650,7 +651,7 @@ bool DumpSymbols::WriteSymbolFile(const std::string &obj_file, int sym_fd) {
|
|||
return false;
|
||||
void *obj_base = mmap(NULL, st.st_size,
|
||||
PROT_READ, MAP_PRIVATE, obj_fd, 0);
|
||||
if (!obj_base)
|
||||
if (obj_base == MAP_FAILED))
|
||||
return false;
|
||||
MmapWrapper map_wrapper(obj_base, st.st_size);
|
||||
GElf_Ehdr elf_header;
|
||||
|
|
|
@ -151,7 +151,7 @@ bool FileID::ElfFileIdentifier(unsigned char identifier[16]) {
|
|||
return false;
|
||||
|
||||
void *base = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (!base)
|
||||
if (base == MAP_FAILED)
|
||||
return false;
|
||||
|
||||
bool success = false;
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
#include <assert.h>
|
||||
|
||||
// Disable exception handler warnings.
|
||||
#pragma warning( disable : 4530 )
|
||||
#pragma warning( disable : 4530 )
|
||||
|
||||
#include <fstream>
|
||||
|
||||
|
@ -66,12 +66,13 @@ bool HTTPUpload::SendRequest(const wstring &url,
|
|||
const map<wstring, wstring> ¶meters,
|
||||
const wstring &upload_file,
|
||||
const wstring &file_part_name,
|
||||
int *timeout,
|
||||
wstring *response_body,
|
||||
int *response_code) {
|
||||
if (response_code) {
|
||||
*response_code = 0;
|
||||
}
|
||||
|
||||
|
||||
// TODO(bryner): support non-ASCII parameter names
|
||||
if (!CheckParameters(parameters)) {
|
||||
return false;
|
||||
|
@ -146,6 +147,22 @@ bool HTTPUpload::SendRequest(const wstring &url,
|
|||
return false;
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
if (!InternetSetOption(request.get(),
|
||||
INTERNET_OPTION_SEND_TIMEOUT,
|
||||
timeout,
|
||||
sizeof(timeout))) {
|
||||
fwprintf(stderr, L"Could not unset send timeout, continuing...\n");
|
||||
}
|
||||
|
||||
if (!InternetSetOption(request.get(),
|
||||
INTERNET_OPTION_RECEIVE_TIMEOUT,
|
||||
timeout,
|
||||
sizeof(timeout))) {
|
||||
fwprintf(stderr, L"Could not unset receive timeout, continuing...\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!HttpSendRequest(request.get(), NULL, 0,
|
||||
const_cast<char *>(request_body.data()),
|
||||
static_cast<DWORD>(request_body.size()))) {
|
||||
|
@ -194,17 +211,19 @@ bool HTTPUpload::ReadResponse(HINTERNET request, wstring *response) {
|
|||
|
||||
DWORD bytes_available;
|
||||
DWORD total_read = 0;
|
||||
bool return_code;
|
||||
BOOL return_code;
|
||||
|
||||
while (((return_code = InternetQueryDataAvailable(request, &bytes_available,
|
||||
0, 0)) != 0) && bytes_available > 0) {
|
||||
|
||||
while ((return_code = InternetQueryDataAvailable(request, &bytes_available,
|
||||
0, 0) != 0) &&
|
||||
bytes_available > 0) {
|
||||
vector<char> response_buffer(bytes_available);
|
||||
DWORD size_read;
|
||||
|
||||
if ((return_code = InternetReadFile(request, &response_buffer[0],
|
||||
bytes_available, &size_read) != 0) &&
|
||||
size_read > 0) {
|
||||
return_code = InternetReadFile(request,
|
||||
&response_buffer[0],
|
||||
bytes_available, &size_read);
|
||||
|
||||
if (return_code && size_read > 0) {
|
||||
total_read += size_read;
|
||||
response_body.append(&response_buffer[0], size_read);
|
||||
} else {
|
||||
|
@ -316,7 +335,7 @@ void HTTPUpload::GetFileContents(const wstring &filename,
|
|||
#endif // _MSC_VER >= 1400
|
||||
if (file.is_open()) {
|
||||
file.seekg(0, ios::end);
|
||||
int length = file.tellg();
|
||||
std::streamoff length = file.tellg();
|
||||
contents->resize(length);
|
||||
if (length != 0) {
|
||||
file.seekg(0, ios::beg);
|
||||
|
|
|
@ -69,6 +69,7 @@ class HTTPUpload {
|
|||
const map<wstring, wstring> ¶meters,
|
||||
const wstring &upload_file,
|
||||
const wstring &file_part_name,
|
||||
int *timeout,
|
||||
wstring *response_body,
|
||||
int *response_code);
|
||||
|
||||
|
|
|
@ -107,7 +107,11 @@ typedef struct {
|
|||
/* Use the same 32-bit alignment when accessing this structure from 64-bit code
|
||||
* as is used natively in 32-bit code. #pragma pack is a MSVC extension
|
||||
* supported by gcc. */
|
||||
#if defined(__SUNPRO_C) || defined(__SUNPRO_CC)
|
||||
#pragma pack(4)
|
||||
#else
|
||||
#pragma pack(push, 4)
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
/* context_flags is not present in ppc_thread_state, but it aids
|
||||
|
@ -136,7 +140,11 @@ typedef struct {
|
|||
MDVectorSaveAreaPPC vector_save;
|
||||
} MDRawContextPPC; /* Based on ppc_thread_state */
|
||||
|
||||
#if defined(__SUNPRO_C) || defined(__SUNPRO_CC)
|
||||
#pragma pack(0)
|
||||
#else
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
|
||||
/* For (MDRawContextPPC).context_flags. These values indicate the type of
|
||||
* context stored in the structure. MD_CONTEXT_PPC is Breakpad-defined. Its
|
||||
|
|
|
@ -69,6 +69,12 @@ class BasicSourceLineResolver : public SourceLineResolverInterface {
|
|||
// retained until the BasicSourceLineResolver is destroyed.
|
||||
virtual bool LoadModule(const string &module_name, const string &map_file);
|
||||
|
||||
// Exactly the same as above, except the given map_buffer is used
|
||||
// for symbols.
|
||||
virtual bool LoadModuleUsingMapBuffer(const string &module_name,
|
||||
const string &map_buffer);
|
||||
|
||||
|
||||
virtual bool HasModule(const string &module_name) const;
|
||||
|
||||
virtual StackFrameInfo* FillSourceLineInfo(StackFrame *frame) const;
|
||||
|
|
|
@ -336,7 +336,9 @@ class MinidumpThreadList : public MinidumpStream {
|
|||
}
|
||||
static u_int32_t max_threads() { return max_threads_; }
|
||||
|
||||
unsigned int thread_count() const { return valid_ ? thread_count_ : 0; }
|
||||
unsigned int thread_count() const {
|
||||
return valid_ ? thread_count_ : 0;
|
||||
}
|
||||
|
||||
// Sequential access to threads.
|
||||
MinidumpThread* GetThreadAtIndex(unsigned int index) const;
|
||||
|
@ -755,8 +757,11 @@ class Minidump {
|
|||
// path is the pathname of a file containing the minidump.
|
||||
explicit Minidump(const string& path);
|
||||
|
||||
~Minidump();
|
||||
virtual ~Minidump();
|
||||
|
||||
virtual string path() const {
|
||||
return path_;
|
||||
}
|
||||
static void set_max_streams(u_int32_t max_streams) {
|
||||
max_streams_ = max_streams;
|
||||
}
|
||||
|
@ -767,19 +772,19 @@ class Minidump {
|
|||
}
|
||||
static u_int32_t max_string_length() { return max_string_length_; }
|
||||
|
||||
const MDRawHeader* header() const { return valid_ ? &header_ : NULL; }
|
||||
virtual const MDRawHeader* header() const { return valid_ ? &header_ : NULL; }
|
||||
|
||||
// Reads the minidump file's header and top-level stream directory.
|
||||
// The minidump is expected to be positioned at the beginning of the
|
||||
// header. Read() sets up the stream list and map, and validates the
|
||||
// Minidump object.
|
||||
bool Read();
|
||||
virtual bool Read();
|
||||
|
||||
// The next set of methods are stubs that call GetStream. They exist to
|
||||
// force code generation of the templatized API within the module, and
|
||||
// to avoid exposing an ugly API (GetStream needs to accept a garbage
|
||||
// parameter).
|
||||
MinidumpThreadList* GetThreadList();
|
||||
virtual MinidumpThreadList* GetThreadList();
|
||||
MinidumpModuleList* GetModuleList();
|
||||
MinidumpMemoryList* GetMemoryList();
|
||||
MinidumpException* GetException();
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#ifndef GOOGLE_BREAKPAD_PROCESSOR_MINIDUMP_PROCESSOR_H__
|
||||
#define GOOGLE_BREAKPAD_PROCESSOR_MINIDUMP_PROCESSOR_H__
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include "google_breakpad/common/breakpad_types.h"
|
||||
|
||||
|
@ -42,16 +43,53 @@ class ProcessState;
|
|||
class SourceLineResolverInterface;
|
||||
class SymbolSupplier;
|
||||
class SystemInfo;
|
||||
// Return type for Process()
|
||||
enum ProcessResult {
|
||||
PROCESS_OK, // The minidump was
|
||||
// processed
|
||||
// successfully.
|
||||
|
||||
PROCESS_ERROR_MINIDUMP_NOT_FOUND, // The minidump file
|
||||
// was not found.
|
||||
|
||||
PROCESS_ERROR_NO_MINIDUMP_HEADER, // The minidump file
|
||||
// had no header
|
||||
|
||||
PROCESS_ERROR_NO_THREAD_LIST, // The minidump file
|
||||
// had no thread list.
|
||||
|
||||
PROCESS_ERROR_GETTING_THREAD, // There was an error
|
||||
// getting one
|
||||
// thread's data from
|
||||
// the minidump.
|
||||
|
||||
PROCESS_ERROR_GETTING_THREAD_ID, // There was an error
|
||||
// getting a thread id
|
||||
// from the thread's
|
||||
// data.
|
||||
|
||||
PROCESS_ERROR_DUPLICATE_REQUESTING_THREADS, // There was more than
|
||||
// one requesting
|
||||
// thread.
|
||||
|
||||
PROCESS_ERROR_NO_MEMORY_FOR_THREAD, // A thread had no
|
||||
// memory region.
|
||||
|
||||
PROCESS_ERROR_NO_STACKWALKER_FOR_THREAD, // We couldn't
|
||||
// determine the
|
||||
// StackWalker to walk
|
||||
// the minidump's
|
||||
// threads.
|
||||
|
||||
PROCESS_SYMBOL_SUPPLIER_INTERRUPTED // The minidump
|
||||
// processing was
|
||||
// interrupted by the
|
||||
// SymbolSupplier(not
|
||||
// fatal)
|
||||
};
|
||||
|
||||
class MinidumpProcessor {
|
||||
public:
|
||||
// Return type for Process()
|
||||
enum ProcessResult {
|
||||
PROCESS_OK, // the minidump was processed successfully
|
||||
PROCESS_ERROR, // there was an error processing the minidump
|
||||
PROCESS_INTERRUPTED // processing was interrupted by the SymbolSupplier
|
||||
};
|
||||
|
||||
// Initializes this MinidumpProcessor. supplier should be an
|
||||
// implementation of the SymbolSupplier abstract base class.
|
||||
MinidumpProcessor(SymbolSupplier *supplier,
|
||||
|
@ -62,6 +100,10 @@ class MinidumpProcessor {
|
|||
ProcessResult Process(const string &minidump_file,
|
||||
ProcessState *process_state);
|
||||
|
||||
// Processes the minidump structure and fills process_state with the
|
||||
// result.
|
||||
ProcessResult Process(Minidump *minidump,
|
||||
ProcessState *process_state);
|
||||
// Populates the cpu_* fields of the |info| parameter with textual
|
||||
// representations of the CPU type that the minidump in |dump| was
|
||||
// produced on. Returns false if this information is not available in
|
||||
|
@ -84,6 +126,21 @@ class MinidumpProcessor {
|
|||
// was caused by a memory access violation.
|
||||
static string GetCrashReason(Minidump *dump, u_int64_t *address);
|
||||
|
||||
// This function returns true if the passed-in error code is
|
||||
// something unrecoverable(i.e. retry should not happen). For
|
||||
// instance, if the minidump is corrupt, then it makes no sense to
|
||||
// retry as we won't be able to glean additional information.
|
||||
// However, as an example of the other case, the symbol supplier can
|
||||
// return an error code indicating it was 'interrupted', which can
|
||||
// happen of the symbols are fetched from a remote store, and a
|
||||
// retry might be successful later on.
|
||||
// You should not call this method with PROCESS_OK! Test for
|
||||
// that separately before calling this.
|
||||
static bool IsErrorUnrecoverable(ProcessResult p) {
|
||||
assert(p != PROCESS_OK);
|
||||
return (p != PROCESS_SYMBOL_SUPPLIER_INTERRUPTED);
|
||||
}
|
||||
|
||||
private:
|
||||
SymbolSupplier *supplier_;
|
||||
SourceLineResolverInterface *resolver_;
|
||||
|
|
|
@ -56,6 +56,9 @@ class SourceLineResolverInterface {
|
|||
// map_file should contain line/address mappings for this module.
|
||||
virtual bool LoadModule(const string &module_name,
|
||||
const string &map_file) = 0;
|
||||
// Same as above, but takes the contents of a pre-read map buffer
|
||||
virtual bool LoadModuleUsingMapBuffer(const string &module_name,
|
||||
const string &map_buffer) = 0;
|
||||
|
||||
// Returns true if a module with the given name has been loaded.
|
||||
virtual bool HasModule(const string &module_name) const = 0;
|
||||
|
|
|
@ -59,12 +59,22 @@ class SymbolSupplier {
|
|||
|
||||
// Retrieves the symbol file for the given CodeModule, placing the
|
||||
// path in symbol_file if successful. system_info contains strings
|
||||
// identifying the operating system and CPU; SymbolSupplier may use to help
|
||||
// locate the symbol file. system_info may be NULL or its fields may be
|
||||
// empty if these values are unknown.
|
||||
// identifying the operating system and CPU; SymbolSupplier may use
|
||||
// to help locate the symbol file. system_info may be NULL or its
|
||||
// fields may be empty if these values are unknown. symbol_file
|
||||
// must be a pointer to a valid string
|
||||
virtual SymbolResult GetSymbolFile(const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
string *symbol_file) = 0;
|
||||
// Same as above, except also places symbol data into symbol_data.
|
||||
// If symbol_data is NULL, the data is not returned.
|
||||
// TODO(nealsid) Once we have symbol data caching behavior implemented
|
||||
// investigate making all symbol suppliers implement all methods,
|
||||
// and make this pure virtual
|
||||
virtual SymbolResult GetSymbolFile(const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
string *symbol_file,
|
||||
string *symbol_data) = 0;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
|
|
@ -29,6 +29,9 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
@ -106,9 +109,13 @@ class BasicSourceLineResolver::Module {
|
|||
public:
|
||||
Module(const string &name) : name_(name) { }
|
||||
|
||||
// Loads the given map file, returning true on success.
|
||||
// Loads the given map file, returning true on success. Reads the
|
||||
// map file into memory and calls LoadMapFromBuffer
|
||||
bool LoadMap(const string &map_file);
|
||||
|
||||
// Loads a map from the given buffer, returning true on success
|
||||
bool LoadMapFromBuffer(const string &map_buffer);
|
||||
|
||||
// Looks up the given relative address, and fills the StackFrame struct
|
||||
// with the result. Additional debugging information, if available, is
|
||||
// returned. If no additional information is available, returns NULL.
|
||||
|
@ -211,6 +218,27 @@ bool BasicSourceLineResolver::LoadModule(const string &module_name,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool BasicSourceLineResolver::LoadModuleUsingMapBuffer(
|
||||
const string &module_name,
|
||||
const string &map_buffer) {
|
||||
// Make sure we don't already have a module with the given name.
|
||||
if (modules_->find(module_name) != modules_->end()) {
|
||||
BPLOG(INFO) << "Symbols for module " << module_name << " already loaded";
|
||||
return false;
|
||||
}
|
||||
|
||||
BPLOG(INFO) << "Loading symbols for module " << module_name << " from buffer";
|
||||
|
||||
Module *module = new Module(module_name);
|
||||
if (!module->LoadMapFromBuffer(map_buffer)) {
|
||||
delete module;
|
||||
return false;
|
||||
}
|
||||
|
||||
modules_->insert(make_pair(module_name, module));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BasicSourceLineResolver::HasModule(const string &module_name) const {
|
||||
return modules_->find(module_name) != modules_->end();
|
||||
}
|
||||
|
@ -238,44 +266,61 @@ class AutoFileCloser {
|
|||
FILE *file_;
|
||||
};
|
||||
|
||||
bool BasicSourceLineResolver::Module::LoadMap(const string &map_file) {
|
||||
FILE *f = fopen(map_file.c_str(), "r");
|
||||
if (!f) {
|
||||
string error_string;
|
||||
int error_code = ErrnoString(&error_string);
|
||||
BPLOG(ERROR) << "Could not open " << map_file <<
|
||||
", error " << error_code << ": " << error_string;
|
||||
bool BasicSourceLineResolver::Module::LoadMapFromBuffer(
|
||||
const string &map_buffer) {
|
||||
linked_ptr<Function> cur_func;
|
||||
int line_number = 0;
|
||||
const char *map_buffer_c_str = map_buffer.c_str();
|
||||
char *save_ptr;
|
||||
|
||||
// set up our input buffer as a c-style string so we
|
||||
// can we use strtok()
|
||||
// have to copy because modifying the result of string::c_str is not
|
||||
// permitted
|
||||
size_t map_buffer_length = strlen(map_buffer_c_str);
|
||||
|
||||
// If the length is 0, we can still pretend we have a symbol file. This is
|
||||
// for scenarios that want to test symbol lookup, but don't necessarily care if
|
||||
// certain modules do not have any information, like system libraries.
|
||||
if (map_buffer_length == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
scoped_array<char> map_buffer_chars(new char[map_buffer_length]);
|
||||
if (map_buffer_chars == NULL) {
|
||||
BPLOG(ERROR) << "Memory allocation of " << map_buffer_length <<
|
||||
" bytes failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoFileCloser closer(f);
|
||||
strncpy(map_buffer_chars.get(), map_buffer_c_str, map_buffer_length);
|
||||
|
||||
// TODO(mmentovai): this might not be large enough to handle really long
|
||||
// lines, which might be present for FUNC lines of highly-templatized
|
||||
// code.
|
||||
char buffer[8192];
|
||||
linked_ptr<Function> cur_func;
|
||||
if (map_buffer_chars[map_buffer_length - 1] == '\n') {
|
||||
map_buffer_chars[map_buffer_length - 1] = '\0';
|
||||
}
|
||||
char *buffer;
|
||||
buffer = strtok_r(map_buffer_chars.get(), "\r\n", &save_ptr);
|
||||
|
||||
int line_number = 0;
|
||||
while (fgets(buffer, sizeof(buffer), f)) {
|
||||
while (buffer != NULL) {
|
||||
++line_number;
|
||||
|
||||
if (strncmp(buffer, "FILE ", 5) == 0) {
|
||||
if (!ParseFile(buffer)) {
|
||||
BPLOG(ERROR) << "ParseFile failed at " <<
|
||||
map_file << ":" << line_number;
|
||||
BPLOG(ERROR) << "ParseFile on buffer failed at " <<
|
||||
":" << line_number;
|
||||
return false;
|
||||
}
|
||||
} else if (strncmp(buffer, "STACK ", 6) == 0) {
|
||||
if (!ParseStackInfo(buffer)) {
|
||||
BPLOG(ERROR) << "ParseStackInfo failed at " <<
|
||||
map_file << ":" << line_number;
|
||||
":" << line_number;
|
||||
return false;
|
||||
}
|
||||
} else if (strncmp(buffer, "FUNC ", 5) == 0) {
|
||||
cur_func.reset(ParseFunction(buffer));
|
||||
if (!cur_func.get()) {
|
||||
BPLOG(ERROR) << "ParseFunction failed at " <<
|
||||
map_file << ":" << line_number;
|
||||
":" << line_number;
|
||||
return false;
|
||||
}
|
||||
// StoreRange will fail if the function has an invalid address or size.
|
||||
|
@ -288,7 +333,7 @@ bool BasicSourceLineResolver::Module::LoadMap(const string &map_file) {
|
|||
|
||||
if (!ParsePublicSymbol(buffer)) {
|
||||
BPLOG(ERROR) << "ParsePublicSymbol failed at " <<
|
||||
map_file << ":" << line_number;
|
||||
":" << line_number;
|
||||
return false;
|
||||
}
|
||||
} else if (strncmp(buffer, "MODULE ", 7) == 0) {
|
||||
|
@ -301,23 +346,80 @@ bool BasicSourceLineResolver::Module::LoadMap(const string &map_file) {
|
|||
} else {
|
||||
if (!cur_func.get()) {
|
||||
BPLOG(ERROR) << "Found source line data without a function at " <<
|
||||
map_file << ":" << line_number;
|
||||
":" << line_number;
|
||||
return false;
|
||||
}
|
||||
Line *line = ParseLine(buffer);
|
||||
if (!line) {
|
||||
BPLOG(ERROR) << "ParseLine failed at " <<
|
||||
map_file << ":" << line_number;
|
||||
BPLOG(ERROR) << "ParseLine failed at " << line_number << " for " <<
|
||||
buffer;
|
||||
return false;
|
||||
}
|
||||
cur_func->lines.StoreRange(line->address, line->size,
|
||||
linked_ptr<Line>(line));
|
||||
}
|
||||
|
||||
buffer = strtok_r(NULL, "\r\n", &save_ptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BasicSourceLineResolver::Module::LoadMap(const string &map_file) {
|
||||
struct stat buf;
|
||||
int error_code = stat(map_file.c_str(), &buf);
|
||||
if (error_code == -1) {
|
||||
string error_string;
|
||||
int error_code = ErrnoString(&error_string);
|
||||
BPLOG(ERROR) << "Could not open " << map_file <<
|
||||
", error " << error_code << ": " << error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
off_t file_size = buf.st_size;
|
||||
|
||||
// Allocate memory for file contents, plus a null terminator
|
||||
// since we'll use strtok() on the contents.
|
||||
char *file_buffer = new char[sizeof(char)*file_size + 1];
|
||||
|
||||
if (file_buffer == NULL) {
|
||||
BPLOG(ERROR) << "Could not allocate memory for " << map_file;
|
||||
return false;
|
||||
}
|
||||
|
||||
BPLOG(ERROR) << "Opening " << map_file;
|
||||
|
||||
FILE *f = fopen(map_file.c_str(), "rt");
|
||||
if (!f) {
|
||||
string error_string;
|
||||
int error_code = ErrnoString(&error_string);
|
||||
BPLOG(ERROR) << "Could not open " << map_file <<
|
||||
", error " << error_code << ": " << error_string;
|
||||
delete [] file_buffer;
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoFileCloser closer(f);
|
||||
|
||||
int items_read = 0;
|
||||
|
||||
items_read = fread(file_buffer, 1, file_size, f);
|
||||
|
||||
if (items_read != file_size) {
|
||||
string error_string;
|
||||
int error_code = ErrnoString(&error_string);
|
||||
BPLOG(ERROR) << "Could not slurp " << map_file <<
|
||||
", error " << error_code << ": " << error_string;
|
||||
delete [] file_buffer;
|
||||
return false;
|
||||
}
|
||||
file_buffer[file_size] = '\0';
|
||||
string map_buffer(file_buffer);
|
||||
delete [] file_buffer;
|
||||
|
||||
return LoadMapFromBuffer(map_buffer);
|
||||
}
|
||||
|
||||
StackFrameInfo* BasicSourceLineResolver::Module::LookupAddress(
|
||||
StackFrame *frame) const {
|
||||
MemAddr address = frame->instruction - frame->module->base_address();
|
||||
|
|
|
@ -173,8 +173,10 @@ static bool RunTests() {
|
|||
ASSERT_EQ(frame_info->prolog_size, 1);
|
||||
|
||||
frame.instruction = 0x216f;
|
||||
resolver.FillSourceLineInfo(&frame);
|
||||
StackFrameInfo *s;
|
||||
s = resolver.FillSourceLineInfo(&frame);
|
||||
ASSERT_EQ(frame.function_name, "Public2_1");
|
||||
delete s;
|
||||
|
||||
ClearSourceLineInfo(&frame);
|
||||
frame.instruction = 0x219f;
|
||||
|
@ -184,9 +186,11 @@ static bool RunTests() {
|
|||
|
||||
frame.instruction = 0x21a0;
|
||||
frame.module = &module2;
|
||||
resolver.FillSourceLineInfo(&frame);
|
||||
s = resolver.FillSourceLineInfo(&frame);
|
||||
ASSERT_EQ(frame.function_name, "Public2_2");
|
||||
|
||||
delete s;
|
||||
|
||||
ASSERT_FALSE(resolver.LoadModule("module3",
|
||||
testdata_dir + "/module3_bad.out"));
|
||||
ASSERT_FALSE(resolver.HasModule("module3"));
|
||||
|
|
|
@ -60,8 +60,12 @@ bool ContainedRangeMap<AddressType, EntryType>::StoreRange(
|
|||
|
||||
// Check for undersize or overflow.
|
||||
if (size <= 0 || high < base) {
|
||||
BPLOG(INFO) << "StoreRange failed, " << HexString(base) << "+" <<
|
||||
HexString(size) << ", " << HexString(high);
|
||||
//TODO(nealsid) We are commenting this out in order to prevent
|
||||
// excessive logging. We plan to move to better logging as this
|
||||
// failure happens quite often and is expected(see comment in
|
||||
// basic_source_line_resolver.cc:671).
|
||||
// BPLOG(INFO) << "StoreRange failed, " << HexString(base) << "+"
|
||||
// << HexString(size) << ", " << HexString(high);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -81,8 +85,9 @@ bool ContainedRangeMap<AddressType, EntryType>::StoreRange(
|
|||
// it must fail. iterator_base->first contains the key, which was the
|
||||
// containing child's high address.
|
||||
if (iterator_base->second->base_ == base && iterator_base->first == high) {
|
||||
BPLOG(INFO) << "StoreRange failed, identical range is already "
|
||||
"present: " << HexString(base) << "+" << HexString(size);
|
||||
// TODO(nealsid): See the TODO above on why this is commented out.
|
||||
// BPLOG(INFO) << "StoreRange failed, identical range is already "
|
||||
// "present: " << HexString(base) << "+" << HexString(size);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,48 +47,45 @@ MinidumpProcessor::MinidumpProcessor(SymbolSupplier *supplier,
|
|||
MinidumpProcessor::~MinidumpProcessor() {
|
||||
}
|
||||
|
||||
MinidumpProcessor::ProcessResult MinidumpProcessor::Process(
|
||||
const string &minidump_file, ProcessState *process_state) {
|
||||
BPLOG(INFO) << "Processing minidump in file " << minidump_file;
|
||||
|
||||
Minidump dump(minidump_file);
|
||||
if (!dump.Read()) {
|
||||
BPLOG(ERROR) << "Minidump " << minidump_file << " could not be read";
|
||||
return PROCESS_ERROR;
|
||||
}
|
||||
ProcessResult MinidumpProcessor::Process(
|
||||
Minidump *dump, ProcessState *process_state) {
|
||||
assert(dump);
|
||||
assert(process_state);
|
||||
|
||||
process_state->Clear();
|
||||
|
||||
const MDRawHeader *header = dump.header();
|
||||
BPLOG_IF(ERROR, !header) << "Minidump " << minidump_file << " has no header";
|
||||
assert(header);
|
||||
const MDRawHeader *header = dump->header();
|
||||
if (!header) {
|
||||
BPLOG(ERROR) << "Minidump " << dump->path() << " has no header";
|
||||
return PROCESS_ERROR_NO_MINIDUMP_HEADER;
|
||||
}
|
||||
process_state->time_date_stamp_ = header->time_date_stamp;
|
||||
|
||||
bool has_cpu_info = GetCPUInfo(&dump, &process_state->system_info_);
|
||||
bool has_os_info = GetOSInfo(&dump, &process_state->system_info_);
|
||||
bool has_cpu_info = GetCPUInfo(dump, &process_state->system_info_);
|
||||
bool has_os_info = GetOSInfo(dump, &process_state->system_info_);
|
||||
|
||||
u_int32_t dump_thread_id = 0;
|
||||
bool has_dump_thread = false;
|
||||
u_int32_t requesting_thread_id = 0;
|
||||
bool has_requesting_thread = false;
|
||||
|
||||
MinidumpBreakpadInfo *breakpad_info = dump.GetBreakpadInfo();
|
||||
MinidumpBreakpadInfo *breakpad_info = dump->GetBreakpadInfo();
|
||||
if (breakpad_info) {
|
||||
has_dump_thread = breakpad_info->GetDumpThreadID(&dump_thread_id);
|
||||
has_requesting_thread =
|
||||
breakpad_info->GetRequestingThreadID(&requesting_thread_id);
|
||||
}
|
||||
|
||||
MinidumpException *exception = dump.GetException();
|
||||
MinidumpException *exception = dump->GetException();
|
||||
if (exception) {
|
||||
process_state->crashed_ = true;
|
||||
has_requesting_thread = exception->GetThreadID(&requesting_thread_id);
|
||||
|
||||
process_state->crash_reason_ = GetCrashReason(
|
||||
&dump, &process_state->crash_address_);
|
||||
dump, &process_state->crash_address_);
|
||||
}
|
||||
|
||||
MinidumpModuleList *module_list = dump.GetModuleList();
|
||||
MinidumpModuleList *module_list = dump->GetModuleList();
|
||||
|
||||
// Put a copy of the module list into ProcessState object. This is not
|
||||
// necessarily a MinidumpModuleList, but it adheres to the CodeModules
|
||||
|
@ -96,21 +93,21 @@ MinidumpProcessor::ProcessResult MinidumpProcessor::Process(
|
|||
if (module_list)
|
||||
process_state->modules_ = module_list->Copy();
|
||||
|
||||
MinidumpThreadList *threads = dump.GetThreadList();
|
||||
MinidumpThreadList *threads = dump->GetThreadList();
|
||||
if (!threads) {
|
||||
BPLOG(ERROR) << "Minidump " << minidump_file << " has no thread list";
|
||||
return PROCESS_ERROR;
|
||||
BPLOG(ERROR) << "Minidump " << dump->path() << " has no thread list";
|
||||
return PROCESS_ERROR_NO_THREAD_LIST;
|
||||
}
|
||||
|
||||
BPLOG(INFO) << "Minidump " << minidump_file << " has " <<
|
||||
(has_cpu_info ? "" : "no ") << "CPU info, " <<
|
||||
(has_os_info ? "" : "no ") << "OS info, " <<
|
||||
(breakpad_info != NULL ? "" : "no ") << "Breakpad info, " <<
|
||||
(exception != NULL ? "" : "no ") << "exception, " <<
|
||||
(module_list != NULL ? "" : "no ") << "module list, " <<
|
||||
(threads != NULL ? "" : "no ") << "thread list, " <<
|
||||
(has_dump_thread ? "" : "no ") << "dump thread, and " <<
|
||||
(has_requesting_thread ? "" : "no ") << "requesting thread";
|
||||
BPLOG(INFO) << "Minidump " << dump->path() << " has " <<
|
||||
(has_cpu_info ? "" : "no ") << "CPU info, " <<
|
||||
(has_os_info ? "" : "no ") << "OS info, " <<
|
||||
(breakpad_info != NULL ? "" : "no ") << "Breakpad info, " <<
|
||||
(exception != NULL ? "" : "no ") << "exception, " <<
|
||||
(module_list != NULL ? "" : "no ") << "module list, " <<
|
||||
(threads != NULL ? "" : "no ") << "thread list, " <<
|
||||
(has_dump_thread ? "" : "no ") << "dump thread, and " <<
|
||||
(has_requesting_thread ? "" : "no ") << "requesting thread";
|
||||
|
||||
bool interrupted = false;
|
||||
bool found_requesting_thread = false;
|
||||
|
@ -121,18 +118,18 @@ MinidumpProcessor::ProcessResult MinidumpProcessor::Process(
|
|||
char thread_string_buffer[64];
|
||||
snprintf(thread_string_buffer, sizeof(thread_string_buffer), "%d/%d",
|
||||
thread_index, thread_count);
|
||||
string thread_string = minidump_file + ":" + thread_string_buffer;
|
||||
string thread_string = dump->path() + ":" + thread_string_buffer;
|
||||
|
||||
MinidumpThread *thread = threads->GetThreadAtIndex(thread_index);
|
||||
if (!thread) {
|
||||
BPLOG(ERROR) << "Could not get thread for " << thread_string;
|
||||
return PROCESS_ERROR;
|
||||
return PROCESS_ERROR_GETTING_THREAD;
|
||||
}
|
||||
|
||||
u_int32_t thread_id;
|
||||
if (!thread->GetThreadID(&thread_id)) {
|
||||
BPLOG(ERROR) << "Could not get thread ID for " << thread_string;
|
||||
return PROCESS_ERROR;
|
||||
return PROCESS_ERROR_GETTING_THREAD_ID;
|
||||
}
|
||||
|
||||
thread_string += " id " + HexString(thread_id);
|
||||
|
@ -152,7 +149,7 @@ MinidumpProcessor::ProcessResult MinidumpProcessor::Process(
|
|||
if (found_requesting_thread) {
|
||||
// There can't be more than one requesting thread.
|
||||
BPLOG(ERROR) << "Duplicate requesting thread: " << thread_string;
|
||||
return PROCESS_ERROR;
|
||||
return PROCESS_ERROR_DUPLICATE_REQUESTING_THREADS;
|
||||
}
|
||||
|
||||
// Use processed_state->threads_.size() instead of thread_index.
|
||||
|
@ -179,7 +176,7 @@ MinidumpProcessor::ProcessResult MinidumpProcessor::Process(
|
|||
MinidumpMemoryRegion *thread_memory = thread->GetMemory();
|
||||
if (!thread_memory) {
|
||||
BPLOG(ERROR) << "No memory region for " << thread_string;
|
||||
return PROCESS_ERROR;
|
||||
return PROCESS_ERROR_NO_MEMORY_FOR_THREAD;
|
||||
}
|
||||
|
||||
// Use process_state->modules_ instead of module_list, because the
|
||||
|
@ -199,36 +196,49 @@ MinidumpProcessor::ProcessResult MinidumpProcessor::Process(
|
|||
resolver_));
|
||||
if (!stackwalker.get()) {
|
||||
BPLOG(ERROR) << "No stackwalker for " << thread_string;
|
||||
return PROCESS_ERROR;
|
||||
return PROCESS_ERROR_NO_STACKWALKER_FOR_THREAD;
|
||||
}
|
||||
|
||||
scoped_ptr<CallStack> stack(new CallStack());
|
||||
if (!stackwalker->Walk(stack.get())) {
|
||||
BPLOG(INFO) << "Stackwalker interrupt (missing symbols?) at " <<
|
||||
thread_string;
|
||||
thread_string;
|
||||
interrupted = true;
|
||||
}
|
||||
process_state->threads_.push_back(stack.release());
|
||||
}
|
||||
|
||||
if (interrupted) {
|
||||
BPLOG(INFO) << "Processing interrupted for " << minidump_file;
|
||||
return PROCESS_INTERRUPTED;
|
||||
BPLOG(INFO) << "Processing interrupted for " << dump->path();
|
||||
return PROCESS_SYMBOL_SUPPLIER_INTERRUPTED;
|
||||
}
|
||||
|
||||
// If a requesting thread was indicated, it must be present.
|
||||
if (has_requesting_thread && !found_requesting_thread) {
|
||||
// Don't mark as an error, but invalidate the requesting thread
|
||||
BPLOG(ERROR) << "Minidump indicated requesting thread " <<
|
||||
HexString(requesting_thread_id) << ", not found in " <<
|
||||
minidump_file;
|
||||
HexString(requesting_thread_id) << ", not found in " <<
|
||||
dump->path();
|
||||
process_state->requesting_thread_ = -1;
|
||||
}
|
||||
|
||||
BPLOG(INFO) << "Processed " << minidump_file;
|
||||
BPLOG(INFO) << "Processed " << dump->path();
|
||||
return PROCESS_OK;
|
||||
}
|
||||
|
||||
ProcessResult MinidumpProcessor::Process(
|
||||
const string &minidump_file, ProcessState *process_state) {
|
||||
BPLOG(INFO) << "Processing minidump in file " << minidump_file;
|
||||
|
||||
Minidump dump(minidump_file);
|
||||
if (!dump.Read()) {
|
||||
BPLOG(ERROR) << "Minidump " << dump.path() << " could not be read";
|
||||
return PROCESS_ERROR_MINIDUMP_NOT_FOUND;
|
||||
}
|
||||
|
||||
return Process(&dump, process_state);
|
||||
}
|
||||
|
||||
// Returns the MDRawSystemInfo from a minidump, or NULL if system info is
|
||||
// not available from the minidump. If system_info is non-NULL, it is used
|
||||
// to pass back the MinidumpSystemInfo object.
|
||||
|
@ -260,7 +270,7 @@ bool MinidumpProcessor::GetCPUInfo(Minidump *dump, SystemInfo *info) {
|
|||
switch (raw_system_info->processor_architecture) {
|
||||
case MD_CPU_ARCHITECTURE_X86:
|
||||
case MD_CPU_ARCHITECTURE_AMD64: {
|
||||
if (raw_system_info->processor_architecture ==
|
||||
if (raw_system_info->processor_architecture ==
|
||||
MD_CPU_ARCHITECTURE_X86)
|
||||
info->cpu = "x86";
|
||||
else
|
||||
|
@ -861,118 +871,118 @@ string MinidumpProcessor::GetCrashReason(Minidump *dump, u_int64_t *address) {
|
|||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGQUIT:
|
||||
reason = "SIGQUIT";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGILL:
|
||||
reason = "SIGILL";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGTRAP:
|
||||
reason = "SIGTRAP";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGIOT:
|
||||
reason = "SIGIOT | SIGABRT";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGEMT:
|
||||
reason = "SIGEMT";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGFPE:
|
||||
reason = "SIGFPE";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGKILL:
|
||||
reason = "SIGKILL";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGBUS:
|
||||
reason = "SIGBUS";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGSEGV:
|
||||
reason = "SIGSEGV";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGSYS:
|
||||
reason = "SIGSYS";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGPIPE:
|
||||
reason = "SIGPIPE";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGALRM:
|
||||
reason = "SIGALRM";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGTERM:
|
||||
reason = "SIGTERM";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGUSR1:
|
||||
reason = "SIGUSR1";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGUSR2:
|
||||
reason = "SIGUSR2";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGCLD:
|
||||
reason = "SIGCLD | SIGCHLD";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGPWR:
|
||||
reason = "SIGPWR";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGWINCH:
|
||||
reason = "SIGWINCH";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGURG:
|
||||
reason = "SIGURG";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGPOLL:
|
||||
reason = "SIGPOLL | SIGIO";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGSTOP:
|
||||
reason = "SIGSTOP";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGTSTP:
|
||||
reason = "SIGTSTP";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGCONT:
|
||||
reason = "SIGCONT";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGTTIN:
|
||||
reason = "SIGTTIN";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGTTOU:
|
||||
reason = "SIGTTOU";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGVTALRM:
|
||||
reason = "SIGVTALRM";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGPROF:
|
||||
reason = "SIGPROF";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGXCPU:
|
||||
reason = "SIGXCPU";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGXFSZ:
|
||||
reason = "SIGXFSZ";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGWAITING:
|
||||
reason = "SIGWAITING";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGLWP:
|
||||
reason = "SIGLWP";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGFREEZE:
|
||||
reason = "SIGFREEZE";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGTHAW:
|
||||
reason = "SIGTHAW";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGCANCEL:
|
||||
reason = "SIGCANCEL";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGLOST:
|
||||
reason = "SIGLOST";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGXRES:
|
||||
reason = "SIGXRES";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGJVM1:
|
||||
reason = "SIGJVM1";
|
||||
break;
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_SOL_SIGJVM2:
|
||||
reason = "SIGJVM2";
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
BPLOG(INFO) << "Unknown exception reason " << reason;
|
||||
break;
|
||||
|
|
|
@ -32,10 +32,14 @@
|
|||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "google_breakpad/processor/basic_source_line_resolver.h"
|
||||
#include "google_breakpad/processor/call_stack.h"
|
||||
#include "google_breakpad/processor/code_module.h"
|
||||
#include "google_breakpad/processor/code_modules.h"
|
||||
#include "google_breakpad/processor/minidump.h"
|
||||
#include "google_breakpad/processor/minidump_processor.h"
|
||||
#include "google_breakpad/processor/process_state.h"
|
||||
#include "google_breakpad/processor/stack_frame.h"
|
||||
|
@ -43,17 +47,34 @@
|
|||
#include "processor/logging.h"
|
||||
#include "processor/scoped_ptr.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
class MockMinidump : public Minidump {
|
||||
public:
|
||||
MockMinidump() : Minidump("") {
|
||||
}
|
||||
|
||||
MOCK_METHOD0(Read,bool());
|
||||
MOCK_CONST_METHOD0(path, string());
|
||||
MOCK_CONST_METHOD0(header,const MDRawHeader*());
|
||||
MOCK_METHOD0(GetThreadList,MinidumpThreadList*());
|
||||
};
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using std::string;
|
||||
using google_breakpad::BasicSourceLineResolver;
|
||||
using google_breakpad::CallStack;
|
||||
using google_breakpad::CodeModule;
|
||||
using google_breakpad::MinidumpProcessor;
|
||||
using google_breakpad::MinidumpThreadList;
|
||||
using google_breakpad::MinidumpThread;
|
||||
using google_breakpad::MockMinidump;
|
||||
using google_breakpad::ProcessState;
|
||||
using google_breakpad::scoped_ptr;
|
||||
using google_breakpad::SymbolSupplier;
|
||||
using google_breakpad::SystemInfo;
|
||||
using std::string;
|
||||
using ::testing::Return;
|
||||
|
||||
static const char *kSystemInfoOS = "Windows NT";
|
||||
static const char *kSystemInfoOSShort = "windows";
|
||||
|
@ -62,17 +83,6 @@ static const char *kSystemInfoCPU = "x86";
|
|||
static const char *kSystemInfoCPUInfo =
|
||||
"GenuineIntel family 6 model 13 stepping 8";
|
||||
|
||||
#define ASSERT_TRUE(cond) \
|
||||
if (!(cond)) { \
|
||||
fprintf(stderr, "FAILED: %s at %s:%d\n", #cond, __FILE__, __LINE__); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define ASSERT_FALSE(cond) ASSERT_TRUE(!(cond))
|
||||
|
||||
#define ASSERT_EQ(e1, e2) ASSERT_TRUE((e1) == (e2))
|
||||
|
||||
// Use ASSERT_*_ABORT in functions that can't return a boolean.
|
||||
#define ASSERT_TRUE_ABORT(cond) \
|
||||
if (!(cond)) { \
|
||||
fprintf(stderr, "FAILED: %s at %s:%d\n", #cond, __FILE__, __LINE__); \
|
||||
|
@ -89,6 +99,11 @@ class TestSymbolSupplier : public SymbolSupplier {
|
|||
const SystemInfo *system_info,
|
||||
string *symbol_file);
|
||||
|
||||
virtual SymbolResult GetSymbolFile(const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
string *symbol_file,
|
||||
string *symbol_data);
|
||||
|
||||
// When set to true, causes the SymbolSupplier to return INTERRUPT
|
||||
void set_interrupt(bool interrupt) { interrupt_ = interrupt; }
|
||||
|
||||
|
@ -123,7 +138,55 @@ SymbolSupplier::SymbolResult TestSymbolSupplier::GetSymbolFile(
|
|||
return NOT_FOUND;
|
||||
}
|
||||
|
||||
static bool RunTests() {
|
||||
SymbolSupplier::SymbolResult TestSymbolSupplier::GetSymbolFile(
|
||||
const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
string *symbol_file,
|
||||
string *symbol_data) {
|
||||
SymbolSupplier::SymbolResult s = GetSymbolFile(module, system_info,
|
||||
symbol_file);
|
||||
if (s == FOUND) {
|
||||
std::ifstream in(symbol_file->c_str());
|
||||
std::getline(in, *symbol_data, std::string::traits_type::to_char_type(
|
||||
std::string::traits_type::eof()));
|
||||
in.close();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
class MinidumpProcessorTest : public ::testing::Test {
|
||||
|
||||
};
|
||||
|
||||
TEST_F(MinidumpProcessorTest, TestCorruptMinidumps) {
|
||||
MockMinidump dump;
|
||||
TestSymbolSupplier supplier;
|
||||
BasicSourceLineResolver resolver;
|
||||
MinidumpProcessor processor(&supplier, &resolver);
|
||||
ProcessState state;
|
||||
|
||||
EXPECT_EQ(processor.Process("nonexistant minidump", &state),
|
||||
google_breakpad::PROCESS_ERROR_MINIDUMP_NOT_FOUND);
|
||||
|
||||
EXPECT_CALL(dump, path()).WillRepeatedly(Return("mock minidump"));
|
||||
EXPECT_CALL(dump, Read()).WillRepeatedly(Return(true));
|
||||
|
||||
MDRawHeader fakeHeader;
|
||||
fakeHeader.time_date_stamp = 0;
|
||||
EXPECT_CALL(dump, header()).WillOnce(Return((MDRawHeader*)NULL)).
|
||||
WillRepeatedly(Return(&fakeHeader));
|
||||
EXPECT_EQ(processor.Process(&dump, &state),
|
||||
google_breakpad::PROCESS_ERROR_NO_MINIDUMP_HEADER);
|
||||
|
||||
EXPECT_CALL(dump, GetThreadList()).
|
||||
WillOnce(Return((MinidumpThreadList*)NULL));
|
||||
EXPECT_EQ(processor.Process(&dump, &state),
|
||||
google_breakpad::PROCESS_ERROR_NO_THREAD_LIST);
|
||||
}
|
||||
|
||||
TEST_F(MinidumpProcessorTest, TestBasicProcessing) {
|
||||
TestSymbolSupplier supplier;
|
||||
BasicSourceLineResolver resolver;
|
||||
MinidumpProcessor processor(&supplier, &resolver);
|
||||
|
@ -133,7 +196,7 @@ static bool RunTests() {
|
|||
|
||||
ProcessState state;
|
||||
ASSERT_EQ(processor.Process(minidump_file, &state),
|
||||
MinidumpProcessor::PROCESS_OK);
|
||||
google_breakpad::PROCESS_OK);
|
||||
ASSERT_EQ(state.system_info()->os, kSystemInfoOS);
|
||||
ASSERT_EQ(state.system_info()->os_short, kSystemInfoOSShort);
|
||||
ASSERT_EQ(state.system_info()->os_version, kSystemInfoOSVersion);
|
||||
|
@ -197,19 +260,12 @@ static bool RunTests() {
|
|||
state.Clear();
|
||||
supplier.set_interrupt(true);
|
||||
ASSERT_EQ(processor.Process(minidump_file, &state),
|
||||
MinidumpProcessor::PROCESS_INTERRUPTED);
|
||||
|
||||
return true;
|
||||
google_breakpad::PROCESS_SYMBOL_SUPPLIER_INTERRUPTED
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
BPLOG_INIT(&argc, &argv);
|
||||
|
||||
if (!RunTests()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -446,7 +447,7 @@ static bool PrintMinidumpProcess(const string &minidump_file,
|
|||
// Process the minidump.
|
||||
ProcessState process_state;
|
||||
if (minidump_processor.Process(minidump_file, &process_state) !=
|
||||
MinidumpProcessor::PROCESS_OK) {
|
||||
google_breakpad::PROCESS_OK) {
|
||||
BPLOG(ERROR) << "MinidumpProcessor::Process failed";
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "processor/simple_symbol_supplier.h"
|
||||
#include "google_breakpad/processor/code_module.h"
|
||||
|
@ -62,15 +64,35 @@ SymbolSupplier::SymbolResult SimpleSymbolSupplier::GetSymbolFile(
|
|||
|
||||
for (unsigned int path_index = 0; path_index < paths_.size(); ++path_index) {
|
||||
SymbolResult result;
|
||||
if ((result = GetSymbolFileAtPath(module, system_info, paths_[path_index],
|
||||
symbol_file)) != NOT_FOUND) {
|
||||
if ((result = GetSymbolFileAtPathFromRoot(module, system_info,
|
||||
paths_[path_index],
|
||||
symbol_file)) != NOT_FOUND) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return NOT_FOUND;
|
||||
}
|
||||
|
||||
SymbolSupplier::SymbolResult SimpleSymbolSupplier::GetSymbolFileAtPath(
|
||||
SymbolSupplier::SymbolResult SimpleSymbolSupplier::GetSymbolFile(
|
||||
const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
string *symbol_file,
|
||||
string *symbol_data) {
|
||||
assert(symbol_data);
|
||||
symbol_data->clear();
|
||||
|
||||
SymbolSupplier::SymbolResult s = GetSymbolFile(module, system_info, symbol_file);
|
||||
|
||||
if (s == FOUND) {
|
||||
std::ifstream in(symbol_file->c_str());
|
||||
std::getline(in, *symbol_data, std::string::traits_type::to_char_type(
|
||||
std::string::traits_type::eof()));
|
||||
in.close();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
SymbolSupplier::SymbolResult SimpleSymbolSupplier::GetSymbolFileAtPathFromRoot(
|
||||
const CodeModule *module, const SystemInfo *system_info,
|
||||
const string &root_path, string *symbol_file) {
|
||||
BPLOG_IF(ERROR, !symbol_file) << "SimpleSymbolSupplier::GetSymbolFileAtPath "
|
||||
|
|
|
@ -102,15 +102,19 @@ class SimpleSymbolSupplier : public SymbolSupplier {
|
|||
|
||||
// Returns the path to the symbol file for the given module. See the
|
||||
// description above.
|
||||
SymbolResult GetSymbolFile(const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
string *symbol_file);
|
||||
virtual SymbolResult GetSymbolFile(const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
string *symbol_file);
|
||||
|
||||
virtual SymbolResult GetSymbolFile(const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
string *symbol_file,
|
||||
string *symbol_data);
|
||||
protected:
|
||||
SymbolResult GetSymbolFileAtPath(const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
const string &root_path,
|
||||
string *symbol_file);
|
||||
SymbolResult GetSymbolFileAtPathFromRoot(const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
const string &root_path,
|
||||
string *symbol_file);
|
||||
|
||||
private:
|
||||
vector<string> paths_;
|
||||
|
|
|
@ -101,13 +101,15 @@ bool Stackwalker::Walk(CallStack *stack) {
|
|||
if (resolver_ &&
|
||||
!resolver_->HasModule(frame->module->code_file()) &&
|
||||
supplier_) {
|
||||
string symbol_file;
|
||||
string symbol_data, symbol_file;
|
||||
SymbolSupplier::SymbolResult symbol_result =
|
||||
supplier_->GetSymbolFile(module, system_info_, &symbol_file);
|
||||
supplier_->GetSymbolFile(module, system_info_,
|
||||
&symbol_file, &symbol_data);
|
||||
|
||||
switch (symbol_result) {
|
||||
case SymbolSupplier::FOUND:
|
||||
resolver_->LoadModule(frame->module->code_file(), symbol_file);
|
||||
resolver_->LoadModuleUsingMapBuffer(frame->module->code_file(),
|
||||
symbol_data);
|
||||
break;
|
||||
case SymbolSupplier::NOT_FOUND:
|
||||
break; // nothing to do
|
||||
|
|
|
@ -43,7 +43,7 @@ int main(int argc, char **argv) {
|
|||
const char *binary = argv[1];
|
||||
|
||||
DumpSymbols dumper;
|
||||
if (!dumper.WriteSymbolFile(binary, fileno(stdout))) {
|
||||
if (!dumper.WriteSymbolFile(binary, stdout)) {
|
||||
fprintf(stderr, "Failed to write symbol file.\n");
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -190,6 +190,36 @@ static void PrintRegisters(const CallStack *stack, const string &cpu) {
|
|||
printf("\n");
|
||||
}
|
||||
|
||||
static void PrintModules(const CodeModules *modules) {
|
||||
if (!modules)
|
||||
return;
|
||||
|
||||
printf("\n");
|
||||
printf("Loaded modules:\n");
|
||||
|
||||
u_int64_t main_address = 0;
|
||||
const CodeModule *main_module = modules->GetMainModule();
|
||||
if (main_module) {
|
||||
main_address = main_module->base_address();
|
||||
}
|
||||
|
||||
unsigned int module_count = modules->module_count();
|
||||
for (unsigned int module_sequence = 0;
|
||||
module_sequence < module_count;
|
||||
++module_sequence) {
|
||||
const CodeModule *module = modules->GetModuleAtSequence(module_sequence);
|
||||
assert(module);
|
||||
u_int64_t base_address = module->base_address();
|
||||
printf("0x%08llx - 0x%08llx %s %s%s %s\n",
|
||||
base_address, base_address + module->size() - 1,
|
||||
PathnameStripper::File(module->code_file()).c_str(),
|
||||
module->version().empty() ? "???" : module->version().c_str(),
|
||||
main_module != NULL && base_address == main_address ?
|
||||
" (main)" : "",
|
||||
module->code_file().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
static void Start(Options *options) {
|
||||
string minidump_file([options->minidumpPath fileSystemRepresentation]);
|
||||
|
@ -258,6 +288,9 @@ static void Start(Options *options) {
|
|||
printf("\nThread %d:", requesting_thread);
|
||||
PrintRegisters(process_state.threads()->at(requesting_thread), cpu);
|
||||
}
|
||||
|
||||
// Print information about modules
|
||||
PrintModules(process_state.modules());
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
|
|
|
@ -47,7 +47,7 @@ class OnDemandSymbolSupplier : public SymbolSupplier {
|
|||
public:
|
||||
// |search_dir| is the directory to search for alternative symbols with
|
||||
// the same name as the module in the minidump
|
||||
OnDemandSymbolSupplier(const string &search_dir,
|
||||
OnDemandSymbolSupplier(const string &search_dir,
|
||||
const string &symbol_search_dir);
|
||||
virtual ~OnDemandSymbolSupplier() {}
|
||||
|
||||
|
@ -56,11 +56,16 @@ class OnDemandSymbolSupplier : public SymbolSupplier {
|
|||
const SystemInfo *system_info,
|
||||
string *symbol_file);
|
||||
|
||||
// Returns the path to the symbol file for the given module.
|
||||
virtual SymbolResult GetSymbolFile(const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
string *symbol_file,
|
||||
string *symbol_data);
|
||||
protected:
|
||||
// Search directory
|
||||
string search_dir_;
|
||||
string symbol_search_dir_;
|
||||
|
||||
|
||||
// When we create a symbol file for a module, save the name of the module
|
||||
// and the path to that module's symbol file.
|
||||
map<string, string> module_file_map_;
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
#include <sys/stat.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "google_breakpad/processor/basic_source_line_resolver.h"
|
||||
#include "google_breakpad/processor/minidump.h"
|
||||
|
@ -136,6 +138,26 @@ OnDemandSymbolSupplier::GetSymbolFile(const CodeModule *module,
|
|||
return FOUND;
|
||||
}
|
||||
|
||||
SymbolSupplier::SymbolResult
|
||||
OnDemandSymbolSupplier::GetSymbolFile(const CodeModule *module,
|
||||
const SystemInfo *system_info,
|
||||
string *symbol_file,
|
||||
string *symbol_data) {
|
||||
SymbolSupplier::SymbolResult s = GetSymbolFile(module,
|
||||
system_info,
|
||||
symbol_file);
|
||||
|
||||
|
||||
if (s == FOUND) {
|
||||
ifstream in(symbol_file->c_str());
|
||||
getline(in, *symbol_data, std::string::traits_type::to_char_type(
|
||||
std::string::traits_type::eof()));
|
||||
in.close();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
string OnDemandSymbolSupplier::GetLocalModulePath(const CodeModule *module) {
|
||||
NSFileManager *mgr = [NSFileManager defaultManager];
|
||||
const char *moduleStr = module->code_file().c_str();
|
||||
|
|
|
@ -131,12 +131,13 @@ static bool DumpSymbolsToTempFile(const wchar_t *file,
|
|||
|
||||
FILE *temp_file = NULL;
|
||||
#if _MSC_VER >= 1400 // MSVC 2005/8
|
||||
if (_wfopen_s(&temp_file, temp_filename, L"w") != 0) {
|
||||
if (_wfopen_s(&temp_file, temp_filename, L"w") != 0)
|
||||
#else // _MSC_VER >= 1400
|
||||
// _wfopen_s was introduced in MSVC8. Use _wfopen for earlier environments.
|
||||
// Don't use it with MSVC8 and later, because it's deprecated.
|
||||
if (!(temp_file = _wfopen(temp_filename, L"w"))) {
|
||||
if (!(temp_file = _wfopen(temp_filename, L"w")))
|
||||
#endif // _MSC_VER >= 1400
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -152,12 +153,33 @@ static bool DumpSymbolsToTempFile(const wchar_t *file,
|
|||
return writer.GetModuleInfo(pdb_info);
|
||||
}
|
||||
|
||||
void printUsageAndExit() {
|
||||
wprintf(L"Usage: symupload [--timeout NN] <file.exe|file.dll> <symbol upload URL>\n\n");
|
||||
wprintf(L"Timeout is in milliseconds, or can be 0 to be unlimited\n\n");
|
||||
wprintf(L"Example:\n\n\tsymupload.exe --timeout 0 chrome.dll http://no.free.symbol.server.for.you\n");
|
||||
exit(0);
|
||||
}
|
||||
int wmain(int argc, wchar_t *argv[]) {
|
||||
if (argc < 3) {
|
||||
wprintf(L"Usage: %s <file.exe|file.dll> <symbol upload URL>\n", argv[0]);
|
||||
return 0;
|
||||
if ((argc != 3) &&
|
||||
(argc != 5)) {
|
||||
printUsageAndExit();
|
||||
}
|
||||
|
||||
const wchar_t *module, *url;
|
||||
int timeout = -1;
|
||||
if (argc == 3) {
|
||||
module = argv[1];
|
||||
url = argv[2];
|
||||
} else {
|
||||
// check for timeout flag
|
||||
if (!wcscmp(L"--timeout", argv[1])) {
|
||||
timeout = _wtoi(argv[2]);
|
||||
module = argv[3];
|
||||
url = argv[4];
|
||||
} else {
|
||||
printUsageAndExit();
|
||||
}
|
||||
}
|
||||
const wchar_t *module = argv[1], *url = argv[2];
|
||||
|
||||
wstring symbol_file;
|
||||
PDBModuleInfo pdb_info;
|
||||
|
@ -186,6 +208,7 @@ int wmain(int argc, wchar_t *argv[]) {
|
|||
|
||||
bool success = HTTPUpload::SendRequest(url, parameters,
|
||||
symbol_file, L"symbol_file",
|
||||
timeout == -1 ? NULL : &timeout,
|
||||
NULL, NULL);
|
||||
_wunlink(symbol_file.c_str());
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче