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);
|
||
|
}
|