osdev.labedz.org

Kod assemblera 'w linii' kodu języka C

Ponieważ stosunkowo często zdarzają się sytuację, kiedy należy wykonać pojedyncze instrukcje w języku assemblera, dla których nie ma sensu tworzyć oddzielnego pliku, projektanci kompilatora GCC zaimplementowali możliwość wykonywania instrukcji procesora bezpośrednio w kodzie programu. Ta opcja jest też przydatna, gdy zależy nam na szybkiemu wykonaniu danych instrukcji (nie ma potrzeby wywoływania innej procedury instrukcją CALL ).

Kompilator GNU C dla instrukcji assemblera korzysta z notacji wprowadzonej przez firmę AT&T, która dość znacznie różni się od zalecanej przez firmę Intel. Różnice te są pokazane w tabeli.

Format przykładowej instrukcji assemblera 'w linii' języka C ma następującą postać:

asm("instrukcja assemblera" : wyjścia : wejścia : rezerwacja rejestrów);

Jeżeli symbol asm jest używany w programie w innym celu, to do oznaczenia instrukcji assemblerowej możemy wykorzystać symbol __asm__. Jeżeli mamy więcej niż jedna instrukcję w kodzie niskiego poziomu, możemy ją napisać w ramach jednej dyrektywy kończąc każdą linię znakami '\n' i '\t'. Jest to wynikiem tego, że każdy ciąg znaków umieszczony w dyrektywie asm jest wysyłany do programu assemblera pośrednio poprzez tworzony na bieżąco plik a następnie przeprowadzana jest assemblacja. Znaki te powodują odpowiednie sformatowanie pliku wejściowego dla assemblera.

Różnice w notacjiPrzykład
IntelAT&T
Kolejność operandów źródło - przeznaczeniemov eax, 1movl $1, %eax
Nazwa rejestrów z/bez znaku %eax%eax
Nazwa mnemonika zależy/nie zależy od wielkości argumentu; dodatkowe dyrektywymov al, byte ptr foo movb foo, %al
Nawias adresu bazowego[ecx](%ecx)
Adresowanie skalowane bazowo-indeksowesub eax, [ebx+ecx*0x4-0x20]subl -0x20(%ebx,%ecx,0x4), %eax
Tabela: Różnice w notacji assemblera firm Intel i AT&T

Kompilator GCC umożliwia również zastosowanie bardziej złożonego formatu instrukcji assemblera, w której mamy możliwość określenia rejestrów jakie mają być użyte w tej instrukcji, przypisania im wartości stałej bądź zmiennej występującej w języku wysokiego poziomu, przypisaniu wyjścia instrukcji zmiennej wysokiego poziomu, oraz określenia których rejestrów kompilator ma nie używać, gdyż zostały one zajęte przez programistę.

Przykład rozszerzonej instrukcji assemblera 'w linii' języka C ma postać:

asm("foo %1,%2,%0" : "=r" (ptr->vtable[3](a,b,c)->foo.bar[baz]) : : "r" (gcc(is) + really(very->cool)), "r" (42));

W tym przypadku argumenty %1, %2, %0 (maksymalnie %9) oznaczają poszczególne zmienne języka wysokiego poziomu. Odpowiednikiem powyższej instrukcji jest program:

register int t0, t1, t2; t1 = gcc(is) + really(very->cool); t2 = 42; asm("foo %1,%2,%0" : "=r" (t0) : "r" (t1), "r" (t2)); ptr->vtable[3](a,b,c)->foo.bar[baz] = t0;

Każde wejście i wyjście instrukcji assemblera składa się z dwóch części: deklaracyjnej oraz z wyrażenia. Wyrażenie jest to po prostu wyrażenie w języku wysokiego poziomu, umieszczone w nawiasach okrągłych ,oznaczające określoną zmienną, bądź też jej wartość.

Część deklaracyjna składa się ze znaku umieszczonego w cudzysłowu określającego jakie rejestry mają być użyte do wygenerowania instrukcji oraz czy instrukcja ma się odnosić do rejestru czy pamięci. W przypadku wejścia instrukcji część deklaracyjna zawiera też znak równości '='.

SymbolOpis
gogólny adres efektywny
madres efektywny pamięci
rrejestr
iwartość bezpośrednia 0..0xFFFFFFFF
qrejestr adresowany bajtowo (EAX - EAX )
a,b,c,d,S,DEAX, EBX, ECX, EDX, ESI, EDI
Tabela: Symbole części deklaracyjnej instrukcji assemblera w linii