How To Create an Xcode Plugin: Part 2/3

Derek Selander
Dtrace your way through Xcode for fun and profit!

Dtrace your way through Xcode for fun and profit!

Update note: This tutorial has only been tested with Xcode 6.3.2 — if you’re working with another version of Xcode, your experience might not match up perfectly with this tutorial.

Welcome to Part 2 of this three-part tutorial series on custom Xcode plugins! In Part 1 of this series, you were treated to a glimpse of Xcode’s underlying classes through NSNotification properties and injected code into the private class DVTBezelAlertPanel. In addition, you added a NSMenuItem to the menu to persist your preference of enabling Rayrolling in Xcode.

In this part, you’ll continue to build out the Rayrolling plugin you started in Part 1 — if you didn’t work through the first part of the tutorial, or just want to start afresh, you can download the finished project from the first part here. You’ll take a deep dive into the tools available for you to explore Xcode and, with your newfound knowledge, modify Xcode’s title bar so it showcases the lyrics of Ray’s very own hit song Never Gonna Live You Up. :]

Plugin_Swizzled_Titlebar

Getting Started

Open Xcode and Terminal and position your windows on the desktop so you can see both of them at the same time:

View_Organization

Since you’ve graduated from plugin n00b to ¡L33T P1|_|gin m4$Ter!, you’ll use LLDB via Terminal for your Xcode explorations; you no longer need to attach Xcode to an new instance of Xcode to see how things work under the hood.

LLDB’s BFF, Dtrace

One of the best tools for exploring Xcode is Dtrace, which is a wickedly awesome debugging tool and the workhorse behind Instruments. It’s an incredibly useful tool — provided you know how to wield it.

First, a “Hello World” tour of Dtrace is in order. You’ll create a script that will keep a running count of all the classes that begin with IDE and increment the count each time you call a class or instance method for that particular class. Dtrace will then dump this data when you exit the script.

Launch Xcode, then type the following into a fresh tab in Terminal:

sudo dtrace -n 'objc$target:IDE*::entry { @[probemod] = count(); }'  -p `pgrep -xo Xcode`

Although you won’t see any output at first, Dtrace is silently generating a trace of all method calls. Head back to Xcode and play around with it a bit; open some files and click on a few items. Then navigate back to Terminal and press Control + C to terminate the script. The contents of the data will be dumped out into Terminal:

Dtrace_Hello_World

Pretty cool, eh? :] There’s quite a bit you can do with Dtrace, but this tutorial won’t cover the full scope of what you can do. Instead, a quick anatomy of a Dtrace program will help get you started:

Dtrace_breakdown_v2

  • Probe Description: Consists of a provider, module, function, and name separated by colons. Omitting any of these items will cause the Probe Description to include all matches. You can use the * or ? operators for pattern matching.
    • Provider: The group that contains the set of classes and functions, such as profile, fbt, or io. For this particular tutorial, you’ll primarily use the objc provider to hook into Objective-C method calls.
    • Module: In Objective-C, this section is where you specify the class name you wish to observe.
    • Function: The part of the probe description that can specify the function name that you wish to observe.
    • Name: Although there are different names available based upon your Provider selection, you will only use entry or return, which will match a probe description for the start or the end of a function.
    • Note that you use the $target keyword to match the process ID. You specify the target through the p or c option flags

  • Predicate: An optional expression to evaluate if the action is a candidate for execution. Think of it as the content of an if block.
  • Action: The action to perform. This could be as simple as printing something to the console, or performing more advanced functions.

Much like the LLDB command image lookup -rn {Regex Query}, you can use Dtrace to dump classes and methods in a particular process using the -l flag.

To see a quick example of this, launch Safari, then type the following in Terminal:

sudo dtrace -ln 'objc$target::-*ecret*:entry' -p `pgrep -xn Safari`

The above Dtrace script prints out all the instance methods that have the string ecret contained in a method name. You supply the entry probe description name as all methods have an entry and return, so you’re basically omitting duplicates for your search query.

Note: If you want to learn more about Dtrace, check out this excellent article from the Big Nerd Ranch Folks as well as this Dtrace book. Dtrace is a complex tool, so don’t be too frustrated if you don’t understand everything about it in this quick introduction.

Now that you’ve covered the basics of Dtrace, it’s time to use it to hunt down NSViews of interest. Since there are a ton of views in Xcode, you’d quickly be overwhelmed using LLDB trying to figure out which view is which. Even with LLDB’s breakpoint conditions, debugging something this common in an application can be an ugly process.

Fortunately, being smart with Dtrace will help you immensely. You’ll use Dtrace to hunt down the NSViews that make up Xcode’s titlebar. But how would you do that?

Here’s one way: when a mouse stops moving or clicks down on an NSView, hitTest: fires, which returns the deepest subview within that point. You’ll use Dtrace along with this method to determine which NSView you should use to explore the potential superview and subviews.

Run the following command in Terminal:

sudo dtrace -qn 'objc$target:NSView:-hitTest?:return /arg1 != 0/ { printf("UIView: 0x%x\n", arg1);  }' -p `pgrep -xo Xcode`

Once the script is running, make sure Xcode is the first responder by clicking somewhere within its window. Move your cursor around the Xcode window; as soon as you stop moving your mouse, Dtrace spits out a memory address multiple times. This is because the hitTest: method is fired on multiple NSViews in the view hierarchy.

Navigate to the Xcode title bar and click on the titlebar. Select the most recent address that appeared in Terminal and copy it to your clipboard.

Open a new tab in Terminal, launch LLDB, attach it to Xcode, then print the address you copied over from Dtrace like so:

> lldb
(lldb) pro attach -n Xcode
... 
(lldb) po {The Address you copied from dtrace}
...

You’ll see some output similar to the following:

Dtrace_hitTest_2

Note: Right now, Xcode is now paused via LLDB in Terminal. You can pause Xcode to start the debugger at any time by typing process interrupt or just pro i. In addition, you can resume Xcode at any time by typing continue or just c. Make sure you are ALWAYS aware of the state of LLDB when playing with Xcode, because you might think Xcode is being unresponsive when it’s simply paused in the debugger.

Depending on the spot you clicked in Xcode, you’ll hit one of one of several views. Explore the superview or the subviews of the memory address until you reach IDEActivityView.

Once you find the reference of IDEActivityView, make sure that this NSView is actually the one you want.

Type the following In LLDB:

(lldb) po [{IDEActivityView Address} setHidden:YES]
...
(lldb) c
...

The Xcode title view is now hidden, which shows that this is the the view you want to maninpulate.

Use LLDB to unhide this view:

(lldb) pro i
(lldb) po [{IDEActivityView Address} setHidden:NO]
(lldb) c

Here’s the flow in LLDB, for reference:

LLDB_setHidden

Based on past experience, you know that the contents of this title view changes when you build or stop running a particular program. You can observe this functionality with Dtrace. The IDEActivity prefix is a pretty unique naming convention; you can observe all classes that begin with IDEActivity to see all related things happening behind the scenes.

Back in Terminal, stop the Dtrace program by pressing Control + C and then paste and execute the following Dtrace script into Terminal:

sudo dtrace -qn 'objc$target:IDEActivity*::entry  { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probemod] = count(); }' -p `pgrep -xn Xcode`

This prints out every called method whose classname begins with IDEActivity. Once you exit this program, it will also print the count of how often a particular class’s methods were called.

Start up your Dtrace program, build and run a project in Xcode, then stop the project. Note that the text changes in the title view, then stop the Dtrace program and view the results:

Dtrace_IDEActivity

Look over the information carefully; the solution to how IDEActivityView and friends operate is right there in the console output, but it’s a lot of information to plow through, isn’t it?

Fortunately, you can selectively limit the information displayed to you. Browse through the classes and see if there are any that you can selectively explore. Perhaps IDEActivityReport* would be a good candidate, since it knocks out several classes that look related.

Augment the Dtrace script so it now looks like this:

sudo dtrace -qn 'objc$target:IDEActivityReport*::entry  { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probemod] = count(); }' -p `pgrep -xn Xcode`

Go through the motion of running and stopping Xcode while keeping a close eye on the console. Stop the Dtrace script once you’ve stopped Xcode. Are there any classes that look like they could be candidates for further exploration?

IDEActivityReportStringSegment looks interesting. Narrow your script to focus only on this class; take note of the probemod to probefunc change:

sudo dtrace -qn 'objc$target:IDEActivityReportStringSegment::entry  { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probefunc] = count(); }' -p `pgrep -xn Xcode`

Go through the build, run Xcode, stop Xcode, stop Dtrace motions once again and look at the count of the methods executed by class instances of IDEActivityReportStringSegment. It seems that initWithString:priority:frontSeparator:backSeparator: and initWithString:priority: look like good items to explore.

Open up a fresh LLDB session and run the following:

(lldb) pro attach -n Xcode
(lldb) rb 'IDEActivityReportStringSegment\ initWithString'
Breakpoint 9: 2 locations.
(lldb) br command add
Enter your debugger command(s).  Type 'DONE' to end.
> po NSLog(@"customidentifier %s %@", $rsi, $rdx) 
> c 
> DONE
(lldb) c

Here you create a custom command that executes whenever you call any method that begins with initWithString and belongs to the IDEActivityReportStringSegment class. This custom command prints the Selector method and the contents of self, which is the instance of IDEActivityReportStringSegment, to the console.

In addition, you tagged the NSLog statement to contain the word customidentifier so you can easily hunt it down in the system console.

Go to the system console now and create a grep‘d tail searching for customidentifier. Create a new Terminal tab using ⌘ + t and type the following:

tail -f /var/log/system.log | grep customidentifier

Build and run in Xcode in order to populate the IDEActivityReportStringSegment changes. This prints out all the messages you added in your custom LLDB command hook:

LLDB_String_Hunting

Comparing the output from the console to the output from Xcode’s titlebar view shows that these are the items you are in fact looking for! :]

Time to Swizzle

Create a new Objective-C Category. Specify the Class as NSObject and name the category Rayrolling IDEActivityReportStringSegment

Add the following code to NSObject+Rayrolling_IDEActivityReportStringSegment.m:

#import "NSObject+Rayrolling_IDEActivityReportStringSegment.h"
#import "NSObject+MethodSwizzler.h"
#import "Rayrolling.h"
 
@interface NSObject ()
 
// 1
- (id)initWithString:(id)arg1 priority:(double)arg2 frontSeparator:(id)arg3 backSeparator:(id)arg4;
@end
 
@implementation NSObject (Rayrolling_IDEActivityReportStringSegment)
 
// 2
+ (void)load
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    [NSClassFromString(@"IDEActivityReportStringSegment") swizzleWithOriginalSelector:@selector(initWithString:priority:frontSeparator:backSeparator:) swizzledSelector:@selector(Rayrolling_initWithString:priority:frontSeparator:backSeparator:) isClassMethod:NO];
  });
}
 
- (id)Rayrolling_initWithString:(NSString *)string priority:(double)priority frontSeparator:(id)frontSeparator backSeparator:(id)backSeparator
{
 
  // 3
  static NSArray *lyrics;
 
  // 4
  static NSInteger index = 0;
  static dispatch_once_t onceToken;
 
  // 5
  dispatch_once(&onceToken, ^{
    lyrics = @[@"Never gonna live you up.",
               @"Never gonna set you down."];
 
 
  });
 
  // 6
  index = index >= lyrics.count -1 ? 0 : index + 1;
 
  // 7
  if  ([Rayrolling isEnabled]) {
    return [self Rayrolling_initWithString:lyrics[index] priority:priority frontSeparator:frontSeparator backSeparator:backSeparator];
  }
  return [self Rayrolling_initWithString:string priority:priority frontSeparator:frontSeparator backSeparator:backSeparator];
}
 
@end

Here’s what’s going on in the code above:

  1. You need to forward declare the private class initializer initWithString:priority:frontSeparator:backSeparator:, otherwise the compiler will be cranky.
  2. load is used to swizzle the private method with the one that will follow below.
  3. Since you’re using a category to swap this method, instance variables are tricky to create in a category. You can add instance variables to existing classes using associated objects, but that’s a whole tutorial unto itself. Therefore, you use a static NSArray to keep the object around once the method has finished.
  4. You use the same static trick to help index survive method calls as well.
  5. Use dispatch_once to initialize the lyrics NSArray.
  6. Increment index and reset it if it gets out of bounds
  7. Check if the plugin is enabled. If so, augment the string parameter; otherwise, use the default string parameter.

As you can see, using the right tools for the job helps you quickly hunt down items of interest. However, there’s always more than one way to find what you’re looking for — and you’ll discover how to also find what you’re looking for through heap analysis.

Revisiting IDEActivityView Through The Heap

It’s time for you to explore Xcode’s IDEActivityView and IDEActivityReportStringSegment again, but you’ll do it in a slightly different fashion.

Until now, you’ve only explored from the top down, where you find a Class, then somehow find its instance(s) in memory, then explore the contents of those properties. It would be ideal to start from the reverse direction and go up the reference chain to find the objects that point to it.

Fortunately, Xcode ships with some nifty Python scripts that allow you to do just that.

Kill any existing LLDB and Dtrace sessions in Terminal and restart Xcode. Start a fresh Xcode and LLDB session. In LLDB perform the usual Xcode-attaching dance and add the following new command script:

(lldb) pro at -n Xcode 
(lldb) command script import lldb.macosx.heap
"malloc_info", "ptr_refs", "cstr_refs", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.

This script loads several incredibly useful functions in the LLDB process.

  • malloc_info: Displays detailed information about the current heap object. Incredibly useful in combination with the MallocStackLogging environment variable.
  • ptr_refs: Given an address in heap memory, this will find other objects that point to this in memory.
  • cstr_refs: Given a char *, finds the occurrences of it in memory.
  • objc_refs: Finds the instances of a particular class in memory.

You’ll look at the easiest to understand one first: objc_refs.

In LLDB, find all the instances of IDEActivityView with the following script:

(lldb) objc_refs -o IDEActivityView
Searching for all instances of classes or subclasses of "IDEActivityView" (isa=0x10fffe650)
0x00007faaee9cbf30: malloc(   304) -> 0x7faaee9cbf30 IDEActivityView.DVTLayerHostingView.NSView.NSResponder.NSObject.isa
<IDEActivityView: 0x7faaee9cbf30>

This prints out all instances of IDEActivityView. The -o option prints the object, and thus the object’s description method)

With one simple command you’ve found the instances in memory that belong to this type of object. This trick also works for subclasses whose superclass you want to find.

Using the memory address spat out by objc_refs, you can explore the instance of IDEActivityView instance variables like so:

(lldb) malloc_info -t 0x7faaee9cbf30
0x00007faaee9cbf30: malloc(   304) -> 0x7faaee9cbf30 IDEActivityView.DVTLayerHostingView.NSView.NSResponder.NSObject.isa (IDEActivityView) *addr = {
  DVTLayerHostingView = {
    NSView = {
      NSResponder = {
...

But wait — that’s not all! You can even get the reference to all the other objects pointing to a particular memory address:

(lldb) ptr_refs  0x7faaee9cbf30
0x00007faaeed9a308: malloc(    16) -> 0x7faaeed9a300 + 8     
0x00007faaeedb4148: malloc(   176) -> 0x7faaeedb4140 + 8      IDEActivityViewBackgroundButton.NSButton.NSControl.NSView.NSResponder._nextResponder
0x00007faaeedb4190: malloc(   176) -> 0x7faaeedb4140 + 80     IDEActivityViewBackgroundButton.NSButton.NSControl._aux.NSObject.isa
0x00007faaeedb4928: malloc(   160) -> 0x7faaeedb4920 + 8      NSView.NSResponder._nextResponder
...
Note: These commands, combined with your im loo -rn {classmethod regex} queries, can help you quickly find and explore instances of classes live in memory. Just make sure that you always pause LLDB first before you try these functions, or else they won’t work!

You can even take it a step further — there is a cool environment variable named MallocStackLogging that takes a backtrace of the stack whenever you instantiate an object. Although many items can point to an object, this can help you figure out who the “ultimate owner” of an object is.

While LLDB is still attached to Xcode, you will kill the Xcode process and relaunch it with the MallocStackLogging environment variable via LLDB:

(lldb) pro kill 
Process 65196 exited with status = 9 (0x00000009)
(lldb) pro launch -v MallocStackLogging=1 -n
Process 67920 launched: '/Applications/Xcode.app/Contents/MacOS/Xcode' (x86_64)
Note: If you are lazy and are constantly killing LLDB/Xcode sessions, launchctl might prove to be an easier way of launching this environment variable:

launchctl setenv MallocStackLogging 1

This sets MallocStackLogging to true for every process created by launchd here on out. Be sure to remove this when you are done if you do go this route with the following command:

launchctl unsetenv MallocStackLogging

To test this all out, make sure the Xcode editor does not have Rayrolling enabled under the Edit menu as below:

Enable_RayRolling

Now, with any project having source code open in Xcode, build and run the project. While the project is actively running, you will see the IDEActivityView (just pretend you don’t know it’s called that for the moment) change its contents as the build completes and runs.

Pause the execution of the program and reload the heap functions if needed with the following:

(lldb) pro i 
(lldb) command script import lldb.macosx.heap
"malloc_info", "ptr_refs", "cstr_refs", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.
Note: If constantly retyping these commands is annoying to you, check out how to set up command aliases in the ~/.lldbinit file. The contents of this file will be called each time you create an instance of LLDB.

Now use the cstr_ref function with the string contents of the Xcode title bar. For example, if the project name was Rayrolling, your title bar would have something like Running Rayrolling : Rayrolling:

(lldb) cstr_ref "Running Rayrolling : Rayrolling" 
0x0000000118cb21d0: malloc(    32) -> 0x118cb21d0
0x000000012079ae21: malloc(    48) -> 0x12079ae10 + 17     __NSCFString1 bytes after __NSCFString
0x00000001207a3bb3: malloc(    64) -> 0x1207a3b90 + 35     __NSCFString19 bytes after __NSCFString
0x00000001223834d1: malloc(    48) -> 0x1223834c0 + 17     __NSCFString1 bytes after __NSCFString
0x000000011f9126a1: malloc(    48) -> 0x11f912690 + 17     __NSCFString1 bytes after __NSCFString

As you can see, the majority of the references are NSString references. You can’t do much with this information by itself, so you will need to figure out how these items were created. Fortunately with MallocStackLogging, this is super easy!

(lldb) cstr_ref "Running Rayrolling : Rayrolling"  -s

This prints out the stack frame whenever this particular char * is created.

Go through all the stack trace instances. Since these are in memory, the number of memory addresses as well as the address location will be different on your computer depending on how you’ve interacted with Xcode.

By reading through the stack traces, you’ll gain some insight into the grouping of classes that are responsible for this area of Xcode.

For example, depending on when you launched this LLDB query you might see classes such as IDEActivityScrollingTextLayer, IDERunOperation or IDEActivityView, etc.

You can also use the ptr_ref on the output of cstr_ref to see what items are retaining the strings.

Miscellaneous Random Exploration Tricks

Finding the correct API in Xcode is tricky, since you’re trying to filter thousands upon thousands of classes and methods.

Using smart lldb regular expressions can greatly aid in finding the item you’re looking for. Sometimes the best place to get a good footing from anywhere in code is to use a Singleton. See if there are any singletons that exist in Xcode that are worth checking out.

While LLDB is paused and attached to Xcode, run the following:

(lldb) i loo -rn "\+\[IDE.*\ shared"

This will look up all class names beginning with “IDE” having a class method that begins with “shared”.

An attractive alternative could be:

(lldb) i loo -rn "\+\[[A-Za-z]*\ [A-Za-z]*\]"

This will print all the class methods with no arguments.

As you saw earlier, you can hunt down classes easily using this Python script given their name. Another popular tool for finding classes is a tool called class-dump; it can be downloaded here. It’s an attractive alternative to the im loo -rn {regex} LLDB command, as it produces much cleaner “header-like” output.

The only downside to this tool is that you have to limit your selection to specific frameworks or plugins, while the image lookup command will look everywhere in all the frameworks & plugins found in Xcode.

Where to Go From Here?

Here’s the completed Rayrolling project from Part 2.

In this article you’ve learned the basics of Dtrace and have explored some advanced LLDB functions available to you.

If you want to learn more about Dtrace, you can read another excellent article about it at obcj.io and get the official documentation on Dtrace here.

In part 3 of this tutorial, you’ll focus on breaking down assembly, and you’ll learn about a neat tool named Cycript.

As always, if you have questions or comments on this tutorial, feel free to join the forum discussion below!

Derek Selander

Derek is an iOS developer who enjoys talking about LLDB and other debugging tools. All of his tutorials involve debugging in one way or another.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 19 total!

Swift Team

... 15 total!

iOS Team

... 33 total!

Android Team

... 15 total!

macOS Team

... 10 total!

Apple Game Frameworks Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 12 total!

Resident Authors Team

... 15 total!