mirror of
https://git.suyu.dev/suyu/breakpad.git
synced 2025-12-27 09:45:27 +01:00
Handle frame pointer omission, (#21), part 4 (final part!): FPO stackwalker.
r=bryner - This change allows Airbag to properly walk win32 stacks produced by code built with MSVC's frame pointer omission optimization (/Oy). This optimization is enabled at /O1 and /O2. - There too many interface and file format changes to list here. http://groups.google.com/group/airbag-dev/browse_thread/thread/85ce85bfa8457ece git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@42 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
5afd60b067
commit
246f406828
33 changed files with 2643 additions and 1744 deletions
50
src/processor/call_stack.cc
Normal file
50
src/processor/call_stack.cc
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// 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.
|
||||
|
||||
// call_stack.cc: A call stack comprised of stack frames.
|
||||
//
|
||||
// See call_stack.h for documentation.
|
||||
//
|
||||
// Author: Mark Mentovai
|
||||
|
||||
#include "google/call_stack.h"
|
||||
#include "google/stack_frame.h"
|
||||
#include "processor/linked_ptr.h"
|
||||
|
||||
namespace google_airbag {
|
||||
|
||||
CallStack::~CallStack() {
|
||||
for (vector<StackFrame *>::const_iterator iterator = frames_.begin();
|
||||
iterator != frames_.end();
|
||||
++iterator) {
|
||||
delete *iterator;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace google_airbag
|
||||
|
|
@ -53,6 +53,8 @@
|
|||
#ifndef PROCESSOR_LINKED_PTR_H__
|
||||
#define PROCESSOR_LINKED_PTR_H__
|
||||
|
||||
namespace google_airbag {
|
||||
|
||||
// This is used internally by all instances of linked_ptr<>. It needs to be
|
||||
// a non-template class because different types of linked_ptr<> can refer to
|
||||
// the same object (linked_ptr<Superclass>(obj) vs linked_ptr<Subclass>(obj)).
|
||||
|
|
@ -186,4 +188,6 @@ linked_ptr<T> make_linked_ptr(T* ptr) {
|
|||
return linked_ptr<T>(ptr);
|
||||
}
|
||||
|
||||
#endif // PROCESSOR_LINKED_PTR_H__
|
||||
} // namespace google_airbag
|
||||
|
||||
#endif // PROCESSOR_LINKED_PTR_H__
|
||||
|
|
|
|||
|
|
@ -647,7 +647,7 @@ const u_int8_t* MinidumpMemoryRegion::GetMemory() {
|
|||
return NULL;
|
||||
|
||||
// TODO(mmentovai): verify rational size!
|
||||
auto_ptr<vector<u_int8_t> > memory(
|
||||
auto_ptr< vector<u_int8_t> > memory(
|
||||
new vector<u_int8_t>(descriptor_->memory.data_size));
|
||||
|
||||
if (!minidump_->ReadBytes(&(*memory)[0], descriptor_->memory.data_size))
|
||||
|
|
@ -1086,7 +1086,7 @@ const u_int8_t* MinidumpModule::GetCVRecord() {
|
|||
// variable-sized due to their pdb_file_name fields; these structures
|
||||
// are not sizeof(MDCVInfoPDB70) or sizeof(MDCVInfoPDB20) and treating
|
||||
// them as such would result in incomplete structures or overruns.
|
||||
auto_ptr<vector<u_int8_t> > cv_record(
|
||||
auto_ptr< vector<u_int8_t> > cv_record(
|
||||
new vector<u_int8_t>(module_.cv_record.data_size));
|
||||
|
||||
if (!minidump_->ReadBytes(&(*cv_record)[0], module_.cv_record.data_size))
|
||||
|
|
@ -1161,7 +1161,7 @@ const MDImageDebugMisc* MinidumpModule::GetMiscRecord() {
|
|||
// because the MDImageDebugMisc is variable-sized due to its data field;
|
||||
// this structure is not sizeof(MDImageDebugMisc) and treating it as such
|
||||
// would result in an incomplete structure or an overrun.
|
||||
auto_ptr<vector<u_int8_t> > misc_record_mem(
|
||||
auto_ptr< vector<u_int8_t> > misc_record_mem(
|
||||
new vector<u_int8_t>(module_.misc_record.data_size));
|
||||
MDImageDebugMisc* misc_record =
|
||||
reinterpret_cast<MDImageDebugMisc*>(&(*misc_record_mem)[0]);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ MinidumpProcessor::~MinidumpProcessor() {
|
|||
}
|
||||
|
||||
bool MinidumpProcessor::Process(const string &minidump_file,
|
||||
StackFrames *stack_frames) {
|
||||
CallStack *stack) {
|
||||
Minidump dump(minidump_file);
|
||||
if (!dump.Read()) {
|
||||
return false;
|
||||
|
|
@ -79,7 +79,7 @@ bool MinidumpProcessor::Process(const string &minidump_file,
|
|||
return false;
|
||||
}
|
||||
|
||||
walker->Walk(stack_frames);
|
||||
walker->Walk(stack);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,13 +31,15 @@
|
|||
// corresponding symbol file, and checks the stack frames for correctness.
|
||||
|
||||
#include <string>
|
||||
#include "google/call_stack.h"
|
||||
#include "google/minidump_processor.h"
|
||||
#include "google/stack_frame.h"
|
||||
#include "google/symbol_supplier.h"
|
||||
#include "processor/minidump.h"
|
||||
|
||||
using std::string;
|
||||
using google_airbag::CallStack;
|
||||
using google_airbag::MinidumpProcessor;
|
||||
using google_airbag::StackFrames;
|
||||
|
||||
#define ASSERT_TRUE(cond) \
|
||||
if (!(cond)) { \
|
||||
|
|
@ -72,40 +74,40 @@ static bool RunTests() {
|
|||
TestSymbolSupplier supplier;
|
||||
MinidumpProcessor processor(&supplier);
|
||||
|
||||
StackFrames stack_frames;
|
||||
CallStack stack;
|
||||
string minidump_file = string(getenv("srcdir") ? getenv("srcdir") : ".") +
|
||||
"/src/processor/testdata/minidump2.dmp";
|
||||
|
||||
ASSERT_TRUE(processor.Process(minidump_file, &stack_frames));
|
||||
ASSERT_EQ(stack_frames.size(), 4);
|
||||
ASSERT_TRUE(processor.Process(minidump_file, &stack));
|
||||
ASSERT_EQ(stack.frames()->size(), 4);
|
||||
|
||||
ASSERT_EQ(stack_frames[0].module_base, 0x400000);
|
||||
ASSERT_EQ(stack_frames[0].module_name, "c:\\test_app.exe");
|
||||
ASSERT_EQ(stack_frames[0].function_name, "CrashFunction");
|
||||
ASSERT_EQ(stack_frames[0].source_file_name, "c:\\test_app.cc");
|
||||
ASSERT_EQ(stack_frames[0].source_line, 36);
|
||||
ASSERT_EQ(stack.frames()->at(0)->module_base, 0x400000);
|
||||
ASSERT_EQ(stack.frames()->at(0)->module_name, "c:\\test_app.exe");
|
||||
ASSERT_EQ(stack.frames()->at(0)->function_name, "CrashFunction()");
|
||||
ASSERT_EQ(stack.frames()->at(0)->source_file_name, "c:\\test_app.cc");
|
||||
ASSERT_EQ(stack.frames()->at(0)->source_line, 65);
|
||||
|
||||
ASSERT_EQ(stack_frames[1].module_base, 0x400000);
|
||||
ASSERT_EQ(stack_frames[1].module_name, "c:\\test_app.exe");
|
||||
ASSERT_EQ(stack_frames[1].function_name, "main");
|
||||
ASSERT_EQ(stack_frames[1].source_file_name, "c:\\test_app.cc");
|
||||
ASSERT_EQ(stack_frames[1].source_line, 42);
|
||||
ASSERT_EQ(stack.frames()->at(1)->module_base, 0x400000);
|
||||
ASSERT_EQ(stack.frames()->at(1)->module_name, "c:\\test_app.exe");
|
||||
ASSERT_EQ(stack.frames()->at(1)->function_name, "main");
|
||||
ASSERT_EQ(stack.frames()->at(1)->source_file_name, "c:\\test_app.cc");
|
||||
ASSERT_EQ(stack.frames()->at(1)->source_line, 70);
|
||||
|
||||
// This comes from the CRT
|
||||
ASSERT_EQ(stack_frames[2].module_base, 0x400000);
|
||||
ASSERT_EQ(stack_frames[2].module_name, "c:\\test_app.exe");
|
||||
ASSERT_EQ(stack_frames[2].function_name, "__tmainCRTStartup");
|
||||
ASSERT_EQ(stack_frames[2].source_file_name,
|
||||
ASSERT_EQ(stack.frames()->at(2)->module_base, 0x400000);
|
||||
ASSERT_EQ(stack.frames()->at(2)->module_name, "c:\\test_app.exe");
|
||||
ASSERT_EQ(stack.frames()->at(2)->function_name, "__tmainCRTStartup");
|
||||
ASSERT_EQ(stack.frames()->at(2)->source_file_name,
|
||||
"f:\\rtm\\vctools\\crt_bld\\self_x86\\crt\\src\\crt0.c");
|
||||
ASSERT_EQ(stack_frames[2].source_line, 318);
|
||||
ASSERT_EQ(stack.frames()->at(2)->source_line, 318);
|
||||
|
||||
// No debug info available for kernel32.dll
|
||||
ASSERT_EQ(stack_frames[3].module_base, 0x7c800000);
|
||||
ASSERT_EQ(stack_frames[3].module_name,
|
||||
ASSERT_EQ(stack.frames()->at(3)->module_base, 0x7c800000);
|
||||
ASSERT_EQ(stack.frames()->at(3)->module_name,
|
||||
"C:\\WINDOWS\\system32\\kernel32.dll");
|
||||
ASSERT_TRUE(stack_frames[3].function_name.empty());
|
||||
ASSERT_TRUE(stack_frames[3].source_file_name.empty());
|
||||
ASSERT_EQ(stack_frames[3].source_line, 0);
|
||||
ASSERT_TRUE(stack.frames()->at(3)->function_name.empty());
|
||||
ASSERT_TRUE(stack.frames()->at(3)->source_file_name.empty());
|
||||
ASSERT_EQ(stack.frames()->at(3)->source_line, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,12 +38,15 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "google/call_stack.h"
|
||||
#include "google/stack_frame.h"
|
||||
#include "processor/minidump.h"
|
||||
#include "processor/stackwalker_x86.h"
|
||||
|
||||
|
||||
using std::auto_ptr;
|
||||
using std::string;
|
||||
using google_airbag::CallStack;
|
||||
using google_airbag::MemoryRegion;
|
||||
using google_airbag::Minidump;
|
||||
using google_airbag::MinidumpContext;
|
||||
|
|
@ -52,7 +55,6 @@ using google_airbag::MinidumpModuleList;
|
|||
using google_airbag::MinidumpThread;
|
||||
using google_airbag::MinidumpThreadList;
|
||||
using google_airbag::StackFrame;
|
||||
using google_airbag::StackFrames;
|
||||
using google_airbag::Stackwalker;
|
||||
|
||||
|
||||
|
|
@ -112,18 +114,17 @@ int main(int argc, char **argv) {
|
|||
exit(1);
|
||||
}
|
||||
|
||||
StackFrames stack;
|
||||
CallStack stack;
|
||||
stackwalker->Walk(&stack);
|
||||
|
||||
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",
|
||||
for (index = 0; index < stack.frames()->size(); ++index) {
|
||||
StackFrame *frame = stack.frames()->at(index);
|
||||
printf("[%2d] instruction = 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);
|
||||
frame->instruction,
|
||||
frame->module_base ? frame->module_name.c_str() : "0x0",
|
||||
frame->instruction - frame->module_base);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ class AutoStackClearer {
|
|||
|
||||
|
||||
template<typename ValueType>
|
||||
bool PostfixEvaluator<ValueType>::Evaluate(const string &expression) {
|
||||
bool PostfixEvaluator<ValueType>::Evaluate(const string &expression,
|
||||
DictionaryValidityType *assigned) {
|
||||
// Ensure that the stack is cleared before returning.
|
||||
AutoStackClearer clearer(&stack_);
|
||||
|
||||
|
|
@ -142,6 +143,8 @@ bool PostfixEvaluator<ValueType>::Evaluate(const string &expression) {
|
|||
return false;
|
||||
|
||||
(*dictionary_)[identifier] = value;
|
||||
if (assigned)
|
||||
(*assigned)[identifier] = true;
|
||||
} else {
|
||||
// The token is not an operator, it's a literal value or an identifier.
|
||||
// Push it onto the stack as-is. Use push_back instead of PushValue
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ template<typename ValueType>
|
|||
class PostfixEvaluator {
|
||||
public:
|
||||
typedef map<string, ValueType> DictionaryType;
|
||||
typedef map<string, bool> DictionaryValidityType;
|
||||
|
||||
// Create a PostfixEvaluator object that may be used (with Evaluate) on
|
||||
// one or more expressions. PostfixEvaluator does not take ownership of
|
||||
|
|
@ -82,8 +83,11 @@ class PostfixEvaluator {
|
|||
// Evaluate the expression. The results of execution will be stored
|
||||
// in one (or more) variables in the dictionary. Returns false if any
|
||||
// failures occure during execution, leaving variables in the dictionary
|
||||
// in an indeterminate state.
|
||||
bool Evaluate(const string &expression);
|
||||
// in an indeterminate state. If assigned is non-NULL, any keys set in
|
||||
// the dictionary as a result of evaluation will also be set to true in
|
||||
// assigned, providing a way to determine if an expression modifies any
|
||||
// of its input variables.
|
||||
bool Evaluate(const string &expression, DictionaryValidityType *assigned);
|
||||
|
||||
DictionaryType* dictionary() const { return dictionary_; }
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ struct EvaluateTest {
|
|||
|
||||
struct EvaluateTestSet {
|
||||
// The dictionary used for all tests in the set.
|
||||
map<string, unsigned int> *dictionary;
|
||||
PostfixEvaluator<unsigned int>::DictionaryType *dictionary;
|
||||
|
||||
// The list of tests.
|
||||
const EvaluateTest *evaluate_tests;
|
||||
|
|
@ -77,7 +77,7 @@ struct EvaluateTestSet {
|
|||
|
||||
bool RunTests() {
|
||||
// The first test set checks the basic operations and failure modes.
|
||||
map<string, unsigned int> dictionary_0;
|
||||
PostfixEvaluator<unsigned int>::DictionaryType dictionary_0;
|
||||
const EvaluateTest evaluate_tests_0[] = {
|
||||
{ "$rAdd 2 2 + =", true }, // $rAdd = 2 + 2 = 4
|
||||
{ "$rAdd $rAdd 2 + =", true }, // $rAdd = $rAdd + 2 = 6
|
||||
|
|
@ -122,7 +122,7 @@ bool RunTests() {
|
|||
// The data is fudged a little bit because the tests use FakeMemoryRegion
|
||||
// instead of a real stack snapshot, but the program strings are real and
|
||||
// the implementation doesn't know or care that the data is not real.
|
||||
map<string, unsigned int> dictionary_1;
|
||||
PostfixEvaluator<unsigned int>::DictionaryType dictionary_1;
|
||||
dictionary_1["$ebp"] = 0xbfff0010;
|
||||
dictionary_1["$eip"] = 0x10000000;
|
||||
dictionary_1["$esp"] = 0xbfff0000;
|
||||
|
|
@ -186,13 +186,17 @@ bool RunTests() {
|
|||
// tests can affect the state of the dictionary for later tests.
|
||||
postfix_evaluator.set_dictionary(evaluate_test_set->dictionary);
|
||||
|
||||
// Use a new validity dictionary for each test set.
|
||||
PostfixEvaluator<unsigned int>::DictionaryValidityType assigned;
|
||||
|
||||
for (unsigned int evaluate_test_index = 0;
|
||||
evaluate_test_index < evaluate_test_count;
|
||||
++evaluate_test_index) {
|
||||
const EvaluateTest *evaluate_test = &evaluate_tests[evaluate_test_index];
|
||||
|
||||
// Do the test.
|
||||
bool result = postfix_evaluator.Evaluate(evaluate_test->expression);
|
||||
bool result = postfix_evaluator.Evaluate(evaluate_test->expression,
|
||||
&assigned);
|
||||
if (result != evaluate_test->evaluable) {
|
||||
fprintf(stderr, "FAIL: evaluate set %d/%d, test %d/%d, "
|
||||
"expression \"%s\", expected %s, observed %s\n",
|
||||
|
|
@ -236,6 +240,24 @@ bool RunTests() {
|
|||
identifier.c_str(), expected_value, observed_value);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The value must be set in the "assigned" dictionary if it was a
|
||||
// variable. It must not have been assigned if it was a constant.
|
||||
bool expected_assigned = identifier[0] == '$';
|
||||
bool observed_assigned = false;
|
||||
PostfixEvaluator<unsigned int>::DictionaryValidityType::const_iterator
|
||||
iterator_assigned = assigned.find(identifier);
|
||||
if (iterator_assigned != assigned.end()) {
|
||||
observed_assigned = iterator_assigned->second;
|
||||
}
|
||||
if (expected_assigned != observed_assigned) {
|
||||
fprintf(stderr, "FAIL: evaluate test set %d/%d, "
|
||||
"validate assignment of \"%s\", "
|
||||
"expected %d, observed %d\n",
|
||||
evaluate_test_set_index, evaluate_test_set_count,
|
||||
identifier.c_str(), expected_assigned, observed_assigned);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,13 +62,19 @@ struct SourceLineResolver::Line {
|
|||
struct SourceLineResolver::Function {
|
||||
Function(const string &function_name,
|
||||
MemAddr function_address,
|
||||
MemAddr code_size)
|
||||
: name(function_name), address(function_address), size(code_size) { }
|
||||
MemAddr code_size,
|
||||
int set_parameter_size)
|
||||
: name(function_name), address(function_address), size(code_size),
|
||||
parameter_size(set_parameter_size) { }
|
||||
|
||||
string name;
|
||||
MemAddr address;
|
||||
MemAddr size;
|
||||
RangeMap<MemAddr, linked_ptr<Line> > lines;
|
||||
|
||||
// The size of parameters passed to this function on the stack.
|
||||
int parameter_size;
|
||||
|
||||
RangeMap< MemAddr, linked_ptr<Line> > lines;
|
||||
};
|
||||
|
||||
class SourceLineResolver::Module {
|
||||
|
|
@ -128,7 +134,7 @@ class SourceLineResolver::Module {
|
|||
|
||||
string name_;
|
||||
FileMap files_;
|
||||
RangeMap<MemAddr, linked_ptr<Function> > functions_;
|
||||
RangeMap< MemAddr, linked_ptr<Function> > functions_;
|
||||
|
||||
// Each element in the array is a ContainedRangeMap for a type listed in
|
||||
// StackInfoTypes. These are split by type because there may be overlaps
|
||||
|
|
@ -184,7 +190,10 @@ bool SourceLineResolver::Module::LoadMap(const string &map_file) {
|
|||
return false;
|
||||
}
|
||||
|
||||
char buffer[1024];
|
||||
// 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];
|
||||
Function *cur_func = NULL;
|
||||
|
||||
while (fgets(buffer, sizeof(buffer), f)) {
|
||||
|
|
@ -201,6 +210,8 @@ bool SourceLineResolver::Module::LoadMap(const string &map_file) {
|
|||
}
|
||||
functions_.StoreRange(cur_func->address, cur_func->size,
|
||||
linked_ptr<Function>(cur_func));
|
||||
} else if (strncmp(buffer, "PUBLIC ", 7) == 0) {
|
||||
// TODO(mmentovai): add a public map
|
||||
} else {
|
||||
if (!cur_func) {
|
||||
return false;
|
||||
|
|
@ -221,18 +232,19 @@ bool SourceLineResolver::Module::LoadMap(const string &map_file) {
|
|||
void SourceLineResolver::Module::LookupAddress(
|
||||
MemAddr address, StackFrame *frame, StackFrameInfo *frame_info) const {
|
||||
if (frame_info) {
|
||||
frame_info->valid = StackFrameInfo::VALID_NONE;
|
||||
|
||||
// Check for debugging info first, before any possible early returns.
|
||||
// The caller will know that frame_info was filled in by checking its
|
||||
// valid field.
|
||||
//
|
||||
// We only know about STACK_INFO_FRAME_DATA and STACK_INFO_FPO.
|
||||
// STACK_INFO_STANDARD looks like it would do the right thing, too.
|
||||
// Prefer them in this order.
|
||||
// We only know about STACK_INFO_FRAME_DATA and STACK_INFO_FPO. Prefer
|
||||
// them in this order. STACK_INFO_FRAME_DATA is the newer type that
|
||||
// includes its own program string. STACK_INFO_FPO is the older type
|
||||
// corresponding to the FPO_DATA struct. See stackwalker_x86.cc.
|
||||
if (!stack_info_[STACK_INFO_FRAME_DATA].RetrieveRange(address,
|
||||
frame_info)) {
|
||||
if (!stack_info_[STACK_INFO_FPO].RetrieveRange(address, frame_info)) {
|
||||
stack_info_[STACK_INFO_STANDARD].RetrieveRange(address, frame_info);
|
||||
}
|
||||
stack_info_[STACK_INFO_FPO].RetrieveRange(address, frame_info);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -252,6 +264,16 @@ void SourceLineResolver::Module::LookupAddress(
|
|||
frame->source_file_name = files_.find(line->source_file_id)->second;
|
||||
}
|
||||
frame->source_line = line->line;
|
||||
|
||||
if (frame_info &&
|
||||
!(frame_info->valid & StackFrameInfo::VALID_PARAMETER_SIZE)) {
|
||||
// Even without a relevant STACK line, many functions contain information
|
||||
// about how much space their parameters consume on the stack. Prefer
|
||||
// the STACK stuff (above), but if it's not present, take the
|
||||
// information from the FUNC line.
|
||||
frame_info->parameter_size = func->parameter_size;
|
||||
frame_info->valid |= StackFrameInfo::VALID_PARAMETER_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
@ -303,19 +325,20 @@ void SourceLineResolver::Module::ParseFile(char *file_line) {
|
|||
|
||||
SourceLineResolver::Function* SourceLineResolver::Module::ParseFunction(
|
||||
char *function_line) {
|
||||
// FUNC <address> <name>
|
||||
// FUNC <address> <stack_param_size> <name>
|
||||
function_line += 5; // skip prefix
|
||||
|
||||
vector<char*> tokens;
|
||||
if (!Tokenize(function_line, 3, &tokens)) {
|
||||
if (!Tokenize(function_line, 4, &tokens)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u_int64_t address = strtoull(tokens[0], NULL, 16);
|
||||
u_int64_t size = strtoull(tokens[1], NULL, 16);
|
||||
char *name = tokens[2];
|
||||
u_int64_t address = strtoull(tokens[0], NULL, 16);
|
||||
u_int64_t size = strtoull(tokens[1], NULL, 16);
|
||||
int stack_param_size = strtoull(tokens[2], NULL, 16);
|
||||
char *name = tokens[3];
|
||||
|
||||
return new Function(name, address, size);
|
||||
return new Function(name, address, size, stack_param_size);
|
||||
}
|
||||
|
||||
SourceLineResolver::Line* SourceLineResolver::Module::ParseLine(
|
||||
|
|
@ -340,17 +363,24 @@ SourceLineResolver::Line* SourceLineResolver::Module::ParseLine(
|
|||
bool SourceLineResolver::Module::ParseStackInfo(char *stack_info_line) {
|
||||
// STACK WIN <type> <rva> <code_size> <prolog_size> <epliog_size>
|
||||
// <parameter_size> <saved_register_size> <local_size> <max_stack_size>
|
||||
// <program_string>
|
||||
// <has_program_string> <program_string_OR_allocates_base_pointer>
|
||||
//
|
||||
// If has_program_string is 1, the rest of the line is a program string.
|
||||
// Otherwise, the final token tells whether the stack info indicates that
|
||||
// a base pointer has been allocated.
|
||||
//
|
||||
// Expect has_program_string to be 1 when type is STACK_INFO_FRAME_DATA and
|
||||
// 0 when type is STACK_INFO_FPO, but don't enforce this.
|
||||
|
||||
// Skip "STACK " prefix.
|
||||
stack_info_line += 6;
|
||||
|
||||
vector<char*> tokens;
|
||||
if (!Tokenize(stack_info_line, 11, &tokens))
|
||||
if (!Tokenize(stack_info_line, 12, &tokens))
|
||||
return false;
|
||||
|
||||
// Only MSVC stack frame info is understood for now.
|
||||
char *platform = tokens[0];
|
||||
const char *platform = tokens[0];
|
||||
if (strcmp(platform, "WIN") != 0)
|
||||
return false;
|
||||
|
||||
|
|
@ -358,15 +388,23 @@ bool SourceLineResolver::Module::ParseStackInfo(char *stack_info_line) {
|
|||
if (type < 0 || type > STACK_INFO_LAST - 1)
|
||||
return false;
|
||||
|
||||
u_int64_t rva = strtoull(tokens[2], NULL, 16);
|
||||
u_int64_t code_size = strtoull(tokens[3], NULL, 16);
|
||||
u_int32_t prolog_size = strtoul(tokens[4], NULL, 16);
|
||||
u_int32_t epilog_size = strtoul(tokens[5], NULL, 16);
|
||||
u_int32_t parameter_size = strtoul(tokens[6], NULL, 16);
|
||||
u_int32_t saved_register_size = strtoul(tokens[7], NULL, 16);
|
||||
u_int32_t local_size = strtoul(tokens[8], NULL, 16);
|
||||
u_int32_t max_stack_size = strtoul(tokens[9], NULL, 16);
|
||||
char *program_string = tokens[10];
|
||||
u_int64_t rva = strtoull(tokens[2], NULL, 16);
|
||||
u_int64_t code_size = strtoull(tokens[3], NULL, 16);
|
||||
u_int32_t prolog_size = strtoul(tokens[4], NULL, 16);
|
||||
u_int32_t epilog_size = strtoul(tokens[5], NULL, 16);
|
||||
u_int32_t parameter_size = strtoul(tokens[6], NULL, 16);
|
||||
u_int32_t saved_register_size = strtoul(tokens[7], NULL, 16);
|
||||
u_int32_t local_size = strtoul(tokens[8], NULL, 16);
|
||||
u_int32_t max_stack_size = strtoul(tokens[9], NULL, 16);
|
||||
int has_program_string = strtoul(tokens[10], NULL, 16);
|
||||
|
||||
const char *program_string = "";
|
||||
int allocates_base_pointer = 0;
|
||||
if (has_program_string) {
|
||||
program_string = tokens[11];
|
||||
} else {
|
||||
allocates_base_pointer = strtoul(tokens[11], NULL, 16);
|
||||
}
|
||||
|
||||
// TODO(mmentovai): I wanted to use StoreRange's return value as this
|
||||
// method's return value, but MSVC infrequently outputs stack info that
|
||||
|
|
@ -395,6 +433,7 @@ bool SourceLineResolver::Module::ParseStackInfo(char *stack_info_line) {
|
|||
saved_register_size,
|
||||
local_size,
|
||||
max_stack_size,
|
||||
allocates_base_pointer,
|
||||
program_string));
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ static bool RunTests() {
|
|||
ASSERT_EQ(frame.function_name, "Function1_1");
|
||||
ASSERT_EQ(frame.source_file_name, "file1_1.cc");
|
||||
ASSERT_EQ(frame.source_line, 44);
|
||||
ASSERT_FALSE(frame_info.allocates_base_pointer);
|
||||
ASSERT_EQ(frame_info.program_string,
|
||||
"$eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =");
|
||||
|
||||
|
|
@ -88,6 +89,7 @@ static bool RunTests() {
|
|||
frame.instruction = 0x800;
|
||||
resolver.FillSourceLineInfo(&frame, &frame_info);
|
||||
ASSERT_TRUE(VerifyEmpty(frame));
|
||||
ASSERT_FALSE(frame_info.allocates_base_pointer);
|
||||
ASSERT_TRUE(frame_info.program_string.empty());
|
||||
|
||||
frame.instruction = 0x1280;
|
||||
|
|
@ -95,6 +97,7 @@ static bool RunTests() {
|
|||
ASSERT_EQ(frame.function_name, "Function1_3");
|
||||
ASSERT_TRUE(frame.source_file_name.empty());
|
||||
ASSERT_EQ(frame.source_line, 0);
|
||||
ASSERT_FALSE(frame_info.allocates_base_pointer);
|
||||
ASSERT_TRUE(frame_info.program_string.empty());
|
||||
|
||||
frame.instruction = 0x1380;
|
||||
|
|
@ -102,6 +105,7 @@ static bool RunTests() {
|
|||
ASSERT_EQ(frame.function_name, "Function1_4");
|
||||
ASSERT_TRUE(frame.source_file_name.empty());
|
||||
ASSERT_EQ(frame.source_line, 0);
|
||||
ASSERT_FALSE(frame_info.allocates_base_pointer);
|
||||
ASSERT_FALSE(frame_info.program_string.empty());
|
||||
|
||||
frame.instruction = 0x2180;
|
||||
|
|
|
|||
|
|
@ -46,13 +46,20 @@ namespace google_airbag {
|
|||
|
||||
struct StackFrameInfo {
|
||||
public:
|
||||
StackFrameInfo() : valid(false),
|
||||
enum Validity {
|
||||
VALID_NONE = 0,
|
||||
VALID_PARAMETER_SIZE = 1,
|
||||
VALID_ALL = -1
|
||||
};
|
||||
|
||||
StackFrameInfo() : valid(VALID_NONE),
|
||||
prolog_size(0),
|
||||
epilog_size(0),
|
||||
parameter_size(0),
|
||||
saved_register_size(0),
|
||||
local_size(0),
|
||||
max_stack_size(0),
|
||||
allocates_base_pointer(0),
|
||||
program_string() {}
|
||||
|
||||
StackFrameInfo(u_int32_t set_prolog_size,
|
||||
|
|
@ -61,18 +68,27 @@ struct StackFrameInfo {
|
|||
u_int32_t set_saved_register_size,
|
||||
u_int32_t set_local_size,
|
||||
u_int32_t set_max_stack_size,
|
||||
int set_allocates_base_pointer,
|
||||
const std::string set_program_string)
|
||||
: valid(true),
|
||||
: valid(VALID_ALL),
|
||||
prolog_size(set_prolog_size),
|
||||
epilog_size(set_epilog_size),
|
||||
parameter_size(set_parameter_size),
|
||||
saved_register_size(set_saved_register_size),
|
||||
local_size(set_local_size),
|
||||
max_stack_size(set_max_stack_size),
|
||||
allocates_base_pointer(set_allocates_base_pointer),
|
||||
program_string(set_program_string) {}
|
||||
|
||||
// True when the contents of the structure are valid.
|
||||
bool valid;
|
||||
// Clears the StackFrameInfo object so that users will see it as though
|
||||
// it contains no information.
|
||||
void Clear() { valid = VALID_NONE; program_string.erase(); }
|
||||
|
||||
// Identifies which fields in the structure are valid. This is of
|
||||
// type Validity, but it is defined as an int because it's not
|
||||
// possible to OR values into an enumerated type. Users must check
|
||||
// this field before using any other.
|
||||
int valid;
|
||||
|
||||
// These values come from IDiaFrameData.
|
||||
u_int32_t prolog_size;
|
||||
|
|
@ -81,6 +97,10 @@ struct StackFrameInfo {
|
|||
u_int32_t saved_register_size;
|
||||
u_int32_t local_size;
|
||||
u_int32_t max_stack_size;
|
||||
|
||||
// Only one of allocates_base_pointer or program_string will be valid.
|
||||
// If program_string is empty, use allocates_base_pointer.
|
||||
bool allocates_base_pointer;
|
||||
std::string program_string;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@
|
|||
#include <memory>
|
||||
|
||||
#include "processor/stackwalker.h"
|
||||
#include "google/call_stack.h"
|
||||
#include "google/stack_frame.h"
|
||||
#include "google/symbol_supplier.h"
|
||||
#include "processor/minidump.h"
|
||||
#include "processor/source_line_resolver.h"
|
||||
|
|
@ -55,22 +57,23 @@ Stackwalker::Stackwalker(MemoryRegion *memory, MinidumpModuleList *modules,
|
|||
}
|
||||
|
||||
|
||||
void Stackwalker::Walk(StackFrames *frames) {
|
||||
frames->clear();
|
||||
void Stackwalker::Walk(CallStack *stack) {
|
||||
stack_frame_info_.clear();
|
||||
SourceLineResolver resolver;
|
||||
|
||||
// Begin with the context frame, and keep getting callers until there are
|
||||
// no more.
|
||||
|
||||
auto_ptr<StackFrame> frame(new StackFrame());
|
||||
auto_ptr<StackFrameInfo> frame_info(new StackFrameInfo());
|
||||
bool valid = GetContextFrame(frame.get());
|
||||
while (valid) {
|
||||
// Take ownership of the pointer returned by GetContextFrame.
|
||||
auto_ptr<StackFrame> frame(GetContextFrame());
|
||||
|
||||
while (frame.get()) {
|
||||
// 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).
|
||||
|
||||
StackFrameInfo frame_info;
|
||||
|
||||
// Resolve the module information, if a module map was provided.
|
||||
if (modules_) {
|
||||
MinidumpModule *module =
|
||||
|
|
@ -84,22 +87,20 @@ void Stackwalker::Walk(StackFrames *frames) {
|
|||
resolver.LoadModule(frame->module_name, symbol_file);
|
||||
}
|
||||
}
|
||||
resolver.FillSourceLineInfo(frame.get(), frame_info.get());
|
||||
resolver.FillSourceLineInfo(frame.get(), &frame_info);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the frame into the frames vector.
|
||||
frames->push_back(*frame);
|
||||
stack_frame_info_.push_back(*frame_info);
|
||||
// Add the frame to the call stack. Relinquish the ownership claim
|
||||
// over the frame, because the stack now owns it.
|
||||
stack->frames_.push_back(frame.release());
|
||||
|
||||
// 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());
|
||||
frame_info.reset(new StackFrameInfo());
|
||||
// Copy the frame info.
|
||||
stack_frame_info_.push_back(frame_info);
|
||||
frame_info.Clear();
|
||||
|
||||
// Get the next frame.
|
||||
valid = GetCallerFrame(frame.get(), frames);
|
||||
// Get the next frame and take ownership.
|
||||
frame.reset(GetCallerFrame(stack));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@
|
|||
// 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.
|
||||
// Stackwalker assembles these StackFrame strucutres into a CallStack.
|
||||
//
|
||||
// Author: Mark Mentovai
|
||||
|
||||
|
|
@ -44,14 +43,15 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include "google/stack_frame.h"
|
||||
#include "processor/memory_region.h"
|
||||
#include "processor/stack_frame_info.h"
|
||||
|
||||
namespace google_airbag {
|
||||
|
||||
class CallStack;
|
||||
class MemoryRegion;
|
||||
class MinidumpContext;
|
||||
class MinidumpModuleList;
|
||||
struct StackFrame;
|
||||
class SymbolSupplier;
|
||||
|
||||
|
||||
|
|
@ -59,10 +59,9 @@ class Stackwalker {
|
|||
public:
|
||||
virtual ~Stackwalker() {}
|
||||
|
||||
// Fills the given vector of StackFrames by calling GetContextFrame and
|
||||
// GetCallerFrame, and populating the returned frames with all available
|
||||
// data.
|
||||
void Walk(StackFrames *frames);
|
||||
// Fills the given CallStack by calling GetContextFrame and GetCallerFrame,
|
||||
// and populating the returned frames with all available data.
|
||||
void Walk(CallStack* stack);
|
||||
|
||||
// Returns a new concrete subclass suitable for the CPU that a stack was
|
||||
// generated on, according to the CPU type indicated by the context
|
||||
|
|
@ -88,23 +87,26 @@ class Stackwalker {
|
|||
MemoryRegion *memory_;
|
||||
|
||||
// Additional debugging information for each stack frame. This vector
|
||||
// parallels the StackFrames vector. Subclasses may use this information
|
||||
// to walk the stack.
|
||||
// parallels the CallStack. Subclasses may use this information to help
|
||||
// walk the stack.
|
||||
std::vector<StackFrameInfo> stack_frame_info_;
|
||||
|
||||
private:
|
||||
// Obtains the context frame, the innermost called procedure in a stack
|
||||
// trace. Returns false on failure.
|
||||
virtual bool GetContextFrame(StackFrame *frame) = 0;
|
||||
// trace. Returns NULL on failure. GetContextFrame allocates a new
|
||||
// StackFrame (or StackFrame subclass), ownership of which is taken by
|
||||
// the caller.
|
||||
virtual StackFrame* GetContextFrame() = 0;
|
||||
|
||||
// Obtains a caller frame. Each call to GetCallerFrame should return the
|
||||
// frame that called the last frame returned by GetContextFrame or
|
||||
// GetCallerFrame. To aid this purpose, walked_frames contains the
|
||||
// StackFrames vector of frames that have already been walked.
|
||||
// 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,
|
||||
const StackFrames *walked_frames) = 0;
|
||||
// GetCallerFrame. To aid this purpose, stack contains the CallStack
|
||||
// made of frames that have already been walked. GetCallerFrame should
|
||||
// return NULL on failure or when there are no more caller frames (when
|
||||
// the end of the stack has been reached). GetCallerFrame allocates a new
|
||||
// StackFrame (or StackFrame subclass), ownership of which is taken by
|
||||
// the caller.
|
||||
virtual StackFrame* GetCallerFrame(const CallStack *stack) = 0;
|
||||
|
||||
// A list of modules, for populating each StackFrame's module information.
|
||||
// This field is optional and may be NULL.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@
|
|||
|
||||
|
||||
#include "processor/stackwalker_ppc.h"
|
||||
#include "google/call_stack.h"
|
||||
#include "google/stack_frame_cpu.h"
|
||||
#include "processor/minidump.h"
|
||||
|
||||
namespace google_airbag {
|
||||
|
|
@ -55,65 +57,77 @@ StackwalkerPPC::StackwalkerPPC(const MDRawContextPPC *context,
|
|||
}
|
||||
|
||||
|
||||
bool StackwalkerPPC::GetContextFrame(StackFrame *frame) {
|
||||
if (!context_ || !memory_ || !frame)
|
||||
return false;
|
||||
StackFrame* StackwalkerPPC::GetContextFrame() {
|
||||
if (!context_ || !memory_)
|
||||
return NULL;
|
||||
|
||||
// The stack frame and instruction pointers are stored directly in
|
||||
// registers, so pull them straight out of the CPU context structure.
|
||||
frame->frame_pointer = context_->gpr[1];
|
||||
frame->instruction = context_->srr0;
|
||||
StackFramePPC *frame = new StackFramePPC();
|
||||
|
||||
return true;
|
||||
// The instruction pointer is stored directly in a register, so pull it
|
||||
// straight out of the CPU context structure.
|
||||
frame->context = *context_;
|
||||
frame->context_validity = StackFramePPC::CONTEXT_VALID_ALL;
|
||||
frame->instruction = frame->context.srr0;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
bool StackwalkerPPC::GetCallerFrame(StackFrame *frame,
|
||||
const StackFrames *walked_frames) {
|
||||
if (!memory_ || !frame || !walked_frames)
|
||||
return false;
|
||||
StackFrame* StackwalkerPPC::GetCallerFrame(const CallStack *stack) {
|
||||
if (!memory_ || !stack)
|
||||
return NULL;
|
||||
|
||||
// The stack frame and instruction pointers for previous frames are saved
|
||||
// on the stack. The typical ppc calling convention is for the called
|
||||
// procedure to store its return address in the calling procedure's stack
|
||||
// frame at 8(%r1), and to allocate its own stack frame by decrementing %r1
|
||||
// (the stack pointer) and saving the old value of %r1 at 0(%r1). Because
|
||||
// the ppc has no hardware stack, there is no distinction between the
|
||||
// stack pointer and frame pointer, and what is typically thought of as
|
||||
// the frame pointer on an x86 is usually referred to as the stack pointer
|
||||
// on a ppc.
|
||||
// The instruction pointers for previous frames are saved on the stack.
|
||||
// The typical ppc calling convention is for the called procedure to store
|
||||
// its return address in the calling procedure's stack frame at 8(%r1),
|
||||
// and to allocate its own stack frame by decrementing %r1 (the stack
|
||||
// pointer) and saving the old value of %r1 at 0(%r1). Because the ppc has
|
||||
// no hardware stack, there is no distinction between the stack pointer and
|
||||
// frame pointer, and what is typically thought of as the frame pointer on
|
||||
// an x86 is usually referred to as the stack pointer on a ppc.
|
||||
|
||||
u_int32_t last_stack_pointer = walked_frames->back().frame_pointer;
|
||||
|
||||
// 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 stack_pointer;
|
||||
if (!memory_->GetMemoryAtAddress(last_stack_pointer, &stack_pointer))
|
||||
return false;
|
||||
StackFramePPC *last_frame = static_cast<StackFramePPC*>(
|
||||
stack->frames()->back());
|
||||
|
||||
// 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 (stack_pointer <= last_stack_pointer)
|
||||
return false;
|
||||
|
||||
u_int32_t instruction;
|
||||
if (!memory_->GetMemoryAtAddress(stack_pointer + 8, &instruction))
|
||||
return false;
|
||||
u_int32_t stack_pointer;
|
||||
if (!memory_->GetMemoryAtAddress(last_frame->context.gpr[1],
|
||||
&stack_pointer) ||
|
||||
stack_pointer <= last_frame->context.gpr[1]) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Mac OS X/Darwin gives 1 as the return address from the bottom-most
|
||||
// frame in a stack (a thread's entry point). I haven't found any
|
||||
// documentation on this, but 0 or 1 would be bogus return addresses,
|
||||
// so check for them here and return false (end of stack) when they're
|
||||
// hit to avoid having a phantom frame.
|
||||
if (instruction <= 1)
|
||||
return false;
|
||||
u_int32_t instruction;
|
||||
if (!memory_->GetMemoryAtAddress(stack_pointer + 8, &instruction) ||
|
||||
instruction <= 1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
frame->frame_pointer = stack_pointer;
|
||||
frame->instruction = instruction;
|
||||
StackFramePPC *frame = new StackFramePPC();
|
||||
|
||||
return true;
|
||||
frame->context = last_frame->context;
|
||||
frame->context.srr0 = instruction;
|
||||
frame->context.gpr[1] = stack_pointer;
|
||||
frame->context_validity = StackFramePPC::CONTEXT_VALID_SRR0 |
|
||||
StackFramePPC::CONTEXT_VALID_GPR1;
|
||||
|
||||
// frame->context.srr0 is the return address, which is one instruction
|
||||
// past the branch that caused us to arrive at the callee. Set
|
||||
// frame_ppc->instruction to four less than that. Since all ppc
|
||||
// instructions are 4 bytes wide, this is the address of the branch
|
||||
// instruction. This allows source line information to match up with the
|
||||
// line that contains a function call. Callers that require the exact
|
||||
// return address value may access the context.srr0 field of StackFramePPC.
|
||||
frame->instruction = frame->context.srr0 - 4;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,9 +64,8 @@ class StackwalkerPPC : public Stackwalker {
|
|||
// Implementation of Stackwalker, using ppc context (stack pointer in %r1,
|
||||
// saved program counter in %srr0) and stack conventions (saved stack
|
||||
// pointer at 0(%r1), return address at 8(0(%r1)).
|
||||
virtual bool GetContextFrame(StackFrame *frame);
|
||||
virtual bool GetCallerFrame(StackFrame *frame,
|
||||
const StackFrames *walked_frames);
|
||||
virtual StackFrame* GetContextFrame();
|
||||
virtual StackFrame* GetCallerFrame(const CallStack *stack);
|
||||
|
||||
// Stores the CPU context corresponding to the innermost stack frame to
|
||||
// be returned by GetContextFrame.
|
||||
|
|
|
|||
|
|
@ -40,13 +40,17 @@
|
|||
#include <cstdio>
|
||||
|
||||
#include "google/airbag_types.h"
|
||||
#include "google/call_stack.h"
|
||||
#include "google/stack_frame.h"
|
||||
#include "google/stack_frame_cpu.h"
|
||||
#include "processor/memory_region.h"
|
||||
#include "processor/minidump_format.h"
|
||||
|
||||
using google_airbag::CallStack;
|
||||
using google_airbag::MemoryRegion;
|
||||
using google_airbag::StackFrame;
|
||||
using google_airbag::StackFrames;
|
||||
using google_airbag::StackFramePPC;
|
||||
using google_airbag::StackFrameX86;
|
||||
|
||||
#if defined(__i386__)
|
||||
#include "processor/stackwalker_x86.h"
|
||||
|
|
@ -78,6 +82,14 @@ class SelfMemoryRegion : public MemoryRegion {
|
|||
private:
|
||||
template<typename T> bool GetMemoryAtAddressInternal(u_int64_t address,
|
||||
T* value) {
|
||||
// Without knowing what addresses are actually mapped, just assume that
|
||||
// everything low is not mapped. This helps the stackwalker catch the
|
||||
// end of a stack when it tries to dereference a null or low pointer
|
||||
// in an attempt to find the caller frame. Other unmapped accesses will
|
||||
// cause the program to crash, but that would properly be a test failure.
|
||||
if (address < 0x100)
|
||||
return false;
|
||||
|
||||
u_int8_t* memory = 0;
|
||||
*value = *reinterpret_cast<const T*>(&memory[address]);
|
||||
return true;
|
||||
|
|
@ -105,6 +117,22 @@ static u_int32_t GetEBP() {
|
|||
}
|
||||
|
||||
|
||||
// The caller's %esp is 8 higher than the value of %ebp in this function,
|
||||
// assuming that it's not inlined and that the standard prolog is used.
|
||||
// The CALL instruction places a 4-byte return address on the stack above
|
||||
// the caller's %esp, and this function's prolog will save the caller's %ebp
|
||||
// on the stack as well, for another 4 bytes, before storing %esp in %ebp.
|
||||
static u_int32_t GetESP() __attribute__((noinline));
|
||||
static u_int32_t GetESP() {
|
||||
u_int32_t ebp;
|
||||
__asm__ __volatile__(
|
||||
"movl %%ebp, %0"
|
||||
: "=a" (ebp)
|
||||
);
|
||||
return ebp + 8;
|
||||
}
|
||||
|
||||
|
||||
// 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.
|
||||
|
|
@ -177,6 +205,7 @@ static unsigned int CountCallerFrames() {
|
|||
MDRawContextX86 context = MDRawContextX86();
|
||||
context.eip = GetEIP();
|
||||
context.ebp = GetEBP();
|
||||
context.esp = GetESP();
|
||||
|
||||
StackwalkerX86 stackwalker = StackwalkerX86(&context, &memory, NULL, NULL);
|
||||
#elif defined(__ppc__)
|
||||
|
|
@ -187,24 +216,32 @@ static unsigned int CountCallerFrames() {
|
|||
StackwalkerPPC stackwalker = StackwalkerPPC(&context, &memory, NULL, NULL);
|
||||
#endif // __i386__ || __ppc__
|
||||
|
||||
StackFrames stack;
|
||||
CallStack stack;
|
||||
stackwalker.Walk(&stack);
|
||||
|
||||
#ifdef PRINT_STACKS
|
||||
printf("\n");
|
||||
for(unsigned int frame_index = 0;
|
||||
frame_index < stack.size();
|
||||
frame_index < stack.Count();
|
||||
++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);
|
||||
StackFrame *frame = stack.FrameAt(frame_index);
|
||||
printf("frame %-3d instruction = 0x%08llx",
|
||||
frame_index, frame->instruction);
|
||||
#if defined(__i386__)
|
||||
StackFrameX86 *frame_x86 = reinterpret_cast<StackFrameX86*>(frame.get());
|
||||
printf(" esp = 0x%08x ebp = 0x%08x\n",
|
||||
frame_x86->context.esp, frame_x86->context.ebp);
|
||||
#elif defined(__ppc__)
|
||||
StackFramePPC *frame_ppc = reinterpret_cast<StackFramePPC*>(frame.get());
|
||||
printf(" gpr[1] = 0x%08x\n", frame_ppc->context.gpr[1]);
|
||||
#endif // __i386__ || __ppc__
|
||||
}
|
||||
#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;
|
||||
return stack.frames()->size() - 2;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,10 @@
|
|||
|
||||
|
||||
#include "processor/stackwalker_x86.h"
|
||||
#include "google/call_stack.h"
|
||||
#include "google/stack_frame_cpu.h"
|
||||
#include "processor/minidump.h"
|
||||
#include "processor/postfix_evaluator-inl.h"
|
||||
|
||||
namespace google_airbag {
|
||||
|
||||
|
|
@ -54,63 +57,249 @@ StackwalkerX86::StackwalkerX86(const MDRawContextX86 *context,
|
|||
}
|
||||
|
||||
|
||||
bool StackwalkerX86::GetContextFrame(StackFrame *frame) {
|
||||
if (!context_ || !memory_ || !frame)
|
||||
return false;
|
||||
StackFrame* StackwalkerX86::GetContextFrame() {
|
||||
if (!context_ || !memory_)
|
||||
return NULL;
|
||||
|
||||
// The frame and instruction pointers are stored directly in registers,
|
||||
// so pull them straight out of the CPU context structure.
|
||||
frame->frame_pointer = context_->ebp;
|
||||
frame->instruction = context_->eip;
|
||||
StackFrameX86 *frame = new StackFrameX86();
|
||||
|
||||
return true;
|
||||
// The instruction pointer is stored directly in a register, so pull it
|
||||
// straight out of the CPU context structure.
|
||||
frame->context = *context_;
|
||||
frame->context_validity = StackFrameX86::CONTEXT_VALID_ALL;
|
||||
frame->instruction = frame->context.eip;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
bool StackwalkerX86::GetCallerFrame(StackFrame *frame,
|
||||
const StackFrames *walked_frames) {
|
||||
if (!memory_ || !frame || !walked_frames)
|
||||
return false;
|
||||
StackFrame* StackwalkerX86::GetCallerFrame(const CallStack *stack) {
|
||||
if (!memory_ || !stack)
|
||||
return NULL;
|
||||
|
||||
// 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.
|
||||
StackFrameX86 *last_frame = static_cast<StackFrameX86*>(
|
||||
stack->frames()->back());
|
||||
StackFrameInfo *last_frame_info = &stack_frame_info_.back();
|
||||
|
||||
// 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).
|
||||
// This stackwalker sets each frame's %esp to its value immediately prior
|
||||
// to the CALL into the callee. This means that %esp points to the last
|
||||
// callee argument pushed onto the stack, which may not be where %esp points
|
||||
// after the callee returns. Specifically, the value is correct for the
|
||||
// cdecl calling convention, but not other conventions. The cdecl
|
||||
// convention requires a caller to pop its callee's arguments from the
|
||||
// stack after the callee returns. This is usually accomplished by adding
|
||||
// the known size of the arguments to %esp. Other calling conventions,
|
||||
// including stdcall, thiscall, and fastcall, require the callee to pop any
|
||||
// parameters stored on the stack before returning. This is usually
|
||||
// accomplished by using the RET n instruction, which pops n bytes off
|
||||
// the stack after popping the return address.
|
||||
//
|
||||
// Because each frame's %esp will point to a location on the stack after
|
||||
// callee arguments have been PUSHed, when locating things in a stack frame
|
||||
// relative to %esp, the size of the arguments to the callee need to be
|
||||
// taken into account. This seems a little bit unclean, but it's better
|
||||
// than the alternative, which would need to take these same things into
|
||||
// account, but only for cdecl functions. With this implementation, we get
|
||||
// to be agnostic about each function's calling convention. Furthermore,
|
||||
// this is how Windows debugging tools work, so it means that the %esp
|
||||
// values produced by this stackwalker directly correspond to the %esp
|
||||
// values you'll see there.
|
||||
//
|
||||
// If the last frame has no callee (because it's the context frame), just
|
||||
// set the callee parameter size to 0: the stack pointer can't point to
|
||||
// callee arguments because there's no callee. This is correct as long
|
||||
// as the context wasn't captured while arguments were being pushed for
|
||||
// a function call. Note that there may be functions whose parameter sizes
|
||||
// are unknown, 0 is also used in that case. When that happens, it should
|
||||
// be possible to walk to the next frame without reference to %esp.
|
||||
|
||||
u_int32_t last_frame_pointer = walked_frames->back().frame_pointer;
|
||||
int frames_already_walked = stack_frame_info_.size();
|
||||
u_int32_t last_frame_callee_parameter_size = 0;
|
||||
if (frames_already_walked >= 2) {
|
||||
StackFrameInfo *last_frame_callee_info =
|
||||
&stack_frame_info_[frames_already_walked - 2];
|
||||
if (last_frame_callee_info->valid & StackFrameInfo::VALID_PARAMETER_SIZE) {
|
||||
last_frame_callee_parameter_size =
|
||||
last_frame_callee_info->parameter_size;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
// Set up the dictionary for the PostfixEvaluator. %ebp and %esp are used
|
||||
// in each program string, and their previous values are known, so set them
|
||||
// here. .cbCalleeParams is an Airbag extension that allows us to use
|
||||
// the PostfixEvaluator engine when certain types of debugging information
|
||||
// are present without having to write the constants into the program string
|
||||
// as literals.
|
||||
PostfixEvaluator<u_int32_t>::DictionaryType dictionary;
|
||||
dictionary["$ebp"] = last_frame->context.ebp;
|
||||
dictionary["$esp"] = last_frame->context.esp;
|
||||
dictionary[".cbCalleeParams"] = last_frame_callee_parameter_size;
|
||||
|
||||
// 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;
|
||||
if (last_frame_info->valid == StackFrameInfo::VALID_ALL) {
|
||||
// FPO debugging data is available. Initialize constants.
|
||||
dictionary[".cbSavedRegs"] = last_frame_info->saved_register_size;
|
||||
dictionary[".cbLocals"] = last_frame_info->local_size;
|
||||
dictionary[".raSearchStart"] = last_frame->context.esp +
|
||||
last_frame_callee_parameter_size +
|
||||
last_frame_info->local_size +
|
||||
last_frame_info->saved_register_size;
|
||||
}
|
||||
if (last_frame_info->valid & StackFrameInfo::VALID_PARAMETER_SIZE) {
|
||||
// This is treated separately because it can either come from FPO data or
|
||||
// from other debugging data.
|
||||
dictionary[".cbParams"] = last_frame_info->parameter_size;
|
||||
}
|
||||
|
||||
u_int32_t instruction;
|
||||
if (!memory_->GetMemoryAtAddress(last_frame_pointer + 4, &instruction))
|
||||
return false;
|
||||
// Decide what type of program string to use. The program string is in
|
||||
// postfix notation and will be passed to PostfixEvaluator::Evaluate.
|
||||
// Given the dictionary and the program string, it is possible to compute
|
||||
// the return address and the values of other registers in the calling
|
||||
// function.
|
||||
string program_string;
|
||||
if (last_frame_info->valid == StackFrameInfo::VALID_ALL) {
|
||||
// FPO data available.
|
||||
if (!last_frame_info->program_string.empty()) {
|
||||
// The FPO data has its own program string, which will tell us how to
|
||||
// get to the caller frame, and may even fill in the values of
|
||||
// nonvolatile registers and provide pointers to local variables and
|
||||
// parameters.
|
||||
program_string = last_frame_info->program_string;
|
||||
} else if (last_frame_info->allocates_base_pointer) {
|
||||
// The function corresponding to the last frame doesn't use the frame
|
||||
// pointer for conventional purposes, but it does allocate a new
|
||||
// frame pointer and use it for its own purposes. Its callee's
|
||||
// information is still accessed relative to %esp, and the previous
|
||||
// value of %ebp can be recovered from a location in its stack frame,
|
||||
// within the saved-register area.
|
||||
//
|
||||
// Functions that fall into this category use the %ebp register for
|
||||
// a purpose other than the frame pointer. They restore the caller's
|
||||
// %ebp before returning. These functions create their stack frame
|
||||
// after a CALL by decrementing the stack pointer in an amount
|
||||
// sufficient to store local variables, and then PUSHing saved
|
||||
// registers onto the stack. Arguments to a callee function, if any,
|
||||
// are PUSHed after that. Walking up to the caller, therefore,
|
||||
// can be done solely with calculations relative to the stack pointer
|
||||
// (%esp). The return address is recovered from the memory location
|
||||
// above the known sizes of the callee's parameters, saved registers,
|
||||
// and locals. The caller's stack pointer (the value of %esp when
|
||||
// the caller executed CALL) is the location immediately above the
|
||||
// saved return address. The saved value of %ebp to be restored for
|
||||
// the caller is at a known location in the saved-register area of
|
||||
// the stack frame.
|
||||
//
|
||||
// %eip_new = *(%esp_old + callee_params + saved_regs + locals)
|
||||
// %ebp_new = *(%esp_old + callee_params + saved_regs - 8)
|
||||
// %esp_new = %esp_old + callee_params + saved_regs + locals + 4
|
||||
program_string = "$eip .raSearchStart ^ = "
|
||||
"$ebp $esp .cbCalleeParams + .cbSavedRegs + 8 - ^ = "
|
||||
"$esp .raSearchStart 4 + =";
|
||||
} else {
|
||||
// The function corresponding to the last frame doesn't use %ebp at
|
||||
// all. The callee frame is located relative to %esp. %ebp is reset
|
||||
// to itself only to cause it to appear to have been set in
|
||||
// dictionary_validity.
|
||||
//
|
||||
// The called procedure's instruction pointer and stack pointer are
|
||||
// recovered in the same way as the case above, except that no
|
||||
// frame pointer (%ebp) is used at all, so it is not saved anywhere
|
||||
// in the callee's stack frame and does not need to be recovered.
|
||||
// Because %ebp wasn't used in the callee, whatever value it has
|
||||
// is the value that it had in the caller, so it can be carried
|
||||
// straight through without bringing its validity into question.
|
||||
//
|
||||
// %eip_new = *(%esp_old + callee_params + saved_regs + locals)
|
||||
// %esp_new = %esp_old + callee_params + saved_regs + locals + 4
|
||||
// %ebp_new = %ebp_old
|
||||
program_string = "$eip .raSearchStart ^ = "
|
||||
"$esp .raSearchStart 4 + = "
|
||||
"$ebp $ebp =";
|
||||
}
|
||||
} else {
|
||||
// No FPO information is available for the last frame. Assume that the
|
||||
// standard %ebp-using x86 calling convention is in use.
|
||||
//
|
||||
// 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 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. The
|
||||
// calling procedure's stack pointer (%esp) is 8 higher than the value
|
||||
// of the called procedure's frame pointer at the time the calling
|
||||
// procedure made the CALL: 4 bytes for the return address pushed by the
|
||||
// CALL itself, and 4 bytes for the callee's PUSH of the caller's frame
|
||||
// pointer.
|
||||
//
|
||||
// %eip_new = *(%ebp_old + 4)
|
||||
// %esp_new = %ebp_old + 8
|
||||
// %ebp_new = *(%ebp_old)
|
||||
program_string = "$eip $ebp 4 + ^ = "
|
||||
"$esp $ebp 8 + = "
|
||||
"$ebp $ebp ^ =";
|
||||
}
|
||||
|
||||
frame->frame_pointer = frame_pointer;
|
||||
frame->instruction = instruction;
|
||||
// Now crank it out, making sure that the program string set the three
|
||||
// required variables.
|
||||
PostfixEvaluator<u_int32_t> evaluator =
|
||||
PostfixEvaluator<u_int32_t>(&dictionary, memory_);
|
||||
PostfixEvaluator<u_int32_t>::DictionaryValidityType dictionary_validity;
|
||||
if (!evaluator.Evaluate(program_string, &dictionary_validity) ||
|
||||
dictionary_validity.find("$eip") == dictionary_validity.end() ||
|
||||
dictionary_validity.find("$esp") == dictionary_validity.end() ||
|
||||
dictionary_validity.find("$ebp") == dictionary_validity.end()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
// Treat an instruction address of 0 as end-of-stack. Treat incorrect stack
|
||||
// direction as end-of-stack to enforce progress and avoid infinite loops.
|
||||
if (dictionary["$eip"] == 0 ||
|
||||
dictionary["$esp"] <= last_frame->context.esp) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create a new stack frame (ownership will be transferred to the caller)
|
||||
// and fill it in.
|
||||
StackFrameX86 *frame = new StackFrameX86();
|
||||
|
||||
frame->context = last_frame->context;
|
||||
frame->context.eip = dictionary["$eip"];
|
||||
frame->context.esp = dictionary["$esp"];
|
||||
frame->context.ebp = dictionary["$ebp"];
|
||||
frame->context_validity = StackFrameX86::CONTEXT_VALID_EIP |
|
||||
StackFrameX86::CONTEXT_VALID_ESP |
|
||||
StackFrameX86::CONTEXT_VALID_EBP;
|
||||
|
||||
// These are nonvolatile (callee-save) registers, and the program string
|
||||
// may have filled them in.
|
||||
if (dictionary_validity.find("$ebx") == dictionary_validity.end()) {
|
||||
frame->context.ebx = dictionary["$ebx"];
|
||||
frame->context_validity |= StackFrameX86::CONTEXT_VALID_EBX;
|
||||
}
|
||||
if (dictionary_validity.find("$esi") == dictionary_validity.end()) {
|
||||
frame->context.esi = dictionary["$esi"];
|
||||
frame->context_validity |= StackFrameX86::CONTEXT_VALID_ESI;
|
||||
}
|
||||
if (dictionary_validity.find("$edi") == dictionary_validity.end()) {
|
||||
frame->context.edi = dictionary["$edi"];
|
||||
frame->context_validity |= StackFrameX86::CONTEXT_VALID_EDI;
|
||||
}
|
||||
|
||||
// frame->context.eip is the return address, which is one instruction
|
||||
// past the CALL that caused us to arrive at the callee. Set
|
||||
// frame->instruction to one less than that. This won't reference the
|
||||
// beginning of the CALL instruction, but it's guaranteed to be within the
|
||||
// CALL, which is sufficient to get the source line information to match up
|
||||
// with the line that contains a function call. Callers that require the
|
||||
// exact return address value may access the context.eip field of
|
||||
// StackFrameX86.
|
||||
frame->instruction = frame->context.eip - 1;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -61,11 +61,11 @@ class StackwalkerX86 : public Stackwalker {
|
|||
SymbolSupplier *supplier);
|
||||
|
||||
private:
|
||||
// Implementation of Stackwalker, using x86 context (%ebp, %eip) and
|
||||
// stack conventions (saved %ebp at [%ebp], saved %eip at 4[%ebp]).
|
||||
virtual bool GetContextFrame(StackFrame *frame);
|
||||
virtual bool GetCallerFrame(StackFrame *frame,
|
||||
const StackFrames *walked_frames);
|
||||
// Implementation of Stackwalker, using x86 context (%ebp, %esp, %eip) and
|
||||
// stack conventions (saved %ebp at [%ebp], saved %eip at 4[%ebp], or
|
||||
// alternate conventions as guided by stack_frame_info_).
|
||||
virtual StackFrame* GetContextFrame();
|
||||
virtual StackFrame* GetCallerFrame(const CallStack *stack);
|
||||
|
||||
// Stores the CPU context corresponding to the innermost stack frame to
|
||||
// be returned by GetContextFrame.
|
||||
|
|
|
|||
46
src/processor/testdata/minidump1.stack.out
vendored
46
src/processor/testdata/minidump1.stack.out
vendored
|
|
@ -1,23 +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
|
||||
[ 0] instruction = 0x020a1515 "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x00221515
|
||||
[ 1] instruction = 0x020a03e2 "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x002203e2
|
||||
[ 2] instruction = 0x023c8a27 "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x00548a27
|
||||
[ 3] instruction = 0x023ccfd8 "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x0054cfd8
|
||||
[ 4] instruction = 0x0222fd11 "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x003afd11
|
||||
[ 5] instruction = 0x022311dc "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x003b11dc
|
||||
[ 6] instruction = 0x034eb0f0 "c:\lizard\trunk\mozilla\dist\bin\components\xpc3250.dll" + 0x0005b0f0
|
||||
[ 7] instruction = 0x0049bda3 "c:\lizard\trunk\mozilla\dist\bin\js3250.dll" + 0x0007bda3
|
||||
[ 8] instruction = 0x0047b92e "c:\lizard\trunk\mozilla\dist\bin\js3250.dll" + 0x0005b92e
|
||||
[ 9] instruction = 0x0046c944 "c:\lizard\trunk\mozilla\dist\bin\js3250.dll" + 0x0004c944
|
||||
[10] instruction = 0x0046d344 "c:\lizard\trunk\mozilla\dist\bin\js3250.dll" + 0x0004d344
|
||||
[11] instruction = 0x00430ec2 "c:\lizard\trunk\mozilla\dist\bin\js3250.dll" + 0x00010ec2
|
||||
[12] instruction = 0x02213b7e "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x00393b7e
|
||||
[13] instruction = 0x02249cec "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x003c9cec
|
||||
[14] instruction = 0x0224a80f "c:\lizard\trunk\mozilla\dist\bin\components\gklayout.dll" + 0x003ca80f
|
||||
[15] instruction = 0x002ebff7 "c:\lizard\trunk\mozilla\dist\bin\xpcom_core.dll" + 0x0007bff7
|
||||
[16] instruction = 0x002ec8eb "c:\lizard\trunk\mozilla\dist\bin\xpcom_core.dll" + 0x0007c8eb
|
||||
[17] instruction = 0x029193b4 "c:\lizard\trunk\mozilla\dist\bin\components\gkwidget.dll" + 0x000293b4
|
||||
[18] instruction = 0x03174b18 "c:\lizard\trunk\mozilla\dist\bin\components\tkitcmps.dll" + 0x00004b18
|
||||
[19] instruction = 0x10008e5f "c:\lizard\trunk\mozilla\dist\bin\xul.dll" + 0x00008e5f
|
||||
[20] instruction = 0x00401035 "c:\lizard\trunk\mozilla\dist\bin\firefox.exe" + 0x00001035
|
||||
[21] instruction = 0x004011bb "c:\lizard\trunk\mozilla\dist\bin\firefox.exe" + 0x000011bb
|
||||
[22] instruction = 0x7c816d4e "C:\WINDOWS\system32\kernel32.dll" + 0x00016d4e
|
||||
|
|
|
|||
BIN
src/processor/testdata/minidump2.dmp
vendored
BIN
src/processor/testdata/minidump2.dmp
vendored
Binary file not shown.
2704
src/processor/testdata/minidump2.sym
vendored
2704
src/processor/testdata/minidump2.sym
vendored
File diff suppressed because it is too large
Load diff
16
src/processor/testdata/module1.out
vendored
16
src/processor/testdata/module1.out
vendored
|
|
@ -1,16 +1,16 @@
|
|||
FILE 1 file1_1.cc
|
||||
FILE 2 file1_2.cc
|
||||
FILE 3 file1_3.cc
|
||||
FUNC 1000 c Function1_1
|
||||
FUNC 1000 c 0 Function1_1
|
||||
1000 4 44 1
|
||||
1004 4 45 1
|
||||
1008 4 46 1
|
||||
FUNC 1100 8 Function1_2
|
||||
FUNC 1100 8 4 Function1_2
|
||||
1100 4 65 2
|
||||
1104 4 66 2
|
||||
FUNC 1200 100 Function1_3
|
||||
FUNC 1300 100 Function1_4
|
||||
STACK WIN 4 1000 c 1 0 0 0 0 0 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
STACK WIN 4 1100 8 1 0 0 0 0 0 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
STACK WIN 4 1100 100 1 0 0 0 0 0 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
STACK WIN 4 1300 100 1 0 0 0 0 0 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
FUNC 1200 100 8 Function1_3
|
||||
FUNC 1300 100 c Function1_4
|
||||
STACK WIN 4 1000 c 1 0 0 0 0 0 1 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
STACK WIN 4 1100 8 1 0 0 0 0 0 1 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
STACK WIN 4 1100 100 1 0 0 0 0 0 1 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
STACK WIN 4 1300 100 1 0 0 0 0 0 1 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
|
|
|
|||
8
src/processor/testdata/module2.out
vendored
8
src/processor/testdata/module2.out
vendored
|
|
@ -1,14 +1,14 @@
|
|||
FILE 1 file2_1.cc
|
||||
FILE 2 file2_2.cc
|
||||
FILE 3 file2_3.cc
|
||||
FUNC 2000 c Function2_1
|
||||
FUNC 2000 c 4 Function2_1
|
||||
1000 4 54 1
|
||||
1004 4 55 1
|
||||
1008 4 56 1
|
||||
FUNC 2170 14 Function2_2
|
||||
FUNC 2170 14 4 Function2_2
|
||||
2170 6 10 2
|
||||
2176 4 12 2
|
||||
217a 6 13 2
|
||||
2180 4 21 2
|
||||
STACK WIN 4 2000 c 1 0 0 0 0 0 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
STACK WIN 4 2170 14 1 0 0 0 0 0 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
STACK WIN 4 2000 c 1 0 0 0 0 0 1 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
STACK WIN 4 2170 14 1 0 0 0 0 0 1 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue