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
33 changes: 27 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,17 @@ jobs:
MINGW_ASM_MASM_COMPILER: llvm-ml
MINGW_ASM_MASM_FLAGS: -m64
- name: Android (API 21, NDK 23)
os: macos-15-large
os: ubuntu-latest
ANDROID_API: 21
ANDROID_NDK: 23.2.8568313
ANDROID_ARCH: x86_64
- name: Android (API 28, NDK 27)
os: ubuntu-latest
ANDROID_API: 28
ANDROID_NDK: 27.3.13750724
ANDROID_ARCH: x86_64
- name: Android (API 31, NDK 27)
os: macos-15-large
os: ubuntu-latest
ANDROID_API: 31
ANDROID_NDK: 27.3.13750724
ANDROID_ARCH: x86_64
Expand Down Expand Up @@ -242,12 +247,12 @@ jobs:
cache: "pip"

- name: Check Linux CC/CXX
if: ${{ runner.os == 'Linux' && !matrix.container }}
if: ${{ runner.os == 'Linux' && !env['ANDROID_API'] &&!matrix.container }}
run: |
[ -n "$CC" ] && [ -n "$CXX" ] || { echo "Ubuntu runner configurations require toolchain selection via CC and CXX" >&2; exit 1; }

- name: Installing Linux Dependencies
if: ${{ runner.os == 'Linux' && !env['TEST_X86'] && !matrix.container }}
if: ${{ runner.os == 'Linux' && !env['TEST_X86'] && !env['ANDROID_API'] && !matrix.container }}
run: |
sudo apt update
# Install common dependencies
Expand Down Expand Up @@ -278,7 +283,7 @@ jobs:
sudo make install

- name: Installing Linux 32-bit Dependencies
if: ${{ runner.os == 'Linux' && env['TEST_X86'] && !matrix.container }}
if: ${{ runner.os == 'Linux' && env['TEST_X86'] && !env['ANDROID_API'] &&!matrix.container }}
run: |
sudo dpkg --add-architecture i386
sudo apt update
Expand Down Expand Up @@ -357,6 +362,22 @@ jobs:
with:
gradle-home-cache-cleanup: true

- name: Setup .NET for Android
if: ${{ env['ANDROID_API'] }}
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'

- name: Install .NET Android workload
if: ${{ env['ANDROID_API'] }}
run: dotnet workload restore tests/fixtures/dotnet_signal/test_dotnet.csproj

- name: Enable KVM group perms
if: ${{ runner.os == 'Linux' && env['ANDROID_API'] }}
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Add sentry.native.test hostname
if: ${{ runner.os == 'Windows' }}
Expand Down Expand Up @@ -386,7 +407,7 @@ jobs:
api-level: ${{ env.ANDROID_API }}
ndk: ${{ env.ANDROID_NDK }}
arch: ${{ env.ANDROID_ARCH }}
target: google_apis
target: default
emulator-boot-timeout: 1200
script: |
# Sync emulator clock with host to avoid timestamp assertion failures
Expand Down
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**:

- Fix `CHAIN_AT_START` handler strategy crashing on Android when the chained Mono handler resets the signal handler and re-raises. ([#1572](https://github.com/getsentry/sentry-native/pull/1572))

## 0.13.2

**Features**:
Expand Down
43 changes: 43 additions & 0 deletions src/backends/sentry_backend_inproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
#ifdef SENTRY_PLATFORM_UNIX
# include <poll.h>
#endif
#ifdef SENTRY_PLATFORM_ANDROID
# include <sys/syscall.h>
#endif
#include <string.h>

/**
Expand Down Expand Up @@ -1563,6 +1566,29 @@ process_ucontext(const sentry_ucontext_t *uctx)
uintptr_t ip = get_instruction_pointer(uctx);
uintptr_t sp = get_stack_pointer(uctx);

# ifdef SENTRY_PLATFORM_ANDROID
// Mask the signal so SA_NODEFER doesn't let re-raises from the chained
// handler kill the process before we regain control.
sigset_t mask, old_mask;
sigemptyset(&mask);
sigaddset(&mask, uctx->signum);
// Raw syscall because ART's libsigchain intercepts
// sigprocmask() and silently drops the request when called
// outside its own special handlers. Without the raw syscall
// the mask change would be ignored and SA_NODEFER would let
// the chained handler's raise() re-deliver the signal
// immediately, crashing the process before we can inspect
// the modified IP/SP.
//
// DANGER: this makes libsigchain's internal mask state
// diverge from the kernel's actual mask. If ART ever relies
// on that state for correctness (e.g. GC safepoints), this
// could cause subtle failures. We restore the mask right
// after the chained handler returns, limiting the window.
syscall(
SYS_rt_sigprocmask, SIG_BLOCK, &mask, &old_mask, sizeof(sigset_t));
# endif

// invoke the previous handler (typically the CLR/Mono
// signal-to-managed-exception handler)
invoke_signal_handler(
Expand All @@ -1578,6 +1604,23 @@ process_ucontext(const sentry_ucontext_t *uctx)
return;
}

# ifdef SENTRY_PLATFORM_ANDROID
// restore our handler
struct sigaction current;
sigaction(uctx->signum, NULL, &current);
if (current.sa_handler == SIG_DFL) {
sigaction(uctx->signum, &g_sigaction, NULL);
}

// consume pending signal
struct timespec timeout = { 0, 0 };
syscall(SYS_rt_sigtimedwait, &mask, NULL, &timeout, sizeof(sigset_t));

// unmask
syscall(
SYS_rt_sigprocmask, SIG_SETMASK, &old_mask, NULL, sizeof(sigset_t));
# endif

// return from runtime handler; continue processing the crash on the
// signal thread until the worker takes over
}
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/dotnet_signal/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<!-- Prevent MSBuild from using parent Directory.Build.props -->
<Project />
31 changes: 31 additions & 0 deletions tests/fixtures/dotnet_signal/Platforms/Android/MainActivity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Android.App;
using Android.OS;

// Required for "adb shell run-as" to access the app's data directory in Release builds
[assembly: Application(Debuggable = true)]

namespace dotnet_signal;

[Activity(Name = "dotnet_signal.MainActivity", MainLauncher = true)]
public class MainActivity : Activity
{
protected override void OnResume()
{
base.OnResume();

var arg = Intent?.GetStringExtra("arg");
if (!string.IsNullOrEmpty(arg))
{
var databasePath = FilesDir?.AbsolutePath + "/.sentry-native";

// Post to the message queue so the activity finishes starting
// before the crash test runs. Without this, "am start -W" may hang.
new Handler(Looper.MainLooper!).Post(() =>
{
Program.RunTest(new[] { arg }, databasePath);
FinishAndRemoveTask();
Java.Lang.JavaSystem.Exit(0);
});
}
}
}
25 changes: 19 additions & 6 deletions tests/fixtures/dotnet_signal/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ class Program
[DllImport("sentry", EntryPoint = "sentry_options_set_debug")]
static extern IntPtr sentry_options_set_debug(IntPtr options, int debug);

[DllImport("sentry", EntryPoint = "sentry_options_set_database_path")]
static extern void sentry_options_set_database_path(IntPtr options, string path);

[DllImport("sentry", EntryPoint = "sentry_init")]
static extern int sentry_init(IntPtr options);

static void Main(string[] args)
public static void RunTest(string[] args, string? databasePath = null)
{
var githubActions = Environment.GetEnvironmentVariable("GITHUB_ACTIONS") ?? string.Empty;
if (githubActions == "true") {
Expand All @@ -38,10 +41,13 @@ static void Main(string[] args)
var options = sentry_options_new();
sentry_options_set_handler_strategy(options, 1);
sentry_options_set_debug(options, 1);
if (databasePath != null)
{
sentry_options_set_database_path(options, databasePath);
}
sentry_init(options);

var doNativeCrash = args is ["native-crash"];
if (doNativeCrash)
if (args.Contains("native-crash"))
{
native_crash();
}
Expand All @@ -51,17 +57,24 @@ static void Main(string[] args)
{
Console.WriteLine("dereference a NULL object from managed code");
var s = default(string);
var c = s.Length;
var c = s!.Length;
}
catch (NullReferenceException exception)
catch (NullReferenceException)
{
}
}
else if (args.Contains("unhandled-managed-exception"))
{
Console.WriteLine("dereference a NULL object from managed code (unhandled)");
var s = default(string);
var c = s.Length;
var c = s!.Length;
}
}

#if !ANDROID
static void Main(string[] args)
{
RunTest(args);
}
#endif
}
17 changes: 16 additions & 1 deletion tests/fixtures/dotnet_signal/test_dotnet.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFrameworks Condition="'$(ANDROID_API)' != ''">$(TargetFrameworks);net10.0-android</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup Condition="$(TargetFramework.Contains('-android'))">
<ApplicationId>io.sentry.ndk.dotnet.signal.test</ApplicationId>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>

<ItemGroup Condition="!$(TargetFramework.Contains('-android'))">
<Compile Remove="Platforms\Android\**" />
</ItemGroup>

<ItemGroup Condition="$(TargetFramework.Contains('-android'))">
<AndroidNativeLibrary Include="native\**\*.so" />
</ItemGroup>
</Project>
4 changes: 2 additions & 2 deletions tests/test_build_static.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
import os
import pytest
from .conditions import has_breakpad, has_crashpad, has_native
from .conditions import has_breakpad, has_crashpad, has_native, is_android


def test_static_lib(cmake):
Expand All @@ -16,7 +16,7 @@ def test_static_lib(cmake):
)

# on linux we can use `ldd` to check that we don’t link to `libsentry.so`
if sys.platform == "linux":
if sys.platform == "linux" and not is_android:
output = subprocess.check_output("ldd sentry_example", cwd=tmp_path, shell=True)
assert b"libsentry.so" not in output

Expand Down
Loading
Loading