Finding cycles with the Memory Graph Debugger with SwiftPM projects

Context is command-line 'server style' executables using Swift PM projects and wanting good tools for analysing memory retain cycles.

So, we can use xctrace successfully (if signing the binary) and get Instruments working properly (for e.g. Allocations analysis)- but we'd also like to get access to the memory graph debugger to get help to understand retain cycles (as we now can see we have a leak basically).

It's my understanding this is only accessible from the Xcode UI, which is a bit problematic in practice, pressing the memory graph debugger button gives the error Unable to acquire required task port (94660:0)

Tried using the tip from Rob Napier to see if it would help for this seemingly related problem, but no dice so far.

So the question to the community:

Anyone who's successfully used the memory graph debugger for SPM packages (as opposed to Xcode projects)?

3 Likes

I don't know the answer, but there is an option to run in a terminal. You can find it in the Options tab of the Run action, Console.

Thanks @NeoNacho - i did find that and it works fine running the app in separate terminal window from Xcode - the memory graph is still no go though with the above error.

1 Like

fwiw, a 2018 report that not signing helped:

1 Like

Thanks @wes1 - to clarify, I have instruments working in general, it’s only the memory graph in Xcode that doesn’t work (it’s not in the instruments app afaict).

For anyone with access to feedbacks, I opened FB11967056 for this one.

And for future readers - disabling SIP makes it work in Xcode...

https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection

Not perfect, would like to have it enabled and to be able to debug memory issues. :-/

3 Likes

disabling SIP makes it work in Xcode

Yowsers! If disabling SIP is the only answer, that’s something that Apple needs to fix. Personally, I only ever disable SIP on ‘victim’ machines.


I did some digging into this. To start, I can only reproduce this an Apple silicon; everything is fine on Intel.

I then updated my Apple silicon Mac to the latest (macOS 13.2, Xcode 14.2), just in case there have been recent changes. There have not.

Here’s how I reproduced this:

  1. In Xcode, I chose File > New > Package.

  2. I added the Package.swift file to add this entry to the targets array:

    .executableTarget(name: "MyLibraryTool", dependencies: ["MyLibrary"]),
    
  3. I created Sources/MyLibraryTool/main.swift and edited it like so:

    print("Hello Cruel World!")
    
  4. I switched to the MyLibraryTool scheme and chose Product > Run. The app runs just fine.

  5. I set a breakpoint on that print statement and chose Product > Run again. It stopped at that breakpoint.

  6. I clicked the Debug Memory Graph button. This triggered the Unable to acquire required task port alert )-:


I then ran through roughly the same process with an Xcode project created from the macOS > Command Line Tool target. Things work in that case. So I compared the code signatures. Here’s the ‘native’ tool:

% codesign -d -vvv --entitlements - MyNativeTool
…
CodeDirectory v=20500 size=654 flags=0x10002(adhoc,runtime) …
…
Internal requirements count=0 size=12
[Dict]
    …
    [Key] com.apple.security.get-task-allow
    [Value]
        [Bool] true

And here’s the SwiftPM tool:

% codesign -d -vvv --entitlements - MyLibraryTool
…
CodeDirectory v=20400 size=550 flags=0x20002(adhoc,linker-signed) …
…
Internal requirements=none

Both tools are ad-hoc signed, which is what you’d expect on Apple silicon (all code on Apple silicon must be signed, so if you haven’t set up a signing identity then you get an ad-hoc signature, aka, Signed to Run Locally). The key difference is the presence of com.apple.security.get-task-allow.

So, I re-signed the SwiftPM tool with that entitlement:

% cat tmp.entitlements 
…
<dict>
    <key>com.apple.security.get-task-allow</key>
    <true/>
</dict>
</plist>
% codesign -s - --entitlements tmp.entitlements -o runtime MyLibraryTool

And back in Xcode I re-ran the app, but this time choosing Product > Perform Action > Run Without Building so that Xcode didn’t overwrite my signature. And now the Debug Memory Graph button works as expected.


This is clearly a bug. You wrote:

I opened FB11967056 for this one.

Thanks for that. I've added my findings to that bug.

In the meantime, I’m hoping that the above will let you re-enable SIP. ’cause, gosh, you really don’t want that off )-:

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

11 Likes

@eskimo thanks so much for reducing it down and updating the case, I'll try to see if I can get Rob Napiers pre-run script that tried to redo the code sign to work again, I must probably have done something wrong - will try to re-enable SIP and see if I can get it to work (and yep, I'm on Apple Silicon).

Will update here if I get the (slightly less intrusive) workaround to work, otherwise I'll just use the workaround you outlined - thanks!

Once again thanks @eskimo - I just tried your workaround and it works perfectly fine (I didn't get the pre-build step as suggested by Rob Napier to work unfortunately) - hoping to see the issue fixed in the future though! :-) (so, SIP is back on)

3 Likes

Digging that out here since it's obviously still a problem.

While I got Rob Napier - Solving "Required kernel recording resources are in use by another document" in Instruments to work, it's still cumbersome to do this for every single SwiftPM-based project.

Anything we mere mortal developers can do to motivate someone taking care?

Here is another workaround that doesn't involve disabling mac security or re-signing tools:

  1. Create a new blank Xcode project. The command line project will likely work well.
  2. Add your Swift Package as a dependency.
  3. Add some code to the new Xcode project that calls into the Swift package.
  4. Build and run from Xcode.
  5. While running, the memory graph debugger should work now.
1 Like