Reputation: 23
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
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 ret
urn 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
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