about summary refs log tree commit diff
path: root/frida_mode/src/instrument/instrument_coverage.c
diff options
context:
space:
mode:
Diffstat (limited to 'frida_mode/src/instrument/instrument_coverage.c')
-rw-r--r--frida_mode/src/instrument/instrument_coverage.c897
1 files changed, 897 insertions, 0 deletions
diff --git a/frida_mode/src/instrument/instrument_coverage.c b/frida_mode/src/instrument/instrument_coverage.c
new file mode 100644
index 00000000..46c816bc
--- /dev/null
+++ b/frida_mode/src/instrument/instrument_coverage.c
@@ -0,0 +1,897 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include "frida-gumjs.h"
+
+#include "debug.h"
+
+#include "instrument.h"
+#include "util.h"
+
+char *instrument_coverage_filename = NULL;
+
+static int normal_coverage_fd = -1;
+static int normal_coverage_pipes[2] = {-1, -1};
+
+static int unstable_coverage_fd = -1;
+static int unstable_coverage_pipes[2] = {-1, -1};
+
+static uint64_t normal_coverage_last_start = 0;
+static gchar *  unstable_coverage_fuzzer_stats = NULL;
+
+typedef struct {
+
+  GumAddress base_address;
+  GumAddress limit;
+  gsize      size;
+  char       path[PATH_MAX + 1];
+  guint64    offset;
+  gboolean   is_executable;
+  guint      count;
+  guint16    id;
+
+} coverage_range_t;
+
+typedef struct {
+
+  uint64_t          start;
+  uint64_t          end;
+  coverage_range_t *module;
+
+} normal_coverage_data_t;
+
+typedef struct {
+
+  guint64 edge;
+  guint64 from;
+  guint64 from_end;
+  guint64 to;
+  guint64 to_end;
+
+} unstable_coverage_data_t;
+
+typedef struct {
+
+  GArray *modules;
+  guint   count;
+
+} coverage_mark_ctx_t;
+
+typedef struct {
+
+  guint32 offset;
+  guint16 length;
+  guint16 module;
+
+} coverage_event_t;
+
+static gboolean coverage_range(const GumRangeDetails *details,
+                               gpointer               user_data) {
+
+  GArray *         coverage_ranges = (GArray *)user_data;
+  coverage_range_t coverage = {0};
+
+  if (details->file == NULL) { return TRUE; }
+  if (details->protection == GUM_PAGE_NO_ACCESS) { return TRUE; }
+
+  coverage.base_address = details->range->base_address;
+  coverage.size = details->range->size;
+  coverage.limit = coverage.base_address + coverage.size;
+
+  strncpy(coverage.path, details->file->path, PATH_MAX);
+  coverage.offset = details->file->offset;
+
+  if ((details->protection & GUM_PAGE_EXECUTE) == 0) {
+
+    coverage.is_executable = false;
+
+  } else {
+
+    coverage.is_executable = true;
+
+  }
+
+  coverage.count = 0;
+  coverage.id = 0;
+
+  g_array_append_val(coverage_ranges, coverage);
+  return TRUE;
+
+}
+
+static gint coverage_sort(gconstpointer a, gconstpointer b) {
+
+  coverage_range_t *ma = (coverage_range_t *)a;
+  coverage_range_t *mb = (coverage_range_t *)b;
+
+  if (ma->base_address < mb->base_address) return -1;
+
+  if (ma->base_address > mb->base_address) return 1;
+
+  return 0;
+
+}
+
+void instrument_coverage_print(char *format, ...) {
+
+  char buffer[4096] = {0};
+  int  len;
+
+  va_list ap;
+  va_start(ap, format);
+
+  if (vsnprintf(buffer, sizeof(buffer) - 1, format, ap) < 0) { return; }
+
+  len = strnlen(buffer, sizeof(buffer));
+  IGNORED_RETURN(write(STDOUT_FILENO, buffer, len));
+  va_end(ap);
+
+}
+
+static GArray *coverage_get_ranges(void) {
+
+  instrument_coverage_print("Coverage - Collecting ranges\n");
+
+  GArray *coverage_ranges =
+      g_array_sized_new(false, false, sizeof(coverage_range_t), 100);
+  gum_process_enumerate_ranges(GUM_PAGE_NO_ACCESS, coverage_range,
+                               coverage_ranges);
+  g_array_sort(coverage_ranges, coverage_sort);
+
+  for (guint i = 0; i < coverage_ranges->len; i++) {
+
+    coverage_range_t *range =
+        &g_array_index(coverage_ranges, coverage_range_t, i);
+    instrument_coverage_print("Coverage Range - %3u: 0x%016" G_GINT64_MODIFIER
+                              "X - 0x%016" G_GINT64_MODIFIER "X (%s)\n",
+                              i, range->base_address, range->limit,
+                              range->path);
+
+  }
+
+  return coverage_ranges;
+
+}
+
+static GArray *coverage_get_modules(void) {
+
+  instrument_coverage_print("Coverage - Collecting modules\n");
+  GArray *coverage_ranges = coverage_get_ranges();
+  GArray *coverage_modules =
+      g_array_sized_new(false, false, sizeof(coverage_range_t), 100);
+
+  coverage_range_t current = {0};
+
+  for (guint i = 0; i < coverage_ranges->len; i++) {
+
+    coverage_range_t *range =
+        &g_array_index(coverage_ranges, coverage_range_t, i);
+
+    if (range->offset == 0 ||
+        (strncmp(range->path, current.path, PATH_MAX) != 0)) {
+
+      if (current.is_executable) {
+
+        g_array_append_val(coverage_modules, current);
+        memset(&current, '\0', sizeof(coverage_range_t));
+
+      }
+
+      memcpy(&current, range, sizeof(coverage_range_t));
+
+    } else {
+
+      current.limit = range->limit;
+      current.size = current.limit - current.base_address;
+      if (range->is_executable) { current.is_executable = true; }
+
+    }
+
+  }
+
+  if (current.is_executable) { g_array_append_val(coverage_modules, current); }
+  g_array_free(coverage_ranges, TRUE);
+
+  for (guint i = 0; i < coverage_modules->len; i++) {
+
+    coverage_range_t *module =
+        &g_array_index(coverage_modules, coverage_range_t, i);
+    instrument_coverage_print("Coverage Module - %3u: 0x%016" G_GINT64_MODIFIER
+                              "X - 0x%016" G_GINT64_MODIFIER "X (%s)\n",
+                              i, module->base_address, module->limit,
+                              module->path);
+
+  }
+
+  return coverage_modules;
+
+}
+
+static void instrument_coverage_mark(void *key, void *value, void *user_data) {
+
+  UNUSED_PARAMETER(key);
+  coverage_mark_ctx_t *   ctx = (coverage_mark_ctx_t *)user_data;
+  GArray *                coverage_modules = ctx->modules;
+  normal_coverage_data_t *val = (normal_coverage_data_t *)value;
+  guint                   i;
+
+  for (i = 0; i < coverage_modules->len; i++) {
+
+    coverage_range_t *module =
+        &g_array_index(coverage_modules, coverage_range_t, i);
+    if (val->start > module->limit) continue;
+
+    if (val->end >= module->limit) break;
+
+    val->module = module;
+    ctx->count = ctx->count + 1;
+    module->count++;
+    return;
+
+  }
+
+  instrument_coverage_print(
+      "Coverage cannot find module for: 0x%016" G_GINT64_MODIFIER
+      "X - 0x%016" G_GINT64_MODIFIER "X\n",
+      val->start, val->end);
+
+}
+
+static void coverage_write(void *data, size_t size) {
+
+  ssize_t written;
+  size_t  remain = size;
+
+  for (char *cursor = (char *)data; remain > 0;
+       remain -= written, cursor += written) {
+
+    written = write(normal_coverage_fd, cursor, remain);
+
+    if (written < 0) {
+
+      FATAL("Coverage - Failed to write: %s (%d)\n", (char *)data, errno);
+
+    }
+
+  }
+
+}
+
+static void coverage_format(char *format, ...) {
+
+  va_list ap;
+  char    buffer[4096] = {0};
+  int     ret;
+  int     len;
+
+  va_start(ap, format);
+  ret = vsnprintf(buffer, sizeof(buffer) - 1, format, ap);
+  va_end(ap);
+
+  if (ret < 0) { return; }
+
+  len = strnlen(buffer, sizeof(buffer));
+
+  coverage_write(buffer, len);
+
+}
+
+static void coverage_write_modules(GArray *coverage_modules) {
+
+  guint emitted = 0;
+  for (guint i = 0; i < coverage_modules->len; i++) {
+
+    coverage_range_t *module =
+        &g_array_index(coverage_modules, coverage_range_t, i);
+    if (module->count == 0) continue;
+
+    coverage_format("%3u, ", emitted);
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", module->base_address);
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", module->limit);
+    /* entry */
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", 0);
+    /* checksum */
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", 0);
+    /* timestamp */
+    coverage_format("%08" G_GINT32_MODIFIER "X, ", 0);
+    coverage_format("%s\n", module->path);
+    emitted++;
+
+  }
+
+}
+
+static void coverage_write_events(void *key, void *value, void *user_data) {
+
+  UNUSED_PARAMETER(key);
+  UNUSED_PARAMETER(user_data);
+  normal_coverage_data_t *val = (normal_coverage_data_t *)value;
+
+  if (val->module == NULL) { return; }
+
+  coverage_event_t evt = {
+
+      .offset = val->start - val->module->base_address,
+      .length = val->end - val->start,
+      .module = val->module->id,
+
+  };
+
+  coverage_write(&evt, sizeof(coverage_event_t));
+
+}
+
+static void coverage_write_header(guint coverage_marked_modules) {
+
+  char version[] = "DRCOV VERSION: 2\n";
+  char flavour[] = "DRCOV FLAVOR: frida\n";
+  char columns[] = "Columns: id, base, end, entry, checksum, timestamp, path\n";
+  coverage_write(version, sizeof(version) - 1);
+  coverage_write(flavour, sizeof(flavour) - 1);
+  coverage_format("Module Table: version 2, count %u\n",
+                  coverage_marked_modules);
+  coverage_write(columns, sizeof(columns) - 1);
+
+}
+
+static guint coverage_mark_modules(GArray *coverage_modules) {
+
+  guint coverage_marked_modules = 0;
+  guint i;
+  for (i = 0; i < coverage_modules->len; i++) {
+
+    coverage_range_t *module =
+        &g_array_index(coverage_modules, coverage_range_t, i);
+
+    instrument_coverage_print(
+        "Coverage Module - %3u: [%c] 0x%016" G_GINT64_MODIFIER
+        "X - 0x%016" G_GINT64_MODIFIER "X [%u] (%u:%s)\n",
+        i, module->count == 0 ? ' ' : 'X', module->base_address, module->limit,
+        module->count, module->id, module->path);
+
+    if (module->count == 0) { continue; }
+
+    module->id = coverage_marked_modules;
+    coverage_marked_modules++;
+
+  }
+
+  return coverage_marked_modules;
+
+}
+
+static void instrument_coverage_normal_run() {
+
+  int                     bytes;
+  normal_coverage_data_t  data;
+  normal_coverage_data_t *value;
+  instrument_coverage_print("Coverage - Running\n");
+
+  if (close(normal_coverage_pipes[STDOUT_FILENO]) != 0) {
+
+    FATAL("Failed to close parent read pipe");
+
+  }
+
+  GHashTable *coverage_hash =
+      g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
+  if (coverage_hash == NULL) {
+
+    FATAL("Failed to g_hash_table_new, errno: %d", errno);
+
+  }
+
+  for (bytes = read(normal_coverage_pipes[STDIN_FILENO], &data,
+                    sizeof(normal_coverage_data_t));
+       bytes == sizeof(normal_coverage_data_t);
+       bytes = read(normal_coverage_pipes[STDIN_FILENO], &data,
+                    sizeof(normal_coverage_data_t))) {
+
+    value =
+        (normal_coverage_data_t *)gum_malloc0(sizeof(normal_coverage_data_t));
+    memcpy(value, &data, sizeof(normal_coverage_data_t));
+    g_hash_table_insert(coverage_hash, GSIZE_TO_POINTER(data.start), value);
+
+  }
+
+  if (bytes != 0) { FATAL("Coverage data truncated"); }
+
+  instrument_coverage_print("Coverage - Preparing\n");
+
+  GArray *coverage_modules = coverage_get_modules();
+
+  guint size = g_hash_table_size(coverage_hash);
+  instrument_coverage_print("Coverage - Total Entries: %u\n", size);
+
+  coverage_mark_ctx_t ctx = {.modules = coverage_modules, .count = 0};
+
+  g_hash_table_foreach(coverage_hash, instrument_coverage_mark, &ctx);
+  instrument_coverage_print("Coverage - Marked Entries: %u\n", ctx.count);
+
+  guint coverage_marked_modules = coverage_mark_modules(coverage_modules);
+  instrument_coverage_print("Coverage - Marked Modules: %u\n",
+                            coverage_marked_modules);
+
+  coverage_write_header(coverage_marked_modules);
+  coverage_write_modules(coverage_modules);
+  coverage_format("BB Table: %u bbs\n", ctx.count);
+  g_hash_table_foreach(coverage_hash, coverage_write_events, NULL);
+
+  g_hash_table_unref(coverage_hash);
+
+  instrument_coverage_print("Coverage - Completed\n");
+
+}
+
+static GArray *instrument_coverage_unstable_read_unstable_ids(void) {
+
+  gchar * contents = NULL;
+  gsize   length = 0;
+  GArray *unstable_edge_ids =
+      g_array_sized_new(false, false, sizeof(gpointer), 100);
+
+  if (!g_file_get_contents(unstable_coverage_fuzzer_stats, &contents, &length,
+                           NULL)) {
+
+    FATAL("Failed to read fuzzer_stats");
+
+  }
+
+  instrument_coverage_print("\n");
+  instrument_coverage_print("Unstable coverage stats:\n");
+  instrument_coverage_print("========================\n");
+  instrument_coverage_print("%s\n", contents);
+  instrument_coverage_print("\n");
+
+  gchar **lines = g_strsplit(contents, "\n", -1);
+  gchar **values = NULL;
+
+  for (guint i = 0; lines[i] != NULL; i++) {
+
+    gchar **fields = g_strsplit(lines[i], ":", 2);
+    if (fields[0] == NULL) {
+
+      g_strfreev(fields);
+      continue;
+
+    }
+
+    g_strstrip(fields[0]);
+    if (g_strcmp0(fields[0], "var_bytes") != 0) {
+
+      g_strfreev(fields);
+      continue;
+
+    }
+
+    if (fields[1] == NULL) {
+
+      g_strfreev(fields);
+      continue;
+
+    }
+
+    g_strstrip(fields[1]);
+    values = g_strsplit(fields[1], " ", -1);
+    g_strfreev(fields);
+
+    break;
+
+  }
+
+  if (values == NULL) {
+
+    instrument_coverage_print(
+        "Failed to find var_bytes, did you set AFL_DEBUG?\n");
+
+  }
+
+  for (guint i = 0; values[i] != NULL; i++) {
+
+    g_strstrip(values[i]);
+    gpointer val = GSIZE_TO_POINTER(g_ascii_strtoull(values[i], NULL, 10));
+    g_array_append_val(unstable_edge_ids, val);
+
+  }
+
+  g_strfreev(values);
+  g_strfreev(lines);
+  g_free(contents);
+
+  for (guint i = 0; i < unstable_edge_ids->len; i++) {
+
+    gpointer *id = &g_array_index(unstable_edge_ids, gpointer, i);
+
+    instrument_coverage_print("Unstable edge (%10u): %" G_GINT64_MODIFIER "u\n",
+                              i, GPOINTER_TO_SIZE(*id));
+
+  }
+
+  return unstable_edge_ids;
+
+}
+
+static GHashTable *instrument_collect_unstable_blocks(
+    GHashTable *unstable_coverage_hash, GArray *unstable_edge_ids) {
+
+  GHashTable *unstable_blocks =
+      g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
+
+  for (guint i = 0; i < unstable_edge_ids->len; i++) {
+
+    gpointer *id = &g_array_index(unstable_edge_ids, gpointer, i);
+
+    GHashTable *child =
+        (GHashTable *)g_hash_table_lookup(unstable_coverage_hash, *id);
+
+    if (child == NULL) { FATAL("Failed to find edge ID"); }
+
+    GHashTableIter iter = {0};
+    gpointer       value;
+    g_hash_table_iter_init(&iter, child);
+    while (g_hash_table_iter_next(&iter, NULL, &value)) {
+
+      unstable_coverage_data_t *unstable = (unstable_coverage_data_t *)value;
+      normal_coverage_data_t *  from =
+          gum_malloc0(sizeof(normal_coverage_data_t));
+      normal_coverage_data_t *to = gum_malloc0(sizeof(normal_coverage_data_t));
+      from->start = unstable->from;
+      from->end = unstable->from_end;
+      from->module = NULL;
+
+      to->start = unstable->to;
+      to->end = unstable->to_end;
+      to->module = NULL;
+
+      g_hash_table_insert(unstable_blocks, GSIZE_TO_POINTER(from->start), from);
+      g_hash_table_insert(unstable_blocks, GSIZE_TO_POINTER(to->start), to);
+
+    }
+
+  }
+
+  return unstable_blocks;
+
+}
+
+static void instrument_coverage_unstable_run(void) {
+
+  int                       bytes;
+  unstable_coverage_data_t  data;
+  unstable_coverage_data_t *value;
+  instrument_coverage_print("Unstable coverage - Running\n");
+
+  if (close(unstable_coverage_pipes[STDOUT_FILENO]) != 0) {
+
+    FATAL("Failed to close parent read pipe");
+
+  }
+
+  GHashTable *unstable_coverage_hash = g_hash_table_new_full(
+      g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_hash_table_unref);
+  if (unstable_coverage_hash == NULL) {
+
+    FATAL("Failed to g_hash_table_new, errno: %d", errno);
+
+  }
+
+  guint edges = 0;
+
+  for (bytes = read(unstable_coverage_pipes[STDIN_FILENO], &data,
+                    sizeof(unstable_coverage_data_t));
+       bytes == sizeof(unstable_coverage_data_t);
+       bytes = read(unstable_coverage_pipes[STDIN_FILENO], &data,
+                    sizeof(unstable_coverage_data_t))) {
+
+    value = (unstable_coverage_data_t *)gum_malloc0(
+        sizeof(unstable_coverage_data_t));
+    memcpy(value, &data, sizeof(unstable_coverage_data_t));
+
+    gpointer hash_value = g_hash_table_lookup(unstable_coverage_hash,
+                                              GSIZE_TO_POINTER(value->edge));
+    if (hash_value == NULL) {
+
+      hash_value =
+          g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
+
+      if (!g_hash_table_insert(unstable_coverage_hash,
+                               GSIZE_TO_POINTER(value->edge), hash_value)) {
+
+        FATAL("Entry already in hashtable");
+
+      }
+
+    }
+
+    if (g_hash_table_insert(hash_value, GSIZE_TO_POINTER(value->from), value)) {
+
+      edges++;
+
+    }
+
+  }
+
+  if (bytes != 0) { FATAL("Unstable coverage data truncated"); }
+
+  instrument_coverage_print("Coverage - Preparing\n");
+
+  GArray *coverage_modules = coverage_get_modules();
+
+  instrument_coverage_print("Found edges: %u\n", edges);
+
+  GArray *unstable_edge_ids = instrument_coverage_unstable_read_unstable_ids();
+
+  GHashTable *unstable_blocks = instrument_collect_unstable_blocks(
+      unstable_coverage_hash, unstable_edge_ids);
+
+  guint size = g_hash_table_size(unstable_blocks);
+  instrument_coverage_print("Unstable blocks: %u\n", size);
+
+  coverage_mark_ctx_t ctx = {.modules = coverage_modules, .count = 0};
+
+  g_hash_table_foreach(unstable_blocks, instrument_coverage_mark, &ctx);
+  instrument_coverage_print("Coverage - Marked Entries: %u\n", ctx.count);
+
+  guint coverage_marked_modules = coverage_mark_modules(coverage_modules);
+  instrument_coverage_print("Coverage - Marked Modules: %u\n",
+                            coverage_marked_modules);
+
+  coverage_write_header(coverage_marked_modules);
+  coverage_write_modules(coverage_modules);
+  coverage_format("BB Table: %u bbs\n", ctx.count);
+  g_hash_table_foreach(unstable_blocks, coverage_write_events, NULL);
+
+  g_hash_table_unref(unstable_blocks);
+  g_array_free(unstable_edge_ids, TRUE);
+  g_hash_table_unref(unstable_coverage_hash);
+
+  instrument_coverage_print("Coverage - Completed\n");
+
+}
+
+void instrument_coverage_config(void) {
+
+  instrument_coverage_filename = getenv("AFL_FRIDA_INST_COVERAGE_FILE");
+
+}
+
+void instrument_coverage_normal_init(void) {
+
+  OKF("Coverage - enabled [%c]",
+      instrument_coverage_filename == NULL ? ' ' : 'X');
+
+  if (instrument_coverage_filename == NULL) { return; }
+
+  OKF("Coverage - file [%s]", instrument_coverage_filename);
+
+  char *path = g_canonicalize_filename(instrument_coverage_filename,
+                                       g_get_current_dir());
+
+  OKF("Coverage - path [%s]", path);
+
+  normal_coverage_fd = open(path, O_RDWR | O_CREAT | O_TRUNC,
+                            S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+
+  if (normal_coverage_fd < 0) {
+
+    FATAL("Failed to open coverage file '%s'", path);
+
+  }
+
+  g_free(path);
+
+  if (pipe(normal_coverage_pipes) != 0) { FATAL("Failed to create pipes"); }
+
+  pid_t pid = fork();
+  if (pid == -1) { FATAL("Failed to start coverage process"); }
+
+  if (pid == 0) {
+
+    instrument_coverage_normal_run();
+    kill(getpid(), SIGKILL);
+    _exit(0);
+
+  }
+
+  if (close(normal_coverage_fd) < 0) {
+
+    FATAL("Failed to close coverage output file");
+
+  }
+
+  if (close(normal_coverage_pipes[STDIN_FILENO]) != 0) {
+
+    FATAL("Failed to close parent read pipe");
+
+  }
+
+}
+
+void instrument_coverage_unstable_find_output(void) {
+
+  pid_t  parent = getpid();
+  gchar *fds_name = g_strdup_printf("/proc/%d/fd/", getppid());
+
+  gchar *root = g_file_read_link("/proc/self/root", NULL);
+  if (root == NULL) { FATAL("Failed to read link"); }
+
+  GDir *dir = g_dir_open(fds_name, 0, NULL);
+
+  OKF("Coverage Unstable - fds: %s", fds_name);
+
+  for (const gchar *filename = g_dir_read_name(dir); filename != NULL;
+       filename = g_dir_read_name(dir)) {
+
+    gchar *fullname = g_build_path("/", fds_name, filename, NULL);
+
+    gchar *link = g_file_read_link(fullname, NULL);
+    if (link == NULL) { FATAL("Failed to read link: %s", fullname); }
+
+    gchar *basename = g_path_get_basename(link);
+    if (g_strcmp0(basename, "default") != 0) {
+
+      g_free(basename);
+      g_free(link);
+      g_free(fullname);
+      continue;
+
+    }
+
+    gchar *relative = NULL;
+    size_t root_len = strnlen(root, PATH_MAX);
+    if (g_str_has_suffix(link, root)) {
+
+      relative = g_build_path("/", &link[root_len], NULL);
+
+    } else {
+
+      relative = g_build_path("/", link, NULL);
+
+    }
+
+    gchar *cmdline = g_build_path("/", relative, "cmdline", NULL);
+    if (!g_file_test(cmdline, G_FILE_TEST_EXISTS)) {
+
+      g_free(cmdline);
+      g_free(basename);
+      g_free(relative);
+      g_free(link);
+      g_free(fullname);
+      continue;
+
+    }
+
+    unstable_coverage_fuzzer_stats =
+        g_build_path("/", relative, "fuzzer_stats", NULL);
+    g_free(cmdline);
+    g_free(basename);
+    g_free(relative);
+    g_free(link);
+    g_free(fullname);
+    break;
+
+  }
+
+  g_dir_close(dir);
+  g_free(fds_name);
+
+  if (unstable_coverage_fuzzer_stats == NULL) {
+
+    FATAL("Failed to find fuzzer stats");
+
+  }
+
+  OKF("Fuzzer stats: %s", unstable_coverage_fuzzer_stats);
+
+}
+
+void instrument_coverage_unstable_init(void) {
+
+  if (instrument_coverage_unstable_filename == NULL) { return; }
+
+  char *path = g_canonicalize_filename(instrument_coverage_unstable_filename,
+                                       g_get_current_dir());
+
+  OKF("Coverage - unstable path [%s]", instrument_coverage_unstable_filename);
+
+  unstable_coverage_fd = open(path, O_RDWR | O_CREAT | O_TRUNC,
+                              S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+
+  if (unstable_coverage_fd < 0) {
+
+    FATAL("Failed to open unstable coverage file '%s'", path);
+
+  }
+
+  g_free(path);
+
+  instrument_coverage_unstable_find_output();
+
+  if (pipe(unstable_coverage_pipes) != 0) {
+
+    FATAL("Failed to create unstable pipes");
+
+  }
+
+  pid_t pid = fork();
+  if (pid == -1) { FATAL("Failed to start coverage process"); }
+
+  if (pid == 0) {
+
+    instrument_coverage_unstable_run();
+    kill(getpid(), SIGKILL);
+    _exit(0);
+
+  }
+
+  if (close(unstable_coverage_fd) < 0) {
+
+    FATAL("Failed to close unstable coverage output file");
+
+  }
+
+  if (close(unstable_coverage_pipes[STDIN_FILENO]) != 0) {
+
+    FATAL("Failed to close parent read pipe");
+
+  }
+
+}
+
+void instrument_coverage_init(void) {
+
+  instrument_coverage_normal_init();
+  instrument_coverage_unstable_init();
+
+}
+
+void instrument_coverage_start(uint64_t address) {
+
+  if (instrument_coverage_filename == NULL) { return; }
+
+  normal_coverage_last_start = address;
+
+}
+
+void instrument_coverage_end(uint64_t address) {
+
+  if (instrument_coverage_filename == NULL) { return; }
+
+  normal_coverage_data_t data = {
+
+      .start = normal_coverage_last_start, .end = address, .module = NULL};
+
+  if (write(normal_coverage_pipes[STDOUT_FILENO], &data,
+            sizeof(normal_coverage_data_t)) != sizeof(normal_coverage_data_t)) {
+
+    FATAL("Coverage I/O error");
+
+  }
+
+}
+
+void instrument_coverage_unstable(guint64 edge, guint64 previous_rip,
+                                  guint64 previous_end, guint64 current_rip,
+                                  guint64 current_end) {
+
+  if (instrument_coverage_unstable_filename == NULL) { return; }
+  unstable_coverage_data_t data = {
+
+      .edge = edge,
+      .from = previous_rip,
+      .from_end = previous_end,
+      .to = current_rip,
+      .to_end = current_end};
+
+  if (write(unstable_coverage_pipes[STDOUT_FILENO], &data,
+            sizeof(unstable_coverage_data_t)) !=
+      sizeof(unstable_coverage_data_t)) {
+
+    FATAL("Unstable coverage I/O error");
+
+  }
+
+}
+