Wstawki asemblerowe w języku C, część 1

02.02.2010 - Piotr Witkowski
TrudnośćTrudnośćTrudność

Skok bezwarunkowy

Kod wykonującego się programu umieszczony jest w spójnym bloku pamięci operacyjnej. Adres (czyli numer) komórki zawierającej aktualnie wykonywaną instrukcję znajduje się w rejestrze ip (ang. Instruction Pointer). Jeśli bieżący rozkaz nie zmienił wartości ip, to jest ona zwiększana automatycznie tak, by wskazywać na następną instrukcję. Rejestr ip nie jest poprawnym argumentem dla poznanych dotychczas instrukcji  mov, add, sub. Do jego zmiany służą specjalne rozkazy, z których najprostszym jest rozkaz skoku bezwarunkowego jmp.

1
2
3
asm("start:");
printf("Nie lubię się powtarzać.");
asm("jmp start"); 
Tłumacząc ten fragment kodu kompilator zapamięta adres pierwszej instrukcji po etykiecie start i wstawi go jako operand instrukcji jmp. Gdy uruchomimy program, funkcja printf wykonywać się będzie w nieskończoność. Zobaczyliśmy tu przykład skoku wstecz, czyli do wcześniejszego miejsca w kodzie. Można też skakać wprzód, czyli do etykiety zdefiniowanej później, niż jej występuje jej użycie. Rozkaz jmp sam w sobie jest mało przydatny. Chcielibyśmy uzależnić wykonanie skoku np. od wyniku operacji arytmetycznej.

Flagi i testowanie flag

Instrukcje arytmetyczne zmieniają wartość nie tylko rejestru/adresu będącego argumentem docelowym. Powodują też zmianę wartości specjalnych bitów ( czyli flag lub znaczników ) w rejestrze stanu procesora. Jeżeli wynikiem ostatniej operacji arytmetycznej było zero to ustawiony zostanie bit ZF (ang Zero Flag) tego rejestru, w przeciwnym przypadku będzie on wyzerowany. Jeśli wynikiem była liczba mniejsza od zera to ustawiony będzie bit SF (ang. Sign Flag) a jeśli większa bądź równa zeru to wyzerowany. Bity ZF i SF są to odpowiednio bity 6 i 7 rejestru stanu, któy nazywa się flags.

Skoki warunkowe

Przy pomocy poznanego właśnie rejestru flags możemy wykonywać skoki warunkowe. Poniższy program przypisuje zmiennej max maksimum z wartości zmiennych i i j:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int i,j, max;
int main(void) 
{
 i = 7; j = 9;
 asm(" mov eax, i;   \
       mov ebx, eax; \
       mov ecx, j;   \
       sub eax, ecx; \
       jg end;       \
       mov ebx, ecx; \
       end:          \
       mov max, ebx; \
   ");
 printf("%i\n", max); //max==9
 return 0;
}
W wierszu 8 wykonywane jest odejmowanie i-j. W zależności od znaku wyniku tego działania ustawiana bądź zerowana jest flaga SF rejestru flags. Instukcja jg end; wykona skok do etykiety end jeżeli ta flaga jest wyzerowana, tzn. wtedy gdy wynik działania i-j był dodatni. Inne przydatne rozkazy skoków:
  • 1
    
    je etykieta
    (ang. jump if equal) - skocz gdy wynikiem operacji arytmetycznej było zero (znacznik ZF jest ustawiony).
  • 1
    
    jge etykieta
    (ang. jump if greater or equal) - skocz gdy wynik był większy bądź równy zeru ( znacznik SF wyzerowany)
  • 1
    
    jl etykieta
    (ang. jump if less than) - skocz gdy wynik był mniejszy od zera(SF ustawiony)
  • 1
    
    jle etykieta
    (ang. jump if less or equal) - skocz gdy wynik był mniejszy bądź równy zeru(ZF ustawiony lub SF ustawiony)
  • 1
    
    jne etykieta
    (ang. jump if not equal) - skocz gdy wynik był różny od zera (ZF wyzerowany)
Powyższe rozkazy nie zmieniają rejestru flags. Zauważmy, że zmiana wartości rejestru eax instrukcją z wiersza 8 nie jest przez nigdzie wykorzystywana. Interesowało nas jedynie ustawienie flag. Nawet więcej, gdybyśmy nie zniszczyli wartości tego rejestru to moglibyśmy użyć go zamiast ebx. Takie sytuacje są na tyle częste, że wprowadzono rozkaz
1
cmp operand1,operand2
który wykonuje operację operand1-operand2 ale nie zapisuje nigdzie wyniku. Ustawia jedynie rejestr flag. Uproszczony kod wygląda tak:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int i,j, max;
int main(void) 
{
 i = 7; j = 9;
 asm(" mov eax, i;   \
       mov ecx, j;   \
       cmp eax, ecx; \
       jg end;       \
       mov eax, ecx; \
       end:          \
       mov max, eax; \
   ");
 printf("%i\n", max); //max==9
 return 0;
}
Snippet icon

Ćwiczenie 3. Niech i,j,k będą zmiennymi typu short int. Napisz wstawkę asemblerową podstawiającą do zmiennej k wartość 0 jeżeli i<j, wartość 1 jeżeli i==j, wartość 2 jeżeli i>j.

Pętle

Zobaczmy teraz przykład wstawki z pętlą. Poniższy program wykona zapisze w k wartość i*j obliczoną w dosyć naiwny sposób - dodając j razy liczbę i do ax po czym umieszczając wartość ax w k.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
short int i,j, k;
int main(void) 
{
 i = 7; j = 9;
 asm(" mov ax,0;  \
       mov cx,j;  \
       start:     \
       cmp cx,0;  \
       je end;    \
       add ax,i;  \
       sub cx,1;  \
       jmp start; \
       end:       \
       mov k, ax; \
   ");
 printf("%i\n", k); //k==63
 return 0;
}
W rejestrze ax obliczana jest żądana suma. Rejestr cx ustawiony początkowo na j służy jako licznik pętli. Program wykona j dodawań zmiennej i do rejestru ax. Wśród programistów asemblerowych przyjął się zwyczaj używania rejestru (e)cx jako licznika w rozmaitych pętlach. Często zachodzi potrzeba, jak w naszym przykładzie, wykonania skoku gdy (e)cx jest równy zeru. W nowszych procesorach wprowadzono odpowiednie ku temu instrukcje:
  • 1
    
    jcxz etykieta
    (Jump if cx register is zero) skocz gdy cx jest wyzerowany
  • 1
    
    jecxz etykieta
    (Jump if ecx register is zero) skocz gdy ecx jest wyzerowany
Rozkazy te nie używają rejestru flags. W wierszu 10 widzimy instrukcję add ax,i. Wykonuje się ona w pętli j razy. Ponieważ wartość i nie ulega modyfikacji, to powinno się składować ją w rejestrze. Zamiast sub cx,1 można użyć instrukcji dekrementacji dec cx. Dla porządku trzeba dodać, że również zamiast add rejestr,1 zwykle lepiej jest napisać inc rejestr. Oto poprawiony kod:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
short int i,j, k;
int main(void) 
{
 i = 7; j = 9;
 asm(" mov ax,0;  \
       mov bx,i;  \        
       mov cx,j;  \
       start:     \
       jcxz end;  \
       add ax,bx; \
       dec cx;    \
       jmp start; \
       end:       \
       mov k, ax; \
   ");
 printf("%i\n", k); //k==63
 return 0;
}
Snippet icon

Ćwiczenie 4. Niech i,k będą zmiennymi typu short int. Napisz wstawkę asemblerową liczącą w pętli sumę liczb 1,2,3,...,i. Zapisz tą liczbę w zmiennej k

Poniższy program, dla N>0, wypisuje na ekranie wartość N-tej liczby Fibonacciego:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const unsigned short N = ?;
unsigned short i,j;
int main(void) 
{
 unsigned short k = 1;
 unsigned short tmp;
 i = 0; j = 1;
 //----------------------------
   while (k++ < N ) {
    tmp = j;
    j += i;
    i = tmp; 
   } 
 //----------------------------
 printf("%i\n", j); 
 return 0;
}
Snippet icon

Ćwiczenie 5. Napisz wstawkę asemblerową kodującą pętlę while z powyższego przykładu. Postaraj się użyć jak najmniej przesłań do/z pamięci i jak najmniej rejestrów.

5
Twoja ocena: Brak Ocena: 5 (3 ocen)

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com