/* debug.c (emx+gcc) */

/* Test ptrace() */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <process.h>
#include <signal.h>
#include <errno.h>
#include <sys/ptrace.h>
#include <sys/uflags.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/wait.h>

typedef unsigned char byte;

struct reg
{
  char *name;
  int regno;
  unsigned value;
};

struct breakpoint
{
  struct breakpoint *next;
  unsigned addr;
  byte save;
};

#define N_EIP 8

static struct reg regs[] =
{
  {"eax", EAX},
  {"ebx", EBX},
  {"ecx", ECX},
  {"edx", EDX},
  {"esi", ESI},
  {"edi", EDI},
  {"esp", UESP},
  {"ebp", EBP},
  {"eip", EIP},
  {NULL, 0}
};


static int pid;
static int silent;
static int auto_switch;
static int notify;
static int uaddr;
static long dump_addr;
static struct breakpoint *breakpoints = NULL;

static void show_regs (void)
{
  int i, j, r;

  j = 4;
  for (i = 0; regs[i].name != NULL; ++i)
    {
      errno = 0;
      r = ptrace (PTRACE_PEEKUSER, pid, uaddr + regs[i].regno * 4, 0);
      if (errno != 0) perror ("ptrace");
      regs[i].value = r;
      if (j == 4)
        {
          j = 0; putchar ('\n');
        }
      ++j;
      printf ("%s=%.8x ", regs[i].name, r);
    }
  errno = 0;
  r = ptrace (PTRACE_PEEKTEXT, pid, regs[N_EIP].value, 0);
  if (errno == 0)
    for (i = 0; i < 4; ++i)
      {
        printf ("%.2x ", r & 0xff);
        r >>= 8;
      }
  putchar ('\n');
}


static void insert_bp (struct breakpoint *bp, int eip)
{
  int r;

  if (bp->addr != eip)
    {
      errno = 0;
      r = ptrace (PTRACE_PEEKTEXT, pid, bp->addr, 0);
      if (errno != 0)
        return;
      bp->save = (byte)r;
      r = (r & ~0xff) | 0xcc;
      ptrace (PTRACE_POKETEXT, pid, bp->addr, r);
    }
}


static void remove_bp (struct breakpoint *bp)
{
  int r;

  errno = 0;
  r = ptrace (PTRACE_PEEKTEXT, pid, bp->addr, 0);
  if (errno != 0)
    return;
  r = (r & ~0xff) | bp->save;
  ptrace (PTRACE_POKETEXT, pid, bp->addr, r);
}


static void insert_breakpoints (struct breakpoint *bp)
{
  int r;

  errno = 0;
  r = ptrace (PTRACE_PEEKUSER, pid, uaddr + EIP * 4, 0);
  if (errno != 0) perror ("ptrace");
  while (bp != NULL)
    {
      insert_bp (bp, r);
      bp = bp->next;
    }
}


static void remove_breakpoints (struct breakpoint *bp)
{
  if (bp != NULL)
    {
      remove_breakpoints (bp->next);
      remove_bp (bp);
    }
}


static void run (int cmd, int signo)
{
  int s, t, p, r, e;
  struct breakpoint *bp;
  union ptn_data ptd;

  insert_breakpoints (breakpoints);
  if (auto_switch)
    ptrace (PTRACE_SESSION, pid, 0, 2);

  for (;;)
    {
      s = ptrace (cmd, pid, 1, signo);
      if (s < 0)
        break;
      p = wait (&t);
      if (p == -1 || !WIFSTOPPED (t) || WSTOPSIG (t) != SIGPTRACENOTIFY)
        break;
      s = ptrace (PTRACE_NOTIFICATION, pid, (int)&ptd, (int)sizeof (ptd));
      if (s < 0)
        break;
      switch (s)
        {
        case PTN_THREAD_NEW:
          printf ("[New thread %d]\n", ptd.thread.tid);
          break;
        case PTN_THREAD_END:
          printf ("[Thread %d terminated]\n", ptd.thread.tid);
          break;
        case PTN_MODULE_LOAD:
          printf ("[Load module %#lx:%s]\n", ptd.module.hmte, ptd.module.name);
          break;
        case PTN_MODULE_FREE:
          printf ("[Free module %#lx:%s]\n", ptd.module.hmte, ptd.module.name);
          break;
        default:
          printf ("[Unknown notification %d]\n", s);
        }
    }
  e = errno;
  if (auto_switch)
    ptrace (PTRACE_SESSION, pid, 0, 0);
  remove_breakpoints (breakpoints);
  errno = e;
  if (s < 0)
    perror ("ptrace");
  else
    {
      if (p == -1)
        perror ("wait");
      if (!silent)
        printf ("wait: %.4x\n", t);
      if (WIFSIGNALED (t))
        {
          printf ("Program terminated (signal %d)\n", WTERMSIG (t));
          exit (0);
        }
      if (WIFEXITED (t))
        {
          printf ("Program terminated (status %d)\n", WEXITSTATUS (t));
          exit (0);
        }
      errno = 0;
      r = ptrace (PTRACE_PEEKUSER, pid, uaddr + EIP * 4, 0);
      if (errno != 0) perror ("ptrace");
      --r;
      for (bp = breakpoints; bp != NULL; bp = bp->next)
        if (bp->addr == r)
          break;
      if (bp != NULL)
        ptrace (PTRACE_POKEUSER, pid, uaddr + EIP * 4, r);
    }
}


static void dump (char *buf)
{
  int i, j, r;
  long n;
  char *p;

  if (*buf != 0)
    {
      errno = 0;
      n = strtol (buf, &p, 16);
      if (errno != 0 || *p != 0)
        {
          printf ("Error!\n");
          return;
        }
      dump_addr = n;
    }
  for (i = 0; i < 8; ++i)
    {
      printf ("%.8lx: ", dump_addr);
      for (j = 0; j < 8; ++j)
        {
          errno = 0;
          r = ptrace (PTRACE_PEEKTEXT, pid, dump_addr, 0);
          if (errno != 0)
            fputs ("?? ?? ", stdout);
          else
            printf ("%.2x %.2x ", r & 0xff, (r >> 8) & 0xff);
          dump_addr += 2;
        }
      putchar ('\n');
    }
}


static void set_bp (char *buf)
{
  int i;
  long addr;
  struct breakpoint *bp, **abp;
  char *p;

  if (*buf == 0)
    {
      for (i = 1, bp = breakpoints; bp != NULL; bp = bp->next)
        printf ("Breakpoint %d at %.8x\n", i, bp->addr);
    }
  else
    {
      errno = 0;
      addr = strtol (buf, &p, 16);
      if (errno != 0 || *p != 0)
        {
          printf ("Error!\n");
          return;
        }
      bp = malloc (sizeof (struct breakpoint));
      if (bp == NULL)
        {
          printf ("Out of memory\n");
          return;
        }
      bp->next = NULL;
      bp->addr = addr;
      for (abp = &breakpoints; *abp != NULL; abp = &(*abp)->next)
        ;
      *abp = bp;
    }
}


static void start (const char *prog, char * const *args, char * const *envp)
{
  struct user u;
  int syserrno;

  pid = spawnve (P_DEBUG | P_WINDOWED, prog, args, envp);
  if (pid < 0)
    {
      syserrno = _syserrno ();
      perror ("spawnve");
      fprintf (stderr, "syserrno = %d\n", syserrno);
      exit (2);
    }
  printf ("pid=%d\n", pid);
  errno = 0;
  uaddr = ptrace (PTRACE_PEEKUSER, pid, (char *)&u.u_ar0 - (char *)&u, 0);
  if (errno != 0) perror ("ptrace");
  uaddr -= 0xe0000000;
}


static void command_loop (void)
{
  char buf[10], *p;

  for (;;)
    {
      if (fgets (buf, sizeof (buf), stdin) == NULL)
        break;
      p = strchr (buf, '\n');
      if (p != NULL) *p = 0;
      if (buf[0] == 'q')
        break;
      else if (buf[0] == 'd')
        dump (buf+1);
      else if (buf[0] == 'b')
        set_bp (buf+1);
      else if (buf[0] == 'g')
        {
          run (PTRACE_RESUME, 0);
          show_regs ();
        }
      else if (buf[0] == 0)
        {
          run (PTRACE_STEP, 0);
          show_regs ();
        }
      else
        {
          puts ("q        quit");
          puts ("d ADDR   dump");
          puts ("g        resume");
          puts ("b ADDR   set breakpoint");
          puts ("(empty)  step");
        }
    }
}


int main (int argc, char **argv, char **envp)
{
  int i;

  i = 1; silent = 0; auto_switch = 0; notify = 0;
  while (i < argc)
    {
      if (strcmp (argv[i], "-s") == 0)
        {
          silent = 1; ++i;
        }
      else if (strcmp (argv[i], "-a") == 0)
        {
          auto_switch = 1; ++i;
        }
      else if (strcmp (argv[i], "-n") == 0)
        {
          notify = 1; ++i;
        }
      else
        break;
    }
  if (i+1 > argc)
    {
      fputs ("Usage: debug [-a] [-n] [-s] prog [args]\n", stderr);
      exit (1);
    }

  if (notify)
    _uflags (_UF_PTRACE_MODEL, _UF_PTRACE_NOTIFY);

  start (argv[i], argv + i, envp);
  if (silent)
    for (;;)
      run (PTRACE_STEP, 0);
  else
    command_loop ();
  return 0;
}
