Articles index

Debugging on Mojave

June 11, 2018

By Jeff Johnson (Developer of StopTheMadness and Underpass)

In March, Xcode 9.3 shipped with a strange new feature. The Xcode Release Notes miscategorized the feature under the "Server" section:

The debugger on macOS now requires the entitlement com.apple.security.get-task-allow to attach to apps built for macOS or for iOS, tvOS or watchOS apps built for Simulator. Xcode automatically injects this entitlement to your builds. The entitlement is stripped from apps distributed using the Organizer window.

At the time, this note caused some confusion and anxiety. In retrospect, it appears that the words were a bit premature — a leak, if you will — and "macOS now requires" referred to macOS 10.14 Mojave, still secret at the time, rather than macOS 10.13 High Sierra. There are indeed some changes to debugging on Mojave, and this blog post will describe what I've discovered so far.

Debugging apps in your Xcode build folder has not changed on Mojave. Examine the code signature of your built app:

$ codesign -d --entitlements - MyApp.app

You can see the com.apple.security.get-task-allow entitlement, which allows you to debug the app either in Xcode or with /usr/bin/lldb in Terminal.

The situation has changed for apps that lack the com.apple.security.get-task-allow entitlement. Apps won't have the entitlement if you didn't build them yourself, or if you exported them for distribution from Xcode. To test, I tried debugging my own app Underpass installed from the Mac App Store. We can confirm with codesign -d --entitlements - /Applications/Underpass.app that the com.apple.security.get-task-allow entitlement does not exist in the App Store version. Here's a sample of a debugging session in Terminal:

$ lldb /Applications/Underpass.app/Contents/MacOS/Underpass
(lldb) target create "/Applications/Underpass.app/Contents/MacOS/Underpass"
Current executable set to '/Applications/Underpass.app/Contents/MacOS/Underpass' (x86_64).
(lldb) breakpoint set --selector count
Breakpoint 1: 141 locations.
(lldb) run

On High Sierra, "it just works", as they say. The process launches and then stops at the breakpoint, most likely in CoreFoundation`-[__NSArrayM count]. On Mojave, however, the first time after login that you try to debug an app without the com.apple.security.get-task-allow entitlement, the system displays a modal dialog requesting an administrator password:

Developer Tool Access needs to take control of another process for debugging to continue.

This happens whether or not you have System Integrity Protection enabled! SIP status doesn't matter here. Unless you authenticate, you can't debug the app. (By the way, pressing the Cancel button just causes the system to ask you again, instead of canceling. I would consider this to be a bug. Eventually it gives up after 10 cancels.) If you successfully authenticate once, then it appears that you can continue to debug other apps without further system requests, as long as you don't logout. If you do logout and login again, the system will start asking again for permission the next time you try to debug. Which is very annoying.

Update

After writing this blog post, I discovered why you have to authenticate again after logout. Prior to Mojave, Xcode would request on first launch to "Enable Developer Mode on this Mac". On Mojave, Xcode no longer requests Developer Mode. However, Developer Mode can still be enabled manually with /usr/sbin/DevToolsSecurity -enable in Terminal. After you authenticate with DevToolsSecurity once, Developer Mode is permanently enabled on Mojave, and you won't have to authenticate again to debug.

Thus, the situation is not quite as bad as feared. It would be nice if Apple communicated and documented this information clearly instead of leaving developers in the dark and fumbling around for answers, like I'm doing here.

Hardened Runtime

Xcode 10 has shipped with another strange new feature, called the "hardened runtime". According to the Xcode Help:

The hardened runtime capability is available in Xcode 10.0 and later on macOS v10.3.6 and later. You can build an app with hardened runtime enabled on macOS v10.3.6 and later, but must test this capability on macOS v10.14 and later.

I believe that "v10.3.6" is a typo and is supposed to be "v10.13.6". On macOS 10.14, the "Hardened Runtime" section appears under your target's "Capabilities" in Xcode 10, but the section does not appear on macOS 10.13.5. When macOS 10.13.6 ships, I expect that "Hardened Runtime" will start appearing in Xcode 10. The reason is that the codesign command-line utility is part of the operating system rather than part of Xcode. On Mojave, the man page for codesign lists a new "runtime" option under "OPTION FLAGS":

On macOS versions >= 10.14.0, opts signed processes into a hardened runtime environment which includes runtime code signing enforcement, library validation, hard, kill, and debugging restrictions. These restrictions can be selectively relaxed via entitlements. Note: macOS versions older than 10.14.0 ignore the presence of this flag in the code signature.

This option flag does not exist in codesign on macOS 10.13.5, so you can't sign an app with the hardened runtime on that version.

What happens on Mojave if you try to debug an app compiled with the hardened runtime? If System Integrity Protection is disabled, then nothing changes; it's the same as I described in the first section of this blog post. So the hardened runtime is enforced by SIP. With SIP enabled, you can still debug your app if it's compiled with the com.apple.security.get-task-allow entitlement. If an app doesn't have that entitlement, however, then you can't debug it at all.

(lldb) run
error: process exited with status -1 (unable to attach)

The hardened runtime is also supposed to make the app ignore DYLD_ environment variables such as DYLD_PRINT_LIBRARIES. In my testing, though, they weren't ignored. Perhaps this will be "fixed" in a later Mojave beta.

Compiling an app with the hardened runtime is currently optional. However, it is required for "notarized" apps.

A notarized app is a macOS app that was uploaded to Apple for validation before it was distributed. When you export a notarized app from Xcode, it code signs the app with a Developer ID certificate and staples a ticket from Apple to the app. The ticket confirms that you previously uploaded the app to Apple.

On macOS 10.14 and later, the user can launch notarized apps when Gatekeeper is enabled. When the user first launches a notarized app, Gatekeeper looks for the app’s ticket online. If the user is offline, Gatekeeper looks for the ticket that was stapled to the app.

Apple has said that notarization is currently optional but will be required for all Developer ID signed apps at some unspecified point in the future. Thus, ultimately the hardened runtime will also be required for all Developer ID signed apps. It seems very likely that the hardened runtime will also become a requirement for apps in the Mac App Store.

In short, the future of debugging on Mojave is … dark.

Articles index