Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NativeAOT fails to resolve PInvoke in System.Data.SqlClient #89874

Closed
vitek-karas opened this issue Aug 2, 2023 · 10 comments · Fixed by #90120
Closed

NativeAOT fails to resolve PInvoke in System.Data.SqlClient #89874

vitek-karas opened this issue Aug 2, 2023 · 10 comments · Fixed by #90120
Assignees
Milestone

Comments

@vitek-karas
Copy link
Member

See #89782 (comment) for details.
The failure is really weird - I can confirm that the native dll is next to the AOT compiled app.
Non-AOT version of the app can load the dll just fine (it fails later on for me, but that's not the issue here).
I also tried just a simple app with a PInvoke (using Libuv package and calling into it) and that works just fine even with NativeAOT.

Repro steps:
https://github.com/dotnet/runtime/files/12243049/sqlClient.zip
Publish as AOT and run on win-x64.

Unhandled Exception: System.DllNotFoundException: Unable to load DLL 'sni.dll' or one of its dependencies: The specified module could not be found.
   at System.Runtime.InteropServices.NativeLibrary.LoadLibErrorTracker.Throw(String) + 0x6f
   at Internal.Runtime.CompilerHelpers.InteropHelpers.FixupModuleCell(InteropHelpers.ModuleFixupCell*) + 0xfd
   at Internal.Runtime.CompilerHelpers.InteropHelpers.ResolvePInvokeSlow(InteropHelpers.MethodFixupCell*) + 0x2f
   at System.Data.SqlClient.SNINativeMethodWrapper.UnmanagedIsTokenRestricted(IntPtr, Boolean&) + 0x31
   at System.Data.Win32NativeMethods.IsTokenRestrictedWrapper(IntPtr) + 0x19
   at System.Data.ProviderBase.DbConnectionPoolIdentity.GetCurrentNative() + 0xb0
   at System.Data.ProviderBase.DbConnectionPoolGroup.GetConnectionPool(DbConnectionFactory) + 0x51
   at System.Data.ProviderBase.DbConnectionFactory.GetConnectionPool(DbConnection, DbConnectionPoolGroup) + 0x5f
   at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection, TaskCompletionSource`1, DbConnectionOptions, DbConnectionInternal, DbConnectionInternal&) + 0xb6
   at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection, DbConnectionFactory, TaskCompletionSource`1, DbConnectionOptions) + 0x118
   at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1) + 0x101
   at System.Data.SqlClient.SqlConnection.Open() + 0xf8
   at Program.<Main>$(String[] args) + 0x45
   at sqlClient!<BaseAddress>+0x413074
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Aug 2, 2023
@ghost
Copy link

ghost commented Aug 2, 2023

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

See #89782 (comment) for details.
The failure is really weird - I can confirm that the native dll is next to the AOT compiled app.
Non-AOT version of the app can load the dll just fine (it fails later on for me, but that's not the issue here).
I also tried just a simple app with a PInvoke (using Libuv package and calling into it) and that works just fine even with NativeAOT.

Repro steps:
https://github.com/dotnet/runtime/files/12243049/sqlClient.zip
Publish as AOT and run on win-x64.

Unhandled Exception: System.DllNotFoundException: Unable to load DLL 'sni.dll' or one of its dependencies: The specified module could not be found.
   at System.Runtime.InteropServices.NativeLibrary.LoadLibErrorTracker.Throw(String) + 0x6f
   at Internal.Runtime.CompilerHelpers.InteropHelpers.FixupModuleCell(InteropHelpers.ModuleFixupCell*) + 0xfd
   at Internal.Runtime.CompilerHelpers.InteropHelpers.ResolvePInvokeSlow(InteropHelpers.MethodFixupCell*) + 0x2f
   at System.Data.SqlClient.SNINativeMethodWrapper.UnmanagedIsTokenRestricted(IntPtr, Boolean&) + 0x31
   at System.Data.Win32NativeMethods.IsTokenRestrictedWrapper(IntPtr) + 0x19
   at System.Data.ProviderBase.DbConnectionPoolIdentity.GetCurrentNative() + 0xb0
   at System.Data.ProviderBase.DbConnectionPoolGroup.GetConnectionPool(DbConnectionFactory) + 0x51
   at System.Data.ProviderBase.DbConnectionFactory.GetConnectionPool(DbConnection, DbConnectionPoolGroup) + 0x5f
   at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection, TaskCompletionSource`1, DbConnectionOptions, DbConnectionInternal, DbConnectionInternal&) + 0xb6
   at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection, DbConnectionFactory, TaskCompletionSource`1, DbConnectionOptions) + 0x118
   at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1) + 0x101
   at System.Data.SqlClient.SqlConnection.Open() + 0xf8
   at Program.<Main>$(String[] args) + 0x45
   at sqlClient!<BaseAddress>+0x413074
Author: vitek-karas
Assignees: -
Labels:

area-NativeAOT-coreclr

Milestone: -

@vitek-karas
Copy link
Member Author

@MichalStrehovsky any ideas - I can try looking into it, but I don't know where to start really.

@jkotas
Copy link
Member

jkotas commented Aug 2, 2023

System.Data.SqlClient is annotated with DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.System32

These search flags get passed correctly into NativeLibrary.LoadLibraryByName

internal static IntPtr LoadLibraryByName(string libraryName, Assembly assembly, DllImportSearchPath searchPath, bool throwOnError)
{
int searchPathFlags = (int)(searchPath & ~DllImportSearchPath.AssemblyDirectory);
bool searchAssemblyDirectory = (searchPath & DllImportSearchPath.AssemblyDirectory) != 0;
that masks out DllImportSearchPath.AssemblyDirectory and so we are left with just DllImportSearchPath.System32.

The problem happens here:

else if ((callingAssembly != null) && searchAssemblyDirectory)
{
// Try to load the module alongside the assembly where the PInvoke was declared.
// This only makes sense in dynamic scenarios (JIT/interpreter), so leaving this out for now.
}

We effectively ignore DllImportSearchPath.AssemblyDirectory/searchAssemblyDirectory and only pass DllImportSearchPath.System32 into LoadLibraryHelper. There is no sni.dll in system32 directory and the attempt to load the library fails.

@vitek-karas
Copy link
Member Author

That comment reads wrong - NativeAOT already made the decision to put native dependencies next to the exe (instead of the assembly the PInvoke came from), so treating AssemblyDirectory as "app directory" would make sense for NativeAOT (also - there's no other location to consider anyway).

@MichalStrehovsky
Copy link
Member

This was written as "better to be conservative than to get an MSRC later". How does CoreCLR single file compute "the location of the assembly", is AssemblyDirectory just bashed to mean ApplicationDirectory? We could do the same, but cc @elinor-fung

@MichalStrehovsky
Copy link
Member

This was written as "better to be conservative than to get an MSRC later". How does CoreCLR single file compute "the location of the assembly", is AssemblyDirectory just bashed to mean ApplicationDirectory? We could do the same, but cc @elinor-fung

And how does this distinction look like for the "NativeAOT'd shared library" case?

@elinor-fung
Copy link
Member

Single-file actually adds the app directory to NATIVE_DLL_SEARCH_DIRECTORIES: #42876, #42772
AssemblyDirectory is currently meaningless for assemblies bundled into the app executable and the app effectively always has ApplicationDirectory. (I do think making AssemblyDirectory for bundled assemblies mean the directory of the app they are bundled in makes more sense - especially now that single-file apps don't have the runtime native bits separate and next to it)

@vitek-karas vitek-karas added this to the 8.0.0 milestone Aug 4, 2023
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Aug 4, 2023
@vitek-karas
Copy link
Member Author

I think this is something we should try to fix for .NET 8. I'll be out for the next week, so if somebody could pick this up?
Just to make it easier for whoever is going to work on this: @elinor-fung could you please paste a link here to where we have CoreCLR/Single-File tests for this? So that we can enable them for NativeAOT once this is fixed.

@elinor-fung
Copy link
Member

The coreclr test cases are:

[Fact]
public void LoadLibrary_AssemblyDirectory()

[ConditionalFact(nameof(CanLoadAssemblyInSubdirectory))]
public static void AssemblyDirectory_Found()

NativeAOT would need a variation of that though. They both use Assembly.LoadFile to load an assembly that is not next to the app and resolve relative to that assembly (corerun adds the app path to the native search directories, so it had to be in a different directory to actually test the AssemblyDirectory logic).

@AaronRobinsonMSFT
Copy link
Member

I'll take a look at this.

@AaronRobinsonMSFT AaronRobinsonMSFT self-assigned this Aug 7, 2023
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Aug 7, 2023
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Aug 8, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Sep 7, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants