// Copyright (c) 2012, 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.

// linux_core_dumper.cc: Implement google_breakpad::LinuxCoreDumper.
// See linux_core_dumper.h for details.

#include "linux/minidump_writer/linux_core_dumper.h"

#include <asm/ptrace.h>
#include <assert.h>
#include <elf.h>
#include <stdio.h>
#include <string.h>
#include <sys/procfs.h>
#if defined(__mips__) && defined(__ANDROID__)
// To get register definitions.
#include <asm/reg.h>
#endif

#include "common/linux/elf_gnu_compat.h"
#include "common/linux/linux_libc_support.h"

namespace google_breakpad {

LinuxCoreDumper::LinuxCoreDumper(pid_t pid,
                                 const char* core_path,
                                 const char* procfs_path,
                                 const char* root_prefix)
    : LinuxDumper(pid, root_prefix),
      core_path_(core_path),
      procfs_path_(procfs_path),
      thread_infos_(&allocator_, 8) {
  assert(core_path_);
}

bool LinuxCoreDumper::BuildProcPath(char* path, pid_t pid,
                                    const char* node) const {
  if (!path || !node)
    return false;

  size_t node_len = my_strlen(node);
  if (node_len == 0)
    return false;

  size_t procfs_path_len = my_strlen(procfs_path_);
  size_t total_length = procfs_path_len + 1 + node_len;
  if (total_length >= NAME_MAX)
    return false;

  memcpy(path, procfs_path_, procfs_path_len);
  path[procfs_path_len] = '/';
  memcpy(path + procfs_path_len + 1, node, node_len);
  path[total_length] = '\0';
  return true;
}

bool LinuxCoreDumper::CopyFromProcess(void* dest, pid_t child,
                                      const void* src, size_t length) {
  ElfCoreDump::Addr virtual_address = reinterpret_cast<ElfCoreDump::Addr>(src);
  // TODO(benchan): Investigate whether the data to be copied could span
  // across multiple segments in the core dump file. ElfCoreDump::CopyData
  // and this method do not handle that case yet.
  if (!core_.CopyData(dest, virtual_address, length)) {
    // If the data segment is not found in the core dump, fill the result
    // with marker characters.
    memset(dest, 0xab, length);
    return false;
  }
  return true;
}

bool LinuxCoreDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) {
  if (index >= thread_infos_.size())
    return false;

  *info = thread_infos_[index];
  const uint8_t* stack_pointer;
#if defined(__i386)
  memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
#elif defined(__x86_64)
  memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
#elif defined(__ARM_EABI__)
  memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
#elif defined(__aarch64__)
  memcpy(&stack_pointer, &info->regs.sp, sizeof(info->regs.sp));
#elif defined(__mips__)
  stack_pointer =
      reinterpret_cast<uint8_t*>(info->mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]);
#else
#error "This code hasn't been ported to your platform yet."
#endif
  info->stack_pointer = reinterpret_cast<uintptr_t>(stack_pointer);
  return true;
}

bool LinuxCoreDumper::GetThreadNameByIndex(size_t index, char* name,
                                           size_t size) {
  // Not implemented
  return false;
}

bool LinuxCoreDumper::IsPostMortem() const {
  return true;
}

bool LinuxCoreDumper::ThreadsSuspend() {
  return true;
}

bool LinuxCoreDumper::ThreadsResume() {
  return true;
}

bool LinuxCoreDumper::EnumerateThreads() {
  if (!mapped_core_file_.Map(core_path_, 0)) {
    fprintf(stderr, "Could not map core dump file into memory\n");
    return false;
  }

  core_.SetContent(mapped_core_file_.content());
  if (!core_.IsValid()) {
    fprintf(stderr, "Invalid core dump file\n");
    return false;
  }

  ElfCoreDump::Note note = core_.GetFirstNote();
  if (!note.IsValid()) {
    fprintf(stderr, "PT_NOTE section not found\n");
    return false;
  }

  bool first_thread = true;
  do {
    ElfCoreDump::Word type = note.GetType();
    MemoryRange name = note.GetName();
    MemoryRange description = note.GetDescription();

    if (type == 0 || name.IsEmpty() || description.IsEmpty()) {
      fprintf(stderr, "Could not found a valid PT_NOTE.\n");
      return false;
    }

    // Based on write_note_info() in linux/kernel/fs/binfmt_elf.c, notes are
    // ordered as follows (NT_PRXFPREG and NT_386_TLS are i386 specific):
    //   Thread           Name          Type
    //   -------------------------------------------------------------------
    //   1st thread       CORE          NT_PRSTATUS
    //   process-wide     CORE          NT_PRPSINFO
    //   process-wide     CORE          NT_SIGINFO
    //   process-wide     CORE          NT_AUXV
    //   1st thread       CORE          NT_FPREGSET
    //   1st thread       LINUX         NT_PRXFPREG
    //   1st thread       LINUX         NT_386_TLS
    //
    //   2nd thread       CORE          NT_PRSTATUS
    //   2nd thread       CORE          NT_FPREGSET
    //   2nd thread       LINUX         NT_PRXFPREG
    //   2nd thread       LINUX         NT_386_TLS
    //
    //   3rd thread       CORE          NT_PRSTATUS
    //   3rd thread       CORE          NT_FPREGSET
    //   3rd thread       LINUX         NT_PRXFPREG
    //   3rd thread       LINUX         NT_386_TLS
    //
    // The following code only works if notes are ordered as expected.
    switch (type) {
      case NT_PRSTATUS: {
        if (description.length() != sizeof(elf_prstatus)) {
          fprintf(stderr, "Found NT_PRSTATUS descriptor of unexpected size\n");
          return false;
        }

        const elf_prstatus* status =
            reinterpret_cast<const elf_prstatus*>(description.data());
        pid_t pid = status->pr_pid;
        ThreadInfo info;
        memset(&info, 0, sizeof(ThreadInfo));
        info.tgid = status->pr_pgrp;
        info.ppid = status->pr_ppid;
#if defined(__mips__)
#if defined(__ANDROID__)
        for (int i = EF_R0; i <= EF_R31; i++)
          info.mcontext.gregs[i - EF_R0] = status->pr_reg[i];
#else  // __ANDROID__
        for (int i = EF_REG0; i <= EF_REG31; i++)
          info.mcontext.gregs[i - EF_REG0] = status->pr_reg[i];
#endif  // __ANDROID__
        info.mcontext.mdlo = status->pr_reg[EF_LO];
        info.mcontext.mdhi = status->pr_reg[EF_HI];
        info.mcontext.pc = status->pr_reg[EF_CP0_EPC];
#else  // __mips__
        memcpy(&info.regs, status->pr_reg, sizeof(info.regs));
#endif  // __mips__
        if (first_thread) {
          crash_thread_ = pid;
          crash_signal_ = status->pr_info.si_signo;
          crash_signal_code_ = status->pr_info.si_code;
        }
        first_thread = false;
        threads_.push_back(pid);
        thread_infos_.push_back(info);
        break;
      }
      case NT_SIGINFO: {
        if (description.length() != sizeof(siginfo_t)) {
          fprintf(stderr, "Found NT_SIGINFO descriptor of unexpected size\n");
          return false;
        }

        const siginfo_t* info =
            reinterpret_cast<const siginfo_t*>(description.data());

        // Set crash_address when si_addr is valid for the signal.
        switch (info->si_signo) {
          case MD_EXCEPTION_CODE_LIN_SIGBUS:
          case MD_EXCEPTION_CODE_LIN_SIGFPE:
          case MD_EXCEPTION_CODE_LIN_SIGILL:
          case MD_EXCEPTION_CODE_LIN_SIGSEGV:
          case MD_EXCEPTION_CODE_LIN_SIGSYS:
          case MD_EXCEPTION_CODE_LIN_SIGTRAP:
            crash_address_ = reinterpret_cast<uintptr_t>(info->si_addr);
            break;
        }

        // Set crash_exception_info for common signals.  Since exception info is
        // unsigned, but some of these fields might be signed, we always cast.
        switch (info->si_signo) {
          case MD_EXCEPTION_CODE_LIN_SIGKILL:
            set_crash_exception_info({
              static_cast<uint64_t>(info->si_pid),
              static_cast<uint64_t>(info->si_uid),
            });
            break;
          case MD_EXCEPTION_CODE_LIN_SIGSYS:
#ifdef si_syscall
            set_crash_exception_info({
              static_cast<uint64_t>(info->si_syscall),
              static_cast<uint64_t>(info->si_arch),
            });
#endif
            break;
        }
        break;
      }
#if defined(__i386) || defined(__x86_64)
      case NT_FPREGSET: {
        if (thread_infos_.empty())
          return false;

        ThreadInfo* info = &thread_infos_.back();
        if (description.length() != sizeof(info->fpregs)) {
          fprintf(stderr, "Found NT_FPREGSET descriptor of unexpected size\n");
          return false;
        }

        memcpy(&info->fpregs, description.data(), sizeof(info->fpregs));
        break;
      }
#endif
#if defined(__i386)
      case NT_PRXFPREG: {
        if (thread_infos_.empty())
          return false;

        ThreadInfo* info = &thread_infos_.back();
        if (description.length() != sizeof(info->fpxregs)) {
          fprintf(stderr, "Found NT_PRXFPREG descriptor of unexpected size\n");
          return false;
        }

        memcpy(&info->fpxregs, description.data(), sizeof(info->fpxregs));
        break;
      }
#endif
    }
    note = note.GetNextNote();
  } while (note.IsValid());

  return true;
}

}  // namespace google_breakpad
