// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "snapshot/linux/exception_snapshot_linux.h"

#include <linux/posix_types.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <ucontext.h>
#include <unistd.h>

#include "base/bit_cast.h"
#include "base/cxx17_backports.h"
#include "base/strings/stringprintf.h"
#include "gtest/gtest.h"
#include "snapshot/cpu_architecture.h"
#include "snapshot/linux/process_reader_linux.h"
#include "snapshot/linux/signal_context.h"
#include "sys/syscall.h"
#include "test/errors.h"
#include "test/linux/fake_ptrace_connection.h"
#include "util/linux/address_types.h"
#include "util/misc/clock.h"
#include "util/misc/from_pointer_cast.h"
#include "util/posix/signals.h"
#include "util/synchronization/semaphore.h"

namespace crashpad {
namespace test {
namespace {

pid_t gettid() {
  return syscall(SYS_gettid);
}

#if defined(ARCH_CPU_X86)
struct FxsaveUContext {
  ucontext_t ucontext;
  CPUContextX86::Fxsave fxsave;
};
using NativeCPUContext = FxsaveUContext;

void InitializeContext(NativeCPUContext* context) {
  context->ucontext.uc_mcontext.gregs[REG_EAX] = 0xabcd1234;
  context->ucontext.uc_mcontext.fpregs = &context->ucontext.__fpregs_mem;
  // glibc and bionic use an unsigned long for status, but the kernel treats
  // status as two uint16_t, with the upper 16 bits called "magic" which, if set
  // to X86_FXSR_MAGIC, indicate that an fxsave follows.
  reinterpret_cast<uint16_t*>(&context->ucontext.__fpregs_mem.status)[1] =
      X86_FXSR_MAGIC;
  memset(&context->fxsave, 43, sizeof(context->fxsave));
}

void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) {
  EXPECT_EQ(actual.architecture, kCPUArchitectureX86);
  EXPECT_EQ(actual.x86->eax,
            bit_cast<uint32_t>(expected.ucontext.uc_mcontext.gregs[REG_EAX]));
  for (unsigned int byte_offset = 0; byte_offset < sizeof(actual.x86->fxsave);
       ++byte_offset) {
    SCOPED_TRACE(base::StringPrintf("byte offset = %u\n", byte_offset));
    EXPECT_EQ(reinterpret_cast<const char*>(&actual.x86->fxsave)[byte_offset],
              reinterpret_cast<const char*>(&expected.fxsave)[byte_offset]);
  }
}
#elif defined(ARCH_CPU_X86_64)
using NativeCPUContext = ucontext_t;

void InitializeContext(NativeCPUContext* context) {
  context->uc_mcontext.gregs[REG_RAX] = 0xabcd1234abcd1234;
  context->uc_mcontext.fpregs = &context->__fpregs_mem;
  memset(&context->__fpregs_mem, 44, sizeof(context->__fpregs_mem));
}

void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) {
  EXPECT_EQ(actual.architecture, kCPUArchitectureX86_64);
  EXPECT_EQ(actual.x86_64->rax,
            bit_cast<uint64_t>(expected.uc_mcontext.gregs[REG_RAX]));
  for (unsigned int byte_offset = 0;
       byte_offset < sizeof(actual.x86_64->fxsave);
       ++byte_offset) {
    SCOPED_TRACE(base::StringPrintf("byte offset = %u\n", byte_offset));
    EXPECT_EQ(
        reinterpret_cast<const char*>(&actual.x86_64->fxsave)[byte_offset],
        reinterpret_cast<const char*>(&expected.__fpregs_mem)[byte_offset]);
  }
}
#elif defined(ARCH_CPU_ARMEL)
// A native ucontext_t on ARM doesn't have enough regspace (yet) to hold all of
// the different possible coprocessor contexts at once. However, the ABI allows
// it and the native regspace may be expanded in the future. Append some extra
// space so this is testable now.
struct NativeCPUContext {
  ucontext_t ucontext;
  char extra[1024];
};

struct CrunchContext {
  uint32_t mvdx[16][2];
  uint32_t mvax[4][3];
  uint32_t dspsc[2];
};

struct IWMMXTContext {
  uint32_t save[38];
};

struct TestCoprocessorContext {
  struct {
    internal::CoprocessorContextHead head;
    CrunchContext context;
  } crunch;
  struct {
    internal::CoprocessorContextHead head;
    IWMMXTContext context;
  } iwmmxt;
  struct {
    internal::CoprocessorContextHead head;
    IWMMXTContext context;
  } dummy;
  struct {
    internal::CoprocessorContextHead head;
    internal::SignalVFPContext context;
  } vfp;
  internal::CoprocessorContextHead terminator;
};

void InitializeContext(NativeCPUContext* context) {
  memset(context, 'x', sizeof(*context));

  for (int index = 0; index < (&context->ucontext.uc_mcontext.fault_address -
                               &context->ucontext.uc_mcontext.arm_r0);
       ++index) {
    (&context->ucontext.uc_mcontext.arm_r0)[index] = index;
  }

  static_assert(
      sizeof(TestCoprocessorContext) <=
          sizeof(context->ucontext.uc_regspace) + sizeof(context->extra),
      "Insufficient context space");
  auto test_context =
      reinterpret_cast<TestCoprocessorContext*>(context->ucontext.uc_regspace);

  test_context->crunch.head.magic = CRUNCH_MAGIC;
  test_context->crunch.head.size = sizeof(test_context->crunch);
  memset(
      &test_context->crunch.context, 'c', sizeof(test_context->crunch.context));

  test_context->iwmmxt.head.magic = IWMMXT_MAGIC;
  test_context->iwmmxt.head.size = sizeof(test_context->iwmmxt);
  memset(
      &test_context->iwmmxt.context, 'i', sizeof(test_context->iwmmxt.context));

  test_context->dummy.head.magic = DUMMY_MAGIC;
  test_context->dummy.head.size = sizeof(test_context->dummy);
  memset(
      &test_context->dummy.context, 'd', sizeof(test_context->dummy.context));

  test_context->vfp.head.magic = VFP_MAGIC;
  test_context->vfp.head.size = sizeof(test_context->vfp);
  memset(&test_context->vfp.context, 'v', sizeof(test_context->vfp.context));
  for (size_t reg = 0; reg < base::size(test_context->vfp.context.vfp.fpregs);
       ++reg) {
    test_context->vfp.context.vfp.fpregs[reg] = reg;
  }
  test_context->vfp.context.vfp.fpscr = 42;

  test_context->terminator.magic = 0;
  test_context->terminator.size = 0;
}

void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) {
  EXPECT_EQ(actual.architecture, kCPUArchitectureARM);

  EXPECT_EQ(memcmp(actual.arm->regs,
                   &expected.ucontext.uc_mcontext.arm_r0,
                   sizeof(actual.arm->regs)),
            0);
  EXPECT_EQ(actual.arm->fp, expected.ucontext.uc_mcontext.arm_fp);
  EXPECT_EQ(actual.arm->ip, expected.ucontext.uc_mcontext.arm_ip);
  EXPECT_EQ(actual.arm->sp, expected.ucontext.uc_mcontext.arm_sp);
  EXPECT_EQ(actual.arm->lr, expected.ucontext.uc_mcontext.arm_lr);
  EXPECT_EQ(actual.arm->pc, expected.ucontext.uc_mcontext.arm_pc);
  EXPECT_EQ(actual.arm->cpsr, expected.ucontext.uc_mcontext.arm_cpsr);

  EXPECT_FALSE(actual.arm->have_fpa_regs);

  EXPECT_TRUE(actual.arm->have_vfp_regs);

  auto test_context = reinterpret_cast<const TestCoprocessorContext*>(
      expected.ucontext.uc_regspace);

  EXPECT_EQ(memcmp(actual.arm->vfp_regs.vfp,
                   &test_context->vfp.context.vfp,
                   sizeof(actual.arm->vfp_regs.vfp)),
            0);
}
#elif defined(ARCH_CPU_ARM64)
using NativeCPUContext = ucontext_t;

struct TestCoprocessorContext {
  esr_context esr;
  fpsimd_context fpsimd;
  _aarch64_ctx terminator;
};

void InitializeContext(NativeCPUContext* context) {
  memset(context, 'x', sizeof(*context));

  for (size_t index = 0; index < base::size(context->uc_mcontext.regs);
       ++index) {
    context->uc_mcontext.regs[index] = index;
  }
  context->uc_mcontext.sp = 1;
  context->uc_mcontext.pc = 2;
  context->uc_mcontext.pstate = 3;

  auto test_context = reinterpret_cast<TestCoprocessorContext*>(
      context->uc_mcontext.__reserved);

  test_context->esr.head.magic = ESR_MAGIC;
  test_context->esr.head.size = sizeof(test_context->esr);
  memset(&test_context->esr.esr, 'e', sizeof(test_context->esr.esr));

  test_context->fpsimd.head.magic = FPSIMD_MAGIC;
  test_context->fpsimd.head.size = sizeof(test_context->fpsimd);
  test_context->fpsimd.fpsr = 1;
  test_context->fpsimd.fpcr = 2;
  for (size_t reg = 0; reg < base::size(test_context->fpsimd.vregs); ++reg) {
    test_context->fpsimd.vregs[reg] = reg;
  }

  test_context->terminator.magic = 0;
  test_context->terminator.size = 0;
}

void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) {
  EXPECT_EQ(actual.architecture, kCPUArchitectureARM64);

  EXPECT_EQ(memcmp(actual.arm64->regs,
                   expected.uc_mcontext.regs,
                   sizeof(actual.arm64->regs)),
            0);
  EXPECT_EQ(actual.arm64->sp, expected.uc_mcontext.sp);
  EXPECT_EQ(actual.arm64->pc, expected.uc_mcontext.pc);
  EXPECT_EQ(actual.arm64->spsr, expected.uc_mcontext.pstate);

  auto test_context = reinterpret_cast<const TestCoprocessorContext*>(
      expected.uc_mcontext.__reserved);

  EXPECT_EQ(actual.arm64->fpsr, test_context->fpsimd.fpsr);
  EXPECT_EQ(actual.arm64->fpcr, test_context->fpsimd.fpcr);
  EXPECT_EQ(memcmp(actual.arm64->fpsimd,
                   &test_context->fpsimd.vregs,
                   sizeof(actual.arm64->fpsimd)),
            0);
}
#elif defined(ARCH_CPU_MIPS_FAMILY)
using NativeCPUContext = ucontext_t;

void InitializeContext(NativeCPUContext* context) {
  for (size_t reg = 0; reg < base::size(context->uc_mcontext.gregs); ++reg) {
    context->uc_mcontext.gregs[reg] = reg;
  }
  memset(&context->uc_mcontext.fpregs, 44, sizeof(context->uc_mcontext.fpregs));
}

void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) {
#if defined(ARCH_CPU_MIPSEL)
  EXPECT_EQ(actual.architecture, kCPUArchitectureMIPSEL);
#define CPU_ARCH_NAME mipsel
#elif defined(ARCH_CPU_MIPS64EL)
  EXPECT_EQ(actual.architecture, kCPUArchitectureMIPS64EL);
#define CPU_ARCH_NAME mips64
#endif

  for (size_t reg = 0; reg < base::size(expected.uc_mcontext.gregs); ++reg) {
    EXPECT_EQ(actual.CPU_ARCH_NAME->regs[reg], expected.uc_mcontext.gregs[reg]);
  }

  EXPECT_EQ(memcmp(&actual.CPU_ARCH_NAME->fpregs,
                   &expected.uc_mcontext.fpregs,
                   sizeof(actual.CPU_ARCH_NAME->fpregs)),
            0);
#undef CPU_ARCH_NAME
}

#else
#error Port.
#endif

TEST(ExceptionSnapshotLinux, SelfBasic) {
  FakePtraceConnection connection;
  ASSERT_TRUE(connection.Initialize(getpid()));

  ProcessReaderLinux process_reader;
  ASSERT_TRUE(process_reader.Initialize(&connection));

  siginfo_t siginfo;
  siginfo.si_signo = SIGSEGV;
  siginfo.si_errno = 42;
  siginfo.si_code = SEGV_MAPERR;
  siginfo.si_addr = reinterpret_cast<void*>(0xdeadbeef);

  NativeCPUContext context;
  InitializeContext(&context);

  internal::ExceptionSnapshotLinux exception;
  ASSERT_TRUE(exception.Initialize(&process_reader,
                                   FromPointerCast<LinuxVMAddress>(&siginfo),
                                   FromPointerCast<LinuxVMAddress>(&context),
                                   gettid()));
  EXPECT_EQ(exception.Exception(), static_cast<uint32_t>(siginfo.si_signo));
  EXPECT_EQ(exception.ExceptionInfo(), static_cast<uint32_t>(siginfo.si_code));
  EXPECT_EQ(exception.ExceptionAddress(),
            FromPointerCast<uint64_t>(siginfo.si_addr));
  ExpectContext(*exception.Context(), context);
}

class ScopedSigactionRestore {
 public:
  ScopedSigactionRestore() : old_action_(), signo_(-1), valid_(false) {}

  ScopedSigactionRestore(const ScopedSigactionRestore&) = delete;
  ScopedSigactionRestore& operator=(const ScopedSigactionRestore&) = delete;

  ~ScopedSigactionRestore() { Reset(); }

  bool Reset() {
    if (valid_) {
      int res = sigaction(signo_, &old_action_, nullptr);
      EXPECT_EQ(res, 0) << ErrnoMessage("sigaction");
      if (res != 0) {
        return false;
      }
    }
    valid_ = false;
    signo_ = -1;
    return true;
  }

  bool ResetInstallHandler(int signo, Signals::Handler handler) {
    if (Reset() && Signals::InstallHandler(signo, handler, 0, &old_action_)) {
      signo_ = signo;
      valid_ = true;
      return true;
    }
    return false;
  }

 private:
  struct sigaction old_action_;
  int signo_;
  bool valid_;
};

class RaiseTest {
 public:
  RaiseTest() = delete;
  RaiseTest(const RaiseTest&) = delete;
  RaiseTest& operator=(const RaiseTest&) = delete;

  static void Run() {
    test_complete_ = false;

    ScopedSigactionRestore sigrestore;
    ASSERT_TRUE(sigrestore.ResetInstallHandler(kSigno, HandleRaisedSignal));

    EXPECT_EQ(raise(kSigno), 0) << ErrnoMessage("raise");
    EXPECT_TRUE(test_complete_);
  }

 private:
  static void HandleRaisedSignal(int signo, siginfo_t* siginfo, void* context) {
    FakePtraceConnection connection;
    ASSERT_TRUE(connection.Initialize(getpid()));

    ProcessReaderLinux process_reader;
    ASSERT_TRUE(process_reader.Initialize(&connection));

    internal::ExceptionSnapshotLinux exception;
    ASSERT_TRUE(exception.Initialize(&process_reader,
                                     FromPointerCast<LinuxVMAddress>(siginfo),
                                     FromPointerCast<LinuxVMAddress>(context),
                                     gettid()));

    EXPECT_EQ(exception.Exception(), static_cast<uint32_t>(kSigno));

    EXPECT_EQ(exception.Codes().size(), 3u);
    EXPECT_EQ(exception.Codes()[0], static_cast<uint64_t>(getpid()));
    EXPECT_EQ(exception.Codes()[1], getuid());
    // Codes()[2] is not set by kill, but we still expect to get it because some
    // interfaces may set it and we don't necessarily know where this signal
    // came
    // from.

    test_complete_ = true;
  }

  static constexpr uint32_t kSigno = SIGUSR1;
  static bool test_complete_;
};
bool RaiseTest::test_complete_ = false;

TEST(ExceptionSnapshotLinux, Raise) {
  RaiseTest::Run();
}

class TimerTest {
 public:
  TimerTest() : event_(), timer_(-1), test_complete_(false) { test_ = this; }

  TimerTest(const TimerTest&) = delete;
  TimerTest& operator=(const TimerTest&) = delete;

  ~TimerTest() { test_ = nullptr; }

  void Run() {
    ScopedSigactionRestore sigrestore;
    ASSERT_TRUE(sigrestore.ResetInstallHandler(kSigno, HandleTimer));

    event_.sigev_notify = SIGEV_SIGNAL;
    event_.sigev_signo = kSigno;
    event_.sigev_value.sival_int = 42;
    ASSERT_EQ(syscall(SYS_timer_create, CLOCK_MONOTONIC, &event_, &timer_), 0);

    itimerspec spec;
    spec.it_interval.tv_sec = 0;
    spec.it_interval.tv_nsec = 0;
    spec.it_value.tv_sec = 0;
    spec.it_value.tv_nsec = 1;
    ASSERT_EQ(syscall(SYS_timer_settime, timer_, TIMER_ABSTIME, &spec, nullptr),
              0);

    for (size_t attempt = 0; attempt < 3; ++attempt) {
      SleepNanoseconds(1);
      if (test_complete_) {
        return;
      }
    }
    ADD_FAILURE() << "signal not received";
  }

 private:
  static void HandleTimer(int signo, siginfo_t* siginfo, void* context) {
    FakePtraceConnection connection;
    ASSERT_TRUE(connection.Initialize(getpid()));

    ProcessReaderLinux process_reader;
    ASSERT_TRUE(process_reader.Initialize(&connection));

    internal::ExceptionSnapshotLinux exception;
    ASSERT_TRUE(exception.Initialize(&process_reader,
                                     FromPointerCast<LinuxVMAddress>(siginfo),
                                     FromPointerCast<LinuxVMAddress>(context),
                                     gettid()));

    EXPECT_EQ(exception.Exception(), static_cast<uint32_t>(kSigno));

    EXPECT_EQ(exception.Codes().size(), 3u);
    EXPECT_EQ(exception.Codes()[0], static_cast<uint64_t>(test_->timer_));
    int overruns = syscall(SYS_timer_getoverrun, test_->timer_);
    ASSERT_GE(overruns, 0);
    EXPECT_EQ(exception.Codes()[1], static_cast<uint64_t>(overruns));
    EXPECT_EQ(exception.Codes()[2],
              static_cast<uint64_t>(test_->event_.sigev_value.sival_int));

    test_->test_complete_ = true;
  }

  sigevent event_;
  __kernel_timer_t timer_;
  volatile bool test_complete_;

  static constexpr uint32_t kSigno = SIGALRM;
  static TimerTest* test_;
};
TimerTest* TimerTest::test_;

TEST(ExceptionSnapshotLinux, SelfTimer) {
  TimerTest test;
  test.Run();
}

}  // namespace
}  // namespace test
}  // namespace crashpad
