diff --git a/lld/ELF/CMakeLists.txt b/lld/ELF/CMakeLists.txt --- a/lld/ELF/CMakeLists.txt +++ b/lld/ELF/CMakeLists.txt @@ -35,6 +35,7 @@ LinkerScript.cpp MapFile.cpp MarkLive.cpp + LLDDwarfLinker.cpp OutputSections.cpp Relocations.cpp ScriptLexer.cpp @@ -52,6 +53,8 @@ BitWriter Core DebugInfoDWARF + DWARFLinker + AsmPrinter Demangle LTO MC diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -155,6 +155,7 @@ bool forceBTI; bool formatBinary = false; bool gcSections; + bool gcDebuginfo; bool gdbIndex; bool gnuHash = false; bool gnuUnique; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -27,6 +27,7 @@ #include "ICF.h" #include "InputFiles.h" #include "InputSection.h" +#include "LLDDwarfLinker.h" #include "LinkerScript.h" #include "MarkLive.h" #include "OutputSections.h" @@ -329,6 +330,8 @@ error("-r and -shared may not be used together"); if (config->gcSections) error("-r and --gc-sections may not be used together"); + if (config->gcDebuginfo) + error("-r and --gc-debuginfo may not be used together"); if (config->gdbIndex) error("-r and --gdb-index may not be used together"); if (config->icf != ICFLevel::None) @@ -909,6 +912,8 @@ args.hasArg(OPT_fix_cortex_a8) && !args.hasArg(OPT_relocatable); config->forceBTI = hasZOption(args, "force-bti"); config->gcSections = args.hasFlag(OPT_gc_sections, OPT_no_gc_sections, false); + config->gcDebuginfo = + args.hasFlag(OPT_gc_debuginfo, OPT_no_gc_debuginfo, false); config->gnuUnique = args.hasFlag(OPT_gnu_unique, OPT_no_gnu_unique, true); config->gdbIndex = args.hasFlag(OPT_gdb_index, OPT_no_gdb_index, false); config->icf = getICF(args); @@ -1995,6 +2000,11 @@ // Garbage collection and removal of shared symbols from unused shared objects. markLive(); + + // If -gc-debuginfo specified remove unused debug data. + if (config->gcSections && config->gcDebuginfo) + linkDebugInfo(); + demoteSharedSymbols(); // Make copies of any input sections that need to be copied into each diff --git a/lld/ELF/LLDDwarfLinker.h b/lld/ELF/LLDDwarfLinker.h new file mode 100644 --- /dev/null +++ b/lld/ELF/LLDDwarfLinker.h @@ -0,0 +1,23 @@ +//===- LLDDwarfLinker.h -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_ELF_LLDDWARFLINKER_H +#define LLD_ELF_LLDDWARFLINKER_H + +namespace lld { +namespace elf { + +// link debug info: this function analyzes liveness information +// and related relocations to remove parts of debug info data +// which points to sections to be removed. +template void linkDebugInfo(); + +} // namespace elf +} // namespace lld + +#endif // LLD_ELF_LLDDWARFLINKER_H diff --git a/lld/ELF/LLDDwarfLinker.cpp b/lld/ELF/LLDDwarfLinker.cpp new file mode 100644 --- /dev/null +++ b/lld/ELF/LLDDwarfLinker.cpp @@ -0,0 +1,110 @@ +//===- LLDDwarfLinker.cpp -------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements --gc-debuginfo functionality: +// +// When the linker does garbage collection, a lot of abandoned debug info +// is left behind. To remove such abandoned debug info LLDDwarfLinker analyzes +// relocations and removes debug info related to deleted sections. +// +// The process of linking debug info is based on liveness information created by +// markLive(). AddressesMap::getValidAddressRanges() creates a list +// of address ranges which points into live sections. DWARFLinker searches +// for 'root DIEs' related to live address ranges and calls cloneDIE() +// recursively. Cloned dies are passed to DwarfEmitter interface. LLD`s +// implementation of DwarfEmitter puts data into the DebugInputSection. +// +//===----------------------------------------------------------------------===// + +#include "LLDDwarfLinker.h" +#include "DWARF.h" +#include "InputFiles.h" +#include "InputSection.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/DWARFLinker/DWARFLinker.h" +#include "llvm/Object/ELF.h" + +using namespace llvm; +using namespace llvm::ELF; +using namespace llvm::object; + +namespace lld { +namespace elf { + +class ObjFileAddressMap : public AddressesMap { +public: + bool areRelocationsResolved() const override { return false; } + + bool hasValidRelocs(bool resetRelocsPtr = true) override { + return false; + } + + bool hasValidRelocationAt(uint64_t startOffset, uint64_t endOffset, + CompileUnit::DIEInfo &info) override { + return false; + } + + bool applyValidRelocs(MutableArrayRef data, uint64_t baseOffset, + bool isLittleEndian) override { + return false; + } + + RangesTy &getValidAddressRanges() override { return addressRanges; }; + + void clear() override {} + +private: + RangesTy addressRanges; +}; + +template void linkDebugInfo() { + assert(config->gcDebuginfo); + + DWARFLinker debugInfoLinker(Triple(sys::getProcessTriple()), nullptr, + DwarfLinkerClient::LLD); + + debugInfoLinker.setNoOutput(true); + debugInfoLinker.setEstimatedObjfilesAmount(objectFiles.size()); + + std::vector> objectsForLinking; + const std::vector emptyWarnings; + std::vector> dwarfContexts; + + // Create DWARFContext for each object file. + // Pass object file to DWARFLinker to be linked. + for (InputFile *file : objectFiles) { + if (ObjFile *obj = cast>(file)) { + dwarfContexts.push_back(std::unique_ptr( + new DWARFContext(std::make_unique>(obj)))); + + const object::ObjectFile *dwarfObjFile = + dwarfContexts.back()->getDWARFObj().getFile(); + + objectsForLinking.push_back( + std::make_unique( + file->getName(), dwarfObjFile, emptyWarnings)); + + objectsForLinking.back()->Addresses = + std::unique_ptr(new ObjFileAddressMap); + + // Add object file to the DWARFLinker. + debugInfoLinker.addObjectFile(*objectsForLinking.back()); + } + } + + // Link debug info. + debugInfoLinker.link(); +} + +template void linkDebugInfo(); +template void linkDebugInfo(); +template void linkDebugInfo(); +template void linkDebugInfo(); + +} // namespace elf +} // namespace lld diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -183,6 +183,10 @@ "Enable garbage collection of unused sections", "Disable garbage collection of unused sections (default)">; +defm gc_debuginfo: B<"gc-debuginfo", + "Enable garbage collection of unused debug information", + "Disable garbage collection of unused debug information (default)">; + defm gdb_index: B<"gdb-index", "Generate .gdb_index section", "Do not generate .gdb_index section (default)">; diff --git a/lld/test/ELF/gc-debuginfo.s b/lld/test/ELF/gc-debuginfo.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/gc-debuginfo.s @@ -0,0 +1,15 @@ +# REQUIRES: x86 + +## This is a test for --gc-debuginfo feature. +## At current moment it checks that --gc-debuginfo option +## is accepted by ld.lld. + +# RUN: echo '.global _start; .global main; _start: jmp main; main: ' \ +# RUN: | llvm-mc --filetype=obj --triple=x86_64-unknown-linux - -o %t.o + +# RUN: ld.lld --gc-sections --gc-debuginfo %t.o -o %t1.out + +# RUN: llvm-dwarfdump --a %t1.out | FileCheck %s + +# CHECK: file format ELF64-x86-64 +# CHECK-NOT: {{.}}