about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--frida_mode/GNUmakefile11
-rw-r--r--frida_mode/README.md125
-rw-r--r--frida_mode/Scripting.md8
-rw-r--r--frida_mode/frida.map2
-rw-r--r--frida_mode/include/entry.h3
-rw-r--r--frida_mode/include/prefetch.h1
-rw-r--r--frida_mode/include/ranges.h2
-rw-r--r--frida_mode/include/stalker.h4
-rw-r--r--frida_mode/include/stats.h56
-rw-r--r--frida_mode/src/entry.c10
-rw-r--r--frida_mode/src/instrument/instrument.c8
-rw-r--r--frida_mode/src/instrument/instrument_coverage.c5
-rw-r--r--frida_mode/src/js/api.js21
-rw-r--r--frida_mode/src/js/js_api.c22
-rw-r--r--frida_mode/src/persistent/persistent.c2
-rw-r--r--frida_mode/src/prefetch.c98
-rw-r--r--frida_mode/src/ranges.c8
-rw-r--r--frida_mode/src/stalker.c80
-rw-r--r--frida_mode/src/stats/stats.c423
-rw-r--r--frida_mode/src/stats/stats_arm32.c13
-rw-r--r--frida_mode/src/stats/stats_arm64.c307
-rw-r--r--frida_mode/src/stats/stats_x64.c325
-rw-r--r--frida_mode/src/stats/stats_x86.c36
-rw-r--r--frida_mode/src/stats/stats_x86_64.c420
-rw-r--r--frida_mode/test/cmplog/GNUmakefile2
-rw-r--r--frida_mode/test/deferred/GNUmakefile2
-rw-r--r--frida_mode/test/entry_point/GNUmakefile2
-rw-r--r--frida_mode/test/exe/GNUmakefile2
-rw-r--r--frida_mode/test/fasan/GNUmakefile2
-rw-r--r--frida_mode/test/jpeg/GNUmakefile2
-rw-r--r--frida_mode/test/js/GNUmakefile2
-rw-r--r--frida_mode/test/libpcap/GNUmakefile4
-rw-r--r--frida_mode/test/osx-lib/GNUmakefile2
-rw-r--r--frida_mode/test/output/GNUmakefile2
-rw-r--r--frida_mode/test/persistent_ret/GNUmakefile2
-rw-r--r--frida_mode/test/png/GNUmakefile2
-rw-r--r--frida_mode/test/png/persistent/GNUmakefile6
-rw-r--r--frida_mode/test/png/persistent/hook/GNUmakefile19
-rw-r--r--frida_mode/test/proj4/GNUmakefile2
-rw-r--r--frida_mode/test/re2/GNUmakefile4
-rw-r--r--frida_mode/test/sqlite/GNUmakefile20
-rw-r--r--frida_mode/test/testinstr/GNUmakefile2
-rw-r--r--frida_mode/test/unstable/GNUmakefile2
-rw-r--r--frida_mode/ts/lib/afl.ts36
-rw-r--r--include/envs.h2
45 files changed, 1453 insertions, 656 deletions
diff --git a/frida_mode/GNUmakefile b/frida_mode/GNUmakefile
index 58a53823..3e35e2f6 100644
--- a/frida_mode/GNUmakefile
+++ b/frida_mode/GNUmakefile
@@ -83,16 +83,7 @@ ifndef OS
  $(error "Operating system unsupported")
 endif
 
-ifeq "$(ARCH)" "arm64"
-# 15.0.0 Not released for aarch64 yet
-GUM_DEVKIT_VERSION=14.2.18
-else
-ifeq "$(ARCH)" "armhf"
-GUM_DEVKIT_VERSION=14.2.18
-else
-GUM_DEVKIT_VERSION=15.0.0
-endif
-endif
+GUM_DEVKIT_VERSION=15.0.16
 GUM_DEVKIT_FILENAME=frida-gumjs-devkit-$(GUM_DEVKIT_VERSION)-$(OS)-$(ARCH).tar.xz
 GUM_DEVKIT_URL="https://github.com/frida/frida/releases/download/$(GUM_DEVKIT_VERSION)/$(GUM_DEVKIT_FILENAME)"
 
diff --git a/frida_mode/README.md b/frida_mode/README.md
index e60340b4..9f00c294 100644
--- a/frida_mode/README.md
+++ b/frida_mode/README.md
@@ -162,7 +162,12 @@ instrumentation (the default where available). Required to use
 `AFL_FRIDA_INST_TRACE`.
 * `AFL_FRIDA_INST_NO_PREFETCH` - Disable prefetching. By default the child will
 report instrumented blocks back to the parent so that it can also instrument
-them and they be inherited by the next child on fork.
+them and they be inherited by the next child on fork, implies
+`AFL_FRIDA_INST_NO_PREFETCH_BACKPATCH`.
+* `AFL_FRIDA_INST_NO_PREFETCH_BACKPATCH` - Disable prefetching of stalker
+backpatching information. By default the child will report applied backpatches
+to the parent so that they can be applied and then be inherited by the next
+child on fork.
 * `AFL_FRIDA_INST_SEED` - Sets the initial seed for the hash function used to
 generate block (and hence edge) IDs. Setting this to a constant value may be
 useful for debugging purposes, e.g. investigating unstable edges.
@@ -189,6 +194,9 @@ gdb \
 		--args <my-executable> [my arguments]
 
 ```
+* `AFL_FRIDA_STALKER_IC_ENTRIES` - Configure the number of inline cache entries
+stored along-side branch instructions which provide a cache to avoid having to
+call back into FRIDA to find the next block. Default is 32.
 * `AFL_FRIDA_STATS_FILE` - Write statistics information about the code being
 instrumented to the given file name. The statistics are written only for the
 child process when new block is instrumented (when the
@@ -198,67 +206,70 @@ the existing blocks instrumented have been executed in a different order.
 ```
 stats
 -----
-Index:                          2
-Pid:                            1815944
-Time:                           2021-05-28 15:26:41
-Blocks:                         1985
-Instructions:                   9192
-Avg Instructions / Block:       4
-
-Call Immediates:                391 (4.25%)
-Call Immediates Excluded:       65 (0.71%)
-Call Register:                  0 (0.00%)
-Call Memory:                    0 (0.00%)
-
-Jump Immediates:                202 (2.20%)
-Jump Register:                  10 (0.11%)
-Jump Memory:                    12 (0.13%)
-
-Conditional Jump Immediates:    1210 (13.16%)
-Conditional Jump CX Immediate:  0 (0.00%)
-Conditional Jump Register:      0 (0.00%)
-Conditional Jump Memory:        0 (0.00%)
-
-Returns:                        159 (0.00%)
-
-Rip Relative:                   247 (0.00%)
-
+Time                  2021-07-21 11:45:49
+Elapsed                                 1 seconds
+
+
+Transitions                    cumulative               delta
+-----------                    ----------               -----
+total                              753619               17645
+call_imm                             9193 ( 1.22%)        344 ( 1.95%) [       344/s]
+call_reg                                0 ( 0.00%)          0 ( 0.00%) [         0/s]
+call_mem                                0 ( 0.00%)          0 ( 0.00%) [         0/s]
+ret_slow_path                       67974 ( 9.02%)       2988 (16.93%) [      2988/s]
+post_call_invoke                     7996 ( 1.06%)        299 ( 1.69%) [       299/s]
+excluded_call_imm                    3804 ( 0.50%)        200 ( 1.13%) [       200/s]
+jmp_imm                              5445 ( 0.72%)        255 ( 1.45%) [       255/s]
+jmp_reg                             42081 ( 5.58%)       1021 ( 5.79%) [      1021/s]
+jmp_mem                            578092 (76.71%)      10956 (62.09%) [     10956/s]
+jmp_cond_imm                        38951 ( 5.17%)       1579 ( 8.95%) [      1579/s]
+jmp_cond_mem                            0 ( 0.00%)          0 ( 0.00%) [         0/s]
+jmp_cond_reg                            0 ( 0.00%)          0 ( 0.00%) [         0/s]
+jmp_cond_jcxz                           0 ( 0.00%)          0 ( 0.00%) [         0/s]
+jmp_continuation                       84 ( 0.01%)          3 ( 0.02%) [         3/s]
+
+
+Instrumentation
+---------------
+Instructions                         7907
+Blocks                               1764
+Avg Instructions / Block                4
+
+
+EOB Instructions
+----------------
+Total                                1763 (22.30%)
+Call Immediates                       358 ( 4.53%)
+Call Immediates Excluded               74 ( 0.94%)
+Call Register                           0 ( 0.00%)
+Call Memory                             0 ( 0.00%)
+Jump Immediates                       176 ( 2.23%)
+Jump Register                           8 ( 0.10%)
+Jump Memory                            10 ( 0.13%)
+Conditional Jump Immediates          1051 (13.29%)
+Conditional Jump CX Immediate           0 ( 0.00%)
+Conditional Jump Register               0 ( 0.00%)
+Conditional Jump Memory                 0 ( 0.00%)
+Returns                               160 ( 2.02%)
+
+
+Relocated Instructions
+----------------------
+Total                                 232 ( 2.93%)
+addsd                                   2 ( 0.86%)
+cmp                                    46 (19.83%)
+comisd                                  2 ( 0.86%)
+divsd                                   2 ( 0.86%)
+divss                                   2 ( 0.86%)
+lea                                   142 (61.21%)
+mov                                    32 (13.79%)
+movsd                                   2 ( 0.86%)
+ucomisd                                 2 ( 0.86%)
 ```
 * `AFL_FRIDA_STATS_INTERVAL` - The maximum frequency to output statistics
 information. Stats will be written whenever they are updated if the given
 interval has elapsed since last time they were written.
-* `AFL_FRIDA_STATS_TRANSITIONS` - Also dump the internal stalker counters to
-stderr when the regular stats are written. Note that these stats are reset in
-the child each time a new fork occurs since they are not stored in shared
-memory. Unfortunately, these stats are internal to stalker, so this is the best
-we can do for now.
-```
-stats
------
-Index: 2
-Pid:   1816794
-Time:  2021-05-28 15:26:41
-
-
-total_transitions: 786
-        call_imms: 97
-        call_regs: 0
-        call_mems: 0
-        post_call_invokes: 86
-        excluded_call_imms: 29
-        ret_slow_paths: 23
 
-        jmp_imms: 58
-        jmp_mems: 7
-        jmp_regs: 26
-
-        jmp_cond_imms: 460
-        jmp_cond_mems: 0
-        jmp_cond_regs: 0
-        jmp_cond_jcxzs: 0
-
-        jmp_continuations: 0
-```
 ## FASAN - Frida Address Sanitizer Mode
 Frida mode also supports FASAN. The design of this is actually quite simple and
 very similar to that used when instrumenting applications compiled from source.
diff --git a/frida_mode/Scripting.md b/frida_mode/Scripting.md
index 5467db99..f6017fad 100644
--- a/frida_mode/Scripting.md
+++ b/frida_mode/Scripting.md
@@ -80,7 +80,6 @@ Afl.setInstrumentEnableTracing();
 Afl.setInstrumentTracingUnique();
 Afl.setStatsFile("/tmp/stats.txt");
 Afl.setStatsInterval(1);
-Afl.setStatsTransitions();
 
 /* *ALWAYS* call this when you have finished all your configuration */
 Afl.done();
@@ -834,13 +833,6 @@ class Afl {
   }
 
   /**
-   * See `AFL_FRIDA_STATS_TRANSITIONS`
-   */
-  public static setStatsTransitions(): void {
-    Afl.jsApiSetStatsTransitions();
-  }
-
-  /**
    * See `AFL_FRIDA_OUTPUT_STDERR`. This function takes a single `string` as
    * an argument.
    */
diff --git a/frida_mode/frida.map b/frida_mode/frida.map
index cf304885..7ad5e682 100644
--- a/frida_mode/frida.map
+++ b/frida_mode/frida.map
@@ -23,11 +23,11 @@
     js_api_set_persistent_debug;
     js_api_set_persistent_hook;
     js_api_set_persistent_return;
+    js_api_set_prefetch_backpatch_disable;
     js_api_set_prefetch_disable;
     js_api_set_stalker_callback;
     js_api_set_stats_file;
     js_api_set_stats_interval;
-    js_api_set_stats_transitions;
     js_api_set_stderr;
     js_api_set_stdout;
 
diff --git a/frida_mode/include/entry.h b/frida_mode/include/entry.h
index cbc5c8c7..3f0a4ecc 100644
--- a/frida_mode/include/entry.h
+++ b/frida_mode/include/entry.h
@@ -4,7 +4,8 @@
 #include "frida-gumjs.h"
 
 extern guint64  entry_point;
-extern gboolean entry_reached;
+extern gboolean entry_compiled;
+extern gboolean entry_run;
 
 void entry_config(void);
 
diff --git a/frida_mode/include/prefetch.h b/frida_mode/include/prefetch.h
index 835d5e8a..d1ea5a31 100644
--- a/frida_mode/include/prefetch.h
+++ b/frida_mode/include/prefetch.h
@@ -4,6 +4,7 @@
 #include "frida-gumjs.h"
 
 extern gboolean prefetch_enable;
+extern gboolean prefetch_backpatch;
 
 void prefetch_config(void);
 void prefetch_init(void);
diff --git a/frida_mode/include/ranges.h b/frida_mode/include/ranges.h
index 2eb9b355..0220a59d 100644
--- a/frida_mode/include/ranges.h
+++ b/frida_mode/include/ranges.h
@@ -10,7 +10,7 @@ extern gboolean ranges_inst_jit;
 void ranges_config(void);
 void ranges_init(void);
 
-gboolean range_is_excluded(gpointer address);
+gboolean range_is_excluded(GumAddress address);
 
 void ranges_exclude();
 
diff --git a/frida_mode/include/stalker.h b/frida_mode/include/stalker.h
index b5e05d5a..955f3913 100644
--- a/frida_mode/include/stalker.h
+++ b/frida_mode/include/stalker.h
@@ -3,11 +3,15 @@
 
 #include "frida-gumjs.h"
 
+extern guint stalker_ic_entries;
+
 void        stalker_config(void);
 void        stalker_init(void);
 GumStalker *stalker_get(void);
 void        stalker_start(void);
 void        stalker_trust(void);
 
+GumStalkerObserver *stalker_get_observer(void);
+
 #endif
 
diff --git a/frida_mode/include/stats.h b/frida_mode/include/stats.h
index cd2350ea..0ad227c3 100644
--- a/frida_mode/include/stats.h
+++ b/frida_mode/include/stats.h
@@ -5,30 +5,56 @@
 
 typedef struct {
 
-  guint64 num_blocks;
-  guint64 num_instructions;
-  guint64 stats_last_time;
-  guint64 stats_idx;
-  guint64 transitions_idx;
+  guint64 stats_time;
+  guint64 total;
+  guint64 call_imm;
+  guint64 call_reg;
+  guint64 call_mem;
+  guint64 excluded_call_reg;
+  guint64 ret_slow_path;
+  guint64 ret;
+  guint64 post_call_invoke;
+  guint64 excluded_call_imm;
+  guint64 jmp_imm;
+  guint64 jmp_reg;
+  guint64 jmp_mem;
+  guint64 jmp_cond_imm;
+  guint64 jmp_cond_mem;
+  guint64 jmp_cond_reg;
+  guint64 jmp_cond_jcxz;
+  guint64 jmp_cond_cc;
+  guint64 jmp_cond_cbz;
+  guint64 jmp_cond_cbnz;
+  guint64 jmp_cond_tbz;
+  guint64 jmp_cond_tbnz;
+  guint64 jmp_continuation;
+
+} stats_t;
 
-} stats_data_header_t;
+typedef struct {
+
+  /* transitions */
+  stats_t curr;
+  stats_t prev;
+
+} stats_data_t;
 
-extern stats_data_header_t *stats_data;
+#define GUM_TYPE_AFL_STALKER_STATS (gum_afl_stalker_stats_get_type())
+G_DECLARE_FINAL_TYPE(GumAflStalkerStats, gum_afl_stalker_stats, GUM,
+                     AFL_STALKER_STATS, GObject)
 
-extern char *   stats_filename;
-extern guint64  stats_interval;
-extern gboolean stats_transitions;
+extern char *  stats_filename;
+extern guint64 stats_interval;
 
 void stats_config(void);
 void stats_init(void);
 void stats_collect(const cs_insn *instr, gboolean begin);
 void stats_print(char *format, ...);
 
-gboolean stats_is_supported_arch(void);
-size_t   stats_data_size_arch(void);
-void     stats_collect_arch(const cs_insn *instr);
-void     stats_write_arch(void);
-void     stats_on_fork(void);
+void starts_arch_init(void);
+void stats_collect_arch(const cs_insn *instr, gboolean begin);
+void stats_write_arch(stats_data_t *data);
+void stats_on_fork(void);
 
 #endif
 
diff --git a/frida_mode/src/entry.c b/frida_mode/src/entry.c
index 0b5f61ec..3ec8f5be 100644
--- a/frida_mode/src/entry.c
+++ b/frida_mode/src/entry.c
@@ -1,3 +1,5 @@
+#include <dlfcn.h>
+
 #include "frida-gumjs.h"
 
 #include "debug.h"
@@ -13,7 +15,8 @@
 extern void __afl_manual_init();
 
 guint64  entry_point = 0;
-gboolean entry_reached = FALSE;
+gboolean entry_compiled = FALSE;
+gboolean entry_run = FALSE;
 
 static void entry_launch(void) {
 
@@ -21,7 +24,7 @@ static void entry_launch(void) {
   __afl_manual_init();
 
   /* Child here */
-  entry_reached = TRUE;
+  entry_run = TRUE;
   instrument_on_fork();
   stats_on_fork();
 
@@ -37,6 +40,8 @@ void entry_init(void) {
 
   OKF("entry_point: 0x%016" G_GINT64_MODIFIER "X", entry_point);
 
+  if (dlopen(NULL, RTLD_NOW) == NULL) { FATAL("Failed to dlopen: %d", errno); }
+
 }
 
 void entry_start(void) {
@@ -49,6 +54,7 @@ static void entry_callout(GumCpuContext *cpu_context, gpointer user_data) {
 
   UNUSED_PARAMETER(cpu_context);
   UNUSED_PARAMETER(user_data);
+  entry_compiled = TRUE;
   entry_launch();
 
 }
diff --git a/frida_mode/src/instrument/instrument.c b/frida_mode/src/instrument/instrument.c
index e37c1d29..9e4dd191 100644
--- a/frida_mode/src/instrument/instrument.c
+++ b/frida_mode/src/instrument/instrument.c
@@ -164,7 +164,7 @@ static void instrument_basic_block(GumStalkerIterator *iterator,
      * our AFL_ENTRYPOINT, since it is not until then that we start the
      * fork-server and thus start executing in the child.
      */
-    excluded = range_is_excluded(GSIZE_TO_POINTER(instr->address));
+    excluded = range_is_excluded(GUM_ADDRESS(instr->address));
 
     stats_collect(instr, begin);
 
@@ -173,11 +173,7 @@ static void instrument_basic_block(GumStalkerIterator *iterator,
       instrument_debug_start(instr->address, output);
       instrument_coverage_start(instr->address);
 
-      if (likely(entry_reached)) {
-
-        prefetch_write(GSIZE_TO_POINTER(instr->address));
-
-      }
+      prefetch_write(GSIZE_TO_POINTER(instr->address));
 
       if (likely(!excluded)) {
 
diff --git a/frida_mode/src/instrument/instrument_coverage.c b/frida_mode/src/instrument/instrument_coverage.c
index 9d1701d1..4c0d1a14 100644
--- a/frida_mode/src/instrument/instrument_coverage.c
+++ b/frida_mode/src/instrument/instrument_coverage.c
@@ -1,3 +1,4 @@
+#include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
 #include <unistd.h>
@@ -277,8 +278,6 @@ static void instrument_coverage_run() {
 
   if (bytes != 0) { FATAL("Coverage data truncated"); }
 
-  if (errno != ENOENT) { FATAL("Coverage I/O error"); }
-
   OKF("Coverage - Preparing");
 
   coverage_get_ranges();
@@ -325,7 +324,7 @@ void instrument_coverage_init(void) {
 
   g_free(path);
 
-  if (pipe2(coverage_pipes, O_DIRECT) != 0) { FATAL("Failed to create pipes"); }
+  if (pipe(coverage_pipes) != 0) { FATAL("Failed to create pipes"); }
 
   coverage_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
   if (coverage_hash == NULL) {
diff --git a/frida_mode/src/js/api.js b/frida_mode/src/js/api.js
index f0cf7311..71b5e4a4 100644
--- a/frida_mode/src/js/api.js
+++ b/frida_mode/src/js/api.js
@@ -172,6 +172,12 @@ class Afl {
         Afl.jsApiSetPersistentReturn(address);
     }
     /**
+     * See `AFL_FRIDA_INST_NO_PREFETCH_BACKPATCH`.
+     */
+    static setPrefetchBackpatchDisable() {
+        Afl.jsApiSetPrefetchBackpatchDisable();
+    }
+    /**
      * See `AFL_FRIDA_INST_NO_PREFETCH`.
      */
     static setPrefetchDisable() {
@@ -185,6 +191,12 @@ class Afl {
         Afl.jsApiSetStalkerCallback(callback);
     }
     /**
+     * See `AFL_FRIDA_STALKER_IC_ENTRIES`.
+     */
+    static setStalkerIcEntries(val) {
+        Afl.jsApiSetStalkerIcEntries(val);
+    }
+    /**
      * See `AFL_FRIDA_STATS_FILE`. This function takes a single `string` as
      * an argument.
      */
@@ -200,12 +212,6 @@ class Afl {
         Afl.jsApiSetStatsInterval(interval);
     }
     /**
-     * See `AFL_FRIDA_STATS_TRANSITIONS`
-     */
-    static setStatsTransitions() {
-        Afl.jsApiSetStatsTransitions();
-    }
-    /**
      * See `AFL_FRIDA_OUTPUT_STDERR`. This function takes a single `string` as
      * an argument.
      */
@@ -254,11 +260,12 @@ Afl.jsApiSetPersistentCount = Afl.jsApiGetFunction("js_api_set_persistent_count"
 Afl.jsApiSetPersistentDebug = Afl.jsApiGetFunction("js_api_set_persistent_debug", "void", []);
 Afl.jsApiSetPersistentHook = Afl.jsApiGetFunction("js_api_set_persistent_hook", "void", ["pointer"]);
 Afl.jsApiSetPersistentReturn = Afl.jsApiGetFunction("js_api_set_persistent_return", "void", ["pointer"]);
+Afl.jsApiSetPrefetchBackpatchDisable = Afl.jsApiGetFunction("js_api_set_prefetch_backpatch_disable", "void", []);
 Afl.jsApiSetPrefetchDisable = Afl.jsApiGetFunction("js_api_set_prefetch_disable", "void", []);
 Afl.jsApiSetStalkerCallback = Afl.jsApiGetFunction("js_api_set_stalker_callback", "void", ["pointer"]);
+Afl.jsApiSetStalkerIcEntries = Afl.jsApiGetFunction("js_api_set_stalker_ic_entries", "void", ["uint32"]);
 Afl.jsApiSetStatsFile = Afl.jsApiGetFunction("js_api_set_stats_file", "void", ["pointer"]);
 Afl.jsApiSetStatsInterval = Afl.jsApiGetFunction("js_api_set_stats_interval", "void", ["uint64"]);
-Afl.jsApiSetStatsTransitions = Afl.jsApiGetFunction("js_api_set_stats_transitions", "void", []);
 Afl.jsApiSetStdErr = Afl.jsApiGetFunction("js_api_set_stderr", "void", ["pointer"]);
 Afl.jsApiSetStdOut = Afl.jsApiGetFunction("js_api_set_stdout", "void", ["pointer"]);
 Afl.jsApiWrite = new NativeFunction(
diff --git a/frida_mode/src/js/js_api.c b/frida_mode/src/js/js_api.c
index e51f852a..c2746d13 100644
--- a/frida_mode/src/js/js_api.c
+++ b/frida_mode/src/js/js_api.c
@@ -7,8 +7,10 @@
 #include "persistent.h"
 #include "prefetch.h"
 #include "ranges.h"
+#include "stalker.h"
 #include "stats.h"
 #include "util.h"
+
 __attribute__((visibility("default"))) void js_api_done() {
 
   js_done = TRUE;
@@ -127,6 +129,13 @@ __attribute__((visibility("default"))) void js_api_set_prefetch_disable(void) {
 
 }
 
+__attribute__((visibility("default"))) void
+js_api_set_prefetch_backpatch_disable(void) {
+
+  prefetch_backpatch = FALSE;
+
+}
+
 __attribute__((visibility("default"))) void js_api_set_instrument_no_optimize(
     void) {
 
@@ -180,12 +189,6 @@ __attribute__((visibility("default"))) void js_api_set_stats_interval(
 
 }
 
-__attribute__((visibility("default"))) void js_api_set_stats_transitions() {
-
-  stats_transitions = TRUE;
-
-}
-
 __attribute__((visibility("default"))) void js_api_set_persistent_hook(
     void *address) {
 
@@ -206,3 +209,10 @@ __attribute__((visibility("default"))) void js_api_set_stalker_callback(
 
 }
 
+__attribute__((visibility("default"))) void js_api_set_stalker_ic_entries(
+    guint val) {
+
+  stalker_ic_entries = val;
+
+}
+
diff --git a/frida_mode/src/persistent/persistent.c b/frida_mode/src/persistent/persistent.c
index 639a694e..b2915a2f 100644
--- a/frida_mode/src/persistent/persistent.c
+++ b/frida_mode/src/persistent/persistent.c
@@ -89,7 +89,7 @@ void persistent_init(void) {
 void persistent_prologue(GumStalkerOutput *output) {
 
   OKF("AFL_FRIDA_PERSISTENT_ADDR reached");
-  entry_reached = TRUE;
+  entry_compiled = TRUE;
   ranges_exclude();
   stalker_trust();
   persistent_prologue_arch(output);
diff --git a/frida_mode/src/prefetch.c b/frida_mode/src/prefetch.c
index 50d10c9e..0efbc9bf 100644
--- a/frida_mode/src/prefetch.c
+++ b/frida_mode/src/prefetch.c
@@ -6,32 +6,66 @@
 
 #include "debug.h"
 
+#include "entry.h"
 #include "intercept.h"
 #include "prefetch.h"
 #include "stalker.h"
+#include "util.h"
 
 #define TRUST 0
 #define PREFETCH_SIZE 65536
 #define PREFETCH_ENTRIES ((PREFETCH_SIZE - sizeof(size_t)) / sizeof(void *))
 
+#define BP_SIZE 524288
+
 typedef struct {
 
   size_t count;
   void * entry[PREFETCH_ENTRIES];
 
+  guint8 backpatch_data[BP_SIZE];
+  gsize  backpatch_size;
+
 } prefetch_data_t;
 
 gboolean prefetch_enable = TRUE;
+gboolean prefetch_backpatch = TRUE;
 
 static prefetch_data_t *prefetch_data = NULL;
 static int              prefetch_shm_id = -1;
 
+static void gum_afl_stalker_backpatcher_notify(GumStalkerObserver *self,
+                                               const GumBackpatch *backpatch,
+                                               gsize               size) {
+
+  UNUSED_PARAMETER(self);
+  if (!entry_run) { return; }
+  gsize remaining =
+      sizeof(prefetch_data->backpatch_data) - prefetch_data->backpatch_size;
+  if (sizeof(gsize) + size > remaining) { return; }
+
+  *(gsize *)(&prefetch_data->backpatch_data[prefetch_data->backpatch_size]) =
+      size;
+  prefetch_data->backpatch_size += sizeof(gsize);
+
+  memcpy(&prefetch_data->backpatch_data[prefetch_data->backpatch_size],
+         backpatch, size);
+  prefetch_data->backpatch_size += size;
+
+}
+
 /*
  * We do this from the transformer since we need one anyway for coverage, this
  * saves the need to use an event sink.
  */
 void prefetch_write(void *addr) {
 
+#if defined(__aarch64__)
+  if (!entry_compiled) { return; }
+#else
+  if (!entry_run) { return; }
+#endif
+
   /* Bail if we aren't initialized */
   if (prefetch_data == NULL) return;
 
@@ -51,10 +85,7 @@ void prefetch_write(void *addr) {
 
 }
 
-/*
- * Read the IPC region one block at the time and prefetch it
- */
-void prefetch_read(void) {
+static void prefetch_read_blocks(void) {
 
   GumStalker *stalker = stalker_get();
   if (prefetch_data == NULL) return;
@@ -74,10 +105,60 @@ void prefetch_read(void) {
 
 }
 
+static void prefetch_read_patches(void) {
+
+  gsize         offset = 0;
+  GumStalker *  stalker = stalker_get();
+  GumBackpatch *backpatch = NULL;
+
+  for (gsize remaining = prefetch_data->backpatch_size - offset;
+       remaining > sizeof(gsize);
+       remaining = prefetch_data->backpatch_size - offset) {
+
+    gsize size = *(gsize *)(&prefetch_data->backpatch_data[offset]);
+    offset += sizeof(gsize);
+
+    if (prefetch_data->backpatch_size - offset < size) {
+
+      FATAL("Incomplete backpatch entry");
+
+    }
+
+    backpatch = (GumBackpatch *)&prefetch_data->backpatch_data[offset];
+    gum_stalker_prefetch_backpatch(stalker, backpatch);
+    offset += size;
+
+  }
+
+  prefetch_data->backpatch_size = 0;
+
+}
+
+/*
+ * Read the IPC region one block at the time and prefetch it
+ */
+void prefetch_read(void) {
+
+  prefetch_read_blocks();
+  prefetch_read_patches();
+
+}
+
 void prefetch_config(void) {
 
   prefetch_enable = (getenv("AFL_FRIDA_INST_NO_PREFETCH") == NULL);
 
+  if (prefetch_enable) {
+
+    prefetch_backpatch =
+        (getenv("AFL_FRIDA_INST_NO_PREFETCH_BACKPATCH") == NULL);
+
+  } else {
+
+    prefetch_backpatch = FALSE;
+
+  }
+
 }
 
 static int prefetch_on_fork(void) {
@@ -97,8 +178,9 @@ static void prefetch_hook_fork(void) {
 
 void prefetch_init(void) {
 
-  g_assert_cmpint(sizeof(prefetch_data_t), ==, PREFETCH_SIZE);
   OKF("Instrumentation - prefetch [%c]", prefetch_enable ? 'X' : ' ');
+  OKF("Instrumentation - prefetch_backpatch [%c]",
+      prefetch_backpatch ? 'X' : ' ');
 
   if (!prefetch_enable) { return; }
   /*
@@ -131,5 +213,11 @@ void prefetch_init(void) {
 
   prefetch_hook_fork();
 
+  if (!prefetch_backpatch) { return; }
+
+  GumStalkerObserver *         observer = stalker_get_observer();
+  GumStalkerObserverInterface *iface = GUM_STALKER_OBSERVER_GET_IFACE(observer);
+  iface->notify_backpatch = gum_afl_stalker_backpatcher_notify;
+
 }
 
diff --git a/frida_mode/src/ranges.c b/frida_mode/src/ranges.c
index 6fdd65a7..5b6eb462 100644
--- a/frida_mode/src/ranges.c
+++ b/frida_mode/src/ranges.c
@@ -635,9 +635,7 @@ void ranges_init(void) {
 
 }
 
-gboolean range_is_excluded(gpointer address) {
-
-  GumAddress test = GUM_ADDRESS(address);
+gboolean range_is_excluded(GumAddress address) {
 
   if (ranges == NULL) { return false; }
 
@@ -646,9 +644,9 @@ gboolean range_is_excluded(gpointer address) {
     GumMemoryRange *curr = &g_array_index(ranges, GumMemoryRange, i);
     GumAddress      curr_limit = curr->base_address + curr->size;
 
-    if (test < curr->base_address) { return false; }
+    if (address < curr->base_address) { return false; }
 
-    if (test < curr_limit) { return true; }
+    if (address < curr_limit) { return true; }
 
   }
 
diff --git a/frida_mode/src/stalker.c b/frida_mode/src/stalker.c
index 5df0386f..814aaeb3 100644
--- a/frida_mode/src/stalker.c
+++ b/frida_mode/src/stalker.c
@@ -1,15 +1,67 @@
 #include "debug.h"
 
 #include "instrument.h"
+#include "prefetch.h"
 #include "stalker.h"
+#include "stats.h"
 #include "util.h"
 
+guint stalker_ic_entries = 0;
+
 static GumStalker *stalker = NULL;
 
+struct _GumAflStalkerObserver {
+
+  GObject parent;
+
+};
+
+#define GUM_TYPE_AFL_STALKER_OBSERVER (gum_afl_stalker_observer_get_type())
+G_DECLARE_FINAL_TYPE(GumAflStalkerObserver, gum_afl_stalker_observer, GUM,
+                     AFL_STALKER_OBSERVER, GObject)
+
+static void gum_afl_stalker_observer_iface_init(gpointer g_iface,
+                                                gpointer iface_data);
+static void gum_afl_stalker_observer_class_init(
+    GumAflStalkerObserverClass *klass);
+static void gum_afl_stalker_observer_init(GumAflStalkerObserver *self);
+
+G_DEFINE_TYPE_EXTENDED(
+    GumAflStalkerObserver, gum_afl_stalker_observer, G_TYPE_OBJECT, 0,
+    G_IMPLEMENT_INTERFACE(GUM_TYPE_STALKER_OBSERVER,
+                          gum_afl_stalker_observer_iface_init))
+
+static GumAflStalkerObserver *observer = NULL;
+
+static void gum_afl_stalker_observer_iface_init(gpointer g_iface,
+                                                gpointer iface_data) {
+
+  UNUSED_PARAMETER(g_iface);
+  UNUSED_PARAMETER(iface_data);
+
+}
+
+static void gum_afl_stalker_observer_class_init(
+    GumAflStalkerObserverClass *klass) {
+
+  UNUSED_PARAMETER(klass);
+
+}
+
+static void gum_afl_stalker_observer_init(GumAflStalkerObserver *self) {
+
+  UNUSED_PARAMETER(self);
+
+}
+
 void stalker_config(void) {
 
   if (!gum_stalker_is_supported()) { FATAL("Failed to initialize embedded"); }
 
+  stalker_ic_entries = util_read_num("AFL_FRIDA_STALKER_IC_ENTRIES");
+
+  observer = g_object_new(GUM_TYPE_AFL_STALKER_OBSERVER, NULL);
+
 }
 
 static gboolean stalker_exclude_self(const GumRangeDetails *details,
@@ -35,7 +87,26 @@ static gboolean stalker_exclude_self(const GumRangeDetails *details,
 
 void stalker_init(void) {
 
+  OKF("Stalker - ic_entries [%u]", stalker_ic_entries);
+
+#if !(defined(__x86_64__) || defined(__i386__))
+  if (stalker_ic_entries != 0) {
+
+    FATAL("AFL_FRIDA_STALKER_IC_ENTRIES not supported");
+
+  }
+
+#endif
+
+  if (stalker_ic_entries == 0) { stalker_ic_entries = 32; }
+
+#if defined(__x86_64__) || defined(__i386__)
+  stalker =
+      g_object_new(GUM_TYPE_STALKER, "ic-entries", stalker_ic_entries, NULL);
+#else
   stalker = gum_stalker_new();
+#endif
+
   if (stalker == NULL) { FATAL("Failed to initialize stalker"); }
 
   gum_stalker_set_trust_threshold(stalker, -1);
@@ -57,6 +128,8 @@ void stalker_start(void) {
   GumStalkerTransformer *transformer = instrument_get_transformer();
   gum_stalker_follow_me(stalker, transformer, NULL);
 
+  gum_stalker_set_observer(stalker, GUM_STALKER_OBSERVER(observer));
+
 }
 
 void stalker_trust(void) {
@@ -65,3 +138,10 @@ void stalker_trust(void) {
 
 }
 
+GumStalkerObserver *stalker_get_observer(void) {
+
+  if (observer == NULL) { FATAL("Stalker not yet initialized"); }
+  return GUM_STALKER_OBSERVER(observer);
+
+}
+
diff --git a/frida_mode/src/stats/stats.c b/frida_mode/src/stats/stats.c
index 91a58741..7972b881 100644
--- a/frida_mode/src/stats/stats.c
+++ b/frida_mode/src/stats/stats.c
@@ -11,204 +11,405 @@
 #include "debug.h"
 #include "util.h"
 
+#include "entry.h"
+#include "stalker.h"
 #include "stats.h"
 
 #define MICRO_TO_SEC 1000000
 
-stats_data_header_t *stats_data = NULL;
+char *               stats_filename = NULL;
+guint64              stats_interval = 0;
+static guint64       stats_interval_us = 0;
+static int           stats_fd = -1;
+static stats_data_t *stats_data = MAP_FAILED;
 
-static int stats_parent_pid = -1;
-static int stats_fd = -1;
+void stats_write(void) {
 
-char *   stats_filename = NULL;
-guint64  stats_interval = 0;
-gboolean stats_transitions = FALSE;
+  if (stats_filename == NULL) { return; }
 
-void stats_config(void) {
+  if (stats_interval == 0) { return; }
 
-  stats_filename = getenv("AFL_FRIDA_STATS_FILE");
-  stats_interval = util_read_num("AFL_FRIDA_STATS_INTERVAL");
-  if (getenv("AFL_FRIDA_STATS_TRANSITIONS") != NULL) {
+  guint64 current_time = g_get_monotonic_time();
+  if ((current_time - stats_data->prev.stats_time) < stats_interval_us) {
 
-    stats_transitions = TRUE;
+    return;
 
   }
 
+  IGNORED_RETURN(ftruncate(stats_fd, 0));
+  IGNORED_RETURN(lseek(stats_fd, 0, SEEK_SET));
+
+  stats_data->curr.stats_time = current_time;
+
+  GDateTime *date_time = g_date_time_new_now_local();
+  char *     date_string = g_date_time_format(date_time, "%Y-%m-%d");
+  char *     time_string = g_date_time_format(date_time, "%H:%M:%S");
+  guint elapsed = (stats_data->curr.stats_time - stats_data->prev.stats_time) /
+                  MICRO_TO_SEC;
+
+  stats_print("stats\n");
+  stats_print("-----\n");
+
+  stats_print("%-21s %s %s\n", "Time", date_string, time_string);
+  stats_print("%-30s %10u seconds \n", "Elapsed", elapsed);
+
+  stats_print("\n");
+  stats_print("\n");
+
+  g_free(time_string);
+  g_free(date_string);
+  g_date_time_unref(date_time);
+
+  stats_write_arch(stats_data);
+
+  memcpy(&stats_data->prev, &stats_data->curr, sizeof(stats_t));
+
 }
 
-void stats_init(void) {
+static void gum_afl_stalker_stats_increment_total(
+    GumStalkerObserver *observer) {
 
-  stats_parent_pid = getpid();
+  UNUSED_PARAMETER(observer);
 
-  OKF("Stats - file [%s]", stats_filename);
-  OKF("Stats - interval [%" G_GINT64_MODIFIER "u]", stats_interval);
+  if (!entry_compiled) { return; }
+  stats_data->curr.total++;
 
-  if (stats_interval != 0 && stats_filename == NULL) {
+}
 
-    FATAL(
-        "AFL_FRIDA_STATS_FILE must be specified if "
-        "AFL_FRIDA_STATS_INTERVAL is");
+static void gum_afl_stalker_stats_increment_call_imm(
+    GumStalkerObserver *observer) {
 
-  }
+  UNUSED_PARAMETER(observer);
 
-  if (stats_interval == 0) { stats_interval = 10; }
+  if (!entry_compiled) { return; }
+  stats_data->curr.call_imm++;
 
-  if (stats_filename == NULL) { return; }
+}
 
-  if (!stats_is_supported_arch()) {
+static void gum_afl_stalker_stats_increment_call_reg(
+    GumStalkerObserver *observer) {
 
-    FATAL("Stats is not supported on this architecture");
+  UNUSED_PARAMETER(observer);
 
-  }
+  if (!entry_compiled) { return; }
+  stats_data->curr.call_reg++;
 
-  char *path = NULL;
+}
 
-  if (stats_filename == NULL) { return; }
+static void gum_afl_stalker_stats_increment_call_mem(
+    GumStalkerObserver *observer) {
 
-  if (stats_transitions) { gum_stalker_set_counters_enabled(TRUE); }
+  UNUSED_PARAMETER(observer);
 
-  path = g_canonicalize_filename(stats_filename, g_get_current_dir());
+  if (!entry_compiled) { return; }
+  stats_data->curr.call_mem++;
 
-  OKF("Stats - path [%s]", path);
+}
 
-  stats_fd = open(path, O_RDWR | O_CREAT | O_TRUNC,
-                  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+static void gum_afl_stalker_stats_increment_excluded_call_reg(
+    GumStalkerObserver *observer) {
 
-  if (stats_fd < 0) { FATAL("Failed to open stats file '%s'", path); }
+  UNUSED_PARAMETER(observer);
 
-  g_free(path);
+  if (!entry_compiled) { return; }
+  stats_data->curr.excluded_call_reg++;
 
-  size_t data_size = stats_data_size_arch();
+}
 
-  int shm_id = shmget(IPC_PRIVATE, data_size, IPC_CREAT | IPC_EXCL | 0600);
-  if (shm_id < 0) { FATAL("shm_id < 0 - errno: %d\n", errno); }
+static void gum_afl_stalker_stats_increment_ret_slow_path(
+    GumStalkerObserver *observer) {
 
-  stats_data = shmat(shm_id, NULL, 0);
-  g_assert(stats_data != MAP_FAILED);
+  UNUSED_PARAMETER(observer);
 
-  /*
-   * Configure the shared memory region to be removed once the process dies.
-   */
-  if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
+  if (!entry_compiled) { return; }
+  stats_data->curr.ret_slow_path++;
 
-    FATAL("shmctl (IPC_RMID) < 0 - errno: %d\n", errno);
+}
 
-  }
+static void gum_afl_stalker_stats_increment_ret(GumStalkerObserver *observer) {
 
-  /* Clear it, not sure it's necessary, just seems like good practice */
-  memset(stats_data, '\0', data_size);
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.ret++;
 
 }
 
-void stats_vprint(int fd, char *format, va_list ap) {
+static void gum_afl_stalker_stats_increment_post_call_invoke(
+    GumStalkerObserver *observer) {
 
-  char buffer[4096] = {0};
-  int  len;
+  UNUSED_PARAMETER(observer);
 
-  if (vsnprintf(buffer, sizeof(buffer) - 1, format, ap) < 0) { return; }
+  if (!entry_compiled) { return; }
+  stats_data->curr.post_call_invoke++;
 
-  len = strnlen(buffer, sizeof(buffer));
-  IGNORED_RETURN(write(fd, buffer, len));
+}
+
+static void gum_afl_stalker_stats_increment_excluded_call_imm(
+    GumStalkerObserver *observer) {
+
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.excluded_call_imm++;
 
 }
 
-void stats_print_fd(int fd, char *format, ...) {
+static void gum_afl_stalker_stats_increment_jmp_imm(
+    GumStalkerObserver *observer) {
 
-  va_list ap;
-  va_start(ap, format);
-  stats_vprint(fd, format, ap);
-  va_end(ap);
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_imm++;
 
 }
 
-void stats_print(char *format, ...) {
+static void gum_afl_stalker_stats_increment_jmp_reg(
+    GumStalkerObserver *observer) {
 
-  va_list ap;
-  va_start(ap, format);
-  stats_vprint(stats_fd, format, ap);
-  va_end(ap);
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_reg++;
 
 }
 
-void stats_write(void) {
+static void gum_afl_stalker_stats_increment_jmp_mem(
+    GumStalkerObserver *observer) {
 
-  if (stats_parent_pid == getpid()) { return; }
+  UNUSED_PARAMETER(observer);
 
-  GDateTime *date_time = g_date_time_new_now_local();
-  char *date_time_string = g_date_time_format(date_time, "%Y-%m-%e %H:%M:%S");
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_mem++;
 
-  stats_print("stats\n");
-  stats_print("-----\n");
+}
 
-  stats_print("Index:                          %" G_GINT64_MODIFIER "u\n",
-              stats_data->stats_idx++);
-  stats_print("Pid:                            %d\n", getpid());
-  stats_print("Time:                           %s\n", date_time_string);
-  stats_print("Blocks:                         %" G_GINT64_MODIFIER "u\n",
-              stats_data->num_blocks);
-  stats_print("Instructions:                   %" G_GINT64_MODIFIER "u\n",
-              stats_data->num_instructions);
-  stats_print("Avg Instructions / Block:       %" G_GINT64_MODIFIER "u\n",
-              stats_data->num_instructions / stats_data->num_blocks);
+static void gum_afl_stalker_stats_increment_jmp_cond_imm(
+    GumStalkerObserver *observer) {
 
-  stats_print("\n");
+  UNUSED_PARAMETER(observer);
 
-  g_free(date_time_string);
-  g_date_time_unref(date_time);
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_cond_imm++;
 
-  stats_write_arch();
+}
 
-  if (stats_transitions) {
+static void gum_afl_stalker_stats_increment_jmp_cond_mem(
+    GumStalkerObserver *observer) {
 
-    GDateTime *date_time = g_date_time_new_now_local();
-    char *date_time_string = g_date_time_format(date_time, "%Y-%m-%e %H:%M:%S");
+  UNUSED_PARAMETER(observer);
 
-    stats_print_fd(STDERR_FILENO, "stats\n");
-    stats_print_fd(STDERR_FILENO, "-----\n");
-    stats_print_fd(STDERR_FILENO, "Index: %" G_GINT64_MODIFIER "u\n",
-                   stats_data->transitions_idx++);
-    stats_print_fd(STDERR_FILENO, "Pid:   %d\n", getpid());
-    stats_print_fd(STDERR_FILENO, "Time:  %s\n", date_time_string);
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_cond_mem++;
 
-    g_free(date_time_string);
-    g_date_time_unref(date_time);
-    gum_stalker_dump_counters();
+}
 
-  }
+static void gum_afl_stalker_stats_increment_jmp_cond_reg(
+    GumStalkerObserver *observer) {
+
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_cond_reg++;
 
 }
 
-void stats_on_fork(void) {
+static void gum_afl_stalker_stats_increment_jmp_cond_jcxz(
+    GumStalkerObserver *observer) {
+
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_cond_jcxz++;
+
+}
+
+static void gum_afl_stalker_stats_increment_jmp_cond_cc(
+    GumStalkerObserver *observer) {
+
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_cond_cc++;
+
+}
+
+static void gum_afl_stalker_stats_increment_jmp_cond_cbz(
+    GumStalkerObserver *observer) {
+
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_cond_cbz++;
+
+}
+
+static void gum_afl_stalker_stats_increment_jmp_cond_cbnz(
+    GumStalkerObserver *observer) {
+
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_cond_cbnz++;
+
+}
+
+static void gum_afl_stalker_stats_increment_jmp_cond_tbz(
+    GumStalkerObserver *observer) {
 
-  guint64 current_time;
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_cond_tbz++;
+
+}
+
+static void gum_afl_stalker_stats_increment_jmp_cond_tbnz(
+    GumStalkerObserver *observer) {
+
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_cond_tbnz++;
+
+}
+
+static void gum_afl_stalker_stats_increment_jmp_continuation(
+    GumStalkerObserver *observer) {
+
+  UNUSED_PARAMETER(observer);
+
+  if (!entry_compiled) { return; }
+  stats_data->curr.jmp_continuation++;
+
+}
+
+static void stats_observer_init(GumStalkerObserver *observer) {
+
+  GumStalkerObserverInterface *iface = GUM_STALKER_OBSERVER_GET_IFACE(observer);
+  iface->increment_total = gum_afl_stalker_stats_increment_total;
+  iface->increment_call_imm = gum_afl_stalker_stats_increment_call_imm;
+  iface->increment_call_reg = gum_afl_stalker_stats_increment_call_reg;
+  iface->increment_call_mem = gum_afl_stalker_stats_increment_call_mem;
+  iface->increment_excluded_call_reg =
+      gum_afl_stalker_stats_increment_excluded_call_reg;
+  iface->increment_ret_slow_path =
+      gum_afl_stalker_stats_increment_ret_slow_path;
+  iface->increment_ret = gum_afl_stalker_stats_increment_ret;
+  iface->increment_post_call_invoke =
+      gum_afl_stalker_stats_increment_post_call_invoke;
+  iface->increment_excluded_call_imm =
+      gum_afl_stalker_stats_increment_excluded_call_imm;
+  iface->increment_jmp_imm = gum_afl_stalker_stats_increment_jmp_imm;
+  iface->increment_jmp_reg = gum_afl_stalker_stats_increment_jmp_reg;
+  iface->increment_jmp_mem = gum_afl_stalker_stats_increment_jmp_mem;
+  iface->increment_jmp_cond_imm = gum_afl_stalker_stats_increment_jmp_cond_imm;
+  iface->increment_jmp_cond_mem = gum_afl_stalker_stats_increment_jmp_cond_mem;
+  iface->increment_jmp_cond_reg = gum_afl_stalker_stats_increment_jmp_cond_reg;
+  iface->increment_jmp_cond_jcxz =
+      gum_afl_stalker_stats_increment_jmp_cond_jcxz;
+  iface->increment_jmp_cond_cc = gum_afl_stalker_stats_increment_jmp_cond_cc;
+  iface->increment_jmp_cond_cbz = gum_afl_stalker_stats_increment_jmp_cond_cbz;
+  iface->increment_jmp_cond_cbnz =
+      gum_afl_stalker_stats_increment_jmp_cond_cbnz;
+  iface->increment_jmp_cond_tbz = gum_afl_stalker_stats_increment_jmp_cond_tbz;
+  iface->increment_jmp_cond_tbnz =
+      gum_afl_stalker_stats_increment_jmp_cond_tbnz;
+  iface->increment_jmp_continuation =
+      gum_afl_stalker_stats_increment_jmp_continuation;
+
+}
+
+void stats_config(void) {
+
+  stats_filename = getenv("AFL_FRIDA_STATS_FILE");
+  stats_interval = util_read_num("AFL_FRIDA_STATS_INTERVAL");
+
+}
+
+void stats_init(void) {
+
+  OKF("Stats - file [%s]", stats_filename);
+  OKF("Stats - interval [%" G_GINT64_MODIFIER "u]", stats_interval);
+
+  if (stats_interval != 0 && stats_filename == NULL) {
+
+    FATAL(
+        "AFL_FRIDA_STATS_FILE must be specified if "
+        "AFL_FRIDA_STATS_INTERVAL is");
+
+  }
+
+  if (stats_interval == 0) { stats_interval = 10; }
+  stats_interval_us = stats_interval * MICRO_TO_SEC;
 
   if (stats_filename == NULL) { return; }
 
-  if (stats_interval == 0) { return; }
+  char *path = g_canonicalize_filename(stats_filename, g_get_current_dir());
+
+  OKF("Stats - path [%s]", path);
 
-  current_time = g_get_monotonic_time();
+  stats_fd = open(path, O_RDWR | O_CREAT | O_TRUNC,
+                  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+
+  if (stats_fd < 0) { FATAL("Failed to open stats file '%s'", path); }
 
-  if ((current_time - stats_data->stats_last_time) >
-      (stats_interval * MICRO_TO_SEC)) {
+  g_free(path);
 
-    stats_write();
-    stats_data->stats_last_time = current_time;
+  int shm_id =
+      shmget(IPC_PRIVATE, sizeof(stats_data_t), IPC_CREAT | IPC_EXCL | 0600);
+  if (shm_id < 0) { FATAL("shm_id < 0 - errno: %d\n", errno); }
+
+  stats_data = shmat(shm_id, NULL, 0);
+  g_assert(stats_data != MAP_FAILED);
+
+  GumStalkerObserver *observer = stalker_get_observer();
+  stats_observer_init(observer);
+
+  /*
+   * Configure the shared memory region to be removed once the process dies.
+   */
+  if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
+
+    FATAL("shmctl (IPC_RMID) < 0 - errno: %d\n", errno);
 
   }
 
+  /* Clear it, not sure it's necessary, just seems like good practice */
+  memset(stats_data, '\0', sizeof(stats_data_t));
+
+  starts_arch_init();
+
 }
 
-void stats_collect(const cs_insn *instr, gboolean begin) {
+void stats_print(char *format, ...) {
 
-  UNUSED_PARAMETER(instr);
-  UNUSED_PARAMETER(begin);
+  char buffer[4096] = {0};
+  int  len;
 
-  if (stats_fd < 0) { return; }
+  va_list ap;
+  va_start(ap, format);
 
-  if (begin) { stats_data->num_blocks++; }
-  stats_data->num_instructions++;
+  if (vsnprintf(buffer, sizeof(buffer) - 1, format, ap) < 0) { return; }
 
-  stats_collect_arch(instr);
+  len = strnlen(buffer, sizeof(buffer));
+  IGNORED_RETURN(write(stats_fd, buffer, len));
+  va_end(ap);
+
+}
+
+void stats_on_fork(void) {
+
+  stats_write();
+
+}
+
+void stats_collect(const cs_insn *instr, gboolean begin) {
+
+  if (!entry_compiled) { return; }
+  if (stats_filename == NULL) { return; }
+  stats_collect_arch(instr, begin);
 
 }
 
diff --git a/frida_mode/src/stats/stats_arm32.c b/frida_mode/src/stats/stats_arm32.c
index 71953af3..5860d33b 100644
--- a/frida_mode/src/stats/stats_arm32.c
+++ b/frida_mode/src/stats/stats_arm32.c
@@ -7,27 +7,22 @@
 
 #if defined(__arm__)
 
-gboolean stats_is_supported_arch(void) {
-
-  return FALSE;
-
-}
-
-size_t stats_data_size_arch(void) {
+void starts_arch_init(void) {
 
   FATAL("Stats not supported on this architecture");
 
 }
 
-void stats_write_arch(void) {
+void stats_write_arch(stats_data_t *data) {
 
   FATAL("Stats not supported on this architecture");
 
 }
 
-void stats_collect_arch(const cs_insn *instr) {
+void stats_collect_arch(const cs_insn *instr, gboolean begin) {
 
   UNUSED_PARAMETER(instr);
+  UNUSED_PARAMETER(begin);
   FATAL("Stats not supported on this architecture");
 
 }
diff --git a/frida_mode/src/stats/stats_arm64.c b/frida_mode/src/stats/stats_arm64.c
index d9d374a4..54b3faf1 100644
--- a/frida_mode/src/stats/stats_arm64.c
+++ b/frida_mode/src/stats/stats_arm64.c
@@ -1,34 +1,323 @@
+#include <sys/shm.h>
+#include <sys/mman.h>
+
 #include "frida-gumjs.h"
 
 #include "debug.h"
 
+#include "ranges.h"
 #include "stats.h"
 #include "util.h"
 
+#define MICRO_TO_SEC 1000000
+
 #if defined(__aarch64__)
 
-gboolean stats_is_supported_arch(void) {
+typedef struct {
+
+  guint64 num_blocks;
+  guint64 num_instructions;
+
+  guint64 num_eob;
+  guint64 num_reloc;
+
+  guint64 num_adr;
+  guint64 num_adrp;
+
+  guint64 num_b;
+  guint64 num_bcc;
+  guint64 num_bl;
+  guint64 num_br;
+
+  guint64 num_cbz;
+  guint64 num_cbnz;
+
+  guint64 num_ldr;
+  guint64 num_ldrsw;
+
+  guint64 num_ret;
+
+  guint64 num_tbz;
+  guint64 num_tbnz;
+
+} stats_data_arch_t;
+
+static stats_data_arch_t *stats_data_arch = NULL;
+
+void starts_arch_init(void) {
+
+  int shm_id = shmget(IPC_PRIVATE, sizeof(stats_data_arch_t),
+                      IPC_CREAT | IPC_EXCL | 0600);
+  if (shm_id < 0) { FATAL("shm_id < 0 - errno: %d\n", errno); }
+
+  stats_data_arch = shmat(shm_id, NULL, 0);
+  g_assert(stats_data_arch != MAP_FAILED);
+
+  /*
+   * Configure the shared memory region to be removed once the process dies.
+   */
+  if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
+
+    FATAL("shmctl (IPC_RMID) < 0 - errno: %d\n", errno);
 
-  return FALSE;
+  }
+
+  /* Clear it, not sure it's necessary, just seems like good practice */
+  memset(stats_data_arch, '\0', sizeof(stats_data_arch_t));
 
 }
 
-size_t stats_data_size_arch(void) {
+static void stats_write_arch_stat(char *label, guint64 value, guint64 total) {
+
+  stats_print("%-30s ", label);
+  stats_print("%10" G_GINT64_MODIFIER "u ", value);
+  if (total == 0) {
+
+    stats_print("(--.--%%), ");
+
+  } else {
+
+    stats_print("(%5.2f%%) ", ((float)value * 100) / total);
 
-  FATAL("Stats not supported on this architecture");
+  }
+
+  stats_print("\n");
 
 }
 
-void stats_write_arch(void) {
+static void stats_write_arch_stat_delta(char *label, guint64 prev_value,
+                                        guint64 curr_value, guint elapsed,
+                                        guint64 prev_total,
+                                        guint64 curr_total) {
+
+  guint64 delta = curr_value - prev_value;
+  guint64 delta_total = curr_total - prev_total;
+  guint64 per_sec = delta / elapsed;
+
+  stats_print("%-30s ", label);
+
+  stats_print("%10" G_GINT64_MODIFIER "u ", curr_value);
+  if (curr_total == 0) {
+
+    stats_print("(--.--%%), ");
+
+  } else {
+
+    stats_print("(%5.2f%%) ", ((float)curr_value * 100) / curr_total);
+
+  }
+
+  stats_print("%10" G_GINT64_MODIFIER "u ", delta);
+  if (delta_total == 0) {
+
+    stats_print("(--.--%%), ");
+
+  } else {
+
+    stats_print("(%5.2f%%) ", ((float)delta * 100) / delta_total);
 
-  FATAL("Stats not supported on this architecture");
+  }
+
+  stats_print("[%10" G_GINT64_MODIFIER "u/s]", per_sec);
+  stats_print("\n");
 
 }
 
-void stats_collect_arch(const cs_insn *instr) {
+void stats_write_arch(stats_data_t *data) {
+
+  guint elapsed =
+      (data->curr.stats_time - data->prev.stats_time) / MICRO_TO_SEC;
+  stats_print("%-30s %10s %19s\n", "Transitions", "cumulative", "delta");
+  stats_print("%-30s %10s %19s\n", "-----------", "----------", "-----");
+  stats_print(
+      "%-30s %10" G_GINT64_MODIFIER "u %-8s %10" G_GINT64_MODIFIER "u\n",
+      "total", data->curr.total, "", data->curr.total - data->prev.total);
+  stats_write_arch_stat_delta("call_imm", data->prev.call_imm,
+                              data->curr.call_imm, elapsed, data->prev.total,
+                              data->curr.total);
+  stats_write_arch_stat_delta("call_reg", data->prev.call_reg,
+                              data->curr.call_reg, elapsed, data->prev.total,
+                              data->curr.total);
+  stats_write_arch_stat_delta("excluded_call_reg", data->prev.excluded_call_reg,
+                              data->curr.excluded_call_reg, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("ret", data->prev.ret, data->curr.ret, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("post_call_invoke", data->prev.post_call_invoke,
+                              data->curr.post_call_invoke, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("excluded_call_imm", data->prev.excluded_call_imm,
+                              data->curr.excluded_call_imm, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_imm", data->prev.jmp_imm, data->curr.jmp_imm,
+                              elapsed, data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_reg", data->prev.jmp_reg, data->curr.jmp_reg,
+                              elapsed, data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_cond_cc", data->prev.jmp_cond_cc,
+                              data->curr.jmp_cond_cc, elapsed, data->prev.total,
+                              data->curr.total);
+  stats_write_arch_stat_delta("jmp_cond_cbz", data->prev.jmp_cond_cbz,
+                              data->curr.jmp_cond_cbz, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_cond_cbnz", data->prev.jmp_cond_cbnz,
+                              data->curr.jmp_cond_cbnz, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_cond_tbz", data->prev.jmp_cond_tbz,
+                              data->curr.jmp_cond_tbz, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_cond_tbnz", data->prev.jmp_cond_tbnz,
+                              data->curr.jmp_cond_tbnz, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_continuation", data->prev.jmp_continuation,
+                              data->curr.jmp_continuation, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_print("\n");
+  stats_print("\n");
+
+  stats_print("Instrumentation\n");
+  stats_print("---------------\n");
+  stats_print("%-30s %10" G_GINT64_MODIFIER "u\n", "Instructions",
+              stats_data_arch->num_instructions);
+  stats_print("%-30s %10" G_GINT64_MODIFIER "u\n", "Blocks",
+              stats_data_arch->num_blocks);
+
+  if (stats_data_arch->num_blocks != 0) {
+
+    stats_print(
+        "%-30s %10" G_GINT64_MODIFIER "u\n", "Avg Instructions / Block ",
+        stats_data_arch->num_instructions / stats_data_arch->num_blocks);
+
+  }
+
+  stats_print("\n");
+  stats_print("\n");
+
+  guint64 num_instructions = stats_data_arch->num_instructions;
+
+  stats_print("EOB Instructions\n");
+  stats_print("----------------\n");
+  stats_write_arch_stat("Total", stats_data_arch->num_eob, num_instructions);
+  stats_write_arch_stat("B", stats_data_arch->num_b, num_instructions);
+  stats_write_arch_stat("Bcc", stats_data_arch->num_bcc, num_instructions);
+  stats_write_arch_stat("BL", stats_data_arch->num_bl, num_instructions);
+  stats_write_arch_stat("BR", stats_data_arch->num_br, num_instructions);
+  stats_write_arch_stat("CBZ", stats_data_arch->num_cbz, num_instructions);
+  stats_write_arch_stat("CBNZ", stats_data_arch->num_cbnz, num_instructions);
+  stats_write_arch_stat("RET", stats_data_arch->num_ret, num_instructions);
+  stats_write_arch_stat("TBZ", stats_data_arch->num_tbz, num_instructions);
+  stats_write_arch_stat("TBNZ", stats_data_arch->num_tbnz, num_instructions);
+  stats_print("\n");
+  stats_print("\n");
+
+  stats_print("Relocated Instructions\n");
+  stats_print("----------------------\n");
+  stats_write_arch_stat("Total", stats_data_arch->num_reloc, num_instructions);
+
+  stats_write_arch_stat("ADR", stats_data_arch->num_adr, num_instructions);
+  stats_write_arch_stat("ADRP", stats_data_arch->num_adrp, num_instructions);
+  stats_write_arch_stat("LDR", stats_data_arch->num_ldr, num_instructions);
+  stats_write_arch_stat("LDRSW", stats_data_arch->num_ldrsw, num_instructions);
+
+  stats_print("\n");
+  stats_print("\n");
+
+}
+
+void stats_collect_arch(const cs_insn *instr, gboolean begin) {
+
+  if (stats_data_arch == NULL) { return; }
+  if (begin) { stats_data_arch->num_blocks++; }
+  stats_data_arch->num_instructions++;
+
+  switch (instr->id) {
+
+    case ARM64_INS_ADR:
+      stats_data_arch->num_adr++;
+      stats_data_arch->num_reloc++;
+      break;
+
+    case ARM64_INS_ADRP:
+      stats_data_arch->num_adrp++;
+      stats_data_arch->num_reloc++;
+      break;
+
+    case ARM64_INS_B:
+      switch (instr->detail->arm64.cc) {
+
+        case ARM64_CC_INVALID:
+        case ARM64_CC_AL:
+        case ARM64_CC_NV:
+          stats_data_arch->num_b++;
+          break;
+        default:
+          stats_data_arch->num_bcc++;
+          break;
+
+      }
+
+      stats_data_arch->num_eob++;
+      break;
+
+    case ARM64_INS_BR:
+    case ARM64_INS_BRAA:
+    case ARM64_INS_BRAAZ:
+    case ARM64_INS_BRAB:
+    case ARM64_INS_BRABZ:
+      stats_data_arch->num_br++;
+      stats_data_arch->num_eob++;
+      break;
+
+    case ARM64_INS_BL:
+    case ARM64_INS_BLR:
+    case ARM64_INS_BLRAA:
+    case ARM64_INS_BLRAAZ:
+    case ARM64_INS_BLRAB:
+    case ARM64_INS_BLRABZ:
+      stats_data_arch->num_bl++;
+      stats_data_arch->num_eob++;
+      break;
+
+    case ARM64_INS_CBZ:
+      stats_data_arch->num_cbz++;
+      stats_data_arch->num_eob++;
+      break;
+
+    case ARM64_INS_CBNZ:
+      stats_data_arch->num_cbnz++;
+      stats_data_arch->num_eob++;
+      break;
+
+    case ARM64_INS_LDR:
+      stats_data_arch->num_ldr++;
+      stats_data_arch->num_reloc++;
+      break;
+
+    case ARM64_INS_LDRSW:
+      stats_data_arch->num_ldrsw++;
+      stats_data_arch->num_reloc++;
+      break;
+
+    case ARM64_INS_RET:
+    case ARM64_INS_RETAA:
+    case ARM64_INS_RETAB:
+      stats_data_arch->num_ret++;
+      stats_data_arch->num_eob++;
+      break;
+
+    case ARM64_INS_TBZ:
+      stats_data_arch->num_tbz++;
+      stats_data_arch->num_eob++;
+      break;
+
+    case ARM64_INS_TBNZ:
+      stats_data_arch->num_tbnz++;
+      stats_data_arch->num_eob++;
+      break;
+
+    default:
+      break;
 
-  UNUSED_PARAMETER(instr);
-  FATAL("Stats not supported on this architecture");
+  }
 
 }
 
diff --git a/frida_mode/src/stats/stats_x64.c b/frida_mode/src/stats/stats_x64.c
deleted file mode 100644
index 11464a2a..00000000
--- a/frida_mode/src/stats/stats_x64.c
+++ /dev/null
@@ -1,325 +0,0 @@
-#include "frida-gumjs.h"
-
-#include "debug.h"
-
-#include "ranges.h"
-#include "stats.h"
-#include "util.h"
-
-#if defined(__x86_64__)
-
-typedef struct {
-
-  stats_data_header_t header;
-
-  guint64 num_call_imm;
-  guint64 num_call_imm_excluded;
-  guint64 num_call_reg;
-  guint64 num_call_mem;
-
-  guint64 num_jmp_imm;
-  guint64 num_jmp_reg;
-  guint64 num_jmp_mem;
-
-  guint64 num_jmp_cond_imm;
-  guint64 num_jmp_cond_reg;
-  guint64 num_jmp_cond_mem;
-
-  guint64 num_jmp_cond_jcxz;
-
-  guint64 num_ret;
-
-  guint64 num_rip_relative;
-
-  guint64 num_rip_relative_type[X86_INS_ENDING];
-  char    name_rip_relative_type[X86_INS_ENDING][CS_MNEMONIC_SIZE];
-
-} stats_data_arch_t;
-
-gboolean stats_is_supported_arch(void) {
-
-  return TRUE;
-
-}
-
-size_t stats_data_size_arch(void) {
-
-  return sizeof(stats_data_arch_t);
-
-}
-
-void stats_write_arch(void) {
-
-  stats_data_arch_t *stats_data_arch = (stats_data_arch_t *)stats_data;
-  guint64 num_instructions = stats_data_arch->header.num_instructions;
-
-  stats_print(
-      "Call Immediates:                %" G_GINT64_MODIFIER
-      "u "
-      "(%3.2f%%)\n",
-      stats_data_arch->num_call_imm,
-      ((float)(stats_data_arch->num_call_imm * 100) / num_instructions));
-  stats_print("Call Immediates Excluded:       %" G_GINT64_MODIFIER
-              "u "
-              "(%3.2f%%)\n",
-              stats_data_arch->num_call_imm_excluded,
-              ((float)(stats_data_arch->num_call_imm_excluded * 100) /
-               num_instructions));
-  stats_print(
-      "Call Register:                  %" G_GINT64_MODIFIER
-      "u "
-      "(%3.2f%%)\n",
-      stats_data_arch->num_call_reg,
-      ((float)(stats_data_arch->num_call_reg * 100) / num_instructions));
-  stats_print(
-      "Call Memory:                    %" G_GINT64_MODIFIER
-      "u "
-      "(%3.2f%%)\n",
-      stats_data_arch->num_call_mem,
-      ((float)(stats_data_arch->num_call_mem * 100) / num_instructions));
-
-  stats_print("\n");
-
-  stats_print("Jump Immediates:                %" G_GINT64_MODIFIER
-              "u "
-              "(%3.2f%%)\n",
-              stats_data_arch->num_jmp_imm,
-              ((float)(stats_data_arch->num_jmp_imm * 100) / num_instructions));
-  stats_print("Jump Register:                  %" G_GINT64_MODIFIER
-              "u "
-              "(%3.2f%%)\n",
-              stats_data_arch->num_jmp_reg,
-              ((float)(stats_data_arch->num_jmp_reg * 100) / num_instructions));
-  stats_print("Jump Memory:                    %" G_GINT64_MODIFIER
-              "u "
-              "(%3.2f%%)\n",
-              stats_data_arch->num_jmp_mem,
-              ((float)(stats_data_arch->num_jmp_mem * 100) / num_instructions));
-
-  stats_print("\n");
-
-  stats_print(
-      "Conditional Jump Immediates:    %" G_GINT64_MODIFIER
-      "u "
-      "(%3.2f%%)\n",
-      stats_data_arch->num_jmp_cond_imm,
-      ((float)(stats_data_arch->num_jmp_cond_imm * 100) / num_instructions));
-  stats_print(
-      "Conditional Jump CX Immediate:  %" G_GINT64_MODIFIER
-      "u "
-      "(%3.2f%%)\n",
-      stats_data_arch->num_jmp_cond_jcxz,
-      ((float)(stats_data_arch->num_jmp_cond_jcxz * 100) / num_instructions));
-  stats_print(
-      "Conditional Jump Register:      %" G_GINT64_MODIFIER
-      "u "
-      "(%3.2f%%)\n",
-      stats_data_arch->num_jmp_cond_reg,
-      ((float)(stats_data_arch->num_jmp_cond_reg * 100) / num_instructions));
-  stats_print(
-      "Conditional Jump Memory:        %" G_GINT64_MODIFIER
-      "u "
-      "(%3.2f%%)\n",
-      stats_data_arch->num_jmp_cond_mem,
-      ((float)(stats_data_arch->num_jmp_cond_mem * 100) / num_instructions));
-
-  stats_print("\n");
-
-  stats_print("Returns:                        %" G_GINT64_MODIFIER
-              "u "
-              "(%3.2f%%)\n",
-              stats_data_arch->num_ret,
-              (stats_data_arch->num_ret * 100 / num_instructions));
-
-  stats_print("\n");
-
-  stats_print("Rip Relative:                   %" G_GINT64_MODIFIER
-              "u "
-              "(%3.2f%%)\n",
-              stats_data_arch->num_rip_relative,
-              (stats_data_arch->num_rip_relative * 100 / num_instructions));
-
-  for (size_t i = 0; i < X86_INS_ENDING; i++) {
-
-    if (stats_data_arch->num_rip_relative_type[i] != 0) {
-
-      stats_print("                     %10d %s\n",
-                  stats_data_arch->num_rip_relative_type[i],
-                  stats_data_arch->name_rip_relative_type[i]);
-
-    }
-
-  }
-
-  stats_print("\n");
-  stats_print("\n");
-
-}
-
-static x86_op_type stats_get_operand_type(const cs_insn *instr) {
-
-  cs_x86 *   x86 = &instr->detail->x86;
-  cs_x86_op *operand;
-
-  if (x86->op_count != 1) {
-
-    FATAL("Unexpected operand count (%d): %s %s\n", x86->op_count,
-          instr->mnemonic, instr->op_str);
-
-  }
-
-  operand = &x86->operands[0];
-
-  return operand->type;
-
-}
-
-static void stats_collect_call_imm_excluded_arch(const cs_insn *instr) {
-
-  stats_data_arch_t *stats_data_arch = (stats_data_arch_t *)stats_data;
-  cs_x86 *           x86 = &instr->detail->x86;
-  cs_x86_op *        operand = &x86->operands[0];
-
-  if (range_is_excluded((gpointer)operand->imm)) {
-
-    stats_data_arch->num_call_imm_excluded++;
-
-  }
-
-}
-
-static void stats_collect_call_arch(const cs_insn *instr) {
-
-  stats_data_arch_t *stats_data_arch = (stats_data_arch_t *)stats_data;
-  x86_op_type        type = stats_get_operand_type(instr);
-  switch (type) {
-
-    case X86_OP_IMM:
-      stats_data_arch->num_call_imm++;
-      stats_collect_call_imm_excluded_arch(instr);
-      break;
-    case X86_OP_REG:
-      stats_data_arch->num_call_reg++;
-      break;
-    case X86_OP_MEM:
-      stats_data_arch->num_call_mem++;
-      break;
-    default:
-      FATAL("Invalid operand type: %s %s\n", instr->mnemonic, instr->op_str);
-
-  }
-
-}
-
-static void stats_collect_jump_arch(const cs_insn *instr) {
-
-  stats_data_arch_t *stats_data_arch = (stats_data_arch_t *)stats_data;
-  x86_op_type        type = stats_get_operand_type(instr);
-  switch (type) {
-
-    case X86_OP_IMM:
-      stats_data_arch->num_jmp_imm++;
-      break;
-    case X86_OP_REG:
-      stats_data_arch->num_jmp_reg++;
-      break;
-    case X86_OP_MEM:
-      stats_data_arch->num_jmp_mem++;
-      break;
-    default:
-      FATAL("Invalid operand type: %s %s\n", instr->mnemonic, instr->op_str);
-
-  }
-
-}
-
-static void stats_collect_jump_cond_arch(const cs_insn *instr) {
-
-  stats_data_arch_t *stats_data_arch = (stats_data_arch_t *)stats_data;
-  x86_op_type        type = stats_get_operand_type(instr);
-  switch (type) {
-
-    case X86_OP_IMM:
-      stats_data_arch->num_jmp_cond_imm++;
-      break;
-    case X86_OP_REG:
-      stats_data_arch->num_jmp_cond_reg++;
-      break;
-    case X86_OP_MEM:
-      stats_data_arch->num_jmp_cond_mem++;
-      break;
-    default:
-      FATAL("Invalid operand type: %s %s\n", instr->mnemonic, instr->op_str);
-
-  }
-
-}
-
-static void stats_collect_rip_relative_arch(const cs_insn *instr) {
-
-  stats_data_arch_t *stats_data_arch = (stats_data_arch_t *)stats_data;
-  cs_x86 *           x86 = &instr->detail->x86;
-  guint              mod;
-  guint              rm;
-
-  if (x86->encoding.modrm_offset == 0) { return; }
-
-  mod = (x86->modrm & 0xc0) >> 6;
-  if (mod != 0) { return; }
-
-  rm = (x86->modrm & 0x07) >> 0;
-  if (rm != 5) { return; }
-
-  stats_data_arch->num_rip_relative++;
-  stats_data_arch->num_rip_relative_type[instr->id]++;
-  memcpy(stats_data_arch->name_rip_relative_type[instr->id], instr->mnemonic,
-         CS_MNEMONIC_SIZE);
-
-}
-
-void stats_collect_arch(const cs_insn *instr) {
-
-  stats_data_arch_t *stats_data_arch = (stats_data_arch_t *)stats_data;
-  switch (instr->id) {
-
-    case X86_INS_CALL:
-      stats_collect_call_arch(instr);
-      break;
-    case X86_INS_JMP:
-      stats_collect_jump_arch(instr);
-      break;
-    case X86_INS_JA:
-    case X86_INS_JAE:
-    case X86_INS_JB:
-    case X86_INS_JBE:
-    case X86_INS_JE:
-    case X86_INS_JG:
-    case X86_INS_JGE:
-    case X86_INS_JL:
-    case X86_INS_JLE:
-    case X86_INS_JNE:
-    case X86_INS_JNO:
-    case X86_INS_JNP:
-    case X86_INS_JNS:
-    case X86_INS_JO:
-    case X86_INS_JP:
-    case X86_INS_JS:
-      stats_collect_jump_cond_arch(instr);
-      break;
-    case X86_INS_JECXZ:
-    case X86_INS_JRCXZ:
-      stats_data_arch->num_jmp_cond_jcxz++;
-      break;
-    case X86_INS_RET:
-      stats_data_arch->num_ret++;
-      break;
-    default:
-      stats_collect_rip_relative_arch(instr);
-      break;
-
-  }
-
-}
-
-#endif
-
diff --git a/frida_mode/src/stats/stats_x86.c b/frida_mode/src/stats/stats_x86.c
deleted file mode 100644
index d9c4f652..00000000
--- a/frida_mode/src/stats/stats_x86.c
+++ /dev/null
@@ -1,36 +0,0 @@
-#include "frida-gumjs.h"
-
-#include "debug.h"
-
-#include "stats.h"
-#include "util.h"
-
-#if defined(__i386__)
-
-gboolean stats_is_supported_arch(void) {
-
-  return FALSE;
-
-}
-
-size_t stats_data_size_arch(void) {
-
-  FATAL("Stats not supported on this architecture");
-
-}
-
-void stats_write_arch(void) {
-
-  FATAL("Stats not supported on this architecture");
-
-}
-
-void stats_collect_arch(const cs_insn *instr) {
-
-  UNUSED_PARAMETER(instr);
-  FATAL("Stats not supported on this architecture");
-
-}
-
-#endif
-
diff --git a/frida_mode/src/stats/stats_x86_64.c b/frida_mode/src/stats/stats_x86_64.c
new file mode 100644
index 00000000..ab914951
--- /dev/null
+++ b/frida_mode/src/stats/stats_x86_64.c
@@ -0,0 +1,420 @@
+#include <sys/shm.h>
+#include <sys/mman.h>
+
+#include "frida-gumjs.h"
+
+#include "debug.h"
+
+#include "ranges.h"
+#include "stats.h"
+#include "util.h"
+
+#define MICRO_TO_SEC 1000000
+
+#if defined(__x86_64__) || defined(__i386__)
+
+typedef struct {
+
+  guint64 num_blocks;
+  guint64 num_instructions;
+
+  guint64 num_eob;
+
+  guint64 num_call_imm;
+  guint64 num_call_imm_excluded;
+  guint64 num_call_reg;
+  guint64 num_call_mem;
+
+  guint64 num_jmp_imm;
+  guint64 num_jmp_reg;
+  guint64 num_jmp_mem;
+
+  guint64 num_jmp_cond_imm;
+  guint64 num_jmp_cond_reg;
+  guint64 num_jmp_cond_mem;
+
+  guint64 num_jmp_cond_jcxz;
+
+  guint64 num_ret;
+
+  guint64 num_rip_relative;
+
+  guint64 num_rip_relative_type[X86_INS_ENDING];
+  char    name_rip_relative_type[X86_INS_ENDING][CS_MNEMONIC_SIZE];
+
+} stats_data_arch_t;
+
+static stats_data_arch_t *stats_data_arch = NULL;
+
+void starts_arch_init(void) {
+
+  int shm_id = shmget(IPC_PRIVATE, sizeof(stats_data_arch_t),
+                      IPC_CREAT | IPC_EXCL | 0600);
+  if (shm_id < 0) { FATAL("shm_id < 0 - errno: %d\n", errno); }
+
+  stats_data_arch = shmat(shm_id, NULL, 0);
+  g_assert(stats_data_arch != MAP_FAILED);
+
+  /*
+   * Configure the shared memory region to be removed once the process dies.
+   */
+  if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
+
+    FATAL("shmctl (IPC_RMID) < 0 - errno: %d\n", errno);
+
+  }
+
+  /* Clear it, not sure it's necessary, just seems like good practice */
+  memset(stats_data_arch, '\0', sizeof(stats_data_arch_t));
+
+}
+
+static void stats_write_arch_stat(char *label, guint64 value, guint64 total) {
+
+  stats_print("%-30s ", label);
+  stats_print("%10" G_GINT64_MODIFIER "u ", value);
+  if (total == 0) {
+
+    stats_print("(--.--%%), ");
+
+  } else {
+
+    stats_print("(%5.2f%%) ", ((float)value * 100) / total);
+
+  }
+
+  stats_print("\n");
+
+}
+
+static void stats_write_arch_stat_delta(char *label, guint64 prev_value,
+                                        guint64 curr_value, guint elapsed,
+                                        guint64 prev_total,
+                                        guint64 curr_total) {
+
+  guint64 delta = curr_value - prev_value;
+  guint64 delta_total = curr_total - prev_total;
+  guint64 per_sec = delta / elapsed;
+
+  stats_print("%-30s ", label);
+
+  stats_print("%10" G_GINT64_MODIFIER "u ", curr_value);
+  if (curr_total == 0) {
+
+    stats_print("(--.--%%), ");
+
+  } else {
+
+    stats_print("(%5.2f%%) ", ((float)curr_value * 100) / curr_total);
+
+  }
+
+  stats_print("%10" G_GINT64_MODIFIER "u ", delta);
+  if (delta_total == 0) {
+
+    stats_print("(--.--%%), ");
+
+  } else {
+
+    stats_print("(%5.2f%%) ", ((float)delta * 100) / delta_total);
+
+  }
+
+  stats_print("[%10" G_GINT64_MODIFIER "u/s]", per_sec);
+  stats_print("\n");
+
+}
+
+void stats_write_arch(stats_data_t *data) {
+
+  guint elapsed =
+      (data->curr.stats_time - data->prev.stats_time) / MICRO_TO_SEC;
+  stats_print("%-30s %10s %19s\n", "Transitions", "cumulative", "delta");
+  stats_print("%-30s %10s %19s\n", "-----------", "----------", "-----");
+  stats_print(
+      "%-30s %10" G_GINT64_MODIFIER "u %-8s %10" G_GINT64_MODIFIER "u\n",
+      "total", data->curr.total, "", data->curr.total - data->prev.total);
+  stats_write_arch_stat_delta("call_imm", data->prev.call_imm,
+                              data->curr.call_imm, elapsed, data->prev.total,
+                              data->curr.total);
+  stats_write_arch_stat_delta("call_reg", data->prev.call_reg,
+                              data->curr.call_reg, elapsed, data->prev.total,
+                              data->curr.total);
+  stats_write_arch_stat_delta("call_mem", data->prev.call_mem,
+                              data->curr.call_mem, elapsed, data->prev.total,
+                              data->curr.total);
+  stats_write_arch_stat_delta("ret_slow_path", data->prev.ret_slow_path,
+                              data->curr.ret_slow_path, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("post_call_invoke", data->prev.post_call_invoke,
+                              data->curr.post_call_invoke, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("excluded_call_imm", data->prev.excluded_call_imm,
+                              data->curr.excluded_call_imm, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_imm", data->prev.jmp_imm, data->curr.jmp_imm,
+                              elapsed, data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_reg", data->prev.jmp_reg, data->curr.jmp_reg,
+                              elapsed, data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_mem", data->prev.jmp_mem, data->curr.jmp_mem,
+                              elapsed, data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_cond_imm", data->prev.jmp_cond_imm,
+                              data->curr.jmp_cond_imm, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_cond_mem", data->prev.jmp_cond_mem,
+                              data->curr.jmp_cond_mem, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_cond_reg", data->prev.jmp_cond_reg,
+                              data->curr.jmp_cond_reg, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_cond_jcxz", data->prev.jmp_cond_jcxz,
+                              data->curr.jmp_cond_jcxz, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_write_arch_stat_delta("jmp_continuation", data->prev.jmp_continuation,
+                              data->curr.jmp_continuation, elapsed,
+                              data->prev.total, data->curr.total);
+  stats_print("\n");
+  stats_print("\n");
+
+  stats_print("Instrumentation\n");
+  stats_print("---------------\n");
+  stats_print("%-30s %10" G_GINT64_MODIFIER "u\n", "Instructions",
+              stats_data_arch->num_instructions);
+  stats_print("%-30s %10" G_GINT64_MODIFIER "u\n", "Blocks",
+              stats_data_arch->num_blocks);
+
+  if (stats_data_arch->num_blocks != 0) {
+
+    stats_print(
+        "%-30s %10" G_GINT64_MODIFIER "u\n", "Avg Instructions / Block ",
+        stats_data_arch->num_instructions / stats_data_arch->num_blocks);
+
+  }
+
+  stats_print("\n");
+  stats_print("\n");
+
+  guint64 num_instructions = stats_data_arch->num_instructions;
+
+  stats_print("EOB Instructions\n");
+  stats_print("----------------\n");
+  stats_write_arch_stat("Total", stats_data_arch->num_eob, num_instructions);
+  stats_write_arch_stat("Call Immediates", stats_data_arch->num_call_imm,
+                        num_instructions);
+  stats_write_arch_stat("Call Immediates Excluded",
+                        stats_data_arch->num_call_imm_excluded,
+                        num_instructions);
+  stats_write_arch_stat("Call Register", stats_data_arch->num_call_reg,
+                        num_instructions);
+  stats_write_arch_stat("Call Memory", stats_data_arch->num_call_mem,
+                        num_instructions);
+  stats_write_arch_stat("Jump Immediates", stats_data_arch->num_jmp_imm,
+                        num_instructions);
+  stats_write_arch_stat("Jump Register", stats_data_arch->num_jmp_reg,
+                        num_instructions);
+  stats_write_arch_stat("Jump Memory", stats_data_arch->num_jmp_mem,
+                        num_instructions);
+  stats_write_arch_stat("Conditional Jump Immediates",
+                        stats_data_arch->num_jmp_cond_imm, num_instructions);
+  stats_write_arch_stat("Conditional Jump CX Immediate",
+                        stats_data_arch->num_jmp_cond_jcxz, num_instructions);
+  stats_write_arch_stat("Conditional Jump Register",
+                        stats_data_arch->num_jmp_cond_reg, num_instructions);
+  stats_write_arch_stat("Conditional Jump Memory",
+                        stats_data_arch->num_jmp_cond_mem, num_instructions);
+  stats_write_arch_stat("Returns", stats_data_arch->num_ret, num_instructions);
+  stats_print("\n");
+  stats_print("\n");
+
+  stats_print("Relocated Instructions\n");
+  stats_print("----------------------\n");
+  stats_write_arch_stat("Total", stats_data_arch->num_rip_relative,
+                        num_instructions);
+
+  for (size_t i = 0; i < X86_INS_ENDING; i++) {
+
+    if (stats_data_arch->num_rip_relative_type[i] != 0) {
+
+      stats_write_arch_stat(stats_data_arch->name_rip_relative_type[i],
+                            stats_data_arch->num_rip_relative_type[i],
+                            stats_data_arch->num_rip_relative);
+
+    }
+
+  }
+
+  stats_print("\n");
+  stats_print("\n");
+
+}
+
+static x86_op_type stats_get_operand_type(const cs_insn *instr) {
+
+  cs_x86 *   x86 = &instr->detail->x86;
+  cs_x86_op *operand;
+
+  if (x86->op_count != 1) {
+
+    FATAL("Unexpected operand count (%d): %s %s\n", x86->op_count,
+          instr->mnemonic, instr->op_str);
+
+  }
+
+  operand = &x86->operands[0];
+
+  return operand->type;
+
+}
+
+static void stats_collect_call_imm_excluded_arch(const cs_insn *instr) {
+
+  cs_x86 *   x86 = &instr->detail->x86;
+  cs_x86_op *operand = &x86->operands[0];
+
+  if (range_is_excluded(GUM_ADDRESS(operand->imm))) {
+
+    stats_data_arch->num_call_imm_excluded++;
+
+  }
+
+}
+
+static void stats_collect_call_arch(const cs_insn *instr) {
+
+  x86_op_type type = stats_get_operand_type(instr);
+  switch (type) {
+
+    case X86_OP_IMM:
+      stats_data_arch->num_call_imm++;
+      stats_collect_call_imm_excluded_arch(instr);
+      break;
+    case X86_OP_REG:
+      stats_data_arch->num_call_reg++;
+      break;
+    case X86_OP_MEM:
+      stats_data_arch->num_call_mem++;
+      break;
+    default:
+      FATAL("Invalid operand type: %s %s\n", instr->mnemonic, instr->op_str);
+
+  }
+
+}
+
+static void stats_collect_jump_arch(const cs_insn *instr) {
+
+  x86_op_type type = stats_get_operand_type(instr);
+  switch (type) {
+
+    case X86_OP_IMM:
+      stats_data_arch->num_jmp_imm++;
+      break;
+    case X86_OP_REG:
+      stats_data_arch->num_jmp_reg++;
+      break;
+    case X86_OP_MEM:
+      stats_data_arch->num_jmp_mem++;
+      break;
+    default:
+      FATAL("Invalid operand type: %s %s\n", instr->mnemonic, instr->op_str);
+
+  }
+
+}
+
+static void stats_collect_jump_cond_arch(const cs_insn *instr) {
+
+  x86_op_type type = stats_get_operand_type(instr);
+  switch (type) {
+
+    case X86_OP_IMM:
+      stats_data_arch->num_jmp_cond_imm++;
+      break;
+    case X86_OP_REG:
+      stats_data_arch->num_jmp_cond_reg++;
+      break;
+    case X86_OP_MEM:
+      stats_data_arch->num_jmp_cond_mem++;
+      break;
+    default:
+      FATAL("Invalid operand type: %s %s\n", instr->mnemonic, instr->op_str);
+
+  }
+
+}
+
+static void stats_collect_rip_relative_arch(const cs_insn *instr) {
+
+  cs_x86 *x86 = &instr->detail->x86;
+  guint   mod;
+  guint   rm;
+
+  if (x86->encoding.modrm_offset == 0) { return; }
+
+  mod = (x86->modrm & 0xc0) >> 6;
+  if (mod != 0) { return; }
+
+  rm = (x86->modrm & 0x07) >> 0;
+  if (rm != 5) { return; }
+
+  stats_data_arch->num_rip_relative++;
+  stats_data_arch->num_rip_relative_type[instr->id]++;
+  memcpy(stats_data_arch->name_rip_relative_type[instr->id], instr->mnemonic,
+         CS_MNEMONIC_SIZE);
+
+}
+
+void stats_collect_arch(const cs_insn *instr, gboolean begin) {
+
+  if (stats_data_arch == NULL) { return; }
+  if (begin) { stats_data_arch->num_blocks++; }
+  stats_data_arch->num_instructions++;
+
+  switch (instr->id) {
+
+    case X86_INS_CALL:
+      stats_collect_call_arch(instr);
+      stats_data_arch->num_eob++;
+      break;
+    case X86_INS_JMP:
+      stats_collect_jump_arch(instr);
+      stats_data_arch->num_eob++;
+      break;
+    case X86_INS_JA:
+    case X86_INS_JAE:
+    case X86_INS_JB:
+    case X86_INS_JBE:
+    case X86_INS_JE:
+    case X86_INS_JG:
+    case X86_INS_JGE:
+    case X86_INS_JL:
+    case X86_INS_JLE:
+    case X86_INS_JNE:
+    case X86_INS_JNO:
+    case X86_INS_JNP:
+    case X86_INS_JNS:
+    case X86_INS_JO:
+    case X86_INS_JP:
+    case X86_INS_JS:
+      stats_collect_jump_cond_arch(instr);
+      stats_data_arch->num_eob++;
+      break;
+    case X86_INS_JECXZ:
+    case X86_INS_JRCXZ:
+      stats_data_arch->num_jmp_cond_jcxz++;
+      stats_data_arch->num_eob++;
+      break;
+    case X86_INS_RET:
+      stats_data_arch->num_ret++;
+      stats_data_arch->num_eob++;
+      break;
+    default:
+      stats_collect_rip_relative_arch(instr);
+      break;
+
+  }
+
+}
+
+#endif
+
diff --git a/frida_mode/test/cmplog/GNUmakefile b/frida_mode/test/cmplog/GNUmakefile
index 4c71bb33..bcaff42d 100644
--- a/frida_mode/test/cmplog/GNUmakefile
+++ b/frida_mode/test/cmplog/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../../)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 
 TEST_CMPLOG_SRC=$(PWD)cmplog.c
diff --git a/frida_mode/test/deferred/GNUmakefile b/frida_mode/test/deferred/GNUmakefile
index f7520051..22aeb2bf 100644
--- a/frida_mode/test/deferred/GNUmakefile
+++ b/frida_mode/test/deferred/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 TESTINSTR_DATA_DIR:=$(BUILD_DIR)in/
 TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)in
diff --git a/frida_mode/test/entry_point/GNUmakefile b/frida_mode/test/entry_point/GNUmakefile
index 5453c1ad..08c660f7 100644
--- a/frida_mode/test/entry_point/GNUmakefile
+++ b/frida_mode/test/entry_point/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 TESTINSTR_DATA_DIR:=$(BUILD_DIR)in/
 TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)in
diff --git a/frida_mode/test/exe/GNUmakefile b/frida_mode/test/exe/GNUmakefile
index 86e5a461..c86ae894 100644
--- a/frida_mode/test/exe/GNUmakefile
+++ b/frida_mode/test/exe/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 TESTINSTR_DATA_DIR:=$(BUILD_DIR)in/
 TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)in
diff --git a/frida_mode/test/fasan/GNUmakefile b/frida_mode/test/fasan/GNUmakefile
index c971c724..e150a6db 100644
--- a/frida_mode/test/fasan/GNUmakefile
+++ b/frida_mode/test/fasan/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 
 TEST_DATA_DIR:=$(BUILD_DIR)in/
diff --git a/frida_mode/test/jpeg/GNUmakefile b/frida_mode/test/jpeg/GNUmakefile
index 1c124743..ca5101cb 100644
--- a/frida_mode/test/jpeg/GNUmakefile
+++ b/frida_mode/test/jpeg/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 
 AFLPP_FRIDA_DRIVER_HOOK_OBJ=$(ROOT)frida_mode/build/frida_hook.so
diff --git a/frida_mode/test/js/GNUmakefile b/frida_mode/test/js/GNUmakefile
index ee8d4ebc..aad81d08 100644
--- a/frida_mode/test/js/GNUmakefile
+++ b/frida_mode/test/js/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 TEST_DATA_DIR:=$(BUILD_DIR)in/
 TEST_DATA_FILE:=$(TEST_DATA_DIR)in
diff --git a/frida_mode/test/libpcap/GNUmakefile b/frida_mode/test/libpcap/GNUmakefile
index 1263ce60..6f2b58af 100644
--- a/frida_mode/test/libpcap/GNUmakefile
+++ b/frida_mode/test/libpcap/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 
 AFLPP_FRIDA_DRIVER_HOOK_OBJ=$(ROOT)frida_mode/build/frida_hook.so
@@ -142,7 +142,7 @@ $(TEST_BIN): $(HARNESS_OBJ) $(PCAPTEST_OBJ) $(LIBPCAP_LIB)
 ########## DUMMY #######
 
 $(AFLPP_DRIVER_DUMMY_INPUT): | $(TCPDUMP_TESTS_DIR)
-	dd if=/dev/zero bs=1M count=1 of=$@
+	dd if=/dev/zero bs=1048576 count=1 of=$@
 
 ###### TEST DATA #######
 
diff --git a/frida_mode/test/osx-lib/GNUmakefile b/frida_mode/test/osx-lib/GNUmakefile
index de0af27b..96dbb5ad 100644
--- a/frida_mode/test/osx-lib/GNUmakefile
+++ b/frida_mode/test/osx-lib/GNUmakefile
@@ -52,7 +52,7 @@ $(TESTINSTR_DATA_FILE): | $(TESTINSTR_DATA_DIR)
 	echo -n "$$FA$$" > $@
 
 $(AFLPP_DRIVER_DUMMY_INPUT): | $(BUILD_DIR)
-	dd if=/dev/zero bs=1M count=1 of=$@
+	dd if=/dev/zero bs=1048576 count=1 of=$@
 
 $(HARNESS_BIN): $(HARNESS_SRC) | $(BUILD_DIR)
 	$(CC) $(CFLAGS) $(LDFLAGS) $(HARNESS_LDFLAGS) -o $@ $<
diff --git a/frida_mode/test/output/GNUmakefile b/frida_mode/test/output/GNUmakefile
index eaa1c4dc..201c23b7 100644
--- a/frida_mode/test/output/GNUmakefile
+++ b/frida_mode/test/output/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 TESTINSTR_DATA_DIR:=$(BUILD_DIR)in/
 TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)in
diff --git a/frida_mode/test/persistent_ret/GNUmakefile b/frida_mode/test/persistent_ret/GNUmakefile
index adcacf5a..71f6c693 100644
--- a/frida_mode/test/persistent_ret/GNUmakefile
+++ b/frida_mode/test/persistent_ret/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 TESTINSTR_DATA_DIR:=$(BUILD_DIR)in/
 TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)in
diff --git a/frida_mode/test/png/GNUmakefile b/frida_mode/test/png/GNUmakefile
index a1a7f1a5..0f591508 100644
--- a/frida_mode/test/png/GNUmakefile
+++ b/frida_mode/test/png/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 
 LIBPNG_BUILD_DIR:=$(BUILD_DIR)libpng/
diff --git a/frida_mode/test/png/persistent/GNUmakefile b/frida_mode/test/png/persistent/GNUmakefile
index f6ccfcb5..c6526fee 100644
--- a/frida_mode/test/png/persistent/GNUmakefile
+++ b/frida_mode/test/png/persistent/GNUmakefile
@@ -1,9 +1,9 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../../..)/
+ROOT:=$(PWD)../../../../
 BUILD_DIR:=$(PWD)build/
 
 TEST_BIN:=$(PWD)../build/test
-TEST_DATA_DIR:=../build/libpng/libpng-1.2.56/contrib/pngsuite/
+TEST_DATA_DIR:=$(PWD)../build/libpng/libpng-1.2.56/contrib/pngsuite/
 
 AFLPP_DRIVER_DUMMY_INPUT:=$(BUILD_DIR)in
 QEMU_OUT:=$(BUILD_DIR)qemu-out
@@ -49,7 +49,7 @@ $(BUILD_DIR):
 	mkdir -p $@
 
 $(AFLPP_DRIVER_DUMMY_INPUT): | $(BUILD_DIR)
-	dd if=/dev/zero bs=1M count=1 of=$@
+	dd if=/dev/zero bs=1048576 count=1 of=$@
 
 qemu: | $(BUILD_DIR)
 	AFL_QEMU_PERSISTENT_ADDR=$(AFL_QEMU_PERSISTENT_ADDR) \
diff --git a/frida_mode/test/png/persistent/hook/GNUmakefile b/frida_mode/test/png/persistent/hook/GNUmakefile
index 049861dd..5010662b 100644
--- a/frida_mode/test/png/persistent/hook/GNUmakefile
+++ b/frida_mode/test/png/persistent/hook/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../../../..)/
+ROOT:=$(PWD)../../../../../
 BUILD_DIR:=$(PWD)build/
 
 AFLPP_FRIDA_DRIVER_HOOK_OBJ=$(ROOT)frida_mode/build/frida_hook.so
@@ -72,7 +72,7 @@ $(TEST_DATA_DIR): | $(BUILD_DIR)
 	mkdir -p $@
 
 $(AFLPP_DRIVER_DUMMY_INPUT): | $(BUILD_DIR)
-	dd if=/dev/zero bs=1M count=1 of=$@
+	dd if=/dev/zero bs=1048576 count=1 of=$@
 
 qemu: $(AFLPP_DRIVER_DUMMY_INPUT) $(AFLPP_QEMU_DRIVER_HOOK_OBJ) | $(BUILD_DIR)
 	AFL_QEMU_PERSISTENT_HOOK=$(AFLPP_QEMU_DRIVER_HOOK_OBJ) \
@@ -127,6 +127,21 @@ frida_entry: $(AFLPP_DRIVER_DUMMY_INPUT) $(AFLPP_FRIDA_DRIVER_HOOK_OBJ) | $(BUIL
 		-- \
 			$(TEST_BIN) $(AFLPP_DRIVER_DUMMY_INPUT)
 
+frida_entry_slow: $(AFLPP_DRIVER_DUMMY_INPUT) $(AFLPP_FRIDA_DRIVER_HOOK_OBJ) | $(BUILD_DIR)
+	AFL_FRIDA_PERSISTENT_HOOK=$(AFLPP_FRIDA_DRIVER_HOOK_OBJ) \
+	AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_PERSISTENT_ADDR) \
+	AFL_ENTRYPOINT=$(AFL_FRIDA_PERSISTENT_ADDR) \
+	AFL_FRIDA_STALKER_IC_ENTRIES=2 \
+	AFL_FRIDA_INST_NO_PREFETCH_BACKPATCH=1 \
+	$(ROOT)afl-fuzz \
+		-D \
+		-V 30 \
+		-O \
+		-i $(TEST_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(TEST_BIN) $(AFLPP_DRIVER_DUMMY_INPUT)
+
 frida_js_load: $(AFLPP_DRIVER_DUMMY_INPUT) $(AFLPP_FRIDA_DRIVER_HOOK_OBJ) | $(BUILD_DIR)
 	AFL_PRELOAD=$(AFL_PRELOAD) \
 	AFL_FRIDA_JS_SCRIPT=load.js \
diff --git a/frida_mode/test/proj4/GNUmakefile b/frida_mode/test/proj4/GNUmakefile
index 8555ebad..7dffab2e 100644
--- a/frida_mode/test/proj4/GNUmakefile
+++ b/frida_mode/test/proj4/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 
 AFLPP_FRIDA_DRIVER_HOOK_OBJ=$(ROOT)frida_mode/build/frida_hook.so
diff --git a/frida_mode/test/re2/GNUmakefile b/frida_mode/test/re2/GNUmakefile
index 8e1f3682..67b37a89 100644
--- a/frida_mode/test/re2/GNUmakefile
+++ b/frida_mode/test/re2/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 
 AFLPP_FRIDA_DRIVER_HOOK_OBJ=$(ROOT)frida_mode/build/frida_hook.so
@@ -124,7 +124,7 @@ $(TEST_DATA_DIR): | $(BUILD_DIR)
 	mkdir -p $@
 
 $(AFLPP_DRIVER_DUMMY_INPUT): | $(TEST_DATA_DIR)
-	dd if=/dev/zero bs=1M count=1 of=$@
+	dd if=/dev/zero bs=1048576 count=1 of=$@
 
 ###### TEST DATA #######
 
diff --git a/frida_mode/test/sqlite/GNUmakefile b/frida_mode/test/sqlite/GNUmakefile
index 1c856d1e..85f213a9 100644
--- a/frida_mode/test/sqlite/GNUmakefile
+++ b/frida_mode/test/sqlite/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 
 SQLITE_BUILD_DIR:=$(BUILD_DIR)sqlite/
@@ -120,7 +120,7 @@ sqlite: $(SQLITE_TEST_DIR) $(TEST_BIN)
 ########## DUMMY #######
 
 $(AFLPP_DRIVER_DUMMY_INPUT): | $(SQLITE_TEST_DIR)
-	dd if=/dev/zero bs=1M count=1 of=$@
+	dd if=/dev/zero bs=1048576 count=1 of=$@
 
 ###### TEST DATA #######
 
@@ -156,6 +156,22 @@ frida: $(TEST_BIN) $(AFLPP_FRIDA_DRIVER_HOOK_OBJ) $(AFLPP_DRIVER_DUMMY_INPUT) |
 		-- \
 			$(TEST_BIN) $(AFLPP_DRIVER_DUMMY_INPUT)
 
+frida_slow: $(TEST_BIN) $(AFLPP_FRIDA_DRIVER_HOOK_OBJ) $(AFLPP_DRIVER_DUMMY_INPUT) | $(SQLITE_TEST_DIR)
+	AFL_FRIDA_PERSISTENT_CNT=1000000 \
+	AFL_FRIDA_PERSISTENT_HOOK=$(AFLPP_FRIDA_DRIVER_HOOK_OBJ) \
+	AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_PERSISTENT_ADDR) \
+	AFL_ENTRYPOINT=$(AFL_FRIDA_PERSISTENT_ADDR) \
+	AFL_FRIDA_STALKER_IC_ENTRIES=2 \
+	AFL_FRIDA_INST_NO_PREFETCH_BACKPATCH=1 \
+	$(ROOT)afl-fuzz \
+		-D \
+		-V 30 \
+		-O \
+		-i $(SQLITE_TEST_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(TEST_BIN) $(AFLPP_DRIVER_DUMMY_INPUT)
+
 debug:
 	gdb \
 		--ex 'set environment LD_PRELOAD=$(ROOT)afl-frida-trace.so' \
diff --git a/frida_mode/test/testinstr/GNUmakefile b/frida_mode/test/testinstr/GNUmakefile
index 3701ddc8..79eee213 100644
--- a/frida_mode/test/testinstr/GNUmakefile
+++ b/frida_mode/test/testinstr/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 TESTINSTR_DATA_DIR:=$(BUILD_DIR)in/
 TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)in
diff --git a/frida_mode/test/unstable/GNUmakefile b/frida_mode/test/unstable/GNUmakefile
index 938d7c17..c5fe1b88 100644
--- a/frida_mode/test/unstable/GNUmakefile
+++ b/frida_mode/test/unstable/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)../../..)/
+ROOT:=$(PWD)../../../
 BUILD_DIR:=$(PWD)build/
 UNSTABLE_DATA_DIR:=$(BUILD_DIR)in/
 UNSTABLE_DATA_FILE:=$(UNSTABLE_DATA_DIR)in
diff --git a/frida_mode/ts/lib/afl.ts b/frida_mode/ts/lib/afl.ts
index c1ed123e..d7f8b7bc 100644
--- a/frida_mode/ts/lib/afl.ts
+++ b/frida_mode/ts/lib/afl.ts
@@ -203,6 +203,13 @@ class Afl {
   }
 
   /**
+   * See `AFL_FRIDA_INST_NO_PREFETCH_BACKPATCH`.
+   */
+  public static setPrefetchBackpatchDisable(): void {
+    Afl.jsApiSetPrefetchBackpatchDisable();
+  }
+
+  /**
    * See `AFL_FRIDA_INST_NO_PREFETCH`.
    */
   public static setPrefetchDisable(): void {
@@ -218,6 +225,13 @@ class Afl {
   }
 
   /**
+   * See `AFL_FRIDA_STALKER_IC_ENTRIES`.
+   */
+  public static setStalkerIcEntries(val: number): void {
+    Afl.jsApiSetStalkerIcEntries(val);
+  }
+
+  /**
    * See `AFL_FRIDA_STATS_FILE`. This function takes a single `string` as
    * an argument.
    */
@@ -235,13 +249,6 @@ class Afl {
   }
 
   /**
-   * See `AFL_FRIDA_STATS_TRANSITIONS`
-   */
-  public static setStatsTransitions(): void {
-    Afl.jsApiSetStatsTransitions();
-  }
-
-  /**
    * See `AFL_FRIDA_OUTPUT_STDERR`. This function takes a single `string` as
    * an argument.
    */
@@ -356,6 +363,11 @@ class Afl {
     "void",
     ["pointer"]);
 
+  private static readonly jsApiSetPrefetchBackpatchDisable = Afl.jsApiGetFunction(
+    "js_api_set_prefetch_backpatch_disable",
+    "void",
+    []);
+
   private static readonly jsApiSetPrefetchDisable = Afl.jsApiGetFunction(
     "js_api_set_prefetch_disable",
     "void",
@@ -366,6 +378,11 @@ class Afl {
     "void",
     ["pointer"]);
 
+  private static readonly jsApiSetStalkerIcEntries = Afl.jsApiGetFunction(
+    "js_api_set_stalker_ic_entries",
+    "void",
+    ["uint32"]);
+
   private static readonly jsApiSetStatsFile = Afl.jsApiGetFunction(
     "js_api_set_stats_file",
     "void",
@@ -376,11 +393,6 @@ class Afl {
     "void",
     ["uint64"]);
 
-  private static readonly jsApiSetStatsTransitions = Afl.jsApiGetFunction(
-    "js_api_set_stats_transitions",
-    "void",
-    []);
-
   private static readonly jsApiSetStdErr = Afl.jsApiGetFunction(
     "js_api_set_stderr",
     "void",
diff --git a/include/envs.h b/include/envs.h
index 722fe1a5..dd84748e 100644
--- a/include/envs.h
+++ b/include/envs.h
@@ -60,6 +60,7 @@ static char *afl_environment_variables[] = {
     "AFL_FRIDA_INST_JIT",
     "AFL_FRIDA_INST_NO_OPTIMIZE",
     "AFL_FRIDA_INST_NO_PREFETCH",
+    "AFL_FRIDA_INST_NO_PREFETCH_BACKPATCH",
     "AFL_FRIDA_INST_RANGES",
     "AFL_FRIDA_INST_SEED",
     "AFL_FRIDA_INST_TRACE",
@@ -74,7 +75,6 @@ static char *afl_environment_variables[] = {
     "AFL_FRIDA_PERSISTENT_RET",
     "AFL_FRIDA_STATS_FILE",
     "AFL_FRIDA_STATS_INTERVAL",
-    "AFL_FRIDA_STATS_TRANSITIONS",
     "AFL_FUZZER_ARGS",  // oss-fuzz
     "AFL_GDB",
     "AFL_GCC_ALLOWLIST",