From 5af04ee4e644617e43bcc969cd321a0bd3f4c016 Mon Sep 17 00:00:00 2001 From: Jayant Chowdhary Date: Wed, 30 Nov 2016 13:14:43 -0800 Subject: [PATCH] Virtual table dumper which uses llvm's ELF API. Test: dump vtables for libziparchive. Bug: 32649129 Change-Id: I42a1600f3b7871907e595d4239c5868304625b77 --- vndk/Android.bp | 19 + vndk/tools/Android.bp | 19 + vndk/tools/abides/Android.bp | 19 + vndk/tools/abides/vtable/Android.bp | 45 ++ .../tools/abides/vtable/llvm_elf_handling.cpp | 453 ++++++++++++++++++ vndk/tools/abides/vtable/llvm_elf_handling.h | 170 +++++++ vndk/tools/abides/vtable/llvm_vtable_dump.cpp | 49 ++ .../vtable/test/expected/arm/libtest.so.txt | 21 + .../vtable/test/expected/arm64/libtest.so.txt | 21 + .../vtable/test/expected/mips/libtest.so.txt | 21 + .../test/expected/mips64/libtest.so.txt | 21 + .../vtable/test/expected/x86/libtest.so.txt | 21 + .../test/expected/x86_64/libtest.so.txt | 21 + vndk/tools/abides/vtable/test/test1.cpp | 32 ++ vndk/tools/abides/vtable/test/test_vtable.py | 267 +++++++++++ 15 files changed, 1199 insertions(+) create mode 100644 vndk/Android.bp create mode 100644 vndk/tools/Android.bp create mode 100644 vndk/tools/abides/Android.bp create mode 100644 vndk/tools/abides/vtable/Android.bp create mode 100644 vndk/tools/abides/vtable/llvm_elf_handling.cpp create mode 100644 vndk/tools/abides/vtable/llvm_elf_handling.h create mode 100644 vndk/tools/abides/vtable/llvm_vtable_dump.cpp create mode 100644 vndk/tools/abides/vtable/test/expected/arm/libtest.so.txt create mode 100644 vndk/tools/abides/vtable/test/expected/arm64/libtest.so.txt create mode 100644 vndk/tools/abides/vtable/test/expected/mips/libtest.so.txt create mode 100644 vndk/tools/abides/vtable/test/expected/mips64/libtest.so.txt create mode 100644 vndk/tools/abides/vtable/test/expected/x86/libtest.so.txt create mode 100644 vndk/tools/abides/vtable/test/expected/x86_64/libtest.so.txt create mode 100644 vndk/tools/abides/vtable/test/test1.cpp create mode 100755 vndk/tools/abides/vtable/test/test_vtable.py diff --git a/vndk/Android.bp b/vndk/Android.bp new file mode 100644 index 000000000..5a94875ff --- /dev/null +++ b/vndk/Android.bp @@ -0,0 +1,19 @@ +// +// Copyright (C) 2017 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. +// + +subdirs = [ + "tools", +] diff --git a/vndk/tools/Android.bp b/vndk/tools/Android.bp new file mode 100644 index 000000000..c1e980119 --- /dev/null +++ b/vndk/tools/Android.bp @@ -0,0 +1,19 @@ +// +// Copyright (C) 2017 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. +// + +subdirs = [ + "abides", +] diff --git a/vndk/tools/abides/Android.bp b/vndk/tools/abides/Android.bp new file mode 100644 index 000000000..cf4c24f72 --- /dev/null +++ b/vndk/tools/abides/Android.bp @@ -0,0 +1,19 @@ +// +// Copyright (C) 2017 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. +// + +subdirs = [ + "vtable", +] diff --git a/vndk/tools/abides/vtable/Android.bp b/vndk/tools/abides/vtable/Android.bp new file mode 100644 index 000000000..6e0f7956b --- /dev/null +++ b/vndk/tools/abides/vtable/Android.bp @@ -0,0 +1,45 @@ +// +// 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. +// + +cc_defaults { + name: "vndk-vtable-defaults", + + defaults: [ + "clang-defaults", + ], + + shared_libs: [ + "libLLVM", + ], + + cflags: [ + "-Wall", + "-Werror", + "-std=c++11", + ], +} + +cc_binary_host { + name: "llvm-figure-out-vtables", + + defaults: [ + "vndk-vtable-defaults" + ], + + srcs: [ + "llvm_elf_handling.cpp", + "llvm_vtable_dump.cpp"], +} diff --git a/vndk/tools/abides/vtable/llvm_elf_handling.cpp b/vndk/tools/abides/vtable/llvm_elf_handling.cpp new file mode 100644 index 000000000..e7adfc3ed --- /dev/null +++ b/vndk/tools/abides/vtable/llvm_elf_handling.cpp @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2017 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 "llvm_elf_handling.h" + +#include + +using llvm::ELF::ELFDATA2MSB; +using llvm::ELF::EM_ARM; +using llvm::ELF::EM_MIPS; +using llvm::ELF::R_AARCH64_ABS64; +using llvm::ELF::R_AARCH64_RELATIVE; +using llvm::ELF::R_ARM_ABS32; +using llvm::ELF::R_ARM_RELATIVE; +using llvm::ELF::R_X86_64_64; +using llvm::ELF::R_X86_64_RELATIVE; +using llvm::ELF::R_MIPS_64; +using llvm::ELF::R_MIPS_REL32; +using llvm::ELF::R_MIPS_NONE; +using llvm::ELF::SHT_PROGBITS; +using llvm::ELF::SHT_REL; +using llvm::ELF::SHT_RELA; +using llvm::Expected; +using llvm::StringRef; +using llvm::dyn_cast; +using llvm::object::ELF32BEObjectFile; +using llvm::object::ELF32LEObjectFile; +using llvm::object::ELF64BEObjectFile; +using llvm::object::ELF64LEObjectFile; +using llvm::object::symbol_iterator; +using llvm::support::endian::read; +using llvm::outs; +using llvm::Error; +using llvm::make_unique; + +static std::string demangle(const std::string &MangledName) { + char *Str = __cxxabiv1::__cxa_demangle( + MangledName.c_str(), + nullptr, + 0, + nullptr); + if (Str) { + std::string DemangledString(Str); + free(Str); + return DemangledString; + } + return ""; +} + +SharedObject::~SharedObject() {} + +template +static std::unique_ptr createELFSharedObject( + const ELFObjectFile *Objfile) { + return make_unique>(Objfile); +} + +static std::unique_ptrcreateELFObjFile(const ObjectFile *Obj) { + if (const ELF32LEObjectFile *Objfile = dyn_cast(Obj)) + return createELFSharedObject(Objfile); + if (const ELF32BEObjectFile *Objfile = dyn_cast(Obj)) + return createELFSharedObject(Objfile); + if (const ELF64LEObjectFile *Objfile = dyn_cast(Obj)) + return createELFSharedObject(Objfile); + if (const ELF64BEObjectFile *Objfile = dyn_cast(Obj)) + return createELFSharedObject(Objfile); + + return nullptr; +} + +std::unique_ptr SharedObject::create(const ObjectFile *Obj) { + std::unique_ptr res(createELFObjFile(Obj)); + if (res && res->getVTables()) { + return res; + } + return nullptr; +} + +template +ELFSharedObject::~ELFSharedObject() {} + +template +ELFSharedObject::ELFSharedObject( + const ELFObjectFile *Objfile) + : mObj(Objfile) {} + +template +bool ELFSharedObject::cacheELFSections() { + for (const SectionRef &ElfSection : mObj->sections()) { + const Elf_Shdr *ElfShdr = + mObj->getSection(ElfSection.getRawDataRefImpl()); + if (!ElfShdr) { + outs() << "Couldn't create elf shdr \n"; + return false; + } + switch (ElfShdr->sh_type) { + case SHT_RELA: + case SHT_REL: + mRelSectionRefs.emplace_back(ElfSection); + break; + case SHT_PROGBITS: + mProgBitSectionRefs.emplace_back(ElfSection); + break; + default : + // Any other section won't have information pertinent + // to vtables. Relocation entries will have the virtual + // functions' relocation information, the PROGBITS sections + // will have the vtables themselves. + break; + } + } + return true; +} + +template +void ELFSharedObject::printVTables() const { + for (const VTable &Vtable : mVTables) { + if (Vtable.getVTableSize() == 0) + continue; + outs() << Vtable.getDemangledName() + << "\n" + << Vtable.getMangledName() + << ": " + << Vtable.getVTableSize() + << " entries" + << "\n"; + for (const VFunction &Vfunction : Vtable) { + outs() << Vfunction.getOffset() + << " (int (*)(...)) " + << Vfunction.getDemangledName() + << "\n"; + } + outs() << "\n" + << "\n"; + } +} + +template +bool ELFSharedObject::getVTables() { + if (!cacheELFSections()) { + return false; + } + if (!initVTableRanges()) { + return true; + } + getVFunctions(); + for (VTable &Vtable : mVTables) { + // Sort the functions by offset before displaying them since the order + // of functions appearing in relocation sections might change. That + // should not result in the vtable layout changing. + Vtable.sortVFunctions(); + } + return true; +} + +template +bool ELFSharedObject::initVTableRanges() { + // Go through all the symbols in the dynsym / symtab sections + // and cache all the relevant symbols. i.e: symbols which correspond + // to either vtables or functions. + + std::vector> SymsAndSizes = + computeSymbolSizes(*mObj); + for (std::pair &Pair : SymsAndSizes) { + SymbolRef Symbol = Pair.first; + SymbolRef::Type SymType = UnWrap(Symbol.getType()); + uint64_t SymValue = Symbol.getValue(); + StringRef SymName = UnWrap(Symbol.getName()); + if (SymName.startswith("__ZTV") || SymName.startswith("_ZTV")) { + mVTables.emplace_back( + SymName.str(), + demangle(SymName.str()), + Symbol.getValue(), + Symbol.getValue() + Pair.second); + } else if (SymType == SymbolRef::ST_Function) { + std::map>::iterator It = + mAddrToSymbolRef.find(SymValue); + if (It == mAddrToSymbolRef.end()) { + mAddrToSymbolRef.insert(std::make_pair( + SymValue, std::vector(1, Symbol))); + } else { + std::vector &SymVec = It->second; + SymVec.emplace_back(Symbol); + } + } + } + if (mVTables.size() == 0) { + return false; + } + std::sort(mVTables.begin(), mVTables.end()); + return true; +} + +template +void ELFSharedObject::getVFunctions() { + for (const SectionRef &Section : mRelSectionRefs) { + for (const RelocationRef &Relocation : Section.relocations()) { + VTable *VtPtr = identifyVTable(Relocation.getOffset()); + if (VtPtr != nullptr) { + relocateSym(Relocation, Section, VtPtr); + } + } + } +} + +template +VTable *ELFSharedObject::identifyVTable(uint64_t RelOffset) { + typename std::vector::iterator It; + It = std::lower_bound(mVTables.begin(), mVTables.end(), RelOffset); + if (It != mVTables.begin() && It->getStartAddr() != RelOffset) { + It--; + } + if (It->getEndAddr() >= RelOffset) { + return &(*It); + } + return nullptr; +} + +template +void ELFSharedObject::relocateSym( + const RelocationRef &Relocation, + const SectionRef &Section, + VTable *Vtablep) { + const Elf_Ehdr *ElfHeader = mObj->getELFFile()->getHeader(); + if (ElfHeader->e_machine == EM_MIPS) { + // bionic/linker/linker_mips.cpp , we handle only one type of + // relocation. Depending on if the symbol can be inferred from r_info we + // make it an absolute or a relative relocation. + if (!absoluteRelocation(Relocation, Vtablep)) { + relativeRelocation(Relocation, Section, Vtablep); + } + } else { + switch(Relocation.getType()) { + case R_AARCH64_RELATIVE: + case R_X86_64_RELATIVE: + case R_ARM_RELATIVE: + { + // The return value is ignored since failure to relocate + // does not mean a fatal error. It might be that the dynsym / + // symbol-table does not have enough information to get the + // symbol name. Like-wise for absolute relocations. + relativeRelocation(Relocation, Section, Vtablep); + break; + } + case R_AARCH64_ABS64: + case R_X86_64_64: + case R_ARM_ABS32: + { + absoluteRelocation(Relocation, Vtablep); + break; + } + default: + break; + } + } +} + +template +bool ELFSharedObject::absoluteRelocation( + const RelocationRef &Relocation, + VTable *Vtablep) { + symbol_iterator Symi = Relocation.getSymbol(); + if (Symi == mObj->symbol_end()) { + return false; + } + SymbolRef Symbol = *Symi; + uint64_t RelOffset = Relocation.getOffset(); + StringRef SymbolName = UnWrap(Symbol.getName()); + std::string DemangledName = demangle(SymbolName.str()); + if (!DemangledName.empty()) { + Vtablep->addVFunction(SymbolName.str(), DemangledName, RelOffset); + return true; + } + return false; +} + +template +bool ELFSharedObject::relativeRelocation( + const RelocationRef &Relocation, + const SectionRef &Section, + VTable *Vtablep) { + uint64_t Addend = 0; + uint64_t RelOffset = Relocation.getOffset(); + if (mObj->getSection(Section.getRawDataRefImpl())->sh_type == SHT_RELA) { + const Elf_Rela *Rela = mObj->getRela(Relocation.getRawDataRefImpl()); + Addend = static_cast(Rela->r_addend); + } + + if (Addend == 0) { + Addend = identifyAddend(Relocation.getOffset()); + } + + std::map>::iterator It = + mAddrToSymbolRef.find(Addend); + if (It == mAddrToSymbolRef.end()) { + return false; + } + SymbolRef Symbol = matchValueToSymbol(It->second, Vtablep); + StringRef SymbolName = UnWrap(Symbol.getName()); + std::string DemangledName = demangle(SymbolName.str()); + if (!DemangledName.empty()) { + Vtablep->addVFunction(SymbolName.str(), DemangledName, RelOffset); + return true; + } + return false; +} + +template +SymbolRef ELFSharedObject::matchValueToSymbol( + std::vector &SymVec, + VTable *Vtablep) { + constexpr size_t pos = sizeof("vtable for ") - 1; + const std::string ClassName(Vtablep->getDemangledName().substr(pos)); + for (const SymbolRef &Symbol : SymVec) { + StringRef SymbolName = UnWrap(Symbol.getName()); + if (SymbolName.str().find(ClassName) != std::string::npos) + return Symbol; + } + // Return the 1st Symbol by default. + return SymVec[0]; +} + +template +uint64_t ELFSharedObject::identifyAddend(uint64_t ROffset) { + for (const SectionRef &Section : mProgBitSectionRefs) { + uint64_t Begin = Section.getAddress(); + uint64_t End = Section.getAddress() + Section.getSize(); + if (ROffset >= Begin && ROffset <= End) { + return getAddendFromSection(Section, ROffset - Begin); + } + } + return 0; +} + +template +uint64_t ELFSharedObject::getAddendFromSection( + const SectionRef &Section, + uint64_t Offset) { + StringRef Contents; + if (Section.getContents(Contents)) + return 0; + const unsigned char *Bytes = Contents.bytes_begin() + Offset; + uintX_t Addend = read(Bytes); + const Elf_Ehdr *ElfHeader = mObj->getELFFile()->getHeader(); + if (ElfHeader->e_machine == EM_ARM || + ElfHeader->e_machine == EM_MIPS) { + // Remove thumb flag as llvm suggests. + Addend &= ~1; + } + return static_cast(Addend); +} + +VFunction::VFunction( + const std::string &MangledName, + const std::string &DemangledName, + uint64_t VFunctionOffset) + : mMangledName(MangledName), + mDemangledName(DemangledName), + mOffset(VFunctionOffset) {} + +uint64_t VFunction::getOffset() const { + return mOffset; +} + +const std::string &VFunction::getDemangledName() const { + return mDemangledName; +} + +const std::string &VFunction::getMangledName() const { + return mMangledName; +} + +bool VFunction::operator<(const VFunction &Vfunction) const { + return mOffset < Vfunction.getOffset(); +} + +VTable::VTable( + const std::string &MangledName, + const std::string &DemangledName, + uint64_t Begin, + uint64_t End) + : mMangledName(MangledName), + mDemangledName(DemangledName), + mStartAddr(Begin), + mEndAddr(End), + mBaseOffset(Begin) {} + +void VTable::addVFunction( + const std::string &MangledName, + const std::string &DemangledName, + uint64_t RelOffset) { + mFunctions.emplace_back( + MangledName, + DemangledName, + RelOffset - mBaseOffset); +} + +const std::string &VTable::getDemangledName() const { + return mDemangledName; +} + +const std::string &VTable::getMangledName() const { + return mMangledName; +} + +uint64_t VTable::getStartAddr() const { + return mStartAddr; +} + +uint64_t VTable::getEndAddr() const { + return mEndAddr; +} + +uint64_t VTable::getBaseOffset() const { + return mBaseOffset; +} + +uint64_t VTable::getVTableSize() const { + return mFunctions.size(); +} + +VTable::func_iterator VTable::begin() const { + return mFunctions.cbegin(); +} + +VTable::func_iterator VTable::end() const { + return mFunctions.cend(); +} + +bool VTable::operator<(const VTable &Vtable) const { + return mStartAddr < Vtable.getStartAddr(); +} + +bool VTable::operator<(const uint64_t ROffset) const { + return mStartAddr < ROffset; +} + +void VTable::sortVFunctions() { + std::sort(mFunctions.begin(), mFunctions.end()); +} diff --git a/vndk/tools/abides/vtable/llvm_elf_handling.h b/vndk/tools/abides/vtable/llvm_elf_handling.h new file mode 100644 index 000000000..25ee6cafa --- /dev/null +++ b/vndk/tools/abides/vtable/llvm_elf_handling.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef ELF_HANDLING_H_ +#define ELF_HANDLING_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using llvm::object::ObjectFile; +using llvm::object::ELFObjectFile; +using llvm::object::SectionRef; +using llvm::object::RelocationRef; +using llvm::object::ELFFile; +using llvm::object::ELFType; +using llvm::object::ELFDataTypeTypedefHelper; +using llvm::object::SymbolRef; +using llvm::outs; + +class SharedObject { +public: + static std::unique_ptr create(const ObjectFile *); + virtual void printVTables() const = 0; + virtual ~SharedObject() = 0; +private: + virtual bool getVTables() = 0; +}; + +class VFunction { +public: + VFunction( + const std::string &, + const std::string &, + uint64_t); + + uint64_t getOffset() const; + bool operator<(const VFunction &) const; + const std::string &getMangledName() const; + const std::string &getDemangledName() const; +private: + std::string mMangledName; + std::string mDemangledName; + uint64_t mOffset; +}; + +class VTable { +public: + using func_iterator = std::vector::const_iterator; + VTable( + const std::string &, + const std::string &, + uint64_t, + uint64_t); + + uint64_t getStartAddr() const; + uint64_t getEndAddr() const; + uint64_t getBaseOffset() const; + uint64_t getVTableSize() const; + func_iterator begin() const; + func_iterator end() const; + const std::string &getMangledName() const; + const std::string &getDemangledName() const; + void sortVFunctions(); + void addVFunction( + const std::string &, + const std::string &, + uint64_t); + + bool operator<(const VTable &) const; + bool operator<(const uint64_t) const; +private: + std::vector mFunctions; + std::string mMangledName; + std::string mDemangledName; + /* This holds the range(st_value, st_value) through which the + * VTable spans. + */ + uint64_t mStartAddr; + uint64_t mEndAddr; + uint64_t mBaseOffset; +}; + +template +class ELFSharedObject : public SharedObject { +public: + void printVTables() const override; + bool getVTables() override; + ~ELFSharedObject(); + ELFSharedObject(const ELFObjectFile *); + +private: + /* We need a sym value to SymbolRef map in case the relocation provides + * us with an addr instead of a sym index into dynsym / symtab. + */ + LLVM_ELF_IMPORT_TYPES_ELFT(ELFT) + typedef ELFFile ELFO; + typedef typename ELFO::Elf_Shdr Elf_Shdr; + typedef typename ELFO::Elf_Ehdr Elf_Ehdr; + typedef typename ELFO::Elf_Sym Elf_Sym; + typedef typename ELFO::Elf_Rela Elf_Rela; + typedef typename ELFO::uintX_t uintX_t; + std::map> mAddrToSymbolRef; + const ELFObjectFile *mObj; + /* We cache the relocation sections, to look through their relocations for + * vfunctions. Sections with type SHT_PROGBITS are cached since they contain + * vtables. We might need to peek at the contents of a vtable in cases of + * relative relocations. + */ + std::vector mRelSectionRefs; + std::vector mProgBitSectionRefs; + std::vector mVTables; + +private: + bool cacheELFSections(); + bool initVTableRanges(); + void getVFunctions(); + VTable *identifyVTable(uint64_t); + void relocateSym( + const RelocationRef &, + const SectionRef &, + VTable *); + + bool absoluteRelocation(const RelocationRef &, VTable *); + bool relativeRelocation( + const RelocationRef &, + const SectionRef &, + VTable *); + + uint64_t identifyAddend(uint64_t); + uint64_t getAddendFromSection(const SectionRef &, uint64_t); + SymbolRef matchValueToSymbol(std::vector &, VTable *); +}; + +template +static inline T UnWrap(llvm::Expected ValueOrError) { + if (!ValueOrError) { + outs() << "\nError: " + << llvm::toString(ValueOrError.takeError()) + << ".\n"; + outs().flush(); + exit(1); + } + return std::move(ValueOrError.get()); +} + + +#endif // ELF_HANDLING_H_ + diff --git a/vndk/tools/abides/vtable/llvm_vtable_dump.cpp b/vndk/tools/abides/vtable/llvm_vtable_dump.cpp new file mode 100644 index 000000000..fcbf79386 --- /dev/null +++ b/vndk/tools/abides/vtable/llvm_vtable_dump.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 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 "llvm_elf_handling.h" + +using llvm::Expected; +using llvm::StringRef; +using llvm::dyn_cast; +using llvm::object::ObjectFile; +using llvm::object::OwningBinary; +using llvm::outs; + +int main (int argc, char **argv) +{ + if (argc != 2) { + outs() << "usage: figure-out-vtables path \n"; + return 1; + } + Expected> Binary = + ObjectFile::createObjectFile(StringRef(argv[1])); + if (!Binary) { + outs() << "Couldn't create object File \n"; + return 1; + } + ObjectFile *Objfile = dyn_cast(&(*Binary.get().getBinary())); + if (!Objfile) { + return 1; + } + auto SoFile = SharedObject::create(Objfile); + if (!SoFile) { + outs() << "Couldn't create ELFObjectFile \n"; + return 1; + } + SoFile->printVTables(); + return 0; +} diff --git a/vndk/tools/abides/vtable/test/expected/arm/libtest.so.txt b/vndk/tools/abides/vtable/test/expected/arm/libtest.so.txt new file mode 100644 index 000000000..24b0126f0 --- /dev/null +++ b/vndk/tools/abides/vtable/test/expected/arm/libtest.so.txt @@ -0,0 +1,21 @@ +vtable for Alpha +_ZTV5Alpha: 3 entries +8 (int (*)(...)) Alpha::getData(int*, int*, int) +12 (int (*)(...)) Alpha::~Alpha() +16 (int (*)(...)) Alpha::~Alpha() + + +vtable for Beta +_ZTV4Beta: 3 entries +8 (int (*)(...)) Beta::getData(int*, int*, int) +12 (int (*)(...)) Beta::~Beta() +16 (int (*)(...)) Beta::~Beta() + + +vtable for Gamma +_ZTV5Gamma: 3 entries +8 (int (*)(...)) Gamma::getData(int*, int*, int) +12 (int (*)(...)) Gamma::~Gamma() +16 (int (*)(...)) Gamma::~Gamma() + + diff --git a/vndk/tools/abides/vtable/test/expected/arm64/libtest.so.txt b/vndk/tools/abides/vtable/test/expected/arm64/libtest.so.txt new file mode 100644 index 000000000..5a0ea37e9 --- /dev/null +++ b/vndk/tools/abides/vtable/test/expected/arm64/libtest.so.txt @@ -0,0 +1,21 @@ +vtable for Alpha +_ZTV5Alpha: 3 entries +16 (int (*)(...)) Alpha::getData(int*, int*, int) +24 (int (*)(...)) Alpha::~Alpha() +32 (int (*)(...)) Alpha::~Alpha() + + +vtable for Beta +_ZTV4Beta: 3 entries +16 (int (*)(...)) Beta::getData(int*, int*, int) +24 (int (*)(...)) Beta::~Beta() +32 (int (*)(...)) Beta::~Beta() + + +vtable for Gamma +_ZTV5Gamma: 3 entries +16 (int (*)(...)) Gamma::getData(int*, int*, int) +24 (int (*)(...)) Gamma::~Gamma() +32 (int (*)(...)) Gamma::~Gamma() + + diff --git a/vndk/tools/abides/vtable/test/expected/mips/libtest.so.txt b/vndk/tools/abides/vtable/test/expected/mips/libtest.so.txt new file mode 100644 index 000000000..24b0126f0 --- /dev/null +++ b/vndk/tools/abides/vtable/test/expected/mips/libtest.so.txt @@ -0,0 +1,21 @@ +vtable for Alpha +_ZTV5Alpha: 3 entries +8 (int (*)(...)) Alpha::getData(int*, int*, int) +12 (int (*)(...)) Alpha::~Alpha() +16 (int (*)(...)) Alpha::~Alpha() + + +vtable for Beta +_ZTV4Beta: 3 entries +8 (int (*)(...)) Beta::getData(int*, int*, int) +12 (int (*)(...)) Beta::~Beta() +16 (int (*)(...)) Beta::~Beta() + + +vtable for Gamma +_ZTV5Gamma: 3 entries +8 (int (*)(...)) Gamma::getData(int*, int*, int) +12 (int (*)(...)) Gamma::~Gamma() +16 (int (*)(...)) Gamma::~Gamma() + + diff --git a/vndk/tools/abides/vtable/test/expected/mips64/libtest.so.txt b/vndk/tools/abides/vtable/test/expected/mips64/libtest.so.txt new file mode 100644 index 000000000..5a0ea37e9 --- /dev/null +++ b/vndk/tools/abides/vtable/test/expected/mips64/libtest.so.txt @@ -0,0 +1,21 @@ +vtable for Alpha +_ZTV5Alpha: 3 entries +16 (int (*)(...)) Alpha::getData(int*, int*, int) +24 (int (*)(...)) Alpha::~Alpha() +32 (int (*)(...)) Alpha::~Alpha() + + +vtable for Beta +_ZTV4Beta: 3 entries +16 (int (*)(...)) Beta::getData(int*, int*, int) +24 (int (*)(...)) Beta::~Beta() +32 (int (*)(...)) Beta::~Beta() + + +vtable for Gamma +_ZTV5Gamma: 3 entries +16 (int (*)(...)) Gamma::getData(int*, int*, int) +24 (int (*)(...)) Gamma::~Gamma() +32 (int (*)(...)) Gamma::~Gamma() + + diff --git a/vndk/tools/abides/vtable/test/expected/x86/libtest.so.txt b/vndk/tools/abides/vtable/test/expected/x86/libtest.so.txt new file mode 100644 index 000000000..24b0126f0 --- /dev/null +++ b/vndk/tools/abides/vtable/test/expected/x86/libtest.so.txt @@ -0,0 +1,21 @@ +vtable for Alpha +_ZTV5Alpha: 3 entries +8 (int (*)(...)) Alpha::getData(int*, int*, int) +12 (int (*)(...)) Alpha::~Alpha() +16 (int (*)(...)) Alpha::~Alpha() + + +vtable for Beta +_ZTV4Beta: 3 entries +8 (int (*)(...)) Beta::getData(int*, int*, int) +12 (int (*)(...)) Beta::~Beta() +16 (int (*)(...)) Beta::~Beta() + + +vtable for Gamma +_ZTV5Gamma: 3 entries +8 (int (*)(...)) Gamma::getData(int*, int*, int) +12 (int (*)(...)) Gamma::~Gamma() +16 (int (*)(...)) Gamma::~Gamma() + + diff --git a/vndk/tools/abides/vtable/test/expected/x86_64/libtest.so.txt b/vndk/tools/abides/vtable/test/expected/x86_64/libtest.so.txt new file mode 100644 index 000000000..5a0ea37e9 --- /dev/null +++ b/vndk/tools/abides/vtable/test/expected/x86_64/libtest.so.txt @@ -0,0 +1,21 @@ +vtable for Alpha +_ZTV5Alpha: 3 entries +16 (int (*)(...)) Alpha::getData(int*, int*, int) +24 (int (*)(...)) Alpha::~Alpha() +32 (int (*)(...)) Alpha::~Alpha() + + +vtable for Beta +_ZTV4Beta: 3 entries +16 (int (*)(...)) Beta::getData(int*, int*, int) +24 (int (*)(...)) Beta::~Beta() +32 (int (*)(...)) Beta::~Beta() + + +vtable for Gamma +_ZTV5Gamma: 3 entries +16 (int (*)(...)) Gamma::getData(int*, int*, int) +24 (int (*)(...)) Gamma::~Gamma() +32 (int (*)(...)) Gamma::~Gamma() + + diff --git a/vndk/tools/abides/vtable/test/test1.cpp b/vndk/tools/abides/vtable/test/test1.cpp new file mode 100644 index 000000000..606cd9836 --- /dev/null +++ b/vndk/tools/abides/vtable/test/test1.cpp @@ -0,0 +1,32 @@ +class Alpha { +public: + virtual void getData(int *src, int *dst, int data); + virtual ~Alpha() {}; +private: + int mPdata = 0; +}; + +class Beta : public Alpha { +public: + Beta(int data) : mCdata(data) {} + virtual void getData(int *src, int *dst, int data); + virtual ~Beta() {}; +private: + int mCdata = 1; +}; + +class Gamma : public Beta { +public: + Gamma(int data) : mGCdata(data), Beta(data) {} + virtual void getData(int *src, int *dst, int data); + virtual ~Gamma() {}; +private: + int mGCdata = 2; +}; + +void Alpha::getData(int *src, int *dst, int data) {} + +void Beta::getData(int *src, int *dst, int data) {} + +void Gamma::getData(int *src, int *dst, int data) {} + diff --git a/vndk/tools/abides/vtable/test/test_vtable.py b/vndk/tools/abides/vtable/test/test_vtable.py new file mode 100755 index 000000000..048e8fac4 --- /dev/null +++ b/vndk/tools/abides/vtable/test/test_vtable.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017 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. + +import argparse +import collections +import difflib +import os +import subprocess +import sys +import tempfile + +"""Test vndk vtable dumper""" + +NDK_VERSION = 'r11' +API_LEVEL = 'android-24' + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +VNDK_VTABLE_DUMP = os.path.join('llvm-figure-out-vtables') + +def get_dirnames(path, n): + """Get directory, n directories before path""" + for i in range(n): + path = os.path.dirname(path) + return path + + +def get_prebuilts_host(): + """Get the host dir for prebuilts""" + if sys.platform.startswith('linux'): + return 'linux-x86' + if sys.platform.startswith('darwin'): + return 'darwin-x86' + raise NotImplementedError('unknown platform') + + +def get_prebuilts_gcc(android_build_top, arch, gcc_version): + """Get the path to gcc for the current platform""" + return os.path.join(android_build_top, 'prebuilts', 'gcc', + get_prebuilts_host(), arch, gcc_version) + +def get_prebuilts_clang(android_build_top): + """Get the path to prebuilt gcc for the current platform""" + return os.path.join(android_build_top, 'prebuilts', 'clang', 'host', + get_prebuilts_host(), 'clang-stable') + +def get_prebuilts_ndk(android_build_top, subdirs): + """Get the path to prebuilt ndk for the current platform and API level""" + return os.path.join(android_build_top, 'prebuilts', 'ndk', NDK_VERSION, + 'platforms', API_LEVEL, *subdirs) + +def run_cmd(cmd, verbose=False): + """Run the command given and print the command if verbose is True""" + if verbose: + print('RUN:', ' '.join(cmd), file=sys.stderr) + subprocess.check_call(cmd) + + +def run_output(cmd, verbose=False): + """Run the command given and print output of the command""" + if verbose: + print('RUN:', ' '.join(cmd), file=sys.stderr) + return subprocess.check_output(cmd, universal_newlines=True) + + +def run_vtable_dump(path, verbose=False): + """Run vndk vtable dumper""" + return run_output([VNDK_VTABLE_DUMP, path], verbose) + + +class Target(object): + """Class representing a target: for eg: x86, arm64 etc""" + def __init__(self, name, triple, cflags, ldflags, gcc_toolchain_dir, + clang_dir, ndk_include, ndk_lib): + """Parameterized Constructor""" + self.name = name + self.target_triple = triple + self.target_cflags = cflags + self.target_ldflags = ldflags + + self.gcc_toolchain_dir = gcc_toolchain_dir + self.clang_dir = clang_dir + self.ndk_include = ndk_include + self.ndk_lib = ndk_lib + + def compile(self, obj_file, src_file, cflags, verbose=False): + """Compiles the given source files and produces a .o at obj_file""" + clangpp = os.path.join(self.clang_dir, 'bin', 'clang++') + + cmd = [clangpp, '-o', obj_file, '-c', src_file] + cmd.extend(['-fPIE', '-fPIC', '-fno-rtti', '-std=c++11']) + cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir]) + cmd.extend(['-target', self.target_triple]) + cmd.extend(['-isystem', self.ndk_include]) + cmd.extend(cflags) + cmd.extend(self.target_cflags) + run_cmd(cmd, verbose) + + def link(self, out_file, obj_files, ldflags, verbose=False): + """Link the given obj files to form a shared library""" + crtbegin = os.path.join(self.ndk_lib, 'crtbegin_so.o') + crtend = os.path.join(self.ndk_lib, 'crtend_so.o') + clangpp = os.path.join(self.clang_dir, 'bin', 'clang++') + + cmd = [clangpp, '-o', out_file] + cmd.extend(['-fPIE', '-fPIC', '-fno-rtti', '-Wl,--no-undefined', '-nostdlib']) + cmd.append('-L' + self.ndk_lib) + cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir]) + cmd.extend(['-target', self.target_triple]) + cmd.append(crtbegin) + cmd.extend(obj_files) + cmd.append(crtend) + cmd.extend(ldflags) + cmd.extend(self.target_ldflags) + run_cmd(cmd, verbose) + + +def create_targets(top): + """Create multiple targets objects, one for each architecture supported""" + return [ + Target('arm', 'arm-linux-androideabi', [],[], + get_prebuilts_gcc(top, 'arm', 'arm-linux-androideabi-4.9'), + get_prebuilts_clang(top), + get_prebuilts_ndk(top, ['arch-arm', 'usr', 'include']), + get_prebuilts_ndk(top, ['arch-arm', 'usr', 'lib'])), + + Target('arm64', 'aarch64-linux-android', [], [], + get_prebuilts_gcc(top, 'aarch64', 'aarch64-linux-android-4.9'), + get_prebuilts_clang(top), + get_prebuilts_ndk(top, ['arch-arm64', 'usr', 'include']), + get_prebuilts_ndk(top, ['arch-arm64', 'usr', 'lib'])), + + Target('mips', 'mipsel-linux-android', [], [], + get_prebuilts_gcc(top, 'mips', 'mips64el-linux-android-4.9'), + get_prebuilts_clang(top), + get_prebuilts_ndk(top, ['arch-mips', 'usr', 'include']), + get_prebuilts_ndk(top, ['arch-mips', 'usr', 'lib'])), + + Target('mips64', 'mips64el-linux-android', + ['-march=mips64el', '-mcpu=mips64r6'], + ['-march=mips64el', '-mcpu=mips64r6'], + get_prebuilts_gcc(top, 'mips', 'mips64el-linux-android-4.9'), + get_prebuilts_clang(top), + get_prebuilts_ndk(top, ['arch-mips64', 'usr', 'include']), + get_prebuilts_ndk(top, ['arch-mips64', 'usr', 'lib64'])), + + Target('x86', 'x86_64-linux-android', ['-m32'], ['-m32'], + get_prebuilts_gcc(top, 'x86', 'x86_64-linux-android-4.9'), + get_prebuilts_clang(top), + get_prebuilts_ndk(top, ['arch-x86', 'usr', 'include']), + get_prebuilts_ndk(top, ['arch-x86', 'usr', 'lib'])), + + Target('x86_64', 'x86_64-linux-android', ['-m64'], ['-m64'], + get_prebuilts_gcc(top, 'x86', 'x86_64-linux-android-4.9'), + get_prebuilts_clang(top), + get_prebuilts_ndk(top, ['arch-x86_64', 'usr', 'include']), + get_prebuilts_ndk(top, ['arch-x86_64', 'usr', 'lib64'])), + ] + + +class TestRunner(object): + """Class to run the test""" + def __init__(self, expected_dir, test_dir, verbose): + """Parameterized constructor""" + self.expected_dir = expected_dir + self.test_dir = test_dir + self.verbose = verbose + self.num_errors = 0 + + def check_output(self, expected_file_path, actual): + """Compare the output of the test run and the expected output""" + actual = actual.splitlines(True) + with open(expected_file_path, 'r') as f: + expected = f.readlines() + if actual == expected: + return + for line in difflib.context_diff(expected, actual, + fromfile=expected_file_path, + tofile='actual'): + sys.stderr.write(line) + self.num_errors += 1 + + def run_test_for_target(self, target): + """Run the test for a specific target""" + print('Testing target', target.name, '...', file=sys.stderr) + + expected_dir = os.path.join(self.expected_dir, target.name) + + # Create test directory for this target. + test_dir = os.path.join(self.test_dir, target.name) + os.makedirs(test_dir, exist_ok=True) + + # Compile and test "libtest.so". + src_file = os.path.join(SCRIPT_DIR, 'test1.cpp') + obj_file = os.path.join(test_dir, 'test.o') + target.compile(obj_file, src_file, [], self.verbose) + + out_file = os.path.join(test_dir, 'libtest.so') + target.link(out_file, [obj_file], + ['-shared', '-lc', '-lgcc', '-lstdc++'], + self.verbose) + self.check_output(os.path.join(expected_dir, 'libtest.so.txt'), + run_vtable_dump(out_file, self.verbose)) + + def run_test(self, targets): + """Run test fo all targets""" + for target in targets: + self.run_test_for_target(target) + + +def main(): + """ Set up and run test""" + # Parse command line arguments. + parser = argparse.ArgumentParser() + parser.add_argument('--verbose', '-v', action='store_true') + parser.add_argument('--android-build-top', help='path to android build top') + parser.add_argument('--test-dir', + help='directory for temporary files') + parser.add_argument('--expected-dir', help='directory with expected output') + args = parser.parse_args() + + # Find ${ANDROID_BUILD_TOP}. + if args.android_build_top: + android_build_top = args.android_build_top + else: + android_build_top = get_dirnames(SCRIPT_DIR, 6) + + # Find expected output directory. + if args.expected_dir: + expected_dir = args.expected_dir + else: + expected_dir = os.path.join(SCRIPT_DIR, 'expected') + + # Load compilation targets. + targets = create_targets(android_build_top) + + # Run tests. + if args.test_dir: + os.makedirs(args.test_dir, exist_ok=True) + runner = TestRunner(expected_dir, args.test_dir, args.verbose) + runner.run_test(targets) + else: + with tempfile.TemporaryDirectory() as test_dir: + runner = TestRunner(expected_dir, test_dir, args.verbose) + runner.run_test(targets) + + if runner.num_errors: + print('FAILED:', runner.num_errors, 'test(s) failed', file=sys.stderr) + else: + print('SUCCESS', file=sys.stderr) + + return 1 if runner.num_errors else 0 + +if __name__ == '__main__': + sys.exit(main())