Почему возврат из _start segfault?

Пробовал помещать код не в основную функцию, а прямо в _start:

    segment .text
    global _start
_start:
    push rbp
    mov rbp, rsp
    ; ... program logic ...
    leave
    ret

Скомпилировать:

yasm -f elf64 main.s
ld -o main main.o

Бежать:

./main
Segmentation fault(core dumped)

Я читаю, оставь это

mov esp,ebp
pop ebp

Но почему такой эпилог к ​​фрейму всплывающего стека и установленный указатель базового фрейма на базовый предыдущий фрейм приводит к ошибке сегментации?

Действительно, выполнение системного вызова exit завершает работу корректно.


person Bulat M.    schedule 18.09.2016    source источник
comment
_start не вызывается ядром, из него нельзя вернуться.   -  person Margaret Bloom    schedule 18.09.2016
comment
Не могли бы вы уточнить подробнее? Я думал, что это обычная рутина. Как правильно вернуться/выйти из него? Через системный вызов выхода? Пишите как ответ.   -  person Bulat M.    schedule 18.09.2016


Ответы (2)


Согласно ABI 1 стек на входе _start равен

Стек при входе в _start

Нет "обратного адреса".
Единственный способ выйти из процесса - через SYS_EXIT

xorl %edi, %edi   ;Error code
movl $60, %eax    ;SYS_EXIT
syscall

1 Раздел 3.4.1 Начальное состояние стека и регистра.

person Margaret Bloom    schedule 18.09.2016
comment
Это отвечает на мой вопрос. - person Bulat M.; 18.09.2016

Инструкция LEAVE определена так, чтобы не вызывать никаких исключений, поэтому она не может быть источником вашей ошибки. Вы должны использовать GDB. Отладчики бесценны в решении подобных проблем.

Вот что происходит:

$ gdb ./main
[...]
Программа получила сигнал SIGSEGV, ошибка сегментации.
0x0000000000000001 в ?? ()

(gdb) x /gx $rsp-8
0x7fffffffe650: 0x0000000000000001

Так что, скорее всего, ваша программа завершилась, но первое, что в стеке, это 0x0000000000000001. RET поместил это в регистр RIP, а затем произошел сбой, потому что этот адрес не отображается.

Я не пишу много кода для Linux, но могу поспорить, что для использования системного вызова выхода требуется _start. Единственный способ вернуться к полезному адресу — это если ядро ​​поместит где-нибудь функцию, которая сделает это за вас.

person icecreamsword    schedule 18.09.2016
comment
Да, прежде чем задать вопрос, я использовал gdb и показать 6 leave (gdb) n _start() at main.s:7 7 ret (gdb) n 0x0000000000000001 in ?? () Не постил, потому что не знаю, что?? означает. - person Bulat M.; 18.09.2016
comment
Я также думаю о настройке рипа в несопоставленное место в памяти, однако было бы неплохо знать точно. - person Bulat M.; 18.09.2016
comment
Это означает, что адрес не находится в допустимом модуле. Поскольку GDB не знает, какому модулю он принадлежит, он выводит ??. Здесь важно отметить, что 0x00000000000000001 не является действительным исполняемым адресом. Когда RIP указывает на недопустимый или неисполняемый адрес, это обычно вызвано либо RET на неверный адрес, либо косвенным JMP или CALL на неверный адрес. Если RET, как в данном случае, 8 байтов ниже вершины стека будут соответствовать RIP. Если JMP или CALL, иногда регистр будет соответствовать RIP. - person icecreamsword; 18.09.2016
comment
На самом деле вы ожидаете, что первым в стеке будет значение 1, поскольку именно здесь Linux передает значение argc, а программа, запущенная без каких-либо аргументов, имеет argc, равное 1. - person Ross Ridge; 18.09.2016
comment
Полезный совет о аргументах. Итак, чтобы получить argc и argv, нужно использовать смещения стека ebp + 8 и ebp + 16 соответственно? - person Bulat M.; 18.09.2016