Merge "[incremental/adb] fix signature size check"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 7933629..89bd66a 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -16,16 +16,25 @@
       "name": "adb_tls_connection_test"
     },
     {
+      "name": "CtsFsMgrTestCases"
+    },
+    {
       "name": "CtsInitTestCases"
     },
     {
+      "name": "CtsLiblogTestCases"
+    },
+    {
+      "name": "CtsLogdTestCases"
+    },
+    {
       "name": "debuggerd_test"
     },
     {
-      "name": "CtsFsMgrTestCases"
+      "name": "fs_mgr_vendor_overlay_test"
     },
     {
-      "name": "fs_mgr_vendor_overlay_test"
+      "name": "init_kill_services_test"
     },
     {
       "name": "libpackagelistparser_test"
@@ -58,5 +67,10 @@
     {
       "name": "propertyinfoserializer_tests"
     }
+  ],
+  "imports": [
+    {
+      "path": "frameworks/base/tests/StagedInstallTest"
+    }
   ]
 }
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/transport_mdns.cpp b/adb/client/transport_mdns.cpp
index 9611212..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);
     }
 }
 
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 86cf30d..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
 };
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 cd64599..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: [
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 0ae5787..88bb234 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -607,7 +607,7 @@
 
     LINFO << "Enabling ext4 metadata_csum on " << blk_device;
 
-    // requires to give last_fsck_time to current to avoid insane time.
+    // Must give `-T now` to prevent last_fsck_time from growing too large,
     // otherwise, tune2fs won't enable metadata_csum.
     const char* tune2fs_args[] = {TUNE2FS_BIN, "-O",        "metadata_csum,64bit,extent",
                                   "-T",        "now", blk_device.c_str()};
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/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..59cc140 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -27,58 +27,34 @@
 #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 {
 
-static bool BindMount(const std::string& source, const std::string& mount_point,
-                      bool recursive = false) {
-    unsigned long mountflags = MS_BIND;
-    if (recursive) {
-        mountflags |= MS_REC;
-    }
-    if (mount(source.c_str(), mount_point.c_str(), nullptr, mountflags, nullptr) == -1) {
+static bool BindMount(const std::string& source, const std::string& mount_point) {
+    if (mount(source.c_str(), mount_point.c_str(), nullptr, MS_BIND | MS_REC, nullptr) == -1) {
         PLOG(ERROR) << "Failed to bind mount " << source;
         return false;
     }
     return true;
 }
 
-static bool MakeShared(const std::string& mount_point, bool recursive = false) {
-    unsigned long mountflags = MS_SHARED;
-    if (recursive) {
-        mountflags |= MS_REC;
-    }
+static bool ChangeMount(const std::string& mount_point, unsigned long mountflags) {
     if (mount(nullptr, mount_point.c_str(), nullptr, mountflags, nullptr) == -1) {
-        PLOG(ERROR) << "Failed to change propagation type to shared";
-        return false;
-    }
-    return true;
-}
-
-static bool MakeSlave(const std::string& mount_point, bool recursive = false) {
-    unsigned long mountflags = MS_SLAVE;
-    if (recursive) {
-        mountflags |= MS_REC;
-    }
-    if (mount(nullptr, mount_point.c_str(), nullptr, mountflags, nullptr) == -1) {
-        PLOG(ERROR) << "Failed to change propagation type to slave";
-        return false;
-    }
-    return true;
-}
-
-static bool MakePrivate(const std::string& mount_point, bool recursive = false) {
-    unsigned long mountflags = MS_PRIVATE;
-    if (recursive) {
-        mountflags |= MS_REC;
-    }
-    if (mount(nullptr, mount_point.c_str(), nullptr, mountflags, nullptr) == -1) {
-        PLOG(ERROR) << "Failed to change propagation type to private";
+        PLOG(ERROR) << "Failed to remount " << mount_point << " as " << std::hex << mountflags;
         return false;
     }
     return true;
@@ -106,6 +82,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 +94,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 +104,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 +119,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 +147,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;
 
@@ -190,17 +192,17 @@
     // needed for /foo/bar, then we will make /foo/bar as a mount point (by
     // bind-mounting by to itself) and set the propagation type of the mount
     // point to private.
-    if (!MakeShared("/", true /*recursive*/)) return false;
+    if (!ChangeMount("/", MS_SHARED | MS_REC)) return false;
 
     // /apex is a private mountpoint to give different sets of APEXes for
     // the bootstrap and default mount namespaces. The processes running with
     // the bootstrap namespace get APEXes from the read-only partition.
-    if (!(MakePrivate("/apex"))) return false;
+    if (!(ChangeMount("/apex", MS_PRIVATE))) return false;
 
     // /linkerconfig is a private mountpoint to give a different linker configuration
     // based on the mount namespace. Subdirectory will be bind-mounted based on current mount
     // namespace
-    if (!(MakePrivate("/linkerconfig"))) return false;
+    if (!(ChangeMount("/linkerconfig", MS_PRIVATE))) return false;
 
     // The two mount namespaces present challenges for scoped storage, because
     // vold, which is responsible for most of the mounting, lives in the
@@ -231,15 +233,15 @@
     if (!mkdir_recursive("/mnt/user", 0755)) return false;
     if (!mkdir_recursive("/mnt/installer", 0755)) return false;
     if (!mkdir_recursive("/mnt/androidwritable", 0755)) return false;
-    if (!(BindMount("/mnt/user", "/mnt/installer", true))) return false;
-    if (!(BindMount("/mnt/user", "/mnt/androidwritable", true))) return false;
+    if (!(BindMount("/mnt/user", "/mnt/installer"))) return false;
+    if (!(BindMount("/mnt/user", "/mnt/androidwritable"))) return false;
     // First, make /mnt/installer and /mnt/androidwritable a slave bind mount
-    if (!(MakeSlave("/mnt/installer"))) return false;
-    if (!(MakeSlave("/mnt/androidwritable"))) return false;
+    if (!(ChangeMount("/mnt/installer", MS_SLAVE))) return false;
+    if (!(ChangeMount("/mnt/androidwritable", MS_SLAVE))) return false;
     // Then, make it shared again - effectively creating a new peer group, that
     // will be inherited by new mount namespaces.
-    if (!(MakeShared("/mnt/installer"))) return false;
-    if (!(MakeShared("/mnt/androidwritable"))) return false;
+    if (!(ChangeMount("/mnt/installer", MS_SHARED))) return false;
+    if (!(ChangeMount("/mnt/androidwritable", MS_SHARED))) return false;
 
     bootstrap_ns_fd.reset(OpenMountNamespace());
     bootstrap_ns_id = GetMountNamespaceId();
@@ -269,9 +271,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..a1e0969 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) {
@@ -952,7 +994,7 @@
                                       &property_infos)) {
             return;
         }
-        // Don't check for failure here, so we always have a sane list of properties.
+        // Don't check for failure here, since we don't always have all of these partitions.
         // E.g. In case of recovery, the vendor partition will not have mounted and we
         // still need the system / platform properties to function.
         if (access("/system_ext/etc/selinux/system_ext_property_contexts", R_OK) != -1) {
diff --git a/init/service_utils.cpp b/init/service_utils.cpp
index 05e632b..f2383d7 100644
--- a/init/service_utils.cpp
+++ b/init/service_utils.cpp
@@ -60,13 +60,14 @@
 Result<void> SetUpMountNamespace(bool remount_proc, bool remount_sys) {
     constexpr unsigned int kSafeFlags = MS_NODEV | MS_NOEXEC | MS_NOSUID;
 
-    // Recursively remount / as slave like zygote does so unmounting and mounting /proc
-    // doesn't interfere with the parent namespace's /proc mount. This will also
-    // prevent any other mounts/unmounts initiated by the service from interfering
-    // with the parent namespace but will still allow mount events from the parent
+    // Recursively remount / as MS_SLAVE like zygote does so that
+    // unmounting and mounting /proc doesn't interfere with the parent
+    // namespace's /proc mount. This will also prevent any other
+    // mounts/unmounts initiated by the service from interfering with the
+    // parent namespace but will still allow mount events from the parent
     // namespace to propagate to the child.
     if (mount("rootfs", "/", nullptr, (MS_SLAVE | MS_REC), nullptr) == -1) {
-        return ErrnoError() << "Could not remount(/) recursively as slave";
+        return ErrnoError() << "Could not remount(/) recursively as MS_SLAVE";
     }
 
     // umount() then mount() /proc and/or /sys
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/test_kill_services/init_kill_services_test.cpp b/init/test_kill_services/init_kill_services_test.cpp
index 7e543f2..66a3328 100644
--- a/init/test_kill_services/init_kill_services_test.cpp
+++ b/init/test_kill_services/init_kill_services_test.cpp
@@ -54,7 +54,7 @@
 TEST_P(InitKillServicesTest, KillCriticalProcesses) {
     ExpectKillingServiceRecovers(GetParam());
 
-    // sanity check init is still responding
+    // Ensure that init is still responding
     EXPECT_TRUE(SetProperty("test.death.test", "asdf"));
     EXPECT_EQ(GetProperty("test.death.test", ""), "asdf");
     EXPECT_TRUE(SetProperty("test.death.test", ""));
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/libbacktrace/backtrace_test.cpp b/libbacktrace/backtrace_test.cpp
index f4191b9..cc32b6d 100644
--- a/libbacktrace/backtrace_test.cpp
+++ b/libbacktrace/backtrace_test.cpp
@@ -1584,7 +1584,7 @@
   // Verify the flag is set.
   ASSERT_EQ(PROT_DEVICE_MAP, map.flags & PROT_DEVICE_MAP);
 
-  // Quick sanity checks.
+  // Quick basic checks of functionality.
   uint64_t offset;
   ASSERT_EQ(std::string(""), backtrace->GetFunctionName(device_map_uint, &offset));
   ASSERT_EQ(std::string(""), backtrace->GetFunctionName(device_map_uint, &offset, &map));
diff --git a/libcutils/ashmem-dev.cpp b/libcutils/ashmem-dev.cpp
index 8c232f0..20cd659 100644
--- a/libcutils/ashmem-dev.cpp
+++ b/libcutils/ashmem-dev.cpp
@@ -122,7 +122,8 @@
         return true;
     }
 
-    /* If its not a number, assume string, but check if its a sane string */
+    // Non-numeric should be a single ASCII character. Characters after the
+    // first are ignored.
     if (tolower(vndk_version[0]) < 'a' || tolower(vndk_version[0]) > 'z') {
         ALOGE("memfd: ro.vndk.version not defined or invalid (%s), this is mandated since P.\n",
               vndk_version.c_str());
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/libcutils/qtaguid.cpp b/libcutils/qtaguid.cpp
index b94d134..2fe877c 100644
--- a/libcutils/qtaguid.cpp
+++ b/libcutils/qtaguid.cpp
@@ -38,24 +38,24 @@
     int (*netdDeleteTagData)(uint32_t, uid_t);
 };
 
-int dummyTagSocket(int, uint32_t, uid_t) {
+int stubTagSocket(int, uint32_t, uid_t) {
     return -EREMOTEIO;
 }
 
-int dummyUntagSocket(int) {
+int stubUntagSocket(int) {
     return -EREMOTEIO;
 }
 
-int dummySetCounterSet(uint32_t, uid_t) {
+int stubSetCounterSet(uint32_t, uid_t) {
     return -EREMOTEIO;
 }
 
-int dummyDeleteTagData(uint32_t, uid_t) {
+int stubDeleteTagData(uint32_t, uid_t) {
     return -EREMOTEIO;
 }
 
 netdHandler initHandler(void) {
-    netdHandler handler = {dummyTagSocket, dummyUntagSocket, dummySetCounterSet, dummyDeleteTagData};
+    netdHandler handler = {stubTagSocket, stubUntagSocket, stubSetCounterSet, stubDeleteTagData};
 
     void* netdClientHandle = dlopen("libnetd_client.so", RTLD_NOW);
     if (!netdClientHandle) {
diff --git a/liblog/include/log/log_properties.h b/liblog/include/log/log_properties.h
index 3497d63..2a0230f 100644
--- a/liblog/include/log/log_properties.h
+++ b/liblog/include/log/log_properties.h
@@ -20,6 +20,7 @@
 extern "C" {
 #endif
 
+/* Returns `1` if the device is debuggable or `0` if not. */
 int __android_log_is_debuggable();
 
 #ifdef __cplusplus
diff --git a/liblog/include/private/android_logger.h b/liblog/include/private/android_logger.h
index d3b72bc..de4c430 100644
--- a/liblog/include/private/android_logger.h
+++ b/liblog/include/private/android_logger.h
@@ -144,14 +144,6 @@
 int __android_log_security_bswrite(int32_t tag, const char* payload);
 int __android_log_security(); /* Device Owner is present */
 
-#define BOOL_DEFAULT_FLAG_TRUE_FALSE 0x1
-#define BOOL_DEFAULT_FALSE 0x0        /* false if property not present   */
-#define BOOL_DEFAULT_TRUE 0x1         /* true if property not present    */
-#define BOOL_DEFAULT_FLAG_PERSIST 0x2 /* <key>, persist.<key>, ro.<key>  */
-#define BOOL_DEFAULT_FLAG_ENG 0x4     /* off for user                    */
-#define BOOL_DEFAULT_FLAG_SVELTE 0x8  /* off for low_ram                 */
-bool __android_logger_property_get_bool(const char* key, int flag);
-
 #define LOG_BUFFER_SIZE (256 * 1024) /* Tuned with ro.logd.size per-platform \
                                       */
 #define LOG_BUFFER_MIN_SIZE (64 * 1024UL)
diff --git a/liblog/liblog.map.txt b/liblog/liblog.map.txt
index 161fcf1..2e01101 100644
--- a/liblog/liblog.map.txt
+++ b/liblog/liblog.map.txt
@@ -85,7 +85,6 @@
     __android_log_pmsg_file_read;
     __android_log_pmsg_file_write;
     __android_logger_get_buffer_size;
-    __android_logger_property_get_bool;
     android_openEventTagMap;
     android_log_processBinaryLogBuffer;
     android_log_processLogBuffer;
diff --git a/liblog/properties.cpp b/liblog/properties.cpp
index f5e060c..9990137 100644
--- a/liblog/properties.cpp
+++ b/liblog/properties.cpp
@@ -294,33 +294,12 @@
 }
 
 int __android_log_is_debuggable() {
-  static uint32_t serial;
-  static struct cache_char tag_cache;
-  static const char key[] = "ro.debuggable";
-  int ret;
+  static int is_debuggable = [] {
+    char value[PROP_VALUE_MAX] = {};
+    return __system_property_get("ro.debuggable", value) > 0 && !strcmp(value, "1");
+  }();
 
-  if (tag_cache.c) { /* ro property does not change after set */
-    ret = tag_cache.c == '1';
-  } else if (lock()) {
-    struct cache_char temp_cache = {{NULL, 0xFFFFFFFF}, '\0'};
-    refresh_cache(&temp_cache, key);
-    ret = temp_cache.c == '1';
-  } else {
-    int change_detected = check_cache(&tag_cache.cache);
-    uint32_t current_serial = __system_property_area_serial();
-    if (current_serial != serial) {
-      change_detected = 1;
-    }
-    if (change_detected) {
-      refresh_cache(&tag_cache, key);
-      serial = current_serial;
-    }
-    ret = tag_cache.c == '1';
-
-    unlock();
-  }
-
-  return ret;
+  return is_debuggable;
 }
 
 /*
@@ -390,21 +369,6 @@
  * need not guess our intentions.
  */
 
-/* Property helper */
-static bool check_flag(const char* prop, const char* flag) {
-  const char* cp = strcasestr(prop, flag);
-  if (!cp) {
-    return false;
-  }
-  /* We only will document comma (,) */
-  static const char sep[] = ",:;|+ \t\f";
-  if ((cp != prop) && !strchr(sep, cp[-1])) {
-    return false;
-  }
-  cp += strlen(flag);
-  return !*cp || !!strchr(sep, *cp);
-}
-
 /* cache structure */
 struct cache_property {
   struct cache cache;
@@ -422,88 +386,8 @@
   __system_property_read(cache->cache.pinfo, 0, cache->property);
 }
 
-/* get boolean with the logger twist that supports eng adjustments */
-bool __android_logger_property_get_bool(const char* key, int flag) {
-  struct cache_property property = {{NULL, 0xFFFFFFFF}, {0}};
-  if (flag & BOOL_DEFAULT_FLAG_PERSIST) {
-    char newkey[strlen("persist.") + strlen(key) + 1];
-    snprintf(newkey, sizeof(newkey), "ro.%s", key);
-    refresh_cache_property(&property, newkey);
-    property.cache.pinfo = NULL;
-    property.cache.serial = 0xFFFFFFFF;
-    snprintf(newkey, sizeof(newkey), "persist.%s", key);
-    refresh_cache_property(&property, newkey);
-    property.cache.pinfo = NULL;
-    property.cache.serial = 0xFFFFFFFF;
-  }
-
-  refresh_cache_property(&property, key);
-
-  if (check_flag(property.property, "true")) {
-    return true;
-  }
-  if (check_flag(property.property, "false")) {
-    return false;
-  }
-  if (property.property[0]) {
-    flag &= ~(BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
-  }
-  if (check_flag(property.property, "eng")) {
-    flag |= BOOL_DEFAULT_FLAG_ENG;
-  }
-  /* this is really a "not" flag */
-  if (check_flag(property.property, "svelte")) {
-    flag |= BOOL_DEFAULT_FLAG_SVELTE;
-  }
-
-  /* Sanity Check */
-  if (flag & (BOOL_DEFAULT_FLAG_SVELTE | BOOL_DEFAULT_FLAG_ENG)) {
-    flag &= ~BOOL_DEFAULT_FLAG_TRUE_FALSE;
-    flag |= BOOL_DEFAULT_TRUE;
-  }
-
-  if ((flag & BOOL_DEFAULT_FLAG_SVELTE) &&
-      __android_logger_property_get_bool("ro.config.low_ram", BOOL_DEFAULT_FALSE)) {
-    return false;
-  }
-  if ((flag & BOOL_DEFAULT_FLAG_ENG) && !__android_log_is_debuggable()) {
-    return false;
-  }
-
-  return (flag & BOOL_DEFAULT_FLAG_TRUE_FALSE) != BOOL_DEFAULT_FALSE;
-}
-
 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 {
@@ -603,9 +487,12 @@
 
   default_size = do_cache2_property_size(&global);
   if (!default_size) {
-    default_size = __android_logger_property_get_bool("ro.config.low_ram", BOOL_DEFAULT_FALSE)
-                       ? LOG_BUFFER_MIN_SIZE /* 64K  */
-                       : LOG_BUFFER_SIZE;    /* 256K */
+    char value[PROP_VALUE_MAX] = {};
+    if (__system_property_get("ro.config.low_ram", value) == 0 || strcmp(value, "true") != 0) {
+      default_size = LOG_BUFFER_SIZE;
+    } else {
+      default_size = LOG_BUFFER_MIN_SIZE;
+    }
   }
 
   snprintf(key_persist, sizeof(key_persist), "%s.%s", global_tunable,
@@ -642,4 +529,4 @@
   return 1;
 }
 
-#endif
\ No newline at end of file
+#endif
diff --git a/liblog/tests/Android.bp b/liblog/tests/Android.bp
index 2a6424b..a17d90c 100644
--- a/liblog/tests/Android.bp
+++ b/liblog/tests/Android.bp
@@ -97,6 +97,7 @@
     cflags: ["-DNO_PSTORE"],
     test_suites: [
         "cts",
+        "device-tests",
         "vts10",
     ],
 }
diff --git a/liblog/tests/log_read_test.cpp b/liblog/tests/log_read_test.cpp
index 3e09617..7acd363 100644
--- a/liblog/tests/log_read_test.cpp
+++ b/liblog/tests/log_read_test.cpp
@@ -20,6 +20,7 @@
 
 #include <string>
 
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android/log.h>  // minimal logging API
 #include <gtest/gtest.h>
@@ -29,6 +30,8 @@
 // Do not use anything in log/log_time.h despite side effects of the above.
 #include <private/android_logger.h>
 
+using android::base::GetBoolProperty;
+
 TEST(liblog, android_logger_get_) {
 #ifdef __ANDROID__
   // This test assumes the log buffers are filled with noise from
@@ -38,31 +41,27 @@
 
   for (int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
     log_id_t id = static_cast<log_id_t>(i);
-    const char* name = android_log_id_to_name(id);
-    if (id != android_name_to_log_id(name)) {
-      continue;
-    }
-    fprintf(stderr, "log buffer %s\r", name);
+    std::string name = android_log_id_to_name(id);
+    fprintf(stderr, "log buffer %s\r", name.c_str());
     struct logger* logger;
     EXPECT_TRUE(NULL != (logger = android_logger_open(logger_list, id)));
     EXPECT_EQ(id, android_logger_get_id(logger));
     ssize_t get_log_size = android_logger_get_log_size(logger);
     /* security buffer is allowed to be denied */
-    if (strcmp("security", name)) {
-      EXPECT_LT(0, get_log_size);
+    if (name != "security") {
+      EXPECT_GT(get_log_size, 0);
       // crash buffer is allowed to be empty, that is actually healthy!
-      // kernel buffer is allowed to be empty on "user" builds
-      // stats buffer is allowed to be empty TEMPORARILY.
-      // TODO: remove stats buffer from here once we start to use it in
-      // framework (b/68266385).
-      EXPECT_LE(  // boolean 1 or 0 depending on expected content or empty
-          !!((strcmp("crash", name) != 0) &&
-             ((strcmp("kernel", name) != 0) ||
-              __android_logger_property_get_bool(
-                  "ro.logd.kernel", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG |
-                                        BOOL_DEFAULT_FLAG_SVELTE)) &&
-             (strcmp("stats", name) != 0)),
-          android_logger_get_log_readable_size(logger));
+      // stats buffer is no longer in use.
+      if (name == "crash" || name == "stats") {
+        continue;
+      }
+
+      // kernel buffer is empty if ro.logd.kernel is false
+      if (name == "kernel" && !GetBoolProperty("ro.logd.kernel", false)) {
+        continue;
+      }
+
+      EXPECT_LE(0, android_logger_get_log_readable_size(logger));
     } else {
       EXPECT_NE(0, get_log_size);
       if (get_log_size < 0) {
@@ -71,7 +70,6 @@
         EXPECT_LE(0, android_logger_get_log_readable_size(logger));
       }
     }
-    EXPECT_LT(0, android_logger_get_log_version(logger));
   }
 
   android_logger_list_close(logger_list);
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/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp
index 9c1621b..3b6cfd8 100644
--- a/libsysutils/src/NetlinkEvent.cpp
+++ b/libsysutils/src/NetlinkEvent.cpp
@@ -185,7 +185,6 @@
     if (!checkRtNetlinkLength(nh, sizeof(*ifaddr)))
         return false;
 
-    // Sanity check.
     int type = nh->nlmsg_type;
     if (type != RTM_NEWADDR && type != RTM_DELADDR) {
         SLOGE("parseIfAddrMessage on incorrect message type 0x%x\n", type);
@@ -349,7 +348,6 @@
     uint8_t type = nh->nlmsg_type;
     const char *msgname = rtMessageName(type);
 
-    // Sanity check.
     if (type != RTM_NEWROUTE && type != RTM_DELROUTE) {
         SLOGE("%s: incorrect message type %d (%s)\n", __func__, type, msgname);
         return false;
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 19c22c8..8cc780a 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -341,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 {
@@ -458,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 7392806..022dedf 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -78,6 +78,9 @@
         "libcutils",
         "liblog",
     ],
+    sanitize: {
+        misc_undefined: ["integer"],
+    },
 
     target: {
         android: {
@@ -245,6 +248,7 @@
         "LruCache_test.cpp",
         "Mutex_test.cpp",
         "SharedBuffer_test.cpp",
+        "Singleton_test.cpp",
         "String8_test.cpp",
         "String16_test.cpp",
         "StrongPointer_test.cpp",
@@ -281,6 +285,11 @@
         },
     },
 
+    data_libs: [
+        "libutils_test_singleton1",
+        "libutils_test_singleton2",
+    ],
+
     cflags: [
         "-Wall",
         "-Wextra",
@@ -291,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",
@@ -324,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/Unicode.cpp b/libutils/Unicode.cpp
index b08e061..b6e457b 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -162,9 +162,9 @@
     if (index >= src_len) {
         return -1;
     }
-    size_t dummy_index;
+    size_t unused_index;
     if (next_index == nullptr) {
-        next_index = &dummy_index;
+        next_index = &unused_index;
     }
     size_t num_read;
     int32_t ret = utf32_at_internal(src + index, &num_read);
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/libutils/include/utils/Vector.h b/libutils/include/utils/Vector.h
index ddf71de..be35ea2 100644
--- a/libutils/include/utils/Vector.h
+++ b/libutils/include/utils/Vector.h
@@ -23,15 +23,13 @@
 #include <log/log.h>
 #include <utils/TypeHelpers.h>
 #include <utils/VectorImpl.h>
-
-/*
- * Used to blacklist some functions from CFI.
- *
- */
 #ifndef __has_attribute
 #define __has_attribute(x) 0
 #endif
 
+/*
+ * Used to exclude some functions from CFI.
+ */
 #if __has_attribute(no_sanitize)
 #define UTILS_VECTOR_NO_CFI __attribute__((no_sanitize("cfi")))
 #else
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index d15fa2b..c7d1634 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -616,15 +616,15 @@
                 if (long_options[option_index].name == wrap_str) {
                     mode |= ANDROID_LOG_WRAP | ANDROID_LOG_NONBLOCK;
                     // ToDo: implement API that supports setting a wrap timeout
-                    size_t dummy = ANDROID_LOG_WRAP_DEFAULT_TIMEOUT;
-                    if (optarg && (!ParseUint(optarg, &dummy) || dummy < 1)) {
+                    size_t timeout = ANDROID_LOG_WRAP_DEFAULT_TIMEOUT;
+                    if (optarg && (!ParseUint(optarg, &timeout) || timeout < 1)) {
                         error(EXIT_FAILURE, 0, "%s %s out of range.",
                               long_options[option_index].name, optarg);
                     }
-                    if (dummy != ANDROID_LOG_WRAP_DEFAULT_TIMEOUT) {
+                    if (timeout != ANDROID_LOG_WRAP_DEFAULT_TIMEOUT) {
                         fprintf(stderr, "WARNING: %s %u seconds, ignoring %zu\n",
                                 long_options[option_index].name, ANDROID_LOG_WRAP_DEFAULT_TIMEOUT,
-                                dummy);
+                                timeout);
                     }
                     break;
                 }
diff --git a/logd/Android.bp b/logd/Android.bp
index 036cb7e..265e19e 100644
--- a/logd/Android.bp
+++ b/logd/Android.bp
@@ -177,6 +177,27 @@
     },
     test_suites: [
         "cts",
+        "device-tests",
         "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/LogAudit.cpp b/logd/LogAudit.cpp
index 0ce9796..0e17476 100644
--- a/logd/LogAudit.cpp
+++ b/logd/LogAudit.cpp
@@ -32,7 +32,7 @@
 #include <sstream>
 
 #include <android-base/macros.h>
-#include <log/log_properties.h>
+#include <android-base/properties.h>
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 
@@ -40,6 +40,8 @@
 #include "LogUtils.h"
 #include "libaudit.h"
 
+using android::base::GetBoolProperty;
+
 #define KMSG_PRIORITY(PRI)                               \
     '<', '0' + LOG_MAKEPRI(LOG_AUTH, LOG_PRI(PRI)) / 10, \
         '0' + LOG_MAKEPRI(LOG_AUTH, LOG_PRI(PRI)) % 10, '>'
@@ -48,8 +50,8 @@
     : SocketListener(getLogSocket(), false),
       logbuf(buf),
       fdDmesg(fdDmesg),
-      main(__android_logger_property_get_bool("ro.logd.auditd.main", BOOL_DEFAULT_TRUE)),
-      events(__android_logger_property_get_bool("ro.logd.auditd.events", BOOL_DEFAULT_TRUE)),
+      main(GetBoolProperty("ro.logd.auditd.main", true)),
+      events(GetBoolProperty("ro.logd.auditd.events", true)),
       initialized(false),
       stats_(stats) {
     static const char auditd_message[] = { KMSG_PRIORITY(LOG_INFO),
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
index dd779f9..26affa8 100644
--- a/logd/LogBufferElement.cpp
+++ b/logd/LogBufferElement.cpp
@@ -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()),
     };
 }
 
diff --git a/logd/LogBufferTest.h b/logd/LogBufferTest.h
index 5d57ad1..1fd22c2 100644
--- a/logd/LogBufferTest.h
+++ b/logd/LogBufferTest.h
@@ -87,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 dbdf7fd..d6c7d25 100644
--- a/logd/LogKlog.cpp
+++ b/logd/LogKlog.cpp
@@ -381,8 +381,8 @@
         }
         if ((i == 9) && (cp[i] == ' ')) {
             int pid = 0;
-            char dummy;
-            if (sscanf(cp + 4, "%d%c", &pid, &dummy) == 2) {
+            char placeholder;
+            if (sscanf(cp + 4, "%d%c", &pid, &placeholder) == 2) {
                 buf = cp + 10;  // skip-it-all
                 return pid;
             }
@@ -392,8 +392,8 @@
         // Mediatek kernels with modified printk
         if (*cp == '[') {
             int pid = 0;
-            char dummy;
-            if (sscanf(cp, "[%d:%*[a-z_./0-9:A-Z]]%c", &pid, &dummy) == 2) {
+            char placeholder;
+            if (sscanf(cp, "[%d:%*[a-z_./0-9:A-Z]]%c", &pid, &placeholder) == 2) {
                 return pid;
             }
             break;  // Only the first one
@@ -703,7 +703,7 @@
         p = " ";
         b = 1;
     }
-    // paranoid sanity check, can not happen ...
+    // This shouldn't happen, but clamp the size if it does.
     if (b > LOGGER_ENTRY_MAX_PAYLOAD) {
         b = LOGGER_ENTRY_MAX_PAYLOAD;
     }
@@ -712,7 +712,7 @@
     }
     // calculate buffer copy requirements
     ssize_t n = 1 + taglen + 1 + b + 1;
-    // paranoid sanity check, first two just can not happen ...
+    // Extra checks for likely impossible cases.
     if ((taglen > n) || (b > n) || (n > (ssize_t)USHRT_MAX) || (n <= 0)) {
         return -EINVAL;
     }
@@ -722,7 +722,7 @@
     // If we malloc'd this buffer, we could get away without n's USHRT_MAX
     // test above, but we would then required a max(n, USHRT_MAX) as
     // truncating length argument to logbuf->log() below. Gain is protection
-    // of stack sanity and speedup, loss is truncated long-line content.
+    // against stack corruption and speedup, loss is truncated long-line content.
     char newstr[n];
     char* np = newstr;
 
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
index 3cd8fde..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;
     }
 }
@@ -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;
diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h
index 5f55802..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>
@@ -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:
@@ -505,20 +515,24 @@
     }
 
   public:
-    explicit 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/README.property b/logd/README.property
index ab9c4d4..8fd7f48 100644
--- a/logd/README.property
+++ b/logd/README.property
@@ -7,8 +7,8 @@
 ro.logd.auditd.events      bool   true   selinux audit messages sent to events.
 persist.logd.security      bool   false  Enable security buffer.
 ro.organization_owned      bool   false  Override persist.logd.security to false
-ro.logd.kernel             bool+ svelte+ Enable klogd daemon
-ro.logd.statistics         bool+ svelte+ Enable logcat -S statistics.
+ro.logd.kernel             bool  svelte+ Enable klogd daemon
+logd.statistics            bool  svelte+ Enable logcat -S statistics.
 ro.debuggable              number        if not "1", logd.statistics &
                                          ro.logd.kernel default false.
 logd.logpersistd.enable    bool   auto   Safe to start logpersist daemon service
@@ -52,13 +52,13 @@
 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
-  ro.debuggable is not "1") or "svelte" (forced false if ro.config.low_ram is
-  true).
 - svelte - see ro.config.low_ram for details.
-- svelte+ - see ro.config.low_ram and ro.debuggable for details.
+- svelte+ - If empty, default to true if `ro.config.low_ram == false && ro.debuggable == true`
 - ro - <base property> temporary override, ro.<base property> platform default.
 - persist - <base property> override, persist.<base property> platform default.
 - build - VERBOSE for native, DEBUG for jvm isLoggable, or developer option.
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/SerializedFlushToState.cpp b/logd/SerializedFlushToState.cpp
index 2633348..b02ccc3 100644
--- a/logd/SerializedFlushToState.cpp
+++ b/logd/SerializedFlushToState.cpp
@@ -31,7 +31,7 @@
 SerializedFlushToState::~SerializedFlushToState() {
     log_id_for_each(i) {
         if (log_positions_[i]) {
-            log_positions_[i]->buffer_it->DecReaderRefCount(true);
+            log_positions_[i]->buffer_it->DecReaderRefCount();
         }
     }
 }
@@ -78,7 +78,7 @@
             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(true);
+            buffer_it->DecReaderRefCount();
             ++buffer_it;
             buffer_it->IncReaderRefCount();
             log_positions_[log_id]->read_offset = 0;
@@ -134,7 +134,7 @@
     }
 
     // // Decrease the ref count since we're deleting our reference.
-    buffer_it->DecReaderRefCount(false);
+    buffer_it->DecReaderRefCount();
 
     // Delete in the reference.
     log_positions_[log_id].reset();
diff --git a/logd/SerializedLogBuffer.cpp b/logd/SerializedLogBuffer.cpp
index 0f30794..972a3f3 100644
--- a/logd/SerializedLogBuffer.cpp
+++ b/logd/SerializedLogBuffer.cpp
@@ -32,12 +32,6 @@
     Init();
 }
 
-SerializedLogBuffer::~SerializedLogBuffer() {
-    if (deleter_thread_.joinable()) {
-        deleter_thread_.join();
-    }
-}
-
 void SerializedLogBuffer::Init() {
     log_id_for_each(i) {
         if (SetSize(i, __android_logger_get_buffer_size(i))) {
@@ -109,69 +103,27 @@
 }
 
 void SerializedLogBuffer::MaybePrune(log_id_t log_id) {
-    auto get_total_size = [](const auto& buffer) {
-        size_t total_size = 0;
-        for (const auto& chunk : buffer) {
-            total_size += chunk.PruneSize();
-        }
-        return total_size;
-    };
-    size_t total_size = get_total_size(logs_[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: " << get_total_size(logs_[log_id]);
+                  << " after size: " << after_size;
     }
+
+    stats_->set_overhead(log_id, after_size);
 }
 
-void SerializedLogBuffer::StartDeleterThread() {
-    if (deleter_thread_running_) {
-        return;
+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();
     }
-    if (deleter_thread_.joinable()) {
-        deleter_thread_.join();
-    }
-    auto new_thread = std::thread([this] { DeleterThread(); });
-    deleter_thread_.swap(new_thread);
-    deleter_thread_running_ = true;
-}
-
-// Decompresses the chunks, call LogStatistics::Subtract() on each entry, then delete the chunks and
-// the list.  Note that the SerializedLogChunk objects have been removed from logs_ and their
-// references have been deleted from any SerializedFlushToState objects, so this can be safely done
-// without holding lock_.  It is done in a separate thread to avoid delaying the writer thread.
-void SerializedLogBuffer::DeleterThread() {
-    prctl(PR_SET_NAME, "logd.deleter");
-    while (true) {
-        std::list<SerializedLogChunk> local_chunks_to_delete;
-        log_id_t log_id;
-        {
-            auto lock = std::lock_guard{lock_};
-            log_id_for_each(i) {
-                if (!chunks_to_delete_[i].empty()) {
-                    local_chunks_to_delete = std::move(chunks_to_delete_[i]);
-                    chunks_to_delete_[i].clear();
-                    log_id = i;
-                    break;
-                }
-            }
-            if (local_chunks_to_delete.empty()) {
-                deleter_thread_running_ = false;
-                return;
-            }
-        }
-
-        for (auto& chunk : local_chunks_to_delete) {
-            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(false);
-        }
-    }
+    chunk.DecReaderRefCount();
 }
 
 void SerializedLogBuffer::NotifyReadersOfPrune(
@@ -197,8 +149,6 @@
         }
     }
 
-    StartDeleterThread();
-
     auto& log_buffer = logs_[log_id];
     auto it = log_buffer.begin();
     while (it != log_buffer.end()) {
@@ -206,7 +156,7 @@
             break;
         }
 
-        // Increment ahead of time since we're going to splice this iterator from the list.
+        // 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
@@ -222,8 +172,8 @@
             }
         } else {
             size_t buffer_size = it_to_prune->PruneSize();
-            chunks_to_delete_[log_id].splice(chunks_to_delete_[log_id].end(), log_buffer,
-                                             it_to_prune);
+            RemoveChunkFromStats(log_id, *it_to_prune);
+            log_buffer.erase(it_to_prune);
             if (buffer_size >= bytes_to_free) {
                 return true;
             }
@@ -349,19 +299,22 @@
     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_};
-    size_t retval = 2 * max_size_[id] / 3;  // See the comment in SetSize().
-    return retval;
+    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.
-// ChattyLogBuffer/SimpleLogBuffer don't consider the 'Overhead' of LogBufferElement or the
-// std::list<> overhead as part of the log size.  SerializedLogBuffer does by its very nature, so
-// the 'size' metric is not compatible between them.  Their actual memory usage is between 1.5x and
-// 2x of what they claim to use, so we conservatively set our internal size as size + size / 2.
 int SerializedLogBuffer::SetSize(log_id_t id, unsigned long size) {
     // Reasonable limits ...
     if (!__android_logger_valid_buffer_size(size)) {
@@ -369,7 +322,7 @@
     }
 
     auto lock = std::lock_guard{lock_};
-    max_size_[id] = size + size / 2;
+    max_size_[id] = size;
 
     MaybePrune(id);
 
diff --git a/logd/SerializedLogBuffer.h b/logd/SerializedLogBuffer.h
index 421d419..a03050e 100644
--- a/logd/SerializedLogBuffer.h
+++ b/logd/SerializedLogBuffer.h
@@ -37,7 +37,6 @@
 class SerializedLogBuffer final : public LogBuffer {
   public:
     SerializedLogBuffer(LogReaderList* reader_list, LogTags* tags, LogStatistics* stats);
-    ~SerializedLogBuffer();
     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,
@@ -61,9 +60,8 @@
             REQUIRES_SHARED(lock_);
     void NotifyReadersOfPrune(log_id_t log_id, const std::list<SerializedLogChunk>::iterator& chunk)
             REQUIRES(reader_list_->reader_threads_lock());
-
-    void StartDeleterThread() REQUIRES(lock_);
-    void DeleterThread();
+    void RemoveChunkFromStats(log_id_t log_id, SerializedLogChunk& chunk);
+    unsigned long GetSizeUsed(log_id_t id) REQUIRES(lock_);
 
     LogReaderList* reader_list_;
     LogTags* tags_;
@@ -73,9 +71,5 @@
     std::list<SerializedLogChunk> logs_[LOG_ID_MAX] GUARDED_BY(lock_);
     RwLock lock_;
 
-    std::list<SerializedLogChunk> chunks_to_delete_[LOG_ID_MAX] GUARDED_BY(lock_);
-    std::thread deleter_thread_ GUARDED_BY(lock_);
-    bool deleter_thread_running_ GUARDED_BY(lock_) = false;
-
     std::atomic<uint64_t> sequence_ = 1;
 };
diff --git a/logd/SerializedLogChunk.cpp b/logd/SerializedLogChunk.cpp
index e444856..de641d6 100644
--- a/logd/SerializedLogChunk.cpp
+++ b/logd/SerializedLogChunk.cpp
@@ -31,7 +31,6 @@
                   << " size used: " << write_offset_
                   << " compressed size: " << compressed_log_.size();
     }
-    contents_.Resize(0);
 }
 
 // TODO: Develop a better reference counting strategy to guard against the case where the writer is
@@ -44,13 +43,13 @@
     CompressionEngine::GetInstance().Decompress(compressed_log_, contents_);
 }
 
-void SerializedLogChunk::DecReaderRefCount(bool compress) {
+void SerializedLogChunk::DecReaderRefCount() {
     CHECK_NE(reader_ref_count_, 0U);
     if (--reader_ref_count_ != 0) {
         return;
     }
-    if (compress && !writer_active_) {
-        Compress();
+    if (!writer_active_) {
+        contents_.Resize(0);
     }
 }
 
@@ -83,18 +82,19 @@
     }
 
     if (new_write_offset == 0) {
-        DecReaderRefCount(false);
+        DecReaderRefCount();
         return true;
     }
 
-    // Clear the old compressed logs and set write_offset_ appropriately for DecReaderRefCount()
-    // to compress the new partially cleared log.
+    // 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(true);
+    DecReaderRefCount();
 
     return false;
 }
diff --git a/logd/SerializedLogChunk.h b/logd/SerializedLogChunk.h
index 65a1e86..0991eac 100644
--- a/logd/SerializedLogChunk.h
+++ b/logd/SerializedLogChunk.h
@@ -30,9 +30,7 @@
 
     void Compress();
     void IncReaderRefCount();
-    // Decrease the reader ref count and compress the log if appropriate.  `compress` should only be
-    // set to false in the case that the log buffer will be deleted afterwards.
-    void DecReaderRefCount(bool compress);
+    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);
@@ -44,12 +42,15 @@
     // 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 compressed_log_.size() ?: contents_.size(); }
+    size_t PruneSize() const {
+        return sizeof(*this) + (compressed_log_.size() ?: contents_.size());
+    }
 
     void FinishWriting() {
         writer_active_ = false;
+        Compress();
         if (reader_ref_count_ == 0) {
-            Compress();
+            contents_.Resize(0);
         }
     }
 
diff --git a/logd/SerializedLogChunkTest.cpp b/logd/SerializedLogChunkTest.cpp
index 6572503..f10b9c6 100644
--- a/logd/SerializedLogChunkTest.cpp
+++ b/logd/SerializedLogChunkTest.cpp
@@ -27,7 +27,7 @@
 TEST(SerializedLogChunk, smoke) {
     size_t chunk_size = 10 * 4096;
     auto chunk = SerializedLogChunk{chunk_size};
-    EXPECT_EQ(chunk_size, chunk.PruneSize());
+    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);
@@ -58,7 +58,7 @@
     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, chunk.PruneSize());
+    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)));
@@ -113,8 +113,7 @@
 TEST(SerializedLogChunk, catch_DecCompressedRef_CHECK) {
     size_t chunk_size = 10 * 4096;
     auto chunk = SerializedLogChunk{chunk_size};
-    EXPECT_DEATH({ chunk.DecReaderRefCount(true); }, "");
-    EXPECT_DEATH({ chunk.DecReaderRefCount(false); }, "");
+    EXPECT_DEATH({ chunk.DecReaderRefCount(); }, "");
 }
 
 // Check that the CHECK() in ClearUidLogs() if the ref count is greater than 0 is caught.
@@ -123,7 +122,7 @@
     auto chunk = SerializedLogChunk{chunk_size};
     chunk.IncReaderRefCount();
     EXPECT_DEATH({ chunk.ClearUidLogs(1000, LOG_ID_MAIN, nullptr); }, "");
-    chunk.DecReaderRefCount(false);
+    chunk.DecReaderRefCount();
 }
 
 class UidClearTest : public testing::TestWithParam<bool> {
@@ -144,7 +143,7 @@
         check(chunk_);
 
         if (finish_writing) {
-            chunk_.DecReaderRefCount(false);
+            chunk_.DecReaderRefCount();
         }
     }
 
diff --git a/logd/SerializedLogEntry.h b/logd/SerializedLogEntry.h
index f599abe..2f2c244 100644
--- a/logd/SerializedLogEntry.h
+++ b/logd/SerializedLogEntry.h
@@ -59,6 +59,7 @@
                 .msg_len = msg_len(),
                 .dropped_count = 0,
                 .log_id = log_id,
+                .total_len = total_len(),
         };
     }
 
diff --git a/logd/fuzz/log_buffer_log_fuzzer.cpp b/logd/fuzz/log_buffer_log_fuzzer.cpp
index 1dc996c..8309f95 100644
--- a/logd/fuzz/log_buffer_log_fuzzer.cpp
+++ b/logd/fuzz/log_buffer_log_fuzzer.cpp
@@ -114,7 +114,7 @@
     LogReaderList reader_list;
     LogTags tags;
     PruneList prune_list;
-    LogStatistics stats(true);
+    LogStatistics stats(true, true);
     std::unique_ptr<LogBuffer> log_buffer;
 #ifdef FUZZ_SERIALIZED
     log_buffer.reset(new SerializedLogBuffer(&reader_list, &tags, &stats));
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 e1ec52b..93dadf5 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>
@@ -61,6 +62,10 @@
 #include "SerializedLogBuffer.h"
 #include "SimpleLogBuffer.h"
 
+using android::base::GetBoolProperty;
+using android::base::GetProperty;
+using android::base::SetProperty;
+
 #define KMSG_PRIORITY(PRI)                                 \
     '<', '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) / 10, \
         '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) % 10, '>'
@@ -79,9 +84,10 @@
         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(FATAL) << "failed to clear PR_SET_DUMPABLE";
+    if (!GetBoolProperty("ro.debuggable", false)) {
+        if (prctl(PR_SET_DUMPABLE, 0) == -1) {
+            PLOG(FATAL) << "failed to clear PR_SET_DUMPABLE";
+        }
     }
 
     std::unique_ptr<struct _cap_struct, int (*)(void*)> caps(cap_init(), cap_free);
@@ -107,6 +113,14 @@
     }
 }
 
+// GetBoolProperty that defaults to true if `ro.debuggable == true && ro.config.low_rawm == false`.
+static bool GetBoolPropertyEngSvelteDefault(const std::string& name) {
+    bool default_value =
+            GetBoolProperty("ro.debuggable", false) && !GetBoolProperty("ro.config.low_ram", false);
+
+    return GetBoolProperty(name, default_value);
+}
+
 char* android::uidToName(uid_t u) {
     struct Userdata {
         uid_t uid;
@@ -233,10 +247,9 @@
     }
 
     int fdPmesg = -1;
-    bool klogd = __android_logger_property_get_bool(
-        "ro.logd.kernel",
-        BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
+    bool klogd = GetBoolPropertyEngSvelteDefault("ro.logd.kernel");
     if (klogd) {
+        SetProperty("ro.logd.kernel", "true");
         static const char proc_kmsg[] = "/proc/kmsg";
         fdPmesg = android_get_control_file(proc_kmsg);
         if (fdPmesg < 0) {
@@ -246,7 +259,7 @@
         if (fdPmesg < 0) PLOG(ERROR) << "Failed to open " << proc_kmsg;
     }
 
-    bool auditd = __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
+    bool auditd = GetBoolProperty("ro.logd.auditd", true);
     DropPrivs(klogd, auditd);
 
     // A cache of event log tags
@@ -255,28 +268,31 @@
     // Pruning configuration.
     PruneList prune_list;
 
-    // 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);
+    std::string buffer_type = GetProperty("logd.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.
+    // Partial (required for chatty) or full logging statistics.
+    LogStatistics log_statistics(GetBoolPropertyEngSvelteDefault("logd.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.
     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;
     }
@@ -284,14 +300,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;
     }
@@ -301,15 +317,13 @@
     // and LogReader is notified to send updates to connected clients.
     LogAudit* al = nullptr;
     if (auditd) {
-        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);
+        int dmesg_fd = GetBoolProperty("ro.logd.auditd.dmesg", true) ? fdDmesg : -1;
+        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/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp b/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
index a643062..3907413 100644
--- a/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
+++ b/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
@@ -139,7 +139,7 @@
 
   auto property_info_area = reinterpret_cast<const PropertyInfoArea*>(serialized_trie.data());
 
-  // Sanity check
+  // Smoke test
   auto root_node = property_info_area->root_node();
   EXPECT_STREQ("root", root_node.name());
   EXPECT_STREQ("default", property_info_area->context(root_node.context_index()));
diff --git a/qemu_pipe/qemu_pipe.cpp b/qemu_pipe/qemu_pipe.cpp
index beeccb0..03afb21 100644
--- a/qemu_pipe/qemu_pipe.cpp
+++ b/qemu_pipe/qemu_pipe.cpp
@@ -35,7 +35,6 @@
 #endif
 
 int qemu_pipe_open(const char* pipeName) {
-    // Sanity check.
     if (!pipeName) {
         errno = EINVAL;
         return -1;
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 fb6f1be..6ef3bdc 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -81,6 +81,11 @@
     # Mount tracefs
     mount tracefs tracefs /sys/kernel/tracing
 
+    # create sys dirctory
+    mkdir /dev/sys 0755 system system
+    mkdir /dev/sys/fs 0755 system system
+    mkdir /dev/sys/block 0755 system system
+
 # Run boringssl self test for each ABI so that later processes can skip it. http://b/139348610
 on early-init && property:ro.product.cpu.abilist32=*
     exec_start boringssl_self_test32
@@ -867,17 +872,26 @@
     chown root system /sys/block/zram0/writeback
     chmod 0664 /sys/block/zram0/writeback
 
+    # to access F2FS sysfs on dm-<num> directly
+    mkdir /dev/sys/fs/by-name 0755 system system
+    symlink /sys/fs/f2fs/${dev.mnt.blk.data} /dev/sys/fs/by-name/userdata
+
+    # to access dm-<num> sysfs
+    mkdir /dev/sys/block/by-name 0755 system system
+    symlink /sys/devices/virtual/block/${dev.mnt.blk.data} /dev/sys/block/by-name/userdata
+
     # F2FS tuning. Set cp_interval larger than dirty_expire_centisecs, 30 secs,
     # to avoid power consumption when system becomes mostly idle. Be careful
     # to make it too large, since it may bring userdata loss, if they
     # 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 /dev/sys/fs/by-name/userdata/cp_interval 200
+    write /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time 50
+    write /dev/sys/fs/by-name/userdata/iostat_enable 1
 
     # limit discard size to 128MB in order to avoid long IO latency
     # for filesystem tuning first (dm or sda)
     # Note that, if dm-<num> is used, sda/mmcblk0 should be tuned in vendor/init.rc
-    write /sys/devices/virtual/block/${dev.mnt.blk.data}/queue/discard_max_bytes 134217728
+    write /dev/sys/block/by-name/userdata/queue/discard_max_bytes 134217728
 
     # Permissions for System Server and daemons.
     chown system system /sys/power/autosleep
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/trusty/gatekeeper/trusty_gatekeeper.cpp b/trusty/gatekeeper/trusty_gatekeeper.cpp
index d149664..e416fb2 100644
--- a/trusty/gatekeeper/trusty_gatekeeper.cpp
+++ b/trusty/gatekeeper/trusty_gatekeeper.cpp
@@ -56,9 +56,9 @@
 
 SizedBuffer hidl_vec2sized_buffer(const hidl_vec<uint8_t>& vec) {
     if (vec.size() == 0 || vec.size() > std::numeric_limits<uint32_t>::max()) return {};
-    auto dummy = new uint8_t[vec.size()];
-    std::copy(vec.begin(), vec.end(), dummy);
-    return {dummy, static_cast<uint32_t>(vec.size())};
+    auto buffer = new uint8_t[vec.size()];
+    std::copy(vec.begin(), vec.end(), buffer);
+    return {buffer, static_cast<uint32_t>(vec.size())};
 }
 
 Return<void> TrustyGateKeeperDevice::enroll(uint32_t uid,
diff --git a/trusty/keymaster/TrustyKeymaster.cpp b/trusty/keymaster/TrustyKeymaster.cpp
index f3ef747..750a9d7 100644
--- a/trusty/keymaster/TrustyKeymaster.cpp
+++ b/trusty/keymaster/TrustyKeymaster.cpp
@@ -173,7 +173,7 @@
 }
 
 GetHmacSharingParametersResponse TrustyKeymaster::GetHmacSharingParameters() {
-    // Dummy empty buffer to allow ForwardCommand to have something to serialize
+    // Empty buffer to allow ForwardCommand to have something to serialize
     Buffer request;
     GetHmacSharingParametersResponse response;
     ForwardCommand(KM_GET_HMAC_SHARING_PARAMETERS, request, &response);
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