blob: a9c7d23affe7ca29eb5d0f4d108e79875bf4c1cd [file] [log] [blame]
// 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(&timestruct, &tt);
#else
gmtime_r(&tt, &timestruct);
#endif
char timestr[20];
size_t rv = strftime(timestr, 20, "%Y-%m-%d %H:%M:%S", &timestruct);
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_ = 64 * 1024 * 1024; // 64MB
MinidumpMemoryRegion::MinidumpMemoryRegion(Minidump* minidump)
: MinidumpObject(minidump),
descriptor_(NULL),
memory_(NULL) {
hexdump_width_ = minidump_ ? minidump_->HexdumpMode() : 0;
hexdump_ = hexdump_width_ != 0;
}
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) {
if (hexdump_) {
// Pretty hexdump view.
for (unsigned int byte_index = 0;
byte_index < descriptor_->memory.data_size;
byte_index += hexdump_width_) {
// In case the memory won't fill a whole line.
unsigned int num_bytes = std::min(
descriptor_->memory.data_size - byte_index, hexdump_width_);
// Display the leading address.
printf("%08x ", byte_index);
// Show the bytes in hex.
for (unsigned int i = 0; i < hexdump_width_; ++i) {
if (i < num_bytes) {
// Show the single byte of memory in hex.
printf("%02x ", memory[byte_index + i]);
} else {
// If this line doesn't fill up, pad it out.
printf(" ");
}
// Insert a space every 8 bytes to make it more readable.
if (((i + 1) % 8) == 0) {
printf(" ");
}
}
// Decode the line as ASCII.
printf("|");
for (unsigned int i = 0; i < hexdump_width_; ++i) {
if (i < num_bytes) {
uint8_t byte = memory[byte_index + i];
printf("%c", isprint(byte) ? byte : '.');
} else {
// If this line doesn't fill up, pad it out.
printf(" ");
}
}
printf("|\n");
}
} else {
// Ugly raw string view.
printf("0x");
for (unsigned int i = 0;
i < descriptor_->memory.data_size;
i++) {
printf("%02x", memory[i]);
}
printf("\n");
}
} else {
printf("No memory\n");
}
}
void MinidumpMemoryRegion::SetPrintMode(bool hexdump,
unsigned int hexdump_width) {
// Require the width to be a multiple of 8 bytes.
if (hexdump_width == 0 || (hexdump_width % 8) != 0) {
BPLOG(ERROR) << "MinidumpMemoryRegion print hexdump_width must be "
"multiple of 8, not " << hexdump_width;
return;
}
hexdump_ = hexdump;
hexdump_width_ = hexdump_width;
}
//
// 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.
size_t bytes =
module_.misc_record.data_size - MDImageDebugMisc_minsize;
if (bytes % 2 == 0) {
size_t 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));
size_t 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_ = 2048;
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()) {