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