DebugInfo: Template names

Hey Paul - you mentioned the SCE debugger always rebuilds names from the DWARF tags (after stripping the <…> suffix from the DW_AT_name in the DWARF, I guess/assume?) - I was curious if you (or others you could cc on this thread) know how that solution deals with some of the more difficult issues I’ve punted on so far with the Simplified Template Names work.

The specific one that comes to mind is any pointer non-type template parameters. (eg: “template<int*> void f1(); int g; … f1<&g>()” - so far as I know there’s not enough data in the DWARF to reconstruct that name.

The other one I came across was unnamed struct types V lambda types - they have distinct manglings & so need different naming I think? But the DWARF has no record of that, and probably doesn’t have enough data to reconstruct the original naming (because the naming is based on the number of lambdas seen so far - and the DWARF isn’t adequately/guaranteed to be ordered like the source and also may omit some types if they’re unused leading to holes in the numbering)

If there are nice solutions to these things, be great to get them upstream and allow the Simplified Template Names work to take advantage of them & perhaps be more (maybe even fully) general.

  • Dave

Hey Paul - you mentioned the SCE debugger always rebuilds names
from the DWARF tags (after stripping the <...> suffix from the
DW_AT_name in the DWARF, I guess/assume?) - I was curious if you
(or others you could cc on this thread) know how that solution
deals with some of the more difficult issues I've punted on so
far with the Simplified Template Names work.

The specific one that comes to mind is any pointer non-type
template parameters. (eg: "template<int*> void f1(); int g; ...
f1<&g>()" - so far as I know there's not enough data in the DWARF
to reconstruct that name.

This one I'm pretty sure about: The template_value_parameter has
a DW_AT_location that is the address of the actual parameter, and
that address ought to be resolvable to "g" (at least once you have
the linked image, it should be straightforward, because "g" would
have a DWARF description whose location had that as the address).
We don't have to worry about things like "&array[10]" because
that's not legal for a non-type template parameter (as per
[temp.arg.nontype]p3 in C++11).

But I've asked anyway, to confirm.

The other one I came across was unnamed struct types V lambda
types - they have distinct manglings & so need different naming
I think? But the DWARF has no record of that, and probably
doesn't have enough data to reconstruct the original naming
(because the naming is based on the number of lambdas seen so
far - and the DWARF isn't adequately/guaranteed to be ordered
like the source and also may omit some types if they're unused
leading to holes in the numbering)

I was recently unable to figure out how to pass a lambda as a
template parameter, so if you could provide an example that would
be helpful both for this question and the other thing I was doing.
I've passed your description along just in case they know off the
tops of their heads.
--paulr

Hey Paul - you mentioned the SCE debugger always rebuilds names
from the DWARF tags (after stripping the <…> suffix from the
DW_AT_name in the DWARF, I guess/assume?) - I was curious if you
(or others you could cc on this thread) know how that solution
deals with some of the more difficult issues I’ve punted on so
far with the Simplified Template Names work.

The specific one that comes to mind is any pointer non-type
template parameters. (eg: “template<int*> void f1(); int g; …
f1<&g>()” - so far as I know there’s not enough data in the DWARF
to reconstruct that name.

This one I’m pretty sure about: The template_value_parameter has
a DW_AT_location that is the address of the actual parameter, and
that address ought to be resolvable to “g” (at least once you have
the linked image, it should be straightforward, because “g” would
have a DWARF description whose location had that as the address).
We don’t have to worry about things like “&array[10]” because
that’s not legal for a non-type template parameter (as per
[temp.arg.nontype]p3 in C++11).

But I’ve asked anyway, to confirm.

Ah, yep. I thought about the possibility of actually doing symbol lookup - but that seemed a bit extreme to me (having to go outside the DWARF to figure out the name). Though that wouldn’t work if the variable/function is optimized out (if it’s defined in another object file and that isn’t referenced/is omitted by the linker, or -Wl,-gc-sections, etc).

There’s some quirks here - for some reason these relocations used in the ELF file to write the address of the variables/functions for pointer non-type-template parameters into the DWARF data cause linker errors if those globals aren’t defined (which leads to -g/-g0 codegen differences, since the linker is pulling in these symbols, could pull in global ctors in the objects that define those symbols, etc) but the symbols /can/ still be eliminated with -ffunction-sections f-data-sections -Wl,-gc-sections…

The other one I came across was unnamed struct types V lambda
types - they have distinct manglings & so need different naming
I think? But the DWARF has no record of that, and probably
doesn’t have enough data to reconstruct the original naming
(because the naming is based on the number of lambdas seen so
far - and the DWARF isn’t adequately/guaranteed to be ordered
like the source and also may omit some types if they’re unused
leading to holes in the numbering)

I was recently unable to figure out how to pass a lambda as a
template parameter, so if you could provide an example that would
be helpful both for this question and the other thing I was doing.
I’ve passed your description along just in case they know off the
tops of their heads.

Oh, sure, consider code something like this:

template

void f1() { }

class { } x;

auto y = { };

int main() {

f1<decltype(x)>();

f1<decltype(y)>();

}

$ clang+±tot test.cpp -g -c && llvm-dwarfdump-tot test.o | grep “DW_AT_name|DW_TAG|DW_AT_type|DW_AT_linkage”

0x0000000b: DW_TAG_compile_unit

DW_AT_name (“test.cpp”)

0x0000002a: DW_TAG_subprogram

DW_AT_name (“main”)

DW_AT_type (0x00000089 “int”)

0x00000043: DW_TAG_subprogram

DW_AT_linkage_name (“_Z2f1I3$_0Evv”)

DW_AT_name (“f1<(unnamed class at test.cpp:3:1)>”)

0x0000005c: DW_TAG_template_type_parameter

DW_AT_type (0x00000090 "class ")

DW_AT_name (“T”)

0x00000066: DW_TAG_subprogram

DW_AT_linkage_name (“_Z2f1I3$_1Evv”)

DW_AT_name (“f1<(lambda at test.cpp:4:10)>”)

0x0000007f: DW_TAG_template_type_parameter

DW_AT_type (0x00000095 "class ")

DW_AT_name (“T”)

0x00000089: DW_TAG_base_type

DW_AT_name (“int”)

0x00000090: DW_TAG_class_type

0x00000095: DW_TAG_class_type

Oh, I had thought these actually mangled differently, but they don’t - so if you rendered both as “unnamed class” that’d probably be OK! Clang/LLVM only includes the line, and not the column, so you’d be able to render this as “(unnamed class at test.cpp:3)” rather than “(unnamed class at test.cpp:3:1)”.

Ah, yep. I thought about the possibility of actually doing symbol lookup - but that seemed a bit extreme to me (having to go outside the DWARF to figure out the name). Though that wouldn’t work if the variable/function is optimized out (if it’s defined in another object file and that isn’t referenced/is omitted by the linker, or -Wl,-gc-sections, etc).

Don’t necessarily need to go outside the DWARF? given that the symbol ought to have a DWARF description whose location expression should be the same address. If you’ve already loaded the unit then the address-based lookup is feasible. And in fact (for example) ELF symbol lookup isn’t going to work for a static item anyway, AFAICT only non-private symbols end up in the ELF symbol table.

There’s some quirks here - for some reason these relocations used in the ELF file to write the address of the variables/functions for pointer non-type-template parameters into the DWARF data cause linker errors if those globals aren’t defined (which leads to -g/-g0 codegen differences, since the linker is pulling in these symbols, could pull in global ctors in the objects that define those symbols, etc) but the symbols /can/ still be eliminated with -ffunction-sections f-data-sections -Wl,-gc-sections…

Hmm don’t think that references from .debug_* sections count as gc roots? I may be mistaken, this might be a Sony-proprietary linker behavior and not something LLD does, but IMO that should be LLD’s behavior as well. The relocations would end up as unresolved and be tombstoned.

–paulr

Ah, yep. I thought about the possibility of actually doing symbol lookup - but that seemed a bit extreme to me (having to go outside the DWARF to figure out the name). Though that wouldn’t work if the variable/function is optimized out (if it’s defined in another object file and that isn’t referenced/is omitted by the linker, or -Wl,-gc-sections, etc).

Don’t necessarily need to go outside the DWARF? given that the symbol ought to have a DWARF description whose location expression should be the same address. If you’ve already loaded the unit then the address-based lookup is feasible. And in fact (for example) ELF symbol lookup isn’t going to work for a static item anyway, AFAICT only non-private symbols end up in the ELF symbol table.

Fair - though the global variable might be in a library/code not built with debug info enabled. Static variables might be harder to miss - since they’d be in the same translation unit/compilation unit. Or if the global is optimized out, then you might not be able to identify which global variable (even if it is described by the DWARF) is the one referenced by this non-type template parameter DWARF description. (perhaps we should/could improve this DWARF by referring to a declaration or definition of the actual variable, with a location on /that/ DWARF description instead of directly in the template parameter description… if that covers all the cases for C++, which I /think/ it does (as you say, you can’t reference an element of an array in a non-type template parameter, it has to be a whole named variable, I think))

There’s some quirks here - for some reason these relocations used in the ELF file to write the address of the variables/functions for pointer non-type-template parameters into the DWARF data cause linker errors if those globals aren’t defined (which leads to -g/-g0 codegen differences, since the linker is pulling in these symbols, could pull in global ctors in the objects that define those symbols, etc) but the symbols /can/ still be eliminated with -ffunction-sections f-data-sections -Wl,-gc-sections…

Hmm don’t think that references from .debug_* sections count as gc roots? I may be mistaken, this might be a Sony-proprietary linker behavior and not something LLD does, but IMO that should be LLD’s behavior as well. The relocations would end up as unresolved and be tombstoned.

Yeah - ELF linkers generally don’t want to special-case the debug info sections (the idea being that ELF is DWARF agnostic - but it isn’t entirely, the tombstoning logic (which value to use for dead references/how to make those references dead) is one of those things). I /guess/ the linker is OK discarding the referenced functions, but has to have a reference to it first or it produces an error about unresolved relocations… - but maybe it doesn’t actually cause the code to be linked in?

Hmm, maybe it works for function pointers but has been overlooked/not implemented for data pointers in some way…:

$ cat test.cpp

template<int *x, void (*y)()>

void f1() { }

void f2();

extern int i;

int main() {

f1<&i, f2>();

}

$ cat test2.cpp

#include

extern int i;

int i = ((std::cout << “Hello, World.\n”), 1);

$ clang+±tot test.cpp test2.cpp -c && clang+±tot test.o -Wl,–start-lib -Wl,test2.o -Wl,–end-lib -fuse-ld=lld && ./a.out

$ clang+±tot -g test.cpp test2.cpp -c && clang+±tot test.o -Wl,–start-lib -Wl,test2.o -Wl,–end-lib -fuse-ld=lld && ./a.out

Hello, World.

$ nm a.out | grep " i$"

0000000000203d48 B i

$ clang+±tot test.cpp test2.cpp -c && clang+±tot test.o -Wl,–start-lib -Wl,test2.o -Wl,–end-lib -fuse-ld=lld && ./a.out

$ nm a.out | grep " i$"

$ clang+±tot test.cpp test2.cpp -c -ffunction-sections -fdata-sections && clang+±tot -Wl,-gc-sections test.o -Wl,–start-lib -Wl,test2.o -Wl,–end-lib -fuse-ld=lld && ./a.out

$ nm a.out | grep " i$"

$ ./a.out

$ clang+±tot -g test.cpp test2.cpp -c -ffunction-sections -fdata-sections && clang+±tot -Wl,-gc-sections test.o -Wl,–start-lib -Wl,test2.o -Wl,–end-lib -fuse-ld=lld && ./a.out

Hello, World.

$ nm a.out | grep " i$"

0000000000203d04 B i

I heard back from our debugger folks (actually they replied a couple weeks ago and I missed the notification somehow). They compliment you on the interesting test cases. :blush:

For the case of a pointer template parameter, “template<int*> void f1() { … }; int g; … f1<&g>();” where “g” has address 0x1234, this indeed would get reconstructed as “f1<0x1234>” although now that you’ve brought it up, they’ve raised an internal ticket to look at symbolizing that. I agree with your point that if “g” is defined in another CU and not compiled with debug info, there might be no more than an external declaration in the current CU’s DWARF to look at, and so information outside of DWARF would be needed to symbolize that. I don’t know about this case specifically but I have seen gdb make use of the ELF symbol table to derive information that isn’t present in the DWARF, so it’s not unprecedented. I think for your round-tripping purposes, if the symbol is in the DWARF you could symbolize it, and for the other cases have to be content with falling back to the pointer literal.

For the second case, using as example code

template void f1() {}

class {} x;

auto y = {};

int main() {

f1<decltype(x)>();

f1<decltype(y)>();

}

this conjures up generated names on the order of

I heard back from our debugger folks (actually they replied a couple weeks ago and I missed the notification somehow). They compliment you on the interesting test cases. :blush:

Thanks for checking into it!

For the case of a pointer template parameter, “template<int*> void f1() { … }; int g; … f1<&g>();” where “g” has address 0x1234, this indeed would get reconstructed as “f1<0x1234>” although now that you’ve brought it up, they’ve raised an internal ticket to look at symbolizing that. I agree with your point that if “g” is defined in another CU and not compiled with debug info, there might be no more than an external declaration in the current CU’s DWARF to look at, and so information outside of DWARF would be needed to symbolize that. I don’t know about this case specifically but I have seen gdb make use of the ELF symbol table to derive information that isn’t present in the DWARF, so it’s not unprecedented.

Ah, yeah - might’ve seen something where gdb might’ve stitched up a declaration in one CU, with no-debug definition in another object based on the symbol name.

I think for your round-tripping purposes, if the symbol is in the DWARF you could symbolize it, and for the other cases have to be content with falling back to the pointer literal.

I think the risk there, compiling with -ffunction-sections -fdata-sections -Wl,–gc-sections is that the pointer literal might be zero if the symbol has been gc’d, so it may not be recoverable/uniqueable.

$ cat test.cpp

template<int*> void f1() { }

static int a;

static int b;

int main() {

f1<&a>();

f1<&b>();

}

$ clang+±tot test.cpp -g && llvm-dwarfdump-tot a.out | grep DW_AT_location

DW_AT_location (DW_OP_addr 0x40402c, DW_OP_stack_value)

DW_AT_location (DW_OP_addr 0x404030, DW_OP_stack_value)

$ clang+±tot test.cpp -g -fdata-sections -Wl,–gc-sections && llvm-dwarfdump-tot a.out | grep DW_AT_location

DW_AT_location (DW_OP_addr 0x0, DW_OP_stack_value)

DW_AT_location (DW_OP_addr 0x0, DW_OP_stack_value)

For the second case, using as example code

template void f1() {}

class {} x;

auto y = {};

int main() {

f1<decltype(x)>();

f1<decltype(y)>();

}

this conjures up generated names on the order of

f1<(anon_class:0x4D62500372C58A1A)>
f1<(anon_class:0x4D62500372C58A65)>

which is admittedly not great, syntactically not identifiers for one thing.

Well, no worse than the compiler generated names, which also aren’t identifiers:
Clang:

f1<(unnamed class at test.cpp:3:1)>

f1<(lambda at test.cpp:5:10)>

GCC:

f1<<lambda()> >

f1< >

At least it sounds like maybe the ones you have are unique (though I wonder how they’re uniqued across CUs to still generate the same identifier) - GCC’s certainly aren’t, Clang’s probably aren’t (with sufficient macro goo):

$ cat test.cpp

template void f1() {}

#define ANONS \

class {} a1; \

class {} a2

ANONS;

#define LAMBDAS \

auto l1 = {}; \

auto l2 = {}

LAMBDAS;

int main() {

f1<decltype(a1)>();

f1<decltype(a2)>();

f1<decltype(l1)>();

f1<decltype(l2)>();

}

$ clang+±tot test.cpp -g -c && llvm-dwarfdump-tot test.o | grep “f1<”

DW_AT_name (“f1<(unnamed class at test.cpp:6:1)>”)

DW_AT_name (“f1<(unnamed class at test.cpp:6:1)>”)

DW_AT_name (“f1<(lambda at test.cpp:11:1)>”)

DW_AT_name (“f1<(lambda at test.cpp:11:1)>”)

Not something anyone has complained about and so not something they’ve thought to look at before, although again there’s now an internal ticket to see about improving this. For your round-tripping, I guess coming up with some name-generation system for anonymous types would have to do, although whether you can easily leave enough breadcrumbs to walk back from the template parameter type to the type-of-x seems like a question.

Yeah, not sure.

Sorry I can’t provide any cleverness here.

No worries at all - glad to know I’ve probably not missed anything game-changing for these cases, at least.