Jackywathy
Jackywathy

Reputation: 23

Assembly passing variables through the stack

So, after dabbling in a bit of assembly for fun, I'm now stuck at calling procedures.

...
_start:
    push dword len
    push dword msg

    call print

    mov eax, SYS_EXIT
    mov ebx, 0
    int 80h

print: ; *char (message), int (len) -> push len, then message
    mov eax, SYS_WRITE
    mov ebx, STDOUT
    pop ecx
    pop edx
    int 80h
    ret

When I run this piece of assembly

nasm -f elf program.asm && ld -m elf_i386 -o program program.o && ./program

It prints out all the contents of the program then seg faults, while if i replace the "call print" with the contents of the print function, it works fine.

Upvotes: 1

Views: 135

Answers (2)

rcd
rcd

Reputation: 112

The code below is the way you should write it:

_start:
    push dword len
    push dword msg
    call print
    add esp, 8 ; equivalent to 2 32-bit POP instructions
               ; (effectively "undoes" the above PUSH instructions
               ;  to restore the stack to its original state)

    mov eax, SYS_EXIT
    mov ebx, 0
    int 80h

print: ; *char (message), int (len) -> push len, then message
    mov eax, SYS_WRITE
    mov ebx, STDOUT
    mov ecx, [esp+4]  ; load "msg" from stack using an offset from ESP
    mov edx, [esp+8]  ; load "length" from stack using an offset from ESP
    int 80h
    ret

The problem was that the stack did not point to where it should. You have to remember the last-in, first-out nature of the stack, and also consider that the call and ret instructions affect the stack pointer. When you call a function, the return address gets pushed onto the stack, so when you do a pop inside of print, you're actually popping the return value off the stack, which not only gives you the wrong value, but also messes up your ability to return later.

The correct way to retrieve parameters passed to a function on the stack is through an offset from the stack pointer (ESP). The first parameter will be found at ESP + 4 (after the 4-byte return address that is pushed onto the stack by call). For additional information, you can look up the STDCALL and CDECL calling conventions that are commonly used by C code.

Upvotes: 2

rkhb
rkhb

Reputation: 14409

The first POP pops the return addres, the second POP pops the address of msg. If you don't mess up the int 80h call you'll get at least a segmentation fault when the function tries to return.

The relevant values can be found behind the return address, here esp+4 and esp+8. You can access this address directly with ESP+xx. When you build more sophisticated procedure you might want to evade to EBP but do it with ESP for the moment:

SYS_EXIT  equ 1
SYS_WRITE equ 4
STDOUT    equ 1

segment .data

    msg db  `Hello world!\n`        ; Backspaces for C-like escape-sequences ("\n")
    len equ $- msg

section .text

    global _start

_start:
    push dword len
    push dword msg

    call print
    ; and the stack?

    mov eax, SYS_EXIT
    mov ebx, 0
    int 80h

print: ; *char (message), int (len) -> push len, then message
    mov eax, SYS_WRITE
    mov ebx, STDOUT
    mov ecx, [esp+4]
    mov edx, [esp+8]
    int 80h
    ret

Upvotes: 1

Related Questions