
Introduce RecordVisitor. This is used for different clients that want to extract information out of RecordSlice types. The first and immediate use case is for serializing symbol information into TBD files.
430 lines
14 KiB
C++
430 lines
14 KiB
C++
//===- 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/ADT/StringMap.h"
|
|
#include "llvm/Object/Binary.h"
|
|
#include "llvm/Object/MachOUniversal.h"
|
|
#include "llvm/Support/Endian.h"
|
|
#include "llvm/TargetParser/Triple.h"
|
|
#include "llvm/TextAPI/RecordsSlice.h"
|
|
#include "llvm/TextAPI/TextAPIError.h"
|
|
#include <iomanip>
|
|
#include <set>
|
|
#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 typename 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 (Opt.Undefineds && (Flags & SymbolRef::SF_Undefined)) {
|
|
Linkage = RecordLinkage::Undefined;
|
|
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);
|
|
}
|
|
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);
|
|
}
|