From bf373edc3c12a6e6df91770fde36075cc9d3e257 Mon Sep 17 00:00:00 2001 From: Christopher Ferris Date: Wed, 16 Jan 2019 17:23:39 -0800 Subject: [PATCH] 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 --- libunwindstack/Android.bp | 2 + libunwindstack/Elf.cpp | 25 ++- libunwindstack/ElfInterface.cpp | 132 +++++++++++-- libunwindstack/MapInfo.cpp | 48 +++++ .../benchmarks/unwind_benchmarks.cpp | 62 ++++++ libunwindstack/include/unwindstack/Elf.h | 6 +- .../include/unwindstack/ElfInterface.h | 15 +- libunwindstack/include/unwindstack/MapInfo.h | 15 +- libunwindstack/tests/ElfFake.h | 8 +- libunwindstack/tests/ElfInterfaceTest.cpp | 17 +- libunwindstack/tests/ElfTest.cpp | 2 +- .../tests/MapInfoGetBuildIDTest.cpp | 183 ++++++++++++++++++ libunwindstack/tests/MapInfoGetElfTest.cpp | 6 + libunwindstack/tools/unwind_info.cpp | 4 +- 14 files changed, 478 insertions(+), 47 deletions(-) create mode 100644 libunwindstack/tests/MapInfoGetBuildIDTest.cpp diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp index 73dc92167..d1b827179 100644 --- a/libunwindstack/Android.bp +++ b/libunwindstack/Android.bp @@ -191,6 +191,7 @@ cc_test { "tests/LocalUnwinderTest.cpp", "tests/LogFake.cpp", "tests/MapInfoCreateMemoryTest.cpp", + "tests/MapInfoGetBuildIDTest.cpp", "tests/MapInfoGetElfTest.cpp", "tests/MapInfoGetLoadBiasTest.cpp", "tests/MapInfoTest.cpp", @@ -343,6 +344,7 @@ cc_benchmark { ], shared_libs: [ + "libbase", "libunwindstack", ], } diff --git a/libunwindstack/Elf.cpp b/libunwindstack/Elf.cpp index 5b586a278..2f5eed9f8 100644 --- a/libunwindstack/Elf.cpp +++ b/libunwindstack/Elf.cpp @@ -140,8 +140,11 @@ bool Elf::GetGlobalVariable(const std::string& name, uint64_t* memory_address) { return true; } -bool Elf::GetBuildID(std::string* build_id) { - return valid_ && interface_->GetBuildID(build_id); +std::string Elf::GetBuildID() { + if (!valid_) { + return ""; + } + return interface_->GetBuildID(); } void Elf::GetLastError(ErrorData* data) { @@ -384,4 +387,22 @@ bool Elf::CacheGet(MapInfo* info) { return false; } +std::string Elf::GetBuildID(Memory* memory) { + if (!IsValidElf(memory)) { + return ""; + } + + uint8_t class_type; + if (!memory->Read(EI_CLASS, &class_type, 1)) { + return ""; + } + + if (class_type == ELFCLASS32) { + return ElfInterface::ReadBuildIDFromMemory(memory); + } else if (class_type == ELFCLASS64) { + return ElfInterface::ReadBuildIDFromMemory(memory); + } + return ""; +} + } // namespace unwindstack diff --git a/libunwindstack/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp index 709e6c977..c1b98d999 100644 --- a/libunwindstack/ElfInterface.cpp +++ b/libunwindstack/ElfInterface.cpp @@ -238,31 +238,31 @@ void ElfInterface::ReadProgramHeaders(const EhdrType& ehdr, uint64_t* load_bias) } template -bool ElfInterface::ReadBuildID(std::string* build_id) { +std::string ElfInterface::ReadBuildID() { // Ensure there is no overflow in any of the calulations below. uint64_t tmp; if (__builtin_add_overflow(gnu_build_id_offset_, gnu_build_id_size_, &tmp)) { - return false; + return ""; } uint64_t offset = 0; while (offset < gnu_build_id_size_) { if (gnu_build_id_size_ - offset < sizeof(NhdrType)) { - return false; + return ""; } NhdrType hdr; if (!memory_->ReadFully(gnu_build_id_offset_ + offset, &hdr, sizeof(hdr))) { - return false; + return ""; } offset += sizeof(hdr); if (gnu_build_id_size_ - offset < hdr.n_namesz) { - return false; + return ""; } if (hdr.n_namesz > 0) { std::string name(hdr.n_namesz, '\0'); if (!memory_->ReadFully(gnu_build_id_offset_ + offset, &(name[0]), hdr.n_namesz)) { - return false; + return ""; } // Trim trailing \0 as GNU is stored as a C string in the ELF file. @@ -273,18 +273,20 @@ bool ElfInterface::ReadBuildID(std::string* build_id) { offset += (hdr.n_namesz + 3) & ~3; if (name == "GNU" && hdr.n_type == NT_GNU_BUILD_ID) { - if (gnu_build_id_size_ - offset < hdr.n_descsz) { - return false; + if (gnu_build_id_size_ - offset < hdr.n_descsz || hdr.n_descsz == 0) { + return ""; } - build_id->resize(hdr.n_descsz); - return memory_->ReadFully(gnu_build_id_offset_ + offset, &(*build_id)[0], - hdr.n_descsz); + std::string build_id(hdr.n_descsz - 1, '\0'); + if (memory_->ReadFully(gnu_build_id_offset_ + offset, &build_id[0], hdr.n_descsz)) { + return build_id; + } + return ""; } } // Align hdr.n_descsz to next power multiple of 4. See man 5 elf. offset += (hdr.n_descsz + 3) & ~3; } - return false; + return ""; } template @@ -536,6 +538,103 @@ void ElfInterface::GetMaxSizeWithTemplate(Memory* memory, uint64_t* size) { *size = ehdr.e_shoff + ehdr.e_shentsize * ehdr.e_shnum; } +template +bool GetBuildIDInfo(Memory* memory, uint64_t* build_id_offset, uint64_t* build_id_size) { + EhdrType ehdr; + if (!memory->ReadFully(0, &ehdr, sizeof(ehdr))) { + return false; + } + + uint64_t offset = ehdr.e_shoff; + uint64_t sec_offset; + uint64_t sec_size; + ShdrType shdr; + if (ehdr.e_shstrndx >= ehdr.e_shnum) { + return false; + } + + uint64_t sh_offset = offset + ehdr.e_shstrndx * ehdr.e_shentsize; + if (!memory->ReadFully(sh_offset, &shdr, sizeof(shdr))) { + return false; + } + sec_offset = shdr.sh_offset; + sec_size = shdr.sh_size; + + // Skip the first header, it's always going to be NULL. + offset += ehdr.e_shentsize; + for (size_t i = 1; i < ehdr.e_shnum; i++, offset += ehdr.e_shentsize) { + if (!memory->ReadFully(offset, &shdr, sizeof(shdr))) { + return false; + } + std::string name; + if (shdr.sh_type == SHT_NOTE && shdr.sh_name < sec_size && + memory->ReadString(sec_offset + shdr.sh_name, &name) && name == ".note.gnu.build-id") { + *build_id_offset = shdr.sh_offset; + *build_id_size = shdr.sh_size; + return true; + } + } + + return false; +} + +template +std::string ElfInterface::ReadBuildIDFromMemory(Memory* memory) { + uint64_t note_offset; + uint64_t note_size; + if (!GetBuildIDInfo(memory, ¬e_offset, ¬e_size)) { + return ""; + } + + // Ensure there is no overflow in any of the calculations below. + uint64_t tmp; + if (__builtin_add_overflow(note_offset, note_size, &tmp)) { + return ""; + } + + uint64_t offset = 0; + while (offset < note_size) { + if (note_size - offset < sizeof(NhdrType)) { + return ""; + } + NhdrType hdr; + if (!memory->ReadFully(note_offset + offset, &hdr, sizeof(hdr))) { + return ""; + } + offset += sizeof(hdr); + + if (note_size - offset < hdr.n_namesz) { + return ""; + } + if (hdr.n_namesz > 0) { + std::string name(hdr.n_namesz, '\0'); + if (!memory->ReadFully(note_offset + offset, &(name[0]), hdr.n_namesz)) { + return ""; + } + + // Trim trailing \0 as GNU is stored as a C string in the ELF file. + if (name.back() == '\0') name.resize(name.size() - 1); + + // Align hdr.n_namesz to next power multiple of 4. See man 5 elf. + offset += (hdr.n_namesz + 3) & ~3; + + if (name == "GNU" && hdr.n_type == NT_GNU_BUILD_ID) { + if (note_size - offset < hdr.n_descsz || hdr.n_descsz == 0) { + return ""; + } + std::string build_id(hdr.n_descsz - 1, '\0'); + if (memory->ReadFully(note_offset + offset, &build_id[0], hdr.n_descsz)) { + return build_id; + } + return ""; + } + } + // Align hdr.n_descsz to next power multiple of 4. See man 5 elf. + offset += (hdr.n_descsz + 3) & ~3; + } + return ""; +} + // Instantiate all of the needed template functions. template void ElfInterface::InitHeadersWithTemplate(uint64_t); template void ElfInterface::InitHeadersWithTemplate(uint64_t); @@ -551,8 +650,8 @@ template void ElfInterface::ReadProgramHeaders(const Elf template void ElfInterface::ReadSectionHeaders(const Elf32_Ehdr&); template void ElfInterface::ReadSectionHeaders(const Elf64_Ehdr&); -template bool ElfInterface::ReadBuildID(std::string*); -template bool ElfInterface::ReadBuildID(std::string*); +template std::string ElfInterface::ReadBuildID(); +template std::string ElfInterface::ReadBuildID(); template bool ElfInterface::GetSonameWithTemplate(std::string*); template bool ElfInterface::GetSonameWithTemplate(std::string*); @@ -571,4 +670,9 @@ template void ElfInterface::GetMaxSizeWithTemplate(Memory*, uint64_t template uint64_t ElfInterface::GetLoadBias(Memory*); template uint64_t ElfInterface::GetLoadBias(Memory*); +template std::string ElfInterface::ReadBuildIDFromMemory( + Memory*); +template std::string ElfInterface::ReadBuildIDFromMemory( + Memory*); + } // namespace unwindstack diff --git a/libunwindstack/MapInfo.cpp b/libunwindstack/MapInfo.cpp index b9d8b3db7..f3199711b 100644 --- a/libunwindstack/MapInfo.cpp +++ b/libunwindstack/MapInfo.cpp @@ -146,6 +146,10 @@ Memory* MapInfo::CreateMemory(const std::shared_ptr& process_memory) { } } + if (process_memory == nullptr) { + return nullptr; + } + // Need to verify that this elf is valid. It's possible that // only part of the elf file to be mapped into memory is in the executable // map. In this case, there will be another read-only map that includes the @@ -263,4 +267,48 @@ uint64_t MapInfo::GetLoadBias(const std::shared_ptr& process_memory) { return cur_load_bias; } +MapInfo::~MapInfo() { + uintptr_t id = build_id.load(); + if (id != 0) { + delete reinterpret_cast(id); + } +} + +std::string MapInfo::GetBuildID() { + uintptr_t id = build_id.load(); + if (build_id != 0) { + return *reinterpret_cast(id); + } + + // No need to lock, at worst if multiple threads do this at the same + // time it should be detected and only one thread should win and + // save the data. + std::unique_ptr cur_build_id(new std::string); + + // Now need to see if the elf object exists. + // Make sure no other thread is trying to add the elf to this map. + mutex_.lock(); + Elf* elf_obj = elf.get(); + mutex_.unlock(); + if (elf_obj != nullptr) { + *cur_build_id = elf_obj->GetBuildID(); + } else { + // This will only work if we can get the file associated with this memory. + // If this is only available in memory, then the section name information + // is not present and we will not be able to find the build id info. + std::unique_ptr memory(GetFileMemory()); + if (memory != nullptr) { + *cur_build_id = Elf::GetBuildID(memory.get()); + } + } + + id = reinterpret_cast(cur_build_id.get()); + uintptr_t expected_id = 0; + if (build_id.compare_exchange_weak(expected_id, id)) { + // Value saved, so make sure the memory is not freed. + cur_build_id.release(); + } + return *reinterpret_cast(id); +} + } // namespace unwindstack diff --git a/libunwindstack/benchmarks/unwind_benchmarks.cpp b/libunwindstack/benchmarks/unwind_benchmarks.cpp index db0fb548a..8caecc783 100644 --- a/libunwindstack/benchmarks/unwind_benchmarks.cpp +++ b/libunwindstack/benchmarks/unwind_benchmarks.cpp @@ -20,6 +20,9 @@ #include +#include + +#include #include #include #include @@ -80,4 +83,63 @@ static void BM_cached_unwind(benchmark::State& state) { } BENCHMARK(BM_cached_unwind); +static void Initialize(benchmark::State& state, unwindstack::Maps& maps, + unwindstack::MapInfo** build_id_map_info) { + if (!maps.Parse()) { + state.SkipWithError("Failed to parse local maps."); + return; + } + + // Find the libc.so share library and use that for benchmark purposes. + *build_id_map_info = nullptr; + for (unwindstack::MapInfo* map_info : maps) { + if (map_info->offset == 0 && map_info->GetBuildID() != "") { + *build_id_map_info = map_info; + break; + } + } + + if (*build_id_map_info == nullptr) { + state.SkipWithError("Failed to find a map with a BuildID."); + } +} + +static void BM_get_build_id_from_elf(benchmark::State& state) { + unwindstack::LocalMaps maps; + unwindstack::MapInfo* build_id_map_info; + Initialize(state, maps, &build_id_map_info); + + unwindstack::Elf* elf = build_id_map_info->GetElf(std::shared_ptr(), + unwindstack::Regs::CurrentArch()); + if (!elf->valid()) { + state.SkipWithError("Cannot get valid elf from map."); + } + + for (auto _ : state) { + uintptr_t id = build_id_map_info->build_id; + if (id != 0) { + delete reinterpret_cast(id); + build_id_map_info->build_id = 0; + } + benchmark::DoNotOptimize(build_id_map_info->GetBuildID()); + } +} +BENCHMARK(BM_get_build_id_from_elf); + +static void BM_get_build_id_from_file(benchmark::State& state) { + unwindstack::LocalMaps maps; + unwindstack::MapInfo* build_id_map_info; + Initialize(state, maps, &build_id_map_info); + + for (auto _ : state) { + uintptr_t id = build_id_map_info->build_id; + if (id != 0) { + delete reinterpret_cast(id); + build_id_map_info->build_id = 0; + } + benchmark::DoNotOptimize(build_id_map_info->GetBuildID()); + } +} +BENCHMARK(BM_get_build_id_from_file); + BENCHMARK_MAIN(); diff --git a/libunwindstack/include/unwindstack/Elf.h b/libunwindstack/include/unwindstack/Elf.h index 27f72018d..00a249ff6 100644 --- a/libunwindstack/include/unwindstack/Elf.h +++ b/libunwindstack/include/unwindstack/Elf.h @@ -65,8 +65,6 @@ class Elf { bool GetGlobalVariable(const std::string& name, uint64_t* memory_address); - bool GetBuildID(std::string* build_id); - uint64_t GetRelPc(uint64_t pc, const MapInfo* map_info); bool Step(uint64_t rel_pc, uint64_t adjusted_rel_pc, Regs* regs, Memory* process_memory, @@ -74,6 +72,8 @@ class Elf { ElfInterface* CreateInterfaceFromMemory(Memory* memory); + std::string GetBuildID(); + uint64_t GetLoadBias() { return load_bias_; } bool IsValidPc(uint64_t pc); @@ -102,6 +102,8 @@ class Elf { static uint64_t GetLoadBias(Memory* memory); + static std::string GetBuildID(Memory* memory); + static void SetCachingEnabled(bool enable); static bool CachingEnabled() { return cache_enabled_; } diff --git a/libunwindstack/include/unwindstack/ElfInterface.h b/libunwindstack/include/unwindstack/ElfInterface.h index 52992d9c9..d41bb13e3 100644 --- a/libunwindstack/include/unwindstack/ElfInterface.h +++ b/libunwindstack/include/unwindstack/ElfInterface.h @@ -62,7 +62,7 @@ class ElfInterface { virtual bool GetGlobalVariable(const std::string& name, uint64_t* memory_address) = 0; - virtual bool GetBuildID(std::string* build_id) = 0; + virtual std::string GetBuildID() = 0; virtual bool Step(uint64_t rel_pc, Regs* regs, Memory* process_memory, bool* finished); @@ -100,6 +100,9 @@ class ElfInterface { template static uint64_t GetLoadBias(Memory* memory); + template + static std::string ReadBuildIDFromMemory(Memory* memory); + protected: template void InitHeadersWithTemplate(uint64_t load_bias); @@ -128,7 +131,7 @@ class ElfInterface { static void GetMaxSizeWithTemplate(Memory* memory, uint64_t* size); template - bool ReadBuildID(std::string* build_id); + std::string ReadBuildID(); Memory* memory_; std::unordered_map pt_loads_; @@ -192,9 +195,7 @@ class ElfInterface32 : public ElfInterface { return ElfInterface::GetGlobalVariableWithTemplate(name, memory_address); } - bool GetBuildID(std::string* build_id) { - return ElfInterface::ReadBuildID(build_id); - } + std::string GetBuildID() override { return ElfInterface::ReadBuildID(); } static void GetMaxSize(Memory* memory, uint64_t* size) { GetMaxSizeWithTemplate(memory, size); @@ -226,9 +227,7 @@ class ElfInterface64 : public ElfInterface { return ElfInterface::GetGlobalVariableWithTemplate(name, memory_address); } - bool GetBuildID(std::string* build_id) { - return ElfInterface::ReadBuildID(build_id); - } + std::string GetBuildID() override { return ElfInterface::ReadBuildID(); } static void GetMaxSize(Memory* memory, uint64_t* size) { GetMaxSizeWithTemplate(memory, size); diff --git a/libunwindstack/include/unwindstack/MapInfo.h b/libunwindstack/include/unwindstack/MapInfo.h index a9febd17d..5143ff1f5 100644 --- a/libunwindstack/include/unwindstack/MapInfo.h +++ b/libunwindstack/include/unwindstack/MapInfo.h @@ -38,7 +38,8 @@ struct MapInfo { flags(flags), name(name), prev_map(map_info), - load_bias(static_cast(-1)) {} + load_bias(static_cast(-1)), + build_id(0) {} MapInfo(MapInfo* map_info, uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, const std::string& name) : start(start), @@ -47,8 +48,9 @@ struct MapInfo { flags(flags), name(name), prev_map(map_info), - load_bias(static_cast(-1)) {} - ~MapInfo() = default; + load_bias(static_cast(-1)), + build_id(0) {} + ~MapInfo(); uint64_t start = 0; uint64_t end = 0; @@ -68,6 +70,11 @@ struct MapInfo { std::atomic_uint64_t load_bias; + // This is a pointer to a new'd std::string. + // Using an atomic value means that we don't need to lock and will + // make it easier to move to a fine grained lock in the future. + std::atomic_uintptr_t build_id; + // This function guarantees it will never return nullptr. Elf* GetElf(const std::shared_ptr& process_memory, ArchEnum expected_arch); @@ -77,6 +84,8 @@ struct MapInfo { bool GetFunctionName(uint64_t addr, std::string* name, uint64_t* func_offset); + std::string GetBuildID(); + private: MapInfo(const MapInfo&) = delete; void operator=(const MapInfo&) = delete; diff --git a/libunwindstack/tests/ElfFake.h b/libunwindstack/tests/ElfFake.h index c2bd0f6c6..946bc3c21 100644 --- a/libunwindstack/tests/ElfFake.h +++ b/libunwindstack/tests/ElfFake.h @@ -72,9 +72,7 @@ class ElfInterfaceFake : public ElfInterface { 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 @@ class ElfInterfaceFake : public ElfInterface { 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 @@ class ElfInterfaceFake : public ElfInterface { private: std::unordered_map globals_; + std::string fake_build_id_; static std::deque functions_; static std::deque steps_; diff --git a/libunwindstack/tests/ElfInterfaceTest.cpp b/libunwindstack/tests/ElfInterfaceTest.cpp index 6023dc4b8..7239749c6 100644 --- a/libunwindstack/tests/ElfInterfaceTest.cpp +++ b/libunwindstack/tests/ElfInterfaceTest.cpp @@ -1228,9 +1228,7 @@ void ElfInterfaceTest::BuildID() { 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 @@ -1292,9 +1290,7 @@ void ElfInterfaceTest::BuildIDTwoNotes() { 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 @@ -1346,8 +1342,7 @@ void ElfInterfaceTest::BuildIDSectionTooSmallForName () { 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 @@ -1399,8 +1394,7 @@ void ElfInterfaceTest::BuildIDSectionTooSmallForDesc () { 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 @@ -1452,8 +1446,7 @@ void ElfInterfaceTest::BuildIDSectionTooSmallForHeader () { 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 f7689ceb7..1ff23064f 100644 --- a/libunwindstack/tests/ElfTest.cpp +++ b/libunwindstack/tests/ElfTest.cpp @@ -311,7 +311,7 @@ class ElfInterfaceMock : public ElfInterface { 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 000000000..3b89c5961 --- /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 +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#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 elf_container_; + std::unique_ptr map_info_; + std::unique_ptr 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 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(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(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(sizeof(shdr)), write(fd, &shdr, sizeof(shdr))); + + offset = 0xf500; + ASSERT_EQ(offset, lseek(fd, offset, SEEK_SET)); + ASSERT_EQ(static_cast(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(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 99f8fa3b0..d7b848544 100644 --- a/libunwindstack/tests/MapInfoGetElfTest.cpp +++ b/libunwindstack/tests/MapInfoGetElfTest.cpp @@ -88,6 +88,12 @@ TEST_F(MapInfoGetElfTest, valid32) { ASSERT_TRUE(elf->valid()); EXPECT_EQ(static_cast(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(), ARCH_ARM); + ASSERT_TRUE(elf != nullptr); + ASSERT_FALSE(elf->valid()); } TEST_F(MapInfoGetElfTest, valid64) { diff --git a/libunwindstack/tools/unwind_info.cpp b/libunwindstack/tools/unwind_info.cpp index 3f2dfb07d..3f5b88ba8 100644 --- a/libunwindstack/tools/unwind_info.cpp +++ b/libunwindstack/tools/unwind_info.cpp @@ -123,8 +123,8 @@ int GetElfInfo(const char* file, uint64_t offset) { printf("Soname: %s\n", soname.c_str()); } - std::string build_id; - if (elf.GetBuildID(&build_id)) { + std::string build_id = elf.GetBuildID(); + if (!build_id.empty()) { printf("Build ID: "); for (size_t i = 0; i < build_id.size(); ++i) { printf("%02hhx", build_id[i]);