Chapters

Hide chapters

Advanced Apple Debugging & Reverse Engineering

Fourth Edition · iOS 16, macOS 13.3 · Swift 5.8, Python 3 · Xcode 14

Section I: Beginning LLDB Commands

Section 1: 10 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

28. Hello, DTrace
Written by Walter Tyree

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Omagerd! It’s DTrace time! DTrace is one of the coolest tools you’ve (likely?) never heard about. With DTrace, you can hook into a function or a group of functions using what’s called a probe. From there, you can perform custom actions to query information out of a specific process, or even system wide on your computer (and monitor multiple users)!

If you’ve ever used the Instruments application it might surprise you that a lot of the power underneath it is powered by DTrace.

In this chapter, you’ll explore a very small section of what DTrace is capable of doing by tracing Objective-C code in already compiled applications. Using DTrace to observe iOS frameworks (like UIKit) can give you an incredible insight into how the authors designed their code.

The Bad News

Let’s get the bad news out of the way first, because after that it’s all exciting and cool things from there. There are several things you need to know about DTrace:

  • You need to disable Rootless for DTrace to work. Do you remember decades ago in Chapter 1 where I mentioned you need to disable Rootless for certain functionality to work? In addition to letting LLDB attach to any process on your macOS, DTrace will not correctly function if System Integrity Protection is enabled. If you skipped Chapter 1, go back and disable Rootless now. Otherwise, you’ll need to sit on the sidelines for the remainder of this section.
  • DTrace is not implemented for iOS devices. Although the Instruments application uses DTrace under the hood for a fair amount of things, it can not run custom DTrace scripts on your iOS device. This means you can only run a limited set of predefined functionality on your iOS device. However, you can still run whatever DTrace scripts you want on the Simulator (or any other application on your macOS) regardless if you’re the owner of the code or not.
  • DTrace has a steep learning curve. DTrace expects you know what you’re doing and what you’re querying. The documentation assumes you know the underlying terminology for the DTrace components. You’ll learn about the fundamental concepts in this chapter but there is quite literally a whole book on this topic which explores the many aspects of DTrace that are out of the scope of what I’ll teach you.

In fact, it’s worth noting right up front, if DTrace interests you, get visit Brendan Gregg’s site and maybe read his book. It focuses on a wider range of topics that might not pertain to your Apple debugging/reverse engineering strategies, but it does teach you how to use DTrace.

Now that I’ve got that off my chest with the bad stuff, it’s time to have some fun.

Jumping Right In

I am not going to start you off with boring terminology. Ain’t nobody got time for that. Instead, you’ll first get your hands dirty, then figure out what you’re doing later.

sudo dtrace -n 'objc$target:UIViewController::entry' -p `pgrep SpringBoard`
dtrace: description 'objc$target:UIViewController::entry' matched 718 probes
sudo dtrace -n 'objc$target:UIViewController:-viewWillAppear?:entry { ustack(); }' -p `pgrep SpringBoard`
dtrace: description 'objc$target:UIViewController:-viewWillAppear?:entry ' matched 1 probe

objc_msgSend(self_or_class, SEL, ...);
printf("\nUIViewcontroller is: 0x%p\n", arg0);
sudo dtrace -n 'objc$target:UIViewController:-viewWillAppear?:entry { printf("\nUIViewcontroller is: 0x%p\n", arg0); ustack(); }' -p `pgrep SpringBoard`
sudo dtrace -n 'objc$target:::entry { @[probemod] = count() }' -p `pgrep SpringBoard`

DTrace Terminology

Now that you’ve gotten your hands dirty on some quick DTrace one-liners, it’s time to learn about the terminology so you actually know what’s going on in these scripts.

dtrace -n 'objc$target:NSObject:-description:entry / arg0 = 0 / { @[probemod] = count(): }' -p `pgrep SpringBoard`

provider:module:function:name / predicate / { action }
dtrace -n 'objc$target:NSView:-init*:entry' -p `pgrep -x Xcode`
dtrace: description ’objc$target:NSObject:-init:entry’ matched 1 probe
CPU     ID                    FUNCTION:NAME
  2 512130                      -init:entry
  2 512130                      -init:entry
  2 512130                      -init:entry
  2 512130                      -init:entry

Learning While Listing Probes

Included in the DTrace command options is a nice little option, -l, which will list all the probes you’ve matched against in your probe description. When you have the -l option, DTrace will only list the probes and not execute any actions, regardless of whether you supply them or not.

sudo dtrace -ln ’objc$target:::’ -p `pgrep -x Finder`
sudo dtrace -ln 'objc$target:NSView::' -p `pgrep -x Finder`
sudo dtrace -ln 'objc$target:NSView::' -p `pgrep -x Finder` | wc -l
sudo dtrace -ln 'objc$target:NSView:-initWithFrame?:' -p `pgrep -x Finder`
sudo dtrace -ln 'objc$target:NSView:-initWithFrame?:entry' -p `pgrep -x Finder`

A Script That Makes DTrace Scripts

When working with DTrace, not only do you get to deal with an exceptionally steep learning curve, you also get to deal with some cryptic errors if you get a build time or runtime DTrace error (yeah, it’s on the same level of cryptic as some of those Swift compiler errors).

Exploring DTrace Through tobjectivec.py

Time to take a whirlwind tour of this script while exploring DTrace on Objective-C code.

(lldb) tobjectivec -g
#!/usr/sbin/dtrace -s  /* 1 */

#pragma D option quiet  /* 2 */

dtrace:::BEGIN { printf("Starting... use Ctrl + c to stop\n"); } /* 3 */
dtrace:::END   { printf("Ending...\n"  ); }                      /* 4 */

/* Script content below */

objc$target:::entry /* 5 */
{
    printf("0x%016p %c[%s %s]\n", arg0, probefunc[0], probemod, (string)&probefunc[1]); /* 6 */
}
(lldb) tobjectivec
Copied script to clipboard... paste in Terminal
sudo /tmp/lldb_dtrace_profile_objc.d  -p 95129  2>/dev/null
$ sudo /tmp/lldb_dtrace_profile_objc.d  -p 95129  2>/dev/null
Password:
Starting... use Ctrl + c to stop

(lldb) continue
(lldb) tobjectivec -m *StatusBar* -g
objc$target:*StatusBar*::entry
{
  printf("0x%016p %c[%s %s]\n",
    arg0, probefunc[0], probemod, (string)&probefunc[1]);
}
(lldb) tobjectivec -m *StatusBar*
 sudo /tmp/lldb_dtrace_profile_objc.d  -p 2646  2>/dev/null

Tracing Debugging Commands

I often find it insightful to know what’s happening behind the scenes when I’m executing simple debugging commands and the code that’s going on behind them to make it work for me.

(lldb) tobjectivec
(lldb) po @"hi this is a long string to avoid tagged pointers"

(lldb) expression -l swift -O -- class b { }; let a = b()
0x000000010336eb08 +[_TtCs12_SwiftObject initialize]
0x000000010336eb08 +[_TtCs12_SwiftObject class]
0x000000010589c9e8 +[_TtCs12_SwiftObject initialize]
0x000000010589c9e8 -[_TtCs12_SwiftObject self]

Tracing an Object

You can use DTrace to trace method calls for a particular reference.

(lldb) po UIApp
<UIApplication: 0x7fa774600f90>
(lldb) tobjectivec -g -p 'arg0 == 0x7fa774600f90'
/* Script content below */

objc$target:::entry / arg0 == 0x7fa774600f90 /
{
    printf("0x%016p %c[%s %s]\n", arg0, probefunc[0], probemod, (string)&probefunc[1]);
}
(lldb) tobjectivec -p 'arg0 == 0x7fa774600f90'
(lldb) tobjectivec -g -p 'arg0 == 0x7fa774600f90' -a '@[probefunc] = count()'
/* Script content below */

objc$target:::entry / arg0 == 0x7fa774600f90 /
{
    @[probefunc] = count()
}

Other DTrace Ideas

Here’s some other ideas for you to try out on your own time:

(lldb) tobjectivec -f ?init*
(lldb) tobjectivec -m NSXPC*
(lldb) tobjectivec -m UIControl -f -touchesBegan?withEvent?

Key Points

  • Using DTrace requires disabling System Integrity Protection and sudo.
  • DTrace can run as a one line command or with a script for more complex procedures.
  • Use the -l switch when testing out DTrace commands to keep from crashing your system.
  • When working with DTrace, always think about filtering results so you aren’t overwhelmed with output.
  • DTrace doesn’t work on an iOS device, but does work on the Simulator.

Where to Go From Here?

This is only the tip of the DTrace iceberg. There’s a lot more that is possible with DTrace.

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 accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now