2017-06-19 62 views
79

这是一个特别在ARM上发生的问题,而不是在x86或x64上。我遇到了用户报告的这个问题,并且能够通过Windows IoT在Raspberry Pi 2上使用UWP进行重现。我之前在调用约定时遇到过这种问题,但我在P/Invoke声明中指定了Cdecl,并且试图在本机端显式添加__cdecl,并得到相同的结果。下面是一些信息:什么可能导致P/Invoke参数传递失序?

的P/Invoke声明(reference):

[DllImport(Constants.DllName, CallingConvention = CallingConvention.Cdecl)] 
public static extern FLSliceResult FLEncoder_Finish(FLEncoder* encoder, FLError* outError); 

C#的结构(reference):

internal unsafe partial struct FLSliceResult 
{ 
    public void* buf; 
    private UIntPtr _size; 

    public ulong size 
    { 
     get { 
      return _size.ToUInt64(); 
     } 
     set { 
      _size = (UIntPtr)value; 
     } 
    } 
} 

internal enum FLError 
{ 
    NoError = 0, 
    MemoryError, 
    OutOfRange, 
    InvalidData, 
    EncodeError, 
    JSONError, 
    UnknownValue, 
    InternalError, 
    NotFound, 
    SharedKeysStateError, 
} 

internal unsafe struct FLEncoder 
{ 
} 

在C标题中的功能(reference

FLSliceResult FLEncoder_Finish(FLEncoder, FLError*); 

FLSliceResult可能会导致一些问题,因为我t是由值返回的,并且在本机端有一些C++的东西?

本机端的结构具有实际的信息,但对于C API,FLEncoder定义为as an opaque pointer。当在x86和x64上调用上面的方法时,情况很顺利,但在ARM上,我观察到以下情况。第一个参数的地址是SECOND参数的地址,第二个参数是空的(例如,当我登录C#端的地址时,例如,0x054f59b8和0x0583f3bc,但在本机端参数是0x0583f3bc和0x00000000)。什么会导致这种乱序问题?没有人有任何的想法,因为我难倒...

这是我跑重现代码:

unsafe { 
    var enc = Native.FLEncoder_New(); 
    Native.FLEncoder_BeginDict(enc, 1); 
    Native.FLEncoder_WriteKey(enc, "answer"); 
    Native.FLEncoder_WriteInt(enc, 42); 
    Native.FLEncoder_EndDict(enc); 
    FLError err; 
    NativeRaw.FLEncoder_Finish(enc, &err); 
    Native.FLEncoder_Free(enc); 
} 

运行以下配置的C++应用程序正常工作:

auto enc = FLEncoder_New(); 
FLEncoder_BeginDict(enc, 1); 
FLEncoder_WriteKey(enc, FLSTR("answer")); 
FLEncoder_WriteInt(enc, 42); 
FLEncoder_EndDict(enc); 
FLError err; 
auto result = FLEncoder_Finish(enc, &err); 
FLEncoder_Free(enc); 

这种逻辑可以触发最新的developer build的崩溃,但不幸的是,我还没有想出如何可靠地通过Nuget提供本地调试符号,以便它可以跨越(仅从源代码构建所有东西似乎是这样做的...... )所以调试有点awkwar因为需要构建本地和托管组件。如果有人想尝试,我很乐意提供如何使这更容易的建议。但是,如果有人以前经历过这种情况,或者对此有何看法,请添加答案,谢谢!当然,如果任何人想要再生产的情况下(不是简单地建立一个不提供源代码或难以建立源代码的代码),那么请留下评论,但我不想通过制作一个如果没有人会使用它(我不确定在实际的ARM上运行Windows内容有多受欢迎)

编辑有趣的更新:如果我在C#中“伪造”签名并删除第二个参数,那么第一个通过确定。

EDIT 2第二个有趣的更新:如果我改变大小的C#FLSliceResult定义从UIntPtrulong那么参数进来正确......这是没有意义的,因为size_t ARM的应该是unsigned int类型。

编辑3添加[StructLayout(LayoutKind.Sequential, Size = 12)]到C#中的定义也使这项工作,但为什么?C/C++中针对此体系结构的sizeof(FLSliceResult)会返回8,因为它应该如此。在C#中设置相同的大小会导致崩溃,但将其设置为12会使其工作。

编辑4我简化了测试用例,以便我也可以编写C++测试用例。在C#UWP中失败,但在C++ UWP中成功。

EDIT 5Here是C++和C#的汇编指令作比较(虽然C#我不知道有多少拿,所以我错在服用侧太多)

编辑6进一步的分析表明,在我说谎并且说C#中的结构是12个字节的“良好”运行期间,返回值被传递到寄存器r0,其他两个参数通过r1,r2进入。然而,在恶劣的运行,这种转移在这样两个ARG游戏通过R0,R1和返回值进来的是别的地方(堆栈指针?)

编辑7我咨询了Procedure Call Standard for the ARM Architecture。我发现这个引用:“大于4字节的复合类型,或者其大小不能由调用者和被调用者静态确定的大小,在调用该函数时作为额外参数传递的地址中存储在内存中(§5.5, 规则A.4),在函数调用期间的任何点都可以修改用于结果的内存。“这意味着传入r0是正确的行为,因为额外的参数意味着第一个参数(因为C调用约定没有指定参数个数的方法)。我想知道CLR是否将这与另一个关于基本原理的规则混淆在一起:64位数据类型:“双字大小的基本数据类型(例如,long long,double和64位容器化矢量)是 在r0中返回并且R1“。

编辑8好吧,有很多证据指出CLR在这里做错了事,所以我提交了一份bug report。我希望有人注意到所有自动化机器人在该回购站发布问题:-S。

+1

评论不适用于扩展讨论;这个对话已经[转移到聊天](http://chat.stackoverflow.com/rooms/157727/discussion-on-question-by-borrrden-what-c​​ould-cause-p-invoke-arguments-to-be-出)。 – Andy

+0

60 upvotes并没有提供赏金......这很奇怪 –

+6

@MauricioGraciaGutierrez我想我可以用“这是一个JIT引擎中的错误”来回答这个问题(我假设大多数人来这里是为了upvote,因为他们对该bug的解决方案 – borrrden

回答

1

我提交给GH的问题在这里已经有很长一段时间了。我相信这种行为只是一个错误,不需要花费更多的时间来研究它。

相关问题