mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2025-07-03 09:48:19 +00:00
BUG=chromium:661037 Change-Id: Ia4da0bd9787c232a6a199cfdfccfbed60c2515c2 Reviewed-on: https://chromium-review.googlesource.com/450090 Reviewed-by: Joshua Peraza <jperaza@chromium.org>
5384 lines
170 KiB
C++
5384 lines
170 KiB
C++
// Copyright (c) 2010 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.
|
|
|
|
// minidump.cc: A minidump reader.
|
|
//
|
|
// See minidump.h for documentation.
|
|
//
|
|
// Author: Mark Mentovai
|
|
|
|
#include "google_breakpad/processor/minidump.h"
|
|
|
|
#include <assert.h>
|
|
#include <fcntl.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#else // _WIN32
|
|
#include <unistd.h>
|
|
#endif // _WIN32
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
#include "processor/range_map-inl.h"
|
|
|
|
#include "common/scoped_ptr.h"
|
|
#include "common/stdio_wrapper.h"
|
|
#include "google_breakpad/processor/dump_context.h"
|
|
#include "processor/basic_code_module.h"
|
|
#include "processor/basic_code_modules.h"
|
|
#include "processor/logging.h"
|
|
|
|
namespace google_breakpad {
|
|
|
|
|
|
using std::istream;
|
|
using std::ifstream;
|
|
using std::numeric_limits;
|
|
using std::vector;
|
|
|
|
// Returns true iff |context_size| matches exactly one of the sizes of the
|
|
// various MDRawContext* types.
|
|
// TODO(blundell): This function can be removed once
|
|
// https://bugs.chromium.org/p/google-breakpad/issues/detail?id=550 is fixed.
|
|
static bool IsContextSizeUnique(uint32_t context_size) {
|
|
int num_matching_contexts = 0;
|
|
if (context_size == sizeof(MDRawContextX86))
|
|
num_matching_contexts++;
|
|
if (context_size == sizeof(MDRawContextPPC))
|
|
num_matching_contexts++;
|
|
if (context_size == sizeof(MDRawContextPPC64))
|
|
num_matching_contexts++;
|
|
if (context_size == sizeof(MDRawContextAMD64))
|
|
num_matching_contexts++;
|
|
if (context_size == sizeof(MDRawContextSPARC))
|
|
num_matching_contexts++;
|
|
if (context_size == sizeof(MDRawContextARM))
|
|
num_matching_contexts++;
|
|
if (context_size == sizeof(MDRawContextARM64))
|
|
num_matching_contexts++;
|
|
if (context_size == sizeof(MDRawContextMIPS))
|
|
num_matching_contexts++;
|
|
return num_matching_contexts == 1;
|
|
}
|
|
|
|
//
|
|
// Swapping routines
|
|
//
|
|
// Inlining these doesn't increase code size significantly, and it saves
|
|
// a whole lot of unnecessary jumping back and forth.
|
|
//
|
|
|
|
|
|
// Swapping an 8-bit quantity is a no-op. This function is only provided
|
|
// to account for certain templatized operations that require swapping for
|
|
// wider types but handle uint8_t too
|
|
// (MinidumpMemoryRegion::GetMemoryAtAddressInternal).
|
|
static inline void Swap(uint8_t* value) {
|
|
}
|
|
|
|
|
|
// Optimization: don't need to AND the furthest right shift, because we're
|
|
// shifting an unsigned quantity. The standard requires zero-filling in this
|
|
// case. If the quantities were signed, a bitmask whould be needed for this
|
|
// right shift to avoid an arithmetic shift (which retains the sign bit).
|
|
// The furthest left shift never needs to be ANDed bitmask.
|
|
|
|
|
|
static inline void Swap(uint16_t* value) {
|
|
*value = (*value >> 8) |
|
|
(*value << 8);
|
|
}
|
|
|
|
|
|
static inline void Swap(uint32_t* value) {
|
|
*value = (*value >> 24) |
|
|
((*value >> 8) & 0x0000ff00) |
|
|
((*value << 8) & 0x00ff0000) |
|
|
(*value << 24);
|
|
}
|
|
|
|
|
|
static inline void Swap(uint64_t* value) {
|
|
uint32_t* value32 = reinterpret_cast<uint32_t*>(value);
|
|
Swap(&value32[0]);
|
|
Swap(&value32[1]);
|
|
uint32_t temp = value32[0];
|
|
value32[0] = value32[1];
|
|
value32[1] = temp;
|
|
}
|
|
|
|
|
|
// Given a pointer to a 128-bit int in the minidump data, set the "low"
|
|
// and "high" fields appropriately.
|
|
static void Normalize128(uint128_struct* value, bool is_big_endian) {
|
|
// The struct format is [high, low], so if the format is big-endian,
|
|
// the most significant bytes will already be in the high field.
|
|
if (!is_big_endian) {
|
|
uint64_t temp = value->low;
|
|
value->low = value->high;
|
|
value->high = temp;
|
|
}
|
|
}
|
|
|
|
// This just swaps each int64 half of the 128-bit value.
|
|
// The value should also be normalized by calling Normalize128().
|
|
static void Swap(uint128_struct* value) {
|
|
Swap(&value->low);
|
|
Swap(&value->high);
|
|
}
|
|
|
|
// Swapping signed integers
|
|
static inline void Swap(int32_t* value) {
|
|
Swap(reinterpret_cast<uint32_t*>(value));
|
|
}
|
|
|
|
static inline void Swap(MDLocationDescriptor* location_descriptor) {
|
|
Swap(&location_descriptor->data_size);
|
|
Swap(&location_descriptor->rva);
|
|
}
|
|
|
|
|
|
static inline void Swap(MDMemoryDescriptor* memory_descriptor) {
|
|
Swap(&memory_descriptor->start_of_memory_range);
|
|
Swap(&memory_descriptor->memory);
|
|
}
|
|
|
|
|
|
static inline void Swap(MDGUID* guid) {
|
|
Swap(&guid->data1);
|
|
Swap(&guid->data2);
|
|
Swap(&guid->data3);
|
|
// Don't swap guid->data4[] because it contains 8-bit quantities.
|
|
}
|
|
|
|
static inline void Swap(MDSystemTime* system_time) {
|
|
Swap(&system_time->year);
|
|
Swap(&system_time->month);
|
|
Swap(&system_time->day_of_week);
|
|
Swap(&system_time->day);
|
|
Swap(&system_time->hour);
|
|
Swap(&system_time->minute);
|
|
Swap(&system_time->second);
|
|
Swap(&system_time->milliseconds);
|
|
}
|
|
|
|
static inline void Swap(MDXStateFeature* xstate_feature) {
|
|
Swap(&xstate_feature->offset);
|
|
Swap(&xstate_feature->size);
|
|
}
|
|
|
|
static inline void Swap(MDXStateConfigFeatureMscInfo* xstate_feature_info) {
|
|
Swap(&xstate_feature_info->size_of_info);
|
|
Swap(&xstate_feature_info->context_size);
|
|
Swap(&xstate_feature_info->enabled_features);
|
|
|
|
for (size_t i = 0; i < MD_MAXIMUM_XSTATE_FEATURES; i++) {
|
|
Swap(&xstate_feature_info->features[i]);
|
|
}
|
|
}
|
|
|
|
static inline void Swap(uint16_t* data, size_t size_in_bytes) {
|
|
size_t data_length = size_in_bytes / sizeof(data[0]);
|
|
for (size_t i = 0; i < data_length; i++) {
|
|
Swap(&data[i]);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Character conversion routines
|
|
//
|
|
|
|
|
|
// Standard wide-character conversion routines depend on the system's own
|
|
// idea of what width a wide character should be: some use 16 bits, and
|
|
// some use 32 bits. For the purposes of a minidump, wide strings are
|
|
// always represented with 16-bit UTF-16 chracters. iconv isn't available
|
|
// everywhere, and its interface varies where it is available. iconv also
|
|
// deals purely with char* pointers, so in addition to considering the swap
|
|
// parameter, a converter that uses iconv would also need to take the host
|
|
// CPU's endianness into consideration. It doesn't seems worth the trouble
|
|
// of making it a dependency when we don't care about anything but UTF-16.
|
|
static string* UTF16ToUTF8(const vector<uint16_t>& in,
|
|
bool swap) {
|
|
scoped_ptr<string> out(new string());
|
|
|
|
// Set the string's initial capacity to the number of UTF-16 characters,
|
|
// because the UTF-8 representation will always be at least this long.
|
|
// If the UTF-8 representation is longer, the string will grow dynamically.
|
|
out->reserve(in.size());
|
|
|
|
for (vector<uint16_t>::const_iterator iterator = in.begin();
|
|
iterator != in.end();
|
|
++iterator) {
|
|
// Get a 16-bit value from the input
|
|
uint16_t in_word = *iterator;
|
|
if (swap)
|
|
Swap(&in_word);
|
|
|
|
// Convert the input value (in_word) into a Unicode code point (unichar).
|
|
uint32_t unichar;
|
|
if (in_word >= 0xdc00 && in_word <= 0xdcff) {
|
|
BPLOG(ERROR) << "UTF16ToUTF8 found low surrogate " <<
|
|
HexString(in_word) << " without high";
|
|
return NULL;
|
|
} else if (in_word >= 0xd800 && in_word <= 0xdbff) {
|
|
// High surrogate.
|
|
unichar = (in_word - 0xd7c0) << 10;
|
|
if (++iterator == in.end()) {
|
|
BPLOG(ERROR) << "UTF16ToUTF8 found high surrogate " <<
|
|
HexString(in_word) << " at end of string";
|
|
return NULL;
|
|
}
|
|
uint32_t high_word = in_word;
|
|
in_word = *iterator;
|
|
if (in_word < 0xdc00 || in_word > 0xdcff) {
|
|
BPLOG(ERROR) << "UTF16ToUTF8 found high surrogate " <<
|
|
HexString(high_word) << " without low " <<
|
|
HexString(in_word);
|
|
return NULL;
|
|
}
|
|
unichar |= in_word & 0x03ff;
|
|
} else {
|
|
// The ordinary case, a single non-surrogate Unicode character encoded
|
|
// as a single 16-bit value.
|
|
unichar = in_word;
|
|
}
|
|
|
|
// Convert the Unicode code point (unichar) into its UTF-8 representation,
|
|
// appending it to the out string.
|
|
if (unichar < 0x80) {
|
|
(*out) += static_cast<char>(unichar);
|
|
} else if (unichar < 0x800) {
|
|
(*out) += 0xc0 | static_cast<char>(unichar >> 6);
|
|
(*out) += 0x80 | static_cast<char>(unichar & 0x3f);
|
|
} else if (unichar < 0x10000) {
|
|
(*out) += 0xe0 | static_cast<char>(unichar >> 12);
|
|
(*out) += 0x80 | static_cast<char>((unichar >> 6) & 0x3f);
|
|
(*out) += 0x80 | static_cast<char>(unichar & 0x3f);
|
|
} else if (unichar < 0x200000) {
|
|
(*out) += 0xf0 | static_cast<char>(unichar >> 18);
|
|
(*out) += 0x80 | static_cast<char>((unichar >> 12) & 0x3f);
|
|
(*out) += 0x80 | static_cast<char>((unichar >> 6) & 0x3f);
|
|
(*out) += 0x80 | static_cast<char>(unichar & 0x3f);
|
|
} else {
|
|
BPLOG(ERROR) << "UTF16ToUTF8 cannot represent high value " <<
|
|
HexString(unichar) << " in UTF-8";
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return out.release();
|
|
}
|
|
|
|
// Return the smaller of the number of code units in the UTF-16 string,
|
|
// not including the terminating null word, or maxlen.
|
|
static size_t UTF16codeunits(const uint16_t *string, size_t maxlen) {
|
|
size_t count = 0;
|
|
while (count < maxlen && string[count] != 0)
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
static inline void Swap(MDTimeZoneInformation* time_zone) {
|
|
Swap(&time_zone->bias);
|
|
// Skip time_zone->standard_name. No need to swap UTF-16 fields.
|
|
// The swap will be done as part of the conversion to UTF-8.
|
|
Swap(&time_zone->standard_date);
|
|
Swap(&time_zone->standard_bias);
|
|
// Skip time_zone->daylight_name. No need to swap UTF-16 fields.
|
|
// The swap will be done as part of the conversion to UTF-8.
|
|
Swap(&time_zone->daylight_date);
|
|
Swap(&time_zone->daylight_bias);
|
|
}
|
|
|
|
static void ConvertUTF16BufferToUTF8String(const uint16_t* utf16_data,
|
|
size_t max_length_in_bytes,
|
|
string* utf8_result,
|
|
bool swap) {
|
|
// Since there is no explicit byte length for each string, use
|
|
// UTF16codeunits to calculate word length, then derive byte
|
|
// length from that.
|
|
size_t max_word_length = max_length_in_bytes / sizeof(utf16_data[0]);
|
|
size_t word_length = UTF16codeunits(utf16_data, max_word_length);
|
|
if (word_length > 0) {
|
|
size_t byte_length = word_length * sizeof(utf16_data[0]);
|
|
vector<uint16_t> utf16_vector(word_length);
|
|
memcpy(&utf16_vector[0], &utf16_data[0], byte_length);
|
|
scoped_ptr<string> temp(UTF16ToUTF8(utf16_vector, swap));
|
|
if (temp.get()) {
|
|
utf8_result->assign(*temp);
|
|
}
|
|
} else {
|
|
utf8_result->clear();
|
|
}
|
|
}
|
|
|
|
|
|
// For fields that may or may not be valid, PrintValueOrInvalid will print the
|
|
// string "(invalid)" if the field is not valid, and will print the value if
|
|
// the field is valid. The value is printed as hexadecimal or decimal.
|
|
|
|
enum NumberFormat {
|
|
kNumberFormatDecimal,
|
|
kNumberFormatHexadecimal,
|
|
};
|
|
|
|
static void PrintValueOrInvalid(bool valid,
|
|
NumberFormat number_format,
|
|
uint32_t value) {
|
|
if (!valid) {
|
|
printf("(invalid)\n");
|
|
} else if (number_format == kNumberFormatDecimal) {
|
|
printf("%d\n", value);
|
|
} else {
|
|
printf("0x%x\n", value);
|
|
}
|
|
}
|
|
|
|
// Converts a time_t to a string showing the time in UTC.
|
|
string TimeTToUTCString(time_t tt) {
|
|
struct tm timestruct;
|
|
#ifdef _WIN32
|
|
gmtime_s(×truct, &tt);
|
|
#else
|
|
gmtime_r(&tt, ×truct);
|
|
#endif
|
|
|
|
char timestr[20];
|
|
int rv = strftime(timestr, 20, "%Y-%m-%d %H:%M:%S", ×truct);
|
|
if (rv == 0) {
|
|
return string();
|
|
}
|
|
|
|
return string(timestr);
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpObject
|
|
//
|
|
|
|
|
|
MinidumpObject::MinidumpObject(Minidump* minidump)
|
|
: DumpObject(),
|
|
minidump_(minidump) {
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpStream
|
|
//
|
|
|
|
|
|
MinidumpStream::MinidumpStream(Minidump* minidump)
|
|
: MinidumpObject(minidump) {
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpContext
|
|
//
|
|
|
|
|
|
MinidumpContext::MinidumpContext(Minidump* minidump)
|
|
: DumpContext(),
|
|
minidump_(minidump) {
|
|
}
|
|
|
|
MinidumpContext::~MinidumpContext() {
|
|
}
|
|
|
|
bool MinidumpContext::Read(uint32_t expected_size) {
|
|
valid_ = false;
|
|
|
|
// Certain raw context types are currently assumed to have unique sizes.
|
|
if (!IsContextSizeUnique(sizeof(MDRawContextAMD64))) {
|
|
BPLOG(ERROR) << "sizeof(MDRawContextAMD64) cannot match the size of any "
|
|
<< "other raw context";
|
|
return false;
|
|
}
|
|
if (!IsContextSizeUnique(sizeof(MDRawContextPPC64))) {
|
|
BPLOG(ERROR) << "sizeof(MDRawContextPPC64) cannot match the size of any "
|
|
<< "other raw context";
|
|
return false;
|
|
}
|
|
if (!IsContextSizeUnique(sizeof(MDRawContextARM64))) {
|
|
BPLOG(ERROR) << "sizeof(MDRawContextARM64) cannot match the size of any "
|
|
<< "other raw context";
|
|
return false;
|
|
}
|
|
|
|
FreeContext();
|
|
|
|
// First, figure out what type of CPU this context structure is for.
|
|
// For some reason, the AMD64 Context doesn't have context_flags
|
|
// at the beginning of the structure, so special case it here.
|
|
if (expected_size == sizeof(MDRawContextAMD64)) {
|
|
BPLOG(INFO) << "MinidumpContext: looks like AMD64 context";
|
|
|
|
scoped_ptr<MDRawContextAMD64> context_amd64(new MDRawContextAMD64());
|
|
if (!minidump_->ReadBytes(context_amd64.get(),
|
|
sizeof(MDRawContextAMD64))) {
|
|
BPLOG(ERROR) << "MinidumpContext could not read amd64 context";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap())
|
|
Swap(&context_amd64->context_flags);
|
|
|
|
uint32_t cpu_type = context_amd64->context_flags & MD_CONTEXT_CPU_MASK;
|
|
if (cpu_type == 0) {
|
|
if (minidump_->GetContextCPUFlagsFromSystemInfo(&cpu_type)) {
|
|
context_amd64->context_flags |= cpu_type;
|
|
} else {
|
|
BPLOG(ERROR) << "Failed to preserve the current stream position";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (cpu_type != MD_CONTEXT_AMD64) {
|
|
// TODO: Fall through to switch below.
|
|
// https://bugs.chromium.org/p/google-breakpad/issues/detail?id=550
|
|
BPLOG(ERROR) << "MinidumpContext not actually amd64 context";
|
|
return false;
|
|
}
|
|
|
|
// Do this after reading the entire MDRawContext structure because
|
|
// GetSystemInfo may seek minidump to a new position.
|
|
if (!CheckAgainstSystemInfo(cpu_type)) {
|
|
BPLOG(ERROR) << "MinidumpContext amd64 does not match system info";
|
|
return false;
|
|
}
|
|
|
|
// Normalize the 128-bit types in the dump.
|
|
// Since this is AMD64, by definition, the values are little-endian.
|
|
for (unsigned int vr_index = 0;
|
|
vr_index < MD_CONTEXT_AMD64_VR_COUNT;
|
|
++vr_index)
|
|
Normalize128(&context_amd64->vector_register[vr_index], false);
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&context_amd64->p1_home);
|
|
Swap(&context_amd64->p2_home);
|
|
Swap(&context_amd64->p3_home);
|
|
Swap(&context_amd64->p4_home);
|
|
Swap(&context_amd64->p5_home);
|
|
Swap(&context_amd64->p6_home);
|
|
// context_flags is already swapped
|
|
Swap(&context_amd64->mx_csr);
|
|
Swap(&context_amd64->cs);
|
|
Swap(&context_amd64->ds);
|
|
Swap(&context_amd64->es);
|
|
Swap(&context_amd64->fs);
|
|
Swap(&context_amd64->ss);
|
|
Swap(&context_amd64->eflags);
|
|
Swap(&context_amd64->dr0);
|
|
Swap(&context_amd64->dr1);
|
|
Swap(&context_amd64->dr2);
|
|
Swap(&context_amd64->dr3);
|
|
Swap(&context_amd64->dr6);
|
|
Swap(&context_amd64->dr7);
|
|
Swap(&context_amd64->rax);
|
|
Swap(&context_amd64->rcx);
|
|
Swap(&context_amd64->rdx);
|
|
Swap(&context_amd64->rbx);
|
|
Swap(&context_amd64->rsp);
|
|
Swap(&context_amd64->rbp);
|
|
Swap(&context_amd64->rsi);
|
|
Swap(&context_amd64->rdi);
|
|
Swap(&context_amd64->r8);
|
|
Swap(&context_amd64->r9);
|
|
Swap(&context_amd64->r10);
|
|
Swap(&context_amd64->r11);
|
|
Swap(&context_amd64->r12);
|
|
Swap(&context_amd64->r13);
|
|
Swap(&context_amd64->r14);
|
|
Swap(&context_amd64->r15);
|
|
Swap(&context_amd64->rip);
|
|
// FIXME: I'm not sure what actually determines
|
|
// which member of the union {flt_save, sse_registers}
|
|
// is valid. We're not currently using either,
|
|
// but it would be good to have them swapped properly.
|
|
|
|
for (unsigned int vr_index = 0;
|
|
vr_index < MD_CONTEXT_AMD64_VR_COUNT;
|
|
++vr_index)
|
|
Swap(&context_amd64->vector_register[vr_index]);
|
|
Swap(&context_amd64->vector_control);
|
|
Swap(&context_amd64->debug_control);
|
|
Swap(&context_amd64->last_branch_to_rip);
|
|
Swap(&context_amd64->last_branch_from_rip);
|
|
Swap(&context_amd64->last_exception_to_rip);
|
|
Swap(&context_amd64->last_exception_from_rip);
|
|
}
|
|
|
|
SetContextFlags(context_amd64->context_flags);
|
|
|
|
SetContextAMD64(context_amd64.release());
|
|
} else if (expected_size == sizeof(MDRawContextPPC64)) {
|
|
// |context_flags| of MDRawContextPPC64 is 64 bits, but other MDRawContext
|
|
// in the else case have 32 bits |context_flags|, so special case it here.
|
|
uint64_t context_flags;
|
|
if (!minidump_->ReadBytes(&context_flags, sizeof(context_flags))) {
|
|
BPLOG(ERROR) << "MinidumpContext could not read context flags";
|
|
return false;
|
|
}
|
|
if (minidump_->swap())
|
|
Swap(&context_flags);
|
|
|
|
uint32_t cpu_type = context_flags & MD_CONTEXT_CPU_MASK;
|
|
scoped_ptr<MDRawContextPPC64> context_ppc64(new MDRawContextPPC64());
|
|
|
|
if (cpu_type == 0) {
|
|
if (minidump_->GetContextCPUFlagsFromSystemInfo(&cpu_type)) {
|
|
context_ppc64->context_flags |= cpu_type;
|
|
} else {
|
|
BPLOG(ERROR) << "Failed to preserve the current stream position";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (cpu_type != MD_CONTEXT_PPC64) {
|
|
// TODO: Fall through to switch below.
|
|
// https://bugs.chromium.org/p/google-breakpad/issues/detail?id=550
|
|
BPLOG(ERROR) << "MinidumpContext not actually ppc64 context";
|
|
return false;
|
|
}
|
|
|
|
// Set the context_flags member, which has already been read, and
|
|
// read the rest of the structure beginning with the first member
|
|
// after context_flags.
|
|
context_ppc64->context_flags = context_flags;
|
|
|
|
size_t flags_size = sizeof(context_ppc64->context_flags);
|
|
uint8_t* context_after_flags =
|
|
reinterpret_cast<uint8_t*>(context_ppc64.get()) + flags_size;
|
|
if (!minidump_->ReadBytes(context_after_flags,
|
|
sizeof(MDRawContextPPC64) - flags_size)) {
|
|
BPLOG(ERROR) << "MinidumpContext could not read ppc64 context";
|
|
return false;
|
|
}
|
|
|
|
// Do this after reading the entire MDRawContext structure because
|
|
// GetSystemInfo may seek minidump to a new position.
|
|
if (!CheckAgainstSystemInfo(cpu_type)) {
|
|
BPLOG(ERROR) << "MinidumpContext ppc64 does not match system info";
|
|
return false;
|
|
}
|
|
if (minidump_->swap()) {
|
|
// context_ppc64->context_flags was already swapped.
|
|
Swap(&context_ppc64->srr0);
|
|
Swap(&context_ppc64->srr1);
|
|
for (unsigned int gpr_index = 0;
|
|
gpr_index < MD_CONTEXT_PPC64_GPR_COUNT;
|
|
++gpr_index) {
|
|
Swap(&context_ppc64->gpr[gpr_index]);
|
|
}
|
|
Swap(&context_ppc64->cr);
|
|
Swap(&context_ppc64->xer);
|
|
Swap(&context_ppc64->lr);
|
|
Swap(&context_ppc64->ctr);
|
|
Swap(&context_ppc64->vrsave);
|
|
for (unsigned int fpr_index = 0;
|
|
fpr_index < MD_FLOATINGSAVEAREA_PPC_FPR_COUNT;
|
|
++fpr_index) {
|
|
Swap(&context_ppc64->float_save.fpregs[fpr_index]);
|
|
}
|
|
// Don't swap context_ppc64->float_save.fpscr_pad because it is only
|
|
// used for padding.
|
|
Swap(&context_ppc64->float_save.fpscr);
|
|
for (unsigned int vr_index = 0;
|
|
vr_index < MD_VECTORSAVEAREA_PPC_VR_COUNT;
|
|
++vr_index) {
|
|
Normalize128(&context_ppc64->vector_save.save_vr[vr_index], true);
|
|
Swap(&context_ppc64->vector_save.save_vr[vr_index]);
|
|
}
|
|
Swap(&context_ppc64->vector_save.save_vscr);
|
|
// Don't swap the padding fields in vector_save.
|
|
Swap(&context_ppc64->vector_save.save_vrvalid);
|
|
}
|
|
|
|
SetContextFlags(static_cast<uint32_t>(context_ppc64->context_flags));
|
|
|
|
// Check for data loss when converting context flags from uint64_t into
|
|
// uint32_t
|
|
if (static_cast<uint64_t>(GetContextFlags()) !=
|
|
context_ppc64->context_flags) {
|
|
BPLOG(ERROR) << "Data loss detected when converting PPC64 context_flags";
|
|
return false;
|
|
}
|
|
|
|
SetContextPPC64(context_ppc64.release());
|
|
} else if (expected_size == sizeof(MDRawContextARM64)) {
|
|
// |context_flags| of MDRawContextARM64 is 64 bits, but other MDRawContext
|
|
// in the else case have 32 bits |context_flags|, so special case it here.
|
|
uint64_t context_flags;
|
|
|
|
BPLOG(INFO) << "MinidumpContext: looks like ARM64 context";
|
|
|
|
if (!minidump_->ReadBytes(&context_flags, sizeof(context_flags))) {
|
|
BPLOG(ERROR) << "MinidumpContext could not read context flags";
|
|
return false;
|
|
}
|
|
if (minidump_->swap())
|
|
Swap(&context_flags);
|
|
|
|
scoped_ptr<MDRawContextARM64> context_arm64(new MDRawContextARM64());
|
|
|
|
uint32_t cpu_type = context_flags & MD_CONTEXT_CPU_MASK;
|
|
if (cpu_type == 0) {
|
|
if (minidump_->GetContextCPUFlagsFromSystemInfo(&cpu_type)) {
|
|
context_arm64->context_flags |= cpu_type;
|
|
} else {
|
|
BPLOG(ERROR) << "Failed to preserve the current stream position";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (cpu_type != MD_CONTEXT_ARM64) {
|
|
// TODO: Fall through to switch below.
|
|
// https://bugs.chromium.org/p/google-breakpad/issues/detail?id=550
|
|
BPLOG(ERROR) << "MinidumpContext not actually arm64 context";
|
|
return false;
|
|
}
|
|
|
|
// Set the context_flags member, which has already been read, and
|
|
// read the rest of the structure beginning with the first member
|
|
// after context_flags.
|
|
context_arm64->context_flags = context_flags;
|
|
|
|
size_t flags_size = sizeof(context_arm64->context_flags);
|
|
uint8_t* context_after_flags =
|
|
reinterpret_cast<uint8_t*>(context_arm64.get()) + flags_size;
|
|
if (!minidump_->ReadBytes(context_after_flags,
|
|
sizeof(MDRawContextARM64) - flags_size)) {
|
|
BPLOG(ERROR) << "MinidumpContext could not read arm64 context";
|
|
return false;
|
|
}
|
|
|
|
// Do this after reading the entire MDRawContext structure because
|
|
// GetSystemInfo may seek minidump to a new position.
|
|
if (!CheckAgainstSystemInfo(cpu_type)) {
|
|
BPLOG(ERROR) << "MinidumpContext arm64 does not match system info";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
// context_arm64->context_flags was already swapped.
|
|
for (unsigned int ireg_index = 0;
|
|
ireg_index < MD_CONTEXT_ARM64_GPR_COUNT;
|
|
++ireg_index) {
|
|
Swap(&context_arm64->iregs[ireg_index]);
|
|
}
|
|
Swap(&context_arm64->cpsr);
|
|
Swap(&context_arm64->float_save.fpsr);
|
|
Swap(&context_arm64->float_save.fpcr);
|
|
for (unsigned int fpr_index = 0;
|
|
fpr_index < MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT;
|
|
++fpr_index) {
|
|
// While ARM64 is bi-endian, iOS (currently the only platform
|
|
// for which ARM64 support has been brought up) uses ARM64 exclusively
|
|
// in little-endian mode.
|
|
Normalize128(&context_arm64->float_save.regs[fpr_index], false);
|
|
Swap(&context_arm64->float_save.regs[fpr_index]);
|
|
}
|
|
}
|
|
SetContextFlags(static_cast<uint32_t>(context_arm64->context_flags));
|
|
|
|
// Check for data loss when converting context flags from uint64_t into
|
|
// uint32_t
|
|
if (static_cast<uint64_t>(GetContextFlags()) !=
|
|
context_arm64->context_flags) {
|
|
BPLOG(ERROR) << "Data loss detected when converting ARM64 context_flags";
|
|
return false;
|
|
}
|
|
|
|
SetContextARM64(context_arm64.release());
|
|
} else {
|
|
uint32_t context_flags;
|
|
if (!minidump_->ReadBytes(&context_flags, sizeof(context_flags))) {
|
|
BPLOG(ERROR) << "MinidumpContext could not read context flags";
|
|
return false;
|
|
}
|
|
if (minidump_->swap())
|
|
Swap(&context_flags);
|
|
|
|
uint32_t cpu_type = context_flags & MD_CONTEXT_CPU_MASK;
|
|
if (cpu_type == 0) {
|
|
// Unfortunately the flag for MD_CONTEXT_ARM that was taken
|
|
// from a Windows CE SDK header conflicts in practice with
|
|
// the CONTEXT_XSTATE flag. MD_CONTEXT_ARM has been renumbered,
|
|
// but handle dumps with the legacy value gracefully here.
|
|
if (context_flags & MD_CONTEXT_ARM_OLD) {
|
|
context_flags |= MD_CONTEXT_ARM;
|
|
context_flags &= ~MD_CONTEXT_ARM_OLD;
|
|
cpu_type = MD_CONTEXT_ARM;
|
|
}
|
|
}
|
|
|
|
if (cpu_type == 0) {
|
|
if (minidump_->GetContextCPUFlagsFromSystemInfo(&cpu_type)) {
|
|
context_flags |= cpu_type;
|
|
} else {
|
|
BPLOG(ERROR) << "Failed to preserve the current stream position";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Allocate the context structure for the correct CPU and fill it. The
|
|
// casts are slightly unorthodox, but it seems better to do that than to
|
|
// maintain a separate pointer for each type of CPU context structure
|
|
// when only one of them will be used.
|
|
switch (cpu_type) {
|
|
case MD_CONTEXT_X86: {
|
|
if (expected_size != sizeof(MDRawContextX86)) {
|
|
BPLOG(ERROR) << "MinidumpContext x86 size mismatch, " <<
|
|
expected_size << " != " << sizeof(MDRawContextX86);
|
|
return false;
|
|
}
|
|
|
|
scoped_ptr<MDRawContextX86> context_x86(new MDRawContextX86());
|
|
|
|
// Set the context_flags member, which has already been read, and
|
|
// read the rest of the structure beginning with the first member
|
|
// after context_flags.
|
|
context_x86->context_flags = context_flags;
|
|
|
|
size_t flags_size = sizeof(context_x86->context_flags);
|
|
uint8_t* context_after_flags =
|
|
reinterpret_cast<uint8_t*>(context_x86.get()) + flags_size;
|
|
if (!minidump_->ReadBytes(context_after_flags,
|
|
sizeof(MDRawContextX86) - flags_size)) {
|
|
BPLOG(ERROR) << "MinidumpContext could not read x86 context";
|
|
return false;
|
|
}
|
|
|
|
// Do this after reading the entire MDRawContext structure because
|
|
// GetSystemInfo may seek minidump to a new position.
|
|
if (!CheckAgainstSystemInfo(cpu_type)) {
|
|
BPLOG(ERROR) << "MinidumpContext x86 does not match system info";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
// context_x86->context_flags was already swapped.
|
|
Swap(&context_x86->dr0);
|
|
Swap(&context_x86->dr1);
|
|
Swap(&context_x86->dr2);
|
|
Swap(&context_x86->dr3);
|
|
Swap(&context_x86->dr6);
|
|
Swap(&context_x86->dr7);
|
|
Swap(&context_x86->float_save.control_word);
|
|
Swap(&context_x86->float_save.status_word);
|
|
Swap(&context_x86->float_save.tag_word);
|
|
Swap(&context_x86->float_save.error_offset);
|
|
Swap(&context_x86->float_save.error_selector);
|
|
Swap(&context_x86->float_save.data_offset);
|
|
Swap(&context_x86->float_save.data_selector);
|
|
// context_x86->float_save.register_area[] contains 8-bit quantities
|
|
// and does not need to be swapped.
|
|
Swap(&context_x86->float_save.cr0_npx_state);
|
|
Swap(&context_x86->gs);
|
|
Swap(&context_x86->fs);
|
|
Swap(&context_x86->es);
|
|
Swap(&context_x86->ds);
|
|
Swap(&context_x86->edi);
|
|
Swap(&context_x86->esi);
|
|
Swap(&context_x86->ebx);
|
|
Swap(&context_x86->edx);
|
|
Swap(&context_x86->ecx);
|
|
Swap(&context_x86->eax);
|
|
Swap(&context_x86->ebp);
|
|
Swap(&context_x86->eip);
|
|
Swap(&context_x86->cs);
|
|
Swap(&context_x86->eflags);
|
|
Swap(&context_x86->esp);
|
|
Swap(&context_x86->ss);
|
|
// context_x86->extended_registers[] contains 8-bit quantities and
|
|
// does not need to be swapped.
|
|
}
|
|
|
|
SetContextX86(context_x86.release());
|
|
|
|
break;
|
|
}
|
|
|
|
case MD_CONTEXT_PPC: {
|
|
if (expected_size != sizeof(MDRawContextPPC)) {
|
|
BPLOG(ERROR) << "MinidumpContext ppc size mismatch, " <<
|
|
expected_size << " != " << sizeof(MDRawContextPPC);
|
|
return false;
|
|
}
|
|
|
|
scoped_ptr<MDRawContextPPC> context_ppc(new MDRawContextPPC());
|
|
|
|
// Set the context_flags member, which has already been read, and
|
|
// read the rest of the structure beginning with the first member
|
|
// after context_flags.
|
|
context_ppc->context_flags = context_flags;
|
|
|
|
size_t flags_size = sizeof(context_ppc->context_flags);
|
|
uint8_t* context_after_flags =
|
|
reinterpret_cast<uint8_t*>(context_ppc.get()) + flags_size;
|
|
if (!minidump_->ReadBytes(context_after_flags,
|
|
sizeof(MDRawContextPPC) - flags_size)) {
|
|
BPLOG(ERROR) << "MinidumpContext could not read ppc context";
|
|
return false;
|
|
}
|
|
|
|
// Do this after reading the entire MDRawContext structure because
|
|
// GetSystemInfo may seek minidump to a new position.
|
|
if (!CheckAgainstSystemInfo(cpu_type)) {
|
|
BPLOG(ERROR) << "MinidumpContext ppc does not match system info";
|
|
return false;
|
|
}
|
|
|
|
// Normalize the 128-bit types in the dump.
|
|
// Since this is PowerPC, by definition, the values are big-endian.
|
|
for (unsigned int vr_index = 0;
|
|
vr_index < MD_VECTORSAVEAREA_PPC_VR_COUNT;
|
|
++vr_index) {
|
|
Normalize128(&context_ppc->vector_save.save_vr[vr_index], true);
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
// context_ppc->context_flags was already swapped.
|
|
Swap(&context_ppc->srr0);
|
|
Swap(&context_ppc->srr1);
|
|
for (unsigned int gpr_index = 0;
|
|
gpr_index < MD_CONTEXT_PPC_GPR_COUNT;
|
|
++gpr_index) {
|
|
Swap(&context_ppc->gpr[gpr_index]);
|
|
}
|
|
Swap(&context_ppc->cr);
|
|
Swap(&context_ppc->xer);
|
|
Swap(&context_ppc->lr);
|
|
Swap(&context_ppc->ctr);
|
|
Swap(&context_ppc->mq);
|
|
Swap(&context_ppc->vrsave);
|
|
for (unsigned int fpr_index = 0;
|
|
fpr_index < MD_FLOATINGSAVEAREA_PPC_FPR_COUNT;
|
|
++fpr_index) {
|
|
Swap(&context_ppc->float_save.fpregs[fpr_index]);
|
|
}
|
|
// Don't swap context_ppc->float_save.fpscr_pad because it is only
|
|
// used for padding.
|
|
Swap(&context_ppc->float_save.fpscr);
|
|
for (unsigned int vr_index = 0;
|
|
vr_index < MD_VECTORSAVEAREA_PPC_VR_COUNT;
|
|
++vr_index) {
|
|
Swap(&context_ppc->vector_save.save_vr[vr_index]);
|
|
}
|
|
Swap(&context_ppc->vector_save.save_vscr);
|
|
// Don't swap the padding fields in vector_save.
|
|
Swap(&context_ppc->vector_save.save_vrvalid);
|
|
}
|
|
|
|
SetContextPPC(context_ppc.release());
|
|
|
|
break;
|
|
}
|
|
|
|
case MD_CONTEXT_SPARC: {
|
|
if (expected_size != sizeof(MDRawContextSPARC)) {
|
|
BPLOG(ERROR) << "MinidumpContext sparc size mismatch, " <<
|
|
expected_size << " != " << sizeof(MDRawContextSPARC);
|
|
return false;
|
|
}
|
|
|
|
scoped_ptr<MDRawContextSPARC> context_sparc(new MDRawContextSPARC());
|
|
|
|
// Set the context_flags member, which has already been read, and
|
|
// read the rest of the structure beginning with the first member
|
|
// after context_flags.
|
|
context_sparc->context_flags = context_flags;
|
|
|
|
size_t flags_size = sizeof(context_sparc->context_flags);
|
|
uint8_t* context_after_flags =
|
|
reinterpret_cast<uint8_t*>(context_sparc.get()) + flags_size;
|
|
if (!minidump_->ReadBytes(context_after_flags,
|
|
sizeof(MDRawContextSPARC) - flags_size)) {
|
|
BPLOG(ERROR) << "MinidumpContext could not read sparc context";
|
|
return false;
|
|
}
|
|
|
|
// Do this after reading the entire MDRawContext structure because
|
|
// GetSystemInfo may seek minidump to a new position.
|
|
if (!CheckAgainstSystemInfo(cpu_type)) {
|
|
BPLOG(ERROR) << "MinidumpContext sparc does not match system info";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
// context_sparc->context_flags was already swapped.
|
|
for (unsigned int gpr_index = 0;
|
|
gpr_index < MD_CONTEXT_SPARC_GPR_COUNT;
|
|
++gpr_index) {
|
|
Swap(&context_sparc->g_r[gpr_index]);
|
|
}
|
|
Swap(&context_sparc->ccr);
|
|
Swap(&context_sparc->pc);
|
|
Swap(&context_sparc->npc);
|
|
Swap(&context_sparc->y);
|
|
Swap(&context_sparc->asi);
|
|
Swap(&context_sparc->fprs);
|
|
for (unsigned int fpr_index = 0;
|
|
fpr_index < MD_FLOATINGSAVEAREA_SPARC_FPR_COUNT;
|
|
++fpr_index) {
|
|
Swap(&context_sparc->float_save.regs[fpr_index]);
|
|
}
|
|
Swap(&context_sparc->float_save.filler);
|
|
Swap(&context_sparc->float_save.fsr);
|
|
}
|
|
SetContextSPARC(context_sparc.release());
|
|
|
|
break;
|
|
}
|
|
|
|
case MD_CONTEXT_ARM: {
|
|
if (expected_size != sizeof(MDRawContextARM)) {
|
|
BPLOG(ERROR) << "MinidumpContext arm size mismatch, " <<
|
|
expected_size << " != " << sizeof(MDRawContextARM);
|
|
return false;
|
|
}
|
|
|
|
scoped_ptr<MDRawContextARM> context_arm(new MDRawContextARM());
|
|
|
|
// Set the context_flags member, which has already been read, and
|
|
// read the rest of the structure beginning with the first member
|
|
// after context_flags.
|
|
context_arm->context_flags = context_flags;
|
|
|
|
size_t flags_size = sizeof(context_arm->context_flags);
|
|
uint8_t* context_after_flags =
|
|
reinterpret_cast<uint8_t*>(context_arm.get()) + flags_size;
|
|
if (!minidump_->ReadBytes(context_after_flags,
|
|
sizeof(MDRawContextARM) - flags_size)) {
|
|
BPLOG(ERROR) << "MinidumpContext could not read arm context";
|
|
return false;
|
|
}
|
|
|
|
// Do this after reading the entire MDRawContext structure because
|
|
// GetSystemInfo may seek minidump to a new position.
|
|
if (!CheckAgainstSystemInfo(cpu_type)) {
|
|
BPLOG(ERROR) << "MinidumpContext arm does not match system info";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
// context_arm->context_flags was already swapped.
|
|
for (unsigned int ireg_index = 0;
|
|
ireg_index < MD_CONTEXT_ARM_GPR_COUNT;
|
|
++ireg_index) {
|
|
Swap(&context_arm->iregs[ireg_index]);
|
|
}
|
|
Swap(&context_arm->cpsr);
|
|
Swap(&context_arm->float_save.fpscr);
|
|
for (unsigned int fpr_index = 0;
|
|
fpr_index < MD_FLOATINGSAVEAREA_ARM_FPR_COUNT;
|
|
++fpr_index) {
|
|
Swap(&context_arm->float_save.regs[fpr_index]);
|
|
}
|
|
for (unsigned int fpe_index = 0;
|
|
fpe_index < MD_FLOATINGSAVEAREA_ARM_FPEXTRA_COUNT;
|
|
++fpe_index) {
|
|
Swap(&context_arm->float_save.extra[fpe_index]);
|
|
}
|
|
}
|
|
SetContextARM(context_arm.release());
|
|
|
|
break;
|
|
}
|
|
|
|
case MD_CONTEXT_MIPS:
|
|
case MD_CONTEXT_MIPS64: {
|
|
if (expected_size != sizeof(MDRawContextMIPS)) {
|
|
BPLOG(ERROR) << "MinidumpContext MIPS size mismatch, "
|
|
<< expected_size
|
|
<< " != "
|
|
<< sizeof(MDRawContextMIPS);
|
|
return false;
|
|
}
|
|
|
|
scoped_ptr<MDRawContextMIPS> context_mips(new MDRawContextMIPS());
|
|
|
|
// Set the context_flags member, which has already been read, and
|
|
// read the rest of the structure beginning with the first member
|
|
// after context_flags.
|
|
context_mips->context_flags = context_flags;
|
|
|
|
size_t flags_size = sizeof(context_mips->context_flags);
|
|
uint8_t* context_after_flags =
|
|
reinterpret_cast<uint8_t*>(context_mips.get()) + flags_size;
|
|
if (!minidump_->ReadBytes(context_after_flags,
|
|
sizeof(MDRawContextMIPS) - flags_size)) {
|
|
BPLOG(ERROR) << "MinidumpContext could not read MIPS context";
|
|
return false;
|
|
}
|
|
|
|
// Do this after reading the entire MDRawContext structure because
|
|
// GetSystemInfo may seek minidump to a new position.
|
|
if (!CheckAgainstSystemInfo(cpu_type)) {
|
|
BPLOG(ERROR) << "MinidumpContext MIPS does not match system info";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
// context_mips->context_flags was already swapped.
|
|
for (int ireg_index = 0;
|
|
ireg_index < MD_CONTEXT_MIPS_GPR_COUNT;
|
|
++ireg_index) {
|
|
Swap(&context_mips->iregs[ireg_index]);
|
|
}
|
|
Swap(&context_mips->mdhi);
|
|
Swap(&context_mips->mdlo);
|
|
for (int dsp_index = 0;
|
|
dsp_index < MD_CONTEXT_MIPS_DSP_COUNT;
|
|
++dsp_index) {
|
|
Swap(&context_mips->hi[dsp_index]);
|
|
Swap(&context_mips->lo[dsp_index]);
|
|
}
|
|
Swap(&context_mips->dsp_control);
|
|
Swap(&context_mips->epc);
|
|
Swap(&context_mips->badvaddr);
|
|
Swap(&context_mips->status);
|
|
Swap(&context_mips->cause);
|
|
for (int fpr_index = 0;
|
|
fpr_index < MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT;
|
|
++fpr_index) {
|
|
Swap(&context_mips->float_save.regs[fpr_index]);
|
|
}
|
|
Swap(&context_mips->float_save.fpcsr);
|
|
Swap(&context_mips->float_save.fir);
|
|
}
|
|
SetContextMIPS(context_mips.release());
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
// Unknown context type - Don't log as an error yet. Let the
|
|
// caller work that out.
|
|
BPLOG(INFO) << "MinidumpContext unknown context type " <<
|
|
HexString(cpu_type);
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
SetContextFlags(context_flags);
|
|
}
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
bool MinidumpContext::CheckAgainstSystemInfo(uint32_t context_cpu_type) {
|
|
// It's OK if the minidump doesn't contain an MD_SYSTEM_INFO_STREAM,
|
|
// as this function just implements a sanity check.
|
|
MinidumpSystemInfo* system_info = minidump_->GetSystemInfo();
|
|
if (!system_info) {
|
|
BPLOG(INFO) << "MinidumpContext could not be compared against "
|
|
"MinidumpSystemInfo";
|
|
return true;
|
|
}
|
|
|
|
// If there is an MD_SYSTEM_INFO_STREAM, it should contain valid system info.
|
|
const MDRawSystemInfo* raw_system_info = system_info->system_info();
|
|
if (!raw_system_info) {
|
|
BPLOG(INFO) << "MinidumpContext could not be compared against "
|
|
"MDRawSystemInfo";
|
|
return false;
|
|
}
|
|
|
|
MDCPUArchitecture system_info_cpu_type = static_cast<MDCPUArchitecture>(
|
|
raw_system_info->processor_architecture);
|
|
|
|
// Compare the CPU type of the context record to the CPU type in the
|
|
// minidump's system info stream.
|
|
bool return_value = false;
|
|
switch (context_cpu_type) {
|
|
case MD_CONTEXT_X86:
|
|
if (system_info_cpu_type == MD_CPU_ARCHITECTURE_X86 ||
|
|
system_info_cpu_type == MD_CPU_ARCHITECTURE_X86_WIN64 ||
|
|
system_info_cpu_type == MD_CPU_ARCHITECTURE_AMD64) {
|
|
return_value = true;
|
|
}
|
|
break;
|
|
|
|
case MD_CONTEXT_PPC:
|
|
if (system_info_cpu_type == MD_CPU_ARCHITECTURE_PPC)
|
|
return_value = true;
|
|
break;
|
|
|
|
case MD_CONTEXT_PPC64:
|
|
if (system_info_cpu_type == MD_CPU_ARCHITECTURE_PPC64)
|
|
return_value = true;
|
|
break;
|
|
|
|
case MD_CONTEXT_AMD64:
|
|
if (system_info_cpu_type == MD_CPU_ARCHITECTURE_AMD64)
|
|
return_value = true;
|
|
break;
|
|
|
|
case MD_CONTEXT_SPARC:
|
|
if (system_info_cpu_type == MD_CPU_ARCHITECTURE_SPARC)
|
|
return_value = true;
|
|
break;
|
|
|
|
case MD_CONTEXT_ARM:
|
|
if (system_info_cpu_type == MD_CPU_ARCHITECTURE_ARM)
|
|
return_value = true;
|
|
break;
|
|
|
|
case MD_CONTEXT_ARM64:
|
|
if (system_info_cpu_type == MD_CPU_ARCHITECTURE_ARM64)
|
|
return_value = true;
|
|
break;
|
|
|
|
case MD_CONTEXT_MIPS:
|
|
if (system_info_cpu_type == MD_CPU_ARCHITECTURE_MIPS)
|
|
return_value = true;
|
|
break;
|
|
|
|
case MD_CONTEXT_MIPS64:
|
|
if (system_info_cpu_type == MD_CPU_ARCHITECTURE_MIPS64)
|
|
return_value = true;
|
|
break;
|
|
}
|
|
|
|
BPLOG_IF(ERROR, !return_value) << "MinidumpContext CPU " <<
|
|
HexString(context_cpu_type) <<
|
|
" wrong for MinidumpSystemInfo CPU " <<
|
|
HexString(system_info_cpu_type);
|
|
|
|
return return_value;
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpMemoryRegion
|
|
//
|
|
|
|
|
|
uint32_t MinidumpMemoryRegion::max_bytes_ = 2 * 1024 * 1024; // 2MB
|
|
|
|
|
|
MinidumpMemoryRegion::MinidumpMemoryRegion(Minidump* minidump)
|
|
: MinidumpObject(minidump),
|
|
descriptor_(NULL),
|
|
memory_(NULL) {
|
|
}
|
|
|
|
|
|
MinidumpMemoryRegion::~MinidumpMemoryRegion() {
|
|
delete memory_;
|
|
}
|
|
|
|
|
|
void MinidumpMemoryRegion::SetDescriptor(MDMemoryDescriptor* descriptor) {
|
|
descriptor_ = descriptor;
|
|
valid_ = descriptor &&
|
|
descriptor_->memory.data_size <=
|
|
numeric_limits<uint64_t>::max() -
|
|
descriptor_->start_of_memory_range;
|
|
}
|
|
|
|
|
|
const uint8_t* MinidumpMemoryRegion::GetMemory() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpMemoryRegion for GetMemory";
|
|
return NULL;
|
|
}
|
|
|
|
if (!memory_) {
|
|
if (descriptor_->memory.data_size == 0) {
|
|
BPLOG(ERROR) << "MinidumpMemoryRegion is empty";
|
|
return NULL;
|
|
}
|
|
|
|
if (!minidump_->SeekSet(descriptor_->memory.rva)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryRegion could not seek to memory region";
|
|
return NULL;
|
|
}
|
|
|
|
if (descriptor_->memory.data_size > max_bytes_) {
|
|
BPLOG(ERROR) << "MinidumpMemoryRegion size " <<
|
|
descriptor_->memory.data_size << " exceeds maximum " <<
|
|
max_bytes_;
|
|
return NULL;
|
|
}
|
|
|
|
scoped_ptr< vector<uint8_t> > memory(
|
|
new vector<uint8_t>(descriptor_->memory.data_size));
|
|
|
|
if (!minidump_->ReadBytes(&(*memory)[0], descriptor_->memory.data_size)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryRegion could not read memory region";
|
|
return NULL;
|
|
}
|
|
|
|
memory_ = memory.release();
|
|
}
|
|
|
|
return &(*memory_)[0];
|
|
}
|
|
|
|
|
|
uint64_t MinidumpMemoryRegion::GetBase() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpMemoryRegion for GetBase";
|
|
return static_cast<uint64_t>(-1);
|
|
}
|
|
|
|
return descriptor_->start_of_memory_range;
|
|
}
|
|
|
|
|
|
uint32_t MinidumpMemoryRegion::GetSize() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpMemoryRegion for GetSize";
|
|
return 0;
|
|
}
|
|
|
|
return descriptor_->memory.data_size;
|
|
}
|
|
|
|
|
|
void MinidumpMemoryRegion::FreeMemory() {
|
|
delete memory_;
|
|
memory_ = NULL;
|
|
}
|
|
|
|
|
|
template<typename T>
|
|
bool MinidumpMemoryRegion::GetMemoryAtAddressInternal(uint64_t address,
|
|
T* value) const {
|
|
BPLOG_IF(ERROR, !value) << "MinidumpMemoryRegion::GetMemoryAtAddressInternal "
|
|
"requires |value|";
|
|
assert(value);
|
|
*value = 0;
|
|
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpMemoryRegion for "
|
|
"GetMemoryAtAddressInternal";
|
|
return false;
|
|
}
|
|
|
|
// Common failure case
|
|
if (address < descriptor_->start_of_memory_range ||
|
|
sizeof(T) > numeric_limits<uint64_t>::max() - address ||
|
|
address + sizeof(T) > descriptor_->start_of_memory_range +
|
|
descriptor_->memory.data_size) {
|
|
BPLOG(INFO) << "MinidumpMemoryRegion request out of range: " <<
|
|
HexString(address) << "+" << sizeof(T) << "/" <<
|
|
HexString(descriptor_->start_of_memory_range) << "+" <<
|
|
HexString(descriptor_->memory.data_size);
|
|
return false;
|
|
}
|
|
|
|
const uint8_t* memory = GetMemory();
|
|
if (!memory) {
|
|
// GetMemory already logged a perfectly good message.
|
|
return false;
|
|
}
|
|
|
|
// If the CPU requires memory accesses to be aligned, this can crash.
|
|
// x86 and ppc are able to cope, though.
|
|
*value = *reinterpret_cast<const T*>(
|
|
&memory[address - descriptor_->start_of_memory_range]);
|
|
|
|
if (minidump_->swap())
|
|
Swap(value);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool MinidumpMemoryRegion::GetMemoryAtAddress(uint64_t address,
|
|
uint8_t* value) const {
|
|
return GetMemoryAtAddressInternal(address, value);
|
|
}
|
|
|
|
|
|
bool MinidumpMemoryRegion::GetMemoryAtAddress(uint64_t address,
|
|
uint16_t* value) const {
|
|
return GetMemoryAtAddressInternal(address, value);
|
|
}
|
|
|
|
|
|
bool MinidumpMemoryRegion::GetMemoryAtAddress(uint64_t address,
|
|
uint32_t* value) const {
|
|
return GetMemoryAtAddressInternal(address, value);
|
|
}
|
|
|
|
|
|
bool MinidumpMemoryRegion::GetMemoryAtAddress(uint64_t address,
|
|
uint64_t* value) const {
|
|
return GetMemoryAtAddressInternal(address, value);
|
|
}
|
|
|
|
|
|
void MinidumpMemoryRegion::Print() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpMemoryRegion cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
const uint8_t* memory = GetMemory();
|
|
if (memory) {
|
|
printf("0x");
|
|
for (unsigned int byte_index = 0;
|
|
byte_index < descriptor_->memory.data_size;
|
|
byte_index++) {
|
|
printf("%02x", memory[byte_index]);
|
|
}
|
|
printf("\n");
|
|
} else {
|
|
printf("No memory\n");
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpThread
|
|
//
|
|
|
|
|
|
MinidumpThread::MinidumpThread(Minidump* minidump)
|
|
: MinidumpObject(minidump),
|
|
thread_(),
|
|
memory_(NULL),
|
|
context_(NULL) {
|
|
}
|
|
|
|
|
|
MinidumpThread::~MinidumpThread() {
|
|
delete memory_;
|
|
delete context_;
|
|
}
|
|
|
|
|
|
bool MinidumpThread::Read() {
|
|
// Invalidate cached data.
|
|
delete memory_;
|
|
memory_ = NULL;
|
|
delete context_;
|
|
context_ = NULL;
|
|
|
|
valid_ = false;
|
|
|
|
if (!minidump_->ReadBytes(&thread_, sizeof(thread_))) {
|
|
BPLOG(ERROR) << "MinidumpThread cannot read thread";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&thread_.thread_id);
|
|
Swap(&thread_.suspend_count);
|
|
Swap(&thread_.priority_class);
|
|
Swap(&thread_.priority);
|
|
Swap(&thread_.teb);
|
|
Swap(&thread_.stack);
|
|
Swap(&thread_.thread_context);
|
|
}
|
|
|
|
// Check for base + size overflow or undersize.
|
|
if (thread_.stack.memory.rva == 0 ||
|
|
thread_.stack.memory.data_size == 0 ||
|
|
thread_.stack.memory.data_size > numeric_limits<uint64_t>::max() -
|
|
thread_.stack.start_of_memory_range) {
|
|
// This is ok, but log an error anyway.
|
|
BPLOG(ERROR) << "MinidumpThread has a memory region problem, " <<
|
|
HexString(thread_.stack.start_of_memory_range) << "+" <<
|
|
HexString(thread_.stack.memory.data_size) <<
|
|
", RVA 0x" << HexString(thread_.stack.memory.rva);
|
|
} else {
|
|
memory_ = new MinidumpMemoryRegion(minidump_);
|
|
memory_->SetDescriptor(&thread_.stack);
|
|
}
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
uint64_t MinidumpThread::GetStartOfStackMemoryRange() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "GetStartOfStackMemoryRange: Invalid MinidumpThread";
|
|
return 0;
|
|
}
|
|
|
|
return thread_.stack.start_of_memory_range;
|
|
}
|
|
|
|
MinidumpMemoryRegion* MinidumpThread::GetMemory() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpThread for GetMemory";
|
|
return NULL;
|
|
}
|
|
|
|
return memory_;
|
|
}
|
|
|
|
|
|
MinidumpContext* MinidumpThread::GetContext() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpThread for GetContext";
|
|
return NULL;
|
|
}
|
|
|
|
if (!context_) {
|
|
if (!minidump_->SeekSet(thread_.thread_context.rva)) {
|
|
BPLOG(ERROR) << "MinidumpThread cannot seek to context";
|
|
return NULL;
|
|
}
|
|
|
|
scoped_ptr<MinidumpContext> context(new MinidumpContext(minidump_));
|
|
|
|
if (!context->Read(thread_.thread_context.data_size)) {
|
|
BPLOG(ERROR) << "MinidumpThread cannot read context";
|
|
return NULL;
|
|
}
|
|
|
|
context_ = context.release();
|
|
}
|
|
|
|
return context_;
|
|
}
|
|
|
|
|
|
bool MinidumpThread::GetThreadID(uint32_t *thread_id) const {
|
|
BPLOG_IF(ERROR, !thread_id) << "MinidumpThread::GetThreadID requires "
|
|
"|thread_id|";
|
|
assert(thread_id);
|
|
*thread_id = 0;
|
|
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpThread for GetThreadID";
|
|
return false;
|
|
}
|
|
|
|
*thread_id = thread_.thread_id;
|
|
return true;
|
|
}
|
|
|
|
|
|
void MinidumpThread::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpThread cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MDRawThread\n");
|
|
printf(" thread_id = 0x%x\n", thread_.thread_id);
|
|
printf(" suspend_count = %d\n", thread_.suspend_count);
|
|
printf(" priority_class = 0x%x\n", thread_.priority_class);
|
|
printf(" priority = 0x%x\n", thread_.priority);
|
|
printf(" teb = 0x%" PRIx64 "\n", thread_.teb);
|
|
printf(" stack.start_of_memory_range = 0x%" PRIx64 "\n",
|
|
thread_.stack.start_of_memory_range);
|
|
printf(" stack.memory.data_size = 0x%x\n",
|
|
thread_.stack.memory.data_size);
|
|
printf(" stack.memory.rva = 0x%x\n", thread_.stack.memory.rva);
|
|
printf(" thread_context.data_size = 0x%x\n",
|
|
thread_.thread_context.data_size);
|
|
printf(" thread_context.rva = 0x%x\n",
|
|
thread_.thread_context.rva);
|
|
|
|
MinidumpContext* context = GetContext();
|
|
if (context) {
|
|
printf("\n");
|
|
context->Print();
|
|
} else {
|
|
printf(" (no context)\n");
|
|
printf("\n");
|
|
}
|
|
|
|
MinidumpMemoryRegion* memory = GetMemory();
|
|
if (memory) {
|
|
printf("Stack\n");
|
|
memory->Print();
|
|
} else {
|
|
printf("No stack\n");
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpThreadList
|
|
//
|
|
|
|
|
|
uint32_t MinidumpThreadList::max_threads_ = 4096;
|
|
|
|
|
|
MinidumpThreadList::MinidumpThreadList(Minidump* minidump)
|
|
: MinidumpStream(minidump),
|
|
id_to_thread_map_(),
|
|
threads_(NULL),
|
|
thread_count_(0) {
|
|
}
|
|
|
|
|
|
MinidumpThreadList::~MinidumpThreadList() {
|
|
delete threads_;
|
|
}
|
|
|
|
|
|
bool MinidumpThreadList::Read(uint32_t expected_size) {
|
|
// Invalidate cached data.
|
|
id_to_thread_map_.clear();
|
|
delete threads_;
|
|
threads_ = NULL;
|
|
thread_count_ = 0;
|
|
|
|
valid_ = false;
|
|
|
|
uint32_t thread_count;
|
|
if (expected_size < sizeof(thread_count)) {
|
|
BPLOG(ERROR) << "MinidumpThreadList count size mismatch, " <<
|
|
expected_size << " < " << sizeof(thread_count);
|
|
return false;
|
|
}
|
|
if (!minidump_->ReadBytes(&thread_count, sizeof(thread_count))) {
|
|
BPLOG(ERROR) << "MinidumpThreadList cannot read thread count";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap())
|
|
Swap(&thread_count);
|
|
|
|
if (thread_count > numeric_limits<uint32_t>::max() / sizeof(MDRawThread)) {
|
|
BPLOG(ERROR) << "MinidumpThreadList thread count " << thread_count <<
|
|
" would cause multiplication overflow";
|
|
return false;
|
|
}
|
|
|
|
if (expected_size != sizeof(thread_count) +
|
|
thread_count * sizeof(MDRawThread)) {
|
|
// may be padded with 4 bytes on 64bit ABIs for alignment
|
|
if (expected_size == sizeof(thread_count) + 4 +
|
|
thread_count * sizeof(MDRawThread)) {
|
|
uint32_t useless;
|
|
if (!minidump_->ReadBytes(&useless, 4)) {
|
|
BPLOG(ERROR) << "MinidumpThreadList cannot read threadlist padded "
|
|
"bytes";
|
|
return false;
|
|
}
|
|
} else {
|
|
BPLOG(ERROR) << "MinidumpThreadList size mismatch, " << expected_size <<
|
|
" != " << sizeof(thread_count) +
|
|
thread_count * sizeof(MDRawThread);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
if (thread_count > max_threads_) {
|
|
BPLOG(ERROR) << "MinidumpThreadList count " << thread_count <<
|
|
" exceeds maximum " << max_threads_;
|
|
return false;
|
|
}
|
|
|
|
if (thread_count != 0) {
|
|
scoped_ptr<MinidumpThreads> threads(
|
|
new MinidumpThreads(thread_count, MinidumpThread(minidump_)));
|
|
|
|
for (unsigned int thread_index = 0;
|
|
thread_index < thread_count;
|
|
++thread_index) {
|
|
MinidumpThread* thread = &(*threads)[thread_index];
|
|
|
|
// Assume that the file offset is correct after the last read.
|
|
if (!thread->Read()) {
|
|
BPLOG(ERROR) << "MinidumpThreadList cannot read thread " <<
|
|
thread_index << "/" << thread_count;
|
|
return false;
|
|
}
|
|
|
|
uint32_t thread_id;
|
|
if (!thread->GetThreadID(&thread_id)) {
|
|
BPLOG(ERROR) << "MinidumpThreadList cannot get thread ID for thread " <<
|
|
thread_index << "/" << thread_count;
|
|
return false;
|
|
}
|
|
|
|
if (GetThreadByID(thread_id)) {
|
|
// Another thread with this ID is already in the list. Data error.
|
|
BPLOG(ERROR) << "MinidumpThreadList found multiple threads with ID " <<
|
|
HexString(thread_id) << " at thread " <<
|
|
thread_index << "/" << thread_count;
|
|
return false;
|
|
}
|
|
id_to_thread_map_[thread_id] = thread;
|
|
}
|
|
|
|
threads_ = threads.release();
|
|
}
|
|
|
|
thread_count_ = thread_count;
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
MinidumpThread* MinidumpThreadList::GetThreadAtIndex(unsigned int index)
|
|
const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpThreadList for GetThreadAtIndex";
|
|
return NULL;
|
|
}
|
|
|
|
if (index >= thread_count_) {
|
|
BPLOG(ERROR) << "MinidumpThreadList index out of range: " <<
|
|
index << "/" << thread_count_;
|
|
return NULL;
|
|
}
|
|
|
|
return &(*threads_)[index];
|
|
}
|
|
|
|
|
|
MinidumpThread* MinidumpThreadList::GetThreadByID(uint32_t thread_id) {
|
|
// Don't check valid_. Read calls this method before everything is
|
|
// validated. It is safe to not check valid_ here.
|
|
return id_to_thread_map_[thread_id];
|
|
}
|
|
|
|
|
|
void MinidumpThreadList::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpThreadList cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MinidumpThreadList\n");
|
|
printf(" thread_count = %d\n", thread_count_);
|
|
printf("\n");
|
|
|
|
for (unsigned int thread_index = 0;
|
|
thread_index < thread_count_;
|
|
++thread_index) {
|
|
printf("thread[%d]\n", thread_index);
|
|
|
|
(*threads_)[thread_index].Print();
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpModule
|
|
//
|
|
|
|
|
|
uint32_t MinidumpModule::max_cv_bytes_ = 32768;
|
|
uint32_t MinidumpModule::max_misc_bytes_ = 32768;
|
|
|
|
|
|
MinidumpModule::MinidumpModule(Minidump* minidump)
|
|
: MinidumpObject(minidump),
|
|
module_valid_(false),
|
|
has_debug_info_(false),
|
|
module_(),
|
|
name_(NULL),
|
|
cv_record_(NULL),
|
|
cv_record_signature_(MD_CVINFOUNKNOWN_SIGNATURE),
|
|
misc_record_(NULL) {
|
|
}
|
|
|
|
|
|
MinidumpModule::~MinidumpModule() {
|
|
delete name_;
|
|
delete cv_record_;
|
|
delete misc_record_;
|
|
}
|
|
|
|
|
|
bool MinidumpModule::Read() {
|
|
// Invalidate cached data.
|
|
delete name_;
|
|
name_ = NULL;
|
|
delete cv_record_;
|
|
cv_record_ = NULL;
|
|
cv_record_signature_ = MD_CVINFOUNKNOWN_SIGNATURE;
|
|
delete misc_record_;
|
|
misc_record_ = NULL;
|
|
|
|
module_valid_ = false;
|
|
has_debug_info_ = false;
|
|
valid_ = false;
|
|
|
|
if (!minidump_->ReadBytes(&module_, MD_MODULE_SIZE)) {
|
|
BPLOG(ERROR) << "MinidumpModule cannot read module";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&module_.base_of_image);
|
|
Swap(&module_.size_of_image);
|
|
Swap(&module_.checksum);
|
|
Swap(&module_.time_date_stamp);
|
|
Swap(&module_.module_name_rva);
|
|
Swap(&module_.version_info.signature);
|
|
Swap(&module_.version_info.struct_version);
|
|
Swap(&module_.version_info.file_version_hi);
|
|
Swap(&module_.version_info.file_version_lo);
|
|
Swap(&module_.version_info.product_version_hi);
|
|
Swap(&module_.version_info.product_version_lo);
|
|
Swap(&module_.version_info.file_flags_mask);
|
|
Swap(&module_.version_info.file_flags);
|
|
Swap(&module_.version_info.file_os);
|
|
Swap(&module_.version_info.file_type);
|
|
Swap(&module_.version_info.file_subtype);
|
|
Swap(&module_.version_info.file_date_hi);
|
|
Swap(&module_.version_info.file_date_lo);
|
|
Swap(&module_.cv_record);
|
|
Swap(&module_.misc_record);
|
|
// Don't swap reserved fields because their contents are unknown (as
|
|
// are their proper widths).
|
|
}
|
|
|
|
// Check for base + size overflow or undersize.
|
|
if (module_.size_of_image == 0 ||
|
|
module_.size_of_image >
|
|
numeric_limits<uint64_t>::max() - module_.base_of_image) {
|
|
BPLOG(ERROR) << "MinidumpModule has a module problem, " <<
|
|
HexString(module_.base_of_image) << "+" <<
|
|
HexString(module_.size_of_image);
|
|
return false;
|
|
}
|
|
|
|
module_valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool MinidumpModule::ReadAuxiliaryData() {
|
|
if (!module_valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModule for ReadAuxiliaryData";
|
|
return false;
|
|
}
|
|
|
|
// Each module must have a name.
|
|
name_ = minidump_->ReadString(module_.module_name_rva);
|
|
if (!name_) {
|
|
BPLOG(ERROR) << "MinidumpModule could not read name";
|
|
return false;
|
|
}
|
|
|
|
// At this point, we have enough info for the module to be valid.
|
|
valid_ = true;
|
|
|
|
// CodeView and miscellaneous debug records are only required if the
|
|
// module indicates that they exist.
|
|
if (module_.cv_record.data_size && !GetCVRecord(NULL)) {
|
|
BPLOG(ERROR) << "MinidumpModule has no CodeView record, "
|
|
"but one was expected";
|
|
return false;
|
|
}
|
|
|
|
if (module_.misc_record.data_size && !GetMiscRecord(NULL)) {
|
|
BPLOG(ERROR) << "MinidumpModule has no miscellaneous debug record, "
|
|
"but one was expected";
|
|
return false;
|
|
}
|
|
|
|
has_debug_info_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
string MinidumpModule::code_file() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModule for code_file";
|
|
return "";
|
|
}
|
|
|
|
return *name_;
|
|
}
|
|
|
|
|
|
string MinidumpModule::code_identifier() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModule for code_identifier";
|
|
return "";
|
|
}
|
|
|
|
if (!has_debug_info_)
|
|
return "";
|
|
|
|
MinidumpSystemInfo *minidump_system_info = minidump_->GetSystemInfo();
|
|
if (!minidump_system_info) {
|
|
BPLOG(ERROR) << "MinidumpModule code_identifier requires "
|
|
"MinidumpSystemInfo";
|
|
return "";
|
|
}
|
|
|
|
const MDRawSystemInfo *raw_system_info = minidump_system_info->system_info();
|
|
if (!raw_system_info) {
|
|
BPLOG(ERROR) << "MinidumpModule code_identifier requires MDRawSystemInfo";
|
|
return "";
|
|
}
|
|
|
|
string identifier;
|
|
|
|
switch (raw_system_info->platform_id) {
|
|
case MD_OS_WIN32_NT:
|
|
case MD_OS_WIN32_WINDOWS: {
|
|
// Use the same format that the MS symbol server uses in filesystem
|
|
// hierarchies.
|
|
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_ANDROID:
|
|
case MD_OS_LINUX: {
|
|
// If ELF CodeView data is present, return the debug id.
|
|
if (cv_record_ && cv_record_signature_ == MD_CVINFOELF_SIGNATURE) {
|
|
const MDCVInfoELF* cv_record_elf =
|
|
reinterpret_cast<const MDCVInfoELF*>(&(*cv_record_)[0]);
|
|
assert(cv_record_elf->cv_signature == MD_CVINFOELF_SIGNATURE);
|
|
|
|
for (unsigned int build_id_index = 0;
|
|
build_id_index < (cv_record_->size() - MDCVInfoELF_minsize);
|
|
++build_id_index) {
|
|
char hexbyte[3];
|
|
snprintf(hexbyte, sizeof(hexbyte), "%02x",
|
|
cv_record_elf->build_id[build_id_index]);
|
|
identifier += hexbyte;
|
|
}
|
|
break;
|
|
}
|
|
// Otherwise fall through to the case below.
|
|
}
|
|
|
|
case MD_OS_MAC_OS_X:
|
|
case MD_OS_IOS:
|
|
case MD_OS_SOLARIS:
|
|
case MD_OS_NACL:
|
|
case MD_OS_PS3: {
|
|
// 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.
|
|
BPLOG(ERROR) << "MinidumpModule code_identifier requires known platform, "
|
|
"found " << HexString(raw_system_info->platform_id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return identifier;
|
|
}
|
|
|
|
|
|
string MinidumpModule::debug_file() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModule for debug_file";
|
|
return "";
|
|
}
|
|
|
|
if (!has_debug_info_)
|
|
return "";
|
|
|
|
string file;
|
|
// Prefer the CodeView record if present.
|
|
if (cv_record_) {
|
|
if (cv_record_signature_ == MD_CVINFOPDB70_SIGNATURE) {
|
|
// It's actually an MDCVInfoPDB70 structure.
|
|
const MDCVInfoPDB70* cv_record_70 =
|
|
reinterpret_cast<const MDCVInfoPDB70*>(&(*cv_record_)[0]);
|
|
assert(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_signature_ == MD_CVINFOPDB20_SIGNATURE) {
|
|
// It's actually an MDCVInfoPDB20 structure.
|
|
const MDCVInfoPDB20* cv_record_20 =
|
|
reinterpret_cast<const MDCVInfoPDB20*>(&(*cv_record_)[0]);
|
|
assert(cv_record_20->cv_header.signature == MD_CVINFOPDB20_SIGNATURE);
|
|
|
|
// GetCVRecord guarantees pdb_file_name is null-terminated.
|
|
file = reinterpret_cast<const char*>(cv_record_20->pdb_file_name);
|
|
} else if (cv_record_signature_ == MD_CVINFOELF_SIGNATURE) {
|
|
// It's actually an MDCVInfoELF structure.
|
|
assert(reinterpret_cast<const MDCVInfoELF*>(&(*cv_record_)[0])->
|
|
cv_signature == MD_CVINFOELF_SIGNATURE);
|
|
|
|
// For MDCVInfoELF, the debug file is the code file.
|
|
file = *name_;
|
|
}
|
|
|
|
// If there's a CodeView record but it doesn't match a known signature,
|
|
// try the miscellaneous record.
|
|
}
|
|
|
|
if (file.empty()) {
|
|
// No usable CodeView record. Try the miscellaneous debug record.
|
|
if (misc_record_) {
|
|
const MDImageDebugMisc* misc_record =
|
|
reinterpret_cast<const MDImageDebugMisc *>(&(*misc_record_)[0]);
|
|
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 - MDImageDebugMisc_minsize);
|
|
} 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 - MDImageDebugMisc_minsize;
|
|
if (bytes % 2 == 0) {
|
|
unsigned int utf16_words = bytes / 2;
|
|
|
|
// UTF16ToUTF8 expects a vector<uint16_t>, so create a temporary one
|
|
// and copy the UTF-16 data into it.
|
|
vector<uint16_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));
|
|
if (new_file.get() != nullptr) {
|
|
file = *new_file;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Relatively common case
|
|
BPLOG_IF(INFO, file.empty()) << "MinidumpModule could not determine "
|
|
"debug_file for " << *name_;
|
|
|
|
return file;
|
|
}
|
|
|
|
static string guid_and_age_to_debug_id(const MDGUID& guid,
|
|
uint32_t age) {
|
|
char identifier_string[41];
|
|
snprintf(identifier_string, sizeof(identifier_string),
|
|
"%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%x",
|
|
guid.data1,
|
|
guid.data2,
|
|
guid.data3,
|
|
guid.data4[0],
|
|
guid.data4[1],
|
|
guid.data4[2],
|
|
guid.data4[3],
|
|
guid.data4[4],
|
|
guid.data4[5],
|
|
guid.data4[6],
|
|
guid.data4[7],
|
|
age);
|
|
return identifier_string;
|
|
}
|
|
|
|
string MinidumpModule::debug_identifier() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModule for debug_identifier";
|
|
return "";
|
|
}
|
|
|
|
if (!has_debug_info_)
|
|
return "";
|
|
|
|
string identifier;
|
|
|
|
// Use the CodeView record if present.
|
|
if (cv_record_) {
|
|
if (cv_record_signature_ == MD_CVINFOPDB70_SIGNATURE) {
|
|
// It's actually an MDCVInfoPDB70 structure.
|
|
const MDCVInfoPDB70* cv_record_70 =
|
|
reinterpret_cast<const MDCVInfoPDB70*>(&(*cv_record_)[0]);
|
|
assert(cv_record_70->cv_signature == MD_CVINFOPDB70_SIGNATURE);
|
|
|
|
// Use the same format that the MS symbol server uses in filesystem
|
|
// hierarchies.
|
|
identifier = guid_and_age_to_debug_id(cv_record_70->signature,
|
|
cv_record_70->age);
|
|
} else if (cv_record_signature_ == MD_CVINFOPDB20_SIGNATURE) {
|
|
// It's actually an MDCVInfoPDB20 structure.
|
|
const MDCVInfoPDB20* cv_record_20 =
|
|
reinterpret_cast<const MDCVInfoPDB20*>(&(*cv_record_)[0]);
|
|
assert(cv_record_20->cv_header.signature == MD_CVINFOPDB20_SIGNATURE);
|
|
|
|
// Use the same format that the MS symbol server uses in filesystem
|
|
// hierarchies.
|
|
char identifier_string[17];
|
|
snprintf(identifier_string, sizeof(identifier_string),
|
|
"%08X%x", cv_record_20->signature, cv_record_20->age);
|
|
identifier = identifier_string;
|
|
} else if (cv_record_signature_ == MD_CVINFOELF_SIGNATURE) {
|
|
// It's actually an MDCVInfoELF structure.
|
|
const MDCVInfoELF* cv_record_elf =
|
|
reinterpret_cast<const MDCVInfoELF*>(&(*cv_record_)[0]);
|
|
assert(cv_record_elf->cv_signature == MD_CVINFOELF_SIGNATURE);
|
|
|
|
// For backwards-compatibility, stuff as many bytes as will fit into
|
|
// a MDGUID and use the MS symbol server format as MDCVInfoPDB70 does
|
|
// with age = 0. Historically Breakpad would do this during dump
|
|
// writing to fit the build id data into a MDCVInfoPDB70 struct.
|
|
// The full build id is available by calling code_identifier.
|
|
MDGUID guid = {0};
|
|
memcpy(&guid, &cv_record_elf->build_id,
|
|
std::min(cv_record_->size() - MDCVInfoELF_minsize,
|
|
sizeof(MDGUID)));
|
|
identifier = guid_and_age_to_debug_id(guid, 0);
|
|
}
|
|
}
|
|
|
|
// TODO(mmentovai): if there's no usable 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 Breakpad 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().
|
|
|
|
// Relatively common case
|
|
BPLOG_IF(INFO, identifier.empty()) << "MinidumpModule could not determine "
|
|
"debug_identifier for " << *name_;
|
|
|
|
return identifier;
|
|
}
|
|
|
|
|
|
string MinidumpModule::version() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModule for version";
|
|
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.
|
|
|
|
BPLOG_IF(INFO, version.empty()) << "MinidumpModule could not determine "
|
|
"version for " << *name_;
|
|
|
|
return version;
|
|
}
|
|
|
|
|
|
CodeModule* MinidumpModule::Copy() const {
|
|
return new BasicCodeModule(this);
|
|
}
|
|
|
|
|
|
uint64_t MinidumpModule::shrink_down_delta() const {
|
|
return 0;
|
|
}
|
|
|
|
void MinidumpModule::SetShrinkDownDelta(uint64_t shrink_down_delta) {
|
|
// Not implemented
|
|
assert(false);
|
|
}
|
|
|
|
|
|
const uint8_t* MinidumpModule::GetCVRecord(uint32_t* size) {
|
|
if (!module_valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModule for GetCVRecord";
|
|
return NULL;
|
|
}
|
|
|
|
if (!cv_record_) {
|
|
// This just guards against 0-sized CodeView records; more specific checks
|
|
// are used when the signature is checked against various structure types.
|
|
if (module_.cv_record.data_size == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!minidump_->SeekSet(module_.cv_record.rva)) {
|
|
BPLOG(ERROR) << "MinidumpModule could not seek to CodeView record";
|
|
return NULL;
|
|
}
|
|
|
|
if (module_.cv_record.data_size > max_cv_bytes_) {
|
|
BPLOG(ERROR) << "MinidumpModule CodeView record size " <<
|
|
module_.cv_record.data_size << " exceeds maximum " <<
|
|
max_cv_bytes_;
|
|
return NULL;
|
|
}
|
|
|
|
// Allocating something that will be accessed as MDCVInfoPDB70 or
|
|
// MDCVInfoPDB20 but is allocated as uint8_t[] can cause alignment
|
|
// problems. x86 and ppc are able to cope, though. This allocation
|
|
// style is needed because the MDCVInfoPDB70 or MDCVInfoPDB20 are
|
|
// variable-sized due to their pdb_file_name fields; these structures
|
|
// are not MDCVInfoPDB70_minsize or MDCVInfoPDB20_minsize and treating
|
|
// them as such would result in incomplete structures or overruns.
|
|
scoped_ptr< vector<uint8_t> > cv_record(
|
|
new vector<uint8_t>(module_.cv_record.data_size));
|
|
|
|
if (!minidump_->ReadBytes(&(*cv_record)[0], module_.cv_record.data_size)) {
|
|
BPLOG(ERROR) << "MinidumpModule could not read CodeView record";
|
|
return NULL;
|
|
}
|
|
|
|
uint32_t signature = MD_CVINFOUNKNOWN_SIGNATURE;
|
|
if (module_.cv_record.data_size > sizeof(signature)) {
|
|
MDCVInfoPDB70* cv_record_signature =
|
|
reinterpret_cast<MDCVInfoPDB70*>(&(*cv_record)[0]);
|
|
signature = cv_record_signature->cv_signature;
|
|
if (minidump_->swap())
|
|
Swap(&signature);
|
|
}
|
|
|
|
if (signature == MD_CVINFOPDB70_SIGNATURE) {
|
|
// Now that the structure type is known, recheck the size,
|
|
// ensuring at least one byte for the null terminator.
|
|
if (MDCVInfoPDB70_minsize + 1 > module_.cv_record.data_size) {
|
|
BPLOG(ERROR) << "MinidumpModule CodeView7 record size mismatch, " <<
|
|
MDCVInfoPDB70_minsize << " > " <<
|
|
module_.cv_record.data_size;
|
|
return NULL;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
MDCVInfoPDB70* cv_record_70 =
|
|
reinterpret_cast<MDCVInfoPDB70*>(&(*cv_record)[0]);
|
|
Swap(&cv_record_70->cv_signature);
|
|
Swap(&cv_record_70->signature);
|
|
Swap(&cv_record_70->age);
|
|
// Don't swap cv_record_70.pdb_file_name because it's an array of 8-bit
|
|
// quantities. (It's a path, is it UTF-8?)
|
|
}
|
|
|
|
// The last field of either structure is null-terminated 8-bit character
|
|
// data. Ensure that it's null-terminated.
|
|
if ((*cv_record)[module_.cv_record.data_size - 1] != '\0') {
|
|
BPLOG(ERROR) << "MinidumpModule CodeView7 record string is not "
|
|
"0-terminated";
|
|
return NULL;
|
|
}
|
|
} else if (signature == MD_CVINFOPDB20_SIGNATURE) {
|
|
// Now that the structure type is known, recheck the size,
|
|
// ensuring at least one byte for the null terminator.
|
|
if (MDCVInfoPDB20_minsize + 1 > module_.cv_record.data_size) {
|
|
BPLOG(ERROR) << "MinidumpModule CodeView2 record size mismatch, " <<
|
|
MDCVInfoPDB20_minsize << " > " <<
|
|
module_.cv_record.data_size;
|
|
return NULL;
|
|
}
|
|
if (minidump_->swap()) {
|
|
MDCVInfoPDB20* cv_record_20 =
|
|
reinterpret_cast<MDCVInfoPDB20*>(&(*cv_record)[0]);
|
|
Swap(&cv_record_20->cv_header.signature);
|
|
Swap(&cv_record_20->cv_header.offset);
|
|
Swap(&cv_record_20->signature);
|
|
Swap(&cv_record_20->age);
|
|
// Don't swap cv_record_20.pdb_file_name because it's an array of 8-bit
|
|
// quantities. (It's a path, is it UTF-8?)
|
|
}
|
|
|
|
// The last field of either structure is null-terminated 8-bit character
|
|
// data. Ensure that it's null-terminated.
|
|
if ((*cv_record)[module_.cv_record.data_size - 1] != '\0') {
|
|
BPLOG(ERROR) << "MindumpModule CodeView2 record string is not "
|
|
"0-terminated";
|
|
return NULL;
|
|
}
|
|
} else if (signature == MD_CVINFOELF_SIGNATURE) {
|
|
// Now that the structure type is known, recheck the size.
|
|
if (MDCVInfoELF_minsize > module_.cv_record.data_size) {
|
|
BPLOG(ERROR) << "MinidumpModule CodeViewELF record size mismatch, " <<
|
|
MDCVInfoELF_minsize << " > " <<
|
|
module_.cv_record.data_size;
|
|
return NULL;
|
|
}
|
|
// There's nothing to swap in CVInfoELF, it's just raw bytes.
|
|
}
|
|
|
|
// If the signature doesn't match something above, it's not something
|
|
// that Breakpad can presently handle directly. Because some modules in
|
|
// the wild contain such CodeView records as MD_CVINFOCV50_SIGNATURE,
|
|
// don't bail out here - allow the data to be returned to the user,
|
|
// although byte-swapping can't be done.
|
|
|
|
// Store the vector type because that's how storage was allocated, but
|
|
// return it casted to uint8_t*.
|
|
cv_record_ = cv_record.release();
|
|
cv_record_signature_ = signature;
|
|
}
|
|
|
|
if (size)
|
|
*size = module_.cv_record.data_size;
|
|
|
|
return &(*cv_record_)[0];
|
|
}
|
|
|
|
|
|
const MDImageDebugMisc* MinidumpModule::GetMiscRecord(uint32_t* size) {
|
|
if (!module_valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModule for GetMiscRecord";
|
|
return NULL;
|
|
}
|
|
|
|
if (!misc_record_) {
|
|
if (module_.misc_record.data_size == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (MDImageDebugMisc_minsize > module_.misc_record.data_size) {
|
|
BPLOG(ERROR) << "MinidumpModule miscellaneous debugging record "
|
|
"size mismatch, " << MDImageDebugMisc_minsize << " > " <<
|
|
module_.misc_record.data_size;
|
|
return NULL;
|
|
}
|
|
|
|
if (!minidump_->SeekSet(module_.misc_record.rva)) {
|
|
BPLOG(ERROR) << "MinidumpModule could not seek to miscellaneous "
|
|
"debugging record";
|
|
return NULL;
|
|
}
|
|
|
|
if (module_.misc_record.data_size > max_misc_bytes_) {
|
|
BPLOG(ERROR) << "MinidumpModule miscellaneous debugging record size " <<
|
|
module_.misc_record.data_size << " exceeds maximum " <<
|
|
max_misc_bytes_;
|
|
return NULL;
|
|
}
|
|
|
|
// Allocating something that will be accessed as MDImageDebugMisc but
|
|
// is allocated as uint8_t[] can cause alignment problems. x86 and
|
|
// ppc are able to cope, though. This allocation style is needed
|
|
// because the MDImageDebugMisc is variable-sized due to its data field;
|
|
// this structure is not MDImageDebugMisc_minsize and treating it as such
|
|
// would result in an incomplete structure or an overrun.
|
|
scoped_ptr< vector<uint8_t> > misc_record_mem(
|
|
new vector<uint8_t>(module_.misc_record.data_size));
|
|
MDImageDebugMisc* misc_record =
|
|
reinterpret_cast<MDImageDebugMisc*>(&(*misc_record_mem)[0]);
|
|
|
|
if (!minidump_->ReadBytes(misc_record, module_.misc_record.data_size)) {
|
|
BPLOG(ERROR) << "MinidumpModule could not read miscellaneous debugging "
|
|
"record";
|
|
return NULL;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&misc_record->data_type);
|
|
Swap(&misc_record->length);
|
|
// Don't swap misc_record.unicode because it's an 8-bit quantity.
|
|
// Don't swap the reserved fields for the same reason, and because
|
|
// they don't contain any valid data.
|
|
if (misc_record->unicode) {
|
|
// There is a potential alignment problem, but shouldn't be a problem
|
|
// in practice due to the layout of MDImageDebugMisc.
|
|
uint16_t* data16 = reinterpret_cast<uint16_t*>(&(misc_record->data));
|
|
unsigned int dataBytes = module_.misc_record.data_size -
|
|
MDImageDebugMisc_minsize;
|
|
Swap(data16, dataBytes);
|
|
}
|
|
}
|
|
|
|
if (module_.misc_record.data_size != misc_record->length) {
|
|
BPLOG(ERROR) << "MinidumpModule miscellaneous debugging record data "
|
|
"size mismatch, " << module_.misc_record.data_size <<
|
|
" != " << misc_record->length;
|
|
return NULL;
|
|
}
|
|
|
|
// Store the vector type because that's how storage was allocated, but
|
|
// return it casted to MDImageDebugMisc*.
|
|
misc_record_ = misc_record_mem.release();
|
|
}
|
|
|
|
if (size)
|
|
*size = module_.misc_record.data_size;
|
|
|
|
return reinterpret_cast<MDImageDebugMisc*>(&(*misc_record_)[0]);
|
|
}
|
|
|
|
|
|
void MinidumpModule::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpModule cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MDRawModule\n");
|
|
printf(" base_of_image = 0x%" PRIx64 "\n",
|
|
module_.base_of_image);
|
|
printf(" size_of_image = 0x%x\n",
|
|
module_.size_of_image);
|
|
printf(" checksum = 0x%x\n",
|
|
module_.checksum);
|
|
printf(" time_date_stamp = 0x%x %s\n",
|
|
module_.time_date_stamp,
|
|
TimeTToUTCString(module_.time_date_stamp).c_str());
|
|
printf(" module_name_rva = 0x%x\n",
|
|
module_.module_name_rva);
|
|
printf(" version_info.signature = 0x%x\n",
|
|
module_.version_info.signature);
|
|
printf(" version_info.struct_version = 0x%x\n",
|
|
module_.version_info.struct_version);
|
|
printf(" version_info.file_version = 0x%x:0x%x\n",
|
|
module_.version_info.file_version_hi,
|
|
module_.version_info.file_version_lo);
|
|
printf(" version_info.product_version = 0x%x:0x%x\n",
|
|
module_.version_info.product_version_hi,
|
|
module_.version_info.product_version_lo);
|
|
printf(" version_info.file_flags_mask = 0x%x\n",
|
|
module_.version_info.file_flags_mask);
|
|
printf(" version_info.file_flags = 0x%x\n",
|
|
module_.version_info.file_flags);
|
|
printf(" version_info.file_os = 0x%x\n",
|
|
module_.version_info.file_os);
|
|
printf(" version_info.file_type = 0x%x\n",
|
|
module_.version_info.file_type);
|
|
printf(" version_info.file_subtype = 0x%x\n",
|
|
module_.version_info.file_subtype);
|
|
printf(" version_info.file_date = 0x%x:0x%x\n",
|
|
module_.version_info.file_date_hi,
|
|
module_.version_info.file_date_lo);
|
|
printf(" cv_record.data_size = %d\n",
|
|
module_.cv_record.data_size);
|
|
printf(" cv_record.rva = 0x%x\n",
|
|
module_.cv_record.rva);
|
|
printf(" misc_record.data_size = %d\n",
|
|
module_.misc_record.data_size);
|
|
printf(" misc_record.rva = 0x%x\n",
|
|
module_.misc_record.rva);
|
|
|
|
printf(" (code_file) = \"%s\"\n", code_file().c_str());
|
|
printf(" (code_identifier) = \"%s\"\n",
|
|
code_identifier().c_str());
|
|
|
|
uint32_t cv_record_size;
|
|
const uint8_t *cv_record = GetCVRecord(&cv_record_size);
|
|
if (cv_record) {
|
|
if (cv_record_signature_ == MD_CVINFOPDB70_SIGNATURE) {
|
|
const MDCVInfoPDB70* cv_record_70 =
|
|
reinterpret_cast<const MDCVInfoPDB70*>(cv_record);
|
|
assert(cv_record_70->cv_signature == MD_CVINFOPDB70_SIGNATURE);
|
|
|
|
printf(" (cv_record).cv_signature = 0x%x\n",
|
|
cv_record_70->cv_signature);
|
|
printf(" (cv_record).signature = %08x-%04x-%04x-%02x%02x-",
|
|
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]);
|
|
for (unsigned int guidIndex = 2;
|
|
guidIndex < 8;
|
|
++guidIndex) {
|
|
printf("%02x", cv_record_70->signature.data4[guidIndex]);
|
|
}
|
|
printf("\n");
|
|
printf(" (cv_record).age = %d\n",
|
|
cv_record_70->age);
|
|
printf(" (cv_record).pdb_file_name = \"%s\"\n",
|
|
cv_record_70->pdb_file_name);
|
|
} else if (cv_record_signature_ == MD_CVINFOPDB20_SIGNATURE) {
|
|
const MDCVInfoPDB20* cv_record_20 =
|
|
reinterpret_cast<const MDCVInfoPDB20*>(cv_record);
|
|
assert(cv_record_20->cv_header.signature == MD_CVINFOPDB20_SIGNATURE);
|
|
|
|
printf(" (cv_record).cv_header.signature = 0x%x\n",
|
|
cv_record_20->cv_header.signature);
|
|
printf(" (cv_record).cv_header.offset = 0x%x\n",
|
|
cv_record_20->cv_header.offset);
|
|
printf(" (cv_record).signature = 0x%x %s\n",
|
|
cv_record_20->signature,
|
|
TimeTToUTCString(cv_record_20->signature).c_str());
|
|
printf(" (cv_record).age = %d\n",
|
|
cv_record_20->age);
|
|
printf(" (cv_record).pdb_file_name = \"%s\"\n",
|
|
cv_record_20->pdb_file_name);
|
|
} else if (cv_record_signature_ == MD_CVINFOELF_SIGNATURE) {
|
|
const MDCVInfoELF* cv_record_elf =
|
|
reinterpret_cast<const MDCVInfoELF*>(cv_record);
|
|
assert(cv_record_elf->cv_signature == MD_CVINFOELF_SIGNATURE);
|
|
|
|
printf(" (cv_record).cv_signature = 0x%x\n",
|
|
cv_record_elf->cv_signature);
|
|
printf(" (cv_record).build_id = ");
|
|
for (unsigned int build_id_index = 0;
|
|
build_id_index < (cv_record_size - MDCVInfoELF_minsize);
|
|
++build_id_index) {
|
|
printf("%02x", cv_record_elf->build_id[build_id_index]);
|
|
}
|
|
printf("\n");
|
|
} else {
|
|
printf(" (cv_record) = ");
|
|
for (unsigned int cv_byte_index = 0;
|
|
cv_byte_index < cv_record_size;
|
|
++cv_byte_index) {
|
|
printf("%02x", cv_record[cv_byte_index]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
} else {
|
|
printf(" (cv_record) = (null)\n");
|
|
}
|
|
|
|
const MDImageDebugMisc* misc_record = GetMiscRecord(NULL);
|
|
if (misc_record) {
|
|
printf(" (misc_record).data_type = 0x%x\n",
|
|
misc_record->data_type);
|
|
printf(" (misc_record).length = 0x%x\n",
|
|
misc_record->length);
|
|
printf(" (misc_record).unicode = %d\n",
|
|
misc_record->unicode);
|
|
if (misc_record->unicode) {
|
|
string misc_record_data_utf8;
|
|
ConvertUTF16BufferToUTF8String(
|
|
reinterpret_cast<const uint16_t*>(misc_record->data),
|
|
misc_record->length - offsetof(MDImageDebugMisc, data),
|
|
&misc_record_data_utf8,
|
|
false); // already swapped
|
|
printf(" (misc_record).data = \"%s\"\n",
|
|
misc_record_data_utf8.c_str());
|
|
} else {
|
|
printf(" (misc_record).data = \"%s\"\n",
|
|
misc_record->data);
|
|
}
|
|
} else {
|
|
printf(" (misc_record) = (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");
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpModuleList
|
|
//
|
|
|
|
|
|
uint32_t MinidumpModuleList::max_modules_ = 1024;
|
|
|
|
|
|
MinidumpModuleList::MinidumpModuleList(Minidump* minidump)
|
|
: MinidumpStream(minidump),
|
|
range_map_(new RangeMap<uint64_t, unsigned int>()),
|
|
modules_(NULL),
|
|
module_count_(0) {
|
|
range_map_->SetEnableShrinkDown(minidump_->IsAndroid());
|
|
}
|
|
|
|
|
|
MinidumpModuleList::~MinidumpModuleList() {
|
|
delete range_map_;
|
|
delete modules_;
|
|
}
|
|
|
|
|
|
bool MinidumpModuleList::Read(uint32_t expected_size) {
|
|
// Invalidate cached data.
|
|
range_map_->Clear();
|
|
delete modules_;
|
|
modules_ = NULL;
|
|
module_count_ = 0;
|
|
|
|
valid_ = false;
|
|
|
|
uint32_t module_count;
|
|
if (expected_size < sizeof(module_count)) {
|
|
BPLOG(ERROR) << "MinidumpModuleList count size mismatch, " <<
|
|
expected_size << " < " << sizeof(module_count);
|
|
return false;
|
|
}
|
|
if (!minidump_->ReadBytes(&module_count, sizeof(module_count))) {
|
|
BPLOG(ERROR) << "MinidumpModuleList could not read module count";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap())
|
|
Swap(&module_count);
|
|
|
|
if (module_count > numeric_limits<uint32_t>::max() / MD_MODULE_SIZE) {
|
|
BPLOG(ERROR) << "MinidumpModuleList module count " << module_count <<
|
|
" would cause multiplication overflow";
|
|
return false;
|
|
}
|
|
|
|
if (expected_size != sizeof(module_count) +
|
|
module_count * MD_MODULE_SIZE) {
|
|
// may be padded with 4 bytes on 64bit ABIs for alignment
|
|
if (expected_size == sizeof(module_count) + 4 +
|
|
module_count * MD_MODULE_SIZE) {
|
|
uint32_t useless;
|
|
if (!minidump_->ReadBytes(&useless, 4)) {
|
|
BPLOG(ERROR) << "MinidumpModuleList cannot read modulelist padded "
|
|
"bytes";
|
|
return false;
|
|
}
|
|
} else {
|
|
BPLOG(ERROR) << "MinidumpModuleList size mismatch, " << expected_size <<
|
|
" != " << sizeof(module_count) +
|
|
module_count * MD_MODULE_SIZE;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (module_count > max_modules_) {
|
|
BPLOG(ERROR) << "MinidumpModuleList count " << module_count_ <<
|
|
" exceeds maximum " << max_modules_;
|
|
return false;
|
|
}
|
|
|
|
if (module_count != 0) {
|
|
scoped_ptr<MinidumpModules> modules(
|
|
new MinidumpModules(module_count, MinidumpModule(minidump_)));
|
|
|
|
for (unsigned int module_index = 0;
|
|
module_index < module_count;
|
|
++module_index) {
|
|
MinidumpModule* module = &(*modules)[module_index];
|
|
|
|
// Assume that the file offset is correct after the last read.
|
|
if (!module->Read()) {
|
|
BPLOG(ERROR) << "MinidumpModuleList could not read module " <<
|
|
module_index << "/" << module_count;
|
|
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.
|
|
uint64_t last_end_address = 0;
|
|
for (unsigned int module_index = 0;
|
|
module_index < module_count;
|
|
++module_index) {
|
|
MinidumpModule* module = &(*modules)[module_index];
|
|
|
|
// ReadAuxiliaryData fails if any data that the module indicates should
|
|
// exist is missing, but we treat some such cases as valid anyway. See
|
|
// issue #222: if a debugging record is of a format that's too large to
|
|
// handle, it shouldn't render the entire dump invalid. Check module
|
|
// validity before giving up.
|
|
if (!module->ReadAuxiliaryData() && !module->valid()) {
|
|
BPLOG(ERROR) << "MinidumpModuleList could not read required module "
|
|
"auxiliary data for module " <<
|
|
module_index << "/" << module_count;
|
|
return false;
|
|
}
|
|
|
|
// It is safe to use module->code_file() after successfully calling
|
|
// module->ReadAuxiliaryData or noting that the module is valid.
|
|
|
|
uint64_t base_address = module->base_address();
|
|
uint64_t module_size = module->size();
|
|
if (base_address == static_cast<uint64_t>(-1)) {
|
|
BPLOG(ERROR) << "MinidumpModuleList found bad base address "
|
|
"for module " << module_index << "/" << module_count <<
|
|
", " << module->code_file();
|
|
return false;
|
|
}
|
|
|
|
if (!range_map_->StoreRange(base_address, module_size, module_index)) {
|
|
// Android's shared memory implementation /dev/ashmem can contain
|
|
// duplicate entries for JITted code, so ignore these.
|
|
// TODO(wfh): Remove this code when Android is fixed.
|
|
// See https://crbug.com/439531
|
|
const string kDevAshmem("/dev/ashmem/");
|
|
if (module->code_file().compare(
|
|
0, kDevAshmem.length(), kDevAshmem) != 0) {
|
|
if (base_address < last_end_address) {
|
|
// If failed due to apparent range overlap the cause may be
|
|
// the client correction applied for Android packed relocations.
|
|
// If this is the case, back out the client correction and retry.
|
|
module_size -= last_end_address - base_address;
|
|
base_address = last_end_address;
|
|
if (!range_map_->StoreRange(base_address,
|
|
module_size, module_index)) {
|
|
BPLOG(ERROR) << "MinidumpModuleList could not store module " <<
|
|
module_index << "/" << module_count << ", " <<
|
|
module->code_file() << ", " <<
|
|
HexString(base_address) << "+" <<
|
|
HexString(module_size) << ", after adjusting";
|
|
return false;
|
|
}
|
|
} else {
|
|
BPLOG(ERROR) << "MinidumpModuleList could not store module " <<
|
|
module_index << "/" << module_count << ", " <<
|
|
module->code_file() << ", " <<
|
|
HexString(base_address) << "+" <<
|
|
HexString(module_size);
|
|
return false;
|
|
}
|
|
} else {
|
|
BPLOG(INFO) << "MinidumpModuleList ignoring overlapping module " <<
|
|
module_index << "/" << module_count << ", " <<
|
|
module->code_file() << ", " <<
|
|
HexString(base_address) << "+" <<
|
|
HexString(module_size);
|
|
}
|
|
}
|
|
last_end_address = base_address + module_size;
|
|
}
|
|
|
|
modules_ = modules.release();
|
|
}
|
|
|
|
module_count_ = module_count;
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
const MinidumpModule* MinidumpModuleList::GetModuleForAddress(
|
|
uint64_t address) const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModuleList for GetModuleForAddress";
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int module_index;
|
|
if (!range_map_->RetrieveRange(address, &module_index, NULL /* base */,
|
|
NULL /* delta */, NULL /* size */)) {
|
|
BPLOG(INFO) << "MinidumpModuleList has no module at " <<
|
|
HexString(address);
|
|
return NULL;
|
|
}
|
|
|
|
return GetModuleAtIndex(module_index);
|
|
}
|
|
|
|
|
|
const MinidumpModule* MinidumpModuleList::GetMainModule() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModuleList for GetMainModule";
|
|
return NULL;
|
|
}
|
|
|
|
// The main code module is the first one present in a minidump file's
|
|
// MDRawModuleList.
|
|
return GetModuleAtIndex(0);
|
|
}
|
|
|
|
|
|
const MinidumpModule* MinidumpModuleList::GetModuleAtSequence(
|
|
unsigned int sequence) const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModuleList for GetModuleAtSequence";
|
|
return NULL;
|
|
}
|
|
|
|
if (sequence >= module_count_) {
|
|
BPLOG(ERROR) << "MinidumpModuleList sequence out of range: " <<
|
|
sequence << "/" << module_count_;
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int module_index;
|
|
if (!range_map_->RetrieveRangeAtIndex(sequence, &module_index,
|
|
NULL /* base */, NULL /* delta */,
|
|
NULL /* size */)) {
|
|
BPLOG(ERROR) << "MinidumpModuleList has no module at sequence " << sequence;
|
|
return NULL;
|
|
}
|
|
|
|
return GetModuleAtIndex(module_index);
|
|
}
|
|
|
|
|
|
const MinidumpModule* MinidumpModuleList::GetModuleAtIndex(
|
|
unsigned int index) const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpModuleList for GetModuleAtIndex";
|
|
return NULL;
|
|
}
|
|
|
|
if (index >= module_count_) {
|
|
BPLOG(ERROR) << "MinidumpModuleList index out of range: " <<
|
|
index << "/" << module_count_;
|
|
return NULL;
|
|
}
|
|
|
|
return &(*modules_)[index];
|
|
}
|
|
|
|
|
|
const CodeModules* MinidumpModuleList::Copy() const {
|
|
return new BasicCodeModules(this);
|
|
}
|
|
|
|
vector<linked_ptr<const CodeModule> >
|
|
MinidumpModuleList::GetShrunkRangeModules() const {
|
|
return vector<linked_ptr<const CodeModule> >();
|
|
}
|
|
|
|
bool MinidumpModuleList::IsModuleShrinkEnabled() const {
|
|
return range_map_->IsShrinkDownEnabled();
|
|
}
|
|
|
|
void MinidumpModuleList::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpModuleList cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MinidumpModuleList\n");
|
|
printf(" module_count = %d\n", module_count_);
|
|
printf("\n");
|
|
|
|
for (unsigned int module_index = 0;
|
|
module_index < module_count_;
|
|
++module_index) {
|
|
printf("module[%d]\n", module_index);
|
|
|
|
(*modules_)[module_index].Print();
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpMemoryList
|
|
//
|
|
|
|
|
|
uint32_t MinidumpMemoryList::max_regions_ = 4096;
|
|
|
|
|
|
MinidumpMemoryList::MinidumpMemoryList(Minidump* minidump)
|
|
: MinidumpStream(minidump),
|
|
range_map_(new RangeMap<uint64_t, unsigned int>()),
|
|
descriptors_(NULL),
|
|
regions_(NULL),
|
|
region_count_(0) {
|
|
}
|
|
|
|
|
|
MinidumpMemoryList::~MinidumpMemoryList() {
|
|
delete range_map_;
|
|
delete descriptors_;
|
|
delete regions_;
|
|
}
|
|
|
|
|
|
bool MinidumpMemoryList::Read(uint32_t expected_size) {
|
|
// Invalidate cached data.
|
|
delete descriptors_;
|
|
descriptors_ = NULL;
|
|
delete regions_;
|
|
regions_ = NULL;
|
|
range_map_->Clear();
|
|
region_count_ = 0;
|
|
|
|
valid_ = false;
|
|
|
|
uint32_t region_count;
|
|
if (expected_size < sizeof(region_count)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryList count size mismatch, " <<
|
|
expected_size << " < " << sizeof(region_count);
|
|
return false;
|
|
}
|
|
if (!minidump_->ReadBytes(®ion_count, sizeof(region_count))) {
|
|
BPLOG(ERROR) << "MinidumpMemoryList could not read memory region count";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap())
|
|
Swap(®ion_count);
|
|
|
|
if (region_count >
|
|
numeric_limits<uint32_t>::max() / sizeof(MDMemoryDescriptor)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryList region count " << region_count <<
|
|
" would cause multiplication overflow";
|
|
return false;
|
|
}
|
|
|
|
if (expected_size != sizeof(region_count) +
|
|
region_count * sizeof(MDMemoryDescriptor)) {
|
|
// may be padded with 4 bytes on 64bit ABIs for alignment
|
|
if (expected_size == sizeof(region_count) + 4 +
|
|
region_count * sizeof(MDMemoryDescriptor)) {
|
|
uint32_t useless;
|
|
if (!minidump_->ReadBytes(&useless, 4)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryList cannot read memorylist padded "
|
|
"bytes";
|
|
return false;
|
|
}
|
|
} else {
|
|
BPLOG(ERROR) << "MinidumpMemoryList size mismatch, " << expected_size <<
|
|
" != " << sizeof(region_count) +
|
|
region_count * sizeof(MDMemoryDescriptor);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (region_count > max_regions_) {
|
|
BPLOG(ERROR) << "MinidumpMemoryList count " << region_count <<
|
|
" exceeds maximum " << max_regions_;
|
|
return false;
|
|
}
|
|
|
|
if (region_count != 0) {
|
|
scoped_ptr<MemoryDescriptors> descriptors(
|
|
new MemoryDescriptors(region_count));
|
|
|
|
// Read the entire array in one fell swoop, instead of reading one entry
|
|
// at a time in the loop.
|
|
if (!minidump_->ReadBytes(&(*descriptors)[0],
|
|
sizeof(MDMemoryDescriptor) * region_count)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryList could not read memory region list";
|
|
return false;
|
|
}
|
|
|
|
scoped_ptr<MemoryRegions> regions(
|
|
new MemoryRegions(region_count, MinidumpMemoryRegion(minidump_)));
|
|
|
|
for (unsigned int region_index = 0;
|
|
region_index < region_count;
|
|
++region_index) {
|
|
MDMemoryDescriptor* descriptor = &(*descriptors)[region_index];
|
|
|
|
if (minidump_->swap())
|
|
Swap(descriptor);
|
|
|
|
uint64_t base_address = descriptor->start_of_memory_range;
|
|
uint32_t region_size = descriptor->memory.data_size;
|
|
|
|
// Check for base + size overflow or undersize.
|
|
if (region_size == 0 ||
|
|
region_size > numeric_limits<uint64_t>::max() - base_address) {
|
|
BPLOG(ERROR) << "MinidumpMemoryList has a memory region problem, " <<
|
|
" region " << region_index << "/" << region_count <<
|
|
", " << HexString(base_address) << "+" <<
|
|
HexString(region_size);
|
|
return false;
|
|
}
|
|
|
|
if (!range_map_->StoreRange(base_address, region_size, region_index)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryList could not store memory region " <<
|
|
region_index << "/" << region_count << ", " <<
|
|
HexString(base_address) << "+" <<
|
|
HexString(region_size);
|
|
return false;
|
|
}
|
|
|
|
(*regions)[region_index].SetDescriptor(descriptor);
|
|
}
|
|
|
|
descriptors_ = descriptors.release();
|
|
regions_ = regions.release();
|
|
}
|
|
|
|
region_count_ = region_count;
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
MinidumpMemoryRegion* MinidumpMemoryList::GetMemoryRegionAtIndex(
|
|
unsigned int index) {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpMemoryList for GetMemoryRegionAtIndex";
|
|
return NULL;
|
|
}
|
|
|
|
if (index >= region_count_) {
|
|
BPLOG(ERROR) << "MinidumpMemoryList index out of range: " <<
|
|
index << "/" << region_count_;
|
|
return NULL;
|
|
}
|
|
|
|
return &(*regions_)[index];
|
|
}
|
|
|
|
|
|
MinidumpMemoryRegion* MinidumpMemoryList::GetMemoryRegionForAddress(
|
|
uint64_t address) {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpMemoryList for GetMemoryRegionForAddress";
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int region_index;
|
|
if (!range_map_->RetrieveRange(address, ®ion_index, NULL /* base */,
|
|
NULL /* delta */, NULL /* size */)) {
|
|
BPLOG(INFO) << "MinidumpMemoryList has no memory region at " <<
|
|
HexString(address);
|
|
return NULL;
|
|
}
|
|
|
|
return GetMemoryRegionAtIndex(region_index);
|
|
}
|
|
|
|
|
|
void MinidumpMemoryList::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpMemoryList cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MinidumpMemoryList\n");
|
|
printf(" region_count = %d\n", region_count_);
|
|
printf("\n");
|
|
|
|
for (unsigned int region_index = 0;
|
|
region_index < region_count_;
|
|
++region_index) {
|
|
MDMemoryDescriptor* descriptor = &(*descriptors_)[region_index];
|
|
printf("region[%d]\n", region_index);
|
|
printf("MDMemoryDescriptor\n");
|
|
printf(" start_of_memory_range = 0x%" PRIx64 "\n",
|
|
descriptor->start_of_memory_range);
|
|
printf(" memory.data_size = 0x%x\n", descriptor->memory.data_size);
|
|
printf(" memory.rva = 0x%x\n", descriptor->memory.rva);
|
|
MinidumpMemoryRegion* region = GetMemoryRegionAtIndex(region_index);
|
|
if (region) {
|
|
printf("Memory\n");
|
|
region->Print();
|
|
} else {
|
|
printf("No memory\n");
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpException
|
|
//
|
|
|
|
|
|
MinidumpException::MinidumpException(Minidump* minidump)
|
|
: MinidumpStream(minidump),
|
|
exception_(),
|
|
context_(NULL) {
|
|
}
|
|
|
|
|
|
MinidumpException::~MinidumpException() {
|
|
delete context_;
|
|
}
|
|
|
|
|
|
bool MinidumpException::Read(uint32_t expected_size) {
|
|
// Invalidate cached data.
|
|
delete context_;
|
|
context_ = NULL;
|
|
|
|
valid_ = false;
|
|
|
|
if (expected_size != sizeof(exception_)) {
|
|
BPLOG(ERROR) << "MinidumpException size mismatch, " << expected_size <<
|
|
" != " << sizeof(exception_);
|
|
return false;
|
|
}
|
|
|
|
if (!minidump_->ReadBytes(&exception_, sizeof(exception_))) {
|
|
BPLOG(ERROR) << "MinidumpException cannot read exception";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&exception_.thread_id);
|
|
// exception_.__align is for alignment only and does not need to be
|
|
// swapped.
|
|
Swap(&exception_.exception_record.exception_code);
|
|
Swap(&exception_.exception_record.exception_flags);
|
|
Swap(&exception_.exception_record.exception_record);
|
|
Swap(&exception_.exception_record.exception_address);
|
|
Swap(&exception_.exception_record.number_parameters);
|
|
// exception_.exception_record.__align is for alignment only and does not
|
|
// need to be swapped.
|
|
for (unsigned int parameter_index = 0;
|
|
parameter_index < MD_EXCEPTION_MAXIMUM_PARAMETERS;
|
|
++parameter_index) {
|
|
Swap(&exception_.exception_record.exception_information[parameter_index]);
|
|
}
|
|
Swap(&exception_.thread_context);
|
|
}
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool MinidumpException::GetThreadID(uint32_t *thread_id) const {
|
|
BPLOG_IF(ERROR, !thread_id) << "MinidumpException::GetThreadID requires "
|
|
"|thread_id|";
|
|
assert(thread_id);
|
|
*thread_id = 0;
|
|
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpException for GetThreadID";
|
|
return false;
|
|
}
|
|
|
|
*thread_id = exception_.thread_id;
|
|
return true;
|
|
}
|
|
|
|
|
|
MinidumpContext* MinidumpException::GetContext() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpException for GetContext";
|
|
return NULL;
|
|
}
|
|
|
|
if (!context_) {
|
|
if (!minidump_->SeekSet(exception_.thread_context.rva)) {
|
|
BPLOG(ERROR) << "MinidumpException cannot seek to context";
|
|
return NULL;
|
|
}
|
|
|
|
scoped_ptr<MinidumpContext> context(new MinidumpContext(minidump_));
|
|
|
|
// Don't log as an error if we can still fall back on the thread's context
|
|
// (which must be possible if we got this far.)
|
|
if (!context->Read(exception_.thread_context.data_size)) {
|
|
BPLOG(INFO) << "MinidumpException cannot read context";
|
|
return NULL;
|
|
}
|
|
|
|
context_ = context.release();
|
|
}
|
|
|
|
return context_;
|
|
}
|
|
|
|
|
|
void MinidumpException::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpException cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MDException\n");
|
|
printf(" thread_id = 0x%x\n",
|
|
exception_.thread_id);
|
|
printf(" exception_record.exception_code = 0x%x\n",
|
|
exception_.exception_record.exception_code);
|
|
printf(" exception_record.exception_flags = 0x%x\n",
|
|
exception_.exception_record.exception_flags);
|
|
printf(" exception_record.exception_record = 0x%" PRIx64 "\n",
|
|
exception_.exception_record.exception_record);
|
|
printf(" exception_record.exception_address = 0x%" PRIx64 "\n",
|
|
exception_.exception_record.exception_address);
|
|
printf(" exception_record.number_parameters = %d\n",
|
|
exception_.exception_record.number_parameters);
|
|
for (unsigned int parameterIndex = 0;
|
|
parameterIndex < exception_.exception_record.number_parameters;
|
|
++parameterIndex) {
|
|
printf(" exception_record.exception_information[%2d] = 0x%" PRIx64 "\n",
|
|
parameterIndex,
|
|
exception_.exception_record.exception_information[parameterIndex]);
|
|
}
|
|
printf(" thread_context.data_size = %d\n",
|
|
exception_.thread_context.data_size);
|
|
printf(" thread_context.rva = 0x%x\n",
|
|
exception_.thread_context.rva);
|
|
MinidumpContext* context = GetContext();
|
|
if (context) {
|
|
printf("\n");
|
|
context->Print();
|
|
} else {
|
|
printf(" (no context)\n");
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
//
|
|
// MinidumpAssertion
|
|
//
|
|
|
|
|
|
MinidumpAssertion::MinidumpAssertion(Minidump* minidump)
|
|
: MinidumpStream(minidump),
|
|
assertion_(),
|
|
expression_(),
|
|
function_(),
|
|
file_() {
|
|
}
|
|
|
|
|
|
MinidumpAssertion::~MinidumpAssertion() {
|
|
}
|
|
|
|
|
|
bool MinidumpAssertion::Read(uint32_t expected_size) {
|
|
// Invalidate cached data.
|
|
valid_ = false;
|
|
|
|
if (expected_size != sizeof(assertion_)) {
|
|
BPLOG(ERROR) << "MinidumpAssertion size mismatch, " << expected_size <<
|
|
" != " << sizeof(assertion_);
|
|
return false;
|
|
}
|
|
|
|
if (!minidump_->ReadBytes(&assertion_, sizeof(assertion_))) {
|
|
BPLOG(ERROR) << "MinidumpAssertion cannot read assertion";
|
|
return false;
|
|
}
|
|
|
|
// Each of {expression, function, file} is a UTF-16 string,
|
|
// we'll convert them to UTF-8 for ease of use.
|
|
ConvertUTF16BufferToUTF8String(assertion_.expression,
|
|
sizeof(assertion_.expression), &expression_,
|
|
minidump_->swap());
|
|
ConvertUTF16BufferToUTF8String(assertion_.function,
|
|
sizeof(assertion_.function), &function_,
|
|
minidump_->swap());
|
|
ConvertUTF16BufferToUTF8String(assertion_.file, sizeof(assertion_.file),
|
|
&file_, minidump_->swap());
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&assertion_.line);
|
|
Swap(&assertion_.type);
|
|
}
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
void MinidumpAssertion::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpAssertion cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MDAssertion\n");
|
|
printf(" expression = %s\n",
|
|
expression_.c_str());
|
|
printf(" function = %s\n",
|
|
function_.c_str());
|
|
printf(" file = %s\n",
|
|
file_.c_str());
|
|
printf(" line = %u\n",
|
|
assertion_.line);
|
|
printf(" type = %u\n",
|
|
assertion_.type);
|
|
printf("\n");
|
|
}
|
|
|
|
//
|
|
// MinidumpSystemInfo
|
|
//
|
|
|
|
|
|
MinidumpSystemInfo::MinidumpSystemInfo(Minidump* minidump)
|
|
: MinidumpStream(minidump),
|
|
system_info_(),
|
|
csd_version_(NULL),
|
|
cpu_vendor_(NULL) {
|
|
}
|
|
|
|
|
|
MinidumpSystemInfo::~MinidumpSystemInfo() {
|
|
delete csd_version_;
|
|
delete cpu_vendor_;
|
|
}
|
|
|
|
|
|
bool MinidumpSystemInfo::Read(uint32_t expected_size) {
|
|
// Invalidate cached data.
|
|
delete csd_version_;
|
|
csd_version_ = NULL;
|
|
delete cpu_vendor_;
|
|
cpu_vendor_ = NULL;
|
|
|
|
valid_ = false;
|
|
|
|
if (expected_size != sizeof(system_info_)) {
|
|
BPLOG(ERROR) << "MinidumpSystemInfo size mismatch, " << expected_size <<
|
|
" != " << sizeof(system_info_);
|
|
return false;
|
|
}
|
|
|
|
if (!minidump_->ReadBytes(&system_info_, sizeof(system_info_))) {
|
|
BPLOG(ERROR) << "MinidumpSystemInfo cannot read system info";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&system_info_.processor_architecture);
|
|
Swap(&system_info_.processor_level);
|
|
Swap(&system_info_.processor_revision);
|
|
// number_of_processors and product_type are 8-bit quantities and need no
|
|
// swapping.
|
|
Swap(&system_info_.major_version);
|
|
Swap(&system_info_.minor_version);
|
|
Swap(&system_info_.build_number);
|
|
Swap(&system_info_.platform_id);
|
|
Swap(&system_info_.csd_version_rva);
|
|
Swap(&system_info_.suite_mask);
|
|
// Don't swap the reserved2 field because its contents are unknown.
|
|
|
|
if (system_info_.processor_architecture == MD_CPU_ARCHITECTURE_X86 ||
|
|
system_info_.processor_architecture == MD_CPU_ARCHITECTURE_X86_WIN64) {
|
|
for (unsigned int i = 0; i < 3; ++i)
|
|
Swap(&system_info_.cpu.x86_cpu_info.vendor_id[i]);
|
|
Swap(&system_info_.cpu.x86_cpu_info.version_information);
|
|
Swap(&system_info_.cpu.x86_cpu_info.feature_information);
|
|
Swap(&system_info_.cpu.x86_cpu_info.amd_extended_cpu_features);
|
|
} else {
|
|
for (unsigned int i = 0; i < 2; ++i)
|
|
Swap(&system_info_.cpu.other_cpu_info.processor_features[i]);
|
|
}
|
|
}
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
string MinidumpSystemInfo::GetOS() {
|
|
string os;
|
|
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpSystemInfo for GetOS";
|
|
return os;
|
|
}
|
|
|
|
switch (system_info_.platform_id) {
|
|
case MD_OS_WIN32_NT:
|
|
case MD_OS_WIN32_WINDOWS:
|
|
os = "windows";
|
|
break;
|
|
|
|
case MD_OS_MAC_OS_X:
|
|
os = "mac";
|
|
break;
|
|
|
|
case MD_OS_IOS:
|
|
os = "ios";
|
|
break;
|
|
|
|
case MD_OS_LINUX:
|
|
os = "linux";
|
|
break;
|
|
|
|
case MD_OS_SOLARIS:
|
|
os = "solaris";
|
|
break;
|
|
|
|
case MD_OS_ANDROID:
|
|
os = "android";
|
|
break;
|
|
|
|
case MD_OS_PS3:
|
|
os = "ps3";
|
|
break;
|
|
|
|
case MD_OS_NACL:
|
|
os = "nacl";
|
|
break;
|
|
|
|
default:
|
|
BPLOG(ERROR) << "MinidumpSystemInfo unknown OS for platform " <<
|
|
HexString(system_info_.platform_id);
|
|
break;
|
|
}
|
|
|
|
return os;
|
|
}
|
|
|
|
|
|
string MinidumpSystemInfo::GetCPU() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpSystemInfo for GetCPU";
|
|
return "";
|
|
}
|
|
|
|
string cpu;
|
|
|
|
switch (system_info_.processor_architecture) {
|
|
case MD_CPU_ARCHITECTURE_X86:
|
|
case MD_CPU_ARCHITECTURE_X86_WIN64:
|
|
cpu = "x86";
|
|
break;
|
|
|
|
case MD_CPU_ARCHITECTURE_AMD64:
|
|
cpu = "x86-64";
|
|
break;
|
|
|
|
case MD_CPU_ARCHITECTURE_PPC:
|
|
cpu = "ppc";
|
|
break;
|
|
|
|
case MD_CPU_ARCHITECTURE_PPC64:
|
|
cpu = "ppc64";
|
|
break;
|
|
|
|
case MD_CPU_ARCHITECTURE_SPARC:
|
|
cpu = "sparc";
|
|
break;
|
|
|
|
case MD_CPU_ARCHITECTURE_ARM:
|
|
cpu = "arm";
|
|
break;
|
|
|
|
case MD_CPU_ARCHITECTURE_ARM64:
|
|
cpu = "arm64";
|
|
break;
|
|
|
|
default:
|
|
BPLOG(ERROR) << "MinidumpSystemInfo unknown CPU for architecture " <<
|
|
HexString(system_info_.processor_architecture);
|
|
break;
|
|
}
|
|
|
|
return cpu;
|
|
}
|
|
|
|
|
|
const string* MinidumpSystemInfo::GetCSDVersion() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpSystemInfo for GetCSDVersion";
|
|
return NULL;
|
|
}
|
|
|
|
if (!csd_version_)
|
|
csd_version_ = minidump_->ReadString(system_info_.csd_version_rva);
|
|
|
|
BPLOG_IF(ERROR, !csd_version_) << "MinidumpSystemInfo could not read "
|
|
"CSD version";
|
|
|
|
return csd_version_;
|
|
}
|
|
|
|
|
|
const string* MinidumpSystemInfo::GetCPUVendor() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpSystemInfo for GetCPUVendor";
|
|
return NULL;
|
|
}
|
|
|
|
// CPU vendor information can only be determined from x86 minidumps.
|
|
if (!cpu_vendor_ &&
|
|
(system_info_.processor_architecture == MD_CPU_ARCHITECTURE_X86 ||
|
|
system_info_.processor_architecture == MD_CPU_ARCHITECTURE_X86_WIN64)) {
|
|
char cpu_vendor_string[13];
|
|
snprintf(cpu_vendor_string, sizeof(cpu_vendor_string),
|
|
"%c%c%c%c%c%c%c%c%c%c%c%c",
|
|
system_info_.cpu.x86_cpu_info.vendor_id[0] & 0xff,
|
|
(system_info_.cpu.x86_cpu_info.vendor_id[0] >> 8) & 0xff,
|
|
(system_info_.cpu.x86_cpu_info.vendor_id[0] >> 16) & 0xff,
|
|
(system_info_.cpu.x86_cpu_info.vendor_id[0] >> 24) & 0xff,
|
|
system_info_.cpu.x86_cpu_info.vendor_id[1] & 0xff,
|
|
(system_info_.cpu.x86_cpu_info.vendor_id[1] >> 8) & 0xff,
|
|
(system_info_.cpu.x86_cpu_info.vendor_id[1] >> 16) & 0xff,
|
|
(system_info_.cpu.x86_cpu_info.vendor_id[1] >> 24) & 0xff,
|
|
system_info_.cpu.x86_cpu_info.vendor_id[2] & 0xff,
|
|
(system_info_.cpu.x86_cpu_info.vendor_id[2] >> 8) & 0xff,
|
|
(system_info_.cpu.x86_cpu_info.vendor_id[2] >> 16) & 0xff,
|
|
(system_info_.cpu.x86_cpu_info.vendor_id[2] >> 24) & 0xff);
|
|
cpu_vendor_ = new string(cpu_vendor_string);
|
|
}
|
|
|
|
return cpu_vendor_;
|
|
}
|
|
|
|
|
|
void MinidumpSystemInfo::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpSystemInfo cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MDRawSystemInfo\n");
|
|
printf(" processor_architecture = 0x%x\n",
|
|
system_info_.processor_architecture);
|
|
printf(" processor_level = %d\n",
|
|
system_info_.processor_level);
|
|
printf(" processor_revision = 0x%x\n",
|
|
system_info_.processor_revision);
|
|
printf(" number_of_processors = %d\n",
|
|
system_info_.number_of_processors);
|
|
printf(" product_type = %d\n",
|
|
system_info_.product_type);
|
|
printf(" major_version = %d\n",
|
|
system_info_.major_version);
|
|
printf(" minor_version = %d\n",
|
|
system_info_.minor_version);
|
|
printf(" build_number = %d\n",
|
|
system_info_.build_number);
|
|
printf(" platform_id = 0x%x\n",
|
|
system_info_.platform_id);
|
|
printf(" csd_version_rva = 0x%x\n",
|
|
system_info_.csd_version_rva);
|
|
printf(" suite_mask = 0x%x\n",
|
|
system_info_.suite_mask);
|
|
if (system_info_.processor_architecture == MD_CPU_ARCHITECTURE_X86 ||
|
|
system_info_.processor_architecture == MD_CPU_ARCHITECTURE_X86_WIN64) {
|
|
printf(" cpu.x86_cpu_info (valid):\n");
|
|
} else {
|
|
printf(" cpu.x86_cpu_info (invalid):\n");
|
|
}
|
|
for (unsigned int i = 0; i < 3; ++i) {
|
|
printf(" cpu.x86_cpu_info.vendor_id[%d] = 0x%x\n",
|
|
i, system_info_.cpu.x86_cpu_info.vendor_id[i]);
|
|
}
|
|
printf(" cpu.x86_cpu_info.version_information = 0x%x\n",
|
|
system_info_.cpu.x86_cpu_info.version_information);
|
|
printf(" cpu.x86_cpu_info.feature_information = 0x%x\n",
|
|
system_info_.cpu.x86_cpu_info.feature_information);
|
|
printf(" cpu.x86_cpu_info.amd_extended_cpu_features = 0x%x\n",
|
|
system_info_.cpu.x86_cpu_info.amd_extended_cpu_features);
|
|
if (system_info_.processor_architecture != MD_CPU_ARCHITECTURE_X86 &&
|
|
system_info_.processor_architecture != MD_CPU_ARCHITECTURE_X86_WIN64) {
|
|
printf(" cpu.other_cpu_info (valid):\n");
|
|
for (unsigned int i = 0; i < 2; ++i) {
|
|
printf(" cpu.other_cpu_info.processor_features[%d] = 0x%" PRIx64 "\n",
|
|
i, system_info_.cpu.other_cpu_info.processor_features[i]);
|
|
}
|
|
}
|
|
const string* csd_version = GetCSDVersion();
|
|
if (csd_version) {
|
|
printf(" (csd_version) = \"%s\"\n",
|
|
csd_version->c_str());
|
|
} else {
|
|
printf(" (csd_version) = (null)\n");
|
|
}
|
|
const string* cpu_vendor = GetCPUVendor();
|
|
if (cpu_vendor) {
|
|
printf(" (cpu_vendor) = \"%s\"\n",
|
|
cpu_vendor->c_str());
|
|
} else {
|
|
printf(" (cpu_vendor) = (null)\n");
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpUnloadedModule
|
|
//
|
|
|
|
|
|
MinidumpUnloadedModule::MinidumpUnloadedModule(Minidump* minidump)
|
|
: MinidumpObject(minidump),
|
|
module_valid_(false),
|
|
unloaded_module_(),
|
|
name_(NULL) {
|
|
|
|
}
|
|
|
|
MinidumpUnloadedModule::~MinidumpUnloadedModule() {
|
|
delete name_;
|
|
}
|
|
|
|
string MinidumpUnloadedModule::code_file() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpUnloadedModule for code_file";
|
|
return "";
|
|
}
|
|
|
|
return *name_;
|
|
}
|
|
|
|
string MinidumpUnloadedModule::code_identifier() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpUnloadedModule for code_identifier";
|
|
return "";
|
|
}
|
|
|
|
MinidumpSystemInfo *minidump_system_info = minidump_->GetSystemInfo();
|
|
if (!minidump_system_info) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModule code_identifier requires "
|
|
"MinidumpSystemInfo";
|
|
return "";
|
|
}
|
|
|
|
const MDRawSystemInfo *raw_system_info = minidump_system_info->system_info();
|
|
if (!raw_system_info) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModule code_identifier requires "
|
|
<< "MDRawSystemInfo";
|
|
return "";
|
|
}
|
|
|
|
string identifier;
|
|
|
|
switch (raw_system_info->platform_id) {
|
|
case MD_OS_WIN32_NT:
|
|
case MD_OS_WIN32_WINDOWS: {
|
|
// Use the same format that the MS symbol server uses in filesystem
|
|
// hierarchies.
|
|
char identifier_string[17];
|
|
snprintf(identifier_string, sizeof(identifier_string), "%08X%x",
|
|
unloaded_module_.time_date_stamp,
|
|
unloaded_module_.size_of_image);
|
|
identifier = identifier_string;
|
|
break;
|
|
}
|
|
|
|
case MD_OS_ANDROID:
|
|
case MD_OS_LINUX:
|
|
case MD_OS_MAC_OS_X:
|
|
case MD_OS_IOS:
|
|
case MD_OS_SOLARIS:
|
|
case MD_OS_NACL:
|
|
case MD_OS_PS3: {
|
|
// 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.
|
|
BPLOG(ERROR) << "MinidumpUnloadedModule code_identifier requires known "
|
|
<< "platform, found "
|
|
<< HexString(raw_system_info->platform_id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return identifier;
|
|
}
|
|
|
|
string MinidumpUnloadedModule::debug_file() const {
|
|
return ""; // No debug info provided with unloaded modules
|
|
}
|
|
|
|
string MinidumpUnloadedModule::debug_identifier() const {
|
|
return ""; // No debug info provided with unloaded modules
|
|
}
|
|
|
|
string MinidumpUnloadedModule::version() const {
|
|
return ""; // No version info provided with unloaded modules
|
|
}
|
|
|
|
CodeModule* MinidumpUnloadedModule::Copy() const {
|
|
return new BasicCodeModule(this);
|
|
}
|
|
|
|
uint64_t MinidumpUnloadedModule::shrink_down_delta() const {
|
|
return 0;
|
|
}
|
|
|
|
void MinidumpUnloadedModule::SetShrinkDownDelta(uint64_t shrink_down_delta) {
|
|
// Not implemented
|
|
assert(false);
|
|
}
|
|
|
|
bool MinidumpUnloadedModule::Read(uint32_t expected_size) {
|
|
|
|
delete name_;
|
|
valid_ = false;
|
|
|
|
if (expected_size < sizeof(unloaded_module_)) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModule expected size is less than size "
|
|
<< "of struct " << expected_size << " < "
|
|
<< sizeof(unloaded_module_);
|
|
return false;
|
|
}
|
|
|
|
if (!minidump_->ReadBytes(&unloaded_module_, sizeof(unloaded_module_))) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModule cannot read module";
|
|
return false;
|
|
}
|
|
|
|
if (expected_size > sizeof(unloaded_module_)) {
|
|
uint32_t module_bytes_remaining = expected_size - sizeof(unloaded_module_);
|
|
off_t pos = minidump_->Tell();
|
|
if (!minidump_->SeekSet(pos + module_bytes_remaining)) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModule unable to seek to end of module";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&unloaded_module_.base_of_image);
|
|
Swap(&unloaded_module_.size_of_image);
|
|
Swap(&unloaded_module_.checksum);
|
|
Swap(&unloaded_module_.time_date_stamp);
|
|
Swap(&unloaded_module_.module_name_rva);
|
|
}
|
|
|
|
// Check for base + size overflow or undersize.
|
|
if (unloaded_module_.size_of_image == 0 ||
|
|
unloaded_module_.size_of_image >
|
|
numeric_limits<uint64_t>::max() - unloaded_module_.base_of_image) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModule has a module problem, " <<
|
|
HexString(unloaded_module_.base_of_image) << "+" <<
|
|
HexString(unloaded_module_.size_of_image);
|
|
return false;
|
|
}
|
|
|
|
|
|
module_valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
bool MinidumpUnloadedModule::ReadAuxiliaryData() {
|
|
if (!module_valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpUnloadedModule for ReadAuxiliaryData";
|
|
return false;
|
|
}
|
|
|
|
// Each module must have a name.
|
|
name_ = minidump_->ReadString(unloaded_module_.module_name_rva);
|
|
if (!name_) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModule could not read name";
|
|
return false;
|
|
}
|
|
|
|
// At this point, we have enough info for the module to be valid.
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// MinidumpUnloadedModuleList
|
|
//
|
|
|
|
|
|
uint32_t MinidumpUnloadedModuleList::max_modules_ = 1024;
|
|
|
|
|
|
MinidumpUnloadedModuleList::MinidumpUnloadedModuleList(Minidump* minidump)
|
|
: MinidumpStream(minidump),
|
|
range_map_(new RangeMap<uint64_t, unsigned int>()),
|
|
unloaded_modules_(NULL),
|
|
module_count_(0) {
|
|
range_map_->SetEnableShrinkDown(true);
|
|
}
|
|
|
|
MinidumpUnloadedModuleList::~MinidumpUnloadedModuleList() {
|
|
delete range_map_;
|
|
delete unloaded_modules_;
|
|
}
|
|
|
|
|
|
bool MinidumpUnloadedModuleList::Read(uint32_t expected_size) {
|
|
range_map_->Clear();
|
|
delete unloaded_modules_;
|
|
unloaded_modules_ = NULL;
|
|
module_count_ = 0;
|
|
|
|
valid_ = false;
|
|
|
|
uint32_t size_of_header;
|
|
if (!minidump_->ReadBytes(&size_of_header, sizeof(size_of_header))) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModuleList could not read header size";
|
|
return false;
|
|
}
|
|
|
|
uint32_t size_of_entry;
|
|
if (!minidump_->ReadBytes(&size_of_entry, sizeof(size_of_entry))) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModuleList could not read entry size";
|
|
return false;
|
|
}
|
|
|
|
uint32_t number_of_entries;
|
|
if (!minidump_->ReadBytes(&number_of_entries, sizeof(number_of_entries))) {
|
|
BPLOG(ERROR) <<
|
|
"MinidumpUnloadedModuleList could not read number of entries";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&size_of_header);
|
|
Swap(&size_of_entry);
|
|
Swap(&number_of_entries);
|
|
}
|
|
|
|
uint32_t header_bytes_remaining = size_of_header - sizeof(size_of_header) -
|
|
sizeof(size_of_entry) - sizeof(number_of_entries);
|
|
if (header_bytes_remaining) {
|
|
off_t pos = minidump_->Tell();
|
|
if (!minidump_->SeekSet(pos + header_bytes_remaining)) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModuleList could not read header sized "
|
|
<< size_of_header;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (expected_size != size_of_header + (size_of_entry * number_of_entries)) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModuleList expected_size mismatch " <<
|
|
expected_size << " != " << size_of_header << " + (" <<
|
|
size_of_entry << " * " << number_of_entries << ")";
|
|
return false;
|
|
}
|
|
|
|
if (number_of_entries > max_modules_) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModuleList count " <<
|
|
number_of_entries << " exceeds maximum " << max_modules_;
|
|
return false;
|
|
}
|
|
|
|
if (number_of_entries != 0) {
|
|
scoped_ptr<MinidumpUnloadedModules> modules(
|
|
new MinidumpUnloadedModules(number_of_entries,
|
|
MinidumpUnloadedModule(minidump_)));
|
|
|
|
for (unsigned int module_index = 0;
|
|
module_index < number_of_entries;
|
|
++module_index) {
|
|
MinidumpUnloadedModule* module = &(*modules)[module_index];
|
|
|
|
if (!module->Read(size_of_entry)) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModuleList could not read module " <<
|
|
module_index << "/" << number_of_entries;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (unsigned int module_index = 0;
|
|
module_index < number_of_entries;
|
|
++module_index) {
|
|
MinidumpUnloadedModule* module = &(*modules)[module_index];
|
|
|
|
if (!module->ReadAuxiliaryData()) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModuleList could not read required "
|
|
"module auxiliary data for module " <<
|
|
module_index << "/" << number_of_entries;
|
|
return false;
|
|
}
|
|
|
|
uint64_t base_address = module->base_address();
|
|
uint64_t module_size = module->size();
|
|
|
|
// Ignore any failures for conflicting address ranges
|
|
range_map_->StoreRange(base_address, module_size, module_index);
|
|
|
|
}
|
|
unloaded_modules_ = modules.release();
|
|
}
|
|
|
|
module_count_ = number_of_entries;
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
const MinidumpUnloadedModule* MinidumpUnloadedModuleList::GetModuleForAddress(
|
|
uint64_t address) const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR)
|
|
<< "Invalid MinidumpUnloadedModuleList for GetModuleForAddress";
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int module_index;
|
|
if (!range_map_->RetrieveRange(address, &module_index, NULL /* base */,
|
|
NULL /* delta */, NULL /* size */)) {
|
|
BPLOG(INFO) << "MinidumpUnloadedModuleList has no module at "
|
|
<< HexString(address);
|
|
return NULL;
|
|
}
|
|
|
|
return GetModuleAtIndex(module_index);
|
|
}
|
|
|
|
const MinidumpUnloadedModule*
|
|
MinidumpUnloadedModuleList::GetMainModule() const {
|
|
return NULL;
|
|
}
|
|
|
|
const MinidumpUnloadedModule*
|
|
MinidumpUnloadedModuleList::GetModuleAtSequence(unsigned int sequence) const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR)
|
|
<< "Invalid MinidumpUnloadedModuleList for GetModuleAtSequence";
|
|
return NULL;
|
|
}
|
|
|
|
if (sequence >= module_count_) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModuleList sequence out of range: "
|
|
<< sequence << "/" << module_count_;
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int module_index;
|
|
if (!range_map_->RetrieveRangeAtIndex(sequence, &module_index,
|
|
NULL /* base */, NULL /* delta */,
|
|
NULL /* size */)) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModuleList has no module at sequence "
|
|
<< sequence;
|
|
return NULL;
|
|
}
|
|
|
|
return GetModuleAtIndex(module_index);
|
|
}
|
|
|
|
const MinidumpUnloadedModule*
|
|
MinidumpUnloadedModuleList::GetModuleAtIndex(
|
|
unsigned int index) const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpUnloadedModuleList for GetModuleAtIndex";
|
|
return NULL;
|
|
}
|
|
|
|
if (index >= module_count_) {
|
|
BPLOG(ERROR) << "MinidumpUnloadedModuleList index out of range: "
|
|
<< index << "/" << module_count_;
|
|
return NULL;
|
|
}
|
|
|
|
return &(*unloaded_modules_)[index];
|
|
}
|
|
|
|
const CodeModules* MinidumpUnloadedModuleList::Copy() const {
|
|
return new BasicCodeModules(this);
|
|
}
|
|
|
|
vector<linked_ptr<const CodeModule>>
|
|
MinidumpUnloadedModuleList::GetShrunkRangeModules() const {
|
|
return vector<linked_ptr<const CodeModule> >();
|
|
}
|
|
|
|
bool MinidumpUnloadedModuleList::IsModuleShrinkEnabled() const {
|
|
return range_map_->IsShrinkDownEnabled();
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpMiscInfo
|
|
//
|
|
|
|
|
|
MinidumpMiscInfo::MinidumpMiscInfo(Minidump* minidump)
|
|
: MinidumpStream(minidump),
|
|
misc_info_() {
|
|
}
|
|
|
|
|
|
bool MinidumpMiscInfo::Read(uint32_t expected_size) {
|
|
valid_ = false;
|
|
|
|
size_t padding = 0;
|
|
if (expected_size != MD_MISCINFO_SIZE &&
|
|
expected_size != MD_MISCINFO2_SIZE &&
|
|
expected_size != MD_MISCINFO3_SIZE &&
|
|
expected_size != MD_MISCINFO4_SIZE &&
|
|
expected_size != MD_MISCINFO5_SIZE) {
|
|
if (expected_size > MD_MISCINFO5_SIZE) {
|
|
// Only read the part of the misc info structure we know how to handle
|
|
BPLOG(INFO) << "MinidumpMiscInfo size larger than expected "
|
|
<< expected_size << ", skipping over the unknown part";
|
|
padding = expected_size - MD_MISCINFO5_SIZE;
|
|
expected_size = MD_MISCINFO5_SIZE;
|
|
} else {
|
|
BPLOG(ERROR) << "MinidumpMiscInfo size mismatch, " << expected_size
|
|
<< " != " << MD_MISCINFO_SIZE << ", " << MD_MISCINFO2_SIZE
|
|
<< ", " << MD_MISCINFO3_SIZE << ", " << MD_MISCINFO4_SIZE
|
|
<< ", " << MD_MISCINFO5_SIZE << ")";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!minidump_->ReadBytes(&misc_info_, expected_size)) {
|
|
BPLOG(ERROR) << "MinidumpMiscInfo cannot read miscellaneous info";
|
|
return false;
|
|
}
|
|
|
|
if (padding != 0) {
|
|
off_t saved_position = minidump_->Tell();
|
|
if (saved_position == -1) {
|
|
BPLOG(ERROR) << "MinidumpMiscInfo could not tell the current position";
|
|
return false;
|
|
}
|
|
|
|
if (!minidump_->SeekSet(saved_position + padding)) {
|
|
BPLOG(ERROR) << "MinidumpMiscInfo could not seek past the miscellaneous "
|
|
<< "info structure";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
// Swap version 1 fields
|
|
Swap(&misc_info_.size_of_info);
|
|
Swap(&misc_info_.flags1);
|
|
Swap(&misc_info_.process_id);
|
|
Swap(&misc_info_.process_create_time);
|
|
Swap(&misc_info_.process_user_time);
|
|
Swap(&misc_info_.process_kernel_time);
|
|
if (misc_info_.size_of_info > MD_MISCINFO_SIZE) {
|
|
// Swap version 2 fields
|
|
Swap(&misc_info_.processor_max_mhz);
|
|
Swap(&misc_info_.processor_current_mhz);
|
|
Swap(&misc_info_.processor_mhz_limit);
|
|
Swap(&misc_info_.processor_max_idle_state);
|
|
Swap(&misc_info_.processor_current_idle_state);
|
|
}
|
|
if (misc_info_.size_of_info > MD_MISCINFO2_SIZE) {
|
|
// Swap version 3 fields
|
|
Swap(&misc_info_.process_integrity_level);
|
|
Swap(&misc_info_.process_execute_flags);
|
|
Swap(&misc_info_.protected_process);
|
|
Swap(&misc_info_.time_zone_id);
|
|
Swap(&misc_info_.time_zone);
|
|
}
|
|
if (misc_info_.size_of_info > MD_MISCINFO3_SIZE) {
|
|
// Swap version 4 fields.
|
|
// Do not swap UTF-16 strings. The swap is done as part of the
|
|
// conversion to UTF-8 (code follows below).
|
|
}
|
|
if (misc_info_.size_of_info > MD_MISCINFO4_SIZE) {
|
|
// Swap version 5 fields
|
|
Swap(&misc_info_.xstate_data);
|
|
Swap(&misc_info_.process_cookie);
|
|
}
|
|
}
|
|
|
|
if (expected_size + padding != misc_info_.size_of_info) {
|
|
BPLOG(ERROR) << "MinidumpMiscInfo size mismatch, " <<
|
|
expected_size << " != " << misc_info_.size_of_info;
|
|
return false;
|
|
}
|
|
|
|
// Convert UTF-16 strings
|
|
if (misc_info_.size_of_info > MD_MISCINFO2_SIZE) {
|
|
// Convert UTF-16 strings in version 3 fields
|
|
ConvertUTF16BufferToUTF8String(misc_info_.time_zone.standard_name,
|
|
sizeof(misc_info_.time_zone.standard_name),
|
|
&standard_name_, minidump_->swap());
|
|
ConvertUTF16BufferToUTF8String(misc_info_.time_zone.daylight_name,
|
|
sizeof(misc_info_.time_zone.daylight_name),
|
|
&daylight_name_, minidump_->swap());
|
|
}
|
|
if (misc_info_.size_of_info > MD_MISCINFO3_SIZE) {
|
|
// Convert UTF-16 strings in version 4 fields
|
|
ConvertUTF16BufferToUTF8String(misc_info_.build_string,
|
|
sizeof(misc_info_.build_string),
|
|
&build_string_, minidump_->swap());
|
|
ConvertUTF16BufferToUTF8String(misc_info_.dbg_bld_str,
|
|
sizeof(misc_info_.dbg_bld_str),
|
|
&dbg_bld_str_, minidump_->swap());
|
|
}
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
void MinidumpMiscInfo::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpMiscInfo cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MDRawMiscInfo\n");
|
|
// Print version 1 fields
|
|
printf(" size_of_info = %d\n", misc_info_.size_of_info);
|
|
printf(" flags1 = 0x%x\n", misc_info_.flags1);
|
|
printf(" process_id = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 & MD_MISCINFO_FLAGS1_PROCESS_ID,
|
|
kNumberFormatDecimal, misc_info_.process_id);
|
|
if (misc_info_.flags1 & MD_MISCINFO_FLAGS1_PROCESS_TIMES) {
|
|
printf(" process_create_time = 0x%x %s\n",
|
|
misc_info_.process_create_time,
|
|
TimeTToUTCString(misc_info_.process_create_time).c_str());
|
|
} else {
|
|
printf(" process_create_time = (invalid)\n");
|
|
}
|
|
printf(" process_user_time = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 & MD_MISCINFO_FLAGS1_PROCESS_TIMES,
|
|
kNumberFormatDecimal, misc_info_.process_user_time);
|
|
printf(" process_kernel_time = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 & MD_MISCINFO_FLAGS1_PROCESS_TIMES,
|
|
kNumberFormatDecimal, misc_info_.process_kernel_time);
|
|
if (misc_info_.size_of_info > MD_MISCINFO_SIZE) {
|
|
// Print version 2 fields
|
|
printf(" processor_max_mhz = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 &
|
|
MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO,
|
|
kNumberFormatDecimal, misc_info_.processor_max_mhz);
|
|
printf(" processor_current_mhz = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 &
|
|
MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO,
|
|
kNumberFormatDecimal, misc_info_.processor_current_mhz);
|
|
printf(" processor_mhz_limit = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 &
|
|
MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO,
|
|
kNumberFormatDecimal, misc_info_.processor_mhz_limit);
|
|
printf(" processor_max_idle_state = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 &
|
|
MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO,
|
|
kNumberFormatDecimal,
|
|
misc_info_.processor_max_idle_state);
|
|
printf(" processor_current_idle_state = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 &
|
|
MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO,
|
|
kNumberFormatDecimal,
|
|
misc_info_.processor_current_idle_state);
|
|
}
|
|
if (misc_info_.size_of_info > MD_MISCINFO2_SIZE) {
|
|
// Print version 3 fields
|
|
printf(" process_integrity_level = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 &
|
|
MD_MISCINFO_FLAGS1_PROCESS_INTEGRITY,
|
|
kNumberFormatHexadecimal,
|
|
misc_info_.process_integrity_level);
|
|
printf(" process_execute_flags = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 &
|
|
MD_MISCINFO_FLAGS1_PROCESS_EXECUTE_FLAGS,
|
|
kNumberFormatHexadecimal,
|
|
misc_info_.process_execute_flags);
|
|
printf(" protected_process = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 &
|
|
MD_MISCINFO_FLAGS1_PROTECTED_PROCESS,
|
|
kNumberFormatDecimal, misc_info_.protected_process);
|
|
printf(" time_zone_id = ");
|
|
PrintValueOrInvalid(misc_info_.flags1 & MD_MISCINFO_FLAGS1_TIMEZONE,
|
|
kNumberFormatDecimal, misc_info_.time_zone_id);
|
|
if (misc_info_.flags1 & MD_MISCINFO_FLAGS1_TIMEZONE) {
|
|
printf(" time_zone.bias = %d\n",
|
|
misc_info_.time_zone.bias);
|
|
printf(" time_zone.standard_name = %s\n", standard_name_.c_str());
|
|
printf(" time_zone.standard_date = "
|
|
"%04d-%02d-%02d (%d) %02d:%02d:%02d.%03d\n",
|
|
misc_info_.time_zone.standard_date.year,
|
|
misc_info_.time_zone.standard_date.month,
|
|
misc_info_.time_zone.standard_date.day,
|
|
misc_info_.time_zone.standard_date.day_of_week,
|
|
misc_info_.time_zone.standard_date.hour,
|
|
misc_info_.time_zone.standard_date.minute,
|
|
misc_info_.time_zone.standard_date.second,
|
|
misc_info_.time_zone.standard_date.milliseconds);
|
|
printf(" time_zone.standard_bias = %d\n",
|
|
misc_info_.time_zone.standard_bias);
|
|
printf(" time_zone.daylight_name = %s\n", daylight_name_.c_str());
|
|
printf(" time_zone.daylight_date = "
|
|
"%04d-%02d-%02d (%d) %02d:%02d:%02d.%03d\n",
|
|
misc_info_.time_zone.daylight_date.year,
|
|
misc_info_.time_zone.daylight_date.month,
|
|
misc_info_.time_zone.daylight_date.day,
|
|
misc_info_.time_zone.daylight_date.day_of_week,
|
|
misc_info_.time_zone.daylight_date.hour,
|
|
misc_info_.time_zone.daylight_date.minute,
|
|
misc_info_.time_zone.daylight_date.second,
|
|
misc_info_.time_zone.daylight_date.milliseconds);
|
|
printf(" time_zone.daylight_bias = %d\n",
|
|
misc_info_.time_zone.daylight_bias);
|
|
} else {
|
|
printf(" time_zone.bias = (invalid)\n");
|
|
printf(" time_zone.standard_name = (invalid)\n");
|
|
printf(" time_zone.standard_date = (invalid)\n");
|
|
printf(" time_zone.standard_bias = (invalid)\n");
|
|
printf(" time_zone.daylight_name = (invalid)\n");
|
|
printf(" time_zone.daylight_date = (invalid)\n");
|
|
printf(" time_zone.daylight_bias = (invalid)\n");
|
|
}
|
|
}
|
|
if (misc_info_.size_of_info > MD_MISCINFO3_SIZE) {
|
|
// Print version 4 fields
|
|
if (misc_info_.flags1 & MD_MISCINFO_FLAGS1_BUILDSTRING) {
|
|
printf(" build_string = %s\n", build_string_.c_str());
|
|
printf(" dbg_bld_str = %s\n", dbg_bld_str_.c_str());
|
|
} else {
|
|
printf(" build_string = (invalid)\n");
|
|
printf(" dbg_bld_str = (invalid)\n");
|
|
}
|
|
}
|
|
if (misc_info_.size_of_info > MD_MISCINFO4_SIZE) {
|
|
// Print version 5 fields
|
|
if (misc_info_.flags1 & MD_MISCINFO_FLAGS1_PROCESS_COOKIE) {
|
|
printf(" xstate_data.size_of_info = %d\n",
|
|
misc_info_.xstate_data.size_of_info);
|
|
printf(" xstate_data.context_size = %d\n",
|
|
misc_info_.xstate_data.context_size);
|
|
printf(" xstate_data.enabled_features = 0x%" PRIx64 "\n",
|
|
misc_info_.xstate_data.enabled_features);
|
|
for (size_t i = 0; i < MD_MAXIMUM_XSTATE_FEATURES; i++) {
|
|
if ((misc_info_.xstate_data.enabled_features >> i) & 1) {
|
|
printf(" xstate_data.features[%02zu] = { %d, %d }\n", i,
|
|
misc_info_.xstate_data.features[i].offset,
|
|
misc_info_.xstate_data.features[i].size);
|
|
}
|
|
}
|
|
if (misc_info_.xstate_data.enabled_features == 0) {
|
|
printf(" xstate_data.features[] = (empty)\n");
|
|
}
|
|
printf(" process_cookie = %d\n",
|
|
misc_info_.process_cookie);
|
|
} else {
|
|
printf(" xstate_data.size_of_info = (invalid)\n");
|
|
printf(" xstate_data.context_size = (invalid)\n");
|
|
printf(" xstate_data.enabled_features = (invalid)\n");
|
|
printf(" xstate_data.features[] = (invalid)\n");
|
|
printf(" process_cookie = (invalid)\n");
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpBreakpadInfo
|
|
//
|
|
|
|
|
|
MinidumpBreakpadInfo::MinidumpBreakpadInfo(Minidump* minidump)
|
|
: MinidumpStream(minidump),
|
|
breakpad_info_() {
|
|
}
|
|
|
|
|
|
bool MinidumpBreakpadInfo::Read(uint32_t expected_size) {
|
|
valid_ = false;
|
|
|
|
if (expected_size != sizeof(breakpad_info_)) {
|
|
BPLOG(ERROR) << "MinidumpBreakpadInfo size mismatch, " << expected_size <<
|
|
" != " << sizeof(breakpad_info_);
|
|
return false;
|
|
}
|
|
|
|
if (!minidump_->ReadBytes(&breakpad_info_, sizeof(breakpad_info_))) {
|
|
BPLOG(ERROR) << "MinidumpBreakpadInfo cannot read Breakpad info";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&breakpad_info_.validity);
|
|
Swap(&breakpad_info_.dump_thread_id);
|
|
Swap(&breakpad_info_.requesting_thread_id);
|
|
}
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool MinidumpBreakpadInfo::GetDumpThreadID(uint32_t *thread_id) const {
|
|
BPLOG_IF(ERROR, !thread_id) << "MinidumpBreakpadInfo::GetDumpThreadID "
|
|
"requires |thread_id|";
|
|
assert(thread_id);
|
|
*thread_id = 0;
|
|
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpBreakpadInfo for GetDumpThreadID";
|
|
return false;
|
|
}
|
|
|
|
if (!(breakpad_info_.validity & MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID)) {
|
|
BPLOG(INFO) << "MinidumpBreakpadInfo has no dump thread";
|
|
return false;
|
|
}
|
|
|
|
*thread_id = breakpad_info_.dump_thread_id;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool MinidumpBreakpadInfo::GetRequestingThreadID(uint32_t *thread_id)
|
|
const {
|
|
BPLOG_IF(ERROR, !thread_id) << "MinidumpBreakpadInfo::GetRequestingThreadID "
|
|
"requires |thread_id|";
|
|
assert(thread_id);
|
|
*thread_id = 0;
|
|
|
|
if (!thread_id || !valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpBreakpadInfo for GetRequestingThreadID";
|
|
return false;
|
|
}
|
|
|
|
if (!(breakpad_info_.validity &
|
|
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID)) {
|
|
BPLOG(INFO) << "MinidumpBreakpadInfo has no requesting thread";
|
|
return false;
|
|
}
|
|
|
|
*thread_id = breakpad_info_.requesting_thread_id;
|
|
return true;
|
|
}
|
|
|
|
|
|
void MinidumpBreakpadInfo::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpBreakpadInfo cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MDRawBreakpadInfo\n");
|
|
printf(" validity = 0x%x\n", breakpad_info_.validity);
|
|
printf(" dump_thread_id = ");
|
|
PrintValueOrInvalid(breakpad_info_.validity &
|
|
MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID,
|
|
kNumberFormatHexadecimal, breakpad_info_.dump_thread_id);
|
|
printf(" requesting_thread_id = ");
|
|
PrintValueOrInvalid(breakpad_info_.validity &
|
|
MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID,
|
|
kNumberFormatHexadecimal,
|
|
breakpad_info_.requesting_thread_id);
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpMemoryInfo
|
|
//
|
|
|
|
|
|
MinidumpMemoryInfo::MinidumpMemoryInfo(Minidump* minidump)
|
|
: MinidumpObject(minidump),
|
|
memory_info_() {
|
|
}
|
|
|
|
|
|
bool MinidumpMemoryInfo::IsExecutable() const {
|
|
uint32_t protection =
|
|
memory_info_.protection & MD_MEMORY_PROTECTION_ACCESS_MASK;
|
|
return protection == MD_MEMORY_PROTECT_EXECUTE ||
|
|
protection == MD_MEMORY_PROTECT_EXECUTE_READ ||
|
|
protection == MD_MEMORY_PROTECT_EXECUTE_READWRITE;
|
|
}
|
|
|
|
|
|
bool MinidumpMemoryInfo::IsWritable() const {
|
|
uint32_t protection =
|
|
memory_info_.protection & MD_MEMORY_PROTECTION_ACCESS_MASK;
|
|
return protection == MD_MEMORY_PROTECT_READWRITE ||
|
|
protection == MD_MEMORY_PROTECT_WRITECOPY ||
|
|
protection == MD_MEMORY_PROTECT_EXECUTE_READWRITE ||
|
|
protection == MD_MEMORY_PROTECT_EXECUTE_WRITECOPY;
|
|
}
|
|
|
|
|
|
bool MinidumpMemoryInfo::Read() {
|
|
valid_ = false;
|
|
|
|
if (!minidump_->ReadBytes(&memory_info_, sizeof(memory_info_))) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfo cannot read memory info";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&memory_info_.base_address);
|
|
Swap(&memory_info_.allocation_base);
|
|
Swap(&memory_info_.allocation_protection);
|
|
Swap(&memory_info_.region_size);
|
|
Swap(&memory_info_.state);
|
|
Swap(&memory_info_.protection);
|
|
Swap(&memory_info_.type);
|
|
}
|
|
|
|
// Check for base + size overflow or undersize.
|
|
if (memory_info_.region_size == 0 ||
|
|
memory_info_.region_size > numeric_limits<uint64_t>::max() -
|
|
memory_info_.base_address) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfo has a memory region problem, " <<
|
|
HexString(memory_info_.base_address) << "+" <<
|
|
HexString(memory_info_.region_size);
|
|
return false;
|
|
}
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
void MinidumpMemoryInfo::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfo cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MDRawMemoryInfo\n");
|
|
printf(" base_address = 0x%" PRIx64 "\n",
|
|
memory_info_.base_address);
|
|
printf(" allocation_base = 0x%" PRIx64 "\n",
|
|
memory_info_.allocation_base);
|
|
printf(" allocation_protection = 0x%x\n",
|
|
memory_info_.allocation_protection);
|
|
printf(" region_size = 0x%" PRIx64 "\n", memory_info_.region_size);
|
|
printf(" state = 0x%x\n", memory_info_.state);
|
|
printf(" protection = 0x%x\n", memory_info_.protection);
|
|
printf(" type = 0x%x\n", memory_info_.type);
|
|
}
|
|
|
|
|
|
//
|
|
// MinidumpMemoryInfoList
|
|
//
|
|
|
|
|
|
MinidumpMemoryInfoList::MinidumpMemoryInfoList(Minidump* minidump)
|
|
: MinidumpStream(minidump),
|
|
range_map_(new RangeMap<uint64_t, unsigned int>()),
|
|
infos_(NULL),
|
|
info_count_(0) {
|
|
}
|
|
|
|
|
|
MinidumpMemoryInfoList::~MinidumpMemoryInfoList() {
|
|
delete range_map_;
|
|
delete infos_;
|
|
}
|
|
|
|
|
|
bool MinidumpMemoryInfoList::Read(uint32_t expected_size) {
|
|
// Invalidate cached data.
|
|
delete infos_;
|
|
infos_ = NULL;
|
|
range_map_->Clear();
|
|
info_count_ = 0;
|
|
|
|
valid_ = false;
|
|
|
|
MDRawMemoryInfoList header;
|
|
if (expected_size < sizeof(MDRawMemoryInfoList)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfoList header size mismatch, " <<
|
|
expected_size << " < " << sizeof(MDRawMemoryInfoList);
|
|
return false;
|
|
}
|
|
if (!minidump_->ReadBytes(&header, sizeof(header))) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfoList could not read header";
|
|
return false;
|
|
}
|
|
|
|
if (minidump_->swap()) {
|
|
Swap(&header.size_of_header);
|
|
Swap(&header.size_of_entry);
|
|
Swap(&header.number_of_entries);
|
|
}
|
|
|
|
// Sanity check that the header is the expected size.
|
|
// TODO(ted): could possibly handle this more gracefully, assuming
|
|
// that future versions of the structs would be backwards-compatible.
|
|
if (header.size_of_header != sizeof(MDRawMemoryInfoList)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfoList header size mismatch, " <<
|
|
header.size_of_header << " != " <<
|
|
sizeof(MDRawMemoryInfoList);
|
|
return false;
|
|
}
|
|
|
|
// Sanity check that the entries are the expected size.
|
|
if (header.size_of_entry != sizeof(MDRawMemoryInfo)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfoList entry size mismatch, " <<
|
|
header.size_of_entry << " != " <<
|
|
sizeof(MDRawMemoryInfo);
|
|
return false;
|
|
}
|
|
|
|
if (header.number_of_entries >
|
|
numeric_limits<uint32_t>::max() / sizeof(MDRawMemoryInfo)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfoList info count " <<
|
|
header.number_of_entries <<
|
|
" would cause multiplication overflow";
|
|
return false;
|
|
}
|
|
|
|
if (expected_size != sizeof(MDRawMemoryInfoList) +
|
|
header.number_of_entries * sizeof(MDRawMemoryInfo)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfoList size mismatch, " << expected_size <<
|
|
" != " << sizeof(MDRawMemoryInfoList) +
|
|
header.number_of_entries * sizeof(MDRawMemoryInfo);
|
|
return false;
|
|
}
|
|
|
|
// Check for data loss when converting header.number_of_entries from
|
|
// uint64_t into MinidumpMemoryInfos::size_type (uint32_t)
|
|
MinidumpMemoryInfos::size_type header_number_of_entries =
|
|
static_cast<unsigned int>(header.number_of_entries);
|
|
if (static_cast<uint64_t>(header_number_of_entries) !=
|
|
header.number_of_entries) {
|
|
BPLOG(ERROR) << "Data loss detected when converting "
|
|
"the header's number_of_entries";
|
|
return false;
|
|
}
|
|
|
|
if (header.number_of_entries != 0) {
|
|
scoped_ptr<MinidumpMemoryInfos> infos(
|
|
new MinidumpMemoryInfos(header_number_of_entries,
|
|
MinidumpMemoryInfo(minidump_)));
|
|
|
|
for (unsigned int index = 0;
|
|
index < header.number_of_entries;
|
|
++index) {
|
|
MinidumpMemoryInfo* info = &(*infos)[index];
|
|
|
|
// Assume that the file offset is correct after the last read.
|
|
if (!info->Read()) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfoList cannot read info " <<
|
|
index << "/" << header.number_of_entries;
|
|
return false;
|
|
}
|
|
|
|
uint64_t base_address = info->GetBase();
|
|
uint64_t region_size = info->GetSize();
|
|
|
|
if (!range_map_->StoreRange(base_address, region_size, index)) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfoList could not store"
|
|
" memory region " <<
|
|
index << "/" << header.number_of_entries << ", " <<
|
|
HexString(base_address) << "+" <<
|
|
HexString(region_size);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
infos_ = infos.release();
|
|
}
|
|
|
|
info_count_ = header_number_of_entries;
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
const MinidumpMemoryInfo* MinidumpMemoryInfoList::GetMemoryInfoAtIndex(
|
|
unsigned int index) const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpMemoryInfoList for GetMemoryInfoAtIndex";
|
|
return NULL;
|
|
}
|
|
|
|
if (index >= info_count_) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfoList index out of range: " <<
|
|
index << "/" << info_count_;
|
|
return NULL;
|
|
}
|
|
|
|
return &(*infos_)[index];
|
|
}
|
|
|
|
|
|
const MinidumpMemoryInfo* MinidumpMemoryInfoList::GetMemoryInfoForAddress(
|
|
uint64_t address) const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid MinidumpMemoryInfoList for"
|
|
" GetMemoryInfoForAddress";
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int info_index;
|
|
if (!range_map_->RetrieveRange(address, &info_index, NULL /* base */,
|
|
NULL /* delta */, NULL /* size */)) {
|
|
BPLOG(INFO) << "MinidumpMemoryInfoList has no memory info at " <<
|
|
HexString(address);
|
|
return NULL;
|
|
}
|
|
|
|
return GetMemoryInfoAtIndex(info_index);
|
|
}
|
|
|
|
|
|
void MinidumpMemoryInfoList::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpMemoryInfoList cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MinidumpMemoryInfoList\n");
|
|
printf(" info_count = %d\n", info_count_);
|
|
printf("\n");
|
|
|
|
for (unsigned int info_index = 0;
|
|
info_index < info_count_;
|
|
++info_index) {
|
|
printf("info[%d]\n", info_index);
|
|
(*infos_)[info_index].Print();
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
//
|
|
// MinidumpLinuxMaps
|
|
//
|
|
|
|
MinidumpLinuxMaps::MinidumpLinuxMaps(Minidump *minidump)
|
|
: MinidumpObject(minidump) {
|
|
}
|
|
|
|
void MinidumpLinuxMaps::Print() const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "MinidumpLinuxMaps cannot print invalid data";
|
|
return;
|
|
}
|
|
std::cout << region_.line << std::endl;
|
|
}
|
|
|
|
//
|
|
// MinidumpLinuxMapsList
|
|
//
|
|
|
|
MinidumpLinuxMapsList::MinidumpLinuxMapsList(Minidump *minidump)
|
|
: MinidumpStream(minidump),
|
|
maps_(NULL),
|
|
maps_count_(0) {
|
|
}
|
|
|
|
MinidumpLinuxMapsList::~MinidumpLinuxMapsList() {
|
|
if (maps_) {
|
|
for (unsigned int i = 0; i < maps_->size(); i++) {
|
|
delete (*maps_)[i];
|
|
}
|
|
delete maps_;
|
|
}
|
|
}
|
|
|
|
const MinidumpLinuxMaps *MinidumpLinuxMapsList::GetLinuxMapsForAddress(
|
|
uint64_t address) const {
|
|
if (!valid_ || (maps_ == NULL)) {
|
|
BPLOG(ERROR) << "Invalid MinidumpLinuxMapsList for GetLinuxMapsForAddress";
|
|
return NULL;
|
|
}
|
|
|
|
// Search every memory mapping.
|
|
for (unsigned int index = 0; index < maps_count_; index++) {
|
|
// Check if address is within bounds of the current memory region.
|
|
if ((*maps_)[index]->GetBase() <= address &&
|
|
(*maps_)[index]->GetBase() + (*maps_)[index]->GetSize() > address) {
|
|
return (*maps_)[index];
|
|
}
|
|
}
|
|
|
|
// No mapping encloses the memory address.
|
|
BPLOG(ERROR) << "MinidumpLinuxMapsList has no mapping at "
|
|
<< HexString(address);
|
|
return NULL;
|
|
}
|
|
|
|
const MinidumpLinuxMaps *MinidumpLinuxMapsList::GetLinuxMapsAtIndex(
|
|
unsigned int index) const {
|
|
if (!valid_ || (maps_ == NULL)) {
|
|
BPLOG(ERROR) << "Invalid MinidumpLinuxMapsList for GetLinuxMapsAtIndex";
|
|
return NULL;
|
|
}
|
|
|
|
// Index out of bounds.
|
|
if (index >= maps_count_ || (maps_ == NULL)) {
|
|
BPLOG(ERROR) << "MinidumpLinuxMapsList index of out range: "
|
|
<< index
|
|
<< "/"
|
|
<< maps_count_;
|
|
return NULL;
|
|
}
|
|
return (*maps_)[index];
|
|
}
|
|
|
|
bool MinidumpLinuxMapsList::Read(uint32_t expected_size) {
|
|
// Invalidate cached data.
|
|
if (maps_) {
|
|
for (unsigned int i = 0; i < maps_->size(); i++) {
|
|
delete (*maps_)[i];
|
|
}
|
|
delete maps_;
|
|
}
|
|
maps_ = NULL;
|
|
maps_count_ = 0;
|
|
|
|
valid_ = false;
|
|
|
|
// Load and check expected stream length.
|
|
uint32_t length = 0;
|
|
if (!minidump_->SeekToStreamType(MD_LINUX_MAPS, &length)) {
|
|
BPLOG(ERROR) << "MinidumpLinuxMapsList stream type not found";
|
|
return false;
|
|
}
|
|
if (expected_size != length) {
|
|
BPLOG(ERROR) << "MinidumpLinuxMapsList size mismatch: "
|
|
<< expected_size
|
|
<< " != "
|
|
<< length;
|
|
return false;
|
|
}
|
|
|
|
// Create a vector to read stream data. The vector needs to have
|
|
// at least enough capacity to read all the data.
|
|
vector<char> mapping_bytes(length);
|
|
if (!minidump_->ReadBytes(&mapping_bytes[0], length)) {
|
|
BPLOG(ERROR) << "MinidumpLinuxMapsList failed to read bytes";
|
|
return false;
|
|
}
|
|
string map_string(mapping_bytes.begin(), mapping_bytes.end());
|
|
vector<MappedMemoryRegion> all_regions;
|
|
|
|
// Parse string into mapping data.
|
|
if (!ParseProcMaps(map_string, &all_regions)) {
|
|
return false;
|
|
}
|
|
|
|
scoped_ptr<MinidumpLinuxMappings> maps(new MinidumpLinuxMappings());
|
|
|
|
// Push mapping data into wrapper classes.
|
|
for (size_t i = 0; i < all_regions.size(); i++) {
|
|
scoped_ptr<MinidumpLinuxMaps> ele(new MinidumpLinuxMaps(minidump_));
|
|
ele->region_ = all_regions[i];
|
|
ele->valid_ = true;
|
|
maps->push_back(ele.release());
|
|
}
|
|
|
|
// Set instance variables.
|
|
maps_ = maps.release();
|
|
maps_count_ = maps_->size();
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
void MinidumpLinuxMapsList::Print() const {
|
|
if (!valid_ || (maps_ == NULL)) {
|
|
BPLOG(ERROR) << "MinidumpLinuxMapsList cannot print valid data";
|
|
return;
|
|
}
|
|
for (size_t i = 0; i < maps_->size(); i++) {
|
|
(*maps_)[i]->Print();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Minidump
|
|
//
|
|
|
|
|
|
uint32_t Minidump::max_streams_ = 128;
|
|
unsigned int Minidump::max_string_length_ = 1024;
|
|
|
|
|
|
Minidump::Minidump(const string& path)
|
|
: header_(),
|
|
directory_(NULL),
|
|
stream_map_(new MinidumpStreamMap()),
|
|
path_(path),
|
|
stream_(NULL),
|
|
swap_(false),
|
|
valid_(false) {
|
|
}
|
|
|
|
Minidump::Minidump(istream& stream)
|
|
: header_(),
|
|
directory_(NULL),
|
|
stream_map_(new MinidumpStreamMap()),
|
|
path_(),
|
|
stream_(&stream),
|
|
swap_(false),
|
|
valid_(false) {
|
|
}
|
|
|
|
Minidump::~Minidump() {
|
|
if (stream_) {
|
|
BPLOG(INFO) << "Minidump closing minidump";
|
|
}
|
|
if (!path_.empty()) {
|
|
delete stream_;
|
|
}
|
|
delete directory_;
|
|
delete stream_map_;
|
|
}
|
|
|
|
|
|
bool Minidump::Open() {
|
|
if (stream_ != NULL) {
|
|
BPLOG(INFO) << "Minidump reopening minidump " << path_;
|
|
|
|
// The file is already open. Seek to the beginning, which is the position
|
|
// the file would be at if it were opened anew.
|
|
return SeekSet(0);
|
|
}
|
|
|
|
stream_ = new ifstream(path_.c_str(), std::ios::in | std::ios::binary);
|
|
if (!stream_ || !stream_->good()) {
|
|
string error_string;
|
|
int error_code = ErrnoString(&error_string);
|
|
BPLOG(ERROR) << "Minidump could not open minidump " << path_ <<
|
|
", error " << error_code << ": " << error_string;
|
|
return false;
|
|
}
|
|
|
|
BPLOG(INFO) << "Minidump opened minidump " << path_;
|
|
return true;
|
|
}
|
|
|
|
bool Minidump::GetContextCPUFlagsFromSystemInfo(uint32_t *context_cpu_flags) {
|
|
// Initialize output parameters
|
|
*context_cpu_flags = 0;
|
|
|
|
// Save the current stream position
|
|
off_t saved_position = Tell();
|
|
if (saved_position == -1) {
|
|
// Failed to save the current stream position.
|
|
// Returns true because the current position of the stream is preserved.
|
|
return true;
|
|
}
|
|
|
|
const MDRawSystemInfo* system_info =
|
|
GetSystemInfo() ? GetSystemInfo()->system_info() : NULL;
|
|
|
|
if (system_info != NULL) {
|
|
switch (system_info->processor_architecture) {
|
|
case MD_CPU_ARCHITECTURE_X86:
|
|
*context_cpu_flags = MD_CONTEXT_X86;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_MIPS:
|
|
*context_cpu_flags = MD_CONTEXT_MIPS;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_MIPS64:
|
|
*context_cpu_flags = MD_CONTEXT_MIPS64;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_ALPHA:
|
|
*context_cpu_flags = MD_CONTEXT_ALPHA;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_PPC:
|
|
*context_cpu_flags = MD_CONTEXT_PPC;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_PPC64:
|
|
*context_cpu_flags = MD_CONTEXT_PPC64;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_SHX:
|
|
*context_cpu_flags = MD_CONTEXT_SHX;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_ARM:
|
|
*context_cpu_flags = MD_CONTEXT_ARM;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_ARM64:
|
|
*context_cpu_flags = MD_CONTEXT_ARM64;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_IA64:
|
|
*context_cpu_flags = MD_CONTEXT_IA64;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_ALPHA64:
|
|
*context_cpu_flags = 0;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_MSIL:
|
|
*context_cpu_flags = 0;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_AMD64:
|
|
*context_cpu_flags = MD_CONTEXT_AMD64;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_X86_WIN64:
|
|
*context_cpu_flags = 0;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_SPARC:
|
|
*context_cpu_flags = MD_CONTEXT_SPARC;
|
|
break;
|
|
case MD_CPU_ARCHITECTURE_UNKNOWN:
|
|
*context_cpu_flags = 0;
|
|
break;
|
|
default:
|
|
*context_cpu_flags = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Restore position and return
|
|
return SeekSet(saved_position);
|
|
}
|
|
|
|
|
|
bool Minidump::Read() {
|
|
// Invalidate cached data.
|
|
delete directory_;
|
|
directory_ = NULL;
|
|
stream_map_->clear();
|
|
|
|
valid_ = false;
|
|
|
|
if (!Open()) {
|
|
BPLOG(ERROR) << "Minidump cannot open minidump";
|
|
return false;
|
|
}
|
|
|
|
if (!ReadBytes(&header_, sizeof(MDRawHeader))) {
|
|
BPLOG(ERROR) << "Minidump cannot read header";
|
|
return false;
|
|
}
|
|
|
|
if (header_.signature != MD_HEADER_SIGNATURE) {
|
|
// The file may be byte-swapped. Under the present architecture, these
|
|
// classes don't know or need to know what CPU (or endianness) the
|
|
// minidump was produced on in order to parse it. Use the signature as
|
|
// a byte order marker.
|
|
uint32_t signature_swapped = header_.signature;
|
|
Swap(&signature_swapped);
|
|
if (signature_swapped != MD_HEADER_SIGNATURE) {
|
|
// This isn't a minidump or a byte-swapped minidump.
|
|
BPLOG(ERROR) << "Minidump header signature mismatch: (" <<
|
|
HexString(header_.signature) << ", " <<
|
|
HexString(signature_swapped) << ") != " <<
|
|
HexString(MD_HEADER_SIGNATURE);
|
|
return false;
|
|
}
|
|
swap_ = true;
|
|
} else {
|
|
// The file is not byte-swapped. Set swap_ false (it may have been true
|
|
// if the object is being reused?)
|
|
swap_ = false;
|
|
}
|
|
|
|
BPLOG(INFO) << "Minidump " << (swap_ ? "" : "not ") <<
|
|
"byte-swapping minidump";
|
|
|
|
if (swap_) {
|
|
Swap(&header_.signature);
|
|
Swap(&header_.version);
|
|
Swap(&header_.stream_count);
|
|
Swap(&header_.stream_directory_rva);
|
|
Swap(&header_.checksum);
|
|
Swap(&header_.time_date_stamp);
|
|
Swap(&header_.flags);
|
|
}
|
|
|
|
// Version check. The high 16 bits of header_.version contain something
|
|
// else "implementation specific."
|
|
if ((header_.version & 0x0000ffff) != MD_HEADER_VERSION) {
|
|
BPLOG(ERROR) << "Minidump version mismatch: " <<
|
|
HexString(header_.version & 0x0000ffff) << " != " <<
|
|
HexString(MD_HEADER_VERSION);
|
|
return false;
|
|
}
|
|
|
|
if (!SeekSet(header_.stream_directory_rva)) {
|
|
BPLOG(ERROR) << "Minidump cannot seek to stream directory";
|
|
return false;
|
|
}
|
|
|
|
if (header_.stream_count > max_streams_) {
|
|
BPLOG(ERROR) << "Minidump stream count " << header_.stream_count <<
|
|
" exceeds maximum " << max_streams_;
|
|
return false;
|
|
}
|
|
|
|
if (header_.stream_count != 0) {
|
|
scoped_ptr<MinidumpDirectoryEntries> directory(
|
|
new MinidumpDirectoryEntries(header_.stream_count));
|
|
|
|
// Read the entire array in one fell swoop, instead of reading one entry
|
|
// at a time in the loop.
|
|
if (!ReadBytes(&(*directory)[0],
|
|
sizeof(MDRawDirectory) * header_.stream_count)) {
|
|
BPLOG(ERROR) << "Minidump cannot read stream directory";
|
|
return false;
|
|
}
|
|
|
|
for (unsigned int stream_index = 0;
|
|
stream_index < header_.stream_count;
|
|
++stream_index) {
|
|
MDRawDirectory* directory_entry = &(*directory)[stream_index];
|
|
|
|
if (swap_) {
|
|
Swap(&directory_entry->stream_type);
|
|
Swap(&directory_entry->location);
|
|
}
|
|
|
|
// Initialize the stream_map_ map, which speeds locating a stream by
|
|
// type.
|
|
unsigned int stream_type = directory_entry->stream_type;
|
|
switch (stream_type) {
|
|
case MD_THREAD_LIST_STREAM:
|
|
case MD_MODULE_LIST_STREAM:
|
|
case MD_MEMORY_LIST_STREAM:
|
|
case MD_EXCEPTION_STREAM:
|
|
case MD_SYSTEM_INFO_STREAM:
|
|
case MD_MISC_INFO_STREAM:
|
|
case MD_BREAKPAD_INFO_STREAM: {
|
|
if (stream_map_->find(stream_type) != stream_map_->end()) {
|
|
// Another stream with this type was already found. A minidump
|
|
// file should contain at most one of each of these stream types.
|
|
BPLOG(ERROR) << "Minidump found multiple streams of type " <<
|
|
stream_type << ", but can only deal with one";
|
|
return false;
|
|
}
|
|
// Fall through to default
|
|
}
|
|
|
|
default: {
|
|
// Overwrites for stream types other than those above, but it's
|
|
// expected to be the user's burden in that case.
|
|
(*stream_map_)[stream_type].stream_index = stream_index;
|
|
}
|
|
}
|
|
}
|
|
|
|
directory_ = directory.release();
|
|
}
|
|
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
MinidumpThreadList* Minidump::GetThreadList() {
|
|
MinidumpThreadList* thread_list;
|
|
return GetStream(&thread_list);
|
|
}
|
|
|
|
|
|
MinidumpModuleList* Minidump::GetModuleList() {
|
|
MinidumpModuleList* module_list;
|
|
return GetStream(&module_list);
|
|
}
|
|
|
|
|
|
MinidumpMemoryList* Minidump::GetMemoryList() {
|
|
MinidumpMemoryList* memory_list;
|
|
return GetStream(&memory_list);
|
|
}
|
|
|
|
|
|
MinidumpException* Minidump::GetException() {
|
|
MinidumpException* exception;
|
|
return GetStream(&exception);
|
|
}
|
|
|
|
MinidumpAssertion* Minidump::GetAssertion() {
|
|
MinidumpAssertion* assertion;
|
|
return GetStream(&assertion);
|
|
}
|
|
|
|
|
|
MinidumpSystemInfo* Minidump::GetSystemInfo() {
|
|
MinidumpSystemInfo* system_info;
|
|
return GetStream(&system_info);
|
|
}
|
|
|
|
|
|
MinidumpUnloadedModuleList* Minidump::GetUnloadedModuleList() {
|
|
MinidumpUnloadedModuleList* unloaded_module_list;
|
|
return GetStream(&unloaded_module_list);
|
|
}
|
|
|
|
|
|
MinidumpMiscInfo* Minidump::GetMiscInfo() {
|
|
MinidumpMiscInfo* misc_info;
|
|
return GetStream(&misc_info);
|
|
}
|
|
|
|
|
|
MinidumpBreakpadInfo* Minidump::GetBreakpadInfo() {
|
|
MinidumpBreakpadInfo* breakpad_info;
|
|
return GetStream(&breakpad_info);
|
|
}
|
|
|
|
MinidumpMemoryInfoList* Minidump::GetMemoryInfoList() {
|
|
MinidumpMemoryInfoList* memory_info_list;
|
|
return GetStream(&memory_info_list);
|
|
}
|
|
|
|
MinidumpLinuxMapsList *Minidump::GetLinuxMapsList() {
|
|
MinidumpLinuxMapsList *linux_maps_list;
|
|
return GetStream(&linux_maps_list);
|
|
}
|
|
|
|
bool Minidump::IsAndroid() {
|
|
// Save the current stream position
|
|
off_t saved_position = Tell();
|
|
if (saved_position == -1) {
|
|
return false;
|
|
}
|
|
const MDRawSystemInfo* system_info =
|
|
GetSystemInfo() ? GetSystemInfo()->system_info() : NULL;
|
|
|
|
// Restore position and return
|
|
if (!SeekSet(saved_position)) {
|
|
BPLOG(ERROR) << "Couldn't seek back to saved position";
|
|
return false;
|
|
}
|
|
|
|
return system_info && system_info->platform_id == MD_OS_ANDROID;
|
|
}
|
|
|
|
static const char* get_stream_name(uint32_t stream_type) {
|
|
switch (stream_type) {
|
|
case MD_UNUSED_STREAM:
|
|
return "MD_UNUSED_STREAM";
|
|
case MD_RESERVED_STREAM_0:
|
|
return "MD_RESERVED_STREAM_0";
|
|
case MD_RESERVED_STREAM_1:
|
|
return "MD_RESERVED_STREAM_1";
|
|
case MD_THREAD_LIST_STREAM:
|
|
return "MD_THREAD_LIST_STREAM";
|
|
case MD_MODULE_LIST_STREAM:
|
|
return "MD_MODULE_LIST_STREAM";
|
|
case MD_MEMORY_LIST_STREAM:
|
|
return "MD_MEMORY_LIST_STREAM";
|
|
case MD_EXCEPTION_STREAM:
|
|
return "MD_EXCEPTION_STREAM";
|
|
case MD_SYSTEM_INFO_STREAM:
|
|
return "MD_SYSTEM_INFO_STREAM";
|
|
case MD_THREAD_EX_LIST_STREAM:
|
|
return "MD_THREAD_EX_LIST_STREAM";
|
|
case MD_MEMORY_64_LIST_STREAM:
|
|
return "MD_MEMORY_64_LIST_STREAM";
|
|
case MD_COMMENT_STREAM_A:
|
|
return "MD_COMMENT_STREAM_A";
|
|
case MD_COMMENT_STREAM_W:
|
|
return "MD_COMMENT_STREAM_W";
|
|
case MD_HANDLE_DATA_STREAM:
|
|
return "MD_HANDLE_DATA_STREAM";
|
|
case MD_FUNCTION_TABLE_STREAM:
|
|
return "MD_FUNCTION_TABLE_STREAM";
|
|
case MD_UNLOADED_MODULE_LIST_STREAM:
|
|
return "MD_UNLOADED_MODULE_LIST_STREAM";
|
|
case MD_MISC_INFO_STREAM:
|
|
return "MD_MISC_INFO_STREAM";
|
|
case MD_MEMORY_INFO_LIST_STREAM:
|
|
return "MD_MEMORY_INFO_LIST_STREAM";
|
|
case MD_THREAD_INFO_LIST_STREAM:
|
|
return "MD_THREAD_INFO_LIST_STREAM";
|
|
case MD_HANDLE_OPERATION_LIST_STREAM:
|
|
return "MD_HANDLE_OPERATION_LIST_STREAM";
|
|
case MD_TOKEN_STREAM:
|
|
return "MD_TOKEN_STREAM";
|
|
case MD_JAVASCRIPT_DATA_STREAM:
|
|
return "MD_JAVASCRIPT_DATA_STREAM";
|
|
case MD_SYSTEM_MEMORY_INFO_STREAM:
|
|
return "MD_SYSTEM_MEMORY_INFO_STREAM";
|
|
case MD_PROCESS_VM_COUNTERS_STREAM:
|
|
return "MD_PROCESS_VM_COUNTERS_STREAM";
|
|
case MD_LAST_RESERVED_STREAM:
|
|
return "MD_LAST_RESERVED_STREAM";
|
|
case MD_BREAKPAD_INFO_STREAM:
|
|
return "MD_BREAKPAD_INFO_STREAM";
|
|
case MD_ASSERTION_INFO_STREAM:
|
|
return "MD_ASSERTION_INFO_STREAM";
|
|
case MD_LINUX_CPU_INFO:
|
|
return "MD_LINUX_CPU_INFO";
|
|
case MD_LINUX_PROC_STATUS:
|
|
return "MD_LINUX_PROC_STATUS";
|
|
case MD_LINUX_LSB_RELEASE:
|
|
return "MD_LINUX_LSB_RELEASE";
|
|
case MD_LINUX_CMD_LINE:
|
|
return "MD_LINUX_CMD_LINE";
|
|
case MD_LINUX_ENVIRON:
|
|
return "MD_LINUX_ENVIRON";
|
|
case MD_LINUX_AUXV:
|
|
return "MD_LINUX_AUXV";
|
|
case MD_LINUX_MAPS:
|
|
return "MD_LINUX_MAPS";
|
|
case MD_LINUX_DSO_DEBUG:
|
|
return "MD_LINUX_DSO_DEBUG";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
void Minidump::Print() {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Minidump cannot print invalid data";
|
|
return;
|
|
}
|
|
|
|
printf("MDRawHeader\n");
|
|
printf(" signature = 0x%x\n", header_.signature);
|
|
printf(" version = 0x%x\n", header_.version);
|
|
printf(" stream_count = %d\n", header_.stream_count);
|
|
printf(" stream_directory_rva = 0x%x\n", header_.stream_directory_rva);
|
|
printf(" checksum = 0x%x\n", header_.checksum);
|
|
printf(" time_date_stamp = 0x%x %s\n",
|
|
header_.time_date_stamp,
|
|
TimeTToUTCString(header_.time_date_stamp).c_str());
|
|
printf(" flags = 0x%" PRIx64 "\n", header_.flags);
|
|
printf("\n");
|
|
|
|
for (unsigned int stream_index = 0;
|
|
stream_index < header_.stream_count;
|
|
++stream_index) {
|
|
MDRawDirectory* directory_entry = &(*directory_)[stream_index];
|
|
|
|
printf("mDirectory[%d]\n", stream_index);
|
|
printf("MDRawDirectory\n");
|
|
printf(" stream_type = 0x%x (%s)\n", directory_entry->stream_type,
|
|
get_stream_name(directory_entry->stream_type));
|
|
printf(" location.data_size = %d\n",
|
|
directory_entry->location.data_size);
|
|
printf(" location.rva = 0x%x\n", directory_entry->location.rva);
|
|
printf("\n");
|
|
}
|
|
|
|
printf("Streams:\n");
|
|
for (MinidumpStreamMap::const_iterator iterator = stream_map_->begin();
|
|
iterator != stream_map_->end();
|
|
++iterator) {
|
|
uint32_t stream_type = iterator->first;
|
|
const MinidumpStreamInfo& info = iterator->second;
|
|
printf(" stream type 0x%x (%s) at index %d\n", stream_type,
|
|
get_stream_name(stream_type),
|
|
info.stream_index);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
|
|
const MDRawDirectory* Minidump::GetDirectoryEntryAtIndex(unsigned int index)
|
|
const {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid Minidump for GetDirectoryEntryAtIndex";
|
|
return NULL;
|
|
}
|
|
|
|
if (index >= header_.stream_count) {
|
|
BPLOG(ERROR) << "Minidump stream directory index out of range: " <<
|
|
index << "/" << header_.stream_count;
|
|
return NULL;
|
|
}
|
|
|
|
return &(*directory_)[index];
|
|
}
|
|
|
|
|
|
bool Minidump::ReadBytes(void* bytes, size_t count) {
|
|
// Can't check valid_ because Read needs to call this method before
|
|
// validity can be determined.
|
|
if (!stream_) {
|
|
return false;
|
|
}
|
|
stream_->read(static_cast<char*>(bytes), count);
|
|
std::streamsize bytes_read = stream_->gcount();
|
|
if (bytes_read == -1) {
|
|
string error_string;
|
|
int error_code = ErrnoString(&error_string);
|
|
BPLOG(ERROR) << "ReadBytes: error " << error_code << ": " << error_string;
|
|
return false;
|
|
}
|
|
|
|
// Convert to size_t and check for data loss
|
|
size_t bytes_read_converted = static_cast<size_t>(bytes_read);
|
|
if (static_cast<std::streamsize>(bytes_read_converted) != bytes_read) {
|
|
BPLOG(ERROR) << "ReadBytes: conversion data loss detected when converting "
|
|
<< bytes_read << " to " << bytes_read_converted;
|
|
return false;
|
|
}
|
|
|
|
if (bytes_read_converted != count) {
|
|
BPLOG(ERROR) << "ReadBytes: read " << bytes_read_converted << "/" << count;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Minidump::SeekSet(off_t offset) {
|
|
// Can't check valid_ because Read needs to call this method before
|
|
// validity can be determined.
|
|
if (!stream_) {
|
|
return false;
|
|
}
|
|
stream_->seekg(offset, std::ios_base::beg);
|
|
if (!stream_->good()) {
|
|
string error_string;
|
|
int error_code = ErrnoString(&error_string);
|
|
BPLOG(ERROR) << "SeekSet: error " << error_code << ": " << error_string;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
off_t Minidump::Tell() {
|
|
if (!valid_ || !stream_) {
|
|
return (off_t)-1;
|
|
}
|
|
|
|
// Check for conversion data loss
|
|
std::streamoff std_streamoff = stream_->tellg();
|
|
off_t rv = static_cast<off_t>(std_streamoff);
|
|
if (static_cast<std::streamoff>(rv) == std_streamoff) {
|
|
return rv;
|
|
} else {
|
|
BPLOG(ERROR) << "Data loss detected";
|
|
return (off_t)-1;
|
|
}
|
|
}
|
|
|
|
|
|
string* Minidump::ReadString(off_t offset) {
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid Minidump for ReadString";
|
|
return NULL;
|
|
}
|
|
if (!SeekSet(offset)) {
|
|
BPLOG(ERROR) << "ReadString could not seek to string at offset " << offset;
|
|
return NULL;
|
|
}
|
|
|
|
uint32_t bytes;
|
|
if (!ReadBytes(&bytes, sizeof(bytes))) {
|
|
BPLOG(ERROR) << "ReadString could not read string size at offset " <<
|
|
offset;
|
|
return NULL;
|
|
}
|
|
if (swap_)
|
|
Swap(&bytes);
|
|
|
|
if (bytes % 2 != 0) {
|
|
BPLOG(ERROR) << "ReadString found odd-sized " << bytes <<
|
|
"-byte string at offset " << offset;
|
|
return NULL;
|
|
}
|
|
unsigned int utf16_words = bytes / 2;
|
|
|
|
if (utf16_words > max_string_length_) {
|
|
BPLOG(ERROR) << "ReadString string length " << utf16_words <<
|
|
" exceeds maximum " << max_string_length_ <<
|
|
" at offset " << offset;
|
|
return NULL;
|
|
}
|
|
|
|
vector<uint16_t> string_utf16(utf16_words);
|
|
|
|
if (utf16_words) {
|
|
if (!ReadBytes(&string_utf16[0], bytes)) {
|
|
BPLOG(ERROR) << "ReadString could not read " << bytes <<
|
|
"-byte string at offset " << offset;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return UTF16ToUTF8(string_utf16, swap_);
|
|
}
|
|
|
|
|
|
bool Minidump::SeekToStreamType(uint32_t stream_type,
|
|
uint32_t* stream_length) {
|
|
BPLOG_IF(ERROR, !stream_length) << "Minidump::SeekToStreamType requires "
|
|
"|stream_length|";
|
|
assert(stream_length);
|
|
*stream_length = 0;
|
|
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid Mindump for SeekToStreamType";
|
|
return false;
|
|
}
|
|
|
|
MinidumpStreamMap::const_iterator iterator = stream_map_->find(stream_type);
|
|
if (iterator == stream_map_->end()) {
|
|
// This stream type didn't exist in the directory.
|
|
BPLOG(INFO) << "SeekToStreamType: type " << stream_type << " not present";
|
|
return false;
|
|
}
|
|
|
|
const MinidumpStreamInfo& info = iterator->second;
|
|
if (info.stream_index >= header_.stream_count) {
|
|
BPLOG(ERROR) << "SeekToStreamType: type " << stream_type <<
|
|
" out of range: " <<
|
|
info.stream_index << "/" << header_.stream_count;
|
|
return false;
|
|
}
|
|
|
|
MDRawDirectory* directory_entry = &(*directory_)[info.stream_index];
|
|
if (!SeekSet(directory_entry->location.rva)) {
|
|
BPLOG(ERROR) << "SeekToStreamType could not seek to stream type " <<
|
|
stream_type;
|
|
return false;
|
|
}
|
|
|
|
*stream_length = directory_entry->location.data_size;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
template<typename T>
|
|
T* Minidump::GetStream(T** stream) {
|
|
// stream is a garbage parameter that's present only to account for C++'s
|
|
// inability to overload a method based solely on its return type.
|
|
|
|
const uint32_t stream_type = T::kStreamType;
|
|
|
|
BPLOG_IF(ERROR, !stream) << "Minidump::GetStream type " << stream_type <<
|
|
" requires |stream|";
|
|
assert(stream);
|
|
*stream = NULL;
|
|
|
|
if (!valid_) {
|
|
BPLOG(ERROR) << "Invalid Minidump for GetStream type " << stream_type;
|
|
return NULL;
|
|
}
|
|
|
|
MinidumpStreamMap::iterator iterator = stream_map_->find(stream_type);
|
|
if (iterator == stream_map_->end()) {
|
|
// This stream type didn't exist in the directory.
|
|
BPLOG(INFO) << "GetStream: type " << stream_type << " not present";
|
|
return NULL;
|
|
}
|
|
|
|
// Get a pointer so that the stored stream field can be altered.
|
|
MinidumpStreamInfo* info = &iterator->second;
|
|
|
|
if (info->stream) {
|
|
// This cast is safe because info.stream is only populated by this
|
|
// method, and there is a direct correlation between T and stream_type.
|
|
*stream = static_cast<T*>(info->stream);
|
|
return *stream;
|
|
}
|
|
|
|
uint32_t stream_length;
|
|
if (!SeekToStreamType(stream_type, &stream_length)) {
|
|
BPLOG(ERROR) << "GetStream could not seek to stream type " << stream_type;
|
|
return NULL;
|
|
}
|
|
|
|
scoped_ptr<T> new_stream(new T(this));
|
|
|
|
if (!new_stream->Read(stream_length)) {
|
|
BPLOG(ERROR) << "GetStream could not read stream type " << stream_type;
|
|
return NULL;
|
|
}
|
|
|
|
*stream = new_stream.release();
|
|
info->stream = *stream;
|
|
return *stream;
|
|
}
|
|
|
|
|
|
} // namespace google_breakpad
|