aboutsummaryrefslogtreecommitdiff
path: root/src/processor
diff options
context:
space:
mode:
authormmentovai <mmentovai@4c0a9323-5329-0410-9bdc-e9ce6186880e>2006-09-25 21:16:15 +0000
committermmentovai <mmentovai@4c0a9323-5329-0410-9bdc-e9ce6186880e>2006-09-25 21:16:15 +0000
commit0dbedc973d5ce1e2c6924a144b3ac02ee4283091 (patch)
treebeb81784cf745a2a3422a7832800e710fc3c5812 /src/processor
parentppc stackwalker (#30). r=bryner (diff)
downloadbreakpad-0dbedc973d5ce1e2c6924a144b3ac02ee4283091.tar.xz
Better testing for Stackwalker (#18). r=bryner
- Test StackwalkerX86 and StackwalkerPPC on the current process, if built by a supported compiler (gcc) on a supported (walkable) CPU (x86, ppc). - This test is not enabled by default because of certain optimizations that interfere with it (stack frame reuse, frame pointer omission). See the comments at the top of stackwalker_selftest.cc. To enable this test in the standard "make check" suite, configure with --enable-selftest. http://groups.google.com/group/airbag-dev/browse_thread/thread/2ca1c8e72c809bec git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@35 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src/processor')
-rw-r--r--src/processor/stackwalker_selftest.cc252
1 files changed, 252 insertions, 0 deletions
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 <cstdio>
+
+#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<typename T> bool GetMemoryAtAddressInternal(u_int64_t address,
+ T* value) {
+ u_int8_t* memory = 0;
+ *value = *reinterpret_cast<const T*>(&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__)