blob: a063fcec7574c46e5e1c8c722d8b8aeee79fc24e [file] [log] [blame]
//===-- 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/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(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,
CreateUniversal,
ReplaceArch,
};
struct InputFile {
Optional<StringRef> ArchType;
StringRef FileName;
};
struct Config {
SmallVector<InputFile, 1> InputFiles;
SmallVector<std::string, 1> VerifyArchList;
SmallVector<InputFile, 1> ReplacementFiles;
StringMap<const uint32_t> SegmentAlignments;
std::string ThinArchType;
std::string OutputFile;
LipoAction ActionToPerform;
};
// 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<uint32_t>(2),
std::min(P2MinAlignment, static_cast<uint32_t>(
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);
}
}
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;
public:
Slice(const MachOObjectFile *O, uint32_t Align)
: B(O), CPUType(O->getHeader().cputype),
CPUSubType(O->getHeader().cpusubtype),
ArchName(O->getArchTriple().getArchName()), P2Alignment(Align) {}
explicit Slice(const MachOObjectFile *O) : Slice(O, calculateAlignment(O)){};
explicit Slice(const Archive *A) : B(A) {
Error Err = Error::success();
std::unique_ptr<MachOObjectFile> FO = nullptr;
for (const Archive::Child &Child : A->children(Err)) {
Expected<std::unique_ptr<Binary>> ChildOrErr = Child.getAsBinary();
if (!ChildOrErr)
reportError(A->getFileName(), ChildOrErr.takeError());
Binary *Bin = ChildOrErr.get().get();
if (Bin->isMachOUniversalBinary())
reportError(("archive member " + Bin->getFileName() +
" is a fat file (not allowed in an archive)")
.str());
if (!Bin->isMachO())
reportError(("archive member " + Bin->getFileName() +
" is not a MachO file (not allowed in an archive)"));
MachOObjectFile *O = cast<MachOObjectFile>(Bin);
if (FO &&
std::tie(FO->getHeader().cputype, FO->getHeader().cpusubtype) !=
std::tie(O->getHeader().cputype, O->getHeader().cpusubtype)) {
reportError(("archive member " + O->getFileName() + " cputype (" +
Twine(O->getHeader().cputype) + ") and cpusubtype(" +
Twine(O->getHeader().cpusubtype) +
") does not match previous archive members cputype (" +
Twine(FO->getHeader().cputype) + ") and cpusubtype(" +
Twine(FO->getHeader().cpusubtype) +
") (all members must match) " + FO->getFileName())
.str());
}
if (!FO) {
ChildOrErr.get().release();
FO.reset(O);
}
}
if (Err)
reportError(A->getFileName(), std::move(Err));
if (!FO)
reportError(("empty archive with no architecture specification: " +
A->getFileName() + " (can't determine architecture for it)")
.str());
CPUType = FO->getHeader().cputype;
CPUSubType = FO->getHeader().cpusubtype;
ArchName = FO->getArchTriple().getArchName();
// Replicate the behavior of cctools lipo.
P2Alignment = FO->is64Bit() ? 3 : 2;
}
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<uint64_t>(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;
}
};
} // 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<const char *> 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 = 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<uint32_t>(Segalign->getValue(1), AlignmentValue, 16))
reportError("argument to -segalign <arch_type> " +
Twine(Segalign->getValue(1)) +
" (hex) is not a proper hexadecimal number");
if (!isPowerOf2_32(AlignmentValue))
reportError("argument to -segalign <arch_type> " +
Twine(Segalign->getValue(1)) +
" (hex) must be a non-zero power of two");
if (Log2_32(AlignmentValue) > MachOUniversalBinary::MaxSectionAlignment)
reportError(
"argument to -segalign <arch_type> " + 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)) +
" <alignment_value> specified multiple times: " +
Twine(1 << Entry.first->second) + ", " +
Twine(AlignmentValue));
}
SmallVector<opt::Arg *, 1> 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<size_t>(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");
C.ThinArchType = ActionArgs[0]->getValue();
validateArchitectureName(C.ThinArchType);
if (C.OutputFile.empty())
reportError("thin expects a single output file");
C.ActionToPerform = LipoAction::ThinArch;
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<OwningBinary<Binary>, 1>
readInputBinaries(ArrayRef<InputFile> InputFiles) {
SmallVector<OwningBinary<Binary>, 1> InputBinaries;
for (const InputFile &IF : InputFiles) {
Expected<OwningBinary<Binary>> 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<MachOObjectFile>(B))
: Slice(cast<Archive>(B));
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<OwningBinary<Binary>> InputBinaries,
ArrayRef<std::string> 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<MachOUniversalBinary>(InputBinaries.front().getBinary())) {
for (StringRef Arch : VerifyArchList) {
Expected<MachOUniversalBinary::ObjectForArch> Obj =
UO->getObjectForArch(Arch);
if (!Obj)
exit(EXIT_FAILURE);
}
} else if (auto O =
dyn_cast<MachOObjectFile>(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<MachOUniversalBinary>(Binary)) {
for (const auto &O : UO->objects()) {
Expected<std::unique_ptr<MachOObjectFile>> MachOObjOrError =
O.getAsObjectFile();
if (MachOObjOrError) {
OS << Slice(MachOObjOrError->get()).getArchString() << " ";
continue;
}
Expected<std::unique_ptr<Archive>> ArchiveOrError = O.getAsArchive();
if (ArchiveOrError) {
consumeError(MachOObjOrError.takeError());
OS << Slice(ArchiveOrError->get()).getArchString() << " ";
continue;
}
consumeError(ArchiveOrError.takeError());
reportError(Binary->getFileName(), MachOObjOrError.takeError());
}
OS << "\n";
return;
}
OS << Slice(cast<MachOObjectFile>(Binary)).getArchString() << " \n";
}
LLVM_ATTRIBUTE_NORETURN
static void printArchs(ArrayRef<OwningBinary<Binary>> 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<OwningBinary<Binary>> 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 extractSlice(ArrayRef<OwningBinary<Binary>> InputBinaries,
StringRef ThinArchType, StringRef OutputFileName) {
assert(!ThinArchType.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<MachOUniversalBinary>(InputBinaries.front().getBinary());
Expected<std::unique_ptr<MachOObjectFile>> Obj =
UO->getMachOObjectForArch(ThinArchType);
Expected<std::unique_ptr<Archive>> Ar = UO->getArchiveForArch(ThinArchType);
if (!Obj && !Ar)
reportError("fat input file " + UO->getFileName() +
" does not contain the specified architecture " + ThinArchType +
" to thin it to");
Binary *B = Obj ? static_cast<Binary *>(Obj->get())
: static_cast<Binary *>(Ar->get());
Expected<std::unique_ptr<FileOutputBuffer>> 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<Slice> Slices) {
DenseMap<uint64_t, const Binary *> 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 <typename Range>
static void updateAlignments(Range &Slices,
const StringMap<const uint32_t> &Alignments) {
for (auto &Slice : Slices) {
auto Alignment = Alignments.find(Slice.getArchString());
if (Alignment != Alignments.end())
Slice.setP2Alignment(Alignment->second);
}
}
static void checkUnusedAlignments(ArrayRef<Slice> Slices,
const StringMap<const uint32_t> &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 +
" <value> 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<Slice, 2> buildSlices(
ArrayRef<OwningBinary<Binary>> InputBinaries,
const StringMap<const uint32_t> &Alignments,
SmallVectorImpl<std::unique_ptr<MachOObjectFile>> &ExtractedObjects) {
SmallVector<Slice, 2> Slices;
for (auto &IB : InputBinaries) {
const Binary *InputBinary = IB.getBinary();
if (auto UO = dyn_cast<MachOUniversalBinary>(InputBinary)) {
for (const auto &O : UO->objects()) {
Expected<std::unique_ptr<MachOObjectFile>> 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<MachOObjectFile>(InputBinary)) {
Slices.emplace_back(O);
} else if (auto A = dyn_cast<Archive>(InputBinary)) {
Slices.emplace_back(A);
} else {
llvm_unreachable("Unexpected binary format");
}
}
updateAlignments(Slices, Alignments);
return Slices;
}
static SmallVector<MachO::fat_arch, 2>
buildFatArchList(ArrayRef<Slice> Slices) {
SmallVector<MachO::fat_arch, 2> 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)
reportError("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.");
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 void createUniversalBinary(SmallVectorImpl<Slice> &Slices,
StringRef OutputFileName) {
MachO::fat_header FatHeader;
FatHeader.magic = MachO::FAT_MAGIC;
FatHeader.nfat_arch = Slices.size();
stable_sort(Slices);
SmallVector<MachO::fat_arch, 2> FatArchList = buildFatArchList(Slices);
const bool IsExecutable = any_of(Slices, [](Slice S) {
return sys::fs::can_execute(S.getBinary()->getFileName());
});
const uint64_t OutputFileSize =
static_cast<uint64_t>(FatArchList.back().offset) +
FatArchList.back().size;
Expected<std::unique_ptr<FileOutputBuffer>> OutFileOrError =
FileOutputBuffer::create(OutputFileName, OutputFileSize,
IsExecutable ? FileOutputBuffer::F_executable
: 0);
if (!OutFileOrError)
reportError(OutputFileName, OutFileOrError.takeError());
std::unique_ptr<FileOutputBuffer> OutFile = std::move(OutFileOrError.get());
std::memset(OutFile->getBufferStart(), 0, OutputFileSize);
if (sys::IsLittleEndianHost)
MachO::swapStruct(FatHeader);
std::memcpy(OutFile->getBufferStart(), &FatHeader, sizeof(MachO::fat_header));
for (size_t Index = 0, Size = Slices.size(); Index < Size; ++Index) {
MemoryBufferRef BufferRef = Slices[Index].getBinary()->getMemoryBufferRef();
std::copy(BufferRef.getBufferStart(), BufferRef.getBufferEnd(),
OutFile->getBufferStart() + FatArchList[Index].offset);
}
// FatArchs written after Slices in order to reduce the number of swaps for
// the LittleEndian case
if (sys::IsLittleEndianHost)
for (MachO::fat_arch &FA : FatArchList)
MachO::swapStruct(FA);
std::memcpy(OutFile->getBufferStart() + sizeof(MachO::fat_header),
FatArchList.begin(),
sizeof(MachO::fat_arch) * FatArchList.size());
if (Error E = OutFile->commit())
reportError(OutputFileName, std::move(E));
}
LLVM_ATTRIBUTE_NORETURN
static void createUniversalBinary(ArrayRef<OwningBinary<Binary>> InputBinaries,
const StringMap<const uint32_t> &Alignments,
StringRef OutputFileName) {
assert(InputBinaries.size() >= 1 && "Incorrect number of input binaries");
assert(!OutputFileName.empty() && "Create expects a single output file");
SmallVector<std::unique_ptr<MachOObjectFile>, 1> ExtractedObjects;
SmallVector<Slice, 1> Slices =
buildSlices(InputBinaries, Alignments, ExtractedObjects);
checkArchDuplicates(Slices);
checkUnusedAlignments(Slices, Alignments);
createUniversalBinary(Slices, OutputFileName);
exit(EXIT_SUCCESS);
}
static StringMap<Slice>
buildReplacementSlices(ArrayRef<OwningBinary<Binary>> ReplacementBinaries,
const StringMap<const uint32_t> &Alignments) {
StringMap<Slice> 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<MachOObjectFile>(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() +
" <file_name> specified multiple times: " +
Entry.first->second.getBinary()->getFileName() + ", " +
O->getFileName());
}
auto SlicesMapRange = map_range(
Slices, [](StringMapEntry<Slice> &E) -> Slice & { return E.getValue(); });
updateAlignments(SlicesMapRange, Alignments);
return Slices;
}
LLVM_ATTRIBUTE_NORETURN
static void replaceSlices(ArrayRef<OwningBinary<Binary>> InputBinaries,
const StringMap<const uint32_t> &Alignments,
StringRef OutputFileName,
ArrayRef<InputFile> 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<OwningBinary<Binary>, 1> ReplacementBinaries =
readInputBinaries(ReplacementFiles);
StringMap<Slice> ReplacementSlices =
buildReplacementSlices(ReplacementBinaries, Alignments);
SmallVector<std::unique_ptr<MachOObjectFile>, 2> ExtractedObjects;
SmallVector<Slice, 2> 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() +
" <file_name> specified but fat file: " +
InputBinaries.front().getBinary()->getFileName() +
" does not contain that architecture");
checkUnusedAlignments(Slices, Alignments);
createUniversalBinary(Slices, OutputFileName);
exit(EXIT_SUCCESS);
}
int main(int argc, char **argv) {
InitLLVM X(argc, argv);
Config C = parseLipoOptions(makeArrayRef(argv + 1, argc));
SmallVector<OwningBinary<Binary>, 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:
extractSlice(InputBinaries, C.ThinArchType, 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;
}