web-dev-qa-db-fra.com

Qu'est-ce qui pourrait provoquer le désordre des arguments P / Invoke une fois passés?

Il s'agit d'un problème qui se produit spécifiquement sur l'ARM, pas sur x86 ou x64. J'ai eu ce problème signalé par un utilisateur et j'ai pu le reproduire en utilisant UWP sur Raspberry Pi 2 via Windows IoT. J'ai déjà vu ce genre de problème avec des conventions d'appel incompatibles, mais je spécifie Cdecl dans la déclaration P/Invoke et j'ai essayé d'ajouter explicitement __cdecl du côté natif avec les mêmes résultats. Voici quelques infos:

Déclaration P/Invoke ( référence ):

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

Les structures C # ( référence ):

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

La fonction dans l'en-tête C ( référence )

FLSliceResult FLEncoder_Finish(FLEncoder, FLError*);

FLSliceResult peut être à l'origine de certains problèmes car il est renvoyé par valeur et contient des éléments C++ du côté natif?

Les structures du côté natif ont des informations réelles, mais pour l'API C, FLEncoder est défini comme un pointeur opaque . Lors de l'appel de la méthode ci-dessus sur x86 et x64, les choses fonctionnent bien, mais sur l'ARM, j'observe ce qui suit. L'adresse du premier argument est l'adresse du SECOND argument, et le deuxième argument est nul (par exemple, lorsque je connecte les adresses du côté C #, j'obtiens, par exemple, 0x054f59b8 et 0x0583f3bc, mais ensuite du côté natif les arguments sont 0x0583f3bc et 0x00000000). Qu'est-ce qui pourrait causer ce genre de problème de panne? Quelqu'un a-t-il des idées, parce que je suis perplexe ...

Voici le code que je lance pour reproduire:

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

L'exécution d'une application C++ avec les éléments suivants fonctionne correctement:

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

Cette logique peut déclencher le crash avec la dernière build développeur mais malheureusement je n'ai pas encore trouvé comment être en mesure de fournir de manière fiable des symboles de débogage natifs via Nuget de telle sorte qu'il puisse être traversé (uniquement en construisant tout à partir de source semble faire ça ...) donc le débogage est un peu gênant car les composants natifs et gérés doivent être construits. Je suis ouvert aux suggestions sur la façon de rendre cela plus facile si quelqu'un veut essayer. Mais si quelqu'un a déjà vécu cela ou a une idée de pourquoi cela se produit, veuillez ajouter une réponse, merci! Bien sûr, si quelqu'un veut un cas de reproduction (soit facile à construire qui ne fournit pas de step source ou difficile à construire qui le fasse) alors laissez un commentaire mais je ne veux pas passer par le processus de fabrication d'un si personne ne va l'utiliser (je ne suis pas sûr de la popularité de l'exécution de Windows sur le ARM is)

[~ # ~] modifier [~ # ~] Mise à jour intéressante: si je "fausse" la signature en C # et que je supprime le 2ème paramètre, puis le premier on passe par OK.

EDIT 2 Deuxième mise à jour intéressante: si je change la définition de taille C # FLSliceResult de UIntPtr en ulong puis les arguments entrer correctement ... ce qui n'a pas de sens puisque size_t sur ARM devrait être non signé int.

EDIT 3 L'ajout de [StructLayout(LayoutKind.Sequential, Size = 12)] à la définition en C # fait également fonctionner cela, mais POURQUOI? sizeof (FLSliceResult) en C/C++ pour cette architecture renvoie 8 comme il se doit. La définition de la même taille en C # provoque un crash, mais sa définition sur 12 le fait fonctionner.

EDIT 4 J'ai minimisé le scénario de test afin de pouvoir également écrire un scénario de test C++. En C # UWP, il échoue, mais en C++ UWP, il réussit.

EDIT 5 Ici sont les instructions désassemblées pour C++ et C # pour comparaison (bien que C # je ne sais pas combien de prenez donc je me suis trompé du côté d'en prendre trop)

EDIT 6 Une analyse plus approfondie montre que pendant la "bonne" exécution quand je mens et que la structure fait 12 octets en C #, la valeur de retour est passée à enregistrer r0, les deux autres arguments venant via r1, r2. Cependant, lors d'une mauvaise exécution, cela est décalé afin que les deux arguments arrivent via r0, r1 et que la valeur de retour soit ailleurs (pointeur de pile?)

EDIT 7 J'ai consulté le Procedure Call Standard for the ARM Architecture . J'ai trouvé ceci quote: "Un type composite supérieur à 4 octets, ou dont la taille ne peut pas être déterminée statiquement par l'appelant et l'appelé, est stocké en mémoire à une adresse transmise comme argument supplémentaire lors de l'appel de la fonction (§5.5, règle A.4) . La mémoire à utiliser pour le résultat peut être modifiée à tout moment pendant l'appel de fonction. "Cela implique que le passage à r0 est le comportement correct car l'argument supplémentaire implique le premier (puisque la convention d'appel C n'a pas de moyen de spécifiez le nombre d'arguments.) Je me demande si le CLR confond cela avec une autre règle concernant les types de données 64 bits fondamentaux : "Un mot double Les types de données fondamentaux (par exemple, les vecteurs conteneurisés long long, double et 64 bits) sont renvoyés dans r0 et r1. "

EDIT 8 Ok il y a beaucoup de preuves montrant que le CLR fait la mauvaise chose ici, donc j'ai déposé un rapport de bogue . J'espère que quelqu'un le remarquera entre tous les bots automatisés affichant des problèmes sur ce dépôt: -S.

79
borrrden

Le problème que j'ai déposé sur GH existe depuis un certain temps. Je crois que ce comportement est simplement un bug et qu'il n'y a plus de temps à y consacrer.

1
borrrden