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-debug
imply 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-types
to 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.