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 "ErrorCollector.h"
10 #include "llvm/ADT/StringRef.h"
11 #include "llvm/ADT/StringSwitch.h"
12 #include "llvm/ADT/Triple.h"
13 #include "llvm/InterfaceStub/ELFObjHandler.h"
14 #include "llvm/InterfaceStub/IFSHandler.h"
15 #include "llvm/InterfaceStub/IFSStub.h"
16 #include "llvm/ObjectYAML/yaml2obj.h"
17 #include "llvm/Support/CommandLine.h"
18 #include "llvm/Support/Debug.h"
19 #include "llvm/Support/Errc.h"
20 #include "llvm/Support/Error.h"
21 #include "llvm/Support/FileOutputBuffer.h"
22 #include "llvm/Support/MemoryBuffer.h"
23 #include "llvm/Support/Path.h"
24 #include "llvm/Support/VersionTuple.h"
25 #include "llvm/Support/WithColor.h"
26 #include "llvm/Support/YAMLTraits.h"
27 #include "llvm/Support/raw_ostream.h"
28 #include "llvm/TextAPI/InterfaceFile.h"
29 #include "llvm/TextAPI/TextAPIReader.h"
30 #include "llvm/TextAPI/TextAPIWriter.h"
31 #include <set>
32 #include <string>
33 #include <vector>
34
35 using namespace llvm;
36 using namespace llvm::yaml;
37 using namespace llvm::MachO;
38 using namespace llvm::ifs;
39
40 #define DEBUG_TYPE "llvm-ifs"
41
42 namespace {
43 const VersionTuple IfsVersionCurrent(3, 0);
44
45 enum class FileFormat { IFS, ELF, TBD };
46 } // end anonymous namespace
47
48 cl::OptionCategory IfsCategory("Ifs Options");
49
50 // TODO: Use OptTable for option parsing in the future.
51 // Command line flags:
52 cl::list<std::string> InputFilePaths(cl::Positional, cl::desc("input"),
53 cl::ZeroOrMore, cl::cat(IfsCategory));
54 cl::opt<FileFormat> InputFormat(
55 "input-format", cl::desc("Specify the input file format"),
56 cl::values(clEnumValN(FileFormat::IFS, "IFS", "Text based ELF stub file"),
57 clEnumValN(FileFormat::ELF, "ELF", "ELF object file")),
58 cl::cat(IfsCategory));
59 cl::opt<FileFormat> OutputFormat(
60 "output-format", cl::desc("Specify the output file format"),
61 cl::values(clEnumValN(FileFormat::IFS, "IFS", "Text based ELF stub file"),
62 clEnumValN(FileFormat::ELF, "ELF", "ELF stub file"),
63 clEnumValN(FileFormat::TBD, "TBD", "Apple TBD text stub file")),
64 cl::Required, cl::cat(IfsCategory));
65 cl::opt<std::string> OptArch("arch",
66 cl::desc("Specify the architecture, e.g. x86_64"),
67 cl::cat(IfsCategory));
68 cl::opt<IFSBitWidthType>
69 OptBitWidth("bitwidth", cl::desc("Specify the bit width"),
70 cl::values(clEnumValN(IFSBitWidthType::IFS32, "32", "32 bits"),
71 clEnumValN(IFSBitWidthType::IFS64, "64", "64 bits")),
72 cl::cat(IfsCategory));
73 cl::opt<IFSEndiannessType> OptEndianness(
74 "endianness", cl::desc("Specify the endianness"),
75 cl::values(clEnumValN(IFSEndiannessType::Little, "little", "Little Endian"),
76 clEnumValN(IFSEndiannessType::Big, "big", "Big Endian")),
77 cl::cat(IfsCategory));
78 cl::opt<std::string> OptTargetTriple(
79 "target", cl::desc("Specify the target triple, e.g. x86_64-linux-gnu"),
80 cl::cat(IfsCategory));
81 cl::opt<std::string> OptTargetTripleHint(
82 "hint-ifs-target",
83 cl::desc("When --output-format is 'IFS', this flag will hint the expected "
84 "target triple for IFS output"),
85 cl::cat(IfsCategory));
86 cl::opt<bool> StripIFSArch(
87 "strip-ifs-arch",
88 cl::desc("Strip target architecture information away from IFS output"),
89 cl::cat(IfsCategory));
90 cl::opt<bool> StripIFSBitWidth(
91 "strip-ifs-bitwidth",
92 cl::desc("Strip target bit width information away from IFS output"),
93 cl::cat(IfsCategory));
94 cl::opt<bool> StripIFSEndiannessWidth(
95 "strip-ifs-endianness",
96 cl::desc("Strip target endianness information away from IFS output"),
97 cl::cat(IfsCategory));
98 cl::opt<bool> StripIFSTarget(
99 "strip-ifs-target",
100 cl::desc("Strip all target information away from IFS output"),
101 cl::cat(IfsCategory));
102 cl::opt<bool>
103 StripUndefined("strip-undefined",
104 cl::desc("Strip undefined symbols from IFS output"),
105 cl::cat(IfsCategory));
106
107 cl::opt<std::string>
108 SoName("soname",
109 cl::desc("Manually set the DT_SONAME entry of any emitted files"),
110 cl::value_desc("name"), cl::cat(IfsCategory));
111 cl::opt<std::string> OutputFilePath("output", cl::desc("Output file"),
112 cl::cat(IfsCategory));
113 cl::alias OutputFilePathA("o", cl::desc("Alias for --output"),
114 cl::aliasopt(OutputFilePath), cl::cat(IfsCategory));
115 cl::opt<bool> WriteIfChanged(
116 "write-if-changed",
117 cl::desc("Write the output file only if it is new or has changed."),
118 cl::cat(IfsCategory));
119
getTypeName(IFSSymbolType Type)120 static std::string getTypeName(IFSSymbolType Type) {
121 switch (Type) {
122 case IFSSymbolType::NoType:
123 return "NoType";
124 case IFSSymbolType::Func:
125 return "Func";
126 case IFSSymbolType::Object:
127 return "Object";
128 case IFSSymbolType::TLS:
129 return "TLS";
130 case IFSSymbolType::Unknown:
131 return "Unknown";
132 }
133 llvm_unreachable("Unexpected ifs symbol type.");
134 }
135
readInputFile(StringRef FilePath)136 static Expected<std::unique_ptr<IFSStub>> readInputFile(StringRef FilePath) {
137 // Read in file.
138 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrError =
139 MemoryBuffer::getFileOrSTDIN(FilePath, /*IsText=*/true);
140 if (!BufOrError)
141 return createStringError(BufOrError.getError(), "Could not open `%s`",
142 FilePath.data());
143
144 std::unique_ptr<MemoryBuffer> FileReadBuffer = std::move(*BufOrError);
145 ErrorCollector EC(/*UseFatalErrors=*/false);
146
147 // First try to read as a binary (fails fast if not binary).
148 if (InputFormat.getNumOccurrences() == 0 || InputFormat == FileFormat::ELF) {
149 Expected<std::unique_ptr<IFSStub>> StubFromELF =
150 readELFFile(FileReadBuffer->getMemBufferRef());
151 if (StubFromELF) {
152 (*StubFromELF)->IfsVersion = IfsVersionCurrent;
153 return std::move(*StubFromELF);
154 }
155 EC.addError(StubFromELF.takeError(), "BinaryRead");
156 }
157
158 // Fall back to reading as a ifs.
159 if (InputFormat.getNumOccurrences() == 0 || InputFormat == FileFormat::IFS) {
160 Expected<std::unique_ptr<IFSStub>> StubFromIFS =
161 readIFSFromBuffer(FileReadBuffer->getBuffer());
162 if (StubFromIFS) {
163 if ((*StubFromIFS)->IfsVersion > IfsVersionCurrent)
164 EC.addError(
165 createStringError(errc::not_supported,
166 "IFS version " +
167 (*StubFromIFS)->IfsVersion.getAsString() +
168 " is unsupported."),
169 "ReadInputFile");
170 else
171 return std::move(*StubFromIFS);
172 } else {
173 EC.addError(StubFromIFS.takeError(), "YamlParse");
174 }
175 }
176
177 // If both readers fail, build a new error that includes all information.
178 EC.addError(createStringError(errc::not_supported,
179 "No file readers succeeded reading `%s` "
180 "(unsupported/malformed file?)",
181 FilePath.data()),
182 "ReadInputFile");
183 EC.escalateToFatal();
184 return EC.makeError();
185 }
186
writeTbdStub(const Triple & T,const std::vector<IFSSymbol> & Symbols,const StringRef Format,raw_ostream & Out)187 static int writeTbdStub(const Triple &T, const std::vector<IFSSymbol> &Symbols,
188 const StringRef Format, raw_ostream &Out) {
189
190 auto PlatformKindOrError =
191 [](const llvm::Triple &T) -> llvm::Expected<llvm::MachO::PlatformKind> {
192 if (T.isMacOSX())
193 return llvm::MachO::PlatformKind::macOS;
194 if (T.isTvOS())
195 return llvm::MachO::PlatformKind::tvOS;
196 if (T.isWatchOS())
197 return llvm::MachO::PlatformKind::watchOS;
198 // Note: put isiOS last because tvOS and watchOS are also iOS according
199 // to the Triple.
200 if (T.isiOS())
201 return llvm::MachO::PlatformKind::iOS;
202
203 return createStringError(errc::not_supported, "Invalid Platform.\n");
204 }(T);
205
206 if (!PlatformKindOrError)
207 return -1;
208
209 PlatformKind Plat = PlatformKindOrError.get();
210 TargetList Targets({Target(llvm::MachO::mapToArchitecture(T), Plat)});
211
212 InterfaceFile File;
213 File.setFileType(FileType::TBD_V3); // Only supporting v3 for now.
214 File.addTargets(Targets);
215
216 for (const auto &Symbol : Symbols) {
217 auto Name = Symbol.Name;
218 auto Kind = SymbolKind::GlobalSymbol;
219 switch (Symbol.Type) {
220 default:
221 case IFSSymbolType::NoType:
222 Kind = SymbolKind::GlobalSymbol;
223 break;
224 case IFSSymbolType::Object:
225 Kind = SymbolKind::GlobalSymbol;
226 break;
227 case IFSSymbolType::Func:
228 Kind = SymbolKind::GlobalSymbol;
229 break;
230 }
231 if (Symbol.Weak)
232 File.addSymbol(Kind, Name, Targets, SymbolFlags::WeakDefined);
233 else
234 File.addSymbol(Kind, Name, Targets);
235 }
236
237 SmallString<4096> Buffer;
238 raw_svector_ostream OS(Buffer);
239 if (Error Result = TextAPIWriter::writeToStream(OS, File))
240 return -1;
241 Out << OS.str();
242 return 0;
243 }
244
fatalError(Error Err)245 static void fatalError(Error Err) {
246 WithColor::defaultErrorHandler(std::move(Err));
247 exit(1);
248 }
249
250 /// writeIFS() writes a Text-Based ELF stub to a file using the latest version
251 /// of the YAML parser.
writeIFS(StringRef FilePath,IFSStub & Stub)252 static Error writeIFS(StringRef FilePath, IFSStub &Stub) {
253 // Write IFS to memory first.
254 std::string IFSStr;
255 raw_string_ostream OutStr(IFSStr);
256 Error YAMLErr = writeIFSToOutputStream(OutStr, Stub);
257 if (YAMLErr)
258 return YAMLErr;
259 OutStr.flush();
260
261 if (WriteIfChanged) {
262 if (ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrError =
263 MemoryBuffer::getFile(FilePath)) {
264 // Compare IFS output with the existing IFS file. If unchanged, avoid changing the file.
265 if ((*BufOrError)->getBuffer() == IFSStr)
266 return Error::success();
267 }
268 }
269 // Open IFS file for writing.
270 std::error_code SysErr;
271 raw_fd_ostream Out(FilePath, SysErr);
272 if (SysErr)
273 return createStringError(SysErr, "Couldn't open `%s` for writing",
274 FilePath.data());
275 Out << IFSStr;
276 return Error::success();
277 }
278
main(int argc,char * argv[])279 int main(int argc, char *argv[]) {
280 // Parse arguments.
281 cl::HideUnrelatedOptions({&IfsCategory, &getColorCategory()});
282 cl::ParseCommandLineOptions(argc, argv);
283
284 if (InputFilePaths.empty())
285 InputFilePaths.push_back("-");
286
287 // If input files are more than one, they can only be IFS files.
288 if (InputFilePaths.size() > 1)
289 InputFormat.setValue(FileFormat::IFS);
290
291 // Attempt to merge input.
292 IFSStub Stub;
293 std::map<std::string, IFSSymbol> SymbolMap;
294 std::string PreviousInputFilePath;
295 for (const std::string &InputFilePath : InputFilePaths) {
296 Expected<std::unique_ptr<IFSStub>> StubOrErr = readInputFile(InputFilePath);
297 if (!StubOrErr)
298 fatalError(StubOrErr.takeError());
299
300 std::unique_ptr<IFSStub> TargetStub = std::move(StubOrErr.get());
301 if (PreviousInputFilePath.empty()) {
302 Stub.IfsVersion = TargetStub->IfsVersion;
303 Stub.Target = TargetStub->Target;
304 Stub.SoName = TargetStub->SoName;
305 Stub.NeededLibs = TargetStub->NeededLibs;
306 } else {
307 if (Stub.IfsVersion != TargetStub->IfsVersion) {
308 if (Stub.IfsVersion.getMajor() != IfsVersionCurrent.getMajor()) {
309 WithColor::error()
310 << "Interface Stub: IfsVersion Mismatch."
311 << "\nFilenames: " << PreviousInputFilePath << " "
312 << InputFilePath << "\nIfsVersion Values: " << Stub.IfsVersion
313 << " " << TargetStub->IfsVersion << "\n";
314 return -1;
315 }
316 if (TargetStub->IfsVersion > Stub.IfsVersion)
317 Stub.IfsVersion = TargetStub->IfsVersion;
318 }
319 if (Stub.Target != TargetStub->Target && !TargetStub->Target.empty()) {
320 WithColor::error() << "Interface Stub: Target Mismatch."
321 << "\nFilenames: " << PreviousInputFilePath << " "
322 << InputFilePath;
323 // << "\nTriple Values: " << Stub.Triple << " "
324 // << TargetStub->Triple << "\n";
325 return -1;
326 }
327 if (Stub.SoName != TargetStub->SoName) {
328 WithColor::error() << "Interface Stub: SoName Mismatch."
329 << "\nFilenames: " << PreviousInputFilePath << " "
330 << InputFilePath
331 << "\nSoName Values: " << Stub.SoName << " "
332 << TargetStub->SoName << "\n";
333 return -1;
334 }
335 if (Stub.NeededLibs != TargetStub->NeededLibs) {
336 WithColor::error() << "Interface Stub: NeededLibs Mismatch."
337 << "\nFilenames: " << PreviousInputFilePath << " "
338 << InputFilePath << "\n";
339 return -1;
340 }
341 }
342
343 for (auto Symbol : TargetStub->Symbols) {
344 auto SI = SymbolMap.find(Symbol.Name);
345 if (SI == SymbolMap.end()) {
346 SymbolMap.insert(
347 std::pair<std::string, IFSSymbol>(Symbol.Name, Symbol));
348 continue;
349 }
350
351 assert(Symbol.Name == SI->second.Name && "Symbol Names Must Match.");
352
353 // Check conflicts:
354 if (Symbol.Type != SI->second.Type) {
355 WithColor::error() << "Interface Stub: Type Mismatch for "
356 << Symbol.Name << ".\nFilename: " << InputFilePath
357 << "\nType Values: " << getTypeName(SI->second.Type)
358 << " " << getTypeName(Symbol.Type) << "\n";
359
360 return -1;
361 }
362 if (Symbol.Size != SI->second.Size) {
363 WithColor::error() << "Interface Stub: Size Mismatch for "
364 << Symbol.Name << ".\nFilename: " << InputFilePath
365 << "\nSize Values: " << SI->second.Size << " "
366 << Symbol.Size << "\n";
367
368 return -1;
369 }
370 if (Symbol.Weak != SI->second.Weak) {
371 Symbol.Weak = false;
372 continue;
373 }
374 // TODO: Not checking Warning. Will be dropped.
375 }
376
377 PreviousInputFilePath = InputFilePath;
378 }
379
380 if (Stub.IfsVersion != IfsVersionCurrent)
381 if (Stub.IfsVersion.getMajor() != IfsVersionCurrent.getMajor()) {
382 WithColor::error() << "Interface Stub: Bad IfsVersion: "
383 << Stub.IfsVersion << ", llvm-ifs supported version: "
384 << IfsVersionCurrent << ".\n";
385 return -1;
386 }
387
388 for (auto &Entry : SymbolMap)
389 Stub.Symbols.push_back(Entry.second);
390
391 // Change SoName before emitting stubs.
392 if (SoName.getNumOccurrences() == 1)
393 Stub.SoName = SoName;
394 Optional<IFSArch> OverrideArch;
395 Optional<IFSEndiannessType> OverrideEndianness;
396 Optional<IFSBitWidthType> OverrideBitWidth;
397 Optional<std::string> OverrideTriple;
398 if (OptArch.getNumOccurrences() == 1)
399 OverrideArch = ELF::convertArchNameToEMachine(OptArch.getValue());
400 if (OptEndianness.getNumOccurrences() == 1)
401 OverrideEndianness = OptEndianness.getValue();
402 if (OptBitWidth.getNumOccurrences() == 1)
403 OverrideBitWidth = OptBitWidth.getValue();
404 if (OptTargetTriple.getNumOccurrences() == 1)
405 OverrideTriple = OptTargetTriple.getValue();
406 Error OverrideError = overrideIFSTarget(
407 Stub, OverrideArch, OverrideEndianness, OverrideBitWidth, OverrideTriple);
408 if (OverrideError)
409 fatalError(std::move(OverrideError));
410
411 switch (OutputFormat.getValue()) {
412 case FileFormat::TBD: {
413 std::error_code SysErr;
414 raw_fd_ostream Out(OutputFilePath, SysErr);
415 if (SysErr) {
416 WithColor::error() << "Couldn't open " << OutputFilePath
417 << " for writing.\n";
418 return -1;
419 }
420 if (!Stub.Target.Triple) {
421 WithColor::error()
422 << "Triple should be defined when output format is TBD";
423 return -1;
424 }
425 return writeTbdStub(llvm::Triple(Stub.Target.Triple.getValue()),
426 Stub.Symbols, "TBD", Out);
427 }
428 case FileFormat::IFS: {
429 Stub.IfsVersion = IfsVersionCurrent;
430 if (InputFormat.getValue() == FileFormat::ELF &&
431 OptTargetTripleHint.getNumOccurrences() == 1) {
432 std::error_code HintEC(1, std::generic_category());
433 IFSTarget HintTarget = parseTriple(OptTargetTripleHint);
434 if (Stub.Target.Arch.getValue() != HintTarget.Arch.getValue())
435 fatalError(make_error<StringError>(
436 "Triple hint does not match the actual architecture", HintEC));
437 if (Stub.Target.Endianness.getValue() !=
438 HintTarget.Endianness.getValue())
439 fatalError(make_error<StringError>(
440 "Triple hint does not match the actual endianness", HintEC));
441 if (Stub.Target.BitWidth.getValue() != HintTarget.BitWidth.getValue())
442 fatalError(make_error<StringError>(
443 "Triple hint does not match the actual bit width", HintEC));
444
445 stripIFSTarget(Stub, true, false, false, false);
446 Stub.Target.Triple = OptTargetTripleHint.getValue();
447 } else {
448 stripIFSTarget(Stub, StripIFSTarget, StripIFSArch,
449 StripIFSEndiannessWidth, StripIFSBitWidth);
450 }
451 if (StripUndefined)
452 stripIFSUndefinedSymbols(Stub);
453 Error IFSWriteError = writeIFS(OutputFilePath.getValue(), Stub);
454 if (IFSWriteError)
455 fatalError(std::move(IFSWriteError));
456 break;
457 }
458 case FileFormat::ELF: {
459 Error TargetError = validateIFSTarget(Stub, true);
460 if (TargetError)
461 fatalError(std::move(TargetError));
462 Error BinaryWriteError =
463 writeBinaryStub(OutputFilePath, Stub, WriteIfChanged);
464 if (BinaryWriteError)
465 fatalError(std::move(BinaryWriteError));
466 break;
467 }
468 }
469 return 0;
470 }
471