Runtime: Func eval of SpanDebugView crashes when stepping over stackalloc

Created on 17 Jul 2019  路  12Comments  路  Source: dotnet/runtime

Repro steps

  1. Create the following program
  2. Set a breakpoint on the commented line
  3. Hit F10 twice (https://github.com/dotnet/coreclr/issues/14926 tracks the need for this). You will now be stopped at the highlighted instruction (see 'Disassembly' below)
  4. Evaluate mib

Result

Target process crashes in coreclr.dll!ClassLoader::LoadTypeHandleForTypeKey_Body resulting in the following message from VS:

---------------------------
Microsoft Visual Studio
---------------------------
The target process exited with code -2146233082 while evaluating the function 'System.SpanDebugView.SpanDebugView'.

If the problem happens regularly, consider disabling the Tools->Options setting "Debugging->General->Enable property evaluation and other implicit function calls" or debugging the cause by evaluating the expression from the Immediate window. See help for information on doing this.
---------------------------
OK Help
---------------------------

Call stack

coreclr.dll!ClassLoader::LoadTypeHandleForTypeKey_Body(TypeKey * pTypeKey, TypeHandle typeHnd, ClassLoadLevel targetLevel) Line 3778
at f:\workspace.1_work\1\ssrc\vm\clsload.cpp(3778)
coreclr.dll!ClassLoader::LoadTypeHandleForTypeKey(TypeKey * pTypeKey, TypeHandle typeHnd, ClassLoadLevel targetLevel, const InstantiationContext * pInstContext) Line 3677
at f:\workspace.1_work\1\ssrc\vm\clsload.cpp(3677)
coreclr.dll!ClassLoader::LoadTypeDefThrowing(Module * pModule, unsigned int typeDef, ClassLoader::NotFoundAction fNotFoundAction, ClassLoader::PermitUninstantiatedFlag fUninstantiated, unsigned int tokenNotToLoad, ClassLoadLevel level, Instantiation * pTargetInstantiation) Line 2565
at f:\workspace.1_work\1\ssrc\vm\clsload.cpp(2565)
coreclr.dll!ClassLoader::LoadTypeDefOrRefThrowing(Module * pModule, unsigned int typeDefOrRef, ClassLoader::NotFoundAction fNotFoundAction, ClassLoader::PermitUninstantiatedFlag fUninstantiated, unsigned int tokenNotToLoad, ClassLoadLevel level) Line 2737
at f:\workspace.1_work\1\ssrc\vm\clsload.cpp(2737)
coreclr.dll!EEDbgInterfaceImpl::LoadClass(Module * pModule, unsigned int classToken) Line 951
at f:\workspace.1_work\1\ssrc\vm\eedbginterfaceimpl.cpp(951)
coreclr.dll!EEDbgInterfaceImpl::LoadMethodDef(Module * pModule, unsigned int methodDef, unsigned long numGenericArgs, TypeHandle * pGenericArgs, TypeHandle * pOwnerType) Line 803
at f:\workspace.1_work\1\ssrc\vm\eedbginterfaceimpl.cpp(803)
coreclr.dll!ResolveFuncEvalGenericArgInfo(DebuggerEval * pDE) Line 1550
at f:\workspace.1_work\1\ssrc\debug\ee\funceval.cpp(1550)
coreclr.dll!DoNormalFuncEval(DebuggerEval * pDE, unsigned char * pCatcherStackAddr, Object * * pObjectRefArray, void * * pMaybeInteriorPtrArray, void * * pByRefMaybeInteriorPtrArray, __int64 * pBufferForArgsArray, ValueClassInfo * * ppProtectedValueClasses) Line 3087
at f:\workspace.1_work\1\ssrc\debug\ee\funceval.cpp(3087)
coreclr.dll!GCProtectArgsAndDoNormalFuncEval(DebuggerEval * pDE, unsigned char * pCatcherStackAddr) Line 3441
at f:\workspace.1_work\1\ssrc\debug\ee\funceval.cpp(3441)
coreclr.dll!FuncEvalHijackRealWorker(DebuggerEval * pDE, Thread * pFEFrame, FuncEvalFrame *) Line 3659
at f:\workspace.1_work\1\ssrc\debug\ee\funceval.cpp(3659)
coreclr.dll!FuncEvalHijackWorker(DebuggerEval * pDE) Line 3782
at f:\workspace.1_work\1\ssrc\debug\ee\funceval.cpp(3782)
coreclr.dll!FuncEvalHijack() Line 27
at F:\workspace.1_work\1\ssrc\debug\eeamd64\dbghelpers.asm(27)

Notes

  • This reproduces on macOS and Windows on 3.0-preview6, I can also see the problem on Windows in .NET Core 2.2.5, so it doesn't seem to be a regression.
  • I can reproduce the same problem with Span<char>.
  • I didn't investigate if possibly VS is doing something wrong in the way we request the func-eval, so if you can't reproduce this in mdbg let me know and I can see if maybe the problem is on our side.
  • If I inspect the span using interpretation instead of func-eval everything works.

Program

```C#
static void Main(string[] args)
{
Span mib = stackalloc int[3]; // breakpoint here
mib[0] = 42;

        Console.WriteLine("Hello World {0}!", mib[0]);
    }
### Disassembly

        Span<int> mib = stackalloc int[3];

00007FFEF90D0FD9 lea rcx,[rbp+28h]
00007FFEF90D0FDD mov edx,0Ch
00007FFEF90D0FE2 mov edx,edx
00007FFEF90D0FE4 test rdx,rdx
00007FFEF90D0FE7 je 00007FFEF90D1007
00007FFEF90D0FE9 add rdx,0Fh
00007FFEF90D0FED shr rdx,4
00007FFEF90D0FF1 add rsp,20h
00007FFEF90D0FF5 push 0
* 00007FFEF90D0FF7 push 0 // crash when evaluate from here
00007FFEF90D0FF9 dec rdx
00007FFEF90D0FFC jne 00007FFEF90D0FF5
00007FFEF90D0FFE sub rsp,20h
00007FFEF90D1002 lea rdx,[rsp+20h]
00007FFEF90D1007 mov r8d,3
00007FFEF90D100D call 00007FFEF90D0730
00007FFEF90D1012 vmovdqu xmm0,xmmword ptr [rbp+28h]
00007FFEF90D1017 vmovdqu xmmword ptr [rbp+38h],xmm0
00007FFEF90D101C vmovdqu xmm0,xmmword ptr [rbp+38h]
00007FFEF90D1021 vmovdqu xmmword ptr [rbp+48h],xmm0
```

area-Diagnostics-coreclr

Most helpful comment

I now understand root cause. And some of the wierd behaviors.

Because of dotnet/runtime#9245 the stack pointer is sometimes unaligned while single stepping. The various ABIs require SP alignment on call boundaries. When we do a FuncEval to evaluate of the Span<T>, we hijack the thread and then call functions. This triggers an alignment exception on memory operations which require a specific pointer alignment.

We need to align the SP when we hijack the thread.

All 12 comments

CC @tommcdon

@sdmaclea @hoyosjs

There are several issues here.

  • We are stopping in the middle of the stack alloc of mib. This is same issue as dotnet/runtime#9245.
  • We are doing a FuncEval of mib before its object has been initialized. This is either because we have calculated an incorrect IL offset, or JIT is reporting incorrect offset for when the span is corrected.
  • FuncEval is throwing
  • FuncEval failure is not caught by the debug controller and the entire target process dies.

Only the last one might be interesting enough to include in 3.0 or backport.

This was what I found and it was reported in a similar situation by @davidfowl

One thought - in testing I didn't see any issues if I evaluated the Span through interpretation rather than func eval. Did I just get lucky, or are the the inspection APIs better at handling this partially constructed state? Also, I saw that the Length was zero. Is that also something that can be counted on?

If this is something we can count on, here is one idea for a trivial short term 'fix' -- change the DebuggerDisplay for Span to [DebuggerDisplay("{ToString(),nse}")]

This does not look like it will make 3.0. Moving to 5.0. If fix is trivial will consider backport for 3.0-preview9 if approved.

@davidfowl Unfortunately I didn't see your response till now. Looking back, I agree with you. Deferring this was a mistake. This create a bad debug experience.

I must have also missed @gregg-miskelly's comment too. Because I don't remember seeing it.

trivial short term 'fix' -- change the DebuggerDisplay for Span to [DebuggerDisplay("{ToString(),nse}")]

I just tried the proposed fix. It still crashed the process on VS Code while stepping.

The target process exited with code 0 while evaluating the function 
'System.SpanDebugView<T>.SpanDebugView'.
The program '[23947] corerun' has exited with code 0 (0x0).

hmm... I am not sure why that didn't convince the debugger that it should interpret Span.ToString. Maybe ,nse doesn't work in DebuggerDisplay? At any rate, if we do think that is a useful approach, I can open a work item to investigate a possible VS work around.

@gregg-miskelly It did stop the eval of the ToString(), but it did still crash. I think it was probably

    [DebuggerTypeProxy(typeof(SpanDebugView<>))]

triggering a call to the SpanDebugView<> constructor, which evals the incomplete object.

Good point. Yes, that must take precedence.

I now understand root cause. And some of the wierd behaviors.

Because of dotnet/runtime#9245 the stack pointer is sometimes unaligned while single stepping. The various ABIs require SP alignment on call boundaries. When we do a FuncEval to evaluate of the Span<T>, we hijack the thread and then call functions. This triggers an alignment exception on memory operations which require a specific pointer alignment.

We need to align the SP when we hijack the thread.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

yahorsi picture yahorsi  路  3Comments

aggieben picture aggieben  路  3Comments

nalywa picture nalywa  路  3Comments

jzabroski picture jzabroski  路  3Comments

noahfalk picture noahfalk  路  3Comments