Skip to content
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

Debugger.Break in non-user code #149

Open
qgindi opened this issue Dec 6, 2023 · 11 comments
Open

Debugger.Break in non-user code #149

qgindi opened this issue Dec 6, 2023 · 11 comments

Comments

@qgindi
Copy link

qgindi commented Dec 6, 2023

Several bad things, possibly related:

  1. Debugger.Break in non-user code is ignored.
  2. Debug.Assert(false) and Debug.Fail don't pause.
  3. -gdb-set just-my-code 0 does not work.

Note: I tested only in "attach" mode, not in "launch" mode.

@viewizard
Copy link
Member

Did you try -gdb-set just-my-code 0 before attach to process?

Please note, in case of launch, debugger disable code optimization for each assembly during loading (this is especially need for release built assemblies):

pModule2->SetJITCompilerFlags(CORDEBUG_JIT_DISABLE_OPTIMIZATION);

Unfortunately, in case you attach debugger to process with already loaded and optimized assemblies, debugger can't guarantee changes in assemblies code with SetJITCompilerFlags().

@qgindi
Copy link
Author

qgindi commented Dec 6, 2023

Yes, before.

Now I understand why -gdb-set just-my-code 0 does not work in attach mode with optimized dlls.

Tested: -gdb-set just-my-code 0 works in attach mode if that dll is loaded AFTER attaching. And Debugger.Break in that dll then works too.

For me the 1 and 3 are not so important. Just would like to use Debug.Assert in attach mode.

@qgindi
Copy link
Author

qgindi commented Dec 8, 2023

  1. Debug.Assert(false) and Debug.Fail don't pause.

Will this be fixed? I think Debug.Assert is very important in debugging. Now it can't be used because does not pause.

@viewizard
Copy link
Member

Will this be fixed?

Inside debugger? No. This is not debugger related issue, but related to .NET runtime work itself. In case assembly load with "optimized" flag and have optimized code, debugger have nothing to do with this.

You could care about this on your side with attributes, for example:
[MethodImpl(MethodImplOptions.NoOptimization)] https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.methodimploptions?view=net-8.0
Probably, some tricks could also prevent optimization for whole assembly during normal execution (before debugger attach).

@qgindi
Copy link
Author

qgindi commented Dec 8, 2023

I test only non-optimized assemblies. In the test process only .NET assemblies are optimized, including the one containing Debug.Assert etc. I tested with the CLI interface in launch mode, even with set just-my-code 0. Debug.Fail just emits a failed assertion message in the debugger's output but does not pause. Therefore I can do nothing on my side. The only workaround is to not use Debug.Assert etc and instead implement similar methods in user code and use them. But end users will be angry.

@qgindi
Copy link
Author

qgindi commented Dec 8, 2023

This console output shows it. Note the "---- DEBUG ASSERTION FAILED ----".

set just-my-code 0
break debugThis.cs:46
 Breakpoint 1 at debugThis.cs:46 --pending, warning: No executable code of the debugger's target code type is associated with this line.
 Error: 0x80004005: E_FAIL
ncdb> run
^running

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Private.CoreLib.dll
no symbols loaded, base address: 0x7ffcce6f0000, size: 13205504(0xc98000)

thread created, id: 5772

library loaded: C:\code\ok\exe\debugThis\debugThis.dll
symbols loaded, base address: 0x25103770000, size: 40960(0xa000)
breakpoint modified,  Breakpoint 1 at C:\code\ok\files\debugThis.cs:47

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Runtime.dll
no symbols loaded, base address: 0x25103780000, size: 57344(0xe000)

library loaded: C:\code\ok\exe\debugThis\Au.dll
no symbols loaded, base address: 0x291983c0000, size: 1335296(0x146000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Threading.Thread.dll
no symbols loaded, base address: 0x25103790000, size: 32768(0x8000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.IO.FileSystem.DriveInfo.dll
no symbols loaded, base address: 0x7ffd77430000, size: 45056(0xb000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Runtime.InteropServices.dll
no symbols loaded, base address: 0x7ffd78d70000, size: 86016(0x15000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Collections.dll
no symbols loaded, base address: 0x7ffd43930000, size: 249856(0x3d000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Runtime.Loader.dll
no symbols loaded, base address: 0x251037b0000, size: 32768(0x8000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Memory.dll
no symbols loaded, base address: 0x7ffd524b0000, size: 147456(0x24000)

thread created, id: 28804

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Threading.dll
no symbols loaded, base address: 0x7ffd5e630000, size: 73728(0x12000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\Microsoft.Win32.Primitives.dll
no symbols loaded, base address: 0x251037c0000, size: 32768(0x8000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Diagnostics.StackTrace.dll
no symbols loaded, base address: 0x7ffd6e060000, size: 36864(0x9000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Reflection.Metadata.dll
no symbols loaded, base address: 0x7ffce3530000, size: 1114112(0x110000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Collections.Immutable.dll
no symbols loaded, base address: 0x7ffd3ff70000, size: 831488(0xcb000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.IO.Compression.dll
no symbols loaded, base address: 0x7ffd42b80000, size: 253952(0x3e000)

library loaded: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Text.Encoding.Extensions.dll
no symbols loaded, base address: 0x251037d0000, size: 32768(0x8000)
---- DEBUG ASSERTION FAILED ----
---- Assert Short Message ----
FAIL
---- Assert Long Message ----

   at Program.<Main>$(String[] args) in C:\code\ok\files\debugThis.cs:line 26

stopped, reason: breakpoint 1 hit, thread id: 5772, stopped threads: all, times= 1, frame={Program.<Main>$() at C:\code\ok\files\debugThis.cs:47}
ncdb>

@viewizard
Copy link
Member

Debugger use runtime debug API, that provide callbacks and call this callbacks at "stop" events like step complete, exception, breakpoints,... if assert related logic don't call callbacks and just print some info in console, not sure that we could do something with this. I didn't see "assert" related callbacks (https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugmanagedcallback-interface), this should be investigated first.

@gbalykov
Copy link
Member

gbalykov commented Dec 8, 2023

In the test process only .NET assemblies are optimized, including the one containing Debug.Assert etc.

Does this mean that Debug.Assert that you want to catch is somewhere in standard .NET library? According to MS docs (https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-8.0), Debug.Assert is not compiled if your dll is compiled in release (dotnet build -c Release):

By default, the Debug.Assert method works only in debug builds. Use the Trace.Assert method if you want to do assertions in release builds. For more information, see Assertions in Managed Code.

This means that there's no Debug.Assert code in il code directly, thus, .NET runtime doesn't know there was an assert during jit compilation. If you want to debug .NET runtime libraries with Debug.Assert, you have to build runtime in debug/checked.

@qgindi
Copy link
Author

qgindi commented Dec 8, 2023

The call to Debug.Assert is in user code, which is compiled with -c Debug. That is why you can see "---- DEBUG ASSERTION FAILED ----" in the output.

Debug.Assert/Fail calls Debugger.Break. But the debugger ignores it.

https://github.com/dotnet/runtime/blob/6253efeab73fd39ba76e4a7ccaaa480bc52842e0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/DebugProvider.Windows.cs#L18

If Debugger.Break is in user code, the debugger works well (pauses there and outputs "stopped, reason: interrupted").

@qgindi
Copy link
Author

qgindi commented Dec 21, 2023

I removed this code in BreakBreakpoint::ManagedCallbackBreak. Now Debug.Assert etc works.

    // Ignore break on Break() outside of code with loaded PDB (see JMC setup during module load).
    if (iCorFrame != nullptr)
    {
        ToRelease<ICorDebugFunction> iCorFunction;
        IfFailRet(iCorFrame->GetFunction(&iCorFunction));
        ToRelease<ICorDebugFunction2> iCorFunction2;
        IfFailRet(iCorFunction->QueryInterface(IID_ICorDebugFunction2, (LPVOID*) &iCorFunction2));
        BOOL JMCStatus;
        IfFailRet(iCorFunction2->GetJMCStatus(&JMCStatus));

        if (JMCStatus == FALSE)
            return S_OK;
    }

@viewizard
Copy link
Member

As you probably noticed, debugger don't provide stop events in code without PDB and this have a reason. Related to this issue fix should care about "special" status of process stop and don't allow anything except "continue", since, for example, we can't guaranty stepping work from such point. This case should be investigated first in order to care about all related code changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants