diff --git a/llvm/test/tools/llvm-objcopy/ELF/basic-only-keep-debug.test b/llvm/test/tools/llvm-objcopy/ELF/basic-only-keep-debug.test deleted file mode 100644 --- a/llvm/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" diff --git a/llvm/test/tools/llvm-objcopy/ELF/only-keep-debug.test b/llvm/test/tools/llvm-objcopy/ELF/only-keep-debug.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/ELF/only-keep-debug.test @@ -0,0 +1,158 @@ +# RUN: yaml2obj --docnum=1 %s -o %t1 +# RUN: llvm-objcopy --only-keep-debug %t1 %t1.dbg +# RUN: llvm-readelf -S -l -x .note1 -x .note2 -x .debug_abbrev -x .debug_frame -x .debug_info %t1.dbg | FileCheck %s + +## Check that SHT_NOTE and .debug* are kept, but others are changed to SHT_NOBITS. +## SHT_NOBITS sections do not occupy space in the output. + +# CHECK: .note1 NOTE 0000000000000120 000120 000001 00 A 0 0 0 +# CHECK-NEXT: .note2 NOTE 0000000000000121 000121 000001 00 A 0 0 0 +# CHECK-NEXT: .text NOBITS 0000000000000122 000122 000001 00 AX 0 0 0 +# CHECK-NEXT: .tdata NOBITS 0000000000001180 000180 000007 00 WAT 0 0 128 +# CHECK-NEXT: .bss NOBITS 00000000000011a0 000180 00003f 00 WA 0 0 32 +## objcopy sets sh_offset to 0x122. We don't do this to keep sh_offset non-decreasing. +# CHECK-NEXT: .debug_abbrev PROGBITS 0000000000000000 000180 000001 00 0 0 0 +# CHECK-NEXT: .debug_frame PROGBITS 0000000000000000 000188 000001 00 0 0 8 +# CHECK-NEXT: .debug_info PROGBITS 0000000000000000 000189 000001 00 0 0 0 +# CHECK-NEXT: .strtab STRTAB 0000000000000000 00018a 000001 00 0 0 1 +# CHECK-NEXT: .shstrtab STRTAB 0000000000000000 00018b 00005a 00 0 0 1 + +# CHECK: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align +# CHECK-NEXT: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x000122 0x000123 R E 0x1000 +# CHECK-NEXT: LOAD 0x000180 0x0000000000001180 0x0000000000000000 0x000000 0x00005f RW 0x1000 +# CHECK-NEXT: TLS 0x000180 0x0000000000001180 0x0000000000000000 0x000000 0x000007 RW 0x80 +# CHECK-NEXT: NOTE 0x000120 0x0000000000000120 0x0000000000000000 0x000002 0x000002 0x1 + +## Contents of SHT_NOTE and .debug* are kept. + +# CHECK: Hex dump of section '.note1': +# CHECK-NEXT: 0x00000120 01 +# CHECK: Hex dump of section '.note2': +# CHECK-NEXT: 0x00000121 02 +# CHECK: Hex dump of section '.debug_abbrev': +# CHECK-NEXT: 0x00000000 03 +# CHECK: Hex dump of section '.debug_frame': +# CHECK-NEXT: 0x00000000 04 +# CHECK: Hex dump of section '.debug_info': +# CHECK-NEXT: 0x00000000 05 + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .note1 + Type: SHT_NOTE + Flags: [ SHF_ALLOC ] + Address: 0x120 # Ensure Address=Offset + Content: 01 + - Name: .note2 + Type: SHT_NOTE + Flags: [ SHF_ALLOC ] + Address: 0x121 # Ensure Address=Offset + Content: 02 + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x122 # Ensure Address=Offset + Content: c3 + - Name: .tdata + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_WRITE, SHF_TLS ] + Address: 0x1180 # Ensure Address=0x1000+Offset + AddressAlign: 128 + Size: 7 + - Name: .bss + Type: SHT_NOBITS + Flags: [ SHF_ALLOC, SHF_WRITE ] + Address: 0x11a0 # Ensure Address=0x1000+Offset + AddressAlign: 32 + Size: 63 + - Name: .debug_abbrev + Type: SHT_PROGBITS + Content: 03 + - Name: .debug_frame + Type: SHT_PROGBITS + AddressAlign: 8 + Content: 04 + - Name: .debug_info + Type: SHT_PROGBITS + Content: 05 +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_R, PF_X ] + Offset: 0 + Align: 4096 + Sections: + - Section: .note1 + - Section: .note2 + - Section: .text + - Type: PT_LOAD + Flags: [ PF_R, PF_W ] + VAddr: 0x1180 # Ensure Offset=VAddr (mod Align) if Offset changes + Align: 4096 + Sections: + - Section: .tdata + - Section: .bss + - Type: PT_TLS + Flags: [ PF_R, PF_W ] + VAddr: 0x1180 # Ensure Offset=VAddr (mod Align) if Offset changes + Sections: + - Section: .tdata + - Type: PT_NOTE + VAddr: 0x120 + Sections: + - Section: .note1 + - Section: .note2 +... + +## Check that p_offset or p_filesz of empty segments or PT_PHDR are not modified. +# RUN: yaml2obj --docnum=2 %s -o %t2 +# CHECK2: .text PROGBITS 00000000000000b0 0000b0 000001 00 AX 0 0 0 +# CHECK2-NEXT: .debug_info PROGBITS 0000000000000000 0000c0 000001 00 0 0 0 + +# CHECK2: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align +# CHECK2-NEXT: PHDR 0x000040 0x0000000000000040 0x0000000000000000 0x0000a8 0x0000a8 R 0x8 +# CHECK2-NEXT: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x000122 0x000123 R E 0x1000 +# CHECK2-NEXT: LOAD 0x0000b1 0x00000000000000b1 0x0000000000000000 0x00000f 0x00000f RW 0x1 + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0xe8 # Ensure Address=Offset + Content: c3 + - Name: .debug_info + Type: SHT_PROGBITS + AddressAlign: 32 + Content: 01 +ProgramHeaders: + - Type: PT_PHDR + Flags: [ PF_R, PF_X ] + Offset: 0x40 + VAddr: 0x40 + # 3 * sizeof(Elf64_Phdr) = 0xa8 + FileSize: 0xa8 + MemSize: 0xa8 + Align: 8 + - Type: PT_LOAD + Flags: [ PF_R, PF_X ] + Offset: 0 + Align: 4096 + Sections: + - Section: .text + - Type: PT_LOAD + Flags: [ PF_R, PF_X ] + Offset: 0xb1 + VAddr: 0xb1 + FileSize: 15 + MemSize: 15 +... diff --git a/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp b/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp --- a/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp +++ b/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp @@ -136,17 +136,17 @@ // Depending on the initial ELFT and OutputFormat we need a different Writer. switch (OutputElfType) { case ELFT_ELF32LE: - return std::make_unique>(Obj, Buf, - !Config.StripSections); + return std::make_unique>(Obj, Buf, !Config.StripSections, + Config.OnlyKeepDebug); case ELFT_ELF64LE: - return std::make_unique>(Obj, Buf, - !Config.StripSections); + return std::make_unique>(Obj, Buf, !Config.StripSections, + Config.OnlyKeepDebug); case ELFT_ELF32BE: - return std::make_unique>(Obj, Buf, - !Config.StripSections); + return std::make_unique>(Obj, Buf, !Config.StripSections, + Config.OnlyKeepDebug); case ELFT_ELF64BE: - return std::make_unique>(Obj, Buf, - !Config.StripSections); + return std::make_unique>(Obj, Buf, !Config.StripSections, + Config.OnlyKeepDebug); } llvm_unreachable("Invalid output format"); } @@ -580,6 +580,11 @@ return &Obj.addSection(*CS); }); + if (Config.OnlyKeepDebug) + for (auto &Sec : Obj.sections()) + if (Sec.Flags & SHF_ALLOC && Sec.Type != SHT_NOTE) + Sec.Type = SHT_NOBITS; + return Obj.removeSections(Config.AllowBrokenLinks, RemovePred); } diff --git a/llvm/tools/llvm-objcopy/ELF/Object.h b/llvm/tools/llvm-objcopy/ELF/Object.h --- a/llvm/tools/llvm-objcopy/ELF/Object.h +++ b/llvm/tools/llvm-objcopy/ELF/Object.h @@ -342,9 +342,13 @@ virtual ~ELFWriter() {} bool WriteSectionHeaders; + // For --only-keep-debug, select an alternative section/segment layout + // algorithm. + bool OnlyKeepDebug; + Error finalize() override; Error write() override; - ELFWriter(Object &Obj, Buffer &Buf, bool WSH); + ELFWriter(Object &Obj, Buffer &Buf, bool WSH, bool OnlyKeepDebug); }; class BinaryWriter : public Writer { @@ -432,8 +436,6 @@ } }; - std::set Sections; - public: uint32_t Type; uint32_t Flags; @@ -448,6 +450,7 @@ uint64_t OriginalOffset; Segment *ParentSegment = nullptr; ArrayRef Contents; + std::set Sections; explicit Segment(ArrayRef Data) : Contents(Data) {} Segment() {} diff --git a/llvm/tools/llvm-objcopy/ELF/Object.cpp b/llvm/tools/llvm-objcopy/ELF/Object.cpp --- a/llvm/tools/llvm-objcopy/ELF/Object.cpp +++ b/llvm/tools/llvm-objcopy/ELF/Object.cpp @@ -1785,10 +1785,9 @@ template void ELFWriter::writeSegmentData() { for (Segment &Seg : Obj.segments()) { - uint8_t *B = Buf.getBufferStart() + Seg.Offset; - assert(Seg.FileSize == Seg.getContents().size() && - "Segment size must match contents size"); - std::memcpy(B, Seg.getContents().data(), Seg.FileSize); + size_t Size = std::min(Seg.FileSize, Seg.getContents().size()); + std::memcpy(Buf.getBufferStart() + Seg.Offset, Seg.getContents().data(), + Size); } // Iterate over removed sections and overwrite their old data with zeroes. @@ -1803,8 +1802,10 @@ } template -ELFWriter::ELFWriter(Object &Obj, Buffer &Buf, bool WSH) - : Writer(Obj, Buf), WriteSectionHeaders(WSH && Obj.HadShdrs) {} +ELFWriter::ELFWriter(Object &Obj, Buffer &Buf, bool WSH, + bool OnlyKeepDebug) + : Writer(Obj, Buf), WriteSectionHeaders(WSH && Obj.HadShdrs), + OnlyKeepDebug(OnlyKeepDebug) {} Error Object::removeSections(bool AllowBrokenLinks, std::function ToRemove) { @@ -1945,6 +1946,78 @@ return Offset; } +// Rewrite sh_offset after some sections are changed to SHT_NOBITS and thus +// occupy no space in the file. +static uint64_t layoutSectionsForOnlyKeepDebug(Object &Obj, uint64_t Off) { + uint32_t Index = 1; + for (auto &Sec : Obj.sections()) { + Sec.Index = Index++; + + auto *FirstSec = Sec.ParentSegment && Sec.ParentSegment->Type == PT_LOAD + ? Sec.ParentSegment->firstSection() + : nullptr; + + // The first section in a PT_LOAD has to have congruent offset and address + // modulo the maximum page size. + if (FirstSec && FirstSec == &Sec) + Off = alignTo(Off, Sec.ParentSegment->Align, Sec.Addr); + + // sh_offset is not significant for SHT_NOBITS sections, but the congruence + // rule must be followed if it is the first section in a PT_LOAD. Do not + // advance Off. + if (Sec.Type == SHT_NOBITS) { + Sec.Offset = Off; + continue; + } + + if (!FirstSec) { + // FirstSec being nullptr generally means that Sec does not have the + // SHF_ALLOC flag. + Off = Sec.Align ? alignTo(Off, Sec.Align) : Off; + } else if (FirstSec != &Sec) { + // The offset is relative to the first section in the PT_LOAD segment. Use + // sh_offset for non-SHF_ALLOC sections. + Off = Sec.OriginalOffset - FirstSec->OriginalOffset + FirstSec->Offset; + } + Sec.Offset = Off; + Off += Sec.Size; + } + return Off; +} + +// Rewrite p_offset and p_filesz of non-empty non-PT_PHDR segments after +// sh_offset are updated. +static uint64_t layoutSegmentsForOnlyKeepDebug(std::vector &Segments, + uint64_t HdrOffset) { + uint64_t MaxOffset = 0; + for (Segment *Seg : Segments) { + const SectionBase *Sec = Seg->firstSection(); + if (Seg->Type == PT_PHDR || !Sec) + continue; + + uint64_t Offset = Sec->Offset; + uint64_t FileSize = 0; + for (const SectionBase *Sec : Seg->Sections) { + uint64_t Size = Sec->Type == SHT_NOBITS ? 0 : Sec->Size; + if (Sec->Offset + Size > Offset) + FileSize = std::max(FileSize, Sec->Offset + Size - Offset); + } + + // If the segment includes EHDR and program headers, don't make it smaller + // than the headers. + if (Seg->Offset <= HdrOffset && HdrOffset <= Seg->Offset + Seg->FileSize) { + FileSize += Offset - Seg->Offset; + Offset = Seg->Offset; + FileSize = std::max(FileSize, HdrOffset - Offset); + } + + Seg->Offset = Offset; + Seg->FileSize = FileSize; + MaxOffset = std::max(MaxOffset, Offset + FileSize); + } + return MaxOffset; +} + template void ELFWriter::initEhdrSegment() { Segment &ElfHdr = Obj.ElfHdrSegment; ElfHdr.Type = PT_PHDR; @@ -1965,12 +2038,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) { + uint64_t HdrOffset = + sizeof(Elf_Ehdr) + llvm::size(Obj.segments()) * sizeof(Elf_Phdr); + Offset = layoutSectionsForOnlyKeepDebug(Obj, HdrOffset); + Offset = std::max( + Offset, layoutSegmentsForOnlyKeepDebug(OrderedSegments, HdrOffset)); + } 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)