Merge "Add libcgrouprc to ld.config.txt."
diff --git a/adb/daemon/usb.cpp b/adb/daemon/usb.cpp
index 1abae87..ac739c4 100644
--- a/adb/daemon/usb.cpp
+++ b/adb/daemon/usb.cpp
@@ -169,12 +169,12 @@
 };
 
 struct UsbFfsConnection : public Connection {
-    UsbFfsConnection(unique_fd control, unique_fd read, unique_fd write,
+    UsbFfsConnection(unique_fd* control, unique_fd read, unique_fd write,
                      std::promise<void> destruction_notifier)
         : worker_started_(false),
           stopped_(false),
           destruction_notifier_(std::move(destruction_notifier)),
-          control_fd_(std::move(control)),
+          control_fd_(control),
           read_fd_(std::move(read)),
           write_fd_(std::move(write)) {
         LOG(INFO) << "UsbFfsConnection constructed";
@@ -183,11 +183,6 @@
             PLOG(FATAL) << "failed to create eventfd";
         }
 
-        monitor_event_fd_.reset(eventfd(0, EFD_CLOEXEC));
-        if (monitor_event_fd_ == -1) {
-            PLOG(FATAL) << "failed to create eventfd";
-        }
-
         aio_context_ = ScopedAioContext::Create(kUsbReadQueueDepth + kUsbWriteQueueDepth);
     }
 
@@ -199,7 +194,6 @@
         // We need to explicitly close our file descriptors before we notify our destruction,
         // because the thread listening on the future will immediately try to reopen the endpoint.
         aio_context_.reset();
-        control_fd_.reset();
         read_fd_.reset();
         write_fd_.reset();
 
@@ -246,13 +240,6 @@
             PLOG(FATAL) << "failed to notify worker eventfd to stop UsbFfsConnection";
         }
         CHECK_EQ(static_cast<size_t>(rc), sizeof(notify));
-
-        rc = adb_write(monitor_event_fd_.get(), &notify, sizeof(notify));
-        if (rc < 0) {
-            PLOG(FATAL) << "failed to notify monitor eventfd to stop UsbFfsConnection";
-        }
-
-        CHECK_EQ(static_cast<size_t>(rc), sizeof(notify));
     }
 
   private:
@@ -271,33 +258,24 @@
         monitor_thread_ = std::thread([this]() {
             adb_thread_setname("UsbFfs-monitor");
 
-            bool bound = false;
             bool enabled = false;
             bool running = true;
             while (running) {
                 adb_pollfd pfd[2] = {
-                  { .fd = control_fd_.get(), .events = POLLIN, .revents = 0 },
-                  { .fd = monitor_event_fd_.get(), .events = POLLIN, .revents = 0 },
+                        {.fd = control_fd_->get(), .events = POLLIN, .revents = 0},
                 };
 
-                // If we don't see our first bind within a second, try again.
-                int timeout_ms = bound ? -1 : 1000;
-
-                int rc = TEMP_FAILURE_RETRY(adb_poll(pfd, 2, timeout_ms));
+                int rc = TEMP_FAILURE_RETRY(adb_poll(pfd, 2, -1));
                 if (rc == -1) {
                     PLOG(FATAL) << "poll on USB control fd failed";
-                } else if (rc == 0) {
-                    LOG(WARNING) << "timed out while waiting for FUNCTIONFS_BIND, trying again";
-                    break;
                 }
 
                 if (pfd[1].revents) {
-                    // We were told to die.
-                    break;
+                    // We were told to die, continue reading until FUNCTIONFS_UNBIND.
                 }
 
                 struct usb_functionfs_event event;
-                rc = TEMP_FAILURE_RETRY(adb_read(control_fd_.get(), &event, sizeof(event)));
+                rc = TEMP_FAILURE_RETRY(adb_read(control_fd_->get(), &event, sizeof(event)));
                 if (rc == -1) {
                     PLOG(FATAL) << "failed to read functionfs event";
                 } else if (rc == 0) {
@@ -313,28 +291,10 @@
 
                 switch (event.type) {
                     case FUNCTIONFS_BIND:
-                        if (bound) {
-                            LOG(WARNING) << "received FUNCTIONFS_BIND while already bound?";
-                            running = false;
-                            break;
-                        }
-
-                        if (enabled) {
-                            LOG(WARNING) << "received FUNCTIONFS_BIND while already enabled?";
-                            running = false;
-                            break;
-                        }
-
-                        bound = true;
+                        LOG(FATAL) << "received FUNCTIONFS_BIND after already opened?";
                         break;
 
                     case FUNCTIONFS_ENABLE:
-                        if (!bound) {
-                            LOG(WARNING) << "received FUNCTIONFS_ENABLE while not bound?";
-                            running = false;
-                            break;
-                        }
-
                         if (enabled) {
                             LOG(WARNING) << "received FUNCTIONFS_ENABLE while already enabled?";
                             running = false;
@@ -346,10 +306,6 @@
                         break;
 
                     case FUNCTIONFS_DISABLE:
-                        if (!bound) {
-                            LOG(WARNING) << "received FUNCTIONFS_DISABLE while not bound?";
-                        }
-
                         if (!enabled) {
                             LOG(WARNING) << "received FUNCTIONFS_DISABLE while not enabled?";
                         }
@@ -363,11 +319,6 @@
                             LOG(WARNING) << "received FUNCTIONFS_UNBIND while still enabled?";
                         }
 
-                        if (!bound) {
-                            LOG(WARNING) << "received FUNCTIONFS_UNBIND when not bound?";
-                        }
-
-                        bound = false;
                         running = false;
                         break;
 
@@ -381,7 +332,7 @@
 
                         if ((event.u.setup.bRequestType & USB_DIR_IN)) {
                             LOG(INFO) << "acking device-to-host control transfer";
-                            ssize_t rc = adb_write(control_fd_.get(), "", 0);
+                            ssize_t rc = adb_write(control_fd_->get(), "", 0);
                             if (rc != 0) {
                                 PLOG(ERROR) << "failed to write empty packet to host";
                                 break;
@@ -390,7 +341,7 @@
                             std::string buf;
                             buf.resize(event.u.setup.wLength + 1);
 
-                            ssize_t rc = adb_read(control_fd_.get(), buf.data(), buf.size());
+                            ssize_t rc = adb_read(control_fd_->get(), buf.data(), buf.size());
                             if (rc != event.u.setup.wLength) {
                                 LOG(ERROR)
                                         << "read " << rc
@@ -426,6 +377,12 @@
                 uint64_t dummy;
                 ssize_t rc = adb_read(worker_event_fd_.get(), &dummy, sizeof(dummy));
                 if (rc == -1) {
+                    if (errno == EINTR) {
+                        // We were interrupted either to stop, or because of a backtrace.
+                        // Check stopped_ again to see if we need to exit.
+                        continue;
+                    }
+
                     PLOG(FATAL) << "failed to read from eventfd";
                 } else if (rc == 0) {
                     LOG(FATAL) << "hit EOF on eventfd";
@@ -462,6 +419,7 @@
         }
 
         worker_thread_.join();
+        worker_started_ = false;
     }
 
     void PrepareReadBlock(IoBlock* block, uint64_t id) {
@@ -679,10 +637,13 @@
     std::once_flag error_flag_;
 
     unique_fd worker_event_fd_;
-    unique_fd monitor_event_fd_;
 
     ScopedAioContext aio_context_;
-    unique_fd control_fd_;
+
+    // We keep a pointer to the control fd, so that we can reuse it to avoid USB reconfiguration,
+    // and still be able to reset it to force a reopen after FUNCTIONFS_UNBIND or running into an
+    // unexpected situation.
+    unique_fd* control_fd_;
     unique_fd read_fd_;
     unique_fd write_fd_;
 
@@ -711,15 +672,16 @@
 static void usb_ffs_open_thread() {
     adb_thread_setname("usb ffs open");
 
+    unique_fd control;
+    unique_fd bulk_out;
+    unique_fd bulk_in;
+
     while (true) {
         if (gFfsAioSupported.has_value() && !gFfsAioSupported.value()) {
             LOG(INFO) << "failed to use nonblocking ffs, falling back to legacy";
             return usb_init_legacy();
         }
 
-        unique_fd control;
-        unique_fd bulk_out;
-        unique_fd bulk_in;
         if (!open_functionfs(&control, &bulk_out, &bulk_in)) {
             std::this_thread::sleep_for(1s);
             continue;
@@ -730,7 +692,7 @@
         std::promise<void> destruction_notifier;
         std::future<void> future = destruction_notifier.get_future();
         transport->SetConnection(std::make_unique<UsbFfsConnection>(
-                std::move(control), std::move(bulk_out), std::move(bulk_in),
+                &control, std::move(bulk_out), std::move(bulk_in),
                 std::move(destruction_notifier)));
         register_transport(transport);
         future.wait();
diff --git a/adb/daemon/usb_ffs.cpp b/adb/daemon/usb_ffs.cpp
index a64ce40..1b54022 100644
--- a/adb/daemon/usb_ffs.cpp
+++ b/adb/daemon/usb_ffs.cpp
@@ -297,8 +297,32 @@
             PLOG(ERROR) << "failed to write USB strings";
             return false;
         }
-        // Signal only when writing the descriptors to ffs
+
+        // Signal init after we've wwritten our descriptors.
         android::base::SetProperty("sys.usb.ffs.ready", "1");
+
+        // Read until we get FUNCTIONFS_BIND from the control endpoint.
+        while (true) {
+            struct usb_functionfs_event event;
+            ssize_t rc = TEMP_FAILURE_RETRY(adb_read(control.get(), &event, sizeof(event)));
+
+            if (rc == -1) {
+                PLOG(FATAL) << "failed to read from FFS control fd";
+            } else if (rc == 0) {
+                LOG(WARNING) << "hit EOF on functionfs control fd during initialization";
+            } else if (rc != sizeof(event)) {
+                LOG(FATAL) << "read functionfs event of unexpected size, expected " << sizeof(event)
+                           << ", got " << rc;
+            }
+
+            if (event.type != FUNCTIONFS_BIND) {
+                LOG(FATAL) << "first read on functionfs control fd returned non-bind: "
+                           << event.type;
+            } else {
+                break;
+            }
+        }
+
         *out_control = std::move(control);
     }
 
diff --git a/base/include/android-base/result.h b/base/include/android-base/result.h
index 897c48f..4a8e1ef 100644
--- a/base/include/android-base/result.h
+++ b/base/include/android-base/result.h
@@ -81,8 +81,7 @@
 
 struct ResultError {
   template <typename T>
-  ResultError(T&& message, int code)
-      : message_(std::forward<T>(message)), code_(code) {}
+  ResultError(T&& message, int code) : message_(std::forward<T>(message)), code_(code) {}
 
   template <typename T>
   operator android::base::expected<T, ResultError>() {
@@ -122,18 +121,16 @@
 
   template <typename T>
   Error& operator<<(T&& t) {
+    if constexpr (std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, ResultError>) {
+      errno_ = t.code();
+      return (*this) << t.message();
+    }
     int saved = errno;
     ss_ << t;
     errno = saved;
     return *this;
   }
 
-  Error& operator<<(const ResultError& result_error) {
-    (*this) << result_error.message();
-    errno_ = result_error.code();
-    return *this;
-  }
-
   const std::string str() const {
     std::string str = ss_.str();
     if (append_errno_) {
diff --git a/base/result_test.cpp b/base/result_test.cpp
index 72f97f4..e864b97 100644
--- a/base/result_test.cpp
+++ b/base/result_test.cpp
@@ -143,8 +143,8 @@
   ASSERT_FALSE(result2);
   ASSERT_FALSE(result2.has_value());
 
-  EXPECT_EQ(0, result.error().code());
-  EXPECT_EQ(error_text, result.error().message());
+  EXPECT_EQ(0, result2.error().code());
+  EXPECT_EQ(error_text, result2.error().message());
 }
 
 TEST(result, result_error_through_ostream) {
@@ -159,8 +159,8 @@
   ASSERT_FALSE(result2);
   ASSERT_FALSE(result2.has_value());
 
-  EXPECT_EQ(0, result.error().code());
-  EXPECT_EQ(error_text, result.error().message());
+  EXPECT_EQ(0, result2.error().code());
+  EXPECT_EQ(error_text, result2.error().message());
 }
 
 TEST(result, result_errno_error_through_ostream) {
@@ -179,8 +179,8 @@
   ASSERT_FALSE(result2);
   ASSERT_FALSE(result2.has_value());
 
-  EXPECT_EQ(test_errno, result.error().code());
-  EXPECT_EQ(error_text + ": " + strerror(test_errno), result.error().message());
+  EXPECT_EQ(test_errno, result2.error().code());
+  EXPECT_EQ(error_text + ": " + strerror(test_errno), result2.error().message());
 }
 
 TEST(result, constructor_forwarding) {
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index 1b09f79..2e9c2d6 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -94,7 +94,7 @@
             {FB_VAR_HAS_SLOT, {GetHasSlot, GetAllPartitionArgsNoSlot}},
             {FB_VAR_SLOT_SUCCESSFUL, {GetSlotSuccessful, nullptr}},
             {FB_VAR_SLOT_UNBOOTABLE, {GetSlotUnbootable, nullptr}},
-            {FB_VAR_PARTITION_SIZE, {GetPartitionSize, GetAllPartitionArgsWithSlot}},
+            {FB_VAR_PARTITION_SIZE, {::GetPartitionSize, GetAllPartitionArgsWithSlot}},
             {FB_VAR_PARTITION_TYPE, {GetPartitionType, GetAllPartitionArgsWithSlot}},
             {FB_VAR_IS_LOGICAL, {GetPartitionIsLogical, GetAllPartitionArgsWithSlot}},
             {FB_VAR_IS_USERSPACE, {GetIsUserspace, nullptr}},
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 56ea92c..410209b 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -245,11 +245,11 @@
             if (should_force_check(*fs_stat)) {
                 ret = android_fork_execvp_ext(
                     ARRAY_SIZE(e2fsck_forced_argv), const_cast<char**>(e2fsck_forced_argv), &status,
-                    true, LOG_KLOG | LOG_FILE, true, const_cast<char*>(FSCK_LOG_FILE), NULL, 0);
+                    true, LOG_KLOG | LOG_FILE, true, const_cast<char*>(FSCK_LOG_FILE), nullptr, 0);
             } else {
                 ret = android_fork_execvp_ext(
                     ARRAY_SIZE(e2fsck_argv), const_cast<char**>(e2fsck_argv), &status, true,
-                    LOG_KLOG | LOG_FILE, true, const_cast<char*>(FSCK_LOG_FILE), NULL, 0);
+                    LOG_KLOG | LOG_FILE, true, const_cast<char*>(FSCK_LOG_FILE), nullptr, 0);
             }
 
             if (ret < 0) {
@@ -263,13 +263,19 @@
         }
     } else if (is_f2fs(fs_type)) {
         const char* f2fs_fsck_argv[] = {F2FS_FSCK_BIN, "-a", blk_device.c_str()};
-        LINFO << "Running " << F2FS_FSCK_BIN << " -a " << realpath(blk_device);
+        const char* f2fs_fsck_forced_argv[] = {F2FS_FSCK_BIN, "-f", blk_device.c_str()};
 
-        ret = android_fork_execvp_ext(ARRAY_SIZE(f2fs_fsck_argv),
-                                      const_cast<char **>(f2fs_fsck_argv),
-                                      &status, true, LOG_KLOG | LOG_FILE,
-                                      true, const_cast<char *>(FSCK_LOG_FILE),
-                                      NULL, 0);
+        if (should_force_check(*fs_stat)) {
+            LINFO << "Running " << F2FS_FSCK_BIN << " -f " << realpath(blk_device);
+            ret = android_fork_execvp_ext(
+                ARRAY_SIZE(f2fs_fsck_forced_argv), const_cast<char**>(f2fs_fsck_forced_argv), &status,
+                true, LOG_KLOG | LOG_FILE, true, const_cast<char*>(FSCK_LOG_FILE), nullptr, 0);
+        } else {
+            LINFO << "Running " << F2FS_FSCK_BIN << " -a " << realpath(blk_device);
+            ret = android_fork_execvp_ext(
+                ARRAY_SIZE(f2fs_fsck_argv), const_cast<char**>(f2fs_fsck_argv), &status, true,
+                LOG_KLOG | LOG_FILE, true, const_cast<char*>(FSCK_LOG_FILE), nullptr, 0);
+        }
         if (ret < 0) {
             /* No need to check for error in fork, we can't really handle it now */
             LERROR << "Failed trying to run " << F2FS_FSCK_BIN;
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index 46bfe92..45c3ede 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -106,6 +106,13 @@
     EXPECT_EQ(extent->num_sectors(), 32768 / LP_SECTOR_SIZE);
     EXPECT_EQ(extent->physical_sector(), 32);
 
+    auto exported = builder->Export();
+    ASSERT_NE(exported, nullptr);
+    ASSERT_EQ(FindPartition(*exported.get(), "not found"), nullptr);
+    auto entry = FindPartition(*exported.get(), "system");
+    ASSERT_NE(entry, nullptr);
+    ASSERT_EQ(GetPartitionSize(*exported.get(), *entry), 32768);
+
     // Test shrinking to 0.
     builder->ResizePartition(system, 0);
     EXPECT_EQ(system->size(), 0);
diff --git a/fs_mgr/liblp/include/liblp/liblp.h b/fs_mgr/liblp/include/liblp/liblp.h
index d3a7b93..135a1b3 100644
--- a/fs_mgr/liblp/include/liblp/liblp.h
+++ b/fs_mgr/liblp/include/liblp/liblp.h
@@ -107,6 +107,10 @@
 std::string SlotSuffixForSlotNumber(uint32_t slot_number);
 std::string GetPartitionSlotSuffix(const std::string& partition_name);
 
+// Helpers for common functions.
+const LpMetadataPartition* FindPartition(const LpMetadata& metadata, const std::string& name);
+uint64_t GetPartitionSize(const LpMetadata& metadata, const LpMetadataPartition& partition);
+
 }  // namespace fs_mgr
 }  // namespace android
 
diff --git a/fs_mgr/liblp/utility.cpp b/fs_mgr/liblp/utility.cpp
index 72a3c57..338b525 100644
--- a/fs_mgr/liblp/utility.cpp
+++ b/fs_mgr/liblp/utility.cpp
@@ -135,6 +135,24 @@
     return list;
 }
 
+const LpMetadataPartition* FindPartition(const LpMetadata& metadata, const std::string& name) {
+    for (const auto& partition : metadata.partitions) {
+        if (GetPartitionName(partition) == name) {
+            return &partition;
+        }
+    }
+    return nullptr;
+}
+
+uint64_t GetPartitionSize(const LpMetadata& metadata, const LpMetadataPartition& partition) {
+    uint64_t total_size = 0;
+    for (uint32_t i = 0; i < partition.num_extents; i++) {
+        const auto& extent = metadata.extents[partition.first_extent_index + i];
+        total_size += extent.num_sectors * LP_SECTOR_SIZE;
+    }
+    return total_size;
+}
+
 std::string GetPartitionSlotSuffix(const std::string& partition_name) {
     if (partition_name.size() <= 2) {
         return "";
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 2a583e8..44cac4b 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -1076,10 +1076,7 @@
 
 static Result<void> do_parse_apex_configs(const BuiltinArguments& args) {
     glob_t glob_result;
-    // @ is added to filter out the later paths, which are bind mounts of the places
-    // where the APEXes are really mounted at. Otherwise, we will parse the
-    // same file twice.
-    static constexpr char glob_pattern[] = "/apex/*@*/etc/*.rc";
+    static constexpr char glob_pattern[] = "/apex/*/etc/*.rc";
     const int ret = glob(glob_pattern, GLOB_MARK, nullptr, &glob_result);
     if (ret != 0 && ret != GLOB_NOMATCH) {
         globfree(&glob_result);
@@ -1088,7 +1085,15 @@
     std::vector<std::string> configs;
     Parser parser = CreateServiceOnlyParser(ServiceList::GetInstance());
     for (size_t i = 0; i < glob_result.gl_pathc; i++) {
-        configs.emplace_back(glob_result.gl_pathv[i]);
+        std::string path = glob_result.gl_pathv[i];
+        // Filter-out /apex/<name>@<ver> paths. The paths are bind-mounted to
+        // /apex/<name> paths, so unless we filter them out, we will parse the
+        // same file twice.
+        std::vector<std::string> paths = android::base::Split(path, "/");
+        if (paths.size() >= 2 && paths[1].find('@') != std::string::npos) {
+            continue;
+        }
+        configs.push_back(path);
     }
     globfree(&glob_result);
 
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 40d8d90..edc316a 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -150,6 +150,7 @@
 }
 
 void SetCgroupAction::EnableResourceCaching() {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
     if (fd_ != FDS_NOT_CACHED) {
         return;
     }
@@ -191,6 +192,7 @@
 }
 
 bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
     if (IsFdValid()) {
         // fd is cached, reuse it
         if (!AddTidToCgroup(pid, fd_)) {
@@ -221,6 +223,7 @@
 }
 
 bool SetCgroupAction::ExecuteForTask(int tid) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
     if (IsFdValid()) {
         // fd is cached, reuse it
         if (!AddTidToCgroup(tid, fd_)) {
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 445647d..77bac2d 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -19,6 +19,7 @@
 #include <sys/cdefs.h>
 #include <sys/types.h>
 #include <map>
+#include <mutex>
 #include <string>
 #include <vector>
 
@@ -127,6 +128,7 @@
     CgroupController controller_;
     std::string path_;
     android::base::unique_fd fd_;
+    mutable std::mutex fd_mutex_;
 
     static bool IsAppDependentPath(const std::string& path);
     static bool AddTidToCgroup(int tid, int fd);
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index b7650a1..5423de5 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -241,6 +241,7 @@
         "tests/files/offline/debug_frame_first_x86/*",
         "tests/files/offline/debug_frame_load_bias_arm/*",
         "tests/files/offline/eh_frame_hdr_begin_x86_64/*",
+        "tests/files/offline/invalid_elf_offset_arm/*",
         "tests/files/offline/jit_debug_arm/*",
         "tests/files/offline/jit_debug_x86/*",
         "tests/files/offline/jit_map_arm/*",
diff --git a/libunwindstack/MapInfo.cpp b/libunwindstack/MapInfo.cpp
index 03658b4..d3cec06 100644
--- a/libunwindstack/MapInfo.cpp
+++ b/libunwindstack/MapInfo.cpp
@@ -231,11 +231,13 @@
     }
   }
 
-  // If there is a read-only map then a read-execute map that represents the
-  // same elf object, make sure the previous map is using the same elf
-  // object if it hasn't already been set.
-  if (prev_map != nullptr && elf_start_offset != offset && prev_map->offset == elf_start_offset &&
-      prev_map->name == name) {
+  if (!elf->valid()) {
+    elf_start_offset = offset;
+  } else if (prev_map != nullptr && elf_start_offset != offset &&
+             prev_map->offset == elf_start_offset && prev_map->name == name) {
+    // If there is a read-only map then a read-execute map that represents the
+    // same elf object, make sure the previous map is using the same elf
+    // object if it hasn't already been set.
     std::lock_guard<std::mutex> guard(prev_map->mutex_);
     if (prev_map->elf.get() == nullptr) {
       prev_map->elf = elf;
diff --git a/libunwindstack/tests/UnwindOfflineTest.cpp b/libunwindstack/tests/UnwindOfflineTest.cpp
index 6c64c40..553b344 100644
--- a/libunwindstack/tests/UnwindOfflineTest.cpp
+++ b/libunwindstack/tests/UnwindOfflineTest.cpp
@@ -62,7 +62,7 @@
     free(cwd_);
   }
 
-  void Init(const char* file_dir, ArchEnum arch) {
+  void Init(const char* file_dir, ArchEnum arch, bool add_stack = true) {
     dir_ = TestGetFileDirectory() + "offline/" + file_dir;
 
     std::string data;
@@ -71,23 +71,25 @@
     maps_.reset(new BufferMaps(data.c_str()));
     ASSERT_TRUE(maps_->Parse());
 
-    std::string stack_name(dir_ + "stack.data");
-    struct stat st;
-    if (stat(stack_name.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
-      std::unique_ptr<MemoryOffline> stack_memory(new MemoryOffline);
-      ASSERT_TRUE(stack_memory->Init((dir_ + "stack.data").c_str(), 0));
-      process_memory_.reset(stack_memory.release());
-    } else {
-      std::unique_ptr<MemoryOfflineParts> stack_memory(new MemoryOfflineParts);
-      for (size_t i = 0;; i++) {
-        stack_name = dir_ + "stack" + std::to_string(i) + ".data";
-        if (stat(stack_name.c_str(), &st) == -1 || !S_ISREG(st.st_mode)) {
-          ASSERT_TRUE(i != 0) << "No stack data files found.";
-          break;
+    if (add_stack) {
+      std::string stack_name(dir_ + "stack.data");
+      struct stat st;
+      if (stat(stack_name.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
+        std::unique_ptr<MemoryOffline> stack_memory(new MemoryOffline);
+        ASSERT_TRUE(stack_memory->Init((dir_ + "stack.data").c_str(), 0));
+        process_memory_.reset(stack_memory.release());
+      } else {
+        std::unique_ptr<MemoryOfflineParts> stack_memory(new MemoryOfflineParts);
+        for (size_t i = 0;; i++) {
+          stack_name = dir_ + "stack" + std::to_string(i) + ".data";
+          if (stat(stack_name.c_str(), &st) == -1 || !S_ISREG(st.st_mode)) {
+            ASSERT_TRUE(i != 0) << "No stack data files found.";
+            break;
+          }
+          AddMemory(stack_name, stack_memory.get());
         }
-        AddMemory(stack_name, stack_memory.get());
+        process_memory_.reset(stack_memory.release());
       }
-      process_memory_.reset(stack_memory.release());
     }
 
     switch (arch) {
@@ -1442,4 +1444,17 @@
   EXPECT_EQ(0x7be4f07d20ULL, unwinder.frames()[12].sp);
 }
 
+TEST_F(UnwindOfflineTest, invalid_elf_offset_arm) {
+  ASSERT_NO_FATAL_FAILURE(Init("invalid_elf_offset_arm/", ARCH_ARM, false));
+
+  Unwinder unwinder(128, maps_.get(), regs_.get(), process_memory_);
+  unwinder.Unwind();
+
+  std::string frame_info(DumpFrames(unwinder));
+  ASSERT_EQ(1U, unwinder.NumFrames()) << "Unwind:\n" << frame_info;
+  EXPECT_EQ("  #00 pc 00aa7508  invalid.apk (offset 0x12e4000)\n", frame_info);
+  EXPECT_EQ(0xc898f508, unwinder.frames()[0].pc);
+  EXPECT_EQ(0xc2044218, unwinder.frames()[0].sp);
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/files/offline/invalid_elf_offset_arm/maps.txt b/libunwindstack/tests/files/offline/invalid_elf_offset_arm/maps.txt
new file mode 100644
index 0000000..022404c
--- /dev/null
+++ b/libunwindstack/tests/files/offline/invalid_elf_offset_arm/maps.txt
@@ -0,0 +1 @@
+c7ee8000-c8c52fff r-xp  12e4000    00:00 0  invalid.apk
diff --git a/libunwindstack/tests/files/offline/invalid_elf_offset_arm/regs.txt b/libunwindstack/tests/files/offline/invalid_elf_offset_arm/regs.txt
new file mode 100644
index 0000000..b7f10ef
--- /dev/null
+++ b/libunwindstack/tests/files/offline/invalid_elf_offset_arm/regs.txt
@@ -0,0 +1,16 @@
+r0: c0434c00
+r1: 2a4c9fbc
+r2: 00000000
+r3: c83ef1f9
+r4: 00000004
+r5: c2044904
+r6: 00000000
+r7: c20443b8
+r8: 000b33ff
+r9: c20444b0
+r10: cac90740
+r11: 00000000
+ip: ed891ca4
+sp: c2044218
+lr: ed807265
+pc: c898f508