diff --git a/risu.c b/risu.c
index 88e586c..93c274b 100644
--- a/risu.c
+++ b/risu.c
@@ -30,7 +30,9 @@
 
 void *memblock;
 
-int apprentice_socket, master_socket;
+int apprentice_fd, master_fd;
+int trace;
+size_t signal_count;
 
 sigjmp_buf jmpbuf;
 
@@ -41,24 +43,60 @@ int test_fp_exc;
 
 int read_sock(void *ptr, size_t bytes)
 {
-    return recv_data_pkt(master_socket, ptr, bytes);
+    return recv_data_pkt(master_fd, ptr, bytes);
+}
+
+int write_trace(void *ptr, size_t bytes)
+{
+    size_t res = write(master_fd, ptr, bytes);
+    return (res == bytes) ? 0 : 1;
 }
 
 void respond_sock(int r)
 {
-    send_response_byte(master_socket, r);
+    send_response_byte(master_fd, r);
 }
 
 /* Apprentice function */
 
 int write_sock(void *ptr, size_t bytes)
 {
-    return send_data_pkt(apprentice_socket, ptr, bytes);
+    return send_data_pkt(apprentice_fd, ptr, bytes);
+}
+
+int read_trace(void *ptr, size_t bytes)
+{
+    size_t res = read(apprentice_fd, ptr, bytes);
+    return (res == bytes) ? 0 : 1;
+}
+
+void respond_trace(int r)
+{
+    switch (r) {
+    case 0: /* test ok */
+    case 1: /* end of test */
+        break;
+    default:
+        /* mismatch - if tracing we need to report, otherwise barf */
+        if (!trace) {
+           abort();
+        }
+        break;
+    }
 }
 
 void master_sigill(int sig, siginfo_t *si, void *uc)
 {
-    switch (recv_and_compare_register_info(read_sock, respond_sock, uc)) {
+    int r;
+    signal_count++;
+
+    if (trace) {
+        r = send_register_info(write_trace, uc);
+    } else {
+        r = recv_and_compare_register_info(read_sock, respond_sock, uc);
+    }
+
+    switch (r) {
     case 0:
         /* match OK */
         advance_pc(uc);
@@ -71,7 +109,16 @@ void master_sigill(int sig, siginfo_t *si, void *uc)
 
 void apprentice_sigill(int sig, siginfo_t *si, void *uc)
 {
-    switch (send_register_info(write_sock, uc)) {
+    int r;
+    signal_count++;
+
+    if (trace) {
+        r = recv_and_compare_register_info(read_trace, respond_trace, uc);
+    } else {
+        r = send_register_info(write_sock, uc);
+    }
+
+    switch (r) {
     case 0:
         /* match OK */
         advance_pc(uc);
@@ -81,6 +128,9 @@ void apprentice_sigill(int sig, siginfo_t *si, void *uc)
         exit(0);
     default:
         /* mismatch */
+        if (trace) {
+            siglongjmp(jmpbuf, 1);
+        }
         exit(1);
     }
 }
@@ -136,12 +186,17 @@ void load_image(const char *imgfile)
     image_start_address = (uintptr_t) addr;
 }
 
-int master(int sock)
+int master(void)
 {
     if (sigsetjmp(jmpbuf, 1)) {
-        return report_match_status();
+        close(master_fd);
+        if (trace) {
+            fprintf(stderr, "trace complete after %zd checkpoints\n", signal_count);
+            return 0;
+        } else {
+            return report_match_status();
+        }
     }
-    master_socket = sock;
     set_sigill_handler(&master_sigill);
     fprintf(stderr, "starting master image at 0x%"PRIxPTR"\n",
             image_start_address);
@@ -151,9 +206,13 @@ int master(int sock)
     exit(1);
 }
 
-int apprentice(int sock)
+int apprentice(void)
 {
-    apprentice_socket = sock;
+    if (sigsetjmp(jmpbuf, 1)) {
+        close(apprentice_fd);
+        fprintf(stderr, "finished early after %zd checkpoints\n", signal_count);
+        return report_match_status();
+    }
     set_sigill_handler(&apprentice_sigill);
     fprintf(stderr, "starting apprentice image at 0x%"PRIxPTR"\n",
             image_start_address);
@@ -175,6 +234,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");
@@ -189,7 +249,7 @@ int main(int argc, char **argv)
     uint16_t port = 9191;
     char *hostname = "localhost";
     char *imgfile;
-    int sock;
+    char *trace_fn = NULL;
 
     /* TODO clean this up later */
 
@@ -203,7 +263,7 @@ int main(int argc, char **argv)
             {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;
         }
@@ -214,6 +274,12 @@ int main(int argc, char **argv)
             /* flag set by getopt_long, do nothing */
             break;
         }
+        case 't':
+        {
+            trace_fn = optarg;
+            trace = 1;
+            break;
+        }
         case 'h':
         {
             hostname = optarg;
@@ -245,12 +311,20 @@ 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) {
+            master_fd = open(trace_fn, O_WRONLY|O_CREAT, S_IRWXU);
+        } else {
+            fprintf(stderr, "master port %d\n", port);
+            master_fd = master_connect(port);
+        }
+        return master();
     } else {
-        fprintf(stderr, "apprentice host %s port %d\n", hostname, port);
-        sock = apprentice_connect(hostname, port);
-        return apprentice(sock);
+        if (trace) {
+            apprentice_fd = open(trace_fn, O_RDONLY);
+        } else {
+            fprintf(stderr, "apprentice host %s port %d\n", hostname, port);
+            apprentice_fd = apprentice_connect(hostname, port);
+        }
+        return apprentice();
     }
 }
