Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

You’ve learned how to create breakpoints on executable code; that is, memory that has read and execute permissions. But using only breakpoints leaves out an important component to debugging — you can monitor when the instruction pointer executes an address, but you can’t monitor when memory is being read or written to. You can’t monitor value changes to instantiated Swift objects on the heap, nor can you monitor reads to a particular address (say, a hardcoded string) in memory. This is where a watchpoint comes into play.

A watchpoint is a special type of breakpoint that can monitor reads or writes to a particular value in memory and is not limited to executable code as are breakpoints. However, there are limitations to using watchpoints: there are a finite amount of watchpoints permitted per architecture (typically 4) and the “watched” size of memory usually caps out at 8 bytes.

Watchpoint best practices

Like all debugging techniques, a watchpoint is a type of tool in the debugging toolbox. You’ll likely not use this tool very often, but it can be extremely useful in certain situations. Watchpoints are great for:

  • Tracking an allocated Swift/Objective-C object when you don’t know how a property is getting set, i.e. via direct ivar access, Objective-C property setter method, Swift property setter method, hardcoded offset access, or other methods.
  • Monitoring when a hardcoded string is being utilized, such as in a print/printf/NSLog/cout function call.
  • Monitor the instruction pointer for a particular type of assembly instruction.

Finding a property’s offset

Watchpoints are great for discovering how a particular piece of memory is being written to. A pratical example of this is when a value is written to a previously allocated instance created from the heap, such as in an Objective-C/Swift class.

(lldb) language objc class-table dump UnixSignalHandler -v 
isa = 0x10e843d90 name = UnixSignalHandler instance size = 56 num ivars = 4 superclass = NSObject
  ivar name = source type = id size = 8 offset = 24
  ivar name = _shouldEnableSignalHandling type = bool size = 1 offset = 32
  ivar name = _signals type = id size = 8 offset = 40
  ivar name = _sharedUserDefaults type = id size = 8 offset = 48
  instance method name = setShouldEnableSignalHandling: type = v20@0:8B16
 ...


(lldb) p/x 0x6000024d0f40 + 32
(long) $0 = 0x00006000024d0f60
(lldb) watchpoint set expression -s 1 -w write -- 0x00006000024d0f60

What caused the watchpoint

What exactly caused the watchpoint to be triggered? Caffeinate up, you’ll be looking at a bit of assembly now. To find out, use LLDB to disassemble the current method.

(lldb) disassemble -F intel -m

0x100c04be7 <+39>:  mov    byte ptr [rsi + rdi], al
*(BOOL *)(rsi + rdi) = al
(lldb) p/x $rsi + $rdi
(lldb) watchpoint list
(lldb) po $rsi + $rdi - 32
<UnixSignalHandler: 0x6000024d0f40>
self->_shouldEnableSignalHandling = shouldEnableSignalHandling;

The Xcode GUI watchpoint equivalent

Xcode provides a GUI for setting watchpoints. You could perform the equivalent of the above methods by setting a breakpoint on the creation method of the UnixSignalHandler singleton, then set a watchpoint via the GUI. First though, you need to delete the previous watchpoint.

(lldb) watchpoint delete
About to delete all watchpoints, do you want to do that?: [Y/n] Y
All watchpoints removed. (1 watchpoints)
(lldb) c 
Process 68247 resuming

Other watchpoint tidbits

Fortunately, the syntax for watchpoints is very similar to the syntax for breakpoints. You can delete, disable, enable, list, command, or modify them just as you would using LLDB’s breakpoint syntax.

(lldb) watchpoint list  -b
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 2: addr = 0x60000274ee20 size = 1 state = enabled type = w
(lldb) watchpoint modify 2 -c '*(BOOL*)0x60000274ee20 == 0' 
(lldb) watchpoint modify 2
(lldb) watchpoint command add 2
Enter your debugger command(s).  Type 'DONE' to end.
> bt 5
> continue
> DONE
(lldb) watchpoint command delete 2 

Where to go from here?

Watchpoints tend to play very nicely with those who understand how an executable is laid out in memory. This layout, known as Mach-O, will be discussed in detail in Chapter 18, “Hello, Mach-O”. Combining this knowledge with watchpoints, you can watch when strings are referenced, or when static pointers are intialized, without having to tediously track the locations at runtime.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.