Merge "graphics: add render intents and new color modes" into pi-dev
diff --git a/adb/Android.mk b/adb/Android.mk
index e52f0cb..c473ed2 100644
--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -36,6 +36,7 @@
 #   CreateFileW(path_wide.c_str());
 ADB_COMMON_windows_CFLAGS := \
     -DUNICODE=1 -D_UNICODE=1 \
+    -D_POSIX_SOURCE
 
 # libadb
 # =========================================================
diff --git a/adb/adb.h b/adb/adb.h
index a6d0463..c74fa99 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -196,10 +196,6 @@
 
 extern const char* adb_device_banner;
 
-#if !ADB_HOST
-extern int SHELL_EXIT_NOTIFY_FD;
-#endif  // !ADB_HOST
-
 #define CHUNK_SIZE (64 * 1024)
 
 #if !ADB_HOST
diff --git a/adb/adb_listeners.cpp b/adb/adb_listeners.cpp
index a142384..fecf452 100644
--- a/adb/adb_listeners.cpp
+++ b/adb/adb_listeners.cpp
@@ -29,6 +29,7 @@
 
 #include "socket_spec.h"
 #include "sysdeps.h"
+#include "sysdeps/memory.h"
 #include "transport.h"
 
 // A listener is an entity which binds to a local port and, upon receiving a connection on that
@@ -203,7 +204,7 @@
         }
     }
 
-    std::unique_ptr<alistener> listener(new alistener(local_name, connect_to));
+    auto listener = std::make_unique<alistener>(local_name, connect_to);
 
     int resolved = 0;
     listener->fd = socket_spec_listen(listener->local_name, error, &resolved);
diff --git a/adb/commandline.cpp b/adb/commandline.cpp
index 6a80bcd..546321e 100644
--- a/adb/commandline.cpp
+++ b/adb/commandline.cpp
@@ -61,6 +61,7 @@
 #include "services.h"
 #include "shell_service.h"
 #include "sysdeps/chrono.h"
+#include "sysdeps/memory.h"
 
 static int install_app(int argc, const char** argv);
 static int install_multiple_app(int argc, const char** argv);
@@ -263,7 +264,7 @@
     char raw_buffer[BUFSIZ];
     char* buffer_ptr = raw_buffer;
     if (use_shell_protocol) {
-        protocol.reset(new ShellProtocol(fd));
+        protocol = std::make_unique<ShellProtocol>(fd);
         if (!protocol) {
             LOG(ERROR) << "failed to allocate memory for ShellProtocol object";
             return 1;
@@ -630,7 +631,7 @@
     args->raw_stdin = raw_stdin;
     args->escape_char = escape_char;
     if (use_shell_protocol) {
-        args->protocol.reset(new ShellProtocol(args->write_fd));
+        args->protocol = std::make_unique<ShellProtocol>(args->write_fd);
     }
 
     if (raw_stdin) stdin_raw_init();
diff --git a/adb/fdevent.cpp b/adb/fdevent.cpp
index d285561..42d851a 100644
--- a/adb/fdevent.cpp
+++ b/adb/fdevent.cpp
@@ -42,13 +42,6 @@
 #include "adb_unique_fd.h"
 #include "adb_utils.h"
 
-#if !ADB_HOST
-// This socket is used when a subproc shell service exists.
-// It wakes up the fdevent_loop() and cause the correct handling
-// of the shell's pseudo-tty master. I.e. force close it.
-int SHELL_EXIT_NOTIFY_FD = -1;
-#endif // !ADB_HOST
-
 #define FDE_EVENTMASK  0x00ff
 #define FDE_STATEMASK  0xff00
 
@@ -294,72 +287,6 @@
     fde->func(fde->fd, events, fde->arg);
 }
 
-#if !ADB_HOST
-
-#include <sys/ioctl.h>
-
-static void fdevent_subproc_event_func(int fd, unsigned ev, void* /* userdata */) {
-    D("subproc handling on fd = %d, ev = %x", fd, ev);
-
-    CHECK_GE(fd, 0);
-
-    if (ev & FDE_READ) {
-        int subproc_fd;
-
-        if(!ReadFdExactly(fd, &subproc_fd, sizeof(subproc_fd))) {
-            LOG(FATAL) << "Failed to read the subproc's fd from " << fd;
-        }
-        auto it = g_poll_node_map.find(subproc_fd);
-        if (it == g_poll_node_map.end()) {
-            D("subproc_fd %d cleared from fd_table", subproc_fd);
-            adb_close(subproc_fd);
-            return;
-        }
-        fdevent* subproc_fde = it->second.fde;
-        if(subproc_fde->fd != subproc_fd) {
-            // Already reallocated?
-            LOG(FATAL) << "subproc_fd(" << subproc_fd << ") != subproc_fde->fd(" << subproc_fde->fd
-                       << ")";
-            return;
-        }
-
-        subproc_fde->force_eof = 1;
-
-        int rcount = 0;
-        ioctl(subproc_fd, FIONREAD, &rcount);
-        D("subproc with fd %d has rcount=%d, err=%d", subproc_fd, rcount, errno);
-        if (rcount != 0) {
-            // If there is data left, it will show up in the select().
-            // This works because there is no other thread reading that
-            // data when in this fd_func().
-            return;
-        }
-
-        D("subproc_fde %s", dump_fde(subproc_fde).c_str());
-        subproc_fde->events |= FDE_READ;
-        if(subproc_fde->state & FDE_PENDING) {
-            return;
-        }
-        subproc_fde->state |= FDE_PENDING;
-        fdevent_call_fdfunc(subproc_fde);
-    }
-}
-
-static void fdevent_subproc_setup() {
-    int s[2];
-
-    if(adb_socketpair(s)) {
-        PLOG(FATAL) << "cannot create shell-exit socket-pair";
-    }
-    D("fdevent_subproc: socket pair (%d, %d)", s[0], s[1]);
-
-    SHELL_EXIT_NOTIFY_FD = s[0];
-    fdevent *fde = fdevent_create(s[1], fdevent_subproc_event_func, NULL);
-    CHECK(fde != nullptr) << "cannot create fdevent for shell-exit handler";
-    fdevent_add(fde, FDE_READ);
-}
-#endif // !ADB_HOST
-
 static void fdevent_run_flush() EXCLUDES(run_queue_mutex) {
     // We need to be careful around reentrancy here, since a function we call can queue up another
     // function.
@@ -400,6 +327,10 @@
             PLOG(FATAL) << "failed to create run queue notify socketpair";
         }
 
+        if (!set_file_block_mode(s[0], false) || !set_file_block_mode(s[1], false)) {
+            PLOG(FATAL) << "failed to make run queue notify socket nonblocking";
+        }
+
         run_queue_notify_fd.reset(s[0]);
         fdevent* fde = fdevent_create(s[1], fdevent_run_func, nullptr);
         CHECK(fde != nullptr);
@@ -416,7 +347,12 @@
     // run_queue_notify_fd could still be -1 if we're called before fdevent has finished setting up.
     // In that case, rely on the setup code to flush the queue without a notification being needed.
     if (run_queue_notify_fd != -1) {
-        if (adb_write(run_queue_notify_fd.get(), "", 1) != 1) {
+        int rc = adb_write(run_queue_notify_fd.get(), "", 1);
+
+        // It's possible that we get EAGAIN here, if lots of notifications came in while handling.
+        if (rc == 0) {
+            PLOG(FATAL) << "run queue notify fd was closed?";
+        } else if (rc == -1 && errno != EAGAIN) {
             PLOG(FATAL) << "failed to write to run queue notify fd";
         }
     }
@@ -424,9 +360,6 @@
 
 void fdevent_loop() {
     set_main_thread();
-#if !ADB_HOST
-    fdevent_subproc_setup();
-#endif // !ADB_HOST
     fdevent_run_setup();
 
     while (true) {
diff --git a/adb/fdevent_test.cpp b/adb/fdevent_test.cpp
index 63cc4d1..e3d5a35 100644
--- a/adb/fdevent_test.cpp
+++ b/adb/fdevent_test.cpp
@@ -26,6 +26,7 @@
 
 #include "adb_io.h"
 #include "fdevent_test.h"
+#include "sysdeps/memory.h"
 
 class FdHandler {
   public:
@@ -99,7 +100,7 @@
 
     std::vector<std::unique_ptr<FdHandler>> fd_handlers;
     for (size_t i = 0; i < read_fds.size(); ++i) {
-        fd_handlers.push_back(std::unique_ptr<FdHandler>(new FdHandler(read_fds[i], write_fds[i])));
+        fd_handlers.push_back(std::make_unique<FdHandler>(read_fds[i], write_fds[i]));
     }
 
     fdevent_loop();
@@ -180,7 +181,13 @@
     PrepareThread();
     std::thread thread(fdevent_loop);
 
-    for (int i = 0; i < 100; ++i) {
+    // Block the main thread for a long time while we queue our callbacks.
+    fdevent_run_on_main_thread([]() {
+        check_main_thread();
+        std::this_thread::sleep_for(std::chrono::seconds(1));
+    });
+
+    for (int i = 0; i < 1000000; ++i) {
         fdevent_run_on_main_thread([i, &vec]() {
             check_main_thread();
             vec.push_back(i);
@@ -189,8 +196,8 @@
 
     TerminateThread(thread);
 
-    ASSERT_EQ(100u, vec.size());
-    for (int i = 0; i < 100; ++i) {
+    ASSERT_EQ(1000000u, vec.size());
+    for (int i = 0; i < 1000000; ++i) {
         ASSERT_EQ(i, vec[i]);
     }
 }
diff --git a/adb/fdevent_test.h b/adb/fdevent_test.h
index 5ca49ac..1a2d41c 100644
--- a/adb/fdevent_test.h
+++ b/adb/fdevent_test.h
@@ -52,13 +52,8 @@
     }
 
     size_t GetAdditionalLocalSocketCount() {
-#if ADB_HOST
         // dummy socket installed in PrepareThread() + fdevent_run_on_main_thread socket
         return 2;
-#else
-        // dummy socket + fdevent_run_on_main_thread + fdevent_subproc_setup() sockets
-        return 3;
-#endif
     }
 
     void TerminateThread(std::thread& thread) {
diff --git a/adb/shell_service.cpp b/adb/shell_service.cpp
index f9f80c0..17c7eba 100644
--- a/adb/shell_service.cpp
+++ b/adb/shell_service.cpp
@@ -372,8 +372,8 @@
         }
         D("protocol FD = %d", protocol_sfd_.get());
 
-        input_.reset(new ShellProtocol(protocol_sfd_));
-        output_.reset(new ShellProtocol(protocol_sfd_));
+        input_ = std::make_unique<ShellProtocol>(protocol_sfd_);
+        output_ = std::make_unique<ShellProtocol>(protocol_sfd_);
         if (!input_ || !output_) {
             *error = "failed to allocate shell protocol objects";
             kill(pid_, SIGKILL);
@@ -681,22 +681,6 @@
         }
         protocol_sfd_.reset(-1);
     }
-
-    // Pass the local socket FD to the shell cleanup fdevent.
-    if (SHELL_EXIT_NOTIFY_FD >= 0) {
-        int fd = local_socket_sfd_;
-        if (WriteFdExactly(SHELL_EXIT_NOTIFY_FD, &fd, sizeof(fd))) {
-            D("passed fd %d to SHELL_EXIT_NOTIFY_FD (%d) for pid %d",
-              fd, SHELL_EXIT_NOTIFY_FD, pid_);
-            // The shell exit fdevent now owns the FD and will close it once
-            // the last bit of data flushes through.
-            static_cast<void>(local_socket_sfd_.release());
-        } else {
-            PLOG(ERROR) << "failed to write fd " << fd
-                        << " to SHELL_EXIT_NOTIFY_FD (" << SHELL_EXIT_NOTIFY_FD
-                        << ") for pid " << pid_;
-        }
-    }
 }
 
 }  // namespace
diff --git a/adb/shell_service_test.cpp b/adb/shell_service_test.cpp
index 839284e..4e27822 100644
--- a/adb/shell_service_test.cpp
+++ b/adb/shell_service_test.cpp
@@ -55,40 +55,20 @@
     static sighandler_t saved_sigpipe_handler_;
 
     int subprocess_fd_ = -1;
-    int shell_exit_receiver_fd_ = -1, saved_shell_exit_fd_;
 };
 
 sighandler_t ShellServiceTest::saved_sigpipe_handler_ = nullptr;
 
 void ShellServiceTest::StartTestSubprocess(
         const char* command, SubprocessType type, SubprocessProtocol protocol) {
-    // We want to intercept the shell exit message to make sure it's sent.
-    saved_shell_exit_fd_ = SHELL_EXIT_NOTIFY_FD;
-    int fd[2];
-    ASSERT_TRUE(adb_socketpair(fd) >= 0);
-    SHELL_EXIT_NOTIFY_FD = fd[0];
-    shell_exit_receiver_fd_ = fd[1];
-
     subprocess_fd_ = StartSubprocess(command, nullptr, type, protocol);
     ASSERT_TRUE(subprocess_fd_ >= 0);
 }
 
 void ShellServiceTest::CleanupTestSubprocess() {
     if (subprocess_fd_ >= 0) {
-        // Subprocess should send its FD to SHELL_EXIT_NOTIFY_FD for cleanup.
-        int notified_fd = -1;
-        ASSERT_TRUE(ReadFdExactly(shell_exit_receiver_fd_, &notified_fd,
-                                  sizeof(notified_fd)));
-        ASSERT_EQ(notified_fd, subprocess_fd_);
-
         adb_close(subprocess_fd_);
         subprocess_fd_ = -1;
-
-        // Restore SHELL_EXIT_NOTIFY_FD.
-        adb_close(SHELL_EXIT_NOTIFY_FD);
-        adb_close(shell_exit_receiver_fd_);
-        shell_exit_receiver_fd_ = -1;
-        SHELL_EXIT_NOTIFY_FD = saved_shell_exit_fd_;
     }
 }
 
diff --git a/adb/socket_test.cpp b/adb/socket_test.cpp
index 04ad6f3..6b40056 100644
--- a/adb/socket_test.cpp
+++ b/adb/socket_test.cpp
@@ -113,16 +113,12 @@
     asocket* s = create_local_socket(arg->socket_fd);
     ASSERT_TRUE(s != nullptr);
     arg->bytes_written = 0;
-    while (true) {
-        std::string data;
-        data.resize(MAX_PAYLOAD);
-        arg->bytes_written += data.size();
-        int ret = s->enqueue(s, std::move(data));
-        if (ret == 1) {
-            // The writer has one packet waiting to send.
-            break;
-        }
-    }
+
+    std::string data;
+    data.resize(MAX_PAYLOAD);
+    arg->bytes_written += data.size();
+    int ret = s->enqueue(s, std::move(data));
+    ASSERT_EQ(1, ret);
 
     asocket* cause_close_s = create_local_socket(arg->cause_close_fd);
     ASSERT_TRUE(cause_close_s != nullptr);
@@ -213,6 +209,45 @@
     TerminateThread(thread);
 }
 
+// Ensure that if we fail to write output to an fd, we will still flush data coming from it.
+TEST_F(LocalSocketTest, flush_after_shutdown) {
+    int head_fd[2];
+    int tail_fd[2];
+    ASSERT_EQ(0, adb_socketpair(head_fd));
+    ASSERT_EQ(0, adb_socketpair(tail_fd));
+
+    asocket* head = create_local_socket(head_fd[1]);
+    asocket* tail = create_local_socket(tail_fd[1]);
+
+    head->peer = tail;
+    head->ready(head);
+
+    tail->peer = head;
+    tail->ready(tail);
+
+    PrepareThread();
+    std::thread thread(fdevent_loop);
+
+    EXPECT_TRUE(WriteFdExactly(head_fd[0], "foo", 3));
+
+    EXPECT_EQ(0, adb_shutdown(head_fd[0], SHUT_RD));
+    const char* str = "write succeeds, but local_socket will fail to write";
+    EXPECT_TRUE(WriteFdExactly(tail_fd[0], str, strlen(str)));
+    EXPECT_TRUE(WriteFdExactly(head_fd[0], "bar", 3));
+
+    char buf[6];
+    EXPECT_TRUE(ReadFdExactly(tail_fd[0], buf, 6));
+    EXPECT_EQ(0, memcmp(buf, "foobar", 6));
+
+    adb_close(head_fd[0]);
+    adb_close(tail_fd[0]);
+
+    // Wait until the local sockets are closed.
+    std::this_thread::sleep_for(SLEEP_FOR_FDEVENT);
+    ASSERT_EQ(GetAdditionalLocalSocketCount(), fdevent_installed_count());
+    TerminateThread(thread);
+}
+
 #if defined(__linux__)
 
 static void ClientThreadFunc() {
diff --git a/adb/sockets.cpp b/adb/sockets.cpp
index 0007fed..e05a3db 100644
--- a/adb/sockets.cpp
+++ b/adb/sockets.cpp
@@ -106,50 +106,131 @@
     }
 }
 
+enum class SocketFlushResult {
+    Destroyed,
+    TryAgain,
+    Completed,
+};
+
+static SocketFlushResult local_socket_flush_incoming(asocket* s) {
+    while (!s->packet_queue.empty()) {
+        Range& r = s->packet_queue.front();
+
+        int rc = adb_write(s->fd, r.data(), r.size());
+        if (rc == static_cast<int>(r.size())) {
+            s->packet_queue.pop_front();
+        } else if (rc > 0) {
+            r.drop_front(rc);
+            fdevent_add(&s->fde, FDE_WRITE);
+            return SocketFlushResult::TryAgain;
+        } else if (rc == -1 && errno == EAGAIN) {
+            fdevent_add(&s->fde, FDE_WRITE);
+            return SocketFlushResult::TryAgain;
+        }
+
+        // We failed to write, but it's possible that we can still read from the socket.
+        // Give that a try before giving up.
+        s->has_write_error = true;
+        break;
+    }
+
+    // If we sent the last packet of a closing socket, we can now destroy it.
+    if (s->closing) {
+        s->close(s);
+        return SocketFlushResult::Destroyed;
+    }
+
+    fdevent_del(&s->fde, FDE_WRITE);
+    return SocketFlushResult::Completed;
+}
+
+// Returns false if the socket has been closed and destroyed as a side-effect of this function.
+static bool local_socket_flush_outgoing(asocket* s) {
+    const size_t max_payload = s->get_max_payload();
+    std::string data;
+    data.resize(max_payload);
+    char* x = &data[0];
+    size_t avail = max_payload;
+    int r = 0;
+    int is_eof = 0;
+
+    while (avail > 0) {
+        r = adb_read(s->fd, x, avail);
+        D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%zu", s->id, s->fd, r,
+          r < 0 ? errno : 0, avail);
+        if (r == -1) {
+            if (errno == EAGAIN) {
+                break;
+            }
+        } else if (r > 0) {
+            avail -= r;
+            x += r;
+            continue;
+        }
+
+        /* r = 0 or unhandled error */
+        is_eof = 1;
+        break;
+    }
+    D("LS(%d): fd=%d post avail loop. r=%d is_eof=%d forced_eof=%d", s->id, s->fd, r, is_eof,
+      s->fde.force_eof);
+
+    if (avail != max_payload && s->peer) {
+        data.resize(max_payload - avail);
+
+        // s->peer->enqueue() may call s->close() and free s,
+        // so save variables for debug printing below.
+        unsigned saved_id = s->id;
+        int saved_fd = s->fd;
+        r = s->peer->enqueue(s->peer, std::move(data));
+        D("LS(%u): fd=%d post peer->enqueue(). r=%d", saved_id, saved_fd, r);
+
+        if (r < 0) {
+            // Error return means they closed us as a side-effect and we must
+            // return immediately.
+            //
+            // Note that if we still have buffered packets, the socket will be
+            // placed on the closing socket list. This handler function will be
+            // called again to process FDE_WRITE events.
+            return false;
+        }
+
+        if (r > 0) {
+            /* if the remote cannot accept further events,
+            ** we disable notification of READs.  They'll
+            ** be enabled again when we get a call to ready()
+            */
+            fdevent_del(&s->fde, FDE_READ);
+        }
+    }
+
+    // Don't allow a forced eof if data is still there.
+    if ((s->fde.force_eof && !r) || is_eof) {
+        D(" closing because is_eof=%d r=%d s->fde.force_eof=%d", is_eof, r, s->fde.force_eof);
+        s->close(s);
+        return false;
+    }
+
+    return true;
+}
+
 static int local_socket_enqueue(asocket* s, std::string data) {
     D("LS(%d): enqueue %zu", s->id, data.size());
 
     Range r(std::move(data));
-
-    /* if there is already data queue'd, we will receive
-    ** events when it's time to write.  just add this to
-    ** the tail
-    */
-    if (!s->packet_queue.empty()) {
-        goto enqueue;
-    }
-
-    /* write as much as we can, until we
-    ** would block or there is an error/eof
-    */
-    while (!r.empty()) {
-        int rc = adb_write(s->fd, r.data(), r.size());
-        if (rc > 0) {
-            r.drop_front(rc);
-            continue;
-        }
-
-        if (rc == 0 || errno != EAGAIN) {
-            D("LS(%d): not ready, errno=%d: %s", s->id, errno, strerror(errno));
-            s->has_write_error = true;
-            s->close(s);
-            return 1; /* not ready (error) */
-        } else {
-            // errno == EAGAIN
-            break;
-        }
-    }
-
-    if (r.empty()) {
-        return 0; /* ready for more data */
-    }
-
-enqueue:
-    /* make sure we are notified when we can drain the queue */
     s->packet_queue.push_back(std::move(r));
-    fdevent_add(&s->fde, FDE_WRITE);
+    switch (local_socket_flush_incoming(s)) {
+        case SocketFlushResult::Destroyed:
+            return -1;
 
-    return 1; /* not ready (backlog) */
+        case SocketFlushResult::TryAgain:
+            return 1;
+
+        case SocketFlushResult::Completed:
+            return 0;
+    }
+
+    return !s->packet_queue.empty();
 }
 
 static void local_socket_ready(asocket* s) {
@@ -224,114 +305,21 @@
     ** in order to simplify the code.
     */
     if (ev & FDE_WRITE) {
-        while (!s->packet_queue.empty()) {
-            Range& r = s->packet_queue.front();
-            while (!r.empty()) {
-                int rc = adb_write(fd, r.data(), r.size());
-                if (rc == -1) {
-                    /* returning here is ok because FDE_READ will
-                    ** be processed in the next iteration loop
-                    */
-                    if (errno == EAGAIN) {
-                        return;
-                    }
-                } else if (rc > 0) {
-                    r.drop_front(rc);
-                    continue;
-                }
-
-                D(" closing after write because rc=%d and errno is %d", rc, errno);
-                s->has_write_error = true;
-                s->close(s);
+        switch (local_socket_flush_incoming(s)) {
+            case SocketFlushResult::Destroyed:
                 return;
-            }
 
-            if (r.empty()) {
-                s->packet_queue.pop_front();
-            }
+            case SocketFlushResult::TryAgain:
+                break;
+
+            case SocketFlushResult::Completed:
+                s->peer->ready(s->peer);
+                break;
         }
-
-        /* if we sent the last packet of a closing socket,
-        ** we can now destroy it.
-        */
-        if (s->closing) {
-            D(" closing because 'closing' is set after write");
-            s->close(s);
-            return;
-        }
-
-        /* no more packets queued, so we can ignore
-        ** writable events again and tell our peer
-        ** to resume writing
-        */
-        fdevent_del(&s->fde, FDE_WRITE);
-        s->peer->ready(s->peer);
     }
 
     if (ev & FDE_READ) {
-        const size_t max_payload = s->get_max_payload();
-        std::string data;
-        data.resize(max_payload);
-        char* x = &data[0];
-        size_t avail = max_payload;
-        int r = 0;
-        int is_eof = 0;
-
-        while (avail > 0) {
-            r = adb_read(fd, x, avail);
-            D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%zu", s->id, s->fd, r,
-              r < 0 ? errno : 0, avail);
-            if (r == -1) {
-                if (errno == EAGAIN) {
-                    break;
-                }
-            } else if (r > 0) {
-                avail -= r;
-                x += r;
-                continue;
-            }
-
-            /* r = 0 or unhandled error */
-            is_eof = 1;
-            break;
-        }
-        D("LS(%d): fd=%d post avail loop. r=%d is_eof=%d forced_eof=%d", s->id, s->fd, r, is_eof,
-          s->fde.force_eof);
-
-        if (avail != max_payload && s->peer) {
-            data.resize(max_payload - avail);
-
-            // s->peer->enqueue() may call s->close() and free s,
-            // so save variables for debug printing below.
-            unsigned saved_id = s->id;
-            int saved_fd = s->fd;
-            r = s->peer->enqueue(s->peer, std::move(data));
-            D("LS(%u): fd=%d post peer->enqueue(). r=%d", saved_id, saved_fd, r);
-
-            if (r < 0) {
-                /* error return means they closed us as a side-effect
-                ** and we must return immediately.
-                **
-                ** note that if we still have buffered packets, the
-                ** socket will be placed on the closing socket list.
-                ** this handler function will be called again
-                ** to process FDE_WRITE events.
-                */
-                return;
-            }
-
-            if (r > 0) {
-                /* if the remote cannot accept further events,
-                ** we disable notification of READs.  They'll
-                ** be enabled again when we get a call to ready()
-                */
-                fdevent_del(&s->fde, FDE_READ);
-            }
-        }
-        /* Don't allow a forced eof if data is still there */
-        if ((s->fde.force_eof && !r) || is_eof) {
-            D(" closing because is_eof=%d r=%d s->fde.force_eof=%d", is_eof, r, s->fde.force_eof);
-            s->close(s);
+        if (!local_socket_flush_outgoing(s)) {
             return;
         }
     }
diff --git a/adb/sysdeps.h b/adb/sysdeps.h
index 307be6d..edeacc1 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -111,14 +111,14 @@
 #define  mkdir  ___xxx_mkdir
 
 // See the comments for the !defined(_WIN32) versions of adb_*().
-extern int  adb_open(const char*  path, int  options);
-extern int  adb_creat(const char*  path, int  mode);
-extern int  adb_read(int  fd, void* buf, int len);
-extern int  adb_write(int  fd, const void*  buf, int  len);
-extern int  adb_lseek(int  fd, int  pos, int  where);
-extern int  adb_shutdown(int  fd);
-extern int  adb_close(int  fd);
-extern int  adb_register_socket(SOCKET s);
+extern int adb_open(const char* path, int options);
+extern int adb_creat(const char* path, int mode);
+extern int adb_read(int fd, void* buf, int len);
+extern int adb_write(int fd, const void* buf, int len);
+extern int adb_lseek(int fd, int pos, int where);
+extern int adb_shutdown(int fd, int direction = SHUT_RDWR);
+extern int adb_close(int fd);
+extern int adb_register_socket(SOCKET s);
 
 // See the comments for the !defined(_WIN32) version of unix_close().
 static __inline__ int  unix_close(int fd)
@@ -419,14 +419,10 @@
 #undef   open
 #define  open    ___xxx_open
 
-static __inline__ int  adb_shutdown(int fd)
-{
-    return shutdown(fd, SHUT_RDWR);
-}
-static __inline__ int  adb_shutdown(int fd, int direction)
-{
+static __inline__ int adb_shutdown(int fd, int direction = SHUT_RDWR) {
     return shutdown(fd, direction);
 }
+
 #undef   shutdown
 #define  shutdown   ____xxx_shutdown
 
diff --git a/adb/sysdeps/memory.h b/adb/sysdeps/memory.h
new file mode 100644
index 0000000..0e4c509
--- /dev/null
+++ b/adb/sysdeps/memory.h
@@ -0,0 +1,36 @@
+#pragma once
+
+/*
+ * 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 <memory>
+#include <type_traits>
+
+#if defined(_WIN32)
+// We don't have C++14 on Windows yet.
+// Reimplement std::make_unique ourselves until we do.
+
+namespace std {
+
+template <typename T, typename... Args>
+typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type make_unique(
+    Args&&... args) {
+    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+}  // namespace std
+
+#endif
diff --git a/adb/sysdeps/posix/network.cpp b/adb/sysdeps/posix/network.cpp
index 45da5af..ecd1fd2 100644
--- a/adb/sysdeps/posix/network.cpp
+++ b/adb/sysdeps/posix/network.cpp
@@ -105,8 +105,7 @@
     }
 
     if (type == SOCK_STREAM || type == SOCK_SEQPACKET) {
-        // Arbitrarily selected value, ported from libcutils.
-        if (listen(s, 4) != 0) {
+        if (listen(s, SOMAXCONN) != 0) {
             set_error(error);
             return -1;
         }
diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp
index 5873b2b..62f4ac8 100644
--- a/adb/sysdeps_win32.cpp
+++ b/adb/sysdeps_win32.cpp
@@ -747,8 +747,6 @@
     return fd;
 }
 
-#define LISTEN_BACKLOG 4
-
 // interface_address is INADDR_LOOPBACK or INADDR_ANY.
 static int _network_server(int port, int type, u_long interface_address, std::string* error) {
     struct sockaddr_in addr;
@@ -805,7 +803,7 @@
         return -1;
     }
     if (type == SOCK_STREAM) {
-        if (listen(s, LISTEN_BACKLOG) == SOCKET_ERROR) {
+        if (listen(s, SOMAXCONN) == SOCKET_ERROR) {
             const DWORD err = WSAGetLastError();
             *error = android::base::StringPrintf(
                 "cannot listen on socket: %s", android::base::SystemErrorCodeToString(err).c_str());
@@ -1013,9 +1011,8 @@
     return ntohs(reinterpret_cast<sockaddr_in*>(&addr_storage)->sin_port);
 }
 
-int  adb_shutdown(int  fd)
-{
-    FH   f = _fh_from_int(fd, __func__);
+int adb_shutdown(int fd, int direction) {
+    FH f = _fh_from_int(fd, __func__);
 
     if (!f || f->clazz != &_fh_socket_class) {
         D("adb_shutdown: invalid fd %d", fd);
@@ -1023,8 +1020,8 @@
         return -1;
     }
 
-    D( "adb_shutdown: %s", f->name);
-    if (shutdown(f->fh_socket, SD_BOTH) == SOCKET_ERROR) {
+    D("adb_shutdown: %s", f->name);
+    if (shutdown(f->fh_socket, direction) == SOCKET_ERROR) {
         const DWORD err = WSAGetLastError();
         D("socket shutdown fd %d failed: %s", fd,
           android::base::SystemErrorCodeToString(err).c_str());
diff --git a/adb/test_adb.py b/adb/test_adb.py
index 98c8a59..e771106 100644
--- a/adb/test_adb.py
+++ b/adb/test_adb.py
@@ -217,8 +217,12 @@
         ipv4.listen(1)
 
         ipv6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
-        ipv6.bind(('::1', ipv4.getsockname()[1] + 1))
-        ipv6.listen(1)
+        try:
+            ipv6.bind(('::1', ipv4.getsockname()[1] + 1))
+            ipv6.listen(1)
+        except socket.error:
+            print("IPv6 not available, skipping")
+            return
 
         for s in (ipv4, ipv6):
             port = s.getsockname()[1]
diff --git a/adb/test_device.py b/adb/test_device.py
index 4cf2206..d422df2 100644
--- a/adb/test_device.py
+++ b/adb/test_device.py
@@ -31,6 +31,7 @@
 import subprocess
 import sys
 import tempfile
+import threading
 import time
 import unittest
 
@@ -493,6 +494,29 @@
         stdout, _ = self.device.shell(["cat", log_path])
         self.assertEqual(stdout.strip(), "SIGHUP")
 
+    def test_exit_stress(self):
+        """Hammer `adb shell exit 42` with multiple threads."""
+        thread_count = 48
+        result = dict()
+        def hammer(thread_idx, thread_count, result):
+            success = True
+            for i in range(thread_idx, 240, thread_count):
+                ret = subprocess.call(['adb', 'shell', 'exit {}'.format(i)])
+                if ret != i % 256:
+                    success = False
+                    break
+            result[thread_idx] = success
+
+        threads = []
+        for i in range(thread_count):
+            thread = threading.Thread(target=hammer, args=(i, thread_count, result))
+            thread.start()
+            threads.append(thread)
+        for thread in threads:
+            thread.join()
+        for i, success in result.iteritems():
+            self.assertTrue(success)
+
 
 class ArgumentEscapingTest(DeviceTest):
     def test_shell_escaping(self):
diff --git a/fastboot/engine.cpp b/fastboot/engine.cpp
index 271b792..60b7124 100644
--- a/fastboot/engine.cpp
+++ b/fastboot/engine.cpp
@@ -27,7 +27,6 @@
  */
 
 #include "fastboot.h"
-#include "fs.h"
 
 #include <errno.h>
 #include <stdarg.h>
@@ -38,150 +37,117 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#define OP_DOWNLOAD   1
-#define OP_COMMAND    2
-#define OP_QUERY      3
-#define OP_NOTICE     4
-#define OP_DOWNLOAD_SPARSE 5
-#define OP_WAIT_FOR_DISCONNECT 6
-#define OP_DOWNLOAD_FD 7
-#define OP_UPLOAD 8
+#include <memory>
+#include <vector>
 
-typedef struct Action Action;
+#include <android-base/stringprintf.h>
 
-#define CMD_SIZE 64
-
-struct Action {
-    unsigned op;
-    Action* next;
-
-    char cmd[CMD_SIZE];
-    const char* prod;
-    void* data;
-    int fd;
-
-    // The protocol only supports 32-bit sizes, so you'll have to break
-    // anything larger into chunks.
-    uint32_t size;
-
-    const char *msg;
-    int (*func)(Action* a, int status, const char* resp);
-
-    double start;
+enum Op {
+    OP_DOWNLOAD,
+    OP_COMMAND,
+    OP_QUERY,
+    OP_NOTICE,
+    OP_DOWNLOAD_SPARSE,
+    OP_WAIT_FOR_DISCONNECT,
+    OP_DOWNLOAD_FD,
+    OP_UPLOAD,
 };
 
-static Action *action_list = 0;
-static Action *action_last = 0;
+struct Action {
+    Action(Op op, const std::string& cmd) : op(op), cmd(cmd) {}
 
+    Op op;
+    std::string cmd;
+    std::string msg;
 
+    std::string product;
 
+    void* data = nullptr;
+    // The protocol only supports 32-bit sizes, so you'll have to break
+    // anything larger into multiple chunks.
+    uint32_t size = 0;
+
+    int fd = -1;
+
+    int (*func)(Action& a, int status, const char* resp) = nullptr;
+
+    double start = -1;
+};
+
+static std::vector<std::unique_ptr<Action>> action_list;
 
 bool fb_getvar(Transport* transport, const std::string& key, std::string* value) {
-    std::string cmd = "getvar:";
-    cmd += key;
+    std::string cmd = "getvar:" + key;
 
     char buf[FB_RESPONSE_SZ + 1];
     memset(buf, 0, sizeof(buf));
-    if (fb_command_response(transport, cmd.c_str(), buf)) {
-      return false;
+    if (fb_command_response(transport, cmd, buf)) {
+        return false;
     }
     *value = buf;
     return true;
 }
 
-static int cb_default(Action* a, int status, const char* resp) {
+static int cb_default(Action& a, int status, const char* resp) {
     if (status) {
         fprintf(stderr,"FAILED (%s)\n", resp);
     } else {
         double split = now();
-        fprintf(stderr,"OKAY [%7.3fs]\n", (split - a->start));
-        a->start = split;
+        fprintf(stderr, "OKAY [%7.3fs]\n", (split - a.start));
+        a.start = split;
     }
     return status;
 }
 
-static Action *queue_action(unsigned op, const char *fmt, ...)
-{
-    va_list ap;
-    size_t cmdsize;
-
-    Action* a = reinterpret_cast<Action*>(calloc(1, sizeof(Action)));
-    if (a == nullptr) die("out of memory");
-
-    va_start(ap, fmt);
-    cmdsize = vsnprintf(a->cmd, sizeof(a->cmd), fmt, ap);
-    va_end(ap);
-
-    if (cmdsize >= sizeof(a->cmd)) {
-        free(a);
-        die("Command length (%zu) exceeds maximum size (%zu)", cmdsize, sizeof(a->cmd));
-    }
-
-    if (action_last) {
-        action_last->next = a;
-    } else {
-        action_list = a;
-    }
-    action_last = a;
-    a->op = op;
+static Action& queue_action(Op op, const std::string& cmd) {
+    std::unique_ptr<Action> a{new Action(op, cmd)};
     a->func = cb_default;
 
-    a->start = -1;
-
-    return a;
+    action_list.push_back(std::move(a));
+    return *action_list.back();
 }
 
-void fb_set_active(const char *slot)
-{
-    Action *a;
-    a = queue_action(OP_COMMAND, "set_active:%s", slot);
-    a->msg = mkmsg("Setting current slot to '%s'", slot);
+void fb_set_active(const std::string& slot) {
+    Action& a = queue_action(OP_COMMAND, "set_active:" + slot);
+    a.msg = "Setting current slot to '" + slot + "'...";
 }
 
-void fb_queue_erase(const char *ptn)
-{
-    Action *a;
-    a = queue_action(OP_COMMAND, "erase:%s", ptn);
-    a->msg = mkmsg("erasing '%s'", ptn);
+void fb_queue_erase(const std::string& partition) {
+    Action& a = queue_action(OP_COMMAND, "erase:" + partition);
+    a.msg = "Erasing '" + partition + "'...";
 }
 
-void fb_queue_flash_fd(const char *ptn, int fd, uint32_t sz)
-{
-    Action *a;
+void fb_queue_flash_fd(const std::string& partition, int fd, uint32_t sz) {
+    Action& a = queue_action(OP_DOWNLOAD_FD, "");
+    a.fd = fd;
+    a.size = sz;
+    a.msg = android::base::StringPrintf("Sending '%s' (%d KB)...", partition.c_str(), sz / 1024);
 
-    a = queue_action(OP_DOWNLOAD_FD, "");
-    a->fd = fd;
-    a->size = sz;
-    a->msg = mkmsg("sending '%s' (%d KB)", ptn, sz / 1024);
-
-    a = queue_action(OP_COMMAND, "flash:%s", ptn);
-    a->msg = mkmsg("writing '%s'", ptn);
+    Action& b = queue_action(OP_COMMAND, "flash:" + partition);
+    b.msg = "Writing '" + partition + "'...";
 }
 
-void fb_queue_flash(const char *ptn, void *data, uint32_t sz)
-{
-    Action *a;
+void fb_queue_flash(const std::string& partition, void* data, uint32_t sz) {
+    Action& a = queue_action(OP_DOWNLOAD, "");
+    a.data = data;
+    a.size = sz;
+    a.msg = android::base::StringPrintf("Sending '%s' (%d KB)...", partition.c_str(), sz / 1024);
 
-    a = queue_action(OP_DOWNLOAD, "");
-    a->data = data;
-    a->size = sz;
-    a->msg = mkmsg("sending '%s' (%d KB)", ptn, sz / 1024);
-
-    a = queue_action(OP_COMMAND, "flash:%s", ptn);
-    a->msg = mkmsg("writing '%s'", ptn);
+    Action& b = queue_action(OP_COMMAND, "flash:" + partition);
+    b.msg = "Writing '" + partition + "'...";
 }
 
-void fb_queue_flash_sparse(const char* ptn, struct sparse_file* s, uint32_t sz, size_t current,
-                           size_t total) {
-    Action *a;
+void fb_queue_flash_sparse(const std::string& partition, struct sparse_file* s, uint32_t sz,
+                           size_t current, size_t total) {
+    Action& a = queue_action(OP_DOWNLOAD_SPARSE, "");
+    a.data = s;
+    a.size = 0;
+    a.msg = android::base::StringPrintf("Sending sparse '%s' %zu/%zu (%d KB)...", partition.c_str(),
+                                        current, total, sz / 1024);
 
-    a = queue_action(OP_DOWNLOAD_SPARSE, "");
-    a->data = s;
-    a->size = 0;
-    a->msg = mkmsg("sending sparse '%s' %zu/%zu (%d KB)", ptn, current, total, sz / 1024);
-
-    a = queue_action(OP_COMMAND, "flash:%s", ptn);
-    a->msg = mkmsg("writing '%s' %zu/%zu", ptn, current, total);
+    Action& b = queue_action(OP_COMMAND, "flash:" + partition);
+    b.msg =
+        android::base::StringPrintf("Writing '%s' %zu/%zu...", partition.c_str(), current, total);
 }
 
 static int match(const char* str, const char** value, unsigned count) {
@@ -205,212 +171,181 @@
     return 0;
 }
 
-
-
-static int cb_check(Action* a, int status, const char* resp, int invert)
-{
-    const char** value = reinterpret_cast<const char**>(a->data);
-    unsigned count = a->size;
+static int cb_check(Action& a, int status, const char* resp, int invert) {
+    const char** value = reinterpret_cast<const char**>(a.data);
+    unsigned count = a.size;
     unsigned n;
-    int yes;
 
     if (status) {
         fprintf(stderr,"FAILED (%s)\n", resp);
         return status;
     }
 
-    if (a->prod) {
-        if (strcmp(a->prod, cur_product) != 0) {
+    if (!a.product.empty()) {
+        if (a.product != cur_product) {
             double split = now();
-            fprintf(stderr,"IGNORE, product is %s required only for %s [%7.3fs]\n",
-                    cur_product, a->prod, (split - a->start));
-            a->start = split;
+            fprintf(stderr, "IGNORE, product is %s required only for %s [%7.3fs]\n", cur_product,
+                    a.product.c_str(), (split - a.start));
+            a.start = split;
             return 0;
         }
     }
 
-    yes = match(resp, value, count);
+    int yes = match(resp, value, count);
     if (invert) yes = !yes;
 
     if (yes) {
         double split = now();
-        fprintf(stderr,"OKAY [%7.3fs]\n", (split - a->start));
-        a->start = split;
+        fprintf(stderr, "OKAY [%7.3fs]\n", (split - a.start));
+        a.start = split;
         return 0;
     }
 
-    fprintf(stderr,"FAILED\n\n");
-    fprintf(stderr,"Device %s is '%s'.\n", a->cmd + 7, resp);
-    fprintf(stderr,"Update %s '%s'",
-            invert ? "rejects" : "requires", value[0]);
+    fprintf(stderr, "FAILED\n\n");
+    fprintf(stderr, "Device %s is '%s'.\n", a.cmd.c_str() + 7, resp);
+    fprintf(stderr, "Update %s '%s'", invert ? "rejects" : "requires", value[0]);
     for (n = 1; n < count; n++) {
-        fprintf(stderr," or '%s'", value[n]);
+        fprintf(stderr, " or '%s'", value[n]);
     }
-    fprintf(stderr,".\n\n");
+    fprintf(stderr, ".\n\n");
     return -1;
 }
 
-static int cb_require(Action*a, int status, const char* resp) {
+static int cb_require(Action& a, int status, const char* resp) {
     return cb_check(a, status, resp, 0);
 }
 
-static int cb_reject(Action* a, int status, const char* resp) {
+static int cb_reject(Action& a, int status, const char* resp) {
     return cb_check(a, status, resp, 1);
 }
 
-static char* xstrdup(const char* s) {
-    char* result = strdup(s);
-    if (!result) die("out of memory");
-    return result;
+void fb_queue_require(const std::string& product, const std::string& var, bool invert,
+                      size_t nvalues, const char** values) {
+    Action& a = queue_action(OP_QUERY, "getvar:" + var);
+    a.product = product;
+    a.data = values;
+    a.size = nvalues;
+    a.msg = "Checking " + var;
+    a.func = invert ? cb_reject : cb_require;
+    if (a.data == nullptr) die("out of memory");
 }
 
-void fb_queue_require(const char *prod, const char *var,
-                      bool invert, size_t nvalues, const char **value)
-{
-    Action *a;
-    a = queue_action(OP_QUERY, "getvar:%s", var);
-    a->prod = prod;
-    a->data = value;
-    a->size = nvalues;
-    a->msg = mkmsg("checking %s", var);
-    a->func = invert ? cb_reject : cb_require;
-    if (a->data == nullptr) die("out of memory");
-}
-
-static int cb_display(Action* a, int status, const char* resp) {
+static int cb_display(Action& a, int status, const char* resp) {
     if (status) {
-        fprintf(stderr, "%s FAILED (%s)\n", a->cmd, resp);
+        fprintf(stderr, "%s FAILED (%s)\n", a.cmd.c_str(), resp);
         return status;
     }
-    fprintf(stderr, "%s: %s\n", static_cast<const char*>(a->data), resp);
-    free(static_cast<char*>(a->data));
+    fprintf(stderr, "%s: %s\n", static_cast<const char*>(a.data), resp);
+    free(static_cast<char*>(a.data));
     return 0;
 }
 
-void fb_queue_display(const char* var, const char* prettyname) {
-    Action* a = queue_action(OP_QUERY, "getvar:%s", var);
-    a->data = xstrdup(prettyname);
-    a->func = cb_display;
+void fb_queue_display(const std::string& label, const std::string& var) {
+    Action& a = queue_action(OP_QUERY, "getvar:" + var);
+    a.data = xstrdup(label.c_str());
+    a.func = cb_display;
 }
 
-static int cb_save(Action* a, int status, const char* resp) {
+static int cb_save(Action& a, int status, const char* resp) {
     if (status) {
-        fprintf(stderr, "%s FAILED (%s)\n", a->cmd, resp);
+        fprintf(stderr, "%s FAILED (%s)\n", a.cmd.c_str(), resp);
         return status;
     }
-    strncpy(reinterpret_cast<char*>(a->data), resp, a->size);
+    strncpy(reinterpret_cast<char*>(a.data), resp, a.size);
     return 0;
 }
 
-void fb_queue_query_save(const char* var, char* dest, uint32_t dest_size) {
-    Action* a = queue_action(OP_QUERY, "getvar:%s", var);
-    a->data = dest;
-    a->size = dest_size;
-    a->func = cb_save;
+void fb_queue_query_save(const std::string& var, char* dest, uint32_t dest_size) {
+    Action& a = queue_action(OP_QUERY, "getvar:" + var);
+    a.data = dest;
+    a.size = dest_size;
+    a.func = cb_save;
 }
 
-static int cb_do_nothing(Action*, int , const char*) {
-    fprintf(stderr,"\n");
+static int cb_do_nothing(Action&, int, const char*) {
+    fprintf(stderr, "\n");
     return 0;
 }
 
-void fb_queue_reboot(void)
-{
-    Action *a = queue_action(OP_COMMAND, "reboot");
-    a->func = cb_do_nothing;
-    a->msg = "rebooting";
+void fb_queue_reboot() {
+    Action& a = queue_action(OP_COMMAND, "reboot");
+    a.func = cb_do_nothing;
+    a.msg = "Rebooting...";
 }
 
-void fb_queue_command(const char *cmd, const char *msg)
-{
-    Action *a = queue_action(OP_COMMAND, cmd);
-    a->msg = msg;
+void fb_queue_command(const std::string& cmd, const std::string& msg) {
+    Action& a = queue_action(OP_COMMAND, cmd);
+    a.msg = msg;
 }
 
-void fb_queue_download(const char *name, void *data, uint32_t size)
-{
-    Action *a = queue_action(OP_DOWNLOAD, "");
-    a->data = data;
-    a->size = size;
-    a->msg = mkmsg("downloading '%s'", name);
+void fb_queue_download(const std::string& name, void* data, uint32_t size) {
+    Action& a = queue_action(OP_DOWNLOAD, "");
+    a.data = data;
+    a.size = size;
+    a.msg = "Downloading '" + name + "'";
 }
 
-void fb_queue_download_fd(const char *name, int fd, uint32_t sz)
-{
-    Action *a;
-    a = queue_action(OP_DOWNLOAD_FD, "");
-    a->fd = fd;
-    a->size = sz;
-    a->msg = mkmsg("sending '%s' (%d KB)", name, sz / 1024);
+void fb_queue_download_fd(const std::string& name, int fd, uint32_t sz) {
+    Action& a = queue_action(OP_DOWNLOAD_FD, "");
+    a.fd = fd;
+    a.size = sz;
+    a.msg = android::base::StringPrintf("Sending '%s' (%d KB)", name.c_str(), sz / 1024);
 }
 
-void fb_queue_upload(const char* outfile) {
-    Action* a = queue_action(OP_UPLOAD, "");
-    a->data = xstrdup(outfile);
-    a->msg = mkmsg("uploading '%s'", outfile);
+void fb_queue_upload(const std::string& outfile) {
+    Action& a = queue_action(OP_UPLOAD, "");
+    a.data = xstrdup(outfile.c_str());
+    a.msg = "Uploading '" + outfile + "'";
 }
 
-void fb_queue_notice(const char* notice) {
-    Action *a = queue_action(OP_NOTICE, "");
-    a->data = (void*) notice;
+void fb_queue_notice(const std::string& notice) {
+    Action& a = queue_action(OP_NOTICE, "");
+    a.msg = notice;
 }
 
-void fb_queue_wait_for_disconnect(void)
-{
+void fb_queue_wait_for_disconnect() {
     queue_action(OP_WAIT_FOR_DISCONNECT, "");
 }
 
-int64_t fb_execute_queue(Transport* transport)
-{
-    Action *a;
-    char resp[FB_RESPONSE_SZ+1];
+int64_t fb_execute_queue(Transport* transport) {
     int64_t status = 0;
-
-    a = action_list;
-    if (!a)
-        return status;
-    resp[FB_RESPONSE_SZ] = 0;
-
-    double start = -1;
-    for (a = action_list; a; a = a->next) {
+    for (auto& a : action_list) {
         a->start = now();
-        if (start < 0) start = a->start;
-        if (a->msg) {
-            // fprintf(stderr,"%30s... ",a->msg);
-            fprintf(stderr,"%s...\n",a->msg);
+        if (!a->msg.empty()) {
+            fprintf(stderr, "%s\n", a->msg.c_str());
         }
         if (a->op == OP_DOWNLOAD) {
             status = fb_download_data(transport, a->data, a->size);
-            status = a->func(a, status, status ? fb_get_error().c_str() : "");
+            status = a->func(*a, status, status ? fb_get_error().c_str() : "");
             if (status) break;
         } else if (a->op == OP_DOWNLOAD_FD) {
             status = fb_download_data_fd(transport, a->fd, a->size);
-            status = a->func(a, status, status ? fb_get_error().c_str() : "");
+            status = a->func(*a, status, status ? fb_get_error().c_str() : "");
             if (status) break;
         } else if (a->op == OP_COMMAND) {
             status = fb_command(transport, a->cmd);
-            status = a->func(a, status, status ? fb_get_error().c_str() : "");
+            status = a->func(*a, status, status ? fb_get_error().c_str() : "");
             if (status) break;
         } else if (a->op == OP_QUERY) {
+            char resp[FB_RESPONSE_SZ + 1] = {};
             status = fb_command_response(transport, a->cmd, resp);
-            status = a->func(a, status, status ? fb_get_error().c_str() : resp);
+            status = a->func(*a, status, status ? fb_get_error().c_str() : resp);
             if (status) break;
         } else if (a->op == OP_NOTICE) {
-            fprintf(stderr,"%s\n",(char*)a->data);
+            // We already showed the notice because it's in `Action::msg`.
         } else if (a->op == OP_DOWNLOAD_SPARSE) {
             status = fb_download_data_sparse(transport, reinterpret_cast<sparse_file*>(a->data));
-            status = a->func(a, status, status ? fb_get_error().c_str() : "");
+            status = a->func(*a, status, status ? fb_get_error().c_str() : "");
             if (status) break;
         } else if (a->op == OP_WAIT_FOR_DISCONNECT) {
             transport->WaitForDisconnect();
         } else if (a->op == OP_UPLOAD) {
             status = fb_upload_data(transport, reinterpret_cast<char*>(a->data));
-            status = a->func(a, status, status ? fb_get_error().c_str() : "");
+            status = a->func(*a, status, status ? fb_get_error().c_str() : "");
         } else {
-            die("bogus action");
+            die("unknown action: %d", a->op);
         }
     }
-
-    fprintf(stderr,"finished. total time: %.3fs\n", (now() - start));
+    action_list.clear();
     return status;
 }
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 6b01aae..237f081 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -123,6 +123,7 @@
     { nullptr,    "boot_other.img",   "boot.sig",     "boot",     true,  true  },
     { "dtbo",     "dtbo.img",         "dtbo.sig",     "dtbo",     true,  false },
     { "dts",      "dt.img",           "dt.sig",       "dts",      true,  false },
+    { "odm",      "odm.img",          "odm.sig",      "odm",      true,  false },
     { "product",  "product.img",      "product.sig",  "product",  true,  false },
     { "recovery", "recovery.img",     "recovery.sig", "recovery", true,  false },
     { "system",   "system.img",       "system.sig",   "system",   false, false },
@@ -634,27 +635,31 @@
     return fd.release();
 }
 
-static char *strip(char *s)
-{
-    int n;
-    while(*s && isspace(*s)) s++;
-    n = strlen(s);
-    while(n-- > 0) {
-        if(!isspace(s[n])) break;
+static char* strip(char* s) {
+    while (*s && isspace(*s)) s++;
+
+    int n = strlen(s);
+    while (n-- > 0) {
+        if (!isspace(s[n])) break;
         s[n] = 0;
     }
     return s;
 }
 
 #define MAX_OPTIONS 32
-static int setup_requirement_line(char *name)
-{
+static void check_requirement(Transport* transport, char* line) {
     char *val[MAX_OPTIONS];
-    char *prod = nullptr;
-    unsigned n, count;
+    unsigned count;
     char *x;
     int invert = 0;
 
+    // "require product=alpha|beta|gamma"
+    // "require version-bootloader=1234"
+    // "require-for-product:gamma version-bootloader=istanbul|constantinople"
+    // "require partition-exists=vendor"
+
+    char* name = line;
+    const char* product = "";
     if (!strncmp(name, "reject ", 7)) {
         name += 7;
         invert = 1;
@@ -663,19 +668,46 @@
         invert = 0;
     } else if (!strncmp(name, "require-for-product:", 20)) {
         // Get the product and point name past it
-        prod = name + 20;
+        product = name + 20;
         name = strchr(name, ' ');
-        if (!name) return -1;
+        if (!name) die("android-info.txt syntax error: %s", line);
         *name = 0;
         name += 1;
         invert = 0;
     }
 
     x = strchr(name, '=');
-    if (x == 0) return 0;
+    if (x == 0) return;
     *x = 0;
     val[0] = x + 1;
 
+    name = strip(name);
+
+    // "require partition-exists=x" is a special case, added because of the trouble we had when
+    // Pixel 2 shipped with new partitions and users used old versions of fastboot to flash them,
+    // missing out new partitions. A device with new partitions can use "partition-exists" to
+    // override the `is_optional` field in the `images` array.
+    if (!strcmp(name, "partition-exists")) {
+        const char* partition_name = val[0];
+        std::string has_slot;
+        if (!fb_getvar(transport, std::string("has-slot:") + partition_name, &has_slot) ||
+            (has_slot != "yes" && has_slot != "no")) {
+            die("device doesn't have required partition %s!", partition_name);
+        }
+        bool known_partition = false;
+        for (size_t i = 0; i < arraysize(images); ++i) {
+            if (images[i].nickname && !strcmp(images[i].nickname, partition_name)) {
+                images[i].is_optional = false;
+                known_partition = true;
+            }
+        }
+        if (!known_partition) {
+            die("device requires partition %s which is not known to this version of fastboot",
+                partition_name);
+        }
+        return;
+    }
+
     for(count = 1; count < MAX_OPTIONS; count++) {
         x = strchr(val[count - 1],'|');
         if (x == 0) break;
@@ -683,54 +715,39 @@
         val[count] = x + 1;
     }
 
-    name = strip(name);
-    for(n = 0; n < count; n++) val[n] = strip(val[n]);
-
-    name = strip(name);
-    if (name == 0) return -1;
-
-    const char* var = name;
     // Work around an unfortunate name mismatch.
-    if (!strcmp(name,"board")) var = "product";
+    const char* var = name;
+    if (!strcmp(name, "board")) var = "product";
 
     const char** out = reinterpret_cast<const char**>(malloc(sizeof(char*) * count));
-    if (out == 0) return -1;
+    if (out == nullptr) die("out of memory");
 
-    for(n = 0; n < count; n++) {
-        out[n] = strdup(strip(val[n]));
-        if (out[n] == 0) {
-            for(size_t i = 0; i < n; ++i) {
-                free((char*) out[i]);
-            }
-            free(out);
-            return -1;
-        }
+    for (size_t i = 0; i < count; ++i) {
+        out[i] = xstrdup(strip(val[i]));
     }
 
-    fb_queue_require(prod, var, invert, n, out);
-    return 0;
+    fb_queue_require(product, var, invert, count, out);
 }
 
-static void setup_requirements(char* data, int64_t sz) {
+static void check_requirements(Transport* transport, char* data, int64_t sz) {
     char* s = data;
     while (sz-- > 0) {
         if (*s == '\n') {
             *s++ = 0;
-            if (setup_requirement_line(data)) {
-                die("out of memory");
-            }
+            check_requirement(transport, data);
             data = s;
         } else {
             s++;
         }
     }
+    if (fb_execute_queue(transport)) die("requirements not met!");
 }
 
 static void queue_info_dump() {
     fb_queue_notice("--------------------------------------------");
-    fb_queue_display("version-bootloader", "Bootloader Version...");
-    fb_queue_display("version-baseband",   "Baseband Version.....");
-    fb_queue_display("serialno",           "Serial Number........");
+    fb_queue_display("Bootloader Version...", "version-bootloader");
+    fb_queue_display("Baseband Version.....", "version-baseband");
+    fb_queue_display("Serial Number........", "serialno");
     fb_queue_notice("--------------------------------------------");
 }
 
@@ -890,14 +907,13 @@
     lseek(fd, 0, SEEK_SET);
 }
 
-static void flash_buf(const char *pname, struct fastboot_buffer *buf)
+static void flash_buf(const std::string& partition, struct fastboot_buffer *buf)
 {
     sparse_file** s;
 
     // Rewrite vbmeta if that's what we're flashing and modification has been requested.
     if ((g_disable_verity || g_disable_verification) &&
-        (strcmp(pname, "vbmeta") == 0 || strcmp(pname, "vbmeta_a") == 0 ||
-         strcmp(pname, "vbmeta_b") == 0)) {
+        (partition == "vbmeta" || partition == "vbmeta_a" || partition == "vbmeta_b")) {
         rewrite_vbmeta_buffer(buf);
     }
 
@@ -913,12 +929,12 @@
 
             for (size_t i = 0; i < sparse_files.size(); ++i) {
                 const auto& pair = sparse_files[i];
-                fb_queue_flash_sparse(pname, pair.first, pair.second, i + 1, sparse_files.size());
+                fb_queue_flash_sparse(partition, pair.first, pair.second, i + 1, sparse_files.size());
             }
             break;
         }
         case FB_BUFFER_FD:
-            fb_queue_flash_fd(pname, buf->fd, buf->sz);
+            fb_queue_flash_fd(partition, buf->fd, buf->sz);
             break;
         default:
             die("unknown buffer type: %d", buf->type);
@@ -1116,11 +1132,11 @@
         }
     }
     if (slot_override != "") {
-        fb_set_active((separator + slot_override).c_str());
+        fb_set_active(separator + slot_override);
     } else {
         std::string current_slot = get_current_slot(transport);
         if (current_slot != "") {
-            fb_set_active((separator + current_slot).c_str());
+            fb_set_active(separator + current_slot);
         }
     }
 }
@@ -1142,7 +1158,7 @@
         die("update package '%s' has no android-info.txt", filename);
     }
 
-    setup_requirements(reinterpret_cast<char*>(data), sz);
+    check_requirements(transport, reinterpret_cast<char*>(data), sz);
 
     std::string secondary;
     if (!skip_secondary) {
@@ -1184,7 +1200,7 @@
         auto update = [&](const std::string& partition) {
             do_update_signature(zip, images[i].sig_name);
             if (erase_first && needs_erase(transport, partition.c_str())) {
-                fb_queue_erase(partition.c_str());
+                fb_queue_erase(partition);
             }
             flash_buf(partition.c_str(), &buf);
             /* not closing the fd here since the sparse code keeps the fd around
@@ -1231,7 +1247,7 @@
     void* data = load_file(fname.c_str(), &sz);
     if (data == nullptr) die("could not load android-info.txt: %s", strerror(errno));
 
-    setup_requirements(reinterpret_cast<char*>(data), sz);
+    check_requirements(transport, reinterpret_cast<char*>(data), sz);
 
     std::string secondary;
     if (!skip_secondary) {
@@ -1266,7 +1282,7 @@
         auto flashall = [&](const std::string &partition) {
             do_send_signature(fname.c_str());
             if (erase_first && needs_erase(transport, partition.c_str())) {
-                fb_queue_erase(partition.c_str());
+                fb_queue_erase(partition);
             }
             flash_buf(partition.c_str(), &buf);
         };
@@ -1306,7 +1322,7 @@
     while (!args->empty()) {
         command += " " + next_arg(args);
     }
-    fb_queue_command(command.c_str(), "");
+    fb_queue_command(command, "");
 }
 
 static int64_t parse_num(const char *arg)
@@ -1361,8 +1377,8 @@
 
 static unsigned fb_get_flash_block_size(Transport* transport, std::string name) {
     std::string sizeString;
-    if (!fb_getvar(transport, name.c_str(), &sizeString) || sizeString.empty()) {
-        /* This device does not report flash block sizes, so return 0 */
+    if (!fb_getvar(transport, name, &sizeString) || sizeString.empty()) {
+        // This device does not report flash block sizes, so return 0.
         return 0;
     }
     sizeString = fb_fix_numeric_var(sizeString);
@@ -1380,7 +1396,7 @@
 }
 
 static void fb_perform_format(Transport* transport,
-                              const char* partition, int skip_if_not_supported,
+                              const std::string& partition, int skip_if_not_supported,
                               const std::string& type_override, const std::string& size_override,
                               const std::string& initial_dir) {
     std::string partition_type, partition_size;
@@ -1399,26 +1415,26 @@
         limit = sparse_limit;
     }
 
-    if (!fb_getvar(transport, std::string("partition-type:") + partition, &partition_type)) {
+    if (!fb_getvar(transport, "partition-type:" + partition, &partition_type)) {
         errMsg = "Can't determine partition type.\n";
         goto failed;
     }
     if (!type_override.empty()) {
         if (partition_type != type_override) {
             fprintf(stderr, "Warning: %s type is %s, but %s was requested for formatting.\n",
-                    partition, partition_type.c_str(), type_override.c_str());
+                    partition.c_str(), partition_type.c_str(), type_override.c_str());
         }
         partition_type = type_override;
     }
 
-    if (!fb_getvar(transport, std::string("partition-size:") + partition, &partition_size)) {
+    if (!fb_getvar(transport, "partition-size:" + partition, &partition_size)) {
         errMsg = "Unable to get partition size\n";
         goto failed;
     }
     if (!size_override.empty()) {
         if (partition_size != size_override) {
             fprintf(stderr, "Warning: %s size is %s, but %s was requested for formatting.\n",
-                    partition, partition_size.c_str(), size_override.c_str());
+                    partition.c_str(), partition_size.c_str(), size_override.c_str());
         }
         partition_size = size_override;
     }
@@ -1448,7 +1464,7 @@
 
     if (fs_generator_generate(gen, output.path, size, initial_dir,
             eraseBlkSize, logicalBlkSize)) {
-        die("Cannot generate image for %s", partition);
+        die("Cannot generate image for %s", partition.c_str());
         return;
     }
 
@@ -1631,6 +1647,8 @@
         return 1;
     }
 
+    const double start = now();
+
     if (!supports_AB(transport) && supports_AB_obsolete(transport)) {
         fprintf(stderr, "Warning: Device A/B support is outdated. Bootloader update required.\n");
     }
@@ -1658,7 +1676,7 @@
 
         if (command == "getvar") {
             std::string variable = next_arg(&args);
-            fb_queue_display(variable.c_str(), variable.c_str());
+            fb_queue_display(variable, variable);
         } else if (command == "erase") {
             std::string partition = next_arg(&args);
             auto erase = [&](const std::string& partition) {
@@ -1670,7 +1688,7 @@
                             partition_type.c_str());
                 }
 
-                fb_queue_erase(partition.c_str());
+                fb_queue_erase(partition);
             };
             do_for_partitions(transport, partition, slot_override, erase, true);
         } else if (android::base::StartsWith(command, "format")) {
@@ -1691,10 +1709,9 @@
 
             auto format = [&](const std::string& partition) {
                 if (erase_first && needs_erase(transport, partition.c_str())) {
-                    fb_queue_erase(partition.c_str());
+                    fb_queue_erase(partition);
                 }
-                fb_perform_format(transport, partition.c_str(), 0, type_override, size_override,
-                                  "");
+                fb_perform_format(transport, partition, 0, type_override, size_override, "");
             };
             do_for_partitions(transport, partition.c_str(), slot_override, format, true);
         } else if (command == "signature") {
@@ -1748,7 +1765,7 @@
 
             auto flash = [&](const std::string &partition) {
                 if (erase_first && needs_erase(transport, partition.c_str())) {
-                    fb_queue_erase(partition.c_str());
+                    fb_queue_erase(partition);
                 }
                 do_flash(transport, partition.c_str(), fname.c_str());
             };
@@ -1763,7 +1780,7 @@
 
             data = load_bootable_image(kernel, ramdisk, second_stage, &sz, cmdline);
             auto flashraw = [&](const std::string& partition) {
-                fb_queue_flash(partition.c_str(), data, sz);
+                fb_queue_flash(partition, data, sz);
             };
             do_for_partitions(transport, partition, slot_override, flashraw, true);
         } else if (command == "flashall") {
@@ -1797,7 +1814,7 @@
                     fb_getvar(transport, "slot-suffixes", &var)) {
                 slot = "_" + slot;
             }
-            fb_set_active(slot.c_str());
+            fb_set_active(slot);
         } else if (command == "stage") {
             std::string filename = next_arg(&args);
 
@@ -1805,10 +1822,10 @@
             if (!load_buf(transport, filename.c_str(), &buf) || buf.type != FB_BUFFER_FD) {
                 die("cannot load '%s'", filename.c_str());
             }
-            fb_queue_download_fd(filename.c_str(), buf.fd, buf.sz);
+            fb_queue_download_fd(filename, buf.fd, buf.sz);
         } else if (command == "get_staged") {
             std::string filename = next_arg(&args);
-            fb_queue_upload(filename.c_str());
+            fb_queue_upload(filename);
         } else if (command == "oem") {
             do_oem_command("oem", &args);
         } else if (command == "flashing") {
@@ -1854,7 +1871,7 @@
         }
     }
     if (wants_set_active) {
-        fb_set_active(next_active.c_str());
+        fb_set_active(next_active);
     }
     if (wants_reboot && !skip_reboot) {
         fb_queue_reboot();
@@ -1867,5 +1884,7 @@
         fb_queue_wait_for_disconnect();
     }
 
-    return fb_execute_queue(transport) ? EXIT_FAILURE : EXIT_SUCCESS;
+    int status = fb_execute_queue(transport) ? EXIT_FAILURE : EXIT_SUCCESS;
+    fprintf(stderr, "Finished. Total time: %.3fs\n", (now() - start));
+    return status;
 }
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index f4faa21..a31057a 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -39,8 +39,8 @@
 struct sparse_file;
 
 /* protocol.c - fastboot protocol */
-int fb_command(Transport* transport, const char* cmd);
-int fb_command_response(Transport* transport, const char* cmd, char* response);
+int fb_command(Transport* transport, const std::string& cmd);
+int fb_command_response(Transport* transport, const std::string& cmd, char* response);
 int64_t fb_download_data(Transport* transport, const void* data, uint32_t size);
 int64_t fb_download_data_fd(Transport* transport, int fd, uint32_t size);
 int fb_download_data_sparse(Transport* transport, struct sparse_file* s);
@@ -52,29 +52,29 @@
 
 /* engine.c - high level command queue engine */
 bool fb_getvar(Transport* transport, const std::string& key, std::string* value);
-void fb_queue_flash(const char *ptn, void *data, uint32_t sz);
-void fb_queue_flash_fd(const char *ptn, int fd, uint32_t sz);
-void fb_queue_flash_sparse(const char* ptn, struct sparse_file* s, uint32_t sz, size_t current,
-                           size_t total);
-void fb_queue_erase(const char *ptn);
-void fb_queue_format(const char *ptn, int skip_if_not_supported, int32_t max_chunk_sz);
-void fb_queue_require(const char *prod, const char *var, bool invert,
-                      size_t nvalues, const char **value);
-void fb_queue_display(const char *var, const char *prettyname);
-void fb_queue_query_save(const char *var, char *dest, uint32_t dest_size);
+void fb_queue_flash(const std::string& partition, void* data, uint32_t sz);
+void fb_queue_flash_fd(const std::string& partition, int fd, uint32_t sz);
+void fb_queue_flash_sparse(const std::string& partition, struct sparse_file* s, uint32_t sz,
+                           size_t current, size_t total);
+void fb_queue_erase(const std::string& partition);
+void fb_queue_format(const std::string& partition, int skip_if_not_supported, int32_t max_chunk_sz);
+void fb_queue_require(const std::string& prod, const std::string& var, bool invert, size_t nvalues,
+                      const char** values);
+void fb_queue_display(const std::string& label, const std::string& var);
+void fb_queue_query_save(const std::string& var, char* dest, uint32_t dest_size);
 void fb_queue_reboot(void);
-void fb_queue_command(const char *cmd, const char *msg);
-void fb_queue_download(const char *name, void *data, uint32_t size);
-void fb_queue_download_fd(const char *name, int fd, uint32_t sz);
-void fb_queue_upload(const char* outfile);
-void fb_queue_notice(const char *notice);
+void fb_queue_command(const std::string& cmd, const std::string& msg);
+void fb_queue_download(const std::string& name, void* data, uint32_t size);
+void fb_queue_download_fd(const std::string& name, int fd, uint32_t sz);
+void fb_queue_upload(const std::string& outfile);
+void fb_queue_notice(const std::string& notice);
 void fb_queue_wait_for_disconnect(void);
 int64_t fb_execute_queue(Transport* transport);
-void fb_set_active(const char *slot);
+void fb_set_active(const std::string& slot);
 
 /* util stuff */
 double now();
-char *mkmsg(const char *fmt, ...);
+char* xstrdup(const char*);
 
 // These printf-like functions are implemented in terms of vsnprintf, so they
 // use the same attribute for compile-time format string checking. On Windows,
diff --git a/fastboot/protocol.cpp b/fastboot/protocol.cpp
index dcdf8f0..c239861 100644
--- a/fastboot/protocol.cpp
+++ b/fastboot/protocol.cpp
@@ -113,10 +113,10 @@
     return -1;
 }
 
-static int64_t _command_start(Transport* transport, const char* cmd, uint32_t size, char* response) {
-    size_t cmdsize = strlen(cmd);
-    if (cmdsize > 64) {
-        g_error = android::base::StringPrintf("command too large (%zu)", cmdsize);
+static int64_t _command_start(Transport* transport, const std::string& cmd, uint32_t size,
+                              char* response) {
+    if (cmd.size() > 64) {
+        g_error = android::base::StringPrintf("command too large (%zu)", cmd.size());
         return -1;
     }
 
@@ -124,7 +124,7 @@
         response[0] = 0;
     }
 
-    if (transport->Write(cmd, cmdsize) != static_cast<int>(cmdsize)) {
+    if (transport->Write(cmd.c_str(), cmd.size()) != static_cast<int>(cmd.size())) {
         g_error = android::base::StringPrintf("command write failed (%s)", strerror(errno));
         transport->Close();
         return -1;
@@ -167,8 +167,8 @@
     return check_response(transport, 0, 0) < 0 ? -1 : 0;
 }
 
-static int64_t _command_send(Transport* transport, const char* cmd, const void* data, uint32_t size,
-                         char* response) {
+static int64_t _command_send(Transport* transport, const std::string& cmd, const void* data,
+                             uint32_t size, char* response) {
     if (size == 0) {
         return -1;
     }
@@ -190,7 +190,7 @@
     return size;
 }
 
-static int64_t _command_send_fd(Transport* transport, const char* cmd, int fd, uint32_t size,
+static int64_t _command_send_fd(Transport* transport, const std::string& cmd, int fd, uint32_t size,
                                 char* response) {
     static constexpr uint32_t MAX_MAP_SIZE = 512 * 1024 * 1024;
     off64_t offset = 0;
@@ -223,15 +223,15 @@
     return size;
 }
 
-static int _command_send_no_data(Transport* transport, const char* cmd, char* response) {
+static int _command_send_no_data(Transport* transport, const std::string& cmd, char* response) {
     return _command_start(transport, cmd, 0, response);
 }
 
-int fb_command(Transport* transport, const char* cmd) {
+int fb_command(Transport* transport, const std::string& cmd) {
     return _command_send_no_data(transport, cmd, 0);
 }
 
-int fb_command_response(Transport* transport, const char* cmd, char* response) {
+int fb_command_response(Transport* transport, const std::string& cmd, char* response) {
     return _command_send_no_data(transport, cmd, response);
 }
 
@@ -339,7 +339,7 @@
     }
 
     std::string cmd(android::base::StringPrintf("download:%08x", size));
-    int r = _command_start(transport, cmd.c_str(), size, 0);
+    int r = _command_start(transport, cmd, size, 0);
     if (r < 0) {
         return -1;
     }
diff --git a/fastboot/util.cpp b/fastboot/util.cpp
index f2bbd34..fb085e7 100644
--- a/fastboot/util.cpp
+++ b/fastboot/util.cpp
@@ -35,35 +35,24 @@
 
 #include "fastboot.h"
 
-double now()
-{
+double now() {
     struct timeval tv;
     gettimeofday(&tv, NULL);
     return (double)tv.tv_sec + (double)tv.tv_usec / 1000000;
 }
 
-char *mkmsg(const char *fmt, ...)
-{
-    char buf[256];
-    char *s;
-    va_list ap;
-
-    va_start(ap, fmt);
-    vsprintf(buf, fmt, ap);
-    va_end(ap);
-
-    s = strdup(buf);
-    if (s == 0) die("out of memory");
-    return s;
-}
-
-void die(const char *fmt, ...)
-{
+void die(const char* fmt, ...) {
     va_list ap;
     va_start(ap, fmt);
     fprintf(stderr,"error: ");
     vfprintf(stderr, fmt, ap);
     fprintf(stderr,"\n");
     va_end(ap);
-    exit(1);
+    exit(EXIT_FAILURE);
+}
+
+char* xstrdup(const char* s) {
+    char* result = strdup(s);
+    if (!result) die("out of memory");
+    return result;
 }
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 9b7405a..9d504d6 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -114,17 +114,21 @@
 #define EM_ICE          2
 #define EM_AES_256_CTS  3
 #define EM_AES_256_HEH  4
+#define EM_SPECK_128_256_XTS 5
+#define EM_SPECK_128_256_CTS 6
 
 static const struct flag_list file_contents_encryption_modes[] = {
     {"aes-256-xts", EM_AES_256_XTS},
+    {"speck128/256-xts", EM_SPECK_128_256_XTS},
     {"software", EM_AES_256_XTS}, /* alias for backwards compatibility */
-    {"ice", EM_ICE}, /* hardware-specific inline cryptographic engine */
+    {"ice", EM_ICE},              /* hardware-specific inline cryptographic engine */
     {0, 0},
 };
 
 static const struct flag_list file_names_encryption_modes[] = {
     {"aes-256-cts", EM_AES_256_CTS},
     {"aes-256-heh", EM_AES_256_HEH},
+    {"speck128/256-cts", EM_SPECK_128_256_CTS},
     {0, 0},
 };
 
diff --git a/init/stable_properties.h b/init/stable_properties.h
index 4d47434..b82f079 100644
--- a/init/stable_properties.h
+++ b/init/stable_properties.h
@@ -36,9 +36,12 @@
     "persist.sys.crash_rcu",
     "persist.sys.usb.usbradio.config",
     "persist.sys.zram_enabled",
+    "ro.board.platform",
     "ro.bootmode",
     "ro.build.type",
+    "ro.debuggable",
     "sys.boot_completed",
+    "sys.boot_from_charger_mode",
     "sys.retaildemo.enabled",
     "sys.shutdown.requested",
     "sys.usb.config",
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 10050eb..f6f7128 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -186,16 +186,11 @@
     { 00755, AID_SYSTEM,    AID_GRAPHICS,  CAP_MASK_LONG(CAP_SYS_NICE),
                                               "system/bin/surfaceflinger" },
 
-    // Support hostapd administering a network interface (Old Path: <= O-MR1 release).
+    // Support hostapd administering a network interface.
     { 00755, AID_WIFI,      AID_WIFI,      CAP_MASK_LONG(CAP_NET_ADMIN) |
                                            CAP_MASK_LONG(CAP_NET_RAW),
                                               "vendor/bin/hostapd" },
 
-    // Support hostapd administering a network interface (New Path: >= P release).
-    { 00750, AID_WIFI,      AID_WIFI,      CAP_MASK_LONG(CAP_NET_ADMIN) |
-                                           CAP_MASK_LONG(CAP_NET_RAW),
-                                              "vendor/bin/hw/hostapd" },
-
     // Support Bluetooth legacy hal accessing /sys/class/rfkill
     // Support RT scheduling in Bluetooth
     { 00700, AID_BLUETOOTH, AID_BLUETOOTH, CAP_MASK_LONG(CAP_NET_ADMIN) |
diff --git a/libunwindstack/DwarfCfa.cpp b/libunwindstack/DwarfCfa.cpp
index aa8cd3a..6ecedce 100644
--- a/libunwindstack/DwarfCfa.cpp
+++ b/libunwindstack/DwarfCfa.cpp
@@ -50,7 +50,17 @@
   memory_->set_cur_offset(start_offset);
   uint64_t cfa_offset;
   cur_pc_ = fde_->pc_start;
-  while ((cfa_offset = memory_->cur_offset()) < end_offset && cur_pc_ <= pc) {
+  loc_regs->pc_start = cur_pc_;
+  while (true) {
+    if (cur_pc_ > pc) {
+      loc_regs->pc_end = cur_pc_;
+      return true;
+    }
+    if ((cfa_offset = memory_->cur_offset()) >= end_offset) {
+      loc_regs->pc_end = fde_->pc_end;
+      return true;
+    }
+    loc_regs->pc_start = cur_pc_;
     operands_.clear();
     // Read the cfa information.
     uint8_t cfa_value;
@@ -129,7 +139,6 @@
       }
     }
   }
-  return true;
 }
 
 template <typename AddressType>
diff --git a/libunwindstack/DwarfSection.cpp b/libunwindstack/DwarfSection.cpp
index 5586e72..65eec65 100644
--- a/libunwindstack/DwarfSection.cpp
+++ b/libunwindstack/DwarfSection.cpp
@@ -55,21 +55,29 @@
 }
 
 bool DwarfSection::Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) {
-  last_error_.code = DWARF_ERROR_NONE;
-  const DwarfFde* fde = GetFdeFromPc(pc);
-  if (fde == nullptr || fde->cie == nullptr) {
-    last_error_.code = DWARF_ERROR_ILLEGAL_STATE;
-    return false;
-  }
+  // Lookup the pc in the cache.
+  auto it = loc_regs_.upper_bound(pc);
+  if (it == loc_regs_.end() || pc < it->second.pc_start) {
+    last_error_.code = DWARF_ERROR_NONE;
+    const DwarfFde* fde = GetFdeFromPc(pc);
+    if (fde == nullptr || fde->cie == nullptr) {
+      last_error_.code = DWARF_ERROR_ILLEGAL_STATE;
+      return false;
+    }
 
-  // Now get the location information for this pc.
-  dwarf_loc_regs_t loc_regs;
-  if (!GetCfaLocationInfo(pc, fde, &loc_regs)) {
-    return false;
+    // Now get the location information for this pc.
+    dwarf_loc_regs_t loc_regs;
+    if (!GetCfaLocationInfo(pc, fde, &loc_regs)) {
+      return false;
+    }
+    loc_regs.cie = fde->cie;
+
+    // Store it in the cache.
+    it = loc_regs_.emplace(loc_regs.pc_end, std::move(loc_regs)).first;
   }
 
   // Now eval the actual registers.
-  return Eval(fde->cie, process_memory, loc_regs, regs, finished);
+  return Eval(it->second.cie, process_memory, it->second, regs, finished);
 }
 
 template <typename AddressType>
diff --git a/libunwindstack/RegsArm.cpp b/libunwindstack/RegsArm.cpp
index e2a9cb0..27cab43 100644
--- a/libunwindstack/RegsArm.cpp
+++ b/libunwindstack/RegsArm.cpp
@@ -51,13 +51,23 @@
 }
 
 uint64_t RegsArm::GetPcAdjustment(uint64_t rel_pc, Elf* elf) {
+  if (!elf->valid()) {
+    return 2;
+  }
+
   uint64_t load_bias = elf->GetLoadBias();
   if (rel_pc < load_bias) {
-    return 0;
+    if (rel_pc < 2) {
+      return 0;
+    }
+    return 2;
   }
   uint64_t adjusted_rel_pc = rel_pc - load_bias;
   if (adjusted_rel_pc < 5) {
-    return 0;
+    if (adjusted_rel_pc < 2) {
+      return 0;
+    }
+    return 2;
   }
 
   if (adjusted_rel_pc & 1) {
diff --git a/libunwindstack/RegsArm64.cpp b/libunwindstack/RegsArm64.cpp
index fe24c80..4a2a6c4 100644
--- a/libunwindstack/RegsArm64.cpp
+++ b/libunwindstack/RegsArm64.cpp
@@ -51,8 +51,8 @@
   regs_[ARM64_REG_SP] = sp;
 }
 
-uint64_t RegsArm64::GetPcAdjustment(uint64_t rel_pc, Elf* elf) {
-  if (!elf->valid() || rel_pc < 4) {
+uint64_t RegsArm64::GetPcAdjustment(uint64_t rel_pc, Elf*) {
+  if (rel_pc < 4) {
     return 0;
   }
   return 4;
diff --git a/libunwindstack/RegsMips.cpp b/libunwindstack/RegsMips.cpp
index 0b10e21..c87e69b 100644
--- a/libunwindstack/RegsMips.cpp
+++ b/libunwindstack/RegsMips.cpp
@@ -51,8 +51,8 @@
   regs_[MIPS_REG_SP] = static_cast<uint32_t>(sp);
 }
 
-uint64_t RegsMips::GetPcAdjustment(uint64_t rel_pc, Elf* elf) {
-  if (!elf->valid() || rel_pc < 8) {
+uint64_t RegsMips::GetPcAdjustment(uint64_t rel_pc, Elf*) {
+  if (rel_pc < 8) {
     return 0;
   }
   // For now, just assume no compact branches
diff --git a/libunwindstack/RegsMips64.cpp b/libunwindstack/RegsMips64.cpp
index 8848e3b..236a922 100644
--- a/libunwindstack/RegsMips64.cpp
+++ b/libunwindstack/RegsMips64.cpp
@@ -51,8 +51,8 @@
   regs_[MIPS64_REG_SP] = sp;
 }
 
-uint64_t RegsMips64::GetPcAdjustment(uint64_t rel_pc, Elf* elf) {
-  if (!elf->valid() || rel_pc < 8) {
+uint64_t RegsMips64::GetPcAdjustment(uint64_t rel_pc, Elf*) {
+  if (rel_pc < 8) {
     return 0;
   }
   // For now, just assume no compact branches
diff --git a/libunwindstack/RegsX86.cpp b/libunwindstack/RegsX86.cpp
index bb95a13..f7e0614 100644
--- a/libunwindstack/RegsX86.cpp
+++ b/libunwindstack/RegsX86.cpp
@@ -50,8 +50,8 @@
   regs_[X86_REG_SP] = static_cast<uint32_t>(sp);
 }
 
-uint64_t RegsX86::GetPcAdjustment(uint64_t rel_pc, Elf* elf) {
-  if (!elf->valid() || rel_pc == 0) {
+uint64_t RegsX86::GetPcAdjustment(uint64_t rel_pc, Elf*) {
+  if (rel_pc == 0) {
     return 0;
   }
   return 1;
diff --git a/libunwindstack/RegsX86_64.cpp b/libunwindstack/RegsX86_64.cpp
index e57e2bc..7d6ad86 100644
--- a/libunwindstack/RegsX86_64.cpp
+++ b/libunwindstack/RegsX86_64.cpp
@@ -50,8 +50,8 @@
   regs_[X86_64_REG_SP] = sp;
 }
 
-uint64_t RegsX86_64::GetPcAdjustment(uint64_t rel_pc, Elf* elf) {
-  if (!elf->valid() || rel_pc == 0) {
+uint64_t RegsX86_64::GetPcAdjustment(uint64_t rel_pc, Elf*) {
+  if (rel_pc == 0) {
     return 0;
   }
   return 1;
diff --git a/libunwindstack/include/unwindstack/DwarfLocation.h b/libunwindstack/include/unwindstack/DwarfLocation.h
index 0881182..3d50ccf 100644
--- a/libunwindstack/include/unwindstack/DwarfLocation.h
+++ b/libunwindstack/include/unwindstack/DwarfLocation.h
@@ -23,6 +23,8 @@
 
 namespace unwindstack {
 
+struct DwarfCie;
+
 enum DwarfLocationEnum : uint8_t {
   DWARF_LOCATION_INVALID = 0,
   DWARF_LOCATION_UNDEFINED,
@@ -38,7 +40,13 @@
   uint64_t values[2];
 };
 
-typedef std::unordered_map<uint32_t, DwarfLocation> dwarf_loc_regs_t;
+struct DwarfLocations : public std::unordered_map<uint32_t, DwarfLocation> {
+  const DwarfCie* cie;
+  // The range of PCs where the locations are valid (end is exclusive).
+  uint64_t pc_start = 0;
+  uint64_t pc_end = 0;
+};
+typedef DwarfLocations dwarf_loc_regs_t;
 
 }  // namespace unwindstack
 
diff --git a/libunwindstack/include/unwindstack/DwarfSection.h b/libunwindstack/include/unwindstack/DwarfSection.h
index da91fd0..209c54a 100644
--- a/libunwindstack/include/unwindstack/DwarfSection.h
+++ b/libunwindstack/include/unwindstack/DwarfSection.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 
 #include <iterator>
+#include <map>
 #include <unordered_map>
 
 #include <unwindstack/DwarfError.h>
@@ -112,6 +113,7 @@
   std::unordered_map<uint64_t, DwarfFde> fde_entries_;
   std::unordered_map<uint64_t, DwarfCie> cie_entries_;
   std::unordered_map<uint64_t, dwarf_loc_regs_t> cie_loc_regs_;
+  std::map<uint64_t, dwarf_loc_regs_t> loc_regs_;  // Single row indexed by pc_end.
 };
 
 template <typename AddressType>
diff --git a/libunwindstack/tests/DwarfSectionImplTest.cpp b/libunwindstack/tests/DwarfSectionImplTest.cpp
index c85764c..c340291 100644
--- a/libunwindstack/tests/DwarfSectionImplTest.cpp
+++ b/libunwindstack/tests/DwarfSectionImplTest.cpp
@@ -853,7 +853,8 @@
   fde.cfa_instructions_offset = 0x6000;
   fde.cfa_instructions_end = 0x6002;
 
-  dwarf_loc_regs_t cie_loc_regs{{6, {DWARF_LOCATION_REGISTER, {4, 0}}}};
+  dwarf_loc_regs_t cie_loc_regs;
+  cie_loc_regs[6] = DwarfLocation{DWARF_LOCATION_REGISTER, {4, 0}};
   this->section_->TestSetCachedCieLocRegs(0x8000, cie_loc_regs);
   this->memory_.SetMemory(0x6000, std::vector<uint8_t>{0x09, 0x04, 0x03});
 
diff --git a/libunwindstack/tests/DwarfSectionTest.cpp b/libunwindstack/tests/DwarfSectionTest.cpp
index 3fcd2b6..071d2df 100644
--- a/libunwindstack/tests/DwarfSectionTest.cpp
+++ b/libunwindstack/tests/DwarfSectionTest.cpp
@@ -165,4 +165,72 @@
   ASSERT_TRUE(mock_section.Step(0x1000, nullptr, &process, &finished));
 }
 
+static bool MockGetCfaLocationInfo(::testing::Unused, const DwarfFde* fde,
+                                   dwarf_loc_regs_t* loc_regs) {
+  loc_regs->pc_start = fde->pc_start;
+  loc_regs->pc_end = fde->pc_end;
+  return true;
+}
+
+TEST_F(DwarfSectionTest, Step_cache) {
+  MockDwarfSection mock_section(&memory_);
+
+  DwarfCie cie{};
+  DwarfFde fde{};
+  fde.pc_start = 0x500;
+  fde.pc_end = 0x2000;
+  fde.cie = &cie;
+
+  EXPECT_CALL(mock_section, GetFdeOffsetFromPc(0x1000, ::testing::_))
+      .WillOnce(::testing::Return(true));
+  EXPECT_CALL(mock_section, GetFdeFromOffset(::testing::_)).WillOnce(::testing::Return(&fde));
+
+  EXPECT_CALL(mock_section, GetCfaLocationInfo(0x1000, &fde, ::testing::_))
+      .WillOnce(::testing::Invoke(MockGetCfaLocationInfo));
+
+  MemoryFake process;
+  EXPECT_CALL(mock_section, Eval(&cie, &process, ::testing::_, nullptr, ::testing::_))
+      .WillRepeatedly(::testing::Return(true));
+
+  bool finished;
+  ASSERT_TRUE(mock_section.Step(0x1000, nullptr, &process, &finished));
+  ASSERT_TRUE(mock_section.Step(0x1000, nullptr, &process, &finished));
+  ASSERT_TRUE(mock_section.Step(0x1500, nullptr, &process, &finished));
+}
+
+TEST_F(DwarfSectionTest, Step_cache_not_in_pc) {
+  MockDwarfSection mock_section(&memory_);
+
+  DwarfCie cie{};
+  DwarfFde fde0{};
+  fde0.pc_start = 0x1000;
+  fde0.pc_end = 0x2000;
+  fde0.cie = &cie;
+  EXPECT_CALL(mock_section, GetFdeOffsetFromPc(0x1000, ::testing::_))
+      .WillOnce(::testing::Return(true));
+  EXPECT_CALL(mock_section, GetFdeFromOffset(::testing::_)).WillOnce(::testing::Return(&fde0));
+  EXPECT_CALL(mock_section, GetCfaLocationInfo(0x1000, &fde0, ::testing::_))
+      .WillOnce(::testing::Invoke(MockGetCfaLocationInfo));
+
+  MemoryFake process;
+  EXPECT_CALL(mock_section, Eval(&cie, &process, ::testing::_, nullptr, ::testing::_))
+      .WillRepeatedly(::testing::Return(true));
+
+  bool finished;
+  ASSERT_TRUE(mock_section.Step(0x1000, nullptr, &process, &finished));
+
+  DwarfFde fde1{};
+  fde1.pc_start = 0x500;
+  fde1.pc_end = 0x800;
+  fde1.cie = &cie;
+  EXPECT_CALL(mock_section, GetFdeOffsetFromPc(0x600, ::testing::_))
+      .WillOnce(::testing::Return(true));
+  EXPECT_CALL(mock_section, GetFdeFromOffset(::testing::_)).WillOnce(::testing::Return(&fde1));
+  EXPECT_CALL(mock_section, GetCfaLocationInfo(0x600, &fde1, ::testing::_))
+      .WillOnce(::testing::Invoke(MockGetCfaLocationInfo));
+
+  ASSERT_TRUE(mock_section.Step(0x600, nullptr, &process, &finished));
+  ASSERT_TRUE(mock_section.Step(0x700, nullptr, &process, &finished));
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/RegsTest.cpp b/libunwindstack/tests/RegsTest.cpp
index 3e80733..d15823e 100644
--- a/libunwindstack/tests/RegsTest.cpp
+++ b/libunwindstack/tests/RegsTest.cpp
@@ -94,48 +94,48 @@
 
 TEST_F(RegsTest, rel_pc) {
   RegsArm64 arm64;
-  ASSERT_EQ(4U, arm64.GetPcAdjustment(0x10, elf_.get()));
-  ASSERT_EQ(4U, arm64.GetPcAdjustment(0x4, elf_.get()));
-  ASSERT_EQ(0U, arm64.GetPcAdjustment(0x3, elf_.get()));
-  ASSERT_EQ(0U, arm64.GetPcAdjustment(0x2, elf_.get()));
-  ASSERT_EQ(0U, arm64.GetPcAdjustment(0x1, elf_.get()));
-  ASSERT_EQ(0U, arm64.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(4U, arm64.GetPcAdjustment(0x10, elf_.get()));
+  EXPECT_EQ(4U, arm64.GetPcAdjustment(0x4, elf_.get()));
+  EXPECT_EQ(0U, arm64.GetPcAdjustment(0x3, elf_.get()));
+  EXPECT_EQ(0U, arm64.GetPcAdjustment(0x2, elf_.get()));
+  EXPECT_EQ(0U, arm64.GetPcAdjustment(0x1, elf_.get()));
+  EXPECT_EQ(0U, arm64.GetPcAdjustment(0x0, elf_.get()));
 
   RegsX86 x86;
-  ASSERT_EQ(1U, x86.GetPcAdjustment(0x100, elf_.get()));
-  ASSERT_EQ(1U, x86.GetPcAdjustment(0x2, elf_.get()));
-  ASSERT_EQ(1U, x86.GetPcAdjustment(0x1, elf_.get()));
-  ASSERT_EQ(0U, x86.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(1U, x86.GetPcAdjustment(0x100, elf_.get()));
+  EXPECT_EQ(1U, x86.GetPcAdjustment(0x2, elf_.get()));
+  EXPECT_EQ(1U, x86.GetPcAdjustment(0x1, elf_.get()));
+  EXPECT_EQ(0U, x86.GetPcAdjustment(0x0, elf_.get()));
 
   RegsX86_64 x86_64;
-  ASSERT_EQ(1U, x86_64.GetPcAdjustment(0x100, elf_.get()));
-  ASSERT_EQ(1U, x86_64.GetPcAdjustment(0x2, elf_.get()));
-  ASSERT_EQ(1U, x86_64.GetPcAdjustment(0x1, elf_.get()));
-  ASSERT_EQ(0U, x86_64.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(1U, x86_64.GetPcAdjustment(0x100, elf_.get()));
+  EXPECT_EQ(1U, x86_64.GetPcAdjustment(0x2, elf_.get()));
+  EXPECT_EQ(1U, x86_64.GetPcAdjustment(0x1, elf_.get()));
+  EXPECT_EQ(0U, x86_64.GetPcAdjustment(0x0, elf_.get()));
 
   RegsMips mips;
-  ASSERT_EQ(8U, mips.GetPcAdjustment(0x10, elf_.get()));
-  ASSERT_EQ(8U, mips.GetPcAdjustment(0x8, elf_.get()));
-  ASSERT_EQ(0U, mips.GetPcAdjustment(0x7, elf_.get()));
-  ASSERT_EQ(0U, mips.GetPcAdjustment(0x6, elf_.get()));
-  ASSERT_EQ(0U, mips.GetPcAdjustment(0x5, elf_.get()));
-  ASSERT_EQ(0U, mips.GetPcAdjustment(0x4, elf_.get()));
-  ASSERT_EQ(0U, mips.GetPcAdjustment(0x3, elf_.get()));
-  ASSERT_EQ(0U, mips.GetPcAdjustment(0x2, elf_.get()));
-  ASSERT_EQ(0U, mips.GetPcAdjustment(0x1, elf_.get()));
-  ASSERT_EQ(0U, mips.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(8U, mips.GetPcAdjustment(0x10, elf_.get()));
+  EXPECT_EQ(8U, mips.GetPcAdjustment(0x8, elf_.get()));
+  EXPECT_EQ(0U, mips.GetPcAdjustment(0x7, elf_.get()));
+  EXPECT_EQ(0U, mips.GetPcAdjustment(0x6, elf_.get()));
+  EXPECT_EQ(0U, mips.GetPcAdjustment(0x5, elf_.get()));
+  EXPECT_EQ(0U, mips.GetPcAdjustment(0x4, elf_.get()));
+  EXPECT_EQ(0U, mips.GetPcAdjustment(0x3, elf_.get()));
+  EXPECT_EQ(0U, mips.GetPcAdjustment(0x2, elf_.get()));
+  EXPECT_EQ(0U, mips.GetPcAdjustment(0x1, elf_.get()));
+  EXPECT_EQ(0U, mips.GetPcAdjustment(0x0, elf_.get()));
 
   RegsMips64 mips64;
-  ASSERT_EQ(8U, mips64.GetPcAdjustment(0x10, elf_.get()));
-  ASSERT_EQ(8U, mips64.GetPcAdjustment(0x8, elf_.get()));
-  ASSERT_EQ(0U, mips64.GetPcAdjustment(0x7, elf_.get()));
-  ASSERT_EQ(0U, mips64.GetPcAdjustment(0x6, elf_.get()));
-  ASSERT_EQ(0U, mips64.GetPcAdjustment(0x5, elf_.get()));
-  ASSERT_EQ(0U, mips64.GetPcAdjustment(0x4, elf_.get()));
-  ASSERT_EQ(0U, mips64.GetPcAdjustment(0x3, elf_.get()));
-  ASSERT_EQ(0U, mips64.GetPcAdjustment(0x2, elf_.get()));
-  ASSERT_EQ(0U, mips64.GetPcAdjustment(0x1, elf_.get()));
-  ASSERT_EQ(0U, mips64.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(8U, mips64.GetPcAdjustment(0x10, elf_.get()));
+  EXPECT_EQ(8U, mips64.GetPcAdjustment(0x8, elf_.get()));
+  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x7, elf_.get()));
+  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x6, elf_.get()));
+  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x5, elf_.get()));
+  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x4, elf_.get()));
+  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x3, elf_.get()));
+  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x2, elf_.get()));
+  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x1, elf_.get()));
+  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x0, elf_.get()));
 }
 
 TEST_F(RegsTest, rel_pc_arm) {
@@ -143,34 +143,36 @@
 
   // Check fence posts.
   elf_->FakeSetLoadBias(0);
-  ASSERT_EQ(2U, arm.GetPcAdjustment(0x5, elf_.get()));
-  ASSERT_EQ(0U, arm.GetPcAdjustment(0x4, elf_.get()));
-  ASSERT_EQ(0U, arm.GetPcAdjustment(0x3, elf_.get()));
-  ASSERT_EQ(0U, arm.GetPcAdjustment(0x2, elf_.get()));
-  ASSERT_EQ(0U, arm.GetPcAdjustment(0x1, elf_.get()));
-  ASSERT_EQ(0U, arm.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0x5, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0x4, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0x3, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0x2, elf_.get()));
+  EXPECT_EQ(0U, arm.GetPcAdjustment(0x1, elf_.get()));
+  EXPECT_EQ(0U, arm.GetPcAdjustment(0x0, elf_.get()));
 
   elf_->FakeSetLoadBias(0x100);
-  ASSERT_EQ(0U, arm.GetPcAdjustment(0xff, elf_.get()));
-  ASSERT_EQ(2U, arm.GetPcAdjustment(0x105, elf_.get()));
-  ASSERT_EQ(0U, arm.GetPcAdjustment(0x104, elf_.get()));
-  ASSERT_EQ(0U, arm.GetPcAdjustment(0x103, elf_.get()));
-  ASSERT_EQ(0U, arm.GetPcAdjustment(0x102, elf_.get()));
-  ASSERT_EQ(0U, arm.GetPcAdjustment(0x101, elf_.get()));
-  ASSERT_EQ(0U, arm.GetPcAdjustment(0x100, elf_.get()));
+  EXPECT_EQ(0U, arm.GetPcAdjustment(0x1, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0x2, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0xff, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0x105, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0x104, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0x103, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0x102, elf_.get()));
+  EXPECT_EQ(0U, arm.GetPcAdjustment(0x101, elf_.get()));
+  EXPECT_EQ(0U, arm.GetPcAdjustment(0x100, elf_.get()));
 
   // Check thumb instructions handling.
   elf_->FakeSetLoadBias(0);
   memory_->SetData32(0x2000, 0);
-  ASSERT_EQ(2U, arm.GetPcAdjustment(0x2005, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0x2005, elf_.get()));
   memory_->SetData32(0x2000, 0xe000f000);
-  ASSERT_EQ(4U, arm.GetPcAdjustment(0x2005, elf_.get()));
+  EXPECT_EQ(4U, arm.GetPcAdjustment(0x2005, elf_.get()));
 
   elf_->FakeSetLoadBias(0x400);
   memory_->SetData32(0x2100, 0);
-  ASSERT_EQ(2U, arm.GetPcAdjustment(0x2505, elf_.get()));
+  EXPECT_EQ(2U, arm.GetPcAdjustment(0x2505, elf_.get()));
   memory_->SetData32(0x2100, 0xf111f111);
-  ASSERT_EQ(4U, arm.GetPcAdjustment(0x2505, elf_.get()));
+  EXPECT_EQ(4U, arm.GetPcAdjustment(0x2505, elf_.get()));
 }
 
 TEST_F(RegsTest, elf_invalid) {
@@ -181,32 +183,33 @@
   RegsMips regs_mips;
   RegsMips64 regs_mips64;
   MapInfo map_info(0x1000, 0x2000);
-  Elf* invalid_elf = new Elf(new MemoryFake);
+  Elf* invalid_elf = new Elf(nullptr);
   map_info.elf.reset(invalid_elf);
 
   regs_arm.set_pc(0x1500);
   EXPECT_EQ(0x500U, invalid_elf->GetRelPc(regs_arm.pc(), &map_info));
-  EXPECT_EQ(4U, regs_arm.GetPcAdjustment(0x500U, invalid_elf));
+  EXPECT_EQ(2U, regs_arm.GetPcAdjustment(0x500U, invalid_elf));
+  EXPECT_EQ(2U, regs_arm.GetPcAdjustment(0x511U, invalid_elf));
 
   regs_arm64.set_pc(0x1600);
   EXPECT_EQ(0x600U, invalid_elf->GetRelPc(regs_arm64.pc(), &map_info));
-  EXPECT_EQ(0U, regs_arm64.GetPcAdjustment(0x600U, invalid_elf));
+  EXPECT_EQ(4U, regs_arm64.GetPcAdjustment(0x600U, invalid_elf));
 
   regs_x86.set_pc(0x1700);
   EXPECT_EQ(0x700U, invalid_elf->GetRelPc(regs_x86.pc(), &map_info));
-  EXPECT_EQ(0U, regs_x86.GetPcAdjustment(0x700U, invalid_elf));
+  EXPECT_EQ(1U, regs_x86.GetPcAdjustment(0x700U, invalid_elf));
 
   regs_x86_64.set_pc(0x1800);
   EXPECT_EQ(0x800U, invalid_elf->GetRelPc(regs_x86_64.pc(), &map_info));
-  EXPECT_EQ(0U, regs_x86_64.GetPcAdjustment(0x800U, invalid_elf));
+  EXPECT_EQ(1U, regs_x86_64.GetPcAdjustment(0x800U, invalid_elf));
 
   regs_mips.set_pc(0x1900);
   EXPECT_EQ(0x900U, invalid_elf->GetRelPc(regs_mips.pc(), &map_info));
-  EXPECT_EQ(0U, regs_mips.GetPcAdjustment(0x900U, invalid_elf));
+  EXPECT_EQ(8U, regs_mips.GetPcAdjustment(0x900U, invalid_elf));
 
   regs_mips64.set_pc(0x1a00);
   EXPECT_EQ(0xa00U, invalid_elf->GetRelPc(regs_mips64.pc(), &map_info));
-  EXPECT_EQ(0U, regs_mips64.GetPcAdjustment(0xa00U, invalid_elf));
+  EXPECT_EQ(8U, regs_mips64.GetPcAdjustment(0xa00U, invalid_elf));
 }
 
 TEST_F(RegsTest, arm_verify_sp_pc) {
diff --git a/libunwindstack/tests/UnwindOfflineTest.cpp b/libunwindstack/tests/UnwindOfflineTest.cpp
index 532640f..8cb640f 100644
--- a/libunwindstack/tests/UnwindOfflineTest.cpp
+++ b/libunwindstack/tests/UnwindOfflineTest.cpp
@@ -284,7 +284,7 @@
       "  #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"
+      "  #03 pc 0000fe80  anonymous:ee74c000 (boolean Main.bar(boolean)+64)\n"
       "  #04 pc 006ad4d2  libartd.so (art_quick_invoke_stub+338)\n"
       "  #05 pc 00146ab5  libartd.so "
       "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+885)\n"
@@ -299,7 +299,7 @@
       "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"
+      "  #11 pc 0000fe03  anonymous:ee74c000 (int Main.compare(Main, Main)+51)\n"
       "  #12 pc 006ad4d2  libartd.so (art_quick_invoke_stub+338)\n"
       "  #13 pc 00146ab5  libartd.so "
       "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+885)\n"
@@ -314,8 +314,8 @@
       "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"
+      "  #19 pc 0000fd3b  anonymous:ee74c000 (int Main.compare(java.lang.Object, "
+      "java.lang.Object)+107)\n"
       "  #20 pc 006ad4d2  libartd.so (art_quick_invoke_stub+338)\n"
       "  #21 pc 00146ab5  libartd.so "
       "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+885)\n"
@@ -330,9 +330,9 @@
       "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 "
+      "  #27 pc 0000fbdb  anonymous:ee74c000 (int "
       "java.util.Arrays.binarySearch0(java.lang.Object[], int, int, java.lang.Object, "
-      "java.util.Comparator)+332)\n"
+      "java.util.Comparator)+331)\n"
       "  #28 pc 006ad6a2  libartd.so (art_quick_invoke_static_stub+418)\n"
       "  #29 pc 00146acb  libartd.so "
       "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+907)\n"
@@ -347,7 +347,7 @@
       "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"
+      "  #35 pc 0000f624  anonymous:ee74c000 (boolean Main.foo()+164)\n"
       "  #36 pc 006ad4d2  libartd.so (art_quick_invoke_stub+338)\n"
       "  #37 pc 00146ab5  libartd.so "
       "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+885)\n"
@@ -362,7 +362,7 @@
       "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"
+      "  #43 pc 0000eedb  anonymous:ee74c000 (void Main.runPrimary()+59)\n"
       "  #44 pc 006ad4d2  libartd.so (art_quick_invoke_stub+338)\n"
       "  #45 pc 00146ab5  libartd.so "
       "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+885)\n"
@@ -377,7 +377,7 @@
       "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"
+      "  #51 pc 0000ac21  anonymous:ee74c000 (void Main.main(java.lang.String[])+97)\n"
       "  #52 pc 006ad6a2  libartd.so (art_quick_invoke_static_stub+418)\n"
       "  #53 pc 00146acb  libartd.so "
       "(_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+907)\n"
@@ -419,7 +419,7 @@
   EXPECT_EQ(0xffeb52a0U, unwinder.frames()[1].sp);
   EXPECT_EQ(0xec6061a8U, unwinder.frames()[2].pc);
   EXPECT_EQ(0xffeb5ce0U, unwinder.frames()[2].sp);
-  EXPECT_EQ(0xee75be81U, unwinder.frames()[3].pc);
+  EXPECT_EQ(0xee75be80U, unwinder.frames()[3].pc);
   EXPECT_EQ(0xffeb5d30U, unwinder.frames()[3].sp);
   EXPECT_EQ(0xf728e4d2U, unwinder.frames()[4].pc);
   EXPECT_EQ(0xffeb5d60U, unwinder.frames()[4].sp);
@@ -435,7 +435,7 @@
   EXPECT_EQ(0xffeb5fb0U, unwinder.frames()[9].sp);
   EXPECT_EQ(0xf72945bdU, unwinder.frames()[10].pc);
   EXPECT_EQ(0xffeb6110U, unwinder.frames()[10].sp);
-  EXPECT_EQ(0xee75be04U, unwinder.frames()[11].pc);
+  EXPECT_EQ(0xee75be03U, unwinder.frames()[11].pc);
   EXPECT_EQ(0xffeb6160U, unwinder.frames()[11].sp);
   EXPECT_EQ(0xf728e4d2U, unwinder.frames()[12].pc);
   EXPECT_EQ(0xffeb6180U, unwinder.frames()[12].sp);
@@ -451,7 +451,7 @@
   EXPECT_EQ(0xffeb63e0U, unwinder.frames()[17].sp);
   EXPECT_EQ(0xf72945bdU, unwinder.frames()[18].pc);
   EXPECT_EQ(0xffeb6530U, unwinder.frames()[18].sp);
-  EXPECT_EQ(0xee75bd3cU, unwinder.frames()[19].pc);
+  EXPECT_EQ(0xee75bd3bU, unwinder.frames()[19].pc);
   EXPECT_EQ(0xffeb6580U, unwinder.frames()[19].sp);
   EXPECT_EQ(0xf728e4d2U, unwinder.frames()[20].pc);
   EXPECT_EQ(0xffeb65b0U, unwinder.frames()[20].sp);
@@ -467,7 +467,7 @@
   EXPECT_EQ(0xffeb6810U, unwinder.frames()[25].sp);
   EXPECT_EQ(0xf72945bdU, unwinder.frames()[26].pc);
   EXPECT_EQ(0xffeb6960U, unwinder.frames()[26].sp);
-  EXPECT_EQ(0xee75bbdcU, unwinder.frames()[27].pc);
+  EXPECT_EQ(0xee75bbdbU, unwinder.frames()[27].pc);
   EXPECT_EQ(0xffeb69b0U, unwinder.frames()[27].sp);
   EXPECT_EQ(0xf728e6a2U, unwinder.frames()[28].pc);
   EXPECT_EQ(0xffeb69f0U, unwinder.frames()[28].sp);
@@ -483,7 +483,7 @@
   EXPECT_EQ(0xffeb6c50U, unwinder.frames()[33].sp);
   EXPECT_EQ(0xf72945bdU, unwinder.frames()[34].pc);
   EXPECT_EQ(0xffeb6dd0U, unwinder.frames()[34].sp);
-  EXPECT_EQ(0xee75b625U, unwinder.frames()[35].pc);
+  EXPECT_EQ(0xee75b624U, unwinder.frames()[35].pc);
   EXPECT_EQ(0xffeb6e20U, unwinder.frames()[35].sp);
   EXPECT_EQ(0xf728e4d2U, unwinder.frames()[36].pc);
   EXPECT_EQ(0xffeb6e50U, unwinder.frames()[36].sp);
@@ -499,7 +499,7 @@
   EXPECT_EQ(0xffeb70a0U, unwinder.frames()[41].sp);
   EXPECT_EQ(0xf72945bdU, unwinder.frames()[42].pc);
   EXPECT_EQ(0xffeb71f0U, unwinder.frames()[42].sp);
-  EXPECT_EQ(0xee75aedcU, unwinder.frames()[43].pc);
+  EXPECT_EQ(0xee75aedbU, unwinder.frames()[43].pc);
   EXPECT_EQ(0xffeb7240U, unwinder.frames()[43].sp);
   EXPECT_EQ(0xf728e4d2U, unwinder.frames()[44].pc);
   EXPECT_EQ(0xffeb72a0U, unwinder.frames()[44].sp);
@@ -515,7 +515,7 @@
   EXPECT_EQ(0xffeb74f0U, unwinder.frames()[49].sp);
   EXPECT_EQ(0xf72945bdU, unwinder.frames()[50].pc);
   EXPECT_EQ(0xffeb7680U, unwinder.frames()[50].sp);
-  EXPECT_EQ(0xee756c22U, unwinder.frames()[51].pc);
+  EXPECT_EQ(0xee756c21U, unwinder.frames()[51].pc);
   EXPECT_EQ(0xffeb76d0U, unwinder.frames()[51].sp);
   EXPECT_EQ(0xf728e6a2U, unwinder.frames()[52].pc);
   EXPECT_EQ(0xffeb76f0U, unwinder.frames()[52].sp);
diff --git a/mkbootimg/Android.mk b/mkbootimg/Android.mk
index 8661d7d..92e1e27 100644
--- a/mkbootimg/Android.mk
+++ b/mkbootimg/Android.mk
@@ -9,3 +9,12 @@
 LOCAL_MODULE := mkbootimg
 
 include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := unpack_bootimg
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
+
+LOCAL_MODULE := unpack_bootimg
+
+include $(BUILD_PREBUILT)
diff --git a/mkbootimg/bootimg.h b/mkbootimg/bootimg.h
index 60834fe..1be8c22 100644
--- a/mkbootimg/bootimg.h
+++ b/mkbootimg/bootimg.h
@@ -20,16 +20,18 @@
 #ifndef _BOOT_IMAGE_H_
 #define _BOOT_IMAGE_H_
 
-typedef struct boot_img_hdr boot_img_hdr;
-
 #define BOOT_MAGIC "ANDROID!"
 #define BOOT_MAGIC_SIZE 8
 #define BOOT_NAME_SIZE 16
 #define BOOT_ARGS_SIZE 512
 #define BOOT_EXTRA_ARGS_SIZE 1024
 
-struct boot_img_hdr
-{
+#define BOOT_HEADER_VERSION_ZERO 0
+/*
+ *  Bootloader expects the structure of boot_img_hdr with header version
+ *  BOOT_HEADER_VERSION_ZERO to be as follows:
+ */
+struct boot_img_hdr_v0 {
     uint8_t magic[BOOT_MAGIC_SIZE];
 
     uint32_t kernel_size;  /* size in bytes */
@@ -43,7 +45,10 @@
 
     uint32_t tags_addr;    /* physical addr for kernel tags */
     uint32_t page_size;    /* flash page size we assume */
-    uint32_t unused;       /* reserved for future expansion: MUST be 0 */
+    /*
+     * version for the boot image header.
+     */
+    uint32_t header_version;
 
     /* operating system version and security patch level; for
      * version "A.B.C" and patch level "Y-M-D":
@@ -64,31 +69,79 @@
 } __attribute__((packed));
 
 /*
-** +-----------------+ 
-** | boot header     | 1 page
-** +-----------------+
-** | kernel          | n pages  
-** +-----------------+
-** | ramdisk         | m pages  
-** +-----------------+
-** | second stage    | o pages
-** +-----------------+
-**
-** n = (kernel_size + page_size - 1) / page_size
-** m = (ramdisk_size + page_size - 1) / page_size
-** o = (second_size + page_size - 1) / page_size
-**
-** 0. all entities are page_size aligned in flash
-** 1. kernel and ramdisk are required (size != 0)
-** 2. second is optional (second_size == 0 -> no second)
-** 3. load each element (kernel, ramdisk, second) at
-**    the specified physical address (kernel_addr, etc)
-** 4. prepare tags at tag_addr.  kernel_args[] is
-**    appended to the kernel commandline in the tags.
-** 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
-** 6. if second_size != 0: jump to second_addr
-**    else: jump to kernel_addr
-*/
+ * It is expected that callers would explicitly specify which version of the
+ * boot image header they need to use.
+ */
+typedef struct boot_img_hdr_v0 boot_img_hdr;
+
+/* When a boot header is of version BOOT_HEADER_VERSION_ZERO, the structure of boot image is as
+ * follows:
+ *
+ * +-----------------+
+ * | boot header     | 1 page
+ * +-----------------+
+ * | kernel          | n pages
+ * +-----------------+
+ * | ramdisk         | m pages
+ * +-----------------+
+ * | second stage    | o pages
+ * +-----------------+
+ *
+ * n = (kernel_size + page_size - 1) / page_size
+ * m = (ramdisk_size + page_size - 1) / page_size
+ * o = (second_size + page_size - 1) / page_size
+ *
+ * 0. all entities are page_size aligned in flash
+ * 1. kernel and ramdisk are required (size != 0)
+ * 2. second is optional (second_size == 0 -> no second)
+ * 3. load each element (kernel, ramdisk, second) at
+ *    the specified physical address (kernel_addr, etc)
+ * 4. prepare tags at tag_addr.  kernel_args[] is
+ *    appended to the kernel commandline in the tags.
+ * 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
+ * 6. if second_size != 0: jump to second_addr
+ *    else: jump to kernel_addr
+ */
+
+#define BOOT_HEADER_VERSION_ONE 1
+
+struct boot_img_hdr_v1 : public boot_img_hdr_v0 {
+    uint32_t recovery_dtbo_size;   /* size in bytes for recovery DTBO image */
+    uint64_t recovery_dtbo_offset; /* physical load addr */
+    uint32_t header_size;
+} __attribute__((packed));
+
+/* When the boot image header has a version of BOOT_HEADER_VERSION_ONE, the structure of the boot
+ * image is as follows:
+ *
+ * +-----------------+
+ * | boot header     | 1 page
+ * +-----------------+
+ * | kernel          | n pages
+ * +-----------------+
+ * | ramdisk         | m pages
+ * +-----------------+
+ * | second stage    | o pages
+ * +-----------------+
+ * | recovery dtbo   | p pages
+ * +-----------------+
+ * n = (kernel_size + page_size - 1) / page_size
+ * m = (ramdisk_size + page_size - 1) / page_size
+ * o = (second_size + page_size - 1) / page_size
+ * p = (recovery_dtbo_size + page_size - 1) / page_size
+ *
+ * 0. all entities are page_size aligned in flash
+ * 1. kernel and ramdisk are required (size != 0)
+ * 2. recovery_dtbo is required for recovery.img in non-A/B devices(recovery_dtbo_size != 0)
+ * 3. second is optional (second_size == 0 -> no second)
+ * 4. load each element (kernel, ramdisk, second, recovery_dtbo) at
+ *    the specified physical address (kernel_addr, etc)
+ * 5. prepare tags at tag_addr.  kernel_args[] is
+ *    appended to the kernel commandline in the tags.
+ * 6. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
+ * 7. if second_size != 0: jump to second_addr
+ *    else: jump to kernel_addr
+ */
 
 #if 0
 typedef struct ptentry ptentry;
diff --git a/mkbootimg/mkbootimg b/mkbootimg/mkbootimg
index 5a13da2..ac20d05 100755
--- a/mkbootimg/mkbootimg
+++ b/mkbootimg/mkbootimg
@@ -57,7 +57,7 @@
         args.base + args.second_offset,                 # physical load addr
         args.base + args.tags_offset,                   # physical addr for kernel tags
         args.pagesize,                                  # flash page size we assume
-        0,                                              # future expansion: MUST be 0
+        args.header_version,                            # version of bootimage header
         (args.os_version << 11) | args.os_patch_level)) # os version and patch level
     args.output.write(pack('16s', args.board.encode())) # asciiz product name
     args.output.write(pack('512s', args.cmdline[:512].encode()))
@@ -66,10 +66,20 @@
     update_sha(sha, args.kernel)
     update_sha(sha, args.ramdisk)
     update_sha(sha, args.second)
+
+    if args.header_version > 0:
+        update_sha(sha, args.recovery_dtbo)
+
     img_id = pack('32s', sha.digest())
 
     args.output.write(img_id)
     args.output.write(pack('1024s', args.cmdline[512:].encode()))
+
+    if args.header_version > 0:
+        args.output.write(pack('I', filesize(args.recovery_dtbo)))           # size in bytes
+        args.output.write(pack('Q', args.base + args.recovery_dtbo_offset))  # physical load addr
+        args.output.write(pack('I', args.output.tell() + 4))                 # size of boot header
+
     pad_file(args.output, args.pagesize)
     return img_id
 
@@ -132,6 +142,7 @@
                         required=True)
     parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb'))
     parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb'))
+    parser.add_argument('--recovery_dtbo', help='path to the recovery DTBO', type=FileType('rb'))
     parser.add_argument('--cmdline', help='extra arguments to be passed on the '
                         'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536)
     parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000)
@@ -139,6 +150,8 @@
     parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000)
     parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,
                         default=0x00f00000)
+    parser.add_argument('--recovery_dtbo_offset', help='recovery dtbo offset', type=parse_int,
+                        default=0x0f000000)
     parser.add_argument('--os_version', help='operating system version', type=parse_os_version,
                         default=0)
     parser.add_argument('--os_patch_level', help='operating system patch level',
@@ -150,6 +163,7 @@
                         choices=[2**i for i in range(11,15)], default=2048)
     parser.add_argument('--id', help='print the image ID on standard output',
                         action='store_true')
+    parser.add_argument('--header_version', help='boot image header version', type=parse_int, default=0)
     parser.add_argument('-o', '--output', help='output file name', type=FileType('wb'),
                         required=True)
     return parser.parse_args()
@@ -160,6 +174,8 @@
     write_padded_file(args.output, args.ramdisk, args.pagesize)
     write_padded_file(args.output, args.second, args.pagesize)
 
+    if args.header_version > 0:
+        write_padded_file(args.output, args.recovery_dtbo, args.pagesize)
 
 def main():
     args = parse_cmdline()
diff --git a/mkbootimg/unpack_bootimg b/mkbootimg/unpack_bootimg
new file mode 100755
index 0000000..8e42ec0
--- /dev/null
+++ b/mkbootimg/unpack_bootimg
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# Copyright 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.
+
+"""unpacks the bootimage.
+
+Extracts the kernel, ramdisk, second bootloader and recovery dtbo images.
+"""
+
+from __future__ import print_function
+from argparse import ArgumentParser, FileType
+from struct import unpack
+import os
+
+
+def create_out_dir(dir_path):
+    """creates a directory 'dir_path' if it does not exist"""
+    if not os.path.exists(dir_path):
+        os.makedirs(dir_path)
+
+
+def extract_image(offset, size, bootimage, extracted_image_name):
+    """extracts an image from the bootimage"""
+    bootimage.seek(offset)
+    with open(extracted_image_name, 'wb') as file_out:
+        file_out.write(bootimage.read(size))
+
+
+def get_number_of_pages(image_size, page_size):
+    """calculates the number of pages required for the image"""
+    return (image_size + page_size - 1) / page_size
+
+
+def unpack_bootimage(args):
+    """extracts kernel, ramdisk, second bootloader and recovery dtbo"""
+    boot_magic = unpack('8s', args.boot_img.read(8))
+    print('boot_magic: %s' % boot_magic)
+    kernel_ramdisk_second_info = unpack('10I', args.boot_img.read(10 * 4))
+    print('kernel_size: %s' % kernel_ramdisk_second_info[0])
+    print('kernel load address: %s' % kernel_ramdisk_second_info[1])
+    print('ramdisk size: %s' % kernel_ramdisk_second_info[2])
+    print('ramdisk load address: %s' % kernel_ramdisk_second_info[3])
+    print('second bootloader size: %s' % kernel_ramdisk_second_info[4])
+    print('second bootloader load address: %s' % kernel_ramdisk_second_info[5])
+    print('kernel tags load address: %s' % kernel_ramdisk_second_info[6])
+    print('page size: %s' % kernel_ramdisk_second_info[7])
+    print('boot image header version: %s' % kernel_ramdisk_second_info[8])
+    print('os version and patch level: %s' % kernel_ramdisk_second_info[9])
+
+    product_name = unpack('16s', args.boot_img.read(16))
+    print('product name: %s' % product_name)
+    cmdline = unpack('512s', args.boot_img.read(512))
+    print('command line args: %s' % cmdline)
+
+    args.boot_img.read(32)  # ignore SHA
+
+    extra_cmdline = unpack('1024s', args.boot_img.read(1024))
+    print('additional command line args: %s' % extra_cmdline)
+
+    kernel_size = kernel_ramdisk_second_info[0]
+    ramdisk_size = kernel_ramdisk_second_info[2]
+    second_size = kernel_ramdisk_second_info[4]
+    page_size = kernel_ramdisk_second_info[7]
+    version = kernel_ramdisk_second_info[8]
+    if version > 0:
+        recovery_dtbo_size = unpack('I', args.boot_img.read(1 * 4))[0]
+        print('recovery dtbo size: %s' % recovery_dtbo_size)
+        recovery_dtbo_address = unpack('Q', args.boot_img.read(8))[0]
+        print('recovery dtbo load address: %s' % recovery_dtbo_address)
+        boot_header_size = unpack('I', args.boot_img.read(4))[0]
+        print('boot header size: %s' % boot_header_size)
+    else:
+        recovery_dtbo_size = 0
+
+    # The first page contains the boot header
+    num_header_pages = 1
+
+    num_kernel_pages = get_number_of_pages(kernel_size, page_size)
+    kernel_offset = page_size * num_header_pages  # header occupies a page
+    image_info_list = [(kernel_offset, kernel_size, 'kernel')]
+
+    num_ramdisk_pages = get_number_of_pages(ramdisk_size, page_size)
+    ramdisk_offset = page_size * (num_header_pages + num_kernel_pages
+                                 ) # header + kernel
+    image_info_list.append((ramdisk_offset, ramdisk_size, 'ramdisk'))
+
+    num_second_pages = get_number_of_pages(second_size, page_size)
+    second_offset = page_size * (
+        num_header_pages + num_kernel_pages + num_ramdisk_pages
+    )  # header + kernel + ramdisk
+    image_info_list.append((second_offset, second_size, 'second'))
+
+    if recovery_dtbo_size > 0:
+        dtbo_offset = page_size * (num_header_pages + num_kernel_pages +
+                                   num_ramdisk_pages + num_second_pages)
+        image_info_list.append((dtbo_offset, recovery_dtbo_size,
+                                'recovery_dtbo'))
+
+    for image_info in image_info_list:
+        extract_image(image_info[0], image_info[1], args.boot_img,
+                      os.path.join(args.out, image_info[2]))
+
+
+def parse_cmdline():
+    """parse command line arguments"""
+    parser = ArgumentParser(
+        description='Unpacks boot.img/recovery.img, extracts the kernel,'
+        'ramdisk, second bootloader and recovery dtbo')
+    parser.add_argument(
+        '--boot_img',
+        help='path to boot image',
+        type=FileType('rb'),
+        required=True)
+    parser.add_argument('--out', help='path to out binaries', default='out')
+    return parser.parse_args()
+
+
+def main():
+    """parse arguments and unpack boot image"""
+    args = parse_cmdline()
+    create_out_dir(args.out)
+    unpack_bootimage(args)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/rootdir/etc/ld.config.txt b/rootdir/etc/ld.config.txt
index 94465f4..ba96cc8 100644
--- a/rootdir/etc/ld.config.txt
+++ b/rootdir/etc/ld.config.txt
@@ -218,13 +218,20 @@
 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
 
+# The "vndk" namespace links to "default" namespace for LLNDK libs and links to
+# "sphal" namespace for vendor libs.  The ordering matters.  The "default"
+# namespace has higher priority than the "sphal" namespace.
+namespace.vndk.links = default,sphal
+
 # When these NDK libs are required inside this namespace, then it is redirected
 # to the default namespace. This is possible since their ABI is stable across
 # Android releases.
-namespace.vndk.links = default
 namespace.vndk.link.default.shared_libs  = %LLNDK_LIBRARIES%
 namespace.vndk.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
 
+# Allow VNDK-SP extensions to use vendor libraries
+namespace.vndk.link.sphal.allow_all_shared_libs = true
+
 ###############################################################################
 # Namespace config for vendor processes. In O, no restriction is enforced for
 # them. However, in O-MR1, access to /system/${LIB} will not be allowed to