C# 以编程方式将调试器附加到另一个应用程序域中运行的代码

C# 以编程方式将调试器附加到另一个应用程序域中运行的代码,c#,debugging,vsix,C#,Debugging,Vsix,我正在开发一个VisualStudio扩展,它的一个功能创建一个新的应用程序域,并将程序集加载到该应用程序域中。然后在应用程序域中运行一些函数。我想做的是,并且不确定是否可能,让我的扩展将调试器附加到新应用程序域中运行的代码上,这样当代码失败时,我就可以真正看到发生了什么。现在我在瞎飞,调试动态加载的程序集是一件痛苦的事情 因此,我有一个类创建我的应用程序域,如下所示: domain = AppDomain.CreateDomain("Test_AppDomain", AppDomai

我正在开发一个VisualStudio扩展,它的一个功能创建一个新的应用程序域,并将程序集加载到该应用程序域中。然后在应用程序域中运行一些函数。我想做的是,并且不确定是否可能,让我的扩展将调试器附加到新应用程序域中运行的代码上,这样当代码失败时,我就可以真正看到发生了什么。现在我在瞎飞,调试动态加载的程序集是一件痛苦的事情

因此,我有一个类创建我的应用程序域,如下所示:

domain = AppDomain.CreateDomain("Test_AppDomain", 
    AppDomain.CurrentDomain.Evidence, 
    AppDomain.CurrentDomain.SetupInformation);
myCollection = domain.CreateInstanceAndUnwrap(
            typeof(MyCollection).Assembly.FullName,
            typeof(MyCollection).FullName,
            false,
            BindingFlags.Default,
            null,
            new object[] { assemblyPath }, null, null);
var processes = dte.Debugger.LocalProcesses.Cast<EnvDTE.Process>();
var currentProcess = System.Diagnostics.Process.GetCurrentProcess().Id;
var process = processes.FirstOrDefault(p => p.ProcessID == currentProcess);
process?.Attach();
然后创建如下对象:

domain = AppDomain.CreateDomain("Test_AppDomain", 
    AppDomain.CurrentDomain.Evidence, 
    AppDomain.CurrentDomain.SetupInformation);
myCollection = domain.CreateInstanceAndUnwrap(
            typeof(MyCollection).Assembly.FullName,
            typeof(MyCollection).FullName,
            false,
            BindingFlags.Default,
            null,
            new object[] { assemblyPath }, null, null);
var processes = dte.Debugger.LocalProcesses.Cast<EnvDTE.Process>();
var currentProcess = System.Diagnostics.Process.GetCurrentProcess().Id;
var process = processes.FirstOrDefault(p => p.ProcessID == currentProcess);
process?.Attach();
MyCollection
在其构造函数中执行以下操作:

_assembly = Assembly.LoadFrom(assemblyPath);
因此,现在该程序集已加载到
Test\u AppDomain
,因为
MyCollection
对象是在该域中创建的。我需要能够将调试器附加到加载的程序集

在某个时刻,myCollection创建一个对象的实例并连接一些事件:

currentObject = Activator.CreateInstance(objectType) as IObjectBase;
proxy.RunRequested += (o, e) => { currentObject?.Run(); };
基本上,我有
runrequest
的处理程序,它运行
currentObject?.Run()
,我希望附加一个调试器,尽管如果调试器更早附加,可能不会有问题(实际上可能工作得更好)

那么有没有办法做到这一点呢?当用户触发将导致调用在新AppDomain中创建的对象的
Run
函数的事件时,是否可以通过编程方式附加调试器?我如何将调试器附加到它(而不是扩展本身)

我试过这样的方法:

domain = AppDomain.CreateDomain("Test_AppDomain", 
    AppDomain.CurrentDomain.Evidence, 
    AppDomain.CurrentDomain.SetupInformation);
myCollection = domain.CreateInstanceAndUnwrap(
            typeof(MyCollection).Assembly.FullName,
            typeof(MyCollection).FullName,
            false,
            BindingFlags.Default,
            null,
            new object[] { assemblyPath }, null, null);
var processes = dte.Debugger.LocalProcesses.Cast<EnvDTE.Process>();
var currentProcess = System.Diagnostics.Process.GetCurrentProcess().Id;
var process = processes.FirstOrDefault(p => p.ProcessID == currentProcess);
process?.Attach();
var processes=dte.Debugger.localprocesss.Cast();
var currentProcess=System.Diagnostics.Process.GetCurrentProcess().Id;
var process=processs.FirstOrDefault(p=>p.ProcessID==currentProcess);
进程?.Attach();

但是
System.Diagnostics.Process.GetCurrentProcess().id
中的id似乎不存在于
localprocesss

解决这一问题的一种方法是使用一个函数生成另一个程序集,该函数将接收MethodInfo对象,只需调用System.Diagnostics.Debugger.Launch(),然后调用给定的MethodInfo,然后你所要做的就是打开程序集的函数,用你想在其中启动实际域的任何方法的信息调用它,你的好消息是,它将启用调试器,然后调用你想让它启动的方法。

即使你可能已经开始了,我发现这个问题非常有趣(与我一直在研究的内容相关)所以我尝试了一下作为一个实验-我不确定您打算如何使用事件触发
Run()
方法(即使它对您的用例很重要),所以我选择了一个简单的方法调用

注入
Debugger.Launch()
作为PoC,在将调试器启动调用传递到动态加载的方法之前,我最终会发出一个派生类并注入调试器启动调用:

public static object CreateWrapper(Type ServiceType, MethodInfo baseMethod)
{
    var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName($"newAssembly_{Guid.NewGuid()}"), AssemblyBuilderAccess.Run);
    var module = asmBuilder.DefineDynamicModule($"DynamicAssembly_{Guid.NewGuid()}");
    var typeBuilder = module.DefineType($"DynamicType_{Guid.NewGuid()}", TypeAttributes.Public, ServiceType);
    var methodBuilder = typeBuilder.DefineMethod("Run", MethodAttributes.Public | MethodAttributes.NewSlot);

    var ilGenerator = methodBuilder.GetILGenerator();

    ilGenerator.EmitCall(OpCodes.Call, typeof(Debugger).GetMethod("Launch", BindingFlags.Static | BindingFlags.Public), null);
    ilGenerator.Emit(OpCodes.Pop);

    ilGenerator.Emit(OpCodes.Ldarg_0);
    ilGenerator.EmitCall(OpCodes.Call, baseMethod, null);
    ilGenerator.Emit(OpCodes.Ret);

    /*
     * the generated method would be roughly equivalent to:
     * new void Run()
     * {
     *   Debugger.Launch();
     *   base.Run();
     * }
     */

    var wrapperType = typeBuilder.CreateType();
    return Activator.CreateInstance(wrapperType);
}
触发该方法 为加载的方法创建包装器似乎与定义动态类型和从目标类中选择正确的方法一样简单:

var wrappedInstance = DebuggerWrapperGenerator.CreateWrapper(ServiceType, ServiceType.GetMethod("Run"));
wrappedInstance.GetType().GetMethod("Run")?.Invoke(wrappedInstance, null);
转到AppDomain 上面的代码似乎不太关心代码将在何处运行,但在进行实验时,我发现,通过利用或确保我的Launcher helper是使用以下工具创建的,我能够确保代码位于正确的
AppDomain


您有权访问要加载的程序集的代码库吗?如果有,您可以将其放在
Run
方法`#的开头,如果是DEBUG System.Diagnostics.Debugger.Launch()##endif`@Vignesh.N:是的,但是目前有500多个程序集,我不想去编辑它们中的每一个。虽然这可能是我可以坚持到它们都使用的公共基类中的东西。但是,我只想在测试扩展时使用调试器。当我实际部署这些程序集时(它们是内部的)我喜欢将它们保留在调试版本中,这样当它们中断时,我就有了所有可用的调试信息。不过,这是个好主意……您是否如中所述添加了
pdb
文件?@Simon:pdb文件与正在加载的程序集存在于同一文件夹中?是的,但是对于不同的AppDomain,可能无法获取它。请检查它们是否已注册n实际调试代码。如果没有,请在调试时添加代码