blob: 90428366217f2b4302d421788d7db2c5b63eca85 [file] [log] [blame]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/lib/unwinder/elf_module_cache.h"
#include <elf.h>
#include <inttypes.h>
#include <cstring>
#include <vector>
#include <gtest/gtest.h>
#include "src/lib/unwinder/memory.h"
#include "src/lib/unwinder/module.h"
namespace unwinder {
namespace {
struct FakeElfModule {
Elf64_Ehdr ehdr;
Elf64_Phdr phdr;
};
void SetupFakeElf(FakeElfModule& fake, uint64_t vaddr, uint64_t memsz) {
memset(&fake, 0, sizeof(fake));
memcpy(fake.ehdr.e_ident, ELFMAG, SELFMAG);
fake.ehdr.e_ident[EI_CLASS] = ELFCLASS64;
fake.ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
fake.ehdr.e_ident[EI_VERSION] = EV_CURRENT;
fake.ehdr.e_type = ET_DYN;
fake.ehdr.e_machine = EM_X86_64;
fake.ehdr.e_version = EV_CURRENT;
fake.ehdr.e_phoff = offsetof(FakeElfModule, phdr);
fake.ehdr.e_phnum = 1;
fake.ehdr.e_phentsize = sizeof(Elf64_Phdr);
fake.phdr.p_type = PT_LOAD;
fake.phdr.p_vaddr = vaddr;
fake.phdr.p_memsz = memsz;
fake.phdr.p_flags = PF_R | PF_X;
}
class FakeMemory : public Memory {
public:
void AddModule(uint64_t load_addr, void* real_addr, uint64_t size) {
modules_[load_addr] = {.real_ptr = reinterpret_cast<uint64_t>(real_addr), .size = size};
}
Error ReadBytes(uint64_t addr, uint64_t size, void* dst) override {
for (auto const& [load_addr, info] : modules_) {
if (addr >= load_addr && addr + size <= load_addr + info.size) {
uint64_t offset = addr - load_addr;
memcpy(dst, reinterpret_cast<void*>(info.real_ptr + offset), size);
return Success();
}
}
return Error("Address not mapped in FakeMemory: %#" PRIx64 "", addr);
}
private:
struct ModuleInfo {
uint64_t real_ptr;
uint64_t size;
};
std::map<uint64_t, ModuleInfo> modules_;
};
TEST(ElfModuleCache, Lookup) {
FakeElfModule fake1;
SetupFakeElf(fake1, 0x1000, 0x1000);
FakeElfModule fake2;
SetupFakeElf(fake2, 0x1000, 0x1000);
constexpr uint64_t kLoadAddr1 = 0x10000000;
constexpr uint64_t kLoadAddr2 = 0x20000000;
FakeMemory mem;
mem.AddModule(kLoadAddr1, &fake1, sizeof(fake1));
mem.AddModule(kLoadAddr2, &fake2, sizeof(fake2));
std::vector<Module> modules;
modules.emplace_back(kLoadAddr1, &mem, Module::AddressMode::kProcess);
modules.emplace_back(kLoadAddr2, &mem, Module::AddressMode::kProcess);
ElfModuleCache cache(modules);
// PC in module 1: load_addr1 + 0x1500
auto result1 = cache.GetLoadedElfModuleForPc(kLoadAddr1 + 0x1500);
ASSERT_TRUE(result1.is_ok()) << result1.error_value().msg();
EXPECT_EQ(result1.value().get().load_address(), kLoadAddr1);
// PC in module 2: load_addr2 + 0x1500
auto result2 = cache.GetLoadedElfModuleForPc(kLoadAddr2 + 0x1500);
ASSERT_TRUE(result2.is_ok()) << result2.error_value().msg();
EXPECT_EQ(result2.value().get().load_address(), kLoadAddr2);
// PC not in any module (below first)
EXPECT_TRUE(cache.GetLoadedElfModuleForPc(0).is_error());
// PC just outside ranges
EXPECT_TRUE(cache.GetLoadedElfModuleForPc(kLoadAddr1 + 0x0500).is_error());
EXPECT_TRUE(cache.GetLoadedElfModuleForPc(kLoadAddr1 + 0x0fff).is_error());
EXPECT_TRUE(cache.GetLoadedElfModuleForPc(kLoadAddr1 + 0x2000).is_error());
EXPECT_TRUE(cache.GetLoadedElfModuleForPc(kLoadAddr1 + 0x2500).is_error());
// IsValidPC
EXPECT_TRUE(cache.IsValidPC(kLoadAddr1 + 0x1500));
EXPECT_TRUE(cache.IsValidPC(kLoadAddr2 + 0x1500));
EXPECT_FALSE(cache.IsValidPC(kLoadAddr1 + 0x0500));
EXPECT_FALSE(cache.IsValidPC(kLoadAddr2 + 0x2500));
}
TEST(ElfModuleCache, LookupEmpty) {
std::vector<Module> modules;
ElfModuleCache cache(modules);
ASSERT_FALSE(cache.IsValidPC(0x12345));
ASSERT_TRUE(cache.GetLoadedElfModuleForPc(0x12345).is_error());
}
} // namespace
} // namespace unwinder