记录一个Unity中unsafe代码在iOS下的bug

Bug记录

问题是这样发生的:

为了将两个对象合并成一条记录,我将两个对象的int型id合并成了一个long型id,结果在解析这个long型id时,得到的并不是我期望的结果。

public static void Splite(long key, out int a, out int b)
{
    unsafe
    {
        int* p = (int*)&key;
        a = *p++;
        b = *p;
    }
}

一切都看上去很正常,平时运行,打包安卓都很正常,直到我们打了iOS包,发现基于它的功能都失效了:返回去的值a与b,总是与预期的不一致(最终排查发现是b总返回一些神奇的值)。

为了验证问题,我们专门使用XCode打了Debug包,并且因为我们使用的是IL2CPP方案,所以专门深入IL2CPP对应代码中进行断点。神奇的事又发生了:一切都变好了。

问题排查

其实进行到这一步,已经可以大致推测出是因为XCode使用Clang进行Release打包,导致这段代码出现了问题。这里展示IL2CPP代码如下:

IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void Splite (int64_t ___key0, int32_t* ___a1, int32_t* ___b2, const RuntimeMethod* method) 
{
    int32_t* V_0 = NULL;
    {
        // int* p = (int*)&key;
        V_0 = (int32_t*)((uintptr_t)(&___key0));
        // a = *p++;
        int32_t* L_0 = ___a1;
        int32_t* L_1 = V_0;
        int32_t* L_2 = L_1;
        V_0 = ((int32_t*)il2cpp_codegen_add((intptr_t)L_2, 4));
        int32_t L_3 = *((int32_t*)L_2);
        *((int32_t*)L_0) = (int32_t)L_3;
        // b = *p;
        int32_t* L_4 = ___b2;
        int32_t* L_5 = V_0;
        int32_t L_6 = *((int32_t*)L_5);
        *((int32_t*)L_4) = (int32_t)L_6;
        // }
        return;
    }
}

观察这段代码,似乎找不到有什么会被Release优化导致___a1___b2返回的值甚至指针与预期不符的情况。所以我又单独建立了一个纯C++的命令行工程,在CLion中使用CLang进行Release编译,以验证问题究竟出在哪里。

经过我本地的排查,一切的问题指向了这一句:

V_0 = ((int32_t*)il2cpp_codegen_add((intptr_t)L_2, 4));

如果我们把这一句直接替换为

V_0++;

那么无论Debug或Release编译,都可以得到我们预期的结果值。

其实根据经验,我们也可以猜得出,就是在V_0修改之后,如果立即有一次调用,也可以修正这个问题。我试着在这里单纯加了一个输出语句将V_0输出,计算结果也正确了。

本来还想通过反编译,看看这里的被Release之后究竟变成了什么,可碍于经验和经历,最终没有这么做,也希望有经验的小伙伴可以试着按这个思路继续下去,看看CLang在这里究竟对Release做了怎样的优化,才会导致出现了这个问题。

关于这个bug的建议

简单粗暴版:在C#中,不要用unsafe,不要用指针

谨小慎微版:unsafe实现的代码,最好单独拿出在CLang编译环境下测试验证,没问题再添加到工程中。

后续补充

其实我后来有把这个方法修改使其能够正常运行。

public static (int, int) Splite(long key)
{
    unsafe
    {
        int* p = (int*)&key;
        int a = *p++;
        int b = *p;
        return (a, b);
    }
}

其实乍看之下,这与原来的方法变化并不大,但是IL2CPP却还是有一些差别的。

IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR ValueTuple Splite (int64_t ___key0, const RuntimeMethod* method) 
{
    static bool s_Il2CppMethodInitialized;
    if (!s_Il2CppMethodInitialized)
    {
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&ValueTuple_2__ctor_mF5D8FB18DBF2C4B2F879F8E8E12D8FB8FCDB5477_RuntimeMethod_var);
        s_Il2CppMethodInitialized = true;
    }
    int32_t* V_0 = NULL;
    int32_t V_1 = 0;
    {
        // int* p = (int*)&key;
        V_0 = (int32_t*)((uintptr_t)(&___key0));
        // int a = *p++;
        int32_t* L_0 = V_0;
        int32_t* L_1 = L_0;
        V_0 = ((int32_t*)il2cpp_codegen_add((intptr_t)L_1, 4));
        int32_t L_2 = *((int32_t*)L_1);
        // int b = *p;
        int32_t* L_3 = V_0;
        int32_t L_4 = *((int32_t*)L_3);
        V_1 = L_4;
        // return (a, b);
        int32_t L_5 = V_1;
        ValueTuple_2_t973F7AB0EF5DD3619E518A966941F10D8098F52D L_6;
        memset((&L_6), 0, sizeof(L_6));
        ValueTuple_2__ctor_mF5D8FB18DBF2C4B2F879F8E8E12D8FB8FCDB5477((&L_6), L_2, L_5, /*hidden argument*/ValueTuple_2__ctor_mF5D8FB18DBF2C4B2F879F8E8E12D8FB8FCDB5477_RuntimeMethod_var);
        return L_6;
    }
}

寻找差别,似乎可以从IL入手,所以我又扒了它们的IL代码,发现虽然实现很相似,但它们的IL还是有细微差别。

它们的IL差别主要集中在这几句上:

a = *p++;   // line1
b = *p;     // line2

针对line1,原始版本的IL如下:

    IL_0006: ldarg.1      // a
    IL_0007: ldloc.0      // p
    IL_0008: dup
    IL_0009: ldc.i4.4
    IL_000a: add
    IL_000b: stloc.0      // p
    IL_000c: ldind.i4
    IL_000d: stind.i4

而新版本IL如下:

    IL_0006: ldloc.0      // p
    IL_0007: dup
    IL_0008: ldc.i4.4
    IL_0009: add
    IL_000a: stloc.0      // p
    IL_000b: ldind.i4
    IL_000c: stloc.1      // a

针对line2,原始版本的IL如下:

    IL_000e: ldarg.2      // b
    IL_000f: ldloc.0      // p
    IL_0010: ldind.i4
    IL_0011: stind.i4

而新版本IL如下:

    IL_000d: ldloc.0      // p
    IL_000e: ldind.i4
    IL_000f: stloc.2      // b
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容