什么会导致P / Invoke参数传递时混乱?


79

这是专门在ARM而不是x86或x64上发生的问题。我有一个用户报告此问题,并能够通过Windows IoT在Raspberry Pi 2上使用UWP再现它。我以前用不匹配的调用约定已经看到过这种问题,但是我在P / Invoke声明中指定了Cdecl,我尝试在本机端显式添加__cdecl并获得相同的结果。这里是一些信息:

P /调用声明(参考):

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

C#结构(参考):

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头文件中的函数(参考

FLSliceResult FLEncoder_Finish(FLEncoder, FLError*);

FLSliceResult可能会引起一些问题,因为它是按值返回的,并且在本机端有一些C ++的东西?

本机端的结构具有实际信息,但是对于C API,FLEncoder被定义为不透明指针。当在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);

此逻辑可以使用最新的开发人员版本触发崩溃但不幸的是,我还没有弄清楚如何能够通过Nuget可靠地提供本机调试符号,以便可以逐步执行它(仅从源代码构建所有内容似乎都可以做到这一点...),因此调试有点尴尬,因为这两种本机和托管组件需要构建。我愿意接受有关如何使此操作更容易的建议,即使有人想尝试。但是,如果有人以前曾经历过此事或对为什么会发生有任何想法,请添加答案,谢谢!当然,如果有人想要一个复制案例(要么易于构建而不提供源代码步进,要么很难构建能够提供源代码步进),然后发表评论,但我不想经历一个复制案例。如果没有人要使用它(我不确定在实际的ARM上运行Windows的流行程度)

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

编辑2第二个有趣的更新:如果我将C#FLSliceResult的size定义从更改为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中它成功。

编辑5 是C ++和C#的反汇编指令,以供比较(尽管C#我不确定要服用多少,所以我错了太多)

编辑6进一步的分析表明,当我撒谎并说C#中的struct为12字节时,在“良好”运行期间,返回值传递到寄存器r0,其他两个args通过r1,r2进入。但是,在糟糕的情况下,这会转移,以便两个args通过r0,r1进入,并且返回值在其他地方(堆栈指针?)。

编辑7我咨询了ARM体系结构过程调用标准。我发现这句话:“大于4个字节的复合类型,或者其大小不能由调用方和被调用方静态确定,在调用该函数时,该类型存储在内存中作为附加参数传递的地址(第5.5节,规则A .4)。在函数调用期间的任何时候都可以修改用于结果的存储器。” 这意味着传递到r0是正确的行为,因为多余的参数意味着第一个参数(因为C调用约定没有指定参数数量的方法)。我想知道CLR是否将其与关于基本原则的另一条规则相混淆 64位数据类型:“在r0和r1中返回双字大小的基本数据类型(例如,long long,double和64位容器化向量)。”

编辑8好吧,这里有很多证据表明CLR做错了事,所以我提交了一个错误报告。我希望有人能注意到所有在该仓库上发布问题的自动机器人:-S。


1
评论不作进一步讨论;此对话已转移至聊天
安迪

60票赞成票,没有提供赏金……这很奇怪
Mauricio Gracia Gutierrez

6
@MauricioGraciaGutierrez我想我可以用“这是JIT引擎中的错误”回答这个问题(我想大多数人来这里投票是因为他们对错误的解决感兴趣)
borrrden

听起来像是印度的大问题和小问题... stackoverflow.com/questions/217980/…–
Proxytype

这个问题似乎可以解决吗?
huysentruitw

Answers:


1

我在GH上提出的问题已经存在了很长时间。我相信这种行为仅仅是一个错误,无需花更多的时间来研究它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.