Merge "bootstat: Three more boot reasons."
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 950a551..f584021 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -65,6 +65,7 @@
 #include "property_service.h"
 #include "reboot.h"
 #include "rlimit_parser.h"
+#include "selinux.h"
 #include "service.h"
 #include "subcontext.h"
 #include "util.h"
@@ -641,8 +642,26 @@
     return Success();
 }
 
+static int MakeSymlink(const std::string& target, const std::string& linkpath) {
+    std::string secontext;
+    // Passing 0 for mode should work.
+    if (SelabelLookupFileContext(linkpath, 0, &secontext) && !secontext.empty()) {
+        setfscreatecon(secontext.c_str());
+    }
+
+    int rc = symlink(target.c_str(), linkpath.c_str());
+
+    if (!secontext.empty()) {
+        int save_errno = errno;
+        setfscreatecon(nullptr);
+        errno = save_errno;
+    }
+
+    return rc;
+}
+
 static Result<Success> do_symlink(const BuiltinArguments& args) {
-    if (symlink(args[1].c_str(), args[2].c_str()) < 0) {
+    if (MakeSymlink(args[1], args[2]) < 0) {
         // The symlink builtin is often used to create symlinks for older devices to be backwards
         // compatible with new paths, therefore we skip reporting this error.
         if (errno == EEXIST && android::base::GetMinimumLogSeverity() > android::base::DEBUG) {
diff --git a/init/util.cpp b/init/util.cpp
index a19a6f3..d80cb1e 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -178,9 +178,26 @@
     return content;
 }
 
+static int OpenFile(const std::string& path, int flags, mode_t mode) {
+    std::string secontext;
+    if (SelabelLookupFileContext(path, mode, &secontext) && !secontext.empty()) {
+        setfscreatecon(secontext.c_str());
+    }
+
+    int rc = open(path.c_str(), flags, mode);
+
+    if (!secontext.empty()) {
+        int save_errno = errno;
+        setfscreatecon(nullptr);
+        errno = save_errno;
+    }
+
+    return rc;
+}
+
 Result<Success> WriteFile(const std::string& path, const std::string& content) {
     android::base::unique_fd fd(TEMP_FAILURE_RETRY(
-        open(path.c_str(), O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC | O_CLOEXEC, 0600)));
+        OpenFile(path, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC | O_CLOEXEC, 0600)));
     if (fd == -1) {
         return ErrnoError() << "open() failed";
     }
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 40364fe..75aa427 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -130,6 +130,7 @@
         "tests/RegsStepIfSignalHandlerTest.cpp",
         "tests/RegsTest.cpp",
         "tests/SymbolsTest.cpp",
+        "tests/UnwindOfflineTest.cpp",
         "tests/UnwindTest.cpp",
         "tests/UnwinderTest.cpp",
     ],
@@ -153,6 +154,8 @@
     data: [
         "tests/files/elf32.xz",
         "tests/files/elf64.xz",
+        "tests/files/offline/straddle_arm32/*",
+        "tests/files/offline/straddle_arm64/*",
     ],
 }
 
diff --git a/libunwindstack/DwarfEhFrame.h b/libunwindstack/DwarfEhFrame.h
index 561d23a..2589c89 100644
--- a/libunwindstack/DwarfEhFrame.h
+++ b/libunwindstack/DwarfEhFrame.h
@@ -40,7 +40,7 @@
 
   uint64_t AdjustPcFromFde(uint64_t pc) override {
     // The eh_frame uses relative pcs.
-    return pc + this->memory_.cur_offset();
+    return pc + this->memory_.cur_offset() - 4;
   }
 };
 
diff --git a/libunwindstack/Elf.cpp b/libunwindstack/Elf.cpp
index 0c65895..48e33ee 100644
--- a/libunwindstack/Elf.cpp
+++ b/libunwindstack/Elf.cpp
@@ -104,8 +104,8 @@
 }
 
 // The relative pc is always relative to the start of the map from which it comes.
-bool Elf::Step(uint64_t rel_pc, uint64_t elf_offset, Regs* regs, Memory* process_memory,
-               bool* finished) {
+bool Elf::Step(uint64_t rel_pc, uint64_t adjusted_rel_pc, uint64_t elf_offset, Regs* regs,
+               Memory* process_memory, bool* finished) {
   if (!valid_) {
     return false;
   }
@@ -117,16 +117,16 @@
   }
 
   // Adjust the load bias to get the real relative pc.
-  if (rel_pc < load_bias_) {
+  if (adjusted_rel_pc < load_bias_) {
     return false;
   }
-  rel_pc -= load_bias_;
+  adjusted_rel_pc -= load_bias_;
 
   // Lock during the step which can update information in the object.
   std::lock_guard<std::mutex> guard(lock_);
-  return interface_->Step(rel_pc, regs, process_memory, finished) ||
+  return interface_->Step(adjusted_rel_pc, regs, process_memory, finished) ||
          (gnu_debugdata_interface_ &&
-          gnu_debugdata_interface_->Step(rel_pc, regs, process_memory, finished));
+          gnu_debugdata_interface_->Step(adjusted_rel_pc, regs, process_memory, finished));
 }
 
 bool Elf::IsValidElf(Memory* memory) {
diff --git a/libunwindstack/Maps.cpp b/libunwindstack/Maps.cpp
index 5012ffd..56370c1 100644
--- a/libunwindstack/Maps.cpp
+++ b/libunwindstack/Maps.cpp
@@ -64,13 +64,13 @@
   // 6f000000-6f01e000 rwxp 00000000 00:0c 16389419   /system/lib/libcomposer.so
   char* str;
   const char* old_str = line;
-  uint64_t start = strtoul(old_str, &str, 16);
+  uint64_t start = strtoull(old_str, &str, 16);
   if (old_str == str || *str++ != '-') {
     return nullptr;
   }
 
   old_str = str;
-  uint64_t end = strtoul(old_str, &str, 16);
+  uint64_t end = strtoull(old_str, &str, 16);
   if (old_str == str || !std::isspace(*str++)) {
     return nullptr;
   }
@@ -112,14 +112,14 @@
   }
 
   old_str = str;
-  uint64_t offset = strtoul(old_str, &str, 16);
+  uint64_t offset = strtoull(old_str, &str, 16);
   if (old_str == str || !std::isspace(*str)) {
     return nullptr;
   }
 
   // Ignore the 00:00 values.
   old_str = str;
-  (void)strtoul(old_str, &str, 16);
+  (void)strtoull(old_str, &str, 16);
   if (old_str == str || *str++ != ':') {
     return nullptr;
   }
@@ -129,14 +129,14 @@
 
   // Skip the inode.
   old_str = str;
-  (void)strtoul(str, &str, 16);
+  (void)strtoull(str, &str, 16);
   if (old_str == str || !std::isspace(*str++)) {
     return nullptr;
   }
 
   // Skip decimal digit.
   old_str = str;
-  (void)strtoul(old_str, &str, 10);
+  (void)strtoull(old_str, &str, 10);
   if (old_str == str || (!std::isspace(*str) && *str != '\0')) {
     return nullptr;
   }
diff --git a/libunwindstack/Unwinder.cpp b/libunwindstack/Unwinder.cpp
index 2e46a11..3092a62 100644
--- a/libunwindstack/Unwinder.cpp
+++ b/libunwindstack/Unwinder.cpp
@@ -32,27 +32,20 @@
 
 namespace unwindstack {
 
-void Unwinder::FillInFrame(MapInfo* map_info, Elf* elf, uint64_t rel_pc, bool adjust_pc) {
+void Unwinder::FillInFrame(MapInfo* map_info, Elf* elf, uint64_t adjusted_rel_pc) {
   size_t frame_num = frames_.size();
   frames_.resize(frame_num + 1);
   FrameData* frame = &frames_.at(frame_num);
   frame->num = frame_num;
-  frame->pc = regs_->pc();
   frame->sp = regs_->sp();
-  frame->rel_pc = rel_pc;
+  frame->rel_pc = adjusted_rel_pc;
 
   if (map_info == nullptr) {
+    frame->pc = regs_->pc();
     return;
   }
 
-  if (adjust_pc) {
-    // Don't adjust the first frame pc.
-    frame->rel_pc = regs_->GetAdjustedPc(rel_pc, elf);
-
-    // Adjust the original pc.
-    frame->pc -= rel_pc - frame->rel_pc;
-  }
-
+  frame->pc = map_info->start + adjusted_rel_pc;
   frame->map_name = map_info->name;
   frame->map_offset = map_info->offset;
   frame->map_start = map_info->start;
@@ -92,21 +85,29 @@
 
     MapInfo* map_info = maps_->Find(regs_->pc());
     uint64_t rel_pc;
+    uint64_t adjusted_rel_pc;
     Elf* elf;
     if (map_info == nullptr) {
       rel_pc = regs_->pc();
+      adjusted_rel_pc = rel_pc;
     } else {
       if (ShouldStop(map_suffixes_to_ignore, map_info->name)) {
         break;
       }
       elf = map_info->GetElf(process_memory_, true);
       rel_pc = elf->GetRelPc(regs_->pc(), map_info);
+      if (adjust_pc) {
+        adjusted_rel_pc = regs_->GetAdjustedPc(rel_pc, elf);
+      } else {
+        adjusted_rel_pc = rel_pc;
+      }
     }
 
     if (map_info == nullptr || initial_map_names_to_skip == nullptr ||
         std::find(initial_map_names_to_skip->begin(), initial_map_names_to_skip->end(),
                   basename(map_info->name.c_str())) == initial_map_names_to_skip->end()) {
-      FillInFrame(map_info, elf, rel_pc, adjust_pc);
+      FillInFrame(map_info, elf, adjusted_rel_pc);
+
       // Once a frame is added, stop skipping frames.
       initial_map_names_to_skip = nullptr;
     }
@@ -133,7 +134,8 @@
           in_device_map = true;
         } else {
           bool finished;
-          stepped = elf->Step(rel_pc, map_info->elf_offset, regs_, process_memory_.get(), &finished);
+          stepped = elf->Step(rel_pc, adjusted_rel_pc, map_info->elf_offset, regs_,
+                              process_memory_.get(), &finished);
           if (stepped && finished) {
             break;
           }
diff --git a/libunwindstack/include/unwindstack/Elf.h b/libunwindstack/include/unwindstack/Elf.h
index 71d7aca..da2ddc0 100644
--- a/libunwindstack/include/unwindstack/Elf.h
+++ b/libunwindstack/include/unwindstack/Elf.h
@@ -51,8 +51,8 @@
 
   uint64_t GetRelPc(uint64_t pc, const MapInfo* map_info);
 
-  bool Step(uint64_t rel_pc, uint64_t elf_offset, Regs* regs, Memory* process_memory,
-            bool* finished);
+  bool Step(uint64_t rel_pc, uint64_t adjusted_rel_pc, uint64_t elf_offset, Regs* regs,
+            Memory* process_memory, bool* finished);
 
   ElfInterface* CreateInterfaceFromMemory(Memory* memory);
 
diff --git a/libunwindstack/include/unwindstack/Unwinder.h b/libunwindstack/include/unwindstack/Unwinder.h
index 37a76b2..b64d460 100644
--- a/libunwindstack/include/unwindstack/Unwinder.h
+++ b/libunwindstack/include/unwindstack/Unwinder.h
@@ -70,7 +70,7 @@
   static std::string FormatFrame(const FrameData& frame, bool bits32);
 
  private:
-  void FillInFrame(MapInfo* map_info, Elf* elf, uint64_t rel_pc, bool adjust_pc);
+  void FillInFrame(MapInfo* map_info, Elf* elf, uint64_t adjusted_rel_pc);
 
   size_t max_frames_;
   Maps* maps_;
diff --git a/libunwindstack/tests/DwarfEhFrameTest.cpp b/libunwindstack/tests/DwarfEhFrameTest.cpp
index 53ee719..3a629f8 100644
--- a/libunwindstack/tests/DwarfEhFrameTest.cpp
+++ b/libunwindstack/tests/DwarfEhFrameTest.cpp
@@ -109,23 +109,23 @@
 
   this->eh_frame_->TestGetFdeInfo(0, &info);
   EXPECT_EQ(0x5100U, info.offset);
-  EXPECT_EQ(0x660cU, info.start);
-  EXPECT_EQ(0x680cU, info.end);
+  EXPECT_EQ(0x6608U, info.start);
+  EXPECT_EQ(0x6808U, info.end);
 
   this->eh_frame_->TestGetFdeInfo(1, &info);
   EXPECT_EQ(0x5200U, info.offset);
-  EXPECT_EQ(0x770cU, info.start);
-  EXPECT_EQ(0x7a0cU, info.end);
+  EXPECT_EQ(0x7708U, info.start);
+  EXPECT_EQ(0x7a08U, info.end);
 
   this->eh_frame_->TestGetFdeInfo(2, &info);
   EXPECT_EQ(0x5400U, info.offset);
-  EXPECT_EQ(0x890cU, info.start);
-  EXPECT_EQ(0x8d0cU, info.end);
+  EXPECT_EQ(0x8908U, info.start);
+  EXPECT_EQ(0x8d08U, info.end);
 
   this->eh_frame_->TestGetFdeInfo(3, &info);
   EXPECT_EQ(0x5500U, info.offset);
-  EXPECT_EQ(0x9a0cU, info.start);
-  EXPECT_EQ(0x9f0cU, info.end);
+  EXPECT_EQ(0x9a08U, info.start);
+  EXPECT_EQ(0x9f08U, info.end);
 }
 
 TYPED_TEST_P(DwarfEhFrameTest, Init32_fde_not_following_cie) {
@@ -193,23 +193,23 @@
 
   this->eh_frame_->TestGetFdeInfo(0, &info);
   EXPECT_EQ(0x5100U, info.offset);
-  EXPECT_EQ(0x661cU, info.start);
-  EXPECT_EQ(0x681cU, info.end);
+  EXPECT_EQ(0x6618U, info.start);
+  EXPECT_EQ(0x6818U, info.end);
 
   this->eh_frame_->TestGetFdeInfo(1, &info);
   EXPECT_EQ(0x5200U, info.offset);
-  EXPECT_EQ(0x771cU, info.start);
-  EXPECT_EQ(0x7a1cU, info.end);
+  EXPECT_EQ(0x7718U, info.start);
+  EXPECT_EQ(0x7a18U, info.end);
 
   this->eh_frame_->TestGetFdeInfo(2, &info);
   EXPECT_EQ(0x5400U, info.offset);
-  EXPECT_EQ(0x891cU, info.start);
-  EXPECT_EQ(0x8d1cU, info.end);
+  EXPECT_EQ(0x8918U, info.start);
+  EXPECT_EQ(0x8d18U, info.end);
 
   this->eh_frame_->TestGetFdeInfo(3, &info);
   EXPECT_EQ(0x5500U, info.offset);
-  EXPECT_EQ(0x9a1cU, info.start);
-  EXPECT_EQ(0x9f1cU, info.end);
+  EXPECT_EQ(0x9a18U, info.start);
+  EXPECT_EQ(0x9f18U, info.end);
 }
 
 TYPED_TEST_P(DwarfEhFrameTest, Init64_fde_not_following_cie) {
@@ -261,8 +261,8 @@
   typename DwarfEhFrame<TypeParam>::FdeInfo info(0, 0, 0);
   this->eh_frame_->TestGetFdeInfo(0, &info);
   EXPECT_EQ(0x5100U, info.offset);
-  EXPECT_EQ(0x660aU, info.start);
-  EXPECT_EQ(0x680aU, info.end);
+  EXPECT_EQ(0x6606U, info.start);
+  EXPECT_EQ(0x6806U, info.end);
 }
 
 TYPED_TEST_P(DwarfEhFrameTest, Init_version4) {
@@ -304,8 +304,8 @@
   typename DwarfEhFrame<TypeParam>::FdeInfo info(0, 0, 0);
   this->eh_frame_->TestGetFdeInfo(0, &info);
   EXPECT_EQ(0x5100U, info.offset);
-  EXPECT_EQ(0x660aU, info.start);
-  EXPECT_EQ(0x680aU, info.end);
+  EXPECT_EQ(0x6606U, info.start);
+  EXPECT_EQ(0x6806U, info.end);
 }
 
 TYPED_TEST_P(DwarfEhFrameTest, GetFdeOffsetFromPc) {
@@ -384,8 +384,8 @@
   ASSERT_TRUE(fde != nullptr);
   EXPECT_EQ(0x14010U, fde->cfa_instructions_offset);
   EXPECT_EQ(0x14024U, fde->cfa_instructions_end);
-  EXPECT_EQ(0x1d00cU, fde->pc_start);
-  EXPECT_EQ(0x1d10cU, fde->pc_end);
+  EXPECT_EQ(0x1d008U, fde->pc_start);
+  EXPECT_EQ(0x1d108U, fde->pc_end);
   EXPECT_EQ(0xf000U, fde->cie_offset);
   EXPECT_EQ(0U, fde->lsda_address);
 
@@ -428,8 +428,8 @@
   ASSERT_TRUE(fde != nullptr);
   EXPECT_EQ(0x8024U, fde->cfa_instructions_offset);
   EXPECT_EQ(0x820cU, fde->cfa_instructions_end);
-  EXPECT_EQ(0xd01cU, fde->pc_start);
-  EXPECT_EQ(0xd31cU, fde->pc_end);
+  EXPECT_EQ(0xd018U, fde->pc_start);
+  EXPECT_EQ(0xd318U, fde->pc_end);
   EXPECT_EQ(0x6000U, fde->cie_offset);
   EXPECT_EQ(0U, fde->lsda_address);
 
diff --git a/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp b/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
index 1028ab9..64b325b 100644
--- a/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
+++ b/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
@@ -345,8 +345,8 @@
   ASSERT_TRUE(fde != nullptr);
   EXPECT_EQ(0x14010U, fde->cfa_instructions_offset);
   EXPECT_EQ(0x14024U, fde->cfa_instructions_end);
-  EXPECT_EQ(0x1d00cU, fde->pc_start);
-  EXPECT_EQ(0x1d10cU, fde->pc_end);
+  EXPECT_EQ(0x1d008U, fde->pc_start);
+  EXPECT_EQ(0x1d108U, fde->pc_end);
   EXPECT_EQ(0xf000U, fde->cie_offset);
   EXPECT_EQ(0U, fde->lsda_address);
 
@@ -387,8 +387,8 @@
   ASSERT_TRUE(fde != nullptr);
   EXPECT_EQ(0x8024U, fde->cfa_instructions_offset);
   EXPECT_EQ(0x820cU, fde->cfa_instructions_end);
-  EXPECT_EQ(0xd01cU, fde->pc_start);
-  EXPECT_EQ(0xd31cU, fde->pc_end);
+  EXPECT_EQ(0xd018U, fde->pc_start);
+  EXPECT_EQ(0xd318U, fde->pc_end);
   EXPECT_EQ(0x6000U, fde->cie_offset);
   EXPECT_EQ(0U, fde->lsda_address);
 
diff --git a/libunwindstack/tests/ElfTest.cpp b/libunwindstack/tests/ElfTest.cpp
index 098f5f4..afd113d 100644
--- a/libunwindstack/tests/ElfTest.cpp
+++ b/libunwindstack/tests/ElfTest.cpp
@@ -132,7 +132,7 @@
   ASSERT_FALSE(elf.GetFunctionName(0, &name, &func_offset));
 
   bool finished;
-  ASSERT_FALSE(elf.Step(0, 0, nullptr, nullptr, &finished));
+  ASSERT_FALSE(elf.Step(0, 0, 0, nullptr, nullptr, &finished));
 }
 
 TEST_F(ElfTest, elf32_invalid_machine) {
@@ -306,7 +306,7 @@
   elf.FakeSetValid(true);
   elf.FakeSetLoadBias(0);
   bool finished;
-  ASSERT_TRUE(elf.Step(0x1000, 0x2000, &regs, &process_memory, &finished));
+  ASSERT_TRUE(elf.Step(0x1000, 0x1000, 0x2000, &regs, &process_memory, &finished));
   EXPECT_FALSE(finished);
   EXPECT_EQ(15U, regs.pc());
   EXPECT_EQ(13U, regs.sp());
@@ -339,7 +339,7 @@
   EXPECT_CALL(*interface, Step(0x1000, &regs, &process_memory, &finished))
       .WillOnce(::testing::Return(true));
 
-  ASSERT_TRUE(elf.Step(0x1000, 0x2000, &regs, &process_memory, &finished));
+  ASSERT_TRUE(elf.Step(0x1004, 0x1000, 0x2000, &regs, &process_memory, &finished));
 }
 
 TEST_F(ElfTest, step_in_interface_non_zero_load_bias) {
@@ -355,12 +355,12 @@
 
   // Invalid relative pc given load_bias.
   bool finished;
-  ASSERT_FALSE(elf.Step(0x1000, 0x2000, &regs, &process_memory, &finished));
+  ASSERT_FALSE(elf.Step(0x1004, 0x1000, 0x2000, &regs, &process_memory, &finished));
 
   EXPECT_CALL(*interface, Step(0x3300, &regs, &process_memory, &finished))
       .WillOnce(::testing::Return(true));
 
-  ASSERT_TRUE(elf.Step(0x7300, 0x2000, &regs, &process_memory, &finished));
+  ASSERT_TRUE(elf.Step(0x7304, 0x7300, 0x2000, &regs, &process_memory, &finished));
 }
 
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/ElfTestUtils.cpp b/libunwindstack/tests/ElfTestUtils.cpp
index 069386b..69163ac 100644
--- a/libunwindstack/tests/ElfTestUtils.cpp
+++ b/libunwindstack/tests/ElfTestUtils.cpp
@@ -47,7 +47,7 @@
   ehdr->e_ehsize = sizeof(Ehdr);
 }
 
-static std::string GetTestFileDirectory() {
+std::string TestGetFileDirectory() {
   std::string exec(testing::internal::GetArgvs()[0]);
   auto const value = exec.find_last_of('/');
   if (value == std::string::npos) {
@@ -102,7 +102,7 @@
   offset = symtab_offset + 0x100;
   if (init_gnu_debugdata) {
     // Read in the compressed elf data and copy it in.
-    name = GetTestFileDirectory();
+    name = TestGetFileDirectory();
     if (elf_class == ELFCLASS32) {
       name += "elf32.xz";
     } else {
diff --git a/libunwindstack/tests/ElfTestUtils.h b/libunwindstack/tests/ElfTestUtils.h
index 6ef00e1..62cd59a 100644
--- a/libunwindstack/tests/ElfTestUtils.h
+++ b/libunwindstack/tests/ElfTestUtils.h
@@ -18,6 +18,7 @@
 #define _LIBUNWINDSTACK_TESTS_ELF_TEST_UTILS_H
 
 #include <functional>
+#include <string>
 
 namespace unwindstack {
 
@@ -30,6 +31,8 @@
 void TestInitGnuDebugdata(uint32_t elf_class, uint32_t machine_type, bool init_gnu_debudata,
                           TestCopyFuncType copy_func);
 
+std::string TestGetFileDirectory();
+
 }  // namespace unwindstack
 
 #endif  // _LIBUNWINDSTACK_TESTS_ELF_TEST_UTILS_H
diff --git a/libunwindstack/tests/UnwindOfflineTest.cpp b/libunwindstack/tests/UnwindOfflineTest.cpp
new file mode 100644
index 0000000..d24abe4
--- /dev/null
+++ b/libunwindstack/tests/UnwindOfflineTest.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+#include <unwindstack/Maps.h>
+#include <unwindstack/Memory.h>
+#include <unwindstack/Regs.h>
+#include <unwindstack/Unwinder.h>
+
+#include "Machine.h"
+
+#include "ElfTestUtils.h"
+
+namespace unwindstack {
+
+static std::string DumpFrames(Unwinder& unwinder) {
+  std::string str;
+  for (size_t i = 0; i < unwinder.NumFrames(); i++) {
+    str += unwinder.FormatFrame(i) + "\n";
+  }
+  return str;
+}
+
+TEST(UnwindOfflineTest, pc_straddle_arm32) {
+  std::string dir(TestGetFileDirectory() + "offline/straddle_arm32/");
+
+  MemoryOffline* memory = new MemoryOffline;
+  ASSERT_TRUE(memory->Init((dir + "stack.data").c_str(), 0));
+
+  FILE* fp = fopen((dir + "regs.txt").c_str(), "r");
+  ASSERT_TRUE(fp != nullptr);
+  RegsArm regs;
+  uint64_t reg_value;
+  ASSERT_EQ(1, fscanf(fp, "pc: %" SCNx64 "\n", &reg_value));
+  regs[ARM_REG_PC] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "sp: %" SCNx64 "\n", &reg_value));
+  regs[ARM_REG_SP] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "lr: %" SCNx64 "\n", &reg_value));
+  regs[ARM_REG_LR] = reg_value;
+  regs.SetFromRaw();
+  fclose(fp);
+
+  fp = fopen((dir + "maps.txt").c_str(), "r");
+  ASSERT_TRUE(fp != nullptr);
+  // The file is guaranteed to be less than 4096 bytes.
+  std::vector<char> buffer(4096);
+  ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp));
+  fclose(fp);
+
+  BufferMaps maps(buffer.data());
+  ASSERT_TRUE(maps.Parse());
+
+  ASSERT_EQ(static_cast<uint32_t>(EM_ARM), regs.MachineType());
+
+  std::shared_ptr<Memory> process_memory(memory);
+
+  char* cwd = getcwd(nullptr, 0);
+  ASSERT_EQ(0, chdir(dir.c_str()));
+  Unwinder unwinder(128, &maps, &regs, process_memory);
+  unwinder.Unwind();
+  ASSERT_EQ(0, chdir(cwd));
+  free(cwd);
+
+  std::string frame_info(DumpFrames(unwinder));
+  ASSERT_EQ(4U, unwinder.NumFrames()) << "Unwind:\n" << frame_info;
+  EXPECT_EQ(
+      "  #00 pc 0001a9f8  libc.so (abort+63)\n"
+      "  #01 pc 00006a1b  libbase.so (_ZN7android4base14DefaultAborterEPKc+6)\n"
+      "  #02 pc 00007441  libbase.so (_ZN7android4base10LogMessageD2Ev+748)\n"
+      "  #03 pc 00015149  /does/not/exist/libhidlbase.so\n",
+      frame_info);
+}
+
+TEST(UnwindOfflineTest, pc_straddle_arm64) {
+  std::string dir(TestGetFileDirectory() + "offline/straddle_arm64/");
+
+  MemoryOffline* memory = new MemoryOffline;
+  ASSERT_TRUE(memory->Init((dir + "stack.data").c_str(), 0));
+
+  FILE* fp = fopen((dir + "regs.txt").c_str(), "r");
+  ASSERT_TRUE(fp != nullptr);
+  RegsArm64 regs;
+  uint64_t reg_value;
+  ASSERT_EQ(1, fscanf(fp, "pc: %" SCNx64 "\n", &reg_value));
+  regs[ARM64_REG_PC] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "sp: %" SCNx64 "\n", &reg_value));
+  regs[ARM64_REG_SP] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "lr: %" SCNx64 "\n", &reg_value));
+  regs[ARM64_REG_LR] = reg_value;
+  ASSERT_EQ(1, fscanf(fp, "x29: %" SCNx64 "\n", &reg_value));
+  regs[ARM64_REG_R29] = reg_value;
+  regs.SetFromRaw();
+  fclose(fp);
+
+  fp = fopen((dir + "maps.txt").c_str(), "r");
+  ASSERT_TRUE(fp != nullptr);
+  // The file is guaranteed to be less than 4096 bytes.
+  std::vector<char> buffer(4096);
+  ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp));
+  fclose(fp);
+
+  BufferMaps maps(buffer.data());
+  ASSERT_TRUE(maps.Parse());
+
+  ASSERT_EQ(static_cast<uint32_t>(EM_AARCH64), regs.MachineType());
+
+  std::shared_ptr<Memory> process_memory(memory);
+
+  char* cwd = getcwd(nullptr, 0);
+  ASSERT_EQ(0, chdir(dir.c_str()));
+  Unwinder unwinder(128, &maps, &regs, process_memory);
+  unwinder.Unwind();
+  ASSERT_EQ(0, chdir(cwd));
+  free(cwd);
+
+  std::string frame_info(DumpFrames(unwinder));
+  ASSERT_EQ(6U, unwinder.NumFrames()) << "Unwind:\n" << frame_info;
+  EXPECT_EQ(
+      "  #00 pc 0000000000429fd8  libunwindstack_test (SignalInnerFunction+24)\n"
+      "  #01 pc 000000000042a078  libunwindstack_test (SignalMiddleFunction+8)\n"
+      "  #02 pc 000000000042a08c  libunwindstack_test (SignalOuterFunction+8)\n"
+      "  #03 pc 000000000042d8fc  libunwindstack_test "
+      "(_ZN11unwindstackL19RemoteThroughSignalEij+20)\n"
+      "  #04 pc 000000000042d8d8  libunwindstack_test "
+      "(_ZN11unwindstack37UnwindTest_remote_through_signal_Test8TestBodyEv+32)\n"
+      "  #05 pc 0000000000455d70  libunwindstack_test (_ZN7testing4Test3RunEv+392)\n",
+      frame_info);
+}
+
+}  // namespace unwindstack
diff --git a/libunwindstack/tests/files/offline/straddle_arm32/libbase.so b/libunwindstack/tests/files/offline/straddle_arm32/libbase.so
new file mode 100644
index 0000000..d1f16ee
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm32/libbase.so
Binary files differ
diff --git a/libunwindstack/tests/files/offline/straddle_arm32/libc.so b/libunwindstack/tests/files/offline/straddle_arm32/libc.so
new file mode 100644
index 0000000..4dc19ca
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm32/libc.so
Binary files differ
diff --git a/libunwindstack/tests/files/offline/straddle_arm32/maps.txt b/libunwindstack/tests/files/offline/straddle_arm32/maps.txt
new file mode 100644
index 0000000..8c26479
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm32/maps.txt
@@ -0,0 +1,4 @@
+f2d9a000-f2da7fff r-xp 00000000 00:00 0   libbase.so
+f3002000-f3005fff rw-p 00000000 00:00 0   [stack:25941]
+f31d0000-f326bfff r-xp 00000000 00:00 0   libc.so
+f3352000-f336bfff r-xp 00000000 00:00 0   /does/not/exist/libhidlbase.so
diff --git a/libunwindstack/tests/files/offline/straddle_arm32/regs.txt b/libunwindstack/tests/files/offline/straddle_arm32/regs.txt
new file mode 100644
index 0000000..3baedf3
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm32/regs.txt
@@ -0,0 +1,3 @@
+pc: f31ea9f8
+sp: e9c866f8
+lr: f31f179f
diff --git a/libunwindstack/tests/files/offline/straddle_arm32/stack.data b/libunwindstack/tests/files/offline/straddle_arm32/stack.data
new file mode 100644
index 0000000..83aeb4a
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm32/stack.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/straddle_arm64/libunwindstack_test b/libunwindstack/tests/files/offline/straddle_arm64/libunwindstack_test
new file mode 100644
index 0000000..092fc3a
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm64/libunwindstack_test
Binary files differ
diff --git a/libunwindstack/tests/files/offline/straddle_arm64/maps.txt b/libunwindstack/tests/files/offline/straddle_arm64/maps.txt
new file mode 100644
index 0000000..bdf29b5
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm64/maps.txt
@@ -0,0 +1,2 @@
+00000064d05ab000-00000064d0a6cfff r-xp 00000000 00:00 0  libunwindstack_test
+0000007fe0d64000-0000007fe0d84fff rw-p 00000000 00:00 0  [stack]
diff --git a/libunwindstack/tests/files/offline/straddle_arm64/regs.txt b/libunwindstack/tests/files/offline/straddle_arm64/regs.txt
new file mode 100644
index 0000000..ff8a936
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm64/regs.txt
@@ -0,0 +1,4 @@
+pc: 00000064d09d4fd8
+sp: 0000007fe0d84040
+lr: 00000064d09d507c
+x29: 0000007fe0d84070
diff --git a/libunwindstack/tests/files/offline/straddle_arm64/stack.data b/libunwindstack/tests/files/offline/straddle_arm64/stack.data
new file mode 100644
index 0000000..824d0e2
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm64/stack.data
Binary files differ