Ошибка сегментации на языке ассемблера
Я изучаю 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
- ты используешь
ecx
без явной инициализации (я не уверен, что Linux будет гарантировать состояниеecx
когда начинается процесс - похоже, это0
на практике если не по правилам) - когда программа принимает
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 помещает адрес возврата в стек.