about summary refs log tree commit diff
path: root/frida_mode
diff options
context:
space:
mode:
Diffstat (limited to 'frida_mode')
-rw-r--r--frida_mode/DEBUGGING.md160
-rw-r--r--frida_mode/GNUmakefile15
-rw-r--r--frida_mode/README.md143
-rw-r--r--frida_mode/Scripting.md8
-rw-r--r--frida_mode/frida.map3
-rw-r--r--frida_mode/include/entry.h3
-rw-r--r--frida_mode/include/instrument.h6
-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/many-linux/Dockerfile10
-rw-r--r--frida_mode/many-linux/GNUmakefile8
-rw-r--r--frida_mode/many-linux/README.md3
-rwxr-xr-x[-rw-r--r--]frida_mode/many-linux/realpath0
-rw-r--r--frida_mode/src/cmplog/cmplog.c4
-rw-r--r--frida_mode/src/entry.c10
-rw-r--r--frida_mode/src/instrument/instrument.c16
-rw-r--r--frida_mode/src/instrument/instrument_coverage.c374
-rw-r--r--frida_mode/src/js/api.js30
-rw-r--r--frida_mode/src/js/js_api.c29
-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/GNUmakefile163
-rw-r--r--frida_mode/test/osx-lib/Makefile12
-rw-r--r--frida_mode/test/osx-lib/harness.c69
-rw-r--r--frida_mode/test/osx-lib/harness2.c69
-rw-r--r--frida_mode/test/osx-lib/harness3.c40
-rw-r--r--frida_mode/test/osx-lib/lib.c17
-rw-r--r--frida_mode/test/osx-lib/lib2.c61
-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.ts50
57 files changed, 2485 insertions, 672 deletions
diff --git a/frida_mode/DEBUGGING.md b/frida_mode/DEBUGGING.md
new file mode 100644
index 00000000..69663510
--- /dev/null
+++ b/frida_mode/DEBUGGING.md
@@ -0,0 +1,160 @@
+If you are using FRIDA mode and have hit some problems, then this guide may help
+you to diagnose any problems you are encountering. This assumes you have
+followed the [osx-lib](#test/osx-lib) example to start fuzzing your target.
+
+It should be noted that attempting to debug code using gdb which has been
+instrumented in FRIDA is unlikely to be successful since the debugger will be
+inserting breakpoints by patching the code in memory. FRIDA works by reading
+this code and generating an instrumented copy to execute. In any case, unless
+you are very familiar with the implementation of Stalker, the instrumented code
+generated by FRIDA is likely to be very difficult to follow. For this reason,
+the following debugging strategies are outlined below.
+
+By convention below all files should be provided with their path (they are
+omitted for readability) and all items in `<braces>` are placeholders and should
+be replaced accordingly.
+
+# Select your version
+Test with both the `dev` and `stable` branches of AFL++. The `dev` branch should
+have the very latest version containing any fixes for identified issues. The
+`stable` branch is updated less frequently, but equally might avoid a problem if
+a regression has been introduced into the `dev` branch.
+
+# Enable Diagnostic Information
+- Run your target specifying the `AFL_DEBUG_CHILD=1` environment variable. This
+  will print a lot more diagnostic information to the screen when the target
+  starts up. If you have a simple configuration issue then you will likely see a
+  warning or error message in the output.
+
+# Check your Test Harness
+If any of the following steps fail, then there is a problem with your test
+harness, or your target library. Since this is running without FRIDA mode or
+`afl-fuzz` that greatly reduces the search area for your defect. This is why it
+is *VERY* important to carry out these basic steps first before taking on the
+additional complexity of debugging with FRIDA mode or `afl-fuzz`.
+
+- Run your harness outside of the fuzzer, passing it a representative seed as
+  it's input `./harness <input>`.
+- Pass you harness multiple seeds to check that it is stable when running
+  multiple tests as it will when running in fork server mode `./harness <input1>
+  <intput2>`.
+- Build your test harness with `CFLAGS=-fsanitize=address` and
+  `LDFLAGS=-fsanitize=address`. Then run it again with multiple inputs to check
+  for errors (note that when fuzzing your harness should not be built with any
+  sanitizer options).
+
+# Check the Samples
+FRIDA mode contains a number of different sample targets in the `test` folder.
+Have a look throught these and find one which is similar to your real target.
+Check whether you have any issues running the sample target and make sure you
+compare the command line used to launch the sample with that you are using to
+launch your real target very carefully to check for any differences. If possible
+start with one of these samples and gradually make changes one at a time
+re-testing as you go until you have migrated it to run your own target.
+
+# FRIDA Mode
+## Basic
+First just try running your target with `LD_PRELOAD=afl-frida-trace.so ./harness
+ <input>`. An error here means that your defect occurs when running with just
+ FRIDA mode and isn't related to `afl-fuzz`.
+
+Now you can try commenting out the implementation of `LLVMFuzzerTestOneInput` so
+that the harness doesn't actually run your target library. This may also aid in
+narrowing down the problem.
+```c
+int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size){
+    // fpn_crashme(data, size);
+    return 0;
+}
+
+```
+
+## Persistent Mode
+If your target is ok running in basic mode, you can try running it in persistent
+mode (if that is the configuration you are having issues with) as follows (again
+outside of afl-fuzz). This time you will want to run it inside a debugger so
+that you can use the debugger to send the `SIGCONT` signals (by continuing)
+usually sent by `afl-fuzz` on each iteration.
+
+```bash
+gdb \
+  --ex 'set environment __AFL_PERSISTENT=1' \
+  --ex 'set environment AFL_FRIDA_PERSISTENT_CNT=3' \
+  --ex 'set environment LD_PRELOAD=afl-frida-trace.so' \
+  --ex 'set environment AFL_FRIDA_PERSISTENT_ADDR=<entry_address>' \
+  --args ./harness <input>
+```
+Note we have to manually set the `__AFL_PERSISTENT` environment variable which
+is usually passed by `afl-fuzz`.
+
+Note that setting breakpoints etc is likely to interfere with FRIDA and cause
+spurious errors.
+
+If this is successful, you can try additionally loading the hook library:
+```bash
+gdb \
+  --ex 'set environment __AFL_PERSISTENT=1' \
+  --ex 'set environment AFL_FRIDA_PERSISTENT_CNT=3' \
+  --ex 'set environment LD_PRELOAD=afl-frida-trace.so' \
+  --ex 'set environment AFL_FRIDA_PERSISTENT_ADDR=<entry_address>' \
+  --ex 'set environment AFL_FRIDA_PERSISTENT_HOOK=frida_hook.so'
+  --args ./harness <input>
+```
+Note that the format of the hook used for FRIDA mode is subtly different to that
+used when running in QEMU mode as shown below. Thus the DSO used for the hook is
+not interchangeable.
+
+```c
+void afl_persistent_hook(GumCpuContext *regs, uint8_t *input_buf,
+                         uint32_t input_buf_len);
+
+void afl_persistent_hook(struct x86_64_regs *regs, uint64_t guest_base,
+                         uint8_t *input_buf, uint32_t input_buf_len);
+```
+
+## ASAN
+It is also possible to enable ASAN (if that is the configuration you are having
+issues with) without having to use `afl-fuzz`. This can be done as follows (note
+that the name of the asan DSO may need to be changed depending on your
+platform). Note that the asan DSO must appear first in the `LD_PRELOAD`
+environment variable:
+
+```bash
+LD_PRELOAD=libclang_rt.asan-x86_64.so:afl-frida-trace.so \
+ASAN_OPTIONS=detect_leaks=false,halt_on_error=0 \
+AFL_USE_FASAN=1 \
+  ./harness <input>
+```
+
+Note that care should be taken to ensure that if you set `AFL_INST_LIBS=1`, you
+use `AFL_FRIDA_INST_RANGES` or `AFL_FRIDA_EXCLUDE_RANGES` to exclude the ASAN
+DSO from coverage. Failure to do so will result in ASAN attempting to sanitize
+itself and as a result detecting failures when it attempts to update the shadow
+maps.
+
+# Printf
+If you have an idea of where things are going wrong for you, then don't be
+scared to add `printf` statements to either AFL++ or FRIDA mode itself to show
+more diagnostic information. Just be sure to set `AFL_DEBUG=1` and
+`AFL_DEBUG_CHILD=1` when you are testing it.
+
+# Core Dumps
+Lastly, if your defect only occurs when using `afl-fuzz` (e.g. when using
+`CMPLOG` which cannot be tested outside of `afl-fuzz` due to it's need for a
+shared memory mapping being created for it to record its data), it is possible
+to enable the creation of a core dump for post-mortem analysis.
+
+Firstly check your `/proc/sys/kernel/core_pattern` configuration is simply set
+to a filename (AFL++ encourages you to set it to the value 'core' in any case
+since it doesn't want any handler applications getting in the way). Next set
+`ulimit -c unlimited` to remove any size limitations for core files. Lastly,
+when you `afl-fuzz` set the environment variable `AFL_DEBUG=1` to enable the
+creation of the `core` file. The file should be created in the working directory
+of the target application. If there is an existing `core` file aleady there,
+then it may not be overwritten.
+
+# Reach out
+Get in touch on discord and ask for help. The groups are pretty active so
+someone may well be able to offer some advice. Better still, if you are able to
+create a minimal reproducer for your problem it will make it easier to diagnose
+the issue.
diff --git a/frida_mode/GNUmakefile b/frida_mode/GNUmakefile
index fad183e1..3e35e2f6 100644
--- a/frida_mode/GNUmakefile
+++ b/frida_mode/GNUmakefile
@@ -1,5 +1,5 @@
 PWD:=$(shell pwd)/
-ROOT:=$(shell realpath $(PWD)..)/
+ROOT:=$(PWD)../
 INC_DIR:=$(PWD)include/
 SRC_DIR:=$(PWD)src/
 INCLUDES:=$(wildcard $(INC_DIR)*.h)
@@ -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)"
 
@@ -166,7 +157,7 @@ $(GUM_DEVKIT_TARBALL): $(FRIDA_GUM_DEVKIT_COMPRESSED_TARBALL)| $(FRIDA_BUILD_DIR
 	cp -v $< $@
 else
 $(GUM_DEVKIT_TARBALL): | $(FRIDA_BUILD_DIR)
-	wget -O $@ $(GUM_DEVKIT_URL)
+	wget -O $@ $(GUM_DEVKIT_URL) || curl -L -o $@ $(GUM_DEVKIT_URL)
 endif
 
 $(GUM_DEVIT_LIBRARY): $(GUM_DEVKIT_TARBALL)
diff --git a/frida_mode/README.md b/frida_mode/README.md
index 3009e171..9f00c294 100644
--- a/frida_mode/README.md
+++ b/frida_mode/README.md
@@ -131,7 +131,8 @@ instances run CMPLOG mode and instrumentation of the binary is less frequent
 (only on CMP, SUB and CALL instructions) performance is not quite so critical.
 
 ## Advanced configuration options
-
+* `AFL_FRIDA_INST_COVERAGE_FILE` - File to write DynamoRio format coverage
+information (e.g. to be loaded within IDA lighthouse).
 * `AFL_FRIDA_INST_DEBUG_FILE` - File to write raw assembly of original blocks
 and their instrumented counterparts during block compilation.
 ```
@@ -161,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.
@@ -188,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
@@ -197,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.
@@ -300,6 +312,21 @@ to validate memory accesses against the shadow memory.
 FRIDA mode has also introduced some improvements to reduce collisions in the map.
 See [here](MapDensity.md) for details.
 
+# OSX Library Fuzzing
+An example of how to fuzz a dynamic library on OSX is included [here](test/osx-lib).
+This requires the use of a simple test harness executable which will load the
+library and call a target function within it. The dependent library can either
+be loaded in using `dlopen` and `dlsym` in a function marked
+`__attribute__((constructor()))` or the test harness can simply be linked
+against it. It is important that the target library is loaded before execution
+of `main`, since this is the point where FRIDA mode is initialized. Otherwise, it
+will not be possible to configure coverage for the test library using
+`AFL_FRIDA_INST_RANGES` or similar.
+
+# Debugging
+Please refer to the [debugging](#debugging) guide for assistant should you
+encounter problems with FRIDA mode.
+
 ## TODO
 
 The next features to be added are Aarch32 support as well as looking at
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 7223d50e..7ad5e682 100644
--- a/frida_mode/frida.map
+++ b/frida_mode/frida.map
@@ -10,6 +10,7 @@
     js_api_error;
     js_api_set_debug_maps;
     js_api_set_entrypoint;
+    js_api_set_instrument_coverage_file;
     js_api_set_instrument_debug_file;
     js_api_set_instrument_jit;
     js_api_set_instrument_libraries;
@@ -22,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/instrument.h b/frida_mode/include/instrument.h
index 29f14da9..2e8d6b6d 100644
--- a/frida_mode/include/instrument.h
+++ b/frida_mode/include/instrument.h
@@ -6,6 +6,7 @@
 #include "config.h"
 
 extern char *           instrument_debug_filename;
+extern char *           instrument_coverage_filename;
 extern gboolean         instrument_tracing;
 extern gboolean         instrument_optimize;
 extern gboolean         instrument_unique;
@@ -38,6 +39,11 @@ void     instrument_debug_end(GumStalkerOutput *output);
 void     instrument_flush(GumStalkerOutput *output);
 gpointer instrument_cur(GumStalkerOutput *output);
 
+void instrument_coverage_config(void);
+void instrument_coverage_init(void);
+void instrument_coverage_start(uint64_t address);
+void instrument_coverage_end(uint64_t address);
+
 void instrument_on_fork();
 
 guint64 instrument_get_offset_hash(GumAddress current_rip);
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/many-linux/Dockerfile b/frida_mode/many-linux/Dockerfile
index 2cd56bc8..170f0757 100644
--- a/frida_mode/many-linux/Dockerfile
+++ b/frida_mode/many-linux/Dockerfile
@@ -6,19 +6,9 @@ RUN chmod +x /bin/realpath
 RUN yum -y install xz
 RUN yum -y install vim-common
 
-WORKDIR /
-RUN git clone https://github.com/AFLplusplus/AFLplusplus.git
-
-WORKDIR /AFLplusplus
-RUN mkdir -p /AFLplusplus/frida_mode/build/frida/
-RUN curl -L -o /AFLplusplus/frida_mode/build/frida/frida-gumjs-devkit-15.0.0-linux-x86_64.tar.xz "https://github.com/frida/frida/releases/download/15.0.0/frida-gumjs-devkit-15.0.0-linux-x86_64.tar.xz"
-
 WORKDIR /AFLplusplus
-RUN git checkout dev
-WORKDIR /AFLplusplus/frida_mode
 ENV CFLAGS="\
     -DADDR_NO_RANDOMIZE=0x0040000 \
     -Wno-implicit-function-declaration \
     "
 ENV CXX=$CC
-RUN make
diff --git a/frida_mode/many-linux/GNUmakefile b/frida_mode/many-linux/GNUmakefile
index 2860f20c..03b619f6 100644
--- a/frida_mode/many-linux/GNUmakefile
+++ b/frida_mode/many-linux/GNUmakefile
@@ -1,20 +1,20 @@
 PWD:=$(shell pwd)/
+ROOT:=$(PWD)../../
 BUILD_DIR:=$(PWD)build/
 
 .PHONY: all clean shell
 
-all: | $(BUILD_DIR)
+all:
 	docker build --tag many-afl-frida .
 	docker run --rm \
-		-v $(PWD)build/:/export \
+		-v $(ROOT):/AFLplusplus \
 		many-afl-frida \
-		cp /AFLplusplus/afl-frida-trace.so /export
+		make -C /AFLplusplus/frida_mode clean all
 
 $(BUILD_DIR):
 	mkdir -p $@
 
 clean:
-	rm -rf $(BUILD_DIR)
 	docker images --filter 'dangling=true' -q --no-trunc | xargs -L1 docker rmi --force
 
 shell:
diff --git a/frida_mode/many-linux/README.md b/frida_mode/many-linux/README.md
index 2c7b6823..4bd7a6c1 100644
--- a/frida_mode/many-linux/README.md
+++ b/frida_mode/many-linux/README.md
@@ -5,4 +5,5 @@ This folder contains a Docker image to allow the building of
 based on CentOS Linux 5. By building `afl-frida-trace.so` for such an old
 version of Linux, given the strong backward compatibility of Linux, this should
 work on the majority of Linux environments. This may be useful for targetting
-Linux distributions other than your development environment.
\ No newline at end of file
+Linux distributions other than your development environment. `many-local` builds
+`AFLplusplus` from the local working copy in the `many-linux` environment.
diff --git a/frida_mode/many-linux/realpath b/frida_mode/many-linux/realpath
index 1fdc49a7..1fdc49a7 100644..100755
--- a/frida_mode/many-linux/realpath
+++ b/frida_mode/many-linux/realpath
diff --git a/frida_mode/src/cmplog/cmplog.c b/frida_mode/src/cmplog/cmplog.c
index a2609c8e..ae3116eb 100644
--- a/frida_mode/src/cmplog/cmplog.c
+++ b/frida_mode/src/cmplog/cmplog.c
@@ -56,7 +56,9 @@ void cmplog_config(void) {
 
 void cmplog_init(void) {
 
-  if (__afl_cmp_map != NULL) { OKF("CMPLOG mode enabled"); }
+  OKF("CMPLOG - Enabled [%c]", __afl_cmp_map == NULL ? ' ' : 'X');
+
+  if (__afl_cmp_map == NULL) { return; }
 
   cmplog_get_ranges();
 
diff --git a/frida_mode/src/entry.c b/frida_mode/src/entry.c
index a0ffd028..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,6 +24,7 @@ static void entry_launch(void) {
   __afl_manual_init();
 
   /* Child here */
+  entry_run = TRUE;
   instrument_on_fork();
   stats_on_fork();
 
@@ -36,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) {
@@ -48,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();
 
 }
@@ -59,7 +66,6 @@ void entry_prologue(GumStalkerIterator *iterator, GumStalkerOutput *output) {
 
   if (persistent_start == 0) {
 
-    entry_reached = TRUE;
     ranges_exclude();
     stalker_trust();
 
diff --git a/frida_mode/src/instrument/instrument.c b/frida_mode/src/instrument/instrument.c
index 67aafa5a..9e4dd191 100644
--- a/frida_mode/src/instrument/instrument.c
+++ b/frida_mode/src/instrument/instrument.c
@@ -116,8 +116,8 @@ __attribute__((hot)) static void on_basic_block(GumCpuContext *context,
 
   }
 
-  instrument_previous_pc =
-      ((current_pc & (MAP_SIZE - 1) >> 1)) | ((current_pc & 0x1) << 15);
+  instrument_previous_pc = ((current_pc & (MAP_SIZE - 1) >> 1)) |
+                           ((current_pc & 0x1) << (MAP_SIZE_POW2 - 1));
 
 }
 
@@ -164,19 +164,16 @@ 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);
 
     if (unlikely(begin)) {
 
       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)) {
 
@@ -216,6 +213,7 @@ static void instrument_basic_block(GumStalkerIterator *iterator,
 
   instrument_flush(output);
   instrument_debug_end(output);
+  instrument_coverage_end(instr->address + instr->size);
 
 }
 
@@ -228,6 +226,7 @@ void instrument_config(void) {
   instrument_fixed_seed = util_read_num("AFL_FRIDA_INST_SEED");
 
   instrument_debug_config();
+  instrument_coverage_config();
   asan_config();
   cmplog_config();
 
@@ -317,6 +316,7 @@ void instrument_init(void) {
   instrument_hash_zero = instrument_get_offset_hash(0);
 
   instrument_debug_init();
+  instrument_coverage_init();
   asan_init();
   cmplog_init();
 
diff --git a/frida_mode/src/instrument/instrument_coverage.c b/frida_mode/src/instrument/instrument_coverage.c
new file mode 100644
index 00000000..4c0d1a14
--- /dev/null
+++ b/frida_mode/src/instrument/instrument_coverage.c
@@ -0,0 +1,374 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include "frida-gumjs.h"
+
+#include "debug.h"
+
+#include "instrument.h"
+#include "util.h"
+
+char *instrument_coverage_filename = NULL;
+
+static int         coverage_fd = -1;
+static int         coverage_pipes[2] = {0};
+static uint64_t    coverage_last_start = 0;
+static GHashTable *coverage_hash = NULL;
+static GArray *    coverage_modules = NULL;
+static guint       coverage_marked_modules = 0;
+static guint       coverage_marked_entries = 0;
+
+typedef struct {
+
+  GumAddress base_address;
+  GumAddress limit;
+  gsize      size;
+  char       name[PATH_MAX + 1];
+  char       path[PATH_MAX + 1];
+  bool       referenced;
+  guint16    id;
+
+} coverage_module_t;
+
+typedef struct {
+
+  uint64_t           start;
+  uint64_t           end;
+  coverage_module_t *module;
+
+} coverage_data_t;
+
+typedef struct {
+
+  guint32 offset;
+  guint16 length;
+  guint16 module;
+
+} coverage_event_t;
+
+static gboolean coverage_module(const GumModuleDetails *details,
+                                gpointer                user_data) {
+
+  UNUSED_PARAMETER(user_data);
+  coverage_module_t coverage = {0};
+
+  coverage.base_address = details->range->base_address;
+  coverage.size = details->range->size;
+  coverage.limit = coverage.base_address + coverage.size;
+
+  if (details->name != NULL) strncpy(coverage.name, details->name, PATH_MAX);
+
+  if (details->path != NULL) strncpy(coverage.path, details->path, PATH_MAX);
+
+  coverage.referenced = false;
+  coverage.id = 0;
+
+  g_array_append_val(coverage_modules, coverage);
+  return TRUE;
+
+}
+
+static gint coverage_sort(gconstpointer a, gconstpointer b) {
+
+  coverage_module_t *ma = (coverage_module_t *)a;
+  coverage_module_t *mb = (coverage_module_t *)b;
+
+  if (ma->base_address < mb->base_address) return -1;
+
+  if (ma->base_address > mb->base_address) return 1;
+
+  return 0;
+
+}
+
+static void coverage_get_ranges(void) {
+
+  OKF("Coverage - Collecting ranges");
+
+  coverage_modules =
+      g_array_sized_new(false, false, sizeof(coverage_module_t), 100);
+  gum_process_enumerate_modules(coverage_module, NULL);
+  g_array_sort(coverage_modules, coverage_sort);
+
+  for (guint i = 0; i < coverage_modules->len; i++) {
+
+    coverage_module_t *module =
+        &g_array_index(coverage_modules, coverage_module_t, i);
+    OKF("Coverage Module - %3u: 0x%016" G_GINT64_MODIFIER
+        "X - 0x%016" G_GINT64_MODIFIER "X",
+        i, module->base_address, module->limit);
+
+  }
+
+}
+
+static void instrument_coverage_mark(void *key, void *value, void *user_data) {
+
+  UNUSED_PARAMETER(key);
+  UNUSED_PARAMETER(user_data);
+  coverage_data_t *val = (coverage_data_t *)value;
+  guint            i;
+
+  for (i = 0; i < coverage_modules->len; i++) {
+
+    coverage_module_t *module =
+        &g_array_index(coverage_modules, coverage_module_t, i);
+    if (val->start > module->limit) continue;
+
+    if (val->end >= module->limit) break;
+
+    val->module = module;
+    coverage_marked_entries++;
+    module->referenced = true;
+    return;
+
+  }
+
+  OKF("Coverage cannot find module for: 0x%016" G_GINT64_MODIFIER
+      "X - 0x%016" G_GINT64_MODIFIER "X %u %u",
+      val->start, val->end, i, coverage_modules->len);
+
+}
+
+static void coverage_write(void *data, size_t size) {
+
+  ssize_t written;
+  size_t  remain = size;
+
+  for (char *cursor = (char *)data; remain > 0;
+       remain -= written, cursor += written) {
+
+    written = write(coverage_fd, cursor, remain);
+
+    if (written < 0) {
+
+      FATAL("Coverage - Failed to write: %s (%d)\n", (char *)data, errno);
+
+    }
+
+  }
+
+}
+
+static void coverage_format(char *format, ...) {
+
+  va_list ap;
+  char    buffer[4096] = {0};
+  int     ret;
+  int     len;
+
+  va_start(ap, format);
+  ret = vsnprintf(buffer, sizeof(buffer) - 1, format, ap);
+  va_end(ap);
+
+  if (ret < 0) { return; }
+
+  len = strnlen(buffer, sizeof(buffer));
+
+  coverage_write(buffer, len);
+
+}
+
+static void coverage_write_modules() {
+
+  guint emitted = 0;
+  for (guint i = 0; i < coverage_modules->len; i++) {
+
+    coverage_module_t *module =
+        &g_array_index(coverage_modules, coverage_module_t, i);
+    if (!module->referenced) continue;
+
+    coverage_format("%3u, ", emitted);
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", module->base_address);
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", module->limit);
+    /* entry */
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", 0);
+    /* checksum */
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", 0);
+    /* timestamp */
+    coverage_format("%08" G_GINT32_MODIFIER "X, ", 0);
+    coverage_format("%s\n", module->path);
+    emitted++;
+
+  }
+
+}
+
+static void coverage_write_events(void *key, void *value, void *user_data) {
+
+  UNUSED_PARAMETER(key);
+  UNUSED_PARAMETER(user_data);
+  coverage_data_t *val = (coverage_data_t *)value;
+  coverage_event_t evt = {
+
+      .offset = val->start - val->module->base_address,
+      .length = val->end - val->start,
+      .module = val->module->id,
+
+  };
+
+  coverage_write(&evt, sizeof(coverage_event_t));
+
+}
+
+static void coverage_write_header() {
+
+  char version[] = "DRCOV VERSION: 2\n";
+  char flavour[] = "DRCOV FLAVOR: frida\n";
+  char columns[] = "Columns: id, base, end, entry, checksum, timestamp, path\n";
+  coverage_write(version, sizeof(version) - 1);
+  coverage_write(flavour, sizeof(flavour) - 1);
+  coverage_format("Module Table: version 2, count %u\n",
+                  coverage_marked_modules);
+  coverage_write(columns, sizeof(columns) - 1);
+  coverage_write_modules();
+  coverage_format("BB Table: %u bbs\n", coverage_marked_entries);
+  g_hash_table_foreach(coverage_hash, coverage_write_events, NULL);
+
+}
+
+static void coverage_mark_modules() {
+
+  guint i;
+  for (i = 0; i < coverage_modules->len; i++) {
+
+    coverage_module_t *module =
+        &g_array_index(coverage_modules, coverage_module_t, i);
+
+    OKF("Coverage Module - %3u: [%c] 0x%016" G_GINT64_MODIFIER
+        "X - 0x%016" G_GINT64_MODIFIER "X (%u:%s)",
+        i, module->referenced ? 'X' : ' ', module->base_address, module->limit,
+        module->id, module->path);
+
+    if (!module->referenced) { continue; }
+
+    module->id = coverage_marked_modules;
+    coverage_marked_modules++;
+
+  }
+
+}
+
+static void instrument_coverage_run() {
+
+  int              bytes;
+  coverage_data_t  data;
+  coverage_data_t *value;
+  OKF("Coverage - Running");
+
+  if (close(coverage_pipes[STDOUT_FILENO]) != 0) {
+
+    FATAL("Failed to close parent read pipe");
+
+  }
+
+  for (bytes =
+           read(coverage_pipes[STDIN_FILENO], &data, sizeof(coverage_data_t));
+       bytes == sizeof(coverage_data_t);
+       bytes =
+           read(coverage_pipes[STDIN_FILENO], &data, sizeof(coverage_data_t))) {
+
+    value = (coverage_data_t *)gum_malloc0(sizeof(coverage_data_t));
+    memcpy(value, &data, sizeof(coverage_data_t));
+    g_hash_table_insert(coverage_hash, GSIZE_TO_POINTER(data.start), value);
+
+  }
+
+  if (bytes != 0) { FATAL("Coverage data truncated"); }
+
+  OKF("Coverage - Preparing");
+
+  coverage_get_ranges();
+
+  guint size = g_hash_table_size(coverage_hash);
+  OKF("Coverage - Total Entries: %u", size);
+
+  g_hash_table_foreach(coverage_hash, instrument_coverage_mark, NULL);
+  OKF("Coverage - Marked Entries: %u", coverage_marked_entries);
+
+  coverage_mark_modules();
+  OKF("Coverage - Marked Modules: %u", coverage_marked_modules);
+
+  coverage_write_header();
+
+  OKF("Coverage - Completed");
+
+}
+
+void instrument_coverage_config(void) {
+
+  instrument_coverage_filename = getenv("AFL_FRIDA_INST_COVERAGE_FILE");
+
+}
+
+void instrument_coverage_init(void) {
+
+  OKF("Coverage - enabled [%c]",
+      instrument_coverage_filename == NULL ? ' ' : 'X');
+
+  if (instrument_coverage_filename == NULL) { return; }
+
+  OKF("Coverage - file [%s]", instrument_coverage_filename);
+
+  char *path = g_canonicalize_filename(instrument_coverage_filename,
+                                       g_get_current_dir());
+
+  OKF("Coverage - path [%s]", path);
+
+  coverage_fd = open(path, O_RDWR | O_CREAT | O_TRUNC,
+                     S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+
+  if (coverage_fd < 0) { FATAL("Failed to open coverage file '%s'", path); }
+
+  g_free(path);
+
+  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) {
+
+    FATAL("Failed to g_hash_table_new, errno: %d", errno);
+
+  }
+
+  pid_t pid = fork();
+  if (pid == -1) { FATAL("Failed to start coverage process"); }
+
+  if (pid == 0) {
+
+    instrument_coverage_run();
+    _exit(0);
+
+  }
+
+  if (close(coverage_pipes[STDIN_FILENO]) != 0) {
+
+    FATAL("Failed to close parent read pipe");
+
+  }
+
+}
+
+void instrument_coverage_start(uint64_t address) {
+
+  coverage_last_start = address;
+
+}
+
+void instrument_coverage_end(uint64_t address) {
+
+  coverage_data_t data = {
+
+      .start = coverage_last_start, .end = address, .module = NULL};
+
+  if (write(coverage_pipes[STDOUT_FILENO], &data, sizeof(coverage_data_t)) !=
+      sizeof(coverage_data_t)) {
+
+    FATAL("Coverage I/O error");
+
+  }
+
+}
+
diff --git a/frida_mode/src/js/api.js b/frida_mode/src/js/api.js
index b8f2d39a..71b5e4a4 100644
--- a/frida_mode/src/js/api.js
+++ b/frida_mode/src/js/api.js
@@ -86,6 +86,14 @@ class Afl {
         Afl.jsApiAflSharedMemFuzzing.writeInt(1);
     }
     /**
+     * See `AFL_FRIDA_INST_COVERAGE_FILE`. This function takes a single `string`
+     * as an argument.
+     */
+    static setInstrumentCoverageFile(file) {
+        const buf = Memory.allocUtf8String(file);
+        Afl.jsApiSetInstrumentCoverageFile(buf);
+    }
+    /**
      * See `AFL_FRIDA_INST_DEBUG_FILE`. This function takes a single `string` as
      * an argument.
      */
@@ -164,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() {
@@ -177,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.
      */
@@ -192,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.
      */
@@ -233,6 +247,7 @@ Afl.jsApiDone = Afl.jsApiGetFunction("js_api_done", "void", []);
 Afl.jsApiError = Afl.jsApiGetFunction("js_api_error", "void", ["pointer"]);
 Afl.jsApiSetDebugMaps = Afl.jsApiGetFunction("js_api_set_debug_maps", "void", []);
 Afl.jsApiSetEntryPoint = Afl.jsApiGetFunction("js_api_set_entrypoint", "void", ["pointer"]);
+Afl.jsApiSetInstrumentCoverageFile = Afl.jsApiGetFunction("js_api_set_instrument_coverage_file", "void", ["pointer"]);
 Afl.jsApiSetInstrumentDebugFile = Afl.jsApiGetFunction("js_api_set_instrument_debug_file", "void", ["pointer"]);
 Afl.jsApiSetInstrumentJit = Afl.jsApiGetFunction("js_api_set_instrument_jit", "void", []);
 Afl.jsApiSetInstrumentLibraries = Afl.jsApiGetFunction("js_api_set_instrument_libraries", "void", []);
@@ -245,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 930a6dc0..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;
@@ -107,6 +109,13 @@ __attribute__((visibility("default"))) void js_api_set_instrument_libraries() {
 
 }
 
+__attribute__((visibility("default"))) void js_api_set_instrument_coverage_file(
+    char *path) {
+
+  instrument_coverage_filename = g_strdup(path);
+
+}
+
 __attribute__((visibility("default"))) void js_api_set_instrument_debug_file(
     char *path) {
 
@@ -120,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) {
 
@@ -173,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) {
 
@@ -199,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 f1ad06e4..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)
-	truncate -s 1M $@
+	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
new file mode 100644
index 00000000..96dbb5ad
--- /dev/null
+++ b/frida_mode/test/osx-lib/GNUmakefile
@@ -0,0 +1,163 @@
+PWD:=$(shell pwd)/
+ROOT:=$(PWD)../../../
+BUILD_DIR:=$(PWD)build/
+TESTINSTR_DATA_DIR:=$(BUILD_DIR)in/
+TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)in
+AFLPP_DRIVER_DUMMY_INPUT:=$(BUILD_DIR)dummy.dat
+
+HARNESS_BIN:=$(BUILD_DIR)harness
+HARNESS_SRC:=$(PWD)harness.c
+
+HARNESS2_BIN:=$(BUILD_DIR)harness2
+HARNESS2_SRC:=$(PWD)harness2.c
+
+HARNESS3_BIN:=$(BUILD_DIR)harness3
+HARNESS3_SRC:=$(PWD)harness3.c
+
+LIB_BIN:=$(BUILD_DIR)libcrashme.dylib
+LIB_SRC:=$(PWD)lib.c
+
+LIB2_BIN:=$(BUILD_DIR)libcrashme2.dylib
+LIB2_SRC:=$(PWD)lib2.c
+
+QEMU_OUT:=$(BUILD_DIR)qemu-out
+FRIDA_OUT:=$(BUILD_DIR)frida-out
+
+HARNESS_LDFLAGS:=-Wl,-no_pie
+LIB_CFLAGS:=-dynamiclib
+
+GET_SYMBOL_ADDR:=$(ROOT)frida_mode/util/get_symbol_addr.sh
+AFL_FRIDA_MAIN_ADDR=$(shell $(GET_SYMBOL_ADDR) $(HARNESS_BIN) main 0x0)
+AFL_FRIDA_MAIN_ADDR2=$(shell $(GET_SYMBOL_ADDR) $(HARNESS2_BIN) main 0x0)
+AFL_FRIDA_FUZZ_ADDR=$(shell $(GET_SYMBOL_ADDR) $(HARNESS_BIN) LLVMFuzzerTestOneInput 0x0)
+AFL_FRIDA_FUZZ_ADDR2=$(shell $(GET_SYMBOL_ADDR) $(HARNESS2_BIN) LLVMFuzzerTestOneInput 0x0)
+AFL_FRIDA_FUZZ_ADDR3=$(shell $(GET_SYMBOL_ADDR) $(HARNESS3_BIN) LLVMFuzzerTestOneInput 0x0)
+
+AFLPP_FRIDA_DRIVER_HOOK_OBJ=$(ROOT)frida_mode/build/frida_hook.so
+
+TEST_FILE:=$(BUILD_DIR)test.dat
+
+.PHONY: all clean qemu frida
+
+all: $(HARNESS_BIN) $(LIB_BIN)
+	make -C $(ROOT)frida_mode/
+
+$(BUILD_DIR):
+	mkdir -p $@
+
+$(TESTINSTR_DATA_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+$(TESTINSTR_DATA_FILE): | $(TESTINSTR_DATA_DIR)
+	echo -n "$$FA$$" > $@
+
+$(AFLPP_DRIVER_DUMMY_INPUT): | $(BUILD_DIR)
+	dd if=/dev/zero bs=1048576 count=1 of=$@
+
+$(HARNESS_BIN): $(HARNESS_SRC) | $(BUILD_DIR)
+	$(CC) $(CFLAGS) $(LDFLAGS) $(HARNESS_LDFLAGS) -o $@ $<
+
+$(LIB_BIN): $(LIB_SRC) | $(BUILD_DIR)
+	$(CC) $(CFLAGS) $(LDFLAGS) $(LIB_CFLAGS) -o $@ $<
+
+$(LIB2_BIN): $(LIB2_SRC) | $(BUILD_DIR)
+	$(CC) $(CFLAGS) $(LDFLAGS) $(LIB_CFLAGS) -o $@ $<
+
+$(HARNESS2_BIN): $(HARNESS2_SRC) $(LIB_BIN) | $(BUILD_DIR)
+	$(CC) $(CFLAGS) $(LDFLAGS) $(HARNESS_LDFLAGS) -o $@ $<
+
+$(HARNESS3_BIN): $(HARNESS3_SRC) $(LIB_BIN) | $(BUILD_DIR)
+	$(CC) $(CFLAGS) $(LDFLAGS) $(HARNESS_LDFLAGS) -L$(BUILD_DIR) -lcrashme -o $@ $<
+
+clean:
+	rm -rf $(BUILD_DIR)
+
+LIBASAN?=/usr/local/opt/llvm/lib/clang/10.0.1/lib/darwin/libclang_rt.asan_osx_dynamic.dylib
+
+.ONESHELL:
+frida_persistent: $(HARNESS_BIN) $(LIB_BIN) $(TESTINSTR_DATA_FILE)
+	cd $(BUILD_DIR) && \
+	AFL_INST_LIBS=1 \
+	AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_MAIN_ADDR) \
+	AFL_FRIDA_PERSISTENT_CNT=1000000 \
+	AFL_ENTRYPOINT=$(AFL_FRIDA_MAIN_ADDR) \
+	$(ROOT)afl-fuzz \
+		-D \
+		-O \
+		-i $(TESTINSTR_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-f $(TEST_FILE) \
+		-- \
+			$(HARNESS_BIN) $(TEST_FILE)
+
+.ONESHELL:
+frida_persistent_asan: $(HARNESS2_BIN) $(LIB2_BIN) $(TESTINSTR_DATA_FILE)
+	cd $(BUILD_DIR) && \
+	AFL_PRELOAD=$(LIBASAN) \
+	AFL_USE_FASAN=1 \
+	AFL_INST_LIBS=1 \
+	AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_MAIN_ADDR2) \
+	AFL_FRIDA_PERSISTENT_CNT=1000000 \
+	AFL_ENTRYPOINT=$(AFL_FRIDA_MAIN_ADDR2) \
+	AFL_FRIDA_INST_RANGES=libcrashme2.dylib,harness2 \
+	$(ROOT)afl-fuzz \
+		-D \
+		-O \
+		-i $(TESTINSTR_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-f $(TEST_FILE) \
+		-- \
+			$(HARNESS2_BIN) $(TEST_FILE)
+
+.ONESHELL:
+frida_persistent_hook: $(HARNESS_BIN) $(LIB_BIN) $(AFLPP_DRIVER_DUMMY_INPUT) $(TESTINSTR_DATA_FILE)
+	cd $(BUILD_DIR) && \
+	AFL_INST_LIBS=1 \
+	AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_FUZZ_ADDR) \
+	AFL_FRIDA_PERSISTENT_CNT=1000000 \
+	AFL_ENTRYPOINT=$(AFL_FRIDA_FUZZ_ADDR) \
+	AFL_FRIDA_PERSISTENT_HOOK=$(AFLPP_FRIDA_DRIVER_HOOK_OBJ) \
+	AFL_FRIDA_INST_RANGES=libcrashme.dylib,harness \
+	$(ROOT)afl-fuzz \
+		-D \
+		-O \
+		-i $(TESTINSTR_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(HARNESS_BIN) $(AFLPP_DRIVER_DUMMY_INPUT)
+
+.ONESHELL:
+frida_persistent_hook_asan: $(HARNESS2_BIN) $(LIB2_BIN) $(AFLPP_DRIVER_DUMMY_INPUT) $(TESTINSTR_DATA_FILE)
+	cd $(BUILD_DIR) && \
+	AFL_PRELOAD=$(LIBASAN) \
+	AFL_USE_FASAN=1 \
+	AFL_INST_LIBS=1 \
+	AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_FUZZ_ADDR2) \
+	AFL_FRIDA_PERSISTENT_CNT=1000000 \
+	AFL_ENTRYPOINT=$(AFL_FRIDA_FUZZ_ADDR2) \
+	AFL_FRIDA_PERSISTENT_HOOK=$(AFLPP_FRIDA_DRIVER_HOOK_OBJ) \
+	AFL_FRIDA_INST_RANGES=libcrashme2.dylib,harness2 \
+	$(ROOT)afl-fuzz \
+		-D \
+		-O \
+		-i $(TESTINSTR_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(HARNESS2_BIN) $(AFLPP_DRIVER_DUMMY_INPUT)
+
+.ONESHELL:
+frida_persistent_hook3: $(HARNESS3_BIN) $(LIB_BIN) $(AFLPP_DRIVER_DUMMY_INPUT) $(TESTINSTR_DATA_FILE)
+	cd $(BUILD_DIR) && \
+	AFL_INST_LIBS=1 \
+	AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_FUZZ_ADDR3) \
+	AFL_FRIDA_PERSISTENT_CNT=1000000 \
+	AFL_ENTRYPOINT=$(AFL_FRIDA_FUZZ_ADDR3) \
+	AFL_FRIDA_PERSISTENT_HOOK=$(AFLPP_FRIDA_DRIVER_HOOK_OBJ) \
+	AFL_FRIDA_INST_RANGES=libcrashme.dylib,harness3 \
+	$(ROOT)afl-fuzz \
+		-D \
+		-O \
+		-i $(TESTINSTR_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(HARNESS3_BIN) $(AFLPP_DRIVER_DUMMY_INPUT)
diff --git a/frida_mode/test/osx-lib/Makefile b/frida_mode/test/osx-lib/Makefile
new file mode 100644
index 00000000..c084de41
--- /dev/null
+++ b/frida_mode/test/osx-lib/Makefile
@@ -0,0 +1,12 @@
+all:
+	@echo trying to use GNU make...
+	@gmake all || echo please install GNUmake
+
+clean:
+	@gmake clean
+
+frida_persistent:
+    @gmake frida_persistent
+
+frida_persistent_hook:
+    @gmake frida_persistent_hook
diff --git a/frida_mode/test/osx-lib/harness.c b/frida_mode/test/osx-lib/harness.c
new file mode 100644
index 00000000..3d427b4a
--- /dev/null
+++ b/frida_mode/test/osx-lib/harness.c
@@ -0,0 +1,69 @@
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+
+
+//typedef for our exported target function.
+typedef void (*CRASHME)(const uint8_t *Data, size_t Size);
+
+//globals
+CRASHME fpn_crashme = NULL;
+
+
+int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size){
+    fpn_crashme(data, size);
+    return 0;
+}
+
+int main(int argc, const char * argv[])
+{
+
+    for (int i = 1; i < argc; i++) {
+        fprintf(stderr, "Running: %s\n", argv[i]);
+        FILE *f = fopen(argv[i], "r");
+        assert(f);
+        fseek(f, 0, SEEK_END);
+        size_t len = ftell(f);
+        fseek(f, 0, SEEK_SET);
+        unsigned char *buf = (unsigned char*)malloc(len);
+        size_t n_read = fread(buf, 1, len, f);
+        fclose(f);
+        assert(n_read == len);
+        LLVMFuzzerTestOneInput(buf, len);
+        free(buf);
+        fprintf(stderr, "Done:    %s: (%zd bytes)\n", argv[i], n_read);
+    }
+
+    return 0;
+}
+
+__attribute__((constructor()))
+void constructor(void) {
+    // handles to required libs
+    void *dylib = NULL;
+
+    dylib = dlopen("./libcrashme.dylib", RTLD_NOW);
+    if (dylib == NULL)
+    {
+
+        printf("[-] Failed to load lib\n");
+        printf("[-] Dlerror: %s\n", dlerror());
+        exit(1);
+
+    }
+
+    printf("[+] Resolve function\n");
+
+    fpn_crashme = (CRASHME)dlsym(dylib, "crashme");
+    if (!fpn_crashme)
+    {
+
+        printf("[-] Failed to find function\n");
+        exit(1);
+
+    }
+
+    printf("[+] Found function.\n");
+}
diff --git a/frida_mode/test/osx-lib/harness2.c b/frida_mode/test/osx-lib/harness2.c
new file mode 100644
index 00000000..464614ee
--- /dev/null
+++ b/frida_mode/test/osx-lib/harness2.c
@@ -0,0 +1,69 @@
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+
+
+//typedef for our exported target function.
+typedef void (*CRASHME)(const uint8_t *Data, size_t Size);
+
+//globals
+CRASHME fpn_crashme = NULL;
+
+
+int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size){
+    fpn_crashme(data, size);
+    return 0;
+}
+
+int main(int argc, const char * argv[])
+{
+
+    for (int i = 1; i < argc; i++) {
+        fprintf(stderr, "Running: %s\n", argv[i]);
+        FILE *f = fopen(argv[i], "r");
+        assert(f);
+        fseek(f, 0, SEEK_END);
+        size_t len = ftell(f);
+        fseek(f, 0, SEEK_SET);
+        unsigned char *buf = (unsigned char*)malloc(len);
+        size_t n_read = fread(buf, 1, len, f);
+        fclose(f);
+        assert(n_read == len);
+        LLVMFuzzerTestOneInput(buf, len);
+        free(buf);
+        fprintf(stderr, "Done:    %s: (%zd bytes)\n", argv[i], n_read);
+    }
+
+    return 0;
+}
+
+__attribute__((constructor()))
+void constructor(void) {
+    // handles to required libs
+    void *dylib = NULL;
+
+    dylib = dlopen("./libcrashme2.dylib", RTLD_NOW);
+    if (dylib == NULL)
+    {
+
+        printf("[-] Failed to load lib\n");
+        printf("[-] Dlerror: %s\n", dlerror());
+        exit(1);
+
+    }
+
+    printf("[+] Resolve function\n");
+
+    fpn_crashme = (CRASHME)dlsym(dylib, "crashme");
+    if (!fpn_crashme)
+    {
+
+        printf("[-] Failed to find function\n");
+        exit(1);
+
+    }
+
+    printf("[+] Found function.\n");
+}
diff --git a/frida_mode/test/osx-lib/harness3.c b/frida_mode/test/osx-lib/harness3.c
new file mode 100644
index 00000000..83983c99
--- /dev/null
+++ b/frida_mode/test/osx-lib/harness3.c
@@ -0,0 +1,40 @@
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+
+
+extern void crashme(const uint8_t *Data, size_t Size);
+
+int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size){
+    crashme(data, size);
+    return 0;
+}
+
+void run (int argc, const char * argv[])
+{
+    for (int i = 1; i < argc; i++) {
+        fprintf(stderr, "Running: %s\n", argv[i]);
+        FILE *f = fopen(argv[i], "r");
+        assert(f);
+        fseek(f, 0, SEEK_END);
+        size_t len = ftell(f);
+        fseek(f, 0, SEEK_SET);
+        unsigned char *buf = (unsigned char*)malloc(len);
+        size_t n_read = fread(buf, 1, len, f);
+        fclose(f);
+        assert(n_read == len);
+        LLVMFuzzerTestOneInput(buf, len);
+        free(buf);
+        fprintf(stderr, "Done:    %s: (%zd bytes)\n", argv[i], n_read);
+    }
+}
+
+int main(int argc, const char * argv[])
+{
+
+    run(argc, argv);
+
+    return 0;
+}
diff --git a/frida_mode/test/osx-lib/lib.c b/frida_mode/test/osx-lib/lib.c
new file mode 100644
index 00000000..b2dad098
--- /dev/null
+++ b/frida_mode/test/osx-lib/lib.c
@@ -0,0 +1,17 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+
+void __attribute__((noinline)) crashme(const uint8_t *Data, size_t Size) {
+
+  if (Size < 5) return;
+
+  if (Data[0] == 'F')
+    if (Data[1] == 'A')
+      if (Data[2] == '$')
+        if (Data[3] == '$')
+          if (Data[4] == '$') abort();
+
+
+}
diff --git a/frida_mode/test/osx-lib/lib2.c b/frida_mode/test/osx-lib/lib2.c
new file mode 100644
index 00000000..ba207210
--- /dev/null
+++ b/frida_mode/test/osx-lib/lib2.c
@@ -0,0 +1,61 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+
+void __attribute__((noinline)) crashme(const uint8_t *Data, size_t Size) {
+
+  if (Size < 1) return;
+
+  char *buf = malloc(10);
+
+  if (buf == NULL) return;
+
+  switch (Data[0]) {
+
+    /* Underflow */
+    case 'U':
+      printf("Underflow\n");
+      buf[-1] = '\0';
+      free(buf);
+      break;
+    /* Overflow */
+    case 'O':
+      printf("Overflow\n");
+      buf[10] = '\0';
+      free(buf);
+      break;
+    /* Double free */
+    case 'D':
+      printf("Double free\n");
+      free(buf);
+      free(buf);
+      break;
+    /* Use after free */
+    case 'A':
+      printf("Use after free\n");
+      free(buf);
+      buf[0] = '\0';
+      break;
+    /* Test Limits (OK) */
+    case 'T':
+      printf("Test-Limits - No Error\n");
+      buf[0] = 'A';
+      buf[9] = 'I';
+      free(buf);
+      break;
+    case 'M':
+      printf("Memset too many\n");
+      memset(buf, '\0', 11);
+      free(buf);
+      break;
+    default:
+      printf("Nop - No Error\n");
+      break;
+
+  }
+
+
+}
+
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 c1ad86e5..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)
-	truncate -s 1M $@
+	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 ddf63a96..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)
-	truncate -s 1M $@
+	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 ce95df3b..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)
-	truncate -s 1M $@
+	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 80e0a939..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)
-	truncate -s 1M $@
+	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 6326c099..d7f8b7bc 100644
--- a/frida_mode/ts/lib/afl.ts
+++ b/frida_mode/ts/lib/afl.ts
@@ -104,6 +104,15 @@ class Afl {
   }
 
   /**
+   * See `AFL_FRIDA_INST_COVERAGE_FILE`. This function takes a single `string`
+   * as an argument.
+   */
+  public static setInstrumentCoverageFile(file: string): void {
+    const buf = Memory.allocUtf8String(file);
+    Afl.jsApiSetInstrumentCoverageFile(buf);
+  }
+
+  /**
    * See `AFL_FRIDA_INST_DEBUG_FILE`. This function takes a single `string` as
    * an argument.
    */
@@ -194,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 {
@@ -209,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.
    */
@@ -226,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.
    */
@@ -282,6 +298,11 @@ class Afl {
     "void",
     ["pointer"]);
 
+  private static readonly jsApiSetInstrumentCoverageFile = Afl.jsApiGetFunction(
+    "js_api_set_instrument_coverage_file",
+    "void",
+    ["pointer"]);
+
   private static readonly jsApiSetInstrumentDebugFile = Afl.jsApiGetFunction(
     "js_api_set_instrument_debug_file",
     "void",
@@ -342,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",
@@ -352,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",
@@ -362,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",