// Copyright 2019 Google
//
// 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 "Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"

#if defined(__arm__) || defined(__arm64__)
#include <mach/arm/thread_status.h>
#include <ptrauth.h>
#endif

#if CLS_CPU_X86_64
#define GET_IP_REGISTER(r) (r->__ss.__rip)
#define GET_FP_REGISTER(r) (r->__ss.__rbp)
#define GET_SP_REGISTER(r) (r->__ss.__rsp)
#define GET_LR_REGISTER(r) 0
#define SET_IP_REGISTER(r, v) (r->__ss.__rip = v)
#define SET_FP_REGISTER(r, v) (r->__ss.__rbp = v)
#define SET_SP_REGISTER(r, v) (r->__ss.__rsp = v)
#define SET_LR_REGISTER(r, v)
#elif CLS_CPU_I386
#define GET_IP_REGISTER(r) (r->__ss.__eip)
#define GET_FP_REGISTER(r) (r->__ss.__ebp)
#define GET_SP_REGISTER(r) (r->__ss.__esp)
#define GET_LR_REGISTER(r) 0
#define SET_IP_REGISTER(r, v) (r->__ss.__eip = v)
#define SET_FP_REGISTER(r, v) (r->__ss.__ebp = v)
#define SET_SP_REGISTER(r, v) (r->__ss.__esp = v)
#define SET_LR_REGISTER(r, v)
#elif CLS_CPU_ARM64
// The arm_thread_state64_get_* macros translate down to the AUTIA and AUTIB instructions which
// authenticate the address, but don't clear the upper bits. From the docs:
//      "If the authentication passes, the upper bits of the address are restored to enable
//      subsequent use of the address. the authentication fails, the upper bits are corrupted and
//      any subsequent use of the address results in a Translation fault."
// Since we only want the address (with the metadata in the upper bits masked out), we used the
// ptrauth_strip macro to clear the upper bits.
//
// We found later that ptrauth_strip doesn't seem to do anything. In many cases, the upper bits were
// already stripped, so for most non-system-library code, Crashlytics would still symbolicate. But
// for system libraries, the upper bits were being left in even when we called ptrauth_strip.
// Instead, we're bit masking and only allowing the latter 36 bits.
#define CLS_PTRAUTH_STRIP(pointer) ((uintptr_t)pointer & 0x0000000FFFFFFFFF)
#define GET_IP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_pc(r->__ss)))
#define GET_FP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_fp(r->__ss)))
#define GET_SP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_sp(r->__ss)))
#define GET_LR_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_lr(r->__ss)))
#define SET_IP_REGISTER(r, v) arm_thread_state64_set_pc_fptr(r->__ss, (void*)v)
#define SET_FP_REGISTER(r, v) arm_thread_state64_set_fp(r->__ss, v)
#define SET_SP_REGISTER(r, v) arm_thread_state64_set_sp(r->__ss, v)
#define SET_LR_REGISTER(r, v) arm_thread_state64_set_lr_fptr(r->__ss, (void*)v)
#elif CLS_CPU_ARM
#define GET_IP_REGISTER(r) (r->__ss.__pc)
#define GET_FP_REGISTER(r) (r->__ss.__r[7])
#define GET_SP_REGISTER(r) (r->__ss.__sp)
#define GET_LR_REGISTER(r) (r->__ss.__lr)
#define SET_IP_REGISTER(r, v) (r->__ss.__pc = v)
#define SET_FP_REGISTER(r, v) (r->__ss.__r[7] = v)
#define SET_SP_REGISTER(r, v) (r->__ss.__sp = v)
#define SET_LR_REGISTER(r, v) (r->__ss.__lr = v)
#else
#error "Architecture Unsupported"
#endif

uintptr_t FIRCLSThreadContextGetPC(FIRCLSThreadContext* registers) {
  if (!registers) {
    return 0;
  }

  return GET_IP_REGISTER(registers);
}

uintptr_t FIRCLSThreadContextGetStackPointer(const FIRCLSThreadContext* registers) {
  if (!registers) {
    return 0;
  }

  return GET_SP_REGISTER(registers);
}

bool FIRCLSThreadContextSetStackPointer(FIRCLSThreadContext* registers, uintptr_t value) {
  if (!FIRCLSIsValidPointer(registers)) {
    return false;
  }

  SET_SP_REGISTER(registers, value);

  return true;
}

uintptr_t FIRCLSThreadContextGetLinkRegister(const FIRCLSThreadContext* registers) {
  if (!FIRCLSIsValidPointer(registers)) {
    return 0;
  }

  return GET_LR_REGISTER(registers);
}

bool FIRCLSThreadContextSetLinkRegister(FIRCLSThreadContext* registers, uintptr_t value) {
  if (!FIRCLSIsValidPointer(registers)) {
    return false;
  }

  SET_LR_REGISTER(registers, value);

  return true;
}

bool FIRCLSThreadContextSetPC(FIRCLSThreadContext* registers, uintptr_t value) {
  if (!registers) {
    return false;
  }

  SET_IP_REGISTER(registers, value);

  return true;
}

uintptr_t FIRCLSThreadContextGetFramePointer(const FIRCLSThreadContext* registers) {
  if (!FIRCLSIsValidPointer(registers)) {
    return 0;
  }

  return GET_FP_REGISTER(registers);
}

bool FIRCLSThreadContextSetFramePointer(FIRCLSThreadContext* registers, uintptr_t value) {
  if (!FIRCLSIsValidPointer(registers)) {
    return false;
  }

  SET_FP_REGISTER(registers, value);

  return true;
}
