6

How can you determine if a variable is a local variable of the function or an argument passed to the function?

3
  • 2
    Needs more info: what are you analyzing, what do you mean by "variable" and what info do you have about it?
    – Igor Skochinsky
    Apr 11, 2013 at 15:56
  • Also it would be useful to know, what tool You use to RE, to point You to correct output structure\display options. Apr 11, 2013 at 15:58
  • I'm not using a tool, it's an assignment for class. I have a block of ASM and I am supposed to determine (If I understand the problem correctly) the calling convention and if variables (DWORDS in this case) are either local variables of the function or arguments passed into the function.
    – Jannu
    Apr 11, 2013 at 17:58

3 Answers 3

17

Parameters

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

Variables can really only be stored in three locations

  1. 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.

  2. global storage, generally in either the heap or in a segment of the executable binary mapped by the loader.

  3. 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.

Calling conventions

IA32 calling conventions

In rough order of likelihood to run into.

cdecl

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.

thiscall

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.

stdcall

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

fastcall

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.

pascal

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

Microsoft

The Microsoft x64 calling convention[9] (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.

Pretty much everyone else

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.[11] As in the Microsoft x64 calling convention, additional arguments are passed on the stack and the return value is stored in RAX.

ARM calling conventions

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.

PowerPC calling conventions

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.

MIPS calling conventions

O32

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.

N32 and N64

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.

1
  • +1 for a detailed answer! One remark: the described fastcall is the way ms implements it. Borland/Embarcadero implement fastcall differently: first 3 arguments are passed in EAX, EDX and ECX, remaining arguments go on stack (left to right)
    – Remko
    Apr 11, 2013 at 21:14
5

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 + ...]
2
  • 1
    The simplest counterexample being anything compiled with "gcc -fomit-frame-pointer"
    – Dougall
    Apr 12, 2013 at 5:44
  • 2
    Yes, arguments can be passed by register, or be referenced relative to ESP, or can be accessed via several other means. The "answer" above is the most common case and I assume is what Jannu's teacher was looking for. Apr 12, 2013 at 14:08
2

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.