Calling C functions from x86 assembly language

For x86_64, note that you have to be careful with some extra things:

  • the stack must be 16-bit aligned before making C calls.

    If you define your function in assembly, the function that called your function put the return value on stack, misaligning it, and so you have to subtract 8 more byte somehow, usually push %rbp.

    This got me here for example: Why does calling the C abort() function from an x86_64 assembly function lead to segmentation fault (SIGSEGV) instead of an abort signal?

  • if you are doing a call from assembly for some reason (TODO why would you ever want to do that?) you have to worry about:

    • marking all the non-callee saved registers as clobbered
    • the red zone for your arguments

    Here is an example: Calling printf in extended inline ASM


I'm going from memory here, so I may be off slightly on a detail or two. However, it should I hope be enough to get you going in the right direction.

You're going to need to tell the GCC assembler that your routine printSomething() is not defined in your assembly file. In 'C' you would use the extern keyword. For assembly you will need to use .globl.

.globl printSomething

If you are using a different assembler than GCC's, the keyword may be different.

The next big question is 'how do I pass the arguments'? This very much depends upon your processor AND OS. Since the title of your question indicates x86, I am going to assume that you are using either the 16-bit or 32-bit modes and the standard x86 ABI (as opposed to x86-64 which is also differs between Windows and Linux). The C parameters are passed to the called routine by pushing them onto the stack. They are pushed onto the stack from right to left.

Thus,

printSomething (arg1, arg2, arg3, arg4);

translates to ...

pushl arg4
pushl arg3
pushl arg2
pushl arg1
call  printSomething
addl  $0x10, %esp

You may be asking yourself, what is this

addl $0x10, %esp

? We passed (aka pushed) four 32-bit arguments to the routine (onto the stack). Although the routine knows to expect those arguments, it is NOT responsible for popping them off the stack. The caller is responsible for that. So, after we return from the routine, we adjust the stack pointer to discard the four 32-bit arguments we previously pushed onto the stack.

In the above example, I am assuming that we are operating in 32-bit mode. If it were 16-bit mode, it would be ...

pushw arg4
pushw arg3
pushw arg2
pushw arg1
call  printSomething
addw  $0x8, %sp

I realize that in your example, printSomething() only takes one (1) argument and in my example I used four (4). Just adjust my example as is needed.

For the final steps, you will need to compile both your C and assembly files into object files, link the object files and then execute.

I hope this helps.

Tags:

C

Assembly

X86