Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 5 additions & 3 deletions docs/agent-guides/platform-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 `<Player>_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 `<Player>_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

Expand Down
97 changes: 97 additions & 0 deletions package-dev/Runtime/Sentry.Unity.Native.Android.dll.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package-dev/Runtime/Sentry.Unity.Native.dll.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/Sentry.Unity.Android/Sentry.Unity.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
<ItemGroup>
<ProjectReference Include="../sentry-dotnet/src/Sentry/Sentry.csproj" Private="false" />
<ProjectReference Include="../Sentry.Unity/Sentry.Unity.csproj" Private="false" />
<ProjectReference Include="../Sentry.Unity.Native/Sentry.Unity.Native.csproj" Private="false" PrivateAssets="all" />
<!-- Build order only. The types come from the Android variant, which keeps DllImport("sentry") -->
<ProjectReference Include="../Sentry.Unity.Native/Sentry.Unity.Native.csproj" Private="false" PrivateAssets="all" ReferenceOutputAssembly="false" />
<Reference Include="$(PackageRuntimePath)/Sentry.Unity.Native.Android.dll" Private="false" />
</ItemGroup>

</Project>
38 changes: 27 additions & 11 deletions src/Sentry.Unity.Editor/Native/BuildPostProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -160,7 +166,7 @@ private static IEnumerable<NativePluginArtifact> 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))
Expand All @@ -169,9 +175,14 @@ private static IEnumerable<NativePluginArtifact> 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;

Expand All @@ -186,7 +197,10 @@ private static IEnumerable<NativePluginArtifact> 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,
Expand All @@ -205,8 +219,8 @@ private static IEnumerable<NativePluginArtifact> 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 (<name>_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 (<name>_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);
Expand All @@ -219,10 +233,11 @@ private static IEnumerable<NativePluginArtifact> 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);
}
Expand All @@ -242,18 +257,16 @@ private static IEnumerable<NativePluginArtifact> 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");
foreach (var stale in new[]
{
Path.Combine(contents, "PlugIns", "Sentry.dylib"),
Path.Combine(contents, "PlugIns", "libsentry.dylib"),
Path.Combine(contents, "PlugIns", MacosLibName),
Path.Combine(contents, "MacOS", "sentry-crash"),
})
{
Expand All @@ -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))
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion src/Sentry.Unity.Native/CFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions src/Sentry.Unity.Native/Sentry.Unity.Native.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,24 @@
/>
</Target>

<!-- Build Android version after the Xbox build. Android's libsentry.so comes from the
sentry-android-ndk AAR, so it keeps the original import name -->
<Target Name="BuildAndroidAssembly" AfterTargets="BuildXboxAssembly">
<Message Importance="High" Text="Building Android-specific native assembly." />

<Csc
Sources="@(Compile)"
References="@(ReferencePath)"
OutputAssembly="$(OutDir)Sentry.Unity.Native.Android.dll"
DefineConstants="$(DefineConstants);SENTRY_NATIVE_ANDROID"
TargetType="library"
EmitDebugInformation="true"
DebugType="portable"
Nullable="$(Nullable)"
LangVersion="$(LangVersion)"
TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
WarningLevel="$(WarningLevel)"
/>
</Target>

</Project>
2 changes: 1 addition & 1 deletion src/Sentry.Unity.Native/SentryNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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?");
}
}
}
7 changes: 5 additions & 2 deletions src/Sentry.Unity.Native/SentryNativeBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();

Expand Down
1 change: 1 addition & 0 deletions src/Sentry.Unity/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
4 changes: 4 additions & 0 deletions test/Scripts.Tests/package-release.zip.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading