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: FileCheck %s --input-file=%t2 + +!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 `{{.*}}.NotAFileInTestingDir` Index: llvm/test/tools/llvm-elfabi/read-unsupported-file.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-elfabi/read-unsupported-file.test @@ -0,0 +1,7 @@ +# RUN: not llvm-elfabi %s --emit-tbe=%t 2>&1 | FileCheck %s + +This is just some text that cannot be read by llvm-elfabi. + +#CHECK: The file was not recognized as a valid object file +#CHECK: YAML failed reading as TBE +#CHECK: No file readers succeeded reading `{{.*}}` (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 --soname=best.so +# RUN: FileCheck %s --input-file=%t2 + +!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-emits-current-version.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-elfabi/tbe-emits-current-version.test @@ -0,0 +1,14 @@ +# RUN: llvm-elfabi %s --emit-tbe=%t +# RUN: FileCheck %s --input-file=%t + +--- !tapi-tbe +TbeVersion: 1.0 +Arch: AArch64 +Symbols: {} +... + +# As the tbe reader/writer is updated, update this check to ensure --emit-tbe +# uses the latest tbe writer by default. + +#CHECK: --- !tapi-tbe +#CHECK-NEXT: TbeVersion: 1.0 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: FileCheck %s --input-file=%t + +--- !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,33 @@ +//===- 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 { + +/// Attempt to read a binary ELF file from a MemoryBuffer. +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,68 @@ +//===- 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 { + +/// Returns a new ELFStub with all members populated from an ELFObjectFile. +/// @param ElfObj Source ELFObjectFile. +template +Expected> +buildStub(const ELFObjectFile &ElfObj) { + std::unique_ptr DestStub = make_unique(); + const ELFFile *ElfFile = ElfObj.getELFFile(); + + DestStub->Arch = ElfFile->getHeader()->e_machine; + + // 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); +} + +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,75 @@ +//===- 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. + + /// 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, StringRef 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(); + +private: + /// Logs all errors to a raw_ostream. + void log(raw_ostream &OS); + + /// Returns true if all errors have been retrieved through makeError(), or + /// false if errors have been added since the last makeError() call. + bool allErrorsHandled() const; + + /// Dump output and crash. + LLVM_ATTRIBUTE_NORETURN void fatalUnhandledError(); + + 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 "llvm/Support/WithColor.h" +#include + +using namespace llvm; +using namespace llvm::elfabi; + +void ErrorCollector::escalateToFatal() { + ErrorsAreFatal = true; +} + +void ErrorCollector::addError(Error &&Err, StringRef Tag) { + if (Err) { + Errors.push_back(std::move(Err)); + Tags.push_back(Tag.str()); + } +} + +Error ErrorCollector::makeError() { + // TODO: Make this return something (an AggregateError?) that gives more + // individual control over each error and which might be of interest. + Error JoinedErrors = Error::success(); + for (Error &E : Errors) { + JoinedErrors = joinErrors(std::move(JoinedErrors), std::move(E)); + } + Errors.clear(); + Tags.clear(); + return JoinedErrors; +} + +void ErrorCollector::log(raw_ostream &OS) { + OS << "Encountered multiple errors:\n"; + for (size_t i = 0; i < Errors.size(); ++i) { + WithColor::error(OS) << "(" << Tags[i] << ") " << Errors[i]; + if (i != Errors.size() - 1) + OS << "\n"; + } +} + +bool ErrorCollector::allErrorsHandled() const { + return Errors.empty(); +} + +ErrorCollector::~ErrorCollector() { + if (ErrorsAreFatal && !allErrorsHandled()) + fatalUnhandledError(); + + for (Error &E : Errors) { + consumeError(std::move(E)); + } +} + +LLVM_ATTRIBUTE_NORETURN void ErrorCollector::fatalUnhandledError() { + errs() << "Program aborted due to unhandled Error(s):\n"; + log(errs()); + errs() << "\n"; + 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,120 @@ +//===- 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/Support/WithColor.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 SOName( + "soname", + cl::desc("Manually set the DT_SONAME entry of any emitted files"), + cl::value_desc("name")); + +/// 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) { + // 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); + ErrorCollector EC(/*UseFatalErrors=*/false); + + // First try to read as a binary (fails fast if not binary). + Expected> StubFromELF = + readELFFile(FileReadBuffer->getMemBufferRef()); + if (StubFromELF) { + return std::move(*StubFromELF); + } + EC.addError(StubFromELF.takeError(), "BinaryRead"); + + // Fall back to reading as a tbe. + Expected> StubFromTBE = + readTBEFromBuffer(FileReadBuffer->getBuffer()); + if (StubFromTBE) { + return std::move(*StubFromTBE); + } + EC.addError(StubFromTBE.takeError(), "YamlParse"); + + // If both readers fail, build a new error that includes all information. + EC.addError(createStringError(errc::not_supported, + "No file readers succeeded reading `%s` " + "(unsupported/malformed file?)", + FilePath.data()), + "ReadInputFile"); + 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(); + WithColor::error() << ReadError << "\n"; + exit(1); + } + + std::unique_ptr TargetStub = std::move(StubOrErr.get()); + + // Write out .tbe file. + if (EmitTBE.getNumOccurrences() == 1) { + TargetStub->TbeVersion = TBEVersionCurrent; + if (SOName.getNumOccurrences() == 1) { + TargetStub->SoName = SOName; + } + Error TBEWriteError = writeTBE(EmitTBE, *TargetStub); + if (TBEWriteError) { + WithColor::error() << TBEWriteError << "\n"; + exit(1); + } + } +}