Wstawki asemblerowe (inline assembly)

Najwygodniejszą metodą używania asemblera (gdy nie chcemy całego projektu pisać tak niskopoziomowo) jest wprowadzanie asemblerowych fragmentów do programów napisanych w innym języku. W kodzie jądra linuxa asembler umieszczany jest wewnątrz funkcji napisanych w C i takiemu rozwiązaniu poświęcony jest ten rozdział.

Dwa głowne problemy przy łączeniu asemblera z kodem w języku wyższego poziomu, to przekazanie danych i wyników, oraz zapobieganie wzajemnemu wymazywaniu zawartości rejestrów.

Język C udostępnia specjalną konstrukcję składniową, umożliwiającą wprowadzenie asemblera. Jej budowa jest następująca:

__asm__ {
	"instrukcja asemblera\n"
	"następna instrukcja\n"
	  ...
	"ostatnia instrukcja\n"
	: wyjściowe zmienne (opcjonalne)
	: wejściowe wartości (opcjonalne)
	: niszczone rejestry (opcjonalne)
	};

Podkreślenia wokół asm nie są konieczne, ale pomagają uniknąć konfliktu nazw. Instrukcje asemblera są zwykłymi napisami których konkatenację kompilator C po wstępnym przetworzeniu przekaże do kompilatora asemblera.

Do kompilacji wstawek asemblerowych kompilator gcc używa Gnu Assembler'a (gas) , stosującego składnię AT&T. Nie wymagne są żadne dodatkowe parametry.

Poniższy kod zamienia wartości zmiennych 'x' i 'y' (typu int):

__asm__ {
	"movl %2, %%eax\n"  //kopiujemy "x wejściowe" do eax
	"movl %3, %0\n"     //kopiujemy "y wejściowe" od razu do "x wyjściowego"
	"movl %%eax, %1\n"  //kopiujemy eax do "y wyjściowego"
 	: "=r"(x),"=r"(y)   //wykona się 7 kopiowań!
	: "r"(x),"r"(y)
	: "%eax"        //podczas instrukcji __asm__ "przepadła" zawartość eax
	};

W instrukcjach asemblera odwołujemy się do argumentów wejściowych i wyjściowych za pomocą ich numerów porządkowych poprezedzonych '%'. Kompilator przerabia napisy zawierające instrukcje asemblera, napotykając znak % próbuje zinterpretować to co się za nim znajduje. Jeśli jest to nazwa rejestru (np. %eax, stąd piszemy %%eax), jest ona przekazywana wprost. Jeśli jednak jest to liczba, do asemblera przekazywana jest lokalizacja odpowiedniego parametru instrukcji __asm__ (nazwa rejestru, lub adres w pamięci).

Elementy list wartości wejściowych i zmiennych wyjściowych oddzielamy przecinkami. Każdy element ma określony sposób przekazywania.

Przy zmiennych wyjściowych bezpośrednio przed literą oznaczającą miejsce przechowywania umiescza się znak '='.

Oczywiście, poprzez wymuszenie użycia konkretnych rejestrów do przechowania parametrów można zamianę wartości zmiennych zapisać prościej.

Kompilator uważa, że dane wejściowe będą odczytywane przed zapisaniem wyników i chętnie używa do obydwu tych rzeczy tych samych rejestrów. Można wyrazić chęć zachowania wejściowych wartości aż do końca wykonywania bloku asemblera poprzez dopisanie & po znaku równości przy parametrze wyjściowym. Nadmierne stosowanie tego oznaczenia może jednak prowadzić do wyczerpania ilości rejestrów.

Lista rejestrów o niszczonej zawartości jest podwójne istotna. Mówi ona kompilatorowi C, aby:

Gdy w instrukcj asemblera chcemy skorzystać z adresu zmiennej to piszemy jej nazwę poprzedzonąznaiem $. Na przykład:

__asm__("movl $xxx, %%eax":::"%eax");

umieści adres zmiennej xxx w rejestrze %%eax. Rejestr ten został wskazany jako zmieniany. Operacja ta powiedzie się jednak tylko dla globalnych zmiennych statycznych, których adres wirtualny znany będzie już w trakcie kompilacji. Dla pozostałych zmiennych niezbędne jest korzystanie z list wartości wejściowych i zmiennych wyjściowych.

Przydatne jest pisanie __volatile__ po __asm__, dla zachowania kontroli nad tym, co robi, a czego nie robi nasz asembler. Dla gcc oznaczać to będzie nieoptymalizowanie tego fragmentu kodu. Gcc może uznać, że nasz asembler tutaj nic istotnego nie wnosi (czytaj: nie zmienia wartości zmiennej, ani nie wywoluje funkcji) i usunąć go z końcowego kodu.

Gdy chcemy mięć dobrą kontrolę nad kształtem kodu wykonywalnego, ale nie zależy nam na pisaniu go od podstaw w asemblerze, możemy wygenerować kod z kompilatora wyższego poziomu do asemblera.

Standardowym sposobem jest skompilowanie z flagą "-s" ( w gcc "-S"). Bardziej przejrzysty kod można uzyskać dodając jeszcze flagę "-fverbose-asm". Isntnieje jeszcze wiele rożnych opcji, właściwych poszczególnym kompilatorom.

W przypadku gcc kod generowany przez kompilator będzie wyrażnie oddzielony komentarzami od wprowadzonego za pomocą instrukcji asm.