Abid Rahman K
Abid Rahman K

Reputation: 52646

Accessing function parameters in C++ from assembly in IA-32

I have been learning IA-32 assembly programming. So I would like to write a function in assembly and call it from C++.

The tutorial I am following is actually for x64 assembly. But I am working on IA-32. In x64, it says function arguments are stored in registers like RCX, RDX, R8, R9 etc.

But on searching a little bit, I could understand in IA-32, arguments are stored in stack, not in registers.

Below is my C++ code :

#include <iostream>
#include <conio.h>

using namespace std;
extern "C" int PassParam(int a,int b);

int main()
{
    cout << "z is " << PassParam(15,13) << endl;
    _getch();
    return 0;
}

Below is assembly code for PassParam() function (it just add two arguments, that's all. It is only for learning purpose) :

PassParam() in assembly :

.model C,flat
.code
PassParam proc
    mov eax,[ebp-212]
    add eax,[ebp-216]
    ret
PassParam endp
end

In my assembly code, you can see I moved first argument from [ebp-212] to eax. That value is obtained as follows :

I wrote PassParam() function in C++ itself and disassembled it. Then checked where ebp is and where is second argument stored (arguments are stored from right to left). I could see there is a difference of 212, so that is how i got that value. Then as usual, first argument is stored 4 bytes later. And it works fine.

Question :

Is this the correct method to access arguments from assembly ? I mean, is it always [ebp-212] where argument stored?

If not, can anyone explain the correct method to pass arguments from C++ to assembly ?

Note :

I am working with Visual C++ 2010, on Windows 7 machine.

Upvotes: 2

Views: 5632

Answers (1)

Necrolis
Necrolis

Reputation: 26171

On 32bit architectures, it depends on the calling convention, Windows for example has both __fastcall and __thiscall that use register and stack args, and __cdecl and __stdcall that use stack args but differ in who does the cleanup. MSDN has a nice listing here (or the more assembly orientated version). Note that FPU/SSE operations also have their own conventions.

For ease and simplicity, try use __stdcall for everything, this allows you to use stack frames to access args via MOV r32,[EBP+4+(arg_index * 4)], or if you aren't using stack frames, you can use MOV r32,[ESP+local_stack_offset+(arg_index * 4)]. The annotated C++ -> x86 Assembly example here should be of help.


So as a simple example, lets say we have the function MulAdd in assembly, with the C++ prototype int __stdcall MulAdd(int base, int mul, int add), it would look something like:

MOV EAX,[ESP+4] //get the first arg('base') off the stack
MOV ECX,[ESP+8] //get the second arg('mul') off the stack
IMUL EAX,ECX //base * mul
MOV ECX,[ESP+12] //get arg 3 off the stack
ADD EAX,ECX
RETN 12 //cleanup the 3 args and return

Or if you use a stack frame:

PUSH EBP
MOV EBP,ESP //save the stack
MOV EAX,[EBP+8] //get the first arg('base') off the stack
MOV ECX,[EBP+12] //get the second arg('mul') off the stack
IMUL EAX,ECX //base * mul
MOV ECX,[EBP+16] //get arg 3 off the stack
ADD EAX,ECX
MOV ESP,EBP //restore the stack
POP EBP
RETN //return to caller

Using the stack frame avoids needing to adjust for changes made to the stack by PUSH'ing of args, spilling or registers or stack allocations made for local variables. Its downside is that it reduces the number of registers you have to work with.

Upvotes: 8

Related Questions