| 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 | |