Выравнивание основного и стекового

У меня есть функция, которая печатает текст и число с плавающей точкой. Вот версия, которая не использует основной

extern printf
extern _exit

section .data
    hello:     db 'Hello world! %f',10,0
    pi:        dq  3.14159
section .text
    global _start
_start:
    xor eax, eax
    lea rdi, [rel hello]
    movsd xmm0, [rel pi]
    mov eax, 1
    call printf
    mov rax, 0
    jmp _exit

Я собираю и связываю это так

nasm -felf64 hello.asm
ld hello.o -dynamic-linker /lib64/ld-linux-x86-64.so.2 -lc -melf_x86_64

Это работает нормально. Тем не менее, теперь я хочу сделать это с помощью main,

global main
extern printf

section .data
    hello:     db 'Hello world! %f',10,0
    pi:        dq  3.14159
section .text
    main:
    sub rsp, 8
    xor eax, eax
    lea rdi, [rel hello]
    movsd xmm0, [rel pi]
    mov eax, 1
    call printf
    mov rax, 0
    add rsp, 8
    ret

Я сборка и ссылка, как это

nasm -felf64 hello_main.asm
gcc hello_main.o

Это тоже хорошо работает. Однако мне пришлось вычесть восемь байтов из указателя стека перед вызовом printf и затем добавить восемь байтов к указателю стека после того, как в противном случае я получу ошибку сегментации.

Глядя на указатель стека, я вижу, что без использования main это 16-байтовое выравнивание, но с main выровнено только восемь байт. Тот факт, что восемь байтов должны быть вычтены и добавлены, говорит о том, что они всегда выровнены на 8 байтов и никогда не выровнены на 16 байтов (если я что-то не так понимаю). Почему это? Я думал, что с кодом x86_64 мы можем предположить, что стек выровнен по 16 байтам (по крайней мере, для стандартных вызовов библиотечных функций, которые, я думаю, включают main).

1 ответ

Решение

Согласно ABI, указатель стека + 8 должен быть выровнен на 16 байт при входе в функции. Причина, по которой вы должны вычесть 8, состоит в том, что call сам помещает 8 байтов адреса возврата в стек, тем самым нарушая это ограничение. По сути, вы должны убедиться, что общее перемещение указателя стека кратно 16, включая адрес возврата. Таким образом, указатель стека должен быть перемещен кратно 16 + 8, чтобы освободить место для адреса возврата.

Что касается _startЯ не думаю, что вы можете полагаться на то, что он работает без ручной настройки. Так получилось, что в вашем случае это работает из-за того, что уже есть в стеке.

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