Chapters

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

29. Hello, DTrace
Written by Derek Selander

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) irregardless 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 this book http://www.brendangregg.com/dtracebook/index.html. 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.

Launch the iPhone X Simulator. Once alive, create a new Terminal window. Type the following into Terminal:

sudo dtrace -n 'objc$target:*ViewController::entry' -p `pgrep SpringBoard`

No, this will not secretly destroy your computer, you need that sudo in there because DTrace is incredibly powerful and can query information about other users on your computer. This means you need to be root to use it.

This DTrace command takes in two options, the name option (-n) and the PID (-p), both of which will be discussed later. Make sure to surround your query in single quotes or else it will not work. Take note of the backtics instead of single quotes that surround pgrep SpringBoard.

If you typed out everything correctly, you’ll get output in the Terminal window similar to the following:

dtrace: description 'objc$target:*ViewController::entry' matched 35794 probes

Navigate around the simulator while keeping an eye on the Terminal window.

This will dump out every hit (aka probe) that contains the Objective-C class name that ends with “ViewController”. Since you left the function field blank (don’t worry - terminology descriptions are coming in the next section), it matches every single Objective-C method so long as the class name ends with ViewController.

Once you get bored of looking at what pops up, kill the Terminal DTrace script with the Ctrl + C combination.

Back in your Terminal, enter the following:

sudo dtrace -n 'objc$target:UIViewController:-viewWillAppear?:entry { ustack(); }' -p `pgrep SpringBoard`

There’s a couple of subtle changes this time:

  • The *ViewController query has been changed to UIViewController.

  • The query -viewWillAppear? has been added to the function location. Again, you’ll cover terminology later. For now, all you need to know is instead of matching every function for any class that contains the string “ViewController”, this new DTrace script will only match -[UIViewController viewWillAppear:]. The question mark stands for a wildcard character in DTrace, which will resolve to the ‘:’ in the viewWillAppear: method.

  • Finally, you are adding brackets with a function called ustack(). This logic will be called every time -[UIViewController viewWillAppear:] gets hit. The ustack() is one of DTrace’s built-in functions which dumps the userland stack trace (aka SpringBoard for this case) when this method gets hit.

  • Keep an eye on that single quote which moved from the end of entry part to the end of the squiggly bracket.

If you typed in everything correctly, you’ll get:

dtrace: description 'objc$target:UIViewController:-viewWillAppear?:entry ' matched 1 probe

Navigate around SpringBoard. Swipe up, swipe down, tap on the Edit button by scrolling to the far left, whatever you need to do to trigger a UIViewController’s viewWillAppear:.

When UIViewController’s viewWillAppear: gets hit, the stack trace will be printed out in the Terminal.

Take note of some stack traces that don’t have the actual function name, but just a module and address.

This is telling us we don’t have debugging information or an indirect symbol table to reference the name of this function.

Once you get bored of exploring the stack trace of all the viewWillAppear:’s in the SpringBoard process, kill the DTrace script again.

Now… Do you remember the whole spiel about objc_msgSend with registers and how the first parameter will be the instance (or class) of an Objective-C class?

For example, when objc_msgSend executes, the function signature will look like:

objc_msgSend(self_or_class, SEL, ...);

You can grab that first parameter (aka the instance of the UIViewController) in DTrace with the arg0 parameter. Unfortunately, you can only get the reference to the pointer - you can’t run any Objective-C code, like [arg0 title].

Add the following line of code right before the ustack() function in your DTrace command:

printf("\nUIViewcontroller is: 0x%p\n", arg0);

Your DTrace one-liner will now look like the following:

sudo dtrace -n 'objc$target:UIViewController:-viewWillAppear?:entry { printf("\nUIViewcontroller is: 0x%p\n", arg0); ustack(); }' -p `pgrep SpringBoard`

Right before printing out the stack trace, you’re printing the reference to the UIViewController that is calling viewWillAppear:.

If you were to copy the address of this pointer spat out by DTrace and attached LLDB to SpringBoard, you will find that it points to a valid UIViewController (provided it hasn’t been dealloc’d yet).

Note: It’s easy to get the pointer from arg0, but getting any other information (i.e. the class name) is a tricky process.

You can’t execute any Objective-C/Swift code in the DTrace script that belongs to the userland process (e.g. SpringBoard). All you can do is traverse memory with the references you have.

In the final chapter, you’ll actually get the class name of arg0 in an Objective-C call by traversing memory in a stripped binary, devoid of debugging information!

Let’s do one more DTrace example.

Kill any DTrace scripts and create a script which aggregates all the unique classes that are being executed as you explore SpringBoard:

sudo dtrace -n 'objc$target:::entry { @[probemod] = count() }' -p `pgrep SpringBoard`

Navigate around SpringBoard again. You’re not going to get any output yet, but as soon as you terminate this script with Ctrl + C, you’ll get an aggregated list of all the times a method for a particular class was executed. This is called Aggregations and you’ll learn about this later.

As you can see from my output, SpringBoard had 187075 method calls implemented by NSObject that were hit during my run of the above DTrace one-liner.

It’s important to differentiate the fact that these were very likely instances of classes which were subclasses of NSObject calling methods implemented by NSObject (i.e. the subclass of the NSObject didn’t override any of these methods).

For example, calling -[UIViewController class] would count as a hit towards the total methods executed by NSObject because UIViewController doesn’t override the Objective-C method, class, nor does UIViewController’s parent class, UIResponder.

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.

Let’s revisit a DTrace probe. You can think of a probe as a query. These probes are events that DTrace can monitor either in a specific process or globally across across your computer.

Consider the following DTrace one-liner:

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

This example will monitor NSObject’s implementation of the description method in the process named SpringBoard. In addition, this says as soon as the description method begins, execute logic to aggregate the amount of times this method is called.

This DTrace one-liner can be further broken down into the following terminology:

  • Probe Description: Encapsulates a group of items that specify 0 or more probes. This consists of a provider, module, function, and name, each separated by colons. Omitting any of these items between the colons will cause the probe description to include all matches. You can use the * or ? operators for pattern matching. The ? operator will act as a wildcard for a single character, while the * will match anything.

  • Provider: Think of the provider as a grouping of code or common functionality. For this particular chapter, you’ll primarily use the objc provider to trace into Objective-C method calls. The objc provider groups all of the Objective-C code. You’ll explore other providers later.

Note: The $target keyword is a special keyword which will match whatever PID you supply DTrace. Certain providers (like objc) expect you to supply this.

Think of $target as a placeholder for the actual PID, which monitors Objective-C in a specific process. If you do reference the $target placeholder, you must specify the target PID through the -p or -c option flags in your DTrace command.

Typically this is done either by -p PID if you knew the exact PID, or more likely -p `pgrep NameOFProcess` . The pgrep Terminal command will look for the PID whose process name is NameOFProcess then return the PID, which then gets applied to the $target variable.

  • Module: In the objc provider, the module section is where you specify the class name you wish to observe. Using the objc provider is a little unique in this sense, because typically the module is used to reference a library in which the code is coming from. In fact, in some providers, there’s no module at all! However, the authors of the objc provider chose to use the module to reference the Objective-C classname. For this particular example, the module is NSObject.

  • Function: The part of the probe description that can specify the function name you wish to observe. For this example, the function is -description. The authors of the objc provider used the + or - to determine if the Objective-C function is a class or instance method (as you’d expect!). If you changed the function to +description, it would query for any probes with +[NSObject description] instead.

  • Name: This typically specifies the location of the probe within a function. Typically, there’s the entry and return names which correspond to a function’s entry and exit. In addition, within the objc provider, you can also specify any assembly instruction offset to create a probe at! For this particular example, the name is entry, or the start of the function.

  • Predicate: An optional expression to evaluate if the action is a candidate for execution. Think of the predicate as the condition in a if-statement. The action section will only execute if the predicate evaluates to true. If you omit the predicate section, then the action block will execute every time for a given probe. For this particular example, the predicate is the / arg0 != 0 /, meaning the content following the predicate will only get evaluated if arg0 is not nil.

  • Action: The action to perform if the probe matches the probe description and the predicate evaluates to true. This could be as simple as printing something to the console, or performing more advanced functions. For this example, the action is the @[probemod] = count(); code.

When all of these components are combined, this will form a DTrace clause. This consists of the probe description, the optional predicate and optional action.

Put simply, a DTrace clause is made up as follows:

provider:module:function:name / predicate / { action }

DTrace “one-liners” can comprise multiple clauses which can monitor different items with the probe description, check for different conditions in the predicate and execute different logic with different actions.

So, with the example:

dtrace -n 'objc$target:NSView:-init*:entry' -p `pgrep -x Xcode`

You have a probe description of objc$target:NSView:-init*:entry, which includes NSView as the module, -init* as the function, and entry as the name with no predicate and no action. DTrace produces a default output for tracing (which you can silence with the -q option). This default output only displays the function and name. For example, if you were tracing -[NSObject init] without silencing the default DTrace action, your DTrace output would look like the following:

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 

From the output, the -[NSObject init] got hit 4 times while the process was being traced. You can tell DTrace to use a different formatted output by combining the -q option with one of the print functions to display alernative formatting for output.

What does that -n argument mean again? The -n argument specifies the DTrace name which can come in the form provider:module:function:name, module:function:name or function:name. In addition, the name option can take an optional probe clause, which is why you surround all your one-liner script content in single quotes to pass to the -n argument.

Got it? No? You’ll repeat the above terminology steps with a useful DTrace option to emphasize what you’ve learned.

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.

This makes the -l option a nice tool to learn what will and will not work.

You will look at a probe description one more time while building up a DTrace script and systematically limiting its scope. Consider the following, Do NOT execute this:

sudo dtrace -ln ’objc$target:::’ -p `pgrep -x Finder`

This will create a probe description on every Objective-C every class, method, and assembly instruction within the Finder application. This is a very bad idea for a DTrace script and will likely not run on your computer because of the hit count you’ll get.

Note: I’ve supplied the -x option to pgrep because I could get multiple PIDs for a pgrep query, which will screw up the placeholder, $target. The -x option says only give me the PID(s) that match exactly for the name, Finder. If there are multiple instances of a process. You can get the oldest one or newest one in pgrep with the -o or -n option. If this sounds confusing, play around with the pgrep command in Terminal without DTrace to understand how it works.

Don’t execute the above script because it will take too long. However, execute the rest of these scripts so you understand what’s happening.

Let’s filter this down a bit. In Terminal, type the following:

sudo dtrace -ln 'objc$target:NSView::' -p `pgrep -x Finder`

Press enter, then enter your password.

This will list a probe on every single method implemented by NSView for all of its methods and every assembly instruction within each of those methods. Still a horrible idea, but at least this one will actually print out after a second.

How many probes is this? You can get that answer by piping your output to the wc command:

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

On my macOS machine in 10.14 (at the time of writing), I get ~42k Objective-C DTrace probes for any code pertaining to NSView within the Finder process. Wow!

Filter the probe description down some more:

sudo dtrace -ln 'objc$target:NSView:-initWithFrame?:' -p `pgrep -x Finder`

This will filter the probe description down to every assembly instruction that’s executed within -[NSView initWithFrame:] in addition to the entry and return probes. Notice the use of a ? instead of a colon to specify the Objective-C selector (which takes a parameter). If a colon was used, then DTrace will incorrectly parse the input thinking the function part was complete and have moved onto specifying the name within the DTrace probe. There’s also the - at the begining of the function description to indicate this is an instance Objective-C method.

This is still too much output, you only want to execute a probe that will monitor the beginning of the -[NSView initWithFrame:] method and no other parts.

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

This will say to only set a probe to the beginning of -[NSView initWithFrame:] and no other parts in this Objective-C method.

Using the -l option is a nice way to learn the scope of your probes before you shoot off making your DTrace actions. I would recommend you make heavy use of the -l option when you’re starting to learn DTrace.

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).

To help mitigate these build issues as you learn DTrace, I’ve created a lovely little script called tobjectivec.py (trace Objective-C), which is an LLDB Python script that will generate a custom DTrace script for you so long as you ask it real nice like.

Note: Oh yeah, now is a good time to mention you can create DTrace scripts as well as DTrace one-liners. As the complexity in your DTrace logic rises, it becomes a better idea to use a script. For simple DTrace queries, stick with the one-liners.

You’ll find the tobjectivec.py script located within the starter directory for this chapter. I am assuming you went through Chapter 26, “SB Examples, Improved Lookup” and have installed the lldbinit.py script and have stuck it in your ~/lldb folder. Provided you did this, all you have to do is copy/paste the tobjectivec.py script into your ~/lldb directory and it will be launched next time LLDB starts up.

If you haven’t done this yet, go back to Chapter 26 and follow the instructions for installing the lldbinit.py file. Alternatively, if you’re extremely stubborn, I suppose you can install this tobjectivec.py manually by augmenting your ~/.lldbinit file.

Exploring DTrace through tobjectivec.py

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

Included in the starter folder is the recycled project Allocator. Open that project up, build, run, then pause in the debugger.

Once you’ve got the Allocator project paused, bring up the LLDB console and type the following:

(lldb) tobjectivec -g

Typically, the tobjectivec script will generate a script in the /tmp/ directory of your computer. However, this -g option says that you’re debugging your script and displays the output to LLDB instead of creating a file in /tmp/. With the -g (or --debug) option, your current script will be displayed to the console.

This dry run of the tobjectivec.py with no extra parameters will produce the following output:

#!/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 */
}

Let’s break this down:

  1. When executing a DTrace script, the first line needs to be #!/usr/sbin/dtrace -s or else the script might not run properly.

  2. This line says to not list the probe count nor perform the default DTrace action when a probe fires. Instead, you’ll give DTrace your own custom action.

  3. This is one third of the DTrace clauses within this script. There are probes for DTrace that monitor for certain DTrace events… like when a DTrace script is about to start. This says, as soon as DTrace starts, print out the "Starting... use Ctrl + c to stop" string.

  4. Here’s another DTrace clause that prints out "Ending..." as soon as the DTrace script finishes.

  5. This is the DTrace probe description of interest. This says to trace all the Objective-C code found in whatever process ID you supply to this script.

  6. The action part of this clause prints out the instance of the Objective-C probe that was triggered, followed by Objective-C styled output. In here, you can see probefunc and probemod being utilized which will be a char* representation of the function and module. DTrace has several builtin variables that you can use, probefunc & probemod being two of them. You also have probeprov and probename at your disposal. Remember the module will represent the class name while the function will represent the Objective-C method. This takes a combination of the probemod & probefunc and displays it in the pretty Objective-C syntax you’re accustomed to.

Now you’ve got an idea of this script, remove the -g option so you’re no longer using the debug option. Type in LLDB:

(lldb) tobjectivec

You’ll get different output this time:

Copied script to clipboard... paste in Terminal

Your clipboard’s contents have been modified. Jump over to your Terminal, then paste in the contents of your clipboard. Here’s mine, but yours will of course be different:

sudo /tmp/lldb_dtrace_profile_objc.d  -p 95129  2>/dev/null

The content you originally saw is now dumped into /tmp/lldb_dtrace_profile_objc.d. If you are at all paranoid about what this script does, I recommend you cat it first to ensure you know what it’s doing.

The script provides the process identifier that LLDB is attached to (so you wouldn’t have to type pgrep Allocator).

Once you get your password prompt, enter in you password to get those root privs:

$ sudo /tmp/lldb_dtrace_profile_objc.d  -p 95129  2>/dev/null
Password:
Starting... use Ctrl + c to stop

Wait until the DTrace script inidicates to you that it’s starting.

With both Xcode and Terminal visible, type a simple po [NSObject class] in the console. Check out the slew of Objective-C messages that get spat out for just this method.

This will prepare you for what’s about to come. Resume execution using LLDB:

(lldb) continue

Navigate around the Allocator app (tap on views, bring down the in-call status bar in the Simulator with ⌘ + Y) iOS Simulator while keeping an eye on the DTrace Terminal window.

Scary, right?

This is too much stuff. Filter some of the noise by adding content to the module specifier.

Back in Xcode, pause execution of the Allocator process and bring up LLDB.

Generate a new script that only focuses on Objective-C classes that have the phrase StatusBar in it’s name. Type the following in LLDB:

(lldb) tobjectivec -m *StatusBar* -g

This will do a dry run and give you the following truncated output:

objc$target:*StatusBar*::entry 
{
    printf("0x%016p %c[%s %s]\n", arg0, probefunc[0], probemod, (string)&probefunc[1]);
}

Notice how the module portion of the probe has changed. The * can be thought of as .* that you know and love in your regular expressions. This means you’re querying for probes that contain the case sensitive word StatusBar for any Objective-C classes when the probe enters the start of the function.

In LLDB, remove the -g option so this script will get copied to your clipboard, then re-execute the command.

(lldb) tobjectivec -m *StatusBar* 

Jump over to your Terminal window. Kill the previous DTrace instance by pressing Ctrl + C, then paste in your new script.

 sudo /tmp/lldb_dtrace_profile_objc.d  -p 2646  2>/dev/null

Resume execution back in Xcode.

Jump to the Simulator and toggle the in-call status bar using ⌘ + Y or rotate the Simulator by using ⌘ + ← or ⌘ + → while keeping an eye on the DTrace Terminal window.

You’ll get a slew of output again.

You can use DTrace to cast a wide net on code with minimal performance hits and quickly drill down when you need to.

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.

Observe how many Objective-C method calls it takes to make a simple Objective-C NSString.

Back in LLDB, type the following:

(lldb) tobjectivec

Paste the contents in the Terminal window, but do not resume execution in LLDB. Instead, just type the following:

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

As soon as you press enter, check out the DTrace Terminal window and see what gets spat out. You’ll get something similar to the following:

We just printed out a simple NSString and look how many Objective-C calls this took!

Here’s one for all you Swift “purists” out there.

Clear the Terminal screen using (⌘ + K), make sure the DTrace Terminal script is still running. Head back to LLDB and type the following:

(lldb) expression -l swift -O -- class b { }; let a = b()

You are using the Swift debugging context to create a pure Swift class then instantiating it. Observe the Objective-C method calls when this class is created.

DTrace will dump out:

0x00000001087541b8 +[SwiftObject class]
0x0000000119149778 +[SwiftObject initialize]
0x0000000119149778 +[SwiftObject class]

If you were to copy any of the addresses down spat out by DTrace and then po that, you’d be greeted with an onslaught of Objective-C method calls for this pure Swift class.

A “pure” Swift class ain’t as pure as you thought, right?

Tracing an object

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

Remove the previous DTrace script with Ctrl + C.

While the application is paused, use LLDB to get the reference to the UIApplication. Make sure you are in an Objective-C stack frame.

(lldb) po UIApp

You’ll get something like:

<UIApplication: 0x7fa774600f90>

Copy the reference and use this to build a predicate which only stops when this reference is arg0 — remember, objc_msgSend’s param is a instance of a Class or the Class itself.

(lldb) tobjectivec -g -p 'arg0 == 0x7fa774600f90'

You’ll get the dry run output of your script printed to the console similar to the following:

#!/usr/sbin/dtrace -s

#pragma D option quiet
dtrace:::BEGIN { printf("Starting... use Ctrl + c to stop\n"); }
dtrace:::END   { printf("Ending...\n"  ); }

/* Script content below */

objc$target:::entry / arg0 == 0x7fa774600f90 /
{
    printf("0x%016p %c[%s %s]\n", arg0, probefunc[0], probemod, (string)&probefunc[1]);
}

Looks good! Execute the command again without the -g option:

(lldb) tobjectivec -p 'arg0 == 0x7fa774600f90'

Resume execution in LLDB, then paste your script into Terminal.

Trigger the home button, (⌘ + Shift + H) or the status bar (⌘ + Y) in the Simulator.

This is dumping every Objective-C method call on the [UIApplication sharedApplication] instance.

Oh, is that too much output to look at? Then aggregate the content!

Back in Xcode, pause execution and in LLDB:

(lldb) tobjectivec -g -p 'arg0 == 0x7fa774600f90' -a '@[probefunc] = count()'

This will produce the following script:

#!/usr/sbin/dtrace -s

#pragma D option quiet
dtrace:::BEGIN { printf("Starting... use Ctrl + c to stop\n"); }
dtrace:::END   { printf("Ending...\n"  ); }

/* Script content below */

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

You know the drill. Rerun the above tobjectivec command without the -g option, then paste your clipboard contents into Terminal and resume execution in LLDB.

No content will be displayed in Terminal yet. But DTrace is quietly aggregating every method that is being sent to the UIApplication instance.

Move around in the Simulator to get a healthy count of methods being sent to the UIApplication. As soon as you kill this script with the usual Ctrl + C, DTrace will dump out the total count of all the Objective-C methods that were applied to the UIApplication instance.

Other DTrace ideas

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

Trace all the initialization methods for all objects:

(lldb) tobjectivec -f ?init*

Monitor inter-process communication related logic (i.e. Webviews, keyboards, etc):

(lldb) tobjectivec -m NSXPC*

Print the UIControl subclass which is handling your starting touch event on your iOS device:

(lldb) tobjectivec -m UIControl -f -touchesBegan?withEvent? 

Where to go from here?

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

I would recommend you check out the following URLs as they are a great resource for learning DTrace.

In the next chapter, you’ll take a deeper dive into whats possible with DTrace and explore profiling Swift code.

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