/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) CNRS, INRIA, Université Bordeaux 1, Télécom SudParis
 * See COPYING in top-level directory.
 *
 *
 * trace.c
 *
 * Determining the size to override (by computing the size of instructions) using single step tracing
 *
 *  Created on: 2 July 2011
 *      Author: Damien Martin-Guillerez <damien.martin-guillerez@inria.fr>
 */

#ifndef ENABLE_BINARY_INSTRUMENTATION
#error "This file is for binary instrumentation only"
#endif

#ifndef __x86_64__
#error "This file is for x86_64 only"
#endif

#include "eztrace-instrumentation/errors.h"
#include "eztrace-instrumentation/tracing.h"
#include "opcodes.h"

#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

/*
 * This function returns the jump size
 */
ssize_t trace_get_jump_size(pid_t child, word_uint instruction_addr) {
  uint8_t buf[10];
  size_t offset = 0;
  uint8_t prefix = 0;
  trace_read(child, instruction_addr, buf, 10);
  ssize_t r = read_jump(buf, 10, offset, prefix);
  while (!r && offset < 9) {
    prefix = buf[offset];
    offset++;
    r = read_jump(buf, 10, offset, prefix);
  }

  if (r > 0) {
    return r + offset; // it's a jump of r + offset bytes
  }
  return 0;
}

/*
 * This function determines the size of instructions that will be replaced.
 * To do so, place rip on the symbol and single step until the size of instruction exceed trampoline_size
 * Output is the size of the replacement
 */
ssize_t get_overridden_size(void* bin __attribute__((unused)), pid_t child, word_uint symbol,
                            size_t trampoline_size) {
  REG_STRUCT regs;
  trace_get_regs(child, &regs); // Backup registers

  set_ip(child, symbol); // Jump to symbol
  word_uint crip = symbol;

  int n_signal = 0;
  while (crip < symbol + trampoline_size) {
    int status = trace_singlestep(child); // single stepping until we go further than symbol + trampoline_size

    if (WIFSTOPPED(status)) {
      if (n_signal++ > 100) {
        printf("Child %d received %d signals (the last one was: %s). Aborting\n", child, n_signal, strsignal(WSTOPSIG(status)));
        abort();
        return 0;
      }
    } else {
      n_signal = 0;
    }

    word_uint newrip = get_ip(child);

    // Unfortunately, this works only if no jump / call
    if (newrip < crip || newrip > crip + 2) { // There might be a jump
                                              // Tells whether it is a jump or not
      ssize_t off = trace_get_jump_size(child, crip);
      if (off) {
        newrip = crip + off;
        set_ip(child, newrip);
      } else if (newrip < crip) {
        trace_set_regs(child, &regs); // Restore registers
        return 0;                     // We have failed!
      }
    } else if (newrip == crip) {
      if (WIFSTOPPED(status)) {
        newrip = crip; // Stopped, continue
      } else {
        pptrace_fubar("got signal %d (term) %d (status)", WTERMSIG(status),
                      WSTOPSIG(status));
      }
    }
    crip = newrip;
  }
  trace_set_regs(child, &regs); // Restore registers
  return (size_t)(crip - symbol);
}
