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 conventionOnce I knew how to pass parameters to an assembly function from C, I coded a simple integer power computation to test it.
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.
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 p_pow: 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_loop: 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 pow_end: mov rsp, rbp ; Stack initial state is restored pop rbp retTo 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.asmDoing 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.cwhich produces the executable pow, that when executed yields the following output:
$ ./pow All tests passed!
No comments:
Post a Comment