| /* |
| * 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()); |
| EXPECT_EQ("", info.GetPrintableBuildID()); |
| } |
| |
| 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()); |
| EXPECT_EQ("46414b455f4255494c445f4944", map_info_->GetPrintableBuildID()); |
| } |
| |
| TEST_F(MapInfoGetBuildIDTest, from_elf_no_sign_extension) { |
| map_info_->elf.reset(elf_container_.release()); |
| |
| std::string build_id = {static_cast<char>(0xfa), static_cast<char>(0xab), static_cast<char>(0x12), |
| static_cast<char>(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<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()); |
| EXPECT_EQ("454c465f4255494c444944", map_info_->GetPrintableBuildID()); |
| } |
| |
| TEST_F(MapInfoGetBuildIDTest, multiple_thread_elf_exists_in_memory) { |
| InitElfData(tf_->fd); |
| |
| MultipleThreadTest("ELF_BUILDID"); |
| } |
| |
| } // namespace unwindstack |