blob: 6b0f96da19dc6294ead1163b7fe29f488929e43a [file] [log] [blame]
//===----------------- MachO.cpp - MachO format utilities -----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "llvm/ExecutionEngine/Orc/MachO.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/Layer.h"
#include "llvm/Object/MachOUniversal.h"
#include "llvm/Object/TapiUniversal.h"
#include "llvm/Support/FileSystem.h"
#define DEBUG_TYPE "orc"
namespace llvm {
namespace orc {
static std::string objDesc(const MemoryBufferRef &Obj, const Triple &TT,
bool ObjIsSlice) {
std::string Desc;
if (ObjIsSlice)
Desc += (TT.getArchName() + " slice of universal binary").str();
Desc += Obj.getBufferIdentifier();
return Desc;
}
template <typename HeaderType>
static Error checkMachORelocatableObject(MemoryBufferRef Obj,
bool SwapEndianness, const Triple &TT,
bool ObjIsSlice) {
StringRef Data = Obj.getBuffer();
HeaderType Hdr;
memcpy(&Hdr, Data.data(), sizeof(HeaderType));
if (SwapEndianness)
swapStruct(Hdr);
if (Hdr.filetype != MachO::MH_OBJECT)
return make_error<StringError>(objDesc(Obj, TT, ObjIsSlice) +
" is not a MachO relocatable object",
inconvertibleErrorCode());
auto ObjArch = object::MachOObjectFile::getArch(Hdr.cputype, Hdr.cpusubtype);
if (ObjArch != TT.getArch())
return make_error<StringError>(
objDesc(Obj, TT, ObjIsSlice) + Triple::getArchTypeName(ObjArch) +
", cannot be loaded into " + TT.str() + " process",
inconvertibleErrorCode());
return Error::success();
}
Error checkMachORelocatableObject(MemoryBufferRef Obj, const Triple &TT,
bool ObjIsSlice) {
StringRef Data = Obj.getBuffer();
if (Data.size() < 4)
return make_error<StringError>(
objDesc(Obj, TT, ObjIsSlice) +
" is not a valid MachO relocatable object file (truncated header)",
inconvertibleErrorCode());
uint32_t Magic;
memcpy(&Magic, Data.data(), sizeof(uint32_t));
switch (Magic) {
case MachO::MH_MAGIC:
case MachO::MH_CIGAM:
return checkMachORelocatableObject<MachO::mach_header>(
std::move(Obj), Magic == MachO::MH_CIGAM, TT, ObjIsSlice);
case MachO::MH_MAGIC_64:
case MachO::MH_CIGAM_64:
return checkMachORelocatableObject<MachO::mach_header_64>(
std::move(Obj), Magic == MachO::MH_CIGAM_64, TT, ObjIsSlice);
default:
return make_error<StringError>(
objDesc(Obj, TT, ObjIsSlice) +
" is not a valid MachO relocatable object (bad magic value)",
inconvertibleErrorCode());
}
}
Expected<std::unique_ptr<MemoryBuffer>>
checkMachORelocatableObject(std::unique_ptr<MemoryBuffer> Obj, const Triple &TT,
bool ObjIsSlice) {
if (auto Err =
checkMachORelocatableObject(Obj->getMemBufferRef(), TT, ObjIsSlice))
return std::move(Err);
return std::move(Obj);
}
Expected<std::pair<std::unique_ptr<MemoryBuffer>, LinkableFileKind>>
loadMachORelocatableObject(StringRef Path, const Triple &TT, LoadArchives LA,
std::optional<StringRef> IdentifierOverride) {
assert((TT.getObjectFormat() == Triple::UnknownObjectFormat ||
TT.getObjectFormat() == Triple::MachO) &&
"TT must specify MachO or Unknown object format");
if (!IdentifierOverride)
IdentifierOverride = Path;
Expected<sys::fs::file_t> FDOrErr =
sys::fs::openNativeFileForRead(Path, sys::fs::OF_None);
if (!FDOrErr)
return createFileError(Path, FDOrErr.takeError());
sys::fs::file_t FD = *FDOrErr;
auto CloseFile = make_scope_exit([&]() { sys::fs::closeFile(FD); });
auto Buf =
MemoryBuffer::getOpenFile(FD, *IdentifierOverride, /*FileSize=*/-1);
if (!Buf)
return make_error<StringError>(
StringRef("Could not load MachO object at path ") + Path,
Buf.getError());
switch (identify_magic((*Buf)->getBuffer())) {
case file_magic::macho_object: {
auto CheckedObj = checkMachORelocatableObject(std::move(*Buf), TT, false);
if (!CheckedObj)
return CheckedObj.takeError();
return std::make_pair(std::move(*CheckedObj),
LinkableFileKind::RelocatableObject);
}
case file_magic::macho_universal_binary:
return loadLinkableSliceFromMachOUniversalBinary(FD, std::move(*Buf), TT,
LoadArchives::Never, Path,
*IdentifierOverride);
default:
return make_error<StringError>(
Path + " does not contain a relocatable object file compatible with " +
TT.str(),
inconvertibleErrorCode());
}
}
Expected<std::pair<std::unique_ptr<MemoryBuffer>, LinkableFileKind>>
loadLinkableSliceFromMachOUniversalBinary(sys::fs::file_t FD,
std::unique_ptr<MemoryBuffer> UBBuf,
const Triple &TT, LoadArchives LA,
StringRef UBPath,
StringRef Identifier) {
auto UniversalBin =
object::MachOUniversalBinary::create(UBBuf->getMemBufferRef());
if (!UniversalBin)
return UniversalBin.takeError();
auto SliceRange = getMachOSliceRangeForTriple(**UniversalBin, TT);
if (!SliceRange)
return SliceRange.takeError();
auto Buf = MemoryBuffer::getOpenFileSlice(FD, Identifier, SliceRange->second,
SliceRange->first);
if (!Buf)
return make_error<StringError>(
"Could not load " + TT.getArchName() +
" slice of MachO universal binary at path " + UBPath,
Buf.getError());
switch (identify_magic((*Buf)->getBuffer())) {
case file_magic::archive:
if (LA != LoadArchives::Never)
return std::make_pair(std::move(*Buf), LinkableFileKind::Archive);
break;
case file_magic::macho_object: {
if (LA != LoadArchives::Required) {
auto CheckedObj = checkMachORelocatableObject(std::move(*Buf), TT, true);
if (!CheckedObj)
return CheckedObj.takeError();
return std::make_pair(std::move(*CheckedObj),
LinkableFileKind::RelocatableObject);
}
break;
}
default:
break;
}
auto FT = [&] {
switch (LA) {
case LoadArchives::Never:
return "a mach-o relocatable object file";
case LoadArchives::Allowed:
return "a mach-o relocatable object file or archive";
case LoadArchives::Required:
return "an archive";
}
llvm_unreachable("Unknown LoadArchives enum");
};
return make_error<StringError>(TT.getArchName() + " slice of " + UBPath +
" does not contain " + FT(),
inconvertibleErrorCode());
}
Expected<std::pair<size_t, size_t>>
getMachOSliceRangeForTriple(object::MachOUniversalBinary &UB,
const Triple &TT) {
for (const auto &Obj : UB.objects()) {
auto ObjTT = Obj.getTriple();
if (ObjTT.getArch() == TT.getArch() &&
ObjTT.getSubArch() == TT.getSubArch() &&
(TT.getVendor() == Triple::UnknownVendor ||
ObjTT.getVendor() == TT.getVendor())) {
// We found a match. Return the range for the slice.
return std::make_pair(Obj.getOffset(), Obj.getSize());
}
}
return make_error<StringError>(Twine("Universal binary ") + UB.getFileName() +
" does not contain a slice for " +
TT.str(),
inconvertibleErrorCode());
}
Expected<std::pair<size_t, size_t>>
getMachOSliceRangeForTriple(MemoryBufferRef UBBuf, const Triple &TT) {
auto UB = object::MachOUniversalBinary::create(UBBuf);
if (!UB)
return UB.takeError();
return getMachOSliceRangeForTriple(**UB, TT);
}
Expected<bool> ForceLoadMachOArchiveMembers::operator()(
object::Archive &A, MemoryBufferRef MemberBuf, size_t Index) {
auto LoadMember = [&]() {
return StaticLibraryDefinitionGenerator::createMemberBuffer(A, MemberBuf,
Index);
};
if (!ObjCOnly) {
// If we're loading all files then just load the buffer immediately. Return
// false to indicate that there's no further loading to do here.
if (auto Err = L.add(JD, LoadMember()))
return Err;
return false;
}
// We need to check whether this archive member contains any Objective-C
// or Swift metadata.
auto Obj = object::ObjectFile::createObjectFile(MemberBuf);
if (!Obj) {
// Invalid files are not loadable, but don't invalidate the archive.
consumeError(Obj.takeError());
return false;
}
if (auto *MachOObj = dyn_cast<object::MachOObjectFile>(&**Obj)) {
// Load the object if any recognized special section is present.
for (auto Sec : MachOObj->sections()) {
auto SegName =
MachOObj->getSectionFinalSegmentName(Sec.getRawDataRefImpl());
if (auto SecName = Sec.getName()) {
if (*SecName == "__objc_classlist" || *SecName == "__objc_protolist" ||
*SecName == "__objc_clsrolist" || *SecName == "__objc_catlist" ||
*SecName == "__objc_catlist2" || *SecName == "__objc_nlcatlist" ||
(SegName == "__TEXT" && (*SecName).starts_with("__swift") &&
*SecName != "__swift_modhash")) {
if (auto Err = L.add(JD, LoadMember()))
return Err;
return false;
}
} else
return SecName.takeError();
}
}
// This is an object file but we didn't load it, so return true to indicate
// that it's still loadable.
return true;
}
SmallVector<std::pair<uint32_t, uint32_t>>
noFallbackArchs(uint32_t CPUType, uint32_t CPUSubType) {
SmallVector<std::pair<uint32_t, uint32_t>> Result;
Result.push_back({CPUType, CPUSubType});
return Result;
}
SmallVector<std::pair<uint32_t, uint32_t>>
standardMachOFallbackArchs(uint32_t CPUType, uint32_t CPUSubType) {
SmallVector<std::pair<uint32_t, uint32_t>> Archs;
// Match given CPU type/subtype first.
Archs.push_back({CPUType, CPUSubType});
switch (CPUType) {
case MachO::CPU_TYPE_ARM64:
// Handle arm64 variants.
switch (CPUSubType) {
case MachO::CPU_SUBTYPE_ARM64_ALL:
Archs.push_back({CPUType, MachO::CPU_SUBTYPE_ARM64E});
break;
default:
break;
}
break;
default:
break;
}
return Archs;
}
Expected<SymbolNameSet>
getDylibInterfaceFromDylib(ExecutionSession &ES, Twine Path,
GetFallbackArchsFn GetFallbackArchs) {
auto InitCPUType = MachO::getCPUType(ES.getTargetTriple());
if (!InitCPUType)
return InitCPUType.takeError();
auto InitCPUSubType = MachO::getCPUSubType(ES.getTargetTriple());
if (!InitCPUSubType)
return InitCPUSubType.takeError();
auto Buf = MemoryBuffer::getFile(Path);
if (!Buf)
return createFileError(Path, Buf.getError());
auto BinFile = object::createBinary((*Buf)->getMemBufferRef());
if (!BinFile)
return BinFile.takeError();
std::unique_ptr<object::MachOObjectFile> MachOFile;
if (isa<object::MachOObjectFile>(**BinFile)) {
MachOFile.reset(dyn_cast<object::MachOObjectFile>(BinFile->release()));
// TODO: Check that dylib arch is compatible.
} else if (auto *MachOUni =
dyn_cast<object::MachOUniversalBinary>(BinFile->get())) {
SmallVector<std::pair<uint32_t, uint32_t>> ArchsToTry;
if (GetFallbackArchs)
ArchsToTry = GetFallbackArchs(*InitCPUType, *InitCPUSubType);
else
ArchsToTry.push_back({*InitCPUType, *InitCPUSubType});
for (auto &[CPUType, CPUSubType] : ArchsToTry) {
for (auto &O : MachOUni->objects()) {
if (O.getCPUType() == CPUType &&
(O.getCPUSubType() & ~MachO::CPU_SUBTYPE_MASK) == CPUSubType) {
if (auto Obj = O.getAsObjectFile())
MachOFile = std::move(*Obj);
else
return Obj.takeError();
break;
}
}
if (MachOFile) // If found, break out.
break;
}
if (!MachOFile)
return make_error<StringError>(
"MachO universal binary at " + Path +
" does not contain a compatible slice for " +
ES.getTargetTriple().str(),
inconvertibleErrorCode());
} else
return make_error<StringError>("File at " + Path + " is not a MachO",
inconvertibleErrorCode());
if (MachOFile->getHeader().filetype != MachO::MH_DYLIB)
return make_error<StringError>("MachO at " + Path + " is not a dylib",
inconvertibleErrorCode());
SymbolNameSet Symbols;
for (auto &Sym : MachOFile->symbols()) {
if (auto Name = Sym.getName())
Symbols.insert(ES.intern(*Name));
else
return Name.takeError();
}
return std::move(Symbols);
}
Expected<SymbolNameSet>
getDylibInterfaceFromTapiFile(ExecutionSession &ES, Twine Path,
GetFallbackArchsFn GetFallbackArchs) {
SymbolNameSet Symbols;
auto TapiFileBuffer = MemoryBuffer::getFile(Path);
if (!TapiFileBuffer)
return createFileError(Path, TapiFileBuffer.getError());
auto Tapi =
object::TapiUniversal::create((*TapiFileBuffer)->getMemBufferRef());
if (!Tapi)
return Tapi.takeError();
auto InitCPUType = MachO::getCPUType(ES.getTargetTriple());
if (!InitCPUType)
return InitCPUType.takeError();
auto InitCPUSubType = MachO::getCPUSubType(ES.getTargetTriple());
if (!InitCPUSubType)
return InitCPUSubType.takeError();
SmallVector<std::pair<uint32_t, uint32_t>> ArchsToTry;
if (GetFallbackArchs)
ArchsToTry = GetFallbackArchs(*InitCPUType, *InitCPUSubType);
else
ArchsToTry.push_back({*InitCPUType, *InitCPUSubType});
auto &IF = (*Tapi)->getInterfaceFile();
auto ArchSet = IF.getArchitectures();
for (auto [CPUType, CPUSubType] : ArchsToTry) {
auto A = MachO::getArchitectureFromCpuType(CPUType, CPUSubType);
if (ArchSet.has(A)) {
if (auto Interface = IF.extract(A)) {
for (auto *Sym : (*Interface)->exports())
Symbols.insert(ES.intern(Sym->getName()));
return Symbols;
} else
return Interface.takeError();
}
}
return make_error<StringError>(
"MachO interface file at " + Path +
" does not contain a compatible slice for " +
ES.getTargetTriple().str(),
inconvertibleErrorCode());
}
Expected<SymbolNameSet> getDylibInterface(ExecutionSession &ES, Twine Path,
GetFallbackArchsFn GetFallbackArchs) {
file_magic Magic;
if (auto EC = identify_magic(Path, Magic))
return createFileError(Path, EC);
switch (Magic) {
case file_magic::macho_universal_binary:
case file_magic::macho_dynamically_linked_shared_lib:
return getDylibInterfaceFromDylib(ES, Path, std::move(GetFallbackArchs));
case file_magic::tapi_file:
return getDylibInterfaceFromTapiFile(ES, Path, std::move(GetFallbackArchs));
default:
return make_error<StringError>("Cannot get interface for " + Path +
" unrecognized file type",
inconvertibleErrorCode());
}
}
} // End namespace orc.
} // End namespace llvm.