diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d69cce8c..759b01cbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Fixed native crash capture on Windows with the Mono scripting backend. The native library is now copied into the player as `sentry-native`, so `DllImport` can no longer resolve to the managed `Sentry.dll` sitting beside it in `Managed/` ([#2818](https://github.com/getsentry/sentry-unity/issues/2818)) + ## 4.9.0 ### Features diff --git a/docs/agent-guides/platform-native.md b/docs/agent-guides/platform-native.md index 01677c9a6..965277d9d 100644 --- a/docs/agent-guides/platform-native.md +++ b/docs/agent-guides/platform-native.md @@ -7,6 +7,8 @@ - `sentry_get_crashed_last_run` clears native state; SDK caches its result for the process lifetime. Do not make it repeatable. - Native backend reinstalls before first scene after Unity takes crash/signal handlers. - Native logger forwarding to C# exists only under IL2CPP. +- The native library is copied into the player as `sentry-native`, not `sentry`, because `sentry` resolves to the managed `Sentry.dll` on Windows. +- Android is the exception: its `libsentry.so` comes from the sentry-android-ndk AAR, so it keeps `sentry` and gets its own `Sentry.Unity.Native.Android.dll` built from the same sources. ## Backend Choices @@ -22,9 +24,9 @@ Experimental native modes raise minimum shutdown timeout to 10 seconds. `Sentry.Unity.Editor/Native/BuildPostProcess.cs` selects legacy `Sentry~` or experimental `SentryNative~`, clears stale handler artifacts when switching backend, copies runtime libraries to player locations, and leaves symbols in package for upload. -- Windows: runtime files beside player `.exe`. -- Linux: `libsentry.so` under `_Data/Plugins/x86_64`; native daemon beside executable. -- macOS: dylib in `.app/Contents/PlugIns`; handler in `.app/Contents/MacOS`. +- Windows: runtime files beside player `.exe`; the native library lands as `sentry-native.dll`. +- Linux: `libsentry-native.so` under `_Data/Plugins/x86_64`; native daemon beside executable. +- macOS: dylib in `.app/Contents/PlugIns` as `libsentry-native.dylib`; handler in `.app/Contents/MacOS`. The Cocoa backend's `Sentry.dylib` keeps its name, it is dlopened rather than P/Invoked. ## Console Plugins diff --git a/package-dev/Runtime/Sentry.Unity.Native.Android.dll.meta b/package-dev/Runtime/Sentry.Unity.Native.Android.dll.meta new file mode 100644 index 000000000..89862f8fb --- /dev/null +++ b/package-dev/Runtime/Sentry.Unity.Native.Android.dll.meta @@ -0,0 +1,97 @@ +fileFormatVersion: 2 +guid: c5bff87923964bd3827bb7a2c0d7f4e3 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude Lumin: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + Exclude tvOS: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + - first: + tvOS: tvOS + second: + enabled: 0 + settings: + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/package-dev/Runtime/Sentry.Unity.Native.dll.meta b/package-dev/Runtime/Sentry.Unity.Native.dll.meta index 8f7b22dcf..f481805d8 100644 --- a/package-dev/Runtime/Sentry.Unity.Native.dll.meta +++ b/package-dev/Runtime/Sentry.Unity.Native.dll.meta @@ -16,7 +16,7 @@ PluginImporter: second: enabled: 0 settings: - Exclude Android: 0 + Exclude Android: 1 Exclude Editor: 1 Exclude GameCoreScarlett: 1 Exclude GameCoreXboxOne: 1 @@ -33,7 +33,7 @@ PluginImporter: - first: Android: Android second: - enabled: 1 + enabled: 0 settings: CPU: ARMv7 - first: diff --git a/src/Sentry.Unity.Android/Sentry.Unity.Android.csproj b/src/Sentry.Unity.Android/Sentry.Unity.Android.csproj index a620dd65c..caa384076 100644 --- a/src/Sentry.Unity.Android/Sentry.Unity.Android.csproj +++ b/src/Sentry.Unity.Android/Sentry.Unity.Android.csproj @@ -6,7 +6,9 @@ - + + + diff --git a/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs b/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs index 371e8f4ea..8476b76b1 100644 --- a/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs +++ b/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs @@ -134,6 +134,12 @@ and not BuildTargetGroup.PS5 _ => false, }; + // Mono probes the calling assembly's own folder first, so on Windows DllImport("sentry") + // binds to the managed Sentry.dll sitting next to it in Managed/ + private const string WindowsLibName = "sentry-native.dll"; + private const string LinuxLibName = "libsentry-native.so"; + private const string MacosLibName = "libsentry-native.dylib"; + private readonly struct NativePluginArtifact(string source, string destination, bool isExecutable = false) { public readonly string Source = source; @@ -160,7 +166,7 @@ private static IEnumerable GetNativePluginArtifact( $"Sentry Windows plugin directory not found: {windowsBackendSourcePath}\n" + $"Run 'dotnet msbuild /t:{buildTarget} src/Sentry.Unity' (or 'dotnet msbuild /t:DownloadNativeSDKs src/Sentry.Unity') to populate it."); } - // Flat copy of every non-PDB file next to the player .exe — sentry.dll and the + // Flat copy of every non-PDB file next to the player .exe. The native library and the // crash handler (crashpad_handler.exe / sentry-crash.exe) all sit at the build root. // PDBs stay in the package and are consumed at symbol-upload time only. foreach (var file in Directory.GetFiles(windowsBackendSourcePath)) @@ -169,9 +175,14 @@ private static IEnumerable GetNativePluginArtifact( { continue; } + var windowsName = Path.GetFileName(file); + if (windowsName.Equals("sentry.dll", StringComparison.OrdinalIgnoreCase)) + { + windowsName = WindowsLibName; + } yield return new NativePluginArtifact( file, - Path.Combine(buildOutputDir, Path.GetFileName(file))); + Path.Combine(buildOutputDir, windowsName)); } break; @@ -186,7 +197,10 @@ private static IEnumerable GetNativePluginArtifact( var isDylib = name.EndsWith(".dylib", StringComparison.OrdinalIgnoreCase); // The .dylibs need to go into the `*.app/Contents/Plugins` dirctory and will be picked // up by unity. The crash handler (sentry-native) needs to be next to the game's executable - var desination = Path.Combine(contents, isDylib ? "PlugIns" : "MacOS", name); + // Only libsentry.dylib is the P/Invoke target. The Cocoa backend's Sentry.dylib is + // dlopened by SentryNativeBridge.m under that exact name, so leave it alone. + var dylibName = name.Equals("libsentry.dylib", StringComparison.OrdinalIgnoreCase) ? MacosLibName : name; + var desination = Path.Combine(contents, isDylib ? "PlugIns" : "MacOS", dylibName); yield return new NativePluginArtifact( file, desination, @@ -205,8 +219,8 @@ private static IEnumerable GetNativePluginArtifact( $"Sentry Linux plugin directory not found: {linuxBackendSourcePath}\n" + $"Run 'dotnet msbuild /t:{buildTarget} src/Sentry.Unity' (or 'dotnet msbuild /t:DownloadNativeSDKs src/Sentry.Unity') to populate it."); } - // libsentry.so must sit in the player's native plugin dir (_Data/Plugins/x86_64) where the - // Linux player resolves DllImport("sentry"). The crash daemon (sentry-crash, native backend only) + // The native library must sit in the player's native plugin dir (_Data/Plugins/x86_64) + // where the Linux player resolves the P/Invoke. The crash daemon (sentry-crash, native backend only) // sits next to the player executable so sentry-native can spawn it on crash. // The .dbg.so / .dbg debug sidecars stay in the package and are consumed at symbol-upload time only. var linuxPluginDir = GetLinuxPluginDir(buildOutputDir); @@ -219,10 +233,11 @@ private static IEnumerable GetNativePluginArtifact( continue; } var isSharedObject = name.EndsWith(".so", StringComparison.OrdinalIgnoreCase); + var soName = name.Equals("libsentry.so", StringComparison.OrdinalIgnoreCase) ? LinuxLibName : name; yield return new NativePluginArtifact( file, isSharedObject - ? Path.Combine(linuxPluginDir, name) + ? Path.Combine(linuxPluginDir, soName) : Path.Combine(buildOutputDir, name), isExecutable: !isSharedObject); } @@ -242,11 +257,8 @@ private static IEnumerable GetNativePluginArtifact( } } - // On case-insensitive APFS, leftover artifacts from a prior build with - // the *other* macOS backend break DllImport("sentry") resolution - // (Sentry.dylib gets picked over libsentry.dylib, surfacing as - // `sentry_options_new` not found at runtime). Wipe both candidates - // before copying the current backend's files in. + // Wipe both backends' leftovers before copying the current one in, so an iterative + // build does not leave two libraries sitting in PlugIns. private static void CleanupStaleMacOSArtifacts(IDiagnosticLogger logger, string executablePath) { var contents = Path.Combine(executablePath, "Contents"); @@ -254,6 +266,7 @@ private static void CleanupStaleMacOSArtifacts(IDiagnosticLogger logger, string { Path.Combine(contents, "PlugIns", "Sentry.dylib"), Path.Combine(contents, "PlugIns", "libsentry.dylib"), + Path.Combine(contents, "PlugIns", MacosLibName), Path.Combine(contents, "MacOS", "sentry-crash"), }) { @@ -277,6 +290,8 @@ private static void CleanupStaleWindowsArtifacts(IDiagnosticLogger logger, strin Path.Combine(buildOutputDir, "crashpad_wer.dll"), Path.Combine(buildOutputDir, "sentry-crash.exe"), Path.Combine(buildOutputDir, "sentry-wer.dll"), + Path.Combine(buildOutputDir, "sentry.dll"), + Path.Combine(buildOutputDir, WindowsLibName), }) { if (File.Exists(stale)) @@ -311,6 +326,7 @@ private static void CleanupStaleLinuxArtifacts(IDiagnosticLogger logger, string if (dataDir is not null) { stalePaths.Add(Path.Combine(dataDir, "Plugins", "x86_64", "libsentry.so")); + stalePaths.Add(Path.Combine(dataDir, "Plugins", "x86_64", LinuxLibName)); } foreach (var stale in stalePaths) diff --git a/src/Sentry.Unity.Native/CFunctions.cs b/src/Sentry.Unity.Native/CFunctions.cs index e0bef2db9..59a5b5d4a 100644 --- a/src/Sentry.Unity.Native/CFunctions.cs +++ b/src/Sentry.Unity.Native/CFunctions.cs @@ -10,8 +10,12 @@ internal static class C { #if SENTRY_NATIVE_SWITCH private const string SentryLib = "__Internal"; -#else +#elif SENTRY_NATIVE_ANDROID || SENTRY_NATIVE_PLAYSTATION || SENTRY_NATIVE_XBOX private const string SentryLib = "sentry"; +#else + // "sentry" would bind to the managed Sentry.dll on Windows, so BuildPostProcess copies the + // native library in under this name. Android and the consoles ship theirs from elsewhere + private const string SentryLib = "sentry-native"; #endif internal static void SetValueIfNotNull(sentry_value_t obj, string key, string? value) diff --git a/src/Sentry.Unity.Native/Sentry.Unity.Native.csproj b/src/Sentry.Unity.Native/Sentry.Unity.Native.csproj index ca7e5386e..9b3cb2710 100644 --- a/src/Sentry.Unity.Native/Sentry.Unity.Native.csproj +++ b/src/Sentry.Unity.Native/Sentry.Unity.Native.csproj @@ -65,4 +65,24 @@ /> + + + + + + + diff --git a/src/Sentry.Unity.Native/SentryNative.cs b/src/Sentry.Unity.Native/SentryNative.cs index c255ada10..c8f1a366d 100644 --- a/src/Sentry.Unity.Native/SentryNative.cs +++ b/src/Sentry.Unity.Native/SentryNative.cs @@ -140,7 +140,7 @@ private static void ReinstallBackend() } catch (EntryPointNotFoundException e) { - Logger?.LogError(e, "Native dependency not found. Did you delete sentry.dll or move files around?"); + Logger?.LogError(e, "Native dependency not found. Did you delete the native Sentry library or move files around?"); } } } diff --git a/src/Sentry.Unity.Native/SentryNativeBridge.cs b/src/Sentry.Unity.Native/SentryNativeBridge.cs index 343521ed3..7e05fe9fc 100644 --- a/src/Sentry.Unity.Native/SentryNativeBridge.cs +++ b/src/Sentry.Unity.Native/SentryNativeBridge.cs @@ -16,8 +16,12 @@ internal static class SentryNativeBridge { #if SENTRY_NATIVE_SWITCH private const string SentryLib = "__Internal"; -#else +#elif SENTRY_NATIVE_ANDROID || SENTRY_NATIVE_PLAYSTATION || SENTRY_NATIVE_XBOX private const string SentryLib = "sentry"; +#else + // "sentry" would bind to the managed Sentry.dll on Windows, so BuildPostProcess copies the + // native library in under this name. Android and the consoles ship theirs from elsewhere + private const string SentryLib = "sentry-native"; #endif private static IDiagnosticLogger? Logger; // This is also the logger we're forwarding native messages to. @@ -165,7 +169,6 @@ internal static string GetDatabasePath(SentryUnityOptions options, IApplication? internal static void AppHangPause() => sentry_app_hang_pause(); - // libsentry.so [DllImport(SentryLib)] private static extern IntPtr sentry_options_new(); diff --git a/src/Sentry.Unity/Properties/AssemblyInfo.cs b/src/Sentry.Unity/Properties/AssemblyInfo.cs index 9e8226ca7..3f9e80ca2 100644 --- a/src/Sentry.Unity/Properties/AssemblyInfo.cs +++ b/src/Sentry.Unity/Properties/AssemblyInfo.cs @@ -4,6 +4,7 @@ [assembly: InternalsVisibleTo("Sentry.Unity.Native.PlayStation")] [assembly: InternalsVisibleTo("Sentry.Unity.Native.Switch")] [assembly: InternalsVisibleTo("Sentry.Unity.Native.Xbox")] +[assembly: InternalsVisibleTo("Sentry.Unity.Native.Android")] [assembly: InternalsVisibleTo("Sentry.Unity.Tests")] [assembly: InternalsVisibleTo("Sentry.Unity.Editor")] [assembly: InternalsVisibleTo("Sentry.Unity.Editor.Tests")] diff --git a/test/Scripts.Tests/package-release.zip.snapshot b/test/Scripts.Tests/package-release.zip.snapshot index 43200dc30..2a8713f6f 100644 --- a/test/Scripts.Tests/package-release.zip.snapshot +++ b/test/Scripts.Tests/package-release.zip.snapshot @@ -321,6 +321,10 @@ Runtime/Sentry.Unity.MacOS.dll Runtime/Sentry.Unity.MacOS.dll.meta Runtime/Sentry.Unity.MacOS.pdb Runtime/Sentry.Unity.MacOS.pdb.meta +Runtime/Sentry.Unity.Native.Android.dll +Runtime/Sentry.Unity.Native.Android.dll.meta +Runtime/Sentry.Unity.Native.Android.pdb +Runtime/Sentry.Unity.Native.Android.pdb.meta Runtime/Sentry.Unity.Native.dll Runtime/Sentry.Unity.Native.dll.meta Runtime/Sentry.Unity.Native.pdb