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