Cross-AppDomain调用会破坏运行时

Ill*_*ack 15 .net c# remoting serialization appdomain

这本来是一个更冗长的问题,但现在我构建了一个较小的可用示例代码,因此原始文本不再相关.

我有两个项目,一个包含一个没有成员的结构,名为TestType.该项目由主项目引用,但程序集不包含在可执行文件目录中.主项目创建一个新的app-domain,它使用所包含程序集的名称注册AssemblyResolve事件.在主app-domain中,处理相同的事件,但它手动从项目资源加载程序集.

然后新的app-domain构建自己的TestType版本,但字段多于原始字段.主app-domain使用虚拟版本,新app-domain使用生成的版本.

当调用 在其签名中具有TestType的方法时(即使只是简单地返回它就足够了),它似乎只会使运行时不稳定并破坏内存.

我使用的是.NET 4.5,在x86下运行.

DummyAssembly:

using System;

[Serializable]
public struct TestType
{

}
Run Code Online (Sandbox Code Playgroud)

主要项目:

using System;
using System.Reflection;
using System.Reflection.Emit;

internal sealed class Program
{
    [STAThread]
    private static void Main(string[] args)
    {
        Assembly assemblyCache = null;

        AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs rargs)
        {
            var name = new AssemblyName(rargs.Name);
            if(name.Name == "DummyAssembly")
            {
                return assemblyCache ?? (assemblyCache = TypeSupport.LoadDummyAssembly(name.Name));
            }
            return null;
        };

        Start();
    }

    private static void Start()
    {
        var server = ServerObject.Create();

        //prints 155680
        server.TestMethod1("Test");
        //prints 0
        server.TestMethod2("Test");
    }
}

public class ServerObject : MarshalByRefObject
{
    public static ServerObject Create()
    {
        var domain = AppDomain.CreateDomain("TestDomain");
        var t = typeof(ServerObject);
        return (ServerObject)domain.CreateInstanceAndUnwrap(t.Assembly.FullName, t.FullName);
    }

    public ServerObject()
    {
        Assembly genAsm = TypeSupport.GenerateDynamicAssembly("DummyAssembly");

        AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs rargs)
        {
            var name = new AssemblyName(rargs.Name);
            if(name.Name == "DummyAssembly")
            {
                return genAsm;
            }
            return null;
        };
    }

    public TestType TestMethod1(string v)
    {
        Console.WriteLine(v.Length);
        return default(TestType);
    }

    public void TestMethod2(string v)
    {
        Console.WriteLine(v.Length);
    }
}

public static class TypeSupport
{
    public static Assembly LoadDummyAssembly(string name)
    {
        var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name);
        if(stream != null)
        {
            var data = new byte[stream.Length];
            stream.Read(data, 0, data.Length);
            return Assembly.Load(data);
        }
        return null;
    }

    public static Assembly GenerateDynamicAssembly(string name)
    {
        var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
            new AssemblyName(name), AssemblyBuilderAccess.Run
        );

        var mod = ab.DefineDynamicModule(name+".dll");

        var tb = GenerateTestType(mod);

        tb.CreateType();

        return ab;
    }

    private static TypeBuilder GenerateTestType(ModuleBuilder mod)
    {
        var tb = mod.DefineType("TestType", TypeAttributes.Public | TypeAttributes.Serializable, typeof(ValueType));

        for(int i = 0; i < 3; i++)
        {
            tb.DefineField("_"+i.ToString(), typeof(int), FieldAttributes.Public);
        }

        return tb;
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然TestMethod1TestMethod2都应该打印4,但第一个访问内存的一些奇怪的部分,并且似乎足以破坏调用堆栈以影响对第二个方法的调用.如果我删除对第一种方法的调用,一切都很好.

如果我在x64下运行代码,第一个方法抛出NullReferenceException.

两种结构的字段数量似乎很重要.如果第二个结构总数大于第一个结构(如果我只生成一个字段或没有),那么一切也可以正常工作,如果DummyAssembly中的结构包含更多字段,则相同.这让我相信JITter会错误地编译方法(不使用生成的程序集),或者调用方法的不正确的本机版本.我已经检查过typeof(TestType)返回该类型的正确(生成)版本.

总而言之,我没有使用任何不安全的代码,所以这不应该发生.

gho*_*ord 2

我能够使用最新的框架在我的机器上重现这个问题。

我在默认应用程序域的程序集解析中添加了版本检查:

if (name.Name == "DummyAssembly" && name.Version.Major == 1)
Run Code Online (Sandbox Code Playgroud)

我得到以下异常:

System.Runtime.Serialization.SerializationException: Cannot find assembly 'DummyAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'



Server stack trace:
   w System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly()
   w System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetType(BinaryAssemblyInfo assemblyInfo, String name)
   w System.Runtime.Serialization.Formatters.Binary.ObjectMap..ctor(String objectName, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable)
   w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record)
   w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum)
   w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
   w System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   w System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   w System.Runtime.Remoting.Channels.CrossAppDomainSerializer.DeserializeObject(MemoryStream stm)
   w System.Runtime.Remoting.Messaging.SmuggledMethodReturnMessage.FixupForNewAppDomain()
   w System.Runtime.Remoting.Channels.CrossAppDomainSink.SyncProcessMessage(IMessage reqMsg)

Exception rethrown at [0]:
   w System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   w System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   w ServerObject.TestMethod1(TestType& result, String v)
Run Code Online (Sandbox Code Playgroud)

这里使用二进制格式化程序进行封送,它从不同的 AppDomain 中查找不同大小的值类型。请注意,当您调用 时,它会尝试加载您的DummyAssembly版本,并且您将之前缓存的虚拟版本传递给它,其中具有不同的大小。0.0.0.0TestMethod11.0.0.0TestType

由于结构的大小不同,当您从方法中按值返回时,之间的封送会出现问题,AppDomains并且堆栈会变得不平衡(可能是运行时中的错误?)。通过引用返回似乎没有问题(引用的大小始终相同)。

使两个程序集中的结构大小相等/通过引用返回应该可以解决此问题。