dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.29k stars 4.74k forks source link

[Debugging] JMC Step becomes Go from finally block in method called from static constructor. #52328

Open chuckries opened 3 years ago

chuckries commented 3 years ago

Originally reported here: https://developercommunity.visualstudio.com/t/The-debugger-does-not-step-through-a-met/1407274

This issue repros in VS with:

Repro code:

using System;

namespace StaticTryFinally
{
    static class MyStaticClass
    {
        private const int COUNT = 10;
        private static int[] values;
        private static bool initComplete;

        static MyStaticClass()
        {
            Init();
        }

        private static void Init()
        {
            values = new int[COUNT]; // bp here

            for (int i = 0; i < COUNT; i++)
            {
                try
                {
                    values[i] = i;
                }
                finally
                {
                    values[i] = values[i] + i;
                } // step to here. step over on this line lands in InitComplete
            }

            initComplete = true;
        }

        public static bool InitComplete
        {
            get
            {
                return initComplete;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            bool complete = MyStaticClass.InitComplete;
        }
    }
}

Repro:

  1. Create a new .NET 5.0 Console App in VS with the code above
  2. Ensure Just My Code is enabled through Debugger Options
  3. Set a bp on the commented line
  4. Step Over through the method to the closing brace of the finally block
  5. Step Over

Expected: step stops on closing brace of the loop and the loop can continue to step'd through Actual: step stops in getter for InitComplete, loop has completed

Notes:

ghost commented 3 years ago

Tagging subscribers to this area: @tommcdon See info in area-owners.md if you want to be subscribed.

Issue Details
Originally reported here: https://developercommunity.visualstudio.com/t/The-debugger-does-not-step-through-a-met/1407274 This issue repros in VS with: - Any **x64** runtime (tested with net472 and net5.0 x64) - With JMC enabled - When stepping from the last line of the finally block in a static method called from a static constructor Repro code: ``` using System; namespace StaticTryFinally { static class MyStaticClass { private const int COUNT = 10; private static int[] values; private static bool initComplete; static MyStaticClass() { Init(); } private static void Init() { values = new int[COUNT]; // bp here for (int i = 0; i < COUNT; i++) { try { values[i] = i; } finally { values[i] = values[i] + i; } // step to here. step over on this line lands in InitComplete } initComplete = true; } public static bool InitComplete { get { return initComplete; } } } class Program { static void Main(string[] args) { bool complete = MyStaticClass.InitComplete; } } } ``` Repro: 1. Create a new .NET 5.0 Console App in VS with the code above 2. Ensure Just My Code is enabled through Debugger Options 3. Set a bp on the commented line 4. Step Over through the method to the closing brace of the finally block 5. Step Over Expected: step stops on closing brace of the loop and the loop can continue to step'd through Actual: step stops in getter for InitComplete, loop has completed Notes: - This only appears to affect JMC stepping in x64 runtimes. This does not repro if you disable JMC or force the runtime to be x86 - VS Debugger uses the following stepping APIs: - ICorDebugThread::CreateStepper - ICorDebugStepper::SetUnmappedStopMask(0) - ICorDebugStepper::SetInterceptMask(INTERCEPT_EXCEPTION_FILTER) - ICorDebugStepper2::SetJMC(true) - ICorDebugStepper::StepRange - The IL ranges we pass to StepRange appear correct as they are not different between the working case (jmc off) and the non working case (jmc on) - **When ICorDebugManagedCallback::StepComplete is fired from the runtime, the process is already in the wrong place. It is the runtime that is failing to correctly stop the step in the correct location.** - I was unable to repro this in other static method scenarios, only a static method called from a static constructor. In researching this I did find that metadata for static constructor can be weird, so maybe that has something to do with it.
Author: chuckries
Assignees: -
Labels: `area-Diagnostics-coreclr`, `untriaged`
Milestone: -
chuckries commented 3 years ago

cc @tommcdon

tommcdon commented 3 years ago

@hoyosjs