From 213800d30c11612cb0457c94d7233813a22d83d5 Mon Sep 17 00:00:00 2001 From: mmentovai Date: Wed, 6 Sep 2006 19:28:46 +0000 Subject: Initial implementation of x86 stackwalker (#9). r=bryner git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@12 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/processor/minidump_stackwalk.cc | 118 +++++++++++++++++++++++++++++ src/processor/minidump_stackwalk_test | 6 ++ src/processor/stackwalker.cc | 78 +++++++++++++++++++ src/processor/stackwalker.h | 83 ++++++++++++++++++++ src/processor/stackwalker_x86.cc | 103 +++++++++++++++++++++++++ src/processor/stackwalker_x86.h | 68 +++++++++++++++++ src/processor/testdata/minidump1.stack.out | 23 ++++++ 7 files changed, 479 insertions(+) create mode 100644 src/processor/minidump_stackwalk.cc create mode 100755 src/processor/minidump_stackwalk_test create mode 100644 src/processor/stackwalker.cc create mode 100644 src/processor/stackwalker.h create mode 100644 src/processor/stackwalker_x86.cc create mode 100644 src/processor/stackwalker_x86.h create mode 100644 src/processor/testdata/minidump1.stack.out (limited to 'src') diff --git a/src/processor/minidump_stackwalk.cc b/src/processor/minidump_stackwalk.cc new file mode 100644 index 00000000..71948a3b --- /dev/null +++ b/src/processor/minidump_stackwalk.cc @@ -0,0 +1,118 @@ +// 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. + +// minidump_stackwalk.cc: Print the stack of the exception thread from a +// minidump. +// +// Author: Mark Mentovai + +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#define O_BINARY 0 +#else // !_WIN32 +#include +#define open _open +#endif // !_WIN32 + +#include + +#include "processor/minidump.h" +#include "processor/stackwalker_x86.h" + + +using std::auto_ptr; +using namespace google_airbag; + + +int main(int argc, char** argv) { + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + exit(1); + } + + int fd = open(argv[1], O_RDONLY | O_BINARY); + if (fd == -1) { + fprintf(stderr, "open failed\n"); + exit(1); + } + + Minidump minidump(fd); + if (!minidump.Read()) { + fprintf(stderr, "minidump.Read() failed\n"); + exit(1); + } + + MinidumpException* exception = minidump.GetException(); + if (!exception) { + fprintf(stderr, "minidump.GetException() failed\n"); + exit(1); + } + + MinidumpThreadList* thread_list = minidump.GetThreadList(); + if (!thread_list) { + fprintf(stderr, "minidump.GetThreadList() failed\n"); + exit(1); + } + + MinidumpThread* exception_thread = + thread_list->GetThreadByID(exception->GetThreadID()); + if (!exception_thread) { + fprintf(stderr, "thread_list->GetThreadByID() failed\n"); + exit(1); + } + + MemoryRegion* stack_memory = exception_thread->GetMemory(); + if (!stack_memory) { + fprintf(stderr, "exception_thread->GetStackMemory() failed\n"); + exit(1); + } + + MinidumpContext* context = exception->GetContext(); + if (!context) { + fprintf(stderr, "exception->GetContext() failed\n"); + exit(1); + } + + MinidumpModuleList* modules = minidump.GetModuleList(); + if (!modules) { + fprintf(stderr, "minidump.GetModuleList() failed\n"); + exit(1); + } + + StackwalkerX86 stackwalker = StackwalkerX86(context, stack_memory, modules); + + auto_ptr stack(stackwalker.Walk()); + if (!stack.get()) { + fprintf(stderr, "stackwalker->Walk() failed\n"); + exit(1); + } + + unsigned int index; + for (index = 0 ; index < stack->size() ; index++) { + StackFrame frame = stack->at(index); + printf("[%2d] ebp = 0x%08llx eip = 0x%08llx \"%s\" + 0x%08llx\n", + index, + frame.frame_pointer, + frame.instruction, + frame.module_base ? frame.module_name.c_str() : "0x0", + frame.instruction - frame.module_base); + } + + return 0; +} diff --git a/src/processor/minidump_stackwalk_test b/src/processor/minidump_stackwalk_test new file mode 100755 index 00000000..a83ea3b3 --- /dev/null +++ b/src/processor/minidump_stackwalk_test @@ -0,0 +1,6 @@ +#!/bin/sh +testdata_dir=$srcdir/src/processor/testdata +./src/processor/minidump_stackwalk $testdata_dir/minidump1.dmp | \ + tr -s '\015' '\012' | \ + diff -u $testdata_dir/minidump1.stack.out - +exit $? diff --git a/src/processor/stackwalker.cc b/src/processor/stackwalker.cc new file mode 100644 index 00000000..646ba955 --- /dev/null +++ b/src/processor/stackwalker.cc @@ -0,0 +1,78 @@ +// 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.cc: Generic stackwalker. +// +// See stackwalker.h for documentation. +// +// Author: Mark Mentovai + + +#include + +#include "processor/stackwalker.h" +#include "processor/minidump.h" + + +namespace google_airbag { + + +using std::auto_ptr; + + +Stackwalker::Stackwalker(MemoryRegion* memory, MinidumpModuleList* modules) + : memory_(memory), modules_(modules) { +} + + +StackFrames* Stackwalker::Walk() { + auto_ptr frames(new StackFrames()); + + // Begin with the context frame, and keep getting callers until there are + // no more. + + auto_ptr frame(new StackFrame()); + bool valid = GetContextFrame(frame.get()); + while (valid) { + // frame already contains a good frame with properly set instruction and + // frame_pointer fields. The frame structure comes from either the + // context frame (above) or a caller frame (below). + + // Resolve the module information, if a module map was provided. + if (modules_) { + MinidumpModule* module = + modules_->GetModuleForAddress(frame->instruction); + if (module) { + frame->module_name = *(module->GetName()); + frame->module_base = module->base_address(); + } + } + + // Copy the frame into the frames vector. + frames->push_back(*frame); + + // Use a new object for the next frame, even though the old object was + // copied. If StackFrame provided some sort of Clear() method, then + // the same frame could be reused. + frame.reset(new StackFrame()); + + // Get the next frame. + valid = GetCallerFrame(frame.get()); + } + + return frames.release(); +} + + +} // namespace google_airbag diff --git a/src/processor/stackwalker.h b/src/processor/stackwalker.h new file mode 100644 index 00000000..1ae9a85d --- /dev/null +++ b/src/processor/stackwalker.h @@ -0,0 +1,83 @@ +// 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.cc: Generic stackwalker. +// +// The Stackwalker class is an abstract base class providing common generic +// methods that apply to stacks from all systems. Specific implementations +// will extend this class by providing GetContextFrame and GetCallerFrame +// methods to fill in system-specific data in a StackFrame structure. +// Stackwalker assembles these StackFrame strucutres into a vector of +// StackFrames. +// +// Author: Mark Mentovai + + +#ifndef PROCESSOR_STACKWALKER_H__ +#define PROCESSOR_STACKWALKER_H__ + + +#include "google/stack_frame.h" +#include "processor/memory_region.h" + + +namespace google_airbag { + + +class MinidumpModuleList; + + +class Stackwalker { + public: + virtual ~Stackwalker() {} + + // Produces a vector of StackFrames by calling GetContextFrame and + // GetCallerFrame, and populating the returned frames with module + // offset and name information if possible. The caller takes ownership + // of the StackFrames object and is responsible for freeing it. + StackFrames* Walk(); + + protected: + // memory identifies a MemoryRegion that provides the stack memory + // for the stack to walk. modules, if non-NULL, is a MinidumpModuleList + // that is used to look up which code module each stack frame is + // associated with. + Stackwalker(MemoryRegion* memory, MinidumpModuleList* modules); + + // The stack memory to walk. Subclasses will require this region to + // get information from the stack. + MemoryRegion* memory_; + + private: + // Obtains the context frame, the innermost called procedure in a stack + // trace. Returns false on failure. + virtual bool GetContextFrame(StackFrame* frame) = 0; + + // Obtains a caller frame. Each call to GetCallerFrame should return the + // frame that called the last frame returned by GetContextFrame or + // GetCallerFrame. GetCallerFrame should return false on failure or + // when there are no more caller frames (when the end of the stack has + // been reached). + virtual bool GetCallerFrame(StackFrame* frame) = 0; + + // A list of modules, for populating each StackFrame's module information. + // This field is optional and may be NULL. + MinidumpModuleList* modules_; +}; + + +} // namespace google_airbag + + +#endif // PROCESSOR_STACKWALKER_H__ diff --git a/src/processor/stackwalker_x86.cc b/src/processor/stackwalker_x86.cc new file mode 100644 index 00000000..d3360caf --- /dev/null +++ b/src/processor/stackwalker_x86.cc @@ -0,0 +1,103 @@ +// 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_x86.cc: x86-specific stackwalker. +// +// See stackwalker_x86.h for documentation. +// +// Author: Mark Mentovai + + +#include "processor/stackwalker_x86.h" +#include "processor/minidump.h" + + +namespace google_airbag { + + +StackwalkerX86::StackwalkerX86(MinidumpContext* context, + MemoryRegion* memory, + MinidumpModuleList* modules) + : Stackwalker(memory, modules) + , last_frame_pointer_(0) { + if (memory_->GetBase() + memory_->GetSize() - 1 > 0xffffffff) { + // The x86 is a 32-bit CPU, the limits of the supplied stack are invalid. + // Mark memory_ = NULL, which will cause stackwalking to fail. + memory_ = NULL; + } + + // TODO(mmentovai): verify that |context| is x86 when Minidump supports + // other CPU types. + context_ = context->context(); +} + + +bool StackwalkerX86::GetContextFrame(StackFrame* frame) { + if (!context_ || !memory_ || !frame) + return false; + + // The frame and instruction pointers are stored directly in registers, + // so pull them straight out of the CPU context structure. + frame->frame_pointer = last_frame_pointer_ = context_->ebp; + frame->instruction = context_->eip; + + return true; +} + + +bool StackwalkerX86::GetCallerFrame(StackFrame* frame) { + if (!memory_ || !frame) + return false; + + // The frame and instruction pointers for previous frames are saved on the + // stack. The typical x86 calling convention, when frame pointers are + // present, is for the calling procedure to use CALL, which pushes the + // return address onto the stack and sets the instruction pointer (%eip) + // to the entry point of the called routine. The called routine's then + // PUSHes the calling routine's frame pointer (%ebp) onto the stack before + // copying the stack pointer (%esp) to the frame pointer (%ebp). Therefore, + // the calling procedure's frame pointer is always available by + // dereferencing the called procedure's frame pointer, and the return + // address is always available at the memory location immediately above + // the address pointed to by the called procedure's frame pointer. + + // If there is no frame pointer, determining the layout of the stack is + // considerably more difficult, requiring debugging information. This + // stackwalker doesn't attempt to solve that problem (at this point). + + // Don't pass frame.frame_pointer or frame.instruction directly + // ReadMemory, because their types are too wide (64-bit), and we + // specifically want to read 32-bit quantities for both. + u_int32_t frame_pointer; + if (!memory_->GetMemoryAtAddress(last_frame_pointer_, &frame_pointer)) + return false; + + // A caller frame must reside higher in memory than its callee frames. + // Anything else is an error, or an indication that we've reached the + // end of the stack. + if (frame_pointer <= last_frame_pointer_) + return false; + + u_int32_t instruction; + if (!memory_->GetMemoryAtAddress(last_frame_pointer_ + 4, &instruction)) + return false; + + frame->frame_pointer = last_frame_pointer_ = frame_pointer; + frame->instruction = instruction; + + return true; +} + + +} // namespace google_airbag diff --git a/src/processor/stackwalker_x86.h b/src/processor/stackwalker_x86.h new file mode 100644 index 00000000..74d893a2 --- /dev/null +++ b/src/processor/stackwalker_x86.h @@ -0,0 +1,68 @@ +// 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_x86.h: x86-specific stackwalker. +// +// Provides stack frames given x86 register context and a memory region +// corresponding to an x86 stack. +// +// Author: Mark Mentovai + + +#ifndef PROCESSOR_STACKWALKER_X86_H__ +#define PROCESSOR_STACKWALKER_X86_H__ + + +#include "google/airbag_types.h" +#include "processor/stackwalker.h" +#include "processor/minidump_format.h" + + +namespace google_airbag { + + +class MinidumpContext; +class MinidumpModuleList; + + +class StackwalkerX86 : public Stackwalker { + public: + // context is a MinidumpContext object that gives access to x86-specific + // register state corresponding to the innermost called frame to be + // included in the stack. memory and modules are passed directly through + // to the base Stackwalker constructor. + StackwalkerX86(MinidumpContext* context, + MemoryRegion* memory, + MinidumpModuleList* modules); + + private: + // Implementation of Stackwalker, using x86 context (%ebp, %eip) and + // stack conventions (saved %ebp at [%ebp], saved %eip at 4[%ebp]). + bool GetContextFrame(StackFrame* frame); + bool GetCallerFrame(StackFrame* frame); + + // Stores the CPU context corresponding to the innermost stack frame to + // be returned by GetContextFrame. + const MDRawContextX86* context_; + + // Stores the frame pointer returned in the last stack frame returned by + // GetContextFrame or GetCallerFrame. + u_int32_t last_frame_pointer_; +}; + + +} // namespace google_airbag + + +#endif // PROCESSOR_STACKWALKER_X86_H__ diff --git a/src/processor/testdata/minidump1.stack.out b/src/processor/testdata/minidump1.stack.out new file mode 100644 index 00000000..d2e254d8 --- /dev/null +++ b/src/processor/testdata/minidump1.stack.out @@ -0,0 +1,23 @@ +[ 0] ebp = 0x0012ecb8 eip = 0x020a1515 "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x00221515 +[ 1] ebp = 0x0012ecd8 eip = 0x020a03e3 "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x002203e3 +[ 2] ebp = 0x0012ecf0 eip = 0x023c8a28 "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x00548a28 +[ 3] ebp = 0x0012ed30 eip = 0x023ccfd9 "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x0054cfd9 +[ 4] ebp = 0x0012ed64 eip = 0x0222fd12 "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x003afd12 +[ 5] ebp = 0x0012ed94 eip = 0x022311dd "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x003b11dd +[ 6] ebp = 0x0012edc8 eip = 0x034eb0f1 "c:\lizard\trunk\mozilla\dist\bin\components\xpc3250.dll" + 0x0005b0f1 +[ 7] ebp = 0x0012eeb0 eip = 0x0049bda4 "c:\lizard\trunk\mozilla\dist\bin\js3250.dll" + 0x0007bda4 +[ 8] ebp = 0x0012f834 eip = 0x0047b92f "c:\lizard\trunk\mozilla\dist\bin\js3250.dll" + 0x0005b92f +[ 9] ebp = 0x0012f93c eip = 0x0046c945 "c:\lizard\trunk\mozilla\dist\bin\js3250.dll" + 0x0004c945 +[10] ebp = 0x0012f9c8 eip = 0x0046d345 "c:\lizard\trunk\mozilla\dist\bin\js3250.dll" + 0x0004d345 +[11] ebp = 0x0012f9f0 eip = 0x00430ec3 "c:\lizard\trunk\mozilla\dist\bin\js3250.dll" + 0x00010ec3 +[12] ebp = 0x0012fa4c eip = 0x02213b7f "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x00393b7f +[13] ebp = 0x0012fb60 eip = 0x02249ced "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x003c9ced +[14] ebp = 0x0012fb70 eip = 0x0224a810 "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x003ca810 +[15] ebp = 0x0012fbbc eip = 0x002ebff8 "c:\lizard\trunk\mozilla\dist\bin\xpcom_core.dll" + 0x0007bff8 +[16] ebp = 0x0012fbe4 eip = 0x002ec8ec "c:\lizard\trunk\mozilla\dist\bin\xpcom_core.dll" + 0x0007c8ec +[17] ebp = 0x0012fc48 eip = 0x029193b5 "c:\lizard\trunk\mozilla\dist\bin\components\gkwidget.dll" + 0x000293b5 +[18] ebp = 0x0012fc5c eip = 0x03174b19 "c:\lizard\trunk\mozilla\dist\bin\components\tkitcmps.dll" + 0x00004b19 +[19] ebp = 0x0012ff54 eip = 0x10008e60 "c:\lizard\trunk\mozilla\dist\bin\xul.dll" + 0x00008e60 +[20] ebp = 0x0012ff68 eip = 0x00401036 "c:\lizard\trunk\mozilla\dist\bin\firefox.exe" + 0x00001036 +[21] ebp = 0x0012ffc0 eip = 0x004011bc "c:\lizard\trunk\mozilla\dist\bin\firefox.exe" + 0x000011bc +[22] ebp = 0x0012fff0 eip = 0x7c816d4f "C:\WINDOWS\system32\kernel32.dll" + 0x00016d4f -- cgit v1.2.1