This is an archive of the discontinued LLVM Phabricator instance.

[DebugInfo][llvm-dwarfutil] Combine overlapped address ranges.
ClosedPublic

Authored by avl on Apr 10 2022, 12:09 PM.

Details

Summary

DWARF files may contain overlapping address ranges. f.e. it can happen if the two
copies of the function have identical instruction sequences and they end up sharing.
That looks incorrect from the point of view of DWARF spec. Current implementation
of DWARFLinker does not combine overlapped address ranges. It would be good if such
ranges would be handled in some useful way. Thus, this patch allows DWARFLinker
to combine overlapped ranges in a single one.

Depends on D86539

Diff Detail

Event Timeline

avl created this revision.Apr 10 2022, 12:09 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 10 2022, 12:09 PM
avl requested review of this revision.Apr 10 2022, 12:09 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 10 2022, 12:09 PM
avl updated this revision to Diff 421808.Apr 10 2022, 1:16 PM

corrected test comment.

At first glance this looks fine to me. @JDevlieghere ?

llvm/include/llvm/DWARFLinker/DWARFLinkerAddressRangesMap.h
21

adjustment

47

Since this is completely independent, it might make sense to write a unit test for this class.

100

maybe call this forEach()?

avl updated this revision to Diff 423119.Apr 15 2022, 8:52 AM

addressed comments.

avl added a comment.Apr 20 2022, 5:45 AM

@JDevlieghere Jonas, Would you mind to check this review, please?

Do you have a small/standalone lto example of these overlapped ranges - to understand both why they happen, and why they're invalid?

avl added a comment.Apr 20 2022, 12:06 PM

Do you have a small/standalone lto example of these overlapped ranges - to understand both why they happen, and why they're invalid?

I do not have at the moment. The issue was raised by @clayborg https://reviews.llvm.org/D86539#3428098 . But I will try to reproduce.
That is the explanation:

This can happen when you have LTO mess with the debug info as many functions might end up being combined. I have a command that dumps the ranges of DIEs and the problem looks like this:

DIE OFFSET RANGE                                     NAME
========== ========================================= ====================
0x0000e88e [0x0000000000028454 - 0x0000000000028474) freep
0x0000e8c2 [0x0000000000028454 - 0x0000000000028474) freep_aligned
0x00022cf4 [0x0000000000028454 - 0x0000000000028474) freep
0x00022d5d [0x0000000000028454 - 0x0000000000028474) freep_aligned

Do you have a small/standalone lto example of these overlapped ranges - to understand both why they happen, and why they're invalid?

Just have LTO combine the contents of two functions with the same opcode bytes and you can end up with ranges within the same CU and across CUs that overlap. I don't consider these invalid, but if they are in the same CU, the verification using llvm-dwarfdump would emit errors.

I have also seen bad DWARF where you have two functions that don't share the exact same range and yet do overlap. This is mostly again in LTO binaries where the LTO linker tried to change the DWARF.

There are so many places where we redo address range and address ranges code in LLVM and LLDB. Might be worth moving the GSYM "Range.h" file into a more accessible area to avoid all of the "intersects" and "overlaps" kind of computations we do on address ranges. The code is here: llvm/include/llvm/DebugInfo/GSYM/Range.h

llvm/include/llvm/DWARFLinker/DWARFLinker.h
57

Would we want to include the LowPC in the ObjFileAddressRange struct and then use a std::set<ObjFileAddressRange>?

llvm/include/llvm/DWARFLinker/DWARFLinkerAddressRangesMap.h
53

Might be worth watching out for invalid LowPC/HighPC combos by using >= instead of ==?

clayborg added inline comments.Apr 20 2022, 3:24 PM
llvm/include/llvm/DWARFLinker/DWARFLinkerAddressRangesMap.h
26–30

It would be great to use a ADT "AddressRange" class here instead like something from llvm/include/llvm/DebugInfo/GSYM/Range.h instead of doing things manually.

38

Might be a bit clearer as suggested, or if we use a AddressRange class it would be:

return Range.contains(Address);
103

If the "Handler" function returns a bool, you can stop early if it returns false, or keep iterating if it returns true. No need if you don't think it would be useful

llvm/unittests/DWARFLinker/DWARFLinkerAddressRangesMapTest.cpp
1 ↗(On Diff #423119)

If we decode to possibly use something like the llvm/include/llvm/DebugInfo/GSYM/Range.h classes, those all have unit tests.

34 ↗(On Diff #423119)

If we change the addRange function to check using <=, add test for that here

avl added inline comments.Apr 21 2022, 4:30 AM
llvm/include/llvm/DWARFLinker/DWARFLinker.h
57

That is already done, DWARFLinkerAddressRangesMap.h:

struct DwarfLinkerAddressRange {
  /// Range LowPC.
  uint64_t LowPC = 0;

  /// Range HighPC.
  uint64_t HighPC = 0;

  ...
}
llvm/include/llvm/DWARFLinker/DWARFLinkerAddressRangesMap.h
26–30

I agree that using an ADT is a good thing here and DebugInfo/GSYM/Range.h:AddressRange is a good candidate. But it could not be used as it is. It requires some refactoring. f.e. it should be refactored out of DebugInfo/GSYM. Another thing is that there should be removed dependency on "class FileWriter;", and probably on "DataExtractor". I am OK with doing such refactoring, but would prefer to do it separately from this review.

avl updated this revision to Diff 424163.Apr 21 2022, 5:29 AM

addressed comments.

Do you have a small/standalone lto example of these overlapped ranges - to understand both why they happen, and why they're invalid?

Just have LTO combine the contents of two functions with the same opcode bytes and you can end up with ranges within the same CU and across CUs that overlap. I don't consider these invalid, but if they are in the same CU, the verification using llvm-dwarfdump would emit errors.

This can/does happen without LTO - actually I think it's less likely to happen /with/ LTO than without, but in any case, here's a simple example with overlapping CU ranges that doesn't require LTO (using __attribute__((nodebug)) to omit unnecessary debug info, the issue still reproduces without it, just with more noise):
inl.h:

inline void f1() {
}
void a();

a.cpp:

#include "inl.h"
__attribute__((nodebug)) void a() {
  f1();
}

b.cpp:

#include "inl.h"
void a();
__attribute__((nodebug)) int main() {
  a();
  f1();
}
clang++ -g a.cpp b.cpp && llvm-dwarfdump a.out | grep "0x000000000\|DW_TAG\|DW_AT_name" | sed -e "s/^............//"`
DW_TAG_compile_unit
  DW_AT_name    ("a.cpp")
  DW_AT_low_pc  (0x0000000000401180)
  DW_AT_high_pc (0x0000000000401186)
  DW_TAG_subprogram
    DW_AT_low_pc        (0x0000000000401180)
    DW_AT_high_pc       (0x0000000000401186)
    DW_AT_name  ("f1")
DW_TAG_compile_unit
  DW_AT_name    ("b.cpp")
  DW_AT_low_pc  (0x0000000000401180)
  DW_AT_high_pc (0x0000000000401186)
  DW_TAG_subprogram
    DW_AT_low_pc        (0x0000000000401180)
    DW_AT_high_pc       (0x0000000000401186)
    DW_AT_name  ("f1")

(this doesn't always happen - only if the two copies of f1 have identical instruction sequences do they end up sharing - if they are optimized differently then whichever one is chosen gets the range, and the other one gets a zero/tombstone address - like a gc'd function definition (otherwise the DWARF would be bogus - could be describing the instruction stream totally incorrectly, etc))

I'd expect this to also happen with Identical Code Folding, but at least a simple case doesn't seem to reproduce that behavior:

void f1() { }
void f2() { }
__attribute__((nodebug)) int main() {
}
clang++ -O3 test.cpp -fno-addrsig -fuse-ld=lld -Wl,--icf=all -g -ffunction-sections &&  llvm-dwarfdump a.out | grep "0x000000000\|DW_TAG\|DW_AT_name" | sed -e "s/^............//"`
DW_TAG_compile_unit
  DW_AT_name    ("test.cpp")
  DW_AT_low_pc  (0x0000000000000000)
     [0x0000000000201720, 0x0000000000201721)
     [0x0000000000000000, 0x0000000000000001))
  DW_TAG_subprogram
    DW_AT_low_pc        (0x0000000000201720)
    DW_AT_high_pc       (0x0000000000201721)
    DW_AT_name  ("f1")
  DW_TAG_subprogram
    DW_AT_low_pc        (0x0000000000000000)
    DW_AT_high_pc       (0x0000000000000001)
    DW_AT_name  ("f2")

Actually LLVM's LTO seems least likely to produce overlapping ranges - its IR representation doesn't map one function to multiple DWARF subprograms (the mapping from IR function to subprogram is from the IR function to a single DISubprogram - it can't represent more than one subprogram associated with a given IR function). Though possibly GCC's LTO or the like might have different behavior in this regard.

I have also seen bad DWARF where you have two functions that don't share the exact same range and yet do overlap. This is mostly again in LTO binaries where the LTO linker tried to change the DWARF.

I'd be curious to hear more about that/see a reproduction if you've got one

LGTM. I think either Adrian or Jonas should probably give the final accept as this is for dsymutil and I am not the code owner.

aprantl accepted this revision.Apr 22 2022, 10:35 AM
aprantl added inline comments.
llvm/include/llvm/DWARFLinker/DWARFLinkerAddressRangesMap.h
26–30

I am OK with doing such refactoring, but would prefer to do it separately from this review.

That would be awesome!

This revision is now accepted and ready to land.Apr 22 2022, 10:35 AM

(be good to fix the patch description/commit message - this is actually less likely (no case that I know of) to happen due to (at least LLVM's) LTO, and isn't an error as such (well, isn't a /bug/ but someone might an argument that it's not valid according to the DWARF spec?) in the cases I know of that do happen)

avl edited the summary of this revision. (Show Details)Apr 26 2022, 3:00 AM
avl updated this revision to Diff 425828.Apr 28 2022, 10:13 AM

rebased on D124350

avl added a comment.Apr 28 2022, 1:25 PM

@clayborg Could you mind to check changes for AddressRangess class, please?

I have changed the behavior so that it combines following kinds of ranges:

[0x100,0x200) and [0x200, 0x300) -> [0x100, 0x300)

It can affect llvm-gsymutil.

avl added a comment.May 24 2022, 3:49 AM

@clayborg Greg, Could you check&accept this review directly, please?

After requested refactoring is done(https://reviews.llvm.org/D123469?id=423119#inline-1190512)
the AddressRanges class was changed. It combines the ranges like this:

[0x100,0x200) and [0x200, 0x300) -> [0x100, 0x300)

It can affect llvm-gsymutil. I think it should be OK to combine ranges like this. Please check that behavior.

@clayborg Greg, Could you check&accept this review directly, please?

After requested refactoring is done(https://reviews.llvm.org/D123469?id=423119#inline-1190512)
the AddressRanges class was changed. It combines the ranges like this:

[0x100,0x200) and [0x200, 0x300) -> [0x100, 0x300)

It can affect llvm-gsymutil. I think it should be OK to combine ranges like this. Please check that behavior.

Sorry, just verified that this will be ok for llvm-gsymutil. The main questions is if this is something that everyone will want from an AddressRanges class. Maybe it would be best to add a default bool argument? See inlined comment

llvm/include/llvm/ADT/AddressRanges.h
55 ↗(On Diff #425828)

Does this default to having 1 contained address range when the size integer isn't specified? If so that is good. Might be nice to specify that in the using? Also, this shouldn't be public as uses can now get ahold of the "Collection::iterator" from the insert() and muck with our structure. See inline comment below.

70 ↗(On Diff #425828)

No one should be playing with or know about the implementation within this class for the "Collection::iterator". I know you use this iterator in the AddressRangesMap class, maybe we can make a "Collection::iterator insert_impl(AddressRange Range);" that is protected so that the AddressRangesMap can use this, and then leave this function as it was with the possibility of adding a new bool to request that ranges be combined?

void insert(AddressRange Range, bool CombineAdjacent = true);

The idea would be we always combine overlapping address ranges, so CombineAdjacent would combine when they don't overlap but one of the boundaries matches. It would be also good to document this in the header doc as to what "insert" will do in all cases. Before this was a class only used by llvm-gsymutil, but now with wider adoption it would be good to document.

avl added a comment.May 24 2022, 12:31 PM

Sorry, just verified that this will be ok for llvm-gsymutil. The main questions is if this is something that everyone will want from an AddressRanges class. Maybe it would be best to add a default bool argument? See inlined comment

It would probably be better to add such bool argument(or other mechanism) when the real case become clear. So that we implemented it in the most convenient way(bool argument to the constructor, bool argument to the insert method, or probably separate class). It looks like current usages do not want that functionality.

llvm/include/llvm/ADT/AddressRanges.h
55 ↗(On Diff #425828)

Does this default to having 1 contained address range when the size integer isn't specified? If so that is good. Might be nice to specify that in the using?

not exactly one range. Following is the comment from SmallVector.h:

/// In the absence of a well-motivated choice for the number of inlined
/// elements \p N, it is recommended to use \c SmallVector<T> (that is,
/// omitting the \p N). This will choose a default number of inlined elements
/// reasonable for allocation on the stack (for example, trying to keep \c
/// sizeof(SmallVector<T>) around 64 bytes)

if you think it is better to specify SmallVector<T, 1> - will change it.

Also, this shouldn't be public as uses can now get ahold of the "Collection::iterator" from the insert() and muck with our structure. See inline comment below.

Ok, will hide it under "protected" clause.

70 ↗(On Diff #425828)

No one should be playing with or know about the implementation within this class for the "Collection::iterator". I know you use this iterator in the AddressRangesMap class, maybe we can make a "Collection::iterator insert_impl(AddressRange Range);" that is protected so that the AddressRangesMap can use this, and then leave this function as it was with the possibility of adding a new bool to request that ranges be combined?

Ok, will make insert_impl protected.

void insert(AddressRange Range, bool CombineAdjacent = true);

The idea would be we always combine overlapping address ranges, so CombineAdjacent >would combine when they don't overlap but one of the boundaries matches. It would be >also good to document this in the header doc as to what "insert" will do in all cases. Before >this was a class only used by llvm-gsymutil, but now with wider adoption it would be good to >document.

I would suggest to not introduce CombineAdjacent at current moment. gsymutil and dwarfutil do not need that functionality. If it would be needed by some future code then we will add it in the most convenient way. if that is OK, will update a comment accordingly.

Ok, if we just protect the Collection::iterator stuff in AddessRanges, this will be good to go.

llvm/include/llvm/ADT/AddressRanges.h
55 ↗(On Diff #425828)

Ok, leave SmallVector<T> as is, I was unaware of this nice feature.

70 ↗(On Diff #425828)

Fine to add it later, maybe add a comment letting users knows that any adjacent address ranges will be combined. I am not sure what people would expect here, but you are correct in that all usage of this wants adjacent ranges to be combined, so all good.

avl updated this revision to Diff 432041.May 25 2022, 10:25 AM

addressed comments(changed returning value of insert() method:
Collection::iterator -> Collection::const_iterator as in begin()/end(),
updated header comment do describe behavior for adjacent ranges).

address ranges look good to me.

avl updated this revision to Diff 446222.Jul 20 2022, 11:44 AM

rebased.

This revision was landed with ongoing or failed builds.Jul 21 2022, 3:16 AM
This revision was automatically updated to reflect the committed changes.