1//===----------------- MachO.cpp - MachO format utilities -----------------===//
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/ExecutionEngine/Orc/MachO.h"
10
11#include "llvm/ADT/ScopeExit.h"
12#include "llvm/BinaryFormat/MachO.h"
13#include "llvm/BinaryFormat/Magic.h"
14#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
15#include "llvm/ExecutionEngine/Orc/Layer.h"
16#include "llvm/Object/MachOUniversal.h"
17#include "llvm/Object/TapiUniversal.h"
18#include "llvm/Support/FileSystem.h"
19
20#define DEBUG_TYPE "orc"
21
22namespace llvm {
23namespace orc {
24
25static std::string objDesc(const MemoryBufferRef &Obj, const Triple &TT,
26 bool ObjIsSlice) {
27 std::string Desc;
28 if (ObjIsSlice)
29 Desc += (TT.getArchName() + " slice of universal binary ").str();
30 Desc += Obj.getBufferIdentifier();
31 return Desc;
32}
33
34template <typename HeaderType>
35static Error checkMachORelocatableObject(MemoryBufferRef Obj,
36 bool SwapEndianness, const Triple &TT,
37 bool ObjIsSlice) {
38 StringRef Data = Obj.getBuffer();
39
40 HeaderType Hdr;
41 memcpy(&Hdr, Data.data(), sizeof(HeaderType));
42
43 if (SwapEndianness)
44 swapStruct(Hdr);
45
46 if (Hdr.filetype != MachO::MH_OBJECT)
47 return make_error<StringError>(Args: objDesc(Obj, TT, ObjIsSlice) +
48 " is not a MachO relocatable object",
49 Args: inconvertibleErrorCode());
50
51 auto ObjArchTT =
52 object::MachOObjectFile::getArchTriple(Hdr.cputype, Hdr.cpusubtype);
53 if (ObjArchTT.getArch() != TT.getArch())
54 return make_error<StringError>(
55 objDesc(Obj, TT, ObjIsSlice) + " (" + ObjArchTT.getArchName() +
56 "), cannot be loaded into " + TT.str() + " process",
57 inconvertibleErrorCode());
58
59 return Error::success();
60}
61
62Error checkMachORelocatableObject(MemoryBufferRef Obj, const Triple &TT,
63 bool ObjIsSlice) {
64 StringRef Data = Obj.getBuffer();
65
66 if (Data.size() < 4)
67 return make_error<StringError>(
68 Args: objDesc(Obj, TT, ObjIsSlice) +
69 " is not a valid MachO relocatable object file (truncated header)",
70 Args: inconvertibleErrorCode());
71
72 uint32_t Magic;
73 memcpy(dest: &Magic, src: Data.data(), n: sizeof(uint32_t));
74
75 switch (Magic) {
76 case MachO::MH_MAGIC:
77 case MachO::MH_CIGAM:
78 return checkMachORelocatableObject<MachO::mach_header>(
79 Obj: std::move(Obj), SwapEndianness: Magic == MachO::MH_CIGAM, TT, ObjIsSlice);
80 case MachO::MH_MAGIC_64:
81 case MachO::MH_CIGAM_64:
82 return checkMachORelocatableObject<MachO::mach_header_64>(
83 Obj: std::move(Obj), SwapEndianness: Magic == MachO::MH_CIGAM_64, TT, ObjIsSlice);
84 default:
85 return make_error<StringError>(
86 Args: objDesc(Obj, TT, ObjIsSlice) +
87 " is not a valid MachO relocatable object (bad magic value)",
88 Args: inconvertibleErrorCode());
89 }
90}
91
92Expected<std::unique_ptr<MemoryBuffer>>
93checkMachORelocatableObject(std::unique_ptr<MemoryBuffer> Obj, const Triple &TT,
94 bool ObjIsSlice) {
95 if (auto Err =
96 checkMachORelocatableObject(Obj: Obj->getMemBufferRef(), TT, ObjIsSlice))
97 return std::move(Err);
98 return std::move(Obj);
99}
100
101Expected<std::pair<std::unique_ptr<MemoryBuffer>, LinkableFileKind>>
102loadMachORelocatableObject(StringRef Path, const Triple &TT, LoadArchives LA,
103 std::optional<StringRef> IdentifierOverride) {
104 assert((TT.getObjectFormat() == Triple::UnknownObjectFormat ||
105 TT.getObjectFormat() == Triple::MachO) &&
106 "TT must specify MachO or Unknown object format");
107
108 if (!IdentifierOverride)
109 IdentifierOverride = Path;
110
111 Expected<sys::fs::file_t> FDOrErr =
112 sys::fs::openNativeFileForRead(Name: Path, Flags: sys::fs::OF_None);
113 if (!FDOrErr)
114 return createFileError(F: Path, E: FDOrErr.takeError());
115 sys::fs::file_t FD = *FDOrErr;
116 llvm::scope_exit CloseFile([&]() { sys::fs::closeFile(F&: FD); });
117
118 auto Buf =
119 MemoryBuffer::getOpenFile(FD, Filename: *IdentifierOverride, /*FileSize=*/-1);
120 if (!Buf)
121 return make_error<StringError>(
122 Args: StringRef("Could not load MachO object at path ") + Path,
123 Args: Buf.getError());
124
125 switch (identify_magic(magic: (*Buf)->getBuffer())) {
126 case file_magic::macho_object: {
127 auto CheckedObj = checkMachORelocatableObject(Obj: std::move(*Buf), TT, ObjIsSlice: false);
128 if (!CheckedObj)
129 return CheckedObj.takeError();
130 return std::make_pair(x: std::move(*CheckedObj),
131 y: LinkableFileKind::RelocatableObject);
132 }
133 case file_magic::macho_universal_binary:
134 return loadLinkableSliceFromMachOUniversalBinary(FD, UBBuf: std::move(*Buf), TT,
135 LA: LoadArchives::Never, UBPath: Path,
136 Identifier: *IdentifierOverride);
137 default:
138 return make_error<StringError>(
139 Args: Path + " does not contain a relocatable object file compatible with " +
140 TT.str(),
141 Args: inconvertibleErrorCode());
142 }
143}
144
145Expected<std::pair<std::unique_ptr<MemoryBuffer>, LinkableFileKind>>
146loadLinkableSliceFromMachOUniversalBinary(sys::fs::file_t FD,
147 std::unique_ptr<MemoryBuffer> UBBuf,
148 const Triple &TT, LoadArchives LA,
149 StringRef UBPath,
150 StringRef Identifier) {
151
152 auto UniversalBin =
153 object::MachOUniversalBinary::create(Source: UBBuf->getMemBufferRef());
154 if (!UniversalBin)
155 return UniversalBin.takeError();
156
157 auto SliceRange = getMachOSliceRangeForTriple(UB&: **UniversalBin, TT);
158 if (!SliceRange)
159 return SliceRange.takeError();
160
161 auto Buf = MemoryBuffer::getOpenFileSlice(FD, Filename: Identifier, MapSize: SliceRange->second,
162 Offset: SliceRange->first);
163 if (!Buf)
164 return make_error<StringError>(
165 Args: "Could not load " + TT.getArchName() +
166 " slice of MachO universal binary at path " + UBPath,
167 Args: Buf.getError());
168
169 switch (identify_magic(magic: (*Buf)->getBuffer())) {
170 case file_magic::archive:
171 if (LA != LoadArchives::Never)
172 return std::make_pair(x: std::move(*Buf), y: LinkableFileKind::Archive);
173 break;
174 case file_magic::macho_object: {
175 if (LA != LoadArchives::Required) {
176 auto CheckedObj = checkMachORelocatableObject(Obj: std::move(*Buf), TT, ObjIsSlice: true);
177 if (!CheckedObj)
178 return CheckedObj.takeError();
179 return std::make_pair(x: std::move(*CheckedObj),
180 y: LinkableFileKind::RelocatableObject);
181 }
182 break;
183 }
184 default:
185 break;
186 }
187
188 auto FT = [&] {
189 switch (LA) {
190 case LoadArchives::Never:
191 return "a mach-o relocatable object file";
192 case LoadArchives::Allowed:
193 return "a mach-o relocatable object file or archive";
194 case LoadArchives::Required:
195 return "an archive";
196 }
197 llvm_unreachable("Unknown LoadArchives enum");
198 };
199
200 return make_error<StringError>(Args: TT.getArchName() + " slice of " + UBPath +
201 " does not contain " + FT(),
202 Args: inconvertibleErrorCode());
203}
204
205Expected<std::pair<size_t, size_t>>
206getMachOSliceRangeForTriple(object::MachOUniversalBinary &UB,
207 const Triple &TT) {
208
209 for (const auto &Obj : UB.objects()) {
210 auto ObjTT = Obj.getTriple();
211 if (ObjTT.getArch() == TT.getArch() &&
212 ObjTT.getSubArch() == TT.getSubArch() &&
213 (TT.getVendor() == Triple::UnknownVendor ||
214 ObjTT.getVendor() == TT.getVendor())) {
215 // We found a match. Return the range for the slice.
216 return std::make_pair(x: Obj.getOffset(), y: Obj.getSize());
217 }
218 }
219
220 return make_error<StringError>(Args: Twine("Universal binary ") + UB.getFileName() +
221 " does not contain a slice for " +
222 TT.str(),
223 Args: inconvertibleErrorCode());
224}
225
226Expected<std::pair<size_t, size_t>>
227getMachOSliceRangeForTriple(MemoryBufferRef UBBuf, const Triple &TT) {
228
229 auto UB = object::MachOUniversalBinary::create(Source: UBBuf);
230 if (!UB)
231 return UB.takeError();
232
233 return getMachOSliceRangeForTriple(UB&: **UB, TT);
234}
235
236Expected<bool> ForceLoadMachOArchiveMembers::operator()(
237 object::Archive &A, MemoryBufferRef MemberBuf, size_t Index) {
238
239 auto LoadMember = [&]() {
240 return StaticLibraryDefinitionGenerator::createMemberBuffer(A, BufRef: MemberBuf,
241 Index);
242 };
243
244 if (!ObjCOnly) {
245 // If we're loading all files then just load the buffer immediately. Return
246 // false to indicate that there's no further loading to do here.
247 if (auto Err = L.add(JD, O: LoadMember()))
248 return Err;
249 return false;
250 }
251
252 // We need to check whether this archive member contains any Objective-C
253 // or Swift metadata.
254 auto Obj = object::ObjectFile::createObjectFile(Object: MemberBuf);
255 if (!Obj) {
256 // Invalid files are not loadable, but don't invalidate the archive.
257 consumeError(Err: Obj.takeError());
258 return false;
259 }
260
261 if (auto *MachOObj = dyn_cast<object::MachOObjectFile>(Val: &**Obj)) {
262 // Load the object if any recognized special section is present.
263 for (auto Sec : MachOObj->sections()) {
264 auto SegName =
265 MachOObj->getSectionFinalSegmentName(Sec: Sec.getRawDataRefImpl());
266 if (auto SecName = Sec.getName()) {
267 if (*SecName == "__objc_classlist" || *SecName == "__objc_protolist" ||
268 *SecName == "__objc_clsrolist" || *SecName == "__objc_catlist" ||
269 *SecName == "__objc_catlist2" || *SecName == "__objc_nlcatlist" ||
270 (SegName == "__TEXT" && (*SecName).starts_with(Prefix: "__swift") &&
271 *SecName != "__swift_modhash")) {
272 if (auto Err = L.add(JD, O: LoadMember()))
273 return Err;
274 return false;
275 }
276 } else
277 return SecName.takeError();
278 }
279 }
280
281 // This is an object file but we didn't load it, so return true to indicate
282 // that it's still loadable.
283 return true;
284}
285
286SmallVector<std::pair<uint32_t, uint32_t>>
287noFallbackArchs(uint32_t CPUType, uint32_t CPUSubType) {
288 SmallVector<std::pair<uint32_t, uint32_t>> Result;
289 Result.push_back(Elt: {CPUType, CPUSubType});
290 return Result;
291}
292
293SmallVector<std::pair<uint32_t, uint32_t>>
294standardMachOFallbackArchs(uint32_t CPUType, uint32_t CPUSubType) {
295 SmallVector<std::pair<uint32_t, uint32_t>> Archs;
296
297 // Match given CPU type/subtype first.
298 Archs.push_back(Elt: {CPUType, CPUSubType});
299
300 switch (CPUType) {
301 case MachO::CPU_TYPE_ARM64:
302 // Handle arm64 variants.
303 switch (CPUSubType) {
304 case MachO::CPU_SUBTYPE_ARM64_ALL:
305 Archs.push_back(Elt: {CPUType, MachO::CPU_SUBTYPE_ARM64E});
306 break;
307 default:
308 break;
309 }
310 break;
311 default:
312 break;
313 }
314
315 return Archs;
316}
317
318Expected<SymbolNameSet>
319getDylibInterfaceFromDylib(ExecutionSession &ES, Twine Path,
320 GetFallbackArchsFn GetFallbackArchs) {
321 auto InitCPUType = MachO::getCPUType(T: ES.getTargetTriple());
322 if (!InitCPUType)
323 return InitCPUType.takeError();
324
325 auto InitCPUSubType = MachO::getCPUSubType(T: ES.getTargetTriple());
326 if (!InitCPUSubType)
327 return InitCPUSubType.takeError();
328
329 auto Buf = MemoryBuffer::getFile(Filename: Path);
330 if (!Buf)
331 return createFileError(F: Path, EC: Buf.getError());
332
333 auto BinFile = object::createBinary(Source: (*Buf)->getMemBufferRef());
334 if (!BinFile)
335 return BinFile.takeError();
336
337 std::unique_ptr<object::MachOObjectFile> MachOFile;
338 if (isa<object::MachOObjectFile>(Val: **BinFile)) {
339 MachOFile.reset(p: dyn_cast<object::MachOObjectFile>(Val: BinFile->release()));
340
341 // TODO: Check that dylib arch is compatible.
342 } else if (auto *MachOUni =
343 dyn_cast<object::MachOUniversalBinary>(Val: BinFile->get())) {
344 SmallVector<std::pair<uint32_t, uint32_t>> ArchsToTry;
345 if (GetFallbackArchs)
346 ArchsToTry = GetFallbackArchs(*InitCPUType, *InitCPUSubType);
347 else
348 ArchsToTry.push_back(Elt: {*InitCPUType, *InitCPUSubType});
349
350 for (auto &[CPUType, CPUSubType] : ArchsToTry) {
351 for (auto &O : MachOUni->objects()) {
352 if (O.getCPUType() == CPUType &&
353 (O.getCPUSubType() & ~MachO::CPU_SUBTYPE_MASK) == CPUSubType) {
354 if (auto Obj = O.getAsObjectFile())
355 MachOFile = std::move(*Obj);
356 else
357 return Obj.takeError();
358 break;
359 }
360 }
361 if (MachOFile) // If found, break out.
362 break;
363 }
364 if (!MachOFile)
365 return make_error<StringError>(
366 Args: "MachO universal binary at " + Path +
367 " does not contain a compatible slice for " +
368 ES.getTargetTriple().str(),
369 Args: inconvertibleErrorCode());
370 } else
371 return make_error<StringError>(Args: "File at " + Path + " is not a MachO",
372 Args: inconvertibleErrorCode());
373
374 if (MachOFile->getHeader().filetype != MachO::MH_DYLIB)
375 return make_error<StringError>(Args: "MachO at " + Path + " is not a dylib",
376 Args: inconvertibleErrorCode());
377
378 SymbolNameSet Symbols;
379 for (auto &Sym : MachOFile->symbols()) {
380 if (auto Name = Sym.getName())
381 Symbols.insert(V: ES.intern(SymName: *Name));
382 else
383 return Name.takeError();
384 }
385
386 return std::move(Symbols);
387}
388
389Expected<SymbolNameSet>
390getDylibInterfaceFromTapiFile(ExecutionSession &ES, Twine Path,
391 GetFallbackArchsFn GetFallbackArchs) {
392 SymbolNameSet Symbols;
393
394 auto TapiFileBuffer = MemoryBuffer::getFile(Filename: Path);
395 if (!TapiFileBuffer)
396 return createFileError(F: Path, EC: TapiFileBuffer.getError());
397
398 auto Tapi =
399 object::TapiUniversal::create(Source: (*TapiFileBuffer)->getMemBufferRef());
400 if (!Tapi)
401 return Tapi.takeError();
402
403 auto InitCPUType = MachO::getCPUType(T: ES.getTargetTriple());
404 if (!InitCPUType)
405 return InitCPUType.takeError();
406
407 auto InitCPUSubType = MachO::getCPUSubType(T: ES.getTargetTriple());
408 if (!InitCPUSubType)
409 return InitCPUSubType.takeError();
410
411 SmallVector<std::pair<uint32_t, uint32_t>> ArchsToTry;
412 if (GetFallbackArchs)
413 ArchsToTry = GetFallbackArchs(*InitCPUType, *InitCPUSubType);
414 else
415 ArchsToTry.push_back(Elt: {*InitCPUType, *InitCPUSubType});
416
417 auto &IF = (*Tapi)->getInterfaceFile();
418
419 auto ArchSet = IF.getArchitectures();
420 for (auto [CPUType, CPUSubType] : ArchsToTry) {
421 auto A = MachO::getArchitectureFromCpuType(CPUType, CPUSubType);
422 if (ArchSet.has(Arch: A)) {
423 if (auto Interface = IF.extract(Arch: A)) {
424 for (auto *Sym : (*Interface)->exports())
425 Symbols.insert(V: ES.intern(SymName: Sym->getName()));
426 return Symbols;
427 } else
428 return Interface.takeError();
429 }
430 }
431
432 return make_error<StringError>(
433 Args: "MachO interface file at " + Path +
434 " does not contain a compatible slice for " +
435 ES.getTargetTriple().str(),
436 Args: inconvertibleErrorCode());
437}
438
439Expected<SymbolNameSet> getDylibInterface(ExecutionSession &ES, Twine Path,
440 GetFallbackArchsFn GetFallbackArchs) {
441 file_magic Magic;
442 if (auto EC = identify_magic(path: Path, result&: Magic))
443 return createFileError(F: Path, EC);
444
445 switch (Magic) {
446 case file_magic::macho_universal_binary:
447 case file_magic::macho_dynamically_linked_shared_lib:
448 return getDylibInterfaceFromDylib(ES, Path, GetFallbackArchs: std::move(GetFallbackArchs));
449 case file_magic::tapi_file:
450 return getDylibInterfaceFromTapiFile(ES, Path, GetFallbackArchs: std::move(GetFallbackArchs));
451 default:
452 return make_error<StringError>(Args: "Cannot get interface for " + Path +
453 " unrecognized file type",
454 Args: inconvertibleErrorCode());
455 }
456}
457
458} // End namespace orc.
459} // End namespace llvm.
460