// 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 #include #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_allocator.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 Dst saturated_cast(Src src) { if (src >= std::numeric_limits::max()) return std::numeric_limits::max(); if (src <= std::numeric_limits::min()) return std::numeric_limits::min(); return static_cast(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& 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::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, bool skip_dump_if_principal_mapping_not_referenced, uintptr_t address_within_principal_mapping, bool sanitize_stack, 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), skip_dump_if_principal_mapping_not_referenced_( skip_dump_if_principal_mapping_not_referenced), address_within_principal_mapping_(address_within_principal_mapping), sanitize_stack_(sanitize_stack), microdump_extra_info_(microdump_extra_info), log_line_(NULL), stack_copy_(NULL), stack_len_(0), stack_lower_bound_(0), stack_pointer_(0) { log_line_ = reinterpret_cast(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(); } void Dump() { CaptureResult stack_capture_result = CaptureCrashingThreadStack(-1); if (stack_capture_result == CAPTURE_UNINTERESTING) { LogLine("Microdump skipped (uninteresting)"); return; } LogLine("-----BEGIN BREAKPAD MICRODUMP-----"); DumpProductInformation(); DumpOSInformation(); DumpProcessType(); DumpCrashReason(); DumpGPUInformation(); #if !defined(__LP64__) DumpFreeSpace(); #endif if (stack_capture_result == CAPTURE_OK) DumpThreadStack(); DumpCPUState(); DumpMappings(); LogLine("-----END BREAKPAD MICRODUMP-----"); } private: enum CaptureResult { CAPTURE_OK, CAPTURE_FAILED, CAPTURE_UNINTERESTING }; // 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(str)); } // Stages the hex repr. of the given int type in the current line buffer. template 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(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(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_); log_line_[0] = 0; } CaptureResult CaptureCrashingThreadStack(int max_stack_len) { stack_pointer_ = UContextReader::GetStackPointer(ucontext_); if (!dumper_->GetStackInfo(reinterpret_cast(&stack_lower_bound_), &stack_len_, stack_pointer_)) { return CAPTURE_FAILED; } if (max_stack_len >= 0 && stack_len_ > static_cast(max_stack_len)) { stack_len_ = max_stack_len; } stack_copy_ = reinterpret_cast(Alloc(stack_len_)); dumper_->CopyFromProcess(stack_copy_, dumper_->crash_thread(), reinterpret_cast(stack_lower_bound_), stack_len_); if (!skip_dump_if_principal_mapping_not_referenced_) return CAPTURE_OK; const MappingInfo* principal_mapping = dumper_->FindMappingNoBias(address_within_principal_mapping_); if (!principal_mapping) return CAPTURE_UNINTERESTING; uintptr_t low_addr = principal_mapping->system_mapping_info.start_addr; uintptr_t high_addr = principal_mapping->system_mapping_info.end_addr; uintptr_t pc = UContextReader::GetInstructionPointer(ucontext_); if (low_addr <= pc && pc <= high_addr) return CAPTURE_OK; if (dumper_->StackHasPointerToMapping(stack_copy_, stack_len_, stack_pointer_ - stack_lower_bound_, *principal_mapping)) { return CAPTURE_OK; } return CAPTURE_UNINTERESTING; } 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 DumpCrashReason() { LogAppend("R "); LogAppend(dumper_->crash_signal()); LogAppend(" "); LogAppend(dumper_->GetCrashSignalString()); LogAppend(" "); LogAppend(dumper_->crash_address()); LogCommitLine(); } void DumpOSInformation() { const uint8_t n_cpus = static_cast(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(); } void DumpThreadStack() { if (sanitize_stack_) { dumper_->SanitizeStackCopy(stack_copy_, stack_len_, stack_pointer_, stack_pointer_ - stack_lower_bound_); } LogAppend("S 0 "); LogAppend(stack_pointer_); LogAppend(" "); LogAppend(stack_lower_bound_); LogAppend(" "); LogAppend(stack_len_); LogCommitLine(); 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(stack_lower_bound_ + stack_off); LogAppend(" "); LogAppend(stack_copy_ + stack_off, std::min(STACK_DUMP_CHUNK_SIZE, stack_len_ - stack_off)); LogCommitLine(); } } void DumpCPUState() { 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 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 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(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 MappingInfo* stack_mapping = nullptr; ThreadInfo info; if (dumper_->GetThreadInfoByIndex(dumper_->GetMainThreadIndex(), &info)) { stack_mapping = dumper_->FindMappingNoBias(info.stack_pointer); } const google_breakpad::wasteful_vector& 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; } if (mappings[curr] == stack_mapping) { // Because we can't determine the top of userspace mappable // memory we treat the start of the process stack as the top // of the allocatable address space. Once we reach // |stack_mapping| we are done scanning for free space regions. break; } size_t next = NextOrderedMapping(mappings, curr); if (next == std::numeric_limits::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(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(i)); LogAppend(":"); LogAppend(saturated_cast(hole_histogram[i])); } LogCommitLine(); } #endif // Write information about the mappings in effect. void 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); } } 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_; bool skip_dump_if_principal_mapping_not_referenced_; uintptr_t address_within_principal_mapping_; bool sanitize_stack_; const MicrodumpExtraInfo microdump_extra_info_; char* log_line_; // The local copy of crashed process stack memory, beginning at // |stack_lower_bound_|. uint8_t* stack_copy_; // The length of crashed process stack copy. size_t stack_len_; // The address of the page containing the stack pointer in the // crashed process. |stack_lower_bound_| <= |stack_pointer_| uintptr_t stack_lower_bound_; // The stack pointer of the crashed thread. uintptr_t stack_pointer_; }; } // namespace namespace google_breakpad { bool WriteMicrodump(pid_t crashing_process, const void* blob, size_t blob_size, const MappingList& mappings, bool skip_dump_if_principal_mapping_not_referenced, uintptr_t address_within_principal_mapping, bool sanitize_stack, 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(blob); dumper.SetCrashInfoFromSigInfo(context->siginfo); dumper.set_crash_thread(context->tid); } MicrodumpWriter writer(context, mappings, skip_dump_if_principal_mapping_not_referenced, address_within_principal_mapping, sanitize_stack, microdump_extra_info, &dumper); if (!writer.Init()) return false; writer.Dump(); return true; } } // namespace google_breakpad