La impresión de números en coma flotante de x86-64 parece requerir que se guarde% rbp

Cuando escribo un progtwig de lenguaje ensamblador simple, vinculado con la biblioteca C, usando gcc 4.6.1 en Ubuntu, y trato de imprimir un entero, funciona bien:

.global main .text main: mov $format, %rdi mov $5, %rsi mov $0, %rax call printf ret format: .asciz "%10d\n" 

Esto imprime 5, como se esperaba.

Pero ahora si hago un pequeño cambio e bash imprimir un valor en coma flotante:

  .global main .text main: mov $format, %rdi movsd x, %xmm0 mov $1, %rax call printf ret format: .asciz "%10.4f\n" x: .double 15.5 

Este progtwig segrea fallas sin imprimir nada . Solo un triste segfault.

Pero puedo solucionar esto presionando y mostrando %rbp .

  .global main .text main: push %rbp mov $format, %rdi movsd x, %xmm0 mov $1, %rax call printf pop %rbp ret format: .asciz "%10.4f\n" x: .double 15.5 

Ahora funciona e imprime 15.5000.

Mi pregunta es: ¿por qué empujar y hacer popping %rbp hace que la aplicación funcione? Según el ABI, %rbp es uno de los registros que debe conservar el destinatario, por lo que printf no lo puede arruinar. De hecho, printf funcionó en el primer progtwig, cuando solo se pasó un entero a printf . ¿Entonces el problema debe estar en otra parte?

Sospecho que el problema no tiene nada que ver con %rbp , sino que tiene que ver con la alineación de la stack. Para citar el ABI:

El ABI requiere que los marcos de stack estén alineados en los límites de 16 bytes. Específicamente, el final del área del argumento (% rbp + 16) debe ser un múltiplo de 16. Este requisito significa que el tamaño del marco debe ser rellenado a un múltiplo de 16 bytes.

La stack está alineada cuando ingresas main() . Llamar a printf() empuja la dirección de retorno a la stack, moviendo el puntero de stack por 8 bytes. Restaure la alineación empujando otros ocho bytes en la stack (que es %rbp pero podría ser otra cosa).

Aquí está el código que genera gcc (también en el explorador del comstackdor Godbolt ):

 .LC1: .ascii "%10.4f\12\0" main: leaq .LC1(%rip), %rdi # format string address subq $8, %rsp ### align the stack by 16 before a CALL movl $1, %eax ### 1 FP arg being passed in a register to a variadic function movsd .LC0(%rip), %xmm0 # load the double itself call printf xorl %eax, %eax # return 0 from main addq $8, %rsp ret 

Como puede ver, trata los requisitos de alineación restando 8 de %rsp al inicio y volviendo a agregarlo al final.

En su lugar, podría hacer un push / pop ficticio del registro que desee en lugar de manipular %rsp directamente; algunos comstackdores usan un empuje ficticio para alinear la stack, porque esto puede ser más barato en las CPU modernas y ahorra tamaño del código.