Index: llvm/test/CMakeLists.txt =================================================================== --- llvm/test/CMakeLists.txt +++ llvm/test/CMakeLists.txt @@ -61,6 +61,7 @@ dsymutil llvm-dwarfdump llvm-dwp + llvm-elfabi llvm-exegesis llvm-extract llvm-isel-fuzzer Index: llvm/test/tools/llvm-elfabi/binary-read-arch.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-elfabi/binary-read-arch.test @@ -0,0 +1,16 @@ +# RUN: yaml2obj %s > %t +# RUN: llvm-elfabi %t -emit-tbe=%t2 +# RUN: cat %t2 | FileCheck %s + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 + +#CHECK: --- !tapi-tbe +#CHECK-NEXT: TbeVersion: {{[1-9]\d*\.(0|([1-9]\d*))}} +#CHECK-NEXT: Arch: x86_64 +#CHECK-NEXT: Symbols: {} +#CHECK-NEXT: ... Index: llvm/test/tools/llvm-elfabi/fail-file-open.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-elfabi/fail-file-open.test @@ -0,0 +1,5 @@ +# RUN: not llvm-elfabi %s.NotAFileInTestingDir -emit-tbe=%t 2>&1 | FileCheck %s + +This file will not be read. An invalid file path is fed to llvm-elfabi. + +#CHECK: Error: Could not open `{{.*}}`. Index: llvm/test/tools/llvm-elfabi/read-unsupported-file.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-elfabi/read-unsupported-file.test @@ -0,0 +1,9 @@ +# RUN: not llvm-elfabi %s -emit-tbe=%t &> %t.out +# RUN: cat %t.out | FileCheck %s + +This is just some text that cannot be read by llvm-elfabi. + +#CHECK: Error: Encountered multiple errors: +#CHECK-NEXT: 0 (BinaryRead): The file was not recognized as a valid object file +#CHECK-NEXT: 1 (YamlParse): YAML failed reading as TBE +#CHECK: All file readers failed to read `{{.*}}`. (unsupported/malformed file?) Index: llvm/test/tools/llvm-elfabi/replace-soname-tbe.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-elfabi/replace-soname-tbe.test @@ -0,0 +1,17 @@ +# RUN: yaml2obj %s > %t +# RUN: llvm-elfabi %t --emit-tbe=%t2 -so-name=best.so +# RUN: cat %t2 | FileCheck %s + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_AARCH64 + +#CHECK: --- !tapi-tbe +#CHECK-NEXT: TbeVersion: {{[1-9]\d*\.(0|([1-9]\d*))}} +#CHECK-NEXT: SoName: best.so +#CHECK-NEXT: Arch: AArch64 +#CHECK-NEXT: Symbols: {} +#CHECK-NEXT: ... Index: llvm/test/tools/llvm-elfabi/tbe-read-basic.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-elfabi/tbe-read-basic.test @@ -0,0 +1,26 @@ +# RUN: llvm-elfabi %s -emit-tbe=%t +# RUN: cat %t | FileCheck %s + +--- !tapi-tbe +SoName: somelib.so +TbeVersion: 1.0 +Arch: x86_64 +Symbols: + foo: { Type: Func } + bar: { Type: Object, Size: 42 } + baz: { Type: Object, Size: 8 } + not: { Type: Object, Undefined: true, Size: 128 } + nor: { Type: Func, Undefined: true } +... + +#CHECK: --- !tapi-tbe +#CHECK-NEXT: TbeVersion: {{[1-9]\d*\.(0|([1-9]\d*))}} +#CHECK-NEXT: SoName: somelib.so +#CHECK-NEXT: Arch: x86_64 +#CHECK-NEXT: Symbols: +#CHECK-NEXT: bar: { Type: Object, Size: 42 } +#CHECK-NEXT: baz: { Type: Object, Size: 8 } +#CHECK-NEXT: foo: { Type: Func } +#CHECK-NEXT: nor: { Type: Func, Undefined: true } +#CHECK-NEXT: not: { Type: Object, Size: 128, Undefined: true } +#CHECK-NEXT: ... Index: llvm/tools/LLVMBuild.txt =================================================================== --- llvm/tools/LLVMBuild.txt +++ llvm/tools/LLVMBuild.txt @@ -32,6 +32,7 @@ llvm-dis llvm-dwarfdump llvm-dwp + llvm-elfabi llvm-exegesis llvm-extract llvm-jitlistener Index: llvm/tools/llvm-elfabi/CMakeLists.txt =================================================================== --- /dev/null +++ llvm/tools/llvm-elfabi/CMakeLists.txt @@ -0,0 +1,11 @@ +set(LLVM_LINK_COMPONENTS + Object + Support + TextAPI + ) + +add_llvm_tool(llvm-elfabi + ELFObjHandler.cpp + ErrorCollector.cpp + llvm-elfabi.cpp + ) Index: llvm/tools/llvm-elfabi/ELFObjHandler.h =================================================================== --- /dev/null +++ llvm/tools/llvm-elfabi/ELFObjHandler.h @@ -0,0 +1,45 @@ +//===- ELFObjHandler.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ +/// +/// This supports reading and writing of elf dynamic shared objects. +/// +//===-----------------------------------------------------------------------===/ + +#ifndef LLVM_TOOLS_ELFABI_ELFOBJHANDLER_H +#define LLVM_TOOLS_ELFABI_ELFOBJHANDLER_H + +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Object/ELFTypes.h" +#include "llvm/TextAPI/ELF/ELFStub.h" + +namespace llvm { + +class MemoryBuffer; + +namespace elfabi { + +/// Returns a new ELFStub with all members populated from an ELFObjectFile. +/// @param ElfObj Pointer to an ELFObjectFile as a source. +template +Expected> +buildStub(const object::ELFObjectFile &ElfObj); + +/// Populate the `Arch` member of an ELFStub. +/// @param TargetStub Reference to an ELFStub who's member will be populated. +/// @param Header Reference to an ELF file header. +template +Error populateArch(ELFStub &TargetStub, const typename ELFT::Ehdr &Header); + +/// Attempt to read a binary ELF file from a MemoryBuffer reference. +Expected> readELFFile(MemoryBufferRef Buf); + +} // end namespace elfabi +} // end namespace llvm + +#endif // LLVM_TOOLS_ELFABI_ELFOBJHANDLER_H Index: llvm/tools/llvm-elfabi/ELFObjHandler.cpp =================================================================== --- /dev/null +++ llvm/tools/llvm-elfabi/ELFObjHandler.cpp @@ -0,0 +1,80 @@ +//===- ELFObjHandler.cpp --------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ + +#include "ELFObjHandler.h" +#include "llvm/Object/Binary.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Object/ELFTypes.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/TextAPI/ELF/ELFStub.h" + +using llvm::MemoryBufferRef; +using llvm::object::ELFObjectFile; + +using namespace llvm; +using namespace llvm::object; +using namespace llvm::elfabi; +using namespace llvm::ELF; + +namespace llvm { +namespace elfabi { + +template +Expected> +buildStub(const object::ELFObjectFile &ElfObj) { + std::unique_ptr DestStub = make_unique(); + using Elf_Ehdr = typename ELFT::Ehdr; + const ELFFile *ElfFile = ElfObj.getELFFile(); + + // Fetch needed tables/headers. This will eventually include .dynamic, + // .dynsym, and .dynstr. + const Elf_Ehdr *ElfHeader = ElfFile->getHeader(); + + // Populate architecture. + Error ObjectFileReadError = populateArch(*DestStub, *ElfHeader); + if (ObjectFileReadError) + return std::move(ObjectFileReadError); + + // TODO: Populate SoName from .dynamic entries and linked string table. + // TODO: Populate NeededLibs from .dynamic entries and linked string table. + // TODO: Populate Symbols from .dynsym table and linked string table. + + return std::move(DestStub); +} + +template +Error populateArch(ELFStub &TargetStub, const typename ELFT::Ehdr &Header) { + TargetStub.Arch = Header.e_machine; + return Error::success(); +} + +Expected> readELFFile(MemoryBufferRef Buf) { + Expected> BinOrErr = createBinary(Buf); + if (!BinOrErr) { + return BinOrErr.takeError(); + } + + Binary *Bin = BinOrErr->get(); + if (auto Obj = dyn_cast>(Bin)) { + return buildStub(*Obj); + } else if (auto Obj = dyn_cast>(Bin)) { + return buildStub(*Obj); + } else if (auto Obj = dyn_cast>(Bin)) { + return buildStub(*Obj); + } else if (auto Obj = dyn_cast>(Bin)) { + return buildStub(*Obj); + } + + return createStringError(errc::not_supported, "Unsupported binary format"); +} + +} // end namespace elfabi +} // end namespace llvm Index: llvm/tools/llvm-elfabi/ErrorCollector.h =================================================================== --- /dev/null +++ llvm/tools/llvm-elfabi/ErrorCollector.h @@ -0,0 +1,76 @@ +//===- ErrorCollector.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ +/// +/// This class collects errors that should be reported or ignored in aggregate. +/// +/// Like llvm::Error, an ErrorCollector cannot be copied. Unlike llvm::Error, +/// an ErrorCollector may be destroyed if it was originally constructed to treat +/// errors as non-fatal. In this case, all Errors are consumed upon destruction. +/// An ErrorCollector may be initially constructed (or escalated) such that +/// errors are treated as fatal. This causes a crash if an attempt is made to +/// delete the ErrorCollector when some Errors have not been retrieved via +/// makeError(). +/// +//===-----------------------------------------------------------------------===/ + +#ifndef LLVM_TOOLS_ELFABI_ERRORCOLLECTOR_H +#define LLVM_TOOLS_ELFABI_ERRORCOLLECTOR_H + +#include "llvm/Support/Error.h" +#include + +namespace llvm { +namespace elfabi { + +class ErrorCollector { +public: + /// Upon destruction, an ErrorCollector will crash if UseFatalErrors=true and + /// there are remaining errors that haven't been fetched by makeError(). + ErrorCollector(bool UseFatalErrors = true) : ErrorsAreFatal(UseFatalErrors) {} + // Don't allow copying. + ErrorCollector(const ErrorCollector &Stub) = delete; + ErrorCollector &operator=(const ErrorCollector &Other) = delete; + ~ErrorCollector(); + + // TODO: Add move constructor and operator= when a testable situation arises. + + /// Logs all errors to a raw_ostream. + void log(raw_ostream &OS); + + /// Returns a single error that contains messages for all stored Errors. + Error makeError(); + + /// Adds an error with a descriptive tag that helps with identification. + /// If the error is an Error::success(), it is checked and discarded. + void addError(Error E, std::string Tag); + + /// This ensures an ErrorCollector will treat unhandled errors as fatal. + /// This function should be called if errors that usually can be ignored + /// are suddenly of concern (i.e. attempt multiple things that return Error, + /// but only care about the Errors if no attempt succeeds). + void escalateToFatal(); + + /// Returns true if all errors have been retrieved through makeError(), or + /// false if errors have been added since the last makeError() call. + bool allErrsHandled() const; + + // TODO: raw_ostream insert (<<) operator when a testable situation arises. + +private: + LLVM_ATTRIBUTE_NORETURN void fatalUncheckedError(); + size_t UnhandledErrIdx = 0; + bool ErrorsAreFatal; + std::vector Errors; + std::vector Tags; +}; + +} // end namespace elfabi +} // end namespace llvm + +#endif // LLVM_TOOLS_ELFABI_ERRORCOLLECTOR_H Index: llvm/tools/llvm-elfabi/ErrorCollector.cpp =================================================================== --- /dev/null +++ llvm/tools/llvm-elfabi/ErrorCollector.cpp @@ -0,0 +1,70 @@ +//===- ErrorCollector.cpp -------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ + +#include "ErrorCollector.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" +#include + +using namespace llvm; +using namespace llvm::elfabi; + +void ErrorCollector::escalateToFatal() { + ErrorsAreFatal = true; +} + +void ErrorCollector::addError(Error Err, std::string Tag) { + if (Err) { + Errors.push_back(std::move(Err)); + Tags.push_back(Tag); + } +} + +Error ErrorCollector::makeError() { + // TODO: Make this return something (an AggregateError?) that gives more + // individual control over each error and which might be of interest. + std::string Message; + raw_string_ostream RawSS(Message); + log(RawSS); + UnhandledErrIdx = Errors.size(); + return createStringError(errc::interrupted, RawSS.str().c_str()); +} + +void ErrorCollector::log(raw_ostream &OS) { + OS << "Encountered multiple errors:\n"; + for (size_t i = 0; i < Errors.size(); ++i) { + OS << i << " (" << Tags[i] << "): " << Errors[i]; + if (i != Errors.size() - 1) + OS << "\n"; + } +} + +bool ErrorCollector::allErrsHandled() const { + if (UnhandledErrIdx < Errors.size()) + return false; + + return true; +} + +ErrorCollector::~ErrorCollector() { + if (ErrorsAreFatal && !allErrsHandled()) + fatalUncheckedError(); + + for (Error &E : Errors) { + consumeError(std::move(E)); + } + Errors.clear(); +} + +LLVM_ATTRIBUTE_NORETURN void ErrorCollector::fatalUncheckedError() { + errs() << "Aborted due to unhandled Error(s):\n"; + log(errs()); + abort(); +} Index: llvm/tools/llvm-elfabi/LLVMBuild.txt =================================================================== --- /dev/null +++ llvm/tools/llvm-elfabi/LLVMBuild.txt @@ -0,0 +1,22 @@ +;===- ./tools/llvm-elfabi/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 = Tool +name = llvm-elfabi +parent = Tools +required_libraries = Object Support TextAPI Index: llvm/tools/llvm-elfabi/llvm-elfabi.cpp =================================================================== --- /dev/null +++ llvm/tools/llvm-elfabi/llvm-elfabi.cpp @@ -0,0 +1,129 @@ +//===- llvm-elfabi.cpp ----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ + +#include "ELFObjHandler.h" +#include "ErrorCollector.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/FileOutputBuffer.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/TextAPI/ELF/TBEHandler.h" +#include + +using namespace llvm; +using namespace llvm::elfabi; + +// Command line flags: +cl::opt InputFilePath(cl::Positional, cl::desc("input"), + cl::Required); +cl::opt + EmitTBE("emit-tbe", + cl::desc("Emit a text-based ELF stub (.tbe) from the input file"), + cl::value_desc("path")); +cl::opt SetSoname( + "so-name", + cl::desc("Manually set the DT_SONAME entry of any emitted files."), + cl::value_desc("name")); + +/// updateTBEVersion() updates the TbeVersion field of any emitted .tbe file to +/// reflect the version of TextAPI writer the file was written with. +static void updateTBEVersion(ELFStub &Stub) { + Stub.TbeVersion = TBEVersionCurrent; +} + +/// updateSoName() overrides the DT_SONAME for a stub as specified by the user. +static void updateSoName(ELFStub &Stub) { + if (SetSoname.getNumOccurrences() == 1) { + Stub.SoName = SetSoname; + } +} + +/// writeTBE() writes a Text-Based ELF stub to a file using the latest version +/// of the YAML parser. +static Error writeTBE(StringRef FilePath, ELFStub &Stub) { + std::error_code SysErr; + + // Open file for writing. + raw_fd_ostream Out(FilePath, SysErr); + if (SysErr) + return createStringError(SysErr, "Couldn't open `%s` for writing.", + FilePath.data()); + // Write file. + Error YAMLErr = writeTBEToOutputStream(Out, Stub); + if (YAMLErr) + return YAMLErr; + + return Error::success(); +} + +/// readInputFile populates an ELFStub by attempting to read the +/// input file using both the TBE and binary ELF parsers. +static Expected> readInputFile(StringRef FilePath) { + ErrorCollector EC(/*UseFatalErrors=*/false); + // Read in file. + ErrorOr> BufOrError = + MemoryBuffer::getFile(FilePath); + if (!BufOrError) { + return createStringError(BufOrError.getError(), "Could not open `%s`.", + FilePath.data()); + } + + std::unique_ptr FileReadBuffer = std::move(BufOrError.get()); + // First try to read as a binary (fails fast if not binary). + Expected> StubFromELF = + readELFFile(FileReadBuffer->getMemBufferRef()); + Error ELFReadError = StubFromELF.takeError(); + if (!ELFReadError) { + return std::move(StubFromELF.get()); + } + EC.addError(std::move(ELFReadError), "BinaryRead"); + + // Fall back to reading as a tbe. + Expected> StubFromTBE = + readTBEFromBuffer(FileReadBuffer->getBuffer()); + Error TBEReadError = StubFromTBE.takeError(); + if (!TBEReadError) { + return std::move(StubFromTBE.get()); + } + EC.addError(std::move(TBEReadError), "YamlParse"); + + // If both readers fail, build a new error that includes all information. + EC.escalateToFatal(); + return EC.makeError(); +} + +int main(int argc, char *argv[]) { + // Parse arguments. + cl::ParseCommandLineOptions(argc, argv); + + Expected> StubOrErr = readInputFile(InputFilePath); + if (!StubOrErr) { + Error ReadError = StubOrErr.takeError(); + errs() << "Error: " << ReadError << "\n"; + errs() << "All file readers failed to read `" << InputFilePath.c_str() + << "`. (unsupported/malformed file?)\n"; + exit(1); + } + + std::unique_ptr TargetStub = std::move(StubOrErr.get()); + + // Write out File. + if (EmitTBE.getNumOccurrences() == 1) { + updateTBEVersion(*TargetStub); + updateSoName(*TargetStub); + Error TBEWriteError = writeTBE(EmitTBE, *TargetStub); + if (TBEWriteError) { + errs() << "Error: " << TBEWriteError << "\n"; + exit(1); + } + } + return 0; +}