Search code examples
c++vectorlldbwatchpoint

Setting watchpoints for large data structures in lldb


I am learning lldb and I am curious how you go about setting watchpoints for larger data structures for example a vector. I know that I can use print and that works but I get a message saying that watch points of size "x" are not supported. Is there a way around this? Thanks for the help!

(lldb) s
Process 36110 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step in
    frame #0: 0x0000000100001600 a.out`main at test.cpp:10
   7        vector<int> arr;
   8        arr.push_back(1);
   9        arr.push_back(2);
-> 10       arr.push_back(3);
   11       arr.push_back(4);
   12       arr.push_back(5);
   13
Target 0: (a.out) stopped.
(lldb) print arr
(std::__1::vector<int, std::__1::allocator<int> >) $2 = size=2 {
  [0] = 1
  [1] = 2
}
(lldb) w s v arr
error: Watchpoint creation failed (addr=0x7ffeefbff458, size=24, variable expression='arr').
error: watch size of 24 is not supported

Solution

  • If you are on a Mac, the x86_64 architecture allows 4 separate watched regions of at most 8 bytes each. At present, lldb will only use one region per watch request. It could gang multiple watch regions together to handle larger requests which would work for this structure. Feel free to file an enhancement request for this feature with http://bugs.llvm.org. But watchpoints are really limited resources, so you generally have to be very targeted about what you are trying to watch - which is probably why nobody's gotten around to supporting > 8 bytes.

    If you want to stop when elements get added to or removed from the vector, it's good enough to watch the end pointer in the vector (i.e. __end_). You can see the actual guts of the vector with the --raw argument to "frame var":

    (lldb) fr v --raw arr
    (std::__1::vector<int, std::__1::allocator<int> >) arr = {
      std::__1::__vector_base<int, std::__1::allocator<int> > = {
        __begin_ = 0x0000000100400000
        __end_ = 0x000000010040001c
        __end_cap_ = {
          std::__1::__compressed_pair_elem<int *, 0, false> = {
            __value_ = 0x0000000100400038
          }
        }
      }
    }
    

    Whenever the vector grows or shrinks, the end marker will get adjusted, so a watchpoint set with:

    (lldb) watch set v arr.__end_
    Watchpoint created: Watchpoint 1: addr = 0x7ffeefbff1c8 size = 8 state = enabled type = w
        declare @ '/tmp/vectors.cpp:6'
        watchpoint spec = 'arr.__end_'
        new value: 0x000000010030020c
    

    will catch push_back, erase, etc.

    If you want to stop when the vector values change, you're going to have to watch individual values; given only 32 bytes to play with you're not going to watch all the data in a vector of meaningful size. And of course when the vector resizes, your watchpoint on the old data will now be pointing to freed memory...