/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) CNRS, INRIA, Université Bordeaux 1, Télécom SudParis
 * See COPYING in top-level directory.
 *
 *
 * bfd.c
 *
 * Parsing of binaries using the BFD library. See binary.h for the interfaces
 * This file needs the -lbfd flag when linking
 *
 *  Created on: 2 juil. 2011
 *      Author: Damien Martin-Guillerez <damien.martin-guillerez@inria.fr>
 */

#include "binary.h"

#include <assert.h>
#include <bfd.h>
#include <eztrace-core/ezt_demangle.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

// Internals
int _initialized = 0;
void _zzt_bin_init() {
  if (_initialized == 0) {
    bfd_init();
  }
}

zzt_word zzt_asymbol_size(asymbol* sym) {
  if (bfd_asymbol_flavour(sym) == bfd_target_elf_flavour) {
    // ELF case hack by reverse engineering libbfd...
    bfd_vma* ielf = (bfd_vma*)(sym + 1); // The elf_internal_sym structure can be found right after the bfd struct
    return *(ielf + 1);                  // and second word in that struct is the size of the symbol
  } else {
    // We don't know
    return 0;
  }
}

/* read the symtable
 * fills syms and return the number of symbols
 */
static int _get_symbol_table(bfd* abfd, asymbol*** symbol_table) {
  asymbol** syms = 0;
  unsigned int size;
  int dyn = 0;
  int i;
  char* p = NULL;

  /* search for symbols a static binary */
  long symcount = bfd_read_minisymbols(abfd, dyn, (void**)&syms, &size);
  if (symcount == 0) {
    /* no symbol found. abfd is probably a dynamic library */
    dyn = 1;
    symcount = bfd_read_minisymbols(abfd, dyn, (void**)&syms, &size);
  }
  if (symcount < 0) {
    return symcount;
  }

  *symbol_table = (asymbol**)malloc(sizeof(asymbol*) * symcount);
  assert(*symbol_table);
  p = (char*)syms;
  for (i = 0; i < symcount; i++) {
    (*symbol_table)[i] = bfd_minisymbol_to_symbol(abfd, dyn, p, NULL);
    p += size;
  }
  return symcount;
}

void* apply_on_symbol(void* bin, char* symbol,
                      void* (*apply)(bfd* abfd, asymbol* sym, va_list ap), ...) {
  va_list ap;
  bfd* abfd = (bfd*)bin;
  asymbol** symbol_table;
  long number_of_symbols;
  long i;

  va_start(ap, apply);
  number_of_symbols = _get_symbol_table(abfd, &symbol_table);

  if (number_of_symbols < 0)
    return NULL;

  for (i = 0; i < number_of_symbols; i++) {
    if (strcmp(symbol_table[i]->name, symbol) == 0 ||
        strcmp(ezt_demangle(symbol_table[i]->name), symbol) == 0) {
      void* result = apply(abfd, symbol_table[i], ap);
      va_end(ap);
      free(symbol_table);
      return result;
    }
  }
  va_end(ap);
  free(symbol_table);

  return NULL;
}

const char* pptrace_bin_error() {
  if (bfd_get_error() == bfd_error_no_error)
    return NULL;
  return bfd_errmsg(bfd_get_error());
}

// free_symbol
void free_symbol(zzt_symbol* symbol) {
  if (symbol == NULL)
    return;

  free(symbol->symbol_name);
  free(symbol->section_name);
  free(symbol);
}

static char* _copy_string(const char* str) {
  int len = strlen(str) + 1;
  char* ret = malloc(sizeof(char)*len);
  memcpy(ret, str, sizeof(char)*len);
  return ret;
}

zzt_symbol* copy_symbol(zzt_symbol* symbol) {
  zzt_symbol* ret = malloc(sizeof(zzt_symbol));

  ret->symbol_offset = symbol->symbol_offset;
  ret->symbol_name = _copy_string(symbol->symbol_name);
  ret->symbol_size = symbol->symbol_size;
  ret->section_name = _copy_string(symbol->section_name);
  ret->section_addr = symbol->section_addr;
  ret->flags = symbol->flags;

  return ret;
}

// open_binary
void* open_binary(const char* path) {
  bfd* bfd;
  _zzt_bin_init();

  bfd = bfd_openr(path, NULL);
  if (!bfd) {
    return NULL;
  }

  if (!bfd_check_format(bfd, bfd_object)) {
    bfd_close(bfd);
    return NULL;
  }
  return bfd;
}

// close_binary
void close_binary(void* bin) {
  bfd_close((bfd*)bin);
}

int get_binary_bits(void* bin) {
  return bfd_arch_bits_per_address((bfd*)bin);
}

// get_symbol
void* symbol_from_bfd_sym(bfd* abfd __attribute__((unused)), asymbol* sym,
                          va_list ap __attribute__((unused))) {
  zzt_symbol* result;

  result = (zzt_symbol*)malloc(sizeof(zzt_symbol));
  result->symbol_name = strdup(sym->name);
  result->symbol_offset = sym->value;
  if (sym->section == NULL) {
    result->section_name = "";
    result->section_addr = 0;
  } else {
    result->section_name = strdup(sym->name);
    result->section_addr = sym->section->vma;
  }

  if (abfd->flags & DYNAMIC) {
    /* The binary is relocatable (ie. compiled with -fPIE). The address
     * is thus an offset from the base address of the binary
     */
    result->flags = ZZT_FLAG_DYNAMIC;
  } else {
    /* the address is the exact place where the symbol will be (ie. not
     * compiled with -fPIE)
     */
    result->flags = ZZT_FLAG_STATIC;
  }
  result->symbol_size = zzt_asymbol_size(sym);

#ifdef __arm__
  /* On ARM processors, a symbol defined in a shared-library (ie. marked as 'U' in the symbol table)
   * may have a base address. The instructions located at this base address seem to be replaced by
   * the 'real' instruction when the library is loaded.
   *
   * In order to simplify pptrace behavior, let's return NULL for these symbols.
   */
  if (result) {
    symbol_info syminfo;
    bfd_get_symbol_info(abfd, sym, &syminfo);
    if (syminfo.type == 'U') {
      result = NULL;
    }
  }
#endif

  return (void*)result;
}

zzt_symbol* get_symbol(void* bin, char* symbol) {
  return (zzt_symbol*)apply_on_symbol(bin, symbol, symbol_from_bfd_sym);
}

// read_symbol
void* read_symbol_from_bfd_sym(bfd* abfd, asymbol* sym, va_list ap) {
  void* buffer = va_arg(ap, void*);
  ssize_t size = va_arg(ap, ssize_t);
  size_t result = zzt_asymbol_size(sym);
  if (result == 0 || result > size)
    result = size;
  if (sym->section != NULL) {
    if (bfd_get_section_contents(abfd, sym->section, buffer, sym->value,
				 result)) {
      return (void*) result;
    }
  }
  return (void*)0;
}

size_t read_symbol(void* bin, zzt_symbol* symbol, void* buffer, ssize_t size) {
  return (size_t)apply_on_symbol(bin, symbol->symbol_name,
                                 read_symbol_from_bfd_sym, buffer, size);
}

// read_zstring
struct _get_section_struct {
  asection* result;
  zzt_word addr;
};

size_t read_zstring(void* bin, zzt_symbol* symbol __attribute__((unused)), zzt_word addr, void* buffer,
                    ssize_t size) {
  asection* sect;
  uint8_t buf;
  bfd* abfd = (bfd*)bin;

  for (sect = abfd->sections; sect != NULL; sect = sect->next) {
    if (sect->vma <= addr && addr < sect->vma + sect->size) {
      addr -= sect->vma;
      if (size > sect->size - addr) {
        size = sect->size - addr;
      }
      size_t result;

      for (result = 0;
           result < size - 1 && bfd_get_section_contents(abfd, sect, &buf, addr + result, 1) && buf != 0; result++) {
        ((uint8_t*)buffer)[result] = buf;
      }
      ((uint8_t*)buffer)[result] = 0;
      result++;
      return result;
    }
  }
  return 0;
}
