diff --git a/llvm/docs/CommandGuide/llvm-dwarfutil.rst b/llvm/docs/CommandGuide/llvm-dwarfutil.rst new file mode 100644 --- /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=, -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= + + 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 . diff --git a/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h b/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h --- a/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h +++ b/llvm/include/llvm/DWARFLinker/DWARFLinkerCompileUnit.h @@ -182,6 +182,10 @@ /// 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 --- a/llvm/lib/DWARFLinker/DWARFLinker.cpp +++ b/llvm/lib/DWARFLinker/DWARFLinker.cpp @@ -505,6 +505,19 @@ 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 --- a/llvm/lib/DWARFLinker/DWARFLinkerCompileUnit.cpp +++ b/llvm/lib/DWARFLinker/DWARFLinkerCompileUnit.cpp @@ -114,6 +114,10 @@ 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 --- a/llvm/test/CMakeLists.txt +++ b/llvm/test/CMakeLists.txt @@ -83,6 +83,7 @@ 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 --- a/llvm/test/lit.cfg.py +++ b/llvm/test/lit.cfg.py @@ -160,8 +160,8 @@ '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 --- /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 --- /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 --- /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 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 --- /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 --- /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 "!" > %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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 class class2; +## +## class1 *var1; +## class2 *var2; +## +## CU2: +## +## class class1 { +## char member1; +## float member2; +## }; +## +## template class class2 { +## char member1; +## }; +## +## class1 *var1; +## class2 *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" + +# 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 *" + +# 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" + +# 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 *" + +--- !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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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] +# 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 --- /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.h b/llvm/tools/llvm-dwarfutil/DebugInfoLinker.h new file mode 100644 --- /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/DebugInfoLinker.cpp b/llvm/tools/llvm-dwarfutil/DebugInfoLinker.cpp new file mode 100644 --- /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 +#include + +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 &CU : Context.compile_units()) { + Expected 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 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 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, 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 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 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 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 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 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> ObjectsForLinking(1); + std::vector> AddresssMapForLinking(1); + std::vector EmptyWarnings; + + std::unique_ptr Context = DWARFContext::create(File); + + // Add object files to the DWARFLinker. + AddresssMapForLinking[0] = + std::make_unique(*Context, Options, File); + + ObjectsForLinking[0] = std::make_unique( + 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/Error.h b/llvm/tools/llvm-dwarfutil/Error.h new file mode 100644 --- /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 --- /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 --- /dev/null +++ b/llvm/tools/llvm-dwarfutil/Options.td @@ -0,0 +1,65 @@ +include "llvm/Option/OptParser.td" + +multiclass BB { + def NAME: Flag<["--"], name>, HelpText; + def no_ # NAME: Flag<["--"], "no-" # name>, HelpText; +} + +def help : Flag<["--"], "help">, + HelpText<"Prints this help output">; + +def h : Flag<["-"], "h">, + Alias, + 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, + HelpText<"Alias for --odr-deduplication">; + +def no_odr : Flag<["--"], "no-odr">, + Alias, + 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; + +def threads: Separate<["--", "-"], "num-threads">, + MetaVarName<"">, + HelpText<"Number of available threads for multi-threaded execution. " + "Defaults to the number of cores on the current machine">; + +def: Separate<["-"], "j">, + Alias, + 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, + 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 --- /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 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 SecName = Sec.getName(); + if (!SecName) + return SecName.takeError(); + + if (isDebugSection(*SecName)) { + Expected 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> BinOrErr = createBinary(FileName); + if (!BinOrErr) + return createFileError(FileName, BinOrErr.takeError()); + + if (BinOrErr->getBinary()->isObject()) { + if (ObjectFile *Obj = static_cast(BinOrErr->getBinary())) { + verbose("Verifying DWARF...", Opts.Verbose); + std::unique_ptr 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(reinterpret_cast(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 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 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>(&InputFile)) { + Expected> MemFile = ELFObjectFile::create( + MemoryBufferRef(LinkedDebugInfoBits, "")); + if (!MemFile) + return MemFile.takeError(); + + if (Error Err = setConfigToAddNewDebugSections(Config, *MemFile)) + return Err; + } else if (dyn_cast>(&InputFile)) { + Expected> MemFile = ELFObjectFile::create( + MemoryBufferRef(LinkedDebugInfoBits, "")); + if (!MemFile) + return MemFile.takeError(); + + if (Error Err = setConfigToAddNewDebugSections(Config, *MemFile)) + return Err; + } else if (dyn_cast>(&InputFile)) { + Expected> MemFile = ELFObjectFile::create( + MemoryBufferRef(LinkedDebugInfoBits, "")); + if (!MemFile) + return MemFile.takeError(); + + if (Error Err = setConfigToAddNewDebugSections(Config, *MemFile)) + return Err; + } else if (dyn_cast>(&InputFile)) { + Expected> MemFile = ELFObjectFile::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 +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 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 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] ").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> BuffOrErr = + MemoryBuffer::getFileOrSTDIN(Opts.InputFileName); + if (BuffOrErr.getError()) + error(createFileError(Opts.InputFileName, BuffOrErr.getError())); + + Expected> BinOrErr = + object::createBinary(**BuffOrErr); + if (!BinOrErr) + error(createFileError(Opts.InputFileName, BinOrErr.takeError())); + + Expected 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((*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; +}