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,17 @@ +# 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: SoName: '' +#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,8 @@ +# 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: All file readers failed to read `{{.*}}`. (unsupported/malformed file?) +#CHECK-NEXT: Binary read error: The file was not recognized as a valid object file +#CHECK-NEXT: YAML parse error: YAML failed reading as TBE 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,10 @@ +set(LLVM_LINK_COMPONENTS + Object + Support + TextAPI + ) + +add_llvm_tool(llvm-elfabi + ELFObjHandler.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,72 @@ +//===- 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; + +template +Expected> +llvm::elfabi::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); + + return std::move(DestStub); +} + +template +Error llvm::elfabi::populateArch(ELFStub &TargetStub, const typename ELFT::Ehdr &Header) { + TargetStub.Arch = Header.e_machine; + return Error::success(); +} + +Expected> +llvm::elfabi::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"); +} 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,153 @@ +//===- 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 "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")); + +// Simple functions to output a errors and terminate: +LLVM_ATTRIBUTE_NORETURN void terminateWithError(std::error_code Err) { + errs() << "Error: " << Err.message() << "\n"; + exit(1); +} + +LLVM_ATTRIBUTE_NORETURN void terminateWithError(Error Err) { + errs() << "Error: " << Err << "\n"; + exit(1); +} + +/// 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; + std::string OldSoName = Stub.SoName; + VersionTuple OldVersion = Stub.TbeVersion; + + // Open file for writing. + raw_fd_ostream Out(FilePath, SysErr); + if (SysErr) + return createStringError(SysErr, "Couldn't open `%s` for writing.", + FilePath.data()); + + // Update SoName for output files. + updateSoName(Stub); + Stub.TbeVersion = TBEVersionCurrent; + + // Write file. + Error YAMLErr = writeTBEToOutputStream(Out, Stub); + if (YAMLErr) + return YAMLErr; + + // Revert SoName so logic is handled properly on any other emitted files. + Stub.SoName = OldSoName; + Stub.TbeVersion = OldVersion; + return Error::success(); +} + +/// readInputFunction to populate 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`.\n", + 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()); + } + + // Fall back to reading as a tbe. + Expected> StubFromTBE = + readTBEFromBuffer(FileReadBuffer->getBuffer()); + Error TBEReadError = StubFromTBE.takeError(); + if (!TBEReadError) { + // Ignore ELF error since this reader succeeded. + // TODO: this approach is not scalable. Create a system for handling errors + // from failed attempts at reading in various formats if more readers are + // added. + consumeError(std::move(ELFReadError)); + return std::move(StubFromTBE.get()); + } + + // If both readers fail, build a new error that includes all information. + std::string ReaderErrors; + raw_string_ostream ErrorCollector(ReaderErrors); + ErrorCollector << "Binary read error: " << ELFReadError << "\n"; + ErrorCollector << "YAML parse error: " << TBEReadError << "\n"; + consumeError(std::move(TBEReadError)); + consumeError(std::move(ELFReadError)); + return createStringError( + errc::not_supported, + "All file readers failed to read `%s`. (unsupported/malformed file?)\n%s", + FilePath.data(), + ErrorCollector.str().c_str()); +} + +int main(int argc, char *argv[]) { + // Parse arguments. + cl::ParseCommandLineOptions(argc, argv); + + Expected> StubOrErr = readInputFile(InputFilePath); + if (!StubOrErr) { + terminateWithError(StubOrErr.takeError()); + } + + std::unique_ptr TargetStub = std::move(StubOrErr.get()); + + // Write out File. + if (EmitTBE.getNumOccurrences() == 1) { + updateTBEVersion(*TargetStub); + Error TBEWriteError = writeTBE(EmitTBE, *TargetStub); + if (TBEWriteError) + terminateWithError(std::move(TBEWriteError)); + } + return 0; +}