Skip to content

Commit 4070dff

Browse files
[llvm-objcopy] Add --gap-fill and --pad-to options (#65815)
`--gap-fill <value>` fills the gaps between sections with a specified 8-bit value, instead of zero. `--pad-to <address>` pads the output binary up to the specified load address, using the 8-bit value from `--gap-fill` or zero. These options are only supported for ELF input and binary output.
1 parent 5b47052 commit 4070dff

File tree

11 files changed

+364
-8
lines changed

11 files changed

+364
-8
lines changed

llvm/docs/CommandGuide/llvm-objcopy.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,11 @@ them.
324324

325325
Extract the named partition from the output.
326326

327+
.. option:: --gap-fill <value>
328+
329+
For binary outputs, fill the gaps between sections with ``<value>`` instead
330+
of zero. The value must be an unsigned 8-bit integer.
331+
327332
.. option:: --globalize-symbol <symbol>
328333

329334
Mark any defined symbols named ``<symbol>`` as global symbols in the output.
@@ -411,6 +416,11 @@ them.
411416
be the same as the value specified for :option:`--input-target` or the input
412417
file's format if that option is also unspecified.
413418

419+
.. option:: --pad-to <address>
420+
421+
For binary outputs, pad the output to the load address ``<address>`` using a value
422+
of zero or the value specified by :option:`--gap-fill`.
423+
414424
.. option:: --prefix-alloc-sections <prefix>
415425

416426
Add ``<prefix>`` to the front of the names of all allocatable sections in the

llvm/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@ Changes to the LLVM tools
270270

271271
* llvm-symbolizer and llvm-addr2line now support addresses specified as symbol names.
272272

273+
* llvm-objcopy now supports ``--gap-fill`` and ``--pad-to`` options, for
274+
ELF input and binary output files only.
275+
273276
Changes to LLDB
274277
---------------------------------
275278

llvm/include/llvm/ObjCopy/CommonConfig.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ struct CommonConfig {
214214
// Cached gnu_debuglink's target CRC
215215
uint32_t GnuDebugLinkCRC32;
216216
std::optional<StringRef> ExtractPartition;
217+
uint8_t GapFill = 0;
218+
uint64_t PadTo = 0;
217219
StringRef SplitDWO;
218220
StringRef SymbolsPrefix;
219221
StringRef AllocSectionsPrefix;

llvm/lib/ObjCopy/ConfigManager.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ Expected<const COFFConfig &> ConfigManager::getCOFFConfig() const {
2323
Common.ExtractDWO || Common.PreserveDates || Common.StripDWO ||
2424
Common.StripNonAlloc || Common.StripSections || Common.Weaken ||
2525
Common.DecompressDebugSections ||
26-
Common.DiscardMode == DiscardType::Locals || !Common.SymbolsToAdd.empty())
26+
Common.DiscardMode == DiscardType::Locals ||
27+
!Common.SymbolsToAdd.empty() || Common.GapFill != 0 || Common.PadTo != 0)
2728
return createStringError(llvm::errc::invalid_argument,
2829
"option is not supported for COFF");
2930

@@ -42,7 +43,8 @@ Expected<const MachOConfig &> ConfigManager::getMachOConfig() const {
4243
Common.PreserveDates || Common.StripAllGNU || Common.StripDWO ||
4344
Common.StripNonAlloc || Common.StripSections ||
4445
Common.DecompressDebugSections || Common.StripUnneeded ||
45-
Common.DiscardMode == DiscardType::Locals || !Common.SymbolsToAdd.empty())
46+
Common.DiscardMode == DiscardType::Locals ||
47+
!Common.SymbolsToAdd.empty() || Common.GapFill != 0 || Common.PadTo != 0)
4648
return createStringError(llvm::errc::invalid_argument,
4749
"option is not supported for MachO");
4850

@@ -60,7 +62,8 @@ Expected<const WasmConfig &> ConfigManager::getWasmConfig() const {
6062
!Common.SymbolsToWeaken.empty() || !Common.SymbolsToKeepGlobal.empty() ||
6163
!Common.SectionsToRename.empty() || !Common.SetSectionAlignment.empty() ||
6264
!Common.SetSectionFlags.empty() || !Common.SetSectionType.empty() ||
63-
!Common.SymbolsToRename.empty())
65+
!Common.SymbolsToRename.empty() || Common.GapFill != 0 ||
66+
Common.PadTo != 0)
6467
return createStringError(llvm::errc::invalid_argument,
6568
"only flags for section dumping, removal, and "
6669
"addition are supported");
@@ -86,7 +89,8 @@ Expected<const XCOFFConfig &> ConfigManager::getXCOFFConfig() const {
8689
Common.ExtractMainPartition || Common.OnlyKeepDebug ||
8790
Common.PreserveDates || Common.StripAllGNU || Common.StripDWO ||
8891
Common.StripDebug || Common.StripNonAlloc || Common.StripSections ||
89-
Common.Weaken || Common.StripUnneeded || Common.DecompressDebugSections) {
92+
Common.Weaken || Common.StripUnneeded || Common.DecompressDebugSections ||
93+
Common.GapFill != 0 || Common.PadTo != 0) {
9094
return createStringError(
9195
llvm::errc::invalid_argument,
9296
"no flags are supported yet, only basic copying is allowed");

llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ static std::unique_ptr<Writer> createWriter(const CommonConfig &Config,
180180
ElfType OutputElfType) {
181181
switch (Config.OutputFormat) {
182182
case FileFormat::Binary:
183-
return std::make_unique<BinaryWriter>(Obj, Out);
183+
return std::make_unique<BinaryWriter>(Obj, Out, Config);
184184
case FileFormat::IHex:
185185
return std::make_unique<IHexWriter>(Obj, Out);
186186
default:

llvm/lib/ObjCopy/ELF/ELFObject.cpp

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2636,9 +2636,36 @@ template <class ELFT> Error ELFWriter<ELFT>::finalize() {
26362636
}
26372637

26382638
Error BinaryWriter::write() {
2639-
for (const SectionBase &Sec : Obj.allocSections())
2639+
SmallVector<const SectionBase *, 30> SectionsToWrite;
2640+
for (const SectionBase &Sec : Obj.allocSections()) {
2641+
if (Sec.Type != SHT_NOBITS)
2642+
SectionsToWrite.push_back(&Sec);
2643+
}
2644+
2645+
if (SectionsToWrite.empty())
2646+
return Error::success();
2647+
2648+
llvm::stable_sort(SectionsToWrite,
2649+
[](const SectionBase *LHS, const SectionBase *RHS) {
2650+
return LHS->Offset < RHS->Offset;
2651+
});
2652+
2653+
assert(SectionsToWrite.front()->Offset == 0);
2654+
2655+
for (size_t i = 0; i != SectionsToWrite.size(); ++i) {
2656+
const SectionBase &Sec = *SectionsToWrite[i];
26402657
if (Error Err = Sec.accept(*SecWriter))
26412658
return Err;
2659+
if (GapFill == 0)
2660+
continue;
2661+
uint64_t PadOffset = (i < SectionsToWrite.size() - 1)
2662+
? SectionsToWrite[i + 1]->Offset
2663+
: Buf->getBufferSize();
2664+
assert(PadOffset <= Buf->getBufferSize());
2665+
assert(Sec.Offset + Sec.Size <= PadOffset);
2666+
std::fill(Buf->getBufferStart() + Sec.Offset + Sec.Size,
2667+
Buf->getBufferStart() + PadOffset, GapFill);
2668+
}
26422669

26432670
// TODO: Implement direct writing to the output stream (without intermediate
26442671
// memory buffer Buf).
@@ -2664,7 +2691,7 @@ Error BinaryWriter::finalize() {
26642691
// file size. This might not be the same as the offset returned by
26652692
// layoutSections, because we want to truncate the last segment to the end of
26662693
// its last non-empty section, to match GNU objcopy's behaviour.
2667-
TotalSize = 0;
2694+
TotalSize = PadTo > MinAddr ? PadTo - MinAddr : 0;
26682695
for (SectionBase &Sec : Obj.allocSections())
26692696
if (Sec.Type != SHT_NOBITS && Sec.Size > 0) {
26702697
Sec.Offset = Sec.Addr - MinAddr;

llvm/lib/ObjCopy/ELF/ELFObject.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ template <class ELFT> class ELFWriter : public Writer {
357357

358358
class BinaryWriter : public Writer {
359359
private:
360+
const uint8_t GapFill;
361+
const uint64_t PadTo;
360362
std::unique_ptr<BinarySectionWriter> SecWriter;
361363

362364
uint64_t TotalSize = 0;
@@ -365,7 +367,8 @@ class BinaryWriter : public Writer {
365367
~BinaryWriter() {}
366368
Error finalize() override;
367369
Error write() override;
368-
BinaryWriter(Object &Obj, raw_ostream &Out) : Writer(Obj, Out) {}
370+
BinaryWriter(Object &Obj, raw_ostream &Out, const CommonConfig &Config)
371+
: Writer(Obj, Out), GapFill(Config.GapFill), PadTo(Config.PadTo) {}
369372
};
370373

371374
class IHexWriter : public Writer {
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# RUN: yaml2obj --docnum=1 %s -o %t
2+
3+
# RUN: not llvm-objcopy --gap-fill 1 %t 2>&1 | FileCheck %s --check-prefix=NOT-BINARY
4+
# NOT-BINARY: error: '--gap-fill' is only supported for binary output
5+
6+
# RUN: not llvm-objcopy -O binary --gap-fill= %t %t.bin 2>&1 | FileCheck %s --check-prefix=BAD-FORMAT
7+
# BAD-FORMAT: error: --gap-fill: bad number:
8+
9+
# RUN: not llvm-objcopy -O binary --gap-fill=x %t %t.bin 2>&1 | FileCheck %s --check-prefix=BAD-INPUT
10+
# BAD-INPUT: error: --gap-fill: bad number: x
11+
12+
# RUN: not llvm-objcopy -O binary --gap-fill=0x %t %t.bin 2>&1 | FileCheck %s --check-prefix=BAD-INPUT2
13+
# BAD-INPUT2: error: --gap-fill: bad number: 0x
14+
15+
# RUN: not llvm-objcopy -O binary --gap-fill=0x1G %t %t.bin 2>&1 | FileCheck %s --check-prefix=BAD-INPUT3
16+
# BAD-INPUT3: error: --gap-fill: bad number: 0x1G
17+
18+
# RUN: not llvm-objcopy -O binary --gap-fill=ff %t %t.bin 2>&1 | FileCheck %s --check-prefix=BAD-INPUT4
19+
# BAD-INPUT4: error: --gap-fill: bad number: ff
20+
21+
# RUN: not llvm-objcopy -O binary --gap-fill=0x1122 %t %t-val16 2>&1 | FileCheck %s --check-prefix=TRUNCATED-ERR
22+
# TRUNCATED-ERR: error: gap-fill value 0x1122 is out of range (0 to 0xff)
23+
24+
## Test no gap fill with all allocatable output sections.
25+
# RUN: llvm-objcopy -O binary %t %t-default
26+
# RUN: od -v -Ax -t x1 %t-default | FileCheck %s --check-prefix=DEFAULT --match-full-lines
27+
# DEFAULT: 000000 ee ff 11 22 33 44 aa bb cc dd fe dc ba 00 a1 b2
28+
# DEFAULT-NEXT: 000010 c3 d4 00 00 00 00 00 00 00 00 00 00 00 00 00 00
29+
# DEFAULT-NEXT: 000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
30+
# DEFAULT-NEXT: 000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
31+
# DEFAULT-NEXT: 000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
32+
# DEFAULT-NEXT: 000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
33+
# DEFAULT-NEXT: 000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
34+
# DEFAULT-NEXT: 000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
35+
# DEFAULT-NEXT: 000080 00 00 89 ab cd ef
36+
# DEFAULT-NEXT: 000086
37+
38+
## Test gap fill with all allocatable output sections.
39+
# RUN: llvm-objcopy -O binary --gap-fill=0xe9 %t %t-filled
40+
# RUN: od -v -Ax -t x1 %t-filled | FileCheck %s --check-prefix=FULL --match-full-lines
41+
# FULL: 000000 ee ff 11 22 33 44 aa bb cc dd fe dc ba e9 a1 b2
42+
# FULL-NEXT: 000010 c3 d4 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
43+
# FULL-NEXT: 000020 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
44+
# FULL-NEXT: 000030 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
45+
# FULL-NEXT: 000040 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
46+
# FULL-NEXT: 000050 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
47+
# FULL-NEXT: 000060 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
48+
# FULL-NEXT: 000070 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
49+
# FULL-NEXT: 000080 e9 e9 89 ab cd ef
50+
# FULL-NEXT: 000086
51+
52+
## Test gap fill with a decimal value.
53+
# RUN: llvm-objcopy -O binary --gap-fill=99 %t %t-filled-decimal
54+
# RUN: od -v -Ax -t x1 %t-filled-decimal | FileCheck %s --check-prefix=DEC --match-full-lines
55+
# DEC: 000000 ee ff 11 22 33 44 aa bb cc dd fe dc ba 63 a1 b2
56+
# DEC-NEXT: 000010 c3 d4 63 63 63 63 63 63 63 63 63 63 63 63 63 63
57+
# DEC-NEXT: 000020 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
58+
# DEC-NEXT: 000030 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
59+
# DEC-NEXT: 000040 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
60+
# DEC-NEXT: 000050 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
61+
# DEC-NEXT: 000060 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
62+
# DEC-NEXT: 000070 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63+
# DEC-NEXT: 000080 63 63 89 ab cd ef
64+
# DEC-NEXT: 000086
65+
66+
## Test gap fill with the last section removed, should be truncated.
67+
# RUN: llvm-objcopy -O binary --gap-fill=0xe9 --remove-section=.foo %t %t-filled
68+
# RUN: od -v -Ax -t x1 %t-filled | FileCheck %s --check-prefix=REMOVE-LAST-SECTION --match-full-lines
69+
# REMOVE-LAST-SECTION: 000000 ee ff 11 22 33 44 aa bb cc dd fe dc ba e9 a1 b2
70+
# REMOVE-LAST-SECTION-NEXT: 000010 c3 d4
71+
# REMOVE-LAST-SECTION-NEXT: 000012
72+
73+
## Test gap fill with the middle section removed, should be filled.
74+
# RUN: llvm-objcopy -O binary --gap-fill=0xe9 --remove-section=.gap2 %t %t-filled
75+
# RUN: od -v -Ax -t x1 %t-filled | FileCheck %s --check-prefix=REMOVE-MIDDLE-SECTION --match-full-lines
76+
# REMOVE-MIDDLE-SECTION: 000000 ee ff 11 22 33 44 aa bb cc dd fe dc ba e9 e9 e9
77+
# REMOVE-MIDDLE-SECTION-NEXT: 000010 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
78+
# REMOVE-MIDDLE-SECTION-NEXT: 000020 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
79+
# REMOVE-MIDDLE-SECTION-NEXT: 000030 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
80+
# REMOVE-MIDDLE-SECTION-NEXT: 000040 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
81+
# REMOVE-MIDDLE-SECTION-NEXT: 000050 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
82+
# REMOVE-MIDDLE-SECTION-NEXT: 000060 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
83+
# REMOVE-MIDDLE-SECTION-NEXT: 000070 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9 e9
84+
# REMOVE-MIDDLE-SECTION-NEXT: 000080 e9 e9 89 ab cd ef
85+
# REMOVE-MIDDLE-SECTION-NEXT: 000086
86+
87+
--- !ELF
88+
FileHeader:
89+
Class: ELFCLASS64
90+
Data: ELFDATA2LSB
91+
Type: ET_EXEC
92+
Machine: EM_X86_64
93+
Sections:
94+
- Name: .space1
95+
Type: Fill
96+
Pattern: 'ABCD'
97+
Size: 0x2
98+
- Name: .nogap
99+
Type: SHT_PROGBITS
100+
Flags: [ SHF_ALLOC ]
101+
Address: 0x0102
102+
Size: 0x6
103+
Content: 'EEFF11223344'
104+
- Name: .gap1
105+
Type: SHT_PROGBITS
106+
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
107+
Address: 0x0108
108+
Content: 'AABBCCDDFEDCBA'
109+
- Name: .space2
110+
Type: Fill
111+
Pattern: 'DC'
112+
Size: 1
113+
- Name: .gap2
114+
Type: SHT_PROGBITS
115+
Flags: [ SHF_ALLOC ]
116+
Address: 0x0110
117+
Content: 'A1B2C3D4'
118+
- Name: .space3
119+
Type: Fill
120+
Pattern: 'FE'
121+
Size: 0x1
122+
- Name: .nobit_tbss
123+
Type: SHT_NOBITS
124+
Flags: [ SHF_WRITE, SHF_ALLOC, SHF_TLS ]
125+
Address: 0x0180
126+
Size: 0x0018
127+
- Name: .space4
128+
Type: Fill
129+
Pattern: '01234567'
130+
Size: 0x4
131+
- Name: .foo
132+
Type: SHT_PROGBITS
133+
Flags: [ SHF_WRITE, SHF_ALLOC ]
134+
Address: 0x0184
135+
Content: '89ABCDEF'
136+
- Name: .nobit_bss
137+
Type: SHT_NOBITS
138+
Flags: [ SHF_WRITE, SHF_ALLOC ]
139+
Address: 0x018A
140+
Size: 0x0008
141+
- Name: .comment
142+
Type: SHT_PROGBITS
143+
Flags: [ SHF_MERGE, SHF_STRINGS ]
144+
EntSize: 0x0001
145+
Content: 4743433A
146+
147+
## In this test, output sections are defined out of order with respect to their
148+
## load addresses. Verify that gaps are still correctly filled.
149+
150+
# RUN: yaml2obj --docnum=2 %s -o %t.2
151+
# RUN: llvm-objcopy -O binary --gap-fill=0xe9 %t.2 %t.2.filled
152+
# RUN: od -v -Ax -t x1 %t.2.filled | FileCheck --match-full-lines %s
153+
# CHECK: 000000 aa bb cc dd e9 e9 e9 e9 11 22 33 44
154+
155+
--- !ELF
156+
FileHeader:
157+
Class: ELFCLASS64
158+
Data: ELFDATA2LSB
159+
Type: ET_EXEC
160+
Machine: EM_X86_64
161+
Sections:
162+
- Name: .bss
163+
Type: SHT_NOBITS
164+
Flags: [ SHF_ALLOC, SHF_WRITE ]
165+
Address: 0x0104
166+
Size: 4
167+
- Name: .section1
168+
Type: SHT_PROGBITS
169+
Flags: [ SHF_ALLOC, SHF_WRITE ]
170+
Address: 0x0108
171+
Content: '11223344'
172+
- Name: .section3
173+
Type: SHT_PROGBITS
174+
Flags: [ SHF_ALLOC, SHF_WRITE ]
175+
Address: 0x0100
176+
Content: 'AABBCCDD'

0 commit comments

Comments
 (0)