Index: docs/CommandGuide/llvm-objcopy.rst =================================================================== --- docs/CommandGuide/llvm-objcopy.rst +++ docs/CommandGuide/llvm-objcopy.rst @@ -63,6 +63,11 @@ Print a summary of command line options. +.. option:: --only-keep-debug + + Remove the contents of non-debug sections from the output, but keep the section + headers. + .. option:: --only-section
, -j Remove all sections from the output, except for sections named ``
``. @@ -136,11 +141,6 @@ objects, :program:`llvm-objcopy` will either emit an error or silently ignore them. -.. option:: --only-keep-debug - - Remove the contents of non-debug sections from the output, but keep the section - headers. - ELF-SPECIFIC OPTIONS -------------------- Index: test/tools/llvm-objcopy/ELF/basic-only-keep-debug.test =================================================================== --- test/tools/llvm-objcopy/ELF/basic-only-keep-debug.test +++ /dev/null @@ -1,22 +0,0 @@ -# NOTE: This test is only intended to be valid as long as --only-keep-debug is -# implemented as a NOP. This test should fail when that changes and you -# will need to update this test. - -# RUN: yaml2obj %s > %t -# RUN: llvm-objcopy %t %t2 -# RUN: llvm-objcopy --only-keep-debug %t %t3 -# RUN: cmp %t2 %t3 -# RUN: llvm-strip --only-keep-debug --no-strip-all %t -o %t4 -# RUN: cmp %t2 %t4 - -!ELF -FileHeader: - Class: ELFCLASS64 - Data: ELFDATA2LSB - Type: ET_EXEC - Machine: EM_X86_64 -Sections: - - Name: .text - Type: SHT_PROGBITS - Flags: [ SHF_ALLOC, SHF_EXECINSTR ] - Content: "DEADBEEF" Index: test/tools/llvm-objcopy/ELF/only-keep-debug.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/ELF/only-keep-debug.test @@ -0,0 +1,110 @@ +# RUN: yaml2obj %s -o %t +# RUN: llvm-objcopy --only-keep-debug %t %t.dbg +# RUN: llvm-readelf -S -l %t.dbg | FileCheck %s +# RUN: llvm-objdump -s %t.dbg | FileCheck --check-prefix=CONTENTS %s +# RUN: llvm-strip --only-keep-debug %t -o %t.dbg2 +# RUN: cmp %t.dbg %t.dbg2 + +## SHF_ALLOC sections that are not SHT_NOTE become SHT_NOBITS. +## They do not occupy space in the output. + +# CHECK: Name Type Address Off Size ES Flg Lk Inf Al +# CHECK: .note NOTE 0000000000000480 000040 000001 00 A 0 0 0 +# CHECK-NEXT: .text NOBITS 0000000000000481 000041 000001 00 AX 0 0 0 +# CHECK-NEXT: .tdata NOBITS 0000000000001400 000080 000007 00 WAT 0 0 128 +# CHECK-NEXT: .bss NOBITS 0000000000001420 000060 00003f 00 WA 0 0 32 +# CHECK-NEXT: .debug_abbrev PROGBITS 0000000000000000 000041 000001 00 0 0 0 + +## sh_offset of .debug_frame advances to the next multiple of sh_addralign. +# CHECK-NEXT: .debug_frame PROGBITS 0000000000000000 000048 000001 00 0 0 8 +# CHECK-NEXT: .debug_info PROGBITS 0000000000000000 000049 000001 00 0 0 0 +# CHECK-NEXT: .comment PROGBITS 0000000000000000 00004a 000001 00 0 0 0 +# CHECK-NEXT: .symtab SYMTAB 0000000000000000 000050 000030 18 10 2 8 +# CHECK-NEXT: .strtab STRTAB 0000000000000000 000080 000005 00 0 0 1 +# CHECK-NEXT: .shstrtab STRTAB 0000000000000000 000085 000063 00 0 0 1 + +## GNU objcopy rewrites program headers to make p_offset/p_filesz match section headers. +## We just delete program headers. They are not used by debuggers. +# CHECK: There are 0 program headers, starting at offset 0 + +## Contents of SHT_NOTE and non-SHF_ALLOC sections are kept. + +# CONTENTS: Contents of section .note: +# CONTENTS-NEXT: 0480 01 +# CONTENTS: Contents of section .debug_abbrev: +# CONTENTS-NEXT: 0000 02 +# CONTENTS-NEXT: Contents of section .debug_frame: +# CONTENTS-NEXT: 0000 03 +# CONTENTS-NEXT: Contents of section .debug_info: +# CONTENTS-NEXT: 0000 04 +# CONTENTS-NEXT: Contents of section .comment: +# CONTENTS-NEXT: 0000 05 + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .note + Type: SHT_NOTE + Flags: [ SHF_ALLOC ] + Address: 0x480 # Ensure Address=Offset + Content: 01 + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x481 # Ensure Address=Offset + Content: c3 + - Name: .tdata + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_WRITE, SHF_TLS ] + Address: 0x1400 # Ensure Address=0x1000+Offset + AddressAlign: 128 + Size: 7 + - Name: .bss + Type: SHT_NOBITS + Flags: [ SHF_ALLOC, SHF_WRITE ] + Address: 0x1420 # Ensure Address=0x1000+Offset + AddressAlign: 32 + Size: 63 + - Name: .debug_abbrev + Type: SHT_PROGBITS + Content: 02 + - Name: .debug_frame + Type: SHT_PROGBITS + AddressAlign: 8 + Content: 03 + - Name: .debug_info + Type: SHT_PROGBITS + Content: 04 + - Name: .comment + Type: SHT_PROGBITS + Content: 05 +Symbols: + - Name: foo # A dummy symbol to test we don't strip .symtab +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_R, PF_X ] + Offset: 0 + Align: 4096 + Sections: + - Section: .note + - Section: .text + - Type: PT_LOAD + Flags: [ PF_R, PF_W ] + VAddr: 0x1400 # Ensure Offset=VAddr (mod Align) if Offset changes + Align: 4096 + Sections: + - Section: .tdata + - Section: .bss + - Type: PT_TLS + Flags: [ PF_R, PF_W ] + VAddr: 0x1480 # Ensure Offset=VAddr (mod Align) if Offset changes + Sections: + - Section: .tdata + - Type: PT_NOTE + VAddr: 0x480 + Sections: + - Section: .note Index: tools/llvm-objcopy/CommonOpts.td =================================================================== --- tools/llvm-objcopy/CommonOpts.td +++ tools/llvm-objcopy/CommonOpts.td @@ -85,8 +85,7 @@ def only_keep_debug : Flag<["--"], "only-keep-debug">, - HelpText<"Clear sections that would not be stripped by --strip-debug. " - "Currently only implemented for COFF.">; + HelpText<"Create a separate debug file">; def discard_locals : Flag<["--"], "discard-locals">, HelpText<"Remove compiler-generated local symbols, (e.g. " Index: tools/llvm-objcopy/CopyConfig.cpp =================================================================== --- tools/llvm-objcopy/CopyConfig.cpp +++ tools/llvm-objcopy/CopyConfig.cpp @@ -852,7 +852,8 @@ if (!InputArgs.hasArg(STRIP_no_strip_all) && !Config.StripDebug && !Config.StripUnneeded && Config.DiscardMode == DiscardType::None && - !Config.StripAllGNU && Config.SymbolsToRemove.empty()) + !Config.StripAllGNU && Config.SymbolsToRemove.empty() && + !Config.OnlyKeepDebug) Config.StripAll = true; if (Config.DiscardMode == DiscardType::All) Index: tools/llvm-objcopy/ELF/ELFObjcopy.cpp =================================================================== --- tools/llvm-objcopy/ELF/ELFObjcopy.cpp +++ tools/llvm-objcopy/ELF/ELFObjcopy.cpp @@ -134,19 +134,20 @@ Object &Obj, Buffer &Buf, ElfType OutputElfType) { // Depending on the initial ELFT and OutputFormat we need a different Writer. + bool WriteSectionHeaders = !Config.StripSections && Obj.HadShdrs; switch (OutputElfType) { case ELFT_ELF32LE: - return std::make_unique>(Obj, Buf, - !Config.StripSections); + return std::make_unique>(Obj, Buf, Config.OnlyKeepDebug, + WriteSectionHeaders); case ELFT_ELF64LE: - return std::make_unique>(Obj, Buf, - !Config.StripSections); + return std::make_unique>(Obj, Buf, Config.OnlyKeepDebug, + WriteSectionHeaders); case ELFT_ELF32BE: - return std::make_unique>(Obj, Buf, - !Config.StripSections); + return std::make_unique>(Obj, Buf, Config.OnlyKeepDebug, + WriteSectionHeaders); case ELFT_ELF64BE: - return std::make_unique>(Obj, Buf, - !Config.StripSections); + return std::make_unique>(Obj, Buf, Config.OnlyKeepDebug, + WriteSectionHeaders); } llvm_unreachable("Invalid output format"); } @@ -486,6 +487,11 @@ }; } + if (Config.OnlyKeepDebug) + for (SectionBase &Sec : Obj.allocSections()) + if (Sec.Type != SHT_NOTE) + Sec.Type = SHT_NOBITS; + if (Config.StripNonAlloc) RemovePred = [RemovePred, &Obj](const SectionBase &Sec) { if (RemovePred(Sec)) Index: tools/llvm-objcopy/ELF/Object.h =================================================================== --- tools/llvm-objcopy/ELF/Object.h +++ tools/llvm-objcopy/ELF/Object.h @@ -340,11 +340,13 @@ public: virtual ~ELFWriter() {} + bool OnlyKeepDebug; bool WriteSectionHeaders; Error finalize() override; Error write() override; - ELFWriter(Object &Obj, Buffer &Buf, bool WSH); + ELFWriter(Object &Obj, Buffer &Buf, bool OnlyKeepDebug, + bool WriteSectionHeaders); }; class BinaryWriter : public Writer { @@ -1052,6 +1054,7 @@ Range segments() { return make_pointee_range(Segments); } ConstRange segments() const { return make_pointee_range(Segments); } + void clearSegments() { Segments.clear(); } Error removeSections(bool AllowBrokenLinks, std::function ToRemove); Index: tools/llvm-objcopy/ELF/Object.cpp =================================================================== --- tools/llvm-objcopy/ELF/Object.cpp +++ tools/llvm-objcopy/ELF/Object.cpp @@ -1782,8 +1782,10 @@ } template -ELFWriter::ELFWriter(Object &Obj, Buffer &Buf, bool WSH) - : Writer(Obj, Buf), WriteSectionHeaders(WSH && Obj.HadShdrs) {} +ELFWriter::ELFWriter(Object &Obj, Buffer &Buf, bool OnlyKeepDebug, + bool WriteSectionHeaders) + : Writer(Obj, Buf), OnlyKeepDebug(OnlyKeepDebug), + WriteSectionHeaders(WriteSectionHeaders) {} Error Object::removeSections(bool AllowBrokenLinks, std::function ToRemove) { @@ -1924,6 +1926,17 @@ return Offset; } +static uint64_t layoutSectionsForOnlyKeepDebug(Object &Obj, uint64_t Off) { + uint32_t Index = 1; + for (SectionBase &Sec : Obj.sections()) { + Sec.Index = Index++; + Sec.Offset = alignTo(Off, Sec.Align ? Sec.Align : 1); + if (Sec.Type != SHT_NOBITS) + Off = Sec.Offset + Sec.Size; + } + return Off; +} + template void ELFWriter::initEhdrSegment() { Segment &ElfHdr = Obj.ElfHdrSegment; ElfHdr.Type = PT_PHDR; @@ -1944,12 +1957,21 @@ OrderedSegments.push_back(&Obj.ElfHdrSegment); OrderedSegments.push_back(&Obj.ProgramHdrSegment); orderSegments(OrderedSegments); - // Offset is used as the start offset of the first segment to be laid out. - // Since the ELF Header (ElfHdrSegment) must be at the start of the file, - // we start at offset 0. - uint64_t Offset = 0; - Offset = layoutSegments(OrderedSegments, Offset); - Offset = layoutSections(Obj.sections(), Offset); + + uint64_t Offset; + if (OnlyKeepDebug) { + // For --only-keep-debug, we delete program headers because + // p_offset/p_filesz will conflict with section headers after we rewrite + // sh_offset fields. + Obj.clearSegments(); + Offset = layoutSectionsForOnlyKeepDebug(Obj, sizeof(Elf_Ehdr)); + } else { + // Offset is used as the start offset of the first segment to be laid out. + // Since the ELF Header (ElfHdrSegment) must be at the start of the file, + // we start at offset 0. + Offset = layoutSegments(OrderedSegments, 0); + Offset = layoutSections(Obj.sections(), Offset); + } // If we need to write the section header table out then we need to align the // Offset so that SHOffset is valid. if (WriteSectionHeaders) @@ -1967,12 +1989,23 @@ } template Error ELFWriter::write() { - // Segment data must be written first, so that the ELF header and program - // header tables can overwrite it, if covered by a segment. - writeSegmentData(); - writeEhdr(); - writePhdrs(); - writeSectionData(); + if (OnlyKeepDebug) { + // --only-keep-debug uses a custom layout algorithm that rewrites sh_offset + // fields. Only ELF header, section header and contents of non-SHT_NOBITS + // sections are needed. + writeEhdr(); + for (SectionBase &Sec : Obj.sections()) + if (Sec.Type != SHT_NOBITS) + Sec.accept(*SecWriter); + } else { + // Segment data must be written first, so that the ELF header and program + // header tables can overwrite it, if covered by a segment. + writeSegmentData(); + writeEhdr(); + writePhdrs(); + writeSectionData(); + } + if (WriteSectionHeaders) writeShdrs(); return Buf.commit();