/* * 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 "utils/MemoryFake.h" #include "utils/OfflineUnwindUtils.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_ = MapInfo::Create(0x1000, 0x20000, 0, PROT_READ | PROT_WRITE, tf_->path); } void TearDown() override { delete memory_; } void MultipleThreadTest(std::string expected_build_id); MemoryFake* memory_; ElfFake* elf_; ElfInterfaceFake* elf_interface_; std::unique_ptr elf_container_; std::shared_ptr map_info_; std::unique_ptr tf_; }; TEST_F(MapInfoGetBuildIDTest, no_elf_and_no_valid_elf_in_memory) { auto info = MapInfo::Create(0x1000, 0x2000, 0, PROT_READ, ""); EXPECT_EQ("", info->GetBuildID()); EXPECT_EQ("", info->GetPrintableBuildID()); } TEST_F(MapInfoGetBuildIDTest, from_elf) { map_info_->set_elf(elf_container_.release()); elf_interface_->FakeSetBuildID("FAKE_BUILD_ID"); EXPECT_EQ("FAKE_BUILD_ID", map_info_->GetBuildID()); EXPECT_EQ("46414b455f4255494c445f4944", map_info_->GetPrintableBuildID()); } TEST_F(MapInfoGetBuildIDTest, from_elf_no_sign_extension) { map_info_->set_elf(elf_container_.release()); std::string build_id = {static_cast(0xfa), static_cast(0xab), static_cast(0x12), static_cast(0x02)}; elf_interface_->FakeSetBuildID(build_id); EXPECT_EQ("\xFA\xAB\x12\x2", map_info_->GetBuildID()); EXPECT_EQ("faab1202", map_info_->GetPrintableBuildID()); } 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_->set_elf(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 = sizeof("GNU"); note_header.n_descsz = sizeof("ELF_BUILDID") - 1; 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", note_header.n_namesz); note_offset += note_header.n_namesz; memcpy(¬e_section[note_offset], "ELF_BUILDID", note_header.n_descsz); 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()); EXPECT_EQ("454c465f4255494c444944", map_info_->GetPrintableBuildID()); } TEST_F(MapInfoGetBuildIDTest, multiple_thread_elf_exists_in_memory) { InitElfData(tf_->fd); MultipleThreadTest("ELF_BUILDID"); } TEST_F(MapInfoGetBuildIDTest, real_elf) { auto map_info = MapInfo::Create(0x1000, 0x20000, 0, PROT_READ | PROT_WRITE, GetOfflineFilesDirectory() + "empty_arm64/libc.so"); EXPECT_EQ("6df0590c4920f4c7b9f34fe833f37d54", map_info->GetPrintableBuildID()); } TEST_F(MapInfoGetBuildIDTest, in_device_map) { auto map_info = MapInfo::Create(0x1000, 0x20000, 0, PROT_READ | PROT_WRITE | MAPS_FLAGS_DEVICE_MAP, GetOfflineFilesDirectory() + "empty_arm64/libc.so"); EXPECT_EQ("", map_info->GetPrintableBuildID()); } } // namespace unwindstack