summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer')
-rw-r--r--toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.cc609
-rw-r--r--toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.h65
-rw-r--r--toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer_unittest.cc257
3 files changed, 931 insertions, 0 deletions
diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.cc
new file mode 100644
index 000000000..a508667a0
--- /dev/null
+++ b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.cc
@@ -0,0 +1,609 @@
+// Copyright (c) 2014, 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.
+
+// This translation unit generates microdumps into the console (logcat on
+// Android). See crbug.com/410294 for more info and design docs.
+
+#include "client/linux/microdump_writer/microdump_writer.h"
+
+#include <limits>
+
+#include <sys/utsname.h>
+
+#include "client/linux/dump_writer_common/thread_info.h"
+#include "client/linux/dump_writer_common/ucontext_reader.h"
+#include "client/linux/handler/exception_handler.h"
+#include "client/linux/handler/microdump_extra_info.h"
+#include "client/linux/log/log.h"
+#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
+#include "common/linux/file_id.h"
+#include "common/linux/linux_libc_support.h"
+#include "common/memory.h"
+
+namespace {
+
+using google_breakpad::auto_wasteful_vector;
+using google_breakpad::ExceptionHandler;
+using google_breakpad::kDefaultBuildIdSize;
+using google_breakpad::LinuxDumper;
+using google_breakpad::LinuxPtraceDumper;
+using google_breakpad::MappingInfo;
+using google_breakpad::MappingList;
+using google_breakpad::MicrodumpExtraInfo;
+using google_breakpad::RawContextCPU;
+using google_breakpad::ThreadInfo;
+using google_breakpad::UContextReader;
+
+const size_t kLineBufferSize = 2048;
+
+#if !defined(__LP64__)
+// The following are only used by DumpFreeSpace, so need to be compiled
+// in conditionally in the same way.
+
+template <typename Dst, typename Src>
+Dst saturated_cast(Src src) {
+ if (src >= std::numeric_limits<Dst>::max())
+ return std::numeric_limits<Dst>::max();
+ if (src <= std::numeric_limits<Dst>::min())
+ return std::numeric_limits<Dst>::min();
+ return static_cast<Dst>(src);
+}
+
+int Log2Floor(uint64_t n) {
+ // Copied from chromium src/base/bits.h
+ if (n == 0)
+ return -1;
+ int log = 0;
+ uint64_t value = n;
+ for (int i = 5; i >= 0; --i) {
+ int shift = (1 << i);
+ uint64_t x = value >> shift;
+ if (x != 0) {
+ value = x;
+ log += shift;
+ }
+ }
+ assert(value == 1u);
+ return log;
+}
+
+bool MappingsAreAdjacent(const MappingInfo& a, const MappingInfo& b) {
+ // Because of load biasing, we can end up with a situation where two
+ // mappings actually overlap. So we will define adjacency to also include a
+ // b start address that lies within a's address range (including starting
+ // immediately after a).
+ // Because load biasing only ever moves the start address backwards, the end
+ // address should still increase.
+ return a.start_addr <= b.start_addr && a.start_addr + a.size >= b.start_addr;
+}
+
+bool MappingLessThan(const MappingInfo* a, const MappingInfo* b) {
+ // Return true if mapping a is before mapping b.
+ // For the same reason (load biasing) we compare end addresses, which - unlike
+ // start addresses - will not have been modified.
+ return a->start_addr + a->size < b->start_addr + b->size;
+}
+
+size_t NextOrderedMapping(
+ const google_breakpad::wasteful_vector<MappingInfo*>& mappings,
+ size_t curr) {
+ // Find the mapping that directly follows mappings[curr].
+ // If no such mapping exists, return |invalid| to indicate this.
+ const size_t invalid = std::numeric_limits<size_t>::max();
+ size_t best = invalid;
+ for (size_t next = 0; next < mappings.size(); ++next) {
+ if (MappingLessThan(mappings[curr], mappings[next]) &&
+ (best == invalid || MappingLessThan(mappings[next], mappings[best]))) {
+ best = next;
+ }
+ }
+ return best;
+}
+
+#endif // !__LP64__
+
+class MicrodumpWriter {
+ public:
+ MicrodumpWriter(const ExceptionHandler::CrashContext* context,
+ const MappingList& mappings,
+ const MicrodumpExtraInfo& microdump_extra_info,
+ LinuxDumper* dumper)
+ : ucontext_(context ? &context->context : NULL),
+#if !defined(__ARM_EABI__) && !defined(__mips__)
+ float_state_(context ? &context->float_state : NULL),
+#endif
+ dumper_(dumper),
+ mapping_list_(mappings),
+ microdump_extra_info_(microdump_extra_info),
+ log_line_(NULL) {
+ log_line_ = reinterpret_cast<char*>(Alloc(kLineBufferSize));
+ if (log_line_)
+ log_line_[0] = '\0'; // Clear out the log line buffer.
+ }
+
+ ~MicrodumpWriter() { dumper_->ThreadsResume(); }
+
+ bool Init() {
+ // In the exceptional case where the system was out of memory and there
+ // wasn't even room to allocate the line buffer, bail out. There is nothing
+ // useful we can possibly achieve without the ability to Log. At least let's
+ // try to not crash.
+ if (!dumper_->Init() || !log_line_)
+ return false;
+ return dumper_->ThreadsSuspend() && dumper_->LateInit();
+ }
+
+ bool Dump() {
+ bool success;
+ LogLine("-----BEGIN BREAKPAD MICRODUMP-----");
+ DumpProductInformation();
+ DumpOSInformation();
+ DumpProcessType();
+ DumpGPUInformation();
+#if !defined(__LP64__)
+ DumpFreeSpace();
+#endif
+ success = DumpCrashingThread();
+ if (success)
+ success = DumpMappings();
+ LogLine("-----END BREAKPAD MICRODUMP-----");
+ dumper_->ThreadsResume();
+ return success;
+ }
+
+ private:
+ // Writes one line to the system log.
+ void LogLine(const char* msg) {
+#if defined(__ANDROID__)
+ logger::writeToCrashLog(msg);
+#else
+ logger::write(msg, my_strlen(msg));
+ logger::write("\n", 1);
+#endif
+ }
+
+ // Stages the given string in the current line buffer.
+ void LogAppend(const char* str) {
+ my_strlcat(log_line_, str, kLineBufferSize);
+ }
+
+ // As above (required to take precedence over template specialization below).
+ void LogAppend(char* str) {
+ LogAppend(const_cast<const char*>(str));
+ }
+
+ // Stages the hex repr. of the given int type in the current line buffer.
+ template<typename T>
+ void LogAppend(T value) {
+ // Make enough room to hex encode the largest int type + NUL.
+ static const char HEX[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F'};
+ char hexstr[sizeof(T) * 2 + 1];
+ for (int i = sizeof(T) * 2 - 1; i >= 0; --i, value >>= 4)
+ hexstr[i] = HEX[static_cast<uint8_t>(value) & 0x0F];
+ hexstr[sizeof(T) * 2] = '\0';
+ LogAppend(hexstr);
+ }
+
+ // Stages the buffer content hex-encoded in the current line buffer.
+ void LogAppend(const void* buf, size_t length) {
+ const uint8_t* ptr = reinterpret_cast<const uint8_t*>(buf);
+ for (size_t i = 0; i < length; ++i, ++ptr)
+ LogAppend(*ptr);
+ }
+
+ // Writes out the current line buffer on the system log.
+ void LogCommitLine() {
+ LogLine(log_line_);
+ my_strlcpy(log_line_, "", kLineBufferSize);
+ }
+
+ void DumpProductInformation() {
+ LogAppend("V ");
+ if (microdump_extra_info_.product_info) {
+ LogAppend(microdump_extra_info_.product_info);
+ } else {
+ LogAppend("UNKNOWN:0.0.0.0");
+ }
+ LogCommitLine();
+ }
+
+ void DumpProcessType() {
+ LogAppend("P ");
+ if (microdump_extra_info_.process_type) {
+ LogAppend(microdump_extra_info_.process_type);
+ } else {
+ LogAppend("UNKNOWN");
+ }
+ LogCommitLine();
+ }
+
+ void DumpOSInformation() {
+ const uint8_t n_cpus = static_cast<uint8_t>(sysconf(_SC_NPROCESSORS_CONF));
+
+#if defined(__ANDROID__)
+ const char kOSId[] = "A";
+#else
+ const char kOSId[] = "L";
+#endif
+
+// Dump the runtime architecture. On multiarch devices it might not match the
+// hw architecture (the one returned by uname()), for instance in the case of
+// a 32-bit app running on a aarch64 device.
+#if defined(__aarch64__)
+ const char kArch[] = "arm64";
+#elif defined(__ARMEL__)
+ const char kArch[] = "arm";
+#elif defined(__x86_64__)
+ const char kArch[] = "x86_64";
+#elif defined(__i386__)
+ const char kArch[] = "x86";
+#elif defined(__mips__)
+# if _MIPS_SIM == _ABIO32
+ const char kArch[] = "mips";
+# elif _MIPS_SIM == _ABI64
+ const char kArch[] = "mips64";
+# else
+# error "This mips ABI is currently not supported (n32)"
+#endif
+#else
+#error "This code has not been ported to your platform yet"
+#endif
+
+ LogAppend("O ");
+ LogAppend(kOSId);
+ LogAppend(" ");
+ LogAppend(kArch);
+ LogAppend(" ");
+ LogAppend(n_cpus);
+ LogAppend(" ");
+
+ // Dump the HW architecture (e.g., armv7l, aarch64).
+ struct utsname uts;
+ const bool has_uts_info = (uname(&uts) == 0);
+ const char* hwArch = has_uts_info ? uts.machine : "unknown_hw_arch";
+ LogAppend(hwArch);
+ LogAppend(" ");
+
+ // If the client has attached a build fingerprint to the MinidumpDescriptor
+ // use that one. Otherwise try to get some basic info from uname().
+ if (microdump_extra_info_.build_fingerprint) {
+ LogAppend(microdump_extra_info_.build_fingerprint);
+ } else if (has_uts_info) {
+ LogAppend(uts.release);
+ LogAppend(" ");
+ LogAppend(uts.version);
+ } else {
+ LogAppend("no build fingerprint available");
+ }
+ LogCommitLine();
+ }
+
+ void DumpGPUInformation() {
+ LogAppend("G ");
+ if (microdump_extra_info_.gpu_fingerprint) {
+ LogAppend(microdump_extra_info_.gpu_fingerprint);
+ } else {
+ LogAppend("UNKNOWN");
+ }
+ LogCommitLine();
+ }
+
+ bool DumpThreadStack(uint32_t thread_id,
+ uintptr_t stack_pointer,
+ int max_stack_len,
+ uint8_t** stack_copy) {
+ *stack_copy = NULL;
+ const void* stack;
+ size_t stack_len;
+
+ if (!dumper_->GetStackInfo(&stack, &stack_len, stack_pointer)) {
+ // The stack pointer might not be available. In this case we don't hard
+ // fail, just produce a (almost useless) microdump w/o a stack section.
+ return true;
+ }
+
+ LogAppend("S 0 ");
+ LogAppend(stack_pointer);
+ LogAppend(" ");
+ LogAppend(reinterpret_cast<uintptr_t>(stack));
+ LogAppend(" ");
+ LogAppend(stack_len);
+ LogCommitLine();
+
+ if (max_stack_len >= 0 &&
+ stack_len > static_cast<unsigned int>(max_stack_len)) {
+ stack_len = max_stack_len;
+ }
+
+ *stack_copy = reinterpret_cast<uint8_t*>(Alloc(stack_len));
+ dumper_->CopyFromProcess(*stack_copy, thread_id, stack, stack_len);
+
+ // Dump the content of the stack, splicing it into chunks which size is
+ // compatible with the max logcat line size (see LOGGER_ENTRY_MAX_PAYLOAD).
+ const size_t STACK_DUMP_CHUNK_SIZE = 384;
+ for (size_t stack_off = 0; stack_off < stack_len;
+ stack_off += STACK_DUMP_CHUNK_SIZE) {
+ LogAppend("S ");
+ LogAppend(reinterpret_cast<uintptr_t>(stack) + stack_off);
+ LogAppend(" ");
+ LogAppend(*stack_copy + stack_off,
+ std::min(STACK_DUMP_CHUNK_SIZE, stack_len - stack_off));
+ LogCommitLine();
+ }
+ return true;
+ }
+
+ // Write information about the crashing thread.
+ bool DumpCrashingThread() {
+ const unsigned num_threads = dumper_->threads().size();
+
+ for (unsigned i = 0; i < num_threads; ++i) {
+ MDRawThread thread;
+ my_memset(&thread, 0, sizeof(thread));
+ thread.thread_id = dumper_->threads()[i];
+
+ // Dump only the crashing thread.
+ if (static_cast<pid_t>(thread.thread_id) != dumper_->crash_thread())
+ continue;
+
+ assert(ucontext_);
+ assert(!dumper_->IsPostMortem());
+
+ uint8_t* stack_copy;
+ const uintptr_t stack_ptr = UContextReader::GetStackPointer(ucontext_);
+ if (!DumpThreadStack(thread.thread_id, stack_ptr, -1, &stack_copy))
+ return false;
+
+ RawContextCPU cpu;
+ my_memset(&cpu, 0, sizeof(RawContextCPU));
+#if !defined(__ARM_EABI__) && !defined(__mips__)
+ UContextReader::FillCPUContext(&cpu, ucontext_, float_state_);
+#else
+ UContextReader::FillCPUContext(&cpu, ucontext_);
+#endif
+ DumpCPUState(&cpu);
+ }
+ return true;
+ }
+
+ void DumpCPUState(RawContextCPU* cpu) {
+ LogAppend("C ");
+ LogAppend(cpu, sizeof(*cpu));
+ LogCommitLine();
+ }
+
+ // If there is caller-provided information about this mapping
+ // in the mapping_list_ list, return true. Otherwise, return false.
+ bool HaveMappingInfo(const MappingInfo& mapping) {
+ for (MappingList::const_iterator iter = mapping_list_.begin();
+ iter != mapping_list_.end();
+ ++iter) {
+ // Ignore any mappings that are wholly contained within
+ // mappings in the mapping_info_ list.
+ if (mapping.start_addr >= iter->first.start_addr &&
+ (mapping.start_addr + mapping.size) <=
+ (iter->first.start_addr + iter->first.size)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Dump information about the provided |mapping|. If |identifier| is non-NULL,
+ // use it instead of calculating a file ID from the mapping.
+ void DumpModule(const MappingInfo& mapping,
+ bool member,
+ unsigned int mapping_id,
+ const uint8_t* identifier) {
+
+ auto_wasteful_vector<uint8_t, kDefaultBuildIdSize> identifier_bytes(
+ dumper_->allocator());
+
+ if (identifier) {
+ // GUID was provided by caller.
+ identifier_bytes.insert(identifier_bytes.end(),
+ identifier,
+ identifier + sizeof(MDGUID));
+ } else {
+ dumper_->ElfFileIdentifierForMapping(
+ mapping,
+ member,
+ mapping_id,
+ identifier_bytes);
+ }
+
+ // Copy as many bytes of |identifier| as will fit into a MDGUID
+ MDGUID module_identifier = {0};
+ memcpy(&module_identifier, &identifier_bytes[0],
+ std::min(sizeof(MDGUID), identifier_bytes.size()));
+
+ char file_name[NAME_MAX];
+ char file_path[NAME_MAX];
+ dumper_->GetMappingEffectiveNameAndPath(
+ mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
+
+ LogAppend("M ");
+ LogAppend(static_cast<uintptr_t>(mapping.start_addr));
+ LogAppend(" ");
+ LogAppend(mapping.offset);
+ LogAppend(" ");
+ LogAppend(mapping.size);
+ LogAppend(" ");
+ LogAppend(module_identifier.data1);
+ LogAppend(module_identifier.data2);
+ LogAppend(module_identifier.data3);
+ LogAppend(module_identifier.data4[0]);
+ LogAppend(module_identifier.data4[1]);
+ LogAppend(module_identifier.data4[2]);
+ LogAppend(module_identifier.data4[3]);
+ LogAppend(module_identifier.data4[4]);
+ LogAppend(module_identifier.data4[5]);
+ LogAppend(module_identifier.data4[6]);
+ LogAppend(module_identifier.data4[7]);
+ LogAppend("0 "); // Age is always 0 on Linux.
+ LogAppend(file_name);
+ LogCommitLine();
+ }
+
+#if !defined(__LP64__)
+ void DumpFreeSpace() {
+ const google_breakpad::wasteful_vector<MappingInfo*>& mappings =
+ dumper_->mappings();
+ if (mappings.size() == 0) return;
+
+ // This is complicated by the fact that mappings is not in order. It should
+ // be mostly in order, however the mapping that contains the entry point for
+ // the process is always at the front of the vector.
+
+ static const int HBITS = sizeof(size_t) * 8;
+ size_t hole_histogram[HBITS];
+ my_memset(hole_histogram, 0, sizeof(hole_histogram));
+
+ // Find the lowest address mapping.
+ size_t curr = 0;
+ for (size_t i = 1; i < mappings.size(); ++i) {
+ if (mappings[i]->start_addr < mappings[curr]->start_addr) curr = i;
+ }
+
+ uintptr_t lo_addr = mappings[curr]->start_addr;
+
+ size_t hole_cnt = 0;
+ size_t hole_max = 0;
+ size_t hole_sum = 0;
+
+ while (true) {
+ // Skip to the end of an adjacent run of mappings. This is an optimization
+ // for the fact that mappings is mostly sorted.
+ while (curr != mappings.size() - 1 &&
+ MappingsAreAdjacent(*mappings[curr], *mappings[curr + 1])) {
+ ++curr;
+ }
+
+ size_t next = NextOrderedMapping(mappings, curr);
+ if (next == std::numeric_limits<size_t>::max())
+ break;
+
+ uintptr_t hole_lo = mappings[curr]->start_addr + mappings[curr]->size;
+ uintptr_t hole_hi = mappings[next]->start_addr;
+
+ if (hole_hi > hole_lo) {
+ size_t hole_sz = hole_hi - hole_lo;
+ hole_sum += hole_sz;
+ hole_max = std::max(hole_sz, hole_max);
+ ++hole_cnt;
+ ++hole_histogram[Log2Floor(hole_sz)];
+ }
+ curr = next;
+ }
+
+ uintptr_t hi_addr = mappings[curr]->start_addr + mappings[curr]->size;
+
+ LogAppend("H ");
+ LogAppend(lo_addr);
+ LogAppend(" ");
+ LogAppend(hi_addr);
+ LogAppend(" ");
+ LogAppend(saturated_cast<uint16_t>(hole_cnt));
+ LogAppend(" ");
+ LogAppend(hole_max);
+ LogAppend(" ");
+ LogAppend(hole_sum);
+ for (unsigned int i = 0; i < HBITS; ++i) {
+ if (!hole_histogram[i]) continue;
+ LogAppend(" ");
+ LogAppend(saturated_cast<uint8_t>(i));
+ LogAppend(":");
+ LogAppend(saturated_cast<uint8_t>(hole_histogram[i]));
+ }
+ LogCommitLine();
+ }
+#endif
+
+ // Write information about the mappings in effect.
+ bool DumpMappings() {
+ // First write all the mappings from the dumper
+ for (unsigned i = 0; i < dumper_->mappings().size(); ++i) {
+ const MappingInfo& mapping = *dumper_->mappings()[i];
+ if (mapping.name[0] == 0 || // only want modules with filenames.
+ !mapping.exec || // only want executable mappings.
+ mapping.size < 4096 || // too small to get a signature for.
+ HaveMappingInfo(mapping)) {
+ continue;
+ }
+
+ DumpModule(mapping, true, i, NULL);
+ }
+ // Next write all the mappings provided by the caller
+ for (MappingList::const_iterator iter = mapping_list_.begin();
+ iter != mapping_list_.end();
+ ++iter) {
+ DumpModule(iter->first, false, 0, iter->second);
+ }
+ return true;
+ }
+
+ void* Alloc(unsigned bytes) { return dumper_->allocator()->Alloc(bytes); }
+
+ const ucontext_t* const ucontext_;
+#if !defined(__ARM_EABI__) && !defined(__mips__)
+ const google_breakpad::fpstate_t* const float_state_;
+#endif
+ LinuxDumper* dumper_;
+ const MappingList& mapping_list_;
+ const MicrodumpExtraInfo microdump_extra_info_;
+ char* log_line_;
+};
+} // namespace
+
+namespace google_breakpad {
+
+bool WriteMicrodump(pid_t crashing_process,
+ const void* blob,
+ size_t blob_size,
+ const MappingList& mappings,
+ const MicrodumpExtraInfo& microdump_extra_info) {
+ LinuxPtraceDumper dumper(crashing_process);
+ const ExceptionHandler::CrashContext* context = NULL;
+ if (blob) {
+ if (blob_size != sizeof(ExceptionHandler::CrashContext))
+ return false;
+ context = reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
+ dumper.set_crash_address(
+ reinterpret_cast<uintptr_t>(context->siginfo.si_addr));
+ dumper.set_crash_signal(context->siginfo.si_signo);
+ dumper.set_crash_thread(context->tid);
+ }
+ MicrodumpWriter writer(context, mappings, microdump_extra_info, &dumper);
+ if (!writer.Init())
+ return false;
+ return writer.Dump();
+}
+
+} // namespace google_breakpad
diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.h b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.h
new file mode 100644
index 000000000..7c742761d
--- /dev/null
+++ b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2014, 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.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "client/linux/dump_writer_common/mapping_info.h"
+
+namespace google_breakpad {
+
+struct MicrodumpExtraInfo;
+
+// Writes a microdump (a reduced dump containing only the state of the crashing
+// thread) on the console (logcat on Android). These functions do not malloc nor
+// use libc functions which may. Thus, it can be used in contexts where the
+// state of the heap may be corrupt.
+// Args:
+// crashing_process: the pid of the crashing process. This must be trusted.
+// blob: a blob of data from the crashing process. See exception_handler.h
+// blob_size: the length of |blob| in bytes.
+// mappings: a list of additional mappings provided by the application.
+// build_fingerprint: a (optional) C string which determines the OS
+// build fingerprint (e.g., aosp/occam/mako:5.1.1/LMY47W/1234:eng/dev-keys).
+// product_info: a (optional) C string which determines the product name and
+// version (e.g., WebView:42.0.2311.136).
+//
+// Returns true iff successful.
+bool WriteMicrodump(pid_t crashing_process,
+ const void* blob,
+ size_t blob_size,
+ const MappingList& mappings,
+ const MicrodumpExtraInfo& microdump_extra_info);
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_
diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer_unittest.cc
new file mode 100644
index 000000000..58a731188
--- /dev/null
+++ b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer_unittest.cc
@@ -0,0 +1,257 @@
+// Copyright (c) 2014 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.
+
+#include <ctype.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <sstream>
+#include <string>
+
+#include "breakpad_googletest_includes.h"
+#include "client/linux/handler/exception_handler.h"
+#include "client/linux/handler/microdump_extra_info.h"
+#include "client/linux/microdump_writer/microdump_writer.h"
+#include "common/linux/eintr_wrapper.h"
+#include "common/linux/ignore_ret.h"
+#include "common/scoped_ptr.h"
+#include "common/tests/auto_tempdir.h"
+#include "common/using_std_string.h"
+
+using namespace google_breakpad;
+
+namespace {
+
+typedef testing::Test MicrodumpWriterTest;
+
+MicrodumpExtraInfo MakeMicrodumpExtraInfo(
+ const char* build_fingerprint,
+ const char* product_info,
+ const char* gpu_fingerprint) {
+ MicrodumpExtraInfo info;
+ info.build_fingerprint = build_fingerprint;
+ info.product_info = product_info;
+ info.gpu_fingerprint = gpu_fingerprint;
+ return info;
+}
+
+void CrashAndGetMicrodump(
+ const MappingList& mappings,
+ const MicrodumpExtraInfo& microdump_extra_info,
+ scoped_array<char>* buf) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ AutoTempDir temp_dir;
+ string stderr_file = temp_dir.path() + "/stderr.log";
+ int err_fd = open(stderr_file.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
+ ASSERT_NE(-1, err_fd);
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
+ close(fds[0]);
+ syscall(__NR_exit);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+
+ // Set a non-zero tid to avoid tripping asserts.
+ context.tid = child;
+
+ // Redirect temporarily stderr to the stderr.log file.
+ int save_err = dup(STDERR_FILENO);
+ ASSERT_NE(-1, save_err);
+ ASSERT_NE(-1, dup2(err_fd, STDERR_FILENO));
+
+ ASSERT_TRUE(WriteMicrodump(child, &context, sizeof(context), mappings,
+ microdump_extra_info));
+
+ // Revert stderr back to the console.
+ dup2(save_err, STDERR_FILENO);
+ close(save_err);
+
+ // Read back the stderr file and check for the microdump marker.
+ fsync(err_fd);
+ lseek(err_fd, 0, SEEK_SET);
+ const size_t kBufSize = 64 * 1024;
+ buf->reset(new char[kBufSize]);
+ ASSERT_GT(read(err_fd, buf->get(), kBufSize), 0);
+
+ close(err_fd);
+ close(fds[1]);
+
+ ASSERT_NE(static_cast<char*>(0), strstr(
+ buf->get(), "-----BEGIN BREAKPAD MICRODUMP-----"));
+ ASSERT_NE(static_cast<char*>(0), strstr(
+ buf->get(), "-----END BREAKPAD MICRODUMP-----"));
+}
+
+void CheckMicrodumpContents(const string& microdump_content,
+ const MicrodumpExtraInfo& expected_info) {
+ std::istringstream iss(microdump_content);
+ bool did_find_os_info = false;
+ bool did_find_product_info = false;
+ bool did_find_gpu_info = false;
+ for (string line; std::getline(iss, line);) {
+ if (line.find("O ") == 0) {
+ std::istringstream os_info_tokens(line);
+ string token;
+ os_info_tokens.ignore(2); // Ignore the "O " preamble.
+ // Check the OS descriptor char (L=Linux, A=Android).
+ os_info_tokens >> token;
+ ASSERT_TRUE(token == "L" || token == "A");
+
+ os_info_tokens >> token; // HW architecture.
+ os_info_tokens >> token; // Number of cpus.
+ for (size_t i = 0; i < token.size(); ++i)
+ ASSERT_TRUE(isxdigit(token[i]));
+ os_info_tokens >> token; // SW architecture.
+
+ // Check that the build fingerprint is in the right place.
+ os_info_tokens >> token;
+ if (expected_info.build_fingerprint)
+ ASSERT_EQ(expected_info.build_fingerprint, token);
+ did_find_os_info = true;
+ } else if (line.find("V ") == 0) {
+ if (expected_info.product_info)
+ ASSERT_EQ(string("V ") + expected_info.product_info, line);
+ did_find_product_info = true;
+ } else if (line.find("G ") == 0) {
+ if (expected_info.gpu_fingerprint)
+ ASSERT_EQ(string("G ") + expected_info.gpu_fingerprint, line);
+ did_find_gpu_info = true;
+ }
+ }
+ ASSERT_TRUE(did_find_os_info);
+ ASSERT_TRUE(did_find_product_info);
+ ASSERT_TRUE(did_find_gpu_info);
+}
+
+void CheckMicrodumpContents(const string& microdump_content,
+ const string& expected_fingerprint,
+ const string& expected_product_info,
+ const string& expected_gpu_fingerprint) {
+ CheckMicrodumpContents(
+ microdump_content,
+ MakeMicrodumpExtraInfo(expected_fingerprint.c_str(),
+ expected_product_info.c_str(),
+ expected_gpu_fingerprint.c_str()));
+}
+
+TEST(MicrodumpWriterTest, BasicWithMappings) {
+ // Push some extra mapping to check the MappingList logic.
+ const uint32_t memory_size = sysconf(_SC_PAGESIZE);
+ const char* kMemoryName = "libfoo.so";
+ const uint8_t kModuleGUID[sizeof(MDGUID)] = {
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
+ };
+
+ MappingInfo info;
+ info.start_addr = memory_size;
+ info.size = memory_size;
+ info.offset = 42;
+ strcpy(info.name, kMemoryName);
+
+ MappingList mappings;
+ MappingEntry mapping;
+ mapping.first = info;
+ memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
+ mappings.push_back(mapping);
+
+ scoped_array<char> buf;
+ CrashAndGetMicrodump(mappings, MicrodumpExtraInfo(), &buf);
+
+#ifdef __LP64__
+ ASSERT_NE(static_cast<char*>(0), strstr(
+ buf.get(), "M 0000000000001000 000000000000002A 0000000000001000 "
+ "33221100554477668899AABBCCDDEEFF0 libfoo.so"));
+#else
+ ASSERT_NE(static_cast<char*>(0), strstr(
+ buf.get(), "M 00001000 0000002A 00001000 "
+ "33221100554477668899AABBCCDDEEFF0 libfoo.so"));
+#endif
+
+ // In absence of a product info in the minidump, the writer should just write
+ // an unknown marker.
+ ASSERT_NE(static_cast<char*>(0), strstr(
+ buf.get(), "V UNKNOWN:0.0.0.0"));
+}
+
+// Ensure that the product info and build fingerprint metadata show up in the
+// final microdump if present.
+TEST(MicrodumpWriterTest, BuildFingerprintAndProductInfo) {
+ const char kProductInfo[] = "MockProduct:42.0.2311.99";
+ const char kBuildFingerprint[] =
+ "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
+ const char kGPUFingerprint[] =
+ "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
+ const MicrodumpExtraInfo kMicrodumpExtraInfo(
+ MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
+ scoped_array<char> buf;
+ MappingList no_mappings;
+
+ CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf);
+ CheckMicrodumpContents(string(buf.get()), kMicrodumpExtraInfo);
+}
+
+TEST(MicrodumpWriterTest, NoProductInfo) {
+ const char kBuildFingerprint[] = "foobar";
+ const char kGPUFingerprint[] = "bazqux";
+ scoped_array<char> buf;
+ MappingList no_mappings;
+
+ const MicrodumpExtraInfo kMicrodumpExtraInfoNoProductInfo(
+ MakeMicrodumpExtraInfo(kBuildFingerprint, NULL, kGPUFingerprint));
+
+ CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoProductInfo, &buf);
+ CheckMicrodumpContents(string(buf.get()), kBuildFingerprint,
+ "UNKNOWN:0.0.0.0", kGPUFingerprint);
+}
+
+TEST(MicrodumpWriterTest, NoGPUInfo) {
+ const char kProductInfo[] = "bazqux";
+ const char kBuildFingerprint[] = "foobar";
+ scoped_array<char> buf;
+ MappingList no_mappings;
+
+ const MicrodumpExtraInfo kMicrodumpExtraInfoNoGPUInfo(
+ MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, NULL));
+
+ CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoGPUInfo, &buf);
+ CheckMicrodumpContents(string(buf.get()), kBuildFingerprint,
+ kProductInfo, "UNKNOWN");
+}
+} // namespace