Standalone debug increase the debug content of single object files by producing debug information on types that are only seen in that particular translation unit via pointers/references. This often makes the object file debuggable in isolation, as opposed to as part of a larger binary compiled with the same flags.
-fstandalone-debug working as intended with non-templated types
byref.cpp:
struct Foo { int a; };
void bar(const Foo&) {}
without -fstandalone-debug:
$ clang++ -g -c byref.cpp
$ llvm-dwarfdump byref.o
byref.o: file format elf64-x86-64
.debug_info contents:
0x00000000: Compile Unit: length = 0x00000043, format = DWARF32, version = 0x0005, unit_type = DW_UT_compile, abbr_offset = 0x0000, addr_size = 0x08 (next unit at 0x00000047)
0x0000000c: DW_TAG_compile_unit
DW_AT_producer ("clang version 18.0.0 (https://github.com/llvm/llvm-project.git 5bae3a0b0ccf8f4f2bcffc86453197f3cc5a9829)")
DW_AT_language (DW_LANG_C_plus_plus_14)
DW_AT_name ("byref.cpp")
DW_AT_str_offsets_base (0x00000008)
DW_AT_stmt_list (0x00000000)
DW_AT_comp_dir ("/tmp/test-debug")
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_addr_base (0x00000008)
0x00000023: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_frame_base (DW_OP_reg6 RBP)
DW_AT_linkage_name ("_Z3barRK3Foo")
DW_AT_name ("bar")
DW_AT_decl_file ("/tmp/test-debug/byref.cpp")
DW_AT_decl_line (2)
DW_AT_external (true)
0x0000002f: DW_TAG_formal_parameter
DW_AT_location (DW_OP_fbreg -8)
DW_AT_decl_file ("/tmp/test-debug/byref.cpp")
DW_AT_decl_line (2)
DW_AT_type (0x0000003a "const Foo &")
0x00000039: NULL
0x0000003a: DW_TAG_reference_type
DW_AT_type (0x0000003f "const Foo")
0x0000003f: DW_TAG_const_type
DW_AT_type (0x00000044 "Foo")
0x00000044: DW_TAG_structure_type
DW_AT_name ("Foo")
DW_AT_declaration (true)
0x00000046: NULL
with -fstandalone-debug:
$ clang++ -g -fstandalone-debug -c byref.cpp
$ llvm-dwarfdump byref.o
byref.o: file format elf64-x86-64
.debug_info contents:
0x00000000: Compile Unit: length = 0x00000055, format = DWARF32, version = 0x0005, unit_type = DW_UT_compile, abbr_offset = 0x0000, addr_size = 0x08 (next unit at 0x00000059)
0x0000000c: DW_TAG_compile_unit
DW_AT_producer ("clang version 18.0.0 (https://github.com/llvm/llvm-project.git 5bae3a0b0ccf8f4f2bcffc86453197f3cc5a9829)")
DW_AT_language (DW_LANG_C_plus_plus_14)
DW_AT_name ("byref.cpp")
DW_AT_str_offsets_base (0x00000008)
DW_AT_stmt_list (0x00000000)
DW_AT_comp_dir ("/tmp/test-debug")
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_addr_base (0x00000008)
0x00000023: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_frame_base (DW_OP_reg6 RBP)
DW_AT_linkage_name ("_Z3barRK3Foo")
DW_AT_name ("bar")
DW_AT_decl_file ("/tmp/test-debug/byref.cpp")
DW_AT_decl_line (2)
DW_AT_external (true)
0x0000002f: DW_TAG_formal_parameter
DW_AT_location (DW_OP_fbreg -8)
DW_AT_decl_file ("/tmp/test-debug/byref.cpp")
DW_AT_decl_line (2)
DW_AT_type (0x0000003a "const Foo &")
0x00000039: NULL
0x0000003a: DW_TAG_reference_type
DW_AT_type (0x0000003f "const Foo")
0x0000003f: DW_TAG_const_type
DW_AT_type (0x00000044 "Foo")
0x00000044: DW_TAG_structure_type
DW_AT_calling_convention (DW_CC_pass_by_value)
DW_AT_name ("Foo")
DW_AT_byte_size (0x04)
DW_AT_decl_file ("/tmp/test-debug/byref.cpp")
DW_AT_decl_line (1)
0x0000004a: DW_TAG_member
DW_AT_name ("a")
DW_AT_type (0x00000054 "int")
DW_AT_decl_file ("/tmp/test-debug/byref.cpp")
DW_AT_decl_line (1)
DW_AT_data_member_location (0x00)
0x00000053: NULL
0x00000054: DW_TAG_base_type
DW_AT_name ("int")
DW_AT_encoding (DW_ATE_signed)
DW_AT_byte_size (0x04)
0x00000058: NULL
However, this does not provide a fully debuggable object file in many cases if the types are templated.
No type information emitted when a templated type is passed into a function by reference
template <typename T>
struct Foo { T a; };
void bar(const Foo<int>&) {}
$ clang++ -g -fstandalone-debug -c byref_templated.cpp
$ llvm-dwarfdump byref_templated.o
byref_templated.o: file format elf64-x86-64
.debug_info contents:
0x00000000: Compile Unit: length = 0x00000043, format = DWARF32, version = 0x0005, unit_type = DW_UT_compile, abbr_offset = 0x0000, addr_size = 0x08 (next unit at 0x00000047)
0x0000000c: DW_TAG_compile_unit
DW_AT_producer ("clang version 18.0.0 (https://github.com/llvm/llvm-project.git 5bae3a0b0ccf8f4f2bcffc86453197f3cc5a9829)")
DW_AT_language (DW_LANG_C_plus_plus_14)
DW_AT_name ("byref_templated.cpp")
DW_AT_str_offsets_base (0x00000008)
DW_AT_stmt_list (0x00000000)
DW_AT_comp_dir ("/tmp/test-debug")
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_addr_base (0x00000008)
0x00000023: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_frame_base (DW_OP_reg6 RBP)
DW_AT_linkage_name ("_Z3barRK3FooIiE")
DW_AT_name ("bar")
DW_AT_decl_file ("/tmp/test-debug/byref_templated.cpp")
DW_AT_decl_line (3)
DW_AT_external (true)
0x0000002f: DW_TAG_formal_parameter
DW_AT_location (DW_OP_fbreg -8)
DW_AT_decl_file ("/tmp/test-debug/byref_templated.cpp")
DW_AT_decl_line (3)
DW_AT_type (0x0000003a "const Foo<int> &")
0x00000039: NULL
0x0000003a: DW_TAG_reference_type
DW_AT_type (0x0000003f "const Foo<int>")
0x0000003f: DW_TAG_const_type
DW_AT_type (0x00000044 "Foo<int>")
0x00000044: DW_TAG_structure_type
DW_AT_name ("Foo<int>")
DW_AT_declaration (true)
0x00000046: NULL
Full type information emitted when a templated type is passed into a function by value and holds a pointer to a non-templated value
template <typename T>
struct PointerHolder { T* ptr; };
void foo(PointerHolder<int>) {}
$ clang++ -g -fstandalone-debug -c pointer_holder_int.cpp
$ llvm-dwarfdump pointer_holder_int.o
pointer_holder_int.o: file format elf64-x86-64
.debug_info contents:
0x00000000: Compile Unit: length = 0x00000056, format = DWARF32, version = 0x0005, unit_type = DW_UT_compile, abbr_offset = 0x0000, addr_size = 0x08 (next unit at 0x0000005a)
0x0000000c: DW_TAG_compile_unit
DW_AT_producer ("clang version 18.0.0 (https://github.com/llvm/llvm-project.git 5bae3a0b0ccf8f4f2bcffc86453197f3cc5a9829)")
DW_AT_language (DW_LANG_C_plus_plus_14)
DW_AT_name ("pointer_holder_int.cpp")
DW_AT_str_offsets_base (0x00000008)
DW_AT_stmt_list (0x00000000)
DW_AT_comp_dir ("/tmp/test-debug")
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_addr_base (0x00000008)
0x00000023: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_frame_base (DW_OP_reg6 RBP)
DW_AT_linkage_name ("_Z3foo13PointerHolderIiE")
DW_AT_name ("foo")
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_int.cpp")
DW_AT_decl_line (3)
DW_AT_external (true)
0x0000002f: DW_TAG_formal_parameter
DW_AT_location (DW_OP_fbreg -8)
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_int.cpp")
DW_AT_decl_line (3)
DW_AT_type (0x0000003a "PointerHolder<int>")
0x00000039: NULL
0x0000003a: DW_TAG_structure_type
DW_AT_calling_convention (DW_CC_pass_by_value)
DW_AT_name ("PointerHolder<int>")
DW_AT_byte_size (0x08)
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_int.cpp")
DW_AT_decl_line (2)
0x00000040: DW_TAG_template_type_parameter
DW_AT_type (0x00000050 "int")
DW_AT_name ("T")
0x00000046: DW_TAG_member
DW_AT_name ("ptr")
DW_AT_type (0x00000054 "int *")
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_int.cpp")
DW_AT_decl_line (2)
DW_AT_data_member_location (0x00)
0x0000004f: NULL
0x00000050: DW_TAG_base_type
DW_AT_name ("int")
DW_AT_encoding (DW_ATE_signed)
DW_AT_byte_size (0x04)
0x00000054: DW_TAG_pointer_type
DW_AT_type (0x00000050 "int")
0x00000059: NULL
Limited type information emitted when a templated type is passed into a function by value and holds a pointer to another templated value
template <typename T>
struct PointerHolder { T* ptr; };
void foo(PointerHolder<PointerHolder<int>>) {}
$ clang++ -g -fstandalone-debug -c pointer_holder_nested.cpp
$ llvm-dwarfdump pointer_holder_nested.o
pointer_holder_nested.o: file format elf64-x86-64
.debug_info contents:
0x00000000: Compile Unit: length = 0x00000054, format = DWARF32, version = 0x0005, unit_type = DW_UT_compile, abbr_offset = 0x0000, addr_size = 0x08 (next unit at 0x00000058)
0x0000000c: DW_TAG_compile_unit
DW_AT_producer ("clang version 18.0.0 (https://github.com/llvm/llvm-project.git 5bae3a0b0ccf8f4f2bcffc86453197f3cc5a9829)")
DW_AT_language (DW_LANG_C_plus_plus_14)
DW_AT_name ("pointer_holder_nested.cpp")
DW_AT_str_offsets_base (0x00000008)
DW_AT_stmt_list (0x00000000)
DW_AT_comp_dir ("/tmp/test-debug")
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_addr_base (0x00000008)
0x00000023: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_frame_base (DW_OP_reg6 RBP)
DW_AT_linkage_name ("_Z3foo13PointerHolderIS_IiEE")
DW_AT_name ("foo")
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_nested.cpp")
DW_AT_decl_line (3)
DW_AT_external (true)
0x0000002f: DW_TAG_formal_parameter
DW_AT_location (DW_OP_fbreg -8)
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_nested.cpp")
DW_AT_decl_line (3)
DW_AT_type (0x0000003a "PointerHolder<PointerHolder<int> >")
0x00000039: NULL
0x0000003a: DW_TAG_structure_type
DW_AT_calling_convention (DW_CC_pass_by_value)
DW_AT_name ("PointerHolder<PointerHolder<int> >")
DW_AT_byte_size (0x08)
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_nested.cpp")
DW_AT_decl_line (2)
0x00000040: DW_TAG_template_type_parameter
DW_AT_type (0x00000050 "PointerHolder<int>")
DW_AT_name ("T")
0x00000046: DW_TAG_member
DW_AT_name ("ptr")
DW_AT_type (0x00000052 "PointerHolder<int> *")
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_nested.cpp")
DW_AT_decl_line (2)
DW_AT_data_member_location (0x00)
0x0000004f: NULL
0x00000050: DW_TAG_structure_type
DW_AT_name ("PointerHolder<int>")
DW_AT_declaration (true)
0x00000052: DW_TAG_pointer_type
DW_AT_type (0x00000050 "PointerHolder<int>")
0x00000057: NULL
In these examples we see that any templated type which has not been initialised is emitted with a DW_AT_declaration (true) tag and no structural information. Further, we can show that with explicit initialisation the full debug information is emitted.
Full type information emitted when a templated type is passed into a function by value and holds a pointer to another templated value which has been explicitly initialised
template <typename T>
struct PointerHolder { T* ptr; };
template struct PointerHolder<int>;
void foo(PointerHolder<PointerHolder<int>>) {}
$ clang++ -g -fstandalone-debug -c pointer_holder_explicit.cpp
$ llvm-dwarfdump pointer_holder_explicit.o
pointer_holder_explicit.o: file format elf64-x86-64
.debug_info contents:
0x00000000: Compile Unit: length = 0x00000071, format = DWARF32, version = 0x0005, unit_type = DW_UT_compile, abbr_offset = 0x0000, addr_size = 0x08 (next unit at 0x00000075)
0x0000000c: DW_TAG_compile_unit
DW_AT_producer ("clang version 18.0.0 (https://github.com/llvm/llvm-project.git 5bae3a0b0ccf8f4f2bcffc86453197f3cc5a9829)")
DW_AT_language (DW_LANG_C_plus_plus_14)
DW_AT_name ("pointer_holder_explicit.cpp")
DW_AT_str_offsets_base (0x00000008)
DW_AT_stmt_list (0x00000000)
DW_AT_comp_dir ("/tmp/test-debug")
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_addr_base (0x00000008)
0x00000023: DW_TAG_structure_type
DW_AT_calling_convention (DW_CC_pass_by_value)
DW_AT_name ("PointerHolder<int>")
DW_AT_byte_size (0x08)
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_explicit.cpp")
DW_AT_decl_line (4)
0x00000029: DW_TAG_template_type_parameter
DW_AT_type (0x00000039 "int")
DW_AT_name ("T")
0x0000002f: DW_TAG_member
DW_AT_name ("ptr")
DW_AT_type (0x0000003d "int *")
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_explicit.cpp")
DW_AT_decl_line (2)
DW_AT_data_member_location (0x00)
0x00000038: NULL
0x00000039: DW_TAG_base_type
DW_AT_name ("int")
DW_AT_encoding (DW_ATE_signed)
DW_AT_byte_size (0x04)
0x0000003d: DW_TAG_pointer_type
DW_AT_type (0x00000039 "int")
0x00000042: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x000000000000000a)
DW_AT_frame_base (DW_OP_reg6 RBP)
DW_AT_linkage_name ("_Z3foo13PointerHolderIS_IiEE")
DW_AT_name ("foo")
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_explicit.cpp")
DW_AT_decl_line (5)
DW_AT_external (true)
0x0000004e: DW_TAG_formal_parameter
DW_AT_location (DW_OP_fbreg -8)
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_explicit.cpp")
DW_AT_decl_line (5)
DW_AT_type (0x00000059 "PointerHolder<PointerHolder<int> >")
0x00000058: NULL
0x00000059: DW_TAG_structure_type
DW_AT_calling_convention (DW_CC_pass_by_value)
DW_AT_name ("PointerHolder<PointerHolder<int> >")
DW_AT_byte_size (0x08)
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_explicit.cpp")
DW_AT_decl_line (2)
0x0000005f: DW_TAG_template_type_parameter
DW_AT_type (0x00000023 "PointerHolder<int>")
DW_AT_name ("T")
0x00000065: DW_TAG_member
DW_AT_name ("ptr")
DW_AT_type (0x0000006f "PointerHolder<int> *")
DW_AT_decl_file ("/tmp/test-debug/pointer_holder_explicit.cpp")
DW_AT_decl_line (2)
DW_AT_data_member_location (0x00)
0x0000006e: NULL
0x0000006f: DW_TAG_pointer_type
DW_AT_type (0x00000023 "PointerHolder<int>")
0x00000074: NULL
Our use case requires debug information of the entire type tree for processing. I would like to know:
- Is this a bug? Does
-fstandalone-debugimply that the structural debug information will be emitted for the templated types in these case? To me it’s surprising behaviour. - If it’s not a bug, would we be able to implement a further flag like
-femit-unused-template-debug-typesto generate this? It seems like in the case where an explicit instantiation request works with no additional includes it should be possible for the compiler to emit this information.