Evaluating a variable after a failed Step Out causes a fatal error, leaving debug session unusable
gareth-rees opened this issue · 0 comments
Steps to reproduce
- In Visual Studio Code, install the C/C++ extension from the Visual Studio Code marketplace.
- Create a Launch Configuration using the "(gdb) Launch" template and update
"program"
to point to a suitable executable with debug symbols. - Use View ⟶ Run to open the Run panel, and then select "(gdb) Launch".
- When the program stops in
main()
, click on Step Out. This fails with an error message like this in the Debug Console:Note that this failure is expected.ERROR: Unexpected GDB output from command "-exec-finish". "finish" not meaningful in the outermost frame.
- In the input field below the Debug Console, type the name of a local variable that you want to evaluate.
- This fails with the following error message (see screenshot):
Now the debug session is no longer usable.
Stopping due to fatal error: ProtocolException: Cannot evaluate expression on the specified stack frame.
Analysis
The fatal error comes from the lookup of the stack frame in GetDebugPropertyFromExpression()
here:
MIEngine/src/OpenDebugAD7/AD7DebugSession.cs
Lines 441 to 454 in 93c8040
Why does the call to m_frameHandles.TryGetFirst()
or m_frameHandles.TryGet()
fail? That's because m_frameHandles
was reset by the call to BeforeContinue()
in StepInternal()
here:
MIEngine/src/OpenDebugAD7/AD7DebugSession.cs
Lines 814 to 839 in 93c8040
The problem here is that if m_program.Step()
fails without running the program, then MIEngine is left without any stack frames. The stack frames will be restored at the next point where HandleStackTraceRequestAsync()
is called, but this does not happen until a subsequent successful attempt to run the program. In the mean time, any calls to GetDebugPropertyFromExpression()
will result in a fatal error.
Suggested fix
In StepInternal()
, wait until the program is known to be running before calling BeforeContinue()
to discard the frame handles and other program state, for example:
ErrorBuilder builder = new ErrorBuilder(() => errorMessage);
m_isStepping = true;
// ... some code elided ...
try
{
builder.CheckHR(m_program.Step(thread, stepKind, stepUnit));
}
catch (AD7Exception)
{
m_isStopped = true;
throw;
}
// The program should now be running, so it is safe to discard the
// cached program state.
BeforeContinue();
Software versions
C/C++ extension: 1.11.5
VSCode: 1.70.1
Commit: 6d9b74a70ca9c7733b29f0456fd8195364076dda
Date: 2022-08-10T06:09:15.055Z
Electron: 18.3.5
Chromium: 100.0.4896.160
Node.js: 16.13.2
V8: 10.0.139.17-electron.0
OS: Linux x64 5.4.0-122-generic snap