view tools/llvm-ifs/llvm-ifs.cpp @ 171:66f3bfe93da9

git version 2c4ca6832fa6b306ee6a7010bfb80a3f2596f824
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Mon, 25 May 2020 11:07:02 +0900
parents
children
line wrap: on
line source

//===- llvm-ifs.cpp -------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===-----------------------------------------------------------------------===/

#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/Triple.h"
#include "llvm/ObjectYAML/yaml2obj.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/VersionTuple.h"
#include "llvm/Support/WithColor.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TextAPI/MachO/InterfaceFile.h"
#include "llvm/TextAPI/MachO/TextAPIReader.h"
#include "llvm/TextAPI/MachO/TextAPIWriter.h"
#include <set>
#include <string>

using namespace llvm;
using namespace llvm::yaml;
using namespace llvm::MachO;

#define DEBUG_TYPE "llvm-ifs"

namespace {
const VersionTuple IFSVersionCurrent(1, 2);
}

static cl::opt<std::string> Action("action", cl::desc("<llvm-ifs action>"),
                                   cl::value_desc("write-ifs | write-bin"),
                                   cl::init("write-ifs"));

static cl::opt<std::string> ForceFormat("force-format",
                                        cl::desc("<force object format>"),
                                        cl::value_desc("ELF | TBD"),
                                        cl::init(""));

static cl::list<std::string> InputFilenames(cl::Positional,
                                            cl::desc("<input ifs files>"),
                                            cl::ZeroOrMore);

static cl::opt<std::string> OutputFilename("o", cl::desc("<output file>"),
                                           cl::value_desc("path"));

enum class IFSSymbolType {
  NoType = 0,
  Object,
  Func,
  // Type information is 4 bits, so 16 is safely out of range.
  Unknown = 16,
};

std::string getTypeName(IFSSymbolType Type) {
  switch (Type) {
  case IFSSymbolType::NoType:
    return "NoType";
  case IFSSymbolType::Func:
    return "Func";
  case IFSSymbolType::Object:
    return "Object";
  case IFSSymbolType::Unknown:
    return "Unknown";
  }
  llvm_unreachable("Unexpected ifs symbol type.");
}

struct IFSSymbol {
  IFSSymbol(std::string SymbolName) : Name(SymbolName) {}
  std::string Name;
  uint64_t Size;
  IFSSymbolType Type;
  bool Weak;
  Optional<std::string> Warning;
  bool operator<(const IFSSymbol &RHS) const { return Name < RHS.Name; }
};

namespace llvm {
namespace yaml {
/// YAML traits for IFSSymbolType.
template <> struct ScalarEnumerationTraits<IFSSymbolType> {
  static void enumeration(IO &IO, IFSSymbolType &SymbolType) {
    IO.enumCase(SymbolType, "NoType", IFSSymbolType::NoType);
    IO.enumCase(SymbolType, "Func", IFSSymbolType::Func);
    IO.enumCase(SymbolType, "Object", IFSSymbolType::Object);
    IO.enumCase(SymbolType, "Unknown", IFSSymbolType::Unknown);
    // Treat other symbol types as noise, and map to Unknown.
    if (!IO.outputting() && IO.matchEnumFallback())
      SymbolType = IFSSymbolType::Unknown;
  }
};

template <> struct ScalarTraits<VersionTuple> {
  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 > IFSVersionCurrent)
      return StringRef("Unsupported IFS 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 IFSSymbol.
template <> struct MappingTraits<IFSSymbol> {
  static void mapping(IO &IO, IFSSymbol &Symbol) {
    IO.mapRequired("Type", Symbol.Type);
    // The need for symbol size depends on the symbol type.
    if (Symbol.Type == IFSSymbolType::NoType)
      IO.mapOptional("Size", Symbol.Size, (uint64_t)0);
    else if (Symbol.Type == IFSSymbolType::Func)
      Symbol.Size = 0;
    else
      IO.mapRequired("Size", Symbol.Size);
    IO.mapOptional("Weak", Symbol.Weak, false);
    IO.mapOptional("Warning", Symbol.Warning);
  }

  // Compacts symbol information into a single line.
  static const bool flow = true;
};

/// YAML traits for set of IFSSymbols.
template <> struct CustomMappingTraits<std::set<IFSSymbol>> {
  static void inputOne(IO &IO, StringRef Key, std::set<IFSSymbol> &Set) {
    std::string Name = Key.str();
    IFSSymbol Sym(Name);
    IO.mapRequired(Name.c_str(), Sym);
    Set.insert(Sym);
  }

  static void output(IO &IO, std::set<IFSSymbol> &Set) {
    for (auto &Sym : Set)
      IO.mapRequired(Sym.Name.c_str(), const_cast<IFSSymbol &>(Sym));
  }
};
} // namespace yaml
} // namespace llvm

// A cumulative representation of ELF stubs.
// Both textual and binary stubs will read into and write from this object.
class IFSStub {
  // TODO: Add support for symbol versioning.
public:
  VersionTuple IfsVersion;
  std::string Triple;
  std::string ObjectFileFormat;
  Optional<std::string> SOName;
  std::vector<std::string> NeededLibs;
  std::set<IFSSymbol> Symbols;

  IFSStub() = default;
  IFSStub(const IFSStub &Stub)
      : IfsVersion(Stub.IfsVersion), Triple(Stub.Triple),
        ObjectFileFormat(Stub.ObjectFileFormat), SOName(Stub.SOName),
        NeededLibs(Stub.NeededLibs), Symbols(Stub.Symbols) {}
  IFSStub(IFSStub &&Stub)
      : IfsVersion(std::move(Stub.IfsVersion)), Triple(std::move(Stub.Triple)),
        ObjectFileFormat(std::move(Stub.ObjectFileFormat)),
        SOName(std::move(Stub.SOName)), NeededLibs(std::move(Stub.NeededLibs)),
        Symbols(std::move(Stub.Symbols)) {}
};

namespace llvm {
namespace yaml {
/// YAML traits for IFSStub objects.
template <> struct MappingTraits<IFSStub> {
  static void mapping(IO &IO, IFSStub &Stub) {
    if (!IO.mapTag("!experimental-ifs-v1", true))
      IO.setError("Not a .ifs YAML file.");
    IO.mapRequired("IfsVersion", Stub.IfsVersion);
    IO.mapOptional("Triple", Stub.Triple);
    IO.mapOptional("ObjectFileFormat", Stub.ObjectFileFormat);
    IO.mapOptional("SOName", Stub.SOName);
    IO.mapOptional("NeededLibs", Stub.NeededLibs);
    IO.mapRequired("Symbols", Stub.Symbols);
  }
};
} // namespace yaml
} // namespace llvm

static Expected<std::unique_ptr<IFSStub>> readInputFile(StringRef FilePath) {
  // Read in file.
  ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrError =
      MemoryBuffer::getFileOrSTDIN(FilePath);
  if (!BufOrError)
    return createStringError(BufOrError.getError(), "Could not open `%s`",
                             FilePath.data());

  std::unique_ptr<MemoryBuffer> FileReadBuffer = std::move(*BufOrError);
  yaml::Input YamlIn(FileReadBuffer->getBuffer());
  std::unique_ptr<IFSStub> Stub(new IFSStub());
  YamlIn >> *Stub;

  if (std::error_code Err = YamlIn.error())
    return createStringError(Err, "Failed reading Interface Stub File.");

  return std::move(Stub);
}

int writeTbdStub(const llvm::Triple &T, const std::set<IFSSymbol> &Symbols,
                 const StringRef Format, raw_ostream &Out) {

  auto PlatformKindOrError =
      [](const llvm::Triple &T) -> llvm::Expected<llvm::MachO::PlatformKind> {
    if (T.isMacOSX())
      return llvm::MachO::PlatformKind::macOS;
    if (T.isTvOS())
      return llvm::MachO::PlatformKind::tvOS;
    if (T.isWatchOS())
      return llvm::MachO::PlatformKind::watchOS;
    // Note: put isiOS last because tvOS and watchOS are also iOS according
    // to the Triple.
    if (T.isiOS())
      return llvm::MachO::PlatformKind::iOS;

    // TODO: Add an option for ForceTriple, but keep ForceFormat for now.
    if (ForceFormat == "TBD")
      return llvm::MachO::PlatformKind::macOS;

    return createStringError(errc::not_supported, "Invalid Platform.\n");
  }(T);

  if (!PlatformKindOrError)
    return -1;

  PlatformKind Plat = PlatformKindOrError.get();
  TargetList Targets({Target(llvm::MachO::mapToArchitecture(T), Plat)});

  InterfaceFile File;
  File.setFileType(FileType::TBD_V3); // Only supporting v3 for now.
  File.addTargets(Targets);

  for (const auto &Symbol : Symbols) {
    auto Name = Symbol.Name;
    auto Kind = SymbolKind::GlobalSymbol;
    switch (Symbol.Type) {
    default:
    case IFSSymbolType::NoType:
      Kind = SymbolKind::GlobalSymbol;
      break;
    case IFSSymbolType::Object:
      Kind = SymbolKind::GlobalSymbol;
      break;
    case IFSSymbolType::Func:
      Kind = SymbolKind::GlobalSymbol;
      break;
    }
    if (Symbol.Weak)
      File.addSymbol(Kind, Name, Targets, SymbolFlags::WeakDefined);
    else
      File.addSymbol(Kind, Name, Targets);
  }

  SmallString<4096> Buffer;
  raw_svector_ostream OS(Buffer);
  if (Error Result = TextAPIWriter::writeToStream(OS, File))
    return -1;
  Out << OS.str();
  return 0;
}

int writeElfStub(const llvm::Triple &T, const std::set<IFSSymbol> &Symbols,
                 const StringRef Format, raw_ostream &Out) {
  SmallString<0> Storage;
  Storage.clear();
  raw_svector_ostream OS(Storage);

  OS << "--- !ELF\n";
  OS << "FileHeader:\n";
  OS << "  Class:           ELFCLASS";
  OS << (T.isArch64Bit() ? "64" : "32");
  OS << "\n";
  OS << "  Data:            ELFDATA2";
  OS << (T.isLittleEndian() ? "LSB" : "MSB");
  OS << "\n";
  OS << "  Type:            ET_DYN\n";
  OS << "  Machine:         "
     << llvm::StringSwitch<llvm::StringRef>(T.getArchName())
            .Case("x86_64", "EM_X86_64")
            .Case("i386", "EM_386")
            .Case("i686", "EM_386")
            .Case("aarch64", "EM_AARCH64")
            .Case("amdgcn", "EM_AMDGPU")
            .Case("r600", "EM_AMDGPU")
            .Case("arm", "EM_ARM")
            .Case("thumb", "EM_ARM")
            .Case("avr", "EM_AVR")
            .Case("mips", "EM_MIPS")
            .Case("mipsel", "EM_MIPS")
            .Case("mips64", "EM_MIPS")
            .Case("mips64el", "EM_MIPS")
            .Case("msp430", "EM_MSP430")
            .Case("ppc", "EM_PPC")
            .Case("ppc64", "EM_PPC64")
            .Case("ppc64le", "EM_PPC64")
            .Case("x86", T.isOSIAMCU() ? "EM_IAMCU" : "EM_386")
            .Case("x86_64", "EM_X86_64")
            .Default("EM_NONE")
     << "\nSections:"
     << "\n  - Name:            .text"
     << "\n    Type:            SHT_PROGBITS"
     << "\n  - Name:            .data"
     << "\n    Type:            SHT_PROGBITS"
     << "\n  - Name:            .rodata"
     << "\n    Type:            SHT_PROGBITS"
     << "\nSymbols:\n";
  for (const auto &Symbol : Symbols) {
    OS << "  - Name:            " << Symbol.Name << "\n"
       << "    Type:            STT_";
    switch (Symbol.Type) {
    default:
    case IFSSymbolType::NoType:
      OS << "NOTYPE";
      break;
    case IFSSymbolType::Object:
      OS << "OBJECT";
      break;
    case IFSSymbolType::Func:
      OS << "FUNC";
      break;
    }
    OS << "\n    Section:         .text"
       << "\n    Binding:         STB_" << (Symbol.Weak ? "WEAK" : "GLOBAL")
       << "\n";
  }
  OS << "...\n";

  std::string YamlStr = OS.str();

  // Only or debugging. Not an offical format.
  LLVM_DEBUG({
    if (ForceFormat == "ELFOBJYAML") {
      Out << YamlStr;
      return 0;
    }
  });

  yaml::Input YIn(YamlStr);
  auto ErrHandler = [](const Twine &Msg) {
    WithColor::error(errs(), "llvm-ifs") << Msg << "\n";
  };
  return convertYAML(YIn, Out, ErrHandler) ? 0 : 1;
}

int writeIfso(const IFSStub &Stub, bool IsWriteIfs, raw_ostream &Out) {
  if (IsWriteIfs) {
    yaml::Output YamlOut(Out, NULL, /*WrapColumn =*/0);
    YamlOut << const_cast<IFSStub &>(Stub);
    return 0;
  }

  std::string ObjectFileFormat =
      ForceFormat.empty() ? Stub.ObjectFileFormat : ForceFormat;

  if (ObjectFileFormat == "ELF" || ForceFormat == "ELFOBJYAML")
    return writeElfStub(llvm::Triple(Stub.Triple), Stub.Symbols,
                        Stub.ObjectFileFormat, Out);
  if (ObjectFileFormat == "TBD")
    return writeTbdStub(llvm::Triple(Stub.Triple), Stub.Symbols,
                        Stub.ObjectFileFormat, Out);

  WithColor::error()
      << "Invalid ObjectFileFormat: Only ELF and TBD are supported.\n";
  return -1;
}

// New Interface Stubs Yaml Format:
// --- !experimental-ifs-v1
// IfsVersion:      1.0
// Triple:          <llvm triple>
// ObjectFileFormat: <ELF | others not yet supported>
// Symbols:
//   _ZSymbolName: { Type: <type> }
// ...

int main(int argc, char *argv[]) {
  // Parse arguments.
  cl::ParseCommandLineOptions(argc, argv);

  if (InputFilenames.empty())
    InputFilenames.push_back("-");

  IFSStub Stub;
  std::map<std::string, IFSSymbol> SymbolMap;

  std::string PreviousInputFilePath = "";
  for (const std::string &InputFilePath : InputFilenames) {
    Expected<std::unique_ptr<IFSStub>> StubOrErr = readInputFile(InputFilePath);
    if (!StubOrErr) {
      WithColor::error() << StubOrErr.takeError() << "\n";
      return -1;
    }
    std::unique_ptr<IFSStub> TargetStub = std::move(StubOrErr.get());

    if (Stub.Triple.empty()) {
      PreviousInputFilePath = InputFilePath;
      Stub.IfsVersion = TargetStub->IfsVersion;
      Stub.Triple = TargetStub->Triple;
      Stub.ObjectFileFormat = TargetStub->ObjectFileFormat;
      Stub.SOName = TargetStub->SOName;
      Stub.NeededLibs = TargetStub->NeededLibs;
    } else {
      if (Stub.IfsVersion != TargetStub->IfsVersion) {
        if (Stub.IfsVersion.getMajor() != IFSVersionCurrent.getMajor()) {
          WithColor::error()
              << "Interface Stub: IfsVersion Mismatch."
              << "\nFilenames: " << PreviousInputFilePath << " "
              << InputFilePath << "\nIfsVersion Values: " << Stub.IfsVersion
              << " " << TargetStub->IfsVersion << "\n";
          return -1;
        }
        if (TargetStub->IfsVersion > Stub.IfsVersion)
          Stub.IfsVersion = TargetStub->IfsVersion;
      }
      if (Stub.ObjectFileFormat != TargetStub->ObjectFileFormat) {
        WithColor::error() << "Interface Stub: ObjectFileFormat Mismatch."
                           << "\nFilenames: " << PreviousInputFilePath << " "
                           << InputFilePath << "\nObjectFileFormat Values: "
                           << Stub.ObjectFileFormat << " "
                           << TargetStub->ObjectFileFormat << "\n";
        return -1;
      }
      if (Stub.Triple != TargetStub->Triple) {
        WithColor::error() << "Interface Stub: Triple Mismatch."
                           << "\nFilenames: " << PreviousInputFilePath << " "
                           << InputFilePath
                           << "\nTriple Values: " << Stub.Triple << " "
                           << TargetStub->Triple << "\n";
        return -1;
      }
      if (Stub.SOName != TargetStub->SOName) {
        WithColor::error() << "Interface Stub: SOName Mismatch."
                           << "\nFilenames: " << PreviousInputFilePath << " "
                           << InputFilePath
                           << "\nSOName Values: " << Stub.SOName << " "
                           << TargetStub->SOName << "\n";
        return -1;
      }
      if (Stub.NeededLibs != TargetStub->NeededLibs) {
        WithColor::error() << "Interface Stub: NeededLibs Mismatch."
                           << "\nFilenames: " << PreviousInputFilePath << " "
                           << InputFilePath << "\n";
        return -1;
      }
    }

    for (auto Symbol : TargetStub->Symbols) {
      auto SI = SymbolMap.find(Symbol.Name);
      if (SI == SymbolMap.end()) {
        SymbolMap.insert(
            std::pair<std::string, IFSSymbol>(Symbol.Name, Symbol));
        continue;
      }

      assert(Symbol.Name == SI->second.Name && "Symbol Names Must Match.");

      // Check conflicts:
      if (Symbol.Type != SI->second.Type) {
        WithColor::error() << "Interface Stub: Type Mismatch for "
                           << Symbol.Name << ".\nFilename: " << InputFilePath
                           << "\nType Values: " << getTypeName(SI->second.Type)
                           << " " << getTypeName(Symbol.Type) << "\n";

        return -1;
      }
      if (Symbol.Size != SI->second.Size) {
        WithColor::error() << "Interface Stub: Size Mismatch for "
                           << Symbol.Name << ".\nFilename: " << InputFilePath
                           << "\nSize Values: " << SI->second.Size << " "
                           << Symbol.Size << "\n";

        return -1;
      }
      if (Symbol.Weak != SI->second.Weak) {
        // TODO: Add conflict resolution for Weak vs non-Weak.
        WithColor::error() << "Interface Stub: Weak Mismatch for "
                           << Symbol.Name << ".\nFilename: " << InputFilePath
                           << "\nWeak Values: " << SI->second.Weak << " "
                           << Symbol.Weak << "\n";

        return -1;
      }
      // TODO: Not checking Warning. Will be dropped.
    }

    PreviousInputFilePath = InputFilePath;
  }

  if (Stub.IfsVersion != IFSVersionCurrent)
    if (Stub.IfsVersion.getMajor() != IFSVersionCurrent.getMajor()) {
      WithColor::error() << "Interface Stub: Bad IfsVersion: "
                         << Stub.IfsVersion << ", llvm-ifs supported version: "
                         << IFSVersionCurrent << ".\n";
      return -1;
    }

  for (auto &Entry : SymbolMap)
    Stub.Symbols.insert(Entry.second);

  std::error_code SysErr;

  // Open file for writing.
  raw_fd_ostream Out(OutputFilename, SysErr);
  if (SysErr) {
    WithColor::error() << "Couldn't open " << OutputFilename
                       << " for writing.\n";
    return -1;
  }

  return writeIfso(Stub, (Action == "write-ifs"), Out);
}