breakpad/src/client/linux/minidump_writer/minidump_writer.cc
Lars Volker f25a411200 Fix stack collection with size limit
src/client/linux/minidump_writer/minidump_writer.cc:273 obtains the
stack info by calling GetStackInfo(). That method will return the
stack base address, aligned to the bottom of the memory page that
'stack_pointer' is in. After that it will cap the size of the memory
area to be copied into the minidump to 'max_stack_len', starting from
the base address, if the caller requested so. This will be the case
when collecting reduced stacks, as introduced by this change:
https://breakpad.appspot.com/487002/

In such cases the caller will request 2048 bytes of memory. However
GetStackInfo() will have aligned the base address to the page
boundary, by default 4096 bytes. If the stack, which grows towards the
base address from the top ends before the 2048 bytes of the first
block, then we will not collect any useful part of the stack.

As a fix we skip chunks of 'max_stack_len' bytes starting from
the base address until the stack_pointer is actually contained in the
chunk, which we will add to the minidump file.

BUG=https://bugs.chromium.org/p/google-breakpad/issues/detail?id=695
R=ivanpe@chromium.org

Review URL: https://codereview.chromium.org/1959643004 .

Patch from Lars Volker <lv@cloudera.com>.
2016-05-24 11:49:35 -07:00

1378 lines
49 KiB
C++

// Copyright (c) 2010, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This code writes out minidump files:
// http://msdn.microsoft.com/en-us/library/ms680378(VS.85,loband).aspx
//
// Minidumps are a Microsoft format which Breakpad uses for recording crash
// dumps. This code has to run in a compromised environment (the address space
// may have received SIGSEGV), thus the following rules apply:
// * You may not enter the dynamic linker. This means that we cannot call
// any symbols in a shared library (inc libc). Because of this we replace
// libc functions in linux_libc_support.h.
// * You may not call syscalls via the libc wrappers. This rule is a subset
// of the first rule but it bears repeating. We have direct wrappers
// around the system calls in linux_syscall_support.h.
// * You may not malloc. There's an alternative allocator in memory.h and
// a canonical instance in the LinuxDumper object. We use the placement
// new form to allocate objects and we don't delete them.
#include "client/linux/handler/minidump_descriptor.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "client/minidump_file_writer-inl.h"
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <link.h>
#include <stdio.h>
#if defined(__ANDROID__)
#include <sys/system_properties.h>
#endif
#include <sys/types.h>
#include <sys/ucontext.h>
#include <sys/user.h>
#include <sys/utsname.h>
#include <time.h>
#include <unistd.h>
#include <algorithm>
#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/minidump_writer/cpu_set.h"
#include "client/linux/minidump_writer/line_reader.h"
#include "client/linux/minidump_writer/linux_dumper.h"
#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
#include "client/linux/minidump_writer/proc_cpuinfo_reader.h"
#include "client/minidump_file_writer.h"
#include "common/linux/file_id.h"
#include "common/linux/linux_libc_support.h"
#include "common/minidump_type_helper.h"
#include "google_breakpad/common/minidump_format.h"
#include "third_party/lss/linux_syscall_support.h"
namespace {
using google_breakpad::AppMemoryList;
using google_breakpad::auto_wasteful_vector;
using google_breakpad::ExceptionHandler;
using google_breakpad::CpuSet;
using google_breakpad::kDefaultBuildIdSize;
using google_breakpad::LineReader;
using google_breakpad::LinuxDumper;
using google_breakpad::LinuxPtraceDumper;
using google_breakpad::MDTypeHelper;
using google_breakpad::MappingEntry;
using google_breakpad::MappingInfo;
using google_breakpad::MappingList;
using google_breakpad::MinidumpFileWriter;
using google_breakpad::PageAllocator;
using google_breakpad::ProcCpuInfoReader;
using google_breakpad::RawContextCPU;
using google_breakpad::ThreadInfo;
using google_breakpad::TypedMDRVA;
using google_breakpad::UContextReader;
using google_breakpad::UntypedMDRVA;
using google_breakpad::wasteful_vector;
typedef MDTypeHelper<sizeof(void*)>::MDRawDebug MDRawDebug;
typedef MDTypeHelper<sizeof(void*)>::MDRawLinkMap MDRawLinkMap;
class MinidumpWriter {
public:
// The following kLimit* constants are for when minidump_size_limit_ is set
// and the minidump size might exceed it.
//
// Estimate for how big each thread's stack will be (in bytes).
static const unsigned kLimitAverageThreadStackLength = 8 * 1024;
// Number of threads whose stack size we don't want to limit. These base
// threads will simply be the first N threads returned by the dumper (although
// the crashing thread will never be limited). Threads beyond this count are
// the extra threads.
static const unsigned kLimitBaseThreadCount = 20;
// Maximum stack size to dump for any extra thread (in bytes).
static const unsigned kLimitMaxExtraThreadStackLen = 2 * 1024;
// Make sure this number of additional bytes can fit in the minidump
// (exclude the stack data).
static const unsigned kLimitMinidumpFudgeFactor = 64 * 1024;
MinidumpWriter(const char* minidump_path,
int minidump_fd,
const ExceptionHandler::CrashContext* context,
const MappingList& mappings,
const AppMemoryList& appmem,
LinuxDumper* dumper)
: fd_(minidump_fd),
path_(minidump_path),
ucontext_(context ? &context->context : NULL),
#if !defined(__ARM_EABI__) && !defined(__mips__)
float_state_(context ? &context->float_state : NULL),
#endif
dumper_(dumper),
minidump_size_limit_(-1),
memory_blocks_(dumper_->allocator()),
mapping_list_(mappings),
app_memory_list_(appmem) {
// Assert there should be either a valid fd or a valid path, not both.
assert(fd_ != -1 || minidump_path);
assert(fd_ == -1 || !minidump_path);
}
bool Init() {
if (!dumper_->Init())
return false;
if (fd_ != -1)
minidump_writer_.SetFile(fd_);
else if (!minidump_writer_.Open(path_))
return false;
return dumper_->ThreadsSuspend() && dumper_->LateInit();
}
~MinidumpWriter() {
// Don't close the file descriptor when it's been provided explicitly.
// Callers might still need to use it.
if (fd_ == -1)
minidump_writer_.Close();
dumper_->ThreadsResume();
}
bool Dump() {
// A minidump file contains a number of tagged streams. This is the number
// of stream which we write.
unsigned kNumWriters = 13;
TypedMDRVA<MDRawDirectory> dir(&minidump_writer_);
{
// Ensure the header gets flushed, as that happens in the destructor.
// If a crash occurs somewhere below, at least the header will be
// intact.
TypedMDRVA<MDRawHeader> header(&minidump_writer_);
if (!header.Allocate())
return false;
if (!dir.AllocateArray(kNumWriters))
return false;
my_memset(header.get(), 0, sizeof(MDRawHeader));
header.get()->signature = MD_HEADER_SIGNATURE;
header.get()->version = MD_HEADER_VERSION;
header.get()->time_date_stamp = time(NULL);
header.get()->stream_count = kNumWriters;
header.get()->stream_directory_rva = dir.position();
}
unsigned dir_index = 0;
MDRawDirectory dirent;
if (!WriteThreadListStream(&dirent))
return false;
dir.CopyIndex(dir_index++, &dirent);
if (!WriteMappings(&dirent))
return false;
dir.CopyIndex(dir_index++, &dirent);
if (!WriteAppMemory())
return false;
if (!WriteMemoryListStream(&dirent))
return false;
dir.CopyIndex(dir_index++, &dirent);
if (!WriteExceptionStream(&dirent))
return false;
dir.CopyIndex(dir_index++, &dirent);
if (!WriteSystemInfoStream(&dirent))
return false;
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_CPU_INFO;
if (!WriteFile(&dirent.location, "/proc/cpuinfo"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_PROC_STATUS;
if (!WriteProcFile(&dirent.location, GetCrashThread(), "status"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_LSB_RELEASE;
if (!WriteFile(&dirent.location, "/etc/lsb-release"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_CMD_LINE;
if (!WriteProcFile(&dirent.location, GetCrashThread(), "cmdline"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_ENVIRON;
if (!WriteProcFile(&dirent.location, GetCrashThread(), "environ"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_AUXV;
if (!WriteProcFile(&dirent.location, GetCrashThread(), "auxv"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_MAPS;
if (!WriteProcFile(&dirent.location, GetCrashThread(), "maps"))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
dirent.stream_type = MD_LINUX_DSO_DEBUG;
if (!WriteDSODebugStream(&dirent))
NullifyDirectoryEntry(&dirent);
dir.CopyIndex(dir_index++, &dirent);
// If you add more directory entries, don't forget to update kNumWriters,
// above.
dumper_->ThreadsResume();
return true;
}
bool FillThreadStack(MDRawThread* thread, 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)) {
UntypedMDRVA memory(&minidump_writer_);
if (max_stack_len >= 0 &&
stack_len > static_cast<unsigned int>(max_stack_len)) {
stack_len = max_stack_len;
// Skip empty chunks of length max_stack_len.
uintptr_t int_stack = reinterpret_cast<uintptr_t>(stack);
if (max_stack_len > 0) {
while (int_stack + max_stack_len < stack_pointer) {
int_stack += max_stack_len;
}
}
stack = reinterpret_cast<const void*>(int_stack);
}
if (!memory.Allocate(stack_len))
return false;
*stack_copy = reinterpret_cast<uint8_t*>(Alloc(stack_len));
dumper_->CopyFromProcess(*stack_copy, thread->thread_id, stack,
stack_len);
memory.Copy(*stack_copy, stack_len);
thread->stack.start_of_memory_range =
reinterpret_cast<uintptr_t>(stack);
thread->stack.memory = memory.location();
memory_blocks_.push_back(thread->stack);
} else {
thread->stack.start_of_memory_range = stack_pointer;
thread->stack.memory.data_size = 0;
thread->stack.memory.rva = minidump_writer_.position();
}
return true;
}
// Write information about the threads.
bool WriteThreadListStream(MDRawDirectory* dirent) {
const unsigned num_threads = dumper_->threads().size();
TypedMDRVA<uint32_t> list(&minidump_writer_);
if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThread)))
return false;
dirent->stream_type = MD_THREAD_LIST_STREAM;
dirent->location = list.location();
*list.get() = num_threads;
// If there's a minidump size limit, check if it might be exceeded. Since
// most of the space is filled with stack data, just check against that.
// If this expects to exceed the limit, set extra_thread_stack_len such
// that any thread beyond the first kLimitBaseThreadCount threads will
// have only kLimitMaxExtraThreadStackLen bytes dumped.
int extra_thread_stack_len = -1; // default to no maximum
if (minidump_size_limit_ >= 0) {
const unsigned estimated_total_stack_size = num_threads *
kLimitAverageThreadStackLength;
const off_t estimated_minidump_size = minidump_writer_.position() +
estimated_total_stack_size + kLimitMinidumpFudgeFactor;
if (estimated_minidump_size > minidump_size_limit_)
extra_thread_stack_len = kLimitMaxExtraThreadStackLen;
}
for (unsigned i = 0; i < num_threads; ++i) {
MDRawThread thread;
my_memset(&thread, 0, sizeof(thread));
thread.thread_id = dumper_->threads()[i];
// We have a different source of information for the crashing thread. If
// we used the actual state of the thread we would find it running in the
// signal handler with the alternative stack, which would be deeply
// unhelpful.
if (static_cast<pid_t>(thread.thread_id) == GetCrashThread() &&
ucontext_ &&
!dumper_->IsPostMortem()) {
uint8_t* stack_copy;
const uintptr_t stack_ptr = UContextReader::GetStackPointer(ucontext_);
if (!FillThreadStack(&thread, stack_ptr, -1, &stack_copy))
return false;
// Copy 256 bytes around crashing instruction pointer to minidump.
const size_t kIPMemorySize = 256;
uint64_t ip = UContextReader::GetInstructionPointer(ucontext_);
// Bound it to the upper and lower bounds of the memory map
// it's contained within. If it's not in mapped memory,
// don't bother trying to write it.
bool ip_is_mapped = false;
MDMemoryDescriptor ip_memory_d;
for (unsigned j = 0; j < dumper_->mappings().size(); ++j) {
const MappingInfo& mapping = *dumper_->mappings()[j];
if (ip >= mapping.start_addr &&
ip < mapping.start_addr + mapping.size) {
ip_is_mapped = true;
// Try to get 128 bytes before and after the IP, but
// settle for whatever's available.
ip_memory_d.start_of_memory_range =
std::max(mapping.start_addr,
uintptr_t(ip - (kIPMemorySize / 2)));
uintptr_t end_of_range =
std::min(uintptr_t(ip + (kIPMemorySize / 2)),
uintptr_t(mapping.start_addr + mapping.size));
ip_memory_d.memory.data_size =
end_of_range - ip_memory_d.start_of_memory_range;
break;
}
}
if (ip_is_mapped) {
UntypedMDRVA ip_memory(&minidump_writer_);
if (!ip_memory.Allocate(ip_memory_d.memory.data_size))
return false;
uint8_t* memory_copy =
reinterpret_cast<uint8_t*>(Alloc(ip_memory_d.memory.data_size));
dumper_->CopyFromProcess(
memory_copy,
thread.thread_id,
reinterpret_cast<void*>(ip_memory_d.start_of_memory_range),
ip_memory_d.memory.data_size);
ip_memory.Copy(memory_copy, ip_memory_d.memory.data_size);
ip_memory_d.memory = ip_memory.location();
memory_blocks_.push_back(ip_memory_d);
}
TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
if (!cpu.Allocate())
return false;
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
#if !defined(__ARM_EABI__) && !defined(__mips__)
UContextReader::FillCPUContext(cpu.get(), ucontext_, float_state_);
#else
UContextReader::FillCPUContext(cpu.get(), ucontext_);
#endif
thread.thread_context = cpu.location();
crashing_thread_context_ = cpu.location();
} else {
ThreadInfo info;
if (!dumper_->GetThreadInfoByIndex(i, &info))
return false;
uint8_t* stack_copy;
int max_stack_len = -1; // default to no maximum for this thread
if (minidump_size_limit_ >= 0 && i >= kLimitBaseThreadCount)
max_stack_len = extra_thread_stack_len;
if (!FillThreadStack(&thread, info.stack_pointer, max_stack_len,
&stack_copy))
return false;
TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
if (!cpu.Allocate())
return false;
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
info.FillCPUContext(cpu.get());
thread.thread_context = cpu.location();
if (dumper_->threads()[i] == GetCrashThread()) {
crashing_thread_context_ = cpu.location();
if (!dumper_->IsPostMortem()) {
// This is the crashing thread of a live process, but
// no context was provided, so set the crash address
// while the instruction pointer is already here.
dumper_->set_crash_address(info.GetInstructionPointer());
}
}
}
list.CopyIndexAfterObject(i, &thread, sizeof(thread));
}
return true;
}
// Write application-provided memory regions.
bool WriteAppMemory() {
for (AppMemoryList::const_iterator iter = app_memory_list_.begin();
iter != app_memory_list_.end();
++iter) {
uint8_t* data_copy =
reinterpret_cast<uint8_t*>(dumper_->allocator()->Alloc(iter->length));
dumper_->CopyFromProcess(data_copy, GetCrashThread(), iter->ptr,
iter->length);
UntypedMDRVA memory(&minidump_writer_);
if (!memory.Allocate(iter->length)) {
return false;
}
memory.Copy(data_copy, iter->length);
MDMemoryDescriptor desc;
desc.start_of_memory_range = reinterpret_cast<uintptr_t>(iter->ptr);
desc.memory = memory.location();
memory_blocks_.push_back(desc);
}
return true;
}
static bool ShouldIncludeMapping(const MappingInfo& mapping) {
if (mapping.name[0] == 0 || // only want modules with filenames.
// Only want to include one mapping per shared lib.
// Avoid filtering executable mappings.
(mapping.offset != 0 && !mapping.exec) ||
mapping.size < 4096) { // too small to get a signature for.
return false;
}
return true;
}
// 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;
}
// Write information about the mappings in effect. Because we are using the
// minidump format, the information about the mappings is pretty limited.
// Because of this, we also include the full, unparsed, /proc/$x/maps file in
// another stream in the file.
bool WriteMappings(MDRawDirectory* dirent) {
const unsigned num_mappings = dumper_->mappings().size();
unsigned num_output_mappings = mapping_list_.size();
for (unsigned i = 0; i < dumper_->mappings().size(); ++i) {
const MappingInfo& mapping = *dumper_->mappings()[i];
if (ShouldIncludeMapping(mapping) && !HaveMappingInfo(mapping))
num_output_mappings++;
}
TypedMDRVA<uint32_t> list(&minidump_writer_);
if (num_output_mappings) {
if (!list.AllocateObjectAndArray(num_output_mappings, MD_MODULE_SIZE))
return false;
} else {
// Still create the module list stream, although it will have zero
// modules.
if (!list.Allocate())
return false;
}
dirent->stream_type = MD_MODULE_LIST_STREAM;
dirent->location = list.location();
*list.get() = num_output_mappings;
// First write all the mappings from the dumper
unsigned int j = 0;
for (unsigned i = 0; i < num_mappings; ++i) {
const MappingInfo& mapping = *dumper_->mappings()[i];
if (!ShouldIncludeMapping(mapping) || HaveMappingInfo(mapping))
continue;
MDRawModule mod;
if (!FillRawModule(mapping, true, i, &mod, NULL))
return false;
list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
}
// Next write all the mappings provided by the caller
for (MappingList::const_iterator iter = mapping_list_.begin();
iter != mapping_list_.end();
++iter) {
MDRawModule mod;
if (!FillRawModule(iter->first, false, 0, &mod, iter->second))
return false;
list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
}
return true;
}
// Fill the MDRawModule |mod| with information about the provided
// |mapping|. If |identifier| is non-NULL, use it instead of calculating
// a file ID from the mapping.
bool FillRawModule(const MappingInfo& mapping,
bool member,
unsigned int mapping_id,
MDRawModule* mod,
const uint8_t* identifier) {
my_memset(mod, 0, MD_MODULE_SIZE);
mod->base_of_image = mapping.start_addr;
mod->size_of_image = mapping.size;
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 {
// Note: ElfFileIdentifierForMapping() can manipulate the |mapping.name|.
dumper_->ElfFileIdentifierForMapping(mapping,
member,
mapping_id,
identifier_bytes);
}
if (!identifier_bytes.empty()) {
UntypedMDRVA cv(&minidump_writer_);
if (!cv.Allocate(MDCVInfoELF_minsize + identifier_bytes.size()))
return false;
const uint32_t cv_signature = MD_CVINFOELF_SIGNATURE;
cv.Copy(&cv_signature, sizeof(cv_signature));
cv.Copy(cv.position() + sizeof(cv_signature), &identifier_bytes[0],
identifier_bytes.size());
mod->cv_record = cv.location();
}
char file_name[NAME_MAX];
char file_path[NAME_MAX];
dumper_->GetMappingEffectiveNameAndPath(
mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
MDLocationDescriptor ld;
if (!minidump_writer_.WriteString(file_path, my_strlen(file_path), &ld))
return false;
mod->module_name_rva = ld.rva;
return true;
}
bool WriteMemoryListStream(MDRawDirectory* dirent) {
TypedMDRVA<uint32_t> list(&minidump_writer_);
if (memory_blocks_.size()) {
if (!list.AllocateObjectAndArray(memory_blocks_.size(),
sizeof(MDMemoryDescriptor)))
return false;
} else {
// Still create the memory list stream, although it will have zero
// memory blocks.
if (!list.Allocate())
return false;
}
dirent->stream_type = MD_MEMORY_LIST_STREAM;
dirent->location = list.location();
*list.get() = memory_blocks_.size();
for (size_t i = 0; i < memory_blocks_.size(); ++i) {
list.CopyIndexAfterObject(i, &memory_blocks_[i],
sizeof(MDMemoryDescriptor));
}
return true;
}
bool WriteExceptionStream(MDRawDirectory* dirent) {
TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_);
if (!exc.Allocate())
return false;
my_memset(exc.get(), 0, sizeof(MDRawExceptionStream));
dirent->stream_type = MD_EXCEPTION_STREAM;
dirent->location = exc.location();
exc.get()->thread_id = GetCrashThread();
exc.get()->exception_record.exception_code = dumper_->crash_signal();
exc.get()->exception_record.exception_address = dumper_->crash_address();
exc.get()->thread_context = crashing_thread_context_;
return true;
}
bool WriteSystemInfoStream(MDRawDirectory* dirent) {
TypedMDRVA<MDRawSystemInfo> si(&minidump_writer_);
if (!si.Allocate())
return false;
my_memset(si.get(), 0, sizeof(MDRawSystemInfo));
dirent->stream_type = MD_SYSTEM_INFO_STREAM;
dirent->location = si.location();
WriteCPUInformation(si.get());
WriteOSInformation(si.get());
return true;
}
bool WriteDSODebugStream(MDRawDirectory* dirent) {
ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr) *>(dumper_->auxv()[AT_PHDR]);
char* base;
int phnum = dumper_->auxv()[AT_PHNUM];
if (!phnum || !phdr)
return false;
// Assume the program base is at the beginning of the same page as the PHDR
base = reinterpret_cast<char *>(reinterpret_cast<uintptr_t>(phdr) & ~0xfff);
// Search for the program PT_DYNAMIC segment
ElfW(Addr) dyn_addr = 0;
for (; phnum >= 0; phnum--, phdr++) {
ElfW(Phdr) ph;
if (!dumper_->CopyFromProcess(&ph, GetCrashThread(), phdr, sizeof(ph)))
return false;
// Adjust base address with the virtual address of the PT_LOAD segment
// corresponding to offset 0
if (ph.p_type == PT_LOAD && ph.p_offset == 0) {
base -= ph.p_vaddr;
}
if (ph.p_type == PT_DYNAMIC) {
dyn_addr = ph.p_vaddr;
}
}
if (!dyn_addr)
return false;
ElfW(Dyn) *dynamic = reinterpret_cast<ElfW(Dyn) *>(dyn_addr + base);
// The dynamic linker makes information available that helps gdb find all
// DSOs loaded into the program. If this information is indeed available,
// dump it to a MD_LINUX_DSO_DEBUG stream.
struct r_debug* r_debug = NULL;
uint32_t dynamic_length = 0;
for (int i = 0; ; ++i) {
ElfW(Dyn) dyn;
dynamic_length += sizeof(dyn);
if (!dumper_->CopyFromProcess(&dyn, GetCrashThread(), dynamic + i,
sizeof(dyn))) {
return false;
}
#ifdef __mips__
const int32_t debug_tag = DT_MIPS_RLD_MAP;
#else
const int32_t debug_tag = DT_DEBUG;
#endif
if (dyn.d_tag == debug_tag) {
r_debug = reinterpret_cast<struct r_debug*>(dyn.d_un.d_ptr);
continue;
} else if (dyn.d_tag == DT_NULL) {
break;
}
}
// The "r_map" field of that r_debug struct contains a linked list of all
// loaded DSOs.
// Our list of DSOs potentially is different from the ones in the crashing
// process. So, we have to be careful to never dereference pointers
// directly. Instead, we use CopyFromProcess() everywhere.
// See <link.h> for a more detailed discussion of the how the dynamic
// loader communicates with debuggers.
// Count the number of loaded DSOs
int dso_count = 0;
struct r_debug debug_entry;
if (!dumper_->CopyFromProcess(&debug_entry, GetCrashThread(), r_debug,
sizeof(debug_entry))) {
return false;
}
for (struct link_map* ptr = debug_entry.r_map; ptr; ) {
struct link_map map;
if (!dumper_->CopyFromProcess(&map, GetCrashThread(), ptr, sizeof(map)))
return false;
ptr = map.l_next;
dso_count++;
}
MDRVA linkmap_rva = minidump_writer_.kInvalidMDRVA;
if (dso_count > 0) {
// If we have at least one DSO, create an array of MDRawLinkMap
// entries in the minidump file.
TypedMDRVA<MDRawLinkMap> linkmap(&minidump_writer_);
if (!linkmap.AllocateArray(dso_count))
return false;
linkmap_rva = linkmap.location().rva;
int idx = 0;
// Iterate over DSOs and write their information to mini dump
for (struct link_map* ptr = debug_entry.r_map; ptr; ) {
struct link_map map;
if (!dumper_->CopyFromProcess(&map, GetCrashThread(), ptr, sizeof(map)))
return false;
ptr = map.l_next;
char filename[257] = { 0 };
if (map.l_name) {
dumper_->CopyFromProcess(filename, GetCrashThread(), map.l_name,
sizeof(filename) - 1);
}
MDLocationDescriptor location;
if (!minidump_writer_.WriteString(filename, 0, &location))
return false;
MDRawLinkMap entry;
entry.name = location.rva;
entry.addr = map.l_addr;
entry.ld = reinterpret_cast<uintptr_t>(map.l_ld);
linkmap.CopyIndex(idx++, &entry);
}
}
// Write MD_LINUX_DSO_DEBUG record
TypedMDRVA<MDRawDebug> debug(&minidump_writer_);
if (!debug.AllocateObjectAndArray(1, dynamic_length))
return false;
my_memset(debug.get(), 0, sizeof(MDRawDebug));
dirent->stream_type = MD_LINUX_DSO_DEBUG;
dirent->location = debug.location();
debug.get()->version = debug_entry.r_version;
debug.get()->map = linkmap_rva;
debug.get()->dso_count = dso_count;
debug.get()->brk = debug_entry.r_brk;
debug.get()->ldbase = debug_entry.r_ldbase;
debug.get()->dynamic = reinterpret_cast<uintptr_t>(dynamic);
wasteful_vector<char> dso_debug_data(dumper_->allocator(), dynamic_length);
// The passed-in size to the constructor (above) is only a hint.
// Must call .resize() to do actual initialization of the elements.
dso_debug_data.resize(dynamic_length);
dumper_->CopyFromProcess(&dso_debug_data[0], GetCrashThread(), dynamic,
dynamic_length);
debug.CopyIndexAfterObject(0, &dso_debug_data[0], dynamic_length);
return true;
}
void set_minidump_size_limit(off_t limit) { minidump_size_limit_ = limit; }
private:
void* Alloc(unsigned bytes) {
return dumper_->allocator()->Alloc(bytes);
}
pid_t GetCrashThread() const {
return dumper_->crash_thread();
}
void NullifyDirectoryEntry(MDRawDirectory* dirent) {
dirent->stream_type = 0;
dirent->location.data_size = 0;
dirent->location.rva = 0;
}
#if defined(__i386__) || defined(__x86_64__) || defined(__mips__)
bool WriteCPUInformation(MDRawSystemInfo* sys_info) {
char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0};
static const char vendor_id_name[] = "vendor_id";
struct CpuInfoEntry {
const char* info_name;
int value;
bool found;
} cpu_info_table[] = {
{ "processor", -1, false },
#if defined(__i386__) || defined(__x86_64__)
{ "model", 0, false },
{ "stepping", 0, false },
{ "cpu family", 0, false },
#endif
};
// processor_architecture should always be set, do this first
sys_info->processor_architecture =
#if defined(__mips__)
# if _MIPS_SIM == _ABIO32
MD_CPU_ARCHITECTURE_MIPS;
# elif _MIPS_SIM == _ABI64
MD_CPU_ARCHITECTURE_MIPS64;
# else
# error "This mips ABI is currently not supported (n32)"
#endif
#elif defined(__i386__)
MD_CPU_ARCHITECTURE_X86;
#else
MD_CPU_ARCHITECTURE_AMD64;
#endif
const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
if (fd < 0)
return false;
{
PageAllocator allocator;
ProcCpuInfoReader* const reader = new(allocator) ProcCpuInfoReader(fd);
const char* field;
while (reader->GetNextField(&field)) {
bool is_first_entry = true;
for (CpuInfoEntry& entry : cpu_info_table) {
if (!is_first_entry && entry.found) {
// except for the 'processor' field, ignore repeated values.
continue;
}
is_first_entry = false;
if (!my_strcmp(field, entry.info_name)) {
size_t value_len;
const char* value = reader->GetValueAndLen(&value_len);
if (value_len == 0)
continue;
uintptr_t val;
if (my_read_decimal_ptr(&val, value) == value)
continue;
entry.value = static_cast<int>(val);
entry.found = true;
}
}
// special case for vendor_id
if (!my_strcmp(field, vendor_id_name)) {
size_t value_len;
const char* value = reader->GetValueAndLen(&value_len);
if (value_len > 0)
my_strlcpy(vendor_id, value, sizeof(vendor_id));
}
}
sys_close(fd);
}
// make sure we got everything we wanted
for (const CpuInfoEntry& entry : cpu_info_table) {
if (!entry.found) {
return false;
}
}
// cpu_info_table[0] holds the last cpu id listed in /proc/cpuinfo,
// assuming this is the highest id, change it to the number of CPUs
// by adding one.
cpu_info_table[0].value++;
sys_info->number_of_processors = cpu_info_table[0].value;
#if defined(__i386__) || defined(__x86_64__)
sys_info->processor_level = cpu_info_table[3].value;
sys_info->processor_revision = cpu_info_table[1].value << 8 |
cpu_info_table[2].value;
#endif
if (vendor_id[0] != '\0') {
my_memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
sizeof(sys_info->cpu.x86_cpu_info.vendor_id));
}
return true;
}
#elif defined(__arm__) || defined(__aarch64__)
bool WriteCPUInformation(MDRawSystemInfo* sys_info) {
// The CPUID value is broken up in several entries in /proc/cpuinfo.
// This table is used to rebuild it from the entries.
const struct CpuIdEntry {
const char* field;
char format;
char bit_lshift;
char bit_length;
} cpu_id_entries[] = {
{ "CPU implementer", 'x', 24, 8 },
{ "CPU variant", 'x', 20, 4 },
{ "CPU part", 'x', 4, 12 },
{ "CPU revision", 'd', 0, 4 },
};
// The ELF hwcaps are listed in the "Features" entry as textual tags.
// This table is used to rebuild them.
const struct CpuFeaturesEntry {
const char* tag;
uint32_t hwcaps;
} cpu_features_entries[] = {
#if defined(__arm__)
{ "swp", MD_CPU_ARM_ELF_HWCAP_SWP },
{ "half", MD_CPU_ARM_ELF_HWCAP_HALF },
{ "thumb", MD_CPU_ARM_ELF_HWCAP_THUMB },
{ "26bit", MD_CPU_ARM_ELF_HWCAP_26BIT },
{ "fastmult", MD_CPU_ARM_ELF_HWCAP_FAST_MULT },
{ "fpa", MD_CPU_ARM_ELF_HWCAP_FPA },
{ "vfp", MD_CPU_ARM_ELF_HWCAP_VFP },
{ "edsp", MD_CPU_ARM_ELF_HWCAP_EDSP },
{ "java", MD_CPU_ARM_ELF_HWCAP_JAVA },
{ "iwmmxt", MD_CPU_ARM_ELF_HWCAP_IWMMXT },
{ "crunch", MD_CPU_ARM_ELF_HWCAP_CRUNCH },
{ "thumbee", MD_CPU_ARM_ELF_HWCAP_THUMBEE },
{ "neon", MD_CPU_ARM_ELF_HWCAP_NEON },
{ "vfpv3", MD_CPU_ARM_ELF_HWCAP_VFPv3 },
{ "vfpv3d16", MD_CPU_ARM_ELF_HWCAP_VFPv3D16 },
{ "tls", MD_CPU_ARM_ELF_HWCAP_TLS },
{ "vfpv4", MD_CPU_ARM_ELF_HWCAP_VFPv4 },
{ "idiva", MD_CPU_ARM_ELF_HWCAP_IDIVA },
{ "idivt", MD_CPU_ARM_ELF_HWCAP_IDIVT },
{ "idiv", MD_CPU_ARM_ELF_HWCAP_IDIVA | MD_CPU_ARM_ELF_HWCAP_IDIVT },
#elif defined(__aarch64__)
// No hwcaps on aarch64.
#endif
};
// processor_architecture should always be set, do this first
sys_info->processor_architecture =
#if defined(__aarch64__)
MD_CPU_ARCHITECTURE_ARM64;
#else
MD_CPU_ARCHITECTURE_ARM;
#endif
// /proc/cpuinfo is not readable under various sandboxed environments
// (e.g. Android services with the android:isolatedProcess attribute)
// prepare for this by setting default values now, which will be
// returned when this happens.
//
// Note: Bogus values are used to distinguish between failures (to
// read /sys and /proc files) and really badly configured kernels.
sys_info->number_of_processors = 0;
sys_info->processor_level = 1U; // There is no ARMv1
sys_info->processor_revision = 42;
sys_info->cpu.arm_cpu_info.cpuid = 0;
sys_info->cpu.arm_cpu_info.elf_hwcaps = 0;
// Counting the number of CPUs involves parsing two sysfs files,
// because the content of /proc/cpuinfo will only mirror the number
// of 'online' cores, and thus will vary with time.
// See http://www.kernel.org/doc/Documentation/cputopology.txt
{
CpuSet cpus_present;
CpuSet cpus_possible;
int fd = sys_open("/sys/devices/system/cpu/present", O_RDONLY, 0);
if (fd >= 0) {
cpus_present.ParseSysFile(fd);
sys_close(fd);
fd = sys_open("/sys/devices/system/cpu/possible", O_RDONLY, 0);
if (fd >= 0) {
cpus_possible.ParseSysFile(fd);
sys_close(fd);
cpus_present.IntersectWith(cpus_possible);
int cpu_count = cpus_present.GetCount();
if (cpu_count > 255)
cpu_count = 255;
sys_info->number_of_processors = static_cast<uint8_t>(cpu_count);
}
}
}
// Parse /proc/cpuinfo to reconstruct the CPUID value, as well
// as the ELF hwcaps field. For the latter, it would be easier to
// read /proc/self/auxv but unfortunately, this file is not always
// readable from regular Android applications on later versions
// (>= 4.1) of the Android platform.
const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
if (fd < 0) {
// Do not return false here to allow the minidump generation
// to happen properly.
return true;
}
{
PageAllocator allocator;
ProcCpuInfoReader* const reader =
new(allocator) ProcCpuInfoReader(fd);
const char* field;
while (reader->GetNextField(&field)) {
for (const CpuIdEntry& entry : cpu_id_entries) {
if (my_strcmp(entry.field, field) != 0)
continue;
uintptr_t result = 0;
const char* value = reader->GetValue();
const char* p = value;
if (value[0] == '0' && value[1] == 'x') {
p = my_read_hex_ptr(&result, value+2);
} else if (entry.format == 'x') {
p = my_read_hex_ptr(&result, value);
} else {
p = my_read_decimal_ptr(&result, value);
}
if (p == value)
continue;
result &= (1U << entry.bit_length)-1;
result <<= entry.bit_lshift;
sys_info->cpu.arm_cpu_info.cpuid |=
static_cast<uint32_t>(result);
}
#if defined(__arm__)
// Get the architecture version from the "Processor" field.
// Note that it is also available in the "CPU architecture" field,
// however, some existing kernels are misconfigured and will report
// invalid values here (e.g. 6, while the CPU is ARMv7-A based).
// The "Processor" field doesn't have this issue.
if (!my_strcmp(field, "Processor")) {
size_t value_len;
const char* value = reader->GetValueAndLen(&value_len);
// Expected format: <text> (v<level><endian>)
// Where <text> is some text like "ARMv7 Processor rev 2"
// and <level> is a decimal corresponding to the ARM
// architecture number. <endian> is either 'l' or 'b'
// and corresponds to the endianess, it is ignored here.
while (value_len > 0 && my_isspace(value[value_len-1]))
value_len--;
size_t nn = value_len;
while (nn > 0 && value[nn-1] != '(')
nn--;
if (nn > 0 && value[nn] == 'v') {
uintptr_t arch_level = 5;
my_read_decimal_ptr(&arch_level, value + nn + 1);
sys_info->processor_level = static_cast<uint16_t>(arch_level);
}
}
#elif defined(__aarch64__)
// The aarch64 architecture does not provide the architecture level
// in the Processor field, so we instead check the "CPU architecture"
// field.
if (!my_strcmp(field, "CPU architecture")) {
uintptr_t arch_level = 0;
const char* value = reader->GetValue();
const char* p = value;
p = my_read_decimal_ptr(&arch_level, value);
if (p == value)
continue;
sys_info->processor_level = static_cast<uint16_t>(arch_level);
}
#endif
// Rebuild the ELF hwcaps from the 'Features' field.
if (!my_strcmp(field, "Features")) {
size_t value_len;
const char* value = reader->GetValueAndLen(&value_len);
// Parse each space-separated tag.
while (value_len > 0) {
const char* tag = value;
size_t tag_len = value_len;
const char* p = my_strchr(tag, ' ');
if (p) {
tag_len = static_cast<size_t>(p - tag);
value += tag_len + 1;
value_len -= tag_len + 1;
} else {
tag_len = strlen(tag);
value_len = 0;
}
for (const CpuFeaturesEntry& entry : cpu_features_entries) {
if (tag_len == strlen(entry.tag) &&
!memcmp(tag, entry.tag, tag_len)) {
sys_info->cpu.arm_cpu_info.elf_hwcaps |= entry.hwcaps;
break;
}
}
}
}
}
sys_close(fd);
}
return true;
}
#else
# error "Unsupported CPU"
#endif
bool WriteFile(MDLocationDescriptor* result, const char* filename) {
const int fd = sys_open(filename, O_RDONLY, 0);
if (fd < 0)
return false;
// We can't stat the files because several of the files that we want to
// read are kernel seqfiles, which always have a length of zero. So we have
// to read as much as we can into a buffer.
static const unsigned kBufSize = 1024 - 2*sizeof(void*);
struct Buffers {
Buffers* next;
size_t len;
uint8_t data[kBufSize];
} *buffers = reinterpret_cast<Buffers*>(Alloc(sizeof(Buffers)));
buffers->next = NULL;
buffers->len = 0;
size_t total = 0;
for (Buffers* bufptr = buffers;;) {
ssize_t r;
do {
r = sys_read(fd, &bufptr->data[bufptr->len], kBufSize - bufptr->len);
} while (r == -1 && errno == EINTR);
if (r < 1)
break;
total += r;
bufptr->len += r;
if (bufptr->len == kBufSize) {
bufptr->next = reinterpret_cast<Buffers*>(Alloc(sizeof(Buffers)));
bufptr = bufptr->next;
bufptr->next = NULL;
bufptr->len = 0;
}
}
sys_close(fd);
if (!total)
return false;
UntypedMDRVA memory(&minidump_writer_);
if (!memory.Allocate(total))
return false;
for (MDRVA pos = memory.position(); buffers; buffers = buffers->next) {
// Check for special case of a zero-length buffer. This should only
// occur if a file's size happens to be a multiple of the buffer's
// size, in which case the final sys_read() will have resulted in
// zero bytes being read after the final buffer was just allocated.
if (buffers->len == 0) {
// This can only occur with final buffer.
assert(buffers->next == NULL);
continue;
}
memory.Copy(pos, &buffers->data, buffers->len);
pos += buffers->len;
}
*result = memory.location();
return true;
}
bool WriteOSInformation(MDRawSystemInfo* sys_info) {
#if defined(__ANDROID__)
sys_info->platform_id = MD_OS_ANDROID;
#else
sys_info->platform_id = MD_OS_LINUX;
#endif
struct utsname uts;
if (uname(&uts))
return false;
static const size_t buf_len = 512;
char buf[buf_len] = {0};
size_t space_left = buf_len - 1;
const char* info_table[] = {
uts.sysname,
uts.release,
uts.version,
uts.machine,
NULL
};
bool first_item = true;
for (const char** cur_info = info_table; *cur_info; cur_info++) {
static const char separator[] = " ";
size_t separator_len = sizeof(separator) - 1;
size_t info_len = my_strlen(*cur_info);
if (info_len == 0)
continue;
if (space_left < info_len + (first_item ? 0 : separator_len))
break;
if (!first_item) {
my_strlcat(buf, separator, sizeof(buf));
space_left -= separator_len;
}
first_item = false;
my_strlcat(buf, *cur_info, sizeof(buf));
space_left -= info_len;
}
MDLocationDescriptor location;
if (!minidump_writer_.WriteString(buf, 0, &location))
return false;
sys_info->csd_version_rva = location.rva;
return true;
}
bool WriteProcFile(MDLocationDescriptor* result, pid_t pid,
const char* filename) {
char buf[NAME_MAX];
if (!dumper_->BuildProcPath(buf, pid, filename))
return false;
return WriteFile(result, buf);
}
// Only one of the 2 member variables below should be set to a valid value.
const int fd_; // File descriptor where the minidum should be written.
const char* path_; // Path to the file where the minidum should be written.
const struct ucontext* const ucontext_; // also from the signal handler
#if !defined(__ARM_EABI__) && !defined(__mips__)
const google_breakpad::fpstate_t* const float_state_; // ditto
#endif
LinuxDumper* dumper_;
MinidumpFileWriter minidump_writer_;
off_t minidump_size_limit_;
MDLocationDescriptor crashing_thread_context_;
// Blocks of memory written to the dump. These are all currently
// written while writing the thread list stream, but saved here
// so a memory list stream can be written afterwards.
wasteful_vector<MDMemoryDescriptor> memory_blocks_;
// Additional information about some mappings provided by the caller.
const MappingList& mapping_list_;
// Additional memory regions to be included in the dump,
// provided by the caller.
const AppMemoryList& app_memory_list_;
};
bool WriteMinidumpImpl(const char* minidump_path,
int minidump_fd,
off_t minidump_size_limit,
pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings,
const AppMemoryList& appmem) {
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);
}
MinidumpWriter writer(minidump_path, minidump_fd, context, mappings,
appmem, &dumper);
// Set desired limit for file size of minidump (-1 means no limit).
writer.set_minidump_size_limit(minidump_size_limit);
if (!writer.Init())
return false;
return writer.Dump();
}
} // namespace
namespace google_breakpad {
bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
const void* blob, size_t blob_size) {
return WriteMinidumpImpl(minidump_path, -1, -1,
crashing_process, blob, blob_size,
MappingList(), AppMemoryList());
}
bool WriteMinidump(int minidump_fd, pid_t crashing_process,
const void* blob, size_t blob_size) {
return WriteMinidumpImpl(NULL, minidump_fd, -1,
crashing_process, blob, blob_size,
MappingList(), AppMemoryList());
}
bool WriteMinidump(const char* minidump_path, pid_t process,
pid_t process_blamed_thread) {
LinuxPtraceDumper dumper(process);
// MinidumpWriter will set crash address
dumper.set_crash_signal(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED);
dumper.set_crash_thread(process_blamed_thread);
MinidumpWriter writer(minidump_path, -1, NULL, MappingList(),
AppMemoryList(), &dumper);
if (!writer.Init())
return false;
return writer.Dump();
}
bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings,
const AppMemoryList& appmem) {
return WriteMinidumpImpl(minidump_path, -1, -1, crashing_process,
blob, blob_size,
mappings, appmem);
}
bool WriteMinidump(int minidump_fd, pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings,
const AppMemoryList& appmem) {
return WriteMinidumpImpl(NULL, minidump_fd, -1, crashing_process,
blob, blob_size,
mappings, appmem);
}
bool WriteMinidump(const char* minidump_path, off_t minidump_size_limit,
pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings,
const AppMemoryList& appmem) {
return WriteMinidumpImpl(minidump_path, -1, minidump_size_limit,
crashing_process, blob, blob_size,
mappings, appmem);
}
bool WriteMinidump(int minidump_fd, off_t minidump_size_limit,
pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings,
const AppMemoryList& appmem) {
return WriteMinidumpImpl(NULL, minidump_fd, minidump_size_limit,
crashing_process, blob, blob_size,
mappings, appmem);
}
bool WriteMinidump(const char* filename,
const MappingList& mappings,
const AppMemoryList& appmem,
LinuxDumper* dumper) {
MinidumpWriter writer(filename, -1, NULL, mappings, appmem, dumper);
if (!writer.Init())
return false;
return writer.Dump();
}
} // namespace google_breakpad