Repository navigation
.NET 9.0.1 Breaking Change in macOS native library resolution #112080
Description
Description
.NET runtime 9.0.1 seems to have introduced a breaking change with regards to how dyld on macOS resolves relative paths to system frameworks.
I suspect this will be related to the change in pipelines from macos-12 to macos-13:
# OSX Public Build Pool (we don't have on-prem OSX BuildPool).
${{ if and(in(parameters.osGroup, 'osx', 'maccatalyst', 'ios', 'iossimulator', 'tvos', 'tvossimulator'), eq(variables['System.TeamProject'], 'public')) }}:
- vmImage: 'macos-12'
+ vmImage: 'macos-13'
# OSX Internal Pool
${{ if and(in(parameters.osGroup, 'osx', 'maccatalyst', 'ios', 'iossimulator', 'tvos', 'tvossimulator'), ne(variables['System.TeamProject'], 'public')) }}:
name: "Azure Pipelines"
- vmImage: 'macOS-12'
+ vmImage: 'macOS-13'
os: macOS
# Official Build Windows Pool(I don't know enough about the .NET build engineering side so I could be wrong here, but that is definitely sus.)
Is there a way to restore the old behaviour for the .NET 9 lifecycle?
If not, should this at least be documented somewhere? I didn't see this in any of the release notes.
See SteamRE/SteamKit#1501 and Homebrew/homebrew-core#206331 (comment) for details.
Reproduction Steps
Attempt to DllImport/LibraryImport a system framework using a relative path on macOS e.g. CoreFoundation.framework/CoreFoundation
Expected behavior
The P/Invoke function is loaded and executed.
Actual behavior
System.DllNotFoundException : Unable to load shared library 'CoreFoundation.framework/CoreFoundation' or one of its dependencies. In order to help diagnose loading problems, consider setting the DYLD_PRINT_LIBRARIES environment variable:
dlopen(/Users/runner/.dotnet/shared/Microsoft.NETCore.App/9.0.1/CoreFoundation.framework/CoreFoundation.dylib, 0x0001): tried: '/Users/runner/.dotnet/shared/Microsoft.NETCore.App/9.0.1/CoreFoundation.framework/CoreFoundation.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/runner/.dotnet/shared/Microsoft.NETCore.App/9.0.1/CoreFoundation.framework/CoreFoundation.dylib' (no such file), '/Users/runner/.dotnet/shared/Microsoft.NETCore.App/9.0.1/CoreFoundation.framework/CoreFoundation.dylib' (no such file)
dlopen(/Users/runner/work/SteamKit/SteamKit/SteamKit2/Tests/bin/Debug/net8.0/CoreFoundation.framework/CoreFoundation.dylib, 0x0001): tried: '/Users/runner/work/SteamKit/SteamKit/SteamKit2/Tests/bin/Debug/net8.0/CoreFoundation.framework/CoreFoundation.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/runner/work/SteamKit/SteamKit/SteamKit2/Tests/bin/Debug/net8.0/CoreFoundation.framework/CoreFoundation.dylib' (no such file), '/Users/runner/work/SteamKit/SteamKit/SteamKit2/Tests/bin/Debug/net8.0/CoreFoundation.framework/CoreFoundation.dylib' (no such file)
dlopen(CoreFoundation.framework/CoreFoundation.dylib, 0x0001): tried: 'CoreFoundation.framework/CoreFoundation.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OSCoreFoundation.framework/CoreFoundation.dylib' (no such file), '/usr/lib/CoreFoundation.framework/CoreFoundation.dylib' (no such file, not in dyld cache), 'CoreFoundation.framework/CoreFoundation.dylib' (no such file)
dlopen(/Users/runner/.dotnet/shared/Microsoft.NETCore.App/9.0.1/CoreFoundation.framework/CoreFoundation, 0x0001): tried: '/Users/runner/.dotnet/shared/Microsoft.NETCore.App/9.0.1/CoreFoundation.framework/CoreFoundation' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/runner/.dotnet/shared/Microsoft.NETCore.App/9.0.1/CoreFoundation.framework/CoreFoundation' (no such file), '/Users/runner/.dotnet/shared/Microsoft.NETCore.App/9.0.1/CoreFoundation.framework/CoreFoundation' (no such file)
dlopen(/Users/runner/work/SteamKit/SteamKit/SteamKit2/Tests/bin/Debug/net8.0/CoreFoundation.framework/CoreFoundation, 0x0001): tried: '/Users/runner/work/SteamKit/SteamKit/SteamKit2/Tests/bin/Debug/net8.0/CoreFoundation.framework/CoreFoundation' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/runner/work/SteamKit/SteamKit/SteamKit2/Tests/bin/Debug/net8.0/CoreFoundation.framework/CoreFoundation' (no such file), '/Users/runner/work/SteamKit/SteamKit/SteamKit2/Tests/bin/Debug/net8.0/CoreFoundation.framework/CoreFoundation' (no such file)
dlopen(CoreFoundation.framework/CoreFoundation, 0x0001): tried: 'CoreFoundation.framework/CoreFoundation' (no such file), '/System/Volumes/Preboot/Cryptexes/OSCoreFoundation.framework/CoreFoundation' (no such file), '/usr/lib/CoreFoundation.framework/CoreFoundation' (no such file, not in dyld cache), 'CoreFoundation.framework/CoreFoundation' (no such file)
Regression?
This worked in .NET 8 and seems to also work in .NET 9.0.0. It fails in .NET 9.0.1.
Known Workarounds
Workaround 1: Launch with the environment variable DYLD_FRAMEWORK_PATH=/System/Library/Frameworks preset.
Workaround 2: Recompile and redeploy binaries with DllImport/LibraryImport set to an absolute path to the framework binaries, rather than a relative path.
Configuration
macos-14 and macos-latest on GitHub Actions, as well as my MacBook Pro (M2 Pro) with Sequoia 15.3 (24D60) arm64
.NET 9.0.1
Other information
cc @dotnet/compat
Metadata
Metadata
Assignees
Type
Projects
- StatusShow more project fieldsNo status
Activity
[-].NET 9.0.1 Breaking Change in macOS assembly resolution[/-][+].NET 9.0.1 Breaking Change in macOS native library resolution[/+]dotnet-policy-service commentedon Feb 3, 2025
Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.
AaronRobinsonMSFT commentedon Feb 3, 2025
@elinor-fung Thoughts on what might have changed here?
yaakov-h commentedon Feb 3, 2025
FWIW I'm also seeing this now on .NET 8.0.12.
If I run the failing unit test with
DOTNET_ROLL_FORWARD=disableit passes, withDOTNET_ROLL_FORWARD=LatestPatchit fails.I suspect that with
<RollForward>LatestMajor</RollForward>in the test .csproj it was rolling forward to 9.0 so I'm not sure which patch of .NET 8 introduced the same bug as I tried to set several but they would have all been skipped due to rollforward behaviour.Though given that the same vmImage update occurred between .NET 8.0.11 and .NET 8.0.12, I would put my money on that...
agocke commentedon Feb 3, 2025
I don’t think we changed anything in the code, so I agree that if something changed the OS image update looks notable. But I have no idea what could have changed in 13 to cause this type of code change.
ejhg commentedon Feb 22, 2025
I am seeing this issue in sdk
8.0.405and8.0.406. (Issue is absent in8.0.404and prior.)DennisDyallo commentedon Mar 6, 2025
We're seeing this issue in the Yubico.NET.SDK which loads some native libraries in order to do some native work (connecting to USB-devices, CFString, etc.)
We came across the error when upgrading our integration test and example projects to .net8 and .net9 and running on Mac (Sequoia 15.2, 15.3.1)
While these workarounds work fine in our own integration testing, it wont work well downstream with the end users of our SDK/3rd party client apps.
Would like to see a confirmation from MSFT on the issue, the intended fix and forward guidance.
DennisDyallo commentedon Apr 22, 2025
Any news?
elinor-fung commentedon Sep 29, 2025
I couldn't find anything official like release notes or some breaking change notice, but there are are various (non-.NET-related) reports about macOS library load behaviour changing after a tools version update - for example, https://developer.apple.com/forums/thread/737920
The dyld man page at https://github.com/apple-oss-distributions/dyld/blob/main/doc/man/man1/dyld.1 does indicate there was some change around this.
DYLD_FALLBACK_FRAMEWORK_PATH
DYLD_FALLBACK_LIBRARY_PATH
Assuming this new behaviour is intentional on the macOS side, I don't know that the .NET runtime should try to do anything explicit to go back to the old behaviour. This step of our native library load is basically 'use default OS load'.
@agocke do we have contacts on the Apple side we could ask about this? And if they have guidance for folks that were relying on the previous fallbacks?
agocke commentedon Sep 30, 2025
Yup, I can start a conversation with Apple to get confirmation that this is intended.
CamJN commentedon Oct 3, 2025
Assuming that the loading behaviour of macOS has changed, how should a C# library add paths to load from on macOS? I've tried reading about it, but not found anything practical. For example this seems to only handle the executable case, not the case of a library providing bindings to a native library.
jkotas commentedon Oct 3, 2025
You can use absolute paths in DllImport.
If it is not possible or desirable, the library that provides binding to native library can register DllImport resolver for itself. For example, including the following file in the library project is going to register the resolver before any code in that library gets executed:
CamJN commentedon Oct 3, 2025
@jkotas thanks so much!
agocke commentedon Oct 3, 2025
In particular, if you're trying to load a library that you know is part of the system install, potentially in a path like
/System/Library/Frameworks, then I would definitely recommend loading via absolute path. In that case you know where the library should be and where you want to load from.