diff --git a/Makefile.am b/Makefile.am index 1215bd94..4b07bd00 100644 --- a/Makefile.am +++ b/Makefile.am @@ -89,9 +89,16 @@ check_PROGRAMS = \ src/processor/postfix_evaluator_unittest \ src/processor/range_map_unittest \ src/processor/source_line_resolver_unittest + +if SELFTEST +check_PROGRAMS += \ + src/processor/stackwalker_selftest +endif SELFTEST + check_SCRIPTS = \ src/processor/minidump_dump_test \ src/processor/minidump_stackwalk_test + TESTS = $(check_PROGRAMS) $(check_SCRIPTS) TESTS_ENVIRONMENT = @@ -119,6 +126,15 @@ src_processor_source_line_resolver_unittest_SOURCES = \ src_processor_source_line_resolver_unittest_LDADD = \ src/processor/source_line_resolver.lo +src_processor_stackwalker_selftest_SOURCES = \ + src/processor/stackwalker_selftest.cc +src_processor_stackwalker_selftest_LDADD = \ + src/processor/minidump.lo \ + src/processor/source_line_resolver.lo \ + src/processor/stackwalker.lo \ + src/processor/stackwalker_ppc.lo \ + src/processor/stackwalker_x86.lo + ## Non-installables noinst_PROGRAMS = noinst_SCRIPTS = $(check_SCRIPTS) diff --git a/Makefile.in b/Makefile.in index 0c260e11..8b07303d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -74,7 +74,11 @@ check_PROGRAMS = src/processor/contained_range_map_unittest$(EXEEXT) \ src/processor/minidump_processor_unittest$(EXEEXT) \ src/processor/postfix_evaluator_unittest$(EXEEXT) \ src/processor/range_map_unittest$(EXEEXT) \ - src/processor/source_line_resolver_unittest$(EXEEXT) + src/processor/source_line_resolver_unittest$(EXEEXT) \ + $(am__EXEEXT_1) +@SELFTEST_TRUE@am__append_1 = \ +@SELFTEST_TRUE@ src/processor/stackwalker_selftest + noinst_PROGRAMS = DIST_COMMON = README $(am__configure_deps) $(dist_doc_DATA) \ $(srcdir)/Makefile.am $(srcdir)/Makefile.in \ @@ -111,6 +115,8 @@ am_src_libairbag_la_OBJECTS = src/processor/minidump.lo \ src/processor/stackwalker_x86.lo src_libairbag_la_OBJECTS = $(am_src_libairbag_la_OBJECTS) binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) +@SELFTEST_TRUE@am__EXEEXT_1 = \ +@SELFTEST_TRUE@ src/processor/stackwalker_selftest$(EXEEXT) PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS) am_src_processor_contained_range_map_unittest_OBJECTS = \ src/processor/contained_range_map_unittest.$(OBJEXT) @@ -156,6 +162,15 @@ src_processor_source_line_resolver_unittest_OBJECTS = \ $(am_src_processor_source_line_resolver_unittest_OBJECTS) src_processor_source_line_resolver_unittest_DEPENDENCIES = \ src/processor/source_line_resolver.lo +am_src_processor_stackwalker_selftest_OBJECTS = \ + src/processor/stackwalker_selftest.$(OBJEXT) +src_processor_stackwalker_selftest_OBJECTS = \ + $(am_src_processor_stackwalker_selftest_OBJECTS) +src_processor_stackwalker_selftest_DEPENDENCIES = \ + src/processor/minidump.lo \ + src/processor/source_line_resolver.lo \ + src/processor/stackwalker.lo src/processor/stackwalker_ppc.lo \ + src/processor/stackwalker_x86.lo SCRIPTS = $(noinst_SCRIPTS) DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir)/src depcomp = $(SHELL) $(top_srcdir)/autotools/depcomp @@ -183,7 +198,8 @@ SOURCES = $(src_libairbag_la_SOURCES) \ $(src_processor_minidump_stackwalk_SOURCES) \ $(src_processor_postfix_evaluator_unittest_SOURCES) \ $(src_processor_range_map_unittest_SOURCES) \ - $(src_processor_source_line_resolver_unittest_SOURCES) + $(src_processor_source_line_resolver_unittest_SOURCES) \ + $(src_processor_stackwalker_selftest_SOURCES) DIST_SOURCES = $(src_libairbag_la_SOURCES) \ $(src_processor_contained_range_map_unittest_SOURCES) \ $(src_processor_minidump_dump_SOURCES) \ @@ -191,7 +207,8 @@ DIST_SOURCES = $(src_libairbag_la_SOURCES) \ $(src_processor_minidump_stackwalk_SOURCES) \ $(src_processor_postfix_evaluator_unittest_SOURCES) \ $(src_processor_range_map_unittest_SOURCES) \ - $(src_processor_source_line_resolver_unittest_SOURCES) + $(src_processor_source_line_resolver_unittest_SOURCES) \ + $(src_processor_stackwalker_selftest_SOURCES) dist_docDATA_INSTALL = $(INSTALL_DATA) DATA = $(dist_doc_DATA) ETAGS = etags @@ -258,6 +275,8 @@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ RANLIB = @RANLIB@ +SELFTEST_FALSE = @SELFTEST_FALSE@ +SELFTEST_TRUE = @SELFTEST_TRUE@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ @@ -379,6 +398,16 @@ src_processor_source_line_resolver_unittest_SOURCES = \ src_processor_source_line_resolver_unittest_LDADD = \ src/processor/source_line_resolver.lo +src_processor_stackwalker_selftest_SOURCES = \ + src/processor/stackwalker_selftest.cc + +src_processor_stackwalker_selftest_LDADD = \ + src/processor/minidump.lo \ + src/processor/source_line_resolver.lo \ + src/processor/stackwalker.lo \ + src/processor/stackwalker_ppc.lo \ + src/processor/stackwalker_x86.lo + noinst_SCRIPTS = $(check_SCRIPTS) src_processor_minidump_dump_SOURCES = \ src/processor/minidump_dump.cc @@ -594,6 +623,12 @@ src/processor/source_line_resolver_unittest.$(OBJEXT): \ src/processor/source_line_resolver_unittest$(EXEEXT): $(src_processor_source_line_resolver_unittest_OBJECTS) $(src_processor_source_line_resolver_unittest_DEPENDENCIES) src/processor/$(am__dirstamp) @rm -f src/processor/source_line_resolver_unittest$(EXEEXT) $(CXXLINK) $(src_processor_source_line_resolver_unittest_LDFLAGS) $(src_processor_source_line_resolver_unittest_OBJECTS) $(src_processor_source_line_resolver_unittest_LDADD) $(LIBS) +src/processor/stackwalker_selftest.$(OBJEXT): \ + src/processor/$(am__dirstamp) \ + src/processor/$(DEPDIR)/$(am__dirstamp) +src/processor/stackwalker_selftest$(EXEEXT): $(src_processor_stackwalker_selftest_OBJECTS) $(src_processor_stackwalker_selftest_DEPENDENCIES) src/processor/$(am__dirstamp) + @rm -f src/processor/stackwalker_selftest$(EXEEXT) + $(CXXLINK) $(src_processor_stackwalker_selftest_LDFLAGS) $(src_processor_stackwalker_selftest_OBJECTS) $(src_processor_stackwalker_selftest_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) @@ -614,6 +649,7 @@ mostlyclean-compile: -rm -f src/processor/stackwalker.lo -rm -f src/processor/stackwalker_ppc.$(OBJEXT) -rm -f src/processor/stackwalker_ppc.lo + -rm -f src/processor/stackwalker_selftest.$(OBJEXT) -rm -f src/processor/stackwalker_x86.$(OBJEXT) -rm -f src/processor/stackwalker_x86.lo @@ -632,6 +668,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/source_line_resolver_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_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_x86.Plo@am__quote@ .cc.o: diff --git a/configure b/configure index e2baaba5..f51a9c5a 100755 --- a/configure +++ b/configure @@ -855,6 +855,8 @@ FFLAGS ac_ct_F77 LIBTOOL LIBTOOL_DEPS +SELFTEST_TRUE +SELFTEST_FALSE LIBOBJS LTLIBOBJS' ac_subst_files='' @@ -1458,6 +1460,8 @@ Optional Features: --enable-fast-install[=PKGS] optimize for fast installation [default=yes] --disable-libtool-lock avoid locking (might break parallel builds) + --enable-selftest Run extra tests with "make check" (may conflict with + optimizations) (default is no) Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -5150,7 +5154,7 @@ ia64-*-hpux*) ;; *-*-irix6*) # Find out which ABI we are using. - echo '#line 5153 "configure"' > conftest.$ac_ext + echo '#line 5157 "configure"' > conftest.$ac_ext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -7315,11 +7319,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7318: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7322: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:7322: \$? = $ac_status" >&5 + echo "$as_me:7326: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -7583,11 +7587,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7586: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7590: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:7590: \$? = $ac_status" >&5 + echo "$as_me:7594: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -7687,11 +7691,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:7690: $lt_compile\"" >&5) + (eval echo "\"\$as_me:7694: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:7694: \$? = $ac_status" >&5 + echo "$as_me:7698: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -10139,7 +10143,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext < conftest.$ac_ext <&5) + (eval echo "\"\$as_me:12614: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:12614: \$? = $ac_status" >&5 + echo "$as_me:12618: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -12711,11 +12715,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:12714: $lt_compile\"" >&5) + (eval echo "\"\$as_me:12718: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:12718: \$? = $ac_status" >&5 + echo "$as_me:12722: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -14281,11 +14285,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:14284: $lt_compile\"" >&5) + (eval echo "\"\$as_me:14288: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:14288: \$? = $ac_status" >&5 + echo "$as_me:14292: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -14385,11 +14389,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:14388: $lt_compile\"" >&5) + (eval echo "\"\$as_me:14392: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:14392: \$? = $ac_status" >&5 + echo "$as_me:14396: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -16615,11 +16619,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:16618: $lt_compile\"" >&5) + (eval echo "\"\$as_me:16622: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:16622: \$? = $ac_status" >&5 + echo "$as_me:16626: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -16883,11 +16887,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:16886: $lt_compile\"" >&5) + (eval echo "\"\$as_me:16890: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:16890: \$? = $ac_status" >&5 + echo "$as_me:16894: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -16987,11 +16991,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:16990: $lt_compile\"" >&5) + (eval echo "\"\$as_me:16994: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:16994: \$? = $ac_status" >&5 + echo "$as_me:16998: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -19875,6 +19879,36 @@ _ACEOF fi +# Check whether --enable-selftest was given. +if test "${enable_selftest+set}" = set; then + enableval=$enable_selftest; case "${enableval}" in + yes) + selftest=true + ;; + no) + selftest=false + ;; + *) + { { echo "$as_me:$LINENO: error: bad value ${enableval} for --enable-selftest" >&5 +echo "$as_me: error: bad value ${enableval} for --enable-selftest" >&2;} + { (exit 1); exit 1; }; } + ;; + esac +else + selftest=false +fi + + + +if test x$selftest = xtrue; then + SELFTEST_TRUE= + SELFTEST_FALSE='#' +else + SELFTEST_TRUE='#' + SELFTEST_FALSE= +fi + + ac_config_files="$ac_config_files Makefile" cat >confcache <<\_ACEOF @@ -19994,6 +20028,13 @@ echo "$as_me: error: conditional \"am__fastdepCXX\" was never defined. Usually this means the macro was only invoked conditionally." >&2;} { (exit 1); exit 1; }; } fi +if test -z "${SELFTEST_TRUE}" && test -z "${SELFTEST_FALSE}"; then + { { echo "$as_me:$LINENO: error: conditional \"SELFTEST\" was never defined. +Usually this means the macro was only invoked conditionally." >&5 +echo "$as_me: error: conditional \"SELFTEST\" was never defined. +Usually this means the macro was only invoked conditionally." >&2;} + { (exit 1); exit 1; }; } +fi : ${CONFIG_STATUS=./config.status} ac_clean_files_save=$ac_clean_files @@ -20654,11 +20695,13 @@ FFLAGS!$FFLAGS$ac_delim ac_ct_F77!$ac_ct_F77$ac_delim LIBTOOL!$LIBTOOL$ac_delim LIBTOOL_DEPS!$LIBTOOL_DEPS$ac_delim +SELFTEST_TRUE!$SELFTEST_TRUE$ac_delim +SELFTEST_FALSE!$SELFTEST_FALSE$ac_delim LIBOBJS!$LIBOBJS$ac_delim LTLIBOBJS!$LTLIBOBJS$ac_delim _ACEOF - if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 6; then + if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 8; then break elif $ac_last_try; then { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 diff --git a/configure.ac b/configure.ac index b7153a97..f1c2cc86 100644 --- a/configure.ac +++ b/configure.ac @@ -47,5 +47,24 @@ AC_SUBST(LIBTOOL_DEPS) AC_HEADER_STDC +AC_ARG_ENABLE(selftest, + AS_HELP_STRING([--enable-selftest], + [Run extra tests with "make check" ] + [(may conflict with optimizations) ] + [(default is no)]), + [case "${enableval}" in + yes) + selftest=true + ;; + no) + selftest=false + ;; + *) + AC_MSG_ERROR(bad value ${enableval} for --enable-selftest) + ;; + esac], + [selftest=false]) +AM_CONDITIONAL(SELFTEST, test x$selftest = xtrue) + AC_CONFIG_FILES([Makefile]) AC_OUTPUT diff --git a/src/processor/stackwalker_selftest.cc b/src/processor/stackwalker_selftest.cc new file mode 100644 index 00000000..08dea134 --- /dev/null +++ b/src/processor/stackwalker_selftest.cc @@ -0,0 +1,252 @@ +// Copyright (C) 2006 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. + +// stackwalker_selftest.cc: Tests StackwalkerX86 or StackwalkerPPC using the +// running process' stack as test data, if running on an x86 or ppc and +// compiled with gcc. This test is not enabled in the "make check" suite +// by default, because certain optimizations interfere with its proper +// operation. To turn it on, configure with --enable-selftest. +// +// Optimizations that cause problems: +// - stack frame reuse. The Recursor function here calls itself with +// |return Recursor|. When the caller's frame is reused, it will cause +// CountCallerFrames to correctly return the same number of frames +// in both the caller and callee. This is considered an unexpected +// condition in the test, which expects a callee to have one more +// caller frame in the stack than its caller. +// - frame pointer omission. Even with a stackwalker that understands +// this optimization, the code to harness debug information currently +// only exists to retrieve it from minidumps, not the current process. +// +// This test can also serve as a developmental and debugging aid if +// PRINT_STACKS is defined. +// +// Author: Mark Mentovai + +#if defined(__GNUC__) && (defined(__i386__) || defined(__ppc__)) + + +#include + +#include "google/airbag_types.h" +#include "google/stack_frame.h" +#include "processor/memory_region.h" +#include "processor/minidump_format.h" + +using google_airbag::MemoryRegion; +using google_airbag::StackFrame; +using google_airbag::StackFrames; + +#if defined(__i386__) +#include "processor/stackwalker_x86.h" +using google_airbag::StackwalkerX86; +#elif defined(__ppc__) +#include "processor/stackwalker_ppc.h" +using google_airbag::StackwalkerPPC; +#endif // __i386__ || __ppc__ + +#define RECURSION_DEPTH 100 + + +// A simple MemoryRegion subclass that provides direct access to this +// process' memory space by pointer. +class SelfMemoryRegion : public MemoryRegion { + public: + virtual u_int64_t GetBase() { return 0; } + virtual u_int32_t GetSize() { return 0xffffffff; } + + bool GetMemoryAtAddress(u_int64_t address, u_int8_t* value) { + return GetMemoryAtAddressInternal(address, value); } + bool GetMemoryAtAddress(u_int64_t address, u_int16_t* value) { + return GetMemoryAtAddressInternal(address, value); } + bool GetMemoryAtAddress(u_int64_t address, u_int32_t* value) { + return GetMemoryAtAddressInternal(address, value); } + bool GetMemoryAtAddress(u_int64_t address, u_int64_t* value) { + return GetMemoryAtAddressInternal(address, value); } + + private: + template bool GetMemoryAtAddressInternal(u_int64_t address, + T* value) { + u_int8_t* memory = 0; + *value = *reinterpret_cast(&memory[address]); + return true; + } +}; + + +#if defined(__i386__) + +// GetEBP returns the current value of the %ebp register. Because it's +// implemented as a function, %ebp itself contains GetEBP's frame pointer +// and not the caller's frame pointer. Dereference %ebp to obtain the +// caller's frame pointer, which the compiler-generated preamble stored +// on the stack (provided frame pointers are not being omitted.) Because +// this function depends on the compiler-generated preamble, inlining is +// disabled. +static u_int32_t GetEBP() __attribute__((noinline)); +static u_int32_t GetEBP() { + u_int32_t ebp; + __asm__ __volatile__( + "movl (%%ebp), %0" + : "=a" (ebp) + ); + return ebp; +} + + +// GetEIP returns the instruction pointer identifying the next instruction +// to execute after GetEIP returns. It obtains this information from the +// stack, where it was placed by the call instruction that called GetEIP. +// This function depends on frame pointers not being omitted. It is possible +// to write a pure asm version of this routine that has no compiler-generated +// preamble and uses %esp instead of %ebp; that would function in the +// absence of frame pointers. However, the simpler approach is used here +// because GetEBP and stackwalking necessarily depends on access to frame +// pointers. Because this function depends on a call instruction and the +// compiler-generated preamble, inlining is disabled. +static u_int32_t GetEIP() __attribute__((noinline)); +static u_int32_t GetEIP() { + u_int32_t eip; + __asm__ __volatile__( + "movl 4(%%ebp), %0" + : "=a" (eip) + ); + return eip; +} + + +#elif defined(__ppc__) + + +// GetSP returns the current value of the %r1 register, which by convention, +// is the stack pointer on ppc. Because it's implemented as a function, +// %r1 itself contains GetSP's own stack pointer and not the caller's stack +// pointer. Dereference %r1 to obtain the caller's stack pointer, which the +// compiler-generated prolog stored on the stack. Because this function +// depends on the compiler-generated prolog, inlining is disabled. +static u_int32_t GetSP() __attribute__((noinline)); +static u_int32_t GetSP() { + u_int32_t sp; + __asm__ __volatile__( + "lwz %0, 0(r1)" + : "=r" (sp) + ); + return sp; +} + + +// GetPC returns the program counter identifying the next instruction to +// execute after GetPC returns. It obtains this information from the +// link register, where it was placed by the branch instruction that called +// GetPC. Because this function depends on the caller's use of a branch +// instruction, inlining is disabled. +static u_int32_t GetPC() __attribute__((noinline)); +static u_int32_t GetPC() { + u_int32_t lr; + __asm__ __volatile__( + "mflr %0" + : "=r" (lr) + ); + return lr; +} + + +#endif // __i386__ || __ppc__ + + +// CountCallerFrames returns the number of stack frames beneath the function +// that called CountCallerFrames. Because this function's return value +// is dependent on the size of the stack beneath it, inlining is disabled, +// and any function that calls this should not be inlined either. +static unsigned int CountCallerFrames() __attribute__((noinline)); +static unsigned int CountCallerFrames() { + SelfMemoryRegion memory; + +#if defined(__i386__) + MDRawContextX86 context = MDRawContextX86(); + context.eip = GetEIP(); + context.ebp = GetEBP(); + + StackwalkerX86 stackwalker = StackwalkerX86(&context, &memory, NULL, NULL); +#elif defined(__ppc__) + MDRawContextPPC context = MDRawContextPPC(); + context.srr0 = GetPC(); + context.gpr[1] = GetSP(); + + StackwalkerPPC stackwalker = StackwalkerPPC(&context, &memory, NULL, NULL); +#endif // __i386__ || __ppc__ + + StackFrames stack; + stackwalker.Walk(&stack); + +#ifdef PRINT_STACKS + printf("\n"); + for(unsigned int frame_index = 0; + frame_index < stack.size(); + ++frame_index) { + StackFrame *frame = &stack[frame_index]; + printf("frame %-3d instruction = 0x%08llx frame_pointer = 0x%08llx\n", + frame_index, frame->instruction, frame->frame_pointer); + } +#endif // PRINT_STACKS + + // Subtract 1 because the caller wants the number of frames beneath + // itself. Because the caller called us, subract two for our frame and its + // frame, which are included in stack->size(). + return stack.size() - 2; +} + + +// Recursor verifies that the number stack frames beneath itself is one more +// than the number of stack frames beneath its parent. When depth frames +// have been reached, Recursor stops checking and returns success. If the +// frame count check fails at any depth, Recursor will stop and return false. +// Because this calls CountCallerFrames, inlining is disabled. +static bool Recursor(unsigned int depth, unsigned int parent_callers) + __attribute__((noinline)); +static bool Recursor(unsigned int depth, unsigned int parent_callers) { + unsigned int callers = CountCallerFrames(); + if (callers != parent_callers + 1) + return false; + + if (depth) + return Recursor(depth - 1, callers); + + // depth == 0 + return true; +} + + +// Because this calls CountCallerFrames, inlining is disabled - but because +// it's main (and nobody calls it other than the entry point), it wouldn't +// be inlined anyway. +int main(int argc, char** argv) __attribute__((noinline)); +int main(int argc, char** argv) { + return Recursor(RECURSION_DEPTH, CountCallerFrames()) ? 0 : 1; +} + + +#else // __GNUC__ && (__i386__ || __ppc__) +// Not gcc? We use gcc's __asm__. +// Not i386 or ppc? We can only test stacks we know how to walk. + + +int main(int argc, char **argv) { + // "make check" interprets an exit status of 77 to mean that the test is + // not supported. + return 77; +} + + +#endif // __GNUC__ && (__i386__ || __ppc__)