diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp index d7e949bad..31837a2b7 100644 --- a/libunwindstack/Android.bp +++ b/libunwindstack/Android.bp @@ -57,16 +57,17 @@ cc_defaults { "ElfInterface.cpp", "ElfInterfaceArm.cpp", "Log.cpp", - "Regs.cpp", "MapInfo.cpp", "Maps.cpp", "Memory.cpp", + "Regs.cpp", "Symbols.cpp", ], shared_libs: [ "libbase", "liblog", + "liblzma", ], } @@ -128,6 +129,7 @@ cc_defaults { shared_libs: [ "libbase", "liblog", + "liblzma", ], static_libs: [ @@ -151,6 +153,11 @@ cc_test { shared_libs: [ "libunwindstack", ], + + data: [ + "tests/elf32.xz", + "tests/elf64.xz", + ], } // These unit tests run against the static debug library. @@ -161,6 +168,11 @@ cc_test { static_libs: [ "libunwindstack_debug", ], + + data: [ + "tests/elf32.xz", + "tests/elf64.xz", + ], } //------------------------------------------------------------------------- @@ -173,6 +185,7 @@ cc_defaults { shared_libs: [ "libunwindstack", "libbase", + "liblzma", ], static_libs: [ @@ -190,3 +203,19 @@ cc_binary { "unwind_info.cpp", ], } + +// Generates the elf data for use in the tests for .gnu_debugdata frames. +// Once these files are generated, use the xz command to compress the data. +cc_binary_host { + name: "gen_gnudebugdata", + + cflags: [ + "-Wall", + "-Werror", + "-Wextra", + ], + + srcs: [ + "tests/GenGnuDebugdata.cpp", + ], +} diff --git a/libunwindstack/Elf.cpp b/libunwindstack/Elf.cpp index 272b5f029..ad1447a02 100644 --- a/libunwindstack/Elf.cpp +++ b/libunwindstack/Elf.cpp @@ -49,6 +49,28 @@ bool Elf::Init() { return valid_; } +// It is expensive to initialize the .gnu_debugdata section. Provide a method +// to initialize this data separately. +void Elf::InitGnuDebugdata() { + if (!valid_ || interface_->gnu_debugdata_offset() == 0) { + return; + } + + gnu_debugdata_memory_.reset(interface_->CreateGnuDebugdataMemory()); + gnu_debugdata_interface_.reset(CreateInterfaceFromMemory(gnu_debugdata_memory_.get())); + ElfInterface* gnu = gnu_debugdata_interface_.get(); + if (gnu == nullptr) { + return; + } + if (gnu->Init()) { + gnu->InitHeaders(); + } else { + // Free all of the memory associated with the gnu_debugdata section. + gnu_debugdata_memory_.reset(nullptr); + gnu_debugdata_interface_.reset(nullptr); + } +} + bool Elf::IsValidElf(Memory* memory) { if (memory == nullptr) { return false; diff --git a/libunwindstack/Elf.h b/libunwindstack/Elf.h index f9db54124..cff9dc4f9 100644 --- a/libunwindstack/Elf.h +++ b/libunwindstack/Elf.h @@ -46,11 +46,15 @@ class Elf { } bool GetFunctionName(uint64_t addr, std::string* name, uint64_t* func_offset) { - return valid_ && interface_->GetFunctionName(addr, name, func_offset); + return valid_ && (interface_->GetFunctionName(addr, name, func_offset) || + (gnu_debugdata_interface_ && + gnu_debugdata_interface_->GetFunctionName(addr, name, func_offset))); } bool Step(uint64_t rel_pc, Regs* regs, Memory* process_memory) { - return valid_ && interface_->Step(rel_pc, regs, process_memory); + return valid_ && (interface_->Step(rel_pc, regs, process_memory) || + (gnu_debugdata_interface_ && + gnu_debugdata_interface_->Step(rel_pc, regs, process_memory))); } ElfInterface* CreateInterfaceFromMemory(Memory* memory); @@ -65,6 +69,8 @@ class Elf { ElfInterface* interface() { return interface_.get(); } + ElfInterface* gnu_debugdata_interface() { return gnu_debugdata_interface_.get(); } + static bool IsValidElf(Memory* memory); protected: @@ -73,6 +79,9 @@ class Elf { std::unique_ptr memory_; uint32_t machine_type_; uint8_t class_type_; + + std::unique_ptr gnu_debugdata_memory_; + std::unique_ptr gnu_debugdata_interface_; }; #endif // _LIBUNWINDSTACK_ELF_H diff --git a/libunwindstack/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp index 3a7f7cb7b..7d38ee041 100644 --- a/libunwindstack/ElfInterface.cpp +++ b/libunwindstack/ElfInterface.cpp @@ -20,6 +20,10 @@ #include #include +#include <7zCrc.h> +#include +#include + #include "DwarfDebugFrame.h" #include "DwarfEhFrame.h" #include "DwarfSection.h" @@ -35,6 +39,60 @@ ElfInterface::~ElfInterface() { } } +Memory* ElfInterface::CreateGnuDebugdataMemory() { + if (gnu_debugdata_offset_ == 0 || gnu_debugdata_size_ == 0) { + return nullptr; + } + + // TODO: Only call these initialization functions once. + CrcGenerateTable(); + Crc64GenerateTable(); + + std::vector src(gnu_debugdata_size_); + if (!memory_->Read(gnu_debugdata_offset_, src.data(), gnu_debugdata_size_)) { + gnu_debugdata_offset_ = 0; + gnu_debugdata_size_ = static_cast(-1); + return nullptr; + } + + ISzAlloc alloc; + CXzUnpacker state; + alloc.Alloc = [](void*, size_t size) { return malloc(size); }; + alloc.Free = [](void*, void* ptr) { return free(ptr); }; + + XzUnpacker_Construct(&state, &alloc); + + std::unique_ptr dst(new MemoryBuffer); + int return_val; + size_t src_offset = 0; + size_t dst_offset = 0; + ECoderStatus status; + dst->Resize(5 * gnu_debugdata_size_); + do { + size_t src_remaining = src.size() - src_offset; + size_t dst_remaining = dst->Size() - dst_offset; + if (dst_remaining < 2 * gnu_debugdata_size_) { + dst->Resize(dst->Size() + 2 * gnu_debugdata_size_); + dst_remaining += 2 * gnu_debugdata_size_; + } + return_val = XzUnpacker_Code(&state, dst->GetPtr(dst_offset), &dst_remaining, &src[src_offset], + &src_remaining, CODER_FINISH_ANY, &status); + src_offset += src_remaining; + dst_offset += dst_remaining; + } while (return_val == SZ_OK && status == CODER_STATUS_NOT_FINISHED); + XzUnpacker_Free(&state); + if (return_val != SZ_OK || !XzUnpacker_IsStreamWasFinished(&state)) { + gnu_debugdata_offset_ = 0; + gnu_debugdata_size_ = static_cast(-1); + return nullptr; + } + + // Shrink back down to the exact size. + dst->Resize(dst_offset); + + return dst.release(); +} + template void ElfInterface::InitHeadersWithTemplate() { if (eh_frame_offset_ != 0) { diff --git a/libunwindstack/tests/ElfTest.cpp b/libunwindstack/tests/ElfTest.cpp index 25fec8e3a..22cade25c 100644 --- a/libunwindstack/tests/ElfTest.cpp +++ b/libunwindstack/tests/ElfTest.cpp @@ -15,6 +15,10 @@ */ #include +#include +#include +#include +#include #include @@ -26,10 +30,6 @@ #define PT_ARM_EXIDX 0x70000001 #endif -#if !defined(EM_AARCH64) -#define EM_AARCH64 183 -#endif - class ElfTest : public ::testing::Test { protected: void SetUp() override { @@ -43,17 +43,18 @@ class ElfTest : public ::testing::Test { ehdr->e_ident[EI_DATA] = ELFDATA2LSB; ehdr->e_ident[EI_VERSION] = EV_CURRENT; ehdr->e_ident[EI_OSABI] = ELFOSABI_SYSV; + + ehdr->e_type = ET_DYN; + ehdr->e_version = EV_CURRENT; } - void InitElf32(uint32_t type) { + void InitElf32(uint32_t machine) { Elf32_Ehdr ehdr; InitEhdr(&ehdr); ehdr.e_ident[EI_CLASS] = ELFCLASS32; - ehdr.e_type = ET_DYN; - ehdr.e_machine = type; - ehdr.e_version = EV_CURRENT; + ehdr.e_machine = machine; ehdr.e_entry = 0; ehdr.e_phoff = 0x100; ehdr.e_shoff = 0; @@ -64,7 +65,7 @@ class ElfTest : public ::testing::Test { ehdr.e_shentsize = sizeof(Elf32_Shdr); ehdr.e_shnum = 0; ehdr.e_shstrndx = 0; - if (type == EM_ARM) { + if (machine == EM_ARM) { ehdr.e_flags = 0x5000200; ehdr.e_phnum = 2; } @@ -82,7 +83,7 @@ class ElfTest : public ::testing::Test { phdr.p_align = 0x1000; memory_->SetMemory(0x100, &phdr, sizeof(phdr)); - if (type == EM_ARM) { + if (machine == EM_ARM) { memset(&phdr, 0, sizeof(phdr)); phdr.p_type = PT_ARM_EXIDX; phdr.p_offset = 0x30000; @@ -96,15 +97,13 @@ class ElfTest : public ::testing::Test { } } - void InitElf64(uint32_t type) { + void InitElf64(uint32_t machine) { Elf64_Ehdr ehdr; InitEhdr(&ehdr); ehdr.e_ident[EI_CLASS] = ELFCLASS64; - ehdr.e_type = ET_DYN; - ehdr.e_machine = type; - ehdr.e_version = EV_CURRENT; + ehdr.e_machine = machine; ehdr.e_entry = 0; ehdr.e_phoff = 0x100; ehdr.e_shoff = 0; @@ -130,6 +129,12 @@ class ElfTest : public ::testing::Test { memory_->SetMemory(0x100, &phdr, sizeof(phdr)); } + template + void GnuDebugdataInitFail(Ehdr* ehdr); + + template + void GnuDebugdataInit(Ehdr* ehdr); + MemoryFake* memory_; }; @@ -208,3 +213,154 @@ TEST_F(ElfTest, elf_x86_64) { ASSERT_EQ(ELFCLASS64, elf.class_type()); ASSERT_TRUE(elf.interface() != nullptr); } + +template +void ElfTest::GnuDebugdataInitFail(Ehdr* ehdr) { + Elf elf(memory_); + + uint64_t offset = 0x2000; + + ehdr->e_shoff = offset; + ehdr->e_shnum = 3; + ehdr->e_shentsize = sizeof(Shdr); + ehdr->e_shstrndx = 2; + memory_->SetMemory(0, ehdr, sizeof(*ehdr)); + + Shdr shdr; + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_type = SHT_NULL; + memory_->SetMemory(offset, &shdr, sizeof(shdr)); + offset += ehdr->e_shentsize; + + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_type = SHT_PROGBITS; + shdr.sh_name = 0x100; + shdr.sh_addr = 0x5000; + shdr.sh_offset = 0x5000; + shdr.sh_entsize = 0x100; + shdr.sh_size = 0x800; + memory_->SetMemory(offset, &shdr, sizeof(shdr)); + offset += ehdr->e_shentsize; + + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_type = SHT_STRTAB; + shdr.sh_name = 0x200000; + shdr.sh_offset = 0xf000; + shdr.sh_size = 0x1000; + memory_->SetMemory(offset, &shdr, sizeof(shdr)); + offset += ehdr->e_shentsize; + + memory_->SetMemory(0xf100, ".gnu_debugdata", sizeof(".gnu_debugdata")); + + ASSERT_TRUE(elf.Init()); + ASSERT_TRUE(elf.interface() != nullptr); + ASSERT_TRUE(elf.gnu_debugdata_interface() == nullptr); + EXPECT_EQ(0x5000U, elf.interface()->gnu_debugdata_offset()); + EXPECT_EQ(0x800U, elf.interface()->gnu_debugdata_size()); + + elf.InitGnuDebugdata(); +} + +TEST_F(ElfTest, gnu_debugdata_init_fail32) { + Elf32_Ehdr ehdr; + InitEhdr(&ehdr); + ehdr.e_ident[EI_CLASS] = ELFCLASS32; + ehdr.e_machine = EM_ARM; + + GnuDebugdataInitFail(&ehdr); +} + +TEST_F(ElfTest, gnu_debugdata_init_fail64) { + Elf64_Ehdr ehdr; + InitEhdr(&ehdr); + ehdr.e_ident[EI_CLASS] = ELFCLASS64; + ehdr.e_machine = EM_AARCH64; + + GnuDebugdataInitFail(&ehdr); +} + +template +void ElfTest::GnuDebugdataInit(Ehdr* ehdr) { + Elf elf(memory_); + + uint64_t offset = 0x2000; + + ehdr->e_shoff = offset; + ehdr->e_shnum = 3; + ehdr->e_shentsize = sizeof(Shdr); + ehdr->e_shstrndx = 2; + memory_->SetMemory(0, ehdr, sizeof(*ehdr)); + + Shdr shdr; + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_type = SHT_NULL; + memory_->SetMemory(offset, &shdr, sizeof(shdr)); + offset += ehdr->e_shentsize; + + uint64_t gnu_offset = offset; + offset += ehdr->e_shentsize; + + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_type = SHT_STRTAB; + shdr.sh_name = 0x200000; + shdr.sh_offset = 0xf000; + shdr.sh_size = 0x1000; + memory_->SetMemory(offset, &shdr, sizeof(shdr)); + offset += ehdr->e_shentsize; + + memory_->SetMemory(0xf100, ".gnu_debugdata", sizeof(".gnu_debugdata")); + + // Read in the compressed elf data and put it in our fake memory. + std::string name("tests/"); + if (sizeof(Ehdr) == sizeof(Elf32_Ehdr)) { + name += "elf32.xz"; + } else { + name += "elf64.xz"; + } + int fd = TEMP_FAILURE_RETRY(open(name.c_str(), O_RDONLY)); + ASSERT_NE(-1, fd) << "Cannot open " + name; + // Assumes the file is less than 1024 bytes. + std::vector buf(1024); + ssize_t bytes = TEMP_FAILURE_RETRY(read(fd, buf.data(), buf.size())); + ASSERT_GT(bytes, 0); + // Make sure the file isn't too big. + ASSERT_NE(static_cast(bytes), buf.size()) + << "File " + name + " is too big, increase buffer size."; + close(fd); + buf.resize(bytes); + memory_->SetMemory(0x5000, buf); + + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_type = SHT_PROGBITS; + shdr.sh_name = 0x100; + shdr.sh_addr = 0x5000; + shdr.sh_offset = 0x5000; + shdr.sh_size = bytes; + memory_->SetMemory(gnu_offset, &shdr, sizeof(shdr)); + + ASSERT_TRUE(elf.Init()); + ASSERT_TRUE(elf.interface() != nullptr); + ASSERT_TRUE(elf.gnu_debugdata_interface() == nullptr); + EXPECT_EQ(0x5000U, elf.interface()->gnu_debugdata_offset()); + + elf.InitGnuDebugdata(); + ASSERT_TRUE(elf.gnu_debugdata_interface() != nullptr); +} + +TEST_F(ElfTest, gnu_debugdata_init32) { + Elf32_Ehdr ehdr; + InitEhdr(&ehdr); + ehdr.e_ident[EI_CLASS] = ELFCLASS32; + ehdr.e_machine = EM_ARM; + + GnuDebugdataInit(&ehdr); +} + +TEST_F(ElfTest, gnu_debugdata_init64) { + Elf64_Ehdr ehdr; + InitEhdr(&ehdr); + ehdr.e_ident[EI_CLASS] = ELFCLASS64; + ehdr.e_machine = EM_AARCH64; + + GnuDebugdataInit(&ehdr); +} diff --git a/libunwindstack/tests/GenGnuDebugdata.cpp b/libunwindstack/tests/GenGnuDebugdata.cpp new file mode 100644 index 000000000..2644582b2 --- /dev/null +++ b/libunwindstack/tests/GenGnuDebugdata.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 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 + +#if !defined(EM_AARCH64) +#define EM_AARCH64 183 +#endif + +template +void InitEhdr(Ehdr* ehdr, uint32_t elf_class, uint32_t machine) { + memset(ehdr, 0, sizeof(Ehdr)); + memcpy(&ehdr->e_ident[0], ELFMAG, SELFMAG); + ehdr->e_ident[EI_DATA] = ELFDATA2LSB; + ehdr->e_ident[EI_VERSION] = EV_CURRENT; + ehdr->e_ident[EI_OSABI] = ELFOSABI_SYSV; + ehdr->e_ident[EI_CLASS] = elf_class; + ehdr->e_type = ET_DYN; + ehdr->e_machine = machine; + ehdr->e_version = EV_CURRENT; + ehdr->e_ehsize = sizeof(Ehdr); +} + +template +void GenElf(Ehdr* ehdr, int fd) { + uint64_t offset = sizeof(Ehdr); + ehdr->e_shoff = offset; + ehdr->e_shnum = 3; + ehdr->e_shentsize = sizeof(Shdr); + ehdr->e_shstrndx = 2; + TEMP_FAILURE_RETRY(write(fd, ehdr, sizeof(Ehdr))); + + Shdr shdr; + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_name = 0; + shdr.sh_type = SHT_NULL; + TEMP_FAILURE_RETRY(write(fd, &shdr, sizeof(Shdr))); + offset += ehdr->e_shentsize; + + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_type = SHT_PROGBITS; + shdr.sh_name = 11; + shdr.sh_addr = 0x5000; + shdr.sh_offset = 0x5000; + shdr.sh_entsize = 0x100; + shdr.sh_size = 0x800; + TEMP_FAILURE_RETRY(write(fd, &shdr, sizeof(Shdr))); + offset += ehdr->e_shentsize; + + memset(&shdr, 0, sizeof(shdr)); + shdr.sh_type = SHT_STRTAB; + shdr.sh_name = 1; + shdr.sh_offset = 0x200; + shdr.sh_size = 24; + TEMP_FAILURE_RETRY(write(fd, &shdr, sizeof(Shdr))); + + // Write out the name entries information. + lseek(fd, 0x200, SEEK_SET); + std::string name; + TEMP_FAILURE_RETRY(write(fd, name.data(), name.size() + 1)); + name = ".shstrtab"; + TEMP_FAILURE_RETRY(write(fd, name.data(), name.size() + 1)); + name = ".debug_frame"; + TEMP_FAILURE_RETRY(write(fd, name.data(), name.size() + 1)); +} + +int main() { + int elf32_fd = TEMP_FAILURE_RETRY(open("elf32", O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)); + if (elf32_fd == -1) { + printf("Failed to create elf32: %s\n", strerror(errno)); + return 1; + } + + int elf64_fd = TEMP_FAILURE_RETRY(open("elf64", O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)); + if (elf64_fd == -1) { + printf("Failed to create elf64: %s\n", strerror(errno)); + return 1; + } + + Elf32_Ehdr ehdr32; + InitEhdr(&ehdr32, ELFCLASS32, EM_ARM); + GenElf(&ehdr32, elf32_fd); + close(elf32_fd); + + Elf64_Ehdr ehdr64; + InitEhdr(&ehdr64, ELFCLASS64, EM_AARCH64); + GenElf(&ehdr64, elf64_fd); + close(elf64_fd); +} diff --git a/libunwindstack/tests/elf32.xz b/libunwindstack/tests/elf32.xz new file mode 100644 index 000000000..f25d43349 Binary files /dev/null and b/libunwindstack/tests/elf32.xz differ diff --git a/libunwindstack/tests/elf64.xz b/libunwindstack/tests/elf64.xz new file mode 100644 index 000000000..eb1618e7e Binary files /dev/null and b/libunwindstack/tests/elf64.xz differ diff --git a/libunwindstack/unwind_info.cpp b/libunwindstack/unwind_info.cpp index 6f158b0e9..da1522b3d 100644 --- a/libunwindstack/unwind_info.cpp +++ b/libunwindstack/unwind_info.cpp @@ -26,6 +26,8 @@ #include #include "ArmExidx.h" +#include "DwarfSection.h" +#include "DwarfStructs.h" #include "Elf.h" #include "ElfInterface.h" #include "ElfInterfaceArm.h" @@ -46,12 +48,14 @@ void DumpArm(ElfInterfaceArm* interface) { std::string name; printf(" PC 0x%" PRIx64, addr + load_bias); uint64_t func_offset; - if (interface->GetFunctionName(addr + load_bias + 1, &name, &func_offset) && !name.empty()) { + uint64_t pc = addr + load_bias; + // This might be a thumb function, so set the low bit. + if (interface->GetFunctionName(pc | 1, &name, &func_offset) && !name.empty()) { printf(" <%s>", name.c_str()); } printf("\n"); uint64_t entry; - if (!interface->FindEntry(addr + load_bias, &entry)) { + if (!interface->FindEntry(pc, &entry)) { printf(" Cannot find entry for address.\n"); continue; } @@ -75,6 +79,27 @@ void DumpArm(ElfInterfaceArm* interface) { printf("\n"); } +void DumpDwarfSection(ElfInterface* interface, DwarfSection* section, uint64_t load_bias) { + for (const DwarfFde* fde : *section) { + // Sometimes there are entries that have empty length, skip those since + // they don't contain any interesting information. + if (fde->pc_start == fde->pc_end) { + continue; + } + printf("\n PC 0x%" PRIx64, fde->pc_start + load_bias); + std::string name; + uint64_t func_offset; + if (interface->GetFunctionName(fde->pc_start + load_bias, &name, &func_offset) && + !name.empty()) { + printf(" <%s>", name.c_str()); + } + printf("\n"); + if (!section->Log(2, UINT64_MAX, load_bias, fde)) { + printf("Failed to process cfa information for entry at 0x%" PRIx64 "\n", fde->pc_start); + } + } +} + int main(int argc, char** argv) { if (argc != 2) { printf("Need to pass the name of an elf file to the program.\n"); @@ -117,5 +142,38 @@ int main(int argc, char** argv) { printf("\n"); } + if (interface->eh_frame() != nullptr) { + printf("eh_frame information:\n"); + DumpDwarfSection(interface, interface->eh_frame(), interface->load_bias()); + printf("\n"); + } else { + printf("\nno eh_frame information\n"); + } + + if (interface->debug_frame() != nullptr) { + printf("\ndebug_frame information:\n"); + DumpDwarfSection(interface, interface->debug_frame(), interface->load_bias()); + printf("\n"); + } else { + printf("\nno debug_frame information\n"); + } + + // If there is a gnu_debugdata interface, dump the information for that. + ElfInterface* gnu_debugdata_interface = elf.gnu_debugdata_interface(); + if (gnu_debugdata_interface != nullptr) { + if (gnu_debugdata_interface->eh_frame() != nullptr) { + printf("\ngnu_debugdata (eh_frame):\n"); + DumpDwarfSection(gnu_debugdata_interface, gnu_debugdata_interface->eh_frame(), 0); + printf("\n"); + } + if (gnu_debugdata_interface->debug_frame() != nullptr) { + printf("\ngnu_debugdata (debug_frame):\n"); + DumpDwarfSection(gnu_debugdata_interface, gnu_debugdata_interface->debug_frame(), 0); + printf("\n"); + } + } else { + printf("\nno valid gnu_debugdata information\n"); + } + return 0; }