Index: llvm/trunk/include/llvm/TextAPI/ELF/ELFStub.h =================================================================== --- llvm/trunk/include/llvm/TextAPI/ELF/ELFStub.h +++ llvm/trunk/include/llvm/TextAPI/ELF/ELFStub.h @@ -0,0 +1,69 @@ +//===- 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_TEXTAPI_ELF_ELFSTUB_H +#define LLVM_TEXTAPI_ELF_ELFSTUB_H + +#include "llvm/BinaryFormat/ELF.h" +#include "llvm/Support/VersionTuple.h" +#include +#include + +namespace llvm { +namespace elfabi { + +typedef uint16_t ELFArch; + +enum class 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 { + ELFSymbol(std::string SymbolName) : Name(SymbolName) {} + std::string Name; + uint64_t Size; + ELFSymbolType Type; + bool Undefined; + bool Weak; + Optional Warning; + bool operator<(const ELFSymbol &RHS) const { + return Name < RHS.Name; + } +}; + +// A cumulative representation of ELF stubs. +// Both textual and binary stubs will read into and write from this object. +class ELFStub { +// TODO: Add support for symbol versioning. +public: + VersionTuple TbeVersion; + std::string SoName; + ELFArch Arch; + std::vector NeededLibs; + std::set Symbols; + + ELFStub() {} + ELFStub(const ELFStub &Stub); + ELFStub(ELFStub &&Stub); +}; +} // end namespace elfabi +} // end namespace llvm + +#endif // LLVM_TEXTAPI_ELF_ELFSTUB_H Index: llvm/trunk/include/llvm/TextAPI/ELF/TBEHandler.h =================================================================== --- llvm/trunk/include/llvm/TextAPI/ELF/TBEHandler.h +++ llvm/trunk/include/llvm/TextAPI/ELF/TBEHandler.h @@ -0,0 +1,46 @@ +//===- 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_TEXTAPI_ELF_TBEHANDLER_H +#define LLVM_TEXTAPI_ELF_TBEHANDLER_H + +#include "llvm/Support/VersionTuple.h" +#include + +namespace llvm { + +class raw_ostream; +class Error; +class StringRef; +class VersionTuple; + +namespace elfabi { + +class ELFStub; + +const VersionTuple TBEVersionCurrent(1, 0); + +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 ELFStub &Stub); +}; +} // end namespace elfabi +} // end namespace llvm + +#endif // LLVM_TEXTAPI_ELF_TBEHANDLER_H Index: llvm/trunk/lib/CMakeLists.txt =================================================================== --- llvm/trunk/lib/CMakeLists.txt +++ llvm/trunk/lib/CMakeLists.txt @@ -23,6 +23,7 @@ add_subdirectory(LineEditor) add_subdirectory(ProfileData) add_subdirectory(Passes) +add_subdirectory(TextAPI) add_subdirectory(ToolDrivers) add_subdirectory(XRay) add_subdirectory(Testing) Index: llvm/trunk/lib/LLVMBuild.txt =================================================================== --- llvm/trunk/lib/LLVMBuild.txt +++ llvm/trunk/lib/LLVMBuild.txt @@ -40,6 +40,7 @@ ProfileData Support TableGen + TextAPI Target Testing ToolDrivers Index: llvm/trunk/lib/TextAPI/CMakeLists.txt =================================================================== --- llvm/trunk/lib/TextAPI/CMakeLists.txt +++ llvm/trunk/lib/TextAPI/CMakeLists.txt @@ -0,0 +1,7 @@ +add_llvm_library(LLVMTextAPI + ELF/ELFStub.cpp + ELF/TBEHandler.cpp + + ADDITIONAL_HEADER_DIRS + "${LLVM_MAIN_INCLUDE_DIR}/llvm/TextAPI" +) Index: llvm/trunk/lib/TextAPI/ELF/ELFStub.cpp =================================================================== --- llvm/trunk/lib/TextAPI/ELF/ELFStub.cpp +++ llvm/trunk/lib/TextAPI/ELF/ELFStub.cpp @@ -0,0 +1,29 @@ +//===- ELFStub.cpp --------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ + +#include "llvm/TextAPI/ELF/ELFStub.h" + +using namespace llvm; +using namespace llvm::elfabi; + +ELFStub::ELFStub(ELFStub const &Stub) { + TbeVersion = Stub.TbeVersion; + Arch = Stub.Arch; + SoName = Stub.SoName; + NeededLibs = Stub.NeededLibs; + Symbols = Stub.Symbols; +} + +ELFStub::ELFStub(ELFStub &&Stub) { + TbeVersion = std::move(Stub.TbeVersion); + Arch = std::move(Stub.Arch); + SoName = std::move(Stub.SoName); + NeededLibs = std::move(Stub.NeededLibs); + Symbols = std::move(Stub.Symbols); +} Index: llvm/trunk/lib/TextAPI/ELF/TBEHandler.cpp =================================================================== --- llvm/trunk/lib/TextAPI/ELF/TBEHandler.cpp +++ llvm/trunk/lib/TextAPI/ELF/TBEHandler.cpp @@ -0,0 +1,176 @@ +//===- TBEHandler.cpp -----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ + +#include "llvm/TextAPI/ELF/TBEHandler.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/TextAPI/ELF/ELFStub.h" + +using namespace llvm; +using namespace llvm::elfabi; + +LLVM_YAML_STRONG_TYPEDEF(ELFArch, ELFArchMapper); + +namespace llvm { +namespace yaml { + +/// YAML traits for ELFSymbolType. +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); + // Treat other symbol types as noise, and map to Unknown. + if (!IO.outputting() && IO.matchEnumFallback()) + SymbolType = ELFSymbolType::Unknown; + } +}; + +/// YAML traits for ELFArch. +template <> struct ScalarTraits { + static void output(const ELFArchMapper &Value, void *, + llvm::raw_ostream &Out) { + // Map from integer to architecture string. + switch (Value) { + case (ELFArch)ELF::EM_X86_64: + Out << "x86_64"; + break; + case (ELFArch)ELF::EM_AARCH64: + Out << "AArch64"; + break; + case (ELFArch)ELF::EM_NONE: + default: + Out << "Unknown"; + } + } + + static StringRef input(StringRef Scalar, void *, ELFArchMapper &Value) { + // Map from architecture string to integer. + Value = StringSwitch(Scalar) + .Case("x86_64", ELF::EM_X86_64) + .Case("AArch64", ELF::EM_AARCH64) + .Case("Unknown", ELF::EM_NONE) + .Default(ELF::EM_NONE); + + // Returning empty StringRef indicates successful parse. + return StringRef(); + } + + // Don't place quotation marks around architecture value. + static QuotingType mustQuote(StringRef) { return QuotingType::None; } +}; + +/// YAML traits for TbeVersion. +template <> struct ScalarTraits { + static void output(const VersionTuple &Value, void *, + llvm::raw_ostream &Out) { + Out << Value.getAsString(); + } + + static StringRef input(StringRef Scalar, void *, VersionTuple &Value) { + if (Value.tryParse(Scalar)) + return StringRef("Can't parse version: invalid version format."); + + if (Value > TBEVersionCurrent) + return StringRef("Unsupported TBE version."); + + // Returning empty StringRef indicates successful parse. + return StringRef(); + } + + // Don't place quotation marks around version value. + static QuotingType mustQuote(StringRef) { return QuotingType::None; } +}; + +/// YAML traits for ELFSymbol. +template <> struct MappingTraits { + static void mapping(IO &IO, ELFSymbol &Symbol) { + IO.mapRequired("Type", Symbol.Type); + // The need for symbol size depends on the symbol type. + if (Symbol.Type == ELFSymbolType::NoType) { + IO.mapOptional("Size", Symbol.Size, (uint64_t)0); + } 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; +}; + +/// YAML traits for set of ELFSymbols. +template <> struct CustomMappingTraits> { + static void inputOne(IO &IO, StringRef Key, std::set &Set) { + ELFSymbol Sym(Key.str()); + IO.mapRequired(Key.str().c_str(), Sym); + Set.insert(Sym); + } + + static void output(IO &IO, std::set &Set) { + for (auto &Sym : Set) + IO.mapRequired(Sym.Name.c_str(), const_cast(Sym)); + } +}; + +/// YAML traits for generic string vectors (i.e. list of needed libraries). +template <> struct SequenceTraits> { + static size_t size(IO &IO, std::vector &List) { + return List.size(); + } + + static std::string &element(IO &IO, std::vector &List, + size_t Index) { + if (Index >= List.size()) + List.resize(Index + 1); + return List[Index]; + } + + // Compacts list of needed libraries into a single line. + static const bool flow = true; +}; + +/// YAML traits for ELFStub objects. +template <> struct MappingTraits { + static void mapping(IO &IO, ELFStub &Stub) { + if (!IO.mapTag("!tapi-tbe", true)) + IO.setError("Not a .tbe YAML file."); + IO.mapRequired("TbeVersion", Stub.TbeVersion); + IO.mapRequired("SoName", Stub.SoName); + IO.mapRequired("Arch", (ELFArchMapper &)Stub.Arch); + IO.mapOptional("NeededLibs", Stub.NeededLibs); + IO.mapRequired("Symbols", Stub.Symbols); + } +}; + +} // end namespace yaml +} // end namespace llvm + +std::unique_ptr TBEHandler::readFile(StringRef Buf) { + yaml::Input YamlIn(Buf); + std::unique_ptr Stub(new ELFStub()); + YamlIn >> *Stub; + if (YamlIn.error()) + return nullptr; + return Stub; +} + +Error TBEHandler::writeFile(raw_ostream &OS, const ELFStub &Stub) { + yaml::Output YamlOut(OS, NULL, /*WrapColumn =*/0); + + YamlOut << const_cast(Stub); + return Error::success(); +} Index: llvm/trunk/lib/TextAPI/LLVMBuild.txt =================================================================== --- llvm/trunk/lib/TextAPI/LLVMBuild.txt +++ llvm/trunk/lib/TextAPI/LLVMBuild.txt @@ -0,0 +1,22 @@ +;===- ./lib/TextAPI/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 = TextAPI +parent = Libraries +required_libraries = Support BinaryFormat Index: llvm/trunk/unittests/CMakeLists.txt =================================================================== --- llvm/trunk/unittests/CMakeLists.txt +++ llvm/trunk/unittests/CMakeLists.txt @@ -30,6 +30,7 @@ add_subdirectory(Passes) add_subdirectory(ProfileData) add_subdirectory(Support) +add_subdirectory(TextAPI) add_subdirectory(Target) add_subdirectory(Transforms) add_subdirectory(XRay) Index: llvm/trunk/unittests/TextAPI/CMakeLists.txt =================================================================== --- llvm/trunk/unittests/TextAPI/CMakeLists.txt +++ llvm/trunk/unittests/TextAPI/CMakeLists.txt @@ -0,0 +1,7 @@ +set(LLVM_LINK_COMPONENTS + TextAPI +) + +add_llvm_unittest(TapiTests + ELFYAMLTest.cpp +) Index: llvm/trunk/unittests/TextAPI/ELFYAMLTest.cpp =================================================================== --- llvm/trunk/unittests/TextAPI/ELFYAMLTest.cpp +++ llvm/trunk/unittests/TextAPI/ELFYAMLTest.cpp @@ -0,0 +1,216 @@ +//===- llvm/unittests/TextAPI/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/TextAPI/ELF/ELFStub.h" +#include "llvm/TextAPI/ELF/TBEHandler.h" +#include "llvm/Support/Error.h" +#include "gtest/gtest.h" +#include + +using namespace llvm; +using namespace llvm::ELF; +using namespace llvm::elfabi; + +std::unique_ptr readFromBuffer(const char Data[]) { + TBEHandler Handler; + + StringRef Buf(Data); + + std::unique_ptr Stub = Handler.readFile(Buf); + EXPECT_NE(Stub.get(), nullptr); + return Stub; +} + +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 these substrings are + // not null terminated. + // This is inefficient, but forces null terminated strings that can be + // cleanly compared. + EXPECT_STREQ(Line1.str().data(), Line2.str().data()); + } +} + +TEST(ElfYamlTextAPI, YAMLReadableTBE) { + const char Data[] = "--- !tapi-tbe\n" + "TbeVersion: 1.0\n" + "SoName: test.so\n" + "Arch: x86_64\n" + "NeededLibs: [libc.so, libfoo.so, libbar.so]\n" + "Symbols:\n" + " foo: { Type: Func, Undefined: true }\n" + "...\n"; + StringRef Buf = StringRef(Data); + TBEHandler Handler; + std::unique_ptr Stub = Handler.readFile(Buf); + EXPECT_NE(Stub.get(), nullptr); + EXPECT_EQ(Stub->Arch, (uint16_t)llvm::ELF::EM_X86_64); + EXPECT_STREQ(Stub->SoName.c_str(), "test.so"); + EXPECT_EQ(Stub->NeededLibs.size(), 3u); + EXPECT_STREQ(Stub->NeededLibs[0].c_str(), "libc.so"); + EXPECT_STREQ(Stub->NeededLibs[1].c_str(), "libfoo.so"); + EXPECT_STREQ(Stub->NeededLibs[2].c_str(), "libbar.so"); +} + +TEST(ElfYamlTextAPI, YAMLReadsTBESymbols) { + const char Data[] = "--- !tapi-tbe\n" + "TbeVersion: 1.0\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"; + std::unique_ptr Stub = readFromBuffer(Data); + EXPECT_EQ(Stub->Symbols.size(), 5u); + + auto Iterator = Stub->Symbols.begin(); + ELFSymbol const &SymBar = *Iterator++; + 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 const &SymBaz = *Iterator++; + 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 const &SymFoo = *Iterator++; + 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 const &SymNor = *Iterator++; + 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 const &SymNot = *Iterator++; + 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(ElfYamlTextAPI, YAMLReadsNoTBESyms) { + const char Data[] = "--- !tapi-tbe\n" + "TbeVersion: 1.0\n" + "SoName: test.so\n" + "Arch: x86_64\n" + "Symbols: {}\n" + "...\n"; + std::unique_ptr Stub = readFromBuffer(Data); + EXPECT_EQ(0u, Stub->Symbols.size()); +} + +TEST(ElfYamlTextAPI, YAMLUnreadableTBE) { + TBEHandler Handler; + // Can't read: wrong format/version. + const char Data[] = "--- !tapi-tbz\n" + "TbeVersion: z.3\n" + "SoName: test.so\n" + "Arch: x86_64\n" + "Symbols:\n" + " foo: { Type: Func, Undefined: true }\n"; + StringRef Buf = StringRef(Data); + std::unique_ptr Stub = Handler.readFile(Buf); + EXPECT_EQ(Stub.get(), nullptr); +} + +TEST(ElfYamlTextAPI, YAMLWritesTBESymbols) { + const char Expected[] = + "--- !tapi-tbe\n" + "TbeVersion: 1.0\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.TbeVersion = VersionTuple(1, 0); + Stub.SoName = "test.so"; + Stub.Arch = ELF::EM_AARCH64; + + ELFSymbol SymFoo("foo"); + SymFoo.Size = 99u; + SymFoo.Type = ELFSymbolType::NoType; + SymFoo.Undefined = false; + SymFoo.Warning = "Does nothing"; + + ELFSymbol SymNor("nor"); + SymNor.Type = ELFSymbolType::Func; + SymNor.Undefined = true; + + ELFSymbol SymNot("not"); + SymNot.Size = 12345678901234u; + SymNot.Type = ELFSymbolType::Unknown; + SymNot.Undefined = false; + + // Deliberately not in order to check that result is sorted. + Stub.Symbols.insert(SymNot); + Stub.Symbols.insert(SymFoo); + Stub.Symbols.insert(SymNor); + + // Ensure move constructor works as expected. + ELFStub Moved = std::move(Stub); + + std::string Result; + raw_string_ostream OS(Result); + TBEHandler Handler; + EXPECT_FALSE(Handler.writeFile(OS, Moved)); + Result = OS.str(); + compareByLine(Result.c_str(), Expected); +} + +TEST(ElfYamlTextAPI, YAMLWritesNoTBESyms) { + const char Expected[] = "--- !tapi-tbe\n" + "TbeVersion: 1.0\n" + "SoName: nosyms.so\n" + "Arch: x86_64\n" + "NeededLibs: [ libc.so, libfoo.so, libbar.so ]\n" + "Symbols: {}\n" + "...\n"; + ELFStub Stub; + Stub.TbeVersion = VersionTuple(1, 0); + Stub.SoName = "nosyms.so"; + Stub.Arch = ELF::EM_X86_64; + Stub.NeededLibs.push_back("libc.so"); + Stub.NeededLibs.push_back("libfoo.so"); + Stub.NeededLibs.push_back("libbar.so"); + + std::string Result; + raw_string_ostream OS(Result); + TBEHandler Handler; + EXPECT_FALSE(Handler.writeFile(OS, Stub)); + Result = OS.str(); + compareByLine(Result.c_str(), Expected); +}