neuecc / ZeroFormatter

Infinitely Fast Deserializer for .NET, .NET Core and Unity.
MIT License
2.38k stars 248 forks source link

Serialize() Crash for sure when Unity Android is built with iL2cpp #85

Open antlaw opened 6 years ago

antlaw commented 6 years ago

We use ZeroFormatter in a Unity Project. Zero Formatter will crash the app when calling ZeroFormatterSerializer.Serialize() if we choose to build with iL2cpp. There is a few observation below:

  1. When building Unity Android app with mono, the problem will not exist.
  2. When building Unity iOS app with il2cpp, the problem will not exist too.
  3. When building Unity Android app with il2cpp, the app will crash when calling ZeroFormatterSerializer.Serialize(). To be more exact, the app crashes when serializing System.DateTIme() like this: ObjectSegmentHelper.SerializeFromFormatter<TTypeResolver, global::System.DateTime>().

I attached a unity project to demonstrate the problem. I tried Unity 5.5 - 5.6.3, and also Unity 2017.1. The problem is just the same.

Actually, I tried to debug the source of zeroFormatter and finally found that the following pointer assignment in BindaryUtil.cs causes the problem:

    public static unsafe int WriteDateTime(ref byte[] bytes, int offset, DateTime dateTime)
    {
        dateTime = dateTime.ToUniversalTime();
        // Do the arithmetic using DateTime.Ticks, which is always non-negative, making things simpler.
        long secondsSinceBclEpoch = dateTime.Ticks / TimeSpan.TicksPerSecond;
        int nanoseconds = (int)(dateTime.Ticks % TimeSpan.TicksPerSecond) * Duration.NanosecondsPerTick;
        EnsureCapacity(ref bytes, offset, 12);
        fixed (byte* ptr = bytes)
        {
            *(long*)(ptr + offset) = (secondsSinceBclEpoch - Timestamp.BclSecondsAtUnixEpoch);
            *(int*)(ptr + offset + 8) = nanoseconds;
        }

        return 12;
    }

Author, would you take care of this problem? If not, would you mind giving us hint on how to solve it?

TestZeroFormatter.zip

dkozlovtsev commented 6 years ago

Can confirm, TimeSpan deserialization and Datetime serialization crash on android with il2cpp with signal 7 (SIGBUS), code 1 (BUS_ADRALN) It sounds a lot like #37

dkozlovtsev commented 6 years ago

I would also like to add that crash doesn't happen if ((offset%4) == 0) which is indication that the issue is with ARM alignment requirements. But the exact reason eludes me:

            Debug.Log("Testing long");
            for (int i = 0; i < 45; i++)
            {
                Debug.Log("Testing aligment " + i);
                Debug.Log("Writing");
                BinaryUtil.WriteInt64(ref test, i, textLong);
                Debug.Log("Reading");
                var result = BinaryUtil.ReadInt64(ref test, i);
                Debug.Log(textLong == result);
            }

            int textInt = Int32.MaxValue;
            Debug.Log("Testing int");
            for (int i = 0; i < 35; i++)
            {
                Debug.Log("Testing aligment " + i);
                Debug.Log("Writing");
                BinaryUtil.WriteInt32(ref test, i, textInt);
                Debug.Log("Reading");
                var result = BinaryUtil.ReadInt32(ref test, i);
                Debug.Log(textInt == result);
            }

Works alright for any i including those that are i%4 != 0, but

            Debug.Log("Testing timeSpan");
            for (int i = 0; i < 35; i++)
            {
                Debug.Log("Testing aligment " + i);
                Debug.Log("Writing");
                BinaryUtil.WriteTimeSpan(ref test, i, testTimeSpan);
                Debug.Log("Reading");
                var result = BinaryUtil.ReadTimeSpan(ref test, i);
                Debug.Log(testTimeSpan == result);
            }

crashes with (i == 1), at BinaryUtil.ReadTimeSpan(ref test, i);, which is weird because BinaryUtil.ReadTimeSpan is basicaly ReadInt64 + ReadInt32

My gut tells me that it is something to do with code il2cpp generates, but i'm not familiar with arm architecture enough to investigate it. For now I'll try to do if (offset%4 == 0) ... else ... logic in Write/ReadIntX as it is done in Read/WriteSingle. But @neuecc, please, if you have time look into it or at least comment if I'm on the right track here. Thanks!

yoonhwan commented 6 years ago

I am also having the same problem. You may have solved this problem, but I have bypassed the problem below.

Here is my bypassed code.(Tip)

Before DateTime code

[Index(4)]
public virtual DateTime _time{get;set;}

Bypass DateTime code

[Index(4)]
public virtual string _time{get;set;}

[IgnoreFormat]
public DateTime time {get{ return DateTime.ParseExact(_time, "yyyyMMddHHmmssFFF", null); }}

Insert Data

_time = DateTime.Now.ToString("yyyyMMddHHmmssFFF");

If this is solved, please let me know. thanks.