xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.44k stars 508 forks source link

net7/8-ios: EntryPointNotFoundException on remapped DllImports, even when #19624

Open kiddkaffeine opened 9 months ago

kiddkaffeine commented 9 months ago

This is a followup to https://github.com/xamarin/xamarin-macios/issues/19599. We've migrated to using NativeLibrary.SetDllImportResolver; however, this results in "EntryPointNotFound" unless the function is statically referenced elsewhere.

In legacy Xamarin, using the "Do not strip native debugging symbols." and -force_load flags would preserve all the entry points but it appears that no longer works as expected.

Steps to Reproduce

  1. Link with a static library in net8.0-ios.
  2. Make sure to check "Do not strip native debugging symbols."
  3. Add force_load to the builder flags, --cxx --gcc_flags "-L$(MSBuildProjectDirectory) -force_load $(MSBuildProjectDirectory)/libSDL2.a"
  4. Create a DllImport which is rewritten, example:

    [DllImport("RemapMe", EntryPoint = "SDL_SetHint", CallingConvention = CallingConvention.Cdecl)] private static extern unsafe SDL_bool Internal_SDL_SetHint( byte name, byte value );

    NativeLibrary.SetDllImportResolver(assembly, MapAndLoad);

    private static IntPtr MapAndLoad( string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath ) { return NativeLibrary.GetMainProgramHandle(); }

(A sample .cs and library are attached.)

Expected Behavior

Native function can be invoked.

Actual Behavior

Native function fails with "EntryPointNotFound" exception.

In the associated test file, we see: Hello world! Assembly is TestProj, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null Attempting to call SDL_SetHint using DllImportResolver with Internal... Received request to map UseInternal TestProj, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null AssemblyDirectory... Rewriting to Internal Received request to map UseInternal TestProj, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null AssemblyDirectory... Rewriting to __Internal System.DllNotFoundException: UseInternal at Program.Main(String[] args) in /Users/miles/Projects/TestProj/Main.cs:line 40 Attempting to call SDL_SetHint using DllImportResolver with GetProgramHandle... Received request to map UseProgramHandle TestProj, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null AssemblyDirectory... Rewriting using MainProgramHandle System.EntryPointNotFoundException: SDL_SetHint at Program.Main(String[] args) in /Users/miles/Projects/TestProj/Main.cs:line 52 Test complete.

Workaround

If the entry point is directly referenced elsewhere in the project, i.e.,

[DllImport("__Internal", EntryPoint = "SDL_SetHint", CallingConvention = CallingConvention.Cdecl)]
private static extern void DoNotUse();

then the remapped calls will function correctly, but any remapped calls to other entry points will still fail.

Environment

net8.0-ios. This worked fine with .dll.config files in legacy Xamarin.

sample.zip

rolfbjarne commented 9 months ago

I can reproduce. Complete test project: sample-8e39058.zip

I've found a couple of workarounds, which may or may not work in a bigger project:

najak3d commented 4 months ago

I can reproduce. Complete test project: sample-8e39058.zip

I've found a couple of workarounds, which may or may not work in a bigger project:

  • Add -dlsym:false to the MtouchExtraArgs. This won't work if you have DllImports to functions that don't exist.
  • Set _ExportSymbolsExplicitly=false in the project file (this may increase the app size, but should otherwise work):
    <PropertyGroup>
      <_ExportSymbolsExplicitly>false</_ExportSymbolsExplicitly>
    </PropertyGroup>

Even as of May 10, 2024, with .NET 8.0, we are stuck on this issue. With your sample project zip, it fails as you reported (i.e. "EntryPointNotFoundException")

If fails even when MtouchExtraArgs looks like this (with 'dlsym:false'):

--cxx --gcc_flags "-LC:/sample/ -force_load C:/sample/libSDL2.a -framework AudioToolbox -framework AVFoundation -framework AVFAudio -framework OpenGLES -framework CoreBluetooth -framework CoreHaptics -framework GameController -framework QuartzCore -framework Metal -framework CoreMotion" -dlsym:false

And it still fails even when adding these 3 lines to the csproj:

<_ExportSymbolsExplicitly>false

The 'Internal_SDL_SetHint' fails with DllNotFoundException.

The 'Internal_SDL_SetHint' call fails with "EntryPointNotFoundException" for "_SDL_SetHint"

We're wondering if the failure might have something to do with "_" underscore prefix.

rolfbjarne commented 3 months ago

If fails even when MtouchExtraArgs looks like this (with 'dlsym:false'): --cxx --gcc_flags "-LC:/sample/ -force_load C:/sample/libSDL2.a -framework AudioToolbox -framework AVFoundation -framework AVFAudio -framework OpenGLES -framework CoreBluetooth -framework CoreHaptics -framework GameController -framework QuartzCore -framework Metal -framework CoreMotion" -dlsym:false

There are Windows-style paths in there, so I'm assuming you're building from Windows.

Do you see the same behavior if you're building on a Mac?

najak3d commented 3 months ago

Thank you for your response. We are building on Windows now, as seems to be what .NET 8 wants us to do. We resolved this by eliminating our need for the C++ DLL on the Mac, porting that code back to C# (where it runs slower, but still runs fast enough).

So for us, this issue was resolved by avoidance (i.e. eliminating usage of our C++ DLL), after spinning our wheels for about 2 days.

ryancheung commented 2 months ago

I can reproduce. Complete test project: sample-8e39058.zip

I've found a couple of workarounds, which may or may not work in a bigger project:

  • Add -dlsym:false to the MtouchExtraArgs. This won't work if you have DllImports to functions that don't exist.
  • Set _ExportSymbolsExplicitly=false in the project file (this may increase the app size, but should otherwise work):
    <PropertyGroup>
      <_ExportSymbolsExplicitly>false</_ExportSymbolsExplicitly>
    </PropertyGroup>

After hours testing, the _ExportSymbolsExplicitly workaround works only for simulator, but not ios-arm64 in real iPhone device. Upgraded to .net 9 preview 5 and still not working.

So, are there any workaround for real iPhone device that don't need change code?

rolfbjarne commented 2 months ago

I can reproduce. Complete test project: sample-8e39058.zip I've found a couple of workarounds, which may or may not work in a bigger project:

  • Add -dlsym:false to the MtouchExtraArgs. This won't work if you have DllImports to functions that don't exist.
  • Set _ExportSymbolsExplicitly=false in the project file (this may increase the app size, but should otherwise work):
    <PropertyGroup>
      <_ExportSymbolsExplicitly>false</_ExportSymbolsExplicitly>
    </PropertyGroup>

After hours testing, the _ExportSymbolsExplicitly workaround works only for simulator, but not ios-arm64 in real iPhone device. Upgraded to .net 9 preview 5 and still not working.

So, are there any workaround for real iPhone device that don't need change code?

It should work just as well on device as well, so maybe something else is going on.

Can you file a new issue and if possible attach a test project - but at the very least we'll need a build log (https://github.com/xamarin/xamarin-macios/wiki/Diagnosis#binary-build-logs) - then we can have a look and try to figure out what's going on.

ryancheung commented 2 months ago

Here is the repro project https://github.com/FNA-NET/Samples/tree/dllimport-repro/iOSGame1 Run dotnet build -t:Run and get the error:

  Launched application 'org.FNA-NET.iOSGame1' on 'iPhone' with pid 1413
2024-07-08 21:56:23.480 iOSGame1[1413:365494] 
Unhandled Exception:
System.EntryPointNotFoundException: SDL_SetHint
   at SDL2.SDL.SDL_SetHint(String name, String value) in /Users/ryan/FNA/FNA-NET.Samples/FNA/lib/SDL2-CS/src/SDL2.cs:line 845
   at iOSGame1.Program.Main(String[] args) in /Users/ryan/FNA/FNA-NET.Samples/iOSGame1/Program.cs:line 28
2024-07-08 21:56:23.481 iOSGame1[1413:365494] Unhandled managed exception: SDL_SetHint (System.EntryPointNotFoundException)
   at SDL2.SDL.SDL_SetHint(String name, String value) in /Users/ryan/FNA/FNA-NET.Samples/FNA/lib/SDL2-CS/src/SDL2.cs:line 845
   at iOSGame1.Program.Main(String[] args) in /Users/ryan/FNA/FNA-NET.Samples/iOSGame1/Program.cs:line 28

=================================================================
        Native Crash Reporting
=================================================================
Got a SIGABRT while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries 
used by your application.
=================================================================

=================================================================
        Native stacktrace:
=================================================================
        0x1021c42a8 - /private/var/containers/Bundle/Application/4C14CAD4-5C5B-4911-934D-82BA8A5ECA72/PublicStaging.app/iOSGame1 : mono_dump_native_crash_info
        0x1021af194 - /private/var/containers/Bundle/Application/4C14CAD4-5C5B-4911-934D-82BA8A5ECA72/PublicStaging.app/iOSGame1 : mono_handle_native_crash
        0x10220815c - /private/var/containers/Bundle/Application/4C14CAD4-5C5B-4911-934D-82BA8A5ECA72/PublicStaging.app/iOSGame1 : sigabrt_signal_handler.cold.1
        0x1021c3a98 - /private/var/containers/Bundle/Application/4C14CAD4-5C5B-4911-934D-82BA8A5ECA72/PublicStaging.app/iOSGame1 : mono_runtime_setup_stat_profiler
        0x20219b2f4 - /usr/lib/system/libsystem_platform.dylib : <redacted>
        0x20223e5f8 - /usr/lib/system/libsystem_pthread.dylib : pthread_kill
        0x1be2f84b8 - /usr/lib/system/libsystem_c.dylib : abort
        0x101efbf48 - /private/var/containers/Bundle/Application/4C14CAD4-5C5B-4911-934D-82BA8A5ECA72/PublicStaging.app/iOSGame1 : xamarin_find_protocol_wrapper_type
        0x1020f9ecc - /private/var/containers/Bundle/Application/4C14CAD4-5C5B-4911-934D-82BA8A5ECA72/PublicStaging.app/iOSGame1 : mono_invoke_unhandled_exception_hook
        0x102199cb4 - /private/var/containers/Bundle/Application/4C14CAD4-5C5B-4911-934D-82BA8A5ECA72/PublicStaging.app/iOSGame1 : mono_jit_exec
        0x101f0e484 - /private/var/containers/Bundle/Application/4C14CAD4-5C5B-4911-934D-82BA8A5ECA72/PublicStaging.app/iOSGame1 : xamarin_main
        0x102202458 - /private/var/containers/Bundle/Application/4C14CAD4-5C5B-4911-934D-82BA8A5ECA72/PublicStaging.app/iOSGame1 : main
        0x1d4cbc344 - /usr/lib/dyld : <redacted>

=================================================================
        Basic Fault Address Reporting
=================================================================
Memory around native instruction pointer (0x1f2caf198):0x1f2caf188  c0 03 5f d6 c0 03 5f d6 10 29 80 d2 01 10 00 d4  .._..._..)......
0x1f2caf198  e3 00 00 54 fd 7b bf a9 fd 03 00 91 73 ee ff 97  ...T.{......s...
0x1f2caf1a8  bf 03 00 91 fd 7b c1 a8 c0 03 5f d6 c0 03 5f d6  .....{...._..._.
0x1f2caf1b8  fd 7b bf a9 fd 03 00 91 50 00 80 d2 01 10 00 d4  .{......P.......

=================================================================
        Managed Stacktrace:
=================================================================
=================================================================
  Application 'org.FNA-NET.iOSGame1' terminated (with exit code '' and/or crashing signal '6).

And the binary build log: msbuild.binlog.zip

rolfbjarne commented 1 month ago

I can reproduce the problem, thanks for the test case.

The problem is that we pass -dead_strip to the native linker, and that ends up removing the native functions that are required at runtime.

As a workaround, you can add this to the csproj:

<MtouchExtraArgs>-gcc_flags -v</MtouchExtraArgs>

This doesn't do anything (except ask the native compiler to be more verbose when compiling), and it works because we disable dead stripping if any value of -gcc_flags is passed.