I am inside a function with some arguments. Let’s say:
__cdecl function(int a,int b,long c)
(Let’s forget __stdcall for now)
.
Now, in assembly I want to call multiple functions which take the a,b,c arguments.
First thing I know I need to be aware is that if I do “calls”, the stack will be dislocated in relation to how I got it since it will take first the current EIP and EBP. I’m assuming that I can store old EIP and old EBP and calculate the current EIP and EBP, replace them in the stack and then just jump. It’s ok if this assumption is wrong and maybe it would bring more problems (feel free to point it out) but it’s irrelevant because I can push the arguments again. Now the real question: Can I by just having pushed the arguments once (or using the stack as I got it) call multiple times several functions with the same stack? Or would it cause some problem?
Example:
push 2
push 3
push 4
call X
call Y
Call Z
add esp, 12
So, I want X,Y,Z to have the same arguments and this way, particularly for multiple functions it would be much more efficient than with “normal code” that would push all the argument once for each function call.
On a fully stack-based calling convention, there’s nothing wrong with implementing the assembly side of calls like:
as a sequence like (AT&T syntax, 32bit x86, I’m a UN*X guy):
The calling conventions (for, as Microsoft calls it,
cdeclstyle, as is also used on the UN*X i386 ABI) for stack-based parameter passing all have the property that the stackpointer is unchanged aftercallreturns. That means if you pushed a series of arguments onto the stack and performed acall, they will still be on the stack after whatever function you called returns. So there’s nothing stopping you from re-using these in-place; as shown, in some cases you might even be able to re-use what’s already on the stack if you’re calling funcs with more/less arguments than you’ve used in previous calls.After a function returns, the stack is again yours; you don’t have to clean up (do an
addl $..., %espdirectly after acall), if what’s already on there is useful to you just keep it.This obviously doesn’t work the same way for register-based function calling. Although, if your CPU architecture allows a multi-register load/store of sorts, you might still be able to use the thing. On ARM, for example, the above can be made into:
I.e. you keep the stuff on the stack and load it from there without changing the stackpointer for the loads (the
sp!on ARM makes the difference between changing and just using the stack register).In the end, it’ll be a good idea to create a C version of the code, run it through a highly optimizing compiler for your platform / CPU / calling convention and check out the code generated. These days, compilers have become quite good at figuring out such opportunities for re-using things.
Edit:
If you’re thinking of the following:
then what you can do is to “play towers-of-hanoi” with the stack and re-order it; the return address into the caller of
myfuncis topmost on the stack, and the arguments follow; so use the clobber registers (%eax,%ecxand%edxon UN*X) to temporarily store values while you move the return address to the very bottom of the stack. With three args that’s easy enough as a single rounds of “hanoi” will do:Edit2: I’ve initially made a mistake here using a nonvolatile register (
%ebx) to hold the return address; as correctly remarked by commentators that’d clobber the value in the register and cause problems to our caller. To prevent this, the above method of re-ordering things on the stack can be used.