From d8fdb2681e1911247eb8eae23edc5d587ff34bd6 Mon Sep 17 00:00:00 2001 From: tzcnt Date: Thu, 2 Jul 2026 08:47:48 -0700 Subject: [PATCH 1/5] handle synthetic frames produced by GDB FrameFilter without a level --- src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs | 10 +++++++++- src/MIDebugEngine/Engine.Impl/DebuggedThread.cs | 16 +++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs index f11ff57d6..eac38cf0d 100755 --- a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs @@ -2050,7 +2050,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..a5229e2f0 100644 --- a/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs @@ -300,15 +300,15 @@ private async Task> WalkStack(DebuggedThread thread) else { stack = new List(); - foreach (var frame in frameinfo) + for (uint i = 0; i < frameinfo.Length; i++) { - stack.Add(CreateContext(frame)); + stack.Add(CreateContext(frameinfo[i], i)); } } return stack; } - private ThreadContext CreateContext(TupleValue frame) + private ThreadContext CreateContext(TupleValue frame, uint index) { ulong? pc = frame.TryFindAddr("addr"); @@ -325,7 +325,13 @@ 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 do not carry a "level" field. + // Fall back to the frame's position in the stack so the whole stack walk doesn't fail. + // Real frames keep their true level (GDB's own -stack-list-* numbering ignores the synthetic frames). + // This can produce duplicate levels (async collides with the real frame below it), but + // the level is only used to get locals of the frame, and synthetic frame locals can't be displayed + // anyway, so it behaves as a graceful fallback, displaying the locals of the real frame below. + uint level = frame.TryFindUint("level") ?? index; string from = frame.TryFindString("from"); return new ThreadContext(pc, textPosition, func, level, from); @@ -444,7 +450,7 @@ private async Task CollectThreadsInfo(int cxtThreadId) if (frames.Any()) { List stack = new List(); - stack.AddRange(frames.Select(frame => CreateContext(frame))); + stack.AddRange(frames.Select((frame, i) => CreateContext(frame, (uint)i))); _topContext[threadId] = stack[0]; if (threadId == cxtThreadId) From 2ebc72dc3a13b3e82f930d87493a0a56b6b1b9cc Mon Sep 17 00:00:00 2001 From: tzcnt Date: Wed, 8 Jul 2026 18:54:45 -0700 Subject: [PATCH 2/5] make level nullable --- src/MIDebugEngine/AD7.Impl/AD7StackFrame.cs | 17 +++++++++++-- src/MIDebugEngine/AD7.Impl/AD7Thread.cs | 12 +++++++-- .../Engine.Impl/DebuggedProcess.cs | 17 ++++++++++--- .../Engine.Impl/DebuggedThread.cs | 21 ++++++++-------- src/MIDebugEngine/Engine.Impl/Structures.cs | 11 ++++++-- src/MIDebugEngine/Engine.Impl/Variables.cs | 25 ++++++++++++++++--- .../Natvis.Impl/VisualizationCache.cs | 5 +++- 7 files changed, 84 insertions(+), 24 deletions(-) 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..ff09de52b 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7Thread.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Thread.cs @@ -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 + // 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]; diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs index eac38cf0d..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")) { diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs b/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs index a5229e2f0..04e54b9d4 100644 --- a/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs @@ -300,15 +300,15 @@ private async Task> WalkStack(DebuggedThread thread) else { stack = new List(); - for (uint i = 0; i < frameinfo.Length; i++) + foreach (var frame in frameinfo) { - stack.Add(CreateContext(frameinfo[i], i)); + stack.Add(CreateContext(frame)); } } return stack; } - private ThreadContext CreateContext(TupleValue frame, uint index) + private ThreadContext CreateContext(TupleValue frame) { ulong? pc = frame.TryFindAddr("addr"); @@ -325,13 +325,12 @@ private ThreadContext CreateContext(TupleValue frame, uint index) MITextPosition textPosition = !ignoreSource ? MITextPosition.TryParse(this._debugger, frame) : null; string func = frame.TryFindString("func"); - // Synthetic frames injected by a GDB Python frame filter do not carry a "level" field. - // Fall back to the frame's position in the stack so the whole stack walk doesn't fail. - // Real frames keep their true level (GDB's own -stack-list-* numbering ignores the synthetic frames). - // This can produce duplicate levels (async collides with the real frame below it), but - // the level is only used to get locals of the frame, and synthetic frame locals can't be displayed - // anyway, so it behaves as a graceful fallback, displaying the locals of the real frame below. - uint level = frame.TryFindUint("level") ?? index; + // 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); @@ -450,7 +449,7 @@ private async Task CollectThreadsInfo(int cxtThreadId) if (frames.Any()) { List stack = new List(); - stack.AddRange(frames.Select((frame, i) => CreateContext(frame, (uint)i))); + stack.AddRange(frames.Select(frame => CreateContext(frame))); _topContext[threadId] = stack[0]; if (threadId == cxtThreadId) 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..228184613 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) + { + return null; + } + 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,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; + } + uint frameLevel = _ctx.Level.Value; string expression = _strippedName; @@ -957,10 +971,15 @@ public void Assign(string expression) { this.VerifyNotDisposed(); + if (_ctx.Level == null) + { + return; + } + _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..0b053cb29 100644 --- a/src/MIDebugEngine/Natvis.Impl/VisualizationCache.cs +++ b/src/MIDebugEngine/Natvis.Impl/VisualizationCache.cs @@ -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); } public VisualizerKey(string name, int threadId, int level) From f17630b2a199eafe877f30858f1959581b23f378 Mon Sep 17 00:00:00 2001 From: tzcnt Date: Fri, 10 Jul 2026 10:16:21 -0700 Subject: [PATCH 3/5] throw typed exception instead of returning null --- src/MIDebugEngine/Engine.Impl/Variables.cs | 7 +++++-- src/MIDebugEngine/ResourceStrings.Designer.cs | 11 ++++++++++- src/MIDebugEngine/ResourceStrings.resx | 3 +++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/MIDebugEngine/Engine.Impl/Variables.cs b/src/MIDebugEngine/Engine.Impl/Variables.cs index 228184613..530bff401 100644 --- a/src/MIDebugEngine/Engine.Impl/Variables.cs +++ b/src/MIDebugEngine/Engine.Impl/Variables.cs @@ -587,7 +587,7 @@ public string EvalDependentExpression(string expr) if (_ctx.Level == null) { - return null; + throw GetEvalUnsupportedInFrameException(); } uint frameLevel = _ctx.Level.Value; @@ -645,6 +645,7 @@ internal async Task Eval(uint radix, enum_EVALFLAGS dwFlags = 0, DAPEvalFlags dw // 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; @@ -924,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") @@ -973,7 +976,7 @@ public void Assign(string expression) if (_ctx.Level == null) { - return; + throw GetEvalUnsupportedInFrameException(); } _engine.DebuggedProcess.WorkerThread.RunOperation(async () => 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} From 2a9193e1161457bba7d257b34931e5abb0e9d80c Mon Sep 17 00:00:00 2001 From: tzcnt Date: Fri, 10 Jul 2026 10:41:24 -0700 Subject: [PATCH 4/5] add no-real-frames guard --- src/MIDebugEngine/AD7.Impl/AD7Thread.cs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/MIDebugEngine/AD7.Impl/AD7Thread.cs b/src/MIDebugEngine/AD7.Impl/AD7Thread.cs index ff09de52b..dcf690d23 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; @@ -127,21 +128,23 @@ 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]; } else { - // -stack-list-arguments takes a low/high *frame number* range. When a Python - // 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. + // -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); From 4e5c6e8601edd85d37359cb2e79f4a6f07fdfb0a Mon Sep 17 00:00:00 2001 From: tzcnt Date: Fri, 10 Jul 2026 15:46:35 -0700 Subject: [PATCH 5/5] address Copilot findings --- src/MIDebugEngine/AD7.Impl/AD7Thread.cs | 10 ++++------ src/MIDebugEngine/Engine.Impl/Variables.cs | 6 +++--- src/MIDebugEngine/Natvis.Impl/VisualizationCache.cs | 2 ++ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/MIDebugEngine/AD7.Impl/AD7Thread.cs b/src/MIDebugEngine/AD7.Impl/AD7Thread.cs index dcf690d23..1c11d34f8 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7Thread.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Thread.cs @@ -128,12 +128,9 @@ int IDebugThread2.EnumFrameInfo(enum_FRAMEINFO_FLAGS dwFieldSpec, uint nRadix, o int numStackFrames = stackFrames != null ? stackFrames.Count : 0; FRAMEINFO[] frameInfoArray; - // -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)) + if (numStackFrames == 0) { - // failed to walk any real frames. Return an empty stack. + // failed to walk any frames. Return an empty stack. frameInfoArray = new FRAMEINFO[0]; } else @@ -152,7 +149,8 @@ int IDebugThread2.EnumFrameInfo(enum_FRAMEINFO_FLAGS dwFieldSpec, uint nRadix, o 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/Variables.cs b/src/MIDebugEngine/Engine.Impl/Variables.cs index 530bff401..b6dd867bb 100644 --- a/src/MIDebugEngine/Engine.Impl/Variables.cs +++ b/src/MIDebugEngine/Engine.Impl/Variables.cs @@ -587,7 +587,7 @@ public string EvalDependentExpression(string expr) if (_ctx.Level == null) { - throw GetEvalUnsupportedInFrameException(); + throw GetEvalUnsupportedInFrameException("-data-evaluate-expression"); } uint frameLevel = _ctx.Level.Value; @@ -925,7 +925,7 @@ private void SetAsError(string msg) Error = true; } - private Exception GetEvalUnsupportedInFrameException() => new UnexpectedMIResultException(_debuggedProcess.MICommandFactory.Name, "-data-evaluate-expression", ResourceStrings.ExpressionEvalUnsupportedInFrame); + private Exception GetEvalUnsupportedInFrameException(string command) => new UnexpectedMIResultException(_debuggedProcess.MICommandFactory.Name, command, ResourceStrings.ExpressionEvalUnsupportedInFrame); private bool IsArrayType() { @@ -976,7 +976,7 @@ public void Assign(string expression) if (_ctx.Level == null) { - throw GetEvalUnsupportedInFrameException(); + throw GetEvalUnsupportedInFrameException("-var-assign"); } _engine.DebuggedProcess.WorkerThread.RunOperation(async () => diff --git a/src/MIDebugEngine/Natvis.Impl/VisualizationCache.cs b/src/MIDebugEngine/Natvis.Impl/VisualizationCache.cs index 0b053cb29..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; @@ -24,6 +25,7 @@ public VisualizerKey(IVariableInformation variable) // 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); }