diff --git a/llvm/test/tools/llvm-objcopy/ELF/update-section.test b/llvm/test/tools/llvm-objcopy/ELF/update-section.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/ELF/update-section.test @@ -0,0 +1,160 @@ +# RUN: yaml2obj %s -o %t + +# Update the segment section with a regular chunk of data the same size as the section. +# RUN: echo -n 11112222 > %t.text +# RUN: llvm-objcopy --update-section=.text=%t.text %t %t2 +# RUN: llvm-readobj --section-headers --section-data --program-headers %t2 | FileCheck %s --check-prefix=LONG + +# Update to original contents after another update should give us the original result. +# RUN: echo -n 00000000 > %t2.text +# RUN: llvm-objcopy %t %t3 +# RUN: llvm-objcopy --update-section=.text=%t.text --update-section=.text=%t2.text %t %t4 +# RUN: cmp %t3 %t4 + +# Update segment section with a smaller chunk of data. This will only overwrite the start of the original data. +# RUN: echo -n 11 > %t3.text +# RUN: llvm-objcopy --update-section=.text=%t3.text %t %t5 +# RUN: llvm-readobj --section-headers --section-data --program-headers %t5 | FileCheck %s --check-prefix=SHORT + +# We can't update sections which don't exist +# RUN: not llvm-objcopy --update-section=.nosection=%t.text %t3 %t-err1 2>&1 | FileCheck %s --check-prefix=ERR1 + +# We can't update certain types of sections +# RUN: not llvm-objcopy --update-section=.bss=%t.text %t3 %t-err3 2>&1 | FileCheck %s --check-prefix=ERR3 + +# Fail on trying to insert data larger than the existing section. +# RUN: echo -n 111122223 > %t4.text +# RUN: not llvm-objcopy --update-section=.text=%t4.text %t %t-err4 2>&1 | FileCheck %s --check-prefix=ERR4 + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x8 + Content: "3030303030303030" + - Name: .init + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Content: "00000000" + Address: 0x1008 + AddressAlign: 0x8 + - Name: .data + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC ] + Content: "3030303030303030" + Address: 0x1010 + AddressAlign: 0x8 + - Name: .bss + Type: SHT_NOBITS + Flags: [ SHF_ALLOC ] + Size: 0x8 + Address: 0x1018 + AddressAlign: 0x8 +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_X, PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Align: 0x1000 + FirstSec: .text + LastSec: .init + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x2010 + PAddr: 0x2010 + Align: 0x1000 + FirstSec: .data + LastSec: .bss + +# LONG: Name: .text +# LONG: Address: 0x1000 +# LONG: Offset: 0x1000 +# LONG: Size: 8 +# LONG: |11112222| +# LONG: Name: .init +# LONG: Address: 0x1008 +# LONG: Offset: 0x1008 +# LONG: Size: 4 +# LONG: Name: .data +# LONG: Address: 0x1010 +# LONG: Offset: 0x1010 +# LONG: Size: 8 +# LONG: ProgramHeaders [ +# LONG-NEXT: ProgramHeader { +# LONG-NEXT: Type: PT_LOAD (0x1) +# LONG-NEXT: Offset: 0x1000 +# LONG-NEXT: VirtualAddress: 0x1000 +# LONG-NEXT: PhysicalAddress: 0x1000 +# LONG-NEXT: FileSize: 12 +# LONG-NEXT: MemSize: 12 +# LONG-NEXT: Flags [ (0x5) +# LONG-NEXT: PF_R (0x4) +# LONG-NEXT: PF_X (0x1) +# LONG-NEXT: ] +# LONG-NEXT: Alignment: 4096 +# LONG-NEXT: } +# LONG-NEXT: ProgramHeader { +# LONG-NEXT: Type: PT_LOAD (0x1) +# LONG-NEXT: Offset: 0x1010 +# LONG-NEXT: VirtualAddress: 0x2010 +# LONG-NEXT: PhysicalAddress: 0x2010 +# LONG-NEXT: FileSize: 8 +# LONG-NEXT: MemSize: 16 +# LONG-NEXT: Flags [ (0x4) +# LONG-NEXT: PF_R (0x4) +# LONG-NEXT: ] +# LONG-NEXT: Alignment: 4096 +# LONG-NEXT: } +# LONG-NEXT: ] + +# SHORT: Name: .text +# SHORT: Address: 0x1000 +# SHORT: Offset: 0x1000 +# SHORT: Size: 8 +# SHORT: |11000000| +# SHORT: Name: .init +# SHORT: Address: 0x1008 +# SHORT: Offset: 0x1008 +# SHORT: Size: 4 +# SHORT: Name: .data +# SHORT: Address: 0x1010 +# SHORT: Offset: 0x1010 +# SHORT: Size: 8 +# SHORT: ProgramHeaders [ +# SHORT-NEXT: ProgramHeader { +# SHORT-NEXT: Type: PT_LOAD (0x1) +# SHORT-NEXT: Offset: 0x1000 +# SHORT-NEXT: VirtualAddress: 0x1000 +# SHORT-NEXT: PhysicalAddress: 0x1000 +# SHORT-NEXT: FileSize: 12 +# SHORT-NEXT: MemSize: 12 +# SHORT-NEXT: Flags [ (0x5) +# SHORT-NEXT: PF_R (0x4) +# SHORT-NEXT: PF_X (0x1) +# SHORT-NEXT: ] +# SHORT-NEXT: Alignment: 4096 +# SHORT-NEXT: } +# SHORT-NEXT: ProgramHeader { +# SHORT-NEXT: Type: PT_LOAD (0x1) +# SHORT-NEXT: Offset: 0x1010 +# SHORT-NEXT: VirtualAddress: 0x2010 +# SHORT-NEXT: PhysicalAddress: 0x2010 +# SHORT-NEXT: FileSize: 8 +# SHORT-NEXT: MemSize: 16 +# SHORT-NEXT: Flags [ (0x4) +# SHORT-NEXT: PF_R (0x4) +# SHORT-NEXT: ] +# SHORT-NEXT: Alignment: 4096 +# SHORT-NEXT: } +# SHORT-NEXT: ] + +# ERR1: error: {{.*}}Section '.nosection' not found +# ERR3: error: {{.*}}Section '.bss' can't be updated +# ERR4: error: {{.*}}Cannot fit data of size 9 into section '.text' with size 8 diff --git a/llvm/tools/llvm-objcopy/CommonConfig.h b/llvm/tools/llvm-objcopy/CommonConfig.h --- a/llvm/tools/llvm-objcopy/CommonConfig.h +++ b/llvm/tools/llvm-objcopy/CommonConfig.h @@ -210,6 +210,7 @@ // Repeated options std::vector AddSection; std::vector DumpSection; + std::vector UpdateSection; // Section matchers NameMatcher KeepSection; diff --git a/llvm/tools/llvm-objcopy/ConfigManager.cpp b/llvm/tools/llvm-objcopy/ConfigManager.cpp --- a/llvm/tools/llvm-objcopy/ConfigManager.cpp +++ b/llvm/tools/llvm-objcopy/ConfigManager.cpp @@ -880,6 +880,17 @@ "bad format for --add-section: missing file name"); Config.AddSection.push_back(ArgValue); } + for (auto Arg : InputArgs.filtered(OBJCOPY_update_section)) { + StringRef ArgValue(Arg->getValue()); + if (!ArgValue.contains('=')) + return createStringError(errc::invalid_argument, + "bad format for --update-section: missing '='"); + if (ArgValue.split("=").second.empty()) + return createStringError( + errc::invalid_argument, + "bad format for --update-section: missing file name"); + Config.UpdateSection.push_back(ArgValue); + } for (auto *Arg : InputArgs.filtered(OBJCOPY_dump_section)) { StringRef Value(Arg->getValue()); if (Value.split('=').second.empty()) diff --git a/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp b/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp --- a/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp +++ b/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp @@ -548,6 +548,22 @@ Sec ? (uint16_t)SYMBOL_SIMPLE_INDEX : (uint16_t)SHN_ABS, 0); } +static Error +handleUserSection(StringRef Flag, + std::function)> F) { + std::pair SecPair = Flag.split("="); + StringRef SecName = SecPair.first; + StringRef File = SecPair.second; + ErrorOr> BufOrErr = MemoryBuffer::getFile(File); + if (!BufOrErr) + return createFileError(File, errorCodeToError(BufOrErr.getError())); + std::unique_ptr Buf = std::move(*BufOrErr); + ArrayRef Data( + reinterpret_cast(Buf->getBufferStart()), + Buf->getBufferSize()); + return F(SecName, Data); +} + // This function handles the high level operations of GNU objcopy including // handling command line options. It's important to outline certain properties // we expect to hold of the command line operations. Any operation that "keeps" @@ -665,21 +681,23 @@ Sec.Type = SHT_NOBITS; for (const auto &Flag : Config.AddSection) { - std::pair SecPair = Flag.split("="); - StringRef SecName = SecPair.first; - StringRef File = SecPair.second; - ErrorOr> BufOrErr = - MemoryBuffer::getFile(File); - if (!BufOrErr) - return createFileError(File, errorCodeToError(BufOrErr.getError())); - std::unique_ptr Buf = std::move(*BufOrErr); - ArrayRef Data( - reinterpret_cast(Buf->getBufferStart()), - Buf->getBufferSize()); - OwnedDataSection &NewSection = - Obj.addSection(SecName, Data); - if (SecName.startswith(".note") && SecName != ".note.GNU-stack") - NewSection.Type = SHT_NOTE; + auto AddSection = [&](StringRef Name, ArrayRef Data) { + OwnedDataSection &NewSection = + Obj.addSection(Name, Data); + if (Name.startswith(".note") && Name != ".note.GNU-stack") + NewSection.Type = SHT_NOTE; + return Error::success(); + }; + if (Error E = handleUserSection(Flag, AddSection)) + return E; + } + + for (const auto &Flag : Config.UpdateSection) { + auto UpdateSection = [&](StringRef Name, ArrayRef Data) { + return Obj.updateSection(Name, Data); + }; + if (Error E = handleUserSection(Flag, UpdateSection)) + return E; } if (!Config.AddGnuDebugLink.empty()) diff --git a/llvm/tools/llvm-objcopy/ELF/Object.h b/llvm/tools/llvm-objcopy/ELF/Object.h --- a/llvm/tools/llvm-objcopy/ELF/Object.h +++ b/llvm/tools/llvm-objcopy/ELF/Object.h @@ -429,6 +429,7 @@ virtual void markSymbols(); virtual void replaceSectionReferences(const DenseMap &); + virtual bool isReplaceable() const { return false; } // Notify the section that it is subject to removal. virtual void onRemove(); }; @@ -493,6 +494,7 @@ function_ref ToRemove) override; Error initialize(SectionTableRef SecTable) override; void finalize() override; + bool isReplaceable() const override { return Type != ELF::SHT_NOBITS; } }; class OwnedDataSection : public SectionBase { @@ -521,6 +523,7 @@ void appendHexData(StringRef HexData); Error accept(SectionVisitor &Sec) const override; Error accept(MutableSectionVisitor &Visitor) override; + bool isReplaceable() const override { return true; } }; class CompressedSection : public SectionBase { @@ -1018,6 +1021,7 @@ std::vector Sections; std::vector Segments; std::vector RemovedSections; + DenseMap> UpdatedSections; static bool sectionIsAlloc(const SectionBase &Sec) { return Sec.Flags & ELF::SHF_ALLOC; @@ -1060,6 +1064,9 @@ return make_filter_range(make_pointee_range(Sections), sectionIsAlloc); } + const auto &getUpdatedSections() const { return UpdatedSections; } + Error updateSection(StringRef Name, ArrayRef Data); + SectionBase *findSection(StringRef Name) { auto SecIt = find_if(Sections, [&](const SecPtr &Sec) { return Sec->Name == Name; }); diff --git a/llvm/tools/llvm-objcopy/ELF/Object.cpp b/llvm/tools/llvm-objcopy/ELF/Object.cpp --- a/llvm/tools/llvm-objcopy/ELF/Object.cpp +++ b/llvm/tools/llvm-objcopy/ELF/Object.cpp @@ -2107,6 +2107,18 @@ Size); } + for (auto it = Obj.getUpdatedSections().begin(); + it != Obj.getUpdatedSections().end(); ++it) { + auto *Sec = it->first; + auto &Data = it->second; + + auto *Parent = Sec->ParentSegment; + assert(Parent && "This section should've been part of a segment."); + uint64_t Offset = + Sec->OriginalOffset - Parent->OriginalOffset + Parent->Offset; + llvm::copy(Data, Buf->getBufferStart() + Offset); + } + // Iterate over removed sections and overwrite their old data with zeroes. for (auto &Sec : Obj.removedSections()) { Segment *Parent = Sec.ParentSegment; @@ -2124,6 +2136,30 @@ : 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()); + if (!(*It)->isReplaceable()) + return createStringError(errc::invalid_argument, + "Section '%s' can't be updated.", + Name.str().c_str()); + + if (Data.size() > (*It)->Size) + return createStringError( + errc::invalid_argument, + "Cannot fit data of size %zu into section '%s' with size %zu.", + Data.size(), Name.str().c_str(), (*It)->Size); + + auto *OldSec = It->get(); + UpdatedSections[OldSec].clear(); + UpdatedSections[OldSec].append(Data.begin(), Data.end()); + + return Error::success(); +} + Error Object::removeSections( bool AllowBrokenLinks, std::function ToRemove) { diff --git a/llvm/tools/llvm-objcopy/ObjcopyOpts.td b/llvm/tools/llvm-objcopy/ObjcopyOpts.td --- a/llvm/tools/llvm-objcopy/ObjcopyOpts.td +++ b/llvm/tools/llvm-objcopy/ObjcopyOpts.td @@ -215,3 +215,7 @@ "compatibility: debug, constructor, warning, indirect, synthetic, " "unique-object, before.">, MetaVarName<"name=[section:]value[,flags]">; + +defm update_section + : Eq<"update-section", "Add section with contents from a file .">, + MetaVarName<"name=file">;