Index: llvm/include/llvm/TextAPI/ELF/ELFStub.h =================================================================== --- /dev/null +++ llvm/include/llvm/TextAPI/ELF/ELFStub.h @@ -0,0 +1,68 @@ +//===- 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/Object/ELF.h" +#include "llvm/Support/VersionTuple.h" +#include + +namespace llvm { +namespace tapi { +namespace elf { + +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 { + std::string Name; + uint64_t Size; + ELFSymbolType Type; + bool Undefined; + bool Weak; + Optional Warning; +}; + +// A cumulative representation of ELF stubs. +// Both textual and binary stubs will read into and write from this object. +class ELFStub { +public: + VersionTuple TbeVersion; + std::string SoName; + ELFArch Arch; + std::vector NeededLibs; + std::vector Symbols; + + ELFStub() {} + ELFStub(const ELFStub &Stub); + ELFStub(ELFStub &&Stub); + + /// Sorts all symbols by name in ascending order. + void sortSymbolsByName(); +}; +} // end namespace elf +} // end namespace tapi +} // end namespace llvm + +#endif // LLVM_TEXTAPI_ELF_ELFSTUB_H Index: llvm/include/llvm/TextAPI/ELF/TBEHandler.h =================================================================== --- /dev/null +++ llvm/include/llvm/TextAPI/ELF/TBEHandler.h @@ -0,0 +1,48 @@ +//===- 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 tapi { +namespace elf { + +class ELFStub; + +const VersionTuple TBEVersionCurrent = VersionTuple(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 elf +} // end namespace tapi +} // end namespace llvm + +#endif // LLVM_TEXTAPI_ELF_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(TextAPI) 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 + TextAPI Target Testing ToolDrivers Index: llvm/lib/TextAPI/CMakeLists.txt =================================================================== --- /dev/null +++ llvm/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/lib/TextAPI/ELF/ELFStub.cpp =================================================================== --- /dev/null +++ llvm/lib/TextAPI/ELF/ELFStub.cpp @@ -0,0 +1,35 @@ +//===- 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 tapi::elf; + +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); +} + +void ELFStub::sortSymbolsByName() { + llvm::sort(Symbols, [](const ELFSymbol &LHS, const ELFSymbol &RHS) -> bool { + return LHS.Name < RHS.Name; + }); +} Index: llvm/lib/TextAPI/ELF/TBEHandler.cpp =================================================================== --- /dev/null +++ llvm/lib/TextAPI/ELF/TBEHandler.cpp @@ -0,0 +1,191 @@ +//===- 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/DenseMap.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 tapi::elf; + +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. + 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 unknown, treat as EM_NONE. + if (ArchMap.count(Value) == 0) + Out << ArchMap.lookup(ELF::EM_NONE); + else + Out << ArchMap.lookup(Value); + } + + static StringRef input(StringRef Scalar, void *, ELFArchMapper &Value) { + // Map from architecture string to integer. + 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 unknown, map to EM_NONE. + if (ArchMap.count(Scalar) == 0) { + Value = ArchMap.lookup("Unknown"); + return StringRef("Unknown architecture."); + } + + Value = ArchMap.lookup(Scalar); + + // 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; +}; + +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. mapOptional() produces invalid YAML + // for empty CustomMappingTraits. This is due to not knowing whether the + // CustomMappingTraits list is empty. The workaround here is to use + // mapRequired() and manually add flow braces to indicate an empty map. + if (List.size() == 0) { + IO.beginFlowMapping(); + IO.endFlowMapping(); + } + + for (auto &Sym : List) + IO.mapRequired(Sym.Name.c_str(), 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; + Stub->sortSymbolsByName(); + 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/lib/TextAPI/LLVMBuild.txt =================================================================== --- /dev/null +++ llvm/lib/TextAPI/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 = TextAPI +parent = Libraries +required_libraries = Object Support BinaryFormat 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(TextAPI) add_subdirectory(Target) add_subdirectory(Transforms) add_subdirectory(XRay) Index: llvm/unittests/TextAPI/CMakeLists.txt =================================================================== --- /dev/null +++ llvm/unittests/TextAPI/CMakeLists.txt @@ -0,0 +1,7 @@ +set(LLVM_LINK_COMPONENTS + TextAPI +) + +add_llvm_unittest(TapiTests + ELFYAMLTest.cpp +) Index: llvm/unittests/TextAPI/ELFYAMLTest.cpp =================================================================== --- /dev/null +++ llvm/unittests/TextAPI/ELFYAMLTest.cpp @@ -0,0 +1,219 @@ +//===- 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/Object/ELF.h" +#include "llvm/TextAPI/ELF/ELFStub.h" +#include "llvm/TextAPI/ELF/TBEHandler.h" +#include "gtest/gtest.h" +#include + +using namespace llvm; +using namespace llvm::ELF; +using namespace llvm::tapi::elf; + +std::unique_ptr readFromBuffer(const char Data[]) { + TBEHandler Handler; + + StringRef Buf = StringRef(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); + + 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(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; + 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); + Stub.sortSymbolsByName(); + + 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" + "...\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); +}