Punteros mal alineados en x86

¿Puede alguien proporcionar un ejemplo si un puntero de un tipo a otro falla debido a una mala alineación?

En los comentarios a esta respuesta , ambos afirman que hacer algo como

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

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

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

¿Qué aspecto tiene un ejemplo de trabajo (es decir, no funciona;))?


Ninguno de los fragmentos de código de las respuestas falla en mi sistema; lo probaré con una versión de comstackdor diferente y en una PC diferente más adelante.

Por cierto, mi propio código de prueba se veía así (ahora también usa asm para establecer el indicador de AC y leer y escribir sin alinear):

 #include  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 transcurre sin problemas, aunque el código generado definitivamente contiene el acceso mov -0x17(%ebp), %edx y movl $0xf1f2f3f4,-0x17(%ebp) .


Entonces, ¿establecerá AC un SIGBUS o no? No pude hacer que funcionara en mi laptop con doble núcleo Intel bajo Windows XP sin 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 …

Hay una condición adicional, no mencionada, para que EFLAGS.AC realmente tenga efecto. CR0.AM se debe configurar para evitar que INT 17h se active en sistemas operativos anteriores anteriores al 486 que no tienen controlador para esta excepción. Desafortunadamente, Windows no lo configura de manera predeterminada, necesita escribir un controlador en modo núcleo para configurarlo.

Las situaciones son poco comunes en las que el acceso desalineado causará problemas en un x86 (más allá de que el acceso a la memoria tarde más). Estos son algunos de los que he escuchado:

  1. Es posible que no cuente esto como un problema x86, pero las operaciones SSE se benefician de la alineación. Los datos alineados se pueden usar como un operando fuente de memoria para guardar instrucciones. Las instrucciones de carga movups como movups son más lentas que movaps en microarchitectures antes que Nehalem, pero en Nehalem y posterior (y familia AMD Bulldozer), las cargas / tiendas desalineadas de 16 bytes son casi tan eficientes como las cargas / tiendas desalineadas de 8 bytes; uop único y sin penalización en absoluto si los datos se alinean en el tiempo de ejecución o no cruzan un límite de la línea de caché; de lo contrario, soporte de hardware eficiente para las divisiones de la línea de caché. 4k splits son muy caros (~ 100 ciclos) hasta Skylake (hasta ~ 10 ciclos como una línea de caché dividida). Consulte https://agner.org/optimize/ y enlaces de rendimiento en la wiki de la etiqueta x86 para obtener más información.

  2. las operaciones interconectadas (como lock add [mem], eax ) son muy lentas si no están lo suficientemente alineadas, especialmente si cruzan un límite de la línea de caché, por lo que no pueden usar un locking de caché dentro del núcleo de la CPU. En sistemas SMP más antiguos (defectuosos), es posible que en realidad no sean atómicos (consulte 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 almacenada en hardware (una situación ciertamente 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 acerca de esto) problemas similares con los accesos no alineados que se extienden a lo largo de los límites de la página que también implican un error de página. Veré si puedo encontrar una referencia para esto.

Y aprendí algo nuevo al analizar esta pregunta (me preguntaba sobre el comando ” $ps |= (1<<18) " GDB que se mencionó en algunos lugares). No me di cuenta de que las CPUs x86 (comenzando con el 486, al parecer) tienen la capacidad de causar una excepción cuando se realiza un acceso desalineado.

De "Aplicaciones de progtwigción para Windows, 4ta edición" 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 de AC (verificación de alineación). Por defecto, este indicador se pone a 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 datos desalineados. La versión x86 de Windows 2000 y Windows 98 nunca altera este bit de indicador de CPU. Por lo tanto, nunca verá una excepción de desalineamiento de datos en una aplicación cuando se ejecuta en un procesador x86.

Esto fue nuevo para mí.

Por supuesto, el gran problema con los accesos mal alineados es que cuando finalmente vas a comstackr el código para un procesador que no sea x86 / x64 terminas teniendo que rastrear y arreglar un montón de cosas, ya que prácticamente todas las otras de 32 bits o más los procesadores son sensibles a los problemas de alineación.

Si lee en la architecture Core I7 (específicamente, su literatura de optimización), Intel realmente ha puesto una TONELADA de hardware 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 la línea de caché tiene un costo adicional, e incluso entonces es mínimo. AMD también tiene muy pocos problemas con accesos desalineados (por ciclos) por lo que recuerdo (aunque ha pasado un tiempo).

Por lo que vale, puse esa bandera en eflags (el bit AC – verificación de alineación) cuando me estaba dejando llevar optimizando un proyecto en el que estaba trabajando. Resulta que Windows está LLENO de accesos mal alineados, tantos que no pude localizar ningún acceso a memoria mal alineado en nuestro código, fui bombardeado con tantos accesos mal alineados en bibliotecas y códigos de ventanas que no tuve tiempo para continuar.

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

HTH

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 comstackdor colocará foo en un límite de palabras, y luego cuando lo incremente estará en una palabra + 1, que no es válida para un puntero int.

 #include  int main(int argc, char **argv) { char c[] = "a"; printf("%d\n", *(int*)(c)); } 

Esto me da un SIGBUS después de establecer 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 desassembly del main en gdb:

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

De todos modos, Christoph su progtwig de prueba falla bajo Linux elevar 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 con este fragmento:

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

Además, asegúrese de que la bandera se haya configurado:

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

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

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

Vea la alineación de datos de Windows en IPF, x86 y x64 para más detalles.

gcc cuando la auto-vectorización asume que uint16_t* está alineado con un límite de 2 bytes. Si infringe esta suposición, puede obtener un segfault: ¿Por qué el acceso desalineado a la memoria de mmap a veces falla por seg en AMD64?

Por lo tanto, respetar las reglas de alineación C es importante incluso cuando se dirige a x86.


Use esto para express eficientemente una carga desalineada en C:

 static inline uint32_t load32(char *p) // char* is allowed to alias anything uint32_t tmp; memcpy(&tmp, p, sizeof(tmp)); return tmp; } 

En x86 comstackrá el único mov que esperas (o vectorizar automáticamente o lo que sea), pero en SPARC o MIPS antes de MIPS64r6 o lo que sea que compile a cualquier secuencia de instrucciones que sea necesaria para una carga desalineada. Este uso de memcpy optimizará totalmente en objectives que soportan cargas desalineadas.

es decir, su comstackdor sabe si el ISA objective admite cargas desalineadas o no, y emitirá asm que las haga o no como lo considere oportuno.