Merge changes I5b1a1ce0,I483a18f9

* changes:
  fastboot: Add 'get_staged' command
  fastboot: Add 'stage' command
diff --git a/adb/daemon/usb.cpp b/adb/daemon/usb.cpp
index 7e46b02..d3b2f3d 100644
--- a/adb/daemon/usb.cpp
+++ b/adb/daemon/usb.cpp
@@ -64,7 +64,7 @@
 
 #define FUNCTIONFS_ENDPOINT_ALLOC       _IOR('g', 231, __u32)
 
-static constexpr size_t ENDPOINT_ALLOC_RETRIES = 2;
+static constexpr size_t ENDPOINT_ALLOC_RETRIES = 10;
 
 static int dummy_fd = -1;
 
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index b705e27..568879e 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -78,6 +78,14 @@
     }                                                                           \
   } while (0)
 
+#define ASSERT_NOT_MATCH(str, pattern)                                                      \
+  do {                                                                                      \
+    std::regex r((pattern));                                                                \
+    if (std::regex_search((str), r)) {                                                      \
+      FAIL() << "regex mismatch: expected to not find " << (pattern) << " in: \n" << (str); \
+    }                                                                                       \
+  } while (0)
+
 static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd) {
   intercept_fd->reset(socket_local_client(kTombstonedInterceptSocketName,
                                           ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
@@ -226,12 +234,14 @@
     FAIL() << "failed to wait for crasher: " << strerror(errno);
   }
 
-  if (WIFEXITED(status)) {
-    FAIL() << "crasher failed to exec: " << strerror(WEXITSTATUS(status));
-  } else if (!WIFSIGNALED(status)) {
-    FAIL() << "crasher didn't terminate via a signal";
+  if (signo == 0) {
+    ASSERT_TRUE(WIFEXITED(status));
+    ASSERT_EQ(0, WEXITSTATUS(signo));
+  } else {
+    ASSERT_FALSE(WIFEXITED(status));
+    ASSERT_TRUE(WIFSIGNALED(status)) << "crasher didn't terminate via a signal";
+    ASSERT_EQ(signo, WTERMSIG(status));
   }
-  ASSERT_EQ(signo, WTERMSIG(status));
   crasher_pid = -1;
 }
 
@@ -336,6 +346,26 @@
   ASSERT_MATCH(result, R"(Abort message: 'abort message goes here')");
 }
 
+TEST_F(CrasherTest, abort_message_backtrace) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    android_set_abort_message("not actually aborting");
+    raise(DEBUGGER_SIGNAL);
+    exit(0);
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(0);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_NOT_MATCH(result, R"(Abort message:)");
+}
+
 TEST_F(CrasherTest, intercept_timeout) {
   int intercept_result;
   unique_fd output_fd;
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index cd00dc5..b70554f 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -389,8 +389,9 @@
 
   log_signal_summary(signal_number, info);
 
-  // Populate si_value with the abort message address, if found.
-  if (abort_message) {
+  // If this was a fatal crash, populate si_value with the abort message address if possible.
+  // Note that applications can set an abort message without aborting.
+  if (abort_message && signal_number != DEBUGGER_SIGNAL) {
     info->si_value.sival_ptr = abort_message;
   }
 
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 0d59901..982545c 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -501,8 +501,7 @@
     return bdata;
 }
 
-static void* unzip_file(ZipArchiveHandle zip, const char* entry_name, int64_t* sz)
-{
+static void* unzip_file(ZipArchiveHandle zip, const char* entry_name, int64_t* sz) {
     ZipString zip_entry_name(entry_name);
     ZipEntry zip_entry;
     if (FindEntry(zip, zip_entry_name, &zip_entry) != 0) {
@@ -512,6 +511,7 @@
 
     *sz = zip_entry.uncompressed_length;
 
+    fprintf(stderr, "extracting %s (%" PRId64 " MB)...\n", entry_name, *sz / 1024 / 1024);
     uint8_t* data = reinterpret_cast<uint8_t*>(malloc(zip_entry.uncompressed_length));
     if (data == nullptr) {
         fprintf(stderr, "failed to allocate %" PRId64 " bytes for '%s'\n", *sz, entry_name);
@@ -560,22 +560,39 @@
     return "";
 }
 
+static int make_temporary_fd() {
+    // TODO: reimplement to avoid leaking a FILE*.
+    return fileno(tmpfile());
+}
+
 #else
 
+static std::string make_temporary_template() {
+    const char* tmpdir = getenv("TMPDIR");
+    if (tmpdir == nullptr) tmpdir = P_tmpdir;
+    return std::string(tmpdir) + "/fastboot_userdata_XXXXXX";
+}
+
 static std::string make_temporary_directory() {
-    const char *tmpdir = getenv("TMPDIR");
-    if (tmpdir == nullptr) {
-        tmpdir = P_tmpdir;
-    }
-    std::string result = std::string(tmpdir) + "/fastboot_userdata_XXXXXX";
-    if (mkdtemp(&result[0]) == NULL) {
-        fprintf(stderr, "Unable to create temporary directory: %s\n",
-            strerror(errno));
+    std::string result(make_temporary_template());
+    if (mkdtemp(&result[0]) == nullptr) {
+        fprintf(stderr, "Unable to create temporary directory: %s\n", strerror(errno));
         return "";
     }
     return result;
 }
 
+static int make_temporary_fd() {
+    std::string path_template(make_temporary_template());
+    int fd = mkstemp(&path_template[0]);
+    if (fd == -1) {
+        fprintf(stderr, "Unable to create temporary file: %s\n", strerror(errno));
+        return -1;
+    }
+    unlink(path_template.c_str());
+    return fd;
+}
+
 #endif
 
 static std::string create_fbemarker_tmpdir() {
@@ -610,8 +627,8 @@
 }
 
 static int unzip_to_file(ZipArchiveHandle zip, char* entry_name) {
-    FILE* fp = tmpfile();
-    if (fp == nullptr) {
+    unique_fd fd(make_temporary_fd());
+    if (fd == -1) {
         fprintf(stderr, "failed to create temporary file for '%s': %s\n",
                 entry_name, strerror(errno));
         return -1;
@@ -621,21 +638,20 @@
     ZipEntry zip_entry;
     if (FindEntry(zip, zip_entry_name, &zip_entry) != 0) {
         fprintf(stderr, "archive does not contain '%s'\n", entry_name);
-        fclose(fp);
         return -1;
     }
 
-    int fd = fileno(fp);
+    fprintf(stderr, "extracting %s (%" PRIu32 " MB)...\n", entry_name,
+            zip_entry.uncompressed_length / 1024 / 1024);
     int error = ExtractEntryToFile(zip, &zip_entry, fd);
     if (error != 0) {
         fprintf(stderr, "failed to extract '%s': %s\n", entry_name, ErrorCodeString(error));
-        fclose(fp);
         return -1;
     }
 
     lseek(fd, 0, SEEK_SET);
     // TODO: We're leaking 'fp' here.
-    return fd;
+    return fd.release();
 }
 
 static char *strip(char *s)
@@ -1158,7 +1174,7 @@
             }
             flash_buf(partition.c_str(), &buf);
             /* not closing the fd here since the sparse code keeps the fd around
-             * but hasn't mmaped data yet. The tmpfile will get cleaned up when the
+             * but hasn't mmaped data yet. The temporary file will get cleaned up when the
              * program exits.
              */
         };
@@ -1419,7 +1435,8 @@
         return;
     }
 
-    fd = fileno(tmpfile());
+    fd = make_temporary_fd();
+    if (fd == -1) return;
 
     unsigned eraseBlkSize, logicalBlkSize;
     eraseBlkSize = fb_get_flash_block_size(transport, "erase-block-size");
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index e3d4f87..247768a 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -1367,7 +1367,8 @@
 
         std::string mount_point;
         if (system_root && !strcmp(fstab->recs[i].mount_point, "/")) {
-            mount_point = "system";
+            // In AVB, the dm device name is vroot instead of system.
+            mount_point = fs_mgr_is_avb(&fstab->recs[i]) ? "vroot" : "system";
         } else {
             mount_point = basename(fstab->recs[i].mount_point);
         }
@@ -1386,6 +1387,10 @@
 
         status = &buffer[io->data_start + sizeof(struct dm_target_spec)];
 
+        // To be consistent in vboot 1.0 and vboot 2.0 (AVB), change the mount_point
+        // back to 'system' for the callback. So it has property [partition.system.verified]
+        // instead of [partition.vroot.verified].
+        if (mount_point == "vroot") mount_point = "system";
         if (*status == 'C' || *status == 'V') {
             callback(&fstab->recs[i], mount_point.c_str(), mode, *status);
         }
diff --git a/init/Android.mk b/init/Android.mk
index 974d400..de3d076 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -93,7 +93,6 @@
     reboot.cpp \
     signal_handler.cpp \
     ueventd.cpp \
-    ueventd_parser.cpp \
     watchdogd.cpp \
 
 LOCAL_MODULE:= init
diff --git a/init/action.cpp b/init/action.cpp
index 8d49e2a..21abe02 100644
--- a/init/action.cpp
+++ b/init/action.cpp
@@ -58,12 +58,7 @@
         return false;
     }
 
-    if (args.empty()) {
-        *err = "command needed, but not provided";
-        return false;
-    }
-
-    auto function = function_map_->FindFunction(args[0], args.size() - 1, err);
+    auto function = function_map_->FindFunction(args, err);
     if (!function) {
         return false;
     }
@@ -204,17 +199,19 @@
     return found;
 }
 
-bool Action::CheckEventTrigger(const std::string& trigger) const {
-    return !event_trigger_.empty() &&
-        trigger == event_trigger_ &&
-        CheckPropertyTriggers();
+bool Action::CheckEvent(const EventTrigger& event_trigger) const {
+    return event_trigger == event_trigger_ && CheckPropertyTriggers();
 }
 
-bool Action::CheckPropertyTrigger(const std::string& name,
-                                  const std::string& value) const {
+bool Action::CheckEvent(const PropertyChange& property_change) const {
+    const auto& [name, value] = property_change;
     return event_trigger_.empty() && CheckPropertyTriggers(name, value);
 }
 
+bool Action::CheckEvent(const BuiltinAction& builtin_action) const {
+    return this == builtin_action;
+}
+
 std::string Action::BuildTriggersString() const {
     std::vector<std::string> triggers;
 
@@ -238,41 +235,6 @@
     }
 }
 
-class EventTrigger : public Trigger {
-public:
-    explicit EventTrigger(const std::string& trigger) : trigger_(trigger) {
-    }
-    bool CheckTriggers(const Action& action) const override {
-        return action.CheckEventTrigger(trigger_);
-    }
-private:
-    const std::string trigger_;
-};
-
-class PropertyTrigger : public Trigger {
-public:
-    PropertyTrigger(const std::string& name, const std::string& value)
-        : name_(name), value_(value) {
-    }
-    bool CheckTriggers(const Action& action) const override {
-        return action.CheckPropertyTrigger(name_, value_);
-    }
-private:
-    const std::string name_;
-    const std::string value_;
-};
-
-class BuiltinTrigger : public Trigger {
-public:
-    explicit BuiltinTrigger(Action* action) : action_(action) {
-    }
-    bool CheckTriggers(const Action& action) const override {
-        return action_ == &action;
-    }
-private:
-    const Action* action_;
-};
-
 ActionManager::ActionManager() : current_command_(0) {
 }
 
@@ -286,16 +248,15 @@
 }
 
 void ActionManager::QueueEventTrigger(const std::string& trigger) {
-    trigger_queue_.push(std::make_unique<EventTrigger>(trigger));
+    event_queue_.emplace(trigger);
 }
 
-void ActionManager::QueuePropertyTrigger(const std::string& name,
-                                         const std::string& value) {
-    trigger_queue_.push(std::make_unique<PropertyTrigger>(name, value));
+void ActionManager::QueuePropertyChange(const std::string& name, const std::string& value) {
+    event_queue_.emplace(std::make_pair(name, value));
 }
 
-void ActionManager::QueueAllPropertyTriggers() {
-    QueuePropertyTrigger("", "");
+void ActionManager::QueueAllPropertyActions() {
+    QueuePropertyChange("", "");
 }
 
 void ActionManager::QueueBuiltinAction(BuiltinFunction func, const std::string& name) {
@@ -308,19 +269,20 @@
 
     action->AddCommand(func, name_vector, 0);
 
-    trigger_queue_.push(std::make_unique<BuiltinTrigger>(action.get()));
+    event_queue_.emplace(action.get());
     actions_.emplace_back(std::move(action));
 }
 
 void ActionManager::ExecuteOneCommand() {
-    // Loop through the trigger queue until we have an action to execute
-    while (current_executing_actions_.empty() && !trigger_queue_.empty()) {
+    // Loop through the event queue until we have an action to execute
+    while (current_executing_actions_.empty() && !event_queue_.empty()) {
         for (const auto& action : actions_) {
-            if (trigger_queue_.front()->CheckTriggers(*action)) {
+            if (std::visit([&action](const auto& event) { return action->CheckEvent(event); },
+                           event_queue_.front())) {
                 current_executing_actions_.emplace(action.get());
             }
         }
-        trigger_queue_.pop();
+        event_queue_.pop();
     }
 
     if (current_executing_actions_.empty()) {
@@ -354,7 +316,7 @@
 }
 
 bool ActionManager::HasMoreCommands() const {
-    return !current_executing_actions_.empty() || !trigger_queue_.empty();
+    return !current_executing_actions_.empty() || !event_queue_.empty();
 }
 
 void ActionManager::DumpState() const {
diff --git a/init/action.h b/init/action.h
index c528a7c..d006c50 100644
--- a/init/action.h
+++ b/init/action.h
@@ -20,6 +20,7 @@
 #include <map>
 #include <queue>
 #include <string>
+#include <variant>
 #include <vector>
 
 #include "builtins.h"
@@ -41,6 +42,10 @@
     int line_;
 };
 
+using EventTrigger = std::string;
+using PropertyChange = std::pair<std::string, std::string>;
+using BuiltinAction = class Action*;
+
 class Action {
   public:
     explicit Action(bool oneshot, const std::string& filename, int line);
@@ -52,9 +57,9 @@
     std::size_t NumCommands() const;
     void ExecuteOneCommand(std::size_t command) const;
     void ExecuteAllCommands() const;
-    bool CheckEventTrigger(const std::string& trigger) const;
-    bool CheckPropertyTrigger(const std::string& name,
-                              const std::string& value) const;
+    bool CheckEvent(const EventTrigger& event_trigger) const;
+    bool CheckEvent(const PropertyChange& property_change) const;
+    bool CheckEvent(const BuiltinAction& builtin_action) const;
     std::string BuildTriggersString() const;
     void DumpState() const;
 
@@ -81,12 +86,6 @@
     static const KeywordMap<BuiltinFunction>* function_map_;
 };
 
-class Trigger {
-public:
-    virtual ~Trigger() { }
-    virtual bool CheckTriggers(const Action& action) const = 0;
-};
-
 class ActionManager {
   public:
     static ActionManager& GetInstance();
@@ -96,8 +95,8 @@
 
     void AddAction(std::unique_ptr<Action> action);
     void QueueEventTrigger(const std::string& trigger);
-    void QueuePropertyTrigger(const std::string& name, const std::string& value);
-    void QueueAllPropertyTriggers();
+    void QueuePropertyChange(const std::string& name, const std::string& value);
+    void QueueAllPropertyActions();
     void QueueBuiltinAction(BuiltinFunction func, const std::string& name);
     void ExecuteOneCommand();
     bool HasMoreCommands() const;
@@ -108,7 +107,7 @@
     void operator=(ActionManager const&) = delete;
 
     std::vector<std::unique_ptr<Action>> actions_;
-    std::queue<std::unique_ptr<Trigger>> trigger_queue_;
+    std::queue<std::variant<EventTrigger, PropertyChange, BuiltinAction>> event_queue_;
     std::queue<const Action*> current_executing_actions_;
     std::size_t current_command_;
 };
diff --git a/init/devices.cpp b/init/devices.cpp
index 6e13863..07d28d0 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -20,8 +20,10 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <fnmatch.h>
+#include <grp.h>
 #include <libgen.h>
 #include <linux/netlink.h>
+#include <pwd.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -49,7 +51,8 @@
 #include <selinux/label.h>
 #include <selinux/selinux.h>
 
-#include "ueventd_parser.h"
+#include "keyword_map.h"
+#include "ueventd.h"
 #include "util.h"
 
 extern struct selabel_handle *sehandle;
@@ -103,6 +106,137 @@
 std::vector<Permissions> dev_permissions;
 std::vector<SysfsPermissions> sysfs_permissions;
 
+bool ParsePermissionsLine(std::vector<std::string>&& args, std::string* err, bool is_sysfs) {
+    if (is_sysfs && args.size() != 5) {
+        *err = "/sys/ lines must have 5 entries";
+        return false;
+    }
+
+    if (!is_sysfs && args.size() != 4) {
+        *err = "/dev/ lines must have 4 entries";
+        return false;
+    }
+
+    auto it = args.begin();
+    const std::string& name = *it++;
+
+    std::string sysfs_attribute;
+    if (is_sysfs) sysfs_attribute = *it++;
+
+    // args is now common to both sys and dev entries and contains: <perm> <uid> <gid>
+    std::string& perm_string = *it++;
+    char* end_pointer = 0;
+    mode_t perm = strtol(perm_string.c_str(), &end_pointer, 8);
+    if (end_pointer == nullptr || *end_pointer != '\0') {
+        *err = "invalid mode '" + perm_string + "'";
+        return false;
+    }
+
+    std::string& uid_string = *it++;
+    passwd* pwd = getpwnam(uid_string.c_str());
+    if (!pwd) {
+        *err = "invalid uid '" + uid_string + "'";
+        return false;
+    }
+    uid_t uid = pwd->pw_uid;
+
+    std::string& gid_string = *it++;
+    struct group* grp = getgrnam(gid_string.c_str());
+    if (!grp) {
+        *err = "invalid gid '" + gid_string + "'";
+        return false;
+    }
+    gid_t gid = grp->gr_gid;
+
+    if (is_sysfs) {
+        sysfs_permissions.emplace_back(name, sysfs_attribute, perm, uid, gid);
+    } else {
+        dev_permissions.emplace_back(name, perm, uid, gid);
+    }
+    return true;
+}
+
+// TODO: Move this to be a member variable of a future devices class.
+static std::vector<Subsystem> subsystems;
+
+std::string Subsystem::ParseDevPath(uevent* uevent) const {
+    std::string devname = devname_source_ == DevnameSource::DEVNAME_UEVENT_DEVNAME
+                              ? uevent->device_name
+                              : android::base::Basename(uevent->path);
+
+    return dir_name_ + "/" + devname;
+}
+
+bool SubsystemParser::ParseSection(std::vector<std::string>&& args, const std::string& filename,
+                                   int line, std::string* err) {
+    if (args.size() != 2) {
+        *err = "subsystems must have exactly one name";
+        return false;
+    }
+
+    if (std::find(subsystems.begin(), subsystems.end(), args[1]) != subsystems.end()) {
+        *err = "ignoring duplicate subsystem entry";
+        return false;
+    }
+
+    subsystem_.name_ = args[1];
+
+    return true;
+}
+
+bool SubsystemParser::ParseDevName(std::vector<std::string>&& args, std::string* err) {
+    if (args[1] == "uevent_devname") {
+        subsystem_.devname_source_ = Subsystem::DevnameSource::DEVNAME_UEVENT_DEVNAME;
+        return true;
+    }
+    if (args[1] == "uevent_devpath") {
+        subsystem_.devname_source_ = Subsystem::DevnameSource::DEVNAME_UEVENT_DEVPATH;
+        return true;
+    }
+
+    *err = "invalid devname '" + args[1] + "'";
+    return false;
+}
+
+bool SubsystemParser::ParseDirName(std::vector<std::string>&& args, std::string* err) {
+    if (args[1].front() != '/') {
+        *err = "dirname '" + args[1] + " ' does not start with '/'";
+        return false;
+    }
+
+    subsystem_.dir_name_ = args[1];
+    return true;
+}
+
+bool SubsystemParser::ParseLineSection(std::vector<std::string>&& args, int line, std::string* err) {
+    using OptionParser =
+        bool (SubsystemParser::*)(std::vector<std::string> && args, std::string * err);
+    static class OptionParserMap : public KeywordMap<OptionParser> {
+      private:
+        const Map& map() const override {
+            // clang-format off
+            static const Map option_parsers = {
+                {"devname",     {1,     1,      &SubsystemParser::ParseDevName}},
+                {"dirname",     {1,     1,      &SubsystemParser::ParseDirName}},
+            };
+            // clang-format on
+            return option_parsers;
+        }
+    } parser_map;
+
+    auto parser = parser_map.FindFunction(args, err);
+
+    if (!parser) {
+        return false;
+    }
+
+    return (this->*parser)(std::move(args), err);
+}
+
+void SubsystemParser::EndSection() {
+    subsystems.emplace_back(std::move(subsystem_));
+}
+
 static void fixup_sys_permissions(const std::string& upath, const std::string& subsystem) {
     // upaths omit the "/sys" that paths in this list
     // contain, so we prepend it...
@@ -483,32 +617,9 @@
     // if it's not a /dev device, nothing to do
     if (uevent->major < 0 || uevent->minor < 0) return;
 
-    std::string name = android::base::Basename(uevent->path);
-    ueventd_subsystem* subsystem = ueventd_subsystem_find_by_name(uevent->subsystem.c_str());
-
     std::string devpath;
 
-    if (subsystem) {
-        std::string devname;
-
-        switch (subsystem->devname_src) {
-        case DEVNAME_UEVENT_DEVNAME:
-            devname = uevent->device_name;
-            break;
-
-        case DEVNAME_UEVENT_DEVPATH:
-            devname = name;
-            break;
-
-        default:
-            LOG(ERROR) << uevent->subsystem << " subsystem's devpath option is not set; ignoring event";
-            return;
-        }
-
-        // TODO: Remove std::string()
-        devpath = std::string(subsystem->dirname) + "/" + devname;
-        mkdir_recursive(android::base::Dirname(devpath), 0755);
-    } else if (android::base::StartsWith(uevent->subsystem, "usb")) {
+    if (android::base::StartsWith(uevent->subsystem, "usb")) {
         if (uevent->subsystem == "usb") {
             if (!uevent->device_name.empty()) {
                 devpath = "/dev/" + uevent->device_name;
@@ -520,15 +631,19 @@
                 int device_id = uevent->minor % 128 + 1;
                 devpath = android::base::StringPrintf("/dev/bus/usb/%03d/%03d", bus_id, device_id);
             }
-            mkdir_recursive(android::base::Dirname(devpath), 0755);
         } else {
             // ignore other USB events
             return;
         }
+    } else if (auto subsystem = std::find(subsystems.begin(), subsystems.end(), uevent->subsystem);
+               subsystem != subsystems.end()) {
+        devpath = subsystem->ParseDevPath(uevent);
     } else {
-        devpath = "/dev/" + name;
+        devpath = "/dev/" + android::base::Basename(uevent->path);
     }
 
+    mkdir_recursive(android::base::Dirname(devpath), 0755);
+
     auto links = get_character_device_symlinks(uevent);
 
     handle_device(uevent->action, devpath, 0, uevent->major, uevent->minor, links);
diff --git a/init/devices.h b/init/devices.h
index 2cbae66..647b4c4 100644
--- a/init/devices.h
+++ b/init/devices.h
@@ -24,6 +24,8 @@
 #include <string>
 #include <vector>
 
+#include "init_parser.h"
+
 enum coldboot_action_t {
     // coldboot continues without creating the device for the uevent
     COLDBOOT_CONTINUE = 0,
@@ -83,9 +85,45 @@
     const std::string attribute_;
 };
 
-extern std::vector<Permissions> dev_permissions;
-extern std::vector<SysfsPermissions> sysfs_permissions;
+class Subsystem {
+  public:
+    friend class SubsystemParser;
 
+    Subsystem() {}
+
+    // Returns the full path for a uevent of a device that is a member of this subsystem,
+    // according to the rules parsed from ueventd.rc
+    std::string ParseDevPath(uevent* uevent) const;
+
+    bool operator==(const std::string& string_name) { return name_ == string_name; }
+
+  private:
+    enum class DevnameSource {
+        DEVNAME_UEVENT_DEVNAME,
+        DEVNAME_UEVENT_DEVPATH,
+    };
+
+    std::string name_;
+    std::string dir_name_ = "/dev";
+    DevnameSource devname_source_;
+};
+
+class SubsystemParser : public SectionParser {
+  public:
+    SubsystemParser() {}
+    bool ParseSection(std::vector<std::string>&& args, const std::string& filename, int line,
+                      std::string* err) override;
+    bool ParseLineSection(std::vector<std::string>&& args, int line, std::string* err) override;
+    void EndSection() override;
+
+  private:
+    bool ParseDevName(std::vector<std::string>&& args, std::string* err);
+    bool ParseDirName(std::vector<std::string>&& args, std::string* err);
+
+    Subsystem subsystem_;
+};
+
+bool ParsePermissionsLine(std::vector<std::string>&& args, std::string* err, bool is_sysfs);
 typedef std::function<coldboot_action_t(struct uevent* uevent)> coldboot_callback;
 extern coldboot_action_t handle_device_fd(coldboot_callback fn = nullptr);
 extern void device_init(const char* path = nullptr, coldboot_callback fn = nullptr);
diff --git a/init/init.cpp b/init/init.cpp
index e91faea..99ce5e6 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -164,8 +164,8 @@
     // waiting on a property.
     if (name == "sys.powerctl") HandlePowerctlMessage(value);
 
-    if (property_triggers_enabled)
-        ActionManager::GetInstance().QueuePropertyTrigger(name, value);
+    if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value);
+
     if (waiting_for_prop) {
         if (wait_prop_name == name && wait_prop_value == value) {
             wait_prop_name.clear();
@@ -535,7 +535,7 @@
 static int queue_property_triggers_action(const std::vector<std::string>& args)
 {
     ActionManager::GetInstance().QueueBuiltinAction(property_enable_triggers_action, "enable_property_trigger");
-    ActionManager::GetInstance().QueueAllPropertyTriggers();
+    ActionManager::GetInstance().QueueAllPropertyActions();
     return 0;
 }
 
diff --git a/init/init_first_stage.cpp b/init/init_first_stage.cpp
index 0bacd9c..43f1c15 100644
--- a/init/init_first_stage.cpp
+++ b/init/init_first_stage.cpp
@@ -232,7 +232,7 @@
 
 bool FirstStageMountVBootV1::GetRequiredDevices(std::set<std::string>* out_devices_partition_names,
                                                 bool* out_need_dm_verity) {
-    std::string meta_partition;
+    std::string verity_loc_device;
     *out_need_dm_verity = false;
 
     for (auto fstab_rec : mount_fstab_recs_) {
@@ -245,29 +245,28 @@
         if (fs_mgr_is_verified(fstab_rec)) {
             *out_need_dm_verity = true;
         }
-        // Checks if verity metadata is on a separate partition and get partition
-        // name from the end of the ->verity_loc path. Verity state is not partition
-        // specific, so there must be only one additional partition that carries
-        // verity state.
+        // Checks if verity metadata is on a separate partition. Note that it is
+        // not partition specific, so there must be only one additional partition
+        // that carries verity state.
         if (fstab_rec->verity_loc) {
-            if (meta_partition.empty()) {
-                meta_partition = basename(fstab_rec->verity_loc);
-            } else if (meta_partition != fstab_rec->verity_loc) {
-                LOG(ERROR) << "More than one meta partition found: " << meta_partition << ", "
-                           << basename(fstab_rec->verity_loc);
+            if (verity_loc_device.empty()) {
+                verity_loc_device = fstab_rec->verity_loc;
+            } else if (verity_loc_device != fstab_rec->verity_loc) {
+                LOG(ERROR) << "More than one verity_loc found: " << verity_loc_device << ", "
+                           << fstab_rec->verity_loc;
                 return false;
             }
         }
     }
 
-    // Includes those fstab partitions and meta_partition (if any).
+    // Includes the partition names of fstab records and verity_loc_device (if any).
     // Notes that fstab_rec->blk_device has A/B suffix updated by fs_mgr when A/B is used.
     for (auto fstab_rec : mount_fstab_recs_) {
         out_devices_partition_names->emplace(basename(fstab_rec->blk_device));
     }
 
-    if (!meta_partition.empty()) {
-        out_devices_partition_names->emplace(std::move(meta_partition));
+    if (!verity_loc_device.empty()) {
+        out_devices_partition_names->emplace(basename(verity_loc_device.c_str()));
     }
 
     return true;
diff --git a/init/init_parser.cpp b/init/init_parser.cpp
index 5c7af79..620367a 100644
--- a/init/init_parser.cpp
+++ b/init/init_parser.cpp
@@ -20,6 +20,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 
 #include "parser.h"
 #include "util.h"
@@ -37,13 +38,16 @@
     section_parsers_[name] = std::move(parser);
 }
 
+void Parser::AddSingleLineParser(const std::string& prefix, LineCallback callback) {
+    line_callbacks_.emplace_back(prefix, callback);
+}
+
 void Parser::ParseData(const std::string& filename, const std::string& data) {
     //TODO: Use a parser with const input and remove this copy
     std::vector<char> data_copy(data.begin(), data.end());
     data_copy.push_back('\0');
 
     parse_state state;
-    state.filename = filename.c_str();
     state.line = 0;
     state.ptr = &data_copy[0];
     state.nexttoken = 0;
@@ -63,21 +67,34 @@
             if (args.empty()) {
                 break;
             }
+            // If we have a line matching a prefix we recognize, call its callback and unset any
+            // current section parsers.  This is meant for /sys/ and /dev/ line entries for uevent.
+            for (const auto& [prefix, callback] : line_callbacks_) {
+                if (android::base::StartsWith(args[0], prefix.c_str())) {
+                    if (section_parser) section_parser->EndSection();
+
+                    std::string ret_err;
+                    if (!callback(std::move(args), &ret_err)) {
+                        LOG(ERROR) << filename << ": " << state.line << ": " << ret_err;
+                    }
+                    section_parser = nullptr;
+                    break;
+                }
+            }
             if (section_parsers_.count(args[0])) {
                 if (section_parser) {
                     section_parser->EndSection();
                 }
                 section_parser = section_parsers_[args[0]].get();
                 std::string ret_err;
-                if (!section_parser->ParseSection(std::move(args), state.filename, state.line,
-                                                  &ret_err)) {
-                    parse_error(&state, "%s\n", ret_err.c_str());
+                if (!section_parser->ParseSection(std::move(args), filename, state.line, &ret_err)) {
+                    LOG(ERROR) << filename << ": " << state.line << ": " << ret_err;
                     section_parser = nullptr;
                 }
             } else if (section_parser) {
                 std::string ret_err;
                 if (!section_parser->ParseLineSection(std::move(args), state.line, &ret_err)) {
-                    parse_error(&state, "%s\n", ret_err.c_str());
+                    LOG(ERROR) << filename << ": " << state.line << ": " << ret_err;
                 }
             }
             args.clear();
diff --git a/init/init_parser.h b/init/init_parser.h
index fe70d7d..bd8a178 100644
--- a/init/init_parser.h
+++ b/init/init_parser.h
@@ -57,24 +57,26 @@
 
 class Parser {
   public:
+    //  LineCallback is the type for callbacks that can parse a line starting with a given prefix.
+    //
+    //  They take the form of bool Callback(std::vector<std::string>&& args, std::string* err)
+    //
+    //  Similar to ParseSection() and ParseLineSection(), this function returns bool with false
+    //  indicating a failure and has an std::string* err parameter into which an error string can
+    //  be written.
+    using LineCallback = std::function<bool(std::vector<std::string>&&, std::string*)>;
+
     static Parser& GetInstance();
 
     // Exposed for testing
     Parser();
 
     bool ParseConfig(const std::string& path);
-    void AddSectionParser(const std::string& name,
-                          std::unique_ptr<SectionParser> parser);
-
-    void set_is_system_etc_init_loaded(bool loaded) {
-        is_system_etc_init_loaded_ = loaded;
-    }
-    void set_is_vendor_etc_init_loaded(bool loaded) {
-        is_vendor_etc_init_loaded_ = loaded;
-    }
-    void set_is_odm_etc_init_loaded(bool loaded) {
-        is_odm_etc_init_loaded_ = loaded;
-    }
+    void AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser);
+    void AddSingleLineParser(const std::string& prefix, LineCallback callback);
+    void set_is_system_etc_init_loaded(bool loaded) { is_system_etc_init_loaded_ = loaded; }
+    void set_is_vendor_etc_init_loaded(bool loaded) { is_vendor_etc_init_loaded_ = loaded; }
+    void set_is_odm_etc_init_loaded(bool loaded) { is_odm_etc_init_loaded_ = loaded; }
     bool is_system_etc_init_loaded() { return is_system_etc_init_loaded_; }
     bool is_vendor_etc_init_loaded() { return is_vendor_etc_init_loaded_; }
     bool is_odm_etc_init_loaded() { return is_odm_etc_init_loaded_; }
@@ -85,6 +87,7 @@
     bool ParseConfigDir(const std::string& path);
 
     std::map<std::string, std::unique_ptr<SectionParser>> section_parsers_;
+    std::vector<std::pair<std::string, LineCallback>> line_callbacks_;
     bool is_system_etc_init_loaded_ = false;
     bool is_vendor_etc_init_loaded_ = false;
     bool is_odm_etc_init_loaded_ = false;
diff --git a/init/keyword_map.h b/init/keyword_map.h
index 2b91260..88bad01 100644
--- a/init/keyword_map.h
+++ b/init/keyword_map.h
@@ -31,11 +31,16 @@
     virtual ~KeywordMap() {
     }
 
-    const Function FindFunction(const std::string& keyword,
-                                size_t num_args,
-                                std::string* err) const {
+    const Function FindFunction(const std::vector<std::string>& args, std::string* err) const {
         using android::base::StringPrintf;
 
+        if (args.empty()) {
+            *err = "keyword needed, but not provided";
+            return nullptr;
+        }
+        auto& keyword = args[0];
+        auto num_args = args.size() - 1;
+
         auto function_info_it = map().find(keyword);
         if (function_info_it == map().end()) {
             *err = StringPrintf("invalid keyword '%s'", keyword.c_str());
diff --git a/init/parser.cpp b/init/parser.cpp
index 5953a88..0d13cfe 100644
--- a/init/parser.cpp
+++ b/init/parser.cpp
@@ -1,28 +1,5 @@
 #include "parser.h"
 
-#include <stdarg.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <android-base/logging.h>
-
-void parse_error(struct parse_state *state, const char *fmt, ...)
-{
-    va_list ap;
-    char buf[128];
-    int off;
-
-    snprintf(buf, sizeof(buf), "%s: %d: ", state->filename, state->line);
-    buf[127] = 0;
-    off = strlen(buf);
-
-    va_start(ap, fmt);
-    vsnprintf(buf + off, 128 - off, fmt, ap);
-    va_end(ap);
-    buf[127] = 0;
-    LOG(ERROR) << buf;
-}
-
 int next_token(struct parse_state *state)
 {
     char *x = state->ptr;
diff --git a/init/parser.h b/init/parser.h
index 95e1164..3dcc566 100644
--- a/init/parser.h
+++ b/init/parser.h
@@ -27,14 +27,8 @@
     char *text;
     int line;
     int nexttoken;
-    void *context;
-    void (*parse_line)(struct parse_state *state, int nargs, char **args);
-    const char *filename;
-    void *priv;
 };
 
-void dump_parser_state(void);
 int next_token(struct parse_state *state);
-void parse_error(struct parse_state *state, const char *fmt, ...);
 
 #endif /* PARSER_H_ */
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 4d65437..838406d 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -205,7 +205,7 @@
     return true;
 }
 
-static void DumpUmountDebuggingInfo() {
+static void DumpUmountDebuggingInfo(bool dump_all) {
     int status;
     if (!security_getenforce()) {
         LOG(INFO) << "Run lsof";
@@ -214,6 +214,10 @@
                                 true, nullptr, nullptr, 0);
     }
     FindPartitionsToUmount(nullptr, nullptr, true);
+    if (dump_all) {
+        // dump current tasks, this log can be lengthy, so only dump with dump_all
+        android::base::WriteStringToFile("t", "/proc/sysrq-trigger");
+    }
 }
 
 static UmountStat UmountPartitions(int timeoutMs) {
@@ -277,11 +281,11 @@
     UmountStat stat = UmountPartitions(timeoutMs - t.duration_ms());
     if (stat != UMOUNT_STAT_SUCCESS) {
         LOG(INFO) << "umount timeout, last resort, kill all and try";
-        if (DUMP_ON_UMOUNT_FAILURE) DumpUmountDebuggingInfo();
+        if (DUMP_ON_UMOUNT_FAILURE) DumpUmountDebuggingInfo(false);
         KillAllProcesses();
         // even if it succeeds, still it is timeout and do not run fsck with all processes killed
         UmountPartitions(0);
-        if (DUMP_ON_UMOUNT_FAILURE) DumpUmountDebuggingInfo();
+        if (DUMP_ON_UMOUNT_FAILURE) DumpUmountDebuggingInfo(true);
     }
 
     if (stat == UMOUNT_STAT_SUCCESS && runFsck) {
@@ -314,8 +318,7 @@
         abort();
     }
 
-    /* TODO update default waiting time based on usage data */
-    constexpr unsigned int shutdownTimeoutDefault = 10;
+    constexpr unsigned int shutdownTimeoutDefault = 6;
     unsigned int shutdownTimeout = shutdownTimeoutDefault;
     if (SHUTDOWN_ZERO_TIMEOUT) {  // eng build
         shutdownTimeout = 0;
@@ -341,18 +344,9 @@
     Service* bootAnim = ServiceManager::GetInstance().FindServiceByName("bootanim");
     Service* surfaceFlinger = ServiceManager::GetInstance().FindServiceByName("surfaceflinger");
     if (bootAnim != nullptr && surfaceFlinger != nullptr && surfaceFlinger->IsRunning()) {
-        property_set("service.bootanim.exit", "0");
-        // Could be in the middle of animation. Stop and start so that it can pick
-        // up the right mode.
-        bootAnim->Stop();
-        // start all animation classes if stopped.
         ServiceManager::GetInstance().ForEachServiceInClass("animation", [](Service* s) {
-            s->Start();
             s->SetShutdownCritical();  // will not check animation class separately
         });
-        bootAnim->Start();
-        surfaceFlinger->SetShutdownCritical();
-        bootAnim->SetShutdownCritical();
     }
 
     // optional shutdown step
@@ -429,7 +423,6 @@
 bool HandlePowerctlMessage(const std::string& command) {
     unsigned int cmd = 0;
     std::vector<std::string> cmd_params = android::base::Split(command, ",");
-    std::string reason_string = cmd_params[0];
     std::string reboot_target = "";
     bool run_fsck = false;
     bool command_invalid = false;
@@ -442,7 +435,6 @@
             // The shutdown reason is PowerManager.SHUTDOWN_USER_REQUESTED.
             // Run fsck once the file system is remounted in read-only mode.
             run_fsck = true;
-            reason_string = cmd_params[1];
         }
     } else if (cmd_params[0] == "reboot") {
         cmd = ANDROID_RB_RESTART2;
@@ -473,6 +465,6 @@
         return false;
     }
 
-    DoReboot(cmd, reason_string, reboot_target, run_fsck);
+    DoReboot(cmd, command, reboot_target, run_fsck);
     return true;
 }
diff --git a/init/service.cpp b/init/service.cpp
index ab404a1..2284a21 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -572,13 +572,8 @@
 }
 
 bool Service::ParseLine(const std::vector<std::string>& args, std::string* err) {
-    if (args.empty()) {
-        *err = "option needed, but not provided";
-        return false;
-    }
-
     static const OptionParserMap parser_map;
-    auto parser = parser_map.FindFunction(args[0], args.size() - 1, err);
+    auto parser = parser_map.FindFunction(args, err);
 
     if (!parser) {
         return false;
@@ -639,7 +634,6 @@
     if (!seclabel_.empty()) {
         scon = seclabel_;
     } else {
-        LOG(INFO) << "computing context for service '" << name_ << "'";
         scon = ComputeContextFromExecutable(name_, args_[0]);
         if (scon == "") {
             return false;
@@ -931,7 +925,9 @@
     std::vector<std::string> str_args(args.begin() + command_arg, args.end());
 
     exec_count_++;
-    std::string name = StringPrintf("exec %d (%s)", exec_count_, str_args[0].c_str());
+    std::string name =
+        "exec " + std::to_string(exec_count_) + " (" + android::base::Join(str_args, " ") + ")";
+
     unsigned flags = SVC_EXEC | SVC_ONESHOT | SVC_TEMPORARY;
     CapSet no_capabilities;
     unsigned namespace_flags = 0;
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index b6c6a01..963cc4d 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -18,9 +18,7 @@
 
 #include <ctype.h>
 #include <fcntl.h>
-#include <grp.h>
 #include <poll.h>
-#include <pwd.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -33,9 +31,11 @@
 
 #include "devices.h"
 #include "log.h"
-#include "ueventd_parser.h"
 #include "util.h"
 
+template <bool sysfs>
+static bool ParseSingleLine(std::vector<std::string>&& line, std::string* err);
+
 int ueventd_main(int argc, char **argv)
 {
     /*
@@ -60,9 +60,14 @@
     cb.func_log = selinux_klog_callback;
     selinux_set_callback(SELINUX_CB_LOG, cb);
 
-    ueventd_parse_config_file("/ueventd.rc");
-    ueventd_parse_config_file("/vendor/ueventd.rc");
-    ueventd_parse_config_file("/odm/ueventd.rc");
+    Parser& parser = Parser::GetInstance();
+    parser.AddSectionParser("subsystem", std::make_unique<SubsystemParser>());
+    using namespace std::placeholders;
+    parser.AddSingleLineParser("/sys/", std::bind(ParsePermissionsLine, _1, _2, true));
+    parser.AddSingleLineParser("/dev/", std::bind(ParsePermissionsLine, _1, _2, false));
+    parser.ParseConfig("/ueventd.rc");
+    parser.ParseConfig("/vendor/ueventd.rc");
+    parser.ParseConfig("/odm/ueventd.rc");
 
     /*
      * keep the current product name base configuration so
@@ -72,7 +77,7 @@
      * device node entries (b/34968103)
      */
     std::string hardware = android::base::GetProperty("ro.hardware", "");
-    ueventd_parse_config_file(android::base::StringPrintf("/ueventd.%s.rc", hardware.c_str()).c_str());
+    parser.ParseConfig("/ueventd." + hardware + ".rc");
 
     device_init();
 
@@ -93,59 +98,3 @@
 
     return 0;
 }
-
-void set_device_permission(const char* fn, int line, int nargs, char **args)
-{
-    char *name;
-    char *attr = 0;
-    mode_t perm;
-    uid_t uid;
-    gid_t gid;
-    char *endptr;
-
-    if (nargs == 0)
-        return;
-
-    if (args[0][0] == '#')
-        return;
-
-    name = args[0];
-
-    if (!strncmp(name,"/sys/", 5) && (nargs == 5)) {
-        LOG(INFO) << "/sys/ rule " << args[0] << " " << args[1];
-        attr = args[1];
-        args++;
-        nargs--;
-    }
-
-    if (nargs != 4) {
-        LOG(ERROR) << "invalid line (" << fn << ":" << line << ") line for '" << args[0] << "'";
-        return;
-    }
-
-    perm = strtol(args[1], &endptr, 8);
-    if (!endptr || *endptr != '\0') {
-        LOG(ERROR) << "invalid mode (" << fn << ":" << line << ") '" << args[1] << "'";
-        return;
-    }
-
-    struct passwd* pwd = getpwnam(args[2]);
-    if (!pwd) {
-        LOG(ERROR) << "invalid uid (" << fn << ":" << line << ") '" << args[2] << "'";
-        return;
-    }
-    uid = pwd->pw_uid;
-
-    struct group* grp = getgrnam(args[3]);
-    if (!grp) {
-        LOG(ERROR) << "invalid gid (" << fn << ":" << line << ") '" << args[3] << "'";
-        return;
-    }
-    gid = grp->gr_gid;
-
-    if (attr) {
-        sysfs_permissions.emplace_back(name, attr, perm, uid, gid);
-    } else {
-        dev_permissions.emplace_back(name, perm, uid, gid);
-    }
-}
diff --git a/init/ueventd.h b/init/ueventd.h
index d44d1ca..1f424d3 100644
--- a/init/ueventd.h
+++ b/init/ueventd.h
@@ -17,24 +17,6 @@
 #ifndef _INIT_UEVENTD_H_
 #define _INIT_UEVENTD_H_
 
-#include <sys/types.h>
-
-#include <cutils/list.h>
-
-enum devname_src_t {
-    DEVNAME_UNKNOWN = 0,
-    DEVNAME_UEVENT_DEVNAME,
-    DEVNAME_UEVENT_DEVPATH,
-};
-
-struct ueventd_subsystem {
-    struct listnode slist;
-
-    const char *name;
-    const char *dirname;
-    devname_src_t devname_src;
-};
-
-int ueventd_main(int argc, char **argv);
+int ueventd_main(int argc, char** argv);
 
 #endif
diff --git a/init/ueventd_keywords.h b/init/ueventd_keywords.h
deleted file mode 100644
index 88e8f01..0000000
--- a/init/ueventd_keywords.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef KEYWORD
-#define __MAKE_KEYWORD_ENUM__
-#define KEYWORD(symbol, flags, nargs) K_##symbol,
-enum {
-    K_UNKNOWN,
-#endif
-    KEYWORD(subsystem,      SECTION,    1)
-    KEYWORD(devname,        OPTION,     1)
-    KEYWORD(dirname,        OPTION,     1)
-#ifdef __MAKE_KEYWORD_ENUM__
-    KEYWORD_COUNT,
-};
-#undef __MAKE_KEYWORD_ENUM__
-#undef KEYWORD
-#endif
diff --git a/init/ueventd_parser.cpp b/init/ueventd_parser.cpp
deleted file mode 100644
index 510d7bb..0000000
--- a/init/ueventd_parser.cpp
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ueventd_parser.h"
-
-#include <ctype.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <android-base/logging.h>
-
-#include "parser.h"
-#include "util.h"
-
-static list_declare(subsystem_list);
-
-static void parse_line_device(struct parse_state *state, int nargs, char **args);
-
-#define SECTION 0x01
-#define OPTION  0x02
-
-#include "ueventd_keywords.h"
-
-#define KEYWORD(symbol, flags, nargs) \
-    [ K_##symbol ] = { #symbol, (nargs) + 1, flags, },
-
-static struct {
-    const char *name;
-    unsigned char nargs;
-    unsigned char flags;
-} keyword_info[KEYWORD_COUNT] = {
-    [ K_UNKNOWN ] = { "unknown", 0, 0 },
-#include "ueventd_keywords.h"
-};
-#undef KEYWORD
-
-#define kw_is(kw, type) (keyword_info[kw].flags & (type))
-#define kw_nargs(kw) (keyword_info[kw].nargs)
-
-static int lookup_keyword(const char *s)
-{
-    switch (*s++) {
-    case 'd':
-        if (!strcmp(s, "evname")) return K_devname;
-        if (!strcmp(s, "irname")) return K_dirname;
-        break;
-    case 's':
-        if (!strcmp(s, "ubsystem")) return K_subsystem;
-        break;
-    }
-    return K_UNKNOWN;
-}
-
-static void parse_line_no_op(struct parse_state*, int, char**) {
-}
-
-static int valid_name(const char *name)
-{
-    while (*name) {
-        if (!isalnum(*name) && (*name != '_') && (*name != '-')) {
-            return 0;
-        }
-        name++;
-    }
-    return 1;
-}
-
-struct ueventd_subsystem *ueventd_subsystem_find_by_name(const char *name)
-{
-    struct listnode *node;
-    struct ueventd_subsystem *s;
-
-    list_for_each(node, &subsystem_list) {
-        s = node_to_item(node, struct ueventd_subsystem, slist);
-        if (!strcmp(s->name, name)) {
-            return s;
-        }
-    }
-    return 0;
-}
-
-static void *parse_subsystem(parse_state* state, int /*nargs*/, char** args) {
-    if (!valid_name(args[1])) {
-        parse_error(state, "invalid subsystem name '%s'\n", args[1]);
-        return 0;
-    }
-
-    ueventd_subsystem* s = ueventd_subsystem_find_by_name(args[1]);
-    if (s) {
-        parse_error(state, "ignored duplicate definition of subsystem '%s'\n",
-                args[1]);
-        return 0;
-    }
-
-    s = (ueventd_subsystem*) calloc(1, sizeof(*s));
-    if (!s) {
-        parse_error(state, "out of memory\n");
-        return 0;
-    }
-    s->name = args[1];
-    s->dirname = "/dev";
-    list_add_tail(&subsystem_list, &s->slist);
-    return s;
-}
-
-static void parse_line_subsystem(struct parse_state *state, int nargs,
-        char **args)
-{
-    struct ueventd_subsystem *s = (ueventd_subsystem*) state->context;
-    int kw;
-
-    if (nargs == 0) {
-        return;
-    }
-
-    kw = lookup_keyword(args[0]);
-    switch (kw) {
-    case K_devname:
-        if (!strcmp(args[1], "uevent_devname"))
-            s->devname_src = DEVNAME_UEVENT_DEVNAME;
-        else if (!strcmp(args[1], "uevent_devpath"))
-            s->devname_src = DEVNAME_UEVENT_DEVPATH;
-        else
-            parse_error(state, "invalid devname '%s'\n", args[1]);
-        break;
-
-    case K_dirname:
-        if (args[1][0] == '/')
-            s->dirname = args[1];
-        else
-            parse_error(state, "dirname '%s' does not start with '/'\n",
-                    args[1]);
-        break;
-
-    default:
-        parse_error(state, "invalid option '%s'\n", args[0]);
-    }
-}
-
-static void parse_new_section(struct parse_state *state, int kw,
-                       int nargs, char **args)
-{
-    printf("[ %s %s ]\n", args[0],
-           nargs > 1 ? args[1] : "");
-
-    switch(kw) {
-    case K_subsystem:
-        state->context = parse_subsystem(state, nargs, args);
-        if (state->context) {
-            state->parse_line = parse_line_subsystem;
-            return;
-        }
-        break;
-    }
-    state->parse_line = parse_line_no_op;
-}
-
-static void parse_line(struct parse_state *state, char **args, int nargs)
-{
-    int kw = lookup_keyword(args[0]);
-    int kw_nargs = kw_nargs(kw);
-
-    if (nargs < kw_nargs) {
-        parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1,
-            kw_nargs > 2 ? "arguments" : "argument");
-        return;
-    }
-
-    if (kw_is(kw, SECTION)) {
-        parse_new_section(state, kw, nargs, args);
-    } else if (kw_is(kw, OPTION)) {
-        state->parse_line(state, nargs, args);
-    } else {
-        parse_line_device(state, nargs, args);
-    }
-}
-
-static void parse_config(const char *fn, const std::string& data)
-{
-    char *args[UEVENTD_PARSER_MAXARGS];
-
-    int nargs = 0;
-    parse_state state;
-    state.filename = fn;
-    state.line = 1;
-    state.ptr = strdup(data.c_str());  // TODO: fix this code!
-    state.nexttoken = 0;
-    state.parse_line = parse_line_no_op;
-    for (;;) {
-        int token = next_token(&state);
-        switch (token) {
-        case T_EOF:
-            parse_line(&state, args, nargs);
-            return;
-        case T_NEWLINE:
-            if (nargs) {
-                parse_line(&state, args, nargs);
-                nargs = 0;
-            }
-            state.line++;
-            break;
-        case T_TEXT:
-            if (nargs < UEVENTD_PARSER_MAXARGS) {
-                args[nargs++] = state.text;
-            }
-            break;
-        }
-    }
-}
-
-int ueventd_parse_config_file(const char *fn)
-{
-    std::string data;
-    if (!read_file(fn, &data)) {
-        return -1;
-    }
-
-    data.push_back('\n'); // TODO: fix parse_config.
-    parse_config(fn, data);
-    return 0;
-}
-
-static void parse_line_device(parse_state* state, int nargs, char** args) {
-    set_device_permission(state->filename, state->line, nargs, args);
-}
diff --git a/init/ueventd_parser.h b/init/ueventd_parser.h
deleted file mode 100644
index 4d69897..0000000
--- a/init/ueventd_parser.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _INIT_UEVENTD_PARSER_H_
-#define _INIT_UEVENTD_PARSER_H_
-
-#include "ueventd.h"
-
-#define UEVENTD_PARSER_MAXARGS 5
-
-int ueventd_parse_config_file(const char *fn);
-void set_device_permission(const char* fn, int line, int nargs, char **args);
-struct ueventd_subsystem *ueventd_subsystem_find_by_name(const char *name);
-
-#endif
diff --git a/libbacktrace/Android.bp b/libbacktrace/Android.bp
index 4a525be..285aa6e 100644
--- a/libbacktrace/Android.bp
+++ b/libbacktrace/Android.bp
@@ -57,6 +57,7 @@
 
 cc_library_headers {
     name: "libbacktrace_headers",
+    vendor_available: true,
     export_include_dirs: ["include"],
 }
 
diff --git a/libbinderwrapper/Android.bp b/libbinderwrapper/Android.bp
new file mode 100644
index 0000000..6fac0d8
--- /dev/null
+++ b/libbinderwrapper/Android.bp
@@ -0,0 +1,61 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_defaults {
+    name: "libbinderwrapper_defaults",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+
+        // for libchrome
+        "-Wno-sign-promo",
+    ],
+    export_include_dirs: ["include"],
+    shared_libs: [
+        "libbinder",
+        "libchrome",
+        "libutils",
+    ],
+}
+
+// libbinderwrapper shared library
+// ========================================================
+cc_library_shared {
+    name: "libbinderwrapper",
+    defaults: ["libbinderwrapper_defaults"],
+
+    srcs: [
+        "binder_wrapper.cc",
+        "real_binder_wrapper.cc",
+    ],
+}
+
+// libbinderwrapper_test_support static library
+// ========================================================
+cc_library_static {
+    name: "libbinderwrapper_test_support",
+    defaults: ["libbinderwrapper_defaults"],
+
+    static_libs: ["libgtest"],
+    shared_libs: ["libbinderwrapper"],
+
+    srcs: [
+        "binder_test_base.cc",
+        "stub_binder_wrapper.cc",
+    ],
+}
diff --git a/libbinderwrapper/Android.mk b/libbinderwrapper/Android.mk
deleted file mode 100644
index c768373..0000000
--- a/libbinderwrapper/Android.mk
+++ /dev/null
@@ -1,62 +0,0 @@
-#
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-binderwrapperCommonCFlags := -Wall -Werror -Wno-unused-parameter
-binderwrapperCommonCFlags += -Wno-sign-promo  # for libchrome
-binderwrapperCommonExportCIncludeDirs := $(LOCAL_PATH)/include
-binderwrapperCommonCIncludes := $(LOCAL_PATH)/include
-binderwrapperCommonSharedLibraries := \
-  libbinder \
-  libchrome \
-  libutils \
-
-# libbinderwrapper shared library
-# ========================================================
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := libbinderwrapper
-LOCAL_CPP_EXTENSION := .cc
-LOCAL_CFLAGS := $(binderwrapperCommonCFlags)
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(binderwrapperCommonExportCIncludeDirs)
-LOCAL_C_INCLUDES := $(binderwrapperCommonCIncludes)
-LOCAL_SHARED_LIBRARIES := $(binderwrapperCommonSharedLibraries)
-LOCAL_SRC_FILES := \
-  binder_wrapper.cc \
-  real_binder_wrapper.cc \
-
-include $(BUILD_SHARED_LIBRARY)
-
-# libbinderwrapper_test_support static library
-# ========================================================
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := libbinderwrapper_test_support
-LOCAL_CPP_EXTENSION := .cc
-LOCAL_CFLAGS := $(binderwrapperCommonCFlags)
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(binderwrapperCommonExportCIncludeDirs)
-LOCAL_C_INCLUDES := $(binderwrapperCommonCIncludes)
-LOCAL_STATIC_LIBRARIES := libgtest
-LOCAL_SHARED_LIBRARIES := \
-  $(binderwrapperCommonSharedLibraries) \
-  libbinderwrapper \
-
-LOCAL_SRC_FILES := \
-  binder_test_base.cc \
-  stub_binder_wrapper.cc \
-
-include $(BUILD_STATIC_LIBRARY)
diff --git a/libcutils/fs_config.c b/libcutils/fs_config.c
index 6e5db0b..e1e8c8d 100644
--- a/libcutils/fs_config.c
+++ b/libcutils/fs_config.c
@@ -189,8 +189,10 @@
                                            CAP_MASK_LONG(CAP_NET_RAW),
                                               "system/bin/hostapd" },
 
-    /* Support Bluetooth legacy hal accessing /sys/class/rfkill */
-    { 00700, AID_BLUETOOTH, AID_BLUETOOTH, CAP_MASK_LONG(CAP_NET_ADMIN),
+    /* Support Bluetooth legacy hal accessing /sys/class/rfkill
+     * Support RT scheduling in Bluetooth */
+    { 00700, AID_BLUETOOTH, AID_BLUETOOTH, CAP_MASK_LONG(CAP_NET_ADMIN) |
+                                           CAP_MASK_LONG(CAP_SYS_NICE),
                                               "vendor/bin/hw/android.hardware.bluetooth@1.0-service" },
 
     /* Support wifi_hal_legacy administering a network interface. */
diff --git a/libcutils/sched_policy.cpp b/libcutils/sched_policy.cpp
index 4a0b035..217733a 100644
--- a/libcutils/sched_policy.cpp
+++ b/libcutils/sched_policy.cpp
@@ -263,26 +263,26 @@
 
     char grpBuf[32];
 
-    if (cpusets_enabled()) {
+    grpBuf[0] = '\0';
+    if (schedboost_enabled()) {
+        if (getCGroupSubsys(tid, "schedtune", grpBuf, sizeof(grpBuf)) < 0) return -1;
+    }
+    if ((grpBuf[0] == '\0') && cpusets_enabled()) {
         if (getCGroupSubsys(tid, "cpuset", grpBuf, sizeof(grpBuf)) < 0) return -1;
-        if (grpBuf[0] == '\0') {
-            *policy = SP_FOREGROUND;
-        } else if (!strcmp(grpBuf, "foreground")) {
-            *policy = SP_FOREGROUND;
-        } else if (!strcmp(grpBuf, "system-background")) {
-            *policy = SP_SYSTEM;
-        } else if (!strcmp(grpBuf, "background")) {
-            *policy = SP_BACKGROUND;
-        } else if (!strcmp(grpBuf, "top-app")) {
-            *policy = SP_TOP_APP;
-        } else {
-            errno = ERANGE;
-            return -1;
-        }
-    } else {
-        // In b/34193533, we removed bg_non_interactive cgroup, so now
-        // all threads are in FOREGROUND cgroup
+    }
+    if (grpBuf[0] == '\0') {
         *policy = SP_FOREGROUND;
+    } else if (!strcmp(grpBuf, "foreground")) {
+        *policy = SP_FOREGROUND;
+    } else if (!strcmp(grpBuf, "system-background")) {
+        *policy = SP_SYSTEM;
+    } else if (!strcmp(grpBuf, "background")) {
+        *policy = SP_BACKGROUND;
+    } else if (!strcmp(grpBuf, "top-app")) {
+        *policy = SP_TOP_APP;
+    } else {
+        errno = ERANGE;
+        return -1;
     }
     return 0;
 }
diff --git a/libsystem/Android.bp b/libsystem/Android.bp
index 4d076d5..846a585 100644
--- a/libsystem/Android.bp
+++ b/libsystem/Android.bp
@@ -1,4 +1,15 @@
 cc_library_headers {
     name: "libsystem_headers",
+    vendor_available: true,
+    host_supported: true,
     export_include_dirs: ["include"],
+
+    target: {
+        linux_bionic: {
+            enabled: true,
+        },
+        windows: {
+            enabled: true,
+        },
+    }
 }
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 05d0353..32ed6c3 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -47,6 +47,7 @@
 
     srcs: [
         "ArmExidx.cpp",
+        "DwarfCfa.cpp",
         "DwarfMemory.cpp",
         "DwarfOp.cpp",
         "Elf.cpp",
@@ -92,6 +93,8 @@
     srcs: [
         "tests/ArmExidxDecodeTest.cpp",
         "tests/ArmExidxExtractTest.cpp",
+        "tests/DwarfCfaLogTest.cpp",
+        "tests/DwarfCfaTest.cpp",
         "tests/DwarfMemoryTest.cpp",
         "tests/DwarfOpLogTest.cpp",
         "tests/DwarfOpTest.cpp",
diff --git a/libunwindstack/DwarfCfa.cpp b/libunwindstack/DwarfCfa.cpp
new file mode 100644
index 0000000..006f039
--- /dev/null
+++ b/libunwindstack/DwarfCfa.cpp
@@ -0,0 +1,713 @@
+/*
+ * 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 <inttypes.h>
+#include <stdint.h>
+
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+
+#include "DwarfCfa.h"
+#include "DwarfEncoding.h"
+#include "DwarfMemory.h"
+#include "DwarfOp.h"
+#include "DwarfStructs.h"
+#include "Log.h"
+
+template <typename AddressType>
+constexpr typename DwarfCfa<AddressType>::process_func DwarfCfa<AddressType>::kCallbackTable[64];
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::GetLocationInfo(uint64_t pc, uint64_t start_offset, uint64_t end_offset,
+                                            dwarf_loc_regs_t* loc_regs) {
+  if (cie_loc_regs_ != nullptr) {
+    for (const auto& entry : *cie_loc_regs_) {
+      (*loc_regs)[entry.first] = entry.second;
+    }
+  }
+  last_error_ = DWARF_ERROR_NONE;
+
+  memory_->set_cur_offset(start_offset);
+  uint64_t cfa_offset;
+  cur_pc_ = fde_->pc_start;
+  while ((cfa_offset = memory_->cur_offset()) < end_offset && cur_pc_ <= pc) {
+    operands_.clear();
+    // Read the cfa information.
+    uint8_t cfa_value;
+    if (!memory_->ReadBytes(&cfa_value, 1)) {
+      last_error_ = DWARF_ERROR_MEMORY_INVALID;
+      return false;
+    }
+    uint8_t cfa_low = cfa_value & 0x3f;
+    // Check the 2 high bits.
+    switch (cfa_value >> 6) {
+      case 1:
+        cur_pc_ += cfa_low * fde_->cie->code_alignment_factor;
+        break;
+      case 2: {
+        uint64_t offset;
+        if (!memory_->ReadULEB128(&offset)) {
+          last_error_ = DWARF_ERROR_MEMORY_INVALID;
+          return false;
+        }
+        SignedType signed_offset =
+            static_cast<SignedType>(offset) * fde_->cie->data_alignment_factor;
+        (*loc_regs)[cfa_low] = {.type = DWARF_LOCATION_OFFSET,
+                                .values = {static_cast<uint64_t>(signed_offset)}};
+        break;
+      }
+      case 3: {
+        if (cie_loc_regs_ == nullptr) {
+          log(0, "restore while processing cie");
+          last_error_ = DWARF_ERROR_ILLEGAL_STATE;
+          return false;
+        }
+
+        auto reg_entry = cie_loc_regs_->find(cfa_low);
+        if (reg_entry == cie_loc_regs_->end()) {
+          loc_regs->erase(cfa_low);
+        } else {
+          (*loc_regs)[cfa_low] = reg_entry->second;
+        }
+        break;
+      }
+      case 0: {
+        const auto handle_func = DwarfCfa<AddressType>::kCallbackTable[cfa_low];
+        if (handle_func == nullptr) {
+          last_error_ = DWARF_ERROR_ILLEGAL_VALUE;
+          return false;
+        }
+
+        const auto cfa = &DwarfCfaInfo::kTable[cfa_low];
+        for (size_t i = 0; i < cfa->num_operands; i++) {
+          if (cfa->operands[i] == DW_EH_PE_block) {
+            uint64_t block_length;
+            if (!memory_->ReadULEB128(&block_length)) {
+              last_error_ = DWARF_ERROR_MEMORY_INVALID;
+              return false;
+            }
+            operands_.push_back(block_length);
+            memory_->set_cur_offset(memory_->cur_offset() + block_length);
+            continue;
+          }
+          uint64_t value;
+          if (!memory_->ReadEncodedValue<AddressType>(cfa->operands[i], &value)) {
+            last_error_ = DWARF_ERROR_MEMORY_INVALID;
+            return false;
+          }
+          operands_.push_back(value);
+        }
+
+        if (!(this->*handle_func)(loc_regs)) {
+          return false;
+        }
+        break;
+      }
+    }
+  }
+  return true;
+}
+
+template <typename AddressType>
+std::string DwarfCfa<AddressType>::GetOperandString(uint8_t operand, uint64_t value,
+                                                    uint64_t* cur_pc) {
+  std::string string;
+  switch (operand) {
+    case DwarfCfaInfo::DWARF_DISPLAY_REGISTER:
+      string = " register(" + std::to_string(value) + ")";
+      break;
+    case DwarfCfaInfo::DWARF_DISPLAY_SIGNED_NUMBER:
+      string += " " + std::to_string(static_cast<SignedType>(value));
+      break;
+    case DwarfCfaInfo::DWARF_DISPLAY_ADVANCE_LOC:
+      *cur_pc += value;
+    // Fall through to log the value.
+    case DwarfCfaInfo::DWARF_DISPLAY_NUMBER:
+      string += " " + std::to_string(value);
+      break;
+    case DwarfCfaInfo::DWARF_DISPLAY_SET_LOC:
+      *cur_pc = value;
+    // Fall through to log the value.
+    case DwarfCfaInfo::DWARF_DISPLAY_ADDRESS:
+      if (std::is_same<AddressType, uint32_t>::value) {
+        string += android::base::StringPrintf(" 0x%" PRIx32, static_cast<uint32_t>(value));
+      } else {
+        string += android::base::StringPrintf(" 0x%" PRIx64, static_cast<uint64_t>(value));
+      }
+      break;
+    default:
+      string = " unknown";
+  }
+  return string;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::LogOffsetRegisterString(uint32_t indent, uint64_t cfa_offset,
+                                                    uint8_t reg) {
+  uint64_t offset;
+  if (!memory_->ReadULEB128(&offset)) {
+    return false;
+  }
+  uint64_t end_offset = memory_->cur_offset();
+  memory_->set_cur_offset(cfa_offset);
+
+  std::string raw_data = "Raw Data:";
+  for (uint64_t i = cfa_offset; i < end_offset; i++) {
+    uint8_t value;
+    if (!memory_->ReadBytes(&value, 1)) {
+      return false;
+    }
+    raw_data += android::base::StringPrintf(" 0x%02x", value);
+  }
+  log(indent, "DW_CFA_offset register(%d) %" PRId64, reg, offset);
+  log(indent, "%s", raw_data.c_str());
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::LogInstruction(uint32_t indent, uint64_t cfa_offset, uint8_t op,
+                                           uint64_t* cur_pc) {
+  const auto* cfa = &DwarfCfaInfo::kTable[op];
+  if (cfa->name == nullptr) {
+    log(indent, "Illegal");
+    log(indent, "Raw Data: 0x%02x", op);
+    return true;
+  }
+
+  std::string log_string(cfa->name);
+  std::vector<std::string> expression_lines;
+  for (size_t i = 0; i < cfa->num_operands; i++) {
+    if (cfa->operands[i] == DW_EH_PE_block) {
+      // This is a Dwarf Expression.
+      uint64_t end_offset;
+      if (!memory_->ReadULEB128(&end_offset)) {
+        return false;
+      }
+      log_string += " " + std::to_string(end_offset);
+      end_offset += memory_->cur_offset();
+
+      DwarfOp<AddressType> op(memory_, nullptr);
+      op.GetLogInfo(memory_->cur_offset(), end_offset, &expression_lines);
+      memory_->set_cur_offset(end_offset);
+    } else {
+      uint64_t value;
+      if (!memory_->ReadEncodedValue<AddressType>(cfa->operands[i], &value)) {
+        return false;
+      }
+      log_string += GetOperandString(cfa->display_operands[i], value, cur_pc);
+    }
+  }
+  log(indent, "%s", log_string.c_str());
+
+  // Get the raw bytes of the data.
+  uint64_t end_offset = memory_->cur_offset();
+  memory_->set_cur_offset(cfa_offset);
+  std::string raw_data("Raw Data:");
+  for (uint64_t i = 0; i < end_offset - cfa_offset; i++) {
+    uint8_t value;
+    if (!memory_->ReadBytes(&value, 1)) {
+      return false;
+    }
+
+    // Only show 10 raw bytes per line.
+    if ((i % 10) == 0 && i != 0) {
+      log(indent, "%s", raw_data.c_str());
+      raw_data.clear();
+    }
+    if (raw_data.empty()) {
+      raw_data = "Raw Data:";
+    }
+    raw_data += android::base::StringPrintf(" 0x%02x", value);
+  }
+  if (!raw_data.empty()) {
+    log(indent, "%s", raw_data.c_str());
+  }
+
+  // Log any of the expression data.
+  for (const auto line : expression_lines) {
+    log(indent + 1, "%s", line.c_str());
+  }
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::Log(uint32_t indent, uint64_t pc, uint64_t load_bias,
+                                uint64_t start_offset, uint64_t end_offset) {
+  memory_->set_cur_offset(start_offset);
+  uint64_t cfa_offset;
+  uint64_t cur_pc = fde_->pc_start;
+  uint64_t old_pc = cur_pc;
+  while ((cfa_offset = memory_->cur_offset()) < end_offset && cur_pc <= pc) {
+    // Read the cfa information.
+    uint8_t cfa_value;
+    if (!memory_->ReadBytes(&cfa_value, 1)) {
+      return false;
+    }
+
+    // Check the 2 high bits.
+    uint8_t cfa_low = cfa_value & 0x3f;
+    switch (cfa_value >> 6) {
+      case 0:
+        if (!LogInstruction(indent, cfa_offset, cfa_low, &cur_pc)) {
+          return false;
+        }
+        break;
+      case 1:
+        log(indent, "DW_CFA_advance_loc %d", cfa_low);
+        log(indent, "Raw Data: 0x%02x", cfa_value);
+        cur_pc += cfa_low * fde_->cie->code_alignment_factor;
+        break;
+      case 2:
+        if (!LogOffsetRegisterString(indent, cfa_offset, cfa_low)) {
+          return false;
+        }
+        break;
+      case 3:
+        log(indent, "DW_CFA_restore register(%d)", cfa_low);
+        log(indent, "Raw Data: 0x%02x", cfa_value);
+        break;
+    }
+    if (cur_pc != old_pc) {
+      log(indent, "");
+      log(indent, "PC 0x%" PRIx64, cur_pc + load_bias);
+    }
+    old_pc = cur_pc;
+  }
+  return true;
+}
+
+// Static data.
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_nop(dwarf_loc_regs_t*) {
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_set_loc(dwarf_loc_regs_t*) {
+  AddressType cur_pc = cur_pc_;
+  AddressType new_pc = operands_[0];
+  if (new_pc < cur_pc) {
+    if (std::is_same<AddressType, uint32_t>::value) {
+      log(0, "Warning: PC is moving backwards: old 0x%" PRIx32 " new 0x%" PRIx32, cur_pc, new_pc);
+    } else {
+      log(0, "Warning: PC is moving backwards: old 0x%" PRIx64 " new 0x%" PRIx64, cur_pc, new_pc);
+    }
+  }
+  cur_pc_ = new_pc;
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_advance_loc(dwarf_loc_regs_t*) {
+  cur_pc_ += operands_[0] * fde_->cie->code_alignment_factor;
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_offset(dwarf_loc_regs_t* loc_regs) {
+  AddressType reg = operands_[0];
+  (*loc_regs)[reg] = {.type = DWARF_LOCATION_OFFSET, .values = {operands_[1]}};
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_restore(dwarf_loc_regs_t* loc_regs) {
+  AddressType reg = operands_[0];
+  if (cie_loc_regs_ == nullptr) {
+    log(0, "restore while processing cie");
+    last_error_ = DWARF_ERROR_ILLEGAL_STATE;
+    return false;
+  }
+  auto reg_entry = cie_loc_regs_->find(reg);
+  if (reg_entry == cie_loc_regs_->end()) {
+    loc_regs->erase(reg);
+  } else {
+    (*loc_regs)[reg] = reg_entry->second;
+  }
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_undefined(dwarf_loc_regs_t* loc_regs) {
+  AddressType reg = operands_[0];
+  (*loc_regs)[reg] = {.type = DWARF_LOCATION_UNDEFINED};
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_same_value(dwarf_loc_regs_t* loc_regs) {
+  AddressType reg = operands_[0];
+  loc_regs->erase(reg);
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_register(dwarf_loc_regs_t* loc_regs) {
+  AddressType reg = operands_[0];
+  AddressType reg_dst = operands_[1];
+  (*loc_regs)[reg] = {.type = DWARF_LOCATION_REGISTER, .values = {reg_dst}};
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_remember_state(dwarf_loc_regs_t* loc_regs) {
+  loc_reg_state_.push(*loc_regs);
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_restore_state(dwarf_loc_regs_t* loc_regs) {
+  if (loc_reg_state_.size() == 0) {
+    log(0, "Warning: Attempt to restore without remember.");
+    return true;
+  }
+  *loc_regs = loc_reg_state_.top();
+  loc_reg_state_.pop();
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_def_cfa(dwarf_loc_regs_t* loc_regs) {
+  (*loc_regs)[CFA_REG] = {.type = DWARF_LOCATION_REGISTER, .values = {operands_[0], operands_[1]}};
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_def_cfa_register(dwarf_loc_regs_t* loc_regs) {
+  auto cfa_location = loc_regs->find(CFA_REG);
+  if (cfa_location == loc_regs->end() || cfa_location->second.type != DWARF_LOCATION_REGISTER) {
+    log(0, "Attempt to set new register, but cfa is not already set to a register.");
+    last_error_ = DWARF_ERROR_ILLEGAL_STATE;
+    return false;
+  }
+
+  cfa_location->second.values[0] = operands_[0];
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_def_cfa_offset(dwarf_loc_regs_t* loc_regs) {
+  // Changing the offset if this is not a register is illegal.
+  auto cfa_location = loc_regs->find(CFA_REG);
+  if (cfa_location == loc_regs->end() || cfa_location->second.type != DWARF_LOCATION_REGISTER) {
+    log(0, "Attempt to set offset, but cfa is not set to a register.");
+    last_error_ = DWARF_ERROR_ILLEGAL_STATE;
+    return false;
+  }
+  cfa_location->second.values[1] = operands_[0];
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_def_cfa_expression(dwarf_loc_regs_t* loc_regs) {
+  (*loc_regs)[CFA_REG] = {.type = DWARF_LOCATION_EXPRESSION,
+                          .values = {operands_[0], memory_->cur_offset()}};
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_expression(dwarf_loc_regs_t* loc_regs) {
+  AddressType reg = operands_[0];
+  (*loc_regs)[reg] = {.type = DWARF_LOCATION_EXPRESSION,
+                      .values = {operands_[1], memory_->cur_offset()}};
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_offset_extended_sf(dwarf_loc_regs_t* loc_regs) {
+  AddressType reg = operands_[0];
+  SignedType value = static_cast<SignedType>(operands_[1]) * fde_->cie->data_alignment_factor;
+  (*loc_regs)[reg] = {.type = DWARF_LOCATION_OFFSET, .values = {static_cast<uint64_t>(value)}};
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_def_cfa_sf(dwarf_loc_regs_t* loc_regs) {
+  SignedType offset = static_cast<SignedType>(operands_[1]) * fde_->cie->data_alignment_factor;
+  (*loc_regs)[CFA_REG] = {.type = DWARF_LOCATION_REGISTER,
+                          .values = {operands_[0], static_cast<uint64_t>(offset)}};
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_def_cfa_offset_sf(dwarf_loc_regs_t* loc_regs) {
+  // Changing the offset if this is not a register is illegal.
+  auto cfa_location = loc_regs->find(CFA_REG);
+  if (cfa_location == loc_regs->end() || cfa_location->second.type != DWARF_LOCATION_REGISTER) {
+    log(0, "Attempt to set offset, but cfa is not set to a register.");
+    last_error_ = DWARF_ERROR_ILLEGAL_STATE;
+    return false;
+  }
+  SignedType offset = static_cast<SignedType>(operands_[0]) * fde_->cie->data_alignment_factor;
+  cfa_location->second.values[1] = static_cast<uint64_t>(offset);
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_val_offset(dwarf_loc_regs_t* loc_regs) {
+  AddressType reg = operands_[0];
+  SignedType offset = static_cast<SignedType>(operands_[1]) * fde_->cie->data_alignment_factor;
+  (*loc_regs)[reg] = {.type = DWARF_LOCATION_VAL_OFFSET, .values = {static_cast<uint64_t>(offset)}};
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_val_offset_sf(dwarf_loc_regs_t* loc_regs) {
+  AddressType reg = operands_[0];
+  SignedType offset = static_cast<SignedType>(operands_[1]) * fde_->cie->data_alignment_factor;
+  (*loc_regs)[reg] = {.type = DWARF_LOCATION_VAL_OFFSET, .values = {static_cast<uint64_t>(offset)}};
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_val_expression(dwarf_loc_regs_t* loc_regs) {
+  AddressType reg = operands_[0];
+  (*loc_regs)[reg] = {.type = DWARF_LOCATION_VAL_EXPRESSION,
+                      .values = {operands_[1], memory_->cur_offset()}};
+  return true;
+}
+
+template <typename AddressType>
+bool DwarfCfa<AddressType>::cfa_gnu_negative_offset_extended(dwarf_loc_regs_t* loc_regs) {
+  AddressType reg = operands_[0];
+  SignedType offset = -static_cast<SignedType>(operands_[1]);
+  (*loc_regs)[reg] = {.type = DWARF_LOCATION_OFFSET, .values = {static_cast<uint64_t>(offset)}};
+  return true;
+}
+
+const DwarfCfaInfo::Info DwarfCfaInfo::kTable[64] = {
+    {
+        // 0x00 DW_CFA_nop
+        "DW_CFA_nop",
+        2,
+        0,
+        {},
+        {},
+    },
+    {
+        "DW_CFA_set_loc",  // 0x01 DW_CFA_set_loc
+        2,
+        1,
+        {DW_EH_PE_absptr},
+        {DWARF_DISPLAY_SET_LOC},
+    },
+    {
+        "DW_CFA_advance_loc1",  // 0x02 DW_CFA_advance_loc1
+        2,
+        1,
+        {DW_EH_PE_udata1},
+        {DWARF_DISPLAY_ADVANCE_LOC},
+    },
+    {
+        "DW_CFA_advance_loc2",  // 0x03 DW_CFA_advance_loc2
+        2,
+        1,
+        {DW_EH_PE_udata2},
+        {DWARF_DISPLAY_ADVANCE_LOC},
+    },
+    {
+        "DW_CFA_advance_loc4",  // 0x04 DW_CFA_advance_loc4
+        2,
+        1,
+        {DW_EH_PE_udata4},
+        {DWARF_DISPLAY_ADVANCE_LOC},
+    },
+    {
+        "DW_CFA_offset_extended",  // 0x05 DW_CFA_offset_extended
+        2,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_uleb128},
+        {DWARF_DISPLAY_REGISTER, DWARF_DISPLAY_NUMBER},
+    },
+    {
+        "DW_CFA_restore_extended",  // 0x06 DW_CFA_restore_extended
+        2,
+        1,
+        {DW_EH_PE_uleb128},
+        {DWARF_DISPLAY_REGISTER},
+    },
+    {
+        "DW_CFA_undefined",  // 0x07 DW_CFA_undefined
+        2,
+        1,
+        {DW_EH_PE_uleb128},
+        {DWARF_DISPLAY_REGISTER},
+    },
+    {
+        "DW_CFA_same_value",  // 0x08 DW_CFA_same_value
+        2,
+        1,
+        {DW_EH_PE_uleb128},
+        {DWARF_DISPLAY_REGISTER},
+    },
+    {
+        "DW_CFA_register",  // 0x09 DW_CFA_register
+        2,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_uleb128},
+        {DWARF_DISPLAY_REGISTER, DWARF_DISPLAY_REGISTER},
+    },
+    {
+        "DW_CFA_remember_state",  // 0x0a DW_CFA_remember_state
+        2,
+        0,
+        {},
+        {},
+    },
+    {
+        "DW_CFA_restore_state",  // 0x0b DW_CFA_restore_state
+        2,
+        0,
+        {},
+        {},
+    },
+    {
+        "DW_CFA_def_cfa",  // 0x0c DW_CFA_def_cfa
+        2,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_uleb128},
+        {DWARF_DISPLAY_REGISTER, DWARF_DISPLAY_NUMBER},
+    },
+    {
+        "DW_CFA_def_cfa_register",  // 0x0d DW_CFA_def_cfa_register
+        2,
+        1,
+        {DW_EH_PE_uleb128},
+        {DWARF_DISPLAY_REGISTER},
+    },
+    {
+        "DW_CFA_def_cfa_offset",  // 0x0e DW_CFA_def_cfa_offset
+        2,
+        1,
+        {DW_EH_PE_uleb128},
+        {DWARF_DISPLAY_NUMBER},
+    },
+    {
+        "DW_CFA_def_cfa_expression",  // 0x0f DW_CFA_def_cfa_expression
+        2,
+        1,
+        {DW_EH_PE_block},
+        {DWARF_DISPLAY_EVAL_BLOCK},
+    },
+    {
+        "DW_CFA_expression",  // 0x10 DW_CFA_expression
+        2,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_block},
+        {DWARF_DISPLAY_REGISTER, DWARF_DISPLAY_EVAL_BLOCK},
+    },
+    {
+        "DW_CFA_offset_extended_sf",  // 0x11 DW_CFA_offset_extend_sf
+        2,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_sleb128},
+        {DWARF_DISPLAY_REGISTER, DWARF_DISPLAY_SIGNED_NUMBER},
+    },
+    {
+        "DW_CFA_def_cfa_sf",  // 0x12 DW_CFA_def_cfa_sf
+        2,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_sleb128},
+        {DWARF_DISPLAY_REGISTER, DWARF_DISPLAY_SIGNED_NUMBER},
+    },
+    {
+        "DW_CFA_def_cfa_offset_sf",  // 0x13 DW_CFA_def_cfa_offset_sf
+        2,
+        1,
+        {DW_EH_PE_sleb128},
+        {DWARF_DISPLAY_SIGNED_NUMBER},
+    },
+    {
+        "DW_CFA_val_offset",  // 0x14 DW_CFA_val_offset
+        2,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_uleb128},
+        {DWARF_DISPLAY_REGISTER, DWARF_DISPLAY_NUMBER},
+    },
+    {
+        "DW_CFA_val_offset_sf",  // 0x15 DW_CFA_val_offset_sf
+        2,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_sleb128},
+        {DWARF_DISPLAY_REGISTER, DWARF_DISPLAY_SIGNED_NUMBER},
+    },
+    {
+        "DW_CFA_val_expression",  // 0x16 DW_CFA_val_expression
+        2,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_block},
+        {DWARF_DISPLAY_REGISTER, DWARF_DISPLAY_EVAL_BLOCK},
+    },
+    {nullptr, 0, 0, {}, {}},  // 0x17 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x18 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x19 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x1a illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x1b illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x1c DW_CFA_lo_user (Treat as illegal)
+    {nullptr, 0, 0, {}, {}},  // 0x1d illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x1e illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x1f illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x20 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x21 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x22 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x23 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x24 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x25 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x26 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x27 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x28 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x29 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x2a illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x2b illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x2c illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x2d DW_CFA_GNU_window_save (Treat as illegal)
+    {
+        "DW_CFA_GNU_args_size",  // 0x2e DW_CFA_GNU_args_size
+        2,
+        1,
+        {DW_EH_PE_uleb128},
+        {DWARF_DISPLAY_NUMBER},
+    },
+    {
+        "DW_CFA_GNU_negative_offset_extended",  // 0x2f DW_CFA_GNU_negative_offset_extended
+        2,
+        2,
+        {DW_EH_PE_uleb128, DW_EH_PE_uleb128},
+        {DWARF_DISPLAY_REGISTER, DWARF_DISPLAY_NUMBER},
+    },
+    {nullptr, 0, 0, {}, {}},  // 0x31 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x32 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x33 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x34 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x35 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x36 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x37 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x38 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x39 illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x3a illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x3b illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x3c illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x3d illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x3e illegal cfa
+    {nullptr, 0, 0, {}, {}},  // 0x3f DW_CFA_hi_user (Treat as illegal)
+};
+
+// Explicitly instantiate DwarfCfa.
+template class DwarfCfa<uint32_t>;
+template class DwarfCfa<uint64_t>;
diff --git a/libunwindstack/DwarfCfa.h b/libunwindstack/DwarfCfa.h
new file mode 100644
index 0000000..ce7da4a
--- /dev/null
+++ b/libunwindstack/DwarfCfa.h
@@ -0,0 +1,255 @@
+/*
+ * 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_CFA_H
+#define _LIBUNWINDSTACK_DWARF_CFA_H
+
+#include <stdint.h>
+
+#include <stack>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "DwarfError.h"
+#include "DwarfLocation.h"
+#include "DwarfMemory.h"
+#include "DwarfStructs.h"
+
+// DWARF Standard home: http://dwarfstd.org/
+// This code is based on DWARF 4: http://http://dwarfstd.org/doc/DWARF4.pdf
+// See section 6.4.2.1 for a description of the DW_CFA_xxx values.
+
+class DwarfCfaInfo {
+ public:
+  enum DisplayType : uint8_t {
+    DWARF_DISPLAY_NONE = 0,
+    DWARF_DISPLAY_REGISTER,
+    DWARF_DISPLAY_NUMBER,
+    DWARF_DISPLAY_SIGNED_NUMBER,
+    DWARF_DISPLAY_EVAL_BLOCK,
+    DWARF_DISPLAY_ADDRESS,
+    DWARF_DISPLAY_SET_LOC,
+    DWARF_DISPLAY_ADVANCE_LOC,
+  };
+
+  struct Info {
+    const char* name;
+    uint8_t supported_version;
+    uint8_t num_operands;
+    uint8_t operands[2];
+    uint8_t display_operands[2];
+  };
+
+  const static Info kTable[64];
+};
+
+template <typename AddressType>
+class DwarfCfa {
+  // Signed version of AddressType
+  typedef typename std::make_signed<AddressType>::type SignedType;
+
+ public:
+  DwarfCfa(DwarfMemory* memory, const DwarfFDE* fde) : memory_(memory), fde_(fde) {}
+  virtual ~DwarfCfa() = default;
+
+  bool GetLocationInfo(uint64_t pc, uint64_t start_offset, uint64_t end_offset,
+                       dwarf_loc_regs_t* loc_regs);
+
+  bool Log(uint32_t indent, uint64_t pc, uint64_t load_bias, uint64_t start_offset,
+           uint64_t end_offset);
+
+  DwarfError last_error() { return last_error_; }
+
+  AddressType cur_pc() { return cur_pc_; }
+
+  void set_cie_loc_regs(const dwarf_loc_regs_t* cie_loc_regs) { cie_loc_regs_ = cie_loc_regs; }
+
+ protected:
+  std::string GetOperandString(uint8_t operand, uint64_t value, uint64_t* cur_pc);
+
+  bool LogOffsetRegisterString(uint32_t indent, uint64_t cfa_offset, uint8_t reg);
+
+  bool LogInstruction(uint32_t indent, uint64_t cfa_offset, uint8_t op, uint64_t* cur_pc);
+
+ private:
+  DwarfError last_error_;
+  DwarfMemory* memory_;
+  const DwarfFDE* fde_;
+
+  AddressType cur_pc_;
+  const dwarf_loc_regs_t* cie_loc_regs_ = nullptr;
+  std::vector<AddressType> operands_;
+  std::stack<dwarf_loc_regs_t> loc_reg_state_;
+
+  // CFA processing functions.
+  bool cfa_nop(dwarf_loc_regs_t*);
+  bool cfa_set_loc(dwarf_loc_regs_t*);
+  bool cfa_advance_loc(dwarf_loc_regs_t*);
+  bool cfa_offset(dwarf_loc_regs_t*);
+  bool cfa_restore(dwarf_loc_regs_t*);
+  bool cfa_undefined(dwarf_loc_regs_t*);
+  bool cfa_same_value(dwarf_loc_regs_t*);
+  bool cfa_register(dwarf_loc_regs_t*);
+  bool cfa_remember_state(dwarf_loc_regs_t*);
+  bool cfa_restore_state(dwarf_loc_regs_t*);
+  bool cfa_def_cfa(dwarf_loc_regs_t*);
+  bool cfa_def_cfa_register(dwarf_loc_regs_t*);
+  bool cfa_def_cfa_offset(dwarf_loc_regs_t*);
+  bool cfa_def_cfa_expression(dwarf_loc_regs_t*);
+  bool cfa_expression(dwarf_loc_regs_t*);
+  bool cfa_offset_extended_sf(dwarf_loc_regs_t*);
+  bool cfa_def_cfa_sf(dwarf_loc_regs_t*);
+  bool cfa_def_cfa_offset_sf(dwarf_loc_regs_t*);
+  bool cfa_val_offset(dwarf_loc_regs_t*);
+  bool cfa_val_offset_sf(dwarf_loc_regs_t*);
+  bool cfa_val_expression(dwarf_loc_regs_t*);
+  bool cfa_gnu_negative_offset_extended(dwarf_loc_regs_t*);
+
+  using process_func = bool (DwarfCfa::*)(dwarf_loc_regs_t*);
+  constexpr static process_func kCallbackTable[64] = {
+      // 0x00 DW_CFA_nop
+      &DwarfCfa::cfa_nop,
+      // 0x01 DW_CFA_set_loc
+      &DwarfCfa::cfa_set_loc,
+      // 0x02 DW_CFA_advance_loc1
+      &DwarfCfa::cfa_advance_loc,
+      // 0x03 DW_CFA_advance_loc2
+      &DwarfCfa::cfa_advance_loc,
+      // 0x04 DW_CFA_advance_loc4
+      &DwarfCfa::cfa_advance_loc,
+      // 0x05 DW_CFA_offset_extended
+      &DwarfCfa::cfa_offset,
+      // 0x06 DW_CFA_restore_extended
+      &DwarfCfa::cfa_restore,
+      // 0x07 DW_CFA_undefined
+      &DwarfCfa::cfa_undefined,
+      // 0x08 DW_CFA_same_value
+      &DwarfCfa::cfa_same_value,
+      // 0x09 DW_CFA_register
+      &DwarfCfa::cfa_register,
+      // 0x0a DW_CFA_remember_state
+      &DwarfCfa::cfa_remember_state,
+      // 0x0b DW_CFA_restore_state
+      &DwarfCfa::cfa_restore_state,
+      // 0x0c DW_CFA_def_cfa
+      &DwarfCfa::cfa_def_cfa,
+      // 0x0d DW_CFA_def_cfa_register
+      &DwarfCfa::cfa_def_cfa_register,
+      // 0x0e DW_CFA_def_cfa_offset
+      &DwarfCfa::cfa_def_cfa_offset,
+      // 0x0f DW_CFA_def_cfa_expression
+      &DwarfCfa::cfa_def_cfa_expression,
+      // 0x10 DW_CFA_expression
+      &DwarfCfa::cfa_expression,
+      // 0x11 DW_CFA_offset_extended_sf
+      &DwarfCfa::cfa_offset_extended_sf,
+      // 0x12 DW_CFA_def_cfa_sf
+      &DwarfCfa::cfa_def_cfa_sf,
+      // 0x13 DW_CFA_def_cfa_offset_sf
+      &DwarfCfa::cfa_def_cfa_offset_sf,
+      // 0x14 DW_CFA_val_offset
+      &DwarfCfa::cfa_val_offset,
+      // 0x15 DW_CFA_val_offset_sf
+      &DwarfCfa::cfa_val_offset_sf,
+      // 0x16 DW_CFA_val_expression
+      &DwarfCfa::cfa_val_expression,
+      // 0x17 illegal cfa
+      nullptr,
+      // 0x18 illegal cfa
+      nullptr,
+      // 0x19 illegal cfa
+      nullptr,
+      // 0x1a illegal cfa
+      nullptr,
+      // 0x1b illegal cfa
+      nullptr,
+      // 0x1c DW_CFA_lo_user (Treat this as illegal)
+      nullptr,
+      // 0x1d illegal cfa
+      nullptr,
+      // 0x1e illegal cfa
+      nullptr,
+      // 0x1f illegal cfa
+      nullptr,
+      // 0x20 illegal cfa
+      nullptr,
+      // 0x21 illegal cfa
+      nullptr,
+      // 0x22 illegal cfa
+      nullptr,
+      // 0x23 illegal cfa
+      nullptr,
+      // 0x24 illegal cfa
+      nullptr,
+      // 0x25 illegal cfa
+      nullptr,
+      // 0x26 illegal cfa
+      nullptr,
+      // 0x27 illegal cfa
+      nullptr,
+      // 0x28 illegal cfa
+      nullptr,
+      // 0x29 illegal cfa
+      nullptr,
+      // 0x2a illegal cfa
+      nullptr,
+      // 0x2b illegal cfa
+      nullptr,
+      // 0x2c illegal cfa
+      nullptr,
+      // 0x2d DW_CFA_GNU_window_save (Treat this as illegal)
+      nullptr,
+      // 0x2e DW_CFA_GNU_args_size
+      &DwarfCfa::cfa_nop,
+      // 0x2f DW_CFA_GNU_negative_offset_extended
+      &DwarfCfa::cfa_gnu_negative_offset_extended,
+      // 0x30 illegal cfa
+      nullptr,
+      // 0x31 illegal cfa
+      nullptr,
+      // 0x32 illegal cfa
+      nullptr,
+      // 0x33 illegal cfa
+      nullptr,
+      // 0x34 illegal cfa
+      nullptr,
+      // 0x35 illegal cfa
+      nullptr,
+      // 0x36 illegal cfa
+      nullptr,
+      // 0x37 illegal cfa
+      nullptr,
+      // 0x38 illegal cfa
+      nullptr,
+      // 0x39 illegal cfa
+      nullptr,
+      // 0x3a illegal cfa
+      nullptr,
+      // 0x3b illegal cfa
+      nullptr,
+      // 0x3c illegal cfa
+      nullptr,
+      // 0x3d illegal cfa
+      nullptr,
+      // 0x3e illegal cfa
+      nullptr,
+      // 0x3f DW_CFA_hi_user (Treat this as illegal)
+      nullptr,
+  };
+};
+
+#endif  // _LIBUNWINDSTACK_DWARF_CFA_H
diff --git a/libunwindstack/DwarfLocation.h b/libunwindstack/DwarfLocation.h
new file mode 100644
index 0000000..062d125
--- /dev/null
+++ b/libunwindstack/DwarfLocation.h
@@ -0,0 +1,41 @@
+/*
+ * 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_LOCATION_H
+#define _LIBUNWINDSTACK_DWARF_LOCATION_H
+
+#include <stdint.h>
+
+#include <unordered_map>
+
+enum DwarfLocationEnum : uint8_t {
+  DWARF_LOCATION_INVALID = 0,
+  DWARF_LOCATION_UNDEFINED,
+  DWARF_LOCATION_OFFSET,
+  DWARF_LOCATION_VAL_OFFSET,
+  DWARF_LOCATION_REGISTER,
+  DWARF_LOCATION_EXPRESSION,
+  DWARF_LOCATION_VAL_EXPRESSION,
+};
+
+struct DwarfLocation {
+  DwarfLocationEnum type;
+  uint64_t values[2];
+};
+
+typedef std::unordered_map<uint16_t, DwarfLocation> dwarf_loc_regs_t;
+
+#endif  // _LIBUNWINDSTACK_DWARF_LOCATION_H
diff --git a/libunwindstack/DwarfStructs.h b/libunwindstack/DwarfStructs.h
new file mode 100644
index 0000000..57aac88
--- /dev/null
+++ b/libunwindstack/DwarfStructs.h
@@ -0,0 +1,52 @@
+/*
+ * 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_STRUCTS_H
+#define _LIBUNWINDSTACK_DWARF_STRUCTS_H
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "DwarfEncoding.h"
+
+struct DwarfCIE {
+  uint8_t version = 0;
+  uint8_t fde_address_encoding = DW_EH_PE_absptr;
+  uint8_t lsda_encoding = DW_EH_PE_omit;
+  uint8_t segment_size = 0;
+  std::vector<char> augmentation_string;
+  uint64_t personality_handler = 0;
+  uint64_t cfa_instructions_offset = 0;
+  uint64_t cfa_instructions_end = 0;
+  uint64_t code_alignment_factor = 0;
+  int64_t data_alignment_factor = 0;
+  uint64_t return_address_register = 0;
+};
+
+struct DwarfFDE {
+  uint64_t cie_offset = 0;
+  uint64_t cfa_instructions_offset = 0;
+  uint64_t cfa_instructions_end = 0;
+  uint64_t pc_start = 0;
+  uint64_t pc_end = 0;
+  uint64_t lsda_address = 0;
+  const DwarfCIE* cie = nullptr;
+};
+
+constexpr uint16_t CFA_REG = static_cast<uint16_t>(-1);
+
+#endif  // _LIBUNWINDSTACK_DWARF_STRUCTS_H
diff --git a/libunwindstack/tests/DwarfCfaLogTest.cpp b/libunwindstack/tests/DwarfCfaLogTest.cpp
new file mode 100644
index 0000000..3185bc3
--- /dev/null
+++ b/libunwindstack/tests/DwarfCfaLogTest.cpp
@@ -0,0 +1,814 @@
+/*
+ * 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 <memory>
+#include <type_traits>
+#include <unordered_map>
+
+#include <android-base/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "DwarfCfa.h"
+#include "DwarfLocation.h"
+#include "DwarfMemory.h"
+#include "DwarfStructs.h"
+#include "Log.h"
+
+#include "LogFake.h"
+#include "MemoryFake.h"
+
+template <typename TypeParam>
+class DwarfCfaLogTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ResetLogs();
+    memory_.Clear();
+
+    dmem_.reset(new DwarfMemory(&memory_));
+
+    cie_.cfa_instructions_offset = 0x1000;
+    cie_.cfa_instructions_end = 0x1030;
+    // These two values should be different to distinguish between
+    // operations that deal with code versus data.
+    cie_.code_alignment_factor = 4;
+    cie_.data_alignment_factor = 8;
+
+    fde_.cfa_instructions_offset = 0x2000;
+    fde_.cfa_instructions_end = 0x2030;
+    fde_.pc_start = 0x2000;
+    fde_.pc_end = 0x2000;
+    fde_.pc_end = 0x10000;
+    fde_.cie = &cie_;
+    cfa_.reset(new DwarfCfa<TypeParam>(dmem_.get(), &fde_));
+  }
+
+  MemoryFake memory_;
+  std::unique_ptr<DwarfMemory> dmem_;
+  std::unique_ptr<DwarfCfa<TypeParam>> cfa_;
+  DwarfCIE cie_;
+  DwarfFDE fde_;
+};
+TYPED_TEST_CASE_P(DwarfCfaLogTest);
+
+// NOTE: All class variable references have to be prefaced with this->.
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_illegal) {
+  for (uint8_t i = 0x17; i < 0x3f; i++) {
+    if (i == 0x2e || i == 0x2f) {
+      // Skip gnu extension ops.
+      continue;
+    }
+    this->memory_.SetMemory(0x2000, std::vector<uint8_t>{i});
+
+    ResetLogs();
+    ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2000, 0x2001));
+    std::string expected = "4 unwind Illegal\n";
+    expected += android::base::StringPrintf("4 unwind Raw Data: 0x%02x\n", i);
+    ASSERT_EQ(expected, GetFakeLogPrint());
+    ASSERT_EQ("", GetFakeLogBuf());
+  }
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_nop) {
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x00});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2000, 0x2001));
+  std::string expected =
+      "4 unwind DW_CFA_nop\n"
+      "4 unwind Raw Data: 0x00\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_offset) {
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x83, 0x04});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2000, 0x2002));
+  std::string expected =
+      "4 unwind DW_CFA_offset register(3) 4\n"
+      "4 unwind Raw Data: 0x83 0x04\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x2100, std::vector<uint8_t>{0x83, 0x84, 0x01});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2100, 0x2103));
+  expected =
+      "4 unwind DW_CFA_offset register(3) 132\n"
+      "4 unwind Raw Data: 0x83 0x84 0x01\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_offset_extended) {
+  this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x05, 0x03, 0x02});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x500, 0x503));
+  std::string expected =
+      "4 unwind DW_CFA_offset_extended register(3) 2\n"
+      "4 unwind Raw Data: 0x05 0x03 0x02\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x1500, std::vector<uint8_t>{0x05, 0x81, 0x01, 0x82, 0x12});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x1500, 0x1505));
+  expected =
+      "4 unwind DW_CFA_offset_extended register(129) 2306\n"
+      "4 unwind Raw Data: 0x05 0x81 0x01 0x82 0x12\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_offset_extended_sf) {
+  this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x11, 0x05, 0x10});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x500, 0x503));
+  std::string expected =
+      "4 unwind DW_CFA_offset_extended_sf register(5) 16\n"
+      "4 unwind Raw Data: 0x11 0x05 0x10\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Check a negative value for the offset.
+  ResetLogs();
+  this->memory_.SetMemory(0x1500, std::vector<uint8_t>{0x11, 0x86, 0x01, 0xff, 0x7f});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x1500, 0x1505));
+  expected =
+      "4 unwind DW_CFA_offset_extended_sf register(134) -1\n"
+      "4 unwind Raw Data: 0x11 0x86 0x01 0xff 0x7f\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_restore) {
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0xc2});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2000, 0x2001));
+  std::string expected =
+      "4 unwind DW_CFA_restore register(2)\n"
+      "4 unwind Raw Data: 0xc2\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x3000, std::vector<uint8_t>{0x82, 0x04, 0xc2});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x3000, 0x3003));
+  expected =
+      "4 unwind DW_CFA_offset register(2) 4\n"
+      "4 unwind Raw Data: 0x82 0x04\n"
+      "4 unwind DW_CFA_restore register(2)\n"
+      "4 unwind Raw Data: 0xc2\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_restore_extended) {
+  this->memory_.SetMemory(0x4000, std::vector<uint8_t>{0x06, 0x08});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x4000, 0x4002));
+  std::string expected =
+      "4 unwind DW_CFA_restore_extended register(8)\n"
+      "4 unwind Raw Data: 0x06 0x08\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x5000, std::vector<uint8_t>{0x05, 0x82, 0x02, 0x04, 0x06, 0x82, 0x02});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x5000, 0x5007));
+  expected =
+      "4 unwind DW_CFA_offset_extended register(258) 4\n"
+      "4 unwind Raw Data: 0x05 0x82 0x02 0x04\n"
+      "4 unwind DW_CFA_restore_extended register(258)\n"
+      "4 unwind Raw Data: 0x06 0x82 0x02\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_set_loc) {
+  uint8_t buffer[1 + sizeof(TypeParam)];
+  buffer[0] = 0x1;
+  TypeParam address;
+  std::string raw_data("Raw Data: 0x01 ");
+  std::string address_str;
+  if (std::is_same<TypeParam, uint32_t>::value) {
+    address = 0x81234578U;
+    address_str = "0x81234578";
+    raw_data += "0x78 0x45 0x23 0x81";
+  } else {
+    address = 0x8123456712345678ULL;
+    address_str = "0x8123456712345678";
+    raw_data += "0x78 0x56 0x34 0x12 0x67 0x45 0x23 0x81";
+  }
+  memcpy(&buffer[1], &address, sizeof(address));
+
+  this->memory_.SetMemory(0x50, buffer, sizeof(buffer));
+  ResetLogs();
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x50, 0x51 + sizeof(TypeParam)));
+  std::string expected = "4 unwind DW_CFA_set_loc " + address_str + "\n";
+  expected += "4 unwind " + raw_data + "\n";
+  expected += "4 unwind \n";
+  expected += "4 unwind PC " + address_str + "\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Check for a set going back.
+  ResetLogs();
+  this->fde_.pc_start = address + 0x10;
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x50, 0x51 + sizeof(TypeParam)));
+  expected = "4 unwind DW_CFA_set_loc " + address_str + "\n";
+  expected += "4 unwind " + raw_data + "\n";
+  expected += "4 unwind \n";
+  expected += "4 unwind PC " + address_str + "\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_advance_loc) {
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x44});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x201));
+  std::string expected =
+      "4 unwind DW_CFA_advance_loc 4\n"
+      "4 unwind Raw Data: 0x44\n"
+      "4 unwind \n"
+      "4 unwind PC 0x2010\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x100, 0x200, 0x201));
+  expected =
+      "4 unwind DW_CFA_advance_loc 4\n"
+      "4 unwind Raw Data: 0x44\n"
+      "4 unwind \n"
+      "4 unwind PC 0x2110\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_advance_loc1) {
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x02, 0x04});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x202));
+  std::string expected =
+      "4 unwind DW_CFA_advance_loc1 4\n"
+      "4 unwind Raw Data: 0x02 0x04\n"
+      "4 unwind \n"
+      "4 unwind PC 0x2004\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x10, 0x200, 0x202));
+  expected =
+      "4 unwind DW_CFA_advance_loc1 4\n"
+      "4 unwind Raw Data: 0x02 0x04\n"
+      "4 unwind \n"
+      "4 unwind PC 0x2014\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_advance_loc2) {
+  this->memory_.SetMemory(0x600, std::vector<uint8_t>{0x03, 0x04, 0x03});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x600, 0x603));
+  std::string expected =
+      "4 unwind DW_CFA_advance_loc2 772\n"
+      "4 unwind Raw Data: 0x03 0x04 0x03\n"
+      "4 unwind \n"
+      "4 unwind PC 0x2304\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x1000, 0x600, 0x603));
+  expected =
+      "4 unwind DW_CFA_advance_loc2 772\n"
+      "4 unwind Raw Data: 0x03 0x04 0x03\n"
+      "4 unwind \n"
+      "4 unwind PC 0x3304\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_advance_loc4) {
+  this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x04, 0x04, 0x03, 0x02, 0x01});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x500, 0x505));
+  std::string expected =
+      "4 unwind DW_CFA_advance_loc4 16909060\n"
+      "4 unwind Raw Data: 0x04 0x04 0x03 0x02 0x01\n"
+      "4 unwind \n"
+      "4 unwind PC 0x1022304\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2000, 0x500, 0x505));
+  expected =
+      "4 unwind DW_CFA_advance_loc4 16909060\n"
+      "4 unwind Raw Data: 0x04 0x04 0x03 0x02 0x01\n"
+      "4 unwind \n"
+      "4 unwind PC 0x1024304\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_undefined) {
+  this->memory_.SetMemory(0xa00, std::vector<uint8_t>{0x07, 0x09});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0xa00, 0xa02));
+  std::string expected =
+      "4 unwind DW_CFA_undefined register(9)\n"
+      "4 unwind Raw Data: 0x07 0x09\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  dwarf_loc_regs_t cie_loc_regs;
+  this->memory_.SetMemory(0x1a00, std::vector<uint8_t>{0x07, 0x81, 0x01});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x1a00, 0x1a03));
+  expected =
+      "4 unwind DW_CFA_undefined register(129)\n"
+      "4 unwind Raw Data: 0x07 0x81 0x01\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_same) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x08, 0x7f});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+  std::string expected =
+      "4 unwind DW_CFA_same_value register(127)\n"
+      "4 unwind Raw Data: 0x08 0x7f\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x2100, std::vector<uint8_t>{0x08, 0xff, 0x01});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2100, 0x2103));
+  expected =
+      "4 unwind DW_CFA_same_value register(255)\n"
+      "4 unwind Raw Data: 0x08 0xff 0x01\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_register) {
+  this->memory_.SetMemory(0x300, std::vector<uint8_t>{0x09, 0x02, 0x01});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x300, 0x303));
+  std::string expected =
+      "4 unwind DW_CFA_register register(2) register(1)\n"
+      "4 unwind Raw Data: 0x09 0x02 0x01\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x4300, std::vector<uint8_t>{0x09, 0xff, 0x01, 0xff, 0x03});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x4300, 0x4305));
+  expected =
+      "4 unwind DW_CFA_register register(255) register(511)\n"
+      "4 unwind Raw Data: 0x09 0xff 0x01 0xff 0x03\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_state) {
+  this->memory_.SetMemory(0x300, std::vector<uint8_t>{0x0a});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x300, 0x301));
+
+  std::string expected =
+      "4 unwind DW_CFA_remember_state\n"
+      "4 unwind Raw Data: 0x0a\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x4300, std::vector<uint8_t>{0x0b});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x4300, 0x4301));
+
+  expected =
+      "4 unwind DW_CFA_restore_state\n"
+      "4 unwind Raw Data: 0x0b\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_state_cfa_offset_restore) {
+  this->memory_.SetMemory(0x3000, std::vector<uint8_t>{0x0a, 0x0e, 0x40, 0x0b});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x3000, 0x3004));
+
+  std::string expected =
+      "4 unwind DW_CFA_remember_state\n"
+      "4 unwind Raw Data: 0x0a\n"
+      "4 unwind DW_CFA_def_cfa_offset 64\n"
+      "4 unwind Raw Data: 0x0e 0x40\n"
+      "4 unwind DW_CFA_restore_state\n"
+      "4 unwind Raw Data: 0x0b\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0c, 0x7f, 0x74});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x103));
+
+  std::string expected =
+      "4 unwind DW_CFA_def_cfa register(127) 116\n"
+      "4 unwind Raw Data: 0x0c 0x7f 0x74\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x0c, 0xff, 0x02, 0xf4, 0x04});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x205));
+
+  expected =
+      "4 unwind DW_CFA_def_cfa register(383) 628\n"
+      "4 unwind Raw Data: 0x0c 0xff 0x02 0xf4 0x04\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa_sf) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x12, 0x30, 0x25});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x103));
+
+  std::string expected =
+      "4 unwind DW_CFA_def_cfa_sf register(48) 37\n"
+      "4 unwind Raw Data: 0x12 0x30 0x25\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Test a negative value.
+  ResetLogs();
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x12, 0xa3, 0x01, 0xfa, 0x7f});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x205));
+
+  expected =
+      "4 unwind DW_CFA_def_cfa_sf register(163) -6\n"
+      "4 unwind Raw Data: 0x12 0xa3 0x01 0xfa 0x7f\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa_register) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0d, 0x72});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+
+  std::string expected =
+      "4 unwind DW_CFA_def_cfa_register register(114)\n"
+      "4 unwind Raw Data: 0x0d 0x72\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x0d, 0xf9, 0x20});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x203));
+
+  expected =
+      "4 unwind DW_CFA_def_cfa_register register(4217)\n"
+      "4 unwind Raw Data: 0x0d 0xf9 0x20\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa_offset) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0e, 0x59});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+
+  std::string expected =
+      "4 unwind DW_CFA_def_cfa_offset 89\n"
+      "4 unwind Raw Data: 0x0e 0x59\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+
+  expected =
+      "4 unwind DW_CFA_def_cfa_offset 89\n"
+      "4 unwind Raw Data: 0x0e 0x59\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x0e, 0xd4, 0x0a});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x203));
+
+  expected =
+      "4 unwind DW_CFA_def_cfa_offset 1364\n"
+      "4 unwind Raw Data: 0x0e 0xd4 0x0a\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa_offset_sf) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x13, 0x23});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+
+  std::string expected =
+      "4 unwind DW_CFA_def_cfa_offset_sf 35\n"
+      "4 unwind Raw Data: 0x13 0x23\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x102));
+
+  expected =
+      "4 unwind DW_CFA_def_cfa_offset_sf 35\n"
+      "4 unwind Raw Data: 0x13 0x23\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Negative offset.
+  ResetLogs();
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x13, 0xf6, 0x7f});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x203));
+
+  expected =
+      "4 unwind DW_CFA_def_cfa_offset_sf -10\n"
+      "4 unwind Raw Data: 0x13 0xf6 0x7f\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_def_cfa_expression) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0f, 0x04, 0x01, 0x02, 0x04, 0x05});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x106));
+
+  std::string expected =
+      "4 unwind DW_CFA_def_cfa_expression 4\n"
+      "4 unwind Raw Data: 0x0f 0x04 0x01 0x02 0x04 0x05\n"
+      "4 unwind   Illegal\n"
+      "4 unwind   Raw Data: 0x01\n"
+      "4 unwind   Illegal\n"
+      "4 unwind   Raw Data: 0x02\n"
+      "4 unwind   Illegal\n"
+      "4 unwind   Raw Data: 0x04\n"
+      "4 unwind   Illegal\n"
+      "4 unwind   Raw Data: 0x05\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  std::vector<uint8_t> ops{0x0f, 0x81, 0x01};
+  expected = "4 unwind Raw Data: 0x0f 0x81 0x01";
+  std::string op_string;
+  for (uint8_t i = 3; i < 132; i++) {
+    ops.push_back(0x05);
+    op_string +=
+        "4 unwind   Illegal\n"
+        "4 unwind   Raw Data: 0x05\n";
+    expected += " 0x05";
+    if (((i + 1) % 10) == 0) {
+      expected += "\n4 unwind Raw Data:";
+    }
+  }
+  expected += '\n';
+  this->memory_.SetMemory(0x200, ops);
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x284));
+
+  expected = "4 unwind DW_CFA_def_cfa_expression 129\n" + expected;
+  ASSERT_EQ(expected + op_string, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_expression) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x10, 0x04, 0x02, 0xc0, 0xc1});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x105));
+
+  std::string expected =
+      "4 unwind DW_CFA_expression register(4) 2\n"
+      "4 unwind Raw Data: 0x10 0x04 0x02 0xc0 0xc1\n"
+      "4 unwind   Illegal\n"
+      "4 unwind   Raw Data: 0xc0\n"
+      "4 unwind   Illegal\n"
+      "4 unwind   Raw Data: 0xc1\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  std::vector<uint8_t> ops{0x10, 0xff, 0x01, 0x82, 0x01};
+  expected = "4 unwind Raw Data: 0x10 0xff 0x01 0x82 0x01";
+  std::string op_string;
+  for (uint8_t i = 5; i < 135; i++) {
+    ops.push_back(0xa0 + (i - 5) % 96);
+    op_string += "4 unwind   Illegal\n";
+    op_string += android::base::StringPrintf("4 unwind   Raw Data: 0x%02x\n", ops.back());
+    expected += android::base::StringPrintf(" 0x%02x", ops.back());
+    if (((i + 1) % 10) == 0) {
+      expected += "\n4 unwind Raw Data:";
+    }
+  }
+  expected = "4 unwind DW_CFA_expression register(255) 130\n" + expected + "\n";
+
+  this->memory_.SetMemory(0x200, ops);
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x200, 0x287));
+
+  ASSERT_EQ(expected + op_string, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_val_offset) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x14, 0x45, 0x54});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x103));
+
+  std::string expected =
+      "4 unwind DW_CFA_val_offset register(69) 84\n"
+      "4 unwind Raw Data: 0x14 0x45 0x54\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x400, std::vector<uint8_t>{0x14, 0xa2, 0x02, 0xb4, 0x05});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x400, 0x405));
+
+  expected =
+      "4 unwind DW_CFA_val_offset register(290) 692\n"
+      "4 unwind Raw Data: 0x14 0xa2 0x02 0xb4 0x05\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_val_offset_sf) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x15, 0x56, 0x12});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x103));
+
+  std::string expected =
+      "4 unwind DW_CFA_val_offset_sf register(86) 18\n"
+      "4 unwind Raw Data: 0x15 0x56 0x12\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Negative value.
+  ResetLogs();
+  this->memory_.SetMemory(0xa00, std::vector<uint8_t>{0x15, 0xff, 0x01, 0xc0, 0x7f});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0xa00, 0xa05));
+
+  expected =
+      "4 unwind DW_CFA_val_offset_sf register(255) -64\n"
+      "4 unwind Raw Data: 0x15 0xff 0x01 0xc0 0x7f\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_val_expression) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x16, 0x05, 0x02, 0xb0, 0xb1});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x100, 0x105));
+
+  std::string expected =
+      "4 unwind DW_CFA_val_expression register(5) 2\n"
+      "4 unwind Raw Data: 0x16 0x05 0x02 0xb0 0xb1\n"
+      "4 unwind   Illegal\n"
+      "4 unwind   Raw Data: 0xb0\n"
+      "4 unwind   Illegal\n"
+      "4 unwind   Raw Data: 0xb1\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  std::vector<uint8_t> ops{0x16, 0x83, 0x10, 0xa8, 0x01};
+  expected = "4 unwind Raw Data: 0x16 0x83 0x10 0xa8 0x01";
+  std::string op_string;
+  for (uint8_t i = 0; i < 168; i++) {
+    ops.push_back(0xa0 + (i % 96));
+    op_string += "4 unwind   Illegal\n";
+    op_string += android::base::StringPrintf("4 unwind   Raw Data: 0x%02x\n", ops.back());
+    expected += android::base::StringPrintf(" 0x%02x", ops.back());
+    if (((i + 6) % 10) == 0) {
+      expected += "\n4 unwind Raw Data:";
+    }
+  }
+  expected = "4 unwind DW_CFA_val_expression register(2051) 168\n" + expected + "\n";
+
+  this->memory_.SetMemory(0xa00, ops);
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0xa00, 0xaad));
+
+  ASSERT_EQ(expected + op_string, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_gnu_args_size) {
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x2e, 0x04});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x2000, 0x2002));
+
+  std::string expected =
+      "4 unwind DW_CFA_GNU_args_size 4\n"
+      "4 unwind Raw Data: 0x2e 0x04\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x5000, std::vector<uint8_t>{0x2e, 0xa4, 0x80, 0x04});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x5000, 0x5004));
+
+  expected =
+      "4 unwind DW_CFA_GNU_args_size 65572\n"
+      "4 unwind Raw Data: 0x2e 0xa4 0x80 0x04\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_gnu_negative_offset_extended) {
+  this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x2f, 0x08, 0x10});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x500, 0x503));
+
+  std::string expected =
+      "4 unwind DW_CFA_GNU_negative_offset_extended register(8) 16\n"
+      "4 unwind Raw Data: 0x2f 0x08 0x10\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x1500, std::vector<uint8_t>{0x2f, 0x81, 0x02, 0xff, 0x01});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x1500, 0x1505));
+
+  expected =
+      "4 unwind DW_CFA_GNU_negative_offset_extended register(257) 255\n"
+      "4 unwind Raw Data: 0x2f 0x81 0x02 0xff 0x01\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaLogTest, cfa_register_override) {
+  this->memory_.SetMemory(0x300, std::vector<uint8_t>{0x09, 0x02, 0x01, 0x09, 0x02, 0x04});
+
+  ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0, 0x300, 0x306));
+
+  std::string expected =
+      "4 unwind DW_CFA_register register(2) register(1)\n"
+      "4 unwind Raw Data: 0x09 0x02 0x01\n"
+      "4 unwind DW_CFA_register register(2) register(4)\n"
+      "4 unwind Raw Data: 0x09 0x02 0x04\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+REGISTER_TYPED_TEST_CASE_P(DwarfCfaLogTest, cfa_illegal, cfa_nop, cfa_offset, cfa_offset_extended,
+                           cfa_offset_extended_sf, cfa_restore, cfa_restore_extended, cfa_set_loc,
+                           cfa_advance_loc, cfa_advance_loc1, cfa_advance_loc2, cfa_advance_loc4,
+                           cfa_undefined, cfa_same, cfa_register, cfa_state,
+                           cfa_state_cfa_offset_restore, cfa_def_cfa, cfa_def_cfa_sf,
+                           cfa_def_cfa_register, cfa_def_cfa_offset, cfa_def_cfa_offset_sf,
+                           cfa_def_cfa_expression, cfa_expression, cfa_val_offset,
+                           cfa_val_offset_sf, cfa_val_expression, cfa_gnu_args_size,
+                           cfa_gnu_negative_offset_extended, cfa_register_override);
+
+typedef ::testing::Types<uint32_t, uint64_t> DwarfCfaLogTestTypes;
+INSTANTIATE_TYPED_TEST_CASE_P(, DwarfCfaLogTest, DwarfCfaLogTestTypes);
diff --git a/libunwindstack/tests/DwarfCfaTest.cpp b/libunwindstack/tests/DwarfCfaTest.cpp
new file mode 100644
index 0000000..6cf028a
--- /dev/null
+++ b/libunwindstack/tests/DwarfCfaTest.cpp
@@ -0,0 +1,959 @@
+/*
+ * 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 <memory>
+#include <unordered_map>
+
+#include <gtest/gtest.h>
+
+#include "DwarfCfa.h"
+#include "DwarfLocation.h"
+#include "DwarfMemory.h"
+#include "DwarfStructs.h"
+#include "Log.h"
+
+#include "LogFake.h"
+#include "MemoryFake.h"
+
+template <typename TypeParam>
+class DwarfCfaTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ResetLogs();
+    memory_.Clear();
+
+    dmem_.reset(new DwarfMemory(&memory_));
+
+    cie_.cfa_instructions_offset = 0x1000;
+    cie_.cfa_instructions_end = 0x1030;
+    // These two values should be different to distinguish between
+    // operations that deal with code versus data.
+    cie_.code_alignment_factor = 4;
+    cie_.data_alignment_factor = 8;
+
+    fde_.cfa_instructions_offset = 0x2000;
+    fde_.cfa_instructions_end = 0x2030;
+    fde_.pc_start = 0x2000;
+    fde_.cie = &cie_;
+
+    cfa_.reset(new DwarfCfa<TypeParam>(dmem_.get(), &fde_));
+  }
+
+  MemoryFake memory_;
+  std::unique_ptr<DwarfMemory> dmem_;
+  std::unique_ptr<DwarfCfa<TypeParam>> cfa_;
+  DwarfCIE cie_;
+  DwarfFDE fde_;
+};
+TYPED_TEST_CASE_P(DwarfCfaTest);
+
+// NOTE: All test class variables need to be referenced as this->.
+
+TYPED_TEST_P(DwarfCfaTest, cfa_illegal) {
+  for (uint8_t i = 0x17; i < 0x3f; i++) {
+    if (i == 0x2e || i == 0x2f) {
+      // Skip gnu extension ops.
+      continue;
+    }
+    this->memory_.SetMemory(0x2000, std::vector<uint8_t>{i});
+    dwarf_loc_regs_t loc_regs;
+
+    ASSERT_FALSE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs));
+    ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->cfa_->last_error());
+    ASSERT_EQ(0x2001U, this->dmem_->cur_offset());
+
+    ASSERT_EQ("", GetFakeLogPrint());
+    ASSERT_EQ("", GetFakeLogBuf());
+  }
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_nop) {
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x00});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs));
+  ASSERT_EQ(0x2001U, this->dmem_->cur_offset());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+// This test needs to be examined.
+TYPED_TEST_P(DwarfCfaTest, cfa_offset) {
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x83, 0x04});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2002, &loc_regs));
+  ASSERT_EQ(0x2002U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(3);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_OFFSET, location->second.type);
+  ASSERT_EQ(32U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x2100, std::vector<uint8_t>{0x83, 0x84, 0x01});
+  loc_regs.clear();
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2100, 0x2103, &loc_regs));
+  ASSERT_EQ(0x2103U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  location = loc_regs.find(3);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_OFFSET, location->second.type);
+  ASSERT_EQ(1056U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_offset_extended) {
+  this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x05, 0x03, 0x02});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x500, 0x503, &loc_regs));
+  ASSERT_EQ(0x503U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(3);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_OFFSET, location->second.type);
+  ASSERT_EQ(2U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0x1500, std::vector<uint8_t>{0x05, 0x81, 0x01, 0x82, 0x12});
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x1500, 0x1505, &loc_regs));
+  ASSERT_EQ(0x1505U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  location = loc_regs.find(129);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_OFFSET, location->second.type);
+  ASSERT_EQ(2306U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_offset_extended_sf) {
+  this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x11, 0x05, 0x10});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x500, 0x503, &loc_regs));
+  ASSERT_EQ(0x503U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(5);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_OFFSET, location->second.type);
+  ASSERT_EQ(0x80U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Check a negative value for the offset.
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0x1500, std::vector<uint8_t>{0x11, 0x86, 0x01, 0xff, 0x7f});
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x1500, 0x1505, &loc_regs));
+  ASSERT_EQ(0x1505U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  location = loc_regs.find(134);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_OFFSET, location->second.type);
+  ASSERT_EQ(static_cast<uint64_t>(-8), location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_restore) {
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0xc2});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_FALSE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs));
+  ASSERT_EQ(DWARF_ERROR_ILLEGAL_STATE, this->cfa_->last_error());
+  ASSERT_EQ(0x2001U, this->dmem_->cur_offset());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  ASSERT_EQ("4 unwind restore while processing cie\n", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  dwarf_loc_regs_t cie_loc_regs;
+  cie_loc_regs[2] = {.type = DWARF_LOCATION_REGISTER, .values = {0, 0}};
+  this->cfa_->set_cie_loc_regs(&cie_loc_regs);
+  this->memory_.SetMemory(0x3000, std::vector<uint8_t>{0x82, 0x04, 0xc2});
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x3000, 0x3003, &loc_regs));
+  ASSERT_EQ(0x3003U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(2);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, location->second.type);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_restore_extended) {
+  this->memory_.SetMemory(0x4000, std::vector<uint8_t>{0x06, 0x08});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_FALSE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x4000, 0x4002, &loc_regs));
+  ASSERT_EQ(DWARF_ERROR_ILLEGAL_STATE, this->cfa_->last_error());
+  ASSERT_EQ(0x4002U, this->dmem_->cur_offset());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  ASSERT_EQ("4 unwind restore while processing cie\n", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0x5000, std::vector<uint8_t>{0x05, 0x82, 0x02, 0x04, 0x06, 0x82, 0x02});
+  dwarf_loc_regs_t cie_loc_regs;
+  cie_loc_regs[258] = {.type = DWARF_LOCATION_REGISTER, .values = {0, 0}};
+  this->cfa_->set_cie_loc_regs(&cie_loc_regs);
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x5000, 0x5007, &loc_regs));
+  ASSERT_EQ(0x5007U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(258);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, location->second.type);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_set_loc) {
+  uint8_t buffer[1 + sizeof(TypeParam)];
+  buffer[0] = 0x1;
+  TypeParam address;
+  std::string raw_data("Raw Data: 0x01 ");
+  std::string address_str;
+  if (sizeof(TypeParam) == 4) {
+    address = 0x81234578U;
+    address_str = "0x81234578";
+    raw_data += "0x78 0x45 0x23 0x81";
+  } else {
+    address = 0x8123456712345678ULL;
+    address_str = "0x8123456712345678";
+    raw_data += "0x78 0x56 0x34 0x12 0x67 0x45 0x23 0x81";
+  }
+  memcpy(&buffer[1], &address, sizeof(address));
+
+  this->memory_.SetMemory(0x50, buffer, sizeof(buffer));
+  ResetLogs();
+  dwarf_loc_regs_t loc_regs;
+  ASSERT_TRUE(
+      this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x50, 0x51 + sizeof(TypeParam), &loc_regs));
+  ASSERT_EQ(0x51 + sizeof(TypeParam), this->dmem_->cur_offset());
+  ASSERT_EQ(address, this->cfa_->cur_pc());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Check for a set going back.
+  ResetLogs();
+  loc_regs.clear();
+  this->fde_.pc_start = address + 0x10;
+  ASSERT_TRUE(
+      this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x50, 0x51 + sizeof(TypeParam), &loc_regs));
+  ASSERT_EQ(0x51 + sizeof(TypeParam), this->dmem_->cur_offset());
+  ASSERT_EQ(address, this->cfa_->cur_pc());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  std::string cur_address_str(address_str);
+  cur_address_str[cur_address_str.size() - 2] = '8';
+  std::string expected = "4 unwind Warning: PC is moving backwards: old " + cur_address_str +
+                         " new " + address_str + "\n";
+  ASSERT_EQ(expected, GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_advance_loc1) {
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x02, 0x04});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x200, 0x202, &loc_regs));
+  ASSERT_EQ(0x202U, this->dmem_->cur_offset());
+  ASSERT_EQ(this->fde_.pc_start + 0x10, this->cfa_->cur_pc());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_advance_loc2) {
+  this->memory_.SetMemory(0x600, std::vector<uint8_t>{0x03, 0x04, 0x03});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x600, 0x603, &loc_regs));
+  ASSERT_EQ(0x603U, this->dmem_->cur_offset());
+  ASSERT_EQ(this->fde_.pc_start + 0xc10U, this->cfa_->cur_pc());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_advance_loc4) {
+  this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x04, 0x04, 0x03, 0x02, 0x01});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x500, 0x505, &loc_regs));
+  ASSERT_EQ(0x505U, this->dmem_->cur_offset());
+  ASSERT_EQ(this->fde_.pc_start + 0x4080c10, this->cfa_->cur_pc());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_undefined) {
+  this->memory_.SetMemory(0xa00, std::vector<uint8_t>{0x07, 0x09});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0xa00, 0xa02, &loc_regs));
+  ASSERT_EQ(0xa02U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(9);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_UNDEFINED, location->second.type);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0x1a00, std::vector<uint8_t>{0x07, 0x81, 0x01});
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x1a00, 0x1a03, &loc_regs));
+  ASSERT_EQ(0x1a03U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  location = loc_regs.find(129);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_UNDEFINED, location->second.type);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_same) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x08, 0x7f});
+  dwarf_loc_regs_t loc_regs;
+
+  loc_regs[127] = {.type = DWARF_LOCATION_REGISTER, .values = {0, 0}};
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x102, &loc_regs));
+  ASSERT_EQ(0x102U, this->dmem_->cur_offset());
+  ASSERT_EQ(0U, loc_regs.size());
+  ASSERT_EQ(0U, loc_regs.count(127));
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0x2100, std::vector<uint8_t>{0x08, 0xff, 0x01});
+
+  loc_regs[255] = {.type = DWARF_LOCATION_REGISTER, .values = {0, 0}};
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2100, 0x2103, &loc_regs));
+  ASSERT_EQ(0x2103U, this->dmem_->cur_offset());
+  ASSERT_EQ(0U, loc_regs.size());
+  ASSERT_EQ(0U, loc_regs.count(255));
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_register) {
+  this->memory_.SetMemory(0x300, std::vector<uint8_t>{0x09, 0x02, 0x01});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x300, 0x303, &loc_regs));
+  ASSERT_EQ(0x303U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(2);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, location->second.type);
+  ASSERT_EQ(1U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0x4300, std::vector<uint8_t>{0x09, 0xff, 0x01, 0xff, 0x03});
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x4300, 0x4305, &loc_regs));
+  ASSERT_EQ(0x4305U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  location = loc_regs.find(255);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, location->second.type);
+  ASSERT_EQ(511U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_state) {
+  this->memory_.SetMemory(0x300, std::vector<uint8_t>{0x0a});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x300, 0x301, &loc_regs));
+  ASSERT_EQ(0x301U, this->dmem_->cur_offset());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x4300, std::vector<uint8_t>{0x0b});
+
+  loc_regs.clear();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x4300, 0x4301, &loc_regs));
+  ASSERT_EQ(0x4301U, this->dmem_->cur_offset());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x85, 0x02, 0x0a, 0x86, 0x04, 0x0b});
+
+  loc_regs.clear();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2005, &loc_regs));
+  ASSERT_EQ(0x2005U, this->dmem_->cur_offset());
+  ASSERT_EQ(2U, loc_regs.size());
+  ASSERT_NE(loc_regs.end(), loc_regs.find(5));
+  ASSERT_NE(loc_regs.end(), loc_regs.find(6));
+
+  loc_regs.clear();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2006, &loc_regs));
+  ASSERT_EQ(0x2006U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_NE(loc_regs.end(), loc_regs.find(5));
+
+  ResetLogs();
+  this->memory_.SetMemory(
+      0x6000, std::vector<uint8_t>{0x0a, 0x85, 0x02, 0x0a, 0x86, 0x04, 0x0a, 0x87, 0x01, 0x0a, 0x89,
+                                   0x05, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b});
+
+  loc_regs.clear();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x6000, 0x600c, &loc_regs));
+  ASSERT_EQ(0x600cU, this->dmem_->cur_offset());
+  ASSERT_EQ(4U, loc_regs.size());
+  ASSERT_NE(loc_regs.end(), loc_regs.find(5));
+  ASSERT_NE(loc_regs.end(), loc_regs.find(6));
+  ASSERT_NE(loc_regs.end(), loc_regs.find(7));
+  ASSERT_NE(loc_regs.end(), loc_regs.find(9));
+
+  loc_regs.clear();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x6000, 0x600d, &loc_regs));
+  ASSERT_EQ(0x600dU, this->dmem_->cur_offset());
+  ASSERT_EQ(3U, loc_regs.size());
+  ASSERT_NE(loc_regs.end(), loc_regs.find(5));
+  ASSERT_NE(loc_regs.end(), loc_regs.find(6));
+  ASSERT_NE(loc_regs.end(), loc_regs.find(7));
+
+  loc_regs.clear();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x6000, 0x600e, &loc_regs));
+  ASSERT_EQ(0x600eU, this->dmem_->cur_offset());
+  ASSERT_EQ(2U, loc_regs.size());
+  ASSERT_NE(loc_regs.end(), loc_regs.find(5));
+  ASSERT_NE(loc_regs.end(), loc_regs.find(6));
+
+  loc_regs.clear();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x6000, 0x600f, &loc_regs));
+  ASSERT_EQ(0x600fU, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_NE(loc_regs.end(), loc_regs.find(5));
+
+  loc_regs.clear();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x6000, 0x6010, &loc_regs));
+  ASSERT_EQ(0x6010U, this->dmem_->cur_offset());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  loc_regs.clear();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x6000, 0x6011, &loc_regs));
+  ASSERT_EQ(0x6011U, this->dmem_->cur_offset());
+  ASSERT_EQ(0U, loc_regs.size());
+}
+
+// This test verifies that the cfa offset is saved and restored properly.
+// Even though the spec is not clear about whether the offset is also
+// restored, the gcc unwinder does, and libunwind does too.
+TYPED_TEST_P(DwarfCfaTest, cfa_state_cfa_offset_restore) {
+  this->memory_.SetMemory(0x3000, std::vector<uint8_t>{0x0a, 0x0e, 0x40, 0x0b});
+  dwarf_loc_regs_t loc_regs;
+  loc_regs[CFA_REG] = {.type = DWARF_LOCATION_REGISTER, .values = {5, 100}};
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x3000, 0x3004, &loc_regs));
+  ASSERT_EQ(0x3004U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, loc_regs[CFA_REG].type);
+  ASSERT_EQ(5U, loc_regs[CFA_REG].values[0]);
+  ASSERT_EQ(100U, loc_regs[CFA_REG].values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_def_cfa) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0c, 0x7f, 0x74});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x103, &loc_regs));
+  ASSERT_EQ(0x103U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, loc_regs[CFA_REG].type);
+  ASSERT_EQ(0x7fU, loc_regs[CFA_REG].values[0]);
+  ASSERT_EQ(0x74U, loc_regs[CFA_REG].values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x0c, 0xff, 0x02, 0xf4, 0x04});
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x200, 0x205, &loc_regs));
+  ASSERT_EQ(0x205U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, loc_regs[CFA_REG].type);
+  ASSERT_EQ(0x17fU, loc_regs[CFA_REG].values[0]);
+  ASSERT_EQ(0x274U, loc_regs[CFA_REG].values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_def_cfa_sf) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x12, 0x30, 0x25});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x103, &loc_regs));
+  ASSERT_EQ(0x103U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, loc_regs[CFA_REG].type);
+  ASSERT_EQ(0x30U, loc_regs[CFA_REG].values[0]);
+  ASSERT_EQ(0x128U, loc_regs[CFA_REG].values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Test a negative value.
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x12, 0xa3, 0x01, 0xfa, 0x7f});
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x200, 0x205, &loc_regs));
+  ASSERT_EQ(0x205U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, loc_regs[CFA_REG].type);
+  ASSERT_EQ(0xa3U, loc_regs[CFA_REG].values[0]);
+  ASSERT_EQ(static_cast<uint64_t>(-48), loc_regs[CFA_REG].values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_def_cfa_register) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0d, 0x72});
+  dwarf_loc_regs_t loc_regs;
+
+  // This fails because the cfa is not defined as a register.
+  ASSERT_FALSE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x102, &loc_regs));
+  ASSERT_EQ(0U, loc_regs.size());
+  ASSERT_EQ(DWARF_ERROR_ILLEGAL_STATE, this->cfa_->last_error());
+
+  ASSERT_EQ("4 unwind Attempt to set new register, but cfa is not already set to a register.\n",
+            GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  loc_regs[CFA_REG] = {.type = DWARF_LOCATION_REGISTER, .values = {3, 20}};
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x102, &loc_regs));
+  ASSERT_EQ(0x102U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, loc_regs[CFA_REG].type);
+  ASSERT_EQ(0x72U, loc_regs[CFA_REG].values[0]);
+  ASSERT_EQ(20U, loc_regs[CFA_REG].values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x0d, 0xf9, 0x20});
+  loc_regs.clear();
+  loc_regs[CFA_REG] = {.type = DWARF_LOCATION_REGISTER, .values = {3, 60}};
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x200, 0x203, &loc_regs));
+  ASSERT_EQ(0x203U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, loc_regs[CFA_REG].type);
+  ASSERT_EQ(0x1079U, loc_regs[CFA_REG].values[0]);
+  ASSERT_EQ(60U, loc_regs[CFA_REG].values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_def_cfa_offset) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0e, 0x59});
+  dwarf_loc_regs_t loc_regs;
+
+  // This fails because the cfa is not defined as a register.
+  ASSERT_FALSE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x102, &loc_regs));
+  ASSERT_EQ(0U, loc_regs.size());
+  ASSERT_EQ(DWARF_ERROR_ILLEGAL_STATE, this->cfa_->last_error());
+
+  ASSERT_EQ("4 unwind Attempt to set offset, but cfa is not set to a register.\n",
+            GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  loc_regs[CFA_REG] = {.type = DWARF_LOCATION_REGISTER, .values = {3}};
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x102, &loc_regs));
+  ASSERT_EQ(0x102U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, loc_regs[CFA_REG].type);
+  ASSERT_EQ(3U, loc_regs[CFA_REG].values[0]);
+  ASSERT_EQ(0x59U, loc_regs[CFA_REG].values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x0e, 0xd4, 0x0a});
+  loc_regs.clear();
+  loc_regs[CFA_REG] = {.type = DWARF_LOCATION_REGISTER, .values = {3}};
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x200, 0x203, &loc_regs));
+  ASSERT_EQ(0x203U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, loc_regs[CFA_REG].type);
+  ASSERT_EQ(3U, loc_regs[CFA_REG].values[0]);
+  ASSERT_EQ(0x554U, loc_regs[CFA_REG].values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_def_cfa_offset_sf) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x13, 0x23});
+  dwarf_loc_regs_t loc_regs;
+
+  // This fails because the cfa is not defined as a register.
+  ASSERT_FALSE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x102, &loc_regs));
+  ASSERT_EQ(DWARF_ERROR_ILLEGAL_STATE, this->cfa_->last_error());
+
+  ASSERT_EQ("4 unwind Attempt to set offset, but cfa is not set to a register.\n",
+            GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  loc_regs[CFA_REG] = {.type = DWARF_LOCATION_REGISTER, .values = {3}};
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x102, &loc_regs));
+  ASSERT_EQ(0x102U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, loc_regs[CFA_REG].type);
+  ASSERT_EQ(3U, loc_regs[CFA_REG].values[0]);
+  ASSERT_EQ(0x118U, loc_regs[CFA_REG].values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Negative offset.
+  ResetLogs();
+  this->memory_.SetMemory(0x200, std::vector<uint8_t>{0x13, 0xf6, 0x7f});
+  loc_regs.clear();
+  loc_regs[CFA_REG] = {.type = DWARF_LOCATION_REGISTER, .values = {3}};
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x200, 0x203, &loc_regs));
+  ASSERT_EQ(0x203U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, loc_regs[CFA_REG].type);
+  ASSERT_EQ(3U, loc_regs[CFA_REG].values[0]);
+  ASSERT_EQ(static_cast<TypeParam>(-80), static_cast<TypeParam>(loc_regs[CFA_REG].values[1]));
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_def_cfa_expression) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x0f, 0x04, 0x01, 0x02, 0x03, 0x04});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x106, &loc_regs));
+  ASSERT_EQ(0x106U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  std::vector<uint8_t> ops{0x0f, 0x81, 0x01};
+  for (uint8_t i = 3; i < 132; i++) {
+    ops.push_back(i - 1);
+  }
+  this->memory_.SetMemory(0x200, ops);
+  loc_regs.clear();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x200, 0x284, &loc_regs));
+  ASSERT_EQ(0x284U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_expression) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x10, 0x04, 0x02, 0x40, 0x20});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x105, &loc_regs));
+  ASSERT_EQ(0x105U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(4);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_EXPRESSION, location->second.type);
+  ASSERT_EQ(2U, location->second.values[0]);
+  ASSERT_EQ(0x105U, location->second.values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  std::vector<uint8_t> ops{0x10, 0xff, 0x01, 0x82, 0x01};
+  for (uint8_t i = 5; i < 135; i++) {
+    ops.push_back(i - 4);
+  }
+
+  this->memory_.SetMemory(0x200, ops);
+  loc_regs.clear();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x200, 0x287, &loc_regs));
+  ASSERT_EQ(0x287U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  location = loc_regs.find(255);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_EXPRESSION, location->second.type);
+  ASSERT_EQ(130U, location->second.values[0]);
+  ASSERT_EQ(0x287U, location->second.values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_val_offset) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x14, 0x45, 0x54});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x103, &loc_regs));
+  ASSERT_EQ(0x103U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(69);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_VAL_OFFSET, location->second.type);
+  ASSERT_EQ(0x2a0U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0x400, std::vector<uint8_t>{0x14, 0xa2, 0x02, 0xb4, 0x05});
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x400, 0x405, &loc_regs));
+  ASSERT_EQ(0x405U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  location = loc_regs.find(290);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_VAL_OFFSET, location->second.type);
+  ASSERT_EQ(0x15a0U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_val_offset_sf) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x15, 0x56, 0x12});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x103, &loc_regs));
+  ASSERT_EQ(0x103U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(86);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_VAL_OFFSET, location->second.type);
+  ASSERT_EQ(0x90U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Negative value.
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0xa00, std::vector<uint8_t>{0x15, 0xff, 0x01, 0xc0, 0x7f});
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0xa00, 0xa05, &loc_regs));
+  ASSERT_EQ(0xa05U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  location = loc_regs.find(255);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_VAL_OFFSET, location->second.type);
+  ASSERT_EQ(static_cast<uint64_t>(-512), location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_val_expression) {
+  this->memory_.SetMemory(0x100, std::vector<uint8_t>{0x16, 0x05, 0x02, 0x10, 0x20});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x100, 0x105, &loc_regs));
+  ASSERT_EQ(0x105U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(5);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_VAL_EXPRESSION, location->second.type);
+  ASSERT_EQ(2U, location->second.values[0]);
+  ASSERT_EQ(0x105U, location->second.values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  std::vector<uint8_t> ops{0x16, 0x83, 0x10, 0xa8, 0x01};
+  for (uint8_t i = 0; i < 168; i++) {
+    ops.push_back(i);
+  }
+
+  this->memory_.SetMemory(0xa00, ops);
+  loc_regs.clear();
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0xa00, 0xaad, &loc_regs));
+  ASSERT_EQ(0xaadU, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  location = loc_regs.find(2051);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_VAL_EXPRESSION, location->second.type);
+  ASSERT_EQ(168U, location->second.values[0]);
+  ASSERT_EQ(0xaadU, location->second.values[1]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_gnu_args_size) {
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x2e, 0x04});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2002, &loc_regs));
+  ASSERT_EQ(0x2002U, this->dmem_->cur_offset());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0x5000, std::vector<uint8_t>{0x2e, 0xa4, 0x80, 0x04});
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x5000, 0x5004, &loc_regs));
+  ASSERT_EQ(0x5004U, this->dmem_->cur_offset());
+  ASSERT_EQ(0U, loc_regs.size());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_gnu_negative_offset_extended) {
+  this->memory_.SetMemory(0x500, std::vector<uint8_t>{0x2f, 0x08, 0x10});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x500, 0x503, &loc_regs));
+  ASSERT_EQ(0x503U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(8);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_OFFSET, location->second.type);
+  ASSERT_EQ(static_cast<uint64_t>(-16), location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  loc_regs.clear();
+  this->memory_.SetMemory(0x1500, std::vector<uint8_t>{0x2f, 0x81, 0x02, 0xff, 0x01});
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x1500, 0x1505, &loc_regs));
+  ASSERT_EQ(0x1505U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  location = loc_regs.find(257);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_OFFSET, location->second.type);
+  ASSERT_EQ(static_cast<uint64_t>(-255), location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+TYPED_TEST_P(DwarfCfaTest, cfa_register_override) {
+  this->memory_.SetMemory(0x300, std::vector<uint8_t>{0x09, 0x02, 0x01, 0x09, 0x02, 0x04});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x300, 0x306, &loc_regs));
+  ASSERT_EQ(0x306U, this->dmem_->cur_offset());
+  ASSERT_EQ(1U, loc_regs.size());
+  auto location = loc_regs.find(2);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_REGISTER, location->second.type);
+  ASSERT_EQ(4U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
+REGISTER_TYPED_TEST_CASE_P(DwarfCfaTest, cfa_illegal, cfa_nop, cfa_offset, cfa_offset_extended,
+                           cfa_offset_extended_sf, cfa_restore, cfa_restore_extended, cfa_set_loc,
+                           cfa_advance_loc1, cfa_advance_loc2, cfa_advance_loc4, cfa_undefined,
+                           cfa_same, cfa_register, cfa_state, cfa_state_cfa_offset_restore,
+                           cfa_def_cfa, cfa_def_cfa_sf, cfa_def_cfa_register, cfa_def_cfa_offset,
+                           cfa_def_cfa_offset_sf, cfa_def_cfa_expression, cfa_expression,
+                           cfa_val_offset, cfa_val_offset_sf, cfa_val_expression, cfa_gnu_args_size,
+                           cfa_gnu_negative_offset_extended, cfa_register_override);
+
+typedef ::testing::Types<uint32_t, uint64_t> DwarfCfaTestTypes;
+INSTANTIATE_TYPED_TEST_CASE_P(, DwarfCfaTest, DwarfCfaTestTypes);
diff --git a/libutils/Android.bp b/libutils/Android.bp
index b46ad62..33770ba 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -16,8 +16,22 @@
     name: "libutils_headers",
     vendor_available: true,
     host_supported: true,
+
+    header_libs: [
+        "libsystem_headers",
+        "libcutils_headers"
+    ],
+    export_header_lib_headers: [
+        "libsystem_headers",
+        "libcutils_headers"
+    ],
     export_include_dirs: ["include"],
+
     target: {
+        android: {
+            header_libs: ["libbacktrace_headers"],
+            export_header_lib_headers: ["libbacktrace_headers"],
+        },
         linux_bionic: {
             enabled: true,
         },
diff --git a/libutils/ProcessCallStack.cpp b/libutils/ProcessCallStack.cpp
index 73ed4eb..983847c 100644
--- a/libutils/ProcessCallStack.cpp
+++ b/libutils/ProcessCallStack.cpp
@@ -21,6 +21,7 @@
 #include <errno.h>
 #include <stdio.h>
 #include <string.h>
+#include <unistd.h>
 #include <memory>
 
 #include <utils/Log.h>
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
index 0b6b28c..d20d90e 100644
--- a/logd/LogStatistics.cpp
+++ b/logd/LogStatistics.cpp
@@ -152,6 +152,10 @@
             tagTable.add(tag, element);
         }
     }
+
+    if (!element->getDropped()) {
+        tagNameTable.add(TagNameKey(element), element);
+    }
 }
 
 void LogStatistics::subtract(LogBufferElement* element) {
@@ -191,6 +195,10 @@
             tagTable.subtract(tag, element);
         }
     }
+
+    if (!element->getDropped()) {
+        tagNameTable.subtract(TagNameKey(element), element);
+    }
 }
 
 // Atomically set an entry to drop
@@ -225,6 +233,8 @@
             tagTable.drop(tag, element);
         }
     }
+
+    tagNameTable.subtract(TagNameKey(element), element);
 }
 
 // caller must own and free character string
@@ -505,6 +515,62 @@
     return formatLine(name, size, pruned);
 }
 
+std::string TagNameEntry::formatHeader(const std::string& name,
+                                       log_id_t /* id */) const {
+    return formatLine(name, std::string("Size"), std::string("")) +
+           formatLine(std::string("  TID/PID/UID   LOG_TAG NAME"),
+                      std::string("BYTES"), std::string(""));
+}
+
+std::string TagNameEntry::format(const LogStatistics& /* stat */,
+                                 log_id_t /* id */) const {
+    std::string name;
+    pid_t tid = getTid();
+    pid_t pid = getPid();
+    std::string pidstr;
+    if (pid != (pid_t)-1) {
+        pidstr = android::base::StringPrintf("%u", pid);
+        if ((tid != (pid_t)-1) && (tid != pid)) pidstr = "/" + pidstr;
+    }
+    int len = 9 - pidstr.length();
+    if (len < 0) len = 0;
+    if ((tid == (pid_t)-1) || (tid == pid)) {
+        name = android::base::StringPrintf("%*s", len, "");
+    } else {
+        name = android::base::StringPrintf("%*u", len, tid);
+    }
+    name += pidstr;
+    uid_t uid = getUid();
+    if (uid != (uid_t)-1) {
+        name += android::base::StringPrintf("/%u", uid);
+    }
+
+    std::string size = android::base::StringPrintf("%zu", getSizes());
+
+    const char* nameTmp = getName();
+    if (nameTmp) {
+        size_t lenSpace = std::max(16 - name.length(), (size_t)1);
+        size_t len = EntryBaseConstants::total_len -
+                     EntryBaseConstants::pruned_len - size.length() -
+                     name.length() - lenSpace - 2;
+        size_t lenNameTmp = strlen(nameTmp);
+        while ((len < lenNameTmp) && (lenSpace > 1)) {
+            ++len;
+            --lenSpace;
+        }
+        name += android::base::StringPrintf("%*s", (int)lenSpace, "");
+        if (len < lenNameTmp) {
+            name += "...";
+            nameTmp += lenNameTmp - std::max(len - 3, (size_t)1);
+        }
+        name += nameTmp;
+    }
+
+    std::string pruned = "";
+
+    return formatLine(name, size, pruned);
+}
+
 static std::string formatMsec(uint64_t val) {
     static const unsigned subsecDigits = 3;
     static const uint64_t sec = MS_PER_SEC;
@@ -740,6 +806,13 @@
             securityTagTable.format(*this, uid, pid, name, LOG_ID_SECURITY);
     }
 
+    if (enable) {
+        name = "Chattiest TAGs";
+        if (pid) name += android::base::StringPrintf(" for PID %d", pid);
+        name += ":";
+        output += tagNameTable.format(*this, uid, pid, name);
+    }
+
     return output;
 }
 
diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h
index e6f01d6..945fc0a 100644
--- a/logd/LogStatistics.h
+++ b/logd/LogStatistics.h
@@ -18,10 +18,14 @@
 #define _LOGD_LOG_STATISTICS_H__
 
 #include <ctype.h>
+#include <inttypes.h>
+#include <stdint.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/types.h>
 
 #include <algorithm>  // std::max
+#include <experimental/string_view>
 #include <memory>
 #include <string>  // std::string
 #include <unordered_map>
@@ -30,6 +34,7 @@
 #include <android/log.h>
 #include <log/log_time.h>
 #include <private/android_filesystem_config.h>
+#include <utils/FastStrcmp.h>
 
 #include "LogBufferElement.h"
 #include "LogUtils.h"
@@ -77,7 +82,7 @@
     std::unique_ptr<const TEntry* []> sort(uid_t uid, pid_t pid,
                                            size_t len) const {
         if (!len) {
-            std::unique_ptr<const TEntry* []> sorted(NULL);
+            std::unique_ptr<const TEntry* []> sorted(nullptr);
             return sorted;
         }
 
@@ -112,7 +117,7 @@
         return sorted;
     }
 
-    inline iterator add(TKey key, LogBufferElement* element) {
+    inline iterator add(const TKey& key, const LogBufferElement* element) {
         iterator it = map.find(key);
         if (it == map.end()) {
             it = map.insert(std::make_pair(key, TEntry(element))).first;
@@ -132,14 +137,21 @@
         return it;
     }
 
-    void subtract(TKey key, LogBufferElement* element) {
+    void subtract(TKey&& key, const LogBufferElement* element) {
+        iterator it = map.find(std::move(key));
+        if ((it != map.end()) && it->second.subtract(element)) {
+            map.erase(it);
+        }
+    }
+
+    void subtract(const TKey& key, const LogBufferElement* element) {
         iterator it = map.find(key);
         if ((it != map.end()) && it->second.subtract(element)) {
             map.erase(it);
         }
     }
 
-    inline void drop(TKey key, LogBufferElement* element) {
+    inline void drop(TKey key, const LogBufferElement* element) {
         iterator it = map.find(key);
         if (it != map.end()) {
             it->second.drop(element);
@@ -199,17 +211,18 @@
 
     EntryBase() : size(0) {
     }
-    explicit EntryBase(LogBufferElement* element) : size(element->getMsgLen()) {
+    explicit EntryBase(const LogBufferElement* element)
+        : size(element->getMsgLen()) {
     }
 
     size_t getSizes() const {
         return size;
     }
 
-    inline void add(LogBufferElement* element) {
+    inline void add(const LogBufferElement* element) {
         size += element->getMsgLen();
     }
-    inline bool subtract(LogBufferElement* element) {
+    inline bool subtract(const LogBufferElement* element) {
         size -= element->getMsgLen();
         return !size;
     }
@@ -240,7 +253,7 @@
 
     EntryBaseDropped() : dropped(0) {
     }
-    explicit EntryBaseDropped(LogBufferElement* element)
+    explicit EntryBaseDropped(const LogBufferElement* element)
         : EntryBase(element), dropped(element->getDropped()) {
     }
 
@@ -248,15 +261,15 @@
         return dropped;
     }
 
-    inline void add(LogBufferElement* element) {
+    inline void add(const LogBufferElement* element) {
         dropped += element->getDropped();
         EntryBase::add(element);
     }
-    inline bool subtract(LogBufferElement* element) {
+    inline bool subtract(const LogBufferElement* element) {
         dropped -= element->getDropped();
         return EntryBase::subtract(element) && !dropped;
     }
-    inline void drop(LogBufferElement* element) {
+    inline void drop(const LogBufferElement* element) {
         dropped += 1;
         EntryBase::subtract(element);
     }
@@ -266,7 +279,7 @@
     const uid_t uid;
     pid_t pid;
 
-    explicit UidEntry(LogBufferElement* element)
+    explicit UidEntry(const LogBufferElement* element)
         : EntryBaseDropped(element),
           uid(element->getUid()),
           pid(element->getPid()) {
@@ -282,7 +295,7 @@
         return pid;
     }
 
-    inline void add(LogBufferElement* element) {
+    inline void add(const LogBufferElement* element) {
         if (pid != element->getPid()) {
             pid = -1;
         }
@@ -308,7 +321,7 @@
           uid(android::pidToUid(pid)),
           name(android::pidToName(pid)) {
     }
-    explicit PidEntry(LogBufferElement* element)
+    explicit PidEntry(const LogBufferElement* element)
         : EntryBaseDropped(element),
           pid(element->getPid()),
           uid(element->getUid()),
@@ -318,7 +331,7 @@
         : EntryBaseDropped(element),
           pid(element.pid),
           uid(element.uid),
-          name(element.name ? strdup(element.name) : NULL) {
+          name(element.name ? strdup(element.name) : nullptr) {
     }
     ~PidEntry() {
         free(name);
@@ -340,14 +353,14 @@
     inline void add(pid_t newPid) {
         if (name && !fastcmp<strncmp>(name, "zygote", 6)) {
             free(name);
-            name = NULL;
+            name = nullptr;
         }
         if (!name) {
             name = android::pidToName(newPid);
         }
     }
 
-    inline void add(LogBufferElement* element) {
+    inline void add(const LogBufferElement* element) {
         uid_t incomingUid = element->getUid();
         if (getUid() != incomingUid) {
             uid = incomingUid;
@@ -376,7 +389,7 @@
           uid(android::pidToUid(tid)),
           name(android::tidToName(tid)) {
     }
-    explicit TidEntry(LogBufferElement* element)
+    explicit TidEntry(const LogBufferElement* element)
         : EntryBaseDropped(element),
           tid(element->getTid()),
           pid(element->getPid()),
@@ -388,7 +401,7 @@
           tid(element.tid),
           pid(element.pid),
           uid(element.uid),
-          name(element.name ? strdup(element.name) : NULL) {
+          name(element.name ? strdup(element.name) : nullptr) {
     }
     ~TidEntry() {
         free(name);
@@ -413,14 +426,14 @@
     inline void add(pid_t incomingTid) {
         if (name && !fastcmp<strncmp>(name, "zygote", 6)) {
             free(name);
-            name = NULL;
+            name = nullptr;
         }
         if (!name) {
             name = android::tidToName(incomingTid);
         }
     }
 
-    inline void add(LogBufferElement* element) {
+    inline void add(const LogBufferElement* element) {
         uid_t incomingUid = element->getUid();
         pid_t incomingPid = element->getPid();
         if ((getUid() != incomingUid) || (getPid() != incomingPid)) {
@@ -443,7 +456,7 @@
     pid_t pid;
     uid_t uid;
 
-    explicit TagEntry(LogBufferElement* element)
+    explicit TagEntry(const LogBufferElement* element)
         : EntryBaseDropped(element),
           tag(element->getTag()),
           pid(element->getPid()),
@@ -463,7 +476,7 @@
         return android::tagToName(tag);
     }
 
-    inline void add(LogBufferElement* element) {
+    inline void add(const LogBufferElement* element) {
         if (uid != element->getUid()) {
             uid = -1;
         }
@@ -477,6 +490,144 @@
     std::string format(const LogStatistics& stat, log_id_t id) const;
 };
 
+struct TagNameKey {
+    std::string* alloc;
+    std::experimental::string_view name;  // Saves space if const char*
+
+    explicit TagNameKey(const LogBufferElement* element)
+        : alloc(nullptr), name("", strlen("")) {
+        if (element->isBinary()) {
+            uint32_t tag = element->getTag();
+            if (tag) {
+                const char* cp = android::tagToName(tag);
+                if (cp) {
+                    name = std::experimental::string_view(cp, strlen(cp));
+                    return;
+                }
+            }
+            alloc = new std::string(
+                android::base::StringPrintf("[%" PRIu32 "]", tag));
+            if (!alloc) return;
+            name = std::experimental::string_view(alloc->c_str(), alloc->size());
+            return;
+        }
+        const char* msg = element->getMsg();
+        if (!msg) {
+            name = std::experimental::string_view("chatty", strlen("chatty"));
+            return;
+        }
+        ++msg;
+        unsigned short len = element->getMsgLen();
+        len = (len <= 1) ? 0 : strnlen(msg, len - 1);
+        if (!len) {
+            name = std::experimental::string_view("<NULL>", strlen("<NULL>"));
+            return;
+        }
+        alloc = new std::string(msg, len);
+        if (!alloc) return;
+        name = std::experimental::string_view(alloc->c_str(), alloc->size());
+    }
+
+    explicit TagNameKey(TagNameKey&& rval)
+        : alloc(rval.alloc), name(rval.name.data(), rval.name.length()) {
+        rval.alloc = nullptr;
+    }
+
+    explicit TagNameKey(const TagNameKey& rval)
+        : alloc(rval.alloc ? new std::string(*rval.alloc) : nullptr),
+          name(alloc ? alloc->data() : rval.name.data(), rval.name.length()) {
+    }
+
+    ~TagNameKey() {
+        if (alloc) delete alloc;
+    }
+
+    operator const std::experimental::string_view() const {
+        return name;
+    }
+
+    const char* data() const {
+        return name.data();
+    }
+    size_t length() const {
+        return name.length();
+    }
+
+    bool operator==(const TagNameKey& rval) const {
+        if (length() != rval.length()) return false;
+        if (length() == 0) return true;
+        return fastcmp<strncmp>(data(), rval.data(), length()) == 0;
+    }
+    bool operator!=(const TagNameKey& rval) const {
+        return !(*this == rval);
+    }
+
+    size_t getAllocLength() const {
+        return alloc ? alloc->length() + 1 + sizeof(std::string) : 0;
+    }
+};
+
+// Hash for TagNameKey
+template <>
+struct std::hash<TagNameKey>
+    : public std::unary_function<const TagNameKey&, size_t> {
+    size_t operator()(const TagNameKey& __t) const noexcept {
+        if (!__t.length()) return 0;
+        return std::hash<std::experimental::string_view>()(
+            std::experimental::string_view(__t));
+    }
+};
+
+struct TagNameEntry : public EntryBase {
+    pid_t tid;
+    pid_t pid;
+    uid_t uid;
+    TagNameKey name;
+
+    explicit TagNameEntry(const LogBufferElement* element)
+        : EntryBase(element),
+          tid(element->getTid()),
+          pid(element->getPid()),
+          uid(element->getUid()),
+          name(element) {
+    }
+
+    const TagNameKey& getKey() const {
+        return name;
+    }
+    const pid_t& getTid() const {
+        return tid;
+    }
+    const pid_t& getPid() const {
+        return pid;
+    }
+    const uid_t& getUid() const {
+        return uid;
+    }
+    const char* getName() const {
+        return name.data();
+    }
+    size_t getNameAllocLength() const {
+        return name.getAllocLength();
+    }
+
+    inline void add(const LogBufferElement* element) {
+        if (uid != element->getUid()) {
+            uid = -1;
+        }
+        if (pid != element->getPid()) {
+            pid = -1;
+        }
+        if (tid != element->getTid()) {
+            tid = -1;
+        }
+        EntryBase::add(element);
+    }
+
+    std::string formatHeader(const std::string& name, log_id_t id) const;
+    std::string format(const LogStatistics& stat, log_id_t id) const;
+};
+
 template <typename TEntry>
 class LogFindWorst {
     std::unique_ptr<const TEntry* []> sorted;
@@ -550,9 +701,14 @@
     // security tag list
     tagTable_t securityTagTable;
 
+    // global tag list
+    typedef LogHashtable<TagNameKey, TagNameEntry> tagNameTable_t;
+    tagNameTable_t tagNameTable;
+
     size_t sizeOf() const {
         size_t size = sizeof(*this) + pidTable.sizeOf() + tidTable.sizeOf() +
                       tagTable.sizeOf() + securityTagTable.sizeOf() +
+                      tagNameTable.sizeOf() +
                       (pidTable.size() * sizeof(pidTable_t::iterator)) +
                       (tagTable.size() * sizeof(tagTable_t::iterator));
         for (auto it : pidTable) {
@@ -563,6 +719,7 @@
             const char* name = it.second.getName();
             if (name) size += strlen(name) + 1;
         }
+        for (auto it : tagNameTable) size += it.second.getNameAllocLength();
         log_id_for_each(id) {
             size += uidTable[id].sizeOf();
             size += uidTable[id].size() * sizeof(uidTable_t::iterator);
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 2dd5771..540e976 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -384,6 +384,7 @@
     mkdir /data/misc/radio 0770 system radio
     mkdir /data/misc/sms 0770 system radio
     mkdir /data/misc/zoneinfo 0775 system system
+    mkdir /data/misc/textclassifier 0771 system system
     mkdir /data/misc/vpn 0770 system vpn
     mkdir /data/misc/shared_relro 0771 shared_relro shared_relro
     mkdir /data/misc/systemkeys 0700 system system
diff --git a/rootdir/init.usb.configfs.rc b/rootdir/init.usb.configfs.rc
index 32f0198..de1aab3 100644
--- a/rootdir/init.usb.configfs.rc
+++ b/rootdir/init.usb.configfs.rc
@@ -2,6 +2,7 @@
     write /config/usb_gadget/g1/UDC "none"
     stop adbd
     setprop sys.usb.ffs.ready 0
+    setprop sys.usb.ffs.mtp.ready 0
     write /config/usb_gadget/g1/bDeviceClass 0
     write /config/usb_gadget/g1/bDeviceSubClass 0
     write /config/usb_gadget/g1/bDeviceProtocol 0
@@ -11,6 +12,9 @@
     rmdir /config/usb_gadget/g1/functions/rndis.gs4
     setprop sys.usb.state ${sys.usb.config}
 
+on property:init.svc.adbd=stopped
+    setprop sys.usb.ffs.ready 0
+
 on property:sys.usb.config=adb && property:sys.usb.configfs=1
     start adbd
 
@@ -20,7 +24,7 @@
     write /config/usb_gadget/g1/UDC ${sys.usb.controller}
     setprop sys.usb.state ${sys.usb.config}
 
-on property:sys.usb.config=mtp && property:sys.usb.configfs=1
+on property:sys.usb.ffs.mtp.ready=1 && property:sys.usb.config=mtp && property:sys.usb.configfs=1
     write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "mtp"
     symlink /config/usb_gadget/g1/functions/mtp.gs0 /config/usb_gadget/g1/configs/b.1/f1
     write /config/usb_gadget/g1/UDC ${sys.usb.controller}
@@ -29,14 +33,15 @@
 on property:sys.usb.config=mtp,adb && property:sys.usb.configfs=1
     start adbd
 
-on property:sys.usb.ffs.ready=1 && property:sys.usb.config=mtp,adb && property:sys.usb.configfs=1
+on property:sys.usb.ffs.ready=1 && property:sys.usb.ffs.mtp.ready=1 && \
+property:sys.usb.config=mtp,adb && property:sys.usb.configfs=1
     write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "mtp_adb"
     symlink /config/usb_gadget/g1/functions/mtp.gs0 /config/usb_gadget/g1/configs/b.1/f1
     symlink /config/usb_gadget/g1/functions/ffs.adb /config/usb_gadget/g1/configs/b.1/f2
     write /config/usb_gadget/g1/UDC ${sys.usb.controller}
     setprop sys.usb.state ${sys.usb.config}
 
-on property:sys.usb.config=ptp && property:sys.usb.configfs=1
+on property:sys.usb.ffs.mtp.ready=1 && property:sys.usb.config=ptp && property:sys.usb.configfs=1
     write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "ptp"
     symlink /config/usb_gadget/g1/functions/ptp.gs1 /config/usb_gadget/g1/configs/b.1/f1
     write /config/usb_gadget/g1/UDC ${sys.usb.controller}
@@ -45,7 +50,8 @@
 on property:sys.usb.config=ptp,adb && property:sys.usb.configfs=1
     start adbd
 
-on property:sys.usb.ffs.ready=1 && property:sys.usb.config=ptp,adb && property:sys.usb.configfs=1
+on property:sys.usb.ffs.ready=1 && property:sys.usb.ffs.mtp.ready=1 && \
+property:sys.usb.config=ptp,adb && property:sys.usb.configfs=1
     write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "ptp_adb"
     symlink /config/usb_gadget/g1/functions/ptp.gs1 /config/usb_gadget/g1/configs/b.1/f1
     symlink /config/usb_gadget/g1/functions/ffs.adb /config/usb_gadget/g1/configs/b.1/f2
diff --git a/trusty/Android.bp b/trusty/Android.bp
new file mode 100644
index 0000000..1b2e2c7
--- /dev/null
+++ b/trusty/Android.bp
@@ -0,0 +1,3 @@
+subdirs = [
+    "libtrusty",
+]
diff --git a/trusty/libtrusty/Android.bp b/trusty/libtrusty/Android.bp
new file mode 100644
index 0000000..f316da2
--- /dev/null
+++ b/trusty/libtrusty/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2015 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.
+
+subdirs = [
+    "tipc-test",
+]
+
+cc_library {
+    name: "libtrusty",
+
+    srcs: ["trusty.c"],
+    export_include_dirs: ["include"],
+
+    shared_libs: ["liblog"],
+}
diff --git a/trusty/libtrusty/Android.mk b/trusty/libtrusty/Android.mk
deleted file mode 100644
index 45fc079..0000000
--- a/trusty/libtrusty/Android.mk
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-# == libtrusty Static library ==
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libtrusty
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := trusty.c
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-
-include $(BUILD_STATIC_LIBRARY)
-
-# ==  libtrusty shared library ==
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libtrusty
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := trusty.c
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_SHARED_LIBRARIES := liblog
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/trusty/libtrusty/tipc-test/Android.bp b/trusty/libtrusty/tipc-test/Android.bp
new file mode 100644
index 0000000..cb00fe7
--- /dev/null
+++ b/trusty/libtrusty/tipc-test/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+    name: "tipc-test",
+    static_executable: true,
+
+    srcs: ["tipc_test.c"],
+    static_libs: [
+        "libc",
+        "libtrusty",
+        "liblog",
+    ],
+    gtest: false,
+}
diff --git a/trusty/libtrusty/tipc-test/Android.mk b/trusty/libtrusty/tipc-test/Android.mk
deleted file mode 100644
index 80030fe..0000000
--- a/trusty/libtrusty/tipc-test/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := tipc-test
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := tipc_test.c
-LOCAL_STATIC_LIBRARIES := libc libtrusty liblog
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-include $(BUILD_EXECUTABLE)