How can you determine if a variable is a local variable of the function or an argument passed to the function?
Not only does it depend on the platform but different functions have different calling conventions. The calling convention basically tells you how you know where the arguments are. It says nothing about the local function stack frame layout.
It's also extremely important to understand that when a function or method can be proven by the compiler or linker to not be accessed by code outside of what the compiler can see in the current translation unit or the linker in the current binary they are free to do whatever they want. This includes passing arguments in manners which do not correspond the the ABI. This is an even larger problem with the rising popularity of link time code generation.
The ABI is basically the interface which all binaries on platform promises to adhere to so that there can be a guarantee that binaries written in different languages can interact. However if your binary does not export a function it does not usually need to adhere to the ABI.
Variables can really only be stored in three locations
they can be stored on the stack in the local stack frame of the current function. This can be seen when the access is done relative to the stack pointer and where the offset is within the function stack frame and antedates the saved local registers. This usually means that the variable antedate the return address for calling conventions without link registers. Accesses where the offset precedes the return address is generally arguments. Calling conventions with link registers don't store return addresses on the stack unless they have to so variables antedate the saved registers portion of the stack frame and stack based arguments precede the saved registers portion of the stack frame.
global storage, generally in either the heap or in a segment of the executable binary mapped by the loader.
registers, this is generally used for variables which are used very frequently in the function since the register banks is the fastest storage available to the CPU. Variables can also be kept in registers for the duration of the function if the compiler determines that the CPU has enough registers to store all variables in the register bank. The optimization of which registers store what and when is called register allocation. It's also important to realize that variables that go out of scope or are unused later on in a function frees up registers. This means that one register can map to different variables at different stages of a function.
IA32 calling conventions
In rough order of likelihood to run into.
In cdecl, subroutine arguments are passed on the stack. Integer values and memory addresses are returned in the EAX register, floating point values—in the ST0 x87 register. Registers EAX, ECX, and EDX are caller-saved, and the rest are callee-saved. The x87 floating point registers ST0 to ST7 must be empty (popped or freed) when calling a new function, and ST1 to ST7 must be empty on exiting a function.
On the Microsoft Visual C++ compiler, the this pointer is passed in ECX and it is the callee that cleans the stack, mirroring the stdcall convention used in C for this compiler and in Windows API functions. When functions use a variable number of arguments, it is the caller that cleans the stack.
Callee is responsible for cleaning up the stack, but the parameters are pushed onto the stack in right-to-left order, as in the _cdecl calling convention. Registers EAX, ECX, and EDX are designated for use within the function. Return values are stored in the EAX registe
Passes the first two arguments (evaluated left to right) that fit into ECX and EDX. Remaining arguments are pushed onto the stack from right to left.
Parameters are pushed on the stack in left-to-right order (opposite of cdecl), and the callee is responsible for balancing the stack before return.
AMD64 calling conventions
The Microsoft x64 calling convention (for long mode on x86-64) uses registers RCX, RDX, R8, R9 are used for the first four integer or pointer arguments (in that order left to right), and XMM0, XMM1, XMM2, XMM3 are used for floating point arguments. Additional arguments are pushed onto the stack (right to left). Integer return values (similar to x86) are returned in RAX if 64 bits or less. Floating point return values are returned in XMM0. Parameters less than 64 bits long are not zero extended; the high bits contain garbage.
Followed on Solaris, GNU/Linux, FreeBSD, and other non-Microsoft operating systems. The first six integer or pointer arguments are passed in registers RDI, RSI, RDX, RCX, R8, and R9, while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for floating point arguments. For system calls, R10 is used instead of RCX. As in the Microsoft x64 calling convention, additional arguments are passed on the stack and the return value is stored in RAX.
r14 is the link register, r12 is the Intra-Procedure-call scratch register, r0 to r3 are used to hold argument values passed to a subroutine, and also hold results returned from a subroutine.
More than 4 arguments and they get pushed on the stack.
Since the PowerPC has so many GPRs (32 compared to ia32's 8), arguments are passed in registers starting with gpr3. Registers gpr3 through gpr12 are volatile (caller-save) registers that (if necessary) must be saved before calling a subroutine and restored after returning. Variable argument count functions store the argument on the stack for the callee.
the first four arguments to a function in the registers $a0-$a3; subsequent arguments are passed on the stack. Space on the stack is reserved for $a0-$a3 in case the callee needs to save its arguments, but the registers are not stored there by the caller. The return value is stored in register $v0; a second return value may be stored in $v1.
pass the first eight arguments to a function in the registers $a0-$a7; subsequent arguments are passed on the stack. The return value (or a pointer to it) is stored in the registers $v0; a second return value may be stored in $v1. In both the N32 and N64 ABIs all registers are considered to be 64-bits wide.
While this answer is certainly not true in all situations, the answer for which your teacher is probably looking:
- Local variables are in the form
[EBP - ...]
- Passed arguments are in the form
[EBP + ...]
I'm not sure what you mean by variable, I'll just assume it's the value of a register or a stack address at some point, during the execution of the function body.
If it was passed by argument, its value was defined by the caller via stack or register (check here for the different calling conventions). If not, then it was defined by the function body itself.