diff --git a/src/MIDebugEngine/AD7.Impl/AD7StackFrame.cs b/src/MIDebugEngine/AD7.Impl/AD7StackFrame.cs index 4711f1eab..5e375fab8 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7StackFrame.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7StackFrame.cs @@ -345,6 +345,13 @@ 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()); + return; + } + IReadOnlyCollection registerGroups = Engine.DebuggedProcess.GetRegisterGroups(); elementsReturned = (uint)registerGroups.Count; @@ -352,7 +359,7 @@ private void CreateRegisterContent(enum_DEBUGPROP_INFO_FLAGS dwFields, out uint Tuple[] 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) @@ -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; } diff --git a/src/MIDebugEngine/AD7.Impl/AD7Thread.cs b/src/MIDebugEngine/AD7.Impl/AD7Thread.cs index b2b0a8d35..1c11d34f8 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7Thread.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Thread.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using Microsoft.VisualStudio.Debugger.Interop; using System.Diagnostics; @@ -134,14 +135,22 @@ 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 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]; List parameters = null; - if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_ARGS) != 0 && !_engine.DebuggedProcess.MICommandFactory.SupportsFrameFormatting) + if ((dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_ARGS) != 0 && !_engine.DebuggedProcess.MICommandFactory.SupportsFrameFormatting + && stackFrames.Any(f => f.Level != null)) { _engine.DebuggedProcess.WorkerThread.RunOperation(async () => parameters = await _engine.DebuggedProcess.GetParameterInfoOnly(this, (dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_ARGS_VALUES) != 0, (dwFieldSpec & enum_FRAMEINFO_FLAGS.FIF_FUNCNAME_ARGS_TYPES) != 0, low, high)); diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs index f11ff57d6..27edfad98 100755 --- a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs @@ -1989,7 +1989,13 @@ internal async Task> GetLocalsAndParameters(AD7Thread { List variables = new List(); - 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) { @@ -2000,7 +2006,7 @@ internal async Task> 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; @@ -2012,7 +2018,12 @@ public async Task> GetParameterInfoOnly(AD7Threa { List parameters = new List(); - 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")) { @@ -2050,7 +2061,15 @@ public async Task> 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("args", out argList); List args = new List(); diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs b/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs index 614ef61a9..04e54b9d4 100644 --- a/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs @@ -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); diff --git a/src/MIDebugEngine/Engine.Impl/Structures.cs b/src/MIDebugEngine/Engine.Impl/Structures.cs index 2cfebba42..c1b3e6886 100644 --- a/src/MIDebugEngine/Engine.Impl/Structures.cs +++ b/src/MIDebugEngine/Engine.Impl/Structures.cs @@ -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; @@ -32,7 +32,14 @@ public ThreadContext(ulong? addr, MITextPosition textPosition, string function, public string From { get; private set; } - public uint Level { get; private set; } + /// + /// [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. + /// + public uint? Level { get; private set; } /// /// Finds the module for this context diff --git a/src/MIDebugEngine/Engine.Impl/Variables.cs b/src/MIDebugEngine/Engine.Impl/Variables.cs index 031dd40c8..b6dd867bb 100644 --- a/src/MIDebugEngine/Engine.Impl/Variables.cs +++ b/src/MIDebugEngine/Engine.Impl/Variables.cs @@ -585,10 +585,16 @@ public string EvalDependentExpression(string expr) { this.VerifyNotDisposed(); + if (_ctx.Level == null) + { + throw GetEvalUnsupportedInFrameException("-data-evaluate-expression"); + } + uint frameLevel = _ctx.Level.Value; + 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; @@ -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; + } + uint frameLevel = _ctx.Level.Value; string expression = _strippedName; @@ -910,6 +925,8 @@ private void SetAsError(string msg) Error = true; } + private Exception GetEvalUnsupportedInFrameException(string command) => new UnexpectedMIResultException(_debuggedProcess.MICommandFactory.Name, command, ResourceStrings.ExpressionEvalUnsupportedInFrame); + private bool IsArrayType() { if (DisplayHint == "array") @@ -957,10 +974,15 @@ public void Assign(string expression) { this.VerifyNotDisposed(); + if (_ctx.Level == null) + { + throw GetEvalUnsupportedInFrameException("-var-assign"); + } + _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); diff --git a/src/MIDebugEngine/Natvis.Impl/VisualizationCache.cs b/src/MIDebugEngine/Natvis.Impl/VisualizationCache.cs index de81f6594..060e91f23 100644 --- a/src/MIDebugEngine/Natvis.Impl/VisualizationCache.cs +++ b/src/MIDebugEngine/Natvis.Impl/VisualizationCache.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -21,7 +22,11 @@ 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. + Debug.Assert(variable.ThreadContext.Level.HasValue, "How are we getting a variable from a synthetic thread context?"); + _level = (int)(variable.ThreadContext.Level ?? 0); } public VisualizerKey(string name, int threadId, int level) diff --git a/src/MIDebugEngine/ResourceStrings.Designer.cs b/src/MIDebugEngine/ResourceStrings.Designer.cs index fd9cda12e..9c24bddc4 100755 --- a/src/MIDebugEngine/ResourceStrings.Designer.cs +++ b/src/MIDebugEngine/ResourceStrings.Designer.cs @@ -210,7 +210,16 @@ internal static string ExceptionSettingsError { return ResourceManager.GetString("ExceptionSettingsError", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Expression evaluation is unavailable in the current frame. + /// + internal static string ExpressionEvalUnsupportedInFrame { + get { + return ResourceManager.GetString("ExpressionEvalUnsupportedInFrame", resourceCulture); + } + } + /// /// Looks up a localized string similar to Error: {0}. /// diff --git a/src/MIDebugEngine/ResourceStrings.resx b/src/MIDebugEngine/ResourceStrings.resx index e6b9e7cc2..273e41f22 100755 --- a/src/MIDebugEngine/ResourceStrings.resx +++ b/src/MIDebugEngine/ResourceStrings.resx @@ -132,6 +132,9 @@ Error while updating exception settings. {0} + + Expression evaluation is unavailable in the current frame + File not found: {0}