Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/MIDebugEngine/AD7.Impl/AD7StackFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,14 +345,21 @@ private void CreateParameterProperties(enum_DEBUGPROP_INFO_FLAGS dwFields, out u

private void CreateRegisterContent(enum_DEBUGPROP_INFO_FLAGS dwFields, out uint elementsReturned, out IEnumDebugPropertyInfo2 enumObject)
{
if (ThreadContext.Level == null)
{
elementsReturned = 0;
enumObject = new AD7PropertyInfoEnum(Array.Empty<DEBUG_PROPERTY_INFO>());
return;
}

IReadOnlyCollection<RegisterGroup> registerGroups = Engine.DebuggedProcess.GetRegisterGroups();

elementsReturned = (uint)registerGroups.Count;
DEBUG_PROPERTY_INFO[] propInfo = new DEBUG_PROPERTY_INFO[elementsReturned];
Tuple<int, string>[] values = null;
Engine.DebuggedProcess.WorkerThread.RunOperation(async () =>
{
values = await Engine.DebuggedProcess.GetRegisters(Thread.GetDebuggedThread().Id, ThreadContext.Level);
values = await Engine.DebuggedProcess.GetRegisters(Thread.GetDebuggedThread().Id, ThreadContext.Level.Value);
});
int i = 0;
foreach (var grp in registerGroups)
Expand All @@ -366,10 +373,16 @@ private void CreateRegisterContent(enum_DEBUGPROP_INFO_FLAGS dwFields, out uint

public string EvaluateExpression(string expr)
{
if (ThreadContext.Level == null)
{
return null;
}
uint level = ThreadContext.Level.Value;

string val = null;
Engine.DebuggedProcess.WorkerThread.RunOperation(async () =>
{
val = await Engine.DebuggedProcess.MICommandFactory.DataEvaluateExpression(expr, Thread.Id, ThreadContext.Level);
val = await Engine.DebuggedProcess.MICommandFactory.DataEvaluateExpression(expr, Thread.Id, level);
});
return val;
}
Expand Down
19 changes: 15 additions & 4 deletions src/MIDebugEngine/AD7.Impl/AD7Thread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Debugger.Interop;
using System.Diagnostics;
Expand Down Expand Up @@ -127,15 +128,25 @@ int IDebugThread2.EnumFrameInfo(enum_FRAMEINFO_FLAGS dwFieldSpec, uint nRadix, o
int numStackFrames = stackFrames != null ? stackFrames.Count : 0;
FRAMEINFO[] frameInfoArray;

if (numStackFrames == 0)
// -stack-list-arguments is a frame-relative command. If we walked no frames, or
// none of them carries a level (e.g. every frame is a GDB synthetic frame-filter
// frame), there is nothing addressable to query.
if (numStackFrames == 0 || !stackFrames.Any(f => f.Level != null))
{
// failed to walk any frames. Return an empty stack.
// failed to walk any real frames. Return an empty stack.
frameInfoArray = new FRAMEINFO[0];
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
}
else
{
uint low = stackFrames[0].Level;
uint high = stackFrames[stackFrames.Count - 1].Level;
// -stack-list-arguments takes a low/high *frame index* range. When a GDB Python
// frame filter is active, those numbers index the decorated stack, not the GDB frame
// level. Thus, synthetic frames are indexed even though they carry no level, and we
// must request the whole [0, count-1] decorated range.
// Synthetic frames in the range have no arguments and are skipped in
// GetParameterInfoOnly, and results are matched back to frames by level below,
// so ordering within the range is moot.
uint low = 0;
uint high = (uint)(stackFrames.Count - 1);
FilterUnknownFrames(stackFrames);
numStackFrames = stackFrames.Count;
frameInfoArray = new FRAMEINFO[numStackFrames];
Comment thread
gregg-miskelly marked this conversation as resolved.
Expand Down
27 changes: 23 additions & 4 deletions src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1989,7 +1989,13 @@ internal async Task<List<VariableInformation>> GetLocalsAndParameters(AD7Thread
{
List<VariableInformation> variables = new List<VariableInformation>();

ValueListValue localsAndParameters = await MICommandFactory.StackListVariables(PrintValue.NoValues, thread.Id, ctx.Level);
if (ctx.Level == null)
{
return variables;
}
uint level = ctx.Level.Value;

ValueListValue localsAndParameters = await MICommandFactory.StackListVariables(PrintValue.NoValues, thread.Id, level);

foreach (var localOrParamResult in localsAndParameters.Content)
{
Expand All @@ -2000,7 +2006,7 @@ internal async Task<List<VariableInformation>> GetLocalsAndParameters(AD7Thread
variables.Add(vi);
}

if (ReturnValue != null && ctx.Level == 0 && ReturnValue.Client.Id == thread.Id)
if (ReturnValue != null && level == 0 && ReturnValue.Client.Id == thread.Id)
variables.Add(ReturnValue);

return variables;
Expand All @@ -2012,7 +2018,12 @@ public async Task<List<SimpleVariableInformation>> GetParameterInfoOnly(AD7Threa
{
List<SimpleVariableInformation> parameters = new List<SimpleVariableInformation>();

ValueListValue localAndParameters = await MICommandFactory.StackListVariables(PrintValue.SimpleValues, thread.Id, ctx.Level);
if (ctx.Level == null)
{
return parameters;
}

ValueListValue localAndParameters = await MICommandFactory.StackListVariables(PrintValue.SimpleValues, thread.Id, ctx.Level.Value);

foreach (var results in localAndParameters.Content.Where(r => r.TryFindString("arg") == "1"))
{
Expand Down Expand Up @@ -2050,7 +2061,15 @@ public async Task<List<ArgumentList>> GetParameterInfoOnly(AD7Thread thread, boo

foreach (var f in frames)
{
int level = f.FindInt("level");
// Synthetic frames injected by a GDB Python frame filter have no "level" field.
// GDB doesn't support querying them by level either, so their argument lists
// can't be retrieved. Just skip them instead of failing the whole stack walk.
uint? levelOpt = f.TryFindUint("level");
if (levelOpt == null)
{
continue;
}
int level = (int)levelOpt.Value;
ListValue argList = null;
f.TryFind<ListValue>("args", out argList);
List<SimpleVariableInformation> args = new List<SimpleVariableInformation>();
Expand Down
7 changes: 6 additions & 1 deletion src/MIDebugEngine/Engine.Impl/DebuggedThread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,12 @@ private ThreadContext CreateContext(TupleValue frame)
MITextPosition textPosition = !ignoreSource ? MITextPosition.TryParse(this._debugger, frame) : null;

string func = frame.TryFindString("func");
uint level = frame.FindUint("level");
// Synthetic frames injected by a GDB Python frame filter (e.g. an async-stack
// decorator) do not carry a "level" field, since they have no underlying debugger
// frame. Leave the level null in that case; frame-relative operations (locals,
// args, registers, evaluation) early-out on a null level rather than failing the
// whole stack walk. Real frames keep their true level.
uint? level = frame.TryFindUint("level");
string from = frame.TryFindString("from");

return new ThreadContext(pc, textPosition, func, level, from);
Expand Down
11 changes: 9 additions & 2 deletions src/MIDebugEngine/Engine.Impl/Structures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.MIDebugEngine
{
internal class ThreadContext
{
public ThreadContext(ulong? addr, MITextPosition textPosition, string function, uint level, string from)
public ThreadContext(ulong? addr, MITextPosition textPosition, string function, uint? level, string from)
{
pc = addr;
sp = 0;
Expand All @@ -32,7 +32,14 @@ public ThreadContext(ulong? addr, MITextPosition textPosition, string function,

public string From { get; private set; }

public uint Level { get; private set; }
/// <summary>
/// [Optional] The GDB/LLDB frame level. This is null for synthetic frames
/// injected by a Python frame filter (e.g. an async-stack decorator): those
/// frames have no underlying debugger frame, so they carry no level and cannot
/// be targeted by frame-relative MI commands (locals, args, registers, eval).
/// Callers that pass the level to the debugger must early-out when it is null.
/// </summary>
public uint? Level { get; private set; }

/// <summary>
/// Finds the module for this context
Expand Down
28 changes: 25 additions & 3 deletions src/MIDebugEngine/Engine.Impl/Variables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -585,10 +585,16 @@ public string EvalDependentExpression(string expr)
{
this.VerifyNotDisposed();

if (_ctx.Level == null)
{
throw GetEvalUnsupportedInFrameException();
}
uint frameLevel = _ctx.Level.Value;
Comment thread
gregg-miskelly marked this conversation as resolved.

string val = null;
Task eval = Task.Run(async () =>
{
val = await _engine.DebuggedProcess.MICommandFactory.DataEvaluateExpression(expr, Client.GetDebuggedThread().Id, _ctx.Level);
val = await _engine.DebuggedProcess.MICommandFactory.DataEvaluateExpression(expr, Client.GetDebuggedThread().Id, frameLevel);
});
eval.Wait();
return val;
Expand Down Expand Up @@ -633,7 +639,16 @@ internal async Task Eval(uint radix, enum_EVALFLAGS dwFlags = 0, DAPEvalFlags dw
}

int threadId = Client.GetDebuggedThread().Id;
uint frameLevel = _ctx.Level;

// Synthetic frames injected by a Python frame filter have no level and provide
// no evaluation context. This is normally unreachable (annotated frames expose
// no expression context), but guard defensively rather than crash on a null level.
if (_ctx.Level == null)
{
SetAsError(ResourceStrings.ExpressionEvalUnsupportedInFrame);
return;
Comment thread
gregg-miskelly marked this conversation as resolved.
}
uint frameLevel = _ctx.Level.Value;

string expression = _strippedName;

Expand Down Expand Up @@ -910,6 +925,8 @@ private void SetAsError(string msg)
Error = true;
}

private Exception GetEvalUnsupportedInFrameException() => new UnexpectedMIResultException(_debuggedProcess.MICommandFactory.Name, "-data-evaluate-expression", ResourceStrings.ExpressionEvalUnsupportedInFrame);

private bool IsArrayType()
{
if (DisplayHint == "array")
Expand Down Expand Up @@ -957,10 +974,15 @@ public void Assign(string expression)
{
this.VerifyNotDisposed();

if (_ctx.Level == null)
{
throw GetEvalUnsupportedInFrameException();
}
Comment thread
gregg-miskelly marked this conversation as resolved.

_engine.DebuggedProcess.WorkerThread.RunOperation(async () =>
{
int threadId = Client.GetDebuggedThread().Id;
uint frameLevel = _ctx.Level;
uint frameLevel = _ctx.Level.Value;

_engine.DebuggedProcess.FlushBreakStateData();
Value = await _engine.DebuggedProcess.MICommandFactory.VarAssign(_internalName, expression, threadId, frameLevel);
Expand Down
5 changes: 4 additions & 1 deletion src/MIDebugEngine/Natvis.Impl/VisualizationCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ public VisualizerKey(IVariableInformation variable)
{
_name = variable.FullName();
_threadId = variable.Client.Id;
_level = (int)variable.ThreadContext.Level;
// Variables only ever come from real (level-bearing) frames; synthetic frames
// from a Python frame filter expose no locals to visualize. Fall back to 0 to
// keep the key total in the null case rather than throwing.
_level = (int)(variable.ThreadContext.Level ?? 0);
Comment thread
gregg-miskelly marked this conversation as resolved.
}

public VisualizerKey(string name, int threadId, int level)
Expand Down
11 changes: 10 additions & 1 deletion src/MIDebugEngine/ResourceStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/MIDebugEngine/ResourceStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@
<data name="ExceptionSettingsError" xml:space="preserve">
<value>Error while updating exception settings. {0}</value>
</data>
<data name="ExpressionEvalUnsupportedInFrame" xml:space="preserve">
<value>Expression evaluation is unavailable in the current frame</value>
</data>
<data name="FileNotFound" xml:space="preserve">
<value>File not found: {0}</value>
</data>
Expand Down
Loading