Usar el registro de puntero base en C ++ inline asm

Quiero poder utilizar el registro de puntero base ( %rbp ) dentro de asm en línea. Un ejemplo de esto es como sigue:

 void Foo(int &x) { asm volatile ("pushq %%rbp;" // 'prologue' "movq %%rsp, %%rbp;" // 'prologue' "subq $12, %%rsp;" // make room "movl $5, -12(%%rbp);" // some asm instruction "movq %%rbp, %%rsp;" // 'epilogue' "popq %%rbp;" // 'epilogue' : : : ); x = 5; } int main() { int x; Foo(x); return 0; } 

Esperé que, dado que estoy usando el método habitual de invocación de funciones de prólogo / epílogo para empujar y mostrar el viejo %rbp , esto estaría bien. Sin embargo, falla cuando trato de acceder a x después del asm en línea.

El código de ensamblaje generado por GCC (ligeramente desglosado) es:

 _Foo: pushq %rbp movq %rsp, %rbp movq %rdi, -8(%rbp) # INLINEASM pushq %rbp; // prologue movq %rsp, %rbp; // prologue subq $12, %rsp; // make room movl $5, -12(%rbp); // some asm instruction movq %rbp, %rsp; // epilogue popq %rbp; // epilogue # /INLINEASM movq -8(%rbp), %rax movl $5, (%rax) // x=5; popq %rbp ret main: pushq %rbp movq %rsp, %rbp subq $16, %rsp leaq -4(%rbp), %rax movq %rax, %rdi call _Foo movl $0, %eax leave ret 

¿Alguien puede decirme por qué este seg falla? Parece que de alguna manera corrompe %rbp pero no veo cómo. Gracias por adelantado.

Estoy ejecutando GCC 4.8.4 en Ubuntu 14.04 de 64 bits.

Consulte la parte inferior de esta respuesta para ver una colección de enlaces a otras preguntas y respuestas en línea.


¿Qué esperas aprender a lograr con el asm en línea? Si quieres aprender asm en línea, aprende a usarlo para hacer un código eficiente, en lugar de cosas horribles como esta. Si desea escribir prólogos de función y presionar / soltar para guardar / restaurar registros, debe escribir funciones completas en asm . (Entonces puede usar fácilmente nasm o yasm, en lugar de la syntax de AT & T menos preferida con las directivas de ensamblador GNU 1 ).

GNU inline asm es difícil de usar, pero le permite mezclar fragmentos de ASM personalizados en C y C ++ mientras permite que el comstackdor maneje la asignación de registro y cualquier guardado / restauración si es necesario. Algunas veces el comstackdor podrá evitar guardar y restaurar al darle un registro que puede ser destruido. Sin volatile , incluso puede levantar declaraciones de asm de bucles cuando la entrada sería la misma. (es decir, a menos que use volatile , se supone que las salidas son una función “pura” de las entradas).

Si solo estás tratando de aprender asm en primer lugar, GNU inline asm es una elección terrible. Debe comprender completamente casi todo lo que está sucediendo con el ASM, y comprender lo que el comstackdor necesita saber, escribir las restricciones correctas de entrada / salida y hacer todo correctamente. Los errores darán lugar a cosas difíciles y a la rotura difícil de depurar. El llamado a la función ABI es mucho más simple y fácil de seguir el límite entre su código y el código del comstackdor.


Compiló con -O0 , por lo que el código de gcc dertwig el parámetro de función de %rdi a una ubicación en la stack. (Esto podría suceder en una función no trivial incluso con -O3 ). Dado que el ABI objective es el x86-64 SysV ABI , utiliza la “Zona roja” (128B por debajo del %rsp que incluso los manejadores de señal asíncronos no pueden pisar), en lugar de desperdiciar una instrucción que decrementa el puntero de la stack para reservar espacio.

Almacena la función de puntero 8B arg en -8(rsp_at_function_entry) . Entonces su asm en línea empuja %rbp , que disminuye% rsp por 8 y luego escribe allí, golpeando el bajo 32b de &x (el puntero).

Cuando su asm en línea está listo,

  • gcc reloads -8(%rbp) (que se ha sobrescrito con %rbp ) y lo utiliza como la dirección de una tienda 4B.
  • Foo regresa a main con %rbp = (upper32)|5 (valor de origen con el valor de 32 bajo en 5 ).
  • main carreras main leave : %rsp = (upper32)|5
  • main runs ret con %rsp = (upper32)|5 , leyendo la dirección de retorno de la dirección virtual (void*)(upper32|5) , que a partir de su comentario es 0x7fff0000000d .

No verifiqué con un depurador; uno de esos pasos puede estar un poco apagado, pero el problema es definitivamente que golpeas la zona roja , lo que hace que el código de gcc destruya la stack.

Incluso agregar un clobber de “memoria” no hace que gcc evite usar la zona roja, por lo que parece que asignar su propia memoria de stack desde un aster en línea es solo una mala idea. (Un toque de memoria significa que puede haber escrito algo de memoria en la que se le permite escribir, no es que haya sobrescrito algo que se supone que no se debe).

Si desea utilizar el espacio reutilizable del asm en línea, probablemente deba declarar una matriz como una variable local y usarla como un operando de solo salida (del que nunca lee).


Esto es lo que debería haber hecho :

 void Bar(int &x) { int tmp; long tmplong; asm ("lea -16 + %[mem1], %%rbp\n\t" "imul $10, %%rbp, %q[reg1]\n\t" // q modifier: 64bit name. "add %k[reg1], %k[reg1]\n\t" // k modifier: 32bit name "movl $5, %[mem1]\n\t" // some asm instruction writing to mem : [mem1] "=m" (tmp), [reg1] "=r" (tmplong) // tmp vars -> tmp regs / mem for use inside asm : : "%rbp" // tell compiler it needs to save/restre %rbp. // gcc refuses to let you clobber %rbp with -fno-omit-frame-pointer (the default at -O0) // clang lets you, but memory operands still use an offset from %rbp, which will crash! // gcc memory operands still reference %rsp, so don't modify it. Declaring a clobber on %rsp does nothing ); x = 5; } 

Tenga en cuenta el push / pop de %rbp en el código fuera de la sección #APP / #NO_APP , emitido por gcc. También tenga en cuenta que la memoria de scratch que le da se encuentra en la zona roja. Si comstack con -O0 , verá que está en una posición diferente de donde se dertwig &x .

Para obtener más reglas de scratch, es mejor simplemente declarar más operandos de salida que nunca sean utilizados por el código circundante no asm. Eso deja la asignación de registros al comstackdor, por lo que puede ser diferente cuando se incluye en diferentes lugares. Elegir antes de tiempo y declarar un clobber solo tiene sentido si necesita usar un registro específico (por ejemplo, conteo de turnos en %cl ). Por supuesto, una restricción de entrada como "c" (count) obtiene gcc para poner el recuento en rcx / ecx / cx / cl, por lo que no emite un mov %[count], %%ecx potencial mov %[count], %%ecx redundante mov %[count], %%ecx .

Si esto parece demasiado complicado, no use asm en línea . Dirija el comstackdor al asm que desee con C que sea como el asm óptimo, o escriba una función completa en asm.

Al usar el asm en línea, manténgalo lo más pequeño posible: idealmente solo una o dos instrucciones que gcc no está emitiendo por sí mismo, con restricciones de entrada / salida para indicarle cómo ingresar y extraer datos de la statement asm. Esto es para lo que está diseñado.

Regla de oro: si su ASM inline de GNU C comienza o termina con un mov , generalmente lo está haciendo mal y debería haber usado una restricción en su lugar.


Notas al pie :

  1. Puede usar la syntax intel de GAS en inline-asm construyendo con -masm=intel (en cuyo caso su código solo funcionará con esa opción), o utilizando alternativas dialecto para que funcione con el comstackdor en la syntax de salida asm de Intel o AT & T. Pero eso no cambia las directivas, y la syntax Intel de GAS no está bien documentada. (Es como MASM, no NASM, sin embargo). Realmente no lo recomiendo a menos que realmente odies la syntax de AT & T.

Enlaces asm en línea:

  • x86 wiki. (La etiqueta wiki también enlaza a esta pregunta, para esta colección de enlaces)
  • El manual Lee esto. Tenga en cuenta que el asm en línea fue diseñado para envolver instrucciones individuales que el comstackdor normalmente no emite. Es por eso que está redactado para decir cosas como “la instrucción”, no “el bloque de código”.
  • Un tutorial
  • Looping sobre matrices con ensamblado en línea Usando restricciones de r para punteros / índices y usando su elección de modo de direccionamiento, frente al uso de restricciones m para que gcc elija entre incrementar punteros vs. matrices de indexación.
  • En GNU C inline asm, ¿cuáles son los modificadores para xmm / ymm / zmm para un solo operando? . Usando %q0 para obtener %rax vs. %w0 para obtener %ax . Usar %g[scalar] para obtener %zmm0 vez de %xmm0 .
  • Eficiente adición de 128 bits utilizando la bandera de llevar La respuesta de Stephen Canon explica un caso en el que se necesita una statement temprana en un operando de lectura y escritura . También tenga en cuenta que el asm en línea x86 / x86-64 no necesita declarar un clobber "cc" (los códigos de condición, también conocidos como flags); está implícito (gcc6 introduce la syntax para usar las condiciones del indicador como operandos de entrada / salida . Antes de eso debes setcc un registro que gcc emitirá código para test , lo que obviamente es peor).
  • Preguntas sobre el rendimiento de diferentes implementaciones de strlen : mi respuesta a una pregunta con algún asme en línea mal usado, con una respuesta similar a esta.
  • Informes llvm: asm en línea no soportado: entrada con tipo ‘void *’ que coincide con la salida con tipo ‘int’ : Usar operandos de memoria offsetable (en x86, todas las direcciones efectivas son compensables: siempre se puede agregar un desplazamiento).
  • Cuándo no se debe usar el asm en línea , con un ejemplo de 32b/32b => 32b división y rest que el comstackdor ya puede hacer con un solo div . (El código en la pregunta es un ejemplo de cómo no utilizar el asm en línea: muchas instrucciones para la configuración y guardar / restaurar que deberían dejarse al comstackdor escribiendo las restricciones de entrada / salida apropiadas).
  • MSVC inline asm vs. GNU C inline asm para envolver una sola instrucción , con un ejemplo correcto de asm en línea para 64b/32b=>32bit división . El diseño y la syntax de MSVC requieren un viaje de ida y vuelta a través de la memoria para las entradas y salidas, lo que hace que sea terrible para las funciones cortas. También es “nunca muy confiable” según el comentario de Ross Ridge sobre esa respuesta.
  • Usando x87 coma flotante, y operandos conmutativos . No es un gran ejemplo, porque no encontré la manera de hacer que gcc emita el código ideal.

Algunos de ellos reiteran algunas de las mismas cosas que expliqué aquí. No volví a leerlos para tratar de evitar la redundancia, lo siento.

En x86-64, el puntero de stack debe alinearse a 8 bytes.

Esta:

 subq $12, %rsp; // make room 

debiera ser:

 subq $16, %rsp; // make room