diff --git a/llvm/include/llvm/Object/MachOUniversalWriter.h b/llvm/include/llvm/Object/MachOUniversalWriter.h index 49352440dca1..606db94c9f20 100644 --- a/llvm/include/llvm/Object/MachOUniversalWriter.h +++ b/llvm/include/llvm/Object/MachOUniversalWriter.h @@ -1,96 +1,102 @@ //===- MachOUniversalWriter.h - MachO universal binary writer----*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// // // Declares the Slice class and writeUniversalBinary function for writing a // MachO universal binary file. // //===----------------------------------------------------------------------===// #ifndef LLVM_OBJECT_MACHOUNIVERSALWRITER_H #define LLVM_OBJECT_MACHOUNIVERSALWRITER_H #include "llvm/Object/Archive.h" #include "llvm/Object/Binary.h" #include "llvm/Object/MachO.h" namespace llvm { class LLVMContext; namespace object { class IRObjectFile; class Slice { const Binary *B; uint32_t CPUType; uint32_t CPUSubType; std::string ArchName; // P2Alignment field stores slice alignment values from universal // binaries. This is also needed to order the slices so the total // file size can be calculated before creating the output buffer. uint32_t P2Alignment; Slice(const IRObjectFile &IRO, uint32_t CPUType, uint32_t CPUSubType, std::string ArchName, uint32_t Align); public: explicit Slice(const MachOObjectFile &O); Slice(const MachOObjectFile &O, uint32_t Align); + /// This constructor takes prespecified \param CPUType, \param CPUSubType, + /// \param ArchName, \param Align instead of inferring them from the archive + /// memebers. + Slice(const Archive &A, uint32_t CPUType, uint32_t CPUSubType, + std::string ArchName, uint32_t Align); + static Expected create(const Archive &A, LLVMContext *LLVMCtx = nullptr); static Expected create(const IRObjectFile &IRO, uint32_t Align); void setP2Alignment(uint32_t Align) { P2Alignment = Align; } const Binary *getBinary() const { return B; } uint32_t getCPUType() const { return CPUType; } uint32_t getCPUSubType() const { return CPUSubType; } uint32_t getP2Alignment() const { return P2Alignment; } uint64_t getCPUID() const { return static_cast(CPUType) << 32 | CPUSubType; } std::string getArchString() const { if (!ArchName.empty()) return ArchName; return ("unknown(" + Twine(CPUType) + "," + Twine(CPUSubType & ~MachO::CPU_SUBTYPE_MASK) + ")") .str(); } friend bool operator<(const Slice &Lhs, const Slice &Rhs) { if (Lhs.CPUType == Rhs.CPUType) return Lhs.CPUSubType < Rhs.CPUSubType; // force arm64-family to follow after all other slices for // compatibility with cctools lipo if (Lhs.CPUType == MachO::CPU_TYPE_ARM64) return false; if (Rhs.CPUType == MachO::CPU_TYPE_ARM64) return true; // Sort by alignment to minimize file size return Lhs.P2Alignment < Rhs.P2Alignment; } }; Error writeUniversalBinary(ArrayRef Slices, StringRef OutputFileName); Expected> writeUniversalBinaryToBuffer(ArrayRef Slices); } // end namespace object } // end namespace llvm #endif // LLVM_OBJECT_MACHOUNIVERSALWRITER_H diff --git a/llvm/lib/Object/MachOUniversalWriter.cpp b/llvm/lib/Object/MachOUniversalWriter.cpp index 165964e077ce..4bb467e56a6f 100644 --- a/llvm/lib/Object/MachOUniversalWriter.cpp +++ b/llvm/lib/Object/MachOUniversalWriter.cpp @@ -1,332 +1,337 @@ //===- MachOUniversalWriter.cpp - MachO universal binary writer---*- C++-*-===// // // 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 // //===----------------------------------------------------------------------===// // // Defines the Slice class and writeUniversalBinary function for writing a MachO // universal binary file. // //===----------------------------------------------------------------------===// #include "llvm/Object/MachOUniversalWriter.h" #include "llvm/ADT/Triple.h" #include "llvm/Object/Archive.h" #include "llvm/Object/Binary.h" #include "llvm/Object/Error.h" #include "llvm/Object/IRObjectFile.h" #include "llvm/Object/MachO.h" #include "llvm/Object/MachOUniversal.h" #include "llvm/Support/SmallVectorMemoryBuffer.h" using namespace llvm; using namespace object; // For compatibility with cctools lipo, a file's alignment is calculated as the // minimum aligment of all segments. For object files, the file's alignment is // the maximum alignment of its sections. static uint32_t calculateFileAlignment(const MachOObjectFile &O) { uint32_t P2CurrentAlignment; uint32_t P2MinAlignment = MachOUniversalBinary::MaxSectionAlignment; const bool Is64Bit = O.is64Bit(); for (const auto &LC : O.load_commands()) { if (LC.C.cmd != (Is64Bit ? MachO::LC_SEGMENT_64 : MachO::LC_SEGMENT)) continue; if (O.getHeader().filetype == MachO::MH_OBJECT) { unsigned NumberOfSections = (Is64Bit ? O.getSegment64LoadCommand(LC).nsects : O.getSegmentLoadCommand(LC).nsects); P2CurrentAlignment = NumberOfSections ? 2 : P2MinAlignment; for (unsigned SI = 0; SI < NumberOfSections; ++SI) { P2CurrentAlignment = std::max(P2CurrentAlignment, (Is64Bit ? O.getSection64(LC, SI).align : O.getSection(LC, SI).align)); } } else { P2CurrentAlignment = countTrailingZeros(Is64Bit ? O.getSegment64LoadCommand(LC).vmaddr : O.getSegmentLoadCommand(LC).vmaddr); } P2MinAlignment = std::min(P2MinAlignment, P2CurrentAlignment); } // return a value >= 4 byte aligned, and less than MachO MaxSectionAlignment return std::max( static_cast(2), std::min(P2MinAlignment, static_cast( MachOUniversalBinary::MaxSectionAlignment))); } static uint32_t calculateAlignment(const MachOObjectFile &ObjectFile) { switch (ObjectFile.getHeader().cputype) { case MachO::CPU_TYPE_I386: case MachO::CPU_TYPE_X86_64: case MachO::CPU_TYPE_POWERPC: case MachO::CPU_TYPE_POWERPC64: return 12; // log2 value of page size(4k) for x86 and PPC case MachO::CPU_TYPE_ARM: case MachO::CPU_TYPE_ARM64: case MachO::CPU_TYPE_ARM64_32: return 14; // log2 value of page size(16k) for Darwin ARM default: return calculateFileAlignment(ObjectFile); } } +Slice::Slice(const Archive &A, uint32_t CPUType, uint32_t CPUSubType, + std::string ArchName, uint32_t Align) + : B(&A), CPUType(CPUType), CPUSubType(CPUSubType), + ArchName(std::move(ArchName)), P2Alignment(Align) {} + Slice::Slice(const MachOObjectFile &O, uint32_t Align) : B(&O), CPUType(O.getHeader().cputype), CPUSubType(O.getHeader().cpusubtype), ArchName(std::string(O.getArchTriple().getArchName())), P2Alignment(Align) {} Slice::Slice(const IRObjectFile &IRO, uint32_t CPUType, uint32_t CPUSubType, std::string ArchName, uint32_t Align) : B(&IRO), CPUType(CPUType), CPUSubType(CPUSubType), ArchName(std::move(ArchName)), P2Alignment(Align) {} Slice::Slice(const MachOObjectFile &O) : Slice(O, calculateAlignment(O)) {} using MachoCPUTy = std::pair; static Expected getMachoCPUFromTriple(Triple TT) { auto CPU = std::make_pair(MachO::getCPUType(TT), MachO::getCPUSubType(TT)); if (!CPU.first) { return CPU.first.takeError(); } if (!CPU.second) { return CPU.second.takeError(); } return std::make_pair(*CPU.first, *CPU.second); } static Expected getMachoCPUFromTriple(StringRef TT) { return getMachoCPUFromTriple(Triple{TT}); } Expected Slice::create(const Archive &A, LLVMContext *LLVMCtx) { Error Err = Error::success(); std::unique_ptr MFO = nullptr; std::unique_ptr IRFO = nullptr; for (const Archive::Child &Child : A.children(Err)) { Expected> ChildOrErr = Child.getAsBinary(LLVMCtx); if (!ChildOrErr) return createFileError(A.getFileName(), ChildOrErr.takeError()); Binary *Bin = ChildOrErr.get().get(); if (Bin->isMachOUniversalBinary()) return createStringError(std::errc::invalid_argument, ("archive member " + Bin->getFileName() + " is a fat file (not allowed in an archive)") .str() .c_str()); if (Bin->isMachO()) { MachOObjectFile *O = cast(Bin); if (IRFO) { return createStringError( std::errc::invalid_argument, "archive member %s is a MachO, while previous archive member " "%s was an IR LLVM object", O->getFileName().str().c_str(), IRFO->getFileName().str().c_str()); } if (MFO && std::tie(MFO->getHeader().cputype, MFO->getHeader().cpusubtype) != std::tie(O->getHeader().cputype, O->getHeader().cpusubtype)) { return createStringError( std::errc::invalid_argument, ("archive member " + O->getFileName() + " cputype (" + Twine(O->getHeader().cputype) + ") and cpusubtype(" + Twine(O->getHeader().cpusubtype) + ") does not match previous archive members cputype (" + Twine(MFO->getHeader().cputype) + ") and cpusubtype(" + Twine(MFO->getHeader().cpusubtype) + ") (all members must match) " + MFO->getFileName()) .str() .c_str()); } if (!MFO) { ChildOrErr.get().release(); MFO.reset(O); } } else if (Bin->isIR()) { IRObjectFile *O = cast(Bin); if (MFO) { return createStringError(std::errc::invalid_argument, "archive member '%s' is an LLVM IR object, " "while previous archive member " "'%s' was a MachO", O->getFileName().str().c_str(), MFO->getFileName().str().c_str()); } if (IRFO) { Expected CPUO = getMachoCPUFromTriple(O->getTargetTriple()); Expected CPUFO = getMachoCPUFromTriple(IRFO->getTargetTriple()); if (!CPUO) return CPUO.takeError(); if (!CPUFO) return CPUFO.takeError(); if (*CPUO != *CPUFO) { return createStringError( std::errc::invalid_argument, ("archive member " + O->getFileName() + " cputype (" + Twine(CPUO->first) + ") and cpusubtype(" + Twine(CPUO->second) + ") does not match previous archive members cputype (" + Twine(CPUFO->first) + ") and cpusubtype(" + Twine(CPUFO->second) + ") (all members must match) " + IRFO->getFileName()) .str() .c_str()); } } else { ChildOrErr.get().release(); IRFO.reset(O); } } else return createStringError(std::errc::invalid_argument, ("archive member " + Bin->getFileName() + " is neither a MachO file or an LLVM IR file " "(not allowed in an archive)") .str() .c_str()); } if (Err) return createFileError(A.getFileName(), std::move(Err)); if (!MFO && !IRFO) return createStringError( std::errc::invalid_argument, ("empty archive with no architecture specification: " + A.getFileName() + " (can't determine architecture for it)") .str() .c_str()); if (MFO) { Slice ArchiveSlice(*(MFO.get()), MFO->is64Bit() ? 3 : 2); ArchiveSlice.B = &A; return ArchiveSlice; } // For IR objects Expected ArchiveSliceOrErr = Slice::create(*IRFO, 0); if (!ArchiveSliceOrErr) return createFileError(A.getFileName(), ArchiveSliceOrErr.takeError()); auto &ArchiveSlice = ArchiveSliceOrErr.get(); ArchiveSlice.B = &A; return std::move(ArchiveSlice); } Expected Slice::create(const IRObjectFile &IRO, uint32_t Align) { Expected CPUOrErr = getMachoCPUFromTriple(IRO.getTargetTriple()); if (!CPUOrErr) return CPUOrErr.takeError(); unsigned CPUType, CPUSubType; std::tie(CPUType, CPUSubType) = CPUOrErr.get(); // We don't directly use the architecture name of the target triple T, as, // for instance, thumb is treated as ARM by the MachOUniversal object. std::string ArchName( MachOObjectFile::getArchTriple(CPUType, CPUSubType).getArchName()); return Slice{IRO, CPUType, CPUSubType, std::move(ArchName), Align}; } static Expected> buildFatArchList(ArrayRef Slices) { SmallVector FatArchList; uint64_t Offset = sizeof(MachO::fat_header) + Slices.size() * sizeof(MachO::fat_arch); for (const auto &S : Slices) { Offset = alignTo(Offset, 1ull << S.getP2Alignment()); if (Offset > UINT32_MAX) return createStringError( std::errc::invalid_argument, ("fat file too large to be created because the offset " "field in struct fat_arch is only 32-bits and the offset " + Twine(Offset) + " for " + S.getBinary()->getFileName() + " for architecture " + S.getArchString() + "exceeds that.") .str() .c_str()); MachO::fat_arch FatArch; FatArch.cputype = S.getCPUType(); FatArch.cpusubtype = S.getCPUSubType(); FatArch.offset = Offset; FatArch.size = S.getBinary()->getMemoryBufferRef().getBufferSize(); FatArch.align = S.getP2Alignment(); Offset += FatArch.size; FatArchList.push_back(FatArch); } return FatArchList; } static Error writeUniversalBinaryToStream(ArrayRef Slices, raw_ostream &Out) { MachO::fat_header FatHeader; FatHeader.magic = MachO::FAT_MAGIC; FatHeader.nfat_arch = Slices.size(); Expected> FatArchListOrErr = buildFatArchList(Slices); if (!FatArchListOrErr) return FatArchListOrErr.takeError(); SmallVector FatArchList = *FatArchListOrErr; if (sys::IsLittleEndianHost) MachO::swapStruct(FatHeader); Out.write(reinterpret_cast(&FatHeader), sizeof(MachO::fat_header)); if (sys::IsLittleEndianHost) for (MachO::fat_arch &FA : FatArchList) MachO::swapStruct(FA); Out.write(reinterpret_cast(FatArchList.data()), sizeof(MachO::fat_arch) * FatArchList.size()); if (sys::IsLittleEndianHost) for (MachO::fat_arch &FA : FatArchList) MachO::swapStruct(FA); size_t Offset = sizeof(MachO::fat_header) + sizeof(MachO::fat_arch) * FatArchList.size(); for (size_t Index = 0, Size = Slices.size(); Index < Size; ++Index) { MemoryBufferRef BufferRef = Slices[Index].getBinary()->getMemoryBufferRef(); assert((Offset <= FatArchList[Index].offset) && "Incorrect slice offset"); Out.write_zeros(FatArchList[Index].offset - Offset); Out.write(BufferRef.getBufferStart(), BufferRef.getBufferSize()); Offset = FatArchList[Index].offset + BufferRef.getBufferSize(); } Out.flush(); return Error::success(); } Error object::writeUniversalBinary(ArrayRef Slices, StringRef OutputFileName) { const bool IsExecutable = any_of(Slices, [](Slice S) { return sys::fs::can_execute(S.getBinary()->getFileName()); }); unsigned Mode = sys::fs::all_read | sys::fs::all_write; if (IsExecutable) Mode |= sys::fs::all_exe; Expected Temp = sys::fs::TempFile::create( OutputFileName + ".temp-universal-%%%%%%", Mode); if (!Temp) return Temp.takeError(); raw_fd_ostream Out(Temp->FD, false); if (Error E = writeUniversalBinaryToStream(Slices, Out)) { if (Error DiscardError = Temp->discard()) return joinErrors(std::move(E), std::move(DiscardError)); return E; } return Temp->keep(OutputFileName); } Expected> object::writeUniversalBinaryToBuffer(ArrayRef Slices) { SmallVector Buffer; raw_svector_ostream Out(Buffer); if (Error E = writeUniversalBinaryToStream(Slices, Out)) return std::move(E); return std::make_unique(std::move(Buffer)); } diff --git a/llvm/test/tools/llvm-objcopy/MachO/strip-all.test b/llvm/test/tools/llvm-objcopy/MachO/strip-all.test index 4ff31f5c1e42..cb41b353ec53 100644 --- a/llvm/test/tools/llvm-objcopy/MachO/strip-all.test +++ b/llvm/test/tools/llvm-objcopy/MachO/strip-all.test @@ -1,47 +1,52 @@ ## Show that llvm-objcopy/llvm-strip removes all symbols and debug sections. # RUN: yaml2obj %p/Inputs/strip-all.yaml -o %t.exec # RUN: yaml2obj %p/Inputs/strip-all-with-dwarf.yaml -o %t.dwarf ## Check that the symbol list satisfies the order: local / defined external / ## undefined external, otherwise llvm-objcopy will fail. # RUN: llvm-objcopy %t.exec /dev/null # RUN: llvm-objcopy --strip-all %t.exec %t.exec.stripped # RUN: llvm-readobj --sections --relocations --symbols %t.exec.stripped \ # RUN: | FileCheck --check-prefix=COMMON %s # RUN: llvm-objcopy --strip-all %t.dwarf %t.dwarf.stripped # RUN: llvm-readobj --sections --relocations --symbols %t.dwarf.stripped \ # RUN: | FileCheck --check-prefixes=COMMON,DWARF %s ## The output of "llvm-strip" should be identical with that of ## "llvm-strip --strip-all" and "llvm-objcopy --strip-all". # RUN: llvm-strip %t.exec -o %t2 # RUN: llvm-strip --strip-all %t.exec -o %t3 # cmp %t2 %t.exec.stripped # cmp %t3 %t.exec.stripped # RUN: llvm-strip %t.dwarf -o %t4 # RUN: llvm-strip --strip-all %t.dwarf -o %t5 # cmp %t4 %t.dwarf.stripped # cmp %t5 %t.dwarf.stripped +# RUN: llvm-lipo %t.dwarf -create -output %t.dwarf.universal +# RUN: llvm-strip %t.dwarf.universal -o %t.dwarf.universal.stripped +# RUN: llvm-lipo %t.dwarf.universal.stripped -thin x86_64 -output %t6 +# RUN: cmp %t6 %t.dwarf.stripped + ## Make sure that debug sections are removed. # DWARF: Sections [ # DWARF-NOT: Name: __debug_str # DWARF-NOT: Name: __debug_abbrev # DWARF-NOT: Name: __debug_info # DWARF-NOT: Name: __debug_macinfo # DWARF-NOT: Name: __apple_names # DWARF-NOT: Name: __apple_objc # DWARF-NOT: Name: __apple_namespac # DWARF-NOT: Name: __apple_types # DWARF-NOT: Name: __debug_line # DWARF: ] ## Make sure that all relocations and symbols are removed. # COMMON: Relocations [ # COMMON-NEXT: ] # COMMON: Symbols [ # COMMON-NEXT: ] diff --git a/llvm/test/tools/llvm-objcopy/MachO/universal-object.test b/llvm/test/tools/llvm-objcopy/MachO/universal-object.test new file mode 100644 index 000000000000..a6146fd56483 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/MachO/universal-object.test @@ -0,0 +1,42 @@ +# This test verifies that llvm-objcopy copies a univeral Mach-O object file properly. + +# RUN: yaml2obj %p/Inputs/i386.yaml -o %t.i386 +# RUN: yaml2obj %p/Inputs/x86_64.yaml -o %t.x86_64 + +## Case 1: copy a universal object containing regular Mach-O objects. +# RUN: llvm-lipo %t.i386 %t.x86_64 -create -output %t.universal +# RUN: llvm-objcopy %t.universal %t.universal.copy +# RUN: llvm-lipo %t.universal.copy -archs | FileCheck --check-prefix=VERIFY_ARCHS %s +# RUN: llvm-lipo %t.universal.copy -thin i386 -output %t.i386.copy +# RUN: llvm-lipo %t.universal.copy -thin x86_64 -output %t.x86_64.copy +# RUN: cmp %t.i386 %t.i386.copy +# RUN: cmp %t.x86_64 %t.x86_64.copy + +## Case 2: copy a universal object file containing an archive. +# RUN: rm -f %t.archive.i386 +# RUN: llvm-ar cr %t.archive.i386 %t.i386 +# RUN: llvm-lipo %t.archive.i386 %t.x86_64 -create -output %t.universal.containing.archive +# RUN: llvm-objcopy %t.universal.containing.archive %t.universal.containing.archive.copy +# RUN: llvm-lipo %t.universal.containing.archive.copy -archs | FileCheck --check-prefix=VERIFY_ARCHS %s +# RUN: llvm-lipo %t.universal.containing.archive.copy -thin i386 -output %t.archive.i386.copy +# RUN: llvm-lipo %t.universal.containing.archive.copy -thin x86_64 -output %t.archive.x86_64.copy +# RUN: cmp %t.archive.i386 %t.archive.i386.copy +# RUN: cmp %t.x86_64 %t.archive.x86_64.copy + +## Case 3: copy an archive containing a universal object. +# RUN: llvm-ar cr %t.archive.containing.universal %t.universal +# RUN: llvm-objcopy %t.archive.containing.universal %t.archive.containing.universal.copy + +## Case 4: try to copy a universal object file contaning a bitcode slice. +# RUN: echo 'target triple = "arm64-apple-ios8.0.0"' | llvm-as -o %t.bitcode +# RUN: llvm-lipo %t.bitcode %t.x86_64 -create -output %t.universal.containing.bitcode +# RUN: not llvm-objcopy %t.universal.containing.bitcode %t.universal.containing.bitcode.copy 2>&1 \ +# RUN: | FileCheck --check-prefix=UNSUPPORTED_UNIVERSAL_OBJECT %s + +## Case 5: try to copy an archive containing an unsupported universal object. +# RUN: llvm-ar cr %t.archive.universal.bitcode %t.universal.containing.bitcode +# RUN: not llvm-objcopy %t.archive.universal.bitcode %t.archive.universal.bitcode.copy 2>&1 \ +# RUN: | FileCheck --check-prefix=UNSUPPORTED_UNIVERSAL_OBJECT %s + +# VERIFY_ARCHS: i386 x86_64 +# UNSUPPORTED_UNIVERSAL_OBJECT: slice for 'arm64' of the universal Mach-O binary {{.*}} is not a Mach-O object or an archive diff --git a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp index 47a08d33002a..337c448f6dfd 100644 --- a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp +++ b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp @@ -1,391 +1,464 @@ //===- MachOObjcopy.cpp -----------------------------------------*- C++ -*-===// // // 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 "MachOObjcopy.h" #include "../CopyConfig.h" +#include "../llvm-objcopy.h" #include "MachOReader.h" #include "MachOWriter.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/Object/ArchiveWriter.h" +#include "llvm/Object/MachOUniversal.h" +#include "llvm/Object/MachOUniversalWriter.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" namespace llvm { namespace objcopy { namespace macho { using namespace object; using SectionPred = std::function &Sec)>; using LoadCommandPred = std::function; #ifndef NDEBUG static bool isLoadCommandWithPayloadString(const LoadCommand &LC) { // TODO: Add support for LC_REEXPORT_DYLIB, LC_LOAD_UPWARD_DYLIB and // LC_LAZY_LOAD_DYLIB return LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_RPATH || LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_ID_DYLIB || LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_LOAD_DYLIB || LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_LOAD_WEAK_DYLIB; } #endif static StringRef getPayloadString(const LoadCommand &LC) { assert(isLoadCommandWithPayloadString(LC) && "unsupported load command encountered"); return StringRef(reinterpret_cast(LC.Payload.data()), LC.Payload.size()) .rtrim('\0'); } static Error removeSections(const CopyConfig &Config, Object &Obj) { SectionPred RemovePred = [](const std::unique_ptr
&) { return false; }; if (!Config.ToRemove.empty()) { RemovePred = [&Config, RemovePred](const std::unique_ptr
&Sec) { return Config.ToRemove.matches(Sec->CanonicalName); }; } if (Config.StripAll || Config.StripDebug) { // Remove all debug sections. RemovePred = [RemovePred](const std::unique_ptr
&Sec) { if (Sec->Segname == "__DWARF") return true; return RemovePred(Sec); }; } if (!Config.OnlySection.empty()) { // Overwrite RemovePred because --only-section takes priority. RemovePred = [&Config](const std::unique_ptr
&Sec) { return !Config.OnlySection.matches(Sec->CanonicalName); }; } return Obj.removeSections(RemovePred); } static void markSymbols(const CopyConfig &Config, Object &Obj) { // Symbols referenced from the indirect symbol table must not be removed. for (IndirectSymbolEntry &ISE : Obj.IndirectSymTable.Symbols) if (ISE.Symbol) (*ISE.Symbol)->Referenced = true; } static void updateAndRemoveSymbols(const CopyConfig &Config, Object &Obj) { for (SymbolEntry &Sym : Obj.SymTable) { auto I = Config.SymbolsToRename.find(Sym.Name); if (I != Config.SymbolsToRename.end()) Sym.Name = std::string(I->getValue()); } auto RemovePred = [Config, &Obj](const std::unique_ptr &N) { if (N->Referenced) return false; if (Config.StripAll) return true; if (Config.DiscardMode == DiscardType::All && !(N->n_type & MachO::N_EXT)) return true; // This behavior is consistent with cctools' strip. if (Config.StripSwiftSymbols && (Obj.Header.Flags & MachO::MH_DYLDLINK) && Obj.SwiftVersion && *Obj.SwiftVersion && N->isSwiftSymbol()) return true; return false; }; Obj.SymTable.removeSymbols(RemovePred); } template static void updateLoadCommandPayloadString(LoadCommand &LC, StringRef S) { assert(isLoadCommandWithPayloadString(LC) && "unsupported load command encountered"); uint32_t NewCmdsize = alignTo(sizeof(LCType) + S.size() + 1, 8); LC.MachOLoadCommand.load_command_data.cmdsize = NewCmdsize; LC.Payload.assign(NewCmdsize - sizeof(LCType), 0); std::copy(S.begin(), S.end(), LC.Payload.begin()); } static LoadCommand buildRPathLoadCommand(StringRef Path) { LoadCommand LC; MachO::rpath_command RPathLC; RPathLC.cmd = MachO::LC_RPATH; RPathLC.path = sizeof(MachO::rpath_command); RPathLC.cmdsize = alignTo(sizeof(MachO::rpath_command) + Path.size() + 1, 8); LC.MachOLoadCommand.rpath_command_data = RPathLC; LC.Payload.assign(RPathLC.cmdsize - sizeof(MachO::rpath_command), 0); std::copy(Path.begin(), Path.end(), LC.Payload.begin()); return LC; } static Error processLoadCommands(const CopyConfig &Config, Object &Obj) { // Remove RPaths. DenseSet RPathsToRemove(Config.RPathsToRemove.begin(), Config.RPathsToRemove.end()); LoadCommandPred RemovePred = [&RPathsToRemove](const LoadCommand &LC) { if (LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_RPATH) { StringRef RPath = getPayloadString(LC); if (RPathsToRemove.count(RPath)) { RPathsToRemove.erase(RPath); return true; } } return false; }; if (Error E = Obj.removeLoadCommands(RemovePred)) return E; // Emit an error if the Mach-O binary does not contain an rpath path name // specified in -delete_rpath. for (StringRef RPath : Config.RPathsToRemove) { if (RPathsToRemove.count(RPath)) return createStringError(errc::invalid_argument, "no LC_RPATH load command with path: %s", RPath.str().c_str()); } DenseSet RPaths; // Get all existing RPaths. for (LoadCommand &LC : Obj.LoadCommands) { if (LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_RPATH) RPaths.insert(getPayloadString(LC)); } // Throw errors for invalid RPaths. for (const auto &OldNew : Config.RPathsToUpdate) { StringRef Old = OldNew.getFirst(); StringRef New = OldNew.getSecond(); if (RPaths.count(Old) == 0) return createStringError(errc::invalid_argument, "no LC_RPATH load command with path: " + Old); if (RPaths.count(New) != 0) return createStringError(errc::invalid_argument, "rpath " + New + " would create a duplicate load command"); } // Update load commands. for (LoadCommand &LC : Obj.LoadCommands) { switch (LC.MachOLoadCommand.load_command_data.cmd) { case MachO::LC_ID_DYLIB: if (Config.SharedLibId) updateLoadCommandPayloadString( LC, *Config.SharedLibId); break; case MachO::LC_RPATH: { StringRef RPath = getPayloadString(LC); StringRef NewRPath = Config.RPathsToUpdate.lookup(RPath); if (!NewRPath.empty()) updateLoadCommandPayloadString(LC, NewRPath); break; } // TODO: Add LC_REEXPORT_DYLIB, LC_LAZY_LOAD_DYLIB, and LC_LOAD_UPWARD_DYLIB // here once llvm-objcopy supports them. case MachO::LC_LOAD_DYLIB: case MachO::LC_LOAD_WEAK_DYLIB: StringRef InstallName = getPayloadString(LC); StringRef NewInstallName = Config.InstallNamesToUpdate.lookup(InstallName); if (!NewInstallName.empty()) updateLoadCommandPayloadString(LC, NewInstallName); break; } } // Add new RPaths. for (StringRef RPath : Config.RPathToAdd) { if (RPaths.count(RPath) != 0) return createStringError(errc::invalid_argument, "rpath " + RPath + " would create a duplicate load command"); RPaths.insert(RPath); Obj.LoadCommands.push_back(buildRPathLoadCommand(RPath)); } return Error::success(); } static Error dumpSectionToFile(StringRef SecName, StringRef Filename, Object &Obj) { for (LoadCommand &LC : Obj.LoadCommands) for (const std::unique_ptr
&Sec : LC.Sections) { if (Sec->CanonicalName == SecName) { Expected> BufferOrErr = FileOutputBuffer::create(Filename, Sec->Content.size()); if (!BufferOrErr) return BufferOrErr.takeError(); std::unique_ptr Buf = std::move(*BufferOrErr); llvm::copy(Sec->Content, Buf->getBufferStart()); if (Error E = Buf->commit()) return E; return Error::success(); } } return createStringError(object_error::parse_failed, "section '%s' not found", SecName.str().c_str()); } static Error addSection(StringRef SecName, StringRef Filename, Object &Obj) { ErrorOr> BufOrErr = MemoryBuffer::getFile(Filename); if (!BufOrErr) return createFileError(Filename, errorCodeToError(BufOrErr.getError())); std::unique_ptr Buf = std::move(*BufOrErr); std::pair Pair = SecName.split(','); StringRef TargetSegName = Pair.first; Section Sec(TargetSegName, Pair.second); Sec.Content = Obj.NewSectionsContents.save(Buf->getBuffer()); // Add the a section into an existing segment. for (LoadCommand &LC : Obj.LoadCommands) { Optional SegName = LC.getSegmentName(); if (SegName && SegName == TargetSegName) { uint64_t Addr = *LC.getSegmentVMAddr(); for (const std::unique_ptr
&S : LC.Sections) Addr = std::max(Addr, S->Addr + S->Size); LC.Sections.push_back(std::make_unique
(Sec)); LC.Sections.back()->Addr = Addr; return Error::success(); } } // There's no segment named TargetSegName. Create a new load command and // Insert a new section into it. LoadCommand &NewSegment = Obj.addSegment(TargetSegName); NewSegment.Sections.push_back(std::make_unique
(Sec)); NewSegment.Sections.back()->Addr = *NewSegment.getSegmentVMAddr(); return Error::success(); } // isValidMachOCannonicalName returns success if Name is a MachO cannonical name // (",
") and lengths of both segment and section names are // valid. Error isValidMachOCannonicalName(StringRef Name) { if (Name.count(',') != 1) return createStringError(errc::invalid_argument, "invalid section name '%s' (should be formatted " "as ',
')", Name.str().c_str()); std::pair Pair = Name.split(','); if (Pair.first.size() > 16) return createStringError(errc::invalid_argument, "too long segment name: '%s'", Pair.first.str().c_str()); if (Pair.second.size() > 16) return createStringError(errc::invalid_argument, "too long section name: '%s'", Pair.second.str().c_str()); return Error::success(); } static Error handleArgs(const CopyConfig &Config, Object &Obj) { if (Config.AllowBrokenLinks || !Config.BuildIdLinkDir.empty() || Config.BuildIdLinkInput || Config.BuildIdLinkOutput || !Config.SplitDWO.empty() || !Config.SymbolsPrefix.empty() || !Config.AllocSectionsPrefix.empty() || !Config.KeepSection.empty() || Config.NewSymbolVisibility || !Config.SymbolsToGlobalize.empty() || !Config.SymbolsToKeep.empty() || !Config.SymbolsToLocalize.empty() || !Config.SymbolsToWeaken.empty() || !Config.SymbolsToKeepGlobal.empty() || !Config.SectionsToRename.empty() || !Config.UnneededSymbolsToRemove.empty() || !Config.SetSectionAlignment.empty() || !Config.SetSectionFlags.empty() || Config.ExtractDWO || Config.LocalizeHidden || Config.PreserveDates || Config.StripAllGNU || Config.StripDWO || Config.StripNonAlloc || Config.StripSections || Config.Weaken || Config.DecompressDebugSections || Config.StripUnneeded || Config.DiscardMode == DiscardType::Locals || !Config.SymbolsToAdd.empty() || Config.EntryExpr) { return createStringError(llvm::errc::invalid_argument, "option not supported by llvm-objcopy for MachO"); } // Dump sections before add/remove for compatibility with GNU objcopy. for (StringRef Flag : Config.DumpSection) { StringRef SectionName; StringRef FileName; std::tie(SectionName, FileName) = Flag.split('='); if (Error E = dumpSectionToFile(SectionName, FileName, Obj)) return E; } if (Error E = removeSections(Config, Obj)) return E; // Mark symbols to determine which symbols are still needed. if (Config.StripAll) markSymbols(Config, Obj); updateAndRemoveSymbols(Config, Obj); if (Config.StripAll) for (LoadCommand &LC : Obj.LoadCommands) for (std::unique_ptr
&Sec : LC.Sections) Sec->Relocations.clear(); for (const auto &Flag : Config.AddSection) { std::pair SecPair = Flag.split("="); StringRef SecName = SecPair.first; StringRef File = SecPair.second; if (Error E = isValidMachOCannonicalName(SecName)) return E; if (Error E = addSection(SecName, File, Obj)) return E; } if (Error E = processLoadCommands(Config, Obj)) return E; return Error::success(); } Error executeObjcopyOnBinary(const CopyConfig &Config, object::MachOObjectFile &In, Buffer &Out) { MachOReader Reader(In); Expected> O = Reader.create(); if (!O) return createFileError(Config.InputFilename, O.takeError()); if (Error E = handleArgs(Config, **O)) return createFileError(Config.InputFilename, std::move(E)); // Page size used for alignment of segment sizes in Mach-O executables and // dynamic libraries. uint64_t PageSize; switch (In.getArch()) { case Triple::ArchType::arm: case Triple::ArchType::aarch64: case Triple::ArchType::aarch64_32: PageSize = 16384; break; default: PageSize = 4096; } MachOWriter Writer(**O, In.is64Bit(), In.isLittleEndian(), PageSize, Out); if (auto E = Writer.finalize()) return E; return Writer.write(); } +Error executeObjcopyOnMachOUniversalBinary(CopyConfig &Config, + const MachOUniversalBinary &In, + Buffer &Out) { + SmallVector, 2> Binaries; + SmallVector Slices; + for (const auto &O : In.objects()) { + Expected> ArOrErr = O.getAsArchive(); + if (ArOrErr) { + Expected> NewArchiveMembersOrErr = + createNewArchiveMembers(Config, **ArOrErr); + if (!NewArchiveMembersOrErr) + return NewArchiveMembersOrErr.takeError(); + Expected> OutputBufferOrErr = + writeArchiveToBuffer(*NewArchiveMembersOrErr, + (*ArOrErr)->hasSymbolTable(), (*ArOrErr)->kind(), + Config.DeterministicArchives, + (*ArOrErr)->isThin()); + if (!OutputBufferOrErr) + return OutputBufferOrErr.takeError(); + Expected> BinaryOrErr = + object::createBinary(**OutputBufferOrErr); + if (!BinaryOrErr) + return BinaryOrErr.takeError(); + Binaries.emplace_back(std::move(*BinaryOrErr), + std::move(*OutputBufferOrErr)); + Slices.emplace_back(*cast(Binaries.back().getBinary()), + O.getCPUType(), O.getCPUSubType(), + O.getArchFlagName(), O.getAlign()); + continue; + } + // The methods getAsArchive, getAsObjectFile, getAsIRObject of the class + // ObjectForArch return an Error in case of the type mismatch. We need to + // check each in turn to see what kind of slice this is, so ignore errors + // produced along the way. + consumeError(ArOrErr.takeError()); + + Expected> ObjOrErr = O.getAsObjectFile(); + if (!ObjOrErr) { + consumeError(ObjOrErr.takeError()); + return createStringError(std::errc::invalid_argument, + "slice for '%s' of the universal Mach-O binary " + "'%s' is not a Mach-O object or an archive", + O.getArchFlagName().c_str(), + Config.InputFilename.str().c_str()); + } + std::string ArchFlagName = O.getArchFlagName(); + MemBuffer MB(ArchFlagName); + if (Error E = executeObjcopyOnBinary(Config, **ObjOrErr, MB)) + return E; + std::unique_ptr OutputBuffer = + MB.releaseMemoryBuffer(); + Expected> BinaryOrErr = + object::createBinary(*OutputBuffer); + if (!BinaryOrErr) + return BinaryOrErr.takeError(); + Binaries.emplace_back(std::move(*BinaryOrErr), std::move(OutputBuffer)); + Slices.emplace_back(*cast(Binaries.back().getBinary()), + O.getAlign()); + } + Expected> B = + writeUniversalBinaryToBuffer(Slices); + if (!B) + return B.takeError(); + if (Error E = Out.allocate((*B)->getBufferSize())) + return E; + memcpy(Out.getBufferStart(), (*B)->getBufferStart(), (*B)->getBufferSize()); + return Out.commit(); +} + } // end namespace macho } // end namespace objcopy } // end namespace llvm diff --git a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.h b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.h index f34e361db7ea..c3f5391f79b6 100644 --- a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.h +++ b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.h @@ -1,31 +1,35 @@ //===- MachOObjcopy.h -------------------------------------------*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// #ifndef LLVM_TOOLS_OBJCOPY_MACHOOBJCOPY_H #define LLVM_TOOLS_OBJCOPY_MACHOOBJCOPY_H namespace llvm { class Error; namespace object { class MachOObjectFile; class MachOUniversalBinary; } // end namespace object namespace objcopy { struct CopyConfig; class Buffer; namespace macho { Error executeObjcopyOnBinary(const CopyConfig &Config, object::MachOObjectFile &In, Buffer &Out); + +Error executeObjcopyOnMachOUniversalBinary( + CopyConfig &Config, const object::MachOUniversalBinary &In, Buffer &Out); + } // end namespace macho } // end namespace objcopy } // end namespace llvm #endif // LLVM_TOOLS_OBJCOPY_MACHOOBJCOPY_H diff --git a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp index 175f2929eb23..e1f1ed8f3cc1 100644 --- a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp +++ b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp @@ -1,355 +1,375 @@ //===- llvm-objcopy.cpp ---------------------------------------------------===// // // 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 "Buffer.h" #include "COFF/COFFObjcopy.h" #include "CopyConfig.h" #include "ELF/ELFObjcopy.h" #include "MachO/MachOObjcopy.h" #include "wasm/WasmObjcopy.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/Object/Archive.h" #include "llvm/Object/ArchiveWriter.h" #include "llvm/Object/Binary.h" #include "llvm/Object/COFF.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/ELFTypes.h" #include "llvm/Object/Error.h" #include "llvm/Object/MachO.h" +#include "llvm/Object/MachOUniversal.h" #include "llvm/Object/Wasm.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/ErrorOr.h" #include "llvm/Support/Host.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Memory.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" #include "llvm/Support/StringSaver.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include #include #include #include namespace llvm { namespace objcopy { // The name this program was invoked as. StringRef ToolName; ErrorSuccess reportWarning(Error E) { assert(E); WithColor::warning(errs(), ToolName) << toString(std::move(E)) << '\n'; return Error::success(); } } // end namespace objcopy } // end namespace llvm using namespace llvm; using namespace llvm::object; using namespace llvm::objcopy; // For regular archives this function simply calls llvm::writeArchive, // For thin archives it writes the archive file itself as well as its members. static Error deepWriteArchive(StringRef ArcName, ArrayRef NewMembers, bool WriteSymtab, object::Archive::Kind Kind, bool Deterministic, bool Thin) { if (Error E = writeArchive(ArcName, NewMembers, WriteSymtab, Kind, Deterministic, Thin)) return createFileError(ArcName, std::move(E)); if (!Thin) return Error::success(); for (const NewArchiveMember &Member : NewMembers) { // Internally, FileBuffer will use the buffer created by // FileOutputBuffer::create, for regular files (that is the case for // deepWriteArchive) FileOutputBuffer::create will return OnDiskBuffer. // OnDiskBuffer uses a temporary file and then renames it. So in reality // there is no inefficiency / duplicated in-memory buffers in this case. For // now in-memory buffers can not be completely avoided since // NewArchiveMember still requires them even though writeArchive does not // write them on disk. FileBuffer FB(Member.MemberName); if (Error E = FB.allocate(Member.Buf->getBufferSize())) return E; std::copy(Member.Buf->getBufferStart(), Member.Buf->getBufferEnd(), FB.getBufferStart()); if (Error E = FB.commit()) return E; } return Error::success(); } /// The function executeObjcopyOnIHex does the dispatch based on the format /// of the output specified by the command line options. static Error executeObjcopyOnIHex(CopyConfig &Config, MemoryBuffer &In, Buffer &Out) { // TODO: support output formats other than ELF. if (Error E = Config.parseELFConfig()) return E; return elf::executeObjcopyOnIHex(Config, In, Out); } /// The function executeObjcopyOnRawBinary does the dispatch based on the format /// of the output specified by the command line options. static Error executeObjcopyOnRawBinary(CopyConfig &Config, MemoryBuffer &In, Buffer &Out) { switch (Config.OutputFormat) { case FileFormat::ELF: // FIXME: Currently, we call elf::executeObjcopyOnRawBinary even if the // output format is binary/ihex or it's not given. This behavior differs from // GNU objcopy. See https://bugs.llvm.org/show_bug.cgi?id=42171 for details. case FileFormat::Binary: case FileFormat::IHex: case FileFormat::Unspecified: if (Error E = Config.parseELFConfig()) return E; return elf::executeObjcopyOnRawBinary(Config, In, Out); } llvm_unreachable("unsupported output format"); } /// The function executeObjcopyOnBinary does the dispatch based on the format /// of the input binary (ELF, MachO or COFF). static Error executeObjcopyOnBinary(CopyConfig &Config, object::Binary &In, Buffer &Out) { if (auto *ELFBinary = dyn_cast(&In)) { if (Error E = Config.parseELFConfig()) return E; return elf::executeObjcopyOnBinary(Config, *ELFBinary, Out); } else if (auto *COFFBinary = dyn_cast(&In)) return coff::executeObjcopyOnBinary(Config, *COFFBinary, Out); else if (auto *MachOBinary = dyn_cast(&In)) return macho::executeObjcopyOnBinary(Config, *MachOBinary, Out); + else if (auto *MachOUniversalBinary = + dyn_cast(&In)) + return macho::executeObjcopyOnMachOUniversalBinary( + Config, *MachOUniversalBinary, Out); else if (auto *WasmBinary = dyn_cast(&In)) return objcopy::wasm::executeObjcopyOnBinary(Config, *WasmBinary, Out); else return createStringError(object_error::invalid_file_type, "unsupported object file format"); } -static Error executeObjcopyOnArchive(CopyConfig &Config, const Archive &Ar) { +namespace llvm { +namespace objcopy { + +Expected> +createNewArchiveMembers(CopyConfig &Config, const Archive &Ar) { std::vector NewArchiveMembers; Error Err = Error::success(); for (const Archive::Child &Child : Ar.children(Err)) { Expected ChildNameOrErr = Child.getName(); if (!ChildNameOrErr) return createFileError(Ar.getFileName(), ChildNameOrErr.takeError()); Expected> ChildOrErr = Child.getAsBinary(); if (!ChildOrErr) return createFileError(Ar.getFileName() + "(" + *ChildNameOrErr + ")", ChildOrErr.takeError()); MemBuffer MB(ChildNameOrErr.get()); if (Error E = executeObjcopyOnBinary(Config, *ChildOrErr->get(), MB)) - return E; + return std::move(E); Expected Member = NewArchiveMember::getOldMember(Child, Config.DeterministicArchives); if (!Member) return createFileError(Ar.getFileName(), Member.takeError()); Member->Buf = MB.releaseMemoryBuffer(); Member->MemberName = Member->Buf->getBufferIdentifier(); NewArchiveMembers.push_back(std::move(*Member)); } if (Err) return createFileError(Config.InputFilename, std::move(Err)); + return std::move(NewArchiveMembers); +} + +} // end namespace objcopy +} // end namespace llvm - return deepWriteArchive(Config.OutputFilename, NewArchiveMembers, +static Error executeObjcopyOnArchive(CopyConfig &Config, + const object::Archive &Ar) { + Expected> NewArchiveMembersOrErr = + createNewArchiveMembers(Config, Ar); + if (!NewArchiveMembersOrErr) + return NewArchiveMembersOrErr.takeError(); + return deepWriteArchive(Config.OutputFilename, *NewArchiveMembersOrErr, Ar.hasSymbolTable(), Ar.kind(), Config.DeterministicArchives, Ar.isThin()); } static Error restoreStatOnFile(StringRef Filename, const sys::fs::file_status &Stat, bool PreserveDates) { int FD; // Writing to stdout should not be treated as an error here, just // do not set access/modification times or permissions. if (Filename == "-") return Error::success(); if (auto EC = sys::fs::openFileForWrite(Filename, FD, sys::fs::CD_OpenExisting)) return createFileError(Filename, EC); if (PreserveDates) if (auto EC = sys::fs::setLastAccessAndModificationTime( FD, Stat.getLastAccessedTime(), Stat.getLastModificationTime())) return createFileError(Filename, EC); sys::fs::file_status OStat; if (std::error_code EC = sys::fs::status(FD, OStat)) return createFileError(Filename, EC); if (OStat.type() == sys::fs::file_type::regular_file) #ifdef _WIN32 if (auto EC = sys::fs::setPermissions( Filename, static_cast(Stat.permissions() & ~sys::fs::getUmask()))) #else if (auto EC = sys::fs::setPermissions( FD, static_cast(Stat.permissions() & ~sys::fs::getUmask()))) #endif return createFileError(Filename, EC); if (auto EC = sys::Process::SafelyCloseFileDescriptor(FD)) return createFileError(Filename, EC); return Error::success(); } /// The function executeObjcopy does the higher level dispatch based on the type /// of input (raw binary, archive or single object file) and takes care of the /// format-agnostic modifications, i.e. preserving dates. static Error executeObjcopy(CopyConfig &Config) { sys::fs::file_status Stat; if (Config.InputFilename != "-") { if (auto EC = sys::fs::status(Config.InputFilename, Stat)) return createFileError(Config.InputFilename, EC); } else { Stat.permissions(static_cast(0777)); } using ProcessRawFn = Error (*)(CopyConfig &, MemoryBuffer &, Buffer &); ProcessRawFn ProcessRaw; switch (Config.InputFormat) { case FileFormat::Binary: ProcessRaw = executeObjcopyOnRawBinary; break; case FileFormat::IHex: ProcessRaw = executeObjcopyOnIHex; break; default: ProcessRaw = nullptr; } if (ProcessRaw) { auto BufOrErr = MemoryBuffer::getFileOrSTDIN(Config.InputFilename); if (!BufOrErr) return createFileError(Config.InputFilename, BufOrErr.getError()); FileBuffer FB(Config.OutputFilename); if (Error E = ProcessRaw(Config, *BufOrErr->get(), FB)) return E; } else { Expected> BinaryOrErr = createBinary(Config.InputFilename); if (!BinaryOrErr) return createFileError(Config.InputFilename, BinaryOrErr.takeError()); if (Archive *Ar = dyn_cast(BinaryOrErr.get().getBinary())) { if (Error E = executeObjcopyOnArchive(Config, *Ar)) return E; } else { FileBuffer FB(Config.OutputFilename); if (Error E = executeObjcopyOnBinary(Config, *BinaryOrErr.get().getBinary(), FB)) return E; } } if (Error E = restoreStatOnFile(Config.OutputFilename, Stat, Config.PreserveDates)) return E; if (!Config.SplitDWO.empty()) { Stat.permissions(static_cast(0666)); if (Error E = restoreStatOnFile(Config.SplitDWO, Stat, Config.PreserveDates)) return E; } return Error::success(); } namespace { enum class ToolType { Objcopy, Strip, InstallNameTool, BitcodeStrip }; } // anonymous namespace int main(int argc, char **argv) { InitLLVM X(argc, argv); ToolName = argv[0]; StringRef Stem = sys::path::stem(ToolName); auto Is = [=](StringRef Tool) { // We need to recognize the following filenames: // // llvm-objcopy -> objcopy // strip-10.exe -> strip // powerpc64-unknown-freebsd13-objcopy -> objcopy // llvm-install-name-tool -> install-name-tool auto I = Stem.rfind_lower(Tool); return I != StringRef::npos && (I + Tool.size() == Stem.size() || !isAlnum(Stem[I + Tool.size()])); }; ToolType Tool = ToolType::Objcopy; if (Is("bitcode-strip") || Is("bitcode_strip")) Tool = ToolType::BitcodeStrip; else if (Is("strip")) Tool = ToolType::Strip; else if (Is("install-name-tool") || Is("install_name_tool")) Tool = ToolType::InstallNameTool; // Expand response files. // TODO: Move these lines, which are copied from lib/Support/CommandLine.cpp, // into a separate function in the CommandLine library and call that function // here. This is duplicated code. SmallVector NewArgv(argv, argv + argc); BumpPtrAllocator A; StringSaver Saver(A); cl::ExpandResponseFiles(Saver, Triple(sys::getProcessTriple()).isOSWindows() ? cl::TokenizeWindowsCommandLine : cl::TokenizeGNUCommandLine, NewArgv); auto Args = makeArrayRef(NewArgv).drop_front(); Expected DriverConfig = (Tool == ToolType::Strip) ? parseStripOptions(Args, reportWarning) : ((Tool == ToolType::InstallNameTool) ? parseInstallNameToolOptions(Args) : ((Tool == ToolType::BitcodeStrip) ? parseBitcodeStripOptions(Args) : parseObjcopyOptions(Args, reportWarning))); if (!DriverConfig) { logAllUnhandledErrors(DriverConfig.takeError(), WithColor::error(errs(), ToolName)); return 1; } for (CopyConfig &CopyConfig : DriverConfig->CopyConfigs) { if (Error E = executeObjcopy(CopyConfig)) { logAllUnhandledErrors(std::move(E), WithColor::error(errs(), ToolName)); return 1; } } return 0; } diff --git a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.h b/llvm/tools/llvm-objcopy/llvm-objcopy.h similarity index 51% copy from llvm/tools/llvm-objcopy/MachO/MachOObjcopy.h copy to llvm/tools/llvm-objcopy/llvm-objcopy.h index f34e361db7ea..97a166769f95 100644 --- a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.h +++ b/llvm/tools/llvm-objcopy/llvm-objcopy.h @@ -1,31 +1,32 @@ -//===- MachOObjcopy.h -------------------------------------------*- C++ -*-===// +//===- llvm-objcopy.h -------------------------------------------*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// -#ifndef LLVM_TOOLS_OBJCOPY_MACHOOBJCOPY_H -#define LLVM_TOOLS_OBJCOPY_MACHOOBJCOPY_H +#ifndef LLVM_TOOLS_OBJCOPY_OBJCOPY_H +#define LLVM_TOOLS_OBJCOPY_OBJCOPY_H + +#include "llvm/Support/Error.h" namespace llvm { -class Error; + +struct NewArchiveMember; namespace object { -class MachOObjectFile; -class MachOUniversalBinary; + +class Archive; + } // end namespace object namespace objcopy { struct CopyConfig; -class Buffer; +Expected> +createNewArchiveMembers(CopyConfig &Config, const object::Archive &Ar); -namespace macho { -Error executeObjcopyOnBinary(const CopyConfig &Config, - object::MachOObjectFile &In, Buffer &Out); -} // end namespace macho } // end namespace objcopy } // end namespace llvm -#endif // LLVM_TOOLS_OBJCOPY_MACHOOBJCOPY_H +#endif // LLVM_TOOLS_OBJCOPY_OBJCOPY_H