nasm не может вызвать функцию в функции

#assembly #x86 #nasm

#сборка #x86 #nasm

Вопрос:

Итак, я недавно снова перешел на nasm и попытался создать некоторые базовые вещи (putc и puts).

Putc работает нормально, но проблема в том, что после того, как я вызываю putc in puts , ret in putc не возвращается к ip отправленному в стек by puts , и поэтому больше никаких инструкций не выполняется puts (отлажена эта часть в gdb).

 msg db "Welcome!", 0ah, 0dh, 0h

putc:
    push ebp
    mov esp, ebp
    
    mov ah, 0ah
    ; al is set before
    mov bh, 0
    mov bl, 0
    mov cx, 1

    int 10h
    ret

puts:
    push ebp
    mov esp, ebp
    
    mov ebx, msg
    dec ebx
puts_l:
    inc ebx
    
    mov al, [ebx]
    call putc

    cmp al, 0h
    jne puts_l

    ret
 

Это явно не самое лучшее, но я думаю, что у меня где-то недоразумение. Я мог бы представить, что регистр будет перезаписан putc, но это не объясняет, почему ret in putc не возвращается к puts

Я также должен упомянуть, что я работаю с x86.

Комментарии:

1. Возможно, вы нажимаете на ebp слишком часто. Это может несколько испортить ожидание того, ret что ip это следующий элемент в стеке

2. В этом действительно может быть какой-то смысл. Итак, что мне делать, если я хочу создать фреймы стека? Это все, что я знаю, как это сделать. Я думал, что ret сделает всю работу за меня, чтобы очистить фрейм стека. Если нет, то мне, возможно, придется воспользоваться leave .

3. ret это просто то, как пишется x86 pop ip (или pop eip в 32-битном коде). Он волшебным образом не знает, куда возвращаться, [E / R] SP уже должен указывать на обратный адрес.

4. Да, но не следует вызывать push текущий ip, а затем изменять ip на новый адрес? Вот что меня немного смущает в вашем комментарии.

5. Да, call выталкивает текущий eip (так называемый обратный адрес ) и переходит к вызываемому адресу, но первое, что на этом адресе ваше push ebp , поэтому, когда вы это делаете ret , eip загружается из этого pushed ebp вместо ожидаемого обратного адреса над ним.

Ответ №1:

Когда вы это сделаете

 push ebp
mov esp, ebp
 

esp больше не указывает на отправленный ebp и обратный адрес, это проблема, которую нельзя решить, используя LEAVE в функции epilogue . BTW LEAVE должен быть сопряжен с ENTER.

Если вам действительно нужен фрейм стека (например, для определения локальной переменной памяти), скелет может выглядеть следующим образом:

 Function:
    PUSH EBP    
    MOV EBP,ESP 
    SUB ESP,4   ; The local variable is now addressable as [ESP] alias [EBP-4].

    ; Here is the Function body which can use local variable(s).

    MOV ESP,EBP ; Discard local variable, ESP will point to the saved EBP. 
    POP EBP     ; Restore EBP which might be used as parent's frame pointer.
    RET
 

Поскольку ваша программа не использует локальные переменные, это может быть

 mov esi, msg       ; Address of the ASCIIZ string.
call puts          
jmp $              ; Program ends here.

puts: ; Function which displays ASCIIZ string at ESI.
    lodsb         ; Load AL from [DS:ESI], increment ESI.
    cmp al, 0h
    je puts_2
    call putc     ; Display a nonzero character in AL.
    jmp puts
puts_2: ret

putc:  ; Function which displays a character in AL.
    mov ah, 0ah ; WRITE CHARACTER.   
    mov bh, 0   ; Videopage 0.
    mov bl, 0   ; Colour in graphic mode.
    mov cx, 1   ; Number of times to write character
    int 10h     ; Invoke BIOS videofunction.
    ret

msg db "Welcome!", 0ah, 0dh, 0h ; The displayed ASCIIZ string.