c++ assembly x86 red-zone

Uso del registro de puntero base en asm en línea C++



assembly x86 (2)

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

Su código está roto porque pisa la zona roja debajo de RSP (con push ) donde GCC mantenía un valor.

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

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

Si solo está tratando de aprender asm en primer lugar, GNU inline asm es una elección terrible. Debe comprender completamente casi todo lo que sucede con el asm y comprender lo que el compilador necesita saber para escribir las restricciones correctas de entrada / salida y hacer todo bien. Los errores conducirán a golpear cosas y a la ruptura difícil de depurar. La función llamada ABI es mucho más simple y fácil de realizar un seguimiento de los límites entre su código y el código del compilador.

¿Por qué esto se rompe?

Compiló con -O0 , por lo que el código de gcc derrama el parámetro de función de %rdi a una ubicación en la pila. (Esto podría suceder en una función no trivial incluso con -O3 ).

Dado que el ABI de destino es el ABI x86-64 SysV , utiliza la "Zona Roja" (128 bytes por debajo de %rsp que incluso los manejadores de señales asíncronas no pueden tropezar), en lugar de desperdiciar una instrucción que disminuye el puntero de la pila para reservar espacio .

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

Cuando termine su asm en línea,

  • gcc vuelve a cargar -8(%rbp) (que se ha sobrescrito con %rbp ) y lo usa como la dirección de una tienda 4B.
  • Foo vuelve a main con %rbp = (upper32)|5 (valor original con el valor 32 bajo establecido en 5 ).
  • main carreras main leave : %rsp = (upper32)|5
  • main ejecuta ret con %rsp = (upper32)|5 , leyendo la dirección de retorno de la dirección virtual (void*)(upper32|5) , que según su comentario es 0x7fff0000000d .

No verifiqué con un depurador; uno de esos pasos puede estar ligeramente desviado, pero el problema definitivamente es que golpeas la zona roja , lo que lleva al código de gcc a la basura.

Incluso agregar un clobber de "memoria" no obtiene gcc para evitar el uso de la zona roja, por lo que parece que asignar su propia memoria de pila desde el asm en línea es una mala idea. (Un clobber de memoria significa que puede haber escrito algo de memoria en la que puede escribir, por ejemplo, una variable global o algo señalado por un global, no es que haya sobrescrito algo que se supone que no debe).

Si desea utilizar el espacio de borrador desde inm asm, probablemente debería declarar una matriz como una variable local y utilizarla como un operando de solo salida (del cual nunca lee).

AFAIK, no hay sintaxis para declarar que modifica la zona roja, por lo que sus únicas opciones son:

  • use un operando de salida "=m" (posiblemente una matriz) para el espacio temporal; el compilador probablemente completará ese operando con un modo de direccionamiento relativo a RBP o RSP. Puede indexarlo con constantes como 4 + %[tmp] o lo que sea. Es posible que reciba una advertencia de ensamblador de 4 + (%rsp) pero no un error.
  • omita la zona roja con add $-128, %rsp / sub $-128, %rsp alrededor de su código. (Necesario si desea utilizar una cantidad desconocida de espacio de pila adicional, por ejemplo, empujar en un bucle o realizar una llamada a una función. Otra razón más para desrefinir un puntero de función en C puro, no en línea).
  • compila con -mno-red-zone (no creo que puedas habilitar eso por función, solo por archivo)
  • No use el espacio para rascar en primer lugar. Dile al compilador qué registra tu clobber y deja que los guarde.

Esto es lo que deberías 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/restore %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 memoria virtual que le brinda está en la zona roja. Si compila con -O0 , verá que está en una posición diferente de donde se derrama &x .

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

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

Cuando use asm en línea, manténgalo lo más pequeño posible: idealmente, solo las una o dos instrucciones que gcc no emite por sí solo, con restricciones de entrada / salida para indicarle cómo ingresar / sacar datos de la declaración asm. Para esto está diseñado.

Regla de oro: si su asm en línea 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 sintaxis intel de GAS en inline-asm -masm=intel con -masm=intel (en cuyo caso su código solo funcionará con esa opción), o usando alternativas dialectales para que funcione con el compilador en la sintaxis de salida asm de Intel o AT&T. Pero eso no cambia las directivas, y la sintaxis Intel de GAS no está bien documentada. (Sin embargo, es como MASM, no NASM). Realmente no lo recomiendo a menos que realmente odies la sintaxis de AT&T.

Enlaces asm en línea:

Algunos de ellos repiten algunas de las mismas cosas que expliqué aquí. No los releí para tratar de evitar la redundancia, lo siento.

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

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

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

El código de ensamblaje generado por GCC (ligeramente despojado) 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é esto falla? Parece que de alguna manera %rbp pero no veo cómo. Gracias por adelantado.

Estoy ejecutando GCC 4.8.4 en Ubuntu 14.04 de 64 bits.


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

Esta:

subq $12, %rsp; // make room

debiera ser:

subq $16, %rsp; // make room