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/GNUmakefile181
-rw-r--r--frida_mode/Makefile349
-rw-r--r--frida_mode/README.md153
-rw-r--r--frida_mode/include/entry.h15
-rw-r--r--frida_mode/include/frida_cmplog.h14
-rw-r--r--frida_mode/include/instrument.h22
-rw-r--r--frida_mode/include/interceptor.h7
-rw-r--r--frida_mode/include/lib.h13
-rw-r--r--frida_mode/include/persistent.h31
-rw-r--r--frida_mode/include/prefetch.h12
-rw-r--r--frida_mode/include/ranges.h7
-rw-r--r--frida_mode/include/stalker.h11
-rw-r--r--frida_mode/include/util.h14
-rw-r--r--frida_mode/src/cmplog/cmplog.c87
-rw-r--r--frida_mode/src/cmplog/cmplog_arm.c19
-rw-r--r--frida_mode/src/cmplog/cmplog_arm64.c19
-rw-r--r--frida_mode/src/cmplog/cmplog_x64.c346
-rw-r--r--frida_mode/src/cmplog/cmplog_x86.c19
-rw-r--r--frida_mode/src/entry.c50
-rw-r--r--frida_mode/src/instrument.c271
-rw-r--r--frida_mode/src/instrument/instrument.c155
-rw-r--r--frida_mode/src/instrument/instrument_arm32.c23
-rw-r--r--frida_mode/src/instrument/instrument_arm64.c97
-rw-r--r--frida_mode/src/instrument/instrument_x64.c93
-rw-r--r--frida_mode/src/instrument/instrument_x86.c23
-rw-r--r--frida_mode/src/interceptor.c21
-rw-r--r--frida_mode/src/lib/lib.c176
-rw-r--r--frida_mode/src/lib/lib_apple.c82
-rw-r--r--frida_mode/src/main.c72
-rw-r--r--frida_mode/src/persistent/persistent.c65
-rw-r--r--frida_mode/src/persistent/persistent_arm32.c72
-rw-r--r--frida_mode/src/persistent/persistent_arm64.c115
-rw-r--r--frida_mode/src/persistent/persistent_x64.c342
-rw-r--r--frida_mode/src/persistent/persistent_x86.c55
-rw-r--r--frida_mode/src/prefetch.c23
-rw-r--r--frida_mode/src/ranges.c462
-rw-r--r--frida_mode/src/stalker.c30
-rw-r--r--frida_mode/src/util.c67
-rw-r--r--frida_mode/test/cmplog/GNUmakefile66
-rw-r--r--frida_mode/test/cmplog/Makefile12
-rwxr-xr-xfrida_mode/test/cmplog/get_section_addrs.py (renamed from frida_mode/test/testinstr.py)0
-rw-r--r--frida_mode/test/entry_point/GNUmakefile61
-rw-r--r--frida_mode/test/entry_point/Makefile12
-rw-r--r--frida_mode/test/entry_point/testinstr.c119
-rw-r--r--frida_mode/test/exe/GNUmakefile50
-rw-r--r--frida_mode/test/exe/Makefile12
-rw-r--r--frida_mode/test/exe/testinstr.c (renamed from frida_mode/test/testinstr.c)4
-rw-r--r--frida_mode/test/png/GNUmakefile109
-rw-r--r--frida_mode/test/png/Makefile12
-rw-r--r--frida_mode/test/png/persistent/GNUmakefile79
-rw-r--r--frida_mode/test/png/persistent/Makefile18
-rwxr-xr-xfrida_mode/test/png/persistent/get_symbol_addr.py36
-rw-r--r--frida_mode/test/png/persistent/hook/GNUmakefile98
-rw-r--r--frida_mode/test/png/persistent/hook/Makefile18
-rw-r--r--frida_mode/test/testinstr/GNUmakefile50
-rw-r--r--frida_mode/test/testinstr/Makefile12
-rw-r--r--frida_mode/test/testinstr/testinstr.c112
-rwxr-xr-xfrida_mode/update_frida_version.sh13
58 files changed, 3620 insertions, 886 deletions
diff --git a/frida_mode/GNUmakefile b/frida_mode/GNUmakefile
new file mode 100644
index 00000000..a15f5c32
--- /dev/null
+++ b/frida_mode/GNUmakefile
@@ -0,0 +1,181 @@
+PWD:=$(shell pwd)/
+ROOT:=$(shell realpath $(PWD)..)/
+INC_DIR:=$(PWD)include/
+SRC_DIR:=$(PWD)src/
+INCLUDES:=$(wildcard $(INC_DIR)*.h)
+BUILD_DIR:=$(PWD)build/
+OBJ_DIR:=$(BUILD_DIR)obj/
+
+SOURCES:=$(wildcard $(SRC_DIR)**/*.c) $(wildcard $(SRC_DIR)*.c)
+OBJS:=$(foreach src,$(SOURCES),$(OBJ_DIR)$(notdir $(patsubst %.c, %.o, $(src))))
+CFLAGS+=-fPIC \
+		-D_GNU_SOURCE \
+		-D_FORTIFY_SOURCE=2 \
+		-g \
+		-O3 \
+		-funroll-loops \
+
+RT_CFLAGS:=-Wno-unused-parameter \
+		   -Wno-sign-compare \
+		   -Wno-unused-function \
+		   -Wno-unused-result \
+
+LDFLAGS+=-shared \
+		 -lpthread \
+		 -lresolv \
+		 -ldl \
+
+ifdef DEBUG
+CFLAGS+=-Werror \
+		-Wall \
+		-Wextra \
+		-Wpointer-arith
+else
+CFLAGS+=-Wno-pointer-arith
+endif
+
+FRIDA_BUILD_DIR:=$(BUILD_DIR)frida/
+FRIDA_TRACE:=$(BUILD_DIR)afl-frida-trace.so
+FRIDA_TRACE_EMBEDDED:=$(BUILD_DIR)afl-frida-trace-embedded
+
+ARCH=$(shell uname -m)
+ifeq "$(ARCH)" "aarch64"
+ ARCH:=arm64
+endif
+
+ifeq "$(ARCH)" "i686"
+ ARCH:=x86
+endif
+
+ifeq "$(shell uname)" "Darwin"
+ OS:=macos
+ RT_CFLAGS:=$(RT_CFLAGS) -Wno-deprecated-declarations
+else
+ifdef DEBUG
+ RT_CFLAGS:=$(RT_CFLAGS) -Wno-prio-ctor-dtor
+endif
+endif
+
+ifeq "$(shell uname)" "Linux"
+ OS:=linux
+endif
+
+ifndef OS
+ $(error "Operating system unsupported")
+endif
+
+GUM_DEVKIT_VERSION=14.2.18
+GUM_DEVKIT_FILENAME=frida-gum-devkit-$(GUM_DEVKIT_VERSION)-$(OS)-$(ARCH).tar.xz
+GUM_DEVKIT_URL="https://github.com/frida/frida/releases/download/$(GUM_DEVKIT_VERSION)/$(GUM_DEVKIT_FILENAME)"
+
+GUM_DEVKIT_TARBALL:=$(FRIDA_BUILD_DIR)$(GUM_DEVKIT_FILENAME)
+GUM_DEVIT_LIBRARY=$(FRIDA_BUILD_DIR)libfrida-gum.a
+GUM_DEVIT_HEADER=$(FRIDA_BUILD_DIR)frida-gum.h
+
+FRIDA_DIR:=$(PWD)build/frida-source/
+FRIDA_MAKEFILE:=$(FRIDA_DIR)Makefile
+FRIDA_GUM:=$(FRIDA_DIR)build/frida-linux-x86_64/lib/libfrida-gum-1.0.a
+FRIDA_GUM_DEVKIT_DIR:=$(FRIDA_DIR)build/gum-devkit/
+FRIDA_GUM_DEVKIT_HEADER:=$(FRIDA_GUM_DEVKIT_DIR)frida-gum.h
+FRIDA_GUM_DEVKIT_TARBALL:=$(FRIDA_DIR)build/frida-gum-devkit-$(GUM_DEVKIT_VERSION)-$(OS)-$(ARCH).tar
+FRIDA_GUM_DEVKIT_COMPRESSED_TARBALL:=$(FRIDA_DIR)build/$(GUM_DEVKIT_FILENAME)
+
+AFL_COMPILER_RT_SRC:=$(ROOT)instrumentation/afl-compiler-rt.o.c
+AFL_COMPILER_RT_OBJ:=$(OBJ_DIR)afl-compiler-rt.o
+
+.PHONY: all clean format $(FRIDA_GUM)
+
+############################## ALL #############################################
+
+all: $(FRIDA_TRACE)
+	make -C $(ROOT)
+
+$(BUILD_DIR):
+	mkdir -p $(BUILD_DIR)
+
+$(OBJ_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+############################# FRIDA ############################################
+
+$(FRIDA_MAKEFILE): | $(BUILD_DIR)
+	git clone --recursive https://github.com/frida/frida.git $(FRIDA_DIR)
+
+$(FRIDA_GUM): $(FRIDA_MAKEFILE)
+	cd $(FRIDA_DIR) && make gum-linux-$(ARCH)
+
+$(FRIDA_GUM_DEVKIT_HEADER): $(FRIDA_GUM)
+	$(FRIDA_DIR)releng/devkit.py frida-gum linux-$(ARCH) $(FRIDA_DIR)build/gum-devkit/
+
+$(FRIDA_GUM_DEVKIT_TARBALL): $(FRIDA_GUM_DEVKIT_HEADER)
+	cd $(FRIDA_GUM_DEVKIT_DIR) && tar cvf $(FRIDA_GUM_DEVKIT_TARBALL) .
+
+$(FRIDA_GUM_DEVKIT_COMPRESSED_TARBALL): $(FRIDA_GUM_DEVKIT_TARBALL)
+	xz -k -f -0 $(FRIDA_GUM_DEVKIT_TARBALL)
+
+############################# DEVKIT ###########################################
+
+$(FRIDA_BUILD_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+ifdef FRIDA_SOURCE
+$(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)
+endif
+
+$(GUM_DEVIT_LIBRARY): | $(GUM_DEVKIT_TARBALL)
+	tar Jxvf $(GUM_DEVKIT_TARBALL) -C $(FRIDA_BUILD_DIR)
+
+$(GUM_DEVIT_HEADER): | $(GUM_DEVKIT_TARBALL)
+	tar Jxvf $(GUM_DEVKIT_TARBALL) -C $(FRIDA_BUILD_DIR)
+
+############################## AFL #############################################
+$(AFL_COMPILER_RT_OBJ): $(AFL_COMPILER_RT_SRC)
+	$(CC) \
+		$(CFLAGS) \
+		$(RT_CFLAGS) \
+		-I $(ROOT) \
+		-I $(ROOT)include \
+		-o $@ \
+		-c $<
+
+############################# SOURCE ###########################################
+
+define BUILD_SOURCE
+$(2): $(1) $(INCLUDES) GNUmakefile | $(OBJ_DIR)
+	$(CC) \
+		$(CFLAGS) \
+		-I $(ROOT)include \
+		-I $(FRIDA_BUILD_DIR) \
+		-I $(INC_DIR) \
+		-c $1 \
+		-o $2
+endef
+
+$(foreach src,$(SOURCES),$(eval $(call BUILD_SOURCE,$(src),$(OBJ_DIR)$(notdir $(patsubst %.c, %.o, $(src))))))
+
+######################## AFL-FRIDA-TRACE #######################################
+
+$(FRIDA_TRACE): $(GUM_DEVIT_LIBRARY) $(GUM_DEVIT_HEADER) $(OBJS) $(AFL_COMPILER_RT_OBJ) GNUmakefile | $(BUILD_DIR)
+	$(CC) \
+		-o $@ \
+		$(OBJS) \
+		$(GUM_DEVIT_LIBRARY) \
+		$(AFL_COMPILER_RT_OBJ) \
+		$(LDFLAGS) \
+
+	cp -v $(FRIDA_TRACE) $(ROOT)
+
+############################# CLEAN ############################################
+clean:
+	rm -rf $(BUILD_DIR)
+
+############################# FORMAT ###########################################
+format:
+	cd $(ROOT) && echo $(SOURCES) | xargs -L1 ./.custom-format.py -i
+	cd $(ROOT) && echo $(INCLUDES) | xargs -L1 ./.custom-format.py -i
+
+############################# RUN #############################################
diff --git a/frida_mode/Makefile b/frida_mode/Makefile
index 822f1c6a..b6d64bff 100644
--- a/frida_mode/Makefile
+++ b/frida_mode/Makefile
@@ -1,348 +1,9 @@
-PWD:=$(shell pwd)/
-INC_DIR:=$(PWD)include/
-SRC_DIR:=$(PWD)src/
-INCLUDES:=$(wildcard $(INC_DIR)*.h)
-SOURCES:=$(wildcard $(SRC_DIR)*.c)
-BUILD_DIR:=$(PWD)build/
-CFLAGS+=-fPIC -D_GNU_SOURCE
+all:
+	@echo trying to use GNU make...
+	@gmake all || echo please install GNUmake
 
-FRIDA_BUILD_DIR:=$(BUILD_DIR)frida/
-FRIDA_TRACE:=$(FRIDA_BUILD_DIR)afl-frida-trace.so
-
-ARCH=$(shell uname -m)
-ifeq "$(ARCH)" "aarch64"
- ARCH:=arm64
- TESTINSTR_BASE:=0x0000aaaaaaaaa000
-endif
-
-ifeq "$(ARCH)" "x86_64"
- TESTINSTR_BASE:=0x0000555555554000
-endif
-
-ifeq "$(shell uname)" "Darwin"
- OS:=macos
- AFL_FRIDA_INST_RANGES=0x0000000000001000-0xFFFFFFFFFFFFFFFF
- CFLAGS:=$(CFLAGS) -Wno-deprecated-declarations
- TEST_LDFLAGS:=-undefined dynamic_lookup
-endif
-ifeq "$(shell uname)" "Linux"
- OS:=linux
- AFL_FRIDA_INST_RANGES=$(shell $(PWD)test/testinstr.py -f $(BUILD_DIR)testinstr -s .testinstr -b $(TESTINSTR_BASE))
- CFLAGS:=$(CFLAGS) -Wno-prio-ctor-dtor
- TEST_LDFLAGS:=
-endif
-
-ifndef OS
- $(error "Operating system unsupported")
-endif
-
-VERSION=14.2.13
-GUM_DEVKIT_FILENAME=frida-gum-devkit-$(VERSION)-$(OS)-$(ARCH).tar.xz
-GUM_DEVKIT_URL="https://github.com/frida/frida/releases/download/$(VERSION)/$(GUM_DEVKIT_FILENAME)"
-GUM_DEVKIT_TARBALL:=$(FRIDA_BUILD_DIR)$(GUM_DEVKIT_FILENAME)
-GUM_DEVIT_LIBRARY=$(FRIDA_BUILD_DIR)libfrida-gum.a
-GUM_DEVIT_HEADER=$(FRIDA_BUILD_DIR)frida-gum.h
-
-TEST_BUILD_DIR:=$(BUILD_DIR)test/
-
-LIBPNG_FILE:=$(TEST_BUILD_DIR)libpng-1.2.56.tar.gz
-LIBPNG_URL:=https://downloads.sourceforge.net/project/libpng/libpng12/older-releases/1.2.56/libpng-1.2.56.tar.gz
-LIBPNG_DIR:=$(TEST_BUILD_DIR)libpng-1.2.56/
-LIBPNG_MAKEFILE:=$(LIBPNG_DIR)Makefile
-LIBPNG_LIB:=$(LIBPNG_DIR).libs/libpng12.a
-
-HARNESS_FILE:=$(TEST_BUILD_DIR)StandaloneFuzzTargetMain.c
-HARNESS_OBJ:=$(TEST_BUILD_DIR)StandaloneFuzzTargetMain.o
-HARNESS_URL:="https://raw.githubusercontent.com/llvm/llvm-project/main/compiler-rt/lib/fuzzer/standalone/StandaloneFuzzTargetMain.c"
-
-PNGTEST_FILE:=$(TEST_BUILD_DIR)target.cc
-PNGTEST_OBJ:=$(TEST_BUILD_DIR)target.o
-PNGTEST_URL:="https://raw.githubusercontent.com/google/fuzzbench/master/benchmarks/libpng-1.2.56/target.cc"
-
-TEST_BIN:=$(TEST_BUILD_DIR)pngtest
-
-TESTINSTBIN:=$(BUILD_DIR)testinstr
-TESTINSTSRC:=$(PWD)test/testinstr.c
-
-TEST_DATA_DIR:=$(PWD)build/test/libpng-1.2.56/contrib/pngsuite/
-
-TESTINSTR_DATA_DIR:=$(BUILD_DIR)testinstr_in/
-TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)test.dat
-FRIDA_OUT:=$(PWD)frida_out
-QEMU_OUT:=$(PWD)qemu_out
-
-.PHONY: all frida test clean format test_frida test_qemu compare testinstr test_testinstr standalone
-
-all: $(FRIDA_TRACE)
-
-frida: $(FRIDA_TRACE)
-
-$(BUILD_DIR):
-	mkdir -p $(BUILD_DIR)
-
-############################# FRIDA ############################################
-$(FRIDA_BUILD_DIR): | $(BUILD_DIR)
-	mkdir -p $@
-
-$(GUM_DEVKIT_TARBALL): | $(FRIDA_BUILD_DIR)
-	wget -O $@ $(GUM_DEVKIT_URL)
-
-$(GUM_DEVIT_LIBRARY): | $(GUM_DEVKIT_TARBALL)
-	tar Jxvf $(GUM_DEVKIT_TARBALL) -C $(FRIDA_BUILD_DIR)
-
-$(GUM_DEVIT_HEADER): | $(GUM_DEVKIT_TARBALL)
-	tar Jxvf $(GUM_DEVKIT_TARBALL) -C $(FRIDA_BUILD_DIR)
-
-$(FRIDA_TRACE): $(GUM_DEVIT_LIBRARY) $(GUM_DEVIT_HEADER) $(SOURCES) Makefile | $(FRIDA_BUILD_DIR)
-	$(CC) -shared \
-		$(CFLAGS) \
-		-o $@ $(SOURCES) \
-		$(GUM_DEVIT_LIBRARY) \
-		-I $(FRIDA_BUILD_DIR) \
-		-I .. \
-		-I ../include \
-		-I $(INC_DIR) \
-		../instrumentation/afl-compiler-rt.o.c \
-		-lpthread -ldl -lresolv
-
-	cp -v $(FRIDA_TRACE) ../
-
-############################# TEST #############################################
-
-test: $(TEST_BIN)
-
-$(TEST_BUILD_DIR): $(BUILD_DIR)
-	mkdir -p $@
-
-$(HARNESS_FILE): | $(TEST_BUILD_DIR)
-	wget -O $@ $(HARNESS_URL)
-
-$(HARNESS_OBJ): $(HARNESS_FILE)
-	$(CC) -o $@ -c $<
-
-$(PNGTEST_FILE): | $(TEST_BUILD_DIR)
-	wget -O $@ $(PNGTEST_URL)
-
-$(PNGTEST_OBJ): $(PNGTEST_FILE) | $(LIBPNG_DIR)
-	$(CXX) -std=c++11 -I $(LIBPNG_DIR) -o $@ -c $<
-
-$(LIBPNG_FILE): | $(TEST_BUILD_DIR)
-	wget -O $@ $(LIBPNG_URL)
-
-$(LIBPNG_DIR): $(LIBPNG_FILE)
-	tar zxvf $(LIBPNG_FILE) -C $(TEST_BUILD_DIR)
-
-$(LIBPNG_MAKEFILE): | $(LIBPNG_DIR)
-	cd $(LIBPNG_DIR) && ./configure
-
-$(LIBPNG_LIB): $(LIBPNG_MAKEFILE)
-	make -C $(LIBPNG_DIR)
-
-$(TEST_BIN): $(HARNESS_OBJ) $(PNGTEST_OBJ) $(LIBPNG_LIB)
-	$(CXX) \
-		-o $@ \
-		$(HARNESS_OBJ) $(PNGTEST_OBJ) $(LIBPNG_LIB) \
-		-lz \
-		$(TEST_LDFLAGS)
-
-############################# TESTINSR #########################################
-$(TESTINSTR_DATA_DIR): | $(BUILD_DIR)
-	mkdir -p $@
-
-$(TESTINSTR_DATA_FILE): | $(TESTINSTR_DATA_DIR)
-	echo -n "000" > $@
-
-$(TESTINSTBIN): $(TESTINSTSRC) | $(BUILD_DIR)
-	$(CC) -o $@ $<
-
-testinstr: $(TESTINSTBIN)
-
-############################# CLEAN ############################################
 clean:
-	rm -rf $(BUILD_DIR)
+	@gmake clean
 
-############################# FORMAT ###########################################
 format:
-	cd .. && echo $(SOURCES) | xargs -L1 ./.custom-format.py -i
-	cd .. && echo $(INCLUDES) | xargs -L1 ./.custom-format.py -i
-	cd .. && ./.custom-format.py -i $(TESTINSTSRC)
-
-############################# RUN #############################################
-
-# Add the environment variable AFL_DEBUG_CHILD=1 to show printf's from the target
-
-png_frida: $(FRIDA_TRACE) $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-fuzz \
-			-O \
-			-i $(TEST_DATA_DIR) \
-			-o $(FRIDA_OUT) \
-			-- \
-				$(TEST_BIN) @@
-
-png_qemu: $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-fuzz \
-			-Q \
-			-i $(TEST_DATA_DIR) \
-			-o $(QEMU_OUT) \
-			-- \
-				$(TEST_BIN) @@
-
-compare: $(FRIDA_TRACE) $(TEST_BIN)
-	cd .. && \
-		./afl-fuzz \
-			-V30 \
-			-O \
-			-i $(TEST_DATA_DIR) \
-			-o $(FRIDA_OUT) \
-			-- \
-				$(TEST_BIN) @@
-	cd .. && \
-		./afl-fuzz \
-			-V30 \
-			-Q \
-			-i $(TEST_DATA_DIR) \
-			-o $(QEMU_OUT) \
-				-- \
-					$(TEST_BIN) @@
-	cat frida_out/default/fuzzer_stats
-	cat qemu_out/default/fuzzer_stats
-
-testinstr_qemu: $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
-	make -C ..
-	cd .. && \
-			AFL_QEMU_INST_RANGES=$(AFL_FRIDA_INST_RANGES) \
-			./afl-fuzz \
-				-Q \
-				-i $(TESTINSTR_DATA_DIR) \
-				-o $(QEMU_OUT) \
-				-- \
-					$(TESTINSTBIN) @@
-
-testinstr_frida: $(FRIDA_TRACE) $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
-	make -C ..
-	cd .. && \
-			AFL_FRIDA_INST_RANGES=$(AFL_FRIDA_INST_RANGES) \
-			AFL_FRIDA_INST_NO_OPTIMIZE=1 \
-			AFL_FRIDA_INST_NO_PREFETCH=1 \
-			AFL_FRIDA_INST_STRICT=1 \
-			./afl-fuzz \
-				-O \
-				-i $(TESTINSTR_DATA_DIR) \
-				-o $(FRIDA_OUT) \
-				-- \
-					$(TESTINSTBIN) @@
-
-standalone: $(FRIDA_TRACE) $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
-	cd .. && \
-			AFL_FRIDA_INST_RANGES=$(AFL_FRIDA_INST_RANGES) \
-			AFL_DEBUG_CHILD=1 \
-			AFL_FRIDA_DEBUG_MAPS=1 \
-			AFL_FRIDA_INST_NO_OPTIMIZE=1 \
-			AFL_FRIDA_INST_NO_PREFETCH=1 \
-			AFL_FRIDA_INST_TRACE=1 \
-			AFL_FRIDA_INST_STRICT=1 \
-			LD_PRELOAD=$(FRIDA_TRACE) \
-			DYLD_INSERT_LIBRARIES=$(FRIDA_TRACE) \
-			$(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
-
-tmin_qemu: $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-tmin \
-			-Q \
-			-i $(TEST_DATA_DIR)basn0g01.png \
-			-o $(QEMU_OUT)/qemu-min-basn0g01.png \
-			-- \
-				$(TEST_BIN) @@
-
-tmin_frida: $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-tmin \
-			-O \
-			-i $(TEST_DATA_DIR)basn0g01.png \
-			-o $(FRIDA_OUT)/qemu-min-basn0g01.png \
-			-- \
-				$(TEST_BIN)
-
-showmap_qemu: $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-showmap \
-			-Q \
-			-i $(TEST_DATA_DIR) \
-			-o $(QEMU_OUT) \
-			-- \
-				$(TEST_BIN) @@
-
-showmap_frida: $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-showmap \
-			-O \
-			-i $(TEST_DATA_DIR) \
-			-o $(FRIDA_OUT) \
-			-- \
-				$(TEST_BIN) @@
-
-analyze_qemu: $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-analyze \
-			-Q \
-			-i $(TEST_DATA_DIR)basn0g01.png \
-			-- \
-				$(TEST_BIN) @@
-
-analyze_frida: $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-analyze \
-			-O \
-			-i $(TEST_DATA_DIR)basn0g01.png \
-			-- \
-				$(TEST_BIN) @@
-
-cmin_qemu: $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-cmin \
-			-Q \
-			-i $(TEST_DATA_DIR) \
-			-o $(QEMU_OUT) \
-			-- \
-				$(TEST_BIN) @@
-
-cmin_frida: $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-cmin \
-			-O \
-			-i $(TEST_DATA_DIR) \
-			-o $(FRIDA_OUT) \
-			-- \
-				$(TEST_BIN) @@
-
-cmin_bash_qemu: $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-cmin.bash \
-			-Q \
-			-i $(TEST_DATA_DIR) \
-			-o $(QEMU_OUT) \
-			-- \
-				$(TEST_BIN) @@
-
-cmin_bash_frida: $(TEST_BIN)
-	make -C ..
-	cd .. && \
-		./afl-cmin.bash \
-			-O \
-			-i $(TEST_DATA_DIR) \
-			-o $(FRIDA_OUT) \
-			-- \
-				$(TEST_BIN) @@
+	@gmake format
diff --git a/frida_mode/README.md b/frida_mode/README.md
index 8abee0dd..9f574a4c 100644
--- a/frida_mode/README.md
+++ b/frida_mode/README.md
@@ -1,34 +1,38 @@
 # FRIDA MODE
-The purpose of FRIDA mode is to provide an alternative binary only fuzzer for AFL
-just like that provided by QEMU mode. The intention is to provide a very similar
-user experience, right down to the options provided through environment variables.
+
+The purpose of FRIDA mode is to provide an alternative binary only fuzzer for
+AFL just like that provided by QEMU mode. The intention is to provide a very
+similar user experience, right down to the options provided through environment
+variables.
 
 Whilst AFLplusplus already has some support for running on FRIDA [here](https://github.com/AFLplusplus/AFLplusplus/tree/stable/utils/afl_frida)
 this requires the code to be fuzzed to be provided as a shared library, it
 cannot be used to fuzz executables. Additionally, it requires the user to write
-a small harness around their target code of interest, FRIDA mode instead takes a
-different approach to avoid these limitations.
-
-# Current Progress
-As FRIDA mode is new, it is missing a lot of features. Most importantly,
-persistent mode. The design is such that it should be possible to add these
-features in a similar manner to QEMU mode and perhaps leverage some of its
-design and implementation.
-
-  | Feature/Instrumentation  | frida-mode |
-  | -------------------------|:----------:|
-  | NeverZero                |            |
-  | Persistent Mode          |            |
-  | LAF-Intel / CompCov      |            |
-  | CmpLog                   |            |
-  | Selective Instrumentation|     x      |
-  | Non-Colliding Coverage   |            |
-  | Ngram prev_loc Coverage  |            |
-  | Context Coverage         |            |
-  | Auto Dictionary          |            |
-  | Snapshot LKM Support     |            |
-
-# Compatibility
+a small harness around their target code of interest.
+FRIDA mode instead takes a different approach to avoid these limitations.
+In Frida mode binary programs are instrumented, similarly to QEMU mode.
+
+## Current Progress
+
+As FRIDA mode is new, it is missing a lot of features. The design is such that it
+should be possible to add these features in a similar manner to QEMU mode and
+perhaps leverage some of its design and implementation.
+
+  | Feature/Instrumentation  | frida-mode | Notes                                   |
+  | -------------------------|:----------:|:---------------------------------------:|
+  | NeverZero                |     x      |                                         |
+  | Persistent Mode          |     x      | (x64 only)(Only on function boundaries) |  
+  | LAF-Intel / CompCov      |     -      | (CMPLOG is better 90% of the time)      |
+  | CMPLOG                   |     x      | (x64 only)                              |
+  | Selective Instrumentation|     x      |                                         |
+  | Non-Colliding Coverage   |     -      |                                         |
+  | Ngram prev_loc Coverage  |     -      |                                         |
+  | Context Coverage         |     -      |                                         |
+  | Auto Dictionary          |     -      |                                         |
+  | Snapshot LKM Support     |     -      |                                         |
+  | In-Memory Test Cases     |     x      | (x64 only)                              |
+
+## Compatibility
 Currently FRIDA mode supports Linux and macOS targets on both x86/x64
 architecture and aarch64. Later releases may add support for aarch32 and Windows
 targets as well as embedded linux environments.
@@ -38,54 +42,53 @@ runtime libraries, so porting should be possible. However, the current build
 system does not support cross compilation.
 
 ## Getting Started
+
 To build everything run `make`.
 
-To run the benchmark sample with qemu run `make png_qemu`.
-To run the benchmark sample with frida run `make png_frida`.
+Various tests can be found in subfolders within the `test/` directory. To use
+these, first run `make` to build any dependencies. Then run `make qemu` or
+`make frida` to run on either QEMU of FRIDA mode respectively.
 
 ## Usage
-FRIDA mode requires some small modifications to `afl-fuzz` and similar tools
-in AFLplusplus. The intention is that it behaves identically to QEMU, but uses
+
+FRIDA mode added some small modifications to `afl-fuzz` and similar tools
+in AFLplusplus. The intention was that it behaves identically to QEMU, but it uses
 the 'O' switch rather than 'Q'. Whilst the options 'f', 'F', 's' or 'S' may have
 made more sense for a mode powered by FRIDA Stalker, they were all taken, so
 instead we use 'O' in hommage to the [author](https://github.com/oleavr) of
 FRIDA.
 
 Similarly, the intention is to mimic the use of environment variables used by
-QEMU where possible (although replacing `s/QEMU/FRIDA/g`). Accodingly, the
-following options are currently supported.
+QEMU where possible (by replacing `s/QEMU/FRIDA/g`). Accordingly, the
+following options are currently supported:
 
 * `AFL_FRIDA_DEBUG_MAPS` - See `AFL_QEMU_DEBUG_MAPS`
 * `AFL_FRIDA_EXCLUDE_RANGES` - See `AFL_QEMU_EXCLUDE_RANGES`
 * `AFL_FRIDA_INST_RANGES` - See `AFL_QEMU_INST_RANGES`
+* `AFL_FRIDA_PERSISTENT_ADDR` - See `AFL_QEMU_PERSISTENT_ADDR`
+* `AFL_FRIDA_PERSISTENT_CNT` - See `AFL_QEMU_PERSISTENT_CNT`
+* `AFL_FRIDA_PERSISTENT_HOOK` - See `AFL_QEMU_PERSISTENT_HOOK`
 
-# Performance
+To enable the powerful CMPLOG mechanism, set `-c 0` for `afl-fuzz`.
+
+## Performance
 
 Additionally, the intention is to be able to make a direct performance
-comparison between the two approaches. Accordingly, FRIDA mode includes a test
-target based on the [libpng](https://libpng.sourceforge.io/) benchmark used by
-[fuzzbench](https://google.github.io/fuzzbench/) and integrated with the
+comparison between the two approaches. Accordingly, FRIDA mode includes various
+test targets based on the [libpng](https://libpng.sourceforge.io/) benchmark
+used by [fuzzbench](https://google.github.io/fuzzbench/) and integrated with the
 [StandaloneFuzzTargetMain](https://raw.githubusercontent.com/llvm/llvm-project/main/compiler-rt/lib/fuzzer/standalone/StandaloneFuzzTargetMain.c)
-from the llvm project. This is built and linked without any special
-modifications to suit FRIDA or QEMU. We use the test data provided with libpng
-as our corpus.
-
-Whilst not much performance tuning has been completed to date, performance is
-around 30-50% of that of QEMU mode, however, this gap may reduce with the 
-introduction of persistent mode. Performance can be tested by running 
-`make compare`, albeit a longer time measurement may be required for more 
-accurate results. 
-
-Whilst [afl_frida](https://github.com/AFLplusplus/AFLplusplus/tree/stable/utils/afl_frida)
-claims a 5-10x performance increase over QEMU, it has not been possible to
-reproduce these claims. However, the number of executions per second can vary
-dramatically as a result of the randomization of the fuzzer input. Some inputs
-may traverse relatively few paths before being rejected as invalid whilst others
-may be valid inputs or be subject to much more processing before rejection.
-Accordingly, it is recommended that testing be carried out over prolongued
-periods to gather timings which are more than indicative.
-
-# Design
+from the llvm project. These tests include basic fork-server support, persistent
+mode and persistent mode with in-memory test-cases. These are built and linked
+without any special modifications to suit FRIDA or QEMU. The test data provided
+with libpng is used as the corpus.
+
+The intention is to add support for FRIDA mode to the FuzzBench project and
+perform a like-for-like comparison with QEMU mode to get an accurate
+appreciation of its performance.
+
+## Design
+
 FRIDA mode is supported by using `LD_PRELOAD` (`DYLD_INSERT_LIBRARIES` on macOS)
 to inject a shared library (`afl-frida-trace.so`) into the target. This shared
 library is built using the [frida-gum](https://github.com/frida/frida-gum)
@@ -102,34 +105,34 @@ this coverage information to AFL++ and also provide a fork server. It also makes
 use of the FRIDA [prefetch](https://github.com/frida/frida-gum/blob/56dd9ba3ee9a5511b4b0c629394bf122775f1ab7/gum/gumstalker.h#L115)
 support to feedback instrumented blocks from the child to the parent using a
 shared memory region to avoid the need to regenerate instrumented blocks on each
-fork. 
+fork.
 
 Whilst FRIDA allows for a normal C function to be used to augment instrumented
-code, to minimize the costs of storing and restoring all of the registers, FRIDA
-mode instead makes use of optimized assembly instead on AARCH64 and x86/64
-targets.
+code, FRIDA mode instead makes use of optimized assembly instead on AARCH64 and
+x86/64 targets. By injecting these small snippets of assembly, we avoid having
+to push and pop the full register context. Note that since this instrumentation
+is used on every basic block to generate coverage, it has a large impact on
+performance.
+
+CMPLOG support also adds code to the assembly, however, at present this code
+makes use of a basic C function and is yet to be optimized. Since not all
+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
 
-# Advanced configuration options
 * `AFL_FRIDA_INST_NO_OPTIMIZE` - Don't use optimized inline assembly coverage
 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.
-* `AFL_FRIDA_INST_STRICT` - Under certain conditions, Stalker may encroach into
-excluded regions and generate both instrumented blocks and coverage data (e.g.
-indirect calls on x86). The excluded block is generally honoured as soon as
-another function is called within the excluded region and so such encroachment
-is usually of little consequence. This detail may however, hinder you when
-checking that the correct number of paths are found for testing purposes or
-similar. There is a performance penatly for this option during block compilation
-where we check the block isn't in a list of excluded ranges.
 * `AFL_FRIDA_INST_TRACE` - Generate some logging when running instrumented code.
 Requires `AFL_FRIDA_INST_NO_OPTIMIZE`.
 
-# TODO
-As can be seen from the progress section above, there are a number of features
-which are missing in its currently form. Chief amongst which is persistent mode.
-The intention is to achieve feature parity with QEMU mode in due course.
-Contributions are welcome, but please get in touch to ensure that efforts are
-deconflicted.
+## TODO
+
+The next features to be added are x86 support, integration with FuzzBench and
+support for ASAN. The intention is to achieve feature parity with QEMU mode in
+due course. Contributions are welcome, but please get in touch to ensure that
+efforts are deconflicted.
diff --git a/frida_mode/include/entry.h b/frida_mode/include/entry.h
new file mode 100644
index 00000000..967831af
--- /dev/null
+++ b/frida_mode/include/entry.h
@@ -0,0 +1,15 @@
+#ifndef _ENTRY_H
+#define _ENTRY_H
+
+#include "frida-gum.h"
+
+extern guint64 entry_start;
+
+void entry_init(void);
+
+void entry_run(void);
+
+void entry_prologue(GumStalkerIterator *iterator, GumStalkerOutput *output);
+
+#endif
+
diff --git a/frida_mode/include/frida_cmplog.h b/frida_mode/include/frida_cmplog.h
new file mode 100644
index 00000000..28864c0e
--- /dev/null
+++ b/frida_mode/include/frida_cmplog.h
@@ -0,0 +1,14 @@
+#ifndef _CMPLOG_H
+#define _CMPLOG_H
+
+extern struct cmp_map *__afl_cmp_map;
+
+void cmplog_init(void);
+
+/* Functions to be implemented by the different architectures */
+void cmplog_instrument(const cs_insn *instr, GumStalkerIterator *iterator);
+
+gboolean cmplog_is_readable(void *addr, size_t size);
+
+#endif
+
diff --git a/frida_mode/include/instrument.h b/frida_mode/include/instrument.h
index ff71bed4..03fd33e5 100644
--- a/frida_mode/include/instrument.h
+++ b/frida_mode/include/instrument.h
@@ -1,7 +1,23 @@
+#ifndef _INSTRUMENT_H
+#define _INSTRUMENT_H
+
 #include "frida-gum.h"
 
-void instr_basic_block(GumStalkerIterator *iterator, GumStalkerOutput *output,
-                       gpointer user_data);
+#include "config.h"
+
+extern __thread uint64_t previous_pc;
+extern uint8_t *         __afl_area_ptr;
+extern uint32_t          __afl_map_size;
+
+void instrument_init(void);
+
+GumStalkerTransformer *instrument_get_transformer(void);
+
+/* Functions to be implemented by the different architectures */
+gboolean instrument_is_coverage_optimize_supported(void);
+
+void instrument_coverage_optimize(const cs_insn *   instr,
+                                  GumStalkerOutput *output);
 
-void instrument_init();
+#endif
 
diff --git a/frida_mode/include/interceptor.h b/frida_mode/include/interceptor.h
index 5ed3cf49..0ff754a4 100644
--- a/frida_mode/include/interceptor.h
+++ b/frida_mode/include/interceptor.h
@@ -1,4 +1,11 @@
+#ifndef _INTERCEPTOR_H
+#define _INTERCEPTOR_H
+
 #include "frida-gum.h"
 
 void intercept(void *address, gpointer replacement, gpointer user_data);
+void unintercept(void *address);
+void unintercept_self(void);
+
+#endif
 
diff --git a/frida_mode/include/lib.h b/frida_mode/include/lib.h
new file mode 100644
index 00000000..237aecb0
--- /dev/null
+++ b/frida_mode/include/lib.h
@@ -0,0 +1,13 @@
+#ifndef _LIB_H
+#define _LIB_H
+
+#include "frida-gum.h"
+
+void lib_init(void);
+
+guint64 lib_get_text_base(void);
+
+guint64 lib_get_text_limit(void);
+
+#endif
+
diff --git a/frida_mode/include/persistent.h b/frida_mode/include/persistent.h
new file mode 100644
index 00000000..e58c5301
--- /dev/null
+++ b/frida_mode/include/persistent.h
@@ -0,0 +1,31 @@
+
+#ifndef _PERSISTENT_H
+#define _PERSISTENT_H
+
+#include "frida-gum.h"
+#include "config.h"
+
+typedef struct arch_api_regs api_regs;
+
+typedef void (*afl_persistent_hook_fn)(api_regs *regs, uint64_t guest_base,
+                                       uint8_t *input_buf,
+                                       uint32_t input_buf_len);
+
+extern int __afl_persistent_loop(unsigned int max_cnt);
+
+extern unsigned int * __afl_fuzz_len;
+extern unsigned char *__afl_fuzz_ptr;
+
+extern guint64                persistent_start;
+extern guint64                persistent_count;
+extern afl_persistent_hook_fn hook;
+
+void persistent_init(void);
+
+/* Functions to be implemented by the different architectures */
+gboolean persistent_is_supported(void);
+
+void persistent_prologue(GumStalkerOutput *output);
+
+#endif
+
diff --git a/frida_mode/include/prefetch.h b/frida_mode/include/prefetch.h
index b7f25a97..8f0cee68 100644
--- a/frida_mode/include/prefetch.h
+++ b/frida_mode/include/prefetch.h
@@ -1,5 +1,11 @@
-void prefetch_init();
-void prefetch_start(GumStalker *stalker);
+#ifndef _PREFETCH_H
+#define _PREFETCH_H
+
+#include "frida-gum.h"
+
+void prefetch_init(void);
 void prefetch_write(void *addr);
-void prefetch_read(GumStalker *stalker);
+void prefetch_read(void);
+
+#endif
 
diff --git a/frida_mode/include/ranges.h b/frida_mode/include/ranges.h
index b9394dbc..f652eb8a 100644
--- a/frida_mode/include/ranges.h
+++ b/frida_mode/include/ranges.h
@@ -1,6 +1,11 @@
+#ifndef _RANGES_H
+#define _RANGES_H
+
 #include "frida-gum.h"
 
-void ranges_init(GumStalker *stalker);
+void ranges_init(void);
 
 gboolean range_is_excluded(gpointer address);
 
+#endif
+
diff --git a/frida_mode/include/stalker.h b/frida_mode/include/stalker.h
new file mode 100644
index 00000000..186ead11
--- /dev/null
+++ b/frida_mode/include/stalker.h
@@ -0,0 +1,11 @@
+#ifndef _STALKER_H
+#define _STALKER_H
+
+#include "frida-gum.h"
+
+void        stalker_init(void);
+GumStalker *stalker_get(void);
+void        stalker_start(void);
+
+#endif
+
diff --git a/frida_mode/include/util.h b/frida_mode/include/util.h
new file mode 100644
index 00000000..afd0b9c1
--- /dev/null
+++ b/frida_mode/include/util.h
@@ -0,0 +1,14 @@
+#ifndef _UTIL_H
+#define _UTIL_H
+
+#include "frida-gum.h"
+
+#define UNUSED_PARAMETER(x) (void)(x)
+#define IGNORED_RERURN(x) (void)!(x)
+
+guint64 util_read_address(char *key);
+
+guint64 util_read_num(char *key);
+
+#endif
+
diff --git a/frida_mode/src/cmplog/cmplog.c b/frida_mode/src/cmplog/cmplog.c
new file mode 100644
index 00000000..3fab1951
--- /dev/null
+++ b/frida_mode/src/cmplog/cmplog.c
@@ -0,0 +1,87 @@
+#include "frida-gum.h"
+
+#include "debug.h"
+
+#include "util.h"
+
+#define DEFAULT_MMAP_MIN_ADDR (32UL << 10)
+
+extern struct cmp_map *__afl_cmp_map;
+
+static GArray *cmplog_ranges = NULL;
+
+static gboolean cmplog_range(const GumRangeDetails *details,
+                             gpointer               user_data) {
+
+  UNUSED_PARAMETER(user_data);
+  GumMemoryRange range = *details->range;
+  g_array_append_val(cmplog_ranges, range);
+  return TRUE;
+
+}
+
+static gint cmplog_sort(gconstpointer a, gconstpointer b) {
+
+  return ((GumMemoryRange *)b)->base_address -
+         ((GumMemoryRange *)a)->base_address;
+
+}
+
+void cmplog_init(void) {
+
+  if (__afl_cmp_map != NULL) { OKF("CMPLOG mode enabled"); }
+
+  cmplog_ranges = g_array_sized_new(false, false, sizeof(GumMemoryRange), 100);
+  gum_process_enumerate_ranges(GUM_PAGE_READ, cmplog_range, NULL);
+  g_array_sort(cmplog_ranges, cmplog_sort);
+
+  for (guint i = 0; i < cmplog_ranges->len; i++) {
+
+    GumMemoryRange *range = &g_array_index(cmplog_ranges, GumMemoryRange, i);
+    OKF("CMPLOG Range - 0x%016" G_GINT64_MODIFIER "X - 0x%016" G_GINT64_MODIFIER
+        "X",
+        range->base_address, range->base_address + range->size);
+
+  }
+
+}
+
+static gboolean cmplog_contains(GumAddress inner_base, GumAddress inner_limit,
+                                GumAddress outer_base, GumAddress outer_limit) {
+
+  return (inner_base >= outer_base && inner_limit <= outer_limit);
+
+}
+
+gboolean cmplog_is_readable(void *addr, size_t size) {
+
+  if (cmplog_ranges == NULL) FATAL("CMPLOG not initialized");
+
+  /*
+   * The Linux kernel prevents mmap from allocating from the very bottom of the
+   * address space to mitigate NULL pointer dereference attacks. The exact size
+   * is set by sysctl by setting mmap_min_addr and 64k is suggested on most
+   * platforms with 32k on ARM systems. We therefore fail fast if the address
+   * is lower than this. This should avoid some overhead when functions are
+   * called where one of the parameters is a size, or a some other small value.
+   */
+  if (GPOINTER_TO_SIZE(addr) < DEFAULT_MMAP_MIN_ADDR) { return false; }
+
+  GumAddress inner_base = GUM_ADDRESS(addr);
+  GumAddress inner_limit = inner_base + size;
+
+  for (guint i = 0; i < cmplog_ranges->len; i++) {
+
+    GumMemoryRange *range = &g_array_index(cmplog_ranges, GumMemoryRange, i);
+    GumAddress      outer_base = range->base_address;
+    GumAddress      outer_limit = outer_base + range->size;
+
+    if (cmplog_contains(inner_base, inner_limit, outer_base, outer_limit))
+      return true;
+
+  }
+
+  return false;
+
+}
+
diff --git a/frida_mode/src/cmplog/cmplog_arm.c b/frida_mode/src/cmplog/cmplog_arm.c
new file mode 100644
index 00000000..5af28f3f
--- /dev/null
+++ b/frida_mode/src/cmplog/cmplog_arm.c
@@ -0,0 +1,19 @@
+#include "frida-gum.h"
+
+#include "debug.h"
+
+#include "frida_cmplog.h"
+#include "util.h"
+
+#if defined(__arm__)
+void cmplog_instrument(const cs_insn *instr, GumStalkerIterator *iterator) {
+
+  UNUSED_PARAMETER(instr);
+  UNUSED_PARAMETER(iterator);
+  if (__afl_cmp_map == NULL) { return; }
+  FATAL("CMPLOG mode not supported on this architecture");
+
+}
+
+#endif
+
diff --git a/frida_mode/src/cmplog/cmplog_arm64.c b/frida_mode/src/cmplog/cmplog_arm64.c
new file mode 100644
index 00000000..187d0162
--- /dev/null
+++ b/frida_mode/src/cmplog/cmplog_arm64.c
@@ -0,0 +1,19 @@
+#include "frida-gum.h"
+
+#include "debug.h"
+
+#include "frida_cmplog.h"
+#include "util.h"
+
+#if defined(__aarch64__)
+void cmplog_instrument(const cs_insn *instr, GumStalkerIterator *iterator) {
+
+  UNUSED_PARAMETER(instr);
+  UNUSED_PARAMETER(iterator);
+  if (__afl_cmp_map == NULL) { return; }
+  FATAL("CMPLOG mode not supported on this architecture");
+
+}
+
+#endif
+
diff --git a/frida_mode/src/cmplog/cmplog_x64.c b/frida_mode/src/cmplog/cmplog_x64.c
new file mode 100644
index 00000000..9bf09ad5
--- /dev/null
+++ b/frida_mode/src/cmplog/cmplog_x64.c
@@ -0,0 +1,346 @@
+#include "frida-gum.h"
+
+#include "debug.h"
+#include "cmplog.h"
+
+#include "frida_cmplog.h"
+#include "util.h"
+
+#if defined(__x86_64__)
+
+  #define X86_REG_8L(LABEL, REG)  \
+    case LABEL: {                 \
+                                  \
+      return REG & GUM_INT8_MASK; \
+                                  \
+    }
+
+  #define X86_REG_8H(LABEL, REG)          \
+    case LABEL: {                         \
+                                          \
+      return (REG & GUM_INT16_MASK) >> 8; \
+                                          \
+    }
+
+  #define X86_REG_16(LABEL, REG)     \
+    case LABEL: {                    \
+                                     \
+      return (REG & GUM_INT16_MASK); \
+                                     \
+    }
+
+  #define X86_REG_32(LABEL, REG)     \
+    case LABEL: {                    \
+                                     \
+      return (REG & GUM_INT32_MASK); \
+                                     \
+    }
+
+  #define X86_REG_64(LABEL, REG) \
+    case LABEL: {                \
+                                 \
+      return (REG);              \
+                                 \
+    }
+
+typedef struct {
+
+  x86_op_type type;
+  uint8_t     size;
+
+  union {
+
+    x86_op_mem mem;
+    x86_reg    reg;
+    int64_t    imm;
+
+  };
+
+} cmplog_ctx_t;
+
+typedef struct {
+
+  cmplog_ctx_t operand1;
+  cmplog_ctx_t operand2;
+
+} cmplog_pair_ctx_t;
+
+static guint64 cmplog_read_reg(GumX64CpuContext *ctx, x86_reg reg) {
+
+  switch (reg) {
+
+    X86_REG_8L(X86_REG_AL, ctx->rax)
+    X86_REG_8L(X86_REG_BL, ctx->rbx)
+    X86_REG_8L(X86_REG_CL, ctx->rcx)
+    X86_REG_8L(X86_REG_DL, ctx->rdx)
+    X86_REG_8L(X86_REG_BPL, ctx->rbp)
+    X86_REG_8L(X86_REG_SIL, ctx->rsi)
+    X86_REG_8L(X86_REG_DIL, ctx->rdi)
+
+    X86_REG_8H(X86_REG_AH, ctx->rax)
+    X86_REG_8H(X86_REG_BH, ctx->rbx)
+    X86_REG_8H(X86_REG_CH, ctx->rcx)
+    X86_REG_8H(X86_REG_DH, ctx->rdx)
+
+    X86_REG_16(X86_REG_AX, ctx->rax)
+    X86_REG_16(X86_REG_BX, ctx->rbx)
+    X86_REG_16(X86_REG_CX, ctx->rcx)
+    X86_REG_16(X86_REG_DX, ctx->rdx)
+    X86_REG_16(X86_REG_DI, ctx->rdi)
+    X86_REG_16(X86_REG_SI, ctx->rsi)
+    X86_REG_16(X86_REG_BP, ctx->rbp)
+
+    X86_REG_32(X86_REG_EAX, ctx->rax)
+    X86_REG_32(X86_REG_ECX, ctx->rcx)
+    X86_REG_32(X86_REG_EDX, ctx->rdx)
+    X86_REG_32(X86_REG_EBX, ctx->rbx)
+    X86_REG_32(X86_REG_ESP, ctx->rsp)
+    X86_REG_32(X86_REG_EBP, ctx->rbp)
+    X86_REG_32(X86_REG_ESI, ctx->rsi)
+    X86_REG_32(X86_REG_EDI, ctx->rdi)
+    X86_REG_32(X86_REG_R8D, ctx->r8)
+    X86_REG_32(X86_REG_R9D, ctx->r9)
+    X86_REG_32(X86_REG_R10D, ctx->r10)
+    X86_REG_32(X86_REG_R11D, ctx->r11)
+    X86_REG_32(X86_REG_R12D, ctx->r12)
+    X86_REG_32(X86_REG_R13D, ctx->r13)
+    X86_REG_32(X86_REG_R14D, ctx->r14)
+    X86_REG_32(X86_REG_R15D, ctx->r15)
+    X86_REG_32(X86_REG_EIP, ctx->rip)
+
+    X86_REG_64(X86_REG_RAX, ctx->rax)
+    X86_REG_64(X86_REG_RCX, ctx->rcx)
+    X86_REG_64(X86_REG_RDX, ctx->rdx)
+    X86_REG_64(X86_REG_RBX, ctx->rbx)
+    X86_REG_64(X86_REG_RSP, ctx->rsp)
+    X86_REG_64(X86_REG_RBP, ctx->rbp)
+    X86_REG_64(X86_REG_RSI, ctx->rsi)
+    X86_REG_64(X86_REG_RDI, ctx->rdi)
+    X86_REG_64(X86_REG_R8, ctx->r8)
+    X86_REG_64(X86_REG_R9, ctx->r9)
+    X86_REG_64(X86_REG_R10, ctx->r10)
+    X86_REG_64(X86_REG_R11, ctx->r11)
+    X86_REG_64(X86_REG_R12, ctx->r12)
+    X86_REG_64(X86_REG_R13, ctx->r13)
+    X86_REG_64(X86_REG_R14, ctx->r14)
+    X86_REG_64(X86_REG_R15, ctx->r15)
+    X86_REG_64(X86_REG_RIP, ctx->rip)
+
+    default:
+      FATAL("Failed to read register: %d", reg);
+      return 0;
+
+  }
+
+}
+
+static guint64 cmplog_read_mem(GumX64CpuContext *ctx, x86_op_mem *mem) {
+
+  guint64 base = 0;
+  guint64 index = 0;
+  guint64 address;
+
+  if (mem->base != X86_REG_INVALID) base = cmplog_read_reg(ctx, mem->base);
+
+  if (mem->index != X86_REG_INVALID) index = cmplog_read_reg(ctx, mem->index);
+
+  address = base + (index * mem->scale) + mem->disp;
+  return address;
+
+}
+
+static guint64 cmplog_get_operand_value(GumCpuContext *context,
+                                        cmplog_ctx_t * ctx) {
+
+  switch (ctx->type) {
+
+    case X86_OP_REG:
+      return cmplog_read_reg(context, ctx->reg);
+    case X86_OP_IMM:
+      return ctx->imm;
+    case X86_OP_MEM:
+      return cmplog_read_mem(context, &ctx->mem);
+    default:
+      FATAL("Invalid operand type: %d\n", ctx->type);
+
+  }
+
+}
+
+static void cmplog_call_callout(GumCpuContext *context, gpointer user_data) {
+
+  UNUSED_PARAMETER(user_data);
+
+  guint64 address = cmplog_read_reg(context, X86_REG_RIP);
+  guint64 rdi = cmplog_read_reg(context, X86_REG_RDI);
+  guint64 rsi = cmplog_read_reg(context, X86_REG_RSI);
+
+  if (((G_MAXULONG - rdi) < 32) || ((G_MAXULONG - rsi) < 32)) return;
+
+  void *ptr1 = GSIZE_TO_POINTER(rdi);
+  void *ptr2 = GSIZE_TO_POINTER(rsi);
+
+  if (!cmplog_is_readable(ptr1, 32) || !cmplog_is_readable(ptr2, 32)) return;
+
+  uintptr_t k = address;
+
+  k = (k >> 4) ^ (k << 8);
+  k &= CMP_MAP_W - 1;
+
+  __afl_cmp_map->headers[k].type = CMP_TYPE_RTN;
+
+  u32 hits = __afl_cmp_map->headers[k].hits;
+  __afl_cmp_map->headers[k].hits = hits + 1;
+
+  __afl_cmp_map->headers[k].shape = 31;
+
+  hits &= CMP_MAP_RTN_H - 1;
+  gum_memcpy(((struct cmpfn_operands *)__afl_cmp_map->log[k])[hits].v0, ptr1,
+             32);
+  gum_memcpy(((struct cmpfn_operands *)__afl_cmp_map->log[k])[hits].v1, ptr2,
+             32);
+
+}
+
+static void cmplog_instrument_put_operand(cmplog_ctx_t *ctx,
+                                          cs_x86_op *   operand) {
+
+  ctx->type = operand->type;
+  ctx->size = operand->size;
+  switch (operand->type) {
+
+    case X86_OP_REG:
+      gum_memcpy(&ctx->reg, &operand->reg, sizeof(x86_reg));
+      break;
+    case X86_OP_IMM:
+      gum_memcpy(&ctx->imm, &operand->imm, sizeof(int64_t));
+      break;
+    case X86_OP_MEM:
+      gum_memcpy(&ctx->mem, &operand->mem, sizeof(x86_op_mem));
+      break;
+    default:
+      FATAL("Invalid operand type: %d\n", operand->type);
+
+  }
+
+}
+
+static void cmplog_instrument_call(const cs_insn *     instr,
+                                   GumStalkerIterator *iterator) {
+
+  cs_x86     x86 = instr->detail->x86;
+  cs_x86_op *operand;
+
+  if (instr->id != X86_INS_CALL) return;
+
+  if (x86.op_count != 1) return;
+
+  operand = &x86.operands[0];
+
+  if (operand->type == X86_OP_INVALID) return;
+  if (operand->type == X86_OP_MEM && operand->mem.segment != X86_REG_INVALID)
+    return;
+
+  gum_stalker_iterator_put_callout(iterator, cmplog_call_callout, NULL, NULL);
+
+}
+
+static void cmplog_handle_cmp_sub(GumCpuContext *context, guint64 operand1,
+                                  guint64 operand2, uint8_t size) {
+
+  guint64 address = cmplog_read_reg(context, X86_REG_RIP);
+
+  register uintptr_t k = (uintptr_t)address;
+
+  k = (k >> 4) ^ (k << 8);
+  k &= CMP_MAP_W - 1;
+
+  __afl_cmp_map->headers[k].type = CMP_TYPE_INS;
+
+  u32 hits = __afl_cmp_map->headers[k].hits;
+  __afl_cmp_map->headers[k].hits = hits + 1;
+
+  __afl_cmp_map->headers[k].shape = (size - 1);
+
+  hits &= CMP_MAP_H - 1;
+  __afl_cmp_map->log[k][hits].v0 = operand1;
+  __afl_cmp_map->log[k][hits].v1 = operand2;
+
+}
+
+static void cmplog_cmp_sub_callout(GumCpuContext *context, gpointer user_data) {
+
+  cmplog_pair_ctx_t *ctx = (cmplog_pair_ctx_t *)user_data;
+
+  if (ctx->operand1.size != ctx->operand2.size) FATAL("Operand size mismatch");
+
+  guint64 operand1 = cmplog_get_operand_value(context, &ctx->operand1);
+  guint64 operand2 = cmplog_get_operand_value(context, &ctx->operand2);
+
+  cmplog_handle_cmp_sub(context, operand1, operand2, ctx->operand1.size);
+
+}
+
+static void cmplog_instrument_cmp_sub_put_callout(GumStalkerIterator *iterator,
+                                                  cs_x86_op *         operand1,
+                                                  cs_x86_op *operand2) {
+
+  cmplog_pair_ctx_t *ctx = g_malloc(sizeof(cmplog_pair_ctx_t));
+  if (ctx == NULL) return;
+
+  cmplog_instrument_put_operand(&ctx->operand1, operand1);
+  cmplog_instrument_put_operand(&ctx->operand2, operand2);
+
+  gum_stalker_iterator_put_callout(iterator, cmplog_cmp_sub_callout, ctx,
+                                   g_free);
+
+}
+
+static void cmplog_instrument_cmp_sub(const cs_insn *     instr,
+                                      GumStalkerIterator *iterator) {
+
+  cs_x86     x86 = instr->detail->x86;
+  cs_x86_op *operand1;
+  cs_x86_op *operand2;
+
+  switch (instr->id) {
+
+    case X86_INS_CMP:
+    case X86_INS_SUB:
+      break;
+    default:
+      return;
+
+  }
+
+  if (x86.op_count != 2) return;
+
+  operand1 = &x86.operands[0];
+  operand2 = &x86.operands[1];
+
+  if (operand1->type == X86_OP_INVALID) return;
+  if (operand2->type == X86_OP_INVALID) return;
+
+  if ((operand1->type == X86_OP_MEM) &&
+      (operand1->mem.segment != X86_REG_INVALID))
+    return;
+
+  if ((operand2->type == X86_OP_MEM) &&
+      (operand2->mem.segment != X86_REG_INVALID))
+    return;
+
+  cmplog_instrument_cmp_sub_put_callout(iterator, operand1, operand2);
+
+}
+
+void cmplog_instrument(const cs_insn *instr, GumStalkerIterator *iterator) {
+
+  if (__afl_cmp_map == NULL) return;
+
+  cmplog_instrument_call(instr, iterator);
+  cmplog_instrument_cmp_sub(instr, iterator);
+
+}
+
+#endif
+
diff --git a/frida_mode/src/cmplog/cmplog_x86.c b/frida_mode/src/cmplog/cmplog_x86.c
new file mode 100644
index 00000000..2401180c
--- /dev/null
+++ b/frida_mode/src/cmplog/cmplog_x86.c
@@ -0,0 +1,19 @@
+#include "frida-gum.h"
+
+#include "debug.h"
+
+#include "frida_cmplog.h"
+#include "util.h"
+
+#if defined(__i386__)
+void cmplog_instrument(const cs_insn *instr, GumStalkerIterator *iterator) {
+
+  UNUSED_PARAMETER(instr);
+  UNUSED_PARAMETER(iterator);
+  if (__afl_cmp_map == NULL) { return; }
+  FATAL("CMPLOG mode not supported on this architecture");
+
+}
+
+#endif
+
diff --git a/frida_mode/src/entry.c b/frida_mode/src/entry.c
new file mode 100644
index 00000000..e71386a0
--- /dev/null
+++ b/frida_mode/src/entry.c
@@ -0,0 +1,50 @@
+#include "frida-gum.h"
+
+#include "debug.h"
+
+#include "entry.h"
+#include "instrument.h"
+#include "stalker.h"
+#include "util.h"
+
+extern void __afl_manual_init();
+
+guint64 entry_start = 0;
+
+static void entry_launch(void) {
+
+  __afl_manual_init();
+
+  /* Child here */
+  previous_pc = 0;
+
+}
+
+void entry_init(void) {
+
+  entry_start = util_read_address("AFL_ENTRYPOINT");
+  OKF("entry_point: 0x%016" G_GINT64_MODIFIER "X", entry_start);
+
+}
+
+void entry_run(void) {
+
+  if (entry_start == 0) { entry_launch(); }
+
+}
+
+static void entry_callout(GumCpuContext *cpu_context, gpointer user_data) {
+
+  UNUSED_PARAMETER(cpu_context);
+  UNUSED_PARAMETER(user_data);
+  entry_launch();
+
+}
+
+void entry_prologue(GumStalkerIterator *iterator, GumStalkerOutput *output) {
+
+  UNUSED_PARAMETER(output);
+  gum_stalker_iterator_put_callout(iterator, entry_callout, NULL, NULL);
+
+}
+
diff --git a/frida_mode/src/instrument.c b/frida_mode/src/instrument.c
deleted file mode 100644
index 22910062..00000000
--- a/frida_mode/src/instrument.c
+++ /dev/null
@@ -1,271 +0,0 @@
-#include "frida-gum.h"
-#include "config.h"
-#include "debug.h"
-#include "prefetch.h"
-#include "ranges.h"
-#include "unistd.h"
-
-extern uint8_t *__afl_area_ptr;
-extern u32      __afl_map_size;
-
-uint64_t __thread previous_pc = 0;
-GumAddress current_log_impl = GUM_ADDRESS(0);
-
-static gboolean tracing = false;
-static gboolean optimize = false;
-static gboolean strict = false;
-
-#if defined(__x86_64__)
-static const guint8 afl_log_code[] = {
-
-    0x9c,                                                         /* pushfq */
-    0x50,                                                       /* push rax */
-    0x51,                                                       /* push rcx */
-    0x52,                                                       /* push rdx */
-
-    0x48, 0x8d, 0x05, 0x27,
-    0x00, 0x00, 0x00,                     /* lea rax, sym._afl_area_ptr_ptr */
-    0x48, 0x8b, 0x00,                               /* mov rax, qword [rax] */
-    0x48, 0x8b, 0x00,                               /* mov rax, qword [rax] */
-    0x48, 0x8d, 0x0d, 0x22,
-    0x00, 0x00, 0x00,                       /* lea rcx, sym.previous_pc     */
-    0x48, 0x8b, 0x11,                               /* mov rdx, qword [rcx] */
-    0x48, 0x8b, 0x12,                               /* mov rdx, qword [rdx] */
-    0x48, 0x31, 0xfa,                                       /* xor rdx, rdi */
-    0xfe, 0x04, 0x10,                               /* inc byte [rax + rdx] */
-    0x48, 0xd1, 0xef,                                         /* shr rdi, 1 */
-    0x48, 0x8b, 0x01,                               /* mov rax, qword [rcx] */
-    0x48, 0x89, 0x38,                               /* mov qword [rax], rdi */
-
-    0x5a,                                                        /* pop rdx */
-    0x59,                                                        /* pop rcx */
-    0x58,                                                        /* pop rax */
-    0x9d,                                                          /* popfq */
-
-    0xc3,                                                            /* ret */
-
-    /* Read-only data goes here: */
-    /* uint8_t** afl_area_ptr_ptr */
-    /* uint64_t* afl_prev_loc_ptr */
-
-};
-
-void instrument_coverage_optimize(const cs_insn *   instr,
-                                  GumStalkerOutput *output) {
-
-  guint64 current_pc = instr->address;
-  guint64 area_offset = (current_pc >> 4) ^ (current_pc << 8);
-  area_offset &= MAP_SIZE - 1;
-  GumX86Writer *cw = output->writer.x86;
-
-  if (current_log_impl == 0 ||
-      !gum_x86_writer_can_branch_directly_between(cw->pc, current_log_impl) ||
-      !gum_x86_writer_can_branch_directly_between(cw->pc + 128,
-                                                  current_log_impl)) {
-
-    gconstpointer after_log_impl = cw->code + 1;
-
-    gum_x86_writer_put_jmp_near_label(cw, after_log_impl);
-
-    current_log_impl = cw->pc;
-    gum_x86_writer_put_bytes(cw, afl_log_code, sizeof(afl_log_code));
-
-    uint8_t **afl_area_ptr_ptr = &__afl_area_ptr;
-    uint64_t *afl_prev_loc_ptr = &previous_pc;
-    gum_x86_writer_put_bytes(cw, (const guint8 *)&afl_area_ptr_ptr,
-                             sizeof(afl_area_ptr_ptr));
-    gum_x86_writer_put_bytes(cw, (const guint8 *)&afl_prev_loc_ptr,
-                             sizeof(afl_prev_loc_ptr));
-
-    gum_x86_writer_put_label(cw, after_log_impl);
-
-  }
-
-  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
-                                        -GUM_RED_ZONE_SIZE);
-  gum_x86_writer_put_push_reg(cw, GUM_REG_RDI);
-  gum_x86_writer_put_mov_reg_address(cw, GUM_REG_RDI, area_offset);
-  gum_x86_writer_put_call_address(cw, current_log_impl);
-  gum_x86_writer_put_pop_reg(cw, GUM_REG_RDI);
-  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
-                                        GUM_RED_ZONE_SIZE);
-
-}
-
-#elif defined(__aarch64__)
-static const guint8 afl_log_code[] = {
-
-    // __afl_area_ptr[current_pc ^ previous_pc]++;
-    // previous_pc = current_pc >> 1;
-    0xE1, 0x0B, 0xBF, 0xA9,  // stp x1, x2, [sp, -0x10]!
-    0xE3, 0x13, 0xBF, 0xA9,  // stp x3, x4, [sp, -0x10]!
-
-    // x0 = current_pc
-    0xc1, 0x01, 0x00, 0x58,  // ldr x1, #0x38, =&__afl_area_ptr
-    0x21, 0x00, 0x40, 0xf9,  // ldr x1, [x1] (=__afl_area_ptr)
-
-    0xc2, 0x01, 0x00, 0x58,  // ldr x2, #0x38, =&previous_pc
-    0x42, 0x00, 0x40, 0xf9,  // ldr x2, [x2] (=previous_pc)
-
-    // __afl_area_ptr[current_pc ^ previous_pc]++;
-    0x42, 0x00, 0x00, 0xca,  // eor x2, x2, x0
-    0x23, 0x68, 0x62, 0xf8,  // ldr x3, [x1, x2]
-    0x63, 0x04, 0x00, 0x91,  // add x3, x3, #1
-    0x23, 0x68, 0x22, 0xf8,  // str x3, [x1, x2]
-
-    // previous_pc = current_pc >> 1;
-    0xe0, 0x07, 0x40, 0x8b,  // add x0, xzr, x0, LSR #1
-    0xe2, 0x00, 0x00, 0x58,  // ldr x2, #0x1c, =&previous_pc
-    0x40, 0x00, 0x00, 0xf9,  // str x0, [x2]
-
-    0xE3, 0x13, 0xc1, 0xA8,  // ldp x3, x4, [sp], #0x10
-    0xE1, 0x0B, 0xc1, 0xA8,  // ldp x1, x2, [sp], #0x10
-    0xC0, 0x03, 0x5F, 0xD6,  // ret
-
-    // &afl_area_ptr_ptr
-    // &afl_prev_loc_ptr
-
-};
-
-void instrument_coverage_optimize(const cs_insn *   instr,
-                                  GumStalkerOutput *output) {
-
-  guint64 current_pc = instr->address;
-  guint64 area_offset = (current_pc >> 4) ^ (current_pc << 8);
-  area_offset &= MAP_SIZE - 1;
-  GumArm64Writer *cw = output->writer.arm64;
-
-  if (current_log_impl == 0 ||
-      !gum_arm64_writer_can_branch_directly_between(cw, cw->pc,
-                                                    current_log_impl) ||
-      !gum_arm64_writer_can_branch_directly_between(cw, cw->pc + 128,
-                                                    current_log_impl)) {
-
-    gconstpointer after_log_impl = cw->code + 1;
-
-    gum_arm64_writer_put_b_label(cw, after_log_impl);
-
-    current_log_impl = cw->pc;
-    gum_arm64_writer_put_bytes(cw, afl_log_code, sizeof(afl_log_code));
-
-    uint8_t **afl_area_ptr_ptr = &__afl_area_ptr;
-    uint64_t *afl_prev_loc_ptr = &previous_pc;
-    gum_arm64_writer_put_bytes(cw, (const guint8 *)&afl_area_ptr_ptr,
-                               sizeof(afl_area_ptr_ptr));
-    gum_arm64_writer_put_bytes(cw, (const guint8 *)&afl_prev_loc_ptr,
-                               sizeof(afl_prev_loc_ptr));
-
-    gum_arm64_writer_put_label(cw, after_log_impl);
-
-  }
-
-  gum_arm64_writer_put_stp_reg_reg_reg_offset(
-      cw, ARM64_REG_LR, ARM64_REG_X0, ARM64_REG_SP, -(16 + GUM_RED_ZONE_SIZE),
-      GUM_INDEX_PRE_ADJUST);
-  gum_arm64_writer_put_ldr_reg_u64(cw, ARM64_REG_X0, area_offset);
-  gum_arm64_writer_put_bl_imm(cw, current_log_impl);
-  gum_arm64_writer_put_ldp_reg_reg_reg_offset(
-      cw, ARM64_REG_LR, ARM64_REG_X0, ARM64_REG_SP, 16 + GUM_RED_ZONE_SIZE,
-      GUM_INDEX_POST_ADJUST);
-
-}
-
-#endif
-
-static void on_basic_block(GumCpuContext *context, gpointer user_data) {
-
-  /*
-   * This function is performance critical as it is called to instrument every
-   * basic block. By moving our print buffer to a global, we avoid it affecting
-   * the critical path with additional stack adjustments if tracing is not
-   * enabled. If tracing is enabled, then we're printing a load of diagnostic
-   * information so this overhead is unlikely to be noticeable.
-   */
-  static char buffer[200];
-  int         len;
-  guint64     current_pc = (guint64)user_data;
-  if (tracing) {
-
-    /* Avoid any functions which may cause an allocation since the target app
-     * may already be running inside malloc and it isn't designed to be
-     * re-entrant on a single thread */
-    len = snprintf(buffer, sizeof(buffer),
-                   "current_pc: 0x%016" G_GINT64_MODIFIER
-                   "x, previous_pc: 0x%016" G_GINT64_MODIFIER "x\n",
-                   current_pc, previous_pc);
-
-    write(STDOUT_FILENO, buffer, len + 1);
-
-  }
-
-  current_pc = (current_pc >> 4) ^ (current_pc << 8);
-  current_pc &= MAP_SIZE - 1;
-
-  __afl_area_ptr[current_pc ^ previous_pc]++;
-  previous_pc = current_pc >> 1;
-
-}
-
-void instr_basic_block(GumStalkerIterator *iterator, GumStalkerOutput *output,
-                       gpointer user_data) {
-
-  const cs_insn *instr;
-  gboolean       begin = TRUE;
-  while (gum_stalker_iterator_next(iterator, &instr)) {
-
-    if (begin) {
-
-      prefetch_write((void *)instr->address);
-      if (!strict || !range_is_excluded((void *)instr->address)) {
-
-        if (optimize) {
-
-          instrument_coverage_optimize(instr, output);
-
-        } else {
-
-          gum_stalker_iterator_put_callout(iterator, on_basic_block,
-                                           (gpointer)instr->address, NULL);
-
-        }
-
-      }
-
-      begin = FALSE;
-
-    }
-
-    gum_stalker_iterator_keep(iterator);
-
-  }
-
-}
-
-void instrument_init() {
-
-  optimize = (getenv("AFL_FRIDA_INST_NO_OPTIMIZE") == NULL);
-  tracing = (getenv("AFL_FRIDA_INST_TRACE") != NULL);
-  strict = (getenv("AFL_FRIDA_INST_STRICT") != NULL);
-
-#if !defined(__x86_64__) && !defined(__aarch64__)
-  optimize = false;
-#endif
-
-  OKF("Instrumentation - optimize [%c]", optimize ? 'X' : ' ');
-  OKF("Instrumentation - tracing [%c]", tracing ? 'X' : ' ');
-  OKF("Instrumentation - strict [%c]", strict ? 'X' : ' ');
-
-  if (tracing && optimize) {
-
-    FATAL("AFL_FRIDA_INST_OPTIMIZE and AFL_FRIDA_INST_TRACE are incompatible");
-
-  }
-
-  if (__afl_map_size != 0x10000) {
-
-    FATAL("Bad map size: 0x%08x", __afl_map_size);
-
-  }
-
-}
-
diff --git a/frida_mode/src/instrument/instrument.c b/frida_mode/src/instrument/instrument.c
new file mode 100644
index 00000000..971f80c0
--- /dev/null
+++ b/frida_mode/src/instrument/instrument.c
@@ -0,0 +1,155 @@
+#include <unistd.h>
+
+#include "frida-gum.h"
+
+#include "config.h"
+#include "debug.h"
+
+#include "entry.h"
+#include "frida_cmplog.h"
+#include "instrument.h"
+#include "persistent.h"
+#include "prefetch.h"
+#include "ranges.h"
+#include "stalker.h"
+#include "util.h"
+
+static gboolean               tracing = false;
+static gboolean               optimize = false;
+static GumStalkerTransformer *transformer = NULL;
+
+__thread uint64_t previous_pc = 0;
+
+__attribute__((hot)) static void on_basic_block(GumCpuContext *context,
+                                                gpointer       user_data) {
+
+  UNUSED_PARAMETER(context);
+  /*
+   * This function is performance critical as it is called to instrument every
+   * basic block. By moving our print buffer to a global, we avoid it affecting
+   * the critical path with additional stack adjustments if tracing is not
+   * enabled. If tracing is enabled, then we're printing a load of diagnostic
+   * information so this overhead is unlikely to be noticeable.
+   */
+  static char buffer[200];
+  int         len;
+  guint64     current_pc = (guint64)user_data;
+  uint8_t *   cursor;
+  uint64_t    value;
+  if (unlikely(tracing)) {
+
+    /* Avoid any functions which may cause an allocation since the target app
+     * may already be running inside malloc and it isn't designed to be
+     * re-entrant on a single thread */
+    len = snprintf(buffer, sizeof(buffer),
+                   "current_pc: 0x%016" G_GINT64_MODIFIER
+                   "x, previous_pc: 0x%016" G_GINT64_MODIFIER "x\n",
+                   current_pc, previous_pc);
+
+    IGNORED_RERURN(write(STDOUT_FILENO, buffer, len + 1));
+
+  }
+
+  current_pc = (current_pc >> 4) ^ (current_pc << 8);
+  current_pc &= MAP_SIZE - 1;
+
+  cursor = &__afl_area_ptr[current_pc ^ previous_pc];
+  value = *cursor;
+
+  if (value == 0xff) {
+
+    value = 1;
+
+  } else {
+
+    value++;
+
+  }
+
+  *cursor = value;
+  previous_pc = current_pc >> 1;
+
+}
+
+static void instr_basic_block(GumStalkerIterator *iterator,
+                              GumStalkerOutput *output, gpointer user_data) {
+
+  UNUSED_PARAMETER(user_data);
+
+  const cs_insn *instr;
+  gboolean       begin = TRUE;
+  while (gum_stalker_iterator_next(iterator, &instr)) {
+
+    if (instr->address == entry_start) { entry_prologue(iterator, output); }
+    if (instr->address == persistent_start) { persistent_prologue(output); }
+
+    if (begin) {
+
+      prefetch_write((void *)instr->address);
+      if (!range_is_excluded((void *)instr->address)) {
+
+        if (optimize) {
+
+          instrument_coverage_optimize(instr, output);
+
+        } else {
+
+          gum_stalker_iterator_put_callout(iterator, on_basic_block,
+                                           (gpointer)instr->address, NULL);
+
+        }
+
+      }
+
+      begin = FALSE;
+
+    }
+
+    if (!range_is_excluded((void *)instr->address)) {
+
+      cmplog_instrument(instr, iterator);
+
+    }
+
+    gum_stalker_iterator_keep(iterator);
+
+  }
+
+}
+
+void instrument_init(void) {
+
+  optimize = (getenv("AFL_FRIDA_INST_NO_OPTIMIZE") == NULL);
+  tracing = (getenv("AFL_FRIDA_INST_TRACE") != NULL);
+
+  if (!instrument_is_coverage_optimize_supported()) optimize = false;
+
+  OKF("Instrumentation - optimize [%c]", optimize ? 'X' : ' ');
+  OKF("Instrumentation - tracing [%c]", tracing ? 'X' : ' ');
+
+  if (tracing && optimize) {
+
+    FATAL("AFL_FRIDA_INST_OPTIMIZE and AFL_FRIDA_INST_TRACE are incompatible");
+
+  }
+
+  if (__afl_map_size != 0x10000) {
+
+    FATAL("Bad map size: 0x%08x", __afl_map_size);
+
+  }
+
+  transformer =
+      gum_stalker_transformer_make_from_callback(instr_basic_block, NULL, NULL);
+
+  cmplog_init();
+
+}
+
+GumStalkerTransformer *instrument_get_transformer(void) {
+
+  if (transformer == NULL) { FATAL("Instrumentation not initialized"); }
+  return transformer;
+
+}
+
diff --git a/frida_mode/src/instrument/instrument_arm32.c b/frida_mode/src/instrument/instrument_arm32.c
new file mode 100644
index 00000000..c2d720a7
--- /dev/null
+++ b/frida_mode/src/instrument/instrument_arm32.c
@@ -0,0 +1,23 @@
+#include "frida-gum.h"
+
+#include "debug.h"
+
+#include "instrument.h"
+
+#if defined(__arm__)
+
+gboolean instrument_is_coverage_optimize_supported(void) {
+
+  return false;
+
+}
+
+void instrument_coverage_optimize(const cs_insn *   instr,
+                                  GumStalkerOutput *output) {
+
+  FATAL("Optimized coverage not supported on this architecture");
+
+}
+
+#endif
+
diff --git a/frida_mode/src/instrument/instrument_arm64.c b/frida_mode/src/instrument/instrument_arm64.c
new file mode 100644
index 00000000..fa3afb48
--- /dev/null
+++ b/frida_mode/src/instrument/instrument_arm64.c
@@ -0,0 +1,97 @@
+#include "frida-gum.h"
+
+#include "config.h"
+#include "debug.h"
+
+#include "instrument.h"
+
+#if defined(__aarch64__)
+
+static GumAddress current_log_impl = GUM_ADDRESS(0);
+
+static const guint8 afl_log_code[] = {
+
+    // __afl_area_ptr[current_pc ^ previous_pc]++;
+    // previous_pc = current_pc >> 1;
+    0xE1, 0x0B, 0xBF, 0xA9,  // stp x1, x2, [sp, -0x10]!
+    0xE3, 0x13, 0xBF, 0xA9,  // stp x3, x4, [sp, -0x10]!
+
+    // x0 = current_pc
+    0xe1, 0x01, 0x00, 0x58,  // ldr x1, #0x3c, =&__afl_area_ptr
+    0x21, 0x00, 0x40, 0xf9,  // ldr x1, [x1] (=__afl_area_ptr)
+
+    0xe2, 0x01, 0x00, 0x58,  // ldr x2, #0x3c, =&previous_pc
+    0x42, 0x00, 0x40, 0xf9,  // ldr x2, [x2] (=previous_pc)
+
+    // __afl_area_ptr[current_pc ^ previous_pc]++;
+    0x42, 0x00, 0x00, 0xca,  // eor x2, x2, x0
+    0x23, 0x68, 0x62, 0xf8,  // ldr x3, [x1, x2]
+    0x63, 0x04, 0x00, 0x91,  // add x3, x3, #1
+    0x63, 0x00, 0x1f, 0x9a,  // adc x3, x3, xzr
+    0x23, 0x68, 0x22, 0xf8,  // str x3, [x1, x2]
+
+    // previous_pc = current_pc >> 1;
+    0xe0, 0x07, 0x40, 0x8b,  // add x0, xzr, x0, LSR #1
+    0xe2, 0x00, 0x00, 0x58,  // ldr x2, #0x1c, =&previous_pc
+    0x40, 0x00, 0x00, 0xf9,  // str x0, [x2]
+
+    0xE3, 0x13, 0xc1, 0xA8,  // ldp x3, x4, [sp], #0x10
+    0xE1, 0x0B, 0xc1, 0xA8,  // ldp x1, x2, [sp], #0x10
+    0xC0, 0x03, 0x5F, 0xD6,  // ret
+
+    // &afl_area_ptr_ptr
+    // &afl_prev_loc_ptr
+
+};
+
+gboolean instrument_is_coverage_optimize_supported(void) {
+
+  return true;
+
+}
+
+void instrument_coverage_optimize(const cs_insn *   instr,
+                                  GumStalkerOutput *output) {
+
+  guint64 current_pc = instr->address;
+  guint64 area_offset = (current_pc >> 4) ^ (current_pc << 8);
+  area_offset &= MAP_SIZE - 1;
+  GumArm64Writer *cw = output->writer.arm64;
+
+  if (current_log_impl == 0 ||
+      !gum_arm64_writer_can_branch_directly_between(cw, cw->pc,
+                                                    current_log_impl) ||
+      !gum_arm64_writer_can_branch_directly_between(cw, cw->pc + 128,
+                                                    current_log_impl)) {
+
+    gconstpointer after_log_impl = cw->code + 1;
+
+    gum_arm64_writer_put_b_label(cw, after_log_impl);
+
+    current_log_impl = cw->pc;
+    gum_arm64_writer_put_bytes(cw, afl_log_code, sizeof(afl_log_code));
+
+    uint8_t **afl_area_ptr_ptr = &__afl_area_ptr;
+    uint64_t *afl_prev_loc_ptr = &previous_pc;
+    gum_arm64_writer_put_bytes(cw, (const guint8 *)&afl_area_ptr_ptr,
+                               sizeof(afl_area_ptr_ptr));
+    gum_arm64_writer_put_bytes(cw, (const guint8 *)&afl_prev_loc_ptr,
+                               sizeof(afl_prev_loc_ptr));
+
+    gum_arm64_writer_put_label(cw, after_log_impl);
+
+  }
+
+  gum_arm64_writer_put_stp_reg_reg_reg_offset(
+      cw, ARM64_REG_LR, ARM64_REG_X0, ARM64_REG_SP, -(16 + GUM_RED_ZONE_SIZE),
+      GUM_INDEX_PRE_ADJUST);
+  gum_arm64_writer_put_ldr_reg_u64(cw, ARM64_REG_X0, area_offset);
+  gum_arm64_writer_put_bl_imm(cw, current_log_impl);
+  gum_arm64_writer_put_ldp_reg_reg_reg_offset(
+      cw, ARM64_REG_LR, ARM64_REG_X0, ARM64_REG_SP, 16 + GUM_RED_ZONE_SIZE,
+      GUM_INDEX_POST_ADJUST);
+
+}
+
+#endif
+
diff --git a/frida_mode/src/instrument/instrument_x64.c b/frida_mode/src/instrument/instrument_x64.c
new file mode 100644
index 00000000..901f3bd0
--- /dev/null
+++ b/frida_mode/src/instrument/instrument_x64.c
@@ -0,0 +1,93 @@
+#include "frida-gum.h"
+
+#include "config.h"
+
+#include "instrument.h"
+
+#if defined(__x86_64__)
+
+static GumAddress current_log_impl = GUM_ADDRESS(0);
+
+static const guint8 afl_log_code[] = {
+
+    // 0xcc,
+
+    0x9c,                                                         /* pushfq */
+    0x51,                                                       /* push rcx */
+    0x52,                                                       /* push rdx */
+
+    0x48, 0x8b, 0x0d, 0x28,
+    0x00, 0x00, 0x00,                          /* mov rcx, sym.&previous_pc */
+    0x48, 0x8b, 0x11,                               /* mov rdx, qword [rcx] */
+    0x48, 0x31, 0xfa,                                       /* xor rdx, rdi */
+
+    0x48, 0x03, 0x15, 0x13,
+    0x00, 0x00, 0x00,                     /* add rdx, sym._afl_area_ptr_ptr */
+
+    0x80, 0x02, 0x01,                              /* add byte ptr [rdx], 1 */
+    0x80, 0x12, 0x00,                              /* adc byte ptr [rdx], 0 */
+    0x48, 0xd1, 0xef,                                         /* shr rdi, 1 */
+    0x48, 0x89, 0x39,                               /* mov qword [rcx], rdi */
+
+    0x5a,                                                        /* pop rdx */
+    0x59,                                                        /* pop rcx */
+    0x9d,                                                          /* popfq */
+
+    0xc3,                                                            /* ret */
+    0x90, 0x90, 0x90                                             /* nop pad */
+
+    /* Read-only data goes here: */
+    /* uint8_t* __afl_area_ptr */
+    /* uint64_t* &previous_pc */
+
+};
+
+gboolean instrument_is_coverage_optimize_supported(void) {
+
+  return true;
+
+}
+
+void instrument_coverage_optimize(const cs_insn *   instr,
+                                  GumStalkerOutput *output) {
+
+  guint64 current_pc = instr->address;
+  guint64 area_offset = (current_pc >> 4) ^ (current_pc << 8);
+  area_offset &= MAP_SIZE - 1;
+  GumX86Writer *cw = output->writer.x86;
+
+  if (current_log_impl == 0 ||
+      !gum_x86_writer_can_branch_directly_between(cw->pc, current_log_impl) ||
+      !gum_x86_writer_can_branch_directly_between(cw->pc + 128,
+                                                  current_log_impl)) {
+
+    gconstpointer after_log_impl = cw->code + 1;
+
+    gum_x86_writer_put_jmp_near_label(cw, after_log_impl);
+
+    current_log_impl = cw->pc;
+    gum_x86_writer_put_bytes(cw, afl_log_code, sizeof(afl_log_code));
+
+    uint64_t *afl_prev_loc_ptr = &previous_pc;
+    gum_x86_writer_put_bytes(cw, (const guint8 *)&__afl_area_ptr,
+                             sizeof(__afl_area_ptr));
+    gum_x86_writer_put_bytes(cw, (const guint8 *)&afl_prev_loc_ptr,
+                             sizeof(afl_prev_loc_ptr));
+
+    gum_x86_writer_put_label(cw, after_log_impl);
+
+  }
+
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        -GUM_RED_ZONE_SIZE);
+  gum_x86_writer_put_push_reg(cw, GUM_REG_RDI);
+  gum_x86_writer_put_mov_reg_address(cw, GUM_REG_RDI, area_offset);
+  gum_x86_writer_put_call_address(cw, current_log_impl);
+  gum_x86_writer_put_pop_reg(cw, GUM_REG_RDI);
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        GUM_RED_ZONE_SIZE);
+
+}
+
+#endif
+
diff --git a/frida_mode/src/instrument/instrument_x86.c b/frida_mode/src/instrument/instrument_x86.c
new file mode 100644
index 00000000..5b8cbbba
--- /dev/null
+++ b/frida_mode/src/instrument/instrument_x86.c
@@ -0,0 +1,23 @@
+#include "frida-gum.h"
+
+#include "debug.h"
+
+#include "instrument.h"
+
+#if defined(__i386__)
+
+gboolean instrument_is_coverage_optimize_supported(void) {
+
+  return false;
+
+}
+
+void instrument_coverage_optimize(const cs_insn *   instr,
+                                  GumStalkerOutput *output) {
+
+  FATAL("Optimized coverage not supported on this architecture");
+
+}
+
+#endif
+
diff --git a/frida_mode/src/interceptor.c b/frida_mode/src/interceptor.c
index ba05a80a..d2802752 100644
--- a/frida_mode/src/interceptor.c
+++ b/frida_mode/src/interceptor.c
@@ -1,4 +1,5 @@
 #include "frida-gum.h"
+
 #include "debug.h"
 
 #include "interceptor.h"
@@ -9,8 +10,26 @@ void intercept(void *address, gpointer replacement, gpointer user_data) {
   gum_interceptor_begin_transaction(interceptor);
   GumReplaceReturn ret =
       gum_interceptor_replace(interceptor, address, replacement, user_data);
-  if (ret != GUM_ATTACH_OK) { FATAL("gum_interceptor_attach: %d", ret); }
+  if (ret != GUM_REPLACE_OK) { FATAL("gum_interceptor_attach: %d", ret); }
   gum_interceptor_end_transaction(interceptor);
 
 }
 
+void unintercept(void *address) {
+
+  GumInterceptor *interceptor = gum_interceptor_obtain();
+
+  gum_interceptor_begin_transaction(interceptor);
+  gum_interceptor_revert(interceptor, address);
+  gum_interceptor_end_transaction(interceptor);
+  gum_interceptor_flush(interceptor);
+
+}
+
+void unintercept_self(void) {
+
+  GumInvocationContext *ctx = gum_interceptor_get_current_invocation();
+  unintercept(ctx->function);
+
+}
+
diff --git a/frida_mode/src/lib/lib.c b/frida_mode/src/lib/lib.c
new file mode 100644
index 00000000..c5045533
--- /dev/null
+++ b/frida_mode/src/lib/lib.c
@@ -0,0 +1,176 @@
+#ifndef __APPLE__
+  #include <elf.h>
+  #include <fcntl.h>
+  #include <limits.h>
+  #include <stdio.h>
+  #include <sys/mman.h>
+  #include <unistd.h>
+
+  #include "frida-gum.h"
+
+  #include "debug.h"
+
+  #include "lib.h"
+
+  #if defined(__arm__) || defined(__i386__)
+    #define ELFCLASS ELFCLASS32
+typedef Elf32_Ehdr Elf_Ehdr;
+typedef Elf32_Phdr Elf_Phdr;
+typedef Elf32_Shdr Elf_Shdr;
+typedef Elf32_Addr Elf_Addr;
+  #elif defined(__aarch64__) || defined(__x86_64__)
+    #define ELFCLASS ELFCLASS64
+typedef Elf64_Ehdr Elf_Ehdr;
+typedef Elf64_Phdr Elf_Phdr;
+typedef Elf64_Shdr Elf_Shdr;
+typedef Elf64_Addr Elf_Addr;
+  #else
+    #error "Unsupported platform"
+  #endif
+
+typedef struct {
+
+  gchar      name[PATH_MAX + 1];
+  gchar      path[PATH_MAX + 1];
+  GumAddress base_address;
+  gsize      size;
+
+} lib_details_t;
+
+static guint64 text_base = 0;
+static guint64 text_limit = 0;
+
+static gboolean lib_find_exe(const GumModuleDetails *details,
+                             gpointer                user_data) {
+
+  lib_details_t *lib_details = (lib_details_t *)user_data;
+
+  memcpy(lib_details->name, details->name, PATH_MAX);
+  memcpy(lib_details->path, details->path, PATH_MAX);
+  lib_details->base_address = details->range->base_address;
+  lib_details->size = details->range->size;
+  return FALSE;
+
+}
+
+static void lib_validate_hdr(Elf_Ehdr *hdr) {
+
+  if (hdr->e_ident[0] != ELFMAG0) FATAL("Invalid e_ident[0]");
+  if (hdr->e_ident[1] != ELFMAG1) FATAL("Invalid e_ident[1]");
+  if (hdr->e_ident[2] != ELFMAG2) FATAL("Invalid e_ident[2]");
+  if (hdr->e_ident[3] != ELFMAG3) FATAL("Invalid e_ident[3]");
+  if (hdr->e_ident[4] != ELFCLASS) FATAL("Invalid class");
+
+}
+
+static void lib_read_text_section(lib_details_t *lib_details, Elf_Ehdr *hdr) {
+
+  Elf_Phdr *phdr;
+  gboolean  found_preferred_base = FALSE;
+  Elf_Addr  preferred_base;
+  Elf_Shdr *shdr;
+  Elf_Shdr *shstrtab;
+  char *    shstr;
+  char *    section_name;
+  Elf_Shdr *curr;
+  char      text_name[] = ".text";
+
+  phdr = (Elf_Phdr *)((char *)hdr + hdr->e_phoff);
+  for (size_t i = 0; i < hdr->e_phnum; i++) {
+
+    if (phdr[i].p_type == PT_LOAD) {
+
+      preferred_base = phdr[i].p_vaddr;
+      found_preferred_base = TRUE;
+      break;
+
+    }
+
+  }
+
+  if (!found_preferred_base) { FATAL("Failed to find preferred load address"); }
+
+  OKF("Image preferred load address 0x%016lx", preferred_base);
+
+  shdr = (Elf_Shdr *)((char *)hdr + hdr->e_shoff);
+  shstrtab = &shdr[hdr->e_shstrndx];
+  shstr = (char *)hdr + shstrtab->sh_offset;
+
+  OKF("shdr: %p", shdr);
+  OKF("shstrtab: %p", shstrtab);
+  OKF("shstr: %p", shstr);
+
+  for (size_t i = 0; i < hdr->e_shnum; i++) {
+
+    curr = &shdr[i];
+
+    if (curr->sh_name == 0) continue;
+
+    section_name = &shstr[curr->sh_name];
+    OKF("Section: %2lu - base: 0x%016lX size: 0x%016lX %s", i, curr->sh_addr,
+        curr->sh_size, section_name);
+    if (memcmp(section_name, text_name, sizeof(text_name)) == 0 &&
+        text_base == 0) {
+
+      text_base = lib_details->base_address + curr->sh_addr - preferred_base;
+      text_limit = text_base + curr->sh_size;
+      OKF("> text_addr: 0x%016lX", text_base);
+      OKF("> text_limit: 0x%016lX", text_limit);
+
+    }
+
+  }
+
+}
+
+static void lib_get_text_section(lib_details_t *details) {
+
+  int       fd = -1;
+  off_t     len;
+  Elf_Ehdr *hdr;
+
+  fd = open(details->path, O_RDONLY);
+  if (fd < 0) { FATAL("Failed to open %s", details->path); }
+
+  len = lseek(fd, 0, SEEK_END);
+
+  if (len == (off_t)-1) { FATAL("Failed to lseek %s", details->path); }
+
+  OKF("len: %ld", len);
+
+  hdr = (Elf_Ehdr *)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
+  if (hdr == MAP_FAILED) { FATAL("Failed to map %s", details->path); }
+
+  lib_validate_hdr(hdr);
+  lib_read_text_section(details, hdr);
+
+  munmap(hdr, len);
+  close(fd);
+
+}
+
+void lib_init(void) {
+
+  lib_details_t lib_details;
+  gum_process_enumerate_modules(lib_find_exe, &lib_details);
+  OKF("Executable: 0x%016lx - %s", lib_details.base_address, lib_details.path);
+  lib_get_text_section(&lib_details);
+
+}
+
+guint64 lib_get_text_base(void) {
+
+  if (text_base == 0) FATAL("Lib not initialized");
+  return text_base;
+
+}
+
+guint64 lib_get_text_limit(void) {
+
+  if (text_limit == 0) FATAL("Lib not initialized");
+  return text_limit;
+
+}
+
+#endif
+
diff --git a/frida_mode/src/lib/lib_apple.c b/frida_mode/src/lib/lib_apple.c
new file mode 100644
index 00000000..8f863861
--- /dev/null
+++ b/frida_mode/src/lib/lib_apple.c
@@ -0,0 +1,82 @@
+#ifdef __APPLE__
+  #include "frida-gum.h"
+
+  #include "debug.h"
+
+  #include "lib.h"
+  #include "util.h"
+
+extern mach_port_t mach_task_self();
+extern void        gum_darwin_enumerate_modules(mach_port_t        task,
+                                                GumFoundModuleFunc func,
+                                                gpointer           user_data);
+
+static guint64 text_base = 0;
+static guint64 text_limit = 0;
+
+static gboolean lib_get_main_module(const GumModuleDetails *details,
+                                    gpointer                user_data) {
+
+  GumDarwinModule **ret = (GumDarwinModule **)user_data;
+  GumDarwinModule * module = gum_darwin_module_new_from_memory(
+      details->path, mach_task_self(), details->range->base_address,
+      GUM_DARWIN_MODULE_FLAGS_NONE, NULL);
+
+  OKF("Found main module: %s", module->name);
+
+  *ret = module;
+
+  return FALSE;
+
+}
+
+gboolean lib_get_text_section(const GumDarwinSectionDetails *details,
+                              gpointer                       user_data) {
+
+  UNUSED_PARAMETER(user_data);
+  static size_t idx = 0;
+  char          text_name[] = "__text";
+
+  OKF("Section: %2lu - base: 0x%016" G_GINT64_MODIFIER
+      "X size: 0x%016" G_GINT64_MODIFIER "X %s",
+      idx++, details->vm_address, details->vm_address + details->size,
+      details->section_name);
+
+  if (memcmp(details->section_name, text_name, sizeof(text_name)) == 0 &&
+      text_base == 0) {
+
+    text_base = details->vm_address;
+    text_limit = details->vm_address + details->size;
+    OKF("> text_addr: 0x%016" G_GINT64_MODIFIER "X", text_base);
+    OKF("> text_limit: 0x%016" G_GINT64_MODIFIER "X", text_limit);
+
+  }
+
+  return TRUE;
+
+}
+
+void lib_init(void) {
+
+  GumDarwinModule *module = NULL;
+  gum_darwin_enumerate_modules(mach_task_self(), lib_get_main_module, &module);
+  gum_darwin_module_enumerate_sections(module, lib_get_text_section, NULL);
+
+}
+
+guint64 lib_get_text_base(void) {
+
+  if (text_base == 0) FATAL("Lib not initialized");
+  return text_base;
+
+}
+
+guint64 lib_get_text_limit(void) {
+
+  if (text_limit == 0) FATAL("Lib not initialized");
+  return text_limit;
+
+}
+
+#endif
+
diff --git a/frida_mode/src/main.c b/frida_mode/src/main.c
index 7505c2f9..e031dbed 100644
--- a/frida_mode/src/main.c
+++ b/frida_mode/src/main.c
@@ -10,13 +10,19 @@
 #endif
 
 #include "frida-gum.h"
+
 #include "config.h"
 #include "debug.h"
 
-#include "interceptor.h"
+#include "entry.h"
 #include "instrument.h"
+#include "interceptor.h"
+#include "lib.h"
+#include "persistent.h"
 #include "prefetch.h"
 #include "ranges.h"
+#include "stalker.h"
+#include "util.h"
 
 #ifdef __APPLE__
 extern mach_port_t mach_task_self();
@@ -30,16 +36,11 @@ extern int  __libc_start_main(int *(main)(int, char **, char **), int argc,
 
 typedef int *(*main_fn_t)(int argc, char **argv, char **envp);
 
-static main_fn_t      main_fn = NULL;
-static GumStalker *   stalker = NULL;
-static GumMemoryRange code_range = {0};
-
-extern void              __afl_manual_init();
-extern __thread uint64_t previous_pc;
+static main_fn_t main_fn = NULL;
 
-static int on_fork() {
+static int on_fork(void) {
 
-  prefetch_read(stalker);
+  prefetch_read();
   return fork();
 
 }
@@ -47,11 +48,17 @@ static int on_fork() {
 #ifdef __APPLE__
 static void on_main_os(int argc, char **argv, char **envp) {
 
+  UNUSED_PARAMETER(argc);
+  UNUSED_PARAMETER(argv);
+  UNUSED_PARAMETER(envp);
+
 }
 
 #else
 static void on_main_os(int argc, char **argv, char **envp) {
 
+  UNUSED_PARAMETER(argc);
+
   /* Personality doesn't affect the current process, it only takes effect on
    * evec */
   int persona = personality(ADDR_NO_RANDOMIZE);
@@ -70,37 +77,43 @@ static void on_main_os(int argc, char **argv, char **envp) {
 
 static int *on_main(int argc, char **argv, char **envp) {
 
-  on_main_os(argc, argv, envp);
+  void *fork_addr;
 
-  stalker = gum_stalker_new();
-  if (stalker == NULL) { FATAL("Failed to initialize stalker"); }
+  on_main_os(argc, argv, envp);
 
-  gum_stalker_set_trust_threshold(stalker, 0);
+  unintercept_self();
 
-  GumStalkerTransformer *transformer =
-      gum_stalker_transformer_make_from_callback(instr_basic_block, NULL, NULL);
+  stalker_init();
 
+  lib_init();
+  entry_init();
   instrument_init();
+  persistent_init();
   prefetch_init();
-  ranges_init(stalker);
+  ranges_init();
 
-  intercept(fork, on_fork, stalker);
+  fork_addr = GSIZE_TO_POINTER(gum_module_find_export_by_name(NULL, "fork"));
+  intercept(fork_addr, on_fork, NULL);
 
-  gum_stalker_follow_me(stalker, transformer, NULL);
-  gum_stalker_deactivate(stalker);
+  stalker_start();
+  entry_run();
 
-  __afl_manual_init();
+  return main_fn(argc, argv, envp);
 
-  /* Child here */
-  previous_pc = 0;
-  prefetch_start(stalker);
-  main_fn(argc, argv, envp);
-  _exit(0);
+}
+
+#if defined(EMBEDDED)
+extern int *main(int argc, char **argv, char **envp);
+
+static void intercept_main(void) {
+
+  main_fn = main;
+  intercept(main, on_main, NULL);
 
 }
 
-#ifdef __APPLE__
-static void intercept_main() {
+#elif defined(__APPLE__)
+static void intercept_main(void) {
 
   mach_port_t task = mach_task_self();
   OKF("Task Id: %u", task);
@@ -119,13 +132,14 @@ static int on_libc_start_main(int *(main)(int, char **, char **), int argc,
                               void(*stack_end)) {
 
   main_fn = main;
+  unintercept_self();
   intercept(main, on_main, NULL);
   return __libc_start_main(main, argc, ubp_av, init, fini, rtld_fini,
                            stack_end);
 
 }
 
-static void intercept_main() {
+static void intercept_main(void) {
 
   intercept(__libc_start_main, on_libc_start_main, NULL);
 
@@ -133,7 +147,7 @@ static void intercept_main() {
 
 #endif
 
-__attribute__((constructor)) static void init() {
+__attribute__((constructor)) static void init(void) {
 
   gum_init_embedded();
   if (!gum_stalker_is_supported()) {
diff --git a/frida_mode/src/persistent/persistent.c b/frida_mode/src/persistent/persistent.c
new file mode 100644
index 00000000..918ff153
--- /dev/null
+++ b/frida_mode/src/persistent/persistent.c
@@ -0,0 +1,65 @@
+#include <dlfcn.h>
+
+#include "frida-gum.h"
+
+#include "config.h"
+#include "debug.h"
+
+#include "persistent.h"
+#include "util.h"
+
+int                    __afl_sharedmem_fuzzing = 0;
+afl_persistent_hook_fn hook = NULL;
+guint64                persistent_start = 0;
+guint64                persistent_count = 0;
+
+void persistent_init(void) {
+
+  char *hook_name = getenv("AFL_FRIDA_PERSISTENT_HOOK");
+
+  persistent_start = util_read_address("AFL_FRIDA_PERSISTENT_ADDR");
+  persistent_count = util_read_num("AFL_FRIDA_PERSISTENT_CNT");
+
+  if (persistent_count != 0 && persistent_start == 0)
+    FATAL(
+        "AFL_FRIDA_PERSISTENT_ADDR must be specified if "
+        "AFL_FRIDA_PERSISTENT_CNT is");
+
+  if (persistent_start != 0 && persistent_count == 0) persistent_count = 1000;
+
+  if (persistent_count != 0 && persistent_count < 100)
+    WARNF("Persistent count out of recommended range (<100)");
+
+  if (persistent_start != 0 && !persistent_is_supported())
+    FATAL("Persistent mode not supported on this architecture");
+
+  OKF("Instrumentation - persistent mode [%c] (0x%016" G_GINT64_MODIFIER "X)",
+      persistent_start == 0 ? ' ' : 'X', persistent_start);
+  OKF("Instrumentation - persistent count [%c] (%" G_GINT64_MODIFIER "d)",
+      persistent_start == 0 ? ' ' : 'X', persistent_count);
+  OKF("Instrumentation - hook [%s]", hook_name);
+
+  if (hook_name != NULL) {
+
+    void *hook_obj = dlopen(hook_name, RTLD_NOW);
+    if (hook_obj == NULL)
+      FATAL("Failed to load AFL_FRIDA_PERSISTENT_HOOK (%s)", hook_name);
+
+    int (*afl_persistent_hook_init_ptr)(void) =
+        dlsym(hook_obj, "afl_persistent_hook_init");
+    if (afl_persistent_hook_init_ptr == NULL)
+      FATAL("Failed to find afl_persistent_hook_init in %s", hook_name);
+
+    if (afl_persistent_hook_init_ptr() == 0)
+      FATAL("afl_persistent_hook_init returned a failure");
+
+    hook = (afl_persistent_hook_fn)dlsym(hook_obj, "afl_persistent_hook");
+    if (hook == NULL)
+      FATAL("Failed to find afl_persistent_hook in %s", hook_name);
+
+    __afl_sharedmem_fuzzing = 1;
+
+  }
+
+}
+
diff --git a/frida_mode/src/persistent/persistent_arm32.c b/frida_mode/src/persistent/persistent_arm32.c
new file mode 100644
index 00000000..bc021ff3
--- /dev/null
+++ b/frida_mode/src/persistent/persistent_arm32.c
@@ -0,0 +1,72 @@
+#include "frida-gum.h"
+
+#include "debug.h"
+
+#include "persistent.h"
+#include "util.h"
+
+#if defined(__arm__)
+
+struct arm_regs {
+
+  uint32_t r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10;
+
+  union {
+
+    uint32_t r11;
+    uint32_t fp;
+
+  };
+
+  union {
+
+    uint32_t r12;
+    uint32_t ip;
+
+  };
+
+  union {
+
+    uint32_t r13;
+    uint32_t sp;
+
+  };
+
+  union {
+
+    uint32_t r14;
+    uint32_t lr;
+
+  };
+
+  union {
+
+    uint32_t r15;
+    uint32_t pc;
+
+  };
+
+  uint32_t cpsr;
+
+  uint8_t  vfp_zregs[32][16];
+  uint32_t vfp_xregs[16];
+
+};
+
+typedef struct arm_regs arch_api_regs;
+
+gboolean persistent_is_supported(void) {
+
+  return false;
+
+}
+
+void persistent_prologue(GumStalkerOutput *output) {
+
+  UNUSED_PARAMETER(output);
+  FATAL("Persistent mode not supported on this architecture");
+
+}
+
+#endif
+
diff --git a/frida_mode/src/persistent/persistent_arm64.c b/frida_mode/src/persistent/persistent_arm64.c
new file mode 100644
index 00000000..c198da69
--- /dev/null
+++ b/frida_mode/src/persistent/persistent_arm64.c
@@ -0,0 +1,115 @@
+#include "frida-gum.h"
+
+#include "config.h"
+#include "debug.h"
+
+#include "instrument.h"
+#include "util.h"
+
+#if defined(__aarch64__)
+
+struct arm64_regs {
+
+  uint64_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10;
+
+  union {
+
+    uint64_t x11;
+    uint32_t fp_32;
+
+  };
+
+  union {
+
+    uint64_t x12;
+    uint32_t ip_32;
+
+  };
+
+  union {
+
+    uint64_t x13;
+    uint32_t sp_32;
+
+  };
+
+  union {
+
+    uint64_t x14;
+    uint32_t lr_32;
+
+  };
+
+  union {
+
+    uint64_t x15;
+    uint32_t pc_32;
+
+  };
+
+  union {
+
+    uint64_t x16;
+    uint64_t ip0;
+
+  };
+
+  union {
+
+    uint64_t x17;
+    uint64_t ip1;
+
+  };
+
+  uint64_t x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28;
+
+  union {
+
+    uint64_t x29;
+    uint64_t fp;
+
+  };
+
+  union {
+
+    uint64_t x30;
+    uint64_t lr;
+
+  };
+
+  union {
+
+    uint64_t x31;
+    uint64_t sp;
+
+  };
+
+  // the zero register is not saved here ofc
+
+  uint64_t pc;
+
+  uint32_t cpsr;
+
+  uint8_t  vfp_zregs[32][16 * 16];
+  uint8_t  vfp_pregs[17][32];
+  uint32_t vfp_xregs[16];
+
+};
+
+typedef struct arm64_regs arch_api_regs;
+
+gboolean persistent_is_supported(void) {
+
+  return false;
+
+}
+
+void persistent_prologue(GumStalkerOutput *output) {
+
+  UNUSED_PARAMETER(output);
+  FATAL("Persistent mode not supported on this architecture");
+
+}
+
+#endif
+
diff --git a/frida_mode/src/persistent/persistent_x64.c b/frida_mode/src/persistent/persistent_x64.c
new file mode 100644
index 00000000..49f1988c
--- /dev/null
+++ b/frida_mode/src/persistent/persistent_x64.c
@@ -0,0 +1,342 @@
+#include "frida-gum.h"
+
+#include "config.h"
+
+#include "instrument.h"
+#include "persistent.h"
+
+#if defined(__x86_64__)
+
+struct x86_64_regs {
+
+  uint64_t rax, rbx, rcx, rdx, rdi, rsi, rbp, r8, r9, r10, r11, r12, r13, r14,
+      r15;
+
+  union {
+
+    uint64_t rip;
+    uint64_t pc;
+
+  };
+
+  union {
+
+    uint64_t rsp;
+    uint64_t sp;
+
+  };
+
+  union {
+
+    uint64_t rflags;
+    uint64_t flags;
+
+  };
+
+  uint8_t zmm_regs[32][64];
+
+};
+
+typedef struct x86_64_regs arch_api_regs;
+
+static arch_api_regs saved_regs = {0};
+static void *        saved_return = NULL;
+
+gboolean persistent_is_supported(void) {
+
+  return true;
+
+}
+
+static void instrument_persitent_save_regs(GumX86Writer *      cw,
+                                           struct x86_64_regs *regs) {
+
+  GumAddress regs_address = GUM_ADDRESS(regs);
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        -(GUM_RED_ZONE_SIZE));
+
+  /* Should be pushing FPU here, but meh */
+  gum_x86_writer_put_pushfx(cw);
+  gum_x86_writer_put_push_reg(cw, GUM_REG_RAX);
+
+  gum_x86_writer_put_mov_reg_address(cw, GUM_REG_RAX, regs_address);
+
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 1),
+                                            GUM_REG_RBX);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 2),
+                                            GUM_REG_RCX);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 3),
+                                            GUM_REG_RDX);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 4),
+                                            GUM_REG_RDI);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 5),
+                                            GUM_REG_RSI);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 6),
+                                            GUM_REG_RBP);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 7),
+                                            GUM_REG_R8);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 8),
+                                            GUM_REG_R9);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 9),
+                                            GUM_REG_R10);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 10),
+                                            GUM_REG_R11);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 11),
+                                            GUM_REG_R12);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 12),
+                                            GUM_REG_R13);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 13),
+                                            GUM_REG_R14);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 14),
+                                            GUM_REG_R15);
+
+  /* Store RIP */
+  gum_x86_writer_put_mov_reg_address(cw, GUM_REG_RBX,
+                                     GUM_ADDRESS(persistent_start));
+
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 15),
+                                            GUM_REG_RBX);
+
+  /* Store adjusted RSP */
+  gum_x86_writer_put_mov_reg_reg(cw, GUM_REG_RBX, GUM_REG_RSP);
+
+  /* RED_ZONE + Saved flags, RAX, alignment */
+  gum_x86_writer_put_add_reg_imm(cw, GUM_REG_RBX,
+                                 GUM_RED_ZONE_SIZE + (0x8 * 3));
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 16),
+                                            GUM_REG_RBX);
+
+  /* Save the flags */
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RBX, GUM_REG_RSP, 0x8);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 17),
+                                            GUM_REG_RBX);
+
+  /* Save the RAX */
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RBX, GUM_REG_RSP, 0x0);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, (0x8 * 0),
+                                            GUM_REG_RBX);
+
+  /* Pop the saved values */
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP, 0x10);
+
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        (GUM_RED_ZONE_SIZE));
+
+}
+
+static void instrument_persitent_restore_regs(GumX86Writer *      cw,
+                                              struct x86_64_regs *regs) {
+
+  GumAddress regs_address = GUM_ADDRESS(regs);
+  gum_x86_writer_put_mov_reg_address(cw, GUM_REG_RAX, regs_address);
+
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RCX, GUM_REG_RAX,
+                                            (0x8 * 2));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RDX, GUM_REG_RAX,
+                                            (0x8 * 3));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RDI, GUM_REG_RAX,
+                                            (0x8 * 4));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RSI, GUM_REG_RAX,
+                                            (0x8 * 5));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RBP, GUM_REG_RAX,
+                                            (0x8 * 6));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_R8, GUM_REG_RAX,
+                                            (0x8 * 7));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_R9, GUM_REG_RAX,
+                                            (0x8 * 8));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_R10, GUM_REG_RAX,
+                                            (0x8 * 9));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_R11, GUM_REG_RAX,
+                                            (0x8 * 10));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_R12, GUM_REG_RAX,
+                                            (0x8 * 11));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_R13, GUM_REG_RAX,
+                                            (0x8 * 12));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_R14, GUM_REG_RAX,
+                                            (0x8 * 13));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_R15, GUM_REG_RAX,
+                                            (0x8 * 14));
+
+  /* Don't restore RIP or RSP */
+
+  /* Restore RBX, RAX & Flags */
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        -(GUM_RED_ZONE_SIZE));
+
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RBX, GUM_REG_RAX,
+                                            (0x8 * 1));
+  gum_x86_writer_put_push_reg(cw, GUM_REG_RBX);
+
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RBX, GUM_REG_RAX,
+                                            (0x8 * 0));
+  gum_x86_writer_put_push_reg(cw, GUM_REG_RBX);
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RBX, GUM_REG_RAX,
+                                            (0x8 * 17));
+  gum_x86_writer_put_push_reg(cw, GUM_REG_RBX);
+
+  gum_x86_writer_put_popfx(cw);
+  gum_x86_writer_put_pop_reg(cw, GUM_REG_RAX);
+  gum_x86_writer_put_pop_reg(cw, GUM_REG_RBX);
+
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        (GUM_RED_ZONE_SIZE));
+
+}
+
+static void instrument_save_ret(GumX86Writer *cw, void **saved_return_ptr) {
+
+  GumAddress saved_return_address = GUM_ADDRESS(saved_return_ptr);
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        -(GUM_RED_ZONE_SIZE));
+  gum_x86_writer_put_push_reg(cw, GUM_REG_RAX);
+  gum_x86_writer_put_push_reg(cw, GUM_REG_RBX);
+
+  gum_x86_writer_put_mov_reg_address(cw, GUM_REG_RAX, saved_return_address);
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RBX, GUM_REG_RSP,
+                                            GUM_RED_ZONE_SIZE + 0x10);
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RAX, 0, GUM_REG_RBX);
+
+  gum_x86_writer_put_pop_reg(cw, GUM_REG_RBX);
+  gum_x86_writer_put_pop_reg(cw, GUM_REG_RAX);
+
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        (GUM_RED_ZONE_SIZE));
+
+}
+
+static void instrument_jump_ret(GumX86Writer *cw, void **saved_return_ptr) {
+
+  GumAddress saved_return_address = GUM_ADDRESS(saved_return_ptr);
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        -(GUM_RED_ZONE_SIZE));
+
+  /* Place holder for ret */
+  gum_x86_writer_put_push_reg(cw, GUM_REG_RAX);
+  gum_x86_writer_put_push_reg(cw, GUM_REG_RAX);
+
+  gum_x86_writer_put_mov_reg_address(cw, GUM_REG_RAX, saved_return_address);
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RAX, GUM_REG_RAX, 0);
+
+  gum_x86_writer_put_mov_reg_offset_ptr_reg(cw, GUM_REG_RSP, 0x8, GUM_REG_RAX);
+  gum_x86_writer_put_pop_reg(cw, GUM_REG_RAX);
+  gum_x86_writer_put_ret_imm(cw, GUM_RED_ZONE_SIZE);
+
+}
+
+static int instrument_afl_persistent_loop_func(void) {
+
+  int ret = __afl_persistent_loop(persistent_count);
+  previous_pc = 0;
+  return ret;
+
+}
+
+static void instrument_afl_persistent_loop(GumX86Writer *cw) {
+
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        -(GUM_RED_ZONE_SIZE));
+  gum_x86_writer_put_call_address_with_arguments(
+      cw, GUM_CALL_CAPI, GUM_ADDRESS(instrument_afl_persistent_loop_func), 0);
+  gum_x86_writer_put_test_reg_reg(cw, GUM_REG_RAX, GUM_REG_RAX);
+
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        (GUM_RED_ZONE_SIZE));
+
+}
+
+static void persistent_prologue_hook(GumX86Writer *      cw,
+                                     struct x86_64_regs *regs) {
+
+  if (hook == NULL) return;
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        -(GUM_RED_ZONE_SIZE));
+
+  gum_x86_writer_put_mov_reg_address(cw, GUM_REG_RCX,
+                                     GUM_ADDRESS(&__afl_fuzz_len));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RCX, GUM_REG_RCX, 0);
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RCX, GUM_REG_RCX, 0);
+  gum_x86_writer_put_mov_reg_u64(cw, GUM_REG_RDI, 0xffffffff);
+  gum_x86_writer_put_and_reg_reg(cw, GUM_REG_RCX, GUM_REG_RDI);
+
+  gum_x86_writer_put_mov_reg_address(cw, GUM_REG_RDX,
+                                     GUM_ADDRESS(&__afl_fuzz_ptr));
+  gum_x86_writer_put_mov_reg_reg_offset_ptr(cw, GUM_REG_RDX, GUM_REG_RDX, 0);
+
+  gum_x86_writer_put_call_address_with_arguments(
+      cw, GUM_CALL_CAPI, GUM_ADDRESS(hook), 4, GUM_ARG_ADDRESS,
+      GUM_ADDRESS(regs), GUM_ARG_ADDRESS, GUM_ADDRESS(0), GUM_ARG_REGISTER,
+      GUM_REG_RDX, GUM_ARG_REGISTER, GUM_REG_RCX);
+
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        (GUM_RED_ZONE_SIZE));
+
+}
+
+void persistent_prologue(GumStalkerOutput *output) {
+
+  /*
+   *  SAVE REGS
+   *  SAVE RET
+   *  POP RET
+   * loop:
+   *  CALL instrument_afl_persistent_loop
+   *  TEST EAX, EAX
+   *  JZ end:
+   *  call hook (optionally)
+   *  RESTORE REGS
+   *  call original
+   *  jmp loop:
+   *
+   * end:
+   *  JMP SAVED RET
+   *
+   * original:
+   *  INSTRUMENTED PERSISTENT FUNC
+   */
+
+  GumX86Writer *cw = output->writer.x86;
+
+  gconstpointer loop = cw->code + 1;
+  // gum_x86_writer_put_breakpoint(cw);
+
+  /* Stack must be 16-byte aligned per ABI */
+  instrument_persitent_save_regs(cw, &saved_regs);
+
+  /* Stash and pop the return value */
+  instrument_save_ret(cw, &saved_return);
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP, (8));
+
+  /* loop: */
+  gum_x86_writer_put_label(cw, loop);
+
+  /* call instrument_prologue_func */
+  instrument_afl_persistent_loop(cw);
+
+  /* jz done */
+  gconstpointer done = cw->code + 1;
+  gum_x86_writer_put_jcc_near_label(cw, X86_INS_JE, done, GUM_UNLIKELY);
+
+  /* Optionally call the persistent hook */
+  persistent_prologue_hook(cw, &saved_regs);
+
+  instrument_persitent_restore_regs(cw, &saved_regs);
+  gconstpointer original = cw->code + 1;
+  /* call original */
+  gum_x86_writer_put_call_near_label(cw, original);
+  /* jmp loop */
+  gum_x86_writer_put_jmp_near_label(cw, loop);
+
+  /* done: */
+  gum_x86_writer_put_label(cw, done);
+
+  instrument_jump_ret(cw, &saved_return);
+
+  /* original: */
+  gum_x86_writer_put_label(cw, original);
+
+  gum_x86_writer_flush(cw);
+
+}
+
+#endif
+
diff --git a/frida_mode/src/persistent/persistent_x86.c b/frida_mode/src/persistent/persistent_x86.c
new file mode 100644
index 00000000..9d39c4e9
--- /dev/null
+++ b/frida_mode/src/persistent/persistent_x86.c
@@ -0,0 +1,55 @@
+#include "frida-gum.h"
+
+#include "debug.h"
+
+#include "persistent.h"
+#include "util.h"
+
+#if defined(__i386__)
+
+struct x86_regs {
+
+  uint32_t eax, ebx, ecx, edx, edi, esi, ebp;
+
+  union {
+
+    uint32_t eip;
+    uint32_t pc;
+
+  };
+
+  union {
+
+    uint32_t esp;
+    uint32_t sp;
+
+  };
+
+  union {
+
+    uint32_t eflags;
+    uint32_t flags;
+
+  };
+
+  uint8_t xmm_regs[8][16];
+
+};
+
+typedef struct x86_regs arch_api_regs;
+
+gboolean persistent_is_supported(void) {
+
+  return false;
+
+}
+
+void persistent_prologue(GumStalkerOutput *output) {
+
+  UNUSED_PARAMETER(output);
+  FATAL("Persistent mode not supported on this architecture");
+
+}
+
+#endif
+
diff --git a/frida_mode/src/prefetch.c b/frida_mode/src/prefetch.c
index 64633c1c..65c09fba 100644
--- a/frida_mode/src/prefetch.c
+++ b/frida_mode/src/prefetch.c
@@ -3,9 +3,12 @@
 #include <sys/mman.h>
 
 #include "frida-gum.h"
-#include "prefetch.h"
+
 #include "debug.h"
 
+#include "prefetch.h"
+#include "stalker.h"
+
 #define TRUST 0
 #define PREFETCH_SIZE 65536
 #define PREFETCH_ENTRIES ((PREFETCH_SIZE - sizeof(size_t)) / sizeof(void *))
@@ -49,8 +52,9 @@ void prefetch_write(void *addr) {
 /*
  * Read the IPC region one block at the time and prefetch it
  */
-void prefetch_read(GumStalker *stalker) {
+void prefetch_read(void) {
 
+  GumStalker *stalker = stalker_get();
   if (prefetch_data == NULL) return;
 
   for (size_t i = 0; i < prefetch_data->count; i++) {
@@ -68,7 +72,7 @@ void prefetch_read(GumStalker *stalker) {
 
 }
 
-void prefetch_init() {
+void prefetch_init(void) {
 
   g_assert_cmpint(sizeof(prefetch_data_t), ==, PREFETCH_SIZE);
   gboolean prefetch = (getenv("AFL_FRIDA_INST_NO_PREFETCH") == NULL);
@@ -106,16 +110,3 @@ void prefetch_init() {
 
 }
 
-__attribute__((noinline)) static void prefetch_activation() {
-
-  asm volatile("");
-
-}
-
-void prefetch_start(GumStalker *stalker) {
-
-  gum_stalker_activate(stalker, prefetch_activation);
-  prefetch_activation();
-
-}
-
diff --git a/frida_mode/src/ranges.c b/frida_mode/src/ranges.c
index 49ef5a62..e3f09f9e 100644
--- a/frida_mode/src/ranges.c
+++ b/frida_mode/src/ranges.c
@@ -1,9 +1,12 @@
-// 0x123-0x321
-// module.so
+#include "frida-gum.h"
 
-#include "ranges.h"
 #include "debug.h"
 
+#include "lib.h"
+#include "ranges.h"
+#include "stalker.h"
+#include "util.h"
+
 #define MAX_RANGES 20
 
 typedef struct {
@@ -14,15 +17,11 @@ typedef struct {
 
 } convert_name_ctx_t;
 
-typedef struct {
-
-  GumStalker *stalker;
-  GArray *    array;
-
-} include_range_ctx_t;
-
-GArray * ranges = NULL;
-gboolean exclude_ranges = false;
+GArray *module_ranges = NULL;
+GArray *libs_ranges = NULL;
+GArray *include_ranges = NULL;
+GArray *exclude_ranges = NULL;
+GArray *ranges = NULL;
 
 static void convert_address_token(gchar *token, GumMemoryRange *range) {
 
@@ -159,236 +158,417 @@ static void convert_token(gchar *token, GumMemoryRange *range) {
 
 }
 
-static gboolean include_ranges(const GumRangeDetails *details,
-                               gpointer               user_data) {
+gint range_sort(gconstpointer a, gconstpointer b) {
 
-  include_range_ctx_t *ctx = (include_range_ctx_t *)user_data;
-  GArray *             array = (GArray *)ctx->array;
-  GumAddress           base = details->range->base_address;
-  GumAddress limit = details->range->base_address + details->range->size;
+  return ((GumMemoryRange *)a)->base_address -
+         ((GumMemoryRange *)b)->base_address;
 
-  OKF("Range for inclusion 0x%016" G_GINT64_MODIFIER
-      "x-0x%016" G_GINT64_MODIFIER "x",
-      base, limit);
+}
 
-  for (int i = 0; i < array->len; i++) {
+static gboolean print_ranges_callback(const GumRangeDetails *details,
+                                      gpointer               user_data) {
 
-    GumMemoryRange *range = &g_array_index(array, GumMemoryRange, i);
-    GumAddress      range_base = range->base_address;
-    GumAddress      range_limit = range->base_address + range->size;
+  UNUSED_PARAMETER(user_data);
+  if (details->file == NULL) {
 
-    /* Before the region */
-    if (range_limit < base) { continue; }
+    OKF("MAP - 0x%016" G_GINT64_MODIFIER "x - 0x%016" G_GINT64_MODIFIER "X",
+        details->range->base_address,
+        details->range->base_address + details->range->size);
 
-    /* After the region */
-    if (range_base > limit) {
+  } else {
 
-      GumMemoryRange exclude = {.base_address = base, .size = limit - base};
-      OKF("\t Excluding 0x%016" G_GINT64_MODIFIER "x-0x%016" G_GINT64_MODIFIER
-          "x",
-          base, limit);
-      gum_stalker_exclude(ctx->stalker, &exclude);
-      return true;
+    OKF("MAP - 0x%016" G_GINT64_MODIFIER "x - 0x%016" G_GINT64_MODIFIER
+        "X %s(0x%016" G_GINT64_MODIFIER "x)",
+        details->range->base_address,
+        details->range->base_address + details->range->size,
+        details->file->path, details->file->offset);
 
-    }
+  }
 
-    /* Overlap the start of the region */
-    if (range_base < base) {
+  return true;
 
-      /* Range contains the region */
-      if (range_limit > limit) {
+}
 
-        return true;
+static void print_ranges(char *key, GArray *ranges) {
 
-      } else {
+  OKF("Range: %s Length: %d", key, ranges->len);
+  for (guint i = 0; i < ranges->len; i++) {
 
-        base = range_limit;
-        continue;
+    GumMemoryRange *curr = &g_array_index(ranges, GumMemoryRange, i);
+    GumAddress      curr_limit = curr->base_address + curr->size;
+    OKF("Range: %s Idx: %3d - 0x%016" G_GINT64_MODIFIER
+        "x-0x%016" G_GINT64_MODIFIER "x",
+        key, i, curr->base_address, curr_limit);
 
-      }
+  }
 
-      /* Overlap the end of the region */
+}
 
-    } else {
+static gboolean collect_module_ranges_callback(const GumRangeDetails *details,
+                                               gpointer user_data) {
 
-      GumMemoryRange exclude = {.base_address = base,
-                                .size = range_base - base};
-      OKF("\t Excluding 0x%016" G_GINT64_MODIFIER "x-0x%016" G_GINT64_MODIFIER
-          "x",
-          base, range_base);
-      gum_stalker_exclude(ctx->stalker, &exclude);
-      /* Extend past the end of the region */
-      if (range_limit >= limit) {
+  GArray *       ranges = (GArray *)user_data;
+  GumMemoryRange range = *details->range;
+  g_array_append_val(ranges, range);
+  return TRUE;
 
-        return true;
+}
 
-        /* Contained within the region */
+static GArray *collect_module_ranges(void) {
 
-      } else {
+  GArray *result;
+  result = g_array_new(false, false, sizeof(GumMemoryRange));
+  gum_process_enumerate_ranges(GUM_PAGE_NO_ACCESS,
+                               collect_module_ranges_callback, result);
+  print_ranges("Modules", result);
+  return result;
 
-        base = range_limit;
-        continue;
+}
 
-      }
+static GArray *collect_ranges(char *env_key) {
 
-    }
+  char *         env_val;
+  gchar **       tokens;
+  int            token_count;
+  GumMemoryRange range;
+  int            i;
+  GArray *       result;
+
+  result = g_array_new(false, false, sizeof(GumMemoryRange));
+
+  env_val = getenv(env_key);
+  if (env_val == NULL) return result;
+
+  tokens = g_strsplit(env_val, ",", MAX_RANGES);
+
+  for (token_count = 0; tokens[token_count] != NULL; token_count++)
+    ;
+
+  for (i = 0; i < token_count; i++) {
+
+    convert_token(tokens[i], &range);
+    g_array_append_val(result, range);
 
   }
 
-  GumMemoryRange exclude = {.base_address = base, .size = limit - base};
-  OKF("\t Excluding 0x%016" G_GINT64_MODIFIER "x-0x%016" G_GINT64_MODIFIER "x",
-      base, limit);
-  gum_stalker_exclude(ctx->stalker, &exclude);
-  return true;
+  g_array_sort(result, range_sort);
 
-}
+  /* Check for overlaps */
+  for (i = 1; i < token_count; i++) {
 
-gint range_sort(gconstpointer a, gconstpointer b) {
+    GumMemoryRange *prev = &g_array_index(result, GumMemoryRange, i - 1);
+    GumMemoryRange *curr = &g_array_index(result, GumMemoryRange, i);
+    GumAddress      prev_limit = prev->base_address + prev->size;
+    GumAddress      curr_limit = curr->base_address + curr->size;
+    if (prev_limit > curr->base_address) {
 
-  return ((GumMemoryRange *)a)->base_address -
-         ((GumMemoryRange *)b)->base_address;
+      FATAL("OVerlapping ranges 0x%016" G_GINT64_MODIFIER
+            "x-0x%016" G_GINT64_MODIFIER "x 0x%016" G_GINT64_MODIFIER
+            "x-0x%016" G_GINT64_MODIFIER "x",
+            prev->base_address, prev_limit, curr->base_address, curr_limit);
+
+    }
+
+  }
+
+  print_ranges(env_key, result);
+
+  g_strfreev(tokens);
+
+  return result;
 
 }
 
-static gboolean print_ranges(const GumRangeDetails *details,
-                             gpointer               user_data) {
+static GArray *collect_libs_ranges(void) {
 
-  if (details->file == NULL) {
+  GArray *       result;
+  GumMemoryRange range;
+  result = g_array_new(false, false, sizeof(GumMemoryRange));
 
-    OKF("MAP - 0x%016" G_GINT64_MODIFIER "x - 0x%016" G_GINT64_MODIFIER "X",
-        details->range->base_address,
-        details->range->base_address + details->range->size);
+  if (getenv("AFL_INST_LIBS") == NULL) {
+
+    range.base_address = lib_get_text_base();
+    range.size = lib_get_text_limit() - lib_get_text_base();
 
   } else {
 
-    OKF("MAP - 0x%016" G_GINT64_MODIFIER "x - 0x%016" G_GINT64_MODIFIER
-        "X %s(0x%016" G_GINT64_MODIFIER "x)",
-        details->range->base_address,
-        details->range->base_address + details->range->size,
-        details->file->path, details->file->offset);
+    range.base_address = 0;
+    range.size = G_MAXULONG;
 
   }
 
+  g_array_append_val(result, range);
+
+  print_ranges("AFL_INST_LIBS", result);
+
+  return result;
+
+}
+
+static gboolean intersect_range(GumMemoryRange *rr, GumMemoryRange *ra,
+                                GumMemoryRange *rb) {
+
+  GumAddress rab = ra->base_address;
+  GumAddress ral = rab + ra->size;
+
+  GumAddress rbb = rb->base_address;
+  GumAddress rbl = rbb + rb->size;
+
+  GumAddress rrb = 0;
+  GumAddress rrl = 0;
+
+  rr->base_address = 0;
+  rr->size = 0;
+
+  /* ra is before rb */
+  if (ral < rbb) { return false; }
+
+  /* ra is after rb */
+  if (rab > rbl) { return true; }
+
+  /* The largest of the two base addresses */
+  rrb = rab > rbb ? rab : rbb;
+
+  /* The smallest of the two limits */
+  rrl = ral < rbl ? ral : rbl;
+
+  rr->base_address = rrb;
+  rr->size = rrl - rrb;
   return true;
 
 }
 
-void ranges_init(GumStalker *stalker) {
+static GArray *intersect_ranges(GArray *a, GArray *b) {
 
-  char *         showmaps;
-  char *         include;
-  char *         exclude;
-  char *         list;
-  gchar **       tokens;
-  int            token_count;
-  GumMemoryRange range;
+  GArray *        result;
+  GumMemoryRange *ra;
+  GumMemoryRange *rb;
+  GumMemoryRange  ri;
 
-  int i;
+  result = g_array_new(false, false, sizeof(GumMemoryRange));
 
-  showmaps = getenv("AFL_FRIDA_DEBUG_MAPS");
-  include = getenv("AFL_FRIDA_INST_RANGES");
-  exclude = getenv("AFL_FRIDA_EXCLUDE_RANGES");
+  for (guint i = 0; i < a->len; i++) {
 
-  if (showmaps) {
+    ra = &g_array_index(a, GumMemoryRange, i);
+    for (guint j = 0; j < b->len; j++) {
 
-    gum_process_enumerate_ranges(GUM_PAGE_NO_ACCESS, print_ranges, NULL);
+      rb = &g_array_index(b, GumMemoryRange, j);
 
-  }
+      if (!intersect_range(&ri, ra, rb)) { break; }
+
+      if (ri.size == 0) { continue; }
 
-  if (include != NULL && exclude != NULL) {
+      g_array_append_val(result, ri);
 
-    FATAL(
-        "Cannot specifify both AFL_FRIDA_INST_RANGES and "
-        "AFL_FRIDA_EXCLUDE_RANGES");
+    }
 
   }
 
-  if (include == NULL && exclude == NULL) { return; }
+  return result;
 
-  list = include == NULL ? exclude : include;
-  exclude_ranges = include == NULL ? true : false;
+}
 
-  tokens = g_strsplit(list, ",", MAX_RANGES);
+static GArray *subtract_ranges(GArray *a, GArray *b) {
 
-  for (token_count = 0; tokens[token_count] != NULL; token_count++)
-    ;
+  GArray *        result;
+  GumMemoryRange *ra;
+  GumAddress      ral;
+  GumMemoryRange *rb;
+  GumMemoryRange  ri;
+  GumMemoryRange  rs;
 
-  ranges = g_array_sized_new(false, false, sizeof(GumMemoryRange), token_count);
+  result = g_array_new(false, false, sizeof(GumMemoryRange));
 
-  for (i = 0; i < token_count; i++) {
+  for (guint i = 0; i < a->len; i++) {
 
-    convert_token(tokens[i], &range);
-    g_array_append_val(ranges, range);
+    ra = &g_array_index(a, GumMemoryRange, i);
+    ral = ra->base_address + ra->size;
+    for (guint j = 0; j < b->len; j++) {
+
+      rb = &g_array_index(b, GumMemoryRange, j);
+
+      /*
+       * If rb is after ra, we have no more possible intersections and we can
+       * simply keep the remaining range
+       */
+      if (!intersect_range(&ri, ra, rb)) { break; }
+
+      /*
+       * If there is no intersection, then rb must be before ra, so we must
+       * continue
+       */
+      if (ri.size == 0) { continue; }
+
+      /*
+       * If the intersection is part way through the range, then we keep the
+       * start of the range
+       */
+      if (ra->base_address < ri.base_address) {
+
+        rs.base_address = ra->base_address;
+        rs.size = ri.base_address - ra->base_address;
+        g_array_append_val(result, rs);
+
+      }
+
+      /*
+       * If the intersection extends past the limit of the range, then we should
+       * continue with the next range
+       */
+      if ((ri.base_address + ri.size) > ral) {
+
+        ra->base_address = ral;
+        ra->size = 0;
+        break;
+
+      }
+
+      /*
+       * Otherwise we advance the base of the range to the end of the
+       * intersection and continue with the remainder of the range
+       */
+      ra->base_address = ri.base_address + ri.size;
+      ra->size = ral - ra->base_address;
+
+    }
+
+    /*
+     * When we have processed all the possible intersections, we add what is
+     * left
+     */
+    if (ra->size != 0) g_array_append_val(result, *ra);
 
   }
 
-  g_array_sort(ranges, range_sort);
+  return result;
 
-  /* Check for overlaps */
-  for (i = 1; i < token_count; i++) {
+}
 
-    GumMemoryRange *prev = &g_array_index(ranges, GumMemoryRange, i - 1);
-    GumMemoryRange *curr = &g_array_index(ranges, GumMemoryRange, i);
-    GumAddress      prev_limit = prev->base_address + prev->size;
-    GumAddress      curr_limit = curr->base_address + curr->size;
-    if (prev_limit > curr->base_address) {
+static GArray *merge_ranges(GArray *a) {
 
-      FATAL("OVerlapping ranges 0x%016" G_GINT64_MODIFIER
-            "x-0x%016" G_GINT64_MODIFIER "x 0x%016" G_GINT64_MODIFIER
-            "x-0x%016" G_GINT64_MODIFIER "x",
-            prev->base_address, prev_limit, curr->base_address, curr_limit);
+  GArray *        result;
+  GumMemoryRange  rp;
+  GumMemoryRange *r;
+
+  result = g_array_new(false, false, sizeof(GumMemoryRange));
+  if (a->len == 0) return result;
+
+  rp = g_array_index(a, GumMemoryRange, 0);
+
+  for (guint i = 1; i < a->len; i++) {
+
+    r = &g_array_index(a, GumMemoryRange, i);
+
+    if (rp.base_address + rp.size == r->base_address) {
+
+      rp.size += r->size;
+
+    } else {
+
+      g_array_append_val(result, rp);
+      rp.base_address = r->base_address;
+      rp.size = r->size;
+      continue;
 
     }
 
   }
 
-  for (i = 0; i < token_count; i++) {
+  g_array_append_val(result, rp);
 
-    GumMemoryRange *curr = &g_array_index(ranges, GumMemoryRange, i);
-    GumAddress      curr_limit = curr->base_address + curr->size;
-    OKF("Range %3d - 0x%016" G_GINT64_MODIFIER "x-0x%016" G_GINT64_MODIFIER "x",
-        i, curr->base_address, curr_limit);
+  return result;
+
+}
+
+void ranges_init(void) {
+
+  GumMemoryRange  ri;
+  GArray *        step1;
+  GArray *        step2;
+  GArray *        step3;
+  GArray *        step4;
+  GumMemoryRange *r;
+  GumStalker *    stalker;
+
+  if (getenv("AFL_FRIDA_DEBUG_MAPS") != NULL) {
+
+    gum_process_enumerate_ranges(GUM_PAGE_NO_ACCESS, print_ranges_callback,
+                                 NULL);
 
   }
 
-  if (include == NULL) {
+  module_ranges = collect_module_ranges();
+  libs_ranges = collect_libs_ranges();
+  include_ranges = collect_ranges("AFL_FRIDA_INST_RANGES");
 
-    for (i = 0; i < token_count; i++) {
+  /* If include ranges is empty, then assume everything is included */
+  if (include_ranges->len == 0) {
 
-      gum_stalker_exclude(stalker, &g_array_index(ranges, GumMemoryRange, i));
+    ri.base_address = 0;
+    ri.size = G_MAXULONG;
+    g_array_append_val(include_ranges, ri);
 
-    }
+  }
 
-  } else {
+  exclude_ranges = collect_ranges("AFL_FRIDA_EXCLUDE_RANGES");
 
-    include_range_ctx_t ctx = {.stalker = stalker, .array = ranges};
-    gum_process_enumerate_ranges(GUM_PAGE_NO_ACCESS, include_ranges, &ctx);
+  /* Intersect with .text section of main executable unless AFL_INST_LIBS */
+  step1 = intersect_ranges(module_ranges, libs_ranges);
+  print_ranges("step1", step1);
+
+  /* Intersect with AFL_FRIDA_INST_RANGES */
+  step2 = intersect_ranges(step1, include_ranges);
+  print_ranges("step2", step2);
+
+  /* Subtract AFL_FRIDA_EXCLUDE_RANGES */
+  step3 = subtract_ranges(step2, exclude_ranges);
+  print_ranges("step3", step3);
+
+  /*
+   * After step3, we have the total ranges to be instrumented, we now subtract
+   * that from the original ranges of the modules to configure stalker.
+   */
+
+  step4 = subtract_ranges(module_ranges, step3);
+  print_ranges("step4", step4);
+
+  ranges = merge_ranges(step4);
+  print_ranges("final", ranges);
+
+  stalker = stalker_get();
+
+  for (guint i = 0; i < ranges->len; i++) {
+
+    r = &g_array_index(ranges, GumMemoryRange, i);
+    gum_stalker_exclude(stalker, r);
 
   }
 
-  g_strfreev(tokens);
+  g_array_free(step4, TRUE);
+  g_array_free(step3, TRUE);
+  g_array_free(step2, TRUE);
+  g_array_free(step1, TRUE);
 
 }
 
 gboolean range_is_excluded(gpointer address) {
 
-  int        i;
   GumAddress test = GUM_ADDRESS(address);
 
   if (ranges == NULL) { return false; }
 
-  for (i = 0; i < ranges->len; i++) {
+  for (guint i = 0; i < ranges->len; i++) {
 
     GumMemoryRange *curr = &g_array_index(ranges, GumMemoryRange, i);
     GumAddress      curr_limit = curr->base_address + curr->size;
 
-    if (test < curr->base_address) { return !exclude_ranges; }
+    if (test < curr->base_address) { return false; }
 
-    if (test < curr_limit) { return exclude_ranges; }
+    if (test < curr_limit) { return true; }
 
   }
 
-  return !exclude_ranges;
+  return false;
 
 }
 
diff --git a/frida_mode/src/stalker.c b/frida_mode/src/stalker.c
new file mode 100644
index 00000000..81973e9c
--- /dev/null
+++ b/frida_mode/src/stalker.c
@@ -0,0 +1,30 @@
+#include "debug.h"
+
+#include "instrument.h"
+#include "stalker.h"
+
+static GumStalker *stalker = NULL;
+
+void stalker_init(void) {
+
+  stalker = gum_stalker_new();
+  if (stalker == NULL) { FATAL("Failed to initialize stalker"); }
+
+  gum_stalker_set_trust_threshold(stalker, 0);
+
+}
+
+GumStalker *stalker_get(void) {
+
+  if (stalker == NULL) { FATAL("Stalker uninitialized"); }
+  return stalker;
+
+}
+
+void stalker_start(void) {
+
+  GumStalkerTransformer *transformer = instrument_get_transformer();
+  gum_stalker_follow_me(stalker, transformer, NULL);
+
+}
+
diff --git a/frida_mode/src/util.c b/frida_mode/src/util.c
new file mode 100644
index 00000000..86b94970
--- /dev/null
+++ b/frida_mode/src/util.c
@@ -0,0 +1,67 @@
+#include "util.h"
+
+#include "debug.h"
+
+guint64 util_read_address(char *key) {
+
+  char *value_str = getenv(key);
+
+  if (value_str == NULL) { return 0; }
+
+  if (!g_str_has_prefix(value_str, "0x")) {
+
+    FATAL("Invalid address should have 0x prefix: %s\n", value_str);
+
+  }
+
+  char *value_str2 = &value_str[2];
+
+  for (char *c = value_str2; *c != '\0'; c++) {
+
+    if (!g_ascii_isxdigit(*c)) {
+
+      FATAL("Invalid address not formed of hex digits: %s ('%c')\n", value_str,
+            *c);
+
+    }
+
+  }
+
+  guint64 value = g_ascii_strtoull(value_str2, NULL, 16);
+  if (value == 0) {
+
+    FATAL("Invalid address failed hex conversion: %s\n", value_str2);
+
+  }
+
+  return value;
+
+}
+
+guint64 util_read_num(char *key) {
+
+  char *value_str = getenv(key);
+
+  if (value_str == NULL) { return 0; }
+
+  for (char *c = value_str; *c != '\0'; c++) {
+
+    if (!g_ascii_isdigit(*c)) {
+
+      FATAL("Invalid address not formed of decimal digits: %s\n", value_str);
+
+    }
+
+  }
+
+  guint64 value = g_ascii_strtoull(value_str, NULL, 10);
+  if (value == 0) {
+
+    FATAL("Invalid address failed numeric conversion: %s\n", value_str);
+
+  }
+
+  return value;
+
+}
+
diff --git a/frida_mode/test/cmplog/GNUmakefile b/frida_mode/test/cmplog/GNUmakefile
new file mode 100644
index 00000000..c203fc5e
--- /dev/null
+++ b/frida_mode/test/cmplog/GNUmakefile
@@ -0,0 +1,66 @@
+PWD:=$(shell pwd)/
+ROOT:=$(shell realpath $(PWD)../../../)/
+BUILD_DIR:=$(PWD)build/
+
+TEST_CMPLOG_DIR:=$(ROOT)qemu_mode/libcompcov/
+TEST_CMPLOG_OBJ=$(TEST_CMPLOG_DIR)compcovtest
+
+TEST_BIN:=$(PWD)../../build/test
+
+
+TEST_DATA_DIR:=$(BUILD_DIR)in/
+CMP_LOG_INPUT:=$(TEST_DATA_DIR)in
+QEMU_OUT:=$(BUILD_DIR)qemu-out
+FRIDA_OUT:=$(BUILD_DIR)frida-out
+
+ARCH=$(shell uname -m)
+ifeq "$(ARCH)" "aarch64"
+ AFL_FRIDA_INST_RANGES=$(shell $(PWD)get_section_addrs.py -f $(TEST_CMPLOG_OBJ) -s .text -b 0x0000aaaaaaaaa000)
+endif
+
+ifeq "$(ARCH)" "x86_64"
+ AFL_FRIDA_INST_RANGES=$(shell $(PWD)get_section_addrs.py -f $(TEST_CMPLOG_OBJ) -s .text -b 0x0000555555554000)
+endif
+
+.PHONY: all clean qemu frida
+
+all:
+	make -C $(ROOT)frida_mode/
+
+$(BUILD_DIR):
+	mkdir -p $@
+
+$(TEST_DATA_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+$(CMP_LOG_INPUT): | $(TEST_DATA_DIR)
+	truncate -s 64 $@
+
+$(TEST_CMPLOG_OBJ): $(TEST_CMPLOG_DIR)compcovtest.cc
+	make -C $(TEST_CMPLOG_DIR) compcovtest
+
+qemu: $(TEST_CMPLOG_OBJ) $(CMP_LOG_INPUT)
+	$(ROOT)afl-fuzz \
+		-D \
+		-Q \
+		-i $(TEST_DATA_DIR) \
+		-o $(QEMU_OUT) \
+		-c 0 \
+		-l 3AT \
+		-- \
+			$(TEST_CMPLOG_OBJ) @@
+
+frida: $(TEST_CMPLOG_OBJ) $(CMP_LOG_INPUT)
+	XAFL_FRIDA_INST_RANGES=$(AFL_FRIDA_INST_RANGES) \
+	$(ROOT)afl-fuzz \
+		-D \
+		-O \
+		-i $(TEST_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-c 0 \
+		-l 3AT \
+		-- \
+			$(TEST_CMPLOG_OBJ) @@
+
+clean:
+	rm -rf $(BUILD_DIR)
\ No newline at end of file
diff --git a/frida_mode/test/cmplog/Makefile b/frida_mode/test/cmplog/Makefile
new file mode 100644
index 00000000..f322d1f5
--- /dev/null
+++ b/frida_mode/test/cmplog/Makefile
@@ -0,0 +1,12 @@
+all:
+	@echo trying to use GNU make...
+	@gmake all || echo please install GNUmake
+
+clean:
+	@gmake clean
+
+qemu:
+	@gmake qemu
+
+frida:
+	@gmake frida
\ No newline at end of file
diff --git a/frida_mode/test/testinstr.py b/frida_mode/test/cmplog/get_section_addrs.py
index f648808b..f648808b 100755
--- a/frida_mode/test/testinstr.py
+++ b/frida_mode/test/cmplog/get_section_addrs.py
diff --git a/frida_mode/test/entry_point/GNUmakefile b/frida_mode/test/entry_point/GNUmakefile
new file mode 100644
index 00000000..891827eb
--- /dev/null
+++ b/frida_mode/test/entry_point/GNUmakefile
@@ -0,0 +1,61 @@
+PWD:=$(shell pwd)/
+ROOT:=$(shell realpath $(PWD)../../..)/
+BUILD_DIR:=$(PWD)build/
+TESTINSTR_DATA_DIR:=$(BUILD_DIR)in/
+TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)in
+
+TESTINSTBIN:=$(BUILD_DIR)testinstr
+TESTINSTSRC:=$(PWD)testinstr.c
+
+QEMU_OUT:=$(BUILD_DIR)qemu-out
+FRIDA_OUT:=$(BUILD_DIR)frida-out
+
+GET_SYMBOL_ADDR:=$(ROOT)frida_mode/test/png/persistent/get_symbol_addr.py
+
+ARCH=$(shell uname -m)
+ifeq "$(ARCH)" "aarch64"
+ AFL_ENTRYPOINT=$(shell $(GET_SYMBOL_ADDR) -f $(TESTINSTBIN) -s run -b 0x0000aaaaaaaaa000)
+endif
+
+ifeq "$(ARCH)" "x86_64"
+ AFL_ENTRYPOINT=$(shell $(GET_SYMBOL_ADDR) -f $(TESTINSTBIN) -s run -b 0x0000555555554000)
+endif
+
+.PHONY: all clean qemu frida
+
+all: $(TESTINSTBIN)
+	make -C $(ROOT)frida_mode/
+
+$(BUILD_DIR):
+	mkdir -p $@
+
+$(TESTINSTR_DATA_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+$(TESTINSTR_DATA_FILE): | $(TESTINSTR_DATA_DIR)
+	echo -n "000" > $@
+
+$(TESTINSTBIN): $(TESTINSTSRC) | $(BUILD_DIR)
+	$(CC) -o $@ $<
+
+clean:
+	rm -rf $(BUILD_DIR)
+
+frida: $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
+	$(ROOT)afl-fuzz \
+		-D \
+		-O \
+		-i $(TESTINSTR_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(TESTINSTBIN) @@
+
+frida_entry: $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
+	AFL_ENTRYPOINT=$(AFL_ENTRYPOINT) \
+	$(ROOT)afl-fuzz \
+		-D \
+		-O \
+		-i $(TESTINSTR_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(TESTINSTBIN) @@
\ No newline at end of file
diff --git a/frida_mode/test/entry_point/Makefile b/frida_mode/test/entry_point/Makefile
new file mode 100644
index 00000000..3b41b94e
--- /dev/null
+++ b/frida_mode/test/entry_point/Makefile
@@ -0,0 +1,12 @@
+all:
+	@echo trying to use GNU make...
+	@gmake all || echo please install GNUmake
+
+clean:
+	@gmake clean
+
+frida:
+	@gmake frida
+
+frida_entry:
+	@gmake frida
\ No newline at end of file
diff --git a/frida_mode/test/entry_point/testinstr.c b/frida_mode/test/entry_point/testinstr.c
new file mode 100644
index 00000000..a6c655f9
--- /dev/null
+++ b/frida_mode/test/entry_point/testinstr.c
@@ -0,0 +1,119 @@
+/*
+   american fuzzy lop++ - a trivial program to test the build
+   --------------------------------------------------------
+   Originally written by Michal Zalewski
+   Copyright 2014 Google Inc. All rights reserved.
+   Copyright 2019-2020 AFLplusplus Project. All rights reserved.
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at:
+     http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef __APPLE__
+  #define TESTINSTR_SECTION
+#else
+  #define TESTINSTR_SECTION __attribute__((section(".testinstr")))
+#endif
+
+void testinstr(char *buf, int len) {
+
+  if (len < 1) return;
+  buf[len] = 0;
+
+  // we support three input cases
+  if (buf[0] == '0')
+    printf("Looks like a zero to me!\n");
+  else if (buf[0] == '1')
+    printf("Pretty sure that is a one!\n");
+  else
+    printf("Neither one or zero? How quaint!\n");
+
+}
+
+int run(char *file) {
+
+  int    fd = -1;
+  off_t  len;
+  char * buf = NULL;
+  size_t n_read;
+  int    result = -1;
+
+  do {
+
+    dprintf(STDERR_FILENO, "Running: %s\n", file);
+
+    fd = open(file, O_RDONLY);
+    if (fd < 0) {
+
+      perror("open");
+      break;
+
+    }
+
+    len = lseek(fd, 0, SEEK_END);
+    if (len < 0) {
+
+      perror("lseek (SEEK_END)");
+      break;
+
+    }
+
+    if (lseek(fd, 0, SEEK_SET) != 0) {
+
+      perror("lseek (SEEK_SET)");
+      break;
+
+    }
+
+    buf = malloc(len);
+    if (buf == NULL) {
+
+      perror("malloc");
+      break;
+
+    }
+
+    n_read = read(fd, buf, len);
+    if (n_read != len) {
+
+      perror("read");
+      break;
+
+    }
+
+    dprintf(STDERR_FILENO, "Running:    %s: (%zd bytes)\n", file, n_read);
+
+    testinstr(buf, len);
+    dprintf(STDERR_FILENO, "Done:    %s: (%zd bytes)\n", file, n_read);
+
+    result = 0;
+
+  } while (false);
+
+  if (buf != NULL) { free(buf); }
+
+  if (fd != -1) { close(fd); }
+
+  return result;
+
+}
+
+void slow() {
+  usleep(100000);
+}
+
+int main(int argc, char **argv) {
+
+  if (argc != 2) { return 1; }
+  slow();
+  return run(argv[1]);
+
+}
+
diff --git a/frida_mode/test/exe/GNUmakefile b/frida_mode/test/exe/GNUmakefile
new file mode 100644
index 00000000..c543cca8
--- /dev/null
+++ b/frida_mode/test/exe/GNUmakefile
@@ -0,0 +1,50 @@
+PWD:=$(shell pwd)/
+ROOT:=$(shell realpath $(PWD)../../..)/
+BUILD_DIR:=$(PWD)build/
+TESTINSTR_DATA_DIR:=$(BUILD_DIR)in/
+TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)in
+
+TESTINSTBIN:=$(BUILD_DIR)testinstr
+TESTINSTSRC:=$(PWD)testinstr.c
+
+QEMU_OUT:=$(BUILD_DIR)qemu-out
+FRIDA_OUT:=$(BUILD_DIR)frida-out
+
+.PHONY: all clean qemu frida
+
+all: $(TESTINSTBIN)
+	make -C $(ROOT)frida_mode/
+
+$(BUILD_DIR):
+	mkdir -p $@
+
+$(TESTINSTR_DATA_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+$(TESTINSTR_DATA_FILE): | $(TESTINSTR_DATA_DIR)
+	echo -n "000" > $@
+
+$(TESTINSTBIN): $(TESTINSTSRC) | $(BUILD_DIR)
+	$(CC) -o $@ $< -no-pie
+
+clean:
+	rm -rf $(BUILD_DIR)
+
+
+qemu: $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
+	$(ROOT)afl-fuzz \
+		-D \
+		-Q \
+		-i $(TESTINSTR_DATA_DIR) \
+		-o $(QEMU_OUT) \
+		-- \
+			$(TESTINSTBIN) @@
+
+frida: $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
+	$(ROOT)afl-fuzz \
+		-D \
+		-O \
+		-i $(TESTINSTR_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(TESTINSTBIN) @@
\ No newline at end of file
diff --git a/frida_mode/test/exe/Makefile b/frida_mode/test/exe/Makefile
new file mode 100644
index 00000000..f322d1f5
--- /dev/null
+++ b/frida_mode/test/exe/Makefile
@@ -0,0 +1,12 @@
+all:
+	@echo trying to use GNU make...
+	@gmake all || echo please install GNUmake
+
+clean:
+	@gmake clean
+
+qemu:
+	@gmake qemu
+
+frida:
+	@gmake frida
\ No newline at end of file
diff --git a/frida_mode/test/testinstr.c b/frida_mode/test/exe/testinstr.c
index 37d47f91..5e26fc46 100644
--- a/frida_mode/test/testinstr.c
+++ b/frida_mode/test/exe/testinstr.c
@@ -22,7 +22,7 @@
   #define TESTINSTR_SECTION __attribute__((section(".testinstr")))
 #endif
 
-TESTINSTR_SECTION void testinstr(char *buf, int len) {
+void testinstr(char *buf, int len) {
 
   if (len < 1) return;
   buf[len] = 0;
@@ -37,7 +37,7 @@ TESTINSTR_SECTION void testinstr(char *buf, int len) {
 
 }
 
-int main(int argc, char **argv) {
+TESTINSTR_SECTION int main(int argc, char **argv) {
 
   char * file;
   int    fd = -1;
diff --git a/frida_mode/test/png/GNUmakefile b/frida_mode/test/png/GNUmakefile
new file mode 100644
index 00000000..515728c4
--- /dev/null
+++ b/frida_mode/test/png/GNUmakefile
@@ -0,0 +1,109 @@
+PWD:=$(shell pwd)/
+ROOT:=$(shell realpath $(PWD)../../..)/
+BUILD_DIR:=$(PWD)build/
+
+LIBPNG_BUILD_DIR:=$(BUILD_DIR)libpng/
+HARNESS_BUILD_DIR:=$(BUILD_DIR)harness/
+PNGTEST_BUILD_DIR:=$(BUILD_DIR)pngtest/
+
+LIBPNG_FILE:=$(LIBPNG_BUILD_DIR)libpng-1.2.56.tar.gz
+LIBPNG_URL:=https://downloads.sourceforge.net/project/libpng/libpng12/older-releases/1.2.56/libpng-1.2.56.tar.gz
+LIBPNG_DIR:=$(LIBPNG_BUILD_DIR)libpng-1.2.56/
+LIBPNG_MAKEFILE:=$(LIBPNG_DIR)Makefile
+LIBPNG_LIB:=$(LIBPNG_DIR).libs/libpng12.a
+
+HARNESS_FILE:=$(HARNESS_BUILD_DIR)StandaloneFuzzTargetMain.c
+HARNESS_OBJ:=$(HARNESS_BUILD_DIR)StandaloneFuzzTargetMain.o
+HARNESS_URL:="https://raw.githubusercontent.com/llvm/llvm-project/main/compiler-rt/lib/fuzzer/standalone/StandaloneFuzzTargetMain.c"
+
+PNGTEST_FILE:=$(PNGTEST_BUILD_DIR)target.cc
+PNGTEST_OBJ:=$(PNGTEST_BUILD_DIR)target.o
+PNGTEST_URL:="https://raw.githubusercontent.com/google/fuzzbench/master/benchmarks/libpng-1.2.56/target.cc"
+
+TEST_BIN:=$(BUILD_DIR)test
+ifeq "$(shell uname)" "Darwin"
+TEST_BIN_LDFLAGS:=-undefined dynamic_lookup
+endif
+
+TEST_DATA_DIR:=$(LIBPNG_DIR)contrib/pngsuite/
+
+QEMU_OUT:=$(BUILD_DIR)qemu-out
+FRIDA_OUT:=$(BUILD_DIR)frida-out
+
+.PHONY: all clean qemu frida
+
+all: $(TEST_BIN)
+	make -C $(ROOT)frida_mode/
+
+$(BUILD_DIR):
+	mkdir -p $@
+
+######### HARNESS ########
+$(HARNESS_BUILD_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+$(HARNESS_FILE): | $(HARNESS_BUILD_DIR)
+	wget -O $@ $(HARNESS_URL)
+
+$(HARNESS_OBJ): $(HARNESS_FILE)
+	$(CC) -o $@ -c $<
+
+######### PNGTEST ########
+
+$(PNGTEST_BUILD_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+$(PNGTEST_FILE): | $(PNGTEST_BUILD_DIR)
+	wget -O $@ $(PNGTEST_URL)
+
+$(PNGTEST_OBJ): $(PNGTEST_FILE) | $(LIBPNG_DIR)
+	$(CXX) -std=c++11 -I $(LIBPNG_DIR) -o $@ -c $<
+
+######### LIBPNG ########
+
+$(LIBPNG_BUILD_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+$(LIBPNG_FILE): | $(LIBPNG_BUILD_DIR)
+	wget -O $@ $(LIBPNG_URL)
+
+$(LIBPNG_DIR): $(LIBPNG_FILE)
+	tar zxvf $(LIBPNG_FILE) -C $(LIBPNG_BUILD_DIR)
+
+$(LIBPNG_MAKEFILE): | $(LIBPNG_DIR)
+	cd $(LIBPNG_DIR) && ./configure
+
+$(LIBPNG_LIB): $(LIBPNG_MAKEFILE)
+	make -C $(LIBPNG_DIR)
+
+######### TEST ########
+
+$(TEST_BIN): $(HARNESS_OBJ) $(PNGTEST_OBJ) $(LIBPNG_LIB)
+	$(CXX) \
+		-o $@ \
+		$(HARNESS_OBJ) $(PNGTEST_OBJ) $(LIBPNG_LIB) \
+		-lz \
+		$(TEST_BIN_LDFLAGS) \
+
+clean:
+	rm -rf $(BUILD_DIR)
+
+qemu: $(TEST_BIN)
+	$(ROOT)afl-fuzz \
+		-D \
+		-V 30 \
+		-Q \
+		-i $(TEST_DATA_DIR) \
+		-o $(QEMU_OUT) \
+		-- \
+			$(TEST_BIN) @@
+
+frida: $(TEST_BIN)
+	$(ROOT)afl-fuzz \
+		-D \
+		-V 30 \
+		-O \
+		-i $(TEST_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(TEST_BIN) @@
diff --git a/frida_mode/test/png/Makefile b/frida_mode/test/png/Makefile
new file mode 100644
index 00000000..f322d1f5
--- /dev/null
+++ b/frida_mode/test/png/Makefile
@@ -0,0 +1,12 @@
+all:
+	@echo trying to use GNU make...
+	@gmake all || echo please install GNUmake
+
+clean:
+	@gmake clean
+
+qemu:
+	@gmake qemu
+
+frida:
+	@gmake frida
\ No newline at end of file
diff --git a/frida_mode/test/png/persistent/GNUmakefile b/frida_mode/test/png/persistent/GNUmakefile
new file mode 100644
index 00000000..531f9bce
--- /dev/null
+++ b/frida_mode/test/png/persistent/GNUmakefile
@@ -0,0 +1,79 @@
+PWD:=$(shell pwd)/
+ROOT:=$(shell realpath $(PWD)../../../..)/
+BUILD_DIR:=$(PWD)build/
+
+TEST_BIN:=$(PWD)../build/test
+TEST_DATA_DIR:=../build/libpng/libpng-1.2.56/contrib/pngsuite/
+
+QEMU_OUT:=$(BUILD_DIR)qemu-out
+FRIDA_OUT:=$(BUILD_DIR)frida-out
+
+AFL_QEMU_PERSISTENT_ADDR=$(shell $(PWD)get_symbol_addr.py -f $(TEST_BIN) -s main -b 0x4000000000)
+
+ARCH=$(shell uname -m)
+ifeq "$(ARCH)" "aarch64"
+ AFL_FRIDA_PERSISTENT_ADDR=$(shell $(PWD)get_symbol_addr.py -f $(TEST_BIN) -s main -b 0x0000aaaaaaaaa000)
+endif
+
+ifeq "$(ARCH)" "x86_64"
+ AFL_FRIDA_PERSISTENT_ADDR=$(shell $(PWD)get_symbol_addr.py -f $(TEST_BIN) -s main -b 0x0000555555554000)
+endif
+
+.PHONY: all clean qemu qemu_entry frida frida_entry
+
+all:
+	make -C $(ROOT)frida_mode/test/png/
+
+$(BUILD_DIR):
+	mkdir -p $@
+
+qemu: | $(BUILD_DIR)
+	AFL_QEMU_PERSISTENT_ADDR=$(AFL_QEMU_PERSISTENT_ADDR) \
+	AFL_QEMU_PERSISTENT_GPR=1 \
+	$(ROOT)afl-fuzz \
+		-D \
+		-V 30 \
+		-Q \
+		-i $(TEST_DATA_DIR) \
+		-o $(QEMU_OUT) \
+		-- \
+			$(TEST_BIN) @@
+
+qemu_entry: | $(BUILD_DIR)
+	AFL_QEMU_PERSISTENT_ADDR=$(AFL_QEMU_PERSISTENT_ADDR) \
+	AFL_QEMU_PERSISTENT_GPR=1 \
+	AFL_ENTRYPOINT=$(AFL_QEMU_PERSISTENT_ADDR) \
+	$(ROOT)afl-fuzz \
+		-D \
+		-V 30 \
+		-Q \
+		-i $(TEST_DATA_DIR) \
+		-o $(QEMU_OUT) \
+		-- \
+			$(TEST_BIN) @@
+
+frida: | $(BUILD_DIR)
+	AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_PERSISTENT_ADDR) \
+	$(ROOT)afl-fuzz \
+		-D \
+		-V 30 \
+		-O \
+		-i $(TEST_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(TEST_BIN) @@
+
+frida_entry: | $(BUILD_DIR)
+	AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_PERSISTENT_ADDR) \
+	AFL_ENTRYPOINT=$(AFL_FRIDA_PERSISTENT_ADDR) \
+	$(ROOT)afl-fuzz \
+		-D \
+		-V 30 \
+		-O \
+		-i $(TEST_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(TEST_BIN) @@
+
+clean:
+	rm -rf $(BUILD_DIR)
\ No newline at end of file
diff --git a/frida_mode/test/png/persistent/Makefile b/frida_mode/test/png/persistent/Makefile
new file mode 100644
index 00000000..5fde63c2
--- /dev/null
+++ b/frida_mode/test/png/persistent/Makefile
@@ -0,0 +1,18 @@
+all:
+	@echo trying to use GNU make...
+	@gmake all || echo please install GNUmake
+
+clean:
+	@gmake clean
+
+qemu:
+	@gmake qemu
+
+qemu_entry:
+	@gmake qemu_entry
+
+frida:
+	@gmake frida
+
+frida_entry:
+	@gmake frida_entry
\ No newline at end of file
diff --git a/frida_mode/test/png/persistent/get_symbol_addr.py b/frida_mode/test/png/persistent/get_symbol_addr.py
new file mode 100755
index 00000000..6458c212
--- /dev/null
+++ b/frida_mode/test/png/persistent/get_symbol_addr.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python3
+import argparse
+from elftools.elf.elffile import ELFFile
+
+def process_file(file, symbol, base):
+    with open(file, 'rb') as f:
+        elf = ELFFile(f)
+        symtab = elf.get_section_by_name('.symtab')
+        mains = symtab.get_symbol_by_name(symbol)
+        if len(mains) != 1:
+            print ("Failed to find main")
+            return 1
+
+        main_addr = mains[0]['st_value']
+        main = base + main_addr
+        print ("0x%016x" % main)
+        return 0
+
+def hex_value(x):
+    return int(x, 16)
+
+def main():
+    parser = argparse.ArgumentParser(description='Process some integers.')
+    parser.add_argument('-f', '--file', dest='file', type=str,
+                    help='elf file name', required=True)
+    parser.add_argument('-s', '--symbol', dest='symbol', type=str,
+                    help='symbol name', required=True)
+    parser.add_argument('-b', '--base', dest='base', type=hex_value,
+                    help='elf base address', required=True)
+
+    args = parser.parse_args()
+    return process_file (args.file, args.symbol, args.base)
+
+if __name__ == "__main__":
+    ret = main()
+    exit(ret)
\ No newline at end of file
diff --git a/frida_mode/test/png/persistent/hook/GNUmakefile b/frida_mode/test/png/persistent/hook/GNUmakefile
new file mode 100644
index 00000000..4f55fe98
--- /dev/null
+++ b/frida_mode/test/png/persistent/hook/GNUmakefile
@@ -0,0 +1,98 @@
+PWD:=$(shell pwd)/
+ROOT:=$(shell realpath $(PWD)../../../../..)/
+BUILD_DIR:=$(PWD)build/
+
+AFLPP_DRIVER_HOOK_DIR=$(ROOT)utils/aflpp_driver/
+AFLPP_DRIVER_HOOK_OBJ=$(AFLPP_DRIVER_HOOK_DIR)aflpp_qemu_driver_hook.so
+
+TEST_BIN:=$(PWD)../../build/test
+TEST_DATA_DIR:=../../build/libpng/libpng-1.2.56/contrib/pngsuite/
+
+AFLPP_DRIVER_DUMMY_INPUT:=$(BUILD_DIR)in
+QEMU_OUT:=$(BUILD_DIR)qemu-out
+FRIDA_OUT:=$(BUILD_DIR)frida-out
+
+AFL_QEMU_PERSISTENT_ADDR=$(shell $(PWD)../get_symbol_addr.py -f $(TEST_BIN) -s LLVMFuzzerTestOneInput -b 0x4000000000)
+
+ARCH=$(shell uname -m)
+ifeq "$(ARCH)" "aarch64"
+ AFL_FRIDA_PERSISTENT_ADDR=$(shell $(PWD)../get_symbol_addr.py -f $(TEST_BIN) -s LLVMFuzzerTestOneInput -b 0x0000aaaaaaaaa000)
+endif
+
+ifeq "$(ARCH)" "x86_64"
+ AFL_FRIDA_PERSISTENT_ADDR=$(shell $(PWD)../get_symbol_addr.py -f $(TEST_BIN) -s LLVMFuzzerTestOneInput -b 0x0000555555554000)
+endif
+
+.PHONY: all clean qemu qemu_entry frida frida_entry
+
+all:
+	make -C $(ROOT)frida_mode/test/png/persistent/
+
+$(BUILD_DIR):
+	mkdir -p $@
+
+$(TEST_DATA_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+$(AFLPP_DRIVER_DUMMY_INPUT): | $(BUILD_DIR)
+	truncate -s 1M $@
+
+$(AFLPP_DRIVER_HOOK_OBJ): | $(AFLPP_DRIVER_HOOK_DIR)
+	make -C $(AFLPP_DRIVER_HOOK_DIR)
+
+qemu: $(AFLPP_DRIVER_DUMMY_INPUT) $(AFLPP_DRIVER_HOOK_OBJ) | $(BUILD_DIR)
+	AFL_QEMU_PERSISTENT_HOOK=$(AFLPP_DRIVER_HOOK_OBJ) \
+	AFL_QEMU_PERSISTENT_ADDR=$(AFL_QEMU_PERSISTENT_ADDR) \
+	AFL_QEMU_PERSISTENT_GPR=1 \
+	$(ROOT)/afl-fuzz \
+		-D \
+		-V 30 \
+		-Q \
+		-i $(TEST_DATA_DIR) \
+		-o $(QEMU_OUT) \
+		-- \
+			$(TEST_BIN) $(AFLPP_DRIVER_DUMMY_INPUT)
+
+qemu_entry: $(AFLPP_DRIVER_DUMMY_INPUT) $(AFLPP_DRIVER_HOOK_OBJ) | $(BUILD_DIR)
+	AFL_QEMU_PERSISTENT_HOOK=$(AFLPP_DRIVER_HOOK_OBJ) \
+	AFL_QEMU_PERSISTENT_ADDR=$(AFL_QEMU_PERSISTENT_ADDR) \
+	AFL_ENTRYPOINT=$(AFL_QEMU_PERSISTENT_ADDR) \
+	AFL_QEMU_PERSISTENT_GPR=1 \
+	$(ROOT)/afl-fuzz \
+		-D \
+		-V 30 \
+		-Q \
+		-i $(TEST_DATA_DIR) \
+		-o $(QEMU_OUT) \
+		-- \
+			$(TEST_BIN) $(AFLPP_DRIVER_DUMMY_INPUT)
+
+frida: $(AFLPP_DRIVER_DUMMY_INPUT) $(AFLPP_DRIVER_HOOK_OBJ) | $(BUILD_DIR)
+	AFL_FRIDA_PERSISTENT_HOOK=$(AFLPP_DRIVER_HOOK_OBJ) \
+	AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_PERSISTENT_ADDR) \
+	$(ROOT)afl-fuzz \
+		-D \
+		-V 30 \
+		-O \
+		-i $(TEST_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(TEST_BIN) $(AFLPP_DRIVER_DUMMY_INPUT)
+
+
+frida_entry: $(AFLPP_DRIVER_DUMMY_INPUT) $(AFLPP_DRIVER_HOOK_OBJ) | $(BUILD_DIR)
+	AFL_FRIDA_PERSISTENT_HOOK=$(AFLPP_DRIVER_HOOK_OBJ) \
+	AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_PERSISTENT_ADDR) \
+	AFL_ENTRYPOINT=$(AFL_FRIDA_PERSISTENT_ADDR) \
+	$(ROOT)afl-fuzz \
+		-D \
+		-V 30 \
+		-O \
+		-i $(TEST_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(TEST_BIN) $(AFLPP_DRIVER_DUMMY_INPUT)
+
+clean:
+	rm -rf $(BUILD_DIR)
+
diff --git a/frida_mode/test/png/persistent/hook/Makefile b/frida_mode/test/png/persistent/hook/Makefile
new file mode 100644
index 00000000..5fde63c2
--- /dev/null
+++ b/frida_mode/test/png/persistent/hook/Makefile
@@ -0,0 +1,18 @@
+all:
+	@echo trying to use GNU make...
+	@gmake all || echo please install GNUmake
+
+clean:
+	@gmake clean
+
+qemu:
+	@gmake qemu
+
+qemu_entry:
+	@gmake qemu_entry
+
+frida:
+	@gmake frida
+
+frida_entry:
+	@gmake frida_entry
\ No newline at end of file
diff --git a/frida_mode/test/testinstr/GNUmakefile b/frida_mode/test/testinstr/GNUmakefile
new file mode 100644
index 00000000..4addbad8
--- /dev/null
+++ b/frida_mode/test/testinstr/GNUmakefile
@@ -0,0 +1,50 @@
+PWD:=$(shell pwd)/
+ROOT:=$(shell realpath $(PWD)../../..)/
+BUILD_DIR:=$(PWD)build/
+TESTINSTR_DATA_DIR:=$(BUILD_DIR)in/
+TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)in
+
+TESTINSTBIN:=$(BUILD_DIR)testinstr
+TESTINSTSRC:=$(PWD)testinstr.c
+
+QEMU_OUT:=$(BUILD_DIR)qemu-out
+FRIDA_OUT:=$(BUILD_DIR)frida-out
+
+.PHONY: all clean qemu frida
+
+all: $(TESTINSTBIN)
+	make -C $(ROOT)frida_mode/
+
+$(BUILD_DIR):
+	mkdir -p $@
+
+$(TESTINSTR_DATA_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+$(TESTINSTR_DATA_FILE): | $(TESTINSTR_DATA_DIR)
+	echo -n "000" > $@
+
+$(TESTINSTBIN): $(TESTINSTSRC) | $(BUILD_DIR)
+	$(CC) -o $@ $<
+
+clean:
+	rm -rf $(BUILD_DIR)
+
+
+qemu: $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
+	$(ROOT)afl-fuzz \
+		-D \
+		-Q \
+		-i $(TESTINSTR_DATA_DIR) \
+		-o $(QEMU_OUT) \
+		-- \
+			$(TESTINSTBIN) @@
+
+frida: $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
+	$(ROOT)afl-fuzz \
+		-D \
+		-O \
+		-i $(TESTINSTR_DATA_DIR) \
+		-o $(FRIDA_OUT) \
+		-- \
+			$(TESTINSTBIN) @@
\ No newline at end of file
diff --git a/frida_mode/test/testinstr/Makefile b/frida_mode/test/testinstr/Makefile
new file mode 100644
index 00000000..f322d1f5
--- /dev/null
+++ b/frida_mode/test/testinstr/Makefile
@@ -0,0 +1,12 @@
+all:
+	@echo trying to use GNU make...
+	@gmake all || echo please install GNUmake
+
+clean:
+	@gmake clean
+
+qemu:
+	@gmake qemu
+
+frida:
+	@gmake frida
\ No newline at end of file
diff --git a/frida_mode/test/testinstr/testinstr.c b/frida_mode/test/testinstr/testinstr.c
new file mode 100644
index 00000000..5e26fc46
--- /dev/null
+++ b/frida_mode/test/testinstr/testinstr.c
@@ -0,0 +1,112 @@
+/*
+   american fuzzy lop++ - a trivial program to test the build
+   --------------------------------------------------------
+   Originally written by Michal Zalewski
+   Copyright 2014 Google Inc. All rights reserved.
+   Copyright 2019-2020 AFLplusplus Project. All rights reserved.
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at:
+     http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef __APPLE__
+  #define TESTINSTR_SECTION
+#else
+  #define TESTINSTR_SECTION __attribute__((section(".testinstr")))
+#endif
+
+void testinstr(char *buf, int len) {
+
+  if (len < 1) return;
+  buf[len] = 0;
+
+  // we support three input cases
+  if (buf[0] == '0')
+    printf("Looks like a zero to me!\n");
+  else if (buf[0] == '1')
+    printf("Pretty sure that is a one!\n");
+  else
+    printf("Neither one or zero? How quaint!\n");
+
+}
+
+TESTINSTR_SECTION int main(int argc, char **argv) {
+
+  char * file;
+  int    fd = -1;
+  off_t  len;
+  char * buf = NULL;
+  size_t n_read;
+  int    result = -1;
+
+  if (argc != 2) { return 1; }
+
+  do {
+
+    file = argv[1];
+
+    dprintf(STDERR_FILENO, "Running: %s\n", file);
+
+    fd = open(file, O_RDONLY);
+    if (fd < 0) {
+
+      perror("open");
+      break;
+
+    }
+
+    len = lseek(fd, 0, SEEK_END);
+    if (len < 0) {
+
+      perror("lseek (SEEK_END)");
+      break;
+
+    }
+
+    if (lseek(fd, 0, SEEK_SET) != 0) {
+
+      perror("lseek (SEEK_SET)");
+      break;
+
+    }
+
+    buf = malloc(len);
+    if (buf == NULL) {
+
+      perror("malloc");
+      break;
+
+    }
+
+    n_read = read(fd, buf, len);
+    if (n_read != len) {
+
+      perror("read");
+      break;
+
+    }
+
+    dprintf(STDERR_FILENO, "Running:    %s: (%zd bytes)\n", file, n_read);
+
+    testinstr(buf, len);
+    dprintf(STDERR_FILENO, "Done:    %s: (%zd bytes)\n", file, n_read);
+
+    result = 0;
+
+  } while (false);
+
+  if (buf != NULL) { free(buf); }
+
+  if (fd != -1) { close(fd); }
+
+  return result;
+
+}
+
diff --git a/frida_mode/update_frida_version.sh b/frida_mode/update_frida_version.sh
new file mode 100755
index 00000000..7d938712
--- /dev/null
+++ b/frida_mode/update_frida_version.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+test -n "$1" && { echo This script has no options. It updates the referenced Frida version in GNUmakefile to the most current one. ; exit 1 ; }
+
+OLD=$(egrep '^GUM_DEVKIT_VERSION=' GNUmakefile 2>/dev/null|awk -F= '{print$2}')
+NEW=$(curl https://github.com/frida/frida/releases/ 2>/dev/null|egrep 'frida-gum-devkit-[0-9.]*-linux-x86_64'|head -n 1|sed 's/.*frida-gum-devkit-//'|sed 's/-linux.*//')
+
+echo Current set version: $OLD
+echo Newest available version: $NEW
+
+test -z "$OLD" -o -z "$NEW" -o "$OLD" = "$NEW" && { echo Nothing to be done. ; exit 0 ; }
+
+sed -i "s/=$OLD/=$NEW/" GNUmakefile || exit 1
+echo Successfully updated GNUmakefile