Asembler x86/Preprocesor/NASM
Jednoliniowe makra
edytuj%define
edytujAby zdefiniować stałą/jednoliniowe makro należy użyć dyrektywy %define. Za dyrektywą stawiamy nazwę dla naszego makra a zaraz za nią jego wartość, przy czym jego wartością może być cokolwiek, np.:
%define TEKST "t e k s t"
konkretną wartość nazywamy stałą.
Od tej pory, gdy w dowolnym miejscu kodu użyjemy nazwy naszego makra, zostanie ona zastąpiona bezpośrednio wartością makra przed przystąpieniem do asemblacji.
Dodatkowo makra mogą przyjmować argumenty jak w poniższym przykładzie:
%define POLE(x, y) shl x, y
...
POLE(eax, ebx)
W miejscu użycia makra najpierw zostaje wstawiona w jej miejsce jej wartość a następnie w miejsce użycia nazwy przekazanego argumentu zostaje wstawiona przekazana mu wartość. Efekt działania tego małego makra jest identyczny jak w pierwszym przykładzie.
Jeśli zadeklarujemy w pewnym miejscu kodu makro o użytej już wcześniej nazwie przez inne makro jej wartość zostaje zmieniona na nową od tego punktu kodu w dół.
%define MAKRO 1
; w tym miejscu kodu MAKRO1 ma wartość 1
%define MAKRO 0
; od tego miejsca w dół, wartością makra jest już 0 a nie 1
%xdefine
edytujIstnieje alternatywna metoda definiowania makr przy użyciu dyrektywy %xdefine. Różni się od dyrektywy %define tym, że kod makra jest rozwijany w miejscu definicji a nie w miejscu użycia. Z pewnością brzmi to niezrozumiale, ale wszystko się wyjaśni w poniższym przykładzie:
%define MAKRO1 1
%define MAKRO2 MAKRO1
%define MAKRO1 0
mov ax MAKRO2
Rejestr ax będzie mieć wartość 0, gdyż w miejscu użycia podstawiana jest za każdym razem od nowa wartość MAKRO1 w MAKRO2. Jeśli skorzystamy z dyrektywy %xdefine:
%xdefine MAKRO1 1
%xdefine MAKRO2 MAKRO1
%xdefine MAKRO1 0
mov ax MAKRO2
to rejestr ax będzie mieć wartość 1, gdyż nazwa MAKRO1 w MAKRO2 została zastąpiona wartością w miejscu definicji a nie w miejscu użycia.
%undef
edytujDyrektywa %undef jest bardzo prosta w użyciu. Jej funkcją jest po prostu usunięcie zdefiniowanego wcześniej makra. Zobrazuje to poniższy przykład:
%define ABC 5
; tu możemy używać naszej stałej
%undef ABC
; tutaj już nie, gdyż została usunięta
%assign
edytujDyrektywa %assign służy do tworzenia stałych, więc nie może przyjmować parametrów i jako wartości mieć niczego co nie byłoby konkretną wartością, ew. działaniem matematycznym. Tak jak w przypadku %define można redefiniować daną stałą, więc tak naprawdę może być zmienna, na poziomie asemblacji. Poza tym jej działanie jest identyczne jak w przypadku %define. Wykorzystywana jest głównie jako licznik pętli dla %rep (przykład w podrozdziale %rep).
%assign PI 3.14
Łączenie nazw
edytujAby połączyć dwa ciągi znaków w nazwę stałej należy skorzystać z operatora %+ stawiając po jego obu stronach łączone nazwy, np:
%define ALA "Ala ma psa i kota",0
%define KOT "Kot ma Ale",0
%define PIES "Pies ma Ale",0
%define ALAKOT "Ala ma kota, kot ma Ale",0
%define ALAPIES "Ala ma psa, pies ma Ale",0
%define SUMUJ(a) a
%define SUMUJ(a,b) a %+ b
mov [edx], SUMUJ(ALA,KOT)
mov [eax], SUMUJ(ALA,PIES)
mov [ecx], SUMUJ(KOT)
W pierwszym przypadku skopiowana zostanie wartość ALAKOT, w drugim ALAPIES a w trzecim KOT.
Wieloliniowe makra
edytujTworzenie
edytujAby utworzyć wieloliniowe makro musimy zastosować dyrektywę preprocesora %macro-%endmacro. Oto schemat użycia:
%macro nazwa ilość_argumentów
kod makra
%endmacro
oraz stosowny przykład:
%macro licz 2
or %1, %2
shl %1, %2
%endmacro
...
licz eax, ebx
Makro pobiera 2 argumenty, które sumuje i mnoży wynik przez drugi argument. Użycie wewnątrz makra symboli %1, %2, %3 itd. odnosi się do argumentów o kolejnych numerach. Jak wyraźnie widać zdefiniowanego makra używa się identycznie co instrukcji procesora.
Uwaga!
|
Makrom możemy nadawać nazwy instrukcji procesora przesłaniając je. Po zdefiniowaniu takiego makra za każdym razem, gdy wykorzystamy daną instrukcję użyte zostanie makro zamiast naszej instrukcji, chyba że nasze makro będzie posiadać inną liczbę parametrów w stosunku do instrukcji (np. gdy zdefiniujemy makro o nazwie call z dwoma parametrami, instrukcja call będzie funkcjonować swoją drogą, gdy przekażemy jeden argument a makro swoją drogą, gdy użyte zostaną dwa argumenty).
Pamiętaj, że jeśli zdefiniujesz wewnątrz makra etykietę i użyjesz tego makra w kilku miejscach w kodzie, pod każde z tych makr zostanie podstawiony ten sam kod i w efekcie w kodzie będzie kilka etykiet o tej samej nazwie jak w tym przykładzie:
%macro czyzero 1
test %1, %1 ; sprawdzenie czy argument jest równy 0
jz kontynuuj
ret
kontynuuj:
%endmacro
...
czyzero eax
czyzero ebx
...
w efekcie kod dwóch ostatnich linijek zostanie podmieniony w ten sposób:
test %1, %1
jz kontynuuj
ret
kontynuuj:
test %1, %1
jz kontynuuj
ret
kontynuuj:
Etykieta zdublowała się. Aby temu zapobiec należy poprzedzić etykietę wewnątrz definicji makra znakami %%:
%macro czyzero 1
test %1, %1
jz %%kontynuuj
ret
%%kontynuuj:
Argumenty
edytujNieokreślona liczba argumentów
edytujAsembler NASM udostępnia kilka ciekawych możliwości powiązanych z argumentami makr.
Pierwszą z nich jest tworzenie makr z nieokreśloną ilością argumentów. Aby utworzyć takie makro należy dopisać + za liczbą argumentów tak jak w przykładzie:
%macro string 2+
%1 db %2, 0
%endmacro
...
string napis, "to jest napis!!!", "a to drugi napis!!!", "a to trzeci!!!", "przy czterech zaczyna się nudzić..."
Przekazywanie większej ilości argumentów polega na tym, że wszystkie argumenty są numerowane normalnie aż do osiągnięcia zdefiniowanej minimalnej liczby argumentów (w powyższym przykładzie 2). Wszystkie dalsze argumenty łączone są w jeden i przekazywane w ostatnim argumencie, tak więc w powyższym przykładzie mamy dwa argumenty:
- %1: napis
- %2: "to jest napis!!!", "a to drugi napis!!!", "a to trzeci!!!", "przy czterech zaczyna się nudzić..."
Jaki jest efekt podstawienia makra łatwo się domyślić.
Domyślne wartości argumentów
edytujMożemy także tworzyć makra z domyślnymi wartościami dla parametrów. W tym celu nie podajemy konkretnej liczby argumentów, lecz ich przedział oraz domyślne wartości dla argumentów, które niekoniecznie muszą zostać przekazane. Oto przykład ilustrujący zagadnienie:
%macro message_box 2-4 0, MB_OK
push %4
push %2
push %1
push %3
call MessageBox
%endmacro
...
message_box "Blossom, the witch", "Still Remains"
W powyższym przykładzie w definicji makra określamy, że może ono przyjmować od 2 do 4 argumentów. Dla 2 argumentów (3. i 4.), które nie muszą być koniecznie przekazane podajemy domyślne wartości w razie gdyby ich zabrakło w miejscu użycia makra tj. 0 dla pierwszego oraz MB_OK (jest to stała zdefiniowana w plikach nagłówkowych WinAPI) dla drugiego.
Jeśli użyjemy przedziału do zdefiniowania ilości argumentów a nie podamy domyślnych wartości dla opcjonalnych argumentów lub też użyjemy znaku * zamiast górnej granicy (oznacza brak górnej granicy) argumenty, którym nie zostaną nadane domyślne wartości, stają się puste, bez żadnej wartości. Aby określić ilość przekazanych argumentów do makra można użyć dyrektywy preprocesora %0 np.:
%macro policz 1-*
mov eax, %0
%endif
W powyższym przykładzie do rejestru eax kopiowana jest ilość przekazanych argumentów.
Asemblacja warunkowa
edytujPodstawowe dyrektywy
edytujNajpospolitszymi dyrektywami asemblacji warunkowej, tj. włączaniu pewnego kodu do asemblacji jedynie, gdy zostanie spełniony konkretny warunek jest ciąg dyrektyw: %if-%elif-%else-%endif, który stosujemy wg schematu:
%if warunek
; kod dołączany do asemblacji, tylko gdy warunek jest spełniony
%elif warunek2
; kod dołączany do asemblacji, tylko gdy wszystkie powyższe warunki
; nie zostaną spełnione (w tym przypadku jedynie warunek) a warunek2 zostanie
%else
; kod dołączany do asemblacji, tylko gdy wszystkie powyższe warunki nie zostaną spełnione
%endif
Oczywiście ilość dyrektyw %elif w całej konstrukcji jest zupełnie dowolna, zaś %else występuje tu zupełnie opcjonalnie. W miejsce warunków możemy wpisywać równania/nierówności z użyciem operatorów =, <, >, <=, >=, <> (nierówność), && (logiczne "i", ang. AND), || (logiczne "lub", ang. OR), ^^ (logiczne XOR). Dostępne są również operatory znane z C: == (równoznaczny z =) oraz != (równoznaczny z <>). Oto przykład obrazujący użycie tych dyrektyw:
%assign stala 2
...
%if stala = 1
push eax
%elif stala = 2
push ebx
%elif stala = 3
push ecx
%else
push edx
%endif
Cały powyższy kod zostanie zamieniony w tym przypadku na push ebx. Jeśli zmienimy wartość naszej stałej wstawiony zostanie również inny kod, zależnie od jej wartości.
%error
edytujDyrektywa %error sama w sobie nie ma nic wspólnego z asemblacją warunkową, gdyż asembler po napotkaniu tej dyrektywy przerywa asemblację i wyrzuca błąd (ang. error), który podamy zaraz za nią. Opis tej dyrektywy znajduje się jednak w tym podrozdziale, gdyż jej użycie ma sens, tylko w połączeniu z dyrektywami warunkowymi preprocesora. Stosowny przykład użycia tej dyrektywy znajduje się poniżej, w kolejnym podrozdziale.
Wariacje %if
edytuj%ifdef
edytujSprawdza czy zdefiniowane zostało jednoliniowe makro/stała o podanej nazwie. Jeśli tak, kod zostanie dołączony do asemblacji.
%define WERSJA_ROZSZERZONA_PROGRAMU
...
%ifdef WERSJA_ROZSZERZONA_PROGRAMU
; kod dołączany, tylko gdy WERSJA_ROZSZERZONA_PROGRAMU została zdefiniowana (w tym przypadku tak)
%endif
Istnieje również przeciwieństwo tej dyrektywy - %ifndef, która dołącza kod do asemblacji, gdy makro o podanej nazwie nie zostało zdefiniowane.
%ifmacro
edytujDołącza dany kod do asemblacji, tylko gdy zdefiniowane zostało wieloliniowe makro o podanej nazwie i konkretnej liczbie argumentów.
%macro licz 2
or %1, %2
shl %1, %2
%endmacro
...
%ifmacro licz 2
%error istnieje już takie makro!!!
%else
%macro licz 2
or %1, %2
shr %1, %2
%endmacro
%endif
W powyższym kodzie przed zdefiniowaniem makra sprawdzana jest dostępność nazwy dla niego. Jeśli istnieje już makro o takiej nazwie asembler zwróci błąd "istnieje już takie makro!!!". Dyrektywa %ifmacro umożliwia podawanie przedziału zamiast konkretnej liczby argumentów. Tak jak w poprzednim przypadku, ta dyrektywa ma również swoje przeciwieństwo - %ifnmacro działające w odwrotny sposób.
Pozostałe dyrektywy preprocesora
edytujPętle
edytujDo tworzenia pętli możemy posłużyć się dyrektywą preprocesora %rep-%endrep. Stosujemy ją wg poniższego schematu:
%rep ilość_powtórzeń
; powtarzany kod
%endrep
A oto i przykład:
%assign indeks 0
%rep 100
xor [edx+indeks], [edx+indeks]
%assign indeks indeks+1
%endrep
Powyższy przykład wypełnia 100-bajtowy obszar pamięci zerami. Przy każdej iteracji (powtórzeniu) pętli zmienna preprocesora indeks jest zwiększana o 1. Wewnątrz pętl możemy korzystać dodatkowo z dyrektywy %exitrep, która przerywa jej wykonanie (korzystanie z niej ma sens jedynie w połączeniu z warunkami preprocesora).