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