Merge "Fix issues in libunwindstack."
am: e05d6afb90

Change-Id: I69d62a4d9fb9810ce13e22d57b84b1cc3d29f05f
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 21dd306..5e94388 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -167,6 +167,7 @@
     data: [
         "tests/files/elf32.xz",
         "tests/files/elf64.xz",
+        "tests/files/offline/gnu_debugdata_arm32/*",
         "tests/files/offline/straddle_arm32/*",
         "tests/files/offline/straddle_arm64/*",
     ],
diff --git a/libunwindstack/Elf.cpp b/libunwindstack/Elf.cpp
index f486e23..5ec4a3d 100644
--- a/libunwindstack/Elf.cpp
+++ b/libunwindstack/Elf.cpp
@@ -79,6 +79,7 @@
   uint64_t load_bias;
   if (gnu->Init(&load_bias)) {
     gnu->InitHeaders();
+    interface_->SetGnuDebugdataInterface(gnu);
   } else {
     // Free all of the memory associated with the gnu_debugdata section.
     gnu_debugdata_memory_.reset(nullptr);
@@ -115,17 +116,9 @@
     return true;
   }
 
-  // Adjust the load bias to get the real relative pc.
-  if (adjusted_rel_pc < load_bias_) {
-    return false;
-  }
-  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(adjusted_rel_pc, regs, process_memory, finished) ||
-         (gnu_debugdata_interface_ &&
-          gnu_debugdata_interface_->Step(adjusted_rel_pc, regs, process_memory, finished));
+  return interface_->Step(adjusted_rel_pc, load_bias_, regs, process_memory, finished);
 }
 
 bool Elf::IsValidElf(Memory* memory) {
diff --git a/libunwindstack/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp
index 334cf76..df1642e 100644
--- a/libunwindstack/ElfInterface.cpp
+++ b/libunwindstack/ElfInterface.cpp
@@ -386,16 +386,29 @@
   return false;
 }
 
-bool ElfInterface::Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) {
+bool ElfInterface::Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
+                        bool* finished) {
+  // Adjust the load bias to get the real relative pc.
+  if (pc < load_bias) {
+    return false;
+  }
+  uint64_t adjusted_pc = pc - load_bias;
+
   // Try the eh_frame first.
   DwarfSection* eh_frame = eh_frame_.get();
-  if (eh_frame != nullptr && eh_frame->Step(pc, regs, process_memory, finished)) {
+  if (eh_frame != nullptr && eh_frame->Step(adjusted_pc, regs, process_memory, finished)) {
     return true;
   }
 
   // Try the debug_frame next.
   DwarfSection* debug_frame = debug_frame_.get();
-  if (debug_frame != nullptr && debug_frame->Step(pc, regs, process_memory, finished)) {
+  if (debug_frame != nullptr && debug_frame->Step(adjusted_pc, regs, process_memory, finished)) {
+    return true;
+  }
+
+  // Finally try the gnu_debugdata interface, but always use a zero load bias.
+  if (gnu_debugdata_interface_ != nullptr &&
+      gnu_debugdata_interface_->Step(pc, 0, regs, process_memory, finished)) {
     return true;
   }
   return false;
diff --git a/libunwindstack/ElfInterfaceArm.cpp b/libunwindstack/ElfInterfaceArm.cpp
index 9841e24..5d99bd7 100644
--- a/libunwindstack/ElfInterfaceArm.cpp
+++ b/libunwindstack/ElfInterfaceArm.cpp
@@ -92,16 +92,24 @@
   return true;
 }
 
-bool ElfInterfaceArm::Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) {
+bool ElfInterfaceArm::Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
+                           bool* finished) {
   // Dwarf unwind information is precise about whether a pc is covered or not,
   // but arm unwind information only has ranges of pc. In order to avoid
   // incorrectly doing a bad unwind using arm unwind information for a
   // different function, always try and unwind with the dwarf information first.
-  return ElfInterface32::Step(pc, regs, process_memory, finished) ||
-         StepExidx(pc, regs, process_memory, finished);
+  return ElfInterface32::Step(pc, load_bias, regs, process_memory, finished) ||
+         StepExidx(pc, load_bias, regs, process_memory, finished);
 }
 
-bool ElfInterfaceArm::StepExidx(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) {
+bool ElfInterfaceArm::StepExidx(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
+                                bool* finished) {
+  // Adjust the load bias to get the real relative pc.
+  if (pc < load_bias) {
+    return false;
+  }
+  pc -= load_bias;
+
   RegsArm* regs_arm = reinterpret_cast<RegsArm*>(regs);
   uint64_t entry_offset;
   if (!FindEntry(pc, &entry_offset)) {
diff --git a/libunwindstack/ElfInterfaceArm.h b/libunwindstack/ElfInterfaceArm.h
index eeb2e17..9c067ba 100644
--- a/libunwindstack/ElfInterfaceArm.h
+++ b/libunwindstack/ElfInterfaceArm.h
@@ -70,9 +70,11 @@
 
   bool HandleType(uint64_t offset, uint32_t type, uint64_t load_bias) override;
 
-  bool Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) override;
+  bool Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
+            bool* finished) override;
 
-  bool StepExidx(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished);
+  bool StepExidx(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
+                 bool* finished);
 
   uint64_t start_offset() { return start_offset_; }
 
diff --git a/libunwindstack/MapInfo.cpp b/libunwindstack/MapInfo.cpp
index 51bce8e..89fe038 100644
--- a/libunwindstack/MapInfo.cpp
+++ b/libunwindstack/MapInfo.cpp
@@ -122,13 +122,21 @@
 }
 
 uint64_t MapInfo::GetLoadBias(const std::shared_ptr<Memory>& process_memory) {
+  uint64_t cur_load_bias = load_bias.load();
+  if (cur_load_bias != static_cast<uint64_t>(-1)) {
+    return cur_load_bias;
+  }
+
   {
     // Make sure no other thread is trying to add the elf to this map.
     std::lock_guard<std::mutex> guard(mutex_);
     if (elf != nullptr) {
       if (elf->valid()) {
-        return elf->GetLoadBias();
+        cur_load_bias = elf->GetLoadBias();
+        load_bias = cur_load_bias;
+        return cur_load_bias;
       } else {
+        load_bias = 0;
         return 0;
       }
     }
@@ -137,7 +145,9 @@
   // Call lightweight static function that will only read enough of the
   // elf data to get the load bias.
   std::unique_ptr<Memory> memory(CreateMemory(process_memory));
-  return Elf::GetLoadBias(memory.get());
+  cur_load_bias = Elf::GetLoadBias(memory.get());
+  load_bias = cur_load_bias;
+  return cur_load_bias;
 }
 
 }  // namespace unwindstack
diff --git a/libunwindstack/Maps.cpp b/libunwindstack/Maps.cpp
index 56370c1..4c16212 100644
--- a/libunwindstack/Maps.cpp
+++ b/libunwindstack/Maps.cpp
@@ -202,6 +202,13 @@
   return return_value;
 }
 
+void Maps::Add(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags,
+               const std::string& name, uint64_t load_bias) {
+  MapInfo* map_info = new MapInfo(start, end, offset, flags, name);
+  map_info->load_bias = load_bias;
+  maps_.push_back(map_info);
+}
+
 Maps::~Maps() {
   for (auto& map : maps_) {
     delete map;
@@ -235,61 +242,4 @@
   return "/proc/" + std::to_string(pid_) + "/maps";
 }
 
-bool OfflineMaps::Parse() {
-  // Format of maps information:
-  //   <uint64_t> StartOffset
-  //   <uint64_t> EndOffset
-  //   <uint64_t> offset
-  //   <uint16_t> flags
-  //   <uint16_t> MapNameLength
-  //   <VariableLengthValue> MapName
-  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(file_.c_str(), O_RDONLY)));
-  if (fd == -1) {
-    return false;
-  }
-
-  std::vector<char> name;
-  while (true) {
-    uint64_t start;
-    ssize_t bytes = TEMP_FAILURE_RETRY(read(fd, &start, sizeof(start)));
-    if (bytes == 0) {
-      break;
-    }
-    if (bytes == -1 || bytes != sizeof(start)) {
-      return false;
-    }
-    uint64_t end;
-    bytes = TEMP_FAILURE_RETRY(read(fd, &end, sizeof(end)));
-    if (bytes == -1 || bytes != sizeof(end)) {
-      return false;
-    }
-    uint64_t offset;
-    bytes = TEMP_FAILURE_RETRY(read(fd, &offset, sizeof(offset)));
-    if (bytes == -1 || bytes != sizeof(offset)) {
-      return false;
-    }
-    uint16_t flags;
-    bytes = TEMP_FAILURE_RETRY(read(fd, &flags, sizeof(flags)));
-    if (bytes == -1 || bytes != sizeof(flags)) {
-      return false;
-    }
-    uint16_t len;
-    bytes = TEMP_FAILURE_RETRY(read(fd, &len, sizeof(len)));
-    if (bytes == -1 || bytes != sizeof(len)) {
-      return false;
-    }
-    if (len > 0) {
-      name.resize(len);
-      bytes = TEMP_FAILURE_RETRY(read(fd, name.data(), len));
-      if (bytes == -1 || bytes != len) {
-        return false;
-      }
-      maps_.push_back(new MapInfo(start, end, offset, flags, std::string(name.data(), len)));
-    } else {
-      maps_.push_back(new MapInfo(start, end, offset, flags, ""));
-    }
-  }
-  return true;
-}
-
 }  // namespace unwindstack
diff --git a/libunwindstack/UcontextX86_64.h b/libunwindstack/UcontextX86_64.h
index d689796..2b8bdc4 100644
--- a/libunwindstack/UcontextX86_64.h
+++ b/libunwindstack/UcontextX86_64.h
@@ -38,6 +38,7 @@
 struct x86_64_stack_t {
   uint64_t ss_sp;    // void __user*
   int32_t ss_flags;  // int
+  int32_t pad;
   uint64_t ss_size;  // size_t
 };
 
diff --git a/libunwindstack/include/unwindstack/ElfInterface.h b/libunwindstack/include/unwindstack/ElfInterface.h
index 5cfe74d..5d3cd5e 100644
--- a/libunwindstack/include/unwindstack/ElfInterface.h
+++ b/libunwindstack/include/unwindstack/ElfInterface.h
@@ -60,7 +60,8 @@
   virtual bool GetFunctionName(uint64_t addr, uint64_t load_bias, std::string* name,
                                uint64_t* offset) = 0;
 
-  virtual bool Step(uint64_t rel_pc, Regs* regs, Memory* process_memory, bool* finished);
+  virtual bool Step(uint64_t rel_pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
+                    bool* finished);
 
   Memory* CreateGnuDebugdataMemory();
 
@@ -68,6 +69,8 @@
 
   const std::unordered_map<uint64_t, LoadInfo>& pt_loads() { return pt_loads_; }
 
+  void SetGnuDebugdataInterface(ElfInterface* interface) { gnu_debugdata_interface_ = interface; }
+
   uint64_t dynamic_offset() { return dynamic_offset_; }
   uint64_t dynamic_size() { return dynamic_size_; }
   uint64_t eh_frame_hdr_offset() { return eh_frame_hdr_offset_; }
@@ -134,6 +137,8 @@
 
   std::unique_ptr<DwarfSection> eh_frame_;
   std::unique_ptr<DwarfSection> debug_frame_;
+  // The Elf object owns the gnu_debugdata interface object.
+  ElfInterface* gnu_debugdata_interface_ = nullptr;
 
   std::vector<Symbols*> symbols_;
 };
diff --git a/libunwindstack/include/unwindstack/MapInfo.h b/libunwindstack/include/unwindstack/MapInfo.h
index 6f8ceca..22e48f7 100644
--- a/libunwindstack/include/unwindstack/MapInfo.h
+++ b/libunwindstack/include/unwindstack/MapInfo.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 
+#include <atomic>
 #include <mutex>
 #include <string>
 
@@ -33,7 +34,12 @@
   MapInfo() = default;
   MapInfo(uint64_t start, uint64_t end) : start(start), end(end) {}
   MapInfo(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, const std::string& name)
-      : start(start), end(end), offset(offset), flags(flags), name(name) {}
+      : start(start),
+        end(end),
+        offset(offset),
+        flags(flags),
+        name(name),
+        load_bias(static_cast<uint64_t>(-1)) {}
   ~MapInfo() { delete elf; }
 
   uint64_t start = 0;
@@ -48,6 +54,8 @@
   // instead of a portion of the file.
   uint64_t elf_offset = 0;
 
+  std::atomic_uint64_t load_bias;
+
   // This function guarantees it will never return nullptr.
   Elf* GetElf(const std::shared_ptr<Memory>& process_memory, bool init_gnu_debugdata = false);
 
diff --git a/libunwindstack/include/unwindstack/Maps.h b/libunwindstack/include/unwindstack/Maps.h
index 34fef7f..17a2d28 100644
--- a/libunwindstack/include/unwindstack/Maps.h
+++ b/libunwindstack/include/unwindstack/Maps.h
@@ -42,6 +42,9 @@
 
   virtual const std::string GetMapsFile() const { return ""; }
 
+  void Add(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, const std::string& name,
+           uint64_t load_bias);
+
   typedef std::vector<MapInfo*>::iterator iterator;
   iterator begin() { return maps_.begin(); }
   iterator end() { return maps_.end(); }
@@ -100,14 +103,6 @@
   const std::string file_;
 };
 
-class OfflineMaps : public FileMaps {
- public:
-  OfflineMaps(const std::string& file) : FileMaps(file) {}
-  virtual ~OfflineMaps() = default;
-
-  bool Parse() override;
-};
-
 }  // namespace unwindstack
 
 #endif  // _LIBUNWINDSTACK_MAPS_H
diff --git a/libunwindstack/tests/ElfFake.cpp b/libunwindstack/tests/ElfFake.cpp
index b94a8a4..68de797 100644
--- a/libunwindstack/tests/ElfFake.cpp
+++ b/libunwindstack/tests/ElfFake.cpp
@@ -43,7 +43,7 @@
   return true;
 }
 
-bool ElfInterfaceFake::Step(uint64_t, Regs* regs, Memory*, bool* finished) {
+bool ElfInterfaceFake::Step(uint64_t, uint64_t, Regs* regs, Memory*, bool* finished) {
   if (steps_.empty()) {
     return false;
   }
diff --git a/libunwindstack/tests/ElfFake.h b/libunwindstack/tests/ElfFake.h
index 565b13f..abf9927 100644
--- a/libunwindstack/tests/ElfFake.h
+++ b/libunwindstack/tests/ElfFake.h
@@ -68,8 +68,7 @@
 
   bool GetFunctionName(uint64_t, uint64_t, std::string*, uint64_t*) override;
 
-  bool Step(uint64_t, Regs*, Memory*, bool*) override;
-
+  bool Step(uint64_t, uint64_t, Regs*, Memory*, bool*) override;
 
   static void FakePushFunctionData(const FunctionData data) { functions_.push_back(data); }
   static void FakePushStepData(const StepData data) { steps_.push_back(data); }
diff --git a/libunwindstack/tests/ElfInterfaceArmTest.cpp b/libunwindstack/tests/ElfInterfaceArmTest.cpp
index 5f7cf60..e6763ab 100644
--- a/libunwindstack/tests/ElfInterfaceArmTest.cpp
+++ b/libunwindstack/tests/ElfInterfaceArmTest.cpp
@@ -302,7 +302,7 @@
 
   // FindEntry fails.
   bool finished;
-  ASSERT_FALSE(interface.StepExidx(0x7000, nullptr, nullptr, &finished));
+  ASSERT_FALSE(interface.StepExidx(0x7000, 0, nullptr, nullptr, &finished));
 
   // ExtractEntry should fail.
   interface.FakeSetStartOffset(0x1000);
@@ -315,20 +315,26 @@
   regs[ARM_REG_LR] = 0x20000;
   regs.set_sp(regs[ARM_REG_SP]);
   regs.set_pc(0x1234);
-  ASSERT_FALSE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
+  ASSERT_FALSE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
 
   // Eval should fail.
   memory_.SetData32(0x1004, 0x81000000);
-  ASSERT_FALSE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
+  ASSERT_FALSE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
 
   // Everything should pass.
   memory_.SetData32(0x1004, 0x80b0b0b0);
-  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
   ASSERT_FALSE(finished);
   ASSERT_EQ(0x1000U, regs.sp());
   ASSERT_EQ(0x1000U, regs[ARM_REG_SP]);
   ASSERT_EQ(0x20000U, regs.pc());
   ASSERT_EQ(0x20000U, regs[ARM_REG_PC]);
+
+  // Load bias is non-zero.
+  ASSERT_TRUE(interface.StepExidx(0x8000, 0x1000, &regs, &process_memory_, &finished));
+
+  // Pc too small.
+  ASSERT_FALSE(interface.StepExidx(0x8000, 0x9000, &regs, &process_memory_, &finished));
 }
 
 TEST_F(ElfInterfaceArmTest, StepExidx_pc_set) {
@@ -349,7 +355,7 @@
 
   // Everything should pass.
   bool finished;
-  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
   ASSERT_FALSE(finished);
   ASSERT_EQ(0x10004U, regs.sp());
   ASSERT_EQ(0x10004U, regs[ARM_REG_SP]);
@@ -372,7 +378,7 @@
   regs.set_pc(0x1234);
 
   bool finished;
-  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
   ASSERT_TRUE(finished);
   ASSERT_EQ(0x10000U, regs.sp());
   ASSERT_EQ(0x10000U, regs[ARM_REG_SP]);
@@ -394,7 +400,7 @@
   regs.set_pc(0x1234);
 
   bool finished;
-  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
   ASSERT_TRUE(finished);
   ASSERT_EQ(0x10000U, regs.sp());
   ASSERT_EQ(0x10000U, regs[ARM_REG_SP]);
@@ -420,7 +426,7 @@
   regs.set_pc(0x1234);
 
   bool finished;
-  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
   ASSERT_TRUE(finished);
   ASSERT_EQ(0U, regs.pc());
 
@@ -432,7 +438,7 @@
   regs.set_sp(regs[ARM_REG_SP]);
   regs.set_pc(0x1234);
 
-  ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
+  ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
   ASSERT_TRUE(finished);
   ASSERT_EQ(0U, regs.pc());
 }
diff --git a/libunwindstack/tests/ElfTest.cpp b/libunwindstack/tests/ElfTest.cpp
index 7491d40..5e808ef 100644
--- a/libunwindstack/tests/ElfTest.cpp
+++ b/libunwindstack/tests/ElfTest.cpp
@@ -346,7 +346,7 @@
   void InitHeaders() override {}
   bool GetSoname(std::string*) override { return false; }
   bool GetFunctionName(uint64_t, uint64_t, std::string*, uint64_t*) override { return false; }
-  MOCK_METHOD4(Step, bool(uint64_t, Regs*, Memory*, bool*));
+  MOCK_METHOD5(Step, bool(uint64_t, uint64_t, Regs*, Memory*, bool*));
 };
 
 TEST_F(ElfTest, step_in_interface) {
@@ -361,7 +361,7 @@
   MemoryFake process_memory;
 
   bool finished;
-  EXPECT_CALL(*interface, Step(0x1000, &regs, &process_memory, &finished))
+  EXPECT_CALL(*interface, Step(0x1000, 0, &regs, &process_memory, &finished))
       .WillOnce(::testing::Return(true));
 
   ASSERT_TRUE(elf.Step(0x1004, 0x1000, 0x2000, &regs, &process_memory, &finished));
@@ -382,7 +382,7 @@
   bool finished;
   ASSERT_FALSE(elf.Step(0x1004, 0x1000, 0x2000, &regs, &process_memory, &finished));
 
-  EXPECT_CALL(*interface, Step(0x3300, &regs, &process_memory, &finished))
+  EXPECT_CALL(*interface, Step(0x7300, 0x4000, &regs, &process_memory, &finished))
       .WillOnce(::testing::Return(true));
 
   ASSERT_TRUE(elf.Step(0x7304, 0x7300, 0x2000, &regs, &process_memory, &finished));
diff --git a/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp b/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp
index 44a73a8..631036b 100644
--- a/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp
+++ b/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp
@@ -68,12 +68,23 @@
   EXPECT_EQ(0U, info.GetLoadBias(process_memory_));
 }
 
+TEST_F(MapInfoGetLoadBiasTest, load_bias_cached_from_elf) {
+  map_info_->elf = elf_container_.release();
+
+  elf_->FakeSetLoadBias(0);
+  EXPECT_EQ(0U, map_info_->GetLoadBias(process_memory_));
+
+  elf_->FakeSetLoadBias(0x1000);
+  EXPECT_EQ(0U, map_info_->GetLoadBias(process_memory_));
+}
+
 TEST_F(MapInfoGetLoadBiasTest, elf_exists) {
   map_info_->elf = elf_container_.release();
 
   elf_->FakeSetLoadBias(0);
   EXPECT_EQ(0U, map_info_->GetLoadBias(process_memory_));
 
+  map_info_->load_bias = static_cast<uint64_t>(-1);
   elf_->FakeSetLoadBias(0x1000);
   EXPECT_EQ(0x1000U, map_info_->GetLoadBias(process_memory_));
 }
@@ -141,6 +152,15 @@
   EXPECT_EQ(0xe000U, map_info_->GetLoadBias(process_memory_));
 }
 
+TEST_F(MapInfoGetLoadBiasTest, elf_exists_in_memory_cached) {
+  InitElfData(memory_, map_info_->start);
+
+  EXPECT_EQ(0xe000U, map_info_->GetLoadBias(process_memory_));
+
+  memory_->Clear();
+  EXPECT_EQ(0xe000U, map_info_->GetLoadBias(process_memory_));
+}
+
 TEST_F(MapInfoGetLoadBiasTest, multiple_thread_elf_exists_in_memory) {
   InitElfData(memory_, map_info_->start);
 
diff --git a/libunwindstack/tests/MapsTest.cpp b/libunwindstack/tests/MapsTest.cpp
index 8dc884f..9622ba5 100644
--- a/libunwindstack/tests/MapsTest.cpp
+++ b/libunwindstack/tests/MapsTest.cpp
@@ -44,6 +44,24 @@
   }
 }
 
+TEST(MapsTest, map_add) {
+  Maps maps;
+
+  maps.Add(0x1000, 0x2000, 0, PROT_READ, "fake_map", 0);
+  maps.Add(0x3000, 0x4000, 0x10, 0, "", 0x1234);
+  maps.Add(0x5000, 0x6000, 1, 2, "fake_map2", static_cast<uint64_t>(-1));
+
+  ASSERT_EQ(3U, maps.Total());
+  MapInfo* info = maps.Get(0);
+  ASSERT_EQ(0x1000U, info->start);
+  ASSERT_EQ(0x2000U, info->end);
+  ASSERT_EQ(0U, info->offset);
+  ASSERT_EQ(PROT_READ, info->flags);
+  ASSERT_EQ("fake_map", info->name);
+  ASSERT_EQ(0U, info->elf_offset);
+  ASSERT_EQ(0U, info->load_bias.load());
+}
+
 TEST(MapsTest, verify_parse_line) {
   MapInfo info;
 
diff --git a/libunwindstack/tests/UnwindOfflineTest.cpp b/libunwindstack/tests/UnwindOfflineTest.cpp
index 962f744..8f28036 100644
--- a/libunwindstack/tests/UnwindOfflineTest.cpp
+++ b/libunwindstack/tests/UnwindOfflineTest.cpp
@@ -96,6 +96,54 @@
       frame_info);
 }
 
+TEST(UnwindOfflineTest, pc_in_gnu_debugdata_arm32) {
+  std::string dir(TestGetFileDirectory() + "offline/gnu_debugdata_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;
+  regs.SetFromRaw();
+  fclose(fp);
+
+  fp = fopen((dir + "maps.txt").c_str(), "r");
+  ASSERT_TRUE(fp != nullptr);
+  // The file is guaranteed to be less than 4096 bytes.
+  std::vector<char> buffer(4096);
+  ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp));
+  fclose(fp);
+
+  BufferMaps maps(buffer.data());
+  ASSERT_TRUE(maps.Parse());
+
+  ASSERT_EQ(ARCH_ARM, regs.Arch());
+
+  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(2U, unwinder.NumFrames()) << "Unwind:\n" << frame_info;
+  EXPECT_EQ(
+      "  #00 pc 0006dc49  libandroid_runtime.so "
+      "(_ZN7android14AndroidRuntime15javaThreadShellEPv+80)\n"
+      "  #01 pc 0006dce5  libandroid_runtime.so "
+      "(_ZN7android14AndroidRuntime19javaCreateThreadEtcEPFiPvES1_PKcijPS1_)\n",
+      frame_info);
+}
+
 TEST(UnwindOfflineTest, pc_straddle_arm64) {
   std::string dir(TestGetFileDirectory() + "offline/straddle_arm64/");
 
diff --git a/libunwindstack/tests/files/offline/gnu_debugdata_arm32/libandroid_runtime.so b/libunwindstack/tests/files/offline/gnu_debugdata_arm32/libandroid_runtime.so
new file mode 100644
index 0000000..e4283e6
--- /dev/null
+++ b/libunwindstack/tests/files/offline/gnu_debugdata_arm32/libandroid_runtime.so
Binary files differ
diff --git a/libunwindstack/tests/files/offline/gnu_debugdata_arm32/maps.txt b/libunwindstack/tests/files/offline/gnu_debugdata_arm32/maps.txt
new file mode 100644
index 0000000..1bcddb6
--- /dev/null
+++ b/libunwindstack/tests/files/offline/gnu_debugdata_arm32/maps.txt
@@ -0,0 +1 @@
+f1f10000-f2049000 r-xp 00000000 00:00 0   libandroid_runtime.so
diff --git a/libunwindstack/tests/files/offline/gnu_debugdata_arm32/regs.txt b/libunwindstack/tests/files/offline/gnu_debugdata_arm32/regs.txt
new file mode 100644
index 0000000..c6a93dc
--- /dev/null
+++ b/libunwindstack/tests/files/offline/gnu_debugdata_arm32/regs.txt
@@ -0,0 +1,2 @@
+pc: f1f6dc49
+sp: d8fe6930
diff --git a/libunwindstack/tests/files/offline/gnu_debugdata_arm32/stack.data b/libunwindstack/tests/files/offline/gnu_debugdata_arm32/stack.data
new file mode 100644
index 0000000..19cdf2d
--- /dev/null
+++ b/libunwindstack/tests/files/offline/gnu_debugdata_arm32/stack.data
Binary files differ
diff --git a/libunwindstack/tools/unwind_info.cpp b/libunwindstack/tools/unwind_info.cpp
index a00b2ee..7f2d11d 100644
--- a/libunwindstack/tools/unwind_info.cpp
+++ b/libunwindstack/tools/unwind_info.cpp
@@ -87,7 +87,7 @@
   for (const DwarfFde* fde : *section) {
     // Sometimes there are entries that have empty length, skip those since
     // they don't contain any interesting information.
-    if (fde->pc_start == fde->pc_end) {
+    if (fde == nullptr || fde->pc_start == fde->pc_end) {
       continue;
     }
     printf("\n  PC 0x%" PRIx64, fde->pc_start + load_bias);