Index: include/llvm/Object/Compressor.h =================================================================== --- /dev/null +++ include/llvm/Object/Compressor.h @@ -0,0 +1,81 @@ +//===-- 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/StringRef.h" +#include "llvm/MC/MCTargetOptions.h" +#include "llvm/Object/ELFTypes.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); + +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(StringRef Name, DebugCompressionType CompressionType); + +/// Returns if the section can be compressed based on its name (must have a +/// debug name that starts with either .debug or .zdebug). +bool isCompressableSectionName(StringRef Name); + +/// 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); +extern template Expected> +compress(StringRef Contents, uint64_t Align, + DebugCompressionType CompressionType); +extern template Expected> +compress(StringRef Contents, uint64_t Align, + DebugCompressionType CompressionType); +extern template Expected> +compress(StringRef Contents, uint64_t Align, + DebugCompressionType CompressionType); +extern template Expected> +compress(StringRef Contents, uint64_t Align, + DebugCompressionType CompressionType); + +} // 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,102 @@ +//===-- 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" + +using namespace llvm; +using namespace llvm::support::endian; +using namespace object; + +namespace llvm { +namespace object { + +Error Compressor::writeCompressedSectionData(support::endian::Writer &W) { + SmallVector CompressedBuffer; + auto Err = zlib::compress(SectionData, CompressedBuffer); + W.OS << StringRef(CompressedBuffer.data(), CompressedBuffer.size()).str(); + return Err; +} + +bool isCompressableSectionName(StringRef Name) { + return (Name.startswith(".debug") || Name.startswith(".zdebug")); +} + +Expected +getNewDebugSectionName(StringRef Name, DebugCompressionType CompressionType) { + if (!isCompressableSectionName(Name)) { + return createStringError(llvm::errc::invalid_argument, + "Invalid Debug Section Name: %s.", + Name.str().c_str()); + } + StringRef Prefix = CompressionType == DebugCompressionType::GNU ? ".z" : "."; + return (Prefix + Name.substr(Name.startswith(".debug") ? 1 : 2)).str(); +} + +template void writeReserved(support::endian::Writer &W) { + if (ELFT::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) { + 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(); +} + +template +Expected> compress(StringRef Contents, uint64_t Align, + DebugCompressionType CompressionType) { + SmallVector CompressedContents; + raw_svector_ostream OS(CompressedContents); + support::endian::Writer W(OS, ELFT::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()); +} + +template Expected> +compress(StringRef Contents, uint64_t Align, + DebugCompressionType CompressionType); +template Expected> +compress(StringRef Contents, uint64_t Align, + DebugCompressionType CompressionType); +template Expected> +compress(StringRef Contents, uint64_t Align, + DebugCompressionType CompressionType); +template Expected> +compress(StringRef Contents, uint64_t Align, + DebugCompressionType CompressionType); + +} // end namespace object +} // end namespace llvm Index: test/tools/llvm-objcopy/Inputs/compress-debug-sections.yaml =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/Inputs/compress-debug-sections.yaml @@ -0,0 +1,22 @@ +--- !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: 636c616e672076657273696f6e20362e302e302d317562756e7475322028746167732f52454c454153455f3630302f66696e616c29666f6f2e632f686f6d652f666f6f2f6261722f6c6c766d6d61696e696e744861726b2079652079657420616761696e20746865206c6974746c65206c6f776572206c6179657220416c6c2076697369626c65206f626a65637473206d616e2061726520627574206173207061737465626f617264206d61736b732042757420696e2065616368206576656e7420696e20746865206c6976696e67206163742074686520756e646f7562746564206465656420746865726520736f6d6520756e6b6e6f776e20627574207374696c6c20726561736f6e696e67207468696e67207075747320666f72746820746865206d6f756c64696e6773206f66206974732066656174757265732066726f6d20626568696e642074686520756e726561736f6e696e67206d61736b204966206d616e2077696c6c20737472696b6520737472696b65207468726f75676820746865206d61736b20486f772063616e2074686520707269736f6e6572207265616368206f7574736964652065786365707420627920746872757374696e67207468726f756768207468652077616c6c + - Name: .note.GNU-stack + Type: SHT_PROGBITS + AddressAlign: 0x0000000000000001 + Content: '' +Symbols: + Local: + - Name: foo.c + Global: + - Name: foo +... Index: test/tools/llvm-objcopy/compress-and-decompress-debug-sections-error.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/compress-and-decompress-debug-sections-error.test @@ -0,0 +1,5 @@ +# RUN: yaml2obj %p/Inputs/compress-debug-sections.yaml -o %t.o +# RUN: not llvm-objcopy --compress-debug-sections=zlib --decompress-debug-sections %t.o 2>&1 | FileCheck %s + +# CHECK: Cannot specify --compress-debug-sections as well as --decompress-debug-sections at the same time. + Index: test/tools/llvm-objcopy/compress-debug-sections-invalid-format.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/compress-debug-sections-invalid-format.test @@ -0,0 +1,5 @@ +# RUN: yaml2obj %p/Inputs/compress-debug-sections.yaml -o %t.o +# RUN: not llvm-objcopy --compress-debug-sections=zlib-fake %t.o 2>&1 | FileCheck %s + +# CHECK: Invalid or unsupported --compress-debug-sections format: zlib-fake. + 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,22 @@ +# REQUIRES: zlib + +# RUN: yaml2obj %p/Inputs/compress-debug-sections.yaml -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: 5a4c4942 00000000 000001d0 789c4550 +# CHECK-ZLIB-GNU: ZLIB + +# SIZE-ORIGINAL: 1049 +# SIZE-COMPRESSED: 896 + Index: test/tools/llvm-objcopy/compress-debug-sections-zlib.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/compress-debug-sections-zlib.test @@ -0,0 +1,28 @@ +# REQUIRES: zlib + +# RUN: yaml2obj %p/Inputs/compress-debug-sections.yaml -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-readobj -s %t-zlib.o | FileCheck %s --check-prefix=CHECK-FLAGS +# RUN: llvm-objdump -s %t-decompressed.o -section=.debug_str | FileCheck %s + +# CHECK: .debug_str +# CHECK: clang + +# CHECK-ZLIB: .debug_str +# CHECK-ZLIB: 01000000 00000000 d0010000 00000000 +# CHECK-ZLIB: 01000000 00000000 789c4550 + +# CHECK-FLAGS: Name: .debug_str (1) +# CHECK-FLAGS: Flags [ (0x800) +# CHECK-FLAGS: SHF_COMPRESSED (0x800) + +# SIZE-ORIGINAL: 1049 +# SIZE-COMPRESSED: 912 + + Index: test/tools/llvm-objcopy/compress-debug-sections.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/compress-debug-sections.test @@ -0,0 +1,17 @@ +# REQUIRES: zlib + +# RUN: yaml2obj %p/Inputs/compress-debug-sections.yaml -o %t.o + +# RUN: llvm-objcopy --compress-debug-sections=zlib %t.o %tz.o +# RUN: llvm-objcopy --compress-debug-sections=zlib-gnu %t.o %tzg.o + +# RUN: llvm-objcopy --decompress-debug-sections %tz.o %t2.o +# RUN: llvm-objcopy --decompress-debug-sections %tzg.o %t3.o + +# RUN: llvm-objdump -s -section=.debug_str - < %t.o > %t.txt +# RUN: llvm-objdump -s -section=.debug_str - < %t2.o > %t2.txt +# RUN: llvm-objdump -s -section=.debug_str - < %t3.o > %t3.txt + +# RUN: diff %t.txt %t2.txt +# RUN: diff %t.txt %t3.txt + 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) {} }; @@ -113,6 +115,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) {} }; @@ -130,6 +133,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) {} }; @@ -310,11 +314,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; @@ -325,12 +328,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(); @@ -339,6 +346,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 @@ -654,7 +682,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 @@ -104,6 +104,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"); } @@ -124,7 +128,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); } @@ -138,6 +142,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(); @@ -926,18 +942,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 @@ -17,9 +17,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" @@ -161,6 +164,8 @@ bool DiscardAll = false; bool OnlyKeepDebug = false; bool KeepFileSymbols = false; + bool DecompressDebugSections = false; + DebugCompressionType CompressDebugSections = DebugCompressionType::None; }; using SectionPred = std::function; @@ -293,37 +298,23 @@ 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(); } @@ -352,6 +343,107 @@ object_error::parse_failed); } +template +Expected>> +decompress(const SectionBase &Section, const CopyConfig &Config) { + StringRef Contents( + reinterpret_cast(Section.OriginalData.data()), + Section.OriginalData.size()); + Expected D = object::Decompressor::create( + Section.Name, Contents, (ELFT::TargetEndianness == endianness::little), + ELFT::Is64Bits); + 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" @@ -359,11 +451,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 @@ -549,6 +642,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()) { @@ -601,17 +700,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, @@ -728,6 +841,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); Config.SymbolsPrefix = InputArgs.getLastArgValue(OBJCOPY_prefix_symbols); @@ -769,6 +895,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)) @@ -780,6 +908,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; }