Module API (#32). r=waylonis, bryner
- Introduces a standard API for dealing with modules. MinidumpModule
is now a concrete implementation of this API. Code may interact with
single modules using the CodeModule interface, and collections of
modules using its container, the CodeModules interface.
- CodeModule is used directly by SymbolSupplier implementations and
SourceLineResolver. Reliance on the specific implementation in
MinidumpModule has been eliminated.
- Module lists are now added to ProcessState objects. Module references
in each stack frame are now pointers to objects in these module lists.
- The sample minidump_stackwalk tool prints the module list after printing
all threads' stacks.
a9c0550edd
git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@74 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
ed61ae0bbd
commit
db3342a10e
36 changed files with 1515 additions and 351 deletions
|
@ -54,6 +54,8 @@ typedef SSIZE_T ssize_t;
|
|||
#include "processor/range_map-inl.h"
|
||||
|
||||
#include "google_airbag/processor/minidump.h"
|
||||
#include "processor/basic_code_module.h"
|
||||
#include "processor/basic_code_modules.h"
|
||||
#include "processor/scoped_ptr.h"
|
||||
|
||||
|
||||
|
@ -999,11 +1001,11 @@ void MinidumpThreadList::Print() {
|
|||
|
||||
MinidumpModule::MinidumpModule(Minidump* minidump)
|
||||
: MinidumpObject(minidump),
|
||||
module_valid_(false),
|
||||
module_(),
|
||||
name_(NULL),
|
||||
cv_record_(NULL),
|
||||
misc_record_(NULL),
|
||||
debug_filename_(NULL) {
|
||||
misc_record_(NULL) {
|
||||
}
|
||||
|
||||
|
||||
|
@ -1011,7 +1013,6 @@ MinidumpModule::~MinidumpModule() {
|
|||
delete name_;
|
||||
delete cv_record_;
|
||||
delete misc_record_;
|
||||
delete debug_filename_;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1023,9 +1024,8 @@ bool MinidumpModule::Read() {
|
|||
cv_record_ = NULL;
|
||||
delete misc_record_;
|
||||
misc_record_ = NULL;
|
||||
delete debug_filename_;
|
||||
debug_filename_ = NULL;
|
||||
|
||||
module_valid_ = false;
|
||||
valid_ = false;
|
||||
|
||||
if (!minidump_->ReadBytes(&module_, MD_MODULE_SIZE))
|
||||
|
@ -1062,24 +1062,239 @@ bool MinidumpModule::Read() {
|
|||
if (module_.size_of_image == 0 || high_address < module_.base_of_image)
|
||||
return false;
|
||||
|
||||
module_valid_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool MinidumpModule::ReadAuxiliaryData() {
|
||||
if (!module_valid_)
|
||||
return false;
|
||||
|
||||
// Each module must have a name.
|
||||
name_ = minidump_->ReadString(module_.module_name_rva);
|
||||
if (!name_)
|
||||
return false;
|
||||
|
||||
// CodeView and miscellaneous debug records are only required if the
|
||||
// module indicates that they exist.
|
||||
if (module_.cv_record.data_size && !GetCVRecord())
|
||||
return false;
|
||||
|
||||
if (module_.misc_record.data_size && !GetMiscRecord())
|
||||
return false;
|
||||
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
const string* MinidumpModule::GetName() {
|
||||
string MinidumpModule::code_file() const {
|
||||
if (!valid_)
|
||||
return NULL;
|
||||
return "";
|
||||
|
||||
if (!name_)
|
||||
name_ = minidump_->ReadString(module_.module_name_rva);
|
||||
return *name_;
|
||||
}
|
||||
|
||||
return name_;
|
||||
|
||||
string MinidumpModule::code_identifier() const {
|
||||
if (!valid_)
|
||||
return "";
|
||||
|
||||
MinidumpSystemInfo *minidump_system_info = minidump_->GetSystemInfo();
|
||||
if (!minidump_system_info)
|
||||
return "";
|
||||
|
||||
const MDRawSystemInfo *raw_system_info = minidump_system_info->system_info();
|
||||
if (!raw_system_info)
|
||||
return "";
|
||||
|
||||
string identifier;
|
||||
|
||||
switch (raw_system_info->platform_id) {
|
||||
case MD_OS_WIN32_NT:
|
||||
case MD_OS_WIN32_WINDOWS: {
|
||||
char identifier_string[17];
|
||||
snprintf(identifier_string, sizeof(identifier_string), "%08x%x",
|
||||
module_.time_date_stamp, module_.size_of_image);
|
||||
identifier = identifier_string;
|
||||
break;
|
||||
}
|
||||
|
||||
case MD_OS_MAC_OS_X: {
|
||||
// TODO(mmentovai): support uuid extension if present, otherwise fall
|
||||
// back to version (from LC_ID_DYLIB?), otherwise fall back to something
|
||||
// else.
|
||||
identifier = "id";
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// Without knowing what OS generated the dump, we can't generate a good
|
||||
// identifier. Return an empty string, signalling failure.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
|
||||
string MinidumpModule::debug_file() const {
|
||||
if (!valid_)
|
||||
return "";
|
||||
|
||||
string file;
|
||||
// Prefer the CodeView record if present.
|
||||
const MDCVInfoPDB70* cv_record_70 =
|
||||
reinterpret_cast<const MDCVInfoPDB70*>(&(*cv_record_)[0]);
|
||||
if (cv_record_70) {
|
||||
if (cv_record_70->cv_signature == MD_CVINFOPDB70_SIGNATURE) {
|
||||
// GetCVRecord guarantees pdb_file_name is null-terminated.
|
||||
file = reinterpret_cast<const char*>(cv_record_70->pdb_file_name);
|
||||
} else if (cv_record_70->cv_signature == MD_CVINFOPDB20_SIGNATURE) {
|
||||
// It's actually a MDCVInfoPDB20 structure.
|
||||
const MDCVInfoPDB20* cv_record_20 =
|
||||
reinterpret_cast<const MDCVInfoPDB20*>(&(*cv_record_)[0]);
|
||||
|
||||
// GetCVRecord guarantees pdb_file_name is null-terminated.
|
||||
file = reinterpret_cast<const char*>(cv_record_20->pdb_file_name);
|
||||
}
|
||||
|
||||
// If there's a CodeView record but it doesn't match a known signature,
|
||||
// try the miscellaneous record - but it's suspicious because
|
||||
// GetCVRecord shouldn't have accepted a CodeView record that doesn't
|
||||
// match a known signature.
|
||||
}
|
||||
|
||||
if (file.empty()) {
|
||||
// No usable CodeView record. Try the miscellaneous debug record.
|
||||
const MDImageDebugMisc* misc_record =
|
||||
reinterpret_cast<const MDImageDebugMisc *>(&(*misc_record_)[0]);
|
||||
if (misc_record) {
|
||||
if (!misc_record->unicode) {
|
||||
// If it's not Unicode, just stuff it into the string. It's unclear
|
||||
// if misc_record->data is 0-terminated, so use an explicit size.
|
||||
file = string(
|
||||
reinterpret_cast<const char*>(misc_record->data),
|
||||
module_.misc_record.data_size - sizeof(MDImageDebugMisc));
|
||||
} else {
|
||||
// There's a misc_record but it encodes the debug filename in UTF-16.
|
||||
// (Actually, because miscellaneous records are so old, it's probably
|
||||
// UCS-2.) Convert it to UTF-8 for congruity with the other strings
|
||||
// that this method (and all other methods in the Minidump family)
|
||||
// return.
|
||||
|
||||
unsigned int bytes =
|
||||
module_.misc_record.data_size - sizeof(MDImageDebugMisc);
|
||||
if (bytes % 2 == 0) {
|
||||
unsigned int utf16_words = bytes / 2;
|
||||
|
||||
// UTF16ToUTF8 expects a vector<u_int16_t>, so create a temporary one
|
||||
// and copy the UTF-16 data into it.
|
||||
vector<u_int16_t> string_utf16(utf16_words);
|
||||
if (utf16_words)
|
||||
memcpy(&string_utf16[0], &misc_record->data, bytes);
|
||||
|
||||
// GetMiscRecord already byte-swapped the data[] field if it contains
|
||||
// UTF-16, so pass false as the swap argument.
|
||||
scoped_ptr<string> new_file(UTF16ToUTF8(string_utf16, false));
|
||||
file = *new_file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
string MinidumpModule::debug_identifier() const {
|
||||
if (!valid_)
|
||||
return "";
|
||||
|
||||
string identifier;
|
||||
|
||||
// Use the CodeView record if present.
|
||||
const MDCVInfoPDB70* cv_record_70 =
|
||||
reinterpret_cast<const MDCVInfoPDB70*>(&(*cv_record_)[0]);
|
||||
if (cv_record_70) {
|
||||
if (cv_record_70->cv_signature == MD_CVINFOPDB70_SIGNATURE) {
|
||||
char identifier_string[41];
|
||||
snprintf(identifier_string, sizeof(identifier_string),
|
||||
"%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%X",
|
||||
cv_record_70->signature.data1,
|
||||
cv_record_70->signature.data2,
|
||||
cv_record_70->signature.data3,
|
||||
cv_record_70->signature.data4[0],
|
||||
cv_record_70->signature.data4[1],
|
||||
cv_record_70->signature.data4[2],
|
||||
cv_record_70->signature.data4[3],
|
||||
cv_record_70->signature.data4[4],
|
||||
cv_record_70->signature.data4[5],
|
||||
cv_record_70->signature.data4[6],
|
||||
cv_record_70->signature.data4[7],
|
||||
cv_record_70->age);
|
||||
identifier = identifier_string;
|
||||
} else if (cv_record_70->cv_signature == MD_CVINFOPDB20_SIGNATURE) {
|
||||
// It's actually a MDCVInfoPDB20 structure.
|
||||
const MDCVInfoPDB20* cv_record_20 =
|
||||
reinterpret_cast<const MDCVInfoPDB20*>(&(*cv_record_)[0]);
|
||||
|
||||
char identifier_string[17];
|
||||
snprintf(identifier_string, sizeof(identifier_string),
|
||||
"%08x%x", cv_record_20->signature, cv_record_20->age);
|
||||
identifier = identifier_string;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(mmentovai): if there's no CodeView record, there might be a
|
||||
// miscellaneous debug record. It only carries a filename, though, and no
|
||||
// identifier. I'm not sure what the right thing to do for the identifier
|
||||
// is in that case, but I don't expect to find many modules without a
|
||||
// CodeView record (or some other Airbag extension structure in place of
|
||||
// a CodeView record). Treat it as an error (empty identifier) for now.
|
||||
|
||||
// TODO(mmentovai): on the Mac, provide fallbacks as in code_identifier().
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
|
||||
string MinidumpModule::version() const {
|
||||
if (!valid_)
|
||||
return "";
|
||||
|
||||
string version;
|
||||
|
||||
if (module_.version_info.signature == MD_VSFIXEDFILEINFO_SIGNATURE &&
|
||||
module_.version_info.struct_version & MD_VSFIXEDFILEINFO_VERSION) {
|
||||
char version_string[24];
|
||||
snprintf(version_string, sizeof(version_string), "%u.%u.%u.%u",
|
||||
module_.version_info.file_version_hi >> 16,
|
||||
module_.version_info.file_version_hi & 0xffff,
|
||||
module_.version_info.file_version_lo >> 16,
|
||||
module_.version_info.file_version_lo & 0xffff);
|
||||
version = version_string;
|
||||
}
|
||||
|
||||
// TODO(mmentovai): possibly support other struct types in place of
|
||||
// the one used with MD_VSFIXEDFILEINFO_SIGNATURE. We can possibly use
|
||||
// a different structure that better represents versioning facilities on
|
||||
// Mac OS X and Linux, instead of forcing them to adhere to the dotted
|
||||
// quad of 16-bit ints that Windows uses.
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
const CodeModule* MinidumpModule::Copy() const {
|
||||
return new BasicCodeModule(this);
|
||||
}
|
||||
|
||||
|
||||
const u_int8_t* MinidumpModule::GetCVRecord() {
|
||||
if (!valid_)
|
||||
if (!module_valid_)
|
||||
return NULL;
|
||||
|
||||
if (!cv_record_) {
|
||||
|
@ -1157,7 +1372,7 @@ const u_int8_t* MinidumpModule::GetCVRecord() {
|
|||
|
||||
|
||||
const MDImageDebugMisc* MinidumpModule::GetMiscRecord() {
|
||||
if (!valid_)
|
||||
if (!module_valid_)
|
||||
return NULL;
|
||||
|
||||
if (!misc_record_) {
|
||||
|
@ -1216,83 +1431,6 @@ const MDImageDebugMisc* MinidumpModule::GetMiscRecord() {
|
|||
}
|
||||
|
||||
|
||||
// This method will perform no allocation-size checking on its own; it relies
|
||||
// on GetCVRecord() and GetMiscRecord() to have made the determination that
|
||||
// the necessary structures aren't oversized.
|
||||
const string* MinidumpModule::GetDebugFilename() {
|
||||
if (!valid_)
|
||||
return NULL;
|
||||
|
||||
if (!debug_filename_) {
|
||||
// Prefer the CodeView record if present.
|
||||
const MDCVInfoPDB70* cv_record_70 =
|
||||
reinterpret_cast<const MDCVInfoPDB70*>(GetCVRecord());
|
||||
if (cv_record_70) {
|
||||
if (cv_record_70->cv_signature == MD_CVINFOPDB70_SIGNATURE) {
|
||||
// GetCVRecord guarantees pdb_file_name is null-terminated.
|
||||
debug_filename_ = new string(
|
||||
reinterpret_cast<const char*>(cv_record_70->pdb_file_name));
|
||||
|
||||
return debug_filename_;
|
||||
} else if (cv_record_70->cv_signature == MD_CVINFOPDB20_SIGNATURE) {
|
||||
// It's actually a MDCVInfoPDB20 structure.
|
||||
const MDCVInfoPDB20* cv_record_20 =
|
||||
reinterpret_cast<const MDCVInfoPDB20*>(cv_record_70);
|
||||
|
||||
// GetCVRecord guarantees pdb_file_name is null-terminated.
|
||||
debug_filename_ = new string(
|
||||
reinterpret_cast<const char*>(cv_record_20->pdb_file_name));
|
||||
|
||||
return debug_filename_;
|
||||
}
|
||||
|
||||
// If there's a CodeView record but it doesn't match either of those
|
||||
// signatures, try the miscellaneous record - but it's suspicious because
|
||||
// GetCVRecord shouldn't have returned a CodeView record that doesn't
|
||||
// match either signature.
|
||||
}
|
||||
|
||||
// No usable CodeView record. Try the miscellaneous debug record.
|
||||
const MDImageDebugMisc* misc_record = GetMiscRecord();
|
||||
if (!misc_record)
|
||||
return NULL;
|
||||
|
||||
if (!misc_record->unicode) {
|
||||
// If it's not Unicode, just stuff it into the string. It's unclear
|
||||
// if misc_record->data is 0-terminated, so use an explicit size.
|
||||
debug_filename_ = new string(
|
||||
reinterpret_cast<const char*>(misc_record->data),
|
||||
module_.misc_record.data_size - sizeof(MDImageDebugMisc));
|
||||
|
||||
return debug_filename_;
|
||||
}
|
||||
|
||||
// There's a misc_record but it encodes the debug filename in UTF-16.
|
||||
// (Actually, because miscellaneous records are so old, it's probably
|
||||
// UCS-2.) Convert it to UTF-8 for congruity with the other strings that
|
||||
// this method (and all other methods in the Minidump family) return.
|
||||
|
||||
unsigned int bytes =
|
||||
module_.misc_record.data_size - sizeof(MDImageDebugMisc);
|
||||
if (bytes % 2 != 0)
|
||||
return NULL;
|
||||
unsigned int utf16_words = bytes / 2;
|
||||
|
||||
// UTF16ToUTF8 expects a vector<u_int16_t>, so create a temporary one and
|
||||
// copy the UTF-16 data into it.
|
||||
vector<u_int16_t> string_utf16(utf16_words);
|
||||
if (utf16_words)
|
||||
memcpy(&string_utf16[0], &misc_record->data, bytes);
|
||||
|
||||
// GetMiscRecord already byte-swapped the data[] field if it contains
|
||||
// UTF-16, so pass false as the swap argument.
|
||||
debug_filename_ = UTF16ToUTF8(string_utf16, false);
|
||||
}
|
||||
|
||||
return debug_filename_;
|
||||
}
|
||||
|
||||
|
||||
void MinidumpModule::Print() {
|
||||
if (!valid_)
|
||||
return;
|
||||
|
@ -1340,11 +1478,9 @@ void MinidumpModule::Print() {
|
|||
printf(" misc_record.rva = 0x%x\n",
|
||||
module_.misc_record.rva);
|
||||
|
||||
const char* module_name = GetName()->c_str();
|
||||
if (module_name)
|
||||
printf(" (module_name) = \"%s\"\n", module_name);
|
||||
else
|
||||
printf(" (module_name) = (null)\n");
|
||||
printf(" (code_file) = \"%s\"\n", code_file().c_str());
|
||||
printf(" (code_identifier) = \"%s\"\n",
|
||||
code_identifier().c_str());
|
||||
|
||||
const MDCVInfoPDB70* cv_record =
|
||||
reinterpret_cast<const MDCVInfoPDB70*>(GetCVRecord());
|
||||
|
@ -1405,13 +1541,10 @@ void MinidumpModule::Print() {
|
|||
printf(" (misc_record) = (null)\n");
|
||||
}
|
||||
|
||||
const string* debug_filename = GetDebugFilename();
|
||||
if (debug_filename) {
|
||||
printf(" (debug_filename) = \"%s\"\n",
|
||||
debug_filename->c_str());
|
||||
} else {
|
||||
printf(" (debug_filename) = (null)\n");
|
||||
}
|
||||
printf(" (debug_file) = \"%s\"\n", debug_file().c_str());
|
||||
printf(" (debug_identifier) = \"%s\"\n",
|
||||
debug_identifier().c_str());
|
||||
printf(" (version) = \"%s\"\n", version().c_str());
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
@ -1471,6 +1604,20 @@ bool MinidumpModuleList::Read(u_int32_t expected_size) {
|
|||
// Assume that the file offset is correct after the last read.
|
||||
if (!module->Read())
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loop through the module list once more to read additional data and
|
||||
// build the range map. This is done in a second pass because
|
||||
// MinidumpModule::ReadAuxiliaryData seeks around, and if it were
|
||||
// included in the loop above, additional seeks would be needed where
|
||||
// none are now to read contiguous data.
|
||||
for (unsigned int module_index = 0;
|
||||
module_index < module_count;
|
||||
++module_index) {
|
||||
MinidumpModule* module = &(*modules)[module_index];
|
||||
|
||||
if (!module->ReadAuxiliaryData())
|
||||
return false;
|
||||
|
||||
u_int64_t base_address = module->base_address();
|
||||
u_int64_t module_size = module->size();
|
||||
|
@ -1491,16 +1638,8 @@ bool MinidumpModuleList::Read(u_int32_t expected_size) {
|
|||
}
|
||||
|
||||
|
||||
MinidumpModule* MinidumpModuleList::GetModuleAtIndex(unsigned int index)
|
||||
const {
|
||||
if (!valid_ || index >= module_count_)
|
||||
return NULL;
|
||||
|
||||
return &(*modules_)[index];
|
||||
}
|
||||
|
||||
|
||||
MinidumpModule* MinidumpModuleList::GetModuleForAddress(u_int64_t address) {
|
||||
const MinidumpModule* MinidumpModuleList::GetModuleForAddress(
|
||||
u_int64_t address) const {
|
||||
if (!valid_)
|
||||
return NULL;
|
||||
|
||||
|
@ -1512,6 +1651,43 @@ MinidumpModule* MinidumpModuleList::GetModuleForAddress(u_int64_t address) {
|
|||
}
|
||||
|
||||
|
||||
const MinidumpModule* MinidumpModuleList::GetMainModule() const {
|
||||
if (!valid_)
|
||||
return NULL;
|
||||
|
||||
// The main code module is the first one present in a minidump file's
|
||||
// MDRawModuleList.
|
||||
return GetModuleAtSequence(0);
|
||||
}
|
||||
|
||||
|
||||
const MinidumpModule* MinidumpModuleList::GetModuleAtSequence(
|
||||
unsigned int sequence) const {
|
||||
if (!valid_ || sequence >= module_count_)
|
||||
return NULL;
|
||||
|
||||
unsigned int module_index;
|
||||
if (!range_map_->RetrieveRangeAtIndex(sequence, &module_index, NULL, NULL))
|
||||
return NULL;
|
||||
|
||||
return GetModuleAtIndex(module_index);
|
||||
}
|
||||
|
||||
|
||||
const MinidumpModule* MinidumpModuleList::GetModuleAtIndex(
|
||||
unsigned int index) const {
|
||||
if (!valid_ || index >= module_count_)
|
||||
return NULL;
|
||||
|
||||
return &(*modules_)[index];
|
||||
}
|
||||
|
||||
|
||||
const CodeModules* MinidumpModuleList::Copy() const {
|
||||
return new BasicCodeModules(this);
|
||||
}
|
||||
|
||||
|
||||
void MinidumpModuleList::Print() {
|
||||
if (!valid_)
|
||||
return;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue