/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) CNRS, INRIA, Université Bordeaux 1, Télécom SudParis
 * See COPYING in top-level directory.
 *
 *
 * arm.c
 *
 * Generate opcodes for ARM cortex-A9 processors
 *
 *      Author: Damien Martin-Guillerez <damien.martin-guillerez@inria.fr>
 */

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

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

#include <stdlib.h>
#include <string.h>

ssize_t insert_opcodes(uint8_t* opcode, int opcode_size, uint8_t* buf,
                       size_t size, size_t offset) {
  int i = 0;
  pptrace_debug(PPTRACE_DEBUG_LEVEL_ALL, "(opcodes)\tinserting opcodes: ");
  while (offset < size && i < opcode_size) {
    pptrace_debug(PPTRACE_DEBUG_LEVEL_ALL, "%02X ", opcode[i]);
    buf[offset] = opcode[i];
    offset++;
    i++;
  }
  pptrace_debug(PPTRACE_DEBUG_LEVEL_ALL, "\n");
  return offset;
}

ssize_t read_jump(uint8_t* buf, size_t size, size_t offset, uint8_t prefix) {
  // Since the size of instructions on ARMv7 is static (=32b), the size of the jump instruction
  //    can be simply computed as
  return ARM_INSTR_SIZE / 8;
}

static inline int get_unsigned_nb_bits(word_uint value) {
  int index = 0;
  while (value) {
    value >>= 1;
    index++;
  }
  return index;
}

int get_signed_nb_bits(word_int value) {
  if (value < 0) {
    value = -value;
  } else {
    value++;
  }
  return get_unsigned_nb_bits(value) + 1;
}

ssize_t generate_nops(uint8_t* buffer, size_t size) {
  int i;
  uint8_t buf[1024];
  for (i = 0; i < 1024; i += 2) {
    buf[i] = 0xbf;
    buf[i + 1] = 0x00;
  }
  return insert_opcodes(buf, 12, buffer, size, 0);
}

ssize_t generate_trampoline(uint8_t* buffer, size_t size, word_uint orig_addr,
                            word_uint reloc_addr) {
  uint8_t buf[12];

  pptrace_debug(PPTRACE_DEBUG_LEVEL_ALL,
                "(opcodes)\tgenerating trampoline from 0x%lx to 0x%lx\n",
                (unsigned long)orig_addr, (unsigned long)reloc_addr);

  if (size < 12)
    return -1;
  uint32_t addr = reloc_addr;

#define GET_IM8(addr_16) ((addr_16) & 0x000000ff)
#define GET_IM3(addr_16) (((addr_16)>>8) &  0x00000007)
#define GET_I(addr_16)   (((addr_16) >>11) & 0x00000001)
#define GET_IM4(addr_16) (((addr_16) >>12) & 0x0000000f)

  /* working with register R8 */
  uint8_t register_id = 8;

  /* switch to Thumb mode */
  addr = addr | 0x01;

  // im4:i:im3:im8
  uint16_t addr16 = addr & 0x0000FFFF;

  // movw rX, addr[0:15]
  uint32_t _im8 = GET_IM8(addr16);
  uint32_t _im3 = GET_IM3(addr16);
  uint32_t _i = GET_I(addr16);
  uint32_t _im4 = GET_IM4(addr16);
  buf[3] = (_im3 << 4) | register_id;
  buf[2] = _im8;
  buf[1] = (_i << 2) | (uint8_t)0xf2;
  buf[0] = _im4 | (uint8_t)0x40;

  // movt rX, addr[16:31]
  addr16 = addr >> 16;
  _im8 = GET_IM8(addr16);
  _im3 = GET_IM3(addr16);
  _i = GET_I(addr16);
  _im4 = GET_IM4(addr16);
  buf[7] = (_im3 << 4) | register_id;
  buf[6] = _im8;
  buf[5] = (_i << 2) | (uint8_t)0xf2;
  buf[4] = _im4 | (uint8_t)0xC0;

  // bx rX
  buf[8] = register_id << 3;
  buf[9] = 0x47;

  // nop
  buf[10] = 0x00;
  buf[11] = 0xbf;
  return insert_opcodes(buf, 12, buffer, size, 0);
}
