Attach to Multiple Processes in macOS

2 hours ago 2

For the vast majority of developers of software for Apple products, the process (har, har) of debugging involves attaching to and inspecting only one process: the main application binary. This is particularly true on iOS-based systems, where spawning subprocesses is not allowed, and arbitrary XPC services are limited. But developers of “app extensions”, a specialized form of XPC service, may well need to attach to at least two processes.

Mac developers, on the other hand, are more likely to encounter situations where attaching to multiple subprocesss is either necessary or very convenient. In particular, the use of custom XPC services allows a Mac app to subdivide the work it performs into separate components that run with potentially different security entitlements from the main application.

For these scenarios, Xcode provides a handy checkbox that can be used to automatically attach when one of an app’s XPC services is launched. Look deep in the “Options” tab of the scheme editor’s “Run Action” page:

Screenshot of a checkbox labeled "Debug XPC services used by app"

This is handy, for example, if you need to attach to components of the open source Sparkle project, which uses XPC services to compartmentalize some parts of the software update process. Here, Xcode automatically attached to the service that handles relaunching the freshly-updated application:

Screenshot of Xcode's debug interface listing processes that are being debugged. It shows the main app, named MarsEdit, and a secondary process from the Sparkle library called "org.sparkle-project.InstallerLauncher".

While this feature is great for XPC services in particular, there are many caveats. For example, Xcode will not automatically attach to XPC services that you didn’t build yourself. So, while an app that uses WebKit will employ many XPC services for accessing network resources or rendering web page content, Xcode will not attach to those processes automatically unless you built the WebKit project locally and are running against that version.

Another shortcoming of Xcode’s default behavior is that it will only attach to XPC processes specifically. If your app spawns other subprocesses that are not XPC services, you have to laboriously ask it to attach to your process by name or by process ID. My app FastScripts, an AppleScript utility, uses subprocesses to run scripts in isolation, both for resilience against crashing scripts, and to accommodate running an arbitrary number of scripts in parallel. When I’m debugging an issue in the subprocess, named “RSScriptRunner” in my debug builds, I have to manually select the “Debug -> Attach to Process by PID or Name” menu item, and type in “RSScriptRunner”:

Screenshot of Xcode's "Attach to Process by PID or Name" panel. It shows the name of the process requested to attach to, along with other debugging options to apply.

But here I run into another unfortunate limitation of Xcode’s attachment functionality: Xcode only attaches to the first named process it finds. FastScripts always keeps at least two RSScriptRunner processes going, so that when a script is invoked there will always be a process “primed and ready”. When several scripts are running in parallel, several RSScriptRunner processes are running. How do I attach to the one I care about debugging?

The solution I’ve come up with is to attach to them all. I’ve created an AppleScript that determines the process identifiers for every running process of a given name, and asks Xcode to attach to each one. I invoke the script (from FastScripts, but any scripting utility would work), type in the name “RSScriptRunner”, and Xcode attaches to each and every one of them:

Screenshot of Xcode's process debugging list, showing the main app, "FastScripts", and two instances of the "RSScriptRunner" process all being debugged at the same time.

I’ve clipped this screenshot to save space, but there are four instances of RSScriptRunner that Xcode has attached to in this example. Once you’ve attached to any number of processes in Xcode, any breakpoints you set will fire when any of the processes reaches them.

You can download the script directly, or copy and paste from this text into your favorite script editor:

set processName to text returned of (display dialog "Enter process name:" default answer "") set processIDs to {} tell application "System Events" set processIDs to unix id of every process whose name is processName end tell tell application "Xcode" repeat with processID in processIDs tell workspace document 1 attach to process identifier processID without suspended end tell end repeat end tell

Ideally, I would love for Xcode to expand its “Debug XPC Processes” option so that it attaches automatically to any child process created by an app, or perhaps any child process matching a particular regular expression. In the meantime, I hope this script helps some of you manage debugging your multiprocess apps!

Read Entire Article