Index: include/llvm/Object/Compressor.h =================================================================== --- /dev/null +++ include/llvm/Object/Compressor.h @@ -0,0 +1,122 @@ +//===-- Compressor.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-----------------------------------------------------------------------===/ + +#ifndef LLVM_OBJECT_COMPRESSOR_H +#define LLVM_OBJECT_COMPRESSOR_H + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/BinaryFormat/ELF.h" +#include "llvm/MC/MCTargetOptions.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Object/ELFTypes.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/Compression.h" +#include "llvm/Support/EndianStream.h" +#include "llvm/Support/Errc.h" + +namespace llvm { +namespace object { + +/// Compressor helps to handle compression of compressed sections. +class Compressor { +public: + explicit Compressor(StringRef Data) : SectionData(Data) {} + + /// Compress section data. + /// @param W Destination buffer stream for compressed data. + Error writeCompressedSectionData(support::endian::Writer &W) { + SmallVector CompressedBuffer; + auto E = zlib::compress(SectionData, CompressedBuffer); + W.OS << StringRef(CompressedBuffer.data(), CompressedBuffer.size()).str(); + return E; + } + +private: + StringRef SectionData; +}; + +/// Returns a new debug section name based on Name and CompressionType. This +/// function assumes that Name is one of the standard debug section names that +/// begins with either .debug or .zdebug: any other prefix will result in an +/// Error. +/// +/// If CompressionType is GNU, then the name prefix is set to ".zdebug", +/// otherwise the prefix will be set to ".debug". This function can be used to +/// fixup GNU ".zdebug" names to the corresponding ".debug" name during +/// decompression. +Expected +getNewDebugSectionName(const StringRef Name, + DebugCompressionType CompressionType); + +/// Returns if the section can be compressed based on its name (must have a +/// debug name, starts with .*debug. +bool isCompressableSectionName(StringRef Name); + +template void writeReserved(support::endian::Writer &W) { + if (T::Is64Bits) + W.write(static_cast(0)); // ch_reserved field. +} + +/// Writes the proper compression header info to W depending on CompressionType. +template +Error produceZLibHeader(support::endian::Writer &W, uint64_t DecompressedSize, + unsigned Align, DebugCompressionType CompressionType) { + if (CompressionType != DebugCompressionType::GNU && + CompressionType != DebugCompressionType::Z) { + return createStringError( + llvm::errc::invalid_argument, + "Invalid DebugCompressionType, only GNU and ZLIB are supported."); + } + + if (CompressionType == DebugCompressionType::GNU) { + const StringRef Magic = "ZLIB"; + W.OS << Magic; + support::endian::write(W.OS, DecompressedSize, support::big); + return Error::success(); + } + + using Chdr = Elf_Chdr_Impl; + W.write(static_cast(ELF::ELFCOMPRESS_ZLIB)); + writeReserved(W); + W.write(static_cast(DecompressedSize)); + W.write(static_cast(Align)); + return Error::success(); +} + +/// Returns compressed section content, including header data. +/// +/// @param Contents Section Content to be compressed. +/// @param Align Alignment (used for Elf_Chdr). +/// @param CompressionType DebugCompressionType GNU or Z (used for Elf_Chdr). +/// +/// Return value is a tuple that includes if the zlib library hit any errors, +/// followed by a boolean denoting if the compressed content plus the header +/// length is smaller than before, and lastly followed by the actual compressed +/// section content. +template +Expected> compress(StringRef Contents, uint64_t Align, + DebugCompressionType CompressionType) { + SmallVector CompressedContents; + raw_svector_ostream OS(CompressedContents); + support::endian::Writer W(OS, T::TargetEndianness); + if (Error E = + produceZLibHeader(W, Contents.size(), Align, CompressionType)) + return std::move(E); + Compressor C(Contents); + if (Error E = C.writeCompressedSectionData(W)) + return std::move(E); + return std::vector(CompressedContents.begin(), + CompressedContents.end()); +} + +} // end namespace object +} // end namespace llvm + +#endif // LLVM_OBJECT_COMPRESSOR_H Index: lib/Object/CMakeLists.txt =================================================================== --- lib/Object/CMakeLists.txt +++ lib/Object/CMakeLists.txt @@ -21,6 +21,7 @@ SymbolSize.cpp WasmObjectFile.cpp WindowsResource.cpp + Compressor.cpp ADDITIONAL_HEADER_DIRS ${LLVM_MAIN_INCLUDE_DIR}/llvm/Object Index: lib/Object/Compressor.cpp =================================================================== --- /dev/null +++ lib/Object/Compressor.cpp @@ -0,0 +1,45 @@ +//===-- Compressor.cpp ----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Object/Compressor.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/BinaryFormat/ELF.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Support/Compression.h" +#include "llvm/Support/DataExtractor.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/EndianStream.h" +#include "llvm/Support/Errc.h" + +using namespace llvm; +using namespace llvm::support::endian; +using namespace object; + +namespace llvm { +namespace object { + +bool isCompressableSectionName(StringRef Name) { + return (Name.startswith(".debug") || Name.startswith(".zdebug")); +} + +Expected +getNewDebugSectionName(const StringRef Name, + DebugCompressionType CompressionType) { + if (!isCompressableSectionName(Name)) { + return createStringError(llvm::errc::invalid_argument, + "Invalid Debug Section Name: %s.", + Name.str().c_str()); + } + std::string Prefix = + (CompressionType == DebugCompressionType::GNU) ? ".z" : "."; + return Prefix + Name.substr(Name.startswith(".debug") ? 1 : 2).str(); +} + +} // end namespace object +} // end namespace llvm Index: test/tools/llvm-objcopy/compress-debug-sections-decompress-xfail.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/compress-debug-sections-decompress-xfail.test @@ -0,0 +1,11 @@ +# REQUIRES: zlib + +# XFAIL: * + +# Reuse compress-debug-sections.test. +# This is expected to fail, we can't specify both --compress-debug-sections and +# --decompress-debug-sections. + +# RUN: yaml2obj %S/compress-debug-sections.test > %t-clean.o +# RUN: llvm-objcopy --compress-debug-sections=zlib --decompress-debug-sections %t-clean.o + Index: test/tools/llvm-objcopy/compress-debug-sections-zlib-gnu.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/compress-debug-sections-zlib-gnu.test @@ -0,0 +1,34 @@ +# REQUIRES: zlib + +# Reuse compress-debug-sections.test. +# Here we are testing that zlib-gnu properly sets the right magic header and +# also roundtrips properly. + +# RUN: yaml2obj %S/compress-debug-sections.test -o %t.o +# RUN: llvm-objcopy --compress-debug-sections=zlib-gnu %t.o %t-zlib-gnu.o +# RUN: llvm-objcopy --decompress-debug-sections %t-zlib-gnu.o %t-decompressed.o +# RUN: wc -c %t.o | FileCheck %s -check-prefix=SIZE-ORIGINAL +# RUN: wc -c %t-zlib-gnu.o | FileCheck %s -check-prefix=SIZE-COMPRESSED + +# RUN: llvm-objdump -s %t.o -section=.debug_str | FileCheck %s +# RUN: llvm-objdump -s %t-zlib-gnu.o | FileCheck %s --check-prefix=CHECK-ZLIB-GNU +# RUN: llvm-objdump -s %t-decompressed.o -section=.debug_str | FileCheck %s + +# CHECK: .debug_str +# CHECK: clang + +# CHECK-ZLIB-GNU: .zdebug_str +# CHECK-ZLIB-GNU: 000001c6 +# CHECK-ZLIB-GNU: ZLIB + +# CHECK-ZLIB-GNU: .zdebug_info +# CHECK-ZLIB-GNU: 00000068 +# CHECK-ZLIB-GNU: ZLIB + +# CHECK-ZLIB-GNU: .zdebug_line +# CHECK-ZLIB-GNU: 0000004f +# CHECK-ZLIB-GNU: ZLIB + +# SIZE-ORIGINAL: 2456 +# SIZE-COMPRESSED: 2168 + Index: test/tools/llvm-objcopy/compress-debug-sections-zlib.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/compress-debug-sections-zlib.test @@ -0,0 +1,31 @@ +# REQUIRES: zlib + +# Reuse compress-debug-sections.test. +# Here we are testing that zlib properly sets the right magic header and +# also roundtrips properly. + +# RUN: yaml2obj %S/compress-debug-sections.test -o %t.o +# RUN: llvm-objcopy --compress-debug-sections=zlib %t.o %t-zlib.o +# RUN: llvm-objcopy --decompress-debug-sections %t-zlib.o %t-decompressed.o +# RUN: wc -c %t.o | FileCheck %s -check-prefix=SIZE-ORIGINAL +# RUN: wc -c %t-zlib.o | FileCheck %s -check-prefix=SIZE-COMPRESSED + +# RUN: llvm-objdump -s %t.o -section=.debug_str | FileCheck %s +# RUN: llvm-objdump -s %t-zlib.o | FileCheck %s --check-prefix=CHECK-ZLIB +# RUN: llvm-objdump -s %t-decompressed.o -section=.debug_str | FileCheck %s + +# CHECK: .debug_str +# CHECK: clang + +# CHECK-ZLIB: .debug_str +# CHECK-ZLIB: c6010000 + +# CHECK-ZLIB: .debug_info +# CHECK-ZLIB: 68000000 + +# SIZE-ORIGINAL: 2456 +# SIZE-COMPRESSED: 2272 + +# .debug_line doesn't get compressed here because the non-gnu header is too big +# on Elf64. + Index: test/tools/llvm-objcopy/compress-debug-sections.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/compress-debug-sections.test @@ -0,0 +1,118 @@ +# REQUIRES: shell +# REQUIRES: zlib + +# Produce the obj file from yaml. Make sure the original has some recognizable +# .debug_str contents, ie "clang" +# RUN: yaml2obj %s > %t-clean.o + +# This produces a small shell script that takes an object file, reads out and +# sorts its debug section names, and then in sorted order it runs llvm-objdump +# (via xargs) on each debug section while redirecting that output to a text +# file. We compare the txt files of the original vs zlib vs zlib-gnu +# round-tripping. +# RUN: echo "echo \".debug_abbrev .debug_info .debug_line .debug_macinfo \ +# RUN: .debug_pubnames .debug_pubtypes .debug_str\" | \ +# RUN: xargs -I% llvm-objdump -s -section=% \$1 | \ +# RUN: grep -v \"file format\" > \$1.txt" > generateObjTxtOutput.sh + +# RUN: cp %t-clean.o %t-clean2.o +# RUN: llvm-objcopy %t-clean2.o +# RUN: sh generateObjTxtOutput.sh %t-clean2.o + +# RUN: cp %t-clean.o %t-zlib.o +# RUN: llvm-objcopy --compress-debug-sections=zlib %t-zlib.o +# RUN: llvm-objcopy --decompress-debug-sections %t-zlib.o +# RUN: sh generateObjTxtOutput.sh %t-zlib.o +# RUN: diff %t-clean2.o.txt %t-zlib.o.txt + +# RUN: cp %t-clean.o %t-zlib-gnu.o +# RUN: llvm-objcopy --compress-debug-sections=zlib-gnu %t-zlib-gnu.o +# RUN: llvm-objcopy --decompress-debug-sections %t-zlib-gnu.o +# RUN: sh generateObjTxtOutput.sh %t-zlib-gnu.o +# RUN: diff %t-clean2.o.txt %t-zlib-gnu.o.txt + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 +Sections: + - Name: .debug_str + Type: SHT_PROGBITS + Flags: [ SHF_MERGE, SHF_STRINGS ] + AddressAlign: 0x0000000000000001 + Content: 46616365626F6F6B20636C616E672076657273696F6E20362E302E3020286C6C766D3A20643161616332376537326138353765393761316432373932383032623335363664373130383161362C206366653A20316431376134323466656137313738613363343434373732303766363033353764386339366335632C20636F6D70696C65722D72743A20663033656661636664666431666533363735383563653962663432313439376661356632653163632C206C6C643A206366396536643666653134313833343339346162306337396233666664643631623732323534613020316431376134323466656137313738613363343434373732303766363033353764386339366335632920287373683A2F2F6769742D726F2E7669702E66616365626F6F6B2E636F6D2F646174612F6769747265706F732F6F736D6574612F65787465726E616C2F6C6C766D20643161616332376537326138353765393761316432373932383032623335363664373130383161362920286261736564206F6E204C4C564D20362E302E302900666F6F2E63002F686F6D652F706C6F7466692F50726F6A656374732F6C6C766D2D7374616E64616C6F6E6500666F6F00696E740061006200 + - Name: .debug_abbrev + Type: SHT_PROGBITS + AddressAlign: 0x0000000000000001 + Content: 011101250E1305030E10171B0EB44219110112060000022E01110112064018030E3A0B3B0B271949133F1900000305000218030E3A0B3B0B49130000042400030E3E0B0B0B000000 + - Name: .debug_info + Type: SHT_PROGBITS + AddressAlign: 0x0000000000000001 + Content: 640000000400000000000801000000000C00000000000000000000000000000000000000000039000000020000000000000000390000000156000000000102600000000302917C000000000102600000000302917800000000010260000000000400000000050400 + - Name: .rela.debug_info + Type: SHT_RELA + Link: .symtab + AddressAlign: 0x0000000000000008 + Info: .debug_info + - Name: .debug_ranges + Type: SHT_PROGBITS + AddressAlign: 0x0000000000000001 + Content: '' + - Name: .debug_macinfo + Type: SHT_PROGBITS + AddressAlign: 0x0000000000000001 + Content: '00' + - Name: .debug_pubnames + Type: SHT_PROGBITS + AddressAlign: 0x0000000000000001 + Content: 16000000020000000000680000002A000000666F6F0000000000 + - Name: .rela.debug_pubnames + Type: SHT_RELA + Link: .symtab + AddressAlign: 0x0000000000000008 + Info: .debug_pubnames + - Name: .debug_pubtypes + Type: SHT_PROGBITS + AddressAlign: 0x0000000000000001 + Content: '160000000200000000006800000060000000696E740000000000' + - Name: .rela.debug_pubtypes + Type: SHT_RELA + Link: .symtab + AddressAlign: 0x0000000000000008 + Info: .debug_pubtypes + - Name: .comment + Type: SHT_PROGBITS + Flags: [ SHF_MERGE, SHF_STRINGS ] + AddressAlign: 0x0000000000000001 + Content: '' + - Name: .note.GNU-stack + Type: SHT_PROGBITS + AddressAlign: 0x0000000000000001 + Content: '' + - Name: .eh_frame + Type: SHT_X86_64_UNWIND + Flags: [ SHF_ALLOC ] + AddressAlign: 0x0000000000000008 + Content: 1400000000000000017A5200017810011B0C0708900100001C0000001C000000000000003900000000410E108602430D0600000000000000 + - Name: .rela.eh_frame + Type: SHT_RELA + Link: .symtab + AddressAlign: 0x0000000000000008 + Info: .eh_frame + - Name: .debug_line + Type: SHT_PROGBITS + AddressAlign: 0x0000000000000001 + Content: 4B00000004001D000000010101FB0E0D00010101010000000100000100666F6F2E63000000000000090200000000000000001305170A0875051A063C05033C050A069F050C063C05033C020B000101 + - Name: .rela.debug_line + Type: SHT_RELA + Link: .symtab + AddressAlign: 0x0000000000000008 + Info: .debug_line +Symbols: + Local: + - Name: foo.c + Global: + - Name: foo +... Index: tools/llvm-objcopy/ObjcopyOpts.td =================================================================== --- tools/llvm-objcopy/ObjcopyOpts.td +++ tools/llvm-objcopy/ObjcopyOpts.td @@ -17,6 +17,11 @@ defm output_target : Eq<"output-target">, HelpText<"Format of the output file">, Values<"binary">; +defm compress_debug_sections : Eq<"compress-debug-sections">, + MetaVarName<"[ none | zlib | zlib-gnu ]">, + HelpText<"Enable zlib-gnu or zlib Compression of DWARF debug sections.">; +def decompress_debug_sections : Flag<["-", "--"], "decompress-debug-sections">, + HelpText<"Decompress DWARF debug sections.">; def O : JoinedOrSeparate<["-"], "O">, Alias; defm split_dwo : Eq<"split-dwo">, Index: tools/llvm-objcopy/Object.h =================================================================== --- tools/llvm-objcopy/Object.h +++ tools/llvm-objcopy/Object.h @@ -15,6 +15,7 @@ #include "llvm/ADT/Twine.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/MC/StringTableBuilder.h" +#include "llvm/Object/Compressor.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/JamCRC.h" @@ -39,6 +40,7 @@ class GnuDebugLinkSection; class GroupSection; class SectionIndexSection; +class CompressedSection; class Segment; class Object; struct Symbol; @@ -62,8 +64,6 @@ T *getSectionOfType(uint32_t Index, Twine IndexErrMsg, Twine TypeErrMsg); }; -enum ElfType { ELFT_ELF32LE, ELFT_ELF64LE, ELFT_ELF32BE, ELFT_ELF64BE }; - class SectionVisitor { public: virtual ~SectionVisitor(); @@ -77,6 +77,7 @@ virtual void visit(const GnuDebugLinkSection &Sec) = 0; virtual void visit(const GroupSection &Sec) = 0; virtual void visit(const SectionIndexSection &Sec) = 0; + virtual void visit(const CompressedSection &Sec) = 0; }; class SectionWriter : public SectionVisitor { @@ -95,6 +96,7 @@ virtual void visit(const GnuDebugLinkSection &Sec) override = 0; virtual void visit(const GroupSection &Sec) override = 0; virtual void visit(const SectionIndexSection &Sec) override = 0; + virtual void visit(const CompressedSection &Sec) override = 0; explicit SectionWriter(Buffer &Buf) : Out(Buf) {} }; @@ -112,6 +114,7 @@ void visit(const GnuDebugLinkSection &Sec) override; void visit(const GroupSection &Sec) override; void visit(const SectionIndexSection &Sec) override; + void visit(const CompressedSection &Sec) override; explicit ELFSectionWriter(Buffer &Buf) : SectionWriter(Buf) {} }; @@ -129,6 +132,7 @@ void visit(const GnuDebugLinkSection &Sec) override; void visit(const GroupSection &Sec) override; void visit(const SectionIndexSection &Sec) override; + void visit(const CompressedSection &Sec) override; explicit BinarySectionWriter(Buffer &Buf) : SectionWriter(Buf) {} }; @@ -250,6 +254,7 @@ uint64_t Offset = 0; uint64_t Size = 0; uint64_t Type = ELF::SHT_NULL; + ArrayRef OriginalData; virtual ~SectionBase() = default; @@ -307,11 +312,10 @@ class Section : public SectionBase { MAKE_SEC_WRITER_FRIEND - ArrayRef Contents; SectionBase *LinkSection = nullptr; public: - explicit Section(ArrayRef Data) : Contents(Data) {} + explicit Section(ArrayRef Data) { OriginalData = Data; } void accept(SectionVisitor &Visitor) const override; void removeSectionReferences(const SectionBase *Sec) override; @@ -322,12 +326,16 @@ class OwnedDataSection : public SectionBase { MAKE_SEC_WRITER_FRIEND +protected: + std::string OwnedName; std::vector Data; public: OwnedDataSection(StringRef SecName, ArrayRef Data) : Data(std::begin(Data), std::end(Data)) { - Name = SecName; + OriginalData = ArrayRef(Data.data(), Data.size()); + OwnedName = SecName.str(); + Name = OwnedName; Type = ELF::SHT_PROGBITS; Size = Data.size(); OriginalOffset = std::numeric_limits::max(); @@ -336,6 +344,27 @@ void accept(SectionVisitor &Sec) const override; }; +class CompressedSection : public OwnedDataSection { + MAKE_SEC_WRITER_FRIEND + + bool isGnuStyle() const { + ArrayRef GnuPrefix = {'Z', 'L', 'I', 'B'}; + return std::equal(GnuPrefix.begin(), GnuPrefix.end(), Data.data()); + } + +public: + CompressedSection(StringRef NewName, ArrayRef Data, + const SectionBase &Sec) + : OwnedDataSection(NewName, Data) { + Align = Sec.Align; + if (!isGnuStyle()) + Flags |= ELF::SHF_COMPRESSED; + } + + void finalize() override; + void accept(SectionVisitor &Visitor) const override; +}; + // There are two types of string tables that can exist, dynamic and not dynamic. // In the dynamic case the string table is allocated. Changing a dynamic string // table would mean altering virtual addresses and thus the memory image. So @@ -651,7 +680,6 @@ Binary *Bin; public: - ElfType getElfType() const; std::unique_ptr create() const override; explicit ELFReader(Binary *B) : Bin(B){}; }; Index: tools/llvm-objcopy/Object.cpp =================================================================== --- tools/llvm-objcopy/Object.cpp +++ tools/llvm-objcopy/Object.cpp @@ -106,6 +106,10 @@ error("Cannot write symbol section index table '" + Sec.Name + "' "); } +void BinarySectionWriter::visit(const CompressedSection &Sec) { + error("Cannot write compressed section '" + Sec.Name + "' "); +} + void BinarySectionWriter::visit(const SymbolTableSection &Sec) { error("Cannot write symbol table '" + Sec.Name + "' out to binary"); } @@ -126,7 +130,7 @@ if (Sec.Type == SHT_NOBITS) return; uint8_t *Buf = Out.getBufferStart() + Sec.Offset; - std::copy(std::begin(Sec.Contents), std::end(Sec.Contents), Buf); + std::copy(std::begin(Sec.OriginalData), std::end(Sec.OriginalData), Buf); } void Section::accept(SectionVisitor &Visitor) const { Visitor.visit(*this); } @@ -140,6 +144,18 @@ Visitor.visit(*this); } +template +void ELFSectionWriter::visit(const CompressedSection &Sec) { + uint8_t *Buf = Out.getBufferStart(); + Buf += Sec.Offset; + std::copy(Sec.Data.begin(), Sec.Data.end(), Buf); +} + +void CompressedSection::finalize() {} +void CompressedSection::accept(SectionVisitor &Visitor) const { + Visitor.visit(*this); +} + void StringTableSection::addString(StringRef Name) { StrTabBuilder.add(Name); Size = StrTabBuilder.getSize(); @@ -925,18 +941,6 @@ Reader::~Reader() {} -ElfType ELFReader::getElfType() const { - if (isa>(Bin)) - return ELFT_ELF32LE; - if (isa>(Bin)) - return ELFT_ELF64LE; - if (isa>(Bin)) - return ELFT_ELF32BE; - if (isa>(Bin)) - return ELFT_ELF64BE; - llvm_unreachable("Invalid ELFType"); -} - std::unique_ptr ELFReader::create() const { auto Obj = llvm::make_unique(); if (auto *o = dyn_cast>(Bin)) { Index: tools/llvm-objcopy/llvm-objcopy.cpp =================================================================== --- tools/llvm-objcopy/llvm-objcopy.cpp +++ tools/llvm-objcopy/llvm-objcopy.cpp @@ -16,9 +16,12 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/BinaryFormat/ELF.h" +#include "llvm/MC/MCTargetOptions.h" #include "llvm/Object/Archive.h" #include "llvm/Object/ArchiveWriter.h" #include "llvm/Object/Binary.h" +#include "llvm/Object/Compressor.h" +#include "llvm/Object/Decompressor.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/ELFTypes.h" #include "llvm/Object/Error.h" @@ -157,6 +160,8 @@ bool DiscardAll = false; bool OnlyKeepDebug = false; bool KeepFileSymbols = false; + bool DecompressDebugSections = false; + DebugCompressionType CompressDebugSections = DebugCompressionType::None; }; using SectionPred = std::function; @@ -288,41 +293,128 @@ return !IsDWOSection(Sec); } +template static std::unique_ptr CreateWriter(const CopyConfig &Config, - Object &Obj, Buffer &Buf, - ElfType OutputElfType) { + Object &Obj, Buffer &Buf) { if (Config.OutputFormat == "binary") { return llvm::make_unique(Obj, Buf); } - // Depending on the initial ELFT and OutputFormat we need a different Writer. - switch (OutputElfType) { - case ELFT_ELF32LE: - return llvm::make_unique>(Obj, Buf, - !Config.StripSections); - case ELFT_ELF64LE: - return llvm::make_unique>(Obj, Buf, - !Config.StripSections); - case ELFT_ELF32BE: - return llvm::make_unique>(Obj, Buf, - !Config.StripSections); - case ELFT_ELF64BE: - return llvm::make_unique>(Obj, Buf, - !Config.StripSections); - } - llvm_unreachable("Invalid output format"); + return llvm::make_unique>(Obj, Buf, !Config.StripSections); } +template static void SplitDWOToFile(const CopyConfig &Config, const Reader &Reader, - StringRef File, ElfType OutputElfType) { + StringRef File) { auto DWOFile = Reader.create(); DWOFile->removeSections( [&](const SectionBase &Sec) { return OnlyKeepDWOPred(*DWOFile, Sec); }); FileBuffer FB(File); - auto Writer = CreateWriter(Config, *DWOFile, FB, OutputElfType); + auto Writer = CreateWriter(Config, *DWOFile, FB); Writer->finalize(); Writer->write(); } +template +Expected>> +decompress(const SectionBase &Section, const CopyConfig &Config) { + bool Is64Bit = T::Is64Bits; + bool IsLittle = (T::TargetEndianness == endianness::little); + StringRef Contents( + reinterpret_cast(Section.OriginalData.data()), + Section.OriginalData.size()); + Expected D = + object::Decompressor::create(Section.Name, Contents, IsLittle, Is64Bit); + if (!D) + return D.takeError(); + + std::unique_ptr> DecompressedContents = + make_unique>(); + if (Error E = D->resizeAndDecompress(*DecompressedContents.get())) { + return std::move(E); + } + + return std::move(DecompressedContents); +} + +static bool isCompressed(const SectionBase &Section) { + return Section.Name.startswith(".zdebug") || + (Section.Flags & ELF::SHF_COMPRESSED); +} + +template +static void compressSections(const CopyConfig &Config, Object &Obj, + SectionPred &RemovePred) { + for (auto &Section : Obj.sections()) { + if (isCompressed(Section) || + !object::isCompressableSectionName(Section.Name)) + continue; + + StringRef Contents( + reinterpret_cast(Section.OriginalData.data()), + Section.OriginalData.size()); + Expected> CompressedContentOrError = object::compress( + Contents, Section.Align, Config.CompressDebugSections); + + if (!CompressedContentOrError) + reportError(Config.InputFilename, CompressedContentOrError.takeError()); + + std::vector CompressedContents = *CompressedContentOrError; + bool IsSmaller = (CompressedContents.size() < Section.Size); + if (!IsSmaller) + continue; + + Expected NewNameOrError = object::getNewDebugSectionName( + Section.Name, Config.CompressDebugSections); + if (!NewNameOrError) + reportError(Config.InputFilename, NewNameOrError.takeError()); + + const uint8_t *BufPtr = + reinterpret_cast(CompressedContents.data()); + size_t BufSize = CompressedContents.size(); + StringRef NewName(*NewNameOrError); + Obj.addSection( + NewName, ArrayRef(BufPtr, BufSize), Section); + + // Replace this Section with a compressed version. + RemovePred = [RemovePred, &Section](const SectionBase &Sec) { + return &Sec == &Section || RemovePred(Sec); + }; + } +} + +template +static void decompressSections(const CopyConfig &Config, Object &Obj, + SectionPred &RemovePred) { + for (auto &Section : Obj.sections()) { + if (!isCompressed(Section)) + continue; + + Expected>> + DecompressedContentsOrError = decompress(Section, Config); + + if (!DecompressedContentsOrError) + reportError(Config.InputFilename, + DecompressedContentsOrError.takeError()); + + Expected NewNameOrError = object::getNewDebugSectionName( + Section.Name, DebugCompressionType::None); + if (!NewNameOrError) + reportError(Config.InputFilename, NewNameOrError.takeError()); + StringRef NewName(*NewNameOrError); + + const uint8_t *BufPtr = reinterpret_cast( + (*DecompressedContentsOrError)->data()); + size_t BufSize = (*DecompressedContentsOrError)->size(); + Obj.addSection(NewName, + ArrayRef(BufPtr, BufSize)); + + // Replace this Section with a decompressed version. + RemovePred = [RemovePred, &Section](const SectionBase &Sec) { + return &Sec == &Section || RemovePred(Sec); + }; + } +} + // This function handles the high level operations of GNU objcopy including // handling command line options. It's important to outline certain properties // we expect to hold of the command line operations. Any operation that "keeps" @@ -330,11 +422,12 @@ // any previous removals. Lastly whether or not something is removed shouldn't // depend a) on the order the options occur in or b) on some opaque priority // system. The only priority is that keeps/copies overrule removes. +template static void HandleArgs(const CopyConfig &Config, Object &Obj, - const Reader &Reader, ElfType OutputElfType) { + const Reader &Reader) { if (!Config.SplitDWO.empty()) { - SplitDWOToFile(Config, Reader, Config.SplitDWO, OutputElfType); + SplitDWOToFile(Config, Reader, Config.SplitDWO); } // TODO: update or remove symbols only if there is an option that affects @@ -517,6 +610,12 @@ }; } + if (Config.CompressDebugSections != DebugCompressionType::None) { + compressSections(Config, Obj, RemovePred); + } else if (Config.DecompressDebugSections) { + decompressSections(Config, Obj, RemovePred); + } + Obj.removeSections(RemovePred); if (!Config.SectionsToRename.empty()) { @@ -559,17 +658,31 @@ Obj.addSection(Config.AddGnuDebugLink); } +template +static bool HandleArgsAndWrite(const CopyConfig &Config, Object &Obj, + const Reader &Reader, Binary &Binary, + Buffer &Out) { + if (isa>(Binary)) { + HandleArgs(Config, Obj, Reader); + std::unique_ptr Writer = CreateWriter(Config, Obj, Out); + Writer->finalize(); + Writer->write(); + return true; + } + + return false; +} + static void ExecuteElfObjcopyOnBinary(const CopyConfig &Config, Binary &Binary, Buffer &Out) { ELFReader Reader(&Binary); std::unique_ptr Obj = Reader.create(); - - HandleArgs(Config, *Obj, Reader, Reader.getElfType()); - - std::unique_ptr Writer = - CreateWriter(Config, *Obj, Out, Reader.getElfType()); - Writer->finalize(); - Writer->write(); + if (HandleArgsAndWrite(Config, *Obj, Reader, Binary, Out) || + HandleArgsAndWrite(Config, *Obj, Reader, Binary, Out) || + HandleArgsAndWrite(Config, *Obj, Reader, Binary, Out) || + HandleArgsAndWrite(Config, *Obj, Reader, Binary, Out)) + return; + llvm_unreachable("Binary has Invalid ELFT"); } // For regular archives this function simply calls llvm::writeArchive, @@ -686,6 +799,19 @@ Config.OutputFormat = InputArgs.getLastArgValue(OBJCOPY_output_target); Config.BinaryArch = InputArgs.getLastArgValue(OBJCOPY_binary_architecture); + Config.CompressDebugSections = + StringSwitch( + InputArgs.getLastArgValue(OBJCOPY_compress_debug_sections)) + .Case("zlib-gnu", DebugCompressionType::GNU) + .Case("zlib", DebugCompressionType::Z) + .Default(DebugCompressionType::None); + + if (Config.CompressDebugSections == DebugCompressionType::None && + InputArgs.getLastArgValue(OBJCOPY_compress_debug_sections) != "") { + error("Invalid or unsupported --compress-debug-sections format: " + + InputArgs.getLastArgValue(OBJCOPY_compress_debug_sections)); + } + Config.SplitDWO = InputArgs.getLastArgValue(OBJCOPY_split_dwo); Config.AddGnuDebugLink = InputArgs.getLastArgValue(OBJCOPY_add_gnu_debuglink); @@ -724,6 +850,8 @@ Config.DiscardAll = InputArgs.hasArg(OBJCOPY_discard_all); Config.OnlyKeepDebug = InputArgs.hasArg(OBJCOPY_only_keep_debug); Config.KeepFileSymbols = InputArgs.hasArg(OBJCOPY_keep_file_symbols); + Config.DecompressDebugSections = + InputArgs.hasArg(OBJCOPY_decompress_debug_sections); for (auto Arg : InputArgs.filtered(OBJCOPY_localize_symbol)) Config.SymbolsToLocalize.push_back(Arg->getValue()); for (auto Arg : InputArgs.filtered(OBJCOPY_globalize_symbol)) @@ -735,6 +863,12 @@ for (auto Arg : InputArgs.filtered(OBJCOPY_keep_symbol)) Config.SymbolsToKeep.push_back(Arg->getValue()); + if (Config.DecompressDebugSections && + Config.CompressDebugSections != DebugCompressionType::None) { + error("Cannot specify --compress-debug-sections as well as " + "--decompress-debug-sections at the same time."); + } + return Config; }