June 19, 2016

Calling C Function from Assembly Code

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.

main.s:

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.

main.s:

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

© Hibariya 2020

Powered by Hugo & Kiss.