diff --git a/llvm/docs/CommandGuide/llvm-objcopy.rst b/llvm/docs/CommandGuide/llvm-objcopy.rst index e6af47ce9710a..166525047d1f5 100644 --- a/llvm/docs/CommandGuide/llvm-objcopy.rst +++ b/llvm/docs/CommandGuide/llvm-objcopy.rst @@ -464,6 +464,11 @@ them. Preserve access and modification timestamps in the output. +.. option:: --remove-note [/] + + Remove notes of integer type ```` and name ```` from SHT_NOTE + sections that are not in a segment. Can be specified multiple times. + .. option:: --rename-section =[,,...] Rename sections called ```` to ```` in the output, and apply any diff --git a/llvm/include/llvm/ObjCopy/CommonConfig.h b/llvm/include/llvm/ObjCopy/CommonConfig.h index 5ae09760e9a54..aea9cd6f9a9c7 100644 --- a/llvm/include/llvm/ObjCopy/CommonConfig.h +++ b/llvm/include/llvm/ObjCopy/CommonConfig.h @@ -281,6 +281,11 @@ struct CommonConfig { SmallVector, 0> compressSections; + + // ErrorCallback is used to handle recoverable errors. An Error returned + // by the callback aborts the execution and is then returned to the caller. + // If the callback is not set, the errors are not issued. + std::function ErrorCallback; }; } // namespace objcopy diff --git a/llvm/include/llvm/ObjCopy/ELF/ELFConfig.h b/llvm/include/llvm/ObjCopy/ELF/ELFConfig.h index 59960b6530743..01a8762cfb9c3 100644 --- a/llvm/include/llvm/ObjCopy/ELF/ELFConfig.h +++ b/llvm/include/llvm/ObjCopy/ELF/ELFConfig.h @@ -15,6 +15,12 @@ namespace llvm { namespace objcopy { +// Note to remove info specified by --remove-note option. +struct RemoveNoteInfo { + StringRef Name; + uint32_t TypeId; +}; + // ELF specific configuration for copying/stripping a single file. struct ELFConfig { uint8_t NewSymbolVisibility = (uint8_t)ELF::STV_DEFAULT; @@ -31,6 +37,9 @@ struct ELFConfig { bool KeepFileSymbols = false; bool LocalizeHidden = false; bool VerifyNoteSections = true; + + // Notes specified by --remove-note option. + SmallVector NotesToRemove; }; } // namespace objcopy diff --git a/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp b/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp index 4793651f1d4e0..42581af387d8d 100644 --- a/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp +++ b/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp @@ -609,6 +609,112 @@ static void addSymbol(Object &Obj, const NewSymbolInfo &SymInfo, Sec ? (uint16_t)SYMBOL_SIMPLE_INDEX : (uint16_t)SHN_ABS, 0); } +namespace { +struct RemoveNoteDetail { + struct DeletedRange { + uint64_t OldFrom; + uint64_t OldTo; + }; + + template + static std::vector + findNotesToRemove(ArrayRef Data, size_t Align, + ArrayRef NotesToRemove); + static std::vector updateData(ArrayRef OldData, + ArrayRef ToRemove); +}; +} // namespace + +template +std::vector +RemoveNoteDetail::findNotesToRemove(ArrayRef Data, size_t Align, + ArrayRef NotesToRemove) { + LLVM_ELF_IMPORT_TYPES_ELFT(ELFT); + std::vector ToRemove; + uint64_t CurPos = 0; + while (CurPos + sizeof(Elf_Nhdr) <= Data.size()) { + auto Nhdr = reinterpret_cast(Data.data() + CurPos); + size_t FullSize = Nhdr->getSize(Align); + if (CurPos + FullSize > Data.size()) + break; + Elf_Note Note(*Nhdr); + bool ShouldRemove = + llvm::any_of(NotesToRemove, [&Note](const RemoveNoteInfo &NoteInfo) { + return NoteInfo.TypeId == Note.getType() && + (NoteInfo.Name.empty() || NoteInfo.Name == Note.getName()); + }); + if (ShouldRemove) + ToRemove.push_back({CurPos, CurPos + FullSize}); + CurPos += FullSize; + } + return ToRemove; +} + +std::vector +RemoveNoteDetail::updateData(ArrayRef OldData, + ArrayRef ToRemove) { + std::vector NewData; + NewData.reserve(OldData.size()); + uint64_t CurPos = 0; + for (const DeletedRange &RemRange : ToRemove) { + if (CurPos < RemRange.OldFrom) { + auto Slice = OldData.slice(CurPos, RemRange.OldFrom - CurPos); + NewData.insert(NewData.end(), Slice.begin(), Slice.end()); + } + CurPos = RemRange.OldTo; + } + if (CurPos < OldData.size()) { + auto Slice = OldData.slice(CurPos); + NewData.insert(NewData.end(), Slice.begin(), Slice.end()); + } + return NewData; +} + +static Error removeNotes(Object &Obj, endianness Endianness, + ArrayRef NotesToRemove, + function_ref ErrorCallback) { + // TODO: Support note segments. + if (ErrorCallback) { + for (Segment &Seg : Obj.segments()) { + if (Seg.Type == PT_NOTE) { + if (Error E = ErrorCallback(createStringError( + errc::not_supported, "note segments are not supported"))) + return E; + break; + } + } + } + for (auto &Sec : Obj.sections()) { + if (Sec.Type != SHT_NOTE || !Sec.hasContents()) + continue; + // TODO: Support note sections in segments. + if (Sec.ParentSegment) { + if (ErrorCallback) + if (Error E = ErrorCallback(createStringError( + errc::not_supported, + "cannot remove note(s) from " + Sec.Name + + ": sections in segments are not supported"))) + return E; + continue; + } + ArrayRef OldData = Sec.getContents(); + size_t Align = std::max(4, Sec.Align); + // Note: notes for both 32-bit and 64-bit ELF files use 4-byte words in the + // header, so the parsers are the same. + auto ToRemove = (Endianness == endianness::little) + ? RemoveNoteDetail::findNotesToRemove( + OldData, Align, NotesToRemove) + : RemoveNoteDetail::findNotesToRemove( + OldData, Align, NotesToRemove); + if (!ToRemove.empty()) { + if (Error E = Obj.updateSectionData( + Sec, RemoveNoteDetail::updateData(OldData, ToRemove))) + return E; + } + } + return Error::success(); +} + static Error handleUserSection(const NewSectionInfo &NewSection, function_ref)> F) { @@ -799,6 +905,12 @@ static Error handleArgs(const CommonConfig &Config, const ELFConfig &ELFConfig, ? endianness::little : endianness::big; + if (!ELFConfig.NotesToRemove.empty()) { + if (Error Err = + removeNotes(Obj, E, ELFConfig.NotesToRemove, Config.ErrorCallback)) + return Err; + } + for (const NewSectionInfo &AddedSection : Config.AddSection) { auto AddSection = [&](StringRef Name, ArrayRef Data) -> Error { OwnedDataSection &NewSection = diff --git a/llvm/lib/ObjCopy/ELF/ELFObject.cpp b/llvm/lib/ObjCopy/ELF/ELFObject.cpp index 01c2f24629077..45c7ea49b5d93 100644 --- a/llvm/lib/ObjCopy/ELF/ELFObject.cpp +++ b/llvm/lib/ObjCopy/ELF/ELFObject.cpp @@ -2154,37 +2154,46 @@ ELFWriter::ELFWriter(Object &Obj, raw_ostream &Buf, bool WSH, : Writer(Obj, Buf), WriteSectionHeaders(WSH && Obj.HadShdrs), OnlyKeepDebug(OnlyKeepDebug) {} -Error Object::updateSection(StringRef Name, ArrayRef Data) { - auto It = llvm::find_if(Sections, - [&](const SecPtr &Sec) { return Sec->Name == Name; }); - if (It == Sections.end()) - return createStringError(errc::invalid_argument, "section '%s' not found", - Name.str().c_str()); - - auto *OldSec = It->get(); - if (!OldSec->hasContents()) +Error Object::updateSectionData(SecPtr &Sec, ArrayRef Data) { + if (!Sec->hasContents()) return createStringError( errc::invalid_argument, "section '%s' cannot be updated because it does not have contents", - Name.str().c_str()); + Sec->Name.c_str()); - if (Data.size() > OldSec->Size && OldSec->ParentSegment) + if (Data.size() > Sec->Size && Sec->ParentSegment) return createStringError(errc::invalid_argument, "cannot fit data of size %zu into section '%s' " "with size %" PRIu64 " that is part of a segment", - Data.size(), Name.str().c_str(), OldSec->Size); + Data.size(), Sec->Name.c_str(), Sec->Size); - if (!OldSec->ParentSegment) { - *It = std::make_unique(*OldSec, Data); + if (!Sec->ParentSegment) { + Sec = std::make_unique(*Sec, Data); } else { // The segment writer will be in charge of updating these contents. - OldSec->Size = Data.size(); - UpdatedSections[OldSec] = Data; + Sec->Size = Data.size(); + UpdatedSections[Sec.get()] = Data; } return Error::success(); } +Error Object::updateSection(StringRef Name, ArrayRef Data) { + auto It = llvm::find_if(Sections, + [&](const SecPtr &Sec) { return Sec->Name == Name; }); + if (It == Sections.end()) + return createStringError(errc::invalid_argument, "section '%s' not found", + Name.str().c_str()); + return updateSectionData(*It, Data); +} + +Error Object::updateSectionData(SectionBase &S, ArrayRef Data) { + auto It = llvm::find_if(Sections, + [&](const SecPtr &Sec) { return Sec.get() == &S; }); + assert(It != Sections.end() && "The section should belong to the object"); + return updateSectionData(*It, Data); +} + Error Object::removeSections( bool AllowBrokenLinks, std::function ToRemove) { diff --git a/llvm/lib/ObjCopy/ELF/ELFObject.h b/llvm/lib/ObjCopy/ELF/ELFObject.h index 6ccf85387131e..d8f79a4b1a3cc 100644 --- a/llvm/lib/ObjCopy/ELF/ELFObject.h +++ b/llvm/lib/ObjCopy/ELF/ELFObject.h @@ -549,6 +549,7 @@ class SectionBase { virtual void replaceSectionReferences(const DenseMap &); virtual bool hasContents() const { return false; } + virtual ArrayRef getContents() const { return {}; } // Notify the section that it is subject to removal. virtual void onRemove(); @@ -619,6 +620,8 @@ class Section : public SectionBase { bool hasContents() const override { return Type != ELF::SHT_NOBITS && Type != ELF::SHT_NULL; } + ArrayRef getContents() const override { return Contents; } + void restoreSymTabLink(SymbolTableSection &SymTab) override; }; @@ -654,6 +657,7 @@ class OwnedDataSection : public SectionBase { Error accept(SectionVisitor &Sec) const override; Error accept(MutableSectionVisitor &Visitor) override; bool hasContents() const override { return true; } + ArrayRef getContents() const override { return Data; } }; class CompressedSection : public SectionBase { @@ -1164,6 +1168,8 @@ class Object { return Sec.Flags & ELF::SHF_ALLOC; }; + Error updateSectionData(SecPtr &Sec, ArrayRef Data); + public: template using ConstRange = iterator_range Data); + Error updateSectionData(SectionBase &S, ArrayRef Data); SectionBase *findSection(StringRef Name) { auto SecIt = diff --git a/llvm/test/tools/llvm-objcopy/ELF/remove-note.test b/llvm/test/tools/llvm-objcopy/ELF/remove-note.test new file mode 100644 index 0000000000000..f8936bf9ea731 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/ELF/remove-note.test @@ -0,0 +1,198 @@ +## Check incompatible options. +# RUN: not llvm-objcopy --remove-note=1 --remove-section=.test - 2>&1 | FileCheck %s --check-prefix=ERR-REMSEC +# RUN: not llvm-objcopy --remove-note=1 --add-section=.test=%s - 2>&1 | FileCheck %s --check-prefix=ERR-ADDSEC +# RUN: not llvm-objcopy --remove-note=1 --update-section=.test=%s - 2>&1 | FileCheck %s --check-prefix=ERR-UPDSEC + +# ERR-REMSEC: error: cannot specify both --remove-note and --remove-section +# ERR-ADDSEC: error: cannot specify both --remove-note and --add-section +# ERR-UPDSEC: error: cannot specify both --remove-note and --update-section + +## Check invalid argument formats. +# RUN: not llvm-objcopy --remove-note= - 2>&1 | FileCheck %s --check-prefix=ERR-NOTYPEID +# RUN: not llvm-objcopy --remove-note=CORE/ - 2>&1 | FileCheck %s --check-prefix=ERR-NOTYPEID +# RUN: not llvm-objcopy --remove-note=/1 - 2>&1 | FileCheck %s --check-prefix=ERR-EMPTYNAME +# RUN: not llvm-objcopy --remove-note=CORE/1/2 - 2>&1 | FileCheck %s --check-prefix=ERR-INVNUM1 +# RUN: not llvm-objcopy --remove-note=Notanumber - 2>&1 | FileCheck %s --check-prefix=ERR-INVNUM2 +# RUN: not llvm-objcopy --remove-note=CORE/Notanumber - 2>&1 | FileCheck %s --check-prefix=ERR-INVNUM2 + +# ERR-NOTYPEID: error: bad format for --remove-note, missing type_id +# ERR-EMPTYNAME: error: bad format for --remove-note, note name is empty +# ERR-INVNUM1: error: bad note type_id for --remove-note: '1/2' +# ERR-INVNUM2: error: bad note type_id for --remove-note: 'Notanumber' + +## Check deleting notes: +## * --remove-note=1 will remove note "CORE/1" and "LINUX/1", +## * --remove-note=DUMMY/2 will not remove any notes because there are no notes with this owner, +## * --remove-note=CORE/3 will remove "CORE/3" but preserve "LINUX/3". +# RUN: yaml2obj --docnum=1 -D ALIGN=8 -D ELFCLASS=64 -D ENDIANNESS=LSB %s -o %t8.64.lsb +# RUN: llvm-objcopy --remove-note=0x01 --remove-note=DUMMY/2 --remove-note=CORE/0x03 %t8.64.lsb %t8.64.lsb.o +# RUN: llvm-readobj --segments --sections --notes %t8.64.lsb.o | \ +# RUN: FileCheck %s -D#SIZE0=32 -D#SIZE1=64 + +# RUN: yaml2obj --docnum=1 -D ALIGN=4 -D ELFCLASS=64 -D ENDIANNESS=MSB %s -o %t4.64.msb +# RUN: llvm-objcopy --remove-note=0x01 --remove-note=DUMMY/0x02 --remove-note=CORE/3 %t4.64.msb %t4.64.msb.o +# RUN: llvm-readobj --segments --sections --notes %t4.64.msb.o | \ +# RUN: FileCheck %s -D#SIZE0=24 -D#SIZE1=48 + +# RUN: yaml2obj --docnum=1 -D ALIGN=4 -D ELFCLASS=32 -D ENDIANNESS=LSB %s -o %t4.32.lsb +# RUN: llvm-objcopy --remove-note=1 --remove-note=DUMMY/0x02 --remove-note=CORE/3 %t4.32.lsb %t4.32.lsb.o +# RUN: llvm-readobj --segments --sections --notes %t4.32.lsb.o | \ +# RUN: FileCheck %s -D#SIZE0=24 -D#SIZE1=48 + +# CHECK: Sections [ +# CHECK: Section { +# CHECK: Name: .note0 +# CHECK-NEXT: Type: SHT_NOTE +# CHECK-NEXT: Flags [ +# CHECK-NEXT: ] +# CHECK-NEXT: Address: +# CHECK-NEXT: Offset: +# CHECK-NEXT: Size: [[#%d,SIZE0]] +# CHECK: Name: .note1 +# CHECK-NEXT: Type: SHT_NOTE +# CHECK-NEXT: Flags [ +# CHECK-NEXT: ] +# CHECK-NEXT: Address: +# CHECK-NEXT: Offset: +# CHECK-NEXT: Size: [[#%d,SIZE1]] +# CHECK: Name: .note2 +# CHECK-NEXT: Type: SHT_NOTE +# CHECK-NEXT: Flags [ +# CHECK-NEXT: ] +# CHECK-NEXT: Address: +# CHECK-NEXT: Offset: +# CHECK-NEXT: Size: 0 + +# CHECK: NoteSections [ +# CHECK-NEXT: NoteSection { +# CHECK-NEXT: Name: .note0 +# CHECK-NEXT: Offset: +# CHECK-NEXT: Size: 0x[[#%X,SIZE0]] +# CHECK-NEXT: Notes [ +# CHECK-NEXT: { +# CHECK-NEXT: Owner: CORE +# CHECK-NEXT: Data size: 0x2 +# CHECK-NEXT: Type: NT_ARCH +# CHECK-NEXT: Description data ( +# CHECK-NEXT: 0000: 0201 +# CHECK-NEXT: ) +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: NoteSection { +# CHECK-NEXT: Name: .note1 +# CHECK-NEXT: Offset: +# CHECK-NEXT: Size: 0x[[#%X,SIZE1]] +# CHECK-NEXT: Notes [ +# CHECK-NEXT: { +# CHECK-NEXT: Owner: LINUX +# CHECK-NEXT: Data size: 0x2 +# CHECK-NEXT: Type: Unknown (0x00000003) +# CHECK-NEXT: Description data ( +# CHECK-NEXT: 0000: 0301 +# CHECK-NEXT: ) +# CHECK-NEXT: } +# CHECK-NEXT: { +# CHECK-NEXT: Owner: CORE +# CHECK-NEXT: Data size: 0x2 +# CHECK-NEXT: Type: Unknown (0x00000004) +# CHECK-NEXT: Description data ( +# CHECK-NEXT: 0000: 0401 +# CHECK-NEXT: ) +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: NoteSection { +# CHECK-NEXT: Name: .note2 +# CHECK-NEXT: Offset: +# CHECK-NEXT: Size: 0x0 +# CHECK-NEXT: Notes [ +# CHECK-NEXT: ] +# CHECK-NEXT: } + +--- !ELF +FileHeader: + Class: ELFCLASS[[ELFCLASS]] + Data: ELFDATA2[[ENDIANNESS]] + Type: ET_REL + Machine: EM_X86_64 +Sections: + - Name: .note0 + Type: SHT_NOTE + AddressAlign: [[ALIGN]] + Notes: + - Name: CORE + Type: 0x01 + Desc: 0101 + - Name: CORE + Type: 0x02 + Desc: 0201 + - Name: .note1 + Type: SHT_NOTE + AddressAlign: [[ALIGN]] + Notes: + - Name: LINUX + Type: 0x03 + Desc: 0301 + - Name: CORE + Type: 0x03 + Desc: 0302 + - Name: CORE + Type: 0x04 + Desc: 0401 + - Name: .note2 + Type: SHT_NOTE + AddressAlign: [[ALIGN]] + Notes: + - Name: LINUX + Type: 0x01 + Desc: 0102 + +# RUN: yaml2obj --docnum=2 %s -o %t2 +# RUN: llvm-objcopy --remove-note=1 %t2 %t2o 2>&1 | FileCheck %s --check-prefix=TEST2 +# TEST2: warning: note segments are not supported +# TEST2-NOT: note segments are not supported + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_CORE + Machine: EM_X86_64 +ProgramHeaders: + - Type: PT_NOTE + FirstSec: .data0 + LastSec: .data0 + - Type: PT_NOTE + FirstSec: .data1 + LastSec: .data1 +Sections: + - Name: .data0 + Type: Fill + Size: 8 + - Name: .data1 + Type: Fill + Size: 8 + +# RUN: yaml2obj --docnum=3 %s -o %t3 +# RUN: llvm-objcopy --remove-note=1 %t3 %t3o 2>&1 | FileCheck %s --check-prefix=TEST3 +# TEST3: warning: cannot remove note(s) from .note: sections in segments are not supported + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +ProgramHeaders: + - Type: PT_LOAD + FirstSec: .note + LastSec: .note +Sections: + - Name: .note + Type: SHT_NOTE + AddressAlign: 4 + Notes: + - Name: ABC + Type: 1 + Desc: 0102 diff --git a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp index 0925fc55317f7..0d209590655ef 100644 --- a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp +++ b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp @@ -538,6 +538,38 @@ static Expected parseNewSymbolInfo(StringRef FlagValue) { return SI; } +static Expected parseRemoveNoteInfo(StringRef FlagValue) { + // Parse value given with --remove-note option. The format is: + // + // [name/]type_id + // + // where: + // - optional note name. If not given, all notes with the specified + // are removed. + // - note type value, can be decimal or hexadecimal number prefixed + // with 0x. + RemoveNoteInfo NI; + StringRef TypeIdStr; + if (auto Idx = FlagValue.find('/'); Idx != StringRef::npos) { + if (Idx == 0) + return createStringError( + errc::invalid_argument, + "bad format for --remove-note, note name is empty"); + NI.Name = FlagValue.slice(0, Idx); + TypeIdStr = FlagValue.substr(Idx + 1); + } else { + TypeIdStr = FlagValue; + } + if (TypeIdStr.empty()) + return createStringError(errc::invalid_argument, + "bad format for --remove-note, missing type_id"); + if (TypeIdStr.getAsInteger(0, NI.TypeId)) + return createStringError(errc::invalid_argument, + "bad note type_id for --remove-note: '%s'", + TypeIdStr.str().c_str()); + return NI; +} + // Parse input option \p ArgValue and load section data. This function // extracts section name and name of the file keeping section data from // ArgValue, loads data from the file, and stores section name and data @@ -1221,6 +1253,29 @@ objcopy::parseObjcopyOptions(ArrayRef ArgsArr, }; } + for (auto *Arg : InputArgs.filtered(OBJCOPY_remove_note)) { + Expected NoteInfo = parseRemoveNoteInfo(Arg->getValue()); + if (!NoteInfo) + return NoteInfo.takeError(); + + ELFConfig.NotesToRemove.push_back(*NoteInfo); + } + + if (!ELFConfig.NotesToRemove.empty()) { + if (!Config.ToRemove.empty()) + return createStringError( + errc::invalid_argument, + "cannot specify both --remove-note and --remove-section"); + if (!Config.AddSection.empty()) + return createStringError( + errc::invalid_argument, + "cannot specify both --remove-note and --add-section"); + if (!Config.UpdateSection.empty()) + return createStringError( + errc::invalid_argument, + "cannot specify both --remove-note and --update-section"); + } + if (Config.DecompressDebugSections && Config.CompressionType != DebugCompressionType::None) { return createStringError( diff --git a/llvm/tools/llvm-objcopy/ObjcopyOpts.td b/llvm/tools/llvm-objcopy/ObjcopyOpts.td index 434b5ff92324e..fbc6a59d9461e 100644 --- a/llvm/tools/llvm-objcopy/ObjcopyOpts.td +++ b/llvm/tools/llvm-objcopy/ObjcopyOpts.td @@ -297,3 +297,7 @@ defm pad_to "of zero or the value specified by the --gap-fill option. " "This option is only supported for ELF input and binary output">, MetaVarName<"address">; + +defm remove_note + : Eq<"remove-note", "Remove note(s) with and optional ">, + MetaVarName<"[name/]type_id">; diff --git a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp index ad3e60472369b..7e708e309f207 100644 --- a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp +++ b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp @@ -248,6 +248,8 @@ int llvm_objcopy_main(int argc, char **argv, const llvm::ToolContext &) { return 1; } for (ConfigManager &ConfigMgr : DriverConfig->CopyConfigs) { + assert(!ConfigMgr.Common.ErrorCallback); + ConfigMgr.Common.ErrorCallback = reportWarning; if (Error E = executeObjcopy(ConfigMgr)) { logAllUnhandledErrors(std::move(E), WithColor::error(errs(), ToolName)); return 1;