Asembler x86/Zmienne/GNU AS

Zmienne

GNU As
FASM
NASM
Spis treści

Segmenty

edytuj

W asemblerze GNU mamy następujące segmenty:

text
kod
data
dane

Aby zdefiniować segment należy w tym celu użyć dyrektywy . Oto schemat użycia:

.nazwa

Przy czym w pole nazwa wpisujemy jedną z powyższych nazw.

Zmienne

edytuj

Tworzenie zmiennych

edytuj

Aby utworzyć nową zmienną należy użyć schematu:

nazwa: .typ wartość

W polu nazwa wpisujemy nazwę dla naszej zmiennej (pole to jest opcjonalne); w polu typ wpisujemy typ zmiennej; zaś w polu wartość wpisujemy startową wartość dla naszej zmiennej. W celu ułatwienia adresowania zmienne należy deklarować wewnątrz sekcji danych. Oto przykład:

.data
liczba: .string "\10\18\3"

Pamiętając o tym, że w czystym asemblerowym kodzie bez specjalnych dyrektyw to co jest niżej w kodzie jest dalej w pamięci oraz pliku wykonywalnym, możemy się odwoływać do 2 pozostałych zmiennych bez nazw dodając do adresu liczby ich przesunięcie względem niej (więcej w podrozdziale Adresowanie).

Grupę zmiennych do których odwołujemy się przy użyciu tej samej nazwy nazywamy tablicą i w niektórych przypadkach łańcuchem. Tablica jest również łańcuchem, gdy poszczególne jej elementy to znaki ASCII. Aby utworzyć ciąg takich znaków możemy napisać, przy użyciu nabytej właśnie wiedzy:

.data
lancuch: .string "napis$"

Jak z pewnością zauważyłeś na końcu naszego łańcucha stoi $. Gdy znamy z góry długość naszego łańcucha jest on zbędny, lecz gdy korzystamy z zewnętrznych funkcji, nie znają one tej długości. Problem rozwiązano właśnie poprzez kończenie każdego łańcucha dolarem. Każda funkcja przetwarzając nasz łańcuch (np. funkcja API wyświetlająca nasz łańcuch) kończy zabawę z naszym łańcuchem, gdy napotka dolar. Niektóre funkcje jako symbol końca łańcucha traktują inne wartości.


Systemy liczbowe

edytuj

Do tej pory używaliśmy jedynie stałych zapisywanych w systemie dziesiętnym. Aby zaznaczyć, że dana liczba jest zapisana w innym systemie liczbowym musimy dodać odpowiedni przyrostek lub przedrostek:

  • system szesnastkowy - przedrostek 0x lub przyrostek h albo H. W przypadku zastosowania przyrostka należy dodać 0 na początku naszej liczby, jeśli pierwszy jej znak to litera, a nie cyfra.
  • system dziesiętny - bez przedrostków/przyrostków.
  • system ósemkowy - przyrostek o, O, q lub Q.
  • system binarny - przyrostek b, B, y lub Y.

Przykłady: Przykłady:

liczba = 0xFF
liczba = FFH #źle, pierwszy znak to litera, brakuje zera
liczba = 0FFH #to poprawny zapis szestnastkowy
liczba = 18
liczba = 3Q
liczba = 1010Y

Rozmiary zmiennych

edytuj
. - zmienna

Adresowanie

edytuj

Aby odnieść się do konkretnego adresu w pamięci możemy użyć tak jak to robiliśmy do tej pory nazwy symbolicznej lub też konkretnej liczby albo adresu zawartego w dowolnym rejestrze. Aby oświadczyć, że dana wartość ma być traktowana jako offset (przesunięcie względem początku segmentu) musimy umieścić ją między nawiasami okrągłymi. Oto przykłady, które powinny zobrazować zagadnienie:

movb         %ds:($4), %eax # do eax kopiowana jest wartość spod offsetu 4 w rejestrze DS
movb         $zmienna, %ecx # do ecx kopiowana jest wartość spod adresu wskazywanego przez zdefiniowaną nazwę symboliczną
movb         (%eax), %edx # do edx kopiowana jest wartość spod offsetu przechowywanego w EAX w segmencie DS

W przypadku użycia konkretnych liczb definiujemy o który segment nam chodzi, gdy używamy nazwy symbolicznej segment zależny jest od miejsca definicji naszej zmiennej, zaś skąd wiemy z którego segmentu będzie czytać procesor w przypadku korzystania z rejestrów, tak jak w ostatnim przypadku? We wszystkich przypadkach procesor odnosi offset względem segmentu DS chyba że rejestrem adresującym jest EBP lub ESP, gdyż w ich przypadku procesor odnosi offset względem segmentu SS. Oto przykłady:

movb         %edx, %eax # do eax kopiowana jest wartość spod adresu DS + EDX
movb         %ebp, %ecx # do ecx kopiowana jest wartość spod adresu SS + EBP
movb         %ebp, %edx # pod adres DS + EDX kopiowana jest wartość ebp

Między nawiasami można stosować przesunięcie i skalowanie. Operator przeniesienia może być stosowany tylko z liczbami 8-, 16- i 32-bitowymi, zaś operator skalowania może być użyty w wyrażeniu tylko jeden raz oraz współczynnikiem skalowania może być tylko 2, 4 lub 8. Oto przykłady

movb         10(%edx), %eax       # do eax kopiowana jest wartość spod adresu DS + EDX + 10
movb         %eax, (, %ebp, 2)    # pod adres SS + 2EBP kopiowana jest wartość rejestru eax
movb         4(%eax, %ecx), %ebx  # do ebx kopiowana jest wartość spod adresu DS + EAX + 4 + ECX
movb         (%eax,%ecx,8),%ebx   # do ebx kopiowana jest wartość spod adresu DS + EAX + 8ECX
movb         (,%eax,3), %ebx      # źle, nie wolno skalować przez 3!

A co jeśli do obliczenia adresu zastosujemy jednocześnie rejestr EBP (który odnosi się względem segmentu SS) oraz np. rejestr EAX (który odnosi się do segmentu DS)? W tym przypadku jeden z nich traktowany jest jako główny i to jego przyporządkowanie do segmentu jest brane pod uwagę. Który z nich ma być główny? Istnieją dwie zasady:

  • jeśli między nawiasami występuje skalowanie to rejestrem głównym jest skalowany rejestr
  • w każdym innym przypadku rejestrem głównym jest pierwszy rejestr w wyrażeniu

Pola bitowe

edytuj

Obecnie asembler GNU nie obsługuje pól bitowych jako takich. Same w sobie nie wnoszą nic do funkcjonalności asemblera, gdyż można je zastąpić definiując różne nazwy symboliczne.