Печать чисел с плавающей запятой из x86-64, кажется, требует сохранения%rbp

Когда я пишу простую программу на ассемблере, связанную с библиотекой C, использую gcc 4.6.1 в Ubuntu и пытаюсь напечатать целое число, она работает нормально:

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

Это печатает 5, как и ожидалось.

Но теперь, если я сделаю небольшое изменение и попытаюсь напечатать значение с плавающей запятой:

        .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

Эта программа обнаруживает ошибки, не печатая ничего. Просто грустный сегмент.

Но я могу это исправить, нажав и нажав %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

Сейчас работает и печатает 15.5000.

У меня вопрос: зачем толкаться и трещать %rbp заставить приложение работать? По данным ABI, %rbp является одним из регистров, которые должен сохранить вызываемый, и так printf не может испортить это. По факту, printf работал в первой программе, когда только целое число было передано printf, Значит проблема должна быть в другом месте?

1 ответ

Решение

Я подозреваю, что проблема не имеет ничего общего с %rbp, а скорее имеет отношение к выравниванию стека. Процитирую ABI:

ABI требует, чтобы кадры стека были выровнены по 16-байтовым границам. В частности, конец области аргумента (%rbp+16) должен быть кратным 16. Это требование означает, что размер кадра должен быть дополнен до 16 байтов.

Стек выравнивается при вводе main(), призвание printf() помещает адрес возврата в стек, перемещая указатель стека на 8 байтов. Вы восстанавливаете выравнивание, помещая в стек еще восемь байтов (которые %rbp но так же легко может быть что-то еще).

Вот код, который gcc генерирует (также в проводнике компилятора 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

Как вы можете видеть, он имеет дело с требованиями выравнивания, вычитая 8 из %rsp в начале и добавив его обратно в конце.

Вместо этого вы можете вместо того, чтобы манипулировать регистром, нажимать / выдавливать любой регистр, который вам нравится. %rsp непосредственно; некоторые компиляторы используют фиктивный толчок для выравнивания стека, поскольку это может быть дешевле на современных процессорах и экономит размер кода.

Другие вопросы по тегам