Ошибка сегментации программы сборки Hello world

Почему я получаю ошибку:

Ошибка сегментации (дамп ядра)

Вот код сборки:

.intel_syntax noprefix
    
.data

    message: .asciz "Hello World!\n"

.text

.global main

main:
    lea rdi, message
    call printf

    ret

person user9352046    schedule 17.04.2021    source источник
comment
Вы получаете segfault, потому что 1) вы не поддерживаете стек, выровненный до 16 байтов, и 2) вы не обнуляете регистр AL, чтобы указать, что векторные регистры не используются. Между прочим, в более ранней версии не было проблемы № 1, которая также делает неактуальной № 2, так что это должно было сработать.   -  person Jester    schedule 17.04.2021


Ответы (1)


Проблема

System V ABI требует, чтобы вы выровняли свой стек по 16 байтам, прежде чем вызывать функцию. Чтобы упростить задачу, ABI гарантирует, что при входе в функцию, если вы подставите указатель стека на 8 * n (n — нечетное число), ваш стек будет выровнен по 16 байтам.

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

Решение

ammarfaizi2@integral:/tmp/test_asm$ cat test.S
.intel_syntax noprefix
    
.data

    message: .asciz "Hello World!\n"

.text

.global main

main:
    sub rsp, 8
    xor eax, eax
    lea rdi, [rip + message]
    call printf
    add rsp, 8
    ret
ammarfaizi2@integral:/tmp/test_asm$ gcc test.S -o test
ammarfaizi2@integral:/tmp/test_asm$ ./test
Hello World!
ammarfaizi2@integral:/tmp/test_asm$ 

Рекомендация

Если вы call выполняете функцию, а следующее, что вы делаете, это ret, вы можете упростить код с помощью хвостового вызова. Он использует jmp для вызываемой целевой функции. Убедитесь, что вы отменили текущий кадр стека функций перед переходом, если вы настроили его раньше.

Для поддержки PIE и PIC рассмотрите возможность использования относительной адресации RIP для доступа к статическому хранилищу. Это также повышает безопасность. Компиляторы в наши дни обычно компилируют цель в PIE по умолчанию.

Эта часть является примером доступа к статическому хранилищу с относительной адресацией RIP:

lea rdi, [rip + message]

Исполнение

ammarfaizi2@integral:/tmp/test_asm$ cat test.S
.intel_syntax noprefix
    
.data

    message: .asciz "Hello World!\n"

.text

.global main

main:
    xor eax, eax
    lea rdi, [rip + message]
    jmp printf

ammarfaizi2@integral:/tmp/test_asm$ gcc test.S -o test
ammarfaizi2@integral:/tmp/test_asm$ ./test
Hello World!
ammarfaizi2@integral:/tmp/test_asm$ 

Редактировать

Добавлено xor eax, eax для безопасности. См.: ошибки сегментации glibc scanf при вызове из функции, которая не выравнивает RSP

person Ammar Faizi    schedule 17.04.2021
comment
Хороший момент о хвостовом вызове. Возможно, стоит добавить, что вы изменили инструкцию lea на lea rdi, [rip + message], чтобы разрешить создание исполняемого файла PIE (в противном случае возникнет ошибка перемещения). Другим решением было бы указать GCC создать EX ELF с --static (PIE — это SH ELF с непереопределяемыми символами). - person Margaret Bloom; 17.04.2021
comment
@MargaretBloom, да, я добавил информацию об относительной адресации RIP. - person Ammar Faizi; 17.04.2021
comment
Оставлять AL неустановленным по-прежнему небезопасно. Старые версии GCC компилируют вариативные функции для использования AL для вычисляемого перехода в последовательность movaps хранилищ. (Текущий GCC и, следовательно, текущие сборки libc, просто отметьте 0 / не-0, чтобы условно запускать все 8 или нет, поэтому нарушение ABI, связанное с возможным наличием AL > 8, на практике не вызывает проблем.) В любом случае, вы должны использовать xor eax, eax. Или используйте puts, который не является вариативным. - person Peter Cordes; 18.04.2021
comment
Выровненные сохранения для дампа возможных аргументов XMM для функций с переменным числом аргументов являются обычной причиной сбоя printf (с AL!=0), но code-gen для некоторых других функций иногда включает загрузку или сохранение с выравниванием по 16 байтам. например ошибки сегментации glibc scanf при вызове из функции, которая не выравнивает RSP - person Peter Cordes; 18.04.2021
comment
@PeterCordes, можно ли использовать xor al, al вместо xor eax, eax? - person Ammar Faizi; 18.04.2021
comment
Я думаю, почему мы должны очищать 32-битную (на самом деле ноль распространяется на 64-битную), в то время как вызываемому нужна только 8-битная часть? - person Ammar Faizi; 18.04.2021
comment
@AmmarFaizi: Это нормально для корректности, но хуже для эффективности, если изменить только младший байт существующего значения RAX вместо использования идиомы обнуления, чтобы просто записать новое значение в регистр. Как лучше всего установить нулевой регистр в сборке x86: xor, mov или and? / Почему GCC не использует частичные регистры?. xor al,al не используется в качестве идиомы обнуления на большинстве процессоров, поэтому mov al,0 на некоторых будет лучше. Но все же хуже, чем xor eax,eax. - person Peter Cordes; 19.04.2021