Index: llvm/include/llvm/TAPI/ELFInterfaceFile.h =================================================================== --- /dev/null +++ llvm/include/llvm/TAPI/ELFInterfaceFile.h @@ -0,0 +1,83 @@ +//===- ELFInterfaceFile.h ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ +/// +/// \file +/// This file defines an interface for interacting with ELF File objects. +/// Metadata that determines how this File object should be treated belong here. +/// Actual details that define the API/ABI an ELF file implements belong in +/// ELFStub.h. +/// +//===-----------------------------------------------------------------------===/ + +#ifndef LLVM_TAPI_ELFINTERFACEFILE_H +#define LLVM_TAPI_ELFINTERFACEFILE_H + +#include "llvm/Object/ELF.h" +#include "llvm/TAPI/ELFStub.h" +#include + +namespace llvm { +namespace tapi { + +enum FileType : unsigned { + Invalid = 0u, + + // Bits 0 - 10 reserved for XCode. + + // ELF dynamic shared object. + ELF_32LE = 1u << 11, + ELF_32BE = 1u << 12, + ELF_64LE = 1u << 13, + ELF_64BE = 1u << 14, + // Text-based ELF stub file (.tbe) version 1. + TBE_V1 = 1u << 15, + All = ~0u, +}; + +class ELFInterfaceFile { +public: + explicit ELFInterfaceFile(FileType Type) : Type(Type) {} + explicit ELFInterfaceFile(FileType Type, ELFStub &SourceStub) + : Stub(SourceStub), Type(Type) {} + + /// Returns all FileTypes this file can be converted among. + uint32_t getCompatibleTypes() const { return SUPPORTED_FILE_TYPES; } + + /// Returns original FileType of this file. + enum FileType getFileType() const { return Type; } + + /// Returns whether or not type is a compatible type. + bool supportsType(FileType OtherType) const { + if (OtherType & SUPPORTED_FILE_TYPES) + return true; + else + return false; + } + + /// Sets this ELFInterfaceFile's type to a supported format. + Error setFileType(FileType NewType); + + /// Returns a short string description of a FileType. + static std::string fileTypeToString(FileType Type); + + static const uint32_t SUPPORTED_FILE_TYPES = + FileType::TBE_V1 | FileType::ELF_32LE | FileType::ELF_32BE | + FileType::ELF_64LE | FileType::ELF_64BE; + + ELFStub getStub() const { return Stub; } + +private: + ELFStub Stub; + enum FileType Type; +}; + +} // end namespace tapi +} // end namespace llvm + +#endif // LLVM_TAPI_ELFINTERFACEFILE_H Index: llvm/include/llvm/TAPI/ELFStub.h =================================================================== --- /dev/null +++ llvm/include/llvm/TAPI/ELFStub.h @@ -0,0 +1,63 @@ +//===- ELFStub.h ------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ +/// +/// \file +/// This file defines an internal representation of an ELF stub. +/// +//===-----------------------------------------------------------------------===/ + +#ifndef LLVM_TAPI_ELFSTUB_H +#define LLVM_TAPI_ELFSTUB_H + +#include "llvm/Object/ELF.h" +#include + +namespace llvm { +namespace tapi { + +typedef uint16_t ELFArch; + +enum ELFSymbolType { + NoType = ELF::STT_NOTYPE, + Object = ELF::STT_OBJECT, + Func = ELF::STT_FUNC, + TLS = ELF::STT_TLS, + + // Type information is 4 bits, so 16 is safely out of range. + Unknown = 16, +}; + +struct ELFSymbol { + std::string Name; + uint64_t Size; + ELFSymbolType Type; + bool Undefined; + Optional Warning; +}; + +// A cumulative representation of ELF stubs. +// Both textual and binary stubs will read into and write from this object. +class ELFStub { +public: + std::vector Symbols; + ELFArch Arch; + std::string SoName; + + ELFStub() {} + ELFStub(const ELFStub &Stub); + + static bool symbolCompareTo(const ELFSymbol &LHS, const ELFSymbol &RHS) { + return LHS.Name < RHS.Name; + } +}; + +} // end namespace tapi +} // end namespace llvm + +#endif // LLVM_TAPI_ELFSTUB_H Index: llvm/include/llvm/TAPI/TBEHandler.h =================================================================== --- /dev/null +++ llvm/include/llvm/TAPI/TBEHandler.h @@ -0,0 +1,36 @@ +//===- TBEHandler.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ +/// +/// \file +/// This file declares an interface for reading and writing .tbe (text-based +/// ELF) files. +/// +//===-----------------------------------------------------------------------===/ + +#ifndef LLVM_TAPI_TBEHANDLER_H +#define LLVM_TAPI_TBEHANDLER_H + +#include "llvm/TAPI/ELFInterfaceFile.h" + +namespace llvm { +namespace tapi { + +class TBEHandler { +public: + /// Attempts to read an ELF interface file from a StringRef buffer. + std::unique_ptr readFile(StringRef Buf); + + /// Attempts to write an ELF interface file to a raw_ostream. + Error writeFile(raw_ostream &OS, const ELFInterfaceFile *File); +}; + +} // end namespace tapi +} // end namespace llvm + +#endif // LLVM_TAPI_TBEHANDLER_H Index: llvm/lib/CMakeLists.txt =================================================================== --- llvm/lib/CMakeLists.txt +++ llvm/lib/CMakeLists.txt @@ -23,6 +23,7 @@ add_subdirectory(LineEditor) add_subdirectory(ProfileData) add_subdirectory(Passes) +add_subdirectory(TAPI) add_subdirectory(ToolDrivers) add_subdirectory(XRay) add_subdirectory(Testing) Index: llvm/lib/LLVMBuild.txt =================================================================== --- llvm/lib/LLVMBuild.txt +++ llvm/lib/LLVMBuild.txt @@ -40,6 +40,7 @@ ProfileData Support TableGen + TAPI Target Testing ToolDrivers Index: llvm/lib/TAPI/CMakeLists.txt =================================================================== --- /dev/null +++ llvm/lib/TAPI/CMakeLists.txt @@ -0,0 +1,8 @@ +add_llvm_library(LLVMTAPI + ELFStub.cpp + ELFInterfaceFile.cpp + TBEHandler.cpp + + ADDITIONAL_HEADER_DIRS + "${LLVM_MAIN_INCLUDE_DIR}/llvm/TAPI" +) Index: llvm/lib/TAPI/ELFInterfaceFile.cpp =================================================================== --- /dev/null +++ llvm/lib/TAPI/ELFInterfaceFile.cpp @@ -0,0 +1,48 @@ +//===- ELFInterfaceFile.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ + +#include "llvm/TAPI/ELFInterfaceFile.h" +#include "llvm/Support/Error.h" + +using namespace llvm; +using namespace tapi; + +Error ELFInterfaceFile::setFileType(FileType NewType) { + assert(NewType != FileType::All && "Can't set file type to FileType::All!"); + if (supportsType(NewType)) { + Type = NewType; + return Error::success(); + } else { + std::string ErrorMsg = "ELF incompatible with file type `"; + ErrorMsg.append(fileTypeToString(NewType)); + ErrorMsg.append("`\n"); + return createStringError(std::make_error_code(std::errc::invalid_argument), + ErrorMsg.c_str()); + } +} + +std::string ELFInterfaceFile::fileTypeToString(FileType Type) { + switch (Type) { + case FileType::Invalid: + return "invalid"; + case FileType::ELF_32LE: + return "32-bit little endian ELF"; + case FileType::ELF_32BE: + return "32-bit big endian ELF"; + case FileType::ELF_64LE: + return "64-bit little endian ELF"; + case FileType::ELF_64BE: + return "64-bit big endian ELF"; + case FileType::TBE_V1: + return "text-based ELF stub v1"; + case FileType::All: + return "all file types"; + } + return "unknown file type"; +} Index: llvm/lib/TAPI/ELFStub.cpp =================================================================== --- /dev/null +++ llvm/lib/TAPI/ELFStub.cpp @@ -0,0 +1,22 @@ +//===- ELFStub.cpp ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ + +#include "llvm/TAPI/ELFStub.h" + +using namespace llvm; +using namespace tapi; + +ELFStub::ELFStub(ELFStub const &Stub) { + Arch = Stub.Arch; + SoName = Stub.SoName; + for (auto Elem : Stub.Symbols) { + Symbols.push_back(Elem); + } + llvm::sort(Symbols, symbolCompareTo); +} Index: llvm/lib/TAPI/LLVMBuild.txt =================================================================== --- /dev/null +++ llvm/lib/TAPI/LLVMBuild.txt @@ -0,0 +1,22 @@ +;===- ./lib/Support/LLVMBuild.txt ------------------------------*- Conf -*--===; +; +; The LLVM Compiler Infrastructure +; +; This file is distributed under the University of Illinois Open Source +; License. See LICENSE.TXT for details. +; +;===------------------------------------------------------------------------===; +; +; This is an LLVMBuild description file for the components in this subdirectory. +; +; For more information on the LLVMBuild system, please see: +; +; http://llvm.org/docs/LLVMBuild.html +; +;===------------------------------------------------------------------------===; + +[component_0] +type = Library +name = TAPI +parent = Libraries +required_libraries = Object Support Index: llvm/lib/TAPI/TBEHandler.cpp =================================================================== --- /dev/null +++ llvm/lib/TAPI/TBEHandler.cpp @@ -0,0 +1,139 @@ +//===- TBEHandler.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ + +#include "llvm/TAPI/TBEHandler.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/TAPI/ELFInterfaceFile.h" + +using namespace llvm; +using namespace tapi; + +LLVM_YAML_STRONG_TYPEDEF(uint16_t, ELFArchMapper); + +namespace llvm { +namespace yaml { + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, ELFSymbolType &SymbolType) { + IO.enumCase(SymbolType, "NoType", ELFSymbolType::NoType); + IO.enumCase(SymbolType, "Func", ELFSymbolType::Func); + IO.enumCase(SymbolType, "Object", ELFSymbolType::Object); + IO.enumCase(SymbolType, "TLS", ELFSymbolType::TLS); + IO.enumCase(SymbolType, "Unknown", ELFSymbolType::Unknown); + if (!IO.outputting() && IO.matchEnumFallback()) + SymbolType = ELFSymbolType::Unknown; + } +}; + +template <> struct ScalarTraits { + static void output(const ELFArchMapper &Value, void *, + llvm::raw_ostream &Out) { + static const DenseMap ArchMap = { + {(uint16_t)ELF::EM_X86_64, "x86_64"}, + {(uint16_t)ELF::EM_AARCH64, "AArch64"}, + {(uint16_t)ELF::EM_NONE, "Unknown"}}; + if (ArchMap.count(Value) == 0) + Out << ArchMap.lookup(ELF::EM_NONE); + else + Out << ArchMap.lookup(Value); + } + + static StringRef input(StringRef Scalar, void *, ELFArchMapper &Value) { + static const StringMap ArchMap = { + {"x86_64", (uint16_t)ELF::EM_X86_64}, + {"AArch64", (uint16_t)ELF::EM_AARCH64}, + {"Unknown", (uint16_t)ELF::EM_NONE}}; + if (ArchMap.count(Scalar) == 0) { + Value = ArchMap.lookup("Unknown"); + return StringRef("Unknown architecture"); + } else { + Value = ArchMap.lookup(Scalar); + } + + // Returning empty StringRef indicates successful parse. + return StringRef(); + } + + // Don't place quotation marks around Architecture entry. + static QuotingType mustQuote(StringRef) { return QuotingType::None; } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, ELFSymbol &Symbol) { + IO.mapRequired("Type", Symbol.Type); + if (Symbol.Type == ELFSymbolType::NoType) { + IO.mapOptional("Size", Symbol.Size, (uint64_t)0u); + } else if (Symbol.Type == ELFSymbolType::Func) { + Symbol.Size = 0; + } else { + IO.mapRequired("Size", Symbol.Size); + } + IO.mapOptional("Undefined", Symbol.Undefined, false); + IO.mapOptional("Warning", Symbol.Warning); + } + + // Compacts symbol information into a single line. + static const bool flow = true; +}; + +template <> struct CustomMappingTraits> { + static void inputOne(IO &IO, StringRef Key, std::vector &List) { + ELFSymbol Sym; + Sym.Name = Key.str(); + List.push_back(Sym); + IO.mapRequired(Key.str().c_str(), List.back()); + } + + static void output(IO &IO, std::vector &List) { + // Force "Symbols: {}" if empty. + // TODO: fix this in YAMLTraits. + if (List.size() == 0) { + IO.beginFlowMapping(); + IO.endFlowMapping(); + } + + for (auto &Sym : List) + IO.mapRequired(Sym.Name.c_str(), Sym); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, ELFStub &Stub) { + if (!IO.mapTag("!tapi-tbe-v1", true)) { + IO.setError("Invalid tbe version"); + } + IO.mapRequired("SoName", Stub.SoName); + IO.mapRequired("Arch", (ELFArchMapper &)Stub.Arch); + IO.mapOptional("Symbols", Stub.Symbols); + } +}; + +} // end namespace yaml +} // end namespace llvm + +std::unique_ptr TBEHandler::readFile(StringRef Buf) { + yaml::Input YamlIn(Buf); + ELFStub Stub; + YamlIn >> Stub; + if (YamlIn.error()) + return nullptr; + return std::unique_ptr( + new ELFInterfaceFile(FileType::TBE_V1, Stub)); +} + +Error TBEHandler::writeFile(raw_ostream &OS, const ELFInterfaceFile *File) { + assert(File != nullptr && "Can't write null file."); + yaml::Output YamlOut(OS, NULL, /*WrapColumn =*/0); + ELFStub Result = File->getStub(); + YamlOut << Result; + return Error::success(); +} Index: llvm/unittests/CMakeLists.txt =================================================================== --- llvm/unittests/CMakeLists.txt +++ llvm/unittests/CMakeLists.txt @@ -30,6 +30,7 @@ add_subdirectory(Passes) add_subdirectory(ProfileData) add_subdirectory(Support) +add_subdirectory(TAPI) add_subdirectory(Target) add_subdirectory(Transforms) add_subdirectory(XRay) Index: llvm/unittests/TAPI/CMakeLists.txt =================================================================== --- /dev/null +++ llvm/unittests/TAPI/CMakeLists.txt @@ -0,0 +1,7 @@ +set(LLVM_LINK_COMPONENTS + TAPI +) + +add_llvm_unittest(TapiTests + YAMLTest.cpp +) Index: llvm/unittests/TAPI/YAMLTest.cpp =================================================================== --- /dev/null +++ llvm/unittests/TAPI/YAMLTest.cpp @@ -0,0 +1,205 @@ +//===- llvm/unittests/TAPI/YAMLTest.cpp -----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ + +#include "llvm/ADT/StringRef.h" +#include "llvm/TAPI/ELFInterfaceFile.h" +#include "llvm/TAPI/TBEHandler.h" +#include "gtest/gtest.h" +#include + +using namespace llvm; +using namespace llvm::ELF; +using namespace llvm::tapi; + +ELFStub readFromBuffer(const char Data[]) { + TBEHandler Handler; + + StringRef Buf = StringRef(Data); + + std::unique_ptr File = Handler.readFile(Buf); + EXPECT_NE(File.get(), nullptr); + EXPECT_EQ(File->getFileType(), FileType::TBE_V1); + return File->getStub(); +} + +void compareByLine(StringRef LHS, StringRef RHS) { + StringRef Line1; + StringRef Line2; + while (LHS.size() > 0 && RHS.size() > 0) { + std::tie(Line1, LHS) = LHS.split('\n'); + std::tie(Line2, RHS) = RHS.split('\n'); + // Comparing StringRef objects works, but has messy output when not equal. + // Using STREQ on StringRef.data() doesn't work since they're not null + // terminated. + // This is inefficient, but forces null terminated strings that can be + // cleanly compared. + std::string LeftLine = Line1.str(); + std::string RightLine = Line2.str(); + EXPECT_STREQ(LeftLine.data(), RightLine.data()); + } +} + +TEST(LLVMTAPI, YAMLReadableTBE) { + const char Data[] = "--- !tapi-tbe-v1\n" + "SoName: test.so\n" + "Arch: x86_64\n" + "Symbols:\n" + " foo: { Type: Func, Undefined: true }\n" + "...\n"; + StringRef Buf = StringRef(Data); + TBEHandler Handler; + std::unique_ptr File = Handler.readFile(Buf); + EXPECT_NE(File.get(), nullptr); + ELFStub Stub = File->getStub(); + EXPECT_EQ(Stub.Arch, (uint16_t)llvm::ELF::EM_X86_64); + EXPECT_STREQ(Stub.SoName.c_str(), "test.so"); +} + +TEST(LLVMTAPI, YAMLReadsSymbols) { + const char Data[] = "--- !tapi-tbe-v1\n" + "SoName: test.so\n" + "Arch: x86_64\n" + "Symbols:\n" + " bar: { Type: Object, Size: 42 }\n" + " baz: { Type: TLS, Size: 3 }\n" + " foo: { Type: Func, Warning: \"Deprecated!\" }\n" + " nor: { Type: NoType, Undefined: true }\n" + " not: { Type: File, Undefined: true, Size: 111, " + "Warning: \'All fields populated!\' }\n" + "...\n"; + ELFStub Stub = readFromBuffer(Data); + EXPECT_EQ(Stub.Symbols.size(), 5u); + + ELFSymbol &SymBar = Stub.Symbols[0]; + EXPECT_STREQ(SymBar.Name.c_str(), "bar"); + EXPECT_EQ(SymBar.Size, 42u); + EXPECT_EQ(SymBar.Type, ELFSymbolType::Object); + EXPECT_FALSE(SymBar.Undefined); + EXPECT_FALSE(SymBar.Warning.hasValue()); + + ELFSymbol &SymBaz = Stub.Symbols[1]; + EXPECT_STREQ(SymBaz.Name.c_str(), "baz"); + EXPECT_EQ(SymBaz.Size, 3u); + EXPECT_EQ(SymBaz.Type, ELFSymbolType::TLS); + EXPECT_FALSE(SymBaz.Undefined); + EXPECT_FALSE(SymBaz.Warning.hasValue()); + + ELFSymbol &SymFoo = Stub.Symbols[2]; + EXPECT_STREQ(SymFoo.Name.c_str(), "foo"); + EXPECT_EQ(SymFoo.Size, 0u); + EXPECT_EQ(SymFoo.Type, ELFSymbolType::Func); + EXPECT_FALSE(SymFoo.Undefined); + EXPECT_TRUE(SymFoo.Warning.hasValue()); + EXPECT_STREQ(SymFoo.Warning->c_str(), "Deprecated!"); + + ELFSymbol &SymNor = Stub.Symbols[3]; + EXPECT_STREQ(SymNor.Name.c_str(), "nor"); + EXPECT_EQ(SymNor.Size, 0u); + EXPECT_EQ(SymNor.Type, ELFSymbolType::NoType); + EXPECT_TRUE(SymNor.Undefined); + EXPECT_FALSE(SymNor.Warning.hasValue()); + + ELFSymbol &SymNot = Stub.Symbols[4]; + EXPECT_STREQ(SymNot.Name.c_str(), "not"); + EXPECT_EQ(SymNot.Size, 111u); + EXPECT_EQ(SymNot.Type, ELFSymbolType::Unknown); + EXPECT_TRUE(SymNot.Undefined); + EXPECT_TRUE(SymNot.Warning.hasValue()); + EXPECT_STREQ(SymNot.Warning->c_str(), "All fields populated!"); +} + +TEST(LLVMTAPI, YAMLReadsNoSyms) { + const unsigned int NUM_SYMS = 0; + const char Data[] = "--- !tapi-tbe-v1\n" + "SoName: test.so\n" + "Arch: x86_64\n" + "Symbols: {}\n" + "...\n"; + ELFStub Stub = readFromBuffer(Data); + EXPECT_EQ(NUM_SYMS, Stub.Symbols.size()); +} + +TEST(LLVMTAPI, YAMLUnreadableTBE) { + TBEHandler Handler; + // Can't read: wrong format/version. + const char Data[] = "--- !ti-tbe-v0\n" + "SoName: test.so\n" + "Arch: x86_64\n" + "Symbols:\n" + " foo: { Type: Func, Undefined: true }\n"; + StringRef Buf = StringRef(Data); + std::unique_ptr File = Handler.readFile(Buf); + EXPECT_EQ(File.get(), nullptr); +} + +TEST(LLVMTAPI, YAMLWritesSymbols) { + const char Expected[] = + "--- !tapi-tbe-v1\n" + "SoName: test.so\n" + "Arch: AArch64\n" + "Symbols: \n" + " foo: { Type: NoType, Size: 99, Warning: Does nothing }\n" + " nor: { Type: Func, Undefined: true }\n" + " not: { Type: Unknown, Size: 12345678901234 }\n" + "...\n"; + ELFStub Stub; + Stub.SoName = "test.so"; + Stub.Arch = ELF::EM_AARCH64; + + ELFSymbol SymFoo; + SymFoo.Name = "foo"; + SymFoo.Size = 99u; + SymFoo.Type = ELFSymbolType::NoType; + SymFoo.Undefined = false; + SymFoo.Warning = "Does nothing"; + + ELFSymbol SymNor; + SymNor.Name = "nor"; + SymNor.Type = ELFSymbolType::Func; + SymNor.Undefined = true; + + ELFSymbol SymNot; + SymNot.Name = "not"; + SymNot.Size = 12345678901234u; + SymNot.Type = ELFSymbolType::Unknown; + SymNot.Undefined = false; + + // Deliberately not in order to check that result is sorted + Stub.Symbols.push_back(SymNot); + Stub.Symbols.push_back(SymFoo); + Stub.Symbols.push_back(SymNor); + + std::string Result; + raw_string_ostream OS(Result); + ELFInterfaceFile File(FileType::TBE_V1, Stub); + TBEHandler Handler; + EXPECT_FALSE(Handler.writeFile(OS, &File)); + Result = OS.str(); + compareByLine(Result.c_str(), Expected); +} + +TEST(LLVMTAPI, YAMLWritesNoSyms) { + const char Expected[] = "--- !tapi-tbe-v1\n" + "SoName: nosyms.so\n" + "Arch: x86_64\n" + "Symbols: \n" + " { }\n" + "...\n"; + ELFStub Stub; + Stub.SoName = "nosyms.so"; + Stub.Arch = ELF::EM_X86_64; + + std::string Result; + raw_string_ostream OS(Result); + ELFInterfaceFile File(FileType::TBE_V1, Stub); + TBEHandler Handler; + EXPECT_FALSE(Handler.writeFile(OS, &File)); + Result = OS.str(); + compareByLine(Result.c_str(), Expected); +}