Punteros desalineados en x86

11 minutos de lectura

¿Alguien puede dar un ejemplo en el que la conversión de un puntero de un tipo a otro falla debido a una desalineación?

En los comentarios a esta respuesta, Bothie afirma que hacer algo como

char * foo = ...;
int bar = *(int *)foo;

podría generar errores incluso en x86 si la verificación de alineación está habilitada.

Traté de producir una condición de error después de configurar el indicador de verificación de alineación a través de set $ps |= (1<<18) en GDB, pero no pasó nada.

¿Cómo se ve un ejemplo de trabajo (es decir, no trabajo;))?


Ninguno de los fragmentos de código de las respuestas falla en mi sistema; lo intentaré con una versión diferente del compilador y en una PC diferente más tarde.

Por cierto, mi propio código de prueba se veía así (ahora también uso asm para configurar AC bandera y lectura y escritura desalineadas):

#include <assert.h>

int main(void)
{
    #ifndef NOASM
    __asm__(
        "pushf\n"
        "orl $(1<<18),(%esp)\n"
        "popf\n"
    );
    #endif

    volatile unsigned char foo[] = { 1, 2, 3, 4, 5, 6 };
    volatile unsigned int bar = 0;

    bar = *(int *)(foo + 1);
    assert(bar == 0x05040302);

    bar = *(int *)(foo + 2);
    assert(bar == 0x06050403);

    *(int *)(foo + 1) = 0xf1f2f3f4;
    assert(foo[1] == 0xf4 && foo[2] == 0xf3 && foo[3] == 0xf2 &&
        foo[4] == 0xf1);

    return 0;
}

La aserción pasa sin problemas, aunque el código generado definitivamente contiene el acceso no alineado mov -0x17(%ebp), %edx y movl $0xf1f2f3f4,-0x17(%ebp).


También lo hará el establecimiento AC desencadenar un SIGBUS ¿O no? No pude hacer que funcionara en mi computadora portátil Intel de doble núcleo con Windows XP con ninguna de las versiones de GCC que probé (MinGW-3.4.5, MinGW-4.3.0, Cygwin-3.4.4), mientras que codelogic y Jonathan Leffler fallas mencionadas en x86…

  • Ese código fallará y quemará los sistemas x86 (SPARC, PPC, posiblemente IA64 a menos que esté configurado para ser tolerante).

    –Jonathan Leffler

    14 de febrero de 2009 a las 1:54

  • No es el compilador o el sistema operativo, es el x86, es muy tolerante con este error. Ver la respuesta de Michael Burr

    – Mark Ransom

    14 de febrero de 2009 a las 3:34

  • @Mark: Entonces, ¿las otras personas imaginaron que se estaban planteando errores?

    – Cristóbal

    14 de febrero de 2009 a las 6:47

  • Christoph, ¿examinó el desmontaje y trató de atravesarlo?

    – codelogic

    14 de febrero de 2009 a las 9:15

  • @codelogic: sí, lo tengo, el acceso no alineado definitivamente está ahí: mov -0x17(%ebp), %edx

    – Cristóbal

    14 de febrero de 2009 a las 9:29

avatar de usuario
miguel rebabas

Las situaciones son poco comunes en las que el acceso no alineado causará problemas en un x86 (más allá de que el acceso a la memoria lleve más tiempo). Estos son algunos de los que he oído hablar:

  1. Es posible que no cuente esto como un problema de x86, pero las operaciones de SSE se benefician de la alineación. Los datos alineados se pueden usar como un operando de fuente de memoria para guardar instrucciones. Instrucciones de carga no alineadas como movups son más lentos que movaps en las microarquitecturas anteriores a Nehalem, pero en Nehalem y posteriores (y la familia AMD Bulldozer), las cargas/almacenamientos de 16 bytes no alineados son casi tan eficientes como las cargas/almacenamientos de 8 bytes no alineados; uop único y sin ninguna penalización si los datos se alinean en tiempo de ejecución o no cruzan un límite de línea de caché; de lo contrario, soporte de hardware eficiente para divisiones de línea de caché. Las divisiones de 4k son muy caras (~100 ciclos) hasta Skylake (hasta ~10 ciclos como una división de línea de caché). Ver https://agner.org/optimizar/ y enlaces de rendimiento en la wiki de etiquetas x86 para obtener más información.

  2. operaciones entrelazadas (como lock add [mem], eax) están muy lento si no están lo suficientemente alineados, especialmente si cruzan un límite de línea de caché para que no puedan usar un bloqueo de caché dentro del núcleo de la CPU. En los sistemas SMP más antiguos (con errores), puede que en realidad fallan en ser atómicos (ver https://blogs.msdn.com/oldnewthing/archive/2004/08/30/222631.aspx).

  3. y otra posibilidad discutida por Raymond Chen es cuando se trata de dispositivos que tienen memoria de hardware almacenada (ciertamente, una situación extraña): https://blogs.msdn.com/oldnewthing/archive/2004/08/27/221486.aspx

  4. Recuerdo (pero no tengo una referencia para, por lo que no estoy seguro de este) problemas similares con accesos no alineados que se extienden a ambos lados de los límites de la página que también implican una falla de página. Veré si puedo encontrar una referencia para esto.

Y aprendí algo nuevo al investigar esta pregunta (me preguntaba sobre el “$ps |= (1<<18)“Comando GDB que se mencionó en un par de lugares). No me di cuenta de que las CPU x86 (a partir de la 486 parece) tienen la capacidad de causar una excepción cuando se realiza un acceso desalineado.

De “Programming Applications for Windows, 4th Ed” de Jeffery Richter:

Echemos un vistazo más de cerca a cómo la CPU x86 maneja la alineación de datos. La CPU x86 contiene un indicador de bit especial en su registro EFLAGS llamado indicador AC (verificación de alineación). De forma predeterminada, este indicador se establece en cero cuando la CPU recibe energía por primera vez. Cuando este indicador es cero, la CPU hace automáticamente lo que tiene que hacer para acceder con éxito a los valores de datos desalineados. Sin embargo, si este indicador se establece en 1, la CPU emite una interrupción INT 17H cada vez que se intenta acceder a los datos desalineados. La versión x86 de Windows 2000 y Windows 98 nunca altera este bit indicador de CPU. Por lo tanto, nunca verá una excepción de desalineación de datos en una aplicación cuando se ejecuta en un procesador x86.

Esto fue una novedad para mí.

Por supuesto, el gran problema con los accesos desalineados es que cuando finalmente va a compilar el código para un procesador que no es x86/x64, termina teniendo que rastrear y arreglar un montón de cosas, ya que prácticamente todos los demás procesadores de 32 bits o más Los procesadores son sensibles a los problemas de alineación.

  • “Las operaciones SSE deben tratar con datos alineados” ya no es necesariamente cierto. En las CPU Intel recientes (creo que Penryn y más nuevas), las operaciones SSE “alineadas” y “no alineadas” en realidad hacen lo mismo, que resulta ser un poco más lento si el acceso no está alineado.

    – kquinn

    14 de febrero de 2009 a las 8:33

  • Sí, en las CPU anteriores a Penryn, las lecturas SSE no alineadas matarán el rendimiento. Sin embargo, supuestamente en Penryn (no tengo uno para comparar), la CPU solo hará dos lecturas alineadas y usará el “Super Shuffle Engine” de Penryn para volver a unirlas en la lectura no alineada solicitada, por lo que no son tan lento.

    – kquinn

    14 de febrero de 2009 a las 10:02

  • Lo acabo de colocar en banco: las operaciones de SSE no alineadas de Penryn todavía tienen aproximadamente tres veces la latencia de una lectura alineada, aunque su rendimiento es mejor que en los núcleos anteriores. Esto es consistente con el comportamiento que describe (y con la forma en que VMX lo maneja, por ejemplo, cargar carga aleatoria).

    – Crashworks

    15 de febrero de 2009 a las 2:18

  • Solo curiosidad: ¿una idea de cuál fue el golpe antes de Penryn? Una suposición salvaje está bien para mí, ya que solo tengo curiosidad.

    – Michael Burr

    15 de febrero de 2009 a las 4:09

  • Corrección menor a “las operaciones entrelazadas deben operar en datos alineados para garantizar que sean atómicos en sistemas multiprocesador”. Las operaciones entrelazadas funcionarán en datos no alineados en X86, simplemente tienen casos extremos que son MUCHO más lento pero su código no debería fallar. FWIW, no tiene que usar la alineación completa para operaciones entrelazadas en algunos PowerPC también (por ejemplo, cierto sistema de juego hecho por Microsoft manejará enclavamientos de 64 bits que solo están alineados en 32 bits).

    – Adisak

    23 de octubre de 2009 a las 13:51

Si lee sobre la arquitectura Core I7 (específicamente, su literatura de optimización), Intel ha puesto una TONELADA de hardware allí para hacer que los accesos a la memoria desalineados sean casi gratuitos. Por lo que puedo decir, solo una desalineación que cruza un límite de línea de caché tiene algún costo adicional, e incluso entonces es mínimo. AMD también tiene muy pocos problemas con los accesos desalineados (en cuanto al ciclo) por lo que recuerdo (aunque ha pasado un tiempo).

Por lo que vale, configuré esa bandera en eflags (el bit de CA – verificación de alineación) cuando me estaba dejando llevar por la optimización de un proyecto en el que estaba trabajando. Resulta que Windows está LLENO de accesos desalineados, tantos que no pude ubicar ningún acceso de memoria desalineado en nuestro código, fui bombardeado con tantos accesos desalineados en bibliotecas y código de Windows que no tuve tiempo de Seguir.

Tal vez podamos aprender que cuando las CPU hacen que las cosas sean gratuitas o de muy bajo costo, los programadores se volverán complacientes y harán cosas que tienen un poco de sobrecarga adicional. Quizás los ingenieros de Intel hicieron parte de esa investigación y descubrieron que el software de escritorio x86 típico realiza millones de accesos desalineados por segundo, por lo que colocaron un hardware de acceso desalineado increíblemente rápido en CoreI7.

HTH

Existe una condición adicional, no mencionada, para que EFLAGS.AC entre en vigor. CR0.AM debe configurarse para evitar que INT 17h se dispare en sistemas operativos anteriores al 486 que no tienen un controlador para esta excepción. Desafortunadamente, Windows no lo configura de forma predeterminada, debe escribir un controlador en modo kernel para configurarlo.

char *foo probablemente esté alineado con los límites int. Prueba esto:

int bar = *(int *)(foo + 1);

char *foo = "....";
foo++;
int *bar = (int *)foo;

El compilador pondría foo en un límite de palabra, y luego, cuando lo incrementa, está en una palabra + 1, lo que no es válido para un puntero int.

  • Parece que Core2Duo en modo de 64 bits no produce este error.

    –Paul Tomblin

    14 de febrero de 2009 a las 2:12

  • En x86, es perfectamente válido acceder a datos desalineados siempre que no haya configurado el indicador para dar una excepción en datos no alineados. Es potencialmente más lento.

    – jcodificador

    18 de diciembre de 2009 a las 17:15

#include <stdio.h>

int main(int argc, char **argv)
{
  char c[] = "a";

  printf("%d\n", *(int*)(c));
}

esto me da un SIGBUS después de configurar set $ps |= (1<<18) en gdb, que aparentemente se lanza cuando la alineación de la dirección es incorrecta (entre otras razones).

EDITAR: Es bastante fácil subir SIGBUS:

int main(int argc, char **argv)
{
    /* EDIT: enable AC check */
    asm("pushf; "
        "orl $(1<<18), (%esp); "
        "popf;");

    char c[] = "1234567";
    char d[] = "12345678";
    return 0;
}

Mirando el desmontaje de main en gdb:

Dump of assembler code for function main:
....
0x08048406 <main+34>:   mov    0x8048510,%eax
0x0804840b <main+39>:   mov    0x8048514,%edx
0x08048411 <main+45>:   mov    %eax,-0x10(%ebp)
0x08048414 <main+48>:   mov    %edx,-0xc(%ebp)
0x08048417 <main+51>:   movl   $0x34333231,-0x19(%ebp)   <== BAM! SIGBUS
0x0804841e <main+58>:   movl   $0x38373635,-0x15(%ebp)
0x08048425 <main+65>:   movb   $0x0,-0x11(%ebp)

De todos modos, Christoph, su programa de prueba falla en Linux al generar un SIGBUS como debería. ¿Es probablemente una cosa de Windows?


Puede habilitar el bit de verificación de alineación en el código usando este fragmento:

/* enable AC check */
asm("pushf; "
    "orl $(1<<18), (%esp); "
    "popf;");

Además, asegúrese de que la bandera esté configurada:

unsigned int flags;
asm("pushf; "
    "movl (%%esp), %0; "
    "popf; " : "=r"(flags));
fprintf(stderr, "%d\n", flags & (1<<18));

  • Parece que Core2Duo en modo de 64 bits no produce este error.

    –Paul Tomblin

    14 de febrero de 2009 a las 2:12

  • En x86, es perfectamente válido acceder a datos desalineados siempre que no haya configurado el indicador para dar una excepción en datos no alineados. Es potencialmente más lento.

    – jcodificador

    18 de diciembre de 2009 a las 17:15

avatar de usuario
jww

Para disfrutar de la excepción, llama SetErrorMode con SEM_NOALIGNMENTFAULTEXCEPT:

int main(int argc, char* argv[])
{
   SetErrorMode(GetErrorMode() | SEM_NOALIGNMENTFAULTEXCEPT);
   ...
}

Ver Alineación de datos de Windows en IPF, x86 y x64 para detalles.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad