summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Bertalan <dani@danielbertalan.dev>2022-01-07 16:29:59 +0100
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2022-01-10 09:55:45 +0330
commitb19cc3cdcbf14f0c4313df8dcc1291c38765d60d (patch)
tree343ef8bd7853e95ede343cbf85b98dabbbf5116e
parent3132ce1d36ff9350fc1bbf83d502b0c5a94ef423 (diff)
downloadserenity-b19cc3cdcbf14f0c4313df8dcc1291c38765d60d.zip
Toolchain: Backport `--update-section` support to llvm-objcopy
This commit backports the LLVM commit that adds support for the `--update-section` flag to llvm-objcopy. We use this feature of GNU objcopy to embed the symbol map in the kernel. The corresponding LLVM Phabricator Differential Revision can be found here: https://reviews.llvm.org/D112116 This patch is identical to the upstream commit, except for two hunks that had to be changed as they didn't apply cleanly.
l---------Ports/llvm/patches/llvm-backport-objcopy-update-section.patch1
-rwxr-xr-xToolchain/BuildClang.sh4
-rw-r--r--Toolchain/Patches/llvm-backport-objcopy-update-section.patch440
3 files changed, 444 insertions, 1 deletions
diff --git a/Ports/llvm/patches/llvm-backport-objcopy-update-section.patch b/Ports/llvm/patches/llvm-backport-objcopy-update-section.patch
new file mode 120000
index 0000000000..bba9bce43a
--- /dev/null
+++ b/Ports/llvm/patches/llvm-backport-objcopy-update-section.patch
@@ -0,0 +1 @@
+../../../Toolchain/Patches/llvm-backport-objcopy-update-section.patch \ No newline at end of file
diff --git a/Toolchain/BuildClang.sh b/Toolchain/BuildClang.sh
index b909d7c9fb..a64d4d62b7 100755
--- a/Toolchain/BuildClang.sh
+++ b/Toolchain/BuildClang.sh
@@ -208,11 +208,13 @@ pushd "$DIR/Tarballs"
git init > /dev/null
git add . > /dev/null
git commit -am "BASE" > /dev/null
+ git am "$DIR"/Patches/llvm-backport-objcopy-update-section.patch > /dev/null
git apply "$DIR"/Patches/llvm.patch > /dev/null
else
patch -p1 < "$DIR/Patches/llvm.patch" > /dev/null
+ patch -p1 < "$DIR/Patches/llvm-backport-objcopy-update-section.patch" > /dev/null
fi
- $MD5SUM "$DIR/Patches/llvm.patch" > .patch.applied
+ $MD5SUM "$DIR/Patches/llvm.patch" "$DIR/Patches/llvm-backport-objcopy-update-section.patch" > .patch.applied
popd
md5=""
diff --git a/Toolchain/Patches/llvm-backport-objcopy-update-section.patch b/Toolchain/Patches/llvm-backport-objcopy-update-section.patch
new file mode 100644
index 0000000000..8b0f3aa38d
--- /dev/null
+++ b/Toolchain/Patches/llvm-backport-objcopy-update-section.patch
@@ -0,0 +1,440 @@
+From 140a4569a033b133fc2dc1787ae83d505e03b982 Mon Sep 17 00:00:00 2001
+From: Leonard Chan <leonardchan@google.com>
+Date: Wed, 20 Oct 2021 13:03:49 -0700
+Subject: [PATCH] Add --update-section
+
+This is another attempt at D59351 which attempted to add --update-section, but
+with some heuristics for adjusting segment/section offsets/sizes in the event
+the data copied into the section is larger than the original size of the section.
+We are opting to not support this case. GNU's objcopy was able to do this because
+the linker and objcopy are tightly coupled enough that segment reformatting was
+simpler. This is not the case with llvm-objcopy and lld where they like to be separated.
+
+This will attempt to copy data into the section without changing any other
+properties of the parent segment (if the section is part of one).
+
+Differential Revision: https://reviews.llvm.org/D112116
+---
+ .../llvm-objcopy/ELF/update-section.test | 176 ++++++++++++++++++
+ llvm/tools/llvm-objcopy/CommonConfig.h | 1 +
+ llvm/tools/llvm-objcopy/ConfigManager.cpp | 11 ++
+ llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp | 48 +++--
+ llvm/tools/llvm-objcopy/ELF/Object.cpp | 42 +++++
+ llvm/tools/llvm-objcopy/ELF/Object.h | 14 ++
+ llvm/tools/llvm-objcopy/ObjcopyOpts.td | 4 +
+ 7 files changed, 281 insertions(+), 15 deletions(-)
+ create mode 100644 llvm/test/tools/llvm-objcopy/ELF/update-section.test
+
+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
+index 000000000..644225557
+--- /dev/null
++++ b/llvm/test/tools/llvm-objcopy/ELF/update-section.test
+@@ -0,0 +1,176 @@
++# 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
++
++## Ensure the data that was in a shortened section within a segment is still retained.
++## For cases where there are gaps in segments not covered by sections, the existing
++## contents are preserved.
++# RUN: od -t x1 -j 0x78 -N 16 %t.short.o | FileCheck %s --check-prefix=PRESERVED
++
++## 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: "3130303030303030"
++ - 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: Offset:
++# NORMAL-SAME: {{ }}0x78{{$}}
++# NORMAL: Size:
++# NORMAL-SAME: {{ }}8{{$}}
++# NORMAL: SectionData (
++# NORMAL-NEXT: |11112222|
++# NORMAL-NEXT: )
++# NORMAL: Name: .unmodified_in_segment
++# NORMAL: Offset:
++# NORMAL-SAME: {{ }}0x80{{$}}
++# NORMAL: Size:
++# NORMAL-SAME: {{ }}8{{$}}
++# NORMAL: SectionData (
++# NORMAL-NEXT: |10000000|
++# NORMAL-NEXT: )
++# NORMAL: ProgramHeaders [
++# NORMAL-NEXT: ProgramHeader {
++# NORMAL-NEXT: Type: PT_LOAD (0x1)
++# NORMAL: FileSize:
++# NORMAL-SAME: {{ }}16{{$}}
++# NORMAL: MemSize:
++# NORMAL-SAME: {{ }}16{{$}}
++# NORMAL: }
++# NORMAL-NEXT: ]
++
++# SHORT: Name: .in_segment
++# SHORT: Offset:
++# SHORT-SAME: {{ }}0x78{{$}}
++# SHORT: Size:
++# SHORT-SAME: {{ }}2{{$}}
++# SHORT: SectionData (
++# SHORT-NEXT: |11|
++# SHORT-NEXT: )
++# SHORT: Name: .unmodified_in_segment
++# SHORT: Offset:
++# SHORT-SAME: {{ }}0x80{{$}}
++# SHORT: Size:
++# SHORT-SAME: {{ }}8{{$}}
++# SHORT: SectionData (
++# SHORT-NEXT: |10000000|
++# SHORT-NEXT: )
++# SHORT: ProgramHeaders [
++# SHORT-NEXT: ProgramHeader {
++# SHORT-NEXT: Type: PT_LOAD (0x1)
++# SHORT: FileSize:
++# SHORT-SAME: {{ }}16{{$}}
++# SHORT: MemSize:
++# SHORT-SAME: {{ }}16{{$}}
++# SHORT: }
++# SHORT-NEXT: ]
++
++## The first 8 bytes are the modified section. The last 8 bytes are the
++## unmodified section.
++# PRESERVED: 31 31 30 30 30 30 30 30 31 30 30 30 30 30 30 30
++
++# LONG: Name: .not_in_segment
++# LONG: Size:
++# LONG-SAME: {{ }}9{{$}}
++# LONG: SectionData (
++# LONG-NEXT: |111122223|
++# LONT-NEXT: )
++
++# ADD-UPDATE: Name: .added
++# ADD-UPDATE: Size:
++# ADD-UPDATE-SAME: {{ }}8{{$}}
++# ADD-UPDATE: SectionData (
++# ADD-UPDATE-NEXT: |11113333|
++# ADD-UPDATE: )
++
++# 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
+index 131ce5c59..b7a7a5ec7 100644
+--- a/llvm/tools/llvm-objcopy/CommonConfig.h
++++ b/llvm/tools/llvm-objcopy/CommonConfig.h
+@@ -210,6 +210,7 @@ struct CommonConfig {
+ // Repeated options
+ std::vector<StringRef> AddSection;
+ std::vector<StringRef> DumpSection;
++ std::vector<StringRef> UpdateSection;
+ std::vector<StringRef> RPathToAdd;
+ std::vector<StringRef> RPathToPrepend;
+ DenseMap<StringRef, StringRef> RPathsToUpdate;
+diff --git a/llvm/tools/llvm-objcopy/ConfigManager.cpp b/llvm/tools/llvm-objcopy/ConfigManager.cpp
+index 9f7d06b99..2840f90cb 100644
+--- a/llvm/tools/llvm-objcopy/ConfigManager.cpp
++++ b/llvm/tools/llvm-objcopy/ConfigManager.cpp
+@@ -887,6 +887,17 @@ objcopy::parseObjcopyOptions(ArrayRef<const char *> RawArgsArr,
+ "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
+index 986eeca62..b9c6abdc1 100644
+--- a/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp
++++ b/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp
+@@ -554,6 +554,22 @@ static void addSymbol(Object &Obj, const NewSymbolInfo &SymInfo,
+ Sec ? (uint16_t)SYMBOL_SIMPLE_INDEX : (uint16_t)SHN_ABS, 0);
+ }
+
++static Error
++handleUserSection(StringRef Flag,
++ function_ref<Error(StringRef, ArrayRef<uint8_t>)> F) {
++ std::pair<StringRef, StringRef> SecPair = Flag.split("=");
++ StringRef SecName = SecPair.first;
++ StringRef File = SecPair.second;
++ ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = MemoryBuffer::getFile(File);
++ if (!BufOrErr)
++ return createFileError(File, errorCodeToError(BufOrErr.getError()));
++ std::unique_ptr<MemoryBuffer> Buf = std::move(*BufOrErr);
++ ArrayRef<uint8_t> Data(
++ reinterpret_cast<const uint8_t *>(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"
+@@ -664,21 +680,23 @@ static Error handleArgs(const CommonConfig &Config, const ELFConfig &ELFConfig,
+ Sec.Type = SHT_NOBITS;
+
+ for (const auto &Flag : Config.AddSection) {
+- std::pair<StringRef, StringRef> SecPair = Flag.split("=");
+- StringRef SecName = SecPair.first;
+- StringRef File = SecPair.second;
+- ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
+- MemoryBuffer::getFile(File);
+- if (!BufOrErr)
+- return createFileError(File, errorCodeToError(BufOrErr.getError()));
+- std::unique_ptr<MemoryBuffer> Buf = std::move(*BufOrErr);
+- ArrayRef<uint8_t> Data(
+- reinterpret_cast<const uint8_t *>(Buf->getBufferStart()),
+- Buf->getBufferSize());
+- OwnedDataSection &NewSection =
+- Obj.addSection<OwnedDataSection>(SecName, Data);
+- if (SecName.startswith(".note") && SecName != ".note.GNU-stack")
+- NewSection.Type = SHT_NOTE;
++ auto AddSection = [&](StringRef Name, ArrayRef<uint8_t> Data) {
++ OwnedDataSection &NewSection =
++ Obj.addSection<OwnedDataSection>(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<uint8_t> 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.cpp b/llvm/tools/llvm-objcopy/ELF/Object.cpp
+index ba91d08e5..c8a27f7a6 100644
+--- a/llvm/tools/llvm-objcopy/ELF/Object.cpp
++++ b/llvm/tools/llvm-objcopy/ELF/Object.cpp
+@@ -2093,6 +2093,17 @@ template <class ELFT> void ELFWriter<ELFT>::writeSegmentData() {
+ Size);
+ }
+
++ for (auto it : Obj.getUpdatedSections()) {
++ SectionBase *Sec = it.first;
++ ArrayRef<uint8_t> 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;
+@@ -2110,6 +2121,37 @@ ELFWriter<ELFT>::ELFWriter(Object &Obj, raw_ostream &Buf, bool WSH,
+ : Writer(Obj, Buf), WriteSectionHeaders(WSH && Obj.HadShdrs),
+ OnlyKeepDebug(OnlyKeepDebug) {}
+
++Error Object::updateSection(StringRef Name, ArrayRef<uint8_t> 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<OwnedDataSection>(*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<bool(const SectionBase &)> ToRemove) {
+
+diff --git a/llvm/tools/llvm-objcopy/ELF/Object.h b/llvm/tools/llvm-objcopy/ELF/Object.h
+index 6fd26afa3..060a92159 100644
+--- a/llvm/tools/llvm-objcopy/ELF/Object.h
++++ b/llvm/tools/llvm-objcopy/ELF/Object.h
+@@ -429,6 +429,7 @@ public:
+ virtual void markSymbols();
+ virtual void
+ replaceSectionReferences(const DenseMap<SectionBase *, SectionBase *> &);
++ virtual bool hasContents() const { return false; }
+ // Notify the section that it is subject to removal.
+ virtual void onRemove();
+ };
+@@ -493,6 +494,9 @@ public:
+ function_ref<bool(const SectionBase *)> 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 @@ public:
+ OriginalOffset = SecOff;
+ }
+
++ OwnedDataSection(SectionBase &S, ArrayRef<uint8_t> 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 {
+@@ -1016,6 +1026,7 @@ private:
+ std::vector<SecPtr> Sections;
+ std::vector<SegPtr> Segments;
+ std::vector<SecPtr> RemovedSections;
++ DenseMap<SectionBase *, std::vector<uint8_t>> UpdatedSections;
+
+ static bool sectionIsAlloc(const SectionBase &Sec) {
+ return Sec.Flags & ELF::SHF_ALLOC;
+@@ -1066,6 +1077,9 @@ public:
+ return make_filter_range(make_pointee_range(Sections), sectionIsAlloc);
+ }
+
++ const auto &getUpdatedSections() const { return UpdatedSections; }
++ Error updateSection(StringRef Name, ArrayRef<uint8_t> Data);
++
+ SectionBase *findSection(StringRef Name) {
+ auto SecIt =
+ find_if(Sections, [&](const SecPtr &Sec) { return Sec->Name == Name; });
+diff --git a/llvm/tools/llvm-objcopy/ObjcopyOpts.td b/llvm/tools/llvm-objcopy/ObjcopyOpts.td
+index 63abbe4c2..ccd7ff292 100644
+--- a/llvm/tools/llvm-objcopy/ObjcopyOpts.td
++++ b/llvm/tools/llvm-objcopy/ObjcopyOpts.td
+@@ -214,3 +214,7 @@ defm add_symbol
+ "compatibility: debug, constructor, warning, indirect, synthetic, "
+ "unique-object, before.">,
+ MetaVarName<"name=[section:]value[,flags]">;
++
++defm update_section
++ : Eq<"update-section", "Add section <name> with contents from a file <file>.">,
++ MetaVarName<"name=file">;
+--
+2.34.1
+