Was kann dazu führen, dass P / Invoke-Argumente beim Bestehen nicht in Ordnung sind?

79

Dies ist ein Problem, das speziell auf dem ARM auftritt, nicht auf x86 oder x64. Ich hatte dieses Problem von einem Benutzer gemeldet und konnte es mit UWP auf Raspberry Pi 2 über Windows IoT reproduzieren. Ich habe diese Art von Problem bereits bei nicht übereinstimmenden Aufrufkonventionen gesehen, aber ich gebe Cdecl in der P / Invoke-Deklaration an und habe versucht, __cdecl auf der nativen Seite explizit mit denselben Ergebnissen hinzuzufügen. Hier einige Infos:

P / Invoke-Erklärung ( Referenz ):

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

Die C # -Strukturen ( Referenz ):

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
{
}

Die Funktion im C-Header ( Referenz )

FLSliceResult FLEncoder_Finish(FLEncoder, FLError*);

FLSliceResult kann einige Probleme verursachen, da es vom Wert zurückgegeben wird und auf der nativen Seite einige C ++ - Inhalte enthält.

Die Strukturen auf der nativen Seite enthalten aktuelle Informationen, aber für die C-API ist FLEncoder als undurchsichtiger Zeiger definiert . Beim Aufrufen der obigen Methode unter x86 und x64 funktionieren die Dinge reibungslos, aber auf dem ARM beobachte ich Folgendes. Die Adresse des ersten Arguments ist die Adresse des zweiten Arguments, und das zweite Argument ist null (z. B. wenn ich die Adressen auf der C # -Seite protokolliere, erhalte ich beispielsweise 0x054f59b8 und 0x0583f3bc, aber dann auf der nativen Seite die Argumente sind 0x0583f3bc und 0x00000000). Was könnte ein solches Problem verursachen? Hat jemand irgendwelche Ideen, weil ich ratlos bin ...

Hier ist der Code, den ich zum Reproduzieren ausführe:

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);
}

Das Ausführen einer C ++ - App mit den folgenden Funktionen funktioniert einwandfrei:

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);

Diese Logik kann den Absturz mit dem neuesten Entwickler-Build auslösenLeider habe ich noch nicht herausgefunden, wie ich native Debug-Symbole zuverlässig über Nuget bereitstellen kann, sodass sie schrittweise ausgeführt werden können (nur das Erstellen von Daten aus der Quelle scheint dies zu tun ...). Daher ist das Debuggen etwas umständlich, da beide native sind und verwaltete Komponenten müssen erstellt werden. Ich bin offen für Vorschläge, wie dies einfacher gemacht werden kann, wenn jemand es versuchen möchte. Aber wenn jemand dies schon einmal erlebt hat oder Ideen dazu hat, fügen Sie bitte eine Antwort hinzu, danke! Natürlich, wenn jemand einen Reproduktionsfall haben möchte (entweder einen einfach zu erstellenden, der kein Quellenschritt bietet, oder einen schwer zu erstellenden, der dies tut), dann hinterlasse einen Kommentar, aber ich möchte nicht den Prozess des Erstellens eines Falles durchlaufen wenn niemand es verwenden wird (ich bin mir nicht sicher, wie beliebt das Ausführen von Windows-Inhalten auf ARM ist)

BEARBEITEN Interessantes Update: Wenn ich die Signatur in C # "fälsche" und den 2. Parameter entferne, kommt der erste durch OK.

EDIT 2 Zweites interessantes Update: Wenn ich die C # FLSliceResult-Definition der Größe von UIntPtrauf ändere , werden ulongdie Argumente korrekt eingegeben ... was keinen Sinn macht, da size_tauf ARM int ohne Vorzeichen sein sollte.

BEARBEITEN 3 Durch Hinzufügen [StructLayout(LayoutKind.Sequential, Size = 12)]zur Definition in C # funktioniert dies ebenfalls, aber WARUM? sizeof (FLSliceResult) in C / C ++ für diese Architektur gibt 8 zurück, wie es sollte. Das Festlegen der gleichen Größe in C # führt zu einem Absturz, aber das Festlegen auf 12 funktioniert.

EDIT 4 Ich habe den Testfall minimiert, damit ich auch einen C ++ - Testfall schreiben kann. In C # UWP schlägt dies fehl, in C ++ UWP ist dies jedoch erfolgreich.

EDIT 5 Hier sind die zerlegten Anweisungen für C ++ und C # zum Vergleich (obwohl C # ich nicht sicher bin, wie viel ich nehmen soll, habe ich mich geirrt, zu viel zu nehmen).

EDIT 6 Eine weitere Analyse zeigt, dass während des "guten" Laufs, wenn ich lüge und sage, dass die Struktur 12 Bytes in C # ist, der Rückgabewert an das Register r0 übergeben wird, wobei die anderen beiden Argumente über r1, r2 eingehen. Im schlechten Lauf wird dies jedoch verschoben, sodass die beiden Argumente über r0, r1 eingehen und der Rückgabewert an einer anderen Stelle liegt (Stapelzeiger?).

EDIT 7 Ich habe den Procedure Call Standard für die ARM-Architektur konsultiert . Ich fand dieses Zitat: "Ein zusammengesetzter Typ, der größer als 4 Bytes ist oder dessen Größe nicht sowohl vom Anrufer als auch vom Angerufenen statisch bestimmt werden kann, wird im Speicher an einer Adresse gespeichert, die beim Aufrufen der Funktion als zusätzliches Argument übergeben wurde (§5.5, Regel A.) .4). Der für das Ergebnis zu verwendende Speicher kann zu jedem Zeitpunkt während des Funktionsaufrufs geändert werden. " Dies impliziert, dass die Übergabe an r0 das richtige Verhalten ist, da ein zusätzliches Argument das erste impliziert (da die C-Aufrufkonvention keine Möglichkeit hat, die Anzahl der Argumente anzugeben). Ich frage mich , ob die CLR ist verwirrend dies mit einer anderen Regel über grundlegende 64-Bit-Datentypen: "Ein grundlegender Datentyp mit doppelter Wortgröße (z. B. lange lange, doppelte und 64-Bit-Containervektoren) wird in r0 und r1 zurückgegeben."

EDIT 8 Ok, es gibt viele Hinweise darauf, dass die CLR hier das Falsche tut, also habe ich einen Fehlerbericht eingereicht . Ich hoffe, jemand bemerkt es zwischen all den automatisierten Bots, die Probleme auf diesem Repo posten: -S.

borrrden
quelle
1
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Andy
60 Upvotes und kein Kopfgeld wurde angeboten ... das ist komisch
Mauricio Gracia Gutierrez
6
@ MauricioGraciaGutierrez Ich nehme an, ich könnte diese Frage mit "Dies ist ein Fehler in der JIT-Engine" beantworten (ich gehe davon aus, dass die meisten Leute hierher kommen, um zu stimmen, weil sie an der Lösung des Fehlers interessiert sind)
borrrden
klingt wie ein großes und kleines indisches Problem ... stackoverflow.com/questions/217980/…
Proxytype
Kann diese Frage geschlossen werden, da es sich um einen Fehler handelt?
Huysentruitw

Antworten:

1

Das Problem, das ich bei GH eingereicht habe, sitzt schon seit einiger Zeit dort. Ich glaube, dass dieses Verhalten einfach ein Fehler ist und keine Zeit mehr damit verbracht werden muss, es zu untersuchen.

borrrden
quelle