Brief introduction to x86 using the _cdecl calling convention and linking with a CRT:
- Every function builds and breaks down its own stack frame.
- Caller pushes arguments onto the stack, calls the function, and then removes the arguments from the stack.
- Function arguments are pushed right-to-left.
- The return value of a function goes in eax.
- The x86 stack grows downwards.
Example
global _main
extern _printf
section .text ; non-writable, executable
; function putstr( const char* )
putstr:
push ebp ; set up a stack frame
mov ebp,esp ; this function is small enough + we're not using any local variables, so we could probably omit the frame pointer
; our arguments are [ebp+8], [ebp+12], ...
push dword [ebp+8]
push format_str
call _printf
add esp, 8 ; cdecl: remove our arguments from the stack
mov eax, 0 ; return 0
leave ; leave our stack frame
ret
; function main( )
_main:
push ebp ; set up a stack frame
mov ebp,esp
; move the next stack pointer to make room for one local variable
sub esp, 4
; copy the constant message pointer into our local variable
; (this obviously isn't necessary, just an example. p.s. mov is shit)
push my_message
pop [ebp-4] ; equivalently [esp+4] but it's nice to base locals off something that isn't going to change
; call our putstr!
push [ebp-4]
call putstr
add esp, 4 ; like popping, but into nowhere in particular.
mov eax, 1337 ; return
leave ; close off stack frame
ret
; Constants:
section .data ; on-executable
my_message: db "GARBAGE COLLECTION IS SHIT", 0x0D, 0x0A, 0 ; \r\n and null-terminated for printf
format_str: db "%s", 0
Building
Assemble:
nasm -fwin32 -o test.obj test.asm
For debugging with Dr.MinGW:
gcc -gcoff -O0 -o test.exe test.obj
Otherwise:
gcc -s -O2 -o test.exe test.obj
It’s nice to run echo %ERRORLEVEL%
to catch the return from main.