Merge "simpleperf: autofdo doc: Change to extbinary format" into main
diff --git a/memory_replay/Alloc.cpp b/memory_replay/Alloc.cpp
index b211218..e97dca0 100644
--- a/memory_replay/Alloc.cpp
+++ b/memory_replay/Alloc.cpp
@@ -19,28 +19,29 @@
 #include <stdio.h>
 #include <unistd.h>
 
+#include <memory_trace/MemoryTrace.h>
+
 #include "Alloc.h"
-#include "AllocParser.h"
 #include "Pointers.h"
 #include "Utils.h"
 
-bool AllocDoesFree(const AllocEntry& entry) {
+bool AllocDoesFree(const memory_trace::Entry& entry) {
   switch (entry.type) {
-    case MALLOC:
-    case CALLOC:
-    case MEMALIGN:
-    case THREAD_DONE:
+    case memory_trace::MALLOC:
+    case memory_trace::CALLOC:
+    case memory_trace::MEMALIGN:
+    case memory_trace::THREAD_DONE:
       return false;
 
-    case FREE:
+    case memory_trace::FREE:
       return entry.ptr != 0;
 
-    case REALLOC:
+    case memory_trace::REALLOC:
       return entry.u.old_ptr != 0;
   }
 }
 
-static uint64_t MallocExecute(const AllocEntry& entry, Pointers* pointers) {
+static uint64_t MallocExecute(const memory_trace::Entry& entry, Pointers* pointers) {
   int pagesize = getpagesize();
   uint64_t time_nsecs = Nanotime();
   void* memory = malloc(entry.size);
@@ -52,7 +53,7 @@
   return time_nsecs;
 }
 
-static uint64_t CallocExecute(const AllocEntry& entry, Pointers* pointers) {
+static uint64_t CallocExecute(const memory_trace::Entry& entry, Pointers* pointers) {
   int pagesize = getpagesize();
   uint64_t time_nsecs = Nanotime();
   void* memory = calloc(entry.u.n_elements, entry.size);
@@ -64,7 +65,7 @@
   return time_nsecs;
 }
 
-static uint64_t ReallocExecute(const AllocEntry& entry, Pointers* pointers) {
+static uint64_t ReallocExecute(const memory_trace::Entry& entry, Pointers* pointers) {
   void* old_memory = nullptr;
   if (entry.u.old_ptr != 0) {
     old_memory = pointers->Remove(entry.u.old_ptr);
@@ -81,7 +82,7 @@
   return time_nsecs;
 }
 
-static uint64_t MemalignExecute(const AllocEntry& entry, Pointers* pointers) {
+static uint64_t MemalignExecute(const memory_trace::Entry& entry, Pointers* pointers) {
   int pagesize = getpagesize();
   uint64_t time_nsecs = Nanotime();
   void* memory = memalign(entry.u.align, entry.size);
@@ -93,7 +94,7 @@
   return time_nsecs;
 }
 
-static uint64_t FreeExecute(const AllocEntry& entry, Pointers* pointers) {
+static uint64_t FreeExecute(const memory_trace::Entry& entry, Pointers* pointers) {
   if (entry.ptr == 0) {
     return 0;
   }
@@ -104,17 +105,17 @@
   return Nanotime() - time_nsecs;
 }
 
-uint64_t AllocExecute(const AllocEntry& entry, Pointers* pointers) {
+uint64_t AllocExecute(const memory_trace::Entry& entry, Pointers* pointers) {
   switch (entry.type) {
-    case MALLOC:
+    case memory_trace::MALLOC:
       return MallocExecute(entry, pointers);
-    case CALLOC:
+    case memory_trace::CALLOC:
       return CallocExecute(entry, pointers);
-    case REALLOC:
+    case memory_trace::REALLOC:
       return ReallocExecute(entry, pointers);
-    case MEMALIGN:
+    case memory_trace::MEMALIGN:
       return MemalignExecute(entry, pointers);
-    case FREE:
+    case memory_trace::FREE:
       return FreeExecute(entry, pointers);
     default:
       return 0;
diff --git a/memory_replay/Alloc.h b/memory_replay/Alloc.h
index f4dcc83..b4c8768 100644
--- a/memory_replay/Alloc.h
+++ b/memory_replay/Alloc.h
@@ -16,11 +16,12 @@
 
 #pragma once
 
-#include "AllocParser.h"
-
 // Forward Declarations.
+namespace memory_trace {
+struct Entry;
+}
 class Pointers;
 
-bool AllocDoesFree(const AllocEntry& entry);
+bool AllocDoesFree(const memory_trace::Entry& entry);
 
-uint64_t AllocExecute(const AllocEntry& entry, Pointers* pointers);
+uint64_t AllocExecute(const memory_trace::Entry& entry, Pointers* pointers);
diff --git a/memory_replay/AllocParser.cpp b/memory_replay/AllocParser.cpp
deleted file mode 100644
index ac6664a..0000000
--- a/memory_replay/AllocParser.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * 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
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <err.h>
-#include <inttypes.h>
-#include <stdio.h>
-
-#include "AllocParser.h"
-
-#include <iostream>
-
-void AllocGetData(const std::string& line, AllocEntry* entry) {
-    int op_prefix_pos = 0;
-    char name[128];
-    // All lines have this format:
-    //   TID: ALLOCATION_TYPE POINTER
-    // where
-    //   TID is the thread id of the thread doing the operation.
-    //   ALLOCATION_TYPE is one of malloc, calloc, memalign, realloc, free, thread_done
-    //   POINTER is the hex value of the actual pointer
-    if (sscanf(line.c_str(), "%d: %127s %" SCNx64 " %n", &entry->tid, name, &entry->ptr,
-               &op_prefix_pos) != 3) {
-        errx(1, "File Error: Failed to process %s", line.c_str());
-    }
-    std::string type(name);
-    if (type == "thread_done") {
-        entry->type = THREAD_DONE;
-    } else {
-        int args_offset = 0;
-        const char* args_beg = &line[op_prefix_pos];
-        if (type == "malloc") {
-            // Format:
-            //   TID: malloc POINTER SIZE_OF_ALLOCATION
-            if (sscanf(args_beg, "%zu%n", &entry->size, &args_offset) != 1) {
-                errx(1, "File Error: Failed to read malloc data %s", line.c_str());
-            }
-            entry->type = MALLOC;
-        } else if (type == "free") {
-            // Format:
-            //   TID: free POINTER
-            entry->type = FREE;
-        } else if (type == "calloc") {
-            // Format:
-            //   TID: calloc POINTER ITEM_COUNT ITEM_SIZE
-            if (sscanf(args_beg, "%" SCNd64 " %zu%n", &entry->u.n_elements, &entry->size,
-                       &args_offset) != 2) {
-                errx(1, "File Error: Failed to read calloc data %s", line.c_str());
-            }
-            entry->type = CALLOC;
-        } else if (type == "realloc") {
-            // Format:
-            //   TID: realloc POINTER OLD_POINTER NEW_SIZE
-            if (sscanf(args_beg, "%" SCNx64 " %zu%n", &entry->u.old_ptr, &entry->size,
-                       &args_offset) != 2) {
-                errx(1, "File Error: Failed to read realloc data %s", line.c_str());
-            }
-            entry->type = REALLOC;
-        } else if (type == "memalign") {
-            // Format:
-            //   TID: memalign POINTER ALIGNMENT SIZE
-            if (sscanf(args_beg, "%" SCNd64 " %zu%n", &entry->u.align, &entry->size,
-                       &args_offset) != 2) {
-                errx(1, "File Error: Failed to read memalign data %s", line.c_str());
-            }
-            entry->type = MEMALIGN;
-        } else {
-            errx(1, "File Error: Unknown type %s", type.c_str());
-        }
-
-        const char* timestamps_beg = &args_beg[args_offset];
-
-        // Timestamps come after the alloc args if present, for example,
-        //   TID: malloc POINTER SIZE_OF_ALLOCATION START_TIME END_TIME
-        int n_match = sscanf(timestamps_beg, "%" SCNd64 " %" SCNd64, &entry->st, &entry->et);
-        if (n_match != EOF && n_match != 2) {
-            errx(1, "File Error: Failed to read timestamps %s", line.c_str());
-        }
-    }
-}
diff --git a/memory_replay/AllocParser.h b/memory_replay/AllocParser.h
deleted file mode 100644
index e58be48..0000000
--- a/memory_replay/AllocParser.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * 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
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <sys/types.h>
-
-#include <string>
-
-enum AllocEnum : uint8_t {
-    MALLOC = 0,
-    CALLOC,
-    MEMALIGN,
-    REALLOC,
-    FREE,
-    THREAD_DONE,
-};
-
-struct AllocEntry {
-    pid_t tid;
-    AllocEnum type;
-    uint64_t ptr = 0;
-    size_t size = 0;
-    union {
-        uint64_t old_ptr = 0;
-        uint64_t n_elements;
-        uint64_t align;
-    } u;
-    uint64_t st = 0;
-    uint64_t et = 0;
-};
-
-void AllocGetData(const std::string& line, AllocEntry* entry);
diff --git a/memory_replay/Android.bp b/memory_replay/Android.bp
index e1f3b68..41adecd 100644
--- a/memory_replay/Android.bp
+++ b/memory_replay/Android.bp
@@ -33,32 +33,46 @@
 }
 
 cc_defaults {
-    name: "memory_flag_defaults",
-    host_supported: false,
+    name: "memory_replay_flag_defaults",
+
+    host_supported: true,
 
     cflags: [
         "-Wall",
         "-Wextra",
         "-Werror",
     ],
-
-    compile_multilib: "both",
-}
-
-cc_library_static {
-    name: "liballoc_parser",
-    host_supported: true,
-    defaults: ["memory_flag_defaults"],
-
-    export_include_dirs: ["."],
-    srcs: [
-        "AllocParser.cpp",
-    ],
 }
 
 cc_defaults {
     name: "memory_replay_defaults",
-    defaults: ["memory_flag_defaults"],
+    defaults: ["memory_replay_flag_defaults"],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libziparchive",
+    ],
+}
+
+cc_library_static {
+    name: "libmemory_trace",
+    host_supported: true,
+    defaults: ["memory_replay_flag_defaults"],
+
+    export_include_dirs: ["include"],
+    shared_libs: ["libbase"],
+    srcs: ["MemoryTrace.cpp"],
+
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.runtime",
+    ],
+}
+
+cc_library_static {
+    name: "libmemory_replay",
+    defaults: ["memory_replay_defaults"],
 
     srcs: [
         "Alloc.cpp",
@@ -69,24 +83,23 @@
         "Threads.cpp",
     ],
 
-    shared_libs: [
-        "libbase",
-        "libziparchive",
-    ],
-
-    static_libs: [
-        "liballoc_parser",
+    whole_static_libs: [
+        "libmemory_trace",
     ],
 }
 
 cc_binary {
     name: "memory_replay",
     defaults: ["memory_replay_defaults"],
+    host_supported: false,
 
     srcs: ["main.cpp"],
 
-    static_libs: ["liblog"],
+    static_libs: [
+        "libmemory_replay",
+    ],
 
+    compile_multilib: "both",
     multilib: {
         lib32: {
             suffix: "32",
@@ -99,28 +112,27 @@
 
 cc_binary_host {
     name: "filter_trace",
-
-    cflags: [
-        "-Wall",
-        "-Wextra",
-        "-Werror",
-    ],
-
-    shared_libs: [
-        "libziparchive",
-    ],
+    defaults: ["memory_replay_defaults"],
 
     static_libs: [
-        "liballoc_parser",
-        "libbase",
-        "liblog",
+        "libmemory_replay",
     ],
 
     srcs: [
-        "Alloc.cpp",
-        "File.cpp",
         "FilterTrace.cpp",
-        "Pointers.cpp",
+    ],
+}
+
+cc_binary_host {
+    name: "print_trace",
+    defaults: ["memory_replay_defaults"],
+
+    static_libs: [
+        "libmemory_replay",
+    ],
+
+    srcs: [
+        "PrintTrace.cpp",
     ],
 }
 
@@ -130,8 +142,8 @@
     isolated: true,
 
     srcs: [
-        "tests/AllocTest.cpp",
         "tests/FileTest.cpp",
+        "tests/MemoryTraceTest.cpp",
         "tests/NativeInfoTest.cpp",
         "tests/PointersTest.cpp",
         "tests/ThreadTest.cpp",
@@ -140,35 +152,28 @@
 
     local_include_dirs: ["tests"],
 
-    target: {
-        android: {
-            test_suites: ["device-tests"],
-        },
-    },
+    static_libs: [
+        "libmemory_replay",
+    ],
 
     data: [
         "tests/test.txt",
         "tests/test.zip",
     ],
+
+    test_suites: ["general-tests"],
 }
 
 cc_benchmark {
     name: "trace_benchmark",
-    defaults: ["memory_flag_defaults"],
+    defaults: ["memory_replay_defaults"],
 
     srcs: [
-        "Alloc.cpp",
         "TraceBenchmark.cpp",
-        "File.cpp",
-    ],
-
-    shared_libs: [
-        "libbase",
-        "libziparchive",
     ],
 
     static_libs: [
-        "liballoc_parser",
+        "libmemory_replay",
     ],
 
     data: [
diff --git a/memory_replay/AndroidTest.xml b/memory_replay/AndroidTest.xml
deleted file mode 100644
index cf3879a..0000000
--- a/memory_replay/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     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
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for memory_replay_tests">
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="memory_replay_tests->/data/local/tmp/memory_replay_tests" />
-    </target_preparer>
-    <option name="test-suite-tag" value="apct" />
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="memory_replay_tests" />
-    </test>
-</configuration>
diff --git a/memory_replay/File.cpp b/memory_replay/File.cpp
index e44c500..4983b4d 100644
--- a/memory_replay/File.cpp
+++ b/memory_replay/File.cpp
@@ -28,8 +28,8 @@
 #include <android-base/strings.h>
 #include <ziparchive/zip_archive.h>
 
-#include "Alloc.h"
-#include "AllocParser.h"
+#include <memory_trace/MemoryTrace.h>
+
 #include "File.h"
 
 std::string ZipGetContents(const char* filename) {
@@ -77,7 +77,7 @@
 
 // This function should not do any memory allocations in the main function.
 // Any true allocation should happen in fork'd code.
-void GetUnwindInfo(const char* filename, AllocEntry** entries, size_t* num_entries) {
+void GetUnwindInfo(const char* filename, memory_trace::Entry** entries, size_t* num_entries) {
   void* mem =
       mmap(nullptr, sizeof(size_t), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
   if (mem == MAP_FAILED) {
@@ -123,12 +123,13 @@
   *num_entries = *reinterpret_cast<size_t*>(mem);
   munmap(mem, sizeof(size_t));
 
-  mem = mmap(nullptr, *num_entries * sizeof(AllocEntry), PROT_READ | PROT_WRITE,
+  mem = mmap(nullptr, *num_entries * sizeof(memory_trace::Entry), PROT_READ | PROT_WRITE,
              MAP_ANONYMOUS | MAP_SHARED, -1, 0);
   if (mem == MAP_FAILED) {
-    err(1, "Unable to allocate a shared map of size %zu", *num_entries * sizeof(AllocEntry));
+    err(1, "Unable to allocate a shared map of size %zu",
+        *num_entries * sizeof(memory_trace::Entry));
   }
-  *entries = reinterpret_cast<AllocEntry*>(mem);
+  *entries = reinterpret_cast<memory_trace::Entry*>(mem);
 
   if ((pid = fork()) == 0) {
     std::string contents;
@@ -153,7 +154,11 @@
         errx(1, "Too many entries, stopped at entry %zu", entry_idx);
       }
       contents[end_str] = '\0';
-      AllocGetData(&contents[start_str], &(*entries)[entry_idx++]);
+      std::string error;
+      if (!memory_trace::FillInEntryFromString(&contents[start_str], (*entries)[entry_idx++],
+                                               error)) {
+        errx(1, "%s", error.c_str());
+      }
       start_str = end_str + 1;
     }
     if (entry_idx != *num_entries) {
@@ -167,6 +172,6 @@
   WaitPid(pid);
 }
 
-void FreeEntries(AllocEntry* entries, size_t num_entries) {
-  munmap(entries, num_entries * sizeof(AllocEntry));
+void FreeEntries(memory_trace::Entry* entries, size_t num_entries) {
+  munmap(entries, num_entries * sizeof(memory_trace::Entry));
 }
diff --git a/memory_replay/File.h b/memory_replay/File.h
index c1447bb..e70ca28 100644
--- a/memory_replay/File.h
+++ b/memory_replay/File.h
@@ -21,11 +21,13 @@
 #include <string>
 
 // Forward Declarations.
-struct AllocEntry;
+namespace memory_trace {
+struct Entry;
+}
 
 std::string ZipGetContents(const char* filename);
 
 // If filename ends with .zip, treat as a zip file to decompress.
-void GetUnwindInfo(const char* filename, AllocEntry** entries, size_t* num_entries);
+void GetUnwindInfo(const char* filename, memory_trace::Entry** entries, size_t* num_entries);
 
-void FreeEntries(AllocEntry* entries, size_t num_entries);
+void FreeEntries(memory_trace::Entry* entries, size_t num_entries);
diff --git a/memory_replay/FilterTrace.cpp b/memory_replay/FilterTrace.cpp
index 27f1945..65543f0 100644
--- a/memory_replay/FilterTrace.cpp
+++ b/memory_replay/FilterTrace.cpp
@@ -28,7 +28,8 @@
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
 
-#include "AllocParser.h"
+#include <memory_trace/MemoryTrace.h>
+
 #include "File.h"
 
 static std::string GetBaseExec() {
@@ -108,42 +109,18 @@
   return true;
 }
 
-static void PrintEntry(const AllocEntry& entry, size_t size, bool print_trace_format) {
+static void PrintEntry(const memory_trace::Entry& entry, size_t size, bool print_trace_format) {
   if (print_trace_format) {
-    switch (entry.type) {
-      case REALLOC:
-        if (entry.u.old_ptr == 0) {
-          // Convert to a malloc since it is functionally the same.
-          printf("%d: malloc %p %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr), entry.size);
-        } else {
-          printf("%d: realloc %p %p %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr),
-                 reinterpret_cast<void*>(entry.u.old_ptr), entry.size);
-        }
-        break;
-      case MALLOC:
-        printf("%d: malloc %p %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr), entry.size);
-        break;
-      case MEMALIGN:
-        printf("%d: memalign %p %zu %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr),
-               entry.u.align, entry.size);
-        break;
-      case CALLOC:
-        printf("%d: calloc %p %zu %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr),
-               entry.u.n_elements, entry.size);
-        break;
-      default:
-        errx(1, "Invalid entry type found %d\n", entry.type);
-        break;
-    }
+    printf("%s\n", memory_trace::CreateStringFromEntry(entry).c_str());
   } else {
-    printf("%s size %zu\n", entry.type == REALLOC && entry.u.old_ptr != 0 ? "realloc" : "alloc",
-           size);
+    printf("%s size %zu\n",
+           entry.type == memory_trace::REALLOC && entry.u.old_ptr != 0 ? "realloc" : "alloc", size);
   }
 }
 
 static void ProcessTrace(const std::string_view& trace, size_t min_size, size_t max_size,
                          bool print_trace_format) {
-  AllocEntry* entries;
+  memory_trace::Entry* entries;
   size_t num_entries;
   GetUnwindInfo(trace.data(), &entries, &num_entries);
 
@@ -159,14 +136,14 @@
   size_t total_allocs = 0;
   size_t total_reallocs = 0;
   for (size_t i = 0; i < num_entries; i++) {
-    const AllocEntry& entry = entries[i];
+    const memory_trace::Entry& entry = entries[i];
     switch (entry.type) {
-      case MALLOC:
-      case MEMALIGN:
-      case REALLOC:
+      case memory_trace::MALLOC:
+      case memory_trace::MEMALIGN:
+      case memory_trace::REALLOC:
         if (entry.size >= min_size && entry.size <= max_size) {
           PrintEntry(entry, entry.size, print_trace_format);
-          if (entry.type == REALLOC) {
+          if (entry.type == memory_trace::REALLOC) {
             total_reallocs++;
           } else {
             total_allocs++;
@@ -174,15 +151,15 @@
         }
         break;
 
-      case CALLOC:
+      case memory_trace::CALLOC:
         if (size_t size = entry.u.n_elements * entry.size;
             size >= min_size && entry.size <= max_size) {
           PrintEntry(entry, size, print_trace_format);
         }
         break;
 
-      case FREE:
-      case THREAD_DONE:
+      case memory_trace::FREE:
+      case memory_trace::THREAD_DONE:
       default:
         break;
     }
diff --git a/memory_replay/MemoryTrace.cpp b/memory_replay/MemoryTrace.cpp
new file mode 100644
index 0000000..e6d21df
--- /dev/null
+++ b/memory_replay/MemoryTrace.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/stringprintf.h>
+
+#include <memory_trace/MemoryTrace.h>
+
+namespace memory_trace {
+
+// This is larger than the maximum length of a possible line.
+constexpr size_t kBufferLen = 256;
+
+bool FillInEntryFromString(const std::string& line, Entry& entry, std::string& error) {
+  // All lines have this format:
+  //   TID: ALLOCATION_TYPE POINTER [START_TIME_NS END_TIME_NS]
+  // where
+  //   TID is the thread id of the thread doing the operation.
+  //   ALLOCATION_TYPE is one of malloc, calloc, memalign, realloc, free, thread_done
+  //   POINTER is the hex value of the actual pointer
+  //   START_TIME_NS is the start time of the operation in nanoseconds.
+  //   END_TIME_NS is the end time of the operation in nanoseconds.
+  // The START_TIME_NS and END_TIME_NS are optional parameters, either both
+  // are present are neither are present.
+  int op_prefix_pos = 0;
+  char name[128];
+  if (sscanf(line.c_str(), "%d: %127s %" SCNx64 " %n", &entry.tid, name, &entry.ptr,
+             &op_prefix_pos) != 3) {
+    error = "Failed to process line: " + line;
+    return false;
+  }
+
+  // Handle each individual type of entry type.
+  std::string type(name);
+  if (type == "thread_done") {
+    //   TID: thread_done 0x0 [END_TIME_NS]
+    // Where END_TIME_NS is optional.
+    entry.type = THREAD_DONE;
+    entry.start_ns = 0;
+    // Thread done has an optional time which is when the thread ended.
+    // This is the only entry type that has a single timestamp.
+    int n_match = sscanf(&line[op_prefix_pos], " %" SCNd64, &entry.end_ns);
+    entry.start_ns = 0;
+    if (n_match == EOF) {
+      entry.end_ns = 0;
+    } else if (n_match != 1) {
+      error = "Failed to read thread_done end time: " + line;
+      return false;
+    }
+    return true;
+  }
+
+  int args_offset = 0;
+  const char* args_beg = &line[op_prefix_pos];
+  if (type == "malloc") {
+    // Format:
+    //   TID: malloc POINTER SIZE_OF_ALLOCATION [START_TIME_NS END_TIME_NS]
+    if (sscanf(args_beg, "%zu%n", &entry.size, &args_offset) != 1) {
+      error = "Failed to read malloc data: " + line;
+      return false;
+    }
+    entry.type = MALLOC;
+  } else if (type == "free") {
+    // Format:
+    //   TID: free POINTER [START_TIME_NS END_TIME_NS]
+    entry.type = FREE;
+  } else if (type == "calloc") {
+    // Format:
+    //   TID: calloc POINTER ITEM_COUNT ITEM_SIZE [START_TIME_NS END_TIME_NS]
+    if (sscanf(args_beg, "%" SCNd64 " %zu%n", &entry.u.n_elements, &entry.size, &args_offset) !=
+        2) {
+      error = "Failed to read calloc data: " + line;
+      return false;
+    }
+    entry.type = CALLOC;
+  } else if (type == "realloc") {
+    // Format:
+    //   TID: realloc POINTER OLD_POINTER NEW_SIZE [START_TIME_NS END_TIME_NS]
+    if (sscanf(args_beg, "%" SCNx64 " %zu%n", &entry.u.old_ptr, &entry.size, &args_offset) != 2) {
+      error = "Failed to read realloc data: " + line;
+      return false;
+    }
+    entry.type = REALLOC;
+  } else if (type == "memalign") {
+    // Format:
+    //   TID: memalign POINTER ALIGNMENT SIZE [START_TIME_NS END_TIME_NS]
+    if (sscanf(args_beg, "%" SCNd64 " %zu%n", &entry.u.align, &entry.size, &args_offset) != 2) {
+      error = "Failed to read memalign data: " + line;
+      return false;
+    }
+    entry.type = MEMALIGN;
+  } else {
+    printf("Unknown type %s: %s\n", type.c_str(), line.c_str());
+    error = "Unknown type " + type + ": " + line;
+    return false;
+  }
+
+  const char* timestamps_beg = &args_beg[args_offset];
+
+  // Get the optional timestamps if they exist.
+  int n_match = sscanf(timestamps_beg, "%" SCNd64 " %" SCNd64, &entry.start_ns, &entry.end_ns);
+  if (n_match == EOF) {
+    entry.start_ns = 0;
+    entry.end_ns = 0;
+  } else if (n_match != 2) {
+    error = "Failed to read timestamps: " + line;
+    return false;
+  }
+  return true;
+}
+
+static const char* TypeToName(const TypeEnum type) {
+  switch (type) {
+    case CALLOC:
+      return "calloc";
+    case FREE:
+      return "free";
+    case MALLOC:
+      return "malloc";
+    case MEMALIGN:
+      return "memalign";
+    case REALLOC:
+      return "realloc";
+    case THREAD_DONE:
+      return "thread_done";
+  }
+  return "unknown";
+}
+
+static size_t FormatEntry(const Entry& entry, char* buffer, size_t buffer_len) {
+  int len = snprintf(buffer, buffer_len, "%d: %s 0x%" PRIx64, entry.tid, TypeToName(entry.type),
+                     entry.ptr);
+  if (len < 0) {
+    return 0;
+  }
+  size_t cur_len = len;
+  switch (entry.type) {
+    case FREE:
+      len = 0;
+      break;
+    case CALLOC:
+      len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRIu64 " %zu", entry.u.n_elements,
+                     entry.size);
+      break;
+    case MALLOC:
+      len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %zu", entry.size);
+      break;
+    case MEMALIGN:
+      len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRIu64 " %zu", entry.u.align,
+                     entry.size);
+      break;
+    case REALLOC:
+      len = snprintf(&buffer[cur_len], buffer_len - cur_len, " 0x%" PRIx64 " %zu", entry.u.old_ptr,
+                     entry.size);
+      break;
+    case THREAD_DONE:
+      // Thread done only has a single optional timestamp, end_ns.
+      if (entry.end_ns != 0) {
+        len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRId64, entry.end_ns);
+        if (len < 0) {
+          return 0;
+        }
+        return cur_len + len;
+      }
+      return cur_len;
+    default:
+      return 0;
+  }
+  if (len < 0) {
+    return 0;
+  }
+
+  cur_len += len;
+  if (entry.start_ns == 0) {
+    return cur_len;
+  }
+
+  len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRIu64 " %" PRIu64, entry.start_ns,
+                 entry.end_ns);
+  if (len < 0) {
+    return 0;
+  }
+  return cur_len + len;
+}
+
+std::string CreateStringFromEntry(const Entry& entry) {
+  std::string line(kBufferLen, '\0');
+
+  size_t size = FormatEntry(entry, line.data(), line.size());
+  if (size == 0) {
+    return "";
+  }
+  line.resize(size);
+  return line;
+}
+
+bool WriteEntryToFd(int fd, const Entry& entry) {
+  char buffer[kBufferLen];
+  size_t size = FormatEntry(entry, buffer, sizeof(buffer));
+  if (size == 0 || size == sizeof(buffer)) {
+    return false;
+  }
+  buffer[size++] = '\n';
+  buffer[size] = '\0';
+  ssize_t bytes = TEMP_FAILURE_RETRY(write(fd, buffer, size));
+  if (bytes < 0 || static_cast<size_t>(bytes) != size) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace memory_trace
diff --git a/memory_replay/PrintTrace.cpp b/memory_replay/PrintTrace.cpp
new file mode 100644
index 0000000..951c31a
--- /dev/null
+++ b/memory_replay/PrintTrace.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#include <android-base/file.h>
+
+#include <memory_trace/MemoryTrace.h>
+
+#include "File.h"
+
+static void Usage() {
+  fprintf(stderr, "Usage: %s TRACE_FILE\n",
+          android::base::Basename(android::base::GetExecutablePath()).c_str());
+  fprintf(stderr, "  TRACE_FILE\n");
+  fprintf(stderr, "      The trace file\n");
+  fprintf(stderr, "\n  Print a trace to stdout.\n");
+}
+
+int main(int argc, char** argv) {
+  if (argc != 2) {
+    Usage();
+    return 1;
+  }
+
+  memory_trace::Entry* entries;
+  size_t num_entries;
+  GetUnwindInfo(argv[1], &entries, &num_entries);
+
+  for (size_t i = 0; i < num_entries; i++) {
+    printf("%s\n", memory_trace::CreateStringFromEntry(entries[i]).c_str());
+  }
+
+  FreeEntries(entries, num_entries);
+  return 0;
+}
diff --git a/memory_replay/TEST_MAPPING b/memory_replay/TEST_MAPPING
new file mode 100644
index 0000000..286527a
--- /dev/null
+++ b/memory_replay/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "memory_replay_tests"
+    }
+  ]
+}
diff --git a/memory_replay/Thread.h b/memory_replay/Thread.h
index ffab01b..810bde7 100644
--- a/memory_replay/Thread.h
+++ b/memory_replay/Thread.h
@@ -21,7 +21,9 @@
 #include <sys/types.h>
 
 // Forward Declarations.
-struct AllocEntry;
+namespace memory_trace {
+struct Entry;
+}
 class Pointers;
 
 class Thread {
@@ -39,8 +41,8 @@
   void set_pointers(Pointers* pointers) { pointers_ = pointers; }
   Pointers* pointers() { return pointers_; }
 
-  void SetAllocEntry(const AllocEntry* entry) { entry_ = entry; }
-  const AllocEntry& GetAllocEntry() { return *entry_; }
+  void SetEntry(const memory_trace::Entry* entry) { entry_ = entry; }
+  const memory_trace::Entry& GetEntry() { return *entry_; }
 
  private:
   pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER;
@@ -53,7 +55,7 @@
 
   Pointers* pointers_ = nullptr;
 
-  const AllocEntry* entry_;
+  const memory_trace::Entry* entry_;
 
   friend class Threads;
 };
diff --git a/memory_replay/Threads.cpp b/memory_replay/Threads.cpp
index 15fc69f..3fcedc0 100644
--- a/memory_replay/Threads.cpp
+++ b/memory_replay/Threads.cpp
@@ -26,6 +26,8 @@
 
 #include <new>
 
+#include <memory_trace/MemoryTrace.h>
+
 #include "Alloc.h"
 #include "Pointers.h"
 #include "Thread.h"
@@ -35,9 +37,9 @@
   Thread* thread = reinterpret_cast<Thread*>(data);
   while (true) {
     thread->WaitForPending();
-    const AllocEntry& entry = thread->GetAllocEntry();
+    const memory_trace::Entry& entry = thread->GetEntry();
     thread->AddTimeNsecs(AllocExecute(entry, thread->pointers()));
-    bool thread_done = entry.type == THREAD_DONE;
+    bool thread_done = entry.type == memory_trace::THREAD_DONE;
     thread->ClearPending();
     if (thread_done) {
       break;
@@ -143,10 +145,10 @@
 }
 
 void Threads::FinishAll() {
-  AllocEntry thread_done = {.type = THREAD_DONE};
+  memory_trace::Entry thread_done = {.type = memory_trace::THREAD_DONE};
   for (size_t i = 0; i < max_threads_; i++) {
     if (threads_[i].tid_ != 0) {
-      threads_[i].SetAllocEntry(&thread_done);
+      threads_[i].SetEntry(&thread_done);
       threads_[i].SetPending();
       Finish(threads_ + i);
     }
diff --git a/memory_replay/TraceBenchmark.cpp b/memory_replay/TraceBenchmark.cpp
index a3aad57..1612997 100644
--- a/memory_replay/TraceBenchmark.cpp
+++ b/memory_replay/TraceBenchmark.cpp
@@ -35,12 +35,13 @@
 #include <android-base/strings.h>
 #include <benchmark/benchmark.h>
 
-#include "Alloc.h"
+#include <memory_trace/MemoryTrace.h>
+
 #include "File.h"
 #include "Utils.h"
 
 struct TraceDataType {
-  AllocEntry* entries = nullptr;
+  memory_trace::Entry* entries = nullptr;
   size_t num_entries = 0;
   void** ptrs = nullptr;
   size_t num_ptrs = 0;
@@ -99,17 +100,17 @@
   std::stack<size_t> free_indices;
   std::unordered_map<uint64_t, size_t> ptr_to_index;
   for (size_t i = 0; i < trace_data->num_entries; i++) {
-    AllocEntry* entry = &trace_data->entries[i];
+    memory_trace::Entry* entry = &trace_data->entries[i];
     switch (entry->type) {
-      case MALLOC:
-      case CALLOC:
-      case MEMALIGN: {
+      case memory_trace::MALLOC:
+      case memory_trace::CALLOC:
+      case memory_trace::MEMALIGN: {
         size_t idx = GetIndex(free_indices, &trace_data->num_ptrs);
         ptr_to_index[entry->ptr] = idx;
         entry->ptr = idx;
         break;
       }
-      case REALLOC: {
+      case memory_trace::REALLOC: {
         if (entry->u.old_ptr != 0) {
           auto idx_entry = ptr_to_index.find(entry->u.old_ptr);
           if (idx_entry == ptr_to_index.end()) {
@@ -125,7 +126,7 @@
         entry->ptr = idx;
         break;
       }
-      case FREE:
+      case memory_trace::FREE:
         if (entry->ptr != 0) {
           auto idx_entry = ptr_to_index.find(entry->ptr);
           if (idx_entry == ptr_to_index.end()) {
@@ -136,7 +137,7 @@
           ptr_to_index.erase(idx_entry);
         }
         break;
-      case THREAD_DONE:
+      case memory_trace::THREAD_DONE:
         break;
     }
   }
@@ -156,9 +157,9 @@
   void** ptrs = trace_data->ptrs;
   for (size_t i = 0; i < trace_data->num_entries; i++) {
     void* ptr;
-    const AllocEntry& entry = trace_data->entries[i];
+    const memory_trace::Entry& entry = trace_data->entries[i];
     switch (entry.type) {
-      case MALLOC:
+      case memory_trace::MALLOC:
         start_ns = Nanotime();
         ptr = malloc(entry.size);
         if (ptr == nullptr) {
@@ -173,7 +174,7 @@
         ptrs[entry.ptr] = ptr;
         break;
 
-      case CALLOC:
+      case memory_trace::CALLOC:
         start_ns = Nanotime();
         ptr = calloc(entry.u.n_elements, entry.size);
         if (ptr == nullptr) {
@@ -188,7 +189,7 @@
         ptrs[entry.ptr] = ptr;
         break;
 
-      case MEMALIGN:
+      case memory_trace::MEMALIGN:
         start_ns = Nanotime();
         ptr = memalign(entry.u.align, entry.size);
         if (ptr == nullptr) {
@@ -203,7 +204,7 @@
         ptrs[entry.ptr] = ptr;
         break;
 
-      case REALLOC:
+      case memory_trace::REALLOC:
         start_ns = Nanotime();
         if (entry.u.old_ptr == 0) {
           ptr = realloc(nullptr, entry.size);
@@ -225,7 +226,7 @@
         ptrs[entry.ptr] = ptr;
         break;
 
-      case FREE:
+      case memory_trace::FREE:
         if (entry.ptr != 0) {
           ptr = ptrs[entry.ptr - 1];
           ptrs[entry.ptr - 1] = nullptr;
@@ -237,7 +238,7 @@
         total_ns += Nanotime() - start_ns;
         break;
 
-      case THREAD_DONE:
+      case memory_trace::THREAD_DONE:
         break;
     }
   }
@@ -249,7 +250,8 @@
 // Run a trace as if all of the allocations occurred in a single thread.
 // This is not completely realistic, but it is a possible worst case that
 // could happen in an app.
-static void BenchmarkTrace(benchmark::State& state, const char* filename, bool enable_decay_time) {
+static void BenchmarkTrace(benchmark::State& state, const char* filename,
+                           [[maybe_unused]] bool enable_decay_time) {
 #if defined(__BIONIC__)
   if (enable_decay_time) {
     mallopt(M_DECAY_TIME, 1);
diff --git a/memory_replay/include/memory_trace/MemoryTrace.h b/memory_replay/include/memory_trace/MemoryTrace.h
new file mode 100644
index 0000000..108a921
--- /dev/null
+++ b/memory_replay/include/memory_trace/MemoryTrace.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <string>
+
+namespace memory_trace {
+
+enum TypeEnum : uint8_t {
+  MALLOC = 0,
+  CALLOC,
+  MEMALIGN,
+  REALLOC,
+  FREE,
+  THREAD_DONE,
+};
+
+struct Entry {
+  pid_t tid;
+  TypeEnum type;
+  uint64_t ptr = 0;
+  size_t size = 0;
+  union {
+    uint64_t old_ptr = 0;
+    uint64_t n_elements;
+    uint64_t align;
+  } u;
+  uint64_t start_ns = 0;
+  uint64_t end_ns = 0;
+};
+
+bool FillInEntryFromString(const std::string& line, Entry& entry, std::string& error);
+
+std::string CreateStringFromEntry(const Entry& entry);
+
+// Guaranteed not to allocate.
+bool WriteEntryToFd(int fd, const Entry& entry);
+
+}  // namespace memory_trace
diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp
index 7d7c538..0f50953 100644
--- a/memory_replay/main.cpp
+++ b/memory_replay/main.cpp
@@ -27,6 +27,8 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <memory_trace/MemoryTrace.h>
+
 #include "Alloc.h"
 #include "File.h"
 #include "NativeInfo.h"
@@ -39,28 +41,28 @@
 
 constexpr size_t kDefaultMaxThreads = 512;
 
-static size_t GetMaxAllocs(const AllocEntry* entries, size_t num_entries) {
+static size_t GetMaxAllocs(const memory_trace::Entry* entries, size_t num_entries) {
   size_t max_allocs = 0;
   size_t num_allocs = 0;
   for (size_t i = 0; i < num_entries; i++) {
     switch (entries[i].type) {
-      case THREAD_DONE:
+      case memory_trace::THREAD_DONE:
         break;
-      case MALLOC:
-      case CALLOC:
-      case MEMALIGN:
+      case memory_trace::MALLOC:
+      case memory_trace::CALLOC:
+      case memory_trace::MEMALIGN:
         if (entries[i].ptr != 0) {
           num_allocs++;
         }
         break;
-      case REALLOC:
+      case memory_trace::REALLOC:
         if (entries[i].ptr == 0 && entries[i].u.old_ptr != 0) {
           num_allocs--;
         } else if (entries[i].ptr != 0 && entries[i].u.old_ptr == 0) {
           num_allocs++;
         }
         break;
-      case FREE:
+      case memory_trace::FREE:
         if (entries[i].ptr != 0) {
           num_allocs--;
         }
@@ -109,7 +111,8 @@
   android_logger_list_close(list);
 }
 
-static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t max_threads) {
+static void ProcessDump(const memory_trace::Entry* entries, size_t num_entries,
+                        size_t max_threads) {
   // Do a pass to get the maximum number of allocations used at one
   // time to allow a single mmap that can hold the maximum number of
   // pointers needed at once.
@@ -128,7 +131,7 @@
       dprintf(STDOUT_FILENO, "  At line %zu:\n", i + 1);
       NativePrintInfo("    ");
     }
-    const AllocEntry& entry = entries[i];
+    const memory_trace::Entry& entry = entries[i];
     Thread* thread = threads.FindThread(entry.tid);
     if (thread == nullptr) {
       thread = threads.CreateThread(entry.tid);
@@ -138,7 +141,7 @@
     // the next action.
     thread->WaitForReady();
 
-    thread->SetAllocEntry(&entry);
+    thread->SetEntry(&entry);
 
     bool does_free = AllocDoesFree(entry);
     if (does_free) {
@@ -151,7 +154,7 @@
     // Tell the thread to execute the action.
     thread->SetPending();
 
-    if (entries[i].type == THREAD_DONE) {
+    if (entries[i].type == memory_trace::THREAD_DONE) {
       // Wait for the thread to finish and clear the thread entry.
       threads.Finish(thread);
     }
@@ -223,7 +226,7 @@
     max_threads = atoi(argv[2]);
   }
 
-  AllocEntry* entries;
+  memory_trace::Entry* entries;
   size_t num_entries;
   GetUnwindInfo(argv[1], &entries, &num_entries);
 
diff --git a/memory_replay/tests/AllocTest.cpp b/memory_replay/tests/AllocTest.cpp
deleted file mode 100644
index d5dd057..0000000
--- a/memory_replay/tests/AllocTest.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * 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
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdint.h>
-
-#include <string>
-
-#include <gtest/gtest.h>
-
-#include "Alloc.h"
-
-TEST(AllocTest, malloc_valid) {
-  std::string line = "1234: malloc 0xabd0000 20";
-  AllocEntry entry;
-  AllocGetData(line, &entry);
-  EXPECT_EQ(MALLOC, entry.type);
-  EXPECT_EQ(1234, entry.tid);
-  EXPECT_EQ(0xabd0000U, entry.ptr);
-  EXPECT_EQ(20U, entry.size);
-  EXPECT_EQ(0U, entry.u.align);
-}
-
-TEST(AllocTest, malloc_invalid) {
-  std::string line = "1234: malloc 0xabd0000";
-  AllocEntry entry;
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-
-  line = "1234: malloc";
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
-
-TEST(AllocTest, free_valid) {
-  std::string line = "1235: free 0x5000";
-  AllocEntry entry;
-  AllocGetData(line, &entry);
-  EXPECT_EQ(FREE, entry.type);
-  EXPECT_EQ(1235, entry.tid);
-  EXPECT_EQ(0x5000U, entry.ptr);
-  EXPECT_EQ(0U, entry.size);
-  EXPECT_EQ(0U, entry.u.align);
-}
-
-TEST(AllocTest, free_invalid) {
-  std::string line = "1234: free";
-  AllocEntry entry;
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
-
-TEST(AllocTest, calloc_valid) {
-  std::string line = "1236: calloc 0x8000 50 30";
-  AllocEntry entry;
-  AllocGetData(line, &entry);
-  EXPECT_EQ(CALLOC, entry.type);
-  EXPECT_EQ(1236, entry.tid);
-  EXPECT_EQ(0x8000U, entry.ptr);
-  EXPECT_EQ(30U, entry.size);
-  EXPECT_EQ(50U, entry.u.n_elements);
-}
-
-TEST(AllocTest, calloc_invalid) {
-  std::string line = "1236: calloc 0x8000 50";
-  AllocEntry entry;
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-
-  line = "1236: calloc 0x8000";
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-
-  line = "1236: calloc";
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
-
-TEST(AllocTest, realloc_valid) {
-  std::string line = "1237: realloc 0x9000 0x4000 80";
-  AllocEntry entry;
-  AllocGetData(line, &entry);
-  EXPECT_EQ(REALLOC, entry.type);
-  EXPECT_EQ(1237, entry.tid);
-  EXPECT_EQ(0x9000U, entry.ptr);
-  EXPECT_EQ(80U, entry.size);
-  EXPECT_EQ(0x4000U, entry.u.old_ptr);
-}
-
-TEST(AllocTest, realloc_invalid) {
-  std::string line = "1237: realloc 0x9000 0x4000";
-  AllocEntry entry;
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-
-  line = "1237: realloc 0x9000";
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-
-  line = "1237: realloc";
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
-
-TEST(AllocTest, memalign_valid) {
-  std::string line = "1238: memalign 0xa000 16 89";
-  AllocEntry entry;
-  AllocGetData(line, &entry);
-  EXPECT_EQ(MEMALIGN, entry.type);
-  EXPECT_EQ(1238, entry.tid);
-  EXPECT_EQ(0xa000U, entry.ptr);
-  EXPECT_EQ(89U, entry.size);
-  EXPECT_EQ(16U, entry.u.align);
-}
-
-TEST(AllocTest, memalign_invalid) {
-  std::string line = "1238: memalign 0xa000 16";
-  AllocEntry entry;
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-
-  line = "1238: memalign 0xa000";
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-
-  line = "1238: memalign";
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
-
-TEST(AllocTest, thread_done_valid) {
-  std::string line = "1239: thread_done 0x0";
-  AllocEntry entry;
-  AllocGetData(line, &entry);
-  EXPECT_EQ(THREAD_DONE, entry.type);
-  EXPECT_EQ(1239, entry.tid);
-  EXPECT_EQ(0U, entry.ptr);
-  EXPECT_EQ(0U, entry.size);
-  EXPECT_EQ(0U, entry.u.old_ptr);
-}
-
-TEST(AllocTest, thread_done_invalid) {
-  std::string line = "1240: thread_done";
-  AllocEntry entry;
-  EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
diff --git a/memory_replay/tests/FileTest.cpp b/memory_replay/tests/FileTest.cpp
index 77c0593..5d92c3d 100644
--- a/memory_replay/tests/FileTest.cpp
+++ b/memory_replay/tests/FileTest.cpp
@@ -22,7 +22,8 @@
 #include <android-base/file.h>
 #include <gtest/gtest.h>
 
-#include "Alloc.h"
+#include <memory_trace/MemoryTrace.h>
+
 #include "File.h"
 
 static std::string GetTestDirectory() {
@@ -46,7 +47,7 @@
   std::string file_name = GetTestZip();
 
   size_t mallinfo_before = mallinfo().uordblks;
-  AllocEntry* entries;
+  memory_trace::Entry* entries;
   size_t num_entries;
   GetUnwindInfo(file_name.c_str(), &entries, &num_entries);
   size_t mallinfo_after = mallinfo().uordblks;
@@ -56,13 +57,13 @@
 
   ASSERT_EQ(2U, num_entries);
   EXPECT_EQ(12345, entries[0].tid);
-  EXPECT_EQ(MALLOC, entries[0].type);
+  EXPECT_EQ(memory_trace::MALLOC, entries[0].type);
   EXPECT_EQ(0x1000U, entries[0].ptr);
   EXPECT_EQ(16U, entries[0].size);
   EXPECT_EQ(0U, entries[0].u.old_ptr);
 
   EXPECT_EQ(12345, entries[1].tid);
-  EXPECT_EQ(FREE, entries[1].type);
+  EXPECT_EQ(memory_trace::FREE, entries[1].type);
   EXPECT_EQ(0x1000U, entries[1].ptr);
   EXPECT_EQ(0U, entries[1].size);
   EXPECT_EQ(0U, entries[1].u.old_ptr);
@@ -71,7 +72,7 @@
 }
 
 TEST(FileTest, get_unwind_info_bad_zip_file) {
-  AllocEntry* entries;
+  memory_trace::Entry* entries;
   size_t num_entries;
   EXPECT_DEATH(GetUnwindInfo("/does/not/exist.zip", &entries, &num_entries), "");
 }
@@ -81,7 +82,7 @@
   std::string file_name = GetTestDirectory() + "/test.txt";
 
   size_t mallinfo_before = mallinfo().uordblks;
-  AllocEntry* entries;
+  memory_trace::Entry* entries;
   size_t num_entries;
   GetUnwindInfo(file_name.c_str(), &entries, &num_entries);
   size_t mallinfo_after = mallinfo().uordblks;
@@ -91,13 +92,13 @@
 
   ASSERT_EQ(2U, num_entries);
   EXPECT_EQ(98765, entries[0].tid);
-  EXPECT_EQ(MEMALIGN, entries[0].type);
+  EXPECT_EQ(memory_trace::MEMALIGN, entries[0].type);
   EXPECT_EQ(0xa000U, entries[0].ptr);
   EXPECT_EQ(124U, entries[0].size);
   EXPECT_EQ(16U, entries[0].u.align);
 
   EXPECT_EQ(98765, entries[1].tid);
-  EXPECT_EQ(FREE, entries[1].type);
+  EXPECT_EQ(memory_trace::FREE, entries[1].type);
   EXPECT_EQ(0xa000U, entries[1].ptr);
   EXPECT_EQ(0U, entries[1].size);
   EXPECT_EQ(0U, entries[1].u.old_ptr);
@@ -106,7 +107,7 @@
 }
 
 TEST(FileTest, get_unwind_info_bad_file) {
-  AllocEntry* entries;
+  memory_trace::Entry* entries;
   size_t num_entries;
   EXPECT_DEATH(GetUnwindInfo("/does/not/exist", &entries, &num_entries), "");
 }
diff --git a/memory_replay/tests/MemoryTraceTest.cpp b/memory_replay/tests/MemoryTraceTest.cpp
new file mode 100644
index 0000000..1cdd3e2
--- /dev/null
+++ b/memory_replay/tests/MemoryTraceTest.cpp
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <android-base/file.h>
+#include <memory_trace/MemoryTrace.h>
+
+TEST(MemoryTraceReadTest, malloc_valid) {
+  std::string line = "1234: malloc 0xabd0000 20";
+  memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+  std::string error;
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::MALLOC, entry.type);
+  EXPECT_EQ(1234, entry.tid);
+  EXPECT_EQ(0xabd0000U, entry.ptr);
+  EXPECT_EQ(20U, entry.size);
+  EXPECT_EQ(0U, entry.u.align);
+  EXPECT_EQ(0U, entry.start_ns);
+  EXPECT_EQ(0U, entry.end_ns);
+
+  line += " 1000 1020";
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::MALLOC, entry.type);
+  EXPECT_EQ(1234, entry.tid);
+  EXPECT_EQ(0xabd0000U, entry.ptr);
+  EXPECT_EQ(20U, entry.size);
+  EXPECT_EQ(0U, entry.u.align);
+  EXPECT_EQ(1000U, entry.start_ns);
+  EXPECT_EQ(1020U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, malloc_invalid) {
+  // Missing size
+  std::string line = "1234: malloc 0xabd0000";
+  memory_trace::Entry entry;
+  std::string error;
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read malloc data: 1234: malloc 0xabd0000", error);
+
+  // Missing pointer and size
+  line = "1234: malloc";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to process line: 1234: malloc", error);
+
+  // Missing end time
+  line = "1234: malloc 0xabd0000 10 100";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read timestamps: 1234: malloc 0xabd0000 10 100", error);
+}
+
+TEST(MemoryTraceReadTest, free_valid) {
+  std::string line = "1235: free 0x5000";
+  memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+  std::string error;
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::FREE, entry.type);
+  EXPECT_EQ(1235, entry.tid);
+  EXPECT_EQ(0x5000U, entry.ptr);
+  EXPECT_EQ(0U, entry.size);
+  EXPECT_EQ(0U, entry.u.align);
+  EXPECT_EQ(0U, entry.start_ns);
+  EXPECT_EQ(0U, entry.end_ns);
+
+  line += " 540 2000";
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::FREE, entry.type);
+  EXPECT_EQ(1235, entry.tid);
+  EXPECT_EQ(0x5000U, entry.ptr);
+  EXPECT_EQ(0U, entry.size);
+  EXPECT_EQ(0U, entry.u.align);
+  EXPECT_EQ(540U, entry.start_ns);
+  EXPECT_EQ(2000U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, free_invalid) {
+  // Missing pointer
+  std::string line = "1234: free";
+  memory_trace::Entry entry;
+  std::string error;
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to process line: 1234: free", error);
+
+  // Missing end time
+  line = "1234: free 0x100 100";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read timestamps: 1234: free 0x100 100", error);
+}
+
+TEST(MemoryTraceReadTest, calloc_valid) {
+  std::string line = "1236: calloc 0x8000 50 30";
+  memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+  std::string error;
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::CALLOC, entry.type);
+  EXPECT_EQ(1236, entry.tid);
+  EXPECT_EQ(0x8000U, entry.ptr);
+  EXPECT_EQ(30U, entry.size);
+  EXPECT_EQ(50U, entry.u.n_elements);
+  EXPECT_EQ(0U, entry.start_ns);
+  EXPECT_EQ(0U, entry.end_ns);
+
+  line += " 700 1000";
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::CALLOC, entry.type);
+  EXPECT_EQ(1236, entry.tid);
+  EXPECT_EQ(0x8000U, entry.ptr);
+  EXPECT_EQ(30U, entry.size);
+  EXPECT_EQ(50U, entry.u.n_elements);
+  EXPECT_EQ(700U, entry.start_ns);
+  EXPECT_EQ(1000U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, calloc_invalid) {
+  // Missing number of elements
+  std::string line = "1236: calloc 0x8000 50";
+  memory_trace::Entry entry;
+  std::string error;
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read calloc data: 1236: calloc 0x8000 50", error);
+
+  // Missing size and number of elements
+  line = "1236: calloc 0x8000";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read calloc data: 1236: calloc 0x8000", error);
+
+  // Missing pointer, size and number of elements
+  line = "1236: calloc";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to process line: 1236: calloc", error);
+
+  // Missing end time
+  line = "1236: calloc 0x8000 50 20 100";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read timestamps: 1236: calloc 0x8000 50 20 100", error);
+}
+
+TEST(MemoryTraceReadTest, realloc_valid) {
+  std::string line = "1237: realloc 0x9000 0x4000 80";
+  memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+  std::string error;
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::REALLOC, entry.type);
+  EXPECT_EQ(1237, entry.tid);
+  EXPECT_EQ(0x9000U, entry.ptr);
+  EXPECT_EQ(80U, entry.size);
+  EXPECT_EQ(0x4000U, entry.u.old_ptr);
+  EXPECT_EQ(0U, entry.start_ns);
+  EXPECT_EQ(0U, entry.end_ns);
+
+  line += " 3999 10020";
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::REALLOC, entry.type);
+  EXPECT_EQ(1237, entry.tid);
+  EXPECT_EQ(0x9000U, entry.ptr);
+  EXPECT_EQ(80U, entry.size);
+  EXPECT_EQ(0x4000U, entry.u.old_ptr);
+  EXPECT_EQ(3999U, entry.start_ns);
+  EXPECT_EQ(10020U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, realloc_invalid) {
+  // Missing size
+  std::string line = "1237: realloc 0x9000 0x4000";
+  memory_trace::Entry entry;
+  std::string error;
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read realloc data: 1237: realloc 0x9000 0x4000", error);
+
+  // Missing size and old pointer
+  line = "1237: realloc 0x9000";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read realloc data: 1237: realloc 0x9000", error);
+
+  // Missing new pointer, size and old pointer
+  line = "1237: realloc";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to process line: 1237: realloc", error);
+
+  // Missing end time
+  line = "1237: realloc 0x9000 0x4000 10 500";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read timestamps: 1237: realloc 0x9000 0x4000 10 500", error);
+}
+
+TEST(MemoryTraceReadTest, memalign_valid) {
+  std::string line = "1238: memalign 0xa000 16 89";
+  memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+  std::string error;
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::MEMALIGN, entry.type);
+  EXPECT_EQ(1238, entry.tid);
+  EXPECT_EQ(0xa000U, entry.ptr);
+  EXPECT_EQ(89U, entry.size);
+  EXPECT_EQ(16U, entry.u.align);
+  EXPECT_EQ(0U, entry.start_ns);
+  EXPECT_EQ(0U, entry.end_ns);
+
+  line += " 900 1000";
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::MEMALIGN, entry.type);
+  EXPECT_EQ(1238, entry.tid);
+  EXPECT_EQ(0xa000U, entry.ptr);
+  EXPECT_EQ(89U, entry.size);
+  EXPECT_EQ(16U, entry.u.align);
+  EXPECT_EQ(900U, entry.start_ns);
+  EXPECT_EQ(1000U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, memalign_invalid) {
+  // Missing size
+  std::string line = "1238: memalign 0xa000 16";
+  memory_trace::Entry entry;
+  std::string error;
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read memalign data: 1238: memalign 0xa000 16", error);
+
+  // Missing alignment and size
+  line = "1238: memalign 0xa000";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read memalign data: 1238: memalign 0xa000", error);
+
+  // Missing pointer, alignment and size
+  line = "1238: memalign";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to process line: 1238: memalign", error);
+
+  // Missing end time
+  line = "1238: memalign 0xa000 16 10 800";
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to read timestamps: 1238: memalign 0xa000 16 10 800", error);
+}
+
+TEST(MemoryTraceReadTest, thread_done_valid) {
+  std::string line = "1239: thread_done 0x0";
+  memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+  std::string error;
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::THREAD_DONE, entry.type);
+  EXPECT_EQ(1239, entry.tid);
+  EXPECT_EQ(0U, entry.ptr);
+  EXPECT_EQ(0U, entry.size);
+  EXPECT_EQ(0U, entry.u.old_ptr);
+  EXPECT_EQ(0U, entry.start_ns);
+  EXPECT_EQ(0U, entry.end_ns);
+
+  line += " 290";
+  ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+  EXPECT_EQ(memory_trace::THREAD_DONE, entry.type);
+  EXPECT_EQ(1239, entry.tid);
+  EXPECT_EQ(0U, entry.ptr);
+  EXPECT_EQ(0U, entry.size);
+  EXPECT_EQ(0U, entry.u.old_ptr);
+  EXPECT_EQ(0U, entry.start_ns);
+  EXPECT_EQ(290U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, thread_done_invalid) {
+  // Missing pointer
+  std::string line = "1240: thread_done";
+  memory_trace::Entry entry;
+  std::string error;
+  EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+  EXPECT_EQ("Failed to process line: 1240: thread_done", error);
+}
+
+class MemoryTraceOutputTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    tmp_file_ = new TemporaryFile();
+    ASSERT_TRUE(tmp_file_->fd != -1);
+  }
+
+  void TearDown() override { delete tmp_file_; }
+
+  void WriteAndReadString(const memory_trace::Entry& entry, std::string& str) {
+    EXPECT_EQ(lseek(tmp_file_->fd, 0, SEEK_SET), 0);
+    EXPECT_TRUE(memory_trace::WriteEntryToFd(tmp_file_->fd, entry));
+    EXPECT_EQ(lseek(tmp_file_->fd, 0, SEEK_SET), 0);
+    EXPECT_TRUE(android::base::ReadFdToString(tmp_file_->fd, &str));
+  }
+
+  std::string WriteAndGetString(const memory_trace::Entry& entry) {
+    std::string str;
+    WriteAndReadString(entry, str);
+    return str;
+  }
+
+  void VerifyEntry(const memory_trace::Entry& entry, const std::string expected) {
+    EXPECT_EQ(expected, memory_trace::CreateStringFromEntry(entry));
+    // The WriteEntryToFd always appends a newline, but string creation doesn't.
+    EXPECT_EQ(expected + "\n", WriteAndGetString(entry));
+  }
+
+  TemporaryFile* tmp_file_ = nullptr;
+};
+
+TEST_F(MemoryTraceOutputTest, malloc_output) {
+  memory_trace::Entry entry{.tid = 123, .type = memory_trace::MALLOC, .ptr = 0x123, .size = 50};
+  VerifyEntry(entry, "123: malloc 0x123 50");
+
+  entry.start_ns = 10;
+  entry.end_ns = 200;
+  VerifyEntry(entry, "123: malloc 0x123 50 10 200");
+}
+
+TEST_F(MemoryTraceOutputTest, calloc_output) {
+  memory_trace::Entry entry{
+      .tid = 123, .type = memory_trace::CALLOC, .ptr = 0x123, .size = 200, .u.n_elements = 400};
+  VerifyEntry(entry, "123: calloc 0x123 400 200");
+
+  entry.start_ns = 15;
+  entry.end_ns = 315;
+  VerifyEntry(entry, "123: calloc 0x123 400 200 15 315");
+}
+
+TEST_F(MemoryTraceOutputTest, memalign_output) {
+  memory_trace::Entry entry{
+      .tid = 123, .type = memory_trace::MEMALIGN, .ptr = 0x123, .size = 1024, .u.align = 0x10};
+  VerifyEntry(entry, "123: memalign 0x123 16 1024");
+
+  entry.start_ns = 23;
+  entry.end_ns = 289;
+  VerifyEntry(entry, "123: memalign 0x123 16 1024 23 289");
+}
+
+TEST_F(MemoryTraceOutputTest, realloc_output) {
+  memory_trace::Entry entry{
+      .tid = 123, .type = memory_trace::REALLOC, .ptr = 0x123, .size = 300, .u.old_ptr = 0x125};
+  VerifyEntry(entry, "123: realloc 0x123 0x125 300");
+
+  entry.start_ns = 45;
+  entry.end_ns = 1000;
+  VerifyEntry(entry, "123: realloc 0x123 0x125 300 45 1000");
+}
+
+TEST_F(MemoryTraceOutputTest, free_output) {
+  memory_trace::Entry entry{.tid = 123, .type = memory_trace::FREE, .ptr = 0x123};
+  VerifyEntry(entry, "123: free 0x123");
+
+  entry.start_ns = 60;
+  entry.end_ns = 2000;
+  VerifyEntry(entry, "123: free 0x123 60 2000");
+}
+
+TEST_F(MemoryTraceOutputTest, thread_done_output) {
+  memory_trace::Entry entry{.tid = 123, .type = memory_trace::THREAD_DONE};
+  VerifyEntry(entry, "123: thread_done 0x0");
+
+  entry.start_ns = 0;
+  entry.end_ns = 2500;
+  VerifyEntry(entry, "123: thread_done 0x0 2500");
+}
diff --git a/memory_replay/tests/ThreadsTest.cpp b/memory_replay/tests/ThreadsTest.cpp
index 990c913..f9516f1 100644
--- a/memory_replay/tests/ThreadsTest.cpp
+++ b/memory_replay/tests/ThreadsTest.cpp
@@ -16,7 +16,8 @@
 
 #include <gtest/gtest.h>
 
-#include "Alloc.h"
+#include <memory_trace/MemoryTrace.h>
+
 #include "Pointers.h"
 #include "Thread.h"
 #include "Threads.h"
@@ -32,8 +33,8 @@
   Thread* found_thread = threads.FindThread(900);
   ASSERT_EQ(thread, found_thread);
 
-  AllocEntry thread_done = {.type = THREAD_DONE};
-  thread->SetAllocEntry(&thread_done);
+  memory_trace::Entry thread_done = {.type = memory_trace::THREAD_DONE};
+  thread->SetEntry(&thread_done);
 
   thread->SetPending();
 
@@ -67,10 +68,10 @@
   Thread* found_thread3 = threads.FindThread(902);
   ASSERT_EQ(thread3, found_thread3);
 
-  AllocEntry thread_done = {.type = THREAD_DONE};
-  thread1->SetAllocEntry(&thread_done);
-  thread2->SetAllocEntry(&thread_done);
-  thread3->SetAllocEntry(&thread_done);
+  memory_trace::Entry thread_done = {.type = memory_trace::THREAD_DONE};
+  thread1->SetEntry(&thread_done);
+  thread2->SetEntry(&thread_done);
+  thread3->SetEntry(&thread_done);
 
   thread1->SetPending();
   threads.Finish(thread1);
@@ -96,25 +97,25 @@
   // If WaitForAllToQuiesce is not correct, then this should provoke an error
   // since we are overwriting the action data while it's being used.
   constexpr size_t kAllocEntries = 512;
-  std::vector<AllocEntry> mallocs(kAllocEntries);
-  std::vector<AllocEntry> frees(kAllocEntries);
+  std::vector<memory_trace::Entry> mallocs(kAllocEntries);
+  std::vector<memory_trace::Entry> frees(kAllocEntries);
   for (size_t i = 0; i < kAllocEntries; i++) {
-    mallocs[i].type = MALLOC;
+    mallocs[i].type = memory_trace::MALLOC;
     mallocs[i].ptr = 0x1234 + i;
     mallocs[i].size = 100;
-    thread->SetAllocEntry(&mallocs[i]);
+    thread->SetEntry(&mallocs[i]);
     thread->SetPending();
     threads.WaitForAllToQuiesce();
 
-    frees[i].type = FREE;
+    frees[i].type = memory_trace::FREE;
     frees[i].ptr = 0x1234 + i;
-    thread->SetAllocEntry(&frees[i]);
+    thread->SetEntry(&frees[i]);
     thread->SetPending();
     threads.WaitForAllToQuiesce();
   }
 
-  AllocEntry thread_done = {.type = THREAD_DONE};
-  thread->SetAllocEntry(&thread_done);
+  memory_trace::Entry thread_done = {.type = memory_trace::THREAD_DONE};
+  thread->SetEntry(&thread_done);
   thread->SetPending();
   threads.Finish(thread);
   ASSERT_EQ(0U, threads.num_threads());
diff --git a/mtectrl/OWNERS b/mtectrl/OWNERS
index 79625df..c95d3cf 100644
--- a/mtectrl/OWNERS
+++ b/mtectrl/OWNERS
@@ -1,5 +1,4 @@
 [email protected]
 
 [email protected]
[email protected]
 [email protected]
diff --git a/profcollectd/libprofcollectd/lib.rs b/profcollectd/libprofcollectd/lib.rs
index 87ce50b..4663e68 100644
--- a/profcollectd/libprofcollectd/lib.rs
+++ b/profcollectd/libprofcollectd/lib.rs
@@ -20,13 +20,8 @@
 mod report;
 mod scheduler;
 mod service;
-mod simpleperf_etm_trace_provider;
-mod simpleperf_lbr_trace_provider;
 mod trace_provider;
 
-#[cfg(feature = "test")]
-mod logging_trace_provider;
-
 use anyhow::{Context, Result};
 use profcollectd_aidl_interface::aidl::com::android::server::profcollect::IProfCollectd::{
     self, BnProfCollectd,
diff --git a/profcollectd/libprofcollectd/trace_provider.rs b/profcollectd/libprofcollectd/trace_provider.rs
index a620743..a57d3ba 100644
--- a/profcollectd/libprofcollectd/trace_provider.rs
+++ b/profcollectd/libprofcollectd/trace_provider.rs
@@ -16,17 +16,23 @@
 
 //! ProfCollect trace provider trait and helper functions.
 
+mod simpleperf_etm;
+mod simpleperf_lbr;
+
+#[cfg(feature = "test")]
+mod logging;
+
 use anyhow::{anyhow, Result};
 use chrono::Utc;
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, Mutex};
 use std::time::Duration;
 
-use crate::simpleperf_etm_trace_provider::SimpleperfEtmTraceProvider;
-use crate::simpleperf_lbr_trace_provider::SimpleperfLbrTraceProvider;
+use simpleperf_etm::SimpleperfEtmTraceProvider;
+use simpleperf_lbr::SimpleperfLbrTraceProvider;
 
 #[cfg(feature = "test")]
-use crate::logging_trace_provider::LoggingTraceProvider;
+use logging::LoggingTraceProvider;
 
 pub trait TraceProvider {
     fn get_name(&self) -> &'static str;
diff --git a/profcollectd/libprofcollectd/logging_trace_provider.rs b/profcollectd/libprofcollectd/trace_provider/logging.rs
similarity index 100%
rename from profcollectd/libprofcollectd/logging_trace_provider.rs
rename to profcollectd/libprofcollectd/trace_provider/logging.rs
diff --git a/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs b/profcollectd/libprofcollectd/trace_provider/simpleperf_etm.rs
similarity index 100%
rename from profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
rename to profcollectd/libprofcollectd/trace_provider/simpleperf_etm.rs
diff --git a/profcollectd/libprofcollectd/simpleperf_lbr_trace_provider.rs b/profcollectd/libprofcollectd/trace_provider/simpleperf_lbr.rs
similarity index 100%
rename from profcollectd/libprofcollectd/simpleperf_lbr_trace_provider.rs
rename to profcollectd/libprofcollectd/trace_provider/simpleperf_lbr.rs
diff --git a/simpleperf/BranchListFile.cpp b/simpleperf/BranchListFile.cpp
index 253dcdb..5e2e4d1 100644
--- a/simpleperf/BranchListFile.cpp
+++ b/simpleperf/BranchListFile.cpp
@@ -17,6 +17,7 @@
 #include "BranchListFile.h"
 
 #include "ETMDecoder.h"
+#include "ZstdUtil.h"
 #include "system/extras/simpleperf/branch_list.pb.h"
 
 namespace simpleperf {
@@ -59,44 +60,11 @@
 }
 
 bool ETMBinaryMapToString(const ETMBinaryMap& binary_map, std::string& s) {
-  proto::BranchList branch_list_proto;
-  branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
-  std::vector<char> branch_buf;
-  for (const auto& p : binary_map) {
-    const BinaryKey& key = p.first;
-    const ETMBinary& binary = p.second;
-    auto binary_proto = branch_list_proto.add_etm_data();
-
-    binary_proto->set_path(key.path);
-    if (!key.build_id.IsEmpty()) {
-      binary_proto->set_build_id(key.build_id.ToString().substr(2));
-    }
-    auto opt_binary_type = ToProtoBinaryType(binary.dso_type);
-    if (!opt_binary_type.has_value()) {
-      return false;
-    }
-    binary_proto->set_type(opt_binary_type.value());
-
-    for (const auto& addr_p : binary.branch_map) {
-      auto addr_proto = binary_proto->add_addrs();
-      addr_proto->set_addr(addr_p.first);
-
-      for (const auto& branch_p : addr_p.second) {
-        const std::vector<bool>& branch = branch_p.first;
-        auto branch_proto = addr_proto->add_branches();
-
-        branch_proto->set_branch(ETMBranchToProtoString(branch));
-        branch_proto->set_branch_size(branch.size());
-        branch_proto->set_count(branch_p.second);
-      }
-    }
-
-    if (binary.dso_type == DSO_KERNEL) {
-      binary_proto->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr);
-    }
+  auto writer = BranchListProtoWriter::CreateForString(&s, false);
+  if (!writer) {
+    return false;
   }
-  if (!branch_list_proto.SerializeToString(&s)) {
-    LOG(ERROR) << "failed to serialize branch list binary map";
+  if (!writer->Write(binary_map)) {
     return false;
   }
   return true;
@@ -116,24 +84,13 @@
   }
 }
 
-static UnorderedETMBranchMap BuildUnorderedETMBranchMap(const proto::ETMBinary& binary_proto) {
-  UnorderedETMBranchMap branch_map;
-  for (size_t i = 0; i < binary_proto.addrs_size(); i++) {
-    const auto& addr_proto = binary_proto.addrs(i);
-    auto& b_map = branch_map[addr_proto.addr()];
-    for (size_t j = 0; j < addr_proto.branches_size(); j++) {
-      const auto& branch_proto = addr_proto.branches(j);
-      std::vector<bool> branch =
-          ProtoStringToETMBranch(branch_proto.branch(), branch_proto.branch_size());
-      b_map[branch] = branch_proto.count();
-    }
-  }
-  return branch_map;
-}
-
 bool StringToETMBinaryMap(const std::string& s, ETMBinaryMap& binary_map) {
   LBRData lbr_data;
-  return ParseBranchListData(s, binary_map, lbr_data);
+  auto reader = BranchListProtoReader::CreateForString(s);
+  if (!reader) {
+    return false;
+  }
+  return reader->Read(binary_map, lbr_data);
 }
 
 class ETMThreadTreeWhenRecording : public ETMThreadTree {
@@ -359,79 +316,415 @@
 ETMBranchListGenerator::~ETMBranchListGenerator() {}
 
 bool LBRDataToString(const LBRData& data, std::string& s) {
-  proto::BranchList branch_list_proto;
-  branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
-  auto lbr_proto = branch_list_proto.mutable_lbr_data();
-  for (const LBRSample& sample : data.samples) {
-    auto sample_proto = lbr_proto->add_samples();
-    sample_proto->set_binary_id(sample.binary_id);
-    sample_proto->set_vaddr_in_file(sample.vaddr_in_file);
-    for (const LBRBranch& branch : sample.branches) {
-      auto branch_proto = sample_proto->add_branches();
-      branch_proto->set_from_binary_id(branch.from_binary_id);
-      branch_proto->set_to_binary_id(branch.to_binary_id);
-      branch_proto->set_from_vaddr_in_file(branch.from_vaddr_in_file);
-      branch_proto->set_to_vaddr_in_file(branch.to_vaddr_in_file);
-    }
+  auto writer = BranchListProtoWriter::CreateForString(&s, false);
+  if (!writer) {
+    return false;
   }
-  for (const BinaryKey& binary : data.binaries) {
-    auto binary_proto = lbr_proto->add_binaries();
-    binary_proto->set_path(binary.path);
-    binary_proto->set_build_id(binary.build_id.ToString().substr(2));
-  }
-  if (!branch_list_proto.SerializeToString(&s)) {
-    LOG(ERROR) << "failed to serialize lbr data";
+  if (!writer->Write(data)) {
     return false;
   }
   return true;
 }
 
-bool ParseBranchListData(const std::string& s, ETMBinaryMap& etm_data, LBRData& lbr_data) {
-  proto::BranchList branch_list_proto;
-  if (!branch_list_proto.ParseFromString(s)) {
+std::unique_ptr<BranchListProtoWriter> BranchListProtoWriter::CreateForFile(
+    const std::string& output_filename, bool compress, size_t max_branches_per_message) {
+  auto writer = std::unique_ptr<BranchListProtoWriter>(
+      new BranchListProtoWriter(output_filename, nullptr, compress, max_branches_per_message));
+  if (!writer->WriteHeader()) {
+    return nullptr;
+  }
+  return writer;
+}
+
+std::unique_ptr<BranchListProtoWriter> BranchListProtoWriter::CreateForString(
+    std::string* output_str, bool compress, size_t max_branches_per_message) {
+  auto writer = std::unique_ptr<BranchListProtoWriter>(
+      new BranchListProtoWriter("", output_str, compress, max_branches_per_message));
+  if (!writer->WriteHeader()) {
+    return nullptr;
+  }
+  return writer;
+}
+
+bool BranchListProtoWriter::Write(const ETMBinaryMap& etm_data) {
+  if (!output_fp_ && !WriteHeader()) {
+    return false;
+  }
+  std::unique_ptr<proto::BranchList> proto_branch_list = std::make_unique<proto::BranchList>();
+  proto::ETMBinary* proto_binary = nullptr;
+  proto::ETMBinary_Address* proto_addr = nullptr;
+  size_t branch_count = 0;
+
+  auto add_proto_binary = [&](const BinaryKey& key, const ETMBinary& binary) {
+    proto_binary = proto_branch_list->add_etm_data();
+    proto_binary->set_path(key.path);
+    if (!key.build_id.IsEmpty()) {
+      proto_binary->set_build_id(key.build_id.ToString().substr(2));
+    }
+    auto opt_binary_type = ToProtoBinaryType(binary.dso_type);
+    if (!opt_binary_type.has_value()) {
+      return false;
+    }
+    proto_binary->set_type(opt_binary_type.value());
+    if (binary.dso_type == DSO_KERNEL) {
+      proto_binary->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr);
+    }
+    return true;
+  };
+
+  auto add_proto_addr = [&](uint64_t addr) {
+    proto_addr = proto_binary->add_addrs();
+    proto_addr->set_addr(addr);
+  };
+
+  for (const auto& [key, binary] : etm_data) {
+    if (!add_proto_binary(key, binary)) {
+      return false;
+    }
+    for (const auto& [addr, branch_map] : binary.branch_map) {
+      add_proto_addr(addr);
+      size_t new_branch_count = 0;
+      for (const auto& [branch, _] : branch_map) {
+        new_branch_count += branch.size();
+      }
+      if (branch_count + new_branch_count > max_branches_per_message_) {
+        if (!WriteProtoBranchList(*proto_branch_list)) {
+          return false;
+        }
+        proto_branch_list.reset(new proto::BranchList);
+        if (!add_proto_binary(key, binary)) {
+          return false;
+        }
+        add_proto_addr(addr);
+        branch_count = 0;
+      }
+      branch_count += new_branch_count;
+      for (const auto& [branch, count] : branch_map) {
+        proto::ETMBinary_Address_Branch* proto_branch = proto_addr->add_branches();
+        proto_branch->set_branch(ETMBranchToProtoString(branch));
+        proto_branch->set_branch_size(branch.size());
+        proto_branch->set_count(count);
+      }
+    }
+  }
+  return WriteProtoBranchList(*proto_branch_list);
+}
+
+bool BranchListProtoWriter::Write(const LBRData& lbr_data) {
+  if (!output_fp_ && !WriteHeader()) {
+    return false;
+  }
+  proto::BranchList proto_branch_list;
+  proto_branch_list.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
+  auto proto_lbr = proto_branch_list.mutable_lbr_data();
+  for (const LBRSample& sample : lbr_data.samples) {
+    auto proto_sample = proto_lbr->add_samples();
+    proto_sample->set_binary_id(sample.binary_id);
+    proto_sample->set_vaddr_in_file(sample.vaddr_in_file);
+    for (const LBRBranch& branch : sample.branches) {
+      auto proto_branch = proto_sample->add_branches();
+      proto_branch->set_from_binary_id(branch.from_binary_id);
+      proto_branch->set_to_binary_id(branch.to_binary_id);
+      proto_branch->set_from_vaddr_in_file(branch.from_vaddr_in_file);
+      proto_branch->set_to_vaddr_in_file(branch.to_vaddr_in_file);
+    }
+  }
+  for (const BinaryKey& binary : lbr_data.binaries) {
+    auto proto_binary = proto_lbr->add_binaries();
+    proto_binary->set_path(binary.path);
+    proto_binary->set_build_id(binary.build_id.ToString().substr(2));
+  }
+  return WriteProtoBranchList(proto_branch_list);
+}
+
+bool BranchListProtoWriter::WriteHeader() {
+  if (!output_filename_.empty()) {
+    output_fp_.reset(fopen(output_filename_.c_str(), "wbe"));
+    if (!output_fp_) {
+      PLOG(ERROR) << "failed to open " << output_filename_;
+      return false;
+    }
+  } else {
+    output_str_->clear();
+  }
+  if (!WriteData(ETM_BRANCH_LIST_PROTO_MAGIC, strlen(ETM_BRANCH_LIST_PROTO_MAGIC))) {
+    return false;
+  }
+  uint32_t version = 1;
+  if (!WriteData(&version, sizeof(version))) {
+    return false;
+  }
+  uint8_t compress = compress_ ? 1 : 0;
+  if (!WriteData(&compress, sizeof(compress))) {
+    return false;
+  }
+  return true;
+}
+
+bool BranchListProtoWriter::WriteProtoBranchList(proto::BranchList& branch_list) {
+  std::string s;
+  if (!branch_list.SerializeToString(&s)) {
+    LOG(ERROR) << "failed to serialize branch list binary map";
+    return false;
+  }
+  if (compress_ && !ZstdCompress(s.data(), s.size(), s)) {
+    return false;
+  }
+  uint32_t msg_size = s.size();
+  return WriteData(&msg_size, sizeof(msg_size)) && WriteData(s.data(), s.size());
+}
+
+bool BranchListProtoWriter::WriteData(const void* data, size_t size) {
+  if (output_fp_) {
+    if (fwrite(data, size, 1, output_fp_.get()) != 1) {
+      LOG(ERROR) << "failed to write to " << output_filename_;
+      return false;
+    }
+  } else {
+    output_str_->insert(output_str_->size(), static_cast<const char*>(data), size);
+  }
+  return true;
+}
+
+std::unique_ptr<BranchListProtoReader> BranchListProtoReader::CreateForFile(
+    const std::string& input_filename) {
+  return std::unique_ptr<BranchListProtoReader>(new BranchListProtoReader(input_filename, ""));
+}
+
+std::unique_ptr<BranchListProtoReader> BranchListProtoReader::CreateForString(
+    const std::string& input_str) {
+  return std::unique_ptr<BranchListProtoReader>(new BranchListProtoReader("", input_str));
+}
+
+bool BranchListProtoReader::Read(ETMBinaryMap& etm_data, LBRData& lbr_data) {
+  if (!input_filename_.empty()) {
+    input_fp_.reset(fopen(input_filename_.c_str(), "rbe"));
+    if (!input_fp_) {
+      PLOG(ERROR) << "failed to open " << input_filename_;
+      return false;
+    }
+  }
+  char magic[24];
+  if (!ReadData(magic, sizeof(magic)) ||
+      memcmp(magic, ETM_BRANCH_LIST_PROTO_MAGIC, sizeof(magic)) != 0) {
+    return ReadOldFileFormat(etm_data, lbr_data);
+  }
+  uint32_t version;
+  if (!ReadData(&version, sizeof(version)) && version != 1) {
+    LOG(ERROR) << "unsupported version in " << input_filename_;
+    return false;
+  }
+  uint8_t compress;
+  if (!ReadData(&compress, sizeof(compress))) {
+    return false;
+  }
+  compress_ = compress == 1;
+  long file_offset = ftell(input_fp_.get());
+  if (file_offset == -1) {
+    PLOG(ERROR) << "failed to call ftell";
+    return false;
+  }
+  uint64_t file_size = GetFileSize(input_filename_);
+  while (file_offset < file_size) {
+    uint32_t msg_size;
+    if (!ReadData(&msg_size, sizeof(msg_size))) {
+      return false;
+    }
+    proto::BranchList proto_branch_list;
+    if (!ReadProtoBranchList(msg_size, proto_branch_list)) {
+      return false;
+    }
+    for (size_t i = 0; i < proto_branch_list.etm_data_size(); i++) {
+      const proto::ETMBinary& proto_binary = proto_branch_list.etm_data(i);
+      if (!AddETMBinary(proto_binary, etm_data)) {
+        return false;
+      }
+    }
+    if (proto_branch_list.has_lbr_data()) {
+      AddLBRData(proto_branch_list.lbr_data(), lbr_data);
+    }
+    file_offset += 4 + msg_size;
+  }
+  return true;
+}
+
+bool BranchListProtoReader::AddETMBinary(const proto::ETMBinary& proto_binary,
+                                         ETMBinaryMap& etm_data) {
+  BinaryKey key(proto_binary.path(), BuildId(proto_binary.build_id()));
+  if (proto_binary.has_kernel_info()) {
+    key.kernel_start_addr = proto_binary.kernel_info().kernel_start_addr();
+  }
+  ETMBinary& binary = etm_data[key];
+  auto dso_type = ToDsoType(proto_binary.type());
+  if (!dso_type) {
+    LOG(ERROR) << "invalid binary type " << proto_binary.type();
+    return false;
+  }
+  binary.dso_type = dso_type.value();
+  auto& branch_map = binary.branch_map;
+  for (size_t i = 0; i < proto_binary.addrs_size(); i++) {
+    const auto& proto_addr = proto_binary.addrs(i);
+    auto& b_map = branch_map[proto_addr.addr()];
+    for (size_t j = 0; j < proto_addr.branches_size(); j++) {
+      const auto& proto_branch = proto_addr.branches(j);
+      std::vector<bool> branch =
+          ProtoStringToETMBranch(proto_branch.branch(), proto_branch.branch_size());
+      b_map[branch] = proto_branch.count();
+    }
+  }
+  return true;
+}
+
+void BranchListProtoReader::AddLBRData(const proto::LBRData& proto_lbr_data, LBRData& lbr_data) {
+  for (size_t i = 0; i < proto_lbr_data.samples_size(); ++i) {
+    const auto& proto_sample = proto_lbr_data.samples(i);
+    lbr_data.samples.resize(lbr_data.samples.size() + 1);
+    LBRSample& sample = lbr_data.samples.back();
+    sample.binary_id = proto_sample.binary_id();
+    sample.vaddr_in_file = proto_sample.vaddr_in_file();
+    sample.branches.resize(proto_sample.branches_size());
+    for (size_t j = 0; j < proto_sample.branches_size(); ++j) {
+      const auto& proto_branch = proto_sample.branches(j);
+      LBRBranch& branch = sample.branches[j];
+      branch.from_binary_id = proto_branch.from_binary_id();
+      branch.to_binary_id = proto_branch.to_binary_id();
+      branch.from_vaddr_in_file = proto_branch.from_vaddr_in_file();
+      branch.to_vaddr_in_file = proto_branch.to_vaddr_in_file();
+    }
+  }
+  for (size_t i = 0; i < proto_lbr_data.binaries_size(); ++i) {
+    const auto& proto_binary = proto_lbr_data.binaries(i);
+    lbr_data.binaries.emplace_back(proto_binary.path(), BuildId(proto_binary.build_id()));
+  }
+}
+
+bool BranchListProtoReader::ReadProtoBranchList(uint32_t size,
+                                                proto::BranchList& proto_branch_list) {
+  std::string s;
+  s.resize(size);
+  if (!ReadData(s.data(), size)) {
+    return false;
+  }
+  if (compress_ && !ZstdDecompress(s.data(), s.size(), s)) {
+    return false;
+  }
+  if (!proto_branch_list.ParseFromString(s)) {
     PLOG(ERROR) << "failed to read ETMBranchList msg";
     return false;
   }
-  if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
-    PLOG(ERROR) << "not in etm branch list format in branch_list.proto";
-    return false;
-  }
-  for (size_t i = 0; i < branch_list_proto.etm_data_size(); i++) {
-    const auto& binary_proto = branch_list_proto.etm_data(i);
-    BinaryKey key(binary_proto.path(), BuildId(binary_proto.build_id()));
-    if (binary_proto.has_kernel_info()) {
-      key.kernel_start_addr = binary_proto.kernel_info().kernel_start_addr();
-    }
-    ETMBinary& binary = etm_data[key];
-    auto dso_type = ToDsoType(binary_proto.type());
-    if (!dso_type) {
-      LOG(ERROR) << "invalid binary type " << binary_proto.type();
+  return true;
+}
+
+bool BranchListProtoReader::ReadData(void* data, size_t size) {
+  if (input_fp_) {
+    if (fread(data, size, 1, input_fp_.get()) != 1) {
+      PLOG(ERROR) << "failed to read " << input_filename_;
       return false;
     }
-    binary.dso_type = dso_type.value();
-    binary.branch_map = BuildUnorderedETMBranchMap(binary_proto);
+  } else {
+    if (input_str_pos_ + size > input_str_.size()) {
+      LOG(ERROR) << "failed to read BranchList from string";
+      return false;
+    }
+    memcpy(data, &input_str_[input_str_pos_], size);
+    input_str_pos_ += size;
   }
-  if (branch_list_proto.has_lbr_data()) {
-    const auto& lbr_data_proto = branch_list_proto.lbr_data();
-    lbr_data.samples.resize(lbr_data_proto.samples_size());
-    for (size_t i = 0; i < lbr_data_proto.samples_size(); ++i) {
-      const auto& sample_proto = lbr_data_proto.samples(i);
-      LBRSample& sample = lbr_data.samples[i];
-      sample.binary_id = sample_proto.binary_id();
-      sample.vaddr_in_file = sample_proto.vaddr_in_file();
-      sample.branches.resize(sample_proto.branches_size());
-      for (size_t j = 0; j < sample_proto.branches_size(); ++j) {
-        const auto& branch_proto = sample_proto.branches(j);
-        LBRBranch& branch = sample.branches[j];
-        branch.from_binary_id = branch_proto.from_binary_id();
-        branch.to_binary_id = branch_proto.to_binary_id();
-        branch.from_vaddr_in_file = branch_proto.from_vaddr_in_file();
-        branch.to_vaddr_in_file = branch_proto.to_vaddr_in_file();
+  return true;
+}
+
+bool BranchListProtoReader::ReadOldFileFormat(ETMBinaryMap& etm_data, LBRData& lbr_data) {
+  size_t size = 0;
+  if (!input_filename_.empty()) {
+    size = static_cast<size_t>(GetFileSize(input_filename_));
+    if (android::base::EndsWith(input_filename_, ".zst")) {
+      compress_ = true;
+    }
+  } else {
+    size = input_str_.size();
+  }
+  proto::BranchList proto_branch_list;
+  if (!ReadProtoBranchList(size, proto_branch_list)) {
+    return false;
+  }
+  if (proto_branch_list.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
+    PLOG(ERROR) << "not in format of branch_list.proto";
+  }
+  for (size_t i = 0; i < proto_branch_list.etm_data_size(); i++) {
+    const proto::ETMBinary& proto_binary = proto_branch_list.etm_data(i);
+    if (!AddETMBinary(proto_binary, etm_data)) {
+      return false;
+    }
+  }
+  if (proto_branch_list.has_lbr_data()) {
+    AddLBRData(proto_branch_list.lbr_data(), lbr_data);
+  }
+  return true;
+}
+
+bool DumpBranchListFile(std::string filename) {
+  ETMBinaryMap etm_data;
+  LBRData lbr_data;
+  auto reader = BranchListProtoReader::CreateForFile(filename);
+  if (!reader || !reader->Read(etm_data, lbr_data)) {
+    return false;
+  }
+
+  if (!etm_data.empty()) {
+    std::vector<BinaryKey> sorted_keys;
+    for (const auto& [key, _] : etm_data) {
+      sorted_keys.emplace_back(key);
+    }
+    std::sort(sorted_keys.begin(), sorted_keys.end(),
+              [](const BinaryKey& key1, const BinaryKey& key2) { return key1.path < key2.path; });
+    PrintIndented(0, "etm_data:\n");
+    for (size_t i = 0; i < sorted_keys.size(); ++i) {
+      const auto& key = sorted_keys[i];
+      const auto& binary = etm_data[key];
+      PrintIndented(1, "binary[%zu].path: %s\n", i, key.path.c_str());
+      PrintIndented(1, "binary[%zu].build_id: %s\n", i, key.build_id.ToString().c_str());
+      PrintIndented(1, "binary[%zu].binary_type: %s\n", i, DsoTypeToString(binary.dso_type));
+      if (binary.dso_type == DSO_KERNEL) {
+        PrintIndented(1, "binary[%zu].kernel_start_addr: 0x%" PRIx64 "\n", i,
+                      key.kernel_start_addr);
+      }
+      PrintIndented(1, "binary[%zu].addrs:\n", i);
+      size_t addr_id = 0;
+      for (const auto& [addr, branches] : binary.GetOrderedBranchMap()) {
+        PrintIndented(2, "addr[%zu]: 0x%" PRIx64 "\n", addr_id++, addr);
+        size_t branch_id = 0;
+        for (const auto& [branch, count] : branches) {
+          std::string s = "0b";
+          for (auto it = branch.rbegin(); it != branch.rend(); ++it) {
+            s.push_back(*it ? '1' : '0');
+          }
+          PrintIndented(3, "branch[%zu].branch: %s\n", branch_id, s.c_str());
+          PrintIndented(3, "branch[%zu].count: %" PRIu64 "\n", branch_id, count);
+          ++branch_id;
+        }
       }
     }
-    for (size_t i = 0; i < lbr_data_proto.binaries_size(); ++i) {
-      const auto& binary_proto = lbr_data_proto.binaries(i);
-      lbr_data.binaries.emplace_back(binary_proto.path(), BuildId(binary_proto.build_id()));
+  }
+  if (!lbr_data.samples.empty()) {
+    PrintIndented(0, "lbr_data:\n");
+    for (size_t i = 0; i < lbr_data.samples.size(); ++i) {
+      const auto& sample = lbr_data.samples[i];
+      PrintIndented(1, "sample[%zu].binary_id: %u\n", i, sample.binary_id);
+      PrintIndented(1, "sample[%zu].vaddr_in_file: 0x%" PRIx64 "\n", i, sample.vaddr_in_file);
+      PrintIndented(1, "sample[%zu].branches:\n", i);
+      for (size_t j = 0; j < sample.branches.size(); ++j) {
+        const auto& branch = sample.branches[j];
+        PrintIndented(2, "branch[%zu].from_binary_id: %u\n", j, branch.from_binary_id);
+        PrintIndented(2, "branch[%zu].from_vaddr_in_file: 0x%" PRIx64 "\n", j,
+                      branch.from_vaddr_in_file);
+        PrintIndented(2, "branch[%zu].to_binary_id: %u\n", j, branch.to_binary_id);
+        PrintIndented(2, "branch[%zu].to_vaddr_in_file: 0x%" PRIx64 "\n", j,
+                      branch.to_vaddr_in_file);
+      }
+    }
+    for (size_t i = 0; i < lbr_data.binaries.size(); ++i) {
+      const auto& binary = lbr_data.binaries[i];
+      PrintIndented(1, "binary[%zu].path: %s\n", i, binary.path.c_str());
+      PrintIndented(1, "binary[%zu].build_id: %s\n", i, binary.build_id.ToString().c_str());
     }
   }
   return true;
diff --git a/simpleperf/BranchListFile.h b/simpleperf/BranchListFile.h
index 7200908..2b9c07c 100644
--- a/simpleperf/BranchListFile.h
+++ b/simpleperf/BranchListFile.h
@@ -170,7 +170,72 @@
 };
 
 bool LBRDataToString(const LBRData& data, std::string& s);
-bool ParseBranchListData(const std::string& s, ETMBinaryMap& etm_data, LBRData& lbr_data);
+
+namespace proto {
+class BranchList;
+class ETMBinary;
+class LBRData;
+}  // namespace proto
+
+class BranchListProtoWriter {
+ private:
+  // This value is choosen to prevent exceeding the 2GB size limit for a protobuf message.
+  static constexpr size_t kMaxBranchesPerMessage = 100000000;
+
+ public:
+  static std::unique_ptr<BranchListProtoWriter> CreateForFile(
+      const std::string& output_filename, bool compress,
+      size_t max_branches_per_message = kMaxBranchesPerMessage);
+  static std::unique_ptr<BranchListProtoWriter> CreateForString(
+      std::string* output_str, bool compress,
+      size_t max_branches_per_message = kMaxBranchesPerMessage);
+
+  bool Write(const ETMBinaryMap& etm_data);
+  bool Write(const LBRData& lbr_data);
+
+ private:
+  BranchListProtoWriter(const std::string& output_filename, std::string* output_str, bool compress,
+                        size_t max_branches_per_message)
+      : output_filename_(output_filename),
+        compress_(compress),
+        max_branches_per_message_(max_branches_per_message),
+        output_fp_(nullptr, fclose),
+        output_str_(output_str) {}
+
+  bool WriteHeader();
+  bool WriteProtoBranchList(proto::BranchList& branch_list);
+  bool WriteData(const void* data, size_t size);
+
+  const std::string output_filename_;
+  const bool compress_;
+  const size_t max_branches_per_message_;
+  std::unique_ptr<FILE, decltype(&fclose)> output_fp_;
+  std::string* output_str_;
+};
+
+class BranchListProtoReader {
+ public:
+  static std::unique_ptr<BranchListProtoReader> CreateForFile(const std::string& input_filename);
+  static std::unique_ptr<BranchListProtoReader> CreateForString(const std::string& input_str);
+  bool Read(ETMBinaryMap& etm_data, LBRData& lbr_data);
+
+ private:
+  BranchListProtoReader(const std::string& input_filename, const std::string& input_str)
+      : input_filename_(input_filename), input_fp_(nullptr, fclose), input_str_(input_str) {}
+  bool ReadProtoBranchList(uint32_t size, proto::BranchList& proto_branch_list);
+  bool AddETMBinary(const proto::ETMBinary& proto_binary, ETMBinaryMap& etm_data);
+  void AddLBRData(const proto::LBRData& proto_lbr_data, LBRData& lbr_data);
+  bool ReadData(void* data, size_t size);
+  bool ReadOldFileFormat(ETMBinaryMap& etm_data, LBRData& lbr_data);
+
+  const std::string input_filename_;
+  std::unique_ptr<FILE, decltype(&fclose)> input_fp_;
+  const std::string& input_str_;
+  size_t input_str_pos_ = 0;
+  bool compress_ = false;
+};
+
+bool DumpBranchListFile(std::string filename);
 
 // for testing
 std::string ETMBranchToProtoString(const std::vector<bool>& branch);
diff --git a/simpleperf/BranchListFile_test.cpp b/simpleperf/BranchListFile_test.cpp
index d7f9a6a..af278a0 100644
--- a/simpleperf/BranchListFile_test.cpp
+++ b/simpleperf/BranchListFile_test.cpp
@@ -34,3 +34,118 @@
     ASSERT_EQ(branch, branch2);
   }
 }
+
+static bool IsETMDataEqual(ETMBinaryMap& data1, ETMBinaryMap& data2) {
+  if (data1.size() != data2.size()) {
+    return false;
+  }
+  for (const auto& [key, binary1] : data1) {
+    auto it = data2.find(key);
+    if (it == data2.end()) {
+      return false;
+    }
+    ETMBinary& binary2 = it->second;
+    if (binary1.dso_type != binary2.dso_type) {
+      return false;
+    }
+    const UnorderedETMBranchMap& branch_map1 = binary1.branch_map;
+    const UnorderedETMBranchMap& branch_map2 = binary2.branch_map;
+    if (branch_map1.size() != branch_map2.size()) {
+      return false;
+    }
+    for (const auto& [addr, b_map1] : branch_map1) {
+      auto it2 = branch_map2.find(addr);
+      if (it2 == branch_map2.end()) {
+        return false;
+      }
+      const auto& b_map2 = it2->second;
+      if (b_map1.size() != b_map2.size()) {
+        return false;
+      }
+      for (const auto& [branch, count1] : b_map1) {
+        auto it3 = b_map2.find(branch);
+        if (it3 == b_map2.end()) {
+          return false;
+        }
+        if (count1 != it3->second) {
+          return false;
+        }
+      }
+    }
+  }
+  return true;
+}
+
+static bool IsLBRDataEqual(const LBRData& data1, const LBRData& data2) {
+  if (data1.samples.size() != data2.samples.size()) {
+    return false;
+  }
+  for (size_t i = 0; i < data1.samples.size(); i++) {
+    const LBRSample& sample1 = data1.samples[i];
+    const LBRSample& sample2 = data2.samples[i];
+    if (sample1.binary_id != sample2.binary_id) {
+      return false;
+    }
+    if (sample1.vaddr_in_file != sample2.vaddr_in_file) {
+      return false;
+    }
+    if (sample1.branches.size() != sample2.branches.size()) {
+      return false;
+    }
+    for (size_t j = 0; j < sample1.branches.size(); j++) {
+      const LBRBranch& b1 = sample1.branches[j];
+      const LBRBranch& b2 = sample2.branches[j];
+      if (b1.from_binary_id != b2.from_binary_id || b1.to_binary_id != b2.to_binary_id ||
+          b1.from_vaddr_in_file != b2.from_vaddr_in_file ||
+          b1.to_vaddr_in_file != b2.to_vaddr_in_file) {
+        return false;
+      }
+    }
+  }
+  return data1.binaries == data2.binaries;
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(BranchListProtoReaderWriter, smoke) {
+  ETMBinaryMap etm_data;
+  ETMBinary& binary = etm_data[BinaryKey("fake_binary", BuildId())];
+  binary.dso_type = DSO_ELF_FILE;
+  UnorderedETMBranchMap& branch_map = binary.branch_map;
+  for (size_t addr = 0; addr <= 1024; addr++) {
+    auto& b_map = branch_map[addr];
+    std::vector<bool> branch1 = {true};
+    b_map[branch1] = 1;
+    std::vector<bool> branch2 = {true, false};
+    b_map[branch2] = 2;
+  }
+  LBRData lbr_data;
+  lbr_data.binaries.emplace_back(BinaryKey("binary1", BuildId()));
+  lbr_data.binaries.emplace_back(BinaryKey("binary2", BuildId()));
+  for (uint64_t from_addr = 0; from_addr <= 10; from_addr++) {
+    for (uint64_t to_addr = 100; to_addr <= 110; to_addr++) {
+      LBRBranch branch = {0, 1, from_addr, to_addr};
+      LBRSample sample = {0, from_addr, {branch}};
+      lbr_data.samples.emplace_back(sample);
+    }
+  }
+
+  TemporaryFile tmpfile;
+  close(tmpfile.fd);
+  for (size_t max_branches_per_message : {100, 100000000}) {
+    for (bool compress : {false, true}) {
+      auto writer =
+          BranchListProtoWriter::CreateForFile(tmpfile.path, compress, max_branches_per_message);
+      ASSERT_TRUE(writer);
+      ASSERT_TRUE(writer->Write(etm_data));
+      ASSERT_TRUE(writer->Write(lbr_data));
+      writer = nullptr;
+      auto reader = BranchListProtoReader::CreateForFile(tmpfile.path);
+      ASSERT_TRUE(reader);
+      ETMBinaryMap new_etm_data;
+      LBRData new_lbr_data;
+      ASSERT_TRUE(reader->Read(new_etm_data, new_lbr_data));
+      ASSERT_TRUE(IsETMDataEqual(etm_data, new_etm_data));
+      ASSERT_TRUE(IsLBRDataEqual(lbr_data, new_lbr_data));
+    }
+  }
+}
diff --git a/simpleperf/branch_list.proto b/simpleperf/branch_list.proto
index 6d6bfb1..706f411 100644
--- a/simpleperf/branch_list.proto
+++ b/simpleperf/branch_list.proto
@@ -14,8 +14,18 @@
  * limitations under the License.
  */
 
-// The branch list file format is generated by the inject command. It contains
-// a single BranchList message.
+// The branch list file format is generated by the inject command. The new format is:
+// struct BranchListFile {
+//   char magic[24] = "simpleperf:EtmBranchList";
+//   uint32 version = 1;
+//   uint8 compressed;  // 1 if compressed, otherwise 0
+//   struct {
+//      uint32 msg_size;
+//      message BranchList msg;
+//   } msgs[];
+// };
+// The old format is a single BranchList message.
+//
 
 syntax = "proto3";
 
diff --git a/simpleperf/cmd_inject.cpp b/simpleperf/cmd_inject.cpp
index 181b4ae..380b646 100644
--- a/simpleperf/cmd_inject.cpp
+++ b/simpleperf/cmd_inject.cpp
@@ -221,14 +221,17 @@
 
 class ETMThreadTreeWithFilter : public ETMThreadTree {
  public:
-  ETMThreadTreeWithFilter(ThreadTree& thread_tree, std::optional<int>& exclude_pid)
-      : thread_tree_(thread_tree), exclude_pid_(exclude_pid) {}
+  ETMThreadTreeWithFilter(ThreadTree& thread_tree, std::optional<int>& exclude_pid,
+                          const std::vector<std::unique_ptr<RegEx>>& exclude_process_names)
+      : thread_tree_(thread_tree),
+        exclude_pid_(exclude_pid),
+        exclude_process_names_(exclude_process_names) {}
 
   void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
 
   const ThreadEntry* FindThread(int tid) override {
     const ThreadEntry* thread = thread_tree_.FindThread(tid);
-    if (thread != nullptr && exclude_pid_ && thread->pid == exclude_pid_) {
+    if (thread != nullptr && ShouldExcludePid(thread->pid)) {
       return nullptr;
     }
     return thread;
@@ -237,18 +240,37 @@
   const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
 
  private:
+  bool ShouldExcludePid(int pid) {
+    if (exclude_pid_ && pid == exclude_pid_) {
+      return true;
+    }
+    if (!exclude_process_names_.empty()) {
+      const ThreadEntry* process = thread_tree_.FindThread(pid);
+      if (process != nullptr) {
+        for (const auto& regex : exclude_process_names_) {
+          if (regex->Search(process->comm)) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
   ThreadTree& thread_tree_;
   std::optional<int>& exclude_pid_;
+  const std::vector<std::unique_ptr<RegEx>>& exclude_process_names_;
 };
 
 // Read perf.data with ETM data and generate AutoFDO or branch list data.
 class ETMPerfDataReader : public PerfDataReader {
  public:
   ETMPerfDataReader(std::unique_ptr<RecordFileReader> reader, bool exclude_perf,
+                    const std::vector<std::unique_ptr<RegEx>>& exclude_process_names,
                     const RegEx* binary_name_regex, ETMDumpOption etm_dump_option)
       : PerfDataReader(std::move(reader), exclude_perf, binary_name_regex),
         etm_dump_option_(etm_dump_option),
-        etm_thread_tree_(thread_tree_, exclude_pid_) {}
+        etm_thread_tree_(thread_tree_, exclude_pid_, exclude_process_names) {}
 
   bool Read() override {
     if (reader_->HasFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST)) {
@@ -522,20 +544,13 @@
   void AddCallback(const LBRDataCallback& callback) { lbr_data_callback_ = callback; }
 
   bool Read() {
-    std::string s;
-    if (!android::base::ReadFileToString(filename_, &s)) {
-      PLOG(ERROR) << "failed to read " << filename_;
+    auto reader = BranchListProtoReader::CreateForFile(filename_);
+    if (!reader) {
       return false;
     }
-    if (android::base::EndsWith(filename_, ".zst")) {
-      if (!ZstdDecompress(s.data(), s.size(), s)) {
-        return false;
-      }
-    }
     ETMBinaryMap etm_data;
     LBRData lbr_data;
-    if (!ParseBranchListData(s, etm_data, lbr_data)) {
-      PLOG(ERROR) << "file is in wrong format: " << filename_;
+    if (!reader->Read(etm_data, lbr_data)) {
       return false;
     }
     if (etm_binary_callback_ && !etm_data.empty()) {
@@ -985,28 +1000,19 @@
 // Write branch lists to a protobuf file specified by branch_list.proto.
 static bool WriteBranchListFile(const std::string& output_filename, const ETMBinaryMap& etm_data,
                                 const LBRData& lbr_data, bool compress) {
-  std::string s;
+  auto writer = BranchListProtoWriter::CreateForFile(output_filename, compress);
+  if (!writer) {
+    return false;
+  }
   if (!etm_data.empty()) {
-    if (!ETMBinaryMapToString(etm_data, s)) {
-      return false;
-    }
-  } else if (!lbr_data.samples.empty()) {
-    if (!LBRDataToString(lbr_data, s)) {
-      return false;
-    }
-  } else {
-    // Don't produce empty output file.
-    LOG(INFO) << "Skip empty output file.";
-    unlink(output_filename.c_str());
-    return true;
+    return writer->Write(etm_data);
   }
-  if (compress && !ZstdCompress(s.data(), s.size(), s)) {
-    return false;
+  if (!lbr_data.samples.empty()) {
+    return writer->Write(lbr_data);
   }
-  if (!android::base::WriteStringToFile(s, output_filename)) {
-    PLOG(ERROR) << "failed to write to " << output_filename;
-    return false;
-  }
+  // Don't produce empty output file.
+  LOG(INFO) << "Skip empty output file.";
+  unlink(output_filename.c_str());
   return true;
 }
 
@@ -1030,10 +1036,13 @@
 "                             Default is autofdo.\n"
 "--dump-etm type1,type2,...   Dump etm data. A type is one of raw, packet and element.\n"
 "--exclude-perf               Exclude trace data for the recording process.\n"
+"--exclude-process-name process_name_regex      Exclude data for processes with name containing\n"
+"                                               the regular expression.\n"
 "--symdir <dir>               Look for binaries in a directory recursively.\n"
 "--allow-mismatched-build-id  Allow mismatched build ids when searching for debug binaries.\n"
 "-j <jobs>                    Use multiple threads to process branch list files.\n"
 "-z                           Compress branch-list output\n"
+"--dump <file>                Dump a branch list file.\n"
 "\n"
 "Examples:\n"
 "1. Generate autofdo text output.\n"
@@ -1050,6 +1059,9 @@
     if (!ParseOptions(args)) {
       return false;
     }
+    if (!dump_branch_list_file_.empty()) {
+      return DumpBranchListFile(dump_branch_list_file_);
+    }
 
     CHECK(!input_filenames_.empty());
     if (IsPerfDataFile(input_filenames_[0])) {
@@ -1078,8 +1090,10 @@
     const OptionFormatMap option_formats = {
         {"--allow-mismatched-build-id", {OptionValueType::NONE, OptionType::SINGLE}},
         {"--binary", {OptionValueType::STRING, OptionType::SINGLE}},
+        {"--dump", {OptionValueType::STRING, OptionType::SINGLE}},
         {"--dump-etm", {OptionValueType::STRING, OptionType::SINGLE}},
         {"--exclude-perf", {OptionValueType::NONE, OptionType::SINGLE}},
+        {"--exclude-process-name", {OptionValueType::STRING, OptionType::MULTIPLE}},
         {"-i", {OptionValueType::STRING, OptionType::MULTIPLE}},
         {"-j", {OptionValueType::UINT, OptionType::SINGLE}},
         {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
@@ -1103,12 +1117,20 @@
         return false;
       }
     }
+    options.PullStringValue("--dump", &dump_branch_list_file_);
     if (auto value = options.PullValue("--dump-etm"); value) {
       if (!ParseEtmDumpOption(value->str_value, &etm_dump_option_)) {
         return false;
       }
     }
     exclude_perf_ = options.PullBoolValue("--exclude-perf");
+    for (const std::string& value : options.PullStringValues("--exclude-process-name")) {
+      std::unique_ptr<RegEx> regex = RegEx::Create(value);
+      if (regex == nullptr) {
+        return false;
+      }
+      exclude_process_names_.emplace_back(std::move(regex));
+    }
 
     for (const OptionValue& value : options.PullValues("-i")) {
       std::vector<std::string> files = android::base::Split(value.str_value, ",");
@@ -1155,11 +1177,6 @@
     }
     compress_ = options.PullBoolValue("-z");
     CHECK(options.values.empty());
-
-    if (compress_ && !android::base::EndsWith(output_filename_, ".zst")) {
-      LOG(ERROR) << "When -z is used, output filename should has a .zst suffix";
-      return false;
-    }
     return true;
   }
 
@@ -1195,7 +1212,8 @@
       std::unique_ptr<PerfDataReader> reader;
       if (data_type == "etm") {
         reader.reset(new ETMPerfDataReader(std::move(file_reader), exclude_perf_,
-                                           binary_name_regex_.get(), etm_dump_option_));
+                                           exclude_process_names_, binary_name_regex_.get(),
+                                           etm_dump_option_));
       } else if (data_type == "lbr") {
         reader.reset(
             new LBRPerfDataReader(std::move(file_reader), exclude_perf_, binary_name_regex_.get()));
@@ -1306,6 +1324,7 @@
 
   std::unique_ptr<RegEx> binary_name_regex_;
   bool exclude_perf_ = false;
+  std::vector<std::unique_ptr<RegEx>> exclude_process_names_;
   std::vector<std::string> input_filenames_;
   std::string output_filename_ = "perf_inject.data";
   OutputFormat output_format_ = OutputFormat::AutoFDO;
@@ -1313,6 +1332,7 @@
   bool compress_ = false;
   bool allow_mismatched_build_id_ = false;
   size_t jobs_ = 1;
+  std::string dump_branch_list_file_;
 
   std::unique_ptr<Dso> placeholder_dso_;
 };
diff --git a/simpleperf/cmd_inject_test.cpp b/simpleperf/cmd_inject_test.cpp
index 4fc84fc..8ecdec2 100644
--- a/simpleperf/cmd_inject_test.cpp
+++ b/simpleperf/cmd_inject_test.cpp
@@ -111,13 +111,10 @@
 TEST(cmd_inject, compress_option) {
   TemporaryFile tmpfile;
   close(tmpfile.release());
-  ASSERT_FALSE(RunInjectCmd({"--output", "branch-list", "-z", "-o", tmpfile.path}));
-  std::string tmp_zstd_path = std::string(tmpfile.path) + ".zst";
-  ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-z", "-o", tmp_zstd_path}));
+  ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-z", "-o", tmpfile.path}));
   std::string autofdo_data;
-  ASSERT_TRUE(RunInjectCmd({"-i", tmp_zstd_path.c_str(), "--output", "autofdo"}, &autofdo_data));
+  ASSERT_TRUE(RunInjectCmd({"-i", tmpfile.path, "--output", "autofdo"}, &autofdo_data));
   CheckMatchingExpectedData("perf_inject.data", autofdo_data);
-  unlink(tmp_zstd_path.c_str());
 }
 
 // @CddTest = 6.1/C-0-2
@@ -340,3 +337,36 @@
       {"-i", std::string(tmpfile.path) + "," + tmpfile.path, "--output", "autofdo", "-j", "0"},
       &autofdo_data));
 }
+
+// @CddTest = 6.1/C-0-2
+TEST(cmd_inject, dump_option) {
+  TemporaryFile tmpfile;
+  close(tmpfile.release());
+  ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-o", tmpfile.path}));
+
+  CaptureStdout capture;
+  ASSERT_TRUE(capture.Start());
+  ASSERT_TRUE(InjectCmd()->Run({"--dump", tmpfile.path}));
+  std::string data = capture.Finish();
+  ASSERT_NE(data.find("binary[0].build_id: 0x0c9a20bf9c009d0e4e8bbf9fad0300ae00000000"),
+            std::string::npos);
+
+  ASSERT_TRUE(RunInjectCmd(
+      {"--output", "branch-list", "-o", tmpfile.path, "-i", GetTestData("lbr/perf_lbr.data")}));
+
+  ASSERT_TRUE(capture.Start());
+  ASSERT_TRUE(InjectCmd()->Run({"--dump", tmpfile.path}));
+  data = capture.Finish();
+  ASSERT_NE(data.find("binary[0].path: /home/yabinc/lbr_test_loop"), std::string::npos);
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(cmd_inject, exclude_process_name_option) {
+  TemporaryFile tmpfile;
+  close(tmpfile.release());
+  ASSERT_TRUE(RunInjectCmd(
+      {"--output", "branch-list", "--exclude-process-name", "etm_test_loop", "-o", tmpfile.path}));
+  struct stat st;
+  ASSERT_EQ(stat(tmpfile.path, &st), -1);
+  ASSERT_EQ(errno, ENOENT);
+}
diff --git a/simpleperf/doc/README.md b/simpleperf/doc/README.md
index 2efa323..b4f991b 100644
--- a/simpleperf/doc/README.md
+++ b/simpleperf/doc/README.md
@@ -263,6 +263,7 @@
    2) Generate binary_cache, containing elf files with debug information. Use -lib option to add
      libs with debug info. Do it with
      `binary_cache_builder.py -i perf.data -lib <dir_of_lib_with_debug_info>`.
+     For Android platform, we can add debug binaries as in [Android platform profiling](android_platform_profiling.md#general-tips).
    3) Use report_html.py to generate report.html with annotated source code and disassembly,
      as described [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/doc/scripts_reference.md#report_html_py).
 
@@ -271,6 +272,11 @@
    2) Use pprof_proto_generator.py to generate pprof proto file. `pprof_proto_generator.py`.
    3) Use pprof to report a function with annotated source code, as described [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/doc/scripts_reference.md#pprof_proto_generator_py).
 
+3. Through Continuous PProf UI.
+   1) Generate pprof proto file as above.
+   2) Upload pprof.profile to pprof/. It can show source file path and line numbers for each symbol.
+      An example is [here](https://pprof.corp.google.com/?id=f5588600d3a225737a1901cb28f3f5b1).
+
 
 ### Reduce lost samples and samples with truncated stack
 
diff --git a/simpleperf/doc/android_platform_profiling.md b/simpleperf/doc/android_platform_profiling.md
index 52bccda..16c967c 100644
--- a/simpleperf/doc/android_platform_profiling.md
+++ b/simpleperf/doc/android_platform_profiling.md
@@ -33,12 +33,24 @@
 # Collect unstripped binaries from $ANDROID_PRODUCT_OUT/symbols to binary_cache/.
 $ ./binary_cache_builder.py -lib $ANDROID_PRODUCT_OUT/symbols
 
-# Report source code and disassembly. Disassembling all binaries is slow, so it's better to add
-# --binary_filter option to only disassemble selected binaries.
+# Collect unstripped binaries from symbol file downloaded from builder server to binary_cache/.
+$ unzip comet-symbols-12488474.zip
+$ ./binary_cache_builder.py -lib out
+
+# To verify that the binaries in binary_cache/ include debug sections, you can perform a manual
+# check.
+
+# Generate an HTML report with source code and disassembly.
+# Disassembling all binaries can be slow, so you can use the --binary_filter
+# option to disassemble only specific binaries, like surfaceflinger.so in this example.
 $ ./report_html.py --add_source_code --source_dirs $ANDROID_BUILD_TOP --add_disassembly \
   --binary_filter surfaceflinger.so
 ```
 
+For a comprehensive guide to displaying source code and disassembly, see
+[Show Annotated Source Code and Disassembly](README.md#show-annotated-source-code-and-disassembly).
+
+
 ## Start simpleperf from system_server process
 
 Sometimes we want to profile a process/system-wide when a special situation happens. In this case,
diff --git a/simpleperf/scripts/simpleperf_utils.py b/simpleperf/scripts/simpleperf_utils.py
index 19b8554..93cd76d 100644
--- a/simpleperf/scripts/simpleperf_utils.py
+++ b/simpleperf/scripts/simpleperf_utils.py
@@ -367,27 +367,27 @@
 
     def get_android_version(self) -> int:
         """ Get Android version on device, like 7 is for Android N, 8 is for Android O."""
-        android_version = 0
-
-        def parse_version(s: str):
+        def parse_version(s: str) -> int:
             if not s:
-                return
+                return 0
             if s[0].isdigit():
                 i = 1
                 while i < len(s) and s[i].isdigit():
                     i += 1
-                android_version = int(s[:i])
+                return int(s[:i])
             else:
                 c = s[0].upper()
                 if c.isupper() and 'L' <= c <= 'V':
-                    android_version = ord(c) - ord('L') + 5
+                    return ord(c) - ord('L') + 5
+            return 0
 
+        android_version = 0
         s = self.get_property('ro.build.version.codename')
         if s != 'REL':
-            parse_version(s)
+            android_version = parse_version(s)
         if android_version == 0:
             s = self.get_property('ro.build.version.release')
-            parse_version(s)
+            android_version = parse_version(s)
         if android_version == 0:
             s = self.get_property('ro.build.version.sdk')
             if int(s) >= 35: