Merge "Add StagedInstallInternalTest to adb TEST_MAPPING file"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index dff6407..5ec40f7 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -28,6 +28,9 @@
       "name": "fs_mgr_vendor_overlay_test"
     },
     {
+      "name": "init_kill_services_test"
+    },
+    {
       "name": "libpackagelistparser_test"
     },
     {
diff --git a/adb/Android.bp b/adb/Android.bp
index 2fc205f..87ac54d 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -12,6 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+tidy_errors = [
+    "-*",
+    "bugprone-inaccurate-erase",
+]
+
 cc_defaults {
     name: "adb_defaults",
 
@@ -73,6 +78,10 @@
             ],
         },
     },
+
+    tidy: true,
+    tidy_checks: tidy_errors,
+    tidy_checks_as_errors: tidy_errors,
 }
 
 cc_defaults {
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp
index d6f536e..ea50f59 100644
--- a/adb/client/adb_install.cpp
+++ b/adb/client/adb_install.cpp
@@ -876,7 +876,7 @@
                     "-S",
                     std::to_string(sb.st_size),
                     session_id_str,
-                    android::base::StringPrintf("%d_%s", i, android::base::Basename(file).c_str()),
+                    android::base::StringPrintf("%d_%s", i, android::base::Basename(split).c_str()),
                     "-",
             };
 
diff --git a/adb/client/auth.cpp b/adb/client/auth.cpp
index b674a81..db4c479 100644
--- a/adb/client/auth.cpp
+++ b/adb/client/auth.cpp
@@ -400,12 +400,12 @@
     for (const std::string& path : paths) {
         int wd = inotify_add_watch(infd, path.c_str(), IN_CREATE | IN_MOVED_TO);
         if (wd < 0) {
-            PLOG(ERROR) << "failed to inotify_add_watch on path '" << path;
+            PLOG(ERROR) << "failed to inotify_add_watch on path '" << path << "'";
             continue;
         }
 
         g_monitored_paths[wd] = path;
-        LOG(INFO) << "watch descriptor " << wd << " registered for " << path;
+        LOG(INFO) << "watch descriptor " << wd << " registered for '" << path << "'";
     }
 
     fdevent* event = fdevent_create(infd, adb_auth_inotify_update, nullptr);
diff --git a/adb/client/bugreport.cpp b/adb/client/bugreport.cpp
index e162aaa..f2e722a 100644
--- a/adb/client/bugreport.cpp
+++ b/adb/client/bugreport.cpp
@@ -104,7 +104,9 @@
             SetLineMessage("pulling");
             status_ =
                 br_->DoSyncPull(srcs, destination.c_str(), false, line_message_.c_str()) ? 0 : 1;
-            if (status_ != 0) {
+            if (status_ == 0) {
+                printf("Bug report copied to %s\n", destination.c_str());
+            } else {
                 fprintf(stderr,
                         "Bug report finished but could not be copied to '%s'.\n"
                         "Try to run 'adb pull %s <directory>'\n"
diff --git a/adb/client/transport_mdns.cpp b/adb/client/transport_mdns.cpp
index 9db2453..a0fc9ca 100644
--- a/adb/client/transport_mdns.cpp
+++ b/adb/client/transport_mdns.cpp
@@ -249,6 +249,9 @@
             return false;
         }
 
+        // Remove any services with the same instance name, as it may be a stale registration.
+        removeDNSService(regType_.c_str(), serviceName_.c_str());
+
         // Add to the service registry before trying to auto-connect, since socket_spec_connect will
         // check these registries for the ip address when connecting via mdns instance name.
         int adbSecureServiceType = serviceIndex();
@@ -268,13 +271,6 @@
                 return false;
         }
 
-        if (!services->empty()) {
-            // Remove the previous resolved service, if any.
-            services->erase(std::remove_if(services->begin(), services->end(),
-                                           [&](std::unique_ptr<ResolvedService>& service) {
-                                               return (serviceName_ == service->serviceName());
-                                           }));
-        }
         services->push_back(std::unique_ptr<ResolvedService>(this));
 
         if (adb_DNSServiceShouldAutoConnect(regType_.c_str(), serviceName_.c_str())) {
@@ -327,6 +323,8 @@
     static bool connectByServiceName(const ServiceRegistry& services,
                                      const std::string& service_name);
 
+    static void removeDNSService(const char* regType, const char* serviceName);
+
   private:
     int clientVersion_ = ADB_SECURE_CLIENT_VERSION;
     std::string addr_format_;
@@ -396,6 +394,37 @@
     return false;
 }
 
+// static
+void ResolvedService::removeDNSService(const char* regType, const char* serviceName) {
+    D("%s: regType=[%s] serviceName=[%s]", __func__, regType, serviceName);
+    int index = adb_DNSServiceIndexByName(regType);
+    ServiceRegistry* services;
+    switch (index) {
+        case kADBTransportServiceRefIndex:
+            services = sAdbTransportServices;
+            break;
+        case kADBSecurePairingServiceRefIndex:
+            services = sAdbSecurePairingServices;
+            break;
+        case kADBSecureConnectServiceRefIndex:
+            services = sAdbSecureConnectServices;
+            break;
+        default:
+            return;
+    }
+
+    if (services->empty()) {
+        return;
+    }
+
+    std::string sName(serviceName);
+    services->erase(std::remove_if(services->begin(), services->end(),
+                                   [&sName](std::unique_ptr<ResolvedService>& service) {
+                                       return (sName == service->serviceName());
+                                   }),
+                    services->end());
+}
+
 void adb_secure_foreach_pairing_service(const char* service_name,
                                         adb_secure_foreach_service_callback cb) {
     ResolvedService::forEachService(*ResolvedService::sAdbSecurePairingServices, service_name, cb);
@@ -481,35 +510,6 @@
     std::string regType_;
 };
 
-static void adb_RemoveDNSService(const char* regType, const char* serviceName) {
-    D("%s: regType=[%s] serviceName=[%s]", __func__, regType, serviceName);
-    int index = adb_DNSServiceIndexByName(regType);
-    ResolvedService::ServiceRegistry* services;
-    switch (index) {
-        case kADBTransportServiceRefIndex:
-            services = ResolvedService::sAdbTransportServices;
-            break;
-        case kADBSecurePairingServiceRefIndex:
-            services = ResolvedService::sAdbSecurePairingServices;
-            break;
-        case kADBSecureConnectServiceRefIndex:
-            services = ResolvedService::sAdbSecureConnectServices;
-            break;
-        default:
-            return;
-    }
-
-    if (services->empty()) {
-        return;
-    }
-
-    std::string sName(serviceName);
-    services->erase(std::remove_if(services->begin(), services->end(),
-                                   [&sName](std::unique_ptr<ResolvedService>& service) {
-                                       return (sName == service->serviceName());
-                                   }));
-}
-
 // Returns the version the device wanted to advertise,
 // or -1 if parsing fails.
 static int parse_version_from_txt_record(uint16_t txtLen, const unsigned char* txtRecord) {
@@ -612,7 +612,7 @@
     } else {
         D("%s: Discover lost serviceName=[%s] regtype=[%s] domain=[%s]", __func__, serviceName,
           regtype, domain);
-        adb_RemoveDNSService(regtype, serviceName);
+        ResolvedService::removeDNSService(regtype, serviceName);
     }
 }
 
@@ -680,6 +680,13 @@
 std::optional<MdnsInfo> mdns_get_connect_service_info(std::string_view name) {
     CHECK(!name.empty());
 
+    // only adb server creates these registries
+    if (!ResolvedService::sAdbTransportServices && !ResolvedService::sAdbSecureConnectServices) {
+        return std::nullopt;
+    }
+    CHECK(ResolvedService::sAdbTransportServices);
+    CHECK(ResolvedService::sAdbSecureConnectServices);
+
     auto mdns_instance = mdns::mdns_parse_instance_name(name);
     if (!mdns_instance.has_value()) {
         D("Failed to parse mDNS name [%s]", name.data());
diff --git a/adb/test_adb.py b/adb/test_adb.py
index b9f0d54..a32d875 100755
--- a/adb/test_adb.py
+++ b/adb/test_adb.py
@@ -618,21 +618,37 @@
     finally:
         zeroconf_ctx.unregister_service(info)
 
+@contextlib.contextmanager
+def zeroconf_register_services(zeroconf_ctx, infos):
+    """Context manager for multiple zeroconf services
+
+    Registers all services given and unregisters all on cleanup. Returns the ServiceInfo
+    list supplied.
+    """
+
+    try:
+        for info in infos:
+            zeroconf_ctx.register_service(info)
+        yield infos
+    finally:
+        for info in infos:
+            zeroconf_ctx.unregister_service(info)
+
 """Should match the service names listed in adb_mdns.h"""
 class MdnsTest:
     """Tests for adb mdns."""
+    @staticmethod
+    def _mdns_services(port):
+        output = subprocess.check_output(["adb", "-P", str(port), "mdns", "services"])
+        return [x.split("\t") for x in output.decode("utf8").strip().splitlines()[1:]]
+
+    @staticmethod
+    def _devices(port):
+        output = subprocess.check_output(["adb", "-P", str(port), "devices"])
+        return [x.split("\t") for x in output.decode("utf8").strip().splitlines()[1:]]
+
 
     class Base(unittest.TestCase):
-        @staticmethod
-        def _mdns_services(port):
-            output = subprocess.check_output(["adb", "-P", str(port), "mdns", "services"])
-            return [x.split("\t") for x in output.decode("utf8").strip().splitlines()[1:]]
-
-        @staticmethod
-        def _devices(port):
-            output = subprocess.check_output(["adb", "-P", str(port), "devices"])
-            return [x.split("\t") for x in output.decode("utf8").strip().splitlines()[1:]]
-
         @contextlib.contextmanager
         def _adb_mdns_connect(self, server_port, mdns_instance, serial, should_connect):
             """Context manager for an ADB connection.
@@ -691,6 +707,50 @@
                         for line in MdnsTest._mdns_services(server_port)))
 
         @unittest.skipIf(not is_zeroconf_installed(), "zeroconf library not installed")
+        def test_mdns_services_register_unregister_multiple(self):
+            """Ensure that `adb mdns services` correctly adds and removes multiple services
+            """
+            from zeroconf import IPVersion, ServiceInfo
+
+            with adb_server() as server_port:
+                output = subprocess.check_output(["adb", "-P", str(server_port),
+                                                  "mdns", "services"]).strip()
+                self.assertTrue(output.startswith(b"List of discovered mdns services"))
+
+                """TODO(joshuaduong): Add ipv6 tests once we have it working in adb"""
+                """Register/Unregister a service"""
+                with zeroconf_context(IPVersion.V4Only) as zc:
+                    srvs = {
+                        'mdns_name': ["testservice0", "testservice1", "testservice2"],
+                        'mdns_type': "_" + self.service_name + "._tcp.",
+                        'ipaddr': [
+                            socket.inet_aton("192.168.0.1"),
+                            socket.inet_aton("10.0.0.255"),
+                            socket.inet_aton("172.16.1.100")],
+                        'port': [10000, 20000, 65535]}
+                    srv_infos = []
+                    for i in range(len(srvs['mdns_name'])):
+                        srv_infos.append(ServiceInfo(
+                                srvs['mdns_type'] + "local.",
+                                name=srvs['mdns_name'][i] + "." + srvs['mdns_type'] + "local.",
+                                addresses=[srvs['ipaddr'][i]],
+                                port=srvs['port'][i]))
+
+                    """ Register all devices, then unregister"""
+                    with zeroconf_register_services(zc, srv_infos) as infos:
+                        """Give adb some time to register the service"""
+                        time.sleep(1)
+                        for i in range(len(srvs['mdns_name'])):
+                            self.assertTrue(any((srvs['mdns_name'][i] in line and srvs['mdns_type'] in line)
+                                for line in MdnsTest._mdns_services(server_port)))
+
+                    """Give adb some time to unregister the service"""
+                    time.sleep(1)
+                    for i in range(len(srvs['mdns_name'])):
+                        self.assertFalse(any((srvs['mdns_name'][i] in line and srvs['mdns_type'] in line)
+                            for line in MdnsTest._mdns_services(server_port)))
+
+        @unittest.skipIf(not is_zeroconf_installed(), "zeroconf library not installed")
         def test_mdns_connect(self):
             """Ensure that `adb connect` by mdns instance name works (for non-pairing services)
             """
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index 5d6cee4..8979e9a 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -436,6 +436,8 @@
     {"reboot,userspace_failed,watchdog_fork", 188},
     {"reboot,userspace_failed,*", 189},
     {"reboot,mount_userdata_failed", 190},
+    {"reboot,forcedsilent", 191},
+    {"reboot,forcednonsilent", 192},
 };
 
 // Converts a string value representing the reason the system booted to an
diff --git a/code_coverage/seccomp_policy/code_coverage.arm.policy b/code_coverage/seccomp_policy/code_coverage.arm.policy
index d6784e3..b80910f 100644
--- a/code_coverage/seccomp_policy/code_coverage.arm.policy
+++ b/code_coverage/seccomp_policy/code_coverage.arm.policy
@@ -6,6 +6,7 @@
 write: 1
 fcntl64: 1
 fstat64: 1
+ftruncate64: 1
 geteuid32: 1
 _llseek: 1
 mmap2: 1
diff --git a/code_coverage/seccomp_policy/code_coverage.arm64.policy b/code_coverage/seccomp_policy/code_coverage.arm64.policy
index 4c3dd26..7040ea2 100644
--- a/code_coverage/seccomp_policy/code_coverage.arm64.policy
+++ b/code_coverage/seccomp_policy/code_coverage.arm64.policy
@@ -6,6 +6,7 @@
 write: 1
 fcntl: 1
 fstat: 1
+ftruncate: 1
 geteuid: 1
 lseek: 1
 mmap: 1
diff --git a/code_coverage/seccomp_policy/code_coverage.policy.def b/code_coverage/seccomp_policy/code_coverage.policy.def
index f136084..599c4a4 100644
--- a/code_coverage/seccomp_policy/code_coverage.policy.def
+++ b/code_coverage/seccomp_policy/code_coverage.policy.def
@@ -22,6 +22,7 @@
 #if     defined(__LP64__)
 fcntl: 1
 fstat: 1
+ftruncate: 1
 geteuid: 1
 lseek: 1
 mmap: 1
@@ -29,6 +30,7 @@
 #else
 fcntl64: 1
 fstat64: 1
+ftruncate64: 1
 geteuid32: 1
 _llseek: 1
 mmap2: 1
diff --git a/code_coverage/seccomp_policy/code_coverage.x86.policy b/code_coverage/seccomp_policy/code_coverage.x86.policy
index 24ff8b9..f8e0cc0 100644
--- a/code_coverage/seccomp_policy/code_coverage.x86.policy
+++ b/code_coverage/seccomp_policy/code_coverage.x86.policy
@@ -6,6 +6,7 @@
 write: 1
 fcntl64: 1
 fstat64: 1
+ftruncate64: 1
 geteuid32: 1
 _llseek: 1
 mmap2: 1
diff --git a/code_coverage/seccomp_policy/code_coverage.x86_64.policy b/code_coverage/seccomp_policy/code_coverage.x86_64.policy
index 3081036..dcf2f9a 100644
--- a/code_coverage/seccomp_policy/code_coverage.x86_64.policy
+++ b/code_coverage/seccomp_policy/code_coverage.x86_64.policy
@@ -6,6 +6,7 @@
 write: 1
 fcntl: 1
 fstat: 1
+ftruncate: 1
 geteuid: 1
 lseek: 1
 mmap: 1
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 31c2d5d..ad10a1f 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -253,7 +253,6 @@
         "libcutils",
         "libdebuggerd_client",
         "liblog",
-        "libminijail",
         "libnativehelper",
         "libunwindstack",
     ],
@@ -261,6 +260,7 @@
     static_libs: [
         "libdebuggerd",
         "libgmock",
+        "libminijail",
     ],
 
     header_libs: [
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index 5c02738..6bfb5f2 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -70,36 +70,6 @@
   tv->tv_usec = static_cast<long>(microseconds.count());
 }
 
-static void get_wchan_header(pid_t pid, std::stringstream& buffer) {
-  struct tm now;
-  time_t t = time(nullptr);
-  localtime_r(&t, &now);
-  char timestamp[32];
-  strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &now);
-  std::string time_now(timestamp);
-
-  std::string path = "/proc/" + std::to_string(pid) + "/cmdline";
-
-  char proc_name_buf[1024];
-  const char* proc_name = nullptr;
-  std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(path.c_str(), "r"), &fclose);
-
-  if (fp) {
-    proc_name = fgets(proc_name_buf, sizeof(proc_name_buf), fp.get());
-  }
-
-  if (!proc_name) {
-    proc_name = "<unknown>";
-  }
-
-  buffer << "\n----- Waiting Channels: pid " << pid << " at " << time_now << " -----\n"
-         << "Cmd line: " << proc_name << "\n";
-}
-
-static void get_wchan_footer(pid_t pid, std::stringstream& buffer) {
-  buffer << "----- end " << std::to_string(pid) << " -----\n";
-}
-
 /**
  * Returns the wchan data for each thread in the process,
  * or empty string if unable to obtain any data.
@@ -125,9 +95,10 @@
   }
 
   if (std::string str = data.str(); !str.empty()) {
-    get_wchan_header(pid, buffer);
+    buffer << "\n----- Waiting Channels: pid " << pid << " at " << get_timestamp() << " -----\n"
+           << "Cmd line: " << get_process_name(pid) << "\n";
     buffer << "\n" << str << "\n";
-    get_wchan_footer(pid, buffer);
+    buffer << "----- end " << std::to_string(pid) << " -----\n";
     buffer << "\n";
   }
 
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 9d7658e..108787e 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -18,6 +18,7 @@
 #include <fcntl.h>
 #include <stdlib.h>
 #include <sys/capability.h>
+#include <sys/mman.h>
 #include <sys/prctl.h>
 #include <sys/ptrace.h>
 #include <sys/resource.h>
@@ -172,6 +173,8 @@
   void StartCrasher(const std::string& crash_type);
   void FinishCrasher();
   void AssertDeath(int signo);
+
+  static void Trap(void* ptr);
 };
 
 CrasherTest::CrasherTest() {
@@ -334,6 +337,48 @@
       R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr (0x100000000000dead|0xdead))");
 }
 
+// Marked as weak to prevent the compiler from removing the malloc in the caller. In theory, the
+// compiler could still clobber the argument register before trapping, but that's unlikely.
+__attribute__((weak)) void CrasherTest::Trap(void* ptr ATTRIBUTE_UNUSED) {
+  __builtin_trap();
+}
+
+TEST_F(CrasherTest, heap_addr_in_register) {
+#if defined(__i386__)
+  GTEST_SKIP() << "architecture does not pass arguments in registers";
+#endif
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    // Crash with a heap pointer in the first argument register.
+    Trap(malloc(1));
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  int status;
+  ASSERT_EQ(crasher_pid, TIMEOUT(30, waitpid(crasher_pid, &status, 0)));
+  ASSERT_TRUE(WIFSIGNALED(status)) << "crasher didn't terminate via a signal";
+  // Don't test the signal number because different architectures use different signals for
+  // __builtin_trap().
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+#if defined(__aarch64__)
+  ASSERT_MATCH(result, "memory near x0");
+#elif defined(__arm__)
+  ASSERT_MATCH(result, "memory near r0");
+#elif defined(__x86_64__)
+  ASSERT_MATCH(result, "memory near rdi");
+#else
+  ASSERT_TRUE(false) << "unsupported architecture";
+#endif
+}
+
 #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
 static void SetTagCheckingLevelSync() {
   int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
@@ -512,6 +557,55 @@
 #endif
 }
 
+#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+static uintptr_t CreateTagMapping() {
+  uintptr_t mapping =
+      reinterpret_cast<uintptr_t>(mmap(nullptr, getpagesize(), PROT_READ | PROT_WRITE | PROT_MTE,
+                                       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
+  if (reinterpret_cast<void*>(mapping) == MAP_FAILED) {
+    return 0;
+  }
+  __asm__ __volatile__(".arch_extension mte; stg %0, [%0]"
+                       :
+                       : "r"(mapping + (1ULL << 56))
+                       : "memory");
+  return mapping;
+}
+#endif
+
+TEST_F(CrasherTest, mte_tag_dump) {
+#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+  if (!mte_supported()) {
+    GTEST_SKIP() << "Requires MTE";
+  }
+
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([&]() {
+    SetTagCheckingLevelSync();
+    Trap(reinterpret_cast<void *>(CreateTagMapping()));
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGTRAP);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  ASSERT_MATCH(result, R"(memory near x0:
+.*
+.*
+    01.............0 0000000000000000 0000000000000000  ................
+    00.............0)");
+#else
+  GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
+#endif
+}
+
 TEST_F(CrasherTest, LD_PRELOAD) {
   int intercept_result;
   unique_fd output_fd;
diff --git a/debuggerd/libdebuggerd/backtrace.cpp b/debuggerd/libdebuggerd/backtrace.cpp
index c606970..f5a873c 100644
--- a/debuggerd/libdebuggerd/backtrace.cpp
+++ b/debuggerd/libdebuggerd/backtrace.cpp
@@ -27,7 +27,6 @@
 #include <string.h>
 #include <sys/ptrace.h>
 #include <sys/types.h>
-#include <time.h>
 #include <unistd.h>
 
 #include <map>
@@ -40,14 +39,10 @@
 
 #include "libdebuggerd/types.h"
 #include "libdebuggerd/utility.h"
+#include "util.h"
 
 static void dump_process_header(log_t* log, pid_t pid, const char* process_name) {
-  time_t t = time(NULL);
-  struct tm tm;
-  localtime_r(&t, &tm);
-  char timestr[64];
-  strftime(timestr, sizeof(timestr), "%F %T", &tm);
-  _LOG(log, logtype::BACKTRACE, "\n\n----- pid %d at %s -----\n", pid, timestr);
+  _LOG(log, logtype::BACKTRACE, "\n\n----- pid %d at %s -----\n", pid, get_timestamp().c_str());
 
   if (process_name) {
     _LOG(log, logtype::BACKTRACE, "Cmd line: %s\n", process_name);
@@ -106,9 +101,8 @@
   log.tfd = output_fd;
   log.amfd_data = nullptr;
 
-  char process_name[128];
-  read_with_default("/proc/self/cmdline", process_name, sizeof(process_name), "<unknown>");
-  dump_process_header(&log, getpid(), process_name);
+  pid_t pid = getpid();
+  dump_process_header(&log, pid, get_process_name(pid).c_str());
 }
 
 void dump_backtrace_footer(int output_fd) {
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 7bfcf5d..76155b1 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -83,8 +83,6 @@
 
 void dump_memory(log_t* log, unwindstack::Memory* backtrace, uint64_t addr, const std::string&);
 
-void read_with_default(const char* path, char* buf, size_t len, const char* default_value);
-
 void drop_capabilities();
 
 bool signal_has_sender(const siginfo_t*, pid_t caller_pid);
diff --git a/debuggerd/libdebuggerd/test/dump_memory_test.cpp b/debuggerd/libdebuggerd/test/dump_memory_test.cpp
index be39582..f16f578 100644
--- a/debuggerd/libdebuggerd/test/dump_memory_test.cpp
+++ b/debuggerd/libdebuggerd/test/dump_memory_test.cpp
@@ -30,39 +30,39 @@
 const char g_expected_full_dump[] =
 "\nmemory near r1:\n"
 #if defined(__LP64__)
-"    0000000012345658 0706050403020100 0f0e0d0c0b0a0908  ................\n"
-"    0000000012345668 1716151413121110 1f1e1d1c1b1a1918  ................\n"
-"    0000000012345678 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
-"    0000000012345688 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
-"    0000000012345698 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
-"    00000000123456a8 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n"
-"    00000000123456b8 6766656463626160 6f6e6d6c6b6a6968  `abcdefghijklmno\n"
-"    00000000123456c8 7776757473727170 7f7e7d7c7b7a7978  pqrstuvwxyz{|}~.\n"
-"    00000000123456d8 8786858483828180 8f8e8d8c8b8a8988  ................\n"
-"    00000000123456e8 9796959493929190 9f9e9d9c9b9a9998  ................\n"
-"    00000000123456f8 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................\n"
-"    0000000012345708 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................\n"
-"    0000000012345718 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
-"    0000000012345728 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
-"    0000000012345738 e7e6e5e4e3e2e1e0 efeeedecebeae9e8  ................\n"
-"    0000000012345748 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................\n";
+"    0000000012345650 0706050403020100 0f0e0d0c0b0a0908  ................\n"
+"    0000000012345660 1716151413121110 1f1e1d1c1b1a1918  ................\n"
+"    0000000012345670 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
+"    0000000012345680 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
+"    0000000012345690 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
+"    00000000123456a0 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n"
+"    00000000123456b0 6766656463626160 6f6e6d6c6b6a6968  `abcdefghijklmno\n"
+"    00000000123456c0 7776757473727170 7f7e7d7c7b7a7978  pqrstuvwxyz{|}~.\n"
+"    00000000123456d0 8786858483828180 8f8e8d8c8b8a8988  ................\n"
+"    00000000123456e0 9796959493929190 9f9e9d9c9b9a9998  ................\n"
+"    00000000123456f0 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................\n"
+"    0000000012345700 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................\n"
+"    0000000012345710 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
+"    0000000012345720 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
+"    0000000012345730 e7e6e5e4e3e2e1e0 efeeedecebeae9e8  ................\n"
+"    0000000012345740 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................\n";
 #else
-"    12345658 03020100 07060504 0b0a0908 0f0e0d0c  ................\n"
-"    12345668 13121110 17161514 1b1a1918 1f1e1d1c  ................\n"
-"    12345678 23222120 27262524 2b2a2928 2f2e2d2c   !\"#$%&'()*+,-./\n"
-"    12345688 33323130 37363534 3b3a3938 3f3e3d3c  0123456789:;<=>?\n"
-"    12345698 43424140 47464544 4b4a4948 4f4e4d4c  @ABCDEFGHIJKLMNO\n"
-"    123456a8 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n"
-"    123456b8 63626160 67666564 6b6a6968 6f6e6d6c  `abcdefghijklmno\n"
-"    123456c8 73727170 77767574 7b7a7978 7f7e7d7c  pqrstuvwxyz{|}~.\n"
-"    123456d8 83828180 87868584 8b8a8988 8f8e8d8c  ................\n"
-"    123456e8 93929190 97969594 9b9a9998 9f9e9d9c  ................\n"
-"    123456f8 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................\n"
-"    12345708 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................\n"
-"    12345718 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................\n"
-"    12345728 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n"
-"    12345738 e3e2e1e0 e7e6e5e4 ebeae9e8 efeeedec  ................\n"
-"    12345748 f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc  ................\n";
+"    12345650 03020100 07060504 0b0a0908 0f0e0d0c  ................\n"
+"    12345660 13121110 17161514 1b1a1918 1f1e1d1c  ................\n"
+"    12345670 23222120 27262524 2b2a2928 2f2e2d2c   !\"#$%&'()*+,-./\n"
+"    12345680 33323130 37363534 3b3a3938 3f3e3d3c  0123456789:;<=>?\n"
+"    12345690 43424140 47464544 4b4a4948 4f4e4d4c  @ABCDEFGHIJKLMNO\n"
+"    123456a0 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n"
+"    123456b0 63626160 67666564 6b6a6968 6f6e6d6c  `abcdefghijklmno\n"
+"    123456c0 73727170 77767574 7b7a7978 7f7e7d7c  pqrstuvwxyz{|}~.\n"
+"    123456d0 83828180 87868584 8b8a8988 8f8e8d8c  ................\n"
+"    123456e0 93929190 97969594 9b9a9998 9f9e9d9c  ................\n"
+"    123456f0 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................\n"
+"    12345700 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................\n"
+"    12345710 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................\n"
+"    12345720 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n"
+"    12345730 e3e2e1e0 e7e6e5e4 ebeae9e8 efeeedec  ................\n"
+"    12345740 f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc  ................\n";
 #endif
 
 const char g_expected_partial_dump[] = \
@@ -112,7 +112,10 @@
     if (last_read_addr_ > 0) {
       offset = addr - last_read_addr_;
     }
-    size_t bytes_available = buffer_.size() - offset;
+    size_t bytes_available = 0;
+    if (offset < buffer_.size()) {
+      bytes_available = buffer_.size() - offset;
+    }
 
     if (partial_read_) {
       bytes = std::min(bytes, bytes_partial_read_);
@@ -258,44 +261,7 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump = \
-"\nmemory near pc:\n"
-#if defined(__LP64__)
-"    00000000a2345658 ---------------- ----------------  ................\n"
-"    00000000a2345668 ---------------- ----------------  ................\n"
-"    00000000a2345678 ---------------- ----------------  ................\n"
-"    00000000a2345688 ---------------- ----------------  ................\n"
-"    00000000a2345698 ---------------- ----------------  ................\n"
-"    00000000a23456a8 ---------------- ----------------  ................\n"
-"    00000000a23456b8 ---------------- ----------------  ................\n"
-"    00000000a23456c8 ---------------- ----------------  ................\n"
-"    00000000a23456d8 ---------------- ----------------  ................\n"
-"    00000000a23456e8 ---------------- ----------------  ................\n"
-"    00000000a23456f8 ---------------- ----------------  ................\n"
-"    00000000a2345708 ---------------- ----------------  ................\n"
-"    00000000a2345718 ---------------- ----------------  ................\n"
-"    00000000a2345728 ---------------- ----------------  ................\n"
-"    00000000a2345738 ---------------- ----------------  ................\n"
-"    00000000a2345748 ---------------- ----------------  ................\n";
-#else
-"    a2345658 -------- -------- -------- --------  ................\n"
-"    a2345668 -------- -------- -------- --------  ................\n"
-"    a2345678 -------- -------- -------- --------  ................\n"
-"    a2345688 -------- -------- -------- --------  ................\n"
-"    a2345698 -------- -------- -------- --------  ................\n"
-"    a23456a8 -------- -------- -------- --------  ................\n"
-"    a23456b8 -------- -------- -------- --------  ................\n"
-"    a23456c8 -------- -------- -------- --------  ................\n"
-"    a23456d8 -------- -------- -------- --------  ................\n"
-"    a23456e8 -------- -------- -------- --------  ................\n"
-"    a23456f8 -------- -------- -------- --------  ................\n"
-"    a2345708 -------- -------- -------- --------  ................\n"
-"    a2345718 -------- -------- -------- --------  ................\n"
-"    a2345728 -------- -------- -------- --------  ................\n"
-"    a2345738 -------- -------- -------- --------  ................\n"
-"    a2345748 -------- -------- -------- --------  ................\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+  ASSERT_STREQ("", tombstone_contents.c_str());
 
   // Verify that the log buf is empty, and no error messages.
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -429,57 +395,17 @@
   ASSERT_STREQ("", getFakeLogPrint().c_str());
 }
 
-TEST_F(DumpMemoryTest, memory_address_too_low) {
-  uint8_t buffer[256];
-  memset(buffer, 0, sizeof(buffer));
-  memory_mock_->SetReadData(buffer, sizeof(buffer));
-
-  dump_memory(&log_, memory_mock_.get(), 0, "memory near r1");
-
-  std::string tombstone_contents;
-  ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
-  ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_STREQ("", tombstone_contents.c_str());
-
-  // Verify that the log buf is empty, and no error messages.
-  ASSERT_STREQ("", getFakeLogBuf().c_str());
-  ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
 TEST_F(DumpMemoryTest, memory_address_too_high) {
   uint8_t buffer[256];
   memset(buffer, 0, sizeof(buffer));
   memory_mock_->SetReadData(buffer, sizeof(buffer));
 
 #if defined(__LP64__)
-  dump_memory(&log_, memory_mock_.get(), 0x4000000000000000UL, "memory near r1");
-  dump_memory(&log_, memory_mock_.get(), 0x4000000000000000UL - 32, "memory near r1");
-  dump_memory(&log_, memory_mock_.get(), 0x4000000000000000UL - 216, "memory near r1");
+  dump_memory(&log_, memory_mock_.get(), -32, "memory near r1");
+  dump_memory(&log_, memory_mock_.get(), -208, "memory near r1");
 #else
-  dump_memory(&log_, memory_mock_.get(), 0xffff0000, "memory near r1");
-  dump_memory(&log_, memory_mock_.get(), 0xffff0000 - 32, "memory near r1");
-  dump_memory(&log_, memory_mock_.get(), 0xffff0000 - 220, "memory near r1");
-#endif
-
-  std::string tombstone_contents;
-  ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
-  ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_STREQ("", tombstone_contents.c_str());
-
-  // Verify that the log buf is empty, and no error messages.
-  ASSERT_STREQ("", getFakeLogBuf().c_str());
-  ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(DumpMemoryTest, memory_address_would_overflow) {
-  uint8_t buffer[256];
-  memset(buffer, 0, sizeof(buffer));
-  memory_mock_->SetReadData(buffer, sizeof(buffer));
-
-#if defined(__LP64__)
-  dump_memory(&log_, memory_mock_.get(), 0xfffffffffffffff0, "memory near r1");
-#else
-  dump_memory(&log_, memory_mock_.get(), 0xfffffff0, "memory near r1");
+  dump_memory(&log_, memory_mock_.get(), 0x100000000 - 32, "memory near r1");
+  dump_memory(&log_, memory_mock_.get(), 0x100000000 - 208, "memory near r1");
 #endif
 
   std::string tombstone_contents;
@@ -500,9 +426,9 @@
   memory_mock_->SetReadData(buffer, sizeof(buffer));
 
 #if defined(__LP64__)
-  dump_memory(&log_, memory_mock_.get(), 0x4000000000000000UL - 224, "memory near r4");
+  dump_memory(&log_, memory_mock_.get(), -224, "memory near r4");
 #else
-  dump_memory(&log_, memory_mock_.get(), 0xffff0000 - 224, "memory near r4");
+  dump_memory(&log_, memory_mock_.get(), 0x100000000 - 224, "memory near r4");
 #endif
 
   std::string tombstone_contents;
@@ -510,40 +436,57 @@
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
   const char* expected_dump = \
 "\nmemory near r4:\n"
-#if defined(__LP64__)
-"    3fffffffffffff00 0706050403020100 0f0e0d0c0b0a0908  ................\n"
-"    3fffffffffffff10 1716151413121110 1f1e1d1c1b1a1918  ................\n"
-"    3fffffffffffff20 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
-"    3fffffffffffff30 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
-"    3fffffffffffff40 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
-"    3fffffffffffff50 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n"
-"    3fffffffffffff60 6766656463626160 6f6e6d6c6b6a6968  `abcdefghijklmno\n"
-"    3fffffffffffff70 7776757473727170 7f7e7d7c7b7a7978  pqrstuvwxyz{|}~.\n"
-"    3fffffffffffff80 8786858483828180 8f8e8d8c8b8a8988  ................\n"
-"    3fffffffffffff90 9796959493929190 9f9e9d9c9b9a9998  ................\n"
-"    3fffffffffffffa0 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................\n"
-"    3fffffffffffffb0 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................\n"
-"    3fffffffffffffc0 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
-"    3fffffffffffffd0 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
-"    3fffffffffffffe0 e7e6e5e4e3e2e1e0 efeeedecebeae9e8  ................\n"
-"    3ffffffffffffff0 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................\n";
+#if defined(__aarch64__)
+"    00ffffffffffff00 0706050403020100 0f0e0d0c0b0a0908  ................\n"
+"    00ffffffffffff10 1716151413121110 1f1e1d1c1b1a1918  ................\n"
+"    00ffffffffffff20 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
+"    00ffffffffffff30 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
+"    00ffffffffffff40 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
+"    00ffffffffffff50 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n"
+"    00ffffffffffff60 6766656463626160 6f6e6d6c6b6a6968  `abcdefghijklmno\n"
+"    00ffffffffffff70 7776757473727170 7f7e7d7c7b7a7978  pqrstuvwxyz{|}~.\n"
+"    00ffffffffffff80 8786858483828180 8f8e8d8c8b8a8988  ................\n"
+"    00ffffffffffff90 9796959493929190 9f9e9d9c9b9a9998  ................\n"
+"    00ffffffffffffa0 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................\n"
+"    00ffffffffffffb0 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................\n"
+"    00ffffffffffffc0 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
+"    00ffffffffffffd0 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
+"    00ffffffffffffe0 e7e6e5e4e3e2e1e0 efeeedecebeae9e8  ................\n"
+"    00fffffffffffff0 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................\n";
+#elif defined(__LP64__)
+"    ffffffffffffff00 0706050403020100 0f0e0d0c0b0a0908  ................\n"
+"    ffffffffffffff10 1716151413121110 1f1e1d1c1b1a1918  ................\n"
+"    ffffffffffffff20 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
+"    ffffffffffffff30 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
+"    ffffffffffffff40 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
+"    ffffffffffffff50 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n"
+"    ffffffffffffff60 6766656463626160 6f6e6d6c6b6a6968  `abcdefghijklmno\n"
+"    ffffffffffffff70 7776757473727170 7f7e7d7c7b7a7978  pqrstuvwxyz{|}~.\n"
+"    ffffffffffffff80 8786858483828180 8f8e8d8c8b8a8988  ................\n"
+"    ffffffffffffff90 9796959493929190 9f9e9d9c9b9a9998  ................\n"
+"    ffffffffffffffa0 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................\n"
+"    ffffffffffffffb0 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................\n"
+"    ffffffffffffffc0 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
+"    ffffffffffffffd0 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
+"    ffffffffffffffe0 e7e6e5e4e3e2e1e0 efeeedecebeae9e8  ................\n"
+"    fffffffffffffff0 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................\n";
 #else
-"    fffeff00 03020100 07060504 0b0a0908 0f0e0d0c  ................\n"
-"    fffeff10 13121110 17161514 1b1a1918 1f1e1d1c  ................\n"
-"    fffeff20 23222120 27262524 2b2a2928 2f2e2d2c   !\"#$%&'()*+,-./\n"
-"    fffeff30 33323130 37363534 3b3a3938 3f3e3d3c  0123456789:;<=>?\n"
-"    fffeff40 43424140 47464544 4b4a4948 4f4e4d4c  @ABCDEFGHIJKLMNO\n"
-"    fffeff50 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n"
-"    fffeff60 63626160 67666564 6b6a6968 6f6e6d6c  `abcdefghijklmno\n"
-"    fffeff70 73727170 77767574 7b7a7978 7f7e7d7c  pqrstuvwxyz{|}~.\n"
-"    fffeff80 83828180 87868584 8b8a8988 8f8e8d8c  ................\n"
-"    fffeff90 93929190 97969594 9b9a9998 9f9e9d9c  ................\n"
-"    fffeffa0 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................\n"
-"    fffeffb0 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................\n"
-"    fffeffc0 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................\n"
-"    fffeffd0 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n"
-"    fffeffe0 e3e2e1e0 e7e6e5e4 ebeae9e8 efeeedec  ................\n"
-"    fffefff0 f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc  ................\n";
+"    ffffff00 03020100 07060504 0b0a0908 0f0e0d0c  ................\n"
+"    ffffff10 13121110 17161514 1b1a1918 1f1e1d1c  ................\n"
+"    ffffff20 23222120 27262524 2b2a2928 2f2e2d2c   !\"#$%&'()*+,-./\n"
+"    ffffff30 33323130 37363534 3b3a3938 3f3e3d3c  0123456789:;<=>?\n"
+"    ffffff40 43424140 47464544 4b4a4948 4f4e4d4c  @ABCDEFGHIJKLMNO\n"
+"    ffffff50 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n"
+"    ffffff60 63626160 67666564 6b6a6968 6f6e6d6c  `abcdefghijklmno\n"
+"    ffffff70 73727170 77767574 7b7a7978 7f7e7d7c  pqrstuvwxyz{|}~.\n"
+"    ffffff80 83828180 87868584 8b8a8988 8f8e8d8c  ................\n"
+"    ffffff90 93929190 97969594 9b9a9998 9f9e9d9c  ................\n"
+"    ffffffa0 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................\n"
+"    ffffffb0 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................\n"
+"    ffffffc0 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................\n"
+"    ffffffd0 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n"
+"    ffffffe0 e3e2e1e0 e7e6e5e4 ebeae9e8 efeeedec  ................\n"
+"    fffffff0 f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc  ................\n";
 #endif
   ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
 
@@ -570,39 +513,41 @@
   const char* expected_dump = \
 "\nmemory near r4:\n"
 #if defined(__LP64__)
-"    0000000010000f88 ---------------- ----------------  ................\n"
-"    0000000010000f98 ---------------- ----------------  ................\n"
-"    0000000010000fa8 ---------------- ----------------  ................\n"
-"    0000000010000fb8 ---------------- ----------------  ................\n"
-"    0000000010000fc8 ---------------- ----------------  ................\n"
-"    0000000010000fd8 ---------------- ----------------  ................\n"
-"    0000000010000fe8 ---------------- ----------------  ................\n"
-"    0000000010000ff8 ---------------- 7f7e7d7c7b7a7978  ........xyz{|}~.\n"
-"    0000000010001008 8786858483828180 8f8e8d8c8b8a8988  ................\n"
-"    0000000010001018 9796959493929190 9f9e9d9c9b9a9998  ................\n"
-"    0000000010001028 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................\n"
-"    0000000010001038 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................\n"
-"    0000000010001048 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
-"    0000000010001058 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
-"    0000000010001068 e7e6e5e4e3e2e1e0 efeeedecebeae9e8  ................\n"
-"    0000000010001078 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................\n";
+R"(    0000000010000f80 ---------------- ----------------  ................
+    0000000010000f90 ---------------- ----------------  ................
+    0000000010000fa0 ---------------- ----------------  ................
+    0000000010000fb0 ---------------- ----------------  ................
+    0000000010000fc0 ---------------- ----------------  ................
+    0000000010000fd0 ---------------- ----------------  ................
+    0000000010000fe0 ---------------- ----------------  ................
+    0000000010000ff0 ---------------- ----------------  ................
+    0000000010001000 8786858483828180 8f8e8d8c8b8a8988  ................
+    0000000010001010 9796959493929190 9f9e9d9c9b9a9998  ................
+    0000000010001020 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................
+    0000000010001030 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................
+    0000000010001040 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................
+    0000000010001050 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................
+    0000000010001060 e7e6e5e4e3e2e1e0 efeeedecebeae9e8  ................
+    0000000010001070 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................
+)";
 #else
-"    10000f88 -------- -------- -------- --------  ................\n"
-"    10000f98 -------- -------- -------- --------  ................\n"
-"    10000fa8 -------- -------- -------- --------  ................\n"
-"    10000fb8 -------- -------- -------- --------  ................\n"
-"    10000fc8 -------- -------- -------- --------  ................\n"
-"    10000fd8 -------- -------- -------- --------  ................\n"
-"    10000fe8 -------- -------- -------- --------  ................\n"
-"    10000ff8 -------- -------- 7b7a7978 7f7e7d7c  ........xyz{|}~.\n"
-"    10001008 83828180 87868584 8b8a8988 8f8e8d8c  ................\n"
-"    10001018 93929190 97969594 9b9a9998 9f9e9d9c  ................\n"
-"    10001028 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................\n"
-"    10001038 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................\n"
-"    10001048 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................\n"
-"    10001058 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n"
-"    10001068 e3e2e1e0 e7e6e5e4 ebeae9e8 efeeedec  ................\n"
-"    10001078 f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc  ................\n";
+R"(    10000f80 -------- -------- -------- --------  ................
+    10000f90 -------- -------- -------- --------  ................
+    10000fa0 -------- -------- -------- --------  ................
+    10000fb0 -------- -------- -------- --------  ................
+    10000fc0 -------- -------- -------- --------  ................
+    10000fd0 -------- -------- -------- --------  ................
+    10000fe0 -------- -------- -------- --------  ................
+    10000ff0 -------- -------- -------- --------  ................
+    10001000 83828180 87868584 8b8a8988 8f8e8d8c  ................
+    10001010 93929190 97969594 9b9a9998 9f9e9d9c  ................
+    10001020 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................
+    10001030 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................
+    10001040 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................
+    10001050 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................
+    10001060 e3e2e1e0 e7e6e5e4 ebeae9e8 efeeedec  ................
+    10001070 f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc  ................
+)";
 #endif
   ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
 
@@ -684,44 +629,7 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump = \
-"\nmemory near r4:\n"
-#if defined(__LP64__)
-"    0000000010000000 ---------------- ----------------  ................\n"
-"    0000000010000010 ---------------- ----------------  ................\n"
-"    0000000010000020 ---------------- ----------------  ................\n"
-"    0000000010000030 ---------------- ----------------  ................\n"
-"    0000000010000040 ---------------- ----------------  ................\n"
-"    0000000010000050 ---------------- ----------------  ................\n"
-"    0000000010000060 ---------------- ----------------  ................\n"
-"    0000000010000070 ---------------- ----------------  ................\n"
-"    0000000010000080 ---------------- ----------------  ................\n"
-"    0000000010000090 ---------------- ----------------  ................\n"
-"    00000000100000a0 ---------------- ----------------  ................\n"
-"    00000000100000b0 ---------------- ----------------  ................\n"
-"    00000000100000c0 ---------------- ----------------  ................\n"
-"    00000000100000d0 ---------------- ----------------  ................\n"
-"    00000000100000e0 ---------------- ----------------  ................\n"
-"    00000000100000f0 ---------------- ----------------  ................\n";
-#else
-"    10000000 -------- -------- -------- --------  ................\n"
-"    10000010 -------- -------- -------- --------  ................\n"
-"    10000020 -------- -------- -------- --------  ................\n"
-"    10000030 -------- -------- -------- --------  ................\n"
-"    10000040 -------- -------- -------- --------  ................\n"
-"    10000050 -------- -------- -------- --------  ................\n"
-"    10000060 -------- -------- -------- --------  ................\n"
-"    10000070 -------- -------- -------- --------  ................\n"
-"    10000080 -------- -------- -------- --------  ................\n"
-"    10000090 -------- -------- -------- --------  ................\n"
-"    100000a0 -------- -------- -------- --------  ................\n"
-"    100000b0 -------- -------- -------- --------  ................\n"
-"    100000c0 -------- -------- -------- --------  ................\n"
-"    100000d0 -------- -------- -------- --------  ................\n"
-"    100000e0 -------- -------- -------- --------  ................\n"
-"    100000f0 -------- -------- -------- --------  ................\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+  ASSERT_STREQ("", tombstone_contents.c_str());
 
   // Verify that the log buf is empty, and no error messages.
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -744,44 +652,7 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump = \
-"\nmemory near r4:\n"
-#if defined(__LP64__)
-"    0000000010000f00 ---------------- ----------------  ................\n"
-"    0000000010000f10 ---------------- ----------------  ................\n"
-"    0000000010000f20 ---------------- ----------------  ................\n"
-"    0000000010000f30 ---------------- ----------------  ................\n"
-"    0000000010000f40 ---------------- ----------------  ................\n"
-"    0000000010000f50 ---------------- ----------------  ................\n"
-"    0000000010000f60 ---------------- ----------------  ................\n"
-"    0000000010000f70 ---------------- ----------------  ................\n"
-"    0000000010000f80 ---------------- ----------------  ................\n"
-"    0000000010000f90 ---------------- ----------------  ................\n"
-"    0000000010000fa0 ---------------- ----------------  ................\n"
-"    0000000010000fb0 ---------------- ----------------  ................\n"
-"    0000000010000fc0 ---------------- ----------------  ................\n"
-"    0000000010000fd0 ---------------- ----------------  ................\n"
-"    0000000010000fe0 ---------------- ----------------  ................\n"
-"    0000000010000ff0 ---------------- ----------------  ................\n";
-#else
-"    10000f00 -------- -------- -------- --------  ................\n"
-"    10000f10 -------- -------- -------- --------  ................\n"
-"    10000f20 -------- -------- -------- --------  ................\n"
-"    10000f30 -------- -------- -------- --------  ................\n"
-"    10000f40 -------- -------- -------- --------  ................\n"
-"    10000f50 -------- -------- -------- --------  ................\n"
-"    10000f60 -------- -------- -------- --------  ................\n"
-"    10000f70 -------- -------- -------- --------  ................\n"
-"    10000f80 -------- -------- -------- --------  ................\n"
-"    10000f90 -------- -------- -------- --------  ................\n"
-"    10000fa0 -------- -------- -------- --------  ................\n"
-"    10000fb0 -------- -------- -------- --------  ................\n"
-"    10000fc0 -------- -------- -------- --------  ................\n"
-"    10000fd0 -------- -------- -------- --------  ................\n"
-"    10000fe0 -------- -------- -------- --------  ................\n"
-"    10000ff0 -------- -------- -------- --------  ................\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+  ASSERT_STREQ("", tombstone_contents.c_str());
 
   // Verify that the log buf is empty, and no error messages.
   ASSERT_STREQ("", getFakeLogBuf().c_str());
diff --git a/debuggerd/libdebuggerd/test/tombstone_test.cpp b/debuggerd/libdebuggerd/test/tombstone_test.cpp
index aec8c60..b42d70c 100644
--- a/debuggerd/libdebuggerd/test/tombstone_test.cpp
+++ b/debuggerd/libdebuggerd/test/tombstone_test.cpp
@@ -359,13 +359,6 @@
   ASSERT_STREQ(expected.c_str(), amfd_data_.c_str());
 }
 
-TEST_F(TombstoneTest, dump_timestamp) {
-  setenv("TZ", "UTC", 1);
-  tzset();
-  dump_timestamp(&log_, 0);
-  ASSERT_STREQ("Timestamp: 1970-01-01 00:00:00+0000\n", amfd_data_.c_str());
-}
-
 class GwpAsanCrashDataTest : public GwpAsanCrashData {
 public:
   GwpAsanCrashDataTest(
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index ab65dd1..face02b 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -58,6 +58,7 @@
 #include "libdebuggerd/open_files_list.h"
 #include "libdebuggerd/scudo.h"
 #include "libdebuggerd/utility.h"
+#include "util.h"
 
 #include "gwp_asan/common.h"
 #include "gwp_asan/crash_handler.h"
@@ -80,15 +81,6 @@
   _LOG(log, logtype::HEADER, "ABI: '%s'\n", ABI_STRING);
 }
 
-static void dump_timestamp(log_t* log, time_t time) {
-  struct tm tm;
-  localtime_r(&time, &tm);
-
-  char buf[strlen("1970-01-01 00:00:00+0830") + 1];
-  strftime(buf, sizeof(buf), "%F %T%z", &tm);
-  _LOG(log, logtype::HEADER, "Timestamp: %s\n", buf);
-}
-
 static std::string get_stack_overflow_cause(uint64_t fault_addr, uint64_t sp,
                                             unwindstack::Maps* maps) {
   static constexpr uint64_t kMaxDifferenceBytes = 256;
@@ -507,10 +499,9 @@
     // (although in this case the pid is redundant).
     char timeBuf[32];
     time_t sec = static_cast<time_t>(log_entry.entry.sec);
-    struct tm tmBuf;
-    struct tm* ptm;
-    ptm = localtime_r(&sec, &tmBuf);
-    strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
+    tm tm;
+    localtime_r(&sec, &tm);
+    strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", &tm);
 
     char* msg = log_entry.msg();
     if (msg == nullptr) {
@@ -571,23 +562,20 @@
   log.tfd = tombstone_fd;
   log.amfd_data = nullptr;
 
-  char thread_name[16];
-  char process_name[128];
-
-  read_with_default("/proc/self/comm", thread_name, sizeof(thread_name), "<unknown>");
-  read_with_default("/proc/self/cmdline", process_name, sizeof(process_name), "<unknown>");
+  std::string thread_name = get_thread_name(tid);
+  std::string process_name = get_process_name(pid);
 
   std::unique_ptr<unwindstack::Regs> regs(
       unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), ucontext));
 
   std::map<pid_t, ThreadInfo> threads;
-  threads[gettid()] = ThreadInfo{
+  threads[tid] = ThreadInfo{
       .registers = std::move(regs),
       .uid = uid,
       .tid = tid,
-      .thread_name = thread_name,
+      .thread_name = thread_name.c_str(),
       .pid = pid,
-      .process_name = process_name,
+      .process_name = process_name.c_str(),
       .siginfo = siginfo,
   };
 
@@ -606,8 +594,8 @@
                        const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
                        const ProcessInfo& process_info, OpenFilesList* open_files,
                        std::string* amfd_data) {
-  // don't copy log messages to tombstone unless this is a dev device
-  bool want_logs = android::base::GetBoolProperty("ro.debuggable", false);
+  // Don't copy log messages to tombstone unless this is a development device.
+  bool want_logs = GetBoolProperty("ro.debuggable", false);
 
   log_t log;
   log.current_tid = target_thread;
@@ -617,7 +605,7 @@
 
   _LOG(&log, logtype::HEADER, "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
   dump_header_info(&log);
-  dump_timestamp(&log, time(nullptr));
+  _LOG(&log, logtype::HEADER, "Timestamp: %s\n", get_timestamp().c_str());
 
   auto it = threads.find(target_thread);
   if (it == threads.end()) {
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index c8a3431..f43092c 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -129,24 +129,23 @@
 #define MEMORY_BYTES_PER_LINE 16
 
 void dump_memory(log_t* log, unwindstack::Memory* memory, uint64_t addr, const std::string& label) {
-  // Align the address to sizeof(long) and start 32 bytes before the address.
-  addr &= ~(sizeof(long) - 1);
+  // Align the address to the number of bytes per line to avoid confusing memory tag output if
+  // memory is tagged and we start from a misaligned address. Start 32 bytes before the address.
+  addr &= ~(MEMORY_BYTES_PER_LINE - 1);
   if (addr >= 4128) {
     addr -= 32;
   }
 
-  // Don't bother if the address looks too low, or looks too high.
-  if (addr < 4096 ||
-#if defined(__LP64__)
-      addr > 0x4000000000000000UL - MEMORY_BYTES_TO_DUMP) {
-#else
-      addr > 0xffff0000 - MEMORY_BYTES_TO_DUMP) {
-#endif
+  // We don't want the address tag to appear in the addresses in the memory dump.
+  addr = untag_address(addr);
+
+  // Don't bother if the address would overflow, taking tag bits into account. Note that
+  // untag_address truncates to 32 bits on 32-bit platforms as a side effect of returning a
+  // uintptr_t, so this also checks for 32-bit overflow.
+  if (untag_address(addr + MEMORY_BYTES_TO_DUMP - 1) < addr) {
     return;
   }
 
-  _LOG(log, logtype::MEMORY, "\n%s:\n", label.c_str());
-
   // Dump 256 bytes
   uintptr_t data[MEMORY_BYTES_TO_DUMP/sizeof(uintptr_t)];
   memset(data, 0, MEMORY_BYTES_TO_DUMP);
@@ -187,6 +186,15 @@
     }
   }
 
+  // If we were unable to read anything, it probably means that the register doesn't contain a
+  // valid pointer. In that case, skip the output for this register entirely rather than emitting 16
+  // lines of dashes.
+  if (bytes == 0) {
+    return;
+  }
+
+  _LOG(log, logtype::MEMORY, "\n%s:\n", label.c_str());
+
   // Dump the code around memory as:
   //  addr             contents                           ascii
   //  0000000000008d34 ef000000e8bd0090 e1b00000512fff1e  ............../Q
@@ -197,8 +205,13 @@
   size_t current = 0;
   size_t total_bytes = start + bytes;
   for (size_t line = 0; line < MEMORY_BYTES_TO_DUMP / MEMORY_BYTES_PER_LINE; line++) {
+    uint64_t tagged_addr = addr;
+    long tag = memory->ReadTag(addr);
+    if (tag >= 0) {
+      tagged_addr |= static_cast<uint64_t>(tag) << 56;
+    }
     std::string logline;
-    android::base::StringAppendF(&logline, "    %" PRIPTR, addr);
+    android::base::StringAppendF(&logline, "    %" PRIPTR, tagged_addr);
 
     addr += MEMORY_BYTES_PER_LINE;
     std::string ascii;
@@ -226,23 +239,6 @@
   }
 }
 
-void read_with_default(const char* path, char* buf, size_t len, const char* default_value) {
-  unique_fd fd(open(path, O_RDONLY | O_CLOEXEC));
-  if (fd != -1) {
-    int rc = TEMP_FAILURE_RETRY(read(fd.get(), buf, len - 1));
-    if (rc != -1) {
-      buf[rc] = '\0';
-
-      // Trim trailing newlines.
-      if (rc > 0 && buf[rc - 1] == '\n') {
-        buf[rc - 1] = '\0';
-      }
-      return;
-    }
-  }
-  strcpy(buf, default_value);
-}
-
 void drop_capabilities() {
   __user_cap_header_struct capheader;
   memset(&capheader, 0, sizeof(capheader));
diff --git a/debuggerd/util.cpp b/debuggerd/util.cpp
index a37b3b9..9d09210 100644
--- a/debuggerd/util.cpp
+++ b/debuggerd/util.cpp
@@ -17,6 +17,7 @@
 #include "util.h"
 
 #include <sys/socket.h>
+#include <time.h>
 
 #include <string>
 #include <utility>
@@ -38,3 +39,19 @@
   android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/comm", tid), &result);
   return android::base::Trim(result);
 }
+
+std::string get_timestamp() {
+  timespec ts;
+  clock_gettime(CLOCK_REALTIME, &ts);
+
+  tm tm;
+  localtime_r(&ts.tv_sec, &tm);
+
+  char buf[strlen("1970-01-01 00:00:00.123456789+0830") + 1];
+  char* s = buf;
+  size_t sz = sizeof(buf), n;
+  n = strftime(s, sz, "%F %H:%M", &tm), s += n, sz -= n;
+  n = snprintf(s, sz, ":%02d.%09ld", tm.tm_sec, ts.tv_nsec), s += n, sz -= n;
+  n = strftime(s, sz, "%z", &tm), s += n, sz -= n;
+  return buf;
+}
diff --git a/debuggerd/util.h b/debuggerd/util.h
index e964423..07e7e99 100644
--- a/debuggerd/util.h
+++ b/debuggerd/util.h
@@ -23,3 +23,5 @@
 
 std::string get_process_name(pid_t pid);
 std::string get_thread_name(pid_t tid);
+
+std::string get_timestamp();
diff --git a/fastboot/fastboot.bash b/fastboot/fastboot.bash
index cb1d354..406e8b8 100644
--- a/fastboot/fastboot.bash
+++ b/fastboot/fastboot.bash
@@ -109,7 +109,7 @@
 
     cur="${COMP_WORDS[COMP_CWORD]}"
     if [[ $i -eq $COMP_CWORD ]]; then
-        partitions="boot bootloader dtbo modem odm oem product radio recovery system vbmeta vendor"
+        partitions="boot bootloader dtbo modem odm odm_dlkm oem product radio recovery system vbmeta vendor vendor_dlkm"
         COMPREPLY=( $(compgen -W "$partitions" -- $cur) )
     else
         _fastboot_util_complete_local_file "${cur}" '!*.img'
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 0e9713d..d33c987 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -145,6 +145,7 @@
     { "dtbo",     "dtbo.img",         "dtbo.sig",     "dtbo",     true,  ImageType::BootCritical },
     { "dts",      "dt.img",           "dt.sig",       "dts",      true,  ImageType::BootCritical },
     { "odm",      "odm.img",          "odm.sig",      "odm",      true,  ImageType::Normal },
+    { "odm_dlkm", "odm_dlkm.img",     "odm_dlkm.sig", "odm_dlkm", true,  ImageType::Normal },
     { "product",  "product.img",      "product.sig",  "product",  true,  ImageType::Normal },
     { "recovery", "recovery.img",     "recovery.sig", "recovery", true,  ImageType::BootCritical },
     { "super",    "super.img",        "super.sig",    "super",    true,  ImageType::Extra },
@@ -166,6 +167,10 @@
                   "vendor_boot.img",  "vendor_boot.sig",
                                                       "vendor_boot",
                                                                   true,  ImageType::BootCritical },
+    { "vendor_dlkm",
+                  "vendor_dlkm.img",  "vendor_dlkm.sig",
+                                                      "vendor_dlkm",
+                                                                  true,  ImageType::Normal },
     { nullptr,    "vendor_other.img", "vendor.sig",   "vendor",   true,  ImageType::Normal },
         // clang-format on
 };
@@ -992,10 +997,69 @@
            fb->GetVar("partition-type:vbmeta_b", &partition_type) == fastboot::SUCCESS;
 }
 
+static std::string fb_fix_numeric_var(std::string var) {
+    // Some bootloaders (angler, for example), send spurious leading whitespace.
+    var = android::base::Trim(var);
+    // Some bootloaders (hammerhead, for example) use implicit hex.
+    // This code used to use strtol with base 16.
+    if (!android::base::StartsWith(var, "0x")) var = "0x" + var;
+    return var;
+}
+
+static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
+    if (buf->sz < AVB_FOOTER_SIZE) {
+        return;
+    }
+
+    std::string partition_size_str;
+    if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) {
+        die("cannot get boot partition size");
+    }
+
+    partition_size_str = fb_fix_numeric_var(partition_size_str);
+    int64_t partition_size;
+    if (!android::base::ParseInt(partition_size_str, &partition_size)) {
+        die("Couldn't parse partition size '%s'.", partition_size_str.c_str());
+    }
+    if (partition_size == buf->sz) {
+        return;
+    }
+    if (partition_size < buf->sz) {
+        die("boot partition is smaller than boot image");
+    }
+
+    std::string data;
+    if (!android::base::ReadFdToString(buf->fd, &data)) {
+        die("Failed reading from boot");
+    }
+
+    uint64_t footer_offset = buf->sz - AVB_FOOTER_SIZE;
+    if (0 != data.compare(footer_offset, AVB_FOOTER_MAGIC_LEN, AVB_FOOTER_MAGIC)) {
+        return;
+    }
+
+    int fd = make_temporary_fd("boot rewriting");
+    if (!android::base::WriteStringToFd(data, fd)) {
+        die("Failed writing to modified boot");
+    }
+    lseek(fd, partition_size - AVB_FOOTER_SIZE, SEEK_SET);
+    if (!android::base::WriteStringToFd(data.substr(footer_offset), fd)) {
+        die("Failed copying AVB footer in boot");
+    }
+    close(buf->fd);
+    buf->fd = fd;
+    buf->sz = partition_size;
+    lseek(fd, 0, SEEK_SET);
+}
+
 static void flash_buf(const std::string& partition, struct fastboot_buffer *buf)
 {
     sparse_file** s;
 
+    if (partition == "boot" || partition == "boot_a" || partition == "boot_b") {
+        copy_boot_avb_footer(partition, buf);
+    }
+
     // Rewrite vbmeta if that's what we're flashing and modification has been requested.
     if (g_disable_verity || g_disable_verification) {
         if (partition == "vbmeta" || partition == "vbmeta_a" || partition == "vbmeta_b") {
@@ -1491,15 +1555,6 @@
     fb->RawCommand(command, "");
 }
 
-static std::string fb_fix_numeric_var(std::string var) {
-    // Some bootloaders (angler, for example), send spurious leading whitespace.
-    var = android::base::Trim(var);
-    // Some bootloaders (hammerhead, for example) use implicit hex.
-    // This code used to use strtol with base 16.
-    if (!android::base::StartsWith(var, "0x")) var = "0x" + var;
-    return var;
-}
-
 static unsigned fb_get_flash_block_size(std::string name) {
     std::string sizeString;
     if (fb->GetVar(name, &sizeString) != fastboot::SUCCESS || sizeString.empty()) {
diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp
index e7f785b..34ab32c 100644
--- a/fastboot/fuzzy_fastboot/main.cpp
+++ b/fastboot/fuzzy_fastboot/main.cpp
@@ -1286,7 +1286,7 @@
     ASSERT_TRUE(PartitionHash(fb.get(), "userdata", &hash_buf, &retcode, &err_msg)) << err_msg;
     ASSERT_EQ(retcode, 0) << err_msg;
 
-    // Sanity check of hash
+    // Validity check of hash
     EXPECT_NE(hash_before, hash_buf)
             << "Writing a random buffer to 'userdata' had the same hash as after erasing it";
     SetLockState(true);  // Lock the device
diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp
index f5daf91..ac784b2 100644
--- a/fs_mgr/Android.bp
+++ b/fs_mgr/Android.bp
@@ -149,6 +149,14 @@
         darwin: {
             enabled: false,
         },
+        vendor: {
+            cflags: [
+                // Skipping entries in fstab should only be done in a system
+                // process as the config file is in /system_ext.
+                // Remove the op from the vendor variant.
+                "-DNO_SKIP_MOUNT",
+            ],
+        },
     },
     export_include_dirs: ["include_fstab"],
     header_libs: [
@@ -162,10 +170,13 @@
     defaults: ["fs_mgr_defaults"],
     static_libs: [
         "libavb_user",
+        "libutils",
+        "libvold_binder",
     ],
     shared_libs: [
         "libbootloader_message",
         "libbase",
+        "libbinder",
         "libcutils",
         "libcrypto",
         "libext4_utils",
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 76837ee..0ae5787 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -331,7 +331,7 @@
             // try backup superblock, if main superblock is corrupted
             for (unsigned int blocksize = EXT4_MIN_BLOCK_SIZE; blocksize <= EXT4_MAX_BLOCK_SIZE;
                  blocksize *= 2) {
-                unsigned int superblock = blocksize * 8;
+                uint64_t superblock = blocksize * 8;
                 if (blocksize == EXT4_MIN_BLOCK_SIZE) superblock++;
 
                 if (TEMP_FAILURE_RETRY(pread(fd, sb, sizeof(*sb), superblock * blocksize)) !=
@@ -1044,7 +1044,8 @@
 
 class CheckpointManager {
   public:
-    CheckpointManager(int needs_checkpoint = -1) : needs_checkpoint_(needs_checkpoint) {}
+    CheckpointManager(int needs_checkpoint = -1, bool metadata_encrypted = false)
+        : needs_checkpoint_(needs_checkpoint), metadata_encrypted_(metadata_encrypted) {}
 
     bool NeedsCheckpoint() {
         if (needs_checkpoint_ != UNKNOWN) {
@@ -1062,7 +1063,7 @@
             return true;
         }
 
-        if (entry->fs_mgr_flags.checkpoint_blk) {
+        if (entry->fs_mgr_flags.checkpoint_blk && !metadata_encrypted_) {
             call_vdc({"checkpoint", "restoreCheckpoint", entry->blk_device}, nullptr);
         }
 
@@ -1171,6 +1172,7 @@
 
     enum { UNKNOWN = -1, NO = 0, YES = 1 };
     int needs_checkpoint_;
+    bool metadata_encrypted_;
     std::map<std::string, std::string> device_map_;
 };
 
@@ -1804,11 +1806,11 @@
 // in turn, and stop on 1st success, or no more match.
 static int fs_mgr_do_mount_helper(Fstab* fstab, const std::string& n_name,
                                   const std::string& n_blk_device, const char* tmp_mount_point,
-                                  int needs_checkpoint) {
+                                  int needs_checkpoint, bool metadata_encrypted) {
     int mount_errors = 0;
     int first_mount_errno = 0;
     std::string mount_point;
-    CheckpointManager checkpoint_manager(needs_checkpoint);
+    CheckpointManager checkpoint_manager(needs_checkpoint, metadata_encrypted);
     AvbUniquePtr avb_handle(nullptr);
 
     if (!fstab) {
@@ -1918,12 +1920,13 @@
 }
 
 int fs_mgr_do_mount(Fstab* fstab, const char* n_name, char* n_blk_device, char* tmp_mount_point) {
-    return fs_mgr_do_mount_helper(fstab, n_name, n_blk_device, tmp_mount_point, -1);
+    return fs_mgr_do_mount_helper(fstab, n_name, n_blk_device, tmp_mount_point, -1, false);
 }
 
 int fs_mgr_do_mount(Fstab* fstab, const char* n_name, char* n_blk_device, char* tmp_mount_point,
-                    bool needs_checkpoint) {
-    return fs_mgr_do_mount_helper(fstab, n_name, n_blk_device, tmp_mount_point, needs_checkpoint);
+                    bool needs_checkpoint, bool metadata_encrypted) {
+    return fs_mgr_do_mount_helper(fstab, n_name, n_blk_device, tmp_mount_point, needs_checkpoint,
+                                  metadata_encrypted);
 }
 
 /*
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index f333a85..54102ec 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -696,7 +696,9 @@
         TransformFstabForDsu(fstab, Split(lp_names, ","));
     }
 
+#ifndef NO_SKIP_MOUNT
     SkipMountingPartitions(fstab);
+#endif
     EnableMandatoryFlags(fstab);
 
     return true;
@@ -726,11 +728,14 @@
         return false;
     }
 
+#ifndef NO_SKIP_MOUNT
     SkipMountingPartitions(fstab);
+#endif
 
     return true;
 }
 
+#ifndef NO_SKIP_MOUNT
 // For GSI to skip mounting /product and /system_ext, until there are well-defined interfaces
 // between them and /system. Otherwise, the GSI flashed on /system might not be able to work with
 // device-specific /product and /system_ext. skip_mount.cfg belongs to system_ext partition because
@@ -762,6 +767,7 @@
 
     return true;
 }
+#endif
 
 // Loads the fstab file and combines with fstab entries passed in from device tree.
 bool ReadDefaultFstab(Fstab* fstab) {
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 1fa1aa1..a7704de 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -809,15 +809,26 @@
     entry.fs_type = mnt_type;
     if ((mnt_type == "f2fs") && !f2fs) entry.fs_type = "ext4";
     if ((mnt_type == "ext4") && !ext4) entry.fs_type = "f2fs";
-    entry.flags = MS_NOATIME;
-    if (readonly) {
-        entry.flags |= MS_RDONLY;
-    } else {
+    entry.flags = MS_NOATIME | MS_RDONLY;
+    auto mounted = true;
+    if (!readonly) {
+        if (entry.fs_type == "ext4") {
+            // check if ext4 de-dupe
+            entry.flags |= MS_RDONLY;
+            auto save_errno = errno;
+            mounted = fs_mgr_do_mount_one(entry) == 0;
+            if (mounted) {
+                mounted = !fs_mgr_has_shared_blocks(entry.mount_point, entry.blk_device);
+                fs_mgr_overlayfs_umount_scratch();
+            }
+            errno = save_errno;
+        }
+        entry.flags &= ~MS_RDONLY;
         fs_mgr_set_blk_ro(device_path, false);
     }
     entry.fs_mgr_flags.check = true;
     auto save_errno = errno;
-    auto mounted = fs_mgr_do_mount_one(entry) == 0;
+    if (mounted) mounted = fs_mgr_do_mount_one(entry) == 0;
     if (!mounted) {
         if ((entry.fs_type == "f2fs") && ext4) {
             entry.fs_type = "ext4";
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index 052efa7..b8b074e 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -24,6 +24,7 @@
 #include <unistd.h>
 
 #include <string>
+#include <thread>
 #include <utility>
 #include <vector>
 
@@ -31,6 +32,8 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
+#include <android/os/IVold.h>
+#include <binder/IServiceManager.h>
 #include <bootloader_message/bootloader_message.h>
 #include <cutils/android_reboot.h>
 #include <fec/io.h>
@@ -103,8 +106,23 @@
     ::exit(0);  // SUCCESS
 }
 
+static android::sp<android::os::IVold> GetVold() {
+    while (true) {
+        if (auto sm = android::defaultServiceManager()) {
+            if (auto binder = sm->getService(android::String16("vold"))) {
+                if (auto vold = android::interface_cast<android::os::IVold>(binder)) {
+                    return vold;
+                }
+            }
+        }
+        std::this_thread::sleep_for(2s);
+    }
+}
+
 }  // namespace
 
+using namespace std::chrono_literals;
+
 enum RemountStatus {
     REMOUNT_SUCCESS = 0,
     NOT_USERDEBUG,
@@ -117,7 +135,9 @@
     BAD_OVERLAY,
     NO_MOUNTS,
     REMOUNT_FAILED,
-    MUST_REBOOT
+    MUST_REBOOT,
+    BINDER_ERROR,
+    CHECKPOINTING
 };
 
 static int do_remount(int argc, char* argv[]) {
@@ -194,6 +214,22 @@
         return NO_FSTAB;
     }
 
+    if (android::base::GetBoolProperty("ro.virtual_ab.enabled", false) &&
+        !android::base::GetBoolProperty("ro.virtual_ab.retrofit", false)) {
+        // Virtual A/B devices can use /data as backing storage; make sure we're
+        // not checkpointing.
+        auto vold = GetVold();
+        bool checkpointing = false;
+        if (!vold->isCheckpointing(&checkpointing).isOk()) {
+            LOG(ERROR) << "Could not determine checkpointing status.";
+            return BINDER_ERROR;
+        }
+        if (checkpointing) {
+            LOG(ERROR) << "Cannot use remount when a checkpoint is in progress.";
+            return CHECKPOINTING;
+        }
+    }
+
     // Generate the list of supported overlayfs mount points.
     auto overlayfs_candidates = fs_mgr_overlayfs_candidate_list(fstab);
 
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 86090c1..2a67b8c 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -69,7 +69,7 @@
 int fs_mgr_do_mount(android::fs_mgr::Fstab* fstab, const char* n_name, char* n_blk_device,
                     char* tmp_mount_point);
 int fs_mgr_do_mount(android::fs_mgr::Fstab* fstab, const char* n_name, char* n_blk_device,
-                    char* tmp_mount_point, bool need_cp);
+                    char* tmp_mount_point, bool need_cp, bool metadata_encrypted);
 int fs_mgr_do_mount_one(const android::fs_mgr::FstabEntry& entry,
                         const std::string& mount_point = "");
 int fs_mgr_do_tmpfs_mount(const char *n_name);
diff --git a/fs_mgr/libdm/Android.bp b/fs_mgr/libdm/Android.bp
index 58241b3..e425284 100644
--- a/fs_mgr/libdm/Android.bp
+++ b/fs_mgr/libdm/Android.bp
@@ -42,6 +42,7 @@
             enabled: false,
         },
     },
+    ramdisk_available: true,
 }
 
 filegroup {
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index 250cb82..8788b5a 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -280,5 +280,12 @@
     return android::base::Join(argv, " ");
 }
 
+std::string DmTargetUser::GetParameterString() const {
+    std::vector<std::string> argv;
+    argv.push_back(std::to_string(start()));
+    argv.push_back(std::to_string(size()));
+    return android::base::Join(argv, " ");
+}
+
 }  // namespace dm
 }  // namespace android
diff --git a/fs_mgr/libdm/include/libdm/dm_target.h b/fs_mgr/libdm/include/libdm/dm_target.h
index f986cfe..57e3884 100644
--- a/fs_mgr/libdm/include/libdm/dm_target.h
+++ b/fs_mgr/libdm/include/libdm/dm_target.h
@@ -309,6 +309,14 @@
     bool is_hw_wrapped_ = false;
 };
 
+class DmTargetUser final : public DmTarget {
+  public:
+    DmTargetUser(uint64_t start, uint64_t length) : DmTarget(start, length) {}
+
+    std::string name() const override { return "user"; }
+    std::string GetParameterString() const override;
+};
+
 }  // namespace dm
 }  // namespace android
 
diff --git a/fs_mgr/libfiemap/fiemap_writer.cpp b/fs_mgr/libfiemap/fiemap_writer.cpp
index 4dd4bcc..621031a 100644
--- a/fs_mgr/libfiemap/fiemap_writer.cpp
+++ b/fs_mgr/libfiemap/fiemap_writer.cpp
@@ -45,7 +45,7 @@
 
 using namespace android::dm;
 
-// We cap the maximum number of extents as a sanity measure.
+// We cap the maximum number of extents as a robustness measure.
 static constexpr uint32_t kMaxExtents = 50000;
 
 // TODO: Fallback to using fibmap if FIEMAP_EXTENT_MERGED is set.
diff --git a/fs_mgr/libfiemap/split_fiemap_writer.cpp b/fs_mgr/libfiemap/split_fiemap_writer.cpp
index 12c7397..36bb3df 100644
--- a/fs_mgr/libfiemap/split_fiemap_writer.cpp
+++ b/fs_mgr/libfiemap/split_fiemap_writer.cpp
@@ -266,7 +266,7 @@
         cursor_file_pos_ += bytes_to_write;
     }
 
-    // If we've reached the end of the current file, close it for sanity.
+    // If we've reached the end of the current file, close it.
     if (cursor_file_pos_ == file->size()) {
         cursor_fd_ = {};
     }
diff --git a/fs_mgr/libfiemap/utility.cpp b/fs_mgr/libfiemap/utility.cpp
index bbb0510..c189855 100644
--- a/fs_mgr/libfiemap/utility.cpp
+++ b/fs_mgr/libfiemap/utility.cpp
@@ -139,8 +139,7 @@
     }
 
     *bdev_name = ::android::base::Basename(sysfs_bdev);
-    // Paranoid sanity check to make sure we just didn't get the
-    // input in return as-is.
+    // Check that the symlink doesn't point to itself.
     if (sysfs_bdev == *bdev_name) {
         LOG(ERROR) << "Malformed symlink for block device: " << sysfs_bdev;
         return false;
diff --git a/fs_mgr/libfs_avb/avb_ops.cpp b/fs_mgr/libfs_avb/avb_ops.cpp
index c192bf5..46072bb 100644
--- a/fs_mgr/libfs_avb/avb_ops.cpp
+++ b/fs_mgr/libfs_avb/avb_ops.cpp
@@ -52,16 +52,16 @@
             partition, offset, num_bytes, buffer, out_num_read);
 }
 
-static AvbIOResult dummy_read_rollback_index(AvbOps* ops ATTRIBUTE_UNUSED,
-                                             size_t rollback_index_location ATTRIBUTE_UNUSED,
-                                             uint64_t* out_rollback_index) {
+static AvbIOResult no_op_read_rollback_index(AvbOps* ops ATTRIBUTE_UNUSED,
+                                            size_t rollback_index_location ATTRIBUTE_UNUSED,
+                                            uint64_t* out_rollback_index) {
     // rollback_index has been checked in bootloader phase.
     // In user-space, returns the smallest value 0 to pass the check.
     *out_rollback_index = 0;
     return AVB_IO_RESULT_OK;
 }
 
-static AvbIOResult dummy_validate_vbmeta_public_key(
+static AvbIOResult no_op_validate_vbmeta_public_key(
         AvbOps* ops ATTRIBUTE_UNUSED, const uint8_t* public_key_data ATTRIBUTE_UNUSED,
         size_t public_key_length ATTRIBUTE_UNUSED,
         const uint8_t* public_key_metadata ATTRIBUTE_UNUSED,
@@ -76,8 +76,8 @@
     return AVB_IO_RESULT_OK;
 }
 
-static AvbIOResult dummy_read_is_device_unlocked(AvbOps* ops ATTRIBUTE_UNUSED,
-                                                 bool* out_is_unlocked) {
+static AvbIOResult no_op_read_is_device_unlocked(AvbOps* ops ATTRIBUTE_UNUSED,
+                                                bool* out_is_unlocked) {
     // The function is for bootloader to update the value into
     // androidboot.vbmeta.device_state in kernel cmdline.
     // In user-space, returns true as we don't need to update it anymore.
@@ -85,9 +85,9 @@
     return AVB_IO_RESULT_OK;
 }
 
-static AvbIOResult dummy_get_unique_guid_for_partition(AvbOps* ops ATTRIBUTE_UNUSED,
-                                                       const char* partition ATTRIBUTE_UNUSED,
-                                                       char* guid_buf, size_t guid_buf_size) {
+static AvbIOResult no_op_get_unique_guid_for_partition(AvbOps* ops ATTRIBUTE_UNUSED,
+                                                      const char* partition ATTRIBUTE_UNUSED,
+                                                      char* guid_buf, size_t guid_buf_size) {
     // The function is for bootloader to set the correct UUID
     // for a given partition in kernel cmdline.
     // In user-space, returns a faking one as we don't need to update
@@ -96,9 +96,9 @@
     return AVB_IO_RESULT_OK;
 }
 
-static AvbIOResult dummy_get_size_of_partition(AvbOps* ops ATTRIBUTE_UNUSED,
-                                               const char* partition ATTRIBUTE_UNUSED,
-                                               uint64_t* out_size_num_byte) {
+static AvbIOResult no_op_get_size_of_partition(AvbOps* ops ATTRIBUTE_UNUSED,
+                                              const char* partition ATTRIBUTE_UNUSED,
+                                              uint64_t* out_size_num_byte) {
     // The function is for bootloader to load entire content of AVB HASH partitions.
     // In user-space, returns 0 as we only need to set up AVB HASHTHREE partitions.
     *out_size_num_byte = 0;
@@ -123,15 +123,15 @@
     // We only need to provide the implementation of read_from_partition()
     // operation since that's all what is being used by the avb_slot_verify().
     // Other I/O operations are only required in bootloader but not in
-    // user-space so we set them as dummy operations. Also zero the entire
+    // user-space so we set them as no-op operations. Also zero the entire
     // struct so operations added in the future will be set to NULL.
     memset(&avb_ops_, 0, sizeof(AvbOps));
     avb_ops_.read_from_partition = read_from_partition;
-    avb_ops_.read_rollback_index = dummy_read_rollback_index;
-    avb_ops_.validate_vbmeta_public_key = dummy_validate_vbmeta_public_key;
-    avb_ops_.read_is_device_unlocked = dummy_read_is_device_unlocked;
-    avb_ops_.get_unique_guid_for_partition = dummy_get_unique_guid_for_partition;
-    avb_ops_.get_size_of_partition = dummy_get_size_of_partition;
+    avb_ops_.read_rollback_index = no_op_read_rollback_index;
+    avb_ops_.validate_vbmeta_public_key = no_op_validate_vbmeta_public_key;
+    avb_ops_.read_is_device_unlocked = no_op_read_is_device_unlocked;
+    avb_ops_.get_unique_guid_for_partition = no_op_get_unique_guid_for_partition;
+    avb_ops_.get_size_of_partition = no_op_get_size_of_partition;
 
     // Sets user_data for GetInstanceFromAvbOps() to convert it back to FsManagerAvbOps.
     avb_ops_.user_data = this;
diff --git a/fs_mgr/libfs_avb/fs_avb.cpp b/fs_mgr/libfs_avb/fs_avb.cpp
index 5d504ab..49333a1 100644
--- a/fs_mgr/libfs_avb/fs_avb.cpp
+++ b/fs_mgr/libfs_avb/fs_avb.cpp
@@ -226,7 +226,7 @@
             return nullptr;
     }
 
-    // Sanity check here because we have to use vbmeta_images_[0] below.
+    // Validity check here because we have to use vbmeta_images_[0] below.
     if (avb_handle->vbmeta_images_.size() < 1) {
         LERROR << "LoadAndVerifyVbmetaByPartition failed, no vbmeta loaded";
         return nullptr;
@@ -405,11 +405,11 @@
     //   - AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION (UNLOCKED only).
     //     Might occur in either the top-level vbmeta or a chained vbmeta.
     //   - AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED (UNLOCKED only).
-    //     Could only occur in a chained vbmeta. Because we have *dummy* operations in
+    //     Could only occur in a chained vbmeta. Because we have *no-op* operations in
     //     FsManagerAvbOps such that avb_ops->validate_vbmeta_public_key() used to validate
     //     the public key of the top-level vbmeta always pass in userspace here.
     //
-    // The following verify result won't happen, because the *dummy* operation
+    // The following verify result won't happen, because the *no-op* operation
     // avb_ops->read_rollback_index() always returns the minimum value zero. So rollbacked
     // vbmeta images, which should be caught in the bootloader stage, won't be detected here.
     //   - AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX
diff --git a/fs_mgr/libfs_avb/tests/util_test.cpp b/fs_mgr/libfs_avb/tests/util_test.cpp
index 5c388aa..a52a00d 100644
--- a/fs_mgr/libfs_avb/tests/util_test.cpp
+++ b/fs_mgr/libfs_avb/tests/util_test.cpp
@@ -222,7 +222,7 @@
     base::FilePath test_dir;
     ASSERT_TRUE(base::CreateTemporaryDirInDir(tmp_dir, "list-file-tests.", &test_dir));
 
-    // Generates dummy files to list.
+    // Generates test files to list.
     base::FilePath file_path_1 = test_dir.Append("1.txt");
     ASSERT_TRUE(base::WriteFile(file_path_1, "1", 1));
     base::FilePath file_path_2 = test_dir.Append("2.txt");
@@ -253,7 +253,7 @@
     base::FilePath test_dir;
     ASSERT_TRUE(base::CreateTemporaryDirInDir(tmp_dir, "list-file-tests.", &test_dir));
 
-    // Generates dummy files to list.
+    // Generates test files to list.
     base::FilePath file_path_1 = test_dir.Append("1.txt");
     ASSERT_TRUE(base::WriteFile(file_path_1, "1", 1));
     base::FilePath file_path_2 = test_dir.Append("2.txt");
@@ -281,7 +281,7 @@
     base::FilePath tmp_dir;
     ASSERT_TRUE(GetTempDir(&tmp_dir));
 
-    // Generates dummy files to list.
+    // Generates test files to list.
     base::FilePath no_such_dir = tmp_dir.Append("not_such_dir");
 
     auto fail = ListFiles(no_such_dir.value());
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index c37d70e..623293e 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -31,6 +31,22 @@
 namespace android {
 namespace fs_mgr {
 
+std::ostream& operator<<(std::ostream& os, const Extent& extent) {
+    switch (extent.GetExtentType()) {
+        case ExtentType::kZero: {
+            os << "type: Zero";
+            break;
+        }
+        case ExtentType::kLinear: {
+            auto linear_extent = static_cast<const LinearExtent*>(&extent);
+            os << "type: Linear, physical sectors: " << linear_extent->physical_sector()
+               << ", end sectors: " << linear_extent->end_sector();
+            break;
+        }
+    }
+    return os;
+}
+
 bool LinearExtent::AddTo(LpMetadata* out) const {
     if (device_index_ >= out->block_devices.size()) {
         LERROR << "Extent references unknown block device.";
@@ -41,6 +57,17 @@
     return true;
 }
 
+bool LinearExtent::operator==(const android::fs_mgr::Extent& other) const {
+    if (other.GetExtentType() != ExtentType::kLinear) {
+        return false;
+    }
+
+    auto other_ptr = static_cast<const LinearExtent*>(&other);
+    return num_sectors_ == other_ptr->num_sectors_ &&
+           physical_sector_ == other_ptr->physical_sector_ &&
+           device_index_ == other_ptr->device_index_;
+}
+
 bool LinearExtent::OverlapsWith(const LinearExtent& other) const {
     if (device_index_ != other.device_index()) {
         return false;
@@ -64,6 +91,10 @@
     return true;
 }
 
+bool ZeroExtent::operator==(const android::fs_mgr::Extent& other) const {
+    return other.GetExtentType() == ExtentType::kZero && num_sectors_ == other.num_sectors();
+}
+
 Partition::Partition(std::string_view name, std::string_view group_name, uint32_t attributes)
     : name_(name), group_name_(group_name), attributes_(attributes), size_(0) {}
 
@@ -205,11 +236,18 @@
         }
     }
 
-    if (IPropertyFetcher::GetInstance()->GetBoolProperty("ro.virtual_ab.enabled", false) &&
-        !always_keep_source_slot) {
-        if (!UpdateMetadataForInPlaceSnapshot(metadata.get(), source_slot_number,
-                                              target_slot_number)) {
-            return nullptr;
+    if (IPropertyFetcher::GetInstance()->GetBoolProperty("ro.virtual_ab.enabled", false)) {
+        if (always_keep_source_slot) {
+            // always_keep_source_slot implies the target build does not support snapshots.
+            // Clear unsupported attributes.
+            SetMetadataHeaderV0(metadata.get());
+        } else {
+            // !always_keep_source_slot implies the target build supports snapshots. Do snapshot
+            // updates.
+            if (!UpdateMetadataForInPlaceSnapshot(metadata.get(), source_slot_number,
+                                                  target_slot_number)) {
+                return nullptr;
+            }
         }
     }
 
@@ -511,7 +549,7 @@
     return partitions_.back().get();
 }
 
-Partition* MetadataBuilder::FindPartition(std::string_view name) {
+Partition* MetadataBuilder::FindPartition(std::string_view name) const {
     for (const auto& partition : partitions_) {
         if (partition->name() == name) {
             return partition.get();
@@ -520,7 +558,7 @@
     return nullptr;
 }
 
-PartitionGroup* MetadataBuilder::FindGroup(std::string_view group_name) {
+PartitionGroup* MetadataBuilder::FindGroup(std::string_view group_name) const {
     for (const auto& group : groups_) {
         if (group->name() == group_name) {
             return group.get();
@@ -1263,5 +1301,50 @@
     return geometry_.logical_block_size;
 }
 
+bool MetadataBuilder::VerifyExtentsAgainstSourceMetadata(
+        const MetadataBuilder& source_metadata, uint32_t source_slot_number,
+        const MetadataBuilder& target_metadata, uint32_t target_slot_number,
+        const std::vector<std::string>& partitions) {
+    for (const auto& base_name : partitions) {
+        // Find the partition in metadata with the slot suffix.
+        auto target_partition_name = base_name + SlotSuffixForSlotNumber(target_slot_number);
+        const auto target_partition = target_metadata.FindPartition(target_partition_name);
+        if (!target_partition) {
+            LERROR << "Failed to find partition " << target_partition_name << " in metadata slot "
+                   << target_slot_number;
+            return false;
+        }
+
+        auto source_partition_name = base_name + SlotSuffixForSlotNumber(source_slot_number);
+        const auto source_partition = source_metadata.FindPartition(source_partition_name);
+        if (!source_partition) {
+            LERROR << "Failed to find partition " << source_partition << " in metadata slot "
+                   << source_slot_number;
+            return false;
+        }
+
+        // We expect the partitions in the target metadata to have the identical extents as the
+        // one in the source metadata. Because they are copied in NewForUpdate.
+        if (target_partition->extents().size() != source_partition->extents().size()) {
+            LERROR << "Extents count mismatch for partition " << base_name << " target slot has "
+                   << target_partition->extents().size() << ", source slot has "
+                   << source_partition->extents().size();
+            return false;
+        }
+
+        for (size_t i = 0; i < target_partition->extents().size(); i++) {
+            const auto& src_extent = *source_partition->extents()[i];
+            const auto& tgt_extent = *target_partition->extents()[i];
+            if (tgt_extent != src_extent) {
+                LERROR << "Extents " << i << " is different for partition " << base_name;
+                LERROR << "tgt extent " << tgt_extent << "; src extent " << src_extent;
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index 1a3250a..e4b617a 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -234,7 +234,7 @@
         EXPECT_EQ(lba, aligned_lba);
     }
 
-    // Sanity check one extent.
+    // Check one extent.
     EXPECT_EQ(exported->extents.back().target_data, 3072);
 }
 
@@ -947,9 +947,10 @@
 }
 
 static void AddPartition(const std::unique_ptr<MetadataBuilder>& builder,
-                         const std::string& partition_name, uint64_t num_sectors,
-                         uint64_t start_sector, std::vector<Interval>* intervals) {
-    Partition* p = builder->AddPartition(partition_name, "group", 0);
+                         const std::string& partition_name, const std::string& group_name,
+                         uint64_t num_sectors, uint64_t start_sector,
+                         std::vector<Interval>* intervals = nullptr) {
+    Partition* p = builder->AddPartition(partition_name, group_name, 0);
     ASSERT_NE(p, nullptr);
     ASSERT_TRUE(builder->AddLinearExtent(p, "super", num_sectors, start_sector));
     ASSERT_EQ(p->extents().size(), 1);
@@ -977,17 +978,17 @@
     ASSERT_TRUE(builder->AddGroup("group", 0));
 
     std::vector<Interval> old_intervals;
-    AddPartition(builder, "system", 10229008, 2048, &old_intervals);
-    AddPartition(builder, "test_a", 648, 12709888, &old_intervals);
-    AddPartition(builder, "test_b", 625184, 12711936, &old_intervals);
-    AddPartition(builder, "test_c", 130912, 13338624, &old_intervals);
-    AddPartition(builder, "test_d", 888, 13469696, &old_intervals);
-    AddPartition(builder, "test_e", 888, 13471744, &old_intervals);
-    AddPartition(builder, "test_f", 888, 13475840, &old_intervals);
-    AddPartition(builder, "test_g", 888, 13477888, &old_intervals);
+    AddPartition(builder, "system", "group", 10229008, 2048, &old_intervals);
+    AddPartition(builder, "test_a", "group", 648, 12709888, &old_intervals);
+    AddPartition(builder, "test_b", "group", 625184, 12711936, &old_intervals);
+    AddPartition(builder, "test_c", "group", 130912, 13338624, &old_intervals);
+    AddPartition(builder, "test_d", "group", 888, 13469696, &old_intervals);
+    AddPartition(builder, "test_e", "group", 888, 13471744, &old_intervals);
+    AddPartition(builder, "test_f", "group", 888, 13475840, &old_intervals);
+    AddPartition(builder, "test_g", "group", 888, 13477888, &old_intervals);
 
     // Don't track the first vendor interval, since it will get extended.
-    AddPartition(builder, "vendor", 2477920, 10231808, nullptr);
+    AddPartition(builder, "vendor", "group", 2477920, 10231808, nullptr);
 
     std::vector<Interval> new_intervals;
 
@@ -1066,3 +1067,30 @@
     ASSERT_NE(p, nullptr);
     ASSERT_FALSE(builder->ResizePartition(p, 18446744073709551615ULL));
 }
+
+TEST_F(BuilderTest, VerifyExtent) {
+    auto source_builder = MetadataBuilder::New(4096 * 50, 40960, 2);
+    ASSERT_NE(source_builder, nullptr);
+    ASSERT_TRUE(source_builder->AddGroup("test_group_a", 40960));
+    ASSERT_TRUE(source_builder->AddGroup("test_group_b", 40960));
+    AddPartition(source_builder, "system_a", "test_group_a", 8192, 2048);
+    AddPartition(source_builder, "vendor_a", "test_group_a", 10240, 10240);
+    AddPartition(source_builder, "system_b", "test_group_b", 8192, 20480);
+
+    auto target_builder = MetadataBuilder::New(4096 * 50, 40960, 2);
+    ASSERT_NE(target_builder, nullptr);
+    ASSERT_TRUE(target_builder->AddGroup("test_group_b", 40960));
+    AddPartition(target_builder, "system_b", "test_group_b", 8192, 2048);
+    AddPartition(target_builder, "vendor_b", "test_group_b", 10240, 10240);
+
+    ASSERT_TRUE(MetadataBuilder::VerifyExtentsAgainstSourceMetadata(
+            *source_builder, 0, *target_builder, 1, std::vector<std::string>{"system", "vendor"}));
+
+    target_builder->RemovePartition("vendor_b");
+    ASSERT_FALSE(target_builder->VerifyExtentsAgainstSourceMetadata(
+            *source_builder, 0, *target_builder, 1, std::vector<std::string>{"vendor"}));
+
+    AddPartition(target_builder, "vendor_b", "test_group_b", 1000, 10240);
+    ASSERT_FALSE(target_builder->VerifyExtentsAgainstSourceMetadata(
+            *source_builder, 0, *target_builder, 1, std::vector<std::string>{"vendor"}));
+}
diff --git a/fs_mgr/liblp/device_test.cpp b/fs_mgr/liblp/device_test.cpp
index 6af9d94..236fd8d 100644
--- a/fs_mgr/liblp/device_test.cpp
+++ b/fs_mgr/liblp/device_test.cpp
@@ -47,7 +47,7 @@
     BlockDeviceInfo device_info;
     ASSERT_TRUE(opener.GetInfo(fs_mgr_get_super_partition_name(), &device_info));
 
-    // Sanity check that the device doesn't give us some weird inefficient
+    // Check that the device doesn't give us some weird inefficient
     // alignment.
     EXPECT_EQ(device_info.alignment % LP_SECTOR_SIZE, 0);
     EXPECT_EQ(device_info.logical_block_size % LP_SECTOR_SIZE, 0);
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index 89a47b1..54f31bc 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -42,6 +42,11 @@
 // Name of the default group in a metadata.
 static constexpr std::string_view kDefaultGroup = "default";
 
+enum class ExtentType {
+    kZero,
+    kLinear,
+};
+
 // Abstraction around dm-targets that can be encoded into logical partition tables.
 class Extent {
   public:
@@ -50,6 +55,10 @@
 
     virtual bool AddTo(LpMetadata* out) const = 0;
     virtual LinearExtent* AsLinearExtent() { return nullptr; }
+    virtual ExtentType GetExtentType() const = 0;
+
+    virtual bool operator==(const Extent& other) const = 0;
+    virtual bool operator!=(const Extent& other) const { return !(*this == other); }
 
     uint64_t num_sectors() const { return num_sectors_; }
     void set_num_sectors(uint64_t num_sectors) { num_sectors_ = num_sectors; }
@@ -58,6 +67,8 @@
     uint64_t num_sectors_;
 };
 
+std::ostream& operator<<(std::ostream& os, const Extent& extent);
+
 // This corresponds to a dm-linear target.
 class LinearExtent final : public Extent {
   public:
@@ -66,6 +77,9 @@
 
     bool AddTo(LpMetadata* metadata) const override;
     LinearExtent* AsLinearExtent() override { return this; }
+    ExtentType GetExtentType() const override { return ExtentType::kLinear; }
+
+    bool operator==(const Extent& other) const override;
 
     uint64_t physical_sector() const { return physical_sector_; }
     uint64_t end_sector() const { return physical_sector_ + num_sectors_; }
@@ -87,6 +101,9 @@
     explicit ZeroExtent(uint64_t num_sectors) : Extent(num_sectors) {}
 
     bool AddTo(LpMetadata* out) const override;
+    ExtentType GetExtentType() const override { return ExtentType::kZero; }
+
+    bool operator==(const Extent& other) const override;
 };
 
 class PartitionGroup final {
@@ -208,8 +225,10 @@
     // metadata may not have the target slot's devices listed yet, in which
     // case, it is automatically upgraded to include all available block
     // devices.
-    // If |always_keep_source_slot| is set, on a Virtual A/B device, source slot
-    // partitions are kept. This is useful when applying a downgrade package.
+    // If |always_keep_source_slot| is set, on a Virtual A/B device
+    // - source slot partitions are kept.
+    // - UPDATED flag is cleared.
+    // This is useful when applying a downgrade package.
     static std::unique_ptr<MetadataBuilder> NewForUpdate(const IPartitionOpener& opener,
                                                          const std::string& source_partition,
                                                          uint32_t source_slot_number,
@@ -241,6 +260,14 @@
         return New(device_info, metadata_max_size, metadata_slot_count);
     }
 
+    // Verifies that the given partitions in the metadata have the same extents as the source
+    // metadata.
+    static bool VerifyExtentsAgainstSourceMetadata(const MetadataBuilder& source_metadata,
+                                                   uint32_t source_slot_number,
+                                                   const MetadataBuilder& target_metadata,
+                                                   uint32_t target_slot_number,
+                                                   const std::vector<std::string>& partitions);
+
     // Define a new partition group. By default there is one group called
     // "default", with an unrestricted size. A non-zero size will restrict the
     // total space used by all partitions in the group.
@@ -265,10 +292,10 @@
     void RemovePartition(std::string_view name);
 
     // Find a partition by name. If no partition is found, nullptr is returned.
-    Partition* FindPartition(std::string_view name);
+    Partition* FindPartition(std::string_view name) const;
 
     // Find a group by name. If no group is found, nullptr is returned.
-    PartitionGroup* FindGroup(std::string_view name);
+    PartitionGroup* FindGroup(std::string_view name) const;
 
     // Add a predetermined extent to a partition.
     bool AddLinearExtent(Partition* partition, const std::string& block_device,
diff --git a/fs_mgr/liblp/partition_opener.cpp b/fs_mgr/liblp/partition_opener.cpp
index 1d4db85..3d3dde6 100644
--- a/fs_mgr/liblp/partition_opener.cpp
+++ b/fs_mgr/liblp/partition_opener.cpp
@@ -49,7 +49,7 @@
         // Dynamic System Update is installed to an sdcard, which won't be in
         // the boot device list.
         //
-        // We whitelist because most devices in /dev/block are not valid for
+        // mmcblk* is allowed because most devices in /dev/block are not valid for
         // storing fiemaps.
         if (android::base::StartsWith(path, "mmcblk")) {
             return "/dev/block/" + path;
diff --git a/fs_mgr/liblp/reader.cpp b/fs_mgr/liblp/reader.cpp
index e6fd9f7..24ccc0f 100644
--- a/fs_mgr/liblp/reader.cpp
+++ b/fs_mgr/liblp/reader.cpp
@@ -174,7 +174,7 @@
         return false;
     }
 
-    // Do basic sanity checks before computing the checksum.
+    // Do basic validity checks before computing the checksum.
     if (header.magic != LP_METADATA_HEADER_MAGIC) {
         LERROR << "Logical partition metadata has invalid magic value.";
         return false;
@@ -255,7 +255,7 @@
 
     LpMetadataHeader& header = metadata->header;
 
-    // Sanity check the table size.
+    // Check the table size.
     if (header.tables_size > geometry.metadata_max_size) {
         LERROR << "Invalid partition metadata header table size.";
         return nullptr;
diff --git a/fs_mgr/liblp/utility.cpp b/fs_mgr/liblp/utility.cpp
index 48c5c83..d8e171b 100644
--- a/fs_mgr/liblp/utility.cpp
+++ b/fs_mgr/liblp/utility.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <fcntl.h>
+#include <inttypes.h>
 #include <stdint.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -29,6 +30,7 @@
 #include <vector>
 
 #include <android-base/file.h>
+#include <android-base/stringprintf.h>
 #include <ext4_utils/ext4_utils.h>
 #include <openssl/sha.h>
 
@@ -285,5 +287,42 @@
     return true;
 }
 
+inline std::string ToHexString(uint64_t value) {
+    return android::base::StringPrintf("0x%" PRIx64, value);
+}
+
+void SetMetadataHeaderV0(LpMetadata* metadata) {
+    if (metadata->header.minor_version <= LP_METADATA_MINOR_VERSION_MIN) {
+        return;
+    }
+    LINFO << "Forcefully setting metadata header version " << LP_METADATA_MAJOR_VERSION << "."
+          << metadata->header.minor_version << " to " << LP_METADATA_MAJOR_VERSION << "."
+          << LP_METADATA_MINOR_VERSION_MIN;
+    metadata->header.minor_version = LP_METADATA_MINOR_VERSION_MIN;
+    metadata->header.header_size = sizeof(LpMetadataHeaderV1_0);
+
+    // Retrofit Virtual A/B devices should have version 10.1, so flags shouldn't be set.
+    // Warn if this is the case, but zero it out anyways.
+    if (metadata->header.flags) {
+        LWARN << "Zeroing unexpected flags: " << ToHexString(metadata->header.flags);
+    }
+
+    // Zero out all fields beyond LpMetadataHeaderV0.
+    static_assert(sizeof(metadata->header) > sizeof(LpMetadataHeaderV1_0));
+    memset(reinterpret_cast<uint8_t*>(&metadata->header) + sizeof(LpMetadataHeaderV1_0), 0,
+           sizeof(metadata->header) - sizeof(LpMetadataHeaderV1_0));
+
+    // Clear partition attributes unknown to V0.
+    // On retrofit Virtual A/B devices, UPDATED flag may be set, so only log info here.
+    for (auto& partition : metadata->partitions) {
+        if (partition.attributes & ~LP_PARTITION_ATTRIBUTE_MASK_V0) {
+            LINFO << "Clearing " << GetPartitionName(partition)
+                  << " partition attribute: " << ToHexString(partition.attributes);
+        }
+
+        partition.attributes &= LP_PARTITION_ATTRIBUTE_MASK_V0;
+    }
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/utility.h b/fs_mgr/liblp/utility.h
index c4fe3ed..aa3a6a0 100644
--- a/fs_mgr/liblp/utility.h
+++ b/fs_mgr/liblp/utility.h
@@ -103,6 +103,10 @@
 bool UpdateMetadataForInPlaceSnapshot(LpMetadata* metadata, uint32_t source_slot_number,
                                       uint32_t target_slot_number);
 
+// Forcefully set metadata header version to 1.0, clearing any incompatible flags and attributes
+// so that when downgrading to a build with liblp V0, the device still boots.
+void SetMetadataHeaderV0(LpMetadata* metadata);
+
 }  // namespace fs_mgr
 }  // namespace android
 
diff --git a/fs_mgr/liblp/writer.cpp b/fs_mgr/liblp/writer.cpp
index 8bf1ee9..2708efa 100644
--- a/fs_mgr/liblp/writer.cpp
+++ b/fs_mgr/liblp/writer.cpp
@@ -81,8 +81,8 @@
     return header_blob + tables;
 }
 
-// Perform sanity checks so we don't accidentally overwrite valid metadata
-// with potentially invalid metadata, or random partition data with metadata.
+// Perform checks so we don't accidentally overwrite valid metadata with
+// potentially invalid metadata, or random partition data with metadata.
 static bool ValidateAndSerializeMetadata([[maybe_unused]] const IPartitionOpener& opener,
                                          const LpMetadata& metadata, const std::string& slot_suffix,
                                          std::string* blob) {
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 95301ff..eaef180 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -310,3 +310,36 @@
     auto_gen_config: true,
     require_root: true,
 }
+
+cc_defaults {
+    name: "snapuserd_defaults",
+    srcs: [
+        "snapuserd.cpp",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror"
+    ],
+
+    static_libs: [
+        "libbase",
+        "liblog",
+        "libdm",
+    ],
+}
+
+cc_binary {
+    name: "snapuserd",
+    defaults: ["snapuserd_defaults"],
+}
+
+cc_binary {
+    name: "snapuserd_ramdisk",
+    stem: "snapuserd",
+    defaults: ["snapuserd_defaults"],
+
+    ramdisk: true,
+    static_executable: true,
+    system_shared_libs: [],
+}
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 3c2c776..a4a3150 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -553,9 +553,8 @@
     // This should only be called in recovery.
     bool UnmapAllPartitions();
 
-    // Sanity check no snapshot overflows. Note that this returns false negatives if the snapshot
-    // overflows, then is remapped and not written afterwards. Hence, the function may only serve
-    // as a sanity check.
+    // Check no snapshot overflows. Note that this returns false negatives if the snapshot
+    // overflows, then is remapped and not written afterwards.
     bool EnsureNoOverflowSnapshot(LockedFile* lock);
 
     enum class Slot { Unknown, Source, Target };
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 55214f5..b49f99e 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -300,9 +300,9 @@
         LOG(ERROR) << "SnapshotStatus has no name.";
         return false;
     }
-    // Sanity check these sizes. Like liblp, we guarantee the partition size
-    // is respected, which means it has to be sector-aligned. (This guarantee
-    // is useful for locating avb footers correctly). The COW file size, however,
+    // Check these sizes. Like liblp, we guarantee the partition size is
+    // respected, which means it has to be sector-aligned. (This guarantee is
+    // useful for locating avb footers correctly). The COW file size, however,
     // can be arbitrarily larger than specified, so we can safely round it up.
     if (status->device_size() % kSectorSize != 0) {
         LOG(ERROR) << "Snapshot " << status->name()
@@ -351,7 +351,6 @@
     }
 
     // The COW file size should have been rounded up to the nearest sector in CreateSnapshot.
-    // Sanity check this.
     if (status.cow_file_size() % kSectorSize != 0) {
         LOG(ERROR) << "Snapshot " << name << " COW file size is not a multiple of the sector size: "
                    << status.cow_file_size();
@@ -1846,7 +1845,7 @@
         PLOG(ERROR) << "Open failed: " << file;
         return nullptr;
     }
-    if (lock_flags != 0 && flock(fd, lock_flags) < 0) {
+    if (lock_flags != 0 && TEMP_FAILURE_RETRY(flock(fd, lock_flags)) < 0) {
         PLOG(ERROR) << "Acquire flock failed: " << file;
         return nullptr;
     }
@@ -1857,7 +1856,7 @@
 }
 
 SnapshotManager::LockedFile::~LockedFile() {
-    if (flock(fd_, LOCK_UN) < 0) {
+    if (TEMP_FAILURE_RETRY(flock(fd_, LOCK_UN)) < 0) {
         PLOG(ERROR) << "Failed to unlock file: " << path_;
     }
 }
@@ -2520,7 +2519,19 @@
         LOG(INFO) << "EnsureMetadataMounted does nothing in Android mode.";
         return std::unique_ptr<AutoUnmountDevice>(new AutoUnmountDevice());
     }
-    return AutoUnmountDevice::New(device_->GetMetadataDir());
+    auto ret = AutoUnmountDevice::New(device_->GetMetadataDir());
+    if (ret == nullptr) return nullptr;
+
+    // In rescue mode, it is possible to erase and format metadata, but /metadata/ota is not
+    // created to execute snapshot updates. Hence, subsequent calls is likely to fail because
+    // Lock*() fails. By failing early and returning nullptr here, update_engine_sideload can
+    // treat this case as if /metadata is not mounted.
+    if (!LockShared()) {
+        LOG(WARNING) << "/metadata is mounted, but errors occur when acquiring a shared lock. "
+                        "Subsequent calls to SnapshotManager will fail. Unmounting /metadata now.";
+        return nullptr;
+    }
+    return ret;
 }
 
 bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callback) {
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz.cpp b/fs_mgr/libsnapshot/snapshot_fuzz.cpp
index 5b145c3..aced3ed 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz.cpp
@@ -141,7 +141,7 @@
                        const RecoveryCreateSnapshotDevicesArgs& args) {
     std::unique_ptr<AutoDevice> device;
     if (args.has_metadata_device_object()) {
-        device = std::make_unique<DummyAutoDevice>(args.metadata_mounted());
+        device = std::make_unique<NoOpAutoDevice>(args.metadata_mounted());
     }
     return snapshot->RecoveryCreateSnapshotDevices(device);
 }
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index fa327b8..5319e69 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -35,9 +35,9 @@
 class AutoMemBasedDir;
 class SnapshotFuzzDeviceInfo;
 
-class DummyAutoDevice : public AutoDevice {
+class NoOpAutoDevice : public AutoDevice {
   public:
-    DummyAutoDevice(bool mounted) : AutoDevice(mounted ? "dummy" : "") {}
+    NoOpAutoDevice(bool mounted) : AutoDevice(mounted ? "no_op" : "") {}
 };
 
 struct SnapshotTestModule {
diff --git a/fs_mgr/libsnapshot/snapshot_metadata_updater.cpp b/fs_mgr/libsnapshot/snapshot_metadata_updater.cpp
index 051584c..12101a2 100644
--- a/fs_mgr/libsnapshot/snapshot_metadata_updater.cpp
+++ b/fs_mgr/libsnapshot/snapshot_metadata_updater.cpp
@@ -173,9 +173,9 @@
         if (iter != groups_.end()) {
             continue;
         }
-        // Update package metadata doesn't have this group. Before deleting it, sanity check that it
-        // doesn't have any partitions left. Update metadata shouldn't assign any partitions to this
-        // group, so all partitions that originally belong to this group should be moved by
+        // Update package metadata doesn't have this group. Before deleting it, check that it
+        // doesn't have any partitions left. Update metadata shouldn't assign any partitions to
+        // this group, so all partitions that originally belong to this group should be moved by
         // MovePartitionsToDefault at this point.
         auto existing_partitions_in_group = builder_->ListPartitionsInGroup(existing_group_name);
         if (!existing_partitions_in_group.empty()) {
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
new file mode 100644
index 0000000..a6ff4fd
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2020 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 <linux/types.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <libdm/dm.h>
+
+using android::base::unique_fd;
+
+#define DM_USER_MAP_READ 0
+#define DM_USER_MAP_WRITE 1
+
+struct dm_user_message {
+    __u64 seq;
+    __u64 type;
+    __u64 flags;
+    __u64 sector;
+    __u64 len;
+    __u8 buf[];
+};
+
+using namespace android::dm;
+
+static int daemon_main(const std::string& device) {
+    unique_fd block_fd(open(device.c_str(), O_RDWR));
+    if (block_fd < 0) {
+        PLOG(ERROR) << "Unable to open " << device;
+        return 1;
+    }
+
+    unique_fd ctrl_fd(open("/dev/dm-user", O_RDWR));
+    if (ctrl_fd < 0) {
+        PLOG(ERROR) << "Unable to open /dev/dm-user";
+        return 1;
+    }
+
+    size_t buf_size = 1UL << 16;
+    auto buf = std::make_unique<char>(buf_size);
+
+    /* Just keeps pumping messages between userspace and the kernel.  We won't
+     * actually be doing anything, but the sequence numbers line up so it'll at
+     * least make forward progress. */
+    while (true) {
+        struct dm_user_message* msg = (struct dm_user_message*)buf.get();
+
+        memset(buf.get(), 0, buf_size);
+
+        ssize_t readed = read(ctrl_fd.get(), buf.get(), buf_size);
+        if (readed < 0) {
+            PLOG(ERROR) << "Control read failed, trying with more space";
+            buf_size *= 2;
+            buf = std::make_unique<char>(buf_size);
+            continue;
+        }
+
+        LOG(DEBUG) << android::base::StringPrintf("read() from dm-user returned %d bytes:",
+                                                  (int)readed);
+        LOG(DEBUG) << android::base::StringPrintf("    msg->seq:    0x%016llx", msg->seq);
+        LOG(DEBUG) << android::base::StringPrintf("    msg->type:   0x%016llx", msg->type);
+        LOG(DEBUG) << android::base::StringPrintf("    msg->flags:  0x%016llx", msg->flags);
+        LOG(DEBUG) << android::base::StringPrintf("    msg->sector: 0x%016llx", msg->sector);
+        LOG(DEBUG) << android::base::StringPrintf("    msg->len:    0x%016llx", msg->len);
+
+        switch (msg->type) {
+            case DM_USER_MAP_READ: {
+                LOG(DEBUG) << android::base::StringPrintf(
+                        "Responding to read of sector %lld with %lld bytes data", msg->sector,
+                        msg->len);
+
+                if ((sizeof(*msg) + msg->len) > buf_size) {
+                    auto old_buf = std::move(buf);
+                    buf_size = sizeof(*msg) + msg->len;
+                    buf = std::make_unique<char>(buf_size);
+                    memcpy(buf.get(), old_buf.get(), sizeof(*msg));
+                    msg = (struct dm_user_message*)buf.get();
+                }
+
+                if (lseek(block_fd.get(), msg->sector * 512, SEEK_SET) < 0) {
+                    PLOG(ERROR) << "lseek failed: " << device;
+                    return 7;
+                }
+                if (!android::base::ReadFully(block_fd.get(), msg->buf, msg->len)) {
+                    PLOG(ERROR) << "read failed: " << device;
+                    return 7;
+                }
+
+                if (!android::base::WriteFully(ctrl_fd.get(), buf.get(), sizeof(*msg) + msg->len)) {
+                    PLOG(ERROR) << "write control failed";
+                    return 3;
+                }
+                break;
+            }
+
+            case DM_USER_MAP_WRITE:
+                abort();
+                break;
+        }
+
+        LOG(DEBUG) << "read() finished, next message";
+    }
+
+    return 0;
+}
+
+int main([[maybe_unused]] int argc, char** argv) {
+    android::base::InitLogging(argv, &android::base::KernelLogger);
+    daemon_main(argv[1]);
+    return 0;
+}
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index 82c4262..d56f7f2 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -15,13 +15,17 @@
 
 adb remount tests
 
---color                     Dress output with highlighting colors
---help                      This help
---no-wait-screen            Do not wait for display screen to settle
---print-time                Report the test duration
---serial                    Specify device (must if multiple are present)
---wait-adb <duration>       adb wait timeout
---wait-fastboot <duration>  fastboot wait timeout
+-c --color                     Dress output with highlighting colors
+-h --help                      This help
+-D --no-wait-screen            Do not wait for display screen to settle
+-t --print-time                Report the test duration
+-s --serial                    Specify device (must if multiple are present)"
+if [ -n "`which timeout`" ]; then
+  USAGE="${USAGE}
+-a --wait-adb <duration>       adb wait timeout
+-f --wait-fastboot <duration>  fastboot wait timeout"
+fi
+USAGE="${USAGE}
 
 Conditions:
  - Must be a userdebug build.
@@ -46,10 +50,10 @@
 ESCAPE="`echo | tr '\n' '\033'`"
 # A _real_ embedded carriage return character
 CR="`echo | tr '\n' '\r'`"
-GREEN="${ESCAPE}[38;5;40m"
-RED="${ESCAPE}[38;5;196m"
-ORANGE="${ESCAPE}[38;5;255:165:0m"
-BLUE="${ESCAPE}[35m"
+GREEN="${ESCAPE}[32m"
+RED="${ESCAPE}[31m"
+YELLOW="${ESCAPE}[33m"
+BLUE="${ESCAPE}[34m"
 NORMAL="${ESCAPE}[0m"
 TMPDIR=${TMPDIR:-/tmp}
 print_time=false
@@ -72,7 +76,7 @@
     if [ -n "${ANDROID_SERIAL}" ]; then
       grep "^${ANDROID_SERIAL}[${SPACE}${TAB}]" > /dev/null
     else
-      wc -l | grep '^1$' >/dev/null
+      wc -l | grep "^[${SPACE}${TAB}]*1\$" >/dev/null
     fi
 }
 
@@ -85,7 +89,7 @@
     if [ -n "${ANDROID_SERIAL}" ]; then
       grep "^${ANDROID_SERIAL}[${SPACE}${TAB}]" > /dev/null
     else
-      wc -l | grep '^1$' >/dev/null
+      wc -l | grep "^[${SPACE}${TAB}]*1\$" >/dev/null
     fi
 }
 
@@ -100,7 +104,7 @@
       grep "^${ANDROID_SERIAL}[${SPACE}${TAB}][${SPACE}${TAB}]*recovery\$" >/dev/null
     return ${?}
   fi
-  if echo "${list}" | wc -l | grep '^1$' >/dev/null; then
+  if echo "${list}" | wc -l | grep "^[${SPACE}${TAB}]*1\$" >/dev/null; then
     echo "${list}" |
       grep "[${SPACE}${TAB}]recovery\$" >/dev/null
     return ${?}
@@ -143,7 +147,7 @@
   adb logcat "${@}" </dev/null |
     tr -d '\r' |
     grep -v 'logd    : logdr: UID=' |
-    sed -e '${/------- beginning of kernel/d}' -e 's/^[0-1][0-9]-[0-3][0-9] //'
+    sed -e '${ /------- beginning of kernel/d }' -e 's/^[0-1][0-9]-[0-3][0-9] //'
 }
 
 [ "USAGE: avc_check >/dev/stderr
@@ -160,7 +164,7 @@
   if [ -z "${L}" ]; then
     return
   fi
-  echo "${ORANGE}[  WARNING ]${NORMAL} unlabeled sepolicy violations:" >&2
+  echo "${YELLOW}[  WARNING ]${NORMAL} unlabeled sepolicy violations:" >&2
   echo "${L}" | sed "s/^/${INDENT}/" >&2
 }
 
@@ -284,7 +288,7 @@
   local start=`date +%s`
   local duration=
   local ret
-  if [ -n "${1}" ]; then
+  if [ -n "${1}" -a -n "`which timeout`" ]; then
     USB_DEVICE=`usb_devnum --next`
     duration=`format_duration ${1}`
     echo -n ". . . waiting ${duration}" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}"
@@ -299,7 +303,7 @@
   if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${ORANGE}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}" >&2
+      echo "${YELLOW}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}" >&2
     fi
   fi
   local end=`date +%s`
@@ -359,18 +363,22 @@
     echo "(In adb mode `adb_user`)"
   else
     echo "(USB stack borken for ${USB_ADDRESS})"
-    USB_DEVICE=`usb_devnum`
-    if [ -n "${USB_DEVICE}" ]; then
-      echo "# lsusb -v -s ${USB_DEVICE#dev}"
-      local D=`lsusb -v -s ${USB_DEVICE#dev} 2>&1`
-      if [ -n "${D}" ]; then
-        echo "${D}"
-      else
-        lsusb -v
+    if [ -n "`which usb_devnum`" ]; then
+      USB_DEVICE=`usb_devnum`
+      if [ -n "`which lsusb`" ]; then
+        if [ -n "${USB_DEVICE}" ]; then
+          echo "# lsusb -v -s ${USB_DEVICE#dev}"
+          local D=`lsusb -v -s ${USB_DEVICE#dev} 2>&1`
+          if [ -n "${D}" ]; then
+            echo "${D}"
+          else
+            lsusb -v
+          fi
+        else
+          echo "# lsusb -v (expected device missing)"
+          lsusb -v
+        fi
       fi
-    else
-      echo "# lsusb -v (expected device missing)"
-      lsusb -v
     fi >&2
   fi
 }
@@ -382,7 +390,7 @@
   local ret
   # fastboot has no wait-for-device, but it does an automatic
   # wait and requires (even a nonsensical) command to do so.
-  if [ -n "${1}" ]; then
+  if [ -n "${1}" -a -n "`which timeout`" ]; then
     USB_DEVICE=`usb_devnum --next`
     echo -n ". . . waiting `format_duration ${1}`" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}"
     timeout --preserve-status --signal=KILL ${1} fastboot wait-for-device >/dev/null 2>/dev/null
@@ -398,7 +406,7 @@
   if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${ORANGE}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
+      echo "${YELLOW}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
     fi >&2
   fi
   return ${ret}
@@ -409,7 +417,7 @@
 Returns: waits until the device has returned for recovery or optional timeout" ]
 recovery_wait() {
   local ret
-  if [ -n "${1}" ]; then
+  if [ -n "${1}" -a -n "`which timeout`" ]; then
     USB_DEVICE=`usb_devnum --next`
     echo -n ". . . waiting `format_duration ${1}`" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}"
     timeout --preserve-status --signal=KILL ${1} adb wait-for-recovery 2>/dev/null
@@ -423,7 +431,7 @@
   if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${ORANGE}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
+      echo "${YELLOW}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
     fi >&2
   fi
   return ${ret}
@@ -732,6 +740,7 @@
   grep -v \
     -e "^\(overlay\|tmpfs\|none\|sysfs\|proc\|selinuxfs\|debugfs\|bpf\) " \
     -e "^\(binfmt_misc\|cg2_bpf\|pstore\|tracefs\|adb\|mtp\|ptp\|devpts\) " \
+    -e "^\(ramdumpfs\) " \
     -e " functionfs " \
     -e "^\(/data/media\|/dev/block/loop[0-9]*\) " \
     -e "^rootfs / rootfs rw," \
@@ -753,13 +762,28 @@
 ##  MAINLINE
 ##
 
-OPTIONS=`getopt --alternative --unquoted \
-                --longoptions help,serial:,colour,color,no-colour,no-color \
-                --longoptions wait-adb:,wait-fastboot: \
-                --longoptions wait-screen,wait-display \
-                --longoptions no-wait-screen,no-wait-display \
-                --longoptions gtest_print_time,print-time \
-                -- "?hs:" ${*}` ||
+HOSTOS=`uname`
+GETOPTS="--alternative --unquoted
+         --longoptions help,serial:,colour,color,no-colour,no-color
+         --longoptions wait-adb:,wait-fastboot:
+         --longoptions wait-screen,wait-display
+         --longoptions no-wait-screen,no-wait-display
+         --longoptions gtest_print_time,print-time
+         --"
+if [ "Darwin" = "${HOSTOS}" ]; then
+  GETOPTS=
+  USAGE="`echo \"${USAGE}\" |
+            sed 's/--color/       /g
+                 1s/--help/-h/
+                 s/--help/      /g
+                 s/--no-wait-screen/                /g
+                 s/--print-time/            /g
+                 1s/--serial/-s/
+                 s/--serial/        /g
+                 s/--wait-adb/          /g
+                 s/--wait-fastboot/               /g'`"
+fi
+OPTIONS=`getopt ${GETOPTS} "?a:cCdDf:hs:t" ${*}` ||
   ( echo "${USAGE}" >&2 ; false ) ||
   die "getopt failure"
 set -- ${OPTIONS}
@@ -775,26 +799,26 @@
       export ANDROID_SERIAL=${2}
       shift
       ;;
-    --color | --colour)
+    -c | --color | --colour)
       color=true
       ;;
-    --no-color | --no-colour)
+    -C | --no-color | --no-colour)
       color=false
       ;;
-    --no-wait-display | --no-wait-screen)
+    -D | --no-wait-display | --no-wait-screen)
       screen_wait=false
       ;;
-    --wait-display | --wait-screen)
+    -d | --wait-display | --wait-screen)
       screen_wait=true
       ;;
-    --print-time | --gtest_print_time)
+    -t | --print-time | --gtest_print_time)
       print_time=true
       ;;
-    --wait-adb)
+    -a | --wait-adb)
       ADB_WAIT=${2}
       shift
       ;;
-    --wait-fastboot)
+    -f | --wait-fastboot)
       FASTBOOT_WAIT=${2}
       shift
       ;;
@@ -815,7 +839,7 @@
 if ! ${color}; then
   GREEN=""
   RED=""
-  ORANGE=""
+  YELLOW=""
   BLUE=""
   NORMAL=""
 fi
@@ -827,14 +851,14 @@
 inFastboot && die "device in fastboot mode"
 inRecovery && die "device in recovery mode"
 if ! inAdb; then
-  echo "${ORANGE}[  WARNING ]${NORMAL} device not in adb mode" >&2
+  echo "${YELLOW}[  WARNING ]${NORMAL} device not in adb mode" >&2
   adb_wait ${ADB_WAIT}
 fi
 inAdb || die "specified device not in adb mode"
 isDebuggable || die "device not a debug build"
 enforcing=true
 if ! adb_su getenforce </dev/null | grep 'Enforcing' >/dev/null; then
-  echo "${ORANGE}[  WARNING ]${NORMAL} device does not have sepolicy in enforcing mode" >&2
+  echo "${YELLOW}[  WARNING ]${NORMAL} device does not have sepolicy in enforcing mode" >&2
   enforcing=false
 fi
 
@@ -846,9 +870,13 @@
 [ -n "${D}" ] || D=`get_property ro.boot.serialno`
 [ -z "${D}" -o -n "${ANDROID_SERIAL}" ] || ANDROID_SERIAL=${D}
 USB_SERIAL=
-[ -z "${ANDROID_SERIAL}" ] || USB_SERIAL=`find /sys/devices -name serial |
-                                          grep usb |
-                                          xargs -r grep -l ${ANDROID_SERIAL}`
+if [ -n "${ANDROID_SERIAL}" -a "Darwin" != "${HOSTOS}" ]; then
+  USB_SERIAL="`find /sys/devices -name serial | grep usb`"
+  if [ -n "${USB_SERIAL}" ]; then
+    USB_SERIAL=`echo "${USB_SERIAL}" |
+                  xargs grep -l ${ANDROID_SERIAL}`
+  fi
+fi
 USB_ADDRESS=
 if [ -n "${USB_SERIAL}" ]; then
   USB_ADDRESS=${USB_SERIAL%/serial}
@@ -860,13 +888,16 @@
 BUILD_DESCRIPTION=`get_property ro.build.description`
 [ -z "${BUILD_DESCRIPTION}" ] ||
   echo "${BLUE}[     INFO ]${NORMAL} ${BUILD_DESCRIPTION}" >&2
+KERNEL_VERSION="`adb_su cat /proc/version </dev/null 2>/dev/null`"
+[ -z "${KERNEL_VERSION}" ] ||
+  echo "${BLUE}[     INFO ]${NORMAL} ${KERNEL_VERSION}" >&2
 ACTIVE_SLOT=`get_active_slot`
 [ -z "${ACTIVE_SLOT}" ] ||
   echo "${BLUE}[     INFO ]${NORMAL} active slot is ${ACTIVE_SLOT}" >&2
 
 # Acquire list of system partitions
 
-PARTITIONS=`adb_su cat /vendor/etc/fstab* |
+PARTITIONS=`adb_su cat /vendor/etc/fstab* </dev/null |
               skip_administrative_mounts |
               sed -n "s@^\([^ ${TAB}/][^ ${TAB}/]*\)[ ${TAB}].*[, ${TAB}]ro[, ${TAB}].*@\1@p" |
               sort -u |
@@ -903,9 +934,12 @@
   done
 
 # If reboot too soon after fresh flash, could trip device update failure logic
+if ${screen_wait}; then
+  echo "${YELLOW}[  WARNING ]${NORMAL} waiting for screen to come up. Consider --no-wait-screen option" >&2
+fi
 if ! wait_for_screen && ${screen_wait}; then
   screen_wait=false
-  echo "${ORANGE}[  WARNING ]${NORMAL} not healthy, no launcher, skipping wait for screen" >&2
+  echo "${YELLOW}[  WARNING ]${NORMAL} not healthy, no launcher, skipping wait for screen" >&2
 fi
 
 # Can we test remount -R command?
@@ -954,7 +988,7 @@
   adb_su remount -R system </dev/null
   err=${?}
   if [ "${err}" != 0 ]; then
-    echo "${ORANGE}[  WARNING ]${NORMAL} adb shell su root remount -R system = ${err}, likely did not reboot!" >&2
+    echo "${YELLOW}[  WARNING ]${NORMAL} adb shell su root remount -R system = ${err}, likely did not reboot!" >&2
     T="-t ${T}"
   else
     # Rebooted, logcat will be meaningless, and last logcat will likely be clear
@@ -980,7 +1014,7 @@
   adb_sh grep "nodev${TAB}overlay" /proc/filesystems </dev/null >/dev/null 2>/dev/null &&
   echo "${GREEN}[       OK ]${NORMAL} overlay module present" >&2 ||
   (
-    echo "${ORANGE}[  WARNING ]${NORMAL} overlay module not present" >&2 &&
+    echo "${YELLOW}[  WARNING ]${NORMAL} overlay module not present" >&2 &&
       false
   ) ||
   overlayfs_supported=false
@@ -989,7 +1023,7 @@
     echo "${GREEN}[       OK ]${NORMAL} overlay module supports override_creds" >&2 ||
     case `adb_sh uname -r </dev/null` in
       4.[456789].* | 4.[1-9][0-9]* | [56789].*)
-        echo "${ORANGE}[  WARNING ]${NORMAL} overlay module does not support override_creds" >&2 &&
+        echo "${YELLOW}[  WARNING ]${NORMAL} overlay module does not support override_creds" >&2 &&
         overlayfs_supported=false
         ;;
       *)
@@ -1011,14 +1045,14 @@
 reboot=false
 for d in ${OVERLAYFS_BACKING}; do
   if adb_sh ls -d /${d}/overlay </dev/null >/dev/null 2>/dev/null; then
-    echo "${ORANGE}[  WARNING ]${NORMAL} /${d}/overlay is setup, surgically wiping" >&2
+    echo "${YELLOW}[  WARNING ]${NORMAL} /${d}/overlay is setup, surgically wiping" >&2
     adb_sh rm -rf /${d}/overlay </dev/null ||
       die "/${d}/overlay wipe"
     reboot=true
   fi
 done
 if ${reboot}; then
-  echo "${ORANGE}[  WARNING ]${NORMAL} rebooting before test" >&2
+  echo "${YELLOW}[  WARNING ]${NORMAL} rebooting before test" >&2
   adb_reboot &&
     adb_wait ${ADB_WAIT} ||
     die "lost device after reboot after wipe `usb_status`"
@@ -1030,7 +1064,7 @@
   D=`echo "${D}" | grep -v " /vendor/..*$" | grep "^overlay "` &&
   echo "${H}" &&
   echo "${D}" &&
-  echo "${ORANGE}[  WARNING ]${NORMAL} overlays present before setup" >&2 ||
+  echo "${YELLOW}[  WARNING ]${NORMAL} overlays present before setup" >&2 ||
   echo "${GREEN}[       OK ]${NORMAL} no overlay present before setup" >&2
 overlayfs_needed=true
 D=`adb_sh cat /proc/mounts </dev/null |
@@ -1083,7 +1117,7 @@
 if [ X"${D}" != X"${H}" ]; then
   echo "${H}"
   if [ X"${D}" != X"${D##*setup failed}" ]; then
-    echo "${ORANGE}[  WARNING ]${NORMAL} overlayfs setup whined" >&2
+    echo "${YELLOW}[  WARNING ]${NORMAL} overlayfs setup whined" >&2
   fi
   D=`adb_sh df -k </dev/null` &&
     H=`echo "${D}" | head -1` &&
@@ -1130,7 +1164,7 @@
 elif ${rebooted}; then
   echo "${GREEN}[       OK ]${NORMAL} verity already disabled" >&2
 else
-  echo "${ORANGE}[  WARNING ]${NORMAL} verity already disabled" >&2
+  echo "${YELLOW}[  WARNING ]${NORMAL} verity already disabled" >&2
 fi
 
 echo "${GREEN}[ RUN      ]${NORMAL} remount" >&2
@@ -1160,7 +1194,7 @@
     die -t ${T} "overlay takeover failed"
   fi
   echo "${D}" | grep "^overlay .* /system\$" >/dev/null ||
-   echo "${ORANGE}[  WARNING ]${NORMAL} overlay takeover not complete" >&2
+   echo "${YELLOW}[  WARNING ]${NORMAL} overlay takeover not complete" >&2
   if [ -z "${virtual_ab}" ]; then
     scratch_partition=scratch
   fi
@@ -1292,7 +1326,7 @@
 
 fixup_from_recovery() {
   inRecovery || return 1
-  echo "${ORANGE}[    ERROR ]${NORMAL} Device in recovery" >&2
+  echo "${YELLOW}[    ERROR ]${NORMAL} Device in recovery" >&2
   adb reboot </dev/null
   adb_wait ${ADB_WAIT}
 }
@@ -1312,7 +1346,7 @@
   adb_su sed -n '1,/overlay \/system/p' /proc/mounts </dev/null |
     skip_administrative_mounts |
     grep -v ' \(erofs\|squashfs\|ext4\|f2fs\|vfat\) ' &&
-    echo "${ORANGE}[  WARNING ]${NORMAL} overlay takeover after first stage init" >&2 ||
+    echo "${YELLOW}[  WARNING ]${NORMAL} overlay takeover after first stage init" >&2 ||
     echo "${GREEN}[       OK ]${NORMAL} overlay takeover in first stage init" >&2
 fi
 
@@ -1373,20 +1407,20 @@
 is_userspace_fastboot=false
 
 if ! ${is_bootloader_fastboot}; then
-  echo "${ORANGE}[  WARNING ]${NORMAL} does not support fastboot, skipping"
+  echo "${YELLOW}[  WARNING ]${NORMAL} does not support fastboot, skipping"
 elif [ -z "${ANDROID_PRODUCT_OUT}" ]; then
-  echo "${ORANGE}[  WARNING ]${NORMAL} build tree not setup, skipping"
+  echo "${YELLOW}[  WARNING ]${NORMAL} build tree not setup, skipping"
 elif [ ! -s "${ANDROID_PRODUCT_OUT}/vendor.img" ]; then
-  echo "${ORANGE}[  WARNING ]${NORMAL} vendor image missing, skipping"
+  echo "${YELLOW}[  WARNING ]${NORMAL} vendor image missing, skipping"
 elif [ "${ANDROID_PRODUCT_OUT}" = "${ANDROID_PRODUCT_OUT%*/${H}}" ]; then
-  echo "${ORANGE}[  WARNING ]${NORMAL} wrong vendor image, skipping"
+  echo "${YELLOW}[  WARNING ]${NORMAL} wrong vendor image, skipping"
 elif [ -z "${ANDROID_HOST_OUT}" ]; then
-  echo "${ORANGE}[  WARNING ]${NORMAL} please run lunch, skipping"
+  echo "${YELLOW}[  WARNING ]${NORMAL} please run lunch, skipping"
 elif ! (
           adb_cat /vendor/build.prop |
           cmp -s ${ANDROID_PRODUCT_OUT}/vendor/build.prop
        ) >/dev/null 2>/dev/null; then
-  echo "${ORANGE}[  WARNING ]${NORMAL} vendor image signature mismatch, skipping"
+  echo "${YELLOW}[  WARNING ]${NORMAL} vendor image signature mismatch, skipping"
 else
   wait_for_screen
   avc_check
@@ -1432,7 +1466,7 @@
   fi
   fastboot reboot ||
     die "can not reboot out of fastboot"
-  echo "${ORANGE}[  WARNING ]${NORMAL} adb after fastboot"
+  echo "${YELLOW}[  WARNING ]${NORMAL} adb after fastboot"
   adb_wait ${ADB_WAIT} ||
     fixup_from_recovery ||
     die "did not reboot after formatting ${scratch_partition} `usb_status`"
@@ -1449,8 +1483,8 @@
       if ${is_userspace_fastboot}; then
         die  "overlay supposed to be minus /vendor takeover after flash vendor"
       else
-        echo "${ORANGE}[  WARNING ]${NORMAL} user fastboot missing required to invalidate, ignoring a failure" >&2
-        echo "${ORANGE}[  WARNING ]${NORMAL} overlay supposed to be minus /vendor takeover after flash vendor" >&2
+        echo "${YELLOW}[  WARNING ]${NORMAL} user fastboot missing required to invalidate, ignoring a failure" >&2
+        echo "${YELLOW}[  WARNING ]${NORMAL} overlay supposed to be minus /vendor takeover after flash vendor" >&2
       fi
   fi
   B="`adb_cat /system/hello`"
@@ -1468,7 +1502,7 @@
     check_eq "cat: /vendor/hello: No such file or directory" "${B}" \
              vendor content after flash vendor
   else
-    echo "${ORANGE}[  WARNING ]${NORMAL} user fastboot missing required to invalidate, ignoring a failure" >&2
+    echo "${YELLOW}[  WARNING ]${NORMAL} user fastboot missing required to invalidate, ignoring a failure" >&2
     check_eq "cat: /vendor/hello: No such file or directory" "${B}" \
              --warning vendor content after flash vendor
   fi
@@ -1489,7 +1523,7 @@
 L=
 D="${H%?Now reboot your device for settings to take effect*}"
 if [ X"${H}" != X"${D}" ]; then
-  echo "${ORANGE}[  WARNING ]${NORMAL} adb remount requires a reboot after partial flash (legacy avb)"
+  echo "${YELLOW}[  WARNING ]${NORMAL} adb remount requires a reboot after partial flash (legacy avb)"
   L=`adb_logcat -b all -v nsec -t ${T} 2>&1`
   adb_reboot &&
     adb_wait ${ADB_WAIT} &&
@@ -1547,7 +1581,7 @@
   err=${?}
   if [ X"${D}" != "${D%?Now reboot your device for settings to take effect*}" ]
   then
-    echo "${ORANGE}[  WARNING ]${NORMAL} adb disable-verity requires a reboot after partial flash"
+    echo "${YELLOW}[  WARNING ]${NORMAL} adb disable-verity requires a reboot after partial flash"
     adb_reboot &&
       adb_wait ${ADB_WAIT} &&
       adb_root ||
@@ -1580,9 +1614,9 @@
   if [ -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${ORANGE}[    ERROR ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
+      echo "${YELLOW}[    ERROR ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
     else
-      echo "${ORANGE}[    ERROR ]${NORMAL} Active slot to be set to ${ACTIVE_SLOT}"
+      echo "${YELLOW}[    ERROR ]${NORMAL} Active slot to be set to ${ACTIVE_SLOT}"
     fi >&2
     fastboot --set-active=${ACTIVE_SLOT}
   fi
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index 2738457..7a3d9a9 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -174,6 +174,8 @@
             }
             return std::make_unique<DmTargetSnapshot>(start_sector, num_sectors, base_device,
                                                       cow_device, mode, chunk_size);
+        } else if (target_type == "user") {
+            return std::make_unique<DmTargetUser>(start_sector, num_sectors);
         } else {
             std::cerr << "Unrecognized target type: " << target_type << std::endl;
             return nullptr;
diff --git a/init/Android.bp b/init/Android.bp
index edf9099..3f2cd07 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -130,6 +130,7 @@
         "libpropertyinfoserializer",
         "libpropertyinfoparser",
         "libsnapshot_init",
+        "libxml2",
         "lib_apex_manifest_proto_lite",
         "update_metadata-protos",
     ],
@@ -164,6 +165,9 @@
         "selinux_policy_version",
     ],
     srcs: init_common_sources + init_device_sources,
+    generated_sources: [
+        "apex-info-list",
+    ],
     whole_static_libs: [
         "libcap",
         "com.android.sysprop.apex",
@@ -178,6 +182,12 @@
     target: {
         recovery: {
             cflags: ["-DRECOVERY"],
+            exclude_static_libs: [
+                "libxml2",
+            ],
+            exclude_generated_sources: [
+                "apex-info-list",
+            ],
             exclude_shared_libs: [
                 "libbinder",
                 "libutils",
@@ -212,6 +222,9 @@
     target: {
         recovery: {
             cflags: ["-DRECOVERY"],
+            exclude_static_libs: [
+                "libxml2",
+            ],
             exclude_shared_libs: [
                 "libbinder",
                 "libutils",
diff --git a/init/README.md b/init/README.md
index 188f19b..c3b64f6 100644
--- a/init/README.md
+++ b/init/README.md
@@ -197,11 +197,14 @@
   Currently defaults to root.  (??? probably should default to nobody)
 
 `interface <interface name> <instance name>`
-> Associates this service with a list of the HIDL services that it provides. The interface name
-  must be a fully-qualified name and not a value name. For instance, this is used to allow
-  hwservicemanager to lazily start services. When multiple interfaces are served, this tag should
-  be used multiple times.
-  For example: interface vendor.foo.bar@1.0::IBaz default
+> Associates this service with a list of the AIDL or HIDL services that it provides. The interface
+  name must be a fully-qualified name and not a value name. For instance, this is used to allow
+  servicemanager or hwservicemanager to lazily start services. When multiple interfaces are served,
+  this tag should be used multiple times. An example of an entry for a HIDL
+  interface is `interface vendor.foo.bar@1.0::IBaz default`. For an AIDL interface, use
+  `interface aidl <instance name>`. The instance name for an AIDL interface is
+  whatever is registered with servicemanager, and these can be listed with `adb
+  shell dumpsys -l`.
 
 `ioprio <class> <priority>`
 > Sets the IO priority and IO priority class for this service via the SYS_ioprio_set syscall.
diff --git a/init/block_dev_initializer.cpp b/init/block_dev_initializer.cpp
index b423f86..8db9793 100644
--- a/init/block_dev_initializer.cpp
+++ b/init/block_dev_initializer.cpp
@@ -37,7 +37,15 @@
 }
 
 bool BlockDevInitializer::InitDeviceMapper() {
-    const std::string dm_path = "/devices/virtual/misc/device-mapper";
+    return InitMiscDevice("device-mapper");
+}
+
+bool BlockDevInitializer::InitDmUser() {
+    return InitMiscDevice("dm-user");
+}
+
+bool BlockDevInitializer::InitMiscDevice(const std::string& name) {
+    const std::string dm_path = "/devices/virtual/misc/" + name;
     bool found = false;
     auto dm_callback = [this, &dm_path, &found](const Uevent& uevent) {
         if (uevent.path == dm_path) {
@@ -49,13 +57,13 @@
     };
     uevent_listener_.RegenerateUeventsForPath("/sys" + dm_path, dm_callback);
     if (!found) {
-        LOG(INFO) << "device-mapper device not found in /sys, waiting for its uevent";
+        LOG(INFO) << name << " device not found in /sys, waiting for its uevent";
         Timer t;
         uevent_listener_.Poll(dm_callback, 10s);
-        LOG(INFO) << "Wait for device-mapper returned after " << t;
+        LOG(INFO) << "Wait for " << name << " returned after " << t;
     }
     if (!found) {
-        LOG(ERROR) << "device-mapper device not found after polling timeout";
+        LOG(ERROR) << name << " device not found after polling timeout";
         return false;
     }
     return true;
diff --git a/init/block_dev_initializer.h b/init/block_dev_initializer.h
index 0d4c6e9..b8dd3f1 100644
--- a/init/block_dev_initializer.h
+++ b/init/block_dev_initializer.h
@@ -27,12 +27,15 @@
     BlockDevInitializer();
 
     bool InitDeviceMapper();
+    bool InitDmUser();
     bool InitDevices(std::set<std::string> devices);
     bool InitDmDevice(const std::string& device);
 
   private:
     ListenerAction HandleUevent(const Uevent& uevent, std::set<std::string>* devices);
 
+    bool InitMiscDevice(const std::string& name);
+
     std::unique_ptr<DeviceHandler> device_handler_;
     UeventListener uevent_listener_;
 };
diff --git a/init/devices.cpp b/init/devices.cpp
index 9fbec64..53ca875 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -193,7 +193,8 @@
     while (directory != "/" && directory != ".") {
         std::string subsystem_link_path;
         if (Realpath(directory + "/subsystem", &subsystem_link_path) &&
-            subsystem_link_path == sysfs_mount_point_ + "/bus/platform") {
+            (subsystem_link_path == sysfs_mount_point_ + "/bus/platform" ||
+             subsystem_link_path == sysfs_mount_point_ + "/bus/amba")) {
             // We need to remove the mount point that we added above before returning.
             directory.erase(0, sysfs_mount_point_.size());
             *platform_device_path = directory;
diff --git a/init/init.cpp b/init/init.cpp
index 631db8e..cb5bbba 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -18,6 +18,7 @@
 
 #include <dirent.h>
 #include <fcntl.h>
+#include <paths.h>
 #include <pthread.h>
 #include <signal.h>
 #include <stdlib.h>
@@ -727,6 +728,12 @@
     InitSecondStageLogging(argv);
     LOG(INFO) << "init second stage started!";
 
+    // Update $PATH in the case the second stage init is newer than first stage init, where it is
+    // first set.
+    if (setenv("PATH", _PATH_DEFPATH, 1) != 0) {
+        PLOG(FATAL) << "Could not set $PATH to '" << _PATH_DEFPATH << "' in second stage";
+    }
+
     // Init should not crash because of a dependence on any other process, therefore we ignore
     // SIGPIPE and handle EPIPE at the call site directly.  Note that setting a signal to SIG_IGN
     // is inherited across exec, but custom signal handlers are not.  Since we do not want to
@@ -868,6 +875,8 @@
     // Run all property triggers based on current state of the properties.
     am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
 
+    // Restore prio before main loop
+    setpriority(PRIO_PROCESS, 0, 0);
     while (true) {
         // By default, sleep until something happens.
         auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 07b4724..fa65740 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -17,6 +17,7 @@
 #include <functional>
 
 #include <android-base/file.h>
+#include <android-base/properties.h>
 #include <gtest/gtest.h>
 
 #include "action.h"
@@ -32,6 +33,8 @@
 #include "service_parser.h"
 #include "util.h"
 
+using android::base::GetIntProperty;
+
 namespace android {
 namespace init {
 
@@ -240,6 +243,10 @@
 }
 
 TEST(init, RejectsCriticalAndOneshotService) {
+    if (GetIntProperty("ro.product.first_api_level", 10000) < 30) {
+        GTEST_SKIP() << "Test only valid for devices launching with R or later";
+    }
+
     std::string init_script =
             R"init(
 service A something
diff --git a/init/main.cpp b/init/main.cpp
index 38bc74b..23f5530 100644
--- a/init/main.cpp
+++ b/init/main.cpp
@@ -52,7 +52,8 @@
 #if __has_feature(address_sanitizer)
     __asan_set_error_report_callback(AsanReportCallback);
 #endif
-
+    // Boost prio which will be restored later
+    setpriority(PRIO_PROCESS, 0, -20);
     if (!strcmp(basename(argv[0]), "ueventd")) {
         return ueventd_main(argc, argv);
     }
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index b9d5d67..f8359bc 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -27,10 +27,19 @@
 #include <android-base/properties.h>
 #include <android-base/result.h>
 #include <android-base/unique_fd.h>
-#include <apex_manifest.pb.h>
 
 #include "util.h"
 
+#ifndef RECOVERY
+#define ACTIVATE_FLATTENED_APEX 1
+#endif
+
+#ifdef ACTIVATE_FLATTENED_APEX
+#include <apex_manifest.pb.h>
+#include <com_android_apex.h>
+#include <selinux/android.h>
+#endif  // ACTIVATE_FLATTENED_APEX
+
 namespace android {
 namespace init {
 namespace {
@@ -106,6 +115,8 @@
     return updatable;
 }
 
+#ifdef ACTIVATE_FLATTENED_APEX
+
 static Result<void> MountDir(const std::string& path, const std::string& mount_path) {
     if (int ret = mkdir(mount_path.c_str(), 0755); ret != 0 && errno != EEXIST) {
         return ErrnoError() << "Could not create mount point " << mount_path;
@@ -116,7 +127,7 @@
     return {};
 }
 
-static Result<std::string> GetApexName(const std::string& apex_dir) {
+static Result<apex::proto::ApexManifest> GetApexManifest(const std::string& apex_dir) {
     const std::string manifest_path = apex_dir + "/apex_manifest.pb";
     std::string content;
     if (!android::base::ReadFileToString(manifest_path, &content)) {
@@ -126,11 +137,12 @@
     if (!manifest.ParseFromString(content)) {
         return Error() << "Can't parse manifest file: " << manifest_path;
     }
-    return manifest.name();
+    return manifest;
 }
 
+template <typename Fn>
 static Result<void> ActivateFlattenedApexesFrom(const std::string& from_dir,
-                                                const std::string& to_dir) {
+                                                const std::string& to_dir, Fn on_activate) {
     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(from_dir.c_str()), closedir);
     if (!dir) {
         return {};
@@ -140,15 +152,16 @@
         if (entry->d_name[0] == '.') continue;
         if (entry->d_type == DT_DIR) {
             const std::string apex_path = from_dir + "/" + entry->d_name;
-            const auto apex_name = GetApexName(apex_path);
-            if (!apex_name.ok()) {
-                LOG(ERROR) << apex_path << " is not an APEX directory: " << apex_name.error();
+            const auto apex_manifest = GetApexManifest(apex_path);
+            if (!apex_manifest.ok()) {
+                LOG(ERROR) << apex_path << " is not an APEX directory: " << apex_manifest.error();
                 continue;
             }
-            const std::string mount_path = to_dir + "/" + (*apex_name);
+            const std::string mount_path = to_dir + "/" + apex_manifest->name();
             if (auto result = MountDir(apex_path, mount_path); !result.ok()) {
                 return result;
             }
+            on_activate(apex_path, *apex_manifest);
         }
     }
     return {};
@@ -167,15 +180,37 @@
             "/vendor/apex",
     };
 
+    std::vector<com::android::apex::ApexInfo> apex_infos;
+    auto on_activate = [&](const std::string& apex_path,
+                           const apex::proto::ApexManifest& apex_manifest) {
+        apex_infos.emplace_back(apex_manifest.name(), apex_path, apex_path, apex_manifest.version(),
+                                apex_manifest.versionname(), /*isFactory=*/true, /*isActive=*/true);
+    };
+
     for (const auto& dir : kBuiltinDirsForApexes) {
-        if (auto result = ActivateFlattenedApexesFrom(dir, kApexTop); !result.ok()) {
+        if (auto result = ActivateFlattenedApexesFrom(dir, kApexTop, on_activate); !result.ok()) {
             LOG(ERROR) << result.error();
             return false;
         }
     }
+
+    std::ostringstream oss;
+    com::android::apex::ApexInfoList apex_info_list(apex_infos);
+    com::android::apex::write(oss, apex_info_list);
+    const std::string kApexInfoList = kApexTop + "/apex-info-list.xml";
+    if (!android::base::WriteStringToFile(oss.str(), kApexInfoList)) {
+        PLOG(ERROR) << "Failed to write " << kApexInfoList;
+        return false;
+    }
+    if (selinux_android_restorecon(kApexInfoList.c_str(), 0) != 0) {
+        PLOG(ERROR) << "selinux_android_restorecon(" << kApexInfoList << ") failed";
+    }
+
     return true;
 }
 
+#endif  // ACTIVATE_FLATTENED_APEX
+
 static android::base::unique_fd bootstrap_ns_fd;
 static android::base::unique_fd default_ns_fd;
 
@@ -269,9 +304,9 @@
         default_ns_fd.reset(OpenMountNamespace());
         default_ns_id = GetMountNamespaceId();
     }
-
+#ifdef ACTIVATE_FLATTENED_APEX
     success &= ActivateFlattenedApexesIfPossible();
-
+#endif
     LOG(INFO) << "SetupMountNamespaces done";
     return success;
 }
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 612854d..1fa3362 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -51,6 +51,7 @@
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
@@ -74,6 +75,7 @@
 using namespace std::literals;
 
 using android::base::GetProperty;
+using android::base::ParseInt;
 using android::base::ReadFileToString;
 using android::base::Split;
 using android::base::StartsWith;
@@ -630,9 +632,11 @@
     char *key, *value, *eol, *sol, *tmp, *fn;
     size_t flen = 0;
 
-    static constexpr const char* const kVendorPathPrefixes[2] = {
+    static constexpr const char* const kVendorPathPrefixes[4] = {
             "/vendor",
             "/odm",
+            "/vendor_dlkm",
+            "/odm_dlkm",
     };
 
     const char* context = kInitContext;
@@ -886,24 +890,62 @@
         load_properties_from_file("/prop.default", nullptr, &properties);
     }
 
+    // /<part>/etc/build.prop is the canonical location of the build-time properties since S.
+    // Falling back to /<part>/defalt.prop and /<part>/build.prop only when legacy path has to
+    // be supported, which is controlled by the support_legacy_path_until argument.
+    const auto load_properties_from_partition = [&properties](const std::string& partition,
+                                                              int support_legacy_path_until) {
+        auto path = "/" + partition + "/etc/build.prop";
+        if (load_properties_from_file(path.c_str(), nullptr, &properties)) {
+            return;
+        }
+        // To read ro.<partition>.build.version.sdk, temporarily load the legacy paths into a
+        // separate map. Then by comparing its value with legacy_version, we know that if the
+        // partition is old enough so that we need to respect the legacy paths.
+        std::map<std::string, std::string> temp;
+        auto legacy_path1 = "/" + partition + "/default.prop";
+        auto legacy_path2 = "/" + partition + "/build.prop";
+        load_properties_from_file(legacy_path1.c_str(), nullptr, &temp);
+        load_properties_from_file(legacy_path2.c_str(), nullptr, &temp);
+        bool support_legacy_path = false;
+        auto version_prop_name = "ro." + partition + ".build.version.sdk";
+        auto it = temp.find(version_prop_name);
+        if (it == temp.end()) {
+            // This is embarassing. Without the prop, we can't determine how old the partition is.
+            // Let's be conservative by assuming it is very very old.
+            support_legacy_path = true;
+        } else if (int value;
+                   ParseInt(it->second.c_str(), &value) && value <= support_legacy_path_until) {
+            support_legacy_path = true;
+        }
+        if (support_legacy_path) {
+            // We don't update temp into properties directly as it might skip any (future) logic
+            // for resolving duplicates implemented in load_properties_from_file.  Instead, read
+            // the files again into the properties map.
+            load_properties_from_file(legacy_path1.c_str(), nullptr, &properties);
+            load_properties_from_file(legacy_path2.c_str(), nullptr, &properties);
+        } else {
+            LOG(FATAL) << legacy_path1 << " and " << legacy_path2 << " were not loaded "
+                       << "because " << version_prop_name << "(" << it->second << ") is newer "
+                       << "than " << support_legacy_path_until;
+        }
+    };
+
+    // Order matters here. The more the partition is specific to a product, the higher its
+    // precedence is.
     load_properties_from_file("/system/build.prop", nullptr, &properties);
-    load_properties_from_file("/system_ext/build.prop", nullptr, &properties);
-
-    // TODO(b/117892318): uncomment the following condition when vendor.imgs for
-    // aosp_* targets are all updated.
-//    if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_R__) {
-        load_properties_from_file("/vendor/default.prop", nullptr, &properties);
-//    }
+    load_properties_from_partition("system_ext", /* support_legacy_path_until */ 30);
+    // TODO(b/117892318): uncomment the following condition when vendor.imgs for aosp_* targets are
+    // all updated.
+    // if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_R__) {
+    load_properties_from_file("/vendor/default.prop", nullptr, &properties);
+    // }
     load_properties_from_file("/vendor/build.prop", nullptr, &properties);
+    load_properties_from_file("/vendor_dlkm/etc/build.prop", nullptr, &properties);
+    load_properties_from_file("/odm_dlkm/etc/build.prop", nullptr, &properties);
+    load_properties_from_partition("odm", /* support_legacy_path_until */ 28);
+    load_properties_from_partition("product", /* support_legacy_path_until */ 30);
 
-    if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_Q__) {
-        load_properties_from_file("/odm/etc/build.prop", nullptr, &properties);
-    } else {
-        load_properties_from_file("/odm/default.prop", nullptr, &properties);
-        load_properties_from_file("/odm/build.prop", nullptr, &properties);
-    }
-
-    load_properties_from_file("/product/build.prop", nullptr, &properties);
     load_properties_from_file("/factory/factory.prop", "ro.*", &properties);
 
     if (access(kDebugRamdiskProp, R_OK) == 0) {
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index f3dd538..9d4ea8c 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -18,6 +18,8 @@
 
 #include <fcntl.h>
 #include <poll.h>
+#include <sys/time.h>
+#include <sys/resource.h>
 #include <unistd.h>
 
 #include <android-base/file.h>
@@ -181,6 +183,8 @@
     trigger_shutdown = [](const std::string& command) { shutdown_command = command; };
 
     auto subcontext_process = SubcontextProcess(function_map, context, init_fd);
+    // Restore prio before main loop
+    setpriority(PRIO_PROCESS, 0, 0);
     subcontext_process.MainLoop();
     return 0;
 }
diff --git a/init/uevent_listener.cpp b/init/uevent_listener.cpp
index d8d9b36..7cd396a 100644
--- a/init/uevent_listener.cpp
+++ b/init/uevent_listener.cpp
@@ -95,20 +95,18 @@
     fcntl(device_fd_, F_SETFL, O_NONBLOCK);
 }
 
-bool UeventListener::ReadUevent(Uevent* uevent) const {
+ReadUeventResult UeventListener::ReadUevent(Uevent* uevent) const {
     char msg[UEVENT_MSG_LEN + 2];
     int n = uevent_kernel_multicast_recv(device_fd_, msg, UEVENT_MSG_LEN);
     if (n <= 0) {
         if (errno != EAGAIN && errno != EWOULDBLOCK) {
             PLOG(ERROR) << "Error reading from Uevent Fd";
         }
-        return false;
+        return ReadUeventResult::kFailed;
     }
     if (n >= UEVENT_MSG_LEN) {
         LOG(ERROR) << "Uevent overflowed buffer, discarding";
-        // Return true here even if we discard as we may have more uevents pending and we
-        // want to keep processing them.
-        return true;
+        return ReadUeventResult::kInvalid;
     }
 
     msg[n] = '\0';
@@ -116,7 +114,7 @@
 
     ParseEvent(msg, uevent);
 
-    return true;
+    return ReadUeventResult::kSuccess;
 }
 
 // RegenerateUevents*() walks parts of the /sys tree and pokes the uevent files to cause the kernel
@@ -137,7 +135,10 @@
         close(fd);
 
         Uevent uevent;
-        while (ReadUevent(&uevent)) {
+        ReadUeventResult result;
+        while ((result = ReadUevent(&uevent)) != ReadUeventResult::kFailed) {
+            // Skip processing the uevent if it is invalid.
+            if (result == ReadUeventResult::kInvalid) continue;
             if (callback(uevent) == ListenerAction::kStop) return ListenerAction::kStop;
         }
     }
@@ -212,7 +213,10 @@
             // We're non-blocking, so if we receive a poll event keep processing until
             // we have exhausted all uevent messages.
             Uevent uevent;
-            while (ReadUevent(&uevent)) {
+            ReadUeventResult result;
+            while ((result = ReadUevent(&uevent)) != ReadUeventResult::kFailed) {
+                // Skip processing the uevent if it is invalid.
+                if (result == ReadUeventResult::kInvalid) continue;
                 if (callback(uevent) == ListenerAction::kStop) return;
             }
         }
diff --git a/init/uevent_listener.h b/init/uevent_listener.h
index aea094e..2772860 100644
--- a/init/uevent_listener.h
+++ b/init/uevent_listener.h
@@ -27,7 +27,7 @@
 
 #include "uevent.h"
 
-#define UEVENT_MSG_LEN 2048
+#define UEVENT_MSG_LEN 8192
 
 namespace android {
 namespace init {
@@ -37,6 +37,12 @@
     kContinue,  // Continue regenerating uevents as we haven't seen the one(s) we're interested in.
 };
 
+enum class ReadUeventResult {
+    kSuccess = 0,  // Uevent was successfully read.
+    kFailed,       // Uevent reading has failed.
+    kInvalid,      // An Invalid Uevent was read (like say, the msg received is >= UEVENT_MSG_LEN).
+};
+
 using ListenerCallback = std::function<ListenerAction(const Uevent&)>;
 
 class UeventListener {
@@ -50,7 +56,7 @@
               const std::optional<std::chrono::milliseconds> relative_timeout = {}) const;
 
   private:
-    bool ReadUevent(Uevent* uevent) const;
+    ReadUeventResult ReadUevent(Uevent* uevent) const;
     ListenerAction RegenerateUeventsForDir(DIR* d, const ListenerCallback& callback) const;
 
     android::base::unique_fd device_fd_;
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 7514b61..54659c5 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -321,6 +321,8 @@
     while (waitpid(-1, nullptr, WNOHANG) > 0) {
     }
 
+    // Restore prio before main loop
+    setpriority(PRIO_PROCESS, 0, 0);
     uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) {
         for (auto& uevent_handler : uevent_handlers) {
             uevent_handler->HandleUevent(uevent);
diff --git a/libappfuse/FuseBridgeLoop.cc b/libappfuse/FuseBridgeLoop.cc
index f71d0c3..22f381c 100644
--- a/libappfuse/FuseBridgeLoop.cc
+++ b/libappfuse/FuseBridgeLoop.cc
@@ -311,6 +311,8 @@
     }
 };
 
+std::recursive_mutex FuseBridgeLoop::mutex_;
+
 FuseBridgeLoop::FuseBridgeLoop() : opened_(true) {
     base::unique_fd epoll_fd(epoll_create1(EPOLL_CLOEXEC));
     if (epoll_fd.get() == -1) {
@@ -328,7 +330,7 @@
 
     std::unique_ptr<FuseBridgeEntry> bridge(
         new FuseBridgeEntry(mount_id, std::move(dev_fd), std::move(proxy_fd)));
-    std::lock_guard<std::mutex> lock(mutex_);
+    std::lock_guard<std::recursive_mutex> lock(mutex_);
     if (!opened_) {
         LOG(ERROR) << "Tried to add a mount to a closed bridge";
         return false;
@@ -372,7 +374,7 @@
         const bool wait_result = epoll_controller_->Wait(bridges_.size(), &entries);
         LOG(VERBOSE) << "Receive epoll events";
         {
-            std::lock_guard<std::mutex> lock(mutex_);
+            std::lock_guard<std::recursive_mutex> lock(mutex_);
             if (!(wait_result && ProcessEventLocked(entries, callback))) {
                 for (auto it = bridges_.begin(); it != bridges_.end();) {
                     callback->OnClosed(it->second->mount_id());
@@ -385,5 +387,13 @@
     }
 }
 
+void FuseBridgeLoop::Lock() {
+    mutex_.lock();
+}
+
+void FuseBridgeLoop::Unlock() {
+    mutex_.unlock();
+}
+
 }  // namespace fuse
 }  // namespace android
diff --git a/libappfuse/include/libappfuse/FuseBridgeLoop.h b/libappfuse/include/libappfuse/FuseBridgeLoop.h
index 6bfda98..d5fc28f 100644
--- a/libappfuse/include/libappfuse/FuseBridgeLoop.h
+++ b/libappfuse/include/libappfuse/FuseBridgeLoop.h
@@ -50,6 +50,10 @@
     // thread from one which invokes |Start|.
     bool AddBridge(int mount_id, base::unique_fd dev_fd, base::unique_fd proxy_fd);
 
+    static void Lock();
+
+    static void Unlock();
+
   private:
     bool ProcessEventLocked(const std::unordered_set<FuseBridgeEntry*>& entries,
                             FuseBridgeLoopCallback* callback);
@@ -60,7 +64,7 @@
     std::map<int, std::unique_ptr<FuseBridgeEntry>> bridges_;
 
     // Lock for multi-threading.
-    std::mutex mutex_;
+    static std::recursive_mutex mutex_;
 
     bool opened_;
 
diff --git a/libbacktrace/Android.bp b/libbacktrace/Android.bp
index f75e8df..c7969f2 100644
--- a/libbacktrace/Android.bp
+++ b/libbacktrace/Android.bp
@@ -96,6 +96,8 @@
 cc_library {
     name: "libbacktrace",
     vendor_available: false,
+    // TODO(b/153609531): remove when no longer needed.
+    native_bridge_supported: true,
     recovery_available: true,
     apex_available: [
         "//apex_available:platform",
@@ -120,6 +122,9 @@
         recovery: {
             cflags: ["-DNO_LIBDEXFILE_SUPPORT"],
         },
+        native_bridge: {
+            cflags: ["-DNO_LIBDEXFILE_SUPPORT"],
+        },
     },
 }
 
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 5805a4d..b9fc82e 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -203,6 +203,7 @@
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/e2fsck" },
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/tune2fs" },
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/resize2fs" },
+    { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/snapuserd" },
     // generic defaults
     { 00755, AID_ROOT,      AID_ROOT,      0, "bin/*" },
     { 00640, AID_ROOT,      AID_SHELL,     0, "fstab.*" },
diff --git a/liblog/logprint.cpp b/liblog/logprint.cpp
index 238431f..a5c5edd 100644
--- a/liblog/logprint.cpp
+++ b/liblog/logprint.cpp
@@ -78,18 +78,21 @@
 static bool descriptive_output = false;
 
 /*
- *  gnome-terminal color tags
- *    See http://misc.flogisoft.com/bash/tip_colors_and_formatting
- *    for ideas on how to set the forground color of the text for xterm.
- *    The color manipulation character stream is defined as:
- *      ESC [ 3 8 ; 5 ; <color#> m
+ * 8-bit color tags. See ECMA-48 Set Graphics Rendition in
+ * [console_codes(4)](https://man7.org/linux/man-pages/man4/console_codes.4.html).
+ *
+ * The text manipulation character stream is defined as:
+ *   ESC [ <parameter #> m
+ *
+ * We use "set <color> foreground" escape sequences instead of
+ * "256/24-bit foreground color". This allows colors to render
+ * according to user preferences in terminal emulator settings
  */
-#define ANDROID_COLOR_BLUE 75
-#define ANDROID_COLOR_DEFAULT 231
-#define ANDROID_COLOR_GREEN 40
-#define ANDROID_COLOR_ORANGE 166
-#define ANDROID_COLOR_RED 196
-#define ANDROID_COLOR_YELLOW 226
+#define ANDROID_COLOR_BLUE 34
+#define ANDROID_COLOR_DEFAULT 39
+#define ANDROID_COLOR_GREEN 32
+#define ANDROID_COLOR_RED 31
+#define ANDROID_COLOR_YELLOW 33
 
 static FilterInfo* filterinfo_new(const char* tag, android_LogPriority pri) {
   FilterInfo* p_ret;
@@ -165,7 +168,7 @@
     case ANDROID_LOG_VERBOSE: return ANDROID_COLOR_DEFAULT;
     case ANDROID_LOG_DEBUG:   return ANDROID_COLOR_BLUE;
     case ANDROID_LOG_INFO:    return ANDROID_COLOR_GREEN;
-    case ANDROID_LOG_WARN:    return ANDROID_COLOR_ORANGE;
+    case ANDROID_LOG_WARN:    return ANDROID_COLOR_YELLOW;
     case ANDROID_LOG_ERROR:   return ANDROID_COLOR_RED;
     case ANDROID_LOG_FATAL:   return ANDROID_COLOR_RED;
     case ANDROID_LOG_SILENT:  return ANDROID_COLOR_DEFAULT;
@@ -1499,7 +1502,7 @@
    */
   if (p_format->colored_output) {
     prefixLen =
-        snprintf(prefixBuf, sizeof(prefixBuf), "\x1B[38;5;%dm", colorFromPri(entry->priority));
+        snprintf(prefixBuf, sizeof(prefixBuf), "\x1B[%dm", colorFromPri(entry->priority));
     prefixLen = MIN(prefixLen, sizeof(prefixBuf));
 
     const char suffixContents[] = "\x1B[0m";
diff --git a/liblog/pmsg_reader.cpp b/liblog/pmsg_reader.cpp
index 0e39aab..5640900 100644
--- a/liblog/pmsg_reader.cpp
+++ b/liblog/pmsg_reader.cpp
@@ -96,7 +96,7 @@
           ((logger_list->start.tv_sec != buf.l.realtime.tv_sec) ||
            (logger_list->start.tv_nsec <= buf.l.realtime.tv_nsec)))) &&
         (!logger_list->pid || (logger_list->pid == buf.p.pid))) {
-      char* msg = reinterpret_cast<char*>(&log_msg->entry) + log_msg->entry.hdr_size;
+      char* msg = reinterpret_cast<char*>(&log_msg->entry) + sizeof(log_msg->entry);
       *msg = buf.prio;
       fd = atomic_load(&logger_list->fd);
       if (fd <= 0) {
diff --git a/liblog/properties.cpp b/liblog/properties.cpp
index f5e060c..2392112 100644
--- a/liblog/properties.cpp
+++ b/liblog/properties.cpp
@@ -474,36 +474,7 @@
 }
 
 bool __android_logger_valid_buffer_size(unsigned long value) {
-  static long pages, pagesize;
-  unsigned long maximum;
-
-  if ((value < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < value)) {
-    return false;
-  }
-
-  if (!pages) {
-    pages = sysconf(_SC_PHYS_PAGES);
-  }
-  if (pages < 1) {
-    return true;
-  }
-
-  if (!pagesize) {
-    pagesize = sysconf(_SC_PAGESIZE);
-    if (pagesize <= 1) {
-      pagesize = PAGE_SIZE;
-    }
-  }
-
-  /* maximum memory impact a somewhat arbitrary ~3% */
-  pages = (pages + 31) / 32;
-  maximum = pages * pagesize;
-
-  if ((maximum < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < maximum)) {
-    return true;
-  }
-
-  return value <= maximum;
+  return LOG_BUFFER_MIN_SIZE <= value && value <= LOG_BUFFER_MAX_SIZE;
 }
 
 struct cache2_property_size {
diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index a7687af..baee4f9 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -36,8 +36,7 @@
                             std::vector<std::string>* post_dependencies);
     void ResetModuleCount() { module_count_ = 0; }
     int GetModuleCount() { return module_count_; }
-    void EnableBlacklist(bool enable);
-    void EnableVerbose(bool enable);
+    void EnableBlocklist(bool enable);
 
   private:
     std::string MakeCanonical(const std::string& module_path);
@@ -55,7 +54,7 @@
     bool ParseSoftdepCallback(const std::vector<std::string>& args);
     bool ParseLoadCallback(const std::vector<std::string>& args);
     bool ParseOptionsCallback(const std::vector<std::string>& args);
-    bool ParseBlacklistCallback(const std::vector<std::string>& args);
+    bool ParseBlocklistCallback(const std::vector<std::string>& args);
     void ParseKernelCmdlineOptions();
     void ParseCfg(const std::string& cfg, std::function<bool(const std::vector<std::string>&)> f);
 
@@ -65,8 +64,8 @@
     std::vector<std::pair<std::string, std::string>> module_post_softdep_;
     std::vector<std::string> module_load_;
     std::unordered_map<std::string, std::string> module_options_;
-    std::set<std::string> module_blacklist_;
+    std::set<std::string> module_blocklist_;
     std::unordered_set<std::string> module_loaded_;
     int module_count_ = 0;
-    bool blacklist_enabled = false;
+    bool blocklist_enabled = false;
 };
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index d193796..bbdd317 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -194,17 +194,17 @@
     return true;
 }
 
-bool Modprobe::ParseBlacklistCallback(const std::vector<std::string>& args) {
+bool Modprobe::ParseBlocklistCallback(const std::vector<std::string>& args) {
     auto it = args.begin();
     const std::string& type = *it++;
 
-    if (type != "blacklist") {
-        LOG(ERROR) << "non-blacklist line encountered in modules.blacklist";
+    if (type != "blocklist") {
+        LOG(ERROR) << "non-blocklist line encountered in modules.blocklist";
         return false;
     }
 
     if (args.size() != 2) {
-        LOG(ERROR) << "lines in modules.blacklist must have exactly 2 entries, not " << args.size();
+        LOG(ERROR) << "lines in modules.blocklist must have exactly 2 entries, not " << args.size();
         return false;
     }
 
@@ -214,7 +214,7 @@
     if (canonical_name.empty()) {
         return false;
     }
-    this->module_blacklist_.emplace(canonical_name);
+    this->module_blocklist_.emplace(canonical_name);
 
     return true;
 }
@@ -331,24 +331,16 @@
         auto options_callback = std::bind(&Modprobe::ParseOptionsCallback, this, _1);
         ParseCfg(base_path + "/modules.options", options_callback);
 
-        auto blacklist_callback = std::bind(&Modprobe::ParseBlacklistCallback, this, _1);
-        ParseCfg(base_path + "/modules.blacklist", blacklist_callback);
+        auto blocklist_callback = std::bind(&Modprobe::ParseBlocklistCallback, this, _1);
+        ParseCfg(base_path + "/modules.blocklist", blocklist_callback);
     }
 
     ParseKernelCmdlineOptions();
     android::base::SetMinimumLogSeverity(android::base::INFO);
 }
 
-void Modprobe::EnableBlacklist(bool enable) {
-    blacklist_enabled = enable;
-}
-
-void Modprobe::EnableVerbose(bool enable) {
-    if (enable) {
-        android::base::SetMinimumLogSeverity(android::base::VERBOSE);
-    } else {
-        android::base::SetMinimumLogSeverity(android::base::INFO);
-    }
+void Modprobe::EnableBlocklist(bool enable) {
+    blocklist_enabled = enable;
 }
 
 std::vector<std::string> Modprobe::GetDependencies(const std::string& module) {
diff --git a/libmodprobe/libmodprobe_ext.cpp b/libmodprobe/libmodprobe_ext.cpp
index 6589708..fb1f5e7 100644
--- a/libmodprobe/libmodprobe_ext.cpp
+++ b/libmodprobe/libmodprobe_ext.cpp
@@ -35,7 +35,7 @@
     android::base::unique_fd fd(
             TEMP_FAILURE_RETRY(open(path_name.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
     if (fd == -1) {
-        LOG(ERROR) << "Could not open module '" << path_name << "'";
+        PLOG(ERROR) << "Could not open module '" << path_name << "'";
         return false;
     }
 
@@ -49,7 +49,7 @@
         options = options + " " + parameters;
     }
 
-    LOG(INFO) << "Loading module " << path_name << " with args \"" << options << "\"";
+    LOG(INFO) << "Loading module " << path_name << " with args '" << options << "'";
     int ret = syscall(__NR_finit_module, fd.get(), options.c_str(), 0);
     if (ret != 0) {
         if (errno == EEXIST) {
@@ -57,7 +57,7 @@
             module_loaded_.emplace(canonical_name);
             return true;
         }
-        LOG(ERROR) << "Failed to insmod '" << path_name << "' with args '" << options << "'";
+        PLOG(ERROR) << "Failed to insmod '" << path_name << "' with args '" << options << "'";
         return false;
     }
 
@@ -80,8 +80,8 @@
 
 bool Modprobe::ModuleExists(const std::string& module_name) {
     struct stat fileStat;
-    if (blacklist_enabled && module_blacklist_.count(module_name)) {
-        LOG(INFO) << "module " << module_name << " is blacklisted";
+    if (blocklist_enabled && module_blocklist_.count(module_name)) {
+        LOG(INFO) << "module " << module_name << " is blocklisted";
         return false;
     }
     auto deps = GetDependencies(module_name);
diff --git a/libmodprobe/libmodprobe_ext_test.cpp b/libmodprobe/libmodprobe_ext_test.cpp
index 9ee5ba7..e79bfaf 100644
--- a/libmodprobe/libmodprobe_ext_test.cpp
+++ b/libmodprobe/libmodprobe_ext_test.cpp
@@ -72,7 +72,7 @@
 
 bool Modprobe::ModuleExists(const std::string& module_name) {
     auto deps = GetDependencies(module_name);
-    if (blacklist_enabled && module_blacklist_.count(module_name)) {
+    if (blocklist_enabled && module_blocklist_.count(module_name)) {
         return false;
     }
     if (deps.empty()) {
diff --git a/libmodprobe/libmodprobe_test.cpp b/libmodprobe/libmodprobe_test.cpp
index eea0abd..5919c49 100644
--- a/libmodprobe/libmodprobe_test.cpp
+++ b/libmodprobe/libmodprobe_test.cpp
@@ -113,9 +113,9 @@
             "options test9.ko param_x=1 param_y=2 param_z=3\n"
             "options test100.ko param_1=1\n";
 
-    const std::string modules_blacklist =
-            "blacklist test9.ko\n"
-            "blacklist test3.ko\n";
+    const std::string modules_blocklist =
+            "blocklist test9.ko\n"
+            "blocklist test3.ko\n";
 
     const std::string modules_load =
             "test4.ko\n"
@@ -139,7 +139,7 @@
                                                  0600, getuid(), getgid()));
     ASSERT_TRUE(android::base::WriteStringToFile(modules_load, dir_path + "/modules.load", 0600,
                                                  getuid(), getgid()));
-    ASSERT_TRUE(android::base::WriteStringToFile(modules_blacklist, dir_path + "/modules.blacklist",
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_blocklist, dir_path + "/modules.blocklist",
                                                  0600, getuid(), getgid()));
 
     for (auto i = test_modules.begin(); i != test_modules.end(); ++i) {
@@ -176,6 +176,6 @@
 
     EXPECT_TRUE(modules_loaded == expected_after_remove);
 
-    m.EnableBlacklist(true);
+    m.EnableBlocklist(true);
     EXPECT_FALSE(m.LoadWithAliases("test4", true));
 }
diff --git a/libnetutils/Android.bp b/libnetutils/Android.bp
index 268496f..65371fa 100644
--- a/libnetutils/Android.bp
+++ b/libnetutils/Android.bp
@@ -23,6 +23,21 @@
     export_include_dirs: ["include"],
 }
 
+cc_library_static {
+    name: "libipchecksum",
+
+    srcs: [
+        "checksum.c",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    export_include_dirs: ["include"],
+}
+
 cc_binary {
     name: "dhcpdbg",
 
diff --git a/libpixelflinger/include/pixelflinger/format.h b/libpixelflinger/include/pixelflinger/format.h
index 82eeca4..d429477 100644
--- a/libpixelflinger/include/pixelflinger/format.h
+++ b/libpixelflinger/include/pixelflinger/format.h
@@ -82,7 +82,7 @@
     GGL_INDEX_CR      = 2,
 };
 
-typedef struct {
+typedef struct GGLFormat {
 #ifdef __cplusplus
     enum {
         ALPHA   = GGL_INDEX_ALPHA,
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 4af4589..a638fca 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -44,6 +44,11 @@
 #define TASK_PROFILE_DB_FILE "/etc/task_profiles.json"
 #define TASK_PROFILE_DB_VENDOR_FILE "/vendor/etc/task_profiles.json"
 
+void ProfileAttribute::Reset(const CgroupController& controller, const std::string& file_name) {
+    controller_ = controller;
+    file_name_ = file_name;
+}
+
 bool ProfileAttribute::GetPathForTask(int tid, std::string* path) const {
     std::string subgroup;
     if (!controller()->GetTaskGroup(tid, &subgroup)) {
@@ -380,15 +385,16 @@
         std::string controller_name = attr[i]["Controller"].asString();
         std::string file_attr = attr[i]["File"].asString();
 
-        if (attributes_.find(name) == attributes_.end()) {
-            auto controller = cg_map.FindController(controller_name);
-            if (controller.HasValue()) {
+        auto controller = cg_map.FindController(controller_name);
+        if (controller.HasValue()) {
+            auto iter = attributes_.find(name);
+            if (iter == attributes_.end()) {
                 attributes_[name] = std::make_unique<ProfileAttribute>(controller, file_attr);
             } else {
-                LOG(WARNING) << "Controller " << controller_name << " is not found";
+                iter->second->Reset(controller, file_attr);
             }
         } else {
-            LOG(WARNING) << "Attribute " << name << " is already defined";
+            LOG(WARNING) << "Controller " << controller_name << " is not found";
         }
     }
 
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 28bc00c..2983a09 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -33,6 +33,7 @@
 
     const CgroupController* controller() const { return &controller_; }
     const std::string& file_name() const { return file_name_; }
+    void Reset(const CgroupController& controller, const std::string& file_name);
 
     bool GetPathForTask(int tid, std::string* path) const;
 
diff --git a/libprocinfo/Android.bp b/libprocinfo/Android.bp
index 15b0d89..ae45742 100644
--- a/libprocinfo/Android.bp
+++ b/libprocinfo/Android.bp
@@ -27,6 +27,8 @@
     name: "libprocinfo",
     defaults: ["libprocinfo_defaults"],
     vendor_available: true,
+    // TODO(b/153609531): remove when no longer needed.
+    native_bridge_supported: true,
     recovery_available: true,
     vndk: {
         enabled: true,
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 3c44534..8cc780a 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -124,6 +124,8 @@
     name: "libunwindstack",
     vendor_available: true,
     recovery_available: true,
+    // TODO(b/153609531): remove when no longer needed.
+    native_bridge_supported: true,
     vndk: {
         enabled: true,
         support_system_process: true,
@@ -145,6 +147,11 @@
             exclude_srcs: ["DexFile.cpp"],
             exclude_shared_libs: ["libdexfile_support"],
         },
+        native_bridge: {
+            cflags: ["-UDEXFILE_SUPPORT"],
+            exclude_srcs: ["DexFile.cpp"],
+            exclude_shared_libs: ["libdexfile_support"],
+        },
     },
 
     apex_available: [
@@ -334,6 +341,37 @@
 }
 
 //-------------------------------------------------------------------------
+// Fuzzers
+//-------------------------------------------------------------------------
+cc_defaults {
+    name: "libunwindstack_fuzz_defaults",
+    host_supported: true,
+    defaults: ["libunwindstack_flags"],
+    cflags: [
+        "-Wno-exit-time-destructors",
+        "-g",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "liblzma",
+        "libunwindstack",
+        "libdexfile_support",
+    ],
+}
+
+cc_fuzz {
+    name: "libunwindstack_fuzz_unwinder",
+    defaults: ["libunwindstack_fuzz_defaults"],
+    srcs: [
+        "tests/MemoryFake.cpp",
+        "tests/ElfFake.cpp",
+        "tests/fuzz/UnwinderComponentCreator.cpp",
+        "tests/fuzz/UnwinderFuzz.cpp",
+    ],
+}
+
+//-------------------------------------------------------------------------
 // Tools
 //-------------------------------------------------------------------------
 cc_defaults {
@@ -451,3 +489,4 @@
         "tests/GenGnuDebugdata.cpp",
     ],
 }
+
diff --git a/libunwindstack/DwarfCfa.cpp b/libunwindstack/DwarfCfa.cpp
index c128b9b..c6db209 100644
--- a/libunwindstack/DwarfCfa.cpp
+++ b/libunwindstack/DwarfCfa.cpp
@@ -26,7 +26,9 @@
 
 #include <unwindstack/DwarfError.h>
 #include <unwindstack/DwarfLocation.h>
+#include <unwindstack/Elf.h>
 #include <unwindstack/Log.h>
+#include <unwindstack/MachineArm64.h>
 
 #include "DwarfCfa.h"
 #include "DwarfEncoding.h"
@@ -204,8 +206,12 @@
 bool DwarfCfa<AddressType>::LogInstruction(uint32_t indent, uint64_t cfa_offset, uint8_t op,
                                            uint64_t* cur_pc) {
   const auto* cfa = &DwarfCfaInfo::kTable[op];
-  if (cfa->name[0] == '\0') {
-    log(indent, "Illegal");
+  if (cfa->name[0] == '\0' || (arch_ != ARCH_ARM64 && op == 0x2d)) {
+    if (op == 0x2d) {
+      log(indent, "Illegal (Only valid on aarch64)");
+    } else {
+      log(indent, "Illegal");
+    }
     log(indent, "Raw Data: 0x%02x", op);
     return true;
   }
@@ -514,6 +520,24 @@
   return true;
 }
 
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_aarch64_negate_ra_state(dwarf_loc_regs_t* loc_regs) {
+  // Only supported on aarch64.
+  if (arch_ != ARCH_ARM64) {
+    last_error_.code = DWARF_ERROR_ILLEGAL_VALUE;
+    return false;
+  }
+
+  auto cfa_location = loc_regs->find(Arm64Reg::ARM64_PREG_RA_SIGN_STATE);
+  if (cfa_location == loc_regs->end()) {
+    (*loc_regs)[Arm64Reg::ARM64_PREG_RA_SIGN_STATE] = {.type = DWARF_LOCATION_PSEUDO_REGISTER,
+                                                       .values = {1}};
+  } else {
+    cfa_location->second.values[0] ^= 1;
+  }
+  return true;
+}
+
 const DwarfCfaInfo::Info DwarfCfaInfo::kTable[64] = {
     {
         // 0x00 DW_CFA_nop
@@ -699,7 +723,13 @@
     {"", 0, 0, {}, {}},  // 0x2a illegal cfa
     {"", 0, 0, {}, {}},  // 0x2b illegal cfa
     {"", 0, 0, {}, {}},  // 0x2c illegal cfa
-    {"", 0, 0, {}, {}},  // 0x2d DW_CFA_GNU_window_save (Treat as illegal)
+    {
+        "DW_CFA_AARCH64_negate_ra_state",  // 0x2d DW_CFA_AARCH64_negate_ra_state
+        3,
+        0,
+        {},
+        {},
+    },
     {
         "DW_CFA_GNU_args_size",  // 0x2e DW_CFA_GNU_args_size
         2,
diff --git a/libunwindstack/DwarfCfa.h b/libunwindstack/DwarfCfa.h
index 569c17c..d627e15 100644
--- a/libunwindstack/DwarfCfa.h
+++ b/libunwindstack/DwarfCfa.h
@@ -31,6 +31,9 @@
 
 namespace unwindstack {
 
+// Forward declarations.
+enum ArchEnum : uint8_t;
+
 // DWARF Standard home: http://dwarfstd.org/
 // This code is based on DWARF 4: http://http://dwarfstd.org/doc/DWARF4.pdf
 // See section 6.4.2.1 for a description of the DW_CFA_xxx values.
@@ -72,7 +75,8 @@
   typedef typename std::make_signed<AddressType>::type SignedType;
 
  public:
-  DwarfCfa(DwarfMemory* memory, const DwarfFde* fde) : memory_(memory), fde_(fde) {}
+  DwarfCfa(DwarfMemory* memory, const DwarfFde* fde, ArchEnum arch)
+      : memory_(memory), fde_(fde), arch_(arch) {}
   virtual ~DwarfCfa() = default;
 
   bool GetLocationInfo(uint64_t pc, uint64_t start_offset, uint64_t end_offset,
@@ -99,6 +103,7 @@
   DwarfErrorData last_error_;
   DwarfMemory* memory_;
   const DwarfFde* fde_;
+  ArchEnum arch_;
 
   AddressType cur_pc_;
   const dwarf_loc_regs_t* cie_loc_regs_ = nullptr;
@@ -128,6 +133,7 @@
   bool cfa_val_offset_sf(dwarf_loc_regs_t*);
   bool cfa_val_expression(dwarf_loc_regs_t*);
   bool cfa_gnu_negative_offset_extended(dwarf_loc_regs_t*);
+  bool cfa_aarch64_negate_ra_state(dwarf_loc_regs_t*);
 
   using process_func = bool (DwarfCfa::*)(dwarf_loc_regs_t*);
   constexpr static process_func kCallbackTable[64] = {
@@ -221,8 +227,9 @@
       nullptr,
       // 0x2c illegal cfa
       nullptr,
-      // 0x2d DW_CFA_GNU_window_save (Treat this as illegal)
-      nullptr,
+      // 0x2d DW_CFA_AARCH64_negate_ra_state (aarch64 only)
+      // DW_CFA_GNU_window_save on other architectures.
+      &DwarfCfa::cfa_aarch64_negate_ra_state,
       // 0x2e DW_CFA_GNU_args_size
       &DwarfCfa::cfa_nop,
       // 0x2f DW_CFA_GNU_negative_offset_extended
diff --git a/libunwindstack/DwarfSection.cpp b/libunwindstack/DwarfSection.cpp
index 18bd490..9e2a3cd 100644
--- a/libunwindstack/DwarfSection.cpp
+++ b/libunwindstack/DwarfSection.cpp
@@ -21,6 +21,7 @@
 #include <unwindstack/DwarfMemory.h>
 #include <unwindstack/DwarfSection.h>
 #include <unwindstack/DwarfStructs.h>
+#include <unwindstack/Elf.h>
 #include <unwindstack/Log.h>
 #include <unwindstack/Memory.h>
 #include <unwindstack/Regs.h>
@@ -49,7 +50,7 @@
 
     // Now get the location information for this pc.
     dwarf_loc_regs_t loc_regs;
-    if (!GetCfaLocationInfo(pc, fde, &loc_regs)) {
+    if (!GetCfaLocationInfo(pc, fde, &loc_regs, regs->Arch())) {
       return false;
     }
     loc_regs.cie = fde->cie;
@@ -464,6 +465,13 @@
         eval_info->return_address_undefined = true;
       }
       break;
+    case DWARF_LOCATION_PSEUDO_REGISTER: {
+      if (!eval_info->regs_info.regs->SetPseudoRegister(reg, loc->values[0])) {
+        last_error_.code = DWARF_ERROR_ILLEGAL_VALUE;
+        return false;
+      }
+      break;
+    }
     default:
       break;
   }
@@ -491,6 +499,10 @@
   // Always set the dex pc to zero when evaluating.
   cur_regs->set_dex_pc(0);
 
+  // Reset necessary pseudo registers before evaluation.
+  // This is needed for ARM64, for example.
+  regs->ResetPseudoRegisters();
+
   EvalInfo<AddressType> eval_info{.loc_regs = &loc_regs,
                                   .cie = cie,
                                   .regular_memory = regular_memory,
@@ -527,8 +539,10 @@
 
     AddressType* reg_ptr;
     if (reg >= cur_regs->total_regs()) {
-      // Skip this unknown register.
-      continue;
+      if (entry.second.type != DWARF_LOCATION_PSEUDO_REGISTER) {
+        // Skip this unknown register.
+        continue;
+      }
     }
 
     reg_ptr = eval_info.regs_info.Save(reg);
@@ -554,8 +568,8 @@
 
 template <typename AddressType>
 bool DwarfSectionImpl<AddressType>::GetCfaLocationInfo(uint64_t pc, const DwarfFde* fde,
-                                                       dwarf_loc_regs_t* loc_regs) {
-  DwarfCfa<AddressType> cfa(&memory_, fde);
+                                                       dwarf_loc_regs_t* loc_regs, ArchEnum arch) {
+  DwarfCfa<AddressType> cfa(&memory_, fde, arch);
 
   // Look for the cached copy of the cie data.
   auto reg_entry = cie_loc_regs_.find(fde->cie_offset);
@@ -576,8 +590,9 @@
 }
 
 template <typename AddressType>
-bool DwarfSectionImpl<AddressType>::Log(uint8_t indent, uint64_t pc, const DwarfFde* fde) {
-  DwarfCfa<AddressType> cfa(&memory_, fde);
+bool DwarfSectionImpl<AddressType>::Log(uint8_t indent, uint64_t pc, const DwarfFde* fde,
+                                        ArchEnum arch) {
+  DwarfCfa<AddressType> cfa(&memory_, fde, arch);
 
   // Always print the cie information.
   const DwarfCie* cie = fde->cie;
diff --git a/libunwindstack/RegsArm64.cpp b/libunwindstack/RegsArm64.cpp
index 5b7431a..b496187 100644
--- a/libunwindstack/RegsArm64.cpp
+++ b/libunwindstack/RegsArm64.cpp
@@ -30,7 +30,10 @@
 namespace unwindstack {
 
 RegsArm64::RegsArm64()
-    : RegsImpl<uint64_t>(ARM64_REG_LAST, Location(LOCATION_REGISTER, ARM64_REG_LR)) {}
+    : RegsImpl<uint64_t>(ARM64_REG_LAST, Location(LOCATION_REGISTER, ARM64_REG_LR)) {
+  ResetPseudoRegisters();
+  pac_mask_ = 0;
+}
 
 ArchEnum RegsArm64::Arch() {
   return ARCH_ARM64;
@@ -45,6 +48,23 @@
 }
 
 void RegsArm64::set_pc(uint64_t pc) {
+  // If the target is aarch64 then the return address may have been
+  // signed using the Armv8.3-A Pointer Authentication extension. The
+  // original return address can be restored by stripping out the
+  // authentication code using a mask or xpaclri. xpaclri is a NOP on
+  // pre-Armv8.3-A architectures.
+  if ((0 != pc) && IsRASigned()) {
+    if (pac_mask_) {
+      pc &= ~pac_mask_;
+#if defined(__aarch64__)
+    } else {
+      register uint64_t x30 __asm("x30") = pc;
+      // This is XPACLRI.
+      asm("hint 0x7" : "+r"(x30));
+      pc = x30;
+#endif
+    }
+  }
   regs_[ARM64_REG_PC] = pc;
 }
 
@@ -144,6 +164,37 @@
   return true;
 }
 
+void RegsArm64::ResetPseudoRegisters(void) {
+  // DWARF for AArch64 says RA_SIGN_STATE should be initialized to 0.
+  this->SetPseudoRegister(Arm64Reg::ARM64_PREG_RA_SIGN_STATE, 0);
+}
+
+bool RegsArm64::SetPseudoRegister(uint16_t id, uint64_t value) {
+  if ((id >= Arm64Reg::ARM64_PREG_FIRST) && (id < Arm64Reg::ARM64_PREG_LAST)) {
+    pseudo_regs_[id - Arm64Reg::ARM64_PREG_FIRST] = value;
+    return true;
+  }
+  return false;
+}
+
+bool RegsArm64::GetPseudoRegister(uint16_t id, uint64_t* value) {
+  if ((id >= Arm64Reg::ARM64_PREG_FIRST) && (id < Arm64Reg::ARM64_PREG_LAST)) {
+    *value = pseudo_regs_[id - Arm64Reg::ARM64_PREG_FIRST];
+    return true;
+  }
+  return false;
+}
+
+bool RegsArm64::IsRASigned() {
+  uint64_t value;
+  auto result = this->GetPseudoRegister(Arm64Reg::ARM64_PREG_RA_SIGN_STATE, &value);
+  return (result && (value != 0));
+}
+
+void RegsArm64::SetPACMask(uint64_t mask) {
+  pac_mask_ = mask;
+}
+
 Regs* RegsArm64::Clone() {
   return new RegsArm64(*this);
 }
diff --git a/libunwindstack/include/unwindstack/DwarfLocation.h b/libunwindstack/include/unwindstack/DwarfLocation.h
index 3d50ccf..bf45bc7 100644
--- a/libunwindstack/include/unwindstack/DwarfLocation.h
+++ b/libunwindstack/include/unwindstack/DwarfLocation.h
@@ -33,6 +33,7 @@
   DWARF_LOCATION_REGISTER,
   DWARF_LOCATION_EXPRESSION,
   DWARF_LOCATION_VAL_EXPRESSION,
+  DWARF_LOCATION_PSEUDO_REGISTER,
 };
 
 struct DwarfLocation {
diff --git a/libunwindstack/include/unwindstack/DwarfSection.h b/libunwindstack/include/unwindstack/DwarfSection.h
index c244749..af823da 100644
--- a/libunwindstack/include/unwindstack/DwarfSection.h
+++ b/libunwindstack/include/unwindstack/DwarfSection.h
@@ -31,6 +31,7 @@
 namespace unwindstack {
 
 // Forward declarations.
+enum ArchEnum : uint8_t;
 class Memory;
 class Regs;
 template <typename AddressType>
@@ -90,13 +91,14 @@
 
   virtual bool Eval(const DwarfCie*, Memory*, const dwarf_loc_regs_t&, Regs*, bool*) = 0;
 
-  virtual bool Log(uint8_t indent, uint64_t pc, const DwarfFde* fde) = 0;
+  virtual bool Log(uint8_t indent, uint64_t pc, const DwarfFde* fde, ArchEnum arch) = 0;
 
   virtual void GetFdes(std::vector<const DwarfFde*>* fdes) = 0;
 
   virtual const DwarfFde* GetFdeFromPc(uint64_t pc) = 0;
 
-  virtual bool GetCfaLocationInfo(uint64_t pc, const DwarfFde* fde, dwarf_loc_regs_t* loc_regs) = 0;
+  virtual bool GetCfaLocationInfo(uint64_t pc, const DwarfFde* fde, dwarf_loc_regs_t* loc_regs,
+                                  ArchEnum arch) = 0;
 
   virtual uint64_t GetCieOffsetFromFde32(uint32_t pointer) = 0;
 
@@ -140,9 +142,10 @@
   bool Eval(const DwarfCie* cie, Memory* regular_memory, const dwarf_loc_regs_t& loc_regs,
             Regs* regs, bool* finished) override;
 
-  bool GetCfaLocationInfo(uint64_t pc, const DwarfFde* fde, dwarf_loc_regs_t* loc_regs) override;
+  bool GetCfaLocationInfo(uint64_t pc, const DwarfFde* fde, dwarf_loc_regs_t* loc_regs,
+                          ArchEnum arch) override;
 
-  bool Log(uint8_t indent, uint64_t pc, const DwarfFde* fde) override;
+  bool Log(uint8_t indent, uint64_t pc, const DwarfFde* fde, ArchEnum arch) override;
 
  protected:
   bool GetNextCieOrFde(const DwarfFde** fde_entry);
diff --git a/libunwindstack/include/unwindstack/MachineArm64.h b/libunwindstack/include/unwindstack/MachineArm64.h
index e953335..358e3d9 100644
--- a/libunwindstack/include/unwindstack/MachineArm64.h
+++ b/libunwindstack/include/unwindstack/MachineArm64.h
@@ -60,6 +60,13 @@
 
   ARM64_REG_SP = ARM64_REG_R31,
   ARM64_REG_LR = ARM64_REG_R30,
+
+  // Pseudo registers. These are not machine registers.
+
+  // AARCH64 Return address signed state pseudo-register
+  ARM64_PREG_RA_SIGN_STATE = 34,
+  ARM64_PREG_FIRST = ARM64_PREG_RA_SIGN_STATE,
+  ARM64_PREG_LAST,
 };
 
 }  // namespace unwindstack
diff --git a/libunwindstack/include/unwindstack/Regs.h b/libunwindstack/include/unwindstack/Regs.h
index a367e6c..5f42565 100644
--- a/libunwindstack/include/unwindstack/Regs.h
+++ b/libunwindstack/include/unwindstack/Regs.h
@@ -64,6 +64,10 @@
   uint64_t dex_pc() { return dex_pc_; }
   void set_dex_pc(uint64_t dex_pc) { dex_pc_ = dex_pc; }
 
+  virtual void ResetPseudoRegisters() {}
+  virtual bool SetPseudoRegister(uint16_t, uint64_t) { return false; }
+  virtual bool GetPseudoRegister(uint16_t, uint64_t*) { return false; }
+
   virtual bool StepIfSignalHandler(uint64_t elf_offset, Elf* elf, Memory* process_memory) = 0;
 
   virtual bool SetPcFromReturnAddress(Memory* process_memory) = 0;
diff --git a/libunwindstack/include/unwindstack/RegsArm64.h b/libunwindstack/include/unwindstack/RegsArm64.h
index 2b3ddeb..bf7ab15 100644
--- a/libunwindstack/include/unwindstack/RegsArm64.h
+++ b/libunwindstack/include/unwindstack/RegsArm64.h
@@ -22,6 +22,7 @@
 #include <functional>
 
 #include <unwindstack/Elf.h>
+#include <unwindstack/MachineArm64.h>
 #include <unwindstack/Regs.h>
 
 namespace unwindstack {
@@ -48,11 +49,25 @@
   void set_pc(uint64_t pc) override;
   void set_sp(uint64_t sp) override;
 
+  void ResetPseudoRegisters() override;
+
+  bool SetPseudoRegister(uint16_t id, uint64_t value) override;
+
+  bool GetPseudoRegister(uint16_t id, uint64_t* value) override;
+
+  bool IsRASigned();
+
+  void SetPACMask(uint64_t mask);
+
   Regs* Clone() override final;
 
   static Regs* Read(void* data);
 
   static Regs* CreateFromUcontext(void* ucontext);
+
+ protected:
+  uint64_t pseudo_regs_[Arm64Reg::ARM64_PREG_LAST - Arm64Reg::ARM64_PREG_FIRST];
+  uint64_t pac_mask_;
 };
 
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/DwarfCfaLogTest.cpp b/libunwindstack/tests/DwarfCfaLogTest.cpp
index def4088..2b5a8dc 100644
--- a/libunwindstack/tests/DwarfCfaLogTest.cpp
+++ b/libunwindstack/tests/DwarfCfaLogTest.cpp
@@ -26,6 +26,7 @@
 #include <unwindstack/DwarfLocation.h>
 #include <unwindstack/DwarfMemory.h>
 #include <unwindstack/DwarfStructs.h>
+#include <unwindstack/Elf.h>
 #include <unwindstack/Log.h>
 
 #include "DwarfCfa.h"
@@ -57,7 +58,7 @@
     fde_.pc_end = 0x2000;
     fde_.pc_end = 0x10000;
     fde_.cie = &cie_;
-    cfa_.reset(new DwarfCfa<TypeParam>(dmem_.get(), &fde_));
+    cfa_.reset(new DwarfCfa<TypeParam>(dmem_.get(), &fde_, ARCH_UNKNOWN));
   }
 
   MemoryFake memory_;
@@ -72,8 +73,8 @@
 
 TYPED_TEST_P(DwarfCfaLogTest, cfa_illegal) {
   for (uint8_t i = 0x17; i < 0x3f; i++) {
-    if (i == 0x2e || i == 0x2f) {
-      // Skip gnu extension ops.
+    if (i == 0x2d || i == 0x2e || i == 0x2f) {
+      // Skip gnu extension ops and aarch64 specialized op.
       continue;
     }
     this->memory_.SetMemory(0x2000, std::vector<uint8_t>{i});
@@ -763,6 +764,26 @@
   ASSERT_EQ("", GetFakeLogBuf());
 }
 
+TYPED_TEST_P(DwarfCfaLogTest, cfa_aarch64_negate_ra_state) {
+  // Verify that if the cfa op is handled properly depending on aarch.
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x2d});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2000, 0x2001));
+  std::string expected = "4 unwind Illegal (Only valid on aarch64)\n";
+  expected += "4 unwind Raw Data: 0x2d\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->cfa_.reset(new DwarfCfa<TypeParam>(this->dmem_.get(), &this->fde_, ARCH_ARM64));
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2000, 0x2001));
+  expected = "4 unwind DW_CFA_AARCH64_negate_ra_state\n";
+  expected += "4 unwind Raw Data: 0x2d\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
 REGISTER_TYPED_TEST_SUITE_P(DwarfCfaLogTest, cfa_illegal, cfa_nop, cfa_offset, cfa_offset_extended,
                             cfa_offset_extended_sf, cfa_restore, cfa_restore_extended, cfa_set_loc,
                             cfa_advance_loc, cfa_advance_loc1, cfa_advance_loc2, cfa_advance_loc4,
@@ -771,7 +792,8 @@
                             cfa_def_cfa_register, cfa_def_cfa_offset, cfa_def_cfa_offset_sf,
                             cfa_def_cfa_expression, cfa_expression, cfa_val_offset,
                             cfa_val_offset_sf, cfa_val_expression, cfa_gnu_args_size,
-                            cfa_gnu_negative_offset_extended, cfa_register_override);
+                            cfa_gnu_negative_offset_extended, cfa_register_override,
+                            cfa_aarch64_negate_ra_state);
 
 typedef ::testing::Types<uint32_t, uint64_t> DwarfCfaLogTestTypes;
 INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfCfaLogTest, DwarfCfaLogTestTypes);
diff --git a/libunwindstack/tests/DwarfCfaTest.cpp b/libunwindstack/tests/DwarfCfaTest.cpp
index 9c6ab05..ea7e708 100644
--- a/libunwindstack/tests/DwarfCfaTest.cpp
+++ b/libunwindstack/tests/DwarfCfaTest.cpp
@@ -25,7 +25,9 @@
 #include <unwindstack/DwarfLocation.h>
 #include <unwindstack/DwarfMemory.h>
 #include <unwindstack/DwarfStructs.h>
+#include <unwindstack/Elf.h>
 #include <unwindstack/Log.h>
+#include <unwindstack/MachineArm64.h>
 
 #include "DwarfCfa.h"
 
@@ -55,7 +57,7 @@
     fde_.pc_start = 0x2000;
     fde_.cie = &cie_;
 
-    cfa_.reset(new DwarfCfa<TypeParam>(dmem_.get(), &fde_));
+    cfa_.reset(new DwarfCfa<TypeParam>(dmem_.get(), &fde_, ARCH_UNKNOWN));
   }
 
   MemoryFake memory_;
@@ -70,8 +72,8 @@
 
 TYPED_TEST_P(DwarfCfaTest, cfa_illegal) {
   for (uint8_t i = 0x17; i < 0x3f; i++) {
-    if (i == 0x2e || i == 0x2f) {
-      // Skip gnu extension ops.
+    if (i == 0x2d || i == 0x2e || i == 0x2f) {
+      // Skip gnu extension ops and aarch64 specialized op.
       continue;
     }
     this->memory_.SetMemory(0x2000, std::vector<uint8_t>{i});
@@ -952,6 +954,57 @@
   ASSERT_EQ("", GetFakeLogBuf());
 }
 
+TYPED_TEST_P(DwarfCfaTest, cfa_aarch64_negate_ra_state) {
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x2d});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_FALSE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs));
+  ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->cfa_->LastErrorCode());
+  ASSERT_EQ(0x2001U, this->dmem_->cur_offset());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->cfa_.reset(new DwarfCfa<TypeParam>(this->dmem_.get(), &this->fde_, ARCH_ARM64));
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs));
+  ASSERT_EQ(0x2001U, this->dmem_->cur_offset());
+
+  auto location = loc_regs.find(Arm64Reg::ARM64_PREG_RA_SIGN_STATE);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_PSEUDO_REGISTER, location->second.type);
+  ASSERT_EQ(1U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Verify that the value is set to 0 after another evaluation.
+  ResetLogs();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs));
+  ASSERT_EQ(0x2001U, this->dmem_->cur_offset());
+
+  location = loc_regs.find(Arm64Reg::ARM64_PREG_RA_SIGN_STATE);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_PSEUDO_REGISTER, location->second.type);
+  ASSERT_EQ(0U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Verify that the value is set to 1 again after a third op.
+  ResetLogs();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs));
+  ASSERT_EQ(0x2001U, this->dmem_->cur_offset());
+
+  location = loc_regs.find(Arm64Reg::ARM64_PREG_RA_SIGN_STATE);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_PSEUDO_REGISTER, location->second.type);
+  ASSERT_EQ(1U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
 REGISTER_TYPED_TEST_SUITE_P(DwarfCfaTest, cfa_illegal, cfa_nop, cfa_offset, cfa_offset_extended,
                             cfa_offset_extended_sf, cfa_restore, cfa_restore_extended, cfa_set_loc,
                             cfa_advance_loc1, cfa_advance_loc2, cfa_advance_loc4, cfa_undefined,
@@ -960,7 +1013,7 @@
                             cfa_def_cfa_offset_sf, cfa_def_cfa_expression, cfa_expression,
                             cfa_val_offset, cfa_val_offset_sf, cfa_val_expression,
                             cfa_gnu_args_size, cfa_gnu_negative_offset_extended,
-                            cfa_register_override);
+                            cfa_register_override, cfa_aarch64_negate_ra_state);
 
 typedef ::testing::Types<uint32_t, uint64_t> DwarfCfaTestTypes;
 INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfCfaTest, DwarfCfaTestTypes);
diff --git a/libunwindstack/tests/DwarfSectionImplTest.cpp b/libunwindstack/tests/DwarfSectionImplTest.cpp
index cac59b7..d57cd33 100644
--- a/libunwindstack/tests/DwarfSectionImplTest.cpp
+++ b/libunwindstack/tests/DwarfSectionImplTest.cpp
@@ -20,6 +20,7 @@
 
 #include <unwindstack/DwarfError.h>
 #include <unwindstack/DwarfSection.h>
+#include <unwindstack/Elf.h>
 
 #include "DwarfEncoding.h"
 
@@ -505,7 +506,7 @@
   this->memory_.SetMemory(0x6000, std::vector<uint8_t>{0x09, 0x04, 0x03});
 
   dwarf_loc_regs_t loc_regs;
-  ASSERT_TRUE(this->section_->GetCfaLocationInfo(0x100, &fde, &loc_regs));
+  ASSERT_TRUE(this->section_->GetCfaLocationInfo(0x100, &fde, &loc_regs, ARCH_UNKNOWN));
   ASSERT_EQ(2U, loc_regs.size());
 
   auto entry = loc_regs.find(2);
@@ -535,7 +536,7 @@
   this->memory_.SetMemory(0x6000, std::vector<uint8_t>{0x09, 0x04, 0x03});
 
   dwarf_loc_regs_t loc_regs;
-  ASSERT_TRUE(this->section_->GetCfaLocationInfo(0x100, &fde, &loc_regs));
+  ASSERT_TRUE(this->section_->GetCfaLocationInfo(0x100, &fde, &loc_regs, ARCH_UNKNOWN));
   ASSERT_EQ(2U, loc_regs.size());
 
   auto entry = loc_regs.find(6);
@@ -560,7 +561,7 @@
 
   this->memory_.SetMemory(0x5000, std::vector<uint8_t>{0x00});
   this->memory_.SetMemory(0x6000, std::vector<uint8_t>{0xc2});
-  ASSERT_TRUE(this->section_->Log(2, 0x1000, &fde));
+  ASSERT_TRUE(this->section_->Log(2, 0x1000, &fde, ARCH_UNKNOWN));
 
   ASSERT_EQ(
       "4 unwind     DW_CFA_nop\n"
diff --git a/libunwindstack/tests/DwarfSectionTest.cpp b/libunwindstack/tests/DwarfSectionTest.cpp
index 953dc75..febd6d3 100644
--- a/libunwindstack/tests/DwarfSectionTest.cpp
+++ b/libunwindstack/tests/DwarfSectionTest.cpp
@@ -20,8 +20,10 @@
 #include <gtest/gtest.h>
 
 #include <unwindstack/DwarfSection.h>
+#include <unwindstack/Elf.h>
 
 #include "MemoryFake.h"
+#include "RegsFake.h"
 
 namespace unwindstack {
 
@@ -35,13 +37,14 @@
   MOCK_METHOD(bool, Eval, (const DwarfCie*, Memory*, const dwarf_loc_regs_t&, Regs*, bool*),
               (override));
 
-  MOCK_METHOD(bool, Log, (uint8_t, uint64_t, const DwarfFde*), (override));
+  MOCK_METHOD(bool, Log, (uint8_t, uint64_t, const DwarfFde*, ArchEnum arch), (override));
 
   MOCK_METHOD(void, GetFdes, (std::vector<const DwarfFde*>*), (override));
 
   MOCK_METHOD(const DwarfFde*, GetFdeFromPc, (uint64_t), (override));
 
-  MOCK_METHOD(bool, GetCfaLocationInfo, (uint64_t, const DwarfFde*, dwarf_loc_regs_t*), (override));
+  MOCK_METHOD(bool, GetCfaLocationInfo,
+              (uint64_t, const DwarfFde*, dwarf_loc_regs_t*, ArchEnum arch), (override));
 
   MOCK_METHOD(uint64_t, GetCieOffsetFromFde32, (uint32_t), (override));
 
@@ -56,8 +59,11 @@
 
   MemoryFake memory_;
   std::unique_ptr<MockDwarfSection> section_;
+  static RegsFake regs_;
 };
 
+RegsFake DwarfSectionTest::regs_(10);
+
 TEST_F(DwarfSectionTest, Step_fail_fde) {
   EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(nullptr));
 
@@ -73,7 +79,7 @@
   EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(&fde));
 
   bool finished;
-  ASSERT_FALSE(section_->Step(0x1000, nullptr, nullptr, &finished));
+  ASSERT_FALSE(section_->Step(0x1000, &regs_, nullptr, &finished));
 }
 
 TEST_F(DwarfSectionTest, Step_fail_cfa_location) {
@@ -83,11 +89,11 @@
   fde.cie = &cie;
 
   EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(&fde));
-  EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_))
+  EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_, ::testing::_))
       .WillOnce(::testing::Return(false));
 
   bool finished;
-  ASSERT_FALSE(section_->Step(0x1000, nullptr, nullptr, &finished));
+  ASSERT_FALSE(section_->Step(0x1000, &regs_, nullptr, &finished));
 }
 
 TEST_F(DwarfSectionTest, Step_pass) {
@@ -97,19 +103,19 @@
   fde.cie = &cie;
 
   EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(&fde));
-  EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_))
+  EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_, ::testing::_))
       .WillOnce(::testing::Return(true));
 
   MemoryFake process;
-  EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, nullptr, ::testing::_))
+  EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, &regs_, ::testing::_))
       .WillOnce(::testing::Return(true));
 
   bool finished;
-  ASSERT_TRUE(section_->Step(0x1000, nullptr, &process, &finished));
+  ASSERT_TRUE(section_->Step(0x1000, &regs_, &process, &finished));
 }
 
 static bool MockGetCfaLocationInfo(::testing::Unused, const DwarfFde* fde,
-                                   dwarf_loc_regs_t* loc_regs) {
+                                   dwarf_loc_regs_t* loc_regs, ArchEnum) {
   loc_regs->pc_start = fde->pc_start;
   loc_regs->pc_end = fde->pc_end;
   return true;
@@ -123,17 +129,17 @@
   fde.cie = &cie;
 
   EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(&fde));
-  EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_))
+  EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_, ::testing::_))
       .WillOnce(::testing::Invoke(MockGetCfaLocationInfo));
 
   MemoryFake process;
-  EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, nullptr, ::testing::_))
+  EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, &regs_, ::testing::_))
       .WillRepeatedly(::testing::Return(true));
 
   bool finished;
-  ASSERT_TRUE(section_->Step(0x1000, nullptr, &process, &finished));
-  ASSERT_TRUE(section_->Step(0x1000, nullptr, &process, &finished));
-  ASSERT_TRUE(section_->Step(0x1500, nullptr, &process, &finished));
+  ASSERT_TRUE(section_->Step(0x1000, &regs_, &process, &finished));
+  ASSERT_TRUE(section_->Step(0x1000, &regs_, &process, &finished));
+  ASSERT_TRUE(section_->Step(0x1500, &regs_, &process, &finished));
 }
 
 TEST_F(DwarfSectionTest, Step_cache_not_in_pc) {
@@ -143,26 +149,26 @@
   fde0.pc_end = 0x2000;
   fde0.cie = &cie;
   EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(&fde0));
-  EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde0, ::testing::_))
+  EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde0, ::testing::_, ::testing::_))
       .WillOnce(::testing::Invoke(MockGetCfaLocationInfo));
 
   MemoryFake process;
-  EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, nullptr, ::testing::_))
+  EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, &regs_, ::testing::_))
       .WillRepeatedly(::testing::Return(true));
 
   bool finished;
-  ASSERT_TRUE(section_->Step(0x1000, nullptr, &process, &finished));
+  ASSERT_TRUE(section_->Step(0x1000, &regs_, &process, &finished));
 
   DwarfFde fde1{};
   fde1.pc_start = 0x500;
   fde1.pc_end = 0x800;
   fde1.cie = &cie;
   EXPECT_CALL(*section_, GetFdeFromPc(0x600)).WillOnce(::testing::Return(&fde1));
-  EXPECT_CALL(*section_, GetCfaLocationInfo(0x600, &fde1, ::testing::_))
+  EXPECT_CALL(*section_, GetCfaLocationInfo(0x600, &fde1, ::testing::_, ::testing::_))
       .WillOnce(::testing::Invoke(MockGetCfaLocationInfo));
 
-  ASSERT_TRUE(section_->Step(0x600, nullptr, &process, &finished));
-  ASSERT_TRUE(section_->Step(0x700, nullptr, &process, &finished));
+  ASSERT_TRUE(section_->Step(0x600, &regs_, &process, &finished));
+  ASSERT_TRUE(section_->Step(0x700, &regs_, &process, &finished));
 }
 
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/RegsTest.cpp b/libunwindstack/tests/RegsTest.cpp
index e4fc6f0..acf72de 100644
--- a/libunwindstack/tests/RegsTest.cpp
+++ b/libunwindstack/tests/RegsTest.cpp
@@ -247,6 +247,14 @@
   EXPECT_EQ(0xc200000000U, mips64.pc());
 }
 
+TEST_F(RegsTest, arm64_strip_pac_mask) {
+  RegsArm64 arm64;
+  arm64.SetPseudoRegister(Arm64Reg::ARM64_PREG_RA_SIGN_STATE, 1);
+  arm64.SetPACMask(0x007fff8000000000ULL);
+  arm64.set_pc(0x0020007214bb3a04ULL);
+  EXPECT_EQ(0x0000007214bb3a04ULL, arm64.pc());
+}
+
 TEST_F(RegsTest, machine_type) {
   RegsArm arm_regs;
   EXPECT_EQ(ARCH_ARM, arm_regs.Arch());
diff --git a/libunwindstack/tests/VerifyBionicTerminationTest.cpp b/libunwindstack/tests/VerifyBionicTerminationTest.cpp
index 6a3e91a..eb2b01d 100644
--- a/libunwindstack/tests/VerifyBionicTerminationTest.cpp
+++ b/libunwindstack/tests/VerifyBionicTerminationTest.cpp
@@ -55,7 +55,7 @@
     return DWARF_LOCATION_INVALID;
   }
   dwarf_loc_regs_t regs;
-  if (!section->GetCfaLocationInfo(rel_pc, fde, &regs)) {
+  if (!section->GetCfaLocationInfo(rel_pc, fde, &regs, ARCH_UNKNOWN)) {
     return DWARF_LOCATION_INVALID;
   }
 
diff --git a/libunwindstack/tests/fuzz/UnwinderComponentCreator.cpp b/libunwindstack/tests/fuzz/UnwinderComponentCreator.cpp
new file mode 100644
index 0000000..94f5a73
--- /dev/null
+++ b/libunwindstack/tests/fuzz/UnwinderComponentCreator.cpp
@@ -0,0 +1,356 @@
+/*
+ * Copyright 2020 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 "UnwinderComponentCreator.h"
+
+std::unique_ptr<Regs> GetRegisters(ArchEnum arch) {
+  switch (arch) {
+    case unwindstack::ARCH_ARM: {
+      std::unique_ptr<unwindstack::RegsArm> regs = std::make_unique<unwindstack::RegsArm>();
+      return regs;
+    }
+    case unwindstack::ARCH_ARM64: {
+      std::unique_ptr<unwindstack::RegsArm64> regs = std::make_unique<unwindstack::RegsArm64>();
+      return regs;
+    }
+    case unwindstack::ARCH_X86: {
+      std::unique_ptr<unwindstack::RegsX86> regs = std::make_unique<unwindstack::RegsX86>();
+      return regs;
+    }
+    case unwindstack::ARCH_X86_64: {
+      std::unique_ptr<unwindstack::RegsX86_64> regs = std::make_unique<unwindstack::RegsX86_64>();
+      return regs;
+    }
+    case unwindstack::ARCH_MIPS: {
+      std::unique_ptr<unwindstack::RegsMips> regs = std::make_unique<unwindstack::RegsMips>();
+      return regs;
+    }
+    case unwindstack::ARCH_MIPS64: {
+      std::unique_ptr<unwindstack::RegsMips64> regs = std::make_unique<unwindstack::RegsMips64>();
+      return regs;
+    }
+    case unwindstack::ARCH_UNKNOWN:
+    default: {
+      std::unique_ptr<unwindstack::RegsX86_64> regs = std::make_unique<unwindstack::RegsX86_64>();
+      return regs;
+    }
+  }
+}
+
+ArchEnum GetArch(FuzzedDataProvider* data_provider) {
+  uint8_t arch = data_provider->ConsumeIntegralInRange<uint8_t>(1, kArchCount);
+  return static_cast<ArchEnum>(arch);
+}
+
+void ElfAddMapInfo(Maps* maps, uint64_t start, uint64_t end, uint64_t offset, uint64_t flags,
+                   const char* name, Elf* elf = nullptr) {
+  std::string str_name(name);
+  maps->Add(start, end, offset, flags, name, static_cast<uint64_t>(-1));
+  if (elf != nullptr) {
+    const auto& map_info = *--maps->end();
+    map_info->elf.reset(elf);
+  }
+}
+
+void ElfPushFakeFunctionData(FuzzedDataProvider* data_provider, ElfInterfaceFake* elf) {
+  uint8_t func_count = data_provider->ConsumeIntegralInRange<uint>(0, kMaxFuncCount);
+  for (uint8_t i = 0; i < func_count; i++) {
+    std::string func_name = data_provider->ConsumeRandomLengthString(kMaxFuncNameLen);
+    bool global = data_provider->ConsumeBool();
+    if (global) {
+      elf->FakeSetGlobalVariable(func_name, data_provider->ConsumeIntegral<uint64_t>());
+    } else {
+      ElfInterfaceFake::FakePushFunctionData(FunctionData(func_name, i));
+    }
+  }
+}
+void ElfPushFakeStepData(FuzzedDataProvider* data_provider) {
+  uint8_t step_count = data_provider->ConsumeIntegralInRange<uint>(0, kMaxStepCount);
+  for (uint8_t i = 0; i < step_count; i++) {
+    uint64_t pc = data_provider->ConsumeIntegral<uint64_t>();
+    uint64_t sp = data_provider->ConsumeIntegral<uint64_t>();
+    bool finished = i + 1 == step_count;
+    ElfInterfaceFake::FakePushStepData(StepData(pc, sp, finished));
+  }
+}
+
+ElfFake* PopulateElfFake(FuzzedDataProvider* data_provider) {
+  // This will be passed to a smart pointer in ElfAddMapInfo.
+  ElfFake* elf = new ElfFake(new MemoryFake);
+
+  // This will be handled by a smart pointer within Elf.
+  ElfInterfaceFake* interface_fake = new ElfInterfaceFake(nullptr);
+  std::string build_id = data_provider->ConsumeRandomLengthString(kMaxBuildIdLen);
+  interface_fake->FakeSetBuildID(build_id);
+  std::string so_name = data_provider->ConsumeRandomLengthString(kMaxSoNameLen);
+  interface_fake->FakeSetSoname(so_name.c_str());
+
+  elf->FakeSetArch(GetArch(data_provider));
+  elf->FakeSetLoadBias(data_provider->ConsumeIntegral<uint64_t>());
+
+  ElfPushFakeFunctionData(data_provider, interface_fake);
+  ElfPushFakeStepData(data_provider);
+
+  elf->FakeSetInterface(interface_fake);
+  ElfInterfaceFake::FakeClear();
+  return elf;
+}
+
+std::unique_ptr<Maps> GetMaps(FuzzedDataProvider* data_provider) {
+  std::unique_ptr<Maps> maps = std::make_unique<Maps>();
+  uint8_t entry_count = data_provider->ConsumeIntegralInRange<uint8_t>(0, kMaxMapEntryCount);
+  for (uint8_t i = 0; i < entry_count; i++) {
+    uint64_t start = data_provider->ConsumeIntegral<uint64_t>();
+    uint64_t end = data_provider->ConsumeIntegralInRange<uint64_t>(start, UINT64_MAX);
+    uint64_t offset = data_provider->ConsumeIntegral<uint64_t>();
+    std::string map_info_name = data_provider->ConsumeRandomLengthString(kMaxMapInfoNameLen);
+    uint8_t flags = PROT_READ | PROT_WRITE;
+
+    bool exec = data_provider->ConsumeBool();
+    if (exec) {
+      flags |= PROT_EXEC;
+    }
+
+    bool shouldAddElf = data_provider->ConsumeBool();
+    if (shouldAddElf) {
+      ElfAddMapInfo(maps.get(), start, end, offset, flags, map_info_name.c_str(),
+                    PopulateElfFake(data_provider));
+    } else {
+      ElfAddMapInfo(maps.get(), start, end, offset, flags, map_info_name.c_str());
+    }
+  }
+  maps->Sort();
+  return maps;
+}
+
+// This code (until PutElfFilesInMemory) is pretty much directly copied from JitDebugTest.cpp
+// There's a few minor modifications, most notably, all methods accept a MemoryFake pointer, and
+// PutElfInMemory inserts JIT data when called.
+void WriteDescriptor32(MemoryFake* memory, uint64_t addr, uint32_t entry) {
+  // Format of the 32 bit JITDescriptor structure:
+  //   uint32_t version
+  memory->SetData32(addr, 1);
+  //   uint32_t action_flag
+  memory->SetData32(addr + 4, 0);
+  //   uint32_t relevant_entry
+  memory->SetData32(addr + 8, 0);
+  //   uint32_t first_entry
+  memory->SetData32(addr + 12, entry);
+}
+
+void WriteDescriptor64(MemoryFake* memory, uint64_t addr, uint64_t entry) {
+  // Format of the 64 bit JITDescriptor structure:
+  //   uint32_t version
+  memory->SetData32(addr, 1);
+  //   uint32_t action_flag
+  memory->SetData32(addr + 4, 0);
+  //   uint64_t relevant_entry
+  memory->SetData64(addr + 8, 0);
+  //   uint64_t first_entry
+  memory->SetData64(addr + 16, entry);
+}
+
+void WriteEntry32Pack(MemoryFake* memory, uint64_t addr, uint32_t prev, uint32_t next,
+                      uint32_t elf_addr, uint64_t elf_size) {
+  // Format of the 32 bit JITCodeEntry structure:
+  //   uint32_t next
+  memory->SetData32(addr, next);
+  //   uint32_t prev
+  memory->SetData32(addr + 4, prev);
+  //   uint32_t symfile_addr
+  memory->SetData32(addr + 8, elf_addr);
+  //   uint64_t symfile_size
+  memory->SetData64(addr + 12, elf_size);
+}
+
+void WriteEntry32Pad(MemoryFake* memory, uint64_t addr, uint32_t prev, uint32_t next,
+                     uint32_t elf_addr, uint64_t elf_size) {
+  // Format of the 32 bit JITCodeEntry structure:
+  //   uint32_t next
+  memory->SetData32(addr, next);
+  //   uint32_t prev
+  memory->SetData32(addr + 4, prev);
+  //   uint32_t symfile_addr
+  memory->SetData32(addr + 8, elf_addr);
+  //   uint32_t pad
+  memory->SetData32(addr + 12, 0);
+  //   uint64_t symfile_size
+  memory->SetData64(addr + 16, elf_size);
+}
+
+void WriteEntry64(MemoryFake* memory, uint64_t addr, uint64_t prev, uint64_t next,
+                  uint64_t elf_addr, uint64_t elf_size) {
+  // Format of the 64 bit JITCodeEntry structure:
+  //   uint64_t next
+  memory->SetData64(addr, next);
+  //   uint64_t prev
+  memory->SetData64(addr + 8, prev);
+  //   uint64_t symfile_addr
+  memory->SetData64(addr + 16, elf_addr);
+  //   uint64_t symfile_size
+  memory->SetData64(addr + 24, elf_size);
+}
+
+template <typename EhdrType, typename ShdrType>
+void PutElfInMemory(MemoryFake* memory, uint64_t offset, uint8_t class_type, uint8_t machine_type,
+                    uint32_t pc, uint32_t size) {
+  EhdrType ehdr;
+  memset(&ehdr, 0, sizeof(ehdr));
+  uint64_t sh_offset = sizeof(ehdr);
+  memcpy(ehdr.e_ident, ELFMAG, SELFMAG);
+  ehdr.e_ident[EI_CLASS] = class_type;
+  ehdr.e_machine = machine_type;
+  ehdr.e_shstrndx = 1;
+  ehdr.e_shoff = sh_offset;
+  ehdr.e_shentsize = sizeof(ShdrType);
+  ehdr.e_shnum = 3;
+  memory->SetMemory(offset, &ehdr, sizeof(ehdr));
+
+  ShdrType shdr;
+  memset(&shdr, 0, sizeof(shdr));
+  shdr.sh_type = SHT_NULL;
+  memory->SetMemory(offset + sh_offset, &shdr, sizeof(shdr));
+
+  sh_offset += sizeof(shdr);
+  memset(&shdr, 0, sizeof(shdr));
+  shdr.sh_type = SHT_STRTAB;
+  shdr.sh_name = 1;
+  shdr.sh_offset = 0x500;
+  shdr.sh_size = 0x100;
+  memory->SetMemory(offset + sh_offset, &shdr, sizeof(shdr));
+  memory->SetMemory(offset + 0x500, ".debug_frame");
+
+  sh_offset += sizeof(shdr);
+  memset(&shdr, 0, sizeof(shdr));
+  shdr.sh_type = SHT_PROGBITS;
+  shdr.sh_name = 0;
+  shdr.sh_addr = 0x600;
+  shdr.sh_offset = 0x600;
+  shdr.sh_size = 0x200;
+  memory->SetMemory(offset + sh_offset, &shdr, sizeof(shdr));
+
+  // Now add a single cie/fde.
+  uint64_t dwarf_offset = offset + 0x600;
+  if (class_type == ELFCLASS32) {
+    // CIE 32 information.
+    memory->SetData32(dwarf_offset, 0xfc);
+    memory->SetData32(dwarf_offset + 0x4, 0xffffffff);
+    memory->SetData8(dwarf_offset + 0x8, 1);
+    memory->SetData8(dwarf_offset + 0x9, '\0');
+    memory->SetData8(dwarf_offset + 0xa, 0x4);
+    memory->SetData8(dwarf_offset + 0xb, 0x4);
+    memory->SetData8(dwarf_offset + 0xc, 0x1);
+
+    // FDE 32 information.
+    memory->SetData32(dwarf_offset + 0x100, 0xfc);
+    memory->SetData32(dwarf_offset + 0x104, 0);
+    memory->SetData32(dwarf_offset + 0x108, pc);
+    memory->SetData32(dwarf_offset + 0x10c, size);
+  } else {
+    // CIE 64 information.
+    memory->SetData32(dwarf_offset, 0xffffffff);
+    memory->SetData64(dwarf_offset + 4, 0xf4);
+    memory->SetData64(dwarf_offset + 0xc, 0xffffffffffffffffULL);
+    memory->SetData8(dwarf_offset + 0x14, 1);
+    memory->SetData8(dwarf_offset + 0x15, '\0');
+    memory->SetData8(dwarf_offset + 0x16, 0x4);
+    memory->SetData8(dwarf_offset + 0x17, 0x4);
+    memory->SetData8(dwarf_offset + 0x18, 0x1);
+
+    // FDE 64 information.
+    memory->SetData32(dwarf_offset + 0x100, 0xffffffff);
+    memory->SetData64(dwarf_offset + 0x104, 0xf4);
+    memory->SetData64(dwarf_offset + 0x10c, 0);
+    memory->SetData64(dwarf_offset + 0x114, pc);
+    memory->SetData64(dwarf_offset + 0x11c, size);
+  }
+}
+
+void PutElfFilesInMemory(MemoryFake* memory, FuzzedDataProvider* data_provider) {
+  uint8_t elf_file_count = data_provider->ConsumeIntegralInRange<uint8_t>(0, kMaxJitElfFiles);
+  int entry_offset = 0;
+  int prev_jit_addr = 0;
+  for (uint8_t i = 0; i < elf_file_count; i++) {
+    uint64_t offset = data_provider->ConsumeIntegral<uint64_t>();
+    // Technically the max valid value is ELFCLASSNUM - 1 (2), but
+    // we want to test values outside of that range.
+    uint8_t class_type = data_provider->ConsumeIntegral<uint8_t>();
+    // Same here, EM_NUM is 253, max valid machine type is 252
+    uint8_t machine_type = data_provider->ConsumeIntegral<uint8_t>();
+    uint32_t pc = data_provider->ConsumeIntegral<uint32_t>();
+    uint32_t size = data_provider->ConsumeIntegral<uint32_t>();
+    bool sixty_four_bit = data_provider->ConsumeBool();
+    bool write_jit = data_provider->ConsumeBool();
+    if (sixty_four_bit) {
+      PutElfInMemory<Elf64_Ehdr, Elf64_Shdr>(memory, offset, class_type, machine_type, pc, size);
+    } else {
+      PutElfInMemory<Elf32_Ehdr, Elf32_Shdr>(memory, offset, class_type, machine_type, pc, size);
+    }
+    if (write_jit) {
+      bool use_pad = data_provider->ConsumeBool();
+      // It is possible this will overwrite part of the ELF.
+      // This provides an interesting test of how malformed ELF
+      // data is handled.
+      uint64_t cur_descriptor_addr = 0x11800 + entry_offset;
+      uint64_t cur_jit_addr = 0x200000 + entry_offset;
+      uint64_t next_jit_addr = cur_jit_addr + size;
+      if (sixty_four_bit) {
+        WriteDescriptor64(memory, 0x11800, cur_jit_addr);
+        WriteEntry64(memory, cur_jit_addr, prev_jit_addr, next_jit_addr, pc, size);
+      } else {
+        // Loop back. Again, this may corrupt data,
+        // but that will allow for testing edge cases with
+        // malformed JIT data.
+        if (cur_jit_addr > UINT32_MAX) {
+          entry_offset = 0;
+          cur_jit_addr = 0x200000;
+          cur_descriptor_addr = 0x11800;
+          next_jit_addr = cur_jit_addr + size;
+        }
+        WriteDescriptor32(memory, cur_descriptor_addr, cur_jit_addr);
+        if (use_pad) {
+          WriteEntry32Pad(memory, cur_jit_addr, prev_jit_addr, next_jit_addr, pc, size);
+        } else {
+          WriteEntry32Pack(memory, cur_jit_addr, prev_jit_addr, next_jit_addr, pc, size);
+        }
+      }
+      entry_offset += size;
+      prev_jit_addr = cur_jit_addr;
+    }
+  }
+}
+
+std::vector<std::string> GetStringList(FuzzedDataProvider* data_provider, uint max_str_len,
+                                       uint max_strings) {
+  uint str_count = data_provider->ConsumeIntegralInRange<uint>(0, max_strings);
+  std::vector<std::string> strings;
+  for (uint i = 0; i < str_count; i++) {
+    strings.push_back(data_provider->ConsumeRandomLengthString(max_str_len));
+  }
+  return strings;
+}
+
+std::unique_ptr<DexFiles> GetDexFiles(FuzzedDataProvider* data_provider,
+                                      std::shared_ptr<Memory> memory, uint max_library_length,
+                                      uint max_libraries) {
+  std::vector<std::string> search_libs =
+      GetStringList(data_provider, max_library_length, max_libraries);
+  if (search_libs.size() <= 0) {
+    return std::make_unique<DexFiles>(memory);
+  }
+
+  return std::make_unique<DexFiles>(memory, search_libs);
+}
diff --git a/libunwindstack/tests/fuzz/UnwinderComponentCreator.h b/libunwindstack/tests/fuzz/UnwinderComponentCreator.h
new file mode 100644
index 0000000..09b3379
--- /dev/null
+++ b/libunwindstack/tests/fuzz/UnwinderComponentCreator.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef _LIBUNWINDSTACK_UNWINDERCOMPONENTCREATOR_H
+#define _LIBUNWINDSTACK_UNWINDERCOMPONENTCREATOR_H
+
+#include <elf.h>
+#include <sys/mman.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <fuzzer/FuzzedDataProvider.h>
+#include <unwindstack/DexFiles.h>
+#include <unwindstack/Maps.h>
+#include <unwindstack/Regs.h>
+#include <unwindstack/RegsArm.h>
+#include <unwindstack/RegsArm64.h>
+#include <unwindstack/RegsMips.h>
+#include <unwindstack/RegsMips64.h>
+#include <unwindstack/RegsX86.h>
+#include <unwindstack/RegsX86_64.h>
+
+#include "../ElfFake.h"
+#include "../MemoryFake.h"
+
+#include "fuzzer/FuzzedDataProvider.h"
+
+using unwindstack::ArchEnum;
+using unwindstack::DexFiles;
+using unwindstack::Elf;
+using unwindstack::ElfFake;
+using unwindstack::ElfInterfaceFake;
+using unwindstack::FunctionData;
+using unwindstack::Maps;
+using unwindstack::Memory;
+using unwindstack::MemoryFake;
+using unwindstack::Regs;
+using unwindstack::StepData;
+
+static constexpr uint8_t kArchCount = 6;
+
+static constexpr uint8_t kMaxSoNameLen = 150;
+
+static constexpr uint8_t kMaxFuncNameLen = 50;
+static constexpr uint8_t kMaxFuncCount = 100;
+
+static constexpr uint8_t kMaxJitElfFiles = 20;
+static constexpr uint8_t kJitElfPadding = 32;
+
+static constexpr uint8_t kMaxStepCount = 100;
+static constexpr uint8_t kMaxMapEntryCount = 50;
+static constexpr uint8_t kMaxBuildIdLen = 100;
+static constexpr uint8_t kMaxMapInfoNameLen = 150;
+
+std::unique_ptr<unwindstack::Regs> GetRegisters(unwindstack::ArchEnum arch);
+std::unique_ptr<unwindstack::Maps> GetMaps(FuzzedDataProvider* data_provider);
+std::vector<std::string> GetStringList(FuzzedDataProvider* data_provider, uint max_str_len,
+                                       uint max_strings);
+unwindstack::ArchEnum GetArch(FuzzedDataProvider* data_provider);
+
+void AddMapInfo(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, const char* name,
+                Elf* elf = nullptr);
+void PutElfFilesInMemory(MemoryFake* memory, FuzzedDataProvider* data_provider);
+
+std::unique_ptr<unwindstack::DexFiles> GetDexFiles(FuzzedDataProvider* data_provider,
+                                                   std::shared_ptr<unwindstack::Memory> memory,
+                                                   uint max_libraries, uint max_library_length);
+#endif  // _LIBUNWINDSTACK_UNWINDERCOMPONENTCREATOR_H
diff --git a/libunwindstack/tests/fuzz/UnwinderFuzz.cpp b/libunwindstack/tests/fuzz/UnwinderFuzz.cpp
new file mode 100644
index 0000000..2f4986a
--- /dev/null
+++ b/libunwindstack/tests/fuzz/UnwinderFuzz.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2020 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 <functional>
+#include <iostream>
+#include <vector>
+
+#include <unwindstack/JitDebug.h>
+#include <unwindstack/Maps.h>
+#include <unwindstack/Memory.h>
+#include <unwindstack/Unwinder.h>
+
+#include "../MemoryFake.h"
+#include "UnwinderComponentCreator.h"
+#include "fuzzer/FuzzedDataProvider.h"
+
+namespace unwindstack {
+
+static constexpr int kMaxUnwindStringLen = 50;
+static constexpr int kMaxUnwindStrings = 50;
+
+void PerformUnwind(FuzzedDataProvider* data_provider, Unwinder* unwinder) {
+  // 0 = don't set any values
+  // 1 = set initial_map_names_to_skip
+  // 2 = set map_suffixes_to_ignore
+  // 3 = set both
+  uint8_t set_values = data_provider->ConsumeIntegral<uint8_t>() % 4;
+  if (set_values == 0) {
+    unwinder->Unwind();
+  } else if (set_values == 1) {
+    // Only setting initial_map_names_to_skip
+    std::vector<std::string> skip_names =
+        GetStringList(data_provider, kMaxUnwindStringLen, kMaxUnwindStrings);
+
+    unwinder->Unwind(&skip_names, nullptr);
+  } else if (set_values == 2) {
+    // Only setting map_suffixes_to_ignore
+    std::vector<std::string> ignore_suffixes =
+        GetStringList(data_provider, kMaxUnwindStringLen, kMaxUnwindStrings);
+
+    unwinder->Unwind(nullptr, &ignore_suffixes);
+  } else if (set_values == 3) {
+    // Setting both values
+    std::vector<std::string> skip_names =
+        GetStringList(data_provider, kMaxUnwindStringLen, kMaxUnwindStrings);
+    std::vector<std::string> ignore_suffixes =
+        GetStringList(data_provider, kMaxUnwindStringLen, kMaxUnwindStrings);
+
+    unwinder->Unwind(&skip_names, &ignore_suffixes);
+  }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  FuzzedDataProvider data_provider(data, size);
+
+  // We need to construct an unwinder.
+  // Generate the Maps:
+  std::unique_ptr<Maps> maps = GetMaps(&data_provider);
+
+  // Generate the Regs:
+  uint8_t arch_val = data_provider.ConsumeIntegralInRange<uint8_t>(1, kArchCount);
+  ArchEnum arch = static_cast<ArchEnum>(arch_val);
+  std::unique_ptr<Regs> regs = GetRegisters(arch);
+
+  // Generate memory:
+  std::shared_ptr<Memory> memory = std::make_shared<MemoryFake>();
+  PutElfFilesInMemory(reinterpret_cast<MemoryFake*>(memory.get()), &data_provider);
+
+  size_t max_frames = data_provider.ConsumeIntegralInRange<size_t>(0, 5000);
+
+  std::unique_ptr<JitDebug> jit_debug_ptr = std::make_unique<JitDebug>(memory);
+
+  // Create instance
+  Unwinder unwinder(max_frames, maps.get(), regs.get(), memory);
+  unwinder.SetJitDebug(jit_debug_ptr.get(), arch);
+  unwinder.SetResolveNames(data_provider.ConsumeBool());
+  // Call unwind
+  PerformUnwind(&data_provider, &unwinder);
+
+  // Run some additional logic that changes after unwind
+  uint64_t pc = data_provider.ConsumeIntegral<uint64_t>();
+  unwinder.BuildFrameFromPcOnly(pc);
+  unwinder.ConsumeFrames();
+  return 0;
+}
+}  // namespace unwindstack
diff --git a/libunwindstack/tools/unwind_info.cpp b/libunwindstack/tools/unwind_info.cpp
index 7a6d8ba..a5002f2 100644
--- a/libunwindstack/tools/unwind_info.cpp
+++ b/libunwindstack/tools/unwind_info.cpp
@@ -96,7 +96,7 @@
       printf(" <%s>", name.c_str());
     }
     printf("\n");
-    if (!section->Log(2, UINT64_MAX, fde)) {
+    if (!section->Log(2, UINT64_MAX, fde, elf->arch())) {
       printf("Failed to process cfa information for entry at 0x%" PRIx64 "\n", fde->pc_start);
     }
   }
diff --git a/libunwindstack/tools/unwind_reg_info.cpp b/libunwindstack/tools/unwind_reg_info.cpp
index 0cbcac5..68e0273 100644
--- a/libunwindstack/tools/unwind_reg_info.cpp
+++ b/libunwindstack/tools/unwind_reg_info.cpp
@@ -64,7 +64,8 @@
   }
 }
 
-void PrintRegInformation(DwarfSection* section, Memory* memory, uint64_t pc, uint8_t class_type) {
+void PrintRegInformation(DwarfSection* section, Memory* memory, uint64_t pc, uint8_t class_type,
+                         ArchEnum arch) {
   const DwarfFde* fde = section->GetFdeFromPc(pc);
   if (fde == nullptr) {
     printf("  No fde found.\n");
@@ -72,7 +73,7 @@
   }
 
   dwarf_loc_regs_t regs;
-  if (!section->GetCfaLocationInfo(pc, fde, &regs)) {
+  if (!section->GetCfaLocationInfo(pc, fde, &regs, arch)) {
     printf("  Cannot get location information.\n");
     return;
   }
@@ -128,6 +129,11 @@
         break;
       }
 
+      case DWARF_LOCATION_PSEUDO_REGISTER: {
+        printf("%" PRId64 " (pseudo)\n", loc->values[0]);
+        break;
+      }
+
       case DWARF_LOCATION_UNDEFINED:
         printf("undefine\n");
         break;
@@ -199,7 +205,7 @@
   DwarfSection* section = interface->eh_frame();
   if (section != nullptr) {
     printf("\neh_frame:\n");
-    PrintRegInformation(section, elf.memory(), pc, elf.class_type());
+    PrintRegInformation(section, elf.memory(), pc, elf.class_type(), elf.arch());
   } else {
     printf("\nno eh_frame information\n");
   }
@@ -207,7 +213,7 @@
   section = interface->debug_frame();
   if (section != nullptr) {
     printf("\ndebug_frame:\n");
-    PrintRegInformation(section, elf.memory(), pc, elf.class_type());
+    PrintRegInformation(section, elf.memory(), pc, elf.class_type(), elf.arch());
     printf("\n");
   } else {
     printf("\nno debug_frame information\n");
@@ -219,7 +225,8 @@
     section = gnu_debugdata_interface->eh_frame();
     if (section != nullptr) {
       printf("\ngnu_debugdata (eh_frame):\n");
-      PrintRegInformation(section, gnu_debugdata_interface->memory(), pc, elf.class_type());
+      PrintRegInformation(section, gnu_debugdata_interface->memory(), pc, elf.class_type(),
+                          elf.arch());
       printf("\n");
     } else {
       printf("\nno gnu_debugdata (eh_frame)\n");
@@ -228,7 +235,8 @@
     section = gnu_debugdata_interface->debug_frame();
     if (section != nullptr) {
       printf("\ngnu_debugdata (debug_frame):\n");
-      PrintRegInformation(section, gnu_debugdata_interface->memory(), pc, elf.class_type());
+      PrintRegInformation(section, gnu_debugdata_interface->memory(), pc, elf.class_type(),
+                          elf.arch());
       printf("\n");
     } else {
       printf("\nno gnu_debugdata (debug_frame)\n");
diff --git a/libutils/Android.bp b/libutils/Android.bp
index ea39d34..022dedf 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -78,6 +78,9 @@
         "libcutils",
         "liblog",
     ],
+    sanitize: {
+        misc_undefined: ["integer"],
+    },
 
     target: {
         android: {
@@ -169,6 +172,8 @@
 cc_library {
     name: "libutilscallstack",
     defaults: ["libutils_defaults"],
+    // TODO(b/153609531): remove when no longer needed.
+    native_bridge_supported: true,
 
     srcs: [
         "CallStack.cpp",
@@ -243,6 +248,7 @@
         "LruCache_test.cpp",
         "Mutex_test.cpp",
         "SharedBuffer_test.cpp",
+        "Singleton_test.cpp",
         "String8_test.cpp",
         "String16_test.cpp",
         "StrongPointer_test.cpp",
@@ -279,6 +285,11 @@
         },
     },
 
+    data_libs: [
+        "libutils_test_singleton1",
+        "libutils_test_singleton2",
+    ],
+
     cflags: [
         "-Wall",
         "-Wextra",
@@ -289,29 +300,10 @@
     test_suites: ["device-tests"],
 }
 
-// TODO: the test infrastructure isn't yet capable of running this,
-// so it's broken out into its own test so that the main libutils_tests
-// can be in presubmit even if this can't.
-
-cc_test {
-    name: "libutils_singleton_test",
-    srcs: ["Singleton_test.cpp"],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-    shared_libs: ["libbase"],
-
-    required: [
-        ":libutils_test_singleton1",
-        ":libutils_test_singleton2",
-    ],
-}
-
 cc_test_library {
     name: "libutils_test_singleton1",
     host_supported: true,
-    relative_install_path: "libutils_test",
+    installable: false,
     srcs: ["Singleton_test1.cpp"],
     cflags: [
         "-Wall",
@@ -322,7 +314,7 @@
 cc_test_library {
     name: "libutils_test_singleton2",
     host_supported: true,
-    relative_install_path: "libutils_test",
+    installable: false,
     srcs: ["Singleton_test2.cpp"],
     cflags: [
         "-Wall",
diff --git a/libutils/String8.cpp b/libutils/String8.cpp
index d00e39c..c837891 100644
--- a/libutils/String8.cpp
+++ b/libutils/String8.cpp
@@ -424,7 +424,7 @@
     char* buf = lockBuffer(len);
     buf += start;
     while (length > 0) {
-        *buf = tolower(*buf);
+        *buf = static_cast<char>(tolower(*buf));
         buf++;
         length--;
     }
@@ -448,7 +448,7 @@
     char* buf = lockBuffer(len);
     buf += start;
     while (length > 0) {
-        *buf = toupper(*buf);
+        *buf = static_cast<char>(toupper(*buf));
         buf++;
         length--;
     }
diff --git a/libutils/include/utils/BitSet.h b/libutils/include/utils/BitSet.h
index 8abfb1a..0fb1a6a 100644
--- a/libutils/include/utils/BitSet.h
+++ b/libutils/include/utils/BitSet.h
@@ -21,9 +21,12 @@
 #include <utils/TypeHelpers.h>
 
 /*
- * Contains some bit manipulation helpers.
+ * A class to provide efficient manipulation of bitsets.
  *
- * DO NOT USE: std::bitset<32> or std::bitset<64> preferred
+ * Consider using std::bitset<32> or std::bitset<64> if all you want is a class to do basic bit
+ * manipulation (i.e. AND / OR / XOR / flip / etc). These classes are only needed if you want to
+ * efficiently perform operations like finding the first set bit in a bitset and you want to
+ * avoid using the built-in functions (e.g. __builtin_clz) on std::bitset::to_ulong.
  */
 
 namespace android {
@@ -46,7 +49,9 @@
     // Returns the number of marked bits in the set.
     inline uint32_t count() const { return count(value); }
 
-    static inline uint32_t count(uint32_t value) { return __builtin_popcountl(value); }
+    static inline uint32_t count(uint32_t value) {
+        return static_cast<uint32_t>(__builtin_popcountl(value));
+    }
 
     // Returns true if the bit set does not contain any marked bits.
     inline bool isEmpty() const { return isEmpty(value); }
@@ -128,7 +133,7 @@
     }
 
     static inline uint32_t getIndexOfBit(uint32_t value, uint32_t n) {
-        return __builtin_popcountl(value & ~(0xffffffffUL >> n));
+        return static_cast<uint32_t>(__builtin_popcountl(value & ~(0xffffffffUL >> n)));
     }
 
     inline bool operator== (const BitSet32& other) const { return value == other.value; }
@@ -153,17 +158,17 @@
     // input, which is only guaranteed to be 16b, not 32. The compiler should optimize this away.
     static inline uint32_t clz_checked(uint32_t value) {
         if (sizeof(unsigned int) == sizeof(uint32_t)) {
-            return __builtin_clz(value);
+            return static_cast<uint32_t>(__builtin_clz(value));
         } else {
-            return __builtin_clzl(value);
+            return static_cast<uint32_t>(__builtin_clzl(value));
         }
     }
 
     static inline uint32_t ctz_checked(uint32_t value) {
         if (sizeof(unsigned int) == sizeof(uint32_t)) {
-            return __builtin_ctz(value);
+            return static_cast<uint32_t>(__builtin_ctz(value));
         } else {
-            return __builtin_ctzl(value);
+            return static_cast<uint32_t>(__builtin_ctzl(value));
         }
     }
 };
@@ -188,7 +193,9 @@
     // Returns the number of marked bits in the set.
     inline uint32_t count() const { return count(value); }
 
-    static inline uint32_t count(uint64_t value) { return __builtin_popcountll(value); }
+    static inline uint32_t count(uint64_t value) {
+        return static_cast<uint32_t>(__builtin_popcountll(value));
+    }
 
     // Returns true if the bit set does not contain any marked bits.
     inline bool isEmpty() const { return isEmpty(value); }
@@ -219,26 +226,32 @@
     // Result is undefined if all bits are unmarked.
     inline uint32_t firstMarkedBit() const { return firstMarkedBit(value); }
 
-    static inline uint32_t firstMarkedBit(uint64_t value) { return __builtin_clzll(value); }
+    static inline uint32_t firstMarkedBit(uint64_t value) {
+        return static_cast<uint32_t>(__builtin_clzll(value));
+    }
 
     // Finds the first unmarked bit in the set.
     // Result is undefined if all bits are marked.
     inline uint32_t firstUnmarkedBit() const { return firstUnmarkedBit(value); }
 
-    static inline uint32_t firstUnmarkedBit(uint64_t value) { return __builtin_clzll(~ value); }
+    static inline uint32_t firstUnmarkedBit(uint64_t value) {
+        return static_cast<uint32_t>(__builtin_clzll(~value));
+    }
 
     // Finds the last marked bit in the set.
     // Result is undefined if all bits are unmarked.
     inline uint32_t lastMarkedBit() const { return lastMarkedBit(value); }
 
-    static inline uint32_t lastMarkedBit(uint64_t value) { return 63 - __builtin_ctzll(value); }
+    static inline uint32_t lastMarkedBit(uint64_t value) {
+        return static_cast<uint32_t>(63 - __builtin_ctzll(value));
+    }
 
     // Finds the first marked bit in the set and clears it.  Returns the bit index.
     // Result is undefined if all bits are unmarked.
     inline uint32_t clearFirstMarkedBit() { return clearFirstMarkedBit(value); }
 
     static inline uint32_t clearFirstMarkedBit(uint64_t& value) {
-        uint64_t n = firstMarkedBit(value);
+        uint32_t n = firstMarkedBit(value);
         clearBit(value, n);
         return n;
     }
@@ -248,7 +261,7 @@
     inline uint32_t markFirstUnmarkedBit() { return markFirstUnmarkedBit(value); }
 
     static inline uint32_t markFirstUnmarkedBit(uint64_t& value) {
-        uint64_t n = firstUnmarkedBit(value);
+        uint32_t n = firstUnmarkedBit(value);
         markBit(value, n);
         return n;
     }
@@ -258,7 +271,7 @@
     inline uint32_t clearLastMarkedBit() { return clearLastMarkedBit(value); }
 
     static inline uint32_t clearLastMarkedBit(uint64_t& value) {
-        uint64_t n = lastMarkedBit(value);
+        uint32_t n = lastMarkedBit(value);
         clearBit(value, n);
         return n;
     }
@@ -268,7 +281,7 @@
     inline uint32_t getIndexOfBit(uint32_t n) const { return getIndexOfBit(value, n); }
 
     static inline uint32_t getIndexOfBit(uint64_t value, uint32_t n) {
-        return __builtin_popcountll(value & ~(0xffffffffffffffffULL >> n));
+        return static_cast<uint32_t>(__builtin_popcountll(value & ~(0xffffffffffffffffULL >> n)));
     }
 
     inline bool operator== (const BitSet64& other) const { return value == other.value; }
diff --git a/llkd/README.md b/llkd/README.md
index 191f988..6f92f14 100644
--- a/llkd/README.md
+++ b/llkd/README.md
@@ -1,199 +1,237 @@
-Android Live-LocK Daemon
-========================
+<!--
+Project: /_project.yaml
+Book: /_book.yaml
 
-Introduction
-------------
+{% include "_versions.html" %}
+-->
 
-Android Live-LocK Daemon (llkd) is used to catch kernel deadlocks and mitigate.
+<!--
+  Copyright 2020 The Android Open Source Project
 
-Code is structured to allow integration into another service as either as part
-of the main loop, or spun off as a thread should that be necessary.  A default
-standalone implementation is provided by llkd component.
+  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
 
-The 'C' interface from libllkd component is thus:
+      http://www.apache.org/licenses/LICENSE-2.0
 
-    #include "llkd.h"
-    bool llkInit(const char* threadname) /* return true if enabled */
-    unsigned llkCheckMillseconds(void)   /* ms to sleep for next check */
+  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.
+-->
 
-If a threadname is provided, a thread will be automatically spawned, otherwise
-caller must call llkCheckMilliseconds in its main loop.  Function will return
-the period of time before the next expected call to this handler.
+# Android Live-LocK Daemon (llkd)
 
-Operations
-----------
+Android 10 <!-- {{ androidQVersionNumber }} --> includes the Android Live-LocK Daemon
+(`llkd`), which is designed to catch and mitigate kernel deadlocks. The `llkd`
+component provides a default standalone implementation, but you can
+alternatively integrate the `llkd` code into another service, either as part of
+the main loop or as a separate thread.
 
-There are two detection scenarios. Persistent D or Z state, and persistent
+## Detection scenarios <!-- {:#detection-scenarios} -->
+
+The `llkd` has two detection scenarios: Persistent D or Z state, and persistent
 stack signature.
 
-If a thread is in D or Z state with no forward progress for longer than
-ro.llk.timeout_ms, or ro.llk.[D|Z].timeout_ms, kill the process or parent
-process respectively.  If another scan shows the same process continues to
-exist, then have a confirmed live-lock condition and need to panic.  Panic
-the kernel in a manner to provide the greatest bugreporting details as to the
-condition.  Add a alarm self watchdog should llkd ever get locked up that is
-double the expected time to flow through the mainloop.  Sampling is every
-ro.llk_sample_ms.
+### Persistent D or Z state <!-- {:#persistent-d-or-z-state} -->
 
-For usedebug releases only, persistent stack signature checking is enabled.
-If a thread in any state but Z, has a persistent listed ro.llk.stack kernel
-symbol always being reported, even if there is forward scheduling progress, for
-longer than ro.llk.timeout_ms, or ro.llk.stack.timeout_ms, then issue a kill
-to the process.  If another scan shows the same process continues to exist,
-then have a confirmed live-lock condition and need to panic.  There is no
-ABA detection since forward scheduling progress is allowed, thus the condition
-for the symbols are:
+If a thread is in D (uninterruptible sleep) or Z (zombie) state with no forward
+progress for longer than `ro.llk.timeout_ms or ro.llk.[D|Z].timeout_ms`, the
+`llkd` kills the process (or parent process). If a subsequent scan shows the
+same process continues to exist, the `llkd` confirms a live-lock condition and
+panics the kernel in a manner that provides the most detailed bug report for the
+condition.
 
-- Check is looking for " __symbol__+0x" or " __symbol__.cfi+0x" in
-  /proc/__pid__/stack.
-- The __symbol__ should be rare and short lived enough that on a typical
-  system the function is seen at most only once in a sample over the timeout
-  period of ro.llk.stack.timeout_ms, samples occur every ro.llk.check_ms. This
-  can be the only way to prevent a false trigger as there is no ABA protection.
-- Persistent continuously when the live lock condition exists.
-- Should be just below the function that is calling the lock that could
-  contend, because if the lock is below or in the symbol function, the
-  symbol will show in all affected processes, not just the one that
-  caused the lockup.
+The `llkd` includes a self watchdog that alarms if `llkd` locks up; watchdog is
+double the expected time to flow through the mainloop and sampling is every
+`ro.llk_sample_ms`.
 
-Default will not monitor init, or [kthreadd] and all that [kthreadd] spawns.
-This reduces the effectiveness of llkd by limiting its coverage.  If there is
-value in covering [kthreadd] spawned threads, the requirement will be that
-the drivers not remain in a persistent 'D' state, or that they have mechanisms
-to recover the thread should it be killed externally (this is good driver
-coding hygiene, a common request to add such to publicly reviewed kernel.org
-maintained drivers).  For instance use wait_event_interruptible() instead of
-wait_event().  The blacklists can be adjusted accordingly if these
-conditions are met to cover kernel components.  For the stack symbol checking,
-there is an additional process blacklist so that we do not incide sepolicy
-violations on services that block ptrace operations.
+### Persistent stack signature <!-- {:#persistent-stack-signature} -->
 
-An accompanying gTest set have been added, and will setup a persistent D or Z
-process, with and without forward progress, but not in a live-lock state
-because that would require a buggy kernel, or a module or kernel modification
-to stimulate.  The test will check that llkd will mitigate first by killing
-the appropriate process.  D state is setup by vfork() waiting for exec() in
-child process.  Z state is setup by fork() and an un-waited for child process.
-Should be noted that both of these conditions should never happen on Android
-on purpose, and llkd effectively sweeps up processes that create these
-conditions.  If the test can, it will reconfigure llkd to expedite the test
-duration by adjusting the ro.llk.* Android properties.  Tests run the D state
-with some scheduling progress to ensure that ABA checking prevents false
-triggers. If 100% reliable ABA on platform, then ro.llk.killtest can be
-set to false; however this will result in some of the unit tests to panic
-kernel instead of deal with more graceful kill operation.
+For userdebug releases, the `llkd` can detect kernel live-locks using persistent
+stack signature checking. If a thread in any state except Z has a persistent
+listed `ro.llk.stack` kernel symbol that is reported for longer than
+`ro.llk.timeout_ms` or `ro.llk.stack.timeout_ms`, the `llkd` kills the process
+(even if there is forward scheduling progress). If a subsequent scan shows the
+same process continues to exist, the `llkd` confirms a live-lock condition and
+panics the kernel in a manner that provides the most detailed bug report for the
+condition.
 
-Android Properties
-------------------
+Note: Because forward scheduling progress is allowed, the `llkd` does not
+perform [ABA detection](https://en.wikipedia.org/wiki/ABA_problem){:.external}.
 
-The following are the Android Properties llkd respond to.
-*prop*_ms named properties are in milliseconds.
-Properties that use comma (*,*) separator for lists, use a leading separator to
-preserve default and add or subtract entries with (*optional*) plus (*+*) and
-minus (*-*) prefixes respectively.
-For these lists, the string "*false*" is synonymous with an *empty* list,
-and *blank* or *missing* resorts to the specified *default* value.
+The `lldk` check persists continuously when the live lock condition exists and
+looks for the composed strings `" symbol+0x"` or `" symbol.cfi+0x"` in the
+`/proc/pid/stack` file on Linux. The list of symbols is in `ro.llk.stack` and
+defaults to the comma-separated list of
+"`cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable`".
 
-#### ro.config.low_ram
-device is configured with limited memory.
+Symbols should be rare and short-lived enough that on a typical system the
+function is seen only once in a sample over the timeout period of
+`ro.llk.stack.timeout_ms` (samples occur every `ro.llk.check_ms`). Due to lack
+of ABA protection, this is the only way to prevent a false trigger. The symbol
+function must appear below the function calling the lock that could contend. If
+the lock is below or in the symbol function, the symbol appears in all affected
+processes, not just the one that caused the lockup.
 
-#### ro.debuggable
-device is configured for userdebug or eng build.
+## Coverage <!-- {:#coverage} -->
 
-#### ro.llk.sysrq_t
-default not ro.config.low_ram, or ro.debuggable if property is "eng".
-if true do sysrq t (dump all threads).
+The default implementation of `llkd` does not monitor `init`, `[kthreadd]`, or
+`[kthreadd]` spawns. For the `llkd` to cover `[kthreadd]`-spawned threads:
 
-#### ro.llk.enable
-default false, allow live-lock daemon to be enabled.
+* Drivers must not remain in a persistent D state,
 
-#### llk.enable
-default ro.llk.enable, and evaluated for eng.
+OR
 
-#### ro.khungtask.enable
-default false, allow [khungtask] daemon to be enabled.
+* Drivers must have mechanisms to recover the thread should it be killed
+  externally. For example, use `wait_event_interruptible()` instead of
+  `wait_event()`.
 
-#### khungtask.enable
-default ro.khungtask.enable and evaluated for eng.
+If one of the above conditions is met, the `llkd` ignorelist can be adjusted to
+cover kernel components.  Stack symbol checking involves an additional process
+ignore list to prevent sepolicy violations on services that block `ptrace`
+operations.
 
-#### ro.llk.mlockall
-default false, enable call to mlockall().
+## Android properties <!-- {:#android-properties} -->
 
-#### ro.khungtask.timeout
-default value 12 minutes, [khungtask] maximum timelimit.
+The `llkd` responds to several Android properties (listed below).
 
-#### ro.llk.timeout_ms
-default 10 minutes, D or Z maximum timelimit, double this value and it sets
-the alarm watchdog for llkd.
+* Properties named `prop_ms` are in milliseconds.
+* Properties that use comma (,) separator for lists use a leading separator to
+  preserve the default entry, then add or subtract entries with optional plus
+  (+) and minus (-) prefixes respectively. For these lists, the string "false"
+  is synonymous with an empty list, and blank or missing entries resort to the
+  specified default value.
 
-#### ro.llk.D.timeout_ms
-default ro.llk.timeout_ms, D maximum timelimit.
+### ro.config.low_ram <!-- {:#ro-config-low-ram} -->
 
-#### ro.llk.Z.timeout_ms
-default ro.llk.timeout_ms, Z maximum timelimit.
+Device is configured with limited memory.
 
-#### ro.llk.stack.timeout_ms
-default ro.llk.timeout_ms,
-checking for persistent stack symbols maximum timelimit.
-Only active on userdebug or eng builds.
+### ro.debuggable <!-- {:#ro-debuggable} -->
 
-#### ro.llk.check_ms
-default 2 minutes samples of threads for D or Z.
+Device is configured for userdebug or eng build.
 
-#### ro.llk.stack
-default cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable
-comma separated list of kernel symbols.
-Look for kernel stack symbols that if ever persistently present can
-indicate a subsystem is locked up.
-Beware, check does not on purpose do forward scheduling ABA except by polling
-every ro.llk_check_ms over the period ro.llk.stack.timeout_ms, so stack symbol
-should be exceptionally rare and fleeting.
-One must be convinced that it is virtually *impossible* for symbol to show up
-persistently in all samples of the stack.
-Again, looks for a match for either " **symbol**+0x" or " **symbol**.cfi+0x"
-in stack expansion.
-Only available on userdebug or eng builds, limited privileges due to security
-concerns on user builds prevents this checking.
+### ro.llk.sysrq_t <!-- {:#ro-llk-sysrq-t} -->
 
-#### ro.llk.blacklist.process
-default 0,1,2 (kernel, init and [kthreadd]) plus process names
-init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd,
-[watchdogd],[watchdogd/0],...,[watchdogd/***get_nprocs**-1*].
-Do not watch these processes.  A process can be comm, cmdline or pid reference.
-NB: automated default here can be larger than the current maximum property
-size of 92.
-NB: false is a very very very unlikely process to want to blacklist.
+If property is "eng", the default is not `ro.config.low_ram` or `ro.debuggable`.
+If true, dump all threads (`sysrq t`).
 
-#### ro.llk.blacklist.parent
-default 0,2,adbd&[setsid] (kernel, [kthreadd] and adbd *only for zombie setsid*).
-Do not watch processes that have this parent.
-An ampersand (*&*) separator is used to specify that the parent is ignored
-only in combination with the target child process.
-Ampersand was selected because it is never part of a process name,
-however a setprop in the shell requires it to be escaped or quoted;
-init rc file where this is normally specified does not have this issue.
-A parent or target processes can be specified as comm, cmdline or pid reference.
+### ro.llk.enable <!-- {:#ro-llk-enable} -->
 
-#### ro.llk.blacklist.uid
-default *empty* or false, comma separated list of uid numbers or names.
-Do not watch processes that match this uid.
+Allow live-lock daemon to be enabled. Default is false.
 
-#### ro.llk.blacklist.process.stack
-default process names init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd.
-This subset of processes are not monitored for live lock stack signatures.
-Also prevents the sepolicy violation associated with processes that block
-ptrace, as these can not be checked anyways.
-Only active on userdebug and eng builds.
+### llk.enable <!-- {:#llk-enable} -->
 
-Architectural Concerns
-----------------------
+Evaluated for eng builds. Default is `ro.llk.enable`.
 
-- built-in [khungtask] daemon is too generic and trips on driver code that
-  sits around in D state too much.  To switch to S instead makes the task(s)
-  killable, so the drivers should be able to resurrect them if needed.
-- Properties are limited to 92 characters.
-- Create kernel module and associated gTest to actually test panic.
-- Create gTest to test out blacklist (ro.llk.blacklist.*properties* generally
-  not be inputs).  Could require more test-only interfaces to libllkd.
-- Speed up gTest using something else than ro.llk.*properties*, which should
-  not be inputs as they should be baked into the product.
+### ro.khungtask.enable <!-- {:#ro-khungtask-enable} -->
+
+Allow `[khungtask]` daemon to be enabled. Default is false.
+
+### khungtask.enable <!-- {:#khungtask-enable} -->
+
+Evaluated for eng builds. Default is `ro.khungtask.enable`.
+
+### ro.llk.mlockall <!-- {:#ro-llk-mlockall} -->
+
+Enable call to `mlockall()`. Default is false.
+
+### ro.khungtask.timeout <!-- {:#ro-khungtask-timeout} -->
+
+`[khungtask]` maximum time limit. Default is 12 minutes.
+
+### ro.llk.timeout_ms <!-- {:#ro-llk-timeout-ms} -->
+
+D or Z maximum time limit. Default is 10 minutes. Double this value to set the
+alarm watchdog for `llkd`.
+
+### ro.llk.D.timeout_ms <!-- {:#ro-llk-D-timeout-ms} -->
+
+D maximum time limit. Default is `ro.llk.timeout_ms`.
+
+### ro.llk.Z.timeout_ms <!-- {:#ro-llk-Z-timeout-ms} -->
+
+Z maximum time limit. Default is `ro.llk.timeout_ms`.
+
+### ro.llk.stack.timeout_ms <!-- {:#ro-llk-stack-timeout-ms} -->
+
+Checks for persistent stack symbols maximum time limit. Default is
+`ro.llk.timeout_ms`. **Active only on userdebug or eng builds**.
+
+### ro.llk.check_ms <!-- {:#ro-llk-check-ms} -->
+
+Samples of threads for D or Z. Default is two minutes.
+
+### ro.llk.stack <!-- {:#ro-llk-stack} -->
+
+Checks for kernel stack symbols that if persistently present can indicate a
+subsystem is locked up. Default is
+`cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable`
+comma-separated list of kernel symbols. The check doesn't do forward scheduling
+ABA except by polling every `ro.llk_check_ms` over the period
+`ro.llk.stack.timeout_ms`, so stack symbols should be exceptionally rare and
+fleeting (it is highly unlikely for a symbol to show up persistently in all
+samples of the stack). Checks for a match for `" symbol+0x"` or
+`" symbol.cfi+0x"` in stack expansion. **Available only on userdebug or eng
+builds**; security concerns on user builds result in limited privileges that
+prevent this check.
+
+### ro.llk.ignorelist.process <!-- {:#ro-llk-ignorelist-process} -->
+
+The `llkd` does not watch the specified processes. Default is `0,1,2` (`kernel`,
+`init`, and `[kthreadd]`) plus process names
+`init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd, [watchdogd],[watchdogd/0],...,[watchdogd/get_nprocs-1]`.
+A process can be a `comm`, `cmdline`, or `pid` reference. An automated default
+can be larger than the current maximum property size of 92.
+
+Note: `false` is an extremely unlikely process to want to ignore.
+
+### ro.llk.ignorelist.parent <!-- {:#ro-llk-ignorelist-parent} -->
+
+The `llkd` does not watch processes that have the specified parent(s). Default
+is `0,2,adbd&[setsid]` (`kernel`, `[kthreadd]`, and `adbd` only for zombie
+`setsid`). An ampersand (&) separator specifies that the parent is ignored only
+in combination with the target child process. Ampersand was selected because it
+is never part of a process name; however, a `setprop` in the shell requires the
+ampersand to be escaped or quoted, although the `init rc` file where this is
+normally specified does not have this issue. A parent or target process can be a
+`comm`, `cmdline`, or `pid` reference.
+
+### ro.llk.ignorelist.uid <!-- {:#ro-llk-ignorelist-uid} -->
+
+The `llkd` does not watch processes that match the specified uid(s).
+Comma-separated list of uid numbers or names. Default is empty or false.
+
+### ro.llk.ignorelist.process.stack <!-- {:#ro-llk-ignorelist-process-stack} -->
+
+The `llkd` does not monitor the specified subset of processes for live lock stack
+signatures. Default is process names
+`init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd`. Prevents the sepolicy
+violation associated with processes that block `ptrace` (as these can't be
+checked). **Active only on userdebug and eng builds**. For details on build
+types, refer to [Building Android](/setup/build/building#choose-a-target).
+
+## Architectural concerns <!-- {:#architectural-concerns} -->
+
+* Properties are limited to 92 characters.  However, this is not limited for
+  defaults defined in the `include/llkd.h` file in the sources.
+* The built-in `[khungtask]` daemon is too generic and trips on driver code that
+  sits around in D state too much. Switching drivers to sleep, or S state,
+  would make task(s) killable, and need to be resurrectable by drivers on an
+  as-need basis.
+
+## Library interface (optional) <!-- {:#library-interface-optional} -->
+
+You can optionally incorporate the `llkd` into another privileged daemon using
+the following C interface from the `libllkd` component:
+
+```
+#include "llkd.h"
+bool llkInit(const char* threadname) /* return true if enabled */
+unsigned llkCheckMillseconds(void)   /* ms to sleep for next check */
+```
+
+If a threadname is provided, a thread automatically spawns, otherwise the caller
+must call `llkCheckMilliseconds` in its main loop. The function returns the
+period of time before the next expected call to this handler.
diff --git a/llkd/include/llkd.h b/llkd/include/llkd.h
index 3586ca1..4b20a56 100644
--- a/llkd/include/llkd.h
+++ b/llkd/include/llkd.h
@@ -30,37 +30,37 @@
 unsigned llkCheckMilliseconds(void);
 
 /* clang-format off */
-#define LLK_ENABLE_WRITEABLE_PROPERTY  "llk.enable"
-#define LLK_ENABLE_PROPERTY            "ro." LLK_ENABLE_WRITEABLE_PROPERTY
-#define LLK_ENABLE_DEFAULT             false /* "eng" and userdebug true */
-#define KHT_ENABLE_WRITEABLE_PROPERTY  "khungtask.enable"
-#define KHT_ENABLE_PROPERTY            "ro." KHT_ENABLE_WRITEABLE_PROPERTY
-#define LLK_ENABLE_SYSRQ_T_PROPERTY    "ro.llk.sysrq_t"
-#define LLK_ENABLE_SYSRQ_T_DEFAULT     true
-#define LLK_MLOCKALL_PROPERTY          "ro.llk.mlockall"
-#define LLK_MLOCKALL_DEFAULT           true
-#define LLK_KILLTEST_PROPERTY          "ro.llk.killtest"
-#define LLK_KILLTEST_DEFAULT           true
-#define LLK_TIMEOUT_MS_PROPERTY        "ro.llk.timeout_ms"
-#define KHT_TIMEOUT_PROPERTY           "ro.khungtask.timeout"
-#define LLK_D_TIMEOUT_MS_PROPERTY      "ro.llk.D.timeout_ms"
-#define LLK_Z_TIMEOUT_MS_PROPERTY      "ro.llk.Z.timeout_ms"
-#define LLK_STACK_TIMEOUT_MS_PROPERTY  "ro.llk.stack.timeout_ms"
-#define LLK_CHECK_MS_PROPERTY          "ro.llk.check_ms"
+#define LLK_ENABLE_WRITEABLE_PROPERTY   "llk.enable"
+#define LLK_ENABLE_PROPERTY             "ro." LLK_ENABLE_WRITEABLE_PROPERTY
+#define LLK_ENABLE_DEFAULT              false /* "eng" and userdebug true */
+#define KHT_ENABLE_WRITEABLE_PROPERTY   "khungtask.enable"
+#define KHT_ENABLE_PROPERTY             "ro." KHT_ENABLE_WRITEABLE_PROPERTY
+#define LLK_ENABLE_SYSRQ_T_PROPERTY     "ro.llk.sysrq_t"
+#define LLK_ENABLE_SYSRQ_T_DEFAULT      true
+#define LLK_MLOCKALL_PROPERTY           "ro.llk.mlockall"
+#define LLK_MLOCKALL_DEFAULT            true
+#define LLK_KILLTEST_PROPERTY           "ro.llk.killtest"
+#define LLK_KILLTEST_DEFAULT            true
+#define LLK_TIMEOUT_MS_PROPERTY         "ro.llk.timeout_ms"
+#define KHT_TIMEOUT_PROPERTY            "ro.khungtask.timeout"
+#define LLK_D_TIMEOUT_MS_PROPERTY       "ro.llk.D.timeout_ms"
+#define LLK_Z_TIMEOUT_MS_PROPERTY       "ro.llk.Z.timeout_ms"
+#define LLK_STACK_TIMEOUT_MS_PROPERTY   "ro.llk.stack.timeout_ms"
+#define LLK_CHECK_MS_PROPERTY           "ro.llk.check_ms"
 /* LLK_CHECK_MS_DEFAULT = actual timeout_ms / LLK_CHECKS_PER_TIMEOUT_DEFAULT */
-#define LLK_CHECKS_PER_TIMEOUT_DEFAULT 5
-#define LLK_CHECK_STACK_PROPERTY       "ro.llk.stack"
-#define LLK_CHECK_STACK_DEFAULT        \
+#define LLK_CHECKS_PER_TIMEOUT_DEFAULT  5
+#define LLK_CHECK_STACK_PROPERTY        "ro.llk.stack"
+#define LLK_CHECK_STACK_DEFAULT         \
     "cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable"
-#define LLK_BLACKLIST_PROCESS_PROPERTY "ro.llk.blacklist.process"
-#define LLK_BLACKLIST_PROCESS_DEFAULT  \
+#define LLK_IGNORELIST_PROCESS_PROPERTY "ro.llk.ignorelist.process"
+#define LLK_IGNORELIST_PROCESS_DEFAULT  \
     "0,1,2,init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd,[watchdogd],[watchdogd/0]"
-#define LLK_BLACKLIST_PARENT_PROPERTY  "ro.llk.blacklist.parent"
-#define LLK_BLACKLIST_PARENT_DEFAULT   "0,2,[kthreadd],adbd&[setsid]"
-#define LLK_BLACKLIST_UID_PROPERTY     "ro.llk.blacklist.uid"
-#define LLK_BLACKLIST_UID_DEFAULT      ""
-#define LLK_BLACKLIST_STACK_PROPERTY   "ro.llk.blacklist.process.stack"
-#define LLK_BLACKLIST_STACK_DEFAULT    "init,lmkd.llkd,llkd,keystore,ueventd,apexd"
+#define LLK_IGNORELIST_PARENT_PROPERTY  "ro.llk.ignorelist.parent"
+#define LLK_IGNORELIST_PARENT_DEFAULT   "0,2,[kthreadd],adbd&[setsid]"
+#define LLK_IGNORELIST_UID_PROPERTY     "ro.llk.ignorelist.uid"
+#define LLK_IGNORELIST_UID_DEFAULT      ""
+#define LLK_IGNORELIST_STACK_PROPERTY   "ro.llk.ignorelist.process.stack"
+#define LLK_IGNORELIST_STACK_DEFAULT    "init,lmkd.llkd,llkd,keystore,ueventd,apexd"
 /* clang-format on */
 
 __END_DECLS
diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp
index 8ad9900..a24d900 100644
--- a/llkd/libllkd.cpp
+++ b/llkd/libllkd.cpp
@@ -98,26 +98,26 @@
 std::unordered_set<std::string> llkCheckStackSymbols;
 #endif
 
-// Blacklist variables, initialized with comma separated lists of high false
+// Ignorelist variables, initialized with comma separated lists of high false
 // positive and/or dangerous references, e.g. without self restart, for pid,
 // ppid, name and uid:
 
 // list of pids, or tids or names to skip. kernel pid (0), init pid (1),
 // [kthreadd] pid (2), ourselves, "init", "[kthreadd]", "lmkd", "llkd" or
 // combinations of watchdogd in kernel and user space.
-std::unordered_set<std::string> llkBlacklistProcess;
+std::unordered_set<std::string> llkIgnorelistProcess;
 // list of parent pids, comm or cmdline names to skip. default:
 // kernel pid (0), [kthreadd] (2), or ourselves, enforced and implied
-std::unordered_set<std::string> llkBlacklistParent;
+std::unordered_set<std::string> llkIgnorelistParent;
 // list of parent and target processes to skip. default:
 // adbd *and* [setsid]
-std::unordered_map<std::string, std::unordered_set<std::string>> llkBlacklistParentAndChild;
+std::unordered_map<std::string, std::unordered_set<std::string>> llkIgnorelistParentAndChild;
 // list of uids, and uid names, to skip, default nothing
-std::unordered_set<std::string> llkBlacklistUid;
+std::unordered_set<std::string> llkIgnorelistUid;
 #ifdef __PTRACE_ENABLED__
 // list of names to skip stack checking. "init", "lmkd", "llkd", "keystore" or
 // "logd" (if not userdebug).
-std::unordered_set<std::string> llkBlacklistStack;
+std::unordered_set<std::string> llkIgnorelistStack;
 #endif
 
 class dir {
@@ -626,9 +626,9 @@
     return flag ? "true" : "false";
 }
 
-std::string llkFormat(const std::unordered_set<std::string>& blacklist) {
+std::string llkFormat(const std::unordered_set<std::string>& ignorelist) {
     std::string ret;
-    for (const auto& entry : blacklist) {
+    for (const auto& entry : ignorelist) {
         if (!ret.empty()) ret += ",";
         ret += entry;
     }
@@ -636,10 +636,10 @@
 }
 
 std::string llkFormat(
-        const std::unordered_map<std::string, std::unordered_set<std::string>>& blacklist,
+        const std::unordered_map<std::string, std::unordered_set<std::string>>& ignorelist,
         bool leading_comma = false) {
     std::string ret;
-    for (const auto& entry : blacklist) {
+    for (const auto& entry : ignorelist) {
         for (const auto& target : entry.second) {
             if (leading_comma || !ret.empty()) ret += ",";
             ret += entry.first + "&" + target;
@@ -699,61 +699,61 @@
 }
 
 bool llkSkipName(const std::string& name,
-                 const std::unordered_set<std::string>& blacklist = llkBlacklistProcess) {
-    if (name.empty() || blacklist.empty()) return false;
+                 const std::unordered_set<std::string>& ignorelist = llkIgnorelistProcess) {
+    if (name.empty() || ignorelist.empty()) return false;
 
-    return blacklist.find(name) != blacklist.end();
+    return ignorelist.find(name) != ignorelist.end();
 }
 
 bool llkSkipProc(proc* procp,
-                 const std::unordered_set<std::string>& blacklist = llkBlacklistProcess) {
+                 const std::unordered_set<std::string>& ignorelist = llkIgnorelistProcess) {
     if (!procp) return false;
-    if (llkSkipName(std::to_string(procp->pid), blacklist)) return true;
-    if (llkSkipName(procp->getComm(), blacklist)) return true;
-    if (llkSkipName(procp->getCmdline(), blacklist)) return true;
-    if (llkSkipName(android::base::Basename(procp->getCmdline()), blacklist)) return true;
+    if (llkSkipName(std::to_string(procp->pid), ignorelist)) return true;
+    if (llkSkipName(procp->getComm(), ignorelist)) return true;
+    if (llkSkipName(procp->getCmdline(), ignorelist)) return true;
+    if (llkSkipName(android::base::Basename(procp->getCmdline()), ignorelist)) return true;
     return false;
 }
 
 const std::unordered_set<std::string>& llkSkipName(
         const std::string& name,
-        const std::unordered_map<std::string, std::unordered_set<std::string>>& blacklist) {
+        const std::unordered_map<std::string, std::unordered_set<std::string>>& ignorelist) {
     static const std::unordered_set<std::string> empty;
-    if (name.empty() || blacklist.empty()) return empty;
-    auto found = blacklist.find(name);
-    if (found == blacklist.end()) return empty;
+    if (name.empty() || ignorelist.empty()) return empty;
+    auto found = ignorelist.find(name);
+    if (found == ignorelist.end()) return empty;
     return found->second;
 }
 
 bool llkSkipPproc(proc* pprocp, proc* procp,
                   const std::unordered_map<std::string, std::unordered_set<std::string>>&
-                          blacklist = llkBlacklistParentAndChild) {
-    if (!pprocp || !procp || blacklist.empty()) return false;
-    if (llkSkipProc(procp, llkSkipName(std::to_string(pprocp->pid), blacklist))) return true;
-    if (llkSkipProc(procp, llkSkipName(pprocp->getComm(), blacklist))) return true;
-    if (llkSkipProc(procp, llkSkipName(pprocp->getCmdline(), blacklist))) return true;
+                          ignorelist = llkIgnorelistParentAndChild) {
+    if (!pprocp || !procp || ignorelist.empty()) return false;
+    if (llkSkipProc(procp, llkSkipName(std::to_string(pprocp->pid), ignorelist))) return true;
+    if (llkSkipProc(procp, llkSkipName(pprocp->getComm(), ignorelist))) return true;
+    if (llkSkipProc(procp, llkSkipName(pprocp->getCmdline(), ignorelist))) return true;
     return llkSkipProc(procp,
-                       llkSkipName(android::base::Basename(pprocp->getCmdline()), blacklist));
+                       llkSkipName(android::base::Basename(pprocp->getCmdline()), ignorelist));
 }
 
 bool llkSkipPid(pid_t pid) {
-    return llkSkipName(std::to_string(pid), llkBlacklistProcess);
+    return llkSkipName(std::to_string(pid), llkIgnorelistProcess);
 }
 
 bool llkSkipPpid(pid_t ppid) {
-    return llkSkipName(std::to_string(ppid), llkBlacklistParent);
+    return llkSkipName(std::to_string(ppid), llkIgnorelistParent);
 }
 
 bool llkSkipUid(uid_t uid) {
     // Match by number?
-    if (llkSkipName(std::to_string(uid), llkBlacklistUid)) {
+    if (llkSkipName(std::to_string(uid), llkIgnorelistUid)) {
         return true;
     }
 
     // Match by name?
     auto pwd = ::getpwuid(uid);
     return (pwd != nullptr) && __predict_true(pwd->pw_name != nullptr) &&
-           __predict_true(pwd->pw_name[0] != '\0') && llkSkipName(pwd->pw_name, llkBlacklistUid);
+           __predict_true(pwd->pw_name[0] != '\0') && llkSkipName(pwd->pw_name, llkIgnorelistUid);
 }
 
 bool getValidTidDir(dirent* dp, std::string* piddir) {
@@ -811,7 +811,7 @@
     }
 
     // Don't check process that are known to block ptrace, save sepolicy noise.
-    if (llkSkipProc(procp, llkBlacklistStack)) return false;
+    if (llkSkipProc(procp, llkIgnorelistStack)) return false;
     auto kernel_stack = ReadFile(piddir + "/stack");
     if (kernel_stack.empty()) {
         LOG(VERBOSE) << piddir << "/stack empty comm=" << procp->getComm()
@@ -917,12 +917,12 @@
               << LLK_CHECK_MS_PROPERTY "=" << llkFormat(llkCheckMs) << "\n"
 #ifdef __PTRACE_ENABLED__
               << LLK_CHECK_STACK_PROPERTY "=" << llkFormat(llkCheckStackSymbols) << "\n"
-              << LLK_BLACKLIST_STACK_PROPERTY "=" << llkFormat(llkBlacklistStack) << "\n"
+              << LLK_IGNORELIST_STACK_PROPERTY "=" << llkFormat(llkIgnorelistStack) << "\n"
 #endif
-              << LLK_BLACKLIST_PROCESS_PROPERTY "=" << llkFormat(llkBlacklistProcess) << "\n"
-              << LLK_BLACKLIST_PARENT_PROPERTY "=" << llkFormat(llkBlacklistParent)
-              << llkFormat(llkBlacklistParentAndChild, true) << "\n"
-              << LLK_BLACKLIST_UID_PROPERTY "=" << llkFormat(llkBlacklistUid);
+              << LLK_IGNORELIST_PROCESS_PROPERTY "=" << llkFormat(llkIgnorelistProcess) << "\n"
+              << LLK_IGNORELIST_PARENT_PROPERTY "=" << llkFormat(llkIgnorelistParent)
+              << llkFormat(llkIgnorelistParentAndChild, true) << "\n"
+              << LLK_IGNORELIST_UID_PROPERTY "=" << llkFormat(llkIgnorelistUid);
 }
 
 void* llkThread(void* obj) {
@@ -932,14 +932,14 @@
 
     std::string name = std::to_string(::gettid());
     if (!llkSkipName(name)) {
-        llkBlacklistProcess.emplace(name);
+        llkIgnorelistProcess.emplace(name);
     }
     name = static_cast<const char*>(obj);
     prctl(PR_SET_NAME, name.c_str());
     if (__predict_false(!llkSkipName(name))) {
-        llkBlacklistProcess.insert(name);
+        llkIgnorelistProcess.insert(name);
     }
-    // No longer modifying llkBlacklistProcess.
+    // No longer modifying llkIgnorelistProcess.
     llkRunning = true;
     llkLogConfig();
     while (llkRunning) {
@@ -1122,12 +1122,12 @@
             }
             if (pprocp) {
                 if (llkSkipPproc(pprocp, procp)) break;
-                if (llkSkipProc(pprocp, llkBlacklistParent)) break;
+                if (llkSkipProc(pprocp, llkIgnorelistParent)) break;
             } else {
-                if (llkSkipName(std::to_string(ppid), llkBlacklistParent)) break;
+                if (llkSkipName(std::to_string(ppid), llkIgnorelistParent)) break;
             }
 
-            if ((llkBlacklistUid.size() != 0) && llkSkipUid(procp->getUid())) {
+            if ((llkIgnorelistUid.size() != 0) && llkSkipUid(procp->getUid())) {
                 continue;
             }
 
@@ -1320,29 +1320,29 @@
     if (debuggable) {
         llkCheckStackSymbols = llkSplit(LLK_CHECK_STACK_PROPERTY, LLK_CHECK_STACK_DEFAULT);
     }
-    std::string defaultBlacklistStack(LLK_BLACKLIST_STACK_DEFAULT);
-    if (!debuggable) defaultBlacklistStack += ",logd,/system/bin/logd";
-    llkBlacklistStack = llkSplit(LLK_BLACKLIST_STACK_PROPERTY, defaultBlacklistStack);
+    std::string defaultIgnorelistStack(LLK_IGNORELIST_STACK_DEFAULT);
+    if (!debuggable) defaultIgnorelistStack += ",logd,/system/bin/logd";
+    llkIgnorelistStack = llkSplit(LLK_IGNORELIST_STACK_PROPERTY, defaultIgnorelistStack);
 #endif
-    std::string defaultBlacklistProcess(
-        std::to_string(kernelPid) + "," + std::to_string(initPid) + "," +
-        std::to_string(kthreaddPid) + "," + std::to_string(::getpid()) + "," +
-        std::to_string(::gettid()) + "," LLK_BLACKLIST_PROCESS_DEFAULT);
+    std::string defaultIgnorelistProcess(
+            std::to_string(kernelPid) + "," + std::to_string(initPid) + "," +
+            std::to_string(kthreaddPid) + "," + std::to_string(::getpid()) + "," +
+            std::to_string(::gettid()) + "," LLK_IGNORELIST_PROCESS_DEFAULT);
     if (threadname) {
-        defaultBlacklistProcess += ","s + threadname;
+        defaultIgnorelistProcess += ","s + threadname;
     }
     for (int cpu = 1; cpu < get_nprocs_conf(); ++cpu) {
-        defaultBlacklistProcess += ",[watchdog/" + std::to_string(cpu) + "]";
+        defaultIgnorelistProcess += ",[watchdog/" + std::to_string(cpu) + "]";
     }
-    llkBlacklistProcess = llkSplit(LLK_BLACKLIST_PROCESS_PROPERTY, defaultBlacklistProcess);
+    llkIgnorelistProcess = llkSplit(LLK_IGNORELIST_PROCESS_PROPERTY, defaultIgnorelistProcess);
     if (!llkSkipName("[khungtaskd]")) {  // ALWAYS ignore as special
-        llkBlacklistProcess.emplace("[khungtaskd]");
+        llkIgnorelistProcess.emplace("[khungtaskd]");
     }
-    llkBlacklistParent = llkSplit(LLK_BLACKLIST_PARENT_PROPERTY,
-                                  std::to_string(kernelPid) + "," + std::to_string(kthreaddPid) +
-                                          "," LLK_BLACKLIST_PARENT_DEFAULT);
-    // derive llkBlacklistParentAndChild by moving entries with '&' from above
-    for (auto it = llkBlacklistParent.begin(); it != llkBlacklistParent.end();) {
+    llkIgnorelistParent = llkSplit(LLK_IGNORELIST_PARENT_PROPERTY,
+                                   std::to_string(kernelPid) + "," + std::to_string(kthreaddPid) +
+                                           "," LLK_IGNORELIST_PARENT_DEFAULT);
+    // derive llkIgnorelistParentAndChild by moving entries with '&' from above
+    for (auto it = llkIgnorelistParent.begin(); it != llkIgnorelistParent.end();) {
         auto pos = it->find('&');
         if (pos == std::string::npos) {
             ++it;
@@ -1350,18 +1350,18 @@
         }
         auto parent = it->substr(0, pos);
         auto child = it->substr(pos + 1);
-        it = llkBlacklistParent.erase(it);
+        it = llkIgnorelistParent.erase(it);
 
-        auto found = llkBlacklistParentAndChild.find(parent);
-        if (found == llkBlacklistParentAndChild.end()) {
-            llkBlacklistParentAndChild.emplace(std::make_pair(
+        auto found = llkIgnorelistParentAndChild.find(parent);
+        if (found == llkIgnorelistParentAndChild.end()) {
+            llkIgnorelistParentAndChild.emplace(std::make_pair(
                     std::move(parent), std::unordered_set<std::string>({std::move(child)})));
         } else {
             found->second.emplace(std::move(child));
         }
     }
 
-    llkBlacklistUid = llkSplit(LLK_BLACKLIST_UID_PROPERTY, LLK_BLACKLIST_UID_DEFAULT);
+    llkIgnorelistUid = llkSplit(LLK_IGNORELIST_UID_PROPERTY, LLK_IGNORELIST_UID_DEFAULT);
 
     // internal watchdog
     ::signal(SIGALRM, llkAlarmHandler);
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 0056c80..d15fa2b 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -333,14 +333,14 @@
                               This can individually control each buffer's size with -b.
   -S, --statistics            Output statistics.
                               --pid can be used to provide pid specific stats.
-  -p, --prune                 Print prune white and ~black list. Service is specified as UID,
-                              UID/PID or /PID. Weighed for quicker pruning if prefix with ~,
-                              otherwise weighed for longevity if unadorned. All other pruning
-                              activity is oldest first. Special case ~! represents an automatic
-                              quicker pruning for the noisiest UID as determined by the current
-                              statistics.
-  -P, --prune='<list> ...'    Set prune white and ~black list, using same format as listed above.
-                              Must be quoted.
+  -p, --prune                 Print prune rules. Each rule is specified as UID, UID/PID or /PID. A
+                              '~' prefix indicates that elements matching the rule should be pruned
+                              with higher priority otherwise they're pruned with lower priority. All
+                              other pruning activity is oldest first. Special case ~! represents an
+                              automatic pruning for the noisiest UID as determined by the current
+                              statistics.  Special case ~1000/! represents pruning of the worst PID
+                              within AID_SYSTEM when AID_SYSTEM is the noisiest UID.
+  -P, --prune='<list> ...'    Set prune rules, using same format as listed above. Must be quoted.
 
 Filtering:
   -s                          Set default filter to silent. Equivalent to filterspec '*:S'
@@ -402,8 +402,8 @@
         "  time       — Display the date, invocation time, priority/tag, and PID of the\n"
         "             process issuing the message.\n"
         "\nAdverb modifiers can be used in combination:\n"
-        "  color       — Display in highlighted color to match priority. i.e. \x1B[38;5;231mVERBOSE\n"
-        "                \x1B[38;5;75mDEBUG \x1B[38;5;40mINFO \x1B[38;5;166mWARNING \x1B[38;5;196mERROR FATAL\x1B[0m\n"
+        "  color       — Display in highlighted color to match priority. i.e. \x1B[39mVERBOSE\n"
+        "                \x1B[34mDEBUG \x1B[32mINFO \x1B[33mWARNING \x1B[31mERROR FATAL\x1B[0m\n"
         "  descriptive — events logs only, descriptions from event-log-tags database.\n"
         "  epoch       — Display time as seconds since Jan 1 1970.\n"
         "  monotonic   — Display time as cpu seconds since last boot.\n"
diff --git a/logcat/tests/logcat_test.cpp b/logcat/tests/logcat_test.cpp
index a2daeb0..61aa938 100644
--- a/logcat/tests/logcat_test.cpp
+++ b/logcat/tests/logcat_test.cpp
@@ -1301,7 +1301,7 @@
 }
 #endif
 
-static bool get_white_black(char** list) {
+static bool get_prune_rules(char** list) {
     FILE* fp = popen(logcat_executable " -p 2>/dev/null", "r");
     if (fp == NULL) {
         fprintf(stderr, "ERROR: logcat -p 2>/dev/null\n");
@@ -1334,7 +1334,7 @@
     return *list != NULL;
 }
 
-static bool set_white_black(const char* list) {
+static bool set_prune_rules(const char* list) {
     char buffer[BIG_BUFFER];
     snprintf(buffer, sizeof(buffer), logcat_executable " -P '%s' 2>&1",
              list ? list : "");
@@ -1363,28 +1363,28 @@
     return pclose(fp) == 0;
 }
 
-TEST(logcat, white_black_adjust) {
+TEST(logcat, prune_rules_adjust) {
     char* list = NULL;
     char* adjust = NULL;
 
-    get_white_black(&list);
+    get_prune_rules(&list);
 
     static const char adjustment[] = "~! 300/20 300/25 2000 ~1000/5 ~1000/30";
-    ASSERT_EQ(true, set_white_black(adjustment));
-    ASSERT_EQ(true, get_white_black(&adjust));
+    ASSERT_EQ(true, set_prune_rules(adjustment));
+    ASSERT_EQ(true, get_prune_rules(&adjust));
     EXPECT_STREQ(adjustment, adjust);
     free(adjust);
     adjust = NULL;
 
     static const char adjustment2[] = "300/20 300/21 2000 ~1000";
-    ASSERT_EQ(true, set_white_black(adjustment2));
-    ASSERT_EQ(true, get_white_black(&adjust));
+    ASSERT_EQ(true, set_prune_rules(adjustment2));
+    ASSERT_EQ(true, get_prune_rules(&adjust));
     EXPECT_STREQ(adjustment2, adjust);
     free(adjust);
     adjust = NULL;
 
-    ASSERT_EQ(true, set_white_black(list));
-    get_white_black(&adjust);
+    ASSERT_EQ(true, set_prune_rules(list));
+    get_prune_rules(&adjust);
     EXPECT_STREQ(list ? list : "", adjust ? adjust : "");
     free(adjust);
     adjust = NULL;
diff --git a/logd/Android.bp b/logd/Android.bp
index 1f6ab34..fe97871 100644
--- a/logd/Android.bp
+++ b/logd/Android.bp
@@ -31,7 +31,11 @@
 cc_defaults {
     name: "logd_defaults",
 
-    shared_libs: ["libbase"],
+    shared_libs: [
+        "libbase",
+        "libz",
+    ],
+    static_libs: ["libzstd"],
     cflags: [
         "-Wextra",
         "-Wthread-safety",
@@ -40,6 +44,7 @@
     lto: {
         thin: true,
     },
+    cpp_std: "experimental",
 }
 
 cc_library_static {
@@ -48,12 +53,16 @@
     host_supported: true,
     srcs: [
         "ChattyLogBuffer.cpp",
+        "CompressionEngine.cpp",
         "LogReaderList.cpp",
         "LogReaderThread.cpp",
         "LogBufferElement.cpp",
         "LogStatistics.cpp",
-        "LogWhiteBlackList.cpp",
         "LogTags.cpp",
+        "PruneList.cpp",
+        "SerializedFlushToState.cpp",
+        "SerializedLogBuffer.cpp",
+        "SerializedLogChunk.cpp",
         "SimpleLogBuffer.cpp",
     ],
     logtags: ["event.logtags"],
@@ -132,6 +141,8 @@
         "ChattyLogBufferTest.cpp",
         "logd_test.cpp",
         "LogBufferTest.cpp",
+        "SerializedLogChunkTest.cpp",
+        "SerializedFlushToStateTest.cpp",
     ],
 
     static_libs: [
@@ -140,6 +151,8 @@
         "liblog",
         "liblogd",
         "libselinux",
+        "libz",
+        "libzstd",
     ],
 }
 
@@ -167,3 +180,23 @@
         "vts10",
     ],
 }
+
+cc_binary {
+    name: "replay_messages",
+    defaults: ["logd_defaults"],
+    host_supported: true,
+
+    srcs: [
+        "ReplayMessages.cpp",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+        "liblogd",
+        "libselinux",
+        "libz",
+        "libzstd",
+    ],
+}
diff --git a/logd/ChattyLogBuffer.cpp b/logd/ChattyLogBuffer.cpp
index c213448..fd183e4 100644
--- a/logd/ChattyLogBuffer.cpp
+++ b/logd/ChattyLogBuffer.cpp
@@ -298,33 +298,37 @@
 // invariably move the logs value down faster as less chatty sources would be
 // expired in the noise.
 //
-// The first loop performs blacklisting and worst offender pruning. Falling
-// through when there are no notable worst offenders and have not hit the
-// region lock preventing further worst offender pruning. This loop also looks
-// after managing the chatty log entries and merging to help provide
-// statistical basis for blame. The chatty entries are not a notification of
-// how much logs you may have, but instead represent how much logs you would
-// have had in a virtual log buffer that is extended to cover all the in-memory
-// logs without loss. They last much longer than the represented pruned logs
-// since they get multiplied by the gains in the non-chatty log sources.
+// The first pass prunes elements that match 3 possible rules:
+// 1) A high priority prune rule, for example ~100/20, which indicates elements from UID 100 and PID
+//    20 should be pruned in this first pass.
+// 2) The default chatty pruning rule, ~!.  This rule sums the total size spent on log messages for
+//    each UID this log buffer.  If the highest sum consumes more than 12.5% of the log buffer, then
+//    these elements from that UID are pruned.
+// 3) The default AID_SYSTEM pruning rule, ~1000/!.  This rule is a special case to 2), if
+//    AID_SYSTEM is the top consumer of the log buffer, then this rule sums the total size spent on
+//    log messages for each PID in AID_SYSTEM in this log buffer and prunes elements from the PID
+//    with the highest sum.
+// This pass reevaluates the sums for rules 2) and 3) for every log message pruned. It creates
+// 'chatty' entries for the elements that it prunes and merges related chatty entries together. It
+// completes when one of three conditions have been met:
+// 1) The requested element count has been pruned.
+// 2) There are no elements that match any of these rules.
+// 3) A reader is referencing the oldest element that would match these rules.
 //
-// The second loop get complicated because an algorithm of watermarks and
-// history is maintained to reduce the order and keep processing time
-// down to a minimum at scale. These algorithms can be costly in the face
-// of larger log buffers, or severly limited processing time granted to a
-// background task at lowest priority.
+// The second pass prunes elements starting from the beginning of the log.  It skips elements that
+// match any low priority prune rules.  It completes when one of three conditions have been met:
+// 1) The requested element count has been pruned.
+// 2) All elements except those mwatching low priority prune rules have been pruned.
+// 3) A reader is referencing the oldest element that would match these rules.
 //
-// This second loop does straight-up expiration from the end of the logs
-// (again, remember for the specified log buffer id) but does some whitelist
-// preservation. Thus whitelist is a Hail Mary low priority, blacklists and
-// spam filtration all take priority. This second loop also checks if a region
-// lock is causing us to buffer too much in the logs to help the reader(s),
-// and will tell the slowest reader thread to skip log entries, and if
-// persistent and hits a further threshold, kill the reader thread.
+// The final pass only happens if there are any low priority prune rules and if the first two passes
+// were unable to prune the requested number of elements.  It prunes elements all starting from the
+// beginning of the log, regardless of if they match any low priority prune rules.
 //
-// The third thread is optional, and only gets hit if there was a whitelist
-// and more needs to be pruned against the backstop of the region lock.
-//
+// If the requested number of logs was unable to be pruned, KickReader() is called to mitigate the
+// situation before the next call to Prune() and the function returns false.  Otherwise, if the
+// requested number of logs or all logs present in the buffer are pruned, in the case of Clear(),
+// it returns true.
 bool ChattyLogBuffer::Prune(log_id_t id, unsigned long pruneRows, uid_t caller_uid) {
     LogReaderThread* oldest = nullptr;
     bool clearAll = pruneRows == ULONG_MAX;
@@ -370,8 +374,8 @@
         return true;
     }
 
-    // prune by worst offenders; by blacklist, UID, and by PID of system UID
-    bool hasBlacklist = (id != LOG_ID_SECURITY) && prune_->naughty();
+    // First prune pass.
+    bool check_high_priority = id != LOG_ID_SECURITY && prune_->HasHighPriorityPruneRules();
     while (!clearAll && (pruneRows > 0)) {
         // recalculate the worst offender on every batched pass
         int worst = -1;  // not valid for uid() or getKey()
@@ -379,7 +383,7 @@
         size_t second_worst_sizes = 0;
         pid_t worstPid = 0;  // POSIX guarantees PID != 0
 
-        if (worstUidEnabledForLogid(id) && prune_->worstUidEnabled()) {
+        if (worstUidEnabledForLogid(id) && prune_->worst_uid_enabled()) {
             // Calculate threshold as 12.5% of available storage
             size_t threshold = max_size(id) / 8;
 
@@ -389,14 +393,14 @@
             } else {
                 stats()->WorstTwoUids(id, threshold, &worst, &worst_sizes, &second_worst_sizes);
 
-                if (worst == AID_SYSTEM && prune_->worstPidOfSystemEnabled()) {
+                if (worst == AID_SYSTEM && prune_->worst_pid_of_system_enabled()) {
                     stats()->WorstTwoSystemPids(id, worst_sizes, &worstPid, &second_worst_sizes);
                 }
             }
         }
 
-        // skip if we have neither worst nor naughty filters
-        if ((worst == -1) && !hasBlacklist) {
+        // skip if we have neither a worst UID or high priority prune rules
+        if (worst == -1 && !check_high_priority) {
             break;
         }
 
@@ -464,7 +468,7 @@
             int key = (id == LOG_ID_EVENTS || id == LOG_ID_SECURITY) ? element.GetTag()
                                                                      : element.uid();
 
-            if (hasBlacklist && prune_->naughty(&element)) {
+            if (check_high_priority && prune_->IsHighPriority(&element)) {
                 last.clear(&element);
                 it = Erase(it);
                 if (dropped) {
@@ -557,15 +561,17 @@
         }
         last.clear();
 
-        if (!kick || !prune_->worstUidEnabled()) {
+        if (!kick || !prune_->worst_uid_enabled()) {
             break;  // the following loop will ask bad clients to skip/drop
         }
     }
 
-    bool whitelist = false;
-    bool hasWhitelist = (id != LOG_ID_SECURITY) && prune_->nice() && !clearAll;
+    // Second prune pass.
+    bool skipped_low_priority_prune = false;
+    bool check_low_priority =
+            id != LOG_ID_SECURITY && prune_->HasLowPriorityPruneRules() && !clearAll;
     it = GetOldest(id);
-    while ((pruneRows > 0) && (it != logs().end())) {
+    while (pruneRows > 0 && it != logs().end()) {
         LogBufferElement& element = *it;
 
         if (element.log_id() != id) {
@@ -574,13 +580,12 @@
         }
 
         if (oldest && oldest->start() <= element.sequence()) {
-            if (!whitelist) KickReader(oldest, id, pruneRows);
+            if (!skipped_low_priority_prune) KickReader(oldest, id, pruneRows);
             break;
         }
 
-        if (hasWhitelist && !element.dropped_count() && prune_->nice(&element)) {
-            // WhiteListed
-            whitelist = true;
+        if (check_low_priority && !element.dropped_count() && prune_->IsLowPriority(&element)) {
+            skipped_low_priority_prune = true;
             it++;
             continue;
         }
@@ -589,10 +594,10 @@
         pruneRows--;
     }
 
-    // Do not save the whitelist if we are reader range limited
-    if (whitelist && (pruneRows > 0)) {
+    // Third prune pass.
+    if (skipped_low_priority_prune && pruneRows > 0) {
         it = GetOldest(id);
-        while ((it != logs().end()) && (pruneRows > 0)) {
+        while (it != logs().end() && pruneRows > 0) {
             LogBufferElement& element = *it;
 
             if (element.log_id() != id) {
diff --git a/logd/ChattyLogBuffer.h b/logd/ChattyLogBuffer.h
index 6f60272..ce3dc7b 100644
--- a/logd/ChattyLogBuffer.h
+++ b/logd/ChattyLogBuffer.h
@@ -33,8 +33,8 @@
 #include "LogReaderThread.h"
 #include "LogStatistics.h"
 #include "LogTags.h"
-#include "LogWhiteBlackList.h"
 #include "LogWriter.h"
+#include "PruneList.h"
 #include "SimpleLogBuffer.h"
 #include "rwlock.h"
 
diff --git a/logd/CommandListener.cpp b/logd/CommandListener.cpp
index 39c5490..2eeb0d9 100644
--- a/logd/CommandListener.cpp
+++ b/logd/CommandListener.cpp
@@ -215,15 +215,13 @@
     return 0;
 }
 
-int CommandListener::GetPruneListCmd::runCommand(SocketClient* cli,
-                                                 int /*argc*/, char** /*argv*/) {
+int CommandListener::GetPruneListCmd::runCommand(SocketClient* cli, int, char**) {
     setname();
-    cli->sendMsg(PackageString(prune()->format()).c_str());
+    cli->sendMsg(PackageString(prune()->Format()).c_str());
     return 0;
 }
 
-int CommandListener::SetPruneListCmd::runCommand(SocketClient* cli, int argc,
-                                                 char** argv) {
+int CommandListener::SetPruneListCmd::runCommand(SocketClient* cli, int argc, char** argv) {
     setname();
     if (!clientHasLogCredentials(cli)) {
         cli->sendMsg("Permission Denied");
@@ -238,15 +236,12 @@
         str += argv[i];
     }
 
-    int ret = prune()->init(str.c_str());
-
-    if (ret) {
+    if (!prune()->Init(str.c_str())) {
         cli->sendMsg("Invalid");
         return 0;
     }
 
     cli->sendMsg("success");
-
     return 0;
 }
 
@@ -301,7 +296,7 @@
 
     LOG(INFO) << "logd reinit";
     buf()->Init();
-    prune()->init(nullptr);
+    prune()->Init(nullptr);
 
     // This only works on userdebug and eng devices to re-read the
     // /data/misc/logd/event-log-tags file right after /data is mounted.
diff --git a/logd/CommandListener.h b/logd/CommandListener.h
index a55a393..c3080ab 100644
--- a/logd/CommandListener.h
+++ b/logd/CommandListener.h
@@ -23,7 +23,7 @@
 #include "LogListener.h"
 #include "LogStatistics.h"
 #include "LogTags.h"
-#include "LogWhiteBlackList.h"
+#include "PruneList.h"
 
 class CommandListener : public FrameworkListener {
   public:
diff --git a/logd/CompressionEngine.cpp b/logd/CompressionEngine.cpp
new file mode 100644
index 0000000..da2628c
--- /dev/null
+++ b/logd/CompressionEngine.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 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 "CompressionEngine.h"
+
+#include <limits>
+
+#include <android-base/logging.h>
+#include <zlib.h>
+#include <zstd.h>
+
+CompressionEngine& CompressionEngine::GetInstance() {
+    static CompressionEngine* engine = new ZstdCompressionEngine();
+    return *engine;
+}
+
+bool ZlibCompressionEngine::Compress(SerializedData& in, size_t data_length, SerializedData& out) {
+    z_stream strm;
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+    int ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION);
+    if (ret != Z_OK) {
+        LOG(FATAL) << "deflateInit() failed";
+    }
+
+    CHECK_LE(data_length, in.size());
+    CHECK_LE(in.size(), std::numeric_limits<uint32_t>::max());
+    uint32_t deflate_bound = deflateBound(&strm, in.size());
+
+    out.Resize(deflate_bound);
+
+    strm.avail_in = data_length;
+    strm.next_in = in.data();
+    strm.avail_out = out.size();
+    strm.next_out = out.data();
+    ret = deflate(&strm, Z_FINISH);
+    CHECK_EQ(ret, Z_STREAM_END);
+
+    uint32_t compressed_size = strm.total_out;
+    deflateEnd(&strm);
+
+    out.Resize(compressed_size);
+
+    return true;
+}
+
+bool ZlibCompressionEngine::Decompress(SerializedData& in, SerializedData& out) {
+    z_stream strm;
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+    strm.avail_in = in.size();
+    strm.next_in = in.data();
+    strm.avail_out = out.size();
+    strm.next_out = out.data();
+
+    inflateInit(&strm);
+    int ret = inflate(&strm, Z_NO_FLUSH);
+
+    CHECK_EQ(strm.avail_in, 0U);
+    CHECK_EQ(strm.avail_out, 0U);
+    CHECK_EQ(ret, Z_STREAM_END);
+    inflateEnd(&strm);
+
+    return true;
+}
+
+bool ZstdCompressionEngine::Compress(SerializedData& in, size_t data_length, SerializedData& out) {
+    CHECK_LE(data_length, in.size());
+
+    size_t compress_bound = ZSTD_compressBound(data_length);
+    out.Resize(compress_bound);
+
+    size_t out_size = ZSTD_compress(out.data(), out.size(), in.data(), data_length, 1);
+    if (ZSTD_isError(out_size)) {
+        LOG(FATAL) << "ZSTD_compress failed: " << ZSTD_getErrorName(out_size);
+    }
+    out.Resize(out_size);
+
+    return true;
+}
+
+bool ZstdCompressionEngine::Decompress(SerializedData& in, SerializedData& out) {
+    size_t result = ZSTD_decompress(out.data(), out.size(), in.data(), in.size());
+    if (ZSTD_isError(result)) {
+        LOG(FATAL) << "ZSTD_decompress failed: " << ZSTD_getErrorName(result);
+    }
+    CHECK_EQ(result, out.size());
+    return true;
+}
diff --git a/logd/CompressionEngine.h b/logd/CompressionEngine.h
new file mode 100644
index 0000000..0f760ed
--- /dev/null
+++ b/logd/CompressionEngine.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 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 <memory>
+
+#include "SerializedData.h"
+
+class CompressionEngine {
+  public:
+    static CompressionEngine& GetInstance();
+
+    virtual ~CompressionEngine(){};
+
+    virtual bool Compress(SerializedData& in, size_t data_length, SerializedData& out) = 0;
+    // Decompress the contents of `in` into `out`.  `out.size()` must be set to the decompressed
+    // size of the contents.
+    virtual bool Decompress(SerializedData& in, SerializedData& out) = 0;
+};
+
+class ZlibCompressionEngine : public CompressionEngine {
+  public:
+    bool Compress(SerializedData& in, size_t data_length, SerializedData& out) override;
+    bool Decompress(SerializedData& in, SerializedData& out) override;
+};
+
+class ZstdCompressionEngine : public CompressionEngine {
+  public:
+    bool Compress(SerializedData& in, size_t data_length, SerializedData& out) override;
+    bool Decompress(SerializedData& in, SerializedData& out) override;
+};
\ No newline at end of file
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
index ef9f1cf..26affa8 100644
--- a/logd/LogBufferElement.cpp
+++ b/logd/LogBufferElement.cpp
@@ -61,7 +61,7 @@
     }
 }
 
-LogBufferElement::LogBufferElement(LogBufferElement&& elem)
+LogBufferElement::LogBufferElement(LogBufferElement&& elem) noexcept
     : uid_(elem.uid_),
       pid_(elem.pid_),
       tid_(elem.tid_),
@@ -99,6 +99,10 @@
 }
 
 LogStatisticsElement LogBufferElement::ToLogStatisticsElement() const {
+    // Estimate the size of this element in the parent std::list<> by adding two void*'s
+    // corresponding to the next/prev pointers and aligning to 64 bit.
+    uint16_t element_in_list_size =
+            (sizeof(*this) + 2 * sizeof(void*) + sizeof(uint64_t) - 1) & -sizeof(uint64_t);
     return LogStatisticsElement{
             .uid = uid(),
             .pid = pid(),
@@ -109,6 +113,7 @@
             .msg_len = msg_len(),
             .dropped_count = dropped_count(),
             .log_id = log_id(),
+            .total_len = static_cast<uint16_t>(element_in_list_size + msg_len()),
     };
 }
 
@@ -134,7 +139,7 @@
     char* retval = nullptr;
     char buffer[256];
     snprintf(buffer, sizeof(buffer), "/proc/%u/comm", tid);
-    int fd = open(buffer, O_RDONLY);
+    int fd = open(buffer, O_RDONLY | O_CLOEXEC);
     if (fd >= 0) {
         ssize_t ret = read(fd, buffer, sizeof(buffer));
         if (ret >= (ssize_t)sizeof(buffer)) {
diff --git a/logd/LogBufferElement.h b/logd/LogBufferElement.h
index fd5d88f..b263ca5 100644
--- a/logd/LogBufferElement.h
+++ b/logd/LogBufferElement.h
@@ -37,7 +37,7 @@
     LogBufferElement(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
                      uint64_t sequence, const char* msg, uint16_t len);
     LogBufferElement(const LogBufferElement& elem);
-    LogBufferElement(LogBufferElement&& elem);
+    LogBufferElement(LogBufferElement&& elem) noexcept;
     ~LogBufferElement();
 
     uint32_t GetTag() const;
diff --git a/logd/LogBufferTest.cpp b/logd/LogBufferTest.cpp
index 412b6f1..47d2a2f 100644
--- a/logd/LogBufferTest.cpp
+++ b/logd/LogBufferTest.cpp
@@ -455,4 +455,5 @@
     CompareLogMessages(after_clear_messages, read_log_messages_after_clear);
 }
 
-INSTANTIATE_TEST_CASE_P(LogBufferTests, LogBufferTest, testing::Values("chatty", "simple"));
+INSTANTIATE_TEST_CASE_P(LogBufferTests, LogBufferTest,
+                        testing::Values("chatty", "serialized", "simple"));
diff --git a/logd/LogBufferTest.h b/logd/LogBufferTest.h
index f91a1b5..1fd22c2 100644
--- a/logd/LogBufferTest.h
+++ b/logd/LogBufferTest.h
@@ -25,7 +25,8 @@
 #include "LogReaderList.h"
 #include "LogStatistics.h"
 #include "LogTags.h"
-#include "LogWhiteBlackList.h"
+#include "PruneList.h"
+#include "SerializedLogBuffer.h"
 #include "SimpleLogBuffer.h"
 
 struct LogMessage {
@@ -67,6 +68,8 @@
     void SetUp() override {
         if (GetParam() == "chatty") {
             log_buffer_.reset(new ChattyLogBuffer(&reader_list_, &tags_, &prune_, &stats_));
+        } else if (GetParam() == "serialized") {
+            log_buffer_.reset(new SerializedLogBuffer(&reader_list_, &tags_, &stats_));
         } else if (GetParam() == "simple") {
             log_buffer_.reset(new SimpleLogBuffer(&reader_list_, &tags_, &stats_));
         } else {
@@ -84,6 +87,6 @@
     LogReaderList reader_list_;
     LogTags tags_;
     PruneList prune_;
-    LogStatistics stats_{false};
+    LogStatistics stats_{false, true};
     std::unique_ptr<LogBuffer> log_buffer_;
 };
diff --git a/logd/LogKlog.cpp b/logd/LogKlog.cpp
index 1ea87a9..dbdf7fd 100644
--- a/logd/LogKlog.cpp
+++ b/logd/LogKlog.cpp
@@ -665,10 +665,9 @@
         ((size == 2) && (isdigit(tag[0]) || isdigit(tag[1]))) ||
         // register names like x18 but not driver names like en0
         ((size == 3) && (isdigit(tag[1]) && isdigit(tag[2]))) ||
-        // blacklist
+        // ignore
         ((size == cpuLen) && !fastcmp<strncmp>(tag, cpu, cpuLen)) ||
-        ((size == warningLen) &&
-         !fastcmp<strncasecmp>(tag, warning, warningLen)) ||
+        ((size == warningLen) && !fastcmp<strncasecmp>(tag, warning, warningLen)) ||
         ((size == errorLen) && !fastcmp<strncasecmp>(tag, error, errorLen)) ||
         ((size == infoLen) && !fastcmp<strncasecmp>(tag, info, infoLen))) {
         p = start;
diff --git a/logd/LogListener.h b/logd/LogListener.h
index c114e38..566af5b 100644
--- a/logd/LogListener.h
+++ b/logd/LogListener.h
@@ -20,7 +20,7 @@
 
 class LogListener {
   public:
-    LogListener(LogBuffer* buf);
+    explicit LogListener(LogBuffer* buf);
     bool StartListener();
 
   private:
diff --git a/logd/LogPermissions.cpp b/logd/LogPermissions.cpp
index 8f02d5a..3fd0ea1 100644
--- a/logd/LogPermissions.cpp
+++ b/logd/LogPermissions.cpp
@@ -89,7 +89,7 @@
     //
     for (int retry = 3; !(ret = foundGid && foundUid && foundLog) && retry;
          --retry) {
-        FILE* file = fopen(filename, "r");
+        FILE* file = fopen(filename, "re");
         if (!file) {
             continue;
         }
diff --git a/logd/LogReader.cpp b/logd/LogReader.cpp
index f71133d..6c97693 100644
--- a/logd/LogReader.cpp
+++ b/logd/LogReader.cpp
@@ -127,7 +127,7 @@
     if (cp) {
         logMask = 0;
         cp += sizeof(_logIds) - 1;
-        while (*cp && *cp != '\0') {
+        while (*cp != '\0') {
             int val = 0;
             while (isdigit(*cp)) {
                 val = val * 10 + *cp - '0';
diff --git a/logd/LogReaderThread.cpp b/logd/LogReaderThread.cpp
index dc8582d..4a8be01 100644
--- a/logd/LogReaderThread.cpp
+++ b/logd/LogReaderThread.cpp
@@ -25,8 +25,6 @@
 #include "LogBuffer.h"
 #include "LogReaderList.h"
 
-using namespace std::placeholders;
-
 LogReaderThread::LogReaderThread(LogBuffer* log_buffer, LogReaderList* reader_list,
                                  std::unique_ptr<LogWriter> writer, bool non_block,
                                  unsigned long tail, LogMask log_mask, pid_t pid,
diff --git a/logd/LogReaderThread.h b/logd/LogReaderThread.h
index bf70b94..1855c0e 100644
--- a/logd/LogReaderThread.h
+++ b/logd/LogReaderThread.h
@@ -60,6 +60,7 @@
     std::string name() const { return writer_->name(); }
     uint64_t start() const { return flush_to_state_->start(); }
     std::chrono::steady_clock::time_point deadline() const { return deadline_; }
+    FlushToState& flush_to_state() { return *flush_to_state_; }
 
   private:
     void ThreadFunction();
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
index d49982a..87069b3 100644
--- a/logd/LogStatistics.cpp
+++ b/logd/LogStatistics.cpp
@@ -26,7 +26,10 @@
 #include <unistd.h>
 
 #include <list>
+#include <vector>
 
+#include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <private/android_logger.h>
 
 #include "LogBufferElement.h"
@@ -60,7 +63,9 @@
     return std::string(msg, len);
 }
 
-LogStatistics::LogStatistics(bool enable_statistics) : enable(enable_statistics) {
+LogStatistics::LogStatistics(bool enable_statistics, bool track_total_size,
+                             std::optional<log_time> start_time)
+    : enable(enable_statistics), track_total_size_(track_total_size) {
     log_time now(CLOCK_REALTIME);
     log_id_for_each(id) {
         mSizes[id] = 0;
@@ -68,8 +73,13 @@
         mDroppedElements[id] = 0;
         mSizesTotal[id] = 0;
         mElementsTotal[id] = 0;
-        mOldest[id] = now;
-        mNewest[id] = now;
+        if (start_time) {
+            mOldest[id] = *start_time;
+            mNewest[id] = *start_time;
+        } else {
+            mOldest[id] = now;
+            mNewest[id] = now;
+        }
         mNewestDropped[id] = now;
     }
 }
@@ -88,7 +98,7 @@
     } else {
         char buffer[512];
         snprintf(buffer, sizeof(buffer), "/proc/%u/cmdline", pid);
-        int fd = open(buffer, O_RDONLY);
+        int fd = open(buffer, O_RDONLY | O_CLOEXEC);
         if (fd >= 0) {
             ssize_t ret = read(fd, buffer, sizeof(buffer));
             if (ret > 0) {
@@ -113,10 +123,15 @@
     ++mElementsTotal[log_id];
 }
 
-void LogStatistics::Add(const LogStatisticsElement& element) {
+void LogStatistics::Add(LogStatisticsElement element) {
     auto lock = std::lock_guard{lock_};
+
+    if (!track_total_size_) {
+        element.total_len = element.msg_len;
+    }
+
     log_id_t log_id = element.log_id;
-    uint16_t size = element.msg_len;
+    uint16_t size = element.total_len;
     mSizes[log_id] += size;
     ++mElements[log_id];
 
@@ -184,10 +199,15 @@
     }
 }
 
-void LogStatistics::Subtract(const LogStatisticsElement& element) {
+void LogStatistics::Subtract(LogStatisticsElement element) {
     auto lock = std::lock_guard{lock_};
+
+    if (!track_total_size_) {
+        element.total_len = element.msg_len;
+    }
+
     log_id_t log_id = element.log_id;
-    uint16_t size = element.msg_len;
+    uint16_t size = element.total_len;
     mSizes[log_id] -= size;
     --mElements[log_id];
     if (element.dropped_count) {
@@ -230,7 +250,9 @@
 
 // Atomically set an entry to drop
 // entry->setDropped(1) must follow this call, caller should do this explicitly.
-void LogStatistics::Drop(const LogStatisticsElement& element) {
+void LogStatistics::Drop(LogStatisticsElement element) {
+    CHECK_EQ(element.dropped_count, 0U);
+
     auto lock = std::lock_guard{lock_};
     log_id_t log_id = element.log_id;
     uint16_t size = element.msg_len;
@@ -265,6 +287,43 @@
     tagNameTable.Subtract(TagNameKey(element), element);
 }
 
+void LogStatistics::Erase(LogStatisticsElement element) {
+    CHECK_GT(element.dropped_count, 0U);
+    CHECK_EQ(element.msg_len, 0U);
+
+    auto lock = std::lock_guard{lock_};
+
+    if (!track_total_size_) {
+        element.total_len = 0;
+    }
+
+    log_id_t log_id = element.log_id;
+    --mElements[log_id];
+    --mDroppedElements[log_id];
+    mSizes[log_id] -= element.total_len;
+
+    uidTable[log_id].Erase(element.uid, element);
+    if (element.uid == AID_SYSTEM) {
+        pidSystemTable[log_id].Erase(element.pid, element);
+    }
+
+    if (!enable) {
+        return;
+    }
+
+    pidTable.Erase(element.pid, element);
+    tidTable.Erase(element.tid, element);
+
+    uint32_t tag = element.tag;
+    if (tag) {
+        if (log_id == LOG_ID_SECURITY) {
+            securityTagTable.Erase(tag, element);
+        } else {
+            tagTable.Erase(tag, element);
+        }
+    }
+}
+
 const char* LogStatistics::UidToName(uid_t uid) const {
     auto lock = std::lock_guard{lock_};
     return UidToNameLocked(uid);
@@ -733,6 +792,31 @@
     return output;
 }
 
+std::string LogStatistics::ReportInteresting() const {
+    auto lock = std::lock_guard{lock_};
+
+    std::vector<std::string> items;
+
+    log_id_for_each(i) { items.emplace_back(std::to_string(mElements[i])); }
+
+    log_id_for_each(i) { items.emplace_back(std::to_string(mSizes[i])); }
+
+    log_id_for_each(i) {
+        items.emplace_back(std::to_string(overhead_[i] ? *overhead_[i] : mSizes[i]));
+    }
+
+    log_id_for_each(i) {
+        uint64_t oldest = mOldest[i].msec() / 1000;
+        uint64_t newest = mNewest[i].msec() / 1000;
+
+        int span = newest - oldest;
+
+        items.emplace_back(std::to_string(span));
+    }
+
+    return android::base::Join(items, ",");
+}
+
 std::string LogStatistics::Format(uid_t uid, pid_t pid, unsigned int logMask) const {
     auto lock = std::lock_guard{lock_};
 
@@ -876,12 +960,20 @@
         if (els) {
             oldLength = output.length();
             if (spaces < 0) spaces = 0;
-            // estimate the std::list overhead.
-            static const size_t overhead =
-                ((sizeof(LogBufferElement) + sizeof(uint64_t) - 1) &
-                 -sizeof(uint64_t)) +
-                sizeof(std::list<LogBufferElement*>);
-            size_t szs = mSizes[id] + els * overhead;
+            size_t szs = 0;
+            if (overhead_[id]) {
+                szs = *overhead_[id];
+            } else if (track_total_size_) {
+                szs = mSizes[id];
+            } else {
+                // Legacy fallback for Chatty without track_total_size_
+                // Estimate the size of this element in the parent std::list<> by adding two void*'s
+                // corresponding to the next/prev pointers and aligning to 64 bit.
+                static const size_t overhead =
+                        (sizeof(LogBufferElement) + 2 * sizeof(void*) + sizeof(uint64_t) - 1) &
+                        -sizeof(uint64_t);
+                szs = mSizes[id] + els * overhead;
+            }
             totalSize += szs;
             output += android::base::StringPrintf("%*s%zu", spaces, "", szs);
             spaces -= output.length() - oldLength;
@@ -944,7 +1036,7 @@
 uid_t pidToUid(pid_t pid) {
     char buffer[512];
     snprintf(buffer, sizeof(buffer), "/proc/%u/status", pid);
-    FILE* fp = fopen(buffer, "r");
+    FILE* fp = fopen(buffer, "re");
     if (fp) {
         while (fgets(buffer, sizeof(buffer), fp)) {
             int uid = AID_LOGD;
diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h
index 200c228..e222d3f 100644
--- a/logd/LogStatistics.h
+++ b/logd/LogStatistics.h
@@ -57,6 +57,7 @@
     uint16_t msg_len;
     uint16_t dropped_count;
     log_id_t log_id;
+    uint16_t total_len;
 };
 
 template <typename TKey, typename TEntry>
@@ -128,9 +129,9 @@
             if (++index < (ssize_t)len) {
                 size_t num = len - index - 1;
                 if (num) {
-                    memmove(&out_keys[index + 1], &out_keys[index], num * sizeof(out_keys[0]));
+                    memmove(&out_keys[index + 1], &out_keys[index], num * sizeof(const TKey*));
                     memmove(&out_entries[index + 1], &out_entries[index],
-                            num * sizeof(out_entries[0]));
+                            num * sizeof(const TEntry*));
                 }
                 out_keys[index] = &key;
                 out_entries[index] = &entry;
@@ -172,6 +173,13 @@
         }
     }
 
+    void Erase(const TKey& key, const LogStatisticsElement& element) {
+        iterator it = map.find(key);
+        if (it != map.end()) {
+            it->second.Erase(element);
+        }
+    }
+
     iterator begin() { return map.begin(); }
     const_iterator begin() const { return map.begin(); }
     iterator end() { return map.end(); }
@@ -181,15 +189,17 @@
 class EntryBase {
   public:
     EntryBase() : size_(0) {}
-    explicit EntryBase(const LogStatisticsElement& element) : size_(element.msg_len) {}
+    explicit EntryBase(const LogStatisticsElement& element) : size_(element.total_len) {}
 
     size_t getSizes() const { return size_; }
 
-    void Add(const LogStatisticsElement& element) { size_ += element.msg_len; }
+    void Add(const LogStatisticsElement& element) { size_ += element.total_len; }
     bool Subtract(const LogStatisticsElement& element) {
-        size_ -= element.msg_len;
-        return !size_;
+        size_ -= element.total_len;
+        return size_ == 0;
     }
+    void Drop(const LogStatisticsElement& element) { size_ -= element.msg_len; }
+    void Erase(const LogStatisticsElement& element) { size_ -= element.total_len; }
 
     static constexpr size_t PRUNED_LEN = 14;
     static constexpr size_t TOTAL_LEN = 80;
@@ -229,11 +239,11 @@
     }
     bool Subtract(const LogStatisticsElement& element) {
         dropped_ -= element.dropped_count;
-        return EntryBase::Subtract(element) && !dropped_;
+        return EntryBase::Subtract(element) && dropped_ == 0;
     }
     void Drop(const LogStatisticsElement& element) {
         dropped_ += 1;
-        EntryBase::Subtract(element);
+        EntryBase::Drop(element);
     }
 
   private:
@@ -477,15 +487,15 @@
                       tagNameTable.sizeOf() +
                       (pidTable.size() * sizeof(pidTable_t::iterator)) +
                       (tagTable.size() * sizeof(tagTable_t::iterator));
-        for (auto it : pidTable) {
+        for (const auto& it : pidTable) {
             const char* name = it.second.name();
             if (name) size += strlen(name) + 1;
         }
-        for (auto it : tidTable) {
+        for (const auto& it : tidTable) {
             const char* name = it.second.name();
             if (name) size += strlen(name) + 1;
         }
-        for (auto it : tagNameTable) {
+        for (const auto& it : tagNameTable) {
             size += sizeof(std::string);
             size_t len = it.first.size();
             // Account for short string optimization: if the string's length is <= 22 bytes for 64
@@ -505,20 +515,24 @@
     }
 
   public:
-    LogStatistics(bool enable_statistics);
+    LogStatistics(bool enable_statistics, bool track_total_size,
+                  std::optional<log_time> start_time = {});
 
     void AddTotal(log_id_t log_id, uint16_t size) EXCLUDES(lock_);
-    void Add(const LogStatisticsElement& entry) EXCLUDES(lock_);
-    void Subtract(const LogStatisticsElement& entry) EXCLUDES(lock_);
-    // entry->setDropped(1) must follow this call
-    void Drop(const LogStatisticsElement& entry) EXCLUDES(lock_);
-    // Correct for coalescing two entries referencing dropped content
-    void Erase(const LogStatisticsElement& element) EXCLUDES(lock_) {
-        auto lock = std::lock_guard{lock_};
-        log_id_t log_id = element.log_id;
-        --mElements[log_id];
-        --mDroppedElements[log_id];
-    }
+
+    // Add is for adding an element to the log buffer.  It may be a chatty element in the case of
+    // log deduplication.  Add the total size of the element to statistics.
+    void Add(LogStatisticsElement entry) EXCLUDES(lock_);
+    // Subtract is for removing an element from the log buffer.  It may be a chatty element.
+    // Subtract the total size of the element from statistics.
+    void Subtract(LogStatisticsElement entry) EXCLUDES(lock_);
+    // Drop is for converting a normal element into a chatty element. entry->setDropped(1) must
+    // follow this call.  Subtract only msg_len from statistics, since a chatty element will remain.
+    void Drop(LogStatisticsElement entry) EXCLUDES(lock_);
+    // Erase is for coalescing two chatty elements into one.  Erase() is called on the element that
+    // is removed from the log buffer.  Subtract the total size of the element, which is by
+    // definition only the size of the LogBufferElement + list overhead for chatty elements.
+    void Erase(LogStatisticsElement element) EXCLUDES(lock_);
 
     void WorstTwoUids(log_id id, size_t threshold, int* worst, size_t* worst_sizes,
                       size_t* second_worst_sizes) const EXCLUDES(lock_);
@@ -533,6 +547,9 @@
     // Snapshot of the sizes for a given log buffer.
     size_t Sizes(log_id_t id) const EXCLUDES(lock_) {
         auto lock = std::lock_guard{lock_};
+        if (overhead_[id]) {
+            return *overhead_[id];
+        }
         return mSizes[id];
     }
     // TODO: Get rid of this entirely.
@@ -540,12 +557,18 @@
         return SizesTotal;
     }
 
+    std::string ReportInteresting() const EXCLUDES(lock_);
     std::string Format(uid_t uid, pid_t pid, unsigned int logMask) const EXCLUDES(lock_);
 
     const char* PidToName(pid_t pid) const EXCLUDES(lock_);
     uid_t PidToUid(pid_t pid) EXCLUDES(lock_);
     const char* UidToName(uid_t uid) const EXCLUDES(lock_);
 
+    void set_overhead(log_id_t id, size_t size) {
+        auto lock = std::lock_guard{lock_};
+        overhead_[id] = size;
+    }
+
   private:
     template <typename TKey, typename TEntry>
     void WorstTwoWithThreshold(const LogHashtable<TKey, TEntry>& table, size_t threshold,
@@ -559,4 +582,7 @@
     const char* UidToNameLocked(uid_t uid) const REQUIRES(lock_);
 
     mutable std::mutex lock_;
+    bool track_total_size_;
+
+    std::optional<size_t> overhead_[LOG_ID_MAX] GUARDED_BY(lock_);
 };
diff --git a/logd/LogTags.cpp b/logd/LogTags.cpp
index 8e18f9d..1b7107f 100644
--- a/logd/LogTags.cpp
+++ b/logd/LogTags.cpp
@@ -276,7 +276,9 @@
             cp++;
         }
     } else if (warn) {
+#ifdef __ANDROID__
         LOG(ERROR) << "Cannot read " << filename;
+#endif
     }
 }
 
diff --git a/logd/LogWhiteBlackList.cpp b/logd/LogWhiteBlackList.cpp
deleted file mode 100644
index 88a3bdc..0000000
--- a/logd/LogWhiteBlackList.cpp
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2014 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 <ctype.h>
-
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-
-#include "LogWhiteBlackList.h"
-
-Prune::Prune(uid_t uid, pid_t pid) : mUid(uid), mPid(pid) {
-}
-
-int Prune::cmp(uid_t uid, pid_t pid) const {
-    if ((mUid == uid_all) || (mUid == uid)) {
-        if (mPid == pid_all) {
-            return 0;
-        }
-        return pid - mPid;
-    }
-    return uid - mUid;
-}
-
-std::string Prune::format() {
-    if (mUid != uid_all) {
-        if (mPid != pid_all) {
-            return android::base::StringPrintf("%u/%u", mUid, mPid);
-        }
-        return android::base::StringPrintf("%u", mUid);
-    }
-    if (mPid != pid_all) {
-        return android::base::StringPrintf("/%u", mPid);
-    }
-    // NB: mPid == pid_all can not happen if mUid == uid_all
-    return std::string("/");
-}
-
-PruneList::PruneList() {
-    init(nullptr);
-}
-
-PruneList::~PruneList() {
-    PruneCollection::iterator it;
-    for (it = mNice.begin(); it != mNice.end();) {
-        it = mNice.erase(it);
-    }
-    for (it = mNaughty.begin(); it != mNaughty.end();) {
-        it = mNaughty.erase(it);
-    }
-}
-
-int PruneList::init(const char* str) {
-    mWorstUidEnabled = true;
-    mWorstPidOfSystemEnabled = true;
-    PruneCollection::iterator it;
-    for (it = mNice.begin(); it != mNice.end();) {
-        it = mNice.erase(it);
-    }
-    for (it = mNaughty.begin(); it != mNaughty.end();) {
-        it = mNaughty.erase(it);
-    }
-
-    // default here means take ro.logd.filter, persist.logd.filter then
-    // internal default in that order.
-    if (str && !strcmp(str, "default")) {
-        str = nullptr;
-    }
-    if (str && !strcmp(str, "disable")) {
-        str = "";
-    }
-
-    std::string filter;
-
-    if (str) {
-        filter = str;
-    } else {
-        filter = android::base::GetProperty("ro.logd.filter", "default");
-        auto persist_filter = android::base::GetProperty("persist.logd.filter", "default");
-        // default here means take ro.logd.filter
-        if (persist_filter != "default") {
-            filter = persist_filter;
-        }
-    }
-
-    // default here means take internal default.
-    if (filter == "default") {
-        // See README.property for description of filter format
-        filter = "~! ~1000/!";
-    }
-    if (filter == "disable") {
-        filter = "";
-    }
-
-    mWorstUidEnabled = false;
-    mWorstPidOfSystemEnabled = false;
-
-    for (str = filter.c_str(); *str; ++str) {
-        if (isspace(*str)) {
-            continue;
-        }
-
-        PruneCollection* list;
-        if ((*str == '~') || (*str == '!')) {  // ~ supported, ! undocumented
-            ++str;
-            // special case, translates to worst UID at priority in blacklist
-            if (*str == '!') {
-                mWorstUidEnabled = true;
-                ++str;
-                if (!*str) {
-                    break;
-                }
-                if (!isspace(*str)) {
-                    return 1;
-                }
-                continue;
-            }
-            // special case, translated to worst PID of System at priority
-            static const char worstPid[] = "1000/!";
-            if (!strncmp(str, worstPid, sizeof(worstPid) - 1)) {
-                mWorstPidOfSystemEnabled = true;
-                str += sizeof(worstPid) - 1;
-                if (!*str) {
-                    break;
-                }
-                if (!isspace(*str)) {
-                    return 1;
-                }
-                continue;
-            }
-            if (!*str) {
-                return 1;
-            }
-            list = &mNaughty;
-        } else {
-            list = &mNice;
-        }
-
-        uid_t uid = Prune::uid_all;
-        if (isdigit(*str)) {
-            uid = 0;
-            do {
-                uid = uid * 10 + *str++ - '0';
-            } while (isdigit(*str));
-        }
-
-        pid_t pid = Prune::pid_all;
-        if (*str == '/') {
-            ++str;
-            if (isdigit(*str)) {
-                pid = 0;
-                do {
-                    pid = pid * 10 + *str++ - '0';
-                } while (isdigit(*str));
-            }
-        }
-
-        if ((uid == Prune::uid_all) && (pid == Prune::pid_all)) {
-            return 1;
-        }
-
-        if (*str && !isspace(*str)) {
-            return 1;
-        }
-
-        // insert sequentially into list
-        PruneCollection::iterator it = list->begin();
-        while (it != list->end()) {
-            Prune& p = *it;
-            int m = uid - p.mUid;
-            if (m == 0) {
-                if (p.mPid == p.pid_all) {
-                    break;
-                }
-                if ((pid == p.pid_all) && (p.mPid != p.pid_all)) {
-                    it = list->erase(it);
-                    continue;
-                }
-                m = pid - p.mPid;
-            }
-            if (m <= 0) {
-                if (m < 0) {
-                    list->insert(it, Prune(uid, pid));
-                }
-                break;
-            }
-            ++it;
-        }
-        if (it == list->end()) {
-            list->push_back(Prune(uid, pid));
-        }
-        if (!*str) {
-            break;
-        }
-    }
-
-    return 0;
-}
-
-std::string PruneList::format() {
-    static const char nice_format[] = " %s";
-    const char* fmt = nice_format + 1;
-
-    std::string string;
-
-    if (mWorstUidEnabled) {
-        string = "~!";
-        fmt = nice_format;
-        if (mWorstPidOfSystemEnabled) {
-            string += " ~1000/!";
-        }
-    }
-
-    PruneCollection::iterator it;
-
-    for (it = mNice.begin(); it != mNice.end(); ++it) {
-        string += android::base::StringPrintf(fmt, (*it).format().c_str());
-        fmt = nice_format;
-    }
-
-    static const char naughty_format[] = " ~%s";
-    fmt = naughty_format + (*fmt != ' ');
-    for (it = mNaughty.begin(); it != mNaughty.end(); ++it) {
-        string += android::base::StringPrintf(fmt, (*it).format().c_str());
-        fmt = naughty_format;
-    }
-
-    return string;
-}
-
-// ToDo: Lists are in sorted order, Prune->cmp() returns + or -
-// If there is scaling issues, resort to a better algorithm than linear
-// based on these assumptions.
-
-bool PruneList::naughty(LogBufferElement* element) {
-    PruneCollection::iterator it;
-    for (it = mNaughty.begin(); it != mNaughty.end(); ++it) {
-        if (!(*it).cmp(element)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-bool PruneList::nice(LogBufferElement* element) {
-    PruneCollection::iterator it;
-    for (it = mNice.begin(); it != mNice.end(); ++it) {
-        if (!(*it).cmp(element)) {
-            return true;
-        }
-    }
-    return false;
-}
diff --git a/logd/LogWhiteBlackList.h b/logd/LogWhiteBlackList.h
deleted file mode 100644
index 447ab87..0000000
--- a/logd/LogWhiteBlackList.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2014 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.h>
-#include <list>
-
-#include "LogBufferElement.h"
-
-class Prune {
-    friend class PruneList;
-
-    const uid_t mUid;
-    const pid_t mPid;
-    int cmp(uid_t uid, pid_t pid) const;
-
-   public:
-    static const uid_t uid_all = (uid_t)-1;
-    static const pid_t pid_all = (pid_t)-1;
-
-    Prune(uid_t uid, pid_t pid);
-
-    uid_t getUid() const {
-        return mUid;
-    }
-    pid_t getPid() const {
-        return mPid;
-    }
-
-    int cmp(LogBufferElement* e) const { return cmp(e->uid(), e->pid()); }
-
-    std::string format();
-};
-
-typedef std::list<Prune> PruneCollection;
-
-class PruneList {
-    PruneCollection mNaughty;
-    PruneCollection mNice;
-    bool mWorstUidEnabled;
-    bool mWorstPidOfSystemEnabled;
-
-   public:
-    PruneList();
-    ~PruneList();
-
-    int init(const char* str);
-
-    bool naughty(LogBufferElement* element);
-    bool naughty(void) {
-        return !mNaughty.empty();
-    }
-    bool nice(LogBufferElement* element);
-    bool nice(void) {
-        return !mNice.empty();
-    }
-    bool worstUidEnabled() const {
-        return mWorstUidEnabled;
-    }
-    bool worstPidOfSystemEnabled() const {
-        return mWorstPidOfSystemEnabled;
-    }
-
-    std::string format();
-};
diff --git a/logd/PruneList.cpp b/logd/PruneList.cpp
new file mode 100644
index 0000000..c3859f3
--- /dev/null
+++ b/logd/PruneList.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2014 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 "PruneList.h"
+
+#include <ctype.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+bool Prune::Matches(LogBufferElement* element) const {
+    return (uid_ == UID_ALL || uid_ == element->uid()) &&
+           (pid_ == PID_ALL || pid_ == element->pid());
+}
+
+std::string Prune::Format() const {
+    if (uid_ != UID_ALL) {
+        if (pid_ != PID_ALL) {
+            return android::base::StringPrintf("%u/%u", uid_, pid_);
+        }
+        return android::base::StringPrintf("%u", uid_);
+    }
+    if (pid_ != PID_ALL) {
+        return android::base::StringPrintf("/%u", pid_);
+    }
+    // NB: pid_ == PID_ALL can not happen if uid_ == UID_ALL
+    return std::string("/");
+}
+
+PruneList::PruneList() {
+    Init(nullptr);
+}
+
+bool PruneList::Init(const char* str) {
+    high_priority_prune_.clear();
+    low_priority_prune_.clear();
+
+    // default here means take ro.logd.filter, persist.logd.filter then internal default in order.
+    if (str && !strcmp(str, "default")) {
+        str = nullptr;
+    }
+    if (str && !strcmp(str, "disable")) {
+        str = "";
+    }
+
+    std::string filter;
+
+    if (str) {
+        filter = str;
+    } else {
+        filter = android::base::GetProperty("ro.logd.filter", "default");
+        auto persist_filter = android::base::GetProperty("persist.logd.filter", "default");
+        // default here means take ro.logd.filter
+        if (persist_filter != "default") {
+            filter = persist_filter;
+        }
+    }
+
+    // default here means take internal default.
+    if (filter == "default") {
+        filter = "~! ~1000/!";
+    }
+    if (filter == "disable") {
+        filter = "";
+    }
+
+    worst_uid_enabled_ = false;
+    worst_pid_of_system_enabled_ = false;
+
+    for (str = filter.c_str(); *str; ++str) {
+        if (isspace(*str)) {
+            continue;
+        }
+
+        std::list<Prune>* list;
+        if (*str == '~' || *str == '!') {  // ~ supported, ! undocumented
+            ++str;
+            // special case, prune the worst UID of those using at least 1/8th of the buffer.
+            if (*str == '!') {
+                worst_uid_enabled_ = true;
+                ++str;
+                if (!*str) {
+                    break;
+                }
+                if (!isspace(*str)) {
+                    LOG(ERROR) << "Nothing expected after '~!', but found '" << str << "'";
+                    return false;
+                }
+                continue;
+            }
+            // special case, translated to worst PID of System at priority
+            static const char WORST_SYSTEM_PID[] = "1000/!";
+            if (!strncmp(str, WORST_SYSTEM_PID, sizeof(WORST_SYSTEM_PID) - 1)) {
+                worst_pid_of_system_enabled_ = true;
+                str += sizeof(WORST_SYSTEM_PID) - 1;
+                if (!*str) {
+                    break;
+                }
+                if (!isspace(*str)) {
+                    LOG(ERROR) << "Nothing expected after '~1000/!', but found '" << str << "'";
+                    return false;
+                }
+                continue;
+            }
+            if (!*str) {
+                LOG(ERROR) << "Expected UID or PID after '~', but found nothing";
+                return false;
+            }
+            list = &high_priority_prune_;
+        } else {
+            list = &low_priority_prune_;
+        }
+
+        uid_t uid = Prune::UID_ALL;
+        if (isdigit(*str)) {
+            uid = 0;
+            do {
+                uid = uid * 10 + *str++ - '0';
+            } while (isdigit(*str));
+        }
+
+        pid_t pid = Prune::PID_ALL;
+        if (*str == '/') {
+            ++str;
+            if (isdigit(*str)) {
+                pid = 0;
+                do {
+                    pid = pid * 10 + *str++ - '0';
+                } while (isdigit(*str));
+            }
+        }
+
+        if (uid == Prune::UID_ALL && pid == Prune::PID_ALL) {
+            LOG(ERROR) << "Expected UID/PID combination, but found none";
+            return false;
+        }
+
+        if (*str && !isspace(*str)) {
+            LOG(ERROR) << "Nothing expected after UID/PID combination, but found '" << str << "'";
+            return false;
+        }
+
+        list->emplace_back(uid, pid);
+        if (!*str) {
+            break;
+        }
+    }
+
+    return true;
+}
+
+std::string PruneList::Format() const {
+    std::vector<std::string> prune_rules;
+
+    if (worst_uid_enabled_) {
+        prune_rules.emplace_back("~!");
+    }
+    if (worst_pid_of_system_enabled_) {
+        prune_rules.emplace_back("~1000/!");
+    }
+    for (const auto& rule : low_priority_prune_) {
+        prune_rules.emplace_back(rule.Format());
+    }
+    for (const auto& rule : high_priority_prune_) {
+        prune_rules.emplace_back("~" + rule.Format());
+    }
+    return android::base::Join(prune_rules, " ");
+}
+
+bool PruneList::IsHighPriority(LogBufferElement* element) const {
+    for (const auto& rule : high_priority_prune_) {
+        if (rule.Matches(element)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool PruneList::IsLowPriority(LogBufferElement* element) const {
+    for (const auto& rule : low_priority_prune_) {
+        if (rule.Matches(element)) {
+            return true;
+        }
+    }
+    return false;
+}
diff --git a/logd/PruneList.h b/logd/PruneList.h
new file mode 100644
index 0000000..94de5c5
--- /dev/null
+++ b/logd/PruneList.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 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.h>
+#include <list>
+
+#include "LogBufferElement.h"
+
+class Prune {
+  public:
+    static const uid_t UID_ALL = (uid_t)-1;
+    static const pid_t PID_ALL = (pid_t)-1;
+
+    Prune(uid_t uid, pid_t pid) : uid_(uid), pid_(pid) {}
+
+    bool Matches(LogBufferElement* element) const;
+    std::string Format() const;
+
+    uid_t uid() const { return uid_; }
+    pid_t pid() const { return pid_; }
+
+  private:
+    const uid_t uid_;
+    const pid_t pid_;
+};
+
+class PruneList {
+  public:
+    PruneList();
+
+    bool Init(const char* str);
+    std::string Format() const;
+
+    bool IsHighPriority(LogBufferElement* element) const;
+    bool IsLowPriority(LogBufferElement* element) const;
+
+    bool HasHighPriorityPruneRules() const { return !high_priority_prune_.empty(); }
+    bool HasLowPriorityPruneRules() const { return !low_priority_prune_.empty(); }
+
+    bool worst_uid_enabled() const { return worst_uid_enabled_; }
+    bool worst_pid_of_system_enabled() const { return worst_pid_of_system_enabled_; }
+
+  private:
+    std::list<Prune> high_priority_prune_;
+    std::list<Prune> low_priority_prune_;
+
+    bool worst_uid_enabled_;
+    bool worst_pid_of_system_enabled_;
+};
diff --git a/logd/README.property b/logd/README.property
index 6a9369a..1d7d990 100644
--- a/logd/README.property
+++ b/logd/README.property
@@ -52,6 +52,9 @@
 log.tag.<tag>             string persist The <tag> specific logging level.
 persist.log.tag.<tag>      string build  default for log.tag.<tag>
 
+logd.buffer_type           string (empty) Set the log buffer type.  Current choices are 'simple',
+                                          'chatty', or 'serialized'.  Defaults to 'chatty' if empty.
+
 NB:
 - auto - managed by /init
 - bool+ - "true", "false" and comma separated list of "eng" (forced false if
@@ -65,8 +68,8 @@
 - number - support multipliers (K or M) for convenience. Range is limited
   to between 64K and 256M for log buffer sizes. Individual log buffer ids
   such as main, system, ... override global default.
-- Pruning filter is of form of a space-separated list of [~][UID][/PID]
-  references, where '~' prefix means to blacklist otherwise whitelist. For
-  blacklisting, UID or PID may be a '!' to instead reference the chattiest
-  client, with the restriction that the PID must be in the UID group 1000
-  (system or AID_SYSTEM).
+- Pruning filter rules are specified as UID, UID/PID or /PID. A '~' prefix indicates that elements
+  matching the rule should be pruned with higher priority otherwise they're pruned with lower
+  priority. All other pruning activity is oldest first. Special case ~! represents an automatic
+  pruning for the noisiest UID as determined by the current statistics.  Special case ~1000/!
+  represents pruning of the worst PID within AID_SYSTEM when AID_SYSTEM is the noisiest UID.
diff --git a/logd/README.replay.md b/logd/README.replay.md
new file mode 100644
index 0000000..5f7ec9e
--- /dev/null
+++ b/logd/README.replay.md
@@ -0,0 +1,46 @@
+logd can record and replay log messages for offline analysis.
+
+Recording Messages
+------------------
+
+logd has a `RecordingLogBuffer` buffer that records messages to /data/misc/logd/recorded-messages.
+It stores messages in memory until that file is accessible, in order to capture all messages since
+the beginning of boot.  It is only meant for logging developers to use and must be manually enabled
+in by adding `RecordingLogBuffer.cpp` to `Android.bp` and setting
+`log_buffer = new SimpleLogBuffer(&reader_list, &log_tags, &log_statistics);` in `main.cpp`.
+
+Recording messages may delay the Log() function from completing and it is highly recommended to make
+the logd socket in `liblog` blocking, by removing `SOCK_NONBLOCK` from the `socket()` call in
+`liblog/logd_writer.cpp`.
+
+Replaying Messages
+------------------
+
+Recorded messages can be replayed offline with the `replay_messages` tool.  It runs on host and
+device and supports the following options:
+
+1. `interesting` - this prints 'interesting' statistics for each of the log buffer types (simple,
+   chatty, serialized).  The statistics are:
+    1. Log Entry Count
+    2. Size (the uncompressed size of the log messages in bytes)
+    3. Overhead (the total cost of the log messages in memory in bytes)
+    4. Range (the range of time that the logs cover in seconds)
+2. `memory_usage BUFFER_TYPE` - this prints the memory usage (sum of private dirty pages of the
+  `replay_messages` process).  Note that the input file is mmap()'ed as RO/Shared so it does not
+  appear in these dirty pages, and a baseline is taken before allocating the log buffers, so only
+  their contributions are measured.  The tool outputs the memory usage every 100,000 messages.
+3. `latency BUFFER_TYPE` - this prints statistics of the latency of the Log() function for the given
+  buffer type.  It specifically prints the 1st, 2nd, and 3rd quartiles; the 95th, 99th, and 99.99th
+  percentiles; and the maximum latency.
+4. `print_logs BUFFER_TYPE [buffers] [print_point]` - this prints the logs as processed by the given
+  buffer_type from the buffers specified by `buffers` starting after the number of logs specified by
+  `print_point` have been logged.  This acts as if a user called `logcat` immediately after the
+  specified logs have been logged, which is particularly useful since it will show the chatty
+  pruning messages at that point.  It additionally prints the statistics from `logcat -S` after the
+  logs.
+  `buffers` is a comma separated list of the numeric buffer id values from `<android/log.h>`.  For
+  example, `0,1,3` represents the main, radio, and system buffers.  It can can also be `all`.
+  `print_point` is an positive integer.  If it is unspecified, logs are printed after the entire
+  input file is consumed.
+5. `nothing BUFFER_TYPE` - this does nothing other than read the input file and call Log() for the
+  given buffer type.  This is used for profiling CPU usage of strictly the log buffer.
diff --git a/logd/RecordedLogMessage.h b/logd/RecordedLogMessage.h
new file mode 100644
index 0000000..f18c422
--- /dev/null
+++ b/logd/RecordedLogMessage.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 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 <inttypes.h>
+
+#include <log/log_time.h>
+
+struct __attribute__((packed)) RecordedLogMessage {
+    uint32_t uid;
+    uint32_t pid;
+    uint32_t tid;
+    log_time realtime;
+    uint16_t msg_len;
+    uint8_t log_id;
+};
diff --git a/logd/RecordingLogBuffer.cpp b/logd/RecordingLogBuffer.cpp
new file mode 100644
index 0000000..f5991f3
--- /dev/null
+++ b/logd/RecordingLogBuffer.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 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 "RecordingLogBuffer.h"
+
+#include <android-base/file.h>
+
+static void WriteLogMessage(int fd, const RecordedLogMessage& meta, const std::string& msg) {
+    android::base::WriteFully(fd, &meta, sizeof(meta));
+    android::base::WriteFully(fd, msg.c_str(), meta.msg_len);
+}
+
+void RecordingLogBuffer::RecordLogMessage(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid,
+                                          pid_t tid, const char* msg, uint16_t len) {
+    auto lock = std::lock_guard{lock_};
+    if (len > LOGGER_ENTRY_MAX_PAYLOAD) {
+        len = LOGGER_ENTRY_MAX_PAYLOAD;
+    }
+
+    RecordedLogMessage recorded_log_message = {
+            .uid = uid,
+            .pid = static_cast<uint32_t>(pid),
+            .tid = static_cast<uint32_t>(tid),
+            .realtime = realtime,
+            .msg_len = len,
+            .log_id = static_cast<uint8_t>(log_id),
+    };
+
+    if (!fd_.ok()) {
+        fd_.reset(open("/data/misc/logd/recorded-messages",
+                       O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0666));
+        if (!fd_.ok()) {
+            since_boot_messages_.emplace_back(recorded_log_message, std::string(msg, len));
+            return;
+        } else {
+            for (const auto& [meta, msg] : since_boot_messages_) {
+                WriteLogMessage(fd_.get(), meta, msg);
+            }
+        }
+    }
+
+    WriteLogMessage(fd_.get(), recorded_log_message, std::string(msg, len));
+}
+
+int RecordingLogBuffer::Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
+                            const char* msg, uint16_t len) {
+    RecordLogMessage(log_id, realtime, uid, pid, tid, msg, len);
+    return SimpleLogBuffer::Log(log_id, realtime, uid, pid, tid, msg, len);
+}
\ No newline at end of file
diff --git a/logd/RecordingLogBuffer.h b/logd/RecordingLogBuffer.h
new file mode 100644
index 0000000..49a0aba
--- /dev/null
+++ b/logd/RecordingLogBuffer.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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 "SimpleLogBuffer.h"
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+#include "RecordedLogMessage.h"
+
+class RecordingLogBuffer : public SimpleLogBuffer {
+  public:
+    RecordingLogBuffer(LogReaderList* reader_list, LogTags* tags, LogStatistics* stats)
+        : SimpleLogBuffer(reader_list, tags, stats) {}
+
+    int Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char* msg,
+            uint16_t len) override;
+
+  private:
+    void RecordLogMessage(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
+                          const char* msg, uint16_t len);
+
+    std::vector<std::pair<RecordedLogMessage, std::string>> since_boot_messages_;
+    android::base::unique_fd fd_;
+};
diff --git a/logd/ReplayMessages.cpp b/logd/ReplayMessages.cpp
new file mode 100644
index 0000000..73b0bd0
--- /dev/null
+++ b/logd/ReplayMessages.cpp
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2020 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 <chrono>
+#include <map>
+
+#include <android-base/file.h>
+#include <android-base/mapped_file.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <android/log.h>
+#include <log/log_time.h>
+#include <log/logprint.h>
+
+#include "ChattyLogBuffer.h"
+#include "LogBuffer.h"
+#include "LogStatistics.h"
+#include "RecordedLogMessage.h"
+#include "SerializedLogBuffer.h"
+#include "SimpleLogBuffer.h"
+
+using android::base::MappedFile;
+using android::base::ParseInt;
+using android::base::ParseUint;
+using android::base::Split;
+
+#ifndef __ANDROID__
+// This is hard coded to 1MB on host.  On device use persist.logd.size to configure.
+unsigned long __android_logger_get_buffer_size(log_id_t) {
+    return 1 * 1024 * 1024;
+}
+
+bool __android_logger_valid_buffer_size(unsigned long) {
+    return true;
+}
+#endif
+
+char* android::uidToName(uid_t) {
+    return nullptr;
+}
+
+static size_t GetPrivateDirty() {
+    // Allocate once and hope that we don't need to reallocate >40000, to prevent heap fragmentation
+    static std::string smaps(40000, '\0');
+    android::base::ReadFileToString("/proc/self/smaps", &smaps);
+
+    size_t result = 0;
+    size_t base = 0;
+    size_t found;
+    while (true) {
+        found = smaps.find("Private_Dirty:", base);
+        if (found == smaps.npos) break;
+
+        found += sizeof("Private_Dirty:");
+
+        result += atoi(&smaps[found]);
+
+        base = found + 1;
+    }
+
+    return result;
+}
+
+static AndroidLogFormat* GetLogFormat() {
+    static AndroidLogFormat* format = [] {
+        auto* format = android_log_format_new();
+        android_log_setPrintFormat(format, android_log_formatFromString("threadtime"));
+        android_log_setPrintFormat(format, android_log_formatFromString("uid"));
+        return format;
+    }();
+    return format;
+}
+
+static void PrintMessage(struct log_msg* buf) {
+    bool is_binary =
+            buf->id() == LOG_ID_EVENTS || buf->id() == LOG_ID_STATS || buf->id() == LOG_ID_SECURITY;
+
+    AndroidLogEntry entry;
+    int err;
+    if (is_binary) {
+        char binaryMsgBuf[1024];
+        err = android_log_processBinaryLogBuffer(&buf->entry, &entry, nullptr, binaryMsgBuf,
+                                                 sizeof(binaryMsgBuf));
+    } else {
+        err = android_log_processLogBuffer(&buf->entry, &entry);
+    }
+    if (err < 0) {
+        fprintf(stderr, "Error parsing log message\n");
+    }
+
+    android_log_printLogLine(GetLogFormat(), STDOUT_FILENO, &entry);
+}
+
+static log_time GetFirstTimeStamp(const MappedFile& recorded_messages) {
+    if (sizeof(RecordedLogMessage) >= recorded_messages.size()) {
+        fprintf(stderr, "At least one log message must be present in the input\n");
+        exit(1);
+    }
+
+    auto* meta = reinterpret_cast<RecordedLogMessage*>(recorded_messages.data());
+    return meta->realtime;
+}
+
+class StdoutWriter : public LogWriter {
+  public:
+    StdoutWriter() : LogWriter(0, true) {}
+    bool Write(const logger_entry& entry, const char* message) override {
+        struct log_msg log_msg;
+        log_msg.entry = entry;
+        if (log_msg.entry.len > LOGGER_ENTRY_MAX_PAYLOAD) {
+            fprintf(stderr, "payload too large %" PRIu16, log_msg.entry.len);
+            exit(1);
+        }
+        memcpy(log_msg.msg(), message, log_msg.entry.len);
+
+        PrintMessage(&log_msg);
+
+        return true;
+    }
+
+    std::string name() const override { return "stdout writer"; }
+};
+
+class Operation {
+  public:
+    virtual ~Operation() {}
+
+    virtual void Begin() {}
+    virtual void Log(const RecordedLogMessage& meta, const char* msg) = 0;
+    virtual void End() {}
+};
+
+class PrintInteresting : public Operation {
+  public:
+    PrintInteresting(log_time first_log_timestamp)
+        : stats_simple_{false, false, first_log_timestamp},
+          stats_chatty_{false, false, first_log_timestamp},
+          stats_serialized_{false, true, first_log_timestamp} {}
+
+    void Begin() override {
+        printf("message_count,simple_main_lines,simple_radio_lines,simple_events_lines,simple_"
+               "system_lines,simple_crash_lines,simple_stats_lines,simple_security_lines,simple_"
+               "kernel_lines,simple_main_size,simple_radio_size,simple_events_size,simple_system_"
+               "size,simple_crash_size,simple_stats_size,simple_security_size,simple_kernel_size,"
+               "simple_main_overhead,simple_radio_overhead,simple_events_overhead,simple_system_"
+               "overhead,simple_crash_overhead,simple_stats_overhead,simple_security_overhead,"
+               "simple_kernel_overhead,simple_main_range,simple_radio_range,simple_events_range,"
+               "simple_system_range,simple_crash_range,simple_stats_range,simple_security_range,"
+               "simple_kernel_range,chatty_main_lines,chatty_radio_lines,chatty_events_lines,"
+               "chatty_system_lines,chatty_crash_lines,chatty_stats_lines,chatty_security_lines,"
+               "chatty_"
+               "kernel_lines,chatty_main_size,chatty_radio_size,chatty_events_size,chatty_system_"
+               "size,chatty_crash_size,chatty_stats_size,chatty_security_size,chatty_kernel_size,"
+               "chatty_main_overhead,chatty_radio_overhead,chatty_events_overhead,chatty_system_"
+               "overhead,chatty_crash_overhead,chatty_stats_overhead,chatty_security_overhead,"
+               "chatty_kernel_overhead,chatty_main_range,chatty_radio_range,chatty_events_range,"
+               "chatty_system_range,chatty_crash_range,chatty_stats_range,chatty_security_range,"
+               "chatty_kernel_range,serialized_main_lines,serialized_radio_lines,serialized_events_"
+               "lines,serialized_"
+               "system_lines,serialized_crash_lines,serialized_stats_lines,serialized_security_"
+               "lines,serialized_"
+               "kernel_lines,serialized_main_size,serialized_radio_size,serialized_events_size,"
+               "serialized_system_"
+               "size,serialized_crash_size,serialized_stats_size,serialized_security_size,"
+               "serialized_kernel_size,"
+               "serialized_main_overhead,serialized_radio_overhead,serialized_events_overhead,"
+               "serialized_system_"
+               "overhead,serialized_crash_overhead,serialized_stats_overhead,serialized_security_"
+               "overhead,"
+               "serialized_kernel_overhead,serialized_main_range,serialized_radio_range,serialized_"
+               "events_range,"
+               "serialized_system_range,serialized_crash_range,serialized_stats_range,serialized_"
+               "security_range,"
+               "serialized_kernel_range\n");
+    }
+
+    void Log(const RecordedLogMessage& meta, const char* msg) override {
+        simple_log_buffer_.Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid,
+                               meta.pid, meta.tid, msg, meta.msg_len);
+
+        chatty_log_buffer_.Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid,
+                               meta.pid, meta.tid, msg, meta.msg_len);
+
+        serialized_log_buffer_.Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid,
+                                   meta.pid, meta.tid, msg, meta.msg_len);
+
+        if (num_message_ % 10000 == 0) {
+            printf("%" PRIu64 ",%s,%s,%s\n", num_message_,
+                   stats_simple_.ReportInteresting().c_str(),
+                   stats_chatty_.ReportInteresting().c_str(),
+                   stats_serialized_.ReportInteresting().c_str());
+        }
+
+        num_message_++;
+    }
+
+  private:
+    uint64_t num_message_ = 1;
+
+    LogReaderList reader_list_;
+    LogTags tags_;
+    PruneList prune_list_;
+
+    LogStatistics stats_simple_;
+    SimpleLogBuffer simple_log_buffer_{&reader_list_, &tags_, &stats_simple_};
+
+    LogStatistics stats_chatty_;
+    ChattyLogBuffer chatty_log_buffer_{&reader_list_, &tags_, &prune_list_, &stats_chatty_};
+
+    LogStatistics stats_serialized_;
+    SerializedLogBuffer serialized_log_buffer_{&reader_list_, &tags_, &stats_serialized_};
+};
+
+class SingleBufferOperation : public Operation {
+  public:
+    SingleBufferOperation(log_time first_log_timestamp, const char* buffer) {
+        if (!strcmp(buffer, "simple")) {
+            stats_.reset(new LogStatistics{false, false, first_log_timestamp});
+            log_buffer_.reset(new SimpleLogBuffer(&reader_list_, &tags_, stats_.get()));
+        } else if (!strcmp(buffer, "chatty")) {
+            stats_.reset(new LogStatistics{false, false, first_log_timestamp});
+            log_buffer_.reset(
+                    new ChattyLogBuffer(&reader_list_, &tags_, &prune_list_, stats_.get()));
+        } else if (!strcmp(buffer, "serialized")) {
+            stats_.reset(new LogStatistics{false, true, first_log_timestamp});
+            log_buffer_.reset(new SerializedLogBuffer(&reader_list_, &tags_, stats_.get()));
+        } else {
+            fprintf(stderr, "invalid log buffer type '%s'\n", buffer);
+            abort();
+        }
+    }
+
+    void Log(const RecordedLogMessage& meta, const char* msg) override {
+        PreOperation();
+        log_buffer_->Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid, meta.pid,
+                         meta.tid, msg, meta.msg_len);
+
+        Operation();
+
+        num_message_++;
+    }
+
+    virtual void PreOperation() {}
+    virtual void Operation() {}
+
+  protected:
+    uint64_t num_message_ = 1;
+
+    LogReaderList reader_list_;
+    LogTags tags_;
+    PruneList prune_list_;
+
+    std::unique_ptr<LogStatistics> stats_;
+    std::unique_ptr<LogBuffer> log_buffer_;
+};
+
+class PrintMemory : public SingleBufferOperation {
+  public:
+    PrintMemory(log_time first_log_timestamp, const char* buffer)
+        : SingleBufferOperation(first_log_timestamp, buffer) {}
+
+    void Operation() override {
+        if (num_message_ % 100000 == 0) {
+            printf("%" PRIu64 ",%s\n", num_message_,
+                   std::to_string(GetPrivateDirty() - baseline_memory_).c_str());
+        }
+    }
+
+  private:
+    size_t baseline_memory_ = GetPrivateDirty();
+};
+
+class PrintLogs : public SingleBufferOperation {
+  public:
+    PrintLogs(log_time first_log_timestamp, const char* buffer, const char* buffers,
+              const char* print_point)
+        : SingleBufferOperation(first_log_timestamp, buffer) {
+        if (buffers != nullptr) {
+            if (strcmp(buffers, "all") != 0) {
+                std::vector<int> buffer_ids;
+                auto string_ids = Split(buffers, ",");
+                for (const auto& string_id : string_ids) {
+                    int result;
+                    if (!ParseInt(string_id, &result, 0, 7)) {
+                        fprintf(stderr, "Could not parse buffer_id '%s'\n", string_id.c_str());
+                        exit(1);
+                    }
+                    buffer_ids.emplace_back(result);
+                }
+                mask_ = 0;
+                for (const auto& buffer_id : buffer_ids) {
+                    mask_ |= 1 << buffer_id;
+                }
+            }
+        }
+        if (print_point != nullptr) {
+            uint64_t result = 0;
+            if (!ParseUint(print_point, &result)) {
+                fprintf(stderr, "Could not parse print point '%s'\n", print_point);
+                exit(1);
+            }
+            print_point_ = result;
+        }
+    }
+
+    void Operation() override {
+        if (print_point_ && num_message_ >= *print_point_) {
+            End();
+            exit(0);
+        }
+    }
+
+    void End() {
+        std::unique_ptr<LogWriter> test_writer(new StdoutWriter());
+        std::unique_ptr<FlushToState> flush_to_state = log_buffer_->CreateFlushToState(1, mask_);
+        log_buffer_->FlushTo(test_writer.get(), *flush_to_state, nullptr);
+
+        auto stats_string = stats_->Format(0, 0, mask_);
+        printf("%s\n", stats_string.c_str());
+    }
+
+  private:
+    LogMask mask_ = kLogMaskAll;
+    std::optional<uint64_t> print_point_;
+};
+
+class PrintLatency : public SingleBufferOperation {
+  public:
+    PrintLatency(log_time first_log_timestamp, const char* buffer)
+        : SingleBufferOperation(first_log_timestamp, buffer) {}
+
+    void PreOperation() override { operation_start_ = std::chrono::steady_clock::now(); }
+
+    void Operation() override {
+        auto end = std::chrono::steady_clock::now();
+        auto duration = (end - operation_start_).count();
+        durations_.emplace_back(duration);
+    }
+
+    void End() {
+        std::sort(durations_.begin(), durations_.end());
+        auto q1 = durations_.size() / 4;
+        auto q2 = durations_.size() / 2;
+        auto q3 = 3 * durations_.size() / 4;
+
+        auto p95 = 95 * durations_.size() / 100;
+        auto p99 = 99 * durations_.size() / 100;
+        auto p9999 = 9999 * durations_.size() / 10000;
+
+        printf("q1: %lld q2: %lld q3: %lld  p95: %lld p99: %lld p99.99: %lld  max: %lld\n",
+               durations_[q1], durations_[q2], durations_[q3], durations_[p95], durations_[p99],
+               durations_[p9999], durations_.back());
+    }
+
+  private:
+    std::chrono::steady_clock::time_point operation_start_;
+    std::vector<long long> durations_;
+};
+
+int main(int argc, char** argv) {
+    if (argc < 3) {
+        fprintf(stderr, "Usage: %s FILE OPERATION [BUFFER] [OPTIONS]\n", argv[0]);
+        return 1;
+    }
+
+    if (strcmp(argv[2], "interesting") != 0 && argc < 4) {
+        fprintf(stderr, "Operations other than 'interesting' require a BUFFER argument\n");
+        return 1;
+    }
+
+    int recorded_messages_fd = open(argv[1], O_RDONLY);
+    if (recorded_messages_fd == -1) {
+        fprintf(stderr, "Couldn't open input file\n");
+        return 1;
+    }
+    struct stat fd_stat;
+    if (fstat(recorded_messages_fd, &fd_stat) != 0) {
+        fprintf(stderr, "Couldn't fstat input file\n");
+        return 1;
+    }
+    auto recorded_messages = MappedFile::FromFd(recorded_messages_fd, 0,
+                                                static_cast<size_t>(fd_stat.st_size), PROT_READ);
+    if (recorded_messages == nullptr) {
+        fprintf(stderr, "Couldn't mmap input file\n");
+        return 1;
+    }
+
+    // LogStatistics typically uses 'now()' to initialize its log range state, but this doesn't work
+    // when replaying older logs, so we instead give it the timestamp from the first log.
+    log_time first_log_timestamp = GetFirstTimeStamp(*recorded_messages);
+
+    std::unique_ptr<Operation> operation;
+    if (!strcmp(argv[2], "interesting")) {
+        operation.reset(new PrintInteresting(first_log_timestamp));
+    } else if (!strcmp(argv[2], "memory_usage")) {
+        operation.reset(new PrintMemory(first_log_timestamp, argv[3]));
+    } else if (!strcmp(argv[2], "latency")) {
+        operation.reset(new PrintLatency(first_log_timestamp, argv[3]));
+    } else if (!strcmp(argv[2], "print_logs")) {
+        operation.reset(new PrintLogs(first_log_timestamp, argv[3], argc > 4 ? argv[4] : nullptr,
+                                      argc > 5 ? argv[5] : nullptr));
+    } else if (!strcmp(argv[2], "nothing")) {
+        operation.reset(new SingleBufferOperation(first_log_timestamp, argv[3]));
+    } else {
+        fprintf(stderr, "unknown operation '%s'\n", argv[2]);
+        return 1;
+    }
+
+    // LogBuffer::Log() won't log without this on host.
+    __android_log_set_minimum_priority(ANDROID_LOG_VERBOSE);
+    // But we still want to suppress messages <= error to not interrupt the rest of the output.
+    __android_log_set_logger([](const struct __android_log_message* log_message) {
+        if (log_message->priority < ANDROID_LOG_ERROR) {
+            return;
+        }
+        __android_log_stderr_logger(log_message);
+    });
+
+    operation->Begin();
+
+    uint64_t read_position = 0;
+    while (read_position + sizeof(RecordedLogMessage) < recorded_messages->size()) {
+        auto* meta =
+                reinterpret_cast<RecordedLogMessage*>(recorded_messages->data() + read_position);
+        if (read_position + sizeof(RecordedLogMessage) + meta->msg_len >=
+            recorded_messages->size()) {
+            break;
+        }
+        char* msg = recorded_messages->data() + read_position + sizeof(RecordedLogMessage);
+        read_position += sizeof(RecordedLogMessage) + meta->msg_len;
+
+        operation->Log(*meta, msg);
+    }
+
+    operation->End();
+
+    return 0;
+}
diff --git a/logd/SerializedData.h b/logd/SerializedData.h
new file mode 100644
index 0000000..d3d1e18
--- /dev/null
+++ b/logd/SerializedData.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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 <algorithm>
+#include <memory>
+
+// This class is used instead of std::string or std::vector because their clear(), erase(), etc
+// functions don't actually deallocate.  shrink_to_fit() does deallocate but is not guaranteed to
+// work and swapping with an empty string/vector is clunky.
+class SerializedData {
+  public:
+    SerializedData() {}
+    SerializedData(size_t size) : data_(new uint8_t[size]), size_(size) {}
+
+    void Resize(size_t new_size) {
+        if (size_ == 0) {
+            data_.reset(new uint8_t[new_size]);
+            size_ = new_size;
+        } else if (new_size == 0) {
+            data_.reset();
+            size_ = 0;
+        } else if (new_size != size_) {
+            std::unique_ptr<uint8_t[]> new_data(new uint8_t[new_size]);
+            size_t copy_size = std::min(size_, new_size);
+            memcpy(new_data.get(), data_.get(), copy_size);
+            data_.swap(new_data);
+            size_ = new_size;
+        }
+    }
+
+    uint8_t* data() { return data_.get(); }
+    const uint8_t* data() const { return data_.get(); }
+    size_t size() const { return size_; }
+
+  private:
+    std::unique_ptr<uint8_t[]> data_;
+    size_t size_ = 0;
+};
\ No newline at end of file
diff --git a/logd/SerializedFlushToState.cpp b/logd/SerializedFlushToState.cpp
new file mode 100644
index 0000000..b02ccc3
--- /dev/null
+++ b/logd/SerializedFlushToState.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2020 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 "SerializedFlushToState.h"
+
+#include <android-base/logging.h>
+
+SerializedFlushToState::SerializedFlushToState(uint64_t start, LogMask log_mask)
+    : FlushToState(start, log_mask) {
+    log_id_for_each(i) {
+        if (((1 << i) & log_mask) == 0) {
+            continue;
+        }
+        logs_needed_from_next_position_[i] = true;
+    }
+}
+
+SerializedFlushToState::~SerializedFlushToState() {
+    log_id_for_each(i) {
+        if (log_positions_[i]) {
+            log_positions_[i]->buffer_it->DecReaderRefCount();
+        }
+    }
+}
+
+void SerializedFlushToState::CreateLogPosition(log_id_t log_id) {
+    CHECK(!logs_[log_id].empty());
+    LogPosition log_position;
+    auto it = logs_[log_id].begin();
+    while (it != logs_[log_id].end() && start() > it->highest_sequence_number()) {
+        ++it;
+    }
+    if (it == logs_[log_id].end()) {
+        --it;
+    }
+    it->IncReaderRefCount();
+    log_position.buffer_it = it;
+
+    // Find the offset of the first log with sequence number >= start().
+    int read_offset = 0;
+    while (read_offset < it->write_offset()) {
+        const auto* entry = it->log_entry(read_offset);
+        if (entry->sequence() >= start()) {
+            break;
+        }
+        read_offset += entry->total_len();
+    }
+    log_position.read_offset = read_offset;
+
+    log_positions_[log_id].emplace(log_position);
+}
+
+void SerializedFlushToState::AddMinHeapEntry(log_id_t log_id) {
+    auto& buffer_it = log_positions_[log_id]->buffer_it;
+    auto read_offset = log_positions_[log_id]->read_offset;
+
+    // If there is another log to read in this buffer, add it to the min heap.
+    if (read_offset < buffer_it->write_offset()) {
+        auto* entry = buffer_it->log_entry(read_offset);
+        min_heap_.emplace(log_id, entry);
+    } else if (read_offset == buffer_it->write_offset()) {
+        // If there are no more logs to read in this buffer and it's the last buffer, then
+        // set logs_needed_from_next_position_ to wait until more logs get logged.
+        if (buffer_it == std::prev(logs_[log_id].end())) {
+            logs_needed_from_next_position_[log_id] = true;
+        } else {
+            // Otherwise, if there is another buffer piece, move to that and do the same check.
+            buffer_it->DecReaderRefCount();
+            ++buffer_it;
+            buffer_it->IncReaderRefCount();
+            log_positions_[log_id]->read_offset = 0;
+            if (buffer_it->write_offset() == 0) {
+                logs_needed_from_next_position_[log_id] = true;
+            } else {
+                auto* entry = buffer_it->log_entry(0);
+                min_heap_.emplace(log_id, entry);
+            }
+        }
+    } else {
+        // read_offset > buffer_it->write_offset() should never happen.
+        CHECK(false);
+    }
+}
+
+void SerializedFlushToState::CheckForNewLogs() {
+    log_id_for_each(i) {
+        if (!logs_needed_from_next_position_[i]) {
+            continue;
+        }
+        if (!log_positions_[i]) {
+            if (logs_[i].empty()) {
+                continue;
+            }
+            CreateLogPosition(i);
+        }
+        logs_needed_from_next_position_[i] = false;
+        // If it wasn't possible to insert, logs_needed_from_next_position will be set back to true.
+        AddMinHeapEntry(i);
+    }
+}
+
+MinHeapElement SerializedFlushToState::PopNextUnreadLog() {
+    auto top = min_heap_.top();
+    min_heap_.pop();
+
+    auto* entry = top.entry;
+    auto log_id = top.log_id;
+
+    log_positions_[log_id]->read_offset += entry->total_len();
+
+    logs_needed_from_next_position_[log_id] = true;
+
+    return top;
+}
+
+void SerializedFlushToState::Prune(log_id_t log_id,
+                                   const std::list<SerializedLogChunk>::iterator& buffer_it) {
+    // If we don't have a position for this log or if we're not referencing buffer_it, ignore.
+    if (!log_positions_[log_id].has_value() || log_positions_[log_id]->buffer_it != buffer_it) {
+        return;
+    }
+
+    // // Decrease the ref count since we're deleting our reference.
+    buffer_it->DecReaderRefCount();
+
+    // Delete in the reference.
+    log_positions_[log_id].reset();
+
+    // Remove the MinHeapElement referencing log_id, if it exists, but retain the others.
+    std::vector<MinHeapElement> old_elements;
+    while (!min_heap_.empty()) {
+        auto& element = min_heap_.top();
+        if (element.log_id != log_id) {
+            old_elements.emplace_back(element);
+        }
+        min_heap_.pop();
+    }
+    for (auto&& element : old_elements) {
+        min_heap_.emplace(element);
+    }
+
+    // Finally set logs_needed_from_next_position_, so CheckForNewLogs() will re-create the
+    // log_position_ object during the next read.
+    logs_needed_from_next_position_[log_id] = true;
+}
diff --git a/logd/SerializedFlushToState.h b/logd/SerializedFlushToState.h
new file mode 100644
index 0000000..0b20822
--- /dev/null
+++ b/logd/SerializedFlushToState.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 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 <bitset>
+#include <list>
+#include <queue>
+
+#include "LogBuffer.h"
+#include "SerializedLogChunk.h"
+#include "SerializedLogEntry.h"
+
+struct LogPosition {
+    std::list<SerializedLogChunk>::iterator buffer_it;
+    int read_offset;
+};
+
+struct MinHeapElement {
+    MinHeapElement(log_id_t log_id, const SerializedLogEntry* entry)
+        : log_id(log_id), entry(entry) {}
+    log_id_t log_id;
+    const SerializedLogEntry* entry;
+    // The change of comparison operators is intentional, std::priority_queue uses operator<() to
+    // compare but creates a max heap.  Since we want a min heap, we return the opposite result.
+    bool operator<(const MinHeapElement& rhs) const {
+        return entry->sequence() > rhs.entry->sequence();
+    }
+};
+
+// This class tracks the specific point where a FlushTo client has read through the logs.  It
+// directly references the std::list<> iterators from the parent SerializedLogBuffer and the offset
+// into each log chunk where it has last read.  All interactions with this class, except for its
+// construction, must be done with SerializedLogBuffer::lock_ held.  No log chunks that it
+// references may be pruned, which is handled by ensuring prune does not touch any log chunk with
+// highest sequence number greater or equal to start().
+class SerializedFlushToState : public FlushToState {
+  public:
+    // Initializes this state object.  For each log buffer set in log_mask, this sets
+    // logs_needed_from_next_position_.
+    SerializedFlushToState(uint64_t start, LogMask log_mask);
+
+    // Decrease the reference of all referenced logs.  This happens when a reader is disconnected.
+    ~SerializedFlushToState() override;
+
+    // We can't hold SerializedLogBuffer::lock_ in the constructor, so we must initialize logs here.
+    void InitializeLogs(std::list<SerializedLogChunk>* logs) {
+        if (logs_ == nullptr) logs_ = logs;
+    }
+
+    bool HasUnreadLogs() {
+        CheckForNewLogs();
+        return !min_heap_.empty();
+    }
+
+    // Pops the next unread log from the min heap and sets logs_needed_from_next_position_ to
+    // indicate that we're waiting for more logs from the associated log buffer.
+    MinHeapElement PopNextUnreadLog();
+
+    // If the parent log buffer prunes logs, the reference that this class contains may become
+    // invalid, so this must be called first to drop the reference to buffer_it, if any.
+    void Prune(log_id_t log_id, const std::list<SerializedLogChunk>::iterator& buffer_it);
+
+  private:
+    // If there is a log in the serialized log buffer for `log_id` at the read_offset, add it to the
+    // min heap for reading, otherwise set logs_needed_from_next_position_ to indicate that we're
+    // waiting for the next log.
+    void AddMinHeapEntry(log_id_t log_id);
+
+    // Create a LogPosition object for the given log_id by searching through the log chunks for the
+    // first chunk and then first log entry within that chunk that is greater or equal to start().
+    void CreateLogPosition(log_id_t log_id);
+
+    // Checks to see if any log buffers set in logs_needed_from_next_position_ have new logs and
+    // calls AddMinHeapEntry() if so.
+    void CheckForNewLogs();
+
+    std::list<SerializedLogChunk>* logs_ = nullptr;
+    // An optional structure that contains an iterator to the serialized log buffer and offset into
+    // it that this logger should handle next.
+    std::optional<LogPosition> log_positions_[LOG_ID_MAX];
+    // A bit for each log that is set if a given log_id has no logs or if this client has read all
+    // of its logs. In order words: `logs_[i].empty() || (buffer_it == std::prev(logs_.end) &&
+    // next_log_position == logs_write_position_)`.  These will be re-checked in each
+    // loop in case new logs came in.
+    std::bitset<LOG_ID_MAX> logs_needed_from_next_position_ = {};
+    // A min heap that has up to one entry per log buffer, sorted by sequence number, of the next
+    // element that this reader should read.
+    std::priority_queue<MinHeapElement> min_heap_;
+};
diff --git a/logd/SerializedFlushToStateTest.cpp b/logd/SerializedFlushToStateTest.cpp
new file mode 100644
index 0000000..f4515c8
--- /dev/null
+++ b/logd/SerializedFlushToStateTest.cpp
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2020 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 "SerializedFlushToState.h"
+
+#include <map>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+
+using android::base::Join;
+using android::base::StringPrintf;
+
+constexpr size_t kChunkSize = 3 * 4096;
+
+class SerializedFlushToStateTest : public testing::Test {
+  protected:
+    void SetUp() override {
+        // This test spams many unneeded INFO logs, so we suppress them.
+        old_log_severity_ = android::base::SetMinimumLogSeverity(android::base::WARNING);
+    }
+    void TearDown() override { android::base::SetMinimumLogSeverity(old_log_severity_); }
+
+    std::string TestReport(const std::vector<uint64_t>& expected,
+                           const std::vector<uint64_t>& read) {
+        auto sequence_to_log_id = [&](uint64_t sequence) -> int {
+            for (const auto& [log_id, sequences] : sequence_numbers_per_buffer_) {
+                if (std::find(sequences.begin(), sequences.end(), sequence) != sequences.end()) {
+                    return log_id;
+                }
+            }
+            return -1;
+        };
+
+        std::map<int, std::vector<uint64_t>> missing_sequences;
+        std::vector<uint64_t> missing_expected;
+        std::set_difference(expected.begin(), expected.end(), read.begin(), read.end(),
+                            std::back_inserter(missing_expected));
+        for (uint64_t sequence : missing_expected) {
+            int log_id = sequence_to_log_id(sequence);
+            missing_sequences[log_id].emplace_back(sequence);
+        }
+
+        std::map<int, std::vector<uint64_t>> extra_sequences;
+        std::vector<uint64_t> extra_read;
+        std::set_difference(read.begin(), read.end(), expected.begin(), expected.end(),
+                            std::back_inserter(extra_read));
+        for (uint64_t sequence : extra_read) {
+            int log_id = sequence_to_log_id(sequence);
+            extra_sequences[log_id].emplace_back(sequence);
+        }
+
+        std::vector<std::string> errors;
+        for (const auto& [log_id, sequences] : missing_sequences) {
+            errors.emplace_back(
+                    StringPrintf("Log id %d missing %zu sequences", log_id, sequences.size()));
+        }
+
+        for (const auto& [log_id, sequences] : extra_sequences) {
+            errors.emplace_back(
+                    StringPrintf("Log id %d has extra %zu sequences", log_id, sequences.size()));
+        }
+
+        return Join(errors, ", ");
+    }
+
+    // Read sequence numbers in order from SerializedFlushToState for every mask combination and all
+    // sequence numbers from 0 through the highest logged sequence number + 1.
+    // This assumes that all of the logs have already been written.
+    void TestAllReading() {
+        uint64_t max_sequence = sequence_ + 1;
+        uint32_t max_mask = (1 << LOG_ID_MAX) - 1;
+        for (uint64_t sequence = 0; sequence < max_sequence; ++sequence) {
+            for (uint32_t mask = 0; mask < max_mask; ++mask) {
+                auto state = SerializedFlushToState{sequence, mask};
+                state.InitializeLogs(log_chunks_);
+                TestReading(sequence, mask, state);
+            }
+        }
+    }
+
+    // Similar to TestAllReading() except that it doesn't assume any logs are in the buffer, instead
+    // it calls write_logs() in a loop for sequence/mask combination.  It clears log_chunks_ and
+    // sequence_numbers_per_buffer_ between calls, such that only the sequence numbers written in
+    // the previous call to write_logs() are expected.
+    void TestAllReadingWithFutureMessages(const std::function<bool(int)>& write_logs) {
+        uint64_t max_sequence = sequence_ + 1;
+        uint32_t max_mask = (1 << LOG_ID_MAX) - 1;
+        for (uint64_t sequence = 1; sequence < max_sequence; ++sequence) {
+            for (uint32_t mask = 1; mask < max_mask; ++mask) {
+                log_id_for_each(i) { log_chunks_[i].clear(); }
+                auto state = SerializedFlushToState{sequence, mask};
+                state.InitializeLogs(log_chunks_);
+                int loop_count = 0;
+                while (write_logs(loop_count++)) {
+                    TestReading(sequence, mask, state);
+                    sequence_numbers_per_buffer_.clear();
+                }
+            }
+        }
+    }
+
+    void TestReading(uint64_t start, LogMask log_mask, SerializedFlushToState& state) {
+        std::vector<uint64_t> expected_sequence;
+        log_id_for_each(i) {
+            if (((1 << i) & log_mask) == 0) {
+                continue;
+            }
+            for (const auto& sequence : sequence_numbers_per_buffer_[i]) {
+                if (sequence >= start) {
+                    expected_sequence.emplace_back(sequence);
+                }
+            }
+        }
+        std::sort(expected_sequence.begin(), expected_sequence.end());
+
+        std::vector<uint64_t> read_sequence;
+
+        while (state.HasUnreadLogs()) {
+            auto top = state.PopNextUnreadLog();
+            read_sequence.emplace_back(top.entry->sequence());
+        }
+
+        EXPECT_TRUE(std::is_sorted(read_sequence.begin(), read_sequence.end()));
+
+        EXPECT_EQ(expected_sequence.size(), read_sequence.size());
+
+        EXPECT_EQ(expected_sequence, read_sequence)
+                << "start: " << start << " log_mask: " << log_mask << " "
+                << TestReport(expected_sequence, read_sequence);
+    }
+
+    // Add a chunk with the given messages to the a given log buffer.  Keep track of the sequence
+    // numbers for future validation.  Optionally mark the block as having finished writing.
+    void AddChunkWithMessages(bool finish_writing, int buffer,
+                              const std::vector<std::string>& messages) {
+        auto chunk = SerializedLogChunk{kChunkSize};
+        for (const auto& message : messages) {
+            auto sequence = sequence_++;
+            sequence_numbers_per_buffer_[buffer].emplace_back(sequence);
+            ASSERT_TRUE(chunk.CanLog(message.size() + 1));
+            chunk.Log(sequence, log_time(), 0, 1, 1, message.c_str(), message.size() + 1);
+        }
+        if (finish_writing) {
+            chunk.FinishWriting();
+        }
+        log_chunks_[buffer].emplace_back(std::move(chunk));
+    }
+
+    android::base::LogSeverity old_log_severity_;
+    std::map<int, std::vector<uint64_t>> sequence_numbers_per_buffer_;
+    std::list<SerializedLogChunk> log_chunks_[LOG_ID_MAX];
+    uint64_t sequence_ = 1;
+};
+
+// 0: multiple chunks, with variable number of entries, with/without finishing writing
+// 1: 1 chunk with 1 log and finished writing
+// 2: 1 chunk with 1 log and not finished writing
+// 3: 1 chunk with 0 logs and not finished writing
+// 4: 1 chunk with 0 logs and finished writing (impossible, but SerializedFlushToState handles it)
+// 5-7: 0 chunks
+TEST_F(SerializedFlushToStateTest, smoke) {
+    AddChunkWithMessages(true, 0, {"1st", "2nd"});
+    AddChunkWithMessages(true, 1, {"3rd"});
+    AddChunkWithMessages(false, 0, {"4th"});
+    AddChunkWithMessages(true, 0, {"4th", "5th", "more", "even", "more", "go", "here"});
+    AddChunkWithMessages(false, 2, {"6th"});
+    AddChunkWithMessages(true, 0, {"7th"});
+    AddChunkWithMessages(false, 3, {});
+    AddChunkWithMessages(true, 4, {});
+
+    TestAllReading();
+}
+
+TEST_F(SerializedFlushToStateTest, random) {
+    srand(1);
+    for (int count = 0; count < 20; ++count) {
+        unsigned int num_messages = 1 + rand() % 15;
+        auto messages = std::vector<std::string>{num_messages, "same message"};
+
+        bool compress = rand() % 2;
+        int buf = rand() % LOG_ID_MAX;
+
+        AddChunkWithMessages(compress, buf, messages);
+    }
+
+    TestAllReading();
+}
+
+// Same start as smoke, but we selectively write logs to the buffers and ensure they're read.
+TEST_F(SerializedFlushToStateTest, future_writes) {
+    auto write_logs = [&](int loop_count) {
+        switch (loop_count) {
+            case 0:
+                // Initial writes.
+                AddChunkWithMessages(true, 0, {"1st", "2nd"});
+                AddChunkWithMessages(true, 1, {"3rd"});
+                AddChunkWithMessages(false, 0, {"4th"});
+                AddChunkWithMessages(true, 0, {"4th", "5th", "more", "even", "more", "go", "here"});
+                AddChunkWithMessages(false, 2, {"6th"});
+                AddChunkWithMessages(true, 0, {"7th"});
+                AddChunkWithMessages(false, 3, {});
+                AddChunkWithMessages(true, 4, {});
+                break;
+            case 1:
+                // Smoke test, add a simple chunk.
+                AddChunkWithMessages(true, 0, {"1st", "2nd"});
+                break;
+            case 2:
+                // Add chunks to all but one of the logs.
+                AddChunkWithMessages(true, 0, {"1st", "2nd"});
+                AddChunkWithMessages(true, 1, {"1st", "2nd"});
+                AddChunkWithMessages(true, 2, {"1st", "2nd"});
+                AddChunkWithMessages(true, 3, {"1st", "2nd"});
+                AddChunkWithMessages(true, 4, {"1st", "2nd"});
+                AddChunkWithMessages(true, 5, {"1st", "2nd"});
+                AddChunkWithMessages(true, 6, {"1st", "2nd"});
+                break;
+            case 3:
+                // Finally add chunks to all logs.
+                AddChunkWithMessages(true, 0, {"1st", "2nd"});
+                AddChunkWithMessages(true, 1, {"1st", "2nd"});
+                AddChunkWithMessages(true, 2, {"1st", "2nd"});
+                AddChunkWithMessages(true, 3, {"1st", "2nd"});
+                AddChunkWithMessages(true, 4, {"1st", "2nd"});
+                AddChunkWithMessages(true, 5, {"1st", "2nd"});
+                AddChunkWithMessages(true, 6, {"1st", "2nd"});
+                AddChunkWithMessages(true, 7, {"1st", "2nd"});
+                break;
+            default:
+                return false;
+        }
+        return true;
+    };
+
+    TestAllReadingWithFutureMessages(write_logs);
+}
+
+TEST_F(SerializedFlushToStateTest, no_dangling_references) {
+    AddChunkWithMessages(true, 0, {"1st", "2nd"});
+    AddChunkWithMessages(true, 0, {"3rd", "4th"});
+
+    auto state = SerializedFlushToState{1, kLogMaskAll};
+    state.InitializeLogs(log_chunks_);
+
+    ASSERT_EQ(log_chunks_[0].size(), 2U);
+    auto first_chunk = log_chunks_[0].begin();
+    auto second_chunk = std::next(first_chunk);
+
+    ASSERT_TRUE(state.HasUnreadLogs());
+    auto first_log = state.PopNextUnreadLog();
+    EXPECT_STREQ(first_log.entry->msg(), "1st");
+    EXPECT_EQ(first_chunk->reader_ref_count(), 1U);
+    EXPECT_EQ(second_chunk->reader_ref_count(), 0U);
+
+    ASSERT_TRUE(state.HasUnreadLogs());
+    auto second_log = state.PopNextUnreadLog();
+    EXPECT_STREQ(second_log.entry->msg(), "2nd");
+    EXPECT_EQ(first_chunk->reader_ref_count(), 1U);
+    EXPECT_EQ(second_chunk->reader_ref_count(), 0U);
+
+    ASSERT_TRUE(state.HasUnreadLogs());
+    auto third_log = state.PopNextUnreadLog();
+    EXPECT_STREQ(third_log.entry->msg(), "3rd");
+    EXPECT_EQ(first_chunk->reader_ref_count(), 0U);
+    EXPECT_EQ(second_chunk->reader_ref_count(), 1U);
+
+    ASSERT_TRUE(state.HasUnreadLogs());
+    auto fourth_log = state.PopNextUnreadLog();
+    EXPECT_STREQ(fourth_log.entry->msg(), "4th");
+    EXPECT_EQ(first_chunk->reader_ref_count(), 0U);
+    EXPECT_EQ(second_chunk->reader_ref_count(), 1U);
+
+    EXPECT_FALSE(state.HasUnreadLogs());
+}
\ No newline at end of file
diff --git a/logd/SerializedLogBuffer.cpp b/logd/SerializedLogBuffer.cpp
new file mode 100644
index 0000000..972a3f3
--- /dev/null
+++ b/logd/SerializedLogBuffer.cpp
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2020 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 "SerializedLogBuffer.h"
+
+#include <sys/prctl.h>
+
+#include <limits>
+
+#include <android-base/logging.h>
+#include <android-base/scopeguard.h>
+
+#include "LogStatistics.h"
+#include "SerializedFlushToState.h"
+
+SerializedLogBuffer::SerializedLogBuffer(LogReaderList* reader_list, LogTags* tags,
+                                         LogStatistics* stats)
+    : reader_list_(reader_list), tags_(tags), stats_(stats) {
+    Init();
+}
+
+void SerializedLogBuffer::Init() {
+    log_id_for_each(i) {
+        if (SetSize(i, __android_logger_get_buffer_size(i))) {
+            SetSize(i, LOG_BUFFER_MIN_SIZE);
+        }
+    }
+
+    // Release any sleeping reader threads to dump their current content.
+    auto reader_threads_lock = std::lock_guard{reader_list_->reader_threads_lock()};
+    for (const auto& reader_thread : reader_list_->reader_threads()) {
+        reader_thread->triggerReader_Locked();
+    }
+}
+
+bool SerializedLogBuffer::ShouldLog(log_id_t log_id, const char* msg, uint16_t len) {
+    if (log_id == LOG_ID_SECURITY) {
+        return true;
+    }
+
+    int prio = ANDROID_LOG_INFO;
+    const char* tag = nullptr;
+    size_t tag_len = 0;
+    if (IsBinary(log_id)) {
+        int32_t tag_int = MsgToTag(msg, len);
+        tag = tags_->tagToName(tag_int);
+        if (tag) {
+            tag_len = strlen(tag);
+        }
+    } else {
+        prio = *msg;
+        tag = msg + 1;
+        tag_len = strnlen(tag, len - 1);
+    }
+    return __android_log_is_loggable_len(prio, tag, tag_len, ANDROID_LOG_VERBOSE);
+}
+
+int SerializedLogBuffer::Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
+                             const char* msg, uint16_t len) {
+    if (log_id >= LOG_ID_MAX || len == 0) {
+        return -EINVAL;
+    }
+
+    if (!ShouldLog(log_id, msg, len)) {
+        stats_->AddTotal(log_id, len);
+        return -EACCES;
+    }
+
+    auto sequence = sequence_.fetch_add(1, std::memory_order_relaxed);
+
+    auto lock = std::lock_guard{lock_};
+
+    if (logs_[log_id].empty()) {
+        logs_[log_id].push_back(SerializedLogChunk(max_size_[log_id] / 4));
+    }
+
+    auto total_len = sizeof(SerializedLogEntry) + len;
+    if (!logs_[log_id].back().CanLog(total_len)) {
+        logs_[log_id].back().FinishWriting();
+        logs_[log_id].push_back(SerializedLogChunk(max_size_[log_id] / 4));
+    }
+
+    auto entry = logs_[log_id].back().Log(sequence, realtime, uid, pid, tid, msg, len);
+    stats_->Add(entry->ToLogStatisticsElement(log_id));
+
+    MaybePrune(log_id);
+
+    reader_list_->NotifyNewLog(1 << log_id);
+    return len;
+}
+
+void SerializedLogBuffer::MaybePrune(log_id_t log_id) {
+    size_t total_size = GetSizeUsed(log_id);
+    size_t after_size = total_size;
+    if (total_size > max_size_[log_id]) {
+        Prune(log_id, total_size - max_size_[log_id], 0);
+        after_size = GetSizeUsed(log_id);
+        LOG(INFO) << "Pruned Logs from log_id: " << log_id << ", previous size: " << total_size
+                  << " after size: " << after_size;
+    }
+
+    stats_->set_overhead(log_id, after_size);
+}
+
+void SerializedLogBuffer::RemoveChunkFromStats(log_id_t log_id, SerializedLogChunk& chunk) {
+    chunk.IncReaderRefCount();
+    int read_offset = 0;
+    while (read_offset < chunk.write_offset()) {
+        auto* entry = chunk.log_entry(read_offset);
+        stats_->Subtract(entry->ToLogStatisticsElement(log_id));
+        read_offset += entry->total_len();
+    }
+    chunk.DecReaderRefCount();
+}
+
+void SerializedLogBuffer::NotifyReadersOfPrune(
+        log_id_t log_id, const std::list<SerializedLogChunk>::iterator& chunk) {
+    for (const auto& reader_thread : reader_list_->reader_threads()) {
+        auto& state = reinterpret_cast<SerializedFlushToState&>(reader_thread->flush_to_state());
+        state.Prune(log_id, chunk);
+    }
+}
+
+bool SerializedLogBuffer::Prune(log_id_t log_id, size_t bytes_to_free, uid_t uid) {
+    // Don't prune logs that are newer than the point at which any reader threads are reading from.
+    LogReaderThread* oldest = nullptr;
+    auto reader_threads_lock = std::lock_guard{reader_list_->reader_threads_lock()};
+    for (const auto& reader_thread : reader_list_->reader_threads()) {
+        if (!reader_thread->IsWatching(log_id)) {
+            continue;
+        }
+        if (!oldest || oldest->start() > reader_thread->start() ||
+            (oldest->start() == reader_thread->start() &&
+             reader_thread->deadline().time_since_epoch().count() != 0)) {
+            oldest = reader_thread.get();
+        }
+    }
+
+    auto& log_buffer = logs_[log_id];
+    auto it = log_buffer.begin();
+    while (it != log_buffer.end()) {
+        if (oldest != nullptr && it->highest_sequence_number() >= oldest->start()) {
+            break;
+        }
+
+        // Increment ahead of time since we're going to erase this iterator from the list.
+        auto it_to_prune = it++;
+
+        // The sequence number check ensures that all readers have read all logs in this chunk, but
+        // they may still hold a reference to the chunk to track their last read log_position.
+        // Notify them to delete the reference.
+        NotifyReadersOfPrune(log_id, it_to_prune);
+
+        if (uid != 0) {
+            // Reorder the log buffer to remove logs from the given UID.  If there are no logs left
+            // in the buffer after the removal, delete it.
+            if (it_to_prune->ClearUidLogs(uid, log_id, stats_)) {
+                log_buffer.erase(it_to_prune);
+            }
+        } else {
+            size_t buffer_size = it_to_prune->PruneSize();
+            RemoveChunkFromStats(log_id, *it_to_prune);
+            log_buffer.erase(it_to_prune);
+            if (buffer_size >= bytes_to_free) {
+                return true;
+            }
+            bytes_to_free -= buffer_size;
+        }
+    }
+
+    // If we've deleted all buffers without bytes_to_free hitting 0, then we're called by Clear()
+    // and should return true.
+    if (it == log_buffer.end()) {
+        return true;
+    }
+
+    // Otherwise we are stuck due to a reader, so mitigate it.
+    CHECK(oldest != nullptr);
+    KickReader(oldest, log_id, bytes_to_free);
+    return false;
+}
+
+// If the selected reader is blocking our pruning progress, decide on
+// what kind of mitigation is necessary to unblock the situation.
+void SerializedLogBuffer::KickReader(LogReaderThread* reader, log_id_t id, size_t bytes_to_free) {
+    if (bytes_to_free >= max_size_[id]) {  // +100%
+        // A misbehaving or slow reader is dropped if we hit too much memory pressure.
+        LOG(WARNING) << "Kicking blocked reader, " << reader->name()
+                     << ", from LogBuffer::kickMe()";
+        reader->release_Locked();
+    } else if (reader->deadline().time_since_epoch().count() != 0) {
+        // Allow a blocked WRAP deadline reader to trigger and start reporting the log data.
+        reader->triggerReader_Locked();
+    } else {
+        // Tell slow reader to skip entries to catch up.
+        unsigned long prune_rows = bytes_to_free / 300;
+        LOG(WARNING) << "Skipping " << prune_rows << " entries from slow reader, " << reader->name()
+                     << ", from LogBuffer::kickMe()";
+        reader->triggerSkip_Locked(id, prune_rows);
+    }
+}
+
+std::unique_ptr<FlushToState> SerializedLogBuffer::CreateFlushToState(uint64_t start,
+                                                                      LogMask log_mask) {
+    return std::make_unique<SerializedFlushToState>(start, log_mask);
+}
+
+bool SerializedLogBuffer::FlushTo(
+        LogWriter* writer, FlushToState& abstract_state,
+        const std::function<FilterResult(log_id_t log_id, pid_t pid, uint64_t sequence,
+                                         log_time realtime)>& filter) {
+    auto lock = std::unique_lock{lock_};
+
+    auto& state = reinterpret_cast<SerializedFlushToState&>(abstract_state);
+    state.InitializeLogs(logs_);
+
+    while (state.HasUnreadLogs()) {
+        MinHeapElement top = state.PopNextUnreadLog();
+        auto* entry = top.entry;
+        auto log_id = top.log_id;
+
+        if (entry->sequence() < state.start()) {
+            continue;
+        }
+        state.set_start(entry->sequence());
+
+        if (!writer->privileged() && entry->uid() != writer->uid()) {
+            continue;
+        }
+
+        if (filter) {
+            auto ret = filter(log_id, entry->pid(), entry->sequence(), entry->realtime());
+            if (ret == FilterResult::kSkip) {
+                continue;
+            }
+            if (ret == FilterResult::kStop) {
+                break;
+            }
+        }
+
+        lock.unlock();
+        // We never prune logs equal to or newer than any LogReaderThreads' `start` value, so the
+        // `entry` pointer is safe here without the lock
+        if (!entry->Flush(writer, log_id)) {
+            return false;
+        }
+        lock.lock();
+    }
+
+    state.set_start(state.start() + 1);
+    return true;
+}
+
+bool SerializedLogBuffer::Clear(log_id_t id, uid_t uid) {
+    // Try three times to clear, then disconnect the readers and try one final time.
+    for (int retry = 0; retry < 3; ++retry) {
+        {
+            auto lock = std::lock_guard{lock_};
+            bool prune_success = Prune(id, ULONG_MAX, uid);
+            if (prune_success) {
+                return true;
+            }
+        }
+        sleep(1);
+    }
+    // Check if it is still busy after the sleep, we try to prune one entry, not another clear run,
+    // so we are looking for the quick side effect of the return value to tell us if we have a
+    // _blocked_ reader.
+    bool busy = false;
+    {
+        auto lock = std::lock_guard{lock_};
+        busy = !Prune(id, 1, uid);
+    }
+    // It is still busy, disconnect all readers.
+    if (busy) {
+        auto reader_threads_lock = std::lock_guard{reader_list_->reader_threads_lock()};
+        for (const auto& reader_thread : reader_list_->reader_threads()) {
+            if (reader_thread->IsWatching(id)) {
+                LOG(WARNING) << "Kicking blocked reader, " << reader_thread->name()
+                             << ", from LogBuffer::clear()";
+                reader_thread->release_Locked();
+            }
+        }
+    }
+    auto lock = std::lock_guard{lock_};
+    return Prune(id, ULONG_MAX, uid);
+}
+
+unsigned long SerializedLogBuffer::GetSizeUsed(log_id_t id) {
+    size_t total_size = 0;
+    for (const auto& chunk : logs_[id]) {
+        total_size += chunk.PruneSize();
+    }
+    return total_size;
+}
+
+unsigned long SerializedLogBuffer::GetSize(log_id_t id) {
+    auto lock = std::lock_guard{lock_};
+    return max_size_[id];
+}
+
+// New SerializedLogChunk objects will be allocated according to the new size, but older one are
+// unchanged.  MaybePrune() is called on the log buffer to reduce it to an appropriate size if the
+// new size is lower.
+int SerializedLogBuffer::SetSize(log_id_t id, unsigned long size) {
+    // Reasonable limits ...
+    if (!__android_logger_valid_buffer_size(size)) {
+        return -1;
+    }
+
+    auto lock = std::lock_guard{lock_};
+    max_size_[id] = size;
+
+    MaybePrune(id);
+
+    return 0;
+}
diff --git a/logd/SerializedLogBuffer.h b/logd/SerializedLogBuffer.h
new file mode 100644
index 0000000..a03050e
--- /dev/null
+++ b/logd/SerializedLogBuffer.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 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 <atomic>
+#include <bitset>
+#include <list>
+#include <mutex>
+#include <queue>
+#include <thread>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+
+#include "LogBuffer.h"
+#include "LogReaderList.h"
+#include "LogStatistics.h"
+#include "LogTags.h"
+#include "SerializedLogChunk.h"
+#include "SerializedLogEntry.h"
+#include "rwlock.h"
+
+class SerializedLogBuffer final : public LogBuffer {
+  public:
+    SerializedLogBuffer(LogReaderList* reader_list, LogTags* tags, LogStatistics* stats);
+    void Init() override;
+
+    int Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char* msg,
+            uint16_t len) override;
+    std::unique_ptr<FlushToState> CreateFlushToState(uint64_t start, LogMask log_mask) override;
+    bool FlushTo(LogWriter* writer, FlushToState& state,
+                 const std::function<FilterResult(log_id_t log_id, pid_t pid, uint64_t sequence,
+                                                  log_time realtime)>& filter) override;
+
+    bool Clear(log_id_t id, uid_t uid) override;
+    unsigned long GetSize(log_id_t id) override;
+    int SetSize(log_id_t id, unsigned long size) override;
+
+    uint64_t sequence() const override { return sequence_.load(std::memory_order_relaxed); }
+
+  private:
+    bool ShouldLog(log_id_t log_id, const char* msg, uint16_t len);
+    void MaybePrune(log_id_t log_id) REQUIRES(lock_);
+    bool Prune(log_id_t log_id, size_t bytes_to_free, uid_t uid) REQUIRES(lock_);
+    void KickReader(LogReaderThread* reader, log_id_t id, size_t bytes_to_free)
+            REQUIRES_SHARED(lock_);
+    void NotifyReadersOfPrune(log_id_t log_id, const std::list<SerializedLogChunk>::iterator& chunk)
+            REQUIRES(reader_list_->reader_threads_lock());
+    void RemoveChunkFromStats(log_id_t log_id, SerializedLogChunk& chunk);
+    unsigned long GetSizeUsed(log_id_t id) REQUIRES(lock_);
+
+    LogReaderList* reader_list_;
+    LogTags* tags_;
+    LogStatistics* stats_;
+
+    unsigned long max_size_[LOG_ID_MAX] GUARDED_BY(lock_) = {};
+    std::list<SerializedLogChunk> logs_[LOG_ID_MAX] GUARDED_BY(lock_);
+    RwLock lock_;
+
+    std::atomic<uint64_t> sequence_ = 1;
+};
diff --git a/logd/SerializedLogChunk.cpp b/logd/SerializedLogChunk.cpp
new file mode 100644
index 0000000..de641d6
--- /dev/null
+++ b/logd/SerializedLogChunk.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 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 "SerializedLogChunk.h"
+
+#include <android-base/logging.h>
+
+#include "CompressionEngine.h"
+
+SerializedLogChunk::~SerializedLogChunk() {
+    CHECK_EQ(reader_ref_count_, 0U);
+}
+
+void SerializedLogChunk::Compress() {
+    if (compressed_log_.size() == 0) {
+        CompressionEngine::GetInstance().Compress(contents_, write_offset_, compressed_log_);
+        LOG(INFO) << "Compressed Log, buffer max size: " << contents_.size()
+                  << " size used: " << write_offset_
+                  << " compressed size: " << compressed_log_.size();
+    }
+}
+
+// TODO: Develop a better reference counting strategy to guard against the case where the writer is
+// much faster than the reader, and we needlessly compess / decompress the logs.
+void SerializedLogChunk::IncReaderRefCount() {
+    if (++reader_ref_count_ != 1 || writer_active_) {
+        return;
+    }
+    contents_.Resize(write_offset_);
+    CompressionEngine::GetInstance().Decompress(compressed_log_, contents_);
+}
+
+void SerializedLogChunk::DecReaderRefCount() {
+    CHECK_NE(reader_ref_count_, 0U);
+    if (--reader_ref_count_ != 0) {
+        return;
+    }
+    if (!writer_active_) {
+        contents_.Resize(0);
+    }
+}
+
+bool SerializedLogChunk::ClearUidLogs(uid_t uid, log_id_t log_id, LogStatistics* stats) {
+    CHECK_EQ(reader_ref_count_, 0U);
+    if (write_offset_ == 0) {
+        return true;
+    }
+
+    IncReaderRefCount();
+
+    int read_offset = 0;
+    int new_write_offset = 0;
+    while (read_offset < write_offset_) {
+        const auto* entry = log_entry(read_offset);
+        if (entry->uid() == uid) {
+            read_offset += entry->total_len();
+            if (stats != nullptr) {
+                stats->Subtract(entry->ToLogStatisticsElement(log_id));
+            }
+            continue;
+        }
+        size_t entry_total_len = entry->total_len();
+        if (read_offset != new_write_offset) {
+            memmove(contents_.data() + new_write_offset, contents_.data() + read_offset,
+                    entry_total_len);
+        }
+        read_offset += entry_total_len;
+        new_write_offset += entry_total_len;
+    }
+
+    if (new_write_offset == 0) {
+        DecReaderRefCount();
+        return true;
+    }
+
+    // Clear the old compressed logs and set write_offset_ appropriately to compress the new
+    // partially cleared log.
+    if (new_write_offset != write_offset_) {
+        compressed_log_.Resize(0);
+        write_offset_ = new_write_offset;
+        Compress();
+    }
+
+    DecReaderRefCount();
+
+    return false;
+}
+
+bool SerializedLogChunk::CanLog(size_t len) {
+    return write_offset_ + len <= contents_.size();
+}
+
+SerializedLogEntry* SerializedLogChunk::Log(uint64_t sequence, log_time realtime, uid_t uid,
+                                            pid_t pid, pid_t tid, const char* msg, uint16_t len) {
+    auto new_log_address = contents_.data() + write_offset_;
+    auto* entry = new (new_log_address) SerializedLogEntry(uid, pid, tid, sequence, realtime, len);
+    memcpy(entry->msg(), msg, len);
+    write_offset_ += entry->total_len();
+    highest_sequence_number_ = sequence;
+    return entry;
+}
\ No newline at end of file
diff --git a/logd/SerializedLogChunk.h b/logd/SerializedLogChunk.h
new file mode 100644
index 0000000..0991eac
--- /dev/null
+++ b/logd/SerializedLogChunk.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 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 "LogWriter.h"
+#include "SerializedData.h"
+#include "SerializedLogEntry.h"
+
+class SerializedLogChunk {
+  public:
+    explicit SerializedLogChunk(size_t size) : contents_(size) {}
+    SerializedLogChunk(SerializedLogChunk&& other) noexcept = default;
+    ~SerializedLogChunk();
+
+    void Compress();
+    void IncReaderRefCount();
+    void DecReaderRefCount();
+
+    // Must have no readers referencing this.  Return true if there are no logs left in this chunk.
+    bool ClearUidLogs(uid_t uid, log_id_t log_id, LogStatistics* stats);
+
+    bool CanLog(size_t len);
+    SerializedLogEntry* Log(uint64_t sequence, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
+                            const char* msg, uint16_t len);
+
+    // If this buffer has been compressed, we only consider its compressed size when accounting for
+    // memory consumption for pruning.  This is since the uncompressed log is only by used by
+    // readers, and thus not a representation of how much these logs cost to keep in memory.
+    size_t PruneSize() const {
+        return sizeof(*this) + (compressed_log_.size() ?: contents_.size());
+    }
+
+    void FinishWriting() {
+        writer_active_ = false;
+        Compress();
+        if (reader_ref_count_ == 0) {
+            contents_.Resize(0);
+        }
+    }
+
+    const SerializedLogEntry* log_entry(int offset) const {
+        return reinterpret_cast<const SerializedLogEntry*>(data() + offset);
+    }
+    const uint8_t* data() const { return contents_.data(); }
+    int write_offset() const { return write_offset_; }
+    uint64_t highest_sequence_number() const { return highest_sequence_number_; }
+
+    // Exposed for testing
+    uint32_t reader_ref_count() const { return reader_ref_count_; }
+
+  private:
+    // The decompressed contents of this log buffer.  Deallocated when the ref_count reaches 0 and
+    // writer_active_ is false.
+    SerializedData contents_;
+    int write_offset_ = 0;
+    uint32_t reader_ref_count_ = 0;
+    bool writer_active_ = true;
+    uint64_t highest_sequence_number_ = 1;
+    SerializedData compressed_log_;
+};
diff --git a/logd/SerializedLogChunkTest.cpp b/logd/SerializedLogChunkTest.cpp
new file mode 100644
index 0000000..f10b9c6
--- /dev/null
+++ b/logd/SerializedLogChunkTest.cpp
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2020 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 "SerializedLogChunk.h"
+
+#include <limits>
+
+#include <android-base/stringprintf.h>
+#include <android/log.h>
+#include <gtest/gtest.h>
+
+using android::base::StringPrintf;
+
+TEST(SerializedLogChunk, smoke) {
+    size_t chunk_size = 10 * 4096;
+    auto chunk = SerializedLogChunk{chunk_size};
+    EXPECT_EQ(chunk_size + sizeof(SerializedLogChunk), chunk.PruneSize());
+
+    static const char log_message[] = "log message";
+    size_t expected_total_len = sizeof(SerializedLogEntry) + sizeof(log_message);
+    ASSERT_TRUE(chunk.CanLog(expected_total_len));
+    EXPECT_TRUE(chunk.CanLog(chunk_size));
+    EXPECT_FALSE(chunk.CanLog(chunk_size + 1));
+
+    log_time time(CLOCK_REALTIME);
+    auto* entry = chunk.Log(1234, time, 0, 1, 2, log_message, sizeof(log_message));
+    ASSERT_NE(nullptr, entry);
+
+    EXPECT_EQ(1234U, entry->sequence());
+    EXPECT_EQ(time, entry->realtime());
+    EXPECT_EQ(0U, entry->uid());
+    EXPECT_EQ(1, entry->pid());
+    EXPECT_EQ(2, entry->tid());
+    EXPECT_EQ(sizeof(log_message), entry->msg_len());
+    EXPECT_STREQ(log_message, entry->msg());
+    EXPECT_EQ(expected_total_len, entry->total_len());
+
+    EXPECT_FALSE(chunk.CanLog(chunk_size));
+    EXPECT_EQ(static_cast<int>(expected_total_len), chunk.write_offset());
+    EXPECT_EQ(1234U, chunk.highest_sequence_number());
+}
+
+TEST(SerializedLogChunk, fill_log_exactly) {
+    static const char log_message[] = "this is a log message";
+    size_t individual_message_size = sizeof(SerializedLogEntry) + sizeof(log_message);
+    size_t chunk_size = individual_message_size * 3;
+    auto chunk = SerializedLogChunk{chunk_size};
+    EXPECT_EQ(chunk_size + sizeof(SerializedLogChunk), chunk.PruneSize());
+
+    ASSERT_TRUE(chunk.CanLog(individual_message_size));
+    EXPECT_NE(nullptr, chunk.Log(1, log_time(), 1000, 1, 1, log_message, sizeof(log_message)));
+
+    ASSERT_TRUE(chunk.CanLog(individual_message_size));
+    EXPECT_NE(nullptr, chunk.Log(2, log_time(), 1000, 2, 1, log_message, sizeof(log_message)));
+
+    ASSERT_TRUE(chunk.CanLog(individual_message_size));
+    EXPECT_NE(nullptr, chunk.Log(3, log_time(), 1000, 3, 1, log_message, sizeof(log_message)));
+
+    EXPECT_FALSE(chunk.CanLog(1));
+}
+
+TEST(SerializedLogChunk, three_logs) {
+    size_t chunk_size = 10 * 4096;
+    auto chunk = SerializedLogChunk{chunk_size};
+
+    chunk.Log(2, log_time(0x1234, 0x5678), 0x111, 0x222, 0x333, "initial message",
+              strlen("initial message"));
+    chunk.Log(3, log_time(0x2345, 0x6789), 0x444, 0x555, 0x666, "second message",
+              strlen("second message"));
+    auto uint64_t_max = std::numeric_limits<uint64_t>::max();
+    auto uint32_t_max = std::numeric_limits<uint32_t>::max();
+    chunk.Log(uint64_t_max, log_time(uint32_t_max, uint32_t_max), uint32_t_max, uint32_t_max,
+              uint32_t_max, "last message", strlen("last message"));
+
+    static const char expected_buffer_data[] =
+            "\x11\x01\x00\x00\x22\x02\x00\x00\x33\x03\x00\x00"  // UID PID TID
+            "\x02\x00\x00\x00\x00\x00\x00\x00"                  // Sequence
+            "\x34\x12\x00\x00\x78\x56\x00\x00"                  // Timestamp
+            "\x0F\x00initial message"                           // msg_len + message
+            "\x44\x04\x00\x00\x55\x05\x00\x00\x66\x06\x00\x00"  // UID PID TID
+            "\x03\x00\x00\x00\x00\x00\x00\x00"                  // Sequence
+            "\x45\x23\x00\x00\x89\x67\x00\x00"                  // Timestamp
+            "\x0E\x00second message"                            // msg_len + message
+            "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"  // UID PID TID
+            "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"                  // Sequence
+            "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"                  // Timestamp
+            "\x0C\x00last message";                             // msg_len + message
+
+    for (size_t i = 0; i < chunk_size; ++i) {
+        if (i < sizeof(expected_buffer_data)) {
+            EXPECT_EQ(static_cast<uint8_t>(expected_buffer_data[i]), chunk.data()[i])
+                    << "position: " << i;
+        } else {
+            EXPECT_EQ(0, chunk.data()[i]) << "position: " << i;
+        }
+    }
+}
+
+// Check that the CHECK() in DecReaderRefCount() if the ref count goes bad is caught.
+TEST(SerializedLogChunk, catch_DecCompressedRef_CHECK) {
+    size_t chunk_size = 10 * 4096;
+    auto chunk = SerializedLogChunk{chunk_size};
+    EXPECT_DEATH({ chunk.DecReaderRefCount(); }, "");
+}
+
+// Check that the CHECK() in ClearUidLogs() if the ref count is greater than 0 is caught.
+TEST(SerializedLogChunk, catch_ClearUidLogs_CHECK) {
+    size_t chunk_size = 10 * 4096;
+    auto chunk = SerializedLogChunk{chunk_size};
+    chunk.IncReaderRefCount();
+    EXPECT_DEATH({ chunk.ClearUidLogs(1000, LOG_ID_MAIN, nullptr); }, "");
+    chunk.DecReaderRefCount();
+}
+
+class UidClearTest : public testing::TestWithParam<bool> {
+  protected:
+    template <typename Write, typename Check>
+    void Test(const Write& write, const Check& check, bool expected_result) {
+        write(chunk_);
+
+        bool finish_writing = GetParam();
+        if (finish_writing) {
+            chunk_.FinishWriting();
+        }
+        EXPECT_EQ(expected_result, chunk_.ClearUidLogs(kUidToClear, LOG_ID_MAIN, nullptr));
+        if (finish_writing) {
+            chunk_.IncReaderRefCount();
+        }
+
+        check(chunk_);
+
+        if (finish_writing) {
+            chunk_.DecReaderRefCount();
+        }
+    }
+
+    static constexpr size_t kChunkSize = 10 * 4096;
+    static constexpr uid_t kUidToClear = 1000;
+    static constexpr uid_t kOtherUid = 1234;
+
+    SerializedLogChunk chunk_{kChunkSize};
+};
+
+// Test that ClearUidLogs() is a no-op if there are no logs of that UID in the buffer.
+TEST_P(UidClearTest, no_logs_in_chunk) {
+    auto write = [](SerializedLogChunk&) {};
+    auto check = [](SerializedLogChunk&) {};
+
+    Test(write, check, true);
+}
+
+// Test that ClearUidLogs() is a no-op if there are no logs of that UID in the buffer.
+TEST_P(UidClearTest, no_logs_from_uid) {
+    static const char msg[] = "this is a log message";
+    auto write = [](SerializedLogChunk& chunk) {
+        chunk.Log(1, log_time(), kOtherUid, 1, 2, msg, sizeof(msg));
+    };
+
+    auto check = [](SerializedLogChunk& chunk) {
+        auto* entry = chunk.log_entry(0);
+        EXPECT_STREQ(msg, entry->msg());
+    };
+
+    Test(write, check, false);
+}
+
+// Test that ClearUidLogs() returns true if all logs in a given buffer correspond to the given UID.
+TEST_P(UidClearTest, all_single) {
+    static const char msg[] = "this is a log message";
+    auto write = [](SerializedLogChunk& chunk) {
+        chunk.Log(1, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
+    };
+    auto check = [](SerializedLogChunk&) {};
+
+    Test(write, check, true);
+}
+
+// Test that ClearUidLogs() returns true if all logs in a given buffer correspond to the given UID.
+TEST_P(UidClearTest, all_multiple) {
+    static const char msg[] = "this is a log message";
+    auto write = [](SerializedLogChunk& chunk) {
+        chunk.Log(2, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
+        chunk.Log(3, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
+        chunk.Log(4, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
+    };
+    auto check = [](SerializedLogChunk&) {};
+
+    Test(write, check, true);
+}
+
+static std::string MakePrintable(const uint8_t* in, size_t length) {
+    std::string result;
+    for (size_t i = 0; i < length; ++i) {
+        uint8_t c = in[i];
+        if (isprint(c)) {
+            result.push_back(c);
+        } else {
+            result.append(StringPrintf("\\%02x", static_cast<int>(c) & 0xFF));
+        }
+    }
+    return result;
+}
+
+// This test clears UID logs at the beginning and end of the buffer, as well as two back to back
+// logs in the interior.
+TEST_P(UidClearTest, clear_beginning_and_end) {
+    static const char msg1[] = "this is a log message";
+    static const char msg2[] = "non-cleared message";
+    static const char msg3[] = "back to back cleared messages";
+    static const char msg4[] = "second in a row gone";
+    static const char msg5[] = "but we save this one";
+    static const char msg6[] = "and this 1!";
+    static const char msg7[] = "the last one goes too";
+    auto write = [](SerializedLogChunk& chunk) {
+        ASSERT_NE(nullptr, chunk.Log(1, log_time(), kUidToClear, 1, 2, msg1, sizeof(msg1)));
+        ASSERT_NE(nullptr, chunk.Log(2, log_time(), kOtherUid, 1, 2, msg2, sizeof(msg2)));
+        ASSERT_NE(nullptr, chunk.Log(3, log_time(), kUidToClear, 1, 2, msg3, sizeof(msg3)));
+        ASSERT_NE(nullptr, chunk.Log(4, log_time(), kUidToClear, 1, 2, msg4, sizeof(msg4)));
+        ASSERT_NE(nullptr, chunk.Log(5, log_time(), kOtherUid, 1, 2, msg5, sizeof(msg5)));
+        ASSERT_NE(nullptr, chunk.Log(6, log_time(), kOtherUid, 1, 2, msg6, sizeof(msg6)));
+        ASSERT_NE(nullptr, chunk.Log(7, log_time(), kUidToClear, 1, 2, msg7, sizeof(msg7)));
+    };
+
+    auto check = [](SerializedLogChunk& chunk) {
+        size_t read_offset = 0;
+        auto* entry = chunk.log_entry(read_offset);
+        EXPECT_STREQ(msg2, entry->msg());
+        read_offset += entry->total_len();
+
+        entry = chunk.log_entry(read_offset);
+        EXPECT_STREQ(msg5, entry->msg());
+        read_offset += entry->total_len();
+
+        entry = chunk.log_entry(read_offset);
+        EXPECT_STREQ(msg6, entry->msg()) << MakePrintable(chunk.data(), chunk.write_offset());
+        read_offset += entry->total_len();
+
+        EXPECT_EQ(static_cast<int>(read_offset), chunk.write_offset());
+    };
+    Test(write, check, false);
+}
+
+// This tests the opposite case of beginning_and_end, in which we don't clear the beginning or end
+// logs.  There is a single log pruned in the middle instead of back to back logs.
+TEST_P(UidClearTest, save_beginning_and_end) {
+    static const char msg1[] = "saved first message";
+    static const char msg2[] = "cleared interior message";
+    static const char msg3[] = "last message stays";
+    auto write = [](SerializedLogChunk& chunk) {
+        ASSERT_NE(nullptr, chunk.Log(1, log_time(), kOtherUid, 1, 2, msg1, sizeof(msg1)));
+        ASSERT_NE(nullptr, chunk.Log(2, log_time(), kUidToClear, 1, 2, msg2, sizeof(msg2)));
+        ASSERT_NE(nullptr, chunk.Log(3, log_time(), kOtherUid, 1, 2, msg3, sizeof(msg3)));
+    };
+
+    auto check = [](SerializedLogChunk& chunk) {
+        size_t read_offset = 0;
+        auto* entry = chunk.log_entry(read_offset);
+        EXPECT_STREQ(msg1, entry->msg());
+        read_offset += entry->total_len();
+
+        entry = chunk.log_entry(read_offset);
+        EXPECT_STREQ(msg3, entry->msg());
+        read_offset += entry->total_len();
+
+        EXPECT_EQ(static_cast<int>(read_offset), chunk.write_offset());
+    };
+    Test(write, check, false);
+}
+
+INSTANTIATE_TEST_CASE_P(UidClearTests, UidClearTest, testing::Values(true, false));
diff --git a/logd/SerializedLogEntry.h b/logd/SerializedLogEntry.h
new file mode 100644
index 0000000..2f2c244
--- /dev/null
+++ b/logd/SerializedLogEntry.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 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 <stdlib.h>
+#include <sys/types.h>
+
+#include <log/log.h>
+#include <log/log_read.h>
+
+#include "LogStatistics.h"
+#include "LogWriter.h"
+
+// These structs are packed into a single chunk of memory for each log type within a
+// SerializedLogChunk object.  Their message is contained immediately at the end of the struct.  The
+// address of the next log in the buffer is *this + sizeof(SerializedLogEntry) + msg_len_.  If that
+// value would overflow the chunk of memory associated with the SerializedLogChunk object, then a
+// new SerializedLogChunk must be allocated to contain the next SerializedLogEntry.
+class __attribute__((packed)) SerializedLogEntry {
+  public:
+    SerializedLogEntry(uid_t uid, pid_t pid, pid_t tid, uint64_t sequence, log_time realtime,
+                       uint16_t len)
+        : uid_(uid),
+          pid_(pid),
+          tid_(tid),
+          sequence_(sequence),
+          realtime_(realtime),
+          msg_len_(len) {}
+    SerializedLogEntry(const SerializedLogEntry& elem) = delete;
+    SerializedLogEntry& operator=(const SerializedLogEntry& elem) = delete;
+    ~SerializedLogEntry() {
+        // Never place anything in this destructor.  This class is in place constructed and never
+        // destructed.
+    }
+
+    LogStatisticsElement ToLogStatisticsElement(log_id_t log_id) const {
+        return LogStatisticsElement{
+                .uid = uid(),
+                .pid = pid(),
+                .tid = tid(),
+                .tag = IsBinary(log_id) ? MsgToTag(msg(), msg_len()) : 0,
+                .realtime = realtime(),
+                .msg = msg(),
+                .msg_len = msg_len(),
+                .dropped_count = 0,
+                .log_id = log_id,
+                .total_len = total_len(),
+        };
+    }
+
+    bool Flush(LogWriter* writer, log_id_t log_id) const {
+        struct logger_entry entry = {};
+
+        entry.hdr_size = sizeof(struct logger_entry);
+        entry.lid = log_id;
+        entry.pid = pid();
+        entry.tid = tid();
+        entry.uid = uid();
+        entry.sec = realtime().tv_sec;
+        entry.nsec = realtime().tv_nsec;
+        entry.len = msg_len();
+
+        return writer->Write(entry, msg());
+    }
+
+    uid_t uid() const { return uid_; }
+    pid_t pid() const { return pid_; }
+    pid_t tid() const { return tid_; }
+    uint16_t msg_len() const { return msg_len_; }
+    uint64_t sequence() const { return sequence_; }
+    log_time realtime() const { return realtime_; }
+
+    char* msg() { return reinterpret_cast<char*>(this) + sizeof(*this); }
+    const char* msg() const { return reinterpret_cast<const char*>(this) + sizeof(*this); }
+    uint16_t total_len() const { return sizeof(*this) + msg_len_; }
+
+  private:
+    const uint32_t uid_;
+    const uint32_t pid_;
+    const uint32_t tid_;
+    const uint64_t sequence_;
+    const log_time realtime_;
+    const uint16_t msg_len_;
+};
diff --git a/logd/SimpleLogBuffer.h b/logd/SimpleLogBuffer.h
index 2172507..9f7d699 100644
--- a/logd/SimpleLogBuffer.h
+++ b/logd/SimpleLogBuffer.h
@@ -31,7 +31,7 @@
   public:
     SimpleLogBuffer(LogReaderList* reader_list, LogTags* tags, LogStatistics* stats);
     ~SimpleLogBuffer();
-    void Init() override;
+    void Init() override final;
 
     int Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char* msg,
             uint16_t len) override;
@@ -42,7 +42,7 @@
 
     bool Clear(log_id_t id, uid_t uid) override;
     unsigned long GetSize(log_id_t id) override;
-    int SetSize(log_id_t id, unsigned long size) override;
+    int SetSize(log_id_t id, unsigned long size) override final;
 
     uint64_t sequence() const override { return sequence_.load(std::memory_order_relaxed); }
 
diff --git a/logd/fuzz/Android.bp b/logd/fuzz/Android.bp
index f65fbdf..d346cd7 100644
--- a/logd/fuzz/Android.bp
+++ b/logd/fuzz/Android.bp
@@ -13,11 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-cc_fuzz {
-    name: "log_buffer_log_fuzzer",
-    srcs: [
-        "log_buffer_log_fuzzer.cpp",
-    ],
+
+cc_defaults {
+    name: "log_fuzzer_defaults",
     static_libs: [
         "libbase",
         "libcutils",
@@ -25,7 +23,28 @@
         "liblog",
         "liblogd",
         "libcutils",
-        "libsysutils",
+        "libz",
+        "libzstd",
     ],
-    cflags: ["-Werror"],
+    cflags: ["-Wextra"],
+    host_supported: true,
+}
+
+cc_fuzz {
+    name: "log_buffer_log_fuzzer",
+    defaults: ["log_fuzzer_defaults"],
+    srcs: [
+        "log_buffer_log_fuzzer.cpp",
+    ],
+}
+
+cc_fuzz {
+    name: "serialized_log_buffer_fuzzer",
+    defaults: ["log_fuzzer_defaults"],
+    srcs: [
+        "serialized_log_buffer_fuzzer.cpp",
+    ],
+    corpus: [
+        "corpus/logentry_use_after_compress",
+    ]
 }
diff --git a/logd/fuzz/corpus/logentry_use_after_compress b/logd/fuzz/corpus/logentry_use_after_compress
new file mode 100644
index 0000000..2081b13
--- /dev/null
+++ b/logd/fuzz/corpus/logentry_use_after_compress
Binary files differ
diff --git a/logd/fuzz/log_buffer_log_fuzzer.cpp b/logd/fuzz/log_buffer_log_fuzzer.cpp
index b576ddf..8309f95 100644
--- a/logd/fuzz/log_buffer_log_fuzzer.cpp
+++ b/logd/fuzz/log_buffer_log_fuzzer.cpp
@@ -15,10 +15,13 @@
  */
 #include <string>
 
+#include <android-base/logging.h>
+
 #include "../ChattyLogBuffer.h"
 #include "../LogReaderList.h"
 #include "../LogReaderThread.h"
 #include "../LogStatistics.h"
+#include "../SerializedLogBuffer.h"
 
 // We don't want to waste a lot of entropy on messages
 #define MAX_MSG_LENGTH 5
@@ -27,7 +30,20 @@
 #define MIN_TAG_ID 1000
 #define TAG_MOD 10
 
-namespace android {
+#ifndef __ANDROID__
+unsigned long __android_logger_get_buffer_size(log_id_t) {
+    return 1024 * 1024;
+}
+
+bool __android_logger_valid_buffer_size(unsigned long) {
+    return true;
+}
+#endif
+
+char* android::uidToName(uid_t) {
+    return strdup("fake");
+}
+
 struct LogInput {
   public:
     log_id_t log_id;
@@ -79,9 +95,13 @@
     return 1;
 }
 
-char* uidToName(uid_t) {
-    return strdup("fake");
-}
+class NoopWriter : public LogWriter {
+  public:
+    NoopWriter() : LogWriter(0, true) {}
+    bool Write(const logger_entry&, const char*) override { return true; }
+
+    std::string name() const override { return "noop_writer"; }
+};
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     // We want a random tag length and a random remaining message length
@@ -89,25 +109,50 @@
         return 0;
     }
 
+    android::base::SetMinimumLogSeverity(android::base::ERROR);
+
     LogReaderList reader_list;
     LogTags tags;
     PruneList prune_list;
-    LogStatistics stats(true);
-    LogBuffer* log_buffer = new ChattyLogBuffer(&reader_list, &tags, &prune_list, &stats);
+    LogStatistics stats(true, true);
+    std::unique_ptr<LogBuffer> log_buffer;
+#ifdef FUZZ_SERIALIZED
+    log_buffer.reset(new SerializedLogBuffer(&reader_list, &tags, &stats));
+#else
+    log_buffer.reset(new ChattyLogBuffer(&reader_list, &tags, &prune_list, &stats));
+#endif
     size_t data_left = size;
     const uint8_t** pdata = &data;
 
-    prune_list.init(nullptr);
+    prune_list.Init(nullptr);
     // We want to get pruning code to get called.
     log_id_for_each(i) { log_buffer->SetSize(i, 10000); }
 
     while (data_left >= sizeof(LogInput) + 2 * sizeof(uint8_t)) {
-        if (!write_log_messages(pdata, &data_left, log_buffer, &stats)) {
+        if (!write_log_messages(pdata, &data_left, log_buffer.get(), &stats)) {
             return 0;
         }
     }
 
+    // Read out all of the logs.
+    {
+        auto lock = std::unique_lock{reader_list.reader_threads_lock()};
+        std::unique_ptr<LogWriter> test_writer(new NoopWriter());
+        std::unique_ptr<LogReaderThread> log_reader(
+                new LogReaderThread(log_buffer.get(), &reader_list, std::move(test_writer), true, 0,
+                                    kLogMaskAll, 0, {}, 1, {}));
+        reader_list.reader_threads().emplace_back(std::move(log_reader));
+    }
+
+    // Wait until the reader has finished.
+    while (true) {
+        usleep(50);
+        auto lock = std::unique_lock{reader_list.reader_threads_lock()};
+        if (reader_list.reader_threads().size() == 0) {
+            break;
+        }
+    }
+
     log_id_for_each(i) { log_buffer->Clear(i, 0); }
     return 0;
 }
-}  // namespace android
diff --git a/logd/fuzz/serialized_log_buffer_fuzzer.cpp b/logd/fuzz/serialized_log_buffer_fuzzer.cpp
new file mode 100644
index 0000000..d4795b0
--- /dev/null
+++ b/logd/fuzz/serialized_log_buffer_fuzzer.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#define FUZZ_SERIALIZED
+
+#include "log_buffer_log_fuzzer.cpp"
diff --git a/logd/logd_test.cpp b/logd/logd_test.cpp
index ed34ea4..307610e 100644
--- a/logd/logd_test.cpp
+++ b/logd/logd_test.cpp
@@ -832,127 +832,3 @@
     GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
 }
-
-#ifdef __ANDROID__
-static inline uint32_t get4LE(const uint8_t* src) {
-  return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
-}
-
-static inline uint32_t get4LE(const char* src) {
-  return get4LE(reinterpret_cast<const uint8_t*>(src));
-}
-#endif
-
-void __android_log_btwrite_multiple__helper(int count) {
-#ifdef __ANDROID__
-    log_time ts(CLOCK_MONOTONIC);
-    usleep(100);
-    log_time ts1(CLOCK_MONOTONIC);
-
-    // We fork to create a unique pid for the submitted log messages
-    // so that we do not collide with the other _multiple_ tests.
-
-    pid_t pid = fork();
-
-    if (pid == 0) {
-        // child
-        for (int i = count; i; --i) {
-            ASSERT_LT(
-                0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
-            usleep(100);
-        }
-        ASSERT_LT(0,
-                  __android_log_btwrite(0, EVENT_TYPE_LONG, &ts1, sizeof(ts1)));
-        usleep(1000000);
-
-        _exit(0);
-    }
-
-    siginfo_t info = {};
-    ASSERT_EQ(0, TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED)));
-    ASSERT_EQ(0, info.si_status);
-
-    struct logger_list* logger_list;
-    ASSERT_TRUE(nullptr != (logger_list = android_logger_list_open(LOG_ID_EVENTS,
-                                                                   ANDROID_LOG_NONBLOCK, 0, pid)));
-
-    int expected_count = (count < 2) ? count : 2;
-    int expected_chatty_count = (count <= 2) ? 0 : 1;
-    int expected_identical_count = (count < 2) ? 0 : (count - 2);
-    static const int expected_expire_count = 0;
-
-    count = 0;
-    int second_count = 0;
-    int chatty_count = 0;
-    int identical_count = 0;
-    int expire_count = 0;
-
-    for (;;) {
-        log_msg log_msg;
-        if (android_logger_list_read(logger_list, &log_msg) <= 0) break;
-
-        if ((log_msg.entry.pid != pid) || (log_msg.entry.len < (4 + 1 + 8)) ||
-            (log_msg.id() != LOG_ID_EVENTS))
-            continue;
-
-        char* eventData = log_msg.msg();
-        if (!eventData) continue;
-
-        uint32_t tag = get4LE(eventData);
-
-        if ((eventData[4] == EVENT_TYPE_LONG) &&
-            (log_msg.entry.len == (4 + 1 + 8))) {
-            if (tag != 0) continue;
-
-            log_time* tx = reinterpret_cast<log_time*>(eventData + 4 + 1);
-            if (ts == *tx) {
-                ++count;
-            } else if (ts1 == *tx) {
-                ++second_count;
-            }
-        } else if (eventData[4] == EVENT_TYPE_STRING) {
-            if (tag != CHATTY_LOG_TAG) continue;
-            ++chatty_count;
-            // int len = get4LE(eventData + 4 + 1);
-            log_msg.buf[LOGGER_ENTRY_MAX_LEN] = '\0';
-            const char* cp;
-            if ((cp = strstr(eventData + 4 + 1 + 4, " identical "))) {
-                unsigned val = 0;
-                sscanf(cp, " identical %u lines", &val);
-                identical_count += val;
-            } else if ((cp = strstr(eventData + 4 + 1 + 4, " expire "))) {
-                unsigned val = 0;
-                sscanf(cp, " expire %u lines", &val);
-                expire_count += val;
-            }
-        }
-    }
-
-    android_logger_list_close(logger_list);
-
-    EXPECT_EQ(expected_count, count);
-    EXPECT_EQ(1, second_count);
-    EXPECT_EQ(expected_chatty_count, chatty_count);
-    EXPECT_EQ(expected_identical_count, identical_count);
-    EXPECT_EQ(expected_expire_count, expire_count);
-#else
-    count = 0;
-    GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
-}
-
-TEST(logd, multiple_test_1) {
-    __android_log_btwrite_multiple__helper(1);
-}
-
-TEST(logd, multiple_test_2) {
-    __android_log_btwrite_multiple__helper(2);
-}
-
-TEST(logd, multiple_test_3) {
-    __android_log_btwrite_multiple__helper(3);
-}
-
-TEST(logd, multiple_test_10) {
-    __android_log_btwrite_multiple__helper(10);
-}
diff --git a/logd/main.cpp b/logd/main.cpp
index 773ffb8..e7a69eb 100644
--- a/logd/main.cpp
+++ b/logd/main.cpp
@@ -38,6 +38,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/macros.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <cutils/android_get_control_file.h>
 #include <cutils/sockets.h>
@@ -58,8 +59,11 @@
 #include "LogStatistics.h"
 #include "LogTags.h"
 #include "LogUtils.h"
+#include "SerializedLogBuffer.h"
 #include "SimpleLogBuffer.h"
 
+using android::base::GetProperty;
+
 #define KMSG_PRIORITY(PRI)                                 \
     '<', '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) / 10, \
         '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) % 10, '>'
@@ -68,49 +72,42 @@
 // has a 'sigstop' feature that sends SIGSTOP to a service immediately before calling exec().  This
 // allows debuggers, etc to be attached to logd at the very beginning, while still having init
 // handle the user, groups, capabilities, files, etc setup.
-static int drop_privs(bool klogd, bool auditd) {
-    sched_param param = {};
-
+static void DropPrivs(bool klogd, bool auditd) {
     if (set_sched_policy(0, SP_BACKGROUND) < 0) {
-        PLOG(ERROR) << "failed to set background scheduling policy";
-        return -1;
+        PLOG(FATAL) << "failed to set background scheduling policy";
     }
 
+    sched_param param = {};
     if (sched_setscheduler((pid_t)0, SCHED_BATCH, &param) < 0) {
-        PLOG(ERROR) << "failed to set batch scheduler";
-        return -1;
+        PLOG(FATAL) << "failed to set batch scheduler";
     }
 
     if (!__android_logger_property_get_bool("ro.debuggable", BOOL_DEFAULT_FALSE) &&
         prctl(PR_SET_DUMPABLE, 0) == -1) {
-        PLOG(ERROR) << "failed to clear PR_SET_DUMPABLE";
-        return -1;
+        PLOG(FATAL) << "failed to clear PR_SET_DUMPABLE";
     }
 
     std::unique_ptr<struct _cap_struct, int (*)(void*)> caps(cap_init(), cap_free);
     if (cap_clear(caps.get()) < 0) {
-        return -1;
+        PLOG(FATAL) << "cap_clear() failed";
     }
-    std::vector<cap_value_t> cap_value;
     if (klogd) {
-        cap_value.emplace_back(CAP_SYSLOG);
+        cap_value_t cap_syslog = CAP_SYSLOG;
+        if (cap_set_flag(caps.get(), CAP_PERMITTED, 1, &cap_syslog, CAP_SET) < 0 ||
+            cap_set_flag(caps.get(), CAP_EFFECTIVE, 1, &cap_syslog, CAP_SET) < 0) {
+            PLOG(FATAL) << "Failed to set CAP_SYSLOG";
+        }
     }
     if (auditd) {
-        cap_value.emplace_back(CAP_AUDIT_CONTROL);
-    }
-
-    if (cap_set_flag(caps.get(), CAP_PERMITTED, cap_value.size(), cap_value.data(), CAP_SET) < 0) {
-        return -1;
-    }
-    if (cap_set_flag(caps.get(), CAP_EFFECTIVE, cap_value.size(), cap_value.data(), CAP_SET) < 0) {
-        return -1;
+        cap_value_t cap_audit_control = CAP_AUDIT_CONTROL;
+        if (cap_set_flag(caps.get(), CAP_PERMITTED, 1, &cap_audit_control, CAP_SET) < 0 ||
+            cap_set_flag(caps.get(), CAP_EFFECTIVE, 1, &cap_audit_control, CAP_SET) < 0) {
+            PLOG(FATAL) << "Failed to set CAP_AUDIT_CONTROL";
+        }
     }
     if (cap_set_proc(caps.get()) < 0) {
-        PLOG(ERROR) << "failed to set CAP_SYSLOG or CAP_AUDIT_CONTROL";
-        return -1;
+        PLOG(FATAL) << "cap_set_proc() failed";
     }
-
-    return 0;
 }
 
 char* android::uidToName(uid_t u) {
@@ -253,9 +250,7 @@
     }
 
     bool auditd = __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
-    if (drop_privs(klogd, auditd) != 0) {
-        return EXIT_FAILURE;
-    }
+    DropPrivs(klogd, auditd);
 
     // A cache of event log tags
     LogTags log_tags;
@@ -263,28 +258,33 @@
     // Pruning configuration.
     PruneList prune_list;
 
+    std::string buffer_type = GetProperty("logd.buffer_type", "serialized");
+
     // Partial (required for chatty) or full logging statistics.
     bool enable_full_log_statistics = __android_logger_property_get_bool(
             "logd.statistics", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_PERSIST |
                                        BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
-    LogStatistics log_statistics(enable_full_log_statistics);
+    LogStatistics log_statistics(enable_full_log_statistics, buffer_type == "serialized");
 
-    // Serves the purpose of managing the last logs times read on a
-    // socket connection, and as a reader lock on a range of log
-    // entries.
+    // Serves the purpose of managing the last logs times read on a socket connection, and as a
+    // reader lock on a range of log entries.
     LogReaderList reader_list;
 
     // LogBuffer is the object which is responsible for holding all log entries.
-    LogBuffer* logBuf;
-    if (true) {
-        logBuf = new ChattyLogBuffer(&reader_list, &log_tags, &prune_list, &log_statistics);
+    LogBuffer* log_buffer = nullptr;
+    if (buffer_type == "chatty") {
+        log_buffer = new ChattyLogBuffer(&reader_list, &log_tags, &prune_list, &log_statistics);
+    } else if (buffer_type == "serialized") {
+        log_buffer = new SerializedLogBuffer(&reader_list, &log_tags, &log_statistics);
+    } else if (buffer_type == "simple") {
+        log_buffer = new SimpleLogBuffer(&reader_list, &log_tags, &log_statistics);
     } else {
-        logBuf = new SimpleLogBuffer(&reader_list, &log_tags, &log_statistics);
+        LOG(FATAL) << "buffer_type must be one of 'chatty', 'serialized', or 'simple'";
     }
 
     // LogReader listens on /dev/socket/logdr. When a client
     // connects, log entries in the LogBuffer are written to the client.
-    LogReader* reader = new LogReader(logBuf, &reader_list);
+    LogReader* reader = new LogReader(log_buffer, &reader_list);
     if (reader->startListener()) {
         return EXIT_FAILURE;
     }
@@ -292,14 +292,14 @@
     // LogListener listens on /dev/socket/logdw for client
     // initiated log messages. New log entries are added to LogBuffer
     // and LogReader is notified to send updates to connected clients.
-    LogListener* swl = new LogListener(logBuf);
+    LogListener* swl = new LogListener(log_buffer);
     if (!swl->StartListener()) {
         return EXIT_FAILURE;
     }
 
     // Command listener listens on /dev/socket/logd for incoming logd
     // administrative commands.
-    CommandListener* cl = new CommandListener(logBuf, &log_tags, &prune_list, &log_statistics);
+    CommandListener* cl = new CommandListener(log_buffer, &log_tags, &prune_list, &log_statistics);
     if (cl->startListener()) {
         return EXIT_FAILURE;
     }
@@ -312,12 +312,12 @@
         int dmesg_fd = __android_logger_property_get_bool("ro.logd.auditd.dmesg", BOOL_DEFAULT_TRUE)
                                ? fdDmesg
                                : -1;
-        al = new LogAudit(logBuf, dmesg_fd, &log_statistics);
+        al = new LogAudit(log_buffer, dmesg_fd, &log_statistics);
     }
 
     LogKlog* kl = nullptr;
     if (klogd) {
-        kl = new LogKlog(logBuf, fdDmesg, fdPmesg, al != nullptr, &log_statistics);
+        kl = new LogKlog(log_buffer, fdDmesg, fdPmesg, al != nullptr, &log_statistics);
     }
 
     readDmesg(al, kl);
diff --git a/logd/rwlock.h b/logd/rwlock.h
index 2b27ff1..c37721e 100644
--- a/logd/rwlock.h
+++ b/logd/rwlock.h
@@ -43,7 +43,7 @@
 
 class SCOPED_CAPABILITY SharedLock {
   public:
-    SharedLock(RwLock& lock) ACQUIRE_SHARED(lock) : lock_(lock) { lock_.lock_shared(); }
+    explicit SharedLock(RwLock& lock) ACQUIRE_SHARED(lock) : lock_(lock) { lock_.lock_shared(); }
     ~SharedLock() RELEASE() { lock_.unlock(); }
 
     void lock_shared() ACQUIRE_SHARED() { lock_.lock_shared(); }
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index a9d0ed0..77fa94e 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -120,6 +120,27 @@
 LOCAL_POST_INSTALL_CMD += ; ln -sf /vendor/odm/priv-app $(TARGET_ROOT_OUT)/odm/priv-app
 LOCAL_POST_INSTALL_CMD += ; ln -sf /vendor/odm/usr $(TARGET_ROOT_OUT)/odm/usr
 
+
+# For /vendor_dlkm partition.
+LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/vendor_dlkm
+# For Treble Generic System Image (GSI), system-as-root GSI needs to work on
+# both devices with and without /vendor_dlkm partition. Those symlinks are for
+# devices without /vendor_dlkm partition. For devices with /vendor_dlkm
+# partition, mount vendor_dlkm.img under /vendor_dlkm will hide those symlinks.
+# Note that /vendor_dlkm/lib is omitted because vendor DLKMs should be accessed
+# via /vendor/lib/modules directly.
+LOCAL_POST_INSTALL_CMD += ; ln -sf /vendor/vendor_dlkm/etc $(TARGET_ROOT_OUT)/vendor_dlkm/etc
+
+# For /odm_dlkm partition.
+LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/odm_dlkm
+# For Treble Generic System Image (GSI), system-as-root GSI needs to work on
+# both devices with and without /odm_dlkm partition. Those symlinks are for
+# devices without /odm_dlkm partition. For devices with /odm_dlkm
+# partition, mount odm_dlkm.img under /odm_dlkm will hide those symlinks.
+# Note that /odm_dlkm/lib is omitted because odm DLKMs should be accessed
+# via /odm/lib/modules directly.
+LOCAL_POST_INSTALL_CMD += ; ln -sf /odm/odm_dlkm/etc $(TARGET_ROOT_OUT)/odm_dlkm/etc
+
 ifdef BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
   LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/cache
 else
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 01551e2..fb58432 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -445,6 +445,9 @@
     # Load persist properties and override properties (if enabled) from /data.
     trigger load_persist_props_action
 
+    # Should be before netd, but after apex, properties and logging is available.
+    trigger load_bpf_programs
+
     # Now we can start zygote for devices with file based encryption
     trigger zygote-start
 
@@ -517,6 +520,13 @@
 
     mkdir /metadata/apex 0700 root system
     mkdir /metadata/apex/sessions 0700 root system
+    # On some devices we see a weird behaviour in which /metadata/apex doesn't
+    # have a correct label. To workaround this bug, explicitly call restorecon
+    # on /metadata/apex. For most of the boot sequences /metadata/apex will
+    # already have a correct selinux label, meaning that this call will be a
+    # no-op.
+    restorecon_recursive /metadata/apex
+
 on late-fs
     # Ensure that tracefs has the correct permissions.
     # This does not work correctly if it is called in post-fs.
@@ -863,6 +873,7 @@
     # are not aware of using fsync()/sync() to prepare sudden power-cut.
     write /sys/fs/f2fs/${dev.mnt.blk.data}/cp_interval 200
     write /sys/fs/f2fs/${dev.mnt.blk.data}/gc_urgent_sleep_time 50
+    write /sys/fs/f2fs/${dev.mnt.blk.data}/iostat_enable 1
 
     # limit discard size to 128MB in order to avoid long IO latency
     # for filesystem tuning first (dm or sda)
diff --git a/toolbox/OWNERS b/toolbox/OWNERS
index 682a067..7529cb9 100644
--- a/toolbox/OWNERS
+++ b/toolbox/OWNERS
@@ -1 +1 @@
-enh@google.com
+include platform/system/core:/janitors/OWNERS
diff --git a/toolbox/modprobe.cpp b/toolbox/modprobe.cpp
index 1b5f54e..7df7b71 100644
--- a/toolbox/modprobe.cpp
+++ b/toolbox/modprobe.cpp
@@ -17,11 +17,16 @@
 #include <ctype.h>
 #include <getopt.h>
 #include <stdlib.h>
-#include <iostream>
 
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/strings.h>
 #include <modprobe/modprobe.h>
 
+namespace {
+
 enum modprobe_mode {
     AddModulesMode,
     RemoveModulesMode,
@@ -29,50 +34,107 @@
     ShowDependenciesMode,
 };
 
-static void print_usage(void) {
-    std::cerr << "Usage:" << std::endl;
-    std::cerr << std::endl;
-    std::cerr << "  modprobe [-alrqvsDb] [-d DIR] [MODULE]+" << std::endl;
-    std::cerr << "  modprobe [-alrqvsDb] [-d DIR] MODULE [symbol=value][...]" << std::endl;
-    std::cerr << std::endl;
-    std::cerr << "Options:" << std::endl;
-    std::cerr << "  -b: Apply blacklist to module names too" << std::endl;
-    std::cerr << "  -d: Load modules from DIR, option may be used multiple times" << std::endl;
-    std::cerr << "  -D: Print dependencies for modules only, do not load";
-    std::cerr << "  -h: Print this help" << std::endl;
-    std::cerr << "  -l: List modules matching pattern" << std::endl;
-    std::cerr << "  -r: Remove MODULE (multiple modules may be specified)" << std::endl;
-    std::cerr << "  -q: Quiet" << std::endl;
-    std::cerr << "  -v: Verbose" << std::endl;
-    std::cerr << std::endl;
+void print_usage(void) {
+    LOG(INFO) << "Usage:";
+    LOG(INFO);
+    // -d option is required on Android
+    LOG(INFO) << "  modprobe [options] -d DIR [--all=FILE|MODULE]...";
+    LOG(INFO) << "  modprobe [options] -d DIR MODULE [symbol=value]...";
+    LOG(INFO);
+    LOG(INFO) << "Options:";
+    LOG(INFO) << "  --all=FILE: FILE to acquire module names from";
+    LOG(INFO) << "  -b, --use-blocklist: Apply blocklist to module names too";
+    LOG(INFO) << "  -d, --dirname=DIR: Load modules from DIR, option may be used multiple times";
+    LOG(INFO) << "  -D, --show-depends: Print dependencies for modules only, do not load";
+    LOG(INFO) << "  -h, --help: Print this help";
+    LOG(INFO) << "  -l, --list: List modules matching pattern";
+    LOG(INFO) << "  -r, --remove: Remove MODULE (multiple modules may be specified)";
+    LOG(INFO) << "  -s, --syslog: print to syslog also";
+    LOG(INFO) << "  -q, --quiet: disable messages";
+    LOG(INFO) << "  -v, --verbose: enable more messages, even more with a second -v";
+    LOG(INFO);
 }
 
-#define check_mode()                                                      \
-    if (mode != AddModulesMode) {                                         \
-        std::cerr << "Error, multiple mode flags specified" << std::endl; \
-        print_usage();                                                    \
-        return EXIT_FAILURE;                                              \
+#define check_mode()                                   \
+    if (mode != AddModulesMode) {                      \
+        LOG(ERROR) << "multiple mode flags specified"; \
+        print_usage();                                 \
+        return EXIT_FAILURE;                           \
     }
 
+std::string stripComments(const std::string& str) {
+    for (std::string rv = str;;) {
+        auto comment = rv.find('#');
+        if (comment == std::string::npos) return rv;
+        auto end = rv.find('\n', comment);
+        if (end != std::string::npos) end = end - comment;
+        rv.erase(comment, end);
+    }
+    /* NOTREACHED */
+}
+
+auto syslog = false;
+
+void MyLogger(android::base::LogId id, android::base::LogSeverity severity, const char* tag,
+              const char* file, unsigned int line, const char* message) {
+    android::base::StdioLogger(id, severity, tag, file, line, message);
+    if (syslog && message[0]) {
+        android::base::KernelLogger(id, severity, tag, file, line, message);
+    }
+}
+
+}  // anonymous namespace
+
 extern "C" int modprobe_main(int argc, char** argv) {
+    android::base::InitLogging(argv, MyLogger);
+    android::base::SetMinimumLogSeverity(android::base::INFO);
+
     std::vector<std::string> modules;
     std::string module_parameters;
+    std::string mods;
     std::vector<std::string> mod_dirs;
     modprobe_mode mode = AddModulesMode;
-    bool blacklist = false;
-    bool verbose = false;
+    bool blocklist = false;
     int rv = EXIT_SUCCESS;
 
     int opt;
-    while ((opt = getopt(argc, argv, "abd:Dhlqrv")) != -1) {
+    int option_index = 0;
+    // NB: We have non-standard short options -l and -D to make it easier for
+    // OEMs to transition from toybox.
+    // clang-format off
+    static struct option long_options[] = {
+        { "all",                 optional_argument, 0, 'a' },
+        { "use-blocklist",       no_argument,       0, 'b' },
+        { "dirname",             required_argument, 0, 'd' },
+        { "show-depends",        no_argument,       0, 'D' },
+        { "help",                no_argument,       0, 'h' },
+        { "list",                no_argument,       0, 'l' },
+        { "quiet",               no_argument,       0, 'q' },
+        { "remove",              no_argument,       0, 'r' },
+        { "syslog",              no_argument,       0, 's' },
+        { "verbose",             no_argument,       0, 'v' },
+    };
+    // clang-format on
+    while ((opt = getopt_long(argc, argv, "a::bd:Dhlqrsv", long_options, &option_index)) != -1) {
         switch (opt) {
             case 'a':
                 // toybox modprobe supported -a to load multiple modules, this
-                // is supported here by default, ignore flag
+                // is supported here by default, ignore flag if no argument.
                 check_mode();
+                if (optarg == NULL) break;
+                if (!android::base::ReadFileToString(optarg, &mods)) {
+                    PLOG(ERROR) << "Failed to open " << optarg;
+                    rv = EXIT_FAILURE;
+                }
+                for (auto mod : android::base::Split(stripComments(mods), "\n")) {
+                    mod = android::base::Trim(mod);
+                    if (mod == "") continue;
+                    if (std::find(modules.begin(), modules.end(), mod) != modules.end()) continue;
+                    modules.emplace_back(mod);
+                }
                 break;
             case 'b':
-                blacklist = true;
+                blocklist = true;
                 break;
             case 'd':
                 mod_dirs.emplace_back(optarg);
@@ -82,24 +144,33 @@
                 mode = ShowDependenciesMode;
                 break;
             case 'h':
+                android::base::SetMinimumLogSeverity(android::base::INFO);
                 print_usage();
-                return EXIT_SUCCESS;
+                return rv;
             case 'l':
                 check_mode();
                 mode = ListModulesMode;
                 break;
             case 'q':
-                verbose = false;
+                android::base::SetMinimumLogSeverity(android::base::WARNING);
                 break;
             case 'r':
                 check_mode();
                 mode = RemoveModulesMode;
                 break;
+            case 's':
+                syslog = true;
+                break;
             case 'v':
-                verbose = true;
+                if (android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
+                    android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+                } else {
+                    android::base::SetMinimumLogSeverity(android::base::DEBUG);
+                }
                 break;
             default:
-                std::cerr << "Unrecognized option: " << opt << std::endl;
+                LOG(ERROR) << "Unrecognized option: " << opt;
+                print_usage();
                 return EXIT_FAILURE;
         }
     }
@@ -118,60 +189,54 @@
         }
     }
 
-    if (verbose) {
-        std::cout << "mode is " << mode << std::endl;
-        std::cout << "verbose is " << verbose << std::endl;
-        std::cout << "mod_dirs is: " << android::base::Join(mod_dirs, "") << std::endl;
-        std::cout << "modules is: " << android::base::Join(modules, "") << std::endl;
-        std::cout << "module parameters is: " << android::base::Join(module_parameters, "")
-                  << std::endl;
-    }
+    LOG(DEBUG) << "mode is " << mode;
+    LOG(DEBUG) << "mod_dirs is: " << android::base::Join(mod_dirs, " ");
+    LOG(DEBUG) << "modules is: " << android::base::Join(modules, " ");
+    LOG(DEBUG) << "module parameters is: " << android::base::Join(module_parameters, " ");
 
     if (modules.empty()) {
         if (mode == ListModulesMode) {
             // emulate toybox modprobe list with no pattern (list all)
             modules.emplace_back("*");
         } else {
-            std::cerr << "No modules given." << std::endl;
+            LOG(ERROR) << "No modules given.";
             print_usage();
             return EXIT_FAILURE;
         }
     }
     if (mod_dirs.empty()) {
-        std::cerr << "No module configuration directories given." << std::endl;
+        LOG(ERROR) << "No module configuration directories given.";
         print_usage();
         return EXIT_FAILURE;
     }
     if (parameter_count && modules.size() > 1) {
-        std::cerr << "Only one module may be loaded when specifying module parameters."
-                  << std::endl;
+        LOG(ERROR) << "Only one module may be loaded when specifying module parameters.";
         print_usage();
         return EXIT_FAILURE;
     }
 
     Modprobe m(mod_dirs);
-    m.EnableVerbose(verbose);
-    if (blacklist) {
-        m.EnableBlacklist(true);
+    if (blocklist) {
+        m.EnableBlocklist(true);
     }
 
     for (const auto& module : modules) {
         switch (mode) {
             case AddModulesMode:
                 if (!m.LoadWithAliases(module, true, module_parameters)) {
-                    std::cerr << "Failed to load module " << module;
+                    PLOG(ERROR) << "Failed to load module " << module;
                     rv = EXIT_FAILURE;
                 }
                 break;
             case RemoveModulesMode:
                 if (!m.Remove(module)) {
-                    std::cerr << "Failed to remove module " << module;
+                    PLOG(ERROR) << "Failed to remove module " << module;
                     rv = EXIT_FAILURE;
                 }
                 break;
             case ListModulesMode: {
                 std::vector<std::string> list = m.ListModules(module);
-                std::cout << android::base::Join(list, "\n") << std::endl;
+                LOG(INFO) << android::base::Join(list, "\n");
                 break;
             }
             case ShowDependenciesMode: {
@@ -182,17 +247,17 @@
                     rv = EXIT_FAILURE;
                     break;
                 }
-                std::cout << "Dependencies for " << module << ":" << std::endl;
-                std::cout << "Soft pre-dependencies:" << std::endl;
-                std::cout << android::base::Join(pre_deps, "\n") << std::endl;
-                std::cout << "Hard dependencies:" << std::endl;
-                std::cout << android::base::Join(deps, "\n") << std::endl;
-                std::cout << "Soft post-dependencies:" << std::endl;
-                std::cout << android::base::Join(post_deps, "\n") << std::endl;
+                LOG(INFO) << "Dependencies for " << module << ":";
+                LOG(INFO) << "Soft pre-dependencies:";
+                LOG(INFO) << android::base::Join(pre_deps, "\n");
+                LOG(INFO) << "Hard dependencies:";
+                LOG(INFO) << android::base::Join(deps, "\n");
+                LOG(INFO) << "Soft post-dependencies:";
+                LOG(INFO) << android::base::Join(post_deps, "\n");
                 break;
             }
             default:
-                std::cerr << "Bad mode";
+                LOG(ERROR) << "Bad mode";
                 rv = EXIT_FAILURE;
         }
     }
diff --git a/trusty/trusty-test.mk b/trusty/trusty-test.mk
new file mode 100644
index 0000000..fd353d1
--- /dev/null
+++ b/trusty/trusty-test.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2020 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.
+
+PRODUCT_PACKAGES += \
+	spiproxyd \
diff --git a/trusty/utils/rpmb_dev/rpmb_dev.c b/trusty/utils/rpmb_dev/rpmb_dev.c
index af97eba..5de1efa 100644
--- a/trusty/utils/rpmb_dev/rpmb_dev.c
+++ b/trusty/utils/rpmb_dev/rpmb_dev.c
@@ -591,7 +591,7 @@
         return EXIT_SUCCESS;
     }
 
-    open_flags = O_RDWR;
+    open_flags = O_RDWR | O_SYNC;
     if (init) {
         open_flags |= O_CREAT | O_TRUNC;
     }
diff --git a/trusty/utils/spiproxyd/Android.bp b/trusty/utils/spiproxyd/Android.bp
new file mode 100644
index 0000000..c1d0987
--- /dev/null
+++ b/trusty/utils/spiproxyd/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2020 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.
+
+cc_binary {
+    name: "spiproxyd",
+    vendor: true,
+
+    srcs: [
+        "main.c",
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libtrusty",
+    ],
+
+    init_rc: [
+        "proxy.rc",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+}
diff --git a/trusty/utils/spiproxyd/main.c b/trusty/utils/spiproxyd/main.c
new file mode 100644
index 0000000..c10866b
--- /dev/null
+++ b/trusty/utils/spiproxyd/main.c
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#define LOG_TAG "spiproxyd"
+
+#include <assert.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <log/log.h>
+#include <stdlib.h>
+#include <string.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+
+int handle_msg(int trusty_dev_fd, int spi_dev_fd) {
+    int rc;
+    uint8_t msg_buf[4096];
+    size_t msg_len;
+
+    /* read request from SPI Trusty app */
+    rc = read(trusty_dev_fd, &msg_buf, sizeof(msg_buf));
+    if (rc < 0) {
+        ALOGE("failed (%d) to read request from TA\n", rc);
+        return rc;
+    }
+    msg_len = rc;
+
+    /* forward request to SPI host device */
+    rc = write(spi_dev_fd, &msg_buf, msg_len);
+    if (rc < 0 || (size_t)rc != msg_len) {
+        ALOGE("failed (%d) to forward request to host\n", rc);
+        return rc < 0 ? rc : -1;
+    }
+
+    /* read response from SPI host device */
+    rc = read(spi_dev_fd, &msg_buf, sizeof(msg_buf));
+    if (rc < 0) {
+        ALOGE("failed (%d) to read response from host\n", rc);
+        return rc;
+    }
+    msg_len = rc;
+
+    /* forward response to SPI Trusty app */
+    rc = write(trusty_dev_fd, &msg_buf, msg_len);
+    if (rc < 0 || (size_t)rc != msg_len) {
+        ALOGE("failed (%d) to forward response to TA\n", rc);
+        return rc < 0 ? rc : -1;
+    }
+
+    return 0;
+}
+
+int event_loop(int trusty_dev_fd, int spi_dev_fd) {
+    while (true) {
+        int rc = handle_msg(trusty_dev_fd, spi_dev_fd);
+        if (rc < 0) {
+            ALOGE("exiting event loop\n");
+            return EXIT_FAILURE;
+        }
+    }
+}
+
+static void show_usage() {
+    ALOGE("usage: spiproxyd -t TRUSTY_DEVICE -s SPI_DEVICE -p SPI_PROXY_PORT\n");
+}
+
+static void parse_args(int argc, char* argv[], const char** trusty_dev_name,
+                       const char** spi_dev_name, const char** spi_proxy_port) {
+    int opt;
+    while ((opt = getopt(argc, argv, "ht:s:p:")) != -1) {
+        switch (opt) {
+            case 'h':
+                show_usage();
+                exit(EXIT_SUCCESS);
+                break;
+            case 't':
+                *trusty_dev_name = strdup(optarg);
+                break;
+            case 's':
+                *spi_dev_name = strdup(optarg);
+                break;
+            case 'p':
+                *spi_proxy_port = strdup(optarg);
+                break;
+            default:
+                show_usage();
+                exit(EXIT_FAILURE);
+                break;
+        }
+    }
+
+    if (!*trusty_dev_name || !*spi_dev_name || !*spi_proxy_port) {
+        show_usage();
+        exit(EXIT_FAILURE);
+    }
+}
+
+int main(int argc, char* argv[]) {
+    int rc;
+    const char* trusty_dev_name = NULL;
+    const char* spi_dev_name = NULL;
+    const char* spi_proxy_port = NULL;
+    int trusty_dev_fd;
+    int spi_dev_fd;
+
+    parse_args(argc, argv, &trusty_dev_name, &spi_dev_name, &spi_proxy_port);
+
+    rc = tipc_connect(trusty_dev_name, spi_proxy_port);
+    if (rc < 0) {
+        ALOGE("failed (%d) to connect to SPI proxy port\n", rc);
+        return rc;
+    }
+    trusty_dev_fd = rc;
+
+    rc = open(spi_dev_name, O_RDWR, 0);
+    if (rc < 0) {
+        ALOGE("failed (%d) to open SPI device\n", rc);
+        return rc;
+    }
+    spi_dev_fd = rc;
+
+    return event_loop(trusty_dev_fd, spi_dev_fd);
+}
diff --git a/trusty/utils/spiproxyd/proxy.rc b/trusty/utils/spiproxyd/proxy.rc
new file mode 100644
index 0000000..7d63e6a
--- /dev/null
+++ b/trusty/utils/spiproxyd/proxy.rc
@@ -0,0 +1,20 @@
+# Copyright (C) 2020 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.
+
+service spiproxyd /vendor/bin/spiproxyd -t /dev/trusty-ipc-dev0 \
+        -s /dev/vport3p2 -p com.android.trusty.spi.proxy
+    class main
+    user system
+    group system
+    oneshot