1 | //===- MachOUniversalWriter.cpp - MachO universal binary writer---*- C++-*-===// |
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 | // Defines the Slice class and writeUniversalBinary function for writing a MachO |
10 | // universal binary file. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "llvm/Object/MachOUniversalWriter.h" |
15 | #include "llvm/ADT/STLExtras.h" |
16 | #include "llvm/ADT/SmallVector.h" |
17 | #include "llvm/Object/Archive.h" |
18 | #include "llvm/Object/Binary.h" |
19 | #include "llvm/Object/IRObjectFile.h" |
20 | #include "llvm/Object/MachO.h" |
21 | #include "llvm/Object/MachOUniversal.h" |
22 | #include "llvm/Support/Casting.h" |
23 | #include "llvm/Support/ErrorHandling.h" |
24 | #include "llvm/Support/FileSystem.h" |
25 | #include "llvm/Support/MathExtras.h" |
26 | #include "llvm/Support/MemoryBufferRef.h" |
27 | #include "llvm/Support/SwapByteOrder.h" |
28 | #include "llvm/Support/raw_ostream.h" |
29 | #include "llvm/TargetParser/Triple.h" |
30 | |
31 | using namespace llvm; |
32 | using namespace object; |
33 | |
34 | // For compatibility with cctools lipo, a file's alignment is calculated as the |
35 | // minimum aligment of all segments. For object files, the file's alignment is |
36 | // the maximum alignment of its sections. |
37 | static uint32_t calculateFileAlignment(const MachOObjectFile &O) { |
38 | uint32_t P2CurrentAlignment; |
39 | uint32_t P2MinAlignment = MachOUniversalBinary::MaxSectionAlignment; |
40 | const bool Is64Bit = O.is64Bit(); |
41 | |
42 | for (const auto &LC : O.load_commands()) { |
43 | if (LC.C.cmd != (Is64Bit ? MachO::LC_SEGMENT_64 : MachO::LC_SEGMENT)) |
44 | continue; |
45 | if (O.getHeader().filetype == MachO::MH_OBJECT) { |
46 | unsigned NumberOfSections = |
47 | (Is64Bit ? O.getSegment64LoadCommand(L: LC).nsects |
48 | : O.getSegmentLoadCommand(L: LC).nsects); |
49 | P2CurrentAlignment = NumberOfSections ? 2 : P2MinAlignment; |
50 | for (unsigned SI = 0; SI < NumberOfSections; ++SI) { |
51 | P2CurrentAlignment = std::max(a: P2CurrentAlignment, |
52 | b: (Is64Bit ? O.getSection64(L: LC, Index: SI).align |
53 | : O.getSection(L: LC, Index: SI).align)); |
54 | } |
55 | } else { |
56 | P2CurrentAlignment = |
57 | llvm::countr_zero(Val: Is64Bit ? O.getSegment64LoadCommand(L: LC).vmaddr |
58 | : O.getSegmentLoadCommand(L: LC).vmaddr); |
59 | } |
60 | P2MinAlignment = std::min(a: P2MinAlignment, b: P2CurrentAlignment); |
61 | } |
62 | // return a value >= 4 byte aligned, and less than MachO MaxSectionAlignment |
63 | return std::max( |
64 | a: static_cast<uint32_t>(2), |
65 | b: std::min(a: P2MinAlignment, b: static_cast<uint32_t>( |
66 | MachOUniversalBinary::MaxSectionAlignment))); |
67 | } |
68 | |
69 | static uint32_t calculateAlignment(const MachOObjectFile &ObjectFile) { |
70 | switch (ObjectFile.getHeader().cputype) { |
71 | case MachO::CPU_TYPE_I386: |
72 | case MachO::CPU_TYPE_X86_64: |
73 | case MachO::CPU_TYPE_POWERPC: |
74 | case MachO::CPU_TYPE_POWERPC64: |
75 | return 12; // log2 value of page size(4k) for x86 and PPC |
76 | case MachO::CPU_TYPE_ARM: |
77 | case MachO::CPU_TYPE_ARM64: |
78 | case MachO::CPU_TYPE_ARM64_32: |
79 | return 14; // log2 value of page size(16k) for Darwin ARM |
80 | default: |
81 | return calculateFileAlignment(O: ObjectFile); |
82 | } |
83 | } |
84 | |
85 | Slice::Slice(const Archive &A, uint32_t CPUType, uint32_t CPUSubType, |
86 | std::string ArchName, uint32_t Align) |
87 | : B(&A), CPUType(CPUType), CPUSubType(CPUSubType), |
88 | ArchName(std::move(ArchName)), P2Alignment(Align) {} |
89 | |
90 | Slice::Slice(const MachOObjectFile &O, uint32_t Align) |
91 | : B(&O), CPUType(O.getHeader().cputype), |
92 | CPUSubType(O.getHeader().cpusubtype), |
93 | ArchName(std::string(O.getArchTriple().getArchName())), |
94 | P2Alignment(Align) {} |
95 | |
96 | Slice::Slice(const IRObjectFile &IRO, uint32_t CPUType, uint32_t CPUSubType, |
97 | std::string ArchName, uint32_t Align) |
98 | : B(&IRO), CPUType(CPUType), CPUSubType(CPUSubType), |
99 | ArchName(std::move(ArchName)), P2Alignment(Align) {} |
100 | |
101 | Slice::Slice(const MachOObjectFile &O) : Slice(O, calculateAlignment(ObjectFile: O)) {} |
102 | |
103 | using MachoCPUTy = std::pair<uint32_t, uint32_t>; |
104 | |
105 | static Expected<MachoCPUTy> getMachoCPUFromTriple(Triple TT) { |
106 | auto CPU = std::make_pair(x: MachO::getCPUType(T: TT), y: MachO::getCPUSubType(T: TT)); |
107 | if (!CPU.first) { |
108 | return CPU.first.takeError(); |
109 | } |
110 | if (!CPU.second) { |
111 | return CPU.second.takeError(); |
112 | } |
113 | return std::make_pair(x&: *CPU.first, y&: *CPU.second); |
114 | } |
115 | |
116 | static Expected<MachoCPUTy> getMachoCPUFromTriple(StringRef TT) { |
117 | return getMachoCPUFromTriple(TT: Triple{TT}); |
118 | } |
119 | |
120 | static MachoCPUTy getMachoCPUFromObjectFile(const MachOObjectFile &O) { |
121 | return std::make_pair(x: O.getHeader().cputype, y: O.getHeader().cpusubtype); |
122 | } |
123 | |
124 | Expected<Slice> Slice::create(const Archive &A, LLVMContext *LLVMCtx) { |
125 | Error Err = Error::success(); |
126 | std::unique_ptr<MachOObjectFile> MFO = nullptr; |
127 | std::unique_ptr<IRObjectFile> IRFO = nullptr; |
128 | std::optional<MachoCPUTy> CPU = std::nullopt; |
129 | for (const Archive::Child &Child : A.children(Err)) { |
130 | Expected<std::unique_ptr<Binary>> ChildOrErr = Child.getAsBinary(Context: LLVMCtx); |
131 | if (!ChildOrErr) |
132 | return createFileError(F: A.getFileName(), E: ChildOrErr.takeError()); |
133 | Binary *Bin = ChildOrErr.get().get(); |
134 | if (Bin->isMachOUniversalBinary()) |
135 | return createStringError(EC: std::errc::invalid_argument, |
136 | Fmt: ("archive member " + Bin->getFileName() + |
137 | " is a fat file (not allowed in an archive)" ) |
138 | .str() |
139 | .c_str()); |
140 | if (Bin->isMachO()) { |
141 | MachOObjectFile *O = cast<MachOObjectFile>(Val: Bin); |
142 | MachoCPUTy ObjectCPU = getMachoCPUFromObjectFile(O: *O); |
143 | |
144 | if (CPU && CPU != ObjectCPU) { |
145 | // If CPU != nullptr, one of MFO, IRFO will be != nullptr. |
146 | StringRef PreviousName = MFO ? MFO->getFileName() : IRFO->getFileName(); |
147 | return createStringError( |
148 | EC: std::errc::invalid_argument, |
149 | Fmt: ("archive member " + O->getFileName() + " cputype (" + |
150 | Twine(ObjectCPU.first) + ") and cpusubtype(" + |
151 | Twine(ObjectCPU.second) + |
152 | ") does not match previous archive members cputype (" + |
153 | Twine(CPU->first) + ") and cpusubtype(" + Twine(CPU->second) + |
154 | ") (all members must match) " + PreviousName) |
155 | .str() |
156 | .c_str()); |
157 | } |
158 | if (!MFO) { |
159 | ChildOrErr.get().release(); |
160 | MFO.reset(p: O); |
161 | if (!CPU) |
162 | CPU.emplace(args&: ObjectCPU); |
163 | } |
164 | } else if (Bin->isIR()) { |
165 | IRObjectFile *O = cast<IRObjectFile>(Val: Bin); |
166 | Expected<MachoCPUTy> ObjectCPU = |
167 | getMachoCPUFromTriple(TT: O->getTargetTriple()); |
168 | if (!ObjectCPU) |
169 | return ObjectCPU.takeError(); |
170 | |
171 | if (CPU && CPU != *ObjectCPU) { |
172 | // If CPU != nullptr, one of MFO, IRFO will be != nullptr. |
173 | StringRef PreviousName = |
174 | IRFO ? IRFO->getFileName() : MFO->getFileName(); |
175 | return createStringError( |
176 | EC: std::errc::invalid_argument, |
177 | Fmt: ("archive member " + O->getFileName() + " cputype (" + |
178 | Twine(ObjectCPU->first) + ") and cpusubtype(" + |
179 | Twine(ObjectCPU->second) + |
180 | ") does not match previous archive members cputype (" + |
181 | Twine(CPU->first) + ") and cpusubtype(" + Twine(CPU->second) + |
182 | ") (all members must match) " + PreviousName) |
183 | .str() |
184 | .c_str()); |
185 | } |
186 | |
187 | if (!IRFO) { |
188 | ChildOrErr.get().release(); |
189 | IRFO.reset(p: O); |
190 | if (!CPU) |
191 | CPU.emplace(args&: *ObjectCPU); |
192 | } |
193 | } else |
194 | return createStringError(EC: std::errc::invalid_argument, |
195 | Fmt: ("archive member " + Bin->getFileName() + |
196 | " is neither a MachO file or an LLVM IR file " |
197 | "(not allowed in an archive)" ) |
198 | .str() |
199 | .c_str()); |
200 | } |
201 | if (Err) |
202 | return createFileError(F: A.getFileName(), E: std::move(Err)); |
203 | if (!MFO && !IRFO) |
204 | return createStringError( |
205 | EC: std::errc::invalid_argument, |
206 | Fmt: ("empty archive with no architecture specification: " + |
207 | A.getFileName() + " (can't determine architecture for it)" ) |
208 | .str() |
209 | .c_str()); |
210 | |
211 | if (MFO) { |
212 | Slice ArchiveSlice(*(MFO), MFO->is64Bit() ? 3 : 2); |
213 | ArchiveSlice.B = &A; |
214 | return ArchiveSlice; |
215 | } |
216 | |
217 | // For IR objects |
218 | Expected<Slice> ArchiveSliceOrErr = Slice::create(IRO: *IRFO, Align: 0); |
219 | if (!ArchiveSliceOrErr) |
220 | return createFileError(F: A.getFileName(), E: ArchiveSliceOrErr.takeError()); |
221 | auto &ArchiveSlice = ArchiveSliceOrErr.get(); |
222 | ArchiveSlice.B = &A; |
223 | return std::move(ArchiveSlice); |
224 | } |
225 | |
226 | Expected<Slice> Slice::create(const IRObjectFile &IRO, uint32_t Align) { |
227 | Expected<MachoCPUTy> CPUOrErr = getMachoCPUFromTriple(TT: IRO.getTargetTriple()); |
228 | if (!CPUOrErr) |
229 | return CPUOrErr.takeError(); |
230 | unsigned CPUType, CPUSubType; |
231 | std::tie(args&: CPUType, args&: CPUSubType) = CPUOrErr.get(); |
232 | // We don't directly use the architecture name of the target triple T, as, |
233 | // for instance, thumb is treated as ARM by the MachOUniversal object. |
234 | std::string ArchName( |
235 | MachOObjectFile::getArchTriple(CPUType, CPUSubType).getArchName()); |
236 | return Slice{IRO, CPUType, CPUSubType, std::move(ArchName), Align}; |
237 | } |
238 | |
239 | template <typename FatArchTy> struct FatArchTraits { |
240 | static const uint64_t OffsetLimit; |
241 | static const std::string StructName; |
242 | static const uint8_t BitCount; |
243 | }; |
244 | |
245 | template <> struct FatArchTraits<MachO::fat_arch> { |
246 | static const uint64_t OffsetLimit = UINT32_MAX; |
247 | static const std::string StructName; |
248 | static const uint8_t BitCount = 32; |
249 | }; |
250 | const std::string FatArchTraits<MachO::fat_arch>::StructName = "fat_arch" ; |
251 | |
252 | template <> struct FatArchTraits<MachO::fat_arch_64> { |
253 | static const uint64_t OffsetLimit = UINT64_MAX; |
254 | static const std::string StructName; |
255 | static const uint8_t BitCount = 64; |
256 | }; |
257 | const std::string FatArchTraits<MachO::fat_arch_64>::StructName = "fat_arch_64" ; |
258 | |
259 | template <typename FatArchTy> |
260 | static Expected<SmallVector<FatArchTy, 2>> |
261 | buildFatArchList(ArrayRef<Slice> Slices) { |
262 | SmallVector<FatArchTy, 2> FatArchList; |
263 | uint64_t Offset = |
264 | sizeof(MachO::fat_header) + Slices.size() * sizeof(FatArchTy); |
265 | |
266 | for (const auto &S : Slices) { |
267 | Offset = alignTo(Value: Offset, Align: 1ull << S.getP2Alignment()); |
268 | if (Offset > FatArchTraits<FatArchTy>::OffsetLimit) |
269 | return createStringError( |
270 | EC: std::errc::invalid_argument, |
271 | Fmt: ("fat file too large to be created because the offset field in the " |
272 | "struct " + |
273 | Twine(FatArchTraits<FatArchTy>::StructName) + " is only " + |
274 | Twine(FatArchTraits<FatArchTy>::BitCount) + "-bits and the offset " + |
275 | Twine(Offset) + " for " + S.getBinary()->getFileName() + |
276 | " for architecture " + S.getArchString() + "exceeds that." ) |
277 | .str() |
278 | .c_str()); |
279 | |
280 | FatArchTy FatArch = {}; |
281 | FatArch.cputype = S.getCPUType(); |
282 | FatArch.cpusubtype = S.getCPUSubType(); |
283 | FatArch.offset = Offset; |
284 | FatArch.size = S.getBinary()->getMemoryBufferRef().getBufferSize(); |
285 | FatArch.align = S.getP2Alignment(); |
286 | Offset += FatArch.size; |
287 | FatArchList.push_back(FatArch); |
288 | } |
289 | return FatArchList; |
290 | } |
291 | |
292 | template <typename FatArchTy> |
293 | static Error (MachO::fat_header , |
294 | ArrayRef<Slice> Slices, |
295 | raw_ostream &Out) { |
296 | Expected<SmallVector<FatArchTy, 2>> FatArchListOrErr = |
297 | buildFatArchList<FatArchTy>(Slices); |
298 | if (!FatArchListOrErr) |
299 | return FatArchListOrErr.takeError(); |
300 | SmallVector<FatArchTy, 2> FatArchList = *FatArchListOrErr; |
301 | |
302 | if (sys::IsLittleEndianHost) |
303 | MachO::swapStruct(mh&: FatHeader); |
304 | Out.write(Ptr: reinterpret_cast<const char *>(&FatHeader), |
305 | Size: sizeof(MachO::fat_header)); |
306 | |
307 | if (sys::IsLittleEndianHost) |
308 | for (FatArchTy &FA : FatArchList) |
309 | MachO::swapStruct(FA); |
310 | Out.write(reinterpret_cast<const char *>(FatArchList.data()), |
311 | sizeof(FatArchTy) * FatArchList.size()); |
312 | |
313 | if (sys::IsLittleEndianHost) |
314 | for (FatArchTy &FA : FatArchList) |
315 | MachO::swapStruct(FA); |
316 | |
317 | size_t Offset = |
318 | sizeof(MachO::fat_header) + sizeof(FatArchTy) * FatArchList.size(); |
319 | for (size_t Index = 0, Size = Slices.size(); Index < Size; ++Index) { |
320 | MemoryBufferRef BufferRef = Slices[Index].getBinary()->getMemoryBufferRef(); |
321 | assert((Offset <= FatArchList[Index].offset) && "Incorrect slice offset" ); |
322 | Out.write_zeros(NumZeros: FatArchList[Index].offset - Offset); |
323 | Out.write(Ptr: BufferRef.getBufferStart(), Size: BufferRef.getBufferSize()); |
324 | Offset = FatArchList[Index].offset + BufferRef.getBufferSize(); |
325 | } |
326 | |
327 | Out.flush(); |
328 | return Error::success(); |
329 | } |
330 | |
331 | Error object::(ArrayRef<Slice> Slices, |
332 | raw_ostream &Out, |
333 | FatHeaderType ) { |
334 | MachO::fat_header ; |
335 | FatHeader.nfat_arch = Slices.size(); |
336 | |
337 | switch (HeaderType) { |
338 | case FatHeaderType::Fat64Header: |
339 | FatHeader.magic = MachO::FAT_MAGIC_64; |
340 | return writeUniversalArchsToStream<MachO::fat_arch_64>(FatHeader, Slices, |
341 | Out); |
342 | break; |
343 | case FatHeaderType::FatHeader: |
344 | FatHeader.magic = MachO::FAT_MAGIC; |
345 | return writeUniversalArchsToStream<MachO::fat_arch>(FatHeader, Slices, Out); |
346 | break; |
347 | } |
348 | |
349 | llvm_unreachable("Invalid fat header type" ); |
350 | } |
351 | |
352 | Error object::(ArrayRef<Slice> Slices, |
353 | StringRef OutputFileName, |
354 | FatHeaderType ) { |
355 | const bool IsExecutable = any_of(Range&: Slices, P: [](Slice S) { |
356 | return sys::fs::can_execute(Path: S.getBinary()->getFileName()); |
357 | }); |
358 | unsigned Mode = sys::fs::all_read | sys::fs::all_write; |
359 | if (IsExecutable) |
360 | Mode |= sys::fs::all_exe; |
361 | Expected<sys::fs::TempFile> Temp = sys::fs::TempFile::create( |
362 | Model: OutputFileName + ".temp-universal-%%%%%%" , Mode); |
363 | if (!Temp) |
364 | return Temp.takeError(); |
365 | raw_fd_ostream Out(Temp->FD, false); |
366 | if (Error E = writeUniversalBinaryToStream(Slices, Out, HeaderType)) { |
367 | if (Error DiscardError = Temp->discard()) |
368 | return joinErrors(E1: std::move(E), E2: std::move(DiscardError)); |
369 | return E; |
370 | } |
371 | return Temp->keep(Name: OutputFileName); |
372 | } |
373 | |