Merge "Use ro.logd.kernel for kernel logcat"
diff --git a/adb/Android.mk b/adb/Android.mk
index d76d175..0eeafb6 100644
--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -237,7 +237,7 @@
 LOCAL_LDLIBS_linux := -lrt -ldl -lpthread
 LOCAL_LDLIBS_darwin := -framework CoreFoundation -framework IOKit -lobjc
 LOCAL_LDLIBS_windows := -lws2_32 -luserenv
-LOCAL_STATIC_LIBRARIES_windows := AdbWinApi
+LOCAL_SHARED_LIBRARIES_windows := AdbWinApi
 
 LOCAL_MULTILIB := first
 
@@ -254,8 +254,8 @@
 # Use wmain instead of main
 LOCAL_LDFLAGS_windows := -municode
 LOCAL_LDLIBS_windows := -lws2_32 -lgdi32
-LOCAL_STATIC_LIBRARIES_windows := AdbWinApi
-LOCAL_REQUIRED_MODULES_windows := AdbWinApi AdbWinUsbApi
+LOCAL_SHARED_LIBRARIES_windows := AdbWinApi
+LOCAL_REQUIRED_MODULES_windows := AdbWinUsbApi
 
 LOCAL_SRC_FILES := \
     adb_client.cpp \
diff --git a/adb/client/usb_libusb.cpp b/adb/client/usb_libusb.cpp
index a5e6f23..18f585d 100644
--- a/adb/client/usb_libusb.cpp
+++ b/adb/client/usb_libusb.cpp
@@ -22,6 +22,7 @@
 
 #include <atomic>
 #include <chrono>
+#include <condition_variable>
 #include <memory>
 #include <mutex>
 #include <string>
diff --git a/adb/transport.cpp b/adb/transport.cpp
index 5cf2450..f221785 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -952,10 +952,18 @@
 }
 
 std::string list_transports(bool long_listing) {
-    std::string result;
-
     std::lock_guard<std::recursive_mutex> lock(transport_lock);
-    for (const auto& t : transport_list) {
+
+    auto sorted_transport_list = transport_list;
+    sorted_transport_list.sort([](atransport*& x, atransport*& y) {
+        if (x->type != y->type) {
+            return x->type < y->type;
+        }
+        return strcmp(x->serial, y->serial) < 0;
+    });
+
+    std::string result;
+    for (const auto& t : sorted_transport_list) {
         append_transport(t, &result, long_listing);
     }
     return result;
diff --git a/base/include/android-base/stringprintf.h b/base/include/android-base/stringprintf.h
index cf666ab..1fd6297 100644
--- a/base/include/android-base/stringprintf.h
+++ b/base/include/android-base/stringprintf.h
@@ -28,27 +28,27 @@
 // if the mingw version of vsnprintf is used, use `gnu_printf' which allows z
 // in %zd and PRIu64 (and related) to be recognized by the compile-time
 // checking.
-#define FORMAT_ARCHETYPE __printf__
+#define ANDROID_BASE_FORMAT_ARCHETYPE __printf__
 #ifdef __USE_MINGW_ANSI_STDIO
 #if __USE_MINGW_ANSI_STDIO
-#undef FORMAT_ARCHETYPE
-#define FORMAT_ARCHETYPE gnu_printf
+#undef ANDROID_BASE_FORMAT_ARCHETYPE
+#define ANDROID_BASE_FORMAT_ARCHETYPE gnu_printf
 #endif
 #endif
 
 // Returns a string corresponding to printf-like formatting of the arguments.
 std::string StringPrintf(const char* fmt, ...)
-    __attribute__((__format__(FORMAT_ARCHETYPE, 1, 2)));
+    __attribute__((__format__(ANDROID_BASE_FORMAT_ARCHETYPE, 1, 2)));
 
 // Appends a printf-like formatting of the arguments to 'dst'.
 void StringAppendF(std::string* dst, const char* fmt, ...)
-    __attribute__((__format__(FORMAT_ARCHETYPE, 2, 3)));
+    __attribute__((__format__(ANDROID_BASE_FORMAT_ARCHETYPE, 2, 3)));
 
 // Appends a printf-like formatting of the arguments to 'dst'.
 void StringAppendV(std::string* dst, const char* format, va_list ap)
-    __attribute__((__format__(FORMAT_ARCHETYPE, 2, 0)));
+    __attribute__((__format__(ANDROID_BASE_FORMAT_ARCHETYPE, 2, 0)));
 
-#undef FORMAT_ARCHETYPE
+#undef ANDROID_BASE_FORMAT_ARCHETYPE
 
 }  // namespace base
 }  // namespace android
diff --git a/base/include/android-base/thread_annotations.h b/base/include/android-base/thread_annotations.h
index fbb5923..1307f0e 100644
--- a/base/include/android-base/thread_annotations.h
+++ b/base/include/android-base/thread_annotations.h
@@ -38,6 +38,12 @@
 #define PT_GUARDED_BY(x) \
       THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
 
+#define EXCLUSIVE_LOCKS_REQUIRED(...) \
+      THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))
+
+#define SHARED_LOCKS_REQUIRED(...) \
+      THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__))
+
 #define ACQUIRED_BEFORE(...) \
       THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
 
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index a1fcad8..ae0a401 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -272,6 +272,15 @@
     {"reboot_rtc", 132},
     {"cold_boot", 133},
     {"hard_rst", 134},
+    {"power-on", 135},
+    {"oem_adsp_resetting_the_soc", 136},
+    {"kpdpwr", 137},
+    {"oem_modem_timeout_waiting", 138},
+    {"usb_chg", 139},
+    {"warm_reset_0x02", 140},
+    {"warm_reset_0x80", 141},
+    {"pon_reason_0xb0", 142},
+    {"reboot_download", 143},
 };
 
 // Converts a string value representing the reason the system booted to an
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index e9a3ebd..4b32b9d 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -289,7 +289,7 @@
         munmap(map, sizeof(int));
         map[0] = '8';
     } else if (!strcasecmp(arg, "seccomp")) {
-        set_seccomp_filter();
+        set_system_seccomp_filter();
         syscall(99999);
 #if defined(__arm__)
     } else if (!strcasecmp(arg, "kuser_helper_version")) {
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 96f3c7c..05e6efa 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -500,6 +500,17 @@
     fatal_errno("failed to set dumpable");
   }
 
+  // On kernels with yama_ptrace enabled, also allow any process to attach.
+  bool restore_orig_ptracer = true;
+  if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) != 0) {
+    if (errno == EINVAL) {
+      // This kernel does not support PR_SET_PTRACER_ANY, or Yama is not enabled.
+      restore_orig_ptracer = false;
+    } else {
+      fatal_errno("failed to set traceable");
+    }
+  }
+
   // Essentially pthread_create without CLONE_FILES, so we still work during file descriptor
   // exhaustion.
   pid_t child_pid =
@@ -521,6 +532,11 @@
     fatal_errno("failed to restore dumpable");
   }
 
+  // Restore PR_SET_PTRACER to its original value.
+  if (restore_orig_ptracer && prctl(PR_SET_PTRACER, 0) != 0) {
+    fatal_errno("failed to restore traceable");
+  }
+
   if (info->si_signo == DEBUGGER_SIGNAL) {
     // If the signal is fatal, don't unlock the mutex to prevent other crashing threads from
     // starting to dump right before our death.
diff --git a/fastboot/Android.mk b/fastboot/Android.mk
index 10ef356..dfcf090 100644
--- a/fastboot/Android.mk
+++ b/fastboot/Android.mk
@@ -50,8 +50,8 @@
 LOCAL_CFLAGS_darwin := -Wno-unused-parameter
 
 LOCAL_SRC_FILES_windows := usb_windows.cpp
-LOCAL_STATIC_LIBRARIES_windows := AdbWinApi
-LOCAL_REQUIRED_MODULES_windows := AdbWinApi AdbWinUsbApi
+LOCAL_SHARED_LIBRARIES_windows := AdbWinApi
+LOCAL_REQUIRED_MODULES_windows := AdbWinUsbApi
 LOCAL_LDLIBS_windows := -lws2_32
 LOCAL_C_INCLUDES_windows := development/host/windows/usb/api
 
diff --git a/fastboot/engine.cpp b/fastboot/engine.cpp
index 7e10cc9..271b792 100644
--- a/fastboot/engine.cpp
+++ b/fastboot/engine.cpp
@@ -114,7 +114,7 @@
 
     if (cmdsize >= sizeof(a->cmd)) {
         free(a);
-        die("Command length (%d) exceeds maximum size (%d)", cmdsize, sizeof(a->cmd));
+        die("Command length (%zu) exceeds maximum size (%zu)", cmdsize, sizeof(a->cmd));
     }
 
     if (action_last) {
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index e3c60ae..f4faa21 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -75,7 +75,22 @@
 /* util stuff */
 double now();
 char *mkmsg(const char *fmt, ...);
-__attribute__((__noreturn__)) void die(const char *fmt, ...);
+
+// These printf-like functions are implemented in terms of vsnprintf, so they
+// use the same attribute for compile-time format string checking. On Windows,
+// if the mingw version of vsnprintf is used, use `gnu_printf' which allows z
+// in %zd and PRIu64 (and related) to be recognized by the compile-time
+// checking.
+#define FASTBOOT_FORMAT_ARCHETYPE __printf__
+#ifdef __USE_MINGW_ANSI_STDIO
+#if __USE_MINGW_ANSI_STDIO
+#undef FASTBOOT_FORMAT_ARCHETYPE
+#define FASTBOOT_FORMAT_ARCHETYPE gnu_printf
+#endif
+#endif
+void die(const char* fmt, ...) __attribute__((__noreturn__))
+__attribute__((__format__(FASTBOOT_FORMAT_ARCHETYPE, 1, 2)));
+#undef FASTBOOT_FORMAT_ARCHETYPE
 
 /* Current product */
 extern char cur_product[FB_RESPONSE_SZ + 1];
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 4b94f9c..a2b80ad 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -38,6 +38,7 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
+#include <cutils/android_filesystem_config.h>
 #include <cutils/android_reboot.h>
 #include <cutils/partition_utils.h>
 #include <cutils/properties.h>
@@ -353,7 +354,7 @@
         reserved_blocks = max_reserved_blocks;
     }
 
-    if (ext4_r_blocks_count(sb) == reserved_blocks) {
+    if ((ext4_r_blocks_count(sb) == reserved_blocks) && (sb->s_def_resgid == AID_RESERVED_DISK)) {
         return;
     }
 
@@ -363,11 +364,12 @@
         return;
     }
 
-    char buf[32];
-    const char* argv[] = {TUNE2FS_BIN, "-r", buf, blk_device};
-
-    snprintf(buf, sizeof(buf), "%" PRIu64, reserved_blocks);
     LINFO << "Setting reserved block count on " << blk_device << " to " << reserved_blocks;
+
+    auto reserved_blocks_str = std::to_string(reserved_blocks);
+    auto reserved_gid_str = std::to_string(AID_RESERVED_DISK);
+    const char* argv[] = {
+        TUNE2FS_BIN, "-r", reserved_blocks_str.c_str(), "-g", reserved_gid_str.c_str(), blk_device};
     if (!run_tune2fs(argv, ARRAY_SIZE(argv))) {
         LERROR << "Failed to run " TUNE2FS_BIN " to set the number of reserved blocks on "
                << blk_device;
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 34afed1..1c01d8c 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -638,6 +638,7 @@
  * frees up memory of the return value without touching a and b. */
 static struct fstab *in_place_merge(struct fstab *a, struct fstab *b)
 {
+    if (!a && !b) return nullptr;
     if (!a) return b;
     if (!b) return a;
 
@@ -654,12 +655,13 @@
     }
 
     for (int i = a->num_entries, j = 0; i < total_entries; i++, j++) {
-        // copy the pointer directly *without* malloc and memcpy
+        // Copy the structs by assignment.
         a->recs[i] = b->recs[j];
     }
 
-    // Frees up b, but don't free b->recs[X] to make sure they are
-    // accessible through a->recs[X].
+    // We can't call fs_mgr_free_fstab because a->recs still references the
+    // memory allocated by strdup.
+    free(b->recs);
     free(b->fstab_filename);
     free(b);
 
@@ -754,15 +756,17 @@
         default_fstab = get_fstab_path();
     }
 
-    if (default_fstab.empty()) {
-        LWARNING << __FUNCTION__ << "(): failed to find device default fstab";
+    struct fstab* fstab = nullptr;
+    if (!default_fstab.empty()) {
+        fstab = fs_mgr_read_fstab(default_fstab.c_str());
+    } else {
+        LINFO << __FUNCTION__ << "(): failed to find device default fstab";
     }
 
+    struct fstab* fstab_dt = fs_mgr_read_fstab_dt();
+
     // combines fstab entries passed in from device tree with
     // the ones found from default_fstab file
-    struct fstab *fstab_dt = fs_mgr_read_fstab_dt();
-    struct fstab *fstab = fs_mgr_read_fstab(default_fstab.c_str());
-
     return in_place_merge(fstab_dt, fstab);
 }
 
diff --git a/init/action.cpp b/init/action.cpp
index ab51eea..16ecdcd 100644
--- a/init/action.cpp
+++ b/init/action.cpp
@@ -50,12 +50,19 @@
     : func_(std::move(f)), execute_in_subcontext_(execute_in_subcontext), args_(args), line_(line) {}
 
 Result<Success> Command::InvokeFunc(Subcontext* subcontext) const {
-    if (execute_in_subcontext_ && subcontext) {
-        return subcontext->Execute(args_);
-    } else {
-        const std::string& context = subcontext ? subcontext->context() : kInitContext;
-        return RunBuiltinFunction(func_, args_, context);
+    if (subcontext) {
+        if (execute_in_subcontext_) {
+            return subcontext->Execute(args_);
+        }
+
+        auto expanded_args = subcontext->ExpandArgs(args_);
+        if (!expanded_args) {
+            return expanded_args.error();
+        }
+        return RunBuiltinFunction(func_, *expanded_args, subcontext->context());
     }
+
+    return RunBuiltinFunction(func_, args_, kInitContext);
 }
 
 std::string Command::BuildCommandString() const {
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 4b6c502..7aa94b0 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -58,7 +58,6 @@
 
 #include "init.h"
 #include "persistent_properties.h"
-#include "space_tokenizer.h"
 #include "util.h"
 
 using android::base::ReadFileToString;
@@ -69,6 +68,7 @@
 using android::base::Trim;
 using android::base::WriteStringToFile;
 using android::properties::BuildTrie;
+using android::properties::ParsePropertyInfoFile;
 using android::properties::PropertyInfoAreaFile;
 using android::properties::PropertyInfoEntry;
 
@@ -350,13 +350,15 @@
     ufds[0].events = POLLIN;
     ufds[0].revents = 0;
     while (*timeout_ms > 0) {
-      Timer timer;
-      int nr = poll(ufds, 1, *timeout_ms);
-      uint64_t millis = timer.duration().count();
-      *timeout_ms = (millis > *timeout_ms) ? 0 : *timeout_ms - millis;
+        auto start_time = std::chrono::steady_clock::now();
+        int nr = poll(ufds, 1, *timeout_ms);
+        auto now = std::chrono::steady_clock::now();
+        auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
+        uint64_t millis = time_elapsed.count();
+        *timeout_ms = (millis > *timeout_ms) ? 0 : *timeout_ms - millis;
 
-      if (nr > 0) {
-        return true;
+        if (nr > 0) {
+            return true;
       }
 
       if (nr == 0) {
@@ -726,22 +728,6 @@
     return 0;
 }
 
-Result<PropertyInfoEntry> ParsePropertyInfoLine(const std::string& line) {
-    auto tokenizer = SpaceTokenizer(line);
-
-    auto property = tokenizer.GetNext();
-    if (property.empty()) return Error() << "Did not find a property entry in '" << line << "'";
-
-    auto context = tokenizer.GetNext();
-    if (context.empty()) return Error() << "Did not find a context entry in '" << line << "'";
-
-    // It is not an error to not find these, as older files will not contain them.
-    auto exact_match = tokenizer.GetNext();
-    auto schema = tokenizer.GetRemaining();
-
-    return {property, context, schema, exact_match == "exact"};
-}
-
 bool LoadPropertyInfoFromFile(const std::string& filename,
                               std::vector<PropertyInfoEntry>* property_infos) {
     auto file_contents = std::string();
@@ -750,20 +736,14 @@
         return false;
     }
 
-    for (const auto& line : Split(file_contents, "\n")) {
-        auto trimmed_line = Trim(line);
-        if (trimmed_line.empty() || StartsWith(trimmed_line, "#")) {
-            continue;
-        }
-
-        auto property_info = ParsePropertyInfoLine(line);
-        if (!property_info) {
-            LOG(ERROR) << "Could not read line from '" << filename << "': " << property_info.error();
-            continue;
-        }
-
-        property_infos->emplace_back(*property_info);
+    auto errors = std::vector<std::string>{};
+    ParsePropertyInfoFile(file_contents, property_infos, &errors);
+    // Individual parsing errors are reported but do not cause a failed boot, which is what
+    // returning false would do here.
+    for (const auto& error : errors) {
+        LOG(ERROR) << "Could not read line from '" << filename << "': " << error;
     }
+
     return true;
 }
 
diff --git a/init/space_tokenizer.h b/init/space_tokenizer.h
deleted file mode 100644
index e7e22c5..0000000
--- a/init/space_tokenizer.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _INIT_SPACE_TOKENIZER_H
-#define _INIT_SPACE_TOKENIZER_H
-
-namespace android {
-namespace init {
-
-class SpaceTokenizer {
-  public:
-    SpaceTokenizer(const std::string& string)
-        : string_(string), it_(string_.begin()), end_(string_.end()) {}
-
-    std::string GetNext() {
-        auto next = std::string();
-        while (it_ != end_ && !isspace(*it_)) {
-            next.push_back(*it_++);
-        }
-        while (it_ != end_ && isspace(*it_)) {
-            it_++;
-        }
-        return next;
-    }
-
-    std::string GetRemaining() { return std::string(it_, end_); }
-
-  private:
-    std::string string_;
-    std::string::const_iterator it_;
-    std::string::const_iterator end_;
-};
-
-}  // namespace init
-}  // namespace android
-
-#endif
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index 068be6e..be754da 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -28,7 +28,6 @@
 
 #include "action.h"
 #include "selinux.h"
-#include "system/core/init/subcontext.pb.h"
 #include "util.h"
 
 using android::base::GetExecutablePath;
@@ -84,7 +83,9 @@
 
   private:
     void RunCommand(const SubcontextCommand::ExecuteCommand& execute_command,
-                    SubcontextReply::ResultMessage* result_message) const;
+                    SubcontextReply* reply) const;
+    void ExpandArgs(const SubcontextCommand::ExpandArgsCommand& expand_args_command,
+                    SubcontextReply* reply) const;
 
     const KeywordFunctionMap* function_map_;
     const std::string context_;
@@ -92,7 +93,7 @@
 };
 
 void SubcontextProcess::RunCommand(const SubcontextCommand::ExecuteCommand& execute_command,
-                                   SubcontextReply::ResultMessage* result_message) const {
+                                   SubcontextReply* reply) const {
     // Need to use ArraySplice instead of this code.
     auto args = std::vector<std::string>();
     for (const auto& string : execute_command.args()) {
@@ -108,11 +109,27 @@
     }
 
     if (result) {
-        result_message->set_success(true);
+        reply->set_success(true);
     } else {
-        result_message->set_success(false);
-        result_message->set_error_string(result.error_string());
-        result_message->set_error_errno(result.error_errno());
+        auto* failure = reply->mutable_failure();
+        failure->set_error_string(result.error_string());
+        failure->set_error_errno(result.error_errno());
+    }
+}
+
+void SubcontextProcess::ExpandArgs(const SubcontextCommand::ExpandArgsCommand& expand_args_command,
+                                   SubcontextReply* reply) const {
+    for (const auto& arg : expand_args_command.args()) {
+        auto expanded_prop = std::string{};
+        if (!expand_props(arg, &expanded_prop)) {
+            auto* failure = reply->mutable_failure();
+            failure->set_error_string("Failed to expand '" + arg + "'");
+            failure->set_error_errno(0);
+            return;
+        } else {
+            auto* expand_args_reply = reply->mutable_expand_args_reply();
+            expand_args_reply->add_expanded_args(expanded_prop);
+        }
     }
 }
 
@@ -142,7 +159,11 @@
         auto reply = SubcontextReply();
         switch (subcontext_command.command_case()) {
             case SubcontextCommand::kExecuteCommand: {
-                RunCommand(subcontext_command.execute_command(), reply.mutable_result());
+                RunCommand(subcontext_command.execute_command(), &reply);
+                break;
+            }
+            case SubcontextCommand::kExpandArgsCommand: {
+                ExpandArgs(subcontext_command.expand_args_command(), &reply);
                 break;
             }
             default:
@@ -219,12 +240,7 @@
     Fork();
 }
 
-Result<Success> Subcontext::Execute(const std::vector<std::string>& args) {
-    auto subcontext_command = SubcontextCommand();
-    std::copy(
-        args.begin(), args.end(),
-        RepeatedPtrFieldBackInserter(subcontext_command.mutable_execute_command()->mutable_args()));
-
+Result<SubcontextReply> Subcontext::TransmitMessage(const SubcontextCommand& subcontext_command) {
     if (auto result = SendMessage(socket_, subcontext_command); !result) {
         Restart();
         return ErrnoError() << "Failed to send message to subcontext";
@@ -236,25 +252,59 @@
         return Error() << "Failed to receive result from subcontext: " << subcontext_message.error();
     }
 
-    auto subcontext_reply = SubcontextReply();
+    auto subcontext_reply = SubcontextReply{};
     if (!subcontext_reply.ParseFromString(*subcontext_message)) {
         Restart();
         return Error() << "Unable to parse message from subcontext";
     }
-
-    switch (subcontext_reply.reply_case()) {
-        case SubcontextReply::kResult: {
-            auto result = subcontext_reply.result();
-            if (result.success()) {
-                return Success();
-            } else {
-                return ResultError(result.error_string(), result.error_errno());
-            }
-        }
-        default:
-            return Error() << "Unknown message type from subcontext: "
-                           << subcontext_reply.reply_case();
+    if (subcontext_reply.reply_case() == SubcontextReply::kFailure) {
+        auto& failure = subcontext_reply.failure();
+        return ResultError(failure.error_string(), failure.error_errno());
     }
+    return subcontext_reply;
+}
+
+Result<Success> Subcontext::Execute(const std::vector<std::string>& args) {
+    auto subcontext_command = SubcontextCommand();
+    std::copy(
+        args.begin(), args.end(),
+        RepeatedPtrFieldBackInserter(subcontext_command.mutable_execute_command()->mutable_args()));
+
+    auto subcontext_reply = TransmitMessage(subcontext_command);
+    if (!subcontext_reply) {
+        return subcontext_reply.error();
+    }
+
+    if (subcontext_reply->reply_case() != SubcontextReply::kSuccess) {
+        return Error() << "Unexpected message type from subcontext: "
+                       << subcontext_reply->reply_case();
+    }
+
+    return Success();
+}
+
+Result<std::vector<std::string>> Subcontext::ExpandArgs(const std::vector<std::string>& args) {
+    auto subcontext_command = SubcontextCommand{};
+    std::copy(args.begin(), args.end(),
+              RepeatedPtrFieldBackInserter(
+                  subcontext_command.mutable_expand_args_command()->mutable_args()));
+
+    auto subcontext_reply = TransmitMessage(subcontext_command);
+    if (!subcontext_reply) {
+        return subcontext_reply.error();
+    }
+
+    if (subcontext_reply->reply_case() != SubcontextReply::kExpandArgsReply) {
+        return Error() << "Unexpected message type from subcontext: "
+                       << subcontext_reply->reply_case();
+    }
+
+    auto& reply = subcontext_reply->expand_args_reply();
+    auto expanded_args = std::vector<std::string>{};
+    for (const auto& string : reply.expanded_args()) {
+        expanded_args.emplace_back(string);
+    }
+    return expanded_args;
 }
 
 static std::vector<Subcontext> subcontexts;
diff --git a/init/subcontext.h b/init/subcontext.h
index eadabee..262440d 100644
--- a/init/subcontext.h
+++ b/init/subcontext.h
@@ -25,6 +25,7 @@
 #include <android-base/unique_fd.h>
 
 #include "builtins.h"
+#include "system/core/init/subcontext.pb.h"
 
 namespace android {
 namespace init {
@@ -39,7 +40,8 @@
         Fork();
     }
 
-    Result<Success> Execute(const std::vector<std::string>& command);
+    Result<Success> Execute(const std::vector<std::string>& args);
+    Result<std::vector<std::string>> ExpandArgs(const std::vector<std::string>& args);
     void Restart();
 
     const std::string& path_prefix() const { return path_prefix_; }
@@ -48,6 +50,7 @@
 
   private:
     void Fork();
+    Result<SubcontextReply> TransmitMessage(const SubcontextCommand& subcontext_command);
 
     std::string path_prefix_;
     std::string context_;
diff --git a/init/subcontext.proto b/init/subcontext.proto
index 0d89734..e68115e 100644
--- a/init/subcontext.proto
+++ b/init/subcontext.proto
@@ -19,15 +19,23 @@
 
 message SubcontextCommand {
     message ExecuteCommand { repeated string args = 1; }
-    oneof command { ExecuteCommand execute_command = 1; }
+    message ExpandArgsCommand { repeated string args = 1; }
+    oneof command {
+        ExecuteCommand execute_command = 1;
+        ExpandArgsCommand expand_args_command = 2;
+    }
 }
 
 message SubcontextReply {
-    message ResultMessage {
-        optional bool success = 1;
-        optional string error_string = 2;
-        optional int32 error_errno = 3;
+    message Failure {
+        optional string error_string = 1;
+        optional int32 error_errno = 2;
     }
+    message ExpandArgsReply { repeated string expanded_args = 1; }
 
-    oneof reply { ResultMessage result = 1; }
+    oneof reply {
+        bool success = 1;
+        Failure failure = 2;
+        ExpandArgsReply expand_args_reply = 3;
+    }
 }
\ No newline at end of file
diff --git a/init/subcontext_test.cpp b/init/subcontext_test.cpp
index ca45266..230203a 100644
--- a/init/subcontext_test.cpp
+++ b/init/subcontext_test.cpp
@@ -143,6 +143,34 @@
     });
 }
 
+TEST(subcontext, ExpandArgs) {
+    RunTest([](auto& subcontext, auto& context_string) {
+        auto args = std::vector<std::string>{
+            "first",
+            "${ro.hardware}",
+            "$$third",
+        };
+        auto result = subcontext.ExpandArgs(args);
+        ASSERT_TRUE(result) << result.error();
+        ASSERT_EQ(3U, result->size());
+        EXPECT_EQ(args[0], result->at(0));
+        EXPECT_EQ(GetProperty("ro.hardware", ""), result->at(1));
+        EXPECT_EQ("$third", result->at(2));
+    });
+}
+
+TEST(subcontext, ExpandArgsFailure) {
+    RunTest([](auto& subcontext, auto& context_string) {
+        auto args = std::vector<std::string>{
+            "first",
+            "${",
+        };
+        auto result = subcontext.ExpandArgs(args);
+        ASSERT_FALSE(result);
+        EXPECT_EQ("Failed to expand '" + args[1] + "'", result.error_string());
+    });
+}
+
 TestFunctionMap BuildTestFunctionMap() {
     TestFunctionMap test_function_map;
     // For CheckDifferentPid
diff --git a/libbacktrace/UnwindStack.cpp b/libbacktrace/UnwindStack.cpp
index 2a555afc..b0345a1 100644
--- a/libbacktrace/UnwindStack.cpp
+++ b/libbacktrace/UnwindStack.cpp
@@ -50,6 +50,7 @@
   auto process_memory = stack_map->process_memory();
   unwindstack::Unwinder unwinder(MAX_BACKTRACE_FRAMES + num_ignore_frames, stack_map->stack_maps(),
                                  regs, stack_map->process_memory());
+  unwinder.SetJitDebug(stack_map->GetJitDebug(), regs->Arch());
   unwinder.Unwind(skip_names, &stack_map->GetSuffixesToIgnore());
 
   if (num_ignore_frames >= unwinder.NumFrames()) {
diff --git a/libbacktrace/UnwindStackMap.cpp b/libbacktrace/UnwindStackMap.cpp
index 836a774..93406dc 100644
--- a/libbacktrace/UnwindStackMap.cpp
+++ b/libbacktrace/UnwindStackMap.cpp
@@ -18,6 +18,9 @@
 #include <stdlib.h>
 #include <sys/types.h>
 
+#include <string>
+#include <vector>
+
 #include <backtrace/BacktraceMap.h>
 #include <unwindstack/Elf.h>
 #include <unwindstack/MapInfo.h>
@@ -39,6 +42,10 @@
   // Create the process memory object.
   process_memory_ = unwindstack::Memory::CreateProcessMemory(pid_);
 
+  // Create a JitDebug object for getting jit unwind information.
+  std::vector<std::string> search_libs_{"libart.so", "libartd.so"};
+  jit_debug_.reset(new unwindstack::JitDebug(process_memory_, search_libs_));
+
   if (!stack_maps_->Parse()) {
     return false;
   }
diff --git a/libbacktrace/UnwindStackMap.h b/libbacktrace/UnwindStackMap.h
index 2f63655..12c5909 100644
--- a/libbacktrace/UnwindStackMap.h
+++ b/libbacktrace/UnwindStackMap.h
@@ -23,6 +23,7 @@
 #include <memory>
 
 #include <backtrace/BacktraceMap.h>
+#include <unwindstack/JitDebug.h>
 #include <unwindstack/Maps.h>
 
 class UnwindStackMap : public BacktraceMap {
@@ -41,11 +42,14 @@
 
   const std::shared_ptr<unwindstack::Memory>& process_memory() { return process_memory_; }
 
+  unwindstack::JitDebug* GetJitDebug() { return jit_debug_.get(); }
+
  protected:
   uint64_t GetLoadBias(size_t index) override;
 
   std::unique_ptr<unwindstack::Maps> stack_maps_;
   std::shared_ptr<unwindstack::Memory> process_memory_;
+  std::unique_ptr<unwindstack::JitDebug> jit_debug_;
 };
 
 #endif  // _LIBBACKTRACE_UNWINDSTACK_MAP_H
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 9cba109..6d00dc6 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -36,7 +36,7 @@
     export_include_dirs: ["include"],
     target: {
         vendor: {
-            export_include_dirs: ["include_vndk"],
+            override_export_include_dirs: ["include_vndk"],
         },
         linux_bionic: {
             enabled: true,
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index 2ecf5bc..5753e49 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -122,6 +122,9 @@
 #define AID_AUTOMOTIVE_EVS 1062  /* Automotive rear and surround view system */
 #define AID_LOWPAN 1063          /* LoWPAN subsystem */
 #define AID_HSM 1064             /* hardware security module subsystem */
+#define AID_RESERVED_DISK 1065   /* GID that has access to reserved disk space */
+#define AID_STATSD 1066          /* statsd daemon */
+#define AID_INCIDENTD 1067       /* incidentd daemon */
 /* Changes to this file must be made in AOSP, *not* in internal branches. */
 
 #define AID_SHELL 2000 /* adb and debug shell user */
diff --git a/libcutils/uevent.cpp b/libcutils/uevent.cpp
index a84e5b0..2dfceed 100644
--- a/libcutils/uevent.cpp
+++ b/libcutils/uevent.cpp
@@ -27,54 +27,6 @@
 
 #include <linux/netlink.h>
 
-#include <fstream>
-
-#include <private/android_filesystem_config.h>
-
-namespace {
-
-// Returns the uid of root in the current user namespace.
-// Returns AID_OVERFLOWUID if the root user is not mapped in the current
-// namespace.
-// Returns 0 if the kernel is not user namespace-aware (for backwards
-// compatibility) or if AID_OVERFLOWUID could not be validated to match what the
-// kernel would return.
-uid_t GetRootUid() {
-    constexpr uid_t kParentRootUid = 0;
-
-    std::ifstream uid_map_file("/proc/self/uid_map");
-    if (!uid_map_file) {
-        // The kernel does not support user namespaces.
-        return kParentRootUid;
-    }
-
-    uid_t current_namespace_uid, parent_namespace_uid;
-    uint32_t length;
-    while (uid_map_file >> current_namespace_uid >> parent_namespace_uid >> length) {
-        // Since kParentRootUid is 0, it should be the first entry in the mapped
-        // range.
-        if (parent_namespace_uid != kParentRootUid || length < 1) continue;
-        return current_namespace_uid;
-    }
-
-    // Sanity check: verify that the overflow UID is the one to be returned by
-    // the kernel.
-    std::ifstream overflowuid_file("/proc/sys/kernel/overflowuid");
-    if (!overflowuid_file) {
-        // It's better to return 0 in case we cannot make sure that the overflow
-        // UID matches.
-        return kParentRootUid;
-    }
-    uid_t kernel_overflow_uid;
-    if (!(overflowuid_file >> kernel_overflow_uid) || kernel_overflow_uid != AID_OVERFLOWUID)
-        return kParentRootUid;
-
-    // root is unmapped, use the kernel "overflow" uid.
-    return AID_OVERFLOWUID;
-}
-
-}  // namespace
-
 extern "C" {
 
 /**
@@ -99,7 +51,6 @@
 }
 
 ssize_t uevent_kernel_recv(int socket, void* buffer, size_t length, bool require_group, uid_t* uid) {
-    static const uid_t root_uid = GetRootUid();
     struct iovec iov = {buffer, length};
     struct sockaddr_nl addr;
     char control[CMSG_SPACE(sizeof(struct ucred))];
@@ -122,10 +73,6 @@
 
     cred = (struct ucred*)CMSG_DATA(cmsg);
     *uid = cred->uid;
-    if (cred->uid != root_uid) {
-        /* ignoring netlink message from non-root user */
-        goto out;
-    }
 
     if (addr.nl_pid != 0) {
         /* ignore non-kernel */
diff --git a/liblog/Android.bp b/liblog/Android.bp
index d5bb29e..eae0b10 100644
--- a/liblog/Android.bp
+++ b/liblog/Android.bp
@@ -55,7 +55,7 @@
             enabled: true,
         },
         vendor: {
-            export_include_dirs: ["include_vndk"],
+            override_export_include_dirs: ["include_vndk"],
         },
     },
 }
@@ -80,10 +80,6 @@
                 address: false,
             },
         },
-        android_arm: {
-            // TODO: This is to work around b/24465209. Remove after root cause is fixed
-            ldflags: ["-Wl,--hash-style=both"],
-        },
         windows: {
             srcs: ["uio.c"],
             enabled: true,
diff --git a/liblog/include/log/log_main.h b/liblog/include/log/log_main.h
index 339a06d..21fc7cc 100644
--- a/liblog/include/log/log_main.h
+++ b/liblog/include/log/log_main.h
@@ -17,6 +17,8 @@
 #ifndef _LIBS_LOG_LOG_MAIN_H
 #define _LIBS_LOG_LOG_MAIN_H
 
+#include <stdbool.h>
+
 #include <android/log.h>
 #include <sys/cdefs.h>
 
@@ -175,10 +177,10 @@
 #if LOG_NDEBUG
 #define ALOGV(...)          \
   do {                      \
-    if (0) {                \
+    if (false) {            \
       __ALOGV(__VA_ARGS__); \
     }                       \
-  } while (0)
+  } while (false)
 #else
 #define ALOGV(...) __ALOGV(__VA_ARGS__)
 #endif
diff --git a/libnativeloader/Android.bp b/libnativeloader/Android.bp
index 4b21edc..17983bc 100644
--- a/libnativeloader/Android.bp
+++ b/libnativeloader/Android.bp
@@ -19,4 +19,8 @@
         "-fvisibility=hidden",
     ],
     export_include_dirs: ["include"],
+    required: [
+        "llndk.libraries.txt",
+        "vndksp.libraries.txt",
+    ],
 }
diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp
index e9f0c0f..6ddec4d 100644
--- a/libnativeloader/native_loader.cpp
+++ b/libnativeloader/native_loader.cpp
@@ -662,22 +662,51 @@
     return handle;
   }
 #else
-  UNUSED(env, target_sdk_version, class_loader, library_path);
-  *needs_native_bridge = false;
-  void* handle = dlopen(path, RTLD_NOW);
-  if (handle == nullptr) {
-    if (NativeBridgeIsSupported(path)) {
-      *needs_native_bridge = true;
-      handle = NativeBridgeLoadLibrary(path, RTLD_NOW);
-      if (handle == nullptr) {
-        *error_msg = NativeBridgeGetError();
-      }
+  UNUSED(env, target_sdk_version, class_loader);
+
+  // Do some best effort to emulate library-path support. It will not
+  // work for dependencies.
+  //
+  // Note: null has a special meaning and must be preserved.
+  std::string c_library_path;  // Empty string by default.
+  if (library_path != nullptr && path != nullptr && path[0] != '/') {
+    ScopedUtfChars library_path_utf_chars(env, library_path);
+    c_library_path = library_path_utf_chars.c_str();
+  }
+
+  std::vector<std::string> library_paths = base::Split(c_library_path, ":");
+
+  for (const std::string& lib_path : library_paths) {
+    *needs_native_bridge = false;
+    const char* path_arg;
+    std::string complete_path;
+    if (path == nullptr) {
+      // Preserve null.
+      path_arg = nullptr;
     } else {
-      *needs_native_bridge = false;
+      complete_path = lib_path;
+      if (!complete_path.empty()) {
+        complete_path.append("/");
+      }
+      complete_path.append(path);
+      path_arg = complete_path.c_str();
+    }
+    void* handle = dlopen(path_arg, RTLD_NOW);
+    if (handle != nullptr) {
+      return handle;
+    }
+    if (NativeBridgeIsSupported(path_arg)) {
+      *needs_native_bridge = true;
+      handle = NativeBridgeLoadLibrary(path_arg, RTLD_NOW);
+      if (handle != nullptr) {
+        return handle;
+      }
+      *error_msg = NativeBridgeGetError();
+    } else {
       *error_msg = dlerror();
     }
   }
-  return handle;
+  return nullptr;
 #endif
 }
 
diff --git a/libsuspend/Android.bp b/libsuspend/Android.bp
index fa06dc4..b3e36c2 100644
--- a/libsuspend/Android.bp
+++ b/libsuspend/Android.bp
@@ -14,6 +14,7 @@
     export_include_dirs: ["include"],
     local_include_dirs: ["include"],
     shared_libs: [
+        "libbase",
         "liblog",
         "libcutils",
     ],
diff --git a/libsuspend/autosuspend_wakeup_count.cpp b/libsuspend/autosuspend_wakeup_count.cpp
index 30f8427..cfca765 100644
--- a/libsuspend/autosuspend_wakeup_count.cpp
+++ b/libsuspend/autosuspend_wakeup_count.cpp
@@ -17,7 +17,6 @@
 #define LOG_TAG "libsuspend"
 //#define LOG_NDEBUG 0
 
-#include <errno.h>
 #include <fcntl.h>
 #include <pthread.h>
 #include <semaphore.h>
@@ -29,22 +28,29 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#include <log/log.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
 
 #include "autosuspend_ops.h"
 
-#define SYS_POWER_STATE "/sys/power/state"
-#define SYS_POWER_WAKEUP_COUNT "/sys/power/wakeup_count"
-
 #define BASE_SLEEP_TIME 100000
+#define MAX_SLEEP_TIME 60000000
 
 static int state_fd;
 static int wakeup_count_fd;
+
+using android::base::ReadFdToString;
+using android::base::Trim;
+using android::base::WriteStringToFd;
+
 static pthread_t suspend_thread;
 static sem_t suspend_lockout;
 static const char* sleep_state = "mem";
 static void (*wakeup_func)(bool success) = NULL;
 static int sleep_time = BASE_SLEEP_TIME;
+static constexpr char sys_power_state[] = "/sys/power/state";
+static constexpr char sys_power_wakeup_count[] = "/sys/power/wakeup_count";
 
 static void update_sleep_time(bool success) {
     if (success) {
@@ -52,13 +58,10 @@
         return;
     }
     // double sleep time after each failure up to one minute
-    sleep_time = MIN(sleep_time * 2, 60000000);
+    sleep_time = MIN(sleep_time * 2, MAX_SLEEP_TIME);
 }
 
 static void* suspend_thread_func(void* arg __attribute__((unused))) {
-    char buf[80];
-    char wakeup_count[20];
-    int wakeup_count_len;
     int ret;
     bool success = true;
 
@@ -66,95 +69,84 @@
         update_sleep_time(success);
         usleep(sleep_time);
         success = false;
-        ALOGV("%s: read wakeup_count", __func__);
+        LOG(VERBOSE) << "read wakeup_count";
         lseek(wakeup_count_fd, 0, SEEK_SET);
-        wakeup_count_len =
-            TEMP_FAILURE_RETRY(read(wakeup_count_fd, wakeup_count, sizeof(wakeup_count)));
-        if (wakeup_count_len < 0) {
-            strerror_r(errno, buf, sizeof(buf));
-            ALOGE("Error reading from %s: %s", SYS_POWER_WAKEUP_COUNT, buf);
-            wakeup_count_len = 0;
-            continue;
-        }
-        if (!wakeup_count_len) {
-            ALOGE("Empty wakeup count");
+        std::string wakeup_count;
+        if (!ReadFdToString(wakeup_count_fd, &wakeup_count)) {
+            PLOG(ERROR) << "error reading from " << sys_power_wakeup_count;
             continue;
         }
 
-        ALOGV("%s: wait", __func__);
+        wakeup_count = Trim(wakeup_count);
+        if (wakeup_count.empty()) {
+            LOG(ERROR) << "empty wakeup count";
+            continue;
+        }
+
+        LOG(VERBOSE) << "wait";
         ret = sem_wait(&suspend_lockout);
         if (ret < 0) {
-            strerror_r(errno, buf, sizeof(buf));
-            ALOGE("Error waiting on semaphore: %s", buf);
+            PLOG(ERROR) << "error waiting on semaphore";
             continue;
         }
 
-        ALOGV("%s: write %*s to wakeup_count", __func__, wakeup_count_len, wakeup_count);
-        ret = TEMP_FAILURE_RETRY(write(wakeup_count_fd, wakeup_count, wakeup_count_len));
-        if (ret < 0) {
-            strerror_r(errno, buf, sizeof(buf));
-            ALOGE("Error writing to %s: %s", SYS_POWER_WAKEUP_COUNT, buf);
-        } else {
-            ALOGV("%s: write %s to %s", __func__, sleep_state, SYS_POWER_STATE);
-            ret = TEMP_FAILURE_RETRY(write(state_fd, sleep_state, strlen(sleep_state)));
-            if (ret >= 0) {
-                success = true;
-            }
+        LOG(VERBOSE) << "write " << wakeup_count << " to wakeup_count";
+        if (WriteStringToFd(wakeup_count, wakeup_count_fd)) {
+            LOG(VERBOSE) << "write " << sleep_state << " to " << sys_power_state;
+            success = WriteStringToFd(sleep_state, state_fd);
+
             void (*func)(bool success) = wakeup_func;
             if (func != NULL) {
                 (*func)(success);
             }
+        } else {
+            PLOG(ERROR) << "error writing to " << sys_power_wakeup_count;
         }
 
-        ALOGV("%s: release sem", __func__);
+        LOG(VERBOSE) << "release sem";
         ret = sem_post(&suspend_lockout);
         if (ret < 0) {
-            strerror_r(errno, buf, sizeof(buf));
-            ALOGE("Error releasing semaphore: %s", buf);
+            PLOG(ERROR) << "error releasing semaphore";
         }
     }
     return NULL;
 }
 
 static int autosuspend_wakeup_count_enable(void) {
-    char buf[80];
     int ret;
 
-    ALOGV("autosuspend_wakeup_count_enable");
+    LOG(VERBOSE) << "autosuspend_wakeup_count_enable";
 
     ret = sem_post(&suspend_lockout);
 
     if (ret < 0) {
-        strerror_r(errno, buf, sizeof(buf));
-        ALOGE("Error changing semaphore: %s", buf);
+        PLOG(ERROR) << "error changing semaphore";
     }
 
-    ALOGV("autosuspend_wakeup_count_enable done");
+    LOG(VERBOSE) << "autosuspend_wakeup_count_enable done";
 
     return ret;
 }
 
 static int autosuspend_wakeup_count_disable(void) {
-    char buf[80];
     int ret;
 
-    ALOGV("autosuspend_wakeup_count_disable");
+    LOG(VERBOSE) << "autosuspend_wakeup_count_disable";
 
     ret = sem_wait(&suspend_lockout);
 
     if (ret < 0) {
-        strerror_r(errno, buf, sizeof(buf));
-        ALOGE("Error changing semaphore: %s", buf);
+        PLOG(ERROR) << "error changing semaphore";
     }
 
-    ALOGV("autosuspend_wakeup_count_disable done");
+    LOG(VERBOSE) << "autosuspend_wakeup_count_disable done";
 
     return ret;
 }
 
 static void autosuspend_set_wakeup_callback(void (*func)(bool success)) {
     if (wakeup_func != NULL) {
-        ALOGE("Duplicate wakeup callback applied, keeping original");
+        LOG(ERROR) << "duplicate wakeup callback applied, keeping original";
         return;
     }
     wakeup_func = func;
@@ -168,36 +160,31 @@
 
 struct autosuspend_ops* autosuspend_wakeup_count_init(void) {
     int ret;
-    char buf[80];
 
-    state_fd = TEMP_FAILURE_RETRY(open(SYS_POWER_STATE, O_RDWR));
+    state_fd = TEMP_FAILURE_RETRY(open(sys_power_state, O_RDWR));
     if (state_fd < 0) {
-        strerror_r(errno, buf, sizeof(buf));
-        ALOGE("Error opening %s: %s", SYS_POWER_STATE, buf);
+        PLOG(ERROR) << "error opening " << sys_power_state;
         goto err_open_state;
     }
 
-    wakeup_count_fd = TEMP_FAILURE_RETRY(open(SYS_POWER_WAKEUP_COUNT, O_RDWR));
+    wakeup_count_fd = TEMP_FAILURE_RETRY(open(sys_power_wakeup_count, O_RDWR));
     if (wakeup_count_fd < 0) {
-        strerror_r(errno, buf, sizeof(buf));
-        ALOGE("Error opening %s: %s", SYS_POWER_WAKEUP_COUNT, buf);
+        PLOG(ERROR) << "error opening " << sys_power_wakeup_count;
         goto err_open_wakeup_count;
     }
 
     ret = sem_init(&suspend_lockout, 0, 0);
     if (ret < 0) {
-        strerror_r(errno, buf, sizeof(buf));
-        ALOGE("Error creating semaphore: %s", buf);
+        PLOG(ERROR) << "error creating semaphore";
         goto err_sem_init;
     }
     ret = pthread_create(&suspend_thread, NULL, suspend_thread_func, NULL);
     if (ret) {
-        strerror_r(ret, buf, sizeof(buf));
-        ALOGE("Error creating thread: %s", buf);
+        LOG(ERROR) << "error creating thread: " << strerror(ret);
         goto err_pthread_create;
     }
 
-    ALOGI("Selected wakeup count");
+    LOG(INFO) << "selected wakeup count";
     return &autosuspend_wakeup_count_ops;
 
 err_pthread_create:
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 133f3b9..484bc7d 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -55,6 +55,7 @@
         "Elf.cpp",
         "ElfInterface.cpp",
         "ElfInterfaceArm.cpp",
+        "JitDebug.cpp",
         "Log.cpp",
         "MapInfo.cpp",
         "Maps.cpp",
@@ -128,6 +129,7 @@
         "tests/ElfInterfaceTest.cpp",
         "tests/ElfTest.cpp",
         "tests/ElfTestUtils.cpp",
+        "tests/JitDebugTest.cpp",
         "tests/LogFake.cpp",
         "tests/MapInfoGetElfTest.cpp",
         "tests/MapInfoGetLoadBiasTest.cpp",
@@ -168,6 +170,7 @@
     data: [
         "tests/files/elf32.xz",
         "tests/files/elf64.xz",
+        "tests/files/offline/jit_debug_x86_32/*",
         "tests/files/offline/gnu_debugdata_arm32/*",
         "tests/files/offline/straddle_arm32/*",
         "tests/files/offline/straddle_arm64/*",
diff --git a/libunwindstack/Elf.cpp b/libunwindstack/Elf.cpp
index 5ec4a3d..220e549 100644
--- a/libunwindstack/Elf.cpp
+++ b/libunwindstack/Elf.cpp
@@ -103,6 +103,37 @@
                                                      addr, load_bias_, name, func_offset)));
 }
 
+bool Elf::GetGlobalVariable(const std::string& name, uint64_t* memory_address) {
+  if (!valid_) {
+    return false;
+  }
+
+  if (!interface_->GetGlobalVariable(name, memory_address) &&
+      (gnu_debugdata_interface_ == nullptr ||
+       !gnu_debugdata_interface_->GetGlobalVariable(name, memory_address))) {
+    return false;
+  }
+
+  // Adjust by the load bias.
+  if (*memory_address < load_bias_) {
+    return false;
+  }
+
+  *memory_address -= load_bias_;
+
+  // If this winds up in the dynamic section, then we might need to adjust
+  // the address.
+  uint64_t dynamic_end = interface_->dynamic_vaddr() + interface_->dynamic_size();
+  if (*memory_address >= interface_->dynamic_vaddr() && *memory_address < dynamic_end) {
+    if (interface_->dynamic_vaddr() > interface_->dynamic_offset()) {
+      *memory_address -= interface_->dynamic_vaddr() - interface_->dynamic_offset();
+    } else {
+      *memory_address += interface_->dynamic_offset() - interface_->dynamic_vaddr();
+    }
+  }
+  return true;
+}
+
 // The relative pc is always relative to the start of the map from which it comes.
 bool Elf::Step(uint64_t rel_pc, uint64_t adjusted_rel_pc, uint64_t elf_offset, Regs* regs,
                Memory* process_memory, bool* finished) {
@@ -160,6 +191,23 @@
   }
 }
 
+bool Elf::IsValidPc(uint64_t pc) {
+  if (!valid_ || pc < load_bias_) {
+    return false;
+  }
+  pc -= load_bias_;
+
+  if (interface_->IsValidPc(pc)) {
+    return true;
+  }
+
+  if (gnu_debugdata_interface_ != nullptr && gnu_debugdata_interface_->IsValidPc(pc)) {
+    return true;
+  }
+
+  return false;
+}
+
 ElfInterface* Elf::CreateInterfaceFromMemory(Memory* memory) {
   if (!IsValidElf(memory)) {
     return nullptr;
diff --git a/libunwindstack/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp
index df1642e..0e3ab2c 100644
--- a/libunwindstack/ElfInterface.cpp
+++ b/libunwindstack/ElfInterface.cpp
@@ -43,6 +43,30 @@
   }
 }
 
+bool ElfInterface::IsValidPc(uint64_t pc) {
+  if (!pt_loads_.empty()) {
+    for (auto& entry : pt_loads_) {
+      uint64_t start = entry.second.table_offset;
+      uint64_t end = start + entry.second.table_size;
+      if (pc >= start && pc < end) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // No PT_LOAD data, look for a fde for this pc in the section data.
+  if (debug_frame_ != nullptr && debug_frame_->GetFdeFromPc(pc) != nullptr) {
+    return true;
+  }
+
+  if (eh_frame_ != nullptr && eh_frame_->GetFdeFromPc(pc) != nullptr) {
+    return true;
+  }
+
+  return false;
+}
+
 Memory* ElfInterface::CreateGnuDebugdataMemory() {
   if (gnu_debugdata_offset_ == 0 || gnu_debugdata_size_ == 0) {
     return nullptr;
@@ -225,6 +249,10 @@
         return false;
       }
       dynamic_offset_ = phdr.p_offset;
+      if (!memory_->ReadField(offset, &phdr, &phdr.p_vaddr, sizeof(phdr.p_vaddr))) {
+        return false;
+      }
+      dynamic_vaddr_ = phdr.p_vaddr;
       if (!memory_->ReadField(offset, &phdr, &phdr.p_memsz, sizeof(phdr.p_memsz))) {
         return false;
       }
@@ -386,6 +414,20 @@
   return false;
 }
 
+template <typename SymType>
+bool ElfInterface::GetGlobalVariableWithTemplate(const std::string& name, uint64_t* memory_address) {
+  if (symbols_.empty()) {
+    return false;
+  }
+
+  for (const auto symbol : symbols_) {
+    if (symbol->GetGlobal<SymType>(memory_, name, memory_address)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 bool ElfInterface::Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
                         bool* finished) {
   // Adjust the load bias to get the real relative pc.
@@ -451,6 +493,9 @@
 template bool ElfInterface::GetFunctionNameWithTemplate<Elf64_Sym>(uint64_t, uint64_t, std::string*,
                                                                    uint64_t*);
 
+template bool ElfInterface::GetGlobalVariableWithTemplate<Elf32_Sym>(const std::string&, uint64_t*);
+template bool ElfInterface::GetGlobalVariableWithTemplate<Elf64_Sym>(const std::string&, uint64_t*);
+
 template void ElfInterface::GetMaxSizeWithTemplate<Elf32_Ehdr>(Memory*, uint64_t*);
 template void ElfInterface::GetMaxSizeWithTemplate<Elf64_Ehdr>(Memory*, uint64_t*);
 
diff --git a/libunwindstack/JitDebug.cpp b/libunwindstack/JitDebug.cpp
new file mode 100644
index 0000000..1008439
--- /dev/null
+++ b/libunwindstack/JitDebug.cpp
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <sys/mman.h>
+
+#include <memory>
+#include <vector>
+
+#include <unwindstack/Elf.h>
+#include <unwindstack/JitDebug.h>
+#include <unwindstack/Maps.h>
+#include <unwindstack/Memory.h>
+
+// This implements the JIT Compilation Interface.
+// See https://sourceware.org/gdb/onlinedocs/gdb/JIT-Interface.html
+
+namespace unwindstack {
+
+struct JITCodeEntry32Pack {
+  uint32_t next;
+  uint32_t prev;
+  uint32_t symfile_addr;
+  uint64_t symfile_size;
+} __attribute__((packed));
+
+struct JITCodeEntry32Pad {
+  uint32_t next;
+  uint32_t prev;
+  uint32_t symfile_addr;
+  uint32_t pad;
+  uint64_t symfile_size;
+};
+
+struct JITCodeEntry64 {
+  uint64_t next;
+  uint64_t prev;
+  uint64_t symfile_addr;
+  uint64_t symfile_size;
+};
+
+struct JITDescriptorHeader {
+  uint32_t version;
+  uint32_t action_flag;
+};
+
+struct JITDescriptor32 {
+  JITDescriptorHeader header;
+  uint32_t relevant_entry;
+  uint32_t first_entry;
+};
+
+struct JITDescriptor64 {
+  JITDescriptorHeader header;
+  uint64_t relevant_entry;
+  uint64_t first_entry;
+};
+
+JitDebug::JitDebug(std::shared_ptr<Memory>& memory) : memory_(memory) {}
+
+JitDebug::JitDebug(std::shared_ptr<Memory>& memory, std::vector<std::string>& search_libs)
+    : memory_(memory), search_libs_(search_libs) {}
+
+JitDebug::~JitDebug() {
+  for (auto* elf : elf_list_) {
+    delete elf;
+  }
+}
+
+uint64_t JitDebug::ReadDescriptor32(uint64_t addr) {
+  JITDescriptor32 desc;
+  if (!memory_->ReadFully(addr, &desc, sizeof(desc))) {
+    return 0;
+  }
+
+  if (desc.header.version != 1 || desc.first_entry == 0) {
+    // Either unknown version, or no jit entries.
+    return 0;
+  }
+
+  return desc.first_entry;
+}
+
+uint64_t JitDebug::ReadDescriptor64(uint64_t addr) {
+  JITDescriptor64 desc;
+  if (!memory_->ReadFully(addr, &desc, sizeof(desc))) {
+    return 0;
+  }
+
+  if (desc.header.version != 1 || desc.first_entry == 0) {
+    // Either unknown version, or no jit entries.
+    return 0;
+  }
+
+  return desc.first_entry;
+}
+
+uint64_t JitDebug::ReadEntry32Pack(uint64_t* start, uint64_t* size) {
+  JITCodeEntry32Pack code;
+  if (!memory_->ReadFully(entry_addr_, &code, sizeof(code))) {
+    return 0;
+  }
+
+  *start = code.symfile_addr;
+  *size = code.symfile_size;
+  return code.next;
+}
+
+uint64_t JitDebug::ReadEntry32Pad(uint64_t* start, uint64_t* size) {
+  JITCodeEntry32Pad code;
+  if (!memory_->ReadFully(entry_addr_, &code, sizeof(code))) {
+    return 0;
+  }
+
+  *start = code.symfile_addr;
+  *size = code.symfile_size;
+  return code.next;
+}
+
+uint64_t JitDebug::ReadEntry64(uint64_t* start, uint64_t* size) {
+  JITCodeEntry64 code;
+  if (!memory_->ReadFully(entry_addr_, &code, sizeof(code))) {
+    return 0;
+  }
+
+  *start = code.symfile_addr;
+  *size = code.symfile_size;
+  return code.next;
+}
+
+void JitDebug::SetArch(ArchEnum arch) {
+  switch (arch) {
+    case ARCH_X86:
+      read_descriptor_func_ = &JitDebug::ReadDescriptor32;
+      read_entry_func_ = &JitDebug::ReadEntry32Pack;
+      break;
+
+    case ARCH_ARM:
+    case ARCH_MIPS:
+      read_descriptor_func_ = &JitDebug::ReadDescriptor32;
+      read_entry_func_ = &JitDebug::ReadEntry32Pad;
+      break;
+
+    case ARCH_ARM64:
+    case ARCH_X86_64:
+    case ARCH_MIPS64:
+      read_descriptor_func_ = &JitDebug::ReadDescriptor64;
+      read_entry_func_ = &JitDebug::ReadEntry64;
+      break;
+    case ARCH_UNKNOWN:
+      abort();
+  }
+}
+
+void JitDebug::Init(Maps* maps) {
+  if (initialized_) {
+    return;
+  }
+  // Regardless of what happens below, consider the init finished.
+  initialized_ = true;
+
+  std::string descriptor_name("__jit_debug_descriptor");
+  uint64_t descriptor_addr = 0;
+  for (MapInfo* info : *maps) {
+    if (!(info->flags & PROT_EXEC) || !(info->flags & PROT_READ) || info->offset != 0) {
+      continue;
+    }
+
+    if (!search_libs_.empty()) {
+      bool found = false;
+      const char* lib = basename(info->name.c_str());
+      for (std::string& name : search_libs_) {
+        if (strcmp(name.c_str(), lib) == 0) {
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        continue;
+      }
+    }
+
+    Elf* elf = info->GetElf(memory_, true);
+    if (elf->GetGlobalVariable(descriptor_name, &descriptor_addr)) {
+      descriptor_addr += info->start;
+      break;
+    }
+  }
+
+  if (descriptor_addr == 0) {
+    return;
+  }
+
+  entry_addr_ = (this->*read_descriptor_func_)(descriptor_addr);
+}
+
+Elf* JitDebug::GetElf(Maps* maps, uint64_t pc) {
+  // Use a single lock, this object should be used so infrequently that
+  // a fine grain lock is unnecessary.
+  std::lock_guard<std::mutex> guard(lock_);
+  if (!initialized_) {
+    Init(maps);
+  }
+
+  // Search the existing elf object first.
+  for (Elf* elf : elf_list_) {
+    if (elf->IsValidPc(pc)) {
+      return elf;
+    }
+  }
+
+  while (entry_addr_ != 0) {
+    uint64_t start;
+    uint64_t size;
+    entry_addr_ = (this->*read_entry_func_)(&start, &size);
+
+    Elf* elf = new Elf(new MemoryRange(memory_, start, size, 0));
+    elf->Init(true);
+    if (!elf->valid()) {
+      // The data is not formatted in a way we understand, do not attempt
+      // to process any other entries.
+      entry_addr_ = 0;
+      delete elf;
+      return nullptr;
+    }
+    elf_list_.push_back(elf);
+
+    if (elf->IsValidPc(pc)) {
+      return elf;
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace unwindstack
diff --git a/libunwindstack/Memory.cpp b/libunwindstack/Memory.cpp
index 285f879..d4ba680 100644
--- a/libunwindstack/Memory.cpp
+++ b/libunwindstack/Memory.cpp
@@ -345,4 +345,26 @@
   return memory_->Read(addr, dst, size);
 }
 
+MemoryOfflineParts::~MemoryOfflineParts() {
+  for (auto memory : memories_) {
+    delete memory;
+  }
+}
+
+size_t MemoryOfflineParts::Read(uint64_t addr, void* dst, size_t size) {
+  if (memories_.empty()) {
+    return 0;
+  }
+
+  // Do a read on each memory object, no support for reading across the
+  // different memory objects.
+  for (MemoryOffline* memory : memories_) {
+    size_t bytes = memory->Read(addr, dst, size);
+    if (bytes != 0) {
+      return bytes;
+    }
+  }
+  return 0;
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/RegsArm.cpp b/libunwindstack/RegsArm.cpp
index 34f29bd..d05c3e2 100644
--- a/libunwindstack/RegsArm.cpp
+++ b/libunwindstack/RegsArm.cpp
@@ -37,10 +37,6 @@
 }
 
 uint64_t RegsArm::GetAdjustedPc(uint64_t rel_pc, Elf* elf) {
-  if (!elf->valid()) {
-    return rel_pc;
-  }
-
   uint64_t load_bias = elf->GetLoadBias();
   if (rel_pc < load_bias) {
     return rel_pc;
diff --git a/libunwindstack/Symbols.cpp b/libunwindstack/Symbols.cpp
index b4b92d6..25def40 100644
--- a/libunwindstack/Symbols.cpp
+++ b/libunwindstack/Symbols.cpp
@@ -108,8 +108,35 @@
   return return_value;
 }
 
+template <typename SymType>
+bool Symbols::GetGlobal(Memory* elf_memory, const std::string& name, uint64_t* memory_address) {
+  uint64_t cur_offset = offset_;
+  while (cur_offset + entry_size_ <= end_) {
+    SymType entry;
+    if (!elf_memory->ReadFully(cur_offset, &entry, sizeof(entry))) {
+      return false;
+    }
+    cur_offset += entry_size_;
+
+    if (entry.st_shndx != SHN_UNDEF && ELF32_ST_TYPE(entry.st_info) == STT_OBJECT &&
+        ELF32_ST_BIND(entry.st_info) == STB_GLOBAL) {
+      uint64_t str_offset = str_offset_ + entry.st_name;
+      if (str_offset < str_end_) {
+        std::string symbol;
+        if (elf_memory->ReadString(str_offset, &symbol, str_end_ - str_offset) && symbol == name) {
+          *memory_address = entry.st_value;
+          return true;
+        }
+      }
+    }
+  }
+  return false;
+}
+
 // Instantiate all of the needed template functions.
 template bool Symbols::GetName<Elf32_Sym>(uint64_t, uint64_t, Memory*, std::string*, uint64_t*);
 template bool Symbols::GetName<Elf64_Sym>(uint64_t, uint64_t, Memory*, std::string*, uint64_t*);
 
+template bool Symbols::GetGlobal<Elf32_Sym>(Memory*, const std::string&, uint64_t*);
+template bool Symbols::GetGlobal<Elf64_Sym>(Memory*, const std::string&, uint64_t*);
 }  // namespace unwindstack
diff --git a/libunwindstack/Symbols.h b/libunwindstack/Symbols.h
index 689144b..7d239c1 100644
--- a/libunwindstack/Symbols.h
+++ b/libunwindstack/Symbols.h
@@ -47,6 +47,9 @@
   bool GetName(uint64_t addr, uint64_t load_bias, Memory* elf_memory, std::string* name,
                uint64_t* func_offset);
 
+  template <typename SymType>
+  bool GetGlobal(Memory* elf_memory, const std::string& name, uint64_t* memory_address);
+
   void ClearCache() {
     symbols_.clear();
     cur_offset_ = offset_;
diff --git a/libunwindstack/Unwinder.cpp b/libunwindstack/Unwinder.cpp
index a83f85b..b0a1c0c 100644
--- a/libunwindstack/Unwinder.cpp
+++ b/libunwindstack/Unwinder.cpp
@@ -27,12 +27,13 @@
 #include <android-base/stringprintf.h>
 
 #include <unwindstack/Elf.h>
+#include <unwindstack/JitDebug.h>
 #include <unwindstack/MapInfo.h>
 #include <unwindstack/Unwinder.h>
 
 namespace unwindstack {
 
-void Unwinder::FillInFrame(MapInfo* map_info, Elf* elf, uint64_t adjusted_rel_pc) {
+void Unwinder::FillInFrame(MapInfo* map_info, Elf* elf, uint64_t adjusted_rel_pc, uint64_t func_pc) {
   size_t frame_num = frames_.size();
   frames_.resize(frame_num + 1);
   FrameData* frame = &frames_.at(frame_num);
@@ -53,7 +54,7 @@
   frame->map_flags = map_info->flags;
   frame->map_load_bias = elf->GetLoadBias();
 
-  if (!elf->GetFunctionName(frame->rel_pc, &frame->function_name, &frame->function_offset)) {
+  if (!elf->GetFunctionName(func_pc, &frame->function_name, &frame->function_offset)) {
     frame->function_name = "";
     frame->function_offset = 0;
   }
@@ -79,17 +80,20 @@
 
   bool return_address_attempt = false;
   bool adjust_pc = false;
+  std::unique_ptr<JitDebug> jit_debug;
   for (; frames_.size() < max_frames_;) {
     uint64_t cur_pc = regs_->pc();
     uint64_t cur_sp = regs_->sp();
 
     MapInfo* map_info = maps_->Find(regs_->pc());
     uint64_t rel_pc;
+    uint64_t adjusted_pc;
     uint64_t adjusted_rel_pc;
     Elf* elf;
     if (map_info == nullptr) {
       rel_pc = regs_->pc();
       adjusted_rel_pc = rel_pc;
+      adjusted_pc = rel_pc;
     } else {
       if (ShouldStop(map_suffixes_to_ignore, map_info->name)) {
         break;
@@ -97,16 +101,30 @@
       elf = map_info->GetElf(process_memory_, true);
       rel_pc = elf->GetRelPc(regs_->pc(), map_info);
       if (adjust_pc) {
-        adjusted_rel_pc = regs_->GetAdjustedPc(rel_pc, elf);
+        adjusted_pc = regs_->GetAdjustedPc(rel_pc, elf);
       } else {
-        adjusted_rel_pc = rel_pc;
+        adjusted_pc = rel_pc;
+      }
+      adjusted_rel_pc = adjusted_pc;
+
+      // If the pc is in an invalid elf file, try and get an Elf object
+      // using the jit debug information.
+      if (!elf->valid() && jit_debug_ != nullptr) {
+        uint64_t adjusted_jit_pc = regs_->pc() - (rel_pc - adjusted_pc);
+        Elf* jit_elf = jit_debug_->GetElf(maps_, adjusted_jit_pc);
+        if (jit_elf != nullptr) {
+          // The jit debug information requires a non relative adjusted pc.
+          adjusted_pc = adjusted_jit_pc;
+          adjusted_rel_pc = adjusted_pc - map_info->start;
+          elf = jit_elf;
+        }
       }
     }
 
     if (map_info == nullptr || initial_map_names_to_skip == nullptr ||
         std::find(initial_map_names_to_skip->begin(), initial_map_names_to_skip->end(),
                   basename(map_info->name.c_str())) == initial_map_names_to_skip->end()) {
-      FillInFrame(map_info, elf, adjusted_rel_pc);
+      FillInFrame(map_info, elf, adjusted_rel_pc, adjusted_pc);
 
       // Once a frame is added, stop skipping frames.
       initial_map_names_to_skip = nullptr;
@@ -134,7 +152,7 @@
           in_device_map = true;
         } else {
           bool finished;
-          stepped = elf->Step(rel_pc, adjusted_rel_pc, map_info->elf_offset, regs_,
+          stepped = elf->Step(rel_pc, adjusted_pc, map_info->elf_offset, regs_,
                               process_memory_.get(), &finished);
           if (stepped && finished) {
             break;
@@ -174,13 +192,13 @@
   if (frame_num >= frames_.size()) {
     return "";
   }
-  return FormatFrame(frames_[frame_num], regs_->Format32Bit());
+  return FormatFrame(frames_[frame_num], regs_->Is32Bit());
 }
 
-std::string Unwinder::FormatFrame(const FrameData& frame, bool bits32) {
+std::string Unwinder::FormatFrame(const FrameData& frame, bool is32bit) {
   std::string data;
 
-  if (bits32) {
+  if (is32bit) {
     data += android::base::StringPrintf("  #%02zu pc %08" PRIx64, frame.num, frame.rel_pc);
   } else {
     data += android::base::StringPrintf("  #%02zu pc %016" PRIx64, frame.num, frame.rel_pc);
@@ -208,4 +226,9 @@
   return data;
 }
 
+void Unwinder::SetJitDebug(JitDebug* jit_debug, ArchEnum arch) {
+  jit_debug->SetArch(arch);
+  jit_debug_ = jit_debug;
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/include/unwindstack/Elf.h b/libunwindstack/include/unwindstack/Elf.h
index a85e5f4..5f34391 100644
--- a/libunwindstack/include/unwindstack/Elf.h
+++ b/libunwindstack/include/unwindstack/Elf.h
@@ -59,6 +59,8 @@
 
   bool GetFunctionName(uint64_t addr, std::string* name, uint64_t* func_offset);
 
+  bool GetGlobalVariable(const std::string& name, uint64_t* memory_address);
+
   uint64_t GetRelPc(uint64_t pc, const MapInfo* map_info);
 
   bool Step(uint64_t rel_pc, uint64_t adjusted_rel_pc, uint64_t elf_offset, Regs* regs,
@@ -68,6 +70,8 @@
 
   uint64_t GetLoadBias() { return load_bias_; }
 
+  bool IsValidPc(uint64_t pc);
+
   bool valid() { return valid_; }
 
   uint32_t machine_type() { return machine_type_; }
diff --git a/libunwindstack/include/unwindstack/ElfInterface.h b/libunwindstack/include/unwindstack/ElfInterface.h
index 5d3cd5e..faa61ee 100644
--- a/libunwindstack/include/unwindstack/ElfInterface.h
+++ b/libunwindstack/include/unwindstack/ElfInterface.h
@@ -60,9 +60,13 @@
   virtual bool GetFunctionName(uint64_t addr, uint64_t load_bias, std::string* name,
                                uint64_t* offset) = 0;
 
+  virtual bool GetGlobalVariable(const std::string& name, uint64_t* memory_address) = 0;
+
   virtual bool Step(uint64_t rel_pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
                     bool* finished);
 
+  virtual bool IsValidPc(uint64_t pc);
+
   Memory* CreateGnuDebugdataMemory();
 
   Memory* memory() { return memory_; }
@@ -72,6 +76,7 @@
   void SetGnuDebugdataInterface(ElfInterface* interface) { gnu_debugdata_interface_ = interface; }
 
   uint64_t dynamic_offset() { return dynamic_offset_; }
+  uint64_t dynamic_vaddr() { return dynamic_vaddr_; }
   uint64_t dynamic_size() { return dynamic_size_; }
   uint64_t eh_frame_hdr_offset() { return eh_frame_hdr_offset_; }
   uint64_t eh_frame_hdr_size() { return eh_frame_hdr_size_; }
@@ -108,6 +113,9 @@
   bool GetFunctionNameWithTemplate(uint64_t addr, uint64_t load_bias, std::string* name,
                                    uint64_t* func_offset);
 
+  template <typename SymType>
+  bool GetGlobalVariableWithTemplate(const std::string& name, uint64_t* memory_address);
+
   virtual bool HandleType(uint64_t, uint32_t, uint64_t) { return false; }
 
   template <typename EhdrType>
@@ -118,6 +126,7 @@
 
   // Stored elf data.
   uint64_t dynamic_offset_ = 0;
+  uint64_t dynamic_vaddr_ = 0;
   uint64_t dynamic_size_ = 0;
 
   uint64_t eh_frame_hdr_offset_ = 0;
@@ -163,6 +172,10 @@
     return ElfInterface::GetFunctionNameWithTemplate<Elf32_Sym>(addr, load_bias, name, func_offset);
   }
 
+  bool GetGlobalVariable(const std::string& name, uint64_t* memory_address) override {
+    return ElfInterface::GetGlobalVariableWithTemplate<Elf32_Sym>(name, memory_address);
+  }
+
   static void GetMaxSize(Memory* memory, uint64_t* size) {
     GetMaxSizeWithTemplate<Elf32_Ehdr>(memory, size);
   }
@@ -188,6 +201,10 @@
     return ElfInterface::GetFunctionNameWithTemplate<Elf64_Sym>(addr, load_bias, name, func_offset);
   }
 
+  bool GetGlobalVariable(const std::string& name, uint64_t* memory_address) override {
+    return ElfInterface::GetGlobalVariableWithTemplate<Elf64_Sym>(name, memory_address);
+  }
+
   static void GetMaxSize(Memory* memory, uint64_t* size) {
     GetMaxSizeWithTemplate<Elf64_Ehdr>(memory, size);
   }
diff --git a/libunwindstack/include/unwindstack/JitDebug.h b/libunwindstack/include/unwindstack/JitDebug.h
new file mode 100644
index 0000000..0bcd0b0
--- /dev/null
+++ b/libunwindstack/include/unwindstack/JitDebug.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LIBUNWINDSTACK_JIT_DEBUG_H
+#define _LIBUNWINDSTACK_JIT_DEBUG_H
+
+#include <stdint.h>
+
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+namespace unwindstack {
+
+// Forward declarations.
+class Elf;
+class Maps;
+class Memory;
+enum ArchEnum : uint8_t;
+
+class JitDebug {
+ public:
+  explicit JitDebug(std::shared_ptr<Memory>& memory);
+  JitDebug(std::shared_ptr<Memory>& memory, std::vector<std::string>& search_libs);
+  ~JitDebug();
+
+  Elf* GetElf(Maps* maps, uint64_t pc);
+
+  void SetArch(ArchEnum arch);
+
+ private:
+  void Init(Maps* maps);
+
+  std::shared_ptr<Memory> memory_;
+  uint64_t entry_addr_ = 0;
+  bool initialized_ = false;
+  std::vector<Elf*> elf_list_;
+  std::vector<std::string> search_libs_;
+
+  std::mutex lock_;
+
+  uint64_t (JitDebug::*read_descriptor_func_)(uint64_t) = nullptr;
+  uint64_t (JitDebug::*read_entry_func_)(uint64_t*, uint64_t*) = nullptr;
+
+  uint64_t ReadDescriptor32(uint64_t);
+  uint64_t ReadDescriptor64(uint64_t);
+
+  uint64_t ReadEntry32Pack(uint64_t* start, uint64_t* size);
+  uint64_t ReadEntry32Pad(uint64_t* start, uint64_t* size);
+  uint64_t ReadEntry64(uint64_t* start, uint64_t* size);
+};
+
+}  // namespace unwindstack
+
+#endif  // _LIBUNWINDSTACK_JIT_DEBUG_H
diff --git a/libunwindstack/include/unwindstack/Memory.h b/libunwindstack/include/unwindstack/Memory.h
index 94ceaab..19bce04 100644
--- a/libunwindstack/include/unwindstack/Memory.h
+++ b/libunwindstack/include/unwindstack/Memory.h
@@ -151,6 +151,19 @@
   std::unique_ptr<MemoryRange> memory_;
 };
 
+class MemoryOfflineParts : public Memory {
+ public:
+  MemoryOfflineParts() = default;
+  virtual ~MemoryOfflineParts();
+
+  void Add(MemoryOffline* memory) { memories_.push_back(memory); }
+
+  size_t Read(uint64_t addr, void* dst, size_t size) override;
+
+ private:
+  std::vector<MemoryOffline*> memories_;
+};
+
 }  // namespace unwindstack
 
 #endif  // _LIBUNWINDSTACK_MEMORY_H
diff --git a/libunwindstack/include/unwindstack/Regs.h b/libunwindstack/include/unwindstack/Regs.h
index 613682f..1904d4d 100644
--- a/libunwindstack/include/unwindstack/Regs.h
+++ b/libunwindstack/include/unwindstack/Regs.h
@@ -51,7 +51,7 @@
 
   virtual ArchEnum Arch() = 0;
 
-  virtual bool Format32Bit() = 0;
+  virtual bool Is32Bit() = 0;
 
   virtual void* RawData() = 0;
   virtual uint64_t pc() = 0;
@@ -94,7 +94,7 @@
   void set_pc(AddressType pc) { pc_ = pc; }
   void set_sp(AddressType sp) { sp_ = sp; }
 
-  bool Format32Bit() override { return sizeof(AddressType) == sizeof(uint32_t); }
+  bool Is32Bit() override { return sizeof(AddressType) == sizeof(uint32_t); }
 
   inline AddressType& operator[](size_t reg) { return regs_[reg]; }
 
diff --git a/libunwindstack/include/unwindstack/Unwinder.h b/libunwindstack/include/unwindstack/Unwinder.h
index b64d460..5adec4f 100644
--- a/libunwindstack/include/unwindstack/Unwinder.h
+++ b/libunwindstack/include/unwindstack/Unwinder.h
@@ -32,6 +32,8 @@
 
 // Forward declarations.
 class Elf;
+class JitDebug;
+enum ArchEnum : uint8_t;
 
 struct FrameData {
   size_t num;
@@ -67,16 +69,19 @@
   const std::vector<FrameData>& frames() { return frames_; }
 
   std::string FormatFrame(size_t frame_num);
-  static std::string FormatFrame(const FrameData& frame, bool bits32);
+  static std::string FormatFrame(const FrameData& frame, bool is32bit);
+
+  void SetJitDebug(JitDebug* jit_debug, ArchEnum arch);
 
  private:
-  void FillInFrame(MapInfo* map_info, Elf* elf, uint64_t adjusted_rel_pc);
+  void FillInFrame(MapInfo* map_info, Elf* elf, uint64_t adjusted_rel_pc, uint64_t adjusted_pc);
 
   size_t max_frames_;
   Maps* maps_;
   Regs* regs_;
   std::vector<FrameData> frames_;
   std::shared_ptr<Memory> process_memory_;
+  JitDebug* jit_debug_ = nullptr;
 };
 
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/ElfFake.cpp b/libunwindstack/tests/ElfFake.cpp
index 68de797..ae9da5e 100644
--- a/libunwindstack/tests/ElfFake.cpp
+++ b/libunwindstack/tests/ElfFake.cpp
@@ -43,6 +43,15 @@
   return true;
 }
 
+bool ElfInterfaceFake::GetGlobalVariable(const std::string& global, uint64_t* offset) {
+  auto entry = globals_.find(global);
+  if (entry == globals_.end()) {
+    return false;
+  }
+  *offset = entry->second;
+  return true;
+}
+
 bool ElfInterfaceFake::Step(uint64_t, uint64_t, Regs* regs, Memory*, bool* finished) {
   if (steps_.empty()) {
     return false;
diff --git a/libunwindstack/tests/ElfFake.h b/libunwindstack/tests/ElfFake.h
index abf9927..099026c 100644
--- a/libunwindstack/tests/ElfFake.h
+++ b/libunwindstack/tests/ElfFake.h
@@ -21,6 +21,7 @@
 
 #include <deque>
 #include <string>
+#include <unordered_map>
 
 #include <unwindstack/Elf.h>
 #include <unwindstack/ElfInterface.h>
@@ -55,6 +56,9 @@
   void FakeSetLoadBias(uint64_t load_bias) { load_bias_ = load_bias; }
 
   void FakeSetInterface(ElfInterface* interface) { interface_.reset(interface); }
+  void FakeSetGnuDebugdataInterface(ElfInterface* interface) {
+    gnu_debugdata_interface_.reset(interface);
+  }
 };
 
 class ElfInterfaceFake : public ElfInterface {
@@ -67,9 +71,14 @@
   bool GetSoname(std::string*) override { return false; }
 
   bool GetFunctionName(uint64_t, uint64_t, std::string*, uint64_t*) override;
+  bool GetGlobalVariable(const std::string&, uint64_t*) override;
 
   bool Step(uint64_t, uint64_t, Regs*, Memory*, bool*) override;
 
+  void FakeSetGlobalVariable(const std::string& global, uint64_t offset) {
+    globals_[global] = offset;
+  }
+
   static void FakePushFunctionData(const FunctionData data) { functions_.push_back(data); }
   static void FakePushStepData(const StepData data) { steps_.push_back(data); }
 
@@ -79,6 +88,8 @@
   }
 
  private:
+  std::unordered_map<std::string, uint64_t> globals_;
+
   static std::deque<FunctionData> functions_;
   static std::deque<StepData> steps_;
 };
diff --git a/libunwindstack/tests/ElfInterfaceTest.cpp b/libunwindstack/tests/ElfInterfaceTest.cpp
index e138c3a..042c5fb 100644
--- a/libunwindstack/tests/ElfInterfaceTest.cpp
+++ b/libunwindstack/tests/ElfInterfaceTest.cpp
@@ -958,4 +958,189 @@
   InitSectionHeadersOffsets<Elf64_Ehdr, Elf64_Shdr, ElfInterface64>();
 }
 
+TEST_F(ElfInterfaceTest, is_valid_pc_from_pt_load) {
+  std::unique_ptr<ElfInterface> elf(new ElfInterface32(&memory_));
+
+  Elf32_Ehdr ehdr;
+  memset(&ehdr, 0, sizeof(ehdr));
+  ehdr.e_phoff = 0x100;
+  ehdr.e_phnum = 1;
+  ehdr.e_phentsize = sizeof(Elf32_Phdr);
+  memory_.SetMemory(0, &ehdr, sizeof(ehdr));
+
+  Elf32_Phdr phdr;
+  memset(&phdr, 0, sizeof(phdr));
+  phdr.p_type = PT_LOAD;
+  phdr.p_vaddr = 0;
+  phdr.p_memsz = 0x10000;
+  phdr.p_flags = PF_R | PF_X;
+  phdr.p_align = 0x1000;
+  memory_.SetMemory(0x100, &phdr, sizeof(phdr));
+
+  uint64_t load_bias = 0;
+  ASSERT_TRUE(elf->Init(&load_bias));
+  EXPECT_EQ(0U, load_bias);
+  EXPECT_TRUE(elf->IsValidPc(0));
+  EXPECT_TRUE(elf->IsValidPc(0x5000));
+  EXPECT_TRUE(elf->IsValidPc(0xffff));
+  EXPECT_FALSE(elf->IsValidPc(0x10000));
+}
+
+TEST_F(ElfInterfaceTest, is_valid_pc_from_pt_load_non_zero_load_bias) {
+  std::unique_ptr<ElfInterface> elf(new ElfInterface32(&memory_));
+
+  Elf32_Ehdr ehdr;
+  memset(&ehdr, 0, sizeof(ehdr));
+  ehdr.e_phoff = 0x100;
+  ehdr.e_phnum = 1;
+  ehdr.e_phentsize = sizeof(Elf32_Phdr);
+  memory_.SetMemory(0, &ehdr, sizeof(ehdr));
+
+  Elf32_Phdr phdr;
+  memset(&phdr, 0, sizeof(phdr));
+  phdr.p_type = PT_LOAD;
+  phdr.p_vaddr = 0x2000;
+  phdr.p_memsz = 0x10000;
+  phdr.p_flags = PF_R | PF_X;
+  phdr.p_align = 0x1000;
+  memory_.SetMemory(0x100, &phdr, sizeof(phdr));
+
+  uint64_t load_bias = 0;
+  ASSERT_TRUE(elf->Init(&load_bias));
+  EXPECT_EQ(0x2000U, load_bias);
+  EXPECT_FALSE(elf->IsValidPc(0));
+  EXPECT_FALSE(elf->IsValidPc(0x1000));
+  EXPECT_FALSE(elf->IsValidPc(0x1fff));
+  EXPECT_TRUE(elf->IsValidPc(0x2000));
+  EXPECT_TRUE(elf->IsValidPc(0x5000));
+  EXPECT_TRUE(elf->IsValidPc(0x11fff));
+  EXPECT_FALSE(elf->IsValidPc(0x12000));
+}
+
+TEST_F(ElfInterfaceTest, is_valid_pc_from_debug_frame) {
+  std::unique_ptr<ElfInterface> elf(new ElfInterface32(&memory_));
+
+  uint64_t sh_offset = 0x100;
+
+  Elf32_Ehdr ehdr;
+  memset(&ehdr, 0, sizeof(ehdr));
+  ehdr.e_shstrndx = 1;
+  ehdr.e_shoff = sh_offset;
+  ehdr.e_shentsize = sizeof(Elf32_Shdr);
+  ehdr.e_shnum = 3;
+  memory_.SetMemory(0, &ehdr, sizeof(ehdr));
+
+  Elf32_Shdr shdr;
+  memset(&shdr, 0, sizeof(shdr));
+  shdr.sh_type = SHT_NULL;
+  memory_.SetMemory(sh_offset, &shdr, sizeof(shdr));
+
+  sh_offset += sizeof(shdr);
+  memset(&shdr, 0, sizeof(shdr));
+  shdr.sh_type = SHT_STRTAB;
+  shdr.sh_name = 1;
+  shdr.sh_offset = 0x500;
+  shdr.sh_size = 0x100;
+  memory_.SetMemory(sh_offset, &shdr, sizeof(shdr));
+  memory_.SetMemory(0x500, ".debug_frame");
+
+  sh_offset += sizeof(shdr);
+  memset(&shdr, 0, sizeof(shdr));
+  shdr.sh_type = SHT_PROGBITS;
+  shdr.sh_name = 0;
+  shdr.sh_addr = 0x600;
+  shdr.sh_offset = 0x600;
+  shdr.sh_size = 0x200;
+  memory_.SetMemory(sh_offset, &shdr, sizeof(shdr));
+
+  // CIE 32.
+  memory_.SetData32(0x600, 0xfc);
+  memory_.SetData32(0x604, 0xffffffff);
+  memory_.SetData8(0x608, 1);
+  memory_.SetData8(0x609, '\0');
+  memory_.SetData8(0x60a, 0x4);
+  memory_.SetData8(0x60b, 0x4);
+  memory_.SetData8(0x60c, 0x1);
+
+  // FDE 32.
+  memory_.SetData32(0x700, 0xfc);
+  memory_.SetData32(0x704, 0);
+  memory_.SetData32(0x708, 0x2100);
+  memory_.SetData32(0x70c, 0x200);
+
+  uint64_t load_bias = 0;
+  ASSERT_TRUE(elf->Init(&load_bias));
+  elf->InitHeaders();
+  EXPECT_EQ(0U, load_bias);
+  EXPECT_FALSE(elf->IsValidPc(0));
+  EXPECT_FALSE(elf->IsValidPc(0x20ff));
+  EXPECT_TRUE(elf->IsValidPc(0x2100));
+  EXPECT_TRUE(elf->IsValidPc(0x2200));
+  EXPECT_TRUE(elf->IsValidPc(0x22ff));
+  EXPECT_FALSE(elf->IsValidPc(0x2300));
+}
+
+TEST_F(ElfInterfaceTest, is_valid_pc_from_eh_frame) {
+  std::unique_ptr<ElfInterface> elf(new ElfInterface32(&memory_));
+
+  uint64_t sh_offset = 0x100;
+
+  Elf32_Ehdr ehdr;
+  memset(&ehdr, 0, sizeof(ehdr));
+  ehdr.e_shstrndx = 1;
+  ehdr.e_shoff = sh_offset;
+  ehdr.e_shentsize = sizeof(Elf32_Shdr);
+  ehdr.e_shnum = 3;
+  memory_.SetMemory(0, &ehdr, sizeof(ehdr));
+
+  Elf32_Shdr shdr;
+  memset(&shdr, 0, sizeof(shdr));
+  shdr.sh_type = SHT_NULL;
+  memory_.SetMemory(sh_offset, &shdr, sizeof(shdr));
+
+  sh_offset += sizeof(shdr);
+  memset(&shdr, 0, sizeof(shdr));
+  shdr.sh_type = SHT_STRTAB;
+  shdr.sh_name = 1;
+  shdr.sh_offset = 0x500;
+  shdr.sh_size = 0x100;
+  memory_.SetMemory(sh_offset, &shdr, sizeof(shdr));
+  memory_.SetMemory(0x500, ".eh_frame");
+
+  sh_offset += sizeof(shdr);
+  memset(&shdr, 0, sizeof(shdr));
+  shdr.sh_type = SHT_PROGBITS;
+  shdr.sh_name = 0;
+  shdr.sh_addr = 0x600;
+  shdr.sh_offset = 0x600;
+  shdr.sh_size = 0x200;
+  memory_.SetMemory(sh_offset, &shdr, sizeof(shdr));
+
+  // CIE 32.
+  memory_.SetData32(0x600, 0xfc);
+  memory_.SetData32(0x604, 0);
+  memory_.SetData8(0x608, 1);
+  memory_.SetData8(0x609, '\0');
+  memory_.SetData8(0x60a, 0x4);
+  memory_.SetData8(0x60b, 0x4);
+  memory_.SetData8(0x60c, 0x1);
+
+  // FDE 32.
+  memory_.SetData32(0x700, 0xfc);
+  memory_.SetData32(0x704, 0x104);
+  memory_.SetData32(0x708, 0x20f8);
+  memory_.SetData32(0x70c, 0x200);
+
+  uint64_t load_bias = 0;
+  ASSERT_TRUE(elf->Init(&load_bias));
+  elf->InitHeaders();
+  EXPECT_EQ(0U, load_bias);
+  EXPECT_FALSE(elf->IsValidPc(0));
+  EXPECT_FALSE(elf->IsValidPc(0x27ff));
+  EXPECT_TRUE(elf->IsValidPc(0x2800));
+  EXPECT_TRUE(elf->IsValidPc(0x2900));
+  EXPECT_TRUE(elf->IsValidPc(0x29ff));
+  EXPECT_FALSE(elf->IsValidPc(0x2a00));
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/ElfTest.cpp b/libunwindstack/tests/ElfTest.cpp
index 5e808ef..7e6a62a 100644
--- a/libunwindstack/tests/ElfTest.cpp
+++ b/libunwindstack/tests/ElfTest.cpp
@@ -346,7 +346,14 @@
   void InitHeaders() override {}
   bool GetSoname(std::string*) override { return false; }
   bool GetFunctionName(uint64_t, uint64_t, std::string*, uint64_t*) override { return false; }
+
   MOCK_METHOD5(Step, bool(uint64_t, uint64_t, Regs*, Memory*, bool*));
+  MOCK_METHOD2(GetGlobalVariable, bool(const std::string&, uint64_t*));
+  MOCK_METHOD1(IsValidPc, bool(uint64_t));
+
+  void MockSetDynamicOffset(uint64_t offset) { dynamic_offset_ = offset; }
+  void MockSetDynamicVaddr(uint64_t vaddr) { dynamic_vaddr_ = vaddr; }
+  void MockSetDynamicSize(uint64_t size) { dynamic_size_ = size; }
 };
 
 TEST_F(ElfTest, step_in_interface) {
@@ -378,14 +385,200 @@
   elf.FakeSetInterface(interface);
   MemoryFake process_memory;
 
-  // Invalid relative pc given load_bias.
   bool finished;
-  ASSERT_FALSE(elf.Step(0x1004, 0x1000, 0x2000, &regs, &process_memory, &finished));
-
   EXPECT_CALL(*interface, Step(0x7300, 0x4000, &regs, &process_memory, &finished))
       .WillOnce(::testing::Return(true));
 
   ASSERT_TRUE(elf.Step(0x7304, 0x7300, 0x2000, &regs, &process_memory, &finished));
 }
 
+TEST_F(ElfTest, get_global_invalid_elf) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(false);
+
+  std::string global("something");
+  uint64_t offset;
+  ASSERT_FALSE(elf.GetGlobalVariable(global, &offset));
+}
+
+TEST_F(ElfTest, get_global_valid_not_in_interface) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(true);
+  elf.FakeSetLoadBias(0);
+
+  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
+  elf.FakeSetInterface(interface);
+
+  uint64_t offset;
+  std::string global("something");
+  EXPECT_CALL(*interface, GetGlobalVariable(global, &offset)).WillOnce(::testing::Return(false));
+
+  ASSERT_FALSE(elf.GetGlobalVariable(global, &offset));
+}
+
+TEST_F(ElfTest, get_global_valid_below_load_bias) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(true);
+  elf.FakeSetLoadBias(0x1000);
+
+  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
+  elf.FakeSetInterface(interface);
+
+  uint64_t offset;
+  std::string global("something");
+  EXPECT_CALL(*interface, GetGlobalVariable(global, &offset))
+      .WillOnce(::testing::DoAll(::testing::SetArgPointee<1>(0x300), ::testing::Return(true)));
+
+  ASSERT_FALSE(elf.GetGlobalVariable(global, &offset));
+}
+
+TEST_F(ElfTest, get_global_valid_dynamic_zero) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(true);
+  elf.FakeSetLoadBias(0);
+
+  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
+  elf.FakeSetInterface(interface);
+
+  ElfInterfaceMock* gnu_interface = new ElfInterfaceMock(memory_);
+  elf.FakeSetGnuDebugdataInterface(gnu_interface);
+
+  uint64_t offset;
+  std::string global("something");
+  EXPECT_CALL(*interface, GetGlobalVariable(global, &offset)).WillOnce(::testing::Return(false));
+
+  EXPECT_CALL(*gnu_interface, GetGlobalVariable(global, &offset))
+      .WillOnce(::testing::DoAll(::testing::SetArgPointee<1>(0x500), ::testing::Return(true)));
+
+  ASSERT_TRUE(elf.GetGlobalVariable(global, &offset));
+  EXPECT_EQ(0x500U, offset);
+}
+
+TEST_F(ElfTest, get_global_valid_in_gnu_debugdata_dynamic_zero) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(true);
+  elf.FakeSetLoadBias(0);
+
+  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
+  elf.FakeSetInterface(interface);
+
+  uint64_t offset;
+  std::string global("something");
+  EXPECT_CALL(*interface, GetGlobalVariable(global, &offset))
+      .WillOnce(::testing::DoAll(::testing::SetArgPointee<1>(0x300), ::testing::Return(true)));
+
+  ASSERT_TRUE(elf.GetGlobalVariable(global, &offset));
+  EXPECT_EQ(0x300U, offset);
+}
+
+TEST_F(ElfTest, get_global_valid_dynamic_zero_non_zero_load_bias) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(true);
+  elf.FakeSetLoadBias(0x100);
+
+  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
+  elf.FakeSetInterface(interface);
+
+  uint64_t offset;
+  std::string global("something");
+  EXPECT_CALL(*interface, GetGlobalVariable(global, &offset))
+      .WillOnce(::testing::DoAll(::testing::SetArgPointee<1>(0x300), ::testing::Return(true)));
+
+  ASSERT_TRUE(elf.GetGlobalVariable(global, &offset));
+  EXPECT_EQ(0x200U, offset);
+}
+
+TEST_F(ElfTest, get_global_valid_dynamic_adjust_negative) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(true);
+  elf.FakeSetLoadBias(0);
+
+  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
+  interface->MockSetDynamicOffset(0x400);
+  interface->MockSetDynamicVaddr(0x800);
+  interface->MockSetDynamicSize(0x100);
+  elf.FakeSetInterface(interface);
+
+  uint64_t offset;
+  std::string global("something");
+  EXPECT_CALL(*interface, GetGlobalVariable(global, &offset))
+      .WillOnce(::testing::DoAll(::testing::SetArgPointee<1>(0x850), ::testing::Return(true)));
+
+  ASSERT_TRUE(elf.GetGlobalVariable(global, &offset));
+  EXPECT_EQ(0x450U, offset);
+}
+
+TEST_F(ElfTest, get_global_valid_dynamic_adjust_positive) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(true);
+  elf.FakeSetLoadBias(0);
+
+  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
+  interface->MockSetDynamicOffset(0x1000);
+  interface->MockSetDynamicVaddr(0x800);
+  interface->MockSetDynamicSize(0x100);
+  elf.FakeSetInterface(interface);
+
+  uint64_t offset;
+  std::string global("something");
+  EXPECT_CALL(*interface, GetGlobalVariable(global, &offset))
+      .WillOnce(::testing::DoAll(::testing::SetArgPointee<1>(0x850), ::testing::Return(true)));
+
+  ASSERT_TRUE(elf.GetGlobalVariable(global, &offset));
+  EXPECT_EQ(0x1050U, offset);
+}
+
+TEST_F(ElfTest, is_valid_pc_elf_invalid) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(false);
+  elf.FakeSetLoadBias(0);
+
+  EXPECT_FALSE(elf.IsValidPc(0x100));
+  EXPECT_FALSE(elf.IsValidPc(0x200));
+}
+
+TEST_F(ElfTest, is_valid_pc_interface) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(true);
+  elf.FakeSetLoadBias(0);
+
+  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
+  elf.FakeSetInterface(interface);
+
+  EXPECT_CALL(*interface, IsValidPc(0x1500)).WillOnce(::testing::Return(true));
+
+  EXPECT_TRUE(elf.IsValidPc(0x1500));
+}
+
+TEST_F(ElfTest, is_valid_pc_non_zero_load_bias) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(true);
+  elf.FakeSetLoadBias(0x1000);
+
+  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
+  elf.FakeSetInterface(interface);
+
+  EXPECT_CALL(*interface, IsValidPc(0x500)).WillOnce(::testing::Return(true));
+
+  EXPECT_FALSE(elf.IsValidPc(0x100));
+  EXPECT_FALSE(elf.IsValidPc(0x200));
+  EXPECT_TRUE(elf.IsValidPc(0x1500));
+}
+
+TEST_F(ElfTest, is_valid_pc_from_gnu_debugdata) {
+  ElfFake elf(memory_);
+  elf.FakeSetValid(true);
+  elf.FakeSetLoadBias(0);
+
+  ElfInterfaceMock* interface = new ElfInterfaceMock(memory_);
+  elf.FakeSetInterface(interface);
+  ElfInterfaceMock* gnu_interface = new ElfInterfaceMock(memory_);
+  elf.FakeSetGnuDebugdataInterface(gnu_interface);
+
+  EXPECT_CALL(*interface, IsValidPc(0x1500)).WillOnce(::testing::Return(false));
+  EXPECT_CALL(*gnu_interface, IsValidPc(0x1500)).WillOnce(::testing::Return(true));
+
+  EXPECT_TRUE(elf.IsValidPc(0x1500));
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/JitDebugTest.cpp b/libunwindstack/tests/JitDebugTest.cpp
new file mode 100644
index 0000000..83d7a49
--- /dev/null
+++ b/libunwindstack/tests/JitDebugTest.cpp
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <elf.h>
+#include <string.h>
+
+#include <memory>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <unwindstack/Elf.h>
+#include <unwindstack/JitDebug.h>
+#include <unwindstack/MapInfo.h>
+#include <unwindstack/Maps.h>
+#include <unwindstack/Memory.h>
+
+#include "ElfFake.h"
+#include "MemoryFake.h"
+
+namespace unwindstack {
+
+class JitDebugTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    memory_ = new MemoryFake;
+    process_memory_.reset(memory_);
+
+    jit_debug_.reset(new JitDebug(process_memory_));
+    jit_debug_->SetArch(ARCH_ARM);
+
+    maps_.reset(
+        new BufferMaps("1000-4000 ---s 00000000 00:00 0\n"
+                       "4000-6000 r--s 00000000 00:00 0\n"
+                       "6000-8000 -w-s 00000000 00:00 0\n"
+                       "a000-c000 --xp 00000000 00:00 0\n"
+                       "c000-f000 rwxp 00000000 00:00 0\n"
+                       "f000-11000 r-xp 00000000 00:00 0\n"
+                       "100000-110000 rw-p 0000000 00:00 0\n"
+                       "200000-210000 rw-p 0000000 00:00 0\n"));
+    ASSERT_TRUE(maps_->Parse());
+
+    MapInfo* map_info = maps_->Get(3);
+    ASSERT_TRUE(map_info != nullptr);
+    elf_memories_.push_back(new MemoryFake);
+    ElfFake* elf = new ElfFake(elf_memories_.back());
+    elf->FakeSetValid(true);
+    ElfInterfaceFake* interface = new ElfInterfaceFake(elf_memories_.back());
+    elf->FakeSetInterface(interface);
+    interface->FakeSetGlobalVariable("__jit_debug_descriptor", 0x800);
+    map_info->elf = elf;
+
+    map_info = maps_->Get(5);
+    ASSERT_TRUE(map_info != nullptr);
+    elf_memories_.push_back(new MemoryFake);
+    elf = new ElfFake(elf_memories_.back());
+    elf->FakeSetValid(true);
+    interface = new ElfInterfaceFake(elf_memories_.back());
+    elf->FakeSetInterface(interface);
+    interface->FakeSetGlobalVariable("__jit_debug_descriptor", 0x800);
+    map_info->elf = elf;
+  }
+
+  template <typename EhdrType, typename ShdrType>
+  void CreateElf(uint64_t offset, uint8_t class_type, uint8_t machine_type, uint32_t pc,
+                 uint32_t size) {
+    EhdrType ehdr;
+    memset(&ehdr, 0, sizeof(ehdr));
+    uint64_t sh_offset = sizeof(ehdr);
+    memcpy(ehdr.e_ident, ELFMAG, SELFMAG);
+    ehdr.e_ident[EI_CLASS] = class_type;
+    ehdr.e_machine = machine_type;
+    ehdr.e_shstrndx = 1;
+    ehdr.e_shoff = sh_offset;
+    ehdr.e_shentsize = sizeof(ShdrType);
+    ehdr.e_shnum = 3;
+    memory_->SetMemory(offset, &ehdr, sizeof(ehdr));
+
+    ShdrType shdr;
+    memset(&shdr, 0, sizeof(shdr));
+    shdr.sh_type = SHT_NULL;
+    memory_->SetMemory(offset + sh_offset, &shdr, sizeof(shdr));
+
+    sh_offset += sizeof(shdr);
+    memset(&shdr, 0, sizeof(shdr));
+    shdr.sh_type = SHT_STRTAB;
+    shdr.sh_name = 1;
+    shdr.sh_offset = 0x500;
+    shdr.sh_size = 0x100;
+    memory_->SetMemory(offset + sh_offset, &shdr, sizeof(shdr));
+    memory_->SetMemory(offset + 0x500, ".debug_frame");
+
+    sh_offset += sizeof(shdr);
+    memset(&shdr, 0, sizeof(shdr));
+    shdr.sh_type = SHT_PROGBITS;
+    shdr.sh_name = 0;
+    shdr.sh_addr = 0x600;
+    shdr.sh_offset = 0x600;
+    shdr.sh_size = 0x200;
+    memory_->SetMemory(offset + sh_offset, &shdr, sizeof(shdr));
+
+    // Now add a single cie/fde.
+    uint64_t dwarf_offset = offset + 0x600;
+    if (class_type == ELFCLASS32) {
+      // CIE 32 information.
+      memory_->SetData32(dwarf_offset, 0xfc);
+      memory_->SetData32(dwarf_offset + 0x4, 0xffffffff);
+      memory_->SetData8(dwarf_offset + 0x8, 1);
+      memory_->SetData8(dwarf_offset + 0x9, '\0');
+      memory_->SetData8(dwarf_offset + 0xa, 0x4);
+      memory_->SetData8(dwarf_offset + 0xb, 0x4);
+      memory_->SetData8(dwarf_offset + 0xc, 0x1);
+
+      // FDE 32 information.
+      memory_->SetData32(dwarf_offset + 0x100, 0xfc);
+      memory_->SetData32(dwarf_offset + 0x104, 0);
+      memory_->SetData32(dwarf_offset + 0x108, pc);
+      memory_->SetData32(dwarf_offset + 0x10c, size);
+    } else {
+      // CIE 64 information.
+      memory_->SetData32(dwarf_offset, 0xffffffff);
+      memory_->SetData64(dwarf_offset + 4, 0xf4);
+      memory_->SetData64(dwarf_offset + 0xc, 0xffffffffffffffffULL);
+      memory_->SetData8(dwarf_offset + 0x14, 1);
+      memory_->SetData8(dwarf_offset + 0x15, '\0');
+      memory_->SetData8(dwarf_offset + 0x16, 0x4);
+      memory_->SetData8(dwarf_offset + 0x17, 0x4);
+      memory_->SetData8(dwarf_offset + 0x18, 0x1);
+
+      // FDE 64 information.
+      memory_->SetData32(dwarf_offset + 0x100, 0xffffffff);
+      memory_->SetData64(dwarf_offset + 0x104, 0xf4);
+      memory_->SetData64(dwarf_offset + 0x10c, 0);
+      memory_->SetData64(dwarf_offset + 0x114, pc);
+      memory_->SetData64(dwarf_offset + 0x11c, size);
+    }
+  }
+
+  void WriteDescriptor32(uint64_t addr, uint32_t entry);
+  void WriteDescriptor64(uint64_t addr, uint64_t entry);
+  void WriteEntry32Pack(uint64_t addr, uint32_t prev, uint32_t next, uint32_t elf_addr,
+                        uint64_t elf_size);
+  void WriteEntry32Pad(uint64_t addr, uint32_t prev, uint32_t next, uint32_t elf_addr,
+                       uint64_t elf_size);
+  void WriteEntry64(uint64_t addr, uint64_t prev, uint64_t next, uint64_t elf_addr,
+                    uint64_t elf_size);
+
+  std::shared_ptr<Memory> process_memory_;
+  MemoryFake* memory_;
+  std::vector<MemoryFake*> elf_memories_;
+  std::unique_ptr<JitDebug> jit_debug_;
+  std::unique_ptr<BufferMaps> maps_;
+};
+
+void JitDebugTest::WriteDescriptor32(uint64_t addr, uint32_t entry) {
+  // Format of the 32 bit JITDescriptor structure:
+  //   uint32_t version
+  memory_->SetData32(addr, 1);
+  //   uint32_t action_flag
+  memory_->SetData32(addr + 4, 0);
+  //   uint32_t relevant_entry
+  memory_->SetData32(addr + 8, 0);
+  //   uint32_t first_entry
+  memory_->SetData32(addr + 12, entry);
+}
+
+void JitDebugTest::WriteDescriptor64(uint64_t addr, uint64_t entry) {
+  // Format of the 64 bit JITDescriptor structure:
+  //   uint32_t version
+  memory_->SetData32(addr, 1);
+  //   uint32_t action_flag
+  memory_->SetData32(addr + 4, 0);
+  //   uint64_t relevant_entry
+  memory_->SetData64(addr + 8, 0);
+  //   uint64_t first_entry
+  memory_->SetData64(addr + 16, entry);
+}
+
+void JitDebugTest::WriteEntry32Pack(uint64_t addr, uint32_t prev, uint32_t next, uint32_t elf_addr,
+                                    uint64_t elf_size) {
+  // Format of the 32 bit JITCodeEntry structure:
+  //   uint32_t next
+  memory_->SetData32(addr, next);
+  //   uint32_t prev
+  memory_->SetData32(addr + 4, prev);
+  //   uint32_t symfile_addr
+  memory_->SetData32(addr + 8, elf_addr);
+  //   uint64_t symfile_size
+  memory_->SetData64(addr + 12, elf_size);
+}
+
+void JitDebugTest::WriteEntry32Pad(uint64_t addr, uint32_t prev, uint32_t next, uint32_t elf_addr,
+                                   uint64_t elf_size) {
+  // Format of the 32 bit JITCodeEntry structure:
+  //   uint32_t next
+  memory_->SetData32(addr, next);
+  //   uint32_t prev
+  memory_->SetData32(addr + 4, prev);
+  //   uint32_t symfile_addr
+  memory_->SetData32(addr + 8, elf_addr);
+  //   uint32_t pad
+  memory_->SetData32(addr + 12, 0);
+  //   uint64_t symfile_size
+  memory_->SetData64(addr + 16, elf_size);
+}
+
+void JitDebugTest::WriteEntry64(uint64_t addr, uint64_t prev, uint64_t next, uint64_t elf_addr,
+                                uint64_t elf_size) {
+  // Format of the 64 bit JITCodeEntry structure:
+  //   uint64_t next
+  memory_->SetData64(addr, next);
+  //   uint64_t prev
+  memory_->SetData64(addr + 8, prev);
+  //   uint64_t symfile_addr
+  memory_->SetData64(addr + 16, elf_addr);
+  //   uint64_t symfile_size
+  memory_->SetData64(addr + 24, elf_size);
+}
+
+TEST_F(JitDebugTest, get_elf_invalid) {
+  Elf* elf = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf == nullptr);
+}
+
+TEST_F(JitDebugTest, get_elf_no_global_variable) {
+  maps_.reset(new BufferMaps(""));
+  Elf* elf = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf == nullptr);
+}
+
+TEST_F(JitDebugTest, get_elf_no_valid_descriptor_in_memory) {
+  CreateElf<Elf32_Ehdr, Elf32_Shdr>(0x4000, ELFCLASS32, EM_ARM, 0x1500, 0x200);
+
+  Elf* elf = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf == nullptr);
+}
+
+TEST_F(JitDebugTest, get_elf_no_valid_code_entry) {
+  CreateElf<Elf32_Ehdr, Elf32_Shdr>(0x4000, ELFCLASS32, EM_ARM, 0x1500, 0x200);
+
+  WriteDescriptor32(0xf800, 0x200000);
+
+  Elf* elf = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf == nullptr);
+}
+
+TEST_F(JitDebugTest, get_elf_invalid_descriptor_first_entry) {
+  CreateElf<Elf32_Ehdr, Elf32_Shdr>(0x4000, ELFCLASS32, EM_ARM, 0x1500, 0x200);
+
+  WriteDescriptor32(0xf800, 0);
+
+  Elf* elf = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf == nullptr);
+}
+
+TEST_F(JitDebugTest, get_elf_invalid_descriptor_version) {
+  CreateElf<Elf32_Ehdr, Elf32_Shdr>(0x4000, ELFCLASS32, EM_ARM, 0x1500, 0x200);
+
+  WriteDescriptor32(0xf800, 0x20000);
+  // Set the version to an invalid value.
+  memory_->SetData32(0xf800, 2);
+
+  Elf* elf = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf == nullptr);
+}
+
+TEST_F(JitDebugTest, get_elf_32) {
+  CreateElf<Elf32_Ehdr, Elf32_Shdr>(0x4000, ELFCLASS32, EM_ARM, 0x1500, 0x200);
+
+  WriteDescriptor32(0xf800, 0x200000);
+  WriteEntry32Pad(0x200000, 0, 0, 0x4000, 0x1000);
+
+  Elf* elf = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf != nullptr);
+
+  // Clear the memory and verify all of the data is cached.
+  memory_->Clear();
+  Elf* elf2 = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf2 != nullptr);
+  EXPECT_EQ(elf, elf2);
+}
+
+TEST_F(JitDebugTest, get_elf_x86) {
+  CreateElf<Elf32_Ehdr, Elf32_Shdr>(0x4000, ELFCLASS32, EM_ARM, 0x1500, 0x200);
+
+  WriteDescriptor32(0xf800, 0x200000);
+  WriteEntry32Pack(0x200000, 0, 0, 0x4000, 0x1000);
+
+  jit_debug_->SetArch(ARCH_X86);
+  Elf* elf = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf != nullptr);
+
+  // Clear the memory and verify all of the data is cached.
+  memory_->Clear();
+  Elf* elf2 = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf2 != nullptr);
+  EXPECT_EQ(elf, elf2);
+}
+
+TEST_F(JitDebugTest, get_elf_64) {
+  CreateElf<Elf64_Ehdr, Elf64_Shdr>(0x4000, ELFCLASS64, EM_AARCH64, 0x1500, 0x200);
+
+  WriteDescriptor64(0xf800, 0x200000);
+  WriteEntry64(0x200000, 0, 0, 0x4000, 0x1000);
+
+  jit_debug_->SetArch(ARCH_ARM64);
+  Elf* elf = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf != nullptr);
+
+  // Clear the memory and verify all of the data is cached.
+  memory_->Clear();
+  Elf* elf2 = jit_debug_->GetElf(maps_.get(), 0x1500);
+  ASSERT_TRUE(elf2 != nullptr);
+  EXPECT_EQ(elf, elf2);
+}
+
+TEST_F(JitDebugTest, get_elf_multiple_entries) {
+  CreateElf<Elf32_Ehdr, Elf32_Shdr>(0x4000, ELFCLASS32, EM_ARM, 0x1500, 0x200);
+  CreateElf<Elf32_Ehdr, Elf32_Shdr>(0x5000, ELFCLASS32, EM_ARM, 0x2300, 0x400);
+
+  WriteDescriptor32(0xf800, 0x200000);
+  WriteEntry32Pad(0x200000, 0, 0x200100, 0x4000, 0x1000);
+  WriteEntry32Pad(0x200100, 0x200100, 0, 0x5000, 0x1000);
+
+  Elf* elf_2 = jit_debug_->GetElf(maps_.get(), 0x2400);
+  ASSERT_TRUE(elf_2 != nullptr);
+
+  Elf* elf_1 = jit_debug_->GetElf(maps_.get(), 0x1600);
+  ASSERT_TRUE(elf_1 != nullptr);
+
+  // Clear the memory and verify all of the data is cached.
+  memory_->Clear();
+  EXPECT_EQ(elf_1, jit_debug_->GetElf(maps_.get(), 0x1500));
+  EXPECT_EQ(elf_1, jit_debug_->GetElf(maps_.get(), 0x16ff));
+  EXPECT_EQ(elf_2, jit_debug_->GetElf(maps_.get(), 0x2300));
+  EXPECT_EQ(elf_2, jit_debug_->GetElf(maps_.get(), 0x26ff));
+  EXPECT_EQ(nullptr, jit_debug_->GetElf(maps_.get(), 0x1700));
+  EXPECT_EQ(nullptr, jit_debug_->GetElf(maps_.get(), 0x2700));
+}
+
+TEST_F(JitDebugTest, get_elf_search_libs) {
+  CreateElf<Elf32_Ehdr, Elf32_Shdr>(0x4000, ELFCLASS32, EM_ARM, 0x1500, 0x200);
+
+  WriteDescriptor32(0xf800, 0x200000);
+  WriteEntry32Pad(0x200000, 0, 0, 0x4000, 0x1000);
+
+  // Only search a given named list of libs.
+  std::vector<std::string> libs{"libart.so"};
+  jit_debug_.reset(new JitDebug(process_memory_, libs));
+  jit_debug_->SetArch(ARCH_ARM);
+  EXPECT_TRUE(jit_debug_->GetElf(maps_.get(), 0x1500) == nullptr);
+
+  // Change the name of the map that includes the value and verify this works.
+  MapInfo* map_info = maps_->Get(5);
+  map_info->name = "/system/lib/libart.so";
+  jit_debug_.reset(new JitDebug(process_memory_, libs));
+  // Make sure that clearing our copy of the libs doesn't affect the
+  // JitDebug object.
+  libs.clear();
+  jit_debug_->SetArch(ARCH_ARM);
+  EXPECT_TRUE(jit_debug_->GetElf(maps_.get(), 0x1500) != nullptr);
+}
+
+}  // namespace unwindstack
diff --git a/libunwindstack/tests/RegsFake.h b/libunwindstack/tests/RegsFake.h
index b81b2ca..8f7d913 100644
--- a/libunwindstack/tests/RegsFake.h
+++ b/libunwindstack/tests/RegsFake.h
@@ -45,7 +45,7 @@
 
   void IterateRegisters(std::function<void(const char*, uint64_t)>) override {}
 
-  bool Format32Bit() { return false; }
+  bool Is32Bit() { return false; }
 
   uint64_t GetAdjustedPc(uint64_t rel_pc, Elf*) override { return rel_pc - 2; }
 
diff --git a/libunwindstack/tests/RegsTest.cpp b/libunwindstack/tests/RegsTest.cpp
index 3f84890..15d5458 100644
--- a/libunwindstack/tests/RegsTest.cpp
+++ b/libunwindstack/tests/RegsTest.cpp
@@ -188,7 +188,7 @@
 
   regs_arm.set_pc(0x1500);
   EXPECT_EQ(0x500U, invalid_elf->GetRelPc(regs_arm.pc(), &map_info));
-  EXPECT_EQ(0x500U, regs_arm.GetAdjustedPc(0x500U, invalid_elf));
+  EXPECT_EQ(0x4fcU, regs_arm.GetAdjustedPc(0x500U, invalid_elf));
 
   regs_arm64.set_pc(0x1600);
   EXPECT_EQ(0x600U, invalid_elf->GetRelPc(regs_arm64.pc(), &map_info));
diff --git a/libunwindstack/tests/SymbolsTest.cpp b/libunwindstack/tests/SymbolsTest.cpp
index da258a6..45a7b58 100644
--- a/libunwindstack/tests/SymbolsTest.cpp
+++ b/libunwindstack/tests/SymbolsTest.cpp
@@ -330,9 +330,69 @@
   ASSERT_EQ(3U, func_offset);
 }
 
+TYPED_TEST_P(SymbolsTest, get_global) {
+  uint64_t start_offset = 0x1000;
+  uint64_t str_offset = 0xa000;
+  Symbols symbols(start_offset, 4 * sizeof(TypeParam), sizeof(TypeParam), str_offset, 0x1000);
+
+  TypeParam sym;
+  memset(&sym, 0, sizeof(sym));
+  sym.st_shndx = SHN_COMMON;
+  sym.st_info = STT_OBJECT | (STB_GLOBAL << 4);
+  sym.st_name = 0x100;
+  this->memory_.SetMemory(start_offset, &sym, sizeof(sym));
+  this->memory_.SetMemory(str_offset + 0x100, "global_0");
+
+  start_offset += sizeof(sym);
+  memset(&sym, 0, sizeof(sym));
+  sym.st_shndx = SHN_COMMON;
+  sym.st_info = STT_FUNC;
+  sym.st_name = 0x200;
+  sym.st_value = 0x10000;
+  sym.st_size = 0x100;
+  this->memory_.SetMemory(start_offset, &sym, sizeof(sym));
+  this->memory_.SetMemory(str_offset + 0x200, "function_0");
+
+  start_offset += sizeof(sym);
+  memset(&sym, 0, sizeof(sym));
+  sym.st_shndx = SHN_COMMON;
+  sym.st_info = STT_OBJECT | (STB_GLOBAL << 4);
+  sym.st_name = 0x300;
+  this->memory_.SetMemory(start_offset, &sym, sizeof(sym));
+  this->memory_.SetMemory(str_offset + 0x300, "global_1");
+
+  start_offset += sizeof(sym);
+  memset(&sym, 0, sizeof(sym));
+  sym.st_shndx = SHN_COMMON;
+  sym.st_info = STT_FUNC;
+  sym.st_name = 0x400;
+  sym.st_value = 0x12000;
+  sym.st_size = 0x100;
+  this->memory_.SetMemory(start_offset, &sym, sizeof(sym));
+  this->memory_.SetMemory(str_offset + 0x400, "function_1");
+
+  uint64_t offset;
+  EXPECT_TRUE(symbols.GetGlobal<TypeParam>(&this->memory_, "global_0", &offset));
+  EXPECT_TRUE(symbols.GetGlobal<TypeParam>(&this->memory_, "global_1", &offset));
+  EXPECT_TRUE(symbols.GetGlobal<TypeParam>(&this->memory_, "global_0", &offset));
+  EXPECT_TRUE(symbols.GetGlobal<TypeParam>(&this->memory_, "global_1", &offset));
+
+  EXPECT_FALSE(symbols.GetGlobal<TypeParam>(&this->memory_, "function_0", &offset));
+  EXPECT_FALSE(symbols.GetGlobal<TypeParam>(&this->memory_, "function_1", &offset));
+
+  std::string name;
+  EXPECT_TRUE(symbols.GetName<TypeParam>(0x10002, 0, &this->memory_, &name, &offset));
+  EXPECT_EQ("function_0", name);
+  EXPECT_EQ(2U, offset);
+
+  EXPECT_TRUE(symbols.GetName<TypeParam>(0x12004, 0, &this->memory_, &name, &offset));
+  EXPECT_EQ("function_1", name);
+  EXPECT_EQ(4U, offset);
+}
+
 REGISTER_TYPED_TEST_CASE_P(SymbolsTest, function_bounds_check, no_symbol, multiple_entries,
                            multiple_entries_nonstandard_size, load_bias, symtab_value_out_of_bounds,
-                           symtab_read_cached);
+                           symtab_read_cached, get_global);
 
 typedef ::testing::Types<Elf32_Sym, Elf64_Sym> SymbolsTestTypes;
 INSTANTIATE_TYPED_TEST_CASE_P(, SymbolsTest, SymbolsTestTypes);
diff --git a/libunwindstack/tests/UnwindOfflineTest.cpp b/libunwindstack/tests/UnwindOfflineTest.cpp
index 8f28036..9216204 100644
--- a/libunwindstack/tests/UnwindOfflineTest.cpp
+++ b/libunwindstack/tests/UnwindOfflineTest.cpp
@@ -25,14 +25,17 @@
 #include <string>
 #include <vector>
 
+#include <unwindstack/JitDebug.h>
 #include <unwindstack/Maps.h>
 #include <unwindstack/Memory.h>
 #include <unwindstack/RegsArm.h>
 #include <unwindstack/RegsArm64.h>
+#include <unwindstack/RegsX86.h>
 #include <unwindstack/Unwinder.h>
 
 #include "MachineArm.h"
 #include "MachineArm64.h"
+#include "MachineX86.h"
 
 #include "ElfTestUtils.h"
 
@@ -92,7 +95,7 @@
       "  #00 pc 0001a9f8  libc.so (abort+63)\n"
       "  #01 pc 00006a1b  libbase.so (_ZN7android4base14DefaultAborterEPKc+6)\n"
       "  #02 pc 00007441  libbase.so (_ZN7android4base10LogMessageD2Ev+748)\n"
-      "  #03 pc 00015149  /does/not/exist/libhidlbase.so\n",
+      "  #03 pc 00015147  /does/not/exist/libhidlbase.so\n",
       frame_info);
 }
 
@@ -200,4 +203,207 @@
       frame_info);
 }
 
+static void AddMemory(std::string file_name, MemoryOfflineParts* parts) {
+  MemoryOffline* memory = new MemoryOffline;
+  ASSERT_TRUE(memory->Init(file_name.c_str(), 0));
+  parts->Add(memory);
+}
+
+TEST(UnwindOfflineTest, jit_debug_x86_32) {
+  std::string dir(TestGetFileDirectory() + "offline/jit_debug_x86_32/");
+
+  MemoryOfflineParts* memory = new MemoryOfflineParts;
+  AddMemory(dir + "descriptor.data", memory);
+  AddMemory(dir + "stack.data", memory);
+  for (size_t i = 0; i < 7; i++) {
+    AddMemory(dir + "entry" + std::to_string(i) + ".data", memory);
+    AddMemory(dir + "jit" + std::to_string(i) + ".data", memory);
+  }
+
+  FILE* fp = fopen((dir + "regs.txt").c_str(), "r");
+  ASSERT_TRUE(fp != nullptr);
+  RegsX86 regs;
+  uint64_t reg_value;
+  ASSERT_EQ(1, fscanf(fp, "eax: %" SCNx64 "\n", &reg_value));
+  regs[X86_REG_EAX] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "ebx: %" SCNx64 "\n", &reg_value));
+  regs[X86_REG_EBX] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "ecx: %" SCNx64 "\n", &reg_value));
+  regs[X86_REG_ECX] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "edx: %" SCNx64 "\n", &reg_value));
+  regs[X86_REG_EDX] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "ebp: %" SCNx64 "\n", &reg_value));
+  regs[X86_REG_EBP] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "edi: %" SCNx64 "\n", &reg_value));
+  regs[X86_REG_EDI] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "esi: %" SCNx64 "\n", &reg_value));
+  regs[X86_REG_ESI] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "esp: %" SCNx64 "\n", &reg_value));
+  regs[X86_REG_ESP] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "eip: %" SCNx64 "\n", &reg_value));
+  regs[X86_REG_EIP] = reg_value;
+  regs.SetFromRaw();
+  fclose(fp);
+
+  fp = fopen((dir + "maps.txt").c_str(), "r");
+  ASSERT_TRUE(fp != nullptr);
+  // The file is guaranteed to be less than 4096 bytes.
+  std::vector<char> buffer(4096);
+  ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp));
+  fclose(fp);
+
+  BufferMaps maps(buffer.data());
+  ASSERT_TRUE(maps.Parse());
+
+  ASSERT_EQ(ARCH_X86, regs.Arch());
+
+  std::shared_ptr<Memory> process_memory(memory);
+
+  char* cwd = getcwd(nullptr, 0);
+  ASSERT_EQ(0, chdir(dir.c_str()));
+  JitDebug jit_debug(process_memory);
+  Unwinder unwinder(128, &maps, &regs, process_memory);
+  unwinder.SetJitDebug(&jit_debug, regs.Arch());
+  unwinder.Unwind();
+  ASSERT_EQ(0, chdir(cwd));
+  free(cwd);
+
+  std::string frame_info(DumpFrames(unwinder));
+  ASSERT_EQ(69U, unwinder.NumFrames()) << "Unwind:\n" << frame_info;
+  EXPECT_EQ(
+      "  #00 pc 00068fb8  libarttestd.so (_ZN3artL13CauseSegfaultEv+72)\n"
+      "  #01 pc 00067f00  libarttestd.so (Java_Main_unwindInProcess+10032)\n"
+      "  #02 pc 000021a8 (offset 0x2000)  137-cfi.odex (boolean Main.unwindInProcess(boolean, int, "
+      "boolean)+136)\n"
+      "  #03 pc 0000fe81  anonymous:ee74c000 (boolean Main.bar(boolean)+65)\n"
+      "  #04 pc 006ad4d2  libartd.so (art_quick_invoke_stub+338)\n"
+      "  #05 pc 00146ab5  libartd.so "
+      "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+885)\n"
+      "  #06 pc 0039cf0d  libartd.so "
+      "(_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_"
+      "11ShadowFrameEtPNS_6JValueE+653)\n"
+      "  #07 pc 00392552  libartd.so "
+      "(_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_"
+      "6JValueEb+354)\n"
+      "  #08 pc 0039399a  libartd.so "
+      "(_ZN3art11interpreter30EnterInterpreterFromEntryPointEPNS_6ThreadERKNS_"
+      "20CodeItemDataAccessorEPNS_11ShadowFrameE+234)\n"
+      "  #09 pc 00684362  libartd.so (artQuickToInterpreterBridge+1058)\n"
+      "  #10 pc 006b35bd  libartd.so (art_quick_to_interpreter_bridge+77)\n"
+      "  #11 pc 0000fe04  anonymous:ee74c000 (int Main.compare(Main, Main)+52)\n"
+      "  #12 pc 006ad4d2  libartd.so (art_quick_invoke_stub+338)\n"
+      "  #13 pc 00146ab5  libartd.so "
+      "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+885)\n"
+      "  #14 pc 0039cf0d  libartd.so "
+      "(_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_"
+      "11ShadowFrameEtPNS_6JValueE+653)\n"
+      "  #15 pc 00392552  libartd.so "
+      "(_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_"
+      "6JValueEb+354)\n"
+      "  #16 pc 0039399a  libartd.so "
+      "(_ZN3art11interpreter30EnterInterpreterFromEntryPointEPNS_6ThreadERKNS_"
+      "20CodeItemDataAccessorEPNS_11ShadowFrameE+234)\n"
+      "  #17 pc 00684362  libartd.so (artQuickToInterpreterBridge+1058)\n"
+      "  #18 pc 006b35bd  libartd.so (art_quick_to_interpreter_bridge+77)\n"
+      "  #19 pc 0000fd3c  anonymous:ee74c000 (int Main.compare(java.lang.Object, "
+      "java.lang.Object)+108)\n"
+      "  #20 pc 006ad4d2  libartd.so (art_quick_invoke_stub+338)\n"
+      "  #21 pc 00146ab5  libartd.so "
+      "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+885)\n"
+      "  #22 pc 0039cf0d  libartd.so "
+      "(_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_"
+      "11ShadowFrameEtPNS_6JValueE+653)\n"
+      "  #23 pc 00392552  libartd.so "
+      "(_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_"
+      "6JValueEb+354)\n"
+      "  #24 pc 0039399a  libartd.so "
+      "(_ZN3art11interpreter30EnterInterpreterFromEntryPointEPNS_6ThreadERKNS_"
+      "20CodeItemDataAccessorEPNS_11ShadowFrameE+234)\n"
+      "  #25 pc 00684362  libartd.so (artQuickToInterpreterBridge+1058)\n"
+      "  #26 pc 006b35bd  libartd.so (art_quick_to_interpreter_bridge+77)\n"
+      "  #27 pc 0000fbdc  anonymous:ee74c000 (int "
+      "java.util.Arrays.binarySearch0(java.lang.Object[], int, int, java.lang.Object, "
+      "java.util.Comparator)+332)\n"
+      "  #28 pc 006ad6a2  libartd.so (art_quick_invoke_static_stub+418)\n"
+      "  #29 pc 00146acb  libartd.so "
+      "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+907)\n"
+      "  #30 pc 0039cf0d  libartd.so "
+      "(_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_"
+      "11ShadowFrameEtPNS_6JValueE+653)\n"
+      "  #31 pc 00392552  libartd.so "
+      "(_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_"
+      "6JValueEb+354)\n"
+      "  #32 pc 0039399a  libartd.so "
+      "(_ZN3art11interpreter30EnterInterpreterFromEntryPointEPNS_6ThreadERKNS_"
+      "20CodeItemDataAccessorEPNS_11ShadowFrameE+234)\n"
+      "  #33 pc 00684362  libartd.so (artQuickToInterpreterBridge+1058)\n"
+      "  #34 pc 006b35bd  libartd.so (art_quick_to_interpreter_bridge+77)\n"
+      "  #35 pc 0000f625  anonymous:ee74c000 (boolean Main.foo()+165)\n"
+      "  #36 pc 006ad4d2  libartd.so (art_quick_invoke_stub+338)\n"
+      "  #37 pc 00146ab5  libartd.so "
+      "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+885)\n"
+      "  #38 pc 0039cf0d  libartd.so "
+      "(_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_"
+      "11ShadowFrameEtPNS_6JValueE+653)\n"
+      "  #39 pc 00392552  libartd.so "
+      "(_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_"
+      "6JValueEb+354)\n"
+      "  #40 pc 0039399a  libartd.so "
+      "(_ZN3art11interpreter30EnterInterpreterFromEntryPointEPNS_6ThreadERKNS_"
+      "20CodeItemDataAccessorEPNS_11ShadowFrameE+234)\n"
+      "  #41 pc 00684362  libartd.so (artQuickToInterpreterBridge+1058)\n"
+      "  #42 pc 006b35bd  libartd.so (art_quick_to_interpreter_bridge+77)\n"
+      "  #43 pc 0000eedc  anonymous:ee74c000 (void Main.runPrimary()+60)\n"
+      "  #44 pc 006ad4d2  libartd.so (art_quick_invoke_stub+338)\n"
+      "  #45 pc 00146ab5  libartd.so "
+      "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+885)\n"
+      "  #46 pc 0039cf0d  libartd.so "
+      "(_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_"
+      "11ShadowFrameEtPNS_6JValueE+653)\n"
+      "  #47 pc 00392552  libartd.so "
+      "(_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_"
+      "6JValueEb+354)\n"
+      "  #48 pc 0039399a  libartd.so "
+      "(_ZN3art11interpreter30EnterInterpreterFromEntryPointEPNS_6ThreadERKNS_"
+      "20CodeItemDataAccessorEPNS_11ShadowFrameE+234)\n"
+      "  #49 pc 00684362  libartd.so (artQuickToInterpreterBridge+1058)\n"
+      "  #50 pc 006b35bd  libartd.so (art_quick_to_interpreter_bridge+77)\n"
+      "  #51 pc 0000ac22  anonymous:ee74c000 (void Main.main(java.lang.String[])+98)\n"
+      "  #52 pc 006ad6a2  libartd.so (art_quick_invoke_static_stub+418)\n"
+      "  #53 pc 00146acb  libartd.so "
+      "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+907)\n"
+      "  #54 pc 0039cf0d  libartd.so "
+      "(_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_"
+      "11ShadowFrameEtPNS_6JValueE+653)\n"
+      "  #55 pc 00392552  libartd.so "
+      "(_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_"
+      "6JValueEb+354)\n"
+      "  #56 pc 0039399a  libartd.so "
+      "(_ZN3art11interpreter30EnterInterpreterFromEntryPointEPNS_6ThreadERKNS_"
+      "20CodeItemDataAccessorEPNS_11ShadowFrameE+234)\n"
+      "  #57 pc 00684362  libartd.so (artQuickToInterpreterBridge+1058)\n"
+      "  #58 pc 006b35bd  libartd.so (art_quick_to_interpreter_bridge+77)\n"
+      "  #59 pc 006ad6a2  libartd.so (art_quick_invoke_static_stub+418)\n"
+      "  #60 pc 00146acb  libartd.so "
+      "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+907)\n"
+      "  #61 pc 005aac95  libartd.so "
+      "(_ZN3artL18InvokeWithArgArrayERKNS_33ScopedObjectAccessAlreadyRunnableEPNS_9ArtMethodEPNS_"
+      "8ArgArrayEPNS_6JValueEPKc+85)\n"
+      "  #62 pc 005aab5a  libartd.so "
+      "(_ZN3art17InvokeWithVarArgsERKNS_33ScopedObjectAccessAlreadyRunnableEP8_jobjectP10_"
+      "jmethodIDPc+362)\n"
+      "  #63 pc 0048a3dd  libartd.so "
+      "(_ZN3art3JNI21CallStaticVoidMethodVEP7_JNIEnvP7_jclassP10_jmethodIDPc+125)\n"
+      "  #64 pc 0018448c  libartd.so "
+      "(_ZN3art8CheckJNI11CallMethodVEPKcP7_JNIEnvP8_jobjectP7_jclassP10_jmethodIDPcNS_"
+      "9Primitive4TypeENS_10InvokeTypeE+1964)\n"
+      "  #65 pc 0017cf06  libartd.so "
+      "(_ZN3art8CheckJNI21CallStaticVoidMethodVEP7_JNIEnvP7_jclassP10_jmethodIDPc+70)\n"
+      "  #66 pc 00001d8c  dalvikvm32 "
+      "(_ZN7_JNIEnv20CallStaticVoidMethodEP7_jclassP10_jmethodIDz+60)\n"
+      "  #67 pc 00001a80  dalvikvm32 (main+1312)\n"
+      "  #68 pc 00018275  libc.so\n",
+      frame_info);
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/137-cfi.odex b/libunwindstack/tests/files/offline/jit_debug_x86_32/137-cfi.odex
new file mode 100644
index 0000000..870ac0a
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/137-cfi.odex
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/dalvikvm32 b/libunwindstack/tests/files/offline/jit_debug_x86_32/dalvikvm32
new file mode 100644
index 0000000..76ffad9
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/dalvikvm32
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/descriptor.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/descriptor.data
new file mode 100644
index 0000000..466dae2
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/descriptor.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/entry0.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry0.data
new file mode 100644
index 0000000..3a725e8
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry0.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/entry1.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry1.data
new file mode 100644
index 0000000..767550f
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry1.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/entry2.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry2.data
new file mode 100644
index 0000000..e7e492e
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry2.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/entry3.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry3.data
new file mode 100644
index 0000000..65f9cd4
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry3.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/entry4.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry4.data
new file mode 100644
index 0000000..30aa28c
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry4.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/entry5.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry5.data
new file mode 100644
index 0000000..3c89673
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry5.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/entry6.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry6.data
new file mode 100644
index 0000000..9c9b83c
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/entry6.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/jit0.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit0.data
new file mode 100644
index 0000000..eaad142
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit0.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/jit1.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit1.data
new file mode 100644
index 0000000..d534816
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit1.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/jit2.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit2.data
new file mode 100644
index 0000000..dbeb886
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit2.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/jit3.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit3.data
new file mode 100644
index 0000000..bf2142d
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit3.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/jit4.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit4.data
new file mode 100644
index 0000000..e2ba1b0
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit4.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/jit5.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit5.data
new file mode 100644
index 0000000..c27ba54
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit5.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/jit6.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit6.data
new file mode 100644
index 0000000..5fc8fae
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/jit6.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/libarttestd.so b/libunwindstack/tests/files/offline/jit_debug_x86_32/libarttestd.so
new file mode 100644
index 0000000..e72e673
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/libarttestd.so
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/libc.so b/libunwindstack/tests/files/offline/jit_debug_x86_32/libc.so
new file mode 100644
index 0000000..9c78790
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/libc.so
Binary files differ
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/maps.txt b/libunwindstack/tests/files/offline/jit_debug_x86_32/maps.txt
new file mode 100644
index 0000000..db4f9f7
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/maps.txt
@@ -0,0 +1,6 @@
+56573000-56577000 r-xp 0 00:00 0   dalvikvm32
+eb833000-eb8cc000 r-xp 0 00:00 0   libarttestd.so
+ec606000-ec607000 r-xp 2000 00:00 0   137-cfi.odex
+ee74c000-f074c000 r-xp 0 00:00 0   anonymous:ee74c000
+f6be1000-f732b000 r-xp 0 00:00 0   libartd.so
+f734b000-f74fc000 r-xp 0 00:00 0   libc.so
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/regs.txt b/libunwindstack/tests/files/offline/jit_debug_x86_32/regs.txt
new file mode 100644
index 0000000..f68305b
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/regs.txt
@@ -0,0 +1,9 @@
+eax: eb8cccd0
+ebx: eb8cccd0
+ecx: ff
+edx: ffeb2ca8
+ebp: ffeb5298
+edi: ffeb5c08
+esi: ffeb5c00
+esp: ffeb5280
+eip: eb89bfb8
diff --git a/libunwindstack/tests/files/offline/jit_debug_x86_32/stack.data b/libunwindstack/tests/files/offline/jit_debug_x86_32/stack.data
new file mode 100644
index 0000000..c345762
--- /dev/null
+++ b/libunwindstack/tests/files/offline/jit_debug_x86_32/stack.data
Binary files differ
diff --git a/libunwindstack/tools/unwind.cpp b/libunwindstack/tools/unwind.cpp
index 81bedb7..07e48af 100644
--- a/libunwindstack/tools/unwind.cpp
+++ b/libunwindstack/tools/unwind.cpp
@@ -27,6 +27,7 @@
 #include <unistd.h>
 
 #include <unwindstack/Elf.h>
+#include <unwindstack/JitDebug.h>
 #include <unwindstack/Maps.h>
 #include <unwindstack/Memory.h>
 #include <unwindstack/Regs.h>
@@ -90,6 +91,8 @@
 
   auto process_memory = unwindstack::Memory::CreateProcessMemory(pid);
   unwindstack::Unwinder unwinder(128, &remote_maps, regs, process_memory);
+  unwindstack::JitDebug jit_debug(process_memory);
+  unwinder.SetJitDebug(&jit_debug, regs->Arch());
   unwinder.Unwind();
 
   // Print the frames.
diff --git a/libunwindstack/tools/unwind_for_offline.cpp b/libunwindstack/tools/unwind_for_offline.cpp
index d64ef8f..74868d4 100644
--- a/libunwindstack/tools/unwind_for_offline.cpp
+++ b/libunwindstack/tools/unwind_for_offline.cpp
@@ -33,6 +33,7 @@
 #include <vector>
 
 #include <unwindstack/Elf.h>
+#include <unwindstack/JitDebug.h>
 #include <unwindstack/Maps.h>
 #include <unwindstack/Memory.h>
 #include <unwindstack/Regs.h>
@@ -191,7 +192,9 @@
   // elf files are involved.
   uint64_t sp = regs->sp();
   auto process_memory = unwindstack::Memory::CreateProcessMemory(pid);
+  unwindstack::JitDebug jit_debug(process_memory);
   unwindstack::Unwinder unwinder(1024, &maps, regs, process_memory);
+  unwinder.SetJitDebug(&jit_debug, regs->Arch());
   unwinder.Unwind();
 
   std::unordered_map<uint64_t, map_info_t> maps_by_start;
@@ -214,6 +217,10 @@
     }
   }
 
+  for (size_t i = 0; i < unwinder.NumFrames(); i++) {
+    printf("%s\n", unwinder.FormatFrame(i).c_str());
+  }
+
   if (!SaveStack(pid, sp, last_sp)) {
     return 1;
   }
diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c
index 5cfa2c8..fd83ecc 100644
--- a/lmkd/lmkd.c
+++ b/lmkd/lmkd.c
@@ -900,7 +900,9 @@
     downgrade_pressure = (int64_t)property_get_int32("ro.lmk.downgrade_pressure", 60);
     is_go_device = property_get_bool("ro.config.low_ram", false);
 
-    mlockall(MCL_FUTURE);
+    if (mlockall(MCL_CURRENT | MCL_FUTURE))
+        ALOGW("mlockall failed: errno=%d", errno);
+
     sched_setscheduler(0, SCHED_FIFO, &param);
     if (!init())
         mainloop();
diff --git a/logd/LogAudit.cpp b/logd/LogAudit.cpp
index 1d0cc33..b76160d 100755
--- a/logd/LogAudit.cpp
+++ b/logd/LogAudit.cpp
@@ -45,7 +45,7 @@
         '0' + LOG_MAKEPRI(LOG_AUTH, LOG_PRI(PRI)) % 10, '>'
 
 LogAudit::LogAudit(LogBuffer* buf, LogReader* reader, int fdDmesg)
-    : SocketListener(mSock = getLogSocket(), false),
+    : SocketListener(getLogSocket(), false),
       logbuf(buf),
       reader(reader),
       fdDmesg(fdDmesg),
@@ -53,8 +53,7 @@
                                               BOOL_DEFAULT_TRUE)),
       events(__android_logger_property_get_bool("ro.logd.auditd.events",
                                                 BOOL_DEFAULT_TRUE)),
-      initialized(false),
-      tooFast(false) {
+      initialized(false) {
     static const char auditd_message[] = { KMSG_PRIORITY(LOG_INFO),
                                            'l',
                                            'o',
@@ -78,54 +77,12 @@
     write(fdDmesg, auditd_message, sizeof(auditd_message));
 }
 
-void LogAudit::checkRateLimit() {
-    // trim list for AUDIT_RATE_LIMIT_BURST_DURATION of history
-    log_time oldest(AUDIT_RATE_LIMIT_BURST_DURATION, 0);
-    bucket.emplace(android_log_clockid());
-    oldest = bucket.back() - oldest;
-    while (bucket.front() < oldest) bucket.pop();
-
-    static const size_t upperThreshold =
-        ((AUDIT_RATE_LIMIT_BURST_DURATION *
-          (AUDIT_RATE_LIMIT_DEFAULT + AUDIT_RATE_LIMIT_MAX)) +
-         1) /
-        2;
-    if (bucket.size() >= upperThreshold) {
-        // Hit peak, slow down source
-        if (!tooFast) {
-            tooFast = true;
-            audit_rate_limit(mSock, AUDIT_RATE_LIMIT_MAX);
-        }
-
-        // We do not need to hold on to the full set of timing data history,
-        // let's ensure it does not grow without bounds.  This also ensures
-        // that std::dequeue underneath behaves almost like a ring buffer.
-        do {
-            bucket.pop();
-        } while (bucket.size() >= upperThreshold);
-        return;
-    }
-
-    if (!tooFast) return;
-
-    static const size_t lowerThreshold =
-        AUDIT_RATE_LIMIT_BURST_DURATION * AUDIT_RATE_LIMIT_MAX;
-
-    if (bucket.size() >= lowerThreshold) return;
-
-    tooFast = false;
-    // Went below max sustained rate, allow source to speed up
-    audit_rate_limit(mSock, AUDIT_RATE_LIMIT_DEFAULT);
-}
-
 bool LogAudit::onDataAvailable(SocketClient* cli) {
     if (!initialized) {
         prctl(PR_SET_NAME, "logd.auditd");
         initialized = true;
     }
 
-    checkRateLimit();
-
     struct audit_message rep;
 
     rep.nlh.nlmsg_type = 0;
@@ -486,6 +443,5 @@
         audit_close(fd);
         fd = -1;
     }
-    (void)audit_rate_limit(fd, AUDIT_RATE_LIMIT_DEFAULT);
     return fd;
 }
diff --git a/logd/LogAudit.h b/logd/LogAudit.h
index 2bd02d4..5904966 100644
--- a/logd/LogAudit.h
+++ b/logd/LogAudit.h
@@ -18,7 +18,6 @@
 #define _LOGD_LOG_AUDIT_H__
 
 #include <map>
-#include <queue>
 
 #include <sysutils/SocketListener.h>
 
@@ -34,11 +33,6 @@
     bool events;
     bool initialized;
 
-    bool tooFast;
-    int mSock;
-    std::queue<log_time> bucket;
-    void checkRateLimit();
-
    public:
     LogAudit(LogBuffer* buf, LogReader* reader, int fdDmesg);
     int log(char* buf, size_t len);
diff --git a/logd/libaudit.c b/logd/libaudit.c
index dfd56f2..9d9a857 100644
--- a/logd/libaudit.c
+++ b/logd/libaudit.c
@@ -160,7 +160,8 @@
      * and the the mask set to AUDIT_STATUS_PID
      */
     status.pid = pid;
-    status.mask = AUDIT_STATUS_PID;
+    status.mask = AUDIT_STATUS_PID | AUDIT_STATUS_RATE_LIMIT;
+    status.rate_limit = AUDIT_RATE_LIMIT; /* audit entries per second */
 
     /* Let the kernel know this pid will be registering for audit events */
     rc = audit_send(fd, AUDIT_SET, &status, sizeof(status));
@@ -183,26 +184,6 @@
     return 0;
 }
 
-int audit_rate_limit(int fd, unsigned rate_limit) {
-    int rc;
-    struct audit_message rep;
-    struct audit_status status;
-
-    memset(&status, 0, sizeof(status));
-
-    status.mask = AUDIT_STATUS_RATE_LIMIT;
-    status.rate_limit = rate_limit; /* audit entries per second */
-
-    rc = audit_send(fd, AUDIT_SET, &status, sizeof(status));
-    if (rc < 0) {
-        return rc;
-    }
-
-    audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0);
-
-    return 0;
-}
-
 int audit_open() {
     return socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_AUDIT);
 }
diff --git a/logd/libaudit.h b/logd/libaudit.h
index a2afe47..2a93ea3 100644
--- a/logd/libaudit.h
+++ b/logd/libaudit.h
@@ -89,22 +89,8 @@
  */
 extern int audit_setup(int fd, pid_t pid);
 
-/**
- * Sets the rate limit to receive audit netlink events from the kernel
- * @param fd
- *  The fd returned by a call to audit_open()
- * @param max_rate
- *  The cap of the maximum number of audit messages a second
- * @return
- *  This function returns 0 on success, -errno on error.
- */
-
-/* Guidelines to follow for dynamic rate_limit */
-#define AUDIT_RATE_LIMIT_DEFAULT 20        /* acceptable burst rate      */
-#define AUDIT_RATE_LIMIT_BURST_DURATION 10 /* number of seconds of burst */
-#define AUDIT_RATE_LIMIT_MAX 5             /* acceptable sustained rate  */
-
-extern int audit_rate_limit(int fd, unsigned rate_limit);
+/* Max audit messages per second  */
+#define AUDIT_RATE_LIMIT 5
 
 __END_DECLS
 
diff --git a/logd/tests/logd_test.cpp b/logd/tests/logd_test.cpp
index 9e1541b..7d7a22f 100644
--- a/logd/tests/logd_test.cpp
+++ b/logd/tests/logd_test.cpp
@@ -1195,51 +1195,14 @@
                           << "fail as this device is in a bad state, "
                           << "but is not strictly a unit test failure.";
     }
-    // sepolicy_rate_limiter_maximum
-    {  // maximum precharch test block.
-        static constexpr int rate = AUDIT_RATE_LIMIT_MAX;
-        static constexpr int duration = 2;
-        // Two seconds of a liveable sustained rate
-        EXPECT_EQ(rate * duration,
-                  count_avc(sepolicy_rate(rate, rate * duration)));
-    }
-    // sepolicy_rate_limiter_sub_burst
-    {  // maximum period below half way between sustainable and burst rate
-        static constexpr int threshold =
-            ((AUDIT_RATE_LIMIT_BURST_DURATION *
-              (AUDIT_RATE_LIMIT_DEFAULT + AUDIT_RATE_LIMIT_MAX)) +
-             1) /
-            2;
-        static constexpr int rate =
-            (threshold / AUDIT_RATE_LIMIT_BURST_DURATION) - 1;
-        static constexpr int duration = AUDIT_RATE_LIMIT_BURST_DURATION;
-        EXPECT_EQ(rate * duration,
-                  count_avc(sepolicy_rate(rate, rate * duration)));
-    }
-    // sepolicy_rate_limiter_spam
-    {  // hit avc: hard beyond reason block.
-        // maximum period of double the maximum burst rate
-        static constexpr int threshold =
-            ((AUDIT_RATE_LIMIT_BURST_DURATION *
-              (AUDIT_RATE_LIMIT_DEFAULT + AUDIT_RATE_LIMIT_MAX)) +
-             1) /
-            2;
-        static constexpr int rate = AUDIT_RATE_LIMIT_DEFAULT * 2;
-        static constexpr int duration = threshold / AUDIT_RATE_LIMIT_DEFAULT;
-        EXPECT_GE(
-            ((AUDIT_RATE_LIMIT_DEFAULT * duration) * 115) / 100,  // +15% margin
-            count_avc(sepolicy_rate(rate, rate * duration)));
-        // give logd another 3 seconds to react to the burst before checking
-        sepolicy_rate(rate, rate * 3);
-        // maximum period at double maximum burst rate (spam filter kicked in)
-        EXPECT_GE(threshold * 2,
-                  count_avc(sepolicy_rate(
-                      rate, rate * AUDIT_RATE_LIMIT_BURST_DURATION)));
-        // cool down, and check unspammy rate still works
-        sleep(2);
-        EXPECT_LE(AUDIT_RATE_LIMIT_BURST_DURATION - 1,  // allow _one_ lost
-                  count_avc(sepolicy_rate(1, AUDIT_RATE_LIMIT_BURST_DURATION)));
-    }
+
+    static const int rate = AUDIT_RATE_LIMIT;
+    static const int duration = 2;
+    // Two seconds of sustained denials. Depending on the overlap in the time
+    // window that the kernel is considering vs what this test is considering,
+    // allow some additional denials to prevent a flaky test.
+    EXPECT_LE(count_avc(sepolicy_rate(rate, rate * duration)),
+              rate * duration + rate);
 #else
     GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/property_service/OWNERS b/property_service/OWNERS
new file mode 100644
index 0000000..babbe4d
--- /dev/null
+++ b/property_service/OWNERS
@@ -0,0 +1 @@
+tomcherry@google.com
diff --git a/property_service/libpropertyinfoparser/Android.bp b/property_service/libpropertyinfoparser/Android.bp
index 3e732b5..ea9b968 100644
--- a/property_service/libpropertyinfoparser/Android.bp
+++ b/property_service/libpropertyinfoparser/Android.bp
@@ -1,11 +1,10 @@
 cc_library_static {
     name: "libpropertyinfoparser",
+    host_supported: true,
+    vendor_available: true,
     srcs: ["property_info_parser.cpp"],
 
     cpp_std: "experimental",
-    sanitize: {
-        misc_undefined: ["signed-integer-overflow"],
-    },
     cppflags: [
         "-Wall",
         "-Wextra",
diff --git a/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h b/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
index 8c3507e..0548021 100644
--- a/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
+++ b/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
@@ -18,6 +18,7 @@
 #define PROPERTY_INFO_PARSER_H
 
 #include <stdint.h>
+#include <stdlib.h>
 
 namespace android {
 namespace properties {
@@ -31,8 +32,8 @@
 
   // This is the context match for this node_; ~0u if it doesn't correspond to any.
   uint32_t context_index;
-  // This is the schema for this node_; ~0u if it doesn't correspond to any.
-  uint32_t schema_index;
+  // This is the type for this node_; ~0u if it doesn't correspond to any.
+  uint32_t type_index;
 };
 
 struct TrieNodeInternal {
@@ -60,7 +61,7 @@
   uint32_t minimum_supported_version;
   uint32_t size;
   uint32_t contexts_offset;
-  uint32_t schemas_offset;
+  uint32_t types_offset;
   uint32_t root_offset;
 };
 
@@ -102,7 +103,7 @@
   }
 
   uint32_t context_index() const { return node_property_entry()->context_index; }
-  uint32_t schema_index() const { return node_property_entry()->schema_index; }
+  uint32_t type_index() const { return node_property_entry()->type_index; }
 
   uint32_t num_child_nodes() const { return trie_node_base_->num_child_nodes; }
   TrieNode child_node(int n) const {
@@ -142,12 +143,11 @@
 
 class PropertyInfoArea : private SerializedData {
  public:
-  void GetPropertyInfoIndexes(const char* name, uint32_t* context_index,
-                              uint32_t* schema_index) const;
-  void GetPropertyInfo(const char* property, const char** context, const char** schema) const;
+  void GetPropertyInfoIndexes(const char* name, uint32_t* context_index, uint32_t* type_index) const;
+  void GetPropertyInfo(const char* property, const char** context, const char** type) const;
 
   int FindContextIndex(const char* context) const;
-  int FindSchemaIndex(const char* schema) const;
+  int FindTypeIndex(const char* type) const;
 
   const char* context(uint32_t index) const {
     uint32_t context_array_size_offset = contexts_offset();
@@ -155,10 +155,10 @@
     return data_base() + context_array[index];
   }
 
-  const char* schema(uint32_t index) const {
-    uint32_t schema_array_size_offset = schemas_offset();
-    const uint32_t* schema_array = uint32_array(schema_array_size_offset + sizeof(uint32_t));
-    return data_base() + schema_array[index];
+  const char* type(uint32_t index) const {
+    uint32_t type_array_size_offset = types_offset();
+    const uint32_t* type_array = uint32_array(type_array_size_offset + sizeof(uint32_t));
+    return data_base() + type_array[index];
   }
 
   uint32_t current_version() const { return header()->current_version; }
@@ -167,21 +167,21 @@
   uint32_t size() const { return SerializedData::size(); }
 
   uint32_t num_contexts() const { return uint32_array(contexts_offset())[0]; }
-  uint32_t num_schemas() const { return uint32_array(schemas_offset())[0]; }
+  uint32_t num_types() const { return uint32_array(types_offset())[0]; }
 
   TrieNode root_node() const { return trie(header()->root_offset); }
 
  private:
   void CheckPrefixMatch(const char* remaining_name, const TrieNode& trie_node,
-                        uint32_t* context_index, uint32_t* schema_index) const;
+                        uint32_t* context_index, uint32_t* type_index) const;
 
   const PropertyInfoAreaHeader* header() const {
     return reinterpret_cast<const PropertyInfoAreaHeader*>(data_base());
   }
   uint32_t contexts_offset() const { return header()->contexts_offset; }
   uint32_t contexts_array_offset() const { return contexts_offset() + sizeof(uint32_t); }
-  uint32_t schemas_offset() const { return header()->schemas_offset; }
-  uint32_t schemas_array_offset() const { return schemas_offset() + sizeof(uint32_t); }
+  uint32_t types_offset() const { return header()->types_offset; }
+  uint32_t types_array_offset() const { return types_offset() + sizeof(uint32_t); }
 
   TrieNode trie(uint32_t offset) const {
     if (offset != 0 && offset > size()) return TrieNode();
diff --git a/property_service/libpropertyinfoparser/property_info_parser.cpp b/property_service/libpropertyinfoparser/property_info_parser.cpp
index a8f6636..489d81a 100644
--- a/property_service/libpropertyinfoparser/property_info_parser.cpp
+++ b/property_service/libpropertyinfoparser/property_info_parser.cpp
@@ -56,12 +56,12 @@
   });
 }
 
-// Binary search the list of schemas to find the index of a given schema string.
+// Binary search the list of types to find the index of a given type string.
 // Only should be used for TrieSerializer to construct the Trie.
-int PropertyInfoArea::FindSchemaIndex(const char* schema) const {
-  return Find(num_schemas(), [this, schema](auto array_offset) {
-    auto string_offset = uint32_array(schemas_array_offset())[array_offset];
-    return strcmp(c_string(string_offset), schema);
+int PropertyInfoArea::FindTypeIndex(const char* type) const {
+  return Find(num_types(), [this, type](auto array_offset) {
+    auto string_offset = uint32_array(types_array_offset())[array_offset];
+    return strcmp(c_string(string_offset), type);
   });
 }
 
@@ -89,7 +89,7 @@
 }
 
 void PropertyInfoArea::CheckPrefixMatch(const char* remaining_name, const TrieNode& trie_node,
-                                        uint32_t* context_index, uint32_t* schema_index) const {
+                                        uint32_t* context_index, uint32_t* type_index) const {
   const uint32_t remaining_name_size = strlen(remaining_name);
   for (uint32_t i = 0; i < trie_node.num_prefixes(); ++i) {
     auto prefix_len = trie_node.prefix(i)->namelen;
@@ -99,8 +99,8 @@
       if (trie_node.prefix(i)->context_index != ~0u) {
         *context_index = trie_node.prefix(i)->context_index;
       }
-      if (trie_node.prefix(i)->schema_index != ~0u) {
-        *schema_index = trie_node.prefix(i)->schema_index;
+      if (trie_node.prefix(i)->type_index != ~0u) {
+        *type_index = trie_node.prefix(i)->type_index;
       }
       return;
     }
@@ -108,9 +108,9 @@
 }
 
 void PropertyInfoArea::GetPropertyInfoIndexes(const char* name, uint32_t* context_index,
-                                              uint32_t* schema_index) const {
+                                              uint32_t* type_index) const {
   uint32_t return_context_index = ~0u;
-  uint32_t return_schema_index = ~0u;
+  uint32_t return_type_index = ~0u;
   const char* remaining_name = name;
   auto trie_node = root_node();
   while (true) {
@@ -120,13 +120,13 @@
     if (trie_node.context_index() != ~0u) {
       return_context_index = trie_node.context_index();
     }
-    if (trie_node.schema_index() != ~0u) {
-      return_schema_index = trie_node.schema_index();
+    if (trie_node.type_index() != ~0u) {
+      return_type_index = trie_node.type_index();
     }
 
     // Check prefixes at this node.  This comes after the node check since these prefixes are by
     // definition longer than the node itself.
-    CheckPrefixMatch(remaining_name, trie_node, &return_context_index, &return_schema_index);
+    CheckPrefixMatch(remaining_name, trie_node, &return_context_index, &return_type_index);
 
     if (sep == nullptr) {
       break;
@@ -153,29 +153,29 @@
           *context_index = return_context_index;
         }
       }
-      if (schema_index != nullptr) {
-        if (trie_node.exact_match(i)->schema_index != ~0u) {
-          *schema_index = trie_node.exact_match(i)->schema_index;
+      if (type_index != nullptr) {
+        if (trie_node.exact_match(i)->type_index != ~0u) {
+          *type_index = trie_node.exact_match(i)->type_index;
         } else {
-          *schema_index = return_schema_index;
+          *type_index = return_type_index;
         }
       }
       return;
     }
   }
   // Check prefix matches for prefixes not deliminated with '.'
-  CheckPrefixMatch(remaining_name, trie_node, &return_context_index, &return_schema_index);
+  CheckPrefixMatch(remaining_name, trie_node, &return_context_index, &return_type_index);
   // Return previously found prefix match.
   if (context_index != nullptr) *context_index = return_context_index;
-  if (schema_index != nullptr) *schema_index = return_schema_index;
+  if (type_index != nullptr) *type_index = return_type_index;
   return;
 }
 
 void PropertyInfoArea::GetPropertyInfo(const char* property, const char** context,
-                                       const char** schema) const {
+                                       const char** type) const {
   uint32_t context_index;
-  uint32_t schema_index;
-  GetPropertyInfoIndexes(property, &context_index, &schema_index);
+  uint32_t type_index;
+  GetPropertyInfoIndexes(property, &context_index, &type_index);
   if (context != nullptr) {
     if (context_index == ~0u) {
       *context = nullptr;
@@ -183,11 +183,11 @@
       *context = this->context(context_index);
     }
   }
-  if (schema != nullptr) {
-    if (schema_index == ~0u) {
-      *schema = nullptr;
+  if (type != nullptr) {
+    if (type_index == ~0u) {
+      *type = nullptr;
     } else {
-      *schema = this->schema(schema_index);
+      *type = this->type(type_index);
     }
   }
 }
diff --git a/property_service/libpropertyinfoserializer/Android.bp b/property_service/libpropertyinfoserializer/Android.bp
index 20e5e13..72ae19a 100644
--- a/property_service/libpropertyinfoserializer/Android.bp
+++ b/property_service/libpropertyinfoserializer/Android.bp
@@ -1,9 +1,8 @@
 cc_defaults {
     name: "propertyinfoserializer_defaults",
+    host_supported: true,
+    vendor_available: true,
     cpp_std: "experimental",
-    sanitize: {
-        misc_undefined: ["signed-integer-overflow"],
-    },
     cppflags: [
         "-Wall",
         "-Wextra",
@@ -19,6 +18,7 @@
     name: "libpropertyinfoserializer",
     defaults: ["propertyinfoserializer_defaults"],
     srcs: [
+        "property_info_file.cpp",
         "property_info_serializer.cpp",
         "trie_builder.cpp",
         "trie_serializer.cpp",
diff --git a/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h b/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h
index f7e708e..439813d 100644
--- a/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h
+++ b/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h
@@ -26,21 +26,25 @@
 struct PropertyInfoEntry {
   PropertyInfoEntry() {}
   template <typename T, typename U, typename V>
-  PropertyInfoEntry(T&& name, U&& context, V&& schema, bool exact_match)
+  PropertyInfoEntry(T&& name, U&& context, V&& type, bool exact_match)
       : name(std::forward<T>(name)),
         context(std::forward<U>(context)),
-        schema(std::forward<V>(schema)),
+        type(std::forward<V>(type)),
         exact_match(exact_match) {}
   std::string name;
   std::string context;
-  std::string schema;
+  std::string type;
   bool exact_match;
 };
 
 bool BuildTrie(const std::vector<PropertyInfoEntry>& property_info,
-               const std::string& default_context, const std::string& default_schema,
+               const std::string& default_context, const std::string& default_type,
                std::string* serialized_trie, std::string* error);
 
+void ParsePropertyInfoFile(const std::string& file_contents,
+                           std::vector<PropertyInfoEntry>* property_infos,
+                           std::vector<std::string>* errors);
+
 }  // namespace properties
 }  // namespace android
 
diff --git a/property_service/libpropertyinfoserializer/property_info_file.cpp b/property_service/libpropertyinfoserializer/property_info_file.cpp
new file mode 100644
index 0000000..bf96d88
--- /dev/null
+++ b/property_service/libpropertyinfoserializer/property_info_file.cpp
@@ -0,0 +1,62 @@
+#include <property_info_serializer/property_info_serializer.h>
+
+#include <android-base/strings.h>
+
+#include "space_tokenizer.h"
+
+using android::base::Split;
+using android::base::StartsWith;
+using android::base::Trim;
+
+namespace android {
+namespace properties {
+
+bool ParsePropertyInfoLine(const std::string& line, PropertyInfoEntry* out, std::string* error) {
+  auto tokenizer = SpaceTokenizer(line);
+
+  auto property = tokenizer.GetNext();
+  if (property.empty()) {
+    *error = "Did not find a property entry in '" + line + "'";
+    return false;
+  }
+
+  auto context = tokenizer.GetNext();
+  if (context.empty()) {
+    *error = "Did not find a context entry in '" + line + "'";
+    return false;
+  }
+
+  // It is not an error to not find these, as older files will not contain them.
+  auto exact_match = tokenizer.GetNext();
+  auto type = tokenizer.GetRemaining();
+
+  *out = {property, context, type, exact_match == "exact"};
+  return true;
+}
+
+void ParsePropertyInfoFile(const std::string& file_contents,
+                           std::vector<PropertyInfoEntry>* property_infos,
+                           std::vector<std::string>* errors) {
+  // Do not clear property_infos to allow this function to be called on multiple files, with
+  // their results concatenated.
+  errors->clear();
+
+  for (const auto& line : Split(file_contents, "\n")) {
+    auto trimmed_line = Trim(line);
+    if (trimmed_line.empty() || StartsWith(trimmed_line, "#")) {
+      continue;
+    }
+
+    auto property_info_entry = PropertyInfoEntry{};
+    auto parse_error = std::string{};
+    if (!ParsePropertyInfoLine(trimmed_line, &property_info_entry, &parse_error)) {
+      errors->emplace_back(parse_error);
+      continue;
+    }
+
+    property_infos->emplace_back(property_info_entry);
+  }
+}
+
+}  // namespace properties
+}  // namespace android
diff --git a/property_service/libpropertyinfoserializer/property_info_serializer.cpp b/property_service/libpropertyinfoserializer/property_info_serializer.cpp
index 656c96e..803657a 100644
--- a/property_service/libpropertyinfoserializer/property_info_serializer.cpp
+++ b/property_service/libpropertyinfoserializer/property_info_serializer.cpp
@@ -27,13 +27,13 @@
 namespace properties {
 
 bool BuildTrie(const std::vector<PropertyInfoEntry>& property_info,
-               const std::string& default_context, const std::string& default_schema,
+               const std::string& default_context, const std::string& default_type,
                std::string* serialized_trie, std::string* error) {
   // Check that names are legal first
-  auto trie_builder = TrieBuilder(default_context, default_schema);
+  auto trie_builder = TrieBuilder(default_context, default_type);
 
-  for (const auto& [name, context, schema, is_exact] : property_info) {
-    if (!trie_builder.AddToTrie(name, context, schema, is_exact, error)) {
+  for (const auto& [name, context, type, is_exact] : property_info) {
+    if (!trie_builder.AddToTrie(name, context, type, is_exact, error)) {
       return false;
     }
   }
diff --git a/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp b/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
index 46c2d06..f484550 100644
--- a/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
+++ b/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
@@ -46,7 +46,7 @@
   auto root_node = property_info_area->root_node();
   EXPECT_STREQ("root", root_node.name());
   EXPECT_STREQ("default", property_info_area->context(root_node.context_index()));
-  EXPECT_STREQ("default", property_info_area->schema(root_node.schema_index()));
+  EXPECT_STREQ("default", property_info_area->type(root_node.type_index()));
 
   EXPECT_EQ(0U, root_node.num_prefixes());
   EXPECT_EQ(0U, root_node.num_exact_matches());
@@ -59,7 +59,7 @@
 
   EXPECT_STREQ("test", test_node.name());
   EXPECT_STREQ("1st", property_info_area->context(test_node.context_index()));
-  EXPECT_STREQ("1st", property_info_area->schema(test_node.schema_index()));
+  EXPECT_STREQ("1st", property_info_area->type(test_node.type_index()));
 
   EXPECT_EQ(0U, test_node.num_child_nodes());
 
@@ -69,7 +69,7 @@
     EXPECT_STREQ("test", serialized_trie.data() + prefix->name_offset);
     EXPECT_EQ(4U, prefix->namelen);
     EXPECT_STREQ("2nd", property_info_area->context(prefix->context_index));
-    EXPECT_STREQ("2nd", property_info_area->schema(prefix->schema_index));
+    EXPECT_STREQ("2nd", property_info_area->type(prefix->type_index));
   }
 
   EXPECT_EQ(3U, test_node.num_exact_matches());
@@ -85,9 +85,9 @@
     EXPECT_STREQ("3rd", property_info_area->context(match2->context_index));
     EXPECT_STREQ("3rd", property_info_area->context(match3->context_index));
 
-    EXPECT_STREQ("3rd", property_info_area->schema(match1->schema_index));
-    EXPECT_STREQ("3rd", property_info_area->schema(match2->schema_index));
-    EXPECT_STREQ("3rd", property_info_area->schema(match3->schema_index));
+    EXPECT_STREQ("3rd", property_info_area->type(match1->type_index));
+    EXPECT_STREQ("3rd", property_info_area->type(match2->type_index));
+    EXPECT_STREQ("3rd", property_info_area->type(match3->type_index));
   }
 
   // Check the long string node
@@ -120,7 +120,7 @@
   auto final_match = long_string_node.exact_match(0);
   EXPECT_STREQ("string", serialized_trie.data() + final_match->name_offset);
   EXPECT_STREQ("4th", property_info_area->context(final_match->context_index));
-  EXPECT_STREQ("4th", property_info_area->schema(final_match->schema_index));
+  EXPECT_STREQ("4th", property_info_area->type(final_match->type_index));
 }
 
 TEST(propertyinfoserializer, GetPropertyInfo) {
@@ -143,109 +143,109 @@
   auto root_node = property_info_area->root_node();
   EXPECT_STREQ("root", root_node.name());
   EXPECT_STREQ("default", property_info_area->context(root_node.context_index()));
-  EXPECT_STREQ("default", property_info_area->schema(root_node.schema_index()));
+  EXPECT_STREQ("default", property_info_area->type(root_node.type_index()));
 
   const char* context;
-  const char* schema;
-  property_info_area->GetPropertyInfo("abc", &context, &schema);
+  const char* type;
+  property_info_area->GetPropertyInfo("abc", &context, &type);
   EXPECT_STREQ("default", context);
-  EXPECT_STREQ("default", schema);
-  property_info_area->GetPropertyInfo("abc.abc", &context, &schema);
+  EXPECT_STREQ("default", type);
+  property_info_area->GetPropertyInfo("abc.abc", &context, &type);
   EXPECT_STREQ("default", context);
-  EXPECT_STREQ("default", schema);
-  property_info_area->GetPropertyInfo("123.abc", &context, &schema);
+  EXPECT_STREQ("default", type);
+  property_info_area->GetPropertyInfo("123.abc", &context, &type);
   EXPECT_STREQ("default", context);
-  EXPECT_STREQ("default", schema);
+  EXPECT_STREQ("default", type);
 
-  property_info_area->GetPropertyInfo("test.a", &context, &schema);
+  property_info_area->GetPropertyInfo("test.a", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("1st", schema);
-  property_info_area->GetPropertyInfo("test.b", &context, &schema);
+  EXPECT_STREQ("1st", type);
+  property_info_area->GetPropertyInfo("test.b", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("1st", schema);
-  property_info_area->GetPropertyInfo("test.c", &context, &schema);
+  EXPECT_STREQ("1st", type);
+  property_info_area->GetPropertyInfo("test.c", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("1st", schema);
+  EXPECT_STREQ("1st", type);
 
-  property_info_area->GetPropertyInfo("test.test", &context, &schema);
+  property_info_area->GetPropertyInfo("test.test", &context, &type);
   EXPECT_STREQ("5th", context);
-  EXPECT_STREQ("5th", schema);
-  property_info_area->GetPropertyInfo("test.testa", &context, &schema);
+  EXPECT_STREQ("5th", type);
+  property_info_area->GetPropertyInfo("test.testa", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("test.testb", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("test.testb", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("test.testc", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("test.testc", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
+  EXPECT_STREQ("2nd", type);
 
-  property_info_area->GetPropertyInfo("test.test.a", &context, &schema);
+  property_info_area->GetPropertyInfo("test.test.a", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("test.test.b", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("test.test.b", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("test.test.c", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("test.test.c", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
+  EXPECT_STREQ("2nd", type);
 
-  property_info_area->GetPropertyInfo("test.test1", &context, &schema);
+  property_info_area->GetPropertyInfo("test.test1", &context, &type);
   EXPECT_STREQ("3rd", context);
-  EXPECT_STREQ("3rd", schema);
-  property_info_area->GetPropertyInfo("test.test2", &context, &schema);
+  EXPECT_STREQ("3rd", type);
+  property_info_area->GetPropertyInfo("test.test2", &context, &type);
   EXPECT_STREQ("7th", context);
-  EXPECT_STREQ("7th", schema);
-  property_info_area->GetPropertyInfo("test.test3", &context, &schema);
+  EXPECT_STREQ("7th", type);
+  property_info_area->GetPropertyInfo("test.test3", &context, &type);
   EXPECT_STREQ("3rd", context);
-  EXPECT_STREQ("3rd", schema);
+  EXPECT_STREQ("3rd", type);
 
-  property_info_area->GetPropertyInfo("test.test11", &context, &schema);
+  property_info_area->GetPropertyInfo("test.test11", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("test.test22", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("test.test22", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("test.test33", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("test.test33", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
+  EXPECT_STREQ("2nd", type);
 
-  property_info_area->GetPropertyInfo("this.is.a.long.string", &context, &schema);
+  property_info_area->GetPropertyInfo("this.is.a.long.string", &context, &type);
   EXPECT_STREQ("4th", context);
-  EXPECT_STREQ("4th", schema);
+  EXPECT_STREQ("4th", type);
 
-  property_info_area->GetPropertyInfo("this.is.a.long", &context, &schema);
+  property_info_area->GetPropertyInfo("this.is.a.long", &context, &type);
   EXPECT_STREQ("default", context);
-  EXPECT_STREQ("default", schema);
-  property_info_area->GetPropertyInfo("this.is.a", &context, &schema);
+  EXPECT_STREQ("default", type);
+  property_info_area->GetPropertyInfo("this.is.a", &context, &type);
   EXPECT_STREQ("default", context);
-  EXPECT_STREQ("default", schema);
-  property_info_area->GetPropertyInfo("this.is", &context, &schema);
+  EXPECT_STREQ("default", type);
+  property_info_area->GetPropertyInfo("this.is", &context, &type);
   EXPECT_STREQ("default", context);
-  EXPECT_STREQ("default", schema);
-  property_info_area->GetPropertyInfo("this", &context, &schema);
+  EXPECT_STREQ("default", type);
+  property_info_area->GetPropertyInfo("this", &context, &type);
   EXPECT_STREQ("default", context);
-  EXPECT_STREQ("default", schema);
+  EXPECT_STREQ("default", type);
 
-  property_info_area->GetPropertyInfo("test.test2.a", &context, &schema);
+  property_info_area->GetPropertyInfo("test.test2.a", &context, &type);
   EXPECT_STREQ("6th", context);
-  EXPECT_STREQ("6th", schema);
+  EXPECT_STREQ("6th", type);
 
-  property_info_area->GetPropertyInfo("testoneword", &context, &schema);
+  property_info_area->GetPropertyInfo("testoneword", &context, &type);
   EXPECT_STREQ("8th", context);
-  EXPECT_STREQ("8th", schema);
+  EXPECT_STREQ("8th", type);
 
-  property_info_area->GetPropertyInfo("testwordprefix", &context, &schema);
+  property_info_area->GetPropertyInfo("testwordprefix", &context, &type);
   EXPECT_STREQ("9th", context);
-  EXPECT_STREQ("9th", schema);
+  EXPECT_STREQ("9th", type);
 
-  property_info_area->GetPropertyInfo("testwordprefixblah", &context, &schema);
+  property_info_area->GetPropertyInfo("testwordprefixblah", &context, &type);
   EXPECT_STREQ("9th", context);
-  EXPECT_STREQ("9th", schema);
+  EXPECT_STREQ("9th", type);
 
-  property_info_area->GetPropertyInfo("testwordprefix.blah", &context, &schema);
+  property_info_area->GetPropertyInfo("testwordprefix.blah", &context, &type);
   EXPECT_STREQ("9th", context);
-  EXPECT_STREQ("9th", schema);
+  EXPECT_STREQ("9th", type);
 }
 
 TEST(propertyinfoserializer, RealProperties) {
@@ -777,35 +777,34 @@
   auto property_info_area = reinterpret_cast<const PropertyInfoArea*>(serialized_trie.data());
 
   const char* context;
-  const char* schema;
-  property_info_area->GetPropertyInfo("persist.radio", &context, &schema);
+  const char* type;
+  property_info_area->GetPropertyInfo("persist.radio", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("1st", schema);
-  property_info_area->GetPropertyInfo("persist.radio.subproperty", &context, &schema);
+  EXPECT_STREQ("1st", type);
+  property_info_area->GetPropertyInfo("persist.radio.subproperty", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("1st", schema);
-  property_info_area->GetPropertyInfo("persist.radiowords", &context, &schema);
+  EXPECT_STREQ("1st", type);
+  property_info_area->GetPropertyInfo("persist.radiowords", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("1st", schema);
-  property_info_area->GetPropertyInfo("persist.radio.long.long.long.sub.property", &context,
-                                      &schema);
+  EXPECT_STREQ("1st", type);
+  property_info_area->GetPropertyInfo("persist.radio.long.long.long.sub.property", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("1st", schema);
-  property_info_area->GetPropertyInfo("persist.radio.something.else.here", &context, &schema);
+  EXPECT_STREQ("1st", type);
+  property_info_area->GetPropertyInfo("persist.radio.something.else.here", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("persist.radio.something.else.here2", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("persist.radio.something.else.here2", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("persist.radio.something.else.here.after", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("persist.radio.something.else.here.after", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("persist.radio.something.else.nothere", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("persist.radio.something.else.nothere", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("1st", schema);
-  property_info_area->GetPropertyInfo("persist.radio.something.else", &context, &schema);
+  EXPECT_STREQ("1st", type);
+  property_info_area->GetPropertyInfo("persist.radio.something.else", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("1st", schema);
+  EXPECT_STREQ("1st", type);
 }
 
 TEST(propertyinfoserializer, GetPropertyInfo_prefix_with_dot_vs_without) {
@@ -823,28 +822,28 @@
   auto property_info_area = reinterpret_cast<const PropertyInfoArea*>(serialized_trie.data());
 
   const char* context;
-  const char* schema;
-  property_info_area->GetPropertyInfo("persist.notradio", &context, &schema);
+  const char* type;
+  property_info_area->GetPropertyInfo("persist.notradio", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("1st", schema);
-  property_info_area->GetPropertyInfo("persist.radio", &context, &schema);
+  EXPECT_STREQ("1st", type);
+  property_info_area->GetPropertyInfo("persist.radio", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("persist.radio.subproperty", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("persist.radio.subproperty", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("persist.radiowords", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("persist.radiowords", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("persist.radio.long.property.prefix.match", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("persist.radio.long.property.prefix.match", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("2nd", schema);
-  property_info_area->GetPropertyInfo("persist.radio.long.property.exact.match", &context, &schema);
+  EXPECT_STREQ("2nd", type);
+  property_info_area->GetPropertyInfo("persist.radio.long.property.exact.match", &context, &type);
   EXPECT_STREQ("3rd", context);
-  EXPECT_STREQ("3rd", schema);
+  EXPECT_STREQ("3rd", type);
 }
 
-TEST(propertyinfoserializer, GetPropertyInfo_empty_context_and_schema) {
+TEST(propertyinfoserializer, GetPropertyInfo_empty_context_and_type) {
   auto property_info = std::vector<PropertyInfoEntry>{
       {"persist.", "1st", "", false},
       {"persist.dot_prefix.", "2nd", "", false},
@@ -862,28 +861,28 @@
   auto property_info_area = reinterpret_cast<const PropertyInfoArea*>(serialized_trie.data());
 
   const char* context;
-  const char* schema;
-  property_info_area->GetPropertyInfo("notpersist.radio.something", &context, &schema);
+  const char* type;
+  property_info_area->GetPropertyInfo("notpersist.radio.something", &context, &type);
   EXPECT_STREQ("default", context);
-  EXPECT_STREQ("default", schema);
-  property_info_area->GetPropertyInfo("persist.nomatch", &context, &schema);
+  EXPECT_STREQ("default", type);
+  property_info_area->GetPropertyInfo("persist.nomatch", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("default", schema);
-  property_info_area->GetPropertyInfo("persist.dot_prefix.something", &context, &schema);
+  EXPECT_STREQ("default", type);
+  property_info_area->GetPropertyInfo("persist.dot_prefix.something", &context, &type);
   EXPECT_STREQ("2nd", context);
-  EXPECT_STREQ("default", schema);
-  property_info_area->GetPropertyInfo("persist.non_dot_prefix.something", &context, &schema);
+  EXPECT_STREQ("default", type);
+  property_info_area->GetPropertyInfo("persist.non_dot_prefix.something", &context, &type);
   EXPECT_STREQ("3rd", context);
-  EXPECT_STREQ("default", schema);
-  property_info_area->GetPropertyInfo("persist.exact_match", &context, &schema);
+  EXPECT_STREQ("default", type);
+  property_info_area->GetPropertyInfo("persist.exact_match", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("default", schema);
-  property_info_area->GetPropertyInfo("persist.dot_prefix2.something", &context, &schema);
+  EXPECT_STREQ("default", type);
+  property_info_area->GetPropertyInfo("persist.dot_prefix2.something", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("4th", schema);
-  property_info_area->GetPropertyInfo("persist.non_dot_prefix2.something", &context, &schema);
+  EXPECT_STREQ("4th", type);
+  property_info_area->GetPropertyInfo("persist.non_dot_prefix2.something", &context, &type);
   EXPECT_STREQ("1st", context);
-  EXPECT_STREQ("5th", schema);
+  EXPECT_STREQ("5th", type);
 }
 
 }  // namespace properties
diff --git a/property_service/libpropertyinfoserializer/space_tokenizer.h b/property_service/libpropertyinfoserializer/space_tokenizer.h
new file mode 100644
index 0000000..fba0c58
--- /dev/null
+++ b/property_service/libpropertyinfoserializer/space_tokenizer.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PROPERTY_INFO_SERIALIZER_SPACE_TOKENIZER_H
+#define PROPERTY_INFO_SERIALIZER_SPACE_TOKENIZER_H
+
+namespace android {
+namespace properties {
+
+class SpaceTokenizer {
+ public:
+  SpaceTokenizer(const std::string& string)
+      : string_(string), it_(string_.begin()), end_(string_.end()) {}
+
+  std::string GetNext() {
+    auto next = std::string();
+    while (it_ != end_ && !isspace(*it_)) {
+      next.push_back(*it_++);
+    }
+    while (it_ != end_ && isspace(*it_)) {
+      it_++;
+    }
+    return next;
+  }
+
+  std::string GetRemaining() { return std::string(it_, end_); }
+
+ private:
+  std::string string_;
+  std::string::const_iterator it_;
+  std::string::const_iterator end_;
+};
+
+}  // namespace properties
+}  // namespace android
+
+#endif
diff --git a/property_service/libpropertyinfoserializer/trie_builder.cpp b/property_service/libpropertyinfoserializer/trie_builder.cpp
index feb753b..8c5ce84 100644
--- a/property_service/libpropertyinfoserializer/trie_builder.cpp
+++ b/property_service/libpropertyinfoserializer/trie_builder.cpp
@@ -23,23 +23,23 @@
 namespace android {
 namespace properties {
 
-TrieBuilder::TrieBuilder(const std::string& default_context, const std::string& default_schema)
+TrieBuilder::TrieBuilder(const std::string& default_context, const std::string& default_type)
     : builder_root_("root") {
   auto* context_pointer = StringPointerFromContainer(default_context, &contexts_);
   builder_root_.set_context(context_pointer);
-  auto* schema_pointer = StringPointerFromContainer(default_schema, &schemas_);
-  builder_root_.set_schema(schema_pointer);
+  auto* type_pointer = StringPointerFromContainer(default_type, &types_);
+  builder_root_.set_type(type_pointer);
 }
 
 bool TrieBuilder::AddToTrie(const std::string& name, const std::string& context,
-                            const std::string& schema, bool exact, std::string* error) {
+                            const std::string& type, bool exact, std::string* error) {
   auto* context_pointer = StringPointerFromContainer(context, &contexts_);
-  auto* schema_pointer = StringPointerFromContainer(schema, &schemas_);
-  return AddToTrie(name, context_pointer, schema_pointer, exact, error);
+  auto* type_pointer = StringPointerFromContainer(type, &types_);
+  return AddToTrie(name, context_pointer, type_pointer, exact, error);
 }
 
 bool TrieBuilder::AddToTrie(const std::string& name, const std::string* context,
-                            const std::string* schema, bool exact, std::string* error) {
+                            const std::string* type, bool exact, std::string* error) {
   TrieBuilderNode* current_node = &builder_root_;
 
   auto name_pieces = Split(name, ".");
@@ -66,12 +66,12 @@
 
   // Store our context based on what type of match it is.
   if (exact) {
-    if (!current_node->AddExactMatchContext(name_pieces.front(), context, schema)) {
+    if (!current_node->AddExactMatchContext(name_pieces.front(), context, type)) {
       *error = "Duplicate exact match detected for '" + name + "'";
       return false;
     }
   } else if (!ends_with_dot) {
-    if (!current_node->AddPrefixContext(name_pieces.front(), context, schema)) {
+    if (!current_node->AddPrefixContext(name_pieces.front(), context, type)) {
       *error = "Duplicate prefix match detected for '" + name + "'";
       return false;
     }
@@ -84,12 +84,12 @@
       *error = "Unable to allocate Trie node";
       return false;
     }
-    if (child->context() != nullptr || child->schema() != nullptr) {
+    if (child->context() != nullptr || child->type() != nullptr) {
       *error = "Duplicate prefix match detected for '" + name + "'";
       return false;
     }
     child->set_context(context);
-    child->set_schema(schema);
+    child->set_type(type);
   }
   return true;
 }
diff --git a/property_service/libpropertyinfoserializer/trie_builder.h b/property_service/libpropertyinfoserializer/trie_builder.h
index f928e76..b971589 100644
--- a/property_service/libpropertyinfoserializer/trie_builder.h
+++ b/property_service/libpropertyinfoserializer/trie_builder.h
@@ -26,13 +26,12 @@
 namespace properties {
 
 struct PropertyEntryBuilder {
-  PropertyEntryBuilder() : context(nullptr), schema(nullptr) {}
-  PropertyEntryBuilder(const std::string& name, const std::string* context,
-                       const std::string* schema)
-      : name(name), context(context), schema(schema) {}
+  PropertyEntryBuilder() : context(nullptr), type(nullptr) {}
+  PropertyEntryBuilder(const std::string& name, const std::string* context, const std::string* type)
+      : name(name), context(context), type(type) {}
   std::string name;
   const std::string* context;
-  const std::string* schema;
+  const std::string* type;
 };
 
 class TrieBuilderNode {
@@ -56,33 +55,33 @@
   TrieBuilderNode* AddChild(const std::string& name) { return &children_.emplace_back(name); }
 
   bool AddPrefixContext(const std::string& prefix, const std::string* context,
-                        const std::string* schema) {
+                        const std::string* type) {
     if (std::find_if(prefixes_.begin(), prefixes_.end(),
                      [&prefix](const auto& t) { return t.name == prefix; }) != prefixes_.end()) {
       return false;
     }
 
-    prefixes_.emplace_back(prefix, context, schema);
+    prefixes_.emplace_back(prefix, context, type);
     return true;
   }
 
   bool AddExactMatchContext(const std::string& exact_match, const std::string* context,
-                            const std::string* schema) {
+                            const std::string* type) {
     if (std::find_if(exact_matches_.begin(), exact_matches_.end(), [&exact_match](const auto& t) {
           return t.name == exact_match;
         }) != exact_matches_.end()) {
       return false;
     }
 
-    exact_matches_.emplace_back(exact_match, context, schema);
+    exact_matches_.emplace_back(exact_match, context, type);
     return true;
   }
 
   const std::string& name() const { return property_entry_.name; }
   const std::string* context() const { return property_entry_.context; }
   void set_context(const std::string* context) { property_entry_.context = context; }
-  const std::string* schema() const { return property_entry_.schema; }
-  void set_schema(const std::string* schema) { property_entry_.schema = schema; }
+  const std::string* type() const { return property_entry_.type; }
+  void set_type(const std::string* type) { property_entry_.type = type; }
 
   const PropertyEntryBuilder property_entry() const { return property_entry_; }
 
@@ -99,23 +98,23 @@
 
 class TrieBuilder {
  public:
-  TrieBuilder(const std::string& default_context, const std::string& default_schema);
-  bool AddToTrie(const std::string& name, const std::string& context, const std::string& schema,
+  TrieBuilder(const std::string& default_context, const std::string& default_type);
+  bool AddToTrie(const std::string& name, const std::string& context, const std::string& type,
                  bool exact, std::string* error);
 
   const TrieBuilderNode builder_root() const { return builder_root_; }
   const std::set<std::string>& contexts() const { return contexts_; }
-  const std::set<std::string>& schemas() const { return schemas_; }
+  const std::set<std::string>& types() const { return types_; }
 
  private:
-  bool AddToTrie(const std::string& name, const std::string* context, const std::string* schema,
+  bool AddToTrie(const std::string& name, const std::string* context, const std::string* type,
                  bool exact, std::string* error);
   const std::string* StringPointerFromContainer(const std::string& string,
                                                 std::set<std::string>* container);
 
   TrieBuilderNode builder_root_;
   std::set<std::string> contexts_;
-  std::set<std::string> schemas_;
+  std::set<std::string> types_;
 };
 
 }  // namespace properties
diff --git a/property_service/libpropertyinfoserializer/trie_builder_test.cpp b/property_service/libpropertyinfoserializer/trie_builder_test.cpp
index 2b948f3..5078810 100644
--- a/property_service/libpropertyinfoserializer/trie_builder_test.cpp
+++ b/property_service/libpropertyinfoserializer/trie_builder_test.cpp
@@ -22,19 +22,19 @@
 namespace properties {
 
 TEST(propertyinfoserializer, BuildTrie_Simple) {
-  auto trie_builder = TrieBuilder("default", "default_schema");
+  auto trie_builder = TrieBuilder("default", "default_type");
 
   // Add test data to tree
   auto error = std::string();
-  EXPECT_TRUE(trie_builder.AddToTrie("test.", "1st", "1st_schema", false, &error));
-  EXPECT_TRUE(trie_builder.AddToTrie("test.test", "2nd", "2nd_schema", false, &error));
-  EXPECT_TRUE(trie_builder.AddToTrie("test.test1", "3rd", "3rd_schema", true, &error));
-  EXPECT_TRUE(trie_builder.AddToTrie("test.test2", "3rd", "3rd_schema", true, &error));
-  EXPECT_TRUE(trie_builder.AddToTrie("test.test3", "3rd", "3rd_schema", true, &error));
-  EXPECT_TRUE(trie_builder.AddToTrie("this.is.a.long.string", "4th", "4th_schema", true, &error));
+  EXPECT_TRUE(trie_builder.AddToTrie("test.", "1st", "1st_type", false, &error));
+  EXPECT_TRUE(trie_builder.AddToTrie("test.test", "2nd", "2nd_type", false, &error));
+  EXPECT_TRUE(trie_builder.AddToTrie("test.test1", "3rd", "3rd_type", true, &error));
+  EXPECT_TRUE(trie_builder.AddToTrie("test.test2", "3rd", "3rd_type", true, &error));
+  EXPECT_TRUE(trie_builder.AddToTrie("test.test3", "3rd", "3rd_type", true, &error));
+  EXPECT_TRUE(trie_builder.AddToTrie("this.is.a.long.string", "4th", "4th_type", true, &error));
 
   ASSERT_EQ(5U, trie_builder.contexts().size());
-  ASSERT_EQ(5U, trie_builder.schemas().size());
+  ASSERT_EQ(5U, trie_builder.types().size());
 
   auto& builder_root = trie_builder.builder_root();
 
@@ -42,8 +42,8 @@
   EXPECT_EQ("root", builder_root.name());
   ASSERT_NE(nullptr, builder_root.context());
   EXPECT_EQ("default", *builder_root.context());
-  ASSERT_NE(nullptr, builder_root.schema());
-  EXPECT_EQ("default_schema", *builder_root.schema());
+  ASSERT_NE(nullptr, builder_root.type());
+  EXPECT_EQ("default_type", *builder_root.type());
 
   EXPECT_EQ(0U, builder_root.prefixes().size());
   EXPECT_EQ(0U, builder_root.exact_matches().size());
@@ -55,8 +55,8 @@
   EXPECT_EQ("test", test_node->name());
   ASSERT_NE(nullptr, test_node->context());
   EXPECT_EQ("1st", *test_node->context());
-  ASSERT_NE(nullptr, test_node->schema());
-  EXPECT_EQ("1st_schema", *test_node->schema());
+  ASSERT_NE(nullptr, test_node->type());
+  EXPECT_EQ("1st_type", *test_node->type());
 
   EXPECT_EQ(0U, test_node->children().size());
   EXPECT_EQ(1U, test_node->prefixes().size());
@@ -65,8 +65,8 @@
     EXPECT_EQ("test", property_entry.name);
     ASSERT_NE(nullptr, property_entry.context);
     EXPECT_EQ("2nd", *property_entry.context);
-    ASSERT_NE(nullptr, property_entry.schema);
-    EXPECT_EQ("2nd_schema", *property_entry.schema);
+    ASSERT_NE(nullptr, property_entry.type);
+    EXPECT_EQ("2nd_type", *property_entry.type);
   }
   EXPECT_EQ(3U, test_node->exact_matches().size());
   EXPECT_EQ("test1", test_node->exact_matches()[0].name);
@@ -80,18 +80,18 @@
   EXPECT_EQ("3rd", *test_node->exact_matches()[1].context);
   EXPECT_EQ("3rd", *test_node->exact_matches()[2].context);
 
-  ASSERT_NE(nullptr, test_node->exact_matches()[0].schema);
-  ASSERT_NE(nullptr, test_node->exact_matches()[1].schema);
-  ASSERT_NE(nullptr, test_node->exact_matches()[2].schema);
-  EXPECT_EQ("3rd_schema", *test_node->exact_matches()[0].schema);
-  EXPECT_EQ("3rd_schema", *test_node->exact_matches()[1].schema);
-  EXPECT_EQ("3rd_schema", *test_node->exact_matches()[2].schema);
+  ASSERT_NE(nullptr, test_node->exact_matches()[0].type);
+  ASSERT_NE(nullptr, test_node->exact_matches()[1].type);
+  ASSERT_NE(nullptr, test_node->exact_matches()[2].type);
+  EXPECT_EQ("3rd_type", *test_node->exact_matches()[0].type);
+  EXPECT_EQ("3rd_type", *test_node->exact_matches()[1].type);
+  EXPECT_EQ("3rd_type", *test_node->exact_matches()[2].type);
 
   // Check the long string node
   auto expect_empty_one_child = [](auto* node) {
     ASSERT_NE(nullptr, node);
     EXPECT_EQ(nullptr, node->context());
-    EXPECT_EQ(nullptr, node->schema());
+    EXPECT_EQ(nullptr, node->type());
     EXPECT_EQ(0U, node->prefixes().size());
     EXPECT_EQ(0U, node->exact_matches().size());
     EXPECT_EQ(1U, node->children().size());
@@ -120,8 +120,8 @@
     EXPECT_EQ("string", property_entry.name);
     ASSERT_NE(nullptr, property_entry.context);
     EXPECT_EQ("4th", *property_entry.context);
-    ASSERT_NE(nullptr, property_entry.schema);
-    EXPECT_EQ("4th_schema", *property_entry.schema);
+    ASSERT_NE(nullptr, property_entry.type);
+    EXPECT_EQ("4th_type", *property_entry.type);
   }
 }
 
diff --git a/property_service/libpropertyinfoserializer/trie_serializer.cpp b/property_service/libpropertyinfoserializer/trie_serializer.cpp
index 5326537..adeed1b 100644
--- a/property_service/libpropertyinfoserializer/trie_serializer.cpp
+++ b/property_service/libpropertyinfoserializer/trie_serializer.cpp
@@ -43,15 +43,15 @@
   uint32_t context_index = property_entry.context != nullptr && !property_entry.context->empty()
                                ? serialized_info()->FindContextIndex(property_entry.context->c_str())
                                : ~0u;
-  uint32_t schema_index = property_entry.schema != nullptr && !property_entry.schema->empty()
-                              ? serialized_info()->FindSchemaIndex(property_entry.schema->c_str())
-                              : ~0u;
+  uint32_t type_index = property_entry.type != nullptr && !property_entry.type->empty()
+                            ? serialized_info()->FindTypeIndex(property_entry.type->c_str())
+                            : ~0u;
   uint32_t offset;
   auto serialized_property_entry = arena_->AllocateObject<PropertyEntry>(&offset);
   serialized_property_entry->name_offset = arena_->AllocateAndWriteString(property_entry.name);
   serialized_property_entry->namelen = property_entry.name.size();
   serialized_property_entry->context_index = context_index;
-  serialized_property_entry->schema_index = schema_index;
+  serialized_property_entry->type_index = type_index;
   return offset;
 }
 
@@ -122,9 +122,9 @@
   header->contexts_offset = arena_->size();
   SerializeStrings(trie_builder.contexts());
 
-  // Store where we're about to write the schemas.
-  header->schemas_offset = arena_->size();
-  SerializeStrings(trie_builder.schemas());
+  // Store where we're about to write the types.
+  header->types_offset = arena_->size();
+  SerializeStrings(trie_builder.types());
 
   // We need to store size() up to this point now for Find*Offset() to work.
   header->size = arena_->size();
diff --git a/property_service/property_info_checker/Android.bp b/property_service/property_info_checker/Android.bp
new file mode 100644
index 0000000..6ee649a
--- /dev/null
+++ b/property_service/property_info_checker/Android.bp
@@ -0,0 +1,12 @@
+cc_binary {
+    name: "property_info_checker",
+    host_supported: true,
+    static_executable: true,
+    cpp_std: "experimental",
+    static_libs: [
+        "libpropertyinfoserializer",
+        "libpropertyinfoparser",
+        "libbase",
+    ],
+    srcs: ["property_info_checker.cpp"],
+}
diff --git a/property_service/property_info_checker/property_info_checker.cpp b/property_service/property_info_checker/property_info_checker.cpp
new file mode 100644
index 0000000..e4f8264
--- /dev/null
+++ b/property_service/property_info_checker/property_info_checker.cpp
@@ -0,0 +1,51 @@
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+
+#include <property_info_serializer/property_info_serializer.h>
+
+using android::base::ReadFileToString;
+using android::properties::BuildTrie;
+using android::properties::ParsePropertyInfoFile;
+using android::properties::PropertyInfoEntry;
+
+int main(int argc, char** argv) {
+  if (argc < 2) {
+    std::cerr << "A list of property info files to be checked is expected on the command line"
+              << std::endl;
+    return -1;
+  }
+
+  auto property_info_entries = std::vector<PropertyInfoEntry>{};
+
+  for (int i = 1; i < argc; ++i) {
+    auto filename = argv[i];
+    auto file_contents = std::string{};
+    if (!ReadFileToString(filename, &file_contents)) {
+      std::cerr << "Could not read properties from '" << filename << "'" << std::endl;
+      return -1;
+    }
+
+    auto errors = std::vector<std::string>{};
+    ParsePropertyInfoFile(file_contents, &property_info_entries, &errors);
+    if (!errors.empty()) {
+      for (const auto& error : errors) {
+        std::cerr << "Could not read line from '" << filename << "': " << error << std::endl;
+      }
+      return -1;
+    }
+  }
+
+  auto serialized_contexts = std::string{};
+  auto build_trie_error = std::string{};
+
+  if (!BuildTrie(property_info_entries, "u:object_r:default_prop:s0", "\\s*", &serialized_contexts,
+                 &build_trie_error)) {
+    std::cerr << "Unable to serialize property contexts: " << build_trie_error << std::endl;
+    return -1;
+  }
+
+  return 0;
+}
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 492d63a..7804f6d 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -77,7 +77,7 @@
 #
 # create some directories (some are mount points) and symlinks
 LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
-    sbin dev proc sys system data oem acct config storage mnt $(BOARD_ROOT_EXTRA_FOLDERS)); \
+    sbin dev proc sys system data odm oem acct config storage mnt $(BOARD_ROOT_EXTRA_FOLDERS)); \
     ln -sf /system/bin $(TARGET_ROOT_OUT)/bin; \
     ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \
     ln -sf /data/user_de/0/com.android.shell/files/bugreports $(TARGET_ROOT_OUT)/bugreports; \
@@ -125,14 +125,12 @@
 bcp_md5 :=
 bcp_dep :=
 
-# If PLATFORM_VNDK_VERSION is defined and not "current", generate versioned
-# module names for ld.config.txt, llndk.libraries.txt and vndksp.libraries.txt
-# files.
-define versioned_module_name
+# If BOARD_VNDK_VERSION is defined, append PLATFORM_VNDK_VERSION to base name.
+define append_vndk_version
 $(strip \
-  $(if $(filter-out current,$(PLATFORM_VNDK_VERSION)), \
-    $(basename $(LOCAL_MODULE)).$(PLATFORM_VNDK_VERSION)$(suffix $(LOCAL_MODULE)), \
-    $(LOCAL_MODULE) \
+  $(if $(BOARD_VNDK_VERSION), \
+    $(basename $(1)).$(PLATFORM_VNDK_VERSION)$(suffix $(1)), \
+    $(1) \
   ) \
 )
 endef
@@ -153,7 +151,7 @@
 LOCAL_MODULE := ld.config.txt
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := $(call versioned_module_name)
+LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
 include $(BUILD_SYSTEM)/base_rules.mk
 
 llndk_libraries := $(call normalize-path-list,$(addsuffix .so,\
@@ -200,7 +198,7 @@
 LOCAL_MODULE := ld.config.txt
 ifeq ($(PRODUCT_TREBLE_LINKER_NAMESPACES)|$(SANITIZE_TARGET),true|)
   LOCAL_SRC_FILES := etc/ld.config.txt
-  LOCAL_MODULE_STEM := $(call versioned_module_name)
+  LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
 else
   LOCAL_SRC_FILES := etc/ld.config.legacy.txt
   LOCAL_MODULE_STEM := $(LOCAL_MODULE)
@@ -216,7 +214,7 @@
 LOCAL_MODULE := llndk.libraries.txt
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := $(call versioned_module_name)
+LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
 include $(BUILD_SYSTEM)/base_rules.mk
 $(LOCAL_BUILT_MODULE): PRIVATE_LLNDK_LIBRARIES := $(LLNDK_LIBRARIES)
 $(LOCAL_BUILT_MODULE):
@@ -232,7 +230,7 @@
 LOCAL_MODULE := vndksp.libraries.txt
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := $(call versioned_module_name)
+LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
 include $(BUILD_SYSTEM)/base_rules.mk
 $(LOCAL_BUILT_MODULE): PRIVATE_VNDK_SAMEPROCESS_LIBRARIES := $(VNDK_SAMEPROCESS_LIBRARIES)
 $(LOCAL_BUILT_MODULE):
diff --git a/rootdir/etc/ld.config.txt.in b/rootdir/etc/ld.config.txt.in
index 70363569..ffc4359 100644
--- a/rootdir/etc/ld.config.txt.in
+++ b/rootdir/etc/ld.config.txt.in
@@ -277,12 +277,6 @@
 namespace.vndk.search.paths  = /system/${LIB}/vndk-sp${VNDK_VER}
 namespace.vndk.search.paths += /system/${LIB}/vndk${VNDK_VER}
 
-# This is exceptionally required since android.hidl.memory@1.0-impl.so is here
-namespace.vndk.permitted.paths = /system/${LIB}/vndk-sp${VNDK_VER}/hw
-
-namespace.vndk.asan.permitted.paths += /data/asan/system/${LIB}/vndk-sp${VNDK_VER}/hw
-namespace.vndk.asan.permitted.paths +=           /system/${LIB}/vndk-sp${VNDK_VER}/hw
-
 namespace.vndk.asan.search.paths  = /data/asan/system/${LIB}/vndk-sp${VNDK_VER}
 namespace.vndk.asan.search.paths +=           /system/${LIB}/vndk-sp${VNDK_VER}
 namespace.vndk.asan.search.paths += /data/asan/system/${LIB}/vndk${VNDK_VER}
diff --git a/rootdir/init.zygote32.rc b/rootdir/init.zygote32.rc
index d836c4e..ac87979 100644
--- a/rootdir/init.zygote32.rc
+++ b/rootdir/init.zygote32.rc
@@ -2,7 +2,7 @@
     class main
     priority -20
     user root
-    group root readproc
+    group root readproc reserved_disk
     socket zygote stream 660 root system
     onrestart write /sys/android_power/request_state wake
     onrestart write /sys/power/state on
diff --git a/rootdir/init.zygote32_64.rc b/rootdir/init.zygote32_64.rc
index 80bb673..a535846 100644
--- a/rootdir/init.zygote32_64.rc
+++ b/rootdir/init.zygote32_64.rc
@@ -2,7 +2,7 @@
     class main
     priority -20
     user root
-    group root readproc
+    group root readproc reserved_disk
     socket zygote stream 660 root system
     onrestart write /sys/android_power/request_state wake
     onrestart write /sys/power/state on
@@ -17,7 +17,7 @@
     class main
     priority -20
     user root
-    group root readproc
+    group root readproc reserved_disk
     socket zygote_secondary stream 660 root system
     onrestart restart zygote
     writepid /dev/cpuset/foreground/tasks
diff --git a/rootdir/init.zygote64.rc b/rootdir/init.zygote64.rc
index 05ec16f..6fc810b 100644
--- a/rootdir/init.zygote64.rc
+++ b/rootdir/init.zygote64.rc
@@ -2,7 +2,7 @@
     class main
     priority -20
     user root
-    group root readproc
+    group root readproc reserved_disk
     socket zygote stream 660 root system
     onrestart write /sys/android_power/request_state wake
     onrestart write /sys/power/state on
diff --git a/rootdir/init.zygote64_32.rc b/rootdir/init.zygote64_32.rc
index 09db7b0..7ddd52e 100644
--- a/rootdir/init.zygote64_32.rc
+++ b/rootdir/init.zygote64_32.rc
@@ -2,7 +2,7 @@
     class main
     priority -20
     user root
-    group root readproc
+    group root readproc reserved_disk
     socket zygote stream 660 root system
     onrestart write /sys/android_power/request_state wake
     onrestart write /sys/power/state on
@@ -17,7 +17,7 @@
     class main
     priority -20
     user root
-    group root readproc
+    group root readproc reserved_disk
     socket zygote_secondary stream 660 root system
     onrestart restart zygote
     writepid /dev/cpuset/foreground/tasks
diff --git a/rootdir/ueventd.rc b/rootdir/ueventd.rc
index eadf219..b03d83b 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -77,6 +77,7 @@
 /dev/graphics/*           0660   root       graphics
 /dev/msm_hw3dm            0660   system     graphics
 /dev/input/*              0660   root       input
+/dev/v4l-touch*           0660   root       input
 /dev/eac                  0660   root       audio
 /dev/cam                  0660   root       camera
 /dev/pmem                 0660   system     graphics
diff --git a/sdcard/Android.bp b/sdcard/Android.bp
new file mode 100644
index 0000000..c096587
--- /dev/null
+++ b/sdcard/Android.bp
@@ -0,0 +1,17 @@
+cc_binary {
+    srcs: ["sdcard.cpp"],
+    name: "sdcard",
+    cflags: [
+        "-Wall",
+        "-Wno-unused-parameter",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "libminijail",
+    ],
+    sanitize: {
+        misc_undefined: ["integer"],
+    },
+}
diff --git a/sdcard/Android.mk b/sdcard/Android.mk
deleted file mode 100644
index 5b4dc58..0000000
--- a/sdcard/Android.mk
+++ /dev/null
@@ -1,12 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := sdcard.cpp fuse.cpp
-LOCAL_MODULE := sdcard
-LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror
-LOCAL_SHARED_LIBRARIES := libbase libcutils libminijail libpackagelistparser
-
-LOCAL_SANITIZE := integer
-
-include $(BUILD_EXECUTABLE)
diff --git a/sdcard/fuse.cpp b/sdcard/fuse.cpp
deleted file mode 100644
index 95559d7..0000000
--- a/sdcard/fuse.cpp
+++ /dev/null
@@ -1,1476 +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 <errno.h>
-#include <string.h>
-#include <unistd.h>
-
-#define LOG_TAG "sdcard"
-
-#include "fuse.h"
-
-#include <android-base/logging.h>
-
-/* FUSE_CANONICAL_PATH is not currently upstreamed */
-#define FUSE_CANONICAL_PATH 2016
-
-#define FUSE_UNKNOWN_INO 0xffffffff
-
-/* Pseudo-error constant used to indicate that no fuse status is needed
- * or that a reply has already been written. */
-#define NO_STATUS 1
-
-static inline void *id_to_ptr(__u64 nid)
-{
-    return (void *) (uintptr_t) nid;
-}
-
-static inline __u64 ptr_to_id(void *ptr)
-{
-    return (__u64) (uintptr_t) ptr;
-}
-
-static void acquire_node_locked(struct node* node)
-{
-    node->refcount++;
-    DLOG(INFO) << "ACQUIRE " << std::hex << node << std::dec
-               << " (" << node->name << ") rc=" << node->refcount;
-}
-
-static void remove_node_from_parent_locked(struct node* node);
-
-static void release_node_locked(struct node* node)
-{
-    DLOG(INFO) << "RELEASE " << std::hex << node << std::dec
-               << " (" << node->name << ") rc=" << node->refcount;
-    if (node->refcount > 0) {
-        node->refcount--;
-        if (!node->refcount) {
-            DLOG(INFO) << "DESTROY " << std::hex << node << std::dec << " (" << node->name << ")";
-            remove_node_from_parent_locked(node);
-
-            /* TODO: remove debugging - poison memory */
-            memset(node->name, 0xef, node->namelen);
-            free(node->name);
-            free(node->actual_name);
-            memset(node, 0xfc, sizeof(*node));
-            free(node);
-        }
-    } else {
-        LOG(ERROR) << std::hex << node << std::dec << " refcount=0";
-    }
-}
-
-static void add_node_to_parent_locked(struct node *node, struct node *parent) {
-    node->parent = parent;
-    node->next = parent->child;
-    parent->child = node;
-    acquire_node_locked(parent);
-}
-
-static void remove_node_from_parent_locked(struct node* node)
-{
-    if (node->parent) {
-        if (node->parent->child == node) {
-            node->parent->child = node->parent->child->next;
-        } else {
-            struct node *node2;
-            node2 = node->parent->child;
-            while (node2->next != node)
-                node2 = node2->next;
-            node2->next = node->next;
-        }
-        release_node_locked(node->parent);
-        node->parent = NULL;
-        node->next = NULL;
-    }
-}
-
-/* Gets the absolute path to a node into the provided buffer.
- *
- * Populates 'buf' with the path and returns the length of the path on success,
- * or returns -1 if the path is too long for the provided buffer.
- */
-static ssize_t get_node_path_locked(struct node* node, char* buf, size_t bufsize) {
-    const char* name;
-    size_t namelen;
-    if (node->graft_path) {
-        name = node->graft_path;
-        namelen = node->graft_pathlen;
-    } else if (node->actual_name) {
-        name = node->actual_name;
-        namelen = node->namelen;
-    } else {
-        name = node->name;
-        namelen = node->namelen;
-    }
-
-    if (bufsize < namelen + 1) {
-        return -1;
-    }
-
-    ssize_t pathlen = 0;
-    if (node->parent && node->graft_path == NULL) {
-        pathlen = get_node_path_locked(node->parent, buf, bufsize - namelen - 1);
-        if (pathlen < 0) {
-            return -1;
-        }
-        buf[pathlen++] = '/';
-    }
-
-    memcpy(buf + pathlen, name, namelen + 1); /* include trailing \0 */
-    return pathlen + namelen;
-}
-
-/* Finds the absolute path of a file within a given directory.
- * Performs a case-insensitive search for the file and sets the buffer to the path
- * of the first matching file.  If 'search' is zero or if no match is found, sets
- * the buffer to the path that the file would have, assuming the name were case-sensitive.
- *
- * Populates 'buf' with the path and returns the actual name (within 'buf') on success,
- * or returns NULL if the path is too long for the provided buffer.
- */
-static char* find_file_within(const char* path, const char* name,
-        char* buf, size_t bufsize, int search)
-{
-    size_t pathlen = strlen(path);
-    size_t namelen = strlen(name);
-    size_t childlen = pathlen + namelen + 1;
-    char* actual;
-
-    if (bufsize <= childlen) {
-        return NULL;
-    }
-
-    memcpy(buf, path, pathlen);
-    buf[pathlen] = '/';
-    actual = buf + pathlen + 1;
-    memcpy(actual, name, namelen + 1);
-
-    if (search && access(buf, F_OK)) {
-        struct dirent* entry;
-        DIR* dir = opendir(path);
-        if (!dir) {
-            PLOG(ERROR) << "opendir(" << path << ") failed";
-            return actual;
-        }
-        while ((entry = readdir(dir))) {
-            if (!strcasecmp(entry->d_name, name)) {
-                /* we have a match - replace the name, don't need to copy the null again */
-                memcpy(actual, entry->d_name, namelen);
-                break;
-            }
-        }
-        closedir(dir);
-    }
-    return actual;
-}
-
-static void attr_from_stat(struct fuse* fuse, struct fuse_attr *attr,
-        const struct stat *s, const struct node* node) {
-    attr->ino = node->ino;
-    attr->size = s->st_size;
-    attr->blocks = s->st_blocks;
-    attr->atime = s->st_atim.tv_sec;
-    attr->mtime = s->st_mtim.tv_sec;
-    attr->ctime = s->st_ctim.tv_sec;
-    attr->atimensec = s->st_atim.tv_nsec;
-    attr->mtimensec = s->st_mtim.tv_nsec;
-    attr->ctimensec = s->st_ctim.tv_nsec;
-    attr->mode = s->st_mode;
-    attr->nlink = s->st_nlink;
-
-    attr->uid = node->uid;
-
-    if (fuse->gid == AID_SDCARD_RW) {
-        /* As an optimization, certain trusted system components only run
-         * as owner but operate across all users. Since we're now handing
-         * out the sdcard_rw GID only to trusted apps, we're okay relaxing
-         * the user boundary enforcement for the default view. The UIDs
-         * assigned to app directories are still multiuser aware. */
-        attr->gid = AID_SDCARD_RW;
-    } else {
-        attr->gid = multiuser_get_uid(node->userid, fuse->gid);
-    }
-
-    int visible_mode = 0775 & ~fuse->mask;
-    if (node->perm == PERM_PRE_ROOT) {
-        /* Top of multi-user view should always be visible to ensure
-         * secondary users can traverse inside. */
-        visible_mode = 0711;
-    } else if (node->under_android) {
-        /* Block "other" access to Android directories, since only apps
-         * belonging to a specific user should be in there; we still
-         * leave +x open for the default view. */
-        if (fuse->gid == AID_SDCARD_RW) {
-            visible_mode = visible_mode & ~0006;
-        } else {
-            visible_mode = visible_mode & ~0007;
-        }
-    }
-    int owner_mode = s->st_mode & 0700;
-    int filtered_mode = visible_mode & (owner_mode | (owner_mode >> 3) | (owner_mode >> 6));
-    attr->mode = (attr->mode & S_IFMT) | filtered_mode;
-}
-
-static int touch(char* path, mode_t mode) {
-    int fd = TEMP_FAILURE_RETRY(open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC,
-                                     mode));
-    if (fd == -1) {
-        if (errno == EEXIST) {
-            return 0;
-        } else {
-            PLOG(ERROR) << "open(" << path << ") failed";
-            return -1;
-        }
-    }
-    close(fd);
-    return 0;
-}
-
-static void derive_permissions_locked(struct fuse* fuse, struct node *parent,
-        struct node *node) {
-    appid_t appid;
-
-    /* By default, each node inherits from its parent */
-    node->perm = PERM_INHERIT;
-    node->userid = parent->userid;
-    node->uid = parent->uid;
-    node->under_android = parent->under_android;
-
-    /* Derive custom permissions based on parent and current node */
-    switch (parent->perm) {
-    case PERM_INHERIT:
-        /* Already inherited above */
-        break;
-    case PERM_PRE_ROOT:
-        /* Legacy internal layout places users at top level */
-        node->perm = PERM_ROOT;
-        node->userid = strtoul(node->name, NULL, 10);
-        break;
-    case PERM_ROOT:
-        /* Assume masked off by default. */
-        if (!strcasecmp(node->name, "Android")) {
-            /* App-specific directories inside; let anyone traverse */
-            node->perm = PERM_ANDROID;
-            node->under_android = true;
-        }
-        break;
-    case PERM_ANDROID:
-        if (!strcasecmp(node->name, "data")) {
-            /* App-specific directories inside; let anyone traverse */
-            node->perm = PERM_ANDROID_DATA;
-        } else if (!strcasecmp(node->name, "obb")) {
-            /* App-specific directories inside; let anyone traverse */
-            node->perm = PERM_ANDROID_OBB;
-            /* Single OBB directory is always shared */
-            node->graft_path = fuse->global->obb_path;
-            node->graft_pathlen = strlen(fuse->global->obb_path);
-        } else if (!strcasecmp(node->name, "media")) {
-            /* App-specific directories inside; let anyone traverse */
-            node->perm = PERM_ANDROID_MEDIA;
-        }
-        break;
-    case PERM_ANDROID_DATA:
-    case PERM_ANDROID_OBB:
-    case PERM_ANDROID_MEDIA:
-        const auto& iter = fuse->global->package_to_appid->find(node->name);
-        if (iter != fuse->global->package_to_appid->end()) {
-            appid = iter->second;
-            node->uid = multiuser_get_uid(parent->userid, appid);
-        }
-        break;
-    }
-}
-
-void derive_permissions_recursive_locked(struct fuse* fuse, struct node *parent) {
-    struct node *node;
-    for (node = parent->child; node; node = node->next) {
-        derive_permissions_locked(fuse, parent, node);
-        if (node->child) {
-            derive_permissions_recursive_locked(fuse, node);
-        }
-    }
-}
-
-/* Kernel has already enforced everything we returned through
- * derive_permissions_locked(), so this is used to lock down access
- * even further, such as enforcing that apps hold sdcard_rw. */
-static bool check_caller_access_to_name(struct fuse* fuse,
-        const struct fuse_in_header *hdr, const struct node* parent_node,
-        const char* name, int mode) {
-    /* Always block security-sensitive files at root */
-    if (parent_node && parent_node->perm == PERM_ROOT) {
-        if (!strcasecmp(name, "autorun.inf")
-                || !strcasecmp(name, ".android_secure")
-                || !strcasecmp(name, "android_secure")) {
-            return false;
-        }
-    }
-
-    /* Root always has access; access for any other UIDs should always
-     * be controlled through packages.list. */
-    if (hdr->uid == 0) {
-        return true;
-    }
-
-    /* No extra permissions to enforce */
-    return true;
-}
-
-static bool check_caller_access_to_node(struct fuse* fuse,
-        const struct fuse_in_header *hdr, const struct node* node, int mode) {
-    return check_caller_access_to_name(fuse, hdr, node->parent, node->name, mode);
-}
-
-struct node *create_node_locked(struct fuse* fuse,
-        struct node *parent, const char *name, const char* actual_name)
-{
-    struct node *node;
-    size_t namelen = strlen(name);
-
-    // Detect overflows in the inode counter. "4 billion nodes should be enough
-    // for everybody".
-    if (fuse->global->inode_ctr == 0) {
-        LOG(ERROR) << "No more inode numbers available";
-        return NULL;
-    }
-
-    node = static_cast<struct node*>(calloc(1, sizeof(struct node)));
-    if (!node) {
-        return NULL;
-    }
-    node->name = static_cast<char*>(malloc(namelen + 1));
-    if (!node->name) {
-        free(node);
-        return NULL;
-    }
-    memcpy(node->name, name, namelen + 1);
-    if (strcmp(name, actual_name)) {
-        node->actual_name = static_cast<char*>(malloc(namelen + 1));
-        if (!node->actual_name) {
-            free(node->name);
-            free(node);
-            return NULL;
-        }
-        memcpy(node->actual_name, actual_name, namelen + 1);
-    }
-    node->namelen = namelen;
-    node->nid = ptr_to_id(node);
-    node->ino = fuse->global->inode_ctr++;
-    node->gen = fuse->global->next_generation++;
-
-    node->deleted = false;
-
-    derive_permissions_locked(fuse, parent, node);
-    acquire_node_locked(node);
-    add_node_to_parent_locked(node, parent);
-    return node;
-}
-
-static int rename_node_locked(struct node *node, const char *name,
-        const char* actual_name)
-{
-    size_t namelen = strlen(name);
-    int need_actual_name = strcmp(name, actual_name);
-
-    /* make the storage bigger without actually changing the name
-     * in case an error occurs part way */
-    if (namelen > node->namelen) {
-        char* new_name = static_cast<char*>(realloc(node->name, namelen + 1));
-        if (!new_name) {
-            return -ENOMEM;
-        }
-        node->name = new_name;
-        if (need_actual_name && node->actual_name) {
-            char* new_actual_name = static_cast<char*>(realloc(node->actual_name, namelen + 1));
-            if (!new_actual_name) {
-                return -ENOMEM;
-            }
-            node->actual_name = new_actual_name;
-        }
-    }
-
-    /* update the name, taking care to allocate storage before overwriting the old name */
-    if (need_actual_name) {
-        if (!node->actual_name) {
-            node->actual_name = static_cast<char*>(malloc(namelen + 1));
-            if (!node->actual_name) {
-                return -ENOMEM;
-            }
-        }
-        memcpy(node->actual_name, actual_name, namelen + 1);
-    } else {
-        free(node->actual_name);
-        node->actual_name = NULL;
-    }
-    memcpy(node->name, name, namelen + 1);
-    node->namelen = namelen;
-    return 0;
-}
-
-static struct node *lookup_node_by_id_locked(struct fuse *fuse, __u64 nid)
-{
-    if (nid == FUSE_ROOT_ID) {
-        return &fuse->global->root;
-    } else {
-        return static_cast<struct node*>(id_to_ptr(nid));
-    }
-}
-
-static struct node* lookup_node_and_path_by_id_locked(struct fuse* fuse, __u64 nid,
-        char* buf, size_t bufsize)
-{
-    struct node* node = lookup_node_by_id_locked(fuse, nid);
-    if (node && get_node_path_locked(node, buf, bufsize) < 0) {
-        node = NULL;
-    }
-    return node;
-}
-
-static struct node *lookup_child_by_name_locked(struct node *node, const char *name)
-{
-    for (node = node->child; node; node = node->next) {
-        /* use exact string comparison, nodes that differ by case
-         * must be considered distinct even if they refer to the same
-         * underlying file as otherwise operations such as "mv x x"
-         * will not work because the source and target nodes are the same. */
-        if (!strcmp(name, node->name) && !node->deleted) {
-            return node;
-        }
-    }
-    return 0;
-}
-
-static struct node* acquire_or_create_child_locked(
-        struct fuse* fuse, struct node* parent,
-        const char* name, const char* actual_name)
-{
-    struct node* child = lookup_child_by_name_locked(parent, name);
-    if (child) {
-        acquire_node_locked(child);
-    } else {
-        child = create_node_locked(fuse, parent, name, actual_name);
-    }
-    return child;
-}
-
-static void fuse_status(struct fuse *fuse, __u64 unique, int err)
-{
-    struct fuse_out_header hdr;
-    hdr.len = sizeof(hdr);
-    hdr.error = err;
-    hdr.unique = unique;
-    ssize_t ret = TEMP_FAILURE_RETRY(write(fuse->fd, &hdr, sizeof(hdr)));
-    if (ret == -1) {
-        PLOG(ERROR) << "*** STATUS FAILED ***";
-    } else if (static_cast<size_t>(ret) != sizeof(hdr)) {
-        LOG(ERROR) << "*** STATUS FAILED: written " << ret << " expected "
-                   << sizeof(hdr) << " ***";
-    }
-}
-
-static void fuse_reply(struct fuse *fuse, __u64 unique, void *data, int len)
-{
-    struct fuse_out_header hdr;
-    hdr.len = len + sizeof(hdr);
-    hdr.error = 0;
-    hdr.unique = unique;
-
-    struct iovec vec[2];
-    vec[0].iov_base = &hdr;
-    vec[0].iov_len = sizeof(hdr);
-    vec[1].iov_base = data;
-    vec[1].iov_len = len;
-
-    ssize_t ret = TEMP_FAILURE_RETRY(writev(fuse->fd, vec, 2));
-    if (ret == -1) {
-        PLOG(ERROR) << "*** REPLY FAILED ***";
-    } else if (static_cast<size_t>(ret) != sizeof(hdr) + len) {
-        LOG(ERROR) << "*** REPLY FAILED: written " << ret << " expected "
-                   << sizeof(hdr) + len << " ***";
-    }
-}
-
-static int fuse_reply_entry(struct fuse* fuse, __u64 unique,
-        struct node* parent, const char* name, const char* actual_name,
-        const char* path)
-{
-    struct node* node;
-    struct fuse_entry_out out;
-    struct stat s;
-
-    if (lstat(path, &s) == -1) {
-        return -errno;
-    }
-
-    pthread_mutex_lock(&fuse->global->lock);
-    node = acquire_or_create_child_locked(fuse, parent, name, actual_name);
-    if (!node) {
-        pthread_mutex_unlock(&fuse->global->lock);
-        return -ENOMEM;
-    }
-    memset(&out, 0, sizeof(out));
-    attr_from_stat(fuse, &out.attr, &s, node);
-    out.attr_valid = 10;
-    out.entry_valid = 10;
-    out.nodeid = node->nid;
-    out.generation = node->gen;
-    pthread_mutex_unlock(&fuse->global->lock);
-    fuse_reply(fuse, unique, &out, sizeof(out));
-    return NO_STATUS;
-}
-
-static int fuse_reply_attr(struct fuse* fuse, __u64 unique, const struct node* node,
-        const char* path)
-{
-    struct fuse_attr_out out;
-    struct stat s;
-
-    if (lstat(path, &s) == -1) {
-        return -errno;
-    }
-    memset(&out, 0, sizeof(out));
-    attr_from_stat(fuse, &out.attr, &s, node);
-    out.attr_valid = 10;
-    fuse_reply(fuse, unique, &out, sizeof(out));
-    return NO_STATUS;
-}
-
-static void fuse_notify_delete(struct fuse* fuse, const __u64 parent,
-        const __u64 child, const char* name) {
-    struct fuse_out_header hdr;
-    struct fuse_notify_delete_out data;
-    size_t namelen = strlen(name);
-    hdr.len = sizeof(hdr) + sizeof(data) + namelen + 1;
-    hdr.error = FUSE_NOTIFY_DELETE;
-    hdr.unique = 0;
-
-    data.parent = parent;
-    data.child = child;
-    data.namelen = namelen;
-    data.padding = 0;
-
-    struct iovec vec[3];
-    vec[0].iov_base = &hdr;
-    vec[0].iov_len = sizeof(hdr);
-    vec[1].iov_base = &data;
-    vec[1].iov_len = sizeof(data);
-    vec[2].iov_base = (void*) name;
-    vec[2].iov_len = namelen + 1;
-
-    ssize_t ret = TEMP_FAILURE_RETRY(writev(fuse->fd, vec, 3));
-    /* Ignore ENOENT, since other views may not have seen the entry */
-    if (ret == -1) {
-        if (errno != ENOENT) {
-            PLOG(ERROR) << "*** NOTIFY FAILED ***";
-        }
-    } else if (static_cast<size_t>(ret) != sizeof(hdr) + sizeof(data) + namelen + 1) {
-        LOG(ERROR) << "*** NOTIFY FAILED: written " << ret << " expected "
-                   << sizeof(hdr) + sizeof(data) + namelen + 1 << " ***";
-    }
-}
-
-static int handle_lookup(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header *hdr, const char* name)
-{
-    struct node* parent_node;
-    char parent_path[PATH_MAX];
-    char child_path[PATH_MAX];
-    const char* actual_name;
-
-    pthread_mutex_lock(&fuse->global->lock);
-    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
-            parent_path, sizeof(parent_path));
-    DLOG(INFO) << "[" << handler->token << "] LOOKUP " << name << " @ " << hdr->nodeid
-               << " (" << (parent_node ? parent_node->name : "?") << ")";
-    pthread_mutex_unlock(&fuse->global->lock);
-
-    if (!parent_node || !(actual_name = find_file_within(parent_path, name,
-            child_path, sizeof(child_path), 1))) {
-        return -ENOENT;
-    }
-    if (!check_caller_access_to_name(fuse, hdr, parent_node, name, R_OK)) {
-        return -EACCES;
-    }
-
-    return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path);
-}
-
-static int handle_forget(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header *hdr, const struct fuse_forget_in *req)
-{
-    struct node* node;
-
-    pthread_mutex_lock(&fuse->global->lock);
-    node = lookup_node_by_id_locked(fuse, hdr->nodeid);
-    DLOG(INFO) << "[" << handler->token << "] FORGET #" << req->nlookup
-               << " @ " << std::hex << hdr->nodeid
-               << " (" << (node ? node->name : "?") << ")";
-    if (node) {
-        __u64 n = req->nlookup;
-        while (n) {
-            n--;
-            release_node_locked(node);
-        }
-    }
-    pthread_mutex_unlock(&fuse->global->lock);
-    return NO_STATUS; /* no reply */
-}
-
-static int handle_getattr(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header *hdr, const struct fuse_getattr_in *req)
-{
-    struct node* node;
-    char path[PATH_MAX];
-
-    pthread_mutex_lock(&fuse->global->lock);
-    node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path));
-    DLOG(INFO) << "[" << handler->token << "] GETATTR flags=" << req->getattr_flags
-               << " fh=" << std::hex << req->fh << " @ " << hdr->nodeid << std::dec
-               << " (" << (node ? node->name : "?") << ")";
-    pthread_mutex_unlock(&fuse->global->lock);
-
-    if (!node) {
-        return -ENOENT;
-    }
-    if (!check_caller_access_to_node(fuse, hdr, node, R_OK)) {
-        return -EACCES;
-    }
-
-    return fuse_reply_attr(fuse, hdr->unique, node, path);
-}
-
-static int handle_setattr(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header *hdr, const struct fuse_setattr_in *req)
-{
-    struct node* node;
-    char path[PATH_MAX];
-    struct timespec times[2];
-
-    pthread_mutex_lock(&fuse->global->lock);
-    node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path));
-    DLOG(INFO) << "[" << handler->token << "] SETATTR fh=" << std::hex << req->fh
-               << " valid=" << std::hex << req->valid << " @ " << hdr->nodeid << std::dec
-               << " (" << (node ? node->name : "?") << ")";
-    pthread_mutex_unlock(&fuse->global->lock);
-
-    if (!node) {
-        return -ENOENT;
-    }
-
-    if (!(req->valid & FATTR_FH) &&
-            !check_caller_access_to_node(fuse, hdr, node, W_OK)) {
-        return -EACCES;
-    }
-
-    /* XXX: incomplete implementation on purpose.
-     * chmod/chown should NEVER be implemented.*/
-
-    if ((req->valid & FATTR_SIZE) && TEMP_FAILURE_RETRY(truncate64(path, req->size)) == -1) {
-        return -errno;
-    }
-
-    /* Handle changing atime and mtime.  If FATTR_ATIME_and FATTR_ATIME_NOW
-     * are both set, then set it to the current time.  Else, set it to the
-     * time specified in the request.  Same goes for mtime.  Use utimensat(2)
-     * as it allows ATIME and MTIME to be changed independently, and has
-     * nanosecond resolution which fuse also has.
-     */
-    if (req->valid & (FATTR_ATIME | FATTR_MTIME)) {
-        times[0].tv_nsec = UTIME_OMIT;
-        times[1].tv_nsec = UTIME_OMIT;
-        if (req->valid & FATTR_ATIME) {
-            if (req->valid & FATTR_ATIME_NOW) {
-              times[0].tv_nsec = UTIME_NOW;
-            } else {
-              times[0].tv_sec = req->atime;
-              times[0].tv_nsec = req->atimensec;
-            }
-        }
-        if (req->valid & FATTR_MTIME) {
-            if (req->valid & FATTR_MTIME_NOW) {
-              times[1].tv_nsec = UTIME_NOW;
-            } else {
-              times[1].tv_sec = req->mtime;
-              times[1].tv_nsec = req->mtimensec;
-            }
-        }
-        DLOG(INFO) << "[" << handler->token << "] Calling utimensat on " << path
-                   << " with atime " << times[0].tv_sec << ", mtime=" << times[1].tv_sec;
-        if (utimensat(-1, path, times, 0) < 0) {
-            return -errno;
-        }
-    }
-    return fuse_reply_attr(fuse, hdr->unique, node, path);
-}
-
-static int handle_mknod(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_mknod_in* req, const char* name)
-{
-    struct node* parent_node;
-    char parent_path[PATH_MAX];
-    char child_path[PATH_MAX];
-    const char* actual_name;
-
-    pthread_mutex_lock(&fuse->global->lock);
-    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
-            parent_path, sizeof(parent_path));
-    DLOG(INFO) << "[" << handler->token << "] MKNOD " << name << " 0" << std::oct << req->mode
-               << " @ " << std::hex << hdr->nodeid
-               << " (" << (parent_node ? parent_node->name : "?") << ")";
-    pthread_mutex_unlock(&fuse->global->lock);
-
-    if (!parent_node || !(actual_name = find_file_within(parent_path, name,
-            child_path, sizeof(child_path), 1))) {
-        return -ENOENT;
-    }
-    if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
-        return -EACCES;
-    }
-    __u32 mode = (req->mode & (~0777)) | 0664;
-    if (mknod(child_path, mode, req->rdev) == -1) {
-        return -errno;
-    }
-    return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path);
-}
-
-static int handle_mkdir(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_mkdir_in* req, const char* name)
-{
-    struct node* parent_node;
-    char parent_path[PATH_MAX];
-    char child_path[PATH_MAX];
-    const char* actual_name;
-
-    pthread_mutex_lock(&fuse->global->lock);
-    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
-            parent_path, sizeof(parent_path));
-    DLOG(INFO) << "[" << handler->token << "] MKDIR " << name << " 0" << std::oct << req->mode
-               << " @ " << std::hex << hdr->nodeid
-               << " (" << (parent_node ? parent_node->name : "?") << ")";
-    pthread_mutex_unlock(&fuse->global->lock);
-
-    if (!parent_node || !(actual_name = find_file_within(parent_path, name,
-            child_path, sizeof(child_path), 1))) {
-        return -ENOENT;
-    }
-    if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
-        return -EACCES;
-    }
-    __u32 mode = (req->mode & (~0777)) | 0775;
-    if (mkdir(child_path, mode) == -1) {
-        return -errno;
-    }
-
-    /* When creating /Android/data and /Android/obb, mark them as .nomedia */
-    if (parent_node->perm == PERM_ANDROID && !strcasecmp(name, "data")) {
-        char nomedia[PATH_MAX];
-        snprintf(nomedia, PATH_MAX, "%s/.nomedia", child_path);
-        if (touch(nomedia, 0664) != 0) {
-            PLOG(ERROR) << "touch(" << nomedia << ") failed";
-            return -ENOENT;
-        }
-    }
-    if (parent_node->perm == PERM_ANDROID && !strcasecmp(name, "obb")) {
-        char nomedia[PATH_MAX];
-        snprintf(nomedia, PATH_MAX, "%s/.nomedia", fuse->global->obb_path);
-        if (touch(nomedia, 0664) != 0) {
-            PLOG(ERROR) << "touch(" << nomedia << ") failed";
-            return -ENOENT;
-        }
-    }
-
-    return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path);
-}
-
-static int handle_unlink(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const char* name)
-{
-    struct node* parent_node;
-    struct node* child_node;
-    char parent_path[PATH_MAX];
-    char child_path[PATH_MAX];
-
-    pthread_mutex_lock(&fuse->global->lock);
-    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
-            parent_path, sizeof(parent_path));
-    DLOG(INFO) << "[" << handler->token << "] UNLINK " << name << " @ " << std::hex << hdr->nodeid
-               << " (" << (parent_node ? parent_node->name : "?") << ")";
-    pthread_mutex_unlock(&fuse->global->lock);
-
-    if (!parent_node || !find_file_within(parent_path, name,
-            child_path, sizeof(child_path), 1)) {
-        return -ENOENT;
-    }
-    if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
-        return -EACCES;
-    }
-    if (unlink(child_path) == -1) {
-        return -errno;
-    }
-    pthread_mutex_lock(&fuse->global->lock);
-    child_node = lookup_child_by_name_locked(parent_node, name);
-    if (child_node) {
-        child_node->deleted = true;
-    }
-    pthread_mutex_unlock(&fuse->global->lock);
-    if (parent_node && child_node) {
-        /* Tell all other views that node is gone */
-        DLOG(INFO) << "[" << handler->token << "] fuse_notify_delete"
-                   << " parent=" << std::hex << parent_node->nid
-                   << ", child=" << std::hex << child_node->nid << std::dec
-                   << ", name=" << name;
-        if (fuse != fuse->global->fuse_default) {
-            fuse_notify_delete(fuse->global->fuse_default, parent_node->nid, child_node->nid, name);
-        }
-        if (fuse != fuse->global->fuse_read) {
-            fuse_notify_delete(fuse->global->fuse_read, parent_node->nid, child_node->nid, name);
-        }
-        if (fuse != fuse->global->fuse_write) {
-            fuse_notify_delete(fuse->global->fuse_write, parent_node->nid, child_node->nid, name);
-        }
-    }
-    return 0;
-}
-
-static int handle_rmdir(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const char* name)
-{
-    struct node* child_node;
-    struct node* parent_node;
-    char parent_path[PATH_MAX];
-    char child_path[PATH_MAX];
-
-    pthread_mutex_lock(&fuse->global->lock);
-    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
-            parent_path, sizeof(parent_path));
-    DLOG(INFO) << "[" << handler->token << "] UNLINK " << name << " @ " << std::hex << hdr->nodeid
-               << " (" << (parent_node ? parent_node->name : "?") << ")";
-    pthread_mutex_unlock(&fuse->global->lock);
-
-    if (!parent_node || !find_file_within(parent_path, name,
-            child_path, sizeof(child_path), 1)) {
-        return -ENOENT;
-    }
-    if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
-        return -EACCES;
-    }
-    if (rmdir(child_path) == -1) {
-        return -errno;
-    }
-    pthread_mutex_lock(&fuse->global->lock);
-    child_node = lookup_child_by_name_locked(parent_node, name);
-    if (child_node) {
-        child_node->deleted = true;
-    }
-    pthread_mutex_unlock(&fuse->global->lock);
-    if (parent_node && child_node) {
-        /* Tell all other views that node is gone */
-        DLOG(INFO) << "[" << handler->token << "] fuse_notify_delete"
-                   << " parent=" << std::hex << parent_node->nid
-                   << ", child=" << std::hex << child_node->nid << std::dec
-                   << ", name=" << name;
-        if (fuse != fuse->global->fuse_default) {
-            fuse_notify_delete(fuse->global->fuse_default, parent_node->nid, child_node->nid, name);
-        }
-        if (fuse != fuse->global->fuse_read) {
-            fuse_notify_delete(fuse->global->fuse_read, parent_node->nid, child_node->nid, name);
-        }
-        if (fuse != fuse->global->fuse_write) {
-            fuse_notify_delete(fuse->global->fuse_write, parent_node->nid, child_node->nid, name);
-        }
-    }
-    return 0;
-}
-
-static int handle_rename(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_rename_in* req,
-        const char* old_name, const char* new_name)
-{
-    struct node* old_parent_node;
-    struct node* new_parent_node;
-    struct node* child_node;
-    char old_parent_path[PATH_MAX];
-    char new_parent_path[PATH_MAX];
-    char old_child_path[PATH_MAX];
-    char new_child_path[PATH_MAX];
-    const char* new_actual_name;
-    int search;
-    int res;
-
-    pthread_mutex_lock(&fuse->global->lock);
-    old_parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
-            old_parent_path, sizeof(old_parent_path));
-    new_parent_node = lookup_node_and_path_by_id_locked(fuse, req->newdir,
-            new_parent_path, sizeof(new_parent_path));
-    DLOG(INFO) << "[" << handler->token << "] RENAME " << old_name << "->" << new_name
-               << " @ " << std::hex << hdr->nodeid
-               << " (" << (old_parent_node ? old_parent_node->name : "?") << ") -> "
-               << std::hex << req->newdir
-               << " (" << (new_parent_node ? new_parent_node->name : "?") << ")";
-    if (!old_parent_node || !new_parent_node) {
-        res = -ENOENT;
-        goto lookup_error;
-    }
-    if (!check_caller_access_to_name(fuse, hdr, old_parent_node, old_name, W_OK)) {
-        res = -EACCES;
-        goto lookup_error;
-    }
-    if (!check_caller_access_to_name(fuse, hdr, new_parent_node, new_name, W_OK)) {
-        res = -EACCES;
-        goto lookup_error;
-    }
-    child_node = lookup_child_by_name_locked(old_parent_node, old_name);
-    if (!child_node || get_node_path_locked(child_node,
-            old_child_path, sizeof(old_child_path)) < 0) {
-        res = -ENOENT;
-        goto lookup_error;
-    }
-    acquire_node_locked(child_node);
-    pthread_mutex_unlock(&fuse->global->lock);
-
-    /* Special case for renaming a file where destination is same path
-     * differing only by case.  In this case we don't want to look for a case
-     * insensitive match.  This allows commands like "mv foo FOO" to work as expected.
-     */
-    search = old_parent_node != new_parent_node
-            || strcasecmp(old_name, new_name);
-    if (!(new_actual_name = find_file_within(new_parent_path, new_name,
-            new_child_path, sizeof(new_child_path), search))) {
-        res = -ENOENT;
-        goto io_error;
-    }
-
-    DLOG(INFO) << "[" << handler->token << "] RENAME " << old_child_path << "->" << new_child_path;
-    res = rename(old_child_path, new_child_path);
-    if (res == -1) {
-        res = -errno;
-        goto io_error;
-    }
-
-    pthread_mutex_lock(&fuse->global->lock);
-    res = rename_node_locked(child_node, new_name, new_actual_name);
-    if (!res) {
-        remove_node_from_parent_locked(child_node);
-        derive_permissions_locked(fuse, new_parent_node, child_node);
-        derive_permissions_recursive_locked(fuse, child_node);
-        add_node_to_parent_locked(child_node, new_parent_node);
-    }
-    goto done;
-
-io_error:
-    pthread_mutex_lock(&fuse->global->lock);
-done:
-    release_node_locked(child_node);
-lookup_error:
-    pthread_mutex_unlock(&fuse->global->lock);
-    return res;
-}
-
-static int open_flags_to_access_mode(int open_flags) {
-    if ((open_flags & O_ACCMODE) == O_RDONLY) {
-        return R_OK;
-    } else if ((open_flags & O_ACCMODE) == O_WRONLY) {
-        return W_OK;
-    } else {
-        /* Probably O_RDRW, but treat as default to be safe */
-        return R_OK | W_OK;
-    }
-}
-
-static int handle_open(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_open_in* req)
-{
-    struct node* node;
-    char path[PATH_MAX];
-    struct fuse_open_out out = {};
-    struct handle *h;
-
-    pthread_mutex_lock(&fuse->global->lock);
-    node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path));
-    DLOG(INFO) << "[" << handler->token << "] OPEN 0" << std::oct << req->flags
-               << " @ " << std::hex << hdr->nodeid << std::dec
-               << " (" << (node ? node->name : "?") << ")";
-    pthread_mutex_unlock(&fuse->global->lock);
-
-    if (!node) {
-        return -ENOENT;
-    }
-    if (!check_caller_access_to_node(fuse, hdr, node,
-            open_flags_to_access_mode(req->flags))) {
-        return -EACCES;
-    }
-    h = static_cast<struct handle*>(malloc(sizeof(*h)));
-    if (!h) {
-        return -ENOMEM;
-    }
-    DLOG(INFO) << "[" << handler->token << "] OPEN " << path;
-    h->fd = TEMP_FAILURE_RETRY(open(path, req->flags));
-    if (h->fd == -1) {
-        free(h);
-        return -errno;
-    }
-    out.fh = ptr_to_id(h);
-    out.open_flags = 0;
-    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
-    return NO_STATUS;
-}
-
-static int handle_read(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_read_in* req)
-{
-    struct handle *h = static_cast<struct handle*>(id_to_ptr(req->fh));
-    __u64 unique = hdr->unique;
-    __u32 size = req->size;
-    __u64 offset = req->offset;
-    int res;
-    __u8 *read_buffer = (__u8 *) ((uintptr_t)(handler->read_buffer + PAGE_SIZE) & ~((uintptr_t)PAGE_SIZE-1));
-
-    /* Don't access any other fields of hdr or req beyond this point, the read buffer
-     * overlaps the request buffer and will clobber data in the request.  This
-     * saves us 128KB per request handler thread at the cost of this scary comment. */
-
-    DLOG(INFO) << "[" << handler->token << "] READ " << std::hex << h << std::dec
-               << "(" << h->fd << ") " << size << "@" << offset;
-    if (size > MAX_READ) {
-        return -EINVAL;
-    }
-    res = TEMP_FAILURE_RETRY(pread64(h->fd, read_buffer, size, offset));
-    if (res == -1) {
-        return -errno;
-    }
-    fuse_reply(fuse, unique, read_buffer, res);
-    return NO_STATUS;
-}
-
-static int handle_write(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_write_in* req,
-        const void* buffer)
-{
-    struct fuse_write_out out;
-    struct handle *h = static_cast<struct handle*>(id_to_ptr(req->fh));
-    int res;
-    __u8 aligned_buffer[req->size] __attribute__((__aligned__(PAGE_SIZE)));
-
-    if (req->flags & O_DIRECT) {
-        memcpy(aligned_buffer, buffer, req->size);
-        buffer = (const __u8*) aligned_buffer;
-    }
-
-    DLOG(INFO) << "[" << handler->token << "] WRITE " << std::hex << h << std::dec
-               << "(" << h->fd << ") " << req->size << "@" << req->offset;
-    res = TEMP_FAILURE_RETRY(pwrite64(h->fd, buffer, req->size, req->offset));
-    if (res == -1) {
-        return -errno;
-    }
-    out.size = res;
-    out.padding = 0;
-    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
-    return NO_STATUS;
-}
-
-static int handle_statfs(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr)
-{
-    char path[PATH_MAX];
-    struct statfs stat;
-    struct fuse_statfs_out out;
-    int res;
-
-    pthread_mutex_lock(&fuse->global->lock);
-    DLOG(INFO) << "[" << handler->token << "] STATFS";
-    res = get_node_path_locked(&fuse->global->root, path, sizeof(path));
-    pthread_mutex_unlock(&fuse->global->lock);
-    if (res < 0) {
-        return -ENOENT;
-    }
-    if (TEMP_FAILURE_RETRY(statfs(fuse->global->root.name, &stat)) == -1) {
-        return -errno;
-    }
-    memset(&out, 0, sizeof(out));
-    out.st.blocks = stat.f_blocks;
-    out.st.bfree = stat.f_bfree;
-    out.st.bavail = stat.f_bavail;
-    out.st.files = stat.f_files;
-    out.st.ffree = stat.f_ffree;
-    out.st.bsize = stat.f_bsize;
-    out.st.namelen = stat.f_namelen;
-    out.st.frsize = stat.f_frsize;
-    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
-    return NO_STATUS;
-}
-
-static int handle_release(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_release_in* req)
-{
-    struct handle *h = static_cast<struct handle*>(id_to_ptr(req->fh));
-
-    DLOG(INFO) << "[" << handler->token << "] RELEASE " << std::hex << h << std::dec
-               << "(" << h->fd << ")";
-    close(h->fd);
-    free(h);
-    return 0;
-}
-
-static int handle_fsync(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_fsync_in* req)
-{
-    bool is_dir = (hdr->opcode == FUSE_FSYNCDIR);
-    bool is_data_sync = req->fsync_flags & 1;
-
-    int fd = -1;
-    if (is_dir) {
-      struct dirhandle *dh = static_cast<struct dirhandle*>(id_to_ptr(req->fh));
-      fd = dirfd(dh->d);
-    } else {
-      struct handle *h = static_cast<struct handle*>(id_to_ptr(req->fh));
-      fd = h->fd;
-    }
-
-    DLOG(INFO) << "[" << handler->token << "] " << (is_dir ? "FSYNCDIR" : "FSYNC") << " "
-               << std::hex << req->fh << std::dec << "(" << fd << ") is_data_sync=" << is_data_sync;
-    int res = is_data_sync ? fdatasync(fd) : fsync(fd);
-    if (res == -1) {
-        return -errno;
-    }
-    return 0;
-}
-
-static int handle_flush(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr)
-{
-    DLOG(INFO) << "[" << handler->token << "] FLUSH";
-    return 0;
-}
-
-static int handle_opendir(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_open_in* req)
-{
-    struct node* node;
-    char path[PATH_MAX];
-    struct fuse_open_out out = {};
-    struct dirhandle *h;
-
-    pthread_mutex_lock(&fuse->global->lock);
-    node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path));
-    DLOG(INFO) << "[" << handler->token << "] OPENDIR @ " << std::hex << hdr->nodeid
-               << " (" << (node ? node->name : "?") << ")";
-    pthread_mutex_unlock(&fuse->global->lock);
-
-    if (!node) {
-        return -ENOENT;
-    }
-    if (!check_caller_access_to_node(fuse, hdr, node, R_OK)) {
-        return -EACCES;
-    }
-    h = static_cast<struct dirhandle*>(malloc(sizeof(*h)));
-    if (!h) {
-        return -ENOMEM;
-    }
-    DLOG(INFO) << "[" << handler->token << "] OPENDIR " << path;
-    h->d = opendir(path);
-    if (!h->d) {
-        free(h);
-        return -errno;
-    }
-    out.fh = ptr_to_id(h);
-    out.open_flags = 0;
-    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
-    return NO_STATUS;
-}
-
-static int handle_readdir(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_read_in* req)
-{
-    char buffer[8192];
-    struct fuse_dirent *fde = (struct fuse_dirent*) buffer;
-    struct dirent *de;
-    struct dirhandle *h = static_cast<struct dirhandle*>(id_to_ptr(req->fh));
-
-    DLOG(INFO) << "[" << handler->token << "] READDIR " << h;
-    if (req->offset == 0) {
-        /* rewinddir() might have been called above us, so rewind here too */
-        DLOG(INFO) << "[" << handler->token << "] calling rewinddir()";
-        rewinddir(h->d);
-    }
-    de = readdir(h->d);
-    if (!de) {
-        return 0;
-    }
-    fde->ino = FUSE_UNKNOWN_INO;
-    /* increment the offset so we can detect when rewinddir() seeks back to the beginning */
-    fde->off = req->offset + 1;
-    fde->type = de->d_type;
-    fde->namelen = strlen(de->d_name);
-    memcpy(fde->name, de->d_name, fde->namelen + 1);
-    fuse_reply(fuse, hdr->unique, fde,
-            FUSE_DIRENT_ALIGN(sizeof(struct fuse_dirent) + fde->namelen));
-    return NO_STATUS;
-}
-
-static int handle_releasedir(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_release_in* req)
-{
-    struct dirhandle *h = static_cast<struct dirhandle*>(id_to_ptr(req->fh));
-
-    DLOG(INFO) << "[" << handler->token << "] RELEASEDIR " << h;
-    closedir(h->d);
-    free(h);
-    return 0;
-}
-
-static int handle_init(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header* hdr, const struct fuse_init_in* req)
-{
-    struct fuse_init_out out;
-    size_t fuse_struct_size;
-
-    DLOG(INFO) << "[" << handler->token << "] INIT ver=" << req->major << "." << req->minor
-               << " maxread=" << req->max_readahead << " flags=" << std::hex << req->flags;
-
-    /* Kernel 2.6.16 is the first stable kernel with struct fuse_init_out
-     * defined (fuse version 7.6). The structure is the same from 7.6 through
-     * 7.22. Beginning with 7.23, the structure increased in size and added
-     * new parameters.
-     */
-    if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) {
-        LOG(ERROR) << "Fuse kernel version mismatch: Kernel version "
-                   << req->major << "." << req->minor
-                   << ", Expected at least " << FUSE_KERNEL_VERSION << ".6";
-        return -1;
-    }
-
-    /* We limit ourselves to 15 because we don't handle BATCH_FORGET yet */
-    out.minor = MIN(req->minor, 15);
-    fuse_struct_size = sizeof(out);
-#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE)
-    /* FUSE_KERNEL_VERSION >= 23. */
-
-    /* Since we return minor version 15, the kernel does not accept the latest
-     * fuse_init_out size. We need to use FUSE_COMPAT_22_INIT_OUT_SIZE always.*/
-    fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
-#endif
-
-    out.major = FUSE_KERNEL_VERSION;
-    out.max_readahead = req->max_readahead;
-    out.flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES;
-    out.max_background = 32;
-    out.congestion_threshold = 32;
-    out.max_write = MAX_WRITE;
-    fuse_reply(fuse, hdr->unique, &out, fuse_struct_size);
-    return NO_STATUS;
-}
-
-static int handle_canonical_path(struct fuse* fuse, struct fuse_handler* handler,
-        const struct fuse_in_header *hdr)
-{
-    struct node* node;
-    char path[PATH_MAX];
-    int len;
-
-    pthread_mutex_lock(&fuse->global->lock);
-    node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
-            path, sizeof(path));
-    DLOG(INFO) << "[" << handler->token << "] CANONICAL_PATH @ " << std::hex << hdr->nodeid
-               << std::dec << " (" << (node ? node->name : "?") << ")";
-    pthread_mutex_unlock(&fuse->global->lock);
-
-    if (!node) {
-        return -ENOENT;
-    }
-    if (!check_caller_access_to_node(fuse, hdr, node, R_OK)) {
-        return -EACCES;
-    }
-    len = strlen(path);
-    if (len + 1 > PATH_MAX)
-        len = PATH_MAX - 1;
-    path[PATH_MAX - 1] = 0;
-    fuse_reply(fuse, hdr->unique, path, len + 1);
-    return NO_STATUS;
-}
-
-static int handle_fuse_request(struct fuse *fuse, struct fuse_handler* handler,
-        const struct fuse_in_header *hdr, const void *data, size_t data_len)
-{
-    switch (hdr->opcode) {
-    case FUSE_LOOKUP: { /* bytez[] -> entry_out */
-        const char *name = static_cast<const char*>(data);
-        return handle_lookup(fuse, handler, hdr, name);
-    }
-
-    case FUSE_FORGET: {
-        const struct fuse_forget_in *req = static_cast<const struct fuse_forget_in*>(data);
-        return handle_forget(fuse, handler, hdr, req);
-    }
-
-    case FUSE_GETATTR: { /* getattr_in -> attr_out */
-        const struct fuse_getattr_in *req = static_cast<const struct fuse_getattr_in*>(data);
-        return handle_getattr(fuse, handler, hdr, req);
-    }
-
-    case FUSE_SETATTR: { /* setattr_in -> attr_out */
-        const struct fuse_setattr_in *req = static_cast<const struct fuse_setattr_in*>(data);
-        return handle_setattr(fuse, handler, hdr, req);
-    }
-
-//    case FUSE_READLINK:
-//    case FUSE_SYMLINK:
-    case FUSE_MKNOD: { /* mknod_in, bytez[] -> entry_out */
-        const struct fuse_mknod_in *req = static_cast<const struct fuse_mknod_in*>(data);
-        const char *name = ((const char*) data) + sizeof(*req);
-        return handle_mknod(fuse, handler, hdr, req, name);
-    }
-
-    case FUSE_MKDIR: { /* mkdir_in, bytez[] -> entry_out */
-        const struct fuse_mkdir_in *req = static_cast<const struct fuse_mkdir_in*>(data);
-        const char *name = ((const char*) data) + sizeof(*req);
-        return handle_mkdir(fuse, handler, hdr, req, name);
-    }
-
-    case FUSE_UNLINK: { /* bytez[] -> */
-        const char *name = static_cast<const char*>(data);
-        return handle_unlink(fuse, handler, hdr, name);
-    }
-
-    case FUSE_RMDIR: { /* bytez[] -> */
-        const char *name = static_cast<const char*>(data);
-        return handle_rmdir(fuse, handler, hdr, name);
-    }
-
-    case FUSE_RENAME: { /* rename_in, oldname, newname ->  */
-        const struct fuse_rename_in *req = static_cast<const struct fuse_rename_in*>(data);
-        const char *old_name = ((const char*) data) + sizeof(*req);
-        const char *new_name = old_name + strlen(old_name) + 1;
-        return handle_rename(fuse, handler, hdr, req, old_name, new_name);
-    }
-
-//    case FUSE_LINK:
-    case FUSE_OPEN: { /* open_in -> open_out */
-        const struct fuse_open_in *req = static_cast<const struct fuse_open_in*>(data);
-        return handle_open(fuse, handler, hdr, req);
-    }
-
-    case FUSE_READ: { /* read_in -> byte[] */
-        const struct fuse_read_in *req = static_cast<const struct fuse_read_in*>(data);
-        return handle_read(fuse, handler, hdr, req);
-    }
-
-    case FUSE_WRITE: { /* write_in, byte[write_in.size] -> write_out */
-        const struct fuse_write_in *req = static_cast<const struct fuse_write_in*>(data);
-        const void* buffer = (const __u8*)data + sizeof(*req);
-        return handle_write(fuse, handler, hdr, req, buffer);
-    }
-
-    case FUSE_STATFS: { /* getattr_in -> attr_out */
-        return handle_statfs(fuse, handler, hdr);
-    }
-
-    case FUSE_RELEASE: { /* release_in -> */
-        const struct fuse_release_in *req = static_cast<const struct fuse_release_in*>(data);
-        return handle_release(fuse, handler, hdr, req);
-    }
-
-    case FUSE_FSYNC:
-    case FUSE_FSYNCDIR: {
-        const struct fuse_fsync_in *req = static_cast<const struct fuse_fsync_in*>(data);
-        return handle_fsync(fuse, handler, hdr, req);
-    }
-
-//    case FUSE_SETXATTR:
-//    case FUSE_GETXATTR:
-//    case FUSE_LISTXATTR:
-//    case FUSE_REMOVEXATTR:
-    case FUSE_FLUSH: {
-        return handle_flush(fuse, handler, hdr);
-    }
-
-    case FUSE_OPENDIR: { /* open_in -> open_out */
-        const struct fuse_open_in *req = static_cast<const struct fuse_open_in*>(data);
-        return handle_opendir(fuse, handler, hdr, req);
-    }
-
-    case FUSE_READDIR: {
-        const struct fuse_read_in *req = static_cast<const struct fuse_read_in*>(data);
-        return handle_readdir(fuse, handler, hdr, req);
-    }
-
-    case FUSE_RELEASEDIR: { /* release_in -> */
-        const struct fuse_release_in *req = static_cast<const struct fuse_release_in*>(data);
-        return handle_releasedir(fuse, handler, hdr, req);
-    }
-
-    case FUSE_INIT: { /* init_in -> init_out */
-        const struct fuse_init_in *req = static_cast<const struct fuse_init_in*>(data);
-        return handle_init(fuse, handler, hdr, req);
-    }
-
-    case FUSE_CANONICAL_PATH: { /* nodeid -> bytez[] */
-        return handle_canonical_path(fuse, handler, hdr);
-    }
-
-    default: {
-        DLOG(INFO) << "[" << handler->token << "] NOTIMPL op=" << hdr->opcode
-                   << "uniq=" << std::hex << hdr->unique << "nid=" << hdr->nodeid << std::dec;
-        return -ENOSYS;
-    }
-    }
-}
-
-void handle_fuse_requests(struct fuse_handler* handler)
-{
-    struct fuse* fuse = handler->fuse;
-    for (;;) {
-        ssize_t len = TEMP_FAILURE_RETRY(read(fuse->fd,
-                handler->request_buffer, sizeof(handler->request_buffer)));
-        if (len == -1) {
-            if (errno == ENODEV) {
-                LOG(ERROR) << "[" << handler->token << "] someone stole our marbles!";
-                exit(2);
-            }
-            PLOG(ERROR) << "[" << handler->token << "] handle_fuse_requests";
-            continue;
-        }
-
-        if (static_cast<size_t>(len) < sizeof(struct fuse_in_header)) {
-            LOG(ERROR) << "[" << handler->token << "] request too short: len=" << len;
-            continue;
-        }
-
-        const struct fuse_in_header* hdr =
-            reinterpret_cast<const struct fuse_in_header*>(handler->request_buffer);
-        if (hdr->len != static_cast<size_t>(len)) {
-            LOG(ERROR) << "[" << handler->token << "] malformed header: len=" << len
-                       << ", hdr->len=" << hdr->len;
-            continue;
-        }
-
-        const void *data = handler->request_buffer + sizeof(struct fuse_in_header);
-        size_t data_len = len - sizeof(struct fuse_in_header);
-        __u64 unique = hdr->unique;
-        int res = handle_fuse_request(fuse, handler, hdr, data, data_len);
-
-        /* We do not access the request again after this point because the underlying
-         * buffer storage may have been reused while processing the request. */
-
-        if (res != NO_STATUS) {
-            if (res) {
-                DLOG(INFO) << "[" << handler->token << "] ERROR " << res;
-            }
-            fuse_status(fuse, unique, res);
-        }
-    }
-}
diff --git a/sdcard/fuse.h b/sdcard/fuse.h
deleted file mode 100644
index 9ccd21d..0000000
--- a/sdcard/fuse.h
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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 FUSE_H_
-#define FUSE_H_
-
-#include <dirent.h>
-#include <fcntl.h>
-#include <linux/fuse.h>
-#include <pthread.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <sys/param.h>
-#include <sys/stat.h>
-#include <sys/statfs.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <unistd.h>
-
-#include <map>
-#include <string>
-
-#include <android-base/logging.h>
-#include <cutils/fs.h>
-#include <cutils/multiuser.h>
-#include <packagelistparser/packagelistparser.h>
-
-#include <private/android_filesystem_config.h>
-
-#define FUSE_TRACE 0
-
-#if FUSE_TRACE
-static constexpr bool kEnableDLog = true;
-#else  // FUSE_TRACE == 0
-static constexpr bool kEnableDLog = false;
-#endif
-
-// Use same strategy as DCHECK().
-#define DLOG(x) \
-    if (kEnableDLog) LOG(x)
-
-/* Maximum number of bytes to write in one request. */
-#define MAX_WRITE (256 * 1024)
-
-/* Maximum number of bytes to read in one request. */
-#define MAX_READ (128 * 1024)
-
-/* Largest possible request.
- * The request size is bounded by the maximum size of a FUSE_WRITE request because it has
- * the largest possible data payload. */
-#define MAX_REQUEST_SIZE (sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + MAX_WRITE)
-
-namespace {
-struct CaseInsensitiveCompare {
-    bool operator()(const std::string& lhs, const std::string& rhs) const {
-        return strcasecmp(lhs.c_str(), rhs.c_str()) < 0;
-    }
-};
-}
-
-using AppIdMap = std::map<std::string, appid_t, CaseInsensitiveCompare>;
-
-/* Permission mode for a specific node. Controls how file permissions
- * are derived for children nodes. */
-typedef enum {
-    /* Nothing special; this node should just inherit from its parent. */
-    PERM_INHERIT,
-    /* This node is one level above a normal root; used for legacy layouts
-     * which use the first level to represent user_id. */
-    PERM_PRE_ROOT,
-    /* This node is "/" */
-    PERM_ROOT,
-    /* This node is "/Android" */
-    PERM_ANDROID,
-    /* This node is "/Android/data" */
-    PERM_ANDROID_DATA,
-    /* This node is "/Android/obb" */
-    PERM_ANDROID_OBB,
-    /* This node is "/Android/media" */
-    PERM_ANDROID_MEDIA,
-} perm_t;
-
-struct handle {
-    int fd;
-};
-
-struct dirhandle {
-    DIR *d;
-};
-
-struct node {
-    __u32 refcount;
-    __u64 nid;
-    __u64 gen;
-    /*
-     * The inode number for this FUSE node. Note that this isn't stable across
-     * multiple invocations of the FUSE daemon.
-     */
-    __u32 ino;
-
-    /* State derived based on current position in hierarchy. */
-    perm_t perm;
-    userid_t userid;
-    uid_t uid;
-    bool under_android;
-
-    struct node *next;          /* per-dir sibling list */
-    struct node *child;         /* first contained file by this dir */
-    struct node *parent;        /* containing directory */
-
-    size_t namelen;
-    char *name;
-    /* If non-null, this is the real name of the file in the underlying storage.
-     * This may differ from the field "name" only by case.
-     * strlen(actual_name) will always equal strlen(name), so it is safe to use
-     * namelen for both fields.
-     */
-    char *actual_name;
-
-    /* If non-null, an exact underlying path that should be grafted into this
-     * position. Used to support things like OBB. */
-    char* graft_path;
-    size_t graft_pathlen;
-
-    bool deleted;
-};
-
-/* Global data for all FUSE mounts */
-struct fuse_global {
-    pthread_mutex_t lock;
-
-    uid_t uid;
-    gid_t gid;
-    bool multi_user;
-
-    char source_path[PATH_MAX];
-    char obb_path[PATH_MAX];
-
-    AppIdMap* package_to_appid;
-
-    __u64 next_generation;
-    struct node root;
-
-    /* Used to allocate unique inode numbers for fuse nodes. We use
-     * a simple counter based scheme where inode numbers from deleted
-     * nodes aren't reused. Note that inode allocations are not stable
-     * across multiple invocation of the sdcard daemon, but that shouldn't
-     * be a huge problem in practice.
-     *
-     * Note that we restrict inodes to 32 bit unsigned integers to prevent
-     * truncation on 32 bit processes when unsigned long long stat.st_ino is
-     * assigned to an unsigned long ino_t type in an LP32 process.
-     *
-     * Also note that fuse_attr and fuse_dirent inode values are 64 bits wide
-     * on both LP32 and LP64, but the fuse kernel code doesn't squash 64 bit
-     * inode numbers into 32 bit values on 64 bit kernels (see fuse_squash_ino
-     * in fs/fuse/inode.c).
-     *
-     * Accesses must be guarded by |lock|.
-     */
-    __u32 inode_ctr;
-
-    struct fuse* fuse_default;
-    struct fuse* fuse_read;
-    struct fuse* fuse_write;
-};
-
-/* Single FUSE mount */
-struct fuse {
-    struct fuse_global* global;
-
-    char dest_path[PATH_MAX];
-
-    int fd;
-
-    gid_t gid;
-    mode_t mask;
-};
-
-/* Private data used by a single FUSE handler */
-struct fuse_handler {
-    struct fuse* fuse;
-    int token;
-
-    /* To save memory, we never use the contents of the request buffer and the read
-     * buffer at the same time.  This allows us to share the underlying storage. */
-    union {
-        __u8 request_buffer[MAX_REQUEST_SIZE];
-        __u8 read_buffer[MAX_READ + PAGE_SIZE];
-    };
-};
-
-void handle_fuse_requests(struct fuse_handler* handler);
-void derive_permissions_recursive_locked(struct fuse* fuse, struct node *parent);
-
-#endif  /* FUSE_H_ */
diff --git a/sdcard/sdcard.cpp b/sdcard/sdcard.cpp
index 77d5a91..574bbfe 100644
--- a/sdcard/sdcard.cpp
+++ b/sdcard/sdcard.cpp
@@ -38,164 +38,18 @@
 #include <cutils/multiuser.h>
 #include <cutils/properties.h>
 
-#include <packagelistparser/packagelistparser.h>
-
 #include <libminijail.h>
 #include <scoped_minijail.h>
 
 #include <private/android_filesystem_config.h>
 
-// README
-//
-// What is this?
-//
-// sdcard is a program that uses FUSE to emulate FAT-on-sdcard style
-// directory permissions (all files are given fixed owner, group, and
-// permissions at creation, owner, group, and permissions are not
-// changeable, symlinks and hardlinks are not createable, etc.
-//
-// See usage() for command line options.
-//
-// It must be run as root, but will drop to requested UID/GID as soon as it
-// mounts a filesystem.  It will refuse to run if requested UID/GID are zero.
-//
-// Things I believe to be true:
-//
-// - ops that return a fuse_entry (LOOKUP, MKNOD, MKDIR, LINK, SYMLINK,
-// CREAT) must bump that node's refcount
-// - don't forget that FORGET can forget multiple references (req->nlookup)
-// - if an op that returns a fuse_entry fails writing the reply to the
-// kernel, you must rollback the refcount to reflect the reference the
-// kernel did not actually acquire
-//
-// This daemon can also derive custom filesystem permissions based on directory
-// structure when requested. These custom permissions support several features:
-//
-// - Apps can access their own files in /Android/data/com.example/ without
-// requiring any additional GIDs.
-// - Separate permissions for protecting directories like Pictures and Music.
-// - Multi-user separation on the same physical device.
-
-#include "fuse.h"
-
-#define PROP_SDCARDFS_DEVICE "ro.sys.sdcardfs"
-#define PROP_SDCARDFS_USER "persist.sys.sdcardfs"
+// NOTE: This is a vestigial program that simply exists to mount the in-kernel
+// sdcardfs filesystem.  The older FUSE-based design that used to live here has
+// been completely removed to avoid confusion.
 
 /* Supplementary groups to execute with. */
 static const gid_t kGroups[1] = { AID_PACKAGE_INFO };
 
-static bool package_parse_callback(pkg_info *info, void *userdata) {
-    struct fuse_global *global = (struct fuse_global *)userdata;
-    bool res = global->package_to_appid->emplace(info->name, info->uid).second;
-    packagelist_free(info);
-    return res;
-}
-
-static bool read_package_list(struct fuse_global* global) {
-    pthread_mutex_lock(&global->lock);
-
-    global->package_to_appid->clear();
-    bool rc = packagelist_parse(package_parse_callback, global);
-    DLOG(INFO) << "read_package_list: found " << global->package_to_appid->size() << " packages";
-
-    // Regenerate ownership details using newly loaded mapping.
-    derive_permissions_recursive_locked(global->fuse_default, &global->root);
-
-    pthread_mutex_unlock(&global->lock);
-
-    return rc;
-}
-
-static void watch_package_list(struct fuse_global* global) {
-    struct inotify_event *event;
-    char event_buf[512];
-
-    int nfd = inotify_init();
-    if (nfd == -1) {
-        PLOG(ERROR) << "inotify_init failed";
-        return;
-    }
-
-    bool active = false;
-    while (1) {
-        if (!active) {
-            int res = inotify_add_watch(nfd, PACKAGES_LIST_FILE, IN_DELETE_SELF);
-            if (res == -1) {
-                if (errno == ENOENT || errno == EACCES) {
-                    /* Framework may not have created the file yet, sleep and retry. */
-                    LOG(ERROR) << "missing \"" << PACKAGES_LIST_FILE << "\"; retrying...";
-                    sleep(3);
-                    continue;
-                } else {
-                    PLOG(ERROR) << "inotify_add_watch failed";
-                    return;
-                }
-            }
-
-            /* Watch above will tell us about any future changes, so
-             * read the current state. */
-            if (read_package_list(global) == false) {
-                LOG(ERROR) << "read_package_list failed";
-                return;
-            }
-            active = true;
-        }
-
-        int event_pos = 0;
-        ssize_t res = TEMP_FAILURE_RETRY(read(nfd, event_buf, sizeof(event_buf)));
-        if (res == -1) {
-            PLOG(ERROR) << "failed to read inotify event";
-            return;
-        } else if (static_cast<size_t>(res) < sizeof(*event)) {
-            LOG(ERROR) << "failed to read inotify event: read " << res << " expected "
-                       << sizeof(event_buf);
-            return;
-        }
-
-        while (res >= static_cast<ssize_t>(sizeof(*event))) {
-            int event_size;
-            event = reinterpret_cast<struct inotify_event*>(event_buf + event_pos);
-
-            DLOG(INFO) << "inotify event: " << std::hex << event->mask << std::dec;
-            if ((event->mask & IN_IGNORED) == IN_IGNORED) {
-                /* Previously watched file was deleted, probably due to move
-                 * that swapped in new data; re-arm the watch and read. */
-                active = false;
-            }
-
-            event_size = sizeof(*event) + event->len;
-            res -= event_size;
-            event_pos += event_size;
-        }
-    }
-}
-
-static int fuse_setup(struct fuse* fuse, gid_t gid, mode_t mask) {
-    char opts[256];
-
-    fuse->fd = TEMP_FAILURE_RETRY(open("/dev/fuse", O_RDWR | O_CLOEXEC));
-    if (fuse->fd == -1) {
-        PLOG(ERROR) << "failed to open fuse device";
-        return -1;
-    }
-
-    umount2(fuse->dest_path, MNT_DETACH);
-
-    snprintf(opts, sizeof(opts),
-            "fd=%i,rootmode=40000,default_permissions,allow_other,user_id=%d,group_id=%d",
-            fuse->fd, fuse->global->uid, fuse->global->gid);
-    if (mount("/dev/fuse", fuse->dest_path, "fuse", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME,
-              opts) == -1) {
-        PLOG(ERROR) << "failed to mount fuse filesystem";
-        return -1;
-    }
-
-    fuse->gid = gid;
-    fuse->mask = mask;
-
-    return 0;
-}
-
 static void drop_privs(uid_t uid, gid_t gid) {
     ScopedMinijail j(minijail_new());
     minijail_set_supplementary_gids(j.get(), arraysize(kGroups), kGroups);
@@ -205,146 +59,28 @@
     minijail_enter(j.get());
 }
 
-static void* start_handler(void* data) {
-    struct fuse_handler* handler = static_cast<fuse_handler*>(data);
-    handle_fuse_requests(handler);
-    return NULL;
-}
-
-static void run(const char* source_path, const char* label, uid_t uid,
-        gid_t gid, userid_t userid, bool multi_user, bool full_write) {
-    struct fuse_global global;
-    struct fuse fuse_default;
-    struct fuse fuse_read;
-    struct fuse fuse_write;
-    struct fuse_handler handler_default;
-    struct fuse_handler handler_read;
-    struct fuse_handler handler_write;
-    pthread_t thread_default;
-    pthread_t thread_read;
-    pthread_t thread_write;
-
-    memset(&global, 0, sizeof(global));
-    memset(&fuse_default, 0, sizeof(fuse_default));
-    memset(&fuse_read, 0, sizeof(fuse_read));
-    memset(&fuse_write, 0, sizeof(fuse_write));
-    memset(&handler_default, 0, sizeof(handler_default));
-    memset(&handler_read, 0, sizeof(handler_read));
-    memset(&handler_write, 0, sizeof(handler_write));
-
-    pthread_mutex_init(&global.lock, NULL);
-    global.package_to_appid = new AppIdMap;
-    global.uid = uid;
-    global.gid = gid;
-    global.multi_user = multi_user;
-    global.next_generation = 0;
-    global.inode_ctr = 1;
-
-    memset(&global.root, 0, sizeof(global.root));
-    global.root.nid = FUSE_ROOT_ID; /* 1 */
-    global.root.refcount = 2;
-    global.root.namelen = strlen(source_path);
-    global.root.name = strdup(source_path);
-    global.root.userid = userid;
-    global.root.uid = AID_ROOT;
-    global.root.under_android = false;
-
-    // Clang static analyzer think strcpy potentially overwrites other fields
-    // in global. Use snprintf() to mute the false warning.
-    snprintf(global.source_path, sizeof(global.source_path), "%s", source_path);
-
-    if (multi_user) {
-        global.root.perm = PERM_PRE_ROOT;
-        snprintf(global.obb_path, sizeof(global.obb_path), "%s/obb", source_path);
-    } else {
-        global.root.perm = PERM_ROOT;
-        snprintf(global.obb_path, sizeof(global.obb_path), "%s/Android/obb", source_path);
-    }
-
-    fuse_default.global = &global;
-    fuse_read.global = &global;
-    fuse_write.global = &global;
-
-    global.fuse_default = &fuse_default;
-    global.fuse_read = &fuse_read;
-    global.fuse_write = &fuse_write;
-
-    snprintf(fuse_default.dest_path, PATH_MAX, "/mnt/runtime/default/%s", label);
-    snprintf(fuse_read.dest_path, PATH_MAX, "/mnt/runtime/read/%s", label);
-    snprintf(fuse_write.dest_path, PATH_MAX, "/mnt/runtime/write/%s", label);
-
-    handler_default.fuse = &fuse_default;
-    handler_read.fuse = &fuse_read;
-    handler_write.fuse = &fuse_write;
-
-    handler_default.token = 0;
-    handler_read.token = 1;
-    handler_write.token = 2;
-
-    umask(0);
-
-    if (multi_user) {
-        /* Multi-user storage is fully isolated per user, so "other"
-         * permissions are completely masked off. */
-        if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006)
-                || fuse_setup(&fuse_read, AID_EVERYBODY, 0027)
-                || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0027)) {
-            PLOG(FATAL) << "failed to fuse_setup";
-        }
-    } else {
-        /* Physical storage is readable by all users on device, but
-         * the Android directories are masked off to a single user
-         * deep inside attr_from_stat(). */
-        if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006)
-                || fuse_setup(&fuse_read, AID_EVERYBODY, full_write ? 0027 : 0022)
-                || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0022)) {
-            PLOG(FATAL) << "failed to fuse_setup";
-        }
-    }
-
-    // Will abort if priv-dropping fails.
-    drop_privs(uid, gid);
-
-    if (multi_user) {
-        fs_prepare_dir(global.obb_path, 0775, uid, gid);
-    }
-
-    if (pthread_create(&thread_default, NULL, start_handler, &handler_default)
-            || pthread_create(&thread_read, NULL, start_handler, &handler_read)
-            || pthread_create(&thread_write, NULL, start_handler, &handler_write)) {
-        LOG(FATAL) << "failed to pthread_create";
-    }
-
-    watch_package_list(&global);
-    LOG(FATAL) << "terminated prematurely";
-}
-
 static bool sdcardfs_setup(const std::string& source_path, const std::string& dest_path,
                            uid_t fsuid, gid_t fsgid, bool multi_user, userid_t userid, gid_t gid,
-                           mode_t mask, bool derive_gid) {
-    std::string opts = android::base::StringPrintf(
-        "fsuid=%d,fsgid=%d,%s%smask=%d,userid=%d,gid=%d", fsuid, fsgid,
-        multi_user ? "multiuser," : "", derive_gid ? "derive_gid," : "", mask, userid, gid);
+                           mode_t mask, bool derive_gid, bool default_normal) {
+    // Try several attempts, each time with one less option, to gracefully
+    // handle older kernels that aren't updated yet.
+    for (int i = 0; i < 4; i++) {
+        std::string new_opts;
+        if (multi_user && i < 3) new_opts += "multiuser,";
+        if (derive_gid && i < 2) new_opts += "derive_gid,";
+        if (default_normal && i < 1) new_opts += "default_normal,";
 
-    if (mount(source_path.c_str(), dest_path.c_str(), "sdcardfs",
-              MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()) == -1) {
-        if (derive_gid) {
-            PLOG(ERROR) << "trying to mount sdcardfs filesystem without derive_gid";
-            /* Maybe this isn't supported on this kernel. Try without. */
-            opts = android::base::StringPrintf("fsuid=%d,fsgid=%d,%smask=%d,userid=%d,gid=%d",
-                                               fsuid, fsgid, multi_user ? "multiuser," : "", mask,
-                                               userid, gid);
-            if (mount(source_path.c_str(), dest_path.c_str(), "sdcardfs",
-                      MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()) == -1) {
-                PLOG(ERROR) << "failed to mount sdcardfs filesystem";
-                return false;
-            }
+        auto opts = android::base::StringPrintf("fsuid=%d,fsgid=%d,%smask=%d,userid=%d,gid=%d",
+                                                fsuid, fsgid, new_opts.c_str(), mask, userid, gid);
+        if (mount(source_path.c_str(), dest_path.c_str(), "sdcardfs",
+                  MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()) == -1) {
+            PLOG(WARNING) << "Failed to mount sdcardfs with options " << opts;
         } else {
-            PLOG(ERROR) << "failed to mount sdcardfs filesystem";
-            return false;
+            return true;
         }
     }
-    return true;
+
+    return false;
 }
 
 static bool sdcardfs_setup_bind_remount(const std::string& source_path, const std::string& dest_path,
@@ -370,7 +106,7 @@
 
 static void run_sdcardfs(const std::string& source_path, const std::string& label, uid_t uid,
                          gid_t gid, userid_t userid, bool multi_user, bool full_write,
-                         bool derive_gid) {
+                         bool derive_gid, bool default_normal) {
     std::string dest_path_default = "/mnt/runtime/default/" + label;
     std::string dest_path_read = "/mnt/runtime/read/" + label;
     std::string dest_path_write = "/mnt/runtime/write/" + label;
@@ -380,7 +116,7 @@
         // Multi-user storage is fully isolated per user, so "other"
         // permissions are completely masked off.
         if (!sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid,
-                            AID_SDCARD_RW, 0006, derive_gid) ||
+                            AID_SDCARD_RW, 0006, derive_gid, default_normal) ||
             !sdcardfs_setup_bind_remount(dest_path_default, dest_path_read, AID_EVERYBODY, 0027) ||
             !sdcardfs_setup_bind_remount(dest_path_default, dest_path_write, AID_EVERYBODY,
                                          full_write ? 0007 : 0027)) {
@@ -391,7 +127,7 @@
         // the Android directories are masked off to a single user
         // deep inside attr_from_stat().
         if (!sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid,
-                            AID_SDCARD_RW, 0006, derive_gid) ||
+                            AID_SDCARD_RW, 0006, derive_gid, default_normal) ||
             !sdcardfs_setup_bind_remount(dest_path_default, dest_path_read, AID_EVERYBODY,
                                          full_write ? 0027 : 0022) ||
             !sdcardfs_setup_bind_remount(dest_path_default, dest_path_write, AID_EVERYBODY,
@@ -411,41 +147,6 @@
     exit(0);
 }
 
-static bool supports_sdcardfs(void) {
-    std::string filesystems;
-    if (!android::base::ReadFileToString("/proc/filesystems", &filesystems)) {
-        PLOG(ERROR) << "Could not read /proc/filesystems";
-        return false;
-    }
-    for (const auto& fs : android::base::Split(filesystems, "\n")) {
-        if (fs.find("sdcardfs") != std::string::npos) return true;
-    }
-    return false;
-}
-
-static bool should_use_sdcardfs(void) {
-    char property[PROPERTY_VALUE_MAX];
-
-    // Allow user to have a strong opinion about state
-    property_get(PROP_SDCARDFS_USER, property, "");
-    if (!strcmp(property, "force_on")) {
-        LOG(WARNING) << "User explicitly enabled sdcardfs";
-        return supports_sdcardfs();
-    } else if (!strcmp(property, "force_off")) {
-        LOG(WARNING) << "User explicitly disabled sdcardfs";
-        return false;
-    }
-
-    // Fall back to device opinion about state
-    if (property_get_bool(PROP_SDCARDFS_DEVICE, true)) {
-        LOG(WARNING) << "Device explicitly enabled sdcardfs";
-        return supports_sdcardfs();
-    } else {
-        LOG(WARNING) << "Device explicitly disabled sdcardfs";
-        return false;
-    }
-}
-
 static int usage() {
     LOG(ERROR) << "usage: sdcard [OPTIONS] <source_path> <label>"
                << "    -u: specify UID to run as"
@@ -466,12 +167,16 @@
     bool multi_user = false;
     bool full_write = false;
     bool derive_gid = false;
+    bool default_normal = false;
     int i;
     struct rlimit rlim;
     int fs_version;
 
+    setenv("ANDROID_LOG_TAGS", "*:v", 1);
+    android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
+
     int opt;
-    while ((opt = getopt(argc, argv, "u:g:U:mwG")) != -1) {
+    while ((opt = getopt(argc, argv, "u:g:U:mwGi")) != -1) {
         switch (opt) {
             case 'u':
                 uid = strtoul(optarg, NULL, 10);
@@ -491,6 +196,9 @@
             case 'G':
                 derive_gid = true;
                 break;
+            case 'i':
+                default_normal = true;
+                break;
             case '?':
             default:
                 return usage();
@@ -533,10 +241,7 @@
         sleep(1);
     }
 
-    if (should_use_sdcardfs()) {
-        run_sdcardfs(source_path, label, uid, gid, userid, multi_user, full_write, derive_gid);
-    } else {
-        run(source_path, label, uid, gid, userid, multi_user, full_write);
-    }
+    run_sdcardfs(source_path, label, uid, gid, userid, multi_user, full_write, derive_gid,
+                 default_normal);
     return 1;
 }
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index 4203db4..3ccb92f 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -12,6 +12,7 @@
         "sh",
         "sh_vendor",
         "toolbox",
+        "toolbox_vendor",
         "toybox",
         "toybox_vendor",
     ],
diff --git a/shell_and_utilities/README.md b/shell_and_utilities/README.md
index 206204b..c423c69 100644
--- a/shell_and_utilities/README.md
+++ b/shell_and_utilities/README.md
@@ -175,18 +175,18 @@
 
 one-true-awk: awk
 
-toolbox: getevent newfs\_msdos
+toolbox: getevent getprop newfs\_msdos
 
 toybox: acpi base64 basename blockdev cal cat chcon chgrp chmod chown
 chroot chrt cksum clear cmp comm cp cpio cut date df diff dirname dmesg
 dos2unix du echo env expand expr fallocate false file find flock free
-getenforce getprop groups gunzip gzip head hostname hwclock id ifconfig
-inotifyd insmod ionice iorenice kill killall ln load\_policy log logname
-losetup ls lsmod lsof lspci lsusb md5sum microcom mkdir mkfifo mknod
-mkswap mktemp modinfo modprobe more mount mountpoint mv netstat nice
-nl nohup od paste patch pgrep pidof pkill pmap printenv printf ps pwd
-readlink realpath renice restorecon rm rmdir rmmod runcon sed sendevent
-seq setenforce setprop setsid sha1sum sha224sum sha256sum sha384sum
+getenforce groups gunzip gzip head hostname hwclock id ifconfig inotifyd
+insmod ionice iorenice kill killall ln load\_policy log logname losetup
+ls lsmod lsof lspci lsusb md5sum microcom mkdir mkfifo mknod mkswap
+mktemp modinfo modprobe more mount mountpoint mv netstat nice nl nohup
+od paste patch pgrep pidof pkill pmap printenv printf ps pwd readlink
+realpath renice restorecon rm rmdir rmmod runcon sed sendevent seq
+setenforce setprop setsid sha1sum sha224sum sha256sum sha384sum
 sha512sum sleep sort split start stat stop strings swapoff swapon sync
 sysctl tac tail tar taskset tee time timeout top touch tr true truncate
 tty ulimit umount uname uniq unix2dos uptime usleep uudecode uuencode
diff --git a/toolbox/Android.bp b/toolbox/Android.bp
index 9c2cafa..9f7fc3b 100644
--- a/toolbox/Android.bp
+++ b/toolbox/Android.bp
@@ -1,11 +1,19 @@
-common_cflags = [
-    "-Werror",
-    "-Wno-unused-parameter",
-    "-Wno-unused-const-variable",
-    "-include bsd-compatibility.h"
-]
+cc_defaults {
+    name: "toolbox_defaults",
+
+    cflags: [
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wno-unused-const-variable",
+        "-include bsd-compatibility.h",
+    ],
+    local_include_dirs: ["upstream-netbsd/include/"],
+}
 
 cc_library_static {
+    name: "libtoolbox_dd",
+    defaults: ["toolbox_defaults"],
+    vendor_available: true,
     srcs: [
         "upstream-netbsd/bin/dd/args.c",
         "upstream-netbsd/bin/dd/conv.c",
@@ -19,17 +27,71 @@
         "upstream-netbsd/lib/libc/string/swab.c",
         "upstream-netbsd/lib/libutil/raise_default_signal.c",
     ],
-    cflags: common_cflags + [
+    cflags: [
         "-Dmain=dd_main",
         "-DNO_CONV",
     ],
-    local_include_dirs: ["upstream-netbsd/include/"],
-    name: "libtoolbox_dd",
+}
+
+genrule {
+    name: "toolbox_input_labels",
+    tool_files: ["generate-input.h-labels.py"],
+    cmd: "$(location) $(in) >$(out)",
+    srcs: [":kernel_input_headers"],
+    out: ["input.h-labels.h"],
+}
+
+cc_defaults {
+    name: "toolbox_binary_defaults",
+    defaults: ["toolbox_defaults"],
+    cpp_std: "experimental",
+    srcs: [
+        "toolbox.c",
+        "getevent.c",
+        "getprop.cpp",
+        "newfs_msdos.c",
+    ],
+    generated_headers: [
+        "toolbox_input_labels",
+    ],
+    whole_static_libs: ["libtoolbox_dd"],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+    ],
+    static_libs: ["libpropertyinfoparser"],
+
+    symlinks: [
+        "dd",
+        "getevent",
+        "getprop",
+        "newfs_msdos",
+    ],
+}
+
+cc_binary {
+    name: "toolbox",
+    defaults: ["toolbox_binary_defaults"],
+}
+
+cc_binary {
+    name: "toolbox_vendor",
+    stem: "toolbox",
+    vendor: true,
+    defaults: ["toolbox_binary_defaults"],
+}
+
+// We only want 'r' on userdebug and eng builds.
+cc_binary {
+    name: "r",
+    defaults: ["toolbox_defaults"],
+    srcs: ["r.c"],
 }
 
 // We build BSD grep separately, so it can provide egrep and fgrep too.
 cc_defaults {
     name: "grep_common",
+    defaults: ["toolbox_defaults"],
     srcs: [
         "upstream-netbsd/usr.bin/grep/fastgrep.c",
         "upstream-netbsd/usr.bin/grep/file.c",
@@ -37,9 +99,10 @@
         "upstream-netbsd/usr.bin/grep/queue.c",
         "upstream-netbsd/usr.bin/grep/util.c",
     ],
-    cflags: common_cflags,
-    local_include_dirs: ["upstream-netbsd/include/"],
-    symlinks: ["egrep", "fgrep"],
+    symlinks: [
+        "egrep",
+        "fgrep",
+    ],
 
     sanitize: {
         integer_overflow: false,
diff --git a/toolbox/Android.mk b/toolbox/Android.mk
deleted file mode 100644
index c4795a7..0000000
--- a/toolbox/Android.mk
+++ /dev/null
@@ -1,70 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-common_cflags := \
-    -Werror -Wno-unused-parameter -Wno-unused-const-variable \
-    -include bsd-compatibility.h \
-
-include $(CLEAR_VARS)
-
-BSD_TOOLS := \
-    dd \
-
-OUR_TOOLS := \
-    getevent \
-    newfs_msdos \
-
-ALL_TOOLS = $(BSD_TOOLS) $(OUR_TOOLS)
-
-LOCAL_SRC_FILES := \
-    toolbox.c \
-    $(patsubst %,%.c,$(OUR_TOOLS)) \
-
-LOCAL_CFLAGS += $(common_cflags)
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/upstream-netbsd/include/
-
-LOCAL_SHARED_LIBRARIES := \
-    libcutils \
-
-LOCAL_WHOLE_STATIC_LIBRARIES := $(patsubst %,libtoolbox_%,$(BSD_TOOLS))
-
-LOCAL_MODULE := toolbox
-
-# Install the symlinks.
-LOCAL_POST_INSTALL_CMD := $(hide) $(foreach t,$(ALL_TOOLS),ln -sf toolbox $(TARGET_OUT)/bin/$(t);)
-
-# Including this will define $(intermediates).
-#
-include $(BUILD_EXECUTABLE)
-
-$(LOCAL_PATH)/toolbox.c: $(intermediates)/tools.h
-
-TOOLS_H := $(intermediates)/tools.h
-$(TOOLS_H): PRIVATE_TOOLS := toolbox $(ALL_TOOLS)
-$(TOOLS_H): PRIVATE_CUSTOM_TOOL = echo "/* file generated automatically */" > $@ ; for t in $(PRIVATE_TOOLS) ; do echo "TOOL($$t)" >> $@ ; done
-$(TOOLS_H): $(LOCAL_PATH)/Android.mk
-$(TOOLS_H):
-	$(transform-generated-source)
-
-$(LOCAL_PATH)/getevent.c: $(intermediates)/input.h-labels.h
-
-UAPI_INPUT_EVENT_CODES_H := bionic/libc/kernel/uapi/linux/input.h bionic/libc/kernel/uapi/linux/input-event-codes.h
-INPUT_H_LABELS_H := $(intermediates)/input.h-labels.h
-$(INPUT_H_LABELS_H): PRIVATE_LOCAL_PATH := $(LOCAL_PATH)
-# The PRIVATE_CUSTOM_TOOL line uses = to evaluate the output path late.
-# We copy the input path so it can't be accidentally modified later.
-$(INPUT_H_LABELS_H): PRIVATE_UAPI_INPUT_EVENT_CODES_H := $(UAPI_INPUT_EVENT_CODES_H)
-$(INPUT_H_LABELS_H): PRIVATE_CUSTOM_TOOL = $(PRIVATE_LOCAL_PATH)/generate-input.h-labels.py $(PRIVATE_UAPI_INPUT_EVENT_CODES_H) > $@
-# The dependency line though gets evaluated now, so the PRIVATE_ copy doesn't exist yet,
-# and the original can't yet have been modified, so this is both sufficient and necessary.
-$(INPUT_H_LABELS_H): $(LOCAL_PATH)/Android.mk $(LOCAL_PATH)/generate-input.h-labels.py $(UAPI_INPUT_EVENT_CODES_H)
-$(INPUT_H_LABELS_H):
-	$(transform-generated-source)
-
-# We only want 'r' on userdebug and eng builds.
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := r.c
-LOCAL_CFLAGS += $(common_cflags)
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/upstream-netbsd/include/
-LOCAL_MODULE := r
-LOCAL_MODULE_TAGS := debug
-include $(BUILD_EXECUTABLE)
diff --git a/toolbox/getprop.cpp b/toolbox/getprop.cpp
new file mode 100644
index 0000000..9e324a0
--- /dev/null
+++ b/toolbox/getprop.cpp
@@ -0,0 +1,161 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <getopt.h>
+#include <sys/system_properties.h>
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <android-base/properties.h>
+#include <property_info_parser/property_info_parser.h>
+
+using android::base::GetProperty;
+using android::properties::PropertyInfoAreaFile;
+
+PropertyInfoAreaFile property_info_file;
+
+enum class ResultType {
+    Value,
+    Context,
+    Type,
+};
+
+void PrintAllProperties(ResultType result_type) {
+    std::vector<std::pair<std::string, std::string>> properties;
+    __system_property_foreach(
+        [](const prop_info* pi, void* cookie) {
+            __system_property_read_callback(
+                pi,
+                [](void* cookie, const char* name, const char* value, unsigned) {
+                    auto properties =
+                        reinterpret_cast<std::vector<std::pair<std::string, std::string>>*>(cookie);
+                    properties->emplace_back(name, value);
+                },
+                cookie);
+        },
+        &properties);
+
+    std::sort(properties.begin(), properties.end());
+
+    if (result_type != ResultType::Value) {
+        for (auto& [name, value] : properties) {
+            const char* context = nullptr;
+            const char* type = nullptr;
+            property_info_file->GetPropertyInfo(name.c_str(), &context, &type);
+            if (result_type == ResultType::Context) {
+                value = context;
+            } else {
+                value = type;
+            }
+        }
+    }
+
+    for (const auto& [name, value] : properties) {
+        std::cout << "[" << name << "]: [" << value << "]" << std::endl;
+    }
+}
+
+void PrintProperty(const char* name, const char* default_value, ResultType result_type) {
+    switch (result_type) {
+        case ResultType::Value:
+            std::cout << GetProperty(name, default_value) << std::endl;
+            break;
+        case ResultType::Context: {
+            const char* context = nullptr;
+            property_info_file->GetPropertyInfo(name, &context, nullptr);
+            std::cout << context << std::endl;
+            break;
+        }
+        case ResultType::Type: {
+            const char* type = nullptr;
+            property_info_file->GetPropertyInfo(name, nullptr, &type);
+            std::cout << type << std::endl;
+            break;
+        }
+    }
+}
+
+extern "C" int getprop_main(int argc, char** argv) {
+    auto result_type = ResultType::Value;
+
+    while (true) {
+        static const struct option long_options[] = {
+            {"help", no_argument, nullptr, 'h'},
+            {nullptr, 0, nullptr, 0},
+        };
+
+        int arg = getopt_long(argc, argv, "TZ", long_options, nullptr);
+
+        if (arg == -1) {
+            break;
+        }
+
+        switch (arg) {
+            case 'h':
+                std::cout << "usage: getprop [-TZ] [NAME [DEFAULT]]\n"
+                             "\n"
+                             "Gets an Android system property, or lists them all.\n"
+                             "\n"
+                             "-T\tShow property types instead of values\n"
+                             "-Z\tShow property contexts instead of values\n"
+                          << std::endl;
+                return 0;
+            case 'T':
+                if (result_type != ResultType::Value) {
+                    std::cerr << "Only one of -T or -Z may be specified" << std::endl;
+                    return -1;
+                }
+                result_type = ResultType::Type;
+                break;
+            case 'Z':
+                if (result_type != ResultType::Value) {
+                    std::cerr << "Only one of -T or -Z may be specified" << std::endl;
+                    return -1;
+                }
+                result_type = ResultType::Context;
+                break;
+            case '?':
+                return -1;
+            default:
+                std::cerr << "getprop: getopt returned invalid result: " << arg << std::endl;
+                return -1;
+        }
+    }
+
+    if (result_type != ResultType::Value) {
+        property_info_file.LoadDefaultPath();
+        if (!property_info_file) {
+            std::cerr << "Unable to load property info file" << std::endl;
+            return -1;
+        }
+    }
+
+    if (optind >= argc) {
+        PrintAllProperties(result_type);
+        return 0;
+    }
+
+    if (optind < argc - 2) {
+        std::cerr << "getprop: Max 2 arguments (see \"getprop --help\")" << std::endl;
+        return -1;
+    }
+
+    PrintProperty(argv[optind], (optind == argc - 1) ? "" : argv[optind + 1], result_type);
+
+    return 0;
+}
diff --git a/toolbox/tools.h b/toolbox/tools.h
new file mode 100644
index 0000000..505f528
--- /dev/null
+++ b/toolbox/tools.h
@@ -0,0 +1,5 @@
+TOOL(dd)
+TOOL(getevent)
+TOOL(getprop)
+TOOL(newfs_msdos)
+TOOL(toolbox)
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index c61e89d..5b83e21 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -379,7 +379,7 @@
     }
 
     if (req->size > MAX_READ_SIZE) {
-        ALOGW("%s: request is too large (%zd > %zd) - refusing\n",
+        ALOGW("%s: request is too large (%u > %d) - refusing\n",
               __func__, req->size, MAX_READ_SIZE);
         msg->result = STORAGE_ERR_NOT_VALID;
         goto err_response;