Calling C Function from Assembly Code
Posted on by Gentaro "hibariya" Terada
I'm developing a tiny kernel to learn how software works from the bottom to up. That's why I write assembly code. However, doing everything with that is very hard. So I'd like to use C or higher level programming language as far as possible.
By the way, there are many calling conventions to call C function. Which convention can be used depends on CPU architecture and compiler. Today I tried two of them.
cdecl
The cdecl can be used with x86 (32-bit) CPU and GCC. In cdecl, arguments are passed via the stack. Integer or pointer values are returned in the EAX register.
#include
int add_print(int a, int b) {
int c = a + b;
printf("%d + %d = %d\n", a, b, c);
return c;
}
There is a C function named add_print
.
It takes 2 integer arguments and returns an integer value.
Let's call this function from assembly code.
global main
extern add_print
section .text
main:
; int eax = add_print(1, 2); // => 3
push dword 2
push dword 1
call add_print
add esp, 8
; add_print(2, eax); // => 5
push dword eax
push dword 1
call add_print
add esp, 8
; return 0;
mov eax, 0
ret
The first block of main
is equivalent to add_print(1, 2)
in C.
The argumetns are pushed into the stack with push
instruction.
Since arguments are passed in the reverse (right-to-left) order, in this case, first argument for this function is 1 and second one is 2.
Needless to say, call
instruction invokes add_print
function.
Finally, instruction add esp, 8
removes the arguments from the stack.
ESP is a register which refers to the current stack pointer.
Since the stack grows from higher addresses to lower addresses, adding 8 to the ESP means that getting rid two values from top of the stack.
In the second block of main
, the value of EAX is used as one of the arguments for add_print
.
It's the return value of first add_print
call.
The original source code is here.
System V AMD64 ABI
This can be used with x86-64 CPU and GCC. In this calling convention, first six integer or pointer arguments are passed via the register RDI, RSI, RDX, RCX, R8 and R9. Additional arguments are passed via the stack and return value is stored in the RAX register.
The following example calls a function take_many(int a, int b, int c, int d, int e, int f, int g, int h)
from assembly code.
global main
extern take_many
bits 64
section .text
main:
; take_many(1, 2, 3, 4, 5, 6, 7, 8);
push 8
push 7
mov r9, 6
mov r8, 5
mov rcx, 4
mov rdx, 3
mov rsi, 2
mov rdi, 1
call take_many
add rsp, 16
; return 0;
mov rax, 0
ret