Сборка x86, ошибка сегментации

section .data
msg: db "hello!", 10, 0 ;my message

section .text
extern printf ;C printf function
global main
main:
    push ebp
    mov ebp, esp
    call print_string
    mov esp, ebp
    pop ebp
    ret ;end of program

print_string:
    pusha
    push msg
    call printf ;should print "Hello"
    popa
    ret ;return back to main

Когда я запускаю этот код, я получаю:
привет!
Ошибка сегментации (дамп ядра)

Что не так с кодом?


person gilianzz    schedule 29.07.2015    source источник
comment
Это не то, как работает ASM, у вас более одной ошибки. Кроме того, ваш вопрос не является вопросом. Советую взглянуть на скомпилированный asm-код с отладчиком.   -  person Konstantin W    schedule 29.07.2015
comment
Если это соглашение о вызовах C, вызывающая сторона несет ответственность за очистку стека. Вы оставили там указатель msg, поэтому ret потерпит неудачу.   -  person Weather Vane    schedule 29.07.2015
comment
Я добавил add esp, 4, и это исправило код. Итак, проблема была в том, что я поместил указатель msg в стек и так и не извлек его? Я предполагал, что printf выскочит.   -  person gilianzz    schedule 29.07.2015
comment
Я предполагал, что printf вытолкнет его. Это не так.   -  person Michael    schedule 29.07.2015


Ответы (2)


Подпрограмма print_string изменяет указатель стека, не восстанавливая его. Подпрограмма main имеет следующий вид:

push ebp    ;save the stack frame of the caller
mov ebp, esp    ;save the stack pointer
<code for subroutine>
mov esp, ebp    ;restore the stack pointer
pop ebp    ;restore the caller's stack frame
ret ;return to address pushed onto the stack by call

Точно так же подпрограмма print_string должна иметь тот же макет, сохраняя указатель стека и затем восстанавливая его перед reting. Любые подпрограммы, использующие стек, должны сохранять и восстанавливать указатель стека.

push ebp
mov ebp, esp
pusha
push msg
call printf ;should print "Hello"
add esp, 4
popa
mov esp, ebp
pop ebp
ret

Без сохранения указателя стека и его восстановления вызываемая подпрограмма модифицирует указатель стека, где инструкция call сохранила адрес возврата. Следовательно, когда встречается ret, выполнение переходит к неправильному адресу возврата, что приводит к ошибке сегментации. Подробнее об соглашениях о вызовах и функциях в ассемблере.

person automaton    schedule 30.07.2015
comment
Ваш ответ не решает реальной проблемы. Это создает дополнительные проблемы, потому что многие регистры будут повреждены! - person Sep Roland; 09.08.2015
comment
Я просто указал, что вызывает ошибку сегментации, и предложил соглашение о вызовах, чтобы предотвратить это. Если вы хотите решить настоящую проблему, будьте моим гостем. - person automaton; 14.08.2015
comment
Что насчет этого pop msg? В каком мире вы можете вытолкнуть мгновенное значение? - person Sep Roland; 15.08.2015

printf ожидает, что указатель помещается в стек для аргумента, но в соответствии с соглашением о вызовах C ваша задача — удалить этот аргумент из стека.

Вы пропустили это, и поэтому инструкция popa помещает неправильные значения во все GPRS, а инструкция ret использует исходное значение EAX в качестве адреса назначения, что вызывает ошибку сегментации.

Решение 1 очистка вручную

print_string:
 pusha
 push  msg
 call  printf   ;should print "Hello"
 add   esp, 4   ; <-- Clean-up
 popa
 ret            ;return back to main

Решение 2 с использованием пролога/эпилога в print_string

print_string:
 push  ebp
 mov   ebp, esp
 push  msg
 call  printf   ;should print "Hello"
 mov   esp, ebp ; <-- Clean-up
 pop   ebp
 ret            ;return back to main

Решение 2 возможно только потому, что printf — это хорошо работающая функция, которая сохраняет регистр EBP. При перемещении EBP в ESP каждый дополнительный элемент, помещенный между прологом и эпилогом, исчезает. Решение 2 может избавить вас от множества этих add esp, 4 инструкций (когда подпрограммы становятся длиннее).

person Sep Roland    schedule 15.08.2015