| //===- DylibReader.cpp -------------- TAPI MachO Dylib Reader --*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// Implements the TAPI Reader for Mach-O dynamic libraries. | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/TextAPI/DylibReader.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/DebugInfo/DWARF/DWARFCompileUnit.h" | |
| #include "llvm/DebugInfo/DWARF/DWARFContext.h" | |
| #include "llvm/Object/Binary.h" | |
| #include "llvm/Object/MachOUniversal.h" | |
| #include "llvm/Support/Endian.h" | |
| #include "llvm/TargetParser/Triple.h" | |
| #include "llvm/TextAPI/InterfaceFile.h" | |
| #include "llvm/TextAPI/RecordsSlice.h" | |
| #include "llvm/TextAPI/TextAPIError.h" | |
| #include <iomanip> | |
| #include <sstream> | |
| #include <string> | |
| #include <tuple> | |
| using namespace llvm; | |
| using namespace llvm::object; | |
| using namespace llvm::MachO; | |
| using namespace llvm::MachO::DylibReader; | |
| using TripleVec = std::vector<Triple>; | |
| static TripleVec::iterator emplace(TripleVec &Container, Triple &&T) { | |
| auto I = partition_point(Container, [=](const Triple &CT) { | |
| return std::forward_as_tuple(CT.getArch(), CT.getOS(), | |
| CT.getEnvironment()) < | |
| std::forward_as_tuple(T.getArch(), T.getOS(), T.getEnvironment()); | |
| }); | |
| if (I != Container.end() && *I == T) | |
| return I; | |
| return Container.emplace(I, T); | |
| } | |
| static TripleVec constructTriples(MachOObjectFile *Obj, | |
| const Architecture ArchT) { | |
| auto getOSVersionStr = [](uint32_t V) { | |
| PackedVersion OSVersion(V); | |
| std::string Vers; | |
| raw_string_ostream VStream(Vers); | |
| VStream << OSVersion; | |
| return VStream.str(); | |
| }; | |
| auto getOSVersion = [&](const MachOObjectFile::LoadCommandInfo &cmd) { | |
| auto Vers = Obj->getVersionMinLoadCommand(cmd); | |
| return getOSVersionStr(Vers.version); | |
| }; | |
| TripleVec Triples; | |
| bool IsIntel = ArchitectureSet(ArchT).hasX86(); | |
| auto Arch = getArchitectureName(ArchT); | |
| for (const auto &cmd : Obj->load_commands()) { | |
| std::string OSVersion; | |
| switch (cmd.C.cmd) { | |
| case MachO::LC_VERSION_MIN_MACOSX: | |
| OSVersion = getOSVersion(cmd); | |
| emplace(Triples, {Arch, "apple", "macos" + OSVersion}); | |
| break; | |
| case MachO::LC_VERSION_MIN_IPHONEOS: | |
| OSVersion = getOSVersion(cmd); | |
| if (IsIntel) | |
| emplace(Triples, {Arch, "apple", "ios" + OSVersion, "simulator"}); | |
| else | |
| emplace(Triples, {Arch, "apple", "ios" + OSVersion}); | |
| break; | |
| case MachO::LC_VERSION_MIN_TVOS: | |
| OSVersion = getOSVersion(cmd); | |
| if (IsIntel) | |
| emplace(Triples, {Arch, "apple", "tvos" + OSVersion, "simulator"}); | |
| else | |
| emplace(Triples, {Arch, "apple", "tvos" + OSVersion}); | |
| break; | |
| case MachO::LC_VERSION_MIN_WATCHOS: | |
| OSVersion = getOSVersion(cmd); | |
| if (IsIntel) | |
| emplace(Triples, {Arch, "apple", "watchos" + OSVersion, "simulator"}); | |
| else | |
| emplace(Triples, {Arch, "apple", "watchos" + OSVersion}); | |
| break; | |
| case MachO::LC_BUILD_VERSION: { | |
| OSVersion = getOSVersionStr(Obj->getBuildVersionLoadCommand(cmd).minos); | |
| switch (Obj->getBuildVersionLoadCommand(cmd).platform) { | |
| case MachO::PLATFORM_MACOS: | |
| emplace(Triples, {Arch, "apple", "macos" + OSVersion}); | |
| break; | |
| case MachO::PLATFORM_IOS: | |
| emplace(Triples, {Arch, "apple", "ios" + OSVersion}); | |
| break; | |
| case MachO::PLATFORM_TVOS: | |
| emplace(Triples, {Arch, "apple", "tvos" + OSVersion}); | |
| break; | |
| case MachO::PLATFORM_WATCHOS: | |
| emplace(Triples, {Arch, "apple", "watchos" + OSVersion}); | |
| break; | |
| case MachO::PLATFORM_BRIDGEOS: | |
| emplace(Triples, {Arch, "apple", "bridgeos" + OSVersion}); | |
| break; | |
| case MachO::PLATFORM_MACCATALYST: | |
| emplace(Triples, {Arch, "apple", "ios" + OSVersion, "macabi"}); | |
| break; | |
| case MachO::PLATFORM_IOSSIMULATOR: | |
| emplace(Triples, {Arch, "apple", "ios" + OSVersion, "simulator"}); | |
| break; | |
| case MachO::PLATFORM_TVOSSIMULATOR: | |
| emplace(Triples, {Arch, "apple", "tvos" + OSVersion, "simulator"}); | |
| break; | |
| case MachO::PLATFORM_WATCHOSSIMULATOR: | |
| emplace(Triples, {Arch, "apple", "watchos" + OSVersion, "simulator"}); | |
| break; | |
| case MachO::PLATFORM_DRIVERKIT: | |
| emplace(Triples, {Arch, "apple", "driverkit" + OSVersion}); | |
| break; | |
| default: | |
| break; // Skip any others. | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| // Record unknown platform for older binaries that don't enforce platform | |
| // load commands. | |
| if (Triples.empty()) | |
| emplace(Triples, {Arch, "apple", "unknown"}); | |
| return Triples; | |
| } | |
| static Error readMachOHeader(MachOObjectFile *Obj, RecordsSlice &Slice) { | |
| auto H = Obj->getHeader(); | |
| auto &BA = Slice.getBinaryAttrs(); | |
| switch (H.filetype) { | |
| default: | |
| llvm_unreachable("unsupported binary type"); | |
| case MachO::MH_DYLIB: | |
| BA.File = FileType::MachO_DynamicLibrary; | |
| break; | |
| case MachO::MH_DYLIB_STUB: | |
| BA.File = FileType::MachO_DynamicLibrary_Stub; | |
| break; | |
| case MachO::MH_BUNDLE: | |
| BA.File = FileType::MachO_Bundle; | |
| break; | |
| } | |
| if (H.flags & MachO::MH_TWOLEVEL) | |
| BA.TwoLevelNamespace = true; | |
| if (H.flags & MachO::MH_APP_EXTENSION_SAFE) | |
| BA.AppExtensionSafe = true; | |
| for (const auto &LCI : Obj->load_commands()) { | |
| switch (LCI.C.cmd) { | |
| case MachO::LC_ID_DYLIB: { | |
| auto DLLC = Obj->getDylibIDLoadCommand(LCI); | |
| BA.InstallName = Slice.copyString(LCI.Ptr + DLLC.dylib.name); | |
| BA.CurrentVersion = DLLC.dylib.current_version; | |
| BA.CompatVersion = DLLC.dylib.compatibility_version; | |
| break; | |
| } | |
| case MachO::LC_REEXPORT_DYLIB: { | |
| auto DLLC = Obj->getDylibIDLoadCommand(LCI); | |
| BA.RexportedLibraries.emplace_back( | |
| Slice.copyString(LCI.Ptr + DLLC.dylib.name)); | |
| break; | |
| } | |
| case MachO::LC_SUB_FRAMEWORK: { | |
| auto SFC = Obj->getSubFrameworkCommand(LCI); | |
| BA.ParentUmbrella = Slice.copyString(LCI.Ptr + SFC.umbrella); | |
| break; | |
| } | |
| case MachO::LC_SUB_CLIENT: { | |
| auto SCLC = Obj->getSubClientCommand(LCI); | |
| BA.AllowableClients.emplace_back(Slice.copyString(LCI.Ptr + SCLC.client)); | |
| break; | |
| } | |
| case MachO::LC_UUID: { | |
| auto UUIDLC = Obj->getUuidCommand(LCI); | |
| std::stringstream Stream; | |
| for (unsigned I = 0; I < 16; ++I) { | |
| if (I == 4 || I == 6 || I == 8 || I == 10) | |
| Stream << '-'; | |
| Stream << std::setfill('0') << std::setw(2) << std::uppercase | |
| << std::hex << static_cast<int>(UUIDLC.uuid[I]); | |
| } | |
| BA.UUID = Slice.copyString(Stream.str()); | |
| break; | |
| } | |
| case MachO::LC_RPATH: { | |
| auto RPLC = Obj->getRpathCommand(LCI); | |
| BA.RPaths.emplace_back(Slice.copyString(LCI.Ptr + RPLC.path)); | |
| break; | |
| } | |
| case MachO::LC_SEGMENT_SPLIT_INFO: { | |
| auto SSILC = Obj->getLinkeditDataLoadCommand(LCI); | |
| if (SSILC.datasize == 0) | |
| BA.OSLibNotForSharedCache = true; | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| for (auto &Sect : Obj->sections()) { | |
| auto SectName = Sect.getName(); | |
| if (!SectName) | |
| return SectName.takeError(); | |
| if (*SectName != "__objc_imageinfo" && *SectName != "__image_info") | |
| continue; | |
| auto Content = Sect.getContents(); | |
| if (!Content) | |
| return Content.takeError(); | |
| if ((Content->size() >= 8) && (Content->front() == 0)) { | |
| uint32_t Flags; | |
| if (Obj->isLittleEndian()) { | |
| auto *p = | |
| reinterpret_cast<const support::ulittle32_t *>(Content->data() + 4); | |
| Flags = *p; | |
| } else { | |
| auto *p = | |
| reinterpret_cast<const support::ubig32_t *>(Content->data() + 4); | |
| Flags = *p; | |
| } | |
| BA.SwiftABI = (Flags >> 8) & 0xFF; | |
| } | |
| } | |
| return Error::success(); | |
| } | |
| static Error readSymbols(MachOObjectFile *Obj, RecordsSlice &Slice, | |
| const ParseOption &Opt) { | |
| auto parseExport = [](const auto ExportFlags, | |
| auto Addr) -> std::tuple<SymbolFlags, RecordLinkage> { | |
| SymbolFlags Flags = SymbolFlags::None; | |
| switch (ExportFlags & MachO::EXPORT_SYMBOL_FLAGS_KIND_MASK) { | |
| case MachO::EXPORT_SYMBOL_FLAGS_KIND_REGULAR: | |
| if (ExportFlags & MachO::EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION) | |
| Flags |= SymbolFlags::WeakDefined; | |
| break; | |
| case MachO::EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: | |
| Flags |= SymbolFlags::ThreadLocalValue; | |
| break; | |
| } | |
| RecordLinkage Linkage = (ExportFlags & MachO::EXPORT_SYMBOL_FLAGS_REEXPORT) | |
| ? RecordLinkage::Rexported | |
| : RecordLinkage::Exported; | |
| return {Flags, Linkage}; | |
| }; | |
| Error Err = Error::success(); | |
| StringMap<std::pair<SymbolFlags, RecordLinkage>> Exports; | |
| // Collect symbols from export trie first. Sometimes, there are more exports | |
| // in the trie than in n-list due to stripping. This is common for swift | |
| // mangled symbols. | |
| for (auto &Sym : Obj->exports(Err)) { | |
| auto [Flags, Linkage] = parseExport(Sym.flags(), Sym.address()); | |
| Slice.addRecord(Sym.name(), Flags, GlobalRecord::Kind::Unknown, Linkage); | |
| Exports[Sym.name()] = {Flags, Linkage}; | |
| } | |
| for (const auto &Sym : Obj->symbols()) { | |
| auto FlagsOrErr = Sym.getFlags(); | |
| if (!FlagsOrErr) | |
| return FlagsOrErr.takeError(); | |
| auto Flags = *FlagsOrErr; | |
| auto NameOrErr = Sym.getName(); | |
| if (!NameOrErr) | |
| return NameOrErr.takeError(); | |
| auto Name = *NameOrErr; | |
| RecordLinkage Linkage = RecordLinkage::Unknown; | |
| SymbolFlags RecordFlags = SymbolFlags::None; | |
| if (Flags & SymbolRef::SF_Undefined) { | |
| if (Opt.Undefineds) | |
| Linkage = RecordLinkage::Undefined; | |
| else | |
| continue; | |
| if (Flags & SymbolRef::SF_Weak) | |
| RecordFlags |= SymbolFlags::WeakReferenced; | |
| } else if (Flags & SymbolRef::SF_Exported) { | |
| auto Exp = Exports.find(Name); | |
| // This should never be possible when binaries are produced with Apple | |
| // linkers. However it is possible to craft dylibs where the export trie | |
| // is either malformed or has conflicting symbols compared to n_list. | |
| if (Exp != Exports.end()) | |
| std::tie(RecordFlags, Linkage) = Exp->second; | |
| else | |
| Linkage = RecordLinkage::Exported; | |
| } else if (Flags & SymbolRef::SF_Hidden) { | |
| Linkage = RecordLinkage::Internal; | |
| } else | |
| continue; | |
| auto TypeOrErr = Sym.getType(); | |
| if (!TypeOrErr) | |
| return TypeOrErr.takeError(); | |
| auto Type = *TypeOrErr; | |
| GlobalRecord::Kind GV = (Type & SymbolRef::ST_Function) | |
| ? GlobalRecord::Kind::Function | |
| : GlobalRecord::Kind::Variable; | |
| if (GV == GlobalRecord::Kind::Function) | |
| RecordFlags |= SymbolFlags::Text; | |
| else | |
| RecordFlags |= SymbolFlags::Data; | |
| Slice.addRecord(Name, RecordFlags, GV, Linkage); | |
| } | |
| return Err; | |
| } | |
| static Error load(MachOObjectFile *Obj, RecordsSlice &Slice, | |
| const ParseOption &Opt, const Architecture Arch) { | |
| if (Arch == AK_unknown) | |
| return make_error<TextAPIError>(TextAPIErrorCode::UnsupportedTarget); | |
| if (Opt.MachOHeader) | |
| if (auto Err = readMachOHeader(Obj, Slice)) | |
| return Err; | |
| if (Opt.SymbolTable) | |
| if (auto Err = readSymbols(Obj, Slice, Opt)) | |
| return Err; | |
| return Error::success(); | |
| } | |
| Expected<Records> DylibReader::readFile(MemoryBufferRef Buffer, | |
| const ParseOption &Opt) { | |
| Records Results; | |
| auto BinOrErr = createBinary(Buffer); | |
| if (!BinOrErr) | |
| return BinOrErr.takeError(); | |
| Binary &Bin = *BinOrErr.get(); | |
| if (auto *Obj = dyn_cast<MachOObjectFile>(&Bin)) { | |
| const auto Arch = getArchitectureFromCpuType(Obj->getHeader().cputype, | |
| Obj->getHeader().cpusubtype); | |
| if (!Opt.Archs.has(Arch)) | |
| return make_error<TextAPIError>(TextAPIErrorCode::NoSuchArchitecture); | |
| auto Triples = constructTriples(Obj, Arch); | |
| for (const auto &T : Triples) { | |
| if (mapToPlatformType(T) == PLATFORM_UNKNOWN) | |
| return make_error<TextAPIError>(TextAPIErrorCode::UnsupportedTarget); | |
| Results.emplace_back(std::make_shared<RecordsSlice>(RecordsSlice({T}))); | |
| if (auto Err = load(Obj, *Results.back(), Opt, Arch)) | |
| return std::move(Err); | |
| Results.back()->getBinaryAttrs().Path = Buffer.getBufferIdentifier(); | |
| } | |
| return Results; | |
| } | |
| // Only expect MachO universal binaries at this point. | |
| assert(isa<MachOUniversalBinary>(&Bin) && | |
| "Expected a MachO universal binary."); | |
| auto *UB = cast<MachOUniversalBinary>(&Bin); | |
| for (auto OI = UB->begin_objects(), OE = UB->end_objects(); OI != OE; ++OI) { | |
| // Skip architecture if not requested. | |
| auto Arch = | |
| getArchitectureFromCpuType(OI->getCPUType(), OI->getCPUSubType()); | |
| if (!Opt.Archs.has(Arch)) | |
| continue; | |
| // Skip unknown architectures. | |
| if (Arch == AK_unknown) | |
| continue; | |
| // This can fail if the object is an archive. | |
| auto ObjOrErr = OI->getAsObjectFile(); | |
| // Skip the archive and consume the error. | |
| if (!ObjOrErr) { | |
| consumeError(ObjOrErr.takeError()); | |
| continue; | |
| } | |
| auto &Obj = *ObjOrErr.get(); | |
| switch (Obj.getHeader().filetype) { | |
| default: | |
| break; | |
| case MachO::MH_BUNDLE: | |
| case MachO::MH_DYLIB: | |
| case MachO::MH_DYLIB_STUB: | |
| for (const auto &T : constructTriples(&Obj, Arch)) { | |
| Results.emplace_back(std::make_shared<RecordsSlice>(RecordsSlice({T}))); | |
| if (auto Err = load(&Obj, *Results.back(), Opt, Arch)) | |
| return std::move(Err); | |
| Results.back()->getBinaryAttrs().Path = Buffer.getBufferIdentifier(); | |
| } | |
| break; | |
| } | |
| } | |
| if (Results.empty()) | |
| return make_error<TextAPIError>(TextAPIErrorCode::EmptyResults); | |
| return Results; | |
| } | |
| Expected<std::unique_ptr<InterfaceFile>> | |
| DylibReader::get(MemoryBufferRef Buffer) { | |
| ParseOption Options; | |
| auto SlicesOrErr = readFile(Buffer, Options); | |
| if (!SlicesOrErr) | |
| return SlicesOrErr.takeError(); | |
| return convertToInterfaceFile(*SlicesOrErr); | |
| } | |
| static void DWARFErrorHandler(Error Err) { /**/ } | |
| static SymbolToSourceLocMap | |
| accumulateLocs(MachOObjectFile &Obj, | |
| const std::unique_ptr<DWARFContext> &DiCtx) { | |
| SymbolToSourceLocMap LocMap; | |
| for (const auto &Symbol : Obj.symbols()) { | |
| Expected<uint32_t> FlagsOrErr = Symbol.getFlags(); | |
| if (!FlagsOrErr) { | |
| consumeError(FlagsOrErr.takeError()); | |
| continue; | |
| } | |
| if (!(*FlagsOrErr & SymbolRef::SF_Exported)) | |
| continue; | |
| Expected<uint64_t> AddressOrErr = Symbol.getAddress(); | |
| if (!AddressOrErr) { | |
| consumeError(AddressOrErr.takeError()); | |
| continue; | |
| } | |
| const uint64_t Address = *AddressOrErr; | |
| auto TypeOrErr = Symbol.getType(); | |
| if (!TypeOrErr) { | |
| consumeError(TypeOrErr.takeError()); | |
| continue; | |
| } | |
| const bool IsCode = (*TypeOrErr & SymbolRef::ST_Function); | |
| auto *DWARFCU = IsCode ? DiCtx->getCompileUnitForCodeAddress(Address) | |
| : DiCtx->getCompileUnitForDataAddress(Address); | |
| if (!DWARFCU) | |
| continue; | |
| const DWARFDie &DIE = IsCode ? DWARFCU->getSubroutineForAddress(Address) | |
| : DWARFCU->getVariableForAddress(Address); | |
| const std::string File = DIE.getDeclFile( | |
| llvm::DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath); | |
| const uint64_t Line = DIE.getDeclLine(); | |
| auto NameOrErr = Symbol.getName(); | |
| if (!NameOrErr) { | |
| consumeError(NameOrErr.takeError()); | |
| continue; | |
| } | |
| auto Name = *NameOrErr; | |
| auto Sym = parseSymbol(Name); | |
| if (!File.empty() && Line != 0) | |
| LocMap.insert({Sym.Name, RecordLoc(File, Line)}); | |
| } | |
| return LocMap; | |
| } | |
| SymbolToSourceLocMap | |
| DylibReader::accumulateSourceLocFromDSYM(const StringRef DSYM, | |
| const Target &T) { | |
| // Find sidecar file. | |
| auto DSYMsOrErr = MachOObjectFile::findDsymObjectMembers(DSYM); | |
| if (!DSYMsOrErr) { | |
| consumeError(DSYMsOrErr.takeError()); | |
| return SymbolToSourceLocMap(); | |
| } | |
| if (DSYMsOrErr->empty()) | |
| return SymbolToSourceLocMap(); | |
| const StringRef Path = DSYMsOrErr->front(); | |
| auto BufOrErr = MemoryBuffer::getFile(Path); | |
| if (auto Err = BufOrErr.getError()) | |
| return SymbolToSourceLocMap(); | |
| auto BinOrErr = createBinary(*BufOrErr.get()); | |
| if (!BinOrErr) { | |
| consumeError(BinOrErr.takeError()); | |
| return SymbolToSourceLocMap(); | |
| } | |
| // Handle single arch. | |
| if (auto *Single = dyn_cast<MachOObjectFile>(BinOrErr->get())) { | |
| auto DiCtx = DWARFContext::create( | |
| *Single, DWARFContext::ProcessDebugRelocations::Process, nullptr, "", | |
| DWARFErrorHandler, DWARFErrorHandler); | |
| return accumulateLocs(*Single, DiCtx); | |
| } | |
| // Handle universal companion file. | |
| if (auto *Fat = dyn_cast<MachOUniversalBinary>(BinOrErr->get())) { | |
| auto ObjForArch = Fat->getObjectForArch(getArchitectureName(T.Arch)); | |
| if (!ObjForArch) { | |
| consumeError(ObjForArch.takeError()); | |
| return SymbolToSourceLocMap(); | |
| } | |
| auto MachOOrErr = ObjForArch->getAsObjectFile(); | |
| if (!MachOOrErr) { | |
| consumeError(MachOOrErr.takeError()); | |
| return SymbolToSourceLocMap(); | |
| } | |
| auto &Obj = **MachOOrErr; | |
| auto DiCtx = DWARFContext::create( | |
| Obj, DWARFContext::ProcessDebugRelocations::Process, nullptr, "", | |
| DWARFErrorHandler, DWARFErrorHandler); | |
| return accumulateLocs(Obj, DiCtx); | |
| } | |
| return SymbolToSourceLocMap(); | |
| } |