How to change DYLD_LIBRARY_PATH at runtime?

Hello, I've searched for other posts on this topic but I haven't found anything that provides an answer.


Here's my test program snippet, using the Xcode Mac Application Obj-C starter project:


- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  // Insert code here to initialize your application

  char const * dyld_library_path_original = getenv("DYLD_LIBRARY_PATH");
  char dyld_library_path_new[1024];
  strcpy(dyld_library_path_new, "/Applications/MATLAB_R2020a.app/bin/maci64:");
  strcat(dyld_library_path_new, dyld_library_path_original);
  int rc = setenv("DYLD_LIBRARY_PATH", dyld_library_path_new, 1);
  NSLog(@"DYLD_LIBRARY_PATH=%s, rc=%d", getenv("DYLD_LIBRARY_PATH"), rc);

  void * handle1 = dlopen("libeng.dylib", RTLD_NOW);
  NSLog(@"Test 1: dlopen(libeng.dylib) = %p, err=%s", handle1, dlerror());

  void * handle2 = dlopen("/Applications/MATLAB_R2020a.app/bin/maci64/libeng.dylib", RTLD_NOW);
  NSLog(@"Test 2: dlopen(libeng.dylib) = %p, err=%s", handle2, dlerror());
}


As you can see, I'm trying to dlopen a dylib in the installed MATLAB application. But I don't think the specific dylib matters. What matters is that I'm dlopening it using just the leaf name of the path, after setting DYLD_LIBRARY_PATH at runtime to include MATLAB's dir (Test 1). This fails.


But when I dlopen it with the full path (Test 2) it works. Here's the output:


DYLD_LIBRARY_PATH=/Applications/MATLAB_R2020a.app/bin/maci64:/Users/hecht/Library/Developer/Xcode/DerivedData/TestML-droybqyctybedebamivvyiixjhnn/Build/Products/Debug:/usr/lib/system/introspection, rc=0
Test 1: dlopen(libeng.dylib) = 0x0, err=dlopen(libeng.dylib, 2): image not found
Test 2: dlopen(libeng.dylib) = 0x6000039041a0, err=(null)

I have Hardened Runtime enabled, with these entitlements turned on:

  • Allow DYLD Environment Variables
  • Disable Library Validation


The second one is doing its job, because without it, Test 2 fails also.


But the first one doesn't help in allowing me to modify DYLD_LIBRARY_PATH at runtime. If I set the environment variable before launching (Xcode > Product > Scheme > Edit Scheme > Arguments > Environment Variables) then Test 1 works. Is there a way to get dyld to honor changes to this environment variable made at runtime?

Replies

But the first one doesn't help in allowing me to modify

DYLD_LIBRARY_PATH
at runtime.

Indeed. I actually dug into this as part of a recent thread here on DevForums. I recommend that you read through that, work out how it applies to your situation, and then post back with more questions.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Quinn. I reviewed the linked thread and, other than a wish that there were an API to modify the DYLD search paths at runtime, I didn't see anything that would help me resolve my issue. I think the answer to my question, "Is there a way to get dyld to honor changes to DYLD_LIBRARY_PATH made at runtime?", is "No".


One possible workaround that I thought of, however, would be to change DYLD_LIBRARY_PATH, then exec a child process that calls dlopen() and interacts with the library (MATLAB in this case) for me. Would that work -- would the child process get my modified DYLD_LIBRARY_PATH, or would it just use the same path as me?

exec a child process

I’m presuming that “child process” means you plan to fork and exec (or, preferably, using a higher-level API, like

posix_spawn
,
NSTask
,
Process
, and so on).

Would that work … ?

Yes, with one caveat. If the helper tool has the hardened runtime enabled (which is a requirement for notarisation), you must sign it with the Allow DYLD Environment Variables (

com.apple.security.cs.allow-dyld-environment-variables
) entitlement.

Then again, if you have a separate helper tool then you may not need this. I haven’t looked at the specific app you’re targeting but I expect that this library references its dependencies via an rpath. If your helper tool has an equivalent rpath, it should resolve the libraries in the same way.

The downside to this is that it requires the target app to be installed at a fixed location. I’m not sure if that’s reasonable in your specific situation.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I had a somewhat similar problem, which I was able to solve by adding this to my Info.plist:

    <key>LSEnvironment</key>
    <dict>
      <key>DYLD_LIBRARY_PATH</key>
      <string>@executable_path/../Resources/lib</string>
    </dict>

When the app starts up, the environment contains DYLD_LIBRARY_PATH=@executable_path/../Resources/lib (verbatim, exactly as shown), but the dlopen calls work. Without this (or other tricks like having the app add DYLD_LIBRARY_PATH to the environment and then exec itself), the dlopen calls fail.

  • It turns out that what I describe above doesn't work (and I can't seem to edit or delete my answer?), at least not on recent macOS with SIP enabled. Regardless of how you try to do it (setenv/putenv followed by exec, or via LSEnvironment), all environment variables starting with DYLD_ are ignored (scrubbed out of the environment, in fact). I haven't tested it, because then I also found that it was working for me even without setting DYLD_LIBRARY_PATH (perhaps I was missing a library that needed to be patched to use @executable_path? unclear), but I believe the entitlement mentioned by Quinn (@eskimo) above will fix that.

Add a Comment

and I can't seem to edit or delete my answer

Indeed. DevForums locks posts after a short time (an hour?). But posting a follow-up is fine. Personally, I like to see the history of how these things evolve.


Coming back to the big picture here, I want to reiterate that Apple’s preferred solution is to go ‘all in’ on @rpath. Embedding Nonstandard Code Structures in a Bundle explains this in some detail.

Now, I recognise that this approach won’t work for everyone but it should be the first thing you reach for. And if it doesn’t work, feel free to start a new thread here on DevForums with the details, that is, what you’re trying to do and why @rpath won’t work. Tag it with Linker so that I see it.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"