Merge "Remove libmemunreachable from the VNDK."
diff --git a/init/devices.cpp b/init/devices.cpp
index c52d8f8..2943fb7 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -147,21 +147,34 @@
     }
 }
 
-// Given a path that may start with a platform device, find the length of the
-// platform device prefix.  If it doesn't start with a platform device, return false
-bool PlatformDeviceList::Find(const std::string& path, std::string* out_path) const {
-    out_path->clear();
-    // platform_devices is searched backwards, since parents are added before their children,
-    // and we want to match as deep of a child as we can.
-    for (auto it = platform_devices_.crbegin(); it != platform_devices_.crend(); ++it) {
-        auto platform_device_path_length = it->length();
-        if (platform_device_path_length < path.length() &&
-            path[platform_device_path_length] == '/' &&
-            android::base::StartsWith(path, it->c_str())) {
-            *out_path = *it;
+// Given a path that may start with a platform device, find the parent platform device by finding a
+// parent directory with a 'subsystem' symlink that points to the platform bus.
+// If it doesn't start with a platform device, return false
+bool DeviceHandler::FindPlatformDevice(std::string path, std::string* platform_device_path) const {
+    platform_device_path->clear();
+
+    // Uevents don't contain the mount point, so we need to add it here.
+    path.insert(0, sysfs_mount_point_);
+
+    std::string directory = android::base::Dirname(path);
+
+    while (directory != "/" && directory != ".") {
+        std::string subsystem_link_path;
+        if (android::base::Realpath(directory + "/subsystem", &subsystem_link_path) &&
+            subsystem_link_path == sysfs_mount_point_ + "/bus/platform") {
+            // We need to remove the mount point that we added above before returning.
+            directory.erase(0, sysfs_mount_point_.size());
+            *platform_device_path = directory;
             return true;
         }
+
+        auto last_slash = path.rfind('/');
+        if (last_slash == std::string::npos) return false;
+
+        path.erase(last_slash);
+        directory = android::base::Dirname(path);
     }
+
     return false;
 }
 
@@ -258,7 +271,7 @@
 
 std::vector<std::string> DeviceHandler::GetCharacterDeviceSymlinks(const Uevent& uevent) const {
     std::string parent_device;
-    if (!platform_devices_.Find(uevent.path, &parent_device)) return {};
+    if (!FindPlatformDevice(uevent.path, &parent_device)) return {};
 
     // skip path to the parent driver
     std::string path = uevent.path.substr(parent_device.length());
@@ -316,7 +329,7 @@
     std::string device;
     std::string type;
 
-    if (platform_devices_.Find(uevent.path, &device)) {
+    if (FindPlatformDevice(uevent.path, &device)) {
         // Skip /devices/platform or /devices/ if present
         static const std::string devices_platform_prefix = "/devices/platform/";
         static const std::string devices_prefix = "/devices/";
@@ -388,14 +401,6 @@
     }
 }
 
-void DeviceHandler::HandlePlatformDeviceEvent(const Uevent& uevent) {
-    if (uevent.action == "add") {
-        platform_devices_.Add(uevent.path);
-    } else if (uevent.action == "remove") {
-        platform_devices_.Remove(uevent.path);
-    }
-}
-
 void DeviceHandler::HandleBlockDeviceEvent(const Uevent& uevent) const {
     // if it's not a /dev device, nothing to do
     if (uevent.major < 0 || uevent.minor < 0) return;
@@ -458,8 +463,6 @@
 
     if (uevent.subsystem == "block") {
         HandleBlockDeviceEvent(uevent);
-    } else if (uevent.subsystem == "platform") {
-        HandlePlatformDeviceEvent(uevent);
     } else {
         HandleGenericDeviceEvent(uevent);
     }
@@ -472,7 +475,8 @@
       sysfs_permissions_(std::move(sysfs_permissions)),
       subsystems_(std::move(subsystems)),
       sehandle_(selinux_android_file_context_handle()),
-      skip_restorecon_(skip_restorecon) {}
+      skip_restorecon_(skip_restorecon),
+      sysfs_mount_point_("/sys") {}
 
 DeviceHandler::DeviceHandler()
     : DeviceHandler(std::vector<Permissions>{}, std::vector<SysfsPermissions>{},
diff --git a/init/devices.h b/init/devices.h
index 09a0ce3..362c38c 100644
--- a/init/devices.h
+++ b/init/devices.h
@@ -93,20 +93,6 @@
     DevnameSource devname_source_;
 };
 
-class PlatformDeviceList {
-  public:
-    void Add(const std::string& path) { platform_devices_.emplace_back(path); }
-    void Remove(const std::string& path) {
-        auto it = std::find(platform_devices_.begin(), platform_devices_.end(), path);
-        if (it != platform_devices_.end()) platform_devices_.erase(it);
-    }
-    bool Find(const std::string& path, std::string* out_path) const;
-    auto size() const { return platform_devices_.size(); }
-
-  private:
-    std::vector<std::string> platform_devices_;
-};
-
 class DeviceHandler {
   public:
     friend class DeviceHandlerTester;
@@ -119,16 +105,11 @@
 
     void HandleDeviceEvent(const Uevent& uevent);
 
-    void FixupSysPermissions(const std::string& upath, const std::string& subsystem) const;
-
-    void HandlePlatformDeviceEvent(const Uevent& uevent);
-    void HandleBlockDeviceEvent(const Uevent& uevent) const;
-    void HandleGenericDeviceEvent(const Uevent& uevent) const;
-
     std::vector<std::string> GetBlockDeviceSymlinks(const Uevent& uevent) const;
     void set_skip_restorecon(bool value) { skip_restorecon_ = value; }
 
   private:
+    bool FindPlatformDevice(std::string path, std::string* platform_device_path) const;
     std::tuple<mode_t, uid_t, gid_t> GetDevicePermissions(
         const std::string& path, const std::vector<std::string>& links) const;
     void MakeDevice(const std::string& path, int block, int major, int minor,
@@ -136,13 +117,17 @@
     std::vector<std::string> GetCharacterDeviceSymlinks(const Uevent& uevent) const;
     void HandleDevice(const std::string& action, const std::string& devpath, int block, int major,
                       int minor, const std::vector<std::string>& links) const;
+    void FixupSysPermissions(const std::string& upath, const std::string& subsystem) const;
+
+    void HandleBlockDeviceEvent(const Uevent& uevent) const;
+    void HandleGenericDeviceEvent(const Uevent& uevent) const;
 
     std::vector<Permissions> dev_permissions_;
     std::vector<SysfsPermissions> sysfs_permissions_;
     std::vector<Subsystem> subsystems_;
-    PlatformDeviceList platform_devices_;
     selabel_handle* sehandle_;
     bool skip_restorecon_;
+    std::string sysfs_mount_point_;
 };
 
 // Exposed for testing
diff --git a/init/devices_test.cpp b/init/devices_test.cpp
index 41b101b..e1e4e49 100644
--- a/init/devices_test.cpp
+++ b/init/devices_test.cpp
@@ -16,33 +16,29 @@
 
 #include "devices.h"
 
-#include <string>
-#include <vector>
-
 #include <android-base/scopeguard.h>
+#include <android-base/test_utils.h>
 #include <gtest/gtest.h>
 
+#include "util.h"
+
+using namespace std::string_literals;
+
 class DeviceHandlerTester {
   public:
-    void AddPlatformDevice(const std::string& path) {
-        Uevent uevent = {
-            .action = "add", .subsystem = "platform", .path = path,
-        };
-        device_handler_.HandlePlatformDeviceEvent(uevent);
-    }
-
-    void RemovePlatformDevice(const std::string& path) {
-        Uevent uevent = {
-            .action = "remove", .subsystem = "platform", .path = path,
-        };
-        device_handler_.HandlePlatformDeviceEvent(uevent);
-    }
-
-    void TestGetSymlinks(const std::string& platform_device_name, const Uevent& uevent,
+    void TestGetSymlinks(const std::string& platform_device, const Uevent& uevent,
                          const std::vector<std::string> expected_links, bool block) {
-        AddPlatformDevice(platform_device_name);
-        auto platform_device_remover = android::base::make_scope_guard(
-            [this, &platform_device_name]() { RemovePlatformDevice(platform_device_name); });
+        TemporaryDir fake_sys_root;
+        device_handler_.sysfs_mount_point_ = fake_sys_root.path;
+
+        std::string platform_device_dir = fake_sys_root.path + platform_device;
+        mkdir_recursive(platform_device_dir, 0777, nullptr);
+
+        std::string platform_bus = fake_sys_root.path + "/bus/platform"s;
+        mkdir_recursive(platform_bus, 0777, nullptr);
+        symlink(platform_bus.c_str(), (platform_device_dir + "/subsystem").c_str());
+
+        mkdir_recursive(android::base::Dirname(fake_sys_root.path + uevent.path), 0777, nullptr);
 
         std::vector<std::string> result;
         if (block) {
@@ -65,30 +61,6 @@
     DeviceHandler device_handler_;
 };
 
-TEST(device_handler, PlatformDeviceList) {
-    PlatformDeviceList platform_device_list;
-
-    platform_device_list.Add("/devices/platform/some_device_name");
-    platform_device_list.Add("/devices/platform/some_device_name/longer");
-    platform_device_list.Add("/devices/platform/other_device_name");
-    EXPECT_EQ(3U, platform_device_list.size());
-
-    std::string out_path;
-    EXPECT_FALSE(platform_device_list.Find("/devices/platform/not_found", &out_path));
-    EXPECT_EQ("", out_path);
-
-    EXPECT_FALSE(platform_device_list.Find("/devices/platform/some_device_name_with_same_prefix",
-                                           &out_path));
-
-    EXPECT_TRUE(platform_device_list.Find("/devices/platform/some_device_name/longer/longer_child",
-                                          &out_path));
-    EXPECT_EQ("/devices/platform/some_device_name/longer", out_path);
-
-    EXPECT_TRUE(
-        platform_device_list.Find("/devices/platform/some_device_name/other_child", &out_path));
-    EXPECT_EQ("/devices/platform/some_device_name", out_path);
-}
-
 TEST(device_handler, get_character_device_symlinks_success) {
     const char* platform_device = "/devices/platform/some_device_name";
     Uevent uevent = {
diff --git a/init/init.cpp b/init/init.cpp
index 4e462d7..999fada 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -880,7 +880,6 @@
     selinux_android_restorecon("/dev/__properties__", 0);
     selinux_android_restorecon("/plat_property_contexts", 0);
     selinux_android_restorecon("/nonplat_property_contexts", 0);
-    selinux_android_restorecon("/sys", SELINUX_ANDROID_RESTORECON_RECURSE);
     selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
     selinux_android_restorecon("/dev/device-mapper", 0);
 
diff --git a/init/init_first_stage.cpp b/init/init_first_stage.cpp
index 0f2b1f3..8433056 100644
--- a/init/init_first_stage.cpp
+++ b/init/init_first_stage.cpp
@@ -19,6 +19,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 
+#include <chrono>
 #include <memory>
 #include <set>
 #include <string>
@@ -35,6 +36,8 @@
 #include "uevent_listener.h"
 #include "util.h"
 
+using namespace std::chrono_literals;
+
 // Class Declarations
 // ------------------
 class FirstStageMount {
@@ -49,11 +52,11 @@
     bool InitDevices();
 
   protected:
-    void InitRequiredDevices();
-    void InitVerityDevice(const std::string& verity_device);
+    bool InitRequiredDevices();
+    bool InitVerityDevice(const std::string& verity_device);
     bool MountPartitions();
 
-    virtual RegenerationAction UeventCallback(const Uevent& uevent);
+    virtual ListenerAction UeventCallback(const Uevent& uevent);
 
     // Pure virtual functions.
     virtual bool GetRequiredDevices() = 0;
@@ -86,7 +89,7 @@
     ~FirstStageMountVBootV2() override = default;
 
   protected:
-    RegenerationAction UeventCallback(const Uevent& uevent) override;
+    ListenerAction UeventCallback(const Uevent& uevent) override;
     bool GetRequiredDevices() override;
     bool SetUpDmVerity(fstab_rec* fstab_rec) override;
     bool InitAvbHandle();
@@ -141,55 +144,60 @@
 }
 
 bool FirstStageMount::InitDevices() {
-    if (!GetRequiredDevices()) return false;
-
-    InitRequiredDevices();
-
-    // InitRequiredDevices() will remove found partitions from required_devices_partition_names_.
-    // So if it isn't empty here, it means some partitions are not found.
-    if (!required_devices_partition_names_.empty()) {
-        LOG(ERROR) << __FUNCTION__ << "(): partition(s) not found: "
-                   << android::base::Join(required_devices_partition_names_, ", ");
-        return false;
-    } else {
-        return true;
-    }
+    return GetRequiredDevices() && InitRequiredDevices();
 }
 
 // Creates devices with uevent->partition_name matching one in the member variable
 // required_devices_partition_names_. Found partitions will then be removed from it
 // for the subsequent member function to check which devices are NOT created.
-void FirstStageMount::InitRequiredDevices() {
+bool FirstStageMount::InitRequiredDevices() {
     if (required_devices_partition_names_.empty()) {
-        return;
+        return true;
     }
 
     if (need_dm_verity_) {
         const std::string dm_path = "/devices/virtual/misc/device-mapper";
-        uevent_listener_.RegenerateUeventsForPath("/sys" + dm_path,
-                                                  [this, &dm_path](const Uevent& uevent) {
-                                                      if (uevent.path == dm_path) {
-                                                          device_handler_.HandleDeviceEvent(uevent);
-                                                          return RegenerationAction::kStop;
-                                                      }
-                                                      return RegenerationAction::kContinue;
-                                                  });
+        bool found = false;
+        auto dm_callback = [this, &dm_path, &found](const Uevent& uevent) {
+            if (uevent.path == dm_path) {
+                device_handler_.HandleDeviceEvent(uevent);
+                found = true;
+                return ListenerAction::kStop;
+            }
+            return ListenerAction::kContinue;
+        };
+        uevent_listener_.RegenerateUeventsForPath("/sys" + dm_path, dm_callback);
+        if (!found) {
+            uevent_listener_.Poll(dm_callback, 10s);
+        }
+        if (!found) {
+            LOG(ERROR) << "device-mapper device not found";
+            return false;
+        }
     }
 
-    uevent_listener_.RegenerateUevents(
-        [this](const Uevent& uevent) { return UeventCallback(uevent); });
+    auto uevent_callback = [this](const Uevent& uevent) { return UeventCallback(uevent); };
+    uevent_listener_.RegenerateUevents(uevent_callback);
+
+    // UeventCallback() will remove found partitions from required_devices_partition_names_.
+    // So if it isn't empty here, it means some partitions are not found.
+    if (!required_devices_partition_names_.empty()) {
+        uevent_listener_.Poll(uevent_callback, 10s);
+    }
+
+    if (!required_devices_partition_names_.empty()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__ << ": partition(s) not found: "
+                   << android::base::Join(required_devices_partition_names_, ", ");
+        return false;
+    }
+
+    return true;
 }
 
-RegenerationAction FirstStageMount::UeventCallback(const Uevent& uevent) {
-    // We need platform devices to create symlinks.
-    if (uevent.subsystem == "platform") {
-        device_handler_.HandleDeviceEvent(uevent);
-        return RegenerationAction::kContinue;
-    }
-
+ListenerAction FirstStageMount::UeventCallback(const Uevent& uevent) {
     // Ignores everything that is not a block device.
     if (uevent.subsystem != "block") {
-        return RegenerationAction::kContinue;
+        return ListenerAction::kContinue;
     }
 
     if (!uevent.partition_name.empty()) {
@@ -198,34 +206,46 @@
         // suffix when A/B is used.
         auto iter = required_devices_partition_names_.find(uevent.partition_name);
         if (iter != required_devices_partition_names_.end()) {
-            LOG(VERBOSE) << __FUNCTION__ << "(): found partition: " << *iter;
+            LOG(VERBOSE) << __PRETTY_FUNCTION__ << ": found partition: " << *iter;
             required_devices_partition_names_.erase(iter);
             device_handler_.HandleDeviceEvent(uevent);
             if (required_devices_partition_names_.empty()) {
-                return RegenerationAction::kStop;
+                return ListenerAction::kStop;
             } else {
-                return RegenerationAction::kContinue;
+                return ListenerAction::kContinue;
             }
         }
     }
     // Not found a partition or find an unneeded partition, continue to find others.
-    return RegenerationAction::kContinue;
+    return ListenerAction::kContinue;
 }
 
 // Creates "/dev/block/dm-XX" for dm-verity by running coldboot on /sys/block/dm-XX.
-void FirstStageMount::InitVerityDevice(const std::string& verity_device) {
+bool FirstStageMount::InitVerityDevice(const std::string& verity_device) {
     const std::string device_name(basename(verity_device.c_str()));
     const std::string syspath = "/sys/block/" + device_name;
+    bool found = false;
 
-    uevent_listener_.RegenerateUeventsForPath(
-        syspath, [&device_name, &verity_device, this](const Uevent& uevent) {
-            if (uevent.device_name == device_name) {
-                LOG(VERBOSE) << "Creating dm-verity device : " << verity_device;
-                device_handler_.HandleDeviceEvent(uevent);
-                return RegenerationAction::kStop;
-            }
-            return RegenerationAction::kContinue;
-        });
+    auto verity_callback = [&device_name, &verity_device, this, &found](const Uevent& uevent) {
+        if (uevent.device_name == device_name) {
+            LOG(VERBOSE) << "Creating dm-verity device : " << verity_device;
+            device_handler_.HandleDeviceEvent(uevent);
+            found = true;
+            return ListenerAction::kStop;
+        }
+        return ListenerAction::kContinue;
+    };
+
+    uevent_listener_.RegenerateUeventsForPath(syspath, verity_callback);
+    if (!found) {
+        uevent_listener_.Poll(verity_callback, 10s);
+    }
+    if (!found) {
+        LOG(ERROR) << "dm-verity device not found";
+        return false;
+    }
+
+    return true;
 }
 
 bool FirstStageMount::MountPartitions() {
@@ -291,7 +311,7 @@
         } else if (ret == FS_MGR_SETUP_VERITY_SUCCESS) {
             // The exact block device name (fstab_rec->blk_device) is changed to "/dev/block/dm-XX".
             // Needs to create it because ueventd isn't started in init first stage.
-            InitVerityDevice(fstab_rec->blk_device);
+            return InitVerityDevice(fstab_rec->blk_device);
         } else {
             return false;
         }
@@ -351,7 +371,7 @@
     return true;
 }
 
-RegenerationAction FirstStageMountVBootV2::UeventCallback(const Uevent& uevent) {
+ListenerAction FirstStageMountVBootV2::UeventCallback(const Uevent& uevent) {
     // Check if this uevent corresponds to one of the required partitions and store its symlinks if
     // so, in order to create FsManagerAvbHandle later.
     // Note that the parent callback removes partitions from the list of required partitions
diff --git a/init/uevent_listener.cpp b/init/uevent_listener.cpp
index 01b8250..c748984 100644
--- a/init/uevent_listener.cpp
+++ b/init/uevent_listener.cpp
@@ -121,8 +121,8 @@
 // make sure we don't overrun the socket's buffer.
 //
 
-RegenerationAction UeventListener::RegenerateUeventsForDir(DIR* d,
-                                                           RegenerateCallback callback) const {
+ListenerAction UeventListener::RegenerateUeventsForDir(DIR* d,
+                                                       const ListenerCallback& callback) const {
     int dfd = dirfd(d);
 
     int fd = openat(dfd, "uevent", O_WRONLY);
@@ -132,7 +132,7 @@
 
         Uevent uevent;
         while (ReadUevent(&uevent)) {
-            if (callback(uevent) == RegenerationAction::kStop) return RegenerationAction::kStop;
+            if (callback(uevent) == ListenerAction::kStop) return ListenerAction::kStop;
         }
     }
 
@@ -147,49 +147,67 @@
         if (d2 == 0) {
             close(fd);
         } else {
-            if (RegenerateUeventsForDir(d2.get(), callback) == RegenerationAction::kStop) {
-                return RegenerationAction::kStop;
+            if (RegenerateUeventsForDir(d2.get(), callback) == ListenerAction::kStop) {
+                return ListenerAction::kStop;
             }
         }
     }
 
     // default is always to continue looking for uevents
-    return RegenerationAction::kContinue;
+    return ListenerAction::kContinue;
 }
 
-RegenerationAction UeventListener::RegenerateUeventsForPath(const std::string& path,
-                                                            RegenerateCallback callback) const {
+ListenerAction UeventListener::RegenerateUeventsForPath(const std::string& path,
+                                                        const ListenerCallback& callback) const {
     std::unique_ptr<DIR, decltype(&closedir)> d(opendir(path.c_str()), closedir);
-    if (!d) return RegenerationAction::kContinue;
+    if (!d) return ListenerAction::kContinue;
 
     return RegenerateUeventsForDir(d.get(), callback);
 }
 
-const char* kRegenerationPaths[] = {"/sys/class", "/sys/block", "/sys/devices"};
+static const char* kRegenerationPaths[] = {"/sys/class", "/sys/block", "/sys/devices"};
 
-void UeventListener::RegenerateUevents(RegenerateCallback callback) const {
+void UeventListener::RegenerateUevents(const ListenerCallback& callback) const {
     for (const auto path : kRegenerationPaths) {
-        if (RegenerateUeventsForPath(path, callback) == RegenerationAction::kStop) return;
+        if (RegenerateUeventsForPath(path, callback) == ListenerAction::kStop) return;
     }
 }
 
-void UeventListener::DoPolling(PollCallback callback) const {
+void UeventListener::Poll(const ListenerCallback& callback,
+                          const std::optional<std::chrono::milliseconds> relative_timeout) const {
+    using namespace std::chrono;
+
     pollfd ufd;
     ufd.events = POLLIN;
     ufd.fd = device_fd_;
 
+    auto start_time = steady_clock::now();
+
     while (true) {
         ufd.revents = 0;
-        int nr = poll(&ufd, 1, -1);
-        if (nr <= 0) {
+
+        int timeout_ms = -1;
+        if (relative_timeout) {
+            auto now = steady_clock::now();
+            auto time_elapsed = duration_cast<milliseconds>(now - start_time);
+            if (time_elapsed > *relative_timeout) return;
+
+            auto remaining_timeout = *relative_timeout - time_elapsed;
+            timeout_ms = remaining_timeout.count();
+        }
+
+        int nr = poll(&ufd, 1, timeout_ms);
+        if (nr == 0) return;
+        if (nr < 0) {
+            PLOG(ERROR) << "poll() of uevent socket failed, continuing";
             continue;
         }
         if (ufd.revents & POLLIN) {
-            // We're non-blocking, so if we receive a poll event keep processing until there
+            // We're non-blocking, so if we receive a poll event keep processing until
             // we have exhausted all uevent messages.
             Uevent uevent;
             while (ReadUevent(&uevent)) {
-                callback(uevent);
+                if (callback(uevent) == ListenerAction::kStop) return;
             }
         }
     }
diff --git a/init/uevent_listener.h b/init/uevent_listener.h
index 8e6f3b4..0dae102 100644
--- a/init/uevent_listener.h
+++ b/init/uevent_listener.h
@@ -19,7 +19,9 @@
 
 #include <dirent.h>
 
+#include <chrono>
 #include <functional>
+#include <optional>
 
 #include <android-base/unique_fd.h>
 
@@ -27,28 +29,26 @@
 
 #define UEVENT_MSG_LEN 2048
 
-enum class RegenerationAction {
+enum class ListenerAction {
     kStop = 0,  // Stop regenerating uevents as we've handled the one(s) we're interested in.
     kContinue,  // Continue regenerating uevents as we haven't seen the one(s) we're interested in.
 };
 
-using RegenerateCallback = std::function<RegenerationAction(const Uevent&)>;
-using PollCallback = std::function<void(const Uevent&)>;
-
-extern const char* kRegenerationPaths[3];
+using ListenerCallback = std::function<ListenerAction(const Uevent&)>;
 
 class UeventListener {
   public:
     UeventListener();
 
-    void RegenerateUevents(RegenerateCallback callback) const;
-    RegenerationAction RegenerateUeventsForPath(const std::string& path,
-                                                RegenerateCallback callback) const;
-    void DoPolling(PollCallback callback) const;
+    void RegenerateUevents(const ListenerCallback& callback) const;
+    ListenerAction RegenerateUeventsForPath(const std::string& path,
+                                            const ListenerCallback& callback) const;
+    void Poll(const ListenerCallback& callback,
+              const std::optional<std::chrono::milliseconds> relative_timeout = {}) const;
 
   private:
     bool ReadUevent(Uevent* uevent) const;
-    RegenerationAction RegenerateUeventsForDir(DIR* d, RegenerateCallback callback) const;
+    ListenerAction RegenerateUeventsForDir(DIR* d, const ListenerCallback& callback) const;
 
     android::base::unique_fd device_fd_;
 };
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 31e4106..4982b77 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -128,15 +128,7 @@
 void ColdBoot::UeventHandlerMain(unsigned int process_num, unsigned int total_processes) {
     for (unsigned int i = process_num; i < uevent_queue_.size(); i += total_processes) {
         auto& uevent = uevent_queue_[i];
-        if (uevent.action == "add" || uevent.action == "change" || uevent.action == "online") {
-            device_handler_.FixupSysPermissions(uevent.path, uevent.subsystem);
-        }
-
-        if (uevent.subsystem == "block") {
-            device_handler_.HandleBlockDeviceEvent(uevent);
-        } else {
-            device_handler_.HandleGenericDeviceEvent(uevent);
-        }
+        device_handler_.HandleDeviceEvent(uevent);
     }
     _exit(EXIT_SUCCESS);
 }
@@ -145,16 +137,8 @@
     uevent_listener_.RegenerateUevents([this](const Uevent& uevent) {
         HandleFirmwareEvent(uevent);
 
-        // This is the one mutable part of DeviceHandler, in which platform devices are
-        // added to a vector for later reference.  Since there is no communication after
-        // fork()'ing subprocess handlers, all platform devices must be in the vector before
-        // we fork, and therefore they must be handled in this loop.
-        if (uevent.subsystem == "platform") {
-            device_handler_.HandlePlatformDeviceEvent(uevent);
-        }
-
         uevent_queue_.emplace_back(std::move(uevent));
-        return RegenerationAction::kContinue;
+        return ListenerAction::kContinue;
     });
 }
 
@@ -174,9 +158,7 @@
 }
 
 void ColdBoot::DoRestoreCon() {
-    for (const char* path : kRegenerationPaths) {
-        selinux_android_restorecon(path, SELINUX_ANDROID_RESTORECON_RECURSE);
-    }
+    selinux_android_restorecon("/sys", SELINUX_ANDROID_RESTORECON_RECURSE);
     device_handler_.set_skip_restorecon(false);
 }
 
@@ -284,9 +266,10 @@
         cold_boot.Run();
     }
 
-    uevent_listener.DoPolling([&device_handler](const Uevent& uevent) {
+    uevent_listener.Poll([&device_handler](const Uevent& uevent) {
         HandleFirmwareEvent(uevent);
         device_handler.HandleDeviceEvent(uevent);
+        return ListenerAction::kContinue;
     });
 
     return 0;
diff --git a/libcutils/include/cutils/sched_policy.h b/libcutils/include/cutils/sched_policy.h
index 15391d9..cf8f5c3 100644
--- a/libcutils/include/cutils/sched_policy.h
+++ b/libcutils/include/cutils/sched_policy.h
@@ -34,7 +34,7 @@
  * Check if Linux kernel enables SCHEDTUNE feature (only available in Android
  * common kernel or Linaro LSK, not in mainline Linux as of v4.9)
  *
- * Return value: 1 if Linux kernel CONFIG_SCHEDTUNE=y; 0 otherwise.
+ * Return value: 1 if Linux kernel CONFIG_CGROUP_SCHEDTUNE=y; 0 otherwise.
  */
 extern bool schedboost_enabled();
 
diff --git a/libcutils/sched_policy.cpp b/libcutils/sched_policy.cpp
index 7e38535..4c303e6 100644
--- a/libcutils/sched_policy.cpp
+++ b/libcutils/sched_policy.cpp
@@ -136,7 +136,7 @@
 
 /*
     Similar to CONFIG_CPUSETS above, but with a different configuration
-    CONFIG_SCHEDTUNE that's in Android common Linux kernel and Linaro
+    CONFIG_CGROUP_SCHEDTUNE that's in Android common Linux kernel and Linaro
     Stable Kernel (LSK), but not in mainline Linux as of v4.9.
 
     With runtime check using the following function, build time
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index eb2b902..d7e949b 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -48,6 +48,8 @@
     srcs: [
         "ArmExidx.cpp",
         "DwarfCfa.cpp",
+        "DwarfDebugFrame.cpp",
+        "DwarfEhFrame.cpp",
         "DwarfMemory.cpp",
         "DwarfOp.cpp",
         "DwarfSection.cpp",
@@ -96,6 +98,8 @@
         "tests/ArmExidxExtractTest.cpp",
         "tests/DwarfCfaLogTest.cpp",
         "tests/DwarfCfaTest.cpp",
+        "tests/DwarfDebugFrameTest.cpp",
+        "tests/DwarfEhFrameTest.cpp",
         "tests/DwarfMemoryTest.cpp",
         "tests/DwarfOpLogTest.cpp",
         "tests/DwarfOpTest.cpp",
diff --git a/libunwindstack/DwarfDebugFrame.cpp b/libunwindstack/DwarfDebugFrame.cpp
new file mode 100644
index 0000000..3ac02fc
--- /dev/null
+++ b/libunwindstack/DwarfDebugFrame.cpp
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <algorithm>
+
+#include "DwarfDebugFrame.h"
+#include "DwarfMemory.h"
+#include "DwarfSection.h"
+#include "DwarfStructs.h"
+#include "Memory.h"
+
+template <typename AddressType>
+bool DwarfDebugFrame<AddressType>::Init(uint64_t offset, uint64_t size) {
+  offset_ = offset;
+  end_offset_ = offset + size;
+
+  memory_.clear_func_offset();
+  memory_.clear_text_offset();
+  memory_.set_data_offset(offset);
+  memory_.set_cur_offset(offset);
+
+  return CreateSortedFdeList();
+}
+
+template <typename AddressType>
+bool DwarfDebugFrame<AddressType>::GetCieInfo(uint8_t* segment_size, uint8_t* encoding) {
+  uint8_t version;
+  if (!memory_.ReadBytes(&version, 1)) {
+    last_error_ = DWARF_ERROR_MEMORY_INVALID;
+    return false;
+  }
+  // Read the augmentation string.
+  std::vector<char> aug_string;
+  char aug_value;
+  bool get_encoding = false;
+  do {
+    if (!memory_.ReadBytes(&aug_value, 1)) {
+      last_error_ = DWARF_ERROR_MEMORY_INVALID;
+      return false;
+    }
+    if (aug_value == 'R') {
+      get_encoding = true;
+    }
+    aug_string.push_back(aug_value);
+  } while (aug_value != '\0');
+
+  if (version == 4) {
+    // Skip the Address Size field.
+    memory_.set_cur_offset(memory_.cur_offset() + 1);
+
+    // Read the segment size.
+    if (!memory_.ReadBytes(segment_size, 1)) {
+      last_error_ = DWARF_ERROR_MEMORY_INVALID;
+      return false;
+    }
+  } else {
+    *segment_size = 0;
+  }
+
+  if (aug_string[0] != 'z' || !get_encoding) {
+    // No encoding
+    return true;
+  }
+
+  // Skip code alignment factor
+  uint8_t value;
+  do {
+    if (!memory_.ReadBytes(&value, 1)) {
+      last_error_ = DWARF_ERROR_MEMORY_INVALID;
+      return false;
+    }
+  } while (value & 0x80);
+
+  // Skip data alignment factor
+  do {
+    if (!memory_.ReadBytes(&value, 1)) {
+      last_error_ = DWARF_ERROR_MEMORY_INVALID;
+      return false;
+    }
+  } while (value & 0x80);
+
+  if (version == 1) {
+    // Skip return address register.
+    memory_.set_cur_offset(memory_.cur_offset() + 1);
+  } else {
+    // Skip return address register.
+    do {
+      if (!memory_.ReadBytes(&value, 1)) {
+        last_error_ = DWARF_ERROR_MEMORY_INVALID;
+        return false;
+      }
+    } while (value & 0x80);
+  }
+
+  // Skip the augmentation length.
+  do {
+    if (!memory_.ReadBytes(&value, 1)) {
+      last_error_ = DWARF_ERROR_MEMORY_INVALID;
+      return false;
+    }
+  } while (value & 0x80);
+
+  for (size_t i = 1; i < aug_string.size(); i++) {
+    if (aug_string[i] == 'R') {
+      if (!memory_.ReadBytes(encoding, 1)) {
+        last_error_ = DWARF_ERROR_MEMORY_INVALID;
+        return false;
+      }
+      // Got the encoding, that's all we are looking for.
+      return true;
+    } else if (aug_string[i] == 'L') {
+      memory_.set_cur_offset(memory_.cur_offset() + 1);
+    } else if (aug_string[i] == 'P') {
+      uint8_t encoding;
+      if (!memory_.ReadBytes(&encoding, 1)) {
+        last_error_ = DWARF_ERROR_MEMORY_INVALID;
+        return false;
+      }
+      uint64_t value;
+      if (!memory_.template ReadEncodedValue<AddressType>(encoding, &value)) {
+        last_error_ = DWARF_ERROR_MEMORY_INVALID;
+        return false;
+      }
+    }
+  }
+
+  // It should be impossible to get here.
+  abort();
+}
+
+template <typename AddressType>
+bool DwarfDebugFrame<AddressType>::AddFdeInfo(uint64_t entry_offset, uint8_t segment_size,
+                                              uint8_t encoding) {
+  if (segment_size != 0) {
+    memory_.set_cur_offset(memory_.cur_offset() + 1);
+  }
+
+  uint64_t start;
+  if (!memory_.template ReadEncodedValue<AddressType>(encoding & 0xf, &start)) {
+    last_error_ = DWARF_ERROR_MEMORY_INVALID;
+    return false;
+  }
+
+  uint64_t length;
+  if (!memory_.template ReadEncodedValue<AddressType>(encoding & 0xf, &length)) {
+    last_error_ = DWARF_ERROR_MEMORY_INVALID;
+    return false;
+  }
+  if (length != 0) {
+    fdes_.emplace_back(entry_offset, start, length);
+  }
+
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfDebugFrame<AddressType>::CreateSortedFdeList() {
+  memory_.set_cur_offset(offset_);
+
+  // Loop through all of the entries and read just enough to create
+  // a sorted list of pcs.
+  // This code assumes that first comes the cie, then the fdes that
+  // it applies to.
+  uint64_t cie_offset = 0;
+  uint8_t address_encoding;
+  uint8_t segment_size;
+  while (memory_.cur_offset() < end_offset_) {
+    uint64_t cur_entry_offset = memory_.cur_offset();
+
+    // Figure out the entry length and type.
+    uint32_t value32;
+    if (!memory_.ReadBytes(&value32, sizeof(value32))) {
+      last_error_ = DWARF_ERROR_MEMORY_INVALID;
+      return false;
+    }
+
+    uint64_t next_entry_offset;
+    if (value32 == static_cast<uint32_t>(-1)) {
+      uint64_t value64;
+      if (!memory_.ReadBytes(&value64, sizeof(value64))) {
+        last_error_ = DWARF_ERROR_MEMORY_INVALID;
+        return false;
+      }
+      next_entry_offset = memory_.cur_offset() + value64;
+
+      // Read the Cie Id of a Cie or the pointer of the Fde.
+      if (!memory_.ReadBytes(&value64, sizeof(value64))) {
+        last_error_ = DWARF_ERROR_MEMORY_INVALID;
+        return false;
+      }
+
+      if (value64 == static_cast<uint64_t>(-1)) {
+        // Cie 64 bit
+        address_encoding = DW_EH_PE_sdata8;
+        if (!GetCieInfo(&segment_size, &address_encoding)) {
+          return false;
+        }
+        cie_offset = cur_entry_offset;
+      } else {
+        if (offset_ + value64 != cie_offset) {
+          // This means that this Fde is not following the Cie.
+          last_error_ = DWARF_ERROR_ILLEGAL_VALUE;
+          return false;
+        }
+
+        // Fde 64 bit
+        if (!AddFdeInfo(cur_entry_offset, segment_size, address_encoding)) {
+          return false;
+        }
+      }
+    } else {
+      next_entry_offset = memory_.cur_offset() + value32;
+
+      // Read the Cie Id of a Cie or the pointer of the Fde.
+      if (!memory_.ReadBytes(&value32, sizeof(value32))) {
+        last_error_ = DWARF_ERROR_MEMORY_INVALID;
+        return false;
+      }
+
+      if (value32 == static_cast<uint32_t>(-1)) {
+        // Cie 32 bit
+        address_encoding = DW_EH_PE_sdata4;
+        if (!GetCieInfo(&segment_size, &address_encoding)) {
+          return false;
+        }
+        cie_offset = cur_entry_offset;
+      } else {
+        if (offset_ + value32 != cie_offset) {
+          // This means that this Fde is not following the Cie.
+          last_error_ = DWARF_ERROR_ILLEGAL_VALUE;
+          return false;
+        }
+
+        // Fde 32 bit
+        if (!AddFdeInfo(cur_entry_offset, segment_size, address_encoding)) {
+          return false;
+        }
+      }
+    }
+
+    if (next_entry_offset < memory_.cur_offset()) {
+      // This indicates some kind of corruption, or malformed section data.
+      last_error_ = DWARF_ERROR_ILLEGAL_VALUE;
+      return false;
+    }
+    memory_.set_cur_offset(next_entry_offset);
+  }
+
+  // Sort the entries.
+  std::sort(fdes_.begin(), fdes_.end(), [](const FdeInfo& a, const FdeInfo& b) {
+    if (a.start == b.start) return a.end < b.end;
+    return a.start < b.start;
+  });
+
+  fde_count_ = fdes_.size();
+
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfDebugFrame<AddressType>::GetFdeOffsetFromPc(uint64_t pc, uint64_t* fde_offset) {
+  if (fde_count_ == 0) {
+    return false;
+  }
+
+  size_t first = 0;
+  size_t last = fde_count_;
+  while (first < last) {
+    size_t current = (first + last) / 2;
+    const FdeInfo* info = &fdes_[current];
+    if (pc >= info->start && pc <= info->end) {
+      *fde_offset = info->offset;
+      return true;
+    }
+
+    if (pc < info->start) {
+      last = current;
+    } else {
+      first = current + 1;
+    }
+  }
+  return false;
+}
+
+template <typename AddressType>
+const DwarfFde* DwarfDebugFrame<AddressType>::GetFdeFromIndex(size_t index) {
+  if (index >= fdes_.size()) {
+    return nullptr;
+  }
+  return this->GetFdeFromOffset(fdes_[index].offset);
+}
+
+// Explicitly instantiate DwarfDebugFrame.
+template class DwarfDebugFrame<uint32_t>;
+template class DwarfDebugFrame<uint64_t>;
diff --git a/libunwindstack/DwarfDebugFrame.h b/libunwindstack/DwarfDebugFrame.h
new file mode 100644
index 0000000..320368c
--- /dev/null
+++ b/libunwindstack/DwarfDebugFrame.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LIBUNWINDSTACK_DWARF_DEBUG_FRAME_H
+#define _LIBUNWINDSTACK_DWARF_DEBUG_FRAME_H
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "DwarfSection.h"
+
+template <typename AddressType>
+class DwarfDebugFrame : public DwarfSectionImpl<AddressType> {
+ public:
+  // Add these so that the protected members of DwarfSectionImpl
+  // can be accessed without needing a this->.
+  using DwarfSectionImpl<AddressType>::memory_;
+  using DwarfSectionImpl<AddressType>::fde_count_;
+  using DwarfSectionImpl<AddressType>::last_error_;
+
+  struct FdeInfo {
+    FdeInfo(uint64_t offset, uint64_t start, uint64_t length)
+        : offset(offset), start(start), end(start + length) {}
+
+    uint64_t offset;
+    AddressType start;
+    AddressType end;
+  };
+
+  DwarfDebugFrame(Memory* memory) : DwarfSectionImpl<AddressType>(memory) {}
+  virtual ~DwarfDebugFrame() = default;
+
+  bool Init(uint64_t offset, uint64_t size) override;
+
+  bool GetFdeOffsetFromPc(uint64_t pc, uint64_t* fde_offset) override;
+
+  const DwarfFde* GetFdeFromIndex(size_t index) override;
+
+  bool IsCie32(uint32_t value32) override { return value32 == static_cast<uint32_t>(-1); }
+
+  bool IsCie64(uint64_t value64) override { return value64 == static_cast<uint64_t>(-1); }
+
+  uint64_t GetCieOffsetFromFde32(uint32_t pointer) override { return offset_ + pointer; }
+
+  uint64_t GetCieOffsetFromFde64(uint64_t pointer) override { return offset_ + pointer; }
+
+  uint64_t AdjustPcFromFde(uint64_t pc) override { return pc; }
+
+  bool GetCieInfo(uint8_t* segment_size, uint8_t* encoding);
+
+  bool AddFdeInfo(uint64_t entry_offset, uint8_t segment_size, uint8_t encoding);
+
+  bool CreateSortedFdeList();
+
+ protected:
+  uint64_t offset_;
+  uint64_t end_offset_;
+
+  std::vector<FdeInfo> fdes_;
+};
+
+#endif  // _LIBUNWINDSTACK_DWARF_DEBUG_FRAME_H
diff --git a/libunwindstack/DwarfEhFrame.cpp b/libunwindstack/DwarfEhFrame.cpp
new file mode 100644
index 0000000..045fb36
--- /dev/null
+++ b/libunwindstack/DwarfEhFrame.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdint.h>
+
+#include "DwarfEhFrame.h"
+#include "DwarfMemory.h"
+#include "DwarfSection.h"
+#include "DwarfStructs.h"
+#include "Memory.h"
+
+template <typename AddressType>
+bool DwarfEhFrame<AddressType>::Init(uint64_t offset, uint64_t size) {
+  uint8_t data[4];
+
+  memory_.clear_func_offset();
+  memory_.clear_text_offset();
+  memory_.set_data_offset(offset);
+  memory_.set_cur_offset(offset);
+
+  // Read the first four bytes all at once.
+  if (!memory_.ReadBytes(data, 4)) {
+    last_error_ = DWARF_ERROR_MEMORY_INVALID;
+    return false;
+  }
+
+  version_ = data[0];
+  if (version_ != 1) {
+    // Unknown version.
+    last_error_ = DWARF_ERROR_UNSUPPORTED_VERSION;
+    return false;
+  }
+
+  ptr_encoding_ = data[1];
+  uint8_t fde_count_encoding = data[2];
+  table_encoding_ = data[3];
+  table_entry_size_ = memory_.template GetEncodedSize<AddressType>(table_encoding_);
+
+  memory_.set_pc_offset(memory_.cur_offset());
+  if (!memory_.template ReadEncodedValue<AddressType>(ptr_encoding_, &ptr_offset_)) {
+    last_error_ = DWARF_ERROR_MEMORY_INVALID;
+    return false;
+  }
+
+  memory_.set_pc_offset(memory_.cur_offset());
+  if (!memory_.template ReadEncodedValue<AddressType>(fde_count_encoding, &fde_count_)) {
+    last_error_ = DWARF_ERROR_MEMORY_INVALID;
+    return false;
+  }
+
+  entries_offset_ = memory_.cur_offset();
+  entries_end_ = offset + size;
+  entries_data_offset_ = offset;
+  cur_entries_offset_ = entries_offset_;
+
+  return true;
+}
+
+template <typename AddressType>
+const DwarfFde* DwarfEhFrame<AddressType>::GetFdeFromIndex(size_t index) {
+  const FdeInfo* info = GetFdeInfoFromIndex(index);
+  if (info == nullptr) {
+    return nullptr;
+  }
+  return this->GetFdeFromOffset(info->offset);
+}
+
+template <typename AddressType>
+const typename DwarfEhFrame<AddressType>::FdeInfo* DwarfEhFrame<AddressType>::GetFdeInfoFromIndex(
+    size_t index) {
+  auto entry = fde_info_.find(index);
+  if (entry != fde_info_.end()) {
+    return &fde_info_[index];
+  }
+  FdeInfo* info = &fde_info_[index];
+
+  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());
+  uint64_t value;
+  if (!memory_.template ReadEncodedValue<AddressType>(table_encoding_, &value) ||
+      !memory_.template ReadEncodedValue<AddressType>(table_encoding_, &info->offset)) {
+    last_error_ = DWARF_ERROR_MEMORY_INVALID;
+    fde_info_.erase(index);
+    return nullptr;
+  }
+  info->pc = value;
+  return info;
+}
+
+template <typename AddressType>
+bool DwarfEhFrame<AddressType>::GetFdeOffsetBinary(uint64_t pc, uint64_t* fde_offset,
+                                                   uint64_t total_entries) {
+  assert(fde_count_ > 0);
+  assert(total_entries <= fde_count_);
+
+  size_t first = 0;
+  size_t last = total_entries;
+  while (first < last) {
+    size_t current = (first + last) / 2;
+    const FdeInfo* info = GetFdeInfoFromIndex(current);
+    if (pc == info->pc) {
+      *fde_offset = info->offset;
+      return true;
+    }
+    if (pc < info->pc) {
+      last = current;
+    } else {
+      first = current + 1;
+    }
+  }
+  if (last != 0) {
+    const FdeInfo* info = GetFdeInfoFromIndex(last - 1);
+    *fde_offset = info->offset;
+    return true;
+  }
+  return false;
+}
+
+template <typename AddressType>
+bool DwarfEhFrame<AddressType>::GetFdeOffsetSequential(uint64_t pc, uint64_t* fde_offset) {
+  assert(fde_count_ != 0);
+  last_error_ = DWARF_ERROR_NONE;
+  // We can do a binary search if the pc is in the range of the elements
+  // that have already been cached.
+  if (!fde_info_.empty()) {
+    const FdeInfo* info = &fde_info_[fde_info_.size() - 1];
+    if (pc >= info->pc) {
+      *fde_offset = info->offset;
+      return true;
+    }
+    if (pc < info->pc) {
+      return GetFdeOffsetBinary(pc, fde_offset, fde_info_.size());
+    }
+  }
+
+  if (cur_entries_offset_ == 0) {
+    // All entries read, or error encountered.
+    return false;
+  }
+
+  memory_.set_data_offset(entries_data_offset_);
+  memory_.set_cur_offset(cur_entries_offset_);
+  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_ = DWARF_ERROR_MEMORY_INVALID;
+      return false;
+    }
+
+    FdeInfo* info = &fde_info_[current];
+    if (!memory_.template ReadEncodedValue<AddressType>(table_encoding_, &info->offset)) {
+      fde_info_.erase(current);
+      last_error_ = DWARF_ERROR_MEMORY_INVALID;
+      return false;
+    }
+    info->pc = value;
+
+    if (pc < info->pc) {
+      if (prev_info == nullptr) {
+        return false;
+      }
+      cur_entries_offset_ = memory_.cur_offset();
+      *fde_offset = prev_info->offset;
+      return true;
+    }
+    prev_info = info;
+  }
+
+  if (fde_count_ == fde_info_.size() && pc >= prev_info->pc) {
+    *fde_offset = prev_info->offset;
+    return true;
+  }
+  return false;
+}
+
+template <typename AddressType>
+bool DwarfEhFrame<AddressType>::GetFdeOffsetFromPc(uint64_t pc, uint64_t* fde_offset) {
+  if (fde_count_ == 0) {
+    return false;
+  }
+
+  if (table_entry_size_ > 0) {
+    // Do a binary search since the size of each table entry is fixed.
+    return GetFdeOffsetBinary(pc, fde_offset, fde_count_);
+  } else {
+    // Do a sequential search since each table entry size is variable.
+    return GetFdeOffsetSequential(pc, fde_offset);
+  }
+}
+
+// Explicitly instantiate DwarfEhFrame.
+template class DwarfEhFrame<uint32_t>;
+template class DwarfEhFrame<uint64_t>;
diff --git a/libunwindstack/DwarfEhFrame.h b/libunwindstack/DwarfEhFrame.h
new file mode 100644
index 0000000..e6909ed
--- /dev/null
+++ b/libunwindstack/DwarfEhFrame.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LIBUNWINDSTACK_DWARF_EH_FRAME_H
+#define _LIBUNWINDSTACK_DWARF_EH_FRAME_H
+
+#include <stdint.h>
+
+#include "DwarfSection.h"
+
+// Forward declarations.
+class Memory;
+
+template <typename AddressType>
+class DwarfEhFrame : public DwarfSectionImpl<AddressType> {
+ public:
+  // Add these so that the protected members of DwarfSectionImpl
+  // can be accessed without needing a this->.
+  using DwarfSectionImpl<AddressType>::memory_;
+  using DwarfSectionImpl<AddressType>::fde_count_;
+  using DwarfSectionImpl<AddressType>::last_error_;
+
+  struct FdeInfo {
+    AddressType pc;
+    uint64_t offset;
+  };
+
+  DwarfEhFrame(Memory* memory) : DwarfSectionImpl<AddressType>(memory) {}
+  virtual ~DwarfEhFrame() = default;
+
+  bool Init(uint64_t offset, uint64_t size) override;
+
+  bool GetFdeOffsetFromPc(uint64_t pc, uint64_t* fde_offset) override;
+
+  const DwarfFde* GetFdeFromIndex(size_t index) override;
+
+  bool IsCie32(uint32_t value32) override { return value32 == 0; }
+
+  bool IsCie64(uint64_t value64) override { return value64 == 0; }
+
+  uint64_t GetCieOffsetFromFde32(uint32_t pointer) override {
+    return memory_.cur_offset() - pointer - 4;
+  }
+
+  uint64_t GetCieOffsetFromFde64(uint64_t pointer) override {
+    return memory_.cur_offset() - pointer - 8;
+  }
+
+  uint64_t AdjustPcFromFde(uint64_t pc) override {
+    // The eh_frame uses relative pcs.
+    return pc + memory_.cur_offset();
+  }
+
+  const FdeInfo* GetFdeInfoFromIndex(size_t index);
+
+  bool GetFdeOffsetSequential(uint64_t pc, uint64_t* fde_offset);
+
+  bool GetFdeOffsetBinary(uint64_t pc, uint64_t* fde_offset, uint64_t total_entries);
+
+ protected:
+  uint8_t version_;
+  uint8_t ptr_encoding_;
+  uint8_t table_encoding_;
+  size_t table_entry_size_;
+
+  uint64_t ptr_offset_;
+
+  uint64_t entries_offset_;
+  uint64_t entries_end_;
+  uint64_t entries_data_offset_;
+  uint64_t cur_entries_offset_ = 0;
+
+  std::unordered_map<uint64_t, FdeInfo> fde_info_;
+};
+
+#endif  // _LIBUNWINDSTACK_DWARF_EH_FRAME_H
diff --git a/libunwindstack/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp
index 087457c..bfa7944 100644
--- a/libunwindstack/ElfInterface.cpp
+++ b/libunwindstack/ElfInterface.cpp
@@ -20,10 +20,33 @@
 #include <memory>
 #include <string>
 
+#include "DwarfDebugFrame.h"
+#include "DwarfEhFrame.h"
 #include "ElfInterface.h"
 #include "Memory.h"
 #include "Regs.h"
 
+template <typename AddressType>
+void ElfInterface::InitHeadersWithTemplate() {
+  if (eh_frame_offset_ != 0) {
+    eh_frame_.reset(new DwarfEhFrame<AddressType>(memory_));
+    if (!eh_frame_->Init(eh_frame_offset_, eh_frame_size_)) {
+      eh_frame_.reset(nullptr);
+      eh_frame_offset_ = 0;
+      eh_frame_size_ = static_cast<uint64_t>(-1);
+    }
+  }
+
+  if (debug_frame_offset_ != 0) {
+    debug_frame_.reset(new DwarfDebugFrame<AddressType>(memory_));
+    if (!debug_frame_->Init(debug_frame_offset_, debug_frame_size_)) {
+      debug_frame_.reset(nullptr);
+      debug_frame_offset_ = 0;
+      debug_frame_size_ = static_cast<uint64_t>(-1);
+    }
+  }
+}
+
 template <typename EhdrType, typename PhdrType, typename ShdrType>
 bool ElfInterface::ReadAllHeaders() {
   EhdrType ehdr;
@@ -210,6 +233,9 @@
 }
 
 // Instantiate all of the needed template functions.
+template void ElfInterface::InitHeadersWithTemplate<uint32_t>();
+template void ElfInterface::InitHeadersWithTemplate<uint64_t>();
+
 template bool ElfInterface::ReadAllHeaders<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr>();
 template bool ElfInterface::ReadAllHeaders<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr>();
 
diff --git a/libunwindstack/ElfInterface.h b/libunwindstack/ElfInterface.h
index 944146c..1cc8aa0 100644
--- a/libunwindstack/ElfInterface.h
+++ b/libunwindstack/ElfInterface.h
@@ -25,6 +25,8 @@
 #include <unordered_map>
 #include <vector>
 
+#include "DwarfSection.h"
+
 // Forward declarations.
 class Memory;
 class Regs;
@@ -68,10 +70,18 @@
   uint64_t dynamic_size() { return dynamic_size_; }
   uint64_t eh_frame_offset() { return eh_frame_offset_; }
   uint64_t eh_frame_size() { return eh_frame_size_; }
+  uint64_t debug_frame_offset() { return debug_frame_offset_; }
+  uint64_t debug_frame_size() { return debug_frame_size_; }
   uint64_t gnu_debugdata_offset() { return gnu_debugdata_offset_; }
   uint64_t gnu_debugdata_size() { return gnu_debugdata_size_; }
 
+  DwarfSection* eh_frame() { return eh_frame_.get(); }
+  DwarfSection* debug_frame() { return debug_frame_.get(); }
+
  protected:
+  template <typename AddressType>
+  void InitHeadersWithTemplate();
+
   template <typename EhdrType, typename PhdrType, typename ShdrType>
   bool ReadAllHeaders();
 
@@ -105,6 +115,9 @@
 
   uint8_t soname_type_ = SONAME_UNKNOWN;
   std::string soname_;
+
+  std::unique_ptr<DwarfSection> eh_frame_;
+  std::unique_ptr<DwarfSection> debug_frame_;
 };
 
 class ElfInterface32 : public ElfInterface {
@@ -116,8 +129,7 @@
     return ElfInterface::ReadAllHeaders<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr>();
   }
 
-  void InitHeaders() override {
-  }
+  void InitHeaders() override { ElfInterface::InitHeadersWithTemplate<uint32_t>(); }
 
   bool GetSoname(std::string* soname) override {
     return ElfInterface::GetSonameWithTemplate<Elf32_Dyn>(soname);
@@ -137,8 +149,7 @@
     return ElfInterface::ReadAllHeaders<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr>();
   }
 
-  void InitHeaders() override {
-  }
+  void InitHeaders() override { ElfInterface::InitHeadersWithTemplate<uint64_t>(); }
 
   bool GetSoname(std::string* soname) override {
     return ElfInterface::GetSonameWithTemplate<Elf64_Dyn>(soname);
diff --git a/libunwindstack/Maps.cpp b/libunwindstack/Maps.cpp
index b369c43..b869918 100644
--- a/libunwindstack/Maps.cpp
+++ b/libunwindstack/Maps.cpp
@@ -71,15 +71,17 @@
     map_info->flags |= PROT_EXEC;
   }
 
-  map_info->name = &line[name_pos];
-  size_t length = map_info->name.length() - 1;
-  if (map_info->name[length] == '\n') {
-    map_info->name.erase(length);
-  }
-  // Mark a device map in /dev/and not in /dev/ashmem/ specially.
-  if (!map_info->name.empty() && map_info->name.substr(0, 5) == "/dev/" &&
-      map_info->name.substr(5, 7) != "ashmem/") {
-    map_info->flags |= MAPS_FLAGS_DEVICE_MAP;
+  if (line[name_pos] != '\0') {
+    map_info->name = &line[name_pos];
+    size_t length = map_info->name.length() - 1;
+    if (map_info->name[length] == '\n') {
+      map_info->name.erase(length);
+    }
+
+    // Mark a device map in /dev/and not in /dev/ashmem/ specially.
+    if (map_info->name.substr(0, 5) == "/dev/" && map_info->name.substr(5, 7) != "ashmem/") {
+      map_info->flags |= MAPS_FLAGS_DEVICE_MAP;
+    }
   }
 
   return true;
diff --git a/libunwindstack/tests/DwarfDebugFrameTest.cpp b/libunwindstack/tests/DwarfDebugFrameTest.cpp
new file mode 100644
index 0000000..94a9974
--- /dev/null
+++ b/libunwindstack/tests/DwarfDebugFrameTest.cpp
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "DwarfDebugFrame.h"
+#include "DwarfEncoding.h"
+
+#include "LogFake.h"
+#include "MemoryFake.h"
+#include "RegsFake.h"
+
+template <typename TypeParam>
+class MockDwarfDebugFrame : public DwarfDebugFrame<TypeParam> {
+ public:
+  MockDwarfDebugFrame(Memory* memory) : DwarfDebugFrame<TypeParam>(memory) {}
+  ~MockDwarfDebugFrame() = default;
+
+  void TestSetFdeCount(uint64_t count) { this->fde_count_ = count; }
+  void TestSetOffset(uint64_t offset) { this->offset_ = offset; }
+  void TestSetEndOffset(uint64_t offset) { this->end_offset_ = offset; }
+  void TestPushFdeInfo(const typename DwarfDebugFrame<TypeParam>::FdeInfo& info) {
+    this->fdes_.push_back(info);
+  }
+
+  uint64_t TestGetFdeCount() { return this->fde_count_; }
+  uint8_t TestGetOffset() { return this->offset_; }
+  uint8_t TestGetEndOffset() { return this->end_offset_; }
+  void TestGetFdeInfo(size_t index, typename DwarfDebugFrame<TypeParam>::FdeInfo* info) {
+    *info = this->fdes_[index];
+  }
+};
+
+template <typename TypeParam>
+class DwarfDebugFrameTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    memory_.Clear();
+    debug_frame_ = new MockDwarfDebugFrame<TypeParam>(&memory_);
+    ResetLogs();
+  }
+
+  void TearDown() override { delete debug_frame_; }
+
+  MemoryFake memory_;
+  MockDwarfDebugFrame<TypeParam>* debug_frame_ = nullptr;
+};
+TYPED_TEST_CASE_P(DwarfDebugFrameTest);
+
+// NOTE: All test class variables need to be referenced as this->.
+
+TYPED_TEST_P(DwarfDebugFrameTest, Init32) {
+  // CIE 32 information.
+  this->memory_.SetData32(0x5000, 0xfc);
+  this->memory_.SetData32(0x5004, 0xffffffff);
+  this->memory_.SetData8(0x5008, 1);
+  this->memory_.SetData8(0x5009, '\0');
+
+  // 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_.SetData32(0x5200, 0xfc);
+  this->memory_.SetData32(0x5204, 0);
+  this->memory_.SetData32(0x5208, 0x2500);
+  this->memory_.SetData32(0x520c, 0x300);
+
+  // CIE 32 information.
+  this->memory_.SetData32(0x5300, 0xfc);
+  this->memory_.SetData32(0x5304, 0xffffffff);
+  this->memory_.SetData8(0x5308, 1);
+  this->memory_.SetData8(0x5309, '\0');
+
+  // FDE 32 information.
+  this->memory_.SetData32(0x5400, 0xfc);
+  this->memory_.SetData32(0x5404, 0x300);
+  this->memory_.SetData32(0x5408, 0x3500);
+  this->memory_.SetData32(0x540c, 0x400);
+
+  this->memory_.SetData32(0x5500, 0xfc);
+  this->memory_.SetData32(0x5504, 0x300);
+  this->memory_.SetData32(0x5508, 0x4500);
+  this->memory_.SetData32(0x550c, 0x500);
+
+  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600));
+  ASSERT_EQ(4U, 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(0x1500U, info.start);
+  EXPECT_EQ(0x1700U, info.end);
+
+  this->debug_frame_->TestGetFdeInfo(1, &info);
+  EXPECT_EQ(0x5200U, info.offset);
+  EXPECT_EQ(0x2500U, info.start);
+  EXPECT_EQ(0x2800U, info.end);
+
+  this->debug_frame_->TestGetFdeInfo(2, &info);
+  EXPECT_EQ(0x5400U, info.offset);
+  EXPECT_EQ(0x3500U, info.start);
+  EXPECT_EQ(0x3900U, info.end);
+
+  this->debug_frame_->TestGetFdeInfo(3, &info);
+  EXPECT_EQ(0x5500U, info.offset);
+  EXPECT_EQ(0x4500U, info.start);
+  EXPECT_EQ(0x4a00U, info.end);
+}
+
+TYPED_TEST_P(DwarfDebugFrameTest, Init32_fde_not_following_cie) {
+  // CIE 32 information.
+  this->memory_.SetData32(0x5000, 0xfc);
+  this->memory_.SetData32(0x5004, 0xffffffff);
+  this->memory_.SetData8(0x5008, 1);
+  this->memory_.SetData8(0x5009, '\0');
+
+  // FDE 32 information.
+  this->memory_.SetData32(0x5100, 0xfc);
+  this->memory_.SetData32(0x5104, 0x1000);
+  this->memory_.SetData32(0x5108, 0x1500);
+  this->memory_.SetData32(0x510c, 0x200);
+
+  ASSERT_FALSE(this->debug_frame_->Init(0x5000, 0x600));
+  ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->debug_frame_->last_error());
+}
+
+TYPED_TEST_P(DwarfDebugFrameTest, Init64) {
+  // CIE 64 information.
+  this->memory_.SetData32(0x5000, 0xffffffff);
+  this->memory_.SetData64(0x5004, 0xf4);
+  this->memory_.SetData64(0x500c, 0xffffffffffffffffULL);
+  this->memory_.SetData8(0x5014, 1);
+  this->memory_.SetData8(0x5015, '\0');
+
+  // FDE 64 information.
+  this->memory_.SetData32(0x5100, 0xffffffff);
+  this->memory_.SetData64(0x5104, 0xf4);
+  this->memory_.SetData64(0x510c, 0);
+  this->memory_.SetData64(0x5114, 0x1500);
+  this->memory_.SetData64(0x511c, 0x200);
+
+  this->memory_.SetData32(0x5200, 0xffffffff);
+  this->memory_.SetData64(0x5204, 0xf4);
+  this->memory_.SetData64(0x520c, 0);
+  this->memory_.SetData64(0x5214, 0x2500);
+  this->memory_.SetData64(0x521c, 0x300);
+
+  // CIE 64 information.
+  this->memory_.SetData32(0x5300, 0xffffffff);
+  this->memory_.SetData64(0x5304, 0xf4);
+  this->memory_.SetData64(0x530c, 0xffffffffffffffffULL);
+  this->memory_.SetData8(0x5314, 1);
+  this->memory_.SetData8(0x5315, '\0');
+
+  // FDE 64 information.
+  this->memory_.SetData32(0x5400, 0xffffffff);
+  this->memory_.SetData64(0x5404, 0xf4);
+  this->memory_.SetData64(0x540c, 0x300);
+  this->memory_.SetData64(0x5414, 0x3500);
+  this->memory_.SetData64(0x541c, 0x400);
+
+  this->memory_.SetData32(0x5500, 0xffffffff);
+  this->memory_.SetData64(0x5504, 0xf4);
+  this->memory_.SetData64(0x550c, 0x300);
+  this->memory_.SetData64(0x5514, 0x4500);
+  this->memory_.SetData64(0x551c, 0x500);
+
+  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600));
+  ASSERT_EQ(4U, 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(0x1500U, info.start);
+  EXPECT_EQ(0x1700U, info.end);
+
+  this->debug_frame_->TestGetFdeInfo(1, &info);
+  EXPECT_EQ(0x5200U, info.offset);
+  EXPECT_EQ(0x2500U, info.start);
+  EXPECT_EQ(0x2800U, info.end);
+
+  this->debug_frame_->TestGetFdeInfo(2, &info);
+  EXPECT_EQ(0x5400U, info.offset);
+  EXPECT_EQ(0x3500U, info.start);
+  EXPECT_EQ(0x3900U, info.end);
+
+  this->debug_frame_->TestGetFdeInfo(3, &info);
+  EXPECT_EQ(0x5500U, info.offset);
+  EXPECT_EQ(0x4500U, info.start);
+  EXPECT_EQ(0x4a00U, info.end);
+}
+
+TYPED_TEST_P(DwarfDebugFrameTest, Init64_fde_not_following_cie) {
+  // CIE 64 information.
+  this->memory_.SetData32(0x5000, 0xffffffff);
+  this->memory_.SetData64(0x5004, 0xf4);
+  this->memory_.SetData64(0x500c, 0xffffffffffffffffULL);
+  this->memory_.SetData8(0x5014, 1);
+  this->memory_.SetData8(0x5015, '\0');
+
+  // FDE 64 information.
+  this->memory_.SetData32(0x5100, 0xffffffff);
+  this->memory_.SetData64(0x5104, 0xf4);
+  this->memory_.SetData64(0x510c, 0x1000);
+  this->memory_.SetData64(0x5114, 0x1500);
+  this->memory_.SetData64(0x511c, 0x200);
+
+  ASSERT_FALSE(this->debug_frame_->Init(0x5000, 0x600));
+  ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->debug_frame_->last_error());
+}
+
+TYPED_TEST_P(DwarfDebugFrameTest, Init_version1) {
+  // CIE 32 information.
+  this->memory_.SetData32(0x5000, 0xfc);
+  this->memory_.SetData32(0x5004, 0xffffffff);
+  this->memory_.SetData8(0x5008, 1);
+  // Augment string.
+  this->memory_.SetMemory(0x5009, std::vector<uint8_t>{'z', 'R', 'P', 'L', '\0'});
+  // Code alignment factor.
+  this->memory_.SetMemory(0x500e, std::vector<uint8_t>{0x80, 0x00});
+  // Data alignment factor.
+  this->memory_.SetMemory(0x5010, std::vector<uint8_t>{0x81, 0x80, 0x80, 0x00});
+  // Return address register
+  this->memory_.SetData8(0x5014, 0x84);
+  // Augmentation length
+  this->memory_.SetMemory(0x5015, std::vector<uint8_t>{0x84, 0x00});
+  // R data.
+  this->memory_.SetData8(0x5017, DW_EH_PE_pcrel | DW_EH_PE_udata2);
+
+  // FDE 32 information.
+  this->memory_.SetData32(0x5100, 0xfc);
+  this->memory_.SetData32(0x5104, 0);
+  this->memory_.SetData16(0x5108, 0x1500);
+  this->memory_.SetData16(0x510a, 0x200);
+
+  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x200));
+  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(0x1500U, info.start);
+  EXPECT_EQ(0x1700U, info.end);
+}
+
+TYPED_TEST_P(DwarfDebugFrameTest, Init_version4) {
+  // CIE 32 information.
+  this->memory_.SetData32(0x5000, 0xfc);
+  this->memory_.SetData32(0x5004, 0xffffffff);
+  this->memory_.SetData8(0x5008, 4);
+  // Augment string.
+  this->memory_.SetMemory(0x5009, std::vector<uint8_t>{'z', 'L', 'P', 'R', '\0'});
+  // Address size.
+  this->memory_.SetData8(0x500e, 4);
+  // Segment size.
+  this->memory_.SetData8(0x500f, 0);
+  // Code alignment factor.
+  this->memory_.SetMemory(0x5010, std::vector<uint8_t>{0x80, 0x00});
+  // Data alignment factor.
+  this->memory_.SetMemory(0x5012, std::vector<uint8_t>{0x81, 0x80, 0x80, 0x00});
+  // Return address register
+  this->memory_.SetMemory(0x5016, std::vector<uint8_t>{0x85, 0x10});
+  // Augmentation length
+  this->memory_.SetMemory(0x5018, std::vector<uint8_t>{0x84, 0x00});
+  // L data.
+  this->memory_.SetData8(0x501a, 0x10);
+  // P data.
+  this->memory_.SetData8(0x501b, DW_EH_PE_udata4);
+  this->memory_.SetData32(0x501c, 0x100);
+  // R data.
+  this->memory_.SetData8(0x5020, DW_EH_PE_pcrel | DW_EH_PE_udata2);
+
+  // FDE 32 information.
+  this->memory_.SetData32(0x5100, 0xfc);
+  this->memory_.SetData32(0x5104, 0);
+  this->memory_.SetData16(0x5108, 0x1500);
+  this->memory_.SetData16(0x510a, 0x200);
+
+  ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x200));
+  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(0x1500U, info.start);
+  EXPECT_EQ(0x1700U, info.end);
+}
+
+TYPED_TEST_P(DwarfDebugFrameTest, GetFdeOffsetFromPc) {
+  typename DwarfDebugFrame<TypeParam>::FdeInfo info(0, 0, 0);
+  for (size_t i = 0; i < 9; i++) {
+    info.start = 0x1000 * (i + 1);
+    info.end = 0x1000 * (i + 2) - 0x10;
+    info.offset = 0x5000 + i * 0x20;
+    this->debug_frame_->TestPushFdeInfo(info);
+  }
+
+  this->debug_frame_->TestSetFdeCount(0);
+  uint64_t fde_offset;
+  ASSERT_FALSE(this->debug_frame_->GetFdeOffsetFromPc(0x1000, &fde_offset));
+  ASSERT_EQ(DWARF_ERROR_NONE, this->debug_frame_->last_error());
+
+  this->debug_frame_->TestSetFdeCount(9);
+  ASSERT_FALSE(this->debug_frame_->GetFdeOffsetFromPc(0x100, &fde_offset));
+  ASSERT_EQ(DWARF_ERROR_NONE, this->debug_frame_->last_error());
+  // Odd number of elements.
+  for (size_t i = 0; i < 9; i++) {
+    TypeParam pc = 0x1000 * (i + 1);
+    ASSERT_TRUE(this->debug_frame_->GetFdeOffsetFromPc(pc, &fde_offset)) << "Failed at index " << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+    ASSERT_TRUE(this->debug_frame_->GetFdeOffsetFromPc(pc + 1, &fde_offset)) << "Failed at index "
+                                                                             << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+    ASSERT_TRUE(this->debug_frame_->GetFdeOffsetFromPc(pc + 0xeff, &fde_offset))
+        << "Failed at index " << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+    ASSERT_FALSE(this->debug_frame_->GetFdeOffsetFromPc(pc + 0xfff, &fde_offset))
+        << "Failed at index " << i;
+    ASSERT_EQ(DWARF_ERROR_NONE, this->debug_frame_->last_error());
+  }
+
+  // Even number of elements.
+  this->debug_frame_->TestSetFdeCount(10);
+  info.start = 0xa000;
+  info.end = 0xaff0;
+  info.offset = 0x5120;
+  this->debug_frame_->TestPushFdeInfo(info);
+
+  for (size_t i = 0; i < 10; i++) {
+    TypeParam pc = 0x1000 * (i + 1);
+    ASSERT_TRUE(this->debug_frame_->GetFdeOffsetFromPc(pc, &fde_offset)) << "Failed at index " << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+    ASSERT_TRUE(this->debug_frame_->GetFdeOffsetFromPc(pc + 1, &fde_offset)) << "Failed at index "
+                                                                             << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+    ASSERT_TRUE(this->debug_frame_->GetFdeOffsetFromPc(pc + 0xeff, &fde_offset))
+        << "Failed at index " << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+    ASSERT_FALSE(this->debug_frame_->GetFdeOffsetFromPc(pc + 0xfff, &fde_offset))
+        << "Failed at index " << i;
+    ASSERT_EQ(DWARF_ERROR_NONE, this->debug_frame_->last_error());
+  }
+}
+
+TYPED_TEST_P(DwarfDebugFrameTest, GetCieFde32) {
+  this->debug_frame_->TestSetOffset(0x4000);
+
+  // CIE 32 information.
+  this->memory_.SetData32(0xf000, 0x100);
+  this->memory_.SetData32(0xf004, 0xffffffff);
+  this->memory_.SetData8(0xf008, 0x1);
+  this->memory_.SetData8(0xf009, '\0');
+  this->memory_.SetData8(0xf00a, 4);
+  this->memory_.SetData8(0xf00b, 8);
+  this->memory_.SetData8(0xf00c, 0x20);
+
+  // FDE 32 information.
+  this->memory_.SetData32(0x14000, 0x20);
+  this->memory_.SetData32(0x14004, 0xb000);
+  this->memory_.SetData32(0x14008, 0x9000);
+  this->memory_.SetData32(0x1400c, 0x100);
+
+  const DwarfFde* fde = this->debug_frame_->GetFdeFromOffset(0x14000);
+  ASSERT_TRUE(fde != nullptr);
+  EXPECT_EQ(0x14010U, fde->cfa_instructions_offset);
+  EXPECT_EQ(0x14024U, fde->cfa_instructions_end);
+  EXPECT_EQ(0x9000U, fde->pc_start);
+  EXPECT_EQ(0x9100U, fde->pc_end);
+  EXPECT_EQ(0xf000U, fde->cie_offset);
+  EXPECT_EQ(0U, fde->lsda_address);
+
+  ASSERT_TRUE(fde->cie != nullptr);
+  EXPECT_EQ(1U, fde->cie->version);
+  EXPECT_EQ(DW_EH_PE_sdata4, fde->cie->fde_address_encoding);
+  EXPECT_EQ(DW_EH_PE_omit, fde->cie->lsda_encoding);
+  EXPECT_EQ(0U, fde->cie->segment_size);
+  EXPECT_EQ(1U, fde->cie->augmentation_string.size());
+  EXPECT_EQ('\0', fde->cie->augmentation_string[0]);
+  EXPECT_EQ(0U, fde->cie->personality_handler);
+  EXPECT_EQ(0xf00dU, fde->cie->cfa_instructions_offset);
+  EXPECT_EQ(0xf104U, fde->cie->cfa_instructions_end);
+  EXPECT_EQ(4U, fde->cie->code_alignment_factor);
+  EXPECT_EQ(8, fde->cie->data_alignment_factor);
+  EXPECT_EQ(0x20U, fde->cie->return_address_register);
+}
+
+TYPED_TEST_P(DwarfDebugFrameTest, GetCieFde64) {
+  this->debug_frame_->TestSetOffset(0x2000);
+
+  // CIE 64 information.
+  this->memory_.SetData32(0x6000, 0xffffffff);
+  this->memory_.SetData64(0x6004, 0x100);
+  this->memory_.SetData64(0x600c, 0xffffffffffffffffULL);
+  this->memory_.SetData8(0x6014, 0x1);
+  this->memory_.SetData8(0x6015, '\0');
+  this->memory_.SetData8(0x6016, 4);
+  this->memory_.SetData8(0x6017, 8);
+  this->memory_.SetData8(0x6018, 0x20);
+
+  // FDE 64 information.
+  this->memory_.SetData32(0x8000, 0xffffffff);
+  this->memory_.SetData64(0x8004, 0x200);
+  this->memory_.SetData64(0x800c, 0x4000);
+  this->memory_.SetData64(0x8014, 0x5000);
+  this->memory_.SetData64(0x801c, 0x300);
+
+  const DwarfFde* fde = this->debug_frame_->GetFdeFromOffset(0x8000);
+  ASSERT_TRUE(fde != nullptr);
+  EXPECT_EQ(0x8024U, fde->cfa_instructions_offset);
+  EXPECT_EQ(0x820cU, fde->cfa_instructions_end);
+  EXPECT_EQ(0x5000U, fde->pc_start);
+  EXPECT_EQ(0x5300U, fde->pc_end);
+  EXPECT_EQ(0x6000U, fde->cie_offset);
+  EXPECT_EQ(0U, fde->lsda_address);
+
+  ASSERT_TRUE(fde->cie != nullptr);
+  EXPECT_EQ(1U, fde->cie->version);
+  EXPECT_EQ(DW_EH_PE_sdata8, fde->cie->fde_address_encoding);
+  EXPECT_EQ(DW_EH_PE_omit, fde->cie->lsda_encoding);
+  EXPECT_EQ(0U, fde->cie->segment_size);
+  EXPECT_EQ(1U, fde->cie->augmentation_string.size());
+  EXPECT_EQ('\0', fde->cie->augmentation_string[0]);
+  EXPECT_EQ(0U, fde->cie->personality_handler);
+  EXPECT_EQ(0x6019U, fde->cie->cfa_instructions_offset);
+  EXPECT_EQ(0x610cU, fde->cie->cfa_instructions_end);
+  EXPECT_EQ(4U, fde->cie->code_alignment_factor);
+  EXPECT_EQ(8, fde->cie->data_alignment_factor);
+  EXPECT_EQ(0x20U, fde->cie->return_address_register);
+}
+
+REGISTER_TYPED_TEST_CASE_P(DwarfDebugFrameTest, Init32, Init32_fde_not_following_cie, Init64,
+                           Init64_fde_not_following_cie, 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
new file mode 100644
index 0000000..4e538d4
--- /dev/null
+++ b/libunwindstack/tests/DwarfEhFrameTest.cpp
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "DwarfEhFrame.h"
+#include "DwarfEncoding.h"
+
+#include "LogFake.h"
+#include "MemoryFake.h"
+#include "RegsFake.h"
+
+template <typename TypeParam>
+class MockDwarfEhFrame : public DwarfEhFrame<TypeParam> {
+ public:
+  MockDwarfEhFrame(Memory* memory) : DwarfEhFrame<TypeParam>(memory) {}
+  ~MockDwarfEhFrame() = default;
+
+  void TestSetTableEncoding(uint8_t encoding) { this->table_encoding_ = encoding; }
+  void TestSetEntriesOffset(uint64_t offset) { this->entries_offset_ = offset; }
+  void TestSetEntriesEnd(uint64_t end) { this->entries_end_ = end; }
+  void TestSetEntriesDataOffset(uint64_t offset) { this->entries_data_offset_ = offset; }
+  void TestSetCurEntriesOffset(uint64_t offset) { this->cur_entries_offset_ = offset; }
+  void TestSetTableEntrySize(size_t size) { this->table_entry_size_ = size; }
+
+  void TestSetFdeCount(uint64_t count) { this->fde_count_ = count; }
+  void TestSetFdeInfo(uint64_t index, const typename DwarfEhFrame<TypeParam>::FdeInfo& info) {
+    this->fde_info_[index] = info;
+  }
+
+  uint8_t TestGetVersion() { return this->version_; }
+  uint8_t TestGetPtrEncoding() { return this->ptr_encoding_; }
+  uint64_t TestGetPtrOffset() { return this->ptr_offset_; }
+  uint8_t TestGetTableEncoding() { return this->table_encoding_; }
+  uint64_t TestGetTableEntrySize() { return this->table_entry_size_; }
+  uint64_t TestGetFdeCount() { return this->fde_count_; }
+  uint64_t TestGetEntriesOffset() { return this->entries_offset_; }
+  uint64_t TestGetEntriesEnd() { return this->entries_end_; }
+  uint64_t TestGetEntriesDataOffset() { return this->entries_data_offset_; }
+  uint64_t TestGetCurEntriesOffset() { return this->cur_entries_offset_; }
+};
+
+template <typename TypeParam>
+class DwarfEhFrameTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    memory_.Clear();
+    eh_frame_ = new MockDwarfEhFrame<TypeParam>(&memory_);
+    ResetLogs();
+  }
+
+  void TearDown() override { delete eh_frame_; }
+
+  MemoryFake memory_;
+  MockDwarfEhFrame<TypeParam>* eh_frame_ = nullptr;
+};
+TYPED_TEST_CASE_P(DwarfEhFrameTest);
+
+// NOTE: All test class variables need to be referenced as this->.
+
+TYPED_TEST_P(DwarfEhFrameTest, Init) {
+  this->memory_.SetMemory(
+      0x1000, std::vector<uint8_t>{0x1, DW_EH_PE_udata2, DW_EH_PE_udata4, DW_EH_PE_sdata4});
+  this->memory_.SetData16(0x1004, 0x500);
+  this->memory_.SetData32(0x1006, 126);
+
+  ASSERT_TRUE(this->eh_frame_->Init(0x1000, 0x100));
+  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());
+  EXPECT_EQ(4U, this->eh_frame_->TestGetTableEntrySize());
+  EXPECT_EQ(126U, 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());
+
+  // Verify an unexpected version will cause a fail.
+  this->memory_.SetData8(0x1000, 0);
+  ASSERT_FALSE(this->eh_frame_->Init(0x1000, 0x100));
+  ASSERT_EQ(DWARF_ERROR_UNSUPPORTED_VERSION, this->eh_frame_->last_error());
+  this->memory_.SetData8(0x1000, 2);
+  ASSERT_FALSE(this->eh_frame_->Init(0x1000, 0x100));
+  ASSERT_EQ(DWARF_ERROR_UNSUPPORTED_VERSION, this->eh_frame_->last_error());
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetFdeInfoFromIndex_expect_cache_fail) {
+  this->eh_frame_->TestSetTableEntrySize(0x10);
+  this->eh_frame_->TestSetTableEncoding(DW_EH_PE_udata4);
+  ASSERT_TRUE(this->eh_frame_->GetFdeInfoFromIndex(0) == nullptr);
+  ASSERT_EQ(DWARF_ERROR_MEMORY_INVALID, this->eh_frame_->last_error());
+  ASSERT_TRUE(this->eh_frame_->GetFdeInfoFromIndex(0) == nullptr);
+  ASSERT_EQ(DWARF_ERROR_MEMORY_INVALID, this->eh_frame_->last_error());
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetFdeInfoFromIndex_read_pcrel) {
+  this->eh_frame_->TestSetTableEncoding(DW_EH_PE_pcrel | DW_EH_PE_udata4);
+  this->eh_frame_->TestSetEntriesOffset(0x1000);
+  this->eh_frame_->TestSetEntriesDataOffset(0x3000);
+  this->eh_frame_->TestSetTableEntrySize(0x10);
+
+  this->memory_.SetData32(0x1040, 0x340);
+  this->memory_.SetData32(0x1044, 0x500);
+
+  auto info = this->eh_frame_->GetFdeInfoFromIndex(2);
+  ASSERT_TRUE(info != nullptr);
+  EXPECT_EQ(0x1380U, info->pc);
+  EXPECT_EQ(0x1540U, info->offset);
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetFdeInfoFromIndex_read_datarel) {
+  this->eh_frame_->TestSetTableEncoding(DW_EH_PE_datarel | DW_EH_PE_udata4);
+  this->eh_frame_->TestSetEntriesOffset(0x1000);
+  this->eh_frame_->TestSetEntriesDataOffset(0x3000);
+  this->eh_frame_->TestSetTableEntrySize(0x10);
+
+  this->memory_.SetData32(0x1040, 0x340);
+  this->memory_.SetData32(0x1044, 0x500);
+
+  auto info = this->eh_frame_->GetFdeInfoFromIndex(2);
+  ASSERT_TRUE(info != nullptr);
+  EXPECT_EQ(0x3340U, info->pc);
+  EXPECT_EQ(0x3500U, info->offset);
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetFdeInfoFromIndex_cached) {
+  this->eh_frame_->TestSetTableEncoding(DW_EH_PE_udata4);
+  this->eh_frame_->TestSetEntriesOffset(0x1000);
+  this->eh_frame_->TestSetTableEntrySize(0x10);
+
+  this->memory_.SetData32(0x1040, 0x340);
+  this->memory_.SetData32(0x1044, 0x500);
+
+  auto info = this->eh_frame_->GetFdeInfoFromIndex(2);
+  ASSERT_TRUE(info != nullptr);
+  EXPECT_EQ(0x340U, info->pc);
+  EXPECT_EQ(0x500U, info->offset);
+
+  // Clear the memory so that this will fail if it doesn't read cached data.
+  this->memory_.Clear();
+
+  info = this->eh_frame_->GetFdeInfoFromIndex(2);
+  ASSERT_TRUE(info != nullptr);
+  EXPECT_EQ(0x340U, info->pc);
+  EXPECT_EQ(0x500U, info->offset);
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetFdeOffsetBinary_verify) {
+  this->eh_frame_->TestSetTableEntrySize(0x10);
+  this->eh_frame_->TestSetFdeCount(10);
+
+  typename DwarfEhFrame<TypeParam>::FdeInfo info;
+  for (size_t i = 0; i < 10; i++) {
+    info.pc = 0x1000 * (i + 1);
+    info.offset = 0x5000 + i * 0x20;
+    this->eh_frame_->TestSetFdeInfo(i, info);
+  }
+
+  uint64_t fde_offset;
+  EXPECT_FALSE(this->eh_frame_->GetFdeOffsetBinary(0x100, &fde_offset, 10));
+  // Not an error, just not found.
+  ASSERT_EQ(DWARF_ERROR_NONE, this->eh_frame_->last_error());
+  // Even number of elements.
+  for (size_t i = 0; i < 10; i++) {
+    TypeParam pc = 0x1000 * (i + 1);
+    EXPECT_TRUE(this->eh_frame_->GetFdeOffsetBinary(pc, &fde_offset, 10)) << "Failed at index " << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+    EXPECT_TRUE(this->eh_frame_->GetFdeOffsetBinary(pc + 1, &fde_offset, 10)) << "Failed at index "
+                                                                              << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+    EXPECT_TRUE(this->eh_frame_->GetFdeOffsetBinary(pc + 0xfff, &fde_offset, 10))
+        << "Failed at index " << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+  }
+  // Odd number of elements.
+  for (size_t i = 0; i < 9; i++) {
+    TypeParam pc = 0x1000 * (i + 1);
+    EXPECT_TRUE(this->eh_frame_->GetFdeOffsetBinary(pc, &fde_offset, 9)) << "Failed at index " << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+    EXPECT_TRUE(this->eh_frame_->GetFdeOffsetBinary(pc + 1, &fde_offset, 9)) << "Failed at index "
+                                                                             << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+    EXPECT_TRUE(this->eh_frame_->GetFdeOffsetBinary(pc + 0xfff, &fde_offset, 9))
+        << "Failed at index " << i;
+    EXPECT_EQ(0x5000 + i * 0x20, fde_offset) << "Failed at index " << i;
+  }
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetFdeOffsetSequential) {
+  this->eh_frame_->TestSetFdeCount(10);
+  this->eh_frame_->TestSetEntriesDataOffset(0x100);
+  this->eh_frame_->TestSetEntriesEnd(0x2000);
+  this->eh_frame_->TestSetTableEncoding(DW_EH_PE_udata4);
+
+  this->memory_.SetData32(0x1040, 0x340);
+  this->memory_.SetData32(0x1044, 0x500);
+
+  this->memory_.SetData32(0x1048, 0x440);
+  this->memory_.SetData32(0x104c, 0x600);
+
+  // Verify that if entries is zero, that it fails.
+  uint64_t fde_offset;
+  ASSERT_FALSE(this->eh_frame_->GetFdeOffsetSequential(0x340, &fde_offset));
+  this->eh_frame_->TestSetCurEntriesOffset(0x1040);
+
+  ASSERT_TRUE(this->eh_frame_->GetFdeOffsetSequential(0x340, &fde_offset));
+  EXPECT_EQ(0x500U, fde_offset);
+
+  ASSERT_TRUE(this->eh_frame_->GetFdeOffsetSequential(0x440, &fde_offset));
+  EXPECT_EQ(0x600U, fde_offset);
+
+  // Expect that the data is cached so no more memory reads will occur.
+  this->memory_.Clear();
+  ASSERT_TRUE(this->eh_frame_->GetFdeOffsetSequential(0x440, &fde_offset));
+  EXPECT_EQ(0x600U, fde_offset);
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetFdeOffsetSequential_last_element) {
+  this->eh_frame_->TestSetFdeCount(2);
+  this->eh_frame_->TestSetEntriesDataOffset(0x100);
+  this->eh_frame_->TestSetEntriesEnd(0x2000);
+  this->eh_frame_->TestSetTableEncoding(DW_EH_PE_udata4);
+  this->eh_frame_->TestSetCurEntriesOffset(0x1040);
+
+  this->memory_.SetData32(0x1040, 0x340);
+  this->memory_.SetData32(0x1044, 0x500);
+
+  this->memory_.SetData32(0x1048, 0x440);
+  this->memory_.SetData32(0x104c, 0x600);
+
+  uint64_t fde_offset;
+  ASSERT_TRUE(this->eh_frame_->GetFdeOffsetSequential(0x540, &fde_offset));
+  EXPECT_EQ(0x600U, fde_offset);
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetFdeOffsetSequential_end_check) {
+  this->eh_frame_->TestSetFdeCount(2);
+  this->eh_frame_->TestSetEntriesDataOffset(0x100);
+  this->eh_frame_->TestSetEntriesEnd(0x1048);
+  this->eh_frame_->TestSetTableEncoding(DW_EH_PE_udata4);
+
+  this->memory_.SetData32(0x1040, 0x340);
+  this->memory_.SetData32(0x1044, 0x500);
+
+  this->memory_.SetData32(0x1048, 0x440);
+  this->memory_.SetData32(0x104c, 0x600);
+
+  uint64_t fde_offset;
+  ASSERT_FALSE(this->eh_frame_->GetFdeOffsetSequential(0x540, &fde_offset));
+  ASSERT_EQ(DWARF_ERROR_NONE, this->eh_frame_->last_error());
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetFdeOffsetFromPc_fail_fde_count) {
+  this->eh_frame_->TestSetFdeCount(0);
+
+  uint64_t fde_offset;
+  ASSERT_FALSE(this->eh_frame_->GetFdeOffsetFromPc(0x100, &fde_offset));
+  ASSERT_EQ(DWARF_ERROR_NONE, this->eh_frame_->last_error());
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetFdeOffsetFromPc_binary_search) {
+  this->eh_frame_->TestSetTableEntrySize(16);
+  this->eh_frame_->TestSetFdeCount(10);
+
+  typename DwarfEhFrame<TypeParam>::FdeInfo info;
+  info.pc = 0x550;
+  info.offset = 0x10500;
+  this->eh_frame_->TestSetFdeInfo(5, info);
+  info.pc = 0x750;
+  info.offset = 0x10700;
+  this->eh_frame_->TestSetFdeInfo(7, info);
+  info.pc = 0x850;
+  info.offset = 0x10800;
+  this->eh_frame_->TestSetFdeInfo(8, info);
+
+  uint64_t fde_offset;
+  ASSERT_TRUE(this->eh_frame_->GetFdeOffsetFromPc(0x800, &fde_offset));
+  EXPECT_EQ(0x10700U, fde_offset);
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetFdeOffsetFromPc_sequential_search) {
+  this->eh_frame_->TestSetFdeCount(10);
+  this->eh_frame_->TestSetTableEntrySize(0);
+
+  typename DwarfEhFrame<TypeParam>::FdeInfo info;
+  info.pc = 0x50;
+  info.offset = 0x10000;
+  this->eh_frame_->TestSetFdeInfo(0, info);
+  info.pc = 0x150;
+  info.offset = 0x10100;
+  this->eh_frame_->TestSetFdeInfo(1, info);
+  info.pc = 0x250;
+  info.offset = 0x10200;
+  this->eh_frame_->TestSetFdeInfo(2, info);
+
+  uint64_t fde_offset;
+  ASSERT_TRUE(this->eh_frame_->GetFdeOffsetFromPc(0x200, &fde_offset));
+  EXPECT_EQ(0x10100U, fde_offset);
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetCieFde32) {
+  // CIE 32 information.
+  this->memory_.SetData32(0xf000, 0x100);
+  this->memory_.SetData32(0xf004, 0);
+  this->memory_.SetData8(0xf008, 0x1);
+  this->memory_.SetData8(0xf009, '\0');
+  this->memory_.SetData8(0xf00a, 4);
+  this->memory_.SetData8(0xf00b, 8);
+  this->memory_.SetData8(0xf00c, 0x20);
+
+  // FDE 32 information.
+  this->memory_.SetData32(0x14000, 0x20);
+  this->memory_.SetData32(0x14004, 0x5004);
+  this->memory_.SetData32(0x14008, 0x9000);
+  this->memory_.SetData32(0x1400c, 0x100);
+
+  const DwarfFde* fde = this->eh_frame_->GetFdeFromOffset(0x14000);
+  ASSERT_TRUE(fde != nullptr);
+  EXPECT_EQ(0x14010U, fde->cfa_instructions_offset);
+  EXPECT_EQ(0x14024U, fde->cfa_instructions_end);
+  EXPECT_EQ(0x1d00cU, fde->pc_start);
+  EXPECT_EQ(0x1d10cU, fde->pc_end);
+  EXPECT_EQ(0xf000U, fde->cie_offset);
+  EXPECT_EQ(0U, fde->lsda_address);
+
+  ASSERT_TRUE(fde->cie != nullptr);
+  EXPECT_EQ(1U, fde->cie->version);
+  EXPECT_EQ(DW_EH_PE_sdata4, fde->cie->fde_address_encoding);
+  EXPECT_EQ(DW_EH_PE_omit, fde->cie->lsda_encoding);
+  EXPECT_EQ(0U, fde->cie->segment_size);
+  EXPECT_EQ(1U, fde->cie->augmentation_string.size());
+  EXPECT_EQ('\0', fde->cie->augmentation_string[0]);
+  EXPECT_EQ(0U, fde->cie->personality_handler);
+  EXPECT_EQ(0xf00dU, fde->cie->cfa_instructions_offset);
+  EXPECT_EQ(0xf104U, fde->cie->cfa_instructions_end);
+  EXPECT_EQ(4U, fde->cie->code_alignment_factor);
+  EXPECT_EQ(8, fde->cie->data_alignment_factor);
+  EXPECT_EQ(0x20U, fde->cie->return_address_register);
+}
+
+TYPED_TEST_P(DwarfEhFrameTest, GetCieFde64) {
+  // CIE 64 information.
+  this->memory_.SetData32(0x6000, 0xffffffff);
+  this->memory_.SetData64(0x6004, 0x100);
+  this->memory_.SetData64(0x600c, 0);
+  this->memory_.SetData8(0x6014, 0x1);
+  this->memory_.SetData8(0x6015, '\0');
+  this->memory_.SetData8(0x6016, 4);
+  this->memory_.SetData8(0x6017, 8);
+  this->memory_.SetData8(0x6018, 0x20);
+
+  // FDE 64 information.
+  this->memory_.SetData32(0x8000, 0xffffffff);
+  this->memory_.SetData64(0x8004, 0x200);
+  this->memory_.SetData64(0x800c, 0x200c);
+  this->memory_.SetData64(0x8014, 0x5000);
+  this->memory_.SetData64(0x801c, 0x300);
+
+  const DwarfFde* fde = this->eh_frame_->GetFdeFromOffset(0x8000);
+  ASSERT_TRUE(fde != nullptr);
+  EXPECT_EQ(0x8024U, fde->cfa_instructions_offset);
+  EXPECT_EQ(0x820cU, fde->cfa_instructions_end);
+  EXPECT_EQ(0xd01cU, fde->pc_start);
+  EXPECT_EQ(0xd31cU, fde->pc_end);
+  EXPECT_EQ(0x6000U, fde->cie_offset);
+  EXPECT_EQ(0U, fde->lsda_address);
+
+  ASSERT_TRUE(fde->cie != nullptr);
+  EXPECT_EQ(1U, fde->cie->version);
+  EXPECT_EQ(DW_EH_PE_sdata8, fde->cie->fde_address_encoding);
+  EXPECT_EQ(DW_EH_PE_omit, fde->cie->lsda_encoding);
+  EXPECT_EQ(0U, fde->cie->segment_size);
+  EXPECT_EQ(1U, fde->cie->augmentation_string.size());
+  EXPECT_EQ('\0', fde->cie->augmentation_string[0]);
+  EXPECT_EQ(0U, fde->cie->personality_handler);
+  EXPECT_EQ(0x6019U, fde->cie->cfa_instructions_offset);
+  EXPECT_EQ(0x610cU, fde->cie->cfa_instructions_end);
+  EXPECT_EQ(4U, fde->cie->code_alignment_factor);
+  EXPECT_EQ(8, fde->cie->data_alignment_factor);
+  EXPECT_EQ(0x20U, fde->cie->return_address_register);
+}
+
+REGISTER_TYPED_TEST_CASE_P(DwarfEhFrameTest, Init, GetFdeInfoFromIndex_expect_cache_fail,
+                           GetFdeInfoFromIndex_read_pcrel, GetFdeInfoFromIndex_read_datarel,
+                           GetFdeInfoFromIndex_cached, GetFdeOffsetBinary_verify,
+                           GetFdeOffsetSequential, GetFdeOffsetSequential_last_element,
+                           GetFdeOffsetSequential_end_check, GetFdeOffsetFromPc_fail_fde_count,
+                           GetFdeOffsetFromPc_binary_search, GetFdeOffsetFromPc_sequential_search,
+                           GetCieFde32, GetCieFde64);
+
+typedef ::testing::Types<uint32_t, uint64_t> DwarfEhFrameTestTypes;
+INSTANTIATE_TYPED_TEST_CASE_P(, DwarfEhFrameTest, DwarfEhFrameTestTypes);
diff --git a/libunwindstack/tests/ElfInterfaceTest.cpp b/libunwindstack/tests/ElfInterfaceTest.cpp
index c31903d..81cdaf5 100644
--- a/libunwindstack/tests/ElfInterfaceTest.cpp
+++ b/libunwindstack/tests/ElfInterfaceTest.cpp
@@ -67,6 +67,18 @@
   template <typename Ehdr, typename Phdr, typename Dyn, typename ElfInterfaceType>
   void SonameSize();
 
+  template <typename ElfType>
+  void InitHeadersEhFrameTest();
+
+  template <typename ElfType>
+  void InitHeadersDebugFrame();
+
+  template <typename ElfType>
+  void InitHeadersEhFrameFail();
+
+  template <typename ElfType>
+  void InitHeadersDebugFrameFail();
+
   MemoryFake memory_;
 };
 
@@ -571,3 +583,138 @@
 TEST_F(ElfInterfaceTest, elf64_soname_size) {
   SonameSize<Elf64_Ehdr, Elf64_Phdr, Elf64_Dyn, ElfInterface64>();
 }
+
+class MockElfInterface32 : public ElfInterface32 {
+ public:
+  MockElfInterface32(Memory* memory) : ElfInterface32(memory) {}
+  virtual ~MockElfInterface32() = default;
+
+  void TestSetEhFrameOffset(uint64_t offset) { eh_frame_offset_ = offset; }
+  void TestSetEhFrameSize(uint64_t size) { eh_frame_size_ = size; }
+
+  void TestSetDebugFrameOffset(uint64_t offset) { debug_frame_offset_ = offset; }
+  void TestSetDebugFrameSize(uint64_t size) { debug_frame_size_ = size; }
+};
+
+class MockElfInterface64 : public ElfInterface64 {
+ public:
+  MockElfInterface64(Memory* memory) : ElfInterface64(memory) {}
+  virtual ~MockElfInterface64() = default;
+
+  void TestSetEhFrameOffset(uint64_t offset) { eh_frame_offset_ = offset; }
+  void TestSetEhFrameSize(uint64_t size) { eh_frame_size_ = size; }
+
+  void TestSetDebugFrameOffset(uint64_t offset) { debug_frame_offset_ = offset; }
+  void TestSetDebugFrameSize(uint64_t size) { debug_frame_size_ = size; }
+};
+
+template <typename ElfType>
+void ElfInterfaceTest::InitHeadersEhFrameTest() {
+  ElfType elf(&memory_);
+
+  elf.TestSetEhFrameOffset(0x10000);
+  elf.TestSetEhFrameSize(0);
+  elf.TestSetDebugFrameOffset(0);
+  elf.TestSetDebugFrameSize(0);
+
+  memory_.SetMemory(0x10000,
+                    std::vector<uint8_t>{0x1, DW_EH_PE_udata2, DW_EH_PE_udata2, DW_EH_PE_udata2});
+  memory_.SetData32(0x10004, 0x500);
+  memory_.SetData32(0x10008, 250);
+
+  elf.InitHeaders();
+
+  EXPECT_FALSE(elf.eh_frame() == nullptr);
+  EXPECT_TRUE(elf.debug_frame() == nullptr);
+}
+
+TEST_F(ElfInterfaceTest, init_headers_eh_frame32) {
+  InitHeadersEhFrameTest<MockElfInterface32>();
+}
+
+TEST_F(ElfInterfaceTest, init_headers_eh_frame64) {
+  InitHeadersEhFrameTest<MockElfInterface64>();
+}
+
+template <typename ElfType>
+void ElfInterfaceTest::InitHeadersDebugFrame() {
+  ElfType elf(&memory_);
+
+  elf.TestSetEhFrameOffset(0);
+  elf.TestSetEhFrameSize(0);
+  elf.TestSetDebugFrameOffset(0x5000);
+  elf.TestSetDebugFrameSize(0x200);
+
+  memory_.SetData32(0x5000, 0xfc);
+  memory_.SetData32(0x5004, 0xffffffff);
+  memory_.SetData8(0x5008, 1);
+  memory_.SetData8(0x5009, '\0');
+
+  memory_.SetData32(0x5100, 0xfc);
+  memory_.SetData32(0x5104, 0);
+  memory_.SetData32(0x5108, 0x1500);
+  memory_.SetData32(0x510c, 0x200);
+
+  elf.InitHeaders();
+
+  EXPECT_TRUE(elf.eh_frame() == nullptr);
+  EXPECT_FALSE(elf.debug_frame() == nullptr);
+}
+
+TEST_F(ElfInterfaceTest, init_headers_debug_frame32) {
+  InitHeadersDebugFrame<MockElfInterface32>();
+}
+
+TEST_F(ElfInterfaceTest, init_headers_debug_frame64) {
+  InitHeadersDebugFrame<MockElfInterface64>();
+}
+
+template <typename ElfType>
+void ElfInterfaceTest::InitHeadersEhFrameFail() {
+  ElfType elf(&memory_);
+
+  elf.TestSetEhFrameOffset(0x1000);
+  elf.TestSetEhFrameSize(0x100);
+  elf.TestSetDebugFrameOffset(0);
+  elf.TestSetDebugFrameSize(0);
+
+  elf.InitHeaders();
+
+  EXPECT_TRUE(elf.eh_frame() == nullptr);
+  EXPECT_EQ(0U, elf.eh_frame_offset());
+  EXPECT_EQ(static_cast<uint64_t>(-1), elf.eh_frame_size());
+  EXPECT_TRUE(elf.debug_frame() == nullptr);
+}
+
+TEST_F(ElfInterfaceTest, init_headers_eh_frame32_fail) {
+  InitHeadersEhFrameFail<MockElfInterface32>();
+}
+
+TEST_F(ElfInterfaceTest, init_headers_eh_frame64_fail) {
+  InitHeadersEhFrameFail<MockElfInterface64>();
+}
+
+template <typename ElfType>
+void ElfInterfaceTest::InitHeadersDebugFrameFail() {
+  ElfType elf(&memory_);
+
+  elf.TestSetEhFrameOffset(0);
+  elf.TestSetEhFrameSize(0);
+  elf.TestSetDebugFrameOffset(0x1000);
+  elf.TestSetDebugFrameSize(0x100);
+
+  elf.InitHeaders();
+
+  EXPECT_TRUE(elf.eh_frame() == nullptr);
+  EXPECT_TRUE(elf.debug_frame() == nullptr);
+  EXPECT_EQ(0U, elf.debug_frame_offset());
+  EXPECT_EQ(static_cast<uint64_t>(-1), elf.debug_frame_size());
+}
+
+TEST_F(ElfInterfaceTest, init_headers_debug_frame32_fail) {
+  InitHeadersDebugFrameFail<MockElfInterface32>();
+}
+
+TEST_F(ElfInterfaceTest, init_headers_debug_frame64_fail) {
+  InitHeadersDebugFrameFail<MockElfInterface64>();
+}
diff --git a/libunwindstack/tests/MapInfoTest.cpp b/libunwindstack/tests/MapInfoTest.cpp
index c846ad7..6e47dc0 100644
--- a/libunwindstack/tests/MapInfoTest.cpp
+++ b/libunwindstack/tests/MapInfoTest.cpp
@@ -75,7 +75,7 @@
   // Make sure this test is valid.
   info.end = 0x101;
   memory.reset(info.CreateMemory(getpid()));
-  ASSERT_FALSE(info.CreateMemory(getpid()) == nullptr);
+  ASSERT_TRUE(memory.get() != nullptr);
 }
 
 // Verify that if the offset is non-zero but there is no elf at the offset,
@@ -212,8 +212,8 @@
   MapInfo info{.start = start, .end = start + 1024, .offset = 0, .name = ""};
 
   // The map contains garbage, but this should still produce an elf object.
-  Elf* elf = info.GetElf(getpid(), false);
-  ASSERT_TRUE(elf != nullptr);
+  std::unique_ptr<Elf> elf(info.GetElf(getpid(), false));
+  ASSERT_TRUE(elf.get() != nullptr);
   ASSERT_FALSE(elf->valid());
 
   ASSERT_EQ(0, munmap(map, 1024));
diff --git a/libunwindstack/tests/MemoryRangeTest.cpp b/libunwindstack/tests/MemoryRangeTest.cpp
index ee5ba01..469afcc 100644
--- a/libunwindstack/tests/MemoryRangeTest.cpp
+++ b/libunwindstack/tests/MemoryRangeTest.cpp
@@ -26,21 +26,13 @@
 
 #include "MemoryFake.h"
 
-class MemoryRangeTest : public ::testing::Test {
- protected:
-  void SetUp() override {
-    memory_ = new MemoryFake;
-  }
-
-  MemoryFake* memory_;
-};
-
-TEST_F(MemoryRangeTest, read) {
+TEST(MemoryRangeTest, read) {
   std::vector<uint8_t> src(1024);
   memset(src.data(), 0x4c, 1024);
-  memory_->SetMemory(9001, src);
+  MemoryFake* memory = new MemoryFake;
+  memory->SetMemory(9001, src);
 
-  MemoryRange range(memory_, 9001, 9001 + src.size());
+  MemoryRange range(memory, 9001, 9001 + src.size());
 
   std::vector<uint8_t> dst(1024);
   ASSERT_TRUE(range.Read(0, dst.data(), src.size()));
@@ -49,12 +41,13 @@
   }
 }
 
-TEST_F(MemoryRangeTest, read_near_limit) {
+TEST(MemoryRangeTest, read_near_limit) {
   std::vector<uint8_t> src(4096);
   memset(src.data(), 0x4c, 4096);
-  memory_->SetMemory(1000, src);
+  MemoryFake* memory = new MemoryFake;
+  memory->SetMemory(1000, src);
 
-  MemoryRange range(memory_, 1000, 2024);
+  MemoryRange range(memory, 1000, 2024);
 
   std::vector<uint8_t> dst(1024);
   ASSERT_TRUE(range.Read(1020, dst.data(), 4));
@@ -71,7 +64,7 @@
   ASSERT_TRUE(range.Read(1020, dst.data(), 4));
 }
 
-TEST_F(MemoryRangeTest, read_overflow) {
+TEST(MemoryRangeTest, read_overflow) {
   std::vector<uint8_t> buffer(100);
 
   std::unique_ptr<MemoryRange> overflow(new MemoryRange(new MemoryFakeAlwaysReadZero, 100, 200));
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index a64b3b1..efe1096 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -62,68 +62,6 @@
 // The maximum number of bytes to scan backwards for the EOCD start.
 static const uint32_t kMaxEOCDSearch = kMaxCommentLen + sizeof(EocdRecord);
 
-static const char* kErrorMessages[] = {
-  "Unknown return code.",
-  "Iteration ended",
-  "Zlib error",
-  "Invalid file",
-  "Invalid handle",
-  "Duplicate entries in archive",
-  "Empty archive",
-  "Entry not found",
-  "Invalid offset",
-  "Inconsistent information",
-  "Invalid entry name",
-  "I/O Error",
-  "File mapping failed"
-};
-
-static const int32_t kErrorMessageUpperBound = 0;
-
-static const int32_t kIterationEnd = -1;
-
-// We encountered a Zlib error when inflating a stream from this file.
-// Usually indicates file corruption.
-static const int32_t kZlibError = -2;
-
-// The input file cannot be processed as a zip archive. Usually because
-// it's too small, too large or does not have a valid signature.
-static const int32_t kInvalidFile = -3;
-
-// An invalid iteration / ziparchive handle was passed in as an input
-// argument.
-static const int32_t kInvalidHandle = -4;
-
-// The zip archive contained two (or possibly more) entries with the same
-// name.
-static const int32_t kDuplicateEntry = -5;
-
-// The zip archive contains no entries.
-static const int32_t kEmptyArchive = -6;
-
-// The specified entry was not found in the archive.
-static const int32_t kEntryNotFound = -7;
-
-// The zip archive contained an invalid local file header pointer.
-static const int32_t kInvalidOffset = -8;
-
-// The zip archive contained inconsistent entry information. This could
-// be because the central directory & local file header did not agree, or
-// if the actual uncompressed length or crc32 do not match their declared
-// values.
-static const int32_t kInconsistentInformation = -9;
-
-// An invalid entry name was encountered.
-static const int32_t kInvalidEntryName = -10;
-
-// An I/O related system call (read, lseek, ftruncate, map) failed.
-static const int32_t kIoError = -11;
-
-// We were not able to mmap the central directory or entry contents.
-static const int32_t kMmapFailed = -12;
-
-static const int32_t kErrorMessageLowerBound = -13;
-
 /*
  * A Read-only Zip archive.
  *
@@ -1106,11 +1044,17 @@
 }
 
 const char* ErrorCodeString(int32_t error_code) {
-  if (error_code > kErrorMessageLowerBound && error_code < kErrorMessageUpperBound) {
-    return kErrorMessages[error_code * -1];
+  // Make sure that the number of entries in kErrorMessages and ErrorCodes
+  // match.
+  static_assert((-kLastErrorCode + 1) == arraysize(kErrorMessages),
+                "(-kLastErrorCode + 1) != arraysize(kErrorMessages)");
+
+  const uint32_t idx = -error_code;
+  if (idx < arraysize(kErrorMessages)) {
+    return kErrorMessages[idx];
   }
 
-  return kErrorMessages[0];
+  return "Unknown return code";
 }
 
 int GetFileDescriptor(const ZipArchiveHandle handle) {
diff --git a/libziparchive/zip_archive_private.h b/libziparchive/zip_archive_private.h
index 971db4f..de93ecd 100644
--- a/libziparchive/zip_archive_private.h
+++ b/libziparchive/zip_archive_private.h
@@ -26,6 +26,69 @@
 
 #include <utils/FileMap.h>
 #include <ziparchive/zip_archive.h>
+#include "android-base/macros.h"
+
+static const char* kErrorMessages[] = {
+    "Success",
+    "Iteration ended",
+    "Zlib error",
+    "Invalid file",
+    "Invalid handle",
+    "Duplicate entries in archive",
+    "Empty archive",
+    "Entry not found",
+    "Invalid offset",
+    "Inconsistent information",
+    "Invalid entry name",
+    "I/O error",
+    "File mapping failed",
+};
+
+enum ErrorCodes : int32_t {
+  kIterationEnd = -1,
+
+  // We encountered a Zlib error when inflating a stream from this file.
+  // Usually indicates file corruption.
+  kZlibError = -2,
+
+  // The input file cannot be processed as a zip archive. Usually because
+  // it's too small, too large or does not have a valid signature.
+  kInvalidFile = -3,
+
+  // An invalid iteration / ziparchive handle was passed in as an input
+  // argument.
+  kInvalidHandle = -4,
+
+  // The zip archive contained two (or possibly more) entries with the same
+  // name.
+  kDuplicateEntry = -5,
+
+  // The zip archive contains no entries.
+  kEmptyArchive = -6,
+
+  // The specified entry was not found in the archive.
+  kEntryNotFound = -7,
+
+  // The zip archive contained an invalid local file header pointer.
+  kInvalidOffset = -8,
+
+  // The zip archive contained inconsistent entry information. This could
+  // be because the central directory & local file header did not agree, or
+  // if the actual uncompressed length or crc32 do not match their declared
+  // values.
+  kInconsistentInformation = -9,
+
+  // An invalid entry name was encountered.
+  kInvalidEntryName = -10,
+
+  // An I/O related system call (read, lseek, ftruncate, map) failed.
+  kIoError = -11,
+
+  // We were not able to mmap the central directory or entry contents.
+  kMmapFailed = -12,
+
+  kLastErrorCode = kMmapFailed,
+};
 
 class MappedZipFile {
  public:
diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc
index 42167dd..52099c3 100644
--- a/libziparchive/zip_archive_test.cc
+++ b/libziparchive/zip_archive_test.cc
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "zip_archive_private.h"
+
 #include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
@@ -708,8 +710,7 @@
   int32_t error_code = 0;
   ExtractEntryToMemory(invalid_csize, &entry, &error_code);
 
-  ASSERT_GT(0, error_code);
-  ASSERT_STREQ("Inconsistent information", ErrorCodeString(error_code));
+  ASSERT_EQ(kInconsistentInformation, error_code);
 
   std::vector<uint8_t> invalid_size = kDataDescriptorZipFile;
   invalid_csize[kSizeOffset] = 0xfe;
@@ -718,8 +719,17 @@
   entry.clear();
   ExtractEntryToMemory(invalid_csize, &entry, &error_code);
 
-  ASSERT_GT(0, error_code);
-  ASSERT_STREQ("Inconsistent information", ErrorCodeString(error_code));
+  ASSERT_EQ(kInconsistentInformation, error_code);
+}
+
+TEST(ziparchive, ErrorCodeString) {
+  ASSERT_STREQ("Success", ErrorCodeString(0));
+
+  // Out of bounds.
+  ASSERT_STREQ("Unknown return code", ErrorCodeString(1));
+  ASSERT_STREQ("Unknown return code", ErrorCodeString(-13));
+
+  ASSERT_STREQ("I/O error", ErrorCodeString(kIoError));
 }
 
 int main(int argc, char** argv) {