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/ExecutionEngine/Orc/ExecutionUtils.h"
14#include "llvm/ExecutionEngine/Orc/Layer.h"
15#include "llvm/Object/MachOUniversal.h"
16#include "llvm/Support/FileSystem.h"
17
18#define DEBUG_TYPE "orc"
19
20namespace llvm {
21namespace orc {
22
23static std::string objDesc(const MemoryBufferRef &Obj, const Triple &TT,
24 bool ObjIsSlice) {
25 std::string Desc;
26 if (ObjIsSlice)
27 Desc += (TT.getArchName() + " slice of universal binary").str();
28 Desc += Obj.getBufferIdentifier();
29 return Desc;
30}
31
32template <typename HeaderType>
33static Error checkMachORelocatableObject(MemoryBufferRef Obj,
34 bool SwapEndianness, const Triple &TT,
35 bool ObjIsSlice) {
36 StringRef Data = Obj.getBuffer();
37
38 HeaderType Hdr;
39 memcpy(&Hdr, Data.data(), sizeof(HeaderType));
40
41 if (SwapEndianness)
42 swapStruct(Hdr);
43
44 if (Hdr.filetype != MachO::MH_OBJECT)
45 return make_error<StringError>(Args: objDesc(Obj, TT, ObjIsSlice) +
46 " is not a MachO relocatable object",
47 Args: inconvertibleErrorCode());
48
49 auto ObjArch = object::MachOObjectFile::getArch(Hdr.cputype, Hdr.cpusubtype);
50 if (ObjArch != TT.getArch())
51 return make_error<StringError>(
52 objDesc(Obj, TT, ObjIsSlice) + Triple::getArchTypeName(Kind: ObjArch) +
53 ", cannot be loaded into " + TT.str() + " process",
54 inconvertibleErrorCode());
55
56 return Error::success();
57}
58
59Error checkMachORelocatableObject(MemoryBufferRef Obj, const Triple &TT,
60 bool ObjIsSlice) {
61 StringRef Data = Obj.getBuffer();
62
63 if (Data.size() < 4)
64 return make_error<StringError>(
65 Args: objDesc(Obj, TT, ObjIsSlice) +
66 " is not a valid MachO relocatable object file (truncated header)",
67 Args: inconvertibleErrorCode());
68
69 uint32_t Magic;
70 memcpy(dest: &Magic, src: Data.data(), n: sizeof(uint32_t));
71
72 switch (Magic) {
73 case MachO::MH_MAGIC:
74 case MachO::MH_CIGAM:
75 return checkMachORelocatableObject<MachO::mach_header>(
76 Obj: std::move(Obj), SwapEndianness: Magic == MachO::MH_CIGAM, TT, ObjIsSlice);
77 case MachO::MH_MAGIC_64:
78 case MachO::MH_CIGAM_64:
79 return checkMachORelocatableObject<MachO::mach_header_64>(
80 Obj: std::move(Obj), SwapEndianness: Magic == MachO::MH_CIGAM_64, TT, ObjIsSlice);
81 default:
82 return make_error<StringError>(
83 Args: objDesc(Obj, TT, ObjIsSlice) +
84 " is not a valid MachO relocatable object (bad magic value)",
85 Args: inconvertibleErrorCode());
86 }
87}
88
89Expected<std::unique_ptr<MemoryBuffer>>
90checkMachORelocatableObject(std::unique_ptr<MemoryBuffer> Obj, const Triple &TT,
91 bool ObjIsSlice) {
92 if (auto Err =
93 checkMachORelocatableObject(Obj: Obj->getMemBufferRef(), TT, ObjIsSlice))
94 return std::move(Err);
95 return std::move(Obj);
96}
97
98Expected<std::pair<std::unique_ptr<MemoryBuffer>, LinkableFileKind>>
99loadMachORelocatableObject(StringRef Path, const Triple &TT, LoadArchives LA,
100 std::optional<StringRef> IdentifierOverride) {
101 assert((TT.getObjectFormat() == Triple::UnknownObjectFormat ||
102 TT.getObjectFormat() == Triple::MachO) &&
103 "TT must specify MachO or Unknown object format");
104
105 if (!IdentifierOverride)
106 IdentifierOverride = Path;
107
108 Expected<sys::fs::file_t> FDOrErr =
109 sys::fs::openNativeFileForRead(Name: Path, Flags: sys::fs::OF_None);
110 if (!FDOrErr)
111 return createFileError(F: Path, E: FDOrErr.takeError());
112 sys::fs::file_t FD = *FDOrErr;
113 auto CloseFile = make_scope_exit(F: [&]() { sys::fs::closeFile(F&: FD); });
114
115 auto Buf =
116 MemoryBuffer::getOpenFile(FD, Filename: *IdentifierOverride, /*FileSize=*/-1);
117 if (!Buf)
118 return make_error<StringError>(
119 Args: StringRef("Could not load MachO object at path ") + Path,
120 Args: Buf.getError());
121
122 switch (identify_magic(magic: (*Buf)->getBuffer())) {
123 case file_magic::macho_object: {
124 auto CheckedObj = checkMachORelocatableObject(Obj: std::move(*Buf), TT, ObjIsSlice: false);
125 if (!CheckedObj)
126 return CheckedObj.takeError();
127 return std::make_pair(x: std::move(*CheckedObj),
128 y: LinkableFileKind::RelocatableObject);
129 }
130 case file_magic::macho_universal_binary:
131 return loadLinkableSliceFromMachOUniversalBinary(FD, UBBuf: std::move(*Buf), TT,
132 LA: LoadArchives::Never, UBPath: Path,
133 Identifier: *IdentifierOverride);
134 default:
135 return make_error<StringError>(
136 Args: Path + " does not contain a relocatable object file compatible with " +
137 TT.str(),
138 Args: inconvertibleErrorCode());
139 }
140}
141
142Expected<std::pair<std::unique_ptr<MemoryBuffer>, LinkableFileKind>>
143loadLinkableSliceFromMachOUniversalBinary(sys::fs::file_t FD,
144 std::unique_ptr<MemoryBuffer> UBBuf,
145 const Triple &TT, LoadArchives LA,
146 StringRef UBPath,
147 StringRef Identifier) {
148
149 auto UniversalBin =
150 object::MachOUniversalBinary::create(Source: UBBuf->getMemBufferRef());
151 if (!UniversalBin)
152 return UniversalBin.takeError();
153
154 auto SliceRange = getMachOSliceRangeForTriple(UB&: **UniversalBin, TT);
155 if (!SliceRange)
156 return SliceRange.takeError();
157
158 auto Buf = MemoryBuffer::getOpenFileSlice(FD, Filename: Identifier, MapSize: SliceRange->second,
159 Offset: SliceRange->first);
160 if (!Buf)
161 return make_error<StringError>(
162 Args: "Could not load " + TT.getArchName() +
163 " slice of MachO universal binary at path " + UBPath,
164 Args: Buf.getError());
165
166 switch (identify_magic(magic: (*Buf)->getBuffer())) {
167 case file_magic::archive:
168 if (LA != LoadArchives::Never)
169 return std::make_pair(x: std::move(*Buf), y: LinkableFileKind::Archive);
170 break;
171 case file_magic::macho_object: {
172 if (LA != LoadArchives::Required) {
173 auto CheckedObj = checkMachORelocatableObject(Obj: std::move(*Buf), TT, ObjIsSlice: true);
174 if (!CheckedObj)
175 return CheckedObj.takeError();
176 return std::make_pair(x: std::move(*CheckedObj),
177 y: LinkableFileKind::RelocatableObject);
178 }
179 break;
180 }
181 default:
182 break;
183 }
184
185 auto FT = [&] {
186 switch (LA) {
187 case LoadArchives::Never:
188 return "a mach-o relocatable object file";
189 case LoadArchives::Allowed:
190 return "a mach-o relocatable object file or archive";
191 case LoadArchives::Required:
192 return "an archive";
193 }
194 llvm_unreachable("Unknown LoadArchives enum");
195 };
196
197 return make_error<StringError>(Args: TT.getArchName() + " slice of " + UBPath +
198 " does not contain " + FT(),
199 Args: inconvertibleErrorCode());
200}
201
202Expected<std::pair<size_t, size_t>>
203getMachOSliceRangeForTriple(object::MachOUniversalBinary &UB,
204 const Triple &TT) {
205
206 for (const auto &Obj : UB.objects()) {
207 auto ObjTT = Obj.getTriple();
208 if (ObjTT.getArch() == TT.getArch() &&
209 ObjTT.getSubArch() == TT.getSubArch() &&
210 (TT.getVendor() == Triple::UnknownVendor ||
211 ObjTT.getVendor() == TT.getVendor())) {
212 // We found a match. Return the range for the slice.
213 return std::make_pair(x: Obj.getOffset(), y: Obj.getSize());
214 }
215 }
216
217 return make_error<StringError>(Args: Twine("Universal binary ") + UB.getFileName() +
218 " does not contain a slice for " +
219 TT.str(),
220 Args: inconvertibleErrorCode());
221}
222
223Expected<std::pair<size_t, size_t>>
224getMachOSliceRangeForTriple(MemoryBufferRef UBBuf, const Triple &TT) {
225
226 auto UB = object::MachOUniversalBinary::create(Source: UBBuf);
227 if (!UB)
228 return UB.takeError();
229
230 return getMachOSliceRangeForTriple(UB&: **UB, TT);
231}
232
233Expected<bool> ForceLoadMachOArchiveMembers::operator()(
234 object::Archive &A, MemoryBufferRef MemberBuf, size_t Index) {
235
236 auto LoadMember = [&]() {
237 return StaticLibraryDefinitionGenerator::createMemberBuffer(A, BufRef: MemberBuf,
238 Index);
239 };
240
241 if (!ObjCOnly) {
242 // If we're loading all files then just load the buffer immediately. Return
243 // false to indicate that there's no further loading to do here.
244 if (auto Err = L.add(JD, O: LoadMember()))
245 return Err;
246 return false;
247 }
248
249 // We need to check whether this archive member contains any Objective-C
250 // or Swift metadata.
251 auto Obj = object::ObjectFile::createObjectFile(Object: MemberBuf);
252 if (!Obj) {
253 // Invalid files are not loadable, but don't invalidate the archive.
254 consumeError(Err: Obj.takeError());
255 return false;
256 }
257
258 if (auto *MachOObj = dyn_cast<object::MachOObjectFile>(Val: &**Obj)) {
259 // Load the object if any recognized special section is present.
260 for (auto Sec : MachOObj->sections()) {
261 auto SegName =
262 MachOObj->getSectionFinalSegmentName(Sec: Sec.getRawDataRefImpl());
263 if (auto SecName = Sec.getName()) {
264 if (*SecName == "__objc_classlist" || *SecName == "__objc_protolist" ||
265 *SecName == "__objc_clsrolist" || *SecName == "__objc_catlist" ||
266 *SecName == "__objc_catlist2" || *SecName == "__objc_nlcatlist" ||
267 (SegName == "__TEXT" && (*SecName).starts_with(Prefix: "__swift") &&
268 *SecName != "__swift_modhash")) {
269 if (auto Err = L.add(JD, O: LoadMember()))
270 return Err;
271 return false;
272 }
273 } else
274 return SecName.takeError();
275 }
276 }
277
278 // This is an object file but we didn't load it, so return true to indicate
279 // that it's still loadable.
280 return true;
281}
282
283} // End namespace orc.
284} // End namespace llvm.
285