1 | //===-- llvm-lipo.cpp - a tool for manipulating universal binaries --------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // A utility for creating / splitting / inspecting universal binaries. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "llvm/ADT/STLExtras.h" |
14 | #include "llvm/BinaryFormat/MachO.h" |
15 | #include "llvm/IR/LLVMContext.h" |
16 | #include "llvm/IR/Module.h" |
17 | #include "llvm/Object/Archive.h" |
18 | #include "llvm/Object/Binary.h" |
19 | #include "llvm/Object/IRObjectFile.h" |
20 | #include "llvm/Object/MachO.h" |
21 | #include "llvm/Object/MachOUniversal.h" |
22 | #include "llvm/Object/MachOUniversalWriter.h" |
23 | #include "llvm/Object/ObjectFile.h" |
24 | #include "llvm/Option/Arg.h" |
25 | #include "llvm/Option/ArgList.h" |
26 | #include "llvm/Support/CommandLine.h" |
27 | #include "llvm/Support/Error.h" |
28 | #include "llvm/Support/FileOutputBuffer.h" |
29 | #include "llvm/Support/LLVMDriver.h" |
30 | #include "llvm/Support/TargetSelect.h" |
31 | #include "llvm/Support/WithColor.h" |
32 | #include "llvm/TargetParser/Triple.h" |
33 | #include "llvm/TextAPI/Architecture.h" |
34 | #include <optional> |
35 | |
36 | using namespace llvm; |
37 | using namespace llvm::object; |
38 | |
39 | static const StringRef ToolName = "llvm-lipo" ; |
40 | |
41 | [[noreturn]] static void reportError(Twine Message) { |
42 | WithColor::error(OS&: errs(), Prefix: ToolName) << Message << "\n" ; |
43 | errs().flush(); |
44 | exit(EXIT_FAILURE); |
45 | } |
46 | |
47 | [[noreturn]] static void reportError(Error E) { |
48 | assert(E); |
49 | std::string Buf; |
50 | raw_string_ostream OS(Buf); |
51 | logAllUnhandledErrors(E: std::move(E), OS); |
52 | OS.flush(); |
53 | reportError(Message: Buf); |
54 | } |
55 | |
56 | [[noreturn]] static void reportError(StringRef File, Error E) { |
57 | assert(E); |
58 | std::string Buf; |
59 | raw_string_ostream OS(Buf); |
60 | logAllUnhandledErrors(E: std::move(E), OS); |
61 | OS.flush(); |
62 | WithColor::error(OS&: errs(), Prefix: ToolName) << "'" << File << "': " << Buf; |
63 | exit(EXIT_FAILURE); |
64 | } |
65 | |
66 | namespace { |
67 | enum LipoID { |
68 | LIPO_INVALID = 0, // This is not an option ID. |
69 | #define OPTION(...) LLVM_MAKE_OPT_ID_WITH_ID_PREFIX(LIPO_, __VA_ARGS__), |
70 | #include "LipoOpts.inc" |
71 | #undef OPTION |
72 | }; |
73 | |
74 | namespace lipo { |
75 | #define OPTTABLE_STR_TABLE_CODE |
76 | #include "LipoOpts.inc" |
77 | #undef OPTTABLE_STR_TABLE_CODE |
78 | |
79 | #define OPTTABLE_PREFIXES_TABLE_CODE |
80 | #include "LipoOpts.inc" |
81 | #undef OPTTABLE_PREFIXES_TABLE_CODE |
82 | |
83 | using namespace llvm::opt; |
84 | static constexpr opt::OptTable::Info LipoInfoTable[] = { |
85 | #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO_WITH_ID_PREFIX(LIPO_, __VA_ARGS__), |
86 | #include "LipoOpts.inc" |
87 | #undef OPTION |
88 | }; |
89 | } // namespace lipo |
90 | |
91 | class LipoOptTable : public opt::GenericOptTable { |
92 | public: |
93 | LipoOptTable() |
94 | : opt::GenericOptTable(lipo::OptionStrTable, lipo::OptionPrefixesTable, |
95 | lipo::LipoInfoTable) {} |
96 | }; |
97 | |
98 | enum class LipoAction { |
99 | PrintArchs, |
100 | PrintInfo, |
101 | VerifyArch, |
102 | ThinArch, |
103 | , |
104 | CreateUniversal, |
105 | ReplaceArch, |
106 | }; |
107 | |
108 | struct InputFile { |
109 | std::optional<StringRef> ArchType; |
110 | StringRef FileName; |
111 | }; |
112 | |
113 | struct Config { |
114 | SmallVector<InputFile, 1> InputFiles; |
115 | SmallVector<std::string, 1> VerifyArchList; |
116 | SmallVector<InputFile, 1> ReplacementFiles; |
117 | StringMap<const uint32_t> SegmentAlignments; |
118 | std::string ArchType; |
119 | std::string OutputFile; |
120 | LipoAction ActionToPerform; |
121 | bool UseFat64; |
122 | }; |
123 | |
124 | static Slice createSliceFromArchive(LLVMContext &LLVMCtx, const Archive &A) { |
125 | Expected<Slice> ArchiveOrSlice = Slice::create(A, LLVMCtx: &LLVMCtx); |
126 | if (!ArchiveOrSlice) |
127 | reportError(File: A.getFileName(), E: ArchiveOrSlice.takeError()); |
128 | return *ArchiveOrSlice; |
129 | } |
130 | |
131 | static Slice createSliceFromIR(const IRObjectFile &IRO, unsigned Align) { |
132 | Expected<Slice> IROrErr = Slice::create(IRO, Align); |
133 | if (!IROrErr) |
134 | reportError(File: IRO.getFileName(), E: IROrErr.takeError()); |
135 | return *IROrErr; |
136 | } |
137 | |
138 | } // end namespace |
139 | |
140 | static void validateArchitectureName(StringRef ArchitectureName) { |
141 | if (!MachOObjectFile::isValidArch(ArchFlag: ArchitectureName)) { |
142 | std::string Buf; |
143 | raw_string_ostream OS(Buf); |
144 | OS << "Invalid architecture: " << ArchitectureName |
145 | << "\nValid architecture names are:" ; |
146 | for (auto arch : MachOObjectFile::getValidArchs()) |
147 | OS << " " << arch; |
148 | reportError(Message: Buf); |
149 | } |
150 | } |
151 | |
152 | static Config parseLipoOptions(ArrayRef<const char *> ArgsArr) { |
153 | Config C; |
154 | LipoOptTable T; |
155 | unsigned MissingArgumentIndex, MissingArgumentCount; |
156 | opt::InputArgList InputArgs = |
157 | T.ParseArgs(Args: ArgsArr, MissingArgIndex&: MissingArgumentIndex, MissingArgCount&: MissingArgumentCount); |
158 | |
159 | if (MissingArgumentCount) |
160 | reportError(Message: "missing argument to " + |
161 | StringRef(InputArgs.getArgString(Index: MissingArgumentIndex)) + |
162 | " option" ); |
163 | |
164 | if (InputArgs.size() == 0) { |
165 | // printHelp does not accept Twine. |
166 | T.printHelp(OS&: errs(), Usage: "llvm-lipo input[s] option[s]" , Title: "llvm-lipo" ); |
167 | exit(EXIT_FAILURE); |
168 | } |
169 | |
170 | if (InputArgs.hasArg(Ids: LIPO_help)) { |
171 | // printHelp does not accept Twine. |
172 | T.printHelp(OS&: outs(), Usage: "llvm-lipo input[s] option[s]" , Title: "llvm-lipo" ); |
173 | exit(EXIT_SUCCESS); |
174 | } |
175 | |
176 | if (InputArgs.hasArg(Ids: LIPO_version)) { |
177 | outs() << ToolName + "\n" ; |
178 | cl::PrintVersionMessage(); |
179 | exit(EXIT_SUCCESS); |
180 | } |
181 | |
182 | for (auto *Arg : InputArgs.filtered(Ids: LIPO_UNKNOWN)) |
183 | reportError(Message: "unknown argument '" + Arg->getAsString(Args: InputArgs) + "'" ); |
184 | |
185 | for (auto *Arg : InputArgs.filtered(Ids: LIPO_INPUT)) |
186 | C.InputFiles.push_back(Elt: {.ArchType: std::nullopt, .FileName: Arg->getValue()}); |
187 | for (auto *Arg : InputArgs.filtered(Ids: LIPO_arch)) { |
188 | validateArchitectureName(ArchitectureName: Arg->getValue(N: 0)); |
189 | assert(Arg->getValue(1) && "file_name is missing" ); |
190 | C.InputFiles.push_back(Elt: {.ArchType: StringRef(Arg->getValue(N: 0)), .FileName: Arg->getValue(N: 1)}); |
191 | } |
192 | |
193 | if (C.InputFiles.empty()) |
194 | reportError(Message: "at least one input file should be specified" ); |
195 | |
196 | if (InputArgs.hasArg(Ids: LIPO_output)) |
197 | C.OutputFile = std::string(InputArgs.getLastArgValue(Id: LIPO_output)); |
198 | |
199 | for (auto *Segalign : InputArgs.filtered(Ids: LIPO_segalign)) { |
200 | if (!Segalign->getValue(N: 1)) |
201 | reportError(Message: "segalign is missing an argument: expects -segalign " |
202 | "arch_type alignment_value" ); |
203 | |
204 | validateArchitectureName(ArchitectureName: Segalign->getValue(N: 0)); |
205 | |
206 | uint32_t AlignmentValue; |
207 | if (!to_integer<uint32_t>(S: Segalign->getValue(N: 1), Num&: AlignmentValue, Base: 16)) |
208 | reportError(Message: "argument to -segalign <arch_type> " + |
209 | Twine(Segalign->getValue(N: 1)) + |
210 | " (hex) is not a proper hexadecimal number" ); |
211 | if (!isPowerOf2_32(Value: AlignmentValue)) |
212 | reportError(Message: "argument to -segalign <arch_type> " + |
213 | Twine(Segalign->getValue(N: 1)) + |
214 | " (hex) must be a non-zero power of two" ); |
215 | if (Log2_32(Value: AlignmentValue) > MachOUniversalBinary::MaxSectionAlignment) |
216 | reportError( |
217 | Message: "argument to -segalign <arch_type> " + Twine(Segalign->getValue(N: 1)) + |
218 | " (hex) must be less than or equal to the maximum section align 2^" + |
219 | Twine(MachOUniversalBinary::MaxSectionAlignment)); |
220 | auto Entry = C.SegmentAlignments.try_emplace(Key: Segalign->getValue(N: 0), |
221 | Args: Log2_32(Value: AlignmentValue)); |
222 | if (!Entry.second) |
223 | reportError(Message: "-segalign " + Twine(Segalign->getValue(N: 0)) + |
224 | " <alignment_value> specified multiple times: " + |
225 | Twine(1 << Entry.first->second) + ", " + |
226 | Twine(AlignmentValue)); |
227 | } |
228 | |
229 | C.UseFat64 = InputArgs.hasArg(Ids: LIPO_fat64); |
230 | |
231 | SmallVector<opt::Arg *, 1> ActionArgs(InputArgs.filtered(Ids: LIPO_action_group)); |
232 | if (ActionArgs.empty()) |
233 | reportError(Message: "at least one action should be specified" ); |
234 | // errors if multiple actions specified other than replace |
235 | // multiple replace flags may be specified, as long as they are not mixed with |
236 | // other action flags |
237 | auto ReplacementArgsRange = InputArgs.filtered(Ids: LIPO_replace); |
238 | if (ActionArgs.size() > 1 && |
239 | ActionArgs.size() != |
240 | static_cast<size_t>(std::distance(first: ReplacementArgsRange.begin(), |
241 | last: ReplacementArgsRange.end()))) { |
242 | std::string Buf; |
243 | raw_string_ostream OS(Buf); |
244 | OS << "only one of the following actions can be specified:" ; |
245 | for (auto *Arg : ActionArgs) |
246 | OS << " " << Arg->getSpelling(); |
247 | reportError(Message: Buf); |
248 | } |
249 | |
250 | switch (ActionArgs[0]->getOption().getID()) { |
251 | case LIPO_verify_arch: |
252 | llvm::append_range(C&: C.VerifyArchList, |
253 | R: InputArgs.getAllArgValues(Id: LIPO_verify_arch)); |
254 | if (C.VerifyArchList.empty()) |
255 | reportError( |
256 | Message: "verify_arch requires at least one architecture to be specified" ); |
257 | if (C.InputFiles.size() > 1) |
258 | reportError(Message: "verify_arch expects a single input file" ); |
259 | C.ActionToPerform = LipoAction::VerifyArch; |
260 | return C; |
261 | |
262 | case LIPO_archs: |
263 | if (C.InputFiles.size() > 1) |
264 | reportError(Message: "archs expects a single input file" ); |
265 | C.ActionToPerform = LipoAction::PrintArchs; |
266 | return C; |
267 | |
268 | case LIPO_info: |
269 | C.ActionToPerform = LipoAction::PrintInfo; |
270 | return C; |
271 | |
272 | case LIPO_thin: |
273 | if (C.InputFiles.size() > 1) |
274 | reportError(Message: "thin expects a single input file" ); |
275 | if (C.OutputFile.empty()) |
276 | reportError(Message: "thin expects a single output file" ); |
277 | C.ArchType = ActionArgs[0]->getValue(); |
278 | validateArchitectureName(ArchitectureName: C.ArchType); |
279 | C.ActionToPerform = LipoAction::ThinArch; |
280 | return C; |
281 | |
282 | case LIPO_extract: |
283 | if (C.InputFiles.size() > 1) |
284 | reportError(Message: "extract expects a single input file" ); |
285 | if (C.OutputFile.empty()) |
286 | reportError(Message: "extract expects a single output file" ); |
287 | C.ArchType = ActionArgs[0]->getValue(); |
288 | validateArchitectureName(ArchitectureName: C.ArchType); |
289 | C.ActionToPerform = LipoAction::ExtractArch; |
290 | return C; |
291 | |
292 | case LIPO_create: |
293 | if (C.OutputFile.empty()) |
294 | reportError(Message: "create expects a single output file to be specified" ); |
295 | C.ActionToPerform = LipoAction::CreateUniversal; |
296 | return C; |
297 | |
298 | case LIPO_replace: |
299 | for (auto *Action : ActionArgs) { |
300 | assert(Action->getValue(1) && "file_name is missing" ); |
301 | validateArchitectureName(ArchitectureName: Action->getValue(N: 0)); |
302 | C.ReplacementFiles.push_back( |
303 | Elt: {.ArchType: StringRef(Action->getValue(N: 0)), .FileName: Action->getValue(N: 1)}); |
304 | } |
305 | |
306 | if (C.OutputFile.empty()) |
307 | reportError(Message: "replace expects a single output file to be specified" ); |
308 | if (C.InputFiles.size() > 1) |
309 | reportError(Message: "replace expects a single input file" ); |
310 | C.ActionToPerform = LipoAction::ReplaceArch; |
311 | return C; |
312 | |
313 | default: |
314 | reportError(Message: "llvm-lipo action unspecified" ); |
315 | } |
316 | } |
317 | |
318 | static SmallVector<OwningBinary<Binary>, 1> |
319 | readInputBinaries(LLVMContext &LLVMCtx, ArrayRef<InputFile> InputFiles) { |
320 | SmallVector<OwningBinary<Binary>, 1> InputBinaries; |
321 | for (const InputFile &IF : InputFiles) { |
322 | Expected<OwningBinary<Binary>> BinaryOrErr = |
323 | createBinary(Path: IF.FileName, Context: &LLVMCtx); |
324 | if (!BinaryOrErr) |
325 | reportError(File: IF.FileName, E: BinaryOrErr.takeError()); |
326 | const Binary *B = BinaryOrErr->getBinary(); |
327 | if (!B->isArchive() && !B->isMachO() && !B->isMachOUniversalBinary() && |
328 | !B->isIR()) |
329 | reportError(Message: "File " + IF.FileName + " has unsupported binary format" ); |
330 | if (IF.ArchType && (B->isMachO() || B->isArchive() || B->isIR())) { |
331 | const auto S = B->isMachO() ? Slice(*cast<MachOObjectFile>(Val: B)) |
332 | : B->isArchive() |
333 | ? createSliceFromArchive(LLVMCtx, A: *cast<Archive>(Val: B)) |
334 | : createSliceFromIR(IRO: *cast<IRObjectFile>(Val: B), Align: 0); |
335 | const auto SpecifiedCPUType = MachO::getCPUTypeFromArchitecture( |
336 | Arch: MachO::getArchitectureFromName( |
337 | Name: Triple(*IF.ArchType).getArchName())) |
338 | .first; |
339 | // For compatibility with cctools' lipo the comparison is relaxed just to |
340 | // checking cputypes. |
341 | if (S.getCPUType() != SpecifiedCPUType) |
342 | reportError(Message: "specified architecture: " + *IF.ArchType + |
343 | " for file: " + B->getFileName() + |
344 | " does not match the file's architecture (" + |
345 | S.getArchString() + ")" ); |
346 | } |
347 | InputBinaries.push_back(Elt: std::move(*BinaryOrErr)); |
348 | } |
349 | return InputBinaries; |
350 | } |
351 | |
352 | [[noreturn]] static void |
353 | verifyArch(ArrayRef<OwningBinary<Binary>> InputBinaries, |
354 | ArrayRef<std::string> VerifyArchList) { |
355 | assert(!VerifyArchList.empty() && |
356 | "The list of architectures should be non-empty" ); |
357 | assert(InputBinaries.size() == 1 && "Incorrect number of input binaries" ); |
358 | |
359 | for (StringRef Arch : VerifyArchList) |
360 | validateArchitectureName(ArchitectureName: Arch); |
361 | |
362 | if (auto UO = |
363 | dyn_cast<MachOUniversalBinary>(Val: InputBinaries.front().getBinary())) { |
364 | for (StringRef Arch : VerifyArchList) { |
365 | Expected<MachOUniversalBinary::ObjectForArch> Obj = |
366 | UO->getObjectForArch(ArchName: Arch); |
367 | if (!Obj) |
368 | exit(EXIT_FAILURE); |
369 | } |
370 | } else if (auto O = |
371 | dyn_cast<MachOObjectFile>(Val: InputBinaries.front().getBinary())) { |
372 | const Triple::ArchType ObjectArch = O->getArch(); |
373 | for (StringRef Arch : VerifyArchList) |
374 | if (ObjectArch != Triple(Arch).getArch()) |
375 | exit(EXIT_FAILURE); |
376 | } else { |
377 | llvm_unreachable("Unexpected binary format" ); |
378 | } |
379 | exit(EXIT_SUCCESS); |
380 | } |
381 | |
382 | static void printBinaryArchs(LLVMContext &LLVMCtx, const Binary *Binary, |
383 | raw_ostream &OS) { |
384 | // Prints trailing space for compatibility with cctools lipo. |
385 | if (auto UO = dyn_cast<MachOUniversalBinary>(Val: Binary)) { |
386 | for (const auto &O : UO->objects()) { |
387 | // Order here is important, because both MachOObjectFile and |
388 | // IRObjectFile can be created with a binary that has embedded bitcode. |
389 | Expected<std::unique_ptr<MachOObjectFile>> MachOObjOrError = |
390 | O.getAsObjectFile(); |
391 | if (MachOObjOrError) { |
392 | OS << Slice(*(MachOObjOrError->get())).getArchString() << " " ; |
393 | continue; |
394 | } |
395 | Expected<std::unique_ptr<IRObjectFile>> IROrError = |
396 | O.getAsIRObject(Ctx&: LLVMCtx); |
397 | if (IROrError) { |
398 | consumeError(Err: MachOObjOrError.takeError()); |
399 | Expected<Slice> SliceOrErr = Slice::create(IRO: **IROrError, Align: O.getAlign()); |
400 | if (!SliceOrErr) { |
401 | reportError(File: Binary->getFileName(), E: SliceOrErr.takeError()); |
402 | continue; |
403 | } |
404 | OS << SliceOrErr.get().getArchString() << " " ; |
405 | continue; |
406 | } |
407 | Expected<std::unique_ptr<Archive>> ArchiveOrError = O.getAsArchive(); |
408 | if (ArchiveOrError) { |
409 | consumeError(Err: MachOObjOrError.takeError()); |
410 | consumeError(Err: IROrError.takeError()); |
411 | OS << createSliceFromArchive(LLVMCtx, A: **ArchiveOrError).getArchString() |
412 | << " " ; |
413 | continue; |
414 | } |
415 | consumeError(Err: ArchiveOrError.takeError()); |
416 | reportError(File: Binary->getFileName(), E: MachOObjOrError.takeError()); |
417 | reportError(File: Binary->getFileName(), E: IROrError.takeError()); |
418 | } |
419 | OS << "\n" ; |
420 | return; |
421 | } |
422 | |
423 | if (const auto *MachO = dyn_cast<MachOObjectFile>(Val: Binary)) { |
424 | OS << Slice(*MachO).getArchString() << " \n" ; |
425 | return; |
426 | } |
427 | |
428 | // This should be always the case, as this is tested in readInputBinaries |
429 | const auto *IR = cast<IRObjectFile>(Val: Binary); |
430 | Expected<Slice> SliceOrErr = createSliceFromIR(IRO: *IR, Align: 0); |
431 | if (!SliceOrErr) |
432 | reportError(File: IR->getFileName(), E: SliceOrErr.takeError()); |
433 | |
434 | OS << SliceOrErr->getArchString() << " \n" ; |
435 | } |
436 | |
437 | [[noreturn]] static void |
438 | printArchs(LLVMContext &LLVMCtx, ArrayRef<OwningBinary<Binary>> InputBinaries) { |
439 | assert(InputBinaries.size() == 1 && "Incorrect number of input binaries" ); |
440 | printBinaryArchs(LLVMCtx, Binary: InputBinaries.front().getBinary(), OS&: outs()); |
441 | exit(EXIT_SUCCESS); |
442 | } |
443 | |
444 | [[noreturn]] static void |
445 | printInfo(LLVMContext &LLVMCtx, ArrayRef<OwningBinary<Binary>> InputBinaries) { |
446 | // Group universal and thin files together for compatibility with cctools lipo |
447 | for (auto &IB : InputBinaries) { |
448 | const Binary *Binary = IB.getBinary(); |
449 | if (Binary->isMachOUniversalBinary()) { |
450 | outs() << "Architectures in the fat file: " << Binary->getFileName() |
451 | << " are: " ; |
452 | printBinaryArchs(LLVMCtx, Binary, OS&: outs()); |
453 | } |
454 | } |
455 | for (auto &IB : InputBinaries) { |
456 | const Binary *Binary = IB.getBinary(); |
457 | if (!Binary->isMachOUniversalBinary()) { |
458 | assert(Binary->isMachO() && "expected MachO binary" ); |
459 | outs() << "Non-fat file: " << Binary->getFileName() |
460 | << " is architecture: " ; |
461 | printBinaryArchs(LLVMCtx, Binary, OS&: outs()); |
462 | } |
463 | } |
464 | exit(EXIT_SUCCESS); |
465 | } |
466 | |
467 | [[noreturn]] static void thinSlice(LLVMContext &LLVMCtx, |
468 | ArrayRef<OwningBinary<Binary>> InputBinaries, |
469 | StringRef ArchType, |
470 | StringRef OutputFileName) { |
471 | assert(!ArchType.empty() && "The architecture type should be non-empty" ); |
472 | assert(InputBinaries.size() == 1 && "Incorrect number of input binaries" ); |
473 | assert(!OutputFileName.empty() && "Thin expects a single output file" ); |
474 | |
475 | if (InputBinaries.front().getBinary()->isMachO()) { |
476 | reportError(Message: "input file " + |
477 | InputBinaries.front().getBinary()->getFileName() + |
478 | " must be a fat file when the -thin option is specified" ); |
479 | exit(EXIT_FAILURE); |
480 | } |
481 | |
482 | auto *UO = cast<MachOUniversalBinary>(Val: InputBinaries.front().getBinary()); |
483 | Expected<std::unique_ptr<MachOObjectFile>> Obj = |
484 | UO->getMachOObjectForArch(ArchName: ArchType); |
485 | Expected<std::unique_ptr<IRObjectFile>> IRObj = |
486 | UO->getIRObjectForArch(ArchName: ArchType, Ctx&: LLVMCtx); |
487 | Expected<std::unique_ptr<Archive>> Ar = UO->getArchiveForArch(ArchName: ArchType); |
488 | if (!Obj && !IRObj && !Ar) |
489 | reportError(Message: "fat input file " + UO->getFileName() + |
490 | " does not contain the specified architecture " + ArchType + |
491 | " to thin it to" ); |
492 | Binary *B; |
493 | // Order here is important, because both Obj and IRObj will be valid with a |
494 | // binary that has embedded bitcode. |
495 | if (Obj) |
496 | B = Obj->get(); |
497 | else if (IRObj) |
498 | B = IRObj->get(); |
499 | else |
500 | B = Ar->get(); |
501 | |
502 | Expected<std::unique_ptr<FileOutputBuffer>> OutFileOrError = |
503 | FileOutputBuffer::create(FilePath: OutputFileName, |
504 | Size: B->getMemoryBufferRef().getBufferSize(), |
505 | Flags: sys::fs::can_execute(Path: UO->getFileName()) |
506 | ? FileOutputBuffer::F_executable |
507 | : 0); |
508 | if (!OutFileOrError) |
509 | reportError(File: OutputFileName, E: OutFileOrError.takeError()); |
510 | std::copy(first: B->getMemoryBufferRef().getBufferStart(), |
511 | last: B->getMemoryBufferRef().getBufferEnd(), |
512 | result: OutFileOrError.get()->getBufferStart()); |
513 | if (Error E = OutFileOrError.get()->commit()) |
514 | reportError(File: OutputFileName, E: std::move(E)); |
515 | exit(EXIT_SUCCESS); |
516 | } |
517 | |
518 | static void checkArchDuplicates(ArrayRef<Slice> Slices) { |
519 | DenseMap<uint64_t, const Binary *> CPUIds; |
520 | for (const auto &S : Slices) { |
521 | auto Entry = CPUIds.try_emplace(Key: S.getCPUID(), Args: S.getBinary()); |
522 | if (!Entry.second) |
523 | reportError(Message: Entry.first->second->getFileName() + " and " + |
524 | S.getBinary()->getFileName() + |
525 | " have the same architecture " + S.getArchString() + |
526 | " and therefore cannot be in the same universal binary" ); |
527 | } |
528 | } |
529 | |
530 | template <typename Range> |
531 | static void updateAlignments(Range &Slices, |
532 | const StringMap<const uint32_t> &Alignments) { |
533 | for (auto &Slice : Slices) { |
534 | auto Alignment = Alignments.find(Slice.getArchString()); |
535 | if (Alignment != Alignments.end()) |
536 | Slice.setP2Alignment(Alignment->second); |
537 | } |
538 | } |
539 | |
540 | static void checkUnusedAlignments(ArrayRef<Slice> Slices, |
541 | const StringMap<const uint32_t> &Alignments) { |
542 | auto HasArch = [&](StringRef Arch) { |
543 | return llvm::any_of(Range&: Slices, |
544 | P: [Arch](Slice S) { return S.getArchString() == Arch; }); |
545 | }; |
546 | for (StringRef Arch : Alignments.keys()) |
547 | if (!HasArch(Arch)) |
548 | reportError(Message: "-segalign " + Arch + |
549 | " <value> specified but resulting fat file does not contain " |
550 | "that architecture " ); |
551 | } |
552 | |
553 | // Updates vector ExtractedObjects with the MachOObjectFiles extracted from |
554 | // Universal Binary files to transfer ownership. |
555 | static SmallVector<Slice, 2> |
556 | buildSlices(LLVMContext &LLVMCtx, ArrayRef<OwningBinary<Binary>> InputBinaries, |
557 | const StringMap<const uint32_t> &Alignments, |
558 | SmallVectorImpl<std::unique_ptr<SymbolicFile>> &) { |
559 | SmallVector<Slice, 2> Slices; |
560 | for (auto &IB : InputBinaries) { |
561 | const Binary *InputBinary = IB.getBinary(); |
562 | if (auto UO = dyn_cast<MachOUniversalBinary>(Val: InputBinary)) { |
563 | for (const auto &O : UO->objects()) { |
564 | // Order here is important, because both MachOObjectFile and |
565 | // IRObjectFile can be created with a binary that has embedded bitcode. |
566 | Expected<std::unique_ptr<MachOObjectFile>> BinaryOrError = |
567 | O.getAsObjectFile(); |
568 | if (BinaryOrError) { |
569 | Slices.emplace_back(Args&: *(BinaryOrError.get()), Args: O.getAlign()); |
570 | ExtractedObjects.push_back(Elt: std::move(BinaryOrError.get())); |
571 | continue; |
572 | } |
573 | Expected<std::unique_ptr<IRObjectFile>> IROrError = |
574 | O.getAsIRObject(Ctx&: LLVMCtx); |
575 | if (IROrError) { |
576 | consumeError(Err: BinaryOrError.takeError()); |
577 | Slice S = createSliceFromIR(IRO: **IROrError, Align: O.getAlign()); |
578 | ExtractedObjects.emplace_back(Args: std::move(IROrError.get())); |
579 | Slices.emplace_back(Args: std::move(S)); |
580 | continue; |
581 | } |
582 | reportError(File: InputBinary->getFileName(), E: BinaryOrError.takeError()); |
583 | } |
584 | } else if (const auto *O = dyn_cast<MachOObjectFile>(Val: InputBinary)) { |
585 | Slices.emplace_back(Args: *O); |
586 | } else if (const auto *A = dyn_cast<Archive>(Val: InputBinary)) { |
587 | Slices.push_back(Elt: createSliceFromArchive(LLVMCtx, A: *A)); |
588 | } else if (const auto *IRO = dyn_cast<IRObjectFile>(Val: InputBinary)) { |
589 | // Original Apple's lipo set the alignment to 0 |
590 | Expected<Slice> SliceOrErr = Slice::create(IRO: *IRO, Align: 0); |
591 | if (!SliceOrErr) { |
592 | reportError(File: InputBinary->getFileName(), E: SliceOrErr.takeError()); |
593 | continue; |
594 | } |
595 | Slices.emplace_back(Args: std::move(SliceOrErr.get())); |
596 | } else { |
597 | llvm_unreachable("Unexpected binary format" ); |
598 | } |
599 | } |
600 | updateAlignments(Slices, Alignments); |
601 | return Slices; |
602 | } |
603 | |
604 | [[noreturn]] static void |
605 | createUniversalBinary(LLVMContext &LLVMCtx, |
606 | ArrayRef<OwningBinary<Binary>> InputBinaries, |
607 | const StringMap<const uint32_t> &Alignments, |
608 | StringRef OutputFileName, FatHeaderType ) { |
609 | assert(InputBinaries.size() >= 1 && "Incorrect number of input binaries" ); |
610 | assert(!OutputFileName.empty() && "Create expects a single output file" ); |
611 | |
612 | SmallVector<std::unique_ptr<SymbolicFile>, 1> ; |
613 | SmallVector<Slice, 1> Slices = |
614 | buildSlices(LLVMCtx, InputBinaries, Alignments, ExtractedObjects); |
615 | checkArchDuplicates(Slices); |
616 | checkUnusedAlignments(Slices, Alignments); |
617 | |
618 | llvm::stable_sort(Range&: Slices); |
619 | if (Error E = writeUniversalBinary(Slices, OutputFileName, FatHeader: HeaderType)) |
620 | reportError(E: std::move(E)); |
621 | |
622 | exit(EXIT_SUCCESS); |
623 | } |
624 | |
625 | [[noreturn]] static void |
626 | (LLVMContext &LLVMCtx, ArrayRef<OwningBinary<Binary>> InputBinaries, |
627 | const StringMap<const uint32_t> &Alignments, StringRef ArchType, |
628 | StringRef OutputFileName) { |
629 | assert(!ArchType.empty() && |
630 | "The architecture type should be non-empty" ); |
631 | assert(InputBinaries.size() == 1 && "Incorrect number of input binaries" ); |
632 | assert(!OutputFileName.empty() && "Thin expects a single output file" ); |
633 | |
634 | if (InputBinaries.front().getBinary()->isMachO()) { |
635 | reportError(Message: "input file " + |
636 | InputBinaries.front().getBinary()->getFileName() + |
637 | " must be a fat file when the -extract option is specified" ); |
638 | } |
639 | |
640 | SmallVector<std::unique_ptr<SymbolicFile>, 2> ; |
641 | SmallVector<Slice, 2> Slices = |
642 | buildSlices(LLVMCtx, InputBinaries, Alignments, ExtractedObjects); |
643 | erase_if(C&: Slices, P: [ArchType](const Slice &S) { |
644 | return ArchType != S.getArchString(); |
645 | }); |
646 | |
647 | if (Slices.empty()) |
648 | reportError( |
649 | Message: "fat input file " + InputBinaries.front().getBinary()->getFileName() + |
650 | " does not contain the specified architecture " + ArchType); |
651 | |
652 | llvm::stable_sort(Range&: Slices); |
653 | if (Error E = writeUniversalBinary(Slices, OutputFileName)) |
654 | reportError(E: std::move(E)); |
655 | exit(EXIT_SUCCESS); |
656 | } |
657 | |
658 | static StringMap<Slice> |
659 | buildReplacementSlices(ArrayRef<OwningBinary<Binary>> ReplacementBinaries, |
660 | const StringMap<const uint32_t> &Alignments) { |
661 | StringMap<Slice> Slices; |
662 | // populates StringMap of slices to replace with; error checks for mismatched |
663 | // replace flag args, fat files, and duplicate arch_types |
664 | for (const auto &OB : ReplacementBinaries) { |
665 | const Binary *ReplacementBinary = OB.getBinary(); |
666 | auto O = dyn_cast<MachOObjectFile>(Val: ReplacementBinary); |
667 | if (!O) |
668 | reportError(Message: "replacement file: " + ReplacementBinary->getFileName() + |
669 | " is a fat file (must be a thin file)" ); |
670 | Slice S(*O); |
671 | auto Entry = Slices.try_emplace(Key: S.getArchString(), Args&: S); |
672 | if (!Entry.second) |
673 | reportError(Message: "-replace " + S.getArchString() + |
674 | " <file_name> specified multiple times: " + |
675 | Entry.first->second.getBinary()->getFileName() + ", " + |
676 | O->getFileName()); |
677 | } |
678 | auto SlicesMapRange = map_range( |
679 | C&: Slices, F: [](StringMapEntry<Slice> &E) -> Slice & { return E.getValue(); }); |
680 | updateAlignments(Slices&: SlicesMapRange, Alignments); |
681 | return Slices; |
682 | } |
683 | |
684 | [[noreturn]] static void |
685 | replaceSlices(LLVMContext &LLVMCtx, |
686 | ArrayRef<OwningBinary<Binary>> InputBinaries, |
687 | const StringMap<const uint32_t> &Alignments, |
688 | StringRef OutputFileName, ArrayRef<InputFile> ReplacementFiles) { |
689 | assert(InputBinaries.size() == 1 && "Incorrect number of input binaries" ); |
690 | assert(!OutputFileName.empty() && "Replace expects a single output file" ); |
691 | |
692 | if (InputBinaries.front().getBinary()->isMachO()) |
693 | reportError(Message: "input file " + |
694 | InputBinaries.front().getBinary()->getFileName() + |
695 | " must be a fat file when the -replace option is specified" ); |
696 | |
697 | SmallVector<OwningBinary<Binary>, 1> ReplacementBinaries = |
698 | readInputBinaries(LLVMCtx, InputFiles: ReplacementFiles); |
699 | |
700 | StringMap<Slice> ReplacementSlices = |
701 | buildReplacementSlices(ReplacementBinaries, Alignments); |
702 | SmallVector<std::unique_ptr<SymbolicFile>, 2> ; |
703 | SmallVector<Slice, 2> Slices = |
704 | buildSlices(LLVMCtx, InputBinaries, Alignments, ExtractedObjects); |
705 | |
706 | for (auto &Slice : Slices) { |
707 | auto It = ReplacementSlices.find(Key: Slice.getArchString()); |
708 | if (It != ReplacementSlices.end()) { |
709 | Slice = It->second; |
710 | ReplacementSlices.erase(I: It); // only keep remaining replacing arch_types |
711 | } |
712 | } |
713 | |
714 | if (!ReplacementSlices.empty()) |
715 | reportError(Message: "-replace " + ReplacementSlices.begin()->first() + |
716 | " <file_name> specified but fat file: " + |
717 | InputBinaries.front().getBinary()->getFileName() + |
718 | " does not contain that architecture" ); |
719 | |
720 | checkUnusedAlignments(Slices, Alignments); |
721 | |
722 | llvm::stable_sort(Range&: Slices); |
723 | if (Error E = writeUniversalBinary(Slices, OutputFileName)) |
724 | reportError(E: std::move(E)); |
725 | exit(EXIT_SUCCESS); |
726 | } |
727 | |
728 | int llvm_lipo_main(int argc, char **argv, const llvm::ToolContext &) { |
729 | llvm::InitializeAllTargetInfos(); |
730 | llvm::InitializeAllTargetMCs(); |
731 | llvm::InitializeAllAsmParsers(); |
732 | |
733 | Config C = parseLipoOptions(ArgsArr: ArrayRef(argv + 1, argc - 1)); |
734 | LLVMContext LLVMCtx; |
735 | SmallVector<OwningBinary<Binary>, 1> InputBinaries = |
736 | readInputBinaries(LLVMCtx, InputFiles: C.InputFiles); |
737 | |
738 | switch (C.ActionToPerform) { |
739 | case LipoAction::VerifyArch: |
740 | verifyArch(InputBinaries, VerifyArchList: C.VerifyArchList); |
741 | break; |
742 | case LipoAction::PrintArchs: |
743 | printArchs(LLVMCtx, InputBinaries); |
744 | break; |
745 | case LipoAction::PrintInfo: |
746 | printInfo(LLVMCtx, InputBinaries); |
747 | break; |
748 | case LipoAction::ThinArch: |
749 | thinSlice(LLVMCtx, InputBinaries, ArchType: C.ArchType, OutputFileName: C.OutputFile); |
750 | break; |
751 | case LipoAction::ExtractArch: |
752 | extractSlice(LLVMCtx, InputBinaries, Alignments: C.SegmentAlignments, ArchType: C.ArchType, |
753 | OutputFileName: C.OutputFile); |
754 | break; |
755 | case LipoAction::CreateUniversal: |
756 | createUniversalBinary( |
757 | LLVMCtx, InputBinaries, Alignments: C.SegmentAlignments, OutputFileName: C.OutputFile, |
758 | HeaderType: C.UseFat64 ? FatHeaderType::Fat64Header : FatHeaderType::FatHeader); |
759 | break; |
760 | case LipoAction::ReplaceArch: |
761 | replaceSlices(LLVMCtx, InputBinaries, Alignments: C.SegmentAlignments, OutputFileName: C.OutputFile, |
762 | ReplacementFiles: C.ReplacementFiles); |
763 | break; |
764 | } |
765 | return EXIT_SUCCESS; |
766 | } |
767 | |