Hide chapters

Advanced Apple Debugging & Reverse Engineering

Third Edition · iOS 12 · Swift 4.2 · Xcode 10

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Low Level

Section 3: 7 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

8. Watchpoints
Written by Derek Selander

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

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
(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.
© 2024 Kodeco Inc.

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 Kodeco Personal Plan.

Unlock now