Add caching of build id in MapInfo object.
Change the GetBuildID function to return a std::string.
Added benchmark to check how long it takes to get the build id from
a file versus an elf object.
Added a way to get an elf without passing in a valid process_memory and
added tests for this.
Test: New unit tests.
Change-Id: I3029019767e0181c758d611fe635bc1bf72d6e8e
diff --git a/libunwindstack/tests/ElfFake.h b/libunwindstack/tests/ElfFake.h
index c2bd0f6..946bc3c 100644
--- a/libunwindstack/tests/ElfFake.h
+++ b/libunwindstack/tests/ElfFake.h
@@ -72,9 +72,7 @@
bool GetFunctionName(uint64_t, std::string*, uint64_t*) override;
bool GetGlobalVariable(const std::string&, uint64_t*) override;
- bool GetBuildID(std::string*) override {
- return false;
- }
+ std::string GetBuildID() override { return fake_build_id_; }
bool Step(uint64_t, Regs*, Memory*, bool*) override;
@@ -82,6 +80,9 @@
globals_[global] = offset;
}
+ void FakeSetBuildID(std::string& build_id) { fake_build_id_ = build_id; }
+ void FakeSetBuildID(const char* build_id) { fake_build_id_ = build_id; }
+
static void FakePushFunctionData(const FunctionData data) { functions_.push_back(data); }
static void FakePushStepData(const StepData data) { steps_.push_back(data); }
@@ -96,6 +97,7 @@
private:
std::unordered_map<std::string, uint64_t> globals_;
+ std::string fake_build_id_;
static std::deque<FunctionData> functions_;
static std::deque<StepData> steps_;
diff --git a/libunwindstack/tests/ElfInterfaceTest.cpp b/libunwindstack/tests/ElfInterfaceTest.cpp
index 6023dc4..7239749 100644
--- a/libunwindstack/tests/ElfInterfaceTest.cpp
+++ b/libunwindstack/tests/ElfInterfaceTest.cpp
@@ -1228,9 +1228,7 @@
uint64_t load_bias = 0;
ASSERT_TRUE(elf->Init(&load_bias));
- std::string build_id;
- ASSERT_TRUE(elf->GetBuildID(&build_id));
- EXPECT_STREQ(build_id.c_str(), "BUILDID");
+ ASSERT_EQ("BUILDID", elf->GetBuildID());
}
template <typename Ehdr, typename Shdr, typename Nhdr, typename ElfInterfaceType>
@@ -1292,9 +1290,7 @@
uint64_t load_bias = 0;
ASSERT_TRUE(elf->Init(&load_bias));
- std::string build_id;
- ASSERT_TRUE(elf->GetBuildID(&build_id));
- EXPECT_STREQ(build_id.c_str(), "BUILDID");
+ ASSERT_EQ("BUILDID", elf->GetBuildID());
}
template <typename Ehdr, typename Shdr, typename Nhdr, typename ElfInterfaceType>
@@ -1346,8 +1342,7 @@
uint64_t load_bias = 0;
ASSERT_TRUE(elf->Init(&load_bias));
- std::string build_id;
- ASSERT_FALSE(elf->GetBuildID(&build_id));
+ ASSERT_EQ("", elf->GetBuildID());
}
template <typename Ehdr, typename Shdr, typename Nhdr, typename ElfInterfaceType>
@@ -1399,8 +1394,7 @@
uint64_t load_bias = 0;
ASSERT_TRUE(elf->Init(&load_bias));
- std::string build_id;
- ASSERT_FALSE(elf->GetBuildID(&build_id));
+ ASSERT_EQ("", elf->GetBuildID());
}
template <typename Ehdr, typename Shdr, typename Nhdr, typename ElfInterfaceType>
@@ -1452,8 +1446,7 @@
uint64_t load_bias = 0;
ASSERT_TRUE(elf->Init(&load_bias));
- std::string build_id;
- ASSERT_FALSE(elf->GetBuildID(&build_id));
+ ASSERT_EQ("", elf->GetBuildID());
}
TEST_F(ElfInterfaceTest, build_id32) {
diff --git a/libunwindstack/tests/ElfTest.cpp b/libunwindstack/tests/ElfTest.cpp
index f7689ce..1ff2306 100644
--- a/libunwindstack/tests/ElfTest.cpp
+++ b/libunwindstack/tests/ElfTest.cpp
@@ -311,7 +311,7 @@
void InitHeaders(uint64_t) override {}
bool GetSoname(std::string*) override { return false; }
bool GetFunctionName(uint64_t, std::string*, uint64_t*) override { return false; }
- bool GetBuildID(std::string*) override { return false; }
+ std::string GetBuildID() override { return ""; }
MOCK_METHOD4(Step, bool(uint64_t, Regs*, Memory*, bool*));
MOCK_METHOD2(GetGlobalVariable, bool(const std::string&, uint64_t*));
diff --git a/libunwindstack/tests/MapInfoGetBuildIDTest.cpp b/libunwindstack/tests/MapInfoGetBuildIDTest.cpp
new file mode 100644
index 0000000..3b89c59
--- /dev/null
+++ b/libunwindstack/tests/MapInfoGetBuildIDTest.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2019 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 <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/test_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <unwindstack/Elf.h>
+#include <unwindstack/MapInfo.h>
+#include <unwindstack/Maps.h>
+#include <unwindstack/Memory.h>
+
+#include "ElfFake.h"
+#include "ElfTestUtils.h"
+#include "MemoryFake.h"
+
+namespace unwindstack {
+
+class MapInfoGetBuildIDTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ tf_.reset(new TemporaryFile);
+
+ memory_ = new MemoryFake;
+ elf_ = new ElfFake(new MemoryFake);
+ elf_interface_ = new ElfInterfaceFake(memory_);
+ elf_->FakeSetInterface(elf_interface_);
+ elf_container_.reset(elf_);
+ map_info_.reset(new MapInfo(nullptr, 0x1000, 0x20000, 0, PROT_READ | PROT_WRITE, tf_->path));
+ }
+
+ void MultipleThreadTest(std::string expected_build_id);
+
+ MemoryFake* memory_;
+ ElfFake* elf_;
+ ElfInterfaceFake* elf_interface_;
+ std::unique_ptr<ElfFake> elf_container_;
+ std::unique_ptr<MapInfo> map_info_;
+ std::unique_ptr<TemporaryFile> tf_;
+};
+
+TEST_F(MapInfoGetBuildIDTest, no_elf_and_no_valid_elf_in_memory) {
+ MapInfo info(nullptr, 0x1000, 0x2000, 0, PROT_READ, "");
+
+ EXPECT_EQ("", info.GetBuildID());
+}
+
+TEST_F(MapInfoGetBuildIDTest, from_elf) {
+ map_info_->elf.reset(elf_container_.release());
+ elf_interface_->FakeSetBuildID("FAKE_BUILD_ID");
+
+ EXPECT_EQ("FAKE_BUILD_ID", map_info_->GetBuildID());
+}
+
+void MapInfoGetBuildIDTest::MultipleThreadTest(std::string expected_build_id) {
+ static constexpr size_t kNumConcurrentThreads = 100;
+
+ std::string build_id_values[kNumConcurrentThreads];
+ std::vector<std::thread*> threads;
+
+ std::atomic_bool wait;
+ wait = true;
+ // Create all of the threads and have them do the GetLoadBias at the same time
+ // to make it likely that a race will occur.
+ for (size_t i = 0; i < kNumConcurrentThreads; i++) {
+ std::thread* thread = new std::thread([i, this, &wait, &build_id_values]() {
+ while (wait)
+ ;
+ build_id_values[i] = map_info_->GetBuildID();
+ });
+ threads.push_back(thread);
+ }
+
+ // Set them all going and wait for the threads to finish.
+ wait = false;
+ for (auto thread : threads) {
+ thread->join();
+ delete thread;
+ }
+
+ // Now verify that all of the elf files are exactly the same and valid.
+ for (size_t i = 0; i < kNumConcurrentThreads; i++) {
+ EXPECT_EQ(expected_build_id, build_id_values[i]) << "Thread " << i << " mismatched.";
+ }
+}
+
+TEST_F(MapInfoGetBuildIDTest, multiple_thread_elf_exists) {
+ map_info_->elf.reset(elf_container_.release());
+ elf_interface_->FakeSetBuildID("FAKE_BUILD_ID");
+
+ MultipleThreadTest("FAKE_BUILD_ID");
+}
+
+static void InitElfData(int fd) {
+ Elf32_Ehdr ehdr;
+ TestInitEhdr(&ehdr, ELFCLASS32, EM_ARM);
+ ehdr.e_shoff = 0x2000;
+ ehdr.e_shnum = 3;
+ ehdr.e_shentsize = sizeof(Elf32_Shdr);
+ ehdr.e_shstrndx = 2;
+ off_t offset = 0;
+ ASSERT_EQ(offset, lseek(fd, offset, SEEK_SET));
+ ASSERT_EQ(static_cast<ssize_t>(sizeof(ehdr)), write(fd, &ehdr, sizeof(ehdr)));
+
+ char note_section[128];
+ Elf32_Nhdr note_header = {};
+ note_header.n_namesz = 4; // "GNU"
+ note_header.n_descsz = 12; // "ELF_BUILDID"
+ note_header.n_type = NT_GNU_BUILD_ID;
+ memcpy(¬e_section, ¬e_header, sizeof(note_header));
+ size_t note_offset = sizeof(note_header);
+ memcpy(¬e_section[note_offset], "GNU", sizeof("GNU"));
+ note_offset += sizeof("GNU");
+ memcpy(¬e_section[note_offset], "ELF_BUILDID", sizeof("ELF_BUILDID"));
+ note_offset += sizeof("ELF_BUILDID");
+
+ Elf32_Shdr shdr = {};
+ shdr.sh_type = SHT_NOTE;
+ shdr.sh_name = 0x500;
+ shdr.sh_offset = 0xb000;
+ shdr.sh_size = sizeof(note_section);
+ offset += ehdr.e_shoff + sizeof(shdr);
+ ASSERT_EQ(offset, lseek(fd, offset, SEEK_SET));
+ ASSERT_EQ(static_cast<ssize_t>(sizeof(shdr)), write(fd, &shdr, sizeof(shdr)));
+
+ // The string data for section header names.
+ memset(&shdr, 0, sizeof(shdr));
+ shdr.sh_type = SHT_STRTAB;
+ shdr.sh_name = 0x20000;
+ shdr.sh_offset = 0xf000;
+ shdr.sh_size = 0x1000;
+ offset += sizeof(shdr);
+ ASSERT_EQ(offset, lseek(fd, offset, SEEK_SET));
+ ASSERT_EQ(static_cast<ssize_t>(sizeof(shdr)), write(fd, &shdr, sizeof(shdr)));
+
+ offset = 0xf500;
+ ASSERT_EQ(offset, lseek(fd, offset, SEEK_SET));
+ ASSERT_EQ(static_cast<ssize_t>(sizeof(".note.gnu.build-id")),
+ write(fd, ".note.gnu.build-id", sizeof(".note.gnu.build-id")));
+
+ offset = 0xb000;
+ ASSERT_EQ(offset, lseek(fd, offset, SEEK_SET));
+ ASSERT_EQ(static_cast<ssize_t>(sizeof(note_section)),
+ write(fd, note_section, sizeof(note_section)));
+}
+
+TEST_F(MapInfoGetBuildIDTest, from_memory) {
+ InitElfData(tf_->fd);
+
+ EXPECT_EQ("ELF_BUILDID", map_info_->GetBuildID());
+}
+
+TEST_F(MapInfoGetBuildIDTest, multiple_thread_elf_exists_in_memory) {
+ InitElfData(tf_->fd);
+
+ MultipleThreadTest("ELF_BUILDID");
+}
+
+} // namespace unwindstack
diff --git a/libunwindstack/tests/MapInfoGetElfTest.cpp b/libunwindstack/tests/MapInfoGetElfTest.cpp
index 99f8fa3..d7b8485 100644
--- a/libunwindstack/tests/MapInfoGetElfTest.cpp
+++ b/libunwindstack/tests/MapInfoGetElfTest.cpp
@@ -88,6 +88,12 @@
ASSERT_TRUE(elf->valid());
EXPECT_EQ(static_cast<uint32_t>(EM_ARM), elf->machine_type());
EXPECT_EQ(ELFCLASS32, elf->class_type());
+
+ // Now verify that an empty process memory returns an invalid elf object.
+ info.elf.reset();
+ elf = info.GetElf(std::shared_ptr<Memory>(), ARCH_ARM);
+ ASSERT_TRUE(elf != nullptr);
+ ASSERT_FALSE(elf->valid());
}
TEST_F(MapInfoGetElfTest, valid64) {