Merge "Add .gnu_debugdata support."

This commit is contained in:
Christopher Ferris 2017-06-29 01:15:21 +00:00 committed by Gerrit Code Review
commit 9302daeb1d
9 changed files with 463 additions and 19 deletions

View File

@ -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",
],
}

View File

@ -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;

View File

@ -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> memory_;
uint32_t machine_type_;
uint8_t class_type_;
std::unique_ptr<Memory> gnu_debugdata_memory_;
std::unique_ptr<ElfInterface> gnu_debugdata_interface_;
};
#endif // _LIBUNWINDSTACK_ELF_H

View File

@ -20,6 +20,10 @@
#include <memory>
#include <string>
#include <7zCrc.h>
#include <Xz.h>
#include <XzCrc64.h>
#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<uint8_t> src(gnu_debugdata_size_);
if (!memory_->Read(gnu_debugdata_offset_, src.data(), gnu_debugdata_size_)) {
gnu_debugdata_offset_ = 0;
gnu_debugdata_size_ = static_cast<uint64_t>(-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<MemoryBuffer> 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<uint64_t>(-1);
return nullptr;
}
// Shrink back down to the exact size.
dst->Resize(dst_offset);
return dst.release();
}
template <typename AddressType>
void ElfInterface::InitHeadersWithTemplate() {
if (eh_frame_offset_ != 0) {

View File

@ -15,6 +15,10 @@
*/
#include <elf.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <gtest/gtest.h>
@ -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<Elf32_Ehdr>(&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<Elf64_Ehdr>(&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 <typename Ehdr, typename Shdr>
void GnuDebugdataInitFail(Ehdr* ehdr);
template <typename Ehdr, typename Shdr>
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 <typename Ehdr, typename Shdr>
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<Elf32_Ehdr>(&ehdr);
ehdr.e_ident[EI_CLASS] = ELFCLASS32;
ehdr.e_machine = EM_ARM;
GnuDebugdataInitFail<Elf32_Ehdr, Elf32_Shdr>(&ehdr);
}
TEST_F(ElfTest, gnu_debugdata_init_fail64) {
Elf64_Ehdr ehdr;
InitEhdr<Elf64_Ehdr>(&ehdr);
ehdr.e_ident[EI_CLASS] = ELFCLASS64;
ehdr.e_machine = EM_AARCH64;
GnuDebugdataInitFail<Elf64_Ehdr, Elf64_Shdr>(&ehdr);
}
template <typename Ehdr, typename Shdr>
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<uint8_t> 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<size_t>(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<Elf32_Ehdr>(&ehdr);
ehdr.e_ident[EI_CLASS] = ELFCLASS32;
ehdr.e_machine = EM_ARM;
GnuDebugdataInit<Elf32_Ehdr, Elf32_Shdr>(&ehdr);
}
TEST_F(ElfTest, gnu_debugdata_init64) {
Elf64_Ehdr ehdr;
InitEhdr<Elf64_Ehdr>(&ehdr);
ehdr.e_ident[EI_CLASS] = ELFCLASS64;
ehdr.e_machine = EM_AARCH64;
GnuDebugdataInit<Elf64_Ehdr, Elf64_Shdr>(&ehdr);
}

View File

@ -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 <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string>
#if !defined(EM_AARCH64)
#define EM_AARCH64 183
#endif
template <typename Ehdr>
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 <typename Ehdr, typename Shdr>
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<Elf32_Ehdr>(&ehdr32, ELFCLASS32, EM_ARM);
GenElf<Elf32_Ehdr, Elf32_Shdr>(&ehdr32, elf32_fd);
close(elf32_fd);
Elf64_Ehdr ehdr64;
InitEhdr<Elf64_Ehdr>(&ehdr64, ELFCLASS64, EM_AARCH64);
GenElf<Elf64_Ehdr, Elf64_Shdr>(&ehdr64, elf64_fd);
close(elf64_fd);
}

Binary file not shown.

Binary file not shown.

View File

@ -26,6 +26,8 @@
#include <unistd.h>
#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;
}