Provide a mechanism for SymbolSuppliers to interrupt processing (#93)

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@80 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
bryner 2006-12-08 04:13:51 +00:00
parent 283fd39248
commit f33b8d2d07
13 changed files with 174 additions and 96 deletions

View File

@ -56,17 +56,18 @@ template<typename T> class linked_ptr;
class CallStack { class CallStack {
public: public:
CallStack() { Clear(); }
~CallStack(); ~CallStack();
// Resets the CallStack to its initial empty state
void Clear();
const vector<StackFrame*>* frames() const { return &frames_; } const vector<StackFrame*>* frames() const { return &frames_; }
private: private:
// Stackwalker is responsible for building the frames_ vector. // Stackwalker is responsible for building the frames_ vector.
friend class Stackwalker; friend class Stackwalker;
// Disallow instantiation other than by friends.
CallStack() : frames_() {}
// Storage for pushed frames. // Storage for pushed frames.
vector<StackFrame*> frames_; vector<StackFrame*> frames_;
}; };

View File

@ -42,15 +42,21 @@ class SymbolSupplier;
class MinidumpProcessor { class MinidumpProcessor {
public: public:
// Return type for Process()
enum ProcessResult {
PROCESS_OK, // the minidump was processed successfully
PROCESS_ERROR, // there was an error processing the minidump
PROCESS_INTERRUPTED, // processing was interrupted by the SymbolSupplier
};
// Initializes this MinidumpProcessor. supplier should be an // Initializes this MinidumpProcessor. supplier should be an
// implementation of the SymbolSupplier abstract base class. // implementation of the SymbolSupplier abstract base class.
explicit MinidumpProcessor(SymbolSupplier *supplier); explicit MinidumpProcessor(SymbolSupplier *supplier);
~MinidumpProcessor(); ~MinidumpProcessor();
// Returns a new ProcessState object produced by processing the minidump // Processes the minidump file and fills process_state with the result.
// file. The caller takes ownership of the ProcessState. Returns NULL on ProcessResult Process(const string &minidump_file,
// failure. ProcessState *process_state);
ProcessState* Process(const string &minidump_file);
// Returns a textual representation of the base CPU type that the minidump // Returns a textual representation of the base CPU type that the minidump
// in dump was produced on. Returns an empty string if this information // in dump was produced on. Returns an empty string if this information

View File

@ -47,8 +47,12 @@ class CodeModules;
class ProcessState { class ProcessState {
public: public:
ProcessState() : modules_(NULL) { Clear(); }
~ProcessState(); ~ProcessState();
// Resets the ProcessState to its default values
void Clear();
// Accessors. See the data declarations below. // Accessors. See the data declarations below.
u_int32_t time_date_stamp() const { return time_date_stamp_; } u_int32_t time_date_stamp() const { return time_date_stamp_; }
bool crashed() const { return crashed_; } bool crashed() const { return crashed_; }
@ -66,11 +70,6 @@ class ProcessState {
// MinidumpProcessor is responsible for building ProcessState objects. // MinidumpProcessor is responsible for building ProcessState objects.
friend class MinidumpProcessor; friend class MinidumpProcessor;
// Disallow instantiation other than by friends.
ProcessState() : time_date_stamp_(0), crashed_(false), crash_reason_(),
crash_address_(0), requesting_thread_(-1), threads_(),
os_(), os_version_(), cpu_(), cpu_info_(), modules_(NULL) {}
// The time-date stamp of the minidump (time_t format) // The time-date stamp of the minidump (time_t format)
u_int32_t time_date_stamp_; u_int32_t time_date_stamp_;

View File

@ -61,10 +61,11 @@ class Stackwalker {
public: public:
virtual ~Stackwalker() {} virtual ~Stackwalker() {}
// Creates a new CallStack and populates it by calling GetContextFrame and // Populates the given CallStack by calling GetContextFrame and
// GetCallerFrame. The frames are further processed to fill all available // GetCallerFrame. The frames are further processed to fill all available
// data. The caller takes ownership of the CallStack returned by Walk. // data. Returns true if the stackwalk completed, or false if it was
CallStack* Walk(); // interrupted by SymbolSupplier::GetSymbolFile().
bool Walk(CallStack *stack);
// Returns a new concrete subclass suitable for the CPU that a stack was // Returns a new concrete subclass suitable for the CPU that a stack was
// generated on, according to the CPU type indicated by the context // generated on, according to the CPU type indicated by the context

View File

@ -42,10 +42,24 @@ class CodeModule;
class SymbolSupplier { class SymbolSupplier {
public: public:
// Result type for GetSymbolFile
enum SymbolResult {
// no symbols were found, but continue processing
NOT_FOUND,
// symbols were found, and the path has been placed in symbol_file
FOUND,
// stops processing the minidump immediately
INTERRUPT,
};
virtual ~SymbolSupplier() {} virtual ~SymbolSupplier() {}
// Returns the path to the symbol file for the given module. // Retrieves the symbol file for the given CodeModule, placing the
virtual string GetSymbolFile(const CodeModule *module) = 0; // path in symbol_file if successful.
virtual SymbolResult GetSymbolFile(const CodeModule *module,
string *symbol_file) = 0;
}; };
} // namespace google_airbag } // namespace google_airbag

View File

@ -39,6 +39,10 @@
namespace google_airbag { namespace google_airbag {
CallStack::~CallStack() { CallStack::~CallStack() {
Clear();
}
void CallStack::Clear() {
for (vector<StackFrame *>::const_iterator iterator = frames_.begin(); for (vector<StackFrame *>::const_iterator iterator = frames_.begin();
iterator != frames_.end(); iterator != frames_.end();
++iterator) { ++iterator) {

View File

@ -45,13 +45,14 @@ MinidumpProcessor::MinidumpProcessor(SymbolSupplier *supplier)
MinidumpProcessor::~MinidumpProcessor() { MinidumpProcessor::~MinidumpProcessor() {
} }
ProcessState* MinidumpProcessor::Process(const string &minidump_file) { MinidumpProcessor::ProcessResult MinidumpProcessor::Process(
const string &minidump_file, ProcessState *process_state) {
Minidump dump(minidump_file); Minidump dump(minidump_file);
if (!dump.Read()) { if (!dump.Read()) {
return NULL; return PROCESS_ERROR;
} }
scoped_ptr<ProcessState> process_state(new ProcessState()); process_state->Clear();
const MDRawHeader *header = dump.header(); const MDRawHeader *header = dump.header();
assert(header); assert(header);
@ -91,7 +92,7 @@ ProcessState* MinidumpProcessor::Process(const string &minidump_file) {
MinidumpThreadList *threads = dump.GetThreadList(); MinidumpThreadList *threads = dump.GetThreadList();
if (!threads) { if (!threads) {
return NULL; return PROCESS_ERROR;
} }
bool found_requesting_thread = false; bool found_requesting_thread = false;
@ -101,12 +102,12 @@ ProcessState* MinidumpProcessor::Process(const string &minidump_file) {
++thread_index) { ++thread_index) {
MinidumpThread *thread = threads->GetThreadAtIndex(thread_index); MinidumpThread *thread = threads->GetThreadAtIndex(thread_index);
if (!thread) { if (!thread) {
return NULL; return PROCESS_ERROR;
} }
u_int32_t thread_id; u_int32_t thread_id;
if (!thread->GetThreadID(&thread_id)) { if (!thread->GetThreadID(&thread_id)) {
return NULL; return PROCESS_ERROR;
} }
// If this thread is the thread that produced the minidump, don't process // If this thread is the thread that produced the minidump, don't process
@ -122,7 +123,7 @@ ProcessState* MinidumpProcessor::Process(const string &minidump_file) {
if (has_requesting_thread && thread_id == requesting_thread_id) { if (has_requesting_thread && thread_id == requesting_thread_id) {
if (found_requesting_thread) { if (found_requesting_thread) {
// There can't be more than one requesting thread. // There can't be more than one requesting thread.
return NULL; return PROCESS_ERROR;
} }
// Use processed_state->threads_.size() instead of thread_index. // Use processed_state->threads_.size() instead of thread_index.
@ -148,7 +149,7 @@ ProcessState* MinidumpProcessor::Process(const string &minidump_file) {
MinidumpMemoryRegion *thread_memory = thread->GetMemory(); MinidumpMemoryRegion *thread_memory = thread->GetMemory();
if (!thread_memory) { if (!thread_memory) {
return NULL; return PROCESS_ERROR;
} }
// Use process_state->modules_ instead of module_list, because the // Use process_state->modules_ instead of module_list, because the
@ -165,23 +166,22 @@ ProcessState* MinidumpProcessor::Process(const string &minidump_file) {
process_state->modules_, process_state->modules_,
supplier_)); supplier_));
if (!stackwalker.get()) { if (!stackwalker.get()) {
return NULL; return PROCESS_ERROR;
} }
scoped_ptr<CallStack> stack(stackwalker->Walk()); scoped_ptr<CallStack> stack(new CallStack());
if (!stack.get()) { if (!stackwalker->Walk(stack.get())) {
return NULL; return PROCESS_INTERRUPTED;
} }
process_state->threads_.push_back(stack.release()); process_state->threads_.push_back(stack.release());
} }
// If a requesting thread was indicated, it must be present. // If a requesting thread was indicated, it must be present.
if (has_requesting_thread && !found_requesting_thread) { if (has_requesting_thread && !found_requesting_thread) {
return NULL; return PROCESS_ERROR;
} }
return process_state.release(); return PROCESS_OK;
} }
// Returns the MDRawSystemInfo from a minidump, or NULL if system info is // Returns the MDRawSystemInfo from a minidump, or NULL if system info is

View File

@ -62,18 +62,33 @@ using google_airbag::SymbolSupplier;
class TestSymbolSupplier : public SymbolSupplier { class TestSymbolSupplier : public SymbolSupplier {
public: public:
virtual string GetSymbolFile(const CodeModule *module); TestSymbolSupplier() : interrupt_(false) {}
virtual SymbolResult GetSymbolFile(const CodeModule *module,
string *symbol_file);
// When set to true, causes the SymbolSupplier to return INTERRUPT
void set_interrupt(bool interrupt) { interrupt_ = interrupt; }
private:
bool interrupt_;
}; };
string TestSymbolSupplier::GetSymbolFile(const CodeModule *module) { SymbolSupplier::SymbolResult TestSymbolSupplier::GetSymbolFile(
if (module && module->code_file() == "C:\\test_app.exe") { const CodeModule *module, string *symbol_file) {
return string(getenv("srcdir") ? getenv("srcdir") : ".") + if (interrupt_) {
"/src/processor/testdata/symbols/test_app.pdb/" + return INTERRUPT;
module->debug_identifier() +
"/test_app.sym";
} }
return ""; if (module && module->code_file() == "C:\\test_app.exe") {
*symbol_file = string(getenv("srcdir") ? getenv("srcdir") : ".") +
"/src/processor/testdata/symbols/test_app.pdb/" +
module->debug_identifier() +
"/test_app.sym";
return FOUND;
}
return NOT_FOUND;
} }
static bool RunTests() { static bool RunTests() {
@ -83,19 +98,20 @@ static bool RunTests() {
string minidump_file = string(getenv("srcdir") ? getenv("srcdir") : ".") + string minidump_file = string(getenv("srcdir") ? getenv("srcdir") : ".") +
"/src/processor/testdata/minidump2.dmp"; "/src/processor/testdata/minidump2.dmp";
scoped_ptr<ProcessState> state(processor.Process(minidump_file)); ProcessState state;
ASSERT_TRUE(state.get()); ASSERT_EQ(processor.Process(minidump_file, &state),
ASSERT_EQ(state->cpu(), "x86"); MinidumpProcessor::PROCESS_OK);
ASSERT_EQ(state->cpu_info(), "GenuineIntel family 6 model 13 stepping 8"); ASSERT_EQ(state.cpu(), "x86");
ASSERT_EQ(state->os(), "Windows NT"); ASSERT_EQ(state.cpu_info(), "GenuineIntel family 6 model 13 stepping 8");
ASSERT_EQ(state->os_version(), "5.1.2600 Service Pack 2"); ASSERT_EQ(state.os(), "Windows NT");
ASSERT_TRUE(state->crashed()); ASSERT_EQ(state.os_version(), "5.1.2600 Service Pack 2");
ASSERT_EQ(state->crash_reason(), "EXCEPTION_ACCESS_VIOLATION"); ASSERT_TRUE(state.crashed());
ASSERT_EQ(state->crash_address(), 0x45); ASSERT_EQ(state.crash_reason(), "EXCEPTION_ACCESS_VIOLATION");
ASSERT_EQ(state->threads()->size(), 1); ASSERT_EQ(state.crash_address(), 0x45);
ASSERT_EQ(state->requesting_thread(), 0); ASSERT_EQ(state.threads()->size(), 1);
ASSERT_EQ(state.requesting_thread(), 0);
CallStack *stack = state->threads()->at(0); CallStack *stack = state.threads()->at(0);
ASSERT_TRUE(stack); ASSERT_TRUE(stack);
ASSERT_EQ(stack->frames()->size(), 4); ASSERT_EQ(stack->frames()->size(), 4);
@ -131,17 +147,23 @@ static bool RunTests() {
ASSERT_TRUE(stack->frames()->at(3)->source_file_name.empty()); ASSERT_TRUE(stack->frames()->at(3)->source_file_name.empty());
ASSERT_EQ(stack->frames()->at(3)->source_line, 0); ASSERT_EQ(stack->frames()->at(3)->source_line, 0);
ASSERT_EQ(state->modules()->module_count(), 13); ASSERT_EQ(state.modules()->module_count(), 13);
ASSERT_TRUE(state->modules()->GetMainModule()); ASSERT_TRUE(state.modules()->GetMainModule());
ASSERT_EQ(state->modules()->GetMainModule()->code_file(), "C:\\test_app.exe"); ASSERT_EQ(state.modules()->GetMainModule()->code_file(), "C:\\test_app.exe");
ASSERT_FALSE(state->modules()->GetModuleForAddress(0)); ASSERT_FALSE(state.modules()->GetModuleForAddress(0));
ASSERT_EQ(state->modules()->GetMainModule(), ASSERT_EQ(state.modules()->GetMainModule(),
state->modules()->GetModuleForAddress(0x400000)); state.modules()->GetModuleForAddress(0x400000));
ASSERT_EQ(state->modules()->GetModuleForAddress(0x7c801234)->debug_file(), ASSERT_EQ(state.modules()->GetModuleForAddress(0x7c801234)->debug_file(),
"kernel32.pdb"); "kernel32.pdb");
ASSERT_EQ(state->modules()->GetModuleForAddress(0x77d43210)->version(), ASSERT_EQ(state.modules()->GetModuleForAddress(0x77d43210)->version(),
"5.1.2600.2622"); "5.1.2600.2622");
// Test that the symbol supplier can interrupt processing
state.Clear();
supplier.set_interrupt(true);
ASSERT_EQ(processor.Process(minidump_file, &state),
MinidumpProcessor::PROCESS_INTERRUPTED);
return true; return true;
} }

View File

@ -195,18 +195,18 @@ static bool PrintMinidumpProcess(const string &minidump_file,
MinidumpProcessor minidump_processor(symbol_supplier.get()); MinidumpProcessor minidump_processor(symbol_supplier.get());
// Process the minidump. // Process the minidump.
scoped_ptr<ProcessState> process_state( ProcessState process_state;
minidump_processor.Process(minidump_file)); if (minidump_processor.Process(minidump_file, &process_state) !=
if (!process_state.get()) { MinidumpProcessor::PROCESS_OK) {
fprintf(stderr, "MinidumpProcessor::Process failed\n"); fprintf(stderr, "MinidumpProcessor::Process failed\n");
return false; return false;
} }
// Print OS and CPU information. // Print OS and CPU information.
string cpu = process_state->cpu(); string cpu = process_state.cpu();
string cpu_info = process_state->cpu_info(); string cpu_info = process_state.cpu_info();
printf("Operating system: %s\n", process_state->os().c_str()); printf("Operating system: %s\n", process_state.os().c_str());
printf(" %s\n", process_state->os_version().c_str()); printf(" %s\n", process_state.os_version().c_str());
printf("CPU: %s\n", cpu.c_str()); printf("CPU: %s\n", cpu.c_str());
if (!cpu_info.empty()) { if (!cpu_info.empty()) {
// This field is optional. // This field is optional.
@ -215,36 +215,36 @@ static bool PrintMinidumpProcess(const string &minidump_file,
printf("\n"); printf("\n");
// Print crash information. // Print crash information.
if (process_state->crashed()) { if (process_state.crashed()) {
printf("Crash reason: %s\n", process_state->crash_reason().c_str()); printf("Crash reason: %s\n", process_state.crash_reason().c_str());
printf("Crash address: 0x%llx\n", process_state->crash_address()); printf("Crash address: 0x%llx\n", process_state.crash_address());
} else { } else {
printf("No crash\n"); printf("No crash\n");
} }
// If the thread that requested the dump is known, print it first. // If the thread that requested the dump is known, print it first.
int requesting_thread = process_state->requesting_thread(); int requesting_thread = process_state.requesting_thread();
if (requesting_thread != -1) { if (requesting_thread != -1) {
printf("\n"); printf("\n");
printf("Thread %d (%s)\n", printf("Thread %d (%s)\n",
requesting_thread, requesting_thread,
process_state->crashed() ? "crashed" : process_state.crashed() ? "crashed" :
"requested dump, did not crash"); "requested dump, did not crash");
PrintStack(process_state->threads()->at(requesting_thread), cpu); PrintStack(process_state.threads()->at(requesting_thread), cpu);
} }
// Print all of the threads in the dump. // Print all of the threads in the dump.
int thread_count = process_state->threads()->size(); int thread_count = process_state.threads()->size();
for (int thread_index = 0; thread_index < thread_count; ++thread_index) { for (int thread_index = 0; thread_index < thread_count; ++thread_index) {
if (thread_index != requesting_thread) { if (thread_index != requesting_thread) {
// Don't print the crash thread again, it was already printed. // Don't print the crash thread again, it was already printed.
printf("\n"); printf("\n");
printf("Thread %d\n", thread_index); printf("Thread %d\n", thread_index);
PrintStack(process_state->threads()->at(thread_index), cpu); PrintStack(process_state.threads()->at(thread_index), cpu);
} }
} }
PrintModules(process_state->modules()); PrintModules(process_state.modules());
return true; return true;
} }

View File

@ -40,13 +40,27 @@
namespace google_airbag { namespace google_airbag {
ProcessState::~ProcessState() { ProcessState::~ProcessState() {
Clear();
}
void ProcessState::Clear() {
time_date_stamp_ = 0;
crashed_ = false;
crash_reason_.clear();
crash_address_ = 0;
requesting_thread_ = -1;
for (vector<CallStack *>::const_iterator iterator = threads_.begin(); for (vector<CallStack *>::const_iterator iterator = threads_.begin();
iterator != threads_.end(); iterator != threads_.end();
++iterator) { ++iterator) {
delete *iterator; delete *iterator;
} }
threads_.clear();
os_.clear();
os_version_.clear();
cpu_.clear();
cpu_info_.clear();
delete modules_; delete modules_;
modules_ = NULL;
} }
} // namespace google_airbag } // namespace google_airbag

View File

@ -33,16 +33,19 @@
// //
// Author: Mark Mentovai // Author: Mark Mentovai
#include <cassert>
#include "processor/simple_symbol_supplier.h" #include "processor/simple_symbol_supplier.h"
#include "google_airbag/processor/code_module.h" #include "google_airbag/processor/code_module.h"
#include "processor/pathname_stripper.h" #include "processor/pathname_stripper.h"
namespace google_airbag { namespace google_airbag {
string SimpleSymbolSupplier::GetSymbolFileAtPath(const CodeModule *module, SymbolSupplier::SymbolResult SimpleSymbolSupplier::GetSymbolFileAtPath(
const string &root_path) { const CodeModule *module, const string &root_path, string *symbol_file) {
assert(symbol_file);
if (!module) if (!module)
return ""; return NOT_FOUND;
// Start with the base path. // Start with the base path.
string path = root_path; string path = root_path;
@ -51,14 +54,14 @@ string SimpleSymbolSupplier::GetSymbolFileAtPath(const CodeModule *module,
path.append("/"); path.append("/");
string debug_file_name = PathnameStripper::File(module->debug_file()); string debug_file_name = PathnameStripper::File(module->debug_file());
if (debug_file_name.empty()) if (debug_file_name.empty())
return ""; return NOT_FOUND;
path.append(debug_file_name); path.append(debug_file_name);
// Append the identifier as a directory name. // Append the identifier as a directory name.
path.append("/"); path.append("/");
string identifier = module->debug_identifier(); string identifier = module->debug_identifier();
if (identifier.empty()) if (identifier.empty())
return ""; return NOT_FOUND;
path.append(identifier); path.append(identifier);
// Transform the debug file name into one ending in .sym. If the existing // Transform the debug file name into one ending in .sym. If the existing
@ -76,7 +79,8 @@ string SimpleSymbolSupplier::GetSymbolFileAtPath(const CodeModule *module,
} }
path.append(".sym"); path.append(".sym");
return path; *symbol_file = path;
return FOUND;
} }
} // namespace google_airbag } // namespace google_airbag

View File

@ -92,13 +92,15 @@ class SimpleSymbolSupplier : public SymbolSupplier {
// Returns the path to the symbol file for the given module. See the // Returns the path to the symbol file for the given module. See the
// description above. // description above.
virtual string GetSymbolFile(const CodeModule *module) { virtual SymbolResult GetSymbolFile(const CodeModule *module,
return GetSymbolFileAtPath(module, path_); string *symbol_file) {
return GetSymbolFileAtPath(module, path_, symbol_file);
} }
protected: protected:
string GetSymbolFileAtPath(const CodeModule *module, SymbolResult GetSymbolFileAtPath(const CodeModule *module,
const string &root_path); const string &root_path,
string *symbol_file);
private: private:
string path_; string path_;

View File

@ -34,6 +34,8 @@
// Author: Mark Mentovai // Author: Mark Mentovai
#include <cassert>
#include "google_airbag/processor/stackwalker.h" #include "google_airbag/processor/stackwalker.h"
#include "google_airbag/processor/call_stack.h" #include "google_airbag/processor/call_stack.h"
#include "google_airbag/processor/code_module.h" #include "google_airbag/processor/code_module.h"
@ -57,10 +59,10 @@ Stackwalker::Stackwalker(MemoryRegion *memory, const CodeModules *modules,
} }
CallStack* Stackwalker::Walk() { bool Stackwalker::Walk(CallStack *stack) {
assert(stack);
SourceLineResolver resolver; SourceLineResolver resolver;
stack->Clear();
scoped_ptr<CallStack> stack(new CallStack());
// stack_frame_info parallels the CallStack. The vector is passed to the // stack_frame_info parallels the CallStack. The vector is passed to the
// GetCallerFrame function. It contains information that may be helpful // GetCallerFrame function. It contains information that may be helpful
@ -87,9 +89,18 @@ CallStack* Stackwalker::Walk() {
if (module) { if (module) {
frame->module = module; frame->module = module;
if (!resolver.HasModule(frame->module->code_file()) && supplier_) { if (!resolver.HasModule(frame->module->code_file()) && supplier_) {
string symbol_file = supplier_->GetSymbolFile(module); string symbol_file;
if (!symbol_file.empty()) { SymbolSupplier::SymbolResult symbol_result =
resolver.LoadModule(frame->module->code_file(), symbol_file); supplier_->GetSymbolFile(module, &symbol_file);
switch (symbol_result) {
case SymbolSupplier::FOUND:
resolver.LoadModule(frame->module->code_file(), symbol_file);
break;
case SymbolSupplier::NOT_FOUND:
break; // nothing to do
case SymbolSupplier::INTERRUPT:
return false;
} }
} }
frame_info.reset(resolver.FillSourceLineInfo(frame.get())); frame_info.reset(resolver.FillSourceLineInfo(frame.get()));
@ -105,10 +116,10 @@ CallStack* Stackwalker::Walk() {
frame_info.reset(NULL); frame_info.reset(NULL);
// Get the next frame and take ownership. // Get the next frame and take ownership.
frame.reset(GetCallerFrame(stack.get(), stack_frame_info)); frame.reset(GetCallerFrame(stack, stack_frame_info));
} }
return stack.release(); return true;
} }