开发者

Calling multiple functions with the same arguments in assembly efficiently

开发者 https://www.devze.com 2023-03-17 15:09 出处:网络
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)

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 effic开发者_JAVA百科ient 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:

void myfunction(void)
{
    call_some_func(1, 2, 3);
    call_another_func(1, 2, 3);
    call_more_stuff(1, 2, 3);
    call_even_more_stuff(0, 1, 2, 3);
    call_yet_more(2, 3);
    ...
}

as a sequence like (AT&T syntax, 32bit x86, I'm a UN*X guy):

myfunction:
    pushl   $3
    pushl   $2
    pushl   $1
    call    call_some_func
    call    call_another_func
    call    call_more_stuff
    pushl   $0
    call    call_even_more_stuff
    addl    $8, %esp
    call    call_yet_more_stuff
    ...
    addl    $8, %esp
    ret

The calling conventions (for, as Microsoft calls it, cdecl style, as is also used on the UN*X i386 ABI) for stack-based parameter passing all have the property that the stackpointer is unchanged after call returns. That means if you pushed a series of arguments onto the stack and performed a call, 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 $..., %esp directly after a call), 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:

myfunction:
    stmfd   sp!, {lr}
    mov    r0, #1
    mov    r1, #2
    mov    r2, #3
    stmfd  sp!, {r0-r2}
    bl     call_some_func
    ldmfd  sp, {r0-r2}
    bl     call_another_func
    ldmfd  sp, {r0-r2}
    bl     call_more_stuff
    ldmfd  sp!, {r1-r3}
    mov    r0, #0
    stmfd  sp!, {r2, r3}
    bl     call_even_more_stuff
    ldmfd  sp!, {r0, r1, lr}
    b      call_yet_more_stuff

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:

void myfunc(void *a1, void *a2, void *a3)
{
    func1(a1, a2, a3);
    func2(a1, a2, a3);
    func3(a1, a2, a3);
}

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 myfunc is topmost on the stack, and the arguments follow; so use the clobber registers (%eax, %ecx and %edx on 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:

myfunc:
    popl    %eax          ; return address now in EAX
    popl    %ecx          ; arg[1]
    popl    %edx          ; arg[2]
    xchgl   %eax, (%esp)  ; swap return address and arg[3]
    pushl   %eax          ; re-push arg[3]
    pushl   %edx          ; and arg[2]
    pushl   %ecx          ; and arg[1]
    call    func1
    call    func2
    call    func3
    popl    %ecx          ; pop of dummy, gets %esp to pre-call
    jmpl    0xc(%esp)     ; use jmpl to return - address at "bottom"

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.


There is no problem to use stack memory for saving temporary variables, but keep in mind that this is not standard calling convection, so all calling functions must be made in assembler.

More conventional way is to use a peace of allocated memory long 3 * 4 bytes (x,y,z variables) and use a pointer reference on it as input for calling functions.


Technically No, but usually Yes

The function is allowed to change the stack parameters; they are just like local frame objects.

However, usually they don't. So, if you are calling your own functions you should be safe, particularly if you add a comment to the function definition.

When calling library functions, you will find out quickly if your parameters get changed. If not, you are safe unless some future library revision starts frobbing the parameters. This is unlikely but since you are violating the API you won't really be able to complain. It's a risk, albeit a quite small one.

0

精彩评论

暂无评论...
验证码 换一张
取 消