无法使用 VS Debugger Interop 执行语句

Can't execute statement with VS Debugger Interop

提问人:Anders Forsgren 提问时间:2/14/2015 最后编辑:marc_sAnders Forsgren 更新时间:3/8/2017 访问量:609

问:

我正在编写一个调试器扩展 VSPackage,我想在命中断点时在调试进程中执行一条语句。在我的扩展代码中,我有这个:

void Initialize()
{
    // ...standard vspackage init code omitted...

    Globals.Init((DTE2)GetService(typeof(DTE)));              
    Globals.DebuggerEvents.OnEnterBreakMode += (dbgEventReason reason, ref dbgExecutionAction action) =>
    {
        try
        {
           var e1 = Globals.Application.Debugger.GetExpression("1+2");
           Debug.WriteLine(e1.Value);     // Prints "3"

           Globals.Application.Debugger.ExecuteStatement("x = 1+2", 1000);
           Debug.WriteLine("OK");         // Never prints this                          
        } 
        catch (Exception ex)
        {
           Debug.WriteLine("Error: "+ex); // Nor this
        }
    }             
}

在 VS 实例中调试此扩展时,我加载了一个如下所示的简单程序

static void Main()
{
   int x = 5;
   Console.WriteLine("X is "+x); // Breakpoint on this line
}

当在调试进程中命中断点时,将调用处理程序,并且扩展的输出窗口显示“3”,因此计算表达式有效,但它永远不会成功执行语句。没有更多内容打印到输出窗口。没有发生异常或超时,我无法继续调试该过程,调试器似乎已崩溃。

globals 类只保存 DTE 和 DebuggerEvents

public static class Globals
{
   public static void Init(DTE2 dte)
   {
      Application = dte;
      DebuggerEvents = dte.Events.DebuggerEvents;    
   }

   public static DTE2 Application { get; private set; }
   public static DebuggerEvents DebuggerEvents { get; private set; }
}

我做错了什么,或者在这里误解了什么?

C# 调试 visual-studio-extensions envdte

评论

0赞 Hans Passant 2/14/2015
这里最好的假设是调试器引擎尚未处于可以开始接收命令的状态。标准技巧是稍后使用计时器或 ThreadPool.QueueUserWorkItem() 执行此操作,以便稍后发生。
0赞 Anders Forsgren 2/15/2015
延迟它在某种程度上会有所帮助,它有时会执行一个表达式。我有一种感觉可能会通过重新进入处理程序来杀死它,就好像 ExecuteStatement 立即引发 OnEnterBreakMode 一样(这可以解释为什么除非您稍微延迟它,否则什么都不会发生)。

答:

1赞 Kobor42 2/6/2017 #1

我在 Visual Studio 调试方面做了很多修改,冻结的最终原因总是与线程处理有关:VS 允许任何代码段在调试时仅在主线程中运行。所有其他线程都被禁用,如果您的调试代码依赖于不同的线程,它也会冻结。

我的猜测:您在与正在调试的线程不同的线程中初始化了 DTE。

假设结果:Delegate 方法尝试加载初始化线程的上下文,该上下文与调试的线程不同,因此它必然会被冻结。

建议的解决方案:不要使用委托方法。它们隐式引用原始执行上下文。相反,请注册一个常规方法,并在该上下文中重新初始化 DTE。

2赞 user594643 3/8/2017 #2

这是一个老问题,但谷歌上关于这些问题的信息太少了,我想我会提供一些帮助。一些重要的考虑因素:

  1. 如果可能,请使用 GetExpresssion3(TreatAsStatement:=True),而不是 ExecuteStatement(我无法让 ExecuteStatement 正常工作)。
  2. 调用委托的线程 (OnEnterBreakMode) 与需要再次运行才能执行表达式或语句的线程相同。因此,请在新线程 (Task.Run..) 上调用 GetExpression 方法。
  3. 您必须监视和管理 OnEnterBreakMode 的 Reason 值。初始原因是实际未处理异常的 UnwindFromException。然后,应设置一个变量,例如 tempStack = New System.Diagnostics.StackTrace(True)。执行此语句后,将再次调用 OnEnterBreakMode,但这次使用 Evaluation for the Reason。此时,您现在调用所有 GetExpressions 来收集所有数据,而无需额外的 OnEnterBreakMode 调用。

    将 dte2 调暗为 EnvDTE80.DTE2 = GetGlobalService(GetType(EnvDTE.DTE))

    将调试器5调暗为EnvDTE100.Debugger5 = Dte2.Debugger

有趣的设计观察:System.Diagnostics.StackTrace 在 .NET 框架的其余部分的上下文中是一个非常奇怪的设计类,直到您必须处理这个项目,在该项目中,您正在通过这种技术提取 StackTrace,并看到其其他奇怪的设计的好处。