Skip to content

Commit 5ad0103

Browse files
committed
[llvm-objcopy][ELF] Implement --only-keep-debug
--only-keep-debug produces a debug file as the output that only preserves contents of sections useful for debugging purposes (the binutils implementation preserves SHT_NOTE and non-SHF_ALLOC sections), by changing their section types to SHT_NOBITS and rewritting file offsets. See https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html The intended use case is: ``` llvm-objcopy --only-keep-debug a a.dbg llvm-objcopy --strip-debug a b llvm-objcopy --add-gnu-debuglink=a.dbg b ``` The current layout algorithm is incapable of deleting contents and shrinking segments, so it is not suitable for implementing the functionality. This patch adds a new algorithm which assigns sh_offset to sections first, then modifies p_offset/p_filesz of program headers. It bears a resemblance to lld/ELF/Writer.cpp. Reviewed By: jhenderson, jakehehrlich Differential Revision: https://reviews.llvm.org/D67137
1 parent ade55d0 commit 5ad0103

File tree

7 files changed

+352
-52
lines changed

7 files changed

+352
-52
lines changed

llvm/docs/CommandGuide/llvm-objcopy.rst

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ multiple file formats.
6767

6868
Print a summary of command line options.
6969

70+
.. option:: --only-keep-debug
71+
72+
Produce a debug file as the output that only preserves contents of sections
73+
useful for debugging purposes.
74+
75+
For ELF objects, this removes the contents of `SHF_ALLOC` sections that are not
76+
`SHT_NOTE` by making them `SHT_NOBITS` and shrinking the program headers where
77+
possible.
78+
7079
.. option:: --only-section <section>, -j
7180

7281
Remove all sections from the output, except for sections named ``<section>``.
@@ -177,11 +186,6 @@ The following options are implemented only for COFF objects. If used with other
177186
objects, :program:`llvm-objcopy` will either emit an error or silently ignore
178187
them.
179188

180-
.. option:: --only-keep-debug
181-
182-
Remove the contents of non-debug sections from the output, but keep the section
183-
headers.
184-
185189
ELF-SPECIFIC OPTIONS
186190
--------------------
187191

llvm/test/tools/llvm-objcopy/ELF/basic-only-keep-debug.test

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# RUN: yaml2obj --docnum=1 %s -o %t1
2+
# RUN: llvm-objcopy --only-keep-debug %t1 %t1.dbg
3+
# RUN: llvm-readelf -S -l -x .note1 -x .note2 -x .debug_abbrev -x .debug_frame -x .debug_info %t1.dbg | FileCheck %s
4+
5+
## Check that SHT_NOTE and .debug* are kept, but others are changed to SHT_NOBITS.
6+
## SHT_NOBITS sections do not occupy space in the output.
7+
8+
# CHECK: [Nr] Name Type Address Off Size ES Flg Lk Inf Al
9+
# CHECK: [ 1] .note1 NOTE 0000000000000400 000400 000001 00 A 0 0 1024
10+
# CHECK-NEXT: [ 2] .note2 NOTE 0000000000000401 000401 000001 00 A 0 0 0
11+
# CHECK-NEXT: [ 3] .text NOBITS 0000000000000402 000402 000001 00 AX 0 0 0
12+
# CHECK-NEXT: [ 4] .tdata NOBITS 0000000000001480 000480 000007 00 WAT 0 0 128
13+
# CHECK-NEXT: [ 5] .tbss NOBITS 0000000000001487 000480 000005 00 WAT 0 0 0
14+
# CHECK-NEXT: [ 6] .bss NOBITS 00000000000014a0 000480 00003f 00 WA 0 0 32
15+
## objcopy sets sh_offset to 0x402. We don't do this to keep sh_offset non-decreasing.
16+
# CHECK-NEXT: [ 7] .debug_abbrev PROGBITS 0000000000000000 000480 000001 00 0 0 0
17+
# CHECK-NEXT: [ 8] .debug_frame PROGBITS 0000000000000000 000488 000001 00 0 0 8
18+
# CHECK-NEXT: [ 9] .debug_info PROGBITS 0000000000000000 000489 000001 00 0 0 0
19+
# CHECK-NEXT: [10] .strtab STRTAB 0000000000000000 00048a 000001 00 0 0 1
20+
# CHECK-NEXT: [11] .shstrtab STRTAB 0000000000000000 00048b 000060 00 0 0 1
21+
22+
# CHECK: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
23+
# CHECK-NEXT: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x000402 0x000403 R E 0x1000
24+
# CHECK-NEXT: LOAD 0x000480 0x0000000000001480 0x0000000000000000 0x000000 0x00005f RW 0x1000
25+
# CHECK-NEXT: TLS 0x000480 0x0000000000001480 0x0000000000000000 0x000000 0x00000c RW 0x80
26+
# CHECK-NEXT: NOTE 0x000400 0x0000000000000400 0x0000000000000000 0x000002 0x000002 0x400
27+
28+
## Contents of SHT_NOTE and .debug* are kept.
29+
30+
# CHECK: Hex dump of section '.note1':
31+
# CHECK-NEXT: 0x00000400 01
32+
# CHECK: Hex dump of section '.note2':
33+
# CHECK-NEXT: 0x00000401 02
34+
# CHECK: Hex dump of section '.debug_abbrev':
35+
# CHECK-NEXT: 0x00000000 03
36+
# CHECK: Hex dump of section '.debug_frame':
37+
# CHECK-NEXT: 0x00000000 04
38+
# CHECK: Hex dump of section '.debug_info':
39+
# CHECK-NEXT: 0x00000000 05
40+
41+
--- !ELF
42+
FileHeader:
43+
Class: ELFCLASS64
44+
Data: ELFDATA2LSB
45+
Type: ET_DYN
46+
Machine: EM_X86_64
47+
Sections:
48+
- Name: .note1
49+
Type: SHT_NOTE
50+
Flags: [ SHF_ALLOC ]
51+
Address: 0x400
52+
AddressAlign: 0x400
53+
Content: 01
54+
- Name: .note2
55+
Type: SHT_NOTE
56+
Flags: [ SHF_ALLOC ]
57+
Address: 0x401
58+
Content: 02
59+
- Name: .text
60+
Type: SHT_PROGBITS
61+
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
62+
Address: 0x402
63+
Content: c3
64+
- Name: .tdata
65+
Type: SHT_PROGBITS
66+
Flags: [ SHF_ALLOC, SHF_WRITE, SHF_TLS ]
67+
Address: 0x1480 # Ensure Address=0x1000+Offset
68+
AddressAlign: 0x80
69+
# An arbitrary non-zero Size tests that .tdata does not occupy space
70+
# and we can rewrite p_filesz of PT_TLS.
71+
Size: 7
72+
- Name: .tbss
73+
Type: SHT_NOBITS
74+
Flags: [ SHF_ALLOC, SHF_WRITE, SHF_TLS ]
75+
Address: 0x1487 # Ensure Address=0x1000+Offset
76+
Size: 5
77+
- Name: .bss
78+
Type: SHT_NOBITS
79+
Flags: [ SHF_ALLOC, SHF_WRITE ]
80+
Address: 0x14a0 # Ensure Address=0x1000+Offset
81+
AddressAlign: 0x20
82+
# An arbitrary non-zero Size tests that .bss does not occupy space.
83+
Size: 63
84+
- Name: .debug_abbrev
85+
Type: SHT_PROGBITS
86+
Content: 03
87+
- Name: .debug_frame
88+
Type: SHT_PROGBITS
89+
# AddressAlign tests the file offset assignment leaves a gap.
90+
AddressAlign: 0x8
91+
Content: 04
92+
- Name: .debug_info
93+
Type: SHT_PROGBITS
94+
Content: 05
95+
ProgramHeaders:
96+
- Type: PT_LOAD
97+
Flags: [ PF_R, PF_X ]
98+
Offset: 0
99+
Align: 0x1000
100+
Sections:
101+
- Section: .note1
102+
- Section: .note2
103+
- Section: .text
104+
- Type: PT_LOAD
105+
Flags: [ PF_R, PF_W ]
106+
VAddr: 0x1480 # Ensure Offset=VAddr (mod Align) if Offset changes
107+
Align: 0x1000
108+
Sections:
109+
- Section: .tdata
110+
- Section: .bss
111+
- Type: PT_TLS
112+
Flags: [ PF_R, PF_W ]
113+
VAddr: 0x1480 # Ensure Offset=VAddr (mod Align) if Offset changes
114+
Sections:
115+
- Section: .tdata
116+
- Section: .tbss
117+
- Type: PT_NOTE
118+
VAddr: 0x400
119+
Sections:
120+
- Section: .note1
121+
- Section: .note2
122+
...
123+
124+
# RUN: yaml2obj --docnum=2 %s -o %t2
125+
# RUN: llvm-objcopy --only-keep-debug %t2 %t2.dbg
126+
# RUN: llvm-readelf -S -l %t2.dbg | FileCheck --check-prefix=CHECK2 %s
127+
128+
## Only the tail of a segment can be trimmed. .text still occupies space because
129+
## it is followed by .note which is not SHT_NOBITS.
130+
# CHECK2: [Nr] Name Type Address Off Size ES Flg Lk Inf Al
131+
# CHECK2: [ 1] .text NOBITS 0000000000000200 000200 000001 00 AX 0 0 512
132+
# CHECK2-NEXT: [ 2] .note NOTE 0000000000000201 000201 000001 00 A 0 0 0
133+
# CHECK2-NEXT: [ 3] .debug_info PROGBITS 0000000000000000 000220 000001 00 0 0 32
134+
# CHECK2-NEXT: [ 4] .strtab STRTAB 0000000000000000 000221 000001 00 0 0 1
135+
# CHECK2-NEXT: [ 5] .shstrtab STRTAB 0000000000000000 000222 00002b 00 0 0 1
136+
137+
## Check that p_offset or p_filesz of empty segments or PT_PHDR are not modified.
138+
# CHECK2: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
139+
# CHECK2-NEXT: PHDR 0x000040 0x0000000000000040 0x0000000000000000 0x0000a8 0x0000a8 R 0x8
140+
# CHECK2-NEXT: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x000202 0x000202 R E 0x1000
141+
# CHECK2-NEXT: LOAD 0x000202 0x0000000000000202 0x0000000000000000 0x00000e 0x00000e RW 0x1
142+
143+
--- !ELF
144+
FileHeader:
145+
Class: ELFCLASS64
146+
Data: ELFDATA2LSB
147+
Type: ET_DYN
148+
Machine: EM_X86_64
149+
Sections:
150+
- Name: .text
151+
Type: SHT_PROGBITS
152+
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
153+
Address: 0x200
154+
AddressAlign: 0x200
155+
Content: c3
156+
- Name: .note
157+
Type: SHT_NOTE
158+
Flags: [ SHF_ALLOC ]
159+
Address: 0x201
160+
Content: 01
161+
- Name: .debug_info
162+
Type: SHT_PROGBITS
163+
AddressAlign: 0x20
164+
Content: 02
165+
ProgramHeaders:
166+
- Type: PT_PHDR
167+
Flags: [ PF_R ]
168+
Offset: 0x40
169+
VAddr: 0x40
170+
# 3 * sizeof(Elf64_Phdr) = 0xa8
171+
FileSize: 0xa8
172+
MemSize: 0xa8
173+
Align: 8
174+
- Type: PT_LOAD
175+
Flags: [ PF_R, PF_X ]
176+
Offset: 0
177+
Align: 4096
178+
Sections:
179+
- Section: .text
180+
- Section: .note
181+
- Type: PT_LOAD
182+
Flags: [ PF_R, PF_W ]
183+
Offset: 0x202
184+
VAddr: 0x202
185+
FileSize: 14
186+
MemSize: 14
187+
...
188+
189+
## If .symtab or .strtab has the SHF_ALLOC flag, it will be changed to SHT_NOBITS.
190+
# RUN: yaml2obj --docnum=3 %s -o %t3
191+
# RUN: llvm-objcopy --only-keep-debug %t3 %t3.dbg
192+
# RUN: llvm-readelf -S -l %t3.dbg | FileCheck --check-prefix=CHECK3 %s
193+
194+
# CHECK3: [Nr] Name Type Address Off Size ES Flg Lk Inf Al
195+
# CHECK3: [ 1] .dynsym NOBITS 0000000000000000 000040 000018 18 A 2 1 1024
196+
# CHECK3-NEXT: [ 2] .dynstr NOBITS 0000000000000000 000040 000001 00 A 0 0 0
197+
# CHECK3-NEXT: [ 3] .symtab NOBITS 0000000000000000 000040 000018 18 A 4 1 0
198+
# CHECK3-NEXT: [ 4] .strtab NOBITS 0000000000000000 000040 000001 00 A 0 0 0
199+
# CHECK3-NEXT: [ 5] .shstrtab STRTAB 0000000000000000 000040 00002b 00 0 0 1
200+
201+
--- !ELF
202+
FileHeader:
203+
Class: ELFCLASS64
204+
Data: ELFDATA2LSB
205+
Type: ET_DYN
206+
Machine: EM_X86_64
207+
Sections:
208+
- Name: .dynsym
209+
Type: SHT_DYNSYM
210+
Flags: [ SHF_ALLOC ]
211+
Link: .dynstr
212+
AddressAlign: 0x400
213+
- Name: .dynstr
214+
Type: SHT_STRTAB
215+
Flags: [ SHF_ALLOC ]
216+
- Name: .symtab
217+
Type: SHT_STRTAB
218+
Flags: [ SHF_ALLOC ]
219+
Link: .strtab
220+
- Name: .strtab
221+
Type: SHT_STRTAB
222+
Flags: [ SHF_ALLOC ]
223+
DynamicSymbols: []
224+
Symbols: []

llvm/tools/llvm-objcopy/CommonOpts.td

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,9 @@ def keep_file_symbols : Flag<["--"], "keep-file-symbols">,
8686

8787
def only_keep_debug
8888
: Flag<["--"], "only-keep-debug">,
89-
HelpText<"Clear sections that would not be stripped by --strip-debug. "
90-
"Currently only implemented for COFF.">;
89+
HelpText<
90+
"Produce a debug file as the output that only preserves contents of "
91+
"sections useful for debugging purposes">;
9192

9293
def discard_locals : Flag<["--"], "discard-locals">,
9394
HelpText<"Remove compiler-generated local symbols, (e.g. "

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,17 @@ static std::unique_ptr<Writer> createELFWriter(const CopyConfig &Config,
136136
// Depending on the initial ELFT and OutputFormat we need a different Writer.
137137
switch (OutputElfType) {
138138
case ELFT_ELF32LE:
139-
return std::make_unique<ELFWriter<ELF32LE>>(Obj, Buf,
140-
!Config.StripSections);
139+
return std::make_unique<ELFWriter<ELF32LE>>(Obj, Buf, !Config.StripSections,
140+
Config.OnlyKeepDebug);
141141
case ELFT_ELF64LE:
142-
return std::make_unique<ELFWriter<ELF64LE>>(Obj, Buf,
143-
!Config.StripSections);
142+
return std::make_unique<ELFWriter<ELF64LE>>(Obj, Buf, !Config.StripSections,
143+
Config.OnlyKeepDebug);
144144
case ELFT_ELF32BE:
145-
return std::make_unique<ELFWriter<ELF32BE>>(Obj, Buf,
146-
!Config.StripSections);
145+
return std::make_unique<ELFWriter<ELF32BE>>(Obj, Buf, !Config.StripSections,
146+
Config.OnlyKeepDebug);
147147
case ELFT_ELF64BE:
148-
return std::make_unique<ELFWriter<ELF64BE>>(Obj, Buf,
149-
!Config.StripSections);
148+
return std::make_unique<ELFWriter<ELF64BE>>(Obj, Buf, !Config.StripSections,
149+
Config.OnlyKeepDebug);
150150
}
151151
llvm_unreachable("Invalid output format");
152152
}
@@ -694,6 +694,11 @@ static Error handleArgs(const CopyConfig &Config, Object &Obj,
694694
}
695695
}
696696

697+
if (Config.OnlyKeepDebug)
698+
for (auto &Sec : Obj.sections())
699+
if (Sec.Flags & SHF_ALLOC && Sec.Type != SHT_NOTE)
700+
Sec.Type = SHT_NOBITS;
701+
697702
for (const auto &Flag : Config.AddSection) {
698703
std::pair<StringRef, StringRef> SecPair = Flag.split("=");
699704
StringRef SecName = SecPair.first;

0 commit comments

Comments
 (0)