/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) CNRS, INRIA, Université Bordeaux 1, Télécom SudParis
 * See COPYING in top-level directory.
 *
 *
 * pptrace.c
 *
 * Public interface of the library
 *
 *  Created on: 2 Aug. 2011
 *      Author: Damien Martin-Guillerez <damien.martin-guillerez@inria.fr>
 */

#include "eztrace-instrumentation/pptrace.h"

#include "binary.h"
#include "eztrace-core/eztrace_attributes.h"
#include "eztrace-instrumentation/errors.h"
#include "eztrace-instrumentation/tracing.h"
#include "proc_maps.h"
#include "eztrace-core/eztrace_config.h"
#include "eztrace-core/eztrace_types.h"

#include <assert.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>

#ifdef ENABLE_BINARY_INSTRUMENTATION
#include "hijack.h"
#include "isize.h"
#include "memory.h"
#include "opcodes.h"
#endif /* ENABLE_BINARY_INSTRUMENTATION */

#ifdef ENABLE_BINARY_INSTRUMENTATION
// To store informations about hijack to install
typedef struct _pptrace_internal_hijack {
  char* function;       // Function to hijack
  zzt_symbol* funSym;   // Symbol of the function in the application
  zzt_symbol* origSym;  // Symbol of the function in eztrace library
  zzt_symbol* replSym;  // Interceptor symbol
} pptrace_internal_hijack;

#endif // ENABLE_BINARY_INSTRUMENTATIOn

// To store informations about libraries to load
typedef struct _pptrace_internal_library {
  char* library;                     // The library name
#ifdef ENABLE_BINARY_INSTRUMENTATION
  pptrace_internal_hijack** hijacks; // The hijacks to install
#endif // ENABLE_BINARY_INSTRUMENTATIOn

  zzt_word baseaddr;

  // Double-linked list
  struct _pptrace_internal_library* next;
  struct _pptrace_internal_library* prev;
} pptrace_internal_library;

// And finally the binary structure
typedef struct _pptrace_internal_binary {
  char* name;   // Name of the binary
  void* binary; // Binary structure

  char** debugger;   // Argument list to debugger an argument matching "{name}" will be replaced by the binary name and arguments matching "{pid}" will be replaced by the program pid
  zzt_word baseaddr; /* base address of the binary */
  // List of libraries
  pptrace_internal_library* first;
  pptrace_internal_library* last;
} pptrace_internal_binary;

#define ALLOC(type) (type*)malloc(sizeof(type));

// Look out for a binary in the path
static char* get_program_path(const char* name) {
  struct stat buf;
  if (stat(name, &buf) == 0) { // The address is relative
    return strdup(name);
  }

  // Looking in the path
  char* path = getenv("PATH");
  if (path == NULL)
    return NULL;
  path = strdup(path); // copying to avoid corrupting the environment variable
  if (path == NULL)
    return NULL;

  char* ifs = getenv("IFS");
  if (ifs == NULL)
    ifs = ":";

  // for each path
  char* p;
  char rpath[1024];
  for (p = strtok(path, ifs); p != NULL; p = strtok(NULL, ifs)) {
    // Add the path component
    if (p[strlen(p) - 1] != '/')
      snprintf(rpath, 1024, "%s/%s", p, name);
    else
      snprintf(rpath, 1024, "%s%s", p, name);
    // Test if it exists
    if (stat(rpath, &buf) == 0) {
      free(path);
      return strdup(rpath);
    }
  }
  free(path);
  return NULL;
}

void* pptrace_prepare_binary(char* binary) {
  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Loading binary %s... ", binary);

  pptrace_clear_error();
  pptrace_internal_binary* bin = ALLOC(pptrace_internal_binary);
  if (!bin) {
    pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "failed!\n");
    pptrace_error("Allocation failed");
    return NULL;
  }
  bin->debugger = NULL;
  bin->first = bin->last = NULL;
  bin->name = get_program_path(binary);
  if (!(bin->name)) {
    pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "failed!\n");
    free(bin);
    pptrace_error("Cannot access binary %s", binary);
    return NULL;
  }
#ifdef ENABLE_BINARY_INSTRUMENTATION
  bin->binary = open_binary(bin->name);
  if (!(bin->binary)) {
    pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "failed!\n");
    pptrace_error("Cannot open binary %s", binary);
    free(bin->name);
    free(bin);
    return NULL;
  }
#endif

  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "ok\n");
  return (void*)bin;
}

void pptrace_add_debugger(void* bin, char** argv) {
  pptrace_internal_binary* ibin = bin;
  ibin->debugger = argv;
  if (argv == NULL)
    pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Installing debugger %s\n");
  else
    pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Uninstalling debugger\n", argv[0]);
}

#ifdef ENABLE_BINARY_INSTRUMENTATION
static void pptrace_free_hijack(pptrace_internal_hijack* hijack) {
  if (!hijack)
    return;
  free_symbol(hijack->origSym);
  free_symbol(hijack->replSym);
  free_symbol(hijack->funSym);
  free(hijack->function);

  free(hijack);
}

static pptrace_internal_hijack* pptrace_get_hijack(const pptrace_internal_binary* bin MAYBE_UNUSED,
                                                   const char* library MAYBE_UNUSED, void* lib MAYBE_UNUSED,
                                                   struct ezt_instrumented_function* function MAYBE_UNUSED,
						   zzt_symbol* functions_symbol, int index) {

  pptrace_internal_hijack* hijack = ALLOC(pptrace_internal_hijack);
  if (!hijack) {
    pptrace_error("Allocation failed");
    return NULL;
  }

  /* The goal of this function is to compute all the addresses needed for instrumenting a function
   * The name of the function to instrument (eg. foo) is in function->function_name
   * We need to compute:
   * - the address of foo in the application binary (app_foo)
   * - the address of foo in the eztrace library (ezt_foo)
   * - the address of the callback in eztrace library that will point to app_foo (callback_foo)
   */

  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO,
                "Searching for symbol %s\n", function->function_name);

  hijack->origSym = hijack->replSym = hijack->funSym = NULL;
  hijack->function = malloc(sizeof(char)*(strlen(function->function_name)+1));
  memcpy(hijack->function, function->function_name,sizeof(char)*(strlen(function->function_name)+1));
  /* search for the address of the function in the application (ie. search for app_foo) */
  hijack->funSym = get_symbol(bin->binary, function->function_name);
  
  if (!hijack->funSym) {
    pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO,
                  "Failed to find symbol %s in binary %s, ignoring!\n",
                  hijack->function, bin->name);
    pptrace_free_hijack(hijack);
    return NULL;
  }

  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO,
		"Symbol %s in the application: %p+%p (%p)\n", hijack->funSym->symbol_name,
		hijack->funSym->symbol_offset, hijack->funSym->section_addr,
		hijack->funSym->symbol_offset+ hijack->funSym->section_addr);

  /* search for the address of the function in the library (ie. search for ezt_foo)  */
  hijack->origSym = get_symbol(lib, hijack->function);
  if (!hijack->origSym) {
    pptrace_error("Failed to find symbol %s in binary %s", hijack->function, library);
    pptrace_free_hijack(hijack);
    return NULL;
  }

    pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO,
		  "Symbol %s in the library: %p+%p (%p)\n", hijack->origSym->symbol_name,
		  hijack->origSym->symbol_offset, hijack->origSym->section_addr,
		  hijack->origSym->symbol_offset+ hijack->origSym->section_addr);

  /* compute the address of the callback
   * the callback is stored in a struct ezt_instrument_function array
   */
  hijack->replSym = copy_symbol(functions_symbol);
  if(! hijack->replSym) {
    pptrace_error("Failed to allocate memory");
    pptrace_free_hijack(hijack);
    return NULL;
  }
  
  hijack->replSym->symbol_size = sizeof(void*);
  hijack->replSym->symbol_offset += (index * sizeof(struct ezt_instrumented_function))
    + ezt_offset_of(struct ezt_instrumented_function, callback);
  
  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO,
		"Callback %s in the library: %p+%p (%p)\n", hijack->replSym->symbol_name,
		hijack->replSym->symbol_offset, hijack->replSym->section_addr,
		hijack->replSym->symbol_offset+ hijack->replSym->section_addr);
  
  return hijack;
}

static void pptrace_free_hijacks(pptrace_internal_hijack** hijacks) {
  int i;
  if (!hijacks)
    return;
  for (i = 0; hijacks[i] != NULL; i++)
    pptrace_free_hijack(hijacks[i]);

  free(hijacks);
}

static pptrace_internal_hijack** pptrace_get_hijacks(const pptrace_internal_binary* bin,
                                                     const char* library, void* lib,
                                                     zzt_symbol* functions) {
  int i, j;
  void* bin_bin = open_binary(library);
  /* allocate an array and copy the symbol to it */
  struct ezt_instrumented_function * function_array = read_function_array(bin_bin, functions);
  int nb_functions;
  for (nb_functions = 0;
       strcmp(function_array[nb_functions].function_name, "") != 0;
       nb_functions++) {
  }

  pptrace_internal_hijack** hijacks = (pptrace_internal_hijack**)malloc(
      sizeof(pptrace_internal_hijack*) * (nb_functions + 1));

  if (!hijacks) {
    pptrace_error("Allocation failed");
    return NULL;
  }

  for (i = 0; i < nb_functions + 1; i++)
    hijacks[i] = NULL;

  for (i = j = 0; j < nb_functions; i++, j++) {
    hijacks[i] = pptrace_get_hijack(bin, library, lib, &function_array[j], functions, j);
    if (!hijacks[i])
      i--; // ignoring this line
  }
  return hijacks;
}

static void _pptrace_get_module_name(const char* libname, char* module_name, size_t buffer_size) {
  int libname_length=strlen(libname)+1;
  char tmp_buffer[libname_length];
  memcpy(tmp_buffer, libname, libname_length);
  memset(module_name, '\0', buffer_size);

  /* a libname looks like this: liba-b-c-d.so
   * first, find the last part of the lib name (eg. d.so)
   */
  char*saveptr = NULL;
  char* token = strtok_r(tmp_buffer, "-", &saveptr);
  while(token) {
    strncpy(module_name, token, buffer_size);
    token = strtok_r(NULL, "-", &saveptr);
  }

  /* now remove the suffix (".so" / ".dynlib") */
  char* suffix = strstr(module_name, "."DYNLIB_SUFFIX);
  if (suffix) {
    /* found the suffix, just insert a \0 to remove it */
    suffix[0]='\0';
  }
  return;
}

static pptrace_internal_hijack** pptrace_load_hijacks(const pptrace_internal_binary* bin MAYBE_UNUSED,
                                                      const char* library MAYBE_UNUSED, void* lib MAYBE_UNUSED) {
  void* bin_bin = open_binary(library);
  char module_name[1024];
  _pptrace_get_module_name(library, module_name, 1024);

  char hijack_symbol[1024];
  _get_pptrace_symbol_external(hijack_symbol, 1024, module_name);

  zzt_symbol* functions = get_symbol(bin_bin, hijack_symbol);
  if(!functions) {
    _get_pptrace_symbol_external(hijack_symbol, 1024, "");
    functions = get_symbol(bin_bin, hijack_symbol);
    if (!functions) {
      pptrace_error("%s library does not contain symbol %s, probably wrong library format",
                    library, hijack_symbol);
      close_binary(bin_bin);

      return NULL;
    }
  }

  close_binary(bin_bin);
  pptrace_internal_hijack** result = pptrace_get_hijacks(bin, library, lib, functions);
  free(functions);
  return result;
}
#endif // ENABLE_BINARY_INSTRUMENTATION

static void pptrace_free_library(pptrace_internal_library* lib) {
  if (!lib)
    return;

  free(lib->library);
#ifdef ENABLE_BINARY_INSTRUMENTATION
  pptrace_free_hijacks(lib->hijacks);
#endif // ENABLE_BINARY_INSTRUMENTATION
  free(lib);
}

static void pptrace_insert_library(pptrace_internal_binary* binary, pptrace_internal_library* lib) {
  lib->next = NULL;
  lib->prev = binary->last;
  if (binary->last) {
    binary->last->next = lib;
    binary->last = lib;
  } else {
    binary->first = binary->last = lib;
  }
}

#ifdef ENABLE_BINARY_INSTRUMENTATION
static void pptrace_remove_last_library(pptrace_internal_binary* binary) {
  if (binary->last) {
    pptrace_internal_library* lib = binary->last;
    binary->last = lib->prev;
    if (lib->prev) {
      lib->prev->next = NULL;
    }
    if (binary->first == lib)
      binary->first = NULL;
  }
}
#endif

int pptrace_load_module(void* bin, char* library) {
  int r = pptrace_add_preload(bin, library);
  if (r < 0)
    return r;

#ifdef ENABLE_BINARY_INSTRUMENTATION
  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Fetching hijacks of library %s\n", library);
  pptrace_internal_binary* binary = (pptrace_internal_binary*)bin;
  pptrace_internal_library* lib = binary->last;

  void* libr = open_binary(library);
  if (!binary) {
    pptrace_remove_last_library(binary);
    pptrace_free_library(lib);
    pptrace_error("Failed to open library %s", library);
    return -1;
  }
  lib->hijacks = pptrace_load_hijacks(binary, library, libr);
  if (!lib->hijacks) {
    pptrace_remove_last_library(binary);
    pptrace_free_library(lib);
    return -1;
  }
  close_binary(libr);
  lib->baseaddr = 0;

  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Hijacks of library %s fetched\n",
                library);
#endif
  return 0;
}

int pptrace_add_preload(void* bin, char* library) {
  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Loading library %s... ", library);
  pptrace_clear_error();
  if (!bin || !library) {
    pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "failed!\n");
    pptrace_error("Invalid argument");
    return -1;
  }
  pptrace_internal_binary* binary = (pptrace_internal_binary*)bin;
  pptrace_internal_library* lib = ALLOC(pptrace_internal_library);

  if (!lib) {
    pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "failed!\n");
    pptrace_error("Allocation failed");
    return -1;
  }

  lib->library = strdup(library);
  lib->baseaddr = -1;
  if (!(lib->library)) {
    free(lib);
    pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "failed!\n");
    pptrace_error("Allocation failed");
    return -1;
  }

#ifdef ENABLE_BINARY_INSTRUMENTATION
  lib->hijacks = NULL;
#endif // ENABLE_BINARY_INSTRUMENTATION
  pptrace_insert_library(binary, lib);
  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "ok\n");
  return 0;
}

static char* pptrace_create_preload(const pptrace_internal_library* first,
				    const char* var_name) {

  char* result = (char*)calloc(1024, sizeof(char));
  snprintf(result, 1024, "%s=", var_name);
  unsigned int alloc_size = 1024;
  unsigned int cur_size = strlen(result);

  while (first != NULL) {
    while (alloc_size - cur_size < strlen(first->library) + 2) {
      alloc_size += 1024;
      result = (char*)realloc(result, alloc_size);
    }
    
    strcpy(result + cur_size, first->library);
    cur_size += strlen(first->library);
    strcpy(result + cur_size, ":");
    cur_size++;
    first = first->next;
  }
  char* envpreload = getenv(var_name);
  if (envpreload != NULL) {
    while (alloc_size - cur_size < strlen(envpreload) + 1) {
      alloc_size += 1024;
      result = (char*)realloc(result, alloc_size);
    }
    strcpy(result + cur_size, envpreload);

  } else if (cur_size > 0) {
    cur_size--; // remove the trailing ':'
    result[cur_size] = 0;
  }
  return result;
}

/* preload the libraries, and load the application */
static pid_t pptrace_launch_preload(const char* path, char** argv, char** envp,
                                    const pptrace_internal_library* first, int debug) {
  // Adding LD_PRELOAD=libraries in envp
  int i, size = 0;
  while (envp[size] != NULL)
    size++;

  char** envp2 = (char**)malloc(sizeof(char*) * (size + 3));
  for (i = 0; i < size; i++)
    envp2[i] = envp[i];

  envp2[size] = pptrace_create_preload(first, "LD_PRELOAD");
  envp2[size + 1] = pptrace_create_preload(first, "OMP_TOOL_LIBRARIES");
  envp2[size + 2] = NULL;

  pptrace_debug(PPTRACE_DEBUG_LEVEL_DEBUG, "\nLD_PRELOAD is %s\n", envp2[size]);

  pid_t child = trace_run(path, argv, envp2, debug);
  free(envp2[size]);
  free(envp2);
  return child;
}

static void pptrace_free_binary(pptrace_internal_binary* binary) {
  if (!binary)
    return;

#ifdef ENABLE_BINARY_INSTRUMENTATION
  if (binary->binary) {
    close_binary(binary->binary);
  }
#endif

  while (binary->first != NULL) {
    pptrace_internal_library* lib = binary->first;
    binary->first = lib->next;
    pptrace_free_library(lib);
  }

  free(binary->name);
  free(binary);
}

/* search for the base address of binary (once it is loaded) and fill
 * the baseaddr field of the binary structure
 */
static int pptrace_get_base_address(pid_t child, pptrace_internal_binary* binary) {
  binary->baseaddr = 0;
  /* the binary is mapped at this address:
     grep "<binary->name>$" /proc/<child>/maps  |grep "r-xp"|cut -d'-' -f1
  */
  char cmd[4096];
  snprintf(cmd, 4096, "grep \"$(basename %s)$\" /proc/%d/maps  |grep \" r-xp \"|tr '-' ' '|cut  -d' ' -f1,5", binary->name, child);
  FILE* f = popen(cmd, "r");
  if (!f) {
    fprintf(stderr, "popen(%s) failed\n", cmd);
    return -1;
  }
  uint64_t offset;
  int fscanf_res = fscanf(f, "%lx %lx", &binary->baseaddr, &offset);
  pclose(f);
  if (fscanf_res != 2) {
    fprintf(stderr, "failed to read baseaddr from popen(%s)\n", cmd);
    return -1;
  }
  binary->baseaddr -= offset;
  pptrace_debug(PPTRACE_DEBUG_LEVEL_VERBOSE,
                "Binary base address: %p\n", binary->baseaddr);
  return 0;
}

#ifdef ENABLE_BINARY_INSTRUMENTATION

static bool filter_proc_maps_entry_read_exec(struct Maps_entry const* map_entry) {
  return maps_entry_filter_permissions(map_entry, "rx");
}

static int wait_for_libraries(pid_t pid, pptrace_internal_binary const* binary) {
  assert(binary != NULL);

  char lib_name[254];
  lib_name[0] = ':';

  char libs_list[8192];
  if (get_mapped_file_paths_in_proc_pid_maps(pid, libs_list, sizeof(libs_list),
                                             &filter_proc_maps_entry_read_exec))
    return -1;

  const pptrace_internal_library* current_lib = binary->first;
  for (; current_lib != NULL; current_lib = current_lib->next) {
    while (true) {
      /* for libPatate.so, get ":libPatate.so:" in lib_name */
      char * dest = &lib_name[1]; // skip the first ':'
      for (char const* src_name = current_lib->library; *src_name != '\0'; ++src_name, ++dest)
        *dest = *src_name;
      dest[0] = ':';
      dest[1] = '\0';

      /* Search for ":libPatate.so:" in libs_list */
      char const* loaded_lib = strstr(libs_list, lib_name);
      if (loaded_lib != NULL)
        break;

      /* If ":libPatate.so:" is not found, wait for a mmap and reload /proc/self/maps */
      word_uint baseaddr = 0;
      trace_wait_syscall(pid, &baseaddr, MMAP_SYSCALLS,
                         SYSCALL_ARGTYPE_IGNORE, SYSCALL_ARGTYPE_IGNORE,
                         SYSCALL_ARGTYPE_INT, (word_uint)(PROT_READ | PROT_EXEC),
                         SYSCALL_ARGTYPE_IGNORE,
                         SYSCALL_ARGTYPE_IGNORE, // TODO ignore les ignore ?
                         SYSCALL_ARGTYPE_IGNORE,
                         SYSCALL_ARGTYPE_END); // TODO can fail : if (baseaddr == 0 || r < 0) return -1 ?
      if (get_mapped_file_paths_in_proc_pid_maps(pid, libs_list, sizeof(libs_list),
                                                 &filter_proc_maps_entry_read_exec))
        return -1;
    }
  }
  return 0;
}

static int get_libraries_addresses(pid_t pid, pptrace_internal_binary* binary) {
  int res = 0;

  struct Maps_entry* maps_entries = NULL;
  int const maps_entries_count = get_entries_in_proc_pid_maps(pid, &maps_entries, &filter_proc_maps_entry_read_exec);
  if (maps_entries_count < 0) {
    res = -1;
    goto exit;
  }

  pptrace_internal_library* current_lib = binary->first;
  for (; current_lib != NULL; current_lib = current_lib->next) {
    for (int i = 0; i < maps_entries_count; ++i) {
      if (strcmp(current_lib->library, maps_entries[i].file) == 0) {
	current_lib->baseaddr = (zzt_word)maps_entries[i].stack_base_addr - maps_entries[i].offset;

	pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO,
		      "%s  (%s) is located at %p\n",
		      current_lib->library,
		      maps_entries[i].permissions,
		      current_lib->baseaddr);
        continue;
      }
    }
    if (current_lib->baseaddr == 0) {
      res = -1;
      goto exit;
    }
  }

exit:
  free(maps_entries);
  return res;
}

/* Install all the detected hijacks into the child process.
 * Return the number of symbols that were successfully installed
 * or -1 if an error occured.
 */
static int pptrace_install_hijacks(pid_t child MAYBE_UNUSED, const pptrace_internal_binary* binary MAYBE_UNUSED) {
  int nb_installed = 0;
  int i;
  pptrace_internal_library* lib;

#if DEBUG
  char cmd[4096];
  sprintf(cmd, "cat /proc/%d/maps", child);
  system(cmd);
#endif

  /* Wait for the application start. In _eztrace_init (cf eztrace_core.c), a SIGUSR2 signal is sent
   * so that we synchronize the two processes.
   */
  trace_wait_signal(child, SIGUSR2);
  for (lib = binary->first; lib != NULL; lib = lib->next) {
    if (lib->hijacks) {
      for (i = 0; lib->hijacks[i] != NULL; i++) {
        if (lib->hijacks[i]->funSym != NULL && lib->hijacks[i]->funSym->symbol_offset) {

          if (lib->hijacks[i]->funSym->flags & ZZT_FLAG_DYNAMIC) {
            /* the symbol is in a relocated binary (ie. compiled with
	     * -fPIE. We need to shift the base address.
	     */
            lib->hijacks[i]->funSym->section_addr = lib->hijacks[i]->funSym->section_addr + binary->baseaddr;

	    lib->hijacks[i]->origSym->section_addr += lib->baseaddr;
	    lib->hijacks[i]->replSym->section_addr += lib->baseaddr;
          }

          // Replacing symbol only when the symbol is not in a dynamic library
          if (hijack(binary->binary, child, lib->hijacks[i]->funSym,
                     lib->hijacks[i]->origSym, lib->hijacks[i]->replSym) < 0) {
            fprintf(stderr, "Failed to install hijack of symbol %s\n",
                    lib->hijacks[i]->function);
          } else {
            nb_installed++;
            pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO,
                          "Function %s instrumented.\n",
                          lib->hijacks[i]->function);
          }
        }
      }
    }
  }
  return nb_installed;
}
#endif

int pptrace_run(void* bin, char** argv, char** envp) {
  pptrace_clear_error();

  pptrace_internal_binary* binary = (pptrace_internal_binary*)bin;
  if (binary == NULL) {
    pptrace_error("Invalid argument");
    goto early_error_occured;
  }

#ifdef ENABLE_BINARY_INSTRUMENTATION
  trace_set_bits(get_binary_bits(binary->binary));
#endif

  /* Running binary */
  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Running binary %s... ", binary->name);
  pid_t child = pptrace_launch_preload(binary->name, argv, envp, binary->first,
                                       (binary->debugger != NULL) ? 1 : 0);
  if (child <= 0) {
    pptrace_error("Failed to run binary %s", binary->name);
    goto early_error_occured;
  }
  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "ok (pid = %d)\n", child);

  /* Getting the binary base address */
  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Getting the binary base address\n");
  if (pptrace_get_base_address(child, binary)) {
    pptrace_error("Failed to get the base address of the binary, exiting after detaching child process...");
    goto error_occured;
  }

#ifdef ENABLE_BINARY_INSTRUMENTATION
  /* Waiting all the library where loaded */
  if (wait_for_libraries(child, binary)) {
    pptrace_error( "Failed to wait all libraries where loaded");
    goto error_occured;
  }

  /* Getting libraries addresses */
  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Getting libraries' addresses\n");
  if (get_libraries_addresses(child, binary)) {
    pptrace_error( "Failed to get libraries addresses");
    goto error_occured;
  }

  /* Installing hijacks */
  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Installing hijacks\n");
  if (pptrace_install_hijacks(child, binary) < 0) {
    pptrace_error( "Failed to install hijacks, exiting after detaching child process...");
    goto error_occured;
  }
#endif

  /* if a debugger is present, give the control to the debugger */
  if (binary->debugger != NULL) {
    pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Giving the control to the debugger %s\n", binary->debugger[0]);
    char pid[10];
    snprintf(pid, 9, "%d", child);
    for (int i = 0; binary->debugger[i] != NULL; i++) {
      if (strcmp(binary->debugger[i], "{pid}") == 0)
        binary->debugger[i] = pid;
      else if (strcmp(binary->debugger[i], "{name}") == 0)
        binary->debugger[i] = binary->name;
    }
    char* debugger = get_program_path(binary->debugger[0]);
    if (debugger == NULL){
      pptrace_fubar("debugger %s was not found!", binary->debugger[0]); // calls exit(-1)
    }
    trace_detach(child);
    execve(debugger, binary->debugger, envp);
    pptrace_fubar("failed to launch debugger!"); // calls exit(-1)
  }

  /* if no debugger, free the binary, detach and wait the end of the process */
  pptrace_free_binary(binary);
  pptrace_debug(PPTRACE_DEBUG_LEVEL_INFO, "Detaching and waiting the end of the process\n");
  trace_detach(child);
  trace_wait(child);
  return 0;

  /* Error management */
error_occured:
  trace_detach(child);
early_error_occured:
  pptrace_free_binary(binary);
  return -1;
}
