Pisanie programów wyłącznie w asemblerze można by delikatnie nazwać przesadą, jeśli weźmie się pod uwagę, że optymalizacja kodu wykonywana przez kompilatory może być równie dobra jak ręczna optymalizacja kodu asemblerowego. Dlatego najczęściej asembler jest używany do tworzenia funkcji, wołanych z programów pisanych w językach wysokiego poziomu. Często też w programach asemblerowych korzysta się z bibliotek funkcji języków wysokiego poziomu.
Język C umożliwia osadzanie w kodzie wstawek asemblerowych, ale takie rozwiązanie może być nieprzenośne (np. konieczność modyfikacji asemblerowego bloku kodu osadzonego w module pisanym w C przy przenoszeniu oprogramowania na inny system operacyjny). Oprócz tego możliwe jest stworzenie kodu aplikacji w postaci niezależnych modułów pisanych bądź w C, bądź w asemblerze. Przy tworzeniu aplikacji składających się z modułów pisanych w asemblerze i w C należy przestrzegać konwencji wywołania funkcji przyjętych w odpowiednich standardach (sposób przekazywania argumentów, odbioru wyniku, zachowywania rejestrów itd).
Konwencja cdecl (stosowana przez język C w trybie 32-bitowym) w wielkim skrócie:
Argumenty procedur wymieniane są za pośrednictwem stosu (umieszczane w kolejności od prawej do lewej).
Wartość w zależności od typu i rozmiaru zwracana jest w rejestrze EAX, EDX:EAX lub ST0
Rejestry EBX, ESI, EDI, EBP, CS, DS, SS, ES po wyjściu z funkcji powinny mieć niezmienione wartości.
Więcej szczegółów można znaleźć w materiałach z wykładu.
Na Listingu 1 pokazano kod programu asemblerowego, korzystającego z funkcji printf.
Uwaga:
W Windows nazwy funkcji poprzedzane są znakiem _. Aby skompilować przykłady należy dodać podkreślenia do etykiet main i printf.
W systemach 64 bitowych należy kompilować te programy jako 32 bitowe.
Listing 1
;KOMPILACJA: plik źródłowy c_asm.asm
;nasm -o c_asm.obj -felf32 c_asm.asm
;gcc -m32 c_asm.obj -o c_asm
section .text
extern printf ; definicja funkcji printf znajduje się w bibliotece standardowej C
global main
main: ; punkt wejścia - funkcja main
enter 0, 0
; printf("Liczba jeden to: %d\n", 1);
push dword 1 ; drugi argument
push dword napis ; pierwszy argument - UWAGA na właściwą kolejność argumentów wołanej funkcji
; pierwszy argument funkcji dodajemy na stos jako ostatni!!!
call printf ; uruchomienie funkcji
add esp, 2*4 ; posprzątanie stosu - rejestr ESP wskazuje to samo, co przed wywołaniem
; funkcji printf
xor eax, eax ; return 0;
leave
ret ; wyjście z programu
section .data
napis: db "Liczba jeden to: %d", 10, 0
Na Listingu 2 pokazano kod programu napisanego w C, wołającego funkcję asemblerową przedstawioną na Listingu 3.
Listing 2
// KOMPILACJA - kod źródłowy C w main.c, kod źródłowy ASM w suma.asm
// LINUX :
// nasm -felf32 suma.asm -o suma.o
// gcc -m32 -o main.o -c main.c
// gcc -m32 main.o suma.o -o suma
//
// WINDOWS :
// nasm -fcoff suma.asm -o suma.obj
// gcc -o main.obj -c main.c
// gcc main.obj suma.obj -o suma.exe
#include <stdio.h>
int suma (int a, int b); /* prototyp funkcji */
int main()
{
int c=1, d=2;
scanf("%d %d",&c,&d);
printf("%d\n", suma(c,d));
return 0;
}
Listing 3
BITS 32
section .text
global suma ;KONWENCJA!!! - funkcja suma ma być widziana w innych modułach aplikacji
; pod Windowsem należy dodać podkreślenie przed suma
suma:
enter 0, 0 ; tworzymy ramkę stosu na początku funkcji
; ENTER 0,0 = PUSH EBP / MOV EPB, ESP
; po wykonaniu push ebp i mov ebp, esp:
; w [ebp] znajduje się stary EBP
; w [ebp+4] znajduje się adres powrotny z procedury
; w [ebp+8] znajduje się pierwszy parametr,
; w [ebp+12] znajduje się drugi parametr
; itd.
%idefine a [ebp+8]
%idefine b [ebp+12]
; tu zaczyna się właściwy kod funkcji
mov eax, a
add eax, b
; tu kończy się właściwy kod funkcji
leave ;usuwamy ramkę stosu LEAVE = MOV ESP, EBP / POP EBP
ret
Napisać w assemblerze funkcję main, która wczytuje dwie liczby całkowite ze znakiem przy użyciu funkcji scanf z biblioteki standardowej języka C i wypisujący na ekran ich iloraz przy użyciu funkcji printf.
Należy przysłać tylko plik ASM.
Napisać aplikację 32 bitową wyliczającą iloczyn elementów tablicy liczb int.
int iloczyn(int n, int * tab);
Aplikacja ma być złożona z dwóch modułów:
w C (inicjalizacja tablicy, operacje wejścia/wyjścia),
w ASM (funkcja licząca iloczyn) parametrami funkcji są ilość elementów tablicy i wskaźnik na pierwszy element tablicy.
Zadbaj o to aby funkcja była zgodna ze standardem cdecl.
Zadanie 3
Napisać funkcję o nagłówku
void sortuj( int * a, int *b, int * c);
sortującą malejąco wartości trzech podanych zmiennych.
Po wywołaniu funkcji wartości zmiennych powinny zostać odpowiednio pozamieniane.
Na przykład
int x=5, y=3, z=4;
sortuj( &x, &y, &z);
printf(" %d %d %d \n", x, y, z);
powinno wypisać
5 4 3
Kilka przypadków testowych:
#include <stdio.h>
void sortuj( int * a, int *b, int * c);
int check(int x, int y, int z){
return (x>=y) && (y>=z) && (y>=z);
}
void test(int x, int y, int z){
sortuj(&x, &y, &z);
printf(" %d %d %d %s\n", x, y, z, check(x,y,z)? "OK": "BLAD!!");
}
int main(int argc, char **argv)
{
test(3,4,5);
test(3,5,4);
test(4,3,5);
test(4,5,3);
test(5,3,4);
test(5,4,3);
test(6,7,6);
test(-1,2,0);
test(-12,-4,-8);
return 0;
}
Napisać moduł asemblerowy implementujący 32 bitowyą funkcję minmax wyliczającą minimalny i maksymalny spośród argumentów funkcji. Pierwszym argumentem funkcji jest liczba całkowita N>0, po której następuje N argumentów całkowitych (patrz uwaga poniżej).
Wyniki mają być zwracane jako struktura MinMax.
#include <stdio.h>
typedef struct{
int min;
int max;
} MinMax;
MinMax minmax( int N, ...);
int main(){
MinMax wynik = minmax(7, 1, -2, 4 , 90, 4, -11, 101);
printf("min = %d, max = %d \n", wynik.min, wynik.max);
wynik = minmax(5, 1, -2, 4 , 90, 4, -11, 101);
printf("min = %d, max = %d \n", wynik.min, wynik.max);
wynik = minmax(1, 0);
printf("min = %d, max = %d \n", wynik.min, wynik.max);
return 0;
}
Aplikacja ma być złożona z dwóch modułów w C (operacje IO, wywołanie funkcji ) i ASM (wyznaczenie minimum i maksimum).
Uwaga: Sposób zwracania zależy od systemu operacyjnego i wersji kompilatora:
Linux: Jako pierwszy argument funkcji minmax zostanie przekazany wskaźnik na obiekt typu MM, który należy uzupełnić.
Windows, starsze gcc pod linuxem: Struktura MM mieści się w sumie rejestrów EDX:EAX i tam powinna być zwrócona.