Skip to content

Commit 25bcd94

Browse files
committed
[llvm-objcopy] 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
1 parent 4c2cf3a commit 25bcd94

File tree

7 files changed

+281
-15
lines changed

7 files changed

+281
-15
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# RUN: yaml2obj %s -o %t
2+
3+
# RUN: echo -n 11112222 > %t.same
4+
# RUN: echo -n 00000000 > %t.zeros
5+
# RUN: echo -n 11 > %t.short
6+
# RUN: echo -n 11113333 > %t.updated
7+
# RUN: echo -n 111122223 > %t.larger
8+
9+
## Update the segment section with a regular chunk of data the same size as the section.
10+
# RUN: llvm-objcopy --update-section=.in_segment=%t.same %t %t.same.o
11+
# RUN: llvm-readobj --section-headers --section-data --program-headers %t.same.o | \
12+
# RUN: FileCheck %s --check-prefix=NORMAL
13+
14+
## Show that if we overwrite a given section (.in_segment) with new data, then rewrite it
15+
## back (from the second `--update-section`), then it should be the exact same as the
16+
## original file.
17+
# RUN: llvm-objcopy %t %t.copy.o
18+
# RUN: llvm-objcopy --update-section=.in_segment=%t.same --update-section=.in_segment=%t.zeros %t %t.original.o
19+
# RUN: cmp %t.copy.o %t.original.o
20+
21+
## Update segment section with a smaller chunk of data. This will also update the
22+
## section size to the length of the new data written. This does not affect the offset
23+
## of any subsequent sections in the same segment as this.
24+
# RUN: llvm-objcopy --update-section=.in_segment=%t.short %t %t.short.o
25+
# RUN: llvm-readobj --section-headers --section-data --program-headers %t.short.o | \
26+
# RUN: FileCheck %s --check-prefix=SHORT
27+
28+
## Ensure the data that was in a shortened section within a segment is still retained.
29+
## For cases where there are gaps in segments not covered by sections, the existing
30+
## contents are preserved.
31+
# RUN: od -t x1 -j 0x78 -N 16 %t.short.o | FileCheck %s --check-prefix=PRESERVED
32+
33+
## Add a new section via --add-section, then update it.
34+
# RUN: llvm-objcopy --add-section=.added=%t.zeros --update-section=.added=%t.updated \
35+
# RUN: %t %t.updated.o
36+
# RUN: llvm-readobj --section-headers --section-data --program-headers %t.updated.o | \
37+
# RUN: FileCheck %s --check-prefix=ADD-UPDATE
38+
39+
## Adding should always be first regardless of flag order.
40+
# RUN: llvm-objcopy --update-section=.added=%t.updated --add-section=.added=%t.updated \
41+
# RUN: %t %t.updated.o
42+
# RUN: llvm-readobj --section-headers --section-data --program-headers %t.updated.o | \
43+
# RUN: FileCheck %s --check-prefix=ADD-UPDATE
44+
45+
## We can't update sections which don't exist.
46+
# RUN: not llvm-objcopy --update-section=.nosection=%t.same %t %t-err1 2>&1 | \
47+
# RUN: FileCheck %s --check-prefix=ERR-NO-SECTION
48+
49+
## We can't update certain types of sections.
50+
# RUN: not llvm-objcopy --update-section=.nobits_type=%t.same %t %t-err2 2>&1 | \
51+
# RUN: FileCheck %s --check-prefix=ERR-NOBITS-TYPE
52+
# RUN: not llvm-objcopy --update-section=.null_type=%t.same %t %t-err2 2>&1 | \
53+
# RUN: FileCheck %s --check-prefix=ERR-NULL-TYPE
54+
55+
## Fail on trying to insert data larger than the existing section.
56+
# RUN: not llvm-objcopy --update-section=.in_segment=%t.larger %t %t-err3 2>&1 | \
57+
# RUN: FileCheck %s --check-prefix=ERR-LARGER
58+
59+
## But we can insert larger data if the section is NOT part of a segment.
60+
# RUN: llvm-objcopy --update-section=.not_in_segment=%t.larger %t %t.larger.o
61+
# RUN: llvm-readobj --section-headers --section-data --program-headers %t.larger.o | \
62+
# RUN: FileCheck %s --check-prefix=LONG
63+
64+
## We should still fail on inserting larger data, even if it is superceded by a
65+
## valid --update-section flag.
66+
# RUN: not llvm-objcopy --update-section=.in_segment=%t.larger --update-section=.in_segment=%t.same %t %t-err3 2>&1 | \
67+
# RUN: FileCheck %s --check-prefix=ERR-LARGER
68+
69+
## Test option parsing failures.
70+
# RUN: not llvm-objcopy --update-section %t %t.out 2>&1 | FileCheck %s --check-prefix=MISSING-EQ
71+
# RUN: not llvm-objcopy --update-section=.in_segment= %t %t.out 2>&1 | FileCheck %s --check-prefix=MISSING-FILE
72+
73+
!ELF
74+
FileHeader:
75+
Class: ELFCLASS64
76+
Data: ELFDATA2LSB
77+
Type: ET_EXEC
78+
Machine: EM_X86_64
79+
Sections:
80+
- Name: .in_segment
81+
Type: SHT_PROGBITS
82+
Content: "3030303030303030"
83+
- Name: .unmodified_in_segment
84+
Type: SHT_PROGBITS
85+
Content: "3130303030303030"
86+
- Name: .nobits_type
87+
Type: SHT_NOBITS
88+
- Name: .not_in_segment
89+
Type: SHT_PROGBITS
90+
- Name: .null_type
91+
Type: SHT_NULL
92+
ProgramHeaders:
93+
- Type: PT_LOAD
94+
VAddr: 0x1000
95+
FirstSec: .in_segment
96+
## The unmodified section is for ensuring that it remains untouched (ie. its
97+
## offset is the same) even when the section before it is shrunk.
98+
LastSec: .unmodified_in_segment
99+
100+
# NORMAL: Name: .in_segment
101+
# NORMAL: Offset:
102+
# NORMAL-SAME: {{ }}0x78{{$}}
103+
# NORMAL: Size:
104+
# NORMAL-SAME: {{ }}8{{$}}
105+
# NORMAL: SectionData (
106+
# NORMAL-NEXT: |11112222|
107+
# NORMAL-NEXT: )
108+
# NORMAL: Name: .unmodified_in_segment
109+
# NORMAL: Offset:
110+
# NORMAL-SAME: {{ }}0x80{{$}}
111+
# NORMAL: Size:
112+
# NORMAL-SAME: {{ }}8{{$}}
113+
# NORMAL: SectionData (
114+
# NORMAL-NEXT: |10000000|
115+
# NORMAL-NEXT: )
116+
# NORMAL: ProgramHeaders [
117+
# NORMAL-NEXT: ProgramHeader {
118+
# NORMAL-NEXT: Type: PT_LOAD (0x1)
119+
# NORMAL: FileSize:
120+
# NORMAL-SAME: {{ }}16{{$}}
121+
# NORMAL: MemSize:
122+
# NORMAL-SAME: {{ }}16{{$}}
123+
# NORMAL: }
124+
# NORMAL-NEXT: ]
125+
126+
# SHORT: Name: .in_segment
127+
# SHORT: Offset:
128+
# SHORT-SAME: {{ }}0x78{{$}}
129+
# SHORT: Size:
130+
# SHORT-SAME: {{ }}2{{$}}
131+
# SHORT: SectionData (
132+
# SHORT-NEXT: |11|
133+
# SHORT-NEXT: )
134+
# SHORT: Name: .unmodified_in_segment
135+
# SHORT: Offset:
136+
# SHORT-SAME: {{ }}0x80{{$}}
137+
# SHORT: Size:
138+
# SHORT-SAME: {{ }}8{{$}}
139+
# SHORT: SectionData (
140+
# SHORT-NEXT: |10000000|
141+
# SHORT-NEXT: )
142+
# SHORT: ProgramHeaders [
143+
# SHORT-NEXT: ProgramHeader {
144+
# SHORT-NEXT: Type: PT_LOAD (0x1)
145+
# SHORT: FileSize:
146+
# SHORT-SAME: {{ }}16{{$}}
147+
# SHORT: MemSize:
148+
# SHORT-SAME: {{ }}16{{$}}
149+
# SHORT: }
150+
# SHORT-NEXT: ]
151+
152+
## The first 8 bytes are the modified section. The last 8 bytes are the
153+
## unmodified section.
154+
# PRESERVED: 31 31 30 30 30 30 30 30 31 30 30 30 30 30 30 30
155+
156+
# LONG: Name: .not_in_segment
157+
# LONG: Size:
158+
# LONG-SAME: {{ }}9{{$}}
159+
# LONG: SectionData (
160+
# LONG-NEXT: |111122223|
161+
# LONT-NEXT: )
162+
163+
# ADD-UPDATE: Name: .added
164+
# ADD-UPDATE: Size:
165+
# ADD-UPDATE-SAME: {{ }}8{{$}}
166+
# ADD-UPDATE: SectionData (
167+
# ADD-UPDATE-NEXT: |11113333|
168+
# ADD-UPDATE: )
169+
170+
# ERR-NO-SECTION: error: {{.*}}section '.nosection' not found
171+
# ERR-NOBITS-TYPE: error: {{.*}}section '.nobits_type' can't be updated because it does not have contents
172+
# ERR-NULL-TYPE: error: {{.*}}section '.null_type' can't be updated because it does not have contents
173+
# ERR-LARGER: error: {{.*}}cannot fit data of size 9 into section '.in_segment' with size 8 that is part of a segment
174+
175+
# MISSING-EQ: error: bad format for --update-section: missing '='
176+
# MISSING-FILE: error: bad format for --update-section: missing file name

llvm/tools/llvm-objcopy/CommonConfig.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ struct CommonConfig {
210210
// Repeated options
211211
std::vector<StringRef> AddSection;
212212
std::vector<StringRef> DumpSection;
213+
std::vector<StringRef> UpdateSection;
213214

214215
// Section matchers
215216
NameMatcher KeepSection;

llvm/tools/llvm-objcopy/ConfigManager.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,17 @@ objcopy::parseObjcopyOptions(ArrayRef<const char *> RawArgsArr,
880880
"bad format for --add-section: missing file name");
881881
Config.AddSection.push_back(ArgValue);
882882
}
883+
for (auto Arg : InputArgs.filtered(OBJCOPY_update_section)) {
884+
StringRef ArgValue(Arg->getValue());
885+
if (!ArgValue.contains('='))
886+
return createStringError(errc::invalid_argument,
887+
"bad format for --update-section: missing '='");
888+
if (ArgValue.split("=").second.empty())
889+
return createStringError(
890+
errc::invalid_argument,
891+
"bad format for --update-section: missing file name");
892+
Config.UpdateSection.push_back(ArgValue);
893+
}
883894
for (auto *Arg : InputArgs.filtered(OBJCOPY_dump_section)) {
884895
StringRef Value(Arg->getValue());
885896
if (Value.split('=').second.empty())

llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,22 @@ static void addSymbol(Object &Obj, const NewSymbolInfo &SymInfo,
548548
Sec ? (uint16_t)SYMBOL_SIMPLE_INDEX : (uint16_t)SHN_ABS, 0);
549549
}
550550

551+
static Error
552+
handleUserSection(StringRef Flag,
553+
function_ref<Error(StringRef, ArrayRef<uint8_t>)> F) {
554+
std::pair<StringRef, StringRef> SecPair = Flag.split("=");
555+
StringRef SecName = SecPair.first;
556+
StringRef File = SecPair.second;
557+
ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = MemoryBuffer::getFile(File);
558+
if (!BufOrErr)
559+
return createFileError(File, errorCodeToError(BufOrErr.getError()));
560+
std::unique_ptr<MemoryBuffer> Buf = std::move(*BufOrErr);
561+
ArrayRef<uint8_t> Data(
562+
reinterpret_cast<const uint8_t *>(Buf->getBufferStart()),
563+
Buf->getBufferSize());
564+
return F(SecName, Data);
565+
}
566+
551567
// This function handles the high level operations of GNU objcopy including
552568
// handling command line options. It's important to outline certain properties
553569
// we expect to hold of the command line operations. Any operation that "keeps"
@@ -665,21 +681,23 @@ static Error handleArgs(const CommonConfig &Config, const ELFConfig &ELFConfig,
665681
Sec.Type = SHT_NOBITS;
666682

667683
for (const auto &Flag : Config.AddSection) {
668-
std::pair<StringRef, StringRef> SecPair = Flag.split("=");
669-
StringRef SecName = SecPair.first;
670-
StringRef File = SecPair.second;
671-
ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
672-
MemoryBuffer::getFile(File);
673-
if (!BufOrErr)
674-
return createFileError(File, errorCodeToError(BufOrErr.getError()));
675-
std::unique_ptr<MemoryBuffer> Buf = std::move(*BufOrErr);
676-
ArrayRef<uint8_t> Data(
677-
reinterpret_cast<const uint8_t *>(Buf->getBufferStart()),
678-
Buf->getBufferSize());
679-
OwnedDataSection &NewSection =
680-
Obj.addSection<OwnedDataSection>(SecName, Data);
681-
if (SecName.startswith(".note") && SecName != ".note.GNU-stack")
682-
NewSection.Type = SHT_NOTE;
684+
auto AddSection = [&](StringRef Name, ArrayRef<uint8_t> Data) {
685+
OwnedDataSection &NewSection =
686+
Obj.addSection<OwnedDataSection>(Name, Data);
687+
if (Name.startswith(".note") && Name != ".note.GNU-stack")
688+
NewSection.Type = SHT_NOTE;
689+
return Error::success();
690+
};
691+
if (Error E = handleUserSection(Flag, AddSection))
692+
return E;
693+
}
694+
695+
for (StringRef Flag : Config.UpdateSection) {
696+
auto UpdateSection = [&](StringRef Name, ArrayRef<uint8_t> Data) {
697+
return Obj.updateSection(Name, Data);
698+
};
699+
if (Error E = handleUserSection(Flag, UpdateSection))
700+
return E;
683701
}
684702

685703
if (!Config.AddGnuDebugLink.empty())

llvm/tools/llvm-objcopy/ELF/Object.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2107,6 +2107,17 @@ template <class ELFT> void ELFWriter<ELFT>::writeSegmentData() {
21072107
Size);
21082108
}
21092109

2110+
for (auto it : Obj.getUpdatedSections()) {
2111+
SectionBase *Sec = it.first;
2112+
ArrayRef<uint8_t> Data = it.second;
2113+
2114+
auto *Parent = Sec->ParentSegment;
2115+
assert(Parent && "This section should've been part of a segment.");
2116+
uint64_t Offset =
2117+
Sec->OriginalOffset - Parent->OriginalOffset + Parent->Offset;
2118+
llvm::copy(Data, Buf->getBufferStart() + Offset);
2119+
}
2120+
21102121
// Iterate over removed sections and overwrite their old data with zeroes.
21112122
for (auto &Sec : Obj.removedSections()) {
21122123
Segment *Parent = Sec.ParentSegment;
@@ -2124,6 +2135,37 @@ ELFWriter<ELFT>::ELFWriter(Object &Obj, raw_ostream &Buf, bool WSH,
21242135
: Writer(Obj, Buf), WriteSectionHeaders(WSH && Obj.HadShdrs),
21252136
OnlyKeepDebug(OnlyKeepDebug) {}
21262137

2138+
Error Object::updateSection(StringRef Name, ArrayRef<uint8_t> Data) {
2139+
auto It = llvm::find_if(Sections,
2140+
[&](const SecPtr &Sec) { return Sec->Name == Name; });
2141+
if (It == Sections.end())
2142+
return createStringError(errc::invalid_argument, "section '%s' not found",
2143+
Name.str().c_str());
2144+
2145+
auto *OldSec = It->get();
2146+
if (!OldSec->hasContents())
2147+
return createStringError(
2148+
errc::invalid_argument,
2149+
"section '%s' can't be updated because it does not have contents",
2150+
Name.str().c_str());
2151+
2152+
if (Data.size() > OldSec->Size && OldSec->ParentSegment)
2153+
return createStringError(errc::invalid_argument,
2154+
"cannot fit data of size %zu into section '%s' "
2155+
"with size %zu that is part of a segment",
2156+
Data.size(), Name.str().c_str(), OldSec->Size);
2157+
2158+
if (!OldSec->ParentSegment) {
2159+
*It = std::make_unique<OwnedDataSection>(*OldSec, Data);
2160+
} else {
2161+
// The segment writer will be in charge of updating these contents.
2162+
OldSec->Size = Data.size();
2163+
UpdatedSections[OldSec] = Data;
2164+
}
2165+
2166+
return Error::success();
2167+
}
2168+
21272169
Error Object::removeSections(
21282170
bool AllowBrokenLinks, std::function<bool(const SectionBase &)> ToRemove) {
21292171

llvm/tools/llvm-objcopy/ELF/Object.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ class SectionBase {
429429
virtual void markSymbols();
430430
virtual void
431431
replaceSectionReferences(const DenseMap<SectionBase *, SectionBase *> &);
432+
virtual bool hasContents() const { return false; }
432433
// Notify the section that it is subject to removal.
433434
virtual void onRemove();
434435
};
@@ -493,6 +494,9 @@ class Section : public SectionBase {
493494
function_ref<bool(const SectionBase *)> ToRemove) override;
494495
Error initialize(SectionTableRef SecTable) override;
495496
void finalize() override;
497+
bool hasContents() const override {
498+
return Type != ELF::SHT_NOBITS && Type != ELF::SHT_NULL;
499+
}
496500
};
497501

498502
class OwnedDataSection : public SectionBase {
@@ -518,9 +522,15 @@ class OwnedDataSection : public SectionBase {
518522
OriginalOffset = SecOff;
519523
}
520524

525+
OwnedDataSection(SectionBase &S, ArrayRef<uint8_t> Data)
526+
: SectionBase(S), Data(std::begin(Data), std::end(Data)) {
527+
Size = Data.size();
528+
}
529+
521530
void appendHexData(StringRef HexData);
522531
Error accept(SectionVisitor &Sec) const override;
523532
Error accept(MutableSectionVisitor &Visitor) override;
533+
bool hasContents() const override { return true; }
524534
};
525535

526536
class CompressedSection : public SectionBase {
@@ -1018,6 +1028,7 @@ class Object {
10181028
std::vector<SecPtr> Sections;
10191029
std::vector<SegPtr> Segments;
10201030
std::vector<SecPtr> RemovedSections;
1031+
DenseMap<SectionBase *, std::vector<uint8_t>> UpdatedSections;
10211032

10221033
static bool sectionIsAlloc(const SectionBase &Sec) {
10231034
return Sec.Flags & ELF::SHF_ALLOC;
@@ -1060,6 +1071,9 @@ class Object {
10601071
return make_filter_range(make_pointee_range(Sections), sectionIsAlloc);
10611072
}
10621073

1074+
const auto &getUpdatedSections() const { return UpdatedSections; }
1075+
Error updateSection(StringRef Name, ArrayRef<uint8_t> Data);
1076+
10631077
SectionBase *findSection(StringRef Name) {
10641078
auto SecIt =
10651079
find_if(Sections, [&](const SecPtr &Sec) { return Sec->Name == Name; });

llvm/tools/llvm-objcopy/ObjcopyOpts.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,7 @@ defm add_symbol
215215
"compatibility: debug, constructor, warning, indirect, synthetic, "
216216
"unique-object, before.">,
217217
MetaVarName<"name=[section:]value[,flags]">;
218+
219+
defm update_section
220+
: Eq<"update-section", "Add section <name> with contents from a file <file>.">,
221+
MetaVarName<"name=file">;

0 commit comments

Comments
 (0)