For a bit of context, I’m maintaining the visualizer scripts for Rust. Some of this might have gone a bit over my head, so let me know if I’m misunderstanding anything:
- LLDB will retain a valobj if it has the same address as it did on the prior step
- On the new step, it will read from that address to update its value
For example, given:
let mut x: u8 = 5;
x += 10;
x will have the same ValueObject on both steps, but a different value. The same is true for synthetic children of ValueObjects
- This update process is distinct from the caching process (and thus happens whether you return
True or False from SyntheticProvider.update(), since the caching process completely yeets the old ValueObject and replaces it with a new one
To sum all that up, when you tell LLDB to cache update, the intention is to store the total number of children and the address of each child (via the child SBValue’s).
There’s a few more questions I had if the logic works like that, but I did some manual testing and either I’m drastically misunderstanding or something is broken.
To quote the full text of the footnote for update()
This method is optional. Also, a boolean value must be returned (since lldb 3.1.0). If False is returned, then whenever the process reaches a new stop, this method will be invoked again to generate an updated list of the children for a given variable. Otherwise, if True is returned, then the value is cached and this method won’t be called again, effectively freezing the state of the value in subsequent stops. Beware that returning True incorrectly could show misleading information to the user.
I don’t know if there’s a single statement in that that is correct at the moment.
-
a boolean value isn’t required to be returned. Returning None works fine (though this may just be None being coerced to False?)
-
update is not invoked at a breakpoint. It’s invoked (twice for some reason) when you request the variable (e.g. frame var x), but only the first time at each breakpoint, regardless of whether update returns True or False
-
At subsequent breakpoints, update is called again (twice) for the value regardless of whether True or False is returned. This appears to be because the underlying ValueObject is not retained between breakpoints.
Here’s a quick demonstration of this behavior. Here is the update function, it prints out every time it is run, and includes the SBValue.GetID() and SBValue.changed values:
def update(self):
print(f"update for var '{self.valobj.name}' ({self.valobj.GetID()=}) ({self.valobj.changed=})")
self.length = self.valobj.GetChildMemberWithName("length").GetValueAsUnsigned()
self.data_ptr: SBValue = self.valobj.GetChildMemberWithName("data_ptr")
self.element_type = self.data_ptr.GetType().GetPointeeType()
self.element_size = self.element_type.GetByteSize()
return True
And the output from LLDB
Process 31272 stopped
* thread #1, name = 'main', stop reason = breakpoint 1.1
frame #0: 0x00007ff6e2b11a2d sample.exe`sample::main::hdd4c521338427624 at main.rs:53:9
50
51 fn main() {
52 let mut x = [1, 2, 3, 4].as_slice();
-> 53 x = x;
54 let y = [5, 6, 7, 8, 9, 10].as_slice();
55 x = y;
56
(lldb) v
update for var 'x' (self.valobj.GetID()=1) (self.valobj.changed=False)
update for var 'x' (self.valobj.GetID()=1) (self.valobj.changed=False)
(&[i32]) x = size=4 {
[0] = 1
[1] = 2
[2] = 3
[3] = 4
}
(lldb) v
(&[i32]) x = size=4 {
[0] = 1
[1] = 2
[2] = 3
[3] = 4
}
(lldb) s
Process 31272 stopped
* thread #1, name = 'main', stop reason = step in
frame #0: 0x00007ff6e2b11a41 sample.exe`sample::main::hdd4c521338427624 at main.rs:54:33
51 fn main() {
52 let mut x = [1, 2, 3, 4].as_slice();
53 x = x;
-> 54 let y = [5, 6, 7, 8, 9, 10].as_slice();
55 x = y;
56
57 dbg!(x);
(lldb) v
update for var 'x' (self.valobj.GetID()=20) (self.valobj.changed=False)
update for var 'x' (self.valobj.GetID()=20) (self.valobj.changed=False)
(&[i32]) x = size=4 {
[0] = 1
[1] = 2
[2] = 3
[3] = 4
}
(lldb) thread step-over
Process 31272 stopped
* thread #1, name = 'main', stop reason = step over
frame #0: 0x00007ff6e2b11a5d sample.exe`sample::main::hdd4c521338427624 at main.rs:55:5
52 let mut x = [1, 2, 3, 4].as_slice();
53 x = x;
54 let y = [5, 6, 7, 8, 9, 10].as_slice();
-> 55 x = y;
56
57 dbg!(x);
58 // let signed = -1i8;
(lldb) s
Process 31272 stopped
* thread #1, name = 'main', stop reason = step in
frame #0: 0x00007ff6e2b11a67 sample.exe`sample::main::hdd4c521338427624 at main.rs:57:5
54 let y = [5, 6, 7, 8, 9, 10].as_slice();
55 x = y;
56
-> 57 dbg!(x);
58 // let signed = -1i8;
59 // let chr = 'c';
60 // let val1 = 1u8;
(lldb) v
update for var 'x' (self.valobj.GetID()=39) (self.valobj.changed=False)
update for var 'x' (self.valobj.GetID()=39) (self.valobj.changed=False)
update for var 'y' (self.valobj.GetID()=61) (self.valobj.changed=False)
update for var 'y' (self.valobj.GetID()=61) (self.valobj.changed=False)
(&[i32]) x = size=6 {
[0] = 5
[1] = 6
[2] = 7
[3] = 8
[4] = 9
[5] = 10
}
(&[i32]) y = size=6 {
[0] = 5
[1] = 6
[2] = 7
[3] = 8
[4] = 9
[5] = 10
}