Quote:
Originally Posted by hda7
I'll probably end up linking against libc and using malloc, but I am not sure how to call C code from assembly.
|
There are various places you can read about the ABI for x86 or x86_64. But that isn't the method I would advise for getting this info, because you might get the wrong version or misunderstand it.
The easy way is to write a trivial C function to call the C function you want to call, then use gcc -S to compile that trivial function into a .s file giving the asssembly code for the call you want.
For example to see a meaninful call to malloc
Code:
void foo()
{
*(int*)malloc(sizeof(int)) = 7;
}
The relevant part of the .s file that resulted (in x86_64) was
Code:
movl $4, %edi
call malloc
movl $7, (%rax)
I know that $4 represents my sizeof(int)
I know that ordinary integer and pointer results of functions are returned in rax, which I see confirmed by the way rax is used after the function call in the way my C code used the result of the function call.
I had forgotten (debugging more Win64 asm code than Linux asm code lately) that the leftmost ordinary arg is passed in rdi (in Win64 it is in rcx), but that was easy to discover by seeing what was done with that $4.
For x86 (32 bit) I recompiled that with gcc -m32 -S
Code:
subl $8, %esp
movl $4, (%esp)
call malloc
movl $7, (%eax)
So you can see args are passed on the stack, not in registers (simpler, but rotten for performance).
Notice also that esp is kept divisible by 8. So even though just 4 bytes are passed on the stack, the code allocates space for 8.
A couple more details you might need to understand by seeing the whole function
Code:
foo:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $4, (%esp)
call malloc
movl $7, (%eax)
leave
ret
The return address is 4 bytes put on when esp is divisible by 8, so on entry to the function esp is always divisible by 4 but never by 8. After pushing ebp, esp is again divisible by 8.
It is typical to delay cleanup of args on the stack. The called function does
not clean up the stack and in this case the calling code does not directly clean it up either. The
leave instruction cleans the stack of any excess pushed onto it during this function.
If your call to a C function is inside a loop, you need to be more careful about managing the amount of stack used. But it is generally wasteful to clean the stack after each call.