1 //===- llvm-ifs.cpp -------------------------------------------------------===//
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
7 //===-----------------------------------------------------------------------===/
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"
36 using namespace llvm::yaml
;
37 using namespace llvm::MachO
;
38 using namespace llvm::ifs
;
40 #define DEBUG_TYPE "llvm-ifs"
43 const VersionTuple
IfsVersionCurrent(3, 0);
45 enum class FileFormat
{ IFS
, ELF
, TBD
};
46 } // end anonymous namespace
48 cl::OptionCategory
IfsCategory("Ifs Options");
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 **DEPRECATED**"),
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::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(
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(
88 cl::desc("Strip target architecture information away from IFS output"),
89 cl::cat(IfsCategory
));
90 cl::opt
<bool> StripIFSBitWidth(
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(
100 cl::desc("Strip all target information away from IFS output"),
101 cl::cat(IfsCategory
));
103 StripUndefined("strip-undefined",
104 cl::desc("Strip undefined symbols from IFS output"),
105 cl::cat(IfsCategory
));
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",
112 cl::desc("Output file **DEPRECATED**"),
113 cl::cat(IfsCategory
));
114 cl::alias
OutputFilePathA("o", cl::desc("Alias for --output"),
115 cl::aliasopt(OutputFilePath
), cl::cat(IfsCategory
));
116 cl::opt
<std::string
> OutputELFFilePath("output-elf",
117 cl::desc("Output path for ELF file"),
118 cl::cat(IfsCategory
));
119 cl::opt
<std::string
> OutputIFSFilePath("output-ifs",
120 cl::desc("Output path for IFS file"),
121 cl::cat(IfsCategory
));
122 cl::opt
<std::string
> OutputTBDFilePath("output-tbd",
123 cl::desc("Output path for TBD file"),
124 cl::cat(IfsCategory
));
126 cl::opt
<bool> WriteIfChanged(
128 cl::desc("Write the output file only if it is new or has changed."),
129 cl::cat(IfsCategory
));
131 static std::string
getTypeName(IFSSymbolType Type
) {
133 case IFSSymbolType::NoType
:
135 case IFSSymbolType::Func
:
137 case IFSSymbolType::Object
:
139 case IFSSymbolType::TLS
:
141 case IFSSymbolType::Unknown
:
144 llvm_unreachable("Unexpected ifs symbol type.");
147 static Expected
<std::unique_ptr
<IFSStub
>> readInputFile(StringRef FilePath
) {
149 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> BufOrError
=
150 MemoryBuffer::getFileOrSTDIN(FilePath
, /*IsText=*/true);
152 return createStringError(BufOrError
.getError(), "Could not open `%s`",
155 std::unique_ptr
<MemoryBuffer
> FileReadBuffer
= std::move(*BufOrError
);
156 ErrorCollector
EC(/*UseFatalErrors=*/false);
158 // First try to read as a binary (fails fast if not binary).
159 if (InputFormat
.getNumOccurrences() == 0 || InputFormat
== FileFormat::ELF
) {
160 Expected
<std::unique_ptr
<IFSStub
>> StubFromELF
=
161 readELFFile(FileReadBuffer
->getMemBufferRef());
163 (*StubFromELF
)->IfsVersion
= IfsVersionCurrent
;
164 return std::move(*StubFromELF
);
166 EC
.addError(StubFromELF
.takeError(), "BinaryRead");
169 // Fall back to reading as a ifs.
170 if (InputFormat
.getNumOccurrences() == 0 || InputFormat
== FileFormat::IFS
) {
171 Expected
<std::unique_ptr
<IFSStub
>> StubFromIFS
=
172 readIFSFromBuffer(FileReadBuffer
->getBuffer());
174 if ((*StubFromIFS
)->IfsVersion
> IfsVersionCurrent
)
176 createStringError(errc::not_supported
,
178 (*StubFromIFS
)->IfsVersion
.getAsString() +
182 return std::move(*StubFromIFS
);
184 EC
.addError(StubFromIFS
.takeError(), "YamlParse");
188 // If both readers fail, build a new error that includes all information.
189 EC
.addError(createStringError(errc::not_supported
,
190 "No file readers succeeded reading `%s` "
191 "(unsupported/malformed file?)",
194 EC
.escalateToFatal();
195 return EC
.makeError();
198 static int writeTbdStub(const Triple
&T
, const std::vector
<IFSSymbol
> &Symbols
,
199 const StringRef Format
, raw_ostream
&Out
) {
201 auto PlatformTypeOrError
=
202 [](const llvm::Triple
&T
) -> llvm::Expected
<llvm::MachO::PlatformType
> {
204 return llvm::MachO::PLATFORM_MACOS
;
206 return llvm::MachO::PLATFORM_TVOS
;
208 return llvm::MachO::PLATFORM_WATCHOS
;
209 // Note: put isiOS last because tvOS and watchOS are also iOS according
212 return llvm::MachO::PLATFORM_IOS
;
214 return createStringError(errc::not_supported
, "Invalid Platform.\n");
217 if (!PlatformTypeOrError
)
220 PlatformType Plat
= PlatformTypeOrError
.get();
221 TargetList
Targets({Target(llvm::MachO::mapToArchitecture(T
), Plat
)});
224 File
.setFileType(FileType::TBD_V3
); // Only supporting v3 for now.
225 File
.addTargets(Targets
);
227 for (const auto &Symbol
: Symbols
) {
228 auto Name
= Symbol
.Name
;
229 auto Kind
= SymbolKind::GlobalSymbol
;
230 switch (Symbol
.Type
) {
232 case IFSSymbolType::NoType
:
233 Kind
= SymbolKind::GlobalSymbol
;
235 case IFSSymbolType::Object
:
236 Kind
= SymbolKind::GlobalSymbol
;
238 case IFSSymbolType::Func
:
239 Kind
= SymbolKind::GlobalSymbol
;
243 File
.addSymbol(Kind
, Name
, Targets
, SymbolFlags::WeakDefined
);
245 File
.addSymbol(Kind
, Name
, Targets
);
248 SmallString
<4096> Buffer
;
249 raw_svector_ostream
OS(Buffer
);
250 if (Error Result
= TextAPIWriter::writeToStream(OS
, File
))
256 static void fatalError(Error Err
) {
257 WithColor::defaultErrorHandler(std::move(Err
));
261 /// writeIFS() writes a Text-Based ELF stub to a file using the latest version
262 /// of the YAML parser.
263 static Error
writeIFS(StringRef FilePath
, IFSStub
&Stub
) {
264 // Write IFS to memory first.
266 raw_string_ostream
OutStr(IFSStr
);
267 Error YAMLErr
= writeIFSToOutputStream(OutStr
, Stub
);
272 if (WriteIfChanged
) {
273 if (ErrorOr
<std::unique_ptr
<MemoryBuffer
>> BufOrError
=
274 MemoryBuffer::getFile(FilePath
)) {
275 // Compare IFS output with the existing IFS file. If unchanged, avoid changing the file.
276 if ((*BufOrError
)->getBuffer() == IFSStr
)
277 return Error::success();
280 // Open IFS file for writing.
281 std::error_code SysErr
;
282 raw_fd_ostream
Out(FilePath
, SysErr
);
284 return createStringError(SysErr
, "Couldn't open `%s` for writing",
287 return Error::success();
290 int main(int argc
, char *argv
[]) {
292 cl::HideUnrelatedOptions({&IfsCategory
, &getColorCategory()});
293 cl::ParseCommandLineOptions(argc
, argv
);
295 if (InputFilePaths
.empty())
296 InputFilePaths
.push_back("-");
298 // If input files are more than one, they can only be IFS files.
299 if (InputFilePaths
.size() > 1)
300 InputFormat
.setValue(FileFormat::IFS
);
302 // Attempt to merge input.
304 std::map
<std::string
, IFSSymbol
> SymbolMap
;
305 std::string PreviousInputFilePath
;
306 for (const std::string
&InputFilePath
: InputFilePaths
) {
307 Expected
<std::unique_ptr
<IFSStub
>> StubOrErr
= readInputFile(InputFilePath
);
309 fatalError(StubOrErr
.takeError());
311 std::unique_ptr
<IFSStub
> TargetStub
= std::move(StubOrErr
.get());
312 if (PreviousInputFilePath
.empty()) {
313 Stub
.IfsVersion
= TargetStub
->IfsVersion
;
314 Stub
.Target
= TargetStub
->Target
;
315 Stub
.SoName
= TargetStub
->SoName
;
316 Stub
.NeededLibs
= TargetStub
->NeededLibs
;
318 if (Stub
.IfsVersion
!= TargetStub
->IfsVersion
) {
319 if (Stub
.IfsVersion
.getMajor() != IfsVersionCurrent
.getMajor()) {
321 << "Interface Stub: IfsVersion Mismatch."
322 << "\nFilenames: " << PreviousInputFilePath
<< " "
323 << InputFilePath
<< "\nIfsVersion Values: " << Stub
.IfsVersion
324 << " " << TargetStub
->IfsVersion
<< "\n";
327 if (TargetStub
->IfsVersion
> Stub
.IfsVersion
)
328 Stub
.IfsVersion
= TargetStub
->IfsVersion
;
330 if (Stub
.Target
!= TargetStub
->Target
&& !TargetStub
->Target
.empty()) {
331 WithColor::error() << "Interface Stub: Target Mismatch."
332 << "\nFilenames: " << PreviousInputFilePath
<< " "
336 if (Stub
.SoName
!= TargetStub
->SoName
) {
337 WithColor::error() << "Interface Stub: SoName Mismatch."
338 << "\nFilenames: " << PreviousInputFilePath
<< " "
340 << "\nSoName Values: " << Stub
.SoName
<< " "
341 << TargetStub
->SoName
<< "\n";
344 if (Stub
.NeededLibs
!= TargetStub
->NeededLibs
) {
345 WithColor::error() << "Interface Stub: NeededLibs Mismatch."
346 << "\nFilenames: " << PreviousInputFilePath
<< " "
347 << InputFilePath
<< "\n";
352 for (auto Symbol
: TargetStub
->Symbols
) {
353 auto SI
= SymbolMap
.find(Symbol
.Name
);
354 if (SI
== SymbolMap
.end()) {
356 std::pair
<std::string
, IFSSymbol
>(Symbol
.Name
, Symbol
));
360 assert(Symbol
.Name
== SI
->second
.Name
&& "Symbol Names Must Match.");
363 if (Symbol
.Type
!= SI
->second
.Type
) {
364 WithColor::error() << "Interface Stub: Type Mismatch for "
365 << Symbol
.Name
<< ".\nFilename: " << InputFilePath
366 << "\nType Values: " << getTypeName(SI
->second
.Type
)
367 << " " << getTypeName(Symbol
.Type
) << "\n";
371 if (Symbol
.Size
!= SI
->second
.Size
) {
372 WithColor::error() << "Interface Stub: Size Mismatch for "
373 << Symbol
.Name
<< ".\nFilename: " << InputFilePath
374 << "\nSize Values: " << SI
->second
.Size
<< " "
375 << Symbol
.Size
<< "\n";
379 if (Symbol
.Weak
!= SI
->second
.Weak
) {
383 // TODO: Not checking Warning. Will be dropped.
386 PreviousInputFilePath
= InputFilePath
;
389 if (Stub
.IfsVersion
!= IfsVersionCurrent
)
390 if (Stub
.IfsVersion
.getMajor() != IfsVersionCurrent
.getMajor()) {
391 WithColor::error() << "Interface Stub: Bad IfsVersion: "
392 << Stub
.IfsVersion
<< ", llvm-ifs supported version: "
393 << IfsVersionCurrent
<< ".\n";
397 for (auto &Entry
: SymbolMap
)
398 Stub
.Symbols
.push_back(Entry
.second
);
400 // Change SoName before emitting stubs.
401 if (SoName
.getNumOccurrences() == 1)
402 Stub
.SoName
= SoName
;
403 Optional
<IFSArch
> OverrideArch
;
404 Optional
<IFSEndiannessType
> OverrideEndianness
;
405 Optional
<IFSBitWidthType
> OverrideBitWidth
;
406 Optional
<std::string
> OverrideTriple
;
407 if (OptArch
.getNumOccurrences() == 1)
408 OverrideArch
= ELF::convertArchNameToEMachine(OptArch
.getValue());
409 if (OptEndianness
.getNumOccurrences() == 1)
410 OverrideEndianness
= OptEndianness
.getValue();
411 if (OptBitWidth
.getNumOccurrences() == 1)
412 OverrideBitWidth
= OptBitWidth
.getValue();
413 if (OptTargetTriple
.getNumOccurrences() == 1)
414 OverrideTriple
= OptTargetTriple
.getValue();
415 Error OverrideError
= overrideIFSTarget(
416 Stub
, OverrideArch
, OverrideEndianness
, OverrideBitWidth
, OverrideTriple
);
418 fatalError(std::move(OverrideError
));
420 if (OutputELFFilePath
.getNumOccurrences() == 0 &&
421 OutputIFSFilePath
.getNumOccurrences() == 0 &&
422 OutputTBDFilePath
.getNumOccurrences() == 0) {
423 if (OutputFormat
.getNumOccurrences() == 0) {
424 WithColor::error() << "at least one output should be specified.";
427 } else if (OutputFormat
.getNumOccurrences() == 1) {
428 WithColor::error() << "'--output-format' cannot be used with "
429 "'--output-{FILE_FORMAT}' options at the same time";
432 if (OutputFormat
.getNumOccurrences() == 1) {
433 // TODO: Remove OutputFormat flag in the next revision.
434 WithColor::warning() << "--output-format option is deprecated, please use "
435 "--output-{FILE_FORMAT} options instead\n";
436 switch (OutputFormat
.getValue()) {
437 case FileFormat::TBD
: {
438 std::error_code SysErr
;
439 raw_fd_ostream
Out(OutputFilePath
, SysErr
);
441 WithColor::error() << "Couldn't open " << OutputFilePath
442 << " for writing.\n";
445 if (!Stub
.Target
.Triple
) {
447 << "Triple should be defined when output format is TBD";
450 return writeTbdStub(llvm::Triple(Stub
.Target
.Triple
.getValue()),
451 Stub
.Symbols
, "TBD", Out
);
453 case FileFormat::IFS
: {
454 Stub
.IfsVersion
= IfsVersionCurrent
;
455 if (InputFormat
.getValue() == FileFormat::ELF
&&
456 OptTargetTripleHint
.getNumOccurrences() == 1) {
457 std::error_code
HintEC(1, std::generic_category());
458 IFSTarget HintTarget
= parseTriple(OptTargetTripleHint
);
459 if (Stub
.Target
.Arch
.getValue() != HintTarget
.Arch
.getValue())
460 fatalError(make_error
<StringError
>(
461 "Triple hint does not match the actual architecture", HintEC
));
462 if (Stub
.Target
.Endianness
.getValue() !=
463 HintTarget
.Endianness
.getValue())
464 fatalError(make_error
<StringError
>(
465 "Triple hint does not match the actual endianness", HintEC
));
466 if (Stub
.Target
.BitWidth
.getValue() != HintTarget
.BitWidth
.getValue())
467 fatalError(make_error
<StringError
>(
468 "Triple hint does not match the actual bit width", HintEC
));
470 stripIFSTarget(Stub
, true, false, false, false);
471 Stub
.Target
.Triple
= OptTargetTripleHint
.getValue();
473 stripIFSTarget(Stub
, StripIFSTarget
, StripIFSArch
,
474 StripIFSEndiannessWidth
, StripIFSBitWidth
);
477 stripIFSUndefinedSymbols(Stub
);
478 Error IFSWriteError
= writeIFS(OutputFilePath
.getValue(), Stub
);
480 fatalError(std::move(IFSWriteError
));
483 case FileFormat::ELF
: {
484 Error TargetError
= validateIFSTarget(Stub
, true);
486 fatalError(std::move(TargetError
));
487 Error BinaryWriteError
=
488 writeBinaryStub(OutputFilePath
, Stub
, WriteIfChanged
);
489 if (BinaryWriteError
)
490 fatalError(std::move(BinaryWriteError
));
495 // Check if output path for individual format.
496 if (OutputELFFilePath
.getNumOccurrences() == 1) {
497 Error TargetError
= validateIFSTarget(Stub
, true);
499 fatalError(std::move(TargetError
));
500 Error BinaryWriteError
=
501 writeBinaryStub(OutputELFFilePath
, Stub
, WriteIfChanged
);
502 if (BinaryWriteError
)
503 fatalError(std::move(BinaryWriteError
));
505 if (OutputIFSFilePath
.getNumOccurrences() == 1) {
506 Stub
.IfsVersion
= IfsVersionCurrent
;
507 if (InputFormat
.getValue() == FileFormat::ELF
&&
508 OptTargetTripleHint
.getNumOccurrences() == 1) {
509 std::error_code
HintEC(1, std::generic_category());
510 IFSTarget HintTarget
= parseTriple(OptTargetTripleHint
);
511 if (Stub
.Target
.Arch
.getValue() != HintTarget
.Arch
.getValue())
512 fatalError(make_error
<StringError
>(
513 "Triple hint does not match the actual architecture", HintEC
));
514 if (Stub
.Target
.Endianness
.getValue() !=
515 HintTarget
.Endianness
.getValue())
516 fatalError(make_error
<StringError
>(
517 "Triple hint does not match the actual endianness", HintEC
));
518 if (Stub
.Target
.BitWidth
.getValue() != HintTarget
.BitWidth
.getValue())
519 fatalError(make_error
<StringError
>(
520 "Triple hint does not match the actual bit width", HintEC
));
522 stripIFSTarget(Stub
, true, false, false, false);
523 Stub
.Target
.Triple
= OptTargetTripleHint
.getValue();
525 stripIFSTarget(Stub
, StripIFSTarget
, StripIFSArch
,
526 StripIFSEndiannessWidth
, StripIFSBitWidth
);
529 stripIFSUndefinedSymbols(Stub
);
530 Error IFSWriteError
= writeIFS(OutputIFSFilePath
.getValue(), Stub
);
532 fatalError(std::move(IFSWriteError
));
534 if (OutputTBDFilePath
.getNumOccurrences() == 1) {
535 std::error_code SysErr
;
536 raw_fd_ostream
Out(OutputTBDFilePath
, SysErr
);
538 WithColor::error() << "Couldn't open " << OutputTBDFilePath
539 << " for writing.\n";
542 if (!Stub
.Target
.Triple
) {
544 << "Triple should be defined when output format is TBD";
547 return writeTbdStub(llvm::Triple(Stub
.Target
.Triple
.getValue()),
548 Stub
.Symbols
, "TBD", Out
);