This is an archive of the discontinued LLVM Phabricator instance.

[symbolizer] Parse DW_TAG_variable DIs to show line info for globals
ClosedPublic

Authored by hctim on Apr 11 2022, 12:58 PM.

Details

Summary

Currently, llvm-symbolizer doesn't like to parse .debug_info in order to
show the line info for global variables. addr2line does this. In the
future, I'm looking to migrate AddressSanitizer off of internal metadata
over to using debuginfo, and this is predicated on being able to get the
line info for global variables.

This patch adds the requisite support for getting the line info from the
.debug_info section for symbolizing global variables. This only happens
when you ask for a global variable to be symbolized as data.

Diff Detail

Event Timeline

hctim created this revision.Apr 11 2022, 12:58 PM
Herald added a project: Restricted Project. · View Herald Transcript
hctim requested review of this revision.Apr 11 2022, 12:58 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 11 2022, 12:58 PM
tschuett added inline comments.
llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h
248
hctim updated this revision to Diff 422021.Apr 11 2022, 1:15 PM
hctim marked an inline comment as done.

.

llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h
248

thanks, changed to set/map given that bionic's libc.so has 10.5k DW_TAG_variables, so DenseSet/DenseMap doesn't seem like the right structure either.

hctim added a comment.Apr 11 2022, 1:33 PM

I do not want to bother you at all:
https://llvm.org/docs/ProgrammersManual.html#map

Is there a better data structure you'd recommend? I picked the map based on the following:

std::map is most useful when your keys or values are very large

given the 10.5k DW_TAG_variables in bionic libc.so and the quote from DenseMap:

it will waste a lot of space if your keys or values are large

Am I misreading the above sentence to mean "if the count of keys is huge", and the intended meaning is "if sizeof(key) is huge"?

Unfortunately not. set and map seem to be very malloc heavy. I could not find any great alternatives in the LLVM manual.

jhenderson added a subscriber: dblaikie.

I think this needs a doc update?

FYI: I've not looked at the parts of this patch that are in DebugInfo/DWARF. Someone with more familiarity with the area should probably review it (I can if nobody else is around - adding @dblaikie who may be a good shout).

llvm/test/tools/llvm-symbolizer/data-location.s
16 ↗(On Diff #422021)

One of the purposes of the cross-project-tests project was to make it easier to test llvm-symbolizer without having to resort to canned binaries or canned YAML blobs like below. By writing the test there, you can use clang and lld directly and consequently start from a much more understandable .c file.

That being said, I wonder if you'd be better off hand-crafting this YAML. It doesn't seem likely to be that complicated to write some .debug_info that has the property you want using yaml2obj's DWARF support, if I follow the required behaviour correctly. There are already some good examples of similar bits. The advantage with using yaml like that is that you can keep it tightly focused on what is important, omitting the various components of the object that are unrelated to the test (e.g. .eh_frame).

hctim added inline comments.Apr 14 2022, 2:54 PM
llvm/test/tools/llvm-symbolizer/data-location.s
16 ↗(On Diff #422021)

I had a quick look at cross-project-tests, and didn't find any examples where llvm-symbolizer was currently being tests there, and I think it's out of scope for this patch.

The right way to test this is to invoke clang and run llvm-symbolizer as the output.

My problem with hand-crafting is that it's:

a) Very time consuming, looking into it further I'd have to read a lot more about the ELF spec for .debug_abbrev, .debug_info, and .debug_line to understand what headers are needed.

b) Hard to change. If someone introduces some new feature, then they have to go through the same "understand nuance of ELF" to just modify my test so that it passes. It's much easier for someone to go "hey, just need to recompile this example file and make sure the new golden file passes the existing test".

it will waste a lot of space if your keys or values are large

Am I misreading the above sentence to mean "if the count of keys is huge", and the intended meaning is "if sizeof(key) is huge"?

That's correct - dense* may perform poorly/use excess memory if sizeof(key) is large.

llvm/test/tools/llvm-symbolizer/data-location.s
16 ↗(On Diff #422021)

(re: cross-project-tests: They must not be used as a replacement for individual project testing - but for added feature/cross-project coverage if there's important interactions between components. But an LLVM patch should still have an LLVM test, a Clang patch with a Clang test, etc - and optionally some additional cross-project feature testing)

re: hand crafting: Yeah, I'm not a super fan of doing that, until/unless yaml2obj can really simplify writing DWARF down to not needing to write abbreviations, multiple sections, attribute forms, etc.

Though is there any reason this test needs a function in it? & any reason it needs two integers?

hctim updated this revision to Diff 423180.Apr 15 2022, 3:51 PM

.

llvm/test/tools/llvm-symbolizer/data-location.s
16 ↗(On Diff #422021)

I updated the test a little and added some more of what I was hoping for.

One global in data, one in bss, one as a function-static global and a string (this patch now depends on D123534).

jhenderson added inline comments.Apr 19 2022, 12:35 AM
llvm/test/tools/llvm-symbolizer/data-location.s
7 ↗(On Diff #423180)

It might be useful to add comments about what is special about each of these cases (i.e. data/bss/function-static/string) or even name the variables along those lines to directly imply their purpose. That way, if the test is ever rewritten, it's easy to understand why each example exists and what is important about it. That being said, is there actually a difference between them for the testing purposes? What do they actually exercise in the code changes? (I haven't looked at the code changes to understand this).

16 ↗(On Diff #422021)

(re: cross-project-tests: They must not be used as a replacement for individual project testing - but for added feature/cross-project coverage if there's important interactions between components. But an LLVM patch should still have an LLVM test, a Clang patch with a Clang test, etc - and optionally some additional cross-project feature testing)

That is one purpose for why I created this testsuite, but the other was for using tools to generate test inputs at runtime, such as clang or lld. In the original thread, I even discussed llvm-symbolizer as a concrete example (using LLD in that particular case, in the same manner as one might use llvm-mc for generating test input at test time, because creating canned test inputs means they're not easily maintainable). There are no current examples of this yet though.

hctim updated this revision to Diff 423732.Apr 19 2022, 1:54 PM
hctim marked an inline comment as done.

Update

llvm/test/tools/llvm-symbolizer/data-location.s
7 ↗(On Diff #423180)

Renamed them (and made the test less in need of massaging if things change). They're individually useful because they're different types of global variables, located in different places.

dblaikie added inline comments.Apr 20 2022, 10:51 AM
llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h
248

I don't believe the number of elements is an argument against the dense data structures - could you help explain/understand the connection there?

llvm/lib/DebugInfo/DWARF/DWARFContext.cpp
1230–1232

Should this logic live in getCompileUnitForAddress insetad of here? (at least given the name, it sounds like it's something getCompileUnitForAddress should handle - but perhaps it should be renamed to be only about function addresses, and the variable walk could/should remain here?)

(awkwardly: LLVM's debug_aranges (Clang doesn't emit these by default anyway, so it's not likely to be much of an issue in the wild) would be sufficient to answer this query, but GCC does not include globals in debug_aranges, so they probably can't be relied upon unfortunately (unfortunate that Clang pays the cost when it emits them, but consumers can't rely on them) - and personally I'd rather we (DWARF in general) move away from aranges anyway, since (apart from this unreliable global variable case) they're covered by CU level high/low/ranges anyway)

1233–1234
llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp
752

Walking the entire DIE tree seems expensive - could this prune any branches? (such as when descending into a type DIE? (DW_TAG_*_type))

753

maybe DWARFDie supports range-based for loop now?

788–790

Rather than doing count + insert this should probably call insert up-front and inspect the return value to see if the insert happened, or if the value was already present - then if it happened, do the update.

793–795

This should probably use find rather than count+[] to avoid doing two lookups.

llvm/test/tools/llvm-symbolizer/data-location.s
16 ↗(On Diff #422021)

(this might be shorter/simpler to read as assembly, rather than yaml?)

hctim marked 11 inline comments as done.Apr 21 2022, 2:33 PM
hctim added inline comments.
llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h
248

changed, thanks for clarifying before

llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp
753

Neat!

llvm/test/tools/llvm-symbolizer/data-location.s
16 ↗(On Diff #422021)

(this might be shorter/simpler to read as assembly, rather than yaml?)

Originally that's what I thought of doing, but llvm-symbolizer doesn't play nicely with objfiles because the symbol addr is a section offset, so f is .text+0x0 and data_global is .data+0x0, and llvm-symbolizer only spits out one of them.

hctim updated this revision to Diff 424296.Apr 21 2022, 2:34 PM
hctim marked 3 inline comments as done.

.

hctim updated this revision to Diff 424322.Apr 21 2022, 3:50 PM

minor fix

hctim updated this revision to Diff 424324.Apr 21 2022, 4:08 PM
XCOFF/print-linenumber.testdepends on the fact that symbolizing '0x0' should pull it from .text, even if there's a global variable also at '0x0'. This is why (as above) I don't think objfiles are a good test case for llvm-symbolizer, as an address alone can point to multiple symbolizable things.

So, now we specifically only do this with the 'DATA 0x0' pathways.

jhenderson added inline comments.Apr 22 2022, 2:52 AM
llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h
247–248

Nit: I don't think you need llvm:: here - we're already inside the llvm namespace.

llvm/lib/DebugInfo/DWARF/DWARFContext.cpp
1044–1046

I might be wrong, but I feel like I've heard of cases where .debug_aranges didn't cover all functions either. Indeed, our internal tool always searches both aranges and the CUs directly when looking up an address. (Not suggesting a change in this patch necessarily though).

1047

Could you avoid using auto here, please: the type isn't obvious, so it can be a little confusing, especially as my natural assumption was that this was a DWARFCompileUnit *, but the below cast indicates it isn't.

The previous code in getLineInfoForAddress used normal_units not compile_units. Why the change?

llvm/test/tools/llvm-symbolizer/data-location.s
7 ↗(On Diff #423180)

They're individually useful because they're different types of global variables, located in different places.

Right, but are they located in differenet places within the DWARF? In other words, are any different code paths actually exercised this way?

Tangentially related: is one of the cases missing from the debug_aranges, so that the fallback code is exercised?

hctim marked 4 inline comments as done.Apr 26 2022, 4:29 PM
hctim added inline comments.
llvm/lib/DebugInfo/DWARF/DWARFContext.cpp
1044–1046

ack, thanks for the colour

1047

Could you avoid using auto here, please: the type isn't obvious, so it can be a little confusing, especially as my natural assumption was that this was a DWARFCompileUnit *, but the below cast indicates it isn't.

Done

The previous code in getLineInfoForAddress used normal_units not compile_units. Why the change?

Either worked in my case, I figured compile_units was a better touch point because DW_TAG_variable seem to all be a child of a DW_TAG_compile_unit, and the implementaion point DWARFContext::getCompileUnitForAddress specifically mentions returning a CompileUnit. And, it seems like compile_units() is a subset of normal_units().

llvm/test/tools/llvm-symbolizer/data-location.s
7 ↗(On Diff #423180)

Right, but are they located in differenet places within the DWARF? In other words, are any different code paths actually exercised this way?

No, bss_global and data_global are functionally here. The reason why I have them both here is because if you compile the source file to an object rather than a DSO, you end up with bad address lookups because bss_global and data_global are both at address 0x0 (just in different sections). I figured this would give some amount of posterity here about why it's a DSO, I know that I was pretty surprised there wasn't a great way to test it.

Maybe a comment is a better way to go about it. Let me know what you think.

Tangentially related: is one of the cases missing from the debug_aranges, so that the fallback code is exercised?

All of them are missing from the debug_aranges :)

hctim updated this revision to Diff 425337.Apr 26 2022, 4:29 PM
hctim marked 3 inline comments as done.

Fix a small ordering bug, update with comments.

jhenderson added inline comments.Apr 27 2022, 1:57 AM
llvm/test/tools/llvm-symbolizer/data-location.s
7 ↗(On Diff #423180)

As things stand, if I came to this test, I'd consider dropping vataiables that appear to be in the same place in the debug info, so a comment is probably needed saying what coverage they provide.

hctim updated this revision to Diff 426173.Apr 29 2022, 3:18 PM
hctim marked an inline comment as done.

Update test, rebase off of string debuginfo.

hctim updated this revision to Diff 426176.Apr 29 2022, 3:19 PM

(restoring after uploading the diff for D123534 here accidentally)

Herald added a project: Restricted Project. · View Herald TranscriptApr 29 2022, 3:20 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript
hctim updated this revision to Diff 426177.Apr 29 2022, 3:22 PM

(restoring after uploading the diff for D123534 here accidentally)

jhenderson added inline comments.May 3 2022, 12:09 AM
llvm/test/tools/llvm-symbolizer/data-location.yaml
47

Nit

hctim updated this revision to Diff 428538.May 10 2022, 5:07 PM
hctim marked an inline comment as done.

(comment)

llvm/test/tools/llvm-symbolizer/data-location.s
7 ↗(On Diff #423180)

Done, and also made this patch no longer depend on D123534.

Okay, nothing else from me, but @dblaikie or another debuginfo person should review it too.

dblaikie added inline comments.May 11 2022, 1:29 PM
llvm/lib/DebugInfo/DWARF/DWARFContext.cpp
1044

Might be worth a more precise statement here - at least my understanding is that GCC never puts global variables in aranges and clang always does (but it's not possible to differentiate the situations, making aranges untrustworthy for this query - so the code here is correct if it's got to handle GCC, not suggesting any code change, but maybe more info in the comment to point to GCC for a concrete example of the issue)

llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp
771–785

Are there any examples of global merge where this might not be correct?

Like if a global was described as "addrx, 5, add" (eg: 5 beyond this address) then only looking at the first operation might mis-conclude that the variable is at this address when it's at 5 bytes past this address - and some other variable is at this address)

LLVM has a "global merge" optimization that might cause something like this. Let's see if I can create an example.

Ah, yeah, here we go:

static int x;
static int y;
int *f(bool b) { return b ? &x : &y; }
$ clang++-tot -O3 merge.cpp -c -g -target aarch64-linux-gnuabi -mllvm -global-merge-ignore-single-use && llvm-dwarfdump-tot merge.o | grep DW_AT_location
                DW_AT_location  (DW_OP_addrx 0x0)
                DW_AT_location  (DW_OP_addrx 0x0, DW_OP_plus_uconst 0x4)

(the -global-merge-ignore-single-use is to simplify the test case - without that it could still be tickled with a more complicated example - seems we only enable global merge on ARM 32 and 64 bit due to the higher register pressure there, by the sounds of it)

I'm guessing this patch would overwrite the VariableDieMap entry for the first global with the second one so queries for the first address would result in the second DIE and queries for the second address wouldn't give any result?

Hmm, also - how does this handle queries that aren't at exactly the starting address of the variable? Presumably the DW_AT_type of the DW_TAG_global_variable would have to be inspected to find the size of the variable starting at the address, and any query in that range should be successful?

hctim updated this revision to Diff 429783.May 16 2022, 10:53 AM
hctim marked 2 inline comments as done.

Patch update.

llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp
771–785

I've added some handling for the DW_OP_plus_uconst. Unfortunately I didn't find any generic existing handling for the expression evaluation (which surprised me, maybe I just suck at looking), so I'm just making the assumption that global variable addresses aren't described using DW_OP_plus or anything else...

Fixed it up to now provide line info when you provide an address halfway through a symbol. Good thing there wasn't a big impact in D123534 about keeping the string sizes around :). Worst case (if the type info sucks) we just only accept the precise start addresses of the symbols.

dblaikie added inline comments.May 16 2022, 4:14 PM
llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp
771–785

Probably worth maybe /only/ matching these expressions, then, rather than currently it's matching any expression that starts with addr, or addr+plus? Even if that's followed by any other garbage that would be ignored (& so the expression would be misinterpreted)

There's probably no expression evaluation logic in LLVM's libDebugInfoDWARF - there's no need to evaluate expressions when symbolizing, and dwarfdump just wants to dump it, not compute the resulting value. (some similar code exists in lldb that would do evaluation).

hctim updated this revision to Diff 430509.May 18 2022, 2:44 PM
hctim marked an inline comment as done.

Explicitly only match 'DW_OP_addr[x] [+ DW_OP_plus_uconst]'

llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp
771–785

Sure, done.

dblaikie accepted this revision.May 21 2022, 5:48 PM

Seems OK

llvm/lib/DebugInfo/DWARF/DWARFContext.cpp
1040–1059
llvm/lib/DebugInfo/DWARF/DWARFDie.cpp
1159

Could be written as None (similarly for a couple of other cases above)

llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp
780–781

Maybe flesh out this comment to explain that it doesn't support the full generality of DWARF expressions (maybe some note about how "here's where to add more expression parsing functionality if/when needed/more interesting cases are discovered") but covers what LLVM producers currently at least. (maybe even point to the source code in LLVM (look around in lib/CodeGen/AsmPrinter to find where these addresses are created, maybe it'd be easy enough to cross-reference these two bits of code to see that they cover each other)

796–799

Maybe swap this around to reduce indentation/conditionality:

This revision is now accepted and ready to land.May 21 2022, 5:48 PM
hctim updated this revision to Diff 431463.May 23 2022, 1:10 PM
hctim marked 4 inline comments as done.

David's comments.

This revision was landed with ongoing or failed builds.May 23 2022, 1:30 PM
This revision was automatically updated to reflect the committed changes.