Your function looks for its 2 args on the stack, above the return address. Notice the accesses to [ebp+8]
and [ebp+12]
, after setting up a traditional stack frame.
You don't declare args in asm; that's a high level concept that you implement yourself in the code in asm.
push
operates on the call stack, not exactly a stack data structure.
https://felixcloutier.com/x86/PUSH.html. It just decreases ESP and stores to [esp]
. It's doing that to save/restore the caller's EBP as part of making a traditional stack frame. This is super-basic stuff, read some more tutorials or guides if you haven't seen this yet.
You can easily just try the code and find what it does by calling it from a C program.
Except you can't because your asm won't assemble. .bits 32
is not a valid GAS directive, but this is pretty clearly GNU assembler (GAS) syntax otherwise. I'm not aware of any assemblers that have a .intel_syntax noprefix
directive other than GAS itself and clang's built-in assembler, and neither of them support .bits 32
. So I'm guessing this function was from a tutorial or something and was hand-ported to GAS from NASM syntax (bits 32
) or something without actually testing.
But you don't need and shouldn't use that directive, or the GAS equivalent .code32
. 32-bit code is already the default when assembling into a 32-bit object file, so .code32
would let you assemble 32-bit code into a 64-bit object file if you just used gcc foo.s
without -m32
, and then you'd have hard-to-debug problems are runtime instead of an obvious error at assemble time.
So remove the .bits 32
line, that solves the error message in the question.
To get rid of the warning messages, expand the one-liner I wrote in a comment by providing the proper headers and prototype.
#include <stdio.h>
int asm0(int,int);
int main(){
printf("%d\n", asm0(123,456));
}
The function is also buggy: it clobbers ebx
, which is a call-preserved register in the i386 System V calling convention. (Like in all the normal x86 calling conventions.)
gcc on Linux distros that enable default-PIE makes code that depends on EBX being call-preserved, and actually crashes. (you could still single-step it with a debugger, though, because the crash happens after asm0
returns.)
But you can make an executable that happens to work and will let you just run it to see which arg it returns:
peter@volta:/tmp$ gcc -g -Og -no-pie -fno-pie -m32 main.c asm0.s
peter@volta:/tmp$ ./a.out
456
asm0
returns its 2nd arg (by copying it to EAX, the return-value register). It loads it to EBX, then copies EBX to EAX, overwriting the result of loading the first arg.
You can watch this happen with gdb ./a.out
(gdb) layout reg
b main
r
si
(press return to single-step one instruction at a time through the code, including into your function)
GDB highlights which registers were changed on each step.
See also the bottom of https://stackoverflow.com/tags/x86/info for more debugging tips.
Further reading: