Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
12 changes: 10 additions & 2 deletions src/MIDebugEngine/AD7.Impl/AD7Thread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,16 @@ int IDebugThread2.EnumFrameInfo(enum_FRAMEINFO_FLAGS dwFieldSpec, uint nRadix, o
}
else
{
uint low = stackFrames[0].Level;
uint high = stackFrames[stackFrames.Count - 1].Level;
// -stack-list-arguments takes a low/high *frame number* range. When a Python
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
// frame filter is active, those numbers index the decorated stack (synthetic
// frames are counted but carry no level), which is the same order as
// stackFrames here. So request the whole [0, count-1] range rather than a
// range of levels: a level-based range would stop at the first synthetic
// frame and miss every real frame below it. 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
25 changes: 22 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)
{
return null;
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
}
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,15 @@ 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)
{
return;
Comment thread
gregg-miskelly marked this conversation as resolved.
}
uint frameLevel = _ctx.Level.Value;

string expression = _strippedName;

Expand Down Expand Up @@ -957,10 +971,15 @@ public void Assign(string expression)
{
this.VerifyNotDisposed();

if (_ctx.Level == null)
{
return;
Comment thread
gregg-miskelly marked this conversation as resolved.
Outdated
}
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