user9623401
user9623401

Reputation:

what's the purpose of pushing address of local variables on the stack(assembly)

Let's there is a function:

int caller()
{
   int arg1 = 1;
   int arg2 = 2
   int a = test(&arg1, &arg2)
}
test(int *a, int *b)
{
    ...
}

so I don't understand why &arg1 and &arg2 have to be pushed on the stack too like this

enter image description here

I can understand that we can get address of arg1 and arg2 in the callee by using

movl  8(%ebp), %edx
movl  12(%ebp), %ecx

but if we don't push these two on the stack, we can also can their address by using:

leal 8(%ebp), %edx
leal 12(%ebp), %ecx 

so why bother pushing &arg1 and &arg2 on the stack?

Upvotes: 3

Views: 425

Answers (2)

Afshin
Afshin

Reputation: 9173

if you access arg1 and arg2 directly, it means you are accessing a portion of stack that does not belong to this function. This is somehow what happens when someone uses a buffer overflow attack to access additional data from calling stack.

When your call has arguments, arguments are pushed into stack(in your case &arg1 and &arg2) and function can use them as valid list of arguments for this function.

Upvotes: 0

Peter Cordes
Peter Cordes

Reputation: 364128

In the general case, test has to work when you pass it arbitrary pointers, including to extern int global_var or whatever. Then main has to call it according to the ABI / calling convention.

So the asm definition of test can't assume anything about where int *a points, e.g. that it points into its caller's stack frame.

(Or you could look at that as optimizing away the addresses in a call-by-reference on locals, so the caller must place the pointed-to objects in the arg-passing slots, and on return those 2 dwords of stack memory hold the potentially-updated values of *a and *b.)

You compiled with optimization disabled. Especially for the special case where the caller is passing pointers to locals, the solution to this problem is to inline the whole function, which compilers will do when optimization is enabled.

Compilers are allowed to make a private clone of test that takes its args by value, or in registers, or with whatever custom calling convention the compiler wants to use. Most compilers don't actually do this, though, and rely on inlining instead of custom calling conventions for private functions to get rid of arg-passing overhead.

Or if it had been declared static test, then the compiler would already know it was private and could in theory use whatever custom calling convention it wanted without making a clone with a name like test.clone1234. gcc does sometimes actually do that for constant-propagation, e.g. if the caller passes a compile-time constant but gcc chooses not to inline. (Or can't because you used __attribute__((noinline)) static test() {})


And BTW, with a good register-args calling convention like x86-64 System V, the caller would do lea 12(%rsp), %rdi / lea 8(%rsp), %rsi / call test or something. The i386 System V calling convention is old and inefficient, passing everything on the stack forcing a store/reload.

You have basically identified one of the reasons that stack-args calling conventions have higher overhead and generally suck.

Upvotes: 4

Related Questions