Ошибка сегментации на языке ассемблера

Я изучаю AT&T на ассемблере x86. Я пытаюсь написать программу сборки, которая принимает целое число n, а затем возвращает результат (n/2+n/3+n/4). Вот что я сделал:

.text
.global _start
_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax
    mov $0, %esi
    movl $4, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $3, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $2, %ebp
    div %ebp
    addl %eax, %esi
    movl %esi, %eax
    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ret

Проблема в том, что я получаю ошибку сегментации. В чем проблема?

5 ответов

Решение

Я думаю, что код не работает здесь:

_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax

Так что вы push $24 (4 байта), а затем call profit, который толкает eip и прыгает на profit, Тогда вы выскакиваете значение eip в ebx и значение $24 в eax,

Тогда, в конце концов, если jg end ветви к end:тогда стек не будет содержать действительный адрес возврата и ret не удастся. Вам наверное нужно pushl %ebx там тоже.

    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ; `pushl %ebx` is needed here!
    ret
  1. ты используешь ecx без явной инициализации (я не уверен, что Linux будет гарантировать состояние ecx когда начинается процесс - похоже, это 0 на практике если не по правилам)
  2. когда программа принимает jg end переходите к концу процедуры, адрес возврата больше не находится в стеке, поэтому ret перенесет управление на какой-то мусорный адрес.

Вы, кажется, не выполняете вызовы функций правильно. Вам нужно прочитать и понять x86 ABI ( 32-битный, 64-битный), в частности разделы "Соглашение о вызовах".

Кроме того, это не ваша непосредственная проблема, но: не пишите _start, записывать main как будто это была программа на Си. Когда вы начнете делать что-то более сложное, вы захотите, чтобы библиотека C была доступна, а это значит, что вы должны позволить ей инициализировать себя. Кроме того, не делайте свои собственные системные вызовы; вызвать обертки в библиотеке C. Это изолирует вас от низкоуровневых изменений в интерфейсе ядра, гарантирует, что errno доступно и тд.

Ваша проблема в том, что вы извлекаете адрес возврата из стека, и когда вы переходите к концу, вы не восстанавливаете его. Быстрое решение добавить push %ebx там тоже.

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

Вместо того, чтобы сделать это, чтобы получить аргумент, а затем восстановить адрес возврата позже

popl %ebx
popl %eax

Вы должны сделать это и оставить адрес возврата и аргументы там, где они есть.

movl 4(%esp), %eax

и избавиться от кода, который возвращает адрес возврата обратно в стек. Затем вы должны добавить

subl $4, %esp

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

Мне кажется, что у вас есть один pushl, прежде чем вы коллируете прибыль, а затем первое, что делает прибыль, - это две инструкции popl. Я ожидал бы, что это вытолкнет значение, которое вы поместили в стек, а также код возврата, чтобы ваш ret не работал.

push и pop должно быть одинаковое количество раз.

call помещает адрес возврата в стек.

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