// membrk_linux.cpp
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <linux/perf_event.h>
#include <cstring>
#include <cerrno>
#include <cstdio>
#include <fcntl.h>
#include <cstdint>   // uintptr_t
#include <cstdlib>

// Try to include hw_breakpoint helper macros; if not available, provide fallbacks.
#if __has_include(<linux/hw_breakpoint.h>)
# include <linux/hw_breakpoint.h>
#else
// Fallback defs if your libc/kernel headers don't provide them.
#ifndef HW_BREAKPOINT_R
# define HW_BREAKPOINT_R  1
#endif
#ifndef HW_BREAKPOINT_W
# define HW_BREAKPOINT_W  2
#endif
#ifndef HW_BREAKPOINT_RW
# define HW_BREAKPOINT_RW (HW_BREAKPOINT_R | HW_BREAKPOINT_W)
#endif
#ifndef HW_BREAKPOINT_X
# define HW_BREAKPOINT_X  4
#endif
#endif

// portable alias if __NR_perf_event_open symbol not present:
#ifndef __NR_perf_event_open
# if defined(__x86_64__)
#  define __NR_perf_event_open 298
# elif defined(__i386__)
#  define __NR_perf_event_open 336
# else
#  error "__NR_perf_event_open unknown for this architecture; please add its number"
# endif
#endif

int setup_hw_breakpoint(void* addr, size_t len = 1, int type = HW_BREAKPOINT_RW) {
  struct perf_event_attr attr;
  std::memset(&attr, 0, sizeof(attr));
  attr.type = PERF_TYPE_BREAKPOINT;
  attr.size = sizeof(attr);            // important!
  attr.config = 0;
  attr.bp_addr = (uintptr_t)addr;
  attr.bp_len  = len;
  attr.bp_type = type;                 // HW_BREAKPOINT_R/W/RW/X
  attr.disabled = 0;                   // start enabled
  attr.exclude_kernel = 1;             // user-space only
  attr.exclude_hv     = 1;

  // pid = 0 -> current process/thread; cpu = -1 -> any CPU; group_fd = -1; flags = 0
  int fd = (int) syscall(__NR_perf_event_open, &attr, 0 /*pid*/, -1 /*cpu*/, -1 /*group_fd*/, 0);
  if (fd == -1) {
      std::perror("perf_event_open");
  }
  return fd;
}

int main() {
  int x = 42;

  int fd = setup_hw_breakpoint(&x, sizeof(int), HW_BREAKPOINT_RW);
  if (fd < 0) {
      std::fprintf(stderr, "Failed to set hw breakpoint. Check /proc/sys/kernel/perf_event_paranoid and permissions.\n");
      return 1;
  }

  std::printf("Hardware breakpoint set on &x = %p, fd=%d\n", (void*)&x, fd);

  // Demonstrate a write that should trigger the breakpoint.
  // By default the kernel will deliver an event; you can poll read(fd, ...) or get SIGIO if configured.
  // Here we just modify the variable; on many kernels this causes an event you can read from fd.
  x = 123;

  // Try to read the event (non-blocking): this will succeed if the kernel produced an event buffer.
  char buf[1024];
  ssize_t r = read(fd, buf, sizeof(buf));
  if (r > 0) {
      std::printf("Event(s) available: read %zd bytes\n", r);
  } else if (r == 0) {
      std::printf("No event data available (read returned 0)\n");
  } else {
      if (errno == EAGAIN) std::printf("No events yet (EAGAIN)\n");
      else std::perror("read");
  }

  close(fd);
  return 0;
}
