//===-- llvm-lipo.cpp - a tool for manipulating universal binaries --------===// // // 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 // //===----------------------------------------------------------------------===// // // A utility for creating / splitting / inspecting universal binaries. // //===----------------------------------------------------------------------===// #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Triple.h" #include "llvm/Object/Binary.h" #include "llvm/Object/MachO.h" #include "llvm/Object/MachOUniversal.h" #include "llvm/Object/MachOUniversalWriter.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/WithColor.h" #include "llvm/TextAPI/MachO/Architecture.h" using namespace llvm; using namespace llvm::object; static const StringRef ToolName = "llvm-lipo"; LLVM_ATTRIBUTE_NORETURN static void reportError(Twine Message) { WithColor::error(errs(), ToolName) << Message << "\n"; errs().flush(); exit(EXIT_FAILURE); } LLVM_ATTRIBUTE_NORETURN static void reportError(Error E) { assert(E); std::string Buf; raw_string_ostream OS(Buf); logAllUnhandledErrors(std::move(E), OS); OS.flush(); reportError(Buf); } LLVM_ATTRIBUTE_NORETURN static void reportError(StringRef File, Error E) { assert(E); std::string Buf; raw_string_ostream OS(Buf); logAllUnhandledErrors(std::move(E), OS); OS.flush(); WithColor::error(errs(), ToolName) << "'" << File << "': " << Buf; exit(EXIT_FAILURE); } namespace { enum LipoID { LIPO_INVALID = 0, // This is not an option ID. #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ LIPO_##ID, #include "LipoOpts.inc" #undef OPTION }; // LipoInfoTable below references LIPO_##PREFIX. OptionGroup has prefix nullptr. const char *const *LIPO_nullptr = nullptr; #define PREFIX(NAME, VALUE) const char *const LIPO_##NAME[] = VALUE; #include "LipoOpts.inc" #undef PREFIX static const opt::OptTable::Info LipoInfoTable[] = { #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ {LIPO_##PREFIX, NAME, HELPTEXT, \ METAVAR, LIPO_##ID, opt::Option::KIND##Class, \ PARAM, FLAGS, LIPO_##GROUP, \ LIPO_##ALIAS, ALIASARGS, VALUES}, #include "LipoOpts.inc" #undef OPTION }; class LipoOptTable : public opt::OptTable { public: LipoOptTable() : OptTable(LipoInfoTable) {} }; enum class LipoAction { PrintArchs, PrintInfo, VerifyArch, ThinArch, ExtractArch, CreateUniversal, ReplaceArch, }; struct InputFile { Optional ArchType; StringRef FileName; }; struct Config { SmallVector InputFiles; SmallVector VerifyArchList; SmallVector ReplacementFiles; StringMap SegmentAlignments; std::string ArchType; std::string OutputFile; LipoAction ActionToPerform; }; static Slice archiveSlice(const Archive *A, StringRef File) { Expected ArchiveOrSlice = Slice::create(A); if (!ArchiveOrSlice) reportError(File, ArchiveOrSlice.takeError()); return *ArchiveOrSlice; } } // end namespace static void validateArchitectureName(StringRef ArchitectureName) { if (!MachOObjectFile::isValidArch(ArchitectureName)) { std::string Buf; raw_string_ostream OS(Buf); OS << "Invalid architecture: " << ArchitectureName << "\nValid architecture names are:"; for (auto arch : MachOObjectFile::getValidArchs()) OS << " " << arch; reportError(OS.str()); } } static Config parseLipoOptions(ArrayRef ArgsArr) { Config C; LipoOptTable T; unsigned MissingArgumentIndex, MissingArgumentCount; opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MissingArgumentIndex, MissingArgumentCount); if (MissingArgumentCount) reportError("missing argument to " + StringRef(InputArgs.getArgString(MissingArgumentIndex)) + " option"); if (InputArgs.size() == 0) { // PrintHelp does not accept Twine. T.PrintHelp(errs(), "llvm-lipo input[s] option[s]", "llvm-lipo"); exit(EXIT_FAILURE); } if (InputArgs.hasArg(LIPO_help)) { // PrintHelp does not accept Twine. T.PrintHelp(outs(), "llvm-lipo input[s] option[s]", "llvm-lipo"); exit(EXIT_SUCCESS); } if (InputArgs.hasArg(LIPO_version)) { outs() << ToolName + "\n"; cl::PrintVersionMessage(); exit(EXIT_SUCCESS); } for (auto Arg : InputArgs.filtered(LIPO_UNKNOWN)) reportError("unknown argument '" + Arg->getAsString(InputArgs) + "'"); for (auto Arg : InputArgs.filtered(LIPO_INPUT)) C.InputFiles.push_back({None, Arg->getValue()}); for (auto Arg : InputArgs.filtered(LIPO_arch)) { validateArchitectureName(Arg->getValue(0)); if (!Arg->getValue(1)) reportError( "arch is missing an argument: expects -arch arch_type file_name"); C.InputFiles.push_back({StringRef(Arg->getValue(0)), Arg->getValue(1)}); } if (C.InputFiles.empty()) reportError("at least one input file should be specified"); if (InputArgs.hasArg(LIPO_output)) C.OutputFile = std::string(InputArgs.getLastArgValue(LIPO_output)); for (auto Segalign : InputArgs.filtered(LIPO_segalign)) { if (!Segalign->getValue(1)) reportError("segalign is missing an argument: expects -segalign " "arch_type alignment_value"); validateArchitectureName(Segalign->getValue(0)); uint32_t AlignmentValue; if (!to_integer(Segalign->getValue(1), AlignmentValue, 16)) reportError("argument to -segalign " + Twine(Segalign->getValue(1)) + " (hex) is not a proper hexadecimal number"); if (!isPowerOf2_32(AlignmentValue)) reportError("argument to -segalign " + Twine(Segalign->getValue(1)) + " (hex) must be a non-zero power of two"); if (Log2_32(AlignmentValue) > MachOUniversalBinary::MaxSectionAlignment) reportError( "argument to -segalign " + Twine(Segalign->getValue(1)) + " (hex) must be less than or equal to the maximum section align 2^" + Twine(MachOUniversalBinary::MaxSectionAlignment)); auto Entry = C.SegmentAlignments.try_emplace(Segalign->getValue(0), Log2_32(AlignmentValue)); if (!Entry.second) reportError("-segalign " + Twine(Segalign->getValue(0)) + " specified multiple times: " + Twine(1 << Entry.first->second) + ", " + Twine(AlignmentValue)); } SmallVector ActionArgs(InputArgs.filtered(LIPO_action_group)); if (ActionArgs.empty()) reportError("at least one action should be specified"); // errors if multiple actions specified other than replace // multiple replace flags may be specified, as long as they are not mixed with // other action flags auto ReplacementArgsRange = InputArgs.filtered(LIPO_replace); if (ActionArgs.size() > 1 && ActionArgs.size() != static_cast(std::distance(ReplacementArgsRange.begin(), ReplacementArgsRange.end()))) { std::string Buf; raw_string_ostream OS(Buf); OS << "only one of the following actions can be specified:"; for (auto Arg : ActionArgs) OS << " " << Arg->getSpelling(); reportError(OS.str()); } switch (ActionArgs[0]->getOption().getID()) { case LIPO_verify_arch: for (auto A : InputArgs.getAllArgValues(LIPO_verify_arch)) C.VerifyArchList.push_back(A); if (C.VerifyArchList.empty()) reportError( "verify_arch requires at least one architecture to be specified"); if (C.InputFiles.size() > 1) reportError("verify_arch expects a single input file"); C.ActionToPerform = LipoAction::VerifyArch; return C; case LIPO_archs: if (C.InputFiles.size() > 1) reportError("archs expects a single input file"); C.ActionToPerform = LipoAction::PrintArchs; return C; case LIPO_info: C.ActionToPerform = LipoAction::PrintInfo; return C; case LIPO_thin: if (C.InputFiles.size() > 1) reportError("thin expects a single input file"); if (C.OutputFile.empty()) reportError("thin expects a single output file"); C.ArchType = ActionArgs[0]->getValue(); validateArchitectureName(C.ArchType); C.ActionToPerform = LipoAction::ThinArch; return C; case LIPO_extract: if (C.InputFiles.size() > 1) reportError("extract expects a single input file"); if (C.OutputFile.empty()) reportError("extract expects a single output file"); C.ArchType = ActionArgs[0]->getValue(); validateArchitectureName(C.ArchType); C.ActionToPerform = LipoAction::ExtractArch; return C; case LIPO_create: if (C.OutputFile.empty()) reportError("create expects a single output file to be specified"); C.ActionToPerform = LipoAction::CreateUniversal; return C; case LIPO_replace: for (auto Action : ActionArgs) { if (!Action->getValue(1)) reportError( "replace is missing an argument: expects -replace arch_type " "file_name"); validateArchitectureName(Action->getValue(0)); C.ReplacementFiles.push_back( {StringRef(Action->getValue(0)), Action->getValue(1)}); } if (C.OutputFile.empty()) reportError("replace expects a single output file to be specified"); if (C.InputFiles.size() > 1) reportError("replace expects a single input file"); C.ActionToPerform = LipoAction::ReplaceArch; return C; default: reportError("llvm-lipo action unspecified"); } } static SmallVector, 1> readInputBinaries(ArrayRef InputFiles) { SmallVector, 1> InputBinaries; for (const InputFile &IF : InputFiles) { Expected> BinaryOrErr = createBinary(IF.FileName); if (!BinaryOrErr) reportError(IF.FileName, BinaryOrErr.takeError()); const Binary *B = BinaryOrErr->getBinary(); if (!B->isArchive() && !B->isMachO() && !B->isMachOUniversalBinary()) reportError("File " + IF.FileName + " has unsupported binary format"); if (IF.ArchType && (B->isMachO() || B->isArchive())) { const auto S = B->isMachO() ? Slice(*cast(B)) : archiveSlice(cast(B), IF.FileName); const auto SpecifiedCPUType = MachO::getCPUTypeFromArchitecture( MachO::getArchitectureFromName( Triple(*IF.ArchType).getArchName())) .first; // For compatibility with cctools' lipo the comparison is relaxed just to // checking cputypes. if (S.getCPUType() != SpecifiedCPUType) reportError("specified architecture: " + *IF.ArchType + " for file: " + B->getFileName() + " does not match the file's architecture (" + S.getArchString() + ")"); } InputBinaries.push_back(std::move(*BinaryOrErr)); } return InputBinaries; } LLVM_ATTRIBUTE_NORETURN static void verifyArch(ArrayRef> InputBinaries, ArrayRef VerifyArchList) { assert(!VerifyArchList.empty() && "The list of architectures should be non-empty"); assert(InputBinaries.size() == 1 && "Incorrect number of input binaries"); for (StringRef Arch : VerifyArchList) validateArchitectureName(Arch); if (auto UO = dyn_cast(InputBinaries.front().getBinary())) { for (StringRef Arch : VerifyArchList) { Expected Obj = UO->getObjectForArch(Arch); if (!Obj) exit(EXIT_FAILURE); } } else if (auto O = dyn_cast(InputBinaries.front().getBinary())) { const Triple::ArchType ObjectArch = O->getArch(); for (StringRef Arch : VerifyArchList) if (ObjectArch != Triple(Arch).getArch()) exit(EXIT_FAILURE); } else { llvm_unreachable("Unexpected binary format"); } exit(EXIT_SUCCESS); } static void printBinaryArchs(const Binary *Binary, raw_ostream &OS) { // Prints trailing space for compatibility with cctools lipo. if (auto UO = dyn_cast(Binary)) { for (const auto &O : UO->objects()) { Expected> MachOObjOrError = O.getAsObjectFile(); if (MachOObjOrError) { OS << Slice(*(MachOObjOrError->get())).getArchString() << " "; continue; } Expected> ArchiveOrError = O.getAsArchive(); if (ArchiveOrError) { consumeError(MachOObjOrError.takeError()); OS << archiveSlice(ArchiveOrError->get(), Binary->getFileName()) .getArchString() << " "; continue; } consumeError(ArchiveOrError.takeError()); reportError(Binary->getFileName(), MachOObjOrError.takeError()); } OS << "\n"; return; } OS << Slice(*cast(Binary)).getArchString() << " \n"; } LLVM_ATTRIBUTE_NORETURN static void printArchs(ArrayRef> InputBinaries) { assert(InputBinaries.size() == 1 && "Incorrect number of input binaries"); printBinaryArchs(InputBinaries.front().getBinary(), outs()); exit(EXIT_SUCCESS); } LLVM_ATTRIBUTE_NORETURN static void printInfo(ArrayRef> InputBinaries) { // Group universal and thin files together for compatibility with cctools lipo for (auto &IB : InputBinaries) { const Binary *Binary = IB.getBinary(); if (Binary->isMachOUniversalBinary()) { outs() << "Architectures in the fat file: " << Binary->getFileName() << " are: "; printBinaryArchs(Binary, outs()); } } for (auto &IB : InputBinaries) { const Binary *Binary = IB.getBinary(); if (!Binary->isMachOUniversalBinary()) { assert(Binary->isMachO() && "expected MachO binary"); outs() << "Non-fat file: " << Binary->getFileName() << " is architecture: "; printBinaryArchs(Binary, outs()); } } exit(EXIT_SUCCESS); } LLVM_ATTRIBUTE_NORETURN static void thinSlice(ArrayRef> InputBinaries, StringRef ArchType, StringRef OutputFileName) { assert(!ArchType.empty() && "The architecture type should be non-empty"); assert(InputBinaries.size() == 1 && "Incorrect number of input binaries"); assert(!OutputFileName.empty() && "Thin expects a single output file"); if (InputBinaries.front().getBinary()->isMachO()) { reportError("input file " + InputBinaries.front().getBinary()->getFileName() + " must be a fat file when the -thin option is specified"); exit(EXIT_FAILURE); } auto *UO = cast(InputBinaries.front().getBinary()); Expected> Obj = UO->getMachOObjectForArch(ArchType); Expected> Ar = UO->getArchiveForArch(ArchType); if (!Obj && !Ar) reportError("fat input file " + UO->getFileName() + " does not contain the specified architecture " + ArchType + " to thin it to"); Binary *B = Obj ? static_cast(Obj->get()) : static_cast(Ar->get()); Expected> OutFileOrError = FileOutputBuffer::create(OutputFileName, B->getMemoryBufferRef().getBufferSize(), sys::fs::can_execute(UO->getFileName()) ? FileOutputBuffer::F_executable : 0); if (!OutFileOrError) reportError(OutputFileName, OutFileOrError.takeError()); std::copy(B->getMemoryBufferRef().getBufferStart(), B->getMemoryBufferRef().getBufferEnd(), OutFileOrError.get()->getBufferStart()); if (Error E = OutFileOrError.get()->commit()) reportError(OutputFileName, std::move(E)); exit(EXIT_SUCCESS); } static void checkArchDuplicates(ArrayRef Slices) { DenseMap CPUIds; for (const auto &S : Slices) { auto Entry = CPUIds.try_emplace(S.getCPUID(), S.getBinary()); if (!Entry.second) reportError(Entry.first->second->getFileName() + " and " + S.getBinary()->getFileName() + " have the same architecture " + S.getArchString() + " and therefore cannot be in the same universal binary"); } } template static void updateAlignments(Range &Slices, const StringMap &Alignments) { for (auto &Slice : Slices) { auto Alignment = Alignments.find(Slice.getArchString()); if (Alignment != Alignments.end()) Slice.setP2Alignment(Alignment->second); } } static void checkUnusedAlignments(ArrayRef Slices, const StringMap &Alignments) { auto HasArch = [&](StringRef Arch) { return llvm::find_if(Slices, [Arch](Slice S) { return S.getArchString() == Arch; }) != Slices.end(); }; for (StringRef Arch : Alignments.keys()) if (!HasArch(Arch)) reportError("-segalign " + Arch + " specified but resulting fat file does not contain " "that architecture "); } // Updates vector ExtractedObjects with the MachOObjectFiles extracted from // Universal Binary files to transfer ownership. static SmallVector buildSlices( ArrayRef> InputBinaries, const StringMap &Alignments, SmallVectorImpl> &ExtractedObjects) { SmallVector Slices; for (auto &IB : InputBinaries) { const Binary *InputBinary = IB.getBinary(); if (auto UO = dyn_cast(InputBinary)) { for (const auto &O : UO->objects()) { Expected> BinaryOrError = O.getAsObjectFile(); if (!BinaryOrError) reportError(InputBinary->getFileName(), BinaryOrError.takeError()); ExtractedObjects.push_back(std::move(BinaryOrError.get())); Slices.emplace_back(*(ExtractedObjects.back().get()), O.getAlign()); } } else if (auto O = dyn_cast(InputBinary)) { Slices.emplace_back(*O); } else if (auto A = dyn_cast(InputBinary)) { Slices.push_back(archiveSlice(A, InputBinary->getFileName())); } else { llvm_unreachable("Unexpected binary format"); } } updateAlignments(Slices, Alignments); return Slices; } LLVM_ATTRIBUTE_NORETURN static void createUniversalBinary(ArrayRef> InputBinaries, const StringMap &Alignments, StringRef OutputFileName) { assert(InputBinaries.size() >= 1 && "Incorrect number of input binaries"); assert(!OutputFileName.empty() && "Create expects a single output file"); SmallVector, 1> ExtractedObjects; SmallVector Slices = buildSlices(InputBinaries, Alignments, ExtractedObjects); checkArchDuplicates(Slices); checkUnusedAlignments(Slices, Alignments); llvm::stable_sort(Slices); if (Error E = writeUniversalBinary(Slices, OutputFileName)) reportError(std::move(E)); exit(EXIT_SUCCESS); } LLVM_ATTRIBUTE_NORETURN static void extractSlice(ArrayRef> InputBinaries, const StringMap &Alignments, StringRef ArchType, StringRef OutputFileName) { assert(!ArchType.empty() && "The architecture type should be non-empty"); assert(InputBinaries.size() == 1 && "Incorrect number of input binaries"); assert(!OutputFileName.empty() && "Thin expects a single output file"); if (InputBinaries.front().getBinary()->isMachO()) { reportError("input file " + InputBinaries.front().getBinary()->getFileName() + " must be a fat file when the -extract option is specified"); } SmallVector, 2> ExtractedObjects; SmallVector Slices = buildSlices(InputBinaries, Alignments, ExtractedObjects); erase_if(Slices, [ArchType](const Slice &S) { return ArchType != S.getArchString(); }); if (Slices.empty()) reportError( "fat input file " + InputBinaries.front().getBinary()->getFileName() + " does not contain the specified architecture " + ArchType); llvm::stable_sort(Slices); if (Error E = writeUniversalBinary(Slices, OutputFileName)) reportError(std::move(E)); exit(EXIT_SUCCESS); } static StringMap buildReplacementSlices(ArrayRef> ReplacementBinaries, const StringMap &Alignments) { StringMap Slices; // populates StringMap of slices to replace with; error checks for mismatched // replace flag args, fat files, and duplicate arch_types for (const auto &OB : ReplacementBinaries) { const Binary *ReplacementBinary = OB.getBinary(); auto O = dyn_cast(ReplacementBinary); if (!O) reportError("replacement file: " + ReplacementBinary->getFileName() + " is a fat file (must be a thin file)"); Slice S(*O); auto Entry = Slices.try_emplace(S.getArchString(), S); if (!Entry.second) reportError("-replace " + S.getArchString() + " specified multiple times: " + Entry.first->second.getBinary()->getFileName() + ", " + O->getFileName()); } auto SlicesMapRange = map_range( Slices, [](StringMapEntry &E) -> Slice & { return E.getValue(); }); updateAlignments(SlicesMapRange, Alignments); return Slices; } LLVM_ATTRIBUTE_NORETURN static void replaceSlices(ArrayRef> InputBinaries, const StringMap &Alignments, StringRef OutputFileName, ArrayRef ReplacementFiles) { assert(InputBinaries.size() == 1 && "Incorrect number of input binaries"); assert(!OutputFileName.empty() && "Replace expects a single output file"); if (InputBinaries.front().getBinary()->isMachO()) reportError("input file " + InputBinaries.front().getBinary()->getFileName() + " must be a fat file when the -replace option is specified"); SmallVector, 1> ReplacementBinaries = readInputBinaries(ReplacementFiles); StringMap ReplacementSlices = buildReplacementSlices(ReplacementBinaries, Alignments); SmallVector, 2> ExtractedObjects; SmallVector Slices = buildSlices(InputBinaries, Alignments, ExtractedObjects); for (auto &Slice : Slices) { auto It = ReplacementSlices.find(Slice.getArchString()); if (It != ReplacementSlices.end()) { Slice = It->second; ReplacementSlices.erase(It); // only keep remaining replacing arch_types } } if (!ReplacementSlices.empty()) reportError("-replace " + ReplacementSlices.begin()->first() + " specified but fat file: " + InputBinaries.front().getBinary()->getFileName() + " does not contain that architecture"); checkUnusedAlignments(Slices, Alignments); llvm::stable_sort(Slices); if (Error E = writeUniversalBinary(Slices, OutputFileName)) reportError(std::move(E)); exit(EXIT_SUCCESS); } int main(int argc, char **argv) { InitLLVM X(argc, argv); Config C = parseLipoOptions(makeArrayRef(argv + 1, argc)); SmallVector, 1> InputBinaries = readInputBinaries(C.InputFiles); switch (C.ActionToPerform) { case LipoAction::VerifyArch: verifyArch(InputBinaries, C.VerifyArchList); break; case LipoAction::PrintArchs: printArchs(InputBinaries); break; case LipoAction::PrintInfo: printInfo(InputBinaries); break; case LipoAction::ThinArch: thinSlice(InputBinaries, C.ArchType, C.OutputFile); break; case LipoAction::ExtractArch: extractSlice(InputBinaries, C.SegmentAlignments, C.ArchType, C.OutputFile); break; case LipoAction::CreateUniversal: createUniversalBinary(InputBinaries, C.SegmentAlignments, C.OutputFile); break; case LipoAction::ReplaceArch: replaceSlices(InputBinaries, C.SegmentAlignments, C.OutputFile, C.ReplacementFiles); break; } return EXIT_SUCCESS; }