diff --git a/risu.c b/risu.c
index 756ca46..f36b4c6 100644
--- a/risu.c
+++ b/risu.c
@@ -31,6 +31,7 @@
 void *memblock = 0;
 
 int apprentice_socket, master_socket;
+int trace_file = 0;
 
 sigjmp_buf jmpbuf;
 
@@ -40,31 +41,57 @@ int test_fp_exc = 0;
 long executed_tests = 0;
 void report_test_status(void *pc)
 {
-   executed_tests += 1;
-   if (executed_tests % 100 == 0) {
-      fprintf(stderr,"Executed %ld test instructions (pc=%p)\r",
-              executed_tests, pc);
+   if (ismaster || trace_file) {
+      executed_tests += 1;
+      if (executed_tests % 100 == 0) {
+         fprintf(stderr,"Executed %ld test instructions (pc=%p)\r",
+                 executed_tests, pc);
+      }
    }
 }
 
+int write_trace(void *ptr, size_t bytes)
+{
+   size_t res = write(trace_file, ptr, bytes);
+   return res == bytes;
+}
+
+int read_trace(void *ptr, size_t bytes)
+{
+   size_t res = read(trace_file, ptr, bytes);
+   return res == bytes;
+}
+
 void master_sigill(int sig, siginfo_t *si, void *uc)
 {
-   switch (recv_and_compare_register_info(master_socket, uc))
-   {
-      case 0:
-         /* match OK */
-         advance_pc(uc);
-         return;
-      default:
-         /* mismatch, or end of test */
-         siglongjmp(jmpbuf, 1);
+   if (trace_file) {
+     if (write_to_tracefile(write_trace, uc)!=0) {
+       /* end of test */
+       siglongjmp(jmpbuf, 1);
+       /* never */
+       return;
+     }
+   } else {
+     if (recv_and_compare_register_info(master_socket, uc)!=0) {
+       /* mismatch, or end of test */
+       siglongjmp(jmpbuf, 1);
+       /* never */
+       return;
+     }
    }
+   advance_pc(uc);
 }
 
 void apprentice_sigill(int sig, siginfo_t *si, void *uc)
 {
-   switch (send_register_info(apprentice_socket, uc))
-   {
+  int resp;
+  if (trace_file) {
+    resp = read_tracefile_and_check(read_trace, uc);
+  } else {
+    resp = send_register_info(apprentice_socket, uc);
+  }
+  switch (resp)
+  {
       case 0:
          /* match OK */
          advance_pc(uc);
@@ -75,6 +102,9 @@ void apprentice_sigill(int sig, siginfo_t *si, void *uc)
          exit(0);
       default:
          /* mismatch */
+         if (trace_file) {
+            report_match_status();
+         }
          exit(1);
    }
 }
@@ -136,7 +166,13 @@ int master(int sock)
 {
    if (sigsetjmp(jmpbuf, 1))
    {
-      return report_match_status();
+      if (trace_file) {
+         close(trace_file);
+         fprintf(stderr,"Done...\n");
+         return 0;
+      } else {
+         return report_match_status();
+      }
    }
    master_socket = sock;
    set_sigill_handler(&master_sigill);
@@ -165,6 +201,7 @@ void usage (void)
    fprintf(stderr, "between master and apprentice risu processes.\n\n");
    fprintf(stderr, "Options:\n");
    fprintf(stderr, "  --master          Be the master (server)\n");
+   fprintf(stderr, "  -t, --trace=FILE  Record/playback trace file\n");
    fprintf(stderr, "  -h, --host=HOST   Specify master host machine (apprentice only)\n");
    fprintf(stderr, "  -p, --port=PORT   Specify the port to connect to/listen on (default 9191)\n");
 }
@@ -175,6 +212,7 @@ int main(int argc, char **argv)
    uint16_t port = 9191;
    char *hostname = "localhost";
    char *imgfile;
+   char *trace_fn = NULL;
    int sock;
 
    // TODO clean this up later
@@ -185,13 +223,14 @@ int main(int argc, char **argv)
          {
             { "help", no_argument, 0, '?'},
             { "master", no_argument, &ismaster, 1 },
+            { "trace", required_argument, 0, 't' },
             { "host", required_argument, 0, 'h' },
             { "port", required_argument, 0, 'p' },
             { "test-fp-exc", no_argument, &test_fp_exc, 1 },
             { 0,0,0,0 }
          };
       int optidx = 0;
-      int c = getopt_long(argc, argv, "h:p:", longopts, &optidx);
+      int c = getopt_long(argc, argv, "h:p:t:", longopts, &optidx);
       if (c == -1)
       {
          break;
@@ -204,6 +243,11 @@ int main(int argc, char **argv)
             /* flag set by getopt_long, do nothing */
             break;
          }
+         case 't':
+         {
+           trace_fn = optarg;
+           break;
+         }
          case 'h':
          {
             hostname = optarg;
@@ -234,18 +278,28 @@ int main(int argc, char **argv)
    }
 
    load_image(imgfile);
-   
+
    if (ismaster)
    {
-      fprintf(stderr, "master port %d\n", port);
-      sock = master_connect(port);
-      return master(sock);
+     if (trace_fn)
+       {
+         trace_file = open(trace_fn, O_WRONLY|O_CREAT, S_IRWXU);
+       } else {
+         fprintf(stderr, "master port %d\n", port);
+         sock = master_connect(port);
+       }
+     return master(sock);
    }
    else
-   {
-      fprintf(stderr, "apprentice host %s port %d\n", hostname, port);
-      sock = apprentice_connect(hostname, port);
-      return apprentice(sock);
+     {
+     if (trace_fn)
+       {
+         trace_file = open(trace_fn, O_RDONLY);
+       } else {
+         fprintf(stderr, "apprentice host %s port %d\n", hostname, port);
+         sock = apprentice_connect(hostname, port);
+       }
+     return apprentice(sock);
    }
 }
 
diff --git a/risu.h b/risu.h
index e4bb323..57161f2 100644
--- a/risu.h
+++ b/risu.h
@@ -52,6 +52,18 @@ int send_register_info(int sock, void *uc);
  */
 int recv_and_compare_register_info(int sock, void *uc);
 
+/* To keep the read/write logic from multiplying across all arches
+ * we wrap up the function here to keep all the changes in one place
+ */
+typedef int (*trace_write_fn) (void *ptr, size_t bytes);
+typedef int (*trace_read_fn) (void *ptr, size_t bytes);
+
+/* Write out to trace file */
+int write_to_tracefile(trace_write_fn write_fn, void *uc);
+
+/* Read from trace file and check */
+int read_tracefile_and_check(trace_read_fn read_fn, void * uc);
+
 /* Print a useful report on the status of the last comparison
  * done in recv_and_compare_register_info(). This is called on
  * exit, so need not restrict itself to signal-safe functions.
diff --git a/risu_aarch64.c b/risu_aarch64.c
index 1595604..9d538cb 100644
--- a/risu_aarch64.c
+++ b/risu_aarch64.c
@@ -13,6 +13,7 @@
 #include <stdio.h>
 #include <ucontext.h>
 #include <string.h>
+#include <unistd.h>
 
 #include "risu.h"
 #include "risu_reginfo_aarch64.h"
@@ -28,9 +29,7 @@ void advance_pc(void *vuc)
 {
     ucontext_t *uc = vuc;
     uc->uc_mcontext.pc += 4;
-    if (ismaster) {
-      report_test_status((void *) uc->uc_mcontext.pc);
-    }
+    report_test_status((void *) uc->uc_mcontext.pc);
 }
 
 static void set_x0(void *vuc, uint64_t x0)
@@ -135,6 +134,91 @@ int recv_and_compare_register_info(int sock, void *uc)
     return resp;
 }
 
+int write_to_tracefile(trace_write_fn write_fn, void *uc)
+{
+   int resp = 0, op;
+   trace_header_t header;
+
+   reginfo_init(&master_ri, uc);
+   op = get_risuop(master_ri.faulting_insn);
+   header.pc = master_ri.pc;
+   header.risu_op = op;
+   write_fn(&header, sizeof(header));
+
+   switch (op) {
+      case OP_TESTEND:
+         resp = 1;
+      case OP_COMPARE:
+      default:
+         write_fn(&master_ri, sizeof(master_ri));
+         break;
+      case OP_SETMEMBLOCK:
+         memblock = (void *)master_ri.regs[0];
+         break;
+      case OP_GETMEMBLOCK:
+         set_x0(uc, master_ri.regs[0] + (uintptr_t)memblock);
+         break;
+      case OP_COMPAREMEM:
+         write_fn(memblock, MEMBLOCKLEN);
+         break;
+   }
+
+   return resp;
+}
+
+
+int read_tracefile_and_check(trace_read_fn read_fn, void * uc)
+{
+   int resp = 0, op;
+   trace_header_t header;
+
+   reginfo_init(&master_ri, uc);
+   op = get_risuop(master_ri.faulting_insn);
+
+   read_fn(&header, sizeof(header));
+   if ( header.pc == master_ri.pc &&
+        header.risu_op == op )
+   {
+      switch (op) {
+         case OP_COMPARE:
+         case OP_TESTEND:
+         default:
+            if (!read_fn(&apprentice_ri, sizeof(apprentice_ri))) {
+               packet_mismatch = 1;
+               resp = 2;
+            } else if (!reginfo_is_eq(&master_ri, &apprentice_ri)) {
+               /* register mismatch */
+               resp = 2;
+            } else if (op == OP_TESTEND) {
+               resp = 1;
+            }
+            break;
+         case OP_SETMEMBLOCK:
+            memblock = (void *)master_ri.regs[0];
+            break;
+         case OP_GETMEMBLOCK:
+            set_x0(uc, master_ri.regs[0] + (uintptr_t)memblock);
+            break;
+         case OP_COMPAREMEM:
+            mem_used = 1;
+            if (!read_fn(apprentice_memblock, MEMBLOCKLEN)) {
+               packet_mismatch = 1;
+               resp = 2;
+            } else if (memcmp(memblock, apprentice_memblock, MEMBLOCKLEN) != 0) {
+               /* memory mismatch */
+               resp = 2;
+            }
+            break;
+      }
+   } else {
+      fprintf(stderr, "out of sync %lx/%lx %d/%d\n",
+              master_ri.pc, header.pc,
+              op, header.risu_op);
+      resp = 2;
+   }
+   return resp;
+}
+
 /* Print a useful report on the status of the last comparison
  * done in recv_and_compare_register_info(). This is called on
  * exit, so need not restrict itself to signal-safe functions.
diff --git a/risu_reginfo_aarch64.h b/risu_reginfo_aarch64.h
index 166b76c..db51cb2 100644
--- a/risu_reginfo_aarch64.h
+++ b/risu_reginfo_aarch64.h
@@ -28,6 +28,13 @@ struct reginfo
     __uint128_t vregs[32];
 };
 
+typedef struct
+{
+    uint64_t pc;
+    uint32_t risu_op;
+} trace_header_t;
+
+
 /* initialize structure from a ucontext */
 void reginfo_init(struct reginfo *ri, ucontext_t *uc);
 
