// 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 <sys/utsname.h> #include "client/linux/dump_writer_common/seccomp_unwinder.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/log/log.h" #include "client/linux/minidump_writer/linux_ptrace_dumper.h" #include "common/linux/linux_libc_support.h" namespace { using google_breakpad::ExceptionHandler; using google_breakpad::LinuxDumper; using google_breakpad::LinuxPtraceDumper; using google_breakpad::MappingInfo; using google_breakpad::MappingList; using google_breakpad::RawContextCPU; using google_breakpad::SeccompUnwinder; using google_breakpad::ThreadInfo; using google_breakpad::UContextReader; const size_t kLineBufferSize = 2048; class MicrodumpWriter { public: MicrodumpWriter(const ExceptionHandler::CrashContext* context, const MappingList& mappings, 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), 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(); } bool Dump() { bool success; LogLine("-----BEGIN BREAKPAD MICRODUMP-----"); success = DumpOSInformation(); if (success) 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) { logger::write(msg, my_strlen(msg)); #if !defined(__ANDROID__) logger::write("\n", 1); // Android logger appends the \n. Linux's doesn't. #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); } bool DumpOSInformation() { struct utsname uts; if (uname(&uts)) return false; 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 // We cannot depend on uts.machine. On multiarch devices it always returns the // primary arch, not the one that match the executable being run. #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__) const char kArch[] = "mips"; #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(" "); LogAppend(uts.machine); LogAppend(" "); LogAppend(uts.release); LogAppend(" "); LogAppend(uts.version); LogCommitLine(); return true; } 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 if (stack_copy) SeccompUnwinder::PopSeccompStackFrame(&cpu, thread, stack_copy); 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) { MDGUID module_identifier; if (identifier) { // GUID was provided by caller. my_memcpy(&module_identifier, identifier, sizeof(MDGUID)); } else { dumper_->ElfFileIdentifierForMapping( mapping, member, mapping_id, reinterpret_cast<uint8_t*>(&module_identifier)); } char file_name[NAME_MAX]; char file_path[NAME_MAX]; LinuxDumper::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(); } // 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 struct ucontext* const ucontext_; #if !defined(__ARM_EABI__) && !defined(__mips__) const google_breakpad::fpstate_t* const float_state_; #endif LinuxDumper* dumper_; const MappingList& mapping_list_; char* log_line_; }; } // namespace namespace google_breakpad { bool WriteMicrodump(pid_t crashing_process, const void* blob, size_t blob_size, const MappingList& mappings) { 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, &dumper); if (!writer.Init()) return false; return writer.Dump(); } } // namespace google_breakpad