diff options
Diffstat (limited to 'toolkit/crashreporter/google-breakpad/src/processor/minidump.cc')
-rw-r--r-- | toolkit/crashreporter/google-breakpad/src/processor/minidump.cc | 4989 |
1 files changed, 4989 insertions, 0 deletions
diff --git a/toolkit/crashreporter/google-breakpad/src/processor/minidump.cc b/toolkit/crashreporter/google-breakpad/src/processor/minidump.cc new file mode 100644 index 000000000..1e1d386df --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/processor/minidump.cc @@ -0,0 +1,4989 @@ +// 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 +// http://code.google.com/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. + // http://code.google.com/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. + // http://code.google.com/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. + // http://code.google.com/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)); + 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. + if (MDCVInfoPDB70_minsize > 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. + if (MDCVInfoPDB20_minsize > 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"); +} + + +// +// 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 & (1 << i)) { + 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); +} + + +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 |