Sunday, April 22, 2012

Calling an assembly function from C: simple example

This semester I'm studying AETC in ETIS in the UOC.
For one of the course assignments I had to use some C mixed with assembly.
To make things simpler in this first assignment, we were asked to use only global variables to pass information between the main C programs and the assembly subroutines.
Since I wanted to know how to do it without global variables, I started to explore a bit.

After some googling and reading several blog posts I found out that, first of all, I needed to identify which calling convention to use. There are many different calling conventions, which one you use depends on your architecture, language or even operative system.
I was using a x86-64 architecture in a 64 Bit Linux, so checking this Wikipedia list of different x86 calling conventions I finally found out that the one I needed to use was: the System V AMD64 ABI convention.
From Wikipedia:
System V AMD64 ABI convention
The calling convention of the System V AMD64 application binary interface is followed on Linux and other non-Microsoft operating systems. The registers RDI, RSI, RDX, RCX, R8 and R9 are used for integer and pointer arguments while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for floating point arguments. For system calls, R10 is used instead of RCX.[9] As in the Microsoft x64 calling convention, additional arguments are pushed onto the stack and the return value is stored in RAX.
Once I knew how to pass parameters to an assembly function from C, I coded a simple integer power computation to test it.
This is the calling C code (pow_c.c):
#include "stdlib.h"
#include "stdio.h"
#include "assert.h"

// Assembly function declaration
extern p_pow(int, int);

int main(void) {  
  assert(4 == p_pow(2, 2));
  assert(9 == p_pow(3, 2));
  assert(25 == p_pow(5, 2));   
  assert(36 == p_pow(6, 2));   
  assert(27 == p_pow(3, 3));      
  assert(1 == p_pow(3, 0));
  assert(1 == p_pow(1, 5));
  assert(4 == p_pow(-2, 2));
  assert(-8 == p_pow(-2, 3));
  printf("All tests passed!\n");
  return EXIT_SUCCESS;
And this is the assembly code (pow.asm):
section .data          
section .text                

; Make the function name global so it can be 
; seen from the C code
global p_pow              

; Function that computes the power of an integer.
; The base (b) is being passed in RDI register
; and the exponent (e) is being passed in RSI register
; The result is returned in the RAX register
 push rbp
 mov rbp, rsp ; Stack initial state is stored
 mov eax, 1   ; Register RAX will hold the result
 cmp esi, 0   ; if (e == 0) -> b^0 = 1, and we're done
 jle pow_end   
  mul edi     ; eax*ebx = edx:eax (when operating 
              ; with ints, edx is not used).
  dec esi     ; esi = esi - 1
  jg mul_loop ; If esi > 0, it continues iterating
 mov rsp, rbp ; Stack initial state is restored
 pop rbp                      
To compile the assembly code I used Yasm which is a rewrite of the NASM assembler under the “new” BSD License. If you have it already installed, you just need to open a console and write this:
$ yasm -f elf64 -g dwarf2 pow.asm
Doing this you'll get an object file: pow.o
Actually only yasm -f elf64 pow.asm is needed to get an object file, but I added -g dwarf2 to obtain debug information so I could use gdb to debug the program.
The elf64 object format is the 64-bit version of the Executable and Linkable Object Format. DWARF is a debugging format used on most modern Unix systems.

To compile the C code and link it with pow.o:
$ gcc -g -o pow pow.o pow_c.c
which produces the executable pow, that when executed yields the following output:
$ ./pow
All tests passed!

No comments:

Post a Comment