[llvm] e2147c2 - [Debuginfo][llvm-dwarfutil] llvm-dwarfutil dsymutil-like tool for ELF.

Alexey Lapshin via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 19 01:24:07 PDT 2022


Author: Alexey Lapshin
Date: 2022-07-19T11:18:36+03:00
New Revision: e2147c26bd1522ad67a98836fbe94933eab869bb

URL: https://github.com/llvm/llvm-project/commit/e2147c26bd1522ad67a98836fbe94933eab869bb
DIFF: https://github.com/llvm/llvm-project/commit/e2147c26bd1522ad67a98836fbe94933eab869bb.diff

LOG: [Debuginfo][llvm-dwarfutil] llvm-dwarfutil dsymutil-like tool for ELF.

This patch implements proposal https://lists.llvm.org/pipermail/llvm-dev/2020-August/144579.html
llvm-dwarfutil - is a tool that is used for processing debug info(DWARF) located in built binary files to improve debug info quality, reduce debug info size. The patch currently implements smaller set of command-line options(comparing to the proposal):

```
./llvm-dwarfutil [options] <input file> <output file>

  --garbage-collection    Do garbage collection for debug info(default)
  -j <value>              Alias for --num-threads
  --no-garbage-collection Don`t do garbage collection for debug info
  --no-odr-deduplication  Don`t do ODR deduplication for debug types
  --no-odr                Alias for --no-odr-deduplication
  --no-separate-debug-file
                          Create single output file, containing debug tables(default)
  --num-threads <threads> Number of available threads for multi-threaded execution. Defaults to the number of cores on the current machine
  --odr-deduplication     Do ODR deduplication for debug types(default)
  --odr                   Alias for --odr-deduplication
  --separate-debug-file   Create two output files: file w/o debug tables and file with debug tables
  --tombstone [bfd,maxpc,exec,universal]
                          Tombstone value used as a marker of invalid address(default: universal)
    =bfd - Zero for all addresses and [1,1] for DWARF v4 (or less) address ranges and exec
    =maxpc - Minus 1 for all addresses and minus 2 for DWARF v4 (or less) address ranges
    =exec - Match with address ranges of executable sections
    =universal - Both: bfd and maxpc
```

Reviewed By: clayborg

Differential Revision: https://reviews.llvm.org/D86539

Added: 
    llvm/docs/CommandGuide/llvm-dwarfutil.rst
    llvm/test/tools/llvm-dwarfutil/ELF/Inputs/common.yaml
    llvm/test/tools/llvm-dwarfutil/ELF/copy-itself.test
    llvm/test/tools/llvm-dwarfutil/ELF/copy.test
    llvm/test/tools/llvm-dwarfutil/ELF/error-separate-file-stdout.test
    llvm/test/tools/llvm-dwarfutil/ELF/error-unsupported-input-file.test
    llvm/test/tools/llvm-dwarfutil/ELF/gc-bfd.test
    llvm/test/tools/llvm-dwarfutil/ELF/gc-class.test
    llvm/test/tools/llvm-dwarfutil/ELF/gc-default.test
    llvm/test/tools/llvm-dwarfutil/ELF/gc-exec.test
    llvm/test/tools/llvm-dwarfutil/ELF/gc-maxpc.test
    llvm/test/tools/llvm-dwarfutil/ELF/gc-no-garbage.test
    llvm/test/tools/llvm-dwarfutil/ELF/odr-fwd-declaration.test
    llvm/test/tools/llvm-dwarfutil/ELF/separate-debug-file.test
    llvm/test/tools/llvm-dwarfutil/ELF/verbose.test
    llvm/test/tools/llvm-dwarfutil/ELF/verify.test
    llvm/test/tools/llvm-dwarfutil/error-invalid-format.test
    llvm/test/tools/llvm-dwarfutil/error-no-gc-odr.test
    llvm/test/tools/llvm-dwarfutil/error-no-input-file.test
    llvm/test/tools/llvm-dwarfutil/error-positional-args.test
    llvm/test/tools/llvm-dwarfutil/error-unknown-option.test
    llvm/test/tools/llvm-dwarfutil/error-unknown-tombstone.test
    llvm/test/tools/llvm-dwarfutil/help.test
    llvm/tools/llvm-dwarfutil/CMakeLists.txt
    llvm/tools/llvm-dwarfutil/DebugInfoLinker.cpp
    llvm/tools/llvm-dwarfutil/DebugInfoLinker.h
    llvm/tools/llvm-dwarfutil/Error.h
    llvm/tools/llvm-dwarfutil/Options.h
    llvm/tools/llvm-dwarfutil/Options.td
    llvm/tools/llvm-dwarfutil/llvm-dwarfutil.cpp

Modified: 
    llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h
    llvm/lib/DWARFLinker/DWARFLinker.cpp
    llvm/lib/DWARFLinker/DWARFLinkerCompileUnit.cpp
    llvm/test/CMakeLists.txt
    llvm/test/lit.cfg.py

Removed: 
    


################################################################################
diff  --git a/llvm/docs/CommandGuide/llvm-dwarfutil.rst b/llvm/docs/CommandGuide/llvm-dwarfutil.rst
new file mode 100644
index 0000000000000..a939fd4a29335
--- /dev/null
+++ b/llvm/docs/CommandGuide/llvm-dwarfutil.rst
@@ -0,0 +1,123 @@
+llvm-dwarfutil - A tool to copy and manipulate debug info
+=========================================================
+
+.. program:: llvm-dwarfutil
+
+SYNOPSIS
+--------
+
+:program:`llvm-dwarfutil` [*options*] *input* *output*
+
+DESCRIPTION
+-----------
+
+:program:`llvm-dwarfutil` is a tool to copy and manipulate debug info.
+
+In basic usage, it makes a semantic copy of the input to the output. If any
+options are specified, the output may be modified along the way, e.g.
+by removing unused debug info.
+
+If "-" is specified for the input file, the input is read from the program's
+standard input stream. If "-" is specified for the output file, the output
+is written to the standard output stream of the program.
+
+The tool is still in active development.
+
+COMMAND-LINE OPTIONS
+--------------------
+
+.. option:: --garbage-collection
+
+ Removes pieces of debug information related to discarded sections.
+ When the linker does section garbage collection the abandoned debug info
+ is left behind. Such abandoned debug info references address ranges using
+ tombstone values. Thus, when this option is specified, the tool removes
+ debug info which is marked with the tombstone value.
+
+ That option is enabled by default.
+
+.. option:: --odr-deduplication
+
+ Remove duplicated types (if "One Definition Rule" is supported by source
+ language). Keeps first type definition and removes other definitions,
+ potentially significantly reducing the size of output debug info.
+
+ That option is enabled by default.
+
+.. option:: --help, -h
+
+ Print a summary of command line options.
+
+.. option:: --no-garbage-collection
+
+ Disable :option:`--garbage-collection`.
+
+.. option:: --no-odr-deduplication
+
+ Disable :option:`--odr-deduplication`.
+
+.. option:: --no-separate-debug-file
+
+ Disable :option:`--separate-debug-file`.
+
+.. option:: --num-threads=<n>, -j
+
+ Specifies the maximum number (`n`) of simultaneous threads to use
+ for processing.
+
+.. option:: --separate-debug-file
+
+ Generate separate file containing output debug info. Using
+ :program:`llvm-dwarfutil` with that option equals to the
+ following set of commands:
+
+.. code-block:: console
+
+ :program:`llvm-objcopy` --only-keep-debug in-file out-file.debug
+ :program:`llvm-objcopy` --strip-debug in-file out-file
+ :program:`llvm-objcopy` --add-gnu-debuglink=out-file.debug out-file
+
+.. option:: --tombstone=<value>
+
+ <value> can be one of the following values:
+
+   - `bfd`: zero for all addresses and [1,1] for DWARF v4 (or less) address
+          ranges.
+
+   - `maxpc`: -1 for all addresses and -2 for DWARF v4 (or less) address ranges.
+
+   - `universal`: both `bfd` and `maxpc`.
+
+   - `exec`: match with address ranges of executable sections.
+
+   The value `universal` is used by default.
+
+.. option:: --verbose
+
+ Enable verbose logging. This option disables multi-thread mode.
+
+.. option:: --verify
+
+ Run the DWARF verifier on the output DWARF debug info.
+
+.. option:: --version
+
+ Print the version of this program.
+
+SUPPORTED FORMATS
+-----------------
+
+The following formats are currently supported by :program:`llvm-dwarfutil`:
+
+ELF
+
+EXIT STATUS
+-----------
+
+:program:`llvm-dwarfutil` exits with a non-zero exit code if there is an error.
+Otherwise, it exits with code 0.
+
+BUGS
+----
+
+To report bugs, please visit <https://github.com/llvm/llvm-project/labels/tools:llvm-dwarfutil/>.

diff  --git a/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h b/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h
index 788275782235c..930db0913226a 100644
--- a/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h
+++ b/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h
@@ -182,6 +182,10 @@ class CompileUnit {
   /// offset \p PCOffset.
   void addFunctionRange(uint64_t LowPC, uint64_t HighPC, int64_t PCOffset);
 
+  /// Check whether specified address range \p LowPC \p HighPC
+  /// overlaps with existing function ranges.
+  bool overlapsWithFunctionRanges(uint64_t LowPC, uint64_t HighPC);
+
   /// Keep track of a DW_AT_range attribute that we will need to patch up later.
   void noteRangeAttribute(const DIE &Die, PatchLocation Attr);
 

diff  --git a/llvm/lib/DWARFLinker/DWARFLinker.cpp b/llvm/lib/DWARFLinker/DWARFLinker.cpp
index 298359dea9af5..47cce9ed67778 100644
--- a/llvm/lib/DWARFLinker/DWARFLinker.cpp
+++ b/llvm/lib/DWARFLinker/DWARFLinker.cpp
@@ -505,6 +505,19 @@ unsigned DWARFLinker::shouldKeepSubprogramDIE(
     return Flags;
   }
 
+  // TODO: Following check is a workaround for overlapping address ranges.
+  //       ELF binaries built with LTO might contain overlapping address
+  //       ranges. The better fix would be to combine such ranges. Following
+  //       is a workaround that should be removed when a good fix is done.
+  if (Unit.overlapsWithFunctionRanges(*LowPc, *HighPc)) {
+    reportWarning(
+        formatv("Overlapping address range [{0:X}, {1:X}]. Range will "
+                "be discarded.\n",
+                *LowPc, *HighPc),
+        File, &DIE);
+    return Flags;
+  }
+
   // Replace the debug map range with a more accurate one.
   Ranges[*LowPc] = ObjFileAddressRange(*HighPc, MyInfo.AddrAdjust);
   Unit.addFunctionRange(*LowPc, *HighPc, MyInfo.AddrAdjust);

diff  --git a/llvm/lib/DWARFLinker/DWARFLinkerCompileUnit.cpp b/llvm/lib/DWARFLinker/DWARFLinkerCompileUnit.cpp
index e9e8be7fd0083..ebb1106521cc6 100644
--- a/llvm/lib/DWARFLinker/DWARFLinkerCompileUnit.cpp
+++ b/llvm/lib/DWARFLinker/DWARFLinkerCompileUnit.cpp
@@ -114,6 +114,10 @@ void CompileUnit::addFunctionRange(uint64_t FuncLowPc, uint64_t FuncHighPc,
   this->HighPc = std::max(HighPc, FuncHighPc + PcOffset);
 }
 
+bool CompileUnit::overlapsWithFunctionRanges(uint64_t LowPC, uint64_t HighPC) {
+  return Ranges.overlaps(LowPC, HighPC);
+}
+
 void CompileUnit::noteRangeAttribute(const DIE &Die, PatchLocation Attr) {
   if (Die.getTag() != dwarf::DW_TAG_compile_unit)
     RangeAttributes.push_back(Attr);

diff  --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt
index b94681f54b7a7..86ca20ada7b80 100644
--- a/llvm/test/CMakeLists.txt
+++ b/llvm/test/CMakeLists.txt
@@ -83,6 +83,7 @@ set(LLVM_TEST_DEPENDS
           llvm-dlltool
           dsymutil
           llvm-dwarfdump
+          llvm-dwarfutil
           llvm-dwp
           llvm-exegesis
           llvm-extract

diff  --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py
index 97079af853a56..93db320610dcb 100644
--- a/llvm/test/lit.cfg.py
+++ b/llvm/test/lit.cfg.py
@@ -160,8 +160,8 @@ def get_asan_rtlib():
     'dsymutil', 'lli', 'lli-child-target', 'llvm-ar', 'llvm-as',
     'llvm-addr2line', 'llvm-bcanalyzer', 'llvm-bitcode-strip', 'llvm-config',
     'llvm-cov', 'llvm-cxxdump', 'llvm-cvtres', 'llvm-debuginfod-find', 'llvm-debuginfod',
-    'llvm-
diff ', 'llvm-dis', 'llvm-dwarfdump', 'llvm-dlltool', 'llvm-exegesis',
-    'llvm-extract', 'llvm-isel-fuzzer', 'llvm-ifs',
+    'llvm-
diff ', 'llvm-dis', 'llvm-dwarfdump', 'llvm-dwarfutil', 'llvm-dlltool',
+    'llvm-exegesis', 'llvm-extract', 'llvm-isel-fuzzer', 'llvm-ifs',
     'llvm-install-name-tool', 'llvm-jitlink', 'llvm-opt-fuzzer', 'llvm-lib',
     'llvm-link', 'llvm-lto', 'llvm-lto2', 'llvm-mc', 'llvm-mca',
     'llvm-modextract', 'llvm-nm', 'llvm-objcopy', 'llvm-objdump', 'llvm-otool',

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/Inputs/common.yaml b/llvm/test/tools/llvm-dwarfutil/ELF/Inputs/common.yaml
new file mode 100644
index 0000000000000..0bfe09056ed50
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/Inputs/common.yaml
@@ -0,0 +1,137 @@
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1000
+    Size:            0x1b
+DWARF:
+  debug_abbrev:
+    - Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_template_type_parameter
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_pointer_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_variable
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_const_value
+            Form:      DW_FORM_data4
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+  debug_info:
+    - Version: 4
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - Value:  0x04
+            - CStr: CU1
+            - Value:  0x1000
+            - Value:  0x1b
+        - AbbrCode: 2
+          Values:
+            - CStr: class1
+        - AbbrCode: 3
+          Values:
+            - Value:  0x00000052
+            - CStr: member1
+        - AbbrCode: 3
+          Values:
+            - Value:  0x00000058
+            - CStr: member2
+        - AbbrCode: 0
+        - AbbrCode: 7
+          Values:
+            - CStr: int
+        - AbbrCode: 7
+          Values:
+            - CStr: char
+        - AbbrCode: 7
+          Values:
+            - CStr: float
+        - AbbrCode: 8
+          Values:
+            - Value:  0x0000002a
+        - AbbrCode: 9
+          Values:
+            - CStr: var1
+            - Value:  0x00000000
+            - Value:  0x0000005f
+        - AbbrCode: 10
+          Values:
+            - CStr: foo1
+            - Value:  0x1000
+            - Value:  0x10
+            - Value:  0x0000002a
+        - AbbrCode: 0
+        - AbbrCode: 0
+...

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/copy-itself.test b/llvm/test/tools/llvm-dwarfutil/ELF/copy-itself.test
new file mode 100644
index 0000000000000..c1934f9d14618
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/copy-itself.test
@@ -0,0 +1,37 @@
+## This test checks that the debug info contained in the source file
+## is fully copied to the destination file when the source and
+## destination are the same file.
+
+## Check --no-separate-debug-file.
+# RUN: yaml2obj %p/Inputs/common.yaml -o %t1
+# RUN: llvm-dwarfutil --no-separate-debug-file %t1 %t1
+# RUN: llvm-dwarfdump -a %t1 | FileCheck --check-prefix=CHECK-DEBUG %s
+
+## Check --separate-debug-file.
+# RUN: yaml2obj %p/Inputs/common.yaml -o %t1
+# RUN: llvm-dwarfutil --separate-debug-file %t1 %t1
+# RUN: llvm-objdump --headers %t1 | FileCheck --check-prefix=CHECK-NON-DEBUG %s
+# RUN: llvm-dwarfdump -a %t1.debug | FileCheck --check-prefix=CHECK-DEBUG %s
+
+# CHECK-NON-DEBUG-NOT: .debug_abbrev
+# CHECK-NON-DEBUG-NOT: .debug_info
+# CHECK-NON-DEBUG: .gnu_debuglink
+# CHECK-NON-DEBUG-NOT: .debug_abbrev
+# CHECK-NON-DEBUG-NOT: .debug_info
+
+# CHECK-DEBUG: .debug_abbrev
+# CHECK-DEBUG: DW_TAG_compile_unit
+# CHECK-DEBUG: .debug_info
+# CHECK-DEBUG: DW_TAG_compile_unit
+# CHECK-DEBUG: DW_AT_producer{{.*}}"by_hand"
+# CHECK-DEBUG: DW_AT_language{{.*}}DW_LANG_C_plus_plus
+# CHECK-DEBUG: DW_AT_name{{.*}}"CU1"
+# CHECK-DEBUG: DW_TAG_class_type
+# CHECK-DEBUG: DW_AT_name{{.*}}"class1"
+# CHECK-DEBUG: DW_TAG_base_type
+# CHECK-DEBUG: DW_AT_name{{.*}}"int"
+# CHECK-DEBUG: DW_AT_name{{.*}}"char"
+# CHECK-DEBUG: DW_AT_name{{.*}}"float"
+# CHECK-DEBUG: DW_TAG_pointer_type
+# CHECK-DEBUG: DW_TAG_variable
+# CHECK-DEBUG: DW_AT_name{{.*}}"var1"

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/copy.test b/llvm/test/tools/llvm-dwarfutil/ELF/copy.test
new file mode 100644
index 0000000000000..8385813bbe549
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/copy.test
@@ -0,0 +1,34 @@
+## This test checks that debug info contained in the source file
+## is fully copied to the destination file in case --no-garbage-collection
+## is specified.
+
+# RUN: yaml2obj %p/Inputs/common.yaml -o %t.o
+
+## Check that the resulting file contains debug info from source file.
+# RUN: llvm-dwarfutil --no-garbage-collection %t.o %t1
+# RUN: llvm-dwarfdump -a %t1 | FileCheck %s
+
+## Check that the second copy matches with the first.
+# RUN: llvm-dwarfutil --no-garbage-collection %t1 %t2
+# RUN: cmp %t1 %t2
+
+## Check that input file passed through <stdin> is correctly processesed.
+# RUN: llvm-dwarfutil --no-garbage-collection - %t2 < %t1
+# RUN: cmp %t1 %t2
+
+# CHECK: .debug_abbrev
+# CHECK: DW_TAG_compile_unit
+# CHECK: .debug_info
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_producer{{.*}}"by_hand"
+# CHECK: DW_AT_language{{.*}}DW_LANG_C_plus_plus
+# CHECK: DW_AT_name{{.*}}"CU1"
+# CHECK: DW_TAG_class_type
+# CHECK: DW_AT_name{{.*}}"class1"
+# CHECK: DW_TAG_base_type
+# CHECK: DW_AT_name{{.*}}"int"
+# CHECK: DW_AT_name{{.*}}"char"
+# CHECK: DW_AT_name{{.*}}"float"
+# CHECK: DW_TAG_pointer_type
+# CHECK: DW_TAG_variable
+# CHECK: DW_AT_name{{.*}}"var1"

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/error-separate-file-stdout.test b/llvm/test/tools/llvm-dwarfutil/ELF/error-separate-file-stdout.test
new file mode 100644
index 0000000000000..07492a495a9e3
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/error-separate-file-stdout.test
@@ -0,0 +1,7 @@
+## This test checks the error message displayed if the destination
+## is stdout and separate debug info file is requested.
+
+# RUN: yaml2obj %p/Inputs/common.yaml -o %t.o
+# RUN: not llvm-dwarfutil --separate-debug-file %t.o - 2>&1 | FileCheck --check-prefix CHECK %s
+
+# CHECK: error: unable to write to stdout when --separate-debug-file specified

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/error-unsupported-input-file.test b/llvm/test/tools/llvm-dwarfutil/ELF/error-unsupported-input-file.test
new file mode 100644
index 0000000000000..13b19b5cfe719
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/error-unsupported-input-file.test
@@ -0,0 +1,7 @@
+## This test checks the error message displayed if input file
+## has unsupported format.
+
+# RUN: echo "!<thin>" > %t1
+# RUN: not llvm-dwarfutil --garbage-collection %t1 - 2>&1 | FileCheck %s -DFILE=%t1
+
+# CHECK: error: '[[FILE]]': unsupported input file

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/gc-bfd.test b/llvm/test/tools/llvm-dwarfutil/ELF/gc-bfd.test
new file mode 100644
index 0000000000000..7e61036ae27de
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/gc-bfd.test
@@ -0,0 +1,150 @@
+## This test checks that debug info related to deleted code (marked with
+## tombstone=bfd) is removed.
+
+# RUN: yaml2obj %s -o %t.o
+
+# RUN: llvm-dwarfutil --tombstone=bfd --garbage-collection %t.o - | llvm-dwarfdump -a - | FileCheck %s
+# RUN: llvm-dwarfutil --tombstone=universal --garbage-collection %t.o - | llvm-dwarfdump -a - | FileCheck %s
+
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"CU1"
+# CHECK: DW_TAG_class_type
+# CHECK: DW_AT_name{{.*}}"class1"
+# CHECK-NOT: DW_TAG_class_type
+# CHECK-NOT: "class2"
+# CHECK-NOT: "class3"
+# CHECK: DW_TAG_subprogram
+# CHECK: DW_AT_name{{.*}}"foo1"
+# CHECK: DW_AT_low_pc{{.*}}0x0000000000001000
+# CHECK: DW_AT_high_pc{{.*}}0x0000000000001010
+# CHECK: DW_AT_type{{.*}}"class1"
+# CHECK-NOT: DW_TAG_subprogram
+# CHECK-NOT: "foo2"
+
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1000
+    Size:            0x1b
+DWARF:
+  debug_abbrev:
+    - Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_template_type_parameter
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+  debug_info:
+    - Version: 4
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - Value:  0x04
+            - CStr: CU1
+            - Value:  0x1000
+            - Value:  0x1b
+        - AbbrCode: 3
+          Values:
+            - CStr: class1
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: class2
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: class3
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 8
+          Values:
+            - CStr: int
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value:  0x1000
+            - Value:  0x10
+            - Value:  0x0000002a
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value:  0x0
+            - Value:  0x100
+            - Value:  0x00000040
+        - AbbrCode: 0
+...

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/gc-class.test b/llvm/test/tools/llvm-dwarfutil/ELF/gc-class.test
new file mode 100644
index 0000000000000..92797e972597e
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/gc-class.test
@@ -0,0 +1,344 @@
+## This test checks debug info for the case when one compilation unit
+## contains a definition of a type and another compilation unit
+## contains a definition of the same type. When --garbage-collection
+## is enabled the result should contain the original definition in CU1
+## and the definition from CU2 should have been removed.
+
+# RUN: yaml2obj %s -o %t.o
+
+## Check --odr-deduplication.
+# RUN: llvm-dwarfutil %t.o %t1
+# RUN: llvm-dwarfdump -a %t1 | FileCheck %s --check-prefixes=CHECK,CHECK-ODR
+
+## Check --odr alias.
+# RUN: llvm-dwarfutil --odr %t.o %t2
+# RUN: cmp %t1 %t2
+
+## Check --no-odr alias.
+# RUN: llvm-dwarfutil --no-odr %t.o %t3
+# RUN: llvm-dwarfdump -a %t3 | FileCheck %s --check-prefixes=CHECK,CHECK-NOODR
+
+## Check --no-odr-deduplication wins if last.
+# RUN: llvm-dwarfutil --odr-deduplication --no-odr-deduplication %t.o %t4
+# RUN: cmp %t3 %t4
+
+## Check --odr-deduplication wins if last.
+# RUN: llvm-dwarfutil --no-odr-deduplication --odr-deduplication %t.o %t6
+# RUN: cmp %t1 %t6
+
+## CU1:
+##
+## class class1 {
+##   char member1;
+##   float member2;
+## };
+##
+## class1 *var1;
+##
+## CU2:
+##
+## class class1 {
+##   char member1;
+##   float member2;
+## };
+##
+## class1 *var1;
+
+# CHECK: file format elf64-x86-64
+# CHECK: .debug_info contents:
+
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"CU1"
+
+# CHECK: 0x[[CU1_CLASS1:[0-9a-f]*]]: DW_TAG_class_type{{.*[[:space:]].*}}DW_AT_name{{.*}}"class1"
+# CHECK: DW_TAG_member
+# CHECK-ODR: DW_AT_type{{.*}}0x00000000[[CU1_CHAR:[0-9a-f]*]] "char"
+# CHECK-NOODR: DW_AT_type{{.*}}0x[[CU1_CHAR:[0-9a-f]*]] "char"
+# CHECK: DW_AT_name{{.*}}"member1"
+
+# CHECK: DW_TAG_member
+# CHECK-ODR: DW_AT_type{{.*}}0x00000000[[CU1_FLOAT:[0-9a-f]*]] "float"
+# CHECK-NOODR: DW_AT_type{{.*}}0x[[CU1_FLOAT:[0-9a-f]*]] "float"
+# CHECK: DW_AT_name{{.*}}"member2"
+
+# CHECK: 0x[[CU1_INT:[0-9a-f]*]]: DW_TAG_base_type
+# CHECK: DW_AT_name{{.*}}"int"
+
+# CHECK: 0x[[CU1_CHAR]]: DW_TAG_base_type
+# CHECK: DW_AT_name{{.*}}"char"
+
+# CHECK: 0x[[CU1_FLOAT]]: DW_TAG_base_type
+# CHECK: DW_AT_name{{.*}}"float"
+
+# CHECK-ODR: 0x[[CU1_PTR_CLASS1:[0-9a-f]*]]: DW_TAG_pointer_type{{.*[[:space:]].*}}DW_AT_type{{.*}}0x00000000[[CU1_CLASS1]] "class1"
+# CHECK-NOODR: 0x[[CU1_PTR_CLASS1:[0-9a-f]*]]: DW_TAG_pointer_type{{.*[[:space:]].*}}DW_AT_type{{.*}}0x[[CU1_CLASS1]] "class1"
+
+# CHECK: DW_TAG_variable
+# CHECK: DW_AT_name{{.*}}"var1"
+# CHECK-ODR: DW_AT_type{{.*}}0x00000000[[CU1_PTR_CLASS1]] "class1 *"
+# CHECK-NOODR: DW_AT_type{{.*}}0x[[CU1_PTR_CLASS1]] "class1 *"
+
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"CU2"
+
+# CHECK-ODR-NOT: DW_TAG_class_type
+# CHECK-ODR-NOT: "class1"
+
+# CHECK-ODR: 0x[[CU2_PTR_CLASS1:[0-9a-f]*]]: DW_TAG_pointer_type{{.*[[:space:]].*}}DW_AT_type{{.*}}0x00000000[[CU1_CLASS1]] "class1"
+
+# CHECK-ODR: DW_TAG_variable
+# CHECK-ODR: DW_AT_name{{.*}}"var1"
+# CHECK-ODR: DW_AT_type{{.*}}0x00000000[[CU2_PTR_CLASS1]] "class1 *"
+
+
+# CHECK-NOODR: 0x[[CU2_CLASS1:[0-9a-f]*]]: DW_TAG_class_type{{.*[[:space:]].*}}DW_AT_name{{.*}}"class1"
+# CHECK-NOODR: DW_TAG_member
+# CHECK-NOODR: DW_AT_type{{.*}}0x[[CU2_CHAR:[0-9a-f]*]] "char"
+# CHECK-NOODR: DW_AT_name{{.*}}"member1"
+
+# CHECK-NOODR: DW_TAG_member
+# CHECK-NOODR: DW_AT_type{{.*}}0x[[CU2_FLOAT:[0-9a-f]*]] "float"
+# CHECK-NOODR: DW_AT_name{{.*}}"member2"
+
+# CHECK-NOODR: 0x[[CU2_INT:[0-9a-f]*]]: DW_TAG_base_type
+# CHECK-NOODR: DW_AT_name{{.*}}"int"
+
+# CHECK-NOODR: 0x[[CU2_CHAR]]: DW_TAG_base_type
+# CHECK-NOODR: DW_AT_name{{.*}}"char"
+
+# CHECK-NOODR: 0x[[CU2_FLOAT]]: DW_TAG_base_type
+# CHECK-NOODR: DW_AT_name{{.*}}"float"
+
+# CHECK-NOODR: 0x[[CU2_PTR_CLASS1:[0-9a-f]*]]: DW_TAG_pointer_type{{.*[[:space:]].*}}DW_AT_type{{.*}}0x[[CU2_CLASS1]] "class1"
+
+# CHECK-NOODR: DW_TAG_variable
+# CHECK-NOODR: DW_AT_name{{.*}}"var1"
+# CHECK-NOODR: DW_AT_type{{.*}}0x[[CU2_PTR_CLASS1]] "class1 *"
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1000
+    Size:            0x1b
+  - Name:            .text2
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x2000
+    Size:            0x1b
+DWARF:
+  debug_abbrev:
+    - Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_template_type_parameter
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_pointer_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_variable
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_const_value
+            Form:      DW_FORM_data4
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+    - Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_template_type_parameter
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_pointer_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_variable
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_const_value
+            Form:      DW_FORM_data4
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+  debug_info:
+    - Version: 4
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - Value:  0x04
+            - CStr: CU1
+            - Value:  0x1000
+            - Value:  0x1b
+        - AbbrCode: 2
+          Values:
+            - CStr: class1
+        - AbbrCode: 3
+          Values:
+            - Value:  0x00000052
+            - CStr: member1
+        - AbbrCode: 3
+          Values:
+            - Value:  0x00000058
+            - CStr: member2
+        - AbbrCode: 0
+        - AbbrCode: 7
+          Values:
+            - CStr: int
+        - AbbrCode: 7
+          Values:
+            - CStr: char
+        - AbbrCode: 7
+          Values:
+            - CStr: float
+        - AbbrCode: 8
+          Values:
+            - Value:  0x0000002a
+        - AbbrCode: 9
+          Values:
+            - CStr: var1
+            - Value:  0x00000000
+            - Value:  0x0000005f
+        - AbbrCode: 0
+    - Version: 4
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - Value:  0x04
+            - CStr: CU2
+            - Value:  0x2000
+            - Value:  0x1b
+        - AbbrCode: 2
+          Values:
+            - CStr: class1
+        - AbbrCode: 3
+          Values:
+            - Value:  0x00000052
+            - CStr: member1
+        - AbbrCode: 3
+          Values:
+            - Value:  0x00000058
+            - CStr: member2
+        - AbbrCode: 0
+        - AbbrCode: 7
+          Values:
+            - CStr: int
+        - AbbrCode: 7
+          Values:
+            - CStr: char
+        - AbbrCode: 7
+          Values:
+            - CStr: float
+        - AbbrCode: 8
+          Values:
+            - Value:  0x0000002a
+        - AbbrCode: 9
+          Values:
+            - CStr: var1
+            - Value:  0x00000000
+            - Value:  0x0000005f
+        - AbbrCode: 0
+...

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/gc-default.test b/llvm/test/tools/llvm-dwarfutil/ELF/gc-default.test
new file mode 100644
index 0000000000000..4cac7c5849dbe
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/gc-default.test
@@ -0,0 +1,162 @@
+## This test checks that debug info related to deleted code (marked with
+## default tombstone value) is removed.
+
+# RUN: yaml2obj %s -o %t.o
+
+# RUN: llvm-dwarfutil %t.o - | llvm-dwarfdump -a - | FileCheck %s --check-prefixes=CHECK,CHECK-GC
+
+# RUN: llvm-dwarfutil --garbage-collection %t.o - | llvm-dwarfdump -a - | FileCheck %s --check-prefixes=CHECK,CHECK-GC
+
+# RUN: llvm-dwarfutil --no-garbage-collection --garbage-collection %t.o - | llvm-dwarfdump -a - | FileCheck %s --check-prefixes=CHECK,CHECK-GC
+
+# RUN: llvm-dwarfutil --garbage-collection --no-garbage-collection %t.o - | llvm-dwarfdump -a - | FileCheck %s --check-prefixes=CHECK,CHECK-NOGC
+
+# RUN: llvm-dwarfutil %t.o --tombstone=universal - | llvm-dwarfdump -a - | FileCheck %s --check-prefixes=CHECK,CHECK-GC
+
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"CU1"
+# CHECK: DW_TAG_class_type
+# CHECK: DW_AT_name{{.*}}"class1"
+# CHECK-GC-NOT: DW_TAG_class_type
+# CHECK-GC-NOT: "class2"
+# CHECK-GC-NOT: "class3"
+# CHECK-NOGC: DW_TAG_class_type
+# CHECK-NOGC: "class2"
+# CHECK-NOGC: "class3"
+# CHECK: DW_TAG_subprogram
+# CHECK: DW_AT_name{{.*}}"foo1"
+# CHECK: DW_AT_low_pc{{.*}}0x0000000000001000
+# CHECK: DW_AT_high_pc{{.*}}0x0000000000001010
+# CHECK: DW_AT_type{{.*}}"class1"
+# CHECK-GC-NOT: DW_TAG_subprogram
+# CHECK-GC-NOT: "foo2"
+# CHECK-NOGC: DW_TAG_subprogram
+# CHECK-NOGC: "foo2"
+
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1000
+    Size:            0x1b
+DWARF:
+  debug_abbrev:
+    - Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_template_type_parameter
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+  debug_info:
+    - Version: 4
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - Value:  0x04
+            - CStr: CU1
+            - Value:  0x1000
+            - Value:  0x1b
+        - AbbrCode: 3
+          Values:
+            - CStr: class1
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: class2
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: class3
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 8
+          Values:
+            - CStr: int
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value:  0x1000
+            - Value:  0x10
+            - Value:  0x0000002a
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value:  0x0
+            - Value:  0x100
+            - Value:  0x00000040
+        - AbbrCode: 0
+...

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/gc-exec.test b/llvm/test/tools/llvm-dwarfutil/ELF/gc-exec.test
new file mode 100644
index 0000000000000..e7a6eed1748b1
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/gc-exec.test
@@ -0,0 +1,150 @@
+## This test checks that debug info related to deleted code (marked with
+## tombstone=exec) is removed.
+
+# RUN: yaml2obj %s -o %t.o
+
+# RUN: llvm-dwarfutil --tombstone=exec --garbage-collection %t.o - | llvm-dwarfdump -a - | FileCheck %s
+# RUN: llvm-dwarfutil --tombstone=universal --garbage-collection %t.o - | llvm-dwarfdump -a - | FileCheck %s
+
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"CU1"
+# CHECK: DW_TAG_class_type
+# CHECK: DW_AT_name{{.*}}"class1"
+# CHECK-NOT: DW_TAG_class_type
+# CHECK-NOT: "class2"
+# CHECK-NOT: "class3"
+# CHECK: DW_TAG_subprogram
+# CHECK: DW_AT_name{{.*}}"foo1"
+# CHECK: DW_AT_low_pc{{.*}}0x0000000000001000
+# CHECK: DW_AT_high_pc{{.*}}0x0000000000001010
+# CHECK: DW_AT_type{{.*}}"class1"
+# CHECK-NOT: DW_TAG_subprogram
+# CHECK-NOT: "foo2"
+
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1000
+    Size:            0x1b
+DWARF:
+  debug_abbrev:
+    - Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_template_type_parameter
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+  debug_info:
+    - Version: 4
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - Value:  0x04
+            - CStr: CU1
+            - Value:  0x1000
+            - Value:  0x1b
+        - AbbrCode: 3
+          Values:
+            - CStr: class1
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: class2
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: class3
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 8
+          Values:
+            - CStr: int
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value:  0x1000
+            - Value:  0x10
+            - Value:  0x0000002a
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value:  0x0
+            - Value:  0x100
+            - Value:  0x00000040
+        - AbbrCode: 0
+...

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/gc-maxpc.test b/llvm/test/tools/llvm-dwarfutil/ELF/gc-maxpc.test
new file mode 100644
index 0000000000000..6184f72496129
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/gc-maxpc.test
@@ -0,0 +1,150 @@
+## This test checks that debug info related to deleted code (marked with
+## tombstone=maxpc) is removed.
+
+## RUN: yaml2obj %s -o %t.o
+
+# RUN: llvm-dwarfutil --tombstone=maxpc --garbage-collection %t.o - | llvm-dwarfdump -a - | FileCheck %s
+# RUN: llvm-dwarfutil --tombstone=universal --garbage-collection %t.o - | llvm-dwarfdump -a - | FileCheck %s
+
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"CU1"
+# CHECK: DW_TAG_class_type
+# CHECK: DW_AT_name{{.*}}"class1"
+# CHECK-NOT: DW_TAG_class_type
+# CHECK-NOT: "class2"
+# CHECK-NOT: "class3"
+# CHECK: DW_TAG_subprogram
+# CHECK: DW_AT_name{{.*}}"foo1"
+# CHECK: DW_AT_low_pc{{.*}}0x0000000000001000
+# CHECK: DW_AT_high_pc{{.*}}0x0000000000001010
+# CHECK: DW_AT_type{{.*}}"class1"
+# CHECK-NOT: DW_TAG_subprogram
+# CHECK-NOT: "foo2"
+
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1000
+    Size:            0x1b
+DWARF:
+  debug_abbrev:
+    - Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_template_type_parameter
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+  debug_info:
+    - Version: 4
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - Value:  0x04
+            - CStr: CU1
+            - Value:  0x1000
+            - Value:  0x1b
+        - AbbrCode: 3
+          Values:
+            - CStr: class1
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: class2
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: class3
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 8
+          Values:
+            - CStr: int
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value:  0x1000
+            - Value:  0x10
+            - Value:  0x0000002a
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value:  0xFFFFFFFFFFFFFFFF
+            - Value:  0x100
+            - Value:  0x00000040
+        - AbbrCode: 0
+...

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/gc-no-garbage.test b/llvm/test/tools/llvm-dwarfutil/ELF/gc-no-garbage.test
new file mode 100644
index 0000000000000..11252295617d7
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/gc-no-garbage.test
@@ -0,0 +1,142 @@
+## This test checks that debug info removal optimization (--garbage-collection)
+## does not affect files which do not have dead debug info.
+
+# RUN: yaml2obj %s -o %t.o
+# RUN: llvm-dwarfutil --tombstone=maxpc --garbage-collection %t.o - | llvm-dwarfdump -a - | FileCheck %s
+
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"CU1"
+# CHECK: DW_TAG_class_type
+# CHECK: DW_AT_name{{.*}}"class1"
+# CHECK: DW_TAG_class_type
+# CHECK: "class2"
+# CHECK: DW_TAG_subprogram
+# CHECK: DW_AT_name{{.*}}"foo1"
+# CHECK: DW_AT_low_pc{{.*}}0x0000000000001000
+# CHECK: DW_AT_high_pc{{.*}}0x0000000000001010
+# CHECK: DW_AT_type{{.*}}"class1"
+# CHECK: DW_TAG_subprogram
+# CHECK: "foo2"
+# CHECK: DW_AT_low_pc{{.*}}0x0000000000001010
+# CHECK: DW_AT_high_pc{{.*}}0x000000000000101b
+# CHECK: DW_AT_type{{.*}}"class2"
+
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1000
+    Size:            0x1b
+DWARF:
+  debug_abbrev:
+    - Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_template_type_parameter
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+  debug_info:
+    - Version: 4
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - Value:  0x04
+            - CStr: CU1
+            - Value:  0x1000
+            - Value:  0x1b
+        - AbbrCode: 3
+          Values:
+            - CStr: class1
+        - AbbrCode: 4
+          Values:
+            - Value:  0x00000056
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: class2
+        - AbbrCode: 4
+          Values:
+            - Value:  0x00000056
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 8
+          Values:
+            - CStr: int
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value:  0x1000
+            - Value:  0x10
+            - Value:  0x0000002a
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value:  0x1010
+            - Value:  0xb
+            - Value:  0x00000040
+        - AbbrCode: 0
+...

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/odr-fwd-declaration.test b/llvm/test/tools/llvm-dwarfutil/ELF/odr-fwd-declaration.test
new file mode 100644
index 0000000000000..224d668870b44
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/odr-fwd-declaration.test
@@ -0,0 +1,341 @@
+## This test checks debug info for the case when one compilation unit
+## contains a forward declaration of a type and another compilation unit
+## contains a definition for that type. The result should contain
+## the original definition and the declaration without modifications.
+
+# RUN: yaml2obj %s -o %t.o
+# RUN: llvm-dwarfutil %t.o - | llvm-dwarfdump -a - | FileCheck %s
+
+## CU1:
+##
+## class class1;
+## template<int> class class2;
+##
+## class1 *var1;
+## class2<int> *var2;
+##
+## CU2:
+##
+## class class1 {
+##   char member1;
+##   float member2;
+## };
+##
+## template<int> class class2 {
+##   char member1;
+## };
+##
+## class1 *var1;
+## class2<int> *var2;
+
+# CHECK: file format elf64-x86-64
+# CHECK: .debug_info contents:
+
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"CU1"
+
+# CHECK: 0x[[CU1_CLASS1:[0-9a-f]*]]: DW_TAG_class_type{{.*[[:space:]].*}}DW_AT_name{{.*}}"class1"
+# CHECK: DW_AT_declaration (true)
+
+# CHECK: 0x[[CU1_CLASS2:[0-9a-f]*]]: DW_TAG_class_type{{.*[[:space:]].*}}DW_AT_name{{.*}}"class2"
+# CHECK: DW_AT_declaration (true)
+# CHECK: DW_TAG_template_type_parameter{{.*[[:space:]].*}}DW_AT_type{{.*}}0x00000000[[CU1_INT:[0-9a-f]*]] "int"
+
+# CHECK: 0x[[CU1_INT]]: DW_TAG_base_type
+# CHECK: DW_AT_name{{.*}}"int"
+
+# CHECK: 0x[[CU1_PTR_CLASS1:[0-9a-f]*]]: DW_TAG_pointer_type{{.*[[:space:]].*}}DW_AT_type{{.*}}0x00000000[[CU1_CLASS1]] "class1"
+
+# CHECK: 0x[[CU1_PTR_CLASS2:[0-9a-f]*]]: DW_TAG_pointer_type{{.*[[:space:]].*}}DW_AT_type{{.*}}0x00000000[[CU1_CLASS2]] "class2<int>"
+
+# CHECK: DW_TAG_variable
+# CHECK: DW_AT_name{{.*}}"var1"
+# CHECK: DW_AT_type{{.*}}0x00000000[[CU1_PTR_CLASS1]] "class1 *"
+# CHECK: DW_TAG_variable
+# CHECK: DW_AT_name{{.*}}"var2"
+# CHECK: DW_AT_type{{.*}}0x00000000[[CU1_PTR_CLASS2]] "class2<int> *"
+
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"CU2"
+
+# CHECK: 0x[[CU2_CLASS1:[0-9a-f]*]]: DW_TAG_class_type{{.*[[:space:]].*}}DW_AT_name{{.*}}"class1"
+# CHECK: DW_TAG_member
+# CHECK: DW_AT_type{{.*}}0x00000000[[CU2_CHAR:[0-9a-f]*]] "char"
+# CHECK: DW_AT_name{{.*}}"member1"
+
+# CHECK: DW_TAG_member
+# CHECK: DW_AT_type{{.*}}0x00000000[[CU2_FLOAT:[0-9a-f]*]] "float"
+# CHECK: DW_AT_name{{.*}}"member2"
+
+# CHECK: 0x[[CU2_CLASS2:[0-9a-f]*]]: DW_TAG_class_type{{.*[[:space:]].*}}DW_AT_name{{.*}}"class2"
+# CHECK: DW_TAG_template_type_parameter{{.*[[:space:]].*}}DW_AT_type{{.*}}0x00000000[[CU2_INT:[0-9a-f]*]] "int"
+# CHECK: DW_TAG_member
+# CHECK: DW_AT_type{{.*}}0x00000000[[CU2_CHAR]] "char"
+# CHECK: DW_AT_name{{.*}}"member1"
+
+# CHECK: 0x[[CU2_INT]]: DW_TAG_base_type
+# CHECK: DW_AT_name{{.*}}"int"
+
+# CHECK: 0x[[CU2_CHAR]]: DW_TAG_base_type
+# CHECK: DW_AT_name{{.*}}"char"
+
+# CHECK: 0x[[CU2_FLOAT]]: DW_TAG_base_type
+# CHECK: DW_AT_name{{.*}}"float"
+
+# CHECK: 0x[[CU2_PTR_CLASS1:[0-9a-f]*]]: DW_TAG_pointer_type{{.*[[:space:]].*}}DW_AT_type{{.*}}0x00000000[[CU2_CLASS1]] "class1"
+
+# CHECK: 0x[[CU2_PTR_CLASS2:[0-9a-f]*]]: DW_TAG_pointer_type{{.*[[:space:]].*}}DW_AT_type{{.*}}0x00000000[[CU2_CLASS2]] "class2<int>"
+
+# CHECK: DW_TAG_variable
+# CHECK: DW_AT_name{{.*}}"var1"
+# CHECK: DW_AT_type{{.*}}0x00000000[[CU2_PTR_CLASS1]] "class1 *"
+# CHECK: DW_TAG_variable
+# CHECK: DW_AT_name{{.*}}"var2"
+# CHECK: DW_AT_type{{.*}}0x00000000[[CU2_PTR_CLASS2]] "class2<int> *"
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1000
+    Size:            0x1b
+  - Name:            .text2
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x2000
+    Size:            0x1b
+DWARF:
+  debug_abbrev:
+    - Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref_addr
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_template_type_parameter
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref_addr
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_pointer_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref_addr
+      - Tag:      DW_TAG_variable
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_const_value
+            Form:      DW_FORM_data4
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref_addr
+    - Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref_addr
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_template_type_parameter
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref_addr
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_pointer_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref_addr
+      - Tag:      DW_TAG_variable
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_const_value
+            Form:      DW_FORM_data4
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref_addr
+  debug_info:
+    - Version: 4
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - Value:  0x04
+            - CStr: CU1
+            - Value:  0x1000
+            - Value:  0x1b
+        - AbbrCode: 4
+          Values:
+            - CStr: class1
+        - AbbrCode: 5
+          Values:
+            - CStr: class2
+        - AbbrCode: 6
+          Values:
+            - Value:  0x00000040
+        - AbbrCode: 0
+        - AbbrCode: 7
+          Values:
+            - CStr: int
+        - AbbrCode: 8
+          Values:
+            - Value:  0x0000002a
+        - AbbrCode: 8
+          Values:
+            - Value:  0x00000032
+        - AbbrCode: 9
+          Values:
+            - CStr: var1
+            - Value:  0x00000000
+            - Value:  0x00000045
+        - AbbrCode: 9
+          Values:
+            - CStr: var2
+            - Value:  0x00000000
+            - Value:  0x0000004a
+        - AbbrCode: 0
+    - Version: 4
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - Value:  0x04
+            - CStr: CU2
+            - Value:  0x2000
+            - Value:  0x1b
+        - AbbrCode: 2
+          Values:
+            - CStr: class1
+        - AbbrCode: 3
+          Values:
+            - Value:  0x000000d9
+            - CStr: member1
+        - AbbrCode: 3
+          Values:
+            - Value:  0x000000df
+            - CStr: member2
+        - AbbrCode: 0
+        - AbbrCode: 2
+          Values:
+            - CStr: class2
+        - AbbrCode: 6
+          Values:
+            - Value:  0x000000d4
+        - AbbrCode: 3
+          Values:
+            - Value:  0x000000d9
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 7
+          Values:
+            - CStr: int
+        - AbbrCode: 7
+          Values:
+            - CStr: char
+        - AbbrCode: 7
+          Values:
+            - CStr: float
+        - AbbrCode: 8
+          Values:
+            - Value:  0x00000096
+        - AbbrCode: 8
+          Values:
+            - Value:  0x000000b9
+        - AbbrCode: 9
+          Values:
+            - CStr: var1
+            - Value:  0x00000000
+            - Value:  0x000000e6
+        - AbbrCode: 9
+          Values:
+            - CStr: var2
+            - Value:  0x00000000
+            - Value:  0x000000eb
+        - AbbrCode: 0
+...

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/separate-debug-file.test b/llvm/test/tools/llvm-dwarfutil/ELF/separate-debug-file.test
new file mode 100644
index 0000000000000..d54cb3a182cdc
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/separate-debug-file.test
@@ -0,0 +1,56 @@
+## This test checks the --[no-]separate-debug-file option.
+
+# RUN: yaml2obj %p/Inputs/common.yaml -o %t.o
+
+## Check --garbage-collection --no-separate-debug-file.
+# RUN: llvm-dwarfutil --garbage-collection --no-separate-debug-file %t.o %t2
+# RUN: llvm-dwarfdump -a %t2 | FileCheck --check-prefix=CHECK-DEBUG %s
+
+## Check --garbage-collection --separate-debug-file.
+# RUN: llvm-dwarfutil --garbage-collection --separate-debug-file %t.o %t2
+# RUN: llvm-objdump --headers %t2 | FileCheck --check-prefix=CHECK-NON-DEBUG %s
+# RUN: llvm-dwarfdump -a %t2.debug | FileCheck --check-prefix=CHECK-DEBUG %s
+
+## Check --no-garbage-collection --no-separate-debug-file.
+# RUN: llvm-dwarfutil --no-garbage-collection --no-separate-debug-file %t.o %t1
+# RUN: llvm-dwarfdump -a %t1 | FileCheck --check-prefix=CHECK-DEBUG %s
+
+## Check --no-garbage-collection --separate-debug-file.
+# RUN: llvm-dwarfutil --no-garbage-collection %t.o --separate-debug-file %t3
+# RUN: llvm-objdump --headers %t3 | FileCheck --check-prefix=CHECK-NON-DEBUG %s
+# RUN: llvm-dwarfdump -a %t3.debug | FileCheck --check-prefix=CHECK-DEBUG %s
+## Copy result to compare it later.
+# RUN: cp %t3 %t4
+# RUN: cp %t3.debug %t4.debug
+
+## Check that --separate-debug-file wins if last.
+# RUN: llvm-dwarfutil --no-garbage-collection --no-separate-debug-file --separate-debug-file %t.o %t3
+# RUN: cmp %t4 %t3
+# RUN: cmp %t4.debug %t3.debug
+
+## Check that --no-separate-debug-file wins if last.
+# RUN: llvm-dwarfutil --no-garbage-collection --separate-debug-file --no-separate-debug-file %t.o %t5
+# RUN: cmp %t1 %t5
+
+# CHECK-NON-DEBUG-NOT: .debug_abbrev
+# CHECK-NON-DEBUG-NOT: .debug_info
+# CHECK-NON-DEBUG: .gnu_debuglink
+# CHECK-NON-DEBUG-NOT: .debug_abbrev
+# CHECK-NON-DEBUG-NOT: .debug_info
+
+# CHECK-DEBUG: .debug_abbrev
+# CHECK-DEBUG: DW_TAG_compile_unit
+# CHECK-DEBUG: .debug_info
+# CHECK-DEBUG: DW_TAG_compile_unit
+# CHECK-DEBUG: DW_AT_producer{{.*}}"by_hand"
+# CHECK-DEBUG: DW_AT_language{{.*}}DW_LANG_C_plus_plus
+# CHECK-DEBUG: DW_AT_name{{.*}}"CU1"
+# CHECK-DEBUG: DW_TAG_class_type
+# CHECK-DEBUG: DW_AT_name{{.*}}"class1"
+# CHECK-DEBUG: DW_TAG_base_type
+# CHECK-DEBUG: DW_AT_name{{.*}}"int"
+# CHECK-DEBUG: DW_AT_name{{.*}}"char"
+# CHECK-DEBUG: DW_AT_name{{.*}}"float"
+# CHECK-DEBUG: DW_TAG_pointer_type
+# CHECK-DEBUG: DW_TAG_variable
+# CHECK-DEBUG: DW_AT_name{{.*}}"var1"

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/verbose.test b/llvm/test/tools/llvm-dwarfutil/ELF/verbose.test
new file mode 100644
index 0000000000000..6675d7cd8a3bb
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/verbose.test
@@ -0,0 +1,25 @@
+## This test checks output of llvm-dwarfutil when verbose mode
+## is enabled.
+
+# RUN: yaml2obj %p/Inputs/common.yaml -o %t.o
+
+## Check verbose output.
+# RUN: llvm-dwarfutil %t.o %t1 --verbose 2>&1 | FileCheck %s
+
+## Check warning displayed in multi-thread mode (--num-threads set explicitly).
+# RUN: llvm-dwarfutil %t.o %t1 --verbose --num-threads 10 2>&1 | FileCheck %s --check-prefix=CHECK-THREAD-WARNING
+
+## Check -j alias.
+# RUN: llvm-dwarfutil %t.o %t1 --verbose -j 10 2>&1 | FileCheck %s --check-prefix=CHECK-THREAD-WARNING
+
+## Check verbose output for --verify.
+# RUN: llvm-dwarfutil %t.o %t1 -j 1 --verbose --verify 2>&1 | FileCheck %s --check-prefixes=CHECK,CHECK-VERIFY
+
+# CHECK-NOT: warning: --num-threads set to 1 because verbose mode is specified
+# CHECK: Do garbage collection for debug info ...
+# CHECK: Input compilation unit:
+# CHECK: DW_TAG_compile_unit
+# CHECK: Keeping subprogram DIE
+# CHECK: DW_TAG_subprogram
+# CHECK-THREAD-WARNING: warning: --num-threads set to 1 because verbose mode is specified
+# CHECK-VERIFY: Verifying DWARF...

diff  --git a/llvm/test/tools/llvm-dwarfutil/ELF/verify.test b/llvm/test/tools/llvm-dwarfutil/ELF/verify.test
new file mode 100644
index 0000000000000..844e8fb671af3
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/verify.test
@@ -0,0 +1,151 @@
+## This test checks that debug info contained in the source file is properly
+## verified by --verify after copying. If --no-garbage-collection is
+## specified then the verification should fail, otherwise the verification
+## should succeed.
+
+# RUN: yaml2obj %s -o %t.o
+
+## Verify resulting debug info after --garbage-collection optimisation.
+# RUN: llvm-dwarfutil %t.o %t1 --verify
+
+## Verify separate debug file after --garbage-collection optimisation.
+# RUN: llvm-dwarfutil %t.o --separate-debug-file %t1 --verify
+
+## Verify not optimised resulting debug info.
+# RUN: not llvm-dwarfutil --no-garbage-collection %t.o %t1 --verify 2>&1 | FileCheck %s -DFILE=%t1
+
+## Verify not optimised resulting separate debug file.
+# RUN: not llvm-dwarfutil --no-garbage-collection %t.o --separate-debug-file %t1 --verify 2>&1 | FileCheck %s -DFILE=%t1.debug
+
+## Check that verification is disabled when destination is stdout.
+# RUN: llvm-dwarfutil %t.o - --verify 2>&1 | FileCheck %s --check-prefix=CHECK-STDOUT
+
+# CHECK: error: '[[FILE]]': output verification failed
+# CHECK-STDOUT: warning: verification skipped because writing to stdout
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+DWARF:
+  debug_abbrev:
+    - Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_class_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_declaration
+            Form:      DW_FORM_flag_present
+      - Tag:      DW_TAG_template_type_parameter
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+  debug_info:
+    - Version: 4
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - Value:  0x04
+            - CStr: CU1
+            - Value:  0x1000
+            - Value:  0x100
+        - AbbrCode: 3
+          Values:
+            - CStr: class1
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: class2
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: class3
+        - AbbrCode: 4
+          Values:
+            - Value:  0x0000006c
+            - CStr: member1
+        - AbbrCode: 0
+        - AbbrCode: 8
+          Values:
+            - CStr: int
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value:  0x1000
+            - Value:  0x10
+            - Value:  0x0000002a
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value:  0x0
+            - Value:  0x100
+            - Value:  0x00000040
+        - AbbrCode: 2
+          Values:
+            - CStr: foo3
+            - Value:  0x0
+            - Value:  0x100
+            - Value:  0x00000040
+        - AbbrCode: 0
+...

diff  --git a/llvm/test/tools/llvm-dwarfutil/error-invalid-format.test b/llvm/test/tools/llvm-dwarfutil/error-invalid-format.test
new file mode 100644
index 0000000000000..531cde3c4f2b7
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/error-invalid-format.test
@@ -0,0 +1,6 @@
+## This test checks that llvm-dwarfutil displays an error message
+## if an input file is not valid.
+
+# RUN: not llvm-dwarfutil %s - 2>&1 | FileCheck %s -DFILE=%s
+
+# CHECK: error: '[[FILE]]': The file was not recognized as a valid object file

diff  --git a/llvm/test/tools/llvm-dwarfutil/error-no-gc-odr.test b/llvm/test/tools/llvm-dwarfutil/error-no-gc-odr.test
new file mode 100644
index 0000000000000..d52cca3c71482
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/error-no-gc-odr.test
@@ -0,0 +1,6 @@
+## This test checks the error message displayed if ODR deduplication
+## is enabled while debug info garbage collection is disabled.
+
+# RUN: not llvm-dwarfutil --no-garbage-collection --odr-deduplication - - 2>&1 | FileCheck --check-prefix CHECK %s
+
+# CHECK: error: cannot use --odr-deduplication without --garbage-collection

diff  --git a/llvm/test/tools/llvm-dwarfutil/error-no-input-file.test b/llvm/test/tools/llvm-dwarfutil/error-no-input-file.test
new file mode 100644
index 0000000000000..6d81e7389a5ad
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/error-no-input-file.test
@@ -0,0 +1,6 @@
+## This test checks the error message displayed if
+## an input file does not exist.
+
+# RUN: not llvm-dwarfutil not-existed not-existed 2>&1 | FileCheck %s -DMSG=%errc_ENOENT
+
+# CHECK: error: 'not-existed': [[MSG]]

diff  --git a/llvm/test/tools/llvm-dwarfutil/error-positional-args.test b/llvm/test/tools/llvm-dwarfutil/error-positional-args.test
new file mode 100644
index 0000000000000..b46f94e671da9
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/error-positional-args.test
@@ -0,0 +1,8 @@
+## This test checks the error message displayed if an incorrect
+## number of positional arguments is specified.
+
+# RUN: not llvm-dwarfutil - 2>&1 | FileCheck --check-prefix CHECK1 %s
+# RUN: not llvm-dwarfutil - - - 2>&1 | FileCheck --check-prefix CHECK3 %s
+
+# CHECK1: error: exactly two positional arguments expected, 1 provided
+# CHECK3: error: exactly two positional arguments expected, 3 provided

diff  --git a/llvm/test/tools/llvm-dwarfutil/error-unknown-option.test b/llvm/test/tools/llvm-dwarfutil/error-unknown-option.test
new file mode 100644
index 0000000000000..d25eb1ce60070
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/error-unknown-option.test
@@ -0,0 +1,6 @@
+## This test checks the error message displayed if an unknown
+## option is specified.
+
+# RUN: not llvm-dwarfutil --unknown-option 2>&1 | FileCheck %s
+
+# CHECK: error: unknown option: --unknown-option

diff  --git a/llvm/test/tools/llvm-dwarfutil/error-unknown-tombstone.test b/llvm/test/tools/llvm-dwarfutil/error-unknown-tombstone.test
new file mode 100644
index 0000000000000..afc5a6db6e211
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/error-unknown-tombstone.test
@@ -0,0 +1,6 @@
+## This test checks the error message diplayed if an incorrect
+## tombstone value is specified.
+
+# RUN: not llvm-dwarfutil --tombstone=unknown - - 2>&1 | FileCheck --check-prefix CHECK %s
+
+# CHECK: error: unknown tombstone value: 'unknown'

diff  --git a/llvm/test/tools/llvm-dwarfutil/help.test b/llvm/test/tools/llvm-dwarfutil/help.test
new file mode 100644
index 0000000000000..7fc1cc17682e7
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/help.test
@@ -0,0 +1,25 @@
+## This test checks the help message of llvm-dwarfutil.
+
+# RUN: llvm-dwarfutil | FileCheck %s
+# RUN: llvm-dwarfutil -h | FileCheck %s
+# RUN: llvm-dwarfutil --help | FileCheck %s
+
+# CHECK: OVERVIEW: llvm-dwarfutil is a tool to copy and manipulate debug info
+# CHECK: USAGE: {{.*}}llvm-dwarfutil{{.*}} [options] <input file> <output file>
+# CHECK: OPTIONS:
+# CHECK:   --garbage-collection
+# CHECK:   --help
+# CHECK:   -h
+# CHECK:   -j
+# CHECK:   --no-garbage-collection
+# CHECK:   --no-odr-deduplication
+# CHECK:   --no-odr
+# CHECK:   --no-separate-debug-file
+# CHECK:   --num-threads
+# CHECK:   --odr-deduplication
+# CHECK:   --separate-debug-file
+# CHECK:   --tombstone
+# CHECK:   --verbose
+# CHECK:   --verify
+# CHECK:   --version
+# CHECK:   -V

diff  --git a/llvm/tools/llvm-dwarfutil/CMakeLists.txt b/llvm/tools/llvm-dwarfutil/CMakeLists.txt
new file mode 100644
index 0000000000000..d932ff1eff2d0
--- /dev/null
+++ b/llvm/tools/llvm-dwarfutil/CMakeLists.txt
@@ -0,0 +1,27 @@
+set(LLVM_TARGET_DEFINITIONS Options.td)
+tablegen(LLVM Options.inc -gen-opt-parser-defs)
+add_public_tablegen_target(DwarfutilTableGen)
+
+set(LLVM_LINK_COMPONENTS
+  ${LLVM_TARGETS_TO_BUILD}
+  DebugInfoDWARF
+  DWARFLinker
+  MC
+  ObjCopy
+  Object
+  Option
+  Support
+  Target
+  AllTargetsCodeGens
+  AllTargetsDescs
+  AllTargetsInfos
+  )
+
+add_llvm_tool(llvm-dwarfutil
+  llvm-dwarfutil.cpp
+  DebugInfoLinker.cpp
+
+  DEPENDS
+  intrinsics_gen
+  ${tablegen_deps}
+  )

diff  --git a/llvm/tools/llvm-dwarfutil/DebugInfoLinker.cpp b/llvm/tools/llvm-dwarfutil/DebugInfoLinker.cpp
new file mode 100644
index 0000000000000..ee6bf4fa78120
--- /dev/null
+++ b/llvm/tools/llvm-dwarfutil/DebugInfoLinker.cpp
@@ -0,0 +1,281 @@
+//=== DebugInfoLinker.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
+//
+//===----------------------------------------------------------------------===//
+
+#include "DebugInfoLinker.h"
+#include "Error.h"
+#include "llvm/DWARFLinker/DWARFLinker.h"
+#include "llvm/DWARFLinker/DWARFStreamer.h"
+#include "llvm/DebugInfo/DWARF/DWARFContext.h"
+#include "llvm/DebugInfo/DWARF/DWARFExpression.h"
+#include "llvm/Object/ObjectFile.h"
+#include <memory>
+#include <vector>
+
+namespace llvm {
+namespace dwarfutil {
+
+// ObjFileAddressMap allows to check whether specified DIE referencing
+// dead addresses. It uses tombstone values to determine dead addresses.
+// The concrete values of tombstone constants were discussed in
+// https://reviews.llvm.org/D81784 and https://reviews.llvm.org/D84825.
+// So we use following values as indicators of dead addresses:
+//
+// bfd: (LowPC == 0) or (LowPC == 1 and HighPC == 1 and  DWARF v4 (or less))
+//      or ([LowPC, HighPC] is not inside address ranges of .text sections).
+//
+// maxpc: (LowPC == -1) or (LowPC == -2 and  DWARF v4 (or less))
+//        That value is assumed to be compatible with
+//        http://www.dwarfstd.org/ShowIssue.php?issue=200609.1
+//
+// exec: [LowPC, HighPC] is not inside address ranges of .text sections
+//
+// universal: maxpc and bfd
+class ObjFileAddressMap : public AddressesMap {
+public:
+  ObjFileAddressMap(DWARFContext &Context, const Options &Options,
+                    object::ObjectFile &ObjFile)
+      : Opts(Options) {
+    // Remember addresses of existing text sections.
+    for (const object::SectionRef &Sect : ObjFile.sections()) {
+      if (!Sect.isText())
+        continue;
+      const uint64_t Size = Sect.getSize();
+      if (Size == 0)
+        continue;
+      const uint64_t StartAddr = Sect.getAddress();
+      TextAddressRanges[{StartAddr}] = {StartAddr + Size, 0};
+    }
+
+    // Check CU address ranges for tombstone value.
+    for (std::unique_ptr<DWARFUnit> &CU : Context.compile_units()) {
+      Expected<llvm::DWARFAddressRangesVector> ARanges =
+          CU->getUnitDIE().getAddressRanges();
+      if (ARanges) {
+        for (auto &Range : *ARanges) {
+          if (!isDeadAddressRange(Range.LowPC, Range.HighPC, CU->getVersion(),
+                                  Options.Tombstone, CU->getAddressByteSize()))
+            DWARFAddressRanges[{Range.LowPC}] = {Range.HighPC, 0};
+        }
+      }
+    }
+  }
+
+  // should be renamed into has valid address ranges
+  bool hasValidRelocs() override { return !DWARFAddressRanges.empty(); }
+
+  bool isLiveSubprogram(const DWARFDie &DIE,
+                        CompileUnit::DIEInfo &Info) override {
+    assert((DIE.getTag() == dwarf::DW_TAG_subprogram ||
+            DIE.getTag() == dwarf::DW_TAG_label) &&
+           "Wrong type of input die");
+
+    if (Optional<uint64_t> LowPC =
+            dwarf::toAddress(DIE.find(dwarf::DW_AT_low_pc))) {
+      if (!isDeadAddress(*LowPC, DIE.getDwarfUnit()->getVersion(),
+                         Opts.Tombstone,
+                         DIE.getDwarfUnit()->getAddressByteSize())) {
+        Info.AddrAdjust = 0;
+        Info.InDebugMap = true;
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool isLiveVariable(const DWARFDie &DIE,
+                      CompileUnit::DIEInfo &Info) override {
+    assert((DIE.getTag() == dwarf::DW_TAG_variable ||
+            DIE.getTag() == dwarf::DW_TAG_constant) &&
+           "Wrong type of input die");
+
+    if (Expected<DWARFLocationExpressionsVector> Loc =
+            DIE.getLocations(dwarf::DW_AT_location)) {
+      DWARFUnit *U = DIE.getDwarfUnit();
+      for (const auto &Entry : *Loc) {
+        DataExtractor Data(toStringRef(Entry.Expr),
+                           U->getContext().isLittleEndian(), 0);
+        DWARFExpression Expression(Data, U->getAddressByteSize(),
+                                   U->getFormParams().Format);
+        bool HasLiveAddresses =
+            any_of(Expression, [&](const DWARFExpression::Operation &Op) {
+              // TODO: add handling of dwarf::DW_OP_addrx
+              return !Op.isError() &&
+                     (Op.getCode() == dwarf::DW_OP_addr &&
+                      !isDeadAddress(Op.getRawOperand(0), U->getVersion(),
+                                     Opts.Tombstone,
+                                     DIE.getDwarfUnit()->getAddressByteSize()));
+            });
+
+        if (HasLiveAddresses) {
+          Info.AddrAdjust = 0;
+          Info.InDebugMap = true;
+          return true;
+        }
+      }
+    } else {
+      // FIXME: missing DW_AT_location is OK here, but other errors should be
+      // reported to the user.
+      consumeError(Loc.takeError());
+    }
+
+    return false;
+  }
+
+  bool applyValidRelocs(MutableArrayRef<char>, uint64_t, bool) override {
+    // no need to apply relocations to the linked binary.
+    return false;
+  }
+
+  RangesTy &getValidAddressRanges() override { return DWARFAddressRanges; };
+
+  void clear() override { DWARFAddressRanges.clear(); }
+
+  llvm::Expected<uint64_t> relocateIndexedAddr(uint64_t, uint64_t) override {
+    // should not be called.
+    return object::createError("no relocations in linked binary");
+  }
+
+protected:
+  // returns true if specified address range is inside address ranges
+  // of executable sections.
+  bool isInsideExecutableSectionsAddressRange(uint64_t LowPC,
+                                              Optional<uint64_t> HighPC) {
+    auto Range = TextAddressRanges.lower_bound(LowPC);
+    if ((Range == TextAddressRanges.end() || Range->first != LowPC) &&
+        Range != TextAddressRanges.begin())
+      --Range;
+
+    if (Range != TextAddressRanges.end() && Range->first <= LowPC &&
+        (HighPC ? Range->second.HighPC >= HighPC
+                : Range->second.HighPC >= LowPC))
+      return true;
+
+    return false;
+  }
+
+  uint64_t isBFDDeadAddressRange(uint64_t LowPC, Optional<uint64_t> HighPC,
+                                 uint16_t Version) {
+    if (LowPC == 0)
+      return true;
+
+    if ((Version <= 4) && HighPC && (LowPC == 1 && *HighPC == 1))
+      return true;
+
+    return !isInsideExecutableSectionsAddressRange(LowPC, HighPC);
+  }
+
+  uint64_t isMAXPCDeadAddressRange(uint64_t LowPC, Optional<uint64_t> HighPC,
+                                   uint16_t Version, uint8_t AddressByteSize) {
+    if (Version <= 4 && HighPC) {
+      if (LowPC == (dwarf::computeTombstoneAddress(AddressByteSize) - 1))
+        return true;
+    } else if (LowPC == dwarf::computeTombstoneAddress(AddressByteSize))
+      return true;
+
+    if (!isInsideExecutableSectionsAddressRange(LowPC, HighPC))
+      warning("Address referencing invalid text section is not marked with "
+              "tombstone value");
+
+    return false;
+  }
+
+  bool isDeadAddressRange(uint64_t LowPC, Optional<uint64_t> HighPC,
+                          uint16_t Version, TombstoneKind Tombstone,
+                          uint8_t AddressByteSize) {
+    switch (Tombstone) {
+    case TombstoneKind::BFD:
+      return isBFDDeadAddressRange(LowPC, HighPC, Version);
+    case TombstoneKind::MaxPC:
+      return isMAXPCDeadAddressRange(LowPC, HighPC, Version, AddressByteSize);
+    case TombstoneKind::Universal:
+      return isBFDDeadAddressRange(LowPC, HighPC, Version) ||
+             isMAXPCDeadAddressRange(LowPC, HighPC, Version, AddressByteSize);
+    case TombstoneKind::Exec:
+      return !isInsideExecutableSectionsAddressRange(LowPC, HighPC);
+    }
+
+    llvm_unreachable("Unknown tombstone value");
+  }
+
+  bool isDeadAddress(uint64_t LowPC, uint16_t Version, TombstoneKind Tombstone,
+                     uint8_t AddressByteSize) {
+    return isDeadAddressRange(LowPC, None, Version, Tombstone, AddressByteSize);
+  }
+
+private:
+  RangesTy DWARFAddressRanges;
+  RangesTy TextAddressRanges;
+  const Options &Opts;
+};
+
+bool linkDebugInfo(object::ObjectFile &File, const Options &Options,
+                   raw_pwrite_stream &OutStream) {
+
+  auto ReportWarn = [&](const Twine &Message, StringRef Context,
+                        const DWARFDie *Die) {
+    warning(Message, Context);
+
+    if (!Options.Verbose || !Die)
+      return;
+
+    DIDumpOptions DumpOpts;
+    DumpOpts.ChildRecurseDepth = 0;
+    DumpOpts.Verbose = Options.Verbose;
+
+    WithColor::note() << "    in DIE:\n";
+    Die->dump(errs(), /*Indent=*/6, DumpOpts);
+  };
+  auto ReportErr = [&](const Twine &Message, StringRef Context,
+                       const DWARFDie *) {
+    WithColor::error(errs(), Context) << Message << '\n';
+  };
+
+  // Create output streamer.
+  DwarfStreamer OutStreamer(OutputFileType::Object, OutStream, nullptr,
+                            ReportWarn, ReportWarn);
+  if (!OutStreamer.init(File.makeTriple(), ""))
+    return false;
+
+  // Create DWARF linker.
+  DWARFLinker DebugInfoLinker(&OutStreamer, DwarfLinkerClient::LLD);
+
+  DebugInfoLinker.setEstimatedObjfilesAmount(1);
+  DebugInfoLinker.setAccelTableKind(DwarfLinkerAccelTableKind::None);
+  DebugInfoLinker.setErrorHandler(ReportErr);
+  DebugInfoLinker.setWarningHandler(ReportWarn);
+  DebugInfoLinker.setNumThreads(Options.NumThreads);
+  DebugInfoLinker.setNoODR(!Options.DoODRDeduplication);
+  DebugInfoLinker.setVerbosity(Options.Verbose);
+  DebugInfoLinker.setUpdate(!Options.DoGarbageCollection);
+
+  std::vector<std::unique_ptr<DWARFFile>> ObjectsForLinking(1);
+  std::vector<std::unique_ptr<AddressesMap>> AddresssMapForLinking(1);
+  std::vector<std::string> EmptyWarnings;
+
+  std::unique_ptr<DWARFContext> Context = DWARFContext::create(File);
+
+  // Add object files to the DWARFLinker.
+  AddresssMapForLinking[0] =
+      std::make_unique<ObjFileAddressMap>(*Context, Options, File);
+
+  ObjectsForLinking[0] = std::make_unique<DWARFFile>(
+      File.getFileName(), &*Context, AddresssMapForLinking[0].get(),
+      EmptyWarnings);
+
+  for (size_t I = 0; I < ObjectsForLinking.size(); I++)
+    DebugInfoLinker.addObjectFile(*ObjectsForLinking[I]);
+
+  // Link debug info.
+  DebugInfoLinker.link();
+  OutStreamer.finish();
+  return true;
+}
+
+} // end of namespace dwarfutil
+} // end of namespace llvm

diff  --git a/llvm/tools/llvm-dwarfutil/DebugInfoLinker.h b/llvm/tools/llvm-dwarfutil/DebugInfoLinker.h
new file mode 100644
index 0000000000000..e95c83cb96090
--- /dev/null
+++ b/llvm/tools/llvm-dwarfutil/DebugInfoLinker.h
@@ -0,0 +1,31 @@
+//===- DebugInfoLinker.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 LLVM_TOOLS_LLVM_DWARFUTIL_DEBUGINFOLINKER_H
+#define LLVM_TOOLS_LLVM_DWARFUTIL_DEBUGINFOLINKER_H
+
+#include "Options.h"
+#include "llvm/Object/Archive.h"
+#include "llvm/Object/ELFObjectFile.h"
+#include "llvm/Object/ObjectFile.h"
+
+namespace llvm {
+namespace dwarfutil {
+
+inline bool isDebugSection(StringRef SecName) {
+  return SecName.startswith(".debug") || SecName.startswith(".zdebug") ||
+         SecName == ".gdb_index";
+}
+
+bool linkDebugInfo(object::ObjectFile &file, const Options &Options,
+                   raw_pwrite_stream &OutStream);
+
+} // end of namespace dwarfutil
+} // end of namespace llvm
+
+#endif // LLVM_TOOLS_LLVM_DWARFUTIL_DEBUGINFOLINKER_H

diff  --git a/llvm/tools/llvm-dwarfutil/Error.h b/llvm/tools/llvm-dwarfutil/Error.h
new file mode 100644
index 0000000000000..9ef288d4f6573
--- /dev/null
+++ b/llvm/tools/llvm-dwarfutil/Error.h
@@ -0,0 +1,44 @@
+//===- Error.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 LLVM_TOOLS_LLVM_DWARFUTIL_ERROR_H
+#define LLVM_TOOLS_LLVM_DWARFUTIL_ERROR_H
+
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSet.h"
+#include "llvm/ADT/Triple.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/WithColor.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace llvm {
+namespace dwarfutil {
+
+inline void error(Error Err, StringRef Prefix = "") {
+  handleAllErrors(std::move(Err), [&](ErrorInfoBase &Info) {
+    WithColor::error(errs(), Prefix) << Info.message() << '\n';
+  });
+  std::exit(EXIT_FAILURE);
+}
+
+inline void warning(const Twine &Message, StringRef Prefix = "") {
+  WithColor::warning(errs(), Prefix) << Message << '\n';
+}
+
+inline void verbose(const Twine &Message, bool Verbose) {
+  if (Verbose)
+    outs() << Message << '\n';
+}
+
+} // end of namespace dwarfutil
+} // end of namespace llvm
+
+#endif // LLVM_TOOLS_LLVM_DWARFUTIL_ERROR_H

diff  --git a/llvm/tools/llvm-dwarfutil/Options.h b/llvm/tools/llvm-dwarfutil/Options.h
new file mode 100644
index 0000000000000..c993200ceb4b7
--- /dev/null
+++ b/llvm/tools/llvm-dwarfutil/Options.h
@@ -0,0 +1,46 @@
+//===- Options.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 LLVM_TOOLS_LLVM_DWARFUTIL_OPTIONS_H
+#define LLVM_TOOLS_LLVM_DWARFUTIL_OPTIONS_H
+
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace llvm {
+namespace dwarfutil {
+
+/// The kind of tombstone value.
+enum class TombstoneKind {
+  BFD,       /// 0/[1:1]. Bfd default.
+  MaxPC,     /// -1/-2. Assumed to match with
+             /// http://www.dwarfstd.org/ShowIssue.php?issue=200609.1.
+  Universal, /// both: BFD + MaxPC
+  Exec,      /// match with address range of executable sections.
+};
+
+struct Options {
+  std::string InputFileName;
+  std::string OutputFileName;
+  bool DoGarbageCollection = false;
+  bool DoODRDeduplication = false;
+  bool BuildSeparateDebugFile = false;
+  TombstoneKind Tombstone = TombstoneKind::Universal;
+  bool Verbose = false;
+  int NumThreads = 0;
+  bool Verify = false;
+
+  std::string getSeparateDebugFileName() const {
+    return OutputFileName + ".debug";
+  }
+};
+
+} // namespace dwarfutil
+} // namespace llvm
+
+#endif // LLVM_TOOLS_LLVM_DWARFUTIL_OPTIONS_H

diff  --git a/llvm/tools/llvm-dwarfutil/Options.td b/llvm/tools/llvm-dwarfutil/Options.td
new file mode 100644
index 0000000000000..4ab1b51d808d8
--- /dev/null
+++ b/llvm/tools/llvm-dwarfutil/Options.td
@@ -0,0 +1,65 @@
+include "llvm/Option/OptParser.td"
+
+multiclass BB<string name, string help1, string help2> {
+  def NAME: Flag<["--"], name>, HelpText<help1>;
+  def no_ # NAME: Flag<["--"], "no-" # name>, HelpText<help2>;
+}
+
+def help : Flag<["--"], "help">,
+  HelpText<"Prints this help output">;
+
+def h : Flag<["-"], "h">,
+  Alias<help>,
+  HelpText<"Alias for --help">;
+
+defm odr_deduplication : BB<"odr-deduplication",
+  "Do ODR deduplication for debug types(default)",
+  "Don`t do ODR deduplication for debug types">;
+
+def odr : Flag<["--"], "odr">,
+  Alias<odr_deduplication>,
+  HelpText<"Alias for --odr-deduplication">;
+
+def no_odr : Flag<["--"], "no-odr">,
+  Alias<no_odr_deduplication>,
+  HelpText<"Alias for --no-odr-deduplication">;
+
+defm garbage_collection : BB<"garbage-collection",
+  "Do garbage collection for debug info(default)",
+  "Don`t do garbage collection for debug info">;
+
+defm separate_debug_file : BB<"separate-debug-file",
+  "Create two output files: file w/o debug tables and file with debug tables",
+  "Create single output file, containing debug tables(default)">;
+
+def tombstone: Separate<["--", "-"], "tombstone">,
+  MetaVarName<"[bfd,maxpc,exec,universal]">,
+  HelpText<"Tombstone value used as a marker of invalid address(default: universal)\n"
+  "    =bfd - Zero for all addresses and [1,1] for DWARF v4 (or less) address ranges and exec\n"
+  "    =maxpc - Minus 1 for all addresses and minus 2 for DWARF v4 (or less) address ranges\n"
+  "    =exec - Match with address ranges of executable sections\n"
+  "    =universal - Both: bfd and maxpc"
+  >;
+def: Joined<["--", "-"], "tombstone=">, Alias<tombstone>;
+
+def threads: Separate<["--", "-"], "num-threads">,
+  MetaVarName<"<threads>">,
+  HelpText<"Number of available threads for multi-threaded execution. "
+  "Defaults to the number of cores on the current machine">;
+
+def: Separate<["-"], "j">,
+  Alias<threads>,
+  HelpText<"Alias for --num-threads">;
+
+def verbose : Flag<["--"], "verbose">,
+  HelpText<"Enable verbose logging">;
+
+def verify : Flag<["--"], "verify">,
+  HelpText<"Run the DWARF verifier on the resulting debug info">;
+
+def version : Flag<["--"], "version">,
+  HelpText<"Print the version and exit">;
+
+def V : Flag<["-"], "V">,
+  Alias<version>,
+  HelpText<"Alias for --version">;

diff  --git a/llvm/tools/llvm-dwarfutil/llvm-dwarfutil.cpp b/llvm/tools/llvm-dwarfutil/llvm-dwarfutil.cpp
new file mode 100644
index 0000000000000..e77c82e0fad9a
--- /dev/null
+++ b/llvm/tools/llvm-dwarfutil/llvm-dwarfutil.cpp
@@ -0,0 +1,527 @@
+//=== llvm-dwarfutil.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
+//
+//===----------------------------------------------------------------------===//
+
+#include "DebugInfoLinker.h"
+#include "Error.h"
+#include "Options.h"
+#include "llvm/DebugInfo/DWARF/DWARFContext.h"
+#include "llvm/DebugInfo/DWARF/DWARFVerifier.h"
+#include "llvm/MC/MCTargetOptionsCommandFlags.h"
+#include "llvm/ObjCopy/CommonConfig.h"
+#include "llvm/ObjCopy/ConfigManager.h"
+#include "llvm/ObjCopy/ObjCopy.h"
+#include "llvm/Option/Arg.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/Option.h"
+#include "llvm/Support/CRC.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileUtilities.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/PrettyStackTrace.h"
+#include "llvm/Support/Process.h"
+#include "llvm/Support/Signals.h"
+#include "llvm/Support/TargetSelect.h"
+
+using namespace llvm;
+using namespace object;
+
+namespace {
+enum ID {
+  OPT_INVALID = 0, // This is not an option ID.
+#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
+               HELPTEXT, METAVAR, VALUES)                                      \
+  OPT_##ID,
+#include "Options.inc"
+#undef OPTION
+};
+
+#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE;
+#include "Options.inc"
+#undef PREFIX
+
+const opt::OptTable::Info InfoTable[] = {
+#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
+               HELPTEXT, METAVAR, VALUES)                                      \
+  {                                                                            \
+      PREFIX,      NAME,      HELPTEXT,                                        \
+      METAVAR,     OPT_##ID,  opt::Option::KIND##Class,                        \
+      PARAM,       FLAGS,     OPT_##GROUP,                                     \
+      OPT_##ALIAS, ALIASARGS, VALUES},
+#include "Options.inc"
+#undef OPTION
+};
+
+class DwarfutilOptTable : public opt::OptTable {
+public:
+  DwarfutilOptTable() : OptTable(InfoTable) {}
+};
+} // namespace
+
+namespace llvm {
+namespace dwarfutil {
+
+std::string ToolName;
+
+static mc::RegisterMCTargetOptionsFlags MOF;
+
+static Error validateAndSetOptions(opt::InputArgList &Args, Options &Options) {
+  auto UnknownArgs = Args.filtered(OPT_UNKNOWN);
+  if (!UnknownArgs.empty())
+    return createStringError(
+        std::errc::invalid_argument,
+        formatv("unknown option: {0}", (*UnknownArgs.begin())->getSpelling())
+            .str()
+            .c_str());
+
+  std::vector<std::string> InputFiles = Args.getAllArgValues(OPT_INPUT);
+  if (InputFiles.size() != 2)
+    return createStringError(
+        std::errc::invalid_argument,
+        formatv("exactly two positional arguments expected, {0} provided",
+                InputFiles.size())
+            .str()
+            .c_str());
+
+  Options.InputFileName = InputFiles[0];
+  Options.OutputFileName = InputFiles[1];
+
+  Options.BuildSeparateDebugFile =
+      Args.hasFlag(OPT_separate_debug_file, OPT_no_separate_debug_file, false);
+  Options.DoODRDeduplication =
+      Args.hasFlag(OPT_odr_deduplication, OPT_no_odr_deduplication, true);
+  Options.DoGarbageCollection =
+      Args.hasFlag(OPT_garbage_collection, OPT_no_garbage_collection, true);
+  Options.Verbose = Args.hasArg(OPT_verbose);
+  Options.Verify = Args.hasArg(OPT_verify);
+
+  if (opt::Arg *NumThreads = Args.getLastArg(OPT_threads))
+    Options.NumThreads = atoi(NumThreads->getValue());
+  else
+    Options.NumThreads = 0; // Use all available hardware threads
+
+  if (opt::Arg *Tombstone = Args.getLastArg(OPT_tombstone)) {
+    StringRef S = Tombstone->getValue();
+    if (S == "bfd")
+      Options.Tombstone = TombstoneKind::BFD;
+    else if (S == "maxpc")
+      Options.Tombstone = TombstoneKind::MaxPC;
+    else if (S == "universal")
+      Options.Tombstone = TombstoneKind::Universal;
+    else if (S == "exec")
+      Options.Tombstone = TombstoneKind::Exec;
+    else
+      return createStringError(
+          std::errc::invalid_argument,
+          formatv("unknown tombstone value: '{0}'", S).str().c_str());
+  }
+
+  if (Options.Verbose) {
+    if (Options.NumThreads != 1 && Args.hasArg(OPT_threads))
+      warning("--num-threads set to 1 because verbose mode is specified");
+
+    Options.NumThreads = 1;
+  }
+
+  if (Options.DoODRDeduplication && Args.hasArg(OPT_odr_deduplication) &&
+      !Options.DoGarbageCollection)
+    return createStringError(
+        std::errc::invalid_argument,
+        "cannot use --odr-deduplication without --garbage-collection");
+
+  if (Options.BuildSeparateDebugFile && Options.OutputFileName == "-")
+    return createStringError(
+        std::errc::invalid_argument,
+        "unable to write to stdout when --separate-debug-file specified");
+
+  return Error::success();
+}
+
+static Error setConfigToAddNewDebugSections(objcopy::ConfigManager &Config,
+                                            ObjectFile &ObjFile) {
+  // Add new debug sections.
+  for (SectionRef Sec : ObjFile.sections()) {
+    Expected<StringRef> SecName = Sec.getName();
+    if (!SecName)
+      return SecName.takeError();
+
+    if (isDebugSection(*SecName)) {
+      Expected<StringRef> SecData = Sec.getContents();
+      if (!SecData)
+        return SecData.takeError();
+
+      Config.Common.AddSection.emplace_back(objcopy::NewSectionInfo(
+          *SecName, MemoryBuffer::getMemBuffer(*SecData, *SecName, false)));
+    }
+  }
+
+  return Error::success();
+}
+
+static Error verifyOutput(const Options &Opts) {
+  if (Opts.OutputFileName == "-") {
+    warning("verification skipped because writing to stdout");
+    return Error::success();
+  }
+
+  std::string FileName = Opts.BuildSeparateDebugFile
+                             ? Opts.getSeparateDebugFileName()
+                             : Opts.OutputFileName;
+  Expected<OwningBinary<Binary>> BinOrErr = createBinary(FileName);
+  if (!BinOrErr)
+    return createFileError(FileName, BinOrErr.takeError());
+
+  if (BinOrErr->getBinary()->isObject()) {
+    if (ObjectFile *Obj = static_cast<ObjectFile *>(BinOrErr->getBinary())) {
+      verbose("Verifying DWARF...", Opts.Verbose);
+      std::unique_ptr<DWARFContext> DICtx = DWARFContext::create(*Obj);
+      DIDumpOptions DumpOpts;
+      if (!DICtx->verify(Opts.Verbose ? outs() : nulls(),
+                         DumpOpts.noImplicitRecursion()))
+        return createFileError(FileName,
+                               createError("output verification failed"));
+
+      return Error::success();
+    }
+  }
+
+  // The file "FileName" was created by this utility in the previous steps
+  // (i.e. it is already known that it should pass the isObject check).
+  // If the createBinary() function does not return an error, the isObject
+  // check should also be successful.
+  llvm_unreachable(
+      formatv("tool unexpectedly did not emit a supported object file: '{0}'",
+              FileName)
+          .str()
+          .c_str());
+}
+
+class raw_crc_ostream : public raw_ostream {
+public:
+  explicit raw_crc_ostream(raw_ostream &O) : OS(O) { SetUnbuffered(); }
+
+  void reserveExtraSpace(uint64_t ExtraSize) override {
+    OS.reserveExtraSpace(ExtraSize);
+  }
+
+  uint32_t getCRC32() { return CRC32; }
+
+protected:
+  raw_ostream &OS;
+  uint32_t CRC32 = 0;
+
+  /// See raw_ostream::write_impl.
+  void write_impl(const char *Ptr, size_t Size) override {
+    CRC32 = crc32(
+        CRC32, ArrayRef<uint8_t>(reinterpret_cast<const uint8_t *>(Ptr), Size));
+    OS.write(Ptr, Size);
+  }
+
+  /// Return the current position within the stream, not counting the bytes
+  /// currently in the buffer.
+  uint64_t current_pos() const override { return OS.tell(); }
+};
+
+static Expected<uint32_t> saveSeparateDebugInfo(const Options &Opts,
+                                                ObjectFile &InputFile) {
+  objcopy::ConfigManager Config;
+  std::string OutputFilename = Opts.getSeparateDebugFileName();
+  Config.Common.InputFilename = Opts.InputFileName;
+  Config.Common.OutputFilename = OutputFilename;
+  Config.Common.OnlyKeepDebug = true;
+  uint32_t WrittenFileCRC32 = 0;
+
+  if (Error Err = writeToOutput(
+          Config.Common.OutputFilename, [&](raw_ostream &OutFile) -> Error {
+            raw_crc_ostream CRCBuffer(OutFile);
+            if (Error Err = objcopy::executeObjcopyOnBinary(Config, InputFile,
+                                                            CRCBuffer))
+              return Err;
+
+            WrittenFileCRC32 = CRCBuffer.getCRC32();
+            return Error::success();
+          }))
+    return std::move(Err);
+
+  return WrittenFileCRC32;
+}
+
+static Error saveNonDebugInfo(const Options &Opts, ObjectFile &InputFile,
+                              uint32_t GnuDebugLinkCRC32) {
+  objcopy::ConfigManager Config;
+  Config.Common.InputFilename = Opts.InputFileName;
+  Config.Common.OutputFilename = Opts.OutputFileName;
+  Config.Common.StripDebug = true;
+  std::string SeparateDebugFileName = Opts.getSeparateDebugFileName();
+  Config.Common.AddGnuDebugLink = sys::path::filename(SeparateDebugFileName);
+  Config.Common.GnuDebugLinkCRC32 = GnuDebugLinkCRC32;
+
+  if (Error Err = writeToOutput(
+          Config.Common.OutputFilename, [&](raw_ostream &OutFile) -> Error {
+            if (Error Err =
+                    objcopy::executeObjcopyOnBinary(Config, InputFile, OutFile))
+              return Err;
+
+            return Error::success();
+          }))
+    return Err;
+
+  return Error::success();
+}
+
+static Error splitDebugIntoSeparateFile(const Options &Opts,
+                                        ObjectFile &InputFile) {
+  Expected<uint32_t> SeparateDebugFileCRC32OrErr =
+      saveSeparateDebugInfo(Opts, InputFile);
+  if (!SeparateDebugFileCRC32OrErr)
+    return SeparateDebugFileCRC32OrErr.takeError();
+
+  if (Error Err =
+          saveNonDebugInfo(Opts, InputFile, *SeparateDebugFileCRC32OrErr))
+    return Err;
+
+  return Error::success();
+}
+
+using DebugInfoBits = SmallString<10000>;
+
+static Error addSectionsFromLinkedData(objcopy::ConfigManager &Config,
+                                       ObjectFile &InputFile,
+                                       DebugInfoBits &LinkedDebugInfoBits) {
+  if (dyn_cast<ELFObjectFile<ELF32LE>>(&InputFile)) {
+    Expected<ELFObjectFile<ELF32LE>> MemFile = ELFObjectFile<ELF32LE>::create(
+        MemoryBufferRef(LinkedDebugInfoBits, ""));
+    if (!MemFile)
+      return MemFile.takeError();
+
+    if (Error Err = setConfigToAddNewDebugSections(Config, *MemFile))
+      return Err;
+  } else if (dyn_cast<ELFObjectFile<ELF64LE>>(&InputFile)) {
+    Expected<ELFObjectFile<ELF64LE>> MemFile = ELFObjectFile<ELF64LE>::create(
+        MemoryBufferRef(LinkedDebugInfoBits, ""));
+    if (!MemFile)
+      return MemFile.takeError();
+
+    if (Error Err = setConfigToAddNewDebugSections(Config, *MemFile))
+      return Err;
+  } else if (dyn_cast<ELFObjectFile<ELF32BE>>(&InputFile)) {
+    Expected<ELFObjectFile<ELF32BE>> MemFile = ELFObjectFile<ELF32BE>::create(
+        MemoryBufferRef(LinkedDebugInfoBits, ""));
+    if (!MemFile)
+      return MemFile.takeError();
+
+    if (Error Err = setConfigToAddNewDebugSections(Config, *MemFile))
+      return Err;
+  } else if (dyn_cast<ELFObjectFile<ELF64BE>>(&InputFile)) {
+    Expected<ELFObjectFile<ELF64BE>> MemFile = ELFObjectFile<ELF64BE>::create(
+        MemoryBufferRef(LinkedDebugInfoBits, ""));
+    if (!MemFile)
+      return MemFile.takeError();
+
+    if (Error Err = setConfigToAddNewDebugSections(Config, *MemFile))
+      return Err;
+  } else
+    return createStringError(std::errc::invalid_argument,
+                             "unsupported file format");
+
+  return Error::success();
+}
+
+static Expected<uint32_t>
+saveSeparateLinkedDebugInfo(const Options &Opts, ObjectFile &InputFile,
+                            DebugInfoBits LinkedDebugInfoBits) {
+  objcopy::ConfigManager Config;
+  std::string OutputFilename = Opts.getSeparateDebugFileName();
+  Config.Common.InputFilename = Opts.InputFileName;
+  Config.Common.OutputFilename = OutputFilename;
+  Config.Common.StripDebug = true;
+  Config.Common.OnlyKeepDebug = true;
+  uint32_t WrittenFileCRC32 = 0;
+
+  if (Error Err =
+          addSectionsFromLinkedData(Config, InputFile, LinkedDebugInfoBits))
+    return std::move(Err);
+
+  if (Error Err = writeToOutput(
+          Config.Common.OutputFilename, [&](raw_ostream &OutFile) -> Error {
+            raw_crc_ostream CRCBuffer(OutFile);
+
+            if (Error Err = objcopy::executeObjcopyOnBinary(Config, InputFile,
+                                                            CRCBuffer))
+              return Err;
+
+            WrittenFileCRC32 = CRCBuffer.getCRC32();
+            return Error::success();
+          }))
+    return std::move(Err);
+
+  return WrittenFileCRC32;
+}
+
+static Error saveSingleLinkedDebugInfo(const Options &Opts,
+                                       ObjectFile &InputFile,
+                                       DebugInfoBits LinkedDebugInfoBits) {
+  objcopy::ConfigManager Config;
+
+  Config.Common.InputFilename = Opts.InputFileName;
+  Config.Common.OutputFilename = Opts.OutputFileName;
+  Config.Common.StripDebug = true;
+  if (Error Err =
+          addSectionsFromLinkedData(Config, InputFile, LinkedDebugInfoBits))
+    return Err;
+
+  if (Error Err = writeToOutput(
+          Config.Common.OutputFilename, [&](raw_ostream &OutFile) -> Error {
+            return objcopy::executeObjcopyOnBinary(Config, InputFile, OutFile);
+          }))
+    return Err;
+
+  return Error::success();
+}
+
+static Error saveLinkedDebugInfo(const Options &Opts, ObjectFile &InputFile,
+                                 DebugInfoBits LinkedDebugInfoBits) {
+  if (Opts.BuildSeparateDebugFile) {
+    Expected<uint32_t> SeparateDebugFileCRC32OrErr =
+        saveSeparateLinkedDebugInfo(Opts, InputFile,
+                                    std::move(LinkedDebugInfoBits));
+    if (!SeparateDebugFileCRC32OrErr)
+      return SeparateDebugFileCRC32OrErr.takeError();
+
+    if (Error Err =
+            saveNonDebugInfo(Opts, InputFile, *SeparateDebugFileCRC32OrErr))
+      return Err;
+  } else {
+    if (Error Err = saveSingleLinkedDebugInfo(Opts, InputFile,
+                                              std::move(LinkedDebugInfoBits)))
+      return Err;
+  }
+
+  return Error::success();
+}
+
+static Error saveCopyOfFile(const Options &Opts, ObjectFile &InputFile) {
+  objcopy::ConfigManager Config;
+
+  Config.Common.InputFilename = Opts.InputFileName;
+  Config.Common.OutputFilename = Opts.OutputFileName;
+
+  if (Error Err = writeToOutput(
+          Config.Common.OutputFilename, [&](raw_ostream &OutFile) -> Error {
+            return objcopy::executeObjcopyOnBinary(Config, InputFile, OutFile);
+          }))
+    return Err;
+
+  return Error::success();
+}
+
+static Error applyCLOptions(const struct Options &Opts, ObjectFile &InputFile) {
+  if (Opts.DoGarbageCollection) {
+    verbose("Do garbage collection for debug info ...", Opts.Verbose);
+
+    DebugInfoBits LinkedDebugInfo;
+    raw_svector_ostream OutStream(LinkedDebugInfo);
+
+    if (linkDebugInfo(InputFile, Opts, OutStream)) {
+      if (Error Err =
+              saveLinkedDebugInfo(Opts, InputFile, std::move(LinkedDebugInfo)))
+        return Err;
+
+      return Error::success();
+    }
+
+    return createStringError(std::errc::invalid_argument,
+                             "possible broken debug info");
+  } else if (Opts.BuildSeparateDebugFile) {
+    if (Error Err = splitDebugIntoSeparateFile(Opts, InputFile))
+      return Err;
+  } else {
+    if (Error Err = saveCopyOfFile(Opts, InputFile))
+      return Err;
+  }
+
+  return Error::success();
+}
+
+} // end of namespace dwarfutil
+} // end of namespace llvm
+
+int main(int Argc, char const *Argv[]) {
+  using namespace dwarfutil;
+
+  InitLLVM X(Argc, Argv);
+  ToolName = Argv[0];
+
+  // Parse arguments.
+  DwarfutilOptTable T;
+  unsigned MAI;
+  unsigned MAC;
+  ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, Argc - 1);
+  opt::InputArgList Args = T.ParseArgs(ArgsArr, MAI, MAC);
+
+  if (Args.hasArg(OPT_help) || Args.size() == 0) {
+    T.printHelp(
+        outs(), (ToolName + " [options] <input file> <output file>").c_str(),
+        "llvm-dwarfutil is a tool to copy and manipulate debug info", false);
+    return EXIT_SUCCESS;
+  }
+
+  if (Args.hasArg(OPT_version)) {
+    cl::PrintVersionMessage();
+    return EXIT_SUCCESS;
+  }
+
+  Options Opts;
+  if (Error Err = validateAndSetOptions(Args, Opts))
+    error(std::move(Err), dwarfutil::ToolName);
+
+  InitializeAllTargets();
+  InitializeAllTargetMCs();
+  InitializeAllTargetInfos();
+  InitializeAllAsmPrinters();
+  InitializeAllAsmParsers();
+
+  ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =
+      MemoryBuffer::getFileOrSTDIN(Opts.InputFileName);
+  if (BuffOrErr.getError())
+    error(createFileError(Opts.InputFileName, BuffOrErr.getError()));
+
+  Expected<std::unique_ptr<Binary>> BinOrErr =
+      object::createBinary(**BuffOrErr);
+  if (!BinOrErr)
+    error(createFileError(Opts.InputFileName, BinOrErr.takeError()));
+
+  Expected<FilePermissionsApplier> PermsApplierOrErr =
+      FilePermissionsApplier::create(Opts.InputFileName);
+  if (!PermsApplierOrErr)
+    error(createFileError(Opts.InputFileName, PermsApplierOrErr.takeError()));
+
+  if (!(*BinOrErr)->isObject())
+    error(createFileError(Opts.InputFileName,
+                          createError("unsupported input file")));
+
+  if (Error Err =
+          applyCLOptions(Opts, *static_cast<ObjectFile *>((*BinOrErr).get())))
+    error(createFileError(Opts.InputFileName, std::move(Err)));
+
+  BinOrErr->reset();
+  BuffOrErr->reset();
+
+  if (Error Err = PermsApplierOrErr->apply(Opts.OutputFileName))
+    error(std::move(Err));
+
+  if (Opts.BuildSeparateDebugFile)
+    if (Error Err = PermsApplierOrErr->apply(Opts.getSeparateDebugFileName()))
+      error(std::move(Err));
+
+  if (Opts.Verify) {
+    if (Error Err = verifyOutput(Opts))
+      error(std::move(Err));
+  }
+
+  return EXIT_SUCCESS;
+}


        


More information about the llvm-commits mailing list