[fidlcat] decode traces

With the option --from=trace, fidlcat is able to read a program trace
and replace all the syscall dumps by their decoded version.

For example:
Attempting to connect to the daemon. This may take a couple seconds...
syscall 0x7ffc90bd25b8 channel_create 1623951365995328698 2821426 2821426 1 2 0
syscall 0x7ffc90bd2bb0 startup 2821426 2821426 Channel(1) dir /svc
syscall 0x7ffc90bd3bd8 channel_create 1623951365995367083 2821426 2821426 9 a 0
syscall 0x55fdf4fdb750 channel_write_etc 1623951365995377909 2821426 2821426 1 24 1
syscall 0x55fdf4fdb750 write_bytes 00 00 00 00 00 00 00 01 ab 89 f9 f2 d9 06 68 55 ff ff ff ff 00 00 00 00
syscall 0x55fdf4fdb750 write_etc_handle 0 0000000a 0000000e 4 0
syscall 0x55fdf4fdb750 write_status 1623951365995394610 0

Will be replaced by:
Attempting to connect to the daemon. This may take a couple seconds...

1623951365.995329 FfxDoctor 2821426:2821426 zx_channel_create()
1623951365.995329   -> ZX_OK (out0: handle = Channel:00000001, out1: handle = Channel:00000002)

1623951365.995367 FfxDoctor 2821426:2821426 zx_channel_create()
1623951365.995367   -> ZX_OK (out0: handle = Channel:00000009, out1: handle = Channel:0000000a)

1623951365.995378 FfxDoctor 2821426:2821426 zx_channel_write_etc(
    handle: handle = Channel:00000001(dir:/svc), options: uint32 = 0)
  sent request fuchsia.overnet/HostOvernet.ConnectServiceConsumer = {
    svc: handle = Move(Channel:0000000a, ZX_RIGHT_TRANSFER | ZX_RIGHT_READ | ZX_RIGHT_WRITE)
  }
1623951365.995394   -> ZX_OK

Change-Id: Ie725722998e15f15b5a744d84b5b95b7161c7f98
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/542391
Reviewed-by: Jeremy Manson <jeremymanson@google.com>
Reviewed-by: Michael Jones <michaelrj@google.com>
Commit-Queue: Vincent Belliard <vbelliard@google.com>
diff --git a/src/developer/ffx/plugins/debug/src/args.rs b/src/developer/ffx/plugins/debug/src/args.rs
index 0392c2c..1f54554 100644
--- a/src/developer/ffx/plugins/debug/src/args.rs
+++ b/src/developer/ffx/plugins/debug/src/args.rs
@@ -39,6 +39,10 @@
     /// several processes. At least one of '--remote-pid', '--remote-name', '--remote-job-id',
     /// --'remote-job-name', 'run' must be specified.
     ///
+    /// dump: The input comes from stdin which is the log output of one or several programs. The
+    /// lines in the log which dump syscalls are decoded and replaced by the decoded version.
+    /// All other lines are unchanged.
+    ///
     /// <path>: playback. Used to replay a session previously recorded with --to <path>
     /// (protobuf format). Path gives the name of the file to read. If path is '-' then the standard
     /// input is used.
diff --git a/src/lib/fidl_codec/message_decoder.cc b/src/lib/fidl_codec/message_decoder.cc
index d4423c9..798b507 100644
--- a/src/lib/fidl_codec/message_decoder.cc
+++ b/src/lib/fidl_codec/message_decoder.cc
@@ -92,10 +92,14 @@
     case SyscallFidlType::kOutputRequest:
       is_request_ = true;
       received_ = false;
-      break;
+      direction_ = Direction::kClient;
+      dispatcher->UpdateDirection(process_koid, handle, direction_);
+      return true;
     case SyscallFidlType::kInputResponse:
       received_ = true;
-      break;
+      direction_ = Direction::kClient;
+      dispatcher->UpdateDirection(process_koid, handle, direction_);
+      return true;
   }
   if (direction_ != Direction::kUnknown) {
     if ((is_request_ && !matched_request_) || (!is_request_ && !matched_response_)) {
diff --git a/tools/devshell/contrib/fidlcat b/tools/devshell/contrib/fidlcat
index 508d01a..930e871 100755
--- a/tools/devshell/contrib/fidlcat
+++ b/tools/devshell/contrib/fidlcat
@@ -55,6 +55,10 @@
 ##                                     At least one of '--remote-pid', '--remote-name',
 ##                                     '--remote-job-id', --'remote-job-name', 'run' must be
 ##                                     specified.
+##                       --from=dump   The input comes from stdin which is the log output of one
+##                                     or several programs. The lines in the log which dump
+##                                     syscalls are decoded and replaced by the decoded version.
+##                                     All other lines are unchanged.
 ##                       --from=<path> Playback. Used to replay a session previously recorded with
 ##                                     --to=<path> (protobuf format). Path gives the name of the
 ##                                     file to read. If path is '-' then the standard input is used.
@@ -254,6 +258,9 @@
       use_gdb=1
   elif [[ $1 == "--from=device" ]]; then
       arguments+=("$1")
+  elif [[ $1 == "--from=dump" ]]; then
+      arguments+=("$1")
+    launch_agent=0
   elif equal_argument "$1" "--from" "<source>"; then
     arguments+=("$1")
     launch_agent=0
diff --git a/tools/fidlcat/command_line_options.cc b/tools/fidlcat/command_line_options.cc
index b5f352e..8af6c69 100644
--- a/tools/fidlcat/command_line_options.cc
+++ b/tools/fidlcat/command_line_options.cc
@@ -106,6 +106,10 @@
                     several processes.
                     At least one of '--remote-pid', '--remote-name', '--remote-job-id',
                     '--remote-job-name, 'run' must be specified.
+      --from=dump   The input comes from stdin which is the log output of one or several programs.
+                    The lines in the log which dump syscalls are decoded and replaced by the
+                    decoded version.
+                    All other lines are unchanged.
       --from=<path> The input comes from a previously recorded session (protobuf format). Path gives
                     the name of the file to read. If path is '-' then the standard input is used.)";
 
@@ -487,7 +491,9 @@
 
   display_options->dump_messages = options->dump_messages;
 
-  if (!options->from.empty() && (options->from != "device")) {
+  if (!options->from.empty() && (options->from == "dump")) {
+    decode_options->input_mode = InputMode::kDump;
+  } else if (!options->from.empty() && (options->from != "device")) {
     decode_options->input_mode = InputMode::kFile;
   } else {
     if (options->connect && options->unix_connect) {
diff --git a/tools/fidlcat/interception_tests/BUILD.gn b/tools/fidlcat/interception_tests/BUILD.gn
index bda798ca..334d455 100644
--- a/tools/fidlcat/interception_tests/BUILD.gn
+++ b/tools/fidlcat/interception_tests/BUILD.gn
@@ -49,6 +49,7 @@
       "thread_test.cc",
       "ticks_test.cc",
       "timer_test.cc",
+      "trace_decode_test.cc",
       "vcpu_test.cc",
       "vmar_test.cc",
       "vmo_test.cc",
diff --git a/tools/fidlcat/interception_tests/trace_decode_test.cc b/tools/fidlcat/interception_tests/trace_decode_test.cc
new file mode 100644
index 0000000..6a56d28
--- /dev/null
+++ b/tools/fidlcat/interception_tests/trace_decode_test.cc
@@ -0,0 +1,299 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+
+#include "tools/fidlcat/interception_tests/interception_workflow_test.h"
+
+namespace fidlcat {
+
+TEST(DispatcherTest, TraceDecode) {
+  std::string trace =
+      ""
+      "syscall 0x7ffd6863e9c0 process 2916209 FfxDoctor\n"
+      "\n"
+      "Welcome to ffx doctor.\n"
+      "- Frontend version: 2021-06-10T15:35:44+00:00\n"
+      "- Default target: fuchsia-5254-0063-5e7a\n"
+      "\n"
+      "First, bringing up a working daemon...\n"
+      "Checking for a running daemon...found\n"
+      "Attempting to connect to the daemon. This may take a couple seconds..."
+      "syscall 0x7ffd686369b0 channel_create 10000 2916209 2916209 1 2 0\n"
+      "syscall 0x7ffd68636f90 startup 2916209 2916209 Channel(1) dir /svc\n"
+      "syscall 0x7ffd68637fd0 channel_create 20000 2916209 2916209 9 a 0\n"
+      "syscall 0x559138249750 channel_write_etc 30000 2916209 2916209 1 24 1\n"
+      "syscall 0x559138249750 write_bytes 00 00 00 00 00 00 00 01 ab 89 f9 f2 d9 06 68 55 "
+      "ff ff ff ff 00 00 00 00 \n"
+      "syscall 0x559138249750 write_etc_handle 0 0000000a 0000000e 4 0\n"
+      "syscall 0x559138249750 write_status 40000 0\n"
+      "syscall 0x559138249750 channel_write_etc 50000 2916209 2916209 9 16 0\n"
+      "syscall 0x559138249750 write_bytes 01 00 00 00 00 00 00 01 e0 80 c0 4d ef a5 af 72 \n"
+      "syscall 0x559138249750 write_status 60000 0\n"
+      "syscall 0x7ffd68641340 channel_create 70000 2916209 2916209 11 12 0\n"
+      "syscall 0x7f973314c240 channel_read 80000 2916209 2916209 2 0 24 1\n"
+      "syscall 0x7f973314c240 read_bytes 00 00 00 00 00 00 00 01 ab 89 f9 f2 d9 06 68 55 "
+      "ff ff ff ff 00 00 00 00 \n"
+      "syscall 0x7f973314c240 read_handles 0000000a \n"
+      "syscall 0x7f973314c240 channel_read 90000 2916209 2916209 a 0 16 0\n"
+      "syscall 0x7f973314c240 read_bytes 01 00 00 00 00 00 00 01 e0 80 c0 4d ef a5 af 72 \n"
+      "syscall 0x5591382ba060 channel_write_etc 100000 2916209 2916209 a 96 0\n"
+      "syscall 0x5591382ba060 write_bytes 01 00 00 00 00 00 00 01 e0 80 c0 4d ef a5 af 72 "
+      "01 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x5591382ba060 write_bytes fb 77 62 62 5e 68 bc c6 01 00 00 00 00 00 00 00 "
+      "01 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x5591382ba060 write_bytes 10 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff "
+      "00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x5591382ba060 write_status 110000 0\n"
+      "syscall 0x7ffd686381a0 channel_read 120000 2916209 2916209 9 0 96 0\n"
+      "syscall 0x7ffd686381a0 read_bytes 01 00 00 00 00 00 00 01 e0 80 c0 4d ef a5 af 72 "
+      "01 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x7ffd686381a0 read_bytes fb 77 62 62 5e 68 bc c6 01 00 00 00 00 00 00 00 "
+      "01 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x7ffd686381a0 read_bytes 10 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff "
+      "00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x5591382ba060 channel_write_etc 130000 2916209 2916209 9 16 0\n"
+      "syscall 0x5591382ba060 write_bytes 01 00 00 00 00 00 00 01 e0 80 c0 4d ef a5 af 72 \n"
+      "syscall 0x5591382ba060 write_status 140000 0\n"
+      "syscall 0x7f973314c240 channel_read 150000 2916209 2916209 a 0 16 0\n"
+      "syscall 0x7f973314c240 read_bytes 01 00 00 00 00 00 00 01 e0 80 c0 4d ef a5 af 72 \n"
+      "syscall 0x5591382da4c0 channel_write_etc 160000 2916209 2916209 a 264 0\n"
+      "syscall 0x5591382da4c0 write_bytes 01 00 00 00 00 00 00 01 e0 80 c0 4d ef a5 af 72 "
+      "02 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x5591382da4c0 write_bytes fb 77 62 62 5e 68 bc c6 01 00 00 00 00 00 00 00 "
+      "01 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x5591382da4c0 write_bytes ff 78 3b 5e 07 65 a4 8f 00 00 00 00 00 00 00 00 "
+      "01 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x5591382da4c0 write_bytes 10 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff "
+      "00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x5591382da4c0 write_bytes 78 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff "
+      "02 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x5591382da4c0 write_bytes 1f 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff "
+      "23 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x5591382da4c0 write_bytes 66 75 63 68 73 69 61 2e 64 65 76 65 6c 6f 70 65 "
+      "72 2e 62 72 69 64 67 65 2e 44 61 65 6d 6f 6e 00 \n"
+      "syscall 0x5591382da4c0 write_bytes 66 75 63 68 73 69 61 2e 6f 76 65 72 6e 65 74 2e "
+      "70 72 6f 74 6f 63 6f 6c 2e 44 69 61 67 6e 6f 73 \n"
+      "syscall 0x5591382da4c0 write_bytes 74 69 63 00 00 00 00 00 \n"
+      "syscall 0x5591382da4c0 write_status 170000 0\n"
+      "syscall 0x7ffd686381a0 channel_read 180000 2916209 2916209 9 0 264 0\n"
+      "syscall 0x7ffd686381a0 read_bytes 01 00 00 00 00 00 00 01 e0 80 c0 4d ef a5 af 72 "
+      "02 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x7ffd686381a0 read_bytes fb 77 62 62 5e 68 bc c6 01 00 00 00 00 00 00 00 "
+      "01 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x7ffd686381a0 read_bytes ff 78 3b 5e 07 65 a4 8f 00 00 00 00 00 00 00 00 "
+      "01 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x7ffd686381a0 read_bytes 10 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff "
+      "00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x7ffd686381a0 read_bytes 78 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff "
+      "02 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x7ffd686381a0 read_bytes 1f 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff "
+      "23 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \n"
+      "syscall 0x7ffd686381a0 read_bytes 66 75 63 68 73 69 61 2e 64 65 76 65 6c 6f 70 65 "
+      "72 2e 62 72 69 64 67 65 2e 44 61 65 6d 6f 6e 00 \n"
+      "syscall 0x7ffd686381a0 read_bytes 66 75 63 68 73 69 61 2e 6f 76 65 72 6e 65 74 2e "
+      "70 72 6f 74 6f 63 6f 6c 2e 44 69 61 67 6e 6f 73 \n"
+      "syscall 0x7ffd686381a0 read_bytes 74 69 63 00 00 00 00 00 \n"
+      "syscall 0x7ffd68637f60 channel_create 190000 2916209 2916209 19 1a 0\n"
+      "syscall 0x5591382da4c0 channel_write_etc 200000 2916209 2916209 1 24 1\n"
+      "syscall 0x5591382da4c0 write_bytes 00 00 00 00 00 00 00 01 ab 89 f9 f2 d9 06 68 55 "
+      "ff ff ff ff 00 00 00 00 \n"
+      "syscall 0x5591382da4c0 write_etc_handle 0 0000001a 0000000e 4 0\n"
+      "syscall 0x5591382da4c0 write_status 210000 0\n"
+      "syscall 0x7ffd686380c0 channel_create 220000 2916209 2916209 21 22 0\n"
+      "syscall 0x5591382da4c0 channel_write_etc 230000 2916209 2916209 19 80 1\n"
+      "syscall 0x5591382da4c0 write_bytes 00 00 00 00 00 00 00 01 4e 42 1c 4c f0 a2 3b 22 "
+      "ff 78 3b 5e 07 65 a4 8f 1f 00 00 00 00 00 00 00 \n"
+      "syscall 0x5591382da4c0 write_bytes ff ff ff ff ff ff ff ff ff ff ff ff 00 00 00 00 "
+      "66 75 63 68 73 69 61 2e 64 65 76 65 6c 6f 70 65 \n"
+      "syscall 0x5591382da4c0 write_bytes 72 2e 62 72 69 64 67 65 2e 44 61 65 6d 6f 6e 00 \n"
+      "syscall 0x5591382da4c0 write_etc_handle 0 00000021 80000000 4 0\n"
+      "syscall 0x5591382da4c0 write_status 240000 0\n"
+      "success\n";
+
+  DecodeOptions decode_options;
+  decode_options.output_mode = OutputMode::kStandard;
+  DisplayOptions display_options;
+  std::stringstream os;
+  SyscallDisplayDispatcher dispatcher(nullptr, decode_options, display_options, os);
+  Replay replay(&dispatcher);
+  std::stringstream is(trace);
+  replay.DecodeTrace(is);
+  replay.dispatcher()->SessionEnded();
+
+  std::string result = os.str();
+
+  std::string expected =
+      ""
+      "\n"
+      "Welcome to ffx doctor.\n"
+      "- Frontend version: 2021-06-10T15:35:44+00:00\n"
+      "- Default target: fuchsia-5254-0063-5e7a\n"
+      "\n"
+      "First, bringing up a working daemon...\n"
+      "Checking for a running daemon...found\n"
+      "Attempting to connect to the daemon. This may take a couple seconds...\n"
+      "\n"
+      "0.000010 FfxDoctor 2916209:2916209 zx_channel_create()\n"
+      "0.000010   -> ZX_OK (out0: handle = Channel:00000001, out1: handle = Channel:00000002)\n"
+      "\n"
+      "0.000020 FfxDoctor 2916209:2916209 zx_channel_create()\n"
+      "0.000020   -> ZX_OK (out0: handle = Channel:00000009, out1: handle = Channel:0000000a)\n"
+      "\n"
+      "0.000030 FfxDoctor 2916209:2916209 zx_channel_write_etc("
+      "handle: handle = Channel:00000001(dir:/svc), options: uint32 = 0)\n"
+      "  Can't decode message: num_bytes=24 num_handles=1 ordinal=556806d9f2f989ab\n"
+      "    data=\n"
+      "      0000: 00, 00, 00, 00, 00, 00, 00, 01, ab, 89, f9, f2, d9, 06, 68, 55, \n"
+      "      0010: ff, ff, ff, ff, 00, 00, 00, 00\n"
+      "    handles=\n"
+      "      0000: 0000000a\n"
+      "0.000040   -> ZX_OK\n"
+      "\n"
+      "0.000050 FfxDoctor 2916209:2916209 zx_channel_write_etc("
+      "handle: handle = Channel:00000009, options: uint32 = 0)\n"
+      "  Can't decode message: num_bytes=16 num_handles=0 txid=1 ordinal=72afa5ef4dc080e0\n"
+      "    data=\n"
+      "      0000: 01, 00, 00, 00, 00, 00, 00, 01, e0, 80, c0, 4d, ef, a5, af, 72\n"
+      "0.000060   -> ZX_OK\n"
+      "\n"
+      "0.000070 FfxDoctor 2916209:2916209 zx_channel_create()\n"
+      "0.000070   -> ZX_OK (out0: handle = Channel:00000011, out1: handle = Channel:00000012)\n"
+      "\n"
+      "0.000080 FfxDoctor 2916209:2916209 zx_channel_read("
+      "handle: handle = Channel:00000002, options: uint32 = 0)\n"
+      "0.000080   -> ZX_OK\n"
+      "    Can't decode message: num_bytes=24 num_handles=1 ordinal=556806d9f2f989ab\n"
+      "      data=\n"
+      "        0000: 00, 00, 00, 00, 00, 00, 00, 01, ab, 89, f9, f2, d9, 06, 68, 55, \n"
+      "        0010: ff, ff, ff, ff, 00, 00, 00, 00\n"
+      "      handles=\n"
+      "        0000: 0000000a\n"
+      "\n"
+      "0.000090 FfxDoctor 2916209:2916209 zx_channel_read("
+      "handle: handle = Channel:0000000a, options: uint32 = 0)\n"
+      "0.000090   -> ZX_OK\n"
+      "    Can't decode message: num_bytes=16 num_handles=0 txid=1 ordinal=72afa5ef4dc080e0\n"
+      "      data=\n"
+      "        0000: 01, 00, 00, 00, 00, 00, 00, 01, e0, 80, c0, 4d, ef, a5, af, 72\n"
+      "\n"
+      "0.000100 FfxDoctor 2916209:2916209 zx_channel_write_etc("
+      "handle: handle = Channel:0000000a, options: uint32 = 0)\n"
+      "  Can't decode message: num_bytes=96 num_handles=0 txid=1 ordinal=72afa5ef4dc080e0\n"
+      "    data=\n"
+      "      0000: 01, 00, 00, 00, 00, 00, 00, 01, e0, 80, c0, 4d, ef, a5, af, 72, \n"
+      "      0010: 01, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      0020: fb, 77, 62, 62, 5e, 68, bc, c6, 01, 00, 00, 00, 00, 00, 00, 00, \n"
+      "      0030: 01, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      0040: 10, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      0050: 00, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff\n"
+      "0.000110   -> ZX_OK\n"
+      "\n"
+      "0.000120 FfxDoctor 2916209:2916209 zx_channel_read("
+      "handle: handle = Channel:00000009, options: uint32 = 0)\n"
+      "0.000120   -> ZX_OK\n"
+      "    Can't decode message: num_bytes=96 num_handles=0 txid=1 ordinal=72afa5ef4dc080e0\n"
+      "      data=\n"
+      "        0000: 01, 00, 00, 00, 00, 00, 00, 01, e0, 80, c0, 4d, ef, a5, af, 72, \n"
+      "        0010: 01, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        0020: fb, 77, 62, 62, 5e, 68, bc, c6, 01, 00, 00, 00, 00, 00, 00, 00, \n"
+      "        0030: 01, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        0040: 10, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        0050: 00, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff\n"
+      "\n"
+      "0.000130 FfxDoctor 2916209:2916209 zx_channel_write_etc("
+      "handle: handle = Channel:00000009, options: uint32 = 0)\n"
+      "  Can't decode message: num_bytes=16 num_handles=0 txid=1 ordinal=72afa5ef4dc080e0\n"
+      "    data=\n"
+      "      0000: 01, 00, 00, 00, 00, 00, 00, 01, e0, 80, c0, 4d, ef, a5, af, 72\n"
+      "0.000140   -> ZX_OK\n"
+      "\n"
+      "0.000150 FfxDoctor 2916209:2916209 zx_channel_read("
+      "handle: handle = Channel:0000000a, options: uint32 = 0)\n"
+      "0.000150   -> ZX_OK\n"
+      "    Can't decode message: num_bytes=16 num_handles=0 txid=1 ordinal=72afa5ef4dc080e0\n"
+      "      data=\n"
+      "        0000: 01, 00, 00, 00, 00, 00, 00, 01, e0, 80, c0, 4d, ef, a5, af, 72\n"
+      "\n"
+      "0.000160 FfxDoctor 2916209:2916209 zx_channel_write_etc("
+      "handle: handle = Channel:0000000a, options: uint32 = 0)\n"
+      "  Can't decode message: num_bytes=264 num_handles=0 txid=1 ordinal=72afa5ef4dc080e0\n"
+      "    data=\n"
+      "      0000: 01, 00, 00, 00, 00, 00, 00, 01, e0, 80, c0, 4d, ef, a5, af, 72, \n"
+      "      0010: 02, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      0020: fb, 77, 62, 62, 5e, 68, bc, c6, 01, 00, 00, 00, 00, 00, 00, 00, \n"
+      "      0030: 01, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      0040: ff, 78, 3b, 5e, 07, 65, a4, 8f, 00, 00, 00, 00, 00, 00, 00, 00, \n"
+      "      0050: 01, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      0060: 10, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      0070: 00, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      0080: 78, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      0090: 02, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      00a0: 1f, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      00b0: 23, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "      00c0: 66, 75, 63, 68, 73, 69, 61, 2e, 64, 65, 76, 65, 6c, 6f, 70, 65, \n"
+      "      00d0: 72, 2e, 62, 72, 69, 64, 67, 65, 2e, 44, 61, 65, 6d, 6f, 6e, 00, \n"
+      "      00e0: 66, 75, 63, 68, 73, 69, 61, 2e, 6f, 76, 65, 72, 6e, 65, 74, 2e, \n"
+      "      00f0: 70, 72, 6f, 74, 6f, 63, 6f, 6c, 2e, 44, 69, 61, 67, 6e, 6f, 73, \n"
+      "      0100: 74, 69, 63, 00, 00, 00, 00, 00\n"
+      "0.000170   -> ZX_OK\n"
+      "\n"
+      "0.000180 FfxDoctor 2916209:2916209 zx_channel_read("
+      "handle: handle = Channel:00000009, options: uint32 = 0)\n"
+      "0.000180   -> ZX_OK\n"
+      "    Can't decode message: num_bytes=264 num_handles=0 txid=1 ordinal=72afa5ef4dc080e0\n"
+      "      data=\n"
+      "        0000: 01, 00, 00, 00, 00, 00, 00, 01, e0, 80, c0, 4d, ef, a5, af, 72, \n"
+      "        0010: 02, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        0020: fb, 77, 62, 62, 5e, 68, bc, c6, 01, 00, 00, 00, 00, 00, 00, 00, \n"
+      "        0030: 01, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        0040: ff, 78, 3b, 5e, 07, 65, a4, 8f, 00, 00, 00, 00, 00, 00, 00, 00, \n"
+      "        0050: 01, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        0060: 10, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        0070: 00, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        0080: 78, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        0090: 02, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        00a0: 1f, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        00b0: 23, 00, 00, 00, 00, 00, 00, 00, ff, ff, ff, ff, ff, ff, ff, ff, \n"
+      "        00c0: 66, 75, 63, 68, 73, 69, 61, 2e, 64, 65, 76, 65, 6c, 6f, 70, 65, \n"
+      "        00d0: 72, 2e, 62, 72, 69, 64, 67, 65, 2e, 44, 61, 65, 6d, 6f, 6e, 00, \n"
+      "        00e0: 66, 75, 63, 68, 73, 69, 61, 2e, 6f, 76, 65, 72, 6e, 65, 74, 2e, \n"
+      "        00f0: 70, 72, 6f, 74, 6f, 63, 6f, 6c, 2e, 44, 69, 61, 67, 6e, 6f, 73, \n"
+      "        0100: 74, 69, 63, 00, 00, 00, 00, 00\n"
+      "\n"
+      "0.000190 FfxDoctor 2916209:2916209 zx_channel_create()\n"
+      "0.000190   -> ZX_OK (out0: handle = Channel:00000019, out1: handle = Channel:0000001a)\n"
+      "\n"
+      "0.000200 FfxDoctor 2916209:2916209 zx_channel_write_etc("
+      "handle: handle = Channel:00000001(dir:/svc), options: uint32 = 0)\n"
+      "  Can't decode message: num_bytes=24 num_handles=1 ordinal=556806d9f2f989ab\n"
+      "    data=\n"
+      "      0000: 00, 00, 00, 00, 00, 00, 00, 01, ab, 89, f9, f2, d9, 06, 68, 55, \n"
+      "      0010: ff, ff, ff, ff, 00, 00, 00, 00\n"
+      "    handles=\n"
+      "      0000: 0000001a\n"
+      "0.000210   -> ZX_OK\n"
+      "\n"
+      "0.000220 FfxDoctor 2916209:2916209 zx_channel_create()\n"
+      "0.000220   -> ZX_OK (out0: handle = Channel:00000021, out1: handle = Channel:00000022)\n"
+      "\n"
+      "0.000230 FfxDoctor 2916209:2916209 zx_channel_write_etc("
+      "handle: handle = Channel:00000019, options: uint32 = 0)\n"
+      "  Can't decode message: num_bytes=80 num_handles=1 ordinal=223ba2f04c1c424e\n"
+      "    data=\n"
+      "      0000: 00, 00, 00, 00, 00, 00, 00, 01, 4e, 42, 1c, 4c, f0, a2, 3b, 22, \n"
+      "      0010: ff, 78, 3b, 5e, 07, 65, a4, 8f, 1f, 00, 00, 00, 00, 00, 00, 00, \n"
+      "      0020: ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, 00, 00, 00, 00, \n"
+      "      0030: 66, 75, 63, 68, 73, 69, 61, 2e, 64, 65, 76, 65, 6c, 6f, 70, 65, \n"
+      "      0040: 72, 2e, 62, 72, 69, 64, 67, 65, 2e, 44, 61, 65, 6d, 6f, 6e, 00\n"
+      "    handles=\n"
+      "      0000: 00000021\n"
+      "0.000240   -> ZX_OK\n"
+      "success\n"
+      "\n";
+
+  EXPECT_EQ(result, expected);
+}
+
+}  // namespace fidlcat
diff --git a/tools/fidlcat/lib/decode_options.h b/tools/fidlcat/lib/decode_options.h
index 40eb0dd..7d4aa86f 100644
--- a/tools/fidlcat/lib/decode_options.h
+++ b/tools/fidlcat/lib/decode_options.h
@@ -15,7 +15,7 @@
 #include "src/lib/fxl/macros.h"
 
 enum StackLevel { kNoStack = 0, kPartialStack = 1, kFullStack = 2 };
-enum class InputMode { kDevice, kFile };
+enum class InputMode { kDevice, kFile, kDump };
 enum class OutputMode { kNone, kStandard, kTextProtobuf };
 
 class Regex {
diff --git a/tools/fidlcat/lib/event.h b/tools/fidlcat/lib/event.h
index f9e0720..944d3f79 100644
--- a/tools/fidlcat/lib/event.h
+++ b/tools/fidlcat/lib/event.h
@@ -59,6 +59,7 @@
   uint32_t handle() const { return handle_; }
   int64_t creation_time() const { return creation_time_; }
   bool startup() const { return startup_; }
+  void set_startup() { startup_ = true; }
   zx_obj_type_t object_type() const { return object_type_; }
   void set_object_type(zx_obj_type_t object_type) { object_type_ = object_type; }
   zx_rights_t rights() const { return rights_; }
@@ -103,7 +104,7 @@
   Thread* const thread_;
   const uint32_t handle_;
   const int64_t creation_time_;
-  const bool startup_;
+  bool startup_;
   // The object type for the handle.
   zx_obj_type_t object_type_ = ZX_OBJ_TYPE_NONE;
   // The rights for the handle.
diff --git a/tools/fidlcat/lib/replay.cc b/tools/fidlcat/lib/replay.cc
index 0abc04e..ae8cb49 100644
--- a/tools/fidlcat/lib/replay.cc
+++ b/tools/fidlcat/lib/replay.cc
@@ -6,6 +6,7 @@
 
 #include <fstream>
 #include <memory>
+#include <sstream>
 #include <string>
 #include <vector>
 
@@ -17,6 +18,181 @@
 
 namespace fidlcat {
 
+std::shared_ptr<InvokedEvent> CreateInvoked(SyscallDisplayDispatcher* dispatcher,
+                                            uint64_t timestamp, uint64_t process_id,
+                                            uint64_t thread_id, Syscall* syscall) {
+  return std::make_shared<InvokedEvent>(
+      timestamp, dispatcher->CreateThread("foo", process_id, thread_id, nullptr), syscall);
+}
+
+Syscall* ReplayBuffer::GetSyscall(SyscallDisplayDispatcher* dispatcher) const {
+  switch (kind_) {
+    case Kind::kRead:
+      return dispatcher->SearchSyscall(etc_ ? "zx_channel_read_etc" : "zx_channel_read");
+    case Kind::kWrite:
+      return dispatcher->SearchSyscall(etc_ ? "zx_channel_write_etc" : "zx_channel_write");
+    case Kind::kCall:
+      return dispatcher->SearchSyscall(etc_ ? "zx_channel_call_etc" : "zx_channel_call");
+  }
+}
+
+void ReplayBuffer::AddWriteBytes(std::istream& stream) {
+  // Bytes are specified in hexadecimal (without any leading 0x).
+  // Up to 32 bytes can be specified on a line.
+  stream >> std::hex;
+  size_t count = write_byte_count_ - write_bytes_.size();
+  // The dump must have 32 bytes (unless there are less bytes remaining).
+  if (count > 32) {
+    count = 32;
+  }
+  while (count > 0) {
+    uint32_t data;
+    stream >> data;
+    write_bytes_.emplace_back(data);
+    --count;
+  }
+  stream >> std::dec;
+}
+
+void ReplayBuffer::AddWriteHandles(std::istream& stream) {
+  // Handles are specified in hexadecimal (without any leading 0x).
+  // Up to 8 handles can be specified on a line.
+  stream >> std::hex;
+  size_t count = write_handle_count_ - write_handles_.size();
+  // The dump must have 8 handles (unless there are less handles remaining).
+  if (count > 8) {
+    count = 8;
+  }
+  zx_handle_disposition_t handle = {.operation = fidl_codec::kNoHandleDisposition,
+                                    .handle = 0,
+                                    .type = ZX_OBJ_TYPE_NONE,
+                                    .rights = 0,
+                                    .result = ZX_OK};
+  while (count > 0) {
+    stream >> handle.handle;
+    write_handles_.emplace_back(handle);
+    --count;
+  }
+  stream >> std::dec;
+}
+
+void ReplayBuffer::AddWriteEtcHandle(std::istream& stream) {
+  // Only one handle disposition is specified per line. The fields are:
+  // - operation (0 or 1).
+  // - handle (in hexdecimal wiout any leading 0x).
+  // - rights (in hexdecimal wiout any leading 0x).
+  // - type (in decimal).
+  zx_handle_disposition_t handle = {.operation = fidl_codec::kNoHandleDisposition,
+                                    .handle = 0,
+                                    .type = ZX_OBJ_TYPE_NONE,
+                                    .rights = 0,
+                                    .result = ZX_OK};
+  stream >> handle.operation >> std::hex >> handle.handle >> handle.rights >> std::dec >>
+      handle.type >> handle.result;
+  write_handles_.emplace_back(handle);
+}
+
+void ReplayBuffer::AddReadBytes(std::istream& stream) {
+  // Bytes are specified in hexadecimal (without any leading 0x).
+  // Up to 32 bytes can be specified on a line.
+  stream >> std::hex;
+  size_t count = read_byte_count_ - read_bytes_.size();
+  // The dump must have 32 bytes (unless there are less bytes remaining).
+  if (count > 32) {
+    count = 32;
+  }
+  while (count > 0) {
+    uint32_t data;
+    stream >> data;
+    read_bytes_.emplace_back(data);
+    --count;
+  }
+  stream >> std::dec;
+}
+
+void ReplayBuffer::AddReadHandles(std::istream& stream) {
+  // Handles are specified in hexadecimal (without any leading 0x).
+  // Up to 8 handles can be specified on a line.
+  stream >> std::hex;
+  size_t count = read_handle_count_ - read_handles_.size();
+  // The dump must have 8 handles (unless there are less handles remaining).
+  if (count > 8) {
+    count = 8;
+  }
+  zx_handle_disposition_t handle = {.operation = fidl_codec::kNoHandleDisposition,
+                                    .handle = 0,
+                                    .type = ZX_OBJ_TYPE_NONE,
+                                    .rights = 0,
+                                    .result = ZX_OK};
+  while (count > 0) {
+    stream >> handle.handle;
+    read_handles_.emplace_back(handle);
+    --count;
+  }
+  stream >> std::dec;
+}
+
+void ReplayBuffer::Dispatch(SyscallDisplayDispatcher* dispatcher) {
+  // Gets the definition of the syscall (from kind_ and etc_).
+  Syscall* syscall = GetSyscall(dispatcher);
+
+  // Creates the invoked event.
+  std::shared_ptr<InvokedEvent> invoked_event =
+      CreateInvoked(dispatcher, invoked_timestamp_, process_id_, thread_id_, syscall);
+
+  // Sets the inline fields shared by all the channel syscalls.
+  zx_handle_disposition_t handle;
+  handle.operation = fidl_codec::kNoHandleDisposition;
+  handle.handle = channel_;
+  handle.rights = 0;
+  handle.type = ZX_OBJ_TYPE_NONE;
+  handle.result = ZX_OK;
+  invoked_event->AddInlineField(syscall->SearchInlineMember("handle", /*invoked=*/true),
+                                std::make_unique<fidl_codec::HandleValue>(handle));
+  invoked_event->AddInlineField(
+      syscall->SearchInlineMember("options", /*invoked=*/true),
+      std::make_unique<fidl_codec::IntegerValue>(/*absolute_value=*/0, /*negative=*/false));
+
+  if ((kind_ == Kind::kWrite) || kind_ == Kind::kCall) {
+    // Decodes the outgoing message.
+    fidl_codec::DecodedMessage message;
+    std::stringstream error_stream;
+    message.DecodeMessage(dispatcher->MessageDecoderDispatcher(), process_id_, channel_,
+                          write_bytes_.data(), write_byte_count_, write_handles_.data(),
+                          write_handle_count_,
+                          (kind_ == Kind::kCall) ? fidl_codec::SyscallFidlType::kOutputRequest
+                                                 : fidl_codec::SyscallFidlType::kOutputMessage,
+                          error_stream);
+    invoked_event->AddOutlineField(
+        syscall->SearchOutlineMember("", /*invoked=*/true),
+        std::make_unique<fidl_codec::FidlMessageValue>(&message, error_stream.str(),
+                                                       write_bytes_.data(), write_byte_count_,
+                                                       write_handles_.data(), write_handle_count_));
+  }
+  dispatcher->AddInvokedEvent(invoked_event);
+
+  // Creates the output event.
+  auto output_event = std::make_shared<OutputEvent>(output_timestamp_, invoked_event->thread(),
+                                                    syscall, status_, invoked_event);
+
+  if (((kind_ == Kind::kRead) || (kind_ == Kind::kCall)) && (status_ == ZX_OK)) {
+    // Decodes the incoming message.
+    fidl_codec::DecodedMessage message;
+    std::stringstream error_stream;
+    message.DecodeMessage(dispatcher->MessageDecoderDispatcher(), process_id_, channel_,
+                          read_bytes_.data(), read_byte_count_, read_handles_.data(),
+                          read_handle_count_,
+                          (kind_ == Kind::kCall) ? fidl_codec::SyscallFidlType::kInputResponse
+                                                 : fidl_codec::SyscallFidlType::kInputMessage,
+                          error_stream);
+    output_event->AddOutlineField(syscall->SearchOutlineMember("", /*invoked=*/false),
+                                  std::make_unique<fidl_codec::FidlMessageValue>(
+                                      &message, error_stream.str(), read_bytes_.data(),
+                                      read_byte_count_, read_handles_.data(), read_handle_count_));
+  }
+  dispatcher->AddOutputEvent(std::move(output_event));
+}
+
 bool Replay::DumpProto(const std::string& proto_file_name) {
   if (proto_file_name == "-") {
     return DumpProto(std::cin);
@@ -127,4 +303,300 @@
   return ok;
 }
 
+void Replay::DecodeTrace(std::istream& is) {
+  // Decodes a trace stream, line per line, until the end of the stream.
+  while (!is.eof()) {
+    DecodeTraceLine(is);
+  }
+}
+
+void Replay::DecodeTraceLine(std::istream& is) {
+  // Decodes one trace line.
+  std::string line;
+  std::getline(is, line);
+  auto position = line.find("syscall ");
+  if (position == std::string::npos) {
+    // If the line doesn't include the keyword syscall, it's a standard trace line. In that case
+    // the line is output without modification (pass through).
+    dispatcher()->os() << line << '\n';
+  } else {
+    std::stringstream stream(line);
+    stream.seekg(position + 8, stream.cur);
+    // Format for all decoded traces:
+    // syscall |instance_id| |action| ...
+    uintptr_t instance;
+    std::string action;
+    stream >> std::hex >> instance >> std::dec >> action;
+    if (action == "process") {
+      // Defines the name of a process. The format is:
+      // syscall |instance_id| process |process_id| |process_name|
+      // For example:
+      // syscall 0x7ffd6863e9c0 process 2916209 FfxDoctor
+      if (position > 0) {
+        // Pass through any text before the keyword "syscall".
+        dispatcher()->os() << line.substr(0, position) << '\n';
+      }
+      std::string process_name;
+      uint64_t process_id;
+      stream >> process_id >> process_name;
+      Process* process = dispatcher()->SearchProcess(process_id);
+      if (process == nullptr) {
+        process = dispatcher()->CreateProcess(process_name, process_id, nullptr);
+      }
+      return;
+    }
+    if (action == "startup") {
+      // Defines a startup handle. That is a handle which is available to the user code either
+      // because the handle was given to the process (Fuchsia case) or because the handle has a
+      // special handling (Linux and other OS case).
+      // The format is (all fields on one line):
+      // syscall |instance_id| startup |process_id| |thread_id| |handle_type|(|handle|)
+      //   |type| |path|
+      // For example:
+      // syscall 0x7ffd68636f90 startup 2916209 2916210 Channel(1) dir /svc
+      if (position > 0) {
+        // Pass through any text before the keyword "syscall".
+        dispatcher()->os() << line.substr(0, position) << '\n';
+      }
+      uint64_t process_id;
+      uint64_t thread_id;
+      stream >> process_id >> thread_id >> std::ws;
+      char handle_type[100];
+      stream.get(handle_type, 100, '(');
+      stream.seekg(1, stream.cur);
+      uint32_t handle;
+      stream >> handle;
+      stream.seekg(1, stream.cur);
+      std::string type;
+      std::string path;
+      stream >> type >> path;
+      Process* process = dispatcher()->SearchProcess(process_id);
+      if (process != nullptr) {
+        HandleInfo* handle_info = process->SearchHandleInfo(handle);
+        if (handle_info != nullptr) {
+          handle_info->set_startup();
+        }
+      }
+      dispatcher()->inference().AddInferredHandleInfo(process_id, handle, type, path, "");
+      return;
+    }
+    if (action == "channel_create") {
+      // Defines a call to zx_channel_create. The format is (on one line):
+      // syscall |instance_id| channel_create |timestamp| |process_id| |thread_id| |out0| |out1|
+      //   |status|
+      // The fields out0 and out1 are in hexadecimal without a leading 0x.
+      // For example:
+      // syscall 0x7ffd68637fd0 channel_create 1234 2916209 2916210 9 a 0
+      if (position > 0) {
+        // Pass through any text before the keyword "syscall".
+        dispatcher()->os() << line.substr(0, position) << '\n';
+      }
+      uint64_t timestamp;
+      uint64_t process_id;
+      uint64_t thread_id;
+      uint32_t out0;
+      uint32_t out1;
+      zx_status_t status;
+      stream >> timestamp >> process_id >> thread_id >> std::hex >> out0 >> out1 >> std::dec >>
+          status;
+      Thread* thread = dispatcher()->CreateThread("foo", process_id, thread_id, nullptr);
+
+      // Specifies that both handles are channels.
+      dispatcher()
+          ->CreateHandleInfo(thread, out0, 0, /*startup=*/false)
+          ->set_object_type(ZX_OBJ_TYPE_CHANNEL);
+      dispatcher()
+          ->CreateHandleInfo(thread, out1, 0, /*startup=*/false)
+          ->set_object_type(ZX_OBJ_TYPE_CHANNEL);
+
+      // Specifies that the two channels are linked.
+      dispatcher()->inference().AddLinkedHandles(process_id, out0, out1);
+      dispatcher()->inference().AddLinkedHandles(process_id, out1, out0);
+
+      // Creates and adds the invoked and the output events.
+      Syscall* syscall = dispatcher()->SearchSyscall("zx_channel_create");
+      std::shared_ptr<InvokedEvent> invoked_event =
+          CreateInvoked(dispatcher(), timestamp, process_id, thread_id, syscall);
+      dispatcher()->AddInvokedEvent(invoked_event);
+      auto output_event = std::make_shared<OutputEvent>(timestamp, invoked_event->thread(), syscall,
+                                                        status, invoked_event);
+      zx_handle_disposition_t handle;
+      handle.operation = fidl_codec::kNoHandleDisposition;
+      handle.handle = out0;
+      handle.rights = 0;
+      handle.type = ZX_OBJ_TYPE_NONE;
+      handle.result = ZX_OK;
+      output_event->AddInlineField(syscall->SearchInlineMember("out0", /*invoked=*/false),
+                                   std::make_unique<fidl_codec::HandleValue>(handle));
+      handle.handle = out1;
+      output_event->AddInlineField(syscall->SearchInlineMember("out1", /*invoked=*/false),
+                                   std::make_unique<fidl_codec::HandleValue>(handle));
+      dispatcher()->AddOutputEvent(std::move(output_event));
+      return;
+    }
+    if ((action == "channel_call") || (action == "channel_call_etc")) {
+      // Defines a zx_channel_call or a zx_channel_call_etc syscall. The format is (on one line):
+      // syscall |instance_id| channel_call |timestamp| |process_id| |thread_id| |channel| |bytes|
+      //   |handles|
+      // The field channel is in hexdecimal without a leading 0x.
+      // The field bytes specifies the number of bytes to be written.
+      // The field handles specifies the number of handles to be written.
+      // If bytes or handles are not zero, this line will be followed by one or several lines which
+      // define the bytes and handles. Each of these lines will have the same instance_id.
+      // For example:
+      // syscall 0x5591382ba060 channel_call 1234 2916209 2916210 a 96 0
+      if (position > 0) {
+        // Pass through any text before the keyword "syscall".
+        dispatcher()->os() << line.substr(0, position) << '\n';
+      }
+      uint64_t timestamp;
+      uint64_t process_id;
+      uint64_t thread_id;
+      uint32_t channel;
+      uint32_t write_byte_count;
+      uint32_t write_handle_count;
+      stream >> timestamp >> process_id >> thread_id >> std::hex >> channel >> std::dec >>
+          write_byte_count >> write_handle_count;
+
+      // Creates a ReplayBuffer used to keep the context while the bytes and handles are read.
+      auto buffer = std::make_unique<ReplayBuffer>(timestamp, process_id, thread_id,
+                                                   ReplayBuffer::Kind::kCall,
+                                                   /*etc=*/action == "channel_call_etc", channel);
+      buffer->SetWrite(write_byte_count, write_handle_count);
+      buffers_[instance] = std::move(buffer);
+      return;
+    }
+    if ((action == "channel_write") || (action == "channel_write_etc")) {
+      // Defines a zx_channel_write or a zx_channel_write_etc syscall. The format is (on one line):
+      // syscall |instance_id| channel_write |timestamp| |process_id| |thread_id| |channel| |bytes|
+      //   |handles|
+      // The field channel is in hexdecimal without a leading 0x.
+      // The field bytes specifies the number of bytes to be written.
+      // The field handles specifies the number of handles to be written.
+      // If bytes or handles are not zero, this line will be followed by one or several lines which
+      // define the bytes and handles. Each of these lines will have the same instance_id.
+      // For example:
+      // syscall 0x5591382ba060 channel_write_etc 1234 2916209 2916210 a 96 0
+      if (position > 0) {
+        // Pass through any text before the keyword "syscall".
+        dispatcher()->os() << line.substr(0, position) << '\n';
+      }
+      uint64_t timestamp;
+      uint64_t process_id;
+      uint64_t thread_id;
+      uint32_t channel;
+      uint32_t write_byte_count;
+      uint32_t write_handle_count;
+      stream >> timestamp >> process_id >> thread_id >> std::hex >> channel >> std::dec >>
+          write_byte_count >> write_handle_count;
+
+      // Creates a ReplayBuffer used to keep the context while the bytes and handles are read.
+      auto buffer = std::make_unique<ReplayBuffer>(timestamp, process_id, thread_id,
+                                                   ReplayBuffer::Kind::kWrite,
+                                                   /*etc=*/action == "channel_write_etc", channel);
+      buffer->SetWrite(write_byte_count, write_handle_count);
+      buffers_[instance] = std::move(buffer);
+      return;
+    }
+    if ((action == "channel_read") || (action == "channel_read_etc")) {
+      // Defines a zx_channel_read or a zx_channel_read_etc syscall. The format is (on one line):
+      // syscall |instance_id| channel_read |timestamp| |process_id| |thread_id| |channel| |status|
+      //   |bytes| |handles|
+      // The field channel is in hexdecimal without a leading 0x.
+      // The field bytes specifies the number of bytes to be written.
+      // The field handles specifies the number of handles to be written.
+      // If bytes or handles are not zero, this line will be followed by one or several lines which
+      // define the bytes and handles. Each of these lines will have the same instance_id.
+      // For example:
+      // syscall 0x7ffd686381a0 channel_read 1234 2916209 2916209 9 0 96 0
+      if (position > 0) {
+        // Pass through any text before the keyword "syscall".
+        dispatcher()->os() << line.substr(0, position) << '\n';
+      }
+      uint64_t timestamp;
+      uint64_t process_id;
+      uint64_t thread_id;
+      uint32_t channel;
+      zx_status_t status;
+      uint32_t read_byte_count;
+      uint32_t read_handle_count;
+      stream >> timestamp >> process_id >> thread_id >> std::hex >> channel >> std::dec >> status >>
+          read_byte_count >> read_handle_count;
+
+      // Creates a ReplayBuffer used to keep the context while the bytes and handles are read.
+      auto buffer = std::make_unique<ReplayBuffer>(timestamp, process_id, thread_id,
+                                                   ReplayBuffer::Kind::kRead,
+                                                   /*etc=*/action == "channel_read_etc", channel);
+      buffer->SetRead(read_byte_count, read_handle_count);
+      buffer->SetStatus(timestamp, status);
+      if (buffer->DecodeOk()) {
+        // Case for which there is no bytes or handles. This happends when the status is not ZX_OK.
+        buffer->Dispatch(dispatcher());
+      } else {
+        buffers_[instance] = std::move(buffer);
+      }
+      return;
+    }
+
+    // The line is not a header line. Search for a pending buffer with the instance id.
+    auto buffer = SearchBuffer(instance);
+    if (buffer == nullptr) {
+      // No buffer found. The line is passed through.
+      dispatcher()->os() << line << '\n';
+      return;
+    }
+
+    // Checks for possible actions on a buffer.
+    if (action == "call_status") {
+      // Defines the status for a zx_channel_call. The format is:
+      // syscall |instance_id| call_status |timestamp| |status| |bytes| |channels|
+      // For example:
+      // syscall 0x559138249750 call_status 1234 0 48 0
+      // If bytes or handles are not zero, this line will be followed by one or several lines which
+      // define the bytes and handles. Each of these lines will have the same instance_id.
+      uint64_t timestamp;
+      zx_status_t status;
+      uint32_t read_byte_count;
+      uint32_t read_handle_count;
+      stream >> timestamp >> status >> read_byte_count >> read_handle_count;
+      buffer->SetStatus(timestamp, status);
+      buffer->SetRead(read_byte_count, read_handle_count);
+    } else if (action == "write_status") {
+      // Defines the status for a zx_channel_write. The format is:
+      // syscall |instance_id| write_status |timestamp| |status|
+      // For example:
+      // syscall 0x559138249750 write_status 1234 0
+      uint64_t timestamp;
+      zx_status_t status;
+      stream >> timestamp >> status;
+      buffer->SetStatus(timestamp, status);
+    } else if (action == "write_bytes") {
+      buffer->AddWriteBytes(stream);
+    } else if (action == "write_handles") {
+      buffer->AddWriteHandles(stream);
+    } else if (action == "write_etc_handle") {
+      buffer->AddWriteEtcHandle(stream);
+    } else if (action == "read_bytes") {
+      buffer->AddReadBytes(stream);
+    } else if (action == "read_handles") {
+      buffer->AddReadHandles(stream);
+    } else {
+      // No valid action found. The line is passed through.
+      dispatcher()->os() << line << '\n';
+      return;
+    }
+
+    if (position > 0) {
+      // Pass through any text before the keyword "syscall".
+      dispatcher()->os() << line.substr(0, position) << '\n';
+    }
+
+    // If the buffer is fully decoded, dispatches it and destroys it.
+    if (buffer->DecodeOk()) {
+      buffer->Dispatch(dispatcher());
+      buffers_.erase(instance);
+    }
+  }
+}
+
 }  // namespace fidlcat
diff --git a/tools/fidlcat/lib/replay.h b/tools/fidlcat/lib/replay.h
index c8914ee..058b094 100644
--- a/tools/fidlcat/lib/replay.h
+++ b/tools/fidlcat/lib/replay.h
@@ -5,6 +5,12 @@
 #ifndef TOOLS_FIDLCAT_LIB_REPLAY_H_
 #define TOOLS_FIDLCAT_LIB_REPLAY_H_
 
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <string>
+
 #include "src/lib/fidl_codec/library_loader.h"
 #include "tools/fidlcat/lib/event.h"
 #include "tools/fidlcat/lib/syscall_decoder_dispatcher.h"
@@ -12,12 +18,100 @@
 
 namespace fidlcat {
 
-// Class to replay a previously stored session. All the formating options can be used (for example
+// A buffer used to store the state of a channel syscall while the bytes and handles are decoded.
+class ReplayBuffer {
+ public:
+  enum class Kind { kRead, kWrite, kCall };
+
+  ReplayBuffer(uint64_t invoked_timestamp, uint64_t process_id, uint64_t thread_id, Kind kind,
+               bool etc, zx_handle_t channel)
+      : invoked_timestamp_(invoked_timestamp),
+        process_id_(process_id),
+        thread_id_(thread_id),
+        kind_(kind),
+        etc_(etc),
+        channel_(channel) {}
+
+  uint64_t thread_id() const { return thread_id_; }
+  Kind kind() const { return kind_; }
+  zx_handle_t channel() const { return channel_; }
+  zx_status_t status() const { return status_; }
+  const std::vector<uint8_t>& write_bytes() const { return write_bytes_; }
+  const std::vector<zx_handle_disposition_t>& write_handles() const { return write_handles_; }
+  const std::vector<uint8_t>& read_bytes() const { return read_bytes_; }
+  const std::vector<zx_handle_disposition_t>& read_handles() const { return read_handles_; }
+
+  // True if all the data for the syscall has been decoded.
+  bool DecodeOk() const {
+    return status_set_ && (write_bytes_.size() == write_byte_count_) &&
+           (write_handles_.size() == write_handle_count_) &&
+           (read_bytes_.size() == read_byte_count_) && (read_handles_.size() == read_handle_count_);
+  }
+
+  Syscall* GetSyscall(SyscallDisplayDispatcher* dispatcher) const;
+
+  void SetWrite(uint32_t write_byte_count, uint32_t write_handle_count) {
+    write_byte_count_ = write_byte_count;
+    write_handle_count_ = write_handle_count;
+  }
+
+  void SetRead(uint32_t read_byte_count, uint32_t read_handle_count) {
+    read_byte_count_ = read_byte_count;
+    read_handle_count_ = read_handle_count;
+  }
+
+  void SetStatus(uint64_t output_timestamp, zx_status_t status) {
+    output_timestamp_ = output_timestamp;
+    status_ = status;
+    status_set_ = true;
+  }
+
+  void AddWriteBytes(std::istream& stream);
+  void AddWriteHandles(std::istream& stream);
+  void AddWriteEtcHandle(std::istream& stream);
+  void AddReadBytes(std::istream& stream);
+  void AddReadHandles(std::istream& stream);
+
+  // When all the data has been decoded, creates the invoke and output events and adds them to the
+  // dispatcher.
+  void Dispatch(SyscallDisplayDispatcher* dispatcher);
+
+ private:
+  uint64_t invoked_timestamp_;
+  uint64_t process_id_;
+  uint64_t thread_id_;
+  const Kind kind_;
+  // True is the syscall is one of zx_channel_read_etc, zx_channel_write_etc and
+  // zx_channel_call_etc.
+  const bool etc_;
+  const zx_handle_t channel_;
+  uint32_t write_byte_count_ = 0;
+  uint32_t write_handle_count_ = 0;
+  uint32_t read_byte_count_ = 0;
+  uint32_t read_handle_count_ = 0;
+  uint64_t output_timestamp_ = 0;
+  zx_status_t status_ = ZX_OK;
+  bool status_set_ = false;
+  std::vector<uint8_t> write_bytes_;
+  std::vector<zx_handle_disposition_t> write_handles_;
+  std::vector<uint8_t> read_bytes_;
+  std::vector<zx_handle_disposition_t> read_handles_;
+};
+
+// Class to replay a previously stored session. All the formatting options can be used (for example
 // the filtering of message).
 class Replay : public EventDecoder {
  public:
   explicit Replay(SyscallDisplayDispatcher* dispatcher) : EventDecoder(dispatcher) {}
 
+  ReplayBuffer* SearchBuffer(uintptr_t instance) {
+    auto result = buffers_.find(instance);
+    if (result == buffers_.end()) {
+      return nullptr;
+    }
+    return result->second.get();
+  }
+
   // Dumps in text a binary protobuf file which contains a session.
   bool DumpProto(const std::string& proto_file_name);
   bool DumpProto(std::istream& is);
@@ -25,6 +119,14 @@
   // Replays a previously save session.
   bool ReplayProto(const std::string& proto_file_name);
   bool ReplayProto(const std::string& file_name, std::istream& is);
+
+  // Decodes traces.
+  void DecodeTrace(std::istream& is);
+  void DecodeTraceLine(std::istream& is);
+
+ private:
+  // Syscalls currently decoded from a trace.
+  std::map<uintptr_t, std::unique_ptr<ReplayBuffer>> buffers_;
 };
 
 }  // namespace fidlcat
diff --git a/tools/fidlcat/lib/syscall_decoder_dispatcher.h b/tools/fidlcat/lib/syscall_decoder_dispatcher.h
index 05d9498..146b906 100644
--- a/tools/fidlcat/lib/syscall_decoder_dispatcher.h
+++ b/tools/fidlcat/lib/syscall_decoder_dispatcher.h
@@ -1858,6 +1858,19 @@
     return returned_value;
   }
 
+  Thread* CreateThread(std::string_view process_name, zx_koid_t process_koid, zx_koid_t thread_koid,
+                       fxl::WeakPtr<zxdb::Process> zxdb_process) {
+    Process* process = SearchProcess(process_koid);
+    if (process == nullptr) {
+      process = CreateProcess(process_name, process_koid, std::move(zxdb_process));
+    }
+    Thread* thread = SearchThread(thread_koid);
+    if (thread != nullptr) {
+      return thread;
+    }
+    return CreateThread(thread_koid, process);
+  }
+
   // Ensures that the handle has been added to the process this thread belongs to.
   // If this handle has not been added (this is the first time fidlcat monitors it),
   // the handle is added to the process list of handle using |creation_time| and |startup|.
diff --git a/tools/fidlcat/log_format.md b/tools/fidlcat/log_format.md
new file mode 100644
index 0000000..695ef87
--- /dev/null
+++ b/tools/fidlcat/log_format.md
@@ -0,0 +1,246 @@
+# Decoding syscall logs with fidlcat
+
+This document describes the format for system call dumps written in log files that can be decoded by
+fidlcat.
+
+When an application writes logs, some low level library can also dump some of the system calls the
+application is doing within the logs. If these logs are given to **fidlcat --from=dump**, fidlcat
+can decode the system calls and the FIDL messages. The output of fidlcat is equal to the input
+except that any syscall dumps decoded by fidlcat is replaced by the decoded version.
+
+For example, these logs:
+
+```
+Attempting to connect to the daemon. This may take a couple seconds...
+syscall 0x7ffc90bd25b8 channel_create 1623951365995328698 2821426 2821426 1 2 0
+syscall 0x7ffc90bd2bb0 startup 2821426 2821426 Channel(1) dir /svc
+syscall 0x7ffc90bd3bd8 channel_create 1623951365995367083 2821426 2821426 9 a 0
+syscall 0x55fdf4fdb750 channel_write_etc 1623951365995377909 2821426 2821426 1 24 1
+syscall 0x55fdf4fdb750 write_bytes 00 00 00 00 00 00 00 01 ab 89 f9 f2 d9 06 68 55 ff ff ff ff 00 00 00 00
+syscall 0x55fdf4fdb750 write_etc_handle 0 0000000a 0000000e 4 0
+syscall 0x55fdf4fdb750 write_status 1623951365995394610 0
+```
+
+Will give this fidlcat output:
+
+```
+Attempting to connect to the daemon. This may take a couple seconds...
+
+1623951365.995329 FfxDoctor 2821426:2821426 zx_channel_create()
+1623951365.995329   -> ZX_OK (out0: handle = Channel:00000001, out1: handle = Channel:00000002)
+
+1623951365.995367 FfxDoctor 2821426:2821426 zx_channel_create()
+1623951365.995367   -> ZX_OK (out0: handle = Channel:00000009, out1: handle = Channel:0000000a)
+
+1623951365.995378 FfxDoctor 2821426:2821426 zx_channel_write_etc(
+    handle: handle = Channel:00000001(dir:/svc), options: uint32 = 0)
+  sent request fuchsia.overnet/HostOvernet.ConnectServiceConsumer = {
+    svc: handle = Move(Channel:0000000a, ZX_RIGHT_TRANSFER | ZX_RIGHT_READ | ZX_RIGHT_WRITE)
+  }
+1623951365.995394   -> ZX_OK
+```
+
+## Log format
+
+To be able to be decoded by fidlcat, dumps within the logs must have a very well defined format.
+
+All timestamps are in nano seconds. The base depends on the operating system. On Unix based systems,
+the zero is the Unix epoch (midnight on January 1 1970) in UTC.
+
+### process name
+
+This log should be present only once per process. It defines the name fidlcat will display when a
+log for this process is found.
+
+The format is:
+
+```
+syscall |instance_id| process |process_id| |process_name|
+```
+
+For example:
+
+```
+syscall 0x7ffd6863e9c0 process 2916209 FfxDoctor
+```
+
+### startup handle
+
+This log describes a startup handle. That is a handle which is available to the user code either
+because the handle was given to the process at startup (Fuchsia case) or because the handle has a
+special handling (Linux and other OS case).
+
+The format is:
+
+```
+syscall |instance_id| startup |process_id| |thread_id| |handle_type|(|handle|) |type| |path|
+```
+
+For example:
+
+```
+syscall 0x7ffd68636f90 startup 2916209 2916210 Channel(1) dir /svc
+```
+
+### zx_channel_create
+
+This log describes a call to **zx_channel_create** system call.
+
+The format is:
+
+```
+syscall |instance_id| channel_create |timestamp| |process_id| |thread_id| |out0| |out1| |status|
+```
+
+The fields **out0** and **out1** are in hexadecimal without a leading 0x.
+
+For example:
+
+```
+syscall 0x7ffd68637fd0 channel_create 1623951365995367083 2916209 2916210 9 a 0
+```
+
+### zx_channel_call, zx_channel_call_etc
+
+This log describes a **zx_channel_call** or a **zx_channel_call_etc** system call.
+
+The format is:
+
+```
+syscall |instance_id| channel_call |timestamp| |process_id| |thread_id| |channel| |bytes| |handles|
+```
+
+*   The field **channel** is in hexadecimal without a leading 0x.
+*   The field **bytes** specifies the number of bytes to be written.
+*   The field **handles** specifies the number of handles to be written.
+
+If **bytes** or **handles** are not zero, this line will be followed by one or several lines which
+define the bytes and handles. Each of these lines will have the same **instance_id**.
+
+### zx_channel_write, zx_channel_write_etc
+
+This log describes a **zx_channel_write** or a **zx_channel_write_etc** system call.
+
+The format is:
+
+```
+syscall |instance_id| channel_write |timestamp| |process_id| |thread_id| |channel| |bytes| |handles|
+```
+
+*   The field **channel** is in hexadecimal without a leading 0x.
+*   The field **bytes** specifies the number of bytes to be written.
+*   The field **handles** specifies the number of handles to be written.
+
+If **bytes** or **handles** are not zero, this line will be followed by one or several lines which
+define the bytes and handles. Each of these lines will have the same **instance_id**.
+
+### zx_channel_read, zx_channel_read_etc
+
+This log describes a **zx_channel_read** or a **zx_channel_read_etc** syscall.
+
+The format is:
+
+syscall |instance_id| channel_read |process_id| |thread_id| |channel| |status| |bytes| |handles|
+
+*   The field **channel** is in hexadecimal without a leading 0x.
+*   The field **bytes** specifies the number of bytes to be written.
+*   The field **handles** specifies the number of handles to be written.
+
+If **bytes** or **handles** are not zero, this line will be followed by one or several lines which
+define the bytes and handles. Each of these lines will have the same **instance_id**.
+
+### call_status
+
+This log describes the status for a **zx_channel_call** or a **zx_channel_call_etc** system call.
+
+The format is:
+
+```
+syscall |instance_id| call_status |timestamp| |status| |bytes| |channels|
+```
+
+The **instance_id** is the same as the write part of the call.
+
+*   The field **bytes** specifies the number of bytes to be written.
+*   The field **handles** specifies the number of handles to be written.
+
+If **bytes** or **handles** are not zero, this line will be followed by one or several lines which
+define the bytes and handles. Each of these lines will have the same **instance_id**.
+
+### write_status
+
+This log describes the status for a **zx_channel_write** or a **zx_channel_write_etc**.
+
+The format is:
+
+```
+syscall |instance_id| write_status |timestamp| |status|
+```
+
+### read_bytes write_bytes
+
+**read_bytes** describes some bytes for **zx_channel_read** or for the receiving part of
+**zx_channel_call** (after **call_status**).
+
+**write_bytes** describes some bytes for **zx_channel_write** or for the sending part of
+**zx_channel_call**.
+
+This log describes up to 32 bytes of data. If a remaining size is greater or equal to 32, 32 bytes
+must be present.
+
+Bytes are specified in hexadecimal (without any leading 0x).
+
+### read_handles write_handles
+
+**read_handles** describes some handles for **zx_channel_read** or for the receiving part of
+**zx_channel_call** (after **call_status**).
+
+**write_handles** describes some handles for **zx_channel_write** or for the sending part of
+**zx_channel_call**.
+
+This log describes up to 8 handles of data. If a remaining size is greater or equal to 8, 8 handles
+must be present.
+
+Handles are specified in hexadecimal (without any leading 0x).
+
+### write_etc_handle
+
+**write_etc_handle** describes one handle for **zx_channel_write**.
+
+The format is:
+
+```
+syscall |instance_id| write_etc_handle |operation| |handle| |rights| |type|
+```
+
+*   **operation** (0 or 1).
+*   **handle** (in hexadecimal without any leading 0x).
+*   **rights** (in hexadecimal without any leading 0x).
+*   **type** (in decimal).
+
+### examples
+
+```
+syscall 0x55fdf4fdb750 channel_write_etc 1623951365995377909 2821426 2821426 1 24 1
+syscall 0x55fdf4fdb750 write_bytes 00 00 00 00 00 00 00 01 ab 89 f9 f2 d9 06 68 55 ff ff ff ff 00 00 00 00
+syscall 0x55fdf4fdb750 write_etc_handle 0 0000000a 0000000e 4 0
+syscall 0x55fdf4fdb750 write_status 1623951365995394610 0
+```
+
+```
+syscall 0x7f792a884280 channel_read 1623951365995767872 2821426 2821426 2 0 24 1
+syscall 0x7f792a884280 read_bytes 00 00 00 00 00 00 00 01 ab 89 f9 f2 d9 06 68 55 ff ff ff ff 00 00 00 00
+syscall 0x7f792a884280 read_handles 0000000a
+```
+
+```
+syscall 0x5591382e1510 channel_call 1623951365995377909 2916209 2916209 21 16 0
+syscall 0x5591382e1510 write_bytes 01 00 00 00 00 00 00 01 cb 1f dd ee 2f e8 6c 52
+syscall 0x5591382e1510 call_status 1623951365995394610 0 192 0
+syscall 0x5591382e1510 read_bytes 01 00 00 00 00 00 00 01 cb 1f dd ee 2f e8 6c 52 03 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff
+syscall 0x5591382e1510 read_bytes 38 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff 08 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff
+syscall 0x5591382e1510 read_bytes 30 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff 28 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff
+syscall 0x5591382e1510 read_bytes 39 35 62 61 37 32 38 66 37 62 61 65 61 34 38 33 36 39 61 64 62 32 36 38 39 34 61 30 33 65 39 65
+syscall 0x5591382e1510 read_bytes 63 39 62 64 35 65 34 66 50 31 c2 60 00 00 00 00 19 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff
+syscall 0x5591382e1510 read_bytes 32 30 32 31 2d 30 36 2d 31 30 54 31 35 3a 33 35 3a 34 34 2b 30 30 3a 30 30 00 00 00 00 00 00 00
+```
diff --git a/tools/fidlcat/main.cc b/tools/fidlcat/main.cc
index ddf6b56..9f35404 100644
--- a/tools/fidlcat/main.cc
+++ b/tools/fidlcat/main.cc
@@ -202,6 +202,10 @@
       }
       replay.dispatcher()->SessionEnded();
     }
+  } else if (decode_options.input_mode == InputMode::kDump) {
+    fidlcat::Replay replay(decoder_dispatcher.get());
+    replay.DecodeTrace(std::cin);
+    replay.dispatcher()->SessionEnded();
   } else {
     InterceptionWorkflow workflow;
     workflow.Initialize(options.symbol_index_files, options.symbol_paths, options.build_id_dirs,