¿Qué podría causar que los argumentos de P / Invoke estén fuera de orden cuando se pasan?

79

Este es un problema que ocurre específicamente en el ARM, no en x86 o x64. Un usuario me informó de este problema y pude reproducirlo usando UWP en Raspberry Pi 2 a través de Windows IoT. He visto este tipo de problema antes con convenciones de llamadas no coincidentes, pero estoy especificando Cdecl en la declaración P / Invoke e intenté agregar explícitamente __cdecl en el lado nativo con los mismos resultados. Aquí hay algo de información:

P / Declaración de invocación ( referencia ):

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

Las estructuras de C # ( referencia ):

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 función en el encabezado C ( referencia )

FLSliceResult FLEncoder_Finish(FLEncoder, FLError*);

¿FLSliceResult puede estar causando algunos problemas porque se devuelve por valor y tiene algunas cosas de C ++ en el lado nativo?

Las estructuras en el lado nativo tienen información real, pero para la API C, FLEncoder se define como un puntero opaco . Al llamar al método anterior en x86 y x64, las cosas funcionan sin problemas, pero en el ARM, observo lo siguiente. La dirección del primer argumento es la dirección del SEGUNDO argumento, y el segundo argumento es nulo (por ejemplo, cuando registro las direcciones en el lado C # obtengo, por ejemplo, 0x054f59b8 y 0x0583f3bc, pero luego en el lado nativo los argumentos son 0x0583f3bc y 0x00000000). ¿Qué podría causar este tipo de problema fuera de servicio? ¿Alguien tiene alguna idea, porque estoy perplejo ...

Aquí está el código que ejecuto para reproducir:

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

Ejecutar una aplicación C ++ con lo siguiente funciona bien:

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

Esta lógica puede desencadenar el bloqueo con la última versión del desarrollador.pero desafortunadamente aún no he descubierto cómo poder proporcionar de manera confiable símbolos de depuración nativos a través de Nuget de modo que se pueda pasar (solo compilar todo desde la fuente parece hacer eso ...) por lo que la depuración es un poco incómoda porque tanto nativa y los componentes administrados deben construirse. Estoy abierto a sugerencias sobre cómo hacer esto más fácil si alguien quiere intentarlo. Pero si alguien ha experimentado esto antes o tiene alguna idea sobre por qué sucede esto, agregue una respuesta, ¡gracias! Por supuesto, si alguien quiere un caso de reproducción (ya sea uno fácil de construir que no proporcione un paso de fuente o uno difícil de construir que sí lo haga), deje un comentario, pero no quiero pasar por el proceso de hacer uno. si nadie lo va a usar (no estoy seguro de qué tan popular es ejecutar cosas de Windows en ARM real)

EDITAR Actualización interesante: si "falsifico" la firma en C # y elimino el segundo parámetro, entonces el primero viene bien.

EDIT 2 Segunda actualización interesante: si cambio la definición de tamaño de C # FLSliceResult de UIntPtra, ulongentonces los argumentos entran correctamente ... lo cual no tiene sentido ya que size_ten ARM debería estar unsigned int.

EDITAR 3 Agregar [StructLayout(LayoutKind.Sequential, Size = 12)]a la definición en C # también hace que esto funcione, pero ¿POR QUÉ? sizeof (FLSliceResult) en C / C ++ para esta arquitectura devuelve 8 como debería. Establecer el mismo tamaño en C # provoca un bloqueo, pero establecerlo en 12 hace que funcione.

EDIT 4 Minimicé el caso de prueba para poder escribir también un caso de prueba de C ++. En C # UWP falla, pero en C ++ UWP tiene éxito.

EDITAR 5 Aquí están las instrucciones desensambladas para C ++ y C # para comparar (aunque C # no estoy seguro de cuánto tomar, así que me equivoqué al tomar demasiado)

EDITAR 6 Un análisis más detallado muestra que durante la ejecución "buena" cuando miento y digo que la estructura es de 12 bytes en C #, el valor de retorno se pasa al registro r0, y los otros dos argumentos entran a través de r1, r2. Sin embargo, en la mala ejecución, esto se cambia para que los dos argumentos ingresen a través de r0, r1 y el valor de retorno esté en otro lugar (¿puntero de pila?)

EDITAR 7 Consulté el estándar de llamada a procedimiento para la arquitectura ARM . Encontré esta cita: "Un tipo compuesto de más de 4 bytes, o cuyo tamaño no puede ser determinado estáticamente tanto por la persona que llama como por la persona que llama, se almacena en la memoria en una dirección pasada como argumento adicional cuando se llamó a la función (§5.5, regla A .4). La memoria que se utilizará para el resultado puede modificarse en cualquier momento durante la llamada a la función. " Esto implica que pasar a r0 es el comportamiento correcto, ya que un argumento adicional implica el primero (ya que la convención de llamada de C no tiene una forma de especificar el número de argumentos). Me pregunto si CLR está confundiendo esto con otra regla sobre fundamental Tipos de datos de 64 bits: "Un tipo de datos fundamentales de tamaño de palabra doble (por ejemplo, vectores en contenedores long long, double y 64 bits) se devuelve en r0 y r1".

EDITAR 8 Ok, hay mucha evidencia que apunta a que CLR está haciendo algo incorrecto aquí, así que presenté un informe de error . Espero que alguien lo note entre todos los bots automatizados que publican problemas en ese repositorio: -S.

borrrden
fuente
1
Los comentarios no son para una discusión extensa; esta conversación se ha movido al chat .
Andy
60 votos a favor y no se ha ofrecido ninguna recompensa ... eso es extraño
Mauricio Gracia Gutierrez
6
@MauricioGraciaGutierrez Supongo que podría responder esta pregunta con "esto es un error en el motor JIT" (supongo que la mayoría de las personas vienen aquí para votar porque están interesadas en la resolución del error)
borrrden
suena como un problema indio grande y pequeño ... stackoverflow.com/questions/217980/…
Proxytype
¿Se puede cerrar esta pregunta ya que parece ser un error?
huysentruitw

Respuestas:

1

El problema que presenté en GH ha estado ahí durante bastante tiempo. Creo que este comportamiento es simplemente un error y no es necesario dedicar más tiempo a investigarlo.

borrrden
fuente