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

时间:2016-10-05 15:02:17

标签: c# debugging vsix

我正在开发Visual Studio扩展程序,其中一个功能会创建一个新的应用程序域并将程序集加载到该应用程序域中。然后它在app域中运行一些函数。我想做的事情,并且不确定是否可能,是否有我的扩展程序将调试器附加到新应用程序域中运行的代码,因此当该代码失败时,我实际上可以看到&# 39;继续现在我飞行失明并调试动力装载组件是一件痛苦的事。

所以我有一个创建我的app域的类:

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);

MyCollection在其构造函数中执行类似的操作:

_assembly = Assembly.LoadFrom(assemblyPath);

现在,自从Test_AppDomain对象在该域中创建以来,该程序集已加载到MyCollection。并且它需要能够将调试器连接到。

在某些时候myCollection会创建一个对象的实例并挂钩一些事件:

currentObject = Activator.CreateInstance(objectType) as IObjectBase;
proxy.RunRequested += (o, e) => { currentObject?.Run(); };

基本上我拥有RunRequested的处理程序且它运行currentObject?.Run(),我希望附加一个调试器,虽然它可能不是一个问题(实际上可能更好)如果之前附加了调试器。

那么有没有办法实现这个目标?当用户触发将导致在调用的新AppDomain中创建的对象的Run函数的事件时,是否可以以编程方式附加调试器?如何附加调试器(而不是扩展本身)?

我试过这样的事情:

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();

但似乎来自System.Diagnostics.Process.GetCurrentProcess().Id的{​​{1}}中的ID不存在?

2 个答案:

答案 0 :(得分:0)

解决此问题的一种方法是生成另一个程序集,该程序集将带有一个MethodInfo对象,并只需调用System.Diagnostics.Debugger.Launch()然后调用给定的MethodInfo,然后要做的就是将该程序集的函数解包,并使用您要在其中启动实际域的任何方法信息来调用它,这很好,它将启用调试器,然后调用您要在其中启动的方法。

答案 1 :(得分:0)

即使您可能已经继续前进,我也发现该问题非常着迷(并且与我对blog about的研究有关)。因此,我作了一个实验实验-我不确定您打算如何使用事件来触发Run()方法(即使它对您的用例来说很重要),所以我选择了一个简单的方法调用。

注入Debugger.Launch()

作为PoC,我最终将IL发射出派生类并注入调试器启动调用,然后再将其传递给动态加载的方法:

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

上面的代码似乎不太关心代码将在何处运行,但是在进行实验时,我发现我可以通过利用{{3 }}或确保我的启动器助手是使用.DoCallBack()创建的:

AppDomain
public class Program
{
    const string PathToDll = @"..\..\..\ClassLibrary1\bin\Debug\ClassLibrary1.dll";

    static void Main(string[] args)
    {
        var appDomain = AppDomain.CreateDomain("AppDomainInMain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);

        appDomain.DoCallBack(() =>
        {
            var launcher = new Launcher(PathToDll);
            launcher.Run();
        });
    }
}