text: // 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 <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/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"
  
  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;
  
  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();
      DumpGPUInformation();
      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 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();
    }
  
    // 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_;
    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
  