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,147 @@ +# RUN: yaml2obj %s -o %t + +# RUN: echo -n 11112222 > %t.same +# RUN: echo -n 00000000 > %t.zeros +# RUN: echo -n 11 > %t.short +# RUN: echo -n 11113333 > %t.updated +# RUN: echo -n 111122223 > %t.larger + +## Update the segment section with a regular chunk of data the same size as the section. +# RUN: llvm-objcopy --update-section=.in_segment=%t.same %t %t.same.o +# RUN: llvm-readobj --section-headers --section-data --program-headers %t.same.o | \ +# RUN: FileCheck %s --check-prefix=NORMAL + +## Show that if we overwrite a given section (.in_segment) with new data, then rewrite it +## back (from the second `--update-section`), then it should be the exact same as the +## original file. +# RUN: llvm-objcopy %t %t.copy.o +# RUN: llvm-objcopy --update-section=.in_segment=%t.same --update-section=.in_segment=%t.zeros %t %t.original.o +# RUN: cmp %t.copy.o %t.original.o + +## Update segment section with a smaller chunk of data. This will also update the +## section size to the length of the new data written. This does not affect the offset +## of any subsequent sections in the same segment as this. +# RUN: llvm-objcopy --update-section=.in_segment=%t.short %t %t.short.o +# RUN: llvm-readobj --section-headers --section-data --program-headers %t.short.o | \ +# RUN: FileCheck %s --check-prefix=SHORT + +## Add a new section via --add-section, then update it. +# RUN: llvm-objcopy --add-section=.added=%t.zeros --update-section=.added=%t.updated \ +# RUN: %t %t.updated.o +# RUN: llvm-readobj --section-headers --section-data --program-headers %t.updated.o | \ +# RUN: FileCheck %s --check-prefix=ADD-UPDATE + +## Adding should always be first regardless of flag order. +# RUN: llvm-objcopy --update-section=.added=%t.updated --add-section=.added=%t.updated \ +# RUN: %t %t.updated.o +# RUN: llvm-readobj --section-headers --section-data --program-headers %t.updated.o | \ +# RUN: FileCheck %s --check-prefix=ADD-UPDATE + +## We can't update sections which don't exist. +# RUN: not llvm-objcopy --update-section=.nosection=%t.same %t %t-err1 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR-NO-SECTION + +## We can't update certain types of sections. +# RUN: not llvm-objcopy --update-section=.nobits_type=%t.same %t %t-err2 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR-NOBITS-TYPE +# RUN: not llvm-objcopy --update-section=.null_type=%t.same %t %t-err2 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR-NULL-TYPE + +## Fail on trying to insert data larger than the existing section. +# RUN: not llvm-objcopy --update-section=.in_segment=%t.larger %t %t-err3 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR-LARGER + +## But we can insert larger data if the section is NOT part of a segment. +# RUN: llvm-objcopy --update-section=.not_in_segment=%t.larger %t %t.larger.o +# RUN: llvm-readobj --section-headers --section-data --program-headers %t.larger.o | \ +# RUN: FileCheck %s --check-prefix=LONG + +## We should still fail on inserting larger data, even if it is superceded by a +## valid --update-section flag. +# RUN: not llvm-objcopy --update-section=.in_segment=%t.larger --update-section=.in_segment=%t.same %t %t-err3 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR-LARGER + +## Test option parsing failures. +# RUN: not llvm-objcopy --update-section %t %t.out 2>&1 | FileCheck %s --check-prefix=MISSING-EQ +# RUN: not llvm-objcopy --update-section=.in_segment= %t %t.out 2>&1 | FileCheck %s --check-prefix=MISSING-FILE + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .in_segment + Type: SHT_PROGBITS + Content: "3030303030303030" + - Name: .unmodified_in_segment + Type: SHT_PROGBITS + Content: "3030303030303030" + - Name: .nobits_type + Type: SHT_NOBITS + - Name: .not_in_segment + Type: SHT_PROGBITS + - Name: .null_type + Type: SHT_NULL +ProgramHeaders: + - Type: PT_LOAD + VAddr: 0x1000 + FirstSec: .in_segment +## The unmodified section is for ensuring that it remains untouched (ie. its +## offset is the same) even when the section before it is shrunk. + LastSec: .unmodified_in_segment + +# NORMAL: Name: .in_segment +# NORMAL: Size: 8 +# NORMAL: |11112222| +# NORMAL: Name: .unmodified_in_segment +# NORMAL: Offset: 0x80 +# NORMAL: Size: 8 +# NORMAL: |00000000| +# NORMAL: Name: .nobits_type +# NORMAL: Size: 0 +# NORMAL: ProgramHeaders [ +# NORMAL-NEXT: ProgramHeader { +# NORMAL-NEXT: Type: PT_LOAD (0x1) +# NORMAL: FileSize: 16 +# NORMAL-NEXT: MemSize: 16 +# NORMAL-NEXT: Flags [ (0x0) +# NORMAL-NEXT: ] +# NORMAL: } +# NORMAL-NEXT: ] + +# SHORT: Name: .in_segment +# SHORT: Size: 2 +# SHORT: |11| +# SHORT: Name: .unmodified_in_segment +# SHORT: Offset: 0x80 +# SHORT: Size: 8 +# SHORT: |00000000| +# SHORT: Name: .nobits_type +# SHORT: Size: 0 +# SHORT: ProgramHeaders [ +# SHORT-NEXT: ProgramHeader { +# SHORT-NEXT: Type: PT_LOAD (0x1) +# SHORT: FileSize: 16 +# SHORT-NEXT: MemSize: 16 +# SHORT-NEXT: Flags [ (0x0) +# SHORT-NEXT: ] +# SHORT: } +# SHORT-NEXT: ] + +# LONG: Name: .not_in_segment +# LONG: Size: 9 +# LONG: |111122223| + +# ADD-UPDATE: Name: .added +# ADD-UPDATE: Size: 8 +# ADD-UPDATE: |11113333| + +# ERR-NO-SECTION: error: {{.*}}section '.nosection' not found +# ERR-NOBITS-TYPE: error: {{.*}}section '.nobits_type' can't be updated because it does not have contents +# ERR-NULL-TYPE: error: {{.*}}section '.null_type' can't be updated because it does not have contents +# ERR-LARGER: error: {{.*}}cannot fit data of size 9 into section '.in_segment' with size 8 that is part of a segment + +# MISSING-EQ: error: bad format for --update-section: missing '=' +# MISSING-FILE: error: bad format for --update-section: missing file name 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, + function_ref)> 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 (StringRef 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 hasContents() const { return false; } // Notify the section that it is subject to removal. virtual void onRemove(); }; @@ -493,6 +494,9 @@ function_ref ToRemove) override; Error initialize(SectionTableRef SecTable) override; void finalize() override; + bool hasContents() const override { + return Type != ELF::SHT_NOBITS && Type != ELF::SHT_NULL; + } }; class OwnedDataSection : public SectionBase { @@ -518,9 +522,15 @@ OriginalOffset = SecOff; } + OwnedDataSection(SectionBase &S, ArrayRef Data) + : SectionBase(S), Data(std::begin(Data), std::end(Data)) { + Size = Data.size(); + } + void appendHexData(StringRef HexData); Error accept(SectionVisitor &Sec) const override; Error accept(MutableSectionVisitor &Visitor) override; + bool hasContents() const override { return true; } }; class CompressedSection : public SectionBase { @@ -1018,6 +1028,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 +1071,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,17 @@ Size); } + for (auto it : Obj.getUpdatedSections()) { + SectionBase *Sec = it.first; + ArrayRef 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 +2135,37 @@ : 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()) + return createStringError( + errc::invalid_argument, + "section '%s' can't be updated because it does not have contents", + Name.str().c_str()); + + if (Data.size() > OldSec->Size && OldSec->ParentSegment) + return createStringError(errc::invalid_argument, + "cannot fit data of size %zu into section '%s' " + "with size %zu that is part of a segment", + Data.size(), Name.str().c_str(), OldSec->Size); + + if (!OldSec->ParentSegment) { + *It = std::make_unique(*OldSec, Data); + } else { + // The segment writer will be in charge of updating these contents. + OldSec->Size = Data.size(); + UpdatedSections[OldSec] = Data; + } + + 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">;