2017-06-19 61 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的問題在這裏已經有很長一段時間了。我相信這種行爲只是一個錯誤,不需要花費更多的時間來研究它。

相關問題