Asembler ATamp;T i wstawki w GCC

ic3

Na wstępie zaznaczam, iż nie jest to kurs asemblera a jedynie
porównanie dwóch jego "wersji". Większość z was zapewne - o ile
miała okazję poznać asemblera - jest przyzwyczajona do notacji
Intela używanej przez takie kompilatory jak NASM, TASM czy
MASM (czyli większość działajacych pod windowsem). Natomiast
kompilator GNU używa składni AT&T, także wstawki w kodzie
pod GCC pisze sie w tejże składni.

Główne różnice

Zaczne od rzeczy, która zapewne będzie się wam ciągle mylić,
podam przykład:

    mov eax , ebx

Zawartość rejestru ebx zostanie przeniesiona do rejestru eax.
W asemblerze AT&T jest odwrotnie, najpierw podajemy źródło a
potem cel. Teraz "prefixy" - w skladni Intela nie ma czegoś
takiego jak prefix, natomiast w AT&T dodajemy przed nazwą
rejestru %, przed nazwą zmiennej lub stałej wartości $. Następną
ważną rzeczą są tzw. sufiksy. Jest to specyfikacja wielkości
danych którą przetważamy. Żeby to wyjaśnić podam przykład:

Intel:
    mov eax , ebx
    mov ax  , bx
    mov al  , bl

AT&T:
    movl %ebx , %eax
    movw %bx  , %ax
    movb %bl  , %al

Jak widać dla operacji na liczbach 32 bitowych używamy sufiksu
l, dla liczb 16 bitowych - w, dla liczb 8 bitowych - b. Teraz
adresowanie pamięci :-). Intelowy "prototyp" adresowania wygląda
tak:

    [wskaźnik bazowy + wskaźnik do indeksu*skala indeksowania + adres]

w AT&T wygląda to troche inaczej:

    adres(wskaźnik bazowy , wskaźnik do indeksu , skala indeksowania)

Do deklaracji zmiennych nie używamy tak jak w składni intela db,
dw, dd, tutaj korzystamy odpowiednio ze słów .byte , .word , .long
dla danych 8, 16 i 32 - bitowych.

Przykład

Oczywiście bez przykładu sie nie obejdzie, pokaże tu prosty program, który
można skompilować na DOSowym pakiecie DJGPP (port narzędzi GNU):

# example.s

.section .text
.p2align 1
.globl _main
_main:
    xorl %ecx , %ecx  
    movl $tablica , %eax   

  loop:
    movb 0(%eax,%ecx,1)  , %bl  
    cmpb %cl , %bl
    incl %ecx
    je   loop

    leave
    ret

.section .data

tablica: 
    .byte 0,1,2,3,4,5,6,7,8,9,0

# gcc example.s -o example.exe

Program będzie kopiował kolejne bajty "tablicy" do rejestru BL i porównywał
wartość z wartością rejetru CL, więc dopóki elementy "tablicy" będą rosły pętla
będzie powtarzana, ostatnie 0 przerwie pętle.

Ogólnie o wstawkach

Czasami potrzebujemy wstawić do naszego programu w C/C++ kod asemblera, w kompilatorze
GNU nie skorzystawmy z tradycyjnej składi, musimy wykorzystać (poznaną wcześniej :)) AT&T. Aby wstawić pojedynczą
instrukcje piszemy np.:

    asm("xorl %eax , %eax");

Aby wstawić kilka instrukcji robimy troche inaczej:

    asm("xorl %eax , %eax\n\t"
        "xorl %ebx , %ebx\n\t"
        "xorl %ecx , %ecx\n\t");

Do czego nam te \n i \t? Dlatego, że "string" w funkcji asm będzie wstawiony
bezpośrednio do kodu programu, możemy to sprawdzić generując kod asemblera
zamiast kompilować programu do postaci wykonywalnej, weźmy na przykład:

// example2.c

int main()
{
    asm("\txorl %eax , %eax\n\t\t"
        "xorl %ebx , %ebx\n" );
    return 0;
}

// gcc -S example2.c

Powinien powstać plik example2.s, możecie zauważyć, że dwie linijki

    xorl %eax , %eax
    xorl %ebx , %ebx

będą bardziej wysunięte od innych, dzieki temu, że dodaliśmy dwa razy znak
\t , oczywiście nie musimy przesadzać, ten przykład był aby nam pokazac
jak kod jest wstawiany :), \n jest raczej wymagane ;) natomiast \t możemy pominąć.

Przykład

Teraz przykład użycia asemblera w kodzie C:

// example3.c

#include <stdio.h>

char tab[] = "ABCD";

int main()
{
    printf("%s -> ", tab );
    asm(" movl $_tab , %eax \n\t" 
        " xorl %ecx , %ecx\n\t"
        "loop1:\n\t "
        " cmpb $0 , 0(%eax,%ecx,1)\n\t"
        " je   end1\n\t"
        " incb 0(%eax,%ecx,1)\n\t"
        " incl %ecx\n\t"
        " jmp  loop1\n\t"
        "end1:\n\t" );

    printf("%s \n" , tab );
    return 0;
} 

// gcc example3.c

Program najpierw wyświetla tablice tab potem (wstawka asemblera) zwiększa
każdy jej element o 1 i wyświetla tablice jeszcze raz. Należy zauważyć, że
w kodzie asemblera użyłem _tab a nie tab, zrobiłem to dlatego, iż kompilator C sam
dodaje _ przed nazwami zmiennych, należy o tym pamiętać! Warto też zrobić
gcc -S example3.c :).

Zakończenie

Za jakiś czas może dodam coś jeszcze... Narazie mam ogromny brak czasu nawet na
myślenie :D. Ale mam nadzieję, że artykuł się spodobał, jeżeli masz jakieś pytania
lub znalazłeś błąd w artykule to koniecznie daj mi znać ( ice_man11@wp.pl )!

5 komentarzy

tez sie nad tym zastanawialem, dzieki za pomysl, postaram sie go urzeczywistnic. :) tabelki rulez :D

świetne! możnaby jeszcze zrobić tabelkę z porównaniem obu składni - jak to psory mówią: "tabelka najlepszym przyjacielem ucznia" :D

Dzieki Wolverine :). Jest to moj pierwszy art wiec wszystkie uwagi mile widziane ! W koncu czlowiek uczy sie na bledach... :)

ode mnie 6 :) fajny art

Popraw błędy ortograficzne!!!