/*
 * ptrace-demo.c
 * - Doprovodny program k clanku "Napiste si debugger"
 * 
 * Michal Ludvig <michal@logix.cz> (c) 2003
 * Homepage: http://www.logix.cz/michal/devel/ptrace-demo
 *
 * This code is public domain. Use it as you want to, 
 * but don't blame me if something doesn't work as you 
 * expect.
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ptrace.h>

#define offsetof(STRUCT,MEMBER) ((long)&((STRUCT *)0)->MEMBER)

#if defined(__amd64__)
#define PC      rip
#elif defined(__i386__)
#define PC      eip
#else
#error "Kompilujte pouze na platforme i386 nebo amd64!"
#endif

/**** Zde je potomek ****/

long var;

void
set_var (long value)
{
  var = value;
  printf ("Hodnota nastavena na %ld\n", var);
}

void
print_var ()
{
  printf ("Hodnota proměnné var je %ld\n", var);
}

void
child ()
{
  printf ("(child) Zastavuji potomka\n");
  ptrace (PTRACE_TRACEME, 0, NULL, NULL);
  kill (getpid (), SIGSTOP);
  printf ("(child) Potomek pokracuje\n");

  set_var (10);
  printf ("(child) Hodnota nastavena\n");
  print_var ();
  printf ("(child) Konec\n");
}

/**** Funkce rodice volajici ptrace(2) na potomka ****/

int
set_text_byte (pid_t child_pid, void *address, char *insn_byte)
{
  char saved_byte;

  union
  {
    long insn_long;
    char insn_char[sizeof (long)];
  } insn;

  insn.insn_long = ptrace (PTRACE_PEEKTEXT, child_pid, address, NULL);
  if (insn.insn_long == -1 && errno)
    {
      perror ("ptrace(PTRACE_PEEKTEXT)");
      return -1;
    }

  saved_byte = insn.insn_char[0];
  insn.insn_char[0] = *insn_byte;
  *insn_byte = saved_byte;

  if (ptrace (PTRACE_POKETEXT, child_pid, address, insn.insn_long) < 0)
    {
      perror ("ptrace(PTRACE_POKETEXT)");
      return -1;
    }

  return 0;
}

int
set_data_long (pid_t child_pid, void *address, long *new_data)
{
  long saved_long;

  saved_long = ptrace (PTRACE_PEEKDATA, child_pid, address, NULL);
  if (saved_long == -1 && errno)
    {
      perror ("ptrace(PTRACE_PEEKDATA)");
      return -1;
    }

  if (ptrace (PTRACE_POKEDATA, child_pid, address, *new_data) < 0)
    {
      perror ("ptrace(PTRACE_POKEDATA)");
      return -1;
    }

  *new_data = saved_long;

  return 0;
}

int
read_register (pid_t child_pid, long offset, long *data)
{
  long ret;

  ret = ptrace (PTRACE_PEEKUSER, child_pid, offset, NULL);
  if (ret == -1 && errno)
    {
      perror ("perror(PTRACE_PEEKUSER)");
      return -1;
    }
  *data = ret;
  return 0;
}

int
write_register (pid_t child_pid, long offset, long data)
{
  return ptrace (PTRACE_POKEUSER, child_pid, offset, data);
}

int
decrement_pc (pid_t child_pid)
{
  long pc;

  if (read_register (child_pid, offsetof (struct user, regs.PC), &pc) < 0)
      return -1;

  if (write_register (child_pid, offsetof (struct user, regs.PC), pc - 1) < 0)
      return -1;

  return 0;
}

/**** Hlani funkce rodice, ktera ovlada chovani potomka ****/

void
parent (pid_t child_pid)
{
  int status;
  char breakpoint_insn;
  long var_value;

  wait4 (child_pid, &status, WUNTRACED, NULL);
  if (!WIFSTOPPED (status) || WSTOPSIG (status) != SIGSTOP)
    {
      printf ("Neco je spatne...\n");
      exit (1);
    }

  printf ("(parent) Potomek zastaven - nastavuji breakpoint\n");

  breakpoint_insn = 0xcc;       /* int3 == 0xcc */
  if (set_text_byte (child_pid, print_var, &breakpoint_insn) < 0)
    exit (1);

  ptrace (PTRACE_CONT, child_pid, NULL, NULL);

  wait4 (child_pid, &status, WUNTRACED, NULL);
  if (!WIFSTOPPED (status) || WSTOPSIG (status) != SIGTRAP)
    {
      printf ("Neco je spatne...\n");
      exit (1);
    }

  printf ("(parent) Potomek zastaven - vracim puvodni instrukci\n");

  if (set_text_byte (child_pid, print_var, &breakpoint_insn) < 0)
    exit (1);

  printf ("(parent)                  - dekrementuji instruction pointer\n");

  if (decrement_pc (child_pid))
    exit (1);

  var_value = 123;
  printf ("(parent)                  - menim hodnoru 'var' na %ld\n",
          var_value);

  if (set_data_long (child_pid, &var, &var_value) < 0)
    exit (1);

  printf ("(parent)                    (puvodni hodnota byla %ld)\n",
          var_value);

  printf ("(parent) A jedeme dal...\n");
  ptrace (PTRACE_CONT, child_pid, NULL, NULL);

  while (wait4 (child_pid, &status, WUNTRACED, NULL) >= 0)
    {
      printf ("(parent) Udalost od potomka: ");
      if (WIFEXITED (status))
        printf ("WEXITSTATUS: %d\n", WEXITSTATUS (status));
      else if (WIFSIGNALED (status))
        printf ("WTERMSIG: %d\n", WTERMSIG (status));
      else if (WIFSTOPPED (status))
        {
          printf ("WSTOPSIG: %d (%s)\n",
                  WSTOPSIG (status), strsignal (WSTOPSIG (status)));
        }
    }
}

/**** Trivialni main - pouze vytvori dva totozne procesy ****/

int
main ()
{
  pid_t child_pid;

  if ((child_pid = fork ()))
    parent (child_pid);
  else
    child ();

  exit (0);
}