From a64c1e740f1e545c67583d4f537a4c0c3016f0b8 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 22 Jun 2026 21:38:21 +0200 Subject: [PATCH 1/7] [mono] Keep in-flight exception alive across LLVM resume unwind When unwinding an exception through an LLVM-compiled finally/fault handler in full-AOT mode, mono_handle_exception_internal stored the in-flight exception object in ResumeState.ex_obj as a raw pointer (marked /* FIXME: GC */) before transferring control to the managed handler. That handler (e.g. Monitor.Exit emitted by a synchronized method wrapper) can reach a GC safepoint, and a moving GC then relocates the exception object, leaving the stored pointer stale. mono_resume_unwind later passed the stale pointer to mono_object_isinst_checked while searching for a matching catch clause, dereferencing a garbage MonoClass and crashing intermittently with a SIGSEGV under load. Store the exception in a pinned GC handle (ResumeState.ex_gchandle) instead, mirroring the existing llvmonly catch path, and read it back in mono_resume_unwind. The now-unused raw ex_obj field is removed. Re-enables the JIT/Regression/CLR-x86-JIT/V1.1-M1-Beta1/b143840 test on Mono. --- src/mono/mono/mini/mini-exceptions.c | 16 ++++++++++++++-- src/mono/mono/mini/mini-runtime.h | 2 -- .../CLR-x86-JIT/V1.1-M1-Beta1/b143840/b143840.il | 4 ---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/mono/mono/mini/mini-exceptions.c b/src/mono/mono/mini/mini-exceptions.c index c795fd2adc5d55..ff19745ee323cf 100644 --- a/src/mono/mono/mini/mini-exceptions.c +++ b/src/mono/mono/mini/mini-exceptions.c @@ -2560,7 +2560,15 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu * mono_resume_unwind () will call us again to continue * the unwinding. */ - jit_tls->resume_state.ex_obj = obj; + /* + * Keep the exception object alive across the (managed) finally + * handler using a pinned GC handle. The handler can reach a GC + * safepoint, and a moving GC would otherwise invalidate a raw + * pointer stored here (see mono_resume_unwind ()). + */ + if (jit_tls->resume_state.ex_gchandle) + mono_gchandle_free_internal (jit_tls->resume_state.ex_gchandle); + jit_tls->resume_state.ex_gchandle = mono_gchandle_new_internal (obj, TRUE); jit_tls->resume_state.ji = ji; jit_tls->resume_state.clause_index = i + 1; jit_tls->resume_state.ctx = *ctx; @@ -3095,7 +3103,11 @@ mono_resume_unwind (MonoContext *ctx) MONO_CONTEXT_SET_SP (ctx, MONO_CONTEXT_GET_SP (&jit_tls->resume_state.ctx)); new_ctx = *ctx; - mono_handle_exception_internal (&new_ctx, (MonoObject *)jit_tls->resume_state.ex_obj, TRUE, NULL); + MonoObject *ex_obj = mono_gchandle_get_target_internal (jit_tls->resume_state.ex_gchandle); + mono_gchandle_free_internal (jit_tls->resume_state.ex_gchandle); + jit_tls->resume_state.ex_gchandle = NULL; + + mono_handle_exception_internal (&new_ctx, ex_obj, TRUE, NULL); mono_restore_context (&new_ctx); } diff --git a/src/mono/mono/mini/mini-runtime.h b/src/mono/mono/mini/mini-runtime.h index 9ca4d1dbf59aa2..487a5295648ccd 100644 --- a/src/mono/mono/mini/mini-runtime.h +++ b/src/mono/mono/mini/mini-runtime.h @@ -131,8 +131,6 @@ typedef struct { MonoJitInfo *ji; int clause_index; MonoContext ctx, new_ctx; - /* FIXME: GC */ - gpointer ex_obj; MonoLMF *lmf; int first_filter_idx, filter_idx; /* MonoMethodILState */ diff --git a/src/tests/JIT/Regression/CLR-x86-JIT/V1.1-M1-Beta1/b143840/b143840.il b/src/tests/JIT/Regression/CLR-x86-JIT/V1.1-M1-Beta1/b143840/b143840.il index 2565bcc03b270e..b1d74f9b87c0ea 100644 --- a/src/tests/JIT/Regression/CLR-x86-JIT/V1.1-M1-Beta1/b143840/b143840.il +++ b/src/tests/JIT/Regression/CLR-x86-JIT/V1.1-M1-Beta1/b143840/b143840.il @@ -45,10 +45,6 @@ .custom instance void [xunit.core]Xunit.FactAttribute::.ctor() = ( 01 00 00 00 ) - .custom instance void [Microsoft.DotNet.XUnitExtensions]Xunit.ActiveIssueAttribute::.ctor(string, valuetype [Microsoft.DotNet.XUnitExtensions]Xunit.TestRuntimes) = { - string('https://github.com/dotnet/runtime/issues/129508') - int32(0x2) // Mono - } .entrypoint .maxstack 3 .locals init (class [System.Threading.Thread]System.Threading.Thread V_0, From 52859abec7140f86dddbfdd599044a5a6cfc3729 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 23 Jun 2026 10:37:53 +0200 Subject: [PATCH 2/7] [mono] Make AOTed-catch ex_gchandle assignment defensive; trigger runtime-llvm The IL-state (AOTed) catch path asserted resume_state.ex_gchandle was NULL before setting it. Now that the finally-resume path shares the same field (this PR replaced the raw ex_obj with a pinned handle), a finally handler that throws a superseding exception caught in AOTed code could reach this path with a stale handle still set, firing the assert and leaking the handle. Free any existing handle instead, mirroring the finally path. Also add arch-backend/exception/runtime mini paths to the runtime-llvm PR trigger so this PR exercises the LLVMFULLAOT leg. --- eng/pipelines/runtime-llvm.yml | 7 +++++++ src/mono/mono/mini/mini-exceptions.c | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/eng/pipelines/runtime-llvm.yml b/eng/pipelines/runtime-llvm.yml index 18e1ac41e41d19..afee6893f31232 100644 --- a/eng/pipelines/runtime-llvm.yml +++ b/eng/pipelines/runtime-llvm.yml @@ -45,6 +45,13 @@ pr: - src/mono/mono/mini/decompose.c - src/mono/mono/mini/method-to-ir.c - src/mono/mono/mini/mini.c + - src/mono/mono/mini/mini-amd64.* + - src/mono/mono/mini/mini-arm64.* + - src/mono/mono/mini/mini-arm.* + - src/mono/mono/mini/mini-riscv.* + - src/mono/mono/mini/mini-exceptions.* + - src/mono/mono/mini/exceptions-*.* + - src/mono/mono/mini/mini-runtime.* variables: - template: /eng/pipelines/common/variables.yml diff --git a/src/mono/mono/mini/mini-exceptions.c b/src/mono/mono/mini/mini-exceptions.c index ff19745ee323cf..ab4c2431a92203 100644 --- a/src/mono/mono/mini/mini-exceptions.c +++ b/src/mono/mono/mini/mini-exceptions.c @@ -2498,7 +2498,15 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu * methods, then execute the clause and the rest of the method * using the interpreter. */ - g_assert (!jit_tls->resume_state.ex_gchandle); + /* + * resume_state.ex_gchandle is shared with the finally-resume path + * (mono_handle_exception_internal above). If a finally handler threw a + * superseding exception that is now being caught here, that path left a + * pinned handle behind that will never be resumed; free it instead of + * asserting, mirroring the finally path. + */ + if (jit_tls->resume_state.ex_gchandle) + mono_gchandle_free_internal (jit_tls->resume_state.ex_gchandle); jit_tls->resume_state.ex_gchandle = mono_gchandle_new_internal ((MonoObject*)obj, TRUE); jit_tls->resume_state.ji = ji; jit_tls->resume_state.clause_index = i; From 24c4400530b8afb9819311eb4abb19730f8d6205 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 23 Jun 2026 11:41:25 +0200 Subject: [PATCH 3/7] [mono] Regenerate wasm/wasi AOT offsets for ResumeState layout change Removing ResumeState.ex_obj shrinks MonoJitTlsData by one pointer (216->212 on wasm32) and shifts the fields after resume_state. Regenerates wasm32-unknown-none.h and wasm32-unknown-wasip2.h so the browser-wasm and wasi-wasm MonoAOTOffsets legs pass. --- src/mono/mono/offsets/wasm32-unknown-none.h | 22 +++++++++---------- src/mono/mono/offsets/wasm32-unknown-wasip2.h | 22 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/mono/mono/offsets/wasm32-unknown-none.h b/src/mono/mono/offsets/wasm32-unknown-none.h index 1f3710d5a3cb58..9ea2c56162bb8d 100644 --- a/src/mono/mono/offsets/wasm32-unknown-none.h +++ b/src/mono/mono/offsets/wasm32-unknown-none.h @@ -239,7 +239,7 @@ DECL_OFFSET2(MonoMethodRuntimeGenericContext,method,4) DECL_OFFSET2(MonoMethodRuntimeGenericContext,method_inst,8) DECL_OFFSET2(MonoMethodRuntimeGenericContext,entries,12) DECL_OFFSET2(MonoMethodRuntimeGenericContext,infos,16) -DECL_SIZE2(MonoJitTlsData,216) +DECL_SIZE2(MonoJitTlsData,212) DECL_OFFSET2(MonoJitTlsData,end_of_stack,0) DECL_OFFSET2(MonoJitTlsData,stack_size,4) DECL_OFFSET2(MonoJitTlsData,lmf,8) @@ -253,16 +253,16 @@ DECL_OFFSET2(MonoJitTlsData,class_cast_from,44) DECL_OFFSET2(MonoJitTlsData,class_cast_to,48) DECL_OFFSET2(MonoJitTlsData,ex_ctx,52) DECL_OFFSET2(MonoJitTlsData,resume_state,72) -DECL_OFFSET2(MonoJitTlsData,handler_block,144) -DECL_OFFSET2(MonoJitTlsData,handler_block_context,148) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,168) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,188) -DECL_OFFSET2(MonoJitTlsData,thrown_exc,192) -DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,196) -DECL_OFFSET2(MonoJitTlsData,calling_image,200) -DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,204) -DECL_OFFSET2(MonoJitTlsData,active_jit_methods,208) -DECL_OFFSET2(MonoJitTlsData,interp_context,212) +DECL_OFFSET2(MonoJitTlsData,handler_block,140) +DECL_OFFSET2(MonoJitTlsData,handler_block_context,144) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,164) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,184) +DECL_OFFSET2(MonoJitTlsData,thrown_exc,188) +DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,192) +DECL_OFFSET2(MonoJitTlsData,calling_image,196) +DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,200) +DECL_OFFSET2(MonoJitTlsData,active_jit_methods,204) +DECL_OFFSET2(MonoJitTlsData,interp_context,208) DECL_SIZE2(MonoGSharedVtMethodRuntimeInfo,4) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,locals_size,0) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,entries,4) diff --git a/src/mono/mono/offsets/wasm32-unknown-wasip2.h b/src/mono/mono/offsets/wasm32-unknown-wasip2.h index ba5cba52e72f58..101023d9b7b60c 100644 --- a/src/mono/mono/offsets/wasm32-unknown-wasip2.h +++ b/src/mono/mono/offsets/wasm32-unknown-wasip2.h @@ -239,7 +239,7 @@ DECL_OFFSET2(MonoMethodRuntimeGenericContext,method,4) DECL_OFFSET2(MonoMethodRuntimeGenericContext,method_inst,8) DECL_OFFSET2(MonoMethodRuntimeGenericContext,entries,12) DECL_OFFSET2(MonoMethodRuntimeGenericContext,infos,16) -DECL_SIZE2(MonoJitTlsData,216) +DECL_SIZE2(MonoJitTlsData,212) DECL_OFFSET2(MonoJitTlsData,end_of_stack,0) DECL_OFFSET2(MonoJitTlsData,stack_size,4) DECL_OFFSET2(MonoJitTlsData,lmf,8) @@ -253,16 +253,16 @@ DECL_OFFSET2(MonoJitTlsData,class_cast_from,44) DECL_OFFSET2(MonoJitTlsData,class_cast_to,48) DECL_OFFSET2(MonoJitTlsData,ex_ctx,52) DECL_OFFSET2(MonoJitTlsData,resume_state,72) -DECL_OFFSET2(MonoJitTlsData,handler_block,144) -DECL_OFFSET2(MonoJitTlsData,handler_block_context,148) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,168) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,188) -DECL_OFFSET2(MonoJitTlsData,thrown_exc,192) -DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,196) -DECL_OFFSET2(MonoJitTlsData,calling_image,200) -DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,204) -DECL_OFFSET2(MonoJitTlsData,active_jit_methods,208) -DECL_OFFSET2(MonoJitTlsData,interp_context,212) +DECL_OFFSET2(MonoJitTlsData,handler_block,140) +DECL_OFFSET2(MonoJitTlsData,handler_block_context,144) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,164) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,184) +DECL_OFFSET2(MonoJitTlsData,thrown_exc,188) +DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,192) +DECL_OFFSET2(MonoJitTlsData,calling_image,196) +DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,200) +DECL_OFFSET2(MonoJitTlsData,active_jit_methods,204) +DECL_OFFSET2(MonoJitTlsData,interp_context,208) DECL_SIZE2(MonoGSharedVtMethodRuntimeInfo,4) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,locals_size,0) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,entries,4) From 306d3977b9ae655e29c0f0716eeb6a3b343aace6 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 23 Jun 2026 12:10:57 +0200 Subject: [PATCH 4/7] [mono] Update Android/Apple AOT offsets for ResumeState layout change Applied the same mechanical shift the build produced for wasm32: removing ResumeState.ex_obj shrinks MonoJitTlsData by one pointer, so DECL_SIZE2(MonoJitTlsData) and every field after resume_state are reduced by the target pointer size (8 for aarch64/x86_64, 4 for armv7/i686). These targets' offsets cannot be regenerated on this host (need the Android NDK / macOS), so they were applied manually; the mobile/apple MonoAOTOffsets CI legs validate them. --- .../mono/offsets/aarch64-apple-darwin10.h | 22 +++++++++---------- .../mono/offsets/aarch64-apple-maccatalyst.h | 22 +++++++++---------- .../mono/offsets/aarch64-v8a-linux-android.h | 22 +++++++++---------- .../offsets/armv7-none-linux-androideabi.h | 22 +++++++++---------- .../mono/offsets/i686-none-linux-android.h | 22 +++++++++---------- src/mono/mono/offsets/x86_64-apple-darwin10.h | 22 +++++++++---------- .../mono/offsets/x86_64-apple-maccatalyst.h | 22 +++++++++---------- .../mono/offsets/x86_64-none-linux-android.h | 22 +++++++++---------- 8 files changed, 88 insertions(+), 88 deletions(-) diff --git a/src/mono/mono/offsets/aarch64-apple-darwin10.h b/src/mono/mono/offsets/aarch64-apple-darwin10.h index be48c3a4df13c9..b84e2149318a8b 100644 --- a/src/mono/mono/offsets/aarch64-apple-darwin10.h +++ b/src/mono/mono/offsets/aarch64-apple-darwin10.h @@ -240,7 +240,7 @@ DECL_OFFSET2(MonoMethodRuntimeGenericContext,method,8) DECL_OFFSET2(MonoMethodRuntimeGenericContext,method_inst,16) DECL_OFFSET2(MonoMethodRuntimeGenericContext,entries,24) DECL_OFFSET2(MonoMethodRuntimeGenericContext,infos,32) -DECL_SIZE2(MonoJitTlsData,4160) +DECL_SIZE2(MonoJitTlsData,4152) DECL_OFFSET2(MonoJitTlsData,end_of_stack,0) DECL_OFFSET2(MonoJitTlsData,stack_size,8) DECL_OFFSET2(MonoJitTlsData,lmf,16) @@ -254,16 +254,16 @@ DECL_OFFSET2(MonoJitTlsData,class_cast_from,80) DECL_OFFSET2(MonoJitTlsData,class_cast_to,88) DECL_OFFSET2(MonoJitTlsData,ex_ctx,96) DECL_OFFSET2(MonoJitTlsData,resume_state,880) -DECL_OFFSET2(MonoJitTlsData,handler_block,2512) -DECL_OFFSET2(MonoJitTlsData,handler_block_context,2528) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,3312) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,4096) -DECL_OFFSET2(MonoJitTlsData,thrown_exc,4104) -DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,4112) -DECL_OFFSET2(MonoJitTlsData,calling_image,4120) -DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,4128) -DECL_OFFSET2(MonoJitTlsData,active_jit_methods,4136) -DECL_OFFSET2(MonoJitTlsData,interp_context,4144) +DECL_OFFSET2(MonoJitTlsData,handler_block,2504) +DECL_OFFSET2(MonoJitTlsData,handler_block_context,2520) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,3304) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,4088) +DECL_OFFSET2(MonoJitTlsData,thrown_exc,4096) +DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,4104) +DECL_OFFSET2(MonoJitTlsData,calling_image,4112) +DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,4120) +DECL_OFFSET2(MonoJitTlsData,active_jit_methods,4128) +DECL_OFFSET2(MonoJitTlsData,interp_context,4136) DECL_SIZE2(MonoGSharedVtMethodRuntimeInfo,8) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,locals_size,0) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,entries,8) diff --git a/src/mono/mono/offsets/aarch64-apple-maccatalyst.h b/src/mono/mono/offsets/aarch64-apple-maccatalyst.h index 287077fa12f0df..941b4879df71da 100644 --- a/src/mono/mono/offsets/aarch64-apple-maccatalyst.h +++ b/src/mono/mono/offsets/aarch64-apple-maccatalyst.h @@ -240,7 +240,7 @@ DECL_OFFSET2(MonoMethodRuntimeGenericContext,method,8) DECL_OFFSET2(MonoMethodRuntimeGenericContext,method_inst,16) DECL_OFFSET2(MonoMethodRuntimeGenericContext,entries,24) DECL_OFFSET2(MonoMethodRuntimeGenericContext,infos,32) -DECL_SIZE2(MonoJitTlsData,4160) +DECL_SIZE2(MonoJitTlsData,4152) DECL_OFFSET2(MonoJitTlsData,end_of_stack,0) DECL_OFFSET2(MonoJitTlsData,stack_size,8) DECL_OFFSET2(MonoJitTlsData,lmf,16) @@ -254,16 +254,16 @@ DECL_OFFSET2(MonoJitTlsData,class_cast_from,80) DECL_OFFSET2(MonoJitTlsData,class_cast_to,88) DECL_OFFSET2(MonoJitTlsData,ex_ctx,96) DECL_OFFSET2(MonoJitTlsData,resume_state,880) -DECL_OFFSET2(MonoJitTlsData,handler_block,2512) -DECL_OFFSET2(MonoJitTlsData,handler_block_context,2528) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,3312) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,4096) -DECL_OFFSET2(MonoJitTlsData,thrown_exc,4104) -DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,4112) -DECL_OFFSET2(MonoJitTlsData,calling_image,4120) -DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,4128) -DECL_OFFSET2(MonoJitTlsData,active_jit_methods,4136) -DECL_OFFSET2(MonoJitTlsData,interp_context,4144) +DECL_OFFSET2(MonoJitTlsData,handler_block,2504) +DECL_OFFSET2(MonoJitTlsData,handler_block_context,2520) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,3304) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,4088) +DECL_OFFSET2(MonoJitTlsData,thrown_exc,4096) +DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,4104) +DECL_OFFSET2(MonoJitTlsData,calling_image,4112) +DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,4120) +DECL_OFFSET2(MonoJitTlsData,active_jit_methods,4128) +DECL_OFFSET2(MonoJitTlsData,interp_context,4136) DECL_SIZE2(MonoGSharedVtMethodRuntimeInfo,8) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,locals_size,0) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,entries,8) diff --git a/src/mono/mono/offsets/aarch64-v8a-linux-android.h b/src/mono/mono/offsets/aarch64-v8a-linux-android.h index 11f5553434c891..581c061e243d9e 100644 --- a/src/mono/mono/offsets/aarch64-v8a-linux-android.h +++ b/src/mono/mono/offsets/aarch64-v8a-linux-android.h @@ -240,7 +240,7 @@ DECL_OFFSET2(MonoMethodRuntimeGenericContext,method,8) DECL_OFFSET2(MonoMethodRuntimeGenericContext,method_inst,16) DECL_OFFSET2(MonoMethodRuntimeGenericContext,entries,24) DECL_OFFSET2(MonoMethodRuntimeGenericContext,infos,32) -DECL_SIZE2(MonoJitTlsData,4160) +DECL_SIZE2(MonoJitTlsData,4152) DECL_OFFSET2(MonoJitTlsData,end_of_stack,0) DECL_OFFSET2(MonoJitTlsData,stack_size,8) DECL_OFFSET2(MonoJitTlsData,lmf,16) @@ -254,16 +254,16 @@ DECL_OFFSET2(MonoJitTlsData,class_cast_from,80) DECL_OFFSET2(MonoJitTlsData,class_cast_to,88) DECL_OFFSET2(MonoJitTlsData,ex_ctx,96) DECL_OFFSET2(MonoJitTlsData,resume_state,880) -DECL_OFFSET2(MonoJitTlsData,handler_block,2512) -DECL_OFFSET2(MonoJitTlsData,handler_block_context,2528) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,3312) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,4096) -DECL_OFFSET2(MonoJitTlsData,thrown_exc,4104) -DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,4112) -DECL_OFFSET2(MonoJitTlsData,calling_image,4120) -DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,4128) -DECL_OFFSET2(MonoJitTlsData,active_jit_methods,4136) -DECL_OFFSET2(MonoJitTlsData,interp_context,4144) +DECL_OFFSET2(MonoJitTlsData,handler_block,2504) +DECL_OFFSET2(MonoJitTlsData,handler_block_context,2520) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,3304) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,4088) +DECL_OFFSET2(MonoJitTlsData,thrown_exc,4096) +DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,4104) +DECL_OFFSET2(MonoJitTlsData,calling_image,4112) +DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,4120) +DECL_OFFSET2(MonoJitTlsData,active_jit_methods,4128) +DECL_OFFSET2(MonoJitTlsData,interp_context,4136) DECL_SIZE2(MonoGSharedVtMethodRuntimeInfo,8) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,locals_size,0) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,entries,8) diff --git a/src/mono/mono/offsets/armv7-none-linux-androideabi.h b/src/mono/mono/offsets/armv7-none-linux-androideabi.h index 54b7496e94885e..71c27cd5e65c2f 100644 --- a/src/mono/mono/offsets/armv7-none-linux-androideabi.h +++ b/src/mono/mono/offsets/armv7-none-linux-androideabi.h @@ -244,7 +244,7 @@ DECL_OFFSET2(MonoMethodRuntimeGenericContext,method,4) DECL_OFFSET2(MonoMethodRuntimeGenericContext,method_inst,8) DECL_OFFSET2(MonoMethodRuntimeGenericContext,entries,12) DECL_OFFSET2(MonoMethodRuntimeGenericContext,infos,16) -DECL_SIZE2(MonoJitTlsData,1168) +DECL_SIZE2(MonoJitTlsData,1164) DECL_OFFSET2(MonoJitTlsData,end_of_stack,0) DECL_OFFSET2(MonoJitTlsData,stack_size,4) DECL_OFFSET2(MonoJitTlsData,lmf,8) @@ -258,16 +258,16 @@ DECL_OFFSET2(MonoJitTlsData,class_cast_from,44) DECL_OFFSET2(MonoJitTlsData,class_cast_to,48) DECL_OFFSET2(MonoJitTlsData,ex_ctx,56) DECL_OFFSET2(MonoJitTlsData,resume_state,264) -DECL_OFFSET2(MonoJitTlsData,handler_block,712) -DECL_OFFSET2(MonoJitTlsData,handler_block_context,720) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,928) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,1136) -DECL_OFFSET2(MonoJitTlsData,thrown_exc,1140) -DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,1144) -DECL_OFFSET2(MonoJitTlsData,calling_image,1148) -DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,1152) -DECL_OFFSET2(MonoJitTlsData,active_jit_methods,1156) -DECL_OFFSET2(MonoJitTlsData,interp_context,1160) +DECL_OFFSET2(MonoJitTlsData,handler_block,708) +DECL_OFFSET2(MonoJitTlsData,handler_block_context,716) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,924) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,1132) +DECL_OFFSET2(MonoJitTlsData,thrown_exc,1136) +DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,1140) +DECL_OFFSET2(MonoJitTlsData,calling_image,1144) +DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,1148) +DECL_OFFSET2(MonoJitTlsData,active_jit_methods,1152) +DECL_OFFSET2(MonoJitTlsData,interp_context,1156) DECL_SIZE2(MonoGSharedVtMethodRuntimeInfo,4) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,locals_size,0) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,entries,4) diff --git a/src/mono/mono/offsets/i686-none-linux-android.h b/src/mono/mono/offsets/i686-none-linux-android.h index cc36503f7561f3..d991571cc0812d 100644 --- a/src/mono/mono/offsets/i686-none-linux-android.h +++ b/src/mono/mono/offsets/i686-none-linux-android.h @@ -245,7 +245,7 @@ DECL_OFFSET2(MonoMethodRuntimeGenericContext,method,4) DECL_OFFSET2(MonoMethodRuntimeGenericContext,method_inst,8) DECL_OFFSET2(MonoMethodRuntimeGenericContext,entries,12) DECL_OFFSET2(MonoMethodRuntimeGenericContext,infos,16) -DECL_SIZE2(MonoJitTlsData,296) +DECL_SIZE2(MonoJitTlsData,292) DECL_OFFSET2(MonoJitTlsData,end_of_stack,0) DECL_OFFSET2(MonoJitTlsData,stack_size,4) DECL_OFFSET2(MonoJitTlsData,lmf,8) @@ -259,16 +259,16 @@ DECL_OFFSET2(MonoJitTlsData,class_cast_from,44) DECL_OFFSET2(MonoJitTlsData,class_cast_to,48) DECL_OFFSET2(MonoJitTlsData,ex_ctx,52) DECL_OFFSET2(MonoJitTlsData,resume_state,88) -DECL_OFFSET2(MonoJitTlsData,handler_block,192) -DECL_OFFSET2(MonoJitTlsData,handler_block_context,196) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,232) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,268) -DECL_OFFSET2(MonoJitTlsData,thrown_exc,272) -DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,276) -DECL_OFFSET2(MonoJitTlsData,calling_image,280) -DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,284) -DECL_OFFSET2(MonoJitTlsData,active_jit_methods,288) -DECL_OFFSET2(MonoJitTlsData,interp_context,292) +DECL_OFFSET2(MonoJitTlsData,handler_block,188) +DECL_OFFSET2(MonoJitTlsData,handler_block_context,192) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,228) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,264) +DECL_OFFSET2(MonoJitTlsData,thrown_exc,268) +DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,272) +DECL_OFFSET2(MonoJitTlsData,calling_image,276) +DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,280) +DECL_OFFSET2(MonoJitTlsData,active_jit_methods,284) +DECL_OFFSET2(MonoJitTlsData,interp_context,288) DECL_SIZE2(MonoGSharedVtMethodRuntimeInfo,4) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,locals_size,0) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,entries,4) diff --git a/src/mono/mono/offsets/x86_64-apple-darwin10.h b/src/mono/mono/offsets/x86_64-apple-darwin10.h index 7739eddddb44cf..3fa58d86031985 100644 --- a/src/mono/mono/offsets/x86_64-apple-darwin10.h +++ b/src/mono/mono/offsets/x86_64-apple-darwin10.h @@ -238,7 +238,7 @@ DECL_OFFSET2(MonoMethodRuntimeGenericContext,method,8) DECL_OFFSET2(MonoMethodRuntimeGenericContext,method_inst,16) DECL_OFFSET2(MonoMethodRuntimeGenericContext,entries,24) DECL_OFFSET2(MonoMethodRuntimeGenericContext,infos,32) -DECL_SIZE2(MonoJitTlsData,2240) +DECL_SIZE2(MonoJitTlsData,2232) DECL_OFFSET2(MonoJitTlsData,end_of_stack,0) DECL_OFFSET2(MonoJitTlsData,stack_size,8) DECL_OFFSET2(MonoJitTlsData,lmf,16) @@ -252,16 +252,16 @@ DECL_OFFSET2(MonoJitTlsData,class_cast_from,80) DECL_OFFSET2(MonoJitTlsData,class_cast_to,88) DECL_OFFSET2(MonoJitTlsData,ex_ctx,96) DECL_OFFSET2(MonoJitTlsData,resume_state,496) -DECL_OFFSET2(MonoJitTlsData,handler_block,1360) -DECL_OFFSET2(MonoJitTlsData,handler_block_context,1376) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,1776) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,2176) -DECL_OFFSET2(MonoJitTlsData,thrown_exc,2184) -DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,2192) -DECL_OFFSET2(MonoJitTlsData,calling_image,2200) -DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,2208) -DECL_OFFSET2(MonoJitTlsData,active_jit_methods,2216) -DECL_OFFSET2(MonoJitTlsData,interp_context,2224) +DECL_OFFSET2(MonoJitTlsData,handler_block,1352) +DECL_OFFSET2(MonoJitTlsData,handler_block_context,1368) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,1768) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,2168) +DECL_OFFSET2(MonoJitTlsData,thrown_exc,2176) +DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,2184) +DECL_OFFSET2(MonoJitTlsData,calling_image,2192) +DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,2200) +DECL_OFFSET2(MonoJitTlsData,active_jit_methods,2208) +DECL_OFFSET2(MonoJitTlsData,interp_context,2216) DECL_SIZE2(MonoGSharedVtMethodRuntimeInfo,8) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,locals_size,0) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,entries,8) diff --git a/src/mono/mono/offsets/x86_64-apple-maccatalyst.h b/src/mono/mono/offsets/x86_64-apple-maccatalyst.h index e830a1f6a5b95f..f2ae8d80a0cb75 100644 --- a/src/mono/mono/offsets/x86_64-apple-maccatalyst.h +++ b/src/mono/mono/offsets/x86_64-apple-maccatalyst.h @@ -239,7 +239,7 @@ DECL_OFFSET2(MonoMethodRuntimeGenericContext,method,8) DECL_OFFSET2(MonoMethodRuntimeGenericContext,method_inst,16) DECL_OFFSET2(MonoMethodRuntimeGenericContext,entries,24) DECL_OFFSET2(MonoMethodRuntimeGenericContext,infos,32) -DECL_SIZE2(MonoJitTlsData,2240) +DECL_SIZE2(MonoJitTlsData,2232) DECL_OFFSET2(MonoJitTlsData,end_of_stack,0) DECL_OFFSET2(MonoJitTlsData,stack_size,8) DECL_OFFSET2(MonoJitTlsData,lmf,16) @@ -253,16 +253,16 @@ DECL_OFFSET2(MonoJitTlsData,class_cast_from,80) DECL_OFFSET2(MonoJitTlsData,class_cast_to,88) DECL_OFFSET2(MonoJitTlsData,ex_ctx,96) DECL_OFFSET2(MonoJitTlsData,resume_state,496) -DECL_OFFSET2(MonoJitTlsData,handler_block,1360) -DECL_OFFSET2(MonoJitTlsData,handler_block_context,1376) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,1776) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,2176) -DECL_OFFSET2(MonoJitTlsData,thrown_exc,2184) -DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,2192) -DECL_OFFSET2(MonoJitTlsData,calling_image,2200) -DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,2208) -DECL_OFFSET2(MonoJitTlsData,active_jit_methods,2216) -DECL_OFFSET2(MonoJitTlsData,interp_context,2224) +DECL_OFFSET2(MonoJitTlsData,handler_block,1352) +DECL_OFFSET2(MonoJitTlsData,handler_block_context,1368) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,1768) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,2168) +DECL_OFFSET2(MonoJitTlsData,thrown_exc,2176) +DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,2184) +DECL_OFFSET2(MonoJitTlsData,calling_image,2192) +DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,2200) +DECL_OFFSET2(MonoJitTlsData,active_jit_methods,2208) +DECL_OFFSET2(MonoJitTlsData,interp_context,2216) DECL_SIZE2(MonoGSharedVtMethodRuntimeInfo,8) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,locals_size,0) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,entries,8) diff --git a/src/mono/mono/offsets/x86_64-none-linux-android.h b/src/mono/mono/offsets/x86_64-none-linux-android.h index 6801adfeb9f673..adba79eb10b1e0 100644 --- a/src/mono/mono/offsets/x86_64-none-linux-android.h +++ b/src/mono/mono/offsets/x86_64-none-linux-android.h @@ -239,7 +239,7 @@ DECL_OFFSET2(MonoMethodRuntimeGenericContext,method,8) DECL_OFFSET2(MonoMethodRuntimeGenericContext,method_inst,16) DECL_OFFSET2(MonoMethodRuntimeGenericContext,entries,24) DECL_OFFSET2(MonoMethodRuntimeGenericContext,infos,32) -DECL_SIZE2(MonoJitTlsData,2176) +DECL_SIZE2(MonoJitTlsData,2168) DECL_OFFSET2(MonoJitTlsData,end_of_stack,0) DECL_OFFSET2(MonoJitTlsData,stack_size,8) DECL_OFFSET2(MonoJitTlsData,lmf,16) @@ -253,16 +253,16 @@ DECL_OFFSET2(MonoJitTlsData,class_cast_from,80) DECL_OFFSET2(MonoJitTlsData,class_cast_to,88) DECL_OFFSET2(MonoJitTlsData,ex_ctx,96) DECL_OFFSET2(MonoJitTlsData,resume_state,488) -DECL_OFFSET2(MonoJitTlsData,handler_block,1328) -DECL_OFFSET2(MonoJitTlsData,handler_block_context,1336) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,1728) -DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,2120) -DECL_OFFSET2(MonoJitTlsData,thrown_exc,2128) -DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,2136) -DECL_OFFSET2(MonoJitTlsData,calling_image,2144) -DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,2152) -DECL_OFFSET2(MonoJitTlsData,active_jit_methods,2160) -DECL_OFFSET2(MonoJitTlsData,interp_context,2168) +DECL_OFFSET2(MonoJitTlsData,handler_block,1320) +DECL_OFFSET2(MonoJitTlsData,handler_block_context,1328) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx,1720) +DECL_OFFSET2(MonoJitTlsData,orig_ex_ctx_set,2112) +DECL_OFFSET2(MonoJitTlsData,thrown_exc,2120) +DECL_OFFSET2(MonoJitTlsData,thrown_non_exc,2128) +DECL_OFFSET2(MonoJitTlsData,calling_image,2136) +DECL_OFFSET2(MonoJitTlsData,abort_exc_stack_threshold,2144) +DECL_OFFSET2(MonoJitTlsData,active_jit_methods,2152) +DECL_OFFSET2(MonoJitTlsData,interp_context,2160) DECL_SIZE2(MonoGSharedVtMethodRuntimeInfo,8) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,locals_size,0) DECL_OFFSET2(MonoGSharedVtMethodRuntimeInfo,entries,8) From 457a0e48b6cc6267081f4971239bcba55ddc0e74 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 23 Jun 2026 13:42:10 +0200 Subject: [PATCH 5/7] [mono] Guard mono_resume_unwind against an unset ex_gchandle Only consume resume_state.ex_gchandle when it is set, so an unexpected or double resume path doesn't free handle 0; ex_obj falls back to NULL, matching the prior behavior. Addresses review feedback. --- src/mono/mono/mini/mini-exceptions.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/mono/mono/mini/mini-exceptions.c b/src/mono/mono/mini/mini-exceptions.c index ab4c2431a92203..cbf4a3ba08ef8c 100644 --- a/src/mono/mono/mini/mini-exceptions.c +++ b/src/mono/mono/mini/mini-exceptions.c @@ -3111,9 +3111,12 @@ mono_resume_unwind (MonoContext *ctx) MONO_CONTEXT_SET_SP (ctx, MONO_CONTEXT_GET_SP (&jit_tls->resume_state.ctx)); new_ctx = *ctx; - MonoObject *ex_obj = mono_gchandle_get_target_internal (jit_tls->resume_state.ex_gchandle); - mono_gchandle_free_internal (jit_tls->resume_state.ex_gchandle); - jit_tls->resume_state.ex_gchandle = NULL; + MonoObject *ex_obj = NULL; + if (jit_tls->resume_state.ex_gchandle) { + ex_obj = mono_gchandle_get_target_internal (jit_tls->resume_state.ex_gchandle); + mono_gchandle_free_internal (jit_tls->resume_state.ex_gchandle); + jit_tls->resume_state.ex_gchandle = NULL; + } mono_handle_exception_internal (&new_ctx, ex_obj, TRUE, NULL); From 2fa26ceeed0d858d56794d590d6405db781f492b Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Tue, 23 Jun 2026 13:07:34 -0500 Subject: [PATCH 6/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- eng/pipelines/runtime-llvm.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eng/pipelines/runtime-llvm.yml b/eng/pipelines/runtime-llvm.yml index afee6893f31232..f2b1b278b88702 100644 --- a/eng/pipelines/runtime-llvm.yml +++ b/eng/pipelines/runtime-llvm.yml @@ -45,10 +45,10 @@ pr: - src/mono/mono/mini/decompose.c - src/mono/mono/mini/method-to-ir.c - src/mono/mono/mini/mini.c - - src/mono/mono/mini/mini-amd64.* - - src/mono/mono/mini/mini-arm64.* - - src/mono/mono/mini/mini-arm.* - - src/mono/mono/mini/mini-riscv.* + - src/mono/mono/mini/mini-amd64*.* + - src/mono/mono/mini/mini-arm64*.* + - src/mono/mono/mini/mini-arm*.* + - src/mono/mono/mini/mini-riscv*.* - src/mono/mono/mini/mini-exceptions.* - src/mono/mono/mini/exceptions-*.* - src/mono/mono/mini/mini-runtime.* From e4a3b20d814b8f0ef0341d7619593a40f36167d6 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 24 Jun 2026 11:01:18 +0200 Subject: [PATCH 7/7] [mono] Harden resume-unwind exception GC handle lifetime In mono_resume_unwind, copy the GC handle to a local and clear resume_state.ex_gchandle before calling mono_handle_exception_internal, freeing it only after the call returns. This keeps the in-flight exception rooted across the managed catch/finally search (which can GC or trigger a nested LLVM finally resume that installs its own handle) and avoids accidentally freeing a newly-installed handle. Also switch the two resume-state exception handles from pinned to non-pinned: the object only needs to stay alive across the resume window (it is always re-fetched via the handle), matching interp_set_resume_state and avoiding unnecessary pinning that inhibits GC compaction. Addresses PR review feedback. --- src/mono/mono/mini/mini-exceptions.c | 34 ++++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/mono/mono/mini/mini-exceptions.c b/src/mono/mono/mini/mini-exceptions.c index cbf4a3ba08ef8c..32ff572eb30dae 100644 --- a/src/mono/mono/mini/mini-exceptions.c +++ b/src/mono/mono/mini/mini-exceptions.c @@ -2502,12 +2502,12 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu * resume_state.ex_gchandle is shared with the finally-resume path * (mono_handle_exception_internal above). If a finally handler threw a * superseding exception that is now being caught here, that path left a - * pinned handle behind that will never be resumed; free it instead of + * handle behind that will never be resumed; free it instead of * asserting, mirroring the finally path. */ if (jit_tls->resume_state.ex_gchandle) mono_gchandle_free_internal (jit_tls->resume_state.ex_gchandle); - jit_tls->resume_state.ex_gchandle = mono_gchandle_new_internal ((MonoObject*)obj, TRUE); + jit_tls->resume_state.ex_gchandle = mono_gchandle_new_internal ((MonoObject*)obj, FALSE); jit_tls->resume_state.ji = ji; jit_tls->resume_state.clause_index = i; jit_tls->resume_state.il_state = frame.il_state; @@ -2570,13 +2570,14 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu */ /* * Keep the exception object alive across the (managed) finally - * handler using a pinned GC handle. The handler can reach a GC - * safepoint, and a moving GC would otherwise invalidate a raw - * pointer stored here (see mono_resume_unwind ()). + * handler using a GC handle. The handler can reach a GC + * safepoint, and a moving GC would otherwise collect the exception + * (or invalidate a raw pointer stored here); the handle keeps it + * alive and tracks relocation (see mono_resume_unwind ()). */ if (jit_tls->resume_state.ex_gchandle) mono_gchandle_free_internal (jit_tls->resume_state.ex_gchandle); - jit_tls->resume_state.ex_gchandle = mono_gchandle_new_internal (obj, TRUE); + jit_tls->resume_state.ex_gchandle = mono_gchandle_new_internal (obj, FALSE); jit_tls->resume_state.ji = ji; jit_tls->resume_state.clause_index = i + 1; jit_tls->resume_state.ctx = *ctx; @@ -3111,15 +3112,24 @@ mono_resume_unwind (MonoContext *ctx) MONO_CONTEXT_SET_SP (ctx, MONO_CONTEXT_GET_SP (&jit_tls->resume_state.ctx)); new_ctx = *ctx; - MonoObject *ex_obj = NULL; - if (jit_tls->resume_state.ex_gchandle) { - ex_obj = mono_gchandle_get_target_internal (jit_tls->resume_state.ex_gchandle); - mono_gchandle_free_internal (jit_tls->resume_state.ex_gchandle); - jit_tls->resume_state.ex_gchandle = NULL; - } + /* + * Copy the GC handle to a local and clear resume_state.ex_gchandle *before* calling + * mono_handle_exception_internal (). That call runs the managed catch/finally search, + * which can trigger a GC or a nested LLVM finally resume that installs its own + * ex_gchandle. Keeping the handle alive across the call (and freeing it only afterwards) + * ensures ex_obj stays rooted, and clearing the field first avoids accidentally freeing a + * newly-installed handle from a nested resume. + */ + MonoGCHandle ex_gchandle = jit_tls->resume_state.ex_gchandle; + jit_tls->resume_state.ex_gchandle = NULL; + + MonoObject *ex_obj = ex_gchandle ? mono_gchandle_get_target_internal (ex_gchandle) : NULL; mono_handle_exception_internal (&new_ctx, ex_obj, TRUE, NULL); + if (ex_gchandle) + mono_gchandle_free_internal (ex_gchandle); + mono_restore_context (&new_ctx); }