From 99124cc082d8b5d5a07bb80a4a37a0a88d6fade4 Mon Sep 17 00:00:00 2001 From: Puyan Lotfi Date: Fri, 7 Sep 2018 08:10:22 +0000 Subject: [PATCH] [llvm-objcopy] Dwarf .debug section compression support (zlib, zlib-gnu). Third Attempt: - Alignment issues resolved. - zlib::isAvailable() detected. - ArrayRef misuse fixed. Usage: llvm-objcopy --compress-debug-sections=zlib foo.o llvm-objcopy --compress-debug-sections=zlib-gnu foo.o In both cases the debug section contents is compressed with zlib. In the GNU style case the header is the "ZLIB" magic string followed by the uint64 big- endian decompressed size. In the non-GNU mode the header is the Elf(32|64)_Chdr. Decompression support is coming soon. Differential Revision: https://reviews.llvm.org/D49678 llvm-svn: 341635 --- .../Inputs/compress-debug-sections.yaml | 21 ++++++ .../compress-debug-sections-default-gnu.test | 9 +++ .../compress-debug-sections-default.test | 13 ++++ .../compress-debug-sections-invalid-format.test | 5 ++ .../compress-debug-sections-zlib-gnu.test | 49 ++++++++++++++ .../llvm-objcopy/compress-debug-sections-zlib.test | 49 ++++++++++++++ llvm/tools/llvm-objcopy/ObjcopyOpts.td | 6 ++ llvm/tools/llvm-objcopy/Object.cpp | 72 ++++++++++++++++++++ llvm/tools/llvm-objcopy/Object.h | 27 +++++++- llvm/tools/llvm-objcopy/llvm-objcopy.cpp | 77 ++++++++++++++++++++-- 10 files changed, 322 insertions(+), 6 deletions(-) create mode 100644 llvm/test/tools/llvm-objcopy/Inputs/compress-debug-sections.yaml create mode 100644 llvm/test/tools/llvm-objcopy/compress-debug-sections-default-gnu.test create mode 100644 llvm/test/tools/llvm-objcopy/compress-debug-sections-default.test create mode 100644 llvm/test/tools/llvm-objcopy/compress-debug-sections-invalid-format.test create mode 100644 llvm/test/tools/llvm-objcopy/compress-debug-sections-zlib-gnu.test create mode 100644 llvm/test/tools/llvm-objcopy/compress-debug-sections-zlib.test diff --git a/llvm/test/tools/llvm-objcopy/Inputs/compress-debug-sections.yaml b/llvm/test/tools/llvm-objcopy/Inputs/compress-debug-sections.yaml new file mode 100644 index 0000000..bb1e9e6 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/Inputs/compress-debug-sections.yaml @@ -0,0 +1,21 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 +Sections: + - Name: .debug_foo + Type: SHT_PROGBITS + Content: 0000000000000000 + - Name: .notdebug_foo + Type: SHT_PROGBITS + Content: 0000000000000000 + - Name: .rela.debug_foo + Type: SHT_RELA + Info: .debug_foo + Relocations: + - Offset: 0x1 + Symbol: .debug_foo + Type: R_X86_64_32 +... diff --git a/llvm/test/tools/llvm-objcopy/compress-debug-sections-default-gnu.test b/llvm/test/tools/llvm-objcopy/compress-debug-sections-default-gnu.test new file mode 100644 index 0000000..9db3d3b --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/compress-debug-sections-default-gnu.test @@ -0,0 +1,9 @@ +# REQUIRES: zlib + +# RUN: yaml2obj %p/Inputs/compress-debug-sections.yaml -o %t.o +# RUN: llvm-objcopy --compress-debug-sections --compress-debug-sections=zlib-gnu %t.o %t-compressed.o +# RUN: llvm-objdump -s %t-compressed.o | FileCheck %s + +# CHECK: .zdebug_foo: +# CHECK: ZLIB + diff --git a/llvm/test/tools/llvm-objcopy/compress-debug-sections-default.test b/llvm/test/tools/llvm-objcopy/compress-debug-sections-default.test new file mode 100644 index 0000000..8ed887d --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/compress-debug-sections-default.test @@ -0,0 +1,13 @@ +# REQUIRES: zlib + +# RUN: yaml2obj %p/Inputs/compress-debug-sections.yaml -o %t.o +# RUN: llvm-objcopy --compress-debug-sections %t.o %t-compressed.o +# RUN: llvm-readobj -s %t-compressed.o | FileCheck %s + +# CHECK: Name: .debug_foo +# CHECK-NEXT: Type: SHT_PROGBITS +# CHECK-NEXT: Flags [ +# CHECK-NEXT: SHF_COMPRESSED +# CHECK-NEXT: ] +# CHECK-NOT: Name: .debug_foo + diff --git a/llvm/test/tools/llvm-objcopy/compress-debug-sections-invalid-format.test b/llvm/test/tools/llvm-objcopy/compress-debug-sections-invalid-format.test new file mode 100644 index 0000000..854dfe2 --- /dev/null +++ b/llvm/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. + diff --git a/llvm/test/tools/llvm-objcopy/compress-debug-sections-zlib-gnu.test b/llvm/test/tools/llvm-objcopy/compress-debug-sections-zlib-gnu.test new file mode 100644 index 0000000..eb69752 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/compress-debug-sections-zlib-gnu.test @@ -0,0 +1,49 @@ +# REQUIRES: zlib + +# RUN: yaml2obj %p/Inputs/compress-debug-sections.yaml -o %t.o +# RUN: llvm-objcopy --compress-debug-sections=zlib-gnu %t.o %t-compressed.o + +# RUN: llvm-objdump -s %t.o -section=.debug_foo | FileCheck %s +# RUN: llvm-objdump -s %t-compressed.o | FileCheck %s --check-prefix=CHECK-COMPRESSED +# RUN: llvm-readobj -relocations -s %t-compressed.o | FileCheck %s --check-prefix=CHECK-FLAGS + +# CHECK: .debug_foo: + +# CHECK-COMPRESSED: .zdebug_foo: +# CHECK-COMPRESSED: ZLIB +# CHECK-COMPRESSED: .notdebug_foo: + +# CHECK-FLAGS-NOT: Name: .debug_foo +# CHECK-FLAGS: Index: 1 +# CHECK-FLAGS-NEXT: Name: .zdebug_foo +# CHECK-FLAGS-NEXT: Type: SHT_PROGBITS +# CHECK-FLAGS-NEXT: Flags [ +# CHECK-FLAGS-NEXT: ] +# CHECK-FLAGS-NEXT: Address: +# CHECK-FLAGS-NEXT: Offset: +# CHECK-FLAGS-NEXT: Size: 23 + +# CHECK-FLAGS: Name: .notdebug_foo +# CHECK-FLAGS-NEXT: Type: SHT_PROGBITS +# CHECK-FLAGS-NEXT: Flags [ +# CHECK-FLAGS-NEXT: ] +# CHECK-FLAGS-NEXT: Address: +# CHECK-FLAGS-NEXT: Offset: +# CHECK-FLAGS-NEXT: Size: 8 + +# CHECK-FLAGS: Name: .rela.debug_foo +# CHECK-FLAGS-NEXT: Type: SHT_RELA +# CHECK-FLAGS-NEXT: Flags [ +# CHECK-FLAGS-NEXT: ] +# CHECK-FLAGS-NEXT: Address: +# CHECK-FLAGS-NEXT: Offset: +# CHECK-FLAGS-NEXT: Size: +# CHECK-FLAGS-NEXT: Link: +# CHECK-FLAGS-NEXT: Info: 1 + +# CHECK-FLAGS: Relocations [ +# CHECK-FLAGS-NEXT: .rela.debug_foo { +# CHECK-FLAGS-NEXT: 0x1 R_X86_64_32 - 0x0 +# CHECK-FLAGS-NEXT: } +# CHECK-FLAGS-NEXT: ] + diff --git a/llvm/test/tools/llvm-objcopy/compress-debug-sections-zlib.test b/llvm/test/tools/llvm-objcopy/compress-debug-sections-zlib.test new file mode 100644 index 0000000..843dbf2 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/compress-debug-sections-zlib.test @@ -0,0 +1,49 @@ +# REQUIRES: zlib + +# RUN: yaml2obj %p/Inputs/compress-debug-sections.yaml -o %t.o +# RUN: llvm-objcopy --compress-debug-sections=zlib %t.o %t-compressed.o + +# RUN: llvm-objdump -s %t.o -section=.debug_foo | FileCheck %s +# RUN: llvm-objdump -s %t-compressed.o | FileCheck %s --check-prefix=CHECK-COMPRESSED +# RUN: llvm-readobj -relocations -s %t-compressed.o | FileCheck %s --check-prefix=CHECK-FLAGS + +# CHECK: .debug_foo: + +# CHECK-COMPRESSED: .debug_foo: +# CHECK-COMPRESSED: .notdebug_foo: + +# CHECK-FLAGS: Index: 1 +# CHECK-FLAGS-NEXT: Name: .debug_foo +# CHECK-FLAGS-NEXT: Type: SHT_PROGBITS +# CHECK-FLAGS-NEXT: Flags [ +# CHECK-FLAGS-NEXT: SHF_COMPRESSED +# CHECK-FLAGS-NEXT: ] +# CHECK-FLAGS-NEXT: Address: +# CHECK-FLAGS-NEXT: Offset: +# CHECK-FLAGS-NEXT: Size: 35 +# CHECK-FLAGS-NOT: Name: .debug_foo + +# CHECK-FLAGS: Name: .notdebug_foo +# CHECK-FLAGS-NEXT: Type: SHT_PROGBITS +# CHECK-FLAGS-NEXT: Flags [ +# CHECK-FLAGS-NEXT: ] +# CHECK-FLAGS-NEXT: Address: +# CHECK-FLAGS-NEXT: Offset: +# CHECK-FLAGS-NEXT: Size: 8 + +# CHECK-FLAGS: Name: .rela.debug_foo +# CHECK-FLAGS-NEXT: Type: SHT_RELA +# CHECK-FLAGS-NEXT: Flags [ +# CHECK-FLAGS-NEXT: ] +# CHECK-FLAGS-NEXT: Address: +# CHECK-FLAGS-NEXT: Offset: +# CHECK-FLAGS-NEXT: Size: +# CHECK-FLAGS-NEXT: Link: +# CHECK-FLAGS-NEXT: Info: 1 + +# CHECK-FLAGS: Relocations [ +# CHECK-FLAGS-NEXT: .rela.debug_foo { +# CHECK-FLAGS-NEXT: 0x1 R_X86_64_32 - 0x0 +# CHECK-FLAGS-NEXT: } +# CHECK-FLAGS-NEXT: ] + diff --git a/llvm/tools/llvm-objcopy/ObjcopyOpts.td b/llvm/tools/llvm-objcopy/ObjcopyOpts.td index c344947..e56dc52 100644 --- a/llvm/tools/llvm-objcopy/ObjcopyOpts.td +++ b/llvm/tools/llvm-objcopy/ObjcopyOpts.td @@ -17,6 +17,12 @@ def I : JoinedOrSeparate<[ "-" ], "I">, Alias; defm output_target : Eq<"output-target">, HelpText<"Format of the output file">, Values<"binary">; +def compress_debug_sections : Flag<["--", "-"], "compress-debug-sections">; +def compress_debug_sections_eq : Joined<["--", "-"], "compress-debug-sections=">, + MetaVarName<"[ zlib | zlib-gnu ]">, + HelpText<"Compress DWARF debug sections using " + "specified style. Supported styles: " + "'zlib-gnu' and 'zlib'">; def O : JoinedOrSeparate<["-"], "O">, Alias; defm split_dwo : Eq<"split-dwo">, diff --git a/llvm/tools/llvm-objcopy/Object.cpp b/llvm/tools/llvm-objcopy/Object.cpp index 2ff24ce..aaefcf99 100644 --- a/llvm/tools/llvm-objcopy/Object.cpp +++ b/llvm/tools/llvm-objcopy/Object.cpp @@ -15,7 +15,9 @@ #include "llvm/ADT/Twine.h" #include "llvm/ADT/iterator_range.h" #include "llvm/BinaryFormat/ELF.h" +#include "llvm/MC/MCTargetOptions.h" #include "llvm/Object/ELFObjectFile.h" +#include "llvm/Support/Compression.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/Path.h" @@ -138,6 +140,76 @@ void OwnedDataSection::accept(SectionVisitor &Visitor) const { Visitor.visit(*this); } +void BinarySectionWriter::visit(const CompressedSection &Sec) { + error("Cannot write compressed section '" + Sec.Name + "' "); +} + +template +void ELFSectionWriter::visit(const CompressedSection &Sec) { + uint8_t *Buf = Out.getBufferStart(); + Buf += Sec.Offset; + + if (Sec.CompressionType == DebugCompressionType::None) { + std::copy(Sec.OriginalData.begin(), Sec.OriginalData.end(), Buf); + return; + } + + if (Sec.CompressionType == DebugCompressionType::GNU) { + const char *Magic = "ZLIB"; + memcpy(Buf, Magic, strlen(Magic)); + Buf += strlen(Magic); + const uint64_t DecompressedSize = + support::endian::read64be(&Sec.DecompressedSize); + memcpy(Buf, &DecompressedSize, sizeof(DecompressedSize)); + Buf += sizeof(DecompressedSize); + } else { + Elf_Chdr_Impl Chdr; + Chdr.ch_type = ELF::ELFCOMPRESS_ZLIB; + Chdr.ch_size = Sec.DecompressedSize; + Chdr.ch_addralign = Sec.DecompressedAlign; + memcpy(Buf, &Chdr, sizeof(Chdr)); + Buf += sizeof(Chdr); + } + + std::copy(Sec.CompressedData.begin(), Sec.CompressedData.end(), Buf); +} + +CompressedSection::CompressedSection(const SectionBase &Sec, + DebugCompressionType CompressionType) + : SectionBase(Sec), CompressionType(CompressionType), + DecompressedSize(Sec.OriginalData.size()), DecompressedAlign(Sec.Align) { + + if (!zlib::isAvailable()) { + CompressionType = DebugCompressionType::None; + return; + } + + if (Error E = zlib::compress( + StringRef(reinterpret_cast(OriginalData.data()), + OriginalData.size()), + CompressedData)) + reportError(Name, std::move(E)); + + size_t ChdrSize; + if (CompressionType == DebugCompressionType::GNU) { + Name = ".z" + Sec.Name.substr(1); + ChdrSize = sizeof("ZLIB") - 1 + sizeof(uint64_t); + } else { + Flags |= ELF::SHF_COMPRESSED; + ChdrSize = + std::max(std::max(sizeof(object::Elf_Chdr_Impl), + sizeof(object::Elf_Chdr_Impl)), + std::max(sizeof(object::Elf_Chdr_Impl), + sizeof(object::Elf_Chdr_Impl))); + } + Size = ChdrSize + CompressedData.size(); + Align = 8; +} + +void CompressedSection::accept(SectionVisitor &Visitor) const { + Visitor.visit(*this); +} + void StringTableSection::addString(StringRef Name) { StrTabBuilder.add(Name); Size = StrTabBuilder.getSize(); diff --git a/llvm/tools/llvm-objcopy/Object.h b/llvm/tools/llvm-objcopy/Object.h index e9a4c35..42b2733 100644 --- a/llvm/tools/llvm-objcopy/Object.h +++ b/llvm/tools/llvm-objcopy/Object.h @@ -26,6 +26,7 @@ #include namespace llvm { +enum class DebugCompressionType; namespace objcopy { class Buffer; @@ -39,6 +40,7 @@ class DynamicRelocationSection; class GnuDebugLinkSection; class GroupSection; class SectionIndexSection; +class CompressedSection; class Segment; class Object; struct Symbol; @@ -86,6 +88,7 @@ public: 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 { @@ -104,6 +107,7 @@ public: 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) {} }; @@ -122,6 +126,7 @@ public: 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) {} }; @@ -139,6 +144,7 @@ public: 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) {} }; @@ -246,7 +252,7 @@ public: class SectionBase { public: - StringRef Name; + std::string Name; Segment *ParentSegment = nullptr; uint64_t HeaderOffset; uint64_t OriginalOffset = std::numeric_limits::max(); @@ -265,6 +271,9 @@ public: uint64_t Type = ELF::SHT_NULL; ArrayRef OriginalData; + SectionBase() = default; + SectionBase(const SectionBase &) = default; + virtual ~SectionBase() = default; virtual void initialize(SectionTableRef SecTable); @@ -341,7 +350,7 @@ class OwnedDataSection : public SectionBase { public: OwnedDataSection(StringRef SecName, ArrayRef Data) : Data(std::begin(Data), std::end(Data)) { - Name = SecName; + Name = SecName.str(); Type = ELF::SHT_PROGBITS; Size = Data.size(); OriginalOffset = std::numeric_limits::max(); @@ -350,6 +359,20 @@ public: void accept(SectionVisitor &Sec) const override; }; +class CompressedSection : public SectionBase { + MAKE_SEC_WRITER_FRIEND + + DebugCompressionType CompressionType; + uint64_t DecompressedSize; + uint64_t DecompressedAlign; + SmallVector CompressedData; + +public: + CompressedSection(const SectionBase &Sec, + DebugCompressionType CompressionType); + 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 diff --git a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp index 8ffab54..36ed77a 100644 --- a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp +++ b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp @@ -17,6 +17,7 @@ #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" @@ -29,6 +30,7 @@ #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Compiler.h" +#include "llvm/Support/Compression.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/ErrorOr.h" @@ -176,6 +178,7 @@ struct CopyConfig { bool StripSections = false; bool StripUnneeded = false; bool Weaken = false; + DebugCompressionType CompressionType = DebugCompressionType::None; }; // Configuration for the overall invocation of this tool. When invoked as @@ -298,12 +301,12 @@ static SectionRename parseRenameSectionValue(StringRef FlagValue) { } static bool isDebugSection(const SectionBase &Sec) { - return Sec.Name.startswith(".debug") || Sec.Name.startswith(".zdebug") || - Sec.Name == ".gdb_index"; + return StringRef(Sec.Name).startswith(".debug") || + StringRef(Sec.Name).startswith(".zdebug") || Sec.Name == ".gdb_index"; } static bool isDWOSection(const SectionBase &Sec) { - return Sec.Name.endswith(".dwo"); + return StringRef(Sec.Name).endswith(".dwo"); } static bool onlyKeepDWOPred(const Object &Obj, const SectionBase &Sec) { @@ -413,6 +416,50 @@ static Error dumpSectionToFile(StringRef SecName, StringRef Filename, object_error::parse_failed); } +static bool isCompressed(const SectionBase &Section) { + const char *Magic = "ZLIB"; + return StringRef(Section.Name).startswith(".zdebug") || + (Section.OriginalData.size() > strlen(Magic) && + !strncmp(reinterpret_cast(Section.OriginalData.data()), + Magic, strlen(Magic))) || + (Section.Flags & ELF::SHF_COMPRESSED); +} + +static bool isCompressable(const SectionBase &Section) { + return !isCompressed(Section) && isDebugSection(Section) && + Section.Name != ".gdb_index"; +} + +static void compressSections(const CopyConfig &Config, Object &Obj, + SectionPred &RemovePred) { + SmallVector ToCompress; + SmallVector RelocationSections; + for (auto &Sec : Obj.sections()) { + if (RelocationSection *R = dyn_cast(&Sec)) { + if (isCompressable(*R->getSection())) + RelocationSections.push_back(R); + continue; + } + + if (isCompressable(Sec)) + ToCompress.push_back(&Sec); + } + + for (SectionBase *S : ToCompress) { + CompressedSection &CS = + Obj.addSection(*S, Config.CompressionType); + + for (RelocationSection *RS : RelocationSections) { + if (RS->getSection() == S) + RS->setSection(&CS); + } + } + + RemovePred = [RemovePred](const SectionBase &Sec) { + return isCompressable(Sec) || 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" @@ -572,7 +619,7 @@ static void handleArgs(const CopyConfig &Config, Object &Obj, return true; if (&Sec == Obj.SectionNames) return false; - if (Sec.Name.startswith(".gnu.warning")) + if (StringRef(Sec.Name).startswith(".gnu.warning")) return false; return (Sec.Flags & SHF_ALLOC) == 0; }; @@ -624,6 +671,9 @@ static void handleArgs(const CopyConfig &Config, Object &Obj, }; } + if (Config.CompressionType != DebugCompressionType::None) + compressSections(Config, Obj, RemovePred); + Obj.removeSections(RemovePred); if (!Config.SectionsToRename.empty()) { @@ -868,6 +918,25 @@ static DriverConfig parseObjcopyOptions(ArrayRef ArgsArr) { Config.BinaryArch = getMachineInfo(BinaryArch); } + if (auto Arg = InputArgs.getLastArg(OBJCOPY_compress_debug_sections, + OBJCOPY_compress_debug_sections_eq)) { + Config.CompressionType = DebugCompressionType::Z; + + if (Arg->getOption().getID() == OBJCOPY_compress_debug_sections_eq) { + Config.CompressionType = + StringSwitch( + InputArgs.getLastArgValue(OBJCOPY_compress_debug_sections_eq)) + .Case("zlib-gnu", DebugCompressionType::GNU) + .Case("zlib", DebugCompressionType::Z) + .Default(DebugCompressionType::None); + if (Config.CompressionType == DebugCompressionType::None) + error("Invalid or unsupported --compress-debug-sections format: " + + InputArgs.getLastArgValue(OBJCOPY_compress_debug_sections_eq)); + if (!zlib::isAvailable()) + error("LLVM was not compiled with LLVM_ENABLE_ZLIB: can not compress."); + } + } + Config.SplitDWO = InputArgs.getLastArgValue(OBJCOPY_split_dwo); Config.AddGnuDebugLink = InputArgs.getLastArgValue(OBJCOPY_add_gnu_debuglink); Config.SymbolsPrefix = InputArgs.getLastArgValue(OBJCOPY_prefix_symbols); -- 2.7.4