Repository navigation
Conversation
This can happen with Cell<u64> and Cell<usize> not resolving correctly in LLDB and causes hangs.
|
rustbot has assigned @Mark-Simulacrum. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
@Walnut356, do you have an opinion on whether a test is worth it for this? |
|
Hmm, tbh I think Rc is barely tested at all atm anyway (for good reason, atm it often reads OoB memory if the allocator doesn't 0 the portion after any |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque, is_atomic: bool = False): | ||
| self.valobj = valobj | ||
|
|
||
| self.ptr = unwrap_unique_or_non_null(self.valobj.GetChildMemberWithName("ptr")) | ||
|
|
||
| self.value = self.ptr.GetChildMemberWithName("data" if is_atomic else "value") | ||
|
|
||
| self.strong = unwrap_scalar_wrappers(self.ptr.GetChildMemberWithName("strong")) | ||
| self.weak = unwrap_scalar_wrappers(self.ptr.GetChildMemberWithName("weak")) | ||
|
|
||
| self.value_builder = ValueBuilder(valobj) | ||
|
|
||
| self.update() | ||
|
|
||
| def num_children(self) -> int: | ||
| # Actually there are 3 children, but only the `value` should be shown as a child | ||
| return 1 | ||
|
|
||
| def get_child_index(self, name: str) -> int: | ||
| if name == "value": | ||
| return 0 | ||
| if name == "strong": | ||
| return 1 | ||
| if name == "weak": | ||
| return 2 | ||
| return -1 | ||
|
|
||
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | ||
| if index == 0: | ||
| return self.value | ||
| if index == 1: | ||
| return self.value_builder.from_uint("strong", self.strong_count) | ||
| if index == 2: | ||
| return self.value_builder.from_uint("weak", self.weak_count) | ||
|
|
||
| return None | ||
|
|
||
| def update(self): | ||
| self.strong_count = self.strong.GetValueAsUnsigned() | ||
| self.weak_count = self.weak.GetValueAsUnsigned() - 1 |
There was a problem hiding this comment.
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque, is_atomic: bool = False): | |
| self.valobj = valobj | |
| self.ptr = unwrap_unique_or_non_null(self.valobj.GetChildMemberWithName("ptr")) | |
| self.value = self.ptr.GetChildMemberWithName("data" if is_atomic else "value") | |
| # infallibly gets an unsigned integer type of at least 64 bits. We don't need to worry about | |
| # whether or not `usize` is actually smaller than that since we don't ever display the | |
| # underlying type to the user anyway | |
| usize_type = valobj.GetTarget().GetBasicType(eBasicTypeUnsignedLongLong) | |
| self.strong = self.ptr.GetChildMemberWithName("strong").Cast(usize_type) | |
| self.weak = self.ptr.GetChildMemberWithName("weak").Cast( | |
| usize_type | |
| ) | |
| def num_children(self) -> int: | |
| # Actually there are 3 children, but only the `value` should be shown as a child | |
| return 1 | |
| def get_child_index(self, name: str) -> int: | |
| if name == "value": | |
| return 0 | |
| if name == "strong": | |
| return 1 | |
| if name == "weak": | |
| return 2 | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| if index == 0: | |
| return self.value | |
| if index == 1: | |
| return self.strong | |
| if index == 2: | |
| return self.weak | |
| return None | |
| def update(self): | |
| pass |
Along the lines of this, where we only operate on SBValue objects
| strong = valobj.GetChildMemberWithName("strong").GetValueAsUnsigned() | ||
| weak = valobj.GetChildMemberWithName("weak").GetValueAsUnsigned() | ||
| return "strong={}, weak={}".format(strong, weak) |
There was a problem hiding this comment.
| strong = valobj.GetChildMemberWithName("strong") | |
| weak = valobj.GetChildMemberWithName("weak") | |
| if not (strong.IsValid() and weak.IsValid()): | |
| strong = "?" | |
| weak = "?" | |
| print("Unable to read ref count, see llvm/llvm-project#196812") | |
| else: | |
| strong = strong.GetValueAsUnsigned() | |
| weak = weak.GetValueAsUnsigned() | |
| return "strong={}, weak={}".format(strong, weak) |
And then return some kind of dummy value and error message.
Also we can subtract 1 from weak here before printing it to the user if that's desirable. The existing Rc synthetic modifies the child itself, but that's not super necessary since it also hides that child from the user.
It's maybe not entirely necessary though because the weak count does start at 1, but iunno.
|
Should we merge this first before changing more of it? I would bias towards some minimal changes to fix the infinite loop and then can iterate on the behavior? What do you think? |
|
Fundamentally, i think this sort of looping behavior is always going to be finicky and unreliable. Whether we patch the infinite loop then delete the loop, or just delete the loop immediately isnt a huge deal to me. I do think we need some kind of warning and dummy value for sure though. A debugger outputting incorrect dara is worse than one that outputs nothing at all. |
|
It's not that debuggers don't output things but they get stuck making it impossible to even debug. I will close this as it seems there is a different direction / cleanup you may want to take it and I don't have bandwidth to test and iterate on suggestions. So, I'll focus on getting the codelldb PR vadimcn/codelldb#1413 over the line instead as it's more valuable if it can defend against these type of infinite loop bugs in visualizers. |
[debugger visualizers] Add workaround to read `Rc` strong/weak counts Implements a fix with the same goal as rust-lang#162725 of removing the infinite loop. Since `RcInner` is `#[repr(C)]`, we can just directly read the values from their known offsets. Worth noting that LLDB fails to even populate the fields and offsets when this bug occurs, so `#[repr(C)]` is a required invariant to retrieve the values from addresses like this: ``` (lldb) script lldb.frame.var("holder").GetNonSyntheticValue().GetChildAtIndex(0).GetChildAtIndex(0).GetChildAtIndex(0).GetType().GetPointeeType() template<> struct RcInner<unsigned int> { private: unsigned int value; } ``` That `RcInner` should have a `strong` and `weak` field, but because LLDB can't read the type node, it omits them entirely. Thankfully, the `value` field is still read correctly and has an accurate offset. This change has 1 small side effect, which is that the `weak` count is not decremented before it is displayed to the user. We can easily do that in the summary function, but imo it feels weird to lie about the value in memory? The `weak` count starts at 1 because the original `Rc` instance "holds" both a strong and a weak reference. It's mostly an artifact of `Rc`'s implementation, but if you're debugging, that seems like something that might matter.
[debugger visualizers] Add workaround to read `Rc` strong/weak counts Implements a fix with the same goal as rust-lang#162725 of removing the infinite loop. Since `RcInner` is `#[repr(C)]`, we can just directly read the values from their known offsets. Worth noting that LLDB fails to even populate the fields and offsets when this bug occurs, so `#[repr(C)]` is a required invariant to retrieve the values from addresses like this: ``` (lldb) script lldb.frame.var("holder").GetNonSyntheticValue().GetChildAtIndex(0).GetChildAtIndex(0).GetChildAtIndex(0).GetType().GetPointeeType() template<> struct RcInner<unsigned int> { private: unsigned int value; } ``` That `RcInner` should have a `strong` and `weak` field, but because LLDB can't read the type node, it omits them entirely. Thankfully, the `value` field is still read correctly and has an accurate offset. This change has 1 small side effect, which is that the `weak` count is not decremented before it is displayed to the user. We can easily do that in the summary function, but imo it feels weird to lie about the value in memory? The `weak` count starts at 1 because the original `Rc` instance "holds" both a strong and a weak reference. It's mostly an artifact of `Rc`'s implementation, but if you're debugging, that seems like something that might matter.
Rollup merge of #163098 - Walnut356:rc_usize, r=Mark-Simulacrum [debugger visualizers] Add workaround to read `Rc` strong/weak counts Implements a fix with the same goal as #162725 of removing the infinite loop. Since `RcInner` is `#[repr(C)]`, we can just directly read the values from their known offsets. Worth noting that LLDB fails to even populate the fields and offsets when this bug occurs, so `#[repr(C)]` is a required invariant to retrieve the values from addresses like this: ``` (lldb) script lldb.frame.var("holder").GetNonSyntheticValue().GetChildAtIndex(0).GetChildAtIndex(0).GetChildAtIndex(0).GetType().GetPointeeType() template<> struct RcInner<unsigned int> { private: unsigned int value; } ``` That `RcInner` should have a `strong` and `weak` field, but because LLDB can't read the type node, it omits them entirely. Thankfully, the `value` field is still read correctly and has an accurate offset. This change has 1 small side effect, which is that the `weak` count is not decremented before it is displayed to the user. We can easily do that in the summary function, but imo it feels weird to lie about the value in memory? The `weak` count starts at 1 because the original `Rc` instance "holds" both a strong and a weak reference. It's mostly an artifact of `Rc`'s implementation, but if you're debugging, that seems like something that might matter.
[debugger visualizers] Add workaround to read `Rc` strong/weak counts Implements a fix with the same goal as rust-lang/rust#162725 of removing the infinite loop. Since `RcInner` is `#[repr(C)]`, we can just directly read the values from their known offsets. Worth noting that LLDB fails to even populate the fields and offsets when this bug occurs, so `#[repr(C)]` is a required invariant to retrieve the values from addresses like this: ``` (lldb) script lldb.frame.var("holder").GetNonSyntheticValue().GetChildAtIndex(0).GetChildAtIndex(0).GetChildAtIndex(0).GetType().GetPointeeType() template<> struct RcInner<unsigned int> { private: unsigned int value; } ``` That `RcInner` should have a `strong` and `weak` field, but because LLDB can't read the type node, it omits them entirely. Thankfully, the `value` field is still read correctly and has an accurate offset. This change has 1 small side effect, which is that the `weak` count is not decremented before it is displayed to the user. We can easily do that in the summary function, but imo it feels weird to lie about the value in memory? The `weak` count starts at 1 because the original `Rc` instance "holds" both a strong and a weak reference. It's mostly an artifact of `Rc`'s implementation, but if you're debugging, that seems like something that might matter.
If you have code like the one below and set a break-point on the first
println!line with code lldb you end up getting your whole editor stuck due to the loop i just changed.This is also related to also the problem described here: llvm/llvm-project#196812
After this PR it will show the wrong strong / weak counts for the Rc (both 0), but at least doesn't freeze the debug session.
I reproduced it in Zed, on arch, but I suspect many editors work the same.
codelldbattempts a cancellation for stuck responses, but it only cancels one so it doesn't help for the example above.Further on, I will follow up with a codelldb PR as well to cancel checks
insidethe struct / field summaries, instead just the top-level variables (to further harden these cases).