#define _GNU_SOURCE
#include <memory.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <sched.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/user.h>
#include <pthread.h>
#include <linux/elf.h>
#include <x86intrin.h>
#include <immintrin.h>

#include "cacheutils.h"

//#define DEBUGMODE

#define PAGESIZE 4096

#define ATTACKER_CORE 6
#define VICTIM_CORE 7

#define FOLLOW_CHILD_ON_FORK 0

#define MSR_REG 0xC0011029ULL
#define STACKENGINE_BIT (1ULL << 19)

char* tracee_path;
uint64_t tracee_binary_base;

uint64_t breakpoint_addr;
long breakpoint_original_data; 


void error_exit(const char *msg) {
  fprintf(stderr, "%s: %s\n", msg, strerror(errno));  // Print error message with errno

  exit(EXIT_FAILURE);
}

int ends_with(const char *str, const char *substr) {
  size_t str_len = strlen(str);
  size_t substr_len = strlen(substr);

  // If substr is longer it can't be at the end
  if (str_len < substr_len) {
    return 0;
  }

  // check for match at the end
  return strcmp(str + str_len - substr_len, substr) == 0;
}

// careful we do not allocate a new string here and just calculate the offset
const char* extract_filename(const char* filepath) {
  // get the last '/'
  const char* filename = strrchr(filepath, '/');
  if (filename == NULL) {
    return filepath;
  }
  // we return the substring after the last '/'
  return filename + 1;
}


// sync variables
_Atomic(int) core_b_rdy = 0;
_Atomic(int) core_b_go = 0;
_Atomic(int) core_b_start = 0;
_Atomic(int) core_a_finished = 0;
_Atomic(int) core_b_finished = 0;

// data exchange
_Atomic(uint64_t) core_b_rsp_before = 0;
_Atomic(uint64_t) core_b_rsp_after = 0;


int init_core_a() {
  char msr_path[64];
  snprintf(msr_path, sizeof(msr_path), "/dev/cpu/%d/msr", ATTACKER_CORE);

  int fd = open(msr_path, O_RDWR);
  if (fd < 0) {
      perror("open msr device");
      return EXIT_FAILURE;
  }
  return fd;
}

void profiler_core_a() {
  int fd = init_core_a();
  set_cpu_affinity(ATTACKER_CORE);
  int success = 0;

  uint64_t cur;
  if (pread(fd, &cur, sizeof(cur), MSR_REG) != sizeof(cur)) {
      perror("pread");
      close(fd);
      return EXIT_FAILURE;
  }
  uint64_t enabled = cur | STACKENGINE_BIT;
  uint64_t disabled = cur & ~STACKENGINE_BIT;
  printf("[A] Current MSR value: 0x%" PRIx64 "\n", cur);
  printf("[A] MSR value with stackengine enabled: 0x%" PRIx64 "\n", disabled);
  printf("[A] MSR value with stackengine disabled: 0x%" PRIx64 "\n", enabled);

  // ensure the stackengine is enabled
  pwrite(fd, &disabled, sizeof(disabled), MSR_REG);

  while (!success) {

    core_b_start = 1;
    while(!core_b_rdy) { asm volatile ("pause"); }
    core_b_rdy = 0;

    core_b_go = 1;  // signal core B to start
    // disable stackengine
    int ret1 = pwrite(fd, &enabled, sizeof(enabled), MSR_REG);
    //int ret1 = 1;
    asm volatile(
      REP4K("nop\n\t")
      ::: "memory");
    while (!core_b_finished) {asm volatile ("pause");}
    core_b_finished = 0;
  

    printf("[B] RSP (before): 0x%" PRIx64 "\n", core_b_rsp_before);
    printf("[B] RSP (after):  0x%" PRIx64 "\n", core_b_rsp_after);
    uint64_t rsp_diff = core_b_rsp_after - core_b_rsp_before;
    printf("[B] RSP diff: %d\n", rsp_diff);
    printf("Success (enable) 1: %d\n", ret1 == sizeof(enabled));

    if (rsp_diff == 0) {
      printf("[SUCCESS] Stackengine is primed!\n");
      success = 1;
    } else {
      printf("[FAILURE] Stackengine is not primed correctly!\n");
      // enable stackengine again
      int ret2 = pwrite(fd, &disabled, sizeof(disabled), MSR_REG);
      
      // sleep to allow the system to stabilize
      sleep(1);
    }
  } // while (!success)

  // signal B to stop
  core_a_finished = 1;
}


void profiler_core_b() {
  set_cpu_affinity(VICTIM_CORE);
  

  while (!core_a_finished) {
    while (!core_b_start) { asm volatile ("pause"); }
    core_b_start = 0;

    asm volatile(INTELASM("mov %0, rsp") : "=r"(core_b_rsp_before) :: "memory");

    core_b_rdy = 1;  // signal that B is ready
    while (!core_b_go) {}  // wait for start signal
    core_b_go = 0;
    asm volatile(INTELASM(
      REP100("nop\n\t")
      "mfence\n\t"
      REP4("pop rdi\n\t mfence\n\t")
      REP4K("nop\n\t")
      REP4K("nop\n\t")
      REP4K("nop\n\t")
      REP4K("nop\n\t")
      REP4K("nop\n\t")
    ) ::: "memory");

    asm volatile(INTELASM("mov %0, rsp") : "=r"(core_b_rsp_after) :: "memory");
    core_b_finished = 1;
  }
}

void general_init() {
    struct sched_param param;
    // priority between 1 and 99
    param.sched_priority = 99;

    if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
        perror("[WARNING] Could not set priority. Run as root to reduce the chances of the system crashing\n");
    }

    if (mlockall(MCL_CURRENT) == -1) {
        perror("[WARNING] Could not lock all memory. Run as root to reduce the chances of the system crashing\n");
    }

}
int main(int argc, char* argv[]) {
  pthread_t core_b_tid = {0};
  pthread_create(&core_b_tid, NULL, profiler_core_b, NULL);

  // parent process
  profiler_core_a();

  printf("[A] Finished\n");
}
