Merge "debuggerd: add support for Java traces."
diff --git a/CleanSpec.mk b/CleanSpec.mk
index e6f8716..0e43dae 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -77,3 +77,4 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/ld.config.txt)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/llndk.libraries.txt)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/vndksp.libraries.txt)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/)
diff --git a/adb/Android.bp b/adb/Android.bp
index 553473f..53bf7e3 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -302,8 +302,6 @@
     name: "adbd",
     defaults: ["adb_defaults"],
 
-    // adbd must be static, as it is copied into the recovery image.
-    static_executable: true,
     recovery_available: true,
 
     srcs: [
@@ -344,7 +342,6 @@
         "libselinux",
         "libsquashfs_utils",
         "libqemu_pipe",
-        "libdebuggerd_handler",
 
         "libbase",
         "libcutils",
@@ -371,6 +368,7 @@
         "liblog",
         "libusb",
         "libmdnssd",
+        "libselinux",
     ],
     test_suites: ["device-tests"],
 }
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index 39983ab..06fcf16 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -1136,8 +1136,7 @@
         cmd += " " + escape_arg(*argv++);
     }
 
-    // No need for shell protocol with logcat, always disable for simplicity.
-    return send_shell_command(cmd, true);
+    return send_shell_command(cmd);
 }
 
 static void write_zeros(int bytes, int fd) {
@@ -1861,7 +1860,7 @@
         cmd += " " + escape_arg(*argv++);
     }
 
-    return send_shell_command(cmd, false);
+    return send_shell_command(cmd);
 }
 
 static int install_app(int argc, const char** argv) {
@@ -2049,7 +2048,7 @@
         cmd += " " + escape_arg(*argv++);
     }
 
-    return send_shell_command(cmd, false);
+    return send_shell_command(cmd);
 }
 
 static int uninstall_app_legacy(int argc, const char** argv) {
@@ -2073,7 +2072,7 @@
 
 static int delete_file(const std::string& filename) {
     std::string cmd = "rm -f " + escape_arg(filename);
-    return send_shell_command(cmd, false);
+    return send_shell_command(cmd);
 }
 
 static int install_app_legacy(int argc, const char** argv) {
diff --git a/adb/client/commandline.h b/adb/client/commandline.h
index 36cd798..3d10030 100644
--- a/adb/client/commandline.h
+++ b/adb/client/commandline.h
@@ -92,7 +92,7 @@
 // resulting output.
 // if |callback| is non-null, stdout/stderr output will be handled by it.
 int send_shell_command(
-    const std::string& command, bool disable_shell_protocol,
-    StandardStreamsCallbackInterface* callback = &DEFAULT_STANDARD_STREAMS_CALLBACK);
+        const std::string& command, bool disable_shell_protocol = false,
+        StandardStreamsCallbackInterface* callback = &DEFAULT_STANDARD_STREAMS_CALLBACK);
 
 #endif  // COMMANDLINE_H
diff --git a/adb/daemon/main.cpp b/adb/daemon/main.cpp
index 232d9c5..c02cafa 100644
--- a/adb/daemon/main.cpp
+++ b/adb/daemon/main.cpp
@@ -38,7 +38,6 @@
 #include <scoped_minijail.h>
 
 #include <private/android_filesystem_config.h>
-#include "debuggerd/handler.h"
 #include "selinux/android.h"
 
 #include "adb.h"
@@ -274,7 +273,6 @@
 
     close_stdin();
 
-    debuggerd_init(nullptr);
     adb_trace_init(argv);
 
     D("Handling main()");
diff --git a/adb/daemon/shell_service.cpp b/adb/daemon/shell_service.cpp
index 401c99c..56d647a 100644
--- a/adb/daemon/shell_service.cpp
+++ b/adb/daemon/shell_service.cpp
@@ -98,6 +98,7 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <private/android_logger.h>
+#include <selinux/android.h>
 
 #include "adb.h"
 #include "adb_io.h"
@@ -343,6 +344,24 @@
                 adb_write(oom_score_adj_fd, oom_score_adj_value, strlen(oom_score_adj_value)));
         }
 
+#ifdef __ANDROID_RECOVERY__
+        // Special routine for recovery. Switch to shell domain when adbd is
+        // is running with dropped privileged (i.e. not running as root) and
+        // is built for the recovery mode. This is required because recovery
+        // rootfs is not labeled and everything is labeled just as rootfs.
+        char* con = nullptr;
+        if (getcon(&con) == 0) {
+            if (!strcmp(con, "u:r:adbd:s0")) {
+                if (selinux_android_setcon("u:r:shell:s0") < 0) {
+                    LOG(FATAL) << "Could not set SELinux context for subprocess";
+                }
+            }
+            freecon(con);
+        } else {
+            LOG(FATAL) << "Failed to get SELinux context";
+        }
+#endif
+
         if (command_.empty()) {
             // Spawn a login shell if we don't have a command.
             execle(_PATH_BSHELL, "-" _PATH_BSHELL, nullptr, cenv.data());
diff --git a/bootstat/boot_reason_test.sh b/bootstat/boot_reason_test.sh
index 8ed92a6..ddd1caf 100755
--- a/bootstat/boot_reason_test.sh
+++ b/bootstat/boot_reason_test.sh
@@ -480,23 +480,25 @@
 - (wait until screen is up, boot has completed)
 - adb shell getprop ro.boot.bootreason (bootloader reason)
 - adb shell getprop persist.sys.boot.reason (last reason)
+- adb shell getprop sys.boot.reason.last (last last reason)
 - adb shell getprop sys.boot.reason (system reason)
 - NB: all should have a value that is compliant with our known set." ]
 test_properties() {
   duration_test 1
   wait_for_screen
   retval=0
-  check_set="ro.boot.bootreason persist.sys.boot.reason sys.boot.reason"
+  check_set="ro.boot.bootreason sys.boot.reason.last sys.boot.reason"
   bootloader=""
   # NB: this test could fail if performed _after_ optional_factory_reset test
   # and will report
   #  ERROR: expected "reboot" got ""
-  #        for Android property persist.sys.boot.reason
+  #        for Android property sys.boot.reason.last
   # following is mitigation for the persist.sys.boot.reason, skip it
   if [ "reboot,factory_reset" = "`validate_property ro.boot_bootreason`" ]; then
     check_set="ro.boot.bootreason sys.boot.reason"
     bootloader="bootloader"
   fi
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   for prop in ${check_set}; do
     reason=`validate_property ${prop}`
     EXPECT_PROPERTY ${prop} ${reason} || retval=${?}
@@ -554,7 +556,8 @@
   popd >&2
   wait_for_screen
   EXPECT_PROPERTY sys.boot.reason "\(reboot,ota\|bootloader\)"
-  EXPECT_PROPERTY persist.sys.boot.reason bootloader
+  EXPECT_PROPERTY sys.boot.reason.last bootloader
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs reboot,ota bootloader
 }
 
@@ -568,7 +571,8 @@
   adb reboot ota
   wait_for_screen
   EXPECT_PROPERTY sys.boot.reason reboot,ota
-  EXPECT_PROPERTY persist.sys.boot.reason reboot,ota
+  EXPECT_PROPERTY sys.boot.reason.last reboot,ota
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs reboot,ota
 }
 
@@ -593,9 +597,15 @@
   wait_for_screen
   bootloader_reason=`validate_property ro.boot.bootreason`
   EXPECT_PROPERTY ro.boot.bootreason ${bootloader_reason}
-  EXPECT_PROPERTY sys.boot.reason ${reason}
-  EXPECT_PROPERTY persist.sys.boot.reason ${reason}
-  report_bootstat_logs ${reason}
+  # to make sys.boot.reason report user friendly
+  reasons=${reason}
+  if [ "${bootloader_reason}" != "${reason}" -a -n "${bootloader_reason}" ]; then
+    reasons="\(${reason}\|${bootloader_reason}\)"
+  fi
+  EXPECT_PROPERTY sys.boot.reason ${reasons}
+  EXPECT_PROPERTY sys.boot.reason.last ${reason}
+  EXPECT_PROPERTY persist.sys.boot.reason ""
+  report_bootstat_logs ${reason} ${bootloader_reason}
 }
 
 [ "USAGE: test_cold
@@ -627,7 +637,8 @@
   adb reboot >&2
   wait_for_screen
   EXPECT_PROPERTY sys.boot.reason reboot,factory_reset
-  EXPECT_PROPERTY persist.sys.boot.reason "reboot,.*"
+  EXPECT_PROPERTY sys.boot.reason.last "reboot,.*"
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs reboot,factory_reset reboot, reboot,adb \
     "-bootstat: Failed to read /data/misc/bootstat/build_date: No such file or directory" \
     "-bootstat: Failed to parse boot time record: /data/misc/bootstat/build_date"
@@ -658,6 +669,7 @@
   wait_for_screen
   ( exit ${save_ret} )  # because one can not just do ?=${save_ret}
   EXPECT_PROPERTY sys.boot.reason reboot,factory_reset
+  EXPECT_PROPERTY sys.boot.reason.last ""
   EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs reboot,factory_reset bootloader \
     "-bootstat: Failed to read /data/misc/bootstat/last_boot_time_utc: No such file or directory" \
@@ -731,7 +743,8 @@
     )
 
   EXPECT_PROPERTY sys.boot.reason shutdown,battery
-  EXPECT_PROPERTY persist.sys.boot.reason cold
+  EXPECT_PROPERTY sys.boot.reason.last cold
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs shutdown,battery "-bootstat: Battery level at shutdown 2%"
   exitPstore
 }
@@ -752,7 +765,8 @@
   echo -n "WARNING: Please power device back up, waiting ... " >&2
   wait_for_screen -n >&2
   EXPECT_PROPERTY sys.boot.reason shutdown,battery
-  EXPECT_PROPERTY persist.sys.boot.reason shutdown,battery
+  EXPECT_PROPERTY sys.boot.reason.last shutdown,battery
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs shutdown,battery
 }
 
@@ -772,7 +786,8 @@
   echo -n "WARNING: Please power device back up, waiting ... " >&2
   wait_for_screen -n >&2
   EXPECT_PROPERTY sys.boot.reason shutdown,thermal,battery
-  EXPECT_PROPERTY persist.sys.boot.reason shutdown,thermal,battery
+  EXPECT_PROPERTY sys.boot.reason.last shutdown,thermal,battery
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs shutdown,thermal,battery
 }
 
@@ -808,7 +823,8 @@
   echo c | adb shell su root tee /proc/sysrq-trigger >/dev/null
   wait_for_screen
   EXPECT_PROPERTY sys.boot.reason ${panic_msg}
-  EXPECT_PROPERTY persist.sys.boot.reason ${panic_msg}
+  EXPECT_PROPERTY sys.boot.reason.last ${panic_msg}
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs kernel_panic,sysrq
   exitPstore
 }
@@ -835,7 +851,8 @@
   echo c | adb shell su root tee /proc/sysrq-trigger >/dev/null
   wait_for_screen
   EXPECT_PROPERTY sys.boot.reason ${panic_msg}
-  EXPECT_PROPERTY persist.sys.boot.reason ${panic_msg}
+  EXPECT_PROPERTY sys.boot.reason.last ${panic_msg}
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs kernel_panic,sysrq,test \
     "-bootstat: Unknown boot reason: kernel_panic,sysrq,test"
   exitPstore
@@ -865,7 +882,8 @@
   adb reboot warm
   wait_for_screen
   EXPECT_PROPERTY sys.boot.reason ${panic_msg}
-  EXPECT_PROPERTY persist.sys.boot.reason ${panic_msg}
+  EXPECT_PROPERTY sys.boot.reason.last ${panic_msg}
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs kernel_panic,hung
   exitPstore
 }
@@ -897,7 +915,8 @@
   echo -n "WARNING: Please power device back up, waiting ... " >&2
   wait_for_screen -n >&2
   EXPECT_PROPERTY sys.boot.reason shutdown,thermal
-  EXPECT_PROPERTY persist.sys.boot.reason shutdown,thermal
+  EXPECT_PROPERTY sys.boot.reason.last shutdown,thermal
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs shutdown,thermal
 }
 
@@ -917,7 +936,8 @@
   echo -n "WARNING: Please power device back up, waiting ... " >&2
   wait_for_screen -n >&2
   EXPECT_PROPERTY sys.boot.reason shutdown,userrequested
-  EXPECT_PROPERTY persist.sys.boot.reason shutdown,userrequested
+  EXPECT_PROPERTY sys.boot.reason.last shutdown,userrequested
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs shutdown,userrequested
 }
 
@@ -933,7 +953,8 @@
   adb shell reboot
   wait_for_screen
   EXPECT_PROPERTY sys.boot.reason reboot,shell
-  EXPECT_PROPERTY persist.sys.boot.reason reboot,shell
+  EXPECT_PROPERTY sys.boot.reason.last reboot,shell
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs reboot,shell
 }
 
@@ -949,7 +970,8 @@
   adb reboot
   wait_for_screen
   EXPECT_PROPERTY sys.boot.reason reboot,adb
-  EXPECT_PROPERTY persist.sys.boot.reason reboot,adb
+  EXPECT_PROPERTY sys.boot.reason.last reboot,adb
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs reboot,adb
 }
 
@@ -984,23 +1006,8 @@
   adb shell 'reboot "Its Just So Hard"'
   wait_for_screen
   EXPECT_PROPERTY sys.boot.reason reboot,its_just_so_hard
-  EXPECT_PROPERTY persist.sys.boot.reason "reboot,Its Just So Hard"
-  # Do not leave this test with an illegal value in persist.sys.boot.reason
-  save_ret=${?}           # hold on to error code from above two lines
-  if isDebuggable; then   # can do this easy, or we can do this hard.
-    adb shell su root setprop persist.sys.boot.reason reboot,its_just_so_hard
-    ( exit ${save_ret} )  # because one can not just do ?=${save_ret}
-  else
-    report_bootstat_logs reboot,its_just_so_hard  # report what we have so far
-    # user build mitigation
-    adb shell reboot its_just_so_hard
-    wait_for_screen
-    ( exit ${save_ret} )  # because one can not just do ?=${save_ret}
-    EXPECT_PROPERTY sys.boot.reason reboot,its_just_so_hard
-  fi
-  # Ensure persist.sys.boot.reason now valid, failure here acts as a signal
-  # that we could choke up following tests.  For example test_properties.
-  EXPECT_PROPERTY persist.sys.boot.reason reboot,its_just_so_hard ${flag}
+  EXPECT_PROPERTY sys.boot.reason.last reboot,its_just_so_hard
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs reboot,its_just_so_hard
 }
 
@@ -1049,7 +1056,8 @@
   # Check values
   EXPECT_PROPERTY ro.boot.bootreason "${bootloader_expected}"
   EXPECT_PROPERTY sys.boot.reason "${sys_expected}"
-  EXPECT_PROPERTY persist.sys.boot.reason "${last_expected}"
+  EXPECT_PROPERTY sys.boot.reason.last "${last_expected}"
+  EXPECT_PROPERTY persist.sys.boot.reason ""
   report_bootstat_logs "${sys_expected}"
 }
 
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index 7614342..9fe25fd 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -735,6 +735,7 @@
 
 const char system_reboot_reason_property[] = "sys.boot.reason";
 const char last_reboot_reason_property[] = LAST_REBOOT_REASON_PROPERTY;
+const char last_last_reboot_reason_property[] = "sys.boot.reason.last";
 const char bootloader_reboot_reason_property[] = "ro.boot.bootreason";
 
 // Scrub, Sanitize, Standardize and Enhance the boot reason string supplied.
@@ -926,27 +927,10 @@
         }
         android_logcat_pclose(&ctx, fp);
         static const char logcat_battery[] = "W/healthd (    0): battery l=";
-        const char* match = logcat_battery;
 
-        if (content == "") {
-          // Service logd.klog not running, go to smaller buffer in the kernel.
-          int rc = klogctl(KLOG_SIZE_BUFFER, nullptr, 0);
-          if (rc > 0) {
-            ssize_t len = rc + 1024;  // 1K Margin should it grow between calls.
-            std::unique_ptr<char[]> buf(new char[len]);
-            rc = klogctl(KLOG_READ_ALL, buf.get(), len);
-            if (rc < len) {
-              len = rc + 1;
-            }
-            buf[--len] = '\0';
-            content = buf.get();
-          }
-          match = battery;
-        }
-
-        pos = content.find(match);  // The first one it finds.
+        pos = content.find(logcat_battery);  // The first one it finds.
         if (pos != std::string::npos) {
-          digits = content.substr(pos + strlen(match), strlen("100 "));
+          digits = content.substr(pos + strlen(logcat_battery), strlen("100 "));
         }
         endptr = digits.c_str();
         level = 0;
@@ -1002,10 +986,6 @@
   }
 
   LOG(INFO) << "Canonical boot reason: " << ret;
-  if (isKernelRebootReason(ret) && (GetProperty(last_reboot_reason_property) != "")) {
-    // Rewrite as it must be old news, kernel reasons trump user space.
-    SetProperty(last_reboot_reason_property, ret);
-  }
   return ret;
 }
 
@@ -1135,6 +1115,15 @@
   const std::string system_boot_reason(BootReasonStrToReason(bootloader_boot_reason));
   // Record the scrubbed system_boot_reason to the property
   SetProperty(system_reboot_reason_property, system_boot_reason);
+  // Shift last_reboot_reason_property to last_last_reboot_reason_property
+  std::string last_boot_reason(GetProperty(last_reboot_reason_property));
+  if (last_boot_reason.empty() || isKernelRebootReason(system_boot_reason)) {
+    last_boot_reason = system_boot_reason;
+  } else {
+    transformReason(last_boot_reason);
+  }
+  SetProperty(last_last_reboot_reason_property, last_boot_reason);
+  SetProperty(last_reboot_reason_property, "");
 }
 
 // Records several metrics related to the time it takes to boot the device,
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 0b13662..1b20157 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -47,6 +47,7 @@
 cc_library_static {
     name: "libtombstoned_client_static",
     defaults: ["debuggerd_defaults"],
+    recovery_available: true,
     srcs: [
         "tombstoned/tombstoned_client.cpp",
         "util.cpp",
@@ -90,7 +91,6 @@
 cc_library_static {
     name: "libdebuggerd_handler",
     defaults: ["debuggerd_defaults"],
-    recovery_available: true,
     srcs: ["handler/debuggerd_fallback_nop.cpp"],
 
     whole_static_libs: [
@@ -104,6 +104,7 @@
 cc_library_static {
     name: "libdebuggerd_handler_fallback",
     defaults: ["debuggerd_defaults"],
+    recovery_available: true,
     srcs: [
         "handler/debuggerd_fallback.cpp",
     ],
@@ -120,6 +121,12 @@
         "liblzma",
         "libcutils",
     ],
+    target: {
+        recovery: {
+            cflags: ["-DNO_LIBDEXFILE_SUPPORT"],
+            exclude_static_libs: ["libdexfile"],
+        },
+    },
 
     export_include_dirs: ["include"],
 }
diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp
index bc3b04b..923442f 100644
--- a/fs_mgr/Android.bp
+++ b/fs_mgr/Android.bp
@@ -56,12 +56,15 @@
         "libselinux",
         "libavb",
         "libfstab",
+        "libdm",
     ],
     export_static_lib_headers: [
         "libfstab",
+        "libdm",
     ],
     whole_static_libs: [
         "liblogwrap",
+        "libdm",
         "libfstab",
     ],
     cppflags: [
diff --git a/fs_mgr/libdm/Android.bp b/fs_mgr/libdm/Android.bp
new file mode 100644
index 0000000..672d401
--- /dev/null
+++ b/fs_mgr/libdm/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_library_static {
+    name: "libdm",
+    recovery_available: true,
+
+    export_include_dirs: ["include"],
+    cflags: [
+        // TODO(b/110035986): Allows us to create a skeleton of required classes
+        "-Wno-unused-private-field",
+    ],
+
+    srcs: [
+        "dm_table.cpp",
+        "dm_target.cpp",
+        "dm.cpp"
+    ],
+
+    header_libs: [
+        "libbase_headers",
+        "liblog_headers",
+    ],
+}
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
new file mode 100644
index 0000000..57c1270
--- /dev/null
+++ b/fs_mgr/libdm/dm.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2018 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 <errno.h>
+#include <fcntl.h>
+#include <linux/dm-ioctl.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/unique_fd.h>
+
+#include "dm.h"
+
+namespace android {
+namespace dm {
+
+DeviceMapper& DeviceMapper::Instance() {
+    static DeviceMapper instance;
+    return instance;
+}
+// Creates a new device mapper device
+bool DeviceMapper::CreateDevice(const std::string& name) {
+    if (name.empty()) {
+        LOG(ERROR) << "Unnamed device mapper device creation is not supported";
+        return false;
+    }
+
+    if (name.size() >= DM_NAME_LEN) {
+        LOG(ERROR) << "[" << name << "] is too long to be device mapper name";
+        return false;
+    }
+
+    std::unique_ptr<struct dm_ioctl, decltype(&free)> io(
+            static_cast<struct dm_ioctl*>(malloc(sizeof(struct dm_ioctl))), free);
+    if (io == nullptr) {
+        LOG(ERROR) << "Failed to allocate dm_ioctl";
+        return false;
+    }
+    InitIo(io.get(), name);
+
+    if (ioctl(fd_, DM_DEV_CREATE, io.get())) {
+        PLOG(ERROR) << "DM_DEV_CREATE failed to create [" << name << "]";
+        return false;
+    }
+
+    // Check to make sure the newly created device doesn't already have targets
+    // added or opened by someone
+    CHECK(io->target_count == 0) << "Unexpected targets for newly created [" << name << "] device";
+    CHECK(io->open_count == 0) << "Unexpected opens for newly created [" << name << "] device";
+
+    // Creates a new device mapper device with the name passed in
+    return true;
+}
+
+bool DeviceMapper::DeleteDevice(const std::string& name) {
+    if (name.empty()) {
+        LOG(ERROR) << "Unnamed device mapper device creation is not supported";
+        return false;
+    }
+
+    if (name.size() >= DM_NAME_LEN) {
+        LOG(ERROR) << "[" << name << "] is too long to be device mapper name";
+        return false;
+    }
+
+    std::unique_ptr<struct dm_ioctl, decltype(&free)> io(
+            static_cast<struct dm_ioctl*>(malloc(sizeof(struct dm_ioctl))), free);
+    if (io == nullptr) {
+        LOG(ERROR) << "Failed to allocate dm_ioctl";
+        return false;
+    }
+    InitIo(io.get(), name);
+
+    if (ioctl(fd_, DM_DEV_REMOVE, io.get())) {
+        PLOG(ERROR) << "DM_DEV_REMOVE failed to create [" << name << "]";
+        return false;
+    }
+
+    // Check to make sure appropriate uevent is generated so ueventd will
+    // do the right thing and remove the corresponding device node and symlinks.
+    CHECK(io->flags & DM_UEVENT_GENERATED_FLAG)
+            << "Didn't generate uevent for [" << name << "] removal";
+
+    return true;
+}
+
+const std::unique_ptr<DmTable> DeviceMapper::table(const std::string& /* name */) const {
+    // TODO(b/110035986): Return the table, as read from the kernel instead
+    return nullptr;
+}
+
+DmDeviceState DeviceMapper::state(const std::string& /* name */) const {
+    // TODO(b/110035986): Return the state, as read from the kernel instead
+    return DmDeviceState::INVALID;
+}
+
+bool DeviceMapper::LoadTableAndActivate(const std::string& /* name */, const DmTable& /* table */) {
+    return false;
+}
+
+// Reads all the available device mapper targets and their corresponding
+// versions from the kernel and returns in a vector
+bool DeviceMapper::GetAvailableTargets(std::vector<DmTarget>* targets) {
+    targets->clear();
+
+    // calculate the space needed to read a maximum of kMaxPossibleDmTargets
+    uint32_t payload_size = sizeof(struct dm_target_versions);
+    payload_size += DM_MAX_TYPE_NAME;
+    // device mapper wants every target spec to be aligned at 8-byte boundary
+    payload_size = DM_ALIGN(payload_size);
+    payload_size *= kMaxPossibleDmTargets;
+
+    uint32_t data_size = sizeof(struct dm_ioctl) + payload_size;
+    auto buffer = std::unique_ptr<void, void (*)(void*)>(calloc(1, data_size), free);
+    if (buffer == nullptr) {
+        LOG(ERROR) << "failed to allocate memory";
+        return false;
+    }
+
+    // Sets appropriate data size and data_start to make sure we tell kernel
+    // about the total size of the buffer we are passing and where to start
+    // writing the list of targets.
+    struct dm_ioctl* io = reinterpret_cast<struct dm_ioctl*>(buffer.get());
+    InitIo(io);
+    io->data_size = data_size;
+    io->data_start = sizeof(*io);
+
+    if (ioctl(fd_, DM_LIST_VERSIONS, io)) {
+        PLOG(ERROR) << "Failed to get DM_LIST_VERSIONS from kernel";
+        return false;
+    }
+
+    // If the provided buffer wasn't enough to list all targets, note that
+    // any data beyond sizeof(*io) must not be read in this case
+    if (io->flags & DM_BUFFER_FULL_FLAG) {
+        LOG(INFO) << data_size << " is not enough memory to list all dm targets";
+        return false;
+    }
+
+    // if there are no targets registered, return success with empty vector
+    if (io->data_size == sizeof(*io)) {
+        return true;
+    }
+
+    // Parse each target and list the name and version
+    // TODO(b/110035986): Templatize this
+    uint32_t next = sizeof(*io);
+    data_size = io->data_size - next;
+    struct dm_target_versions* vers =
+            reinterpret_cast<struct dm_target_versions*>(static_cast<char*>(buffer.get()) + next);
+    while (next && data_size) {
+        targets->emplace_back((vers));
+        if (vers->next == 0) {
+            break;
+        }
+        next += vers->next;
+        data_size -= vers->next;
+        vers = reinterpret_cast<struct dm_target_versions*>(static_cast<char*>(buffer.get()) + next);
+    }
+
+    return true;
+}
+
+bool DeviceMapper::GetAvailableDevices(std::vector<DmBlockDevice>* devices) {
+    devices->clear();
+
+    // calculate the space needed to read a maximum of 256 targets, each with
+    // name with maximum length of 16 bytes
+    uint32_t payload_size = sizeof(struct dm_name_list);
+    // 128-bytes for the name
+    payload_size += DM_NAME_LEN;
+    // dm wants every device spec to be aligned at 8-byte boundary
+    payload_size = DM_ALIGN(payload_size);
+    payload_size *= kMaxPossibleDmDevices;
+    uint32_t data_size = sizeof(struct dm_ioctl) + payload_size;
+    auto buffer = std::unique_ptr<void, void (*)(void*)>(calloc(1, data_size), free);
+    if (buffer == nullptr) {
+        LOG(ERROR) << "failed to allocate memory";
+        return false;
+    }
+
+    // Sets appropriate data size and data_start to make sure we tell kernel
+    // about the total size of the buffer we are passing and where to start
+    // writing the list of targets.
+    struct dm_ioctl* io = reinterpret_cast<struct dm_ioctl*>(buffer.get());
+    InitIo(io);
+    io->data_size = data_size;
+    io->data_start = sizeof(*io);
+
+    if (ioctl(fd_, DM_LIST_DEVICES, io)) {
+        PLOG(ERROR) << "Failed to get DM_LIST_DEVICES from kernel";
+        return false;
+    }
+
+    // If the provided buffer wasn't enough to list all devices any data
+    // beyond sizeof(*io) must not be read.
+    if (io->flags & DM_BUFFER_FULL_FLAG) {
+        LOG(INFO) << data_size << " is not enough memory to list all dm devices";
+        return false;
+    }
+
+    // if there are no devices created yet, return success with empty vector
+    if (io->data_size == sizeof(*io)) {
+        return true;
+    }
+
+    // Parse each device and add a new DmBlockDevice to the vector
+    // created from the kernel data.
+    uint32_t next = sizeof(*io);
+    data_size = io->data_size - next;
+    struct dm_name_list* dm_dev =
+            reinterpret_cast<struct dm_name_list*>(static_cast<char*>(buffer.get()) + next);
+
+    while (next && data_size) {
+        devices->emplace_back((dm_dev));
+        if (dm_dev->next == 0) {
+            break;
+        }
+        next += dm_dev->next;
+        data_size -= dm_dev->next;
+        dm_dev = reinterpret_cast<struct dm_name_list*>(static_cast<char*>(buffer.get()) + next);
+    }
+
+    return true;
+}
+
+// Accepts a device mapper device name (like system_a, vendor_b etc) and
+// returns the path to it's device node (or symlink to the device node)
+std::string DeviceMapper::GetDmDevicePathByName(const std::string& /* name */) {
+    return "";
+}
+
+// private methods of DeviceMapper
+void DeviceMapper::InitIo(struct dm_ioctl* io, const std::string& name) const {
+    CHECK(io != nullptr) << "nullptr passed to dm_ioctl initialization";
+    memset(io, 0, sizeof(*io));
+
+    io->version[0] = DM_VERSION0;
+    io->version[1] = DM_VERSION1;
+    io->version[2] = DM_VERSION2;
+    io->data_size = sizeof(*io);
+    io->data_start = 0;
+    if (!name.empty()) {
+        strlcpy(io->name, name.c_str(), sizeof(io->name));
+    }
+}
+
+}  // namespace dm
+}  // namespace android
diff --git a/fs_mgr/libdm/dm_table.cpp b/fs_mgr/libdm/dm_table.cpp
new file mode 100644
index 0000000..14b3932
--- /dev/null
+++ b/fs_mgr/libdm/dm_table.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 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/logging.h>
+#include <android-base/macros.h>
+
+#include <string>
+#include <vector>
+
+#include "dm_table.h"
+
+namespace android {
+namespace dm {
+
+bool DmTable::AddTarget(std::unique_ptr<DmTarget>&& /* target */) {
+    return true;
+}
+
+bool DmTable::RemoveTarget(std::unique_ptr<DmTarget>&& /* target */) {
+    return true;
+}
+
+bool DmTable::valid() const {
+    return true;
+}
+
+uint64_t DmTable::size() const {
+    return valid() ? size_ : 0;
+}
+
+// Returns a string represnetation of the table that is ready to be passed
+// down to the kernel for loading
+//
+// Implementation must verify there are no gaps in the table, table starts
+// with sector == 0, and iterate over each target to get its table
+// serialized.
+std::string DmTable::Serialize() const {
+    return "";
+}
+
+}  // namespace dm
+}  // namespace android
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
new file mode 100644
index 0000000..8bcd526
--- /dev/null
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 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/logging.h>
+#include <android-base/macros.h>
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "dm_target.h"
+
+namespace android {
+namespace dm {}  // namespace dm
+}  // namespace android
diff --git a/fs_mgr/libdm/include/dm.h b/fs_mgr/libdm/include/dm.h
new file mode 100644
index 0000000..52a9a11
--- /dev/null
+++ b/fs_mgr/libdm/include/dm.h
@@ -0,0 +1,152 @@
+/*
+ *  Copyright 2018 Google, Inc
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#ifndef _LIBDM_DM_H_
+#define _LIBDM_DM_H_
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/dm-ioctl.h>
+#include <linux/kdev_t.h>
+#include <sys/sysmacros.h>
+#include <unistd.h>
+
+#include <memory>
+
+#include <android-base/logging.h>
+
+#include <dm_table.h>
+
+// The minimum expected device mapper major.minor version
+#define DM_VERSION0 (4)
+#define DM_VERSION1 (0)
+#define DM_VERSION2 (0)
+
+#define DM_ALIGN_MASK (7)
+#define DM_ALIGN(x) ((x + DM_ALIGN_MASK) & ~DM_ALIGN_MASK)
+
+namespace android {
+namespace dm {
+
+enum class DmDeviceState { INVALID, SUSPENDED, ACTIVE };
+
+class DeviceMapper final {
+  public:
+    class DmBlockDevice final {
+      public:
+        // only allow creating this with dm_name_list
+        DmBlockDevice() = delete;
+
+        explicit DmBlockDevice(struct dm_name_list* d) : name_(d->name), dev_(d->dev){};
+
+        // Returs device mapper name associated with the block device
+        const std::string& name() const { return name_; }
+
+        // Return major number for the block device
+        uint32_t Major() const { return major(dev_); }
+
+        // Return minor number for the block device
+        uint32_t Minor() const { return minor(dev_); }
+        ~DmBlockDevice() = default;
+
+      private:
+        std::string name_;
+        uint64_t dev_;
+    };
+
+    // Creates a device mapper device with given name.
+    // Return 'true' on success and 'false' on failure to
+    // create OR if a device mapper device with the same name already
+    // exists.
+    // TODO(b/110035986): Make this method private and to be only
+    // called through LoadTableAndActivate() below.
+    bool CreateDevice(const std::string& name);
+
+    // Removes a device mapper device with the given name.
+    // Returns 'true' on success, false otherwise.
+    bool DeleteDevice(const std::string& name);
+
+    // Reads the device mapper table from the device with given anme and
+    // returns it in a DmTable object.
+    const std::unique_ptr<DmTable> table(const std::string& name) const;
+
+    // Returns the current state of the underlying device mapper device
+    // with given name.
+    // One of INVALID, SUSPENDED or ACTIVE.
+    DmDeviceState state(const std::string& name) const;
+
+    // Loads the device mapper table from parameter into the underlying
+    // device mapper device with given name and activate / resumes the device in the process.
+    // If a device mapper device with the 'name', doesn't exist, it will be created.
+    // Returns 'true' on success, false otherwise.
+    bool LoadTableAndActivate(const std::string& name, const DmTable& table);
+
+    // Returns true if a list of available device mapper targets registered in the kernel was
+    // successfully read and stored in 'targets'. Returns 'false' otherwise.
+    bool GetAvailableTargets(std::vector<DmTarget>* targets);
+
+    // Return 'true' if it can successfully read the list of device mapper block devices
+    // currently created. 'devices' will be empty if the kernel interactions
+    // were successful and there are no block devices at the moment. Returns
+    // 'false' in case of any failure along the way.
+    bool GetAvailableDevices(std::vector<DmBlockDevice>* devices);
+
+    // Returns the path to the device mapper device node in '/dev' corresponding to
+    // 'name'.
+    std::string GetDmDevicePathByName(const std::string& name);
+
+    // The only way to create a DeviceMapper object.
+    static DeviceMapper& Instance();
+
+    ~DeviceMapper() {
+        if (fd_ != -1) {
+            ::close(fd_);
+        }
+    }
+
+  private:
+    // Maximum possible device mapper targets registered in the kernel.
+    // This is only used to read the list of targets from kernel so we allocate
+    // a finite amount of memory. This limit is in no way enforced by the kernel.
+    static constexpr uint32_t kMaxPossibleDmTargets = 256;
+
+    // Maximum possible device mapper created block devices. Note that this is restricted by
+    // the minor numbers (that used to be 8 bits) that can be range from 0 to 2^20-1 in newer
+    // kernels. In Android systems however, we never expect these to grow beyond the artificial
+    // limit we are imposing here of 256.
+    static constexpr uint32_t kMaxPossibleDmDevices = 256;
+
+    void InitIo(struct dm_ioctl* io, const std::string& name = std::string()) const;
+
+    DeviceMapper() : fd_(-1) {
+        fd_ = TEMP_FAILURE_RETRY(open("/dev/device-mapper", O_RDWR | O_CLOEXEC));
+        if (fd_ < 0) {
+            PLOG(ERROR) << "Failed to open device-mapper";
+        }
+    }
+
+    int fd_;
+    // Non-copyable & Non-movable
+    DeviceMapper(const DeviceMapper&) = delete;
+    DeviceMapper& operator=(const DeviceMapper&) = delete;
+    DeviceMapper& operator=(DeviceMapper&&) = delete;
+    DeviceMapper(DeviceMapper&&) = delete;
+};
+
+}  // namespace dm
+}  // namespace android
+
+#endif /* _LIBDM_DM_H_ */
diff --git a/fs_mgr/libdm/include/dm_table.h b/fs_mgr/libdm/include/dm_table.h
new file mode 100644
index 0000000..0b1685d
--- /dev/null
+++ b/fs_mgr/libdm/include/dm_table.h
@@ -0,0 +1,75 @@
+/*
+ *  Copyright 2018 Google, Inc
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#ifndef _LIBDM_DMTABLE_H_
+#define _LIBDM_DMTABLE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "dm_target.h"
+
+namespace android {
+namespace dm {
+
+class DmTable {
+  public:
+    DmTable() : size_(0){};
+
+    // Adds a target to the device mapper table for a range specified in the target object.
+    // The function will return 'true' if the target was successfully added and doesn't overlap with
+    // any of the existing targets in the table. Gaps are allowed. The final check, including
+    // overlaps and gaps are done before loading the table. Returns 'false' on failure.
+    bool AddTarget(std::unique_ptr<DmTarget>&& target);
+
+    // Removes a target from the table for the range specified in the target object. Returns 'false'
+    // if the target name doesn't match with the one in the table. Returns 'true' if target is
+    // successfully removed.
+    bool RemoveTarget(std::unique_ptr<DmTarget>&& target);
+
+    // Checks the table to make sure it is valid. i.e. Checks for range overlaps, range gaps
+    // and returns 'true' if the table is ready to be loaded into kernel. Returns 'false' if the
+    // table is malformed.
+    bool valid() const;
+
+    // Returns the total size represented by the table in terms of number of 512-byte sectors.
+    // NOTE: This function will overlook if there are any gaps in the targets added in the table.
+    uint64_t size() const;
+
+    // Returns the string represntation of the table that is ready to be passed into the kernel
+    // as part of the DM_TABLE_LOAD ioctl.
+    std::string Serialize() const;
+
+    ~DmTable() = default;
+
+  private:
+    // list of targets defined in this table sorted by
+    // their start and end sectors.
+    // Note: Overlapping targets MUST never be added in this list.
+    std::vector<std::unique_ptr<DmTarget>> targets_;
+
+    // Total size in terms of # of sectors, as calculated by looking at the last and the first
+    // target in 'target_'.
+    uint64_t size_;
+};
+
+}  // namespace dm
+}  // namespace android
+
+#endif /* _LIBDM_DMTABLE_H_ */
diff --git a/fs_mgr/libdm/include/dm_target.h b/fs_mgr/libdm/include/dm_target.h
new file mode 100644
index 0000000..31b0cb6
--- /dev/null
+++ b/fs_mgr/libdm/include/dm_target.h
@@ -0,0 +1,77 @@
+/*
+ *  Copyright 2018 Google, Inc
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#ifndef _LIBDM_DMTARGET_H_
+#define _LIBDM_DMTARGET_H_
+
+#include <linux/dm-ioctl.h>
+#include <stdint.h>
+
+#include <android-base/logging.h>
+
+#include <string>
+
+namespace android {
+namespace dm {
+
+class DmTarget {
+  public:
+    DmTarget(const std::string& name, uint64_t start = 0, uint64_t length = 0)
+        : name_(name), v0_(0), v1_(0), v2_(0), start_(start), length_(length){};
+
+    // Creates a DmTarget object from dm_target_version as read from kernel
+    // with DM_LIST_VERSION ioctl.
+    DmTarget(const struct dm_target_versions* vers) : start_(0), length_(0) {
+        CHECK(vers != nullptr) << "Can't create DmTarget with dm_target_versions set to nullptr";
+        v0_ = vers->version[0];
+        v1_ = vers->version[1];
+        v2_ = vers->version[2];
+        name_ = vers->name;
+    }
+
+    virtual ~DmTarget() = default;
+
+    // Returns name of the target.
+    const std::string& name() const { return name_; }
+
+    // Returns size in number of sectors when this target is part of
+    // a DmTable, return 0 otherwise.
+    uint64_t size() const { return length_; }
+
+    // Return string representation of the device mapper target version.
+    std::string version() const {
+        return std::to_string(v0_) + "." + std::to_string(v1_) + "." + std::to_string(v2_);
+    }
+
+    // Function that converts this object to a string of arguments that can
+    // be passed to the kernel for adding this target in a table. Each target (e.g. verity, linear)
+    // must implement this, for it to be used on a device.
+    virtual std::string Serialize() const { return ""; }
+
+  private:
+    // Name of the target.
+    std::string name_;
+    // Target version.
+    uint32_t v0_, v1_, v2_;
+    // logical sector number start and total length (in terms of 512-byte sectors) represented
+    // by this target within a DmTable.
+    uint64_t start_, length_;
+};
+
+}  // namespace dm
+}  // namespace android
+
+#endif /* _LIBDM_DMTARGET_H_ */
diff --git a/fs_mgr/tools/Android.bp b/fs_mgr/tools/Android.bp
new file mode 100644
index 0000000..4d4aae4
--- /dev/null
+++ b/fs_mgr/tools/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_binary {
+    name: "dmctl",
+    srcs: ["dmctl.cpp"],
+
+    static_libs: [
+        "libfs_mgr",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+
+    cflags: ["-Werror"],
+}
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
new file mode 100644
index 0000000..b40b83a
--- /dev/null
+++ b/fs_mgr/tools/dmctl.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2018 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 <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <linux/dm-ioctl.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/unique_fd.h>
+#include <dm.h>
+
+#include <functional>
+#include <iomanip>
+#include <ios>
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+using DeviceMapper = ::android::dm::DeviceMapper;
+using DmTarget = ::android::dm::DmTarget;
+using DmBlockDevice = ::android::dm::DeviceMapper::DmBlockDevice;
+
+static int Usage(void) {
+    std::cerr << "usage: dmctl <command> [command options]" << std::endl;
+    std::cerr << "commands:" << std::endl;
+    std::cerr << "  create <dm-name> [<dm-target> [-lo <filename>] <dm-target-args>]" << std::endl;
+    std::cerr << "  delete <dm-name>" << std::endl;
+    std::cerr << "  list <devices | targets>" << std::endl;
+    std::cerr << "  help" << std::endl;
+    return -EINVAL;
+}
+
+static int DmCreateCmdHandler(int argc, char** argv) {
+    if (argc < 1) {
+        std::cerr << "DmCreateCmdHandler: atleast 'name' MUST be provided for target device";
+        return -EINVAL;
+    }
+
+    std::string name = argv[0];
+    DeviceMapper& dm = DeviceMapper::Instance();
+    if (!dm.CreateDevice(name)) {
+        std::cerr << "DmCreateCmdHandler: Failed to create " << name << " device";
+        return -EIO;
+    }
+
+    // if we also have target specified
+    if (argc > 1) {
+        // fall through for now. This will eventually create a DmTarget() based on the target name
+        // passing it the table that is specified at the command line
+    }
+
+    return 0;
+}
+
+static int DmDeleteCmdHandler(int argc, char** argv) {
+    if (argc < 1) {
+        std::cerr << "DmCreateCmdHandler: atleast 'name' MUST be provided for target device";
+        return -EINVAL;
+    }
+
+    std::string name = argv[0];
+    DeviceMapper& dm = DeviceMapper::Instance();
+    if (!dm.DeleteDevice(name)) {
+        std::cerr << "DmCreateCmdHandler: Failed to create " << name << " device";
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static int DmListTargets(DeviceMapper& dm) {
+    std::vector<DmTarget> targets;
+    if (!dm.GetAvailableTargets(&targets)) {
+        std::cerr << "Failed to read available device mapper targets" << std::endl;
+        return -errno;
+    }
+
+    std::cout << "Available Device Mapper Targets:" << std::endl;
+    if (targets.empty()) {
+        std::cout << "  <empty>" << std::endl;
+        return 0;
+    }
+
+    for (const auto& target : targets) {
+        std::cout << std::left << std::setw(20) << target.name() << " : " << target.version()
+                  << std::endl;
+    }
+
+    return 0;
+}
+
+static int DmListDevices(DeviceMapper& dm) {
+    std::vector<DmBlockDevice> devices;
+    if (!dm.GetAvailableDevices(&devices)) {
+        std::cerr << "Failed to read available device mapper devices" << std::endl;
+        return -errno;
+    }
+    std::cout << "Available Device Mapper Devices:" << std::endl;
+    if (devices.empty()) {
+        std::cout << "  <empty>" << std::endl;
+        return 0;
+    }
+
+    for (const auto& dev : devices) {
+        std::cout << std::left << std::setw(20) << dev.name() << " : " << dev.Major() << ":"
+                  << dev.Minor() << std::endl;
+    }
+
+    return 0;
+}
+
+static const std::map<std::string, std::function<int(DeviceMapper&)>> listmap = {
+        {"targets", DmListTargets},
+        {"devices", DmListDevices},
+};
+
+static int DmListCmdHandler(int argc, char** argv) {
+    if (argc < 1) {
+        std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+        return -EINVAL;
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    for (const auto& l : listmap) {
+        if (l.first == argv[0]) return l.second(dm);
+    }
+
+    std::cerr << "Invalid argument to \'dmctl list\': " << argv[0] << std::endl;
+    return -EINVAL;
+}
+
+static int HelpCmdHandler(int /* argc */, char** /* argv */) {
+    Usage();
+    return 0;
+}
+
+static std::map<std::string, std::function<int(int, char**)>> cmdmap = {
+        {"create", DmCreateCmdHandler},
+        {"delete", DmDeleteCmdHandler},
+        {"list", DmListCmdHandler},
+        {"help", HelpCmdHandler},
+};
+
+int main(int argc, char** argv) {
+    android::base::InitLogging(argv, &android::base::StderrLogger);
+    if (argc < 2) {
+        return Usage();
+    }
+
+    for (const auto& cmd : cmdmap) {
+        if (cmd.first == argv[1]) {
+            return cmd.second(argc - 2, argv + 2);
+        }
+    }
+
+    return Usage();
+}
diff --git a/init/Android.bp b/init/Android.bp
index 25877c0..7d863c8 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -67,24 +67,26 @@
         "libsquashfs_utils",
         "liblogwrap",
         "libext4_utils",
-        "libcutils",
-        "libbase",
-        "libc",
         "libseccomp_policy",
-        "libselinux",
-        "liblog",
         "libcrypto_utils",
-        "libcrypto",
-        "libc++_static",
-        "libdl",
         "libsparse",
-        "libz",
         "libprocessgroup",
         "libavb",
         "libkeyutils",
         "libprotobuf-cpp-lite",
         "libpropertyinfoserializer",
         "libpropertyinfoparser",
+        "libselinux",
+    ],
+    shared_libs: [
+        "libcutils",
+        "libbase",
+        "libc",
+        "liblog",
+        "libcrypto",
+        "libc++",
+        "libdl",
+        "libz",
     ],
 }
 
@@ -166,7 +168,6 @@
 cc_test {
     name: "init_tests",
     defaults: ["init_defaults"],
-    static_executable: true,
     srcs: [
         "devices_test.cpp",
         "init_test.cpp",
@@ -187,7 +188,6 @@
 
 cc_benchmark {
     name: "init_benchmarks",
-    static_executable: true,
     defaults: ["init_defaults"],
     srcs: [
         "subcontext_benchmark.cpp",
diff --git a/init/Android.mk b/init/Android.mk
index c4a6a50..da27a73 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -45,7 +45,6 @@
 
 LOCAL_MODULE:= init
 
-LOCAL_FORCE_STATIC_EXECUTABLE := true
 LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
 LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_UNSTRIPPED)
 
@@ -59,18 +58,10 @@
     libsquashfs_utils \
     liblogwrap \
     libext4_utils \
-    libcutils \
-    libbase \
-    libc \
     libseccomp_policy \
-    libselinux \
-    liblog \
     libcrypto_utils \
-    libcrypto \
-    libc++_static \
-    libdl \
     libsparse \
-    libz \
+    libselinux \
     libprocessgroup \
     libavb \
     libkeyutils \
@@ -78,6 +69,25 @@
     libpropertyinfoserializer \
     libpropertyinfoparser \
 
+shared_libs := \
+    libcutils \
+    libbase \
+    liblog \
+    libcrypto \
+    libdl \
+    libz \
+
+ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
+# init is static executable for non-system-as-root devices, because the dynamic linker
+# and shared libs are not available before /system is mounted, but init has to run
+# before the partition is mounted.
+LOCAL_STATIC_LIBRARIES += $(shared_libs) libc++_static
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+else
+LOCAL_SHARED_LIBRARIES := $(shared_libs) libc++
+endif
+shared_libs :=
+
 LOCAL_REQUIRED_MODULES := \
     e2fsdroid \
     mke2fs \
diff --git a/init/init.cpp b/init/init.cpp
index 82648d9..b494bcc 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -604,47 +604,61 @@
     if (is_first_stage) {
         boot_clock::time_point start_time = boot_clock::now();
 
+        std::vector<std::pair<std::string, int>> errors;
+#define CHECKCALL(x) \
+    if (x != 0) errors.emplace_back(#x " failed", errno);
+
         // Clear the umask.
         umask(0);
 
-        clearenv();
-        setenv("PATH", _PATH_DEFPATH, 1);
+        CHECKCALL(clearenv());
+        CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
         // Get the basic filesystem setup we need put together in the initramdisk
         // on / and then we'll let the rc file figure out the rest.
-        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
-        mkdir("/dev/pts", 0755);
-        mkdir("/dev/socket", 0755);
-        mount("devpts", "/dev/pts", "devpts", 0, NULL);
-        #define MAKE_STR(x) __STRING(x)
-        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
+        CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
+        CHECKCALL(mkdir("/dev/pts", 0755));
+        CHECKCALL(mkdir("/dev/socket", 0755));
+        CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
+#define MAKE_STR(x) __STRING(x)
+        CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
+#undef MAKE_STR
         // Don't expose the raw commandline to unprivileged processes.
-        chmod("/proc/cmdline", 0440);
+        CHECKCALL(chmod("/proc/cmdline", 0440));
         gid_t groups[] = { AID_READPROC };
-        setgroups(arraysize(groups), groups);
-        mount("sysfs", "/sys", "sysfs", 0, NULL);
-        mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
+        CHECKCALL(setgroups(arraysize(groups), groups));
+        CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
+        CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
 
-        mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
+        CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
 
         if constexpr (WORLD_WRITABLE_KMSG) {
-            mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11));
+            CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
         }
 
-        mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
-        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
+        CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
+        CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
 
         // Mount staging areas for devices managed by vold
         // See storage config details at http://source.android.com/devices/storage/
-        mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
-              "mode=0755,uid=0,gid=1000");
+        CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
+                        "mode=0755,uid=0,gid=1000"));
         // /mnt/vendor is used to mount vendor-specific partitions that can not be
         // part of the vendor partition, e.g. because they are mounted read-write.
-        mkdir("/mnt/vendor", 0755);
+        CHECKCALL(mkdir("/mnt/vendor", 0755));
+
+#undef CHECKCALL
 
         // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
         // talk to the outside world...
         InitKernelLogging(argv);
 
+        if (!errors.empty()) {
+            for (const auto& [error_string, error_errno] : errors) {
+                LOG(ERROR) << error_string << " " << strerror(error_errno);
+            }
+            LOG(FATAL) << "Init encountered errors starting first stage, aborting";
+        }
+
         LOG(INFO) << "init first stage started!";
 
         if (!DoFirstStageMount()) {
diff --git a/init/parser.cpp b/init/parser.cpp
index ee6ee06..4f1cac4 100644
--- a/init/parser.cpp
+++ b/init/parser.cpp
@@ -70,24 +70,23 @@
             case T_EOF:
                 end_section();
                 return;
-            case T_NEWLINE:
+            case T_NEWLINE: {
                 state.line++;
                 if (args.empty()) break;
                 // If we have a line matching a prefix we recognize, call its callback and unset any
                 // current section parsers.  This is meant for /sys/ and /dev/ line entries for
                 // uevent.
-                for (const auto& [prefix, callback] : line_callbacks_) {
-                    if (android::base::StartsWith(args[0], prefix)) {
-                        end_section();
+                auto line_callback = std::find_if(
+                    line_callbacks_.begin(), line_callbacks_.end(),
+                    [&args](const auto& c) { return android::base::StartsWith(args[0], c.first); });
+                if (line_callback != line_callbacks_.end()) {
+                    end_section();
 
-                        if (auto result = callback(std::move(args)); !result) {
-                            parse_error_count_++;
-                            LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
-                        }
-                        break;
+                    if (auto result = line_callback->second(std::move(args)); !result) {
+                        parse_error_count_++;
+                        LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                     }
-                }
-                if (section_parsers_.count(args[0])) {
+                } else if (section_parsers_.count(args[0])) {
                     end_section();
                     section_parser = section_parsers_[args[0]].get();
                     section_start_line = state.line;
@@ -111,6 +110,7 @@
                 }
                 args.clear();
                 break;
+            }
             case T_TEXT:
                 args.emplace_back(state.text);
                 break;
diff --git a/libcutils/OWNERS b/libcutils/OWNERS
new file mode 100644
index 0000000..c18ed51
--- /dev/null
+++ b/libcutils/OWNERS
@@ -0,0 +1,4 @@
+cferris@google.com
+enh@google.com
+jmgao@google.com
+tomcherry@google.com
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 6169324..267b7b3 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -154,6 +154,7 @@
     { 00755, AID_ROOT,      AID_SHELL,     0, "system/bin/crash_dump64" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "system/bin/debuggerd" },
     { 00750, AID_ROOT,      AID_ROOT,      0, "system/bin/install-recovery.sh" },
+    { 00550, AID_LOGD,      AID_LOGD,      0, "system/bin/logd" },
     { 00700, AID_ROOT,      AID_ROOT,      0, "system/bin/secilc" },
     { 00750, AID_ROOT,      AID_ROOT,      0, "system/bin/uncrypt" },
     { 00600, AID_ROOT,      AID_ROOT,      0, "system/build.prop" },
@@ -179,12 +180,6 @@
     // in user builds.
     { 00700, AID_SYSTEM,    AID_SHELL,     CAP_MASK_LONG(CAP_BLOCK_SUSPEND),
                                               "system/bin/inputflinger" },
-    { 00550, AID_LOGD,      AID_LOGD,      CAP_MASK_LONG(CAP_SYSLOG) |
-                                           CAP_MASK_LONG(CAP_AUDIT_CONTROL) |
-                                           CAP_MASK_LONG(CAP_SETGID),
-                                              "system/bin/logd" },
-    { 00550, AID_SYSTEM,    AID_LOG,      CAP_MASK_LONG(CAP_SYSLOG),
-                                              "system/bin/bootstat" },
     { 00750, AID_ROOT,      AID_SHELL,     CAP_MASK_LONG(CAP_SETUID) |
                                            CAP_MASK_LONG(CAP_SETGID),
                                               "system/bin/run-as" },
diff --git a/libsparse/.clang-format b/libsparse/.clang-format
new file mode 120000
index 0000000..fd0645f
--- /dev/null
+++ b/libsparse/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2
\ No newline at end of file
diff --git a/libsparse/Android.bp b/libsparse/Android.bp
index c7c089f..8ad339f 100644
--- a/libsparse/Android.bp
+++ b/libsparse/Android.bp
@@ -6,11 +6,11 @@
     recovery_available: true,
     unique_host_soname: true,
     srcs: [
-        "backed_block.c",
-        "output_file.c",
-        "sparse.c",
-        "sparse_crc32.c",
-        "sparse_err.c",
+        "backed_block.cpp",
+        "output_file.cpp",
+        "sparse.cpp",
+        "sparse_crc32.cpp",
+        "sparse_err.cpp",
         "sparse_read.cpp",
     ],
     cflags: ["-Werror"],
@@ -31,8 +31,8 @@
     name: "simg2img",
     host_supported: true,
     srcs: [
-        "simg2img.c",
-        "sparse_crc32.c",
+        "simg2img.cpp",
+        "sparse_crc32.cpp",
     ],
     static_libs: [
         "libsparse",
@@ -46,7 +46,7 @@
 cc_binary {
     name: "img2simg",
     host_supported: true,
-    srcs: ["img2simg.c"],
+    srcs: ["img2simg.cpp"],
     static_libs: [
         "libsparse",
         "libz",
@@ -58,7 +58,7 @@
 
 cc_binary_host {
     name: "append2simg",
-    srcs: ["append2simg.c"],
+    srcs: ["append2simg.cpp"],
     static_libs: [
         "libsparse",
         "libz",
diff --git a/libsparse/OWNERS b/libsparse/OWNERS
new file mode 100644
index 0000000..70b375f
--- /dev/null
+++ b/libsparse/OWNERS
@@ -0,0 +1 @@
+ccross@google.com
diff --git a/libsparse/append2simg.c b/libsparse/append2simg.c
deleted file mode 100644
index eef8764..0000000
--- a/libsparse/append2simg.c
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define _FILE_OFFSET_BITS 64
-#define _LARGEFILE64_SOURCE 1
-#define _GNU_SOURCE
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <sparse/sparse.h>
-#include "sparse_file.h"
-#include "backed_block.h"
-
-#ifndef O_BINARY
-#define O_BINARY 0
-#endif
-
-#if defined(__APPLE__) && defined(__MACH__)
-#define lseek64 lseek
-#endif
-#if defined(__APPLE__) && defined(__MACH__)
-#define lseek64 lseek
-#define off64_t off_t
-#endif
-
-void usage()
-{
-    fprintf(stderr, "Usage: append2simg <output> <input>\n");
-}
-
-int main(int argc, char *argv[])
-{
-    int output;
-    int output_block;
-    char *output_path;
-    struct sparse_file *sparse_output;
-
-    int input;
-    char *input_path;
-    off64_t input_len;
-
-    int tmp_fd;
-    char *tmp_path;
-
-    int ret;
-
-    if (argc == 3) {
-        output_path = argv[1];
-        input_path = argv[2];
-    } else {
-        usage();
-        exit(-1);
-    }
-
-    ret = asprintf(&tmp_path, "%s.append2simg", output_path);
-    if (ret < 0) {
-        fprintf(stderr, "Couldn't allocate filename\n");
-        exit(-1);
-    }
-
-    output = open(output_path, O_RDWR | O_BINARY);
-    if (output < 0) {
-        fprintf(stderr, "Couldn't open output file (%s)\n", strerror(errno));
-        exit(-1);
-    }
-
-    sparse_output = sparse_file_import_auto(output, false, true);
-    if (!sparse_output) {
-        fprintf(stderr, "Couldn't import output file\n");
-        exit(-1);
-    }
-
-    input = open(input_path, O_RDONLY | O_BINARY);
-    if (input < 0) {
-        fprintf(stderr, "Couldn't open input file (%s)\n", strerror(errno));
-        exit(-1);
-    }
-
-    input_len = lseek64(input, 0, SEEK_END);
-    if (input_len < 0) {
-        fprintf(stderr, "Couldn't get input file length (%s)\n", strerror(errno));
-        exit(-1);
-    } else if (input_len % sparse_output->block_size) {
-        fprintf(stderr, "Input file is not a multiple of the output file's block size");
-        exit(-1);
-    }
-    lseek64(input, 0, SEEK_SET);
-
-    output_block = sparse_output->len / sparse_output->block_size;
-    if (sparse_file_add_fd(sparse_output, input, 0, input_len, output_block) < 0) {
-        fprintf(stderr, "Couldn't add input file\n");
-        exit(-1);
-    }
-    sparse_output->len += input_len;
-
-    tmp_fd = open(tmp_path, O_WRONLY | O_CREAT | O_BINARY, 0664);
-    if (tmp_fd < 0) {
-        fprintf(stderr, "Couldn't open temporary file (%s)\n", strerror(errno));
-        exit(-1);
-    }
-
-    lseek64(output, 0, SEEK_SET);
-    if (sparse_file_write(sparse_output, tmp_fd, false, true, false) < 0) {
-        fprintf(stderr, "Failed to write sparse file\n");
-        exit(-1);
-    }
-
-    sparse_file_destroy(sparse_output);
-    close(tmp_fd);
-    close(output);
-    close(input);
-
-    ret = rename(tmp_path, output_path);
-    if (ret < 0) {
-        fprintf(stderr, "Failed to rename temporary file (%s)\n", strerror(errno));
-        exit(-1);
-    }
-
-    free(tmp_path);
-
-    exit(0);
-}
diff --git a/libsparse/append2simg.cpp b/libsparse/append2simg.cpp
new file mode 100644
index 0000000..99f4339
--- /dev/null
+++ b/libsparse/append2simg.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _FILE_OFFSET_BITS 64
+#define _LARGEFILE64_SOURCE 1
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sparse/sparse.h>
+#include "backed_block.h"
+#include "sparse_file.h"
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+#if defined(__APPLE__) && defined(__MACH__)
+#define lseek64 lseek
+#endif
+#if defined(__APPLE__) && defined(__MACH__)
+#define lseek64 lseek
+#define off64_t off_t
+#endif
+
+void usage() {
+  fprintf(stderr, "Usage: append2simg <output> <input>\n");
+}
+
+int main(int argc, char* argv[]) {
+  int output;
+  int output_block;
+  char* output_path;
+  struct sparse_file* sparse_output;
+
+  int input;
+  char* input_path;
+  off64_t input_len;
+
+  int tmp_fd;
+  char* tmp_path;
+
+  int ret;
+
+  if (argc == 3) {
+    output_path = argv[1];
+    input_path = argv[2];
+  } else {
+    usage();
+    exit(-1);
+  }
+
+  ret = asprintf(&tmp_path, "%s.append2simg", output_path);
+  if (ret < 0) {
+    fprintf(stderr, "Couldn't allocate filename\n");
+    exit(-1);
+  }
+
+  output = open(output_path, O_RDWR | O_BINARY);
+  if (output < 0) {
+    fprintf(stderr, "Couldn't open output file (%s)\n", strerror(errno));
+    exit(-1);
+  }
+
+  sparse_output = sparse_file_import_auto(output, false, true);
+  if (!sparse_output) {
+    fprintf(stderr, "Couldn't import output file\n");
+    exit(-1);
+  }
+
+  input = open(input_path, O_RDONLY | O_BINARY);
+  if (input < 0) {
+    fprintf(stderr, "Couldn't open input file (%s)\n", strerror(errno));
+    exit(-1);
+  }
+
+  input_len = lseek64(input, 0, SEEK_END);
+  if (input_len < 0) {
+    fprintf(stderr, "Couldn't get input file length (%s)\n", strerror(errno));
+    exit(-1);
+  } else if (input_len % sparse_output->block_size) {
+    fprintf(stderr, "Input file is not a multiple of the output file's block size");
+    exit(-1);
+  }
+  lseek64(input, 0, SEEK_SET);
+
+  output_block = sparse_output->len / sparse_output->block_size;
+  if (sparse_file_add_fd(sparse_output, input, 0, input_len, output_block) < 0) {
+    fprintf(stderr, "Couldn't add input file\n");
+    exit(-1);
+  }
+  sparse_output->len += input_len;
+
+  tmp_fd = open(tmp_path, O_WRONLY | O_CREAT | O_BINARY, 0664);
+  if (tmp_fd < 0) {
+    fprintf(stderr, "Couldn't open temporary file (%s)\n", strerror(errno));
+    exit(-1);
+  }
+
+  lseek64(output, 0, SEEK_SET);
+  if (sparse_file_write(sparse_output, tmp_fd, false, true, false) < 0) {
+    fprintf(stderr, "Failed to write sparse file\n");
+    exit(-1);
+  }
+
+  sparse_file_destroy(sparse_output);
+  close(tmp_fd);
+  close(output);
+  close(input);
+
+  ret = rename(tmp_path, output_path);
+  if (ret < 0) {
+    fprintf(stderr, "Failed to rename temporary file (%s)\n", strerror(errno));
+    exit(-1);
+  }
+
+  free(tmp_path);
+
+  exit(0);
+}
diff --git a/libsparse/backed_block.c b/libsparse/backed_block.c
deleted file mode 100644
index 794cd6b..0000000
--- a/libsparse/backed_block.c
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright (C) 2010 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 <assert.h>
-#include <errno.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "backed_block.h"
-#include "sparse_defs.h"
-
-struct backed_block {
-	unsigned int block;
-	unsigned int len;
-	enum backed_block_type type;
-	union {
-		struct {
-			void *data;
-		} data;
-		struct {
-			char *filename;
-			int64_t offset;
-		} file;
-		struct {
-			int fd;
-			int64_t offset;
-		} fd;
-		struct {
-			uint32_t val;
-		} fill;
-	};
-	struct backed_block *next;
-};
-
-struct backed_block_list {
-	struct backed_block *data_blocks;
-	struct backed_block *last_used;
-	unsigned int block_size;
-};
-
-struct backed_block *backed_block_iter_new(struct backed_block_list *bbl)
-{
-	return bbl->data_blocks;
-}
-
-struct backed_block *backed_block_iter_next(struct backed_block *bb)
-{
-	return bb->next;
-}
-
-unsigned int backed_block_len(struct backed_block *bb)
-{
-	return bb->len;
-}
-
-unsigned int backed_block_block(struct backed_block *bb)
-{
-	return bb->block;
-}
-
-void *backed_block_data(struct backed_block *bb)
-{
-	assert(bb->type == BACKED_BLOCK_DATA);
-	return bb->data.data;
-}
-
-const char *backed_block_filename(struct backed_block *bb)
-{
-	assert(bb->type == BACKED_BLOCK_FILE);
-	return bb->file.filename;
-}
-
-int backed_block_fd(struct backed_block *bb)
-{
-	assert(bb->type == BACKED_BLOCK_FD);
-	return bb->fd.fd;
-}
-
-int64_t backed_block_file_offset(struct backed_block *bb)
-{
-	assert(bb->type == BACKED_BLOCK_FILE || bb->type == BACKED_BLOCK_FD);
-	if (bb->type == BACKED_BLOCK_FILE) {
-		return bb->file.offset;
-	} else { /* bb->type == BACKED_BLOCK_FD */
-		return bb->fd.offset;
-	}
-}
-
-uint32_t backed_block_fill_val(struct backed_block *bb)
-{
-	assert(bb->type == BACKED_BLOCK_FILL);
-	return bb->fill.val;
-}
-
-enum backed_block_type backed_block_type(struct backed_block *bb)
-{
-	return bb->type;
-}
-
-void backed_block_destroy(struct backed_block *bb)
-{
-	if (bb->type == BACKED_BLOCK_FILE) {
-		free(bb->file.filename);
-	}
-
-	free(bb);
-}
-
-struct backed_block_list *backed_block_list_new(unsigned int block_size)
-{
-	struct backed_block_list *b = calloc(sizeof(struct backed_block_list), 1);
-	b->block_size = block_size;
-	return b;
-}
-
-void backed_block_list_destroy(struct backed_block_list *bbl)
-{
-	if (bbl->data_blocks) {
-		struct backed_block *bb = bbl->data_blocks;
-		while (bb) {
-			struct backed_block *next = bb->next;
-			backed_block_destroy(bb);
-			bb = next;
-		}
-	}
-
-	free(bbl);
-}
-
-void backed_block_list_move(struct backed_block_list *from,
-		struct backed_block_list *to, struct backed_block *start,
-		struct backed_block *end)
-{
-	struct backed_block *bb;
-
-	if (start == NULL) {
-		start = from->data_blocks;
-	}
-
-	if (!end) {
-		for (end = start; end && end->next; end = end->next)
-			;
-	}
-
-	if (start == NULL || end == NULL) {
-		return;
-	}
-
-	from->last_used = NULL;
-	to->last_used = NULL;
-	if (from->data_blocks == start) {
-		from->data_blocks = end->next;
-	} else {
-		for (bb = from->data_blocks; bb; bb = bb->next) {
-			if (bb->next == start) {
-				bb->next = end->next;
-				break;
-			}
-		}
-	}
-
-	if (!to->data_blocks) {
-		to->data_blocks = start;
-		end->next = NULL;
-	} else {
-		for (bb = to->data_blocks; bb; bb = bb->next) {
-			if (!bb->next || bb->next->block > start->block) {
-				end->next = bb->next;
-				bb->next = start;
-				break;
-			}
-		}
-	}
-}
-
-/* may free b */
-static int merge_bb(struct backed_block_list *bbl,
-		struct backed_block *a, struct backed_block *b)
-{
-	unsigned int block_len;
-
-	/* Block doesn't exist (possible if one block is the last block) */
-	if (!a || !b) {
-		return -EINVAL;
-	}
-
-	assert(a->block < b->block);
-
-	/* Blocks are of different types */
-	if (a->type != b->type) {
-		return -EINVAL;
-	}
-
-	/* Blocks are not adjacent */
-	block_len = a->len / bbl->block_size; /* rounds down */
-	if (a->block + block_len != b->block) {
-		return -EINVAL;
-	}
-
-	switch (a->type) {
-	case BACKED_BLOCK_DATA:
-		/* Don't support merging data for now */
-		return -EINVAL;
-	case BACKED_BLOCK_FILL:
-		if (a->fill.val != b->fill.val) {
-			return -EINVAL;
-		}
-		break;
-	case BACKED_BLOCK_FILE:
-		/* Already make sure b->type is BACKED_BLOCK_FILE */
-		if (strcmp(a->file.filename, b->file.filename) ||
-				a->file.offset + a->len != b->file.offset) {
-			return -EINVAL;
-		}
-		break;
-	case BACKED_BLOCK_FD:
-		if (a->fd.fd != b->fd.fd ||
-				a->fd.offset + a->len != b->fd.offset) {
-			return -EINVAL;
-		}
-		break;
-	}
-
-	/* Blocks are compatible and adjacent, with a before b.  Merge b into a,
-	 * and free b */
-	a->len += b->len;
-	a->next = b->next;
-
-	backed_block_destroy(b);
-
-	return 0;
-}
-
-static int queue_bb(struct backed_block_list *bbl, struct backed_block *new_bb)
-{
-	struct backed_block *bb;
-
-	if (bbl->data_blocks == NULL) {
-		bbl->data_blocks = new_bb;
-		return 0;
-	}
-
-	if (bbl->data_blocks->block > new_bb->block) {
-		new_bb->next = bbl->data_blocks;
-		bbl->data_blocks = new_bb;
-		return 0;
-	}
-
-	/* Optimization: blocks are mostly queued in sequence, so save the
-	   pointer to the last bb that was added, and start searching from
-	   there if the next block number is higher */
-	if (bbl->last_used && new_bb->block > bbl->last_used->block)
-		bb = bbl->last_used;
-	else
-		bb = bbl->data_blocks;
-	bbl->last_used = new_bb;
-
-	for (; bb->next && bb->next->block < new_bb->block; bb = bb->next)
-		;
-
-	if (bb->next == NULL) {
-		bb->next = new_bb;
-	} else {
-		new_bb->next = bb->next;
-		bb->next = new_bb;
-	}
-
-	merge_bb(bbl, new_bb, new_bb->next);
-	if (!merge_bb(bbl, bb, new_bb)) {
-		/* new_bb destroyed, point to retained as last_used */
-		bbl->last_used = bb;
-	}
-
-	return 0;
-}
-
-/* Queues a fill block of memory to be written to the specified data blocks */
-int backed_block_add_fill(struct backed_block_list *bbl, unsigned int fill_val,
-		unsigned int len, unsigned int block)
-{
-	struct backed_block *bb = calloc(1, sizeof(struct backed_block));
-	if (bb == NULL) {
-		return -ENOMEM;
-	}
-
-	bb->block = block;
-	bb->len = len;
-	bb->type = BACKED_BLOCK_FILL;
-	bb->fill.val = fill_val;
-	bb->next = NULL;
-
-	return queue_bb(bbl, bb);
-}
-
-/* Queues a block of memory to be written to the specified data blocks */
-int backed_block_add_data(struct backed_block_list *bbl, void *data,
-		unsigned int len, unsigned int block)
-{
-	struct backed_block *bb = calloc(1, sizeof(struct backed_block));
-	if (bb == NULL) {
-		return -ENOMEM;
-	}
-
-	bb->block = block;
-	bb->len = len;
-	bb->type = BACKED_BLOCK_DATA;
-	bb->data.data = data;
-	bb->next = NULL;
-
-	return queue_bb(bbl, bb);
-}
-
-/* Queues a chunk of a file on disk to be written to the specified data blocks */
-int backed_block_add_file(struct backed_block_list *bbl, const char *filename,
-		int64_t offset, unsigned int len, unsigned int block)
-{
-	struct backed_block *bb = calloc(1, sizeof(struct backed_block));
-	if (bb == NULL) {
-		return -ENOMEM;
-	}
-
-	bb->block = block;
-	bb->len = len;
-	bb->type = BACKED_BLOCK_FILE;
-	bb->file.filename = strdup(filename);
-	bb->file.offset = offset;
-	bb->next = NULL;
-
-	return queue_bb(bbl, bb);
-}
-
-/* Queues a chunk of a fd to be written to the specified data blocks */
-int backed_block_add_fd(struct backed_block_list *bbl, int fd, int64_t offset,
-		unsigned int len, unsigned int block)
-{
-	struct backed_block *bb = calloc(1, sizeof(struct backed_block));
-	if (bb == NULL) {
-		return -ENOMEM;
-	}
-
-	bb->block = block;
-	bb->len = len;
-	bb->type = BACKED_BLOCK_FD;
-	bb->fd.fd = fd;
-	bb->fd.offset = offset;
-	bb->next = NULL;
-
-	return queue_bb(bbl, bb);
-}
-
-int backed_block_split(struct backed_block_list *bbl, struct backed_block *bb,
-		unsigned int max_len)
-{
-	struct backed_block *new_bb;
-
-	max_len = ALIGN_DOWN(max_len, bbl->block_size);
-
-	if (bb->len <= max_len) {
-		return 0;
-	}
-
-	new_bb = malloc(sizeof(struct backed_block));
-	if (new_bb == NULL) {
-		return -ENOMEM;
-	}
-
-	*new_bb = *bb;
-
-	new_bb->len = bb->len - max_len;
-	new_bb->block = bb->block + max_len / bbl->block_size;
-	new_bb->next = bb->next;
-	bb->next = new_bb;
-	bb->len = max_len;
-
-	switch (bb->type) {
-	case BACKED_BLOCK_DATA:
-		new_bb->data.data = (char *)bb->data.data + max_len;
-		break;
-	case BACKED_BLOCK_FILE:
-		new_bb->file.offset += max_len;
-		break;
-	case BACKED_BLOCK_FD:
-		new_bb->fd.offset += max_len;
-		break;
-	case BACKED_BLOCK_FILL:
-		break;
-	}
-
-	return 0;
-}
diff --git a/libsparse/backed_block.cpp b/libsparse/backed_block.cpp
new file mode 100644
index 0000000..7f5632e
--- /dev/null
+++ b/libsparse/backed_block.cpp
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2010 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 <assert.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "backed_block.h"
+#include "sparse_defs.h"
+
+struct backed_block {
+  unsigned int block;
+  unsigned int len;
+  enum backed_block_type type;
+  union {
+    struct {
+      void* data;
+    } data;
+    struct {
+      char* filename;
+      int64_t offset;
+    } file;
+    struct {
+      int fd;
+      int64_t offset;
+    } fd;
+    struct {
+      uint32_t val;
+    } fill;
+  };
+  struct backed_block* next;
+};
+
+struct backed_block_list {
+  struct backed_block* data_blocks;
+  struct backed_block* last_used;
+  unsigned int block_size;
+};
+
+struct backed_block* backed_block_iter_new(struct backed_block_list* bbl) {
+  return bbl->data_blocks;
+}
+
+struct backed_block* backed_block_iter_next(struct backed_block* bb) {
+  return bb->next;
+}
+
+unsigned int backed_block_len(struct backed_block* bb) {
+  return bb->len;
+}
+
+unsigned int backed_block_block(struct backed_block* bb) {
+  return bb->block;
+}
+
+void* backed_block_data(struct backed_block* bb) {
+  assert(bb->type == BACKED_BLOCK_DATA);
+  return bb->data.data;
+}
+
+const char* backed_block_filename(struct backed_block* bb) {
+  assert(bb->type == BACKED_BLOCK_FILE);
+  return bb->file.filename;
+}
+
+int backed_block_fd(struct backed_block* bb) {
+  assert(bb->type == BACKED_BLOCK_FD);
+  return bb->fd.fd;
+}
+
+int64_t backed_block_file_offset(struct backed_block* bb) {
+  assert(bb->type == BACKED_BLOCK_FILE || bb->type == BACKED_BLOCK_FD);
+  if (bb->type == BACKED_BLOCK_FILE) {
+    return bb->file.offset;
+  } else { /* bb->type == BACKED_BLOCK_FD */
+    return bb->fd.offset;
+  }
+}
+
+uint32_t backed_block_fill_val(struct backed_block* bb) {
+  assert(bb->type == BACKED_BLOCK_FILL);
+  return bb->fill.val;
+}
+
+enum backed_block_type backed_block_type(struct backed_block* bb) {
+  return bb->type;
+}
+
+void backed_block_destroy(struct backed_block* bb) {
+  if (bb->type == BACKED_BLOCK_FILE) {
+    free(bb->file.filename);
+  }
+
+  free(bb);
+}
+
+struct backed_block_list* backed_block_list_new(unsigned int block_size) {
+  struct backed_block_list* b =
+      reinterpret_cast<backed_block_list*>(calloc(sizeof(struct backed_block_list), 1));
+  b->block_size = block_size;
+  return b;
+}
+
+void backed_block_list_destroy(struct backed_block_list* bbl) {
+  if (bbl->data_blocks) {
+    struct backed_block* bb = bbl->data_blocks;
+    while (bb) {
+      struct backed_block* next = bb->next;
+      backed_block_destroy(bb);
+      bb = next;
+    }
+  }
+
+  free(bbl);
+}
+
+void backed_block_list_move(struct backed_block_list* from, struct backed_block_list* to,
+                            struct backed_block* start, struct backed_block* end) {
+  struct backed_block* bb;
+
+  if (start == NULL) {
+    start = from->data_blocks;
+  }
+
+  if (!end) {
+    for (end = start; end && end->next; end = end->next)
+      ;
+  }
+
+  if (start == NULL || end == NULL) {
+    return;
+  }
+
+  from->last_used = NULL;
+  to->last_used = NULL;
+  if (from->data_blocks == start) {
+    from->data_blocks = end->next;
+  } else {
+    for (bb = from->data_blocks; bb; bb = bb->next) {
+      if (bb->next == start) {
+        bb->next = end->next;
+        break;
+      }
+    }
+  }
+
+  if (!to->data_blocks) {
+    to->data_blocks = start;
+    end->next = NULL;
+  } else {
+    for (bb = to->data_blocks; bb; bb = bb->next) {
+      if (!bb->next || bb->next->block > start->block) {
+        end->next = bb->next;
+        bb->next = start;
+        break;
+      }
+    }
+  }
+}
+
+/* may free b */
+static int merge_bb(struct backed_block_list* bbl, struct backed_block* a, struct backed_block* b) {
+  unsigned int block_len;
+
+  /* Block doesn't exist (possible if one block is the last block) */
+  if (!a || !b) {
+    return -EINVAL;
+  }
+
+  assert(a->block < b->block);
+
+  /* Blocks are of different types */
+  if (a->type != b->type) {
+    return -EINVAL;
+  }
+
+  /* Blocks are not adjacent */
+  block_len = a->len / bbl->block_size; /* rounds down */
+  if (a->block + block_len != b->block) {
+    return -EINVAL;
+  }
+
+  switch (a->type) {
+    case BACKED_BLOCK_DATA:
+      /* Don't support merging data for now */
+      return -EINVAL;
+    case BACKED_BLOCK_FILL:
+      if (a->fill.val != b->fill.val) {
+        return -EINVAL;
+      }
+      break;
+    case BACKED_BLOCK_FILE:
+      /* Already make sure b->type is BACKED_BLOCK_FILE */
+      if (strcmp(a->file.filename, b->file.filename) || a->file.offset + a->len != b->file.offset) {
+        return -EINVAL;
+      }
+      break;
+    case BACKED_BLOCK_FD:
+      if (a->fd.fd != b->fd.fd || a->fd.offset + a->len != b->fd.offset) {
+        return -EINVAL;
+      }
+      break;
+  }
+
+  /* Blocks are compatible and adjacent, with a before b.  Merge b into a,
+   * and free b */
+  a->len += b->len;
+  a->next = b->next;
+
+  backed_block_destroy(b);
+
+  return 0;
+}
+
+static int queue_bb(struct backed_block_list* bbl, struct backed_block* new_bb) {
+  struct backed_block* bb;
+
+  if (bbl->data_blocks == NULL) {
+    bbl->data_blocks = new_bb;
+    return 0;
+  }
+
+  if (bbl->data_blocks->block > new_bb->block) {
+    new_bb->next = bbl->data_blocks;
+    bbl->data_blocks = new_bb;
+    return 0;
+  }
+
+  /* Optimization: blocks are mostly queued in sequence, so save the
+     pointer to the last bb that was added, and start searching from
+     there if the next block number is higher */
+  if (bbl->last_used && new_bb->block > bbl->last_used->block)
+    bb = bbl->last_used;
+  else
+    bb = bbl->data_blocks;
+  bbl->last_used = new_bb;
+
+  for (; bb->next && bb->next->block < new_bb->block; bb = bb->next)
+    ;
+
+  if (bb->next == NULL) {
+    bb->next = new_bb;
+  } else {
+    new_bb->next = bb->next;
+    bb->next = new_bb;
+  }
+
+  merge_bb(bbl, new_bb, new_bb->next);
+  if (!merge_bb(bbl, bb, new_bb)) {
+    /* new_bb destroyed, point to retained as last_used */
+    bbl->last_used = bb;
+  }
+
+  return 0;
+}
+
+/* Queues a fill block of memory to be written to the specified data blocks */
+int backed_block_add_fill(struct backed_block_list* bbl, unsigned int fill_val, unsigned int len,
+                          unsigned int block) {
+  struct backed_block* bb = reinterpret_cast<backed_block*>(calloc(1, sizeof(struct backed_block)));
+  if (bb == NULL) {
+    return -ENOMEM;
+  }
+
+  bb->block = block;
+  bb->len = len;
+  bb->type = BACKED_BLOCK_FILL;
+  bb->fill.val = fill_val;
+  bb->next = NULL;
+
+  return queue_bb(bbl, bb);
+}
+
+/* Queues a block of memory to be written to the specified data blocks */
+int backed_block_add_data(struct backed_block_list* bbl, void* data, unsigned int len,
+                          unsigned int block) {
+  struct backed_block* bb = reinterpret_cast<backed_block*>(calloc(1, sizeof(struct backed_block)));
+  if (bb == NULL) {
+    return -ENOMEM;
+  }
+
+  bb->block = block;
+  bb->len = len;
+  bb->type = BACKED_BLOCK_DATA;
+  bb->data.data = data;
+  bb->next = NULL;
+
+  return queue_bb(bbl, bb);
+}
+
+/* Queues a chunk of a file on disk to be written to the specified data blocks */
+int backed_block_add_file(struct backed_block_list* bbl, const char* filename, int64_t offset,
+                          unsigned int len, unsigned int block) {
+  struct backed_block* bb = reinterpret_cast<backed_block*>(calloc(1, sizeof(struct backed_block)));
+  if (bb == NULL) {
+    return -ENOMEM;
+  }
+
+  bb->block = block;
+  bb->len = len;
+  bb->type = BACKED_BLOCK_FILE;
+  bb->file.filename = strdup(filename);
+  bb->file.offset = offset;
+  bb->next = NULL;
+
+  return queue_bb(bbl, bb);
+}
+
+/* Queues a chunk of a fd to be written to the specified data blocks */
+int backed_block_add_fd(struct backed_block_list* bbl, int fd, int64_t offset, unsigned int len,
+                        unsigned int block) {
+  struct backed_block* bb = reinterpret_cast<backed_block*>(calloc(1, sizeof(struct backed_block)));
+  if (bb == NULL) {
+    return -ENOMEM;
+  }
+
+  bb->block = block;
+  bb->len = len;
+  bb->type = BACKED_BLOCK_FD;
+  bb->fd.fd = fd;
+  bb->fd.offset = offset;
+  bb->next = NULL;
+
+  return queue_bb(bbl, bb);
+}
+
+int backed_block_split(struct backed_block_list* bbl, struct backed_block* bb,
+                       unsigned int max_len) {
+  struct backed_block* new_bb;
+
+  max_len = ALIGN_DOWN(max_len, bbl->block_size);
+
+  if (bb->len <= max_len) {
+    return 0;
+  }
+
+  new_bb = reinterpret_cast<backed_block*>(malloc(sizeof(struct backed_block)));
+  if (new_bb == NULL) {
+    return -ENOMEM;
+  }
+
+  *new_bb = *bb;
+
+  new_bb->len = bb->len - max_len;
+  new_bb->block = bb->block + max_len / bbl->block_size;
+  new_bb->next = bb->next;
+  bb->next = new_bb;
+  bb->len = max_len;
+
+  switch (bb->type) {
+    case BACKED_BLOCK_DATA:
+      new_bb->data.data = (char*)bb->data.data + max_len;
+      break;
+    case BACKED_BLOCK_FILE:
+      new_bb->file.offset += max_len;
+      break;
+    case BACKED_BLOCK_FD:
+      new_bb->fd.offset += max_len;
+      break;
+    case BACKED_BLOCK_FILL:
+      break;
+  }
+
+  return 0;
+}
diff --git a/libsparse/backed_block.h b/libsparse/backed_block.h
index 1a159be..3a75460 100644
--- a/libsparse/backed_block.h
+++ b/libsparse/backed_block.h
@@ -23,42 +23,40 @@
 struct backed_block;
 
 enum backed_block_type {
-	BACKED_BLOCK_DATA,
-	BACKED_BLOCK_FILE,
-	BACKED_BLOCK_FD,
-	BACKED_BLOCK_FILL,
+  BACKED_BLOCK_DATA,
+  BACKED_BLOCK_FILE,
+  BACKED_BLOCK_FD,
+  BACKED_BLOCK_FILL,
 };
 
-int backed_block_add_data(struct backed_block_list *bbl, void *data,
-		unsigned int len, unsigned int block);
-int backed_block_add_fill(struct backed_block_list *bbl, unsigned int fill_val,
-		unsigned int len, unsigned int block);
-int backed_block_add_file(struct backed_block_list *bbl, const char *filename,
-		int64_t offset, unsigned int len, unsigned int block);
-int backed_block_add_fd(struct backed_block_list *bbl, int fd,
-		int64_t offset, unsigned int len, unsigned int block);
+int backed_block_add_data(struct backed_block_list* bbl, void* data, unsigned int len,
+                          unsigned int block);
+int backed_block_add_fill(struct backed_block_list* bbl, unsigned int fill_val, unsigned int len,
+                          unsigned int block);
+int backed_block_add_file(struct backed_block_list* bbl, const char* filename, int64_t offset,
+                          unsigned int len, unsigned int block);
+int backed_block_add_fd(struct backed_block_list* bbl, int fd, int64_t offset, unsigned int len,
+                        unsigned int block);
 
-struct backed_block *backed_block_iter_new(struct backed_block_list *bbl);
-struct backed_block *backed_block_iter_next(struct backed_block *bb);
-unsigned int backed_block_len(struct backed_block *bb);
-unsigned int backed_block_block(struct backed_block *bb);
-void *backed_block_data(struct backed_block *bb);
-const char *backed_block_filename(struct backed_block *bb);
-int backed_block_fd(struct backed_block *bb);
-int64_t backed_block_file_offset(struct backed_block *bb);
-uint32_t backed_block_fill_val(struct backed_block *bb);
-enum backed_block_type backed_block_type(struct backed_block *bb);
-int backed_block_split(struct backed_block_list *bbl, struct backed_block *bb,
-		unsigned int max_len);
+struct backed_block* backed_block_iter_new(struct backed_block_list* bbl);
+struct backed_block* backed_block_iter_next(struct backed_block* bb);
+unsigned int backed_block_len(struct backed_block* bb);
+unsigned int backed_block_block(struct backed_block* bb);
+void* backed_block_data(struct backed_block* bb);
+const char* backed_block_filename(struct backed_block* bb);
+int backed_block_fd(struct backed_block* bb);
+int64_t backed_block_file_offset(struct backed_block* bb);
+uint32_t backed_block_fill_val(struct backed_block* bb);
+enum backed_block_type backed_block_type(struct backed_block* bb);
+int backed_block_split(struct backed_block_list* bbl, struct backed_block* bb, unsigned int max_len);
 
-struct backed_block *backed_block_iter_new(struct backed_block_list *bbl);
-struct backed_block *backed_block_iter_next(struct backed_block *bb);
+struct backed_block* backed_block_iter_new(struct backed_block_list* bbl);
+struct backed_block* backed_block_iter_next(struct backed_block* bb);
 
-struct backed_block_list *backed_block_list_new(unsigned int block_size);
-void backed_block_list_destroy(struct backed_block_list *bbl);
+struct backed_block_list* backed_block_list_new(unsigned int block_size);
+void backed_block_list_destroy(struct backed_block_list* bbl);
 
-void backed_block_list_move(struct backed_block_list *from,
-		struct backed_block_list *to, struct backed_block *start,
-		struct backed_block *end);
+void backed_block_list_move(struct backed_block_list* from, struct backed_block_list* to,
+                            struct backed_block* start, struct backed_block* end);
 
 #endif
diff --git a/libsparse/defs.h b/libsparse/defs.h
index 34e63c5..28e5cab 100644
--- a/libsparse/defs.h
+++ b/libsparse/defs.h
@@ -17,7 +17,7 @@
 #ifndef _LIBSPARSE_DEFS_H_
 
 #ifndef __unused
-#define __unused        __attribute__((__unused__))
+#define __unused __attribute__((__unused__))
 #endif
 
 #endif /* _LIBSPARSE_DEFS_H_ */
diff --git a/libsparse/img2simg.c b/libsparse/img2simg.c
deleted file mode 100644
index a0db36f..0000000
--- a/libsparse/img2simg.c
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define _FILE_OFFSET_BITS 64
-#define _LARGEFILE64_SOURCE 1
-
-#include <fcntl.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <sparse/sparse.h>
-
-#ifndef O_BINARY
-#define O_BINARY 0
-#endif
-
-#if defined(__APPLE__) && defined(__MACH__)
-#define lseek64 lseek
-#define off64_t off_t
-#endif
-
-void usage()
-{
-    fprintf(stderr, "Usage: img2simg <raw_image_file> <sparse_image_file> [<block_size>]\n");
-}
-
-int main(int argc, char *argv[])
-{
-	int in;
-	int out;
-	int ret;
-	struct sparse_file *s;
-	unsigned int block_size = 4096;
-	off64_t len;
-
-	if (argc < 3 || argc > 4) {
-		usage();
-		exit(-1);
-	}
-
-	if (argc == 4) {
-		block_size = atoi(argv[3]);
-	}
-
-	if (block_size < 1024 || block_size % 4 != 0) {
-		usage();
-		exit(-1);
-	}
-
-	if (strcmp(argv[1], "-") == 0) {
-		in = STDIN_FILENO;
-	} else {
-		in = open(argv[1], O_RDONLY | O_BINARY);
-		if (in < 0) {
-			fprintf(stderr, "Cannot open input file %s\n", argv[1]);
-			exit(-1);
-		}
-	}
-
-	if (strcmp(argv[2], "-") == 0) {
-		out = STDOUT_FILENO;
-	} else {
-		out = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664);
-		if (out < 0) {
-			fprintf(stderr, "Cannot open output file %s\n", argv[2]);
-			exit(-1);
-		}
-	}
-
-	len = lseek64(in, 0, SEEK_END);
-	lseek64(in, 0, SEEK_SET);
-
-	s = sparse_file_new(block_size, len);
-	if (!s) {
-		fprintf(stderr, "Failed to create sparse file\n");
-		exit(-1);
-	}
-
-	sparse_file_verbose(s);
-	ret = sparse_file_read(s, in, false, false);
-	if (ret) {
-		fprintf(stderr, "Failed to read file\n");
-		exit(-1);
-	}
-
-	ret = sparse_file_write(s, out, false, true, false);
-	if (ret) {
-		fprintf(stderr, "Failed to write sparse file\n");
-		exit(-1);
-	}
-
-	close(in);
-	close(out);
-
-	exit(0);
-}
diff --git a/libsparse/img2simg.cpp b/libsparse/img2simg.cpp
new file mode 100644
index 0000000..4c2c6ca
--- /dev/null
+++ b/libsparse/img2simg.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _FILE_OFFSET_BITS 64
+#define _LARGEFILE64_SOURCE 1
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <sparse/sparse.h>
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+#if defined(__APPLE__) && defined(__MACH__)
+#define lseek64 lseek
+#define off64_t off_t
+#endif
+
+void usage() {
+  fprintf(stderr, "Usage: img2simg <raw_image_file> <sparse_image_file> [<block_size>]\n");
+}
+
+int main(int argc, char* argv[]) {
+  int in;
+  int out;
+  int ret;
+  struct sparse_file* s;
+  unsigned int block_size = 4096;
+  off64_t len;
+
+  if (argc < 3 || argc > 4) {
+    usage();
+    exit(-1);
+  }
+
+  if (argc == 4) {
+    block_size = atoi(argv[3]);
+  }
+
+  if (block_size < 1024 || block_size % 4 != 0) {
+    usage();
+    exit(-1);
+  }
+
+  if (strcmp(argv[1], "-") == 0) {
+    in = STDIN_FILENO;
+  } else {
+    in = open(argv[1], O_RDONLY | O_BINARY);
+    if (in < 0) {
+      fprintf(stderr, "Cannot open input file %s\n", argv[1]);
+      exit(-1);
+    }
+  }
+
+  if (strcmp(argv[2], "-") == 0) {
+    out = STDOUT_FILENO;
+  } else {
+    out = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664);
+    if (out < 0) {
+      fprintf(stderr, "Cannot open output file %s\n", argv[2]);
+      exit(-1);
+    }
+  }
+
+  len = lseek64(in, 0, SEEK_END);
+  lseek64(in, 0, SEEK_SET);
+
+  s = sparse_file_new(block_size, len);
+  if (!s) {
+    fprintf(stderr, "Failed to create sparse file\n");
+    exit(-1);
+  }
+
+  sparse_file_verbose(s);
+  ret = sparse_file_read(s, in, false, false);
+  if (ret) {
+    fprintf(stderr, "Failed to read file\n");
+    exit(-1);
+  }
+
+  ret = sparse_file_write(s, out, false, true, false);
+  if (ret) {
+    fprintf(stderr, "Failed to write sparse file\n");
+    exit(-1);
+  }
+
+  close(in);
+  close(out);
+
+  exit(0);
+}
diff --git a/libsparse/include/sparse/sparse.h b/libsparse/include/sparse/sparse.h
index 1b91ead..3d5fb0c 100644
--- a/libsparse/include/sparse/sparse.h
+++ b/libsparse/include/sparse/sparse.h
@@ -246,9 +246,24 @@
 int sparse_file_read(struct sparse_file *s, int fd, bool sparse, bool crc);
 
 /**
- * sparse_file_import - import an existing sparse file
+ * sparse_file_read_buf - read a buffer into a sparse file cookie
  *
  * @s - sparse file cookie
+ * @buf - buffer to read from
+ * @crc - verify the crc of a file in the Android sparse file format
+ *
+ * Reads a buffer into a sparse file cookie. The buffer must remain
+ * valid until the sparse file cookie is freed. If crc is true, the
+ * crc of the sparse file will be verified.
+ *
+ * Returns 0 on success, negative errno on error.
+ */
+int sparse_file_read_buf(struct sparse_file *s, char *buf, bool crc);
+
+/**
+ * sparse_file_import - import an existing sparse file
+ *
+ * @fd - file descriptor to read from
  * @verbose - print verbose errors while reading the sparse file
  * @crc - verify the crc of a file in the Android sparse file format
  *
@@ -261,6 +276,21 @@
 struct sparse_file *sparse_file_import(int fd, bool verbose, bool crc);
 
 /**
+ * sparse_file_import_buf - import an existing sparse file from a buffer
+ *
+ * @buf - buffer to read from
+ * @verbose - print verbose errors while reading the sparse file
+ * @crc - verify the crc of a file in the Android sparse file format
+ *
+ * Reads existing sparse file data into a sparse file cookie, recreating the same
+ * sparse cookie that was used to write it.  If verbose is true, prints verbose
+ * errors when the sparse file is formatted incorrectly.
+ *
+ * Returns a new sparse file cookie on success, NULL on error.
+ */
+struct sparse_file *sparse_file_import_buf(char* buf, bool verbose, bool crc);
+
+/**
  * sparse_file_import_auto - import an existing sparse or normal file
  *
  * @fd - file descriptor to read from
diff --git a/libsparse/output_file.c b/libsparse/output_file.c
deleted file mode 100644
index 002ad27..0000000
--- a/libsparse/output_file.c
+++ /dev/null
@@ -1,776 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define _FILE_OFFSET_BITS 64
-#define _LARGEFILE64_SOURCE 1
-
-#include <fcntl.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <zlib.h>
-
-#include "defs.h"
-#include "output_file.h"
-#include "sparse_crc32.h"
-#include "sparse_format.h"
-
-#ifndef _WIN32
-#include <sys/mman.h>
-#define O_BINARY 0
-#else
-#define ftruncate64 ftruncate
-#endif
-
-#if defined(__APPLE__) && defined(__MACH__)
-#define lseek64 lseek
-#define ftruncate64 ftruncate
-#define mmap64 mmap
-#define off64_t off_t
-#endif
-
-#define min(a, b) \
-	({ typeof(a) _a = (a); typeof(b) _b = (b); (_a < _b) ? _a : _b; })
-
-#define SPARSE_HEADER_MAJOR_VER 1
-#define SPARSE_HEADER_MINOR_VER 0
-#define SPARSE_HEADER_LEN       (sizeof(sparse_header_t))
-#define CHUNK_HEADER_LEN (sizeof(chunk_header_t))
-
-#define container_of(inner, outer_t, elem) \
-	((outer_t *)((char *)(inner) - offsetof(outer_t, elem)))
-
-struct output_file_ops {
-	int (*open)(struct output_file *, int fd);
-	int (*skip)(struct output_file *, int64_t);
-	int (*pad)(struct output_file *, int64_t);
-	int (*write)(struct output_file *, void *, size_t);
-	void (*close)(struct output_file *);
-};
-
-struct sparse_file_ops {
-	int (*write_data_chunk)(struct output_file *out, unsigned int len,
-			void *data);
-	int (*write_fill_chunk)(struct output_file *out, unsigned int len,
-			uint32_t fill_val);
-	int (*write_skip_chunk)(struct output_file *out, int64_t len);
-	int (*write_end_chunk)(struct output_file *out);
-};
-
-struct output_file {
-	int64_t cur_out_ptr;
-	unsigned int chunk_cnt;
-	uint32_t crc32;
-	struct output_file_ops *ops;
-	struct sparse_file_ops *sparse_ops;
-	int use_crc;
-	unsigned int block_size;
-	int64_t len;
-	char *zero_buf;
-	uint32_t *fill_buf;
-	char *buf;
-};
-
-struct output_file_gz {
-	struct output_file out;
-	gzFile gz_fd;
-};
-
-#define to_output_file_gz(_o) \
-	container_of((_o), struct output_file_gz, out)
-
-struct output_file_normal {
-	struct output_file out;
-	int fd;
-};
-
-#define to_output_file_normal(_o) \
-	container_of((_o), struct output_file_normal, out)
-
-struct output_file_callback {
-	struct output_file out;
-	void *priv;
-	int (*write)(void *priv, const void *buf, size_t len);
-};
-
-#define to_output_file_callback(_o) \
-	container_of((_o), struct output_file_callback, out)
-
-static int file_open(struct output_file *out, int fd)
-{
-	struct output_file_normal *outn = to_output_file_normal(out);
-
-	outn->fd = fd;
-	return 0;
-}
-
-static int file_skip(struct output_file *out, int64_t cnt)
-{
-	off64_t ret;
-	struct output_file_normal *outn = to_output_file_normal(out);
-
-	ret = lseek64(outn->fd, cnt, SEEK_CUR);
-	if (ret < 0) {
-		error_errno("lseek64");
-		return -1;
-	}
-	return 0;
-}
-
-static int file_pad(struct output_file *out, int64_t len)
-{
-	int ret;
-	struct output_file_normal *outn = to_output_file_normal(out);
-
-	ret = ftruncate64(outn->fd, len);
-	if (ret < 0) {
-		return -errno;
-	}
-
-	return 0;
-}
-
-static int file_write(struct output_file *out, void *data, size_t len)
-{
-	ssize_t ret;
-	struct output_file_normal *outn = to_output_file_normal(out);
-
-	while (len > 0) {
-		ret = write(outn->fd, data, len);
-		if (ret < 0) {
-			if (errno == EINTR) {
-				continue;
-			}
-			error_errno("write");
-			return -1;
-		}
-
-		data = (char *)data + ret;
-		len -= ret;
-	}
-
-	return 0;
-}
-
-static void file_close(struct output_file *out)
-{
-	struct output_file_normal *outn = to_output_file_normal(out);
-
-	free(outn);
-}
-
-static struct output_file_ops file_ops = {
-	.open = file_open,
-	.skip = file_skip,
-	.pad = file_pad,
-	.write = file_write,
-	.close = file_close,
-};
-
-static int gz_file_open(struct output_file *out, int fd)
-{
-	struct output_file_gz *outgz = to_output_file_gz(out);
-
-	outgz->gz_fd = gzdopen(fd, "wb9");
-	if (!outgz->gz_fd) {
-		error_errno("gzopen");
-		return -errno;
-	}
-
-	return 0;
-}
-
-
-static int gz_file_skip(struct output_file *out, int64_t cnt)
-{
-	off64_t ret;
-	struct output_file_gz *outgz = to_output_file_gz(out);
-
-	ret = gzseek(outgz->gz_fd, cnt, SEEK_CUR);
-	if (ret < 0) {
-		error_errno("gzseek");
-		return -1;
-	}
-	return 0;
-}
-
-static int gz_file_pad(struct output_file *out, int64_t len)
-{
-	off64_t ret;
-	struct output_file_gz *outgz = to_output_file_gz(out);
-
-	ret = gztell(outgz->gz_fd);
-	if (ret < 0) {
-		return -1;
-	}
-
-	if (ret >= len) {
-		return 0;
-	}
-
-	ret = gzseek(outgz->gz_fd, len - 1, SEEK_SET);
-	if (ret < 0) {
-		return -1;
-	}
-
-	gzwrite(outgz->gz_fd, "", 1);
-
-	return 0;
-}
-
-static int gz_file_write(struct output_file *out, void *data, size_t len)
-{
-	int ret;
-	struct output_file_gz *outgz = to_output_file_gz(out);
-
-	while (len > 0) {
-		ret = gzwrite(outgz->gz_fd, data,
-			      min(len, (unsigned int)INT_MAX));
-		if (ret == 0) {
-			error("gzwrite %s", gzerror(outgz->gz_fd, NULL));
-			return -1;
-		}
-		len -= ret;
-		data = (char *)data + ret;
-	}
-
-	return 0;
-}
-
-static void gz_file_close(struct output_file *out)
-{
-	struct output_file_gz *outgz = to_output_file_gz(out);
-
-	gzclose(outgz->gz_fd);
-	free(outgz);
-}
-
-static struct output_file_ops gz_file_ops = {
-	.open = gz_file_open,
-	.skip = gz_file_skip,
-	.pad = gz_file_pad,
-	.write = gz_file_write,
-	.close = gz_file_close,
-};
-
-static int callback_file_open(struct output_file *out __unused, int fd __unused)
-{
-	return 0;
-}
-
-static int callback_file_skip(struct output_file *out, int64_t off)
-{
-	struct output_file_callback *outc = to_output_file_callback(out);
-	int to_write;
-	int ret;
-
-	while (off > 0) {
-		to_write = min(off, (int64_t)INT_MAX);
-		ret = outc->write(outc->priv, NULL, to_write);
-		if (ret < 0) {
-			return ret;
-		}
-		off -= to_write;
-	}
-
-	return 0;
-}
-
-static int callback_file_pad(struct output_file *out __unused, int64_t len __unused)
-{
-	return -1;
-}
-
-static int callback_file_write(struct output_file *out, void *data, size_t len)
-{
-	struct output_file_callback *outc = to_output_file_callback(out);
-
-	return outc->write(outc->priv, data, len);
-}
-
-static void callback_file_close(struct output_file *out)
-{
-	struct output_file_callback *outc = to_output_file_callback(out);
-
-	free(outc);
-}
-
-static struct output_file_ops callback_file_ops = {
-	.open = callback_file_open,
-	.skip = callback_file_skip,
-	.pad = callback_file_pad,
-	.write = callback_file_write,
-	.close = callback_file_close,
-};
-
-int read_all(int fd, void *buf, size_t len)
-{
-	size_t total = 0;
-	int ret;
-	char *ptr = buf;
-
-	while (total < len) {
-		ret = read(fd, ptr, len - total);
-
-		if (ret < 0)
-			return -errno;
-
-		if (ret == 0)
-			return -EINVAL;
-
-		ptr += ret;
-		total += ret;
-	}
-
-	return 0;
-}
-
-static int write_sparse_skip_chunk(struct output_file *out, int64_t skip_len)
-{
-	chunk_header_t chunk_header;
-	int ret;
-
-	if (skip_len % out->block_size) {
-		error("don't care size %"PRIi64" is not a multiple of the block size %u",
-				skip_len, out->block_size);
-		return -1;
-	}
-
-	/* We are skipping data, so emit a don't care chunk. */
-	chunk_header.chunk_type = CHUNK_TYPE_DONT_CARE;
-	chunk_header.reserved1 = 0;
-	chunk_header.chunk_sz = skip_len / out->block_size;
-	chunk_header.total_sz = CHUNK_HEADER_LEN;
-	ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
-	if (ret < 0)
-		return -1;
-
-	out->cur_out_ptr += skip_len;
-	out->chunk_cnt++;
-
-	return 0;
-}
-
-static int write_sparse_fill_chunk(struct output_file *out, unsigned int len,
-		uint32_t fill_val)
-{
-	chunk_header_t chunk_header;
-	int rnd_up_len, count;
-	int ret;
-
-	/* Round up the fill length to a multiple of the block size */
-	rnd_up_len = ALIGN(len, out->block_size);
-
-	/* Finally we can safely emit a chunk of data */
-	chunk_header.chunk_type = CHUNK_TYPE_FILL;
-	chunk_header.reserved1 = 0;
-	chunk_header.chunk_sz = rnd_up_len / out->block_size;
-	chunk_header.total_sz = CHUNK_HEADER_LEN + sizeof(fill_val);
-	ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
-
-	if (ret < 0)
-		return -1;
-	ret = out->ops->write(out, &fill_val, sizeof(fill_val));
-	if (ret < 0)
-		return -1;
-
-	if (out->use_crc) {
-		count = out->block_size / sizeof(uint32_t);
-		while (count--)
-			out->crc32 = sparse_crc32(out->crc32, &fill_val, sizeof(uint32_t));
-	}
-
-	out->cur_out_ptr += rnd_up_len;
-	out->chunk_cnt++;
-
-	return 0;
-}
-
-static int write_sparse_data_chunk(struct output_file *out, unsigned int len,
-		void *data)
-{
-	chunk_header_t chunk_header;
-	int rnd_up_len, zero_len;
-	int ret;
-
-	/* Round up the data length to a multiple of the block size */
-	rnd_up_len = ALIGN(len, out->block_size);
-	zero_len = rnd_up_len - len;
-
-	/* Finally we can safely emit a chunk of data */
-	chunk_header.chunk_type = CHUNK_TYPE_RAW;
-	chunk_header.reserved1 = 0;
-	chunk_header.chunk_sz = rnd_up_len / out->block_size;
-	chunk_header.total_sz = CHUNK_HEADER_LEN + rnd_up_len;
-	ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
-
-	if (ret < 0)
-		return -1;
-	ret = out->ops->write(out, data, len);
-	if (ret < 0)
-		return -1;
-	if (zero_len) {
-		ret = out->ops->write(out, out->zero_buf, zero_len);
-		if (ret < 0)
-			return -1;
-	}
-
-	if (out->use_crc) {
-		out->crc32 = sparse_crc32(out->crc32, data, len);
-		if (zero_len)
-			out->crc32 = sparse_crc32(out->crc32, out->zero_buf, zero_len);
-	}
-
-	out->cur_out_ptr += rnd_up_len;
-	out->chunk_cnt++;
-
-	return 0;
-}
-
-int write_sparse_end_chunk(struct output_file *out)
-{
-	chunk_header_t chunk_header;
-	int ret;
-
-	if (out->use_crc) {
-		chunk_header.chunk_type = CHUNK_TYPE_CRC32;
-		chunk_header.reserved1 = 0;
-		chunk_header.chunk_sz = 0;
-		chunk_header.total_sz = CHUNK_HEADER_LEN + 4;
-
-		ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
-		if (ret < 0) {
-			return ret;
-		}
-		out->ops->write(out, &out->crc32, 4);
-		if (ret < 0) {
-			return ret;
-		}
-
-		out->chunk_cnt++;
-	}
-
-	return 0;
-}
-
-static struct sparse_file_ops sparse_file_ops = {
-		.write_data_chunk = write_sparse_data_chunk,
-		.write_fill_chunk = write_sparse_fill_chunk,
-		.write_skip_chunk = write_sparse_skip_chunk,
-		.write_end_chunk = write_sparse_end_chunk,
-};
-
-static int write_normal_data_chunk(struct output_file *out, unsigned int len,
-		void *data)
-{
-	int ret;
-	unsigned int rnd_up_len = ALIGN(len, out->block_size);
-
-	ret = out->ops->write(out, data, len);
-	if (ret < 0) {
-		return ret;
-	}
-
-	if (rnd_up_len > len) {
-		ret = out->ops->skip(out, rnd_up_len - len);
-	}
-
-	return ret;
-}
-
-static int write_normal_fill_chunk(struct output_file *out, unsigned int len,
-		uint32_t fill_val)
-{
-	int ret;
-	unsigned int i;
-	unsigned int write_len;
-
-	/* Initialize fill_buf with the fill_val */
-	for (i = 0; i < out->block_size / sizeof(uint32_t); i++) {
-		out->fill_buf[i] = fill_val;
-	}
-
-	while (len) {
-		write_len = min(len, out->block_size);
-		ret = out->ops->write(out, out->fill_buf, write_len);
-		if (ret < 0) {
-			return ret;
-		}
-
-		len -= write_len;
-	}
-
-	return 0;
-}
-
-static int write_normal_skip_chunk(struct output_file *out, int64_t len)
-{
-	return out->ops->skip(out, len);
-}
-
-int write_normal_end_chunk(struct output_file *out)
-{
-	return out->ops->pad(out, out->len);
-}
-
-static struct sparse_file_ops normal_file_ops = {
-		.write_data_chunk = write_normal_data_chunk,
-		.write_fill_chunk = write_normal_fill_chunk,
-		.write_skip_chunk = write_normal_skip_chunk,
-		.write_end_chunk = write_normal_end_chunk,
-};
-
-void output_file_close(struct output_file *out)
-{
-	out->sparse_ops->write_end_chunk(out);
-	out->ops->close(out);
-}
-
-static int output_file_init(struct output_file *out, int block_size,
-		int64_t len, bool sparse, int chunks, bool crc)
-{
-	int ret;
-
-	out->len = len;
-	out->block_size = block_size;
-	out->cur_out_ptr = 0ll;
-	out->chunk_cnt = 0;
-	out->crc32 = 0;
-	out->use_crc = crc;
-
-	out->zero_buf = calloc(block_size, 1);
-	if (!out->zero_buf) {
-		error_errno("malloc zero_buf");
-		return -ENOMEM;
-	}
-
-	out->fill_buf = calloc(block_size, 1);
-	if (!out->fill_buf) {
-		error_errno("malloc fill_buf");
-		ret = -ENOMEM;
-		goto err_fill_buf;
-	}
-
-	if (sparse) {
-		out->sparse_ops = &sparse_file_ops;
-	} else {
-		out->sparse_ops = &normal_file_ops;
-	}
-
-	if (sparse) {
-		sparse_header_t sparse_header = {
-				.magic = SPARSE_HEADER_MAGIC,
-				.major_version = SPARSE_HEADER_MAJOR_VER,
-				.minor_version = SPARSE_HEADER_MINOR_VER,
-				.file_hdr_sz = SPARSE_HEADER_LEN,
-				.chunk_hdr_sz = CHUNK_HEADER_LEN,
-				.blk_sz = out->block_size,
-				.total_blks = DIV_ROUND_UP(out->len, out->block_size),
-				.total_chunks = chunks,
-				.image_checksum = 0
-		};
-
-		if (out->use_crc) {
-			sparse_header.total_chunks++;
-		}
-
-		ret = out->ops->write(out, &sparse_header, sizeof(sparse_header));
-		if (ret < 0) {
-			goto err_write;
-		}
-	}
-
-	return 0;
-
-err_write:
-	free(out->fill_buf);
-err_fill_buf:
-	free(out->zero_buf);
-	return ret;
-}
-
-static struct output_file *output_file_new_gz(void)
-{
-	struct output_file_gz *outgz = calloc(1, sizeof(struct output_file_gz));
-	if (!outgz) {
-		error_errno("malloc struct outgz");
-		return NULL;
-	}
-
-	outgz->out.ops = &gz_file_ops;
-
-	return &outgz->out;
-}
-
-static struct output_file *output_file_new_normal(void)
-{
-	struct output_file_normal *outn = calloc(1, sizeof(struct output_file_normal));
-	if (!outn) {
-		error_errno("malloc struct outn");
-		return NULL;
-	}
-
-	outn->out.ops = &file_ops;
-
-	return &outn->out;
-}
-
-struct output_file *output_file_open_callback(
-		int (*write)(void *, const void *, size_t),
-		void *priv, unsigned int block_size, int64_t len,
-		int gz __unused, int sparse, int chunks, int crc)
-{
-	int ret;
-	struct output_file_callback *outc;
-
-	outc = calloc(1, sizeof(struct output_file_callback));
-	if (!outc) {
-		error_errno("malloc struct outc");
-		return NULL;
-	}
-
-	outc->out.ops = &callback_file_ops;
-	outc->priv = priv;
-	outc->write = write;
-
-	ret = output_file_init(&outc->out, block_size, len, sparse, chunks, crc);
-	if (ret < 0) {
-		free(outc);
-		return NULL;
-	}
-
-	return &outc->out;
-}
-
-struct output_file *output_file_open_fd(int fd, unsigned int block_size, int64_t len,
-		int gz, int sparse, int chunks, int crc)
-{
-	int ret;
-	struct output_file *out;
-
-	if (gz) {
-		out = output_file_new_gz();
-	} else {
-		out = output_file_new_normal();
-	}
-	if (!out) {
-		return NULL;
-	}
-
-	out->ops->open(out, fd);
-
-	ret = output_file_init(out, block_size, len, sparse, chunks, crc);
-	if (ret < 0) {
-		free(out);
-		return NULL;
-	}
-
-	return out;
-}
-
-/* Write a contiguous region of data blocks from a memory buffer */
-int write_data_chunk(struct output_file *out, unsigned int len, void *data)
-{
-	return out->sparse_ops->write_data_chunk(out, len, data);
-}
-
-/* Write a contiguous region of data blocks with a fill value */
-int write_fill_chunk(struct output_file *out, unsigned int len,
-		uint32_t fill_val)
-{
-	return out->sparse_ops->write_fill_chunk(out, len, fill_val);
-}
-
-int write_fd_chunk(struct output_file *out, unsigned int len,
-		int fd, int64_t offset)
-{
-	int ret;
-	int64_t aligned_offset;
-	int aligned_diff;
-	uint64_t buffer_size;
-	char *ptr;
-
-	aligned_offset = offset & ~(4096 - 1);
-	aligned_diff = offset - aligned_offset;
-	buffer_size = (uint64_t)len + (uint64_t)aligned_diff;
-
-#ifndef _WIN32
-	if (buffer_size > SIZE_MAX)
-		return -E2BIG;
-	char *data = mmap64(NULL, buffer_size, PROT_READ, MAP_SHARED, fd,
-			aligned_offset);
-	if (data == MAP_FAILED) {
-		return -errno;
-	}
-	ptr = data + aligned_diff;
-#else
-	off64_t pos;
-	char *data = malloc(len);
-	if (!data) {
-		return -errno;
-	}
-	pos = lseek64(fd, offset, SEEK_SET);
-	if (pos < 0) {
-                free(data);
-		return -errno;
-	}
-	ret = read_all(fd, data, len);
-	if (ret < 0) {
-                free(data);
-		return ret;
-	}
-	ptr = data;
-#endif
-
-	ret = out->sparse_ops->write_data_chunk(out, len, ptr);
-
-#ifndef _WIN32
-	munmap(data, buffer_size);
-#else
-	free(data);
-#endif
-
-	return ret;
-}
-
-/* Write a contiguous region of data blocks from a file */
-int write_file_chunk(struct output_file *out, unsigned int len,
-		const char *file, int64_t offset)
-{
-	int ret;
-
-	int file_fd = open(file, O_RDONLY | O_BINARY);
-	if (file_fd < 0) {
-		return -errno;
-	}
-
-	ret = write_fd_chunk(out, len, file_fd, offset);
-
-	close(file_fd);
-
-	return ret;
-}
-
-int write_skip_chunk(struct output_file *out, int64_t len)
-{
-	return out->sparse_ops->write_skip_chunk(out, len);
-}
diff --git a/libsparse/output_file.cpp b/libsparse/output_file.cpp
new file mode 100644
index 0000000..5388e77
--- /dev/null
+++ b/libsparse/output_file.cpp
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _FILE_OFFSET_BITS 64
+#define _LARGEFILE64_SOURCE 1
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#include "defs.h"
+#include "output_file.h"
+#include "sparse_crc32.h"
+#include "sparse_format.h"
+
+#ifndef _WIN32
+#include <sys/mman.h>
+#define O_BINARY 0
+#else
+#define ftruncate64 ftruncate
+#endif
+
+#if defined(__APPLE__) && defined(__MACH__)
+#define lseek64 lseek
+#define ftruncate64 ftruncate
+#define mmap64 mmap
+#define off64_t off_t
+#endif
+
+#define min(a, b)        \
+  ({                     \
+    typeof(a) _a = (a);  \
+    typeof(b) _b = (b);  \
+    (_a < _b) ? _a : _b; \
+  })
+
+#define SPARSE_HEADER_MAJOR_VER 1
+#define SPARSE_HEADER_MINOR_VER 0
+#define SPARSE_HEADER_LEN (sizeof(sparse_header_t))
+#define CHUNK_HEADER_LEN (sizeof(chunk_header_t))
+
+#define container_of(inner, outer_t, elem) ((outer_t*)((char*)(inner)-offsetof(outer_t, elem)))
+
+struct output_file_ops {
+  int (*open)(struct output_file*, int fd);
+  int (*skip)(struct output_file*, int64_t);
+  int (*pad)(struct output_file*, int64_t);
+  int (*write)(struct output_file*, void*, size_t);
+  void (*close)(struct output_file*);
+};
+
+struct sparse_file_ops {
+  int (*write_data_chunk)(struct output_file* out, unsigned int len, void* data);
+  int (*write_fill_chunk)(struct output_file* out, unsigned int len, uint32_t fill_val);
+  int (*write_skip_chunk)(struct output_file* out, int64_t len);
+  int (*write_end_chunk)(struct output_file* out);
+};
+
+struct output_file {
+  int64_t cur_out_ptr;
+  unsigned int chunk_cnt;
+  uint32_t crc32;
+  struct output_file_ops* ops;
+  struct sparse_file_ops* sparse_ops;
+  int use_crc;
+  unsigned int block_size;
+  int64_t len;
+  char* zero_buf;
+  uint32_t* fill_buf;
+  char* buf;
+};
+
+struct output_file_gz {
+  struct output_file out;
+  gzFile gz_fd;
+};
+
+#define to_output_file_gz(_o) container_of((_o), struct output_file_gz, out)
+
+struct output_file_normal {
+  struct output_file out;
+  int fd;
+};
+
+#define to_output_file_normal(_o) container_of((_o), struct output_file_normal, out)
+
+struct output_file_callback {
+  struct output_file out;
+  void* priv;
+  int (*write)(void* priv, const void* buf, size_t len);
+};
+
+#define to_output_file_callback(_o) container_of((_o), struct output_file_callback, out)
+
+static int file_open(struct output_file* out, int fd) {
+  struct output_file_normal* outn = to_output_file_normal(out);
+
+  outn->fd = fd;
+  return 0;
+}
+
+static int file_skip(struct output_file* out, int64_t cnt) {
+  off64_t ret;
+  struct output_file_normal* outn = to_output_file_normal(out);
+
+  ret = lseek64(outn->fd, cnt, SEEK_CUR);
+  if (ret < 0) {
+    error_errno("lseek64");
+    return -1;
+  }
+  return 0;
+}
+
+static int file_pad(struct output_file* out, int64_t len) {
+  int ret;
+  struct output_file_normal* outn = to_output_file_normal(out);
+
+  ret = ftruncate64(outn->fd, len);
+  if (ret < 0) {
+    return -errno;
+  }
+
+  return 0;
+}
+
+static int file_write(struct output_file* out, void* data, size_t len) {
+  ssize_t ret;
+  struct output_file_normal* outn = to_output_file_normal(out);
+
+  while (len > 0) {
+    ret = write(outn->fd, data, len);
+    if (ret < 0) {
+      if (errno == EINTR) {
+        continue;
+      }
+      error_errno("write");
+      return -1;
+    }
+
+    data = (char*)data + ret;
+    len -= ret;
+  }
+
+  return 0;
+}
+
+static void file_close(struct output_file* out) {
+  struct output_file_normal* outn = to_output_file_normal(out);
+
+  free(outn);
+}
+
+static struct output_file_ops file_ops = {
+    .open = file_open,
+    .skip = file_skip,
+    .pad = file_pad,
+    .write = file_write,
+    .close = file_close,
+};
+
+static int gz_file_open(struct output_file* out, int fd) {
+  struct output_file_gz* outgz = to_output_file_gz(out);
+
+  outgz->gz_fd = gzdopen(fd, "wb9");
+  if (!outgz->gz_fd) {
+    error_errno("gzopen");
+    return -errno;
+  }
+
+  return 0;
+}
+
+static int gz_file_skip(struct output_file* out, int64_t cnt) {
+  off64_t ret;
+  struct output_file_gz* outgz = to_output_file_gz(out);
+
+  ret = gzseek(outgz->gz_fd, cnt, SEEK_CUR);
+  if (ret < 0) {
+    error_errno("gzseek");
+    return -1;
+  }
+  return 0;
+}
+
+static int gz_file_pad(struct output_file* out, int64_t len) {
+  off64_t ret;
+  struct output_file_gz* outgz = to_output_file_gz(out);
+
+  ret = gztell(outgz->gz_fd);
+  if (ret < 0) {
+    return -1;
+  }
+
+  if (ret >= len) {
+    return 0;
+  }
+
+  ret = gzseek(outgz->gz_fd, len - 1, SEEK_SET);
+  if (ret < 0) {
+    return -1;
+  }
+
+  gzwrite(outgz->gz_fd, "", 1);
+
+  return 0;
+}
+
+static int gz_file_write(struct output_file* out, void* data, size_t len) {
+  int ret;
+  struct output_file_gz* outgz = to_output_file_gz(out);
+
+  while (len > 0) {
+    ret = gzwrite(outgz->gz_fd, data, min(len, (unsigned int)INT_MAX));
+    if (ret == 0) {
+      error("gzwrite %s", gzerror(outgz->gz_fd, NULL));
+      return -1;
+    }
+    len -= ret;
+    data = (char*)data + ret;
+  }
+
+  return 0;
+}
+
+static void gz_file_close(struct output_file* out) {
+  struct output_file_gz* outgz = to_output_file_gz(out);
+
+  gzclose(outgz->gz_fd);
+  free(outgz);
+}
+
+static struct output_file_ops gz_file_ops = {
+    .open = gz_file_open,
+    .skip = gz_file_skip,
+    .pad = gz_file_pad,
+    .write = gz_file_write,
+    .close = gz_file_close,
+};
+
+static int callback_file_open(struct output_file* out __unused, int fd __unused) {
+  return 0;
+}
+
+static int callback_file_skip(struct output_file* out, int64_t off) {
+  struct output_file_callback* outc = to_output_file_callback(out);
+  int to_write;
+  int ret;
+
+  while (off > 0) {
+    to_write = min(off, (int64_t)INT_MAX);
+    ret = outc->write(outc->priv, NULL, to_write);
+    if (ret < 0) {
+      return ret;
+    }
+    off -= to_write;
+  }
+
+  return 0;
+}
+
+static int callback_file_pad(struct output_file* out __unused, int64_t len __unused) {
+  return -1;
+}
+
+static int callback_file_write(struct output_file* out, void* data, size_t len) {
+  struct output_file_callback* outc = to_output_file_callback(out);
+
+  return outc->write(outc->priv, data, len);
+}
+
+static void callback_file_close(struct output_file* out) {
+  struct output_file_callback* outc = to_output_file_callback(out);
+
+  free(outc);
+}
+
+static struct output_file_ops callback_file_ops = {
+    .open = callback_file_open,
+    .skip = callback_file_skip,
+    .pad = callback_file_pad,
+    .write = callback_file_write,
+    .close = callback_file_close,
+};
+
+int read_all(int fd, void* buf, size_t len) {
+  size_t total = 0;
+  int ret;
+  char* ptr = reinterpret_cast<char*>(buf);
+
+  while (total < len) {
+    ret = read(fd, ptr, len - total);
+
+    if (ret < 0) return -errno;
+
+    if (ret == 0) return -EINVAL;
+
+    ptr += ret;
+    total += ret;
+  }
+
+  return 0;
+}
+
+static int write_sparse_skip_chunk(struct output_file* out, int64_t skip_len) {
+  chunk_header_t chunk_header;
+  int ret;
+
+  if (skip_len % out->block_size) {
+    error("don't care size %" PRIi64 " is not a multiple of the block size %u", skip_len,
+          out->block_size);
+    return -1;
+  }
+
+  /* We are skipping data, so emit a don't care chunk. */
+  chunk_header.chunk_type = CHUNK_TYPE_DONT_CARE;
+  chunk_header.reserved1 = 0;
+  chunk_header.chunk_sz = skip_len / out->block_size;
+  chunk_header.total_sz = CHUNK_HEADER_LEN;
+  ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
+  if (ret < 0) return -1;
+
+  out->cur_out_ptr += skip_len;
+  out->chunk_cnt++;
+
+  return 0;
+}
+
+static int write_sparse_fill_chunk(struct output_file* out, unsigned int len, uint32_t fill_val) {
+  chunk_header_t chunk_header;
+  int rnd_up_len, count;
+  int ret;
+
+  /* Round up the fill length to a multiple of the block size */
+  rnd_up_len = ALIGN(len, out->block_size);
+
+  /* Finally we can safely emit a chunk of data */
+  chunk_header.chunk_type = CHUNK_TYPE_FILL;
+  chunk_header.reserved1 = 0;
+  chunk_header.chunk_sz = rnd_up_len / out->block_size;
+  chunk_header.total_sz = CHUNK_HEADER_LEN + sizeof(fill_val);
+  ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
+
+  if (ret < 0) return -1;
+  ret = out->ops->write(out, &fill_val, sizeof(fill_val));
+  if (ret < 0) return -1;
+
+  if (out->use_crc) {
+    count = out->block_size / sizeof(uint32_t);
+    while (count--) out->crc32 = sparse_crc32(out->crc32, &fill_val, sizeof(uint32_t));
+  }
+
+  out->cur_out_ptr += rnd_up_len;
+  out->chunk_cnt++;
+
+  return 0;
+}
+
+static int write_sparse_data_chunk(struct output_file* out, unsigned int len, void* data) {
+  chunk_header_t chunk_header;
+  int rnd_up_len, zero_len;
+  int ret;
+
+  /* Round up the data length to a multiple of the block size */
+  rnd_up_len = ALIGN(len, out->block_size);
+  zero_len = rnd_up_len - len;
+
+  /* Finally we can safely emit a chunk of data */
+  chunk_header.chunk_type = CHUNK_TYPE_RAW;
+  chunk_header.reserved1 = 0;
+  chunk_header.chunk_sz = rnd_up_len / out->block_size;
+  chunk_header.total_sz = CHUNK_HEADER_LEN + rnd_up_len;
+  ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
+
+  if (ret < 0) return -1;
+  ret = out->ops->write(out, data, len);
+  if (ret < 0) return -1;
+  if (zero_len) {
+    ret = out->ops->write(out, out->zero_buf, zero_len);
+    if (ret < 0) return -1;
+  }
+
+  if (out->use_crc) {
+    out->crc32 = sparse_crc32(out->crc32, data, len);
+    if (zero_len) out->crc32 = sparse_crc32(out->crc32, out->zero_buf, zero_len);
+  }
+
+  out->cur_out_ptr += rnd_up_len;
+  out->chunk_cnt++;
+
+  return 0;
+}
+
+int write_sparse_end_chunk(struct output_file* out) {
+  chunk_header_t chunk_header;
+  int ret;
+
+  if (out->use_crc) {
+    chunk_header.chunk_type = CHUNK_TYPE_CRC32;
+    chunk_header.reserved1 = 0;
+    chunk_header.chunk_sz = 0;
+    chunk_header.total_sz = CHUNK_HEADER_LEN + 4;
+
+    ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
+    if (ret < 0) {
+      return ret;
+    }
+    out->ops->write(out, &out->crc32, 4);
+    if (ret < 0) {
+      return ret;
+    }
+
+    out->chunk_cnt++;
+  }
+
+  return 0;
+}
+
+static struct sparse_file_ops sparse_file_ops = {
+    .write_data_chunk = write_sparse_data_chunk,
+    .write_fill_chunk = write_sparse_fill_chunk,
+    .write_skip_chunk = write_sparse_skip_chunk,
+    .write_end_chunk = write_sparse_end_chunk,
+};
+
+static int write_normal_data_chunk(struct output_file* out, unsigned int len, void* data) {
+  int ret;
+  unsigned int rnd_up_len = ALIGN(len, out->block_size);
+
+  ret = out->ops->write(out, data, len);
+  if (ret < 0) {
+    return ret;
+  }
+
+  if (rnd_up_len > len) {
+    ret = out->ops->skip(out, rnd_up_len - len);
+  }
+
+  return ret;
+}
+
+static int write_normal_fill_chunk(struct output_file* out, unsigned int len, uint32_t fill_val) {
+  int ret;
+  unsigned int i;
+  unsigned int write_len;
+
+  /* Initialize fill_buf with the fill_val */
+  for (i = 0; i < out->block_size / sizeof(uint32_t); i++) {
+    out->fill_buf[i] = fill_val;
+  }
+
+  while (len) {
+    write_len = min(len, out->block_size);
+    ret = out->ops->write(out, out->fill_buf, write_len);
+    if (ret < 0) {
+      return ret;
+    }
+
+    len -= write_len;
+  }
+
+  return 0;
+}
+
+static int write_normal_skip_chunk(struct output_file* out, int64_t len) {
+  return out->ops->skip(out, len);
+}
+
+int write_normal_end_chunk(struct output_file* out) {
+  return out->ops->pad(out, out->len);
+}
+
+static struct sparse_file_ops normal_file_ops = {
+    .write_data_chunk = write_normal_data_chunk,
+    .write_fill_chunk = write_normal_fill_chunk,
+    .write_skip_chunk = write_normal_skip_chunk,
+    .write_end_chunk = write_normal_end_chunk,
+};
+
+void output_file_close(struct output_file* out) {
+  out->sparse_ops->write_end_chunk(out);
+  out->ops->close(out);
+}
+
+static int output_file_init(struct output_file* out, int block_size, int64_t len, bool sparse,
+                            int chunks, bool crc) {
+  int ret;
+
+  out->len = len;
+  out->block_size = block_size;
+  out->cur_out_ptr = 0ll;
+  out->chunk_cnt = 0;
+  out->crc32 = 0;
+  out->use_crc = crc;
+
+  out->zero_buf = reinterpret_cast<char*>(calloc(block_size, 1));
+  if (!out->zero_buf) {
+    error_errno("malloc zero_buf");
+    return -ENOMEM;
+  }
+
+  out->fill_buf = reinterpret_cast<uint32_t*>(calloc(block_size, 1));
+  if (!out->fill_buf) {
+    error_errno("malloc fill_buf");
+    ret = -ENOMEM;
+    goto err_fill_buf;
+  }
+
+  if (sparse) {
+    out->sparse_ops = &sparse_file_ops;
+  } else {
+    out->sparse_ops = &normal_file_ops;
+  }
+
+  if (sparse) {
+    sparse_header_t sparse_header = {
+        .magic = SPARSE_HEADER_MAGIC,
+        .major_version = SPARSE_HEADER_MAJOR_VER,
+        .minor_version = SPARSE_HEADER_MINOR_VER,
+        .file_hdr_sz = SPARSE_HEADER_LEN,
+        .chunk_hdr_sz = CHUNK_HEADER_LEN,
+        .blk_sz = out->block_size,
+        .total_blks = static_cast<unsigned>(DIV_ROUND_UP(out->len, out->block_size)),
+        .total_chunks = static_cast<unsigned>(chunks),
+        .image_checksum = 0};
+
+    if (out->use_crc) {
+      sparse_header.total_chunks++;
+    }
+
+    ret = out->ops->write(out, &sparse_header, sizeof(sparse_header));
+    if (ret < 0) {
+      goto err_write;
+    }
+  }
+
+  return 0;
+
+err_write:
+  free(out->fill_buf);
+err_fill_buf:
+  free(out->zero_buf);
+  return ret;
+}
+
+static struct output_file* output_file_new_gz(void) {
+  struct output_file_gz* outgz =
+      reinterpret_cast<struct output_file_gz*>(calloc(1, sizeof(struct output_file_gz)));
+  if (!outgz) {
+    error_errno("malloc struct outgz");
+    return NULL;
+  }
+
+  outgz->out.ops = &gz_file_ops;
+
+  return &outgz->out;
+}
+
+static struct output_file* output_file_new_normal(void) {
+  struct output_file_normal* outn =
+      reinterpret_cast<struct output_file_normal*>(calloc(1, sizeof(struct output_file_normal)));
+  if (!outn) {
+    error_errno("malloc struct outn");
+    return NULL;
+  }
+
+  outn->out.ops = &file_ops;
+
+  return &outn->out;
+}
+
+struct output_file* output_file_open_callback(int (*write)(void*, const void*, size_t), void* priv,
+                                              unsigned int block_size, int64_t len, int gz __unused,
+                                              int sparse, int chunks, int crc) {
+  int ret;
+  struct output_file_callback* outc;
+
+  outc =
+      reinterpret_cast<struct output_file_callback*>(calloc(1, sizeof(struct output_file_callback)));
+  if (!outc) {
+    error_errno("malloc struct outc");
+    return NULL;
+  }
+
+  outc->out.ops = &callback_file_ops;
+  outc->priv = priv;
+  outc->write = write;
+
+  ret = output_file_init(&outc->out, block_size, len, sparse, chunks, crc);
+  if (ret < 0) {
+    free(outc);
+    return NULL;
+  }
+
+  return &outc->out;
+}
+
+struct output_file* output_file_open_fd(int fd, unsigned int block_size, int64_t len, int gz,
+                                        int sparse, int chunks, int crc) {
+  int ret;
+  struct output_file* out;
+
+  if (gz) {
+    out = output_file_new_gz();
+  } else {
+    out = output_file_new_normal();
+  }
+  if (!out) {
+    return NULL;
+  }
+
+  out->ops->open(out, fd);
+
+  ret = output_file_init(out, block_size, len, sparse, chunks, crc);
+  if (ret < 0) {
+    free(out);
+    return NULL;
+  }
+
+  return out;
+}
+
+/* Write a contiguous region of data blocks from a memory buffer */
+int write_data_chunk(struct output_file* out, unsigned int len, void* data) {
+  return out->sparse_ops->write_data_chunk(out, len, data);
+}
+
+/* Write a contiguous region of data blocks with a fill value */
+int write_fill_chunk(struct output_file* out, unsigned int len, uint32_t fill_val) {
+  return out->sparse_ops->write_fill_chunk(out, len, fill_val);
+}
+
+int write_fd_chunk(struct output_file* out, unsigned int len, int fd, int64_t offset) {
+  int ret;
+  int64_t aligned_offset;
+  int aligned_diff;
+  uint64_t buffer_size;
+  char* ptr;
+
+  aligned_offset = offset & ~(4096 - 1);
+  aligned_diff = offset - aligned_offset;
+  buffer_size = (uint64_t)len + (uint64_t)aligned_diff;
+
+#ifndef _WIN32
+  if (buffer_size > SIZE_MAX) return -E2BIG;
+  char* data =
+      reinterpret_cast<char*>(mmap64(NULL, buffer_size, PROT_READ, MAP_SHARED, fd, aligned_offset));
+  if (data == MAP_FAILED) {
+    return -errno;
+  }
+  ptr = data + aligned_diff;
+#else
+  off64_t pos;
+  char* data = reinterpret_cast<char*>(malloc(len));
+  if (!data) {
+    return -errno;
+  }
+  pos = lseek64(fd, offset, SEEK_SET);
+  if (pos < 0) {
+    free(data);
+    return -errno;
+  }
+  ret = read_all(fd, data, len);
+  if (ret < 0) {
+    free(data);
+    return ret;
+  }
+  ptr = data;
+#endif
+
+  ret = out->sparse_ops->write_data_chunk(out, len, ptr);
+
+#ifndef _WIN32
+  munmap(data, buffer_size);
+#else
+  free(data);
+#endif
+
+  return ret;
+}
+
+/* Write a contiguous region of data blocks from a file */
+int write_file_chunk(struct output_file* out, unsigned int len, const char* file, int64_t offset) {
+  int ret;
+
+  int file_fd = open(file, O_RDONLY | O_BINARY);
+  if (file_fd < 0) {
+    return -errno;
+  }
+
+  ret = write_fd_chunk(out, len, file_fd, offset);
+
+  close(file_fd);
+
+  return ret;
+}
+
+int write_skip_chunk(struct output_file* out, int64_t len) {
+  return out->sparse_ops->write_skip_chunk(out, len);
+}
diff --git a/libsparse/output_file.h b/libsparse/output_file.h
index 690f610..278430b 100644
--- a/libsparse/output_file.h
+++ b/libsparse/output_file.h
@@ -25,23 +25,19 @@
 
 struct output_file;
 
-struct output_file *output_file_open_fd(int fd, unsigned int block_size, int64_t len,
-		int gz, int sparse, int chunks, int crc);
-struct output_file *output_file_open_callback(
-		int (*write)(void *, const void *, size_t),
-		void *priv, unsigned int block_size, int64_t len, int gz, int sparse,
-		int chunks, int crc);
-int write_data_chunk(struct output_file *out, unsigned int len, void *data);
-int write_fill_chunk(struct output_file *out, unsigned int len,
-		uint32_t fill_val);
-int write_file_chunk(struct output_file *out, unsigned int len,
-		const char *file, int64_t offset);
-int write_fd_chunk(struct output_file *out, unsigned int len,
-		int fd, int64_t offset);
-int write_skip_chunk(struct output_file *out, int64_t len);
-void output_file_close(struct output_file *out);
+struct output_file* output_file_open_fd(int fd, unsigned int block_size, int64_t len, int gz,
+                                        int sparse, int chunks, int crc);
+struct output_file* output_file_open_callback(int (*write)(void*, const void*, size_t), void* priv,
+                                              unsigned int block_size, int64_t len, int gz,
+                                              int sparse, int chunks, int crc);
+int write_data_chunk(struct output_file* out, unsigned int len, void* data);
+int write_fill_chunk(struct output_file* out, unsigned int len, uint32_t fill_val);
+int write_file_chunk(struct output_file* out, unsigned int len, const char* file, int64_t offset);
+int write_fd_chunk(struct output_file* out, unsigned int len, int fd, int64_t offset);
+int write_skip_chunk(struct output_file* out, int64_t len);
+void output_file_close(struct output_file* out);
 
-int read_all(int fd, void *buf, size_t len);
+int read_all(int fd, void* buf, size_t len);
 
 #ifdef __cplusplus
 }
diff --git a/libsparse/simg2img.c b/libsparse/simg2img.c
deleted file mode 100644
index b9b438e..0000000
--- a/libsparse/simg2img.c
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2010 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 <sparse/sparse.h>
-
-#include <fcntl.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#ifndef O_BINARY
-#define O_BINARY 0
-#endif
-
-void usage()
-{
-  fprintf(stderr, "Usage: simg2img <sparse_image_files> <raw_image_file>\n");
-}
-
-int main(int argc, char *argv[])
-{
-	int in;
-	int out;
-	int i;
-	struct sparse_file *s;
-
-	if (argc < 3) {
-		usage();
-		exit(-1);
-	}
-
-	out = open(argv[argc - 1], O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664);
-	if (out < 0) {
-		fprintf(stderr, "Cannot open output file %s\n", argv[argc - 1]);
-		exit(-1);
-	}
-
-	for (i = 1; i < argc - 1; i++) {
-		if (strcmp(argv[i], "-") == 0) {
-			in = STDIN_FILENO;
-		} else {
-			in = open(argv[i], O_RDONLY | O_BINARY);
-			if (in < 0) {
-				fprintf(stderr, "Cannot open input file %s\n", argv[i]);
-				exit(-1);
-			}
-		}
-
-		s = sparse_file_import(in, true, false);
-		if (!s) {
-			fprintf(stderr, "Failed to read sparse file\n");
-			exit(-1);
-		}
-
-		if (lseek(out, 0, SEEK_SET) == -1) {
-			perror("lseek failed");
-			exit(EXIT_FAILURE);
-		}
-
-		if (sparse_file_write(s, out, false, false, false) < 0) {
-			fprintf(stderr, "Cannot write output file\n");
-			exit(-1);
-		}
-		sparse_file_destroy(s);
-		close(in);
-	}
-
-	close(out);
-
-	exit(0);
-}
-
diff --git a/libsparse/simg2img.cpp b/libsparse/simg2img.cpp
new file mode 100644
index 0000000..8ba5f69
--- /dev/null
+++ b/libsparse/simg2img.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2010 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 <sparse/sparse.h>
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+void usage() {
+  fprintf(stderr, "Usage: simg2img <sparse_image_files> <raw_image_file>\n");
+}
+
+int main(int argc, char* argv[]) {
+  int in;
+  int out;
+  int i;
+  struct sparse_file* s;
+
+  if (argc < 3) {
+    usage();
+    exit(-1);
+  }
+
+  out = open(argv[argc - 1], O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664);
+  if (out < 0) {
+    fprintf(stderr, "Cannot open output file %s\n", argv[argc - 1]);
+    exit(-1);
+  }
+
+  for (i = 1; i < argc - 1; i++) {
+    if (strcmp(argv[i], "-") == 0) {
+      in = STDIN_FILENO;
+    } else {
+      in = open(argv[i], O_RDONLY | O_BINARY);
+      if (in < 0) {
+        fprintf(stderr, "Cannot open input file %s\n", argv[i]);
+        exit(-1);
+      }
+    }
+
+    s = sparse_file_import(in, true, false);
+    if (!s) {
+      fprintf(stderr, "Failed to read sparse file\n");
+      exit(-1);
+    }
+
+    if (lseek(out, 0, SEEK_SET) == -1) {
+      perror("lseek failed");
+      exit(EXIT_FAILURE);
+    }
+
+    if (sparse_file_write(s, out, false, false, false) < 0) {
+      fprintf(stderr, "Cannot write output file\n");
+      exit(-1);
+    }
+    sparse_file_destroy(s);
+    close(in);
+  }
+
+  close(out);
+
+  exit(0);
+}
diff --git a/libsparse/simg2simg.c b/libsparse/simg2simg.c
deleted file mode 100644
index 5f9ccf6..0000000
--- a/libsparse/simg2simg.c
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define _FILE_OFFSET_BITS 64
-#define _LARGEFILE64_SOURCE 1
-#define _GNU_SOURCE
-
-#include <fcntl.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <sparse/sparse.h>
-
-#ifndef O_BINARY
-#define O_BINARY 0
-#endif
-
-void usage()
-{
-  fprintf(stderr, "Usage: simg2simg <sparse image file> <sparse_image_file> <max_size>\n");
-}
-
-int main(int argc, char *argv[])
-{
-	int in;
-	int out;
-	int i;
-	int ret;
-	struct sparse_file *s;
-	int64_t max_size;
-	struct sparse_file **out_s;
-	int files;
-	char filename[4096];
-
-	if (argc != 4) {
-		usage();
-		exit(-1);
-	}
-
-	max_size = atoll(argv[3]);
-
-	in = open(argv[1], O_RDONLY | O_BINARY);
-	if (in < 0) {
-		fprintf(stderr, "Cannot open input file %s\n", argv[1]);
-		exit(-1);
-	}
-
-	s = sparse_file_import(in, true, false);
-	if (!s) {
-		fprintf(stderr, "Failed to import sparse file\n");
-		exit(-1);
-	}
-
-	files = sparse_file_resparse(s, max_size, NULL, 0);
-	if (files < 0) {
-		fprintf(stderr, "Failed to resparse\n");
-		exit(-1);
-	}
-
-	out_s = calloc(sizeof(struct sparse_file *), files);
-	if (!out_s) {
-		fprintf(stderr, "Failed to allocate sparse file array\n");
-		exit(-1);
-	}
-
-	files = sparse_file_resparse(s, max_size, out_s, files);
-	if (files < 0) {
-		fprintf(stderr, "Failed to resparse\n");
-		exit(-1);
-	}
-
-	for (i = 0; i < files; i++) {
-		ret = snprintf(filename, sizeof(filename), "%s.%d", argv[2], i);
-		if (ret >= (int)sizeof(filename)) {
-			fprintf(stderr, "Filename too long\n");
-			exit(-1);
-		}
-
-		out = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664);
-		if (out < 0) {
-			fprintf(stderr, "Cannot open output file %s\n", argv[2]);
-			exit(-1);
-		}
-
-		ret = sparse_file_write(out_s[i], out, false, true, false);
-		if (ret) {
-			fprintf(stderr, "Failed to write sparse file\n");
-			exit(-1);
-		}
-		close(out);
-	}
-
-	close(in);
-
-	exit(0);
-}
diff --git a/libsparse/simg2simg.cpp b/libsparse/simg2simg.cpp
new file mode 100644
index 0000000..7e65701
--- /dev/null
+++ b/libsparse/simg2simg.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _FILE_OFFSET_BITS 64
+#define _LARGEFILE64_SOURCE 1
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <sparse/sparse.h>
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+void usage() {
+  fprintf(stderr, "Usage: simg2simg <sparse image file> <sparse_image_file> <max_size>\n");
+}
+
+int main(int argc, char* argv[]) {
+  int in;
+  int out;
+  int i;
+  int ret;
+  struct sparse_file* s;
+  int64_t max_size;
+  struct sparse_file** out_s;
+  int files;
+  char filename[4096];
+
+  if (argc != 4) {
+    usage();
+    exit(-1);
+  }
+
+  max_size = atoll(argv[3]);
+
+  in = open(argv[1], O_RDONLY | O_BINARY);
+  if (in < 0) {
+    fprintf(stderr, "Cannot open input file %s\n", argv[1]);
+    exit(-1);
+  }
+
+  s = sparse_file_import(in, true, false);
+  if (!s) {
+    fprintf(stderr, "Failed to import sparse file\n");
+    exit(-1);
+  }
+
+  files = sparse_file_resparse(s, max_size, NULL, 0);
+  if (files < 0) {
+    fprintf(stderr, "Failed to resparse\n");
+    exit(-1);
+  }
+
+  out_s = calloc(sizeof(struct sparse_file*), files);
+  if (!out_s) {
+    fprintf(stderr, "Failed to allocate sparse file array\n");
+    exit(-1);
+  }
+
+  files = sparse_file_resparse(s, max_size, out_s, files);
+  if (files < 0) {
+    fprintf(stderr, "Failed to resparse\n");
+    exit(-1);
+  }
+
+  for (i = 0; i < files; i++) {
+    ret = snprintf(filename, sizeof(filename), "%s.%d", argv[2], i);
+    if (ret >= (int)sizeof(filename)) {
+      fprintf(stderr, "Filename too long\n");
+      exit(-1);
+    }
+
+    out = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664);
+    if (out < 0) {
+      fprintf(stderr, "Cannot open output file %s\n", argv[2]);
+      exit(-1);
+    }
+
+    ret = sparse_file_write(out_s[i], out, false, true, false);
+    if (ret) {
+      fprintf(stderr, "Failed to write sparse file\n");
+      exit(-1);
+    }
+    close(out);
+  }
+
+  close(in);
+
+  exit(0);
+}
diff --git a/libsparse/sparse.c b/libsparse/sparse.c
deleted file mode 100644
index 466435f..0000000
--- a/libsparse/sparse.c
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright (C) 2012 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 <assert.h>
-#include <stdlib.h>
-
-#include <sparse/sparse.h>
-
-#include "defs.h"
-#include "sparse_file.h"
-
-#include "output_file.h"
-#include "backed_block.h"
-#include "sparse_defs.h"
-#include "sparse_format.h"
-
-struct sparse_file *sparse_file_new(unsigned int block_size, int64_t len)
-{
-	struct sparse_file *s = calloc(sizeof(struct sparse_file), 1);
-	if (!s) {
-		return NULL;
-	}
-
-	s->backed_block_list = backed_block_list_new(block_size);
-	if (!s->backed_block_list) {
-		free(s);
-		return NULL;
-	}
-
-	s->block_size = block_size;
-	s->len = len;
-
-	return s;
-}
-
-void sparse_file_destroy(struct sparse_file *s)
-{
-	backed_block_list_destroy(s->backed_block_list);
-	free(s);
-}
-
-int sparse_file_add_data(struct sparse_file *s,
-		void *data, unsigned int len, unsigned int block)
-{
-	return backed_block_add_data(s->backed_block_list, data, len, block);
-}
-
-int sparse_file_add_fill(struct sparse_file *s,
-		uint32_t fill_val, unsigned int len, unsigned int block)
-{
-	return backed_block_add_fill(s->backed_block_list, fill_val, len, block);
-}
-
-int sparse_file_add_file(struct sparse_file *s,
-		const char *filename, int64_t file_offset, unsigned int len,
-		unsigned int block)
-{
-	return backed_block_add_file(s->backed_block_list, filename, file_offset,
-			len, block);
-}
-
-int sparse_file_add_fd(struct sparse_file *s,
-		int fd, int64_t file_offset, unsigned int len, unsigned int block)
-{
-	return backed_block_add_fd(s->backed_block_list, fd, file_offset,
-			len, block);
-}
-unsigned int sparse_count_chunks(struct sparse_file *s)
-{
-	struct backed_block *bb;
-	unsigned int last_block = 0;
-	unsigned int chunks = 0;
-
-	for (bb = backed_block_iter_new(s->backed_block_list); bb;
-			bb = backed_block_iter_next(bb)) {
-		if (backed_block_block(bb) > last_block) {
-			/* If there is a gap between chunks, add a skip chunk */
-			chunks++;
-		}
-		chunks++;
-		last_block = backed_block_block(bb) +
-				DIV_ROUND_UP(backed_block_len(bb), s->block_size);
-	}
-	if (last_block < DIV_ROUND_UP(s->len, s->block_size)) {
-		chunks++;
-	}
-
-	return chunks;
-}
-
-static int sparse_file_write_block(struct output_file *out,
-		struct backed_block *bb)
-{
-	int ret = -EINVAL;
-
-	switch (backed_block_type(bb)) {
-	case BACKED_BLOCK_DATA:
-		ret = write_data_chunk(out, backed_block_len(bb), backed_block_data(bb));
-		break;
-	case BACKED_BLOCK_FILE:
-		ret = write_file_chunk(out, backed_block_len(bb),
-				       backed_block_filename(bb),
-				       backed_block_file_offset(bb));
-		break;
-	case BACKED_BLOCK_FD:
-		ret = write_fd_chunk(out, backed_block_len(bb),
-				     backed_block_fd(bb),
-				     backed_block_file_offset(bb));
-		break;
-	case BACKED_BLOCK_FILL:
-		ret = write_fill_chunk(out, backed_block_len(bb),
-				       backed_block_fill_val(bb));
-		break;
-	}
-
-	return ret;
-}
-
-static int write_all_blocks(struct sparse_file *s, struct output_file *out)
-{
-	struct backed_block *bb;
-	unsigned int last_block = 0;
-	int64_t pad;
-	int ret = 0;
-
-	for (bb = backed_block_iter_new(s->backed_block_list); bb;
-			bb = backed_block_iter_next(bb)) {
-		if (backed_block_block(bb) > last_block) {
-			unsigned int blocks = backed_block_block(bb) - last_block;
-			write_skip_chunk(out, (int64_t)blocks * s->block_size);
-		}
-		ret = sparse_file_write_block(out, bb);
-		if (ret)
-			return ret;
-		last_block = backed_block_block(bb) +
-				DIV_ROUND_UP(backed_block_len(bb), s->block_size);
-	}
-
-	pad = s->len - (int64_t)last_block * s->block_size;
-	assert(pad >= 0);
-	if (pad > 0) {
-		write_skip_chunk(out, pad);
-	}
-
-	return 0;
-}
-
-int sparse_file_write(struct sparse_file *s, int fd, bool gz, bool sparse,
-		bool crc)
-{
-	int ret;
-	int chunks;
-	struct output_file *out;
-
-	chunks = sparse_count_chunks(s);
-	out = output_file_open_fd(fd, s->block_size, s->len, gz, sparse, chunks, crc);
-
-	if (!out)
-		return -ENOMEM;
-
-	ret = write_all_blocks(s, out);
-
-	output_file_close(out);
-
-	return ret;
-}
-
-int sparse_file_callback(struct sparse_file *s, bool sparse, bool crc,
-		int (*write)(void *priv, const void *data, size_t len), void *priv)
-{
-	int ret;
-	int chunks;
-	struct output_file *out;
-
-	chunks = sparse_count_chunks(s);
-	out = output_file_open_callback(write, priv, s->block_size, s->len, false,
-			sparse, chunks, crc);
-
-	if (!out)
-		return -ENOMEM;
-
-	ret = write_all_blocks(s, out);
-
-	output_file_close(out);
-
-	return ret;
-}
-
-struct chunk_data {
-	void		*priv;
-	unsigned int	block;
-	unsigned int	nr_blocks;
-	int (*write)(void *priv, const void *data, size_t len,
-		     unsigned int block, unsigned int nr_blocks);
-};
-
-static int foreach_chunk_write(void *priv, const void *data, size_t len)
-{
-	struct chunk_data *chk = priv;
-
-	return chk->write(chk->priv, data, len, chk->block, chk->nr_blocks);
-}
-
-int sparse_file_foreach_chunk(struct sparse_file *s, bool sparse, bool crc,
-	int (*write)(void *priv, const void *data, size_t len, unsigned int block,
-		     unsigned int nr_blocks),
-	void *priv)
-{
-	int ret;
-	int chunks;
-	struct chunk_data chk;
-	struct output_file *out;
-	struct backed_block *bb;
-
-	chk.priv = priv;
-	chk.write = write;
-	chk.block = chk.nr_blocks = 0;
-	chunks = sparse_count_chunks(s);
-	out = output_file_open_callback(foreach_chunk_write, &chk,
-					s->block_size, s->len, false, sparse,
-					chunks, crc);
-
-	if (!out)
-		return -ENOMEM;
-
-	for (bb = backed_block_iter_new(s->backed_block_list); bb;
-			bb = backed_block_iter_next(bb)) {
-		chk.block = backed_block_block(bb);
-		chk.nr_blocks = (backed_block_len(bb) - 1) / s->block_size + 1;
-		ret = sparse_file_write_block(out, bb);
-		if (ret)
-			return ret;
-	}
-
-	output_file_close(out);
-
-	return ret;
-}
-
-static int out_counter_write(void *priv, const void *data __unused, size_t len)
-{
-	int64_t *count = priv;
-	*count += len;
-	return 0;
-}
-
-int64_t sparse_file_len(struct sparse_file *s, bool sparse, bool crc)
-{
-	int ret;
-	int chunks = sparse_count_chunks(s);
-	int64_t count = 0;
-	struct output_file *out;
-
-	out = output_file_open_callback(out_counter_write, &count,
-			s->block_size, s->len, false, sparse, chunks, crc);
-	if (!out) {
-		return -1;
-	}
-
-	ret = write_all_blocks(s, out);
-
-	output_file_close(out);
-
-	if (ret < 0) {
-		return -1;
-	}
-
-	return count;
-}
-
-unsigned int sparse_file_block_size(struct sparse_file *s)
-{
-	return s->block_size;
-}
-
-static struct backed_block *move_chunks_up_to_len(struct sparse_file *from,
-		struct sparse_file *to, unsigned int len)
-{
-	int64_t count = 0;
-	struct output_file *out_counter;
-	struct backed_block *last_bb = NULL;
-	struct backed_block *bb;
-	struct backed_block *start;
-	unsigned int last_block = 0;
-	int64_t file_len = 0;
-	int ret;
-
-	/*
-	 * overhead is sparse file header, the potential end skip
-	 * chunk and crc chunk.
-	 */
-	int overhead = sizeof(sparse_header_t) + 2 * sizeof(chunk_header_t) +
-			sizeof(uint32_t);
-	len -= overhead;
-
-	start = backed_block_iter_new(from->backed_block_list);
-	out_counter = output_file_open_callback(out_counter_write, &count,
-			to->block_size, to->len, false, true, 0, false);
-	if (!out_counter) {
-		return NULL;
-	}
-
-	for (bb = start; bb; bb = backed_block_iter_next(bb)) {
-		count = 0;
-		if (backed_block_block(bb) > last_block)
-			count += sizeof(chunk_header_t);
-		last_block = backed_block_block(bb) +
-				DIV_ROUND_UP(backed_block_len(bb), to->block_size);
-
-		/* will call out_counter_write to update count */
-		ret = sparse_file_write_block(out_counter, bb);
-		if (ret) {
-			bb = NULL;
-			goto out;
-		}
-		if (file_len + count > len) {
-			/*
-			 * If the remaining available size is more than 1/8th of the
-			 * requested size, split the chunk.  Results in sparse files that
-			 * are at least 7/8ths of the requested size
-			 */
-			file_len += sizeof(chunk_header_t);
-			if (!last_bb || (len - file_len > (len / 8))) {
-				backed_block_split(from->backed_block_list, bb, len - file_len);
-				last_bb = bb;
-			}
-			goto move;
-		}
-		file_len += count;
-		last_bb = bb;
-	}
-
-move:
-	backed_block_list_move(from->backed_block_list,
-		to->backed_block_list, start, last_bb);
-
-out:
-	output_file_close(out_counter);
-
-	return bb;
-}
-
-int sparse_file_resparse(struct sparse_file *in_s, unsigned int max_len,
-		struct sparse_file **out_s, int out_s_count)
-{
-	struct backed_block *bb;
-	struct sparse_file *s;
-	struct sparse_file *tmp;
-	int c = 0;
-
-	tmp = sparse_file_new(in_s->block_size, in_s->len);
-	if (!tmp) {
-		return -ENOMEM;
-	}
-
-	do {
-		s = sparse_file_new(in_s->block_size, in_s->len);
-
-		bb = move_chunks_up_to_len(in_s, s, max_len);
-
-		if (c < out_s_count) {
-			out_s[c] = s;
-		} else {
-			backed_block_list_move(s->backed_block_list, tmp->backed_block_list,
-					NULL, NULL);
-			sparse_file_destroy(s);
-		}
-		c++;
-	} while (bb);
-
-	backed_block_list_move(tmp->backed_block_list, in_s->backed_block_list,
-			NULL, NULL);
-
-	sparse_file_destroy(tmp);
-
-	return c;
-}
-
-void sparse_file_verbose(struct sparse_file *s)
-{
-	s->verbose = true;
-}
diff --git a/libsparse/sparse.cpp b/libsparse/sparse.cpp
new file mode 100644
index 0000000..6ff97b6
--- /dev/null
+++ b/libsparse/sparse.cpp
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2012 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 <assert.h>
+#include <stdlib.h>
+
+#include <sparse/sparse.h>
+
+#include "defs.h"
+#include "sparse_file.h"
+
+#include "backed_block.h"
+#include "output_file.h"
+#include "sparse_defs.h"
+#include "sparse_format.h"
+
+struct sparse_file* sparse_file_new(unsigned int block_size, int64_t len) {
+  struct sparse_file* s = reinterpret_cast<sparse_file*>(calloc(sizeof(struct sparse_file), 1));
+  if (!s) {
+    return NULL;
+  }
+
+  s->backed_block_list = backed_block_list_new(block_size);
+  if (!s->backed_block_list) {
+    free(s);
+    return NULL;
+  }
+
+  s->block_size = block_size;
+  s->len = len;
+
+  return s;
+}
+
+void sparse_file_destroy(struct sparse_file* s) {
+  backed_block_list_destroy(s->backed_block_list);
+  free(s);
+}
+
+int sparse_file_add_data(struct sparse_file* s, void* data, unsigned int len, unsigned int block) {
+  return backed_block_add_data(s->backed_block_list, data, len, block);
+}
+
+int sparse_file_add_fill(struct sparse_file* s, uint32_t fill_val, unsigned int len,
+                         unsigned int block) {
+  return backed_block_add_fill(s->backed_block_list, fill_val, len, block);
+}
+
+int sparse_file_add_file(struct sparse_file* s, const char* filename, int64_t file_offset,
+                         unsigned int len, unsigned int block) {
+  return backed_block_add_file(s->backed_block_list, filename, file_offset, len, block);
+}
+
+int sparse_file_add_fd(struct sparse_file* s, int fd, int64_t file_offset, unsigned int len,
+                       unsigned int block) {
+  return backed_block_add_fd(s->backed_block_list, fd, file_offset, len, block);
+}
+unsigned int sparse_count_chunks(struct sparse_file* s) {
+  struct backed_block* bb;
+  unsigned int last_block = 0;
+  unsigned int chunks = 0;
+
+  for (bb = backed_block_iter_new(s->backed_block_list); bb; bb = backed_block_iter_next(bb)) {
+    if (backed_block_block(bb) > last_block) {
+      /* If there is a gap between chunks, add a skip chunk */
+      chunks++;
+    }
+    chunks++;
+    last_block = backed_block_block(bb) + DIV_ROUND_UP(backed_block_len(bb), s->block_size);
+  }
+  if (last_block < DIV_ROUND_UP(s->len, s->block_size)) {
+    chunks++;
+  }
+
+  return chunks;
+}
+
+static int sparse_file_write_block(struct output_file* out, struct backed_block* bb) {
+  int ret = -EINVAL;
+
+  switch (backed_block_type(bb)) {
+    case BACKED_BLOCK_DATA:
+      ret = write_data_chunk(out, backed_block_len(bb), backed_block_data(bb));
+      break;
+    case BACKED_BLOCK_FILE:
+      ret = write_file_chunk(out, backed_block_len(bb), backed_block_filename(bb),
+                             backed_block_file_offset(bb));
+      break;
+    case BACKED_BLOCK_FD:
+      ret = write_fd_chunk(out, backed_block_len(bb), backed_block_fd(bb),
+                           backed_block_file_offset(bb));
+      break;
+    case BACKED_BLOCK_FILL:
+      ret = write_fill_chunk(out, backed_block_len(bb), backed_block_fill_val(bb));
+      break;
+  }
+
+  return ret;
+}
+
+static int write_all_blocks(struct sparse_file* s, struct output_file* out) {
+  struct backed_block* bb;
+  unsigned int last_block = 0;
+  int64_t pad;
+  int ret = 0;
+
+  for (bb = backed_block_iter_new(s->backed_block_list); bb; bb = backed_block_iter_next(bb)) {
+    if (backed_block_block(bb) > last_block) {
+      unsigned int blocks = backed_block_block(bb) - last_block;
+      write_skip_chunk(out, (int64_t)blocks * s->block_size);
+    }
+    ret = sparse_file_write_block(out, bb);
+    if (ret) return ret;
+    last_block = backed_block_block(bb) + DIV_ROUND_UP(backed_block_len(bb), s->block_size);
+  }
+
+  pad = s->len - (int64_t)last_block * s->block_size;
+  assert(pad >= 0);
+  if (pad > 0) {
+    write_skip_chunk(out, pad);
+  }
+
+  return 0;
+}
+
+int sparse_file_write(struct sparse_file* s, int fd, bool gz, bool sparse, bool crc) {
+  int ret;
+  int chunks;
+  struct output_file* out;
+
+  chunks = sparse_count_chunks(s);
+  out = output_file_open_fd(fd, s->block_size, s->len, gz, sparse, chunks, crc);
+
+  if (!out) return -ENOMEM;
+
+  ret = write_all_blocks(s, out);
+
+  output_file_close(out);
+
+  return ret;
+}
+
+int sparse_file_callback(struct sparse_file* s, bool sparse, bool crc,
+                         int (*write)(void* priv, const void* data, size_t len), void* priv) {
+  int ret;
+  int chunks;
+  struct output_file* out;
+
+  chunks = sparse_count_chunks(s);
+  out = output_file_open_callback(write, priv, s->block_size, s->len, false, sparse, chunks, crc);
+
+  if (!out) return -ENOMEM;
+
+  ret = write_all_blocks(s, out);
+
+  output_file_close(out);
+
+  return ret;
+}
+
+struct chunk_data {
+  void* priv;
+  unsigned int block;
+  unsigned int nr_blocks;
+  int (*write)(void* priv, const void* data, size_t len, unsigned int block, unsigned int nr_blocks);
+};
+
+static int foreach_chunk_write(void* priv, const void* data, size_t len) {
+  struct chunk_data* chk = reinterpret_cast<chunk_data*>(priv);
+
+  return chk->write(chk->priv, data, len, chk->block, chk->nr_blocks);
+}
+
+int sparse_file_foreach_chunk(struct sparse_file* s, bool sparse, bool crc,
+                              int (*write)(void* priv, const void* data, size_t len,
+                                           unsigned int block, unsigned int nr_blocks),
+                              void* priv) {
+  int ret;
+  int chunks;
+  struct chunk_data chk;
+  struct output_file* out;
+  struct backed_block* bb;
+
+  chk.priv = priv;
+  chk.write = write;
+  chk.block = chk.nr_blocks = 0;
+  chunks = sparse_count_chunks(s);
+  out = output_file_open_callback(foreach_chunk_write, &chk, s->block_size, s->len, false, sparse,
+                                  chunks, crc);
+
+  if (!out) return -ENOMEM;
+
+  for (bb = backed_block_iter_new(s->backed_block_list); bb; bb = backed_block_iter_next(bb)) {
+    chk.block = backed_block_block(bb);
+    chk.nr_blocks = (backed_block_len(bb) - 1) / s->block_size + 1;
+    ret = sparse_file_write_block(out, bb);
+    if (ret) return ret;
+  }
+
+  output_file_close(out);
+
+  return ret;
+}
+
+static int out_counter_write(void* priv, const void* data __unused, size_t len) {
+  int64_t* count = reinterpret_cast<int64_t*>(priv);
+  *count += len;
+  return 0;
+}
+
+int64_t sparse_file_len(struct sparse_file* s, bool sparse, bool crc) {
+  int ret;
+  int chunks = sparse_count_chunks(s);
+  int64_t count = 0;
+  struct output_file* out;
+
+  out = output_file_open_callback(out_counter_write, &count, s->block_size, s->len, false, sparse,
+                                  chunks, crc);
+  if (!out) {
+    return -1;
+  }
+
+  ret = write_all_blocks(s, out);
+
+  output_file_close(out);
+
+  if (ret < 0) {
+    return -1;
+  }
+
+  return count;
+}
+
+unsigned int sparse_file_block_size(struct sparse_file* s) {
+  return s->block_size;
+}
+
+static struct backed_block* move_chunks_up_to_len(struct sparse_file* from, struct sparse_file* to,
+                                                  unsigned int len) {
+  int64_t count = 0;
+  struct output_file* out_counter;
+  struct backed_block* last_bb = NULL;
+  struct backed_block* bb;
+  struct backed_block* start;
+  unsigned int last_block = 0;
+  int64_t file_len = 0;
+  int ret;
+
+  /*
+   * overhead is sparse file header, the potential end skip
+   * chunk and crc chunk.
+   */
+  int overhead = sizeof(sparse_header_t) + 2 * sizeof(chunk_header_t) + sizeof(uint32_t);
+  len -= overhead;
+
+  start = backed_block_iter_new(from->backed_block_list);
+  out_counter = output_file_open_callback(out_counter_write, &count, to->block_size, to->len, false,
+                                          true, 0, false);
+  if (!out_counter) {
+    return NULL;
+  }
+
+  for (bb = start; bb; bb = backed_block_iter_next(bb)) {
+    count = 0;
+    if (backed_block_block(bb) > last_block) count += sizeof(chunk_header_t);
+    last_block = backed_block_block(bb) + DIV_ROUND_UP(backed_block_len(bb), to->block_size);
+
+    /* will call out_counter_write to update count */
+    ret = sparse_file_write_block(out_counter, bb);
+    if (ret) {
+      bb = NULL;
+      goto out;
+    }
+    if (file_len + count > len) {
+      /*
+       * If the remaining available size is more than 1/8th of the
+       * requested size, split the chunk.  Results in sparse files that
+       * are at least 7/8ths of the requested size
+       */
+      file_len += sizeof(chunk_header_t);
+      if (!last_bb || (len - file_len > (len / 8))) {
+        backed_block_split(from->backed_block_list, bb, len - file_len);
+        last_bb = bb;
+      }
+      goto move;
+    }
+    file_len += count;
+    last_bb = bb;
+  }
+
+move:
+  backed_block_list_move(from->backed_block_list, to->backed_block_list, start, last_bb);
+
+out:
+  output_file_close(out_counter);
+
+  return bb;
+}
+
+int sparse_file_resparse(struct sparse_file* in_s, unsigned int max_len, struct sparse_file** out_s,
+                         int out_s_count) {
+  struct backed_block* bb;
+  struct sparse_file* s;
+  struct sparse_file* tmp;
+  int c = 0;
+
+  tmp = sparse_file_new(in_s->block_size, in_s->len);
+  if (!tmp) {
+    return -ENOMEM;
+  }
+
+  do {
+    s = sparse_file_new(in_s->block_size, in_s->len);
+
+    bb = move_chunks_up_to_len(in_s, s, max_len);
+
+    if (c < out_s_count) {
+      out_s[c] = s;
+    } else {
+      backed_block_list_move(s->backed_block_list, tmp->backed_block_list, NULL, NULL);
+      sparse_file_destroy(s);
+    }
+    c++;
+  } while (bb);
+
+  backed_block_list_move(tmp->backed_block_list, in_s->backed_block_list, NULL, NULL);
+
+  sparse_file_destroy(tmp);
+
+  return c;
+}
+
+void sparse_file_verbose(struct sparse_file* s) {
+  s->verbose = true;
+}
diff --git a/libsparse/sparse_crc32.c b/libsparse/sparse_crc32.c
deleted file mode 100644
index 38bfe4a..0000000
--- a/libsparse/sparse_crc32.c
+++ /dev/null
@@ -1,111 +0,0 @@
-/*-
- *  COPYRIGHT (C) 1986 Gary S. Brown.  You may use this program, or
- *  code or tables extracted from it, as desired without restriction.
- */
-
-/*
- *  First, the polynomial itself and its table of feedback terms.  The
- *  polynomial is
- *  X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
- *
- *  Note that we take it "backwards" and put the highest-order term in
- *  the lowest-order bit.  The X^32 term is "implied"; the LSB is the
- *  X^31 term, etc.  The X^0 term (usually shown as "+1") results in
- *  the MSB being 1
- *
- *  Note that the usual hardware shift register implementation, which
- *  is what we're using (we're merely optimizing it by doing eight-bit
- *  chunks at a time) shifts bits into the lowest-order term.  In our
- *  implementation, that means shifting towards the right.  Why do we
- *  do it this way?  Because the calculated CRC must be transmitted in
- *  order from highest-order term to lowest-order term.  UARTs transmit
- *  characters in order from LSB to MSB.  By storing the CRC this way
- *  we hand it to the UART in the order low-byte to high-byte; the UART
- *  sends each low-bit to hight-bit; and the result is transmission bit
- *  by bit from highest- to lowest-order term without requiring any bit
- *  shuffling on our part.  Reception works similarly
- *
- *  The feedback terms table consists of 256, 32-bit entries.  Notes
- *
- *      The table can be generated at runtime if desired; code to do so
- *      is shown later.  It might not be obvious, but the feedback
- *      terms simply represent the results of eight shift/xor opera
- *      tions for all combinations of data and CRC register values
- *
- *      The values must be right-shifted by eight bits by the "updcrc
- *      logic; the shift must be unsigned (bring in zeroes).  On some
- *      hardware you could probably optimize the shift in assembler by
- *      using byte-swap instructions
- *      polynomial $edb88320
- *
- *
- * CRC32 code derived from work by Gary S. Brown.
- */
-
-/* Code taken from FreeBSD 8 */
-#include <stdint.h>
-
-static uint32_t crc32_tab[] = {
-        0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
-        0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
-        0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
-        0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
-        0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
-        0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
-        0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
-        0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
-        0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
-        0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
-        0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
-        0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
-        0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
-        0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
-        0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
-        0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
-        0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
-        0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
-        0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
-        0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
-        0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
-        0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
-        0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
-        0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
-        0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
-        0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
-        0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
-        0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
-        0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
-        0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
-        0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
-        0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
-        0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
-        0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
-        0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
-        0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
-        0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
-        0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
-        0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
-        0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
-        0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
-        0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
-        0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
-};
-
-/*
- * A function that calculates the CRC-32 based on the table above is
- * given below for documentation purposes. An equivalent implementation
- * of this function that's actually used in the kernel can be found
- * in sys/libkern.h, where it can be inlined.
- */
-
-uint32_t sparse_crc32(uint32_t crc_in, const void *buf, int size)
-{
-        const uint8_t *p = buf;
-        uint32_t crc;
-
-        crc = crc_in ^ ~0U;
-        while (size--)
-                crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
-        return crc ^ ~0U;
-}
-
diff --git a/libsparse/sparse_crc32.cpp b/libsparse/sparse_crc32.cpp
new file mode 100644
index 0000000..267322c
--- /dev/null
+++ b/libsparse/sparse_crc32.cpp
@@ -0,0 +1,97 @@
+/*-
+ *  COPYRIGHT (C) 1986 Gary S. Brown.  You may use this program, or
+ *  code or tables extracted from it, as desired without restriction.
+ */
+
+/*
+ *  First, the polynomial itself and its table of feedback terms.  The
+ *  polynomial is
+ *  X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
+ *
+ *  Note that we take it "backwards" and put the highest-order term in
+ *  the lowest-order bit.  The X^32 term is "implied"; the LSB is the
+ *  X^31 term, etc.  The X^0 term (usually shown as "+1") results in
+ *  the MSB being 1
+ *
+ *  Note that the usual hardware shift register implementation, which
+ *  is what we're using (we're merely optimizing it by doing eight-bit
+ *  chunks at a time) shifts bits into the lowest-order term.  In our
+ *  implementation, that means shifting towards the right.  Why do we
+ *  do it this way?  Because the calculated CRC must be transmitted in
+ *  order from highest-order term to lowest-order term.  UARTs transmit
+ *  characters in order from LSB to MSB.  By storing the CRC this way
+ *  we hand it to the UART in the order low-byte to high-byte; the UART
+ *  sends each low-bit to hight-bit; and the result is transmission bit
+ *  by bit from highest- to lowest-order term without requiring any bit
+ *  shuffling on our part.  Reception works similarly
+ *
+ *  The feedback terms table consists of 256, 32-bit entries.  Notes
+ *
+ *      The table can be generated at runtime if desired; code to do so
+ *      is shown later.  It might not be obvious, but the feedback
+ *      terms simply represent the results of eight shift/xor opera
+ *      tions for all combinations of data and CRC register values
+ *
+ *      The values must be right-shifted by eight bits by the "updcrc
+ *      logic; the shift must be unsigned (bring in zeroes).  On some
+ *      hardware you could probably optimize the shift in assembler by
+ *      using byte-swap instructions
+ *      polynomial $edb88320
+ *
+ *
+ * CRC32 code derived from work by Gary S. Brown.
+ */
+
+/* Code taken from FreeBSD 8 */
+#include <stdint.h>
+#include <stdio.h>
+
+static uint32_t crc32_tab[] = {
+    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+    0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+    0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+    0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+    0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+    0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+    0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+    0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+    0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+    0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+    0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+    0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+    0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+    0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+    0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+    0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+    0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+    0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+    0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+    0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+    0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+    0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
+
+/*
+ * A function that calculates the CRC-32 based on the table above is
+ * given below for documentation purposes. An equivalent implementation
+ * of this function that's actually used in the kernel can be found
+ * in sys/libkern.h, where it can be inlined.
+ */
+
+uint32_t sparse_crc32(uint32_t crc_in, const void* buf, size_t size) {
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(buf);
+  uint32_t crc;
+
+  crc = crc_in ^ ~0U;
+  while (size--) crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
+  return crc ^ ~0U;
+}
diff --git a/libsparse/sparse_crc32.h b/libsparse/sparse_crc32.h
index 50cd9e9..2702c4f 100644
--- a/libsparse/sparse_crc32.h
+++ b/libsparse/sparse_crc32.h
@@ -19,14 +19,6 @@
 
 #include <stdint.h>
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-uint32_t sparse_crc32(uint32_t crc, const void *buf, size_t size);
-
-#ifdef __cplusplus
-}
-#endif
+uint32_t sparse_crc32(uint32_t crc, const void* buf, size_t size);
 
 #endif
diff --git a/libsparse/sparse_defs.h b/libsparse/sparse_defs.h
index b99cfd5..9137805 100644
--- a/libsparse/sparse_defs.h
+++ b/libsparse/sparse_defs.h
@@ -39,11 +39,14 @@
 typedef unsigned short int u16;
 typedef unsigned char u8;
 
-#define DIV_ROUND_UP(x, y) (((x) + (y) - 1)/(y))
-#define ALIGN(x, y) ((y) * DIV_ROUND_UP((x), (y)))
+#define DIV_ROUND_UP(x, y) (((x) + (y)-1) / (y))
+#define ALIGN(x, y) ((y)*DIV_ROUND_UP((x), (y)))
 #define ALIGN_DOWN(x, y) ((y) * ((x) / (y)))
 
-#define error(fmt, args...) do { fprintf(stderr, "error: %s: " fmt "\n", __func__, ## args); } while (0)
+#define error(fmt, args...)                                    \
+  do {                                                         \
+    fprintf(stderr, "error: %s: " fmt "\n", __func__, ##args); \
+  } while (0)
 #define error_errno(s, args...) error(s ": %s", ##args, strerror(errno))
 
 #endif
diff --git a/libsparse/sparse_err.c b/libsparse/sparse_err.cpp
similarity index 73%
rename from libsparse/sparse_err.c
rename to libsparse/sparse_err.cpp
index 0f392ad..6886d31 100644
--- a/libsparse/sparse_err.c
+++ b/libsparse/sparse_err.cpp
@@ -20,14 +20,13 @@
 #include <stdio.h>
 #include <unistd.h>
 
-void sparse_default_print(const char *fmt, ...)
-{
-	va_list argp;
+void sparse_default_print(const char* fmt, ...) {
+  va_list argp;
 
-	va_start(argp, fmt);
-	vfprintf(stderr, fmt, argp);
-	va_end(argp);
+  va_start(argp, fmt);
+  vfprintf(stderr, fmt, argp);
+  va_end(argp);
 }
 
-void (*sparse_print_error)(const char *fmt, ...) = sparse_default_print;
-void (*sparse_print_verbose)(const char *fmt, ...) = sparse_default_print;
+void (*sparse_print_error)(const char* fmt, ...) = sparse_default_print;
+void (*sparse_print_verbose)(const char* fmt, ...) = sparse_default_print;
diff --git a/libsparse/sparse_file.h b/libsparse/sparse_file.h
index 763f43f..e565f63 100644
--- a/libsparse/sparse_file.h
+++ b/libsparse/sparse_file.h
@@ -24,12 +24,12 @@
 #include <sparse/sparse.h>
 
 struct sparse_file {
-	unsigned int block_size;
-	int64_t len;
-	bool verbose;
+  unsigned int block_size;
+  int64_t len;
+  bool verbose;
 
-	struct backed_block_list *backed_block_list;
-	struct output_file *out;
+  struct backed_block_list* backed_block_list;
+  struct output_file* out;
 };
 
 #ifdef __cplusplus
diff --git a/libsparse/sparse_format.h b/libsparse/sparse_format.h
index 779e038..a8a721e 100644
--- a/libsparse/sparse_format.h
+++ b/libsparse/sparse_format.h
@@ -23,31 +23,31 @@
 #endif
 
 typedef struct sparse_header {
-  __le32	magic;		/* 0xed26ff3a */
-  __le16	major_version;	/* (0x1) - reject images with higher major versions */
-  __le16	minor_version;	/* (0x0) - allow images with higer minor versions */
-  __le16	file_hdr_sz;	/* 28 bytes for first revision of the file format */
-  __le16	chunk_hdr_sz;	/* 12 bytes for first revision of the file format */
-  __le32	blk_sz;		/* block size in bytes, must be a multiple of 4 (4096) */
-  __le32	total_blks;	/* total blocks in the non-sparse output image */
-  __le32	total_chunks;	/* total chunks in the sparse input image */
-  __le32	image_checksum; /* CRC32 checksum of the original data, counting "don't care" */
-				/* as 0. Standard 802.3 polynomial, use a Public Domain */
-				/* table implementation */
+  __le32 magic;          /* 0xed26ff3a */
+  __le16 major_version;  /* (0x1) - reject images with higher major versions */
+  __le16 minor_version;  /* (0x0) - allow images with higer minor versions */
+  __le16 file_hdr_sz;    /* 28 bytes for first revision of the file format */
+  __le16 chunk_hdr_sz;   /* 12 bytes for first revision of the file format */
+  __le32 blk_sz;         /* block size in bytes, must be a multiple of 4 (4096) */
+  __le32 total_blks;     /* total blocks in the non-sparse output image */
+  __le32 total_chunks;   /* total chunks in the sparse input image */
+  __le32 image_checksum; /* CRC32 checksum of the original data, counting "don't care" */
+                         /* as 0. Standard 802.3 polynomial, use a Public Domain */
+                         /* table implementation */
 } sparse_header_t;
 
-#define SPARSE_HEADER_MAGIC	0xed26ff3a
+#define SPARSE_HEADER_MAGIC 0xed26ff3a
 
-#define CHUNK_TYPE_RAW		0xCAC1
-#define CHUNK_TYPE_FILL		0xCAC2
-#define CHUNK_TYPE_DONT_CARE	0xCAC3
-#define CHUNK_TYPE_CRC32    0xCAC4
+#define CHUNK_TYPE_RAW 0xCAC1
+#define CHUNK_TYPE_FILL 0xCAC2
+#define CHUNK_TYPE_DONT_CARE 0xCAC3
+#define CHUNK_TYPE_CRC32 0xCAC4
 
 typedef struct chunk_header {
-  __le16	chunk_type;	/* 0xCAC1 -> raw; 0xCAC2 -> fill; 0xCAC3 -> don't care */
-  __le16	reserved1;
-  __le32	chunk_sz;	/* in blocks in output image */
-  __le32	total_sz;	/* in bytes of chunk input file including chunk header and data */
+  __le16 chunk_type; /* 0xCAC1 -> raw; 0xCAC2 -> fill; 0xCAC3 -> don't care */
+  __le16 reserved1;
+  __le32 chunk_sz; /* in blocks in output image */
+  __le32 total_sz; /* in bytes of chunk input file including chunk header and data */
 } chunk_header_t;
 
 /* Following a Raw or Fill or CRC32 chunk is data.
diff --git a/libsparse/sparse_read.cpp b/libsparse/sparse_read.cpp
index 4379635..56e2c9a 100644
--- a/libsparse/sparse_read.cpp
+++ b/libsparse/sparse_read.cpp
@@ -17,16 +17,16 @@
 #define _FILE_OFFSET_BITS 64
 #define _LARGEFILE64_SOURCE 1
 
-#include <algorithm>
-#include <inttypes.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <stdarg.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <string>
 #include <unistd.h>
+#include <algorithm>
+#include <string>
 
 #include <sparse/sparse.h>
 
@@ -37,447 +37,541 @@
 #include "sparse_file.h"
 #include "sparse_format.h"
 
-
 #if defined(__APPLE__) && defined(__MACH__)
 #define lseek64 lseek
 #define off64_t off_t
 #endif
 
 #define SPARSE_HEADER_MAJOR_VER 1
-#define SPARSE_HEADER_LEN       (sizeof(sparse_header_t))
+#define SPARSE_HEADER_LEN (sizeof(sparse_header_t))
 #define CHUNK_HEADER_LEN (sizeof(chunk_header_t))
 
 static constexpr int64_t COPY_BUF_SIZE = 1024 * 1024;
-static char *copybuf;
+static char* copybuf;
 
-static std::string ErrorString(int err)
-{
-	if (err == -EOVERFLOW) return "EOF while reading file";
-	if (err == -EINVAL) return "Invalid sparse file format";
-	if (err == -ENOMEM) return "Failed allocation while reading file";
-	return android::base::StringPrintf("Unknown error %d", err);
+static std::string ErrorString(int err) {
+  if (err == -EOVERFLOW) return "EOF while reading file";
+  if (err == -EINVAL) return "Invalid sparse file format";
+  if (err == -ENOMEM) return "Failed allocation while reading file";
+  return android::base::StringPrintf("Unknown error %d", err);
 }
 
-static void verbose_error(bool verbose, int err, const char *fmt, ...)
-{
-	if (!verbose) return;
+class SparseFileSource {
+ public:
+  /* Seeks the source ahead by the given offset. */
+  virtual void Seek(int64_t offset) = 0;
 
-	std::string msg = ErrorString(err);
-	if (fmt) {
-		msg += " at ";
-		va_list argp;
-		va_start(argp, fmt);
-		android::base::StringAppendV(&msg, fmt, argp);
-		va_end(argp);
-	}
-	sparse_print_verbose("%s\n", msg.c_str());
+  /* Return the current offset. */
+  virtual int64_t GetOffset() = 0;
+
+  /* Set the current offset. Return 0 if successful. */
+  virtual int SetOffset(int64_t offset) = 0;
+
+  /* Adds the given length from the current offset of the source to the file at the given block.
+   * Return 0 if successful. */
+  virtual int AddToSparseFile(struct sparse_file* s, int64_t len, unsigned int block) = 0;
+
+  /* Get data of fixed size from the current offset and seek len bytes. Return 0 if successful. */
+  virtual int ReadValue(void* ptr, int len) = 0;
+
+  /* Find the crc32 of the next len bytes and seek ahead len bytes. Return 0 if successful. */
+  virtual int GetCrc32(uint32_t* crc32, int64_t len) = 0;
+
+  virtual ~SparseFileSource(){};
+};
+
+class SparseFileFdSource : public SparseFileSource {
+ private:
+  int fd;
+
+ public:
+  SparseFileFdSource(int fd) : fd(fd) {}
+  ~SparseFileFdSource() override {}
+
+  void Seek(int64_t off) override { lseek64(fd, off, SEEK_CUR); }
+
+  int64_t GetOffset() override { return lseek64(fd, 0, SEEK_CUR); }
+
+  int SetOffset(int64_t offset) override {
+    return lseek64(fd, offset, SEEK_SET) == offset ? 0 : -errno;
+  }
+
+  int AddToSparseFile(struct sparse_file* s, int64_t len, unsigned int block) override {
+    return sparse_file_add_fd(s, fd, GetOffset(), len, block);
+  }
+
+  int ReadValue(void* ptr, int len) override { return read_all(fd, ptr, len); }
+
+  int GetCrc32(uint32_t* crc32, int64_t len) override {
+    int chunk;
+    int ret;
+    while (len) {
+      chunk = std::min(len, COPY_BUF_SIZE);
+      ret = read_all(fd, copybuf, chunk);
+      if (ret < 0) {
+        return ret;
+      }
+      *crc32 = sparse_crc32(*crc32, copybuf, chunk);
+      len -= chunk;
+    }
+    return 0;
+  }
+};
+
+class SparseFileBufSource : public SparseFileSource {
+ private:
+  char* buf;
+  int64_t offset;
+
+ public:
+  SparseFileBufSource(char* buf) : buf(buf), offset(0) {}
+  ~SparseFileBufSource() override {}
+
+  void Seek(int64_t off) override {
+    buf += off;
+    offset += off;
+  }
+
+  int64_t GetOffset() override { return offset; }
+
+  int SetOffset(int64_t off) override {
+    buf += off - offset;
+    offset = off;
+    return 0;
+  }
+
+  int AddToSparseFile(struct sparse_file* s, int64_t len, unsigned int block) override {
+    return sparse_file_add_data(s, buf, len, block);
+  }
+
+  int ReadValue(void* ptr, int len) override {
+    memcpy(ptr, buf, len);
+    Seek(len);
+    return 0;
+  }
+
+  int GetCrc32(uint32_t* crc32, int64_t len) override {
+    *crc32 = sparse_crc32(*crc32, buf, len);
+    Seek(len);
+    return 0;
+  }
+};
+
+static void verbose_error(bool verbose, int err, const char* fmt, ...) {
+  if (!verbose) return;
+
+  std::string msg = ErrorString(err);
+  if (fmt) {
+    msg += " at ";
+    va_list argp;
+    va_start(argp, fmt);
+    android::base::StringAppendV(&msg, fmt, argp);
+    va_end(argp);
+  }
+  sparse_print_verbose("%s\n", msg.c_str());
 }
 
-static int process_raw_chunk(struct sparse_file *s, unsigned int chunk_size,
-		int fd, int64_t offset, unsigned int blocks, unsigned int block,
-		uint32_t *crc32)
-{
-	int ret;
-	int chunk;
-	int64_t len = blocks * s->block_size;
+static int process_raw_chunk(struct sparse_file* s, unsigned int chunk_size,
+                             SparseFileSource* source, unsigned int blocks, unsigned int block,
+                             uint32_t* crc32) {
+  int ret;
+  int64_t len = blocks * s->block_size;
 
-	if (chunk_size % s->block_size != 0) {
-		return -EINVAL;
-	}
+  if (chunk_size % s->block_size != 0) {
+    return -EINVAL;
+  }
 
-	if (chunk_size / s->block_size != blocks) {
-		return -EINVAL;
-	}
+  if (chunk_size / s->block_size != blocks) {
+    return -EINVAL;
+  }
 
-	ret = sparse_file_add_fd(s, fd, offset, len, block);
-	if (ret < 0) {
-		return ret;
-	}
+  ret = source->AddToSparseFile(s, len, block);
+  if (ret < 0) {
+    return ret;
+  }
 
-	if (crc32) {
-		while (len) {
-			chunk = std::min(len, COPY_BUF_SIZE);
-			ret = read_all(fd, copybuf, chunk);
-			if (ret < 0) {
-				return ret;
-			}
-			*crc32 = sparse_crc32(*crc32, copybuf, chunk);
-			len -= chunk;
-		}
-	} else {
-		lseek64(fd, len, SEEK_CUR);
-	}
+  if (crc32) {
+    ret = source->GetCrc32(crc32, len);
+    if (ret < 0) {
+      return ret;
+    }
+  } else {
+    source->Seek(len);
+  }
 
-	return 0;
+  return 0;
 }
 
-static int process_fill_chunk(struct sparse_file *s, unsigned int chunk_size,
-		int fd, unsigned int blocks, unsigned int block, uint32_t *crc32)
-{
-	int ret;
-	int chunk;
-	int64_t len = (int64_t)blocks * s->block_size;
-	uint32_t fill_val;
-	uint32_t *fillbuf;
-	unsigned int i;
+static int process_fill_chunk(struct sparse_file* s, unsigned int chunk_size,
+                              SparseFileSource* source, unsigned int blocks, unsigned int block,
+                              uint32_t* crc32) {
+  int ret;
+  int chunk;
+  int64_t len = (int64_t)blocks * s->block_size;
+  uint32_t fill_val;
+  uint32_t* fillbuf;
+  unsigned int i;
 
-	if (chunk_size != sizeof(fill_val)) {
-		return -EINVAL;
-	}
+  if (chunk_size != sizeof(fill_val)) {
+    return -EINVAL;
+  }
 
-	ret = read_all(fd, &fill_val, sizeof(fill_val));
-	if (ret < 0) {
-		return ret;
-	}
+  ret = source->ReadValue(&fill_val, sizeof(fill_val));
+  if (ret < 0) {
+    return ret;
+  }
 
-	ret = sparse_file_add_fill(s, fill_val, len, block);
-	if (ret < 0) {
-		return ret;
-	}
+  ret = sparse_file_add_fill(s, fill_val, len, block);
+  if (ret < 0) {
+    return ret;
+  }
 
-	if (crc32) {
-		/* Fill copy_buf with the fill value */
-		fillbuf = (uint32_t *)copybuf;
-		for (i = 0; i < (COPY_BUF_SIZE / sizeof(fill_val)); i++) {
-			fillbuf[i] = fill_val;
-		}
+  if (crc32) {
+    /* Fill copy_buf with the fill value */
+    fillbuf = (uint32_t*)copybuf;
+    for (i = 0; i < (COPY_BUF_SIZE / sizeof(fill_val)); i++) {
+      fillbuf[i] = fill_val;
+    }
 
-		while (len) {
-			chunk = std::min(len, COPY_BUF_SIZE);
-			*crc32 = sparse_crc32(*crc32, copybuf, chunk);
-			len -= chunk;
-		}
-	}
+    while (len) {
+      chunk = std::min(len, COPY_BUF_SIZE);
+      *crc32 = sparse_crc32(*crc32, copybuf, chunk);
+      len -= chunk;
+    }
+  }
 
-	return 0;
+  return 0;
 }
 
-static int process_skip_chunk(struct sparse_file *s, unsigned int chunk_size,
-		int fd __unused, unsigned int blocks,
-		unsigned int block __unused, uint32_t *crc32)
-{
-	if (chunk_size != 0) {
-		return -EINVAL;
-	}
+static int process_skip_chunk(struct sparse_file* s, unsigned int chunk_size,
+                              SparseFileSource* source __unused, unsigned int blocks,
+                              unsigned int block __unused, uint32_t* crc32) {
+  if (chunk_size != 0) {
+    return -EINVAL;
+  }
 
-	if (crc32) {
-	        int64_t len = (int64_t)blocks * s->block_size;
-		memset(copybuf, 0, COPY_BUF_SIZE);
+  if (crc32) {
+    int64_t len = (int64_t)blocks * s->block_size;
+    memset(copybuf, 0, COPY_BUF_SIZE);
 
-		while (len) {
-			int chunk = std::min(len, COPY_BUF_SIZE);
-			*crc32 = sparse_crc32(*crc32, copybuf, chunk);
-			len -= chunk;
-		}
-	}
+    while (len) {
+      int chunk = std::min(len, COPY_BUF_SIZE);
+      *crc32 = sparse_crc32(*crc32, copybuf, chunk);
+      len -= chunk;
+    }
+  }
 
-	return 0;
+  return 0;
 }
 
-static int process_crc32_chunk(int fd, unsigned int chunk_size, uint32_t *crc32)
-{
-	uint32_t file_crc32;
-	int ret;
+static int process_crc32_chunk(SparseFileSource* source, unsigned int chunk_size, uint32_t* crc32) {
+  uint32_t file_crc32;
 
-	if (chunk_size != sizeof(file_crc32)) {
-		return -EINVAL;
-	}
+  if (chunk_size != sizeof(file_crc32)) {
+    return -EINVAL;
+  }
 
-	ret = read_all(fd, &file_crc32, sizeof(file_crc32));
-	if (ret < 0) {
-		return ret;
-	}
+  int ret = source->ReadValue(&file_crc32, sizeof(file_crc32));
+  if (ret < 0) {
+    return ret;
+  }
 
-	if (crc32 != NULL && file_crc32 != *crc32) {
-		return -EINVAL;
-	}
+  if (crc32 != NULL && file_crc32 != *crc32) {
+    return -EINVAL;
+  }
 
-	return 0;
+  return 0;
 }
 
-static int process_chunk(struct sparse_file *s, int fd, off64_t offset,
-		unsigned int chunk_hdr_sz, chunk_header_t *chunk_header,
-		unsigned int cur_block, uint32_t *crc_ptr)
-{
-	int ret;
-	unsigned int chunk_data_size;
+static int process_chunk(struct sparse_file* s, SparseFileSource* source, unsigned int chunk_hdr_sz,
+                         chunk_header_t* chunk_header, unsigned int cur_block, uint32_t* crc_ptr) {
+  int ret;
+  unsigned int chunk_data_size;
+  int64_t offset = source->GetOffset();
 
-	chunk_data_size = chunk_header->total_sz - chunk_hdr_sz;
+  chunk_data_size = chunk_header->total_sz - chunk_hdr_sz;
 
-	switch (chunk_header->chunk_type) {
-		case CHUNK_TYPE_RAW:
-			ret = process_raw_chunk(s, chunk_data_size, fd, offset,
-					chunk_header->chunk_sz, cur_block, crc_ptr);
-			if (ret < 0) {
-				verbose_error(s->verbose, ret, "data block at %" PRId64, offset);
-				return ret;
-			}
-			return chunk_header->chunk_sz;
-		case CHUNK_TYPE_FILL:
-			ret = process_fill_chunk(s, chunk_data_size, fd,
-					chunk_header->chunk_sz, cur_block, crc_ptr);
-			if (ret < 0) {
-				verbose_error(s->verbose, ret, "fill block at %" PRId64, offset);
-				return ret;
-			}
-			return chunk_header->chunk_sz;
-		case CHUNK_TYPE_DONT_CARE:
-			ret = process_skip_chunk(s, chunk_data_size, fd,
-					chunk_header->chunk_sz, cur_block, crc_ptr);
-			if (chunk_data_size != 0) {
-				if (ret < 0) {
-					verbose_error(s->verbose, ret, "skip block at %" PRId64, offset);
-					return ret;
-				}
-			}
-			return chunk_header->chunk_sz;
-		case CHUNK_TYPE_CRC32:
-			ret = process_crc32_chunk(fd, chunk_data_size, crc_ptr);
-			if (ret < 0) {
-				verbose_error(s->verbose, -EINVAL, "crc block at %" PRId64,
-						offset);
-				return ret;
-			}
-			return 0;
-		default:
-			verbose_error(s->verbose, -EINVAL, "unknown block %04X at %" PRId64,
-					chunk_header->chunk_type, offset);
-	}
+  switch (chunk_header->chunk_type) {
+    case CHUNK_TYPE_RAW:
+      ret =
+          process_raw_chunk(s, chunk_data_size, source, chunk_header->chunk_sz, cur_block, crc_ptr);
+      if (ret < 0) {
+        verbose_error(s->verbose, ret, "data block at %" PRId64, offset);
+        return ret;
+      }
+      return chunk_header->chunk_sz;
+    case CHUNK_TYPE_FILL:
+      ret = process_fill_chunk(s, chunk_data_size, source, chunk_header->chunk_sz, cur_block,
+                               crc_ptr);
+      if (ret < 0) {
+        verbose_error(s->verbose, ret, "fill block at %" PRId64, offset);
+        return ret;
+      }
+      return chunk_header->chunk_sz;
+    case CHUNK_TYPE_DONT_CARE:
+      ret = process_skip_chunk(s, chunk_data_size, source, chunk_header->chunk_sz, cur_block,
+                               crc_ptr);
+      if (chunk_data_size != 0) {
+        if (ret < 0) {
+          verbose_error(s->verbose, ret, "skip block at %" PRId64, offset);
+          return ret;
+        }
+      }
+      return chunk_header->chunk_sz;
+    case CHUNK_TYPE_CRC32:
+      ret = process_crc32_chunk(source, chunk_data_size, crc_ptr);
+      if (ret < 0) {
+        verbose_error(s->verbose, -EINVAL, "crc block at %" PRId64, offset);
+        return ret;
+      }
+      return 0;
+    default:
+      verbose_error(s->verbose, -EINVAL, "unknown block %04X at %" PRId64, chunk_header->chunk_type,
+                    offset);
+  }
 
-	return 0;
+  return 0;
 }
 
-static int sparse_file_read_sparse(struct sparse_file *s, int fd, bool crc)
-{
-	int ret;
-	unsigned int i;
-	sparse_header_t sparse_header;
-	chunk_header_t chunk_header;
-	uint32_t crc32 = 0;
-	uint32_t *crc_ptr = 0;
-	unsigned int cur_block = 0;
-	off64_t offset;
+static int sparse_file_read_sparse(struct sparse_file* s, SparseFileSource* source, bool crc) {
+  int ret;
+  unsigned int i;
+  sparse_header_t sparse_header;
+  chunk_header_t chunk_header;
+  uint32_t crc32 = 0;
+  uint32_t* crc_ptr = 0;
+  unsigned int cur_block = 0;
 
-	if (!copybuf) {
-		copybuf = (char *)malloc(COPY_BUF_SIZE);
-	}
+  if (!copybuf) {
+    copybuf = (char*)malloc(COPY_BUF_SIZE);
+  }
 
-	if (!copybuf) {
-		return -ENOMEM;
-	}
+  if (!copybuf) {
+    return -ENOMEM;
+  }
 
-	if (crc) {
-		crc_ptr = &crc32;
-	}
+  if (crc) {
+    crc_ptr = &crc32;
+  }
 
-	ret = read_all(fd, &sparse_header, sizeof(sparse_header));
-	if (ret < 0) {
-		return ret;
-	}
+  ret = source->ReadValue(&sparse_header, sizeof(sparse_header));
+  if (ret < 0) {
+    return ret;
+  }
 
-	if (sparse_header.magic != SPARSE_HEADER_MAGIC) {
-		return -EINVAL;
-	}
+  if (sparse_header.magic != SPARSE_HEADER_MAGIC) {
+    return -EINVAL;
+  }
 
-	if (sparse_header.major_version != SPARSE_HEADER_MAJOR_VER) {
-		return -EINVAL;
-	}
+  if (sparse_header.major_version != SPARSE_HEADER_MAJOR_VER) {
+    return -EINVAL;
+  }
 
-	if (sparse_header.file_hdr_sz < SPARSE_HEADER_LEN) {
-		return -EINVAL;
-	}
+  if (sparse_header.file_hdr_sz < SPARSE_HEADER_LEN) {
+    return -EINVAL;
+  }
 
-	if (sparse_header.chunk_hdr_sz < sizeof(chunk_header)) {
-		return -EINVAL;
-	}
+  if (sparse_header.chunk_hdr_sz < sizeof(chunk_header)) {
+    return -EINVAL;
+  }
 
-	if (sparse_header.file_hdr_sz > SPARSE_HEADER_LEN) {
-		/* Skip the remaining bytes in a header that is longer than
-		 * we expected.
-		 */
-		lseek64(fd, sparse_header.file_hdr_sz - SPARSE_HEADER_LEN, SEEK_CUR);
-	}
+  if (sparse_header.file_hdr_sz > SPARSE_HEADER_LEN) {
+    /* Skip the remaining bytes in a header that is longer than
+     * we expected.
+     */
+    source->Seek(sparse_header.file_hdr_sz - SPARSE_HEADER_LEN);
+  }
 
-	for (i = 0; i < sparse_header.total_chunks; i++) {
-		ret = read_all(fd, &chunk_header, sizeof(chunk_header));
-		if (ret < 0) {
-			return ret;
-		}
+  for (i = 0; i < sparse_header.total_chunks; i++) {
+    ret = source->ReadValue(&chunk_header, sizeof(chunk_header));
+    if (ret < 0) {
+      return ret;
+    }
 
-		if (sparse_header.chunk_hdr_sz > CHUNK_HEADER_LEN) {
-			/* Skip the remaining bytes in a header that is longer than
-			 * we expected.
-			 */
-			lseek64(fd, sparse_header.chunk_hdr_sz - CHUNK_HEADER_LEN, SEEK_CUR);
-		}
+    if (sparse_header.chunk_hdr_sz > CHUNK_HEADER_LEN) {
+      /* Skip the remaining bytes in a header that is longer than
+       * we expected.
+       */
+      source->Seek(sparse_header.chunk_hdr_sz - CHUNK_HEADER_LEN);
+    }
 
-		offset = lseek64(fd, 0, SEEK_CUR);
+    ret = process_chunk(s, source, sparse_header.chunk_hdr_sz, &chunk_header, cur_block, crc_ptr);
+    if (ret < 0) {
+      return ret;
+    }
 
-		ret = process_chunk(s, fd, offset, sparse_header.chunk_hdr_sz, &chunk_header,
-				cur_block, crc_ptr);
-		if (ret < 0) {
-			return ret;
-		}
+    cur_block += ret;
+  }
 
-		cur_block += ret;
-	}
+  if (sparse_header.total_blks != cur_block) {
+    return -EINVAL;
+  }
 
-	if (sparse_header.total_blks != cur_block) {
-		return -EINVAL;
-	}
-
-	return 0;
+  return 0;
 }
 
-static int sparse_file_read_normal(struct sparse_file *s, int fd)
-{
-	int ret;
-	uint32_t *buf = (uint32_t *)malloc(s->block_size);
-	unsigned int block = 0;
-	int64_t remain = s->len;
-	int64_t offset = 0;
-	unsigned int to_read;
-	unsigned int i;
-	bool sparse_block;
+static int sparse_file_read_normal(struct sparse_file* s, int fd) {
+  int ret;
+  uint32_t* buf = (uint32_t*)malloc(s->block_size);
+  unsigned int block = 0;
+  int64_t remain = s->len;
+  int64_t offset = 0;
+  unsigned int to_read;
+  unsigned int i;
+  bool sparse_block;
 
-	if (!buf) {
-		return -ENOMEM;
-	}
+  if (!buf) {
+    return -ENOMEM;
+  }
 
-	while (remain > 0) {
-		to_read = std::min(remain, (int64_t)(s->block_size));
-		ret = read_all(fd, buf, to_read);
-		if (ret < 0) {
-			error("failed to read sparse file");
-			free(buf);
-			return ret;
-		}
+  while (remain > 0) {
+    to_read = std::min(remain, (int64_t)(s->block_size));
+    ret = read_all(fd, buf, to_read);
+    if (ret < 0) {
+      error("failed to read sparse file");
+      free(buf);
+      return ret;
+    }
 
-		if (to_read == s->block_size) {
-			sparse_block = true;
-			for (i = 1; i < s->block_size / sizeof(uint32_t); i++) {
-				if (buf[0] != buf[i]) {
-					sparse_block = false;
-					break;
-				}
-			}
-		} else {
-			sparse_block = false;
-		}
+    if (to_read == s->block_size) {
+      sparse_block = true;
+      for (i = 1; i < s->block_size / sizeof(uint32_t); i++) {
+        if (buf[0] != buf[i]) {
+          sparse_block = false;
+          break;
+        }
+      }
+    } else {
+      sparse_block = false;
+    }
 
-		if (sparse_block) {
-			/* TODO: add flag to use skip instead of fill for buf[0] == 0 */
-			sparse_file_add_fill(s, buf[0], to_read, block);
-		} else {
-			sparse_file_add_fd(s, fd, offset, to_read, block);
-		}
+    if (sparse_block) {
+      /* TODO: add flag to use skip instead of fill for buf[0] == 0 */
+      sparse_file_add_fill(s, buf[0], to_read, block);
+    } else {
+      sparse_file_add_fd(s, fd, offset, to_read, block);
+    }
 
-		remain -= to_read;
-		offset += to_read;
-		block++;
-	}
+    remain -= to_read;
+    offset += to_read;
+    block++;
+  }
 
-	free(buf);
-	return 0;
+  free(buf);
+  return 0;
 }
 
-int sparse_file_read(struct sparse_file *s, int fd, bool sparse, bool crc)
-{
-	if (crc && !sparse) {
-		return -EINVAL;
-	}
+int sparse_file_read(struct sparse_file* s, int fd, bool sparse, bool crc) {
+  if (crc && !sparse) {
+    return -EINVAL;
+  }
 
-	if (sparse) {
-		return sparse_file_read_sparse(s, fd, crc);
-	} else {
-		return sparse_file_read_normal(s, fd);
-	}
+  if (sparse) {
+    SparseFileFdSource source(fd);
+    return sparse_file_read_sparse(s, &source, crc);
+  } else {
+    return sparse_file_read_normal(s, fd);
+  }
 }
 
-struct sparse_file *sparse_file_import(int fd, bool verbose, bool crc)
-{
-	int ret;
-	sparse_header_t sparse_header;
-	int64_t len;
-	struct sparse_file *s;
-
-	ret = read_all(fd, &sparse_header, sizeof(sparse_header));
-	if (ret < 0) {
-		verbose_error(verbose, ret, "header");
-		return NULL;
-	}
-
-	if (sparse_header.magic != SPARSE_HEADER_MAGIC) {
-		verbose_error(verbose, -EINVAL, "header magic");
-		return NULL;
-	}
-
-	if (sparse_header.major_version != SPARSE_HEADER_MAJOR_VER) {
-		verbose_error(verbose, -EINVAL, "header major version");
-		return NULL;
-	}
-
-	if (sparse_header.file_hdr_sz < SPARSE_HEADER_LEN) {
-		return NULL;
-	}
-
-	if (sparse_header.chunk_hdr_sz < sizeof(chunk_header_t)) {
-		return NULL;
-	}
-
-	len = (int64_t)sparse_header.total_blks * sparse_header.blk_sz;
-	s = sparse_file_new(sparse_header.blk_sz, len);
-	if (!s) {
-		verbose_error(verbose, -EINVAL, NULL);
-		return NULL;
-	}
-
-	ret = lseek64(fd, 0, SEEK_SET);
-	if (ret < 0) {
-		verbose_error(verbose, ret, "seeking");
-		sparse_file_destroy(s);
-		return NULL;
-	}
-
-	s->verbose = verbose;
-
-	ret = sparse_file_read(s, fd, true, crc);
-	if (ret < 0) {
-		sparse_file_destroy(s);
-		return NULL;
-	}
-
-	return s;
+int sparse_file_read_buf(struct sparse_file* s, char* buf, bool crc) {
+  SparseFileBufSource source(buf);
+  return sparse_file_read_sparse(s, &source, crc);
 }
 
-struct sparse_file *sparse_file_import_auto(int fd, bool crc, bool verbose)
-{
-	struct sparse_file *s;
-	int64_t len;
-	int ret;
+static struct sparse_file* sparse_file_import_source(SparseFileSource* source, bool verbose,
+                                                     bool crc) {
+  int ret;
+  sparse_header_t sparse_header;
+  int64_t len;
+  struct sparse_file* s;
 
-	s = sparse_file_import(fd, verbose, crc);
-	if (s) {
-		return s;
-	}
+  ret = source->ReadValue(&sparse_header, sizeof(sparse_header));
+  if (ret < 0) {
+    verbose_error(verbose, ret, "header");
+    return NULL;
+  }
 
-	len = lseek64(fd, 0, SEEK_END);
-	if (len < 0) {
-		return NULL;
-	}
+  if (sparse_header.magic != SPARSE_HEADER_MAGIC) {
+    verbose_error(verbose, -EINVAL, "header magic");
+    return NULL;
+  }
 
-	lseek64(fd, 0, SEEK_SET);
+  if (sparse_header.major_version != SPARSE_HEADER_MAJOR_VER) {
+    verbose_error(verbose, -EINVAL, "header major version");
+    return NULL;
+  }
 
-	s = sparse_file_new(4096, len);
-	if (!s) {
-		return NULL;
-	}
+  if (sparse_header.file_hdr_sz < SPARSE_HEADER_LEN) {
+    return NULL;
+  }
 
-	ret = sparse_file_read_normal(s, fd);
-	if (ret < 0) {
-		sparse_file_destroy(s);
-		return NULL;
-	}
+  if (sparse_header.chunk_hdr_sz < sizeof(chunk_header_t)) {
+    return NULL;
+  }
 
-	return s;
+  len = (int64_t)sparse_header.total_blks * sparse_header.blk_sz;
+  s = sparse_file_new(sparse_header.blk_sz, len);
+  if (!s) {
+    verbose_error(verbose, -EINVAL, NULL);
+    return NULL;
+  }
+
+  ret = source->SetOffset(0);
+  if (ret < 0) {
+    verbose_error(verbose, ret, "seeking");
+    sparse_file_destroy(s);
+    return NULL;
+  }
+
+  s->verbose = verbose;
+
+  ret = sparse_file_read_sparse(s, source, crc);
+  if (ret < 0) {
+    sparse_file_destroy(s);
+    return NULL;
+  }
+
+  return s;
+}
+
+struct sparse_file* sparse_file_import(int fd, bool verbose, bool crc) {
+  SparseFileFdSource source(fd);
+  return sparse_file_import_source(&source, verbose, crc);
+}
+
+struct sparse_file* sparse_file_import_buf(char* buf, bool verbose, bool crc) {
+  SparseFileBufSource source(buf);
+  return sparse_file_import_source(&source, verbose, crc);
+}
+
+struct sparse_file* sparse_file_import_auto(int fd, bool crc, bool verbose) {
+  struct sparse_file* s;
+  int64_t len;
+  int ret;
+
+  s = sparse_file_import(fd, verbose, crc);
+  if (s) {
+    return s;
+  }
+
+  len = lseek64(fd, 0, SEEK_END);
+  if (len < 0) {
+    return NULL;
+  }
+
+  lseek64(fd, 0, SEEK_SET);
+
+  s = sparse_file_new(4096, len);
+  if (!s) {
+    return NULL;
+  }
+
+  ret = sparse_file_read_normal(s, fd);
+  if (ret < 0) {
+    sparse_file_destroy(s);
+    return NULL;
+  }
+
+  return s;
 }
diff --git a/libsync/include/android/sync.h b/libsync/include/android/sync.h
index 68f74a0..32bb878 100644
--- a/libsync/include/android/sync.h
+++ b/libsync/include/android/sync.h
@@ -41,28 +41,8 @@
 
 __BEGIN_DECLS
 
-struct sync_fence_info_data {
- uint32_t len;
- char name[32];
- int32_t status;
- uint8_t pt_info[0];
-};
-
-struct sync_pt_info {
- uint32_t len;
- char obj_name[32];
- char driver_name[32];
- int32_t status;
- uint64_t timestamp_ns;
- uint8_t driver_data[0];
-};
-
 /* timeout in msecs */
 int sync_wait(int fd, int timeout);
-struct sync_fence_info_data *sync_fence_info(int fd);
-struct sync_pt_info *sync_pt_info(struct sync_fence_info_data *info,
-                                  struct sync_pt_info *itr);
-void sync_fence_info_free(struct sync_fence_info_data *info);
 
 __END_DECLS
 
diff --git a/libsync/sync.c b/libsync/sync.c
index 6b187fa..b8c48c7 100644
--- a/libsync/sync.c
+++ b/libsync/sync.c
@@ -30,6 +30,29 @@
 
 #include <android/sync.h>
 
+/* Prototypes for deprecated functions that used to be declared in the legacy
+ * android/sync.h. They've been moved here to make sure new code does not use
+ * them, but the functions are still defined to avoid breaking existing
+ * binaries. Eventually they can be removed altogether.
+ */
+struct sync_fence_info_data {
+    uint32_t len;
+    char name[32];
+    int32_t status;
+    uint8_t pt_info[0];
+};
+struct sync_pt_info {
+    uint32_t len;
+    char obj_name[32];
+    char driver_name[32];
+    int32_t status;
+    uint64_t timestamp_ns;
+    uint8_t driver_data[0];
+};
+struct sync_fence_info_data* sync_fence_info(int fd);
+struct sync_pt_info* sync_pt_info(struct sync_fence_info_data* info, struct sync_pt_info* itr);
+void sync_fence_info_free(struct sync_fence_info_data* info);
+
 /* Legacy Sync API */
 
 struct sync_legacy_merge_data {
diff --git a/libsync/tests/sync_test.cpp b/libsync/tests/sync_test.cpp
index 0fb86d6..011b09d 100644
--- a/libsync/tests/sync_test.cpp
+++ b/libsync/tests/sync_test.cpp
@@ -15,6 +15,35 @@
 #include <random>
 #include <unordered_map>
 
+/* These deprecated declarations were in the legacy android/sync.h. They've been removed to
+ * encourage code to move to the modern equivalents. But they are still implemented in libsync.so
+ * to avoid breaking existing binaries; as long as that's true we should keep testing them here.
+ * That means making local copies of the declarations.
+ */
+extern "C" {
+
+struct sync_fence_info_data {
+    uint32_t len;
+    char name[32];
+    int32_t status;
+    uint8_t pt_info[0];
+};
+
+struct sync_pt_info {
+    uint32_t len;
+    char obj_name[32];
+    char driver_name[32];
+    int32_t status;
+    uint64_t timestamp_ns;
+    uint8_t driver_data[0];
+};
+
+struct sync_fence_info_data* sync_fence_info(int fd);
+struct sync_pt_info* sync_pt_info(struct sync_fence_info_data* info, struct sync_pt_info* itr);
+void sync_fence_info_free(struct sync_fence_info_data* info);
+
+}  // extern "C"
+
 // TODO: better stress tests?
 // Handle more than 64 fd's simultaneously, i.e. fix sync_fence_info's 4k limit.
 // Handle wraparound in timelines like nvidia.
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index d4cef7c..df5da65 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -223,6 +223,7 @@
         "tests/files/offline/art_quick_osr_stub_arm/*",
         "tests/files/offline/bad_eh_frame_hdr_arm64/*",
         "tests/files/offline/debug_frame_first_x86/*",
+        "tests/files/offline/debug_frame_load_bias_arm/*",
         "tests/files/offline/eh_frame_hdr_begin_x86_64/*",
         "tests/files/offline/jit_debug_arm/*",
         "tests/files/offline/jit_debug_x86/*",
diff --git a/libunwindstack/ArmExidx.cpp b/libunwindstack/ArmExidx.cpp
index 6e397e3..818f5d1 100644
--- a/libunwindstack/ArmExidx.cpp
+++ b/libunwindstack/ArmExidx.cpp
@@ -31,6 +31,8 @@
 
 namespace unwindstack {
 
+static constexpr uint8_t LOG_CFA_REG = 64;
+
 void ArmExidx::LogRawData() {
   std::string log_str("Raw Data:");
   for (const uint8_t data : data_) {
@@ -63,8 +65,10 @@
   if (data == 1) {
     // This is a CANT UNWIND entry.
     status_ = ARM_STATUS_NO_UNWIND;
-    if (log_) {
-      log(log_indent_, "Raw Data: 0x00 0x00 0x00 0x01");
+    if (log_type_ != ARM_LOG_NONE) {
+      if (log_type_ == ARM_LOG_FULL) {
+        log(log_indent_, "Raw Data: 0x00 0x00 0x00 0x01");
+      }
       log(log_indent_, "[cantunwind]");
     }
     return false;
@@ -86,7 +90,7 @@
       // If this didn't end with a finish op, add one.
       data_.push_back(ARM_OP_FINISH);
     }
-    if (log_) {
+    if (log_type_ == ARM_LOG_FULL) {
       LogRawData();
     }
     return true;
@@ -163,7 +167,7 @@
     data_.push_back(ARM_OP_FINISH);
   }
 
-  if (log_) {
+  if (log_type_ == ARM_LOG_FULL) {
     LogRawData();
   }
   return true;
@@ -190,32 +194,45 @@
   registers |= byte;
   if (registers == 0) {
     // 10000000 00000000: Refuse to unwind
-    if (log_) {
+    if (log_type_ != ARM_LOG_NONE) {
       log(log_indent_, "Refuse to unwind");
     }
     status_ = ARM_STATUS_NO_UNWIND;
     return false;
   }
   // 1000iiii iiiiiiii: Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}
-  if (log_) {
-    bool add_comma = false;
-    std::string msg = "pop {";
-    for (size_t i = 0; i < 12; i++) {
-      if (registers & (1 << i)) {
-        if (add_comma) {
-          msg += ", ";
+  registers <<= 4;
+
+  if (log_type_ != ARM_LOG_NONE) {
+    if (log_type_ == ARM_LOG_FULL) {
+      bool add_comma = false;
+      std::string msg = "pop {";
+      for (size_t reg = 4; reg < 16; reg++) {
+        if (registers & (1 << reg)) {
+          if (add_comma) {
+            msg += ", ";
+          }
+          msg += android::base::StringPrintf("r%zu", reg);
+          add_comma = true;
         }
-        msg += android::base::StringPrintf("r%zu", i + 4);
-        add_comma = true;
+      }
+      log(log_indent_, "%s}", msg.c_str());
+    } else {
+      uint32_t cfa_offset = __builtin_popcount(registers) * 4;
+      log_cfa_offset_ += cfa_offset;
+      for (size_t reg = 4; reg < 16; reg++) {
+        if (registers & (1 << reg)) {
+          log_regs_[reg] = cfa_offset;
+          cfa_offset -= 4;
+        }
       }
     }
-    log(log_indent_, "%s}", msg.c_str());
+
     if (log_skip_execution_) {
       return true;
     }
   }
 
-  registers <<= 4;
   for (size_t reg = 4; reg < 16; reg++) {
     if (registers & (1 << reg)) {
       if (!process_memory_->Read32(cfa_, &(*regs_)[reg])) {
@@ -246,15 +263,20 @@
   if (bits == 13 || bits == 15) {
     // 10011101: Reserved as prefix for ARM register to register moves
     // 10011111: Reserved as prefix for Intel Wireless MMX register to register moves
-    if (log_) {
+    if (log_type_ != ARM_LOG_NONE) {
       log(log_indent_, "[Reserved]");
     }
     status_ = ARM_STATUS_RESERVED;
     return false;
   }
   // 1001nnnn: Set vsp = r[nnnn] (nnnn != 13, 15)
-  if (log_) {
-    log(log_indent_, "vsp = r%d", bits);
+  if (log_type_ != ARM_LOG_NONE) {
+    if (log_type_ == ARM_LOG_FULL) {
+      log(log_indent_, "vsp = r%d", bits);
+    } else {
+      log_regs_[LOG_CFA_REG] = bits;
+    }
+
     if (log_skip_execution_) {
       return true;
     }
@@ -270,17 +292,36 @@
 
   // 10100nnn: Pop r4-r[4+nnn]
   // 10101nnn: Pop r4-r[4+nnn], r14
-  if (log_) {
-    std::string msg = "pop {r4";
+  if (log_type_ != ARM_LOG_NONE) {
     uint8_t end_reg = byte & 0x7;
-    if (end_reg) {
-      msg += android::base::StringPrintf("-r%d", 4 + end_reg);
-    }
-    if (byte & 0x8) {
-      log(log_indent_, "%s, r14}", msg.c_str());
+    if (log_type_ == ARM_LOG_FULL) {
+      std::string msg = "pop {r4";
+      if (end_reg) {
+        msg += android::base::StringPrintf("-r%d", 4 + end_reg);
+      }
+      if (byte & 0x8) {
+        log(log_indent_, "%s, r14}", msg.c_str());
+      } else {
+        log(log_indent_, "%s}", msg.c_str());
+      }
     } else {
-      log(log_indent_, "%s}", msg.c_str());
+      end_reg += 4;
+      uint32_t cfa_offset = (end_reg - 3) * 4;
+      if (byte & 0x8) {
+        cfa_offset += 4;
+      }
+      log_cfa_offset_ += cfa_offset;
+
+      for (uint8_t reg = 4; reg <= end_reg; reg++) {
+        log_regs_[reg] = cfa_offset;
+        cfa_offset -= 4;
+      }
+
+      if (byte & 0x8) {
+        log_regs_[14] = cfa_offset;
+      }
     }
+
     if (log_skip_execution_) {
       return true;
     }
@@ -307,8 +348,11 @@
 
 inline bool ArmExidx::DecodePrefix_10_11_0000() {
   // 10110000: Finish
-  if (log_) {
-    log(log_indent_, "finish");
+  if (log_type_ != ARM_LOG_NONE) {
+    if (log_type_ == ARM_LOG_FULL) {
+      log(log_indent_, "finish");
+    }
+
     if (log_skip_execution_) {
       status_ = ARM_STATUS_FINISH;
       return false;
@@ -326,7 +370,7 @@
 
   if (byte == 0) {
     // 10110001 00000000: Spare
-    if (log_) {
+    if (log_type_ != ARM_LOG_NONE) {
       log(log_indent_, "Spare");
     }
     status_ = ARM_STATUS_SPARE;
@@ -334,7 +378,7 @@
   }
   if (byte >> 4) {
     // 10110001 xxxxyyyy: Spare (xxxx != 0000)
-    if (log_) {
+    if (log_type_ != ARM_LOG_NONE) {
       log(log_indent_, "Spare");
     }
     status_ = ARM_STATUS_SPARE;
@@ -342,19 +386,32 @@
   }
 
   // 10110001 0000iiii: Pop integer registers under mask {r3, r2, r1, r0}
-  if (log_) {
-    bool add_comma = false;
-    std::string msg = "pop {";
-    for (size_t i = 0; i < 4; i++) {
-      if (byte & (1 << i)) {
-        if (add_comma) {
-          msg += ", ";
+  if (log_type_ != ARM_LOG_NONE) {
+    if (log_type_ == ARM_LOG_FULL) {
+      bool add_comma = false;
+      std::string msg = "pop {";
+      for (size_t i = 0; i < 4; i++) {
+        if (byte & (1 << i)) {
+          if (add_comma) {
+            msg += ", ";
+          }
+          msg += android::base::StringPrintf("r%zu", i);
+          add_comma = true;
         }
-        msg += android::base::StringPrintf("r%zu", i);
-        add_comma = true;
+      }
+      log(log_indent_, "%s}", msg.c_str());
+    } else {
+      byte &= 0xf;
+      uint32_t cfa_offset = __builtin_popcount(byte) * 4;
+      log_cfa_offset_ += cfa_offset;
+      for (size_t reg = 0; reg < 4; reg++) {
+        if (byte & (1 << reg)) {
+          log_regs_[reg] = cfa_offset;
+          cfa_offset -= 4;
+        }
       }
     }
-    log(log_indent_, "%s}", msg.c_str());
+
     if (log_skip_execution_) {
       return true;
     }
@@ -373,6 +430,15 @@
   return true;
 }
 
+inline void ArmExidx::AdjustRegisters(int32_t offset) {
+  for (auto& entry : log_regs_) {
+    if (entry.first >= LOG_CFA_REG) {
+      break;
+    }
+    entry.second += offset;
+  }
+}
+
 inline bool ArmExidx::DecodePrefix_10_11_0010() {
   // 10110010 uleb128: vsp = vsp + 0x204 + (uleb128 << 2)
   uint32_t result = 0;
@@ -387,8 +453,15 @@
     shift += 7;
   } while (byte & 0x80);
   result <<= 2;
-  if (log_) {
-    log(log_indent_, "vsp = vsp + %d", 0x204 + result);
+  if (log_type_ != ARM_LOG_NONE) {
+    int32_t cfa_offset = 0x204 + result;
+    if (log_type_ == ARM_LOG_FULL) {
+      log(log_indent_, "vsp = vsp + %d", cfa_offset);
+    } else {
+      log_cfa_offset_ += cfa_offset;
+    }
+    AdjustRegisters(cfa_offset);
+
     if (log_skip_execution_) {
       return true;
     }
@@ -404,14 +477,20 @@
     return false;
   }
 
-  if (log_) {
+  if (log_type_ != ARM_LOG_NONE) {
     uint8_t start_reg = byte >> 4;
-    std::string msg = android::base::StringPrintf("pop {d%d", start_reg);
     uint8_t end_reg = start_reg + (byte & 0xf);
-    if (end_reg) {
-      msg += android::base::StringPrintf("-d%d", end_reg);
+
+    if (log_type_ == ARM_LOG_FULL) {
+      std::string msg = android::base::StringPrintf("pop {d%d", start_reg);
+      if (end_reg) {
+        msg += android::base::StringPrintf("-d%d", end_reg);
+      }
+      log(log_indent_, "%s}", msg.c_str());
+    } else {
+      log(log_indent_, "Unsupported DX register display");
     }
-    log(log_indent_, "%s}", msg.c_str());
+
     if (log_skip_execution_) {
       return true;
     }
@@ -422,7 +501,7 @@
 
 inline bool ArmExidx::DecodePrefix_10_11_01nn() {
   // 101101nn: Spare
-  if (log_) {
+  if (log_type_ != ARM_LOG_NONE) {
     log(log_indent_, "Spare");
   }
   status_ = ARM_STATUS_SPARE;
@@ -433,13 +512,18 @@
   CHECK((byte & ~0x07) == 0xb8);
 
   // 10111nnn: Pop VFP double-precision registers D[8]-D[8+nnn] by FSTMFDX
-  if (log_) {
-    std::string msg = "pop {d8";
-    uint8_t last_reg = (byte & 0x7);
-    if (last_reg) {
-      msg += android::base::StringPrintf("-d%d", last_reg + 8);
+  if (log_type_ != ARM_LOG_NONE) {
+    if (log_type_ == ARM_LOG_FULL) {
+      uint8_t last_reg = (byte & 0x7);
+      std::string msg = "pop {d8";
+      if (last_reg) {
+        msg += android::base::StringPrintf("-d%d", last_reg + 8);
+      }
+      log(log_indent_, "%s}", msg.c_str());
+    } else {
+      log(log_indent_, "Unsupported DX register display");
     }
-    log(log_indent_, "%s}", msg.c_str());
+
     if (log_skip_execution_) {
       return true;
     }
@@ -489,14 +573,19 @@
     }
 
     // 11000110 sssscccc: Intel Wireless MMX pop wR[ssss]-wR[ssss+cccc]
-    if (log_) {
-      uint8_t start_reg = byte >> 4;
-      std::string msg = android::base::StringPrintf("pop {wR%d", start_reg);
-      uint8_t end_reg = byte & 0xf;
-      if (end_reg) {
-        msg += android::base::StringPrintf("-wR%d", start_reg + end_reg);
+    if (log_type_ != ARM_LOG_NONE) {
+      if (log_type_ == ARM_LOG_FULL) {
+        uint8_t start_reg = byte >> 4;
+        std::string msg = android::base::StringPrintf("pop {wR%d", start_reg);
+        uint8_t end_reg = byte & 0xf;
+        if (end_reg) {
+          msg += android::base::StringPrintf("-wR%d", start_reg + end_reg);
+        }
+        log(log_indent_, "%s}", msg.c_str());
+      } else {
+        log(log_indent_, "Unsupported wRX register display");
       }
-      log(log_indent_, "%s}", msg.c_str());
+
       if (log_skip_execution_) {
         return true;
       }
@@ -510,32 +599,40 @@
 
     if (byte == 0) {
       // 11000111 00000000: Spare
-      if (log_) {
+      if (log_type_ != ARM_LOG_NONE) {
         log(log_indent_, "Spare");
       }
       status_ = ARM_STATUS_SPARE;
       return false;
     } else if ((byte >> 4) == 0) {
       // 11000111 0000iiii: Intel Wireless MMX pop wCGR registers {wCGR0,1,2,3}
-      if (log_) {
-        bool add_comma = false;
-        std::string msg = "pop {";
-        for (size_t i = 0; i < 4; i++) {
-          if (byte & (1 << i)) {
-            if (add_comma) {
-              msg += ", ";
+      if (log_type_ != ARM_LOG_NONE) {
+        if (log_type_ == ARM_LOG_FULL) {
+          bool add_comma = false;
+          std::string msg = "pop {";
+          for (size_t i = 0; i < 4; i++) {
+            if (byte & (1 << i)) {
+              if (add_comma) {
+                msg += ", ";
+              }
+              msg += android::base::StringPrintf("wCGR%zu", i);
+              add_comma = true;
             }
-            msg += android::base::StringPrintf("wCGR%zu", i);
-            add_comma = true;
           }
+          log(log_indent_, "%s}", msg.c_str());
+        } else {
+          log(log_indent_, "Unsupported wCGR register display");
         }
-        log(log_indent_, "%s}", msg.c_str());
+
+        if (log_skip_execution_) {
+          return true;
+        }
       }
       // Only update the cfa.
       cfa_ += __builtin_popcount(byte) * 4;
     } else {
       // 11000111 xxxxyyyy: Spare (xxxx != 0000)
-      if (log_) {
+      if (log_type_ != ARM_LOG_NONE) {
         log(log_indent_, "Spare");
       }
       status_ = ARM_STATUS_SPARE;
@@ -543,13 +640,18 @@
     }
   } else {
     // 11000nnn: Intel Wireless MMX pop wR[10]-wR[10+nnn] (nnn != 6, 7)
-    if (log_) {
-      std::string msg = "pop {wR10";
-      uint8_t nnn = byte & 0x7;
-      if (nnn) {
-        msg += android::base::StringPrintf("-wR%d", 10 + nnn);
+    if (log_type_ != ARM_LOG_NONE) {
+      if (log_type_ == ARM_LOG_FULL) {
+        std::string msg = "pop {wR10";
+        uint8_t nnn = byte & 0x7;
+        if (nnn) {
+          msg += android::base::StringPrintf("-wR%d", 10 + nnn);
+        }
+        log(log_indent_, "%s}", msg.c_str());
+      } else {
+        log(log_indent_, "Unsupported wRX register display");
       }
-      log(log_indent_, "%s}", msg.c_str());
+
       if (log_skip_execution_) {
         return true;
       }
@@ -570,14 +672,19 @@
       return false;
     }
 
-    if (log_) {
-      uint8_t start_reg = byte >> 4;
-      std::string msg = android::base::StringPrintf("pop {d%d", 16 + start_reg);
-      uint8_t end_reg = byte & 0xf;
-      if (end_reg) {
-        msg += android::base::StringPrintf("-d%d", 16 + start_reg + end_reg);
+    if (log_type_ != ARM_LOG_NONE) {
+      if (log_type_ == ARM_LOG_FULL) {
+        uint8_t start_reg = byte >> 4;
+        std::string msg = android::base::StringPrintf("pop {d%d", 16 + start_reg);
+        uint8_t end_reg = byte & 0xf;
+        if (end_reg) {
+          msg += android::base::StringPrintf("-d%d", 16 + start_reg + end_reg);
+        }
+        log(log_indent_, "%s}", msg.c_str());
+      } else {
+        log(log_indent_, "Unsupported DX register display");
       }
-      log(log_indent_, "%s}", msg.c_str());
+
       if (log_skip_execution_) {
         return true;
       }
@@ -590,14 +697,19 @@
       return false;
     }
 
-    if (log_) {
-      uint8_t start_reg = byte >> 4;
-      std::string msg = android::base::StringPrintf("pop {d%d", start_reg);
-      uint8_t end_reg = byte & 0xf;
-      if (end_reg) {
-        msg += android::base::StringPrintf("-d%d", start_reg + end_reg);
+    if (log_type_ != ARM_LOG_NONE) {
+      if (log_type_ == ARM_LOG_FULL) {
+        uint8_t start_reg = byte >> 4;
+        std::string msg = android::base::StringPrintf("pop {d%d", start_reg);
+        uint8_t end_reg = byte & 0xf;
+        if (end_reg) {
+          msg += android::base::StringPrintf("-d%d", start_reg + end_reg);
+        }
+        log(log_indent_, "%s}", msg.c_str());
+      } else {
+        log(log_indent_, "Unsupported DX register display");
       }
-      log(log_indent_, "%s}", msg.c_str());
+
       if (log_skip_execution_) {
         return true;
       }
@@ -606,7 +718,7 @@
     cfa_ += (byte & 0xf) * 8 + 8;
   } else {
     // 11001yyy: Spare (yyy != 000, 001)
-    if (log_) {
+    if (log_type_ != ARM_LOG_NONE) {
       log(log_indent_, "Spare");
     }
     status_ = ARM_STATUS_SPARE;
@@ -619,13 +731,18 @@
   CHECK((byte & ~0x07) == 0xd0);
 
   // 11010nnn: Pop VFP double precision registers D[8]-D[8+nnn] by VPUSH
-  if (log_) {
-    std::string msg = "pop {d8";
-    uint8_t end_reg = byte & 0x7;
-    if (end_reg) {
-      msg += android::base::StringPrintf("-d%d", 8 + end_reg);
+  if (log_type_ != ARM_LOG_NONE) {
+    if (log_type_ == ARM_LOG_FULL) {
+      std::string msg = "pop {d8";
+      uint8_t end_reg = byte & 0x7;
+      if (end_reg) {
+        msg += android::base::StringPrintf("-d%d", 8 + end_reg);
+      }
+      log(log_indent_, "%s}", msg.c_str());
+    } else {
+      log(log_indent_, "Unsupported DX register display");
     }
-    log(log_indent_, "%s}", msg.c_str());
+
     if (log_skip_execution_) {
       return true;
     }
@@ -646,7 +763,7 @@
     return DecodePrefix_11_010(byte);
   default:
     // 11xxxyyy: Spare (xxx != 000, 001, 010)
-    if (log_) {
+    if (log_type_ != ARM_LOG_NONE) {
       log(log_indent_, "Spare");
     }
     status_ = ARM_STATUS_SPARE;
@@ -664,8 +781,15 @@
   switch (byte >> 6) {
   case 0:
     // 00xxxxxx: vsp = vsp + (xxxxxxx << 2) + 4
-    if (log_) {
-      log(log_indent_, "vsp = vsp + %d", ((byte & 0x3f) << 2) + 4);
+    if (log_type_ != ARM_LOG_NONE) {
+      int32_t cfa_offset = ((byte & 0x3f) << 2) + 4;
+      if (log_type_ == ARM_LOG_FULL) {
+        log(log_indent_, "vsp = vsp + %d", cfa_offset);
+      } else {
+        log_cfa_offset_ += cfa_offset;
+      }
+      AdjustRegisters(cfa_offset);
+
       if (log_skip_execution_) {
         break;
       }
@@ -674,8 +798,15 @@
     break;
   case 1:
     // 01xxxxxx: vsp = vsp - (xxxxxxx << 2) + 4
-    if (log_) {
-      log(log_indent_, "vsp = vsp - %d", ((byte & 0x3f) << 2) + 4);
+    if (log_type_ != ARM_LOG_NONE) {
+      uint32_t cfa_offset = ((byte & 0x3f) << 2) + 4;
+      if (log_type_ == ARM_LOG_FULL) {
+        log(log_indent_, "vsp = vsp - %d", cfa_offset);
+      } else {
+        log_cfa_offset_ -= cfa_offset;
+      }
+      AdjustRegisters(-cfa_offset);
+
       if (log_skip_execution_) {
         break;
       }
@@ -696,4 +827,36 @@
   return status_ == ARM_STATUS_FINISH;
 }
 
+void ArmExidx::LogByReg() {
+  if (log_type_ != ARM_LOG_BY_REG) {
+    return;
+  }
+
+  uint8_t cfa_reg;
+  if (log_regs_.count(LOG_CFA_REG) == 0) {
+    cfa_reg = 13;
+  } else {
+    cfa_reg = log_regs_[LOG_CFA_REG];
+  }
+
+  if (log_cfa_offset_ != 0) {
+    char sign = (log_cfa_offset_ > 0) ? '+' : '-';
+    log(log_indent_, "cfa = r%zu %c %d", cfa_reg, sign, abs(log_cfa_offset_));
+  } else {
+    log(log_indent_, "cfa = r%zu", cfa_reg);
+  }
+
+  for (const auto& entry : log_regs_) {
+    if (entry.first >= LOG_CFA_REG) {
+      break;
+    }
+    if (entry.second == 0) {
+      log(log_indent_, "r%zu = [cfa]", entry.first);
+    } else {
+      char sign = (entry.second > 0) ? '-' : '+';
+      log(log_indent_, "r%zu = [cfa %c %d]", entry.first, sign, abs(entry.second));
+    }
+  }
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/ArmExidx.h b/libunwindstack/ArmExidx.h
index 96756a0..d9fc371 100644
--- a/libunwindstack/ArmExidx.h
+++ b/libunwindstack/ArmExidx.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 
 #include <deque>
+#include <map>
 
 namespace unwindstack {
 
@@ -44,6 +45,12 @@
   ARM_OP_FINISH = 0xb0,
 };
 
+enum ArmLogType : uint8_t {
+  ARM_LOG_NONE,
+  ARM_LOG_FULL,
+  ARM_LOG_BY_REG,
+};
+
 class ArmExidx {
  public:
   ArmExidx(RegsArm* regs, Memory* elf_memory, Memory* process_memory)
@@ -52,6 +59,8 @@
 
   void LogRawData();
 
+  void LogByReg();
+
   bool ExtractEntryData(uint32_t entry_offset);
 
   bool Eval();
@@ -71,12 +80,13 @@
   bool pc_set() { return pc_set_; }
   void set_pc_set(bool pc_set) { pc_set_ = pc_set; }
 
-  void set_log(bool log) { log_ = log; }
+  void set_log(ArmLogType log_type) { log_type_ = log_type; }
   void set_log_skip_execution(bool skip_execution) { log_skip_execution_ = skip_execution; }
   void set_log_indent(uint8_t indent) { log_indent_ = indent; }
 
  private:
   bool GetByte(uint8_t* byte);
+  void AdjustRegisters(int32_t offset);
 
   bool DecodePrefix_10_00(uint8_t byte);
   bool DecodePrefix_10_01(uint8_t byte);
@@ -103,10 +113,12 @@
   Memory* elf_memory_;
   Memory* process_memory_;
 
-  bool log_ = false;
+  ArmLogType log_type_ = ARM_LOG_NONE;
   uint8_t log_indent_ = 0;
   bool log_skip_execution_ = false;
   bool pc_set_ = false;
+  int32_t log_cfa_offset_ = 0;
+  std::map<uint8_t, int32_t> log_regs_;
 };
 
 }  // namespace unwindstack
diff --git a/libunwindstack/DwarfCfa.cpp b/libunwindstack/DwarfCfa.cpp
index 6ecedce..cd9ef61 100644
--- a/libunwindstack/DwarfCfa.cpp
+++ b/libunwindstack/DwarfCfa.cpp
@@ -264,8 +264,8 @@
 }
 
 template <typename AddressType>
-bool DwarfCfa<AddressType>::Log(uint32_t indent, uint64_t pc, uint64_t load_bias,
-                                uint64_t start_offset, uint64_t end_offset) {
+bool DwarfCfa<AddressType>::Log(uint32_t indent, uint64_t pc, uint64_t start_offset,
+                                uint64_t end_offset) {
   memory_->set_cur_offset(start_offset);
   uint64_t cfa_offset;
   uint64_t cur_pc = fde_->pc_start;
@@ -301,8 +301,8 @@
         break;
     }
     if (cur_pc != old_pc) {
-      log(indent, "");
-      log(indent, "PC 0x%" PRIx64, cur_pc + load_bias);
+      log(0, "");
+      log(indent, "PC 0x%" PRIx64, cur_pc);
     }
     old_pc = cur_pc;
   }
diff --git a/libunwindstack/DwarfCfa.h b/libunwindstack/DwarfCfa.h
index 16c66e2..c5ffb8e 100644
--- a/libunwindstack/DwarfCfa.h
+++ b/libunwindstack/DwarfCfa.h
@@ -71,8 +71,7 @@
   bool GetLocationInfo(uint64_t pc, uint64_t start_offset, uint64_t end_offset,
                        dwarf_loc_regs_t* loc_regs);
 
-  bool Log(uint32_t indent, uint64_t pc, uint64_t load_bias, uint64_t start_offset,
-           uint64_t end_offset);
+  bool Log(uint32_t indent, uint64_t pc, uint64_t start_offset, uint64_t end_offset);
 
   const DwarfErrorData& last_error() { return last_error_; }
   DwarfErrorCode LastErrorCode() { return last_error_.code; }
diff --git a/libunwindstack/DwarfEhFrameWithHdr.cpp b/libunwindstack/DwarfEhFrameWithHdr.cpp
index 9a49013..fd6a457 100644
--- a/libunwindstack/DwarfEhFrameWithHdr.cpp
+++ b/libunwindstack/DwarfEhFrameWithHdr.cpp
@@ -22,12 +22,18 @@
 
 #include "Check.h"
 #include "DwarfEhFrameWithHdr.h"
+#include "DwarfEncoding.h"
 
 namespace unwindstack {
 
+static inline bool IsEncodingRelative(uint8_t encoding) {
+  encoding >>= 4;
+  return encoding > 0 && encoding <= DW_EH_PE_funcrel;
+}
+
 template <typename AddressType>
-bool DwarfEhFrameWithHdr<AddressType>::Init(uint64_t offset, uint64_t size) {
-  uint8_t data[4];
+bool DwarfEhFrameWithHdr<AddressType>::Init(uint64_t offset, uint64_t size, uint64_t load_bias) {
+  load_bias_ = load_bias;
 
   memory_.clear_func_offset();
   memory_.clear_text_offset();
@@ -35,6 +41,7 @@
   memory_.set_cur_offset(offset);
 
   // Read the first four bytes all at once.
+  uint8_t data[4];
   if (!memory_.ReadBytes(data, 4)) {
     last_error_.code = DWARF_ERROR_MEMORY_INVALID;
     last_error_.address = memory_.cur_offset();
@@ -100,7 +107,7 @@
 
   memory_.set_data_offset(entries_data_offset_);
   memory_.set_cur_offset(entries_offset_ + 2 * index * table_entry_size_);
-  memory_.set_pc_offset(memory_.cur_offset());
+  memory_.set_pc_offset(0);
   uint64_t value;
   if (!memory_.template ReadEncodedValue<AddressType>(table_encoding_, &value) ||
       !memory_.template ReadEncodedValue<AddressType>(table_encoding_, &info->offset)) {
@@ -109,6 +116,11 @@
     fde_info_.erase(index);
     return nullptr;
   }
+
+  // Relative encodings require adding in the load bias.
+  if (IsEncodingRelative(table_encoding_)) {
+    value += load_bias_;
+  }
   info->pc = value;
   return info;
 }
@@ -174,27 +186,27 @@
 
   memory_.set_data_offset(entries_data_offset_);
   memory_.set_cur_offset(cur_entries_offset_);
+  memory_.set_pc_offset(0);
   cur_entries_offset_ = 0;
 
   FdeInfo* prev_info = nullptr;
   for (size_t current = fde_info_.size();
        current < fde_count_ && memory_.cur_offset() < entries_end_; current++) {
-    memory_.set_pc_offset(memory_.cur_offset());
-    uint64_t value;
-    if (!memory_.template ReadEncodedValue<AddressType>(table_encoding_, &value)) {
-      last_error_.code = DWARF_ERROR_MEMORY_INVALID;
-      last_error_.address = memory_.cur_offset();
-      return false;
-    }
-
     FdeInfo* info = &fde_info_[current];
-    if (!memory_.template ReadEncodedValue<AddressType>(table_encoding_, &info->offset)) {
+    uint64_t value;
+    if (!memory_.template ReadEncodedValue<AddressType>(table_encoding_, &value) ||
+        !memory_.template ReadEncodedValue<AddressType>(table_encoding_, &info->offset)) {
       fde_info_.erase(current);
       last_error_.code = DWARF_ERROR_MEMORY_INVALID;
       last_error_.address = memory_.cur_offset();
       return false;
     }
-    info->pc = value + 4;
+
+    // Relative encodings require adding in the load bias.
+    if (IsEncodingRelative(table_encoding_)) {
+      value += load_bias_;
+    }
+    info->pc = value;
 
     if (pc < info->pc) {
       if (prev_info == nullptr) {
diff --git a/libunwindstack/DwarfEhFrameWithHdr.h b/libunwindstack/DwarfEhFrameWithHdr.h
index 3571166..d16dd10 100644
--- a/libunwindstack/DwarfEhFrameWithHdr.h
+++ b/libunwindstack/DwarfEhFrameWithHdr.h
@@ -38,6 +38,7 @@
   using DwarfSectionImpl<AddressType>::entries_offset_;
   using DwarfSectionImpl<AddressType>::entries_end_;
   using DwarfSectionImpl<AddressType>::last_error_;
+  using DwarfSectionImpl<AddressType>::load_bias_;
 
   struct FdeInfo {
     AddressType pc;
@@ -47,7 +48,7 @@
   DwarfEhFrameWithHdr(Memory* memory) : DwarfEhFrame<AddressType>(memory) {}
   virtual ~DwarfEhFrameWithHdr() = default;
 
-  bool Init(uint64_t offset, uint64_t size) override;
+  bool Init(uint64_t offset, uint64_t size, uint64_t load_bias) override;
 
   bool GetFdeOffsetFromPc(uint64_t pc, uint64_t* fde_offset) override;
 
diff --git a/libunwindstack/DwarfSection.cpp b/libunwindstack/DwarfSection.cpp
index 65eec65..eb83949 100644
--- a/libunwindstack/DwarfSection.cpp
+++ b/libunwindstack/DwarfSection.cpp
@@ -420,6 +420,7 @@
           last_error_.address = memory_.cur_offset();
           return false;
         }
+        memory_.set_pc_offset(pc_offset_);
         if (!memory_.ReadEncodedValue<AddressType>(encoding, &cie->personality_handler)) {
           last_error_.code = DWARF_ERROR_MEMORY_INVALID;
           last_error_.address = memory_.cur_offset();
@@ -521,19 +522,19 @@
   }
   memory_.set_cur_offset(cur_offset);
 
-  if (!memory_.ReadEncodedValue<AddressType>(cie->fde_address_encoding & 0xf, &fde->pc_start)) {
-    last_error_.code = DWARF_ERROR_MEMORY_INVALID;
-    last_error_.address = memory_.cur_offset();
-    return false;
-  }
+  // The load bias only applies to the start.
+  memory_.set_pc_offset(load_bias_);
+  bool valid = memory_.ReadEncodedValue<AddressType>(cie->fde_address_encoding, &fde->pc_start);
   fde->pc_start = AdjustPcFromFde(fde->pc_start);
 
-  if (!memory_.ReadEncodedValue<AddressType>(cie->fde_address_encoding & 0xf, &fde->pc_end)) {
+  memory_.set_pc_offset(0);
+  if (!valid || !memory_.ReadEncodedValue<AddressType>(cie->fde_address_encoding, &fde->pc_end)) {
     last_error_.code = DWARF_ERROR_MEMORY_INVALID;
     last_error_.address = memory_.cur_offset();
     return false;
   }
   fde->pc_end += fde->pc_start;
+
   if (cie->augmentation_string.size() > 0 && cie->augmentation_string[0] == 'z') {
     // Augmentation Size
     uint64_t aug_length;
@@ -544,6 +545,7 @@
     }
     uint64_t cur_offset = memory_.cur_offset();
 
+    memory_.set_pc_offset(pc_offset_);
     if (!memory_.ReadEncodedValue<AddressType>(cie->lsda_encoding, &fde->lsda_address)) {
       last_error_.code = DWARF_ERROR_MEMORY_INVALID;
       last_error_.address = memory_.cur_offset();
@@ -582,17 +584,16 @@
 }
 
 template <typename AddressType>
-bool DwarfSectionImpl<AddressType>::Log(uint8_t indent, uint64_t pc, uint64_t load_bias,
-                                        const DwarfFde* fde) {
+bool DwarfSectionImpl<AddressType>::Log(uint8_t indent, uint64_t pc, const DwarfFde* fde) {
   DwarfCfa<AddressType> cfa(&memory_, fde);
 
   // Always print the cie information.
   const DwarfCie* cie = fde->cie;
-  if (!cfa.Log(indent, pc, load_bias, cie->cfa_instructions_offset, cie->cfa_instructions_end)) {
+  if (!cfa.Log(indent, pc, cie->cfa_instructions_offset, cie->cfa_instructions_end)) {
     last_error_ = cfa.last_error();
     return false;
   }
-  if (!cfa.Log(indent, pc, load_bias, fde->cfa_instructions_offset, fde->cfa_instructions_end)) {
+  if (!cfa.Log(indent, pc, fde->cfa_instructions_offset, fde->cfa_instructions_end)) {
     last_error_ = cfa.last_error();
     return false;
   }
@@ -600,15 +601,16 @@
 }
 
 template <typename AddressType>
-bool DwarfSectionImpl<AddressType>::Init(uint64_t offset, uint64_t size) {
+bool DwarfSectionImpl<AddressType>::Init(uint64_t offset, uint64_t size, uint64_t load_bias) {
+  load_bias_ = load_bias;
   entries_offset_ = offset;
   entries_end_ = offset + size;
 
   memory_.clear_func_offset();
   memory_.clear_text_offset();
-  memory_.set_data_offset(offset);
   memory_.set_cur_offset(offset);
-  memory_.set_pc_offset(offset);
+  memory_.set_data_offset(offset);
+  pc_offset_ = offset;
 
   return CreateSortedFdeList();
 }
@@ -717,6 +719,7 @@
         return false;
       }
       uint64_t value;
+      memory_.set_pc_offset(pc_offset_);
       if (!memory_.template ReadEncodedValue<AddressType>(encoding, &value)) {
         last_error_.code = DWARF_ERROR_MEMORY_INVALID;
         last_error_.address = memory_.cur_offset();
@@ -737,15 +740,13 @@
   }
 
   uint64_t start;
-  if (!memory_.template ReadEncodedValue<AddressType>(encoding & 0xf, &start)) {
-    last_error_.code = DWARF_ERROR_MEMORY_INVALID;
-    last_error_.address = memory_.cur_offset();
-    return false;
-  }
+  memory_.set_pc_offset(load_bias_);
+  bool valid = memory_.template ReadEncodedValue<AddressType>(encoding, &start);
   start = AdjustPcFromFde(start);
 
   uint64_t length;
-  if (!memory_.template ReadEncodedValue<AddressType>(encoding & 0xf, &length)) {
+  memory_.set_pc_offset(0);
+  if (!valid || !memory_.template ReadEncodedValue<AddressType>(encoding, &length)) {
     last_error_.code = DWARF_ERROR_MEMORY_INVALID;
     last_error_.address = memory_.cur_offset();
     return false;
@@ -877,7 +878,7 @@
   while (first < last) {
     size_t current = (first + last) / 2;
     const FdeInfo* info = &fdes_[current];
-    if (pc >= info->start && pc <= info->end) {
+    if (pc >= info->start && pc < info->end) {
       *fde_offset = info->offset;
       return true;
     }
diff --git a/libunwindstack/Elf.cpp b/libunwindstack/Elf.cpp
index 3762107..4723606 100644
--- a/libunwindstack/Elf.cpp
+++ b/libunwindstack/Elf.cpp
@@ -53,7 +53,7 @@
 
   valid_ = interface_->Init(&load_bias_);
   if (valid_) {
-    interface_->InitHeaders();
+    interface_->InitHeaders(load_bias_);
     if (init_gnu_debugdata) {
       InitGnuDebugdata();
     } else {
@@ -83,7 +83,7 @@
   // is in the uncompressed data.
   uint64_t load_bias;
   if (gnu->Init(&load_bias)) {
-    gnu->InitHeaders();
+    gnu->InitHeaders(load_bias);
     interface_->SetGnuDebugdataInterface(gnu);
   } else {
     // Free all of the memory associated with the gnu_debugdata section.
@@ -103,9 +103,9 @@
 
 bool Elf::GetFunctionName(uint64_t addr, std::string* name, uint64_t* func_offset) {
   std::lock_guard<std::mutex> guard(lock_);
-  return valid_ && (interface_->GetFunctionName(addr, load_bias_, name, func_offset) ||
-                    (gnu_debugdata_interface_ && gnu_debugdata_interface_->GetFunctionName(
-                                                     addr, load_bias_, name, func_offset)));
+  return valid_ && (interface_->GetFunctionName(addr, name, func_offset) ||
+                    (gnu_debugdata_interface_ &&
+                     gnu_debugdata_interface_->GetFunctionName(addr, name, func_offset)));
 }
 
 bool Elf::GetGlobalVariable(const std::string& name, uint64_t* memory_address) {
@@ -174,7 +174,7 @@
 
   // Lock during the step which can update information in the object.
   std::lock_guard<std::mutex> guard(lock_);
-  return interface_->Step(adjusted_rel_pc, load_bias_, regs, process_memory, finished);
+  return interface_->Step(adjusted_rel_pc, regs, process_memory, finished);
 }
 
 bool Elf::IsValidElf(Memory* memory) {
@@ -220,7 +220,6 @@
   if (!valid_ || pc < load_bias_) {
     return false;
   }
-  pc -= load_bias_;
 
   if (interface_->IsValidPc(pc)) {
     return true;
diff --git a/libunwindstack/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp
index 4c05a1b..954a821 100644
--- a/libunwindstack/ElfInterface.cpp
+++ b/libunwindstack/ElfInterface.cpp
@@ -124,10 +124,10 @@
 }
 
 template <typename AddressType>
-void ElfInterface::InitHeadersWithTemplate() {
+void ElfInterface::InitHeadersWithTemplate(uint64_t load_bias) {
   if (eh_frame_hdr_offset_ != 0) {
     eh_frame_.reset(new DwarfEhFrameWithHdr<AddressType>(memory_));
-    if (!eh_frame_->Init(eh_frame_hdr_offset_, eh_frame_hdr_size_)) {
+    if (!eh_frame_->Init(eh_frame_hdr_offset_, eh_frame_hdr_size_, load_bias)) {
       eh_frame_.reset(nullptr);
     }
   }
@@ -136,7 +136,7 @@
     // If there is an eh_frame section without an eh_frame_hdr section,
     // or using the frame hdr object failed to init.
     eh_frame_.reset(new DwarfEhFrame<AddressType>(memory_));
-    if (!eh_frame_->Init(eh_frame_offset_, eh_frame_size_)) {
+    if (!eh_frame_->Init(eh_frame_offset_, eh_frame_size_, load_bias)) {
       eh_frame_.reset(nullptr);
     }
   }
@@ -150,7 +150,7 @@
 
   if (debug_frame_offset_ != 0) {
     debug_frame_.reset(new DwarfDebugFrame<AddressType>(memory_));
-    if (!debug_frame_->Init(debug_frame_offset_, debug_frame_size_)) {
+    if (!debug_frame_->Init(debug_frame_offset_, debug_frame_size_, load_bias)) {
       debug_frame_.reset(nullptr);
       debug_frame_offset_ = 0;
       debug_frame_size_ = static_cast<uint64_t>(-1);
@@ -441,14 +441,14 @@
 }
 
 template <typename SymType>
-bool ElfInterface::GetFunctionNameWithTemplate(uint64_t addr, uint64_t load_bias, std::string* name,
+bool ElfInterface::GetFunctionNameWithTemplate(uint64_t addr, std::string* name,
                                                uint64_t* func_offset) {
   if (symbols_.empty()) {
     return false;
   }
 
   for (const auto symbol : symbols_) {
-    if (symbol->GetName<SymType>(addr, load_bias, memory_, name, func_offset)) {
+    if (symbol->GetName<SymType>(addr, memory_, name, func_offset)) {
       return true;
     }
   }
@@ -469,34 +469,25 @@
   return false;
 }
 
-bool ElfInterface::Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
-                        bool* finished) {
+bool ElfInterface::Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) {
   last_error_.code = ERROR_NONE;
   last_error_.address = 0;
 
-  // Adjust the load bias to get the real relative pc.
-  if (pc < load_bias) {
-    last_error_.code = ERROR_UNWIND_INFO;
-    return false;
-  }
-  uint64_t adjusted_pc = pc - load_bias;
-
   // Try the debug_frame first since it contains the most specific unwind
   // information.
   DwarfSection* debug_frame = debug_frame_.get();
-  if (debug_frame != nullptr && debug_frame->Step(adjusted_pc, regs, process_memory, finished)) {
+  if (debug_frame != nullptr && debug_frame->Step(pc, regs, process_memory, finished)) {
     return true;
   }
 
   // Try the eh_frame next.
   DwarfSection* eh_frame = eh_frame_.get();
-  if (eh_frame != nullptr && eh_frame->Step(adjusted_pc, regs, process_memory, finished)) {
+  if (eh_frame != nullptr && eh_frame->Step(pc, regs, process_memory, finished)) {
     return true;
   }
 
-  // Finally try the gnu_debugdata interface, but always use a zero load bias.
   if (gnu_debugdata_interface_ != nullptr &&
-      gnu_debugdata_interface_->Step(pc, 0, regs, process_memory, finished)) {
+      gnu_debugdata_interface_->Step(pc, regs, process_memory, finished)) {
     return true;
   }
 
@@ -559,8 +550,8 @@
 }
 
 // Instantiate all of the needed template functions.
-template void ElfInterface::InitHeadersWithTemplate<uint32_t>();
-template void ElfInterface::InitHeadersWithTemplate<uint64_t>();
+template void ElfInterface::InitHeadersWithTemplate<uint32_t>(uint64_t);
+template void ElfInterface::InitHeadersWithTemplate<uint64_t>(uint64_t);
 
 template bool ElfInterface::ReadAllHeaders<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr>(uint64_t*);
 template bool ElfInterface::ReadAllHeaders<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr>(uint64_t*);
@@ -574,9 +565,9 @@
 template bool ElfInterface::GetSonameWithTemplate<Elf32_Dyn>(std::string*);
 template bool ElfInterface::GetSonameWithTemplate<Elf64_Dyn>(std::string*);
 
-template bool ElfInterface::GetFunctionNameWithTemplate<Elf32_Sym>(uint64_t, uint64_t, std::string*,
+template bool ElfInterface::GetFunctionNameWithTemplate<Elf32_Sym>(uint64_t, std::string*,
                                                                    uint64_t*);
-template bool ElfInterface::GetFunctionNameWithTemplate<Elf64_Sym>(uint64_t, uint64_t, std::string*,
+template bool ElfInterface::GetFunctionNameWithTemplate<Elf64_Sym>(uint64_t, std::string*,
                                                                    uint64_t*);
 
 template bool ElfInterface::GetGlobalVariableWithTemplate<Elf32_Sym>(const std::string&, uint64_t*);
diff --git a/libunwindstack/ElfInterfaceArm.cpp b/libunwindstack/ElfInterfaceArm.cpp
index f93baeb..9b61599 100644
--- a/libunwindstack/ElfInterfaceArm.cpp
+++ b/libunwindstack/ElfInterfaceArm.cpp
@@ -26,6 +26,14 @@
 
 namespace unwindstack {
 
+bool ElfInterfaceArm::Init(uint64_t* load_bias) {
+  if (!ElfInterface32::Init(load_bias)) {
+    return false;
+  }
+  load_bias_ = *load_bias;
+  return true;
+}
+
 bool ElfInterfaceArm::FindEntry(uint32_t pc, uint64_t* entry_offset) {
   if (start_offset_ == 0 || total_entries_ == 0) {
     last_error_.code = ERROR_UNWIND_INFO;
@@ -96,24 +104,22 @@
   return true;
 }
 
-bool ElfInterfaceArm::Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
-                           bool* finished) {
+bool ElfInterfaceArm::Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) {
   // Dwarf unwind information is precise about whether a pc is covered or not,
   // but arm unwind information only has ranges of pc. In order to avoid
   // incorrectly doing a bad unwind using arm unwind information for a
   // different function, always try and unwind with the dwarf information first.
-  return ElfInterface32::Step(pc, load_bias, regs, process_memory, finished) ||
-         StepExidx(pc, load_bias, regs, process_memory, finished);
+  return ElfInterface32::Step(pc, regs, process_memory, finished) ||
+         StepExidx(pc, regs, process_memory, finished);
 }
 
-bool ElfInterfaceArm::StepExidx(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
-                                bool* finished) {
+bool ElfInterfaceArm::StepExidx(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) {
   // Adjust the load bias to get the real relative pc.
-  if (pc < load_bias) {
+  if (pc < load_bias_) {
     last_error_.code = ERROR_UNWIND_INFO;
     return false;
   }
-  pc -= load_bias;
+  pc -= load_bias_;
 
   RegsArm* regs_arm = reinterpret_cast<RegsArm*>(regs);
   uint64_t entry_offset;
@@ -167,13 +173,12 @@
   return return_value;
 }
 
-bool ElfInterfaceArm::GetFunctionName(uint64_t addr, uint64_t load_bias, std::string* name,
-                                      uint64_t* offset) {
+bool ElfInterfaceArm::GetFunctionName(uint64_t addr, std::string* name, uint64_t* offset) {
   // For ARM, thumb function symbols have bit 0 set, but the address passed
   // in here might not have this bit set and result in a failure to find
   // the thumb function names. Adjust the address and offset to account
   // for this possible case.
-  if (ElfInterface32::GetFunctionName(addr | 1, load_bias, name, offset)) {
+  if (ElfInterface32::GetFunctionName(addr | 1, name, offset)) {
     *offset &= ~1;
     return true;
   }
diff --git a/libunwindstack/ElfInterfaceArm.h b/libunwindstack/ElfInterfaceArm.h
index c1597ce..18efb6c 100644
--- a/libunwindstack/ElfInterfaceArm.h
+++ b/libunwindstack/ElfInterfaceArm.h
@@ -64,28 +64,30 @@
   iterator begin() { return iterator(this, 0); }
   iterator end() { return iterator(this, total_entries_); }
 
+  bool Init(uint64_t* load_bias) override;
+
   bool GetPrel31Addr(uint32_t offset, uint32_t* addr);
 
   bool FindEntry(uint32_t pc, uint64_t* entry_offset);
 
   bool HandleType(uint64_t offset, uint32_t type, uint64_t load_bias) override;
 
-  bool Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
-            bool* finished) override;
+  bool Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) override;
 
-  bool StepExidx(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
-                 bool* finished);
+  bool StepExidx(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished);
 
-  bool GetFunctionName(uint64_t addr, uint64_t load_bias, std::string* name,
-                       uint64_t* offset) override;
+  bool GetFunctionName(uint64_t addr, std::string* name, uint64_t* offset) override;
 
   uint64_t start_offset() { return start_offset_; }
 
   size_t total_entries() { return total_entries_; }
 
+  void set_load_bias(uint64_t load_bias) { load_bias_ = load_bias; }
+
  protected:
   uint64_t start_offset_ = 0;
   size_t total_entries_ = 0;
+  uint64_t load_bias_ = 0;
 
   std::unordered_map<size_t, uint32_t> addrs_;
 };
diff --git a/libunwindstack/Symbols.cpp b/libunwindstack/Symbols.cpp
index 25def40..14ebdbb 100644
--- a/libunwindstack/Symbols.cpp
+++ b/libunwindstack/Symbols.cpp
@@ -54,10 +54,7 @@
 }
 
 template <typename SymType>
-bool Symbols::GetName(uint64_t addr, uint64_t load_bias, Memory* elf_memory, std::string* name,
-                      uint64_t* func_offset) {
-  addr += load_bias;
-
+bool Symbols::GetName(uint64_t addr, Memory* elf_memory, std::string* name, uint64_t* func_offset) {
   if (symbols_.size() != 0) {
     const Info* info = GetInfoFromCache(addr);
     if (info) {
@@ -81,9 +78,6 @@
     if (entry.st_shndx != SHN_UNDEF && ELF32_ST_TYPE(entry.st_info) == STT_FUNC) {
       // Treat st_value as virtual address.
       uint64_t start_offset = entry.st_value;
-      if (entry.st_shndx != SHN_ABS) {
-        start_offset += load_bias;
-      }
       uint64_t end_offset = start_offset + entry.st_size;
 
       // Cache the value.
@@ -134,8 +128,8 @@
 }
 
 // Instantiate all of the needed template functions.
-template bool Symbols::GetName<Elf32_Sym>(uint64_t, uint64_t, Memory*, std::string*, uint64_t*);
-template bool Symbols::GetName<Elf64_Sym>(uint64_t, uint64_t, Memory*, std::string*, uint64_t*);
+template bool Symbols::GetName<Elf32_Sym>(uint64_t, Memory*, std::string*, uint64_t*);
+template bool Symbols::GetName<Elf64_Sym>(uint64_t, Memory*, std::string*, uint64_t*);
 
 template bool Symbols::GetGlobal<Elf32_Sym>(Memory*, const std::string&, uint64_t*);
 template bool Symbols::GetGlobal<Elf64_Sym>(Memory*, const std::string&, uint64_t*);
diff --git a/libunwindstack/Symbols.h b/libunwindstack/Symbols.h
index 7d239c1..7fcd067 100644
--- a/libunwindstack/Symbols.h
+++ b/libunwindstack/Symbols.h
@@ -44,8 +44,7 @@
   const Info* GetInfoFromCache(uint64_t addr);
 
   template <typename SymType>
-  bool GetName(uint64_t addr, uint64_t load_bias, Memory* elf_memory, std::string* name,
-               uint64_t* func_offset);
+  bool GetName(uint64_t addr, Memory* elf_memory, std::string* name, uint64_t* func_offset);
 
   template <typename SymType>
   bool GetGlobal(Memory* elf_memory, const std::string& name, uint64_t* memory_address);
diff --git a/libunwindstack/include/unwindstack/DwarfSection.h b/libunwindstack/include/unwindstack/DwarfSection.h
index 209c54a..847f382 100644
--- a/libunwindstack/include/unwindstack/DwarfSection.h
+++ b/libunwindstack/include/unwindstack/DwarfSection.h
@@ -78,13 +78,13 @@
   DwarfErrorCode LastErrorCode() { return last_error_.code; }
   uint64_t LastErrorAddress() { return last_error_.address; }
 
-  virtual bool Init(uint64_t offset, uint64_t size) = 0;
+  virtual bool Init(uint64_t offset, uint64_t size, uint64_t load_bias) = 0;
 
   virtual bool Eval(const DwarfCie*, Memory*, const dwarf_loc_regs_t&, Regs*, bool*) = 0;
 
   virtual bool GetFdeOffsetFromPc(uint64_t pc, uint64_t* fde_offset) = 0;
 
-  virtual bool Log(uint8_t indent, uint64_t pc, uint64_t load_bias, const DwarfFde* fde) = 0;
+  virtual bool Log(uint8_t indent, uint64_t pc, const DwarfFde* fde) = 0;
 
   virtual const DwarfFde* GetFdeFromIndex(size_t index) = 0;
 
@@ -131,7 +131,7 @@
   DwarfSectionImpl(Memory* memory) : DwarfSection(memory) {}
   virtual ~DwarfSectionImpl() = default;
 
-  bool Init(uint64_t offset, uint64_t size) override;
+  bool Init(uint64_t offset, uint64_t size, uint64_t load_bias) override;
 
   bool GetFdeOffsetFromPc(uint64_t pc, uint64_t* fde_offset) override;
 
@@ -150,7 +150,7 @@
 
   bool GetCfaLocationInfo(uint64_t pc, const DwarfFde* fde, dwarf_loc_regs_t* loc_regs) override;
 
-  bool Log(uint8_t indent, uint64_t pc, uint64_t load_bias, const DwarfFde* fde) override;
+  bool Log(uint8_t indent, uint64_t pc, const DwarfFde* fde) override;
 
  protected:
   bool EvalExpression(const DwarfLocation& loc, Memory* regular_memory, AddressType* value,
@@ -162,6 +162,9 @@
 
   bool CreateSortedFdeList();
 
+  uint64_t load_bias_ = 0;
+  uint64_t pc_offset_ = 0;
+
   std::vector<FdeInfo> fdes_;
   uint64_t entries_offset_;
   uint64_t entries_end_;
diff --git a/libunwindstack/include/unwindstack/ElfInterface.h b/libunwindstack/include/unwindstack/ElfInterface.h
index 3a221bc..4d25c40 100644
--- a/libunwindstack/include/unwindstack/ElfInterface.h
+++ b/libunwindstack/include/unwindstack/ElfInterface.h
@@ -54,17 +54,15 @@
 
   virtual bool Init(uint64_t* load_bias) = 0;
 
-  virtual void InitHeaders() = 0;
+  virtual void InitHeaders(uint64_t load_bias) = 0;
 
   virtual bool GetSoname(std::string* name) = 0;
 
-  virtual bool GetFunctionName(uint64_t addr, uint64_t load_bias, std::string* name,
-                               uint64_t* offset) = 0;
+  virtual bool GetFunctionName(uint64_t addr, std::string* name, uint64_t* offset) = 0;
 
   virtual bool GetGlobalVariable(const std::string& name, uint64_t* memory_address) = 0;
 
-  virtual bool Step(uint64_t rel_pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
-                    bool* finished);
+  virtual bool Step(uint64_t rel_pc, Regs* regs, Memory* process_memory, bool* finished);
 
   virtual bool IsValidPc(uint64_t pc);
 
@@ -100,7 +98,7 @@
 
  protected:
   template <typename AddressType>
-  void InitHeadersWithTemplate();
+  void InitHeadersWithTemplate(uint64_t load_bias);
 
   template <typename EhdrType, typename PhdrType, typename ShdrType>
   bool ReadAllHeaders(uint64_t* load_bias);
@@ -115,8 +113,7 @@
   bool GetSonameWithTemplate(std::string* soname);
 
   template <typename SymType>
-  bool GetFunctionNameWithTemplate(uint64_t addr, uint64_t load_bias, std::string* name,
-                                   uint64_t* func_offset);
+  bool GetFunctionNameWithTemplate(uint64_t addr, std::string* name, uint64_t* func_offset);
 
   template <typename SymType>
   bool GetGlobalVariableWithTemplate(const std::string& name, uint64_t* memory_address);
@@ -169,15 +166,16 @@
     return ElfInterface::ReadAllHeaders<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr>(load_bias);
   }
 
-  void InitHeaders() override { ElfInterface::InitHeadersWithTemplate<uint32_t>(); }
+  void InitHeaders(uint64_t load_bias) override {
+    ElfInterface::InitHeadersWithTemplate<uint32_t>(load_bias);
+  }
 
   bool GetSoname(std::string* soname) override {
     return ElfInterface::GetSonameWithTemplate<Elf32_Dyn>(soname);
   }
 
-  bool GetFunctionName(uint64_t addr, uint64_t load_bias, std::string* name,
-                       uint64_t* func_offset) override {
-    return ElfInterface::GetFunctionNameWithTemplate<Elf32_Sym>(addr, load_bias, name, func_offset);
+  bool GetFunctionName(uint64_t addr, std::string* name, uint64_t* func_offset) override {
+    return ElfInterface::GetFunctionNameWithTemplate<Elf32_Sym>(addr, name, func_offset);
   }
 
   bool GetGlobalVariable(const std::string& name, uint64_t* memory_address) override {
@@ -198,15 +196,16 @@
     return ElfInterface::ReadAllHeaders<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr>(load_bias);
   }
 
-  void InitHeaders() override { ElfInterface::InitHeadersWithTemplate<uint64_t>(); }
+  void InitHeaders(uint64_t load_bias) override {
+    ElfInterface::InitHeadersWithTemplate<uint64_t>(load_bias);
+  }
 
   bool GetSoname(std::string* soname) override {
     return ElfInterface::GetSonameWithTemplate<Elf64_Dyn>(soname);
   }
 
-  bool GetFunctionName(uint64_t addr, uint64_t load_bias, std::string* name,
-                       uint64_t* func_offset) override {
-    return ElfInterface::GetFunctionNameWithTemplate<Elf64_Sym>(addr, load_bias, name, func_offset);
+  bool GetFunctionName(uint64_t addr, std::string* name, uint64_t* func_offset) override {
+    return ElfInterface::GetFunctionNameWithTemplate<Elf64_Sym>(addr, name, func_offset);
   }
 
   bool GetGlobalVariable(const std::string& name, uint64_t* memory_address) override {
diff --git a/libunwindstack/tests/ArmExidxDecodeTest.cpp b/libunwindstack/tests/ArmExidxDecodeTest.cpp
index 8d6d00d..5f3d1ea 100644
--- a/libunwindstack/tests/ArmExidxDecodeTest.cpp
+++ b/libunwindstack/tests/ArmExidxDecodeTest.cpp
@@ -36,8 +36,6 @@
 class ArmExidxDecodeTest : public ::testing::TestWithParam<std::string> {
  protected:
   void Init(Memory* process_memory = nullptr) {
-    TearDown();
-
     if (process_memory == nullptr) {
       process_memory = &process_memory_;
     }
@@ -50,8 +48,8 @@
     regs_arm_->set_sp(0);
 
     exidx_.reset(new ArmExidx(regs_arm_.get(), &elf_memory_, process_memory));
-    if (log_) {
-      exidx_->set_log(true);
+    if (log_ != ARM_LOG_NONE) {
+      exidx_->set_log(log_);
       exidx_->set_log_indent(0);
       exidx_->set_log_skip_execution(false);
     }
@@ -60,14 +58,20 @@
   }
 
   void SetUp() override {
-    if (GetParam() != "no_logging") {
-      log_ = false;
+    if (GetParam() == "no_logging") {
+      log_ = ARM_LOG_NONE;
+    } else if (GetParam() == "register_logging") {
+      log_ = ARM_LOG_BY_REG;
     } else {
-      log_ = true;
+      log_ = ARM_LOG_FULL;
     }
-    ResetLogs();
     elf_memory_.Clear();
     process_memory_.Clear();
+    ResetExidx();
+  }
+
+  void ResetExidx() {
+    ResetLogs();
     Init();
   }
 
@@ -77,7 +81,7 @@
 
   MemoryFake elf_memory_;
   MemoryFake process_memory_;
-  bool log_;
+  ArmLogType log_;
 };
 
 TEST_P(ArmExidxDecodeTest, vsp_incr) {
@@ -86,38 +90,59 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = vsp + 4\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = vsp + 4\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r13 + 4\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10004U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->clear();
   data_->push_back(0x01);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = vsp + 8\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = vsp + 8\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r13 + 8\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x1000cU, exidx_->cfa());
+  ASSERT_EQ(0x10008U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->clear();
   data_->push_back(0x3f);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = vsp + 256\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = vsp + 256\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r13 + 256\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x1010cU, exidx_->cfa());
+  ASSERT_EQ(0x10100U, exidx_->cfa());
 }
 
 TEST_P(ArmExidxDecodeTest, vsp_decr) {
@@ -126,38 +151,59 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = vsp - 4\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = vsp - 4\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r13 - 4\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0xfffcU, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->clear();
   data_->push_back(0x41);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = vsp - 8\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = vsp - 8\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r13 - 8\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0xfff4U, exidx_->cfa());
+  ASSERT_EQ(0xfff8U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->clear();
   data_->push_back(0x7f);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = vsp - 256\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = vsp - 256\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r13 - 256\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0xfef4U, exidx_->cfa());
+  ASSERT_EQ(0xff00U, exidx_->cfa());
 }
 
 TEST_P(ArmExidxDecodeTest, refuse_unwind) {
@@ -166,10 +212,14 @@
   data_->push_back(0x00);
   ASSERT_FALSE(exidx_->Decode());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind Refuse to unwind\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Refuse to unwind\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(ARM_STATUS_NO_UNWIND, exidx_->status());
 }
@@ -182,29 +232,60 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_TRUE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r15}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r15}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 4\n"
+          "4 unwind r15 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10004U, exidx_->cfa());
   ASSERT_EQ(0x10U, (*exidx_->regs())[15]);
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0x8f);
   data_->push_back(0xff);
   for (size_t i = 0; i < 12; i++) {
-    process_memory_.SetData32(0x10004 + i * 4, i + 0x20);
+    process_memory_.SetData32(0x10000 + i * 4, i + 0x20);
   }
   exidx_->set_pc_set(false);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_TRUE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15}\n",
-              GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15}\n",
+                GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 48\n"
+          "4 unwind r4 = [cfa - 48]\n"
+          "4 unwind r5 = [cfa - 44]\n"
+          "4 unwind r6 = [cfa - 40]\n"
+          "4 unwind r7 = [cfa - 36]\n"
+          "4 unwind r8 = [cfa - 32]\n"
+          "4 unwind r9 = [cfa - 28]\n"
+          "4 unwind r10 = [cfa - 24]\n"
+          "4 unwind r11 = [cfa - 20]\n"
+          "4 unwind r12 = [cfa - 16]\n"
+          "4 unwind r13 = [cfa - 12]\n"
+          "4 unwind r14 = [cfa - 8]\n"
+          "4 unwind r15 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
   // Popping r13 results in a modified cfa.
   ASSERT_EQ(0x29U, exidx_->cfa());
@@ -222,7 +303,7 @@
   ASSERT_EQ(0x2aU, (*exidx_->regs())[14]);
   ASSERT_EQ(0x2bU, (*exidx_->regs())[15]);
 
-  ResetLogs();
+  ResetExidx();
   exidx_->set_cfa(0x10034);
   data_->push_back(0x81);
   data_->push_back(0x28);
@@ -233,10 +314,22 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r7, r9, r12}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r7, r9, r12}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 12\n"
+          "4 unwind r7 = [cfa - 12]\n"
+          "4 unwind r9 = [cfa - 8]\n"
+          "4 unwind r12 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10040U, exidx_->cfa());
   ASSERT_EQ(0x11U, (*exidx_->regs())[7]);
@@ -255,34 +348,63 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = r0\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = r0\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r0\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(1U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
+  exidx_->set_cfa(0x100);
+  for (size_t i = 0; i < 15; i++) {
+    (*regs_arm_)[i] = i + 1;
+  }
   data_->push_back(0x93);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = r3\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = r3\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r3\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(4U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
+  exidx_->set_cfa(0x100);
+  for (size_t i = 0; i < 15; i++) {
+    (*regs_arm_)[i] = i + 1;
+  }
   data_->push_back(0x9e);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = r14\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = r14\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r14\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(15U, exidx_->cfa());
 }
@@ -292,22 +414,30 @@
   data_->push_back(0x9d);
   ASSERT_FALSE(exidx_->Decode());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind [Reserved]\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind [Reserved]\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(ARM_STATUS_RESERVED, exidx_->status());
 
   // 10011111: Reserved as prefix for Intel Wireless MMX register to register moves
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0x9f);
   ASSERT_FALSE(exidx_->Decode());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind [Reserved]\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind [Reserved]\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(ARM_STATUS_RESERVED, exidx_->status());
 }
@@ -319,53 +449,93 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r4}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r4}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 4\n"
+          "4 unwind r4 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10004U, exidx_->cfa());
   ASSERT_EQ(0x14U, (*exidx_->regs())[4]);
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xa3);
-  process_memory_.SetData32(0x10004, 0x20);
-  process_memory_.SetData32(0x10008, 0x30);
-  process_memory_.SetData32(0x1000c, 0x40);
-  process_memory_.SetData32(0x10010, 0x50);
+  process_memory_.SetData32(0x10000, 0x20);
+  process_memory_.SetData32(0x10004, 0x30);
+  process_memory_.SetData32(0x10008, 0x40);
+  process_memory_.SetData32(0x1000c, 0x50);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r4-r7}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r4-r7}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 16\n"
+          "4 unwind r4 = [cfa - 16]\n"
+          "4 unwind r5 = [cfa - 12]\n"
+          "4 unwind r6 = [cfa - 8]\n"
+          "4 unwind r7 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10014U, exidx_->cfa());
+  ASSERT_EQ(0x10010U, exidx_->cfa());
   ASSERT_EQ(0x20U, (*exidx_->regs())[4]);
   ASSERT_EQ(0x30U, (*exidx_->regs())[5]);
   ASSERT_EQ(0x40U, (*exidx_->regs())[6]);
   ASSERT_EQ(0x50U, (*exidx_->regs())[7]);
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xa7);
-  process_memory_.SetData32(0x10014, 0x41);
-  process_memory_.SetData32(0x10018, 0x51);
-  process_memory_.SetData32(0x1001c, 0x61);
-  process_memory_.SetData32(0x10020, 0x71);
-  process_memory_.SetData32(0x10024, 0x81);
-  process_memory_.SetData32(0x10028, 0x91);
-  process_memory_.SetData32(0x1002c, 0xa1);
-  process_memory_.SetData32(0x10030, 0xb1);
+  process_memory_.SetData32(0x10000, 0x41);
+  process_memory_.SetData32(0x10004, 0x51);
+  process_memory_.SetData32(0x10008, 0x61);
+  process_memory_.SetData32(0x1000c, 0x71);
+  process_memory_.SetData32(0x10010, 0x81);
+  process_memory_.SetData32(0x10014, 0x91);
+  process_memory_.SetData32(0x10018, 0xa1);
+  process_memory_.SetData32(0x1001c, 0xb1);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r4-r11}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r4-r11}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 32\n"
+          "4 unwind r4 = [cfa - 32]\n"
+          "4 unwind r5 = [cfa - 28]\n"
+          "4 unwind r6 = [cfa - 24]\n"
+          "4 unwind r7 = [cfa - 20]\n"
+          "4 unwind r8 = [cfa - 16]\n"
+          "4 unwind r9 = [cfa - 12]\n"
+          "4 unwind r10 = [cfa - 8]\n"
+          "4 unwind r11 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10034U, exidx_->cfa());
+  ASSERT_EQ(0x10020U, exidx_->cfa());
   ASSERT_EQ(0x41U, (*exidx_->regs())[4]);
   ASSERT_EQ(0x51U, (*exidx_->regs())[5]);
   ASSERT_EQ(0x61U, (*exidx_->regs())[6]);
@@ -384,57 +554,100 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r4, r14}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r4, r14}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 8\n"
+          "4 unwind r4 = [cfa - 8]\n"
+          "4 unwind r14 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10008U, exidx_->cfa());
   ASSERT_EQ(0x12U, (*exidx_->regs())[4]);
   ASSERT_EQ(0x22U, (*exidx_->regs())[14]);
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xab);
-  process_memory_.SetData32(0x10008, 0x1);
-  process_memory_.SetData32(0x1000c, 0x2);
-  process_memory_.SetData32(0x10010, 0x3);
-  process_memory_.SetData32(0x10014, 0x4);
-  process_memory_.SetData32(0x10018, 0x5);
+  process_memory_.SetData32(0x10000, 0x1);
+  process_memory_.SetData32(0x10004, 0x2);
+  process_memory_.SetData32(0x10008, 0x3);
+  process_memory_.SetData32(0x1000c, 0x4);
+  process_memory_.SetData32(0x10010, 0x5);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r4-r7, r14}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r4-r7, r14}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 20\n"
+          "4 unwind r4 = [cfa - 20]\n"
+          "4 unwind r5 = [cfa - 16]\n"
+          "4 unwind r6 = [cfa - 12]\n"
+          "4 unwind r7 = [cfa - 8]\n"
+          "4 unwind r14 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x1001cU, exidx_->cfa());
+  ASSERT_EQ(0x10014U, exidx_->cfa());
   ASSERT_EQ(0x1U, (*exidx_->regs())[4]);
   ASSERT_EQ(0x2U, (*exidx_->regs())[5]);
   ASSERT_EQ(0x3U, (*exidx_->regs())[6]);
   ASSERT_EQ(0x4U, (*exidx_->regs())[7]);
   ASSERT_EQ(0x5U, (*exidx_->regs())[14]);
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xaf);
-  process_memory_.SetData32(0x1001c, 0x1a);
-  process_memory_.SetData32(0x10020, 0x2a);
-  process_memory_.SetData32(0x10024, 0x3a);
-  process_memory_.SetData32(0x10028, 0x4a);
-  process_memory_.SetData32(0x1002c, 0x5a);
-  process_memory_.SetData32(0x10030, 0x6a);
-  process_memory_.SetData32(0x10034, 0x7a);
-  process_memory_.SetData32(0x10038, 0x8a);
-  process_memory_.SetData32(0x1003c, 0x9a);
+  process_memory_.SetData32(0x10000, 0x1a);
+  process_memory_.SetData32(0x10004, 0x2a);
+  process_memory_.SetData32(0x10008, 0x3a);
+  process_memory_.SetData32(0x1000c, 0x4a);
+  process_memory_.SetData32(0x10010, 0x5a);
+  process_memory_.SetData32(0x10014, 0x6a);
+  process_memory_.SetData32(0x10018, 0x7a);
+  process_memory_.SetData32(0x1001c, 0x8a);
+  process_memory_.SetData32(0x10020, 0x9a);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r4-r11, r14}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r4-r11, r14}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 36\n"
+          "4 unwind r4 = [cfa - 36]\n"
+          "4 unwind r5 = [cfa - 32]\n"
+          "4 unwind r6 = [cfa - 28]\n"
+          "4 unwind r7 = [cfa - 24]\n"
+          "4 unwind r8 = [cfa - 20]\n"
+          "4 unwind r9 = [cfa - 16]\n"
+          "4 unwind r10 = [cfa - 12]\n"
+          "4 unwind r11 = [cfa - 8]\n"
+          "4 unwind r14 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10040U, exidx_->cfa());
+  ASSERT_EQ(0x10024U, exidx_->cfa());
   ASSERT_EQ(0x1aU, (*exidx_->regs())[4]);
   ASSERT_EQ(0x2aU, (*exidx_->regs())[5]);
   ASSERT_EQ(0x3aU, (*exidx_->regs())[6]);
@@ -451,10 +664,17 @@
   data_->push_back(0xb0);
   ASSERT_FALSE(exidx_->Decode());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind finish\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind finish\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r13\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10000U, exidx_->cfa());
   ASSERT_EQ(ARM_STATUS_FINISH, exidx_->status());
@@ -466,10 +686,14 @@
   data_->push_back(0x00);
   ASSERT_FALSE(exidx_->Decode());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10000U, exidx_->cfa());
   ASSERT_EQ(ARM_STATUS_SPARE, exidx_->status());
@@ -477,15 +701,19 @@
   // 10110001 xxxxyyyy: Spare (xxxx != 0000)
   for (size_t x = 1; x < 16; x++) {
     for (size_t y = 0; y < 16; y++) {
-      ResetLogs();
+      ResetExidx();
       data_->push_back(0xb1);
       data_->push_back((x << 4) | y);
       ASSERT_FALSE(exidx_->Decode()) << "x, y = " << x << ", " << y;
       ASSERT_EQ("", GetFakeLogBuf()) << "x, y = " << x << ", " << y;
-      if (log_) {
-        ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint()) << "x, y = " << x << ", " << y;
-      } else {
-        ASSERT_EQ("", GetFakeLogPrint());
+      switch (log_) {
+        case ARM_LOG_NONE:
+          ASSERT_EQ("", GetFakeLogPrint());
+          break;
+        case ARM_LOG_FULL:
+        case ARM_LOG_BY_REG:
+          ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint()) << "x, y = " << x << ", " << y;
+          break;
       }
       ASSERT_EQ(0x10000U, exidx_->cfa()) << "x, y = " << x << ", " << y;
       ASSERT_EQ(ARM_STATUS_SPARE, exidx_->status());
@@ -494,29 +722,37 @@
 
   // 101101nn: Spare
   for (size_t n = 0; n < 4; n++) {
-    ResetLogs();
+    ResetExidx();
     data_->push_back(0xb4 | n);
     ASSERT_FALSE(exidx_->Decode()) << "n = " << n;
     ASSERT_EQ("", GetFakeLogBuf()) << "n = " << n;
-    if (log_) {
-      ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint()) << "n = " << n;
-    } else {
-      ASSERT_EQ("", GetFakeLogPrint());
+    switch (log_) {
+      case ARM_LOG_NONE:
+        ASSERT_EQ("", GetFakeLogPrint());
+        break;
+      case ARM_LOG_FULL:
+      case ARM_LOG_BY_REG:
+        ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint()) << "n = " << n;
+        break;
     }
     ASSERT_EQ(0x10000U, exidx_->cfa()) << "n = " << n;
     ASSERT_EQ(ARM_STATUS_SPARE, exidx_->status());
   }
 
   // 11000111 00000000: Spare
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xc7);
   data_->push_back(0x00);
   ASSERT_FALSE(exidx_->Decode());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10000U, exidx_->cfa());
   ASSERT_EQ(ARM_STATUS_SPARE, exidx_->status());
@@ -524,15 +760,19 @@
   // 11000111 xxxxyyyy: Spare (xxxx != 0000)
   for (size_t x = 1; x < 16; x++) {
     for (size_t y = 0; y < 16; y++) {
-      ResetLogs();
+      ResetExidx();
       data_->push_back(0xc7);
       data_->push_back(0x10);
       ASSERT_FALSE(exidx_->Decode()) << "x, y = " << x << ", " << y;
       ASSERT_EQ("", GetFakeLogBuf()) << "x, y = " << x << ", " << y;
-      if (log_) {
-        ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint()) << "x, y = " << x << ", " << y;
-      } else {
-        ASSERT_EQ("", GetFakeLogPrint());
+      switch (log_) {
+        case ARM_LOG_NONE:
+          ASSERT_EQ("", GetFakeLogPrint());
+          break;
+        case ARM_LOG_FULL:
+        case ARM_LOG_BY_REG:
+          ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint()) << "x, y = " << x << ", " << y;
+          break;
       }
       ASSERT_EQ(0x10000U, exidx_->cfa()) << "x, y = " << x << ", " << y;
       ASSERT_EQ(ARM_STATUS_SPARE, exidx_->status());
@@ -541,14 +781,18 @@
 
   // 11001yyy: Spare (yyy != 000, 001)
   for (size_t y = 2; y < 8; y++) {
-    ResetLogs();
+    ResetExidx();
     data_->push_back(0xc8 | y);
     ASSERT_FALSE(exidx_->Decode()) << "y = " << y;
     ASSERT_EQ("", GetFakeLogBuf()) << "y = " << y;
-    if (log_) {
-      ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint()) << "y = " << y;
-    } else {
-      ASSERT_EQ("", GetFakeLogPrint());
+    switch (log_) {
+      case ARM_LOG_NONE:
+        ASSERT_EQ("", GetFakeLogPrint());
+        break;
+      case ARM_LOG_FULL:
+      case ARM_LOG_BY_REG:
+        ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint()) << "y = " << y;
+        break;
     }
     ASSERT_EQ(0x10000U, exidx_->cfa()) << "y = " << y;
     ASSERT_EQ(ARM_STATUS_SPARE, exidx_->status());
@@ -557,14 +801,18 @@
   // 11xxxyyy: Spare (xxx != 000, 001, 010)
   for (size_t x = 3; x < 8; x++) {
     for (size_t y = 0; y < 8; y++) {
-      ResetLogs();
+      ResetExidx();
       data_->push_back(0xc0 | (x << 3) | y);
       ASSERT_FALSE(exidx_->Decode()) << "x, y = " << x << ", " << y;
       ASSERT_EQ("", GetFakeLogBuf()) << "x, y = " << x << ", " << y;
-      if (log_) {
-        ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint()) << "x, y = " << x << ", " << y;
-      } else {
-        ASSERT_EQ("", GetFakeLogPrint());
+      switch (log_) {
+        case ARM_LOG_NONE:
+          ASSERT_EQ("", GetFakeLogPrint());
+          break;
+        case ARM_LOG_FULL:
+        case ARM_LOG_BY_REG:
+          ASSERT_EQ("4 unwind Spare\n", GetFakeLogPrint()) << "x, y = " << x << ", " << y;
+          break;
       }
       ASSERT_EQ(0x10000U, exidx_->cfa()) << "x, y = " << x << ", " << y;
       ASSERT_EQ(ARM_STATUS_SPARE, exidx_->status());
@@ -580,47 +828,81 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r0}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r0}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 4\n"
+          "4 unwind r0 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10004U, exidx_->cfa());
   ASSERT_EQ(0x45U, (*exidx_->regs())[0]);
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xb1);
   data_->push_back(0x0a);
-  process_memory_.SetData32(0x10004, 0x23);
-  process_memory_.SetData32(0x10008, 0x24);
+  process_memory_.SetData32(0x10000, 0x23);
+  process_memory_.SetData32(0x10004, 0x24);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r1, r3}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r1, r3}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 8\n"
+          "4 unwind r1 = [cfa - 8]\n"
+          "4 unwind r3 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x1000cU, exidx_->cfa());
+  ASSERT_EQ(0x10008U, exidx_->cfa());
   ASSERT_EQ(0x23U, (*exidx_->regs())[1]);
   ASSERT_EQ(0x24U, (*exidx_->regs())[3]);
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xb1);
   data_->push_back(0x0f);
-  process_memory_.SetData32(0x1000c, 0x65);
-  process_memory_.SetData32(0x10010, 0x54);
-  process_memory_.SetData32(0x10014, 0x43);
-  process_memory_.SetData32(0x10018, 0x32);
+  process_memory_.SetData32(0x10000, 0x65);
+  process_memory_.SetData32(0x10004, 0x54);
+  process_memory_.SetData32(0x10008, 0x43);
+  process_memory_.SetData32(0x1000c, 0x32);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {r0, r1, r2, r3}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {r0, r1, r2, r3}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 16\n"
+          "4 unwind r0 = [cfa - 16]\n"
+          "4 unwind r1 = [cfa - 12]\n"
+          "4 unwind r2 = [cfa - 8]\n"
+          "4 unwind r3 = [cfa - 4]\n",
+          GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x1001cU, exidx_->cfa());
+  ASSERT_EQ(0x10010U, exidx_->cfa());
   ASSERT_EQ(0x65U, (*exidx_->regs())[0]);
   ASSERT_EQ(0x54U, (*exidx_->regs())[1]);
   ASSERT_EQ(0x43U, (*exidx_->regs())[2]);
@@ -634,28 +916,42 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = vsp + 1024\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = vsp + 1024\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r13 + 1024\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10400U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xb2);
   data_->push_back(0xff);
   data_->push_back(0x02);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = vsp + 2048\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = vsp + 2048\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r13 + 2048\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10c00U, exidx_->cfa());
+  ASSERT_EQ(0x10800U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xb2);
   data_->push_back(0xff);
   data_->push_back(0x82);
@@ -663,12 +959,19 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = vsp + 3147776\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind vsp = vsp + 3147776\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r13 + 3147776\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x311400U, exidx_->cfa());
+  ASSERT_EQ(0x310800U, exidx_->cfa());
 }
 
 TEST_P(ArmExidxDecodeTest, pop_vfp_fstmfdx) {
@@ -678,25 +981,37 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d0}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d0}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x1000cU, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xb3);
   data_->push_back(0x48);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d4-d12}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d4-d12}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10058U, exidx_->cfa());
+  ASSERT_EQ(0x1004cU, exidx_->cfa());
 }
 
 TEST_P(ArmExidxDecodeTest, pop_vfp8_fstmfdx) {
@@ -705,36 +1020,54 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d8}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d8}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x1000cU, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xbb);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d8-d11}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d8-d11}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10030U, exidx_->cfa());
+  ASSERT_EQ(0x10024U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xbf);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d8-d15}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d8-d15}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10074U, exidx_->cfa());
+  ASSERT_EQ(0x10044U, exidx_->cfa());
 }
 
 TEST_P(ArmExidxDecodeTest, pop_mmx_wr10) {
@@ -743,36 +1076,54 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {wR10}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {wR10}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported wRX register display\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10008U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xc2);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {wR10-wR12}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {wR10-wR12}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported wRX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10020U, exidx_->cfa());
+  ASSERT_EQ(0x10018U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xc5);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {wR10-wR15}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {wR10-wR15}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported wRX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10050U, exidx_->cfa());
+  ASSERT_EQ(0x10030U, exidx_->cfa());
 }
 
 TEST_P(ArmExidxDecodeTest, pop_mmx_wr) {
@@ -782,38 +1133,56 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {wR0}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {wR0}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported wRX register display\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10008U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xc6);
   data_->push_back(0x25);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {wR2-wR7}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {wR2-wR7}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported wRX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10038U, exidx_->cfa());
+  ASSERT_EQ(0x10030U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xc6);
   data_->push_back(0xff);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {wR15-wR30}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {wR15-wR30}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported wRX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x100b8U, exidx_->cfa());
+  ASSERT_EQ(0x10080U, exidx_->cfa());
 }
 
 TEST_P(ArmExidxDecodeTest, pop_mmx_wcgr) {
@@ -823,38 +1192,56 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {wCGR0}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {wCGR0}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported wCGR register display\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10004U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xc7);
   data_->push_back(0x0a);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {wCGR1, wCGR3}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {wCGR1, wCGR3}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported wCGR register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x1000cU, exidx_->cfa());
+  ASSERT_EQ(0x10008U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xc7);
   data_->push_back(0x0f);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {wCGR0, wCGR1, wCGR2, wCGR3}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {wCGR0, wCGR1, wCGR2, wCGR3}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported wCGR register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x1001cU, exidx_->cfa());
+  ASSERT_EQ(0x10010U, exidx_->cfa());
 }
 
 TEST_P(ArmExidxDecodeTest, pop_vfp16_vpush) {
@@ -864,38 +1251,56 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d16}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d16}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10008U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xc8);
   data_->push_back(0x14);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d17-d21}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d17-d21}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10030U, exidx_->cfa());
+  ASSERT_EQ(0x10028U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xc8);
   data_->push_back(0xff);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d31-d46}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d31-d46}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x100b0U, exidx_->cfa());
+  ASSERT_EQ(0x10080U, exidx_->cfa());
 }
 
 TEST_P(ArmExidxDecodeTest, pop_vfp_vpush) {
@@ -905,38 +1310,56 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d0}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d0}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10008U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xc9);
   data_->push_back(0x23);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d2-d5}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d2-d5}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10028U, exidx_->cfa());
+  ASSERT_EQ(0x10020U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xc9);
   data_->push_back(0xff);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d15-d30}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d15-d30}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x100a8U, exidx_->cfa());
+  ASSERT_EQ(0x10080U, exidx_->cfa());
 }
 
 TEST_P(ArmExidxDecodeTest, pop_vfp8_vpush) {
@@ -945,36 +1368,54 @@
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d8}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d8}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10008U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xd2);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d8-d10}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d8-d10}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10020U, exidx_->cfa());
+  ASSERT_EQ(0x10018U, exidx_->cfa());
 
-  ResetLogs();
+  ResetExidx();
   data_->push_back(0xd7);
   ASSERT_TRUE(exidx_->Decode());
   ASSERT_FALSE(exidx_->pc_set());
   ASSERT_EQ("", GetFakeLogBuf());
-  if (log_) {
-    ASSERT_EQ("4 unwind pop {d8-d15}\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ("4 unwind pop {d8-d15}\n", GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      ASSERT_EQ("4 unwind Unsupported DX register display\n", GetFakeLogPrint());
+      break;
   }
-  ASSERT_EQ(0x10060U, exidx_->cfa());
+  ASSERT_EQ(0x10040U, exidx_->cfa());
 }
 
 TEST_P(ArmExidxDecodeTest, expect_truncated) {
@@ -1047,32 +1488,147 @@
 TEST_P(ArmExidxDecodeTest, eval_multiple_decodes) {
   // vsp = vsp + 4
   data_->push_back(0x00);
-  // vsp = vsp + 8
+  // vsp = vsp + 12
   data_->push_back(0x02);
   // Finish
   data_->push_back(0xb0);
 
   ASSERT_TRUE(exidx_->Eval());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = vsp + 4\n"
-              "4 unwind vsp = vsp + 12\n"
-              "4 unwind finish\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ(
+          "4 unwind vsp = vsp + 4\n"
+          "4 unwind vsp = vsp + 12\n"
+          "4 unwind finish\n",
+          GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ("4 unwind cfa = r13 + 16\n", GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10010U, exidx_->cfa());
   ASSERT_FALSE(exidx_->pc_set());
 }
 
+TEST_P(ArmExidxDecodeTest, eval_vsp_add_after_pop) {
+  // Pop {r15}
+  data_->push_back(0x88);
+  data_->push_back(0x00);
+  // vsp = vsp + 12
+  data_->push_back(0x02);
+  // Finish
+  data_->push_back(0xb0);
+  process_memory_.SetData32(0x10000, 0x10);
+
+  ASSERT_TRUE(exidx_->Eval());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ(
+          "4 unwind pop {r15}\n"
+          "4 unwind vsp = vsp + 12\n"
+          "4 unwind finish\n",
+          GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 16\n"
+          "4 unwind r15 = [cfa - 16]\n",
+          GetFakeLogPrint());
+      break;
+  }
+  ASSERT_EQ(0x10010U, exidx_->cfa());
+  ASSERT_TRUE(exidx_->pc_set());
+  ASSERT_EQ(0x10U, (*exidx_->regs())[15]);
+}
+
+TEST_P(ArmExidxDecodeTest, eval_vsp_add_large_after_pop) {
+  // Pop {r15}
+  data_->push_back(0x88);
+  data_->push_back(0x00);
+  // vsp = vsp + 1024
+  data_->push_back(0xb2);
+  data_->push_back(0x7f);
+  // Finish
+  data_->push_back(0xb0);
+  process_memory_.SetData32(0x10000, 0x10);
+
+  ASSERT_TRUE(exidx_->Eval());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ(
+          "4 unwind pop {r15}\n"
+          "4 unwind vsp = vsp + 1024\n"
+          "4 unwind finish\n",
+          GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 1028\n"
+          "4 unwind r15 = [cfa - 1028]\n",
+          GetFakeLogPrint());
+      break;
+  }
+  ASSERT_EQ(0x10404U, exidx_->cfa());
+  ASSERT_TRUE(exidx_->pc_set());
+  ASSERT_EQ(0x10U, (*exidx_->regs())[15]);
+}
+
+TEST_P(ArmExidxDecodeTest, eval_vsp_sub_after_pop) {
+  // Pop {r15}
+  data_->push_back(0x88);
+  data_->push_back(0x00);
+  // vsp = vsp - 4
+  data_->push_back(0x41);
+  // Finish
+  data_->push_back(0xb0);
+  process_memory_.SetData32(0x10000, 0x10);
+
+  ASSERT_TRUE(exidx_->Eval());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ(
+          "4 unwind pop {r15}\n"
+          "4 unwind vsp = vsp - 8\n"
+          "4 unwind finish\n",
+          GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 - 4\n"
+          "4 unwind r15 = [cfa + 4]\n",
+          GetFakeLogPrint());
+      break;
+  }
+  ASSERT_EQ(0xfffcU, exidx_->cfa());
+  ASSERT_TRUE(exidx_->pc_set());
+  ASSERT_EQ(0x10U, (*exidx_->regs())[15]);
+}
+
 TEST_P(ArmExidxDecodeTest, eval_pc_set) {
   // vsp = vsp + 4
   data_->push_back(0x00);
-  // vsp = vsp + 8
+  // vsp = vsp + 12
   data_->push_back(0x02);
   // Pop {r15}
   data_->push_back(0x88);
   data_->push_back(0x00);
-  // vsp = vsp + 8
+  // vsp = vsp + 12
   data_->push_back(0x02);
   // Finish
   data_->push_back(0xb0);
@@ -1080,20 +1636,33 @@
   process_memory_.SetData32(0x10010, 0x10);
 
   ASSERT_TRUE(exidx_->Eval());
-  if (log_) {
-    ASSERT_EQ("4 unwind vsp = vsp + 4\n"
-              "4 unwind vsp = vsp + 12\n"
-              "4 unwind pop {r15}\n"
-              "4 unwind vsp = vsp + 12\n"
-              "4 unwind finish\n", GetFakeLogPrint());
-  } else {
-    ASSERT_EQ("", GetFakeLogPrint());
+  switch (log_) {
+    case ARM_LOG_NONE:
+      ASSERT_EQ("", GetFakeLogPrint());
+      break;
+    case ARM_LOG_FULL:
+      ASSERT_EQ(
+          "4 unwind vsp = vsp + 4\n"
+          "4 unwind vsp = vsp + 12\n"
+          "4 unwind pop {r15}\n"
+          "4 unwind vsp = vsp + 12\n"
+          "4 unwind finish\n",
+          GetFakeLogPrint());
+      break;
+    case ARM_LOG_BY_REG:
+      exidx_->LogByReg();
+      ASSERT_EQ(
+          "4 unwind cfa = r13 + 32\n"
+          "4 unwind r15 = [cfa - 16]\n",
+          GetFakeLogPrint());
+      break;
   }
   ASSERT_EQ(0x10020U, exidx_->cfa());
   ASSERT_TRUE(exidx_->pc_set());
   ASSERT_EQ(0x10U, (*exidx_->regs())[15]);
 }
 
-INSTANTIATE_TEST_CASE_P(, ArmExidxDecodeTest, ::testing::Values("logging", "no_logging"));
+INSTANTIATE_TEST_CASE_P(, ArmExidxDecodeTest,
+                        ::testing::Values("logging", "register_logging", "no_logging"));
 
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/ArmExidxExtractTest.cpp b/libunwindstack/tests/ArmExidxExtractTest.cpp
index 8d0f0e5..79c799c 100644
--- a/libunwindstack/tests/ArmExidxExtractTest.cpp
+++ b/libunwindstack/tests/ArmExidxExtractTest.cpp
@@ -301,7 +301,7 @@
   elf_memory_.SetData32(0x1000, 0x7fff2340);
   elf_memory_.SetData32(0x1004, 1);
 
-  exidx_->set_log(true);
+  exidx_->set_log(ARM_LOG_FULL);
   exidx_->set_log_indent(0);
   exidx_->set_log_skip_execution(false);
 
@@ -316,7 +316,7 @@
   elf_memory_.SetData32(0x4000, 0x7ffa3000);
   elf_memory_.SetData32(0x4004, 0x80a8b0b0);
 
-  exidx_->set_log(true);
+  exidx_->set_log(ARM_LOG_FULL);
   exidx_->set_log_indent(0);
   exidx_->set_log_skip_execution(false);
 
@@ -330,7 +330,7 @@
   elf_memory_.SetData32(0x6234, 0x2);
   elf_memory_.SetData32(0x6238, 0x00112233);
 
-  exidx_->set_log(true);
+  exidx_->set_log(ARM_LOG_FULL);
   exidx_->set_log_indent(0);
   exidx_->set_log_skip_execution(false);
 
diff --git a/libunwindstack/tests/DwarfCfaLogTest.cpp b/libunwindstack/tests/DwarfCfaLogTest.cpp
index b17ca33..bb2e8f0 100644
--- a/libunwindstack/tests/DwarfCfaLogTest.cpp
+++ b/libunwindstack/tests/DwarfCfaLogTest.cpp
@@ -79,7 +79,7 @@
     this->memory_.SetMemory(0x2000, std::vector<uint8_t>{i});
 
     ResetLogs();
-    ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2000, 0x2001));
+    ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2000, 0x2001));
     std::string expected = "4 unwind Illegal\n";
     expected += android::base::StringPrintf("4 unwind Raw Data: 0x%02x\n", i);
     ASSERT_EQ(expected, GetFakeLogPrint());
@@ -90,7 +90,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_nop) {
   this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x00});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2000, 0x2001));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2000, 0x2001));
   std::string expected =
       "4 unwind DW_CFA_nop\n"
       "4 unwind Raw Data: 0x00\n";
@@ -101,7 +101,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_offset) {
   this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x83, 0x04});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2000, 0x2002));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2000, 0x2002));
   std::string expected =
       "4 unwind DW_CFA_offset register(3) 4\n"
       "4 unwind Raw Data: 0x83 0x04\n";
@@ -111,7 +111,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x2100, std::vector<uint8_t>{0x83, 0x84, 0x01});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2100, 0x2103));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2100, 0x2103));
   expected =
       "4 unwind DW_CFA_offset register(3) 132\n"
       "4 unwind Raw Data: 0x83 0x84 0x01\n";
@@ -122,7 +122,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_offset_extended) {
   this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x05, 0x03, 0x02});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x500, 0x503));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x500, 0x503));
   std::string expected =
       "4 unwind DW_CFA_offset_extended register(3) 2\n"
       "4 unwind Raw Data: 0x05 0x03 0x02\n";
@@ -132,7 +132,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x1500, std::vector<uint8_t>{0x05, 0x81, 0x01, 0x82, 0x12});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x1500, 0x1505));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x1500, 0x1505));
   expected =
       "4 unwind DW_CFA_offset_extended register(129) 2306\n"
       "4 unwind Raw Data: 0x05 0x81 0x01 0x82 0x12\n";
@@ -143,7 +143,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_offset_extended_sf) {
   this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x11, 0x05, 0x10});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x500, 0x503));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x500, 0x503));
   std::string expected =
       "4 unwind DW_CFA_offset_extended_sf register(5) 16\n"
       "4 unwind Raw Data: 0x11 0x05 0x10\n";
@@ -154,7 +154,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x1500, std::vector<uint8_t>{0x11, 0x86, 0x01, 0xff, 0x7f});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x1500, 0x1505));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x1500, 0x1505));
   expected =
       "4 unwind DW_CFA_offset_extended_sf register(134) -1\n"
       "4 unwind Raw Data: 0x11 0x86 0x01 0xff 0x7f\n";
@@ -165,7 +165,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_restore) {
   this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0xc2});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2000, 0x2001));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2000, 0x2001));
   std::string expected =
       "4 unwind DW_CFA_restore register(2)\n"
       "4 unwind Raw Data: 0xc2\n";
@@ -175,7 +175,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x3000, std::vector<uint8_t>{0x82, 0x04, 0xc2});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x3000, 0x3003));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x3000, 0x3003));
   expected =
       "4 unwind DW_CFA_offset register(2) 4\n"
       "4 unwind Raw Data: 0x82 0x04\n"
@@ -188,7 +188,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_restore_extended) {
   this->memory_.SetMemory(0x4000, std::vector<uint8_t>{0x06, 0x08});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x4000, 0x4002));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x4000, 0x4002));
   std::string expected =
       "4 unwind DW_CFA_restore_extended register(8)\n"
       "4 unwind Raw Data: 0x06 0x08\n";
@@ -198,7 +198,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x5000, std::vector<uint8_t>{0x05, 0x82, 0x02, 0x04, 0x06, 0x82, 0x02});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x5000, 0x5007));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x5000, 0x5007));
   expected =
       "4 unwind DW_CFA_offset_extended register(258) 4\n"
       "4 unwind Raw Data: 0x05 0x82 0x02 0x04\n"
@@ -228,7 +228,7 @@
   this->memory_.SetMemory(0x50, buffer, sizeof(buffer));
   ResetLogs();
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x50, 0x51 + sizeof(TypeParam)));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x50, 0x51 + sizeof(TypeParam)));
   std::string expected = "4 unwind DW_CFA_set_loc " + address_str + "\n";
   expected += "4 unwind " + raw_data + "\n";
   expected += "4 unwind \n";
@@ -240,7 +240,7 @@
   ResetLogs();
   this->fde_.pc_start = address + 0x10;
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x50, 0x51 + sizeof(TypeParam)));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x50, 0x51 + sizeof(TypeParam)));
   expected = "4 unwind DW_CFA_set_loc " + address_str + "\n";
   expected += "4 unwind " + raw_data + "\n";
   expected += "4 unwind \n";
@@ -252,7 +252,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_advance_loc) {
   this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x44});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x201));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x200, 0x201));
   std::string expected =
       "4 unwind DW_CFA_advance_loc 4\n"
       "4 unwind Raw Data: 0x44\n"
@@ -260,22 +260,12 @@
       "4 unwind PC 0x2010\n";
   ASSERT_EQ(expected, GetFakeLogPrint());
   ASSERT_EQ("", GetFakeLogBuf());
-
-  ResetLogs();
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x200, 0x201));
-  expected =
-      "4 unwind DW_CFA_advance_loc 4\n"
-      "4 unwind Raw Data: 0x44\n"
-      "4 unwind \n"
-      "4 unwind PC 0x2110\n";
-  ASSERT_EQ(expected, GetFakeLogPrint());
-  ASSERT_EQ("", GetFakeLogBuf());
 }
 
 TYPED_TEST_P(DwarfCfaLogTest, cfa_advance_loc1) {
   this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x02, 0x04});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x202));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x200, 0x202));
   std::string expected =
       "4 unwind DW_CFA_advance_loc1 4\n"
       "4 unwind Raw Data: 0x02 0x04\n"
@@ -283,22 +273,12 @@
       "4 unwind PC 0x2004\n";
   ASSERT_EQ(expected, GetFakeLogPrint());
   ASSERT_EQ("", GetFakeLogBuf());
-
-  ResetLogs();
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x10, 0x200, 0x202));
-  expected =
-      "4 unwind DW_CFA_advance_loc1 4\n"
-      "4 unwind Raw Data: 0x02 0x04\n"
-      "4 unwind \n"
-      "4 unwind PC 0x2014\n";
-  ASSERT_EQ(expected, GetFakeLogPrint());
-  ASSERT_EQ("", GetFakeLogBuf());
 }
 
 TYPED_TEST_P(DwarfCfaLogTest, cfa_advance_loc2) {
   this->memory_.SetMemory(0x600, std::vector<uint8_t>{0x03, 0x04, 0x03});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x600, 0x603));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x600, 0x603));
   std::string expected =
       "4 unwind DW_CFA_advance_loc2 772\n"
       "4 unwind Raw Data: 0x03 0x04 0x03\n"
@@ -306,22 +286,12 @@
       "4 unwind PC 0x2304\n";
   ASSERT_EQ(expected, GetFakeLogPrint());
   ASSERT_EQ("", GetFakeLogBuf());
-
-  ResetLogs();
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x1000, 0x600, 0x603));
-  expected =
-      "4 unwind DW_CFA_advance_loc2 772\n"
-      "4 unwind Raw Data: 0x03 0x04 0x03\n"
-      "4 unwind \n"
-      "4 unwind PC 0x3304\n";
-  ASSERT_EQ(expected, GetFakeLogPrint());
-  ASSERT_EQ("", GetFakeLogBuf());
 }
 
 TYPED_TEST_P(DwarfCfaLogTest, cfa_advance_loc4) {
   this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x04, 0x04, 0x03, 0x02, 0x01});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x500, 0x505));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x500, 0x505));
   std::string expected =
       "4 unwind DW_CFA_advance_loc4 16909060\n"
       "4 unwind Raw Data: 0x04 0x04 0x03 0x02 0x01\n"
@@ -329,22 +299,12 @@
       "4 unwind PC 0x1022304\n";
   ASSERT_EQ(expected, GetFakeLogPrint());
   ASSERT_EQ("", GetFakeLogBuf());
-
-  ResetLogs();
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2000, 0x500, 0x505));
-  expected =
-      "4 unwind DW_CFA_advance_loc4 16909060\n"
-      "4 unwind Raw Data: 0x04 0x04 0x03 0x02 0x01\n"
-      "4 unwind \n"
-      "4 unwind PC 0x1024304\n";
-  ASSERT_EQ(expected, GetFakeLogPrint());
-  ASSERT_EQ("", GetFakeLogBuf());
 }
 
 TYPED_TEST_P(DwarfCfaLogTest, cfa_undefined) {
   this->memory_.SetMemory(0xa00, std::vector<uint8_t>{0x07, 0x09});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0xa00, 0xa02));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0xa00, 0xa02));
   std::string expected =
       "4 unwind DW_CFA_undefined register(9)\n"
       "4 unwind Raw Data: 0x07 0x09\n";
@@ -355,7 +315,7 @@
   dwarf_loc_regs_t cie_loc_regs;
   this->memory_.SetMemory(0x1a00, std::vector<uint8_t>{0x07, 0x81, 0x01});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x1a00, 0x1a03));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x1a00, 0x1a03));
   expected =
       "4 unwind DW_CFA_undefined register(129)\n"
       "4 unwind Raw Data: 0x07 0x81 0x01\n";
@@ -366,7 +326,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_same) {
   this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x08, 0x7f});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x102));
   std::string expected =
       "4 unwind DW_CFA_same_value register(127)\n"
       "4 unwind Raw Data: 0x08 0x7f\n";
@@ -376,7 +336,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x2100, std::vector<uint8_t>{0x08, 0xff, 0x01});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2100, 0x2103));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2100, 0x2103));
   expected =
       "4 unwind DW_CFA_same_value register(255)\n"
       "4 unwind Raw Data: 0x08 0xff 0x01\n";
@@ -387,7 +347,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_register) {
   this->memory_.SetMemory(0x300, std::vector<uint8_t>{0x09, 0x02, 0x01});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x300, 0x303));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x300, 0x303));
   std::string expected =
       "4 unwind DW_CFA_register register(2) register(1)\n"
       "4 unwind Raw Data: 0x09 0x02 0x01\n";
@@ -397,7 +357,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x4300, std::vector<uint8_t>{0x09, 0xff, 0x01, 0xff, 0x03});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x4300, 0x4305));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x4300, 0x4305));
   expected =
       "4 unwind DW_CFA_register register(255) register(511)\n"
       "4 unwind Raw Data: 0x09 0xff 0x01 0xff 0x03\n";
@@ -408,7 +368,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_state) {
   this->memory_.SetMemory(0x300, std::vector<uint8_t>{0x0a});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x300, 0x301));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x300, 0x301));
 
   std::string expected =
       "4 unwind DW_CFA_remember_state\n"
@@ -419,7 +379,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x4300, std::vector<uint8_t>{0x0b});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x4300, 0x4301));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x4300, 0x4301));
 
   expected =
       "4 unwind DW_CFA_restore_state\n"
@@ -431,7 +391,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_state_cfa_offset_restore) {
   this->memory_.SetMemory(0x3000, std::vector<uint8_t>{0x0a, 0x0e, 0x40, 0x0b});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x3000, 0x3004));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x3000, 0x3004));
 
   std::string expected =
       "4 unwind DW_CFA_remember_state\n"
@@ -447,7 +407,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa) {
   this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0c, 0x7f, 0x74});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x103));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x103));
 
   std::string expected =
       "4 unwind DW_CFA_def_cfa register(127) 116\n"
@@ -458,7 +418,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x0c, 0xff, 0x02, 0xf4, 0x04});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x205));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x200, 0x205));
 
   expected =
       "4 unwind DW_CFA_def_cfa register(383) 628\n"
@@ -470,7 +430,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa_sf) {
   this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x12, 0x30, 0x25});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x103));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x103));
 
   std::string expected =
       "4 unwind DW_CFA_def_cfa_sf register(48) 37\n"
@@ -482,7 +442,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x12, 0xa3, 0x01, 0xfa, 0x7f});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x205));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x200, 0x205));
 
   expected =
       "4 unwind DW_CFA_def_cfa_sf register(163) -6\n"
@@ -494,7 +454,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa_register) {
   this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0d, 0x72});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x102));
 
   std::string expected =
       "4 unwind DW_CFA_def_cfa_register register(114)\n"
@@ -505,7 +465,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x0d, 0xf9, 0x20});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x203));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x200, 0x203));
 
   expected =
       "4 unwind DW_CFA_def_cfa_register register(4217)\n"
@@ -517,7 +477,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa_offset) {
   this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0e, 0x59});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x102));
 
   std::string expected =
       "4 unwind DW_CFA_def_cfa_offset 89\n"
@@ -526,7 +486,7 @@
   ASSERT_EQ("", GetFakeLogBuf());
 
   ResetLogs();
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x102));
 
   expected =
       "4 unwind DW_CFA_def_cfa_offset 89\n"
@@ -537,7 +497,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x0e, 0xd4, 0x0a});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x203));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x200, 0x203));
 
   expected =
       "4 unwind DW_CFA_def_cfa_offset 1364\n"
@@ -549,7 +509,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa_offset_sf) {
   this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x13, 0x23});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x102));
 
   std::string expected =
       "4 unwind DW_CFA_def_cfa_offset_sf 35\n"
@@ -558,7 +518,7 @@
   ASSERT_EQ("", GetFakeLogBuf());
 
   ResetLogs();
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x102));
 
   expected =
       "4 unwind DW_CFA_def_cfa_offset_sf 35\n"
@@ -570,7 +530,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x13, 0xf6, 0x7f});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x203));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x200, 0x203));
 
   expected =
       "4 unwind DW_CFA_def_cfa_offset_sf -10\n"
@@ -582,7 +542,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa_expression) {
   this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0f, 0x04, 0x01, 0x02, 0x04, 0x05});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x106));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x106));
 
   std::string expected =
       "4 unwind DW_CFA_def_cfa_expression 4\n"
@@ -614,7 +574,7 @@
   }
   expected += '\n';
   this->memory_.SetMemory(0x200, ops);
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x284));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x200, 0x284));
 
   expected = "4 unwind DW_CFA_def_cfa_expression 129\n" + expected;
   ASSERT_EQ(expected + op_string, GetFakeLogPrint());
@@ -624,7 +584,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_expression) {
   this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x10, 0x04, 0x02, 0xc0, 0xc1});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x105));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x105));
 
   std::string expected =
       "4 unwind DW_CFA_expression register(4) 2\n"
@@ -652,7 +612,7 @@
   expected = "4 unwind DW_CFA_expression register(255) 130\n" + expected + "\n";
 
   this->memory_.SetMemory(0x200, ops);
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x287));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x200, 0x287));
 
   ASSERT_EQ(expected + op_string, GetFakeLogPrint());
   ASSERT_EQ("", GetFakeLogBuf());
@@ -661,7 +621,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_val_offset) {
   this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x14, 0x45, 0x54});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x103));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x103));
 
   std::string expected =
       "4 unwind DW_CFA_val_offset register(69) 84\n"
@@ -672,7 +632,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x400, std::vector<uint8_t>{0x14, 0xa2, 0x02, 0xb4, 0x05});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x400, 0x405));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x400, 0x405));
 
   expected =
       "4 unwind DW_CFA_val_offset register(290) 692\n"
@@ -684,7 +644,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_val_offset_sf) {
   this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x15, 0x56, 0x12});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x103));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x103));
 
   std::string expected =
       "4 unwind DW_CFA_val_offset_sf register(86) 18\n"
@@ -696,7 +656,7 @@
   ResetLogs();
   this->memory_.SetMemory(0xa00, std::vector<uint8_t>{0x15, 0xff, 0x01, 0xc0, 0x7f});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0xa00, 0xa05));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0xa00, 0xa05));
 
   expected =
       "4 unwind DW_CFA_val_offset_sf register(255) -64\n"
@@ -708,7 +668,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_val_expression) {
   this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x16, 0x05, 0x02, 0xb0, 0xb1});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x105));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x105));
 
   std::string expected =
       "4 unwind DW_CFA_val_expression register(5) 2\n"
@@ -737,7 +697,7 @@
 
   this->memory_.SetMemory(0xa00, ops);
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0xa00, 0xaad));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0xa00, 0xaad));
 
   ASSERT_EQ(expected + op_string, GetFakeLogPrint());
   ASSERT_EQ("", GetFakeLogBuf());
@@ -746,7 +706,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_gnu_args_size) {
   this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x2e, 0x04});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2000, 0x2002));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2000, 0x2002));
 
   std::string expected =
       "4 unwind DW_CFA_GNU_args_size 4\n"
@@ -757,7 +717,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x5000, std::vector<uint8_t>{0x2e, 0xa4, 0x80, 0x04});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x5000, 0x5004));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x5000, 0x5004));
 
   expected =
       "4 unwind DW_CFA_GNU_args_size 65572\n"
@@ -769,7 +729,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_gnu_negative_offset_extended) {
   this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x2f, 0x08, 0x10});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x500, 0x503));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x500, 0x503));
 
   std::string expected =
       "4 unwind DW_CFA_GNU_negative_offset_extended register(8) 16\n"
@@ -780,7 +740,7 @@
   ResetLogs();
   this->memory_.SetMemory(0x1500, std::vector<uint8_t>{0x2f, 0x81, 0x02, 0xff, 0x01});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x1500, 0x1505));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x1500, 0x1505));
 
   expected =
       "4 unwind DW_CFA_GNU_negative_offset_extended register(257) 255\n"
@@ -792,7 +752,7 @@
 TYPED_TEST_P(DwarfCfaLogTest, cfa_register_override) {
   this->memory_.SetMemory(0x300, std::vector<uint8_t>{0x09, 0x02, 0x01, 0x09, 0x02, 0x04});
 
-  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x300, 0x306));
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x300, 0x306));
 
   std::string expected =
       "4 unwind DW_CFA_register register(2) register(1)\n"
diff --git a/libunwindstack/tests/DwarfDebugFrameTest.cpp b/libunwindstack/tests/DwarfDebugFrameTest.cpp
index c28a41e..3a52044 100644
--- a/libunwindstack/tests/DwarfDebugFrameTest.cpp
+++ b/libunwindstack/tests/DwarfDebugFrameTest.cpp
@@ -103,7 +103,7 @@
   this->memory_.SetData32(0x5508, 0x4500);
   this->memory_.SetData32(0x550c, 0x500);
 
-  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600));
+  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600, 0));
   ASSERT_EQ(4U, this->debug_frame_->TestGetFdeCount());
 
   typename DwarfDebugFrame<TypeParam>::FdeInfo info(0, 0, 0);
@@ -142,7 +142,7 @@
   this->memory_.SetData32(0x5108, 0x1500);
   this->memory_.SetData32(0x510c, 0x200);
 
-  ASSERT_FALSE(this->debug_frame_->Init(0x5000, 0x600));
+  ASSERT_FALSE(this->debug_frame_->Init(0x5000, 0x600, 0));
   ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->debug_frame_->LastErrorCode());
 }
 
@@ -181,7 +181,7 @@
   this->memory_.SetData32(0x5508, 0x4500);
   this->memory_.SetData32(0x550c, 0x500);
 
-  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600));
+  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600, 0));
   ASSERT_EQ(2U, this->debug_frame_->TestGetFdeCount());
 }
 
@@ -226,7 +226,7 @@
   this->memory_.SetData64(0x5514, 0x4500);
   this->memory_.SetData64(0x551c, 0x500);
 
-  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600));
+  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600, 0));
   ASSERT_EQ(4U, this->debug_frame_->TestGetFdeCount());
 
   typename DwarfDebugFrame<TypeParam>::FdeInfo info(0, 0, 0);
@@ -267,7 +267,7 @@
   this->memory_.SetData64(0x5114, 0x1500);
   this->memory_.SetData64(0x511c, 0x200);
 
-  ASSERT_FALSE(this->debug_frame_->Init(0x5000, 0x600));
+  ASSERT_FALSE(this->debug_frame_->Init(0x5000, 0x600, 0));
   ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->debug_frame_->LastErrorCode());
 }
 
@@ -312,10 +312,48 @@
   this->memory_.SetData64(0x5514, 0x4500);
   this->memory_.SetData64(0x551c, 0x500);
 
-  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600));
+  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600, 0));
   ASSERT_EQ(2U, this->debug_frame_->TestGetFdeCount());
 }
 
+TYPED_TEST_P(DwarfDebugFrameTest, Init_non_zero_load_bias) {
+  // CIE 32 information.
+  this->memory_.SetData32(0x5000, 0xfc);
+  this->memory_.SetData32(0x5004, 0xffffffff);
+  this->memory_.SetData8(0x5008, 1);
+  this->memory_.SetData8(0x5009, 'z');
+  this->memory_.SetData8(0x500a, 'R');
+  this->memory_.SetData8(0x500b, '\0');
+  this->memory_.SetData8(0x500c, 0);
+  this->memory_.SetData8(0x500d, 0);
+  this->memory_.SetData8(0x500e, 0);
+  this->memory_.SetData8(0x500f, 0);
+  this->memory_.SetData8(0x5010, 0x1b);
+
+  // FDE 32 information.
+  this->memory_.SetData32(0x5100, 0xfc);
+  this->memory_.SetData32(0x5104, 0);
+  this->memory_.SetData32(0x5108, 0x1500);
+  this->memory_.SetData32(0x510c, 0x200);
+  this->memory_.SetData8(0x5110, 0);
+  this->memory_.SetData8(0x5111, 0);
+
+  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x200, 0x1000));
+  ASSERT_EQ(1U, this->debug_frame_->TestGetFdeCount());
+
+  typename DwarfDebugFrame<TypeParam>::FdeInfo info(0, 0, 0);
+
+  this->debug_frame_->TestGetFdeInfo(0, &info);
+  EXPECT_EQ(0x5100U, info.offset);
+  EXPECT_EQ(0x2500U, info.start);
+  EXPECT_EQ(0x2700U, info.end);
+
+  const DwarfFde* fde = this->debug_frame_->GetFdeFromPc(0x2504);
+  ASSERT_TRUE(fde != nullptr);
+  EXPECT_EQ(0x2500U, fde->pc_start);
+  EXPECT_EQ(0x2700U, fde->pc_end);
+}
+
 TYPED_TEST_P(DwarfDebugFrameTest, Init_version1) {
   // CIE 32 information.
   this->memory_.SetData32(0x5000, 0xfc);
@@ -340,7 +378,7 @@
   this->memory_.SetData16(0x5108, 0x1500);
   this->memory_.SetData16(0x510a, 0x200);
 
-  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x200));
+  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x200, 0));
   ASSERT_EQ(1U, this->debug_frame_->TestGetFdeCount());
 
   typename DwarfDebugFrame<TypeParam>::FdeInfo info(0, 0, 0);
@@ -383,7 +421,7 @@
   this->memory_.SetData16(0x5108, 0x1500);
   this->memory_.SetData16(0x510a, 0x200);
 
-  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x200));
+  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x200, 0));
   ASSERT_EQ(1U, this->debug_frame_->TestGetFdeCount());
 
   typename DwarfDebugFrame<TypeParam>::FdeInfo info(0, 0, 0);
@@ -538,8 +576,8 @@
 REGISTER_TYPED_TEST_CASE_P(DwarfDebugFrameTest, Init32, Init32_fde_not_following_cie,
                            Init32_do_not_fail_on_bad_next_entry, Init64,
                            Init64_do_not_fail_on_bad_next_entry, Init64_fde_not_following_cie,
-                           Init_version1, Init_version4, GetFdeOffsetFromPc, GetCieFde32,
-                           GetCieFde64);
+                           Init_non_zero_load_bias, Init_version1, Init_version4,
+                           GetFdeOffsetFromPc, GetCieFde32, GetCieFde64);
 
 typedef ::testing::Types<uint32_t, uint64_t> DwarfDebugFrameTestTypes;
 INSTANTIATE_TYPED_TEST_CASE_P(, DwarfDebugFrameTest, DwarfDebugFrameTestTypes);
diff --git a/libunwindstack/tests/DwarfEhFrameTest.cpp b/libunwindstack/tests/DwarfEhFrameTest.cpp
index a73db65..e8d53e6 100644
--- a/libunwindstack/tests/DwarfEhFrameTest.cpp
+++ b/libunwindstack/tests/DwarfEhFrameTest.cpp
@@ -103,7 +103,7 @@
   this->memory_.SetData32(0x5508, 0x4500);
   this->memory_.SetData32(0x550c, 0x500);
 
-  ASSERT_TRUE(this->eh_frame_->Init(0x5000, 0x600));
+  ASSERT_TRUE(this->eh_frame_->Init(0x5000, 0x600, 0));
   ASSERT_EQ(4U, this->eh_frame_->TestGetFdeCount());
 
   typename DwarfEhFrame<TypeParam>::FdeInfo info(0, 0, 0);
@@ -142,7 +142,7 @@
   this->memory_.SetData32(0x5108, 0x1500);
   this->memory_.SetData32(0x510c, 0x200);
 
-  ASSERT_FALSE(this->eh_frame_->Init(0x5000, 0x600));
+  ASSERT_FALSE(this->eh_frame_->Init(0x5000, 0x600, 0));
   ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->eh_frame_->LastErrorCode());
 }
 
@@ -187,7 +187,7 @@
   this->memory_.SetData64(0x5514, 0x4500);
   this->memory_.SetData64(0x551c, 0x500);
 
-  ASSERT_TRUE(this->eh_frame_->Init(0x5000, 0x600));
+  ASSERT_TRUE(this->eh_frame_->Init(0x5000, 0x600, 0));
   ASSERT_EQ(4U, this->eh_frame_->TestGetFdeCount());
 
   typename DwarfEhFrame<TypeParam>::FdeInfo info(0, 0, 0);
@@ -228,10 +228,48 @@
   this->memory_.SetData64(0x5114, 0x1500);
   this->memory_.SetData64(0x511c, 0x200);
 
-  ASSERT_FALSE(this->eh_frame_->Init(0x5000, 0x600));
+  ASSERT_FALSE(this->eh_frame_->Init(0x5000, 0x600, 0));
   ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->eh_frame_->LastErrorCode());
 }
 
+TYPED_TEST_P(DwarfEhFrameTest, Init_non_zero_load_bias) {
+  // CIE 32 information.
+  this->memory_.SetData32(0x5000, 0xfc);
+  this->memory_.SetData32(0x5004, 0);
+  this->memory_.SetData8(0x5008, 1);
+  this->memory_.SetData8(0x5009, 'z');
+  this->memory_.SetData8(0x500a, 'R');
+  this->memory_.SetData8(0x500b, '\0');
+  this->memory_.SetData8(0x500c, 0);
+  this->memory_.SetData8(0x500d, 0);
+  this->memory_.SetData8(0x500e, 0);
+  this->memory_.SetData8(0x500f, 0);
+  this->memory_.SetData8(0x5010, 0x1b);
+
+  // FDE 32 information.
+  this->memory_.SetData32(0x5100, 0xfc);
+  this->memory_.SetData32(0x5104, 0x104);
+  this->memory_.SetData32(0x5108, 0x1500);
+  this->memory_.SetData32(0x510c, 0x200);
+  this->memory_.SetData8(0x5110, 0);
+  this->memory_.SetData8(0x5111, 0);
+
+  ASSERT_TRUE(this->eh_frame_->Init(0x5000, 0x200, 0x2000));
+  ASSERT_EQ(1U, this->eh_frame_->TestGetFdeCount());
+
+  typename DwarfEhFrame<TypeParam>::FdeInfo info(0, 0, 0);
+
+  this->eh_frame_->TestGetFdeInfo(0, &info);
+  EXPECT_EQ(0x5100U, info.offset);
+  EXPECT_EQ(0x8608U, info.start);
+  EXPECT_EQ(0x8808U, info.end);
+
+  const DwarfFde* fde = this->eh_frame_->GetFdeFromPc(0x8700);
+  ASSERT_TRUE(fde != nullptr);
+  EXPECT_EQ(0x8608U, fde->pc_start);
+  EXPECT_EQ(0x8808U, fde->pc_end);
+}
+
 TYPED_TEST_P(DwarfEhFrameTest, Init_version1) {
   // CIE 32 information.
   this->memory_.SetData32(0x5000, 0xfc);
@@ -256,7 +294,7 @@
   this->memory_.SetData16(0x5108, 0x1500);
   this->memory_.SetData16(0x510a, 0x200);
 
-  ASSERT_TRUE(this->eh_frame_->Init(0x5000, 0x200));
+  ASSERT_TRUE(this->eh_frame_->Init(0x5000, 0x200, 0));
   ASSERT_EQ(1U, this->eh_frame_->TestGetFdeCount());
 
   typename DwarfEhFrame<TypeParam>::FdeInfo info(0, 0, 0);
@@ -299,7 +337,7 @@
   this->memory_.SetData16(0x5108, 0x1500);
   this->memory_.SetData16(0x510a, 0x200);
 
-  ASSERT_TRUE(this->eh_frame_->Init(0x5000, 0x200));
+  ASSERT_TRUE(this->eh_frame_->Init(0x5000, 0x200, 0));
   ASSERT_EQ(1U, this->eh_frame_->TestGetFdeCount());
 
   typename DwarfEhFrame<TypeParam>::FdeInfo info(0, 0, 0);
@@ -450,8 +488,8 @@
 }
 
 REGISTER_TYPED_TEST_CASE_P(DwarfEhFrameTest, Init32, Init32_fde_not_following_cie, Init64,
-                           Init64_fde_not_following_cie, Init_version1, Init_version4,
-                           GetFdeOffsetFromPc, GetCieFde32, GetCieFde64);
+                           Init64_fde_not_following_cie, Init_non_zero_load_bias, Init_version1,
+                           Init_version4, GetFdeOffsetFromPc, GetCieFde32, GetCieFde64);
 
 typedef ::testing::Types<uint32_t, uint64_t> DwarfEhFrameTestTypes;
 INSTANTIATE_TYPED_TEST_CASE_P(, DwarfEhFrameTest, DwarfEhFrameTestTypes);
diff --git a/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp b/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
index 4240419..19c7b98 100644
--- a/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
+++ b/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
@@ -83,7 +83,7 @@
   this->memory_.SetData16(0x1004, 0x500);
   this->memory_.SetData32(0x1006, 126);
 
-  ASSERT_TRUE(this->eh_frame_->Init(0x1000, 0x100));
+  ASSERT_TRUE(this->eh_frame_->Init(0x1000, 0x100, 0));
   EXPECT_EQ(1U, this->eh_frame_->TestGetVersion());
   EXPECT_EQ(DW_EH_PE_udata2, this->eh_frame_->TestGetPtrEncoding());
   EXPECT_EQ(DW_EH_PE_sdata4, this->eh_frame_->TestGetTableEncoding());
@@ -97,19 +97,66 @@
 
   // Verify a zero fde count fails to init.
   this->memory_.SetData32(0x1006, 0);
-  ASSERT_FALSE(this->eh_frame_->Init(0x1000, 0x100));
+  ASSERT_FALSE(this->eh_frame_->Init(0x1000, 0x100, 0));
   ASSERT_EQ(DWARF_ERROR_NO_FDES, this->eh_frame_->LastErrorCode());
 
   // Verify an unexpected version will cause a fail.
   this->memory_.SetData32(0x1006, 126);
   this->memory_.SetData8(0x1000, 0);
-  ASSERT_FALSE(this->eh_frame_->Init(0x1000, 0x100));
+  ASSERT_FALSE(this->eh_frame_->Init(0x1000, 0x100, 0));
   ASSERT_EQ(DWARF_ERROR_UNSUPPORTED_VERSION, this->eh_frame_->LastErrorCode());
   this->memory_.SetData8(0x1000, 2);
-  ASSERT_FALSE(this->eh_frame_->Init(0x1000, 0x100));
+  ASSERT_FALSE(this->eh_frame_->Init(0x1000, 0x100, 0));
   ASSERT_EQ(DWARF_ERROR_UNSUPPORTED_VERSION, this->eh_frame_->LastErrorCode());
 }
 
+TYPED_TEST_P(DwarfEhFrameWithHdrTest, Init_non_zero_load_bias) {
+  this->memory_.SetMemory(0x1000, std::vector<uint8_t>{0x1, DW_EH_PE_udata2, DW_EH_PE_udata4,
+                                                       DW_EH_PE_pcrel | DW_EH_PE_sdata4});
+  this->memory_.SetData16(0x1004, 0x500);
+  this->memory_.SetData32(0x1006, 1);
+  this->memory_.SetData32(0x100a, 0x2500);
+  this->memory_.SetData32(0x100e, 0x1400);
+
+  // CIE 32 information.
+  this->memory_.SetData32(0x1300, 0xfc);
+  this->memory_.SetData32(0x1304, 0);
+  this->memory_.SetData8(0x1308, 1);
+  this->memory_.SetData8(0x1309, 'z');
+  this->memory_.SetData8(0x130a, 'R');
+  this->memory_.SetData8(0x130b, '\0');
+  this->memory_.SetData8(0x130c, 0);
+  this->memory_.SetData8(0x130d, 0);
+  this->memory_.SetData8(0x130e, 0);
+  this->memory_.SetData8(0x130f, 0);
+  this->memory_.SetData8(0x1310, 0x1b);
+
+  // FDE 32 information.
+  this->memory_.SetData32(0x1400, 0xfc);
+  this->memory_.SetData32(0x1404, 0x104);
+  this->memory_.SetData32(0x1408, 0x10f8);
+  this->memory_.SetData32(0x140c, 0x200);
+  this->memory_.SetData8(0x1410, 0);
+  this->memory_.SetData8(0x1411, 0);
+
+  ASSERT_TRUE(this->eh_frame_->Init(0x1000, 0x100, 0x2000));
+  EXPECT_EQ(1U, this->eh_frame_->TestGetVersion());
+  EXPECT_EQ(DW_EH_PE_udata2, this->eh_frame_->TestGetPtrEncoding());
+  EXPECT_EQ(0x1b, this->eh_frame_->TestGetTableEncoding());
+  EXPECT_EQ(4U, this->eh_frame_->TestGetTableEntrySize());
+  EXPECT_EQ(1U, this->eh_frame_->TestGetFdeCount());
+  EXPECT_EQ(0x500U, this->eh_frame_->TestGetPtrOffset());
+  EXPECT_EQ(0x100aU, this->eh_frame_->TestGetEntriesOffset());
+  EXPECT_EQ(0x1100U, this->eh_frame_->TestGetEntriesEnd());
+  EXPECT_EQ(0x1000U, this->eh_frame_->TestGetEntriesDataOffset());
+  EXPECT_EQ(0x100aU, this->eh_frame_->TestGetCurEntriesOffset());
+
+  const DwarfFde* fde = this->eh_frame_->GetFdeFromPc(0x4600);
+  ASSERT_TRUE(fde != nullptr);
+  EXPECT_EQ(0x4500U, fde->pc_start);
+  EXPECT_EQ(0x4700U, fde->pc_end);
+}
+
 TYPED_TEST_P(DwarfEhFrameWithHdrTest, GetFdeInfoFromIndex_expect_cache_fail) {
   this->eh_frame_->TestSetTableEntrySize(0x10);
   this->eh_frame_->TestSetTableEncoding(DW_EH_PE_udata4);
@@ -123,6 +170,7 @@
   EXPECT_EQ(0x1000U, this->eh_frame_->LastErrorAddress());
 }
 
+// We are assuming that pc rel, is really relative to the load_bias.
 TYPED_TEST_P(DwarfEhFrameWithHdrTest, GetFdeInfoFromIndex_read_pcrel) {
   this->eh_frame_->TestSetTableEncoding(DW_EH_PE_pcrel | DW_EH_PE_udata4);
   this->eh_frame_->TestSetEntriesOffset(0x1000);
@@ -134,8 +182,8 @@
 
   auto info = this->eh_frame_->GetFdeInfoFromIndex(2);
   ASSERT_TRUE(info != nullptr);
-  EXPECT_EQ(0x1380U, info->pc);
-  EXPECT_EQ(0x1540U, info->offset);
+  EXPECT_EQ(0x340U, info->pc);
+  EXPECT_EQ(0x500U, info->offset);
 }
 
 TYPED_TEST_P(DwarfEhFrameWithHdrTest, GetFdeInfoFromIndex_read_datarel) {
@@ -430,14 +478,14 @@
   ASSERT_EQ(nullptr, this->eh_frame_->GetFdeFromPc(0x800));
 }
 
-REGISTER_TYPED_TEST_CASE_P(DwarfEhFrameWithHdrTest, Init, GetFdeInfoFromIndex_expect_cache_fail,
-                           GetFdeInfoFromIndex_read_pcrel, GetFdeInfoFromIndex_read_datarel,
-                           GetFdeInfoFromIndex_cached, GetFdeOffsetBinary_verify,
-                           GetFdeOffsetBinary_index_fail, GetFdeOffsetSequential,
-                           GetFdeOffsetSequential_last_element, GetFdeOffsetSequential_end_check,
-                           GetFdeOffsetFromPc_fail_fde_count, GetFdeOffsetFromPc_binary_search,
-                           GetFdeOffsetFromPc_sequential_search, GetCieFde32, GetCieFde64,
-                           GetFdeFromPc_fde_not_found);
+REGISTER_TYPED_TEST_CASE_P(DwarfEhFrameWithHdrTest, Init, Init_non_zero_load_bias,
+                           GetFdeInfoFromIndex_expect_cache_fail, GetFdeInfoFromIndex_read_pcrel,
+                           GetFdeInfoFromIndex_read_datarel, GetFdeInfoFromIndex_cached,
+                           GetFdeOffsetBinary_verify, GetFdeOffsetBinary_index_fail,
+                           GetFdeOffsetSequential, GetFdeOffsetSequential_last_element,
+                           GetFdeOffsetSequential_end_check, GetFdeOffsetFromPc_fail_fde_count,
+                           GetFdeOffsetFromPc_binary_search, GetFdeOffsetFromPc_sequential_search,
+                           GetCieFde32, GetCieFde64, GetFdeFromPc_fde_not_found);
 
 typedef ::testing::Types<uint32_t, uint64_t> DwarfEhFrameWithHdrTestTypes;
 INSTANTIATE_TYPED_TEST_CASE_P(, DwarfEhFrameWithHdrTest, DwarfEhFrameWithHdrTestTypes);
diff --git a/libunwindstack/tests/DwarfSectionImplTest.cpp b/libunwindstack/tests/DwarfSectionImplTest.cpp
index c340291..414f2f2 100644
--- a/libunwindstack/tests/DwarfSectionImplTest.cpp
+++ b/libunwindstack/tests/DwarfSectionImplTest.cpp
@@ -36,7 +36,7 @@
   MockDwarfSectionImpl(Memory* memory) : DwarfSectionImpl<TypeParam>(memory) {}
   virtual ~MockDwarfSectionImpl() = default;
 
-  MOCK_METHOD2(Init, bool(uint64_t, uint64_t));
+  MOCK_METHOD3(Init, bool(uint64_t, uint64_t, uint64_t));
 
   MOCK_METHOD2(GetFdeOffsetFromPc, bool(uint64_t, uint64_t*));
 
@@ -884,7 +884,7 @@
 
   this->memory_.SetMemory(0x5000, std::vector<uint8_t>{0x00});
   this->memory_.SetMemory(0x6000, std::vector<uint8_t>{0xc2});
-  ASSERT_TRUE(this->section_->Log(2, 0x1000, 0x1000, &fde));
+  ASSERT_TRUE(this->section_->Log(2, 0x1000, &fde));
 
   ASSERT_EQ(
       "4 unwind     DW_CFA_nop\n"
diff --git a/libunwindstack/tests/DwarfSectionTest.cpp b/libunwindstack/tests/DwarfSectionTest.cpp
index 071d2df..2c6c879 100644
--- a/libunwindstack/tests/DwarfSectionTest.cpp
+++ b/libunwindstack/tests/DwarfSectionTest.cpp
@@ -30,13 +30,13 @@
   MockDwarfSection(Memory* memory) : DwarfSection(memory) {}
   virtual ~MockDwarfSection() = default;
 
-  MOCK_METHOD4(Log, bool(uint8_t, uint64_t, uint64_t, const DwarfFde*));
+  MOCK_METHOD3(Log, bool(uint8_t, uint64_t, const DwarfFde*));
 
   MOCK_METHOD5(Eval, bool(const DwarfCie*, Memory*, const dwarf_loc_regs_t&, Regs*, bool*));
 
   MOCK_METHOD3(GetCfaLocationInfo, bool(uint64_t, const DwarfFde*, dwarf_loc_regs_t*));
 
-  MOCK_METHOD2(Init, bool(uint64_t, uint64_t));
+  MOCK_METHOD3(Init, bool(uint64_t, uint64_t, uint64_t));
 
   MOCK_METHOD2(GetFdeOffsetFromPc, bool(uint64_t, uint64_t*));
 
diff --git a/libunwindstack/tests/ElfFake.cpp b/libunwindstack/tests/ElfFake.cpp
index 66207db..3d5ddd6 100644
--- a/libunwindstack/tests/ElfFake.cpp
+++ b/libunwindstack/tests/ElfFake.cpp
@@ -32,7 +32,7 @@
 std::deque<FunctionData> ElfInterfaceFake::functions_;
 std::deque<StepData> ElfInterfaceFake::steps_;
 
-bool ElfInterfaceFake::GetFunctionName(uint64_t, uint64_t, std::string* name, uint64_t* offset) {
+bool ElfInterfaceFake::GetFunctionName(uint64_t, std::string* name, uint64_t* offset) {
   if (functions_.empty()) {
     return false;
   }
@@ -52,7 +52,7 @@
   return true;
 }
 
-bool ElfInterfaceFake::Step(uint64_t, uint64_t, Regs* regs, Memory*, bool* finished) {
+bool ElfInterfaceFake::Step(uint64_t, Regs* regs, Memory*, bool* finished) {
   if (steps_.empty()) {
     return false;
   }
diff --git a/libunwindstack/tests/ElfFake.h b/libunwindstack/tests/ElfFake.h
index e232986..a3bf5ce 100644
--- a/libunwindstack/tests/ElfFake.h
+++ b/libunwindstack/tests/ElfFake.h
@@ -67,13 +67,13 @@
   virtual ~ElfInterfaceFake() = default;
 
   bool Init(uint64_t*) override { return false; }
-  void InitHeaders() override {}
+  void InitHeaders(uint64_t) override {}
   bool GetSoname(std::string*) override { return false; }
 
-  bool GetFunctionName(uint64_t, uint64_t, std::string*, uint64_t*) override;
+  bool GetFunctionName(uint64_t, std::string*, uint64_t*) override;
   bool GetGlobalVariable(const std::string&, uint64_t*) override;
 
-  bool Step(uint64_t, uint64_t, Regs*, Memory*, bool*) override;
+  bool Step(uint64_t, Regs*, Memory*, bool*) override;
 
   void FakeSetGlobalVariable(const std::string& global, uint64_t offset) {
     globals_[global] = offset;
diff --git a/libunwindstack/tests/ElfInterfaceArmTest.cpp b/libunwindstack/tests/ElfInterfaceArmTest.cpp
index 70a52ad..5f1c2ac 100644
--- a/libunwindstack/tests/ElfInterfaceArmTest.cpp
+++ b/libunwindstack/tests/ElfInterfaceArmTest.cpp
@@ -302,7 +302,7 @@
 
   // FindEntry fails.
   bool finished;
-  ASSERT_FALSE(interface.StepExidx(0x7000, 0, nullptr, nullptr, &finished));
+  ASSERT_FALSE(interface.StepExidx(0x7000, nullptr, nullptr, &finished));
   EXPECT_EQ(ERROR_UNWIND_INFO, interface.LastErrorCode());
 
   // ExtractEntry should fail.
@@ -316,18 +316,18 @@
   regs[ARM_REG_LR] = 0x20000;
   regs.set_sp(regs[ARM_REG_SP]);
   regs.set_pc(0x1234);
-  ASSERT_FALSE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
+  ASSERT_FALSE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
   EXPECT_EQ(ERROR_MEMORY_INVALID, interface.LastErrorCode());
   EXPECT_EQ(0x1004U, interface.LastErrorAddress());
 
   // Eval should fail.
   memory_.SetData32(0x1004, 0x81000000);
-  ASSERT_FALSE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
+  ASSERT_FALSE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
   EXPECT_EQ(ERROR_UNWIND_INFO, interface.LastErrorCode());
 
   // Everything should pass.
   memory_.SetData32(0x1004, 0x80b0b0b0);
-  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
   EXPECT_EQ(ERROR_UNWIND_INFO, interface.LastErrorCode());
   ASSERT_FALSE(finished);
   ASSERT_EQ(0x1000U, regs.sp());
@@ -336,11 +336,13 @@
   ASSERT_EQ(0x20000U, regs[ARM_REG_PC]);
 
   // Load bias is non-zero.
-  ASSERT_TRUE(interface.StepExidx(0x8000, 0x1000, &regs, &process_memory_, &finished));
+  interface.set_load_bias(0x1000);
+  ASSERT_TRUE(interface.StepExidx(0x8000, &regs, &process_memory_, &finished));
   EXPECT_EQ(ERROR_UNWIND_INFO, interface.LastErrorCode());
 
   // Pc too small.
-  ASSERT_FALSE(interface.StepExidx(0x8000, 0x9000, &regs, &process_memory_, &finished));
+  interface.set_load_bias(0x9000);
+  ASSERT_FALSE(interface.StepExidx(0x8000, &regs, &process_memory_, &finished));
   EXPECT_EQ(ERROR_UNWIND_INFO, interface.LastErrorCode());
 }
 
@@ -362,7 +364,7 @@
 
   // Everything should pass.
   bool finished;
-  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
   EXPECT_EQ(ERROR_NONE, interface.LastErrorCode());
   ASSERT_FALSE(finished);
   ASSERT_EQ(0x10004U, regs.sp());
@@ -386,7 +388,7 @@
   regs.set_pc(0x1234);
 
   bool finished;
-  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
   EXPECT_EQ(ERROR_NONE, interface.LastErrorCode());
   ASSERT_TRUE(finished);
   ASSERT_EQ(0x10000U, regs.sp());
@@ -409,7 +411,7 @@
   regs.set_pc(0x1234);
 
   bool finished;
-  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
   EXPECT_EQ(ERROR_NONE, interface.LastErrorCode());
   ASSERT_TRUE(finished);
   ASSERT_EQ(0x10000U, regs.sp());
@@ -436,7 +438,7 @@
   regs.set_pc(0x1234);
 
   bool finished;
-  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
   EXPECT_EQ(ERROR_NONE, interface.LastErrorCode());
   ASSERT_TRUE(finished);
   ASSERT_EQ(0U, regs.pc());
@@ -449,7 +451,7 @@
   regs.set_sp(regs[ARM_REG_SP]);
   regs.set_pc(0x1234);
 
-  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
   EXPECT_EQ(ERROR_NONE, interface.LastErrorCode());
   ASSERT_TRUE(finished);
   ASSERT_EQ(0U, regs.pc());
diff --git a/libunwindstack/tests/ElfInterfaceTest.cpp b/libunwindstack/tests/ElfInterfaceTest.cpp
index bf97e30..4008e9b 100644
--- a/libunwindstack/tests/ElfInterfaceTest.cpp
+++ b/libunwindstack/tests/ElfInterfaceTest.cpp
@@ -647,7 +647,7 @@
   memory_.SetData32(0x10004, 0x500);
   memory_.SetData32(0x10008, 250);
 
-  elf.InitHeaders();
+  elf.InitHeaders(0);
 
   EXPECT_FALSE(elf.eh_frame() == nullptr);
   EXPECT_TRUE(elf.debug_frame() == nullptr);
@@ -680,7 +680,7 @@
   memory_.SetData32(0x5108, 0x1500);
   memory_.SetData32(0x510c, 0x200);
 
-  elf.InitHeaders();
+  elf.InitHeaders(0);
 
   EXPECT_TRUE(elf.eh_frame() == nullptr);
   EXPECT_FALSE(elf.debug_frame() == nullptr);
@@ -703,7 +703,7 @@
   elf.FakeSetDebugFrameOffset(0);
   elf.FakeSetDebugFrameSize(0);
 
-  elf.InitHeaders();
+  elf.InitHeaders(0);
 
   EXPECT_TRUE(elf.eh_frame() == nullptr);
   EXPECT_EQ(0U, elf.eh_frame_offset());
@@ -728,7 +728,7 @@
   elf.FakeSetDebugFrameOffset(0x1000);
   elf.FakeSetDebugFrameSize(0x100);
 
-  elf.InitHeaders();
+  elf.InitHeaders(0);
 
   EXPECT_TRUE(elf.eh_frame() == nullptr);
   EXPECT_TRUE(elf.debug_frame() == nullptr);
@@ -833,10 +833,10 @@
   // Look in the first symbol table.
   std::string name;
   uint64_t name_offset;
-  ASSERT_TRUE(elf->GetFunctionName(0x90010, 0, &name, &name_offset));
+  ASSERT_TRUE(elf->GetFunctionName(0x90010, &name, &name_offset));
   EXPECT_EQ("function_one", name);
   EXPECT_EQ(16U, name_offset);
-  ASSERT_TRUE(elf->GetFunctionName(0xd0020, 0, &name, &name_offset));
+  ASSERT_TRUE(elf->GetFunctionName(0xd0020, &name, &name_offset));
   EXPECT_EQ("function_two", name);
   EXPECT_EQ(32U, name_offset);
 }
@@ -1065,7 +1065,7 @@
 
   uint64_t load_bias = 0;
   ASSERT_TRUE(elf->Init(&load_bias));
-  elf->InitHeaders();
+  elf->InitHeaders(0);
   EXPECT_EQ(0U, load_bias);
   EXPECT_FALSE(elf->IsValidPc(0));
   EXPECT_FALSE(elf->IsValidPc(0x20ff));
@@ -1128,7 +1128,7 @@
 
   uint64_t load_bias = 0;
   ASSERT_TRUE(elf->Init(&load_bias));
-  elf->InitHeaders();
+  elf->InitHeaders(0);
   EXPECT_EQ(0U, load_bias);
   EXPECT_FALSE(elf->IsValidPc(0));
   EXPECT_FALSE(elf->IsValidPc(0x27ff));
diff --git a/libunwindstack/tests/ElfTest.cpp b/libunwindstack/tests/ElfTest.cpp
index aecbf6d..55fe16f 100644
--- a/libunwindstack/tests/ElfTest.cpp
+++ b/libunwindstack/tests/ElfTest.cpp
@@ -297,16 +297,11 @@
   elf.FakeSetInterface(interface);
 
   elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0);
   MapInfo map_info(0x1000, 0x2000);
 
   ASSERT_EQ(0x101U, elf.GetRelPc(0x1101, &map_info));
 
-  elf.FakeSetLoadBias(0x3000);
-  ASSERT_EQ(0x3101U, elf.GetRelPc(0x1101, &map_info));
-
   elf.FakeSetValid(false);
-  elf.FakeSetLoadBias(0);
   ASSERT_EQ(0x101U, elf.GetRelPc(0x1101, &map_info));
 }
 
@@ -328,7 +323,6 @@
   }
 
   elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0);
   bool finished;
   ASSERT_TRUE(elf.Step(0x3000, 0x1000, &regs, &process_memory, &finished));
   EXPECT_FALSE(finished);
@@ -342,11 +336,11 @@
   virtual ~ElfInterfaceMock() = default;
 
   bool Init(uint64_t*) override { return false; }
-  void InitHeaders() override {}
+  void InitHeaders(uint64_t) override {}
   bool GetSoname(std::string*) override { return false; }
-  bool GetFunctionName(uint64_t, uint64_t, std::string*, uint64_t*) override { return false; }
+  bool GetFunctionName(uint64_t, std::string*, uint64_t*) override { return false; }
 
-  MOCK_METHOD5(Step, bool(uint64_t, uint64_t, Regs*, Memory*, bool*));
+  MOCK_METHOD4(Step, bool(uint64_t, Regs*, Memory*, bool*));
   MOCK_METHOD2(GetGlobalVariable, bool(const std::string&, uint64_t*));
   MOCK_METHOD1(IsValidPc, bool(uint64_t));
 
@@ -358,7 +352,6 @@
 TEST_F(ElfTest, step_in_interface) {
   ElfFake elf(memory_);
   elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0);
 
   RegsArm regs;
 
@@ -367,30 +360,12 @@
   MemoryFake process_memory;
 
   bool finished;
-  EXPECT_CALL(*interface, Step(0x1000, 0, &regs, &process_memory, &finished))
+  EXPECT_CALL(*interface, Step(0x1000, &regs, &process_memory, &finished))
       .WillOnce(::testing::Return(true));
 
   ASSERT_TRUE(elf.Step(0x1004, 0x1000, &regs, &process_memory, &finished));
 }
 
-TEST_F(ElfTest, step_in_interface_non_zero_load_bias) {
-  ElfFake elf(memory_);
-  elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0x4000);
-
-  RegsArm regs;
-
-  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
-  elf.FakeSetInterface(interface);
-  MemoryFake process_memory;
-
-  bool finished;
-  EXPECT_CALL(*interface, Step(0x7300, 0x4000, &regs, &process_memory, &finished))
-      .WillOnce(::testing::Return(true));
-
-  ASSERT_TRUE(elf.Step(0x7304, 0x7300, &regs, &process_memory, &finished));
-}
-
 TEST_F(ElfTest, get_global_invalid_elf) {
   ElfFake elf(memory_);
   elf.FakeSetValid(false);
@@ -403,7 +378,6 @@
 TEST_F(ElfTest, get_global_valid_not_in_interface) {
   ElfFake elf(memory_);
   elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0);
 
   ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
   elf.FakeSetInterface(interface);
@@ -431,10 +405,26 @@
   ASSERT_FALSE(elf.GetGlobalVariable(global, &offset));
 }
 
+TEST_F(ElfTest, get_global_valid_dynamic_zero_non_zero_load_bias) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(true);
+  elf.FakeSetLoadBias(0x100);
+
+  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
+  elf.FakeSetInterface(interface);
+
+  uint64_t offset;
+  std::string global("something");
+  EXPECT_CALL(*interface, GetGlobalVariable(global, &offset))
+      .WillOnce(::testing::DoAll(::testing::SetArgPointee<1>(0x300), ::testing::Return(true)));
+
+  ASSERT_TRUE(elf.GetGlobalVariable(global, &offset));
+  EXPECT_EQ(0x200U, offset);
+}
+
 TEST_F(ElfTest, get_global_valid_dynamic_zero) {
   ElfFake elf(memory_);
   elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0);
 
   ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
   elf.FakeSetInterface(interface);
@@ -456,7 +446,6 @@
 TEST_F(ElfTest, get_global_valid_in_gnu_debugdata_dynamic_zero) {
   ElfFake elf(memory_);
   elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0);
 
   ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
   elf.FakeSetInterface(interface);
@@ -470,27 +459,9 @@
   EXPECT_EQ(0x300U, offset);
 }
 
-TEST_F(ElfTest, get_global_valid_dynamic_zero_non_zero_load_bias) {
-  ElfFake elf(memory_);
-  elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0x100);
-
-  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
-  elf.FakeSetInterface(interface);
-
-  uint64_t offset;
-  std::string global("something");
-  EXPECT_CALL(*interface, GetGlobalVariable(global, &offset))
-      .WillOnce(::testing::DoAll(::testing::SetArgPointee<1>(0x300), ::testing::Return(true)));
-
-  ASSERT_TRUE(elf.GetGlobalVariable(global, &offset));
-  EXPECT_EQ(0x200U, offset);
-}
-
 TEST_F(ElfTest, get_global_valid_dynamic_adjust_negative) {
   ElfFake elf(memory_);
   elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0);
 
   ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
   interface->MockSetDynamicOffset(0x400);
@@ -510,7 +481,6 @@
 TEST_F(ElfTest, get_global_valid_dynamic_adjust_positive) {
   ElfFake elf(memory_);
   elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0);
 
   ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
   interface->MockSetDynamicOffset(0x1000);
@@ -530,7 +500,6 @@
 TEST_F(ElfTest, is_valid_pc_elf_invalid) {
   ElfFake elf(memory_);
   elf.FakeSetValid(false);
-  elf.FakeSetLoadBias(0);
 
   EXPECT_FALSE(elf.IsValidPc(0x100));
   EXPECT_FALSE(elf.IsValidPc(0x200));
@@ -539,7 +508,6 @@
 TEST_F(ElfTest, is_valid_pc_interface) {
   ElfFake elf(memory_);
   elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0);
 
   ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
   elf.FakeSetInterface(interface);
@@ -549,25 +517,9 @@
   EXPECT_TRUE(elf.IsValidPc(0x1500));
 }
 
-TEST_F(ElfTest, is_valid_pc_non_zero_load_bias) {
-  ElfFake elf(memory_);
-  elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0x1000);
-
-  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
-  elf.FakeSetInterface(interface);
-
-  EXPECT_CALL(*interface, IsValidPc(0x500)).WillOnce(::testing::Return(true));
-
-  EXPECT_FALSE(elf.IsValidPc(0x100));
-  EXPECT_FALSE(elf.IsValidPc(0x200));
-  EXPECT_TRUE(elf.IsValidPc(0x1500));
-}
-
 TEST_F(ElfTest, is_valid_pc_from_gnu_debugdata) {
   ElfFake elf(memory_);
   elf.FakeSetValid(true);
-  elf.FakeSetLoadBias(0);
 
   ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
   elf.FakeSetInterface(interface);
diff --git a/libunwindstack/tests/SymbolsTest.cpp b/libunwindstack/tests/SymbolsTest.cpp
index 45a7b58..b40a253 100644
--- a/libunwindstack/tests/SymbolsTest.cpp
+++ b/libunwindstack/tests/SymbolsTest.cpp
@@ -70,18 +70,18 @@
 
   std::string name;
   uint64_t func_offset;
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5000, &this->memory_, &name, &func_offset));
   ASSERT_EQ("fake_function", name);
   ASSERT_EQ(0U, func_offset);
 
   name.clear();
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x500f, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x500f, &this->memory_, &name, &func_offset));
   ASSERT_EQ("fake_function", name);
   ASSERT_EQ(0xfU, func_offset);
 
   // Check one before and one after the function.
-  ASSERT_FALSE(symbols.GetName<TypeParam>(0x4fff, 0, &this->memory_, &name, &func_offset));
-  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5010, 0, &this->memory_, &name, &func_offset));
+  ASSERT_FALSE(symbols.GetName<TypeParam>(0x4fff, &this->memory_, &name, &func_offset));
+  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5010, &this->memory_, &name, &func_offset));
 }
 
 TYPED_TEST_P(SymbolsTest, no_symbol) {
@@ -98,7 +98,7 @@
   // First verify that we can get the name.
   std::string name;
   uint64_t func_offset;
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5000, &this->memory_, &name, &func_offset));
   ASSERT_EQ("fake_function", name);
   ASSERT_EQ(0U, func_offset);
 
@@ -107,7 +107,7 @@
   this->memory_.SetMemory(offset, &sym, sizeof(sym));
   // Clear the cache to force the symbol data to be re-read.
   symbols.ClearCache();
-  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));
+  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5000, &this->memory_, &name, &func_offset));
 
   // Set the function back, and set the shndx to UNDEF.
   sym.st_info = STT_FUNC;
@@ -115,7 +115,7 @@
   this->memory_.SetMemory(offset, &sym, sizeof(sym));
   // Clear the cache to force the symbol data to be re-read.
   symbols.ClearCache();
-  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));
+  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5000, &this->memory_, &name, &func_offset));
 }
 
 TYPED_TEST_P(SymbolsTest, multiple_entries) {
@@ -144,34 +144,34 @@
 
   std::string name;
   uint64_t func_offset;
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x3005, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x3005, &this->memory_, &name, &func_offset));
   ASSERT_EQ("function_two", name);
   ASSERT_EQ(1U, func_offset);
 
   name.clear();
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5004, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5004, &this->memory_, &name, &func_offset));
   ASSERT_EQ("function_one", name);
   ASSERT_EQ(4U, func_offset);
 
   name.clear();
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0xa011, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0xa011, &this->memory_, &name, &func_offset));
   ASSERT_EQ("function_three", name);
   ASSERT_EQ(1U, func_offset);
 
   // Reget some of the others to verify getting one function name doesn't
   // affect any of the next calls.
   name.clear();
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5008, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5008, &this->memory_, &name, &func_offset));
   ASSERT_EQ("function_one", name);
   ASSERT_EQ(8U, func_offset);
 
   name.clear();
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x3008, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x3008, &this->memory_, &name, &func_offset));
   ASSERT_EQ("function_two", name);
   ASSERT_EQ(4U, func_offset);
 
   name.clear();
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0xa01a, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0xa01a, &this->memory_, &name, &func_offset));
   ASSERT_EQ("function_three", name);
   ASSERT_EQ(0xaU, func_offset);
 }
@@ -203,47 +203,21 @@
 
   std::string name;
   uint64_t func_offset;
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x3005, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x3005, &this->memory_, &name, &func_offset));
   ASSERT_EQ("function_two", name);
   ASSERT_EQ(1U, func_offset);
 
   name.clear();
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5004, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5004, &this->memory_, &name, &func_offset));
   ASSERT_EQ("function_one", name);
   ASSERT_EQ(4U, func_offset);
 
   name.clear();
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0xa011, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0xa011, &this->memory_, &name, &func_offset));
   ASSERT_EQ("function_three", name);
   ASSERT_EQ(1U, func_offset);
 }
 
-TYPED_TEST_P(SymbolsTest, load_bias) {
-  Symbols symbols(0x1000, sizeof(TypeParam), sizeof(TypeParam), 0x2000, 0x100);
-
-  TypeParam sym;
-  this->InitSym(&sym, 0x5000, 0x10, 0x40);
-  uint64_t offset = 0x1000;
-  this->memory_.SetMemory(offset, &sym, sizeof(sym));
-
-  std::string fake_name("fake_function");
-  this->memory_.SetMemory(0x2040, fake_name.c_str(), fake_name.size() + 1);
-
-  // Set a non-zero load_bias that should be a valid function offset.
-  std::string name;
-  uint64_t func_offset;
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5004, 0x1000, &this->memory_, &name, &func_offset));
-  ASSERT_EQ("fake_function", name);
-  ASSERT_EQ(4U, func_offset);
-
-  // Set a flag that should cause the load_bias to be ignored.
-  sym.st_shndx = SHN_ABS;
-  this->memory_.SetMemory(offset, &sym, sizeof(sym));
-  // Clear the cache to force the symbol data to be re-read.
-  symbols.ClearCache();
-  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5004, 0x1000, &this->memory_, &name, &func_offset));
-}
-
 TYPED_TEST_P(SymbolsTest, symtab_value_out_of_bounds) {
   Symbols symbols_end_at_100(0x1000, sizeof(TypeParam) * 2, sizeof(TypeParam), 0x2000, 0x100);
   Symbols symbols_end_at_200(0x1000, sizeof(TypeParam) * 2, sizeof(TypeParam), 0x2000, 0x200);
@@ -265,18 +239,16 @@
   std::string name;
   uint64_t func_offset;
   // Verify that we can get the function name properly for both entries.
-  ASSERT_TRUE(symbols_end_at_200.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols_end_at_200.GetName<TypeParam>(0x5000, &this->memory_, &name, &func_offset));
   ASSERT_EQ("fake_function", name);
   ASSERT_EQ(0U, func_offset);
-  ASSERT_TRUE(symbols_end_at_200.GetName<TypeParam>(0x3000, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols_end_at_200.GetName<TypeParam>(0x3000, &this->memory_, &name, &func_offset));
   ASSERT_EQ("function", name);
   ASSERT_EQ(0U, func_offset);
 
   // Now use the symbol table that ends at 0x100.
-  ASSERT_FALSE(
-      symbols_end_at_100.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));
-  ASSERT_FALSE(
-      symbols_end_at_100.GetName<TypeParam>(0x3000, 0, &this->memory_, &name, &func_offset));
+  ASSERT_FALSE(symbols_end_at_100.GetName<TypeParam>(0x5000, &this->memory_, &name, &func_offset));
+  ASSERT_FALSE(symbols_end_at_100.GetName<TypeParam>(0x3000, &this->memory_, &name, &func_offset));
 }
 
 // Verify the entire func table is cached.
@@ -302,9 +274,9 @@
   // Do call that should cache all of the entries (except the string data).
   std::string name;
   uint64_t func_offset;
-  ASSERT_FALSE(symbols.GetName<TypeParam>(0x6000, 0, &this->memory_, &name, &func_offset));
+  ASSERT_FALSE(symbols.GetName<TypeParam>(0x6000, &this->memory_, &name, &func_offset));
   this->memory_.Clear();
-  ASSERT_FALSE(symbols.GetName<TypeParam>(0x6000, 0, &this->memory_, &name, &func_offset));
+  ASSERT_FALSE(symbols.GetName<TypeParam>(0x6000, &this->memory_, &name, &func_offset));
 
   // Clear the memory and only put the symbol data string data in memory.
   this->memory_.Clear();
@@ -317,15 +289,15 @@
   fake_name = "third_entry";
   this->memory_.SetMemory(0xa300, fake_name.c_str(), fake_name.size() + 1);
 
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5001, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5001, &this->memory_, &name, &func_offset));
   ASSERT_EQ("first_entry", name);
   ASSERT_EQ(1U, func_offset);
 
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x2002, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x2002, &this->memory_, &name, &func_offset));
   ASSERT_EQ("second_entry", name);
   ASSERT_EQ(2U, func_offset);
 
-  ASSERT_TRUE(symbols.GetName<TypeParam>(0x1003, 0, &this->memory_, &name, &func_offset));
+  ASSERT_TRUE(symbols.GetName<TypeParam>(0x1003, &this->memory_, &name, &func_offset));
   ASSERT_EQ("third_entry", name);
   ASSERT_EQ(3U, func_offset);
 }
@@ -381,17 +353,17 @@
   EXPECT_FALSE(symbols.GetGlobal<TypeParam>(&this->memory_, "function_1", &offset));
 
   std::string name;
-  EXPECT_TRUE(symbols.GetName<TypeParam>(0x10002, 0, &this->memory_, &name, &offset));
+  EXPECT_TRUE(symbols.GetName<TypeParam>(0x10002, &this->memory_, &name, &offset));
   EXPECT_EQ("function_0", name);
   EXPECT_EQ(2U, offset);
 
-  EXPECT_TRUE(symbols.GetName<TypeParam>(0x12004, 0, &this->memory_, &name, &offset));
+  EXPECT_TRUE(symbols.GetName<TypeParam>(0x12004, &this->memory_, &name, &offset));
   EXPECT_EQ("function_1", name);
   EXPECT_EQ(4U, offset);
 }
 
 REGISTER_TYPED_TEST_CASE_P(SymbolsTest, function_bounds_check, no_symbol, multiple_entries,
-                           multiple_entries_nonstandard_size, load_bias, symtab_value_out_of_bounds,
+                           multiple_entries_nonstandard_size, symtab_value_out_of_bounds,
                            symtab_read_cached, get_global);
 
 typedef ::testing::Types<Elf32_Sym, Elf64_Sym> SymbolsTestTypes;
diff --git a/libunwindstack/tests/UnwindOfflineTest.cpp b/libunwindstack/tests/UnwindOfflineTest.cpp
index 285fc9e..a65c077 100644
--- a/libunwindstack/tests/UnwindOfflineTest.cpp
+++ b/libunwindstack/tests/UnwindOfflineTest.cpp
@@ -1200,4 +1200,43 @@
   EXPECT_EQ(0xffcc1558U, unwinder.frames()[18].sp);
 }
 
+// Test using a non-zero load bias library that has the fde entries
+// encoded as 0xb, which is not set as pc relative.
+TEST_F(UnwindOfflineTest, debug_frame_load_bias_arm) {
+  ASSERT_NO_FATAL_FAILURE(Init("debug_frame_load_bias_arm/", ARCH_ARM));
+
+  Unwinder unwinder(128, maps_.get(), regs_.get(), process_memory_);
+  unwinder.Unwind();
+
+  std::string frame_info(DumpFrames(unwinder));
+  ASSERT_EQ(8U, unwinder.NumFrames()) << "Unwind:\n" << frame_info;
+  EXPECT_EQ(
+      "  #00 pc 0005138c  libc.so (__ioctl+8)\n"
+      "  #01 pc 0002140f  libc.so (ioctl+30)\n"
+      "  #02 pc 00039535  libbinder.so (_ZN7android14IPCThreadState14talkWithDriverEb+204)\n"
+      "  #03 pc 00039633  libbinder.so (_ZN7android14IPCThreadState20getAndExecuteCommandEv+10)\n"
+      "  #04 pc 00039b57  libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb+38)\n"
+      "  #05 pc 00000c21  mediaserver (main+104)\n"
+      "  #06 pc 00084b89  libc.so (__libc_init+48)\n"
+      "  #07 pc 00000b77  mediaserver (_start_main+38)\n",
+      frame_info);
+
+  EXPECT_EQ(0xf0be238cU, unwinder.frames()[0].pc);
+  EXPECT_EQ(0xffd4a638U, unwinder.frames()[0].sp);
+  EXPECT_EQ(0xf0bb240fU, unwinder.frames()[1].pc);
+  EXPECT_EQ(0xffd4a638U, unwinder.frames()[1].sp);
+  EXPECT_EQ(0xf1a75535U, unwinder.frames()[2].pc);
+  EXPECT_EQ(0xffd4a650U, unwinder.frames()[2].sp);
+  EXPECT_EQ(0xf1a75633U, unwinder.frames()[3].pc);
+  EXPECT_EQ(0xffd4a6b0U, unwinder.frames()[3].sp);
+  EXPECT_EQ(0xf1a75b57U, unwinder.frames()[4].pc);
+  EXPECT_EQ(0xffd4a6d0U, unwinder.frames()[4].sp);
+  EXPECT_EQ(0x8d1cc21U, unwinder.frames()[5].pc);
+  EXPECT_EQ(0xffd4a6e8U, unwinder.frames()[5].sp);
+  EXPECT_EQ(0xf0c15b89U, unwinder.frames()[6].pc);
+  EXPECT_EQ(0xffd4a700U, unwinder.frames()[6].sp);
+  EXPECT_EQ(0x8d1cb77U, unwinder.frames()[7].pc);
+  EXPECT_EQ(0xffd4a718U, unwinder.frames()[7].sp);
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/libbinder.so b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/libbinder.so
new file mode 100644
index 0000000..4b7bf44
--- /dev/null
+++ b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/libbinder.so
Binary files differ
diff --git a/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/libc.so b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/libc.so
new file mode 100644
index 0000000..013858e
--- /dev/null
+++ b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/libc.so
Binary files differ
diff --git a/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/maps.txt b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/maps.txt
new file mode 100644
index 0000000..10f1325
--- /dev/null
+++ b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/maps.txt
@@ -0,0 +1,3 @@
+8d1c000-8d1f000 r-xp 0 00:00 0   mediaserver
+f0b91000-f0c2c000 r-xp 0 00:00 0   libc.so
+f1a41000-f1a97000 r-xp 0 00:00 0   libbinder.so
diff --git a/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/mediaserver b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/mediaserver
new file mode 100644
index 0000000..9e4a83f
--- /dev/null
+++ b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/mediaserver
Binary files differ
diff --git a/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/regs.txt b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/regs.txt
new file mode 100644
index 0000000..f147247
--- /dev/null
+++ b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/regs.txt
@@ -0,0 +1,16 @@
+r0: 3
+r1: c0306201
+r2: ffd4a658
+r3: 0
+r4: f0c36d8c
+r5: ffd4a658
+r6: f0168000
+r7: 36
+r8: ffd4a678
+r9: f016802c
+r10: ffd4a660
+r11: 0
+ip: 0
+sp: ffd4a638
+lr: f0bb2413
+pc: f0be238c
diff --git a/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/stack.data b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/stack.data
new file mode 100644
index 0000000..847c819
--- /dev/null
+++ b/libunwindstack/tests/files/offline/debug_frame_load_bias_arm/stack.data
Binary files differ
diff --git a/libunwindstack/tools/unwind_info.cpp b/libunwindstack/tools/unwind_info.cpp
index 5a8edfd..266a6db 100644
--- a/libunwindstack/tools/unwind_info.cpp
+++ b/libunwindstack/tools/unwind_info.cpp
@@ -37,7 +37,7 @@
 
 namespace unwindstack {
 
-void DumpArm(ElfInterfaceArm* interface) {
+void DumpArm(Elf* elf, ElfInterfaceArm* interface) {
   if (interface == nullptr) {
     printf("No ARM Unwind Information.\n\n");
     return;
@@ -48,12 +48,11 @@
     uint64_t load_bias = entry.second.table_offset;
     printf(" PC Range 0x%" PRIx64 " - 0x%" PRIx64 "\n", entry.second.offset + load_bias,
            entry.second.table_size + load_bias);
-    for (auto addr : *interface) {
+    for (auto pc : *interface) {
       std::string name;
-      printf("  PC 0x%" PRIx64, addr + load_bias);
+      printf("  PC 0x%" PRIx64, pc + load_bias);
       uint64_t func_offset;
-      uint64_t pc = addr + load_bias;
-      if (interface->GetFunctionName(pc, load_bias, &name, &func_offset) && !name.empty()) {
+      if (elf->GetFunctionName(pc + load_bias, &name, &func_offset) && !name.empty()) {
         printf(" <%s>", name.c_str());
       }
       printf("\n");
@@ -63,7 +62,7 @@
         continue;
       }
       ArmExidx arm(nullptr, interface->memory(), nullptr);
-      arm.set_log(true);
+      arm.set_log(ARM_LOG_FULL);
       arm.set_log_skip_execution(true);
       arm.set_log_indent(2);
       if (!arm.ExtractEntryData(entry)) {
@@ -82,21 +81,21 @@
   printf("\n");
 }
 
-void DumpDwarfSection(ElfInterface* interface, DwarfSection* section, uint64_t load_bias) {
+void DumpDwarfSection(Elf* elf, DwarfSection* section, uint64_t) {
   for (const DwarfFde* fde : *section) {
     // Sometimes there are entries that have empty length, skip those since
     // they don't contain any interesting information.
     if (fde == nullptr || fde->pc_start == fde->pc_end) {
       continue;
     }
-    printf("\n  PC 0x%" PRIx64, fde->pc_start + load_bias);
+    printf("\n  PC 0x%" PRIx64 "-0x%" PRIx64, fde->pc_start, fde->pc_end);
     std::string name;
     uint64_t func_offset;
-    if (interface->GetFunctionName(fde->pc_start, load_bias, &name, &func_offset) && !name.empty()) {
+    if (elf->GetFunctionName(fde->pc_start, &name, &func_offset) && !name.empty()) {
       printf(" <%s>", name.c_str());
     }
     printf("\n");
-    if (!section->Log(2, UINT64_MAX, load_bias, fde)) {
+    if (!section->Log(2, UINT64_MAX, fde)) {
       printf("Failed to process cfa information for entry at 0x%" PRIx64 "\n", fde->pc_start);
     }
   }
@@ -126,13 +125,13 @@
 
   ElfInterface* interface = elf.interface();
   if (elf.machine_type() == EM_ARM) {
-    DumpArm(reinterpret_cast<ElfInterfaceArm*>(interface));
+    DumpArm(&elf, reinterpret_cast<ElfInterfaceArm*>(interface));
     printf("\n");
   }
 
   if (interface->eh_frame() != nullptr) {
     printf("eh_frame information:\n");
-    DumpDwarfSection(interface, interface->eh_frame(), elf.GetLoadBias());
+    DumpDwarfSection(&elf, interface->eh_frame(), elf.GetLoadBias());
     printf("\n");
   } else {
     printf("\nno eh_frame information\n");
@@ -140,7 +139,7 @@
 
   if (interface->debug_frame() != nullptr) {
     printf("\ndebug_frame information:\n");
-    DumpDwarfSection(interface, interface->debug_frame(), elf.GetLoadBias());
+    DumpDwarfSection(&elf, interface->debug_frame(), elf.GetLoadBias());
     printf("\n");
   } else {
     printf("\nno debug_frame information\n");
@@ -151,12 +150,12 @@
   if (gnu_debugdata_interface != nullptr) {
     if (gnu_debugdata_interface->eh_frame() != nullptr) {
       printf("\ngnu_debugdata (eh_frame):\n");
-      DumpDwarfSection(gnu_debugdata_interface, gnu_debugdata_interface->eh_frame(), 0);
+      DumpDwarfSection(&elf, gnu_debugdata_interface->eh_frame(), 0);
       printf("\n");
     }
     if (gnu_debugdata_interface->debug_frame() != nullptr) {
       printf("\ngnu_debugdata (debug_frame):\n");
-      DumpDwarfSection(gnu_debugdata_interface, gnu_debugdata_interface->debug_frame(), 0);
+      DumpDwarfSection(&elf, gnu_debugdata_interface->debug_frame(), 0);
       printf("\n");
     }
   } else {
diff --git a/libunwindstack/tools/unwind_reg_info.cpp b/libunwindstack/tools/unwind_reg_info.cpp
index 47a4f91..0f01566 100644
--- a/libunwindstack/tools/unwind_reg_info.cpp
+++ b/libunwindstack/tools/unwind_reg_info.cpp
@@ -34,7 +34,9 @@
 #include <unwindstack/ElfInterface.h>
 #include <unwindstack/Log.h>
 
+#include "ArmExidx.h"
 #include "DwarfOp.h"
+#include "ElfInterfaceArm.h"
 
 namespace unwindstack {
 
@@ -136,6 +138,32 @@
   }
 }
 
+void PrintArmRegInformation(ElfInterfaceArm* interface, uint64_t pc) {
+  printf("\nArm exidx:\n");
+  uint64_t entry_offset;
+  if (!interface->FindEntry(pc, &entry_offset)) {
+    return;
+  }
+
+  ArmExidx arm(nullptr, interface->memory(), nullptr);
+
+  log_to_stdout(true);
+  arm.set_log(ARM_LOG_BY_REG);
+  arm.set_log_skip_execution(true);
+  arm.set_log_indent(1);
+  if (!arm.ExtractEntryData(entry_offset)) {
+    if (arm.status() != ARM_STATUS_NO_UNWIND) {
+      printf("  Error trying to extract data.\n");
+    }
+    return;
+  }
+  if (arm.data()->size() != 0 && arm.Eval()) {
+    arm.LogByReg();
+  } else {
+    printf("  Error tring to evaluate exidx data.\n");
+  }
+}
+
 int GetInfo(const char* file, uint64_t pc) {
   MemoryFileAtOffset* memory = new MemoryFileAtOffset;
   if (!memory->Init(file, 0)) {
@@ -162,12 +190,22 @@
     printf("Soname: %s\n\n", soname.c_str());
   }
 
-  printf("PC 0x%" PRIx64 ":\n", pc);
+  printf("PC 0x%" PRIx64, pc);
+  std::string function_name;
+  uint64_t function_offset;
+  if (elf.GetFunctionName(pc, &function_name, &function_offset)) {
+    printf(" (%s)", function_name.c_str());
+  }
+  printf(":\n");
+
+  if (elf.machine_type() == EM_ARM) {
+    PrintArmRegInformation(reinterpret_cast<ElfInterfaceArm*>(interface), pc - load_bias);
+  }
 
   DwarfSection* section = interface->eh_frame();
   if (section != nullptr) {
     printf("\neh_frame:\n");
-    PrintRegInformation(section, memory, pc - load_bias, elf.class_type());
+    PrintRegInformation(section, memory, pc, elf.class_type());
   } else {
     printf("\nno eh_frame information\n");
   }
@@ -175,7 +213,7 @@
   section = interface->debug_frame();
   if (section != nullptr) {
     printf("\ndebug_frame:\n");
-    PrintRegInformation(section, memory, pc - load_bias, elf.class_type());
+    PrintRegInformation(section, memory, pc, elf.class_type());
     printf("\n");
   } else {
     printf("\nno debug_frame information\n");
diff --git a/libunwindstack/tools/unwind_symbols.cpp b/libunwindstack/tools/unwind_symbols.cpp
index 086dffe..f8e3e92 100644
--- a/libunwindstack/tools/unwind_symbols.cpp
+++ b/libunwindstack/tools/unwind_symbols.cpp
@@ -95,7 +95,6 @@
   }
 
   std::string name;
-  uint64_t load_bias = elf.GetLoadBias();
   if (argc == 3) {
     std::string cur_name;
     uint64_t func_offset;
@@ -113,8 +112,8 @@
 
   // This is a crude way to get the symbols in order.
   for (const auto& entry : elf.interface()->pt_loads()) {
-    uint64_t start = entry.second.offset + load_bias;
-    uint64_t end = entry.second.table_size + load_bias;
+    uint64_t start = entry.second.offset;
+    uint64_t end = entry.second.table_size;
     for (uint64_t addr = start; addr < end; addr += 4) {
       std::string cur_name;
       uint64_t func_offset;
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 9395ef8..bbfa9d8 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -47,6 +47,7 @@
 cc_defaults {
     name: "libutils_defaults",
     vendor_available: true,
+    recovery_available: true,
     vndk: {
         enabled: true,
         support_system_process: true,
@@ -90,6 +91,10 @@
             },
         },
 
+        recovery: {
+            exclude_shared_libs: ["libvndksupport"],
+        },
+
         host: {
             cflags: ["-DLIBUTILS_NATIVE=1"],
 
diff --git a/libutils/misc.cpp b/libutils/misc.cpp
index da28dfa..f074341 100644
--- a/libutils/misc.cpp
+++ b/libutils/misc.cpp
@@ -23,7 +23,7 @@
 #include <utils/Log.h>
 #include <utils/Vector.h>
 
-#if defined(__ANDROID__)
+#if defined(__ANDROID__) && !defined(__ANDROID_RECOVERY__)
 #include <dlfcn.h>
 #include <vndksupport/linker.h>
 #endif
@@ -70,7 +70,7 @@
 void add_sysprop_change_callback(sysprop_change_callback, int) {}
 #endif
 
-#if defined(__ANDROID__)
+#if defined(__ANDROID__) && !defined(__ANDROID_RECOVERY__)
 void (*get_report_sysprop_change_func())() {
     void (*func)() = nullptr;
     void* handle = android_load_sphal_library("libutils.so", RTLD_NOW);
@@ -85,7 +85,7 @@
 void report_sysprop_change() {
     do_report_sysprop_change();
 
-#if defined(__ANDROID__)
+#if defined(__ANDROID__) && !defined(__ANDROID_RECOVERY__)
     // libutils.so is double loaded; from the default namespace and from the
     // 'sphal' namespace. Redirect the sysprop change event to the other instance
     // of libutils.so loaded in the 'sphal' namespace so that listeners attached
diff --git a/libziparchive/Android.bp b/libziparchive/Android.bp
index 6c06618..2606aa9 100644
--- a/libziparchive/Android.bp
+++ b/libziparchive/Android.bp
@@ -58,6 +58,7 @@
     name: "libziparchive",
     host_supported: true,
     vendor_available: true,
+    recovery_available: true,
     vndk: {
         enabled: true,
     },
diff --git a/logd/logd.rc b/logd/logd.rc
index bd303b7..c740ecf 100644
--- a/logd/logd.rc
+++ b/logd/logd.rc
@@ -6,6 +6,7 @@
     file /dev/kmsg w
     user logd
     group logd system package_info readproc
+    capabilities SYSLOG AUDIT_CONTROL SETGID
     writepid /dev/cpuset/system-background/tasks
 
 service logd-reinit /system/bin/logd --reinit
diff --git a/logd/main.cpp b/logd/main.cpp
index 606aa63..b697d44 100644
--- a/logd/main.cpp
+++ b/logd/main.cpp
@@ -87,37 +87,27 @@
 //
 
 static int drop_privs(bool klogd, bool auditd) {
-    // Tricky, if ro.build.type is "eng" then this is true because of the
-    // side effect that ro.debuggable == 1 as well, else it is false.
-    bool eng =
-        __android_logger_property_get_bool("ro.build.type", BOOL_DEFAULT_FALSE);
-
-    struct sched_param param;
-    memset(&param, 0, sizeof(param));
+    sched_param param = {};
 
     if (set_sched_policy(0, SP_BACKGROUND) < 0) {
         android::prdebug("failed to set background scheduling policy");
-        if (!eng) return -1;
+        return -1;
     }
 
     if (sched_setscheduler((pid_t)0, SCHED_BATCH, &param) < 0) {
         android::prdebug("failed to set batch scheduler");
-        if (!eng) return -1;
+        return -1;
     }
 
     if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) {
         android::prdebug("failed to set background cgroup");
-        if (!eng) return -1;
-    }
-
-    if (!eng && (prctl(PR_SET_DUMPABLE, 0) < 0)) {
-        android::prdebug("failed to clear PR_SET_DUMPABLE");
         return -1;
     }
 
-    if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
-        android::prdebug("failed to set PR_SET_KEEPCAPS");
-        if (!eng) return -1;
+    if (__android_logger_property_get_bool("ro.debuggable", BOOL_DEFAULT_FALSE) &&
+        prctl(PR_SET_DUMPABLE, 0) == -1) {
+        android::prdebug("failed to clear PR_SET_DUMPABLE");
+        return -1;
     }
 
     std::unique_ptr<struct _cap_struct, int (*)(void*)> caps(cap_init(),
@@ -138,24 +128,24 @@
         android::prdebug(
             "failed to set CAP_SETGID, CAP_SYSLOG or CAP_AUDIT_CONTROL (%d)",
             errno);
-        if (!eng) return -1;
+        return -1;
     }
 
     gid_t groups[] = { AID_READPROC };
 
     if (setgroups(arraysize(groups), groups) == -1) {
         android::prdebug("failed to set AID_READPROC groups");
-        if (!eng) return -1;
+        return -1;
     }
 
     if (setgid(AID_LOGD) != 0) {
         android::prdebug("failed to set AID_LOGD gid");
-        if (!eng) return -1;
+        return -1;
     }
 
     if (setuid(AID_LOGD) != 0) {
         android::prdebug("failed to set AID_LOGD uid");
-        if (!eng) return -1;
+        return -1;
     }
 
     if (cap_set_flag(caps.get(), CAP_PERMITTED, 1, cap_value, CAP_CLEAR) < 0) {
@@ -166,7 +156,7 @@
     }
     if (cap_set_proc(caps.get()) < 0) {
         android::prdebug("failed to clear CAP_SETGID (%d)", errno);
-        if (!eng) return -1;
+        return -1;
     }
 
     return 0;
@@ -473,7 +463,7 @@
     bool auditd =
         __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
     if (drop_privs(klogd, auditd) != 0) {
-        return -1;
+        return EXIT_FAILURE;
     }
 
     // Serves the purpose of managing the last logs times read on a
@@ -501,7 +491,7 @@
 
     LogReader* reader = new LogReader(logBuf);
     if (reader->startListener()) {
-        exit(1);
+        return EXIT_FAILURE;
     }
 
     // LogListener listens on /dev/socket/logdw for client
@@ -511,7 +501,7 @@
     LogListener* swl = new LogListener(logBuf, reader);
     // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
     if (swl->startListener(600)) {
-        exit(1);
+        return EXIT_FAILURE;
     }
 
     // Command listener listens on /dev/socket/logd for incoming logd
@@ -519,7 +509,7 @@
 
     CommandListener* cl = new CommandListener(logBuf, reader, swl);
     if (cl->startListener()) {
-        exit(1);
+        return EXIT_FAILURE;
     }
 
     // LogAudit listens on NETLINK_AUDIT socket for selinux
@@ -554,5 +544,5 @@
 
     TEMP_FAILURE_RETRY(pause());
 
-    exit(0);
+    return EXIT_SUCCESS;
 }
diff --git a/mkbootimg/OWNERS b/mkbootimg/OWNERS
index 39448cf..3a00860 100644
--- a/mkbootimg/OWNERS
+++ b/mkbootimg/OWNERS
@@ -1 +1,2 @@
+hridya@google.com
 tbao@google.com
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 3c9e5f3..70b6a3e 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -306,6 +306,16 @@
 _enforce_vndk_lite_at_runtime :=
 
 #######################################
+# ld.config.txt for recovery
+include $(CLEAR_VARS)
+LOCAL_MODULE := ld.config.recovery.txt
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := etc/ld.config.recovery.txt
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/etc
+LOCAL_MODULE_STEM := ld.config.txt
+include $(BUILD_PREBUILT)
+
+#######################################
 # llndk.libraries.txt
 include $(CLEAR_VARS)
 LOCAL_MODULE := llndk.libraries.txt
diff --git a/rootdir/etc/ld.config.recovery.txt b/rootdir/etc/ld.config.recovery.txt
new file mode 100644
index 0000000..5d6c01a
--- /dev/null
+++ b/rootdir/etc/ld.config.recovery.txt
@@ -0,0 +1,9 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Bionic loader config file for recovery mode
+#
+
+dir.recovery = /system/bin
+
+[recovery]
+namespace.default.search.paths = /system/${LIB}
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index 2e42b70..6438e3d 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -12,10 +12,13 @@
         "mkshrc_vendor",
         "reboot",
         "sh",
+        "sh.recovery",
         "sh_vendor",
         "toolbox",
+        "toolbox.recovery",
         "toolbox_vendor",
         "toybox",
+        "toybox.recovery",
         "toybox_vendor",
     ],
 }
diff --git a/toolbox/Android.bp b/toolbox/Android.bp
index 077f542..f12c810 100644
--- a/toolbox/Android.bp
+++ b/toolbox/Android.bp
@@ -46,6 +46,7 @@
 cc_binary {
     name: "toolbox",
     defaults: ["toolbox_binary_defaults"],
+    recovery_available: true,
 }
 
 cc_binary {