how to dump string using Windbg poi function - debugging

Debugging .Net String value in windbg and WinDbg and SoS, how do I print/dump a large string? show a script that dump the string to a local file:
$$ Dumps the managed strings to a file
$$ Platform x86
$$ Usage $$>a<"c:\temp\dumpstringtofolder.txt" 6544f9ac 5000 c:\temp\stringtest
$$ First argument is the string method table pointer
$$ Second argument is the Min size of the string that needs to be used filter the strings
$$ Third is the path of the file
.foreach ($string {!dumpheap -short -mt ${$arg1} -min ${$arg2}})
{
$$ MT Field Offset Type VT Attr Value Name
$$ 65452978 40000ed 4 System.Int32 1 instance 71117 m_stringLength
$$ 65451dc8 40000ee 8 System.Char 1 instance 3c m_firstChar
$$ 6544f9ac 40000ef 8 System.String 0 shared static Empty
$$ start of string is stored in the 8th offset, which can be inferred from above
$$ Size of the string which is stored in the 4th offset
r#$t0= poi(${$string}+4)*2
.writemem ${$arg3}${$string}.txt ${$string}+8 ${$string}+8+#$t0
}
this script is on x86. I modify the code and try on .net 4.0, x64 system.
the only difference is that the offset is different. for example:
$$ .net 4.0 , the offset is different
$$ MT Field Offset Type VT Attr Value Name
$$ 000007fee4abc7e8 4000103 8 System.Int32 1 instance 460 m_stringLength
$$ 000007fee4abb328 4000104 c System.Char 1 instance 26 m_firstChar
$$ 000007fee4ab6900 4000105 10 System.String 0 shared static Empty
So, I change my code to:
r#$t0= poi(${$string}+8)*2
.writemem ${$arg3}${$string}.txt ${$string}+c ${$string}+c+#$t0
Here, I wonder the function poi,
1. why here need '*2'?
2. I find a string address, run !do, like this:
0:000> !do 0x00000000ffad0de0
Name: System.String
MethodTable: 000007fef5da6738
EEClass: 000007fef592ed68
Size: 794(0x31a) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: jv15Rr2HXPn3....
Fields:
MT Field Offset Type VT Attr Value Name
000007fef5dac620 4000103 8 System.Int32 1 instance 384 m_stringLength
000007fef5dab160 4000104 c System.Char 1 instance 6a m_firstChar
000007fef5da6738 4000105 10 System.String 0 shared static Empty
I run
0:000> ? poi(0x00000000ffad0de0)+8
Evaluate expression: 8791627818816 = 000007fe`f5da6740
to get the length value of 384, but the output value is wrong(displayed is 8791627818816).
What is wrong?
Thank you for helping me!
updated 1):
I tried:
0:000> r#$t0= poi(0x00000000ffad0de0+8)
0:000> r#$t0
$t0=0076006a00000180
the result seems not correct.
Update 2):
I tried to debug the script:
.echo ${$str}
r#$t0= poi(${$str}+8)*2
.printf "#$t0 is %d\n", #$t0
.echo ${$arg3}${$str}.txt
.printf "${$str}+c: %p\n", ${$str}+c
.printf "${$str}+c+#$t0: %p\n", ${$str}+c+#$t0
$$.writemem ${$arg3}${$str}.txt ${$str}+c ${$str}+c+#$t0
then I got the output:
0x00000000ffad4550
#$t0 is 640
c:\stringtest\0x00000000ffad4550.txt
0x00000000ffad4550+c: 00000000ffad455c
0x00000000ffad4550+c+#$t0: 00ec00d4ffad47dc
then I run the .writemem for the output address:
0:000> .writemem c:\stringtest\ss.txt 00000000ffad455c L0n640
Writing 280 bytes.
I got the correct string, it's like this:
/txrqcf...........j7ULyzqxSmB3bpu
I run the command:
0:000> .writemem c:\stringtest\ss.txt 00000000ffad455c 00ec00d4ffad47dc
^ Range error
I got the error Range error, I check the link to find out the result, but I have no idea.

poi(${$string}+8) is the string length in characters.
*2 is needed because Strings in C# are Unicode and for writing memory, we need the bytes, not characters.
+c is the offset of the char[] on 64 bit. That's the start address to write from.
? poi(0x00000000ffad0de0)+8
This is incorrect, because poi(0x00000000ffad0de0) gives you the value of the method table of the .NET object, which is 000007fef5da6738 and then you add 8, which is 7FEF5DA6740.
What you want to do is
? poi(0x00000000ffad0de0+8)
inside the braces.
To bypass the range error, use the L? syntax instead range start and range end:
.writemem ${$arg3}${$str}.txt ${$str}+c L? #$t0

Related

Does the args command in Delve also show the return value (and not just the function arguments)?

During reverse engineering a Golang binary using Delve I was inspecting the full stack in which frame 0 belonged to the Go function strings.genSplit:
(dlv) stack -full
0 0x00000000004a4278 in strings.genSplit
at /usr/lib/go/src/strings/strings.go:266
s = "my_string" my comment: actual string modified but not the rest of the variables
sep = ","
sepSave = 0
n = 11
~r4 = (unreadable empty OP stack)
a = []string len: 12, cap: 12, [...]
i = (unreadable could not find loclist entry at 0x9d89c for address 0x4a4278)
m = (unreadable could not find loclist entry at 0x9da7d for address 0x4a4278)
Now if you look at the documentation for strings.genSplit here, you can see that s, sep, sepSave, and n are the actual (input) arguments while a, i, and m are the local variables of the function. Also
(dlv) goroutine 22 frame 0 locals
a = []string len: 12, cap: 12, [...]
i = (unreadable could not find loclist entry at 0x9d89c for address 0x4a4278)
m = (unreadable could not find loclist entry at 0x9da7d for address 0x4a4278)
and
(dlv) goroutine 22 frame 0 args
s = "my_string" my comment: actual string modified but not the rest of the variables
sep = ","
sepSave = 0
n = 11
~r4 = (unreadable empty OP stack)
My questions are 1. Is Delve including the return value with the actual function arguments (i.e., inputs)? 2. What do the ~ sign and 4 in ~r4 mean?
trying to answer both your questions in one step.
In go function arguments and results are allocated in the stack on function call, so the return variables are allocated as soon as you call the function.
If you use named return values, delve will show you the variable name, otherwise it will use the internal representation that will start at 0 for the first argument and go on through the return values.
In the case you mention you have 4 arguments that would be r0-r3 and one unnamed return that is r4. The ~ sign denotes an unnamed value and I've only seen it used to also represent ~panic.

Declare an lldb summary-string for a sized string type

I would like to have a formatter for the buildin string type of the nim language, but somehow I fail at providing it. Nim compilis to c, and the c representation of the string type you see here:
#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER)
# define SEQ_DECL_SIZE /* empty is correct! */
#else
# define SEQ_DECL_SIZE 1000000
#endif
typedef char NIM_CHAR;
typedef long long int NI64;
typedef NI64 NI;
struct TGenericSeq {NI len; NI reserved; };
struct NimStringDesc {TGenericSeq Sup; NIM_CHAR data[SEQ_DECL_SIZE]; };
and here is the output of what I have tried in the lldb session:
(lldb) frame variable *longstring
(NimStringDesc) *longstring = {
Sup = (len = 9, reserved = 15)
data = {}
}
(lldb) frame variable longstring->data
(NIM_CHAR []) longstring->data = {}
(lldb) type summary add --summary-string "${&var[0]%s}" "NIM_CHAR []"
(lldb) frame variable longstring->data
(NIM_CHAR []) longstring->data = {}
(lldb) type summary add --summary-string "${var%s}" "NIM_CHAR *"
(lldb) frame variable longstring->data
(NIM_CHAR []) longstring->data = {}
(lldb) frame variable &longstring->data[0]
(NIM_CHAR *) &[0] = 0x00007ffff7f3a060 "9 - 3 - 2"
(lldb) frame variable *longstring
(lldb) type summary add --summary-string "${var.data%s}" "NimStringDesc"
(lldb) frame variable *longstring
(NimStringDesc) *longstring = NIM_CHAR [] # 0x7ffff7f3a060
(lldb) type summary add --summary-string "${&var.data[0]%s}" "NimStringDesc"
(lldb) frame variable *longstring
(NimStringDesc) *longstring = {
Sup = (len = 9, reserved = 15)
data = {}
}
(lldb)
I simply can't manage, that the output will just be data interpreted as a '\0' terminated c-string
The summary string syntax you've tried is (by design) not as syntax rich as C.
And since you're using a zero-sized array, I don't think we have any magic provision to treat that as a pointer-to string. You might want to file a bug about it, but in this case, it's arguable whether it would help you. Since your string is length-encoded it doesn't really need to be zero-terminated, and that is the only hint LLDB would understand out of the box to know when to stop reading out of a pointer-to characters.
In your case, you're going to have to resort to Python formatters
The things you need are:
the memory location of the string buffer
the length of the string buffer
a process to read memory out of
This is a very small Python snippet that does it - I'll give you enhancement suggestions as well, but let's start with the basics:
def NimStringSummary(valobj,stuff):
l = valobj.GetChildMemberWithName('Sup').GetChildMemberWithName('len').GetValueAsUnsigned(0)
s = valobj.GetChildMemberWithName('data').AddressOf()
return '"%s"'% valobj.process.ReadMemory(s.GetValueAsUnsigned(0),l,lldb.SBError())
As you can see, first of all it reads the value of the length field;
then it reads the address-of the data buffer; then it uses the process that the value comes from to read the string content, and returns it in quotes
Now, this is a proof of concept. If you used it in production, you'd quickly run into a few issues:
What if your string buffer hasn't been initialized yet, and it says the size of the buffer is 20 gigabytes? You're going to have to limit the size of the data you're willing to read. For string-like types it has builtin knowledge of (char*, std::string, Swift.String, ...) LLDB prints out the truncated buffer followed by ..., e.g.
(const char*) buffer = "myBufferIsVeryLong"...
What if the pointer to the data is invalid? You should check that s.GetValueAsUnsigned(0) isn't actually zero - if it is you might want to print an error message like "null buffer".
Also, here I just passed an SBError that I then ignore - it would be better to pass one and then check it
All in all, you'd end up with something like:
import lldb
import os
def NimStringSummary(valobj,stuff):
l = valobj.GetChildMemberWithName('Sup').GetChildMemberWithName('len').GetValueAsUnsigned(0)
if l == 0: return '""'
if l > 1024: l = 1024
s = valobj.GetChildMemberWithName('data').AddressOf()
addr = s.GetValueAsUnsigned(0)
if addr == 0: return '<null buffer>'
err = lldb.SBError()
buf = valobj.process.ReadMemory(s.GetValueAsUnsigned(0),l,err)
if err.Fail(): return '<error: %s>' % str(err)
return '"%s"' % buf
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand("type summary add NimStringDesc -F %s.NimStringSummary" % os.path.splitext(os.path.basename(__file__))[0])
The one extra trick is the __lldb_init_module function - this function is automatically called by LLDB whenever you 'command script import' a python file. This will allow you to add the 'command script import ' to your ~/.lldbinit file and automatically get the formatter to be picked up in all debug sessions
Hope this helps!

Ruby: FormatMessageW trouble

What's a correct way to call FormatMessageW in Ruby?
require 'win32api'
FormatMessage = Win32API.new 'kernel32', 'FormatMessageW', 'IPIIPII', 'I'
msg = '\0' * 255
FormatMessage.call 0x00001000 | 0x00000100, nil, 6, 1024, msg, 0, 0
FormatMessage returns not null result but msg contains not readable message. What's wrong?
I believe this is the code you are looking for:
require 'win32api'
FORMAT_MESSAGE_FROM_SYSTEM = 0x1000
FormatMessage = Win32API.new 'kernel32', 'FormatMessageW', 'IPIIPIP', 'I'
msgw = ("\x00" * 256).force_encoding("UTF-16LE")
count = FormatMessage.call FORMAT_MESSAGE_FROM_SYSTEM, nil, 6, 1033, msgw, msgw.size, nil
msgw = msgw[0, count]
msg = msgw.encode("UTF-8")
puts msg
When I run this with Ruby, the output is "The handle is invalid.", which is the correct Windows error message for error code 6.
There were some problems with your original code.
The last argument to FormatMessageW is a pointer, so you should use P instead of I, especially if you want it to work on 64-bit Windows.
In Ruby '\0' is actually a two-byte ASCII string, not a single-byte null character. You can confirm this by running p '\0'.bytes.to_a. It looks like you tried to allocate 255 bytes, but you actually allocated 510 bytes. You should allocate an even number of bytes because wide characters in Windows take 2 bytes.
As #theB pointed out, your first argument to FormatMessageW was wrong, since you specified that FormatMessageW should allocate its own buffer.
You specified language code 1024. I can't find a definition for that. Maybe you meant 1033, which is "English - United States". Specifying 1024 doesn't seem to actually cause problems though.
You should use force_encoding to set the encoding of your string to UTF-16LE, because that is the encoding used for wide strings in Windows (or if it's not exactly the same, at least it is compatible most of the time).
The 6th argument to FormatMessageW should be the number of characters in your buffer (which is the number of bytes divided by 2, by the way). Your code just passed 0 for that argument.
Strings in Ruby can contain any arbitrary bytes, including null characters, but it's not necessarily a good idea to let that happen because things like String#size will return surprising results. FormatMessageW returns the number of characters in the formatted message, so we can use that to trim off the null characters at the end. (Conveniently, FormatMessageW returns 0 if there is an error, so our trimming would result in an empty string.)
You should use String#encode to convert your string from UTF-16LE to UTF-8 because UTF-8 strings are much easier to operate on and print in Ruby.
If you don't care about internationalization and unicode, you could have just used FormatMessageA instead. Here is some code that will work for that:
require 'win32api'
FORMAT_MESSAGE_FROM_SYSTEM = 0x1000
FormatMessage = Win32API.new 'kernel32', 'FormatMessageA', 'IPIIPIP', 'I'
msg = ("\x00" * 256).force_encoding("ASCII-8BIT")
count = FormatMessage.call FORMAT_MESSAGE_FROM_SYSTEM, nil, 6, 1033, msg, msg.size, nil
msg = msg[0, count]
puts msg
P.S. DWORD is an unsigned integer type. I am not sure what the right letter for that is in Ruby's Win32API class; it might be that I represents a signed integer, and should be replaced by something else.

Can a breakpoint display the contents of "const unsigned char* variable"?

I'm on the trail of why the contents of a TXT record in a Bonjour service discovery is sometimes being incompletely interpreted, and I've reached a point where it would be really useful to have a breakpoint print out the contents of an unsigned char in a callback (I've tried NSLog, but using NSLog in a threaded callback can get really tricky).
The callback function is defined this way:
static void resolveCallback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
const char* fullname, const char* hosttarget, uint16_t port, uint16_t txtLen,
const unsigned char* txtRecord, void* context) {
So I'm interested in the txtRecord
Right now my breakpoint is using:
memory read --size 4 --format x --count 4 `txtRecord`
But that's only because that was an example on the lldv.llvm.org example page ;-) It's certainly showing data that I expect to be there, partially.
Do I have to apply informed knowledge of the length or can the breakpoint be coded such that it uses the length that is present? I'm thinking that instead of "hard coding" the two 4s in the example there ought to be a way to wrap in other read instructions inside back ticks like I did with the variable name.
Looking at http://lldb.llvm.org/varFormats.html I thought I'd try a format of C instead of x but that prints out series of dots which must mean I picked a wrong format or something.
I just tried
memory read `txtRecord`
and that's almost exactly what I wanted to see as it gives:
0x1c5dd884: 10 65 6e 30 3d 31 39 32 2e 31 36 38 2e 31 2e 33 .en0=192.168.1.3
0x1c5dd894: 36 0a 70 6f 72 74 3d 35 30 32 37 38 00 00 00 00 6.port=50278....
This looks really close:
memory read `txtRecord` --format C
giving:
0x1d0c6974: .en0=192.168.1.36.port=50278....
If that's the best I can get, I guess I can deal with the length bytes in front of each of the two strings in that txtRecord.
I'm asking this question because I'd like to display the actual and correct values... the bug is that sometimes the IP address comes back wrong, losing the frontmost 1, other times the port comes back "short" (in network byte order) with non-numeric characters at the end, like "502¿" instead of "50278" (in this example run).
My initial response to this question, while informative, was not complete. I originally thought the problem being reported was just about printing a c-string array of type unsigned char * where the default formatters (char *) weren't being used. That answer comes first. Then comes the answer about how to print this (somewhat unique) array of pascal strings data that the program is actually dealing with.
First answer: lldb knows how to handle the char * well; it's the unsigned char * bit that is making it behave a little worse than usual. e.g. if txtRecord were a const char *,
(lldb) p txtRecord
(const char *) $0 = 0x0000000100000f51 ".en0=192.168.1.36.port=50278"
You can copy the type summary lldb has built in for char * for unsigned char *. type summary list lists all of the built in type summaries; copying lldb-179.5's summaries for char *:
(lldb) type summary add -p -C false -s ${var%s} 'unsigned char *'
(lldb) type summary add -p -C false -s ${var%s} 'const unsigned char *'
(lldb) fr va txtRecord
(const unsigned char *) txtRecord = 0x0000000100000f51 ".en0=192.168.1.36.port=50278"
(lldb) p txtRecord
(const unsigned char *) $2 = 0x0000000100000f51 ".en0=192.168.1.36.port=50278"
(lldb)
Of course you can put these in your ~/.lldbinit file and they'll be picked up by Xcode et al from now on.
Second answer: To print the array of pascal strings that this is actually using, you'll need to create a python function. It will take two arguments, the size of the pascal string buffer (txtLen) and the address of the start of the buffer (txtRecord). Create a python file like pstrarray.py (I like to put these in a directory I made, ~/lldb) and load it into your lldb via the ~/.lldbinit file so you have the command available:
command script import ~/lldb/pstrarray.py
The python script is a little long; I'm sure someone more familiar with python could express this more concisely. There's also a bunch of error handling which adds bulk. But the main idea is to take two parameters: the size of the buffer and the pointer to the buffer. The user will express these with variable names like pstrarray txtLen txtRecord, in which case you could look up the variables in the current frame, but they might also want to use an acutal expression like pstrarray sizeof(str) str. So we need to pass these parameters through the expression evaluation engine to get them down to an integer size and a pointer address. Then we read the memory out of the process and print the strings.
import lldb
import shlex
import optparse
def pstrarray(debugger, command, result, dict):
command_args = shlex.split(command)
parser = create_pstrarray_options()
try:
(options, args) = parser.parse_args(command_args)
except:
return
if debugger and debugger.GetSelectedTarget() and debugger.GetSelectedTarget().GetProcess():
process = debugger.GetSelectedTarget().GetProcess()
if len(args) < 2:
print "Usage: pstrarray size-of-buffer pointer-to-array-of-pascal-strings"
return
if process.GetSelectedThread() and process.GetSelectedThread().GetSelectedFrame():
frame = process.GetSelectedThread().GetSelectedFrame()
size_of_buffer_sbval = frame.EvaluateExpression (args[0])
if not size_of_buffer_sbval.IsValid() or size_of_buffer_sbval.GetValueAsUnsigned (lldb.LLDB_INVALID_ADDRESS) == lldb.LLDB_INVALID_ADDRESS:
print 'Could not evaluate "%s" down to an integral value' % args[0]
return
size_of_buffer = size_of_buffer_sbval.GetValueAsUnsigned ()
address_of_buffer_sbval = frame.EvaluateExpression (args[1])
if not address_of_buffer_sbval.IsValid():
print 'could not evaluate "%s" down to a pointer value' % args[1]
return
address_of_buffer = address_of_buffer_sbval.GetValueAsUnsigned (lldb.LLDB_INVALID_ADDRESS)
# If the expression eval didn't give us an integer value, try it again with an & prepended.
if address_of_buffer == lldb.LLDB_INVALID_ADDRESS:
address_of_buffer_sbval = frame.EvaluateExpression ('&%s' % args[1])
if address_of_buffer_sbval.IsValid():
address_of_buffer = address_of_buffer_sbval.GetValueAsUnsigned (lldb.LLDB_INVALID_ADDRESS)
if address_of_buffer == lldb.LLDB_INVALID_ADDRESS:
print 'could not evaluate "%s" down to a pointer value' % args[1]
return
err = lldb.SBError()
pascal_string_buffer = process.ReadMemory (address_of_buffer, size_of_buffer, err)
if (err.Fail()):
print 'Failed to read memory at address 0x%x' % address_of_buffer
return
pascal_string_array = bytearray(pascal_string_buffer, 'ascii')
index = 0
while index < size_of_buffer:
length = ord(pascal_string_buffer[index])
print "%s" % pascal_string_array[index+1:index+1+length]
index = index + length + 1
def create_pstrarray_options():
usage = "usage: %prog"
description='''print an buffer which has an array of pascal strings in it'''
parser = optparse.OptionParser(description=description, prog='pstrarray',usage=usage)
return parser
def __lldb_init_module (debugger, dict):
parser = create_pstrarray_options()
pstrarray.__doc__ = parser.format_help()
debugger.HandleCommand('command script add -f %s.pstrarray pstrarray' % __name__)
and an example program to run this on:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
int main ()
{
unsigned char str[] = {16,'e','n','0','=','1','9','2','.','1','6','8','.','1','.','3','6',
10,'p','o','r','t','=','5','1','6','8','7'};
uint8_t *p = str;
while (p < str + sizeof (str))
{
int len = *p++;
char buf[len + 1];
strlcpy (buf, (char*) p, len + 1);
puts (buf);
p += len;
}
puts ("done"); // break here
}
and in use:
(lldb) br s -p break
Breakpoint 1: where = a.out`main + 231 at a.c:17, address = 0x0000000100000ed7
(lldb) r
Process 74549 launched: '/private/tmp/a.out' (x86_64)
en0=192.168.1.36
port=51687
Process 74549 stopped
* thread #1: tid = 0x1c03, 0x0000000100000ed7 a.out`main + 231 at a.c:17, stop reason = breakpoint 1.1
#0: 0x0000000100000ed7 a.out`main + 231 at a.c:17
14 puts (buf);
15 p += len;
16 }
-> 17 puts ("done"); // break here
18 }
(lldb) pstrarray sizeof(str) str
en0=192.168.1.36
port=51687
(lldb)
While it's cool that it's possible to do this in lldb, it's not as smooth as we'd like to see. If the size of the buffer and the address of the buffer were contained in a single object, struct PStringArray {uint16_t size; uint8_t *addr;}, that would work much better. You could define a type summary formatter for all variables of type struct PStringArray and no special commands would be required. You'd still need to write a python function, but it could get all the information it needed out of the object directly so it would disappear into the lldb type format system. You could just write (lldb) p strs and the custom formatter function would be called on strs to print all the strings in there.

How can I find out what is changing an object (or a simple variable) in Xcode 4 / lldb?

In some debuggers this is called "setting a trap" on a variable. What I want to do is trigger a breakpoint on any statement that changes the object. Or changes a property of the object.
I have an NSMutableDictionary that gets a value/key added to it but I can't find any statement that could be doing that.
You can set a watchpoint (from here):
Set a watchpoint on a variable when it is written to.
(lldb) watchpoint set variable -w write global_var
(lldb) watch set var -w write global_var
(gdb) watch global_var
Set a watchpoint on a memory location when it is written into. The size of the region to watch for defaults to the pointer size if no '-x byte_size' is specified. This command takes raw input, evaluated as an expression returning an unsigned integer pointing to the start of the region, after the '--' option terminator.
(lldb) watchpoint set expression -w write -- my_ptr
(lldb) watch set exp -w write -- my_ptr
(gdb) watch -location g_char_ptr
Set a condition on a watchpoint.
(lldb) watch set var -w write global
(lldb) watchpoint modify -c '(global==5)'
(lldb) c
...
(lldb) bt
* thread #1: tid = 0x1c03, 0x0000000100000ef5 a.out`modify + 21 at main.cpp:16, stop reason = watchpoint 1
frame #0: 0x0000000100000ef5 a.out`modify + 21 at main.cpp:16
frame #1: 0x0000000100000eac a.out`main + 108 at main.cpp:25
frame #2: 0x00007fff8ac9c7e1 libdyld.dylib`start + 1
(lldb) frame var global
(int32_t) global = 5
List all watchpoints.
(lldb) watchpoint list
(lldb) watch l
(gdb) info break
Delete a watchpoint.
(lldb) watchpoint delete 1
(lldb) watch del 1
(gdb) delete 1
Watchpoints are used to track a write to an address in memory (the default behavior). If you know where an object is in memory (you have a pointer to it), and you know the offset into the object that you care about, that's what watchpoints are for. For instance, in a simple C example, if you have:
struct info
{
int a;
int b;
int c;
};
int main()
{
struct info variable = {5, 10, 20};
variable.a += 5; // put a breakpoint on this line, run to the breakpoint
variable.b += 5;
variable.c += 5;
return variable.a + variable.b + variable.c;
}
Once you're at a breakpoint on variable.a, do:
(lldb) wa se va variable.c
(lldb) continue
And the program will pause when variable.c has been modified. (I didn't bother to type out the full "watch set variable" command).
With a complex object like an NSMutableDictionary, for instance, I don't think watchpoints will do what you need. You would need to know the implementation details of the NSMutableDictionary object layout to know which word (or words) of memory to set a watchpoint.

Resources