In this post, we’ll see one simple way to include assembly code in a C program. More specifically, we’ll see how to call functions written in assembly from C.
We’ll start by writing a simple program in C. Then, we’ll substitute one of the functions written in C with an equivalent one written in assembly in a different file. By including this file during compilation, we’ll be able to generate an executable that uses both C and assembly code.
C program
For our demonstration, we’ll use a simple program that checks whether 10 random numbers are odd or even.
#include <stdio.h> #include <stdlib.h> #include <time.h> int is_even(int n){ return n % 2 == 0; } int main(){ int n, i; time_t seed; srand((unsigned) time(&seed)); for (i=0; i<10; i++) { n = rand() % 10; if (is_even(n)) printf("%d is even\n", n); else printf("%d is odd\n", n); } return 0; }
The rand
function in C generates a pseudo-random number. To generate different values each time the program is executed, we need to vary the value of the seed, which is used by rand
. We do that by calling the srand
function and passing it a seed value. In this case, we use the actual time (returned by the time
function).
After compilation and execution, we’ll get:
$ gcc c_assembly_source.c -o main $ ./main 7 is odd 8 is even 7 is odd 1 is odd 7 is odd 5 is odd 9 is odd 6 is even 0 is even 6 is even
Assembly function
Now, we’ll write a function equivalent to is_even
, but written in assembly. We can name our function however we want. We’ll name it is_even_assembly
.
/* int is_even_assembly(int n) */ /* n in %rdi */ is_even_assembly: decl %edi /* Subtract one to n */ movl %edi, %eax /* Move 1st argument to return register %rax */ andl $1, %eax /* Get least significant bit */ ret /* return */
The assembly code is machine-dependent. Here, we’ll assume a x86 architecture which is the most common one. Following the x86 conventions, the first argument will be stored in the %rdi
register when the function is called. In our case, we’ll have n
stored in the low-order bytes of the %rdi
register (%edi
). Under the same conventions, the function return value is the one stored in the %rax
register after calling the ret
instruction.
We want to verify if n
is even. To do that, we’ll check its least-significant bit. For even numbers, it will be 0
. For odd numbers, it will be 1
. First, we decremented n
by one. That’s the same as flipping the last bit. If it was 0
, it will be 1
after decrementing n. If it was 1
, it will be turned to 0
. We do that because for C 0
represents false
and 1
(or any non-zero value) represents true
. After decrementing n
, we use a mask using the and
operator to get the least significant bit. Since we flipped the last bit before with the dec
operator, it will be 1
for even values. That will correspond to true
. For odd values, the last bit after decrementing will be 0
, which corresponds to false
.
Besides the function, we need to include a special instruction to make the function global
so that our C program can call it. The complete assembly code will look as follows:
.globl is_even_assembly /* int is_even_assembly(int n) */ /* n in %rdi */ is_even_assembly: decl %edi /* Subtract one to n */ movl %edi, %eax /* Move 1st argument to return register %rax */ andl $1, %eax /* Get least significant bit */ ret /* return */
Including the assembly function in C
To call our assembly function, we just need to declare it in our original C program. Next, we substitute all the prior function calls to is_even
with is_even_assembly
. The rest of the program remains unchanged.
#include <stdio.h> #include <stdlib.h> #include <time.h> int is_even_assembly(int n); int main(){ int n, i; time_t seed; srand((unsigned) time(&seed)); for (i=0; i<10; i++) { n = rand() % 10; if (is_even_assembly(n)) printf("%d is even\n", n); else printf("%d is odd\n", n); } return 0; }
See that we have to call the function in the C program as it was named in the assembly code file.
Compiling assembly and C code
The final step is to include both files during compilation. Assuming the assembly code is stored in the file is_even_assembly.s
and the main C program in c_assembly.c
, we’ll have:
$ gcc c_assembly.c is_even_assembly.s -o main $ ./main 9 is odd 3 is odd 2 is even 1 is odd 3 is odd 2 is even 5 is odd 8 is even 2 is even 3 is odd
And that’s it! Our C program called the function written in assembly successfully.
For further reference, you can check Combining Assembly Code with C programs.