Выравнивание основного и стекового
У меня есть функция, которая печатает текст и число с плавающей точкой. Вот версия, которая не использует основной
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
Я не думаю, что вы можете полагаться на то, что он работает без ручной настройки. Так получилось, что в вашем случае это работает из-за того, что уже есть в стеке.