Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

[CoreCLR debugger] Function evaluation at user-unhandled notification can resume the app without Continue

未关闭
#13,319 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
5/5
预计耗时
一周以上
新手友好度
24/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
android, csharp
领域
tooling

调研方向

Start by tracing the supported coreclr_mobile launch configuration and the debugger handoff across the mobile debug engine, remote provider/profiler, and runtime; the report does not identify which component causes the resume. Use the managed SharedFixture reproduction and its CATCH_CONTROL and notification stops to compare actual method evaluation and target progress. Done means evaluation completes or is explicitly rejected while the target remains stopped until Continue, with a regression covering a real method invocation.

由索引模型根据 Issue 内容生成。

描述

Area: CoreCLR Area: Debugger bug needs-triage
Android framework version

net11.0-android (Preview)

Affected platform version
Component Tested version
Android source baseline Clean, independently built main at eec176c804f89d846a24801e166f5b569f7a6bed
Build SDK 12.0.100-alpha.1.26523.103
Actual Android runtime Microsoft.NETCore.App.Runtime.android-arm64/11.0.0-preview.7.26381.103
Actual CoreLib informational version 11.0.0-preview.7.26381.103+e2c1e00b3d0f96afb892fb261d5921565b400246
App net11.0-android37.0, ARM64 CoreCLR; PublishAot=false, Optimize=false, PublishReadyToRun=false
Device Android 15 / API 35 ARM64 emulator
Host macOS 26.7 / ARM64
VS Code Insiders 1.144.0-insider
C# extension 11.7.18
MAUI extension / paired mobile debugger resources 11.0.114
vsdbg 18.13.11021.6, commit f27481280582c85f53886696125e0190e736c56b

The SDK version is not the target runtime version. Runtime provenance above was read from the running app and restored runtime package. CoreLib's e2c1e00... is a dotnet/dotnet VMR commit; its source manifest maps runtime to 253bde0a42aff6d8e99e039db1e710c5e1f5436a.

Description

Tooling/debugger handoff: /cc @rmarinho, following the mobile-debugger escalation used in #10141. Please triage ownership across the supported mobile launch configuration, debug engine, remote provider/profiler, and runtime.

At a Debugger.BreakForUserUnhandledException(exception) stop, evaluating a method can resume ordinary application execution without a user Continue, leave the evaluate request pending, and eventually fail after the evaluation thread has exited. This is more serious than an expression being unavailable at an optimized/GC-unsafe stop: the debugger should either finish the evaluation and keep the original stop, or reject it while retaining the stop.

This is independent of the generated JNI boundary attributes in #13154. The problem reproduces in a separately built clean-main application with a tiny pure-managed callback/catch/notification, no NUnit, no JNI callback, and no constructor/export generated forwarder on the reproducing path. A separate real-JNI acceptance run exercises the existing JniMarshal.SafeInvokeFunc<int> path. The investigation does not establish that #13154 introduced this behavior.

Normal attach, ordinary source breakpoints, the user-unhandled exception stop itself, value inspection, and explicit Continue work. Actual managed and Java method evaluation works at the ordinary source breakpoint in the same catch on the same thread, but fails at the subsequent notification stop with the baseline configuration. This is not a claim that debugging as a whole is broken.

Requested tooling follow-up
  1. Determine whether the supported Android debugging launch should arrange the necessary runtime/debugger optimization settings, rather than requiring users to discover the workaround below.
  2. Fix the default failure path: an ineligible evaluation must not silently run application code past the stop or strand a pending evaluate. A clear failed-evaluation response with the target still stopped is acceptable when evaluation really is unavailable.
  3. Add a regression covering a real method invocation at this debugger-recognized notification, successful or explicitly rejected, with no ordinary target progress until user Continue. Do not rely on constants or debugger-interpreted expressions as evidence of actual runtime evaluation.

No product patch is proposed here: the captured diagnostics do not identify which native setup/resume/completion subcomponent is responsible. This report is not a request to change JNI exception policy, move notification after JNI Throw, or impose debug settings on shipping applications.

Steps to Reproduce
Minimal shared managed source

This exact file was used for both desktop and Android comparison. SHA-256: 53d8869d1c253a40bd8cfdd6a69b18a30918f092e7af4efceb86503d418c2265.

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace NotificationRepro;

public static class SharedFixture
{
    public static int EvaluationCount;

    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    public static int Return42()
    {
        EvaluationCount++;
        return 42;
    }

    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    public static void Callback()
    {
        throw new InvalidOperationException("notification-func-eval");
    }

    [DebuggerNonUserCode]
    [DebuggerDisableUserUnhandledExceptions]
    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    public static void Boundary(Action callback)
    {
        try
        {
            callback();
        }
        catch (Exception exception)
        {
            Console.WriteLine("CATCH_CONTROL"); // Ordinary source breakpoint.
            Debugger.BreakForUserUnhandledException(exception);
            Console.WriteLine("AFTER_NOTIFICATION");
        }
    }
}

In an Android Activity, run the fixture on a dedicated worker so worker exit is observable while the UI process remains alive:

new System.Threading.Thread(() => {
    Console.WriteLine("REPRO_BEGIN");
    SharedFixture.Boundary(SharedFixture.Callback);
    Console.WriteLine($"REPRO_COMPLETE EvaluationCount={SharedFixture.EvaluationCount}");
}) { Name = "NotificationRepro" }.Start();

The minimal reproducing path has no Java invocation unless requested from the debugger.

App and supported debugger configuration

Use an ARM64 CoreCLR Android application, with the paired MAUI mobile debugger target/host resources, internet permission, portable symbols, Optimize=false, PublishTrimmed=false, PublishAot=false, and PublishReadyToRun=false. The last property was already present in the failing configuration.

For the controlled repro, the installed target profiler is libvsdbgremotecoreclrtarget.so; its SHA-256 is 0c03b692261861e0c1c25f015078c5e3645a45e4087b82c029b174613cce069d. The six profiler/transport runtime variables were:

CORECLR_ENABLE_PROFILING=1
CORECLR_PROFILER={9DC623E8-C88F-4FD5-AD99-77E67E1D9631}
CORECLR_PROFILER_PATH=libvsdbgremotecoreclrtarget.so
CORECLR_REMOTE_DEBUGGER_PORT=56130
CORECLR_REMOTE_DEBUGGER_ISSERVER=1
CORECLR_REMOTE_DEBUGGER_IP=127.0.0.1

Use the supported C# extension debug API/handshake, not a standalone vsdbg handshake bypass. The relevant launch configuration is:

{
  "type": "coreclr_mobile",
  "request": "launch",
  "name": "Notification evaluation repro",
  "program": "<app-output>/NotificationRepro.dll",
  "stopAtEntry": false,
  "justMyCode": true,
  "exceptionBreakpointFilters": ["user-unhandled"],
  "expressionEvaluationOptions": { "allowFastEvaluate": false },
  "logging": {
    "engineLogging": true,
    "diagnosticsLog": {
      "expressionEvaluationTracing": true,
      "debugRuntimeEventTracing": true,
      "debugEngineAPITracing": "error"
    }
  },
  "coreClrMobileDebuggerOptions": {
    "ip": "127.0.0.1",
    "port": 56130,
    "platform": "android;arm64",
    "isServer": false,
    "assetsPath": "<linked-managed-assets-directory>/;<paired-MAUI-resources>/VsdbgRemoteCoreclrHost/osx-arm64"
  }
}

The linked-assets directory must retain the trailing /. An earlier missing slash produced an unrelated metadata file-not-found failure, including for constants; that setup problem was corrected before the reproducing/passing comparisons reported here.

Configure the user-unhandled filter before the worker executes. In the manual prebuilt-app setup, forward the chosen port and enable the CoreCLR bootstrap on the test emulator before starting the target:

adb -s <test-emulator> forward tcp:56130 tcp:56130
adb -s <test-emulator> shell setprop debug.coreclr.enabled 1

Start/configure the supported adapter, then launch the app promptly. Reset the property and remove the forward afterward. The automated comparison serialized stop handlers, recorded every DAP request/response/event, rejected stale stops, and never issued fallback Continue on inspection/evaluation failure.

Comparison
  1. Put a source breakpoint on Console.WriteLine("CATCH_CONTROL") (line 35 of the exact shared file). Run with user-unhandled exceptions enabled.
  2. Select the live SharedFixture.Boundary frame, not an [Exception] reconstructed frame. Evaluate sequentially:
    • 1 + 1
    • NotificationRepro.SharedFixture.Return42()
    • NotificationRepro.SharedFixture.EvaluationCount
    • System.GC.GetTotalMemory(false)
    • Java.Lang.JavaSystem.CurrentTimeMillis() on Android.
  3. At the ordinary breakpoint, all method calls complete with actual DBI EvalComplete callbacks. Observe the target held for at least 2 seconds, then explicitly Continue.
  4. At the subsequent user-unhandled notification stop, repeat evaluation. The physical top frame is System.Diagnostics.Debugger.BreakForUserUnhandledException; the selected live caller is SharedFixture.Boundary at line 36. Constant evaluation succeeds. In the Android baseline, Return42() starts evaluation but the application runs past AFTER_NOTIFICATION and exits its worker, without executing the requested helper or receiving user Continue. The request later fails.
  5. Repeat with both workaround settings below. Actual managed and Java evaluation now completes at both stops; the target remains held until each explicit Continue.
Desktop controls and source interpretation

Supported desktop coreclr comparison used the same source, C# 11.7.18 / vsdbg 18.13.11021.6, and installed .NET 10.0.13 and 11.0.0-rc.2.26504.105. These desktop runtimes are not version-matched to Android preview 7.

Desktop runtime Runtime ReadyToRun suppressJITOptimizations Notification GC.GetTotalMemory(false)
11 RC2 default false Immediate optimized-method rejection; remains paused
10.0.13 default false Immediate optimized-method rejection; remains paused
11 RC2 disabled true Actual method evaluation succeeds; remains paused
10.0.13 disabled true Actual method evaluation succeeds; remains paused
11 RC2 disabled false Immediate optimized-method rejection; remains paused
11 RC2 default true Immediate GC-unsafe-point rejection; remains paused

A successful Return42() result and counter increment alone were misleading in some desktop configurations: the debugger produced them without runtime func-eval dispatch/completion despite allowFastEvaluate=false. The noninterpretable GC call and engine/runtime completion events distinguish real invocation. Android passing runs require per-request EE start and actual DBI EvalComplete, not a text match on an error-stack method such as WaitForFuncEvalComplete.

The API itself is an intentionally empty managed NoInlining | NoOptimization method recognized by the debugger, not an intrinsic runtime custom-notification call. In the captured Android baseline, the underlying callback is DBI Breakpoint, not Exception2; the DAP reason exception must not be used to infer native exception-event state.

In version-matched runtime source jitinterface.cpp, method NoOptimization requests MIN_OPT/FRAMED separately from debugger-disabled module optimizations requesting DEBUG_CODE. The runtime environment switch also prevents use of existing ReadyToRun code. These distinctions explain why Optimize=false/NoOptimization/PublishReadyToRun=false are not equivalent to the successful debugger setup; they do not prove a specific native bug.

Did you find any workaround?

Yes, debug-only: package the target runtime variable:

<ItemGroup>
  <RuntimeEnvironmentVariable Include="DOTNET_ReadyToRun" Value="0" />
</ItemGroup>

and add to the supported debugger launch:

"suppressJITOptimizations": true

Do not apply these unconditionally to shipping applications. PublishReadyToRun=false alone is not the runtime-environment setting. Both switches were individually isolated on desktop; on Android the tested joint combination is sufficient, but individual necessity was not isolated.

Validation after applying the combination:

  • Clean-main, NUnit-free managed repro: 2 stops, 6 actual method completions (Return42, GC, Java at each), 2-second held-state checks, explicit-only Continue.
  • Clean-main real JNI fixture: 4 stops, 12 actual method completions; Java evaluation succeeds at both JNI notification stops. Four scenarios pass: Java catches an unhandled managed callback; application Handled=true suppresses the stop/pending exception; Java rethrow preserves the original managed exception identity; normal success returns 42. Exactly-once application policy and Java catch/finally remain correct, with no JNI pending exception during policy or left afterward.
  • Original #13154 owning worktree, unchanged head e0b8cddb0034fab75565683563f5635a8eaec734: generated constructor and primitive export each stop once on the original throw, evaluate JavaSystem.CurrentTimeMillis() successfully, remain paused for 2983 ms / 2028 ms, then Continue and preserve exception identity. Both handled-policy tests produce zero stops. Complete category 66/66 passed, 11 stops / 11 successful explicit Continues, zero evaluation/inspection/Continue failures. The live process returned "0" for DOTNET_ReadyToRun at both representative stops.
  • Desktop six-case comparison: 48 paired evaluations and 18 explicit Continues with none issued during a pending evaluation. Both successful configurations have actual engine func-eval dispatch/completion. Some separate desktop attempts crashed the host vsdbg-ui; those were excluded, not counted as successful repeats.
  • Independent Android SDK: make prepare CONFIGURATION=Release passed; make all CONFIGURATION=Release reached the known missing extra-API local-workload output, resolved by make leeroy CONFIGURATION=Release, which passed. Minimal/JNI app build/install passed. Harness/analyzer tests 9/9 passed, including detecting target progress without any DAP continued event or worker-exit proxy.
Relevant log output

Condensed Android baseline chronology, host UTC (complete DAP capture; same evaluation thread):

14:42:50       ordinary same-catch breakpoint, thread 4835
               Return42() => 42, EvaluationCount => 1
               GC.GetTotalMemory(false) => 9354024
               JavaSystem.CurrentTimeMillis() => success
               each method has DBI EvalComplete; held >=2s, explicit Continue seq18
14:42:53.192   underlying DBI Breakpoint callback for notification (not Exception2)
14:42:53.341   DAP evaluate seq23: SharedFixture.Return42(), physical caller frame1003
14:42:53.384   EE: Starting func-eval (intent; not proof native setup succeeded)
14:42:53.391-.399
               target AFTER_NOTIFICATION / REPRO_COMPLETE EvaluationCount=1
14:42:53.399   same CordbThread ExitThread
14:42:53.402   DAP thread 4835 exited; Android UI process remains alive
               NO intervening client Continue/step/pause/disconnect
               NO DAP continued event and NO notification EvalComplete
14:42:58.453   engine abort intent
14:43:04.463   engine rude-abort intent
14:43:04.472   evaluate response fails: Unknown Error: 0x80131622
               telemetry: System.ObjectDisposedException after evaluation-thread exit

Target logcat timestamps were calibrated against actual Java time evaluations; both progress markers fall inside the pending seq23 interval independently of the thread-exit evidence. Zero DAP continued events does not mean the target remained paused.

Frozen full traces, source/config/harness, default and passing APKs, and validation output are retained locally; they are not publicly attached to this issue. The self-contained reproducing source, settings, versions, and decisive chronology are included above. Raw trace identities for follow-up:

Artifact SHA-256
probe-default.jsonl 129f6f74a1e01ccbbb70afd9712f56f9e1466a0c213d965216ab7bb6fb124e0f
probe-joint.jsonl 87bc6e3c1a380431fd58b140d9029798aa74f2ffd840424d8d7d2e07fd30a6a3
probe-jni.jsonl eac39a075c6b3570ddb41f4302fb4a8718684a16d8fc38e5303adaf74870b11a

The remaining attribution gap is native func-eval setup HRESULT/actual exception-state flag, active stopped context/hijack, and completion/resume handling in the mobile stack. Existing EE start messages and desktop behavior are insufficient to choose between the proprietary engine, remote provider/profiler, or open runtime. The shipped remote host aliases libremotemscordbihost.dylib / libvsdbgremotecoreclrhost.dylib were identical (SHA-256 3d0f92127c7ce0a8444a52c92e4f9cc87ab32d1a7a4bfbe2e26e679bc4498d0c); supported tracing did not expose those native fields. Please keep this uncertainty explicit when routing the fix.

By opening this issue I agree to follow this project's Code of Conduct.

主要语言
C#
星标
2.1k
派生
579
平均合并
1 天 16 小时
30 天内合并 PR
253

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 有 Pull Request 模板
  • 没有贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

dotnet/android 的其他 Issue

查看 dotnet/android 的全部 Issue

相似的 Issue

更多 C# Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。