diff --git a/libunwindstack/Elf.cpp b/libunwindstack/Elf.cpp index 4fc7c679f..4f7476d92 100644 --- a/libunwindstack/Elf.cpp +++ b/libunwindstack/Elf.cpp @@ -124,6 +124,28 @@ bool Elf::IsValidElf(Memory* memory) { return true; } +void Elf::GetInfo(Memory* memory, bool* valid, uint64_t* size) { + if (!IsValidElf(memory)) { + *valid = false; + return; + } + *size = 0; + *valid = true; + + // Now read the section header information. + uint8_t class_type; + if (!memory->Read(EI_CLASS, &class_type, 1)) { + return; + } + if (class_type == ELFCLASS32) { + ElfInterface32::GetMaxSize(memory, size); + } else if (class_type == ELFCLASS64) { + ElfInterface64::GetMaxSize(memory, size); + } else { + *valid = false; + } +} + ElfInterface* Elf::CreateInterfaceFromMemory(Memory* memory) { if (!IsValidElf(memory)) { return nullptr; diff --git a/libunwindstack/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp index 75abc85f7..be4f88a26 100644 --- a/libunwindstack/ElfInterface.cpp +++ b/libunwindstack/ElfInterface.cpp @@ -370,6 +370,22 @@ bool ElfInterface::Step(uint64_t pc, Regs* regs, Memory* process_memory) { return false; } +// This is an estimation of the size of the elf file using the location +// of the section headers and size. This assumes that the section headers +// are at the end of the elf file. If the elf has a load bias, the size +// will be too large, but this is acceptable. +template +void ElfInterface::GetMaxSizeWithTemplate(Memory* memory, uint64_t* size) { + EhdrType ehdr; + if (!memory->Read(0, &ehdr, sizeof(ehdr))) { + return; + } + if (ehdr.e_shnum == 0) { + return; + } + *size = ehdr.e_shoff + ehdr.e_shentsize * ehdr.e_shnum; +} + // Instantiate all of the needed template functions. template void ElfInterface::InitHeadersWithTemplate(); template void ElfInterface::InitHeadersWithTemplate(); @@ -391,4 +407,7 @@ template bool ElfInterface::GetFunctionNameWithTemplate(uint64_t, std template bool ElfInterface::GetFunctionNameWithTemplate(uint64_t, std::string*, uint64_t*); +template void ElfInterface::GetMaxSizeWithTemplate(Memory*, uint64_t*); +template void ElfInterface::GetMaxSizeWithTemplate(Memory*, uint64_t*); + } // namespace unwindstack diff --git a/libunwindstack/MapInfo.cpp b/libunwindstack/MapInfo.cpp index d0e1216f3..32722154a 100644 --- a/libunwindstack/MapInfo.cpp +++ b/libunwindstack/MapInfo.cpp @@ -27,6 +27,55 @@ namespace unwindstack { +Memory* MapInfo::GetFileMemory() { + std::unique_ptr memory(new MemoryFileAtOffset); + if (offset == 0) { + if (memory->Init(name, 0)) { + return memory.release(); + } + return nullptr; + } + + // There are two possibilities when the offset is non-zero. + // - There is an elf file embedded in a file. + // - The whole file is an elf file, and the offset needs to be saved. + // + // Map in just the part of the file for the map. If this is not + // a valid elf, then reinit as if the whole file is an elf file. + // If the offset is a valid elf, then determine the size of the map + // and reinit to that size. This is needed because the dynamic linker + // only maps in a portion of the original elf, and never the symbol + // file data. + uint64_t map_size = end - start; + if (!memory->Init(name, offset, map_size)) { + return nullptr; + } + + bool valid; + uint64_t max_size; + Elf::GetInfo(memory.get(), &valid, &max_size); + if (!valid) { + // Init as if the whole file is an elf. + if (memory->Init(name, 0)) { + elf_offset = offset; + return memory.release(); + } + return nullptr; + } + + if (max_size > map_size) { + if (memory->Init(name, offset, max_size)) { + return memory.release(); + } + // Try to reinit using the default map_size. + if (memory->Init(name, offset, map_size)) { + return memory.release(); + } + return nullptr; + } + return memory.release(); +} + Memory* MapInfo::CreateMemory(pid_t pid) { if (end <= start) { return nullptr; @@ -40,33 +89,13 @@ Memory* MapInfo::CreateMemory(pid_t pid) { if (flags & MAPS_FLAGS_DEVICE_MAP) { return nullptr; } - - std::unique_ptr file_memory(new MemoryFileAtOffset); - uint64_t map_size; - if (offset != 0) { - // Only map in a piece of the file. - map_size = end - start; - } else { - map_size = UINT64_MAX; - } - if (file_memory->Init(name, offset, map_size)) { - // It's possible that a non-zero offset might not be pointing to - // valid elf data. Check if this is a valid elf, and if not assume - // that this was meant to incorporate the entire file. - if (offset != 0 && !Elf::IsValidElf(file_memory.get())) { - // Don't bother checking the validity that will happen on the elf init. - if (file_memory->Init(name, 0)) { - elf_offset = offset; - return file_memory.release(); - } - // Fall through if the init fails. - } else { - return file_memory.release(); - } + Memory* memory = GetFileMemory(); + if (memory != nullptr) { + return memory; } } - Memory* memory = nullptr; + Memory* memory; if (pid == getpid()) { memory = new MemoryLocal(); } else { diff --git a/libunwindstack/include/unwindstack/Elf.h b/libunwindstack/include/unwindstack/Elf.h index d89a7464a..4e7eb3447 100644 --- a/libunwindstack/include/unwindstack/Elf.h +++ b/libunwindstack/include/unwindstack/Elf.h @@ -70,6 +70,8 @@ class Elf { static bool IsValidElf(Memory* memory); + static void GetInfo(Memory* memory, bool* valid, uint64_t* size); + protected: bool valid_ = false; std::unique_ptr interface_; diff --git a/libunwindstack/include/unwindstack/ElfInterface.h b/libunwindstack/include/unwindstack/ElfInterface.h index 5cac0d358..142a6259e 100644 --- a/libunwindstack/include/unwindstack/ElfInterface.h +++ b/libunwindstack/include/unwindstack/ElfInterface.h @@ -102,6 +102,9 @@ class ElfInterface { virtual bool HandleType(uint64_t, uint32_t) { return false; } + template + static void GetMaxSizeWithTemplate(Memory* memory, uint64_t* size); + Memory* memory_; std::unordered_map pt_loads_; uint64_t load_bias_ = 0; @@ -146,6 +149,10 @@ class ElfInterface32 : public ElfInterface { bool GetFunctionName(uint64_t addr, std::string* name, uint64_t* func_offset) override { return ElfInterface::GetFunctionNameWithTemplate(addr, name, func_offset); } + + static void GetMaxSize(Memory* memory, uint64_t* size) { + GetMaxSizeWithTemplate(memory, size); + } }; class ElfInterface64 : public ElfInterface { @@ -166,6 +173,10 @@ class ElfInterface64 : public ElfInterface { bool GetFunctionName(uint64_t addr, std::string* name, uint64_t* func_offset) override { return ElfInterface::GetFunctionNameWithTemplate(addr, name, func_offset); } + + static void GetMaxSize(Memory* memory, uint64_t* size) { + GetMaxSizeWithTemplate(memory, size); + } }; } // namespace unwindstack diff --git a/libunwindstack/include/unwindstack/MapInfo.h b/libunwindstack/include/unwindstack/MapInfo.h index 185476799..b8ba92576 100644 --- a/libunwindstack/include/unwindstack/MapInfo.h +++ b/libunwindstack/include/unwindstack/MapInfo.h @@ -40,6 +40,7 @@ struct MapInfo { // instead of a portion of the file. uint64_t elf_offset; + Memory* GetFileMemory(); Memory* CreateMemory(pid_t pid); // This function guarantees it will never return nullptr. Elf* GetElf(pid_t pid, bool init_gnu_debugdata = false); diff --git a/libunwindstack/tests/MapInfoCreateMemoryTest.cpp b/libunwindstack/tests/MapInfoCreateMemoryTest.cpp index 9e45e7812..2aab9c652 100644 --- a/libunwindstack/tests/MapInfoCreateMemoryTest.cpp +++ b/libunwindstack/tests/MapInfoCreateMemoryTest.cpp @@ -38,30 +38,50 @@ namespace unwindstack { class MapInfoCreateMemoryTest : public ::testing::Test { protected: + template + static void InitElf(int fd, uint64_t file_offset, uint64_t sh_offset, uint8_t class_type) { + std::vector buffer(20000); + memset(buffer.data(), 0, buffer.size()); + + Ehdr ehdr; + memset(&ehdr, 0, sizeof(ehdr)); + memcpy(ehdr.e_ident, ELFMAG, SELFMAG); + ehdr.e_ident[EI_CLASS] = class_type; + ehdr.e_shoff = sh_offset; + ehdr.e_shentsize = sizeof(Shdr) + 100; + ehdr.e_shnum = 4; + memcpy(&buffer[file_offset], &ehdr, sizeof(ehdr)); + + ASSERT_TRUE(android::base::WriteFully(fd, buffer.data(), buffer.size())); + } + static void SetUpTestCase() { std::vector buffer(1024); + memset(buffer.data(), 0, buffer.size()); memcpy(buffer.data(), ELFMAG, SELFMAG); - for (size_t i = SELFMAG; i < buffer.size(); i++) { - buffer[i] = i / 256 + 1; - } + buffer[EI_CLASS] = ELFCLASS32; ASSERT_TRUE(android::base::WriteFully(elf_.fd, buffer.data(), buffer.size())); - for (size_t i = 0; i < 0x100; i++) { - buffer[i] = i / 256 + 1; - } + memset(buffer.data(), 0, buffer.size()); memcpy(&buffer[0x100], ELFMAG, SELFMAG); - for (size_t i = 0x100 + SELFMAG; i < buffer.size(); i++) { - buffer[i] = i / 256 + 1; - } + buffer[0x100 + EI_CLASS] = ELFCLASS64; ASSERT_TRUE(android::base::WriteFully(elf_at_100_.fd, buffer.data(), buffer.size())); + + InitElf(elf32_at_map_.fd, 0x1000, 0x2000, ELFCLASS32); + InitElf(elf64_at_map_.fd, 0x2000, 0x3000, ELFCLASS64); } static TemporaryFile elf_; static TemporaryFile elf_at_100_; + + static TemporaryFile elf32_at_map_; + static TemporaryFile elf64_at_map_; }; TemporaryFile MapInfoCreateMemoryTest::elf_; TemporaryFile MapInfoCreateMemoryTest::elf_at_100_; +TemporaryFile MapInfoCreateMemoryTest::elf32_at_map_; +TemporaryFile MapInfoCreateMemoryTest::elf64_at_map_; TEST_F(MapInfoCreateMemoryTest, end_le_start) { MapInfo info{.start = 0x100, .end = 0x100, .offset = 0, .name = elf_.path}; @@ -93,8 +113,9 @@ TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_full_file) { std::vector buffer(1024); ASSERT_TRUE(memory->Read(0, buffer.data(), 1024)); ASSERT_TRUE(memcmp(buffer.data(), ELFMAG, SELFMAG) == 0); - for (size_t i = SELFMAG; i < buffer.size(); i++) { - ASSERT_EQ(i / 256 + 1, buffer[i]) << "Failed at byte " << i; + ASSERT_EQ(ELFCLASS32, buffer[EI_CLASS]); + for (size_t i = EI_CLASS + 1; i < buffer.size(); i++) { + ASSERT_EQ(0, buffer[i]) << "Failed at byte " << i; } ASSERT_FALSE(memory->Read(1024, buffer.data(), 1)); @@ -113,13 +134,50 @@ TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_partial_file) { std::vector buffer(0x100); ASSERT_TRUE(memory->Read(0, buffer.data(), 0x100)); ASSERT_TRUE(memcmp(buffer.data(), ELFMAG, SELFMAG) == 0); - for (size_t i = SELFMAG; i < buffer.size(); i++) { - ASSERT_EQ(2, buffer[i]) << "Failed at byte " << i; + ASSERT_EQ(ELFCLASS64, buffer[EI_CLASS]); + for (size_t i = EI_CLASS + 1; i < buffer.size(); i++) { + ASSERT_EQ(0, buffer[i]) << "Failed at byte " << i; } ASSERT_FALSE(memory->Read(0x100, buffer.data(), 1)); } +// Verify that if the offset is non-zero and there is an elf at that +// offset, that only part of the file is used. Further verify that if the +// embedded elf is bigger than the initial map, the new object is larger +// than the original map size. Do this for a 32 bit elf and a 64 bit elf. +TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_partial_file_whole_elf32) { + MapInfo info{.start = 0x5000, .end = 0x6000, .offset = 0x1000, .name = elf32_at_map_.path}; + + std::unique_ptr memory(info.CreateMemory(getpid())); + ASSERT_TRUE(memory.get() != nullptr); + ASSERT_EQ(0U, info.elf_offset); + + // Verify the memory is a valid elf. + uint8_t e_ident[SELFMAG + 1]; + ASSERT_TRUE(memory->Read(0, e_ident, SELFMAG)); + ASSERT_EQ(0, memcmp(e_ident, ELFMAG, SELFMAG)); + + // Read past the end of what would normally be the size of the map. + ASSERT_TRUE(memory->Read(0x1000, e_ident, 1)); +} + +TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_partial_file_whole_elf64) { + MapInfo info{.start = 0x7000, .end = 0x8000, .offset = 0x2000, .name = elf64_at_map_.path}; + + std::unique_ptr memory(info.CreateMemory(getpid())); + ASSERT_TRUE(memory.get() != nullptr); + ASSERT_EQ(0U, info.elf_offset); + + // Verify the memory is a valid elf. + uint8_t e_ident[SELFMAG + 1]; + ASSERT_TRUE(memory->Read(0, e_ident, SELFMAG)); + ASSERT_EQ(0, memcmp(e_ident, ELFMAG, SELFMAG)); + + // Read past the end of what would normally be the size of the map. + ASSERT_TRUE(memory->Read(0x1000, e_ident, 1)); +} + // Verify that device file names will never result in Memory object creation. TEST_F(MapInfoCreateMemoryTest, check_device_maps) { // Set up some memory so that a valid local memory object would diff --git a/libunwindstack/tools/unwind_symbols.cpp b/libunwindstack/tools/unwind_symbols.cpp index b757c1e56..dc9ae5a06 100644 --- a/libunwindstack/tools/unwind_symbols.cpp +++ b/libunwindstack/tools/unwind_symbols.cpp @@ -28,8 +28,11 @@ #include int main(int argc, char** argv) { - if (argc != 2) { - printf("Need to pass the name of an elf file to the program.\n"); + if (argc != 2 && argc != 3) { + printf("Usage: unwind_symbols []\n"); + printf(" Dump all function symbols in ELF_FILE. If FUNC_ADDRESS is\n"); + printf(" specified, then get the function at that address.\n"); + printf(" FUNC_ADDRESS must be a hex number.\n"); return 1; } @@ -43,6 +46,16 @@ int main(int argc, char** argv) { return 1; } + uint64_t func_addr; + if (argc == 3) { + char* name; + func_addr = strtoull(argv[2], &name, 16); + if (*name != '\0') { + printf("%s is not a hex number.\n", argv[2]); + return 1; + } + } + // Send all log messages to stdout. unwindstack::log_to_stdout(true); @@ -76,9 +89,24 @@ int main(int argc, char** argv) { return 1; } - // This is a crude way to get the symbols in order. std::string name; uint64_t load_bias = elf.interface()->load_bias(); + if (argc == 3) { + std::string cur_name; + uint64_t func_offset; + if (!elf.GetFunctionName(func_addr, &cur_name, &func_offset)) { + printf("No known function at 0x%" PRIx64 "\n", func_addr); + return 1; + } + printf("<0x%" PRIx64 ">", func_addr - func_offset); + if (func_offset != 0) { + printf("+%" PRId64, func_offset); + } + printf(": %s\n", cur_name.c_str()); + return 0; + } + + // This is a crude way to get the symbols in order. for (const auto& entry : elf.interface()->pt_loads()) { uint64_t start = entry.second.offset + load_bias; uint64_t end = entry.second.table_size + load_bias;