-
-
Notifications
You must be signed in to change notification settings - Fork 199
fix: AOT interop with managed .NET runtimes #1392
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
Changes from 20 commits
c1bd96a
45dccd6
02ebf36
81293d8
10ba6bd
d9eafcd
1bf0f52
060b18b
d4dd399
4da4c4a
345ab4d
e8576cf
06c84e1
e92bb00
de6d67c
4a73907
72df376
95c3c9b
b6e37d1
ac6877e
8a5c5b6
2ef7c5b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -456,9 +456,48 @@ registers_from_uctx(const sentry_ucontext_t *uctx) | |
| return registers; | ||
| } | ||
|
|
||
| #ifdef SENTRY_PLATFORM_LINUX | ||
| static uintptr_t | ||
| get_stack_pointer(const sentry_ucontext_t *uctx) | ||
| { | ||
| # if defined(__i386__) | ||
| return uctx->user_context->uc_mcontext.gregs[REG_ESP]; | ||
| # elif defined(__x86_64__) | ||
| return uctx->user_context->uc_mcontext.gregs[REG_RSP]; | ||
| # elif defined(__arm__) | ||
| return uctx->user_context->uc_mcontext.arm_sp; | ||
| # elif defined(__aarch64__) | ||
| return uctx->user_context->uc_mcontext.sp; | ||
| # else | ||
| SENTRY_WARN("get_stack_pointer is not implemented for this architecture. " | ||
| "Signal chaining may not work as expected."); | ||
| return NULL; | ||
| # endif | ||
| } | ||
|
|
||
| static uintptr_t | ||
| get_instruction_pointer(const sentry_ucontext_t *uctx) | ||
| { | ||
| # if defined(__i386__) | ||
| return uctx->user_context->uc_mcontext.gregs[REG_EIP]; | ||
| # elif defined(__x86_64__) | ||
| return uctx->user_context->uc_mcontext.gregs[REG_RIP]; | ||
| # elif defined(__arm__) | ||
| return uctx->user_context->uc_mcontext.arm_pc; | ||
| # elif defined(__aarch64__) | ||
| return uctx->user_context->uc_mcontext.pc; | ||
| # else | ||
| SENTRY_WARN( | ||
| "get_instruction_pointer is not implemented for this architecture. " | ||
| "Signal chaining may not work as expected."); | ||
| return NULL; | ||
| # endif | ||
| } | ||
jpnurmi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| #endif | ||
|
|
||
| static sentry_value_t | ||
| make_signal_event( | ||
| const struct signal_slot *sig_slot, const sentry_ucontext_t *uctx) | ||
| make_signal_event(const struct signal_slot *sig_slot, | ||
| const sentry_ucontext_t *uctx, sentry_handler_strategy_t strategy) | ||
| { | ||
| sentry_value_t event = sentry_value_new_event(); | ||
| sentry_value_set_by_key( | ||
|
|
@@ -494,10 +533,11 @@ make_signal_event( | |
| = sentry_unwind_stack_from_ucontext(uctx, &backtrace[0], MAX_FRAMES); | ||
| SENTRY_DEBUGF( | ||
| "captured backtrace from ucontext with %lu frames", frame_count); | ||
| // if unwinding from a ucontext didn't yield any results, try again with a | ||
| // direct unwind. this is most likely the case when using `libbacktrace`, | ||
| // since that does not allow to unwind from a ucontext at all. | ||
| if (!frame_count) { | ||
| // if unwinding from a ucontext didn't yield any results and we haven't | ||
| // chained signal handlers, try again with a direct unwind. this is most | ||
| // likely the case when using `libbacktrace`, since that does not allow to | ||
| // unwind from a ucontext at all. | ||
| if (!frame_count && strategy != SENTRY_HANDLER_STRATEGY_CHAIN_AT_START) { | ||
jpnurmi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| frame_count = sentry_unwind_stack(NULL, &backtrace[0], MAX_FRAMES); | ||
| } | ||
| SENTRY_DEBUGF("captured backtrace with %lu frames", frame_count); | ||
|
|
@@ -534,20 +574,7 @@ handle_ucontext(const sentry_ucontext_t *uctx) | |
|
|
||
| SENTRY_INFO("entering signal handler"); | ||
|
|
||
| const struct signal_slot *sig_slot = NULL; | ||
| for (int i = 0; i < SIGNAL_COUNT; ++i) { | ||
| #ifdef SENTRY_PLATFORM_UNIX | ||
| if (SIGNAL_DEFINITIONS[i].signum == uctx->signum) { | ||
| #elif defined SENTRY_PLATFORM_WINDOWS | ||
| if (SIGNAL_DEFINITIONS[i].signum | ||
| == uctx->exception_ptrs.ExceptionRecord->ExceptionCode) { | ||
| #else | ||
| # error Unsupported platform | ||
| #endif | ||
| sig_slot = &SIGNAL_DEFINITIONS[i]; | ||
| } | ||
| } | ||
|
|
||
| sentry_handler_strategy_t strategy = SENTRY_HANDLER_STRATEGY_DEFAULT; | ||
| #ifdef SENTRY_PLATFORM_UNIX | ||
| // inform the sentry_sync system that we're in a signal handler. This will | ||
| // make mutexes spin on a spinlock instead as it's no longer safe to use a | ||
|
|
@@ -556,42 +583,77 @@ handle_ucontext(const sentry_ucontext_t *uctx) | |
| #endif | ||
|
|
||
| SENTRY_WITH_OPTIONS (options) { | ||
| // Flush logs in a crash-safe manner before crash handling | ||
| if (options->enable_logs) { | ||
| sentry__logs_flush_crash_safe(); | ||
| } | ||
| #ifdef SENTRY_PLATFORM_LINUX | ||
| // On Linux (and thus Android) CLR/Mono converts signals provoked by | ||
| // AOT/JIT-generated native code into managed code exceptions. In these | ||
| // cases, we shouldn't react to the signal at all and let their handler | ||
| // discontinue the signal chain by invoking the runtime handler before | ||
| // we process the signal. | ||
| if (sentry_options_get_handler_strategy(options) | ||
| == SENTRY_HANDLER_STRATEGY_CHAIN_AT_START) { | ||
| strategy = sentry_options_get_handler_strategy(options); | ||
| if (strategy == SENTRY_HANDLER_STRATEGY_CHAIN_AT_START) { | ||
| SENTRY_DEBUG("defer to runtime signal handler at start"); | ||
| // there is a good chance that we won't return from the previous | ||
| // handler and that would mean we couldn't enter this handler with | ||
| // the next signal coming in if we didn't "leave" here. | ||
| sentry__leave_signal_handler(); | ||
| if (!options->enable_logging_when_crashed) { | ||
| sentry__logger_enable(); | ||
| } | ||
|
|
||
| uintptr_t ip = get_instruction_pointer(uctx); | ||
| uintptr_t sp = get_stack_pointer(uctx); | ||
|
|
||
| // invoke the previous handler (typically the CLR/Mono | ||
| // signal-to-managed-exception handler) | ||
| invoke_signal_handler( | ||
| uctx->signum, uctx->siginfo, (void *)uctx->user_context); | ||
|
|
||
| // If the execution returns here in AOT mode, and the instruction | ||
| // or stack pointer were changed, it means CLR/Mono converted the | ||
| // signal into a managed exception and transferred execution to a | ||
| // managed exception handler. | ||
| // https://github.com/dotnet/runtime/blob/6d96e28597e7da0d790d495ba834cc4908e442cd/src/mono/mono/mini/exceptions-arm64.c#L538 | ||
| if (ip != get_instruction_pointer(uctx) | ||
| || sp != get_stack_pointer(uctx)) { | ||
| SENTRY_DEBUG("runtime converted the signal to a managed " | ||
| "exception, we do not handle the signal"); | ||
| return; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Signal Chaining Logic Fails on Some ArchitecturesThe new signal chaining logic has two issues: on architectures where instruction and stack pointers aren't retrieved, it incorrectly proceeds with crash handling even after a runtime converts a signal. Additionally, when a signal conversion is detected and the function returns early, the internal signal handler state becomes unbalanced. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is absolutely correct, but the only side-effect currently visible is for the logging toggle. Similarly, to how we "leave" the signal handler before chaining, we must also re-enable logging immediately after "leaving" and disable it again before re-entering, because if it were a managed code exception, we want logging to remain enabled. We can also move the entire However, I think both have a lower priority than figuring out the signaling sequence of both runtimes and how we can align them. |
||
|
|
||
| // let's re-enter because it means this was an actual native crash | ||
| if (!options->enable_logging_when_crashed) { | ||
| sentry__logger_disable(); | ||
| } | ||
| sentry__enter_signal_handler(); | ||
| SENTRY_DEBUG( | ||
| "return from runtime signal handler, we handle the signal"); | ||
| } | ||
| #endif | ||
|
|
||
| const struct signal_slot *sig_slot = NULL; | ||
| for (int i = 0; i < SIGNAL_COUNT; ++i) { | ||
| #ifdef SENTRY_PLATFORM_UNIX | ||
| if (SIGNAL_DEFINITIONS[i].signum == uctx->signum) { | ||
| #elif defined SENTRY_PLATFORM_WINDOWS | ||
| if (SIGNAL_DEFINITIONS[i].signum | ||
| == uctx->exception_ptrs.ExceptionRecord->ExceptionCode) { | ||
| #else | ||
| # error Unsupported platform | ||
| #endif | ||
| sig_slot = &SIGNAL_DEFINITIONS[i]; | ||
| } | ||
| } | ||
|
|
||
| #ifdef SENTRY_PLATFORM_UNIX | ||
| // use a signal-safe allocator before we tear down. | ||
| sentry__page_allocator_enable(); | ||
| #endif | ||
| // Flush logs in a crash-safe manner before crash handling | ||
| if (options->enable_logs) { | ||
| sentry__logs_flush_crash_safe(); | ||
| } | ||
|
|
||
| sentry_value_t event = make_signal_event(sig_slot, uctx); | ||
| sentry_value_t event = make_signal_event(sig_slot, uctx, strategy); | ||
| bool should_handle = true; | ||
| sentry__write_crash_marker(options); | ||
|
|
||
|
|
@@ -647,8 +709,10 @@ handle_ucontext(const sentry_ucontext_t *uctx) | |
| // forward as we're not restoring the page allocator. | ||
| reset_signal_handlers(); | ||
| sentry__leave_signal_handler(); | ||
| invoke_signal_handler( | ||
| uctx->signum, uctx->siginfo, (void *)uctx->user_context); | ||
| if (strategy != SENTRY_HANDLER_STRATEGY_CHAIN_AT_START) { | ||
jpnurmi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| invoke_signal_handler( | ||
| uctx->signum, uctx->siginfo, (void *)uctx->user_context); | ||
| } | ||
| #endif | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.