Add a global elf cache.

Bug: 65682279

Test: Ran new unit tests.
Change-Id: I19c64614b2b11a27f58204d4cc34913c02e04c36
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 8dae956..28d7e64 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -124,6 +124,7 @@
         "tests/DwarfOpTest.cpp",
         "tests/DwarfSectionTest.cpp",
         "tests/DwarfSectionImplTest.cpp",
+        "tests/ElfCacheTest.cpp",
         "tests/ElfFake.cpp",
         "tests/ElfInterfaceArmTest.cpp",
         "tests/ElfInterfaceTest.cpp",
diff --git a/libunwindstack/Elf.cpp b/libunwindstack/Elf.cpp
index f120da2..dbf772e 100644
--- a/libunwindstack/Elf.cpp
+++ b/libunwindstack/Elf.cpp
@@ -35,6 +35,10 @@
 
 namespace unwindstack {
 
+bool Elf::cache_enabled_;
+std::unordered_map<std::string, std::shared_ptr<Elf>>* Elf::cache_;
+std::mutex* Elf::cache_lock_;
+
 bool Elf::Init(bool init_gnu_debugdata) {
   load_bias_ = 0;
   if (!memory_) {
@@ -301,4 +305,42 @@
   return 0;
 }
 
+void Elf::SetCachingEnabled(bool enable) {
+  if (!cache_enabled_ && enable) {
+    cache_enabled_ = true;
+    cache_ = new std::unordered_map<std::string, std::shared_ptr<Elf>>;
+    cache_lock_ = new std::mutex;
+  } else if (cache_enabled_ && !enable) {
+    cache_enabled_ = false;
+    delete cache_;
+    delete cache_lock_;
+  }
+}
+
+void Elf::CacheLock() {
+  cache_lock_->lock();
+}
+
+void Elf::CacheUnlock() {
+  cache_lock_->unlock();
+}
+
+void Elf::CacheAdd(MapInfo* info) {
+  if (info->offset == 0) {
+    (*cache_)[info->name] = info->elf;
+  } else {
+    std::string name(info->name + ':' + std::to_string(info->offset));
+    (*cache_)[name] = info->elf;
+  }
+}
+
+bool Elf::CacheGet(const std::string& name, std::shared_ptr<Elf>* elf) {
+  auto entry = cache_->find(name);
+  if (entry != cache_->end()) {
+    *elf = entry->second;
+    return true;
+  }
+  return false;
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/MapInfo.cpp b/libunwindstack/MapInfo.cpp
index 89fe038..0c15335 100644
--- a/libunwindstack/MapInfo.cpp
+++ b/libunwindstack/MapInfo.cpp
@@ -109,16 +109,46 @@
   // Make sure no other thread is trying to add the elf to this map.
   std::lock_guard<std::mutex> guard(mutex_);
 
-  if (elf) {
-    return elf;
+  if (elf.get() != nullptr) {
+    return elf.get();
   }
 
-  elf = new Elf(CreateMemory(process_memory));
-  elf->Init(init_gnu_debugdata);
+  bool locked = false;
+  if (Elf::CachingEnabled() && !name.empty()) {
+    Elf::CacheLock();
+    locked = true;
+    if (offset != 0) {
+      std::string hash(name + ':' + std::to_string(offset));
+      if (Elf::CacheGet(hash, &elf)) {
+        Elf::CacheUnlock();
+        return elf.get();
+      }
+    } else if (Elf::CacheGet(name, &elf)) {
+      Elf::CacheUnlock();
+      return elf.get();
+    }
+  }
 
+  Memory* memory = CreateMemory(process_memory);
+  if (locked && offset != 0 && elf_offset != 0) {
+    // In this case, the whole file is the elf, need to see if the elf
+    // data was cached.
+    if (Elf::CacheGet(name, &elf)) {
+      delete memory;
+      Elf::CacheUnlock();
+      return elf.get();
+    }
+  }
+  elf.reset(new Elf(memory));
   // If the init fails, keep the elf around as an invalid object so we
   // don't try to reinit the object.
-  return elf;
+  elf->Init(init_gnu_debugdata);
+
+  if (locked) {
+    Elf::CacheAdd(this);
+    Elf::CacheUnlock();
+  }
+  return elf.get();
 }
 
 uint64_t MapInfo::GetLoadBias(const std::shared_ptr<Memory>& process_memory) {
diff --git a/libunwindstack/include/unwindstack/Elf.h b/libunwindstack/include/unwindstack/Elf.h
index e562d6d..a874709 100644
--- a/libunwindstack/include/unwindstack/Elf.h
+++ b/libunwindstack/include/unwindstack/Elf.h
@@ -22,6 +22,7 @@
 #include <memory>
 #include <mutex>
 #include <string>
+#include <unordered_map>
 
 #include <unwindstack/ElfInterface.h>
 #include <unwindstack/Memory.h>
@@ -96,6 +97,14 @@
 
   static uint64_t GetLoadBias(Memory* memory);
 
+  static void SetCachingEnabled(bool enable);
+  static bool CachingEnabled() { return cache_enabled_; }
+
+  static void CacheLock();
+  static void CacheUnlock();
+  static void CacheAdd(MapInfo* info);
+  static bool CacheGet(const std::string& name, std::shared_ptr<Elf>* elf);
+
  protected:
   bool valid_ = false;
   uint64_t load_bias_ = 0;
@@ -109,6 +118,10 @@
 
   std::unique_ptr<Memory> gnu_debugdata_memory_;
   std::unique_ptr<ElfInterface> gnu_debugdata_interface_;
+
+  static bool cache_enabled_;
+  static std::unordered_map<std::string, std::shared_ptr<Elf>>* cache_;
+  static std::mutex* cache_lock_;
 };
 
 }  // namespace unwindstack
diff --git a/libunwindstack/include/unwindstack/MapInfo.h b/libunwindstack/include/unwindstack/MapInfo.h
index 22e48f7..a57fe68 100644
--- a/libunwindstack/include/unwindstack/MapInfo.h
+++ b/libunwindstack/include/unwindstack/MapInfo.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 
 #include <atomic>
+#include <memory>
 #include <mutex>
 #include <string>
 
@@ -40,14 +41,14 @@
         flags(flags),
         name(name),
         load_bias(static_cast<uint64_t>(-1)) {}
-  ~MapInfo() { delete elf; }
+  ~MapInfo() = default;
 
   uint64_t start = 0;
   uint64_t end = 0;
   uint64_t offset = 0;
   uint16_t flags = 0;
   std::string name;
-  Elf* elf = nullptr;
+  std::shared_ptr<Elf> elf;
   // This value is only non-zero if the offset is non-zero but there is
   // no elf signature found at that offset. This indicates that the
   // entire file is represented by the Memory object returned by CreateMemory,
diff --git a/libunwindstack/tests/ElfCacheTest.cpp b/libunwindstack/tests/ElfCacheTest.cpp
new file mode 100644
index 0000000..0086c9e
--- /dev/null
+++ b/libunwindstack/tests/ElfCacheTest.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <elf.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <unwindstack/Elf.h>
+#include <unwindstack/MapInfo.h>
+
+#include "ElfTestUtils.h"
+#include "MemoryFake.h"
+
+namespace unwindstack {
+
+class ElfCacheTest : public ::testing::Test {
+ protected:
+  static void SetUpTestCase() { memory_.reset(new MemoryFake); }
+
+  void SetUp() override { Elf::SetCachingEnabled(true); }
+
+  void TearDown() override { Elf::SetCachingEnabled(false); }
+
+  void WriteElfFile(uint64_t offset, TemporaryFile* tf, uint32_t type) {
+    ASSERT_TRUE(type == EM_ARM || type == EM_386 || type == EM_X86_64);
+    size_t ehdr_size;
+    Elf32_Ehdr ehdr32;
+    Elf64_Ehdr ehdr64;
+    void* ptr;
+    if (type == EM_ARM || type == EM_386) {
+      ehdr_size = sizeof(ehdr32);
+      ptr = &ehdr32;
+      TestInitEhdr(&ehdr32, ELFCLASS32, type);
+    } else {
+      ehdr_size = sizeof(ehdr64);
+      ptr = &ehdr64;
+      TestInitEhdr(&ehdr64, ELFCLASS64, type);
+    }
+
+    ASSERT_EQ(offset, static_cast<uint64_t>(lseek(tf->fd, offset, SEEK_SET)));
+    ASSERT_TRUE(android::base::WriteFully(tf->fd, ptr, ehdr_size));
+  }
+
+  void VerifyWithinSameMap(bool cache_enabled);
+  void VerifySameMap(bool cache_enabled);
+
+  static std::shared_ptr<Memory> memory_;
+};
+
+std::shared_ptr<Memory> ElfCacheTest::memory_;
+
+void ElfCacheTest::VerifySameMap(bool cache_enabled) {
+  if (!cache_enabled) {
+    Elf::SetCachingEnabled(false);
+  }
+
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  WriteElfFile(0, &tf, EM_ARM);
+  close(tf.fd);
+
+  uint64_t start = 0x1000;
+  uint64_t end = 0x20000;
+  MapInfo info1(start, end, 0, 0x5, tf.path);
+  MapInfo info2(start, end, 0, 0x5, tf.path);
+
+  Elf* elf1 = info1.GetElf(memory_, true);
+  ASSERT_TRUE(elf1->valid());
+  Elf* elf2 = info2.GetElf(memory_, true);
+  ASSERT_TRUE(elf2->valid());
+
+  if (cache_enabled) {
+    EXPECT_EQ(elf1, elf2);
+  } else {
+    EXPECT_NE(elf1, elf2);
+  }
+}
+
+TEST_F(ElfCacheTest, no_caching) {
+  VerifySameMap(false);
+}
+
+TEST_F(ElfCacheTest, caching_invalid_elf) {
+  VerifySameMap(true);
+}
+
+void ElfCacheTest::VerifyWithinSameMap(bool cache_enabled) {
+  if (!cache_enabled) {
+    Elf::SetCachingEnabled(false);
+  }
+
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  WriteElfFile(0, &tf, EM_ARM);
+  WriteElfFile(0x100, &tf, EM_386);
+  WriteElfFile(0x200, &tf, EM_X86_64);
+  lseek(tf.fd, 0x500, SEEK_SET);
+  uint8_t value = 0;
+  write(tf.fd, &value, 1);
+  close(tf.fd);
+
+  uint64_t start = 0x1000;
+  uint64_t end = 0x20000;
+  // Will have an elf at offset 0 in file.
+  MapInfo info0_1(start, end, 0, 0x5, tf.path);
+  MapInfo info0_2(start, end, 0, 0x5, tf.path);
+  // Will have an elf at offset 0x100 in file.
+  MapInfo info100_1(start, end, 0x100, 0x5, tf.path);
+  MapInfo info100_2(start, end, 0x100, 0x5, tf.path);
+  // Will have an elf at offset 0x200 in file.
+  MapInfo info200_1(start, end, 0x200, 0x5, tf.path);
+  MapInfo info200_2(start, end, 0x200, 0x5, tf.path);
+  // Will have an elf at offset 0 in file.
+  MapInfo info300_1(start, end, 0x300, 0x5, tf.path);
+  MapInfo info300_2(start, end, 0x300, 0x5, tf.path);
+
+  Elf* elf0_1 = info0_1.GetElf(memory_, true);
+  ASSERT_TRUE(elf0_1->valid());
+  EXPECT_EQ(ARCH_ARM, elf0_1->arch());
+  Elf* elf0_2 = info0_2.GetElf(memory_, true);
+  ASSERT_TRUE(elf0_2->valid());
+  EXPECT_EQ(ARCH_ARM, elf0_2->arch());
+  EXPECT_EQ(0U, info0_1.elf_offset);
+  EXPECT_EQ(0U, info0_2.elf_offset);
+  if (cache_enabled) {
+    EXPECT_EQ(elf0_1, elf0_2);
+  } else {
+    EXPECT_NE(elf0_1, elf0_2);
+  }
+
+  Elf* elf100_1 = info100_1.GetElf(memory_, true);
+  ASSERT_TRUE(elf100_1->valid());
+  EXPECT_EQ(ARCH_X86, elf100_1->arch());
+  Elf* elf100_2 = info100_2.GetElf(memory_, true);
+  ASSERT_TRUE(elf100_2->valid());
+  EXPECT_EQ(ARCH_X86, elf100_2->arch());
+  EXPECT_EQ(0U, info100_1.elf_offset);
+  EXPECT_EQ(0U, info100_2.elf_offset);
+  if (cache_enabled) {
+    EXPECT_EQ(elf100_1, elf100_2);
+  } else {
+    EXPECT_NE(elf100_1, elf100_2);
+  }
+
+  Elf* elf200_1 = info200_1.GetElf(memory_, true);
+  ASSERT_TRUE(elf200_1->valid());
+  EXPECT_EQ(ARCH_X86_64, elf200_1->arch());
+  Elf* elf200_2 = info200_2.GetElf(memory_, true);
+  ASSERT_TRUE(elf200_2->valid());
+  EXPECT_EQ(ARCH_X86_64, elf200_2->arch());
+  EXPECT_EQ(0U, info200_1.elf_offset);
+  EXPECT_EQ(0U, info200_2.elf_offset);
+  if (cache_enabled) {
+    EXPECT_EQ(elf200_1, elf200_2);
+  } else {
+    EXPECT_NE(elf200_1, elf200_2);
+  }
+
+  Elf* elf300_1 = info300_1.GetElf(memory_, true);
+  ASSERT_TRUE(elf300_1->valid());
+  EXPECT_EQ(ARCH_ARM, elf300_1->arch());
+  Elf* elf300_2 = info300_2.GetElf(memory_, true);
+  ASSERT_TRUE(elf300_2->valid());
+  EXPECT_EQ(ARCH_ARM, elf300_2->arch());
+  EXPECT_EQ(0x300U, info300_1.elf_offset);
+  EXPECT_EQ(0x300U, info300_2.elf_offset);
+  if (cache_enabled) {
+    EXPECT_EQ(elf300_1, elf300_2);
+    EXPECT_EQ(elf0_1, elf300_1);
+  } else {
+    EXPECT_NE(elf300_1, elf300_2);
+    EXPECT_NE(elf0_1, elf300_1);
+  }
+}
+
+TEST_F(ElfCacheTest, no_caching_valid_elf_offset_non_zero) {
+  VerifyWithinSameMap(false);
+}
+
+TEST_F(ElfCacheTest, caching_valid_elf_offset_non_zero) {
+  VerifyWithinSameMap(true);
+}
+
+}  // namespace unwindstack
diff --git a/libunwindstack/tests/JitDebugTest.cpp b/libunwindstack/tests/JitDebugTest.cpp
index 1a50989..37628f8 100644
--- a/libunwindstack/tests/JitDebugTest.cpp
+++ b/libunwindstack/tests/JitDebugTest.cpp
@@ -62,7 +62,7 @@
     ElfInterfaceFake* interface = new ElfInterfaceFake(elf_memories_.back());
     elf->FakeSetInterface(interface);
     interface->FakeSetGlobalVariable("__jit_debug_descriptor", 0x800);
-    map_info->elf = elf;
+    map_info->elf.reset(elf);
 
     map_info = maps_->Get(5);
     ASSERT_TRUE(map_info != nullptr);
@@ -72,7 +72,7 @@
     interface = new ElfInterfaceFake(elf_memories_.back());
     elf->FakeSetInterface(interface);
     interface->FakeSetGlobalVariable("__jit_debug_descriptor", 0x800);
-    map_info->elf = elf;
+    map_info->elf.reset(elf);
 
     map_info = maps_->Get(6);
     ASSERT_TRUE(map_info != nullptr);
@@ -82,7 +82,7 @@
     interface = new ElfInterfaceFake(elf_memories_.back());
     elf->FakeSetInterface(interface);
     interface->FakeSetGlobalVariable("__jit_debug_descriptor", 0x800);
-    map_info->elf = elf;
+    map_info->elf.reset(elf);
   }
 
   template <typename EhdrType, typename ShdrType>
diff --git a/libunwindstack/tests/MapInfoGetElfTest.cpp b/libunwindstack/tests/MapInfoGetElfTest.cpp
index 948597b..f599503 100644
--- a/libunwindstack/tests/MapInfoGetElfTest.cpp
+++ b/libunwindstack/tests/MapInfoGetElfTest.cpp
@@ -180,16 +180,14 @@
   ASSERT_TRUE(elf != nullptr);
   ASSERT_FALSE(elf->valid());
 
-  delete info.elf;
-  info.elf = nullptr;
+  info.elf.reset();
   info.end = 0xfff;
   elf = info.GetElf(process_memory_, false);
   ASSERT_TRUE(elf != nullptr);
   ASSERT_FALSE(elf->valid());
 
   // Make sure this test is valid.
-  delete info.elf;
-  info.elf = nullptr;
+  info.elf.reset();
   info.end = 0x2000;
   elf = info.GetElf(process_memory_, false);
   ASSERT_TRUE(elf != nullptr);
@@ -328,8 +326,7 @@
   ASSERT_TRUE(elf != nullptr);
   ASSERT_FALSE(elf->valid());
 
-  delete info.elf;
-  info.elf = nullptr;
+  info.elf.reset();
   info.flags = PROT_READ;
   elf = info.GetElf(process_memory_, false);
   ASSERT_TRUE(elf->valid());
@@ -352,15 +349,13 @@
   ASSERT_FALSE(elf->valid());
 
   // Set the name to nothing to verify that it still fails.
-  delete info.elf;
-  info.elf = nullptr;
+  info.elf.reset();
   info.name = "";
   elf = info.GetElf(process_memory_, false);
   ASSERT_FALSE(elf->valid());
 
   // Change the flags and verify the elf is valid now.
-  delete info.elf;
-  info.elf = nullptr;
+  info.elf.reset();
   info.flags = PROT_READ;
   elf = info.GetElf(process_memory_, false);
   ASSERT_TRUE(elf->valid());
@@ -403,7 +398,7 @@
   }
 
   // Now verify that all of the elf files are exactly the same and valid.
-  Elf* elf = info.elf;
+  Elf* elf = info.elf.get();
   ASSERT_TRUE(elf != nullptr);
   EXPECT_TRUE(elf->valid());
   for (size_t i = 0; i < kNumConcurrentThreads; i++) {
diff --git a/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp b/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp
index 631036b..7e64a8a 100644
--- a/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp
+++ b/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp
@@ -69,7 +69,7 @@
 }
 
 TEST_F(MapInfoGetLoadBiasTest, load_bias_cached_from_elf) {
-  map_info_->elf = elf_container_.release();
+  map_info_->elf.reset(elf_container_.release());
 
   elf_->FakeSetLoadBias(0);
   EXPECT_EQ(0U, map_info_->GetLoadBias(process_memory_));
@@ -79,7 +79,7 @@
 }
 
 TEST_F(MapInfoGetLoadBiasTest, elf_exists) {
-  map_info_->elf = elf_container_.release();
+  map_info_->elf.reset(elf_container_.release());
 
   elf_->FakeSetLoadBias(0);
   EXPECT_EQ(0U, map_info_->GetLoadBias(process_memory_));
@@ -122,7 +122,7 @@
 }
 
 TEST_F(MapInfoGetLoadBiasTest, multiple_thread_elf_exists) {
-  map_info_->elf = elf_container_.release();
+  map_info_->elf.reset(elf_container_.release());
   elf_->FakeSetLoadBias(0x1000);
 
   MultipleThreadTest(0x1000);
diff --git a/libunwindstack/tests/RegsTest.cpp b/libunwindstack/tests/RegsTest.cpp
index 15d5458..7c06373 100644
--- a/libunwindstack/tests/RegsTest.cpp
+++ b/libunwindstack/tests/RegsTest.cpp
@@ -184,7 +184,7 @@
   RegsMips64 regs_mips64;
   MapInfo map_info(0x1000, 0x2000);
   Elf* invalid_elf = new Elf(new MemoryFake);
-  map_info.elf = invalid_elf;
+  map_info.elf.reset(invalid_elf);
 
   regs_arm.set_pc(0x1500);
   EXPECT_EQ(0x500U, invalid_elf->GetRelPc(regs_arm.pc(), &map_info));
diff --git a/libunwindstack/tests/UnwinderTest.cpp b/libunwindstack/tests/UnwinderTest.cpp
index bf2c1d8..45cf907 100644
--- a/libunwindstack/tests/UnwinderTest.cpp
+++ b/libunwindstack/tests/UnwinderTest.cpp
@@ -60,7 +60,7 @@
     maps_.FakeClear();
     MapInfo* info = new MapInfo(0x1000, 0x8000, 0, PROT_READ | PROT_WRITE, "/system/fake/libc.so");
     ElfFake* elf = new ElfFake(new MemoryFake);
-    info->elf = elf;
+    info->elf.reset(elf);
     elf->FakeSetInterface(new ElfInterfaceFake(nullptr));
     maps_.FakeAddMapInfo(info);
 
@@ -73,25 +73,25 @@
 
     info = new MapInfo(0x20000, 0x22000, 0, PROT_READ | PROT_WRITE, "/system/fake/libunwind.so");
     elf = new ElfFake(new MemoryFake);
-    info->elf = elf;
+    info->elf.reset(elf);
     elf->FakeSetInterface(new ElfInterfaceFake(nullptr));
     maps_.FakeAddMapInfo(info);
 
     info = new MapInfo(0x23000, 0x24000, 0, PROT_READ | PROT_WRITE, "/fake/libanother.so");
     elf = new ElfFake(new MemoryFake);
-    info->elf = elf;
+    info->elf.reset(elf);
     elf->FakeSetInterface(new ElfInterfaceFake(nullptr));
     maps_.FakeAddMapInfo(info);
 
     info = new MapInfo(0x33000, 0x34000, 0, PROT_READ | PROT_WRITE, "/fake/compressed.so");
     elf = new ElfFake(new MemoryFake);
-    info->elf = elf;
+    info->elf.reset(elf);
     elf->FakeSetInterface(new ElfInterfaceFake(nullptr));
     maps_.FakeAddMapInfo(info);
 
     info = new MapInfo(0x43000, 0x44000, 0x1d000, PROT_READ | PROT_WRITE, "/fake/fake.apk");
     elf = new ElfFake(new MemoryFake);
-    info->elf = elf;
+    info->elf.reset(elf);
     elf->FakeSetInterface(new ElfInterfaceFake(nullptr));
     maps_.FakeAddMapInfo(info);