• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===- llvm-ifs.cpp -------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===-----------------------------------------------------------------------===/
8 
9 #include "llvm/ADT/StringRef.h"
10 #include "llvm/ADT/StringSwitch.h"
11 #include "llvm/ADT/Triple.h"
12 #include "llvm/ObjectYAML/yaml2obj.h"
13 #include "llvm/Support/CommandLine.h"
14 #include "llvm/Support/Debug.h"
15 #include "llvm/Support/Errc.h"
16 #include "llvm/Support/Error.h"
17 #include "llvm/Support/FileOutputBuffer.h"
18 #include "llvm/Support/MemoryBuffer.h"
19 #include "llvm/Support/Path.h"
20 #include "llvm/Support/VersionTuple.h"
21 #include "llvm/Support/WithColor.h"
22 #include "llvm/Support/YAMLTraits.h"
23 #include "llvm/Support/raw_ostream.h"
24 #include "llvm/TextAPI/MachO/InterfaceFile.h"
25 #include "llvm/TextAPI/MachO/TextAPIReader.h"
26 #include "llvm/TextAPI/MachO/TextAPIWriter.h"
27 #include <set>
28 #include <string>
29 #include <vector>
30 
31 using namespace llvm;
32 using namespace llvm::yaml;
33 using namespace llvm::MachO;
34 
35 #define DEBUG_TYPE "llvm-ifs"
36 
37 namespace {
38 const VersionTuple IFSVersionCurrent(2, 0);
39 } // end anonymous namespace
40 
41 static cl::opt<std::string> Action("action", cl::desc("<llvm-ifs action>"),
42                                    cl::value_desc("write-ifs | write-bin"),
43                                    cl::init("write-ifs"));
44 
45 static cl::opt<std::string> ForceFormat("force-format",
46                                         cl::desc("<force object format>"),
47                                         cl::value_desc("ELF | TBD"),
48                                         cl::init(""));
49 
50 static cl::list<std::string> InputFilenames(cl::Positional,
51                                             cl::desc("<input ifs files>"),
52                                             cl::ZeroOrMore);
53 
54 static cl::opt<std::string> OutputFilename("o", cl::desc("<output file>"),
55                                            cl::value_desc("path"));
56 
57 enum class IFSSymbolType {
58   NoType = 0,
59   Object,
60   Func,
61   // Type information is 4 bits, so 16 is safely out of range.
62   Unknown = 16,
63 };
64 
getTypeName(IFSSymbolType Type)65 static std::string getTypeName(IFSSymbolType Type) {
66   switch (Type) {
67   case IFSSymbolType::NoType:
68     return "NoType";
69   case IFSSymbolType::Func:
70     return "Func";
71   case IFSSymbolType::Object:
72     return "Object";
73   case IFSSymbolType::Unknown:
74     return "Unknown";
75   }
76   llvm_unreachable("Unexpected ifs symbol type.");
77 }
78 
79 struct IFSSymbol {
80   IFSSymbol() = default;
IFSSymbolIFSSymbol81   IFSSymbol(std::string SymbolName) : Name(SymbolName) {}
82   std::string Name;
83   uint64_t Size;
84   IFSSymbolType Type;
85   bool Weak;
86   Optional<std::string> Warning;
operator <IFSSymbol87   bool operator<(const IFSSymbol &RHS) const { return Name < RHS.Name; }
88 };
89 
90 LLVM_YAML_IS_SEQUENCE_VECTOR(IFSSymbol)
91 
92 namespace llvm {
93 namespace yaml {
94 /// YAML traits for IFSSymbolType.
95 template <> struct ScalarEnumerationTraits<IFSSymbolType> {
enumerationllvm::yaml::ScalarEnumerationTraits96   static void enumeration(IO &IO, IFSSymbolType &SymbolType) {
97     IO.enumCase(SymbolType, "NoType", IFSSymbolType::NoType);
98     IO.enumCase(SymbolType, "Func", IFSSymbolType::Func);
99     IO.enumCase(SymbolType, "Object", IFSSymbolType::Object);
100     IO.enumCase(SymbolType, "Unknown", IFSSymbolType::Unknown);
101     // Treat other symbol types as noise, and map to Unknown.
102     if (!IO.outputting() && IO.matchEnumFallback())
103       SymbolType = IFSSymbolType::Unknown;
104   }
105 };
106 
107 /// YAML traits for IFSSymbol.
108 template <> struct MappingTraits<IFSSymbol> {
mappingllvm::yaml::MappingTraits109   static void mapping(IO &IO, IFSSymbol &Symbol) {
110     IO.mapRequired("Name", Symbol.Name);
111     IO.mapRequired("Type", Symbol.Type);
112     // The need for symbol size depends on the symbol type.
113     if (Symbol.Type == IFSSymbolType::NoType)
114       IO.mapOptional("Size", Symbol.Size, (uint64_t)0);
115     else if (Symbol.Type == IFSSymbolType::Func)
116       Symbol.Size = 0;
117     else
118       IO.mapRequired("Size", Symbol.Size);
119     IO.mapOptional("Weak", Symbol.Weak, false);
120     IO.mapOptional("Warning", Symbol.Warning);
121   }
122 
123   // Compacts symbol information into a single line.
124   static const bool flow = true;
125 };
126 
127 } // namespace yaml
128 } // namespace llvm
129 
130 // A cumulative representation of ELF stubs.
131 // Both textual and binary stubs will read into and write from this object.
132 class IFSStub {
133   // TODO: Add support for symbol versioning.
134 public:
135   VersionTuple IfsVersion;
136   std::string Triple;
137   std::string ObjectFileFormat;
138   Optional<std::string> SOName;
139   std::vector<std::string> NeededLibs;
140   std::vector<IFSSymbol> Symbols;
141 
142   IFSStub() = default;
IFSStub(const IFSStub & Stub)143   IFSStub(const IFSStub &Stub)
144       : IfsVersion(Stub.IfsVersion), Triple(Stub.Triple),
145         ObjectFileFormat(Stub.ObjectFileFormat), SOName(Stub.SOName),
146         NeededLibs(Stub.NeededLibs), Symbols(Stub.Symbols) {}
IFSStub(IFSStub && Stub)147   IFSStub(IFSStub &&Stub)
148       : IfsVersion(std::move(Stub.IfsVersion)), Triple(std::move(Stub.Triple)),
149         ObjectFileFormat(std::move(Stub.ObjectFileFormat)),
150         SOName(std::move(Stub.SOName)), NeededLibs(std::move(Stub.NeededLibs)),
151         Symbols(std::move(Stub.Symbols)) {}
152 };
153 
154 namespace llvm {
155 namespace yaml {
156 /// YAML traits for IFSStub objects.
157 template <> struct MappingTraits<IFSStub> {
mappingllvm::yaml::MappingTraits158   static void mapping(IO &IO, IFSStub &Stub) {
159     if (!IO.mapTag("!experimental-ifs-v2", true))
160       IO.setError("Not a .ifs YAML file.");
161 
162     auto OldContext = IO.getContext();
163     IO.setContext(&Stub);
164     IO.mapRequired("IfsVersion", Stub.IfsVersion);
165     IO.mapOptional("Triple", Stub.Triple);
166     IO.mapOptional("ObjectFileFormat", Stub.ObjectFileFormat);
167     IO.mapOptional("SOName", Stub.SOName);
168     IO.mapOptional("NeededLibs", Stub.NeededLibs);
169     IO.mapRequired("Symbols", Stub.Symbols);
170     IO.setContext(&OldContext);
171   }
172 };
173 } // namespace yaml
174 } // namespace llvm
175 
readInputFile(StringRef FilePath)176 static Expected<std::unique_ptr<IFSStub>> readInputFile(StringRef FilePath) {
177   // Read in file.
178   ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrError =
179       MemoryBuffer::getFileOrSTDIN(FilePath);
180   if (!BufOrError)
181     return createStringError(BufOrError.getError(), "Could not open `%s`",
182                              FilePath.data());
183 
184   std::unique_ptr<MemoryBuffer> FileReadBuffer = std::move(*BufOrError);
185   yaml::Input YamlIn(FileReadBuffer->getBuffer());
186   std::unique_ptr<IFSStub> Stub(new IFSStub());
187   YamlIn >> *Stub;
188 
189   if (std::error_code Err = YamlIn.error())
190     return createStringError(Err, "Failed reading Interface Stub File.");
191 
192   if (Stub->IfsVersion > IFSVersionCurrent)
193     return make_error<StringError>(
194         "IFS version " + Stub->IfsVersion.getAsString() + " is unsupported.",
195         std::make_error_code(std::errc::invalid_argument));
196 
197   return std::move(Stub);
198 }
199 
writeTbdStub(const Triple & T,const std::vector<IFSSymbol> & Symbols,const StringRef Format,raw_ostream & Out)200 static int writeTbdStub(const Triple &T, const std::vector<IFSSymbol> &Symbols,
201                         const StringRef Format, raw_ostream &Out) {
202 
203   auto PlatformKindOrError =
204       [](const llvm::Triple &T) -> llvm::Expected<llvm::MachO::PlatformKind> {
205     if (T.isMacOSX())
206       return llvm::MachO::PlatformKind::macOS;
207     if (T.isTvOS())
208       return llvm::MachO::PlatformKind::tvOS;
209     if (T.isWatchOS())
210       return llvm::MachO::PlatformKind::watchOS;
211     // Note: put isiOS last because tvOS and watchOS are also iOS according
212     // to the Triple.
213     if (T.isiOS())
214       return llvm::MachO::PlatformKind::iOS;
215 
216     // TODO: Add an option for ForceTriple, but keep ForceFormat for now.
217     if (ForceFormat == "TBD")
218       return llvm::MachO::PlatformKind::macOS;
219 
220     return createStringError(errc::not_supported, "Invalid Platform.\n");
221   }(T);
222 
223   if (!PlatformKindOrError)
224     return -1;
225 
226   PlatformKind Plat = PlatformKindOrError.get();
227   TargetList Targets({Target(llvm::MachO::mapToArchitecture(T), Plat)});
228 
229   InterfaceFile File;
230   File.setFileType(FileType::TBD_V3); // Only supporting v3 for now.
231   File.addTargets(Targets);
232 
233   for (const auto &Symbol : Symbols) {
234     auto Name = Symbol.Name;
235     auto Kind = SymbolKind::GlobalSymbol;
236     switch (Symbol.Type) {
237     default:
238     case IFSSymbolType::NoType:
239       Kind = SymbolKind::GlobalSymbol;
240       break;
241     case IFSSymbolType::Object:
242       Kind = SymbolKind::GlobalSymbol;
243       break;
244     case IFSSymbolType::Func:
245       Kind = SymbolKind::GlobalSymbol;
246       break;
247     }
248     if (Symbol.Weak)
249       File.addSymbol(Kind, Name, Targets, SymbolFlags::WeakDefined);
250     else
251       File.addSymbol(Kind, Name, Targets);
252   }
253 
254   SmallString<4096> Buffer;
255   raw_svector_ostream OS(Buffer);
256   if (Error Result = TextAPIWriter::writeToStream(OS, File))
257     return -1;
258   Out << OS.str();
259   return 0;
260 }
261 
writeElfStub(const Triple & T,const std::vector<IFSSymbol> & Symbols,const StringRef Format,raw_ostream & Out)262 static int writeElfStub(const Triple &T, const std::vector<IFSSymbol> &Symbols,
263                         const StringRef Format, raw_ostream &Out) {
264   SmallString<0> Storage;
265   Storage.clear();
266   raw_svector_ostream OS(Storage);
267 
268   OS << "--- !ELF\n";
269   OS << "FileHeader:\n";
270   OS << "  Class:           ELFCLASS";
271   OS << (T.isArch64Bit() ? "64" : "32");
272   OS << "\n";
273   OS << "  Data:            ELFDATA2";
274   OS << (T.isLittleEndian() ? "LSB" : "MSB");
275   OS << "\n";
276   OS << "  Type:            ET_DYN\n";
277   OS << "  Machine:         "
278      << llvm::StringSwitch<llvm::StringRef>(T.getArchName())
279             .Case("x86_64", "EM_X86_64")
280             .Case("i386", "EM_386")
281             .Case("i686", "EM_386")
282             .Case("aarch64", "EM_AARCH64")
283             .Case("amdgcn", "EM_AMDGPU")
284             .Case("r600", "EM_AMDGPU")
285             .Case("arm", "EM_ARM")
286             .Case("thumb", "EM_ARM")
287             .Case("avr", "EM_AVR")
288             .Case("mips", "EM_MIPS")
289             .Case("mipsel", "EM_MIPS")
290             .Case("mips64", "EM_MIPS")
291             .Case("mips64el", "EM_MIPS")
292             .Case("msp430", "EM_MSP430")
293             .Case("ppc", "EM_PPC")
294             .Case("ppc64", "EM_PPC64")
295             .Case("ppc64le", "EM_PPC64")
296             .Case("x86", T.isOSIAMCU() ? "EM_IAMCU" : "EM_386")
297             .Case("x86_64", "EM_X86_64")
298             .Default("EM_NONE")
299      << "\nSections:"
300      << "\n  - Name:            .text"
301      << "\n    Type:            SHT_PROGBITS"
302      << "\n  - Name:            .data"
303      << "\n    Type:            SHT_PROGBITS"
304      << "\n  - Name:            .rodata"
305      << "\n    Type:            SHT_PROGBITS"
306      << "\nSymbols:\n";
307   for (const auto &Symbol : Symbols) {
308     OS << "  - Name:            " << Symbol.Name << "\n"
309        << "    Type:            STT_";
310     switch (Symbol.Type) {
311     default:
312     case IFSSymbolType::NoType:
313       OS << "NOTYPE";
314       break;
315     case IFSSymbolType::Object:
316       OS << "OBJECT";
317       break;
318     case IFSSymbolType::Func:
319       OS << "FUNC";
320       break;
321     }
322     OS << "\n    Section:         .text"
323        << "\n    Binding:         STB_" << (Symbol.Weak ? "WEAK" : "GLOBAL")
324        << "\n";
325   }
326   OS << "...\n";
327 
328   std::string YamlStr = std::string(OS.str());
329 
330   // Only or debugging. Not an offical format.
331   LLVM_DEBUG({
332     if (ForceFormat == "ELFOBJYAML") {
333       Out << YamlStr;
334       return 0;
335     }
336   });
337 
338   yaml::Input YIn(YamlStr);
339   auto ErrHandler = [](const Twine &Msg) {
340     WithColor::error(errs(), "llvm-ifs") << Msg << "\n";
341   };
342   return convertYAML(YIn, Out, ErrHandler) ? 0 : 1;
343 }
344 
writeIfso(const IFSStub & Stub,bool IsWriteIfs,raw_ostream & Out)345 static int writeIfso(const IFSStub &Stub, bool IsWriteIfs, raw_ostream &Out) {
346   if (IsWriteIfs) {
347     yaml::Output YamlOut(Out, NULL, /*WrapColumn =*/0);
348     YamlOut << const_cast<IFSStub &>(Stub);
349     return 0;
350   }
351 
352   std::string ObjectFileFormat =
353       ForceFormat.empty() ? Stub.ObjectFileFormat : ForceFormat;
354 
355   if (ObjectFileFormat == "ELF" || ForceFormat == "ELFOBJYAML")
356     return writeElfStub(llvm::Triple(Stub.Triple), Stub.Symbols,
357                         Stub.ObjectFileFormat, Out);
358   if (ObjectFileFormat == "TBD")
359     return writeTbdStub(llvm::Triple(Stub.Triple), Stub.Symbols,
360                         Stub.ObjectFileFormat, Out);
361 
362   WithColor::error()
363       << "Invalid ObjectFileFormat: Only ELF and TBD are supported.\n";
364   return -1;
365 }
366 
367 // TODO: Drop ObjectFileFormat, it can be subsumed from the triple.
368 // New Interface Stubs Yaml Format:
369 // --- !experimental-ifs-v2
370 // IfsVersion: 2.0
371 // Triple:          <llvm triple>
372 // ObjectFileFormat: <ELF | others not yet supported>
373 // Symbols:
374 //   _ZSymbolName: { Type: <type> }
375 // ...
376 
main(int argc,char * argv[])377 int main(int argc, char *argv[]) {
378   // Parse arguments.
379   cl::ParseCommandLineOptions(argc, argv);
380 
381   if (InputFilenames.empty())
382     InputFilenames.push_back("-");
383 
384   IFSStub Stub;
385   std::map<std::string, IFSSymbol> SymbolMap;
386 
387   std::string PreviousInputFilePath = "";
388   for (const std::string &InputFilePath : InputFilenames) {
389     Expected<std::unique_ptr<IFSStub>> StubOrErr = readInputFile(InputFilePath);
390     if (!StubOrErr) {
391       WithColor::error() << StubOrErr.takeError() << "\n";
392       return -1;
393     }
394     std::unique_ptr<IFSStub> TargetStub = std::move(StubOrErr.get());
395 
396     if (Stub.Triple.empty()) {
397       PreviousInputFilePath = InputFilePath;
398       Stub.IfsVersion = TargetStub->IfsVersion;
399       Stub.Triple = TargetStub->Triple;
400       Stub.ObjectFileFormat = TargetStub->ObjectFileFormat;
401       Stub.SOName = TargetStub->SOName;
402       Stub.NeededLibs = TargetStub->NeededLibs;
403     } else {
404       Stub.ObjectFileFormat = !Stub.ObjectFileFormat.empty()
405                                   ? Stub.ObjectFileFormat
406                                   : TargetStub->ObjectFileFormat;
407 
408       if (Stub.IfsVersion != TargetStub->IfsVersion) {
409         if (Stub.IfsVersion.getMajor() != IFSVersionCurrent.getMajor()) {
410           WithColor::error()
411               << "Interface Stub: IfsVersion Mismatch."
412               << "\nFilenames: " << PreviousInputFilePath << " "
413               << InputFilePath << "\nIfsVersion Values: " << Stub.IfsVersion
414               << " " << TargetStub->IfsVersion << "\n";
415           return -1;
416         }
417         if (TargetStub->IfsVersion > Stub.IfsVersion)
418           Stub.IfsVersion = TargetStub->IfsVersion;
419       }
420       if (Stub.ObjectFileFormat != TargetStub->ObjectFileFormat &&
421           !TargetStub->ObjectFileFormat.empty()) {
422         WithColor::error() << "Interface Stub: ObjectFileFormat Mismatch."
423                            << "\nFilenames: " << PreviousInputFilePath << " "
424                            << InputFilePath << "\nObjectFileFormat Values: "
425                            << Stub.ObjectFileFormat << " "
426                            << TargetStub->ObjectFileFormat << "\n";
427         return -1;
428       }
429       if (Stub.Triple != TargetStub->Triple && !TargetStub->Triple.empty()) {
430         WithColor::error() << "Interface Stub: Triple Mismatch."
431                            << "\nFilenames: " << PreviousInputFilePath << " "
432                            << InputFilePath
433                            << "\nTriple Values: " << Stub.Triple << " "
434                            << TargetStub->Triple << "\n";
435         return -1;
436       }
437       if (Stub.SOName != TargetStub->SOName) {
438         WithColor::error() << "Interface Stub: SOName Mismatch."
439                            << "\nFilenames: " << PreviousInputFilePath << " "
440                            << InputFilePath
441                            << "\nSOName Values: " << Stub.SOName << " "
442                            << TargetStub->SOName << "\n";
443         return -1;
444       }
445       if (Stub.NeededLibs != TargetStub->NeededLibs) {
446         WithColor::error() << "Interface Stub: NeededLibs Mismatch."
447                            << "\nFilenames: " << PreviousInputFilePath << " "
448                            << InputFilePath << "\n";
449         return -1;
450       }
451     }
452 
453     for (auto Symbol : TargetStub->Symbols) {
454       auto SI = SymbolMap.find(Symbol.Name);
455       if (SI == SymbolMap.end()) {
456         SymbolMap.insert(
457             std::pair<std::string, IFSSymbol>(Symbol.Name, Symbol));
458         continue;
459       }
460 
461       assert(Symbol.Name == SI->second.Name && "Symbol Names Must Match.");
462 
463       // Check conflicts:
464       if (Symbol.Type != SI->second.Type) {
465         WithColor::error() << "Interface Stub: Type Mismatch for "
466                            << Symbol.Name << ".\nFilename: " << InputFilePath
467                            << "\nType Values: " << getTypeName(SI->second.Type)
468                            << " " << getTypeName(Symbol.Type) << "\n";
469 
470         return -1;
471       }
472       if (Symbol.Size != SI->second.Size) {
473         WithColor::error() << "Interface Stub: Size Mismatch for "
474                            << Symbol.Name << ".\nFilename: " << InputFilePath
475                            << "\nSize Values: " << SI->second.Size << " "
476                            << Symbol.Size << "\n";
477 
478         return -1;
479       }
480       if (Symbol.Weak != SI->second.Weak) {
481         Symbol.Weak = false;
482         continue;
483       }
484       // TODO: Not checking Warning. Will be dropped.
485     }
486 
487     PreviousInputFilePath = InputFilePath;
488   }
489 
490   if (Stub.IfsVersion != IFSVersionCurrent)
491     if (Stub.IfsVersion.getMajor() != IFSVersionCurrent.getMajor()) {
492       WithColor::error() << "Interface Stub: Bad IfsVersion: "
493                          << Stub.IfsVersion << ", llvm-ifs supported version: "
494                          << IFSVersionCurrent << ".\n";
495       return -1;
496     }
497 
498   for (auto &Entry : SymbolMap)
499     Stub.Symbols.push_back(Entry.second);
500 
501   std::error_code SysErr;
502 
503   // Open file for writing.
504   raw_fd_ostream Out(OutputFilename, SysErr);
505   if (SysErr) {
506     WithColor::error() << "Couldn't open " << OutputFilename
507                        << " for writing.\n";
508     return -1;
509   }
510 
511   return writeIfso(Stub, (Action == "write-ifs"), Out);
512 }
513