How to call Assembly function from C code ?

In this post, we use ARM are reference architecture for writing the assembly code but the basic way of accessing functions defined in assembly program from C should remain almost similar ( except instructions ) for other architectures.

Q. Write a C program helloworld.c which would call a function “simpleAdd” defined in assembly code ?

First make a layout of C program as,

$ vim helloworld.c
#include <stdio.h>

extern void simpleAdd(void);

int main(int argc, char **argv) {
        simpleAdd();
        printf("HelloWorld after addtion from assembly program");
        return 0;
}

Here, we declared simpleAdd function to be written in assembly and called from inside main.

Now, we will write the assembly code for this simpleAdd function inside add_assembly.S file as,

$ vim add_assembly.S
        .globl simpleAdd
        .text
        .thumb

        .type    simpleAdd,%function
simpleAdd:
        .fnstart

        MOV  R0, #1
        MOV R1, #2
        ADD R2, R1, R0

        BX lr

        .fnend

as you can see above, we defined a function label as “simpleAdd” and used “globl” to export from here so it can be called from C program. The mov and add instruction just defines and adds two number with final addition result in R2 register.

Note the end of function with “BX lr” as this is required to return the execution back to C code since Link Register “LR” contains the return address.

You can compile this C and Assembly code using ARM GCC compiler and link those objects to create final executable as,

Compile assembly code, this creates add_assembly.o as output object file.

$ arm-linux-gnueabihf-gcc -g -c add_assembly.S

Compile helloworld.c program and create respective object file helloworld.o as,

$ arm-linux-gnueabihf-gcc -g -c helloworld.c

Now, link both object files add_assembly.o and helloworld.o to generate the executable as,

$ arm-linux-gnueabihf-gcc -g -o helloworld helloworld.o add_assembly.o -static

Notice: here we have used “-g” to add debug symbols to debug with GDB and its not necessary.

Prerequisite : Download and setup the ARM toolchain as mentioned in “Debugging ARM binary on Ubuntu Host using qemu ARM and GDB”

Below we will just show, how we can use GDB to verify program can work properly by executing on desktop itself using qemu. You need to refer “Debugging ARM binary on Ubuntu Host using qemu ARM and GDB” for detailed understanding of using qemu.

On First terminal type command,

$ qemu-arm -L gcc-arm-none-eabi-10-2020-q4-major/lib -g 8091 ./helloworld

On Second terminal, run GDB to debug the program as,

$ arm-none-eabi-gdb
(gdb) file helloworld
(gdb) target remote:8091
Remote debugging using :8091
(gdb) br main
Breakpoint 1 at 0x1042e: file helloworld.c, line 6.
(gdb) c
Continuing.

Breakpoint 1, main (argc=1, argv=0xfffef124) at helloworld.c:6
6		simpleAdd();

As you can see above, program execution got stopped at main, now we will do single stepping to go inside of “simpleAdd” function, for that type “si” and enter on GDB console.

(gdb) si
simpleAdd () at add_assembly.S:8
8		MOV  R0, #1

we are now in simpleAdd function from assembly, now type “next” to go to next instruction and continue execution till “BX lr”

(gdb) next
9		MOV R1, #2
(gdb) next
10	        ADD R2, R1, R0
(gdb) 
11		BX lr

Now, lets check registers before returning to main C program as,

(gdb) info registers
r0             0x1                 1
r1             0x2                 2
r2             0x3                 3

as, you can see we got addition of “r0” and “r1” in “r2” now continue execution.

(gdb) next
main (argc=1, argv=0xfffef124) at helloworld.c:7
7		printf("HelloWorld after addtion from assembly program");
(gdb) 
8		return 0;
(gdb) 
9	}

We successfully came back to main and executed the printf.

Leave a Comment