Merge "adb: convert more stuff to unique_fd."
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 7c57258..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,18 +0,0 @@
-#
-# Copyright (C) 2008 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.
-#
-LOCAL_PATH := $(my-dir)
-
-include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/TEST_MAPPING b/TEST_MAPPING
index f9c3b4a..bc5685b 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -7,6 +7,9 @@
       "name": "debuggerd_test"
     },
     {
+      "name": "fs_mgr_unit_test"
+    },
+    {
       "name": "init_tests"
     },
     {
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 9d4f280..8076909 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -686,6 +686,7 @@
     userdata.fs_mgr_flags.logical = true;
     userdata.fs_mgr_flags.quota = true;
     userdata.fs_mgr_flags.late_mount = true;
+    userdata.fs_mgr_flags.formattable = true;
     fstab->emplace_back(userdata);
 }
 
diff --git a/fs_mgr/fs_mgr_slotselect.cpp b/fs_mgr/fs_mgr_slotselect.cpp
index 3cb718c..41cd7dd 100644
--- a/fs_mgr/fs_mgr_slotselect.cpp
+++ b/fs_mgr/fs_mgr_slotselect.cpp
@@ -34,6 +34,12 @@
     return "";
 }
 
+// Returns "_b" or "_a", which is *the other* slot of androidboot.slot_suffix
+// in kernel cmdline, or an empty string if that parameter does not exist.
+std::string fs_mgr_get_other_slot_suffix() {
+    return other_suffix(fs_mgr_get_slot_suffix());
+}
+
 // Returns "_a" or "_b" based on androidboot.slot_suffix in kernel cmdline, or an empty string
 // if that parameter does not exist.
 std::string fs_mgr_get_slot_suffix() {
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index 4a05949..38f96c0 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -98,6 +98,7 @@
 int fs_mgr_is_fs_verity(const struct fstab_rec* fstab);
 
 std::string fs_mgr_get_slot_suffix();
+std::string fs_mgr_get_other_slot_suffix();
 std::set<std::string> fs_mgr_get_boot_devices();
 
 struct FstabEntry {
diff --git a/fs_mgr/libfiemap_writer/fiemap_writer.cpp b/fs_mgr/libfiemap_writer/fiemap_writer.cpp
index 164fc91..6ccdb57 100644
--- a/fs_mgr/libfiemap_writer/fiemap_writer.cpp
+++ b/fs_mgr/libfiemap_writer/fiemap_writer.cpp
@@ -359,6 +359,21 @@
     return moved_blocks_nr == 0;
 }
 
+bool FiemapWriter::HasPinnedExtents(const std::string& file_path) {
+    android::base::unique_fd fd(open(file_path.c_str(), O_NOFOLLOW | O_CLOEXEC | O_RDONLY));
+    if (fd < 0) {
+        PLOG(ERROR) << "open: " << file_path;
+        return false;
+    }
+
+    struct statfs64 sfs;
+    if (fstatfs64(fd, &sfs)) {
+        PLOG(ERROR) << "fstatfs64: " << file_path;
+        return false;
+    }
+    return IsFilePinned(fd, file_path, sfs.f_type);
+}
+
 static void LogExtent(uint32_t num, const struct fiemap_extent& ext) {
     LOG(INFO) << "Extent #" << num;
     LOG(INFO) << "  fe_logical:  " << ext.fe_logical;
diff --git a/fs_mgr/libfiemap_writer/fiemap_writer_test.cpp b/fs_mgr/libfiemap_writer/fiemap_writer_test.cpp
index 5101537..3d20ff3 100644
--- a/fs_mgr/libfiemap_writer/fiemap_writer_test.cpp
+++ b/fs_mgr/libfiemap_writer/fiemap_writer_test.cpp
@@ -51,6 +51,8 @@
         testfile = ::android::base::StringPrintf("%s/testdata/%s", exec_dir.c_str(), tinfo->name());
     }
 
+    void TearDown() override { unlink(testfile.c_str()); }
+
     // name of the file we use for testing
     std::string testfile;
 };
@@ -102,6 +104,12 @@
     EXPECT_EQ(invocations, 2);
 }
 
+TEST_F(FiemapWriterTest, CheckPinning) {
+    auto ptr = FiemapWriter::Open(testfile, 4096);
+    ASSERT_NE(ptr, nullptr);
+    EXPECT_TRUE(FiemapWriter::HasPinnedExtents(testfile));
+}
+
 TEST_F(FiemapWriterTest, CheckBlockDevicePath) {
     FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 4096);
     EXPECT_EQ(fptr->size(), 4096);
diff --git a/fs_mgr/libfiemap_writer/include/libfiemap_writer/fiemap_writer.h b/fs_mgr/libfiemap_writer/include/libfiemap_writer/fiemap_writer.h
index ab78f93..a0085cf 100644
--- a/fs_mgr/libfiemap_writer/include/libfiemap_writer/fiemap_writer.h
+++ b/fs_mgr/libfiemap_writer/include/libfiemap_writer/fiemap_writer.h
@@ -45,6 +45,18 @@
                                 bool create = true,
                                 std::function<bool(uint64_t, uint64_t)> progress = {});
 
+    // Check that a file still has the same extents since it was last opened with FiemapWriter,
+    // assuming the file was not resized outside of FiemapWriter. Returns false either on error
+    // or if the file was not pinned.
+    //
+    // This will always return true on Ext4. On F2FS, it will return true if either of the
+    // following cases are true:
+    //   - The file was never pinned.
+    //   - The file is pinned and has not been moved by the GC.
+    // Thus, this method should only be called for pinned files (such as those returned by
+    // FiemapWriter::Open).
+    static bool HasPinnedExtents(const std::string& file_path);
+
     // Syncs block device writes.
     bool Flush() const;
 
diff --git a/fs_mgr/libfs_avb/Android.bp b/fs_mgr/libfs_avb/Android.bp
index 6f368e4..191e803 100644
--- a/fs_mgr/libfs_avb/Android.bp
+++ b/fs_mgr/libfs_avb/Android.bp
@@ -56,6 +56,11 @@
         "tests/data/*",
     ],
     static_libs: [
+        "libavb",
+        "libavb_host_sysdeps",
+        "libdm",
+        "libfs_avb",
+        "libfstab",
         "libgtest_host",
     ],
     shared_libs: [
@@ -86,8 +91,12 @@
     static_libs: [
         "libfs_avb_test_util",
     ],
+    shared_libs: [
+        "libcrypto",
+    ],
     srcs: [
         "tests/basic_test.cpp",
+        "tests/fs_avb_test.cpp",
     ],
 }
 
@@ -96,10 +105,11 @@
     defaults: ["libfs_avb_host_test_defaults"],
     static_libs: [
         "libfs_avb_test_util",
-        "libfstab",
     ],
     srcs: [
+        "avb_util.cpp",
         "util.cpp",
+        "tests/avb_util_test.cpp",
         "tests/util_test.cpp",
     ],
 }
diff --git a/fs_mgr/libfs_avb/avb_ops.cpp b/fs_mgr/libfs_avb/avb_ops.cpp
index 3efa794..6a3e2c0 100644
--- a/fs_mgr/libfs_avb/avb_ops.cpp
+++ b/fs_mgr/libfs_avb/avb_ops.cpp
@@ -29,6 +29,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
+
 #include <string>
 
 #include <android-base/macros.h>
@@ -190,7 +191,8 @@
     // Copies avb_slot_data->vbmeta_images[].
     for (size_t i = 0; i < avb_slot_data->num_vbmeta_images; i++) {
         out_vbmeta_images->emplace_back(VBMetaData(avb_slot_data->vbmeta_images[i].vbmeta_data,
-                                                   avb_slot_data->vbmeta_images[i].vbmeta_size));
+                                                   avb_slot_data->vbmeta_images[i].vbmeta_size,
+                                                   avb_slot_data->vbmeta_images[i].partition_name));
     }
 
     // Free the local resource.
diff --git a/fs_mgr/libfs_avb/avb_util.cpp b/fs_mgr/libfs_avb/avb_util.cpp
index 0ceb6ee..f57c9d6 100644
--- a/fs_mgr/libfs_avb/avb_util.cpp
+++ b/fs_mgr/libfs_avb/avb_util.cpp
@@ -16,19 +16,67 @@
 
 #include "avb_util.h"
 
+#include <unistd.h>
+
 #include <array>
 #include <sstream>
 
 #include <android-base/file.h>
+#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 
 #include "util.h"
 
+using android::base::StartsWith;
 using android::base::unique_fd;
 
 namespace android {
 namespace fs_mgr {
 
+// Helper functions to print enum class VBMetaVerifyResult.
+const char* VBMetaVerifyResultToString(VBMetaVerifyResult result) {
+    // clang-format off
+    static const char* const name[] = {
+        "ResultSuccess",
+        "ResultError",
+        "ResultErrorVerification",
+        "ResultUnknown",
+    };
+    // clang-format on
+
+    uint32_t index = static_cast<uint32_t>(result);
+    uint32_t unknown_index = sizeof(name) / sizeof(char*) - 1;
+    if (index >= unknown_index) {
+        index = unknown_index;
+    }
+
+    return name[index];
+}
+
+std::ostream& operator<<(std::ostream& os, VBMetaVerifyResult result) {
+    os << VBMetaVerifyResultToString(result);
+    return os;
+}
+
+// class VBMetaData
+// ----------------
+std::unique_ptr<AvbVBMetaImageHeader> VBMetaData::GetVBMetaHeader(bool update_vbmeta_size) {
+    auto vbmeta_header(std::make_unique<AvbVBMetaImageHeader>());
+
+    if (!vbmeta_header) return nullptr;
+
+    /* Byteswap the header. */
+    avb_vbmeta_image_header_to_host_byte_order((AvbVBMetaImageHeader*)vbmeta_ptr_.get(),
+                                               vbmeta_header.get());
+    if (update_vbmeta_size) {
+        vbmeta_size_ = sizeof(AvbVBMetaImageHeader) +
+                       vbmeta_header->authentication_data_block_size +
+                       vbmeta_header->auxiliary_data_block_size;
+    }
+
+    return vbmeta_header;
+}
+
 // Constructs dm-verity arguments for sending DM_TABLE_LOAD ioctl to kernel.
 // See the following link for more details:
 // https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity
@@ -173,5 +221,300 @@
     return true;
 }
 
+// Converts a AVB partition_name (without A/B suffix) to a device partition name.
+// e.g.,       "system" => "system_a",
+//       "system_other" => "system_b".
+//
+// If the device is non-A/B, converts it to a partition name without suffix.
+// e.g.,       "system" => "system",
+//       "system_other" => "system".
+std::string AvbPartitionToDevicePatition(const std::string& avb_partition_name,
+                                         const std::string& ab_suffix,
+                                         const std::string& ab_other_suffix) {
+    bool is_other_slot = false;
+    std::string sanitized_partition_name(avb_partition_name);
+
+    auto other_suffix = sanitized_partition_name.rfind("_other");
+    if (other_suffix != std::string::npos) {
+        sanitized_partition_name.erase(other_suffix);  // converts system_other => system
+        is_other_slot = true;
+    }
+
+    auto append_suffix = is_other_slot ? ab_other_suffix : ab_suffix;
+    return sanitized_partition_name + append_suffix;
+}
+
+off64_t GetTotalSize(int fd) {
+    off64_t saved_current = lseek64(fd, 0, SEEK_CUR);
+    if (saved_current == -1) {
+        PERROR << "Failed to get current position";
+        return -1;
+    }
+
+    // lseek64() returns the resulting offset location from the beginning of the file.
+    off64_t total_size = lseek64(fd, 0, SEEK_END);
+    if (total_size == -1) {
+        PERROR << "Failed to lseek64 to end of the partition";
+        return -1;
+    }
+
+    // Restores the original offset.
+    if (lseek64(fd, saved_current, SEEK_SET) == -1) {
+        PERROR << "Failed to lseek64 to the original offset: " << saved_current;
+    }
+
+    return total_size;
+}
+
+std::unique_ptr<AvbFooter> GetAvbFooter(int fd) {
+    std::array<uint8_t, AVB_FOOTER_SIZE> footer_buf;
+    auto footer(std::make_unique<AvbFooter>());
+
+    off64_t footer_offset = GetTotalSize(fd) - AVB_FOOTER_SIZE;
+
+    ssize_t num_read =
+            TEMP_FAILURE_RETRY(pread64(fd, footer_buf.data(), AVB_FOOTER_SIZE, footer_offset));
+    if (num_read < 0 || num_read != AVB_FOOTER_SIZE) {
+        PERROR << "Failed to read AVB footer";
+        return nullptr;
+    }
+
+    if (!avb_footer_validate_and_byteswap((const AvbFooter*)footer_buf.data(), footer.get())) {
+        PERROR << "AVB footer verification failed.";
+        return nullptr;
+    }
+
+    return footer;
+}
+
+bool VerifyPublicKeyBlob(const uint8_t* key, size_t length, const std::string& expected_key_blob) {
+    if (expected_key_blob.empty()) {  // no expectation of the key, return true.
+        return true;
+    }
+    if (expected_key_blob.size() != length) {
+        return false;
+    }
+    if (0 == memcmp(key, expected_key_blob.data(), length)) {
+        return true;
+    }
+    return false;
+}
+
+VBMetaVerifyResult VerifyVBMetaSignature(const VBMetaData& vbmeta,
+                                         const std::string& expected_public_key_blob) {
+    const uint8_t* pk_data;
+    size_t pk_len;
+    ::AvbVBMetaVerifyResult vbmeta_ret;
+
+    vbmeta_ret = avb_vbmeta_image_verify(vbmeta.data(), vbmeta.size(), &pk_data, &pk_len);
+
+    switch (vbmeta_ret) {
+        case AVB_VBMETA_VERIFY_RESULT_OK:
+            if (pk_data == nullptr || pk_len <= 0) {
+                LERROR << vbmeta.partition()
+                       << ": Error verifying vbmeta image: failed to get public key";
+                return VBMetaVerifyResult::kError;
+            }
+            if (!VerifyPublicKeyBlob(pk_data, pk_len, expected_public_key_blob)) {
+                LERROR << vbmeta.partition() << ": Error verifying vbmeta image: public key used to"
+                       << " sign data does not match key in chain descriptor";
+                return VBMetaVerifyResult::kErrorVerification;
+            }
+            return VBMetaVerifyResult::kSuccess;
+        case AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED:
+        case AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH:
+        case AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH:
+            LERROR << vbmeta.partition() << ": Error verifying vbmeta image: "
+                   << avb_vbmeta_verify_result_to_string(vbmeta_ret);
+            return VBMetaVerifyResult::kErrorVerification;
+        case AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER:
+            // No way to continue this case.
+            LERROR << vbmeta.partition() << ": Error verifying vbmeta image: invalid vbmeta header";
+            break;
+        case AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION:
+            // No way to continue this case.
+            LERROR << vbmeta.partition()
+                   << ": Error verifying vbmeta image: unsupported AVB version";
+            break;
+        default:
+            LERROR << "Unknown vbmeta image verify return value: " << vbmeta_ret;
+            break;
+    }
+
+    return VBMetaVerifyResult::kError;
+}
+
+std::unique_ptr<VBMetaData> VerifyVBMetaData(int fd, const std::string& partition_name,
+                                             const std::string& expected_public_key_blob,
+                                             VBMetaVerifyResult* out_verify_result) {
+    uint64_t vbmeta_offset = 0;
+    uint64_t vbmeta_size = VBMetaData::kMaxVBMetaSize;
+    bool is_vbmeta_partition = StartsWith(partition_name, "vbmeta");
+
+    if (!is_vbmeta_partition) {
+        std::unique_ptr<AvbFooter> footer = GetAvbFooter(fd);
+        if (!footer) {
+            return nullptr;
+        }
+        vbmeta_offset = footer->vbmeta_offset;
+        vbmeta_size = footer->vbmeta_size;
+    }
+
+    if (vbmeta_size > VBMetaData::kMaxVBMetaSize) {
+        LERROR << "VbMeta size in footer exceeds kMaxVBMetaSize";
+        return nullptr;
+    }
+
+    auto vbmeta = std::make_unique<VBMetaData>(vbmeta_size, partition_name);
+    ssize_t num_read = TEMP_FAILURE_RETRY(pread64(fd, vbmeta->data(), vbmeta_size, vbmeta_offset));
+    // Allows partial read for vbmeta partition, because its vbmeta_size is kMaxVBMetaSize.
+    if (num_read < 0 || (!is_vbmeta_partition && static_cast<uint64_t>(num_read) != vbmeta_size)) {
+        PERROR << partition_name << ": Failed to read vbmeta at offset " << vbmeta_offset
+               << " with size " << vbmeta_size;
+        return nullptr;
+    }
+
+    auto verify_result = VerifyVBMetaSignature(*vbmeta, expected_public_key_blob);
+    if (out_verify_result != nullptr) *out_verify_result = verify_result;
+
+    if (verify_result == VBMetaVerifyResult::kSuccess ||
+        verify_result == VBMetaVerifyResult::kErrorVerification) {
+        return vbmeta;
+    }
+
+    return nullptr;
+}
+
+bool RollbackDetected(const std::string& partition_name ATTRIBUTE_UNUSED,
+                      uint64_t rollback_index ATTRIBUTE_UNUSED) {
+    // TODO(bowgotsai): Support rollback protection.
+    return false;
+}
+
+std::vector<ChainInfo> GetChainPartitionInfo(const VBMetaData& vbmeta, bool* fatal_error) {
+    CHECK(fatal_error != nullptr);
+    std::vector<ChainInfo> chain_partitions;
+
+    size_t num_descriptors;
+    std::unique_ptr<const AvbDescriptor* [], decltype(&avb_free)> descriptors(
+            avb_descriptor_get_all(vbmeta.data(), vbmeta.size(), &num_descriptors), avb_free);
+
+    if (!descriptors || num_descriptors < 1) {
+        return {};
+    }
+
+    for (size_t i = 0; i < num_descriptors; i++) {
+        AvbDescriptor desc;
+        if (!avb_descriptor_validate_and_byteswap(descriptors[i], &desc)) {
+            LERROR << "Descriptor[" << i << "] is invalid in vbmeta: " << vbmeta.partition();
+            *fatal_error = true;
+            return {};
+        }
+        if (desc.tag == AVB_DESCRIPTOR_TAG_CHAIN_PARTITION) {
+            AvbChainPartitionDescriptor chain_desc;
+            if (!avb_chain_partition_descriptor_validate_and_byteswap(
+                        (AvbChainPartitionDescriptor*)descriptors[i], &chain_desc)) {
+                LERROR << "Chain descriptor[" << i
+                       << "] is invalid in vbmeta: " << vbmeta.partition();
+                *fatal_error = true;
+                return {};
+            }
+            const char* chain_partition_name =
+                    ((const char*)descriptors[i]) + sizeof(AvbChainPartitionDescriptor);
+            const char* chain_public_key_blob =
+                    chain_partition_name + chain_desc.partition_name_len;
+            chain_partitions.emplace_back(
+                    std::string(chain_partition_name, chain_desc.partition_name_len),
+                    std::string(chain_public_key_blob, chain_desc.public_key_len));
+        }
+    }
+
+    return chain_partitions;
+}
+
+VBMetaVerifyResult LoadAndVerifyVbmetaImpl(
+        const std::string& partition_name, const std::string& ab_suffix,
+        const std::string& ab_other_suffix, const std::string& expected_public_key_blob,
+        bool allow_verification_error, bool load_chained_vbmeta, bool rollback_protection,
+        std::function<std::string(const std::string&)> device_path_constructor,
+        bool is_chained_vbmeta, std::vector<VBMetaData>* out_vbmeta_images) {
+    // Ensures the device path (might be a symlink created by init) is ready to access.
+    auto device_path = device_path_constructor(
+            AvbPartitionToDevicePatition(partition_name, ab_suffix, ab_other_suffix));
+    if (!WaitForFile(device_path, 1s)) {
+        PERROR << "No such partition: " << device_path;
+        return VBMetaVerifyResult::kError;
+    }
+
+    unique_fd fd(TEMP_FAILURE_RETRY(open(device_path.c_str(), O_RDONLY | O_CLOEXEC)));
+    if (fd < 0) {
+        PERROR << "Failed to open: " << device_path;
+        return VBMetaVerifyResult::kError;
+    }
+
+    VBMetaVerifyResult verify_result;
+    std::unique_ptr<VBMetaData> vbmeta =
+            VerifyVBMetaData(fd, partition_name, expected_public_key_blob, &verify_result);
+    if (!vbmeta) {
+        LERROR << partition_name << ": Failed to load vbmeta, result: " << verify_result;
+        return VBMetaVerifyResult::kError;
+    }
+
+    if (!allow_verification_error && verify_result == VBMetaVerifyResult::kErrorVerification) {
+        LERROR << partition_name << ": allow verification error is not allowed";
+        return VBMetaVerifyResult::kError;
+    }
+
+    std::unique_ptr<AvbVBMetaImageHeader> vbmeta_header =
+            vbmeta->GetVBMetaHeader(true /* update_vbmeta_size */);
+    if (!vbmeta_header) {
+        LERROR << partition_name << ": Failed to get vbmeta header";
+        return VBMetaVerifyResult::kError;
+    }
+
+    if (rollback_protection && RollbackDetected(partition_name, vbmeta_header->rollback_index)) {
+        return VBMetaVerifyResult::kError;
+    }
+
+    // vbmeta flags can only be set by the top-level vbmeta image.
+    if (is_chained_vbmeta && vbmeta_header->flags != 0) {
+        LERROR << partition_name << ": chained vbmeta image has non-zero flags";
+        return VBMetaVerifyResult::kError;
+    }
+
+    if (out_vbmeta_images) {
+        out_vbmeta_images->emplace_back(std::move(*vbmeta));
+    }
+
+    // If verification has been disabled by setting a bit in the image, we're done.
+    if (vbmeta_header->flags & AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED) {
+        LWARNING << "VERIFICATION_DISABLED bit is set for partition: " << partition_name;
+        return verify_result;
+    }
+
+    if (load_chained_vbmeta) {
+        bool fatal_error = false;
+        auto chain_partitions = GetChainPartitionInfo(*out_vbmeta_images->rbegin(), &fatal_error);
+        if (fatal_error) {
+            return VBMetaVerifyResult::kError;
+        }
+        for (auto& chain : chain_partitions) {
+            auto sub_ret = LoadAndVerifyVbmetaImpl(
+                    chain.partition_name, ab_suffix, ab_other_suffix, chain.public_key_blob,
+                    allow_verification_error, load_chained_vbmeta, rollback_protection,
+                    device_path_constructor, true, /* is_chained_vbmeta */
+                    out_vbmeta_images);
+            if (sub_ret != VBMetaVerifyResult::kSuccess) {
+                verify_result = sub_ret;  // might be 'ERROR' or 'ERROR VERIFICATION'.
+                if (verify_result == VBMetaVerifyResult::kError) {
+                    return verify_result;  // stop here if we got an 'ERROR'.
+                }
+            }
+        }
+    }
+
+    return verify_result;
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/libfs_avb/avb_util.h b/fs_mgr/libfs_avb/avb_util.h
index b81e931..babbfef 100644
--- a/fs_mgr/libfs_avb/avb_util.h
+++ b/fs_mgr/libfs_avb/avb_util.h
@@ -16,9 +16,11 @@
 
 #pragma once
 
+#include <ostream>
 #include <string>
 #include <vector>
 
+#include <fstab/fstab.h>
 #include <libavb/libavb.h>
 #include <libdm/dm.h>
 
@@ -27,6 +29,22 @@
 namespace android {
 namespace fs_mgr {
 
+enum class VBMetaVerifyResult {
+    kSuccess = 0,
+    kError = 1,
+    kErrorVerification = 2,
+};
+
+std::ostream& operator<<(std::ostream& os, VBMetaVerifyResult);
+
+struct ChainInfo {
+    std::string partition_name;
+    std::string public_key_blob;
+
+    ChainInfo(const std::string& chain_partition_name, const std::string& chain_public_key_blob)
+        : partition_name(chain_partition_name), public_key_blob(chain_public_key_blob) {}
+};
+
 // AvbHashtreeDescriptor to dm-verity table setup.
 bool GetHashtreeDescriptor(const std::string& partition_name,
                            const std::vector<VBMetaData>& vbmeta_images,
@@ -41,5 +59,37 @@
                            const std::string& salt, const std::string& root_digest,
                            bool wait_for_verity_dev);
 
+// Maps AVB partition name to a device partition name.
+std::string AvbPartitionToDevicePatition(const std::string& avb_partition_name,
+                                         const std::string& ab_suffix,
+                                         const std::string& ab_other_suffix);
+
+// AvbFooter and AvbMetaImage maninpulations.
+off64_t GetTotalSize(int fd);
+
+std::unique_ptr<AvbFooter> GetAvbFooter(int fd);
+
+std::unique_ptr<VBMetaData> VerifyVBMetaData(int fd, const std::string& partition_name,
+                                             const std::string& expected_public_key_blob,
+                                             VBMetaVerifyResult* out_verify_result);
+
+VBMetaVerifyResult VerifyVBMetaSignature(const VBMetaData& vbmeta,
+                                         const std::string& expected_public_key_blob);
+
+bool VerifyPublicKeyBlob(const uint8_t* key, size_t length, const std::string& expected_key_blob);
+
+// Detects if whether a partition contains a rollback image.
+bool RollbackDetected(const std::string& partition_name, uint64_t rollback_index);
+
+// Extracts chain partition info.
+std::vector<ChainInfo> GetChainPartitionInfo(const VBMetaData& vbmeta, bool* fatal_error);
+
+VBMetaVerifyResult LoadAndVerifyVbmetaImpl(
+        const std::string& partition_name, const std::string& ab_suffix,
+        const std::string& ab_other_suffix, const std::string& expected_public_key_blob,
+        bool allow_verification_error, bool load_chained_vbmeta, bool rollback_protection,
+        std::function<std::string(const std::string&)> device_path_constructor,
+        bool is_chained_vbmeta, std::vector<VBMetaData>* out_vbmeta_images);
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/libfs_avb/fs_avb.cpp b/fs_mgr/libfs_avb/fs_avb.cpp
index 957aa87..9f8ad53 100644
--- a/fs_mgr/libfs_avb/fs_avb.cpp
+++ b/fs_mgr/libfs_avb/fs_avb.cpp
@@ -39,6 +39,7 @@
 
 using android::base::Basename;
 using android::base::ParseUint;
+using android::base::ReadFileToString;
 using android::base::StringPrintf;
 
 namespace android {
@@ -59,6 +60,51 @@
     return std::make_pair(total_size, matched);
 }
 
+template <typename Hasher>
+std::pair<std::string, size_t> CalculateVbmetaDigest(const std::vector<VBMetaData>& vbmeta_images) {
+    std::string digest;
+    size_t total_size = 0;
+
+    Hasher hasher;
+    for (const auto& vbmeta : vbmeta_images) {
+        hasher.update(vbmeta.data(), vbmeta.size());
+        total_size += vbmeta.size();
+    }
+
+    // Converts digest bytes to a hex string.
+    digest = BytesToHex(hasher.finalize(), Hasher::DIGEST_SIZE);
+    return std::make_pair(digest, total_size);
+}
+
+// Helper functions to dump enum class AvbHandleStatus.
+const char* AvbHandleStatusToString(AvbHandleStatus status) {
+    // clang-format off
+    static const char* const name[] = {
+        "Success",
+        "Uninitialized",
+        "HashtreeDisabled",
+        "VerificationDisabled",
+        "VerificationError",
+        "Unknown",
+    };
+    // clang-format on
+
+    uint32_t index = static_cast<uint32_t>(status);
+    uint32_t unknown_index = sizeof(name) / sizeof(char*) - 1;
+    if (index >= unknown_index) {
+        index = unknown_index;
+    }
+
+    return name[index];
+}
+
+std::ostream& operator<<(std::ostream& os, AvbHandleStatus status) {
+    os << AvbHandleStatusToString(status);
+    return os;
+}
+
+// class AvbVerifier
+// -----------------
 // Reads the following values from kernel cmdline and provides the
 // VerifyVbmetaImages() to verify AvbSlotVerifyData.
 //   - androidboot.vbmeta.hash_alg
@@ -74,12 +120,6 @@
     AvbVerifier() = default;
 
   private:
-    enum HashAlgorithm {
-        kInvalid = 0,
-        kSHA256 = 1,
-        kSHA512 = 2,
-    };
-
     HashAlgorithm hash_alg_;
     uint8_t digest_[SHA512_DIGEST_LENGTH];
     size_t vbmeta_size_;
@@ -105,10 +145,10 @@
     fs_mgr_get_boot_config("vbmeta.hash_alg", &hash_alg);
     if (hash_alg == "sha256") {
         expected_digest_size = SHA256_DIGEST_LENGTH * 2;
-        avb_verifier->hash_alg_ = kSHA256;
+        avb_verifier->hash_alg_ = HashAlgorithm::kSHA256;
     } else if (hash_alg == "sha512") {
         expected_digest_size = SHA512_DIGEST_LENGTH * 2;
-        avb_verifier->hash_alg_ = kSHA512;
+        avb_verifier->hash_alg_ = HashAlgorithm::kSHA512;
     } else {
         LERROR << "Unknown hash algorithm: " << hash_alg.c_str();
         return nullptr;
@@ -140,10 +180,10 @@
     size_t total_size = 0;
     bool digest_matched = false;
 
-    if (hash_alg_ == kSHA256) {
+    if (hash_alg_ == HashAlgorithm::kSHA256) {
         std::tie(total_size, digest_matched) =
                 VerifyVbmetaDigest<SHA256Hasher>(vbmeta_images, digest_);
-    } else if (hash_alg_ == kSHA512) {
+    } else if (hash_alg_ == HashAlgorithm::kSHA512) {
         std::tie(total_size, digest_matched) =
                 VerifyVbmetaDigest<SHA512Hasher>(vbmeta_images, digest_);
     }
@@ -162,6 +202,98 @@
     return true;
 }
 
+// class AvbHandle
+// ---------------
+AvbUniquePtr AvbHandle::LoadAndVerifyVbmeta(
+        const std::string& partition_name, const std::string& ab_suffix,
+        const std::string& ab_other_suffix, const std::string& expected_public_key_path,
+        const HashAlgorithm& hash_algorithm, bool allow_verification_error,
+        bool load_chained_vbmeta, bool rollback_protection,
+        std::function<std::string(const std::string&)> custom_device_path) {
+    AvbUniquePtr avb_handle(new AvbHandle());
+    if (!avb_handle) {
+        LERROR << "Failed to allocate AvbHandle";
+        return nullptr;
+    }
+
+    std::string expected_key_blob;
+    if (!expected_public_key_path.empty()) {
+        if (access(expected_public_key_path.c_str(), F_OK) != 0) {
+            LERROR << "Expected public key path doesn't exist: " << expected_public_key_path;
+            return nullptr;
+        } else if (!ReadFileToString(expected_public_key_path, &expected_key_blob)) {
+            LERROR << "Failed to load: " << expected_public_key_path;
+            return nullptr;
+        }
+    }
+
+    auto android_by_name_symlink = [](const std::string& partition_name_with_ab) {
+        return "/dev/block/by-name/" + partition_name_with_ab;
+    };
+
+    auto device_path = custom_device_path ? custom_device_path : android_by_name_symlink;
+
+    auto verify_result = LoadAndVerifyVbmetaImpl(
+            partition_name, ab_suffix, ab_other_suffix, expected_key_blob, allow_verification_error,
+            load_chained_vbmeta, rollback_protection, device_path, false,
+            /* is_chained_vbmeta */ &avb_handle->vbmeta_images_);
+    switch (verify_result) {
+        case VBMetaVerifyResult::kSuccess:
+            avb_handle->status_ = AvbHandleStatus::kSuccess;
+            break;
+        case VBMetaVerifyResult::kErrorVerification:
+            avb_handle->status_ = AvbHandleStatus::kVerificationError;
+            break;
+        default:
+            LERROR << "LoadAndVerifyVbmetaImpl failed, result: " << verify_result;
+            return nullptr;
+    }
+
+    // Sets the MAJOR.MINOR for init to set it into "ro.boot.avb_version".
+    avb_handle->avb_version_ = StringPrintf("%d.%d", AVB_VERSION_MAJOR, AVB_VERSION_MINOR);
+
+    // Checks any disabled flag is set.
+    std::unique_ptr<AvbVBMetaImageHeader> vbmeta_header =
+            avb_handle->vbmeta_images_[0].GetVBMetaHeader();
+    bool verification_disabled = ((AvbVBMetaImageFlags)vbmeta_header->flags &
+                                  AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED);
+    bool hashtree_disabled =
+            ((AvbVBMetaImageFlags)vbmeta_header->flags & AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED);
+    if (verification_disabled) {
+        avb_handle->status_ = AvbHandleStatus::kVerificationDisabled;
+    } else if (hashtree_disabled) {
+        avb_handle->status_ = AvbHandleStatus::kHashtreeDisabled;
+    }
+
+    // Calculates the summary info for all vbmeta_images_;
+    std::string digest;
+    size_t total_size;
+    if (hash_algorithm == HashAlgorithm::kSHA256) {
+        std::tie(digest, total_size) =
+                CalculateVbmetaDigest<SHA256Hasher>(avb_handle->vbmeta_images_);
+    } else if (hash_algorithm == HashAlgorithm::kSHA512) {
+        std::tie(digest, total_size) =
+                CalculateVbmetaDigest<SHA512Hasher>(avb_handle->vbmeta_images_);
+    } else {
+        LERROR << "Invalid hash algorithm";
+        return nullptr;
+    }
+    avb_handle->vbmeta_info_ = VBMetaInfo(digest, hash_algorithm, total_size);
+
+    LINFO << "Returning avb_handle with status: " << avb_handle->status_;
+    return avb_handle;
+}
+
+AvbUniquePtr AvbHandle::LoadAndVerifyVbmeta() {
+    // Loads inline vbmeta images, starting from /vbmeta.
+    return LoadAndVerifyVbmeta("vbmeta", fs_mgr_get_slot_suffix(), fs_mgr_get_other_slot_suffix(),
+                               {} /* expected_public_key, already checked by bootloader */,
+                               HashAlgorithm::kSHA256,
+                               IsDeviceUnlocked(), /* allow_verification_error */
+                               true,               /* load_chained_vbmeta */
+                               false, /* rollback_protection, already checked by bootloader */
+                               nullptr /* custom_device_path */);
+}
 
 AvbUniquePtr AvbHandle::Open() {
     bool is_device_unlocked = IsDeviceUnlocked();
@@ -192,14 +324,14 @@
     //     for more details.
     switch (verify_result) {
         case AVB_SLOT_VERIFY_RESULT_OK:
-            avb_handle->status_ = kAvbHandleSuccess;
+            avb_handle->status_ = AvbHandleStatus::kSuccess;
             break;
         case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION:
             if (!is_device_unlocked) {
                 LERROR << "ERROR_VERIFICATION isn't allowed when the device is LOCKED";
                 return nullptr;
             }
-            avb_handle->status_ = kAvbHandleVerificationError;
+            avb_handle->status_ = AvbHandleStatus::kVerificationError;
             break;
         default:
             LERROR << "avb_slot_verify failed, result: " << verify_result;
@@ -220,7 +352,7 @@
                                   AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED);
 
     if (verification_disabled) {
-        avb_handle->status_ = kAvbHandleVerificationDisabled;
+        avb_handle->status_ = AvbHandleStatus::kVerificationDisabled;
     } else {
         // Verifies vbmeta structs against the digest passed from bootloader in kernel cmdline.
         std::unique_ptr<AvbVerifier> avb_verifier = AvbVerifier::Create();
@@ -237,7 +369,7 @@
         bool hashtree_disabled = ((AvbVBMetaImageFlags)vbmeta_header.flags &
                                   AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED);
         if (hashtree_disabled) {
-            avb_handle->status_ = kAvbHandleHashtreeDisabled;
+            avb_handle->status_ = AvbHandleStatus::kHashtreeDisabled;
         }
     }
 
@@ -246,11 +378,12 @@
 }
 
 AvbHashtreeResult AvbHandle::SetUpAvbHashtree(FstabEntry* fstab_entry, bool wait_for_verity_dev) {
-    if (!fstab_entry || status_ == kAvbHandleUninitialized || vbmeta_images_.size() < 1) {
+    if (!fstab_entry || status_ == AvbHandleStatus::kUninitialized || vbmeta_images_.size() < 1) {
         return AvbHashtreeResult::kFail;
     }
 
-    if (status_ == kAvbHandleHashtreeDisabled || status_ == kAvbHandleVerificationDisabled) {
+    if (status_ == AvbHandleStatus::kHashtreeDisabled ||
+        status_ == AvbHandleStatus::kVerificationDisabled) {
         LINFO << "AVB HASHTREE disabled on: " << fstab_entry->mount_point;
         return AvbHashtreeResult::kDisabled;
     }
diff --git a/fs_mgr/libfs_avb/include/fs_avb/fs_avb.h b/fs_mgr/libfs_avb/include/fs_avb/fs_avb.h
index eca6984..7af3c7e 100644
--- a/fs_mgr/libfs_avb/include/fs_avb/fs_avb.h
+++ b/fs_mgr/libfs_avb/include/fs_avb/fs_avb.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <functional>
 #include <memory>
 #include <string>
 #include <vector>
@@ -32,23 +33,56 @@
     kDisabled,
 };
 
+enum class HashAlgorithm {
+    kInvalid = 0,
+    kSHA256 = 1,
+    kSHA512 = 2,
+};
+
+enum class AvbHandleStatus {
+    kSuccess = 0,
+    kUninitialized = 1,
+    kHashtreeDisabled = 2,
+    kVerificationDisabled = 3,
+    kVerificationError = 4,
+};
+
+struct VBMetaInfo {
+    std::string digest;
+    HashAlgorithm hash_algorithm;
+    size_t total_size;
+
+    VBMetaInfo() {}
+
+    VBMetaInfo(std::string digest_value, HashAlgorithm algorithm, size_t size)
+        : digest(std::move(digest_value)), hash_algorithm(algorithm), total_size(size) {}
+};
+
 class VBMetaData {
   public:
     // Constructors
     VBMetaData() : vbmeta_ptr_(nullptr), vbmeta_size_(0){};
 
-    VBMetaData(const uint8_t* data, size_t size)
-        : vbmeta_ptr_(new (std::nothrow) uint8_t[size]), vbmeta_size_(size) {
+    VBMetaData(const uint8_t* data, size_t size, const std::string& partition_name)
+        : vbmeta_ptr_(new (std::nothrow) uint8_t[size]),
+          vbmeta_size_(size),
+          partition_name_(partition_name) {
         // The ownership of data is NOT transferred, i.e., the caller still
         // needs to release the memory as we make a copy here.
         memcpy(vbmeta_ptr_.get(), data, size * sizeof(uint8_t));
     }
 
-    explicit VBMetaData(size_t size)
-        : vbmeta_ptr_(new (std::nothrow) uint8_t[size]), vbmeta_size_(size) {}
+    explicit VBMetaData(size_t size, const std::string& partition_name)
+        : vbmeta_ptr_(new (std::nothrow) uint8_t[size]),
+          vbmeta_size_(size),
+          partition_name_(partition_name) {}
+
+    // Extracts vbmeta header from the vbmeta buffer, set update_vbmeta_size to
+    // true to update vbmeta_size_ to the actual size with valid content.
+    std::unique_ptr<AvbVBMetaImageHeader> GetVBMetaHeader(bool update_vbmeta_size = false);
 
     // Get methods for each data member.
-    const std::string& device_path() const { return device_path_; }
+    const std::string& partition() const { return partition_name_; }
     uint8_t* data() const { return vbmeta_ptr_.get(); }
     const size_t& size() const { return vbmeta_size_; }
 
@@ -56,9 +90,9 @@
     static const size_t kMaxVBMetaSize = 64 * 1024;
 
   private:
-    std::string device_path_;
     std::unique_ptr<uint8_t[]> vbmeta_ptr_;
     size_t vbmeta_size_;
+    std::string partition_name_;
 };
 
 class FsManagerAvbOps;
@@ -71,7 +105,7 @@
 // descriptors to load verity table into kernel through ioctl.
 class AvbHandle {
   public:
-    // The factory method to return a AvbUniquePtr that holds
+    // The factory methods to return a AvbUniquePtr that holds
     // the verified AVB (external/avb) metadata of all verified partitions
     // in vbmeta_images_.
     //
@@ -79,31 +113,40 @@
     //   - androidboot.vbmeta.{hash_alg, size, digest}.
     //
     // A typical usage will be:
-    //   - AvbUniquePtr handle = AvbHandle::Open();
+    //   - AvbUniquePtr handle = AvbHandle::Open(); or
+    //   - AvbUniquePtr handle = AvbHandle::LoadAndVerifyVbmeta();
     //
     // Possible return values:
     //   - nullptr: any error when reading and verifying the metadata,
     //     e.g., I/O error, digest value mismatch, size mismatch, etc.
     //
-    //   - a valid unique_ptr with status kAvbHandleHashtreeDisabled:
+    //   - a valid unique_ptr with status AvbHandleStatus::HashtreeDisabled:
     //     to support the existing 'adb disable-verity' feature in Android.
     //     It's very helpful for developers to make the filesystem writable to
     //     allow replacing binaries on the device.
     //
-    //   - a valid unique_ptr with status kAvbHandleVerificationDisabled:
+    //   - a valid unique_ptr with status AvbHandleStatus::VerificationDisabled:
     //     to support 'avbctl disable-verification': only the top-level
     //     vbmeta is read, vbmeta structs in other partitions are not processed.
     //     It's needed to bypass AVB when using the generic system.img to run
     //     VTS for project Treble.
     //
-    //   - a valid unique_ptr with status kAvbHandleVerificationError:
+    //   - a valid unique_ptr with status AvbHandleStatus::VerificationError:
     //     there is verification error when libavb loads vbmeta from each
     //     partition. This is only allowed when the device is unlocked.
     //
-    //   - a valid unique_ptr with status kAvbHandleSuccess: the metadata
+    //   - a valid unique_ptr with status AvbHandleStatus::Success: the metadata
     //     is verified and can be trusted.
     //
-    static AvbUniquePtr Open();
+    // TODO(bowgotsai): remove Open() and switch to LoadAndVerifyVbmeta().
+    static AvbUniquePtr Open();                 // loads inline vbmeta, via libavb.
+    static AvbUniquePtr LoadAndVerifyVbmeta();  // loads inline vbmeta.
+    static AvbUniquePtr LoadAndVerifyVbmeta(    // loads offline vbmeta.
+            const std::string& partition_name, const std::string& ab_suffix,
+            const std::string& ab_other_suffix, const std::string& expected_public_key,
+            const HashAlgorithm& hash_algorithm, bool allow_verification_error,
+            bool load_chained_vbmeta, bool rollback_protection,
+            std::function<std::string(const std::string&)> custom_device_path = nullptr);
 
     // Sets up dm-verity on the given fstab entry.
     // The 'wait_for_verity_dev' parameter makes this function wait for the
@@ -118,6 +161,8 @@
     AvbHashtreeResult SetUpAvbHashtree(FstabEntry* fstab_entry, bool wait_for_verity_dev);
 
     const std::string& avb_version() const { return avb_version_; }
+    const VBMetaInfo& vbmeta_info() const { return vbmeta_info_; }
+    AvbHandleStatus status() const { return status_; }
 
     AvbHandle(const AvbHandle&) = delete;             // no copy
     AvbHandle& operator=(const AvbHandle&) = delete;  // no assignment
@@ -126,17 +171,10 @@
     AvbHandle& operator=(AvbHandle&&) noexcept = delete;  // no move assignment
 
   private:
-    enum AvbHandleStatus {
-        kAvbHandleSuccess = 0,
-        kAvbHandleUninitialized,
-        kAvbHandleHashtreeDisabled,
-        kAvbHandleVerificationDisabled,
-        kAvbHandleVerificationError,
-    };
-
-    AvbHandle() : status_(kAvbHandleUninitialized) {}
+    AvbHandle() : status_(AvbHandleStatus::kUninitialized) {}
 
     std::vector<VBMetaData> vbmeta_images_;
+    VBMetaInfo vbmeta_info_;  // A summary info for vbmeta_images_.
     AvbHandleStatus status_;
     std::string avb_version_;
 };
diff --git a/fs_mgr/libfs_avb/tests/avb_util_test.cpp b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
new file mode 100644
index 0000000..26b3294
--- /dev/null
+++ b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
@@ -0,0 +1,1088 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/unique_fd.h>
+#include <base/files/file_util.h>
+#include <base/rand_util.h>
+#include <base/strings/string_util.h>
+
+#include "avb_util.h"
+#include "fs_avb_test_util.h"
+
+// Target classes or functions to test:
+using android::fs_mgr::AvbPartitionToDevicePatition;
+using android::fs_mgr::GetAvbFooter;
+using android::fs_mgr::GetChainPartitionInfo;
+using android::fs_mgr::GetTotalSize;
+using android::fs_mgr::LoadAndVerifyVbmetaImpl;
+using android::fs_mgr::VBMetaData;
+using android::fs_mgr::VBMetaVerifyResult;
+using android::fs_mgr::VerifyPublicKeyBlob;
+using android::fs_mgr::VerifyVBMetaData;
+using android::fs_mgr::VerifyVBMetaSignature;
+
+namespace fs_avb_host_test {
+
+class AvbUtilTest : public BaseFsAvbTest {
+  public:
+    AvbUtilTest(){};
+
+  protected:
+    ~AvbUtilTest(){};
+    // Helper function for VerifyVBMetaSignature test. Modifies vbmeta.data()
+    // in a number of places at |offset| of size |length| and checks that
+    // VerifyVBMetaSignature() returns |expected_result|.
+    bool TestVBMetaModification(VBMetaVerifyResult expected_result, const VBMetaData& vbmeta,
+                                size_t offset, size_t length);
+    // Modifies a random bit for a file, in the range of [offset, offset + length - 1].
+    void ModifyFile(const base::FilePath& file_path, size_t offset, ssize_t length);
+
+    // Loads the content of avb_image_path and comparies it with the content of vbmeta.
+    bool CompareVBMeta(const base::FilePath& avb_image_path, const VBMetaData& expected_vbmeta);
+};
+
+TEST_F(AvbUtilTest, AvbPartitionToDevicePatition) {
+    EXPECT_EQ("system", AvbPartitionToDevicePatition("system", "", ""));
+    EXPECT_EQ("system", AvbPartitionToDevicePatition("system", "", "_b"));
+
+    EXPECT_EQ("system_a", AvbPartitionToDevicePatition("system", "_a", ""));
+    EXPECT_EQ("system_a", AvbPartitionToDevicePatition("system", "_a", "_b"));
+
+    EXPECT_EQ("system_b", AvbPartitionToDevicePatition("system_other", "", "_b"));
+    EXPECT_EQ("system_b", AvbPartitionToDevicePatition("system_other", "_a", "_b"));
+}
+
+TEST_F(AvbUtilTest, GetFdTotalSize) {
+    // Generates a raw test.img via BaseFsAvbTest.
+    const size_t image_size = 5 * 1024 * 1024;
+    base::FilePath image_path = GenerateImage("test.img", image_size);
+
+    // Checks file size is as expected via base::GetFileSize().
+    int64_t file_size;
+    ASSERT_TRUE(base::GetFileSize(image_path, &file_size));
+    EXPECT_EQ(image_size, file_size);
+
+    // Checks file size is expected via libfs_avb internal utils.
+    auto fd = OpenUniqueReadFd(image_path);
+    EXPECT_EQ(image_size, GetTotalSize(fd));
+}
+
+TEST_F(AvbUtilTest, GetFdTotalSizeWithOffset) {
+    // Generates a raw test.img via BaseFsAvbTest.
+    const size_t image_size = 10 * 1024 * 1024;
+    base::FilePath image_path = GenerateImage("test.img", image_size);
+
+    // Checks file size is expected even with a non-zero offset at the beginning.
+    auto fd = OpenUniqueReadFd(image_path);
+    off_t initial_offset = 2019;
+    EXPECT_EQ(initial_offset, lseek(fd, initial_offset, SEEK_SET));
+    EXPECT_EQ(image_size, GetTotalSize(fd));            // checks that total size is still returned.
+    EXPECT_EQ(initial_offset, lseek(fd, 0, SEEK_CUR));  // checks original offset is restored.
+}
+
+TEST_F(AvbUtilTest, GetAvbFooter) {
+    // Generates a raw system.img
+    const size_t image_size = 10 * 1024 * 1024;
+    const size_t partition_size = 15 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system.img", image_size);
+    EXPECT_NE(0U, system_path.value().size());
+
+    // Checks image size is as expected.
+    int64_t file_size;
+    ASSERT_TRUE(base::GetFileSize(system_path, &file_size));
+    EXPECT_EQ(image_size, file_size);
+
+    // Appends AVB Hashtree Footer.
+    AddAvbFooter(system_path, "hashtree", "system", partition_size, "SHA512_RSA8192", 20,
+                 data_dir_.Append("testkey_rsa8192.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Checks partition size is as expected, after adding footer.
+    ASSERT_TRUE(base::GetFileSize(system_path, &file_size));
+    EXPECT_EQ(partition_size, file_size);
+
+    // Checks avb footer and avb vbmeta.
+    EXPECT_EQ(
+            "Footer version:           1.0\n"
+            "Image size:               15728640 bytes\n"
+            "Original image size:      10485760 bytes\n"
+            "VBMeta offset:            10661888\n"
+            "VBMeta size:              3648 bytes\n"
+            "--\n"
+            "Minimum libavb version:   1.0\n"
+            "Header Block:             256 bytes\n"
+            "Authentication Block:     1088 bytes\n"
+            "Auxiliary Block:          2304 bytes\n"
+            "Algorithm:                SHA512_RSA8192\n"
+            "Rollback Index:           20\n"
+            "Flags:                    0\n"
+            "Release String:           'unit test'\n"
+            "Descriptors:\n"
+            "    Hashtree descriptor:\n"
+            "      Version of dm-verity:  1\n"
+            "      Image Size:            10485760 bytes\n"
+            "      Tree Offset:           10485760\n"
+            "      Tree Size:             86016 bytes\n"
+            "      Data Block Size:       4096 bytes\n"
+            "      Hash Block Size:       4096 bytes\n"
+            "      FEC num roots:         2\n"
+            "      FEC offset:            10571776\n"
+            "      FEC size:              90112 bytes\n"
+            "      Hash Algorithm:        sha1\n"
+            "      Partition Name:        system\n"
+            "      Salt:                  d00df00d\n"
+            "      Root Digest:           a3d5dd307341393d85de356c384ff543ec1ed81b\n"
+            "      Flags:                 0\n",
+            InfoImage(system_path));
+
+    // Checks each field from GetAvbFooter(fd).
+    auto fd = OpenUniqueReadFd(system_path);
+    auto footer = GetAvbFooter(fd);
+    EXPECT_NE(nullptr, footer);
+    EXPECT_EQ(10485760, footer->original_image_size);
+    EXPECT_EQ(10661888, footer->vbmeta_offset);
+    EXPECT_EQ(3648, footer->vbmeta_size);
+}
+
+TEST_F(AvbUtilTest, GetAvbFooterErrorVerification) {
+    // Generates a raw system.img
+    const size_t image_size = 5 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system.img", image_size);
+
+    // Checks each field from GetAvbFooter(fd).
+    auto fd = OpenUniqueReadFd(system_path);
+    auto footer = GetAvbFooter(fd);
+    EXPECT_EQ(nullptr, footer);
+}
+
+TEST_F(AvbUtilTest, GetAvbFooterInsufficientSize) {
+    // Generates a raw system.img
+    const size_t image_size = AVB_FOOTER_SIZE - 10;
+    base::FilePath system_path = GenerateImage("system.img", image_size);
+
+    // Checks each field from GetAvbFooter(fd).
+    auto fd = OpenUniqueReadFd(system_path);
+    auto footer = GetAvbFooter(fd);
+    EXPECT_EQ(nullptr, footer);
+}
+
+TEST_F(AvbUtilTest, GetVBMetaHeader) {
+    // Generates a raw boot.img
+    const size_t image_size = 5 * 1024 * 1024;
+    const size_t partition_size = 10 * 1024 * 1024;
+    base::FilePath boot_path = GenerateImage("boot.img", image_size);
+    // Appends AVB Hash Footer.
+    AddAvbFooter(boot_path, "hash", "boot", partition_size, "SHA256_RSA4096", 10,
+                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+    // Extracts boot vbmeta from boot.img into boot-vbmeta.img.
+    base::FilePath boot_vbmeta = ExtractVBMetaImage(boot_path, "boot-vbmeta.img");
+    EXPECT_EQ(
+            "Minimum libavb version:   1.0\n"
+            "Header Block:             256 bytes\n"
+            "Authentication Block:     576 bytes\n"
+            "Auxiliary Block:          1216 bytes\n"
+            "Algorithm:                SHA256_RSA4096\n"
+            "Rollback Index:           10\n"
+            "Flags:                    0\n"
+            "Release String:           'unit test'\n"
+            "Descriptors:\n"
+            "    Hash descriptor:\n"
+            "      Image Size:            5242880 bytes\n"
+            "      Hash Algorithm:        sha256\n"
+            "      Partition Name:        boot\n"
+            "      Salt:                  d00df00d\n"
+            "      Digest:                "
+            "222dd01e98284a1fcd7781f85d1392e43a530511a64eff96db197db90ebc4df1\n"
+            "      Flags:                 0\n",
+            InfoImage("boot-vbmeta.img"));
+
+    // Creates a VBMetaData with the content from boot-vbmeta.img.
+    std::string content;
+    EXPECT_TRUE(base::ReadFileToString(boot_vbmeta, &content));
+    VBMetaData vbmeta((uint8_t*)content.data(), content.size(), "boot-vbmeta");
+    EXPECT_EQ(content.size(), vbmeta.size());
+
+    // Checks each field returned from GetVBMetaHeader().
+    auto vbmeta_header = vbmeta.GetVBMetaHeader(false /* update_vbmeta_size */);
+    EXPECT_NE(nullptr, vbmeta_header);
+    EXPECT_EQ(576, vbmeta_header->authentication_data_block_size);
+    EXPECT_EQ(1216, vbmeta_header->auxiliary_data_block_size);
+    EXPECT_EQ(AVB_ALGORITHM_TYPE_SHA256_RSA4096, vbmeta_header->algorithm_type);
+    EXPECT_EQ(0, vbmeta_header->hash_offset);
+    EXPECT_EQ(32, vbmeta_header->hash_size);
+    EXPECT_EQ(32, vbmeta_header->signature_offset);
+    EXPECT_EQ(512, vbmeta_header->signature_size);
+    EXPECT_EQ(176, vbmeta_header->public_key_offset);
+    EXPECT_EQ(1032, vbmeta_header->public_key_size);
+    EXPECT_EQ(0, vbmeta_header->descriptors_offset);
+    EXPECT_EQ(176, vbmeta_header->descriptors_size);
+    EXPECT_EQ(10, vbmeta_header->rollback_index);
+    EXPECT_EQ(0, vbmeta_header->flags);
+    EXPECT_EQ("unit test", std::string((const char*)vbmeta_header->release_string));
+
+    // Appends some garbage to the end of the vbmeta buffer, checks it still can work.
+    std::string padding(2020, 'A');  // Generate a padding with length 2020.
+    std::string content_padding = content + padding;
+    VBMetaData vbmeta_padding((const uint8_t*)content_padding.data(), content_padding.size(),
+                              "boot");
+    EXPECT_EQ(content_padding.size(), vbmeta_padding.size());
+
+    // Checks each field still can be parsed properly, even with garbage padding.
+    vbmeta_header = vbmeta_padding.GetVBMetaHeader(false /* update_vbmeta_size */);
+    EXPECT_NE(nullptr, vbmeta_header);
+    EXPECT_EQ(576, vbmeta_header->authentication_data_block_size);
+    EXPECT_EQ(1216, vbmeta_header->auxiliary_data_block_size);
+    EXPECT_EQ(AVB_ALGORITHM_TYPE_SHA256_RSA4096, vbmeta_header->algorithm_type);
+    EXPECT_EQ(0, vbmeta_header->hash_offset);
+    EXPECT_EQ(32, vbmeta_header->hash_size);
+    EXPECT_EQ(32, vbmeta_header->signature_offset);
+    EXPECT_EQ(512, vbmeta_header->signature_size);
+    EXPECT_EQ(176, vbmeta_header->public_key_offset);
+    EXPECT_EQ(1032, vbmeta_header->public_key_size);
+    EXPECT_EQ(0, vbmeta_header->descriptors_offset);
+    EXPECT_EQ(176, vbmeta_header->descriptors_size);
+    EXPECT_EQ(10, vbmeta_header->rollback_index);
+    EXPECT_EQ(0, vbmeta_header->flags);
+    EXPECT_EQ("unit test", std::string((const char*)vbmeta_header->release_string));
+
+    // Checks vbmeta size is updated to the actual size without padding.
+    vbmeta_header = vbmeta_padding.GetVBMetaHeader(true /* update_vbmeta_size */);
+    EXPECT_EQ(content_padding.size() - padding.size(), vbmeta_padding.size());
+}
+
+TEST_F(AvbUtilTest, VerifyPublicKeyBlob) {
+    // Generates a raw key.bin
+    const size_t key_size = 2048;
+    base::FilePath key_path = GenerateImage("key.bin", key_size);
+
+    uint8_t key_data[key_size];
+    EXPECT_EQ(key_size, base::ReadFile(key_path, (char*)key_data, key_size));
+
+    std::string expected_key_blob;
+    EXPECT_TRUE(base::ReadFileToString(key_path, &expected_key_blob));
+    EXPECT_TRUE(VerifyPublicKeyBlob(key_data, key_size, expected_key_blob));
+
+    key_data[10] ^= 0x80;  // toggles a bit and expects a failure
+    EXPECT_FALSE(VerifyPublicKeyBlob(key_data, key_size, expected_key_blob));
+    key_data[10] ^= 0x80;  // toggles the bit again, should pass
+    EXPECT_TRUE(VerifyPublicKeyBlob(key_data, key_size, expected_key_blob));
+}
+
+TEST_F(AvbUtilTest, VerifyEmptyPublicKeyBlob) {
+    // Generates a raw key.bin
+    const size_t key_size = 2048;
+    base::FilePath key_path = GenerateImage("key.bin", key_size);
+
+    uint8_t key_data[key_size];
+    EXPECT_EQ(key_size, base::ReadFile(key_path, (char*)key_data, key_size));
+
+    std::string expected_key_blob = "";  // empty means no expectation, thus return true.
+    EXPECT_TRUE(VerifyPublicKeyBlob(key_data, key_size, expected_key_blob));
+}
+
+TEST_F(AvbUtilTest, VerifyVBMetaSignature) {
+    const size_t image_size = 10 * 1024 * 1024;
+    const size_t partition_size = 15 * 1024 * 1024;
+    auto signing_key = data_dir_.Append("testkey_rsa4096.pem");
+    auto vbmeta = GenerateImageAndExtractVBMetaData("system", image_size, partition_size,
+                                                    "hashtree", signing_key, "SHA256_RSA4096",
+                                                    10 /* rollback_index */);
+
+    auto expected_public_key = ExtractPublicKeyAvbBlob(signing_key);
+    EXPECT_EQ(VBMetaVerifyResult::kSuccess, VerifyVBMetaSignature(vbmeta, expected_public_key));
+
+    // Converts the expected key into an 'unexpected' key.
+    expected_public_key[10] ^= 0x80;
+    EXPECT_EQ(VBMetaVerifyResult::kErrorVerification,
+              VerifyVBMetaSignature(vbmeta, expected_public_key));
+}
+
+bool AvbUtilTest::TestVBMetaModification(VBMetaVerifyResult expected_result,
+                                         const VBMetaData& vbmeta, size_t offset, size_t length) {
+    uint8_t* d = reinterpret_cast<uint8_t*>(vbmeta.data());
+    const int kNumCheckIntervals = 8;
+
+    // Tests |kNumCheckIntervals| modifications in the start, middle, and
+    // end of the given sub-array at offset with size.
+    for (int n = 0; n <= kNumCheckIntervals; n++) {
+        size_t o = std::min(length * n / kNumCheckIntervals, length - 1) + offset;
+        d[o] ^= 0x80;
+        VBMetaVerifyResult result = VerifyVBMetaSignature(vbmeta, "" /* expected_public_key */);
+        d[o] ^= 0x80;
+        if (result != expected_result) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+TEST_F(AvbUtilTest, VerifyVBMetaSignatureWithModification) {
+    const size_t image_size = 10 * 1024 * 1024;
+    const size_t partition_size = 15 * 1024 * 1024;
+    auto signing_key = data_dir_.Append("testkey_rsa4096.pem");
+    auto vbmeta = GenerateImageAndExtractVBMetaData("system", image_size, partition_size,
+                                                    "hashtree", signing_key, "SHA256_RSA4096",
+                                                    10 /* rollback_index */);
+
+    auto header = vbmeta.GetVBMetaHeader(true /* update_vbmeta_size */);
+    size_t header_block_offset = 0;
+    size_t authentication_block_offset = header_block_offset + sizeof(AvbVBMetaImageHeader);
+    size_t auxiliary_block_offset =
+            authentication_block_offset + header->authentication_data_block_size;
+
+    // Should detect modifications in the auxiliary data block.
+    EXPECT_TRUE(TestVBMetaModification(VBMetaVerifyResult::kErrorVerification, vbmeta,
+                                       auxiliary_block_offset, header->auxiliary_data_block_size));
+
+    // Sholud detect modifications in the hash part of authentication data block.
+    EXPECT_TRUE(TestVBMetaModification(VBMetaVerifyResult::kErrorVerification, vbmeta,
+                                       authentication_block_offset + header->hash_offset,
+                                       header->hash_size));
+
+    // Sholud detect modifications in the signature part of authentication data block.
+    EXPECT_TRUE(TestVBMetaModification(VBMetaVerifyResult::kErrorVerification, vbmeta,
+                                       authentication_block_offset + header->signature_offset,
+                                       header->signature_size));
+}
+
+TEST_F(AvbUtilTest, VerifyVBMetaSignatureNotSigned) {
+    const size_t image_size = 10 * 1024 * 1024;
+    const size_t partition_size = 15 * 1024 * 1024;
+    auto vbmeta = GenerateImageAndExtractVBMetaData(
+            "system", image_size, partition_size, "hashtree", {} /* avb_signing_key */,
+            "" /* avb_algorithm */, 10 /* rollback_index */);
+
+    EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, VerifyVBMetaSignature(vbmeta, ""));
+}
+
+TEST_F(AvbUtilTest, VerifyVBMetaSignatureInvalidVBMeta) {
+    const size_t buffer_size = 5 * 1024 * 1024;
+    std::vector<uint8_t> vbmeta_buffer(buffer_size);
+    for (size_t n = 0; n < buffer_size; n++) {
+        vbmeta_buffer[n] = uint8_t(n);
+    }
+
+    VBMetaData invalid_vbmeta((const uint8_t*)vbmeta_buffer.data(), vbmeta_buffer.size(),
+                              "invalid_vbmeta");
+    EXPECT_EQ(VBMetaVerifyResult::kError, VerifyVBMetaSignature(invalid_vbmeta, ""));
+}
+
+bool AvbUtilTest::CompareVBMeta(const base::FilePath& avb_image_path,
+                                const VBMetaData& expected_vbmeta) {
+    if (!base::PathExists(avb_image_path)) return false;
+
+    std::string image_file_name = avb_image_path.RemoveExtension().BaseName().value();
+
+    base::FilePath extracted_vbmeta_path;
+    if (base::StartsWith(image_file_name, "vbmeta", base::CompareCase::INSENSITIVE_ASCII)) {
+        extracted_vbmeta_path = avb_image_path;  // no need to extract if it's a vbmeta image.
+    } else {
+        extracted_vbmeta_path = ExtractVBMetaImage(avb_image_path, image_file_name + "-vbmeta.img");
+    }
+
+    // Gets file size of the vbmeta image.
+    int64_t extracted_vbmeta_size;
+    EXPECT_TRUE(base::GetFileSize(extracted_vbmeta_path, &extracted_vbmeta_size));
+
+    // Reads the vbmeta into a vector.
+    std::vector<uint8_t> extracted_vbmeta_content(extracted_vbmeta_size);
+    EXPECT_TRUE(base::ReadFile(extracted_vbmeta_path,
+                               reinterpret_cast<char*>(extracted_vbmeta_content.data()),
+                               extracted_vbmeta_size));
+
+    // Compares extracted_vbmeta_content with the expected_vbmeta.
+    EXPECT_EQ(expected_vbmeta.size(), extracted_vbmeta_size);
+    return memcmp(reinterpret_cast<void*>(extracted_vbmeta_content.data()),
+                  reinterpret_cast<void*>(expected_vbmeta.data()), extracted_vbmeta_size) == 0;
+}
+
+TEST_F(AvbUtilTest, VerifyVBMetaDataWithoutFooter) {
+    // Generates chain partition descriptors.
+    base::FilePath rsa2048_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
+    base::FilePath rsa4096_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));
+
+    // Makes a vbmeta image includeing 'boot' and 'system' chained descriptors.
+    auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
+                                           data_dir_.Append("testkey_rsa8192.pem"),
+                                           {}, /* include_descriptor_image_paths */
+                                           {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
+                                            {"system", 2, rsa4096_public_key}},
+                                           "--internal_release_string \"unit test\"");
+    EXPECT_EQ(
+            "Minimum libavb version:   1.0\n"
+            "Header Block:             256 bytes\n"
+            "Authentication Block:     1088 bytes\n"
+            "Auxiliary Block:          3840 bytes\n"
+            "Algorithm:                SHA256_RSA8192\n"
+            "Rollback Index:           0\n"
+            "Flags:                    0\n"
+            "Release String:           'unit test'\n"
+            "Descriptors:\n"
+            "    Chain Partition descriptor:\n"
+            "      Partition Name:          boot\n"
+            "      Rollback Index Location: 1\n"
+            "      Public key (sha1):       cdbb77177f731920bbe0a0f94f84d9038ae0617d\n"
+            "    Chain Partition descriptor:\n"
+            "      Partition Name:          system\n"
+            "      Rollback Index Location: 2\n"
+            "      Public key (sha1):       2597c218aae470a130f61162feaae70afd97f011\n",
+            InfoImage("vbmeta.img"));
+
+    android::base::unique_fd fd(open(vbmeta_path.value().c_str(), O_RDONLY | O_CLOEXEC));
+    ASSERT_TRUE(fd > 0);
+
+    VBMetaVerifyResult verify_result;
+    std::unique_ptr<VBMetaData> vbmeta =
+            VerifyVBMetaData(fd, "vbmeta", "" /*expected_public_key_blob */, &verify_result);
+    EXPECT_TRUE(vbmeta != nullptr);
+    EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result);
+
+    // Checkes the returned vbmeta content is the same as that extracted via avbtool.
+    vbmeta->GetVBMetaHeader(true /* update_vbmeta_size */);
+    EXPECT_TRUE(CompareVBMeta(vbmeta_path, *vbmeta));
+}
+
+TEST_F(AvbUtilTest, VerifyVBMetaDataWithFooter) {
+    const size_t image_size = 10 * 1024 * 1024;
+    const size_t partition_size = 15 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system.img", image_size);
+
+    // Appends AVB Hashtree Footer.
+    AddAvbFooter(system_path, "hashtree", "system", partition_size, "SHA512_RSA8192", 20,
+                 data_dir_.Append("testkey_rsa8192.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    android::base::unique_fd fd(open(system_path.value().c_str(), O_RDONLY | O_CLOEXEC));
+    ASSERT_TRUE(fd > 0);
+
+    VBMetaVerifyResult verify_result;
+    std::unique_ptr<VBMetaData> vbmeta =
+            VerifyVBMetaData(fd, "system", "" /*expected_public_key_blob */, &verify_result);
+    EXPECT_TRUE(vbmeta != nullptr);
+    EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result);
+
+    // Checkes the returned vbmeta content is the same as that extracted via avbtool.
+    EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
+}
+
+// Modifies a random bit for a file, in the range of [offset, offset + length - 1].
+// Length < 0 means only resets previous modification without introducing new modification.
+void AvbUtilTest::ModifyFile(const base::FilePath& file_path, size_t offset, ssize_t length) {
+    static int last_modified_location = -1;
+    static std::string last_file_path;
+
+    int64_t file_size;
+    ASSERT_TRUE(base::GetFileSize(file_path, &file_size));
+
+    std::vector<uint8_t> file_content(file_size);
+    ASSERT_TRUE(base::ReadFile(file_path, reinterpret_cast<char*>(file_content.data()), file_size));
+
+    // Resets previous modification for consecutive calls on the same file.
+    if (last_file_path == file_path.value()) {
+        file_content[last_modified_location] ^= 0x80;
+    }
+
+    // Introduces a new modification.
+    if (length > 0) {
+        int modify_location = base::RandInt(offset, offset + length - 1);
+        file_content[modify_location] ^= 0x80;
+        last_file_path = file_path.value();
+        last_modified_location = modify_location;
+    }
+
+    ASSERT_EQ(file_size, static_cast<const size_t>(base::WriteFile(
+                                 file_path, reinterpret_cast<const char*>(file_content.data()),
+                                 file_content.size())));
+}
+
+TEST_F(AvbUtilTest, VerifyVBMetaDataError) {
+    const size_t image_size = 10 * 1024 * 1024;
+    const size_t partition_size = 15 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system.img", image_size);
+
+    // Appends AVB Hashtree Footer.
+    AddAvbFooter(system_path, "hashtree", "system", partition_size, "SHA512_RSA8192", 20,
+                 data_dir_.Append("testkey_rsa8192.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    android::base::unique_fd fd(open(system_path.value().c_str(), O_RDONLY | O_CLOEXEC));
+    ASSERT_TRUE(fd > 0);
+
+    std::unique_ptr<AvbFooter> footer = GetAvbFooter(fd);
+    EXPECT_TRUE(footer != nullptr);
+
+    VBMetaVerifyResult verify_result;
+    std::unique_ptr<VBMetaData> vbmeta =
+            VerifyVBMetaData(fd, "system", "" /*expected_public_key_blob */, &verify_result);
+    EXPECT_NE(nullptr, vbmeta);
+    EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result);
+
+    // Modifies hash and signature, checks there is verification error.
+    auto header = vbmeta->GetVBMetaHeader(true /* update_vbmeta_size */);
+    size_t header_block_offset = 0;
+    size_t authentication_block_offset = header_block_offset + sizeof(AvbVBMetaImageHeader);
+
+    // Modifies the hash.
+    ModifyFile(system_path,
+               footer->vbmeta_offset + authentication_block_offset + header->hash_offset,
+               header->hash_size);
+    android::base::unique_fd hash_modified_fd(
+            open(system_path.value().c_str(), O_RDONLY | O_CLOEXEC));
+    ASSERT_TRUE(hash_modified_fd > 0);
+    // Should return ErrorVerification.
+    vbmeta = VerifyVBMetaData(hash_modified_fd, "system", "" /*expected_public_key_blob */,
+                              &verify_result);
+    EXPECT_NE(nullptr, vbmeta);
+    EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
+    EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result);
+
+    // Modifies the auxiliary data block.
+    size_t auxiliary_block_offset =
+            authentication_block_offset + header->authentication_data_block_size;
+    ModifyFile(system_path, footer->vbmeta_offset + auxiliary_block_offset,
+               header->auxiliary_data_block_size);
+    android::base::unique_fd aux_modified_fd(
+            open(system_path.value().c_str(), O_RDONLY | O_CLOEXEC));
+    ASSERT_TRUE(aux_modified_fd > 0);
+    // Should return ErrorVerification.
+    vbmeta = VerifyVBMetaData(aux_modified_fd, "system", "" /*expected_public_key_blob */,
+                              &verify_result);
+    EXPECT_NE(nullptr, vbmeta);
+    EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
+    EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result);
+
+    // Resets previous modification by setting offset to -1, and checks the verification can pass.
+    ModifyFile(system_path, 0 /* offset */, -1 /* length */);
+    android::base::unique_fd ok_fd(open(system_path.value().c_str(), O_RDONLY | O_CLOEXEC));
+    ASSERT_TRUE(ok_fd > 0);
+    // Should return ResultOK..
+    vbmeta = VerifyVBMetaData(ok_fd, "system", "" /*expected_public_key_blob */, &verify_result);
+    EXPECT_NE(nullptr, vbmeta);
+    EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
+    EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result);
+}
+
+TEST_F(AvbUtilTest, GetChainPartitionInfo) {
+    // Generates a raw boot.img
+    const size_t boot_image_size = 5 * 1024 * 1024;
+    const size_t boot_partition_size = 10 * 1024 * 1024;
+    base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+    // Adds AVB Hash Footer.
+    AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10,
+                 data_dir_.Append("testkey_rsa2048.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates a raw system.img, use a smaller size to speed-up unit test.
+    const size_t system_image_size = 10 * 1024 * 1024;
+    const size_t system_partition_size = 15 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system.img", system_image_size);
+    // Adds AVB Hashtree Footer.
+    AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20,
+                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates chain partition descriptors.
+    base::FilePath rsa2048_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
+    base::FilePath rsa4096_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));
+    // Makes a vbmeta_system.img including the 'system' chained descriptor.
+    GenerateVBMetaImage("vbmeta_system.img", "SHA256_RSA4096", 0,
+                        data_dir_.Append("testkey_rsa4096.pem"),
+                        {},                                  /* include_descriptor_image_paths */
+                        {{"system", 3, rsa4096_public_key}}, /* chain_partitions */
+                        "--internal_release_string \"unit test\"");
+
+    // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors.
+    GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"),
+                        {},                               /* include_descriptor_image_paths */
+                        {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
+                         {"vbmeta_system", 2, rsa4096_public_key}},
+                        "--internal_release_string \"unit test\"");
+
+    // Calculates the digest of all chained partitions, to ensure the chained is formed properly.
+    EXPECT_EQ("6f4bf815a651aa35ec7102a88b7906b91aef284bc5e20d0bf527c7d460da3266",
+              CalcVBMetaDigest("vbmeta.img", "sha256"));
+    // Loads the key blobs for comparison.
+    std::string expected_key_blob_2048;
+    EXPECT_TRUE(base::ReadFileToString(rsa2048_public_key, &expected_key_blob_2048));
+    std::string expected_key_blob_4096;
+    EXPECT_TRUE(base::ReadFileToString(rsa4096_public_key, &expected_key_blob_4096));
+
+    // Checks chain descriptors in vbmeta.img
+    EXPECT_EQ(
+            "Minimum libavb version:   1.0\n"
+            "Header Block:             256 bytes\n"
+            "Authentication Block:     1088 bytes\n"
+            "Auxiliary Block:          3840 bytes\n"
+            "Algorithm:                SHA256_RSA8192\n"
+            "Rollback Index:           0\n"
+            "Flags:                    0\n"
+            "Release String:           'unit test'\n"
+            "Descriptors:\n"
+            "    Chain Partition descriptor:\n"
+            "      Partition Name:          boot\n"
+            "      Rollback Index Location: 1\n"
+            "      Public key (sha1):       cdbb77177f731920bbe0a0f94f84d9038ae0617d\n"
+            "    Chain Partition descriptor:\n"
+            "      Partition Name:          vbmeta_system\n"
+            "      Rollback Index Location: 2\n"
+            "      Public key (sha1):       2597c218aae470a130f61162feaae70afd97f011\n",
+            InfoImage("vbmeta.img"));
+
+    bool fatal_error = false;
+    auto chained_descriptors = GetChainPartitionInfo(LoadVBMetaData("vbmeta.img"), &fatal_error);
+    EXPECT_EQ(2, chained_descriptors.size());  // contains 'boot' and 'vbmeta_system'.
+    EXPECT_EQ(false, fatal_error);
+
+    EXPECT_EQ("boot", chained_descriptors[0].partition_name);
+    EXPECT_EQ(expected_key_blob_2048, chained_descriptors[0].public_key_blob);
+
+    EXPECT_EQ("vbmeta_system", chained_descriptors[1].partition_name);
+    EXPECT_EQ(expected_key_blob_4096, chained_descriptors[1].public_key_blob);
+
+    // Checks chain descriptors in vbmeta_system.img
+    EXPECT_EQ(
+            "Minimum libavb version:   1.0\n"
+            "Header Block:             256 bytes\n"
+            "Authentication Block:     576 bytes\n"
+            "Auxiliary Block:          2176 bytes\n"
+            "Algorithm:                SHA256_RSA4096\n"
+            "Rollback Index:           0\n"
+            "Flags:                    0\n"
+            "Release String:           'unit test'\n"
+            "Descriptors:\n"
+            "    Chain Partition descriptor:\n"
+            "      Partition Name:          system\n"
+            "      Rollback Index Location: 3\n"
+            "      Public key (sha1):       2597c218aae470a130f61162feaae70afd97f011\n",
+            InfoImage("vbmeta_system.img"));
+
+    chained_descriptors = GetChainPartitionInfo(LoadVBMetaData("vbmeta_system.img"), &fatal_error);
+    EXPECT_EQ(1, chained_descriptors.size());  // contains 'system' only.
+    EXPECT_EQ(false, fatal_error);
+    EXPECT_EQ("system", chained_descriptors[0].partition_name);
+    EXPECT_EQ(expected_key_blob_4096, chained_descriptors[0].public_key_blob);
+}
+
+TEST_F(AvbUtilTest, GetChainPartitionInfoNone) {
+    // Generates a raw boot.img
+    const size_t boot_image_size = 5 * 1024 * 1024;
+    const size_t boot_partition_size = 10 * 1024 * 1024;
+    base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+    // Adds AVB Hash Footer.
+    AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA4096", 10,
+                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates a raw system.img, use a smaller size to speed-up unit test.
+    const size_t system_image_size = 10 * 1024 * 1024;
+    const size_t system_partition_size = 15 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system.img", system_image_size);
+    // Adds AVB Hashtree Footer.
+    AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA8192", 20,
+                 data_dir_.Append("testkey_rsa8192.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Makes a vbmeta.img including both 'boot' and 'system' descriptors.
+    GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0, data_dir_.Append("testkey_rsa2048.pem"),
+                        {boot_path, system_path}, /* include_descriptor_image_paths */
+                        {},                       /* chain_partitions */
+                        "--internal_release_string \"unit test\"");
+    EXPECT_EQ("a069cbfc30c816cddf3b53f1ad53b7ca5d61a3d93845eb596bbb1b40caa1c62f",
+              CalcVBMetaDigest("vbmeta.img", "sha256"));
+
+    EXPECT_EQ(
+            "Minimum libavb version:   1.0\n"
+            "Header Block:             256 bytes\n"
+            "Authentication Block:     320 bytes\n"
+            "Auxiliary Block:          960 bytes\n"
+            "Algorithm:                SHA256_RSA2048\n"
+            "Rollback Index:           0\n"
+            "Flags:                    0\n"
+            "Release String:           'unit test'\n"
+            "Descriptors:\n"
+            "    Hash descriptor:\n"
+            "      Image Size:            5242880 bytes\n"
+            "      Hash Algorithm:        sha256\n"
+            "      Partition Name:        boot\n"
+            "      Salt:                  d00df00d\n"
+            "      Digest:                "
+            "222dd01e98284a1fcd7781f85d1392e43a530511a64eff96db197db90ebc4df1\n"
+            "      Flags:                 0\n"
+            "    Hashtree descriptor:\n"
+            "      Version of dm-verity:  1\n"
+            "      Image Size:            10485760 bytes\n"
+            "      Tree Offset:           10485760\n"
+            "      Tree Size:             86016 bytes\n"
+            "      Data Block Size:       4096 bytes\n"
+            "      Hash Block Size:       4096 bytes\n"
+            "      FEC num roots:         2\n"
+            "      FEC offset:            10571776\n"
+            "      FEC size:              90112 bytes\n"
+            "      Hash Algorithm:        sha1\n"
+            "      Partition Name:        system\n"
+            "      Salt:                  d00df00d\n"
+            "      Root Digest:           a3d5dd307341393d85de356c384ff543ec1ed81b\n"
+            "      Flags:                 0\n",
+            InfoImage("vbmeta.img"));
+
+    // Checks none of chain descriptors is found.
+    bool fatal_error = false;
+    auto chained_descriptors = GetChainPartitionInfo(LoadVBMetaData("vbmeta.img"), &fatal_error);
+    EXPECT_EQ(0, chained_descriptors.size());  // There is no chain descriptors.
+    EXPECT_EQ(false, fatal_error);
+}
+
+TEST_F(AvbUtilTest, LoadAndVerifyVbmetaImpl) {
+    // Generates a raw boot.img
+    const size_t boot_image_size = 5 * 1024 * 1024;
+    const size_t boot_partition_size = 10 * 1024 * 1024;
+    base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+
+    // Adds AVB Hash Footer.
+    AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10,
+                 data_dir_.Append("testkey_rsa2048.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates a raw system.img, use a smaller size to speed-up unit test.
+    const size_t system_image_size = 10 * 1024 * 1024;
+    const size_t system_partition_size = 15 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system.img", system_image_size);
+    // Adds AVB Hashtree Footer.
+    AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20,
+                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates chain partition descriptors.
+    base::FilePath rsa2048_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
+    base::FilePath rsa4096_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));
+    // Makes a vbmeta_system.img including the 'system' chained descriptor.
+    auto vbmeta_system_path = GenerateVBMetaImage(
+            "vbmeta_system.img", "SHA256_RSA4096", 0, data_dir_.Append("testkey_rsa4096.pem"),
+            {},                                  /* include_descriptor_image_paths */
+            {{"system", 3, rsa4096_public_key}}, /* chain_partitions */
+            "--internal_release_string \"unit test\"");
+
+    // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors.
+    auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
+                                           data_dir_.Append("testkey_rsa8192.pem"),
+                                           {}, /* include_descriptor_image_paths */
+                                           {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
+                                            {"vbmeta_system", 2, rsa4096_public_key}},
+                                           "--internal_release_string \"unit test\"");
+
+    // Calculates the digest of all chained partitions, to ensure the chained is formed properly.
+    EXPECT_EQ("6f4bf815a651aa35ec7102a88b7906b91aef284bc5e20d0bf527c7d460da3266",
+              CalcVBMetaDigest("vbmeta.img", "sha256"));
+
+    // Starts to test LoadAndVerifyVbmetaImpl.
+    std::vector<VBMetaData> vbmeta_images;
+    auto vbmeta_image_path = [this](const std::string& partition_name) {
+        return test_dir_.Append(partition_name + ".img").value();
+    };
+
+    EXPECT_EQ(VBMetaVerifyResult::kSuccess,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+                      "" /* expected_public_key_blob*/, false /* allow_verification_error */,
+                      true /* load_chained_vbmeta */, true /* rollback_protection */,
+                      vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images));
+
+    EXPECT_EQ(4UL, vbmeta_images.size());  // vbmeta, boot, vbmeta_system and system
+    // Binary comparison for each vbmeta image.
+    EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0]));
+    EXPECT_TRUE(CompareVBMeta(boot_path, vbmeta_images[1]));
+    EXPECT_TRUE(CompareVBMeta(vbmeta_system_path, vbmeta_images[2]));
+    EXPECT_TRUE(CompareVBMeta(system_path, vbmeta_images[3]));
+
+    // Skip loading chained vbmeta images.
+    vbmeta_images.clear();
+    EXPECT_EQ(VBMetaVerifyResult::kSuccess,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+                      "" /* expected_public_key_blob*/, false /* allow_verification_error */,
+                      false /* load_chained_vbmeta */, true /* rollback_protection */,
+                      vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images));
+    // Only vbmeta is loaded.
+    EXPECT_EQ(1UL, vbmeta_images.size());
+    EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0]));
+}
+
+TEST_F(AvbUtilTest, LoadAndVerifyVbmetaImplWithSuffixes) {
+    // Tests the following chained partitions.
+    // vbmeta_a.img
+    // |--> boot_b.img (boot_other)
+    // |--> vbmeta_system_b.img (vbmeta_system_other)
+    //      |--> system_a.img
+
+    // Generates a raw boot_b.img
+    const size_t boot_image_size = 5 * 1024 * 1024;
+    const size_t boot_partition_size = 10 * 1024 * 1024;
+    base::FilePath boot_path = GenerateImage("boot_b.img", boot_image_size);
+
+    // Adds AVB Hash Footer.
+    AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10,
+                 data_dir_.Append("testkey_rsa2048.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates a raw system_a.img, use a smaller size to speed-up unit test.
+    const size_t system_image_size = 10 * 1024 * 1024;
+    const size_t system_partition_size = 15 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system_a.img", system_image_size);
+    // Adds AVB Hashtree Footer.
+    AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20,
+                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates chain partition descriptors.
+    base::FilePath rsa2048_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
+    base::FilePath rsa4096_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));
+    // Makes a vbmeta_system_b.img including the 'system' chained descriptor.
+    auto vbmeta_system_path = GenerateVBMetaImage(
+            "vbmeta_system_b.img", "SHA256_RSA4096", 0, data_dir_.Append("testkey_rsa4096.pem"),
+            {},                                  /* include_descriptor_image_paths */
+            {{"system", 3, rsa4096_public_key}}, /* chain_partitions */
+            "--internal_release_string \"unit test\"");
+
+    // Makes a vbmeta_a.img includeing 'boot_other' and 'vbmeta_system_other' chained descriptors.
+    auto vbmeta_path = GenerateVBMetaImage(
+            "vbmeta_a.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"),
+            {},                                     /* include_descriptor_image_paths */
+            {{"boot_other", 1, rsa2048_public_key}, /* chain_partitions */
+             {"vbmeta_system_other", 2, rsa4096_public_key}},
+            "--internal_release_string \"unit test\"");
+
+    // Starts to test LoadAndVerifyVbmetaImpl with ab_suffix and ab_other_suffix.
+    auto vbmeta_image_path = [this](const std::string& partition_name) {
+        return test_dir_.Append(partition_name + ".img").value();
+    };
+
+    std::vector<VBMetaData> vbmeta_images;
+    EXPECT_EQ(VBMetaVerifyResult::kSuccess,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "_a" /* ab_suffix */, "_b" /* other_suffix */,
+                      "" /* expected_public_key_blob*/, false /* allow_verification_error */,
+                      true /* load_chained_vbmeta */, true /* rollback_protection */,
+                      vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images));
+
+    EXPECT_EQ(4UL, vbmeta_images.size());  // vbmeta, boot_other, vbmeta_system_other and system
+    // Binary comparison for each vbmeta image.
+    EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0]));
+    EXPECT_TRUE(CompareVBMeta(boot_path, vbmeta_images[1]));
+    EXPECT_TRUE(CompareVBMeta(vbmeta_system_path, vbmeta_images[2]));
+    EXPECT_TRUE(CompareVBMeta(system_path, vbmeta_images[3]));
+
+    // Skips loading chained vbmeta images.
+    vbmeta_images.clear();
+    EXPECT_EQ(VBMetaVerifyResult::kSuccess,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "_a" /* ab_suffix */, "_b" /* other_suffix */,
+                      "" /* expected_public_key_blob*/, false /* allow_verification_error */,
+                      false /* load_chained_vbmeta */, true /* rollback_protection */,
+                      vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images));
+    // Only vbmeta is loaded.
+    EXPECT_EQ(1UL, vbmeta_images.size());
+    EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0]));
+
+    // Using an invalid suffix for 'other' slot, checks it returns error.
+    EXPECT_EQ(VBMetaVerifyResult::kError,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "_a" /* ab_suffix */,
+                      "_invalid_suffix" /* other_suffix */, "" /* expected_public_key_blob*/,
+                      false /* allow_verification_error */, true /* load_chained_vbmeta */,
+                      true /* rollback_protection */, vbmeta_image_path,
+                      false /* is_chained_vbmeta*/, &vbmeta_images));
+}
+
+TEST_F(AvbUtilTest, LoadAndVerifyVbmetaImplErrorVerification) {
+    // Generates a raw boot.img
+    const size_t boot_image_size = 5 * 1024 * 1024;
+    const size_t boot_partition_size = 10 * 1024 * 1024;
+    base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+
+    // Adds AVB Hash Footer.
+    AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10,
+                 data_dir_.Append("testkey_rsa2048.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates a raw system.img, use a smaller size to speed-up unit test.
+    const size_t system_image_size = 10 * 1024 * 1024;
+    const size_t system_partition_size = 15 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system.img", system_image_size);
+    // Adds AVB Hashtree Footer.
+    AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20,
+                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates chain partition descriptors.
+    base::FilePath rsa2048_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
+    base::FilePath rsa4096_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));
+
+    // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors.
+    auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
+                                           data_dir_.Append("testkey_rsa8192.pem"),
+                                           {}, /* include_descriptor_image_paths */
+                                           {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
+                                            {"system", 2, rsa4096_public_key}},
+                                           "--internal_release_string \"unit test\"");
+
+    // Calculates the digest of all chained partitions, to ensure the chained is formed properly.
+    EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f",
+              CalcVBMetaDigest("vbmeta.img", "sha256"));
+
+    auto vbmeta = LoadVBMetaData("vbmeta.img");
+
+    // Modifies hash, checks there is error if allow_verification_error is false.
+    auto header = vbmeta.GetVBMetaHeader(true /* update_vbmeta_size */);
+    size_t header_block_offset = 0;
+    size_t authentication_block_offset = header_block_offset + sizeof(AvbVBMetaImageHeader);
+
+    // Modifies the hash.
+    ModifyFile(vbmeta_path, authentication_block_offset + header->hash_offset, header->hash_size);
+
+    // Starts to test LoadAndVerifyVbmetaImpl.
+    std::vector<VBMetaData> vbmeta_images;
+    auto vbmeta_image_path = [this](const std::string& partition_name) {
+        return test_dir_.Append(partition_name + ".img").value();
+    };
+    EXPECT_EQ(VBMetaVerifyResult::kError,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+                      "" /* expected_public_key_blob*/, false /* allow_verification_error */,
+                      true /* load_chained_vbmeta */, true /* rollback_protection */,
+                      vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images));
+    // Stops to load vbmeta because the top-level vbmeta has verification error.
+    EXPECT_EQ(0UL, vbmeta_images.size());
+
+    // Tries again with verification error allowed.
+    EXPECT_EQ(VBMetaVerifyResult::kErrorVerification,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "" /* ab_suffix */, "", /* other_suffix */
+                      "" /* expected_public_key_blob*/, true /* allow_verification_error */,
+                      true /* load_chained_vbmeta */, true /* rollback_protection */,
+                      vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images));
+
+    EXPECT_EQ(3UL, vbmeta_images.size());  // vbmeta, boot, and system
+    // Binary comparison for each vbmeta image.
+    EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0]));
+    EXPECT_TRUE(CompareVBMeta(boot_path, vbmeta_images[1]));
+    EXPECT_TRUE(CompareVBMeta(system_path, vbmeta_images[2]));
+
+    // Resets the modification of the hash.
+    ModifyFile(vbmeta_path, 0 /* offset */, -1 /* length */);
+
+    // Modifies the auxiliary data of system.img
+    auto fd = OpenUniqueReadFd(system_path);
+    auto system_footer = GetAvbFooter(fd);
+    auto system_vbmeta = ExtractAndLoadVBMetaData(system_path, "system-vbmeta.img");
+    auto system_header = system_vbmeta.GetVBMetaHeader(true /* update_vbmeta_size */);
+    size_t auxiliary_block_offset =
+            authentication_block_offset + system_header->authentication_data_block_size;
+
+    // Modifies the auxiliary data block.
+    ModifyFile(system_path, system_footer->vbmeta_offset + auxiliary_block_offset,
+               system_header->auxiliary_data_block_size);
+    vbmeta_images.clear();
+    EXPECT_EQ(VBMetaVerifyResult::kError,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+                      "" /* expected_public_key_blob*/, false /* allow_verification_error */,
+                      true /* load_chained_vbmeta */, true /* rollback_protection */,
+                      vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images));
+    // 'vbmeta', 'boot' but no 'system', because of verification error.
+    EXPECT_EQ(2UL, vbmeta_images.size());
+    // Binary comparison for the loaded 'vbmeta' and 'boot'.
+    EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0]));
+    EXPECT_TRUE(CompareVBMeta(boot_path, vbmeta_images[1]));
+
+    // Resets the modification of the auxiliary data.
+    ModifyFile(vbmeta_path, 0 /* offset */, -1 /* length */);
+
+    // Sets the vbmeta header flags on a chained partition, which introduces an error.
+    ModifyFile(system_path, system_footer->vbmeta_offset + offsetof(AvbVBMetaImageHeader, flags),
+               sizeof(uint32_t));
+    EXPECT_EQ(VBMetaVerifyResult::kError,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+                      "" /* expected_public_key_blob*/, true /* allow_verification_error */,
+                      true /* load_chained_vbmeta */, true /* rollback_protection */,
+                      vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images));
+}
+
+TEST_F(AvbUtilTest, LoadAndVerifyVbmetaImplUnexpectedPublicKey) {
+    // Generates chain partition descriptors.
+    base::FilePath rsa2048_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
+    base::FilePath rsa4096_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));
+    base::FilePath rsa8192_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa8192.pem"));
+
+    // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors.
+    auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
+                                           data_dir_.Append("testkey_rsa8192.pem"),
+                                           {}, /* include_descriptor_image_paths */
+                                           {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
+                                            {"system", 2, rsa4096_public_key}},
+                                           "--internal_release_string \"unit test\"");
+    std::string expected_key_blob_4096;
+    EXPECT_TRUE(base::ReadFileToString(rsa4096_public_key, &expected_key_blob_4096));
+    std::string expected_key_blob_8192;
+    EXPECT_TRUE(base::ReadFileToString(rsa8192_public_key, &expected_key_blob_8192));
+
+    auto vbmeta_image_path = [this](const std::string& partition_name) {
+        return test_dir_.Append(partition_name + ".img").value();
+    };
+    std::vector<VBMetaData> vbmeta_images;
+    // Uses the correct expected public key.
+    EXPECT_EQ(VBMetaVerifyResult::kSuccess,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+                      expected_key_blob_8192, true /* allow_verification_error */,
+                      false /* load_chained_vbmeta */, true /* rollback_protection */,
+                      vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images));
+
+    // Uses the wrong expected public key with allow_verification_error set to true.
+    vbmeta_images.clear();
+    EXPECT_EQ(VBMetaVerifyResult::kErrorVerification,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+                      expected_key_blob_4096, true /* allow_verification_error */,
+                      false /* load_chained_vbmeta */, true /* rollback_protection */,
+                      vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images));
+
+    // Uses the wrong expected public key with allow_verification_error set to false.
+    vbmeta_images.clear();
+    EXPECT_EQ(VBMetaVerifyResult::kError,
+              LoadAndVerifyVbmetaImpl(
+                      "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+                      expected_key_blob_4096, false /* allow_verification_error */,
+                      false /* load_chained_vbmeta */, true /* rollback_protection */,
+                      vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images));
+}
+
+}  // namespace fs_avb_host_test
diff --git a/fs_mgr/libfs_avb/tests/fs_avb_test.cpp b/fs_mgr/libfs_avb/tests/fs_avb_test.cpp
new file mode 100644
index 0000000..2c819a9
--- /dev/null
+++ b/fs_mgr/libfs_avb/tests/fs_avb_test.cpp
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <endian.h>
+#include <stdlib.h>
+
+#include <android-base/file.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <fs_avb/fs_avb.h>
+#include <libavb/libavb.h>
+
+#include "fs_avb_test_util.h"
+
+// Target classes or functions to test:
+using android::fs_mgr::AvbHandle;
+using android::fs_mgr::AvbHandleStatus;
+using android::fs_mgr::HashAlgorithm;
+
+namespace fs_avb_host_test {
+
+class PublicFsAvbTest : public BaseFsAvbTest {
+  public:
+    PublicFsAvbTest(){};
+
+  protected:
+    ~PublicFsAvbTest(){};
+    // Modifies |flags| field in the vbmeta header in an Avb image.
+    // e.g., AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED.
+    void ModifyVBMetaHeaderFlags(const base::FilePath& vbmeta_image_path, uint32_t flags);
+};
+
+void PublicFsAvbTest::ModifyVBMetaHeaderFlags(const base::FilePath& vbmeta_image_path,
+                                              uint32_t flags) {
+    if (!base::PathExists(vbmeta_image_path)) return;
+
+    // Only support modifying the flags in vbmeta*.img.
+    std::string image_file_name = vbmeta_image_path.RemoveExtension().BaseName().value();
+    ASSERT_TRUE(base::StartsWith(image_file_name, "vbmeta", base::CompareCase::INSENSITIVE_ASCII));
+
+    android::base::unique_fd fd(open(vbmeta_image_path.value().c_str(), O_RDWR | O_CLOEXEC));
+    EXPECT_TRUE(fd > 0);
+
+    auto flags_offset = offsetof(AvbVBMetaImageHeader, flags);
+    uint32_t flags_data = htobe32(flags);
+    EXPECT_EQ(flags_offset, lseek64(fd, flags_offset, SEEK_SET));
+    EXPECT_EQ(sizeof flags_data, write(fd, &flags_data, sizeof flags_data));
+}
+
+TEST_F(PublicFsAvbTest, LoadAndVerifyVbmeta) {
+    // Generates a raw boot.img
+    const size_t boot_image_size = 5 * 1024 * 1024;
+    const size_t boot_partition_size = 10 * 1024 * 1024;
+    base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+
+    // Adds AVB Hash Footer.
+    AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10,
+                 data_dir_.Append("testkey_rsa2048.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates a raw system.img, use a smaller size to speed-up unit test.
+    const size_t system_image_size = 10 * 1024 * 1024;
+    const size_t system_partition_size = 15 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system.img", system_image_size);
+    // Adds AVB Hashtree Footer.
+    AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20,
+                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates chain partition descriptors.
+    base::FilePath rsa2048_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
+    base::FilePath rsa4096_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));
+
+    // Makes a vbmeta image includeing 'boot' and 'system' chained descriptors.
+    auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
+                                           data_dir_.Append("testkey_rsa8192.pem"),
+                                           {}, /* include_descriptor_image_paths */
+                                           {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
+                                            {"system", 2, rsa4096_public_key}},
+                                           "--internal_release_string \"unit test\"");
+
+    // Calculates the digest of all chained partitions, to ensure the chained is formed properly.
+    EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f",
+              CalcVBMetaDigest("vbmeta.img", "sha256"));
+
+    // Invokes the public API from fs_avb.h.
+    auto vbmeta_image_path = [this](const std::string& partition_name) {
+        return test_dir_.Append(partition_name + ".img").value();
+    };
+    auto avb_handle = AvbHandle::LoadAndVerifyVbmeta(
+            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
+            false /* allow_verification_error */, true /* load_chained_vbmeta */,
+            true /* rollback_protection */, vbmeta_image_path);
+    EXPECT_NE(nullptr, avb_handle);
+    EXPECT_EQ(AvbHandleStatus::kSuccess, avb_handle->status());
+
+    // Checks the summary info for all vbmeta images.
+    // Checks the digest matches the value calculated by CalcVBMetaDigest().
+    EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f",
+              avb_handle->vbmeta_info().digest);
+    EXPECT_EQ(8576UL, avb_handle->vbmeta_info().total_size);
+    EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm);
+
+    // Skip loading chained vbmeta.
+    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
+            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
+            false /* allow_verification_error */, false /* load_chained_vbmeta */,
+            true /* rollback_protection */, vbmeta_image_path);
+    EXPECT_NE(nullptr, avb_handle);
+    EXPECT_EQ(AvbHandleStatus::kSuccess, avb_handle->status());
+    EXPECT_EQ("5c31197992b3c72a854ec7dc0eb9609ffebcffab7917ffd381a99ecee328f09c",
+              avb_handle->vbmeta_info().digest);
+    EXPECT_EQ(5184UL, avb_handle->vbmeta_info().total_size);
+    EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm);
+}
+
+TEST_F(PublicFsAvbTest, LoadAndVerifyVbmetaWithModifications) {
+    // Generates a raw boot.img
+    const size_t boot_image_size = 5 * 1024 * 1024;
+    const size_t boot_partition_size = 10 * 1024 * 1024;
+    base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+
+    // Adds AVB Hash Footer.
+    AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10,
+                 data_dir_.Append("testkey_rsa2048.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates a raw system.img, use a smaller size to speed-up unit test.
+    const size_t system_image_size = 10 * 1024 * 1024;
+    const size_t system_partition_size = 15 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system.img", system_image_size);
+    // Adds AVB Hashtree Footer.
+    AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20,
+                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates chain partition descriptors.
+    base::FilePath rsa2048_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
+    base::FilePath rsa4096_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));
+
+    // Makes a vbmeta image includeing 'boot' and 'system' chained descriptors.
+    auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
+                                           data_dir_.Append("testkey_rsa8192.pem"),
+                                           {}, /* include_descriptor_image_paths */
+                                           {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
+                                            {"system", 2, rsa4096_public_key}},
+                                           "--internal_release_string \"unit test\"");
+
+    // Calculates the digest of all chained partitions, to ensure the chained is formed properly.
+    EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f",
+              CalcVBMetaDigest("vbmeta.img", "sha256"));
+
+    // Sets AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED in the vbmeta.img.
+    ModifyVBMetaHeaderFlags(vbmeta_path, AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED);
+
+    auto vbmeta_image_path = [this](const std::string& partition_name) {
+        return test_dir_.Append(partition_name + ".img").value();
+    };
+    auto avb_handle = AvbHandle::LoadAndVerifyVbmeta(
+            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
+            false /* allow_verification_error */, true /* load_chained_vbmeta */,
+            true /* rollback_protection */, vbmeta_image_path);
+    // Returns a null handler because allow_verification is not True.
+    EXPECT_EQ(nullptr, avb_handle);
+
+    // Try again with allow_verification_error set to true.
+    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
+            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
+            true /* allow_verification_error */, true /* load_chained_vbmeta */,
+            true /* rollback_protection */, vbmeta_image_path);
+    EXPECT_NE(nullptr, avb_handle);
+    EXPECT_EQ(AvbHandleStatus::kHashtreeDisabled, avb_handle->status());
+
+    // Checks the summary info for all vbmeta images.
+    // Checks the digest matches the value calculated by CalcVBMetaDigest().
+    EXPECT_EQ("ae8f7ad95cbb7ce4f0feeeedc2a0a39824af5cd29dad4d028597cab4b8c2e83c",
+              CalcVBMetaDigest("vbmeta.img", "sha256"));
+    EXPECT_EQ("ae8f7ad95cbb7ce4f0feeeedc2a0a39824af5cd29dad4d028597cab4b8c2e83c",
+              avb_handle->vbmeta_info().digest);
+    EXPECT_EQ(8576UL, avb_handle->vbmeta_info().total_size);
+    EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm);
+
+    // Sets AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED in the vbmeta.img.
+    ModifyVBMetaHeaderFlags(vbmeta_path, AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED);
+    // Loads the vbmeta with allow_verification_error set to true.
+    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
+            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
+            true /* allow_verification_error */, true /* load_chained_vbmeta */,
+            true /* rollback_protection */, vbmeta_image_path);
+    EXPECT_NE(nullptr, avb_handle);
+    EXPECT_EQ(AvbHandleStatus::kVerificationDisabled, avb_handle->status());
+    // Only the top-level vbmeta.img is loaded, when VERIFICATION_DISABLED is set.
+    // However, CalcVBMetaDigest() reads all vbmeta structs to calculate the digest,
+    // including vbmeta.img, boot.img and syste.img. So we don't compare the digest here.
+    EXPECT_EQ(5184UL, avb_handle->vbmeta_info().total_size);
+
+    // Sets a unknown flag in the vbmeta.imgm and expects to get
+    // AvbHandleStatus::kVerificationError.
+    ModifyVBMetaHeaderFlags(vbmeta_path, 0x10000000);
+    // Loads the vbmeta with allow_verification_error set to true.
+    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
+            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
+            true /* allow_verification_error */, true /* load_chained_vbmeta */,
+            true /* rollback_protection */, vbmeta_image_path);
+    EXPECT_NE(nullptr, avb_handle);
+    EXPECT_EQ(AvbHandleStatus::kVerificationError, avb_handle->status());
+    // Checks the digest matches the value calculated by CalcVBMetaDigest().
+    EXPECT_EQ("8fb99c4f54500053c3582df5eaf04e9a533137398879188aad9968ec19a664f1",
+              CalcVBMetaDigest("vbmeta.img", "sha256"));
+    EXPECT_EQ("8fb99c4f54500053c3582df5eaf04e9a533137398879188aad9968ec19a664f1",
+              avb_handle->vbmeta_info().digest);
+    EXPECT_EQ(8576UL, avb_handle->vbmeta_info().total_size);
+    EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm);
+}
+
+TEST_F(PublicFsAvbTest, LoadAndVerifyVbmetaWithPublicKeys) {
+    // Generates a raw boot.img
+    const size_t boot_image_size = 5 * 1024 * 1024;
+    const size_t boot_partition_size = 10 * 1024 * 1024;
+    base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+
+    // Adds AVB Hash Footer.
+    AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10,
+                 data_dir_.Append("testkey_rsa2048.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates a raw system.img, use a smaller size to speed-up unit test.
+    const size_t system_image_size = 10 * 1024 * 1024;
+    const size_t system_partition_size = 15 * 1024 * 1024;
+    base::FilePath system_path = GenerateImage("system.img", system_image_size);
+    // Adds AVB Hashtree Footer.
+    AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20,
+                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Generates chain partition descriptors.
+    base::FilePath rsa2048_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
+    base::FilePath rsa4096_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));
+    base::FilePath rsa8192_public_key =
+            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa8192.pem"));
+
+    // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors.
+    auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
+                                           data_dir_.Append("testkey_rsa8192.pem"),
+                                           {}, /* include_descriptor_image_paths */
+                                           {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
+                                            {"system", 2, rsa4096_public_key}},
+                                           "--internal_release_string \"unit test\"");
+
+    auto vbmeta_image_path = [this](const std::string& partition_name) {
+        return test_dir_.Append(partition_name + ".img").value();
+    };
+    std::vector<VBMetaData> vbmeta_images;
+    // Uses the correct expected public key.
+    auto avb_handle = AvbHandle::LoadAndVerifyVbmeta(
+            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+            rsa8192_public_key.value(), HashAlgorithm::kSHA256, true /* allow_verification_error */,
+            true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path);
+    EXPECT_NE(nullptr, avb_handle);
+    EXPECT_EQ(AvbHandleStatus::kSuccess, avb_handle->status());
+
+    // Uses a non-existed public key.
+    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
+            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+            "/path/to/non-existed/key", HashAlgorithm::kSHA256, true /* allow_verification_error */,
+            true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path);
+    EXPECT_EQ(nullptr, avb_handle);
+
+    // Uses an incorrect public key, with allow_verification_error false.
+    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
+            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+            rsa4096_public_key.value(), HashAlgorithm::kSHA256,
+            false /* allow_verification_error */, true /* load_chained_vbmeta */,
+            true /* rollback_protection */, vbmeta_image_path);
+    EXPECT_EQ(nullptr, avb_handle);
+
+    // Uses an incorrect public key, with allow_verification_error true.
+    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
+            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
+            rsa4096_public_key.value(), HashAlgorithm::kSHA256, true /* allow_verification_error */,
+            true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path);
+    EXPECT_NE(nullptr, avb_handle);
+    EXPECT_EQ(AvbHandleStatus::kVerificationError, avb_handle->status());
+}
+
+}  // namespace fs_avb_host_test
diff --git a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
index 95b17d8..17f4c4e 100644
--- a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
+++ b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
@@ -69,7 +69,7 @@
     return trimmed_digest_data;
 }
 
-void BaseFsAvbTest::GenerateVBMetaImage(
+base::FilePath BaseFsAvbTest::GenerateVBMetaImage(
         const std::string& file_name, const std::string& avb_algorithm, uint64_t rollback_index,
         const base::FilePath& key_path,
         const std::vector<base::FilePath>& include_descriptor_image_paths,
@@ -107,17 +107,19 @@
                    chain_partition_options.c_str(), additional_options.c_str(),
                    vbmeta_image.path.value().c_str());
     int64_t file_size;
-    ASSERT_TRUE(base::GetFileSize(vbmeta_image.path, &file_size));
+    EXPECT_TRUE(base::GetFileSize(vbmeta_image.path, &file_size));
     vbmeta_image.content.resize(file_size);
-    ASSERT_TRUE(base::ReadFile(vbmeta_image.path,
+    EXPECT_TRUE(base::ReadFile(vbmeta_image.path,
                                reinterpret_cast<char*>(vbmeta_image.content.data()), file_size));
     // Stores the generated vbmeta image into vbmeta_images_ member object.
     vbmeta_images_.emplace(file_name, std::move(vbmeta_image));
+
+    return vbmeta_images_[file_name].path;  // returns the path.
 }
 
-void BaseFsAvbTest::ExtractVBMetaImage(const base::FilePath& image_path,
-                                       const std::string& output_file_name,
-                                       const size_t padding_size) {
+base::FilePath BaseFsAvbTest::ExtractVBMetaImage(const base::FilePath& image_path,
+                                                 const std::string& output_file_name,
+                                                 const size_t padding_size) {
     VBMetaImage vbmeta_image;
     vbmeta_image.path = test_dir_.Append(output_file_name);
     EXPECT_COMMAND(0,
@@ -127,12 +129,15 @@
                    " --padding_size %zu",
                    image_path.value().c_str(), vbmeta_image.path.value().c_str(), padding_size);
     int64_t file_size;
-    ASSERT_TRUE(base::GetFileSize(vbmeta_image.path, &file_size));
+    EXPECT_TRUE(base::GetFileSize(vbmeta_image.path, &file_size));
     vbmeta_image.content.resize(file_size);
-    ASSERT_TRUE(base::ReadFile(vbmeta_image.path,
+    EXPECT_TRUE(base::ReadFile(vbmeta_image.path,
                                reinterpret_cast<char*>(vbmeta_image.content.data()), file_size));
     // Stores the extracted vbmeta image into vbmeta_images_ member object.
     vbmeta_images_.emplace(output_file_name, std::move(vbmeta_image));
+
+    // Returns the output file path.
+    return vbmeta_images_[output_file_name].path;
 }
 
 // Generates a file with name |file_name| of size |image_size| with
@@ -179,6 +184,49 @@
                    additional_options.c_str());
 }
 
+VBMetaData BaseFsAvbTest::GenerateImageAndExtractVBMetaData(
+        const std::string& partition_name, const size_t image_size, const size_t partition_size,
+        const std::string& footer_type, const base::FilePath& avb_signing_key,
+        const std::string& avb_algorithm, const uint64_t rollback_index) {
+    // Generates a raw image first
+    base::FilePath image_path = GenerateImage(partition_name + ".img", image_size);
+
+    // Appends AVB Hashtree Footer.
+    AddAvbFooter(image_path, footer_type, partition_name, partition_size, avb_algorithm,
+                 rollback_index, avb_signing_key, "d00df00d",
+                 "--internal_release_string \"unit test\"");
+
+    // Extracts vbmeta from the ram image into another *-vbmeta.img.
+    auto vbmeta_image = ExtractVBMetaImage(image_path, partition_name + "-vbmeta.img");
+
+    // Loads *-vbmeta.img into a VBMetaData.
+    std::string vbmeta_buffer;
+    EXPECT_TRUE(base::ReadFileToString(vbmeta_image, &vbmeta_buffer));
+
+    return {(const uint8_t*)vbmeta_buffer.data(), vbmeta_buffer.size(), partition_name};
+}
+
+VBMetaData BaseFsAvbTest::LoadVBMetaData(const std::string& file_name) {
+    auto iter = vbmeta_images_.find(file_name);
+    EXPECT_NE(iter, vbmeta_images_.end());  // ensures file_name is generated before.
+
+    // Gets the image path from iterator->second.path: VBMetaImage.path.
+    base::FilePath image_path = iter->second.path;
+
+    // Loads the vbmeta_image into a VBMetaData.
+    std::string vbmeta_buffer;
+    EXPECT_TRUE(base::ReadFileToString(image_path, &vbmeta_buffer));
+
+    std::string partition_name = image_path.RemoveExtension().BaseName().value();
+    return {(const uint8_t*)vbmeta_buffer.data(), vbmeta_buffer.size(), partition_name};
+}
+
+VBMetaData BaseFsAvbTest::ExtractAndLoadVBMetaData(const base::FilePath& image_path,
+                                                   const std::string& output_file_name) {
+    ExtractVBMetaImage(image_path, output_file_name);
+    return LoadVBMetaData(output_file_name);
+}
+
 std::string BaseFsAvbTest::InfoImage(const base::FilePath& image_path) {
     base::FilePath tmp_path = test_dir_.Append("info_output.txt");
     EXPECT_COMMAND(0, "avbtool info_image --image %s --output %s", image_path.value().c_str(),
diff --git a/fs_mgr/libfs_avb/tests/fs_avb_test_util.h b/fs_mgr/libfs_avb/tests/fs_avb_test_util.h
index f80dc5f..2e46644 100644
--- a/fs_mgr/libfs_avb/tests/fs_avb_test_util.h
+++ b/fs_mgr/libfs_avb/tests/fs_avb_test_util.h
@@ -16,14 +16,20 @@
 
 #pragma once
 
+#include <fcntl.h>
 #include <inttypes.h>
 #include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <sys/wait.h>
+
 #include <string>
 #include <vector>
 
+#include <android-base/unique_fd.h>
 #include <base/files/file_path.h>
 #include <base/strings/stringprintf.h>
+#include <fs_avb/fs_avb.h>
 #include <gtest/gtest.h>
 
 // Utility macro to run the command expressed by the printf()-style string
@@ -36,6 +42,8 @@
         EXPECT_EQ(WEXITSTATUS(rc), expected_exit_status);                           \
     } while (0);
 
+using android::fs_mgr::VBMetaData;
+
 namespace fs_avb_host_test {
 
 struct VBMetaImage {
@@ -51,6 +59,10 @@
     base::FilePath key_blob_path;
 };
 
+inline android::base::unique_fd OpenUniqueReadFd(const base::FilePath& file_path) {
+    return android::base::unique_fd(open(file_path.value().c_str(), O_RDONLY | O_CLOEXEC));
+}
+
 /* Base-class used for unit test. */
 class BaseFsAvbTest : public ::testing::Test {
   public:
@@ -66,16 +78,18 @@
     // Generates a vbmeta image with |file_name| by avbtool.
     // The generated vbmeta image will be written to disk, see the
     // |vbmeta_images_| variable for its path and the content.
-    void GenerateVBMetaImage(const std::string& file_name, const std::string& avb_algorithm,
-                             uint64_t rollback_index, const base::FilePath& key_path,
-                             const std::vector<base::FilePath>& include_descriptor_image_paths,
-                             const std::vector<ChainPartitionConfig>& chain_partitions,
-                             const std::string& additional_options = "");
+    base::FilePath GenerateVBMetaImage(
+            const std::string& file_name, const std::string& avb_algorithm, uint64_t rollback_index,
+            const base::FilePath& key_path,
+            const std::vector<base::FilePath>& include_descriptor_image_paths,
+            const std::vector<ChainPartitionConfig>& chain_partitions,
+            const std::string& additional_options = "");
     // Similar to above, but extracts a vbmeta image from the given image_path.
     // The extracted vbmeta image will be written to disk, with |output_file_name|.
     // See the |vbmeta_images_| variable for its path and the content.
-    void ExtractVBMetaImage(const base::FilePath& image_path, const std::string& output_file_name,
-                            const size_t padding_size = 0);
+    base::FilePath ExtractVBMetaImage(const base::FilePath& image_path,
+                                      const std::string& output_file_name,
+                                      const size_t padding_size = 0);
 
     // Generate a file with name |file_name| of size |image_size| with
     // known content (0x00 0x01 0x02 .. 0xff 0x00 0x01 ..).
@@ -86,9 +100,19 @@
     void AddAvbFooter(const base::FilePath& image_path, const std::string& footer_type,
                       const std::string& partition_name, const uint64_t partition_size,
                       const std::string& avb_algorithm, uint64_t rollback_index,
-                      const base::FilePath& key_path, const std::string& salt = "d00df00d",
+                      const base::FilePath& avb_signing_key, const std::string& salt = "d00df00d",
                       const std::string& additional_options = "");
 
+    VBMetaData GenerateImageAndExtractVBMetaData(
+            const std::string& partition_name, const size_t image_size, const size_t partition_size,
+            const std::string& footer_type, const base::FilePath& avb_signing_key,
+            const std::string& avb_algorithm, const uint64_t rollback_index);
+
+    VBMetaData ExtractAndLoadVBMetaData(const base::FilePath& image_path,
+                                        const std::string& output_file_name);
+
+    VBMetaData LoadVBMetaData(const std::string& file_name);
+
     // Returns the output of 'avbtool info_image' for the |image_path|.
     std::string InfoImage(const base::FilePath& image_path);
     // Same as above, but for an internal vbmeta image with |file_name| in |vbmeta_images_|.
diff --git a/fs_mgr/libfs_avb/tests/util_test.cpp b/fs_mgr/libfs_avb/tests/util_test.cpp
index 835e8fd..9e37d22 100644
--- a/fs_mgr/libfs_avb/tests/util_test.cpp
+++ b/fs_mgr/libfs_avb/tests/util_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <unistd.h>
+
 #include <future>
 #include <string>
 #include <thread>
diff --git a/fs_mgr/libfs_avb/util.cpp b/fs_mgr/libfs_avb/util.cpp
index 17d47d9..9d4f05f 100644
--- a/fs_mgr/libfs_avb/util.cpp
+++ b/fs_mgr/libfs_avb/util.cpp
@@ -17,6 +17,7 @@
 #include "util.h"
 
 #include <sys/ioctl.h>
+
 #include <thread>
 
 #include <android-base/unique_fd.h>
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 110d56e..c39fbe7 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -838,9 +838,10 @@
                << block_device.size << ")";
         return false;
     }
-    if (device_info.logical_block_size != geometry_.logical_block_size) {
-        LERROR << "Device logical block size does not match (got " << device_info.logical_block_size
-               << ", expected " << geometry_.logical_block_size << ")";
+    if (geometry_.logical_block_size % device_info.logical_block_size) {
+        LERROR << "Device logical block size is misaligned (block size="
+               << device_info.logical_block_size << ", alignment=" << geometry_.logical_block_size
+               << ")";
         return false;
     }
 
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index 7d615a3..8f08169 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -495,6 +495,11 @@
     EXPECT_EQ(new_info.size, 1024 * 1024);
 
     new_info.logical_block_size = 512;
+    ASSERT_TRUE(builder->UpdateBlockDeviceInfo("super", new_info));
+    ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info));
+    EXPECT_EQ(new_info.logical_block_size, 4096);
+
+    new_info.logical_block_size = 7;
     ASSERT_FALSE(builder->UpdateBlockDeviceInfo("super", new_info));
     ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info));
     EXPECT_EQ(new_info.logical_block_size, 4096);
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index 42f3f29..ede0122 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -62,16 +62,17 @@
 Returns: true if the command succeeded" ]
 adb_sh() {
   args=
-  for i in ${@}; do
+  for i in "${@}"; do
+    [ -z "${args}" ] || args="${args} "
     if [ X"${i}" != X"${i#\'}" ]; then
-      args="${args} ${i}"
+      args="${args}${i}"
     elif [ X"${i}" != X"${i#* }" ]; then
-      args="${args} '${i}'"
+      args="${args}'${i}'"
     else
-      args="${args} ${i}"
+      args="${args}${i}"
     fi
   done
-  adb shell ${args}
+  adb shell "${args}"
 }
 
 [ "USAGE: adb_date >/dev/stdout
@@ -332,6 +333,12 @@
 
 # Do something
 
+D=`get_property ro.serialno`
+[ -n "${D}" ] || D=`get_property ro.boot.serialno`
+[ -z "${D}" ] || ANDROID_SERIAL=${D}
+BUILD_DESCRIPTION=`get_property ro.build.description`
+echo "${BLUE}[     INFO ]${NORMAL} ${ANDROID_SERIAL} ${BUILD_DESCRIPTION}" >&2
+
 echo "${GREEN}[ RUN      ]${NORMAL} Testing kernel support for overlayfs" >&2
 
 overlayfs_supported=true;
@@ -553,8 +560,8 @@
 echo "${GREEN}[ RUN      ]${NORMAL} push content to /system and /vendor" >&2
 
 A="Hello World! $(date)"
-echo "${A}" | adb_sh "cat - > /system/hello"
-echo "${A}" | adb_sh "cat - > /vendor/hello"
+echo "${A}" | adb_sh cat - ">/system/hello"
+echo "${A}" | adb_sh cat - ">/vendor/hello"
 B="`adb_cat /system/hello`" ||
   die "sytem hello"
 check_eq "${A}" "${B}" /system before reboot
@@ -575,7 +582,7 @@
     ( echo "${L}" && false ) ||
     die -d "overlay takeover failed after reboot"
 
-  adb_su "sed -n '1,/overlay \\/system/p' /proc/mounts" </dev/null |
+  adb_su sed -n '1,/overlay \/system/p' /proc/mounts </dev/null |
     skip_administrative_mounts |
     grep -v ' \(squashfs\|ext4\|f2fs\) ' &&
     echo "${ORANGE}[  WARNING ]${NORMAL} overlay takeover after first stage init" >&2 ||
@@ -705,17 +712,22 @@
 
 if [ -n "${scratch_partition}" ]; then
 
-  echo "${GREEN}[ RUN      ]${NORMAL} test fastboot flash to ${scratch_partition}" >&2
+  echo "${GREEN}[ RUN      ]${NORMAL} test fastboot flash to ${scratch_partition} recovery" >&2
 
   adb reboot-fastboot ||
     die "Reboot into fastbootd"
+  cleanup() {
+    rm /tmp/adb-remount-test.img
+  }
   dd if=/dev/zero of=/tmp/adb-remount-test.img bs=4096 count=16 2>/dev/null &&
     fastboot_wait 2m ||
-    ( rm /tmp/adb-remount-test.img && false) ||
     die "reboot into fastboot"
   fastboot flash --force ${scratch_partition} /tmp/adb-remount-test.img
   err=${?}
-  rm /tmp/adb-remount-test.img
+  cleanup
+  cleanup() {
+    :
+  }
   fastboot reboot ||
     die "can not reboot out of fastboot"
   [ 0 -eq ${err} ] ||
@@ -726,6 +738,8 @@
   T=`adb_date`
   D=`adb disable-verity 2>&1`
   err=${?}
+  adb remount ||
+    die "remount failed"
   echo "${D}"
   [ ${err} = 0 ] &&
     [ X"${D}" = X"${D##*setup failed}" ] &&
@@ -734,4 +748,18 @@
     die -t ${T} "setup for overlayfs"
 fi
 
+echo "${GREEN}[ RUN      ]${NORMAL} test raw remount command" >&2
+
+# prerequisite is a prepped device from above
+adb_reboot &&
+  adb_wait 2m ||
+  die "lost device after reboot to ro state"
+adb_sh grep " /vendor .* rw," /proc/mounts >/dev/null &&
+  die "/vendor is not read-only"
+adb_su mount -o rw,remount /vendor ||
+  die "remount command"
+adb_sh grep " /vendor .* rw," /proc/mounts >/dev/null ||
+  die "/vendor is not read-write"
+echo "${GREEN}[       OK ]${NORMAL} mount -o rw,remount command works" >&2
+
 echo "${GREEN}[  PASSED  ]${NORMAL} adb remount" >&2
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 1922a69..4582401 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -138,38 +138,32 @@
 }
 
 TEST(fs_mgr, fs_mgr_read_fstab_file_proc_mounts) {
-    auto fstab = fs_mgr_read_fstab("/proc/mounts");
-    ASSERT_NE(fstab, nullptr);
+    Fstab fstab;
+    ASSERT_TRUE(ReadFstabFromFile("/proc/mounts", &fstab));
 
     std::unique_ptr<std::FILE, int (*)(std::FILE*)> mounts(setmntent("/proc/mounts", "re"),
                                                            endmntent);
     ASSERT_NE(mounts, nullptr);
 
     mntent* mentry;
-    int i = 0;
+    size_t i = 0;
     while ((mentry = getmntent(mounts.get())) != nullptr) {
-        ASSERT_LT(i, fstab->num_entries);
-        auto fsrec = &fstab->recs[i];
+        ASSERT_LT(i, fstab.size());
+        auto& entry = fstab[i];
 
-        std::string mnt_fsname(mentry->mnt_fsname ?: "nullptr");
-        std::string blk_device(fsrec->blk_device ?: "nullptr");
-        EXPECT_EQ(mnt_fsname, blk_device);
-
-        std::string mnt_dir(mentry->mnt_dir ?: "nullptr");
-        std::string mount_point(fsrec->mount_point ?: "nullptr");
-        EXPECT_EQ(mnt_dir, mount_point);
-
-        std::string mnt_type(mentry->mnt_type ?: "nullptr");
-        std::string fs_type(fsrec->fs_type ?: "nullptr");
-        EXPECT_EQ(mnt_type, fs_type);
+        EXPECT_EQ(mentry->mnt_fsname, entry.blk_device);
+        EXPECT_EQ(mentry->mnt_dir, entry.mount_point);
+        EXPECT_EQ(mentry->mnt_type, entry.fs_type);
 
         std::set<std::string> mnt_opts;
-        for (auto& s : android::base::Split(mentry->mnt_opts ?: "nullptr", ",")) {
+        for (auto& s : android::base::Split(mentry->mnt_opts, ",")) {
             mnt_opts.emplace(s);
         }
         std::set<std::string> fs_options;
-        for (auto& s : android::base::Split(fsrec->fs_options ?: "nullptr", ",")) {
-            fs_options.emplace(s);
+        if (!entry.fs_options.empty()) {
+            for (auto& s : android::base::Split(entry.fs_options, ",")) {
+                fs_options.emplace(s);
+            }
         }
         // matches private content in fs_mgr_fstab.c
         static struct flag_list {
@@ -194,14 +188,17 @@
                 {0, 0},
         };
         for (auto f = 0; mount_flags[f].name; ++f) {
-            if (mount_flags[f].flag & fsrec->flags) {
+            if (mount_flags[f].flag & entry.flags) {
                 fs_options.emplace(mount_flags[f].name);
             }
         }
-        if (!(fsrec->flags & MS_RDONLY)) fs_options.emplace("rw");
+        if (!(entry.flags & MS_RDONLY)) {
+            fs_options.emplace("rw");
+        }
         EXPECT_EQ(mnt_opts, fs_options);
         ++i;
     }
+    EXPECT_EQ(i, fstab.size());
 }
 
 TEST(fs_mgr, ReadFstabFromFile_FsOptions) {
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index affa39e..153b857 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -73,7 +73,9 @@
     bool InitRequiredDevices();
     bool InitMappedDevice(const std::string& verity_device);
     bool CreateLogicalPartitions();
-    bool MountPartition(FstabEntry* fstab_entry);
+    bool MountPartition(const Fstab::iterator& begin, bool erase_used_fstab_entry,
+                        Fstab::iterator* end = nullptr);
+
     bool MountPartitions();
     bool TrySwitchSystemAsRoot();
     bool TrySkipMountingPartitions();
@@ -385,29 +387,40 @@
     return true;
 }
 
-bool FirstStageMount::MountPartition(FstabEntry* fstab_entry) {
-    if (fstab_entry->fs_mgr_flags.logical) {
-        if (!fs_mgr_update_logical_partition(fstab_entry)) {
+bool FirstStageMount::MountPartition(const Fstab::iterator& begin, bool erase_used_fstab_entry,
+                                     Fstab::iterator* end) {
+    if (begin->fs_mgr_flags.logical) {
+        if (!fs_mgr_update_logical_partition(&(*begin))) {
             return false;
         }
-        if (!InitMappedDevice(fstab_entry->blk_device)) {
+        if (!InitMappedDevice(begin->blk_device)) {
             return false;
         }
     }
-    if (!SetUpDmVerity(fstab_entry)) {
-        PLOG(ERROR) << "Failed to setup verity for '" << fstab_entry->mount_point << "'";
+    if (!SetUpDmVerity(&(*begin))) {
+        PLOG(ERROR) << "Failed to setup verity for '" << begin->mount_point << "'";
         return false;
     }
-    if (fs_mgr_do_mount_one(*fstab_entry)) {
-        if (fstab_entry->fs_mgr_flags.formattable) {
-            PLOG(INFO) << "Failed to mount '" << fstab_entry->mount_point << "', "
-                       << "ignoring mount for formattable partition";
-            return true;
+
+    bool mounted = (fs_mgr_do_mount_one(*begin) == 0);
+
+    // Try other mounts with the same mount point.
+    Fstab::iterator current = begin + 1;
+    for (; current != fstab_.end() && current->mount_point == begin->mount_point; current++) {
+        if (!mounted) {
+            // blk_device is already updated to /dev/dm-<N> by SetUpDmVerity() above.
+            // Copy it from the begin iterator.
+            current->blk_device = begin->blk_device;
+            mounted = (fs_mgr_do_mount_one(*current) == 0);
         }
-        PLOG(ERROR) << "Failed to mount '" << fstab_entry->mount_point << "'";
-        return false;
     }
-    return true;
+    if (erase_used_fstab_entry) {
+        current = fstab_.erase(begin, current);
+    }
+    if (end) {
+        *end = current;
+    }
+    return mounted;
 }
 
 // If system is in the fstab then we're not a system-as-root device, and in
@@ -418,8 +431,7 @@
         return entry.mount_point == "/metadata";
     });
     if (metadata_partition != fstab_.end()) {
-        if (MountPartition(&(*metadata_partition))) {
-            fstab_.erase(metadata_partition);
+        if (MountPartition(metadata_partition, true /* erase_used_fstab_entry */)) {
             UseGsiIfPresent();
         }
     }
@@ -430,30 +442,13 @@
 
     if (system_partition == fstab_.end()) return true;
 
-    bool mounted = false;
-    bool no_fail = false;
-    for (auto it = system_partition; it != fstab_.end();) {
-        if (it->mount_point != "/system") {
-            break;
-        }
-        no_fail |= (it->fs_mgr_flags).no_fail;
-        if (MountPartition(&(*it))) {
-            mounted = true;
-            SwitchRoot("/system");
-            break;
-        }
-        it++;
-    }
-
-    if (!mounted && !no_fail) {
-        LOG(ERROR) << "Failed to mount /system";
+    if (MountPartition(system_partition, true /* erase_used_fstab_entry */)) {
+        SwitchRoot("/system");
+    } else {
+        PLOG(ERROR) << "Failed to mount /system";
         return false;
     }
 
-    auto it = std::remove_if(fstab_.begin(), fstab_.end(),
-                             [](const auto& entry) { return entry.mount_point == "/system"; });
-    fstab_.erase(it, fstab_.end());
-
     return true;
 }
 
@@ -490,23 +485,21 @@
 
     if (!TrySkipMountingPartitions()) return false;
 
-    for (auto it = fstab_.begin(); it != fstab_.end();) {
-        bool mounted = false;
-        bool no_fail = false;
-        auto start_mount_point = it->mount_point;
-        do {
-            no_fail |= (it->fs_mgr_flags).no_fail;
-            if (!mounted)
-                mounted = MountPartition(&(*it));
-            else
-                LOG(INFO) << "Skip already-mounted partition: " << start_mount_point;
-            it++;
-        } while (it != fstab_.end() && it->mount_point == start_mount_point);
-
-        if (!mounted && !no_fail) {
-            LOG(ERROR) << start_mount_point << " mounted unsuccessfully but it is required!";
-            return false;
+    for (auto current = fstab_.begin(); current != fstab_.end();) {
+        Fstab::iterator end;
+        if (!MountPartition(current, false, &end)) {
+            if (current->fs_mgr_flags.no_fail) {
+                LOG(INFO) << "Failed to mount " << current->mount_point
+                          << ", ignoring mount for no_fail partition";
+            } else if (current->fs_mgr_flags.formattable) {
+                LOG(INFO) << "Failed to mount " << current->mount_point
+                          << ", ignoring mount for formattable partition";
+            } else {
+                PLOG(ERROR) << "Failed to mount " << current->mount_point;
+                return false;
+            }
         }
+        current = end;
     }
 
     // heads up for instantiating required device(s) for overlayfs logic
diff --git a/init/selinux.cpp b/init/selinux.cpp
index d93e9ec..ee302c1 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -34,16 +34,18 @@
 // identical to the system image shipped on a vendor's device.
 
 // The split SEPolicy is loaded as described below:
-// 1) There is a precompiled SEPolicy located at /vendor/etc/selinux/precompiled_sepolicy.
-//    Stored along with this file is the sha256 hash of the parts of the SEPolicy on /system that
-//    were used to compile this precompiled policy.  The system partition contains a similar sha256
-//    of the parts of the SEPolicy that it currently contains.  If these two hashes match, then the
-//    system loads this precompiled_sepolicy directly.
-// 2) If these hashes do not match, then /system has been updated out of sync with /vendor and the
-//    init needs to compile the SEPolicy.  /system contains the SEPolicy compiler, secilc, and it
-//    is used by the LoadSplitPolicy() function below to compile the SEPolicy to a temp directory
-//    and load it.  That function contains even more documentation with the specific implementation
-//    details of how the SEPolicy is compiled if needed.
+// 1) There is a precompiled SEPolicy located at either /vendor/etc/selinux/precompiled_sepolicy or
+//    /odm/etc/selinux/precompiled_sepolicy if odm parition is present.  Stored along with this file
+//    are the sha256 hashes of the parts of the SEPolicy on /system and /product that were used to
+//    compile this precompiled policy.  The system partition contains a similar sha256 of the parts
+//    of the SEPolicy that it currently contains.  Symmetrically, product paritition contains a
+//    sha256 of its SEPolicy.  System loads this precompiled_sepolicy directly if and only if hashes
+//    for system policy match and hashes for product policy match.
+// 2) If these hashes do not match, then either /system or /product (or both) have been updated out
+//    of sync with /vendor and the init needs to compile the SEPolicy.  /system contains the
+//    SEPolicy compiler, secilc, and it is used by the LoadSplitPolicy() function below to compile
+//    the SEPolicy to a temp directory and load it.  That function contains even more documentation
+//    with the specific implementation details of how the SEPolicy is compiled if needed.
 
 #include "selinux.h"
 
@@ -217,20 +219,35 @@
         return false;
     }
     std::string actual_plat_id;
-    if (!ReadFirstLine("/system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256", &actual_plat_id)) {
+    if (!ReadFirstLine("/system/etc/selinux/plat_sepolicy_and_mapping.sha256", &actual_plat_id)) {
         PLOG(INFO) << "Failed to read "
-                      "/system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256";
+                      "/system/etc/selinux/plat_sepolicy_and_mapping.sha256";
+        return false;
+    }
+    std::string actual_product_id;
+    if (!ReadFirstLine("/product/etc/selinux/product_sepolicy_and_mapping.sha256",
+                       &actual_product_id)) {
+        PLOG(INFO) << "Failed to read "
+                      "/product/etc/selinux/product_sepolicy_and_mapping.sha256";
         return false;
     }
 
     std::string precompiled_plat_id;
-    std::string precompiled_sha256 = *file + ".plat_and_mapping.sha256";
-    if (!ReadFirstLine(precompiled_sha256.c_str(), &precompiled_plat_id)) {
-        PLOG(INFO) << "Failed to read " << precompiled_sha256;
+    std::string precompiled_plat_sha256 = *file + ".plat_sepolicy_and_mapping.sha256";
+    if (!ReadFirstLine(precompiled_plat_sha256.c_str(), &precompiled_plat_id)) {
+        PLOG(INFO) << "Failed to read " << precompiled_plat_sha256;
         file->clear();
         return false;
     }
-    if ((actual_plat_id.empty()) || (actual_plat_id != precompiled_plat_id)) {
+    std::string precompiled_product_id;
+    std::string precompiled_product_sha256 = *file + ".product_sepolicy_and_mapping.sha256";
+    if (!ReadFirstLine(precompiled_product_sha256.c_str(), &precompiled_product_id)) {
+        PLOG(INFO) << "Failed to read " << precompiled_product_sha256;
+        file->clear();
+        return false;
+    }
+    if (actual_plat_id.empty() || actual_plat_id != precompiled_plat_id ||
+        actual_product_id.empty() || actual_product_id != precompiled_product_id) {
         file->clear();
         return false;
     }
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 1490fbc..59cbbc5 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -197,6 +197,9 @@
     { 00750, AID_ROOT,      AID_SHELL,     CAP_MASK_LONG(CAP_SETUID) |
                                            CAP_MASK_LONG(CAP_SETGID),
                                               "system/bin/run-as" },
+    { 00750, AID_ROOT,      AID_SHELL,     CAP_MASK_LONG(CAP_SETUID) |
+                                           CAP_MASK_LONG(CAP_SETGID),
+                                              "system/bin/simpleperf_app_runner" },
 
     // Support FIFO scheduling mode in SurfaceFlinger.
     { 00755, AID_SYSTEM,    AID_GRAPHICS,  CAP_MASK_LONG(CAP_SYS_NICE),
diff --git a/libmeminfo/include/meminfo/procmeminfo.h b/libmeminfo/include/meminfo/procmeminfo.h
index 95e5053..1fb4151 100644
--- a/libmeminfo/include/meminfo/procmeminfo.h
+++ b/libmeminfo/include/meminfo/procmeminfo.h
@@ -40,6 +40,11 @@
     const MemUsage& Usage();
     const MemUsage& Wss();
 
+    // Same as Maps() except, only valid for reading working set using CONFIG_IDLE_PAGE_TRACKING
+    // support in kernel. If the kernel support doesn't exist, the function will return an empty
+    // vector.
+    const std::vector<Vma>& MapsWithPageIdle();
+
     // Collect all 'vma' or 'maps' from /proc/<pid>/smaps and store them in 'maps_'. Returns a
     // constant reference to the vma vector after the collection is done.
     //
@@ -83,8 +88,8 @@
     ~ProcMemInfo() = default;
 
   private:
-    bool ReadMaps(bool get_wss);
-    bool ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss);
+    bool ReadMaps(bool get_wss, bool use_pageidle = false);
+    bool ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss, bool use_pageidle);
 
     pid_t pid_;
     bool get_wss_;
diff --git a/libmeminfo/procmeminfo.cpp b/libmeminfo/procmeminfo.cpp
index 1df03b2..069b6b3 100644
--- a/libmeminfo/procmeminfo.cpp
+++ b/libmeminfo/procmeminfo.cpp
@@ -121,6 +121,14 @@
     return maps_;
 }
 
+const std::vector<Vma>& ProcMemInfo::MapsWithPageIdle() {
+    if (maps_.empty() && !ReadMaps(get_wss_, true)) {
+        LOG(ERROR) << "Failed to read maps with page idle for Process " << pid_;
+    }
+
+    return maps_;
+}
+
 const std::vector<Vma>& ProcMemInfo::Smaps(const std::string& path) {
     if (!maps_.empty()) {
         return maps_;
@@ -226,7 +234,7 @@
     return true;
 }
 
-bool ProcMemInfo::ReadMaps(bool get_wss) {
+bool ProcMemInfo::ReadMaps(bool get_wss, bool use_pageidle) {
     // Each object reads /proc/<pid>/maps only once. This is done to make sure programs that are
     // running for the lifetime of the system can recycle the objects and don't have to
     // unnecessarily retain and update this object in memory (which can get significantly large).
@@ -256,7 +264,7 @@
     }
 
     for (auto& vma : maps_) {
-        if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss)) {
+        if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss, use_pageidle)) {
             LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start << "-"
                        << vma.end << "]";
             maps_.clear();
@@ -268,7 +276,7 @@
     return true;
 }
 
-bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss) {
+bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss, bool use_pageidle) {
     PageAcct& pinfo = PageAcct::Instance();
     uint64_t pagesz = getpagesize();
     uint64_t num_pages = (vma.end - vma.start) / pagesz;
@@ -281,6 +289,13 @@
         return false;
     }
 
+    if (get_wss && use_pageidle) {
+        if (!pinfo.InitPageAcct(true)) {
+            LOG(ERROR) << "Failed to init idle page accounting";
+            return false;
+        }
+    }
+
     std::unique_ptr<uint64_t[]> pg_flags(new uint64_t[num_pages]);
     std::unique_ptr<uint64_t[]> pg_counts(new uint64_t[num_pages]);
     for (uint64_t i = 0; i < num_pages; ++i) {
@@ -323,7 +338,8 @@
         bool is_private = (pg_counts[i] == 1);
         // Working set
         if (get_wss) {
-            bool is_referenced = !!(pg_flags[i] & (1 << KPF_REFERENCED));
+            bool is_referenced = use_pageidle ? (pinfo.IsPageIdle(page_frame) == 1)
+                                              : !!(pg_flags[i] & (1 << KPF_REFERENCED));
             if (!is_referenced) {
                 continue;
             }
diff --git a/libmeminfo/tools/Android.bp b/libmeminfo/tools/Android.bp
index 24054c6..2e89c41 100644
--- a/libmeminfo/tools/Android.bp
+++ b/libmeminfo/tools/Android.bp
@@ -67,3 +67,17 @@
         "libmeminfo",
     ],
 }
+
+cc_binary {
+    name: "wsstop",
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    srcs: ["wsstop.cpp"],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libmeminfo",
+    ],
+}
diff --git a/libmeminfo/tools/wsstop.cpp b/libmeminfo/tools/wsstop.cpp
new file mode 100644
index 0000000..368d04e
--- /dev/null
+++ b/libmeminfo/tools/wsstop.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <meminfo/pageacct.h>
+#include <meminfo/procmeminfo.h>
+
+using ::android::meminfo::ProcMemInfo;
+using ::android::meminfo::Vma;
+
+// Global options
+static int32_t g_delay = 0;
+static int32_t g_total = 2;
+static pid_t g_pid = -1;
+
+[[noreturn]] static void usage(int exit_status) {
+    fprintf(stderr,
+            "%s [-d DELAY_BETWEEN_EACH_SAMPLE] [-n REFRESH_TOTAL] PID\n"
+            "-d\tdelay between each working set sample (default 0)\n"
+            "-n\ttotal number of refreshes before we exit (default 2)\n",
+            getprogname());
+
+    exit(exit_status);
+}
+
+static void print_header() {
+    const char* addr1 = "           start              end ";
+    const char* addr2 = "            addr             addr ";
+
+    printf("%s  virtual                        shared    shared   private   private\n", addr1);
+    printf("%s     size       RSS       PSS     clean     dirty     clean     dirty      swap   "
+           "swapPSS",
+           addr2);
+    printf(" object\n");
+}
+
+static void print_divider() {
+    printf("---------------- ---------------- ");
+    printf("--------- --------- --------- --------- --------- --------- --------- --------- "
+           "--------- ");
+    printf("------------------------------\n");
+}
+
+static void print_vma(const Vma& v) {
+    printf("%16" PRIx64 " %16" PRIx64 " ", v.start, v.end);
+    printf("%8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64
+           "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K ",
+           v.usage.vss / 1024, v.usage.rss / 1024, v.usage.pss / 1024, v.usage.shared_clean / 1024,
+           v.usage.shared_dirty / 1024, v.usage.private_clean / 1024, v.usage.private_dirty / 1024,
+           v.usage.swap / 1024, v.usage.swap_pss / 1024);
+    printf("%s\n", v.name.c_str());
+}
+
+static bool same_vma(const Vma& cur, const Vma& last) {
+    return (cur.start == last.start && cur.end == last.end && cur.name == last.name &&
+            cur.flags == last.flags && cur.offset == last.offset);
+}
+
+static Vma diff_vma_params(const Vma& cur, const Vma& last) {
+    Vma res;
+    res.usage.shared_clean = cur.usage.shared_clean > last.usage.shared_clean
+                                     ? cur.usage.shared_clean - last.usage.shared_clean
+                                     : 0;
+    res.usage.shared_dirty = cur.usage.shared_dirty > last.usage.shared_dirty
+                                     ? cur.usage.shared_dirty - last.usage.shared_dirty
+                                     : 0;
+    res.usage.private_clean = cur.usage.private_clean > last.usage.private_clean
+                                      ? cur.usage.private_clean - last.usage.private_clean
+                                      : 0;
+    res.usage.private_dirty = cur.usage.private_dirty > last.usage.private_dirty
+                                      ? cur.usage.private_dirty - last.usage.private_dirty
+                                      : 0;
+
+    res.usage.rss = cur.usage.rss > last.usage.rss ? cur.usage.rss - last.usage.rss : 0;
+    res.usage.pss = cur.usage.pss > last.usage.pss ? cur.usage.pss - last.usage.pss : 0;
+    res.usage.uss = cur.usage.uss > last.usage.uss ? cur.usage.uss - last.usage.uss : 0;
+    res.usage.swap = cur.usage.swap > last.usage.swap ? cur.usage.swap - last.usage.swap : 0;
+    res.usage.swap_pss =
+            cur.usage.swap_pss > last.usage.swap_pss ? cur.usage.swap_pss - last.usage.swap_pss : 0;
+
+    // set vma properties to the same as the current one.
+    res.start = cur.start;
+    res.end = cur.end;
+    res.offset = cur.offset;
+    res.flags = cur.flags;
+    res.name = cur.name;
+    return res;
+}
+
+static void diff_workingset(std::vector<Vma>& wss, std::vector<Vma>& old, std::vector<Vma>* res) {
+    res->clear();
+    auto vma_sorter = [](const Vma& a, const Vma& b) { return a.start < b.start; };
+    std::sort(wss.begin(), wss.end(), vma_sorter);
+    std::sort(old.begin(), old.end(), vma_sorter);
+    if (old.empty()) {
+        *res = wss;
+        return;
+    }
+
+    for (auto& i : wss) {
+        bool found_same_vma = false;
+        // TODO: This is highly inefficient, fix it if it takes
+        // too long. Worst case will be system_server
+        for (auto& j : old) {
+            if (same_vma(i, j)) {
+                res->emplace_back(diff_vma_params(i, j));
+                found_same_vma = true;
+                break;
+            }
+        }
+
+        if (!found_same_vma) {
+            res->emplace_back(i);
+        }
+    }
+
+    std::sort(res->begin(), res->end(), vma_sorter);
+    return;
+}
+
+static int workingset() {
+    std::vector<Vma> last_wss = {};
+    std::vector<Vma> diff_wss = {};
+    uint32_t nr_refresh = 0;
+
+    while (true) {
+        std::unique_ptr<ProcMemInfo> proc_mem = std::make_unique<ProcMemInfo>(g_pid, true);
+        std::vector<Vma> wss = proc_mem->MapsWithPageIdle();
+
+        diff_workingset(wss, last_wss, &diff_wss);
+        diff_wss.erase(std::remove_if(diff_wss.begin(), diff_wss.end(),
+                                      [](const auto& v) { return v.usage.rss == 0; }),
+                       diff_wss.end());
+        if ((nr_refresh % 5) == 0) {
+            print_header();
+            print_divider();
+        }
+
+        for (const auto& v : diff_wss) {
+            print_vma(v);
+        }
+
+        nr_refresh++;
+        if (nr_refresh == g_total) {
+            break;
+        }
+
+        last_wss = wss;
+        sleep(g_delay);
+        print_divider();
+    }
+
+    return 0;
+}
+
+int main(int argc, char* argv[]) {
+    struct option longopts[] = {
+            {"help", no_argument, nullptr, 'h'},
+            {0, 0, nullptr, 0},
+    };
+
+    int opt;
+    while ((opt = getopt_long(argc, argv, "d:n:h", longopts, nullptr)) != -1) {
+        switch (opt) {
+            case 'd':
+                g_delay = atoi(optarg);
+                break;
+            case 'n':
+                g_total = atoi(optarg);
+                break;
+            case 'h':
+                usage(EXIT_SUCCESS);
+            default:
+                usage(EXIT_FAILURE);
+        }
+    }
+
+    if ((argc - 1) < optind) {
+        fprintf(stderr, "Invalid arguments: Must provide <pid> at the end\n");
+        usage(EXIT_FAILURE);
+    }
+
+    g_pid = atoi(argv[optind]);
+    if (g_pid <= 0) {
+        fprintf(stderr, "Invalid process id %s\n", argv[optind]);
+        usage(EXIT_FAILURE);
+    }
+
+    if (!::android::meminfo::PageAcct::KernelHasPageIdle()) {
+        fprintf(stderr, "Missing support for Idle page tracking in the kernel\n");
+        return 0;
+    }
+
+    return workingset();
+}
diff --git a/libsparse/Android.bp b/libsparse/Android.bp
index 8ad339f..2ec4754 100644
--- a/libsparse/Android.bp
+++ b/libsparse/Android.bp
@@ -67,3 +67,18 @@
 
     cflags: ["-Werror"],
 }
+
+python_binary_host {
+    name: "simg_dump.py",
+    main: "simg_dump.py",
+    srcs: ["simg_dump.py"],
+    version: {
+        py2: {
+            embedded_launcher: true,
+            enabled: true,
+        },
+        py3: {
+            enabled: false,
+        },
+    },
+}
diff --git a/libsparse/Android.mk b/libsparse/Android.mk
deleted file mode 100644
index 05e68bc..0000000
--- a/libsparse/Android.mk
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright 2010 The Android Open Source Project
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := simg_dump.py
-LOCAL_SRC_FILES := simg_dump.py
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_IS_HOST_MODULE := true
-LOCAL_CFLAGS := -Werror
-include $(BUILD_PREBUILT)
diff --git a/libunwindstack/DwarfOp.cpp b/libunwindstack/DwarfOp.cpp
index 5bc60b9..393eb3e 100644
--- a/libunwindstack/DwarfOp.cpp
+++ b/libunwindstack/DwarfOp.cpp
@@ -32,8 +32,1451 @@
 
 namespace unwindstack {
 
+enum DwarfOpHandleFunc : uint8_t {
+  OP_ILLEGAL = 0,
+  OP_DEREF,
+  OP_DEREF_SIZE,
+  OP_PUSH,
+  OP_DUP,
+  OP_DROP,
+  OP_OVER,
+  OP_PICK,
+  OP_SWAP,
+  OP_ROT,
+  OP_ABS,
+  OP_AND,
+  OP_DIV,
+  OP_MINUS,
+  OP_MOD,
+  OP_MUL,
+  OP_NEG,
+  OP_NOT,
+  OP_OR,
+  OP_PLUS,
+  OP_PLUS_UCONST,
+  OP_SHL,
+  OP_SHR,
+  OP_SHRA,
+  OP_XOR,
+  OP_BRA,
+  OP_EQ,
+  OP_GE,
+  OP_GT,
+  OP_LE,
+  OP_LT,
+  OP_NE,
+  OP_SKIP,
+  OP_LIT,
+  OP_REG,
+  OP_REGX,
+  OP_BREG,
+  OP_BREGX,
+  OP_NOP,
+  OP_NOT_IMPLEMENTED,
+};
+
+struct OpCallback {
+  // It may seem tempting to "clean this up" by replacing "const char[26]" with
+  // "const char*", but doing so would place the entire callback table in
+  // .data.rel.ro section, instead of .rodata section, and thus increase
+  // dirty memory usage.  Libunwindstack is used by the linker and therefore
+  // loaded for every running process, so every bit of memory counts.
+  // Unlike C standard, C++ standard guarantees this array is big enough to
+  // store the names, or else we would get a compilation error.
+  const char name[26];
+
+  // Similarily for this field, we do NOT want to directly store function
+  // pointers here. Not only would that cause the callback table to be placed
+  // in .data.rel.ro section, but it would be duplicated for each AddressType.
+  // Instead, we use DwarfOpHandleFunc enum to decouple the callback table from
+  // the function pointers.
+  DwarfOpHandleFunc handle_func;
+
+  uint8_t num_required_stack_values;
+  uint8_t num_operands;
+  uint8_t operands[2];
+};
+
+constexpr static OpCallback kCallbackTable[256] = {
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0x00 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0x01 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0x02 illegal op
+    {
+        // 0x03 DW_OP_addr
+        "DW_OP_addr",
+        OP_PUSH,
+        0,
+        1,
+        {DW_EH_PE_absptr},
+    },
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0x04 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0x05 illegal op
+    {
+        // 0x06 DW_OP_deref
+        "DW_OP_deref",
+        OP_DEREF,
+        1,
+        0,
+        {},
+    },
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0x07 illegal op
+    {
+        // 0x08 DW_OP_const1u
+        "DW_OP_const1u",
+        OP_PUSH,
+        0,
+        1,
+        {DW_EH_PE_udata1},
+    },
+    {
+        // 0x09 DW_OP_const1s
+        "DW_OP_const1s",
+        OP_PUSH,
+        0,
+        1,
+        {DW_EH_PE_sdata1},
+    },
+    {
+        // 0x0a DW_OP_const2u
+        "DW_OP_const2u",
+        OP_PUSH,
+        0,
+        1,
+        {DW_EH_PE_udata2},
+    },
+    {
+        // 0x0b DW_OP_const2s
+        "DW_OP_const2s",
+        OP_PUSH,
+        0,
+        1,
+        {DW_EH_PE_sdata2},
+    },
+    {
+        // 0x0c DW_OP_const4u
+        "DW_OP_const4u",
+        OP_PUSH,
+        0,
+        1,
+        {DW_EH_PE_udata4},
+    },
+    {
+        // 0x0d DW_OP_const4s
+        "DW_OP_const4s",
+        OP_PUSH,
+        0,
+        1,
+        {DW_EH_PE_sdata4},
+    },
+    {
+        // 0x0e DW_OP_const8u
+        "DW_OP_const8u",
+        OP_PUSH,
+        0,
+        1,
+        {DW_EH_PE_udata8},
+    },
+    {
+        // 0x0f DW_OP_const8s
+        "DW_OP_const8s",
+        OP_PUSH,
+        0,
+        1,
+        {DW_EH_PE_sdata8},
+    },
+    {
+        // 0x10 DW_OP_constu
+        "DW_OP_constu",
+        OP_PUSH,
+        0,
+        1,
+        {DW_EH_PE_uleb128},
+    },
+    {
+        // 0x11 DW_OP_consts
+        "DW_OP_consts",
+        OP_PUSH,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x12 DW_OP_dup
+        "DW_OP_dup",
+        OP_DUP,
+        1,
+        0,
+        {},
+    },
+    {
+        // 0x13 DW_OP_drop
+        "DW_OP_drop",
+        OP_DROP,
+        1,
+        0,
+        {},
+    },
+    {
+        // 0x14 DW_OP_over
+        "DW_OP_over",
+        OP_OVER,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x15 DW_OP_pick
+        "DW_OP_pick",
+        OP_PICK,
+        0,
+        1,
+        {DW_EH_PE_udata1},
+    },
+    {
+        // 0x16 DW_OP_swap
+        "DW_OP_swap",
+        OP_SWAP,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x17 DW_OP_rot
+        "DW_OP_rot",
+        OP_ROT,
+        3,
+        0,
+        {},
+    },
+    {
+        // 0x18 DW_OP_xderef
+        "DW_OP_xderef",
+        OP_NOT_IMPLEMENTED,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x19 DW_OP_abs
+        "DW_OP_abs",
+        OP_ABS,
+        1,
+        0,
+        {},
+    },
+    {
+        // 0x1a DW_OP_and
+        "DW_OP_and",
+        OP_AND,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x1b DW_OP_div
+        "DW_OP_div",
+        OP_DIV,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x1c DW_OP_minus
+        "DW_OP_minus",
+        OP_MINUS,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x1d DW_OP_mod
+        "DW_OP_mod",
+        OP_MOD,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x1e DW_OP_mul
+        "DW_OP_mul",
+        OP_MUL,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x1f DW_OP_neg
+        "DW_OP_neg",
+        OP_NEG,
+        1,
+        0,
+        {},
+    },
+    {
+        // 0x20 DW_OP_not
+        "DW_OP_not",
+        OP_NOT,
+        1,
+        0,
+        {},
+    },
+    {
+        // 0x21 DW_OP_or
+        "DW_OP_or",
+        OP_OR,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x22 DW_OP_plus
+        "DW_OP_plus",
+        OP_PLUS,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x23 DW_OP_plus_uconst
+        "DW_OP_plus_uconst",
+        OP_PLUS_UCONST,
+        1,
+        1,
+        {DW_EH_PE_uleb128},
+    },
+    {
+        // 0x24 DW_OP_shl
+        "DW_OP_shl",
+        OP_SHL,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x25 DW_OP_shr
+        "DW_OP_shr",
+        OP_SHR,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x26 DW_OP_shra
+        "DW_OP_shra",
+        OP_SHRA,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x27 DW_OP_xor
+        "DW_OP_xor",
+        OP_XOR,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x28 DW_OP_bra
+        "DW_OP_bra",
+        OP_BRA,
+        1,
+        1,
+        {DW_EH_PE_sdata2},
+    },
+    {
+        // 0x29 DW_OP_eq
+        "DW_OP_eq",
+        OP_EQ,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x2a DW_OP_ge
+        "DW_OP_ge",
+        OP_GE,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x2b DW_OP_gt
+        "DW_OP_gt",
+        OP_GT,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x2c DW_OP_le
+        "DW_OP_le",
+        OP_LE,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x2d DW_OP_lt
+        "DW_OP_lt",
+        OP_LT,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x2e DW_OP_ne
+        "DW_OP_ne",
+        OP_NE,
+        2,
+        0,
+        {},
+    },
+    {
+        // 0x2f DW_OP_skip
+        "DW_OP_skip",
+        OP_SKIP,
+        0,
+        1,
+        {DW_EH_PE_sdata2},
+    },
+    {
+        // 0x30 DW_OP_lit0
+        "DW_OP_lit0",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x31 DW_OP_lit1
+        "DW_OP_lit1",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x32 DW_OP_lit2
+        "DW_OP_lit2",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x33 DW_OP_lit3
+        "DW_OP_lit3",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x34 DW_OP_lit4
+        "DW_OP_lit4",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x35 DW_OP_lit5
+        "DW_OP_lit5",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x36 DW_OP_lit6
+        "DW_OP_lit6",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x37 DW_OP_lit7
+        "DW_OP_lit7",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x38 DW_OP_lit8
+        "DW_OP_lit8",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x39 DW_OP_lit9
+        "DW_OP_lit9",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x3a DW_OP_lit10
+        "DW_OP_lit10",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x3b DW_OP_lit11
+        "DW_OP_lit11",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x3c DW_OP_lit12
+        "DW_OP_lit12",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x3d DW_OP_lit13
+        "DW_OP_lit13",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x3e DW_OP_lit14
+        "DW_OP_lit14",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x3f DW_OP_lit15
+        "DW_OP_lit15",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x40 DW_OP_lit16
+        "DW_OP_lit16",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x41 DW_OP_lit17
+        "DW_OP_lit17",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x42 DW_OP_lit18
+        "DW_OP_lit18",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x43 DW_OP_lit19
+        "DW_OP_lit19",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x44 DW_OP_lit20
+        "DW_OP_lit20",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x45 DW_OP_lit21
+        "DW_OP_lit21",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x46 DW_OP_lit22
+        "DW_OP_lit22",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x47 DW_OP_lit23
+        "DW_OP_lit23",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x48 DW_OP_lit24
+        "DW_OP_lit24",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x49 DW_OP_lit25
+        "DW_OP_lit25",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x4a DW_OP_lit26
+        "DW_OP_lit26",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x4b DW_OP_lit27
+        "DW_OP_lit27",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x4c DW_OP_lit28
+        "DW_OP_lit28",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x4d DW_OP_lit29
+        "DW_OP_lit29",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x4e DW_OP_lit30
+        "DW_OP_lit30",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x4f DW_OP_lit31
+        "DW_OP_lit31",
+        OP_LIT,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x50 DW_OP_reg0
+        "DW_OP_reg0",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x51 DW_OP_reg1
+        "DW_OP_reg1",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x52 DW_OP_reg2
+        "DW_OP_reg2",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x53 DW_OP_reg3
+        "DW_OP_reg3",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x54 DW_OP_reg4
+        "DW_OP_reg4",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x55 DW_OP_reg5
+        "DW_OP_reg5",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x56 DW_OP_reg6
+        "DW_OP_reg6",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x57 DW_OP_reg7
+        "DW_OP_reg7",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x58 DW_OP_reg8
+        "DW_OP_reg8",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x59 DW_OP_reg9
+        "DW_OP_reg9",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x5a DW_OP_reg10
+        "DW_OP_reg10",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x5b DW_OP_reg11
+        "DW_OP_reg11",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x5c DW_OP_reg12
+        "DW_OP_reg12",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x5d DW_OP_reg13
+        "DW_OP_reg13",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x5e DW_OP_reg14
+        "DW_OP_reg14",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x5f DW_OP_reg15
+        "DW_OP_reg15",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x60 DW_OP_reg16
+        "DW_OP_reg16",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x61 DW_OP_reg17
+        "DW_OP_reg17",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x62 DW_OP_reg18
+        "DW_OP_reg18",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x63 DW_OP_reg19
+        "DW_OP_reg19",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x64 DW_OP_reg20
+        "DW_OP_reg20",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x65 DW_OP_reg21
+        "DW_OP_reg21",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x66 DW_OP_reg22
+        "DW_OP_reg22",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x67 DW_OP_reg23
+        "DW_OP_reg23",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x68 DW_OP_reg24
+        "DW_OP_reg24",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x69 DW_OP_reg25
+        "DW_OP_reg25",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x6a DW_OP_reg26
+        "DW_OP_reg26",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x6b DW_OP_reg27
+        "DW_OP_reg27",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x6c DW_OP_reg28
+        "DW_OP_reg28",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x6d DW_OP_reg29
+        "DW_OP_reg29",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x6e DW_OP_reg30
+        "DW_OP_reg30",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x6f DW_OP_reg31
+        "DW_OP_reg31",
+        OP_REG,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x70 DW_OP_breg0
+        "DW_OP_breg0",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x71 DW_OP_breg1
+        "DW_OP_breg1",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x72 DW_OP_breg2
+        "DW_OP_breg2",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x73 DW_OP_breg3
+        "DW_OP_breg3",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x74 DW_OP_breg4
+        "DW_OP_breg4",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x75 DW_OP_breg5
+        "DW_OP_breg5",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x76 DW_OP_breg6
+        "DW_OP_breg6",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x77 DW_OP_breg7
+        "DW_OP_breg7",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x78 DW_OP_breg8
+        "DW_OP_breg8",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x79 DW_OP_breg9
+        "DW_OP_breg9",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x7a DW_OP_breg10
+        "DW_OP_breg10",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x7b DW_OP_breg11
+        "DW_OP_breg11",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x7c DW_OP_breg12
+        "DW_OP_breg12",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x7d DW_OP_breg13
+        "DW_OP_breg13",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x7e DW_OP_breg14
+        "DW_OP_breg14",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x7f DW_OP_breg15
+        "DW_OP_breg15",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x80 DW_OP_breg16
+        "DW_OP_breg16",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x81 DW_OP_breg17
+        "DW_OP_breg17",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x82 DW_OP_breg18
+        "DW_OP_breg18",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x83 DW_OP_breg19
+        "DW_OP_breg19",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x84 DW_OP_breg20
+        "DW_OP_breg20",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x85 DW_OP_breg21
+        "DW_OP_breg21",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x86 DW_OP_breg22
+        "DW_OP_breg22",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x87 DW_OP_breg23
+        "DW_OP_breg23",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x88 DW_OP_breg24
+        "DW_OP_breg24",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x89 DW_OP_breg25
+        "DW_OP_breg25",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x8a DW_OP_breg26
+        "DW_OP_breg26",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x8b DW_OP_breg27
+        "DW_OP_breg27",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x8c DW_OP_breg28
+        "DW_OP_breg28",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x8d DW_OP_breg29
+        "DW_OP_breg29",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x8e DW_OP_breg30
+        "DW_OP_breg30",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x8f DW_OP_breg31
+        "DW_OP_breg31",
+        OP_BREG,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x90 DW_OP_regx
+        "DW_OP_regx",
+        OP_REGX,
+        0,
+        1,
+        {DW_EH_PE_uleb128},
+    },
+    {
+        // 0x91 DW_OP_fbreg
+        "DW_OP_fbreg",
+        OP_NOT_IMPLEMENTED,
+        0,
+        1,
+        {DW_EH_PE_sleb128},
+    },
+    {
+        // 0x92 DW_OP_bregx
+        "DW_OP_bregx",
+        OP_BREGX,
+        0,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_sleb128},
+    },
+    {
+        // 0x93 DW_OP_piece
+        "DW_OP_piece",
+        OP_NOT_IMPLEMENTED,
+        0,
+        1,
+        {DW_EH_PE_uleb128},
+    },
+    {
+        // 0x94 DW_OP_deref_size
+        "DW_OP_deref_size",
+        OP_DEREF_SIZE,
+        1,
+        1,
+        {DW_EH_PE_udata1},
+    },
+    {
+        // 0x95 DW_OP_xderef_size
+        "DW_OP_xderef_size",
+        OP_NOT_IMPLEMENTED,
+        0,
+        1,
+        {DW_EH_PE_udata1},
+    },
+    {
+        // 0x96 DW_OP_nop
+        "DW_OP_nop",
+        OP_NOP,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x97 DW_OP_push_object_address
+        "DW_OP_push_object_address",
+        OP_NOT_IMPLEMENTED,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x98 DW_OP_call2
+        "DW_OP_call2",
+        OP_NOT_IMPLEMENTED,
+        0,
+        1,
+        {DW_EH_PE_udata2},
+    },
+    {
+        // 0x99 DW_OP_call4
+        "DW_OP_call4",
+        OP_NOT_IMPLEMENTED,
+        0,
+        1,
+        {DW_EH_PE_udata4},
+    },
+    {
+        // 0x9a DW_OP_call_ref
+        "DW_OP_call_ref",
+        OP_NOT_IMPLEMENTED,
+        0,
+        0,  // Has a different sized operand (4 bytes or 8 bytes).
+        {},
+    },
+    {
+        // 0x9b DW_OP_form_tls_address
+        "DW_OP_form_tls_address",
+        OP_NOT_IMPLEMENTED,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x9c DW_OP_call_frame_cfa
+        "DW_OP_call_frame_cfa",
+        OP_NOT_IMPLEMENTED,
+        0,
+        0,
+        {},
+    },
+    {
+        // 0x9d DW_OP_bit_piece
+        "DW_OP_bit_piece",
+        OP_NOT_IMPLEMENTED,
+        0,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_uleb128},
+    },
+    {
+        // 0x9e DW_OP_implicit_value
+        "DW_OP_implicit_value",
+        OP_NOT_IMPLEMENTED,
+        0,
+        1,
+        {DW_EH_PE_uleb128},
+    },
+    {
+        // 0x9f DW_OP_stack_value
+        "DW_OP_stack_value",
+        OP_NOT_IMPLEMENTED,
+        1,
+        0,
+        {},
+    },
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xa0 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xa1 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xa2 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xa3 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xa4 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xa5 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xa6 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xa7 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xa8 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xa9 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xaa illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xab illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xac illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xad illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xae illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xaf illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xb0 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xb1 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xb2 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xb3 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xb4 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xb5 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xb6 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xb7 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xb8 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xb9 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xba illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xbb illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xbc illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xbd illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xbe illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xbf illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xc0 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xc1 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xc2 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xc3 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xc4 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xc5 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xc6 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xc7 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xc8 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xc9 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xca illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xcb illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xcc illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xcd illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xce illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xcf illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xd0 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xd1 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xd2 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xd3 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xd4 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xd5 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xd6 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xd7 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xd8 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xd9 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xda illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xdb illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xdc illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xdd illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xde illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xdf illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xe0 DW_OP_lo_user
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xe1 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xe2 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xe3 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xe4 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xe5 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xe6 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xe7 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xe8 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xe9 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xea illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xeb illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xec illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xed illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xee illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xef illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xf0 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xf1 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xf2 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xf3 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xf4 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xf5 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xf6 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xf7 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xf8 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xf9 illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xfa illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xfb illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xfc illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xfd illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xfe illegal op
+    {"", OP_ILLEGAL, 0, 0, {}},  // 0xff DW_OP_hi_user
+};
+
 template <typename AddressType>
-constexpr typename DwarfOp<AddressType>::OpCallback DwarfOp<AddressType>::kCallbackTable[256];
+const typename DwarfOp<AddressType>::OpHandleFuncPtr DwarfOp<AddressType>::kOpHandleFuncList[] = {
+    [OP_ILLEGAL] = nullptr,
+    [OP_DEREF] = &DwarfOp<AddressType>::op_deref,
+    [OP_DEREF_SIZE] = &DwarfOp<AddressType>::op_deref_size,
+    [OP_PUSH] = &DwarfOp<AddressType>::op_push,
+    [OP_DUP] = &DwarfOp<AddressType>::op_dup,
+    [OP_DROP] = &DwarfOp<AddressType>::op_drop,
+    [OP_OVER] = &DwarfOp<AddressType>::op_over,
+    [OP_PICK] = &DwarfOp<AddressType>::op_pick,
+    [OP_SWAP] = &DwarfOp<AddressType>::op_swap,
+    [OP_ROT] = &DwarfOp<AddressType>::op_rot,
+    [OP_ABS] = &DwarfOp<AddressType>::op_abs,
+    [OP_AND] = &DwarfOp<AddressType>::op_and,
+    [OP_DIV] = &DwarfOp<AddressType>::op_div,
+    [OP_MINUS] = &DwarfOp<AddressType>::op_minus,
+    [OP_MOD] = &DwarfOp<AddressType>::op_mod,
+    [OP_MUL] = &DwarfOp<AddressType>::op_mul,
+    [OP_NEG] = &DwarfOp<AddressType>::op_neg,
+    [OP_NOT] = &DwarfOp<AddressType>::op_not,
+    [OP_OR] = &DwarfOp<AddressType>::op_or,
+    [OP_PLUS] = &DwarfOp<AddressType>::op_plus,
+    [OP_PLUS_UCONST] = &DwarfOp<AddressType>::op_plus_uconst,
+    [OP_SHL] = &DwarfOp<AddressType>::op_shl,
+    [OP_SHR] = &DwarfOp<AddressType>::op_shr,
+    [OP_SHRA] = &DwarfOp<AddressType>::op_shra,
+    [OP_XOR] = &DwarfOp<AddressType>::op_xor,
+    [OP_BRA] = &DwarfOp<AddressType>::op_bra,
+    [OP_EQ] = &DwarfOp<AddressType>::op_eq,
+    [OP_GE] = &DwarfOp<AddressType>::op_ge,
+    [OP_GT] = &DwarfOp<AddressType>::op_gt,
+    [OP_LE] = &DwarfOp<AddressType>::op_le,
+    [OP_LT] = &DwarfOp<AddressType>::op_lt,
+    [OP_NE] = &DwarfOp<AddressType>::op_ne,
+    [OP_SKIP] = &DwarfOp<AddressType>::op_skip,
+    [OP_LIT] = &DwarfOp<AddressType>::op_lit,
+    [OP_REG] = &DwarfOp<AddressType>::op_reg,
+    [OP_REGX] = &DwarfOp<AddressType>::op_regx,
+    [OP_BREG] = &DwarfOp<AddressType>::op_breg,
+    [OP_BREGX] = &DwarfOp<AddressType>::op_bregx,
+    [OP_NOP] = &DwarfOp<AddressType>::op_nop,
+    [OP_NOT_IMPLEMENTED] = &DwarfOp<AddressType>::op_not_implemented,
+};
 
 template <typename AddressType>
 bool DwarfOp<AddressType>::Eval(uint64_t start, uint64_t end) {
@@ -97,12 +1540,13 @@
   }
 
   const auto* op = &kCallbackTable[cur_op_];
-  const auto handle_func = op->handle_func;
-  if (handle_func == nullptr) {
+  if (op->handle_func == OP_ILLEGAL) {
     last_error_.code = DWARF_ERROR_ILLEGAL_VALUE;
     return false;
   }
 
+  const auto handle_func = kOpHandleFuncList[op->handle_func];
+
   // Make sure that the required number of stack elements is available.
   if (stack_.size() < op->num_required_stack_values) {
     last_error_.code = DWARF_ERROR_STACK_INDEX_NOT_VALID;
@@ -135,7 +1579,7 @@
     std::string raw_string(android::base::StringPrintf("Raw Data: 0x%02x", cur_op));
     std::string log_string;
     const auto* op = &kCallbackTable[cur_op];
-    if (op->handle_func == nullptr) {
+    if (op->handle_func == OP_ILLEGAL) {
       log_string = "Illegal";
     } else {
       log_string = op->name;
diff --git a/libunwindstack/DwarfOp.h b/libunwindstack/DwarfOp.h
index 4c69b3d..ac9fd2d 100644
--- a/libunwindstack/DwarfOp.h
+++ b/libunwindstack/DwarfOp.h
@@ -42,14 +42,6 @@
   // Signed version of AddressType
   typedef typename std::make_signed<AddressType>::type SignedType;
 
-  struct OpCallback {
-    const char* name;
-    bool (DwarfOp::*handle_func)();
-    uint8_t num_required_stack_values;
-    uint8_t num_operands;
-    uint8_t operands[2];
-  };
-
  public:
   DwarfOp(DwarfMemory* memory, Memory* regular_memory)
       : memory_(memory), regular_memory_(regular_memory) {}
@@ -143,1342 +135,8 @@
   bool op_nop();
   bool op_not_implemented();
 
-  constexpr static OpCallback kCallbackTable[256] = {
-      {nullptr, nullptr, 0, 0, {}},  // 0x00 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0x01 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0x02 illegal op
-      {
-          // 0x03 DW_OP_addr
-          "DW_OP_addr",
-          &DwarfOp::op_push,
-          0,
-          1,
-          {DW_EH_PE_absptr},
-      },
-      {nullptr, nullptr, 0, 0, {}},  // 0x04 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0x05 illegal op
-      {
-          // 0x06 DW_OP_deref
-          "DW_OP_deref",
-          &DwarfOp::op_deref,
-          1,
-          0,
-          {},
-      },
-      {nullptr, nullptr, 0, 0, {}},  // 0x07 illegal op
-      {
-          // 0x08 DW_OP_const1u
-          "DW_OP_const1u",
-          &DwarfOp::op_push,
-          0,
-          1,
-          {DW_EH_PE_udata1},
-      },
-      {
-          // 0x09 DW_OP_const1s
-          "DW_OP_const1s",
-          &DwarfOp::op_push,
-          0,
-          1,
-          {DW_EH_PE_sdata1},
-      },
-      {
-          // 0x0a DW_OP_const2u
-          "DW_OP_const2u",
-          &DwarfOp::op_push,
-          0,
-          1,
-          {DW_EH_PE_udata2},
-      },
-      {
-          // 0x0b DW_OP_const2s
-          "DW_OP_const2s",
-          &DwarfOp::op_push,
-          0,
-          1,
-          {DW_EH_PE_sdata2},
-      },
-      {
-          // 0x0c DW_OP_const4u
-          "DW_OP_const4u",
-          &DwarfOp::op_push,
-          0,
-          1,
-          {DW_EH_PE_udata4},
-      },
-      {
-          // 0x0d DW_OP_const4s
-          "DW_OP_const4s",
-          &DwarfOp::op_push,
-          0,
-          1,
-          {DW_EH_PE_sdata4},
-      },
-      {
-          // 0x0e DW_OP_const8u
-          "DW_OP_const8u",
-          &DwarfOp::op_push,
-          0,
-          1,
-          {DW_EH_PE_udata8},
-      },
-      {
-          // 0x0f DW_OP_const8s
-          "DW_OP_const8s",
-          &DwarfOp::op_push,
-          0,
-          1,
-          {DW_EH_PE_sdata8},
-      },
-      {
-          // 0x10 DW_OP_constu
-          "DW_OP_constu",
-          &DwarfOp::op_push,
-          0,
-          1,
-          {DW_EH_PE_uleb128},
-      },
-      {
-          // 0x11 DW_OP_consts
-          "DW_OP_consts",
-          &DwarfOp::op_push,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x12 DW_OP_dup
-          "DW_OP_dup",
-          &DwarfOp::op_dup,
-          1,
-          0,
-          {},
-      },
-      {
-          // 0x13 DW_OP_drop
-          "DW_OP_drop",
-          &DwarfOp::op_drop,
-          1,
-          0,
-          {},
-      },
-      {
-          // 0x14 DW_OP_over
-          "DW_OP_over",
-          &DwarfOp::op_over,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x15 DW_OP_pick
-          "DW_OP_pick",
-          &DwarfOp::op_pick,
-          0,
-          1,
-          {DW_EH_PE_udata1},
-      },
-      {
-          // 0x16 DW_OP_swap
-          "DW_OP_swap",
-          &DwarfOp::op_swap,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x17 DW_OP_rot
-          "DW_OP_rot",
-          &DwarfOp::op_rot,
-          3,
-          0,
-          {},
-      },
-      {
-          // 0x18 DW_OP_xderef
-          "DW_OP_xderef",
-          &DwarfOp::op_not_implemented,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x19 DW_OP_abs
-          "DW_OP_abs",
-          &DwarfOp::op_abs,
-          1,
-          0,
-          {},
-      },
-      {
-          // 0x1a DW_OP_and
-          "DW_OP_and",
-          &DwarfOp::op_and,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x1b DW_OP_div
-          "DW_OP_div",
-          &DwarfOp::op_div,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x1c DW_OP_minus
-          "DW_OP_minus",
-          &DwarfOp::op_minus,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x1d DW_OP_mod
-          "DW_OP_mod",
-          &DwarfOp::op_mod,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x1e DW_OP_mul
-          "DW_OP_mul",
-          &DwarfOp::op_mul,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x1f DW_OP_neg
-          "DW_OP_neg",
-          &DwarfOp::op_neg,
-          1,
-          0,
-          {},
-      },
-      {
-          // 0x20 DW_OP_not
-          "DW_OP_not",
-          &DwarfOp::op_not,
-          1,
-          0,
-          {},
-      },
-      {
-          // 0x21 DW_OP_or
-          "DW_OP_or",
-          &DwarfOp::op_or,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x22 DW_OP_plus
-          "DW_OP_plus",
-          &DwarfOp::op_plus,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x23 DW_OP_plus_uconst
-          "DW_OP_plus_uconst",
-          &DwarfOp::op_plus_uconst,
-          1,
-          1,
-          {DW_EH_PE_uleb128},
-      },
-      {
-          // 0x24 DW_OP_shl
-          "DW_OP_shl",
-          &DwarfOp::op_shl,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x25 DW_OP_shr
-          "DW_OP_shr",
-          &DwarfOp::op_shr,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x26 DW_OP_shra
-          "DW_OP_shra",
-          &DwarfOp::op_shra,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x27 DW_OP_xor
-          "DW_OP_xor",
-          &DwarfOp::op_xor,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x28 DW_OP_bra
-          "DW_OP_bra",
-          &DwarfOp::op_bra,
-          1,
-          1,
-          {DW_EH_PE_sdata2},
-      },
-      {
-          // 0x29 DW_OP_eq
-          "DW_OP_eq",
-          &DwarfOp::op_eq,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x2a DW_OP_ge
-          "DW_OP_ge",
-          &DwarfOp::op_ge,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x2b DW_OP_gt
-          "DW_OP_gt",
-          &DwarfOp::op_gt,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x2c DW_OP_le
-          "DW_OP_le",
-          &DwarfOp::op_le,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x2d DW_OP_lt
-          "DW_OP_lt",
-          &DwarfOp::op_lt,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x2e DW_OP_ne
-          "DW_OP_ne",
-          &DwarfOp::op_ne,
-          2,
-          0,
-          {},
-      },
-      {
-          // 0x2f DW_OP_skip
-          "DW_OP_skip",
-          &DwarfOp::op_skip,
-          0,
-          1,
-          {DW_EH_PE_sdata2},
-      },
-      {
-          // 0x30 DW_OP_lit0
-          "DW_OP_lit0",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x31 DW_OP_lit1
-          "DW_OP_lit1",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x32 DW_OP_lit2
-          "DW_OP_lit2",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x33 DW_OP_lit3
-          "DW_OP_lit3",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x34 DW_OP_lit4
-          "DW_OP_lit4",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x35 DW_OP_lit5
-          "DW_OP_lit5",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x36 DW_OP_lit6
-          "DW_OP_lit6",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x37 DW_OP_lit7
-          "DW_OP_lit7",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x38 DW_OP_lit8
-          "DW_OP_lit8",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x39 DW_OP_lit9
-          "DW_OP_lit9",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x3a DW_OP_lit10
-          "DW_OP_lit10",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x3b DW_OP_lit11
-          "DW_OP_lit11",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x3c DW_OP_lit12
-          "DW_OP_lit12",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x3d DW_OP_lit13
-          "DW_OP_lit13",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x3e DW_OP_lit14
-          "DW_OP_lit14",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x3f DW_OP_lit15
-          "DW_OP_lit15",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x40 DW_OP_lit16
-          "DW_OP_lit16",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x41 DW_OP_lit17
-          "DW_OP_lit17",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x42 DW_OP_lit18
-          "DW_OP_lit18",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x43 DW_OP_lit19
-          "DW_OP_lit19",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x44 DW_OP_lit20
-          "DW_OP_lit20",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x45 DW_OP_lit21
-          "DW_OP_lit21",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x46 DW_OP_lit22
-          "DW_OP_lit22",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x47 DW_OP_lit23
-          "DW_OP_lit23",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x48 DW_OP_lit24
-          "DW_OP_lit24",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x49 DW_OP_lit25
-          "DW_OP_lit25",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x4a DW_OP_lit26
-          "DW_OP_lit26",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x4b DW_OP_lit27
-          "DW_OP_lit27",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x4c DW_OP_lit28
-          "DW_OP_lit28",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x4d DW_OP_lit29
-          "DW_OP_lit29",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x4e DW_OP_lit30
-          "DW_OP_lit30",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x4f DW_OP_lit31
-          "DW_OP_lit31",
-          &DwarfOp::op_lit,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x50 DW_OP_reg0
-          "DW_OP_reg0",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x51 DW_OP_reg1
-          "DW_OP_reg1",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x52 DW_OP_reg2
-          "DW_OP_reg2",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x53 DW_OP_reg3
-          "DW_OP_reg3",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x54 DW_OP_reg4
-          "DW_OP_reg4",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x55 DW_OP_reg5
-          "DW_OP_reg5",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x56 DW_OP_reg6
-          "DW_OP_reg6",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x57 DW_OP_reg7
-          "DW_OP_reg7",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x58 DW_OP_reg8
-          "DW_OP_reg8",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x59 DW_OP_reg9
-          "DW_OP_reg9",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x5a DW_OP_reg10
-          "DW_OP_reg10",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x5b DW_OP_reg11
-          "DW_OP_reg11",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x5c DW_OP_reg12
-          "DW_OP_reg12",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x5d DW_OP_reg13
-          "DW_OP_reg13",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x5e DW_OP_reg14
-          "DW_OP_reg14",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x5f DW_OP_reg15
-          "DW_OP_reg15",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x60 DW_OP_reg16
-          "DW_OP_reg16",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x61 DW_OP_reg17
-          "DW_OP_reg17",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x62 DW_OP_reg18
-          "DW_OP_reg18",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x63 DW_OP_reg19
-          "DW_OP_reg19",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x64 DW_OP_reg20
-          "DW_OP_reg20",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x65 DW_OP_reg21
-          "DW_OP_reg21",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x66 DW_OP_reg22
-          "DW_OP_reg22",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x67 DW_OP_reg23
-          "DW_OP_reg23",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x68 DW_OP_reg24
-          "DW_OP_reg24",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x69 DW_OP_reg25
-          "DW_OP_reg25",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x6a DW_OP_reg26
-          "DW_OP_reg26",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x6b DW_OP_reg27
-          "DW_OP_reg27",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x6c DW_OP_reg28
-          "DW_OP_reg28",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x6d DW_OP_reg29
-          "DW_OP_reg29",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x6e DW_OP_reg30
-          "DW_OP_reg30",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x6f DW_OP_reg31
-          "DW_OP_reg31",
-          &DwarfOp::op_reg,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x70 DW_OP_breg0
-          "DW_OP_breg0",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x71 DW_OP_breg1
-          "DW_OP_breg1",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x72 DW_OP_breg2
-          "DW_OP_breg2",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x73 DW_OP_breg3
-          "DW_OP_breg3",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x74 DW_OP_breg4
-          "DW_OP_breg4",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x75 DW_OP_breg5
-          "DW_OP_breg5",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x76 DW_OP_breg6
-          "DW_OP_breg6",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x77 DW_OP_breg7
-          "DW_OP_breg7",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x78 DW_OP_breg8
-          "DW_OP_breg8",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x79 DW_OP_breg9
-          "DW_OP_breg9",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x7a DW_OP_breg10
-          "DW_OP_breg10",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x7b DW_OP_breg11
-          "DW_OP_breg11",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x7c DW_OP_breg12
-          "DW_OP_breg12",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x7d DW_OP_breg13
-          "DW_OP_breg13",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x7e DW_OP_breg14
-          "DW_OP_breg14",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x7f DW_OP_breg15
-          "DW_OP_breg15",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x80 DW_OP_breg16
-          "DW_OP_breg16",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x81 DW_OP_breg17
-          "DW_OP_breg17",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x82 DW_OP_breg18
-          "DW_OP_breg18",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x83 DW_OP_breg19
-          "DW_OP_breg19",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x84 DW_OP_breg20
-          "DW_OP_breg20",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x85 DW_OP_breg21
-          "DW_OP_breg21",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x86 DW_OP_breg22
-          "DW_OP_breg22",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x87 DW_OP_breg23
-          "DW_OP_breg23",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x88 DW_OP_breg24
-          "DW_OP_breg24",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x89 DW_OP_breg25
-          "DW_OP_breg25",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x8a DW_OP_breg26
-          "DW_OP_breg26",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x8b DW_OP_breg27
-          "DW_OP_breg27",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x8c DW_OP_breg28
-          "DW_OP_breg28",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x8d DW_OP_breg29
-          "DW_OP_breg29",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x8e DW_OP_breg30
-          "DW_OP_breg30",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x8f DW_OP_breg31
-          "DW_OP_breg31",
-          &DwarfOp::op_breg,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x90 DW_OP_regx
-          "DW_OP_regx",
-          &DwarfOp::op_regx,
-          0,
-          1,
-          {DW_EH_PE_uleb128},
-      },
-      {
-          // 0x91 DW_OP_fbreg
-          "DW_OP_fbreg",
-          &DwarfOp::op_not_implemented,
-          0,
-          1,
-          {DW_EH_PE_sleb128},
-      },
-      {
-          // 0x92 DW_OP_bregx
-          "DW_OP_bregx",
-          &DwarfOp::op_bregx,
-          0,
-          2,
-          {DW_EH_PE_uleb128, DW_EH_PE_sleb128},
-      },
-      {
-          // 0x93 DW_OP_piece
-          "DW_OP_piece",
-          &DwarfOp::op_not_implemented,
-          0,
-          1,
-          {DW_EH_PE_uleb128},
-      },
-      {
-          // 0x94 DW_OP_deref_size
-          "DW_OP_deref_size",
-          &DwarfOp::op_deref_size,
-          1,
-          1,
-          {DW_EH_PE_udata1},
-      },
-      {
-          // 0x95 DW_OP_xderef_size
-          "DW_OP_xderef_size",
-          &DwarfOp::op_not_implemented,
-          0,
-          1,
-          {DW_EH_PE_udata1},
-      },
-      {
-          // 0x96 DW_OP_nop
-          "DW_OP_nop",
-          &DwarfOp::op_nop,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x97 DW_OP_push_object_address
-          "DW_OP_push_object_address",
-          &DwarfOp::op_not_implemented,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x98 DW_OP_call2
-          "DW_OP_call2",
-          &DwarfOp::op_not_implemented,
-          0,
-          1,
-          {DW_EH_PE_udata2},
-      },
-      {
-          // 0x99 DW_OP_call4
-          "DW_OP_call4",
-          &DwarfOp::op_not_implemented,
-          0,
-          1,
-          {DW_EH_PE_udata4},
-      },
-      {
-          // 0x9a DW_OP_call_ref
-          "DW_OP_call_ref",
-          &DwarfOp::op_not_implemented,
-          0,
-          0,  // Has a different sized operand (4 bytes or 8 bytes).
-          {},
-      },
-      {
-          // 0x9b DW_OP_form_tls_address
-          "DW_OP_form_tls_address",
-          &DwarfOp::op_not_implemented,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x9c DW_OP_call_frame_cfa
-          "DW_OP_call_frame_cfa",
-          &DwarfOp::op_not_implemented,
-          0,
-          0,
-          {},
-      },
-      {
-          // 0x9d DW_OP_bit_piece
-          "DW_OP_bit_piece",
-          &DwarfOp::op_not_implemented,
-          0,
-          2,
-          {DW_EH_PE_uleb128, DW_EH_PE_uleb128},
-      },
-      {
-          // 0x9e DW_OP_implicit_value
-          "DW_OP_implicit_value",
-          &DwarfOp::op_not_implemented,
-          0,
-          1,
-          {DW_EH_PE_uleb128},
-      },
-      {
-          // 0x9f DW_OP_stack_value
-          "DW_OP_stack_value",
-          &DwarfOp::op_not_implemented,
-          1,
-          0,
-          {},
-      },
-      {nullptr, nullptr, 0, 0, {}},  // 0xa0 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xa1 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xa2 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xa3 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xa4 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xa5 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xa6 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xa7 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xa8 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xa9 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xaa illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xab illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xac illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xad illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xae illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xaf illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xb0 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xb1 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xb2 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xb3 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xb4 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xb5 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xb6 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xb7 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xb8 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xb9 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xba illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xbb illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xbc illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xbd illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xbe illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xbf illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xc0 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xc1 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xc2 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xc3 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xc4 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xc5 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xc6 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xc7 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xc8 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xc9 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xca illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xcb illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xcc illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xcd illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xce illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xcf illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xd0 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xd1 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xd2 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xd3 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xd4 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xd5 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xd6 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xd7 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xd8 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xd9 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xda illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xdb illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xdc illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xdd illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xde illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xdf illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xe0 DW_OP_lo_user
-      {nullptr, nullptr, 0, 0, {}},  // 0xe1 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xe2 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xe3 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xe4 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xe5 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xe6 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xe7 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xe8 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xe9 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xea illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xeb illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xec illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xed illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xee illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xef illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xf0 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xf1 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xf2 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xf3 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xf4 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xf5 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xf6 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xf7 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xf8 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xf9 illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xfa illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xfb illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xfc illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xfd illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xfe illegal op
-      {nullptr, nullptr, 0, 0, {}},  // 0xff DW_OP_hi_user
-  };
+  using OpHandleFuncPtr = bool (DwarfOp::*)();
+  static const OpHandleFuncPtr kOpHandleFuncList[];
 };
 
 }  // namespace unwindstack
diff --git a/libunwindstack/Unwinder.cpp b/libunwindstack/Unwinder.cpp
index 0dd95cf..2734cf8 100644
--- a/libunwindstack/Unwinder.cpp
+++ b/libunwindstack/Unwinder.cpp
@@ -247,7 +247,7 @@
         // or the pc in the first frame is in a valid map.
         // This allows for a case where the code jumps into the middle of
         // nowhere, but there is no other unwind information after that.
-        if (frames_.size() != 2 || maps_->Find(frames_[0].pc) != nullptr) {
+        if (frames_.size() > 2 || (frames_.size() > 0 && maps_->Find(frames_[0].pc) != nullptr)) {
           // Remove the speculative frame.
           frames_.pop_back();
         }
diff --git a/libunwindstack/include/unwindstack/DexFiles.h b/libunwindstack/include/unwindstack/DexFiles.h
index 0336173..67a9640 100644
--- a/libunwindstack/include/unwindstack/DexFiles.h
+++ b/libunwindstack/include/unwindstack/DexFiles.h
@@ -40,7 +40,7 @@
  public:
   explicit DexFiles(std::shared_ptr<Memory>& memory);
   DexFiles(std::shared_ptr<Memory>& memory, std::vector<std::string>& search_libs);
-  ~DexFiles();
+  virtual ~DexFiles();
 
   DexFile* GetDexFile(uint64_t dex_file_offset, MapInfo* info);
 
diff --git a/libunwindstack/include/unwindstack/JitDebug.h b/libunwindstack/include/unwindstack/JitDebug.h
index f64b04f..8b7b4b5 100644
--- a/libunwindstack/include/unwindstack/JitDebug.h
+++ b/libunwindstack/include/unwindstack/JitDebug.h
@@ -38,7 +38,7 @@
  public:
   explicit JitDebug(std::shared_ptr<Memory>& memory);
   JitDebug(std::shared_ptr<Memory>& memory, std::vector<std::string>& search_libs);
-  ~JitDebug();
+  virtual ~JitDebug();
 
   Elf* GetElf(Maps* maps, uint64_t pc);
 
diff --git a/libunwindstack/include/unwindstack/Unwinder.h b/libunwindstack/include/unwindstack/Unwinder.h
index ab239c1..f4788d7 100644
--- a/libunwindstack/include/unwindstack/Unwinder.h
+++ b/libunwindstack/include/unwindstack/Unwinder.h
@@ -72,7 +72,7 @@
     frames_.reserve(max_frames);
   }
 
-  ~Unwinder() = default;
+  virtual ~Unwinder() = default;
 
   void Unwind(const std::vector<std::string>* initial_map_names_to_skip = nullptr,
               const std::vector<std::string>* map_suffixes_to_ignore = nullptr);
@@ -124,7 +124,7 @@
 class UnwinderFromPid : public Unwinder {
  public:
   UnwinderFromPid(size_t max_frames, pid_t pid) : Unwinder(max_frames), pid_(pid) {}
-  ~UnwinderFromPid() = default;
+  virtual ~UnwinderFromPid() = default;
 
   bool Init(ArchEnum arch);
 
diff --git a/libunwindstack/tests/UnwinderTest.cpp b/libunwindstack/tests/UnwinderTest.cpp
index 49aeeb3..d88531f 100644
--- a/libunwindstack/tests/UnwinderTest.cpp
+++ b/libunwindstack/tests/UnwinderTest.cpp
@@ -749,6 +749,23 @@
   EXPECT_EQ(PROT_READ | PROT_WRITE, frame->map_flags);
 }
 
+// Verify that a speculative frame does not cause a crash when it wasn't
+// really added due to a filter.
+TEST_F(UnwinderTest, speculative_frame_check_with_no_frames) {
+  regs_.set_pc(0x23000);
+  regs_.set_sp(0x10000);
+  regs_.FakeSetReturnAddress(0x23100);
+  regs_.FakeSetReturnAddressValid(true);
+
+  Unwinder unwinder(64, maps_.get(), &regs_, process_memory_);
+
+  std::vector<std::string> skip_names{"libanother.so"};
+  unwinder.Unwind(&skip_names);
+  EXPECT_EQ(ERROR_NONE, unwinder.LastErrorCode());
+
+  ASSERT_EQ(0U, unwinder.NumFrames());
+}
+
 // Verify that an unwind stops when a frame is in given suffix.
 TEST_F(UnwinderTest, map_ignore_suffixes) {
   ElfInterfaceFake::FakePushFunctionData(FunctionData("Frame0", 0));
diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp
index 5f0a51f..24a745a 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -275,25 +275,6 @@
   return ss-s;
 }
 
-
-char16_t *strncpy16(char16_t *dst, const char16_t *src, size_t n)
-{
-  char16_t *q = dst;
-  const char16_t *p = src;
-  char ch;
-
-  while (n) {
-    n--;
-    *q++ = ch = *p++;
-    if ( !ch )
-      break;
-  }
-
-  *q = 0;
-
-  return dst;
-}
-
 size_t strnlen16(const char16_t *s, size_t maxlen)
 {
   const char16_t *ss = s;
diff --git a/libutils/include/utils/Unicode.h b/libutils/include/utils/Unicode.h
index 61a1b4f..a2aaa47 100644
--- a/libutils/include/utils/Unicode.h
+++ b/libutils/include/utils/Unicode.h
@@ -28,7 +28,6 @@
 size_t strlen16(const char16_t *);
 size_t strnlen16(const char16_t *, size_t);
 char16_t *strcpy16(char16_t *, const char16_t *);
-char16_t *strncpy16(char16_t *, const char16_t *, size_t);
 char16_t *strstr16(const char16_t*, const char16_t*);
 
 // Version of comparison that supports embedded NULs.
diff --git a/logd/Android.bp b/logd/Android.bp
index bdbdf12..360f2fe 100644
--- a/logd/Android.bp
+++ b/logd/Android.bp
@@ -79,3 +79,10 @@
 
     cflags: ["-Werror"],
 }
+
+
+prebuilt_etc {
+    name: "logtagd.rc",
+    src: "logtagd.rc",
+    sub_dir: "init",
+}
diff --git a/logd/Android.mk b/logd/Android.mk
deleted file mode 100644
index aafa28d..0000000
--- a/logd/Android.mk
+++ /dev/null
@@ -1,10 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := logtagd.rc
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/init
-
-include $(BUILD_PREBUILT)
diff --git a/logd/LogAudit.cpp b/logd/LogAudit.cpp
index 470ffed..a21555c 100644
--- a/logd/LogAudit.cpp
+++ b/logd/LogAudit.cpp
@@ -111,7 +111,7 @@
 }
 
 std::map<std::string, std::string> LogAudit::populateDenialMap() {
-    std::ifstream bug_file("/system/etc/selinux/selinux_denial_metadata");
+    std::ifstream bug_file("/vendor/etc/selinux/selinux_denial_metadata");
     std::string line;
     // allocate a map for the static map pointer in auditParse to keep track of,
     // this function only runs once
diff --git a/mkbootimg/include/bootimg/bootimg.h b/mkbootimg/include/bootimg/bootimg.h
index 4432f9e..9ee7869 100644
--- a/mkbootimg/include/bootimg/bootimg.h
+++ b/mkbootimg/include/bootimg/bootimg.h
@@ -115,7 +115,7 @@
     uint32_t header_size;
 } __attribute__((packed));
 
-/* When the boot image header has a version of 1, the structure of the boot
+/* When the boot image header has a version of 2, the structure of the boot
  * image is as follows:
  *
  * +---------------------+
@@ -129,17 +129,21 @@
  * +---------------------+
  * | recovery dtbo/acpio | p pages
  * +---------------------+
+ * | dtb                 | q pages
+ * +---------------------+
+
  * n = (kernel_size + page_size - 1) / page_size
  * m = (ramdisk_size + page_size - 1) / page_size
  * o = (second_size + page_size - 1) / page_size
  * p = (recovery_dtbo_size + page_size - 1) / page_size
+ * q = (dtb_size + page_size - 1) / page_size
  *
  * 0. all entities are page_size aligned in flash
- * 1. kernel and ramdisk are required (size != 0)
+ * 1. kernel, ramdisk and DTB are required (size != 0)
  * 2. recovery_dtbo/recovery_acpio is required for recovery.img in non-A/B
  *    devices(recovery_dtbo_size != 0)
  * 3. second is optional (second_size == 0 -> no second)
- * 4. load each element (kernel, ramdisk, second) at
+ * 4. load each element (kernel, ramdisk, second, dtb) at
  *    the specified physical address (kernel_addr, etc)
  * 5. If booting to recovery mode in a non-A/B device, extract recovery
  *    dtbo/acpio and apply the correct set of overlays on the base device tree
@@ -150,3 +154,7 @@
  * 8. if second_size != 0: jump to second_addr
  *    else: jump to kernel_addr
  */
+struct boot_img_hdr_v2 : public boot_img_hdr_v1 {
+    uint32_t dtb_size; /* size in bytes for DTB image */
+    uint64_t dtb_addr; /* physical load address for DTB image */
+} __attribute__((packed));
diff --git a/mkbootimg/mkbootimg.py b/mkbootimg/mkbootimg.py
index 2eb2bab..92b11a5 100644
--- a/mkbootimg/mkbootimg.py
+++ b/mkbootimg/mkbootimg.py
@@ -62,7 +62,13 @@
 
 
 def write_header(args):
+    BOOT_IMAGE_HEADER_V1_SIZE = 1648
+    BOOT_IMAGE_HEADER_V2_SIZE = 1660
     BOOT_MAGIC = 'ANDROID!'.encode()
+
+    if (args.header_version > 2):
+        raise ValueError('Boot header version %d not supported' % args.header_version)
+
     args.output.write(pack('8s', BOOT_MAGIC))
     args.output.write(pack('10I',
         filesize(args.kernel),                          # size in bytes
@@ -85,6 +91,8 @@
 
     if args.header_version > 0:
         update_sha(sha, args.recovery_dtbo)
+    if args.header_version > 1:
+        update_sha(sha, args.dtb)
 
     img_id = pack('32s', sha.digest())
 
@@ -97,8 +105,16 @@
             args.output.write(pack('Q', get_recovery_dtbo_offset(args))) # recovery dtbo offset
         else:
             args.output.write(pack('Q', 0)) # Will be set to 0 for devices without a recovery dtbo
-        args.output.write(pack('I', args.output.tell() + 4))         # size of boot header
 
+    # Populate boot image header size for header versions 1 and 2.
+    if args.header_version == 1:
+        args.output.write(pack('I', BOOT_IMAGE_HEADER_V1_SIZE))
+    elif args.header_version == 2:
+        args.output.write(pack('I', BOOT_IMAGE_HEADER_V2_SIZE))
+
+    if args.header_version > 1:
+        args.output.write(pack('I', filesize(args.dtb)))   # size in bytes
+        args.output.write(pack('Q', args.base + args.dtb_offset)) # dtb physical load address
     pad_file(args.output, args.pagesize)
     return img_id
 
@@ -161,6 +177,7 @@
                         required=True)
     parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb'))
     parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb'))
+    parser.add_argument('--dtb', help='path to dtb', type=FileType('rb'))
     recovery_dtbo_group = parser.add_mutually_exclusive_group()
     recovery_dtbo_group.add_argument('--recovery_dtbo', help='path to the recovery DTBO', type=FileType('rb'))
     recovery_dtbo_group.add_argument('--recovery_acpio', help='path to the recovery ACPIO',
@@ -172,6 +189,8 @@
     parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000)
     parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,
                         default=0x00f00000)
+    parser.add_argument('--dtb_offset', help='dtb offset', type=parse_int, default=0x01f00000)
+
     parser.add_argument('--os_version', help='operating system version', type=parse_os_version,
                         default=0)
     parser.add_argument('--os_patch_level', help='operating system patch level',
@@ -196,6 +215,8 @@
 
     if args.header_version > 0:
         write_padded_file(args.output, args.recovery_dtbo, args.pagesize)
+    if args.header_version > 1:
+        write_padded_file(args.output, args.dtb, args.pagesize)
 
 def main():
     args = parse_cmdline()
diff --git a/mkbootimg/unpack_bootimg.py b/mkbootimg/unpack_bootimg.py
old mode 100644
new mode 100755
index c37acd5..6b5d5d0
--- a/mkbootimg/unpack_bootimg.py
+++ b/mkbootimg/unpack_bootimg.py
@@ -15,7 +15,7 @@
 
 """unpacks the bootimage.
 
-Extracts the kernel, ramdisk, second bootloader and recovery dtbo images.
+Extracts the kernel, ramdisk, second bootloader, dtb and recovery dtbo images.
 """
 
 from __future__ import print_function
@@ -82,6 +82,14 @@
         print('boot header size: %s' % boot_header_size)
     else:
         recovery_dtbo_size = 0
+    if version > 1:
+        dtb_size = unpack('I', args.boot_img.read(4))[0]
+        print('dtb size: %s' % dtb_size)
+        dtb_load_address = unpack('Q', args.boot_img.read(8))[0]
+        print('dtb address: %s' % dtb_load_address)
+    else:
+        dtb_size = 0
+
 
     # The first page contains the boot header
     num_header_pages = 1
@@ -103,6 +111,15 @@
     if recovery_dtbo_size > 0:
         image_info_list.append((recovery_dtbo_offset, recovery_dtbo_size,
                                 'recovery_dtbo'))
+    if dtb_size > 0:
+        num_second_pages = get_number_of_pages(second_size, page_size)
+        num_recovery_dtbo_pages = get_number_of_pages(recovery_dtbo_size, page_size)
+        dtb_offset = page_size * (
+            num_header_pages + num_kernel_pages + num_ramdisk_pages + num_second_pages +
+            num_recovery_dtbo_pages
+        )
+
+        image_info_list.append((dtb_offset, dtb_size, 'dtb'))
 
     for image_info in image_info_list:
         extract_image(image_info[0], image_info[1], args.boot_img,
@@ -113,7 +130,7 @@
     """parse command line arguments"""
     parser = ArgumentParser(
         description='Unpacks boot.img/recovery.img, extracts the kernel,'
-        'ramdisk, second bootloader and recovery dtbo')
+        'ramdisk, second bootloader, recovery dtbo and dtb')
     parser.add_argument(
         '--boot_img',
         help='path to boot image',
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 85c7e9e..5a6f41b 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -24,6 +24,26 @@
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/init
 
+# Start of runtime APEX compatibility.
+#
+# Meta-comment:
+# The placing of this section is somewhat arbitrary. The LOCAL_POST_INSTALL_CMD
+# entries need to be associated with something that goes into /system.
+# init-debug.rc qualifies but it could be anything else in /system until soong
+# supports creation of symlinks. http://b/123333111
+#
+# Keeping the appearance of files/dirs having old locations for apps that have
+# come to rely on them.
+
+# http://b/121248172 - create a link from /system/usr/icu to
+# /apex/com.android.runtime/etc/icu so that apps can find the ICU .dat file.
+# A symlink can't overwrite a directory and the /system/usr/icu directory once
+# existed so the required structure must be created whatever we find.
+LOCAL_POST_INSTALL_CMD = mkdir -p $(TARGET_OUT)/usr && rm -rf $(TARGET_OUT)/usr/icu
+LOCAL_POST_INSTALL_CMD += ; ln -sf /apex/com.android.runtime/etc/icu $(TARGET_OUT)/usr/icu
+
+# End of runtime APEX compatibilty.
+
 include $(BUILD_PREBUILT)
 
 #######################################
@@ -253,14 +273,11 @@
 include $(LOCAL_PATH)/update_and_install_ld_config.mk
 endef
 
-# For VNDK snapshot versions prior to 28, ld.config.txt is installed from the
-# prebuilt under /prebuilts/vndk
 vndk_snapshots := $(wildcard prebuilts/vndk/*)
 supported_vndk_snapshot_versions := \
-  $(strip $(foreach ver,$(patsubst prebuilts/vndk/v%,%,$(vndk_snapshots)),\
-    $(if $(call math_gt_or_eq,$(ver),28),$(ver),)))
-$(eval $(foreach ver,$(supported_vndk_snapshot_versions),\
-  $(call build_versioned_ld_config,$(ver))))
+  $(strip $(patsubst prebuilts/vndk/v%,%,$(vndk_snapshots)))
+$(foreach ver,$(supported_vndk_snapshot_versions),\
+  $(eval $(call build_versioned_ld_config,$(ver))))
 
 vndk_snapshots :=
 supported_vndk_snapshot_versions :=
diff --git a/rootdir/etc/ld.config.legacy.txt b/rootdir/etc/ld.config.legacy.txt
index 461184a..3356d4e 100644
--- a/rootdir/etc/ld.config.legacy.txt
+++ b/rootdir/etc/ld.config.legacy.txt
@@ -32,6 +32,74 @@
 namespace.default.asan.search.paths +=           /odm/${LIB}
 
 ###############################################################################
+# APEX related namespaces.
+###############################################################################
+
+additional.namespaces = runtime,conscrypt,media
+namespace.default.asan.permitted.paths += /apex/com.android.resolv/${LIB}
+
+# Keep in sync with ld.config.txt in the com.android.runtime APEX.
+namespace.default.links = runtime
+namespace.default.asan.links = runtime
+# Visible because some libraries are dlopen'ed, e.g. libopenjdk is dlopen'ed by
+# libart.
+namespace.default.visible = true
+namespace.default.link.runtime.shared_libs  = libart.so:libartd.so
+namespace.default.link.runtime.shared_libs += libdexfile_external.so
+namespace.default.link.runtime.shared_libs += libnativebridge.so
+namespace.default.link.runtime.shared_libs += libnativehelper.so
+namespace.default.link.runtime.shared_libs += libnativeloader.so
+
+###############################################################################
+# "runtime" APEX namespace
+#
+# This namespace exposes externally accessible libraries from the Runtime APEX.
+###############################################################################
+namespace.runtime.isolated = true
+
+# Keep in sync with ld.config.txt in the com.android.runtime APEX.
+namespace.runtime.search.paths = /apex/com.android.runtime/${LIB}
+namespace.runtime.asan.search.paths = /apex/com.android.runtime/${LIB}
+namespace.runtime.links = default
+# TODO(b/119867084): Restrict to Bionic dlopen dependencies and PALette library
+# when it exists.
+namespace.runtime.link.default.allow_all_shared_libs = true
+
+###############################################################################
+# "media" APEX namespace
+#
+# This namespace is for libraries within the media APEX.
+###############################################################################
+namespace.media.isolated = true
+namespace.media.visible = true
+
+namespace.media.search.paths = /apex/com.android.media/${LIB}
+namespace.media.asan.search.paths = /apex/com.android.media/${LIB}
+
+namespace.media.links = default
+namespace.media.link.default.shared_libs  = %LLNDK_LIBRARIES%
+namespace.media.link.default.shared_libs += libandroid.so
+namespace.media.link.default.shared_libs += libbinder_ndk.so
+namespace.media.link.default.shared_libs += libmediametrics.so
+namespace.media.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
+
+###############################################################################
+# "conscrypt" APEX namespace
+#
+# This namespace is for libraries within the conscrypt APEX.
+###############################################################################
+namespace.conscrypt.isolated = true
+namespace.conscrypt.visible = true
+
+# Keep in sync with ld.config.txt in the com.android.runtime APEX.
+namespace.conscrypt.search.paths = /apex/com.android.conscrypt/${LIB}
+namespace.conscrypt.asan.search.paths = /apex/com.android.conscrypt/${LIB}
+namespace.conscrypt.links = default
+namespace.conscrypt.link.default.shared_libs  = libc.so
+namespace.conscrypt.link.default.shared_libs += libm.so
+namespace.conscrypt.link.default.shared_libs += libdl.so
+
+###############################################################################
 # Namespace config for binaries under /postinstall.
 # Only one default namespace is defined and it has no directories other than
 # /system/lib and /product/lib in the search paths. This is because linker
diff --git a/rootdir/etc/ld.config.txt b/rootdir/etc/ld.config.txt
index 1d4b1e2..05f75bf 100644
--- a/rootdir/etc/ld.config.txt
+++ b/rootdir/etc/ld.config.txt
@@ -127,6 +127,7 @@
 
 # Keep in sync with ld.config.txt in the com.android.runtime APEX.
 namespace.runtime.search.paths = /apex/com.android.runtime/${LIB}
+namespace.runtime.asan.search.paths = /apex/com.android.runtime/${LIB}
 namespace.runtime.links = default
 # TODO(b/119867084): Restrict to Bionic dlopen dependencies and PALette library
 # when it exists.
@@ -141,11 +142,13 @@
 namespace.media.visible = true
 
 namespace.media.search.paths = /apex/com.android.media/${LIB}
+namespace.media.asan.search.paths = /apex/com.android.media/${LIB}
 
 namespace.media.links = default
 namespace.media.link.default.shared_libs  = %LLNDK_LIBRARIES%
 namespace.media.link.default.shared_libs += libandroid.so
 namespace.media.link.default.shared_libs += libbinder_ndk.so
+namespace.media.link.default.shared_libs += libmediametrics.so
 namespace.media.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
 
 ###############################################################################
@@ -158,6 +161,7 @@
 
 # Keep in sync with ld.config.txt in the com.android.runtime APEX.
 namespace.conscrypt.search.paths = /apex/com.android.conscrypt/${LIB}
+namespace.conscrypt.asan.search.paths = /apex/com.android.conscrypt/${LIB}
 namespace.conscrypt.links = default
 namespace.conscrypt.link.default.shared_libs  = libc.so
 namespace.conscrypt.link.default.shared_libs += libm.so
@@ -326,6 +330,9 @@
 # partition (VNDK and LLNDK libraries) are not loaded here but from the
 # separate namespace 'system'. The delegation to the system namespace is done
 # via the 'namespace.default.link.system.shared_libs' property below.
+#
+# '#VNDK27#' TAG is only for building ld.config.27.txt for backward
+# compatibility. (TODO:b/123390078) Move them to a separate file.
 ###############################################################################
 namespace.default.isolated = true
 namespace.default.visible = true
@@ -335,11 +342,17 @@
 
 namespace.default.permitted.paths  = /odm
 namespace.default.permitted.paths += /vendor
+#VNDK27#namespace.default.search.paths += /vendor/${LIB}/hw
+#VNDK27#namespace.default.search.paths += /vendor/${LIB}/egl
 
 namespace.default.asan.search.paths  = /data/asan/odm/${LIB}
 namespace.default.asan.search.paths +=           /odm/${LIB}
 namespace.default.asan.search.paths += /data/asan/vendor/${LIB}
 namespace.default.asan.search.paths +=           /vendor/${LIB}
+#VNDK27#namespace.default.asan.search.paths += /data/asan/vendor/${LIB}/hw
+#VNDK27#namespace.default.asan.search.paths +=           /vendor/${LIB}/hw
+#VNDK27#namespace.default.asan.search.paths += /data/asan/vendor/${LIB}/egl
+#VNDK27#namespace.default.asan.search.paths +=           /vendor/${LIB}/egl
 
 namespace.default.asan.permitted.paths  = /data/asan/odm
 namespace.default.asan.permitted.paths +=           /odm
diff --git a/rootdir/etc/ld.config.vndk_lite.txt b/rootdir/etc/ld.config.vndk_lite.txt
index d54156b..335369e 100644
--- a/rootdir/etc/ld.config.vndk_lite.txt
+++ b/rootdir/etc/ld.config.vndk_lite.txt
@@ -76,6 +76,7 @@
 # Keep in sync with the default namespace in the com.android.runtime APEX
 # ld.config.txt.
 namespace.runtime.search.paths = /apex/com.android.runtime/${LIB}
+namespace.runtime.asan.search.paths = /apex/com.android.runtime/${LIB}
 namespace.runtime.links = default
 # TODO(b/119867084): Restrict to Bionic dlopen dependencies and PALette library
 # when it exists.
@@ -90,11 +91,13 @@
 namespace.media.visible = true
 
 namespace.media.search.paths = /apex/com.android.media/${LIB}
+namespace.media.asan.search.paths = /apex/com.android.media/${LIB}
 
 namespace.media.links = default
 namespace.media.link.default.shared_libs  = %LLNDK_LIBRARIES%
 namespace.media.link.default.shared_libs += libandroid.so
 namespace.media.link.default.shared_libs += libbinder_ndk.so
+namespace.media.link.default.shared_libs += libmediametrics.so
 namespace.media.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
 
 ###############################################################################
@@ -107,6 +110,7 @@
 
 # Keep in sync with ld.config.txt in the com.android.runtime APEX.
 namespace.conscrypt.search.paths = /apex/com.android.conscrypt/${LIB}
+namespace.conscrypt.asan.search.paths = /apex/com.android.conscrypt/${LIB}
 namespace.conscrypt.links = default
 namespace.conscrypt.link.default.shared_libs  = libc.so
 namespace.conscrypt.link.default.shared_libs += libm.so
diff --git a/rootdir/init.zygote32.rc b/rootdir/init.zygote32.rc
index 2e95687..e8c5d8e 100644
--- a/rootdir/init.zygote32.rc
+++ b/rootdir/init.zygote32.rc
@@ -4,6 +4,7 @@
     user root
     group root readproc reserved_disk
     socket zygote stream 660 root system
+    socket blastula_pool stream 660 root system
     updatable
     onrestart write /sys/android_power/request_state wake
     onrestart write /sys/power/state on
diff --git a/rootdir/init.zygote32_64.rc b/rootdir/init.zygote32_64.rc
index 1cfc3d6..9c7e807 100644
--- a/rootdir/init.zygote32_64.rc
+++ b/rootdir/init.zygote32_64.rc
@@ -4,6 +4,7 @@
     user root
     group root readproc reserved_disk
     socket zygote stream 660 root system
+    socket blastula_pool stream 660 root system
     updatable
     onrestart write /sys/android_power/request_state wake
     onrestart write /sys/power/state on
@@ -20,6 +21,7 @@
     user root
     group root readproc reserved_disk
     socket zygote_secondary stream 660 root system
+    socket blastula_pool_secondary stream 660 root system
     updatable
     onrestart restart zygote
     writepid /dev/cpuset/foreground/tasks
diff --git a/rootdir/init.zygote64.rc b/rootdir/init.zygote64.rc
index 8ab012d..9908c99 100644
--- a/rootdir/init.zygote64.rc
+++ b/rootdir/init.zygote64.rc
@@ -4,6 +4,7 @@
     user root
     group root readproc reserved_disk
     socket zygote stream 660 root system
+    socket blastula_pool stream 660 root system
     updatable
     onrestart write /sys/android_power/request_state wake
     onrestart write /sys/power/state on
diff --git a/rootdir/init.zygote64_32.rc b/rootdir/init.zygote64_32.rc
index 5abf149..0b5edff 100644
--- a/rootdir/init.zygote64_32.rc
+++ b/rootdir/init.zygote64_32.rc
@@ -4,6 +4,7 @@
     user root
     group root readproc reserved_disk
     socket zygote stream 660 root system
+    socket blastula_pool stream 660 root system
     updatable
     onrestart write /sys/android_power/request_state wake
     onrestart write /sys/power/state on
@@ -20,6 +21,7 @@
     user root
     group root readproc reserved_disk
     socket zygote_secondary stream 660 root system
+    socket blastula_pool_secondary stream 660 root system
     updatable
     onrestart restart zygote
     writepid /dev/cpuset/foreground/tasks
diff --git a/rootdir/update_and_install_ld_config.mk b/rootdir/update_and_install_ld_config.mk
index 79bed7b..450be66 100644
--- a/rootdir/update_and_install_ld_config.mk
+++ b/rootdir/update_and_install_ld_config.mk
@@ -88,6 +88,7 @@
 $(LOCAL_BUILT_MODULE): PRIVATE_VNDK_VERSION_SUFFIX := $(vndk_version_suffix)
 $(LOCAL_BUILT_MODULE): PRIVATE_INTERMEDIATES_DIR := $(intermediates_dir)
 $(LOCAL_BUILT_MODULE): PRIVATE_COMP_CHECK_SCRIPT := $(compatibility_check_script)
+$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_VERSION_TAG := \#VNDK$(vndk_version)\#
 deps := $(llndk_libraries_file) $(vndksp_libraries_file) $(vndkcore_libraries_file) \
   $(vndkprivate_libraries_file)
 ifeq ($(check_backward_compatibility),true)
@@ -114,10 +115,12 @@
 	paste -sd ":" $(PRIVATE_INTERMEDIATES_DIR)/private_llndk | \
 	sed -i.bak -e "s?%PRIVATE_LLNDK_LIBRARIES%?$$(cat -)?g" $@
 
-	$(hide) sed -i.bak -e 's?%SANITIZER_RUNTIME_LIBRARIES%?$(PRIVATE_SANITIZER_RUNTIME_LIBRARIES)?g' $@
-	$(hide) sed -i.bak -e 's?%VNDK_VER%?$(PRIVATE_VNDK_VERSION_SUFFIX)?g' $@
-	$(hide) sed -i.bak -e 's?%PRODUCT%?$(TARGET_COPY_OUT_PRODUCT)?g' $@
-	$(hide) sed -i.bak -e 's?%PRODUCT_SERVICES%?$(TARGET_COPY_OUT_PRODUCT_SERVICES)?g' $@
+	$(hide) sed -i.bak -e "s?%SANITIZER_RUNTIME_LIBRARIES%?$(PRIVATE_SANITIZER_RUNTIME_LIBRARIES)?g" $@
+	$(hide) sed -i.bak -e "s?%VNDK_VER%?$(PRIVATE_VNDK_VERSION_SUFFIX)?g" $@
+	$(hide) sed -i.bak -e "s?%PRODUCT%?$(TARGET_COPY_OUT_PRODUCT)?g" $@
+	$(hide) sed -i.bak -e "s?%PRODUCT_SERVICES%?$(TARGET_COPY_OUT_PRODUCT_SERVICES)?g" $@
+	$(hide) sed -i.bak -e "s?^$(PRIVATE_VNDK_VERSION_TAG)??g" $@
+	$(hide) sed -i.bak "/^\#VNDK[0-9]\{2\}\#.*$$/d" $@
 	$(hide) rm -f $@.bak
 
 ld_config_template :=
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index cb5c5cb..5ce43d5 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -19,6 +19,7 @@
         "reboot",
         "sh",
         "simpleperf",
+        "simpleperf_app_runner",
         "tcpdump",
         "toolbox",
         "toybox",